Exemple #1
0
    def add_context_menu_actions(self, parent: QTableView,
                                 edit_path: QLineEdit):
        parent.setContextMenuPolicy(Qt.ActionsContextMenu)
        select_action = QAction('Выбрать все', self)
        deselect_action = QAction('Снять выделение со всех', self)
        rename_action = QAction('Переименовать', self)
        select_action.triggered.connect(self.select_all)
        deselect_action.triggered.connect(self.deselect_all)
        rename_action.triggered.connect(
            lambda checked, x=parent, y=edit_path: self.rename_file(x, y))

        parent.addAction(select_action)
        parent.addAction(deselect_action)
        parent.addAction(rename_action)
Exemple #2
0
class Ui_WebSetting(QWidget):
    # 1. Signal obj
    json_update_signal = pyqtSignal()

    def __init__(self, parent=None):
        super().__init__()
        self.setWindowTitle("Web ID/PW 관리")
        # 우하단 위젯   # self.setGeometry(300,300,500,10)
        rect = QDesktopWidget().availableGeometry(
        )  # 작업표시줄 제외한 화면크기 반환 # .screenGeometry() : 화면해상도 class (0, 0, x, y)
        max_x = rect.width()
        max_y = rect.height()

        width, height = 300, 300
        left = max_x - width
        top = max_y - height

        self.setGeometry(left, top - 500, width, height)

        result = ddld_json_to_df()
        self.ddld_dic = result[0]
        self.idpw_dic_lst_dic = result[1]
        self._df = result[2]

        self.initUI()
        # self.show()

    def initUI(self):

        self.df = self._df
        self.emty_site_set = AddTable.item_set - set(
            self.df['website'].to_list())

        self.model = PandasModel(self.df)
        self.view = QTableView()
        self.view.setModel(self.model)
        self.view.setSelectionBehavior(
            QTableView.SelectRows)  # multiple row 단위 선택 가능
        # self.view.setSelectionMode(QTableView.SingleSelection)  # one row 선택 제한
        self.view.setSelectionMode(QAbstractItemView.SingleSelection)  #

        # # widget 창에 꽉 차게 http://bitly.kr/l8mzD7J  https://hoy.kr/dFFaj
        self.view.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
        # self.view.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)
        self.view.horizontalHeader().setSectionResizeMode(
            QHeaderView.ResizeToContents)
        # =====
        # size policy : https://hoy.kr/dFFaj
        self.view.setAlternatingRowColors(True)
        self.view.setSizeAdjustPolicy(QAbstractScrollArea.AdjustToContents)
        self.view.setSizePolicy(
            QSizePolicy.Minimum,
            QSizePolicy.Minimum)  # Expanding | Minimum | Maximum

        # header color : https://stackoverflow.com/questions/19198634/pyqt-qtablewidget-horizontalheaderlabel-stylesheet
        stylesheet = """QHeaderView::section{ background-color: #CEECF5; color: blue; }"""
        self.view.horizontalHeader().setStyleSheet(stylesheet)

        # right click menu 추가 : https://freeprog.tistory.com/334 [취미로 하는 프로그래밍 !!!]
        self.view.setContextMenuPolicy(Qt.ActionsContextMenu)
        del_action = QAction("선택행 삭제", self.view)
        self.view.addAction(del_action)
        del_action.triggered.connect(self.del_row)
        # =====
        # # enable sorting : https://codeday.me/ko/qa/20190422/381866.html
        # # 삭제시 df 와 tablevew 간의 row index 일치시키기 위해 죽여놓음
        # self.view.setSortingEnabled(True)
        le = QLineEdit('우클릭: 삭제 / 더블 클릭: 수정')
        le.setReadOnly(True)

        # Qt Style Sheets Examples : https://hoy.kr/d0nYi
        le.setStyleSheet(
            """QLineEdit { border: 2px solid gray; border-radius: 10px;
                    padding: 2 15px; background: #f0f0f0; color: red; font: bold;
                    selection-background-color: darkgray;} """)

        self.layout = QVBoxLayout()
        self.layout.addWidget(le)

        self.btn1 = QPushButton('저장 후 닫기', self)
        self.btn2 = QPushButton('웹사이트 ID/PW 추가', self)
        self.btn1.setStyleSheet(
            """QPushButton { background-color: #A9F5F2; color: blue; font: bold;
                                border: 1px solid gray; padding: 5 0px;}""")
        self.btn2.setStyleSheet(
            """QPushButton { background-color: #A9F5F2; color: blue; font: bold;
                                border: 1px solid gray; padding: 5 0px;}""")
        self.layout.addWidget(self.btn2)

        self.add_table = AddTable(self)
        self.add_table.setHidden(True)  # toggle set

        self.layout.addWidget(self.add_table)
        self.layout.addWidget(self.view)
        self.layout.addWidget(self.btn1)

        self.setLayout(self.layout)

        # ======== 5. Signal connect Slot (동일 파일 내)
        self.make_connection(self.add_table)
        # self.make_connection_view(self.model)  --- Main __main__ 으로

        # ========
        self.btn1.clicked.connect(self.save_df_to_json)
        self.btn2.clicked.connect(self.add_row)

    @pyqtSlot()
    def del_row(self):
        row_idx = self.view.selectionModel().currentIndex().row()

        self.df = self.df.drop(self.df.index[row_idx], axis=0)
        self.df.reset_index()
        self.model = PandasModel(self.df)
        self.view.setModel(self.model)

        self.btn1.setStyleSheet(
            """QPushButton { background-color: #ffff00; color: blue; font: bold }"""
        )

        self.emty_site_set = AddTable.item_set - set(
            self.df['website'].to_list())

    @pyqtSlot()  # 엑셀저장  http://bitly.kr/cyM01Yn
    def save_df_to_json(self):
        #============= 3. Dataframe to original dic_lst_dic : https://hoy.kr/dXq62
        # 1. 값 있는 것
        df_to_dict = self.df.to_dict('split')
        _data_lst_lst = df_to_dict['data']
        # 2. 값 없는 것
        empty_site_lst = list(self.emty_site_set)
        empty_site_lst_lst = [[website, "", ""] for website in empty_site_lst]
        # 3. 1+2
        data_lst_lst = _data_lst_lst + empty_site_lst_lst

        web_dic_lst_dic = dict()

        for web, id, pw in data_lst_lst:
            inner_dic = dict()

            inner_dic['id'] = id
            inner_dic['pw'] = pw

            if inner_dic['id'] != "":
                if web in web_dic_lst_dic.keys():
                    web_dic_lst_dic[web].append(inner_dic)  # nd2 id/pw 이후
                    continue
                web_dic_lst_dic[web] = [inner_dic]  # first id/pw
                continue
            web_dic_lst_dic[web] = []  # id/pw 가 없는 경우

        #============ 4. dict to json
        # fulljs = r'C:\Ataxtech\ATT\Ver1.0\json\web.json'
        self.ddld_dic['idpw'] = web_dic_lst_dic
        with open(fulljs, 'w', encoding='utf-8') as fn:
            json.dump(self.ddld_dic, fn, ensure_ascii=False, indent=4)

        self.btn1.setStyleSheet(
            """QPushButton { background-color: #A9F5F2; color: blue; font: bold;
                                border: 1px solid gray; padding: 5 0px;}""")

        # 2. Signal(json_update_signal) emit
        self.json_update_signal.emit()

    @pyqtSlot()
    def add_row(self):
        sender_obj = self.sender()

        if sender_obj.text() == "웹사이트 ID/PW 추가":
            self.add_table.setHidden(False)  # toggle
            sender_obj.setText("ID/PW 저장")
            # sender_obj.setStyleSheet(
            #         """QPushButton { background-color: #ffff00; color: blue; font: bold }""")

        elif sender_obj.text() == "ID/PW 저장":
            # print(self.add_table.website,'===', self.add_table.id, '===', self.add_table.pw)
            self.add_table.setHidden(True)  # toggle
            sender_obj.setText("웹사이트 ID/PW 추가")

            # df에 리스트 바로 붙이기 https://hoy.kr/v8Krj
            if self.add_table.id != "" and self.add_table.pw != "":  # id/pw 모두 입력
                add_df_row_lst = [
                    self.add_table.website, self.add_table.id,
                    self.add_table.pw
                ]
                self.df.loc[len(self.df) + 1] = add_df_row_lst
                self.model = PandasModel(self.df)
                self.view.setModel(self.model)

                self.add_table.setItem(0, 1, QTableWidgetItem(""))
                self.add_table.setItem(0, 2, QTableWidgetItem(""))

                sender_obj.setStyleSheet(
                    """QPushButton { background-color: #A9F5F2; color: blue; font: bold }"""
                )

                self.btn1.setStyleSheet(
                    """QPushButton { background-color: #ffff00; color: blue; font: bold }"""
                )

                self.emty_site_set = AddTable.item_set - set(
                    self.df['website'].to_list())
                # print(self.emty_site_set)

    # 3. Signal connect Slot
    def make_connection(self, signal_emit_object):
        signal_emit_object.pw_changed_signal.connect(self.receive_add_idpw)

    # 3. Signal connect Slot
    def make_connection_view(self, signal_emit_object):
        signal_emit_object.view_edit_signal.connect(self.receive_edit_idpw)

    # 3. Signal(json_update_signal) connect Slot
    def make_connection_json_update(self, signal_emit_object):
        signal_emit_object.json_update_signal.connect(self.receive_json_update)

    # 4. receive Signal
    @pyqtSlot()
    def receive_add_idpw(self):
        self.btn2.setStyleSheet(
            """QPushButton { background-color: #ffff00; color: blue; font: bold }"""
        )

    # 4. receive Signal
    @pyqtSlot()
    def receive_edit_idpw(self):
        self.btn1.setStyleSheet(
            """QPushButton { background-color: #ffff00; color: blue; font: bold }"""
        )

    # 4. receive Signal(json_update_signal)
    @pyqtSlot()
    def receive_json_update(self):
        self.update_json()
        self.model = PandasModel(self.df)
        self.view.setModel(self.model)

    def update_json(self):

        result = ddld_json_to_df()
        self.ddld_dic = result[0]
        self.idpw_dic_lst_dic = result[1]
        self.df = result[2]

        self.emty_site_set = AddTable.item_set - set(
            self.df['website'].to_list())

        #============= 3. Dataframe to original dic_lst_dic : https://hoy.kr/dXq62
        # 1. 값 있는 것
        df_to_dict = self.df.to_dict('split')
        _data_lst_lst = df_to_dict['data']
        # 2. 값 없는 것
        empty_site_lst = list(self.emty_site_set)
        empty_site_lst_lst = [[website, "", ""] for website in empty_site_lst]
        # 3. 1+2
        data_lst_lst = _data_lst_lst + empty_site_lst_lst

        web_dic_lst_dic = dict()

        for web, id, pw in data_lst_lst:
            inner_dic = dict()

            inner_dic['id'] = id
            inner_dic['pw'] = pw

            if inner_dic['id'] != "":
                if web in web_dic_lst_dic.keys():
                    web_dic_lst_dic[web].append(inner_dic)  # nd2 id/pw 이후
                    continue
                web_dic_lst_dic[web] = [inner_dic]  # first id/pw
                continue
            web_dic_lst_dic[web] = []  # id/pw 가 없는 경우

        #============ 4. dict to json
        # fulljs = r'C:\Ataxtech\ATT\Ver1.0\json\web.json'
        self.ddld_dic['idpw'] = web_dic_lst_dic
        with open(fulljs, 'w', encoding='utf-8') as fn:
            json.dump(self.ddld_dic, fn, ensure_ascii=False, indent=4)
