diff --git a/.gitignore b/.gitignore index 82f9275..e405b20 100644 --- a/.gitignore +++ b/.gitignore @@ -159,4 +159,7 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ +*.db +*.kdbx + diff --git a/asset/img/eye-off.svg b/asset/img/eye-off.svg new file mode 100644 index 0000000..95f4c30 --- /dev/null +++ b/asset/img/eye-off.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/asset/img/eye.svg b/asset/img/eye.svg new file mode 100644 index 0000000..663f3f1 --- /dev/null +++ b/asset/img/eye.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..a501667 --- /dev/null +++ b/main.py @@ -0,0 +1,18 @@ +# coding: utf8 +import sys + +from PySide6.QtWidgets import QApplication + +from src.mw_kps_unifier import KpsUnifier +import src.rc_kps_unifier + + +def main(): + app = QApplication(sys.argv) + win = KpsUnifier() + win.show() + return app.exec() + + +if __name__ == '__main__': + main() diff --git a/src/kps_unifier.qrc b/src/kps_unifier.qrc new file mode 100644 index 0000000..3939f56 --- /dev/null +++ b/src/kps_unifier.qrc @@ -0,0 +1,6 @@ + + + asset/img/eye.svg + asset/img/eye-off.svg + + diff --git a/src/mw_kps_unifier.py b/src/mw_kps_unifier.py new file mode 100644 index 0000000..148d9df --- /dev/null +++ b/src/mw_kps_unifier.py @@ -0,0 +1,153 @@ +# coding: utf8 +from PySide6 import QtWidgets, QtCore, QtGui + + +class GbxKpsLogin(QtWidgets.QGroupBox): + def __init__(self, path: str, parent=None): + super().__init__(parent) + self.is_loaded = False + + self.icon_eye = QtGui.QIcon(":/asset/img/eye.svg") + self.icon_eye_off = QtGui.QIcon(":/asset/img/eye-off.svg") + + self.vly_m = QtWidgets.QVBoxLayout() + self.setLayout(self.vly_m) + + self.hly_path = QtWidgets.QHBoxLayout() + self.vly_m.addLayout(self.hly_path) + + self.lb_path = QtWidgets.QLabel("路径:", self) + self.hly_path.addWidget(self.lb_path) + self.lne_path = QtWidgets.QLineEdit(path, self) + self.lne_path.setEnabled(False) + self.hly_path.addWidget(self.lne_path) + + self.hly_password = QtWidgets.QHBoxLayout() + self.vly_m.addLayout(self.hly_password) + + self.lb_password = QtWidgets.QLabel("密码:", self) + self.hly_password.addWidget(self.lb_password) + self.lne_password = QtWidgets.QLineEdit(self) + self.lne_password.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password) + self.hly_password.addWidget(self.lne_password) + + self.pbn_eye = QtWidgets.QPushButton(icon=self.icon_eye_off, parent=self) + self.hly_password.addWidget(self.pbn_eye) + + self.hly_bottom = QtWidgets.QHBoxLayout() + self.vly_m.addLayout(self.hly_bottom) + self.lb_loaded = QtWidgets.QLabel("【已加载】", self) + self.hly_bottom.addWidget(self.lb_loaded) + self.lb_loaded.setVisible(False) + self.hly_bottom.addStretch(1) + + self.pbn_load = PushButtonWithItem(self, self, "加载") + self.hly_bottom.addWidget(self.pbn_load) + self.pbn_remove = PushButtonWithItem(self, self, "移除") + self.hly_bottom.addWidget(self.pbn_remove) + + self.pbn_eye.clicked.connect(self.on_pbn_eye_clicked) + + def on_pbn_eye_clicked(self): + is_pass_mode = self.lne_password.echoMode() == QtWidgets.QLineEdit.EchoMode.Password + if is_pass_mode: + self.lne_password.setEchoMode(QtWidgets.QLineEdit.EchoMode.Normal) + self.pbn_eye.setIcon(self.icon_eye) + else: + self.lne_password.setEchoMode(QtWidgets.QLineEdit.EchoMode.Password) + self.pbn_eye.setIcon(self.icon_eye_off) + + +class PushButtonWithItem(QtWidgets.QPushButton): + + clicked_with_item = QtCore.Signal(GbxKpsLogin) + + def __init__(self, item: GbxKpsLogin, parent: QtWidgets.QWidget = None, title: str = ""): + super().__init__(title, parent) + self.item = item + self.clicked.connect(self.on_self_clicked) + + def on_self_clicked(self): + self.clicked_with_item.emit(self.item) + + +class WgLoadKps(QtWidgets.QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.vly_m = QtWidgets.QVBoxLayout() + self.setLayout(self.vly_m) + self.vly_m.addStretch(1) + + def add_kps(self, path: str): + wg = GbxKpsLogin(path, self) + wg.pbn_remove.clicked_with_item.connect(self.on_item_pbn_remove_clicked) + wg.pbn_load.clicked_with_item.connect(self.on_item_pbn_load_clicked) + # 从倒数第二个位置插入,保证弹簧始终在最后 + self.vly_m.insertWidget(self.vly_m.count() - 1, wg) + + def on_item_pbn_remove_clicked(self, item: GbxKpsLogin): + self.vly_m.removeWidget(item) + item.deleteLater() + + def on_item_pbn_load_clicked(self, item: GbxKpsLogin): + item.is_loaded = True + item.lb_loaded.setVisible(True) + item.pbn_load.setDisabled(True) + + +class TabLoad(QtWidgets.QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.hly_m = QtWidgets.QHBoxLayout() + self.setLayout(self.hly_m) + self.vly_left = QtWidgets.QVBoxLayout() + self.hly_m.addLayout(self.vly_left) + + self.pbn_add = QtWidgets.QPushButton("添加", self) + self.vly_left.addWidget(self.pbn_add) + self.pbn_load_all = QtWidgets.QPushButton("加载全部", self) + self.vly_left.addWidget(self.pbn_load_all) + self.vly_left.addStretch(1) + + self.sa = QtWidgets.QScrollArea(self) + self.sa.setWidgetResizable(True) + self.hly_m.addWidget(self.sa) + self.wg_sa = WgLoadKps(self.sa) + self.sa.setWidget(self.wg_sa) + + self.hly_m.setStretchFactor(self.vly_left, 1) + self.hly_m.setStretchFactor(self.sa, 6) + + self.pbn_add.clicked.connect(self.on_pbn_add_clicked) + + def on_pbn_add_clicked(self): + filenames, _ = QtWidgets.QFileDialog.getOpenFileNames(self, "选择", "../", + filter="KeePass 2 数据库 (*.kdbx);;所有文件(*)") + if len(filenames) == 0: + return + for filename in filenames: + self.wg_sa.add_kps(filename) + + +class UiKpsUnifier(object): + def __init__(self, window: QtWidgets.QWidget): + window.setWindowTitle('KeePassXC 多合一') + self.vly_m = QtWidgets.QVBoxLayout() + window.setLayout(self.vly_m) + + self.tw_m = QtWidgets.QTabWidget(window) + self.vly_m.addWidget(self.tw_m) + + self.tab_load = TabLoad(window) + self.tw_m.addTab(self.tab_load, "加载") + self.tab_query = QtWidgets.QWidget(window) + self.tw_m.addTab(self.tab_query, "查询") + + +class KpsUnifier(QtWidgets.QWidget): + def __init__(self, parent=None): + super().__init__(parent) + self.ui = UiKpsUnifier(self) + + def sizeHint(self): + return QtCore.QSize(860, 640) diff --git a/src/rc_kps_unifier.py b/src/rc_kps_unifier.py new file mode 100644 index 0000000..5d9f4e0 --- /dev/null +++ b/src/rc_kps_unifier.py @@ -0,0 +1,157 @@ +# Resource object code (Python 3) +# Created by: object code +# Created by: The Resource Compiler for Qt version 6.5.2 +# WARNING! All changes made in this file will be lost! + +from PySide6 import QtCore + +qt_resource_data = b"\ +\x00\x00\x01\xf4\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 class=\x22io\ +nicon\x22 viewBox=\x22\ +0 0 512 512\x22>\ +\ +\x00\x00\x04|\ +<\ +svg xmlns=\x22http:\ +//www.w3.org/200\ +0/svg\x22 class=\x22io\ +nicon\x22 viewBox=\x22\ +0 0 512 512\x22>\ +" + +qt_resource_name = b"\ +\x00\x05\ +\x00h\xa9\xc4\ +\x00a\ +\x00s\x00s\x00e\x00t\ +\x00\x03\ +\x00\x00p7\ +\x00i\ +\x00m\x00g\ +\x00\x07\ +\x0c\xf8Z\x07\ +\x00e\ +\x00y\x00e\x00.\x00s\x00v\x00g\ +\x00\x0b\ +\x05\x10\xaaG\ +\x00e\ +\x00y\x00e\x00-\x00o\x00f\x00f\x00.\x00s\x00v\x00g\ +" + +qt_resource_struct = b"\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x01\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x00\x00\x02\x00\x00\x00\x01\x00\x00\x00\x02\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x00\x10\x00\x02\x00\x00\x00\x02\x00\x00\x00\x03\ +\x00\x00\x00\x00\x00\x00\x00\x00\ +\x00\x00\x000\x00\x00\x00\x00\x00\x01\x00\x00\x01\xf8\ +\x00\x00\x01\x90\xc8\xe4\x89P\ +\x00\x00\x00\x1c\x00\x00\x00\x00\x00\x01\x00\x00\x00\x00\ +\x00\x00\x01\x90\xc8\xe4\xa5\xc7\ +" + +def qInitResources(): + QtCore.qRegisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +def qCleanupResources(): + QtCore.qUnregisterResourceData(0x03, qt_resource_struct, qt_resource_name, qt_resource_data) + +qInitResources()