class Scene(QtGui.QGraphicsScene): def __init__(self): super(QtGui.QGraphicsScene, self).__init__() self.view = QtGui.QGraphicsView(self) self.webview = QGraphicsWebView() self.webview.setFlags(QtGui.QGraphicsItem.ItemClipsToShape) self.webview.setCacheMode(QtGui.QGraphicsItem.NoCache) self.addItem(self.webview) self.webview.loadFinished.connect(self.svgLoaded) def svgLoaded(self): frame = self.webview.page().mainFrame() fsize = frame.contentsSize() self.webview.resize(QtCore.QSizeF(fsize)) self.view.resize(fsize.width() + 10, fsize.height() + 10) self.webview.page().mainFrame().evaluateJavaScript(getJsScript('circle-test.js'))
class ARPAP_SpatialReportDialogChart(QtGui.QDialog, FORM_CLASS): webview = None chartTypes = dict() algorithm = None reslayer = list() modelCategory = None modelValue = None validation = None chartGenerated = False mapChartType = {'bar':bar,'pie':pie} def __init__(self, parent=None): super(ARPAP_SpatialReportDialogChart, self).__init__(parent) self.parent = parent self.algorithm = parent.algorithm self.reslayer = parent.reslayer self.validation = ValidationInputdata(self,self.tr) self.setupUi(self) self.manageGui() def manageGui(self): self.chartGeneratorButton.setIcon(QtGui.QIcon(':/plugins/ARPAP_SpatialReport/icons/histogram.png')) self.statisticsGeneratorButton.setIcon(QtGui.QIcon(':/plugins/ARPAP_SpatialReport/icons/mActionOpenTable.png')) self.savePngFile.setIcon(QtGui.QIcon(':/plugins/ARPAP_SpatialReport/icons/mActionFileSave.png')) self.saveCSVFile.setIcon(QtGui.QIcon(':/plugins/ARPAP_SpatialReport/icons/mActionFileSave.png')) QObject.connect(self.chartGeneratorButton, SIGNAL('clicked()'),self.generateChart) QObject.connect(self.statisticsGeneratorButton, SIGNAL('clicked()'),self.generateStatistics) QObject.connect(self.savePngFile, SIGNAL('clicked()'),self.savepng) QObject.connect(self.saveCSVFile, SIGNAL('clicked()'),self.saveCSV) self.populateChartTypesCombo() self.populateCombosField() #manage resizeEvent QWebview def onResizeWebview(e): if self.chartGenerated: self.generateChart() self.graphicsView.resizeEvent = onResizeWebview def populateChartTypesCombo(self): self.chartTypes = { 'bar':self.tr('Bar (occurrences)'), 'pie':self.tr('Pie (distribution)'), } for type in self.chartTypes.keys(): self.selectChartType.addItem(self.chartTypes[type],type) def getChartType(self): return self.selectChartType.itemData(self.selectChartType.currentIndex()) def getSelectedListViewItem(self,listViewName): model_index = getattr(self,listViewName.lower()+'FieldListView').currentIndex() if model_index.row() != -1: return getattr(self,'model'+listViewName.capitalize()).itemFromIndex(model_index) def populateCombosField(self): layer = self.reslayer[0] #populate category listview self.modelCategory = QtGui.QStandardItemModel(self.categoryFieldListView) self.modelValue = QtGui.QStandardItemModel(self.valueFieldListView) self.categoryFieldListView.setModel(self.modelCategory) self.valueFieldListView.setModel(self.modelValue) for field in layer.pendingFields(): itemCategory = QtGui.QStandardItem(self.parent.formatFieldToString(field)) itemCategory.setData(field) itemValue = QtGui.QStandardItem(self.parent.formatFieldToString(field)) itemValue.setData(field) self.modelCategory.appendRow(itemCategory) self.modelValue.appendRow(itemValue) def _purificateValues(self,value): if type(value) == QDate: value = value.toString() elif type(value) == QPyNullVariant: value = None return value def generateChart(self): scene = QtGui.QGraphicsScene() self.graphicsView.setScene(scene) #select data adn chart type chartType = self.getChartType() if self.validation.validateChart(chartType): chart = self.mapChartType[chartType]() self.webview = QGraphicsWebView() self.webview.resize(self.graphicsView.width()-20,self.graphicsView.height()-20) path = os.path.dirname(__file__)+'/js/' chartData = getattr(self,chartType + 'ChartData' )() chart.setData(chartData) self.webview.setHtml(chart.getHTML(),baseUrl=QUrl().fromLocalFile(path)) self.webview.setFlags(QtGui.QGraphicsItem.ItemClipsToShape) self.webview.setCacheMode(QtGui.QGraphicsItem.NoCache) frame = self.webview.page().mainFrame() frame.setScrollBarPolicy(Qt.Vertical,Qt.ScrollBarAlwaysOff) frame.setScrollBarPolicy(Qt.Horizontal,Qt.ScrollBarAlwaysOff) scene.addItem(self.webview) self.graphicsView.show() self.savePngFile.setEnabled(True) self.chartGenerated = True else: self.showValidateErrors() def barChartData(self): categoryItem = self.getSelectedListViewItem('category') layer = self.reslayer[0] features = layer.getFeatures() occourences = dict() for f in features: value = self._purificateValues(f.attribute(categoryItem.data().name())) if value not in occourences: occourences[value] = 0 occourences[value] += 1 chartData = {'values':[{'label':k,'value':occourences[k]} for k in occourences.keys()]} return [chartData] def pieChartData(self): categoryItem = self.getSelectedListViewItem('category') valueItem = self.getSelectedListViewItem('value') layer = self.reslayer[0] features = layer.getFeatures() occourences = dict() totValue = 0 for f in features: key = f.attribute(categoryItem.data().name()) if not key: key = self.tr('Unknown') value = self._purificateValues(f.attribute(valueItem.data().name())) if key not in occourences: occourences[key] = 0 if not value: value = 0 occourences[key] += value totValue += value chartData = [{'key':k,'y':float(float(occourences[k])/float(totValue))} for k in occourences.keys()] return chartData def generateStatistics(self): categoryItem = self.getSelectedListViewItem('category') valueItem = self.getSelectedListViewItem('value') layer = self.reslayer[0] features = layer.getFeatures() occourences = dict() for f in features: key = self._purificateValues(f.attribute(categoryItem.data().name())) if not key: key = self.tr('Unknown') value = self._purificateValues(f.attribute(valueItem.data().name())) if key not in occourences: occourences[key] = list() if not value: value = 0 occourences[key].append(value) # statistics calcs self.statistics = dict() model = QtGui.QStandardItemModel(self.statisticsTableView) self.statisticsTableView.setModel(model) c = 0 self.headersFieldsTableMethods = { self.tr('Sum'):sum, self.tr('Min'):min, self.tr('Max'):max, self.tr('Median'):numpy.median, self.tr('Mean'):numpy.mean, self.tr('Standard deviation'):numpy.std } print occourences for columnName in self.headersFieldsTableMethods: model.setHorizontalHeaderItem(self.headersFieldsTableMethods.keys().index(columnName),QtGui.QStandardItem(columnName)) for key in occourences: if key not in self.statistics: self.statistics[key] = dict() rowToAppend = list() for func in self.headersFieldsTableMethods: self.statistics[key][func] = str(self.headersFieldsTableMethods[func](occourences[key])) rowToAppend.append(QtGui.QStandardItem(self.statistics[key][func])) model.appendRow(rowToAppend) model.setVerticalHeaderItem(c,QtGui.QStandardItem(str(key))) c += 1 self.saveCSVFile.setEnabled(True) def sum(self,data): return sum(data) def min(self,data): return min(data) def max(self,data): return max(data) def savepng(self): dialog = QtGui.QFileDialog() dialog.setAcceptMode(1) dialog.setDefaultSuffix("png") dialog.setNameFilters(["PNG files (*.png)", "All files (*)"]) if dialog.exec_() == 0: return pathFileToSave = dialog.selectedFiles()[0] p = QtGui.QPixmap.grabWidget(self.graphicsView) res = p.save(pathFileToSave) def saveCSV(self): dialog = QtGui.QFileDialog() dialog.setAcceptMode(1) dialog.setDefaultSuffix("csv") dialog.setNameFilters(["CSV files (*.csv)", "All files (*)"]) if dialog.exec_() == 0: return pathFileToSave = dialog.selectedFiles()[0] fileCSV = open(pathFileToSave, 'wb') compilatorCSV = csv.writer( fileCSV, delimiter=';' ) header = [''] header.extend(self.headersFieldsTableMethods.keys()) compilatorCSV.writerow(header) for i in self.statistics: row = [i] dataRow = [x.encode('utf-8') for x in self.statistics[i].values()] row.extend(dataRow) compilatorCSV.writerow(row) fileCSV.close() def showValidateErrors(self): QtGui.QMessageBox.warning( self, self.tr("ARPA Spatial Report"), self.tr( "Validation error:\n" ) + ';\n'.join(self.validation.getErrors()) )
class Lectern(QMainWindow): def __init__(self, parent=None): super(Lectern, self).__init__(parent) self.anchor = None self.initMainMenu() self.initToolbar() splitter = QSplitter() self.tocView = QTreeView() self.tocView.clicked.connect(self.navTo) self.tocModel = TableOfContents() self.tocModel.isEmpty.connect(self.handleTOCLoad) self.tocView.setModel(self.tocModel) self.tocView.expandAll() self.tocView.hide() splitter.addWidget(self.tocView) self.webView = QGraphicsWebView() frame = self.webView.page().mainFrame() scene = QGraphicsScene() scene.addItem(self.webView) self.graphicsView = GraphicsView(scene) self.graphicsView.setFrameShape(QFrame.NoFrame) glWidget = QGLWidget(self) self.graphicsView.setViewport(glWidget) self.webView.loadFinished.connect(self.handleLoad) splitter.addWidget(self.graphicsView) self.setCentralWidget(splitter) self.ebook_info = {} self.setWindowTitle('Lectern') try: self.ebook_info = self.openBook(QApplication.arguments()[1]) except IndexError: pass def initMainMenu(self): menuBar = self.menuBar() menuBar.setNativeMenuBar(True) # TODO: add CROSS-PLATFORM shortcut keys. (e.g. For Quit, use ⌘Q on Mac OS X, ALT-F4 elsewhere) fileMenu = QMenu('File', menuBar) navMenu = QMenu('Navigate', menuBar) # File Menu openAction = QAction('Open', fileMenu) openAction.triggered.connect(self.chooseEbook) fileMenu.addAction(openAction) quitAction = QAction('Quit', fileMenu) quitAction.triggered.connect(self.closeEvent) fileMenu.addAction(quitAction) # Nav Menu prevChatperAction = QAction('Previous Chapter', navMenu) prevChatperAction.triggered.connect(self.prevChapter) navMenu.addAction(prevChatperAction) nextChatperAction = QAction('Next Chapter', navMenu) nextChatperAction.triggered.connect(self.nextChapter) navMenu.addAction(nextChatperAction) menuBar.addMenu(fileMenu) menuBar.addMenu(navMenu) def initToolbar(self): toolBar = QToolBar(self) chooseAction = QAction(self.style().standardIcon( QStyle.SP_DialogOpenButton), 'Open', toolBar) chooseAction.triggered.connect(self.chooseEbook) toolBar.addAction(chooseAction) self.prevAction = QAction(self.style().standardIcon( QStyle.SP_ArrowBack), 'Go back', toolBar) self.prevAction.setEnabled(False) self.prevAction.triggered.connect(self.prevChapter) toolBar.addAction(self.prevAction) self.nextAction = QAction(self.style().standardIcon( QStyle.SP_ArrowForward), 'Go forward', toolBar) self.nextAction.setEnabled(False) self.nextAction.triggered.connect(self.nextChapter) toolBar.addAction(self.nextAction) self.addToolBar(toolBar) def chooseEbook(self): path = QFileDialog.getOpenFileName(self, 'Open eBook', QDesktopServices.storageLocation( QDesktopServices.DocumentsLocation),'EPUBs (*.epub)') if not isfile(path): return if self.ebook_info is not None and 'temp_path' in self.ebook_info: if exists(self.ebook_info['temp_path']): rmtree(self.ebook_info['temp_path']) path = QDir.toNativeSeparators(path) self.ebook_info = self.openBook(path) def openBook(self, path): ebook_info = {} path = realpath(path) if not isfile(path): QMessageBox.critical(self, 'File not found', 'File not found') mimetype, _ = guess_type(path) if mimetype != 'application/epub+zip': QMessageBox.critical(self, 'Not an EPUB', 'Not an EPUB') return None ebook = ZipFile(path) names = ebook.namelist() if not 'META-INF/container.xml' in names: ebook.close() QMessageBox.critical(self, 'Invalid EPUB', 'container.xml not '\ 'found') return None container_tree = etree.parse(ebook.open('META-INF/container.xml')) rootfile = container_tree.xpath("//*[local-name() = 'rootfile']") if len(rootfile) == 0: ebook.close() QMessageBox.critical(self, 'Invalid EPUB', 'root not found in '\ 'manifest') return None content_opf = rootfile[0].get('full-path') if content_opf is None: ebook.close() QMessageBox.critical(self, 'Invalid EPUB', 'content.opf not found') return None ebook_info['opf_root'] = posixpath.dirname(content_opf) tree = etree.parse(ebook.open(content_opf)) manifest = tree.xpath("*[local-name() = 'manifest']") if len(manifest) == 0: ebook.close() QMessageBox.critical(self, 'Invalid EPUB', 'Manifest not found') return None manifest = manifest[0] items = {} for item in manifest: item_id = item.get('id') if item_id is None: ebook.close() QMessageBox.critical(self, 'Invalid EPUB', 'Item has no id') return None href = item.get('href') if href is None: ebook.close() QMessageBox.critical(self, 'Invalid EPUB', 'Item has no href') return None items[item_id] = href spine = tree.xpath("*[local-name() = 'spine']") if len(spine) == 0: ebook.close() QMessageBox.critical(self, 'Invalid EPUB', 'Spine not found') return None spine = spine[0] ebook_info['chapters'] = [] for itemref in spine: idref = itemref.get('idref') if not idref in items: ebook.close() QMessageBox.critical(self, 'Invalid EPUB', 'Item in spine '\ 'not found in manifest') return None ebook_info['chapters'].append(items[idref]) if len(ebook_info['chapters']) == 0: ebook.close() QMessageBox.critical(self, 'Invalid EPUB', 'Content not found') return None # Table of contents toc = tree.find("//*[@href='toc.ncx']") if toc is not None: toc_path = posixpath.join(ebook_info['opf_root'], 'toc.ncx') if toc_path in names: toc_tree = etree.parse(ebook.open(toc_path)) navMap = toc_tree.xpath("//*[local-name() = 'navMap']") if len(navMap) > 0: self.tocModel.importNavMap(navMap[0]) temp = QDir.toNativeSeparators(QDesktopServices.storageLocation( QDesktopServices.TempLocation)) # In case we have two copies of Lectern opening the same book. filename = '{0}-{1}'.format(splitext(basename(path))[0], uuid4()) ebook_info['temp_path'] = join(temp, filename) if exists(ebook_info['temp_path']): rmtree(ebook_info['temp_path']) ebook.extractall(ebook_info['temp_path']) ebook.close() ebook_info['index'] = 0 url = join(ebook_info['temp_path'], ebook_info['opf_root'], ebook_info['chapters'][0]) self.webView.setUrl(QUrl(url)) if len(ebook_info['chapters']) > 1: self.nextAction.setEnabled(True) return ebook_info def prevChapter(self): index = self.ebook_info['index'] chapters = self.ebook_info['chapters'] if index > 0: index -= 1 if index == 0: self.prevAction.setEnabled(False) url = join(self.ebook_info['temp_path'], self.ebook_info['opf_root'], chapters[index]) self.webView.setUrl(QUrl(url)) self.ebook_info['index'] = index self.nextAction.setEnabled(True) def nextChapter(self): index = self.ebook_info['index'] chapters = self.ebook_info['chapters'] if index < len(chapters) - 1: index += 1 if index == len(chapters) - 1: self.nextAction.setEnabled(False) url = join(self.ebook_info['temp_path'], self.ebook_info['opf_root'], chapters[index]) self.webView.setUrl(QUrl(url)) self.ebook_info['index'] = index self.prevAction.setEnabled(True) def closeBook(self): if self.ebook_info is not None and 'temp_path' in self.ebook_info: if exists(self.ebook_info['temp_path']): rmtree(self.ebook_info['temp_path']) self.ebook_info = None self.tocView.hide() self.prevAction.setEnabled(False) self.nextAction.setEnabled(False) def closeEvent(self, event = 0): if(event == 0): event = PyQt4.QtGui.QCloseEvent() self.closeBook() super(Lectern, self).closeEvent(event) # Suppress "cannot make invalid context current" warnings sys.exit(0) def navTo(self, index): navPoint = index.internalPointer() href = posixpath.join(self.ebook_info['temp_path'], self.ebook_info['opf_root'], navPoint.src) try: path, anchor = href.split('#') if path == self.webView.url().path(): self.webView.page().mainFrame().scrollToAnchor(anchor) return else: self.anchor = anchor except ValueError: pass url = QUrl.fromEncoded(href) self.webView.setUrl(url) def handleLoad(self, ok): if self.anchor is not None: self.webView.page().mainFrame().addToJavaScriptWindowObject("app", self); self.webView.page().mainFrame().scrollToAnchor(self.anchor) def handleTOCLoad(self, isEmpty): if isEmpty: self.tocView.hide() else: self.tocView.show()