class MainWidget(TritonWidget): def __init__(self, base): TritonWidget.__init__(self, base) self.addOTP = None self.closeEvent = self.widgetDeleted self.setWindowTitle('TritonAuth') self.setBackgroundColor(self, Qt.white) self.menu = QMenuBar() self.addMenu = self.menu.addMenu('Add') self.authAction = QAction('Authenticator', self) self.authAction.triggered.connect(self.openAddOTP) self.steamAction = QAction('Steam', self) self.steamAction.triggered.connect(self.openAddSteam) self.addMenu.addAction(self.authAction) self.addMenu.addAction(self.steamAction) self.sortMenu = self.menu.addMenu('Sort') self.nameAction = QAction('Sort by name', self) self.nameAction.triggered.connect(self.sortByName) self.sortMenu.addAction(self.nameAction) self.exportMenu = self.menu.addMenu('Export') self.andOTPAction = QAction('Export to andOTP', self) self.andOTPAction.triggered.connect(self.exportToAndOTP) self.exportMenu.addAction(self.andOTPAction) self.widget = QWidget() self.widget.setContentsMargins(10, 10, 10, 10) self.scrollArea = QScrollArea() self.scrollArea.setFixedSize(400, 495) self.scrollArea.setWidgetResizable(True) self.scrollWidget = QWidget() self.scrollLayout = QVBoxLayout(self.scrollWidget) self.scrollLayout.setAlignment(Qt.AlignTop) self.createAccounts() self.scrollArea.setWidget(self.scrollWidget) self.widgetLayout = QVBoxLayout(self.widget) self.widgetLayout.addWidget(self.scrollArea) self.boxLayout = QVBoxLayout(self) self.boxLayout.setContentsMargins(0, 5, 0, 0) self.boxLayout.addWidget(self.menu) self.boxLayout.addWidget(self.widget) self.setFixedSize(self.sizeHint()) self.center() self.show() def keyPressEvent(self, event): if type(event) != QKeyEvent: return letter = event.text().strip().lower() for i in range(self.scrollLayout.count()): widget = self.scrollLayout.itemAt(i).widget() if widget is not None and widget.name[0].lower() == letter: self.scrollArea.verticalScrollBar().setValue( widget.geometry().top()) return def widgetDeleted(self, arg): self.closeAddOTP() def closeAddOTP(self): if self.addOTP: self.addOTP.close() self.addOTP = None def addAccount(self, account): entry = EntryWidget(self.base, account) self.scrollLayout.addWidget(entry) def deleteAccount(self, account): for i in range(self.scrollLayout.count()): widget = self.scrollLayout.itemAt(i).widget() if widget.account == account: widget.close() def clearAccounts(self): for i in range(self.scrollLayout.count()): self.scrollLayout.itemAt(i).widget().close() def createAccounts(self): for account in self.base.getAccounts(): self.addAccount(account) def openAddOTP(self): self.closeAddOTP() self.addOTP = AddOTPWidget(self.base) def openAddSteam(self): self.closeAddOTP() self.addOTP = AddSteamWidget(self.base) def sortByName(self): self.base.sortAccountsByName() self.clearAccounts() self.createAccounts() def exportToAndOTP(self): accounts = [] for account in self.base.getAccounts(): type = account['type'] if type == Globals.OTPAuth: accounts.append({ 'secret': account['key'], 'digits': 6, 'period': 30, 'label': account['name'], 'type': 'TOTP', 'algorithm': 'SHA1', 'thumbnail': 'Default', 'last_used': 0, 'tags': [] }) elif type == Globals.SteamAuth: accounts.append({ 'secret': base64.b32encode(base64.b64decode( account['sharedSecret'])).decode('utf-8'), 'digits': 5, 'period': 30, 'label': account['name'], 'type': 'STEAM', 'algorithm': 'SHA1', 'thumbnail': 'Default', 'last_used': 0, 'tags': [] }) accounts = json.dumps(accounts) filename, _ = QFileDialog.getSaveFileName( self, 'Export to andOTP JSON file', '', 'All Files (*)') if filename: with open(filename, 'w') as file: file.write(accounts)
class OrderableListWidget(QScrollArea): """All available items in this list""" _item_list: list[OrderableListItem] """This lists actual widget""" _widget: QWidget """The widgets layout""" _layout: QLayout """Decides which way this list is ordered; 1 for ascending, -1 for descending""" _order_factor: int def __init__(self, order_asc=True, orientation_horizontal=False): """Init gui :type order_asc: bool :param order_asc: Whether to order the list ascending :type orientation_horizontal: bool :param orientation_horizontal: Should the list orientation be horizontal? """ super().__init__() if order_asc: self._order_factor = 1 else: self._order_factor = -1 self._widget = QWidget() self.setWidget(self._widget) self.setWidgetResizable(True) # Set layout if orientation_horizontal: self._layout = QHBoxLayout() else: self._layout = QVBoxLayout() self._widget.setLayout(self._layout) self._layout.setAlignment(Qt.AlignTop) self._item_list = [] def _get_order(self, list_item_a, list_item_b): """Defines this lists widget order :type list_item_a: OrderableListItem :param list_item_a: The first item to compare :type list_item_b: OrderableListItem :param list_item_b: The second item to compare :returns -1|0|1: list_item_a is: before, same, after list_item_b""" str_a: str = list_item_a.get_order_string() str_b: str = list_item_b.get_order_string() if str_a == str_b: return 0 elif str_a < str_b: return -1 * self._order_factor else: return 1 * self._order_factor def add(self, list_item): """Add a new item to the list :type list_item: OrderableListItem :param list_item: The item to add """ # Subscribe to changes list_item.subscribe(OrderableListItem.DELETED, self._item_deleted) list_item.subscribe(OrderableListItem.UPDATED, self._item_updated) # Make sure to add the item only once if list_item not in self._item_list: list_item_inserted = False self._item_list.append(list_item) # Walk all existing items for i in range(self._layout.count()): existing_item: OrderableListItem = self._layout.itemAt(i).widget() if 1 == self._get_order(existing_item, list_item): self._layout.insertWidget(i, list_item) list_item_inserted = True break if not list_item_inserted: self._layout.addWidget(list_item) def _item_deleted(self, item): """Delete an item from the list :type item: OrderableListItem :param item: The item to delete """ # See if the item exists in this list try: i: int = self._item_list.index(item) except ValueError: return # Delete the item self._item_list.pop(i) def _item_updated(self, item): """Update the list with the items new information :type item: OrderableListItem :param item: The item that was updated """ pass