import os import sys import pandas as pd import typing from PyQt6 import QtWidgets, QtCore, QtGui from docx import Document class CheckBoxModel(QtCore.QAbstractTableModel): def __init__(self, data, add_checkboxes=False): super().__init__() self._data = data self.add_checkboxes = add_checkboxes self.check_state = [[QtCore.Qt.CheckState.Unchecked for _ in range(self.columnCount(None))]] self.check_state.extend([[QtCore.Qt.CheckState.Unchecked for _ in range(2)] for __ in range(len(self._data.index))]) def rowCount(self, parent=None): return len(self._data.index) + 1 def columnCount(self, parent=None): if self.add_checkboxes: return len(self._data.columns) + 2 else: return len(self._data.columns) def data(self, index: QtCore.QModelIndex, role: int = QtCore.Qt.ItemDataRole.DisplayRole): row = index.row() col = index.column() if 0 <= row < len(self.check_state) and 0 <= col < len(self.check_state[0]): print(f"L'état de la case à cocher pour la ligne {row} et la colonne {col} est {self.check_state[row][col]}") row = index.row() col = index.column() if self.add_checkboxes: if row == 0: if role == QtCore.Qt.ItemDataRole.CheckStateRole and col < 2: return self.check_state[row][col] else: return None else: if col < 2: if role == QtCore.Qt.ItemDataRole.CheckStateRole: return self.check_state[row][col] else: return None else: if role == QtCore.Qt.ItemDataRole.DisplayRole: return str(self._data.iloc[row - 1, col - 2]) else: if role == QtCore.Qt.ItemDataRole.DisplayRole: return str(self._data.iloc[row - 1, col]) def setData(self, index: QtCore.QModelIndex, value: typing.Any, role: int = QtCore.Qt.ItemDataRole.EditRole) -> bool: print(f"La méthode setData a été appelée pour la ligne {index.row()} et la colonne {index.column()}") row = index.row() col = index.column() if self.add_checkboxes: if col < 2: if role == QtCore.Qt.ItemDataRole.CheckStateRole: self.check_state[row][col] = value if row == 0: for i in range(1, len(self.check_state)): self.check_state[i][col] = value self.dataChanged.emit(self.index(i, col), self.index(i, col)) self.dataChanged.emit(index, index) return True return False def flags(self, index: QtCore.QModelIndex) -> QtCore.Qt.ItemFlag: row = index.row() col = index.column() if self.add_checkboxes and row == 0 and col < 2: return QtCore.Qt.ItemFlag.ItemIsUserCheckable | QtCore.Qt.ItemFlag.ItemIsEnabled elif row > 0 and col < 2: return super().flags(index) | QtCore.Qt.ItemFlag.ItemIsUserCheckable else: return super().flags(index) def headerData(self, section: int, orientation: QtCore.Qt.Orientation, role: int = QtCore.Qt.ItemDataRole.DisplayRole): if role == QtCore.Qt.ItemDataRole.DisplayRole: if orientation == QtCore.Qt.Orientation.Horizontal: if self.add_checkboxes: if section < 2: if section == 0: return "Référence" else: return "Fusionner" else: return self._data.columns[section - 2] else: return self._data.columns[section] else: if section > 0: return str(self._data.index[section - 1]) else: return "" class MainWindow(QtWidgets.QMainWindow): def __init__(self): super().__init__() script_dir = sys.path[0] icon_path = os.path.join(script_dir, "icons", "sonatrach.ico") icon = QtGui.QIcon(icon_path) self.setWindowIcon(icon) self.setWindowTitle("Data & Fusion articles") self.resize(1015, 593) self.imported_files = [] # Créez un QLabel pour afficher l'état du traitement self.status_label = QtWidgets.QLabel("Prêt") # Créez un QStatusBar et ajoutez-y le QLabel self.statusBar = QtWidgets.QStatusBar() self.statusBar.addPermanentWidget(self.status_label) self.setStatusBar(self.statusBar) # Créez un widget QTabWidget self.tab_widget = QtWidgets.QTabWidget() # Active les boutons de fermeture sur les onglets self.tab_widget.setTabsClosable(True) # Connecte le signal tabCloseRequested à la méthode close_tab self.tab_widget.tabCloseRequested.connect(self.close_tab) # Crée un menu principal menu = self.menuBar() file_menu = menu.addMenu("Fichier") # Ajoute les commandes "Importer" et "Quitter" au sous-menu "Fichier" file_menu.addAction("Importer", self.import_file) # noinspection PyArgumentList file_menu.addAction("Quitter", self.close) # Ajoute un nouveau menu "Outils" à côté du menu "Fichier" tools_menu = menu.addMenu("Outils") # Ajoute la commande "Descriptions similaires" find_similar_action = tools_menu.addAction("Extraire les descriptions similaires") note_menu = tools_menu.addMenu("Note") fusion_note_action = note_menu.addAction("Créer une note de fusion") # Connecte l'action fusion_note_action à la méthode create_fusion_note fusion_note_action.triggered.connect(self.create_fusion_note) # Stocke l'action find_similar_action dans une variable d'instance pour y accéder plus tard self.find_similar_action = find_similar_action # Connecte l'action find_similar_action à la méthode find_similar self.find_similar_action.triggered.connect(self.find_similar) # Crée une barre de recherche search_bar = QtWidgets.QLineEdit() search_bar.setPlaceholderText("Rechercher...") search_bar.setFixedWidth(189) # 5 cm en pixels search_bar.setStyleSheet("QLineEdit { border: 1px solid gray; border-radius: 10px; padding: 0 8px; }") spacer = QtWidgets.QWidget() spacer.setSizePolicy(QtWidgets.QSizePolicy.Policy.Expanding, QtWidgets.QSizePolicy.Policy.Expanding) # Crée un widget QToolBar pour contenir la barre de recherche tool_bar = QtWidgets.QToolBar() tool_bar.addWidget(spacer) tool_bar.addWidget(search_bar) # Créez une disposition verticale pour organiser les widgets layout = QtWidgets.QVBoxLayout() # Ajoutez le QToolBar et le QTabWidget à la disposition layout.addWidget(tool_bar) layout.addWidget(self.tab_widget) # Crée un widget central pour contenir la disposition central_widget = QtWidgets.QWidget() central_widget.setLayout(layout) self.setCentralWidget(central_widget) def import_file(self): filename, _ = QtWidgets.QFileDialog.getOpenFileName(self, "Sélectionner un fichier", "", "Fichiers Excel (*.xlsx *.xls *.xlsm)") if filename: # Mettez à jour le texte du QLabel pour afficher "Traitement en cours" self.status_label.setText("Traitement en cours") QtWidgets.QApplication.processEvents() basename = os.path.basename(filename) filename_without_extension = os.path.splitext(basename)[0] if filename_without_extension in self.imported_files: QtWidgets.QMessageBox.warning(self, "Attention", "Ce fichier a déjà été importé.") return basename = os.path.basename(filename) filename_without_extension = os.path.splitext(basename)[0] self.imported_files.append(filename_without_extension) # Lit le fichier Excel et stocke les données dans un DataFrame df = pd.read_excel(filename) # Vérifie si le DataFrame a une colonne SIMILARITY if "SIMILARITY" in df.columns: # Convertit la colonne SIMILARITY en chaîne de caractères df['SIMILARITY'] = df['SIMILARITY'].astype(str) # Stocke les données du DataFrame dans une variable d'instance pour y accéder plus tard self._data = df # Créez le premier onglet tab1 = QtWidgets.QWidget() tab1_layout = QtWidgets.QVBoxLayout() self.tab1_table = QtWidgets.QTableView() tab1_layout.addWidget(self.tab1_table) tab1.setLayout(tab1_layout) self.tab_widget.addTab(tab1, "Importé: " + os.path.splitext(os.path.basename(filename))[0]) # Définissez le modèle pour le QTableView en utilisant les données de votre DataFrame model = CheckBoxModel(df, add_checkboxes=False) self.tab1_table.setModel(model) # Permet le déplacement des onglets pour changer l'ordre des pages self.tab_widget.setMovable(True) # Mettez à jour le texte du QLabel pour afficher "Prêt" une fois les données importées self.status_label.setText("Prêt") QtWidgets.QApplication.processEvents() def close_tab(self, index): if self.tab_widget.tabText(index).startswith("Importé: "): self._data = None tab_text = self.tab_widget.tabText(index) if tab_text.startswith("Importé: "): filename = tab_text[len("Importé: "):] filename = os.path.basename(filename) if filename in self.imported_files: self.imported_files.remove(filename) self.tab_widget.removeTab(index) def find_similar(self): import_tab_exists = False for i in range(self.tab_widget.count()): if self.tab_widget.tabText(i).startswith("Importé: "): import_tab_exists = True break if not import_tab_exists: self.show_warning("Attention", "Veuillez importer des données avant d'utiliser cette fonctionnalité.") return current_tab_index = self.tab_widget.currentIndex() if current_tab_index != -1 and self.tab_widget.tabText(current_tab_index).startswith("Extrait: "): self.show_warning("Attention", "La fonctionnalité Descriptions similaires n'est pas disponible pour les onglets contenant des données extraites.") return current_tab = self.tab_widget.currentWidget() if not current_tab or not current_tab.findChild(QtWidgets.QTableView): self.show_warning("Attention", "La fonctionnalité Descriptions similaires n'est disponible que pour les onglets contenant des données importées.") return if not self.tab_widget.findChild(QtWidgets.QTableView): self.show_warning("Attention", "Veuillez importer des données avant d'utiliser cette fonctionnalité.") return # Mettez à jour le texte du QLabel pour afficher "Traitement en cours" self.status_label.setText("Traitement en cours") QtWidgets.QApplication.processEvents() # Récupère l'index de la ligne et de la colonne de la cellule sélectionnée selected_indexes = current_tab.findChild(QtWidgets.QTableView).selectedIndexes() if len(selected_indexes) != 1: self.show_warning("Attention", "Veuillez sélectionner une seule cellule.") return row = selected_indexes[0].row() col = selected_indexes[0].column() # Récupère les données du QTableView de l'onglet actuellement sélectionné model = current_tab.findChild(QtWidgets.QTableView).model() headers = [model.headerData(i, QtCore.Qt.Orientation.Horizontal) for i in range(model.columnCount())] # Vérifie si la colonne sélectionnée est la colonne SIMILARITY if headers[col] != "SIMILARITY": self.show_warning("Attention", "Veuillez sélectionner une cellule dans la colonne SIMILARITY.") return tab_name = f"extraire: {model.index(row, headers.index('ITEM')).data()}" for i in range(self.tab_widget.count()): if self.tab_widget.tabText(i) == tab_name: self.show_warning("Attention", "Un onglet avec les mêmes données extraites existe déjà.") return # Vérifie si la valeur de la cellule sélectionnée n'est pas 0 if model.index(row, col).data() == "0": self.show_warning("Attention", "La cellule sélectionnée ne doit pas contenir 0.") return # Récupère les numéros de la colonne ITEM mentionnés dans la cellule sélectionnée item_numbers = model.index(row, col).data().split(" ") # Crée un nouveau DataFrame contenant les entêtes, la ligne sélectionnée et les lignes correspondant aux numéros de la colonne ITEM new_df = pd.DataFrame(columns=headers) new_df = new_df.append(pd.Series([model.index(row, i).data() for i in range(model.columnCount())], index=headers), ignore_index=True) for item_number in item_numbers: for i in range(model.rowCount()): if model.index(i, headers.index('ITEM')).data() == item_number: new_df = new_df.append( pd.Series([model.index(i, j).data() for j in range(model.columnCount())], index=headers), ignore_index=True) # Crée un nouveau QTableView pour afficher les données du nouveau DataFrame new_tab_table = QtWidgets.QTableView() new_model = CheckBoxModel(new_df, add_checkboxes=True) new_tab_table.setModel(new_model) # Crée un nouvel onglet avec le nouveau QTableView new_tab = QtWidgets.QWidget() new_tab_layout = QtWidgets.QVBoxLayout() new_tab_layout.addWidget(new_tab_table) new_tab.setLayout(new_tab_layout) self.tab_widget.addTab(new_tab, f"extraire: {model.index(row, headers.index('ITEM')).data()}") # Mettez à jour le texte du QLabel pour afficher "Prêt" une fois les données extraites self.status_label.setText("Prêt") QtWidgets.QApplication.processEvents() def show_warning(self, title, text): # Mettez à jour le texte du QLabel pour afficher "Prêt" avant d'afficher le message d'avertissement self.status_label.setText("Prêt") QtWidgets.QApplication.processEvents() QtWidgets.QMessageBox.warning(self, title, text) def create_fusion_note(self): # Récupère l'onglet actuellement sélectionné current_tab = self.tab_widget.currentWidget() # Vérifie si l'onglet actuellement sélectionné est une page extraire if not current_tab or not self.tab_widget.tabText(self.tab_widget.currentIndex()).startswith("extraire: "): QtWidgets.QMessageBox.warning(self, "Attention", "Veuillez sélectionner une page extraire avant de créer une note de fusion.") return # Récupère le QTableView de l'onglet actuellement sélectionné table_view = current_tab.findChild(QtWidgets.QTableView) # Récupère le modèle de données du QTableView model = table_view.model() # Parcourez les lignes du modèle for row in range(model.rowCount()): # Récupérez l'état des cases à cocher dans les colonnes Référence et Fusionner reference_checked = model.index(row, 0).data(QtCore.Qt.ItemDataRole.CheckStateRole) == QtCore.Qt.CheckState.Checked merge_checked = model.index(row, 1).data(QtCore.Qt.ItemDataRole.CheckStateRole) == QtCore.Qt.CheckState.Checked # Ouvrir le modèle de document Word doc = Document("modele_note_vierge.docx") # Parcourir les tableaux du document for table in doc.tables: # Vérifier si le tableau contient les signes "{{1}}" ou "{{2}}" if "{{1}}" in table.cell(0, 0).text: # Parcourir les lignes du modèle for row in range(model.rowCount()): # Vérifier l'état des cases à cocher dans la colonne Référence reference_checked = model.index(row, 0).data(QtCore.Qt.ItemDataRole.CheckStateRole) == QtCore.Qt.CheckState.Checked # Si la case à cocher dans la colonne Référence est cochée if reference_checked: # Ajouter une nouvelle ligne au tableau new_row = table.add_row() # Remplir les cellules de la nouvelle ligne avec les données appropriées new_row.cells[0].text = model.index(row, 2).data() new_row.cells[1].text = model.index(row, 3).data() new_row.cells[2].text = model.index(row, 4).data() new_row.cells[3].text = model.index(row, 5).data() new_row.cells[4].text = model.index(row, 6).data() elif "{{2}}" in table.cell(0, 0).text: # Parcourir les lignes du modèle for row in range(model.rowCount()): # Vérifier l'état des cases à cocher dans la colonne Fusionner merge_checked = model.index(row, 1).data(QtCore.Qt.ItemDataRole.CheckStateRole) == QtCore.Qt.CheckState.Checked # Si la case à cocher dans la colonne Fusionner est cochée if merge_checked: # Ajouter une nouvelle ligne au tableau new_row = table.add_row() # Remplir les cellules de la nouvelle ligne avec les données appropriées new_row.cells[0].text = model.index(row, 2).data() new_row.cells[1].text = model.index(row, 3).data() new_row.cells[2].text = model.index(row, 4).data() new_row.cells[3].text = model.index(row, 5).data() new_row.cells[4].text = model.index(row, 6).data() # Demander à l'utilisateur où sauvegarder le fichier Word modifié filename, _ = QtWidgets.QFileDialog.getSaveFileName(None, "Enregistrer sous", "Note de fusion articles N°", "Fichiers Word (*.docx)") # Enregistrer le fichier Word modifié if filename: doc.save(filename) app = QtWidgets.QApplication(sys.argv) window = MainWindow() window.show() app.exec()