commit 405c1642632f3603d1067c071e4433438de3a3d9 Author: Jérôme Launay Date: Fri Dec 11 13:11:48 2015 +0100 [Add] commit initial reprise du code diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba1f176 --- /dev/null +++ b/.gitignore @@ -0,0 +1,103 @@ +#### joe made this: http://goel.io/joe + +#####=== Python ===##### + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo +*.pot + +# Django stuff: +*.log + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +#####=== Vim ===##### +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist +*~ + +#####=== Emacs ===##### +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ + +# pas de zip +*.zip diff --git a/MesCigares/MesCigares.py b/MesCigares/MesCigares.py new file mode 100755 index 0000000..9f0bb11 --- /dev/null +++ b/MesCigares/MesCigares.py @@ -0,0 +1,1070 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# pylint: disable-msg=C0103 +import sys, os +import sqlite3 +import locale +import configparser + +try: + from gi.repository import Gtk as gtk, GdkPixbuf +except: + print("GTK (PyGI) n'est pas installé") + sys.exit(1) + +__BDD__ = "bdd/main.db" +__CONFDIR__ = os.path.dirname(os.path.realpath("MesCigares.py")) +__CONFIGFILE__ = "conf/mescigares.conf" + +class MesCigares: + + def on_window_main_destroy(self, widget, data=None): + db.close() + gtk.main_quit() + + def on_gtk_quit_activate(self, menuitem, data=None): + db.close() + gtk.main_quit() + + def on_gtk_about_activate(self, menuitem, data=None): + self.aboutdialog.run() + self.aboutdialog.hide() + + # Mise à jour de la quantité en fonction du cigare choisié dans la liste déroulante + def on_cbchoixcigqte_changed(self,object,data=None): + item = self.cbchoixcigqte.get_active() + + # Si aucune séléction, pas besoin de traitement. + if item == -1: + return + + model = self.cbchoixcigqte.get_model() + idc = model[item] + + cursor = db.cursor() + qte = cursor.execute("Select quantite from stocks where id_cigare = '{0}'".format(idc[0])).fetchone()[0] + cursor.close() + + self.scaleqte.set_value(int(qte)) + + # Permet d'alimenter la treeview avec la liste des cigares + def loadtreeview(self): + if len(self.table) != 0: + self.table.clear() + + cursor = db.cursor() + cursor.execute('Select c.designation, m.libelle, mo.module, s.quantite,c.id from cigares c inner join modules mo on mo.id = c.module inner join marques m on m.id = c.marque inner join stocks s on s.id_cigare = c.id') + rs = cursor.fetchall() + + cursor.execute("select max(d.note), avg(d.note),id_cigare from degustation d group by id_cigare") + rs2 = cursor.fetchall() + + cursor.execute("select min(prix),id_cigare from achats group by id_cigare") + rs3 = cursor.fetchall() + + cursor.execute("select commentaires, id_cigare from degustation group by id_cigare") + rs4 = cursor.fetchall() + + for item in rs: + iter = self.table.append() + self.table.set(iter, 0, item[0], 1, item[1], 2, item[2], 3, item[3], 4, item[4]) + # Il n'y a pas de outer join en sqlite du coup je suis obligé d'alimenter les notes à part + for item2 in rs2: + itr = self.table.get_iter_first () + while ( itr != None ): + # itr, 4 = id de la table cigare (colonne 4 invisible), item2[2] = id_cigare table degustation + if self.table.get_value (itr, 4) == item2[2]: + #print(self.table.get_value (itr, 4)) + self.table.set(itr, 5, item2[0], 6, item2[1]) + itr= self.table.iter_next(itr) + for item3 in rs3: + itr = self.table.get_iter_first () + while ( itr != None ): + # itr, 4 = id de la table cigare (colonne 4 invisible), item3[1] = id_cigare table achats + if self.table.get_value (itr, 4) == item3[1]: + # .2f pour 2 decimales + it = '%.2f' % item3[0] + self.table.set(itr, 7, it) + itr= self.table.iter_next(itr) + for item4 in rs4: + itr = self.table.get_iter_first () + while ( itr != None ): + # itr, 4 = id de la table cigare (colonne 4 invisible), item4[1] = id_cigare table achats + if self.table.get_value (itr, 4) == item4[1]: + self.table.set(itr, 8, item4[0]) + itr= self.table.iter_next(itr) + + # On calcule le nombre de cigares et le prix + nbcigares = 0 + montant = 0 + itr = self.table.get_iter_first () + while ( itr != None ): + nbcigares += int(self.table.get_value(itr, 3)) + if self.table.get_value(itr, 7) is not None: + montant += float(self.table.get_value(itr, 7)) * int(self.table.get_value(itr, 3)) + itr= self.table.iter_next(itr) + montant = '%.2f' % montant + + # On calcule le prix total + montant_total = 0 + cursor.execute("select prix, quantite from achats") + rs = cursor.fetchall() + + for item in rs: + montant_total += (item[0] * item[1]) + montant_total = '%.2f' % montant_total + + cursor.close() + # On affiche le nombre de résultats trouvés sur la barre de statut + if nbcigares == 0: + self.statusbar.push(self.context_id, "Aucun cigare dans la base") + else: + self.statusbar.push(self.context_id, str(nbcigares) + " cigares dans la base pour un montant de " + str(montant) + "€ - (" + str(montant_total) + "€ depuis le début)") + + # Permet l'affichage de la photo du cigare séléctionné dans la zone rétractable + def affiche_preview(self): + + cursor = db.cursor() + photo = cursor.execute("select photo from photos where id_cigare = '{0}'".format(self.treeview_selection[4])) + + try: + photo = cursor.fetchone()[0] + except TypeError: + photo = None + cursor.close() + # Si aucune photo n'existe pour ce cigare + if(photo is None): + photo = __CONFDIR__ + "/images/no_photo.jpg" + self.image_preview.set_from_file(photo) + else: + if os.path.exists(photo): + pixbuf = GdkPixbuf.Pixbuf().new_from_file(photo) + # Si l'image est plus grande que la fenêtre on re-dimensionne + new_size = self.window.get_size()[0] - 20 + if(pixbuf.get_width() > new_size): + w, h = pixbuf.get_width(), pixbuf.get_height() + pixbuf = pixbuf.scale_simple( new_size, new_size * h / w, GdkPixbuf.InterpType.HYPER) + # Si la largeur de l'image est plus petite que la hauteur on fait pivoter + if(pixbuf.get_width() < pixbuf.get_height()): + pixbuf = pixbuf.rotate_simple(270) + # Si la hauteur est dépasse la moitié de la fenêtre principale, on re-dimensionne + if(pixbuf.get_height() > self.window.get_size()[1]/2): + w, h = pixbuf.get_width(), pixbuf.get_height() + pixbuf = pixbuf.scale_simple( (w * self.window.get_size()[1]/2)/h, self.window.get_size()[1]/2 , GdkPixbuf.InterpType.HYPER) + self.image_preview.set_from_pixbuf(pixbuf) + else: + # Si la photo n'existe plus ou a été renommée + photo = __CONFDIR__ + "/images/bad_photo.jpg" + self.image_preview.set_from_file(photo) + self.imgpath = photo + + # Si la fenêtre principale change de taille on re-dimensionne le preview aussi + def on_window_main_check_resize(self, widget): + #print(self.window.get_size()[1],self.window_last_h) + if (( abs(self.window_last_h - self.window.get_size()[1]) > 40) | ( abs(self.window_last_w - self.window.get_size()[0]) > 5) ): + self.window_last_h = self.window.get_size()[1] + self.window_last_w = self.window.get_size()[0] + + if not os.path.exists(self.imgpath): + return + pixbuf = GdkPixbuf.Pixbuf().new_from_file(self.imgpath) + # Si l'image est plus grande que la fenêtre on re-dimensionne + new_size = self.window.get_size()[0] - 20 + if(pixbuf.get_width() > new_size): + w, h = pixbuf.get_width(), pixbuf.get_height() + pixbuf = pixbuf.scale_simple( new_size, new_size * h / w, GdkPixbuf.InterpType.HYPER) + # Si la largeur de l'image est plus petite que la hauteur on fait pivoter + if(pixbuf.get_width() < pixbuf.get_height()): + pixbuf = pixbuf.rotate_simple(270) + # Si la hauteur est dépasse la moitié de la fenêtre principale, on re-dimensionne + if(pixbuf.get_height() > self.window.get_size()[1]/2): + w, h = pixbuf.get_width(), pixbuf.get_height() + pixbuf = pixbuf.scale_simple( (w * self.window.get_size()[1]/2)/h, self.window.get_size()[1]/2 , GdkPixbuf.InterpType.HYPER) + self.image_preview.set_from_pixbuf(pixbuf) + + # Dès qu'une ligne est séléctionnée sur la treeview on enregistre les valeurs + def on_treeview_cigares_row_activated(self, widget): + selection = self.tree.get_selection() + (model, iter) = selection.get_selected() + if iter is not None: + self.treeview_selection.clear() + self.treeview_selection.append(model.get_value(iter, 0)) + self.treeview_selection.append(model.get_value(iter, 1)) + self.treeview_selection.append(model.get_value(iter, 2)) + self.treeview_selection.append(model.get_value(iter, 3)) + self.treeview_selection.append(model.get_value(iter, 4)) + self.treeview_selection.append(model.get_value(iter, 5)) + self.treeview_selection.append(model.get_value(iter, 6)) + # for i in range(0,6): + # print(model.get_value(iter, i)) + # print("\n") + # Et on met à jour l'affichage du preview + self.affiche_preview() + + # Changer de base de données + def on_bdd_change_activate(self, widget): + filter = gtk.FileFilter() + filter.set_name("Bases sqlite") + filter.add_mime_type("application/octet-stream") + filter.add_pattern("*.db") + self.filechooser_bdd.add_filter(filter) + self.filechooser_bdd.set_current_folder(__CONFDIR__ + "/bdd/") + self.filechooser_bdd.run() + self.loadtreeview() + self.filechooser_bdd.hide() + + # Double clic sur le sélécteur de fichiers pour choisir la bdd + def on_filechooser_bdd_file_activated(self, widget): + if not os.path.exists(self.filechooser_bdd.get_filename()): + createdb(self.filechooser_bdd.get_filename()) + setconfig(self.filechooser_bdd.get_filename()) + global db + db.close() + db = sqlite3.connect(self.filechooser_bdd.get_filename()) + self.loadtreeview() + self.filechooser_bdd.hide() + + # Clic sur le bouton OK du sélécteur de fichiers pour choisir l'image + def on_btchooseokbdd_clicked(self, widget): + if not os.path.exists(self.filechooser_bdd.get_filename()): + createdb(self.filechooser_bdd.get_filename()) + # changer la conf + setconfig(self.filechooser_bdd.get_filename()) + global db + db.close() + db = sqlite3.connect(self.filechooser_bdd.get_filename()) + self.loadtreeview() + + # Ajouter une image pour un cigare + def on_inserer_image_activate(self, widget): + self.filechooser_image.run() + self.filechooser_image.hide() + + # Modifier le commentaire d'un cigare + def on_modifier_commentaire_activate(self, widget): + cursor = db.cursor() + # On regarde récupère le commentaire + commentaire = cursor.execute("select commentaires from degustation where id_cigare = '{0}'".format(self.treeview_selection[4])).fetchone() + # si nb = 0 on insère sinon on update la table photos + db.commit() + cursor.close() + + try: + commentaire = commentaire[0] + except TypeError: + # Afficher un message d'infos pour alerter qu'il faut d'abord faire une dégustation + self.msgdialog.format_secondary_text("Vous devez d'abord ajouter une dégustation pour ce cigare pour pouvoir ajouter un commentaire") + self.msgdialog.run() + self.msgdialog.hide() + return + # On affiche le commentaire + self.txtview_commentaire_modif.get_buffer().set_text(commentaire) + # On affiche la fenêtre de modification du commentaire + self.dialog_commentaires.run() + self.dialog_commentaires.hide() + # On recharge la treeview + self.loadtreeview() + + # Valider la modification du commentaire + def on_bt_commentaires_modif_clicked(self, widget): + # modifier + textbuffer = self.txtview_commentaire_modif.get_buffer() + start = textbuffer.get_start_iter() + end = textbuffer.get_end_iter() + # On récupère le commentaire et on échape les quote pour l'insert SQL + textlines = textbuffer.get_text(start, end, include_hidden_chars=False).replace("'","''") + # On met à jour la base de données + cursor = db.cursor() + sql = "update degustation set commentaires = '{0}' where id_cigare = '{1}'".format(textlines, self.treeview_selection[4]) + print(sql) + cursor.execute(sql) + db.commit() + cursor.close() + # Afficher un message d'infos pour confirmer + self.msgdialog.format_secondary_text("Commentaire modifié pour {0}.".format(self.treeview_selection[1])) + self.msgdialog.run() + self.msgdialog.hide() + + # Double clic sur le sélécteur de fichiers pour choisir l'image + def on_filechooser_image_file_activated(self, widget): + self.addimage(self.filechooser_image.get_filename()) + self.filechooser_image.hide() + + # Clic sur le bouton OK du sélécteur de fichiers pour choisir l'image + def on_btchooseok_clicked(self, widget): + self.addimage(self.filechooser_image.get_filename()) + + # Mettre à jour la BDD avec le chemin de l'image choisie + def addimage(self, path): + + cursor = db.cursor() + # On regarde s'il y a déjà une photo pour le cigare séléctionné + nb = cursor.execute("select count(*) from photos where id_cigare = '{0}'".format(self.treeview_selection[4])).fetchone()[0] + # si nb = 0 on insère sinon on update la table photos + if ( nb == 0): + cursor.execute("insert into photos (id_cigare, photo) values('{0}','{1}')".format(self.treeview_selection[4], path)) + else: + cursor.execute("update photos set photo = '{0}' where id_cigare='{1}'".format(path, self.treeview_selection[4])) + db.commit() + cursor.close() + + # Clic droit sur un cigare dans la treeview + def on_treeview_cigares_button_press_event(self, treeview, event): + x = int(event.x) + y = int(event.y) + if event.button == 3: # Clic droit + pthinfo = self.tree.get_path_at_pos(x, y) + if pthinfo is not None: + path, col, cellx, celly = pthinfo + self.tree.grab_focus() + self.tree.set_cursor( path, col, 0) + # On affiche le popup + self.popup.show_all(); + self.popup.popup(None, None, None, None, event.button, event.time) + + # Mise à jour du listestore si édition d'une cellule + def cell_edited_callback(self, cellule, path, new_text, col, data=None): + iter = self.table.get_iter(path) + if col == 3: + self.table.set_value(iter, col, int(new_text)) + else: + self.table.set_value(iter, col, new_text) + self.update_db(tuple(self.treeview_selection),tuple(self.table[path])) + + # Mise à jour de la BDD si changement dans un cellule + def update_db(self, old_value, new_value): + cursor = db.cursor() + # update cigares.designation + if (new_value[0] != old_value[0]): + cursor.execute("update cigares set designation = '{0}' where id = '{1}'".format(new_value[0].replace('"','\"'),old_value[4])) + + # update quantité dans table stock + if (new_value[3] != old_value[3]): + cursor.execute("update stocks set quantite = '{0}' where id_cigare='{1}'".format(new_value[3], old_value[4])) + + db.commit() + cursor.close() + + # recharger la treeview + self.loadtreeview() + + # Affichage de la fenêtre d'ajout d'un cigare + def on_gtk_new_activate(self, widget): + # On commence par vider le liststore_marques (2è recherche) + if len(self.tablemarques) != 0: + self.tablemarques.clear() + if len(self.tablemodules) != 0: + self.tablemodules.clear() + + + cursor = db.cursor() + cursor.execute('Select id, libelle from marques order by libelle') + rs = cursor.fetchall() + # On alimente la combobox des marques + for item in rs: + iter = self.tablemarques.append() + self.tablemarques.set(iter, 0, item[0], 1, item[1]) + + cursor.execute('Select id, module from modules order by calibre, id') + rs = cursor.fetchall() + # Et celle des modules + for item in rs: + iter = self.tablemodules.append() + self.tablemodules.set(iter, 0, item[0], 1, item[1]) + + cursor.close() + self.dialog_add_cigare.run() + self.dialog_add_cigare.hide() + + # Validation de l'ajout d'un nouveau cigare + def on_btadd_clicked(self,widget): + index = self.cbmarques.get_active() + model = self.cbmarques.get_model() + itemmarque = model[index] + # item[0] = id à insérer dans champ marque de la table cigares + #print (str(itemmarque[0]), str(itemmarque[1])) + + index = self.cbmodules.get_active() + model = self.cbmodules.get_model() + itemmodel = model[index] + # item[0] = id à insérer dans champ marque de la table cigares + #print (str(itemmodel[0]), str(itemmodel[1])) + # Il faudra faire une requete sur cigares pour obtenir le dernier id et faire + # + 1 : Select max(id) from cigares + # table stocks : id_cigare = max id + 1 + #print (str(self.entry_nom_cigare.get_text())) + + + cursor = db.cursor() + cursor.execute('Select max(id) from cigares') + try: + maxid = cursor.fetchone()[0] + 1 + except TypeError: + maxid = 1 + cursor.execute("insert into stocks (id_cigare, quantite) values ('{0}','{1}')".format(int(maxid),0)) + cursor.execute("insert into cigares (designation, marque, module) values ('{0}','{1}', '{2}')".format(str(self.entry_nom_cigare.get_text()), int(itemmarque[0]),int(itemmodel[0]))) + db.commit() + cursor.close() + + # On recharge le treeview + self.loadtreeview() + + # Affichage de la fenêtre de gestion des achats + def on_gtk_achats_activate(self, widget): + # On commence par vider le listestore_liste_cigares (2è recherche) + if len(self.tablechoixcig) != 0: + self.tablechoixcig.clear() + + + cursor = db.cursor() + cursor.execute('Select c.id, c.designation from cigares c order by c.designation asc') + rs = cursor.fetchall() + for item in rs: + iter = self.tablechoixcig.append() + self.tablechoixcig.set(iter, 0, item[0], 1, item[1]) + cursor.close() + # On affiche la fenêtre de gestion des achats + self.dialog_achats.run() + self.dialog_achats.hide() + + # Validation des achats + def on_bt_valid_achats_clicked(self, widget): + # On récupère le cigare choisi dans la liste déroulante + # [0] id_cigare ~ [1] libelle + index = self.cb_liste_cigares_achats.get_active() + itemchoixcig = self.tablechoixcig[index] + + id_cigare = itemchoixcig[0] + qte_achat = int(self.scale_qte_achats.get_value()) + prix_achat = self.entry_prix_achats.get_text().replace(",",".") + date_achat = "{0}/{1}/{2}".format(self.calendar_date_achats.get_date()[2], self.calendar_date_achats.get_date()[1] ,self.calendar_date_achats.get_date()[0]) + + # Insert des achats en base de données + + cursor = db.cursor() + cursor.execute("insert into achats (id_cigare, date, prix, quantite) values ('{0}','{1}','{2}','{3}')".format(id_cigare, date_achat, prix_achat, qte_achat)) + # ajout dans le stock + cursor.execute("update stocks set quantite = quantite + {1} where id = {0}".format(id_cigare, qte_achat)) + + db.commit() + cursor.close() + + # Afficher un message d'infos pour confirmer + self.msgdialog.format_secondary_text("Achats ajoutés avec succés pour {0}".format(itemchoixcig[1])) + self.msgdialog.run() + self.msgdialog.hide() + + # On recharge la liste des cigares + self.loadtreeview() + + # Affichage de la fenêtre de gestion des stocks + def on_gtk_stocks_activate(self, menuitem, data=None): + # On commence par vider le listestore_liste_cigares (2è recherche) + if len(self.tablechoixcig) != 0: + self.tablechoixcig.clear() + + + cursor = db.cursor() + cursor.execute('Select c.id, c.designation from cigares c order by c.designation asc') + rs = cursor.fetchall() + for item in rs: + iter = self.tablechoixcig.append() + self.tablechoixcig.set(iter, 0, item[0], 1, item[1]) + cursor.close() + + self.dialog_stocks.run() + self.dialog_stocks.hide() + + # Validation des stocks + def on_btstockvalid_clicked(self, value): + item = self.cbchoixcigqte.get_active() + model = self.cbchoixcigqte.get_model() + idc = model[item] + + + cursor = db.cursor() + cursor.execute("Update stocks set quantite = '{0}' where id_cigare = '{1}'".format(int(self.scaleqte.get_value()), idc[0])) + db.commit() + cursor.close() + # affichage message maj ok pour qte + self.msgdialog.format_secondary_text("Quantitée mise à jour pour {0}.\nVous avez maintenant {1} exemplaire(s)".format(idc[1], int(self.scaleqte.get_value()))) + self.msgdialog.run() + self.msgdialog.hide() + # On recharge la treeview principale + self.loadtreeview() + + # Affichage de la fenêtre de dégustation + def on_gtk_degustation_activate(self,menuitem, data=None): + if len(self.tablechoixcigare) != 0: + self.tablechoixcigare.clear() + + cursor = db.cursor() + cursor.execute('Select c.id, c.designation from cigares c where c.id not in (select s.id_cigare from stocks s where s.quantite = 0) order by c.designation asc') + rs = cursor.fetchall() + for item in rs: + iter = self.tablechoixcigare.append() + self.tablechoixcigare.set(iter, 0, item[0], 1, item[1]) + + cursor.close() + self.window_degustation.run() + self.window_degustation.hide() + + # Validation d'une dégusation + def on_btvalid_clicked(self, widget): + + # Formatage de la date du calendar au format %d/%m/%Y + DateDegustation = "{0}/{1}/{2}".format(self.DegustationDate.get_date()[2], self.DegustationDate.get_date()[1],self.DegustationDate.get_date()[0]) + + textbuffer = self.txtview_commentaire.get_buffer() + start = textbuffer.get_start_iter() + end = textbuffer.get_end_iter() + # On récupère le commentaire et on échape les quote pour l'insert SQL + textlines = textbuffer.get_text(start, end, include_hidden_chars=False).replace("'","''") + + index = self.cbchoixcigare.get_active() + itemchoixcig = self.tablechoixcigare[index] + + note = int(self.scaleq1.get_value()) + int(self.scaleq2.get_value())+int(self.scaleq3.get_value())+int(self.scaleq4.get_value())+int(self.scaleq5.get_value())+int(self.scaleq6.get_value())+int(self.scaleq7.get_value())+int(self.scaleq8.get_value())+int(self.scaleq9.get_value())+int(self.scaleq10.get_value())+int(self.scaleq11.get_value())+int(self.scaleq12.get_value())+int(self.scaleq13.get_value()) + + + cursor = db.cursor() + cursor.execute("insert into degustation (id_cigare,date,commentaires,q1,q2,q3,q4,q5,q6,q7,q8,q9,q10,q11,q12,q13,note) values ('{0}','{1}','{2}','{3}','{4}','{5}','{6}','{7}','{8}','{9}','{10}','{11}','{12}','{13}','{14}','{15}','{16}')".format(itemchoixcig[0],DateDegustation ,textlines,int(self.scaleq1.get_value()),int(self.scaleq2.get_value()),int(self.scaleq3.get_value()),int(self.scaleq4.get_value()),int(self.scaleq5.get_value()),int(self.scaleq6.get_value()),int(self.scaleq7.get_value()),int(self.scaleq8.get_value()),int(self.scaleq9.get_value()),int(self.scaleq10.get_value()),int(self.scaleq11.get_value()),int(self.scaleq12.get_value()),int(self.scaleq13.get_value()),note)) + # - 1 sur le stock + cursor.execute("update stocks set quantite = quantite - 1 where id = {0}".format(itemchoixcig[0])) + db.commit() + cursor.close() + + # TODO ajouter une checkbox sur le formulaire pour choisir si retirer 1 du stock ou non + self.msgdialog.format_secondary_text("Note de dégustation ajoutée avec succès pour : {0}.\n1 cigare retiré du stock.".format(itemchoixcig[1])) + self.msgdialog.run() + self.msgdialog.hide() + + # On recharge la treeview + self.loadtreeview() + + # Affichage de la fenêtre des cigares à tester + def on_gtk_wish_activate(self, menuitem, data=None): + # On commence par vider le listestore_wishes (2è recherche) + if len(self.liststore_wishes) != 0: + self.liststore_wishes.clear() + + cursor = db.cursor() + cursor.execute('select nom, provenance,raison,source,id from tester order by id') + rs = cursor.fetchall() + for item in rs: + iter = self.liststore_wishes.append() + self.liststore_wishes.set(iter, 0, item[0], 1, item[1], 2, item[2], 3, item[3], 4, item[4]) + cursor.close() + + self.window_wishes.resize(self.window.get_size()[0], self.window.get_size()[1]) + + self.window_wishes.show() + + # Quitter la fenêtre des cigares à tester + def on_window_wishes_delete_event(self, widget, data=None): + self.window_wishes.hide() + return True + + # Affichage de l'assistant d'aide pour la documentation + def on_gtk_how_to_activate(self, widget): + self.doc.show() + #self.doc.hide() + + def on_lbl_intro_activate_link(self, widget): + self.doc.set_forward_page_func(page_func=None, data=None) + + # Quitter la fenêtre d'assistant + def on_assistant_delete_event(self, widget, data=None): + self.doc.hide() + return True + + # Affichage de la fenêtre d'ajout d'un cigare à tester + def on_bt_add_wish_clicked(self, widget): + self.dialog_add_wish.run() + self.dialog_add_wish.hide() + + # Valider l'ajout d'un cigare à tester + def on_bt_valid_add_wish_clicked(self, widget): + print("toto") + + # Copier l'url dans le presse papier au changement de ligne sur la treeview des cigares à tester + def on_treeview_wishes_cursor_changed(self, widget): + try: + from lib import pyperclip + except: + print('pyperclip not available') + return False + + selection = self.treeview_wishes.get_selection() + (model, iter) = selection.get_selected() + if iter is not None: + # On copie dans le presse papier + pyperclip.copy(model.get_value(iter, 3)) + + # TODO: ajouter un popup pour supprimer au clic droit + + def __init__(self): + self.gladefile = "ui/ui.glade" + self.builder = gtk.Builder() + self.builder.add_from_file(self.gladefile) + self.builder.connect_signals(self) + self.window = self.builder.get_object("window_main") + self.aboutdialog = self.builder.get_object("aboutdialog") + self.statusbar = self.builder.get_object("window_main_statusbar") + self.context_id = self.statusbar.get_context_id("exemple") + self.table = self.builder.get_object("liststore_treeview_cigares") + self.dialog_add_cigare = self.builder.get_object("dialog_add_cigare") + self.dialog_commentaires = self.builder.get_object("dialog_commentaires") + self.txtview_commentaire_modif = self.builder.get_object("txtview_commentaire_modif") + self.cbmarques = self.builder.get_object("cbmarques") + self.cbchoixcigare = self.builder.get_object("cbchoixcigare") + self.tablechoixcigare = self.builder.get_object("liststore_cigares_a_noter") + self.scaleq1 = self.builder.get_object("scaleq1") + self.scaleq2 = self.builder.get_object("scaleq2") + self.scaleq3 = self.builder.get_object("scaleq3") + self.scaleq4 = self.builder.get_object("scaleq4") + self.scaleq5 = self.builder.get_object("scaleq5") + self.scaleq6 = self.builder.get_object("scaleq6") + self.scaleq7 = self.builder.get_object("scaleq7") + self.scaleq8 = self.builder.get_object("scaleq8") + self.scaleq9 = self.builder.get_object("scaleq9") + self.scaleq10 = self.builder.get_object("scaleq10") + self.scaleq11 = self.builder.get_object("scaleq11") + self.scaleq12 = self.builder.get_object("scaleq12") + self.scaleq13 = self.builder.get_object("scaleq13") + self.tablemarques = self.builder.get_object("liststore_marques") + self.cbmodules = self.builder.get_object("cbmodules") + self.tablemodules = self.builder.get_object("liststore_modules") + self.entry_nom_cigare = self.builder.get_object("entry_nom_cigare") + self.window_degustation = self.builder.get_object("window_degustation") + self.txtview_commentaire = self.builder.get_object("txtview_commentaire") + self.popup = self.builder.get_object("popup") + self.filechooser_image = self.builder.get_object("filechooser_image") + self.filechooser_bdd = self.builder.get_object("filechooser_bdd") + self.msgdialog = self.builder.get_object("msgdialog") + + self.msginfo = self.builder.get_object("lblmsginfo") + self.tablechoixcig = self.builder.get_object("listestore_liste_cigares") + self.cbchoixcigqte = self.builder.get_object("cbchoixcigqte") + self.lblqte = self.builder.get_object("lblqte") + self.dialog_stocks = self.builder.get_object("dialog_stocks") + self.scaleqte = self.builder.get_object("scaleqte") + self.adjqte = self.builder.get_object("adjqte") + # Assistant pour la documentation de l'appli + self.doc = self.builder.get_object("assistant") + # Image pour afficher le preview dans la zone rétractable + self.image_preview = self.builder.get_object("image_preview") + # Date pour la dégustation + self.DegustationDate = self.builder.get_object("calendar_degustation") + # Fenêtre de gestion des achats + self.dialog_achats = self.builder.get_object("dialog_achats") + # Combo pour la liste des cigares (achats) - utilise aussi listestore_choix_cigare_qte + self.cb_liste_cigares_achats = self.builder.get_object("cb_liste_cigares_achats") + # Scale pour la quantité achetée + self.scale_qte_achats = self.builder.get_object("scale_qte_achats") + # Entry pour le prix d'achat + self.entry_prix_achats = self.builder.get_object("entry_prix_achats") + # Calendar pour la date d'achat + self.calendar_date_achats = self.builder.get_object("calendar_date_achats") + # Fenêtre pour la liste des cigares à tester + self.window_wishes = self.builder.get_object("window_wishes") + # Fenêtre d'ajout d'un cigare à tester + self.dialog_add_wish = self.builder.get_object("dialog_add_wish") + # Treeview wishes + self.treeview_wishes = self.builder.get_object("treeview_wishes") + # Liststore associé à treeview wishes + self.liststore_wishes = self.builder.get_object("liststore_wishes") + + # Ligne séléctionnée au changement sur la treeview + self.treeview_selection = [] + # Chemin de l'image preview + self.imgpath = "" + self.window_last_h = self.window.get_size()[1] + self.window_last_w = self.window.get_size()[0] + + # Constuire l'entête du treeview + self.cell1 = gtk.CellRendererText() + self.cell2 = gtk.CellRendererText() + self.cell3 = gtk.CellRendererText() + self.cell4 = gtk.CellRendererText() + self.cell5 = gtk.CellRendererText() + self.cell6 = gtk.CellRendererText() + self.cell7 = gtk.CellRendererText() + self.cell8 = gtk.CellRendererText() + self.cell9 = gtk.CellRendererText() + + self.cell1.connect('edited', self.cell_edited_callback,0) + self.cell1.set_property('editable', True) + + self.tree = self.builder.get_object("treeview_cigares") + + self.column1 = gtk.TreeViewColumn('Nom', self.cell1, text=0) + self.column1.set_sort_column_id(0) + self.tree.append_column(self.column1) + + self.cell2.connect('edited', self.cell_edited_callback,1) + self.cell2.set_property('editable', False) + self.column2 = gtk.TreeViewColumn('Marque', self.cell2, text=1) + self.column2.set_sort_column_id(1) + self.tree.append_column(self.column2) + + self.cell3.connect('edited', self.cell_edited_callback,2) + self.cell3.set_property('editable', False) + self.column3 = gtk.TreeViewColumn('Module', self.cell3, text=2) + self.column3.set_sort_column_id(2) + self.tree.append_column(self.column3) + + self.cell4.connect('edited', self.cell_edited_callback,3) + self.cell4.set_property('editable', True) + self.column4 = gtk.TreeViewColumn('Quantitée', self.cell4, text=3) + self.column4.set_sort_column_id(3) + self.tree.append_column(self.column4) + + # colonne invisible pour l'id_cigare + self.cell5.set_property('visible',False) + self.column5 = gtk.TreeViewColumn('id', self.cell5, text=4) + self.column5.set_sort_column_id(4) + + self.cell6.set_property('visible',True) + self.column6 = gtk.TreeViewColumn('Meilleure note', self.cell6, text=5) + self.column6.set_sort_column_id(5) + self.tree.append_column(self.column6) + + self.cell7.set_property('visible',True) + self.column7 = gtk.TreeViewColumn('Note moyenne', self.cell7, text=6) + self.column7.set_sort_column_id(6) + self.tree.append_column(self.column7) + + self.cell8.set_property('visible',True) + #self.cell8.set_property('xalign',1.0) + self.column8 = gtk.TreeViewColumn('Prix (en €)', self.cell8, text=7) + self.column8.set_sort_column_id(7) + self.tree.append_column(self.column8) + + # colonne invisible pour le commentaire de dégustation + self.cell9.set_property('visible',False) + self.column9 = gtk.TreeViewColumn('commentaire', self.cell5, text=8) + self.column9.set_sort_column_id(8) + # On défini cette colonne comme colonne d'infobulle + self.tree.set_tooltip_column(8) + + # Utiliser gtkspellcheck dans la txtview des commentaires + # http://koehlma.github.io/projects/pygtkspellcheck.html + try: + from gtkspellcheck import SpellChecker + SpellChecker(self.txtview_commentaire, locale.getdefaultlocale()[0]) + SpellChecker(self.txtview_commentaire_modif, locale.getdefaultlocale()[0]) + except: + print("gtkspellcheck n'est pas installé") + pass + + self.window.show_all() + # On charge la liste des cigares + self.loadtreeview() + +def createdb(new_bdd): + db = sqlite3.connect(new_bdd) + cursor = db.cursor() + sql = 'create table achats' + \ + '(id INTEGER PRIMARY KEY, id_cigare INTEGER, date TEXT, prix FLOAT, quantite INTEGER)' + cursor.execute(sql) + sql = 'create table cigares' + \ + '(id INTEGER PRIMARY KEY, designation TEXT, marque INTEGER, module INTEGER)' + cursor.execute(sql) + sql = 'create table degustation' + \ + '(id INTEGER PRIMARY KEY, id_cigare INTEGER, date TEXT, note NUMERIC, commentaires TEXT, q1 INTEGER, q2 INTEGER, q3 INTEGER, q4 INTEGER, q5 INTEGER, q6 INTEGER, q7 INTEGER, q8 INTEGER, q9 INTEGER, q10 INTEGER, q11 INTEGER, q12 INTEGER, q13 INTEGER)' + cursor.execute(sql) + sql = 'create table marques' + \ + '(id INTEGER PRIMARY KEY, libelle TEXT, provenance TEXT)' + cursor.execute(sql) + sql = 'create table modules' + \ + '(id INTEGER PRIMARY KEY, module TEXT, calibre TEXT, diametre TEXT, longueur TEXT)' + cursor.execute(sql) + sql = 'create table photos' + \ + '(id INTEGER PRIMARY KEY, id_cigare INTEGER, photo TEXT)' + cursor.execute(sql) + sql = 'create table stocks' + \ + '(id INTEGER PRIMARY KEY, id_cigare INTEGER, quantite INTEGER)' + cursor.execute(sql) + sql = 'create table tester' + \ + '(id INTEGER PRIMARY KEY, nom TEXT, provenance TEXT, raison TEXT, source TEXT)' + cursor.execute(sql) + + # INSERT à mettre dans un fichier à part + sql = "INSERT INTO modules VALUES(1, 'Laguito N °3', '26', '10.32', '115')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(2, 'Carolina', '26', '10.32', '121')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(3, 'Panetela Larga', '28', '11.11', '175')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(4, 'Chico', '29', '11.51', '106')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(5, 'Entreacto', '30', '11.91', '100')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(6, 'Palmita', '32', '12.70', '152')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(7, 'Delicioso', '33', '13.10', '159')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(8, 'Palma', '33', '13.10', '170')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(9, 'Ninfa', '33', '13.10', '178')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(10, 'Panetela', '34', '13.49', '117')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(11, 'Placera', '34', '13.49', '125')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(12, 'Epicure', '35', '13.89', '110')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(13, 'Sport', '35', '13.89', '117')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(14, 'Conchita', '35', '13.89', '127')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(15, 'Carlota', '35', '13.89', '143')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(16, 'Cadete', '36', '14.29', '115')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(17, 'Seoane', '36', '14.29', '125')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(18, 'Veguerito', '36', '14.29', '127')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(19, 'Delicado Extra', '36', '14.29', '185')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(20, 'Trabuco', '38', '15.08', '110')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(21, 'Laguito N ° 2', '38', '15.08', '152')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(22, 'Parejo', '38', '15.08', '166')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(23, 'Delicado', '38', '15.08', '192')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(24, 'Laguito N ° 1', '38', '15.08', '192')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(25, 'Belvedere', '39', '15.48', '125')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(26, 'Perla', '40', '15.87', '102')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(27, 'Franciscano', '40', '15.87', '116')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(28, 'Coronita', '40', '15.87', '117')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(29, 'Standard', '40', '15.87', '123')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(30, 'Londres', '40', '15.87', '126')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(31, 'Petit Cetro', '40', '15.87', '129')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(32, 'Almuerzo', '40', '15.87', '130')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(33, 'Crema', '40', '15.87', '140')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(34, 'Laguito No.1', '40', '15.87', '192')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(35, 'Minuto', '42', '16.67', '110')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(36, 'Mareva', '42', '16.67', '129')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(37, 'Petit Corona', '42', '16.67', '127')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(38, 'Eminente', '42', '16.67', '132')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(39, 'Nacional', '42', '15.87', '134')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(40, 'Cosaco', '42', '16.67', '135')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(41, 'Corona', '42', '16.67', '140')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(42, 'Corona Grande', '42', '16.67', '155')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(43, 'Cervante', '42', '16.67', '165')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(44, 'Conserva', '43', '17.07', '145')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(45, 'Cazadore', '43', '17.07', '162')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(46, 'Dalia', '43', '17.07', '170')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(47, 'Francisco', '44', '17.46', '143')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(48, 'Corona Gorda', '46', '18.26', '143')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(49, 'Taco', '47', '18.65', '158')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(50, 'Julieta / Churchill', '47', '18.65', '178')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(51, 'Gran Corona', '47', '18.65', '235')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(52, 'Hermoso N ° 4', '49', '19.05', '127')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(53, 'Paco', '49', '19.45', '180')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(54, 'Double corona', '49', '19.45', '194')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(55, 'Robusto', '50', '19.84', '127')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(56, 'Gordito', '50', '19.84', '141')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(57, 'Campana', '52', '20.64', '140')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(58, 'Panetelas Extra', '37', '13.49', '127')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(59, 'Oficios (Corona)', '43', '17.07', '135')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(60, 'Majestic (Petit Corona)', '39', '15.87', '140')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(61, 'Figurados', '60', '20+', '150+')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(62, 'Torpedo', '52', '20.8', '125')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(63, 'short robusto', '60', '24', '102')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(64, 'Mini Panetella', '20', '8', '125')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(65, 'Indéfini', '0', '0', '0')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(66, 'Très petit corona', '40', '15.88', '102')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(67, 'Toro', '52', '20.8', '152')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(68, 'Prominente', '49', '19.45', '194')" + cursor.execute(sql) + + # MARQUES + sql = "INSERT INTO marques VALUES(1, 'Cohiba', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(2, 'Trinidad', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(3, 'Vegas Robaina', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(4, 'Montecristo', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(5, 'Cuaba', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(6, 'Romeo y Julieta', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(7, 'Partagas', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(8, 'San Cristobal de la Habana', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(9, 'Punch', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(10, 'Hoyo de Monterrey', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(11, 'Bolivar', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(12, 'La Gloria Cubana', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(13, 'H.Upmann', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(14, 'Fonseca', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(15, 'La Flor de Cano', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(16, 'Troya', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(17, 'Quintero', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(18, 'Los Statos de Luxe', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(19, 'Caney', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(20, 'Belinda', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(21, 'Cabanas', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(22, 'La Corona', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(23, 'José L.Piedra', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(24, 'Cumpay', 'Nicaragua')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(25, 'CAO', 'Nicaragua')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(26, 'Dunhill', 'Nicaragua')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(27, 'Nicarao', 'Nicaragua')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(28, 'Oliva', 'Nicaragua')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(29, 'Padron', 'Nicaragua')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(30, 'Flor de Copan', 'Honduras')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(31, 'Flor de Selva', 'Honduras')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(32, 'J. Cortès', 'Honduras')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(33, 'Zino', 'Honduras')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(34, 'Villa Zamorano', 'Honduras')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(35, 'Flor de Rafaël Gonzáles Márquez', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(36, 'Macanudo', 'République Dominicaine')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(37, 'Pléiades', 'Nicaragua')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(38, 'Toscano', 'Italie')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(39, 'Davidoff', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(40, 'Avo Uvezian', 'République Dominicaine')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(41, 'Don Pepin Garcia', 'Nicaragua')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(42, 'O Line', 'Nicaragua')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(43, 'Plasencia', 'Nicaragua')" + cursor.execute(sql) + + db.commit() + +def checkconfig(): + """Récupérer la configuration ou la créer""" + # Fichier de configuration + #configfile = os.path.expanduser("~/.config/cltwit.conf") + # On ouvre le fichier de conf + config = configparser.RawConfigParser() + try: + config.read(__CONFIGFILE__) + if config.has_option('MesCigares','bdd'): + bdd = config.get('MesCigares','bdd') + except: + pass + + # Si aucune conf + if not(config.has_option('MesCigares','bdd')): + bdd = __CONFDIR__ + "/bdd/main.db" + # écrire le fichier de conf avec les informations par defaut + try: + cfgfile = open(__CONFIGFILE__,'w') + if not(config.has_section('MesCigares')): + config.add_section('MesCigares') + config.set('MesCigares','bdd',bdd) + config.write(cfgfile) + except IOError: + pass + finally: + cfgfile.close + createdb(bdd) + return(bdd) + +def setconfig(new_bdd): + """Mettre à jour la configuration""" + config = configparser.RawConfigParser() + try: + cfgfile = open(__CONFIGFILE__,'w') + if not(config.has_section('MesCigares')): + config.add_section('MesCigares') + config.set('MesCigares','bdd',new_bdd) + config.write(cfgfile) + except IOError: + pass + finally: + cfgfile.close + +if __name__ == "__main__": + dbconf = checkconfig() + db = sqlite3.connect(dbconf) + + main = MesCigares() + gtk.main() diff --git a/MesCigares/bdd/JEROME.db b/MesCigares/bdd/JEROME.db new file mode 100644 index 0000000..f988301 Binary files /dev/null and b/MesCigares/bdd/JEROME.db differ diff --git a/MesCigares/bdd/JEROME_KO.db b/MesCigares/bdd/JEROME_KO.db new file mode 100644 index 0000000..52a06a0 Binary files /dev/null and b/MesCigares/bdd/JEROME_KO.db differ diff --git a/MesCigares/bdd/main.db b/MesCigares/bdd/main.db new file mode 100644 index 0000000..c372e8a Binary files /dev/null and b/MesCigares/bdd/main.db differ diff --git a/MesCigares/conf/mescigares.conf b/MesCigares/conf/mescigares.conf new file mode 100644 index 0000000..6c0365e --- /dev/null +++ b/MesCigares/conf/mescigares.conf @@ -0,0 +1,3 @@ +[MesCigares] +bdd = /home/jlaunay/Documents/Cigares/MesCigares/bdd/JEROME.db + diff --git a/MesCigares/images/MesCigares.ico b/MesCigares/images/MesCigares.ico new file mode 100644 index 0000000..91b2368 Binary files /dev/null and b/MesCigares/images/MesCigares.ico differ diff --git a/MesCigares/images/bad_photo.jpg b/MesCigares/images/bad_photo.jpg new file mode 100644 index 0000000..d793168 Binary files /dev/null and b/MesCigares/images/bad_photo.jpg differ diff --git a/MesCigares/images/no_photo.jpg b/MesCigares/images/no_photo.jpg new file mode 100644 index 0000000..28956ca Binary files /dev/null and b/MesCigares/images/no_photo.jpg differ diff --git a/MesCigares/ui/ui.glade b/MesCigares/ui/ui.glade new file mode 100644 index 0000000..0e5b3d4 --- /dev/null +++ b/MesCigares/ui/ui.glade @@ -0,0 +1,2550 @@ + + + + + False + 5 + False + True + center-on-parent + dialog + window_main + MesCigares + 0.5 + Copyright © Jérôme Launay + Logiciel de gestion de cave à cigares + http://www.projet-libre.org + GPL + Jérôme Launay + True + gpl-3-0 + + + False + vertical + 2 + + + False + end + + + False + True + end + 0 + + + + + + + + + + 50 + 1 + 1 + 10 + + + 1 + 4 + 1 + 1 + 1 + + + 1 + 3 + 1 + 1 + 1 + + + 1 + 3 + 1 + 1 + 1 + + + 1 + 20 + 1 + 1 + 1 + + + 1 + 3 + 1 + 1 + 1 + + + 1 + 80 + 1 + 1 + 10 + + + 1 + 3 + 1 + 1 + 1 + + + 1 + 3 + 1 + 1 + 1 + + + 5 + 25 + 5 + 5 + 5 + + + 2 + 10 + 2 + 2 + 2 + + + 2 + 10 + 2 + 2 + 2 + + + 2 + 10 + 2 + 2 + 2 + + + 1 + 3 + 1 + 1 + 1 + + + 1 + 3 + 1 + 1 + 1 + + + False + Comment utiliser MesCigares + center-on-parent + window_main + + + + + + + + True + False + 1 - Commencez par ajouter un cigare via "menu" -> "Ajouter un cigare" + Vous pouvez aussi utiliser le raccourci clavier CTRL + A + +2 - Une fois un cigare ajouté vous pouvez utiliser "Actions" -> "Gérer les achats" + pour définir la quantité et le prix pour le cigare précédement ajouté. + Vous pouvez aussi utiliser le raccourci clavier ALT + A + +3 - Pour noter une dégustation, utilisez le menu "Actions" -> "Dégustation et notation d'un cigare" + Cette action donnera une note sur 100 au cigare choisi et vous pourez ensuite modifier le + commentaire sur ce cigare qui apparait lors de son survol dans la liste + + + + intro + True + + + + + True + False + Un clic droit sur un cigare dans la liste principale permet : + + 1 - D'insérer une image qui sera visible dans la zone retractable apreçu (en bas à gauche). + + 2 - Modifier le dernier commentaire laissé pour un cigare lors d'une précédente dégustation. + + 3 - Quitter le logiciel + + + True + + + + + True + False + A continuer ! + + + confirm + True + + + + + + + + + + + False + end + 6 + + + + + False + 5 + False + dialog + + + False + vertical + 2 + + + False + end + + + Annuler + True + True + True + + + False + True + 0 + + + + + OK + True + True + True + + + + False + True + 1 + + + + + False + True + end + 0 + + + + + True + False + vertical + + + True + False + 15 + Veuillez choisir un cigare dans la liste ci-dessous ou l'ajouter +depuis le menu principal s'il n'est pas dans la liste. + + + False + True + 0 + + + + + True + False + listestore_liste_cigares + + + + 1 + + + + + False + True + 1 + + + + + True + False + 15 + Veuillez renseigner la quantité achetée, le prix et la date. + + + False + True + 2 + + + + + True + False + + + True + False + Quantité : + + + False + True + 0 + + + + + True + True + adjustment15 + 1 + 0 + + + True + True + 1 + + + + + False + True + 3 + + + + + True + False + + + True + False + Prix : + + + False + True + 0 + + + + + True + True + 10 + + number + + + False + True + 1 + + + + + False + True + 4 + + + + + True + False + + + True + False + Date : + + + False + True + 0 + + + + + True + True + 2013 + 4 + 25 + + + False + True + 1 + + + + + False + True + 5 + + + + + False + True + 1 + + + + + + button3 + bt_valid_achats + + + + False + 5 + Ajouter un cigare + False + True + center-on-parent + dialog + window_main + + + False + vertical + 2 + + + False + end + + + Annuler + True + True + True + 0.56999999284744263 + + + False + True + 0 + + + + + Ajouter + True + True + True + 0.47999998927116394 + + + + + False + True + 1 + + + + + False + True + end + 0 + + + + + True + False + vertical + + + True + False + + + True + False + Nom: + + + False + True + 0 + + + + + True + True + + + + True + True + 1 + + + + + False + True + 0 + + + + + True + False + + + True + False + Marque: + + + False + True + 0 + + + + + True + False + liststore_marques + + + + 1 + + + + + True + True + 1 + + + + + False + True + 1 + + + + + True + False + + + True + False + Module: + + + False + True + 0 + + + + + True + False + liststore_modules + + + + 1 + + + + + True + True + 1 + + + + + False + True + 2 + + + + + False + True + 1 + + + + + + button2 + btadd + + + + False + 5 + False + center-on-parent + dialog + window_wishes + + + False + vertical + 2 + + + False + end + + + Annuler + True + True + True + + + False + True + 0 + + + + + Ajouter + True + True + True + + + + False + True + 1 + + + + + False + True + end + 0 + + + + + True + False + + + True + False + Nom : + + + False + True + 0 + + + + + True + True + + True + + + False + True + 1 + + + + + True + False + Provenance : + + + False + True + 3 + + + + + True + True + + True + + + False + True + 4 + + + + + True + False + Source : + + + False + True + 6 + + + + + True + True + + True + + + False + True + 7 + + + + + True + False + Raison : + + + False + True + 9 + + + + + 200 + 80 + True + True + True + True + + + False + True + 10 + + + + + False + True + 1 + + + + + + button7 + bt_valid_add_wish + + + + False + 5 + Modification du commentaire + center-on-parent + dialog + window_main + + + False + vertical + 2 + + + False + end + + + Annuler + True + True + True + + + False + True + 0 + + + + + Modifier + True + True + True + + + + False + True + 1 + + + + + False + True + end + 0 + + + + + 450 + 200 + True + True + + + True + True + 1 + + + + + + button5 + bt_commentaires_modif + + + + False + 5 + Gestion du stock + False + center-on-parent + dialog + window_main + + + False + vertical + 2 + + + False + end + + + Annuler + True + True + True + + + False + True + 0 + + + + + Valider + True + True + True + + + + False + True + 1 + + + + + False + True + end + 0 + + + + + True + False + vertical + True + + + True + False + Veuillez choisir un cigare ci-dessous et ajuster la quantitée + + + False + True + 0 + + + + + True + False + listestore_liste_cigares + + + + + 1 + + + + + False + True + 1 + + + + + True + True + adjqte + 0 + 0 + + + False + True + 2 + + + + + False + True + 1 + + + + + + button4 + btstockvalid + + + + False + 5 + GtkFileChooserDialog + center-on-parent + dialog + window_main + + + + False + vertical + 2 + + + False + end + + + Annuler + True + True + True + + + False + True + 0 + + + + + OK + True + True + True + + + + False + True + 1 + + + + + False + True + end + 0 + + + + + + + + + btchooseko1 + btchooseokbdd + + + + False + 5 + GtkFileChooserDialog + center-on-parent + dialog + window_main + + + + False + vertical + 2 + + + False + end + + + Annuler + True + True + True + + + False + True + 0 + + + + + OK + True + True + True + + + + False + True + 1 + + + + + False + True + end + 0 + + + + + + + + + btchooseko + btchooseok + + + + True + False + gtk-help + + + True + False + gtk-info + + + True + False + gtk-add + + + True + False + gtk-add + + + True + False + gtk-edit + + + True + False + 0.43999999761581421 + gtk-new + + + True + False + gtk-edit + + + True + False + gtk-info + + + True + False + gtk-dialog-question + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + 5 + center-on-parent + dialog + True + window_main + + + False + vertical + 2 + + + False + spread + + + OK + True + True + True + + + False + True + 1 + + + + + True + True + end + 0 + + + + + + button6 + + + + True + True + True + + + True + False + Insérer image + + + + + + True + False + Modifier le commentaire + + + + + + True + False + Quitter + + + + + + False + MesCigares + center + 1100 + 599 + ../images/MesCigares.ico + + + + + True + False + vertical + + + True + False + + + True + False + _Menu + True + + + True + False + + + Ajouter un cigare + True + False + image_gtk_new + False + + + + + + + Changer de base de données + True + False + image_gtk_new1 + False + + + + + + + True + False + + + + + gtk-quit + True + False + True + True + + + + + + + + + + + True + False + _Actions + True + + + True + False + + + Gérer les achats + True + False + image_gtk_achats + False + + + + + + + Gérer le stock + True + False + image_gtk_stocks + False + + + + + + + True + False + + + + + Dégustation et notation d'un cigare + True + False + image_gtk_degustation + False + + + + + + + True + False + + + + + Ajouter un module + True + False + False + image_gtk_add_module + False + + + + + + Ajouter une marque + True + False + False + image_gtk_add_marque + False + + + + + + True + False + + + + + Cigares à tester + True + False + image_gtk_wish + False + + + + + + + + + + + True + False + False + Rapports + + + True + False + + + gtk-about + True + False + True + True + + + + + + + + + True + False + _Aide + True + + + True + False + + + Comment utiliser MesCigares + True + False + image1 + False + + + + + + gtk-about + True + False + True + True + + + + + + + + + + False + True + 0 + + + + + True + True + in + + + True + True + True + liststore_treeview_cigares + 0 + both + + + + + + + + + + True + True + 1 + + + + + True + True + + + True + False + gtk-missing-image + + + + + True + False + Aperçu + + + + + False + True + 2 + + + + + True + False + vertical + 2 + + + False + True + 3 + + + + + + + False + 5 + Dégustation + center-on-parent + 1000 + 600 + dialog + window_main + + + False + vertical + 2 + + + False + end + + + Annuler + True + True + True + + + False + True + 2 + + + + + Valider + True + True + True + + + + False + True + 3 + + + + + False + True + end + 0 + + + + + True + True + in + + + True + False + + + True + False + vertical + + + True + False + + + True + False + 10 + Veuillez choisir le cigare à noter : + + + False + True + 0 + + + + + True + False + liststore_cigares_a_noter + + + + 1 + + + + + True + True + 1 + + + + + False + True + 0 + + + + + True + False + + + True + False + 5 + 1) L’aspect visuel : 0/3 + 1= aspect très rustique/ nervures proéminentes/ cape rugueuse/ couleur inégale + 2= aspect classique/ nervures proéminentes/ cape lisse/ couleur uniforme + 3= aspect excellent/ très légères nervures/ cape lisse et grasse/ couleur uniforme + + + False + True + 0 + + + + + True + True + 77 + adjustment14 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 1 + + + + + True + False + + + True + False + 5 + 2) Le toucher : 0/3 + 1= toucher très ferme, très léger + 2= toucher très moelleux, mou + 3= toucher, ferme et moelleux + + + False + True + 0 + + + + + True + True + 355 + adjustment2 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 2 + + + + + True + False + + + True + False + 5 + 3) Le nez (parfum) 0/3 + 1= parfum inexistant ou très puissant, désagréable + 2=parfum très léger et moyen + 3= parfum léger et agréable + + + False + True + 0 + + + + + True + True + 253 + adjustment3 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 3 + + + + + True + False + + + True + False + 5 + 4) Richesse et évolution aromatique 0/25 + 5= inexistante (absente) + 10= moyenne (une dominante sur 1 arome) + 15= bonne (2 aromes maximum) + 20= très bonne (3 aromes max) + 25= excellente (3 à 5 aromes, bonne évolution) + + + False + True + 0 + + + + + True + True + 266 + adjustment4 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 4 + + + + + True + False + + + True + False + 5 + 5) Perception et sensation des saveurs 0/10 + 2= très fade ou trop puissante + 4= moyenne + 6= bonne + 8= très bonne + 10= excellent + + + False + True + 0 + + + + + True + True + 305 + adjustment5 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 5 + + + + + True + False + + + True + False + 5 + 6) Puissance 0/10 + 2= puissance inexistante/ trop forte/ écrasant + 4= puissance très légère + 6= puissance moyenne + 8= puissance forte et équilibrée + 10= puissance modérée et équilibrée + + + False + True + 0 + + + + + True + True + 279 + adjustment6 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 6 + + + + + True + False + + + True + False + 5 + 7) longueur en bouche (durée pendant laquelle les goûts et les arômes restent en bouche) 0/10 + 2= courte/ éphémère (les aromes s’évanouissent au bout de quelques secondes) + 4= moyenne (5 à 10sec) + 6= bonne (10 à 15sec) + 8= très bonne (20 à 35sec) + 10= excellente (les aromes persistent de 40 à 50sec, jusqu’à la prochaine bouffée) + + + False + True + 0 + + + + + True + True + 12 + adjustment7 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 7 + + + + + True + False + + + True + False + 5 + 8) Rondeur en bouche 0/3 + 1= agressive, acidité, acre ou trop suave + 2= douce/ moyenne + 3= grasse et enveloppante/ dense et rassasiante + + + False + True + 0 + + + + + True + True + 264 + adjustment8 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 8 + + + + + True + False + + + True + False + 5 + 9) tirage 0/3 + 1= Tirage bouché/ trop serré + 2= Tirage serré/ moyen + 3= Tirage très bon/ ventilé + + + False + True + 0 + + + + + True + True + 380 + adjustment9 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 9 + + + + + True + False + + + True + False + 5 + 10) Combustion 0/4 + 1= Mauvaise (5 à 10 allumages) + 2= Moyenne (1 à 5) + 3= Acceptable (1 à 2) + 4= Excellente (aucun) + + + False + True + 0 + + + + + True + True + 364 + adjustment10 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 10 + + + + + True + False + + + True + False + 5 + 11) Cendre 0/3 + 1= très friable/ très légère + 2= Ferme et irrégulière + 3= droite et régulière/ très ferme + + + False + True + 0 + + + + + True + True + 353 + adjustment11 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 11 + + + + + True + False + + + True + False + 5 + 12) Perception du 4e tiers (une fois le cigare fumé) 0/3 + 1= exécrable + 2= moyenne + 3= très bonne + + + False + True + 0 + + + + + True + True + 250 + adjustment12 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 12 + + + + + True + False + + + True + False + 5 + 13) Note intuitive d’ensemble sur cette dégustation 0/20 + 1 à 5= aucun intérêt/ à éviter + 5 à 10= sans plus + 10 à 15= pourquoi pas, à l’occasion + 20= à renouveler sans aucun problème + + + False + True + 0 + + + + + True + True + 236 + adjustment13 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 13 + + + + + True + False + + + True + False + 135 + + + + + + + + + + +Commentaires : + + + False + True + 0 + + + + + 150 + True + True + True + GTK_INPUT_HINT_SPELLCHECK | GTK_INPUT_HINT_WORD_COMPLETION | GTK_INPUT_HINT_NONE + + + True + True + 1 + + + + + False + True + 14 + + + + + True + False + + + True + False + Date de la dégusation: + + + False + True + 0 + + + + + True + True + + + False + True + 1 + + + + + False + True + 15 + + + + + + + + + True + True + 1 + + + + + + btcancel + btvalid + + + + False + Liste des cigares à tester + center-on-parent + 600 + 400 + window_main + + + + True + False + vertical + + + True + True + in + + + True + True + Dès que vous séléctionnez une nouvelle ligne l'url "source" est copiée dans votre presse papier. +Utiliser CTRL + V pour la coller dans votre naviguateur web. + +Clic droit supprimer pour enlever de la liste. + 11 + liststore_wishes + True + both + + + + + + + True + Nom + True + True + True + 0 + + + + 0 + + + + + + + True + Provenance + True + True + True + 1 + + + + 1 + + + + + + + True + Raison + True + True + True + 2 + + + + 2 + + + + + + + True + Source + True + True + True + 3 + + + + 3 + + + + + + + False + id + + + False + + + 4 + + + + + + + + + True + True + 1 + + + + + True + False + + + Quitter + True + True + True + + + + False + True + end + 0 + + + + + Ajouter un cigare à tester + True + True + True + + + + False + True + end + 1 + + + + + False + True + end + 2 + + + + + + diff --git a/app_gestion/MesCigares/MesCigares.py b/app_gestion/MesCigares/MesCigares.py new file mode 100755 index 0000000..1ebbd4c --- /dev/null +++ b/app_gestion/MesCigares/MesCigares.py @@ -0,0 +1,1514 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import sys, os +import sqlite3 +import locale +import ntpath +import configparser + +try: + from gi.repository import Gtk as gtk, GdkPixbuf +except: + print("GTK (PyGI) n'est pas installé") + sys.exit(1) + +__BDD__ = "bdd/main.db" +__CONFDIR__ = os.path.dirname(os.path.realpath("MesCigares.py")) +__CONFIGFILE__ = "conf/mescigares.conf" + +class MesCigares: + + def on_window_main_destroy(self, widget, data=None): + db.close() + gtk.main_quit() + + def on_gtk_quit_activate(self, menuitem, data=None): + db.close() + gtk.main_quit() + + def on_gtk_about_activate(self, menuitem, data=None): + self.aboutdialog.run() + self.aboutdialog.hide() + + # Mise à jour de la quantité en fonction du cigare choisi dans la liste déroulante + def on_cbchoixcigqte_changed(self,object,data=None): + item = self.cbchoixcigqte.get_active() + + # Si aucune séléction, pas besoin de traitement. + if item == -1: + return + + model = self.cbchoixcigqte.get_model() + idc = model[item] + + cursor = db.cursor() + qte = cursor.execute("Select quantite from stocks where id_cigare = '{0}'".format(idc[0])).fetchone()[0] + cursor.close() + + self.scaleqte.set_value(int(qte)) + + # Permet d'alimenter la treeview avec la liste des cigares + def loadtreeview(self): + if len(self.table) != 0: + self.table.clear() + + cursor = db.cursor() + cursor.execute('Select c.designation, m.libelle, mo.module,m.provenance, s.quantite,c.id from cigares c inner join modules mo on mo.id = c.module inner join marques m on m.id = c.marque inner join stocks s on s.id_cigare = c.id') + rs = cursor.fetchall() + + cursor.execute("select avg(d.note), max(d.note),id_cigare from degustation d group by id_cigare") + rs2 = cursor.fetchall() + # top = 5 premier items de la liste par meilleure note [2] pour la note seulement + top = sorted(rs2, reverse=True)[0:5] + print (top) + # top5 = juste les id + top5 = list() + for i in top: + top5.append(i[2]) + + cursor.execute("select min(prix),id_cigare from achats group by id_cigare") + rs3 = cursor.fetchall() + + cursor.execute("select commentaires, id_cigare from degustation group by id_cigare") + rs4 = cursor.fetchall() + + for item in rs: + iter = self.table.append() + # Si le nombre de cigares est = 0 on affiche la ligne en rouge clair + if (item[4] == 0): + color = '#F3B9B9' # rouge clair + else: + color = '#FFFFFF' # blanc + + # On regarde si l'enregistrement est dans le top 5 + try: + index = top5.index(item[5]) + except: + index = 9 + # Et on affecte la couleur (dégradé de vert) + if(index == 0): + topcolor = '#8FFF80' + if(index == 1): + topcolor = '#A4FF97' + if(index == 2): + topcolor = '#BFFFB7' + if(index == 3): + topcolor = '#CDFFC7' + if(index == 4): + topcolor = '#DBFFD7' + if(index == 9): + topcolor = color + + self.table.set(iter, 0, item[0], 1, item[1], 2, item[2].title(), 3, item[3].title(), 4, item[4], 5, item[5], 11, color, 12, topcolor) + # Il n'y a pas de outer join en sqlite du coup je suis obligé d'alimenter les notes à part + for item2 in rs2: + itr = self.table.get_iter_first() + while ( itr != None ): + # itr, 5 = id de la table cigare (colonne 4 invisible), item2[2] = id_cigare table degustation + if self.table.get_value (itr, 5) == item2[2]: + self.table.set(itr, 6, item2[1], 7, item2[0]) + itr= self.table.iter_next(itr) + for item3 in rs3: + itr = self.table.get_iter_first () + while ( itr != None ): + # itr, 5 = id de la table cigare (colonne 4 invisible), item3[1] = id_cigare table achats + if self.table.get_value (itr, 5) == item3[1]: + # .2f pour 2 decimales + it = '%.2f' % item3[0] + self.table.set(itr, 8, it + " €", 10, float(item3[0])) + itr= self.table.iter_next(itr) + for item4 in rs4: + itr = self.table.get_iter_first () + while ( itr != None ): + # itr, 4 = id de la table cigare (colonne 4 invisible), item4[1] = id_cigare table achats + if self.table.get_value (itr, 5) == item4[1]: + self.table.set(itr, 9, item4[0]) + itr= self.table.iter_next(itr) + + # On calcule le nombre de cigares et le prix + nbcigares = 0 + montant = 0 + itr = self.table.get_iter_first () + while ( itr != None ): + nbcigares += int(self.table.get_value(itr, 4)) + if self.table.get_value(itr, 8) is not None: + montant += float(self.table.get_value(itr, 10)) * int(self.table.get_value(itr, 4)) + itr= self.table.iter_next(itr) + montant = '%.2f' % montant + + # On calcule le prix total + montant_total = 0 + cursor.execute("select prix, quantite from achats") + rs = cursor.fetchall() + + for item in rs: + montant_total += (item[0] * item[1]) + montant_total = '%.2f' % montant_total + + cursor.close() + # On affiche le nombre de résultats trouvés sur la barre de statut + if nbcigares == 0: + self.statusbar.push(self.context_id, "Aucun cigare dans la base") + else: + self.statusbar.push(self.context_id, str(nbcigares) + " cigares dans la base pour un montant de " + str(montant) + "€ - (" + str(montant_total) + "€ depuis le début)") + + # Permet l'affichage de la photo du cigare séléctionné dans la zone rétractable + def affiche_preview(self): + + cursor = db.cursor() + photo = cursor.execute("select photo from photos where id_cigare = '{0}'".format(self.treeview_selection[5])) + + try: + photo = cursor.fetchone()[0] + except TypeError: + photo = None + cursor.close() + # Si aucune photo n'existe pour ce cigare + if(photo is None): + photo = __CONFDIR__ + "/images/no_photo.jpg" + self.image_preview.set_from_file(photo) + else: + if os.path.exists(photo): + pixbuf = GdkPixbuf.Pixbuf().new_from_file(photo) + # Si l'image est plus grande que la fenêtre on re-dimensionne + new_size = self.window.get_size()[0] - 20 + if(pixbuf.get_width() > new_size): + w, h = pixbuf.get_width(), pixbuf.get_height() + pixbuf = pixbuf.scale_simple( new_size, new_size * h / w, GdkPixbuf.InterpType.HYPER) + # Si la largeur de l'image est plus petite que la hauteur on fait pivoter + if(pixbuf.get_width() < pixbuf.get_height()): + pixbuf = pixbuf.rotate_simple(270) + # Si la hauteur est dépasse la moitié de la fenêtre principale, on re-dimensionne + if(pixbuf.get_height() > self.window.get_size()[1]/2): + w, h = pixbuf.get_width(), pixbuf.get_height() + pixbuf = pixbuf.scale_simple( (w * self.window.get_size()[1]/2)/h, self.window.get_size()[1]/2 , GdkPixbuf.InterpType.HYPER) + self.image_preview.set_from_pixbuf(pixbuf) + else: + # Si la photo n'existe plus ou a été renommée + photo = __CONFDIR__ + "/images/bad_photo.jpg" + self.image_preview.set_from_file(photo) + self.imgpath = photo + + # Si la fenêtre principale change de taille on re-dimensionne le preview aussi + def on_window_main_check_resize(self, widget): + #print(self.window.get_size()[1],self.window_last_h) + if (( abs(self.window_last_h - self.window.get_size()[1]) > 40) | ( abs(self.window_last_w - self.window.get_size()[0]) > 5) ): + self.window_last_h = self.window.get_size()[1] + self.window_last_w = self.window.get_size()[0] + + if not os.path.exists(self.imgpath): + return + pixbuf = GdkPixbuf.Pixbuf().new_from_file(self.imgpath) + # Si l'image est plus grande que la fenêtre on re-dimensionne + new_size = self.window.get_size()[0] - 20 + if(pixbuf.get_width() > new_size): + w, h = pixbuf.get_width(), pixbuf.get_height() + pixbuf = pixbuf.scale_simple( new_size, new_size * h / w, GdkPixbuf.InterpType.HYPER) + # Si la largeur de l'image est plus petite que la hauteur on fait pivoter + if(pixbuf.get_width() < pixbuf.get_height()): + pixbuf = pixbuf.rotate_simple(270) + # Si la hauteur est dépasse la moitié de la fenêtre principale, on re-dimensionne + if(pixbuf.get_height() > self.window.get_size()[1]/2): + w, h = pixbuf.get_width(), pixbuf.get_height() + pixbuf = pixbuf.scale_simple( (w * self.window.get_size()[1]/2)/h, self.window.get_size()[1]/2 , GdkPixbuf.InterpType.HYPER) + self.image_preview.set_from_pixbuf(pixbuf) + + # Dès qu'une ligne est séléctionnée sur la treeview on enregistre les valeurs + def on_treeview_cigares_row_activated(self, widget): + selection = self.tree.get_selection() + (model, iter) = selection.get_selected() + if iter is not None: + self.treeview_selection.clear() + self.treeview_selection.append(model.get_value(iter, 0)) + self.treeview_selection.append(model.get_value(iter, 1)) + self.treeview_selection.append(model.get_value(iter, 2)) + self.treeview_selection.append(model.get_value(iter, 3)) + self.treeview_selection.append(model.get_value(iter, 4)) + self.treeview_selection.append(model.get_value(iter, 5)) + self.treeview_selection.append(model.get_value(iter, 6)) + self.treeview_selection.append(model.get_value(iter, 7)) + # for i in range(0,6): + # print(model.get_value(iter, i)) + # print("\n") + # Et on met à jour l'affichage du preview + self.affiche_preview() + + def get_libelle_quantite(self): + selection = self.tree.get_selection() + (model, iter) = selection.get_selected() + if iter is not None: + lib = model.get_value(iter, 0) + qtee = model.get_value(iter,5) + return(lib, qtee) + + # Changer de base de données + def on_bdd_change_activate(self, widget): + filter = gtk.FileFilter() + filter.set_name("Bases sqlite") + filter.add_mime_type("application/octet-stream") + filter.add_pattern("*.db") + self.filechooser_bdd.add_filter(filter) + self.filechooser_bdd.set_current_folder(__CONFDIR__ + "/bdd/") + self.filechooser_bdd.run() + self.loadtreeview() + self.filechooser_bdd.hide() + + # Double clic sur le sélécteur de fichiers pour choisir la bdd + def on_filechooser_bdd_file_activated(self, widget): + if not os.path.exists(self.filechooser_bdd.get_filename()): + createdb(self.filechooser_bdd.get_filename()) + setconfig(self.filechooser_bdd.get_filename()) + global db + db.close() + db = sqlite3.connect(self.filechooser_bdd.get_filename()) + # On met à jour le titre de la fenêtre + self.window.set_title("MesCigares - base : {0}".format(ntpath.basename(self.filechooser_bdd.get_filename().replace('.db','')))) + self.loadtreeview() + self.filechooser_bdd.hide() + + # Clic sur le bouton OK du sélécteur de fichiers pour choisir l'image + def on_btchooseokbdd_clicked(self, widget): + if not os.path.exists(self.filechooser_bdd.get_filename()): + createdb(self.filechooser_bdd.get_filename()) + # changer la conf + setconfig(self.filechooser_bdd.get_filename()) + global db + db.close() + db = sqlite3.connect(self.filechooser_bdd.get_filename()) + self.loadtreeview() + + # Ajouter une image pour un cigare + def on_inserer_image_activate(self, widget): + self.filechooser_image.run() + self.filechooser_image.hide() + + # Modifier/Supprimer une note de degustation + def on_modsupp_degustation_activate(self, widget): + # On commence par vider la liststore_date_modif_degustation (2ème utilisation) + if len(self.liststore_date_modif_degustation) != 0: + self.liststore_date_modif_degustation.clear() + + cursor = db.cursor() + # On regarde récupère le commentaire + cursor.execute("select date, q1, q2, q3, q4, q5, q6, q7, q8, q9, q10, q11, q12, q13, id, commentaires from degustation where id_cigare = '{0}'".format(self.treeview_selection[5])) + rs = cursor.fetchall() + # si nb = 0 on insère sinon on update la table photos + db.commit() + cursor.close() + + if(len(rs) == 0): + # Afficher un message d'infos pour alerter qu'il faut d'abord faire une dégustation + self.msgdialog.format_secondary_text("Il n'y a aucune dégustation pour ce cigare") + self.msgdialog.run() + self.msgdialog.hide() + return + + for item in rs: + date = item[0] + q1 = item[1] + q2 = item[2] + q3 = item[3] + q4 = item[4] + q5 = item[5] + q6 = item[6] + q7 = item[7] + q8 = item[8] + q9 = item[9] + q10 = item[10] + q11 = item[11] + q12 = item[12] + q13 = item[13] + idcomm = item[14] + commentaire = item[15] + iter = self.liststore_date_modif_degustation.append() + self.liststore_date_modif_degustation.set(iter, 0, date, 1, q1, 2, q2, 3, q3, 4, q4, 5, q5, 6, q6, 7, q7, 8, q8, 9, q9, 10, q10, 11, q11, 12, q12, 13, q13, 14, idcomm, 15, commentaire) + + # On affiche la fenêtre de modification du commentaire + self.window_modif_degustation.run() + self.window_modif_degustation.hide() + # On recharge la treeview + self.loadtreeview() + + # Changement sur la liste déroulante de choix de la not de dégustation à modifier/supprimer + def on_cbchoix_date_modif_degustation_changed(self, widget): + index = self.cbchoix_date_modif_degustation.get_active() + q = list() + try: + for i in range(1, 14): + q.append(self.liststore_date_modif_degustation[index][i]) + idcomm = self.liststore_date_modif_degustation[index][14] + + except IndexError: + pass + + try: + commentaire = self.liststore_date_modif_degustation[index][15] + self.txtview_commentaire_modif.get_buffer().set_text(commentaire) + except IndexError: + self.txtview_commentaire_modif.get_buffer().set_text("") + + # On met la bonne valeur sur les scales + try: + self.scaleq1m.set_value(q[0]) + self.scaleq2m.set_value(q[1]) + self.scaleq3m.set_value(q[2]) + self.scaleq4m.set_value(q[3]) + self.scaleq5m.set_value(q[4]) + self.scaleq6m.set_value(q[5]) + self.scaleq7m.set_value(q[6]) + self.scaleq8m.set_value(q[7]) + self.scaleq9m.set_value(q[8]) + self.scaleq10m.set_value(q[9]) + self.scaleq11m.set_value(q[10]) + self.scaleq12m.set_value(q[11]) + self.scaleq13m.set_value(q[12]) + except IndexError: + self.scaleq1m.set_value(1) + self.scaleq2m.set_value(1) + self.scaleq3m.set_value(1) + self.scaleq4m.set_value(5) + self.scaleq5m.set_value(2) + self.scaleq6m.set_value(2) + self.scaleq7m.set_value(2) + self.scaleq8m.set_value(1) + self.scaleq9m.set_value(1) + self.scaleq10m.set_value(1) + self.scaleq11m.set_value(1) + self.scaleq12m.set_value(1) + self.scaleq13m.set_value(1) + + + # Calcul de la note globale pour modification d'une note de dégustation + def on_scaleqm_change_value(self, scale, enum, new_value): + # On récupère les notes + q1=int(self.scaleq1m.get_value()) + q2=int(self.scaleq2m.get_value()) + q3=int(self.scaleq3m.get_value()) + q4=int(self.scaleq4m.get_value()) + q5=int(self.scaleq5m.get_value()) + q6=int(self.scaleq6m.get_value()) + q7=int(self.scaleq7m.get_value()) + q8=int(self.scaleq8m.get_value()) + q9=int(self.scaleq9m.get_value()) + q10=int(self.scaleq10m.get_value()) + q11=int(self.scaleq11m.get_value()) + q12=int(self.scaleq12m.get_value()) + q13=int(self.scaleq13m.get_value()) + + note = q1 + q2 + q3 + q4 + q5 + q6 + q7 + q8 + q9 + q10 + q11 + q12 + q13 + # On affiche la note / 100 temporaire + self.lbl_note_totale.set_text("Nouvelle note: {0}/100".format(note)) + + # Validation de la modification d'une dégustation + def on_btvalid_degustation_clicked(self, widget): + # modifier + index = self.cbchoix_date_modif_degustation.get_active() + textbuffer = self.txtview_commentaire_modif.get_buffer() + start = textbuffer.get_start_iter() + end = textbuffer.get_end_iter() + # On récupère le commentaire et on échape les quote pour l'insert SQL + commentaire = textbuffer.get_text(start, end, include_hidden_chars=False).replace("'","''") + # On récupère les notes + q1=int(self.scaleq1m.get_value()) + q2=int(self.scaleq2m.get_value()) + q3=int(self.scaleq3m.get_value()) + q4=int(self.scaleq4m.get_value()) + q5=int(self.scaleq5m.get_value()) + q6=int(self.scaleq6m.get_value()) + q7=int(self.scaleq7m.get_value()) + q8=int(self.scaleq8m.get_value()) + q9=int(self.scaleq9m.get_value()) + q10=int(self.scaleq10m.get_value()) + q11=int(self.scaleq11m.get_value()) + q12=int(self.scaleq12m.get_value()) + q13=int(self.scaleq13m.get_value()) + + note = q1 + q2 + q3 + q4 + q5 + q6 + q7 + q8 + q9 + q10 + q11 + q12 + q13 + # Le commentaire et l'id + idcomm = self.liststore_date_modif_degustation[index][14] + id_cigare = self.treeview_selection[5] + + # On met à jour la base de données + cursor = db.cursor() + sql = "update degustation set commentaires = '{0}', q1 = '{1}', q2 = '{2}', q3 = '{3}', q4 = '{4}', q5 = '{5}', q7 = '{7}', q8 = '{8}', q9 = '{9}', q10 = '{10}', q11 = '{11}', q12 = '{12}', q13 = '{13}', note = '{14}' where id_cigare = '{15}' and id = '{16}'".format(commentaire, q1, q2, q3, q4, q5, q6, q7, q8, q9, q10, q11, q12, q13, note , id_cigare, idcomm) + cursor.execute(sql) + db.commit() + cursor.close() + # Afficher un message d'infos pour confirmer + self.msgdialog.format_secondary_text("Modification de la dégustation pour {0}.\nNouvelle note: {1}".format(self.treeview_selection[1], note)) + self.msgdialog.run() + self.msgdialog.hide() + + # On recharge la liste des cigares + self.loadtreeview() + + # Double clic sur le sélécteur de fichiers pour choisir l'image + def on_filechooser_image_file_activated(self, widget): + self.addimage(self.filechooser_image.get_filename()) + self.filechooser_image.hide() + + # Clic sur le bouton OK du sélécteur de fichiers pour choisir l'image + def on_btchooseok_clicked(self, widget): + self.addimage(self.filechooser_image.get_filename()) + + # Mettre à jour la BDD avec le chemin de l'image choisie + def addimage(self, path): + + cursor = db.cursor() + # On regarde s'il y a déjà une photo pour le cigare séléctionné + nb = cursor.execute("select count(*) from photos where id_cigare = '{0}'".format(self.treeview_selection[5])).fetchone()[0] + # si nb = 0 on insère sinon on update la table photos + if ( nb == 0): + cursor.execute("insert into photos (id_cigare, photo) values('{0}','{1}')".format(self.treeview_selection[5], path)) + else: + cursor.execute("update photos set photo = '{0}' where id_cigare='{1}'".format(path, self.treeview_selection[5])) + db.commit() + cursor.close() + + # Clic droit sur un cigare dans la treeview + def on_treeview_cigares_button_press_event(self, treeview, event): + x = int(event.x) + y = int(event.y) + if event.button == 3: # Clic droit + pthinfo = self.tree.get_path_at_pos(x, y) + if pthinfo is not None: + path, col, cellx, celly = pthinfo + self.tree.grab_focus() + self.tree.set_cursor( path, col, 0) + # On affiche le popup + self.popup.show_all(); + self.popup.popup(None, None, None, None, event.button, event.time) + + + def text_edited(self, widget, path, text): + self.liststore[path][1] = text + + # Mise à jour du listestore si édition d'une cellule + def cell_edited_callback(self, cellule, path, new_text, col, data=None): + iter = self.table.get_iter(path) + if col == 4: + try: + self.table.set_value(iter, col, int(new_text)) + self.update_db("id", new_text,tuple(self.treeview_selection)[5]) + except ValueError: # Si la quantité saisie n'est pas un entier + pass + else: + self.table.set_value(iter, col, new_text) + self.update_db("nom", new_text,tuple(self.treeview_selection)[5]) + + # Mise à jour de la BDD si changement dans un cellule + def update_db(self, operation, new_text, indice): + cursor = db.cursor() + # update cigares.designation + if (operation == 'nom'): + sql = "update cigares set designation = '{0}' where id = '{1}'".format(new_text.replace("'","''"),str(indice)) + #print(sql) + cursor.execute(sql) + db.commit() + + # update quantité dans table stock + if (operation == 'id'): + sql = "update stocks set quantite = '{0}' where id_cigare='{1}'".format(new_text, str(indice)) + #print(sql) + cursor.execute(sql) + db.commit() + + cursor.close() + # recharger la treeview + self.loadtreeview() + + # Affichage de la fenêtre d'ajout d'un cigare + def on_gtk_new_activate(self, widget): + # On commence par vider le liststore_marques (2è recherche) + if len(self.tablemarques) != 0: + self.tablemarques.clear() + if len(self.tablemodules) != 0: + self.tablemodules.clear() + + + cursor = db.cursor() + cursor.execute('Select id, libelle, provenance from marques order by lower(libelle)') + rs = cursor.fetchall() + # On alimente la combobox des marques + for item in rs: + iter = self.tablemarques.append() + self.tablemarques.set(iter, 0, item[0], 1, item[1].title(), 2, item[2].title()) + + cursor.execute('Select id, module, longueur, diametre, calibre from modules order by lower(module)') + rs = cursor.fetchall() + # Et celle des modules + for item in rs: + iter = self.tablemodules.append() + self.tablemodules.set(iter, 0, item[0], 1, item[1].title(), 2, item[2], 3, item[3], 4, item[4]) + + cursor.close() + self.dialog_add_cigare.run() + self.dialog_add_cigare.hide() + + # Changement dans la liste déroulante des marques + def on_cbmarques_changed(self,widget): + index = self.cbmarques.get_active() + try: + provenance = self.tablemarques[index][2].title() + self.lbl_marque.set_text("Terroir: " + provenance) + except IndexError: + self.lbl_marque.set_text("") + + + # Changement dans la liste déroulante des modules + def on_cbmodules_changed(self,widget): + try: + index = self.cbmodules.get_active() + longueur = self.tablemodules[index][2] + diametre = self.tablemodules[index][3] + calibre = self.tablemodules[index][4] + self.lbl_module.set_text("{0} mm, diamètre {1}, calibre(cepo) {2}".format(longueur, diametre, calibre)) + except IndexError: + self.lbl_module.set_text("") + + # Validation de l'ajout d'un nouveau cigare + def on_btadd_clicked(self,widget): + index = self.cbmarques.get_active() + model = self.cbmarques.get_model() + itemmarque = model[index] + # item[0] = id à insérer dans champ marque de la table cigares + #print (str(itemmarque[0]), str(itemmarque[1])) + + index = self.cbmodules.get_active() + model = self.cbmodules.get_model() + itemmodel = model[index] + # item[0] = id à insérer dans champ marque de la table cigares + #print (str(itemmodel[0]), str(itemmodel[1])) + # Il faudra faire une requete sur cigares pour obtenir le dernier id et faire + # + 1 : Select max(id) from cigares + # table stocks : id_cigare = max id + 1 + #print (str(self.entry_nom_cigare.get_text())) + + + cursor = db.cursor() + cursor.execute('Select max(id) from cigares') + try: + maxid = cursor.fetchone()[0] + 1 + except TypeError: + maxid = 1 + cursor.execute("insert into stocks (id_cigare, quantite) values ('{0}','{1}')".format(int(maxid),0)) + cursor.execute("insert into cigares (designation, marque, module) values ('{0}','{1}', '{2}')".format(str(self.entry_nom_cigare.get_text()), int(itemmarque[0]),int(itemmodel[0]))) + db.commit() + cursor.close() + + # On recharge le treeview + self.loadtreeview() + + # Affichage de la fenêtre de gestion des achats + def on_gtk_achats_activate(self, widget): + # On remet les selections à zero + self.scale_qte_achats.set_value(1) + self.scale_typeboite.set_value(25) + self.entry_prix_achats.set_text("") + self.entry_code_boite.set_text("") + self.entry_prix_boite.set_text("") + self.notebook_achats.set_current_page(0) + # On vide le listestore_liste_cigares (2è recherche) + if len(self.tablechoixcig) != 0: + self.tablechoixcig.clear() + + + cursor = db.cursor() + cursor.execute('Select c.id, c.designation from cigares c order by c.designation asc') + rs = cursor.fetchall() + for item in rs: + iter = self.tablechoixcig.append() + self.tablechoixcig.set(iter, 0, item[0], 1, item[1]) + cursor.close() + # On affiche la fenêtre de gestion des achats + self.dialog_achats.run() + + # Cancel sur l'ajout d'un achat + def on_bt_cancel_achats_clicked(self, widget): + self.dialog_achats.hide() + + # Validation des achats + def on_bt_valid_achats_clicked(self, widget): + # On récupère le cigare choisi dans la liste déroulante + # [0] id_cigare ~ [1] libelle + index = self.cb_liste_cigares_achats.get_active() + itemchoixcig = self.tablechoixcig[index] + + id_cigare = itemchoixcig[0] + + # On vérifie les saisies + if(index == -1): # Aucun cigare choisi + self.msgdialog.format_secondary_text("Vous devez d'abord choisir un cigare dans la liste") + self.msgdialog.run() + self.msgdialog.hide() + return + # Mauvais prix unité + if(self.notebook_achats.get_current_page() == 0 and not is_number(self.entry_prix_achats.get_text())): + self.msgdialog.format_secondary_text("Saisie erronée pour le prix du cigare.") + self.msgdialog.run() + self.msgdialog.hide() + return + # Mauvais prix boite + if(self.notebook_achats.get_current_page() == 1 and not is_number(self.entry_prix_boite.get_text())): + self.msgdialog.format_secondary_text("Saisie erronée pour le prix de la boite.") + self.msgdialog.run() + self.msgdialog.hide() + return + if(self.notebook_achats.get_current_page() == 0): # Choix à l'unité + qte_achat = int(self.scale_qte_achats.get_value()) + prix_achat = self.entry_prix_achats.get_text().replace(",",".") + prix_achat = '%.2f' % float(prix_achat) + code_boite = "" + else: # = 1 choix par boites + qte_achat = int(self.scale_typeboite.get_value()) + prix_achat = float(self.entry_prix_boite.get_text()) / qte_achat + prix_achat = '%.2f' % prix_achat + code_boite = self.entry_code_boite.get_text() + print(qte_achat) + print(prix_achat) + print(code_boite) + + # On formate la date (ajout d'un 0 si besoin) + j = str(self.calendar_date_achats.get_date()[2]) + if(len(str(self.calendar_date_achats.get_date()[1] +1)) == 1): + m = "0" + str(self.calendar_date_achats.get_date()[1] +1) + else: + m = str(self.calendar_date_achats.get_date()[1] +1) + a = self.calendar_date_achats.get_date()[0] + + date_achat = "{0}/{1}/{2}".format(j, m, a) + + # Insert des achats en base de données + cursor = db.cursor() + cursor.execute("insert into achats (id_cigare, date, prix, quantite, code_boite) values ('{0}','{1}','{2}','{3}', '{4}')".format(id_cigare, date_achat, prix_achat, qte_achat, code_boite)) + # ajout dans le stock + cursor.execute("update stocks set quantite = quantite + {1} where id = {0}".format(id_cigare, qte_achat)) + + db.commit() + cursor.close() + + # Afficher un message d'infos pour confirmer + self.msgdialog.format_secondary_text("Achats ajoutés avec succés pour {0}".format(itemchoixcig[1])) + self.msgdialog.run() + self.msgdialog.hide() + + # On recharge la liste des cigares + self.loadtreeview() + + self.dialog_achats.hide() + + # Affiche du prix d'un cigare pour les achats par boite + def on_scale_typeboite_change_value(self, scale, enum, new_value): + if(is_number(self.entry_prix_boite.get_text())): + prix_unite = float(self.entry_prix_boite.get_text()) / int(self.scale_typeboite.get_value()) + prix_unite = '%.2f' % prix_unite + # on met à jour le label + self.lbl_prix_unite_boite.set_text(" Soit {0}€ le cigare".format(prix_unite)) + + def on_entry_prix_boite_changed(self, widget): + self.on_scale_typeboite_change_value(None, None, None) + + # Affichage de la fenêtre d'ajout d'une marque + def on_gtk_add_marque_activate(self, menuitem, data=None): + self.dialog_add_marque.run() + self.dialog_add_marque.hide() + + # Validation de l'ajout d'une marque + def on_bt_add_marque_clicked(self, widget): + print("todo") + e_marque = self.entry_marque.get_text().title() + e_provenance = self.entry_provenance.get_text().title() + + # Insert de la nouvelle marque en base de données + cursor = db.cursor() + cursor.execute("insert into marques (libelle, provenance) values ('{0}','{1}')".format(e_marque, e_provenance)) + + db.commit() + cursor.close() + + # Afficher un message d'infos pour confirmer + self.msgdialog.format_secondary_text("Marque {0} ajoutée.".format(e_marque)) + self.msgdialog.run() + self.msgdialog.hide() + + # Affichage de la fenêtre de gestion des stocks + def on_gtk_stocks_activate(self, menuitem, data=None): + # On commence par vider le listestore_liste_cigares (2è recherche) + if len(self.tablechoixcig) != 0: + self.tablechoixcig.clear() + + cursor = db.cursor() + cursor.execute('Select c.id, c.designation from cigares c order by c.designation asc') + rs = cursor.fetchall() + for item in rs: + iter = self.tablechoixcig.append() + self.tablechoixcig.set(iter, 0, item[0], 1, item[1]) + cursor.close() + + self.dialog_stocks.run() + self.dialog_stocks.hide() + + # Validation des stocks + def on_btstockvalid_clicked(self, value): + item = self.cbchoixcigqte.get_active() + model = self.cbchoixcigqte.get_model() + idc = model[item] + + + cursor = db.cursor() + cursor.execute("Update stocks set quantite = '{0}' where id_cigare = '{1}'".format(int(self.scaleqte.get_value()), idc[0])) + db.commit() + cursor.close() + # affichage message maj ok pour qte + self.msgdialog.format_secondary_text("Quantité mise à jour pour {0}.\nVous avez maintenant {1} exemplaire(s)".format(idc[1], int(self.scaleqte.get_value()))) + self.msgdialog.run() + self.msgdialog.hide() + # On recharge la treeview principale + self.loadtreeview() + + # Affichage de la fenêtre de dégustation + def on_gtk_degustation_activate(self,menuitem, data=None): + if len(self.tablechoixcigare) != 0: + self.tablechoixcigare.clear() + + cursor = db.cursor() + cursor.execute('Select c.id, c.designation from cigares c where c.id not in (select s.id_cigare from stocks s where s.quantite = 0) order by c.designation asc') + rs = cursor.fetchall() + for item in rs: + iter = self.tablechoixcigare.append() + self.tablechoixcigare.set(iter, 0, item[0], 1, item[1]) + + cursor.close() + self.window_degustation.run() + self.window_degustation.hide() + + # Validation d'une dégusation + def on_btvalid_clicked(self, widget): + + # On formate la date (ajout d'un 0 si besoin) + j = str(self.DegustationDate.get_date()[2]) + if(len(str(self.DegustationDate.get_date()[1] +1)) == 1): + m = "0" + str(self.DegustationDate.get_date()[1] +1) + else: + m = str(self.DegustationDate.get_date()[1] +1) + a = self.DegustationDate.get_date()[0] + + # Formatage de la date du calendar au format %d/%m/%Y + DateDegustation = "{0}/{1}/{2}".format(j, m, a) + + textbuffer = self.txtview_commentaire.get_buffer() + start = textbuffer.get_start_iter() + end = textbuffer.get_end_iter() + # On récupère le commentaire et on échape les quote pour l'insert SQL + textlines = textbuffer.get_text(start, end, include_hidden_chars=False).replace("'","''") + + index = self.cbchoixcigare.get_active() + itemchoixcig = self.tablechoixcigare[index] + + note = int(self.scaleq1.get_value()) + int(self.scaleq2.get_value())+int(self.scaleq3.get_value())+int(self.scaleq4.get_value())+int(self.scaleq5.get_value())+int(self.scaleq6.get_value())+int(self.scaleq7.get_value())+int(self.scaleq8.get_value())+int(self.scaleq9.get_value())+int(self.scaleq10.get_value())+int(self.scaleq11.get_value())+int(self.scaleq12.get_value())+int(self.scaleq13.get_value()) + + + cursor = db.cursor() + cursor.execute("insert into degustation (id_cigare,date,commentaires,q1,q2,q3,q4,q5,q6,q7,q8,q9,q10,q11,q12,q13,note) values ('{0}','{1}','{2}','{3}','{4}','{5}','{6}','{7}','{8}','{9}','{10}','{11}','{12}','{13}','{14}','{15}','{16}')".format(itemchoixcig[0],DateDegustation ,textlines,int(self.scaleq1.get_value()),int(self.scaleq2.get_value()),int(self.scaleq3.get_value()),int(self.scaleq4.get_value()),int(self.scaleq5.get_value()),int(self.scaleq6.get_value()),int(self.scaleq7.get_value()),int(self.scaleq8.get_value()),int(self.scaleq9.get_value()),int(self.scaleq10.get_value()),int(self.scaleq11.get_value()),int(self.scaleq12.get_value()),int(self.scaleq13.get_value()),note)) + # - 1 sur le stock + cursor.execute("update stocks set quantite = quantite - 1 where id = {0}".format(itemchoixcig[0])) + db.commit() + cursor.close() + + # TODO ajouter une checkbox sur le formulaire pour choisir si retirer 1 du stock ou non + self.msgdialog.format_secondary_text("Note de dégustation ajoutée avec succès pour : {0}.\n1 cigare retiré du stock.".format(itemchoixcig[1])) + self.msgdialog.run() + self.msgdialog.hide() + + # On recharge la treeview + self.loadtreeview() + + # Affichage de la fenêtre des cigares à tester + def on_gtk_wish_activate(self, menuitem, data=None): + # On commence par vider le listestore_wishes (2è recherche) + if len(self.liststore_wishes) != 0: + self.liststore_wishes.clear() + + cursor = db.cursor() + cursor.execute('select nom, provenance,raison,source,id from tester order by id') + rs = cursor.fetchall() + for item in rs: + iter = self.liststore_wishes.append() + self.liststore_wishes.set(iter, 0, item[0], 1, item[1], 2, item[2], 3, item[3], 4, item[4]) + cursor.close() + + self.window_wishes.resize(self.window.get_size()[0], self.window.get_size()[1]) + + self.window_wishes.show() + + # Quitter la fenêtre des cigares à tester + def on_window_wishes_delete_event(self, widget, data=None): + self.window_wishes.hide() + return True + + # Affichage de l'assistant d'aide pour la documentation + def on_gtk_how_to_activate(self, widget): + self.doc.show() + #self.doc.hide() + + def on_lbl_intro_activate_link(self, widget): + self.doc.set_forward_page_func(page_func=None, data=None) + + # Quitter la fenêtre d'assistant + def on_assistant_delete_event(self, widget, data=None): + self.doc.hide() + return True + + # Affichage de la fenêtre d'ajout d'un cigare à tester + def on_bt_add_wish_clicked(self, widget): + self.dialog_add_wish.run() + self.dialog_add_wish.hide() + + # Valider l'ajout d'un cigare à tester + def on_bt_valid_add_wish_clicked(self, widget): + textbuffer = self.txtview_test_raison.get_buffer() + start = textbuffer.get_start_iter() + end = textbuffer.get_end_iter() + # On récupère le commentaire et on échape les quote pour l'insert SQL + textlines = textbuffer.get_text(start, end, include_hidden_chars=False).replace("'","''") + + # On ajoute en base de données + cursor = db.cursor() + sql = "insert into tester (nom, provenance, source, raison) values ('{0}','{1}','{2}','{3}')".format(self.entry_test_nom.get_text(), self.entry_test_origine.get_text(), self.entry_test_source.get_text(), textlines) + cursor.execute(sql) + db.commit() + cursor.close() + + # On recharge la treeview des cigares à tester + self.on_gtk_wish_activate(self) + + # Clic droit sur un cigare à tester dans la treeview + def on_treeview_wishes_button_press_event(self, treeview, event): + x = int(event.x) + y = int(event.y) + if event.button == 3: # Clic droit + pthinfo = self.treeview_wishes.get_path_at_pos(x, y) + if pthinfo is not None: + path, col, cellx, celly = pthinfo + self.treeview_wishes.grab_focus() + self.treeview_wishes.set_cursor( path, col, 0) + # On affiche le popup + self.popup_test.show_all(); + self.popup_test.popup(None, None, None, None, event.button, event.time) + + + # Ouvrir l'url dans le navigateur au clic droit sur une ligne de la treeview des cigares à tester + def on_gtk_copier_test_activate(self, widget): + try: + import webbrowser + from urllib.parse import urlparse + except: + print('webbrowser not available') + return False + + selection = self.treeview_wishes.get_selection() + (model, iter) = selection.get_selected() + if iter is not None: + url = model.get_value(iter, 3) + if not urlparse(url).scheme: + url = "http://"+url + # On copie dans le presse papier + webbrowser.open(url) + + # Pour supprimer au clic droit + def on_gtk_supprimer_activate(self,widget): + id_supp = self.treeview_selection[5] + cursor = db.cursor() + sql = "delete from cigares where id = {0}".format(id_supp) + cursor.execute(sql) + sql = "delete from achats where id_cigare = {0}".format(id_supp) + cursor.execute(sql) + sql = "delete from stocks where id_cigare = {0}".format(id_supp) + cursor.execute(sql) + sql = "delete from degustation where id_cigare = {0}".format(id_supp) + cursor.execute(sql) + sql = "delete from photos where id_cigare = {0}".format(id_supp) + cursor.execute(sql) + + db.commit() + cursor.close() + + # On recharge la treeview + self.loadtreeview() + + # Pour supprimer un cigare à tester au clic droit + def on_gtk_supprimer_test_activate(self,widget): + selection = self.treeview_wishes.get_selection() + (model, iter) = selection.get_selected() + if iter is not None: + cursor = db.cursor() + sql = "delete from tester where nom = '{0}' and provenance = '{1}' and raison = '{2}' and source = '{3}'".format(model.get_value(iter, 0), model.get_value(iter, 1), model.get_value(iter, 2), model.get_value(iter, 3)) + cursor.execute(sql) + + db.commit() + cursor.close() + + # Afficher un message d'infos pour alerter que c'est bien supprimé + self.msgdialog.format_secondary_text("Cigare à tester correctement supprimé") + self.msgdialog.run() + self.msgdialog.hide() + + # On recharge la treeview + self.on_gtk_wish_activate(self) + + # Permet le filtrage de la treeview de cigares + def filter_func(self, model, iterr, data=None): + query = self.entry_search.get_buffer().get_text() + value = self.table.get_value(iterr, 0) + + try: + if query == "": + return True + elif query in value.lower(): + return True + except AttributeError: + pass + return False + # Filtrer au changement sur entry_search + def on_entry_search_changed(self, data=None): + self.tree_filter.refilter() + + def __init__(self): + self.gladefile = "ui/ui.glade" + self.builder = gtk.Builder() + self.builder.add_from_file(self.gladefile) + self.builder.connect_signals(self) + self.window = self.builder.get_object("window_main") + self.aboutdialog = self.builder.get_object("aboutdialog") + self.statusbar = self.builder.get_object("window_main_statusbar") + self.context_id = self.statusbar.get_context_id("exemple") + self.table = self.builder.get_object("liststore_treeview_cigares") + self.dialog_add_cigare = self.builder.get_object("dialog_add_cigare") + self.dialog_add_marque = self.builder.get_object("dialog_add_marque") + self.dialog_add_module = self.builder.get_object("dialog_add_module") + + # Modification/Suppression d'une note de dégustation + self.window_modif_degustation = self.builder.get_object("window_modif_degustation") + self.liststore_date_modif_degustation = self.builder.get_object("liststore_date_modif_degustation") + self.cbchoix_date_modif_degustation = self.builder.get_object("cbchoix_date_modif_degustation") + self.scaleq1m = self.builder.get_object("scaleq1m") + self.scaleq2m = self.builder.get_object("scaleq2m") + self.scaleq3m = self.builder.get_object("scaleq3m") + self.scaleq4m = self.builder.get_object("scaleq4m") + self.scaleq5m = self.builder.get_object("scaleq5m") + self.scaleq6m = self.builder.get_object("scaleq6m") + self.scaleq7m = self.builder.get_object("scaleq7m") + self.scaleq8m = self.builder.get_object("scaleq8m") + self.scaleq9m = self.builder.get_object("scaleq9m") + self.scaleq10m = self.builder.get_object("scaleq10m") + self.scaleq11m = self.builder.get_object("scaleq11m") + self.scaleq12m = self.builder.get_object("scaleq12m") + self.scaleq13m = self.builder.get_object("scaleq13m") + self.lbl_note_totale = self.builder.get_object("lbl_note_totale") + self.txtview_commentaire_modif = self.builder.get_object("txtview_commentaire_modif") + + self.cbmarques = self.builder.get_object("cbmarques") + self.cbchoixcigare = self.builder.get_object("cbchoixcigare") + self.tablechoixcigare = self.builder.get_object("liststore_cigares_a_noter") + self.scaleq1 = self.builder.get_object("scaleq1") + self.scaleq2 = self.builder.get_object("scaleq2") + self.scaleq3 = self.builder.get_object("scaleq3") + self.scaleq4 = self.builder.get_object("scaleq4") + self.scaleq5 = self.builder.get_object("scaleq5") + self.scaleq6 = self.builder.get_object("scaleq6") + self.scaleq7 = self.builder.get_object("scaleq7") + self.scaleq8 = self.builder.get_object("scaleq8") + self.scaleq9 = self.builder.get_object("scaleq9") + self.scaleq10 = self.builder.get_object("scaleq10") + self.scaleq11 = self.builder.get_object("scaleq11") + self.scaleq12 = self.builder.get_object("scaleq12") + self.scaleq13 = self.builder.get_object("scaleq13") + self.tablemarques = self.builder.get_object("liststore_marques") + self.cbmodules = self.builder.get_object("cbmodules") + self.tablemodules = self.builder.get_object("liststore_modules") + self.entry_nom_cigare = self.builder.get_object("entry_nom_cigare") + self.window_degustation = self.builder.get_object("window_degustation") + self.txtview_commentaire = self.builder.get_object("txtview_commentaire") + self.popup = self.builder.get_object("popup") + self.popup_test = self.builder.get_object("popup_test") + self.filechooser_image = self.builder.get_object("filechooser_image") + self.filechooser_bdd = self.builder.get_object("filechooser_bdd") + + + # Fenêtre d'ajout d'une marque + self.entry_marque = self.builder.get_object("entry_marque") + self.entry_provenance = self.builder.get_object("entry_provenance") + + # Fenêtre des cigares à tester + self.txtview_test_raison = self.builder.get_object("txtview_test_raison") + self.entry_test_nom = self.builder.get_object("entry_test_nom") + self.entry_test_source = self.builder.get_object("entry_test_source") + self.entry_test_origine = self.builder.get_object("entry_test_origine") + + self.lbl_marque = self.builder.get_object("lbl_marque") + self.lbl_module = self.builder.get_object("lbl_module") + + self.msgdialog = self.builder.get_object("msgdialog") + + self.tablechoixcig = self.builder.get_object("listestore_liste_cigares") + self.cbchoixcigqte = self.builder.get_object("cbchoixcigqte") + self.lblqte = self.builder.get_object("lblqte") + self.dialog_stocks = self.builder.get_object("dialog_stocks") + self.scaleqte = self.builder.get_object("scaleqte") + self.adjqte = self.builder.get_object("adjqte") + # Assistant pour la documentation de l'appli + self.doc = self.builder.get_object("assistant") + # Image pour afficher le preview dans la zone rétractable + self.image_preview = self.builder.get_object("image_preview") + # Date pour la dégustation + self.DegustationDate = self.builder.get_object("calendar_degustation") + + # Fenêtre de gestion des achats + self.dialog_achats = self.builder.get_object("dialog_achats") + # Combo pour la liste des cigares (achats) - utilise aussi listestore_choix_cigare_qte + self.cb_liste_cigares_achats = self.builder.get_object("cb_liste_cigares_achats") + # Scale pour la quantité achetée + self.scale_qte_achats = self.builder.get_object("scale_qte_achats") + # Entry pour le prix d'achat + self.entry_prix_achats = self.builder.get_object("entry_prix_achats") + # Notebook (tabs) pour les achats + self.notebook_achats = self.builder.get_object("notebook_achats") + self.scale_typeboite = self.builder.get_object("scale_typeboite") + self.entry_prix_boite = self.builder.get_object("entry_prix_boite") + self.entry_code_boite = self.builder.get_object("entry_code_boite") + self.lbl_prix_unite_boite = self.builder.get_object("lbl_prix_unite_boite") + + # Calendar pour la date d'achat + self.calendar_date_achats = self.builder.get_object("calendar_date_achats") + # Fenêtre pour la liste des cigares à tester + self.window_wishes = self.builder.get_object("window_wishes") + # Fenêtre d'ajout d'un cigare à tester + self.dialog_add_wish = self.builder.get_object("dialog_add_wish") + # Treeview wishes + self.treeview_wishes = self.builder.get_object("treeview_wishes") + # Liststore associé à treeview wishes + self.liststore_wishes = self.builder.get_object("liststore_wishes") + + # Ligne séléctionnée au changement sur la treeview + self.treeview_selection = [] + # Chemin de l'image preview + self.imgpath = "" + self.window_last_h = self.window.get_size()[1] + self.window_last_w = self.window.get_size()[0] + + # Recherche et filrage d'un cigare + self.entry_search = self.builder.get_object("entry_search") + + # Filter la treeview des cigares + self.tree_filter = self.table.filter_new() + self.tree_filter.set_visible_func(self.filter_func) + self.filtered_model = gtk.TreeModelSort(model=self.tree_filter) + + # Treeview de la liste des cigares + self.tree = self.builder.get_object("treeview_cigares") + + # On défini le modèle pour la treeview qui permet le filtrage + self.tree.set_model(self.filtered_model) + + # Constuire l'entête du treeview + self.editable_cell_nom = gtk.CellRendererText() + self.no_editable_cell = gtk.CellRendererText() + self.editable_cell_qte = gtk.CellRendererText() + self.invisible_cell = gtk.CellRendererText() + + self.editable_cell_nom.connect('edited', self.cell_edited_callback,0) + self.editable_cell_nom.set_property('editable', True) + + self.col_nom = gtk.TreeViewColumn('Nom', self.editable_cell_nom, text=0, background=11) + self.col_nom.set_sort_column_id(0) + self.tree.append_column(self.col_nom) + + # Recherche d'un cigare + self.tree.set_search_column(0) + self.tree.set_search_entry(self.entry_search) + + self.no_editable_cell.set_property('editable', False) + + self.col_marque = gtk.TreeViewColumn('Marque', self.no_editable_cell, text=1, background=11) + self.col_marque.set_sort_column_id(1) + self.tree.append_column(self.col_marque) + + self.col_module= gtk.TreeViewColumn('Module', self.no_editable_cell, text=2, background=11) + self.col_module.set_sort_column_id(2) + self.tree.append_column(self.col_module) + + self.col_provenance= gtk.TreeViewColumn('Terroir', self.no_editable_cell, text=3, background=11) + self.col_provenance.set_sort_column_id(3) + self.tree.append_column(self.col_provenance) + + self.editable_cell_qte.connect('edited', self.cell_edited_callback,4) + self.editable_cell_qte.set_property('editable', True) + self.col_qte = gtk.TreeViewColumn('Quantité', self.editable_cell_qte, text=4, background=11) + self.col_qte.set_sort_column_id(4) + self.tree.append_column(self.col_qte) + + # colonne invisible pour l'id_cigare + self.invisible_cell.set_property('visible',False) + self.col_id = gtk.TreeViewColumn('id', self.invisible_cell, text=5, background=11) + self.col_id.set_sort_column_id(5) + + self.col_best_note = gtk.TreeViewColumn('Meilleure note', self.no_editable_cell, text=6, background=11) + self.col_best_note.set_sort_column_id(6) + self.tree.append_column(self.col_best_note) + + self.col_mid_note = gtk.TreeViewColumn('Note moyenne', self.no_editable_cell, text=7, background=12) + self.col_mid_note.set_sort_column_id(7) + self.tree.append_column(self.col_mid_note) + + self.col_prix = gtk.TreeViewColumn('Prix', self.no_editable_cell, text=8, background=11) + # On utilise l'id de la col_tri (id = 10) qui contient le prix en float pour le tri + self.col_prix.set_sort_column_id(10) + self.tree.append_column(self.col_prix) + + # colonne invisible pour le commentaire de dégustation + self.col_comment = gtk.TreeViewColumn('commentaire', self.invisible_cell, text=9) + self.col_comment.set_sort_column_id(9) + # On défini cette colonne comme colonne d'infobulle + self.tree.set_tooltip_column(9) + + # colonne invisible pour le tri des prix + self.col_tri = gtk.TreeViewColumn('tri', self.invisible_cell, text=10) + + # colonne invisible pour la couleur d'une ligne + self.col_couleur = gtk.TreeViewColumn('couleur', self.invisible_cell, text=11) + + # colonne invisible pour la couleur du top 5 + self.col_couleurtop5 = gtk.TreeViewColumn('couleurtop5', self.invisible_cell, text=12) + + # Utiliser gtkspellcheck dans la txtview des commentaires + # http://koehlma.github.io/projects/pygtkspellcheck.html + try: + from gtkspellcheck import SpellChecker + SpellChecker(self.txtview_commentaire, locale.getdefaultlocale()[0]) + SpellChecker(self.txtview_commentaire_modif, locale.getdefaultlocale()[0]) + SpellChecker(self.txtview_test_raison, locale.getdefaultlocale()[0]) + except: + print("gtkspellcheck n'est pas installé") + pass + + self.window.show_all() + # On charge la liste des cigares + self.loadtreeview() + +# Vérifier qu'il s'agit d'un nombre +def is_number(s): + try: + float(s) + return True + except ValueError: + return False + +def createdb(new_bdd): + db = sqlite3.connect(new_bdd) + cursor = db.cursor() + sql = 'create table achats' + \ + '(id INTEGER PRIMARY KEY, id_cigare INTEGER, date TEXT, code_boite TEXT, prix FLOAT, quantite INTEGER)' + cursor.execute(sql) + sql = 'create table cigares' + \ + '(id INTEGER PRIMARY KEY, designation TEXT, marque INTEGER, module INTEGER)' + cursor.execute(sql) + sql = 'create table degustation' + \ + '(id INTEGER PRIMARY KEY, id_cigare INTEGER, date TEXT, note NUMERIC, commentaires TEXT, q1 INTEGER, q2 INTEGER, q3 INTEGER, q4 INTEGER, q5 INTEGER, q6 INTEGER, q7 INTEGER, q8 INTEGER, q9 INTEGER, q10 INTEGER, q11 INTEGER, q12 INTEGER, q13 INTEGER)' + cursor.execute(sql) + sql = 'create table marques' + \ + '(id INTEGER PRIMARY KEY, libelle TEXT, provenance TEXT)' + cursor.execute(sql) + sql = 'create table modules' + \ + '(id INTEGER PRIMARY KEY, module TEXT, calibre TEXT, diametre TEXT, longueur TEXT)' + cursor.execute(sql) + sql = 'create table photos' + \ + '(id INTEGER PRIMARY KEY, id_cigare INTEGER, photo TEXT)' + cursor.execute(sql) + sql = 'create table stocks' + \ + '(id INTEGER PRIMARY KEY, id_cigare INTEGER, quantite INTEGER)' + cursor.execute(sql) + sql = 'create table tester' + \ + '(id INTEGER PRIMARY KEY, nom TEXT, provenance TEXT, raison TEXT, source TEXT)' + cursor.execute(sql) + + # INSERT à mettre dans un fichier à part + sql = "INSERT INTO modules VALUES(1, 'Laguito N °3', '26', '10.32', '115')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(2, 'Carolina', '26', '10.32', '121')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(3, 'Panetela Larga', '28', '11.11', '175')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(4, 'Chico', '29', '11.51', '106')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(5, 'Entreacto', '30', '11.91', '100')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(6, 'Palmita', '32', '12.70', '152')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(7, 'Delicioso', '33', '13.10', '159')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(8, 'Palma', '33', '13.10', '170')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(9, 'Ninfa', '33', '13.10', '178')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(10, 'Panetela', '34', '13.49', '117')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(11, 'Placera', '34', '13.49', '125')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(12, 'Epicure', '35', '13.89', '110')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(13, 'Sport', '35', '13.89', '117')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(14, 'Conchita', '35', '13.89', '127')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(15, 'Carlota', '35', '13.89', '143')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(16, 'Cadete', '36', '14.29', '115')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(17, 'Seoane', '36', '14.29', '125')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(18, 'Veguerito', '36', '14.29', '127')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(19, 'Delicado Extra', '36', '14.29', '185')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(20, 'Trabuco', '38', '15.08', '110')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(21, 'Laguito N ° 2', '38', '15.08', '152')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(22, 'Parejo', '38', '15.08', '166')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(23, 'Delicado', '38', '15.08', '192')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(24, 'Laguito N ° 1', '38', '15.08', '192')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(25, 'Belvedere', '39', '15.48', '125')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(26, 'Perla', '40', '15.87', '102')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(27, 'Franciscano', '40', '15.87', '116')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(28, 'Coronita', '40', '15.87', '117')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(29, 'Standard', '40', '15.87', '123')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(30, 'Londres', '40', '15.87', '126')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(31, 'Petit Cetro', '40', '15.87', '129')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(32, 'Almuerzo', '40', '15.87', '130')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(33, 'Crema', '40', '15.87', '140')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(34, 'Laguito No.1', '40', '15.87', '192')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(35, 'Minuto', '42', '16.67', '110')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(36, 'Mareva', '42', '16.67', '129')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(37, 'Petit Corona', '42', '16.67', '127')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(38, 'Eminente', '42', '16.67', '132')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(39, 'Nacional', '42', '15.87', '134')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(40, 'Cosaco', '42', '16.67', '135')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(41, 'Corona', '42', '16.67', '140')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(42, 'Corona Grande', '42', '16.67', '155')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(43, 'Cervante', '42', '16.67', '165')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(44, 'Conserva', '43', '17.07', '145')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(45, 'Cazadore', '43', '17.07', '162')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(46, 'Dalia', '43', '17.07', '170')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(47, 'Francisco', '44', '17.46', '143')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(48, 'Corona Gorda', '46', '18.26', '143')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(49, 'Taco', '47', '18.65', '158')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(50, 'Julieta / Churchill', '47', '18.65', '178')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(51, 'Gran Corona', '47', '18.65', '235')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(52, 'Hermoso N ° 4', '49', '19.05', '127')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(53, 'Paco', '49', '19.45', '180')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(54, 'Double corona', '49', '19.45', '194')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(55, 'Robusto', '50', '19.84', '127')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(56, 'Gordito', '50', '19.84', '141')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(57, 'Campana', '52', '20.64', '140')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(58, 'Panetelas Extra', '37', '13.49', '127')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(59, 'Oficios (Corona)', '43', '17.07', '135')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(60, 'Majestic (Petit Corona)', '39', '15.87', '140')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(61, 'Figurados', '60', '20+', '150+')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(62, 'Torpedo', '52', '20.8', '125')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(63, 'short robusto', '60', '24', '102')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(64, 'Mini Panetella', '20', '8', '125')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(65, 'Indéfini', '0', '0', '0')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(66, 'Très petit corona', '40', '15.88', '102')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(67, 'Toro', '52', '20.8', '152')" + cursor.execute(sql) + sql = "INSERT INTO modules VALUES(68, 'Prominente', '49', '19.45', '194')" + cursor.execute(sql) + + # MARQUES + sql = "INSERT INTO marques VALUES(1, 'Cohiba', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(2, 'Trinidad', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(3, 'Vegas Robaina', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(4, 'Montecristo', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(5, 'Cuaba', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(6, 'Romeo y Julieta', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(7, 'Partagas', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(8, 'San Cristobal de la Habana', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(9, 'Punch', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(10, 'Hoyo de Monterrey', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(11, 'Bolivar', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(12, 'La Gloria Cubana', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(13, 'H.Upmann', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(14, 'Fonseca', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(15, 'La Flor de Cano', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(16, 'Troya', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(17, 'Quintero', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(18, 'Los Statos de Luxe', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(19, 'Caney', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(20, 'Belinda', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(21, 'Cabanas', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(22, 'La Corona', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(23, 'José L.Piedra', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(24, 'Cumpay', 'Nicaragua')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(25, 'CAO', 'Nicaragua')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(26, 'Dunhill', 'Nicaragua')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(27, 'Nicarao', 'Nicaragua')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(28, 'Oliva', 'Nicaragua')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(29, 'Padron', 'Nicaragua')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(30, 'Flor de Copan', 'Honduras')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(31, 'Flor de Selva', 'Honduras')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(32, 'J. Cortès', 'Honduras')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(33, 'Zino', 'Honduras')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(34, 'Villa Zamorano', 'Honduras')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(35, 'Flor de Rafaël Gonzáles Márquez', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(36, 'Macanudo', 'République Dominicaine')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(37, 'Pléiades', 'Nicaragua')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(38, 'Toscano', 'Italie')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(39, 'Davidoff', 'cuba')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(40, 'Avo Uvezian', 'République Dominicaine')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(41, 'Don Pepin Garcia', 'Nicaragua')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(42, 'O Line', 'Nicaragua')" + cursor.execute(sql) + sql = "INSERT INTO marques VALUES(43, 'Plasencia', 'Nicaragua')" + cursor.execute(sql) + + db.commit() + +def checkconfig(): + """Récupérer la configuration ou la créer""" + # Fichier de configuration + #configfile = os.path.expanduser("~/.config/cltwit.conf") + # On ouvre le fichier de conf + config = configparser.RawConfigParser() + try: + config.read(__CONFIGFILE__) + if config.has_option('MesCigares','bdd'): + bdd = config.get('MesCigares','bdd') + except: + pass + + if not os.path.exists(bdd): + createdb(bdd) + + # Si aucune conf + if not(config.has_option('MesCigares','bdd')): + bdd = __CONFDIR__ + "/bdd/main.db" + # écrire le fichier de conf avec les informations par defaut + try: + cfgfile = open(__CONFIGFILE__,'w') + if not(config.has_section('MesCigares')): + config.add_section('MesCigares') + config.set('MesCigares','bdd',bdd) + config.write(cfgfile) + except IOError: + pass + finally: + cfgfile.close + createdb(bdd) + return(bdd) + +def setconfig(new_bdd): + """Mettre à jour la configuration""" + config = configparser.RawConfigParser() + try: + cfgfile = open(__CONFIGFILE__,'w') + if not(config.has_section('MesCigares')): + config.add_section('MesCigares') + config.set('MesCigares','bdd',new_bdd) + config.write(cfgfile) + except IOError: + pass + finally: + cfgfile.close + +if __name__ == "__main__": + dbconf = checkconfig() + db = sqlite3.connect(dbconf) + main = MesCigares() + main.window.set_title("MesCigares - base : {0}".format(ntpath.basename(dbconf)).replace('.db','')) + gtk.main() diff --git a/app_gestion/MesCigares/bdd/jerome.db b/app_gestion/MesCigares/bdd/jerome.db new file mode 100644 index 0000000..23e5a0f Binary files /dev/null and b/app_gestion/MesCigares/bdd/jerome.db differ diff --git a/app_gestion/MesCigares/bdd/main.db b/app_gestion/MesCigares/bdd/main.db new file mode 100644 index 0000000..f2efd4f Binary files /dev/null and b/app_gestion/MesCigares/bdd/main.db differ diff --git a/app_gestion/MesCigares/conf/mescigares.conf b/app_gestion/MesCigares/conf/mescigares.conf new file mode 100644 index 0000000..d5fbdf7 --- /dev/null +++ b/app_gestion/MesCigares/conf/mescigares.conf @@ -0,0 +1,3 @@ +[MesCigares] +bdd = /home/jlaunay/Documents/Cigares/app_gestion/MesCigares/bdd/jerome.db + diff --git a/app_gestion/MesCigares/images/MesCigares.ico b/app_gestion/MesCigares/images/MesCigares.ico new file mode 100644 index 0000000..91b2368 Binary files /dev/null and b/app_gestion/MesCigares/images/MesCigares.ico differ diff --git a/app_gestion/MesCigares/images/bad_photo.jpg b/app_gestion/MesCigares/images/bad_photo.jpg new file mode 100644 index 0000000..d793168 Binary files /dev/null and b/app_gestion/MesCigares/images/bad_photo.jpg differ diff --git a/app_gestion/MesCigares/images/no_photo.jpg b/app_gestion/MesCigares/images/no_photo.jpg new file mode 100644 index 0000000..28956ca Binary files /dev/null and b/app_gestion/MesCigares/images/no_photo.jpg differ diff --git a/app_gestion/MesCigares/ui/ui.glade b/app_gestion/MesCigares/ui/ui.glade new file mode 100644 index 0000000..0063897 --- /dev/null +++ b/app_gestion/MesCigares/ui/ui.glade @@ -0,0 +1,3858 @@ + + + + + False + 5 + False + True + center-on-parent + dialog + window_main + MesCigares + version de test + Copyright © Jérôme Launay + Logiciel de gestion de cave à cigares + http://www.projet-libre.org + GPL + Jérôme Launay + True + gpl-3-0 + + + False + vertical + 2 + + + False + end + + + False + True + end + 0 + + + + + + + 50 + 1 + 1 + 10 + + + 1 + 4 + 1 + 1 + 1 + + + 1 + 3 + 1 + 1 + 1 + + + 1 + 3 + 1 + 1 + 1 + + + 1 + 20 + 1 + 1 + 1 + + + 1 + 3 + 1 + 1 + 1 + + + 1 + 80 + 1 + 1 + 10 + + + 1 + 3 + 1 + 1 + 1 + + + 1 + 3 + 1 + 1 + 1 + + + 5 + 25 + 5 + 5 + 5 + + + 2 + 10 + 2 + 2 + 2 + + + 2 + 10 + 2 + 2 + 2 + + + 2 + 10 + 2 + 2 + 2 + + + 1 + 3 + 1 + 1 + 1 + + + 1 + 3 + 1 + 1 + 1 + + + 50 + 25 + 1 + 5 + + + False + Comment utiliser MesCigares + center-on-parent + window_main + + + + + + + + True + False + 1 - Commencez par ajouter un cigare via "menu" -> "Ajouter un cigare" + Vous pouvez aussi utiliser le raccourci clavier CTRL + A + +2 - Une fois un cigare ajouté vous pouvez utiliser "Actions" -> "Gérer les achats" + pour définir la quantité et le prix pour le cigare précédement ajouté. + Vous pouvez aussi utiliser le raccourci clavier ALT + A + +3 - Pour noter une dégustation, utilisez le menu "Actions" -> "Dégustation et notation d'un cigare" + Cette action donnera une note sur 100 au cigare choisi et vous pourez ensuite modifier le + commentaire sur ce cigare qui apparait lors de son survol dans la liste + + + + intro + True + + + + + True + False + Un clic droit sur un cigare dans la liste principale permet : + + 1 - D'insérer une image qui sera visible dans la zone retractable d'aperçu (en bas à gauche). + + 2 - Modifier le dernier commentaire laissé pour un cigare lors d'une précédente dégustation. + + 3 - Supprimer le cigare séléctionné. + + + True + + + + + True + False + - Utilisez le raccourci CTRL +F ou cliquer dans le champ texte en haut + à droite pour filter la liste des cigares. + + + A continuer ! + + + confirm + True + + + + + + + + + + + False + end + 6 + + + + + False + 5 + Gérer les achats + False + dialog + + + False + vertical + 2 + + + True + False + vertical + + + True + False + 15 + Veuillez choisir un cigare dans la liste ci-dessous ou l'ajouter +depuis le menu principal s'il n'est pas dans la liste. + + + False + True + 0 + + + + + True + False + listestore_liste_cigares + + + + 1 + + + + + False + True + 1 + + + + + True + True + + + True + False + vertical + + + True + False + 0 + 0 + 10 + Veuillez renseigner la quantité achetée, le prix et la date. + + + False + True + 0 + + + + + True + False + + + 110 + True + False + 0 + Quantité: + + + False + True + 0 + + + + + True + True + adjustment15 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 1 + + + + + True + False + + + 110 + True + False + 0 + Prix: + + + False + True + 0 + + + + + True + True + 10 + + True + number + + + False + True + 1 + + + + + False + True + 2 + + + + + + + True + False + A l'unité + + + False + + + + + True + False + vertical + + + True + False + 0 + 0 + 10 + Veuillez renseigner le type de boite achetée ainsi que le prix et la date. + + + False + True + 0 + + + + + True + False + + + 200 + True + False + 0 + Nombre de cigares dans la boite: + + + False + True + 0 + + + + + True + True + adjustment_typeboite + 0 + 0 + left + + + + True + True + 1 + + + + + False + True + 1 + + + + + True + False + + + 200 + True + False + 0 + Prix de la boite: + + + False + True + 0 + + + + + True + True + 10 + + + + + False + True + 1 + + + + + 140 + True + False + + + + + False + True + 2 + + + + + False + True + 2 + + + + + True + False + + + 200 + True + False + 0 + Code de la boite (facultatif): + + + False + True + 0 + + + + + True + True + + + + False + True + 1 + + + + + False + True + 3 + + + + + 1 + + + + + True + False + Par boite + + + 1 + False + + + + + False + True + 2 + + + + + True + False + + + 110 + True + False + 0 + 0 + 10 + Date: + + + False + True + 0 + + + + + True + True + 10 + + + False + True + 1 + + + + + False + True + 3 + + + + + False + True + 0 + + + + + False + end + + + Annuler + True + True + True + + + + False + True + 0 + + + + + OK + True + True + True + + + + False + True + 1 + + + + + False + True + end + 0 + + + + + + bt_cancel_achats + bt_valid_achats + + + + False + 5 + Ajouter un cigare + False + True + center-on-parent + dialog + window_main + + + False + vertical + 2 + + + False + end + + + Annuler + True + True + True + 0.56999999284744263 + + + False + True + 0 + + + + + Ajouter + True + True + True + 0.47999998927116394 + + + + + False + True + 1 + + + + + False + True + end + 0 + + + + + True + False + vertical + + + True + False + + + 60 + True + False + 0 + Nom: + + + False + True + 0 + + + + + True + True + + + + True + True + 1 + + + + + False + True + 0 + + + + + True + False + + + 60 + True + False + 0 + Marque: + + + False + True + 0 + + + + + True + False + liststore_marques + + + + + 1 + + + + + True + True + 1 + + + + + False + True + 1 + + + + + 25 + True + False + 0 + + + False + True + 2 + + + + + True + False + + + 60 + True + False + 0 + Module: + + + False + True + 0 + + + + + True + False + liststore_modules + + + + + 1 + + + + + True + True + 1 + + + + + False + True + 3 + + + + + 25 + True + False + 0 + + + False + True + 4 + + + + + False + True + 1 + + + + + + button2 + btadd + + + + False + 5 + center-on-parent + dialog + window_main + + + False + vertical + 2 + + + False + end + + + Annuler + True + True + True + + + False + True + 0 + + + + + Ajouter + True + True + True + + + + False + True + 1 + + + + + False + True + end + 0 + + + + + True + False + vertical + + + True + False + + + 50 + True + False + 0.49000000953674316 + Marque: + + + False + True + 0 + + + + + True + True + + + + True + True + 1 + + + + + False + True + 0 + + + + + True + False + + + 50 + 39 + True + False + 0 + 1 + Terroir : +(Cuba...) + + + False + True + 0 + + + + + True + True + + + + True + True + 1 + + + + + False + True + 1 + + + + + False + True + 1 + + + + + + button1 + bt_add_marque + + + + False + 5 + center-on-parent + dialog + window_main + + + False + vertical + 2 + + + False + end + + + Annuler + True + True + True + + + False + True + 0 + + + + + Ajouter + True + True + True + + + False + True + 1 + + + + + False + True + end + 0 + + + + + + + + + button9 + bt_add_module + + + + False + 5 + False + center-on-parent + dialog + window_wishes + + + False + vertical + 2 + + + False + end + + + Annuler + True + True + True + + + False + True + 0 + + + + + Ajouter + True + True + True + + + + False + True + 1 + + + + + False + True + end + 0 + + + + + True + False + + + True + False + Nom : + + + False + True + 0 + + + + + True + True + + True + + + False + True + 1 + + + + + True + False + Terroir : + + + False + True + 2 + + + + + True + True + + True + + + False + True + 3 + + + + + True + False + Source (url): + + + False + True + 4 + + + + + True + True + + True + + + False + True + 5 + + + + + True + False + Raison : + + + False + True + 6 + + + + + 164 + True + True + 1 + in + + + True + True + + + + + False + True + 7 + + + + + False + True + 1 + + + + + + button7 + bt_valid_add_wish + + + + False + 5 + Gestion du stock + False + center-on-parent + dialog + window_main + + + False + vertical + 2 + + + False + end + + + Annuler + True + True + True + + + False + True + 0 + + + + + Valider + True + True + True + + + + False + True + 1 + + + + + False + True + end + 0 + + + + + True + False + vertical + True + + + True + False + Veuillez choisir un cigare ci-dessous et ajuster la quantitée + + + False + True + 0 + + + + + True + False + listestore_liste_cigares + + + + + 1 + + + + + False + True + 1 + + + + + True + True + adjqte + 0 + 0 + + + False + True + 2 + + + + + False + True + 1 + + + + + + button4 + btstockvalid + + + + False + 5 + GtkFileChooserDialog + center-on-parent + dialog + window_main + + + + False + vertical + 2 + + + False + end + + + Annuler + True + True + True + + + False + True + 0 + + + + + OK + True + True + True + + + + False + True + 1 + + + + + False + True + end + 0 + + + + + + btchooseko1 + btchooseokbdd + + + + False + 5 + GtkFileChooserDialog + center-on-parent + dialog + window_main + + + + False + vertical + 2 + + + False + end + + + Annuler + True + True + True + + + False + True + 0 + + + + + OK + True + True + True + + + + False + True + 1 + + + + + False + True + end + 0 + + + + + + btchooseko + btchooseok + + + + True + False + gtk-help + + + True + False + gtk-info + + + True + False + gtk-add + + + True + False + gtk-add + + + True + False + gtk-edit + + + True + False + 0.43999999761581421 + gtk-new + + + True + False + gtk-edit + + + True + False + gtk-info + + + True + False + gtk-dialog-question + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + 5 + center-on-parent + dialog + True + window_main + + + False + vertical + 2 + + + False + spread + + + OK + True + True + True + + + False + True + 1 + + + + + True + True + end + 0 + + + + + + button6 + + + + True + True + True + + + True + False + Insérer une photo de ce cigare + + + + + + True + False + Modifier/Supprimer une dégustation + + + + + + True + False + Supprimer + + + + + + True + True + True + + + True + False + Supprimer + + + + + + True + False + Ouvrir l'url source dans le navigateur + + + + + + False + MesCigares + center + 1400 + 750 + ../images/MesCigares.ico + + + + + True + False + vertical + + + True + False + + + True + False + + + True + False + Menu + + + True + False + + + Ajouter un cigare + True + False + image_gtk_new + False + + + + + + + Changer de base de données + True + False + image_gtk_new1 + False + + + + + + + True + False + + + + + gtk-quit + True + False + True + True + + + + + + + + + + + True + False + Actions + + + True + False + + + Gérer les achats + True + False + image_gtk_achats + False + + + + + + + Gérer le stock + True + False + image_gtk_stocks + False + + + + + + + True + False + + + + + Dégustation et notation d'un cigare + True + False + image_gtk_degustation + False + + + + + + + True + False + + + + + Ajouter un module + True + False + False + image_gtk_add_module + False + + + + + + Ajouter une marque + True + False + image_gtk_add_marque + False + + + + + + + True + False + + + + + Cigares à tester + True + False + image_gtk_wish + False + + + + + + + + + + + True + False + False + Rapports + + + True + False + + + gtk-about + True + False + True + True + + + + + + + + + True + False + Aide + + + True + False + + + Comment utiliser MesCigares + True + False + image1 + False + + + + + + gtk-about + True + False + True + True + + + + + + + + + + False + True + 0 + + + + + True + True + + + + + + False + True + end + 0 + + + + + True + False + (Ctrl + f) + + + + + + + False + True + end + 1 + + + + + True + False + gtk-find + + + False + True + end + 3 + + + + + False + True + 0 + + + + + True + True + in + + + True + True + True + liststore_treeview_cigares + 0 + both + + + + + + + + + + True + True + 2 + + + + + True + True + + + True + False + gtk-missing-image + + + + + True + False + Aperçu + + + + + False + True + 3 + + + + + True + False + vertical + 2 + + + False + True + 4 + + + + + + + False + 5 + Dégustation + center-on-parent + 1000 + 600 + dialog + window_main + + + False + vertical + 2 + + + False + end + + + Annuler + True + True + True + + + False + True + 2 + + + + + Valider + True + True + True + + + + False + True + 3 + + + + + False + True + end + 0 + + + + + True + True + in + + + True + False + + + True + False + vertical + + + True + False + + + True + False + 10 + Veuillez choisir le cigare à noter : + + + False + True + 0 + + + + + True + False + liststore_cigares_a_noter + + + + 1 + + + + + True + True + 1 + + + + + False + True + 0 + + + + + True + False + + + True + False + 5 + 1) L’aspect visuel : 0/3 + 1= aspect très rustique/ nervures proéminentes/ cape rugueuse/ couleur inégale + 2= aspect classique/ nervures proéminentes/ cape lisse/ couleur uniforme + 3= aspect excellent/ très légères nervures/ cape lisse et grasse/ couleur uniforme + + + False + True + 0 + + + + + True + True + 77 + adjustment14 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 1 + + + + + True + False + + + True + False + 5 + 2) Le toucher : 0/3 + 1= toucher très ferme, très léger + 2= toucher très moelleux, mou + 3= toucher, ferme et moelleux + + + False + True + 0 + + + + + True + True + 355 + adjustment2 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 2 + + + + + True + False + + + True + False + 5 + 3) Le nez (parfum) 0/3 + 1= parfum inexistant ou très puissant, désagréable + 2=parfum très léger et moyen + 3= parfum léger et agréable + + + False + True + 0 + + + + + True + True + 253 + adjustment3 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 3 + + + + + True + False + + + True + False + 5 + 4) Richesse et évolution aromatique 0/25 + 5= inexistante (absente) + 10= moyenne (une dominante sur 1 arome) + 15= bonne (2 aromes maximum) + 20= très bonne (3 aromes max) + 25= excellente (3 à 5 aromes, bonne évolution) + + + False + True + 0 + + + + + True + True + 266 + adjustment4 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 4 + + + + + True + False + + + True + False + 5 + 5) Perception et sensation des saveurs 0/10 + 2= très fade ou trop puissante + 4= moyenne + 6= bonne + 8= très bonne + 10= excellent + + + False + True + 0 + + + + + True + True + 305 + adjustment5 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 5 + + + + + True + False + + + True + False + 5 + 6) Puissance 0/10 + 2= puissance inexistante/ trop forte/ écrasant + 4= puissance très légère + 6= puissance moyenne + 8= puissance forte et équilibrée + 10= puissance modérée et équilibrée + + + False + True + 0 + + + + + True + True + 279 + adjustment6 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 6 + + + + + True + False + + + True + False + 5 + 7) longueur en bouche (durée pendant laquelle les goûts et les arômes restent en bouche) 0/10 + 2= courte/ éphémère (les aromes s’évanouissent au bout de quelques secondes) + 4= moyenne (5 à 10sec) + 6= bonne (10 à 15sec) + 8= très bonne (20 à 35sec) + 10= excellente (les aromes persistent de 40 à 50sec, jusqu’à la prochaine bouffée) + + + False + True + 0 + + + + + True + True + 12 + adjustment7 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 7 + + + + + True + False + + + True + False + 5 + 8) Rondeur en bouche 0/3 + 1= agressive, acidité, acre ou trop suave + 2= douce/ moyenne + 3= grasse et enveloppante/ dense et rassasiante + + + False + True + 0 + + + + + True + True + 264 + adjustment8 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 8 + + + + + True + False + + + True + False + 5 + 9) tirage 0/3 + 1= Tirage bouché/ trop serré + 2= Tirage serré/ moyen + 3= Tirage très bon/ ventilé + + + False + True + 0 + + + + + True + True + 380 + adjustment9 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 9 + + + + + True + False + + + True + False + 5 + 10) Combustion 0/4 + 1= Mauvaise (5 à 10 allumages) + 2= Moyenne (1 à 5) + 3= Acceptable (1 à 2) + 4= Excellente (aucun) + + + False + True + 0 + + + + + True + True + 364 + adjustment10 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 10 + + + + + True + False + + + True + False + 5 + 11) Cendre 0/3 + 1= très friable/ très légère + 2= Ferme et irrégulière + 3= droite et régulière/ très ferme + + + False + True + 0 + + + + + True + True + 353 + adjustment11 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 11 + + + + + True + False + + + True + False + 5 + 12) Perception du 4e tiers (une fois le cigare fumé) 0/3 + 1= exécrable + 2= moyenne + 3= très bonne + + + False + True + 0 + + + + + True + True + 250 + adjustment12 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 12 + + + + + True + False + + + True + False + 5 + 13) Note intuitive d’ensemble sur cette dégustation 0/20 + 1 à 5= aucun intérêt/ à éviter + 5 à 10= sans plus + 10 à 15= pourquoi pas, à l’occasion + 20= à renouveler sans aucun problème + + + False + True + 0 + + + + + True + True + 236 + adjustment13 + 0 + 0 + left + + + True + True + 1 + + + + + False + True + 13 + + + + + True + False + + + 140 + True + False + 10 + 130 + 0 + 0 + Commentaires : + + + False + True + 0 + + + + + True + True + 1 + in + + + True + True + 10 + + + + + True + True + 1 + + + + + False + True + 14 + + + + + True + False + + + 140 + True + False + 10 + 0 + 0 + Date de la dégusation: + + + False + True + 0 + + + + + True + True + 10 + + + False + True + 1 + + + + + False + True + 15 + + + + + + + + + True + True + 1 + + + + + + btcancel + btvalid + + + + False + 5 + Dégustation + center-on-parent + 1000 + 600 + dialog + window_main + + + False + vertical + 2 + + + False + end + + + Annuler + True + True + True + + + False + True + 0 + + + + + Supprimer cette dégustation + True + True + True + + + False + True + 1 + + + + + Valider les modifications + True + True + True + + + + False + True + 2 + + + + + False + True + end + 0 + + + + + True + True + in + + + True + False + + + True + False + vertical + + + True + False + + + True + False + 10 + Veuillez choisir la date de la degustation à modifier/supprimer : + + + False + True + 0 + + + + + 180 + True + False + liststore_date_modif_degustation + + + + + 0 + + + + + False + True + 1 + + + + + False + True + 0 + + + + + True + False + + + True + False + 5 + 1) L’aspect visuel : 0/3 + 1= aspect très rustique/ nervures proéminentes/ cape rugueuse/ couleur inégale + 2= aspect classique/ nervures proéminentes/ cape lisse/ couleur uniforme + 3= aspect excellent/ très légères nervures/ cape lisse et grasse/ couleur uniforme + + + False + True + 0 + + + + + True + True + 77 + adjustment14 + 0 + 0 + left + + + + True + True + 1 + + + + + False + True + 1 + + + + + True + False + + + True + False + 5 + 2) Le toucher : 0/3 + 1= toucher très ferme, très léger + 2= toucher très moelleux, mou + 3= toucher, ferme et moelleux + + + False + True + 0 + + + + + True + True + 355 + adjustment2 + 0 + 0 + left + + + + True + True + 1 + + + + + False + True + 2 + + + + + True + False + + + True + False + 5 + 3) Le nez (parfum) 0/3 + 1= parfum inexistant ou très puissant, désagréable + 2=parfum très léger et moyen + 3= parfum léger et agréable + + + False + True + 0 + + + + + True + True + 253 + adjustment3 + 0 + 0 + left + + + + True + True + 1 + + + + + False + True + 3 + + + + + True + False + + + True + False + 5 + 4) Richesse et évolution aromatique 0/25 + 5= inexistante (absente) + 10= moyenne (une dominante sur 1 arome) + 15= bonne (2 aromes maximum) + 20= très bonne (3 aromes max) + 25= excellente (3 à 5 aromes, bonne évolution) + + + False + True + 0 + + + + + True + True + 266 + adjustment4 + 0 + 0 + left + + + + True + True + 1 + + + + + False + True + 4 + + + + + True + False + + + True + False + 5 + 5) Perception et sensation des saveurs 0/10 + 2= très fade ou trop puissante + 4= moyenne + 6= bonne + 8= très bonne + 10= excellent + + + False + True + 0 + + + + + True + True + 305 + adjustment5 + 0 + 0 + left + + + + True + True + 1 + + + + + False + True + 5 + + + + + True + False + + + True + False + 5 + 6) Puissance 0/10 + 2= puissance inexistante/ trop forte/ écrasant + 4= puissance très légère + 6= puissance moyenne + 8= puissance forte et équilibrée + 10= puissance modérée et équilibrée + + + False + True + 0 + + + + + True + True + 279 + adjustment6 + 0 + 0 + left + + + + True + True + 1 + + + + + False + True + 6 + + + + + True + False + + + True + False + 5 + 7) longueur en bouche (durée pendant laquelle les goûts et les arômes restent en bouche) 0/10 + 2= courte/ éphémère (les aromes s’évanouissent au bout de quelques secondes) + 4= moyenne (5 à 10sec) + 6= bonne (10 à 15sec) + 8= très bonne (20 à 35sec) + 10= excellente (les aromes persistent de 40 à 50sec, jusqu’à la prochaine bouffée) + + + False + True + 0 + + + + + True + True + 12 + adjustment7 + 0 + 0 + left + + + + True + True + 1 + + + + + False + True + 7 + + + + + True + False + + + True + False + 5 + 8) Rondeur en bouche 0/3 + 1= agressive, acidité, acre ou trop suave + 2= douce/ moyenne + 3= grasse et enveloppante/ dense et rassasiante + + + False + True + 0 + + + + + True + True + 264 + adjustment8 + 0 + 0 + left + + + + True + True + 1 + + + + + False + True + 8 + + + + + True + False + + + True + False + 5 + 9) tirage 0/3 + 1= Tirage bouché/ trop serré + 2= Tirage serré/ moyen + 3= Tirage très bon/ ventilé + + + False + True + 0 + + + + + True + True + 380 + adjustment9 + 0 + 0 + left + + + + True + True + 1 + + + + + False + True + 9 + + + + + True + False + + + True + False + 5 + 10) Combustion 0/4 + 1= Mauvaise (5 à 10 allumages) + 2= Moyenne (1 à 5) + 3= Acceptable (1 à 2) + 4= Excellente (aucun) + + + False + True + 0 + + + + + True + True + 364 + adjustment10 + 0 + 0 + left + + + + True + True + 1 + + + + + False + True + 10 + + + + + True + False + + + True + False + 5 + 11) Cendre 0/3 + 1= très friable/ très légère + 2= Ferme et irrégulière + 3= droite et régulière/ très ferme + + + False + True + 0 + + + + + True + True + 353 + adjustment11 + 0 + 0 + left + + + + True + True + 1 + + + + + False + True + 11 + + + + + True + False + + + True + False + 5 + 12) Perception du 4e tiers (une fois le cigare fumé) 0/3 + 1= exécrable + 2= moyenne + 3= très bonne + + + False + True + 0 + + + + + True + True + 250 + adjustment12 + 0 + 0 + left + + + + True + True + 1 + + + + + False + True + 12 + + + + + True + False + + + True + False + 5 + 13) Note intuitive d’ensemble sur cette dégustation 0/20 + 1 à 5= aucun intérêt/ à éviter + 5 à 10= sans plus + 10 à 15= pourquoi pas, à l’occasion + 20= à renouveler sans aucun problème + + + False + True + 0 + + + + + True + True + 236 + adjustment13 + 0 + 0 + left + + + + True + True + 1 + + + + + False + True + 13 + + + + + True + False + 12 + + + + + + False + True + 14 + + + + + True + False + + + True + False + 0 + 0 + Commentaires : + + + False + True + 0 + + + + + 350 + 150 + True + True + in + + + 300 + 190 + True + True + + + + + True + True + 1 + + + + + False + True + 15 + + + + + + + + + True + True + 1 + + + + + + btcancel1 + btsupp_degustation + btvalid_degustation + + + + False + Liste des cigares à tester + center-on-parent + 600 + 400 + window_main + + + + True + False + vertical + + + True + True + in + + + True + True + Clic droit -> "Supprimer" pour enlever de la liste. +Clic droit -> "Copier l'url source" pour copier l'url source dans le presse papier. + 11 + liststore_wishes + True + 0 + both + + + + + + + True + Nom + True + True + True + 0 + + + + 0 + + + + + + + True + Terroir + True + True + True + 1 + + + + 1 + + + + + + + True + Raison + True + True + True + 2 + + + + 2 + + + + + + + True + Source + True + True + True + 3 + + + + 3 + + + + + + + False + id + + + False + + + 4 + + + + + + + + + True + True + 1 + + + + + True + False + + + Quitter + True + True + True + + + + False + True + end + 0 + + + + + Ajouter un cigare à tester + True + True + True + + + + False + True + end + 1 + + + + + False + True + end + 2 + + + + + + diff --git a/app_gestion/phpgtk/calc.glade b/app_gestion/phpgtk/calc.glade new file mode 100644 index 0000000..80b5d7f --- /dev/null +++ b/app_gestion/phpgtk/calc.glade @@ -0,0 +1,329 @@ + + + + + + True + False + + + True + False + + + True + False + + + True + False + False + _File + True + + + True + False + + + True + False + False + + + + + gtk-quit + True + False + False + True + True + + + + + + + + + + False + True + 0 + + + + + True + True + + False + False + True + True + + + True + True + 1 + + + + + True + False + 4 + 4 + + + 1 + True + True + True + False + 0.49000000953674316 + + + + 2 + 3 + + + + + 2 + True + True + True + False + + + + 1 + 2 + 2 + 3 + + + + + 3 + True + True + True + False + + + + 2 + 3 + 2 + 3 + + + + + 4 + True + True + True + False + + + + 1 + 2 + + + + + 5 + True + True + True + False + + + + 1 + 2 + 1 + 2 + + + + + 6 + True + True + True + False + + + + 2 + 3 + 1 + 2 + + + + + 7 + True + True + True + False + + + + + + 8 + True + True + True + False + + + + 1 + 2 + + + + + 9 + True + True + True + False + + + + 2 + 3 + + + + + 0 + True + True + True + False + + + + 3 + 4 + + + + + C + True + True + True + False + + + + 1 + 2 + 3 + 4 + + + + + = + True + True + True + False + + + + 2 + 3 + 3 + 4 + + + + + + + True + True + True + False + + + + 3 + 4 + + + + + - + True + True + True + False + + + + 3 + 4 + 1 + 2 + + + + + * + True + True + True + False + 0.50999999046325684 + + + + 3 + 4 + 2 + 3 + + + + + / + True + True + True + False + + + + 3 + 4 + 3 + 4 + + + + + True + True + 2 + + + + + + + diff --git a/app_gestion/phpgtk/calc.php b/app_gestion/phpgtk/calc.php new file mode 100644 index 0000000..90232fe --- /dev/null +++ b/app_gestion/phpgtk/calc.php @@ -0,0 +1,79 @@ +glade = $glade; + } + /* Calculer le résultat de l'opération */ + protected function calculate($operation) { + $secondParam = (float) $this->glade->get_widget('entry1')->get_text(); + $firstParam = (float) $this->firstParam; + $result = 0; + switch($this->operation) { + case '*': + $result = $firstParam * $secondParam; + break; + case '/': + $result = ($secondParam > 0 ? $firstParam / $secondParam : 0); + break; + case '+': + $result = $firstParam + $secondParam; + break; + case '-': + $result = $firstParam - $secondParam; + break; + } + $this->glade->get_widget('entry1')->set_text($result); + $this->firstParam = $result; + $this->operation = null; + } + /* Effectuer une opération */ + public function performAction($obj) { + if ($this->firstParam == null) { + $this->firstParam = $this->glade->get_widget('entry1')->get_text(); + $this->glade->get_widget('entry1')->set_text(''); + } + if ($this->operation == null) { + $this->operation = str_replace( + array('action_mul', 'action_add', 'action_min', 'action_div'), + array('*', '+', '-', '/'), + $obj->name + ); + $this->glade->get_widget('entry1')->set_text(''); + } else { + $this->calculate($obj->name); + } + } + /* Ajout du caractère à la valeur courante de la zone d'entrée */ + public function enterValue($obj) { + /* Puisque nous avons les noms des boutons Input0 .. input1, vous devez couper le "input" et obtenir la valeur */ + /* Je sais que c'est stupide, mais la tâche n'est pas d'écrire des articles pour l'algorithme de calcul) */ + $this->glade->get_widget('entry1')->set_text( + $this->glade->get_widget('entry1')->get_text(). + str_replace('input', '', $obj->name) + ); + } + /* Effacer le champ d'entrée, et la valeur de la première variable, et l'opération étant exécutée */ + public function clearCalc($obj) { + $this->firstParam = null; + $this->operation = null; + $this->glade->get_widget('entry1')->set_text(''); + } + /* quitter */ + public function quit() { + exit; + } +} +/* Загрузить файл интефейса */ +$glade = new GladeXML('calc.glade'); +/* Указать, что все обработчики событий в классе Calc */ +$glade->signal_autoconnect_instance(new Calc($glade)); +/* При закрытии окна закрывать приложение */ +$glade->get_widget('window1')->connect_simple('destroy', array('Gtk', 'main_quit')); +/* Инициализация GTK приложения */ +Gtk::main(); + + diff --git a/app_tarif/pyperclip.py b/app_tarif/pyperclip.py new file mode 100644 index 0000000..c7382f0 --- /dev/null +++ b/app_tarif/pyperclip.py @@ -0,0 +1,156 @@ +# Pyperclip v1.3 +# A cross-platform clipboard module for Python. (only handles plain text for now) +# By Al Sweigart al@coffeeghost.net + +# Usage: +# import pyperclip +# pyperclip.copy('The text to be copied to the clipboard.') +# spam = pyperclip.paste() + +# On Mac, this module makes use of the pbcopy and pbpaste commands, which should come with the os. +# On Linux, this module makes use of the xclip command, which should come with the os. Otherwise run "sudo apt-get install xclip" + + +# Copyright (c) 2010, Albert Sweigart +# All rights reserved. +# +# BSD-style license: +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of the pyperclip nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY Albert Sweigart "AS IS" AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL Albert Sweigart BE LIABLE FOR ANY +# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +# Change Log: +# 1.2 Use the platform module to help determine OS. +# 1.3 Changed ctypes.windll.user32.OpenClipboard(None) to ctypes.windll.user32.OpenClipboard(0), after some people ran into some TypeError + +import platform, os + +def winGetClipboard(): + ctypes.windll.user32.OpenClipboard(0) + pcontents = ctypes.windll.user32.GetClipboardData(1) # 1 is CF_TEXT + data = ctypes.c_char_p(pcontents).value + #ctypes.windll.kernel32.GlobalUnlock(pcontents) + ctypes.windll.user32.CloseClipboard() + return data + +def winSetClipboard(text): + GMEM_DDESHARE = 0x2000 + ctypes.windll.user32.OpenClipboard(0) + ctypes.windll.user32.EmptyClipboard() + try: + # works on Python 2 (bytes() only takes one argument) + hCd = ctypes.windll.kernel32.GlobalAlloc(GMEM_DDESHARE, len(bytes(text))+1) + except TypeError: + # works on Python 3 (bytes() requires an encoding) + hCd = ctypes.windll.kernel32.GlobalAlloc(GMEM_DDESHARE, len(bytes(text, 'ascii'))+1) + pchData = ctypes.windll.kernel32.GlobalLock(hCd) + try: + # works on Python 2 (bytes() only takes one argument) + ctypes.cdll.msvcrt.strcpy(ctypes.c_char_p(pchData), bytes(text)) + except TypeError: + # works on Python 3 (bytes() requires an encoding) + ctypes.cdll.msvcrt.strcpy(ctypes.c_char_p(pchData), bytes(text, 'ascii')) + ctypes.windll.kernel32.GlobalUnlock(hCd) + ctypes.windll.user32.SetClipboardData(1,hCd) + ctypes.windll.user32.CloseClipboard() + +def macSetClipboard(text): + outf = os.popen('pbcopy', 'w') + outf.write(text) + outf.close() + +def macGetClipboard(): + outf = os.popen('pbpaste', 'r') + content = outf.read() + outf.close() + return content + +def gtkGetClipboard(): + return gtk.Clipboard().wait_for_text() + +def gtkSetClipboard(text): + cb = gtk.Clipboard() + cb.set_text(text) + cb.store() + +def qtGetClipboard(): + return str(cb.text()) + +def qtSetClipboard(text): + cb.setText(text) + +def xclipSetClipboard(text): + outf = os.popen('xclip -selection c', 'w') + outf.write(text) + outf.close() + +def xclipGetClipboard(): + outf = os.popen('xclip -selection c -o', 'r') + content = outf.read() + outf.close() + return content + +def xselSetClipboard(text): + outf = os.popen('xsel -i', 'w') + outf.write(text) + outf.close() + +def xselGetClipboard(): + outf = os.popen('xsel -o', 'r') + content = outf.read() + outf.close() + return content + + +if os.name == 'nt' or platform.system() == 'Windows': + import ctypes + getcb = winGetClipboard + setcb = winSetClipboard +elif os.name == 'mac' or platform.system() == 'Darwin': + getcb = macGetClipboard + setcb = macSetClipboard +elif os.name == 'posix' or platform.system() == 'Linux': + xclipExists = os.system('which xclip') == 0 + if xclipExists: + getcb = xclipGetClipboard + setcb = xclipSetClipboard + else: + xselExists = os.system('which xsel') == 0 + if xselExists: + getcb = xselGetClipboard + setcb = xselSetClipboard + try: + import gtk + getcb = gtkGetClipboard + setcb = gtkSetClipboard + except: + try: + import PyQt4.QtCore + import PyQt4.QtGui + app = QApplication([]) + cb = PyQt4.QtGui.QApplication.clipboard() + getcb = qtGetClipboard + setcb = qtSetClipboard + except: + raise Exception('Pyperclip requires the gtk or PyQt4 module installed, or the xclip command.') +copy = setcb +paste = getcb \ No newline at end of file diff --git a/app_tarif/tarifs.db b/app_tarif/tarifs.db new file mode 100644 index 0000000..7410566 Binary files /dev/null and b/app_tarif/tarifs.db differ diff --git a/app_tarif/tarifs.py b/app_tarif/tarifs.py new file mode 100755 index 0000000..b05f7f5 --- /dev/null +++ b/app_tarif/tarifs.py @@ -0,0 +1,151 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# pylint: disable-msg=C0103 +import sys +import sqlite3 + +try: + from gi.repository import Gtk as gtk +except: + print('GTK not available') + sys.exit(1) + +class TarifCig: + + def on_window1_destroy(self, object, data=None): + #print("quitter depuis croix") + gtk.main_quit() + + def on_gtk_quit_activate(self, menuitem, data=None): + #print("quitter depuis menu") + gtk.main_quit() + + def on_gtk_about_activate(self, menuitem, data=None): + #print("about selectionne") + self.response = self.aboutdialog.run() + self.aboutdialog.hide() + + def on_gtk_search_activate(self, menuitem, data=None): + #print("lancement de la recherche") + self.response = self.dialogSearch.run() + self.dialogSearch.hide() + + def on_btcancel_clicked(self, object, data=None): + print("clic sur annuler") + + def on_btsearch_clicked(self, object, data=None): + #print("recherche: " + self.search.get_text()) + self.label1.set_text("Recherche : " + self.search.get_text()) + + # On commence par vider le liststore (2è recherche) + if len(self.table) != 0: + self.table.clear() + + db = sqlite3.connect("tarifs.db") + cursor = db.cursor() + cursor.execute('select * from mars2013 where nom like "%{0}%"'.format( self.search.get_text())) + db.commit() + rs = cursor.fetchall() + ct = 0 + for item in rs: + ct += 1 + iter = self.table.append() + #self.table.set(iter, 0, item[0], 1, item[1] + " €" if item[1] != '' else '', 2, item[2] + " €" if item[2] != '' else '') + self.table.set(iter, 0, item[0], 1, item[1], 2, item[2]) + #print( item) + #self.table.append([item[0], item[1], item[2]]) + cursor.close() + + # On affiche le nombre de résultats trouvés par la requête + if ct == 0: + self.statusbar.push(self.context_id, "Aucun résultat trouvé") + else: + self.statusbar.push(self.context_id, str(ct) + " résultats trouvés") + + + def on_treeview1_row_activated(self, widget, position, column): + selection = self.tree.get_selection() + (model, iter) = selection.get_selected() + self.valeur.clear() + self.valeur.append(model.get_value(iter, 0)) + self.valeur.append(model.get_value(iter, 1)) + self.valeur.append(model.get_value(iter, 2)) + + def on_treeview1_button_press_event(self, treeview, event): + x = int(event.x) + y = int(event.y) + if event.button == 3: # Clic droit + pthinfo = self.tree.get_path_at_pos(x, y) + if pthinfo is not None: + path, col, cellx, celly = pthinfo + self.tree.grab_focus() + self.tree.set_cursor( path, col, 0) + self.popup.show_all(); + self.popup.popup(None, None, None, None, event.button, event.time) + + + def on_copier_activate(self, menuitem, data=None): + + try: + import pyperclip + except: + print('pyperclip not available') + return False + selection = self.tree.get_selection() + (model, iter) = selection.get_selected() + # On copie dans le presse papier + pyperclip.copy(model.get_value(iter, 0) + ';' + model.get_value(iter, 1) + ';' + model.get_value(iter, 2)) + + def cell_edited_callback(self, cellule, path, new_text,col, model=None): + iter = self.table.get_iter(path) + self.table.set_value(iter, 0, new_text) + self.update_db(tuple(self.valeur),tuple(self.table[path])) + + def update_db(self, old_value, new_value): + db = sqlite3.connect("tarifs.db") + cursor = db.cursor() + cursor.execute('update mars2013 set Nom = "{0}", Prix_Unite = "{1}", Prix_Conditionne = "{2}" where Nom = "{3}" and Prix_Unite = "{4}" and Prix_Conditionne = "{5}"'.format(new_value[0],new_value[1],new_value[2],old_value[0],old_value[1],old_value[2])) + db.commit() + cursor.close() + + def __init__(self): + self.gladefile = "ui.glade" + self.builder = gtk.Builder() + self.builder.add_from_file(self.gladefile) + self.builder.connect_signals(self) + self.window = self.builder.get_object("window1") + self.aboutdialog = self.builder.get_object("aboutdialog1") + self.dialogSearch = self.builder.get_object("dialogSearch") + self.label1 = self.builder.get_object("label1") + self.search = self.builder.get_object("entry1") + self.statusbar = self.builder.get_object("statusbar1") + self.context_id = self.statusbar.get_context_id("exemple") + self.actions = self.builder.get_object("actiongroup1") + self.popup = self.builder.get_object("popup") + self.table = self.builder.get_object("liststore1") + self.valeur = [] + # Constuire l'entête du treeview + self.cell = gtk.CellRendererText() + self.cell2 = gtk.CellRendererText() + self.cell3 = gtk.CellRendererText() + self.cell.connect('edited', self.cell_edited_callback, 1) + self.cell2.connect('edited', self.cell_edited_callback, 2) + self.cell3.connect('edited', self.cell_edited_callback, 3) + self.cell.set_property('editable', True) + self.cell2.set_property('editable', True) + self.cell3.set_property('editable', True) + self.tree = self.builder.get_object("treeview1") + self.column1 = gtk.TreeViewColumn('Nom', self.cell, text=0) + self.column1.set_sort_column_id(0) + self.tree.append_column(self.column1) + self.column2 = gtk.TreeViewColumn('Prix unitaire', self.cell2, text=1) + self.column2.set_sort_column_id(1) + self.tree.append_column(self.column2) + self.column3 = gtk.TreeViewColumn('Prix conditionné', self.cell3, text=2) + self.column3.set_sort_column_id(2) + self.tree.append_column(self.column3) + self.window.show_all() + +if __name__ == "__main__": + main = TarifCig() + gtk.main() diff --git a/app_tarif/ui.glade b/app_tarif/ui.glade new file mode 100644 index 0000000..5ebcc74 --- /dev/null +++ b/app_tarif/ui.glade @@ -0,0 +1,330 @@ + + + + + + + gtk-copy + + + + + + gtk-quit + + + + + + False + 5 + dialog + Tarifs Cigares + O.3 + Copyright © Jérôme Launay + Tarifs d'Avril 2013 + www.projet-libre.org + www.projet-libre.org + WTFPL + + + False + vertical + 2 + + + False + end + + + False + True + end + 0 + + + + + + + + + + False + 5 + popup + Recherche d'un cigare + False + True + center-on-parent + normal + window1 + False + + + False + True + vertical + 2 + + + False + end + + + Annuler + True + True + True + + + + False + True + 0 + + + + + Lancer la recherche + True + True + True + True + True + + + + + False + True + 1 + + + + + False + True + end + 0 + + + + + True + False + vertical + + + True + False + Veuillez saisir votre recherche + + + False + True + 0 + + + + + True + True + + + + False + True + 1 + + + + + False + True + 1 + + + + + + btcancel + btsearch + + + + + + + + + + + + + + True + True + True + + + False + copier + True + False + Copier + + + + + False + quitter + True + False + _Quitter + True + + + + + False + center + 1100 + 650 + + + + True + False + vertical + + + True + False + + + True + False + _Cigares + True + + + True + False + + + gtk-find + True + False + True + True + + + + + + + True + False + + + + + gtk-quit + True + False + True + True + + + + + + + + + + + True + False + Aid_e + True + + + True + False + + + gtk-about + True + False + True + True + + + + + + + + + + False + True + 0 + + + + + True + False + + + False + False + 1 + + + + + True + True + in + + + True + True + liststore1 + horizontal + True + + + + + + + + + + True + True + 2 + + + + + True + False + vertical + 2 + + + False + True + 3 + + + + + + diff --git a/photos/Cohiba-Siglo2.jpg b/photos/Cohiba-Siglo2.jpg new file mode 100644 index 0000000..5abdd5c Binary files /dev/null and b/photos/Cohiba-Siglo2.jpg differ diff --git a/photos/Partagas-Mille-Fleurs.jpg b/photos/Partagas-Mille-Fleurs.jpg new file mode 100644 index 0000000..4212806 Binary files /dev/null and b/photos/Partagas-Mille-Fleurs.jpg differ diff --git a/photos/Quintero-Brevas.jpg b/photos/Quintero-Brevas.jpg new file mode 100644 index 0000000..eed11bb Binary files /dev/null and b/photos/Quintero-Brevas.jpg differ diff --git a/photos/R_J-Cedros De Luxe No3.jpg b/photos/R_J-Cedros De Luxe No3.jpg new file mode 100644 index 0000000..c1570e4 Binary files /dev/null and b/photos/R_J-Cedros De Luxe No3.jpg differ diff --git a/photos/Romeo-y-Julieta-Mille-Fleurs-01.jpg b/photos/Romeo-y-Julieta-Mille-Fleurs-01.jpg new file mode 100644 index 0000000..a09490b Binary files /dev/null and b/photos/Romeo-y-Julieta-Mille-Fleurs-01.jpg differ diff --git a/photos/macanudo_maduro.jpe b/photos/macanudo_maduro.jpe new file mode 100644 index 0000000..5cc06b3 Binary files /dev/null and b/photos/macanudo_maduro.jpe differ diff --git a/photos/rafael_gonzales_panetelas-extra.jpg b/photos/rafael_gonzales_panetelas-extra.jpg new file mode 100644 index 0000000..23254d9 Binary files /dev/null and b/photos/rafael_gonzales_panetelas-extra.jpg differ diff --git a/photos/san-cristobal-oficios-b.jpg b/photos/san-cristobal-oficios-b.jpg new file mode 100644 index 0000000..092d225 Binary files /dev/null and b/photos/san-cristobal-oficios-b.jpg differ diff --git a/photos/upmann-majestic.jpg b/photos/upmann-majestic.jpg new file mode 100644 index 0000000..98c3a2a Binary files /dev/null and b/photos/upmann-majestic.jpg differ