class MainWindow(QMainWindow):

    def __init__(self, data):
        super().__init__()

        self.resize(400, 600)
        self.setWindowTitle('Logger Skeleton')
        self.statusBar().showMessage("Ready", 2000)

        # Make widgets ####################################

        self.tabs = QTabWidget(self)
        self.setCentralWidget(self.tabs)

        # Add tabs
        self.table_tab = QWidget(self)
        self.stats_tab = QWidget(self)

        self.tabs.addTab(self.table_tab, "Table")
        self.tabs.addTab(self.stats_tab, "Stats")

        # Table tab ###########################################################

        self.table_view = QTableView(self.table_tab)
        self.text_edit = QPlainTextEdit()
        self.btn_add_row = QPushButton("Add a row")
        #self.btn_remove_row = QPushButton("Remove selected rows")

        table_tab_vbox = QVBoxLayout()

        table_tab_vbox.addWidget(self.table_view)
        table_tab_vbox.addWidget(self.text_edit)
        table_tab_vbox.addWidget(self.btn_add_row)
        #table_tab_vbox.addWidget(self.btn_remove_row)

        self.table_tab.setLayout(table_tab_vbox)

        # Set model #######################################

        my_model = DataQtModel(data, parent=self)  # TODO: right use of "parent" ?

        # Proxy model #####################################

        proxy_model = QSortFilterProxyModel(parent=self)  # TODO: right use of "parent" ?
        proxy_model.setSourceModel(my_model)

        self.table_view.setModel(proxy_model)
        #self.table_view.setModel(my_model)

        # Set the view ####################################

        self.table_view.setSelectionBehavior(QAbstractItemView.SelectRows)    # Select the full row when a cell is selected (See http://doc.qt.io/qt-5/qabstractitemview.html#selectionBehavior-prop )
        #self.table_view.setSelectionMode(QAbstractItemView.SingleSelection)  # Set selection mode. See http://doc.qt.io/qt-5/qabstractitemview.html#selectionMode-prop

        self.table_view.setAlternatingRowColors(True)
        self.table_view.setSortingEnabled(True)
        self.table_view.setColumnWidth(0, 200)                       # TODO: automatically get the best width

        self.table_view.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)    # https://stackoverflow.com/q/17535563

        self.table_view.setColumnHidden(COMMENT_COLUMN_INDEX, True)

        delegate = Delegate()
        self.table_view.setItemDelegate(delegate)

        # Set key shortcut ################################

        # see https://stackoverflow.com/a/17631703  and  http://doc.qt.io/qt-5/qaction.html#details

        # Add row action

        add_action = QAction(self.table_view)
        add_action.setShortcut(Qt.CTRL | Qt.Key_N)

        add_action.triggered.connect(self.add_row_btn_callback)
        self.table_view.addAction(add_action)

        # Delete action

        del_action = QAction(self.table_view)
        del_action.setShortcut(Qt.Key_Delete)

        del_action.triggered.connect(self.remove_row_callback)
        self.table_view.addAction(del_action)

        # Set QDataWidgetMapper ###########################

        self.mapper = QDataWidgetMapper()
        self.mapper.setModel(proxy_model)          # WARNING: do not use `my_model` here otherwise the index mapping will be wrong!
        self.mapper.addMapping(self.text_edit, COMMENT_COLUMN_INDEX)
        self.mapper.toFirst()                      # TODO: is it a good idea ?

        self.table_view.selectionModel().selectionChanged.connect(self.update_selection)

        # Set slots #######################################

        self.btn_add_row.clicked.connect(self.add_row_btn_callback)
        #self.btn_remove_row.clicked.connect(self.remove_row_callback)

        #self.table_view.setColumnHidden(1, True)

        # Stats tab ###########################################################

        # See https://matplotlib.org/examples/user_interfaces/embedding_in_qt5.html

        stats_tab_layout = QVBoxLayout(self.stats_tab)
        self.plot_canvas = PlotCanvas(data, self.stats_tab, width=5, height=4, dpi=100)
        stats_tab_layout.addWidget(self.plot_canvas)

        ###################################################

        #proxy_model.dataChanged.connect(plot_canvas.update_figure)
        #proxy_model.rowsInserted.connect(plot_canvas.update_figure)  # TODO
        #proxy_model.rowsRemoved.connect(plot_canvas.update_figure)   # TODO

        self.tabs.currentChanged.connect(self.updatePlot)  # Update the stats plot when the tabs switch to the stats tab

        # Show ############################################

        self.show()


    def update_selection(self, selected, deselected):
        index = self.table_view.selectionModel().currentIndex()
        self.mapper.setCurrentIndex(index.row())
        print("Index: ", index.row())


    def updatePlot(self, index):
        """

        Parameters
        ----------
        index

        Returns
        -------

        """
        if index == self.tabs.indexOf(self.stats_tab):
            self.plot_canvas.update_figure()


    def add_row_btn_callback(self):
        parent = QModelIndex()                                   # More useful with e.g. tree structures

        #row_index = 0                                           # Insert new rows to the begining
        row_index = self.table_view.model().rowCount(parent)     # Insert new rows to the end

        self.table_view.model().insertRows(row_index, 1, parent)

    def remove_row_callback(self):
        parent = QModelIndex()                                   # More useful with e.g. tree structures

        # See http://doc.qt.io/qt-5/model-view-programming.html#handling-selections-in-item-views
        #current_index = self.table_view.selectionModel().currentIndex()
        #print("Current index:", current_index.row(), current_index.column())

        selection_index_list = self.table_view.selectionModel().selectedRows()
        selected_row_list = [selection_index.row() for selection_index in selection_index_list]

        print("Current selection:", selected_row_list)

        #row_index = 0                                           # Remove the first row
        #row_index = self.table_view.model().rowCount(parent) - 1 # Remove the last row

        # WARNING: the list of rows to remove MUST be sorted in reverse order
        # otherwise the index of rows to remove may change at each iteration of the for loop!

        # TODO: there should be a lock mechanism to avoid model modifications from external sources while iterating this loop...
        #       Or as a much simpler alternative, modify the ItemSelectionMode to forbid the non contiguous selection of rows and remove the following for loop
        for row_index in sorted(selected_row_list, reverse=True):
            # Remove rows one by one to allow the removql of non-contiguously selected rows (e.g. "rows 0, 2 and 3")
            success = self.table_view.model().removeRows(row_index, 1, parent)
            if not success:
                raise Exception("Unknown error...")   # TODO
