class DownloadProgress(QWidget): def __init__(self, parent=None): QWidget.__init__(self, parent) self.setVisible(False) self.l = QVBoxLayout(self) self.items = {} def add_item(self, download_id, filename): self.setVisible(True) item = DownloadItem(download_id, filename, self) self.l.addWidget(item) self.items[download_id] = item def update_item(self, download_id, done, total): item = self.items.get(download_id) if item is not None: item(done, total) def remove_item(self, download_id): item = self.items.pop(download_id, None) if item is not None: self.l.removeWidget(item) item.setVisible(False) item.setParent(None) item.deleteLater() if not self.items: self.setVisible(False)
class SearchTheInternet(QWidget): changed_signal = pyqtSignal() def __init__(self, parent): QWidget.__init__(self, parent) self.sa = QScrollArea(self) self.lw = QWidget(self) self.l = QVBoxLayout(self.lw) self.sa.setWidget(self.lw), self.sa.setWidgetResizable(True) self.gl = gl = QVBoxLayout(self) self.la = QLabel(_( 'Add new locations to search for books or authors using the "Search the internet" feature' ' of the Content server. The URLs should contain {author} which will be' ' replaced by the author name and, for book URLs, {title} which will' ' be replaced by the book title.')) self.la.setWordWrap(True) gl.addWidget(self.la) self.h = QHBoxLayout() gl.addLayout(self.h) self.add_url_button = b = QPushButton(QIcon(I('plus.png')), _('&Add URL')) b.clicked.connect(self.add_url) self.h.addWidget(b) self.export_button = b = QPushButton(_('Export URLs')) b.clicked.connect(self.export_urls) self.h.addWidget(b) self.import_button = b = QPushButton(_('Import URLs')) b.clicked.connect(self.import_urls) self.h.addWidget(b) self.clear_button = b = QPushButton(_('Clear')) b.clicked.connect(self.clear) self.h.addWidget(b) self.h.addStretch(10) gl.addWidget(self.sa, stretch=10) self.items = [] def genesis(self): self.current_urls = search_the_net_urls() or [] @property def current_urls(self): return [item.as_dict for item in self.items if not item.is_empty] def append_item(self, item_as_dict): self.items.append(URLItem(item_as_dict, self)) self.l.addWidget(self.items[-1]) def clear(self): [(self.l.removeWidget(w), w.setParent(None), w.deleteLater()) for w in self.items] self.items = [] self.changed_signal.emit() @current_urls.setter def current_urls(self, val): self.clear() for entry in val: self.append_item(entry) def add_url(self): self.items.append(URLItem(None, self)) self.l.addWidget(self.items[-1]) QTimer.singleShot(100, self.scroll_to_bottom) def scroll_to_bottom(self): sb = self.sa.verticalScrollBar() if sb: sb.setValue(sb.maximum()) self.items[-1].name_widget.setFocus(Qt.OtherFocusReason) @property def serialized_urls(self): return json.dumps(self.current_urls, indent=2) def commit(self): for item in self.items: if not item.validate(): return False cu = self.current_urls if cu: with lopen(search_the_net_urls.path, 'wb') as f: f.write(self.serialized_urls) else: try: os.remove(search_the_net_urls.path) except EnvironmentError as err: if err.errno != errno.ENOENT: raise return True def export_urls(self): path = choose_save_file( self, 'search-net-urls', _('Choose URLs file'), filters=[(_('URL files'), ['json'])], initial_filename='search-urls.json') if path: with lopen(path, 'wb') as f: f.write(self.serialized_urls) def import_urls(self): paths = choose_files(self, 'search-net-urls', _('Choose URLs file'), filters=[(_('URL files'), ['json'])], all_files=False, select_only_single_file=True) if paths: with lopen(paths[0], 'rb') as f: items = json.loads(f.read()) [self.append_item(x) for x in items] self.changed_signal.emit()
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 ConfigWidget(QWidget): def __init__(self): self.plugin_prefs = prefs self.restart_required = False self.inspector = None QWidget.__init__(self) self.l = QVBoxLayout() self.setLayout(self.l) #add checkbox self.inspector_checkbox = QCheckBox('Show Inspector') self.inspector_checkbox.setChecked( self.plugin_prefs.get('inspector_enabled', False)) self.inspector_checkbox.stateChanged.connect( self._inspector_enabled_changed) self.inspector_checkbox.setToolTip( 'Enable Javascript Console for debugging WebView requests to QuietThyme API' ) self.l.addWidget(self.inspector_checkbox) self.beta_checkbox = QCheckBox('Beta Mode') self.beta_checkbox.setChecked(self.plugin_prefs.get( 'beta_mode', False)) self.beta_checkbox.stateChanged.connect(self._set_restart_required) self.beta_checkbox.stateChanged.connect(self._beta_mode_changed) self.beta_checkbox.setToolTip( 'Tell Calibre to communicate with the Beta version of QuietThyme') self.l.addWidget(self.beta_checkbox) self.config_url = QUrl(self.plugin_prefs.get('web_base') + '/storage') self.webview = QTWebView(bearer_token=self.plugin_prefs.get('token')) self.webview.load(self.config_url) self.global_settings = self.webview.page().settings( ) #QWebSettings.globalSettings() self.global_settings.setAttribute( QWebSettings.LocalStorageEnabled, True) #required since this is where we store tokens. self.global_settings.setAttribute(QWebSettings.PrivateBrowsingEnabled, False) self.global_settings.setAttribute(QWebSettings.JavascriptEnabled, True) self.global_settings.setAttribute( QWebSettings.JavascriptCanOpenWindows, True) self.global_settings.setAttribute( QWebSettings.JavascriptCanCloseWindows, True) self.global_settings.setAttribute(QWebSettings.PluginsEnabled, True) self.global_settings.setAttribute( QWebSettings.LocalContentCanAccessRemoteUrls, True) self.global_settings.setAttribute( QWebSettings.LocalContentCanAccessFileUrls, True) self.global_settings.setAttribute(QWebSettings.XSSAuditingEnabled, False) self.global_settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True) # ensure that we get a random offline/localstorage/cache path for the brower (so that the localstorage data is not persistent across sessions) path = None while True: potential_path = QTemporaryDir() if potential_path.isValid(): path = potential_path.path() break self.global_settings.setOfflineStoragePath(path) self.global_settings.setOfflineWebApplicationCachePath(path) self.global_settings.enablePersistentStorage(path) self.global_settings.setLocalStoragePath(path) self.webview.show() self.l.addWidget(self.webview) self.configure_webview_inspector_ui() self.webview.urlChanged.connect(self._url_changed) self.webview.loadFinished.connect(self._load_finished) ################################################################################################################ # Configure UI methods # def configure_webview_inspector_ui(self): if self.plugin_prefs.get('inspector_enabled'): self.webview.setMinimumSize(QSize(600, 300)) self.inspector = QWebInspector() self.inspector.setMinimumSize(QSize(600, 300)) self.inspector.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.l.addWidget(self.inspector) self.inspector.setPage(self.webview.page()) else: self.webview.setMinimumSize(QSize(600, 600)) if self.inspector is not None: self.l.removeWidget(self.inspector) self.inspector.setParent(None) self.inspector = None def save_settings(self): self.plugin_prefs.set('inspector_enabled', self.inspector_checkbox.isChecked()) self.plugin_prefs.set('beta_mode', self.beta_checkbox.isChecked()) # # If restart needed, inform user if self.restart_required: # do_restart = show_restart_warning('Restart calibre for the changes to be applied.', # parent=self.l) # if do_restart: # self.gui.quit(restart=True) # TODO: figure out if theres a way to call self.gui.quit() msg = QMessageBox() msg.setIcon(QMessageBox.Warning) msg.setText("Restart Required") msg.setInformativeText( "A configuration change requires you to restart Calibre, You should do so now," ) msg.setWindowTitle("Restart Required") msg.setStandardButtons(QMessageBox.Ok) msg.exec_() def validate(self): # TODO: validate the that the api_endpoint and web endpoint are valid urls. return True ################################################################################################################ # Event Handlers # def _url_changed(self, url): logger.debug(sys._getframe().f_code.co_name, url) def _load_finished(self, ok): logger.debug(sys._getframe().f_code.co_name, ok) if self.webview.page().mainFrame().url() == self.config_url: logger.debug( "Loading finished and the url is the same as the desired destination url, ", self.config_url) self.webview.page().mainFrame().evaluateJavaScript(""" console.log("GET LOCALSTORAGE TOKEN") """) token = self.webview.page().mainFrame().evaluateJavaScript(""" localStorage.getItem('id_token'); """) logger.debug("Got JWT Token", token) self.plugin_prefs.set('token', token) def _set_restart_required(self, state): ''' Set restart_required flag to show show dialog when closing dialog ''' logger.debug(sys._getframe().f_code.co_name, "restart required") self.restart_required = True def _beta_mode_changed(self, state): logger.debug(sys._getframe().f_code.co_name, self.beta_checkbox.isChecked()) self.plugin_prefs.set('beta_mode', self.beta_checkbox.isChecked()) # if the beta mode has changed, we need to reset the api endpoints, and then wipe the token (not valid between envs) self.plugin_prefs.set('token', '') if self.plugin_prefs.get('beta_mode'): self.plugin_prefs.set('api_base', beta_api_base) self.plugin_prefs.set('web_base', beta_web_base) else: self.plugin_prefs.set('api_base', master_api_base) self.plugin_prefs.set('web_base', master_web_base) # after we reset the token, we need to generate a new QTWebView with the new config, and then reload the login page. self.config_url = QUrl(self.plugin_prefs.get('web_base') + '/storage') self.webview.set_bearer_token(self.plugin_prefs.get('token')) self.webview.load(self.config_url) def _inspector_enabled_changed(self, state): logger.debug(sys._getframe().f_code.co_name, self.inspector_checkbox.isChecked()) self.plugin_prefs.set('inspector_enabled', self.inspector_checkbox.isChecked()) self.configure_webview_inspector_ui()
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