class Application(Gtk.Application): def __init__(self, *args, **kwargs): super().__init__(*args, application_id="easy-ebook-viewer", flags=Gio.ApplicationFlags.HANDLES_COMMAND_LINE, **kwargs) self.window = None self.file_path = None GLib.set_application_name('Easy eBook Viewer') GLib.set_prgname('easy-ebook-viewer') GLib.setenv('PULSE_PROP_application.icon_name', 'easy-ebook-viewer', True) def do_startup(self): Gtk.Application.do_startup(self) action = Gio.SimpleAction.new("about", None) action.connect("activate", self.on_about) self.add_action(action) action = Gio.SimpleAction.new("quit", None) action.connect("activate", self.on_quit) self.add_action(action) def do_activate(self): GObject.threads_init() gettext.install('easy-ebook-viewer', '/usr/share/easy-ebook-viewer/locale') # We only allow a single window and raise any existing ones if not self.window: # Windows are associated with the application # when the last one is closed the application shuts down self.window = MainWindow(file_path=self.file_path) self.window.connect("delete-event", self.on_quit) self.window.set_wmclass("easy-ebook-viewer", "easy-ebook-viewer") self.window.show_all() if not self.window.book_loaded: self.window.header_bar_component.hide_jumping_navigation() Gtk.main() def do_command_line(self, command_line): # If book came from arguments ie. was oppened using "Open with..." method etc. if len(sys.argv) > 1: # Check if that file really exists if os.path.exists(sys.argv[1]): self.file_path = sys.argv[1] self.activate() return 0 def on_about(self, action, param): dialog = about_dialog.AboutDialog() dialog.show_all() def on_quit(self, action, param): Gdk.threads_leave() Gtk.main_quit() self.quit()
class Main(): def __init__(self): if os.geteuid() != 0: root_popup = Popup('Error', 'You need to be root to run the VPN.') root_popup.set_icon_from_file("surfshark_linux_client.png") root_popup.connect('destroy', Gtk.main_quit) Gtk.main() return None self.debug_on = True # set to False to disable debug infos # TODO : Make a loader # TODO : Find a way to have a popup to run the app as root self.folder_path = os.path.abspath(os.path.dirname(__file__)) + "/" self.servers = self.get_servers() self.unhash_pass = "" self.config_files = {} self.vpn_command = False self.thread = False self.ip = "" with open(self.folder_path + "config.json", "r") as file: self.config = json.load(file) style = Gtk.CssProvider() #Theme if (self.config['theme'] == 'light'): style.load_from_path(self.folder_path + "style/style_lightmode.css") else: style.load_from_path(self.folder_path + "style/style_darkmode.css") Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), style, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) if (self.config['password_needed'] or not self.config['registered']): self.log_window = LogWindow(self) self.log_window.connect("destroy", Gtk.main_quit) self.log_window.show_all() self.main_window = MainWindow(self) self.main_window.connect("destroy", self.soft_quit_g) if (not self.config['password_needed'] and self.config['registered']): screen = self.main_window.get_display() monitor_size = screen.get_monitor_at_window( Gdk.get_default_root_window()).get_geometry() self.main_window.move( monitor_size.width / 2 - self.main_window.get_size().width / 2, monitor_size.height / 2 - self.main_window.get_size().height / 2) self.main_window.credentials_username.set_text( self.config['vpn_username']) self.main_window.credentials_password.set_text( self.config['vpn_password']) self.main_window.show_all() try: Gtk.main() except (KeyboardInterrupt, SystemExit): self.soft_quit() def log_action(self, widget): hashed_password = self.hash_pass(self.log_window.password.get_text()) if self.config['registered']: if (hashed_password == self.config['password']): self.log_in() else: self.log_window.password.get_style_context().add_class('error') self.debug("Wrong password") else: if (widget == self.log_window.log_without_pass_button): self.config['registered'] = True self.config['password_needed'] = False self.save_config() self.log_in() else: if (self.log_window.password.get_text() == self.log_window.confirm_password.get_text()): self.config['registered'] = True self.config['password_needed'] = True self.config['password'] = hashed_password self.save_config() self.debug("Account created") self.log_in() else: self.log_window.password.get_style_context().add_class( 'error') self.log_window.confirm_password.get_style_context( ).add_class('error') def get_servers(self): with urllib.request.urlopen( "https://api.surfshark.com/v3/server/clusters/all") as url: return json.loads(url.read().decode()) def log_in(self): if (self.config['password_needed']): self.unhash_pass = self.log_window.password.get_text() old_pos = self.log_window.get_position() self.log_window.hide() self.main_window.move(old_pos.root_x, old_pos.root_y) self.main_window.show_all() if ('vpn_password' in self.config and 'vpn_username' in self.config and self.config['vpn_username'] != "" and self.config['vpn_password'] != ""): if (self.config['password_needed']): try: vpn_username = self.sym_decrypt( self.config['vpn_username']) vpn_password = self.sym_decrypt( self.config['vpn_password']) except: vpn_username = "" vpn_password = "" else: vpn_username = self.config['vpn_username'] vpn_password = self.config['vpn_password'] self.main_window.credentials_username.set_text(vpn_username) self.main_window.credentials_password.set_text(vpn_password) self.debug("Logged In") def save_config(self): with open(self.folder_path + "config.json", "w") as file: file.write(json.dumps(self.config)) self.debug("Config saved") def debug(self, message, type=None): if (self.debug_on): type = "INFO" if not type else type print(type + ": " + message) def save_credentials(self, widget): vpn_username = self.main_window.credentials_username.get_text() vpn_password = self.main_window.credentials_password.get_text() error_count = 0 if (vpn_username == ""): self.main_window.credentials_username.get_style_context( ).add_class('error') error_count += 1 if (vpn_password == ""): self.main_window.credentials_password.get_style_context( ).add_class('error') error_count += 1 if (error_count > 0): return if (self.config['password_needed']): vpn_username = self.sym_encrypt(vpn_username) vpn_password = self.sym_encrypt(vpn_password) self.config['vpn_username'] = vpn_username self.config['vpn_password'] = vpn_password self.save_config() creds_updated = threading.Thread(target=self.credential_updated) creds_updated.start() def sym_encrypt(self, raw): private_key = hashlib.sha256(self.unhash_pass.encode("utf-8")).digest() raw = pad(raw.encode('utf-8'), AES.block_size) iv = Random.new().read(AES.block_size) cipher = AES.new(private_key, AES.MODE_CBC, iv) return base64.b64encode(iv + cipher.encrypt(raw)).decode('utf8') def sym_decrypt(self, encrypted_text): private_key = hashlib.sha256(self.unhash_pass.encode("utf-8")).digest() encrypted_text = base64.b64decode(encrypted_text) iv = encrypted_text[:16] cipher = AES.new(private_key, AES.MODE_CBC, iv) return unpad(cipher.decrypt(encrypted_text[16:]), AES.block_size).decode('utf8') def hash_pass(self, password): return base64.b64encode( hashlib.sha3_512( password.encode("utf-8")).hexdigest().encode()).decode() def change_protocol(self, switch, is_tcp): if (is_tcp): self.config["connection_protocol"] = "tcp" self.main_window.udp_label.set_markup("UDP") self.main_window.tcp_label.set_markup("<b>TCP</b>") else: self.config["connection_protocol"] = "udp" self.main_window.udp_label.set_markup("<b>UDP</b>") self.main_window.tcp_label.set_markup("TCP") self.save_config() def change_password_need(self, button): button.set_sensitive(False) if (self.config['password_needed']): self.config['password_needed'] = False self.config['vpn_username'] = self.sym_decrypt( self.config['vpn_username']) self.config['vpn_password'] = self.sym_decrypt( self.config['vpn_password']) button.set_label("Enable Pass") else: self.config['password_needed'] = True self.config['vpn_username'] = self.sym_encrypt( self.config['vpn_username']) self.config['vpn_password'] = self.sym_encrypt( self.config['vpn_password']) button.set_label("Disable Pass") self.save_config() button.set_sensitive(True) def update_password(self, button): self.main_window.new_password.get_style_context().remove_class("error") self.main_window.confirm_new_password.get_style_context().remove_class( "error") if (self.main_window.new_password.get_text() == self.main_window.confirm_new_password.get_text()): self.config['password'] = self.hash_pass( self.main_window.new_password.get_text()) if (self.config['password_needed']): vpn_user = self.sym_decrypt(self.config['vpn_username']) vpn_pass = self.sym_decrypt(self.config['vpn_password']) else: vpn_user = self.config['vpn_username'] vpn_pass = self.config['vpn_password'] self.config['password_needed'] = True self.main_window.disable_pass_button.set_label("Disable Pass") self.main_window.enable_password_container.set_sensitive(True) self.unhash_pass = self.main_window.new_password.get_text() self.config['vpn_username'] = self.sym_encrypt(vpn_user) self.config['vpn_password'] = self.sym_encrypt(vpn_pass) self.main_window.new_password.set_text("") self.main_window.confirm_new_password.set_text("") self.save_config() password_updated_t = threading.Thread(target=self.password_updated) password_updated_t.start() else: self.main_window.new_password.get_style_context().add_class( "error") self.main_window.confirm_new_password.get_style_context( ).add_class("error") def password_updated(self): self.main_window.updated_password_label.set_label("Password updated") time.sleep(2) self.main_window.updated_password_label.set_label("") def credential_updated(self): self.main_window.updated_vpn_credential_label.set_label( "Credentials updated") time.sleep(2) self.main_window.updated_vpn_credential_label.set_label("") def change_theme(self, switch, current_theme): style = Gtk.CssProvider() if (current_theme): style.load_from_path(self.folder_path + "style/style_darkmode.css") self.config['theme'] = "dark" self.main_window.light_label.set_markup("Light") self.main_window.dark_label.set_markup("<b>Dark</b>") else: style.load_from_path(self.folder_path + "style/style_lightmode.css") self.config['theme'] = "light" self.main_window.light_label.set_markup("<b>Light</b>") self.main_window.dark_label.set_markup("Dark") Gtk.StyleContext.add_provider_for_screen( Gdk.Screen.get_default(), style, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION) self.save_config() def change_killswitch(self, switch, killswitch_status): if (killswitch_status): self.config["killswitch"] = "on" self.main_window.killswitch_off_label.set_markup("OFF") self.main_window.killswitch_on_label.set_markup("<b>ON</b>") if (self.vpn_command): self.enable_killswitch() success_popup = Popup( "Killswitch activated", "The killswitch is now activated!\nThe current version of the killswitch can leak on port 1194" ) else: self.config["killswitch"] = "off" self.main_window.killswitch_off_label.set_markup("<b>OFF</b>") self.main_window.killswitch_on_label.set_markup("ON") self.disable_killswitch() success_popup = Popup( "Killswitch deactivated", "The killswitch is now inactive!\nYour network traffic is now possible to leak" ) self.save_config() def credential_updated(self): self.main_window.updated_vpn_credential_label.set_label( "Credentials updated") time.sleep(2) self.main_window.updated_vpn_credential_label.set_label("") def switch_server(self, button): text = self.main_window.selected_label.get_text() if (text == "Nothing"): return False self.main_window.connected_to_label.set_label("Connecting...") self.main_window.switch_server_btn.set_sensitive(False) openvpn_config_file = self.config_files[text] + "_" + self.config[ 'connection_protocol'] + ".ovpn" if (self.vpn_command): self.vpn_command.terminate() self.thread.join() self.main_window.ip_label.set_label("") with open(self.folder_path + '.tmp_creds_file', 'a+') as cred_file: if (self.config['password_needed']): cred_file.write( self.sym_decrypt(self.config['vpn_username']) + "\n" + self.sym_decrypt(self.config['vpn_password'])) else: cred_file.write(self.config['vpn_username'] + "\n" + self.config['vpn_password']) subprocess.call([ "cp", self.folder_path + "vpn_config_files/" + openvpn_config_file, self.folder_path + ".tmp_cfg_file" ]) try: i = 0 with open(self.folder_path + '.tmp_cfg_file', 'r+') as cfg_file: file = cfg_file.readlines() for line in file: if ("auth-user-pass" in line): break i += 1 except: self.debug("VPN config file is missing", "ERROR") self.main_window.connected_to_label.set_label("ERROR") self.main_window.switch_server_btn.set_sensitive(True) Popup( 'Error', "Couldn't find config file for this server, try to update the config files.", self.main_window) return False with open(self.folder_path + '.tmp_cfg_file', 'w') as cfg_file: file[i] = "auth-user-pass " + self.folder_path + ".tmp_creds_file" cfg_file.writelines(file) #disable the killswitch to connect to a new server if (self.config["killswitch"] == "on"): self.disable_killswitch() self.vpn_command = subprocess.Popen( ["openvpn", self.folder_path + ".tmp_cfg_file"], stdout=subprocess.PIPE) self.thread = threading.Thread(target=self.command_log) self.thread.start() def command_log(self): time.sleep(.1) subprocess.call(["rm", self.folder_path + ".tmp_cfg_file"]) subprocess.call(["rm", self.folder_path + ".tmp_creds_file"]) for line in iter(self.vpn_command.stdout.readline, b''): line = line.decode()[:-1] if ("Exiting due to fatal error" in line): self.debug('Fatal error, VPN disconnected', type="ERROR") self.ip = "" self.main_window.vpn_exited() elif ("AUTH_FAILED" in line): self.debug('Wrong credentials', type="ERROR") self.ip = "" self.main_window.vpn_connection_failed() elif ("Initialization Sequence Completed" in line): self.debug('Connected to VPN') p = subprocess.Popen(["curl", "ipecho.net/plain"], stdout=subprocess.PIPE) ip, err = p.communicate() self.ip = ip.decode() self.main_window.confirm_connection() if (self.config["killswitch"] == "on"): self.enable_killswitch() with open( self.folder_path + "logs/openvpn-logs-" + str(date.today()) + ".txt", 'a') as logfile: logfile.write(line + "\n") def disconnect(self, button): self.debug('DISCONNECTED') self.vpn_command.terminate() self.thread.join() self.disable_killswitch() self.ip = "" self.main_window.connected_to_label.set_label("Disconnected") self.main_window.ip_label.set_label("") self.main_window.disconnect_btn.set_sensitive(False) self.main_window.switch_server_btn.set_sensitive(True) def check_updates(self, button): subprocess.call([ "wget", "https://account.surfshark.com/api/v1/server/configurations", "-O", self.folder_path + "vpn_config_files/conf.zip" ]) p = subprocess.Popen( ["md5sum", self.folder_path + "vpn_config_files/conf.zip"], stdout=subprocess.PIPE) checksum, err = p.communicate() checksum = checksum.decode().split(" ")[0] if (checksum != self.config['config_md5']): subprocess.call("rm " + self.folder_path + "vpn_config_files/*.ovpn", shell=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) subprocess.call([ "unzip", self.folder_path + "vpn_config_files/conf.zip", "-d", self.folder_path + "vpn_config_files" ], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) self.config['config_md5'] = checksum self.save_config() self.main_window.updates_info.set_label( "The config files were updated !") else: self.main_window.updates_info.set_label("Everything is okay !") subprocess.call(["rm", self.folder_path + "vpn_config_files/conf.zip"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) def soft_quit(self): if self.vpn_command and self.thread: self.vpn_command.terminate() self.thread.join() self.disable_killswitch() self.create_tray() Gtk.main_quit() def soft_quit_g(self, window): self.soft_quit() def enable_killswitch(self): #enable killswitch enable_killswitch_command = "sudo ./enablekillswitch.sh" command = enable_killswitch_command.split() subprocess.run(command) def disable_killswitch(self): #restore old iptable rules restore_iptables_command = "sudo ./restoreiptables.sh" command = restore_iptables_command.split() subprocess.run(command) def create_tray(self): #TODO pass
class PSSOptimisation(object): def __init__(self): QtCore.QCoreApplication.setApplicationName("PSSOptimisation") QtCore.QCoreApplication.setApplicationVersion(str(VERSION)) QtCore.QCoreApplication.setOrganizationName("Hubert Grzeskowiak") self.settings = QtCore.QSettings() self.main_window = MainWindow(True) self.grades_model = GradesModel(self.main_window) self.proxy_model = GradesModelProxy() self.proxy_model.setSourceModel(self.grades_model) self.initUI() # tray = QtGui.QSystemTrayIcon(self.main_window) # tray.setIcon(QtGui.QIcon("icons/Accessories-calculator.svg")) # tray.connect(tray, # SIGNAL("activated(QSystemTrayIcon::ActivationReason)"), # self.trayClicked) # tray.show() # self.tray = tray # def trayClicked(self, reason): # print reason # if reason == QtGui.QSystemTrayIcon.DoubleClick: # self.main_window.setVisible(not self.main_window.isVisible()) def initUI(self): self.connectUI() self.main_window.show() def __columnsChanged(self): """This should be called whenever the columns filter is changed. It's a workaround for a bug in the headerview not updating the columns properly on filter change. """ header = self.main_window.grades_table.horizontalHeader() header.resizeSections(QtGui.QHeaderView.ResizeToContents) header.resizeSection(0, header.sectionSize(0) - 1) header.resizeSection(0, header.sectionSize(0) + 1) def connectUI(self): self.main_window.grades_table.setModel(self.proxy_model) delegate = CheckBoxDelegate() self.main_window.grades_table.setItemDelegate(delegate) self.main_window.connect(self.main_window.action_download, SIGNAL("triggered()"), self.openLoginDialog) self.main_window.connect(self.main_window.action_donate, SIGNAL("triggered()"), self.openDonationDialog) self.main_window.connect(self.main_window.action_open, SIGNAL("triggered()"), self.openFileDialog) self.main_window.connect(self.grades_model, SIGNAL("dataChanged()"), self.updateStats) self.main_window.connect( self.grades_model, SIGNAL("dataChanged()"), self.main_window.grades_table.resizeColumnsToContents) self.main_window.connect(self.grades_model, SIGNAL("modelReset()"), self.updateStats) self.main_window.connect( self.grades_model, SIGNAL("modelReset()"), self.main_window.grades_table.resizeColumnsToContents) # workaround for buggy headerview self.main_window.connect(self.proxy_model, SIGNAL("columnsVisibilityChanged()"), self.__columnsChanged) # create actions for toggling table columns header = self.main_window.grades_table.horizontalHeader() self.header_actions = QtGui.QActionGroup(header) self.header_actions.setExclusive(False) for nr, (name, visible) in enumerate( zip(self.grades_model.header_data, self.proxy_model.col_visibility)): action = self.header_actions.addAction(name) action.setCheckable(True) action.setChecked(visible) action.connect(action, SIGNAL("triggered()"), lambda nr=nr: self.proxy_model.toggleColumn(nr)) # add that menu as context menu for the header header.addActions(self.header_actions.actions()) header.setContextMenuPolicy(QtCore.Qt.ActionsContextMenu) # and put it into the main window's menus self.main_window.menu_table_columns.clear() for action in self.header_actions.actions(): self.main_window.menu_table_columns.addAction(action) # automatically download new grades (depends on settings) if self.settings.value("updateOnStart", False).toBool(): QtCore.QTimer.singleShot(200, self.tryAutoDownloadFromPSSO) def openLoginDialog(self): self.login_dialog = LoginDialog(self.main_window) self.login_dialog.exec_() if self.login_dialog.result(): username = str(self.login_dialog.username_line.text()) password = str(self.login_dialog.password_line.text()) if username and password: remember = self.login_dialog.remember_checkbox.isChecked() self.handleLoginData(username, password, remember) def handleLoginData(self, username, password, remember=False): """Try to load the grades by using the provided login credentials. On failure, an error popup is displayed. On success a loading progress bar and after that a table is shown. If "remember" is True, then the login data is saved. """ self.main_window.setDisabled(True) QtGui.QApplication.processEvents() QtGui.QApplication.processEvents() progress = 0 try: iterator = self.grades_model.getFromPSSOIterator( username, password) for step in iterator: self.main_window.showProgress(progress, step) # getFromPSSOIterator defines 8 steps, but 1st is at 0 progress += 100.0 / 7 QtGui.QApplication.processEvents() except (ConnectionError, ServiceUnavailableError, ParsingError) as e: QtGui.QMessageBox.critical(self.main_window, e.title, e.message) return except LoginError as e: self.clearLoginData(username) QtGui.QMessageBox.critical(self.main_window, e.title, e.message) return finally: self.main_window.setEnabled(True) self.main_window.showProgress(-1) self.main_window.showTable() self.main_window.setEnabled(True) if remember: self.saveLoginData(username, password) def saveLoginData(self, username, password): assert username and password self.settings.setValue("username", username) keyring.set_password("PSSO", username, password) def getLoginData(self): """Try to retrieve previously saved username and password. Returns a tuple of two empty strings on failure. """ username = str(self.settings.value("username").toString() or "") try: password = keyring.get_password("PSSO", username) or "" except IOError: return "", "" return username, password def clearLoginData(self, username=None): """Remove username and password settings. If a username is given, its password will be removed. If there is a saved username, it will also get removed alongside with the corresponding password. """ username2 = str(self.settings.value("username").toString() or "") try: keyring.delete_password("PSSO", username) except: pass try: keyring.delete_password("PSSO", username2) except: pass self.settings.remove("username") def tryAutoDownloadFromPSSO(self): u, p = self.getLoginData() if u and p: self.handleLoginData(u, p) def openFileDialog(self): ofd = OpenFileDialog(self.main_window) accepted = ofd.exec_() if accepted and ofd.html_file: try: html = codecs.open(ofd.html_file, encoding='utf-8') except IOError: QtGui.QMessageBox.warning( self.main_window, "File not found", "Sorry, but there seems to be no file called {}.".format( ofd.html_file)) return self.grades_model.getFromHTML(html) self.main_window.showTable() def openDonationDialog(self): dp = DonationDialog(self.main_window) dp.exec_() def updateStats(self): self.main_window.num_of_grades.setText( str(self.grades_model.getNumOfGrades())) self.main_window.num_of_credits.setText( str(self.grades_model.getNumOfCredits())) self.main_window.average_grade.setText( str(self.grades_model.getAverageGrade()))
#!/usr/bin/env python3 # Easy eBook Viewer by Michal Daniel # Easy eBook Viewer is free software; you can redistribute it and/or modify it under the terms # of the GNU General Public Licence as published by the Free Software Foundation. # Easy eBook Viewer is distributed in the hope that it will be useful, but WITHOUT ANY # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A # PARTICULAR PURPOSE. See the GNU General Public Licence for more details. # You should have received a copy of the GNU General Public Licence along with # Easy eBook Viewer; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, # Fifth Floor, Boston, MA 02110-1301, USA. import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk from main_window import MainWindow # Let the fun begin... if __name__ == "__main__": win = MainWindow() win.connect("delete-event", Gtk.main_quit) win.show_all() if not win.book_loaded: win.header_bar_component.hide_jumping_navigation() Gtk.main()
class PSSOptimisation(object): def __init__(self): QtCore.QCoreApplication.setApplicationName("PSSOptimisation") QtCore.QCoreApplication.setApplicationVersion(str(VERSION)) QtCore.QCoreApplication.setOrganizationName("Hubert Grzeskowiak") self.settings = QtCore.QSettings() self.main_window = MainWindow(True) self.grades_model = GradesModel(self.main_window) self.proxy_model = GradesModelProxy() self.proxy_model.setSourceModel(self.grades_model) self.initUI() # tray = QtGui.QSystemTrayIcon(self.main_window) # tray.setIcon(QtGui.QIcon("icons/Accessories-calculator.svg")) # tray.connect(tray, # SIGNAL("activated(QSystemTrayIcon::ActivationReason)"), # self.trayClicked) # tray.show() # self.tray = tray # def trayClicked(self, reason): # print reason # if reason == QtGui.QSystemTrayIcon.DoubleClick: # self.main_window.setVisible(not self.main_window.isVisible()) def initUI(self): self.connectUI() self.main_window.show() def __columnsChanged(self): """This should be called whenever the columns filter is changed. It's a workaround for a bug in the headerview not updating the columns properly on filter change. """ header = self.main_window.grades_table.horizontalHeader() header.resizeSections(QtGui.QHeaderView.ResizeToContents) header.resizeSection(0, header.sectionSize(0)-1) header.resizeSection(0, header.sectionSize(0)+1) def connectUI(self): self.main_window.grades_table.setModel(self.proxy_model) delegate = CheckBoxDelegate() self.main_window.grades_table.setItemDelegate(delegate) self.main_window.connect(self.main_window.action_download, SIGNAL("triggered()"), self.openLoginDialog) self.main_window.connect(self.main_window.action_donate, SIGNAL("triggered()"), self.openDonationDialog) self.main_window.connect(self.main_window.action_open, SIGNAL("triggered()"), self.openFileDialog) self.main_window.connect(self.grades_model, SIGNAL("dataChanged()"), self.updateStats) self.main_window.connect(self.grades_model, SIGNAL("dataChanged()"), self.main_window.grades_table.resizeColumnsToContents) self.main_window.connect(self.grades_model, SIGNAL("modelReset()"), self.updateStats) self.main_window.connect(self.grades_model, SIGNAL("modelReset()"), self.main_window.grades_table.resizeColumnsToContents) # workaround for buggy headerview self.main_window.connect(self.proxy_model, SIGNAL("columnsVisibilityChanged()"), self.__columnsChanged) # create actions for toggling table columns header = self.main_window.grades_table.horizontalHeader() self.header_actions = QtGui.QActionGroup(header) self.header_actions.setExclusive(False) for nr, (name, visible) in enumerate(zip( self.grades_model.header_data, self.proxy_model.col_visibility)): action = self.header_actions.addAction(name) action.setCheckable(True) action.setChecked(visible) action.connect(action, SIGNAL("triggered()"), lambda nr=nr: self.proxy_model.toggleColumn(nr)) # add that menu as context menu for the header header.addActions(self.header_actions.actions()) header.setContextMenuPolicy( QtCore.Qt.ActionsContextMenu) # and put it into the main window's menus self.main_window.menu_table_columns.clear() for action in self.header_actions.actions(): self.main_window.menu_table_columns.addAction(action) # automatically download new grades (depends on settings) if self.settings.value("updateOnStart", False).toBool(): QtCore.QTimer.singleShot(200, self.tryAutoDownloadFromPSSO) def openLoginDialog(self): self.login_dialog = LoginDialog(self.main_window) self.login_dialog.exec_() if self.login_dialog.result(): username = str(self.login_dialog.username_line.text()) password = str(self.login_dialog.password_line.text()) if username and password: remember = self.login_dialog.remember_checkbox.isChecked() self.handleLoginData(username, password, remember) def handleLoginData(self, username, password, remember=False): """Try to load the grades by using the provided login credentials. On failure, an error popup is displayed. On success a loading progress bar and after that a table is shown. If "remember" is True, then the login data is saved. """ self.main_window.setDisabled(True) QtGui.QApplication.processEvents() QtGui.QApplication.processEvents() progress = 0 try: iterator = self.grades_model.getFromPSSOIterator(username, password) for step in iterator: self.main_window.showProgress(progress, step) # getFromPSSOIterator defines 8 steps, but 1st is at 0 progress += 100.0/7 QtGui.QApplication.processEvents() except (ConnectionError, ServiceUnavailableError, ParsingError) as e: QtGui.QMessageBox.critical(self.main_window, e.title, e.message) return except LoginError as e: self.clearLoginData(username) QtGui.QMessageBox.critical(self.main_window, e.title, e.message) return finally: self.main_window.setEnabled(True) self.main_window.showProgress(-1) self.main_window.showTable() self.main_window.setEnabled(True) if remember: self.saveLoginData(username, password) def saveLoginData(self, username, password): assert username and password self.settings.setValue("username", username) keyring.set_password("PSSO", username, password) def getLoginData(self): """Try to retrieve previously saved username and password. Returns a tuple of two empty strings on failure. """ username = str(self.settings.value("username").toString() or "") try: password = keyring.get_password("PSSO", username) or "" except IOError: return "", "" return username, password def clearLoginData(self, username=None): """Remove username and password settings. If a username is given, its password will be removed. If there is a saved username, it will also get removed alongside with the corresponding password. """ username2 = str(self.settings.value("username").toString() or "") try: keyring.delete_password("PSSO", username) except: pass try: keyring.delete_password("PSSO", username2) except: pass self.settings.remove("username") def tryAutoDownloadFromPSSO(self): u, p = self.getLoginData() if u and p: self.handleLoginData(u, p) def openFileDialog(self): ofd = OpenFileDialog(self.main_window) accepted = ofd.exec_() if accepted and ofd.html_file: try: html = codecs.open(ofd.html_file, encoding='utf-8') except IOError: QtGui.QMessageBox.warning(self.main_window, "File not found", "Sorry, but there seems to be no file called {}.".format( ofd.html_file)) return self.grades_model.getFromHTML(html) self.main_window.showTable() def openDonationDialog(self): dp = DonationDialog(self.main_window) dp.exec_() def updateStats(self): self.main_window.num_of_grades.setText(str( self.grades_model.getNumOfGrades())) self.main_window.num_of_credits.setText(str( self.grades_model.getNumOfCredits())) self.main_window.average_grade.setText(str( self.grades_model.getAverageGrade()))
def do_activate(self): win = MainWindow(app=self) # win.connect("delete-event", Gtk.main_quit) win.connect("key_press_event", win.press_key_event) win.set_resizable(False) win.show_all()