class TaskViewer(QTreeWidget): """ Class implementing the task viewer. @signal displayFile(str, int) emitted to go to a file task """ displayFile = pyqtSignal(str, int) def __init__(self, parent, project): """ Constructor @param parent the parent (QWidget) @param project reference to the project object """ super(TaskViewer, self).__init__(parent) self.setRootIsDecorated(False) self.setItemsExpandable(False) self.setSortingEnabled(True) self.__headerItem = QTreeWidgetItem([ "", "", self.tr("Summary"), self.tr("Filename"), self.tr("Line"), "" ]) self.__headerItem.setIcon(0, UI.PixmapCache.getIcon("taskCompleted.png")) self.__headerItem.setIcon(1, UI.PixmapCache.getIcon("taskPriority.png")) self.setHeaderItem(self.__headerItem) self.header().setSortIndicator(2, Qt.AscendingOrder) self.__resizeColumns() self.tasks = [] self.copyTask = None self.projectOpen = False self.project = project self.projectTasksScanFilter = "" from .TaskFilter import TaskFilter self.taskFilter = TaskFilter() self.taskFilter.setActive(False) self.__projectTasksSaveTimer = AutoSaver(self, self.saveProjectTasks) self.__projectTasksMenu = QMenu(self.tr("P&roject Tasks"), self) self.__projectTasksMenu.addAction(self.tr("&Regenerate project tasks"), self.__regenerateProjectTasks) self.__projectTasksMenu.addSeparator() self.__projectTasksMenu.addAction( self.tr("&Configure scan options"), self.__configureProjectTasksScanOptions) self.__menu = QMenu(self) self.__menu.addAction(self.tr("&New Task..."), self.__newTask) self.__menu.addSeparator() self.projectTasksMenuItem = self.__menu.addMenu( self.__projectTasksMenu) self.__menu.addSeparator() self.gotoItem = self.__menu.addAction(self.tr("&Go To"), self.__goToTask) self.__menu.addSeparator() self.copyItem = self.__menu.addAction(self.tr("&Copy"), self.__copyTask) self.pasteItem = self.__menu.addAction(self.tr("&Paste"), self.__pasteTask) self.deleteItem = self.__menu.addAction(self.tr("&Delete"), self.__deleteTask) self.__menu.addSeparator() self.markCompletedItem = self.__menu.addAction( self.tr("&Mark Completed"), self.__markCompleted) self.__menu.addAction(self.tr("Delete Completed &Tasks"), self.__deleteCompleted) self.__menu.addSeparator() self.__menu.addAction(self.tr("P&roperties..."), self.__editTaskProperties) self.__menu.addSeparator() self.__menuFilteredAct = self.__menu.addAction( self.tr("&Filtered display")) self.__menuFilteredAct.setCheckable(True) self.__menuFilteredAct.setChecked(False) self.__menuFilteredAct.triggered[bool].connect(self.__activateFilter) self.__menu.addAction(self.tr("Filter c&onfiguration..."), self.__configureFilter) self.__menu.addSeparator() self.__menu.addAction(self.tr("Resi&ze columns"), self.__resizeColumns) self.__menu.addSeparator() self.__menu.addAction(self.tr("Configure..."), self.__configure) self.__backMenu = QMenu(self) self.__backMenu.addAction(self.tr("&New Task..."), self.__newTask) self.__backMenu.addSeparator() self.backProjectTasksMenuItem = self.__backMenu.addMenu( self.__projectTasksMenu) self.__backMenu.addSeparator() self.backPasteItem = self.__backMenu.addAction(self.tr("&Paste"), self.__pasteTask) self.__backMenu.addSeparator() self.__backMenu.addAction(self.tr("Delete Completed &Tasks"), self.__deleteCompleted) self.__backMenu.addSeparator() self.__backMenuFilteredAct = self.__backMenu.addAction( self.tr("&Filtered display")) self.__backMenuFilteredAct.setCheckable(True) self.__backMenuFilteredAct.setChecked(False) self.__backMenuFilteredAct.triggered[bool].connect( self.__activateFilter) self.__backMenu.addAction(self.tr("Filter c&onfiguration..."), self.__configureFilter) self.__backMenu.addSeparator() self.__backMenu.addAction(self.tr("Resi&ze columns"), self.__resizeColumns) self.__backMenu.addSeparator() self.__backMenu.addAction(self.tr("Configure..."), self.__configure) self.__activating = False self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.__showContextMenu) self.itemActivated.connect(self.__taskItemActivated) self.setWindowIcon(UI.PixmapCache.getIcon("eric.png")) def __resort(self): """ Private method to resort the tree. """ self.sortItems(self.sortColumn(), self.header().sortIndicatorOrder()) def __resizeColumns(self): """ Private method to resize the list columns. """ self.header().resizeSections(QHeaderView.ResizeToContents) self.header().setStretchLastSection(True) def __refreshDisplay(self): """ Private method to refresh the display. """ for task in self.tasks: task.setHidden(not self.taskFilter.showTask(task)) self.__resort() self.__resizeColumns() def __taskItemActivated(self, itm, col): """ Private slot to handle the activation of an item. @param itm reference to the activated item (QTreeWidgetItem) @param col column the item was activated in (integer) """ if not self.__activating: self.__activating = True fn = itm.getFilename() if fn: self.displayFile.emit(fn, itm.getLineno()) else: self.__editTaskProperties() self.__activating = False def __showContextMenu(self, coord): """ Private slot to show the context menu of the list. @param coord the position of the mouse pointer (QPoint) """ itm = self.itemAt(coord) coord = self.mapToGlobal(coord) if itm is None: self.backProjectTasksMenuItem.setEnabled(self.projectOpen) if self.copyTask: self.backPasteItem.setEnabled(True) else: self.backPasteItem.setEnabled(False) self.__backMenu.popup(coord) else: self.projectTasksMenuItem.setEnabled(self.projectOpen) if itm.getFilename(): self.gotoItem.setEnabled(True) self.deleteItem.setEnabled(True) self.markCompletedItem.setEnabled(False) self.copyItem.setEnabled(False) else: self.gotoItem.setEnabled(False) self.deleteItem.setEnabled(True) self.markCompletedItem.setEnabled(True) self.copyItem.setEnabled(True) if self.copyTask: self.pasteItem.setEnabled(True) else: self.pasteItem.setEnabled(False) self.__menu.popup(coord) def setProjectOpen(self, o=False): """ Public slot to set the project status. @param o flag indicating the project status """ self.projectOpen = o def addTask(self, summary, priority=1, filename="", lineno=0, completed=False, _time=0, isProjectTask=False, taskType=Task.TypeTodo, description=""): """ Public slot to add a task. @param summary summary text of the task (string) @param priority priority of the task (0=high, 1=normal, 2=low) @param filename filename containing the task (string) @param lineno line number containing the task (integer) @param completed flag indicating completion status (boolean) @param _time creation time of the task (float, if 0 use current time) @param isProjectTask flag indicating a task related to the current project (boolean) @param taskType type of the task (one of Task.TypeFixme, Task.TypeTodo, Task.TypeWarning, Task.TypeNote) @param description explanatory text of the task (string) """ task = Task(summary, priority, filename, lineno, completed, _time, isProjectTask, taskType, self.project, description) self.tasks.append(task) self.addTopLevelItem(task) task.setHidden(not self.taskFilter.showTask(task)) self.__resort() self.__resizeColumns() if isProjectTask: self.__projectTasksSaveTimer.changeOccurred() def addFileTask(self, summary, filename, lineno, taskType=Task.TypeTodo, description=""): """ Public slot to add a file related task. @param summary summary text of the task (string) @param filename filename containing the task (string) @param lineno line number containing the task (integer) @param taskType type of the task (one of Task.TypeFixme, Task.TypeTodo, Task.TypeWarning, Task.TypeNote) @param description explanatory text of the task (string) """ self.addTask( summary, filename=filename, lineno=lineno, isProjectTask=(self.project and self.project.isProjectSource(filename)), taskType=taskType, description=description) def getProjectTasks(self): """ Public method to retrieve all project related tasks. @return copy of tasks (list of Task) """ tasks = [task for task in self.tasks if task.isProjectTask()] return tasks[:] def getGlobalTasks(self): """ Public method to retrieve all non project related tasks. @return copy of tasks (list of Task) """ tasks = [task for task in self.tasks if not task.isProjectTask()] return tasks[:] def clearTasks(self): """ Public slot to clear all tasks from display. """ self.tasks = [] self.clear() def clearProjectTasks(self, fileOnly=False): """ Public slot to clear project related tasks. @keyparam fileOnly flag indicating to clear only file related project tasks (boolean) """ for task in self.tasks[:]: if (fileOnly and task.isProjectFileTask()) or \ (not fileOnly and task.isProjectTask()): if self.copyTask == task: self.copyTask = None index = self.indexOfTopLevelItem(task) self.takeTopLevelItem(index) self.tasks.remove(task) del task def clearFileTasks(self, filename, conditionally=False): """ Public slot to clear all tasks related to a file. @param filename name of the file (string) @param conditionally flag indicating to clear the tasks of the file checking some conditions (boolean) """ if conditionally: if self.project and self.project.isProjectSource(filename): # project related tasks will not be cleared return if not Preferences.getTasks("ClearOnFileClose"): return for task in self.tasks[:]: if task.getFilename() == filename: if self.copyTask == task: self.copyTask = None index = self.indexOfTopLevelItem(task) self.takeTopLevelItem(index) self.tasks.remove(task) if task.isProjectTask: self.__projectTasksSaveTimer.changeOccurred() del task def __editTaskProperties(self): """ Private slot to handle the "Properties" context menu entry. """ from .TaskPropertiesDialog import TaskPropertiesDialog task = self.currentItem() dlg = TaskPropertiesDialog(task, self, self.projectOpen) ro = task.getFilename() != "" if ro: dlg.setReadOnly() if dlg.exec_() == QDialog.Accepted and not ro: data = dlg.getData() task.setSummary(data[0]) task.setPriority(data[1]) task.setCompleted(data[2]) task.setProjectTask(data[3]) task.setDescription(data[4]) self.__projectTasksSaveTimer.changeOccurred() def __newTask(self): """ Private slot to handle the "New Task" context menu entry. """ from .TaskPropertiesDialog import TaskPropertiesDialog dlg = TaskPropertiesDialog(None, self, self.projectOpen) if dlg.exec_() == QDialog.Accepted: data = dlg.getData() self.addTask(data[0], data[1], completed=data[2], isProjectTask=data[3], description=data[4]) def __markCompleted(self): """ Private slot to handle the "Mark Completed" context menu entry. """ task = self.currentItem() task.setCompleted(True) def __deleteCompleted(self): """ Private slot to handle the "Delete Completed Tasks" context menu entry. """ for task in self.tasks[:]: if task.isCompleted(): if self.copyTask == task: self.copyTask = None index = self.indexOfTopLevelItem(task) self.takeTopLevelItem(index) self.tasks.remove(task) if task.isProjectTask: self.__projectTasksSaveTimer.changeOccurred() del task ci = self.currentItem() if ci: ind = self.indexFromItem(ci, self.currentColumn()) self.scrollTo(ind, QAbstractItemView.PositionAtCenter) def __copyTask(self): """ Private slot to handle the "Copy" context menu entry. """ task = self.currentItem() self.copyTask = task def __pasteTask(self): """ Private slot to handle the "Paste" context menu entry. """ if self.copyTask: self.addTask(self.copyTask.summary, priority=self.copyTask.priority, completed=self.copyTask.completed, description=self.copyTask.description, isProjectTask=self.copyTask._isProjectTask) def __deleteTask(self): """ Private slot to handle the "Delete Task" context menu entry. """ task = self.currentItem() if self.copyTask == task: self.copyTask = None index = self.indexOfTopLevelItem(task) self.takeTopLevelItem(index) self.tasks.remove(task) if task.isProjectTask: self.__projectTasksSaveTimer.changeOccurred() del task ci = self.currentItem() if ci: ind = self.indexFromItem(ci, self.currentColumn()) self.scrollTo(ind, QAbstractItemView.PositionAtCenter) def __goToTask(self): """ Private slot to handle the "Go To" context menu entry. """ task = self.currentItem() self.displayFile.emit(task.getFilename(), task.getLineno()) def handlePreferencesChanged(self): """ Public slot to react to changes of the preferences. """ for task in self.tasks: task.colorizeTask() def __activateFilter(self, on): """ Private slot to handle the "Filtered display" context menu entry. @param on flag indicating the filter state (boolean) """ if on and not self.taskFilter.hasActiveFilter(): res = E5MessageBox.yesNo( self, self.tr("Activate task filter"), self.tr("""The task filter doesn't have any active filters.""" """ Do you want to configure the filter settings?"""), yesDefault=True) if not res: on = False else: self.__configureFilter() on = self.taskFilter.hasActiveFilter() self.taskFilter.setActive(on) self.__menuFilteredAct.setChecked(on) self.__backMenuFilteredAct.setChecked(on) self.__refreshDisplay() def __configureFilter(self): """ Private slot to handle the "Configure filter" context menu entry. """ from .TaskFilterConfigDialog import TaskFilterConfigDialog dlg = TaskFilterConfigDialog(self.taskFilter) if dlg.exec_() == QDialog.Accepted: dlg.configureTaskFilter(self.taskFilter) self.__refreshDisplay() def __configureProjectTasksScanOptions(self): """ Private slot to configure scan options for project tasks. """ filter, ok = QInputDialog.getText( self, self.tr("Scan Filter Patterns"), self.tr("Enter filename patterns of files" " to be excluded separated by a comma:"), QLineEdit.Normal, self.projectTasksScanFilter) if ok: self.projectTasksScanFilter = filter def __regenerateProjectTasks(self): """ Private slot to handle the "Regenerated project tasks" context menu entry. """ markers = { Task.TypeWarning: Preferences.getTasks("TasksWarningMarkers").split(), Task.TypeNote: Preferences.getTasks("TasksNoteMarkers").split(), Task.TypeTodo: Preferences.getTasks("TasksTodoMarkers").split(), Task.TypeFixme: Preferences.getTasks("TasksFixmeMarkers").split(), } files = self.project.pdata["SOURCES"] # apply file filter filterList = [ f.strip() for f in self.projectTasksScanFilter.split(",") if f.strip() ] if filterList: for filter in filterList: files = [f for f in files if not fnmatch.fnmatch(f, filter)] # remove all project tasks self.clearProjectTasks(fileOnly=True) # now process them progress = E5ProgressDialog(self.tr("Extracting project tasks..."), self.tr("Abort"), 0, len(files), self.tr("%v/%m Files")) progress.setMinimumDuration(0) progress.setWindowTitle(self.tr("Tasks")) count = 0 for file in files: progress.setLabelText( self.tr("Extracting project tasks...\n{0}").format(file)) progress.setValue(count) QApplication.processEvents() if progress.wasCanceled(): break fn = os.path.join(self.project.ppath, file) # read the file and split it into textlines try: text, encoding = Utilities.readEncodedFile(fn) lines = text.splitlines() except (UnicodeError, IOError): count += 1 progress.setValue(count) continue # now search tasks and record them lineIndex = 0 for line in lines: lineIndex += 1 shouldBreak = False for taskType, taskMarkers in markers.items(): for taskMarker in taskMarkers: index = line.find(taskMarker) if index > -1: task = line[index:] self.addFileTask(task, fn, lineIndex, taskType) shouldBreak = True break if shouldBreak: break count += 1 progress.setValue(len(files)) def __configure(self): """ Private method to open the configuration dialog. """ e5App().getObject("UserInterface").showPreferences("tasksPage") def saveProjectTasks(self): """ Public method to write the project tasks. """ if self.projectOpen and Preferences.getTasks("TasksProjectAutoSave"): self.project.writeTasks()
class SpeedDial(QObject): """ Class implementing the speed dial. @signal pagesChanged() emitted after the list of pages changed @signal speedDialSaved() emitted after the speed dial data was saved """ pagesChanged = pyqtSignal() speedDialSaved = pyqtSignal() def __init__(self, parent=None): """ Constructor @param parent reference to the parent object (QObject) """ super(SpeedDial, self).__init__(parent) self.__regenerateScript = True self.__webPages = [] self.__webFrames = [] self.__initialScript = "" self.__thumbnailsDirectory = "" self.__thumbnailers = [] self.__initialize() self.pagesChanged.connect(self.__pagesChanged) self.__saveTimer = AutoSaver(self, self.save) self.pagesChanged.connect(self.__saveTimer.changeOccurred) def addWebFrame(self, frame): """ Public method to add a web frame. @param frame reference to the frame to be added (QWebFrame) """ if frame not in self.__webFrames: self.__webFrames.append(frame) def addPage(self, url, title): """ Public method to add a page for the given data. @param url URL of the page (QUrl) @param title title of the page (string) """ if url.isEmpty(): return from .Page import Page page = Page(url.toString(), title) self.__webPages.append(page) self.pagesChanged.emit() def removePage(self, url): """ Public method to remove a page. @param url URL of the page (QUrl) """ page = self.pageForUrl(url) if not page.url: return self.removeImageForUrl(page.url) self.__webPages.remove(page) self.pagesChanged.emit() def __imageFileName(self, url): """ Private method to generate the image file name for a URL. @param url URL to generate the file name from (string) @return name of the image file (string) """ return os.path.join( self.__thumbnailsDirectory, str(QCryptographicHash.hash(QByteArray(url.encode("utf-8")), QCryptographicHash.Md5).toHex(), encoding="utf-8") + ".png") def initialScript(self): """ Public method to get the 'initial' JavaScript script. @return initial JavaScript script (string) """ if self.__regenerateScript: self.__regenerateScript = False self.__initialScript = "" for page in self.__webPages: if page.broken: imgSource = "qrc:icons/brokenPage.png" else: imgSource = self.__imageFileName(page.url) if not os.path.exists(imgSource): self.loadThumbnail(page.url) imgSource = "qrc:icons/loading.gif" if not page.url: imgSource = "" else: imgSource = QUrl.fromLocalFile(imgSource).toString() self.__initialScript += \ "addBox('{0}', '{1}', '{2}');\n".format( page.url, Utilities.html_uencode(page.title), imgSource) return self.__initialScript def getFileName(self): """ Public method to get the file name of the user agents file. @return name of the user agents file (string) """ return os.path.join( Utilities.getConfigDir(), "browser", "speedDial.xml") def __initialize(self): """ Private method to initialize the speed dial. """ self.__thumbnailsDirectory = os.path.join( Utilities.getConfigDir(), "browser", "thumbnails") # Create directory if it does not exist yet if not os.path.exists(self.__thumbnailsDirectory): os.makedirs(self.__thumbnailsDirectory) self.__load() def reload(self): """ Public method to reload the speed dial data. """ self.__load() def __load(self): """ Private method to load the speed dial configuration. """ allPages, pagesPerRow, speedDialSize = [], 0, 0 speedDialFile = self.getFileName() if os.path.exists(speedDialFile): from .SpeedDialReader import SpeedDialReader reader = SpeedDialReader() allPages, pagesPerRow, speedDialSize = reader.read(speedDialFile) self.__pagesPerRow = pagesPerRow if pagesPerRow else 4 self.__speedDialSize = speedDialSize if speedDialSize else 231 if allPages: self.__webPages = allPages self.pagesChanged.emit() else: allPages = \ 'url:"http://eric-ide.python-projects.org/"|'\ 'title:"Eric Web Site";'\ 'url:"http://www.riverbankcomputing.com/"|'\ 'title:"PyQt Web Site";'\ 'url:"http://www.qt.io/"|title:"Qt Web Site";'\ 'url:"http://blog.qt.digia.com/"|title:"Qt Blog";'\ 'url:"http://www.python.org"|title:"Python Language Website";'\ 'url:"http://www.google.com"|title:"Google";' self.changed(allPages) def save(self): """ Public method to save the speed dial configuration. """ from .SpeedDialWriter import SpeedDialWriter speedDialFile = self.getFileName() writer = SpeedDialWriter() if not writer.write(speedDialFile, self.__webPages, self.__pagesPerRow, self.__speedDialSize): E5MessageBox.critical( None, self.tr("Saving Speed Dial data"), self.tr( """<p>Speed Dial data could not be saved to""" """ <b>{0}</b></p>""").format(speedDialFile)) else: self.speedDialSaved.emit() def close(self): """ Public method to close the user agents manager. """ self.__saveTimer.saveIfNeccessary() def pageForUrl(self, url): """ Public method to get the page for the given URL. @param url URL to be searched for (QUrl) @return page for the URL (Page) """ urlString = url.toString() for page in self.__webPages: if page.url == urlString: return page from .Page import Page return Page() def urlForShortcut(self, key): """ Public method to get the URL for the given shortcut key. @param key shortcut key (integer) @return URL for the key (QUrl) """ if key < 0 or len(self.__webPages) <= key: return QUrl() return QUrl.fromEncoded(self.__webPages[key].url.encode("utf-8")) @pyqtSlot(str) def changed(self, allPages): """ Public slot to react on changed pages. @param allPages string giving all pages (string) """ if not allPages: return entries = allPages.split('";') self.__webPages = [] from .Page import Page for entry in entries: if not entry: continue tmp = entry.split('"|') if len(tmp) == 2: broken = False elif len(tmp) == 3: broken = "brokenPage" in tmp[2][5:] else: continue page = Page(tmp[0][5:], tmp[1][7:], broken) self.__webPages.append(page) self.pagesChanged.emit() @pyqtSlot(str) @pyqtSlot(str, bool) def loadThumbnail(self, url, loadTitle=False): """ Public slot to load a thumbnail of the given URL. @param url URL of the thumbnail (string) @param loadTitle flag indicating to get the title for the thumbnail from the site (boolean) """ if not url: return from .PageThumbnailer import PageThumbnailer thumbnailer = PageThumbnailer(self) thumbnailer.setUrl(QUrl.fromEncoded(url.encode("utf-8"))) thumbnailer.setLoadTitle(loadTitle) thumbnailer.thumbnailCreated.connect(self.__thumbnailCreated) self.__thumbnailers.append(thumbnailer) thumbnailer.start() @pyqtSlot(str) def removeImageForUrl(self, url): """ Public slot to remove the image for a URL. @param url URL to remove the image for (string) """ fileName = self.__imageFileName(url) if os.path.exists(fileName): os.remove(fileName) @pyqtSlot(str, result=str) def urlFromUserInput(self, url): """ Public slot to get the URL from user input. @param url URL entered by the user (string) @return sanitized URL (string) """ return QUrl.fromUserInput(url).toString() @pyqtSlot(str, result=str) def unescapeTitle(self, title): """ Public slot to unescape the titel string. @param title escaped title (string) @return un-escaped title (string) """ return Utilities.html_udecode(title) @pyqtSlot(int) def setPagesInRow(self, count): """ Public slot to set the number of pages per row. @param count number of pages per row (integer) """ self.__pagesPerRow = count self.__saveTimer.changeOccurred() def pagesInRow(self): """ Public method to get the number of dials per row. @return number of dials per row (integer) """ return self.__pagesPerRow @pyqtSlot(int) def setSdSize(self, size): """ Public slot to set the size of the speed dial. @param size size of the speed dial (integer) """ self.__speedDialSize = size self.__saveTimer.changeOccurred() def sdSize(self): """ Public method to get the speed dial size. @return speed dial size (integer) """ return self.__speedDialSize def __thumbnailCreated(self, image): """ Private slot to handle the creation of a thumbnail image. @param image thumbnail image (QPixmap) """ from .PageThumbnailer import PageThumbnailer thumbnailer = self.sender() if not isinstance(thumbnailer, PageThumbnailer) or \ thumbnailer not in self.__thumbnailers: return loadTitle = thumbnailer.loadTitle() title = thumbnailer.title() url = thumbnailer.url().toString() fileName = self.__imageFileName(url) if image.isNull(): fileName = "qrc:icons/brokenPage.png" title = self.tr("Unable to load") loadTitle = True page = self.pageForUrl(thumbnailer.url()) page.broken = True else: if not image.save(fileName): qWarning( "SpeedDial.__thumbnailCreated: Cannot save thumbnail" " to {0}".format(fileName)) fileName = QUrl.fromLocalFile(fileName).toString() self.__regenerateScript = True for frame in self.__cleanFrames(): frame.evaluateJavaScript("setImageToUrl('{0}', '{1}');".format( url, fileName)) if loadTitle: frame.evaluateJavaScript("setTitleToUrl('{0}', '{1}');".format( url, Utilities.html_uencode(title))) thumbnailer.deleteLater() self.__thumbnailers.remove(thumbnailer) def __cleanFrames(self): """ Private method to clean all frames. @return list of speed dial frames (list of QWebFrame) """ frames = [] for frame in self.__webFrames[:]: if frame.url().toString() == "eric:speeddial": frames.append(frame) else: self.__webFrames.remove(frame) return frames def __pagesChanged(self): """ Private slot to react on a change of the pages configuration. """ # update all speed dial pages self.__regenerateScript = True for frame in self.__cleanFrames(): frame.page().triggerAction(QWebPage.Reload)
class HistoryManager(QWebHistoryInterface): """ Class implementing the history manager. @signal historyCleared() emitted after the history has been cleared @signal historyReset() emitted after the history has been reset @signal entryAdded(HistoryEntry) emitted after a history entry has been added @signal entryRemoved(HistoryEntry) emitted after a history entry has been removed @signal entryUpdated(int) emitted after a history entry has been updated @signal historySaved() emitted after the history was saved """ historyCleared = pyqtSignal() historyReset = pyqtSignal() entryAdded = pyqtSignal(HistoryEntry) entryRemoved = pyqtSignal(HistoryEntry) entryUpdated = pyqtSignal(int) historySaved = pyqtSignal() def __init__(self, parent=None): """ Constructor @param parent reference to the parent object (QObject) """ super(HistoryManager, self).__init__(parent) self.__saveTimer = AutoSaver(self, self.save) self.__daysToExpire = Preferences.getHelp("HistoryLimit") self.__history = [] self.__lastSavedUrl = "" self.__expiredTimer = QTimer(self) self.__expiredTimer.setSingleShot(True) self.__expiredTimer.timeout.connect(self.__checkForExpired) self.__frequencyTimer = QTimer(self) self.__frequencyTimer.setSingleShot(True) self.__frequencyTimer.timeout.connect(self.__refreshFrequencies) self.entryAdded.connect(self.__saveTimer.changeOccurred) self.entryRemoved.connect(self.__saveTimer.changeOccurred) self.__load() from .HistoryModel import HistoryModel from .HistoryFilterModel import HistoryFilterModel from .HistoryTreeModel import HistoryTreeModel self.__historyModel = HistoryModel(self, self) self.__historyFilterModel = HistoryFilterModel(self.__historyModel, self) self.__historyTreeModel = HistoryTreeModel(self.__historyFilterModel, self) super(HistoryManager, self).setDefaultInterface(self) self.__startFrequencyTimer() def close(self): """ Public method to close the history manager. """ # remove history items on application exit if self.__daysToExpire == -2: self.clear() self.__saveTimer.saveIfNeccessary() def history(self): """ Public method to return the history. @return reference to the list of history entries (list of HistoryEntry) """ return self.__history[:] def setHistory(self, history, loadedAndSorted=False): """ Public method to set a new history. @param history reference to the list of history entries to be set (list of HistoryEntry) @param loadedAndSorted flag indicating that the list is sorted (boolean) """ self.__history = history[:] if not loadedAndSorted: self.__history.sort() self.__checkForExpired() if loadedAndSorted: try: self.__lastSavedUrl = self.__history[0].url except IndexError: self.__lastSavedUrl = "" else: self.__lastSavedUrl = "" self.__saveTimer.changeOccurred() self.historyReset.emit() def historyContains(self, url): """ Public method to check the history for an entry. @param url URL to check for (string) @return flag indicating success (boolean) """ return self.__historyFilterModel.historyContains(url) def _addHistoryEntry(self, itm): """ Protected method to add a history item. @param itm reference to the history item to add (HistoryEntry) """ globalSettings = QWebSettings.globalSettings() if globalSettings.testAttribute(QWebSettings.PrivateBrowsingEnabled): return self.__history.insert(0, itm) self.entryAdded.emit(itm) if len(self.__history) == 1: self.__checkForExpired() def _removeHistoryEntry(self, itm): """ Protected method to remove a history item. @param itm reference to the history item to remove (HistoryEntry) """ self.__lastSavedUrl = "" self.__history.remove(itm) self.entryRemoved.emit(itm) def addHistoryEntry(self, url): """ Public method to add a history entry. @param url URL to be added (string) """ cleanurl = QUrl(url) if cleanurl.scheme() not in ["eric", "about"]: if cleanurl.password(): # don't save the password in the history cleanurl.setPassword("") if cleanurl.host(): cleanurl.setHost(cleanurl.host().lower()) itm = HistoryEntry(cleanurl.toString(), QDateTime.currentDateTime()) self._addHistoryEntry(itm) def updateHistoryEntry(self, url, title): """ Public method to update a history entry. @param url URL of the entry to update (string) @param title title of the entry to update (string) """ cleanurl = QUrl(url) if cleanurl.scheme() not in ["eric", "about"]: for index in range(len(self.__history)): if url == self.__history[index].url: self.__history[index].title = title self.__saveTimer.changeOccurred() if not self.__lastSavedUrl: self.__lastSavedUrl = self.__history[index].url self.entryUpdated.emit(index) break def removeHistoryEntry(self, url, title=""): """ Public method to remove a history entry. @param url URL of the entry to remove (QUrl) @param title title of the entry to remove (string) """ for index in range(len(self.__history)): if url == QUrl(self.__history[index].url) and (not title or title == self.__history[index].title): self._removeHistoryEntry(self.__history[index]) break def historyModel(self): """ Public method to get a reference to the history model. @return reference to the history model (HistoryModel) """ return self.__historyModel def historyFilterModel(self): """ Public method to get a reference to the history filter model. @return reference to the history filter model (HistoryFilterModel) """ return self.__historyFilterModel def historyTreeModel(self): """ Public method to get a reference to the history tree model. @return reference to the history tree model (HistoryTreeModel) """ return self.__historyTreeModel def __checkForExpired(self): """ Private slot to check entries for expiration. """ if self.__daysToExpire < 0 or len(self.__history) == 0: return now = QDateTime.currentDateTime() nextTimeout = 0 while self.__history: checkForExpired = QDateTime(self.__history[-1].dateTime) checkForExpired.setDate(checkForExpired.date().addDays(self.__daysToExpire)) if now.daysTo(checkForExpired) > 7: nextTimeout = 7 * 86400 else: nextTimeout = now.secsTo(checkForExpired) if nextTimeout > 0: break itm = self.__history.pop(-1) self.__lastSavedUrl = "" self.entryRemoved.emit(itm) self.__saveTimer.saveIfNeccessary() if nextTimeout > 0: self.__expiredTimer.start(nextTimeout * 1000) def daysToExpire(self): """ Public method to get the days for entry expiration. @return days for entry expiration (integer) """ return self.__daysToExpire def setDaysToExpire(self, limit): """ Public method to set the days for entry expiration. @param limit days for entry expiration (integer) """ if self.__daysToExpire == limit: return self.__daysToExpire = limit self.__checkForExpired() self.__saveTimer.changeOccurred() def preferencesChanged(self): """ Public method to indicate a change of preferences. """ self.setDaysToExpire(Preferences.getHelp("HistoryLimit")) @pyqtSlot() def clear(self, period=0): """ Public slot to clear the complete history. @param period history period in milliseconds to be cleared (integer) """ if period == 0: self.__history = [] self.historyReset.emit() else: breakMS = QDateTime.currentMSecsSinceEpoch() - period while self.__history and (QDateTime(self.__history[0].dateTime).toMSecsSinceEpoch() > breakMS): itm = self.__history.pop(0) self.entryRemoved.emit(itm) self.__lastSavedUrl = "" self.__saveTimer.changeOccurred() self.__saveTimer.saveIfNeccessary() self.historyCleared.emit() def getFileName(self): """ Public method to get the file name of the history file. @return name of the history file (string) """ return os.path.join(Utilities.getConfigDir(), "browser", "history") def reload(self): """ Public method to reload the history. """ self.__load() def __load(self): """ Private method to load the saved history entries from disk. """ historyFile = QFile(self.getFileName()) if not historyFile.exists(): return if not historyFile.open(QIODevice.ReadOnly): E5MessageBox.warning( None, self.tr("Loading History"), self.tr("""<p>Unable to open history file <b>{0}</b>.<br/>""" """Reason: {1}</p>""").format( historyFile.fileName, historyFile.errorString() ), ) return history = [] # double check, that the history file is sorted as it is read needToSort = False lastInsertedItem = HistoryEntry() data = QByteArray(historyFile.readAll()) stream = QDataStream(data, QIODevice.ReadOnly) stream.setVersion(QDataStream.Qt_4_6) while not stream.atEnd(): ver = stream.readUInt32() if ver != HISTORY_VERSION: continue itm = HistoryEntry() itm.url = Utilities.readStringFromStream(stream) stream >> itm.dateTime itm.title = Utilities.readStringFromStream(stream) if not itm.dateTime.isValid(): continue if itm == lastInsertedItem: if not lastInsertedItem.title and len(history) > 0: history[0].title = itm.title continue if not needToSort and history and lastInsertedItem < itm: needToSort = True history.insert(0, itm) lastInsertedItem = itm historyFile.close() if needToSort: history.sort() self.setHistory(history, True) # if the history had to be sorted, rewrite the history sorted if needToSort: self.__lastSavedUrl = "" self.__saveTimer.changeOccurred() def save(self): """ Public slot to save the history entries to disk. """ historyFile = QFile(self.getFileName()) if not historyFile.exists(): self.__lastSavedUrl = "" saveAll = self.__lastSavedUrl == "" first = len(self.__history) - 1 if not saveAll: # find the first one to save for index in range(len(self.__history)): if self.__history[index].url == self.__lastSavedUrl: first = index - 1 break if first == len(self.__history) - 1: saveAll = True if saveAll: # use a temporary file when saving everything f = QTemporaryFile() f.setAutoRemove(False) opened = f.open() else: f = historyFile opened = f.open(QIODevice.Append) if not opened: E5MessageBox.warning( None, self.tr("Saving History"), self.tr("""<p>Unable to open history file <b>{0}</b>.<br/>""" """Reason: {1}</p>""").format( f.fileName(), f.errorString() ), ) return for index in range(first, -1, -1): data = QByteArray() stream = QDataStream(data, QIODevice.WriteOnly) stream.setVersion(QDataStream.Qt_4_6) itm = self.__history[index] stream.writeUInt32(HISTORY_VERSION) stream.writeString(itm.url.encode("utf-8")) stream << itm.dateTime stream.writeString(itm.title.encode("utf-8")) f.write(data) f.close() if saveAll: if historyFile.exists() and not historyFile.remove(): E5MessageBox.warning( None, self.tr("Saving History"), self.tr("""<p>Error removing old history file <b>{0}</b>.""" """<br/>Reason: {1}</p>""").format( historyFile.fileName(), historyFile.errorString() ), ) if not f.copy(historyFile.fileName()): E5MessageBox.warning( None, self.tr("Saving History"), self.tr( """<p>Error moving new history file over old one """ """(<b>{0}</b>).<br/>Reason: {1}</p>""" ).format(historyFile.fileName(), f.errorString()), ) self.historySaved.emit() try: self.__lastSavedUrl = self.__history[0].url except IndexError: self.__lastSavedUrl = "" def __refreshFrequencies(self): """ Private slot to recalculate the refresh frequencies. """ self.__historyFilterModel.recalculateFrequencies() self.__startFrequencyTimer() def __startFrequencyTimer(self): """ Private method to start the timer to recalculate the frequencies. """ tomorrow = QDateTime(QDate.currentDate().addDays(1), QTime(3, 0)) self.__frequencyTimer.start(QDateTime.currentDateTime().secsTo(tomorrow) * 1000)
class DownloadManager(QDialog, Ui_DownloadManager): """ Class implementing the download manager. """ RemoveNever = 0 RemoveExit = 1 RemoveSuccessFullDownload = 2 def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget (QWidget) """ super(DownloadManager, self).__init__(parent) self.setupUi(self) self.setWindowFlags(Qt.Window) self.__saveTimer = AutoSaver(self, self.save) self.__model = DownloadModel(self) self.__manager = Helpviewer.HelpWindow.HelpWindow\ .networkAccessManager() self.__iconProvider = None self.__downloads = [] self.__downloadDirectory = "" self.__loaded = False self.setDownloadDirectory(Preferences.getUI("DownloadPath")) self.downloadsView.setShowGrid(False) self.downloadsView.verticalHeader().hide() self.downloadsView.horizontalHeader().hide() self.downloadsView.setAlternatingRowColors(True) self.downloadsView.horizontalHeader().setStretchLastSection(True) self.downloadsView.setModel(self.__model) self.downloadsView.setContextMenuPolicy(Qt.CustomContextMenu) self.downloadsView.customContextMenuRequested.connect( self.__customContextMenuRequested) self.__load() def __customContextMenuRequested(self, pos): """ Private slot to handle the context menu request for the bookmarks tree. @param pos position the context menu was requested (QPoint) """ menu = QMenu() selectedRowsCount = len( self.downloadsView.selectionModel().selectedRows()) if selectedRowsCount == 1: row = self.downloadsView.selectionModel().selectedRows()[0].row() itm = self.__downloads[row] if itm.downloadCanceled(): menu.addAction(UI.PixmapCache.getIcon("restart.png"), self.tr("Retry"), self.__contextMenuRetry) else: if itm.downloadedSuccessfully(): menu.addAction(UI.PixmapCache.getIcon("open.png"), self.tr("Open"), self.__contextMenuOpen) elif itm.downloading(): menu.addAction(UI.PixmapCache.getIcon("stopLoading.png"), self.tr("Cancel"), self.__contextMenuCancel) menu.addSeparator() menu.addAction(self.tr("Open Containing Folder"), self.__contextMenuOpenFolder) menu.addSeparator() menu.addAction(self.tr("Go to Download Page"), self.__contextMenuGotoPage) menu.addAction(self.tr("Copy Download Link"), self.__contextMenuCopyLink) menu.addSeparator() menu.addAction(self.tr("Select All"), self.__contextMenuSelectAll) if selectedRowsCount > 1 or \ (selectedRowsCount == 1 and not self.__downloads[ self.downloadsView.selectionModel().selectedRows()[0].row()] .downloading()): menu.addSeparator() menu.addAction(self.tr("Remove From List"), self.__contextMenuRemoveSelected) menu.exec_(QCursor.pos()) def shutdown(self): """ Public method to stop the download manager. """ self.__saveTimer.changeOccurred() self.__saveTimer.saveIfNeccessary() self.close() def activeDownloads(self): """ Public method to get the number of active downloads. @return number of active downloads (integer) """ count = 0 for download in self.__downloads: if download.downloading(): count += 1 return count def allowQuit(self): """ Public method to check, if it is ok to quit. @return flag indicating allowance to quit (boolean) """ if self.activeDownloads() > 0: res = E5MessageBox.yesNo( self, self.tr(""), self.tr( """There are %n downloads in progress.\n""" """Do you want to quit anyway?""", "", self.activeDownloads()), icon=E5MessageBox.Warning) if not res: self.show() return False return True def download(self, requestOrUrl, requestFileName=False, mainWindow=None): """ Public method to download a file. @param requestOrUrl reference to a request object (QNetworkRequest) or a URL to be downloaded (QUrl) @keyparam requestFileName flag indicating to ask for the download file name (boolean) @keyparam mainWindow reference to the main window (HelpWindow) """ request = QNetworkRequest(requestOrUrl) if request.url().isEmpty(): return self.handleUnsupportedContent(self.__manager.get(request), requestFileName=requestFileName, download=True, mainWindow=mainWindow) def handleUnsupportedContent(self, reply, requestFileName=False, webPage=None, download=False, mainWindow=None): """ Public method to handle unsupported content by downloading the referenced resource. @param reply reference to the reply object (QNetworkReply) @keyparam requestFileName indicating to ask for a filename (boolean) @keyparam webPage reference to the web page (HelpWebPage) @keyparam download flag indicating a download request (boolean) @keyparam mainWindow reference to the main window (HelpWindow) """ if reply is None or reply.url().isEmpty(): return size = reply.header(QNetworkRequest.ContentLengthHeader) if size == 0: return from .DownloadItem import DownloadItem itm = DownloadItem(reply=reply, requestFilename=requestFileName, webPage=webPage, download=download, parent=self, mainWindow=mainWindow) self.__addItem(itm) if itm.canceledFileSelect(): return if not self.isVisible(): self.show() self.activateWindow() self.raise_() def __addItem(self, itm): """ Private method to add a download to the list of downloads. @param itm reference to the download item (DownloadItem) """ itm.statusChanged.connect(self.__updateRow) itm.downloadFinished.connect(self.__finished) row = len(self.__downloads) self.__model.beginInsertRows(QModelIndex(), row, row) self.__downloads.append(itm) self.__model.endInsertRows() self.downloadsView.setIndexWidget(self.__model.index(row, 0), itm) icon = self.style().standardIcon(QStyle.SP_FileIcon) itm.setIcon(icon) self.downloadsView.setRowHeight(row, itm.sizeHint().height() * 1.5) # just in case the download finished before the constructor returned self.__updateRow(itm) self.changeOccurred() self.__updateActiveItemCount() def __updateRow(self, itm=None): """ Private slot to update a download item. @param itm reference to the download item (DownloadItem) """ if itm is None: itm = self.sender() if itm not in self.__downloads: return row = self.__downloads.index(itm) if self.__iconProvider is None: self.__iconProvider = QFileIconProvider() icon = self.__iconProvider.icon(QFileInfo(itm.fileName())) if icon.isNull(): icon = self.style().standardIcon(QStyle.SP_FileIcon) itm.setIcon(icon) oldHeight = self.downloadsView.rowHeight(row) self.downloadsView.setRowHeight( row, max(oldHeight, itm.minimumSizeHint().height() * 1.5)) remove = False globalSettings = QWebSettings.globalSettings() if not itm.downloading() and \ globalSettings.testAttribute(QWebSettings.PrivateBrowsingEnabled): remove = True if itm.downloadedSuccessfully() and \ self.removePolicy() == DownloadManager.RemoveSuccessFullDownload: remove = True if remove: self.__model.removeRow(row) self.cleanupButton.setEnabled( (len(self.__downloads) - self.activeDownloads()) > 0) # record the change self.changeOccurred() def removePolicy(self): """ Public method to get the remove policy. @return remove policy (integer) """ return Preferences.getHelp("DownloadManagerRemovePolicy") def setRemovePolicy(self, policy): """ Public method to set the remove policy. @param policy policy to be set (DownloadManager.RemoveExit, DownloadManager.RemoveNever, DownloadManager.RemoveSuccessFullDownload) """ assert policy in (DownloadManager.RemoveExit, DownloadManager.RemoveNever, DownloadManager.RemoveSuccessFullDownload) if policy == self.removePolicy(): return Preferences.setHelp("DownloadManagerRemovePolicy", self.policy) def save(self): """ Public method to save the download settings. """ if not self.__loaded: return Preferences.setHelp("DownloadManagerSize", self.size()) Preferences.setHelp("DownloadManagerPosition", self.pos()) if self.removePolicy() == DownloadManager.RemoveExit: return downloads = [] for download in self.__downloads: downloads.append(download.getData()) Preferences.setHelp("DownloadManagerDownloads", downloads) def __load(self): """ Private method to load the download settings. """ if self.__loaded: return size = Preferences.getHelp("DownloadManagerSize") if size.isValid(): self.resize(size) pos = Preferences.getHelp("DownloadManagerPosition") self.move(pos) downloads = Preferences.getHelp("DownloadManagerDownloads") for download in downloads: if not download[0].isEmpty() and \ download[1] != "": from .DownloadItem import DownloadItem itm = DownloadItem(parent=self) itm.setData(download) self.__addItem(itm) self.cleanupButton.setEnabled( (len(self.__downloads) - self.activeDownloads()) > 0) self.__loaded = True self.__updateActiveItemCount() def cleanup(self): """ Public slot to cleanup the downloads. """ self.on_cleanupButton_clicked() @pyqtSlot() def on_cleanupButton_clicked(self): """ Private slot cleanup the downloads. """ if len(self.__downloads) == 0: return self.__model.removeRows(0, len(self.__downloads)) if len(self.__downloads) == 0 and \ self.__iconProvider is not None: self.__iconProvider = None self.changeOccurred() self.__updateActiveItemCount() def __updateItemCount(self): """ Private method to update the count label. """ count = len(self.__downloads) self.countLabel.setText(self.tr("%n Download(s)", "", count)) def __updateActiveItemCount(self): """ Private method to update the window title. """ count = self.activeDownloads() if count > 0: self.setWindowTitle(self.tr("Downloading %n file(s)", "", count)) else: self.setWindowTitle(self.tr("Downloads")) def __finished(self): """ Private slot to handle a finished download. """ self.__updateActiveItemCount() if self.isVisible(): QApplication.alert(self) def setDownloadDirectory(self, directory): """ Public method to set the current download directory. @param directory current download directory (string) """ self.__downloadDirectory = directory if self.__downloadDirectory != "": self.__downloadDirectory += "/" def downloadDirectory(self): """ Public method to get the current download directory. @return current download directory (string) """ return self.__downloadDirectory def count(self): """ Public method to get the number of downloads. @return number of downloads (integer) """ return len(self.__downloads) def downloads(self): """ Public method to get a reference to the downloads. @return reference to the downloads (list of DownloadItem) """ return self.__downloads def changeOccurred(self): """ Public method to signal a change. """ self.__saveTimer.changeOccurred() self.__updateItemCount() ########################################################################### ## Context menu related methods below ########################################################################### def __currentItem(self): """ Private method to get a reference to the current item. @return reference to the current item (DownloadItem) """ index = self.downloadsView.currentIndex() if index and index.isValid(): row = index.row() return self.__downloads[row] return None def __contextMenuRetry(self): """ Private method to retry of the download. """ itm = self.__currentItem() if itm is not None: itm.retry() def __contextMenuOpen(self): """ Private method to open the downloaded file. """ itm = self.__currentItem() if itm is not None: itm.openFile() def __contextMenuOpenFolder(self): """ Private method to open the folder containing the downloaded file. """ itm = self.__currentItem() if itm is not None: itm.openFolder() def __contextMenuCancel(self): """ Private method to cancel the current download. """ itm = self.__currentItem() if itm is not None: itm.cancelDownload() def __contextMenuGotoPage(self): """ Private method to open the download page. """ itm = self.__currentItem() if itm is not None: url = itm.getPageUrl() Helpviewer.HelpWindow.HelpWindow.mainWindow().openUrl(url, "") def __contextMenuCopyLink(self): """ Private method to copy the download link to the clipboard. """ itm = self.__currentItem() if itm is not None: url = itm.getPageUrl().toString() QApplication.clipboard().setText(url) def __contextMenuSelectAll(self): """ Private method to select all downloads. """ self.downloadsView.selectAll() def __contextMenuRemoveSelected(self): """ Private method to remove the selected downloads from the list. """ self.downloadsView.removeSelected()
class HistoryManager(QWebHistoryInterface): """ Class implementing the history manager. @signal historyCleared() emitted after the history has been cleared @signal historyReset() emitted after the history has been reset @signal entryAdded emitted after a history entry has been added @signal entryRemoved emitted after a history entry has been removed @signal entryUpdated(int) emitted after a history entry has been updated """ def __init__(self, parent = None): """ Constructor @param parent reference to the parent object (QObject) """ QWebHistoryInterface.__init__(self, parent) self.__saveTimer = AutoSaver(self, self.save) self.__daysToExpire = Preferences.getHelp("HistoryLimit") self.__history = [] self.__lastSavedUrl = QString() self.__expiredTimer = QTimer() self.__expiredTimer.setSingleShot(True) self.connect(self.__expiredTimer, SIGNAL("timeout()"), self.__checkForExpired) self.__frequencyTimer = QTimer() self.__frequencyTimer.setSingleShot(True) self.connect(self.__frequencyTimer, SIGNAL("timeout()"), self.__refreshFrequencies) self.connect(self, SIGNAL("entryAdded"), self.__saveTimer.changeOccurred) self.connect(self, SIGNAL("entryRemoved"), self.__saveTimer.changeOccurred) self.__load() self.__historyModel = HistoryModel(self, self) self.__historyFilterModel = HistoryFilterModel(self.__historyModel, self) self.__historyTreeModel = HistoryTreeModel(self.__historyFilterModel, self) QWebHistoryInterface.setDefaultInterface(self) self.__startFrequencyTimer() def close(self): """ Public method to close the history manager. """ # remove history items on application exit if self.__daysToExpire == -2: self.clear() self.__saveTimer.saveIfNeccessary() def history(self): """ Public method to return the history. @return reference to the list of history entries (list of HistoryEntry) """ return self.__history[:] def setHistory(self, history, loadedAndSorted = False): """ Public method to set a new history. @param history reference to the list of history entries to be set (list of HistoryEntry) @param loadedAndSorted flag indicating that the list is sorted (boolean) """ self.__history = history[:] if not loadedAndSorted: self.__history.sort() self.__checkForExpired() if loadedAndSorted: try: self.__lastSavedUrl = QString(self.__history[0].url) except IndexError: self.__lastSavedUrl = QString() else: self.__lastSavedUrl.clear() self.__saveTimer.changeOccurred() self.emit(SIGNAL("historyReset()")) def historyContains(self, url): """ Public method to check the history for an entry. @param url URL to check for (QString) @return flag indicating success (boolean) """ return self.__historyFilterModel.historyContains(url) def _addHistoryEntry(self, itm): """ Protected method to add a history item. @param itm reference to the history item to add (HistoryEntry) """ globalSettings = QWebSettings.globalSettings() if globalSettings.testAttribute(QWebSettings.PrivateBrowsingEnabled): return self.__history.insert(0, itm) self.emit(SIGNAL("entryAdded"), itm) if len(self.__history) == 1: self.__checkForExpired() def _removeHistoryEntry(self, itm): """ Protected method to remove a history item. @param itm reference to the history item to remove (HistoryEntry) """ self.__lastSavedUrl.clear() self.__history.remove(itm) self.emit(SIGNAL("entryRemoved"), itm) def addHistoryEntry(self, url): """ Public method to add a history entry. @param url URL to be added (QString) """ cleanurl = QUrl(url) cleanurl.setPassword("") cleanurl.setHost(cleanurl.host().toLower()) itm = HistoryEntry(cleanurl.toString(), QDateTime.currentDateTime()) self._addHistoryEntry(itm) def updateHistoryEntry(self, url, title): """ Public method to update a history entry. @param url URL of the entry to update (QString) @param title title of the entry to update (QString) """ for index in range(len(self.__history)): if url == self.__history[index].url: self.__history[index].title = title self.__saveTimer.changeOccurred() if self.__lastSavedUrl.isEmpty(): self.__lastSavedUrl = QString(self.__history[index].url) self.emit(SIGNAL("entryUpdated(int)"), index) break def removeHistoryEntry(self, url, title = QString()): """ Public method to remove a history entry. @param url URL of the entry to remove (QUrl) @param title title of the entry to remove (QString) """ for index in range(len(self.__history)): if url == QUrl(self.__history[index].url) and \ (title.isEmpty() or title == self.__history[index].title): self._removeHistoryEntry(self.__history[index]) break def historyModel(self): """ Public method to get a reference to the history model. @return reference to the history model (HistoryModel) """ return self.__historyModel def historyFilterModel(self): """ Public method to get a reference to the history filter model. @return reference to the history filter model (HistoryFilterModel) """ return self.__historyFilterModel def historyTreeModel(self): """ Public method to get a reference to the history tree model. @return reference to the history tree model (HistoryTreeModel) """ return self.__historyTreeModel def __checkForExpired(self): """ Private slot to check entries for expiration. """ if self.__daysToExpire < 0 or len(self.__history) == 0: return now = QDateTime.currentDateTime() nextTimeout = 0 while self.__history: checkForExpired = QDateTime(self.__history[-1].dateTime) checkForExpired.setDate(checkForExpired.date().addDays(self.__daysToExpire)) if now.daysTo(checkForExpired) > 7: nextTimeout = 7 * 86400 else: nextTimeout = now.secsTo(checkForExpired) if nextTimeout > 0: break itm = self.__history.pop(-1) self.__lastSavedUrl.clear() self.emit(SIGNAL("entryRemoved"), itm) self.__saveTimer.saveIfNeccessary() if nextTimeout > 0: self.__expiredTimer.start(nextTimeout * 1000) def daysToExpire(self): """ Public method to get the days for entry expiration. @return days for entry expiration (integer) """ return self.__daysToExpire def setDaysToExpire(self, limit): """ Public method to set the days for entry expiration. @param limit days for entry expiration (integer) """ if self.__daysToExpire == limit: return self.__daysToExpire = limit self.__checkForExpired() self.__saveTimer.changeOccurred() def preferencesChanged(self): """ Public method to indicate a change of preferences. """ self.setDaysToExpire(Preferences.getHelp("HistoryLimit")) def clear(self): """ Public slot to clear the complete history. """ self.__history = [] self.__lastSavedUrl.clear() self.__saveTimer.changeOccurred() self.__saveTimer.saveIfNeccessary() self.emit(SIGNAL("historyReset()")) self.emit(SIGNAL("historyCleared()")) def __load(self): """ Private method to load the saved history entries from disk. """ historyFile = QFile(Utilities.getConfigDir() + "/browser/history") if not historyFile.exists(): return if not historyFile.open(QIODevice.ReadOnly): KQMessageBox.warning(None, self.trUtf8("Loading History"), self.trUtf8("""<p>Unable to open history file <b>%1</b>.""" """<br/>Reason: %2</p>""")\ .arg(historyFile.fileName).arg(historyFile.errorString())) return history = [] historyStream = QDataStream(historyFile) # double check, that the history file is sorted as it is read needToSort = False lastInsertedItem = HistoryEntry() data = QByteArray() stream = QDataStream() buffer = QBuffer() stream.setDevice(buffer) while not historyFile.atEnd(): historyStream >> data buffer.close() buffer.setBuffer(data) buffer.open(QIODevice.ReadOnly) ver = stream.readUInt32() if ver != HISTORY_VERSION: continue itm = HistoryEntry() stream >> itm.url stream >> itm.dateTime stream >> itm.title if not itm.dateTime.isValid(): continue if itm == lastInsertedItem: if lastInsertedItem.title.isEmpty() and len(history) > 0: history[0].title = itm.title continue if not needToSort and history and lastInsertedItem < itm: needToSort = True history.insert(0, itm) lastInsertedItem = itm historyFile.close() if needToSort: history.sort() self.setHistory(history, True) # if the history had to be sorted, rewrite the history sorted if needToSort: self.__lastSavedUrl.clear() self.__saveTimer.changeOccurred() def save(self): """ Public slot to save the history entries to disk. """ historyFile = QFile(Utilities.getConfigDir() + "/browser/history") if not historyFile.exists(): self.__lastSavedUrl.clear() saveAll = self.__lastSavedUrl.isEmpty() first = len(self.__history) - 1 if not saveAll: # find the first one to save for index in range(len(self.__history)): if self.__history[index].url == self.__lastSavedUrl: first = index - 1 break if first == len(self.__history) - 1: saveAll = True # use a temporary file when saving everything tempFile = QTemporaryFile() tempFile.setAutoRemove(False) if saveAll: opened = tempFile.open() else: opened = historyFile.open(QIODevice.Append) if not opened: if saveAll: f = tempFile else: f = historyFile KQMessageBox.warning(None, self.trUtf8("Saving History"), self.trUtf8("""<p>Unable to open history file <b>%1</b>.""" """<br/>Reason: %2</p>""")\ .arg(f.fileName()).arg(f.errorString())) return if saveAll: historyStream = QDataStream(tempFile) else: historyStream = QDataStream(historyFile) for index in range(first, -1, -1): data = QByteArray() stream = QDataStream(data, QIODevice.WriteOnly) itm = self.__history[index] stream.writeUInt32(HISTORY_VERSION) stream << itm.url stream << itm.dateTime stream << itm.title historyStream << data if saveAll: tempFile.close() if historyFile.exists() and not historyFile.remove(): KQMessageBox.warning(None, self.trUtf8("Saving History"), self.trUtf8("""<p>Error removing old history file <b>%1</b>.""" """<br/>Reason: %2</p>""")\ .arg(historyFile.fileName()).arg(historyFile.errorString())) if not tempFile.copy(historyFile.fileName()): KQMessageBox.warning(None, self.trUtf8("Saving History"), self.trUtf8("""<p>Error moving new history file over old one """ """(<b>%1</b>).<br/>Reason: %2</p>""")\ .arg(historyFile.fileName()).arg(tempFile.errorString())) else: historyFile.close() try: self.__lastSavedUrl = QString(self.__history[0].url) except IndexError: self.__lastSavedUrl = QString() def __refreshFrequencies(self): """ Private slot to recalculate the refresh frequencies. """ self.__historyFilterModel.recalculateFrequencies() self.__startFrequencyTimer() def __startFrequencyTimer(self): """ Private method to start the timer to recalculate the frequencies. """ tomorrow = QDateTime(QDate.currentDate().addDays(1), QTime(3, 0)) self.__frequencyTimer.start(QDateTime.currentDateTime().secsTo(tomorrow) * 1000)
class HistoryManager(QWebHistoryInterface): """ Class implementing the history manager. @signal historyCleared() emitted after the history has been cleared @signal historyReset() emitted after the history has been reset @signal entryAdded(HistoryEntry) emitted after a history entry has been added @signal entryRemoved(HistoryEntry) emitted after a history entry has been removed @signal entryUpdated(int) emitted after a history entry has been updated @signal historySaved() emitted after the history was saved """ historyCleared = pyqtSignal() historyReset = pyqtSignal() entryAdded = pyqtSignal(HistoryEntry) entryRemoved = pyqtSignal(HistoryEntry) entryUpdated = pyqtSignal(int) historySaved = pyqtSignal() def __init__(self, parent=None): """ Constructor @param parent reference to the parent object (QObject) """ super(HistoryManager, self).__init__(parent) self.__saveTimer = AutoSaver(self, self.save) self.__daysToExpire = Preferences.getHelp("HistoryLimit") self.__history = [] self.__lastSavedUrl = "" self.__expiredTimer = QTimer(self) self.__expiredTimer.setSingleShot(True) self.__expiredTimer.timeout.connect(self.__checkForExpired) self.__frequencyTimer = QTimer(self) self.__frequencyTimer.setSingleShot(True) self.__frequencyTimer.timeout.connect(self.__refreshFrequencies) self.entryAdded.connect(self.__saveTimer.changeOccurred) self.entryRemoved.connect(self.__saveTimer.changeOccurred) self.__load() from .HistoryModel import HistoryModel from .HistoryFilterModel import HistoryFilterModel from .HistoryTreeModel import HistoryTreeModel self.__historyModel = HistoryModel(self, self) self.__historyFilterModel = \ HistoryFilterModel(self.__historyModel, self) self.__historyTreeModel = \ HistoryTreeModel(self.__historyFilterModel, self) super(HistoryManager, self).setDefaultInterface(self) self.__startFrequencyTimer() def close(self): """ Public method to close the history manager. """ # remove history items on application exit if self.__daysToExpire == -2: self.clear() self.__saveTimer.saveIfNeccessary() def history(self): """ Public method to return the history. @return reference to the list of history entries (list of HistoryEntry) """ return self.__history[:] def setHistory(self, history, loadedAndSorted=False): """ Public method to set a new history. @param history reference to the list of history entries to be set (list of HistoryEntry) @param loadedAndSorted flag indicating that the list is sorted (boolean) """ self.__history = history[:] if not loadedAndSorted: self.__history.sort() self.__checkForExpired() if loadedAndSorted: try: self.__lastSavedUrl = self.__history[0].url except IndexError: self.__lastSavedUrl = "" else: self.__lastSavedUrl = "" self.__saveTimer.changeOccurred() self.historyReset.emit() def historyContains(self, url): """ Public method to check the history for an entry. @param url URL to check for (string) @return flag indicating success (boolean) """ return self.__historyFilterModel.historyContains(url) def _addHistoryEntry(self, itm): """ Protected method to add a history item. @param itm reference to the history item to add (HistoryEntry) """ globalSettings = QWebSettings.globalSettings() if globalSettings.testAttribute(QWebSettings.PrivateBrowsingEnabled): return self.__history.insert(0, itm) self.entryAdded.emit(itm) if len(self.__history) == 1: self.__checkForExpired() def _removeHistoryEntry(self, itm): """ Protected method to remove a history item. @param itm reference to the history item to remove (HistoryEntry) """ self.__lastSavedUrl = "" self.__history.remove(itm) self.entryRemoved.emit(itm) def addHistoryEntry(self, url): """ Public method to add a history entry. @param url URL to be added (string) """ cleanurl = QUrl(url) if cleanurl.scheme() not in ["eric", "about"]: if cleanurl.password(): # don't save the password in the history cleanurl.setPassword("") if cleanurl.host(): cleanurl.setHost(cleanurl.host().lower()) itm = HistoryEntry(cleanurl.toString(), QDateTime.currentDateTime()) self._addHistoryEntry(itm) def updateHistoryEntry(self, url, title): """ Public method to update a history entry. @param url URL of the entry to update (string) @param title title of the entry to update (string) """ cleanurl = QUrl(url) if cleanurl.scheme() not in ["eric", "about"]: for index in range(len(self.__history)): if url == self.__history[index].url: self.__history[index].title = title self.__saveTimer.changeOccurred() if not self.__lastSavedUrl: self.__lastSavedUrl = self.__history[index].url self.entryUpdated.emit(index) break def removeHistoryEntry(self, url, title=""): """ Public method to remove a history entry. @param url URL of the entry to remove (QUrl) @param title title of the entry to remove (string) """ for index in range(len(self.__history)): if url == QUrl(self.__history[index].url) and \ (not title or title == self.__history[index].title): self._removeHistoryEntry(self.__history[index]) break def historyModel(self): """ Public method to get a reference to the history model. @return reference to the history model (HistoryModel) """ return self.__historyModel def historyFilterModel(self): """ Public method to get a reference to the history filter model. @return reference to the history filter model (HistoryFilterModel) """ return self.__historyFilterModel def historyTreeModel(self): """ Public method to get a reference to the history tree model. @return reference to the history tree model (HistoryTreeModel) """ return self.__historyTreeModel def __checkForExpired(self): """ Private slot to check entries for expiration. """ if self.__daysToExpire < 0 or len(self.__history) == 0: return now = QDateTime.currentDateTime() nextTimeout = 0 while self.__history: checkForExpired = QDateTime(self.__history[-1].dateTime) checkForExpired.setDate(checkForExpired.date().addDays( self.__daysToExpire)) if now.daysTo(checkForExpired) > 7: nextTimeout = 7 * 86400 else: nextTimeout = now.secsTo(checkForExpired) if nextTimeout > 0: break itm = self.__history.pop(-1) self.__lastSavedUrl = "" self.entryRemoved.emit(itm) self.__saveTimer.saveIfNeccessary() if nextTimeout > 0: self.__expiredTimer.start(nextTimeout * 1000) def daysToExpire(self): """ Public method to get the days for entry expiration. @return days for entry expiration (integer) """ return self.__daysToExpire def setDaysToExpire(self, limit): """ Public method to set the days for entry expiration. @param limit days for entry expiration (integer) """ if self.__daysToExpire == limit: return self.__daysToExpire = limit self.__checkForExpired() self.__saveTimer.changeOccurred() def preferencesChanged(self): """ Public method to indicate a change of preferences. """ self.setDaysToExpire(Preferences.getHelp("HistoryLimit")) @pyqtSlot() def clear(self, period=0): """ Public slot to clear the complete history. @param period history period in milliseconds to be cleared (integer) """ if period == 0: self.__history = [] self.historyReset.emit() else: breakMS = QDateTime.currentMSecsSinceEpoch() - period while self.__history and \ (QDateTime(self.__history[0].dateTime).toMSecsSinceEpoch() > breakMS): itm = self.__history.pop(0) self.entryRemoved.emit(itm) self.__lastSavedUrl = "" self.__saveTimer.changeOccurred() self.__saveTimer.saveIfNeccessary() self.historyCleared.emit() def getFileName(self): """ Public method to get the file name of the history file. @return name of the history file (string) """ return os.path.join(Utilities.getConfigDir(), "browser", "history") def reload(self): """ Public method to reload the history. """ self.__load() def __load(self): """ Private method to load the saved history entries from disk. """ historyFile = QFile(self.getFileName()) if not historyFile.exists(): return if not historyFile.open(QIODevice.ReadOnly): E5MessageBox.warning( None, self.tr("Loading History"), self.tr("""<p>Unable to open history file <b>{0}</b>.<br/>""" """Reason: {1}</p>""").format( historyFile.fileName, historyFile.errorString())) return history = [] # double check, that the history file is sorted as it is read needToSort = False lastInsertedItem = HistoryEntry() data = QByteArray(historyFile.readAll()) stream = QDataStream(data, QIODevice.ReadOnly) stream.setVersion(QDataStream.Qt_4_6) while not stream.atEnd(): ver = stream.readUInt32() if ver != HISTORY_VERSION: continue itm = HistoryEntry() itm.url = Utilities.readStringFromStream(stream) stream >> itm.dateTime itm.title = Utilities.readStringFromStream(stream) if not itm.dateTime.isValid(): continue if itm == lastInsertedItem: if not lastInsertedItem.title and len(history) > 0: history[0].title = itm.title continue if not needToSort and history and lastInsertedItem < itm: needToSort = True history.insert(0, itm) lastInsertedItem = itm historyFile.close() if needToSort: history.sort() self.setHistory(history, True) # if the history had to be sorted, rewrite the history sorted if needToSort: self.__lastSavedUrl = "" self.__saveTimer.changeOccurred() def save(self): """ Public slot to save the history entries to disk. """ historyFile = QFile(self.getFileName()) if not historyFile.exists(): self.__lastSavedUrl = "" saveAll = self.__lastSavedUrl == "" first = len(self.__history) - 1 if not saveAll: # find the first one to save for index in range(len(self.__history)): if self.__history[index].url == self.__lastSavedUrl: first = index - 1 break if first == len(self.__history) - 1: saveAll = True if saveAll: # use a temporary file when saving everything f = QTemporaryFile() f.setAutoRemove(False) opened = f.open() else: f = historyFile opened = f.open(QIODevice.Append) if not opened: E5MessageBox.warning( None, self.tr("Saving History"), self.tr("""<p>Unable to open history file <b>{0}</b>.<br/>""" """Reason: {1}</p>""").format(f.fileName(), f.errorString())) return for index in range(first, -1, -1): data = QByteArray() stream = QDataStream(data, QIODevice.WriteOnly) stream.setVersion(QDataStream.Qt_4_6) itm = self.__history[index] stream.writeUInt32(HISTORY_VERSION) stream.writeString(itm.url.encode("utf-8")) stream << itm.dateTime stream.writeString(itm.title.encode('utf-8')) f.write(data) f.close() if saveAll: if historyFile.exists() and not historyFile.remove(): E5MessageBox.warning( None, self.tr("Saving History"), self.tr( """<p>Error removing old history file <b>{0}</b>.""" """<br/>Reason: {1}</p>""").format( historyFile.fileName(), historyFile.errorString())) if not f.copy(historyFile.fileName()): E5MessageBox.warning( None, self.tr("Saving History"), self.tr( """<p>Error moving new history file over old one """ """(<b>{0}</b>).<br/>Reason: {1}</p>""").format( historyFile.fileName(), f.errorString())) self.historySaved.emit() try: self.__lastSavedUrl = self.__history[0].url except IndexError: self.__lastSavedUrl = "" def __refreshFrequencies(self): """ Private slot to recalculate the refresh frequencies. """ self.__historyFilterModel.recalculateFrequencies() self.__startFrequencyTimer() def __startFrequencyTimer(self): """ Private method to start the timer to recalculate the frequencies. """ tomorrow = QDateTime(QDate.currentDate().addDays(1), QTime(3, 0)) self.__frequencyTimer.start( QDateTime.currentDateTime().secsTo(tomorrow) * 1000)
class BookmarksManager(QObject): """ Class implementing the bookmarks manager. @signal entryAdded(BookmarkNode) emitted after a bookmark node has been added @signal entryRemoved(BookmarkNode, int, BookmarkNode) emitted after a bookmark node has been removed @signal entryChanged(BookmarkNode) emitted after a bookmark node has been changed @signal bookmarksSaved() emitted after the bookmarks were saved @signal bookmarksReloaded() emitted after the bookmarks were reloaded """ entryAdded = pyqtSignal(BookmarkNode) entryRemoved = pyqtSignal(BookmarkNode, int, BookmarkNode) entryChanged = pyqtSignal(BookmarkNode) bookmarksSaved = pyqtSignal() bookmarksReloaded = pyqtSignal() def __init__(self, parent=None): """ Constructor @param parent reference to the parent object (QObject) """ super(BookmarksManager, self).__init__(parent) self.__saveTimer = AutoSaver(self, self.save) self.entryAdded.connect(self.__saveTimer.changeOccurred) self.entryRemoved.connect(self.__saveTimer.changeOccurred) self.entryChanged.connect(self.__saveTimer.changeOccurred) self.__initialize() def __initialize(self): """ Private method to initialize some data. """ self.__loaded = False self.__bookmarkRootNode = None self.__toolbar = None self.__menu = None self.__bookmarksModel = None self.__commands = QUndoStack() @classmethod def getFileName(cls): """ Class method to get the file name of the bookmark file. @return name of the bookmark file (string) """ return os.path.join(Utilities.getConfigDir(), "browser", "bookmarks.xbel") def close(self): """ Public method to close the bookmark manager. """ self.__saveTimer.saveIfNeccessary() def undoRedoStack(self): """ Public method to get a reference to the undo stack. @return reference to the undo stack (QUndoStack) """ return self.__commands def changeExpanded(self): """ Public method to handle a change of the expanded state. """ self.__saveTimer.changeOccurred() def reload(self): """ Public method used to initiate a reloading of the bookmarks. """ self.__initialize() self.load() self.bookmarksReloaded.emit() def load(self): """ Public method to load the bookmarks. @exception RuntimeError raised to indicate an error loading the bookmarks """ if self.__loaded: return self.__loaded = True bookmarkFile = self.getFileName() if not QFile.exists(bookmarkFile): from . import DefaultBookmarks_rc # __IGNORE_WARNING__ bookmarkFile = QFile(":/DefaultBookmarks.xbel") bookmarkFile.open(QIODevice.ReadOnly) from .XbelReader import XbelReader reader = XbelReader() self.__bookmarkRootNode = reader.read(bookmarkFile) if reader.error() != QXmlStreamReader.NoError: E5MessageBox.warning( None, self.tr("Loading Bookmarks"), self.tr( """Error when loading bookmarks on line {0},""" """ column {1}:\n {2}""") .format(reader.lineNumber(), reader.columnNumber(), reader.errorString())) others = [] for index in range( len(self.__bookmarkRootNode.children()) - 1, -1, -1): node = self.__bookmarkRootNode.children()[index] if node.type() == BookmarkNode.Folder: if (node.title == self.tr("Toolbar Bookmarks") or node.title == BOOKMARKBAR) and \ self.__toolbar is None: node.title = self.tr(BOOKMARKBAR) self.__toolbar = node if (node.title == self.tr("Menu") or node.title == BOOKMARKMENU) and \ self.__menu is None: node.title = self.tr(BOOKMARKMENU) self.__menu = node else: others.append(node) self.__bookmarkRootNode.remove(node) if len(self.__bookmarkRootNode.children()) > 0: raise RuntimeError("Error loading bookmarks.") if self.__toolbar is None: self.__toolbar = BookmarkNode(BookmarkNode.Folder, self.__bookmarkRootNode) self.__toolbar.title = self.tr(BOOKMARKBAR) else: self.__bookmarkRootNode.add(self.__toolbar) if self.__menu is None: self.__menu = BookmarkNode(BookmarkNode.Folder, self.__bookmarkRootNode) self.__menu.title = self.tr(BOOKMARKMENU) else: self.__bookmarkRootNode.add(self.__menu) for node in others: self.__menu.add(node) self.__convertFromOldBookmarks() def save(self): """ Public method to save the bookmarks. """ if not self.__loaded: return from .XbelWriter import XbelWriter writer = XbelWriter() bookmarkFile = self.getFileName() # save root folder titles in English (i.e. not localized) self.__menu.title = BOOKMARKMENU self.__toolbar.title = BOOKMARKBAR if not writer.write(bookmarkFile, self.__bookmarkRootNode): E5MessageBox.warning( None, self.tr("Saving Bookmarks"), self.tr("""Error saving bookmarks to <b>{0}</b>.""") .format(bookmarkFile)) # restore localized titles self.__menu.title = self.tr(BOOKMARKMENU) self.__toolbar.title = self.tr(BOOKMARKBAR) self.bookmarksSaved.emit() def addBookmark(self, parent, node, row=-1): """ Public method to add a bookmark. @param parent reference to the node to add to (BookmarkNode) @param node reference to the node to add (BookmarkNode) @param row row number (integer) """ if not self.__loaded: return self.setTimestamp(node, BookmarkNode.TsAdded, QDateTime.currentDateTime()) command = InsertBookmarksCommand(self, parent, node, row) self.__commands.push(command) def removeBookmark(self, node): """ Public method to remove a bookmark. @param node reference to the node to be removed (BookmarkNode) """ if not self.__loaded: return parent = node.parent() row = parent.children().index(node) command = RemoveBookmarksCommand(self, parent, row) self.__commands.push(command) def setTitle(self, node, newTitle): """ Public method to set the title of a bookmark. @param node reference to the node to be changed (BookmarkNode) @param newTitle title to be set (string) """ if not self.__loaded: return command = ChangeBookmarkCommand(self, node, newTitle, True) self.__commands.push(command) def setUrl(self, node, newUrl): """ Public method to set the URL of a bookmark. @param node reference to the node to be changed (BookmarkNode) @param newUrl URL to be set (string) """ if not self.__loaded: return command = ChangeBookmarkCommand(self, node, newUrl, False) self.__commands.push(command) def setNodeChanged(self, node): """ Public method to signal changes of bookmarks other than title, URL or timestamp. @param node reference to the bookmark (BookmarkNode) """ self.__saveTimer.changeOccurred() def setTimestamp(self, node, timestampType, timestamp): """ Public method to set the URL of a bookmark. @param node reference to the node to be changed (BookmarkNode) @param timestampType type of the timestamp to set (BookmarkNode.TsAdded, BookmarkNode.TsModified, BookmarkNode.TsVisited) @param timestamp timestamp to set (QDateTime) """ if not self.__loaded: return assert timestampType in [BookmarkNode.TsAdded, BookmarkNode.TsModified, BookmarkNode.TsVisited] if timestampType == BookmarkNode.TsAdded: node.added = timestamp elif timestampType == BookmarkNode.TsModified: node.modified = timestamp elif timestampType == BookmarkNode.TsVisited: node.visited = timestamp self.__saveTimer.changeOccurred() def bookmarks(self): """ Public method to get a reference to the root bookmark node. @return reference to the root bookmark node (BookmarkNode) """ if not self.__loaded: self.load() return self.__bookmarkRootNode def menu(self): """ Public method to get a reference to the bookmarks menu node. @return reference to the bookmarks menu node (BookmarkNode) """ if not self.__loaded: self.load() return self.__menu def toolbar(self): """ Public method to get a reference to the bookmarks toolbar node. @return reference to the bookmarks toolbar node (BookmarkNode) """ if not self.__loaded: self.load() return self.__toolbar def bookmarksModel(self): """ Public method to get a reference to the bookmarks model. @return reference to the bookmarks model (BookmarksModel) """ if self.__bookmarksModel is None: from .BookmarksModel import BookmarksModel self.__bookmarksModel = BookmarksModel(self, self) return self.__bookmarksModel def importBookmarks(self): """ Public method to import bookmarks. """ from .BookmarksImportDialog import BookmarksImportDialog dlg = BookmarksImportDialog() if dlg.exec_() == QDialog.Accepted: importRootNode = dlg.getImportedBookmarks() if importRootNode is not None: self.addBookmark(self.menu(), importRootNode) def exportBookmarks(self): """ Public method to export the bookmarks. """ fileName, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( None, self.tr("Export Bookmarks"), "eric6_bookmarks.xbel", self.tr("XBEL bookmarks (*.xbel);;" "XBEL bookmarks (*.xml);;" "HTML Bookmarks (*.html)")) if not fileName: return ext = QFileInfo(fileName).suffix() if not ext: ex = selectedFilter.split("(*")[1].split(")")[0] if ex: fileName += ex ext = QFileInfo(fileName).suffix() if ext == "html": from .NsHtmlWriter import NsHtmlWriter writer = NsHtmlWriter() else: from .XbelWriter import XbelWriter writer = XbelWriter() if not writer.write(fileName, self.__bookmarkRootNode): E5MessageBox.critical( None, self.tr("Exporting Bookmarks"), self.tr("""Error exporting bookmarks to <b>{0}</b>.""") .format(fileName)) def __convertFromOldBookmarks(self): """ Private method to convert the old bookmarks into the new ones. """ bmNames = Preferences.Prefs.settings.value('Bookmarks/Names') bmFiles = Preferences.Prefs.settings.value('Bookmarks/Files') if bmNames is not None and bmFiles is not None: if len(bmNames) == len(bmFiles): convertedRootNode = BookmarkNode(BookmarkNode.Folder) convertedRootNode.title = self.tr("Converted {0}")\ .format(QDate.currentDate().toString( Qt.SystemLocaleShortDate)) for i in range(len(bmNames)): node = BookmarkNode(BookmarkNode.Bookmark, convertedRootNode) node.title = bmNames[i] url = QUrl(bmFiles[i]) if not url.scheme(): url.setScheme("file") node.url = url.toString() self.addBookmark(self.menu(), convertedRootNode) Preferences.Prefs.settings.remove('Bookmarks') def iconChanged(self, url): """ Public slot to update the icon image for an URL. @param url URL of the icon to update (QUrl or string) """ if isinstance(url, QUrl): url = url.toString() nodes = self.bookmarksForUrl(url) for node in nodes: self.bookmarksModel().entryChanged(node) def bookmarkForUrl(self, url, start=StartRoot): """ Public method to get a bookmark node for a given URL. @param url URL of the bookmark to search for (QUrl or string) @keyparam start indicator for the start of the search (StartRoot, StartMenu, StartToolBar) @return bookmark node for the given url (BookmarkNode) """ if start == StartMenu: startNode = self.__menu elif start == StartToolBar: startNode = self.__toolbar else: startNode = self.__bookmarkRootNode if startNode is None: return None if isinstance(url, QUrl): url = url.toString() return self.__searchBookmark(url, startNode) def __searchBookmark(self, url, startNode): """ Private method get a bookmark node for a given URL. @param url URL of the bookmark to search for (string) @param startNode reference to the node to start searching (BookmarkNode) @return bookmark node for the given url (BookmarkNode) """ bm = None for node in startNode.children(): if node.type() == BookmarkNode.Folder: bm = self.__searchBookmark(url, node) elif node.type() == BookmarkNode.Bookmark: if node.url == url: bm = node if bm is not None: return bm return None def bookmarksForUrl(self, url, start=StartRoot): """ Public method to get a list of bookmark nodes for a given URL. @param url URL of the bookmarks to search for (QUrl or string) @keyparam start indicator for the start of the search (StartRoot, StartMenu, StartToolBar) @return list of bookmark nodes for the given url (list of BookmarkNode) """ if start == StartMenu: startNode = self.__menu elif start == StartToolBar: startNode = self.__toolbar else: startNode = self.__bookmarkRootNode if startNode is None: return None if isinstance(url, QUrl): url = url.toString() return self.__searchBookmarks(url, startNode) def __searchBookmarks(self, url, startNode): """ Private method get a list of bookmark nodes for a given URL. @param url URL of the bookmarks to search for (string) @param startNode reference to the node to start searching (BookmarkNode) @return list of bookmark nodes for the given url (list of BookmarkNode) """ bm = [] for node in startNode.children(): if node.type() == BookmarkNode.Folder: bm.extend(self.__searchBookmarks(url, node)) elif node.type() == BookmarkNode.Bookmark: if node.url == url: bm.append(node) return bm
class DownloadManager(QDialog, Ui_DownloadManager): """ Class implementing the download manager. @signal downloadsCountChanged() emitted to indicate a change of the count of download items """ RemoveNever = 0 RemoveExit = 1 RemoveSuccessFullDownload = 2 UpdateTimerTimeout = 1000 downloadsCountChanged = pyqtSignal() def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget (QWidget) """ super(DownloadManager, self).__init__(parent) self.setupUi(self) self.setWindowFlags(Qt.Window) self.__winTaskbarButton = None self.__saveTimer = AutoSaver(self, self.save) self.__model = DownloadModel(self) self.__manager = WebBrowserWindow.networkManager() self.__iconProvider = None self.__downloads = [] self.__downloadDirectory = "" self.__loaded = False self.__rowHeightMultiplier = 1.1 self.setDownloadDirectory(Preferences.getUI("DownloadPath")) self.downloadsView.setShowGrid(False) self.downloadsView.verticalHeader().hide() self.downloadsView.horizontalHeader().hide() self.downloadsView.setAlternatingRowColors(True) self.downloadsView.horizontalHeader().setStretchLastSection(True) self.downloadsView.setModel(self.__model) self.downloadsView.setContextMenuPolicy(Qt.CustomContextMenu) self.downloadsView.customContextMenuRequested.connect( self.__customContextMenuRequested) self.__clearShortcut = QShortcut(QKeySequence("Ctrl+L"), self) self.__clearShortcut.activated.connect(self.on_cleanupButton_clicked) self.__load() self.__updateTimer = QBasicTimer() def __customContextMenuRequested(self, pos): """ Private slot to handle the context menu request for the bookmarks tree. @param pos position the context menu was requested (QPoint) """ menu = QMenu() selectedRowsCount = len( self.downloadsView.selectionModel().selectedRows()) if selectedRowsCount == 1: row = self.downloadsView.selectionModel().selectedRows()[0].row() itm = self.__downloads[row] if itm.downloadedSuccessfully(): menu.addAction(UI.PixmapCache.getIcon("open.png"), self.tr("Open"), self.__contextMenuOpen) elif itm.downloading(): menu.addAction(UI.PixmapCache.getIcon("stopLoading.png"), self.tr("Cancel"), self.__contextMenuCancel) menu.addSeparator() menu.addAction(self.tr("Open Containing Folder"), self.__contextMenuOpenFolder) menu.addSeparator() menu.addAction(self.tr("Go to Download Page"), self.__contextMenuGotoPage) menu.addAction(self.tr("Copy Download Link"), self.__contextMenuCopyLink) menu.addSeparator() menu.addAction(self.tr("Select All"), self.__contextMenuSelectAll) if (selectedRowsCount > 1 or (selectedRowsCount == 1 and not self.__downloads[self.downloadsView.selectionModel( ).selectedRows()[0].row()].downloading())): menu.addSeparator() menu.addAction(self.tr("Remove From List"), self.__contextMenuRemoveSelected) menu.exec_(QCursor.pos()) def shutdown(self): """ Public method to stop the download manager. """ self.save() self.close() def activeDownloadsCount(self): """ Public method to get the number of active downloads. @return number of active downloads (integer) """ count = 0 for download in self.__downloads: if download.downloading(): count += 1 return count def allowQuit(self): """ Public method to check, if it is ok to quit. @return flag indicating allowance to quit (boolean) """ if self.activeDownloadsCount() > 0: res = E5MessageBox.yesNo( self, self.tr(""), self.tr( """There are %n downloads in progress.\n""" """Do you want to quit anyway?""", "", self.activeDownloadsCount()), icon=E5MessageBox.Warning) if not res: self.show() return False self.close() return True def __testWebBrowserView(self, view, url): """ Private method to test a web browser view against an URL. @param view reference to the web browser view to be tested @type WebBrowserView @param url URL to test against @type QUrl @return flag indicating, that the view is the one for the URL @rtype bool """ if view.tabWidget().count() < 2: return False page = view.page() if page.history().count() != 0: return False if (not page.url().isEmpty() and page.url().host() == url.host()): return True requestedUrl = page.requestedUrl() if requestedUrl.isEmpty(): requestedUrl = QUrl(view.tabWidget().urlBarForView(view).text()) return requestedUrl.isEmpty() or requestedUrl.host() == url.host() def __closeDownloadTab(self, url): """ Private method to close an empty tab, that was opened only for loading the download URL. @param url download URL @type QUrl """ if self.__testWebBrowserView( WebBrowserWindow.getWindow().currentBrowser(), url): WebBrowserWindow.getWindow().closeCurrentBrowser() return for window in WebBrowserWindow.mainWindows(): for browser in window.browsers(): if self.__testWebBrowserView(browser, url): window.closeBrowser(browser) return def download(self, downloadItem): """ Public method to download a file. @param downloadItem reference to the download object containing the download data. @type QWebEngineDownloadItem """ url = downloadItem.url() if url.isEmpty(): return self.__closeDownloadTab(url) # Safe Browsing from WebBrowser.SafeBrowsing.SafeBrowsingManager import ( SafeBrowsingManager) if SafeBrowsingManager.isEnabled(): threatLists = ( WebBrowserWindow.safeBrowsingManager().lookupUrl(url)[0]) if threatLists: threatMessages = (WebBrowserWindow.safeBrowsingManager(). getThreatMessages(threatLists)) res = E5MessageBox.warning( WebBrowserWindow.getWindow(), self.tr("Suspicuous URL detected"), self.tr("<p>The URL <b>{0}</b> was found in the Safe" " Browsing database.</p>{1}").format( url.toString(), "".join(threatMessages)), E5MessageBox.StandardButtons(E5MessageBox.Abort | E5MessageBox.Ignore), E5MessageBox.Abort) if res == E5MessageBox.Abort: downloadItem.cancel() return window = WebBrowserWindow.getWindow() if window: pageUrl = window.currentBrowser().url() else: pageUrl = QUrl() from .DownloadItem import DownloadItem itm = DownloadItem(downloadItem=downloadItem, pageUrl=pageUrl, parent=self) self.__addItem(itm) if Preferences.getWebBrowser("DownloadManagerAutoOpen"): self.show() else: self.__startUpdateTimer() def show(self): """ Public slot to show the download manager dialog. """ self.__startUpdateTimer() super(DownloadManager, self).show() self.activateWindow() self.raise_() def __addItem(self, itm, append=False): """ Private method to add a download to the list of downloads. @param itm reference to the download item @type DownloadItem @param append flag indicating to append the item @type bool """ itm.statusChanged.connect(lambda: self.__updateRow(itm)) itm.downloadFinished.connect(self.__finished) # insert at top of window if append: row = self.downloadsCount() else: row = 0 self.__model.beginInsertRows(QModelIndex(), row, row) if append: self.__downloads.append(itm) else: self.__downloads.insert(0, itm) self.__model.endInsertRows() self.downloadsView.setIndexWidget(self.__model.index(row, 0), itm) icon = self.style().standardIcon(QStyle.SP_FileIcon) itm.setIcon(icon) self.downloadsView.setRowHeight( row, itm.sizeHint().height() * self.__rowHeightMultiplier) # just in case the download finished before the constructor returned self.__updateRow(itm) self.changeOccurred() self.downloadsCountChanged.emit() def __updateRow(self, itm): """ Private slot to update a download item. @param itm reference to the download item @type DownloadItem """ if itm not in self.__downloads: return row = self.__downloads.index(itm) if self.__iconProvider is None: self.__iconProvider = QFileIconProvider() icon = self.__iconProvider.icon(QFileInfo(itm.fileName())) if icon.isNull(): icon = self.style().standardIcon(QStyle.SP_FileIcon) itm.setIcon(icon) self.downloadsView.setRowHeight( row, itm.minimumSizeHint().height() * self.__rowHeightMultiplier) remove = False if (itm.downloadedSuccessfully() and self.removePolicy() == DownloadManager.RemoveSuccessFullDownload): remove = True if remove: self.__model.removeRow(row) self.cleanupButton.setEnabled( (self.downloadsCount() - self.activeDownloadsCount()) > 0) # record the change self.changeOccurred() def removePolicy(self): """ Public method to get the remove policy. @return remove policy (integer) """ return Preferences.getWebBrowser("DownloadManagerRemovePolicy") def setRemovePolicy(self, policy): """ Public method to set the remove policy. @param policy policy to be set (DownloadManager.RemoveExit, DownloadManager.RemoveNever, DownloadManager.RemoveSuccessFullDownload) """ assert policy in (DownloadManager.RemoveExit, DownloadManager.RemoveNever, DownloadManager.RemoveSuccessFullDownload) if policy == self.removePolicy(): return Preferences.setWebBrowser("DownloadManagerRemovePolicy", self.policy) def save(self): """ Public method to save the download settings. """ if not self.__loaded: return Preferences.setWebBrowser("DownloadManagerSize", self.size()) Preferences.setWebBrowser("DownloadManagerPosition", self.pos()) if self.removePolicy() == DownloadManager.RemoveExit: return from WebBrowser.WebBrowserWindow import WebBrowserWindow if WebBrowserWindow.isPrivate(): return downloads = [] for download in self.__downloads: downloads.append(download.getData()) Preferences.setWebBrowser("DownloadManagerDownloads", downloads) def __load(self): """ Private method to load the download settings. """ if self.__loaded: return size = Preferences.getWebBrowser("DownloadManagerSize") if size.isValid(): self.resize(size) pos = Preferences.getWebBrowser("DownloadManagerPosition") self.move(pos) from WebBrowser.WebBrowserWindow import WebBrowserWindow if not WebBrowserWindow.isPrivate(): downloads = Preferences.getWebBrowser("DownloadManagerDownloads") for download in downloads: if (not download["URL"].isEmpty() and bool(download["Location"])): from .DownloadItem import DownloadItem itm = DownloadItem(parent=self) itm.setData(download) self.__addItem(itm, append=True) self.cleanupButton.setEnabled( (self.downloadsCount() - self.activeDownloadsCount()) > 0) self.__loaded = True self.downloadsCountChanged.emit() def closeEvent(self, evt): """ Protected event handler for the close event. @param evt reference to the close event @type QCloseEvent """ self.save() def cleanup(self): """ Public slot to cleanup the downloads. """ self.on_cleanupButton_clicked() @pyqtSlot() def on_cleanupButton_clicked(self): """ Private slot to cleanup the downloads. """ if self.downloadsCount() == 0: return self.__model.removeRows(0, self.downloadsCount()) if (self.downloadsCount() == 0 and self.__iconProvider is not None): self.__iconProvider = None self.changeOccurred() self.downloadsCountChanged.emit() def __finished(self, success): """ Private slot to handle a finished download. @param success flag indicating a successful download @type bool """ if self.isVisible(): QApplication.alert(self) self.downloadsCountChanged.emit() if self.activeDownloadsCount() == 0: # all active downloads are done if success and e5App().activeWindow() is not self: if WebBrowserWindow.notificationsEnabled(): WebBrowserWindow.showNotification( UI.PixmapCache.getPixmap("downloads48.png"), self.tr("Downloads finished"), self.tr("All files have been downloaded.")) if not Preferences.getWebBrowser("DownloadManagerAutoClose"): self.raise_() self.activateWindow() self.__stopUpdateTimer() self.infoLabel.clear() self.setWindowTitle(self.tr("Download Manager")) if Globals.isWindowsPlatform(): self.__taskbarButton().progress().hide() if Preferences.getWebBrowser("DownloadManagerAutoClose"): self.close() def setDownloadDirectory(self, directory): """ Public method to set the current download directory. @param directory current download directory (string) """ self.__downloadDirectory = directory if self.__downloadDirectory != "": self.__downloadDirectory += "/" def downloadDirectory(self): """ Public method to get the current download directory. @return current download directory (string) """ return self.__downloadDirectory def downloadsCount(self): """ Public method to get the number of downloads. @return number of downloads @rtype int """ return len(self.__downloads) def downloads(self): """ Public method to get a reference to the downloads. @return reference to the downloads (list of DownloadItem) """ return self.__downloads def changeOccurred(self): """ Public method to signal a change. """ self.__saveTimer.changeOccurred() def __taskbarButton(self): """ Private method to get a reference to the task bar button (Windows only). @return reference to the task bar button @rtype QWinTaskbarButton or None """ if Globals.isWindowsPlatform(): from PyQt5.QtWinExtras import QWinTaskbarButton if self.__winTaskbarButton is None: window = WebBrowserWindow.mainWindow() self.__winTaskbarButton = QWinTaskbarButton( window.windowHandle()) self.__winTaskbarButton.progress().setRange(0, 100) return self.__winTaskbarButton def timerEvent(self, evt): """ Protected event handler for timer events. @param evt reference to the timer event @type QTimerEvent """ if evt.timerId() == self.__updateTimer.timerId(): if self.activeDownloadsCount() == 0: self.__stopUpdateTimer() self.infoLabel.clear() self.setWindowTitle(self.tr("Download Manager")) if Globals.isWindowsPlatform(): self.__taskbarButton().progress().hide() else: progresses = [] for itm in self.__downloads: if (itm is None or itm.downloadCanceled() or not itm.downloading()): continue progresses.append( (itm.downloadProgress(), itm.remainingTime(), itm.currentSpeed())) if not progresses: return remaining = 0 progress = 0 speed = 0.0 for progressData in progresses: if progressData[1] > remaining: remaining = progressData[1] progress += progressData[0] speed += progressData[2] progress = progress / len(progresses) if self.isVisible(): self.infoLabel.setText( self.tr("{0}% of %n file(s) ({1}) {2}", "", len(progresses)).format( progress, speedString(speed), timeString(remaining), )) self.setWindowTitle(self.tr("{0}% - Download Manager")) if Globals.isWindowsPlatform(): self.__taskbarButton().progress().show() self.__taskbarButton().progress().setValue(progress) super(DownloadManager, self).timerEvent(evt) def __startUpdateTimer(self): """ Private slot to start the update timer. """ if self.activeDownloadsCount() and not self.__updateTimer.isActive(): self.__updateTimer.start(DownloadManager.UpdateTimerTimeout, self) def __stopUpdateTimer(self): """ Private slot to stop the update timer. """ self.__updateTimer.stop() ########################################################################### ## Context menu related methods below ########################################################################### def __currentItem(self): """ Private method to get a reference to the current item. @return reference to the current item (DownloadItem) """ index = self.downloadsView.currentIndex() if index and index.isValid(): row = index.row() return self.__downloads[row] return None def __contextMenuOpen(self): """ Private method to open the downloaded file. """ itm = self.__currentItem() if itm is not None: itm.openFile() def __contextMenuOpenFolder(self): """ Private method to open the folder containing the downloaded file. """ itm = self.__currentItem() if itm is not None: itm.openFolder() def __contextMenuCancel(self): """ Private method to cancel the current download. """ itm = self.__currentItem() if itm is not None: itm.cancelDownload() def __contextMenuGotoPage(self): """ Private method to open the download page. """ itm = self.__currentItem() if itm is not None: url = itm.getPageUrl() WebBrowserWindow.mainWindow().openUrl(url, "") def __contextMenuCopyLink(self): """ Private method to copy the download link to the clipboard. """ itm = self.__currentItem() if itm is not None: url = itm.getPageUrl().toDisplayString(QUrl.FullyDecoded) QApplication.clipboard().setText(url) def __contextMenuSelectAll(self): """ Private method to select all downloads. """ self.downloadsView.selectAll() def __contextMenuRemoveSelected(self): """ Private method to remove the selected downloads from the list. """ self.downloadsView.removeSelected()
class PasswordManager(QObject): """ Class implementing the password manager. @signal changed() emitted to indicate a change """ SEPARATOR = "====================" FORMS = "=====FORMS=====" NEVER = "=====NEVER=====" def __init__(self, parent = None): """ Constructor @param parent reference to the parent object (QObject) """ QObject.__init__(self, parent) self.__logins = {} self.__loginForms = {} self.__never = [] self.__loaded = False self.__saveTimer = AutoSaver(self, self.save) self.connect(self, SIGNAL("changed()"), self.__saveTimer.changeOccurred) def clear(self): """ Public slot to clear the saved passwords. """ if not self.__loaded: self.__load() self.__logins = {} self.__loginForms = {} self.__never = [] self.__saveTimer.changeOccurred() self.__saveTimer.saveIfNeccessary() self.emit(SIGNAL("changed()")) def getLogin(self, url, realm): """ Public method to get the login credentials. @param url URL to get the credentials for (QUrl) @param realm realm to get the credentials for (string or QString) @return tuple containing the user name (string) and password (string) """ if not self.__loaded: self.__load() key = self.__createKey(url, realm) try: return self.__logins[key][0], Utilities.pwDecode(self.__logins[key][1]) except KeyError: return "", "" def setLogin(self, url, realm, username, password): """ Public method to set the login credentials. @param url URL to set the credentials for (QUrl) @param realm realm to set the credentials for (string or QString) @param username username for the login (string or QString) @param password password for the login (string or QString) """ if not self.__loaded: self.__load() key = self.__createKey(url, realm) self.__logins[key] = (unicode(username), Utilities.pwEncode(password)) self.emit(SIGNAL("changed()")) def __createKey(self, url, realm): """ Private method to create the key string for the login credentials. @param url URL to get the credentials for (QUrl) @param realm realm to get the credentials for (string or QString) @return key string (string) """ realm = unicode(realm) if realm: key = "%s://%s (%s)" % (url.scheme(), url.authority(), realm) else: key = "%s://%s" % (url.scheme(), url.authority()) return key def save(self): """ Public slot to save the login entries to disk. """ if not self.__loaded: return loginFile = os.path.join(Utilities.getConfigDir(), "browser", "logins") try: f = open(loginFile, "w") for key, login in self.__logins.items(): f.write("%s\n" % key) f.write("%s\n" % login[0]) f.write("%s\n" % login[1]) f.write("%s\n" % self.SEPARATOR) if self.__loginForms: f.write("%s\n" % self.FORMS) for key, form in self.__loginForms.items(): f.write("%s\n" % key) form.save(f) f.write("%s\n" % self.SEPARATOR) if self.__never: f.write("%s\n" % self.NEVER) for key in self.__never: f.write("%s\n") % key f.close() except IOError, err: KQMessageBox.critical(None, self.trUtf8("Saving login data"), self.trUtf8("""<p>Login data could not be saved to <b>%1</b></p>""" """<p>Reason: %2</p>""").arg(loginFile).arg(str(err))) return
class PasswordManager(QObject): """ Class implementing the password manager. @signal changed() emitted to indicate a change @signal passwordsSaved() emitted after the passwords were saved """ changed = pyqtSignal() passwordsSaved = pyqtSignal() def __init__(self, parent=None): """ Constructor @param parent reference to the parent object (QObject) """ super(PasswordManager, self).__init__(parent) # setup userscript to monitor forms script = QWebEngineScript() script.setName("_eric_passwordmonitor") script.setInjectionPoint(QWebEngineScript.DocumentReady) script.setWorldId(WebBrowserPage.SafeJsWorld) script.setRunsOnSubFrames(True) script.setSourceCode(Scripts.setupFormObserver()) profile = WebBrowser.WebBrowserWindow.WebBrowserWindow.webProfile() profile.scripts().insert(script) self.__logins = {} self.__loginForms = {} self.__never = [] self.__loaded = False self.__saveTimer = AutoSaver(self, self.save) self.changed.connect(self.__saveTimer.changeOccurred) def clear(self): """ Public slot to clear the saved passwords. """ if not self.__loaded: self.__load() self.__logins = {} self.__loginForms = {} self.__never = [] self.__saveTimer.changeOccurred() self.__saveTimer.saveIfNeccessary() self.changed.emit() def getLogin(self, url, realm): """ Public method to get the login credentials. @param url URL to get the credentials for (QUrl) @param realm realm to get the credentials for (string) @return tuple containing the user name (string) and password (string) """ if not self.__loaded: self.__load() key = self.__createKey(url, realm) try: return self.__logins[key][0], Utilities.crypto.pwConvert( self.__logins[key][1], encode=False) except KeyError: return "", "" def setLogin(self, url, realm, username, password): """ Public method to set the login credentials. @param url URL to set the credentials for (QUrl) @param realm realm to set the credentials for (string) @param username username for the login (string) @param password password for the login (string) """ if not self.__loaded: self.__load() key = self.__createKey(url, realm) self.__logins[key] = (username, Utilities.crypto.pwConvert(password, encode=True)) self.changed.emit() def __createKey(self, url, realm): """ Private method to create the key string for the login credentials. @param url URL to get the credentials for (QUrl) @param realm realm to get the credentials for (string) @return key string (string) """ authority = url.authority() if authority.startswith("@"): authority = authority[1:] if realm: key = "{0}://{1} ({2})".format(url.scheme(), authority, realm) else: key = "{0}://{1}".format(url.scheme(), authority) return key def getFileName(self): """ Public method to get the file name of the passwords file. @return name of the passwords file (string) """ return os.path.join(Utilities.getConfigDir(), "web_browser", "logins.xml") def save(self): """ Public slot to save the login entries to disk. """ if not self.__loaded: return from WebBrowser.WebBrowserWindow import WebBrowserWindow if not WebBrowserWindow.isPrivate(): from .PasswordWriter import PasswordWriter loginFile = self.getFileName() writer = PasswordWriter() if not writer.write(loginFile, self.__logins, self.__loginForms, self.__never): E5MessageBox.critical( None, self.tr("Saving login data"), self.tr("""<p>Login data could not be saved to""" """ <b>{0}</b></p>""").format(loginFile)) else: self.passwordsSaved.emit() def __load(self): """ Private method to load the saved login credentials. """ if self.__loaded: return loginFile = self.getFileName() if os.path.exists(loginFile): from .PasswordReader import PasswordReader reader = PasswordReader() self.__logins, self.__loginForms, self.__never = reader.read( loginFile) if reader.error() != QXmlStreamReader.NoError: E5MessageBox.warning( None, self.tr("Loading login data"), self.tr("""Error when loading login data on""" """ line {0}, column {1}:\n{2}""").format( reader.lineNumber(), reader.columnNumber(), reader.errorString())) self.__loaded = True def reload(self): """ Public method to reload the login data. """ if not self.__loaded: return self.__loaded = False self.__load() def close(self): """ Public method to close the passwords manager. """ self.__saveTimer.saveIfNeccessary() def removePassword(self, site): """ Public method to remove a password entry. @param site web site name (string) """ if site in self.__logins: del self.__logins[site] if site in self.__loginForms: del self.__loginForms[site] self.changed.emit() def allSiteNames(self): """ Public method to get a list of all site names. @return sorted list of all site names (list of strings) """ if not self.__loaded: self.__load() return sorted(self.__logins.keys()) def sitesCount(self): """ Public method to get the number of available sites. @return number of sites (integer) """ if not self.__loaded: self.__load() return len(self.__logins) def siteInfo(self, site): """ Public method to get a reference to the named site. @param site web site name (string) @return tuple containing the user name (string) and password (string) """ if not self.__loaded: self.__load() if site not in self.__logins: return None return self.__logins[site][0], Utilities.crypto.pwConvert( self.__logins[site][1], encode=False) def formSubmitted(self, urlStr, userName, password, data, page): """ Public method to record login data. @param urlStr form submission URL @type str @param userName name of the user @type str @param password user password @type str @param data data to be submitted @type QByteArray @param page reference to the calling page @type QWebEnginePage """ # shall passwords be saved? if not Preferences.getUser("SavePasswords"): return if WebBrowser.WebBrowserWindow.WebBrowserWindow.isPrivate(): return if not self.__loaded: self.__load() if urlStr in self.__never: return if userName and password: url = QUrl(urlStr) url = self.__stripUrl(url) key = self.__createKey(url, "") if key not in self.__loginForms: mb = E5MessageBox.E5MessageBox( E5MessageBox.Question, self.tr("Save password"), self.tr( """<b>Would you like to save this password?</b><br/>""" """To review passwords you have saved and remove""" """ them, use the password management dialog of the""" """ Settings menu."""), modal=True, parent=page.view()) neverButton = mb.addButton(self.tr("Never for this site"), E5MessageBox.DestructiveRole) noButton = mb.addButton(self.tr("Not now"), E5MessageBox.RejectRole) mb.addButton(E5MessageBox.Yes) mb.exec_() if mb.clickedButton() == neverButton: self.__never.append(url.toString()) return elif mb.clickedButton() == noButton: return self.__logins[key] = (userName, Utilities.crypto.pwConvert(password, encode=True)) from .LoginForm import LoginForm form = LoginForm() form.url = url form.name = userName form.postData = Utilities.crypto.pwConvert( bytes(data).decode("utf-8"), encode=True) self.__loginForms[key] = form self.changed.emit() def __stripUrl(self, url): """ Private method to strip off all unneeded parts of a URL. @param url URL to be stripped (QUrl) @return stripped URL (QUrl) """ cleanUrl = QUrl(url) cleanUrl.setQuery("") cleanUrl.setUserInfo("") authority = cleanUrl.authority() if authority.startswith("@"): authority = authority[1:] cleanUrl = QUrl("{0}://{1}{2}".format(cleanUrl.scheme(), authority, cleanUrl.path())) cleanUrl.setFragment("") return cleanUrl def completePage(self, page): """ Public slot to complete login forms with saved data. @param page reference to the web page (WebBrowserPage) """ if page is None: return if not self.__loaded: self.__load() url = page.url() url = self.__stripUrl(url) key = self.__createKey(url, "") if (key not in self.__loginForms or key not in self.__logins): return form = self.__loginForms[key] if form.url != url: return postData = QByteArray( Utilities.crypto.pwConvert(form.postData, encode=False).encode("utf-8")) script = Scripts.completeFormData(postData) page.runJavaScript(script, WebBrowserPage.SafeJsWorld) def masterPasswordChanged(self, oldPassword, newPassword): """ Public slot to handle the change of the master password. @param oldPassword current master password (string) @param newPassword new master password (string) """ if not self.__loaded: self.__load() progress = E5ProgressDialog( self.tr("Re-encoding saved passwords..."), None, 0, len(self.__logins) + len(self.__loginForms), self.tr("%v/%m Passwords"), QApplication.activeModalWidget()) progress.setMinimumDuration(0) progress.setWindowTitle(self.tr("Passwords")) count = 0 # step 1: do the logins for key in self.__logins: progress.setValue(count) QCoreApplication.processEvents() username, pwHash = self.__logins[key] pwHash = Utilities.crypto.pwRecode(pwHash, oldPassword, newPassword) self.__logins[key] = (username, pwHash) count += 1 # step 2: do the login forms for key in self.__loginForms: progress.setValue(count) QCoreApplication.processEvents() postData = self.__loginForms[key].postData postData = Utilities.crypto.pwRecode(postData, oldPassword, newPassword) self.__loginForms[key].postData = postData count += 1 progress.setValue(len(self.__logins) + len(self.__loginForms)) QCoreApplication.processEvents() self.changed.emit()
class TaskViewer(QTreeWidget): """ Class implementing the task viewer. @signal displayFile(str, int) emitted to go to a file task """ displayFile = pyqtSignal(str, int) def __init__(self, parent, project): """ Constructor @param parent the parent (QWidget) @param project reference to the project object """ super(TaskViewer, self).__init__(parent) self.setSortingEnabled(True) self.setExpandsOnDoubleClick(False) self.__headerItem = QTreeWidgetItem( ["", "", self.tr("Summary"), self.tr("Filename"), self.tr("Line"), ""]) self.__headerItem.setIcon( 0, UI.PixmapCache.getIcon("taskCompleted.png")) self.__headerItem.setIcon( 1, UI.PixmapCache.getIcon("taskPriority.png")) self.setHeaderItem(self.__headerItem) self.header().setSortIndicator(2, Qt.AscendingOrder) self.__resizeColumns() self.tasks = [] self.copyTask = None self.projectOpen = False self.project = project self.projectTasksScanFilter = "" from .TaskFilter import TaskFilter self.taskFilter = TaskFilter() self.taskFilter.setActive(False) self.__projectTasksSaveTimer = AutoSaver(self, self.saveProjectTasks) self.__projectTasksMenu = QMenu( self.tr("P&roject Tasks"), self) self.__projectTasksMenu.addAction( self.tr("&Regenerate project tasks"), self.__regenerateProjectTasks) self.__projectTasksMenu.addSeparator() self.__projectTasksMenu.addAction( self.tr("&Configure scan options"), self.__configureProjectTasksScanOptions) self.__menu = QMenu(self) self.__menu.addAction(self.tr("&New Task..."), self.__newTask) self.subtaskItem = self.__menu.addAction( self.tr("New &Sub-Task..."), self.__newSubTask) self.__menu.addSeparator() self.projectTasksMenuItem = self.__menu.addMenu( self.__projectTasksMenu) self.__menu.addSeparator() self.gotoItem = self.__menu.addAction( self.tr("&Go To"), self.__goToTask) self.__menu.addSeparator() self.copyItem = self.__menu.addAction( self.tr("&Copy"), self.__copyTask) self.pasteItem = self.__menu.addAction( self.tr("&Paste"), self.__pasteTask) self.pasteMainItem = self.__menu.addAction( self.tr("Paste as &Main Task"), self.__pasteMainTask) self.deleteItem = self.__menu.addAction( self.tr("&Delete"), self.__deleteTask) self.__menu.addSeparator() self.markCompletedItem = self.__menu.addAction( self.tr("&Mark Completed"), self.__markCompleted) self.__menu.addAction( self.tr("Delete Completed &Tasks"), self.__deleteCompleted) self.__menu.addSeparator() self.__menu.addAction( self.tr("P&roperties..."), self.__editTaskProperties) self.__menu.addSeparator() self.__menuFilteredAct = self.__menu.addAction( self.tr("&Filtered display")) self.__menuFilteredAct.setCheckable(True) self.__menuFilteredAct.setChecked(False) self.__menuFilteredAct.triggered[bool].connect(self.__activateFilter) self.__menu.addAction( self.tr("Filter c&onfiguration..."), self.__configureFilter) self.__menu.addSeparator() self.__menu.addAction( self.tr("Resi&ze columns"), self.__resizeColumns) self.__menu.addSeparator() self.__menu.addAction(self.tr("Configure..."), self.__configure) self.__backMenu = QMenu(self) self.__backMenu.addAction(self.tr("&New Task..."), self.__newTask) self.__backMenu.addSeparator() self.backProjectTasksMenuItem = self.__backMenu.addMenu( self.__projectTasksMenu) self.__backMenu.addSeparator() self.backPasteItem = self.__backMenu.addAction( self.tr("&Paste"), self.__pasteTask) self.backPasteMainItem = self.__menu.addAction( self.tr("Paste as &Main Task"), self.__pasteMainTask) self.__backMenu.addSeparator() self.__backMenu.addAction( self.tr("Delete Completed &Tasks"), self.__deleteCompleted) self.__backMenu.addSeparator() self.__backMenuFilteredAct = self.__backMenu.addAction( self.tr("&Filtered display")) self.__backMenuFilteredAct.setCheckable(True) self.__backMenuFilteredAct.setChecked(False) self.__backMenuFilteredAct.triggered[bool].connect( self.__activateFilter) self.__backMenu.addAction( self.tr("Filter c&onfiguration..."), self.__configureFilter) self.__backMenu.addSeparator() self.__backMenu.addAction( self.tr("Resi&ze columns"), self.__resizeColumns) self.__backMenu.addSeparator() self.__backMenu.addAction( self.tr("Configure..."), self.__configure) self.__activating = False self.setContextMenuPolicy(Qt.CustomContextMenu) self.customContextMenuRequested.connect(self.__showContextMenu) self.itemActivated.connect(self.__taskItemActivated) self.setWindowIcon(UI.PixmapCache.getIcon("eric.png")) self.__generateTopLevelItems() def __generateTopLevelItems(self): """ Private method to generate the 'Extracted Tasks' item. """ self.__extractedItem = QTreeWidgetItem(self, [self.tr("Extracted Tasks")]) self.__manualItem = QTreeWidgetItem(self, [self.tr("Manual Tasks")]) for itm in [self.__extractedItem, self.__manualItem]: itm.setFirstColumnSpanned(True) itm.setExpanded(True) itm.setHidden(True) font = itm.font(0) font.setUnderline(True) itm.setFont(0, font) def __checkTopLevelItems(self): """ Private slot to check the 'Extracted Tasks' item for children. """ for itm in [self.__extractedItem, self.__manualItem]: visibleCount = itm.childCount() for index in range(itm.childCount()): if itm.child(index).isHidden(): visibleCount -= 1 itm.setHidden(visibleCount == 0) def __resort(self): """ Private method to resort the tree. """ self.sortItems(self.sortColumn(), self.header().sortIndicatorOrder()) def __resizeColumns(self): """ Private method to resize the list columns. """ self.header().resizeSections(QHeaderView.ResizeToContents) self.header().setStretchLastSection(True) def findParentTask(self, parentUid): """ Public method to find a parent task by its ID. @param parentUid uid of the parent task (string) @return reference to the task (Task) """ if not parentUid: return None parentTask = None for task in self.tasks: if task.getUuid() == parentUid: parentTask = task break return parentTask def __refreshDisplay(self): """ Private method to refresh the display. """ for task in self.tasks: task.setHidden(not self.taskFilter.showTask(task)) self.__checkTopLevelItems() self.__resort() self.__resizeColumns() def __taskItemActivated(self, itm, col): """ Private slot to handle the activation of an item. @param itm reference to the activated item (QTreeWidgetItem) @param col column the item was activated in (integer) """ if not self.__activating and \ itm is not self.__extractedItem and \ itm is not self.__manualItem: self.__activating = True fn = itm.getFilename() if fn: self.displayFile.emit(fn, itm.getLineno()) else: self.__editTaskProperties() self.__activating = False def __showContextMenu(self, coord): """ Private slot to show the context menu of the list. @param coord the position of the mouse pointer (QPoint) """ itm = self.itemAt(coord) coord = self.mapToGlobal(coord) if itm is None or \ itm is self.__extractedItem or \ itm is self.__manualItem: self.backProjectTasksMenuItem.setEnabled(self.projectOpen) if self.copyTask: self.backPasteItem.setEnabled(True) self.backPasteMainItem.setEnabled(True) else: self.backPasteItem.setEnabled(False) self.backPasteMainItem.setEnabled(False) self.__backMenu.popup(coord) else: self.projectTasksMenuItem.setEnabled(self.projectOpen) if itm.getFilename(): self.gotoItem.setEnabled(True) self.deleteItem.setEnabled(True) self.markCompletedItem.setEnabled(False) self.copyItem.setEnabled(False) self.subtaskItem.setEnabled(False) else: self.gotoItem.setEnabled(False) self.deleteItem.setEnabled(True) self.markCompletedItem.setEnabled(True) self.copyItem.setEnabled(True) self.subtaskItem.setEnabled(True) if self.copyTask: self.pasteItem.setEnabled(True) self.pasteMainItem.setEnabled(True) else: self.pasteItem.setEnabled(False) self.pasteMainItem.setEnabled(False) self.__menu.popup(coord) def setProjectOpen(self, o=False): """ Public slot to set the project status. @param o flag indicating the project status """ self.projectOpen = o def addTask(self, summary, priority=1, filename="", lineno=0, completed=False, _time=0, isProjectTask=False, taskType=Task.TypeTodo, description="", uid="", parentTask=None): """ Public slot to add a task. @param summary summary text of the task (string) @param priority priority of the task (0=high, 1=normal, 2=low) @param filename filename containing the task (string) @param lineno line number containing the task (integer) @param completed flag indicating completion status (boolean) @param _time creation time of the task (float, if 0 use current time) @param isProjectTask flag indicating a task related to the current project (boolean) @param taskType type of the task (one of Task.TypeFixme, Task.TypeTodo, Task.TypeWarning, Task.TypeNote) @param description explanatory text of the task (string) @param uid unique id of the task (string) @param parentTask reference to the parent task item (Task) @return reference to the task item (Task) """ if parentTask: parentUid = parentTask.getUuid() else: parentUid = "" task = Task(summary, priority, filename, lineno, completed, _time, isProjectTask, taskType, self.project, description, uid, parentUid) self.tasks.append(task) if parentTask: parentTask.addChild(task) parentTask.setExpanded(True) elif filename: self.__extractedItem.addChild(task) else: self.__manualItem.addChild(task) task.setHidden(not self.taskFilter.showTask(task)) self.__checkTopLevelItems() self.__resort() self.__resizeColumns() if isProjectTask: self.__projectTasksSaveTimer.changeOccurred() return task def addFileTask(self, summary, filename, lineno, taskType=Task.TypeTodo, description=""): """ Public slot to add a file related task. @param summary summary text of the task (string) @param filename filename containing the task (string) @param lineno line number containing the task (integer) @param taskType type of the task (one of Task.TypeFixme, Task.TypeTodo, Task.TypeWarning, Task.TypeNote) @param description explanatory text of the task (string) """ self.addTask(summary, filename=filename, lineno=lineno, isProjectTask=( self.project and self.project.isProjectSource(filename)), taskType=taskType, description=description) def getProjectTasks(self): """ Public method to retrieve all project related tasks. @return copy of tasks (list of Task) """ tasks = [task for task in self.tasks if task.isProjectTask()] return tasks[:] def getGlobalTasks(self): """ Public method to retrieve all non project related tasks. @return copy of tasks (list of Task) """ tasks = [task for task in self.tasks if not task.isProjectTask()] return tasks[:] def clearTasks(self): """ Public slot to clear all tasks from display. """ self.tasks = [] self.clear() self.__generateTopLevelItems() def clearProjectTasks(self, fileOnly=False): """ Public slot to clear project related tasks. @keyparam fileOnly flag indicating to clear only file related project tasks (boolean) """ for task in reversed(self.tasks[:]): if (fileOnly and task.isProjectFileTask()) or \ (not fileOnly and task.isProjectTask()): if self.copyTask == task: self.copyTask = None parent = task.parent() parent.removeChild(task) self.tasks.remove(task) del task self.__checkTopLevelItems() self.__resort() self.__resizeColumns() def clearFileTasks(self, filename, conditionally=False): """ Public slot to clear all tasks related to a file. @param filename name of the file (string) @param conditionally flag indicating to clear the tasks of the file checking some conditions (boolean) """ if conditionally: if self.project and self.project.isProjectSource(filename): # project related tasks will not be cleared return if not Preferences.getTasks("ClearOnFileClose"): return for task in self.tasks[:]: if task.getFilename() == filename: if self.copyTask == task: self.copyTask = None self.__extractedItem.removeChild(task) self.tasks.remove(task) if task.isProjectTask: self.__projectTasksSaveTimer.changeOccurred() del task self.__checkTopLevelItems() self.__resort() self.__resizeColumns() def __editTaskProperties(self): """ Private slot to handle the "Properties" context menu entry. """ from .TaskPropertiesDialog import TaskPropertiesDialog task = self.currentItem() dlg = TaskPropertiesDialog(task, self, self.projectOpen) ro = task.getFilename() != "" if ro: dlg.setReadOnly() if dlg.exec_() == QDialog.Accepted and not ro: summary, priority, completed, isProjectTask, description = \ dlg.getData() task.setSummary(summary) task.setPriority(priority) task.setCompleted(completed) task.setProjectTask(isProjectTask) task.setDescription(description) self.__projectTasksSaveTimer.changeOccurred() def __newTask(self): """ Private slot to handle the "New Task" context menu entry. """ from .TaskPropertiesDialog import TaskPropertiesDialog dlg = TaskPropertiesDialog(None, self, self.projectOpen) if dlg.exec_() == QDialog.Accepted: summary, priority, completed, isProjectTask, description = \ dlg.getData() self.addTask(summary, priority, completed=completed, isProjectTask=isProjectTask, description=description) def __newSubTask(self): """ Private slot to handle the "New Sub-Task" context menu entry. """ parentTask = self.currentItem() projectTask = parentTask.isProjectTask() from .TaskPropertiesDialog import TaskPropertiesDialog dlg = TaskPropertiesDialog(None, self, self.projectOpen) dlg.setSubTaskMode(projectTask) if dlg.exec_() == QDialog.Accepted: summary, priority, completed, isProjectTask, description = \ dlg.getData() self.addTask(summary, priority, completed=completed, isProjectTask=isProjectTask, description=description, parentTask=parentTask) def __markCompleted(self): """ Private slot to handle the "Mark Completed" context menu entry. """ task = self.currentItem() task.setCompleted(True) def __deleteCompleted(self): """ Private slot to handle the "Delete Completed Tasks" context menu entry. """ for task in reversed(self.tasks[:]): if task.isCompleted(): if self.copyTask == task: self.copyTask = None parent = task.parent() parent.removeChild(task) self.tasks.remove(task) if task.isProjectTask: self.__projectTasksSaveTimer.changeOccurred() del task self.__checkTopLevelItems() self.__resort() self.__resizeColumns() ci = self.currentItem() if ci: ind = self.indexFromItem(ci, self.currentColumn()) self.scrollTo(ind, QAbstractItemView.PositionAtCenter) def __copyTask(self): """ Private slot to handle the "Copy" context menu entry. """ task = self.currentItem() self.copyTask = task def __pasteTask(self): """ Private slot to handle the "Paste" context menu entry. """ if self.copyTask: parent = self.copyTask.parent() if not isinstance(parent, Task): parent = None self.addTask(self.copyTask.summary, priority=self.copyTask.priority, completed=self.copyTask.completed, description=self.copyTask.description, isProjectTask=self.copyTask._isProjectTask, parentTask=parent) def __pasteMainTask(self): """ Private slot to handle the "Paste as Main Task" context menu entry. """ if self.copyTask: self.addTask(self.copyTask.summary, priority=self.copyTask.priority, completed=self.copyTask.completed, description=self.copyTask.description, isProjectTask=self.copyTask._isProjectTask) def __deleteSubTasks(self, task): """ Private method to delete all sub-tasks. @param task task to delete sub-tasks of (Task) """ for subtask in task.takeChildren(): if self.copyTask == subtask: self.copyTask = None if subtask.childCount() > 0: self.__deleteSubTasks(subtask) self.tasks.remove(subtask) def __deleteTask(self): """ Private slot to handle the "Delete Task" context menu entry. """ task = self.currentItem() if self.copyTask == task: self.copyTask = None if task.childCount() > 0: self.__deleteSubTasks(task) parent = task.parent() parent.removeChild(task) self.tasks.remove(task) if task.isProjectTask: self.__projectTasksSaveTimer.changeOccurred() del task self.__checkTopLevelItems() self.__resort() self.__resizeColumns() ci = self.currentItem() if ci: ind = self.indexFromItem(ci, self.currentColumn()) self.scrollTo(ind, QAbstractItemView.PositionAtCenter) def __goToTask(self): """ Private slot to handle the "Go To" context menu entry. """ task = self.currentItem() self.displayFile.emit(task.getFilename(), task.getLineno()) def handlePreferencesChanged(self): """ Public slot to react to changes of the preferences. """ for task in self.tasks: task.colorizeTask() def __activateFilter(self, on): """ Private slot to handle the "Filtered display" context menu entry. @param on flag indicating the filter state (boolean) """ if on and not self.taskFilter.hasActiveFilter(): res = E5MessageBox.yesNo( self, self.tr("Activate task filter"), self.tr( """The task filter doesn't have any active filters.""" """ Do you want to configure the filter settings?"""), yesDefault=True) if not res: on = False else: self.__configureFilter() on = self.taskFilter.hasActiveFilter() self.taskFilter.setActive(on) self.__menuFilteredAct.setChecked(on) self.__backMenuFilteredAct.setChecked(on) self.__refreshDisplay() def __configureFilter(self): """ Private slot to handle the "Configure filter" context menu entry. """ from .TaskFilterConfigDialog import TaskFilterConfigDialog dlg = TaskFilterConfigDialog(self.taskFilter) if dlg.exec_() == QDialog.Accepted: dlg.configureTaskFilter(self.taskFilter) self.__refreshDisplay() def __configureProjectTasksScanOptions(self): """ Private slot to configure scan options for project tasks. """ filter, ok = QInputDialog.getText( self, self.tr("Scan Filter Patterns"), self.tr("Enter filename patterns of files" " to be excluded separated by a comma:"), QLineEdit.Normal, self.projectTasksScanFilter) if ok: self.projectTasksScanFilter = filter def __regenerateProjectTasks(self): """ Private slot to handle the "Regenerated project tasks" context menu entry. """ markers = { Task.TypeWarning: Preferences.getTasks("TasksWarningMarkers").split(), Task.TypeNote: Preferences.getTasks("TasksNoteMarkers").split(), Task.TypeTodo: Preferences.getTasks("TasksTodoMarkers").split(), Task.TypeFixme: Preferences.getTasks("TasksFixmeMarkers").split(), } files = self.project.pdata["SOURCES"] # apply file filter filterList = [f.strip() for f in self.projectTasksScanFilter.split(",") if f.strip()] if filterList: for filter in filterList: files = [f for f in files if not fnmatch.fnmatch(f, filter)] # remove all project tasks self.clearProjectTasks(fileOnly=True) # now process them progress = E5ProgressDialog( self.tr("Extracting project tasks..."), self.tr("Abort"), 0, len(files), self.tr("%v/%m Files")) progress.setMinimumDuration(0) progress.setWindowTitle(self.tr("Tasks")) count = 0 for file in files: progress.setLabelText( self.tr("Extracting project tasks...\n{0}").format(file)) progress.setValue(count) QApplication.processEvents() if progress.wasCanceled(): break fn = os.path.join(self.project.ppath, file) # read the file and split it into textlines try: text, encoding = Utilities.readEncodedFile(fn) lines = text.splitlines() except (UnicodeError, IOError): count += 1 progress.setValue(count) continue # now search tasks and record them lineIndex = 0 for line in lines: lineIndex += 1 shouldBreak = False for taskType, taskMarkers in markers.items(): for taskMarker in taskMarkers: index = line.find(taskMarker) if index > -1: task = line[index:] self.addFileTask(task, fn, lineIndex, taskType) shouldBreak = True break if shouldBreak: break count += 1 progress.setValue(len(files)) def __configure(self): """ Private method to open the configuration dialog. """ e5App().getObject("UserInterface").showPreferences("tasksPage") def saveProjectTasks(self): """ Public method to write the project tasks. """ if self.projectOpen and Preferences.getTasks("TasksProjectAutoSave"): self.project.writeTasks()
class CookieJar(QNetworkCookieJar): """ Class implementing a QNetworkCookieJar subclass with various accept policies. @signal cookiesChanged() emitted after the cookies have been changed """ cookiesChanged = pyqtSignal() JAR_VERSION = 23 AcceptAlways = 0 AcceptNever = 1 AcceptOnlyFromSitesNavigatedTo = 2 KeepUntilExpire = 0 KeepUntilExit = 1 KeepUntilTimeLimit = 2 Allow = 0 Block = 1 AllowForSession = 2 def __init__(self, parent=None): """ Constructor @param parent reference to the parent object (QObject) """ super(CookieJar, self).__init__(parent) self.__loaded = False self.__acceptCookies = self.AcceptOnlyFromSitesNavigatedTo self.__saveTimer = AutoSaver(self, self.save) self.__cookiesFile = os.path.join(Utilities.getConfigDir(), "browser", "cookies.ini") def saveCookies(self, cookiesList): """ Public method to save the cookies. @param cookiesList list of cookies to be saved @return saved cookies as a byte array (QByteArray) """ data = QByteArray() stream = QDataStream(data, QIODevice.WriteOnly) stream.setVersion(QDataStream.Qt_4_6) stream.writeUInt16(self.JAR_VERSION) stream.writeUInt32(len(cookiesList)) for cookie in cookiesList: stream << cookie.toRawForm() return data def loadCookies(self, cookies): """ Public method to restore the saved cookies. @param cookies byte array containing the saved cookies (QByteArray) @return list of cookies """ if cookies.isEmpty(): return [] cookiesList = [] data = QByteArray(cookies) stream = QDataStream(data, QIODevice.ReadOnly) stream.setVersion(QDataStream.Qt_4_6) version = stream.readUInt16() if version != self.JAR_VERSION: return [] stream.readUInt32() # number of cookies rawCookie = QByteArray() while not stream.atEnd(): stream >> rawCookie newCookies = QNetworkCookie.parseCookies(rawCookie) for newCookie in newCookies: cookiesList.append(newCookie) return cookiesList def close(self): """ Public slot to close the cookie jar. """ if self.__loaded and self.__keepCookies == self.KeepUntilExit: self.clear() self.__saveTimer.saveIfNeccessary() def clear(self): """ Public method to clear all cookies. """ if not self.__loaded: self.load() self.setAllCookies([]) self.__saveTimer.changeOccurred() self.cookiesChanged.emit() def load(self): """ Public method to load the cookies. """ if self.__loaded: return cookieSettings = QSettings(self.__cookiesFile, QSettings.IniFormat) # load cookies cookies = cookieSettings.value("Cookies") if cookies: cookiesList = self.loadCookies(cookies) else: cookiesList = [] self.setAllCookies(cookiesList) # load exceptions self.__exceptionsBlock = Preferences.toList( cookieSettings.value("Exceptions/block")) self.__exceptionsAllow = Preferences.toList( cookieSettings.value("Exceptions/allow")) self.__exceptionsAllowForSession = Preferences.toList( cookieSettings.value("Exceptions/allowForSession")) self.__exceptionsBlock.sort() self.__exceptionsAllow.sort() self.__exceptionsAllowForSession.sort() self.__acceptCookies = Preferences.getHelp("AcceptCookies") self.__keepCookies = Preferences.getHelp("KeepCookiesUntil") if self.__keepCookies == self.KeepUntilExit: self.setAllCookies([]) self.__filterTrackingCookies = Preferences.toBool( Preferences.getHelp("FilterTrackingCookies")) self.__loaded = True self.cookiesChanged.emit() def save(self): """ Public method to save the cookies. """ if not self.__loaded: return self.__purgeOldCookies() cookieSettings = QSettings(self.__cookiesFile, QSettings.IniFormat) cookiesList = self.allCookies() for index in range(len(cookiesList) - 1, -1, -1): if cookiesList[index].isSessionCookie(): del cookiesList[index] cookies = self.saveCookies(cookiesList) cookieSettings.setValue("Cookies", cookies) cookieSettings.setValue("Exceptions/block", self.__exceptionsBlock) cookieSettings.setValue("Exceptions/allow", self.__exceptionsAllow) cookieSettings.setValue("Exceptions/allowForSession", self.__exceptionsAllowForSession) Preferences.setHelp("AcceptCookies", self.__acceptCookies) Preferences.setHelp("KeepCookiesUntil", self.__keepCookies) Preferences.setHelp("FilterTrackingCookies", self.__filterTrackingCookies) def __purgeOldCookies(self): """ Private method to purge old cookies. """ cookies = self.allCookies() if len(cookies) == 0: return oldCount = len(cookies) now = QDateTime.currentDateTime() for index in range(len(cookies) - 1, -1, -1): if not cookies[index].isSessionCookie() and \ cookies[index].expirationDate() < now: del cookies[index] if oldCount == len(cookies): return self.setAllCookies(cookies) self.cookiesChanged.emit() def cookiesForUrl(self, url): """ Public method to get the cookies for a URL. @param url URL to get cookies for (QUrl) @return list of cookies (list of QNetworkCookie) """ if not self.__loaded: self.load() globalSettings = QWebSettings.globalSettings() if globalSettings.testAttribute(QWebSettings.PrivateBrowsingEnabled): return [] return QNetworkCookieJar.cookiesForUrl(self, url) def setCookiesFromUrl(self, cookieList, url): """ Public method to set cookies for a URL. @param cookieList list of cookies to set (list of QNetworkCookie) @param url url to set cookies for (QUrl) @return flag indicating cookies were set (boolean) """ if not self.__loaded: self.load() globalSettings = QWebSettings.globalSettings() if globalSettings.testAttribute(QWebSettings.PrivateBrowsingEnabled): return False host = url.host() eBlock = self.__isOnDomainList(self.__exceptionsBlock, host) eAllow = not eBlock and \ self.__isOnDomainList(self.__exceptionsAllow, host) eAllowSession = not eBlock and \ not eAllow and \ self.__isOnDomainList( self.__exceptionsAllowForSession, host) addedCookies = False acceptInitially = self.__acceptCookies != self.AcceptNever if (acceptInitially and not eBlock) or \ (not acceptInitially and (eAllow or eAllowSession)): # url domain == cookie domain soon = QDateTime.currentDateTime() soon = soon.addDays(90) for cookie in cookieList: lst = [] if not (self.__filterTrackingCookies and cookie.name().startsWith(b"__utm")): if eAllowSession: cookie.setExpirationDate(QDateTime()) if self.__keepCookies == self.KeepUntilTimeLimit and \ not cookie.isSessionCookie and \ cookie.expirationDate() > soon: cookie.setExpirationDate(soon) lst.append(cookie) if QNetworkCookieJar.setCookiesFromUrl(self, lst, url): addedCookies = True elif self.__acceptCookies == self.AcceptAlways: # force it in if wanted cookies = self.allCookies() for ocookie in cookies[:]: # does the cookie exist already? if cookie.name() == ocookie.name() and \ cookie.domain() == ocookie.domain() and \ cookie.path() == ocookie.path(): # found a match cookies.remove(ocookie) cookies.append(cookie) self.setAllCookies(cookies) addedCookies = True if addedCookies: self.__saveTimer.changeOccurred() self.cookiesChanged.emit() return addedCookies def acceptPolicy(self): """ Public method to get the accept policy. @return current accept policy """ if not self.__loaded: self.load() return self.__acceptCookies def setAcceptPolicy(self, policy): """ Public method to set the accept policy. @param policy accept policy to be set """ if not self.__loaded: self.load() if policy > self.AcceptOnlyFromSitesNavigatedTo: return if policy == self.__acceptCookies: return self.__acceptCookies = policy self.__saveTimer.changeOccurred() def keepPolicy(self): """ Public method to get the keep policy. @return keep policy """ if not self.__loaded: self.load() return self.__keepCookies def setKeepPolicy(self, policy): """ Public method to set the keep policy. @param policy keep policy to be set """ if not self.__loaded: self.load() if policy > self.KeepUntilTimeLimit: return if policy == self.__keepCookies: return self.__keepCookies = policy self.__saveTimer.changeOccurred() def blockedCookies(self): """ Public method to return the blocked cookies. @return list of blocked cookies (list of strings) """ if not self.__loaded: self.load() return self.__exceptionsBlock def allowedCookies(self): """ Public method to return the allowed cookies. @return list of allowed cookies (list of strings) """ if not self.__loaded: self.load() return self.__exceptionsAllow def allowForSessionCookies(self): """ Public method to return the allowed session cookies. @return list of allowed session cookies (list of strings) """ if not self.__loaded: self.load() return self.__exceptionsAllowForSession def setBlockedCookies(self, list_): """ Public method to set the list of blocked cookies. @param list_ list of blocked cookies (list of strings) """ if not self.__loaded: self.load() self.__exceptionsBlock = list_[:] self.__exceptionsBlock.sort() self.__applyRules() self.__saveTimer.changeOccurred() def setAllowedCookies(self, list_): """ Public method to set the list of allowed cookies. @param list_ list of allowed cookies (list of strings) """ if not self.__loaded: self.load() self.__exceptionsAllow = list_[:] self.__exceptionsAllow.sort() self.__applyRules() self.__saveTimer.changeOccurred() def setAllowForSessionCookies(self, list_): """ Public method to set the list of allowed session cookies. @param list_ list of allowed session cookies (list of strings) """ if not self.__loaded: self.load() self.__exceptionsAllowForSession = list_[:] self.__exceptionsAllowForSession.sort() self.__applyRules() self.__saveTimer.changeOccurred() def filterTrackingCookies(self): """ Public method to get the filter tracking cookies flag. @return filter tracking cookies flag (boolean) """ return self.__filterTrackingCookies def setFilterTrackingCookies(self, filterTrackingCookies): """ Public method to set the filter tracking cookies flag. @param filterTrackingCookies filter tracking cookies flag (boolean) """ if filterTrackingCookies == self.__filterTrackingCookies: return self.__filterTrackingCookies = filterTrackingCookies self.__saveTimer.changeOccurred() def __isOnDomainList(self, rules, domain): """ Private method to check, if either the rule matches the domain exactly or the domain ends with ".rule". @param rules list of rules (list of strings) @param domain domain name to check (string) @return flag indicating a match (boolean) """ for rule in rules: if rule.startswith("."): if domain.endswith(rule): return True withoutDot = rule[1:] if domain == withoutDot: return True else: domainEnding = domain[-(len(rule) + 1):] if domainEnding and \ domainEnding[0] == "." and \ domain.endswith(rule): return True if rule == domain: return True return False def __applyRules(self): """ Private method to apply the cookie rules. """ cookiesList = self.allCookies() changed = False for index in range(len(cookiesList) - 1, -1, -1): cookie = cookiesList[index] if self.__isOnDomainList(self.__exceptionsBlock, cookie.domain()): del cookiesList[index] changed = True elif self.__isOnDomainList(self.__exceptionsAllowForSession, cookie.domain()): cookie.setExpirationDate(QDateTime()) changed = True if changed: self.setAllCookies(cookiesList) self.__saveTimer.changeOccurred() self.cookiesChanged.emit() def cookies(self): """ Public method to get the cookies of the cookie jar. @return list of all cookies (list of QNetworkCookie) """ if not self.__loaded: self.load() return self.allCookies() def setCookies(self, cookies): """ Public method to set all cookies. @param cookies list of cookies to be set (list of QNetworkCookie) """ if not self.__loaded: self.load() self.setAllCookies(cookies) self.__saveTimer.changeOccurred() self.cookiesChanged.emit()
class BookmarksManager(QObject): """ Class implementing the bookmarks manager. @signal entryAdded emitted after a bookmark node has been added @signal entryRemoved emitted after a bookmark node has been removed @signal entryChanged emitted after a bookmark node has been changed """ def __init__(self, parent = None): """ Constructor @param parent reference to the parent object (QObject) """ QObject.__init__(self, parent) self.__loaded = False self.__saveTimer = AutoSaver(self, self.save) self.__bookmarkRootNode = None self.__toolbar = None self.__menu = None self.__bookmarksModel = None self.__commands = QUndoStack() self.connect(self, SIGNAL("entryAdded"), self.__saveTimer.changeOccurred) self.connect(self, SIGNAL("entryRemoved"), self.__saveTimer.changeOccurred) self.connect(self, SIGNAL("entryChanged"), self.__saveTimer.changeOccurred) def close(self): """ Public method to close the bookmark manager. """ self.__saveTimer.saveIfNeccessary() def undoRedoStack(self): """ Public method to get a reference to the undo stack. @return reference to the undo stack (QUndoStack) """ return self.__commands def changeExpanded(self): """ Public method to handle a change of the expanded state. """ self.__saveTimer.changeOccurred() def load(self): """ Public method to load the bookmarks. """ if self.__loaded: return self.__loaded = True bookmarkFile = os.path.join(Utilities.getConfigDir(), "browser", "bookmarks.xbel") if not QFile.exists(bookmarkFile): ba = QByteArray(DefaultBookmarks) bookmarkFile = QBuffer(ba) bookmarkFile.open(QIODevice.ReadOnly) reader = XbelReader() self.__bookmarkRootNode = reader.read(bookmarkFile) if reader.error() != QXmlStreamReader.NoError: KQMessageBox.warning(None, self.trUtf8("Loading Bookmarks"), self.trUtf8("""Error when loading bookmarks on line %1, column %2:\n""" """%3""")\ .arg(reader.lineNumber())\ .arg(reader.columnNumber())\ .arg(reader.errorString())) others = [] for index in range(len(self.__bookmarkRootNode.children()) - 1, -1, -1): node = self.__bookmarkRootNode.children()[index] if node.type() == BookmarkNode.Folder: if (node.title == self.trUtf8("Toolbar Bookmarks") or \ node.title == BOOKMARKBAR) and \ self.__toolbar is None: node.title = self.trUtf8(BOOKMARKBAR) self.__toolbar = node if (node.title == self.trUtf8("Menu") or \ node.title == BOOKMARKMENU) and \ self.__menu is None: node.title = self.trUtf8(BOOKMARKMENU) self.__menu = node else: others.append(node) self.__bookmarkRootNode.remove(node) if len(self.__bookmarkRootNode.children()) > 0: raise RuntimeError("Error loading bookmarks.") if self.__toolbar is None: self.__toolbar = BookmarkNode(BookmarkNode.Folder, self.__bookmarkRootNode) self.__toolbar.title = self.trUtf8(BOOKMARKBAR) else: self.__bookmarkRootNode.add(self.__toolbar) if self.__menu is None: self.__menu = BookmarkNode(BookmarkNode.Folder, self.__bookmarkRootNode) self.__menu.title = self.trUtf8(BOOKMARKMENU) else: self.__bookmarkRootNode.add(self.__menu) for node in others: self.__menu.add(node) self.__convertFromOldBookmarks() def save(self): """ Public method to save the bookmarks. """ if not self.__loaded: return writer = XbelWriter() bookmarkFile = os.path.join(Utilities.getConfigDir(), "browser", "bookmarks.xbel") # save root folder titles in English (i.e. not localized) self.__menu.title = BOOKMARKMENU self.__toolbar.title = BOOKMARKBAR if not writer.write(bookmarkFile, self.__bookmarkRootNode): KQMessageBox.warning(None, self.trUtf8("Saving Bookmarks"), self.trUtf8("""Error saving bookmarks to <b>%1</b>.""").arg(bookmarkFile)) # restore localized titles self.__menu.title = self.trUtf8(BOOKMARKMENU) self.__toolbar.title = self.trUtf8(BOOKMARKBAR) def addBookmark(self, parent, node, row = -1): """ Public method to add a bookmark. @param parent reference to the node to add to (BookmarkNode) @param node reference to the node to add (BookmarkNode) @param row row number (integer) """ if not self.__loaded: return command = InsertBookmarksCommand(self, parent, node, row) self.__commands.push(command) def removeBookmark(self, node): """ Public method to remove a bookmark. @param node reference to the node to be removed (BookmarkNode) """ if not self.__loaded: return parent = node.parent() row = parent.children().index(node) command = RemoveBookmarksCommand(self, parent, row) self.__commands.push(command) def setTitle(self, node, newTitle): """ Public method to set the title of a bookmark. @param node reference to the node to be changed (BookmarkNode) @param newTitle title to be set (QString) """ if not self.__loaded: return command = ChangeBookmarkCommand(self, node, newTitle, True) self.__commands.push(command) def setUrl(self, node, newUrl): """ Public method to set the URL of a bookmark. @param node reference to the node to be changed (BookmarkNode) @param newUrl URL to be set (QString) """ if not self.__loaded: return command = ChangeBookmarkCommand(self, node, newUrl, False) self.__commands.push(command) def bookmarks(self): """ Public method to get a reference to the root bookmark node. @return reference to the root bookmark node (BookmarkNode) """ if not self.__loaded: self.load() return self.__bookmarkRootNode def menu(self): """ Public method to get a reference to the bookmarks menu node. @return reference to the bookmarks menu node (BookmarkNode) """ if not self.__loaded: self.load() return self.__menu def toolbar(self): """ Public method to get a reference to the bookmarks toolbar node. @return reference to the bookmarks toolbar node (BookmarkNode) """ if not self.__loaded: self.load() return self.__toolbar def bookmarksModel(self): """ Public method to get a reference to the bookmarks model. @return reference to the bookmarks model (BookmarksModel) """ if self.__bookmarksModel is None: self.__bookmarksModel = BookmarksModel(self, self) return self.__bookmarksModel def importBookmarks(self): """ Public method to import bookmarks. """ supportedFormats = QStringList() \ << self.trUtf8("XBEL bookmarks").append(" (*.xbel *.xml)") \ << self.trUtf8("HTML Netscape bookmarks").append(" (*.html *.htm)") fileName = KQFileDialog.getOpenFileName(\ None, self.trUtf8("Import Bookmarks"), QString(), supportedFormats.join(";;"), None) if fileName.isEmpty(): return reader = XbelReader() importRootNode = None if fileName.endsWith(".html"): inFile = QFile(fileName) inFile.open(QIODevice.ReadOnly) if inFile.openMode == QIODevice.NotOpen: KQMessageBox.warning(None, self.trUtf8("Import Bookmarks"), self.trUtf8("""Error opening bookmarks file <b>%1</b>.""")\ .arg(fileName)) return webpage = QWebPage() webpage.mainFrame().setHtml(QString(inFile.readAll())) result = webpage.mainFrame().evaluateJavaScript(extract_js).toByteArray() buffer_ = QBuffer(result) buffer_.open(QIODevice.ReadOnly) importRootNode = reader.read(buffer_) else: importRootNode = reader.read(fileName) if reader.error() != QXmlStreamReader.NoError: KQMessageBox.warning(None, self.trUtf8("Import Bookmarks"), self.trUtf8("""Error when importing bookmarks on line %1, column %2:\n""" """%3""")\ .arg(reader.lineNumber())\ .arg(reader.columnNumber())\ .arg(reader.errorString())) return importRootNode.setType(BookmarkNode.Folder) importRootNode.title = self.trUtf8("Imported %1")\ .arg(QDate.currentDate().toString(Qt.SystemLocaleShortDate)) self.addBookmark(self.menu(), importRootNode) def exportBookmarks(self): """ Public method to export the bookmarks. """ fileName = KQFileDialog.getSaveFileName(\ None, self.trUtf8("Export Bookmarks"), "eric4_bookmarks.xbel", self.trUtf8("XBEL bookmarks").append(" (*.xbel *.xml)")) if fileName.isEmpty(): return writer = XbelWriter() if not writer.write(fileName, self.__bookmarkRootNode): KQMessageBox.critical(None, self.trUtf8("Exporting Bookmarks"), self.trUtf8("""Error exporting bookmarks to <b>%1</b>.""")\ .arg(fileName)) def __convertFromOldBookmarks(self): """ Private method to convert the old bookmarks into the new ones. """ bmNames = Preferences.Prefs.settings.value('Bookmarks/Names') bmFiles = Preferences.Prefs.settings.value('Bookmarks/Files') if bmNames.isValid() and bmFiles.isValid(): bmNames = bmNames.toStringList() bmFiles = bmFiles.toStringList() if len(bmNames) == len(bmFiles): convertedRootNode = BookmarkNode(BookmarkNode.Folder) convertedRootNode.title = self.trUtf8("Converted %1")\ .arg(QDate.currentDate().toString(Qt.SystemLocaleShortDate)) for i in range(len(bmNames)): node = BookmarkNode(BookmarkNode.Bookmark, convertedRootNode) node.title = bmNames[i] url = QUrl(bmFiles[i]) if url.scheme().isEmpty(): url.setScheme("file") node.url = url.toString() self.addBookmark(self.menu(), convertedRootNode) Preferences.Prefs.settings.remove('Bookmarks')
class DownloadManager(QDialog, Ui_DownloadManager): """ Class implementing the download manager. """ RemoveNever = 0 RemoveExit = 1 RemoveSuccessFullDownload = 2 def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget (QWidget) """ super(DownloadManager, self).__init__(parent) self.setupUi(self) self.__saveTimer = AutoSaver(self, self.save) self.__model = DownloadModel(self) self.__manager = Helpviewer.HelpWindow.HelpWindow\ .networkAccessManager() self.__iconProvider = None self.__downloads = [] self.__downloadDirectory = "" self.__loaded = False self.setDownloadDirectory(Preferences.getUI("DownloadPath")) self.downloadsView.setShowGrid(False) self.downloadsView.verticalHeader().hide() self.downloadsView.horizontalHeader().hide() self.downloadsView.setAlternatingRowColors(True) self.downloadsView.horizontalHeader().setStretchLastSection(True) self.downloadsView.setModel(self.__model) self.downloadsView.setContextMenuPolicy(Qt.CustomContextMenu) self.downloadsView.customContextMenuRequested.connect( self.__customContextMenuRequested) self.__load() def __customContextMenuRequested(self, pos): """ Private slot to handle the context menu request for the bookmarks tree. @param pos position the context menu was requested (QPoint) """ menu = QMenu() selectedRowsCount = len( self.downloadsView.selectionModel().selectedRows()) if selectedRowsCount == 1: row = self.downloadsView.selectionModel().selectedRows()[0].row() itm = self.__downloads[row] if itm.downloadCanceled(): menu.addAction( UI.PixmapCache.getIcon("restart.png"), self.tr("Retry"), self.__contextMenuRetry) else: if itm.downloadedSuccessfully(): menu.addAction( UI.PixmapCache.getIcon("open.png"), self.tr("Open"), self.__contextMenuOpen) elif itm.downloading(): menu.addAction( UI.PixmapCache.getIcon("stopLoading.png"), self.tr("Cancel"), self.__contextMenuCancel) menu.addSeparator() menu.addAction( self.tr("Open Containing Folder"), self.__contextMenuOpenFolder) menu.addSeparator() menu.addAction( self.tr("Go to Download Page"), self.__contextMenuGotoPage) menu.addAction( self.tr("Copy Download Link"), self.__contextMenuCopyLink) menu.addSeparator() menu.addAction(self.tr("Select All"), self.__contextMenuSelectAll) if selectedRowsCount > 1 or \ (selectedRowsCount == 1 and not self.__downloads[ self.downloadsView.selectionModel().selectedRows()[0].row()] .downloading()): menu.addSeparator() menu.addAction( self.tr("Remove From List"), self.__contextMenuRemoveSelected) menu.exec_(QCursor.pos()) def shutdown(self): """ Public method to stop the download manager. """ self.__saveTimer.changeOccurred() self.__saveTimer.saveIfNeccessary() self.close() def activeDownloads(self): """ Public method to get the number of active downloads. @return number of active downloads (integer) """ count = 0 for download in self.__downloads: if download.downloading(): count += 1 return count def allowQuit(self): """ Public method to check, if it is ok to quit. @return flag indicating allowance to quit (boolean) """ if self.activeDownloads() > 0: res = E5MessageBox.yesNo( self, self.tr(""), self.tr("""There are %n downloads in progress.\n""" """Do you want to quit anyway?""", "", self.activeDownloads()), icon=E5MessageBox.Warning) if not res: self.show() return False return True def download(self, requestOrUrl, requestFileName=False, mainWindow=None): """ Public method to download a file. @param requestOrUrl reference to a request object (QNetworkRequest) or a URL to be downloaded (QUrl) @keyparam requestFileName flag indicating to ask for the download file name (boolean) @keyparam mainWindow reference to the main window (HelpWindow) """ request = QNetworkRequest(requestOrUrl) if request.url().isEmpty(): return self.handleUnsupportedContent( self.__manager.get(request), requestFileName=requestFileName, download=True, mainWindow=mainWindow) def handleUnsupportedContent(self, reply, requestFileName=False, webPage=None, download=False, mainWindow=None): """ Public method to handle unsupported content by downloading the referenced resource. @param reply reference to the reply object (QNetworkReply) @keyparam requestFileName indicating to ask for a filename (boolean) @keyparam webPage reference to the web page (HelpWebPage) @keyparam download flag indicating a download request (boolean) @keyparam mainWindow reference to the main window (HelpWindow) """ if reply is None or reply.url().isEmpty(): return size = reply.header(QNetworkRequest.ContentLengthHeader) if size == 0: return from .DownloadItem import DownloadItem itm = DownloadItem( reply=reply, requestFilename=requestFileName, webPage=webPage, download=download, parent=self, mainWindow=mainWindow) self.__addItem(itm) if itm.canceledFileSelect(): return if not self.isVisible(): self.show() self.activateWindow() self.raise_() def __addItem(self, itm): """ Private method to add a download to the list of downloads. @param itm reference to the download item (DownloadItem) """ itm.statusChanged.connect(self.__updateRow) itm.downloadFinished.connect(self.__finished) row = len(self.__downloads) self.__model.beginInsertRows(QModelIndex(), row, row) self.__downloads.append(itm) self.__model.endInsertRows() self.downloadsView.setIndexWidget(self.__model.index(row, 0), itm) icon = self.style().standardIcon(QStyle.SP_FileIcon) itm.setIcon(icon) self.downloadsView.setRowHeight(row, itm.sizeHint().height() * 1.5) # just in case the download finished before the constructor returned self.__updateRow(itm) self.changeOccurred() self.__updateActiveItemCount() def __updateRow(self, itm=None): """ Private slot to update a download item. @param itm reference to the download item (DownloadItem) """ if itm is None: itm = self.sender() if itm not in self.__downloads: return row = self.__downloads.index(itm) if self.__iconProvider is None: self.__iconProvider = QFileIconProvider() icon = self.__iconProvider.icon(QFileInfo(itm.fileName())) if icon.isNull(): icon = self.style().standardIcon(QStyle.SP_FileIcon) itm.setIcon(icon) oldHeight = self.downloadsView.rowHeight(row) self.downloadsView.setRowHeight( row, max(oldHeight, itm.minimumSizeHint().height() * 1.5)) remove = False globalSettings = QWebSettings.globalSettings() if not itm.downloading() and \ globalSettings.testAttribute(QWebSettings.PrivateBrowsingEnabled): remove = True if itm.downloadedSuccessfully() and \ self.removePolicy() == DownloadManager.RemoveSuccessFullDownload: remove = True if remove: self.__model.removeRow(row) self.cleanupButton.setEnabled( (len(self.__downloads) - self.activeDownloads()) > 0) # record the change self.changeOccurred() def removePolicy(self): """ Public method to get the remove policy. @return remove policy (integer) """ return Preferences.getHelp("DownloadManagerRemovePolicy") def setRemovePolicy(self, policy): """ Public method to set the remove policy. @param policy policy to be set (DownloadManager.RemoveExit, DownloadManager.RemoveNever, DownloadManager.RemoveSuccessFullDownload) """ assert policy in (DownloadManager.RemoveExit, DownloadManager.RemoveNever, DownloadManager.RemoveSuccessFullDownload) if policy == self.removePolicy(): return Preferences.setHelp("DownloadManagerRemovePolicy", self.policy) def save(self): """ Public method to save the download settings. """ if not self.__loaded: return Preferences.setHelp("DownloadManagerSize", self.size()) Preferences.setHelp("DownloadManagerPosition", self.pos()) if self.removePolicy() == DownloadManager.RemoveExit: return downloads = [] for download in self.__downloads: downloads.append(download.getData()) Preferences.setHelp("DownloadManagerDownloads", downloads) def __load(self): """ Private method to load the download settings. """ if self.__loaded: return size = Preferences.getHelp("DownloadManagerSize") if size.isValid(): self.resize(size) pos = Preferences.getHelp("DownloadManagerPosition") self.move(pos) downloads = Preferences.getHelp("DownloadManagerDownloads") for download in downloads: if not download[0].isEmpty() and \ download[1] != "": from .DownloadItem import DownloadItem itm = DownloadItem(parent=self) itm.setData(download) self.__addItem(itm) self.cleanupButton.setEnabled( (len(self.__downloads) - self.activeDownloads()) > 0) self.__loaded = True self.__updateActiveItemCount() def cleanup(self): """ Public slot to cleanup the downloads. """ self.on_cleanupButton_clicked() @pyqtSlot() def on_cleanupButton_clicked(self): """ Private slot cleanup the downloads. """ if len(self.__downloads) == 0: return self.__model.removeRows(0, len(self.__downloads)) if len(self.__downloads) == 0 and \ self.__iconProvider is not None: self.__iconProvider = None self.changeOccurred() self.__updateActiveItemCount() def __updateItemCount(self): """ Private method to update the count label. """ count = len(self.__downloads) self.countLabel.setText(self.tr("%n Download(s)", "", count)) def __updateActiveItemCount(self): """ Private method to update the window title. """ count = self.activeDownloads() if count > 0: self.setWindowTitle( self.tr("Downloading %n file(s)", "", count)) else: self.setWindowTitle(self.tr("Downloads")) def __finished(self): """ Private slot to handle a finished download. """ self.__updateActiveItemCount() if self.isVisible(): QApplication.alert(self) def setDownloadDirectory(self, directory): """ Public method to set the current download directory. @param directory current download directory (string) """ self.__downloadDirectory = directory if self.__downloadDirectory != "": self.__downloadDirectory += "/" def downloadDirectory(self): """ Public method to get the current download directory. @return current download directory (string) """ return self.__downloadDirectory def count(self): """ Public method to get the number of downloads. @return number of downloads (integer) """ return len(self.__downloads) def downloads(self): """ Public method to get a reference to the downloads. @return reference to the downloads (list of DownloadItem) """ return self.__downloads def changeOccurred(self): """ Public method to signal a change. """ self.__saveTimer.changeOccurred() self.__updateItemCount() ########################################################################### ## Context menu related methods below ########################################################################### def __currentItem(self): """ Private method to get a reference to the current item. @return reference to the current item (DownloadItem) """ index = self.downloadsView.currentIndex() if index and index.isValid(): row = index.row() return self.__downloads[row] return None def __contextMenuRetry(self): """ Private method to retry of the download. """ itm = self.__currentItem() if itm is not None: itm.retry() def __contextMenuOpen(self): """ Private method to open the downloaded file. """ itm = self.__currentItem() if itm is not None: itm.openFile() def __contextMenuOpenFolder(self): """ Private method to open the folder containing the downloaded file. """ itm = self.__currentItem() if itm is not None: itm.openFolder() def __contextMenuCancel(self): """ Private method to cancel the current download. """ itm = self.__currentItem() if itm is not None: itm.cancelDownload() def __contextMenuGotoPage(self): """ Private method to open the download page. """ itm = self.__currentItem() if itm is not None: url = itm.getPageUrl() Helpviewer.HelpWindow.HelpWindow.mainWindow().openUrl(url, "") def __contextMenuCopyLink(self): """ Private method to copy the download link to the clipboard. """ itm = self.__currentItem() if itm is not None: url = itm.getPageUrl().toString() QApplication.clipboard().setText(url) def __contextMenuSelectAll(self): """ Private method to select all downloads. """ self.downloadsView.selectAll() def __contextMenuRemoveSelected(self): """ Private method to remove the selected downloads from the list. """ self.downloadsView.removeSelected()
class CookieJar(QNetworkCookieJar): """ Class implementing a QNetworkCookieJar subclass with various accept policies. @signal cookiesChanged() emitted after the cookies have been changed """ JAR_VERSION = 23 AcceptAlways = 0 AcceptNever = 1 AcceptOnlyFromSitesNavigatedTo = 2 KeepUntilExpire = 0 KeepUntilExit = 1 KeepUntilTimeLimit = 2 Allow = 0 Block = 1 AllowForSession = 2 def __init__(self, parent = None): """ Constructor @param parent reference to the parent object (QObject) """ QNetworkCookieJar.__init__(self, parent) self.__loaded = False self.__acceptCookies = self.AcceptOnlyFromSitesNavigatedTo self.__saveTimer = AutoSaver(self, self.save) self.__cookiesFile = os.path.join(Utilities.getConfigDir(), "browser", "cookies.ini") def saveCookies(self, cookiesList): """ Public method to save the cookies. @param cookiesList list of cookies to be saved @return saved cookies as a byte array (QByteArray) """ data = QByteArray() stream = QDataStream(data, QIODevice.WriteOnly) stream.writeUInt16(self.JAR_VERSION) stream.writeUInt32(len(cookiesList)) for cookie in cookiesList: stream << cookie.toRawForm() return data def loadCookies(self, cookies): """ Public method to restore the saved cookies. @param cookies byte array containing the saved cookies (QByteArray) @return list of cookies """ if cookies.isEmpty(): return [] cookiesList = [] data = QByteArray(cookies) stream = QDataStream(data, QIODevice.ReadOnly) version = stream.readUInt16() if version != self.JAR_VERSION: return [] noCookies = stream.readUInt32() rawCookie = QByteArray() while not stream.atEnd(): stream >> rawCookie newCookies = QNetworkCookie.parseCookies(rawCookie) for newCookie in newCookies: cookiesList.append(newCookie) return cookiesList def close(self): """ Public slot to close the cookie jar. """ if self.__loaded and self.__keepCookies == self.KeepUntilExit: self.clear() self.__saveTimer.saveIfNeccessary() def clear(self): """ Public method to clear all cookies. """ if not self.__loaded: self.load() self.setAllCookies([]) self.__saveTimer.changeOccurred() self.emit(SIGNAL("cookiesChanged()")) def load(self): """ Public method to load the cookies. """ if self.__loaded: return cookieSettings = QSettings(self.__cookiesFile, QSettings.IniFormat) # load cookies cookies = cookieSettings.value("Cookies") if cookies.isValid(): cookiesList = self.loadCookies(cookies.toByteArray()) else: cookiesList = [] self.setAllCookies(cookiesList) # load exceptions self.__exceptionsBlock = \ cookieSettings.value("Exceptions/block").toStringList() self.__exceptionsAllow = \ cookieSettings.value("Exceptions/allow").toStringList() self.__exceptionsAllowForSession = \ cookieSettings.value("Exceptions/allowForSession").toStringList() self.__exceptionsBlock.sort() self.__exceptionsAllow.sort() self.__exceptionsAllowForSession.sort() self.__acceptCookies = Preferences.getHelp("AcceptCookies") self.__keepCookies = Preferences.getHelp("KeepCookiesUntil") if self.__keepCookies == self.KeepUntilExit: self.setAllCookies([]) self.__filterTrackingCookies = Preferences.getHelp("FilterTrackingCookies") self.__loaded = True self.emit(SIGNAL("cookiesChanged()")) def save(self): """ Public method to save the cookies. """ if not self.__loaded: return self.__purgeOldCookies() cookieSettings = QSettings(self.__cookiesFile, QSettings.IniFormat) cookiesList = self.allCookies() for index in range(len(cookiesList) -1, -1, -1): if cookiesList[index].isSessionCookie(): del cookiesList[index] cookies = self.saveCookies(cookiesList) cookieSettings.setValue("Cookies", QVariant(cookies)) cookieSettings.setValue("Exceptions/block", QVariant(self.__exceptionsBlock)) cookieSettings.setValue("Exceptions/allow", QVariant(self.__exceptionsAllow)) cookieSettings.setValue("Exceptions/allowForSession", QVariant(self.__exceptionsAllowForSession)) Preferences.setHelp("AcceptCookies", self.__acceptCookies) Preferences.setHelp("KeepCookiesUntil", self.__keepCookies) Preferences.setHelp("FilterTrackingCookies", int(self.__filterTrackingCookies)) def __purgeOldCookies(self): """ Private method to purge old cookies """ cookies = self.allCookies() if len(cookies) == 0: return oldCount = len(cookies) now = QDateTime.currentDateTime() for index in range(len(cookies) - 1, -1, -1): if not cookies[index].isSessionCookie() and \ cookies[index].expirationDate() < now: del cookies[index] if oldCount == len(cookies): return self.setAllCookies(cookies) self.emit(SIGNAL("cookiesChanged()")) def cookiesForUrl(self, url): """ Public method to get the cookies for a URL. @param url URL to get cookies for (QUrl) @return list of cookies (list of QNetworkCookie) """ if not self.__loaded: self.load() globalSettings = QWebSettings.globalSettings() if globalSettings.testAttribute(QWebSettings.PrivateBrowsingEnabled): return [] return QNetworkCookieJar.cookiesForUrl(self, url) def setCookiesFromUrl(self, cookieList, url): """ Public method to set cookies for a URL. @param cookieList list of cookies to set (list of QNetworkCookie) @param url url to set cookies for (QUrl) @return flag indicating cookies were set (boolean) """ if not self.__loaded: self.load() globalSettings = QWebSettings.globalSettings() if globalSettings.testAttribute(QWebSettings.PrivateBrowsingEnabled): return False host = url.host() eBlock = self.__isOnDomainList(self.__exceptionsBlock, host) eAllow = not eBlock and \ self.__isOnDomainList(self.__exceptionsAllow, host) eAllowSession = not eBlock and \ not eAllow and \ self.__isOnDomainList(self.__exceptionsAllowForSession, host) addedCookies = False acceptInitially = self.__acceptCookies != self.AcceptNever if (acceptInitially and not eBlock) or \ (not acceptInitially and (eAllow or eAllowSession)): # url domain == cookie domain soon = QDateTime.currentDateTime() soon = soon.addDays(90) for cookie in cookieList: lst = [] if not (self.__filterTrackingCookies and \ cookie.name().startsWith("__utm")): if eAllowSession: cookie.setExpirationDate(QDateTime()) if self.__keepCookies == self.KeepUntilTimeLimit and \ not cookie.isSessionCookie and \ cookie.expirationDate() > soon: cookie.setExpirationDate(soon) lst.append(cookie) if QNetworkCookieJar.setCookiesFromUrl(self, lst, url): addedCookies = True elif self.__acceptCookies == self.AcceptAlways: # force it in if wanted cookies = self.allCookies() for ocookie in cookies[:]: # does the cookie exist already? if cookie.name() == ocookie.name() and \ cookie.domain() == ocookie.domain() and \ cookie.path() == ocookie.path(): # found a match cookies.remove(ocookie) cookies.append(cookie) self.setAllCookies(cookies) addedCookies = True if addedCookies: self.__saveTimer.changeOccurred() self.emit(SIGNAL("cookiesChanged()")) return addedCookies def acceptPolicy(self): """ Public method to get the accept policy. @return current accept policy """ if not self.__loaded: self.load() return self.__acceptCookies def setAcceptPolicy(self, policy): """ Public method to set the accept policy. @param policy accept policy to be set """ if not self.__loaded: self.load() if policy > self.AcceptOnlyFromSitesNavigatedTo: return if policy == self.__acceptCookies: return self.__acceptCookies = policy self.__saveTimer.changeOccurred() def keepPolicy(self): """ Private method to get the keep policy. """ if not self.__loaded: self.load() return self.__keepCookies def setKeepPolicy(self, policy): """ Public method to set the keep policy. @param policy keep policy to be set """ if not self.__loaded: self.load() if policy > self.KeepUntilTimeLimit: return if policy == self.__keepCookies: return self.__keepCookies = policy self.__saveTimer.changeOccurred() def blockedCookies(self): """ Public method to return the blocked cookies. @return list of blocked cookies (QStringList) """ if not self.__loaded: self.load() return self.__exceptionsBlock def allowedCookies(self): """ Public method to return the allowed cookies. @return list of allowed cookies (QStringList) """ if not self.__loaded: self.load() return self.__exceptionsAllow def allowForSessionCookies(self): """ Public method to return the allowed session cookies. @return list of allowed session cookies (QStringList) """ if not self.__loaded: self.load() return self.__exceptionsAllowForSession def setBlockedCookies(self, list_): """ Public method to set the list of blocked cookies. @param list_ list of blocked cookies (QStringList) """ if not self.__loaded: self.load() self.__exceptionsBlock = QStringList(list_) self.__exceptionsBlock.sort() self.__applyRules() self.__saveTimer.changeOccurred() def setAllowedCookies(self, list_): """ Public method to set the list of allowed cookies. @param list_ list of allowed cookies (QStringList) """ if not self.__loaded: self.load() self.__exceptionsAllow = QStringList(list_) self.__exceptionsAllow.sort() self.__applyRules() self.__saveTimer.changeOccurred() def setAllowForSessionCookies(self, list_): """ Public method to set the list of allowed session cookies. @param list_ list of allowed session cookies (QStringList) """ if not self.__loaded: self.load() self.__exceptionsAllowForSession = QStringList(list_) self.__exceptionsAllowForSession.sort() self.__applyRules() self.__saveTimer.changeOccurred() def filterTrackingCookies(self): """ Public method to get the filter tracking cookies flag. @return filter tracking cookies flag (boolean) """ return self.__filterTrackingCookies def setFilterTrackingCookies(self, filterTrackingCookies): """ Public method to set the filter tracking cookies flag. @param filterTrackingCookies filter tracking cookies flag (boolean) """ self.__filterTrackingCookies = filterTrackingCookies def __isOnDomainList(self, rules, domain): """ Private method to check, if either the rule matches the domain exactly or the domain ends with ".rule". @param rules list of rules (QStringList) @param domain domain name to check (QString) @return flag indicating a match (boolean) """ domain = QString(domain) for rule in rules: if rule.startsWith("."): if domain.endsWith(rule): return True withoutDot = rule.right(rule.size() - 1) if domain == withoutDot: return True else: domainEnding = domain.right(rule.size() + 1) if not domainEnding.isEmpty() and \ domainEnding[0] == "." and \ domain.endsWith(rule): return True if rule == domain: return True return False def __applyRules(self): """ Private method to apply the cookie rules. """ cookiesList = self.allCookies() changed = False for index in range(len(cookiesList) - 1, -1, -1): cookie = cookiesList[index] if self.__isOnDomainList(self.__exceptionsBlock, cookie.domain()): del cookiesList[index] changed = True elif self.__isOnDomainList(self.__exceptionsAllowForSession, cookie.domain()): cookie.setExpirationDate(QDateTime()) changed = True if changed: self.setAllCookies(cookiesList) self.__saveTimer.changeOccurred() self.emit(SIGNAL("cookiesChanged()")) def cookies(self): """ Public method to get the cookies of the cookie jar. @return list of all cookies (list of QNetworkCookie) """ if not self.__loaded: self.load() return self.allCookies() def setCookies(self, cookies): """ Public method to set all cookies. @param cookies list of cookies to be set. """ if not self.__loaded: self.load() self.setAllCookies(cookies) self.__saveTimer.changeOccurred() self.emit(SIGNAL("cookiesChanged()"))
class SpeedDial(QObject): """ Class implementing the speed dial. @signal pagesChanged() emitted after the list of pages changed @signal speedDialSaved() emitted after the speed dial data was saved """ pagesChanged = pyqtSignal() speedDialSaved = pyqtSignal() def __init__(self, parent=None): """ Constructor @param parent reference to the parent object (QObject) """ super(SpeedDial, self).__init__(parent) self.__regenerateScript = True self.__webPages = [] self.__webFrames = [] self.__initialScript = "" self.__thumbnailsDirectory = "" self.__thumbnailers = [] self.__initialize() self.pagesChanged.connect(self.__pagesChanged) self.__saveTimer = AutoSaver(self, self.save) self.pagesChanged.connect(self.__saveTimer.changeOccurred) def addWebFrame(self, frame): """ Public method to add a web frame. @param frame reference to the frame to be added (QWebFrame) """ if frame not in self.__webFrames: self.__webFrames.append(frame) def addPage(self, url, title): """ Public method to add a page for the given data. @param url URL of the page (QUrl) @param title title of the page (string) """ if url.isEmpty(): return from .Page import Page page = Page(url.toString(), title) self.__webPages.append(page) self.pagesChanged.emit() def removePage(self, url): """ Public method to remove a page. @param url URL of the page (QUrl) """ page = self.pageForUrl(url) if not page.url: return self.removeImageForUrl(page.url) self.__webPages.remove(page) self.pagesChanged.emit() def __imageFileName(self, url): """ Private method to generate the image file name for a URL. @param url URL to generate the file name from (string) @return name of the image file (string) """ return os.path.join( self.__thumbnailsDirectory, str(QCryptographicHash.hash(QByteArray(url.encode("utf-8")), QCryptographicHash.Md5).toHex(), encoding="utf-8") + ".png") def initialScript(self): """ Public method to get the 'initial' JavaScript script. @return initial JavaScript script (string) """ if self.__regenerateScript: self.__regenerateScript = False self.__initialScript = "" for page in self.__webPages: if page.broken: imgSource = "qrc:icons/brokenPage.png" else: imgSource = self.__imageFileName(page.url) if not os.path.exists(imgSource): self.loadThumbnail(page.url) imgSource = "qrc:icons/loading.gif" if not page.url: imgSource = "" else: imgSource = QUrl.fromLocalFile(imgSource).toString() self.__initialScript += \ "addBox('{0}', '{1}', '{2}');\n".format( page.url, Utilities.html_uencode(page.title), imgSource) return self.__initialScript def getFileName(self): """ Public method to get the file name of the user agents file. @return name of the user agents file (string) """ return os.path.join(Utilities.getConfigDir(), "browser", "speedDial.xml") def __initialize(self): """ Private method to initialize the speed dial. """ self.__thumbnailsDirectory = os.path.join(Utilities.getConfigDir(), "browser", "thumbnails") # Create directory if it does not exist yet if not os.path.exists(self.__thumbnailsDirectory): os.makedirs(self.__thumbnailsDirectory) self.__load() def reload(self): """ Public method to reload the speed dial data. """ self.__load() def __load(self): """ Private method to load the speed dial configuration. """ allPages, pagesPerRow, speedDialSize = [], 0, 0 speedDialFile = self.getFileName() if os.path.exists(speedDialFile): from .SpeedDialReader import SpeedDialReader reader = SpeedDialReader() allPages, pagesPerRow, speedDialSize = reader.read(speedDialFile) self.__pagesPerRow = pagesPerRow if pagesPerRow else 4 self.__speedDialSize = speedDialSize if speedDialSize else 231 if allPages: self.__webPages = allPages self.pagesChanged.emit() else: allPages = \ 'url:"http://eric-ide.python-projects.org/"|'\ 'title:"Eric Web Site";'\ 'url:"http://www.riverbankcomputing.com/"|'\ 'title:"PyQt Web Site";'\ 'url:"http://www.qt.io/"|title:"Qt Web Site";'\ 'url:"http://blog.qt.digia.com/"|title:"Qt Blog";'\ 'url:"http://www.python.org"|title:"Python Language Website";'\ 'url:"http://www.google.com"|title:"Google";' self.changed(allPages) def save(self): """ Public method to save the speed dial configuration. """ from .SpeedDialWriter import SpeedDialWriter speedDialFile = self.getFileName() writer = SpeedDialWriter() if not writer.write(speedDialFile, self.__webPages, self.__pagesPerRow, self.__speedDialSize): E5MessageBox.critical( None, self.tr("Saving Speed Dial data"), self.tr("""<p>Speed Dial data could not be saved to""" """ <b>{0}</b></p>""").format(speedDialFile)) else: self.speedDialSaved.emit() def close(self): """ Public method to close the user agents manager. """ self.__saveTimer.saveIfNeccessary() def pageForUrl(self, url): """ Public method to get the page for the given URL. @param url URL to be searched for (QUrl) @return page for the URL (Page) """ urlString = url.toString() for page in self.__webPages: if page.url == urlString: return page from .Page import Page return Page() def urlForShortcut(self, key): """ Public method to get the URL for the given shortcut key. @param key shortcut key (integer) @return URL for the key (QUrl) """ if key < 0 or len(self.__webPages) <= key: return QUrl() return QUrl.fromEncoded(self.__webPages[key].url.encode("utf-8")) @pyqtSlot(str) def changed(self, allPages): """ Public slot to react on changed pages. @param allPages string giving all pages (string) """ if not allPages: return entries = allPages.split('";') self.__webPages = [] from .Page import Page for entry in entries: if not entry: continue tmp = entry.split('"|') if len(tmp) == 2: broken = False elif len(tmp) == 3: broken = "brokenPage" in tmp[2][5:] else: continue page = Page(tmp[0][5:], tmp[1][7:], broken) self.__webPages.append(page) self.pagesChanged.emit() @pyqtSlot(str) @pyqtSlot(str, bool) def loadThumbnail(self, url, loadTitle=False): """ Public slot to load a thumbnail of the given URL. @param url URL of the thumbnail (string) @param loadTitle flag indicating to get the title for the thumbnail from the site (boolean) """ if not url: return from .PageThumbnailer import PageThumbnailer thumbnailer = PageThumbnailer(self) thumbnailer.setUrl(QUrl.fromEncoded(url.encode("utf-8"))) thumbnailer.setLoadTitle(loadTitle) thumbnailer.thumbnailCreated.connect(self.__thumbnailCreated) self.__thumbnailers.append(thumbnailer) thumbnailer.start() @pyqtSlot(str) def removeImageForUrl(self, url): """ Public slot to remove the image for a URL. @param url URL to remove the image for (string) """ fileName = self.__imageFileName(url) if os.path.exists(fileName): os.remove(fileName) @pyqtSlot(str, result=str) def urlFromUserInput(self, url): """ Public slot to get the URL from user input. @param url URL entered by the user (string) @return sanitized URL (string) """ return QUrl.fromUserInput(url).toString() @pyqtSlot(str, result=str) def unescapeTitle(self, title): """ Public slot to unescape the titel string. @param title escaped title (string) @return un-escaped title (string) """ return Utilities.html_udecode(title) @pyqtSlot(int) def setPagesInRow(self, count): """ Public slot to set the number of pages per row. @param count number of pages per row (integer) """ self.__pagesPerRow = count self.__saveTimer.changeOccurred() def pagesInRow(self): """ Public method to get the number of dials per row. @return number of dials per row (integer) """ return self.__pagesPerRow @pyqtSlot(int) def setSdSize(self, size): """ Public slot to set the size of the speed dial. @param size size of the speed dial (integer) """ self.__speedDialSize = size self.__saveTimer.changeOccurred() def sdSize(self): """ Public method to get the speed dial size. @return speed dial size (integer) """ return self.__speedDialSize def __thumbnailCreated(self, image): """ Private slot to handle the creation of a thumbnail image. @param image thumbnail image (QPixmap) """ from .PageThumbnailer import PageThumbnailer thumbnailer = self.sender() if not isinstance(thumbnailer, PageThumbnailer) or \ thumbnailer not in self.__thumbnailers: return loadTitle = thumbnailer.loadTitle() title = thumbnailer.title() url = thumbnailer.url().toString() fileName = self.__imageFileName(url) if image.isNull(): fileName = "qrc:icons/brokenPage.png" title = self.tr("Unable to load") loadTitle = True page = self.pageForUrl(thumbnailer.url()) page.broken = True else: if not image.save(fileName): qWarning("SpeedDial.__thumbnailCreated: Cannot save thumbnail" " to {0}".format(fileName)) fileName = QUrl.fromLocalFile(fileName).toString() self.__regenerateScript = True for frame in self.__cleanFrames(): frame.evaluateJavaScript("setImageToUrl('{0}', '{1}');".format( url, fileName)) if loadTitle: frame.evaluateJavaScript("setTitleToUrl('{0}', '{1}');".format( url, Utilities.html_uencode(title))) thumbnailer.deleteLater() self.__thumbnailers.remove(thumbnailer) def __cleanFrames(self): """ Private method to clean all frames. @return list of speed dial frames (list of QWebFrame) """ frames = [] for frame in self.__webFrames[:]: if frame.url().toString() == "eric:speeddial": frames.append(frame) else: self.__webFrames.remove(frame) return frames def __pagesChanged(self): """ Private slot to react on a change of the pages configuration. """ # update all speed dial pages self.__regenerateScript = True for frame in self.__cleanFrames(): frame.page().triggerAction(QWebPage.Reload)
class BookmarksManager(QObject): """ Class implementing the bookmarks manager. @signal entryAdded(BookmarkNode) emitted after a bookmark node has been added @signal entryRemoved(BookmarkNode, int, BookmarkNode) emitted after a bookmark node has been removed @signal entryChanged(BookmarkNode) emitted after a bookmark node has been changed @signal bookmarksSaved() emitted after the bookmarks were saved @signal bookmarksReloaded() emitted after the bookmarks were reloaded """ entryAdded = pyqtSignal(BookmarkNode) entryRemoved = pyqtSignal(BookmarkNode, int, BookmarkNode) entryChanged = pyqtSignal(BookmarkNode) bookmarksSaved = pyqtSignal() bookmarksReloaded = pyqtSignal() def __init__(self, parent=None): """ Constructor @param parent reference to the parent object (QObject) """ super(BookmarksManager, self).__init__(parent) self.__saveTimer = AutoSaver(self, self.save) self.entryAdded.connect(self.__saveTimer.changeOccurred) self.entryRemoved.connect(self.__saveTimer.changeOccurred) self.entryChanged.connect(self.__saveTimer.changeOccurred) self.__initialize() def __initialize(self): """ Private method to initialize some data. """ self.__loaded = False self.__bookmarkRootNode = None self.__toolbar = None self.__menu = None self.__bookmarksModel = None self.__commands = QUndoStack() @classmethod def getFileName(cls): """ Class method to get the file name of the bookmark file. @return name of the bookmark file (string) """ return os.path.join(Utilities.getConfigDir(), "web_browser", "bookmarks.xbel") def close(self): """ Public method to close the bookmark manager. """ self.__saveTimer.saveIfNeccessary() def undoRedoStack(self): """ Public method to get a reference to the undo stack. @return reference to the undo stack (QUndoStack) """ return self.__commands def changeExpanded(self): """ Public method to handle a change of the expanded state. """ self.__saveTimer.changeOccurred() def reload(self): """ Public method used to initiate a reloading of the bookmarks. """ self.__initialize() self.load() self.bookmarksReloaded.emit() def load(self): """ Public method to load the bookmarks. @exception RuntimeError raised to indicate an error loading the bookmarks """ if self.__loaded: return self.__loaded = True bookmarkFile = self.getFileName() if not QFile.exists(bookmarkFile): from . import DefaultBookmarks_rc # __IGNORE_WARNING__ bookmarkFile = QFile(":/DefaultBookmarks.xbel") bookmarkFile.open(QIODevice.ReadOnly) from .XbelReader import XbelReader reader = XbelReader() self.__bookmarkRootNode = reader.read(bookmarkFile) if reader.error() != QXmlStreamReader.NoError: E5MessageBox.warning( None, self.tr("Loading Bookmarks"), self.tr( """Error when loading bookmarks on line {0},""" """ column {1}:\n {2}""") .format(reader.lineNumber(), reader.columnNumber(), reader.errorString())) others = [] for index in range( len(self.__bookmarkRootNode.children()) - 1, -1, -1): node = self.__bookmarkRootNode.children()[index] if node.type() == BookmarkNode.Folder: if ( (node.title == self.tr("Toolbar Bookmarks") or node.title == BOOKMARKBAR) and self.__toolbar is None ): node.title = self.tr(BOOKMARKBAR) self.__toolbar = node if ( (node.title == self.tr("Menu") or node.title == BOOKMARKMENU) and self.__menu is None ): node.title = self.tr(BOOKMARKMENU) self.__menu = node else: others.append(node) self.__bookmarkRootNode.remove(node) if len(self.__bookmarkRootNode.children()) > 0: raise RuntimeError("Error loading bookmarks.") if self.__toolbar is None: self.__toolbar = BookmarkNode(BookmarkNode.Folder, self.__bookmarkRootNode) self.__toolbar.title = self.tr(BOOKMARKBAR) else: self.__bookmarkRootNode.add(self.__toolbar) if self.__menu is None: self.__menu = BookmarkNode(BookmarkNode.Folder, self.__bookmarkRootNode) self.__menu.title = self.tr(BOOKMARKMENU) else: self.__bookmarkRootNode.add(self.__menu) for node in others: self.__menu.add(node) def save(self): """ Public method to save the bookmarks. """ if not self.__loaded: return from .XbelWriter import XbelWriter writer = XbelWriter() bookmarkFile = self.getFileName() # save root folder titles in English (i.e. not localized) self.__menu.title = BOOKMARKMENU self.__toolbar.title = BOOKMARKBAR if not writer.write(bookmarkFile, self.__bookmarkRootNode): E5MessageBox.warning( None, self.tr("Saving Bookmarks"), self.tr("""Error saving bookmarks to <b>{0}</b>.""") .format(bookmarkFile)) # restore localized titles self.__menu.title = self.tr(BOOKMARKMENU) self.__toolbar.title = self.tr(BOOKMARKBAR) self.bookmarksSaved.emit() def addBookmark(self, parent, node, row=-1): """ Public method to add a bookmark. @param parent reference to the node to add to (BookmarkNode) @param node reference to the node to add (BookmarkNode) @param row row number (integer) """ if not self.__loaded: return self.setTimestamp(node, BookmarkNode.TsAdded, QDateTime.currentDateTime()) command = InsertBookmarksCommand(self, parent, node, row) self.__commands.push(command) def removeBookmark(self, node): """ Public method to remove a bookmark. @param node reference to the node to be removed (BookmarkNode) """ if not self.__loaded: return parent = node.parent() row = parent.children().index(node) command = RemoveBookmarksCommand(self, parent, row) self.__commands.push(command) def setTitle(self, node, newTitle): """ Public method to set the title of a bookmark. @param node reference to the node to be changed (BookmarkNode) @param newTitle title to be set (string) """ if not self.__loaded: return command = ChangeBookmarkCommand(self, node, newTitle, True) self.__commands.push(command) def setUrl(self, node, newUrl): """ Public method to set the URL of a bookmark. @param node reference to the node to be changed (BookmarkNode) @param newUrl URL to be set (string) """ if not self.__loaded: return command = ChangeBookmarkCommand(self, node, newUrl, False) self.__commands.push(command) def setNodeChanged(self, node): """ Public method to signal changes of bookmarks other than title, URL or timestamp. @param node reference to the bookmark (BookmarkNode) """ self.__saveTimer.changeOccurred() def setTimestamp(self, node, timestampType, timestamp): """ Public method to set the URL of a bookmark. @param node reference to the node to be changed (BookmarkNode) @param timestampType type of the timestamp to set (BookmarkNode.TsAdded, BookmarkNode.TsModified, BookmarkNode.TsVisited) @param timestamp timestamp to set (QDateTime) """ if not self.__loaded: return assert timestampType in [BookmarkNode.TsAdded, BookmarkNode.TsModified, BookmarkNode.TsVisited] if timestampType == BookmarkNode.TsAdded: node.added = timestamp elif timestampType == BookmarkNode.TsModified: node.modified = timestamp elif timestampType == BookmarkNode.TsVisited: node.visited = timestamp self.__saveTimer.changeOccurred() def incVisitCount(self, node): """ Public method to increment the visit count of a bookmark. @param node reference to the node to be changed (BookmarkNode) """ if not self.__loaded: return if node: node.visitCount += 1 self.__saveTimer.changeOccurred() def setVisitCount(self, node, count): """ Public method to set the visit count of a bookmark. @param node reference to the node to be changed (BookmarkNode) @param count visit count to be set (int or str) """ try: node.visitCount = int(count) self.__saveTimer.changeOccurred() except ValueError: # ignore invalid values pass def bookmarks(self): """ Public method to get a reference to the root bookmark node. @return reference to the root bookmark node (BookmarkNode) """ if not self.__loaded: self.load() return self.__bookmarkRootNode def menu(self): """ Public method to get a reference to the bookmarks menu node. @return reference to the bookmarks menu node (BookmarkNode) """ if not self.__loaded: self.load() return self.__menu def toolbar(self): """ Public method to get a reference to the bookmarks toolbar node. @return reference to the bookmarks toolbar node (BookmarkNode) """ if not self.__loaded: self.load() return self.__toolbar def bookmarksModel(self): """ Public method to get a reference to the bookmarks model. @return reference to the bookmarks model (BookmarksModel) """ if self.__bookmarksModel is None: from .BookmarksModel import BookmarksModel self.__bookmarksModel = BookmarksModel(self, self) return self.__bookmarksModel def importBookmarks(self): """ Public method to import bookmarks. """ from .BookmarksImportDialog import BookmarksImportDialog dlg = BookmarksImportDialog() if dlg.exec_() == QDialog.Accepted: importRootNode = dlg.getImportedBookmarks() if importRootNode is not None: self.addBookmark(self.menu(), importRootNode) def exportBookmarks(self): """ Public method to export the bookmarks. """ fileName, selectedFilter = E5FileDialog.getSaveFileNameAndFilter( None, self.tr("Export Bookmarks"), "eric6_bookmarks.xbel", self.tr("XBEL bookmarks (*.xbel);;" "XBEL bookmarks (*.xml);;" "HTML Bookmarks (*.html)")) if not fileName: return ext = QFileInfo(fileName).suffix() if not ext: ex = selectedFilter.split("(*")[1].split(")")[0] if ex: fileName += ex ext = QFileInfo(fileName).suffix() if ext == "html": from .NsHtmlWriter import NsHtmlWriter writer = NsHtmlWriter() else: from .XbelWriter import XbelWriter writer = XbelWriter() if not writer.write(fileName, self.__bookmarkRootNode): E5MessageBox.critical( None, self.tr("Exporting Bookmarks"), self.tr("""Error exporting bookmarks to <b>{0}</b>.""") .format(fileName)) def faviconChanged(self, url): """ Public slot to update the icon image for an URL. @param url URL of the icon to update (QUrl or string) """ if isinstance(url, QUrl): url = url.toString() nodes = self.bookmarksForUrl(url) for node in nodes: self.bookmarksModel().entryChanged(node) def bookmarkForUrl(self, url, start=StartRoot): """ Public method to get a bookmark node for a given URL. @param url URL of the bookmark to search for (QUrl or string) @keyparam start indicator for the start of the search (StartRoot, StartMenu, StartToolBar) @return bookmark node for the given url (BookmarkNode) """ if start == StartMenu: startNode = self.__menu elif start == StartToolBar: startNode = self.__toolbar else: startNode = self.__bookmarkRootNode if startNode is None: return None if isinstance(url, QUrl): url = url.toString() return self.__searchBookmark(url, startNode) def __searchBookmark(self, url, startNode): """ Private method get a bookmark node for a given URL. @param url URL of the bookmark to search for (string) @param startNode reference to the node to start searching (BookmarkNode) @return bookmark node for the given url (BookmarkNode) """ bm = None for node in startNode.children(): if node.type() == BookmarkNode.Folder: bm = self.__searchBookmark(url, node) elif node.type() == BookmarkNode.Bookmark: if node.url == url: bm = node if bm is not None: return bm return None def bookmarksForUrl(self, url, start=StartRoot): """ Public method to get a list of bookmark nodes for a given URL. @param url URL of the bookmarks to search for (QUrl or string) @keyparam start indicator for the start of the search (StartRoot, StartMenu, StartToolBar) @return list of bookmark nodes for the given url (list of BookmarkNode) """ if start == StartMenu: startNode = self.__menu elif start == StartToolBar: startNode = self.__toolbar else: startNode = self.__bookmarkRootNode if startNode is None: return [] if isinstance(url, QUrl): url = url.toString() return self.__searchBookmarks(url, startNode) def __searchBookmarks(self, url, startNode): """ Private method get a list of bookmark nodes for a given URL. @param url URL of the bookmarks to search for (string) @param startNode reference to the node to start searching (BookmarkNode) @return list of bookmark nodes for the given url (list of BookmarkNode) """ bm = [] for node in startNode.children(): if node.type() == BookmarkNode.Folder: bm.extend(self.__searchBookmarks(url, node)) elif node.type() == BookmarkNode.Bookmark: if node.url == url: bm.append(node) return bm
class PasswordManager(QObject): """ Class implementing the password manager. @signal changed() emitted to indicate a change @signal passwordsSaved() emitted after the passwords were saved """ changed = pyqtSignal() passwordsSaved = pyqtSignal() SEPARATOR = "====================" FORMS = "=====FORMS=====" NEVER = "=====NEVER=====" def __init__(self, parent=None): """ Constructor @param parent reference to the parent object (QObject) """ super(PasswordManager, self).__init__(parent) self.__logins = {} self.__loginForms = {} self.__never = [] self.__loaded = False self.__saveTimer = AutoSaver(self, self.save) self.changed.connect(self.__saveTimer.changeOccurred) def clear(self): """ Public slot to clear the saved passwords. """ if not self.__loaded: self.__load() self.__logins = {} self.__loginForms = {} self.__never = [] self.__saveTimer.changeOccurred() self.__saveTimer.saveIfNeccessary() self.changed.emit() def getLogin(self, url, realm): """ Public method to get the login credentials. @param url URL to get the credentials for (QUrl) @param realm realm to get the credentials for (string) @return tuple containing the user name (string) and password (string) """ if not self.__loaded: self.__load() key = self.__createKey(url, realm) try: return self.__logins[key][0], Utilities.crypto.pwConvert( self.__logins[key][1], encode=False) except KeyError: return "", "" def setLogin(self, url, realm, username, password): """ Public method to set the login credentials. @param url URL to set the credentials for (QUrl) @param realm realm to set the credentials for (string) @param username username for the login (string) @param password password for the login (string) """ if not self.__loaded: self.__load() key = self.__createKey(url, realm) self.__logins[key] = ( username, Utilities.crypto.pwConvert(password, encode=True) ) self.changed.emit() def __createKey(self, url, realm): """ Private method to create the key string for the login credentials. @param url URL to get the credentials for (QUrl) @param realm realm to get the credentials for (string) @return key string (string) """ authority = url.authority() if authority.startswith("@"): authority = authority[1:] if realm: key = "{0}://{1} ({2})".format( url.scheme(), authority, realm) else: key = "{0}://{1}".format(url.scheme(), authority) return key def getFileName(self): """ Public method to get the file name of the passwords file. @return name of the passwords file (string) """ return os.path.join(Utilities.getConfigDir(), "browser", "logins.xml") def save(self): """ Public slot to save the login entries to disk. """ if not self.__loaded: return from .PasswordWriter import PasswordWriter loginFile = self.getFileName() writer = PasswordWriter() if not writer.write( loginFile, self.__logins, self.__loginForms, self.__never): E5MessageBox.critical( None, self.tr("Saving login data"), self.tr( """<p>Login data could not be saved to <b>{0}</b></p>""" ).format(loginFile)) else: self.passwordsSaved.emit() def __load(self): """ Private method to load the saved login credentials. """ loginFile = self.getFileName() if not os.path.exists(loginFile): self.__loadNonXml(os.path.splitext(loginFile)[0]) else: from .PasswordReader import PasswordReader reader = PasswordReader() self.__logins, self.__loginForms, self.__never = \ reader.read(loginFile) if reader.error() != QXmlStreamReader.NoError: E5MessageBox.warning( None, self.tr("Loading login data"), self.tr("""Error when loading login data on""" """ line {0}, column {1}:\n{2}""") .format(reader.lineNumber(), reader.columnNumber(), reader.errorString())) self.__loaded = True def __loadNonXml(self, loginFile): """ Private method to load non-XML password files. This method is to convert from the old, non-XML format to the new XML based format. @param loginFile name of the non-XML password file (string) """ if os.path.exists(loginFile): try: f = open(loginFile, "r", encoding="utf-8") lines = f.read() f.close() except IOError as err: E5MessageBox.critical( None, self.tr("Loading login data"), self.tr("""<p>Login data could not be loaded """ """from <b>{0}</b></p>""" """<p>Reason: {1}</p>""") .format(loginFile, str(err))) return data = [] section = 0 # 0 = login data, 1 = forms data, 2 = never store info for line in lines.splitlines(): if line == self.FORMS: section = 1 continue elif line == self.NEVER: section = 2 continue if section == 0: if line != self.SEPARATOR: data.append(line) else: if len(data) != 3: E5MessageBox.critical( None, self.tr("Loading login data"), self.tr( """<p>Login data could not be loaded """ """from <b>{0}</b></p>""" """<p>Reason: Wrong input format</p>""") .format(loginFile)) return self.__logins[data[0]] = (data[1], data[2]) data = [] elif section == 1: if line != self.SEPARATOR: data.append(line) else: from .LoginForm import LoginForm key = data[0] form = LoginForm() form.url = QUrl(data[1]) form.name = data[2] form.hasAPassword = data[3] == "True" for element in data[4:]: name, value = element.split(" = ", 1) form.elements.append((name, value)) self.__loginForms[key] = form data = [] elif section == 2: self.__never.append(line) os.remove(loginFile) self.__loaded = True # this does the conversion self.save() def reload(self): """ Public method to reload the login data. """ if not self.__loaded: return self.__load() def close(self): """ Public method to close the passwords manager. """ self.__saveTimer.saveIfNeccessary() def removePassword(self, site): """ Public method to remove a password entry. @param site web site name (string) """ if site in self.__logins: del self.__logins[site] if site in self.__loginForms: del self.__loginForms[site] self.changed.emit() def allSiteNames(self): """ Public method to get a list of all site names. @return sorted list of all site names (list of strings) """ if not self.__loaded: self.__load() return sorted(self.__logins.keys()) def sitesCount(self): """ Public method to get the number of available sites. @return number of sites (integer) """ if not self.__loaded: self.__load() return len(self.__logins) def siteInfo(self, site): """ Public method to get a reference to the named site. @param site web site name (string) @return tuple containing the user name (string) and password (string) """ if not self.__loaded: self.__load() if site not in self.__logins: return None return self.__logins[site][0], Utilities.crypto.pwConvert( self.__logins[site][1], encode=False) def post(self, request, data): """ Public method to check, if the data to be sent contains login data. @param request reference to the network request (QNetworkRequest) @param data data to be sent (QByteArray) """ # shall passwords be saved? if not Preferences.getUser("SavePasswords"): return # observe privacy if QWebSettings.globalSettings().testAttribute( QWebSettings.PrivateBrowsingEnabled): return if not self.__loaded: self.__load() # determine the url refererHeader = request.rawHeader(b"Referer") if refererHeader.isEmpty(): return url = QUrl.fromEncoded(refererHeader) url = self.__stripUrl(url) # check that url isn't in __never if url.toString() in self.__never: return # check the request type navType = request.attribute(QNetworkRequest.User + 101) if navType is None: return if navType != QWebPage.NavigationTypeFormSubmitted: return # determine the QWebPage webPage = request.attribute(QNetworkRequest.User + 100) if webPage is None: return # determine the requests content type contentTypeHeader = request.rawHeader(b"Content-Type") if contentTypeHeader.isEmpty(): return multipart = contentTypeHeader.startsWith(b"multipart/form-data") if multipart: boundary = contentTypeHeader.split(" ")[1].split("=")[1] else: boundary = None # find the matching form on the web page form = self.__findForm(webPage, data, boundary=boundary) if not form.isValid(): return form.url = QUrl(url) # check, if the form has a password if not form.hasAPassword: return # prompt, if the form has never be seen key = self.__createKey(url, "") if key not in self.__loginForms: mb = E5MessageBox.E5MessageBox( E5MessageBox.Question, self.tr("Save password"), self.tr( """<b>Would you like to save this password?</b><br/>""" """To review passwords you have saved and remove them, """ """use the password management dialog of the Settings""" """ menu."""), modal=True) neverButton = mb.addButton( self.tr("Never for this site"), E5MessageBox.DestructiveRole) noButton = mb.addButton( self.tr("Not now"), E5MessageBox.RejectRole) mb.addButton(E5MessageBox.Yes) mb.exec_() if mb.clickedButton() == neverButton: self.__never.append(url.toString()) return elif mb.clickedButton() == noButton: return # extract user name and password user = "" password = "" for index in range(len(form.elements)): element = form.elements[index] type_ = form.elementTypes[element[0]] if user == "" and \ type_ == "text": user = element[1] elif password == "" and \ type_ == "password": password = element[1] form.elements[index] = (element[0], "--PASSWORD--") if user and password: self.__logins[key] = \ (user, Utilities.crypto.pwConvert(password, encode=True)) self.__loginForms[key] = form self.changed.emit() def __stripUrl(self, url): """ Private method to strip off all unneeded parts of a URL. @param url URL to be stripped (QUrl) @return stripped URL (QUrl) """ cleanUrl = QUrl(url) if qVersion() >= "5.0.0": cleanUrl.setQuery("") else: cleanUrl.setQueryItems([]) cleanUrl.setUserInfo("") authority = cleanUrl.authority() if authority.startswith("@"): authority = authority[1:] cleanUrl = QUrl("{0}://{1}{2}".format( cleanUrl.scheme(), authority, cleanUrl.path())) cleanUrl.setFragment("") return cleanUrl def __findForm(self, webPage, data, boundary=None): """ Private method to find the form used for logging in. @param webPage reference to the web page (QWebPage) @param data data to be sent (QByteArray) @keyparam boundary boundary string (QByteArray) for multipart encoded data, None for urlencoded data @return parsed form (LoginForm) """ from .LoginForm import LoginForm form = LoginForm() if boundary is not None: args = self.__extractMultipartQueryItems(data, boundary) else: if qVersion() >= "5.0.0": from PyQt5.QtCore import QUrlQuery argsUrl = QUrl.fromEncoded( QByteArray(b"foo://bar.com/?" + QUrl.fromPercentEncoding( data.replace(b"+", b"%20")).encode("utf-8"))) encodedArgs = QUrlQuery(argsUrl).queryItems() else: argsUrl = QUrl.fromEncoded( QByteArray(b"foo://bar.com/?" + data.replace(b"+", b"%20")) ) encodedArgs = argsUrl.queryItems() args = set() for arg in encodedArgs: key = arg[0] value = arg[1] args.add((key, value)) # extract the forms from Helpviewer.JavaScriptResources import parseForms_js lst = webPage.mainFrame().evaluateJavaScript(parseForms_js) for map in lst: formHasPasswords = False formName = map["name"] formIndex = map["index"] if isinstance(formIndex, float) and formIndex.is_integer(): formIndex = int(formIndex) elements = map["elements"] formElements = set() formElementTypes = {} deadElements = set() for elementMap in elements: try: name = elementMap["name"] value = elementMap["value"] type_ = elementMap["type"] except KeyError: continue if type_ == "password": formHasPasswords = True t = (name, value) try: if elementMap["autocomplete"] == "off": deadElements.add(t) except KeyError: pass if name: formElements.add(t) formElementTypes[name] = type_ if formElements.intersection(args) == args: form.hasAPassword = formHasPasswords if not formName: form.name = formIndex else: form.name = formName args.difference_update(deadElements) for elt in deadElements: if elt[0] in formElementTypes: del formElementTypes[elt[0]] form.elements = list(args) form.elementTypes = formElementTypes break return form def __extractMultipartQueryItems(self, data, boundary): """ Private method to extract the query items for a post operation. @param data data to be sent (QByteArray) @param boundary boundary string (QByteArray) @return set of name, value pairs (set of tuple of string, string) """ args = set() dataStr = bytes(data).decode() boundaryStr = bytes(boundary).decode() parts = dataStr.split(boundaryStr + "\r\n") for part in parts: if part.startswith("Content-Disposition"): lines = part.split("\r\n") name = lines[0].split("=")[1][1:-1] value = lines[2] args.add((name, value)) return args def fill(self, page): """ Public slot to fill login forms with saved data. @param page reference to the web page (QWebPage) """ if page is None or page.mainFrame() is None: return if not self.__loaded: self.__load() url = page.mainFrame().url() url = self.__stripUrl(url) key = self.__createKey(url, "") if key not in self.__loginForms or \ key not in self.__logins: return form = self.__loginForms[key] if form.url != url: return if form.name == "": formName = "0" else: try: formName = "{0:d}".format(int(form.name)) except ValueError: formName = '"{0}"'.format(form.name) for element in form.elements: name = element[0] value = element[1] disabled = page.mainFrame().evaluateJavaScript( 'document.forms[{0}].elements["{1}"].disabled'.format( formName, name)) if disabled: continue readOnly = page.mainFrame().evaluateJavaScript( 'document.forms[{0}].elements["{1}"].readOnly'.format( formName, name)) if readOnly: continue type_ = page.mainFrame().evaluateJavaScript( 'document.forms[{0}].elements["{1}"].type'.format( formName, name)) if type_ == "" or \ type_ in ["hidden", "reset", "submit"]: continue if type_ == "password": value = Utilities.crypto.pwConvert( self.__logins[key][1], encode=False) setType = type_ == "checkbox" and "checked" or "value" value = value.replace("\\", "\\\\") value = value.replace('"', '\\"') javascript = \ 'document.forms[{0}].elements["{1}"].{2}="{3}";'.format( formName, name, setType, value) page.mainFrame().evaluateJavaScript(javascript) def masterPasswordChanged(self, oldPassword, newPassword): """ Public slot to handle the change of the master password. @param oldPassword current master password (string) @param newPassword new master password (string) """ if not self.__loaded: self.__load() progress = E5ProgressDialog( self.tr("Re-encoding saved passwords..."), None, 0, len(self.__logins), self.tr("%v/%m Passwords"), QApplication.activeModalWidget()) progress.setMinimumDuration(0) progress.setWindowTitle(self.tr("Passwords")) count = 0 for key in self.__logins: progress.setValue(count) QCoreApplication.processEvents() username, hash = self.__logins[key] hash = Utilities.crypto.pwRecode(hash, oldPassword, newPassword) self.__logins[key] = (username, hash) count += 1 progress.setValue(len(self.__logins)) QCoreApplication.processEvents() self.changed.emit()
class CookieJar(QNetworkCookieJar): """ Class implementing a QNetworkCookieJar subclass with various accept policies. @signal cookiesChanged() emitted after the cookies have been changed """ cookiesChanged = pyqtSignal() AcceptAlways = 0 AcceptNever = 1 AcceptOnlyFromSitesNavigatedTo = 2 AcceptMax = 2 KeepUntilExpire = 0 KeepUntilExit = 1 KeepMax = 1 Allow = 0 Block = 1 AllowForSession = 2 def __init__(self, parent=None): """ Constructor @param parent reference to the parent object (QObject) """ super(CookieJar, self).__init__(parent) self.__loaded = False self.__acceptCookies = self.AcceptOnlyFromSitesNavigatedTo self.__saveTimer = AutoSaver(self, self.__save) self.__cookiesFile = os.path.join(Utilities.getConfigDir(), "web_browser", "cookies.ini") self.__store = WebBrowserWindow.webProfile().cookieStore() try: self.__store.setCookieFilter(self.__cookieFilter) except AttributeError: # pre Qt 5.11 pass self.__store.cookieAdded.connect(self.__cookieAdded) self.__store.cookieRemoved.connect(self.__cookieRemoved) self.__load() self.__store.loadAllCookies() def close(self): """ Public slot to close the cookie jar. """ if not self.__loaded: self.__load() if self.__keepCookies == self.KeepUntilExit: self.clear() self.__saveTimer.saveIfNeccessary() def clear(self): """ Public method to clear all cookies. """ if not self.__loaded: self.__load() self.setAllCookies([]) self.__store.deleteAllCookies() self.cookiesChanged.emit() def removeCookies(self, cookies): """ Public method to remove a list of cookies. @param cookies list of cookies to be removed @type list of QNetworkCookie """ wasBlocked = self.blockSignals(True) for cookie in cookies: self.__store.deleteCookie(cookie) self.blockSignals(wasBlocked) self.cookiesChanged.emit() def removeCookie(self, cookie): """ Public method to remove a cookie. @param cookie cookie to be removed @type QNetworkCookie """ self.__store.deleteCookie(cookie) self.cookiesChanged.emit() def __load(self): """ Private method to load the cookies settings. """ if self.__loaded: return cookieSettings = QSettings(self.__cookiesFile, QSettings.IniFormat) # load exceptions self.__exceptionsBlock = Preferences.toList( cookieSettings.value("Exceptions/block")) self.__exceptionsAllow = Preferences.toList( cookieSettings.value("Exceptions/allow")) self.__exceptionsAllowForSession = Preferences.toList( cookieSettings.value("Exceptions/allowForSession")) self.__exceptionsBlock.sort() self.__exceptionsAllow.sort() self.__exceptionsAllowForSession.sort() self.__acceptCookies = Preferences.getWebBrowser("AcceptCookies") self.__keepCookies = Preferences.getWebBrowser("KeepCookiesUntil") if self.__keepCookies == self.KeepUntilExit: self.clear() self.__filterTrackingCookies = Preferences.toBool( Preferences.getWebBrowser("FilterTrackingCookies")) self.__loaded = True self.cookiesChanged.emit() def __save(self): """ Private method to save the cookies settings. """ if not self.__loaded: return cookieSettings = QSettings(self.__cookiesFile, QSettings.IniFormat) cookieSettings.setValue("Exceptions/block", self.__exceptionsBlock) cookieSettings.setValue("Exceptions/allow", self.__exceptionsAllow) cookieSettings.setValue("Exceptions/allowForSession", self.__exceptionsAllowForSession) Preferences.setWebBrowser("AcceptCookies", self.__acceptCookies) Preferences.setWebBrowser("KeepCookiesUntil", self.__keepCookies) Preferences.setWebBrowser("FilterTrackingCookies", self.__filterTrackingCookies) @pyqtSlot(QNetworkCookie) def __cookieAdded(self, cookie): """ Private slot handling the addition of a cookie. @param cookie cookie which was added @type QNetworkCookie """ if self.__rejectCookie(cookie, cookie.domain()): self.__store.deleteCookie(cookie) return self.insertCookie(cookie) self.cookiesChanged.emit() @pyqtSlot(QNetworkCookie) def __cookieRemoved(self, cookie): """ Private slot handling the removal of a cookie. @param cookie cookie which was removed @type QNetworkCookie """ if self.deleteCookie(cookie): self.cookiesChanged.emit() def __cookieFilter(self, request): """ Private method to filter cookies. Note: This method is used for Qt 5.11+ only. @param request reference to the cookie filter request object @type QWebEngineCookieStore.FilterRequest @return flag indicating cookie access is allowed @rtype bool """ if not self.__loaded: self.__load() if self.__acceptCookies == self.AcceptNever: res = self.__isOnDomainList(self.__exceptionsAllow, request.origin.host()) if not res: return False if self.__acceptCookies == self.AcceptAlways: res = self.__isOnDomainList(self.__exceptionsBlock, request.origin.host()) if res: return False if (self.__acceptCookies == self.AcceptOnlyFromSitesNavigatedTo and request.thirdParty): return False return True def __rejectCookie(self, cookie, cookieDomain): """ Private method to test, if a cookie shall be rejected. @param cookie cookie to be tested @type QNetworkCookie @param cookieDomain domain of the cookie @type str @return flag indicating the cookie shall be rejected @rtype bool """ if not self.__loaded: self.__load() if self.__acceptCookies == self.AcceptNever: res = self.__isOnDomainList(self.__exceptionsAllow, cookieDomain) if not res: return True if self.__acceptCookies == self.AcceptAlways: res = self.__isOnDomainList(self.__exceptionsBlock, cookieDomain) if res: return True if self.__acceptCookies == self.AcceptOnlyFromSitesNavigatedTo: mainWindow = WebBrowserWindow.mainWindow() if mainWindow is not None: browser = mainWindow.getWindow().currentBrowser() if browser is not None: url = browser.url() if url.isValid(): host = url.host() else: host = "" res = self.__matchDomain(cookieDomain, host) if not res: return True if self.__filterTrackingCookies and cookie.name().startsWith(b"__utm"): return True return False def acceptPolicy(self): """ Public method to get the accept policy. @return current accept policy """ if not self.__loaded: self.__load() return self.__acceptCookies def setAcceptPolicy(self, policy): """ Public method to set the accept policy. @param policy accept policy to be set """ if not self.__loaded: self.__load() if policy > self.AcceptMax: return if policy == self.__acceptCookies: return self.__acceptCookies = policy self.__saveTimer.changeOccurred() def keepPolicy(self): """ Public method to get the keep policy. @return keep policy """ if not self.__loaded: self.__load() return self.__keepCookies def setKeepPolicy(self, policy): """ Public method to set the keep policy. @param policy keep policy to be set """ if not self.__loaded: self.__load() if policy > self.KeepMax: return if policy == self.__keepCookies: return self.__keepCookies = policy self.__saveTimer.changeOccurred() def blockedCookies(self): """ Public method to return the list of blocked domains. @return list of blocked domains (list of strings) """ if not self.__loaded: self.__load() return self.__exceptionsBlock def allowedCookies(self): """ Public method to return the list of allowed domains. @return list of allowed domains (list of strings) """ if not self.__loaded: self.__load() return self.__exceptionsAllow def allowForSessionCookies(self): """ Public method to return the list of allowed session cookie domains. @return list of allowed session cookie domains (list of strings) """ if not self.__loaded: self.__load() return self.__exceptionsAllowForSession def setBlockedCookies(self, list_): """ Public method to set the list of blocked domains. @param list_ list of blocked domains (list of strings) """ if not self.__loaded: self.__load() self.__exceptionsBlock = list_[:] self.__exceptionsBlock.sort() self.__saveTimer.changeOccurred() def setAllowedCookies(self, list_): """ Public method to set the list of allowed domains. @param list_ list of allowed domains (list of strings) """ if not self.__loaded: self.__load() self.__exceptionsAllow = list_[:] self.__exceptionsAllow.sort() self.__saveTimer.changeOccurred() def setAllowForSessionCookies(self, list_): """ Public method to set the list of allowed session cookie domains. @param list_ list of allowed session cookie domains (list of strings) """ if not self.__loaded: self.__load() self.__exceptionsAllowForSession = list_[:] self.__exceptionsAllowForSession.sort() self.__saveTimer.changeOccurred() def filterTrackingCookies(self): """ Public method to get the filter tracking cookies flag. @return filter tracking cookies flag (boolean) """ return self.__filterTrackingCookies def setFilterTrackingCookies(self, filterTrackingCookies): """ Public method to set the filter tracking cookies flag. @param filterTrackingCookies filter tracking cookies flag (boolean) """ if filterTrackingCookies == self.__filterTrackingCookies: return self.__filterTrackingCookies = filterTrackingCookies self.__saveTimer.changeOccurred() def __isOnDomainList(self, rules, domain): """ Private method to check, if either the rule matches the domain exactly or the domain ends with ".rule". @param rules list of rules (list of strings) @param domain domain name to check (string) @return flag indicating a match (boolean) """ for rule in rules: if rule.startswith("."): if domain.endswith(rule): return True withoutDot = rule[1:] if domain == withoutDot: return True else: domainEnding = domain[-(len(rule) + 1):] if (domainEnding and domainEnding[0] == "." and domain.endswith(rule)): return True if rule == domain: return True return False def __matchDomain(self, cookieDomain, siteDomain): """ Private method to check, if a URLs host matches a cookie domain according to RFC 6265. @param cookieDomain domain of the cookie @type str @param siteDomain domain or host of an URL @type str @return flag indicating a match @rtype bool """ if not siteDomain: # empty URLs always match return True if cookieDomain.startswith("."): cookieDomain = cookieDomain[1:] if siteDomain.startswith("."): siteDomain = siteDomain[1:] if cookieDomain == siteDomain: return True if not siteDomain.endswith(cookieDomain): return False index = siteDomain.find(cookieDomain) return index > 0 and siteDomain[index - 1] == "." def cookies(self): """ Public method to get the cookies of the cookie jar. @return list of all cookies (list of QNetworkCookie) """ if not self.__loaded: self.__load() return self.allCookies() def cookieDomains(self): """ Public method to get a list of all domains used by the cookies. @return list of domain names @rtype list of str """ domains = [] for cookie in self.cookies(): domain = cookie.domain() if domain not in domains: domains.append(domain) return domains