Exemple #4
0
class MainWindow(QMainWindow):
    def __init__(self, data):
        super().__init__()

        self.resize(400, 600)
        self.setWindowTitle('Logger Skeleton')
        self.statusBar().showMessage("Ready", 2000)

        # Make widgets ####################################

        self.tabs = QTabWidget(self)
        self.setCentralWidget(self.tabs)

        # Add tabs
        self.table_tab = QWidget(self)
        self.stats_tab = QWidget(self)

        self.tabs.addTab(self.table_tab, "Table")
        self.tabs.addTab(self.stats_tab, "Stats")

        # Table tab ###########################################################

        self.table_view = QTableView(self.table_tab)
        self.text_edit = QPlainTextEdit()
        self.btn_add_row = QPushButton("Add a row")
        #self.btn_remove_row = QPushButton("Remove selected rows")

        table_tab_vbox = QVBoxLayout()

        table_tab_vbox.addWidget(self.table_view)
        table_tab_vbox.addWidget(self.text_edit)
        table_tab_vbox.addWidget(self.btn_add_row)
        #table_tab_vbox.addWidget(self.btn_remove_row)

        self.table_tab.setLayout(table_tab_vbox)

        # Set model #######################################

        my_model = DataQtModel(data,
                               parent=self)  # TODO: right use of "parent" ?

        # Proxy model #####################################

        proxy_model = QSortFilterProxyModel(
            parent=self)  # TODO: right use of "parent" ?
        proxy_model.setSourceModel(my_model)

        self.table_view.setModel(proxy_model)
        #self.table_view.setModel(my_model)

        # Set the view ####################################

        self.table_view.setSelectionBehavior(
            QAbstractItemView.SelectRows
        )  # Select the full row when a cell is selected (See http://doc.qt.io/qt-5/qabstractitemview.html#selectionBehavior-prop )
        #self.table_view.setSelectionMode(QAbstractItemView.SingleSelection)  # Set selection mode. See http://doc.qt.io/qt-5/qabstractitemview.html#selectionMode-prop

        self.table_view.setAlternatingRowColors(True)
        self.table_view.setSortingEnabled(True)
        self.table_view.setColumnWidth(
            0, 200)  # TODO: automatically get the best width

        self.table_view.horizontalHeader().setSectionResizeMode(
            QHeaderView.Stretch)  # https://stackoverflow.com/q/17535563

        self.table_view.setColumnHidden(COMMENT_COLUMN_INDEX, True)

        delegate = Delegate()
        self.table_view.setItemDelegate(delegate)

        # Set key shortcut ################################

        # see https://stackoverflow.com/a/17631703  and  http://doc.qt.io/qt-5/qaction.html#details

        # Add row action

        add_action = QAction(self.table_view)
        add_action.setShortcut(Qt.CTRL | Qt.Key_N)

        add_action.triggered.connect(self.add_row_btn_callback)
        self.table_view.addAction(add_action)

        # Delete action

        del_action = QAction(self.table_view)
        del_action.setShortcut(Qt.Key_Delete)

        del_action.triggered.connect(self.remove_row_callback)
        self.table_view.addAction(del_action)

        # Set QDataWidgetMapper ###########################

        self.mapper = QDataWidgetMapper()
        self.mapper.setModel(
            proxy_model
        )  # WARNING: do not use `my_model` here otherwise the index mapping will be wrong!
        self.mapper.addMapping(self.text_edit, COMMENT_COLUMN_INDEX)
        self.mapper.toFirst()  # TODO: is it a good idea ?

        self.table_view.selectionModel().selectionChanged.connect(
            self.update_selection)

        # Set slots #######################################

        self.btn_add_row.clicked.connect(self.add_row_btn_callback)
        #self.btn_remove_row.clicked.connect(self.remove_row_callback)

        #self.table_view.setColumnHidden(1, True)

        # Stats tab ###########################################################

        # See https://matplotlib.org/examples/user_interfaces/embedding_in_qt5.html

        stats_tab_layout = QVBoxLayout(self.stats_tab)
        self.plot_canvas = PlotCanvas(data,
                                      self.stats_tab,
                                      width=5,
                                      height=4,
                                      dpi=100)
        stats_tab_layout.addWidget(self.plot_canvas)

        ###################################################

        #proxy_model.dataChanged.connect(plot_canvas.update_figure)
        #proxy_model.rowsInserted.connect(plot_canvas.update_figure)  # TODO
        #proxy_model.rowsRemoved.connect(plot_canvas.update_figure)   # TODO

        self.tabs.currentChanged.connect(
            self.updatePlot
        )  # Update the stats plot when the tabs switch to the stats tab

        # Show ############################################

        self.show()

    def update_selection(self, selected, deselected):
        index = self.table_view.selectionModel().currentIndex()
        self.mapper.setCurrentIndex(index.row())
        print("Index: ", index.row())

    def updatePlot(self, index):
        """

        Parameters
        ----------
        index

        Returns
        -------

        """
        if index == self.tabs.indexOf(self.stats_tab):
            self.plot_canvas.update_figure()

    def add_row_btn_callback(self):
        parent = QModelIndex()  # More useful with e.g. tree structures

        #row_index = 0                                           # Insert new rows to the begining
        row_index = self.table_view.model().rowCount(
            parent)  # Insert new rows to the end

        self.table_view.model().insertRows(row_index, 1, parent)

    def remove_row_callback(self):
        parent = QModelIndex()  # More useful with e.g. tree structures

        # See http://doc.qt.io/qt-5/model-view-programming.html#handling-selections-in-item-views
        #current_index = self.table_view.selectionModel().currentIndex()
        #print("Current index:", current_index.row(), current_index.column())

        selection_index_list = self.table_view.selectionModel().selectedRows()
        selected_row_list = [
            selection_index.row() for selection_index in selection_index_list
        ]

        print("Current selection:", selected_row_list)

        #row_index = 0                                           # Remove the first row
        #row_index = self.table_view.model().rowCount(parent) - 1 # Remove the last row

        # WARNING: the list of rows to remove MUST be sorted in reverse order
        # otherwise the index of rows to remove may change at each iteration of the for loop!

        # TODO: there should be a lock mechanism to avoid model modifications from external sources while iterating this loop...
        #       Or as a much simpler alternative, modify the ItemSelectionMode to forbid the non contiguous selection of rows and remove the following for loop
        for row_index in sorted(selected_row_list, reverse=True):
            # Remove rows one by one to allow the removql of non-contiguously selected rows (e.g. "rows 0, 2 and 3")
            success = self.table_view.model().removeRows(row_index, 1, parent)
            if not success:
                raise Exception("Unknown error...")  # TODO