def __init__(self): """ Default constructor. """ self._text_editor = TextEditor() self._memento_list = [] self._prev_state_ptr = -1
def view_xml(self, m=None): if not self.dock_window: self.dock_window = TextEditor(self) self.iface.mainWindow().addDockWidget(Qt.BottomDockWidgetArea, self.dock_window) if not self.using_mapnik: # http://trac.osgeo.org/qgis/changeset/12955 - render starting signal QObject.connect(self.canvas, SIGNAL("renderComplete(QPainter *)"), self.checkLayers) self.dock_window.show() if self.loaded_mapfile: # if we have loaded a map xml or mml # so lets just display the active file xml = open(self.loaded_mapfile, 'rb').read() else: if not m: # regenerate from qgis objects e_c = sync.EasyCanvas(self, self.canvas) m = e_c.to_mapnik() if hasattr(mapnik, 'save_map_to_string'): xml = mapnik.save_map_to_string(m) else: (handle, mapfile) = tempfile.mkstemp('.xml', 'quantumnik-map-') os.close(handle) mapnik.save_map(m, str(mapfile)) xml = open(mapfile, 'rb').read() e = self.canvas.extent() bbox = '%s %s %s %s' % (e.xMinimum(), e.yMinimum(), e.xMaximum(), e.yMaximum()) cmd = '\n<!-- nik2img.py mapnik.xml out.png -d %s %s -e %s -->\n' % ( self.canvas.width(), self.canvas.height(), bbox) try: if self.mapnik_map: cmd += '<!-- <MinScaleDenominator>%s</MinScaleDenominator> -->\n' % ( self.mapnik_map.scale_denominator()) except: pass code = xml + cmd if HIGHLIGHTING: highlighted = highlight( code, XmlLexer(), HtmlFormatter(linenos=False, nowrap=False, full=True)) self.dock_window.textEdit.setHtml(highlighted) else: self.dock_window.textEdit.setText(xml + cmd)
def text_editor ( self, ui, object, name, description, parent ): return TextEditor( parent, factory = self, ui = ui, object = object, name = name, description = description )
def test_symbols(self): self.app.simulate_typing('foo') self.app.simulate_keystroke(curses.ascii.ESC) # quit self.app = TextEditor(self.args) # re-open self.app.simulate_typing('~ ~') self.app.simulate_keystroke(curses.ascii.ESC) # quit self.outfile.seek(0) self.assertEqual(self.outfile.read(), "~ ~foo")
def __init__(self, msg_service: MessageService): self.doc = Doc() self.doc.site = int(random.getrandbits(32)) self.patch_set = [] self.msg_service = msg_service self.text_field = TextEditor( scrollbar=True, line_numbers=False, search_field=SearchToolbar(), key_bindings=self.__init_bindings(), lexer=AuthorLexer(self.doc) )
def view_xml(self, m=None): if not self.dock_window: self.dock_window = TextEditor(self) self.iface.mainWindow().addDockWidget(Qt.BottomDockWidgetArea, self.dock_window) if not self.using_mapnik: # http://trac.osgeo.org/qgis/changeset/12955 - render starting signal QObject.connect(self.canvas, SIGNAL("renderComplete(QPainter *)"), self.checkLayers) self.dock_window.show() if self.loaded_mapfile: # if we have loaded a map xml or mml # so lets just display the active file xml = open(self.loaded_mapfile, "rb").read() else: if not m: # regenerate from qgis objects e_c = sync.EasyCanvas(self, self.canvas) m = e_c.to_mapnik() if hasattr(mapnik, "save_map_to_string"): xml = mapnik.save_map_to_string(m) else: (handle, mapfile) = tempfile.mkstemp(".xml", "quantumnik-map-") os.close(handle) mapnik.save_map(m, str(mapfile)) xml = open(mapfile, "rb").read() e = self.canvas.extent() bbox = "%s %s %s %s" % (e.xMinimum(), e.yMinimum(), e.xMaximum(), e.yMaximum()) cmd = "\n<!-- nik2img.py mapnik.xml out.png -d %s %s -e %s -->\n" % ( self.canvas.width(), self.canvas.height(), bbox, ) try: if self.mapnik_map: cmd += "<!-- <MinScaleDenominator>%s</MinScaleDenominator> -->\n" % ( self.mapnik_map.scale_denominator() ) except: pass code = xml + cmd if HIGHLIGHTING: highlighted = highlight(code, XmlLexer(), HtmlFormatter(linenos=False, nowrap=False, full=True)) self.dock_window.textEdit.setHtml(highlighted) else: self.dock_window.textEdit.setText(xml + cmd)
def __init__(self, title="", label_text="", completer=None): self.future = Future() def accept_text(buf) -> bool: """ Change focus to OK button after user pressed enter on text field. """ get_app().layout.focus(ok_button) buf.complete_state = None return True def accept(): """ Set future result to text from text field if user pressed OK button. """ self.future.set_result(self.text_area.text) def cancel(): """ Set future result to None if user pressed cancel button. """ self.future.set_result(None) self.text_area = TextEditor( completer=completer, multiline=False, width=D(preferred=40), accept_handler=accept_text, key_bindings=load_key_bindings(), ) ok_button = Button(text="OK", handler=accept) cancel_button = Button(text="Cancel", handler=cancel) self.dialog = Dialog( title=title, body=HSplit([Label(text=label_text), self.text_area]), buttons=[ok_button, cancel_button], width=D(preferred=80), modal=True, )
def setUp(self): self.outfile = tempfile.NamedTemporaryFile() self.args = ['text_editor.py', self.outfile.name] self.app = TextEditor(self.args) super(TextEditorTest, self).setUp()
class TextEditorTest(AudioAppTest): def setUp(self): self.outfile = tempfile.NamedTemporaryFile() self.args = ['text_editor.py', self.outfile.name] self.app = TextEditor(self.args) super(TextEditorTest, self).setUp() def test_simple(self): # should speak previous word after non-alpha character self.app.simulate_typing('hello world ') self.assertJustSaid('world') # should speak character just deleted self.app.simulate_keystroke(curses.ascii.DEL) self.assertJustSaid('space') # should play tone when attempting to move cursor beyond bounds self.app.simulate_keystroke(curses.KEY_RIGHT) self.assertJustSaid('[tone]') for i in range(5): # move cursor five characters left self.app.simulate_keystroke(curses.KEY_LEFT) self.app.simulate_typing('awesome ') self.app.simulate_keystroke(curses.ascii.ESC) # quit self.outfile.seek(0) # check that contents were properly saved self.assertEqual(self.outfile.read(), "hello awesome world") def test_symbols(self): self.app.simulate_typing('foo') self.app.simulate_keystroke(curses.ascii.ESC) # quit self.app = TextEditor(self.args) # re-open self.app.simulate_typing('~ ~') self.app.simulate_keystroke(curses.ascii.ESC) # quit self.outfile.seek(0) self.assertEqual(self.outfile.read(), "~ ~foo") def test_gibberish(self): # type n random printable characters n = 100 self.app.simulate_typing( random.choice(string.printable) for i in range(n)) self.app.simulate_keystroke(curses.ascii.ESC) # quit self.outfile.seek(0) self.assertEqual(n, len(self.outfile.read())) def test_sentence(self): self.app.simulate_typing('Hello there. This is a second sentence.') self.assertJustSaid('this is a second sentence')
class Quantumnik(QObject): def __init__(self, iface): QObject.__init__(self) self.iface = iface self.canvas = iface.mapCanvas() # Fake canvas to use in tab to overlay the quantumnik layer self.qCanvas = None self.qCanvasPan = None self.qCanvasZoomIn = None self.qCanvasZoomOut = None self.tabWidget = None self.mapnik_map = None self.using_mapnik = False self.from_mapfile = False self.loaded_mapfile = None self.been_warned = False self.last_image_path = None self.dock_window = None self.keyAction = None self.keyAction2 = None self.keyAction3 = None def initGui(self): self.action = QAction(QIcon(":/mapnikglobe.png"), QString("Create Mapnik Canvas"), self.iface.mainWindow()) self.action.setWhatsThis("Create Mapnik Canvas") self.action.setStatusTip("%s: render with Mapnik" % NAME) QObject.connect(self.action, SIGNAL("triggered()"), self.toggle) self.action4 = QAction(QString("View live xml"), self.iface.mainWindow()) QObject.connect(self.action4, SIGNAL("triggered()"), self.view_xml) self.action3 = QAction(QString("Export Mapnik xml"), self.iface.mainWindow()) QObject.connect(self.action3, SIGNAL("triggered()"), self.save_xml) self.action5 = QAction(QString("Load Mapnik xml"), self.iface.mainWindow()) QObject.connect(self.action5, SIGNAL("triggered()"), self.load_xml) self.action6 = QAction(QString("Load Cascadenik mml"), self.iface.mainWindow()) QObject.connect(self.action6, SIGNAL("triggered()"), self.load_mml) self.action7 = QAction(QString("Export Map Graphics"), self.iface.mainWindow()) QObject.connect(self.action7, SIGNAL("triggered()"), self.export_image_gui) self.helpaction = QAction(QIcon(":/mapnikhelp.png"),"About", self.iface.mainWindow()) self.helpaction.setWhatsThis("%s Help" % NAME) QObject.connect(self.helpaction, SIGNAL("triggered()"), self.helprun) self.iface.addToolBarIcon(self.action) self.iface.addPluginToMenu("&%s" % NAME, self.action) self.iface.addPluginToMenu("&%s" % NAME, self.helpaction) self.iface.addPluginToMenu("&%s" % NAME, self.action3) self.iface.addPluginToMenu("&%s" % NAME, self.action4) self.iface.addPluginToMenu("&%s" % NAME, self.action5) self.iface.addPluginToMenu("&%s" % NAME, self.action6) self.iface.addPluginToMenu("&%s" % NAME, self.action7) # > QGIS 1.2 if hasattr(self.iface,'registerMainWindowAction'): self.keyAction2 = QAction(QString("Switch to QGIS"), self.iface.mainWindow()) self.iface.registerMainWindowAction(self.keyAction2, "Ctrl+[") self.iface.addPluginToMenu("&%s" % NAME, self.keyAction2) QObject.connect(self.keyAction2, SIGNAL("triggered()"),self.switch_tab_qgis) self.keyAction3 = QAction(QString("Switch to Mapnik"), self.iface.mainWindow()) self.iface.registerMainWindowAction(self.keyAction3, "Ctrl+]") self.iface.addPluginToMenu("&%s" % NAME, self.keyAction3) QObject.connect(self.keyAction3, SIGNAL("triggered()"),self.switch_tab_mapnik) def unload(self): self.iface.removePluginMenu("&%s" % NAME,self.action) self.iface.removePluginMenu("&%s" % NAME,self.helpaction) self.iface.removePluginMenu("&%s" % NAME,self.action3) self.iface.removePluginMenu("&%s" % NAME,self.action4) self.iface.removePluginMenu("&%s" % NAME,self.action5) self.iface.removePluginMenu("&%s" % NAME,self.action6) self.iface.removePluginMenu("&%s" % NAME,self.action7) self.iface.removeToolBarIcon(self.action) if self.keyAction: self.iface.unregisterMainWindowAction(self.keyAction) if self.keyAction2: self.iface.unregisterMainWindowAction(self.keyAction2) if self.keyAction3: self.iface.unregisterMainWindowAction(self.keyAction3) def export_image_gui(self): flags = Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.WindowStaysOnTopHint export = ImageExport(self,flags) export.show() def view_xml(self,m=None): if not self.dock_window: self.dock_window = TextEditor(self) self.iface.mainWindow().addDockWidget( Qt.BottomDockWidgetArea, self.dock_window ) if not self.using_mapnik: # http://trac.osgeo.org/qgis/changeset/12955 - render starting signal QObject.connect(self.canvas, SIGNAL("renderComplete(QPainter *)"), self.checkLayers) self.dock_window.show() if self.loaded_mapfile: # if we have loaded a map xml or mml # so lets just display the active file xml = open(self.loaded_mapfile,'rb').read() else: if not m: # regenerate from qgis objects e_c = sync.EasyCanvas(self,self.canvas) m = e_c.to_mapnik() if hasattr(mapnik,'save_map_to_string'): xml = mapnik.save_map_to_string(m) else: (handle, mapfile) = tempfile.mkstemp('.xml', 'quantumnik-map-') os.close(handle) mapnik.save_map(m,str(mapfile)) xml = open(mapfile,'rb').read() e = self.canvas.extent() bbox = '%s %s %s %s' % (e.xMinimum(),e.yMinimum(), e.xMaximum(),e.yMaximum()) cmd = '\n<!-- nik2img.py mapnik.xml out.png -d %s %s -e %s -->\n' % (self.canvas.width(), self.canvas.height(), bbox) try: if self.mapnik_map: cmd += '<!-- <MinScaleDenominator>%s</MinScaleDenominator> -->\n' % (self.mapnik_map.scale_denominator()) except: pass code = xml + cmd if HIGHLIGHTING: highlighted = highlight(code, XmlLexer(), HtmlFormatter(linenos=False, nowrap=False, full=True)) self.dock_window.textEdit.setHtml(highlighted) else: self.dock_window.textEdit.setText(xml + cmd) def helprun(self): infoString = QString("Written by Dane Springmeyer\nhttps://github.com/springmeyer/quantumnik") QMessageBox.information(self.iface.mainWindow(),"About %s" % NAME,infoString) def toggle(self): if self.using_mapnik: self.stop_rendering() else: self.start_rendering() def proj_warning(self): self.been_warned = True ren = self.canvas.mapRenderer() if not ren.hasCrsTransformEnabled() and self.canvas.layerCount() > 1: if hasattr(self.canvas.layer(0),'crs'): if not self.canvas.layer(0).crs().toProj4() == ren.destinationCrs().toProj4(): QMessageBox.information(self.iface.mainWindow(),"Warning","The projection of the map and the first layer do not match. Mapnik may not render the layer(s) correctly.\n\nYou likely need to either enable 'On-the-fly' CRS transformation or set the Map projection in your Project Properties to the projection of your layer(s).") else: if not self.canvas.layer(0).srs().toProj4() == ren.destinationSrs().toProj4(): QMessageBox.information(self.iface.mainWindow(),"Warning","The projection of the map and the first layer do not match. Mapnik may not render the layer(s) correctly.\n\nYou likely need to either enable 'On-the-fly' CRS transformation or set the Map projection in your Project Properties to the projection of your layer(s).") def save_xml(self): # need to expose as an option! relative_paths = True mapfile = QFileDialog.getSaveFileName(None, "Save file dialog", 'mapnik.xml', "Mapfile (*.xml)") if mapfile: e_c = sync.EasyCanvas(self,self.canvas) mapfile_ = str(mapfile) base_path = os.path.dirname(mapfile_) e_c.base_path = base_path m = e_c.to_mapnik() mapnik.save_map(m,mapfile_) if relative_paths: relativism.fix_paths(mapfile_,base_path) def make_bundle(self): pass # todo: accept directory name # move mapfile and all file based datasources # into that folder and stash some docs inside # provide option to zip and upload to url on the fly def set_canvas_from_mapnik(self): # set up keyboard shortcut # > QGIS 1.2 if hasattr(self.iface,'registerMainWindowAction'): if not self.keyAction: # TODO - hotkey does not work on linux.... self.keyAction = QAction(QString("Refresh " + NAME), self.iface.mainWindow()) self.iface.registerMainWindowAction(self.keyAction, "Ctrl+r") self.iface.addPluginToMenu("&%s" % NAME, self.keyAction) QObject.connect(self.keyAction, SIGNAL("triggered()"),self.toggle) self.mapnik_map.zoom_all() e = self.mapnik_map.envelope() crs = QgsCoordinateReferenceSystem() srs = self.mapnik_map.srs if srs == '+init=epsg:900913': # until we can look it up in srs.db... merc = "+init=EPSG:900913" crs.createFromProj4(QString(merc)) elif 'init' in srs: # TODO - quick hack, needs regex and fallbacks epsg = srs.split(':')[1] crs.createFromEpsg(int(epsg)) else: if srs == '+proj=latlong +datum=WGS84': # expand the Mapnik srs a bit # http://trac.mapnik.org/ticket/333 srs = '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs' crs.createFromProj4(QString(srs)) if hasattr(self.canvas.mapRenderer(),'setDestinationCrs'): self.canvas.mapRenderer().setDestinationCrs(crs) else: self.canvas.mapRenderer().setDestinationSrs(crs) if not crs.isValid(): QMessageBox.information(self.iface.mainWindow(), "Warning","Projection not understood") return QObject.connect(self.canvas, SIGNAL("renderComplete(QPainter *)"), self.render_dynamic) self.canvas.setExtent(QgsRectangle(e.minx,e.miny,e.maxx,e.maxy)) self.canvas.refresh() def set_mapnik_to_canvas(self): QObject.connect(self.canvas, SIGNAL("renderComplete(QPainter *)"), self.render_dynamic) self.canvas.refresh() def refresh_loaded_mapfile(self): if self.mapfile_format == 'Cascadenik mml': self.load_mml(refresh=True) else: self.load_xml(refresh=True) def load_mml(self,refresh=False): self.from_mapfile = True self.mapfile_format = 'Cascadenik mml' if self.loaded_mapfile and refresh: mapfile = self.loaded_mapfile else: mapfile = QFileDialog.getOpenFileName(None, "Open file dialog", '', "Cascadenik MML (*.mml)") if mapfile: self.mapnik_map = mapnik.Map(1,1) import cascadenik if hasattr(cascadenik,'VERSION'): major = int(cascadenik.VERSION.split('.')[0]) if major < 1: from cascadenik import compile compiled = '%s_compiled.xml' % os.path.splitext(str(mapfile))[0] open(compiled, 'w').write(compile(str(mapfile))) mapnik.load_map(self.mapnik_map, compiled) elif major == 1: output_dir = os.path.dirname(str(mapfile)) cascadenik.load_map(self.mapnik_map,str(mapfile),output_dir,verbose=False) elif major > 1: raise NotImplementedError('This nik2img version does not yet support Cascadenik > 1.x, please upgrade nik2img to the latest release') else: from cascadenik import compile compiled = '%s_compiled.xml' % os.path.splitext(str(mapfile))[0] #if os.path.exits(compiled): #pass open(compiled, 'w').write(compile(str(mapfile))) mapnik.load_map(self.mapnik_map, compiled) if self.loaded_mapfile and refresh: self.set_mapnik_to_canvas() else: self.set_canvas_from_mapnik() self.loaded_mapfile = str(mapfile) def load_xml(self,refresh=False): # TODO - consider putting into its own layer: # https://trac.osgeo.org/qgis/ticket/2392#comment:4 self.from_mapfile = True self.mapfile_format = 'xml mapfile' if self.loaded_mapfile and refresh: mapfile = self.loaded_mapfile else: mapfile = QFileDialog.getOpenFileName(None, "Open file dialog", '', "XML Mapfile (*.xml)") if mapfile: self.mapnik_map = mapnik.Map(1,1) mapnik.load_map(self.mapnik_map,str(mapfile)) if self.loaded_mapfile and refresh: self.set_mapnik_to_canvas() else: self.set_canvas_from_mapnik() self.loaded_mapfile = str(mapfile) def finishStopRender(self): self.iface.mapCanvas().setMinimumSize(QSize(0, 0)) def stop_rendering(self): # Disconnect all the signals as we disable the tool QObject.disconnect(self.qCanvas, SIGNAL("renderComplete(QPainter *)"), self.render_dynamic) QObject.disconnect(self.qCanvas, SIGNAL("xyCoordinates(const QgsPoint&)"), self.updateCoordsDisplay) QObject.disconnect(self.canvas, SIGNAL("renderComplete(QPainter *)"), self.checkLayers) QObject.disconnect(self.canvas, SIGNAL("extentsChanged()"), self.checkExtentsChanged) QObject.disconnect(self.canvas, SIGNAL("mapToolSet(QgsMapTool *)"), self.mapToolSet) self.using_mapnik = False # If the current tab is quantumnik then we need to update the extent # of the main map when exiting to make sure they are in sync if self.tabWidget.currentIndex() == 1: self.mapnikMapCoordChange() # Need to restore the main map instead of the mapnik tab tabWidgetSize = self.tabWidget.size() mapCanvasExtent = self.iface.mapCanvas().extent() self.iface.mapCanvas().setMinimumSize(tabWidgetSize) self.iface.mainWindow().setCentralWidget(self.iface.mapCanvas()) self.iface.mapCanvas().show() # Set the canvas extent to the same place it was before getting # rid of the tabs self.iface.mapCanvas().setExtent(mapCanvasExtent) self.canvas.refresh() # null out some vars self.qCanvasPan = None self.qCanvasZoomIn = None self.qCanvasZoomOut = None # We have to let the main app swizzle the screen and then # hammer it back to the size we want QTimer.singleShot(1, self.finishStopRender) def create_mapnik_map(self): if not self.been_warned: self.proj_warning() self.easyCanvas = sync.EasyCanvas(self,self.canvas) self.mapnik_map = self.easyCanvas.to_mapnik() if self.dock_window: self.view_xml(self.mapnik_map) @property def background(self): return sync.css_color(self.canvas.backgroundBrush().color()) # Provide a hack to try and find the map coordinate status bar element # to take over while the mapnik canvas is in play. def findMapCoordsStatus(self): coordStatusWidget = None sb = self.iface.mainWindow().statusBar() for x in sb.children(): # Check if we have a line edit if isinstance(x, QLineEdit): # Now check if the text does not contain a ':' if not ':' in x.text(): # we have our coord status widget coordStatusWidget = x return coordStatusWidget def finishStartRendering(self): self.tabWidget.setMinimumSize(QSize(0, 0)) self.canvas.refresh() def start_rendering(self): if self.from_mapfile and not self.canvas.layerCount(): self.refresh_loaded_mapfile() else: self.from_mapfile = False # http://trac.osgeo.org/qgis/changeset/12926 # TODO - if not dirty we don't need to create a new map from scratch... self.create_mapnik_map() # Need to create a tab widget to toss into the main window # to hold both the main canvas as well as the mapnik rendering mapCanvasSize = self.canvas.size() mapCanvasExtent = self.iface.mapCanvas().extent() newWidget = QTabWidget(self.iface.mainWindow()) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth(newWidget.sizePolicy().hasHeightForWidth()) newWidget.setSizePolicy(sizePolicy) newWidget.setSizeIncrement(QSize(0, 0)) newWidget.setBaseSize(mapCanvasSize) newWidget.resize(mapCanvasSize) # Very important: Set the min size of the tabs to the size of the # original canvas. We will then let the main app take control # and then use a one shot timer to set the min size back down. It # is a hack, but allows us to keep the canvas and tab size correct. newWidget.setMinimumSize(mapCanvasSize) # This is the new blank canvas that we will use the qpainter # from to draw the mapnik image over. self.qCanvas = QgsMapCanvas(self.iface.mainWindow()) self.qCanvas.setCanvasColor(QColor(255,255,255)) self.qCanvas.enableAntiAliasing(True) self.qCanvas.useImageToRender(False) self.qCanvas.show() # A set of map tools for the mapnik canvas self.qCanvasPan = QgsMapToolPan(self.qCanvas) self.qCanvasZoomIn = QgsMapToolZoom(self.qCanvas,False) self.qCanvasZoomOut = QgsMapToolZoom(self.qCanvas,True) self.mapToolSet(self.canvas.mapTool()) # Add the canvas items to the tabs newWidget.addTab(self.canvas, "Main Map") newWidget.addTab(self.qCanvas, "Mapnik Rendered Map") self.tabWidget = newWidget # Add the tabs as the central widget self.iface.mainWindow().setCentralWidget(newWidget) # Need to set the extent of both canvases as we have just resized # things self.canvas.setExtent(mapCanvasExtent) self.qCanvas.setExtent(mapCanvasExtent) # Hook up to the tabs changing so we can make sure to update the # rendering in a lazy way... i.e. a pan in the main canvas will # not cause a redraw in the mapnik tab until the mapnik tab # is selected. self.connect(self.tabWidget,SIGNAL("currentChanged(int)"), self.tabChanged) # Grab the maptool change signal so the mapnik canvas tool # can stay in sync. # TODO: We need to get the in/out property for the zoom tool # exposed to the python bindings. As it stands now, we can # not tell what direction the tool is going when we get this # signal and it is a zoom tool. QObject.connect(self.canvas, SIGNAL("mapToolSet(QgsMapTool *)"), self.mapToolSet) # Catch any mouse movements over the mapnik canvas and # sneek in and update the cord display ## This is a hack to find the status element to populate with xy self.mapCoords = self.findMapCoordsStatus() QObject.connect(self.qCanvas, SIGNAL("xyCoordinates(const QgsPoint&)"), self.updateCoordsDisplay) # Get the renderComplete signal for the qCanvas to allow us to # render the mapnik image over it. QObject.connect(self.qCanvas, SIGNAL("renderComplete(QPainter *)"), self.render_dynamic) # Get the renderComplete signal for the main canvas so we can tell # if there have been any layer changes and if we need to re-draw # the mapnik image. This is mainly for when the mapnik tab is # active but layer changes are happening. QObject.connect(self.canvas, SIGNAL("renderComplete(QPainter *)"), self.checkLayers) QObject.connect(self.canvas, SIGNAL("extentsChanged()"), self.checkExtentsChanged) self.using_mapnik=True # We use a single shot timer to let the main app resize the main # window with us holding a minsize we want, then we reset the # allowable min size after the main app has its turn. Hack, but # allows for the window to be rezised with a new main widget. QTimer.singleShot(1, self.finishStartRendering) def updateCoordsDisplay(self, p): if self.mapCoords: capturePyString = "%.5f,%.5f" % (p.x(),p.y()) capture_string = QString(capturePyString) self.mapCoords.setText(capture_string) def mapToolSet(self, tool): # something changed here in recent QGIS versions causing: # exceptions when closing QGIS because these objects are None if tool: if isinstance(tool,QgsMapToolPan): self.qCanvas.setMapTool(self.qCanvasPan) elif isinstance(tool,QgsMapToolZoom): # Yet another hack to find out if the tool we are using is a # zoom in or out if tool.action().text() == QString("Zoom In"): self.qCanvas.setMapTool(self.qCanvasZoomIn) else: self.qCanvas.setMapTool(self.qCanvasZoomOut) else: self.qCanvas.setMapTool(self.qCanvasPan) def switch_tab_qgis(self): if self.tabWidget: self.tabWidget.setCurrentIndex(0) def switch_tab_mapnik(self): if self.tabWidget: self.tabWidget.setCurrentIndex(1) def tabChanged(self, index): if index == 0: self.mapnikMapCoordChange() else: self.mainMapCoordChange() def mainMapCoordChange(self): # print "coordChange" self.mapnik_map = self.easyCanvas.to_mapnik(self.mapnik_map) self.qCanvas.setExtent(self.iface.mapCanvas().extent()) self.qCanvas.refresh() def mapnikMapCoordChange(self): # print "coordChange" self.canvas.setExtent(self.qCanvas.extent()) self.canvas.refresh() # Here we are checking to see if we got a new extent on the main # canvas even though we are in the mapnik tab... in that case we have # done something like zoom to full extent etc. def checkExtentsChanged(self): if self.tabWidget: if self.tabWidget.currentIndex() == 1: self.mainMapCoordChange() # Here we are checking to see if we got a render complete on the main # canvas even though we are in the mapnik tab... in that case we have # a new layer etc. def checkLayers(self, painter=None): if self.tabWidget: if self.tabWidget.currentIndex() == 1: # There was a change in the main canvas while we are viewing # the mapnik canvas (i.e. layer added/removed etc) so we # need to refresh the mapnik map self.mapnik_map = self.easyCanvas.to_mapnik(self.mapnik_map) self.qCanvas.refresh() # We also make sure the main map canvas gets put back to the # current extent of the qCanvas incase the main map got changed # as a side effect since updates to it are lazy loaded on tab # change. self.canvas.setExtent(self.qCanvas.extent()) # We make sure to update the XML viewer if # if is open if self.dock_window: self.view_xml(self.mapnik_map) if self.dock_window: self.view_xml() def render_dynamic(self, painter): if self.mapnik_map: w = painter.device().width() h = painter.device().height() # using canvas dims leads to shift in QGIS < 1.3... #w = self.canvas.width() #h = self.canvas.height() try: self.mapnik_map.resize(w,h) except: self.mapnik_map.width = w self.mapnik_map.height = h if self.qCanvas: can = self.qCanvas else: can = self.canvas try: e = can.extent() except: can = self.canvas e = can.extent() bbox = mapnik.Envelope(e.xMinimum(),e.yMinimum(), e.xMaximum(),e.yMaximum()) self.mapnik_map.zoom_to_box(bbox) im = mapnik.Image(w,h) mapnik.render(self.mapnik_map,im) if os.name == 'nt': qim = QImage() qim.loadFromData(QByteArray(im.tostring('png'))) painter.drawImage(0,0,qim) else: qim = QImage(im.tostring(),w,h,QImage.Format_ARGB32) painter.drawImage(0,0,qim.rgbSwapped()) can.refresh()
def build(self): lt = self.config.get('UI', 'display_type') dtlut = { "RPI Touch": 0, "Small Desktop": 1, "Large Desktop": 2, "Wide Desktop": 3, "RPI Full Screen": 4 } self.is_desktop = dtlut.get(lt, 0) # load the layouts for the desktop screen if self.is_desktop == 1: Builder.load_file('desktop.kv') Window.size = (1024, 768) elif self.is_desktop == 2 or self.is_desktop == 3 or self.is_desktop == 4: Builder.load_file('desktop_large.kv' if self.is_desktop == 2 else 'desktop_wide.kv') if self.is_desktop != 4: # because rpi_egl does not like to be told the size s = self.config.get('UI', 'screen_size') if s == 'auto': Window.size = (1280, 1024) if self.is_desktop == 2 else (1280, 800) elif 'x' in s: (w, h) = s.split('x') Window.size = (int(w), int(h)) p = self.config.get('UI', 'screen_pos') if p != 'auto' and ',' in p: (t, l) = p.split(',') Window.top = int(t) Window.left = int(l) Window.bind(on_request_close=self.window_request_close) else: self.is_desktop = 0 # load the layouts for rpi 7" touch screen Builder.load_file('rpi.kv') self.is_cnc = self.config.getboolean('UI', 'cnc') self.tab_top = self.config.getboolean('UI', 'tab_top') self.is_webserver = self.config.getboolean('Web', 'webserver') self.is_show_camera = self.config.getboolean('Web', 'show_video') self.is_spindle_camera = self.config.getboolean( 'General', 'is_spindle_camera') self.manual_tool_change = self.config.getboolean( 'General', 'manual_tool_change') self.wait_on_m0 = self.config.getboolean('General', 'wait_on_m0') self.is_v2 = self.config.getboolean('General', 'v2') self.comms = Comms(App.get_running_app(), self.config.getfloat('General', 'report_rate')) self.gcode_file = self.config.get('General', 'last_print_file') self.sm = ScreenManager() ms = MainScreen(name='main') self.main_window = ms.ids.main_window self.sm.add_widget(ms) self.sm.add_widget(GcodeViewerScreen(name='viewer', comms=self.comms)) self.config_editor = ConfigEditor(name='config_editor') self.sm.add_widget(self.config_editor) self.gcode_help = GcodeHelp(name='gcode_help') self.sm.add_widget(self.gcode_help) if self.is_desktop == 0: self.text_editor = TextEditor(name='text_editor') self.sm.add_widget(self.text_editor) self.blank_timeout = self.config.getint('General', 'blank_timeout') Logger.info("SmoothieHost: screen blank set for {} seconds".format( self.blank_timeout)) self.sm.bind(on_touch_down=self._on_touch) Clock.schedule_interval(self._every_second, 1) # select the file chooser to use # select which one we want from config filechooser = self.config.get('UI', 'filechooser') if self.is_desktop > 0: if filechooser != 'default': NativeFileChooser.type_name = filechooser Factory.register('filechooser', cls=NativeFileChooser) try: f = Factory.filechooser() except Exception: Logger.error( "SmoothieHost: can't use selected file chooser: {}". format(filechooser)) Factory.unregister('filechooser') Factory.register('filechooser', cls=FileDialog) else: # use Kivy filechooser Factory.register('filechooser', cls=FileDialog) # we want to capture arrow keys Window.bind(on_key_down=self._on_keyboard_down) else: # use Kivy filechooser Factory.register('filechooser', cls=FileDialog) # setup for cnc or 3d printer if self.is_cnc: if self.is_desktop < 3: # remove Extruder panel from tabpanel and tab self.main_window.ids.tabs.remove_widget( self.main_window.ids.tabs.extruder_tab) # if not CNC mode then do not show the ZABC buttons in jogrose if not self.is_cnc: self.main_window.ids.tabs.jog_rose.jogrosemain.remove_widget( self.main_window.ids.tabs.jog_rose.abc_panel) if self.is_webserver: self.webserver = ProgressServer() self.webserver.start(self, 8000) if self.is_show_camera: self.camera_url = self.config.get('Web', 'camera_url') self.sm.add_widget(CameraScreen(name='web cam')) self.main_window.tools_menu.add_widget( ActionButton(text='Web Cam', on_press=lambda x: self._show_web_cam())) if self.is_spindle_camera: if self.is_desktop in [0, 4]: try: self.sm.add_widget(SpindleCamera(name='spindle camera')) except Exception as err: self.main_window.display( 'ERROR: failed to load spindle camera. Check logs') Logger.error( 'Main: spindle camera exception: {}'.format(err)) self.main_window.tools_menu.add_widget( ActionButton(text='Spindle Cam', on_press=lambda x: self._show_spindle_cam())) # load any modules specified in config self._load_modules() if self.blank_timeout > 0: # unblank if blanked self.unblank_screen() return self.sm
# Za povratke vaše # # O, igračke drage # Iz djetinjstva mog""" # "This is the longest line ever in the world here ever longest really " # "longest too long for ordinary people for ordinary world too too long for anything in reality for real cmon " # "too long really too too too long\n" # "This is the longest line ever in the world here ever longest really " # "longest too long for ordinary people for ordinary world too too long for anything in reality for real cmon " # "too long really too too too long\n" # "This is the longest line ever in the world here ever longest really " # "longest too long for ordinary people for ordinary world too too long for anything in reality for real cmon " # "too long really too too too long\n" "The first line.\n" "In between the two...\n" "The last line.") root = Tk() tedi = TextEditor(model) root.geometry("555x800+300+300") root.mainloop() """ "Vidimo da osnovni razred omogućava da grafički podsustav samostalno poziva naš kod za crtanje kad god se za to javi potreba, iako je oblikovan i izveden davno prije naše grafičke komponente. Koji oblikovni obrazac to omogućava?" --> TODO "Vidimo također da naša grafička komponenta preko osnovnog razreda može dobiti informacije o pritisnutim tipkama bez potrebe za čekanjem u radnoj petlji. Koji oblikovni obrazac to omogućava?" --> Observer """
def __init__(self): """Initialize Tab with layout and behavior.""" super(Tab, self).__init__() # regex pattern for SQL query block comments self.block_comment_re = re.compile( r'(^)?[^\S\n]*/(?:\*(.*?)\*/[^\S\n]*|/[^\n]*)($)?', re.DOTALL | re.MULTILINE) main_layout = QVBoxLayout(self) # define gdb props self.gdb = None self.gdb_items = None self.gdb_columns_names = None self.gdb_schemas = None # connected geodatabase path toolbar self.connected_gdb_path_label = QLabel('') self.connected_gdb_path_label.setTextInteractionFlags( Qt.TextSelectableByMouse) self.connected_gdb_path_label.setToolTip( 'Connected geodatabase that queries will be run against') self.connected_gdb_path_label.setText(not_connected_to_gdb_message) self.browse_to_gdb = QPushButton('Browse') self.browse_to_gdb.setShortcut(QKeySequence('Ctrl+B')) self.browse_to_gdb.clicked.connect( lambda evt, arg=True: self.connect_to_geodatabase( evt, triggered_with_browse=True)) self.gdb_sql_dialect_combobox = QComboBox() for dialect in sql_dialects_names: self.gdb_sql_dialect_combobox.addItem(dialect) self.gdb_browse_toolbar = QToolBar() self.gdb_browse_toolbar.setMaximumHeight(50) self.gdb_browse_toolbar.addWidget(self.browse_to_gdb) self.gdb_browse_toolbar.addWidget(self.connected_gdb_path_label) self.gdb_browse_toolbar.addSeparator() self.gdb_browse_toolbar.addWidget(self.gdb_sql_dialect_combobox) # table with results self.table = ResultTable() # execute SQL query self.execute = QAction('Execute', self) self.execute.setShortcuts( [QKeySequence('F5'), QKeySequence('Ctrl+Return')]) self.execute.triggered.connect(self.run_query) self.addAction(self.execute) # enter a SQL query self.query = TextEditor() self.query.setPlainText('') font = self.query.font() font.setFamily('Consolas') font.setStyleHint(QFont.Monospace) # TODO: add line numbers to the text editor font.setPointSize(14) self.query.setFont(font) self.query.setTabStopWidth(20) self.highlighter = Highlighter(self.query.document()) # TODO select block of text - Ctrl+/ and they become comments self.completer = Completer() self.query.set_completer(self.completer.completer) # errors panel to show if query fails to execute properly self.errors_panel = QPlainTextEdit() font = self.query.font() font.setPointSize(12) self.errors_panel.setStyleSheet('color:red') self.errors_panel.setFont(font) self.errors_panel.hide() # splitter between the toolbar, query window, and the result set table splitter = QSplitter(Qt.Vertical) splitter.addWidget(self.gdb_browse_toolbar) splitter.addWidget(self.query) splitter.addWidget(self.table) splitter.addWidget(self.errors_panel) # add the settings after the widget have been added splitter.setCollapsible(0, True) splitter.setCollapsible(1, False) splitter.setCollapsible(2, False) splitter.setCollapsible(3, False) splitter.setStretchFactor(0, 3) splitter.setStretchFactor(1, 7) splitter.setSizes((100, 200, 300)) self.table.hide() # TOC self.toc = QTreeWidget() self.toc.setHeaderHidden(True) # second splitter between the TOC to the left and the query/table to the # right toc_splitter = QSplitter(Qt.Horizontal) toc_splitter.addWidget(self.toc) toc_splitter.addWidget(splitter) toc_splitter.setCollapsible(0, True) toc_splitter.setSizes((200, 800)) # set the TOC vs data panel main_layout.addWidget(toc_splitter) margins = QMargins() margins.setBottom(10) margins.setLeft(10) margins.setRight(10) margins.setTop(10) main_layout.setContentsMargins(margins) self.setLayout(main_layout) QApplication.setStyle(QStyleFactory.create('Cleanlooks')) self.show() return
class Tab(QWidget): """Tab in the QTableWidget where user executes query and sees the result.""" # ---------------------------------------------------------------------- def __init__(self): """Initialize Tab with layout and behavior.""" super(Tab, self).__init__() # regex pattern for SQL query block comments self.block_comment_re = re.compile( r'(^)?[^\S\n]*/(?:\*(.*?)\*/[^\S\n]*|/[^\n]*)($)?', re.DOTALL | re.MULTILINE) main_layout = QVBoxLayout(self) # define gdb props self.gdb = None self.gdb_items = None self.gdb_columns_names = None self.gdb_schemas = None # connected geodatabase path toolbar self.connected_gdb_path_label = QLabel('') self.connected_gdb_path_label.setTextInteractionFlags( Qt.TextSelectableByMouse) self.connected_gdb_path_label.setToolTip( 'Connected geodatabase that queries will be run against') self.connected_gdb_path_label.setText(not_connected_to_gdb_message) self.browse_to_gdb = QPushButton('Browse') self.browse_to_gdb.setShortcut(QKeySequence('Ctrl+B')) self.browse_to_gdb.clicked.connect( lambda evt, arg=True: self.connect_to_geodatabase( evt, triggered_with_browse=True)) self.gdb_sql_dialect_combobox = QComboBox() for dialect in sql_dialects_names: self.gdb_sql_dialect_combobox.addItem(dialect) self.gdb_browse_toolbar = QToolBar() self.gdb_browse_toolbar.setMaximumHeight(50) self.gdb_browse_toolbar.addWidget(self.browse_to_gdb) self.gdb_browse_toolbar.addWidget(self.connected_gdb_path_label) self.gdb_browse_toolbar.addSeparator() self.gdb_browse_toolbar.addWidget(self.gdb_sql_dialect_combobox) # table with results self.table = ResultTable() # execute SQL query self.execute = QAction('Execute', self) self.execute.setShortcuts( [QKeySequence('F5'), QKeySequence('Ctrl+Return')]) self.execute.triggered.connect(self.run_query) self.addAction(self.execute) # enter a SQL query self.query = TextEditor() self.query.setPlainText('') font = self.query.font() font.setFamily('Consolas') font.setStyleHint(QFont.Monospace) # TODO: add line numbers to the text editor font.setPointSize(14) self.query.setFont(font) self.query.setTabStopWidth(20) self.highlighter = Highlighter(self.query.document()) # TODO select block of text - Ctrl+/ and they become comments self.completer = Completer() self.query.set_completer(self.completer.completer) # errors panel to show if query fails to execute properly self.errors_panel = QPlainTextEdit() font = self.query.font() font.setPointSize(12) self.errors_panel.setStyleSheet('color:red') self.errors_panel.setFont(font) self.errors_panel.hide() # splitter between the toolbar, query window, and the result set table splitter = QSplitter(Qt.Vertical) splitter.addWidget(self.gdb_browse_toolbar) splitter.addWidget(self.query) splitter.addWidget(self.table) splitter.addWidget(self.errors_panel) # add the settings after the widget have been added splitter.setCollapsible(0, True) splitter.setCollapsible(1, False) splitter.setCollapsible(2, False) splitter.setCollapsible(3, False) splitter.setStretchFactor(0, 3) splitter.setStretchFactor(1, 7) splitter.setSizes((100, 200, 300)) self.table.hide() # TOC self.toc = QTreeWidget() self.toc.setHeaderHidden(True) # second splitter between the TOC to the left and the query/table to the # right toc_splitter = QSplitter(Qt.Horizontal) toc_splitter.addWidget(self.toc) toc_splitter.addWidget(splitter) toc_splitter.setCollapsible(0, True) toc_splitter.setSizes((200, 800)) # set the TOC vs data panel main_layout.addWidget(toc_splitter) margins = QMargins() margins.setBottom(10) margins.setLeft(10) margins.setRight(10) margins.setTop(10) main_layout.setContentsMargins(margins) self.setLayout(main_layout) QApplication.setStyle(QStyleFactory.create('Cleanlooks')) self.show() return # ---------------------------------------------------------------------- def connect_to_geodatabase(self, evt, triggered_with_browse=True): """Connect to geodatabase by letting user browse to a gdb folder.""" if triggered_with_browse: gdb_connect_dialog = QFileDialog(self) gdb_connect_dialog.setFileMode(QFileDialog.Directory) gdb_path = gdb_connect_dialog.getExistingDirectory() # TODO: add a filter to show only .gdb folders? # https://stackoverflow.com/questions/4893122/filtering-in-qfiledialog if gdb_path and gdb_path.endswith('.gdb'): self.gdb = Geodatabase(gdb_path) if self.gdb.is_valid(): self.connected_gdb_path_label.setText(self.gdb.path) self._set_gdb_items_highlight() self._set_gdb_items_complete() self._fill_toc() else: msg = QMessageBox() msg.setText('This is not a valid file geodatabase') msg.setWindowTitle('Validation error') msg.setStandardButtons(QMessageBox.Ok) msg.exec_() else: if self.gdb.is_valid(): self._set_gdb_items_highlight() self._set_gdb_items_complete() return # ---------------------------------------------------------------------- def wheelEvent(self, event): # noqa: N802 """Override built-in method to handle mouse wheel scrolling. Necessary to do when the tab is focused. """ modifiers = QApplication.keyboardModifiers() if modifiers == Qt.ControlModifier: if event.angleDelta().y() > 0: # scroll forward self.query.zoomIn(1) else: self.query.zoomOut(1) return # ---------------------------------------------------------------------- def run_query(self): """Run SQL query and draw the record set and call table drawing.""" if not self.gdb: self.print_sql_execute_errors(not_connected_to_gdb_message) return try: if not self.gdb.is_valid(): return # use the text of what user selected, if none -> need to run the # whole query part_sql_query = self.query.textCursor().selection().toPlainText() if part_sql_query: sql_query = part_sql_query else: sql_query = self.query.toPlainText() if sql_query: # removing block comments and single line comments sql_query = self.block_comment_re.sub( self._strip_block_comments, sql_query) sql_query = self._strip_single_comments(sql_query) else: return # TODO: add threading to allow user to cancel a long running query QApplication.setOverrideCursor(Qt.WaitCursor) start_time = time.time() self.gdb.open_connection() res, errors = self.gdb.execute_sql( sql_query, self.gdb_sql_dialect_combobox.currentText()) end_time = time.time() if errors: self.print_sql_execute_errors(errors) if res: self.table.show() self.errors_panel.hide() self.draw_result_table(res) msg = 'Executed in {exec_time:.1f} secs | {rows} rows'.format( exec_time=end_time - start_time, rows=self.table.table_data.number_layer_rows) self.update_app_status_bar(msg) except Exception as err: print(err) finally: QApplication.restoreOverrideCursor() return # ---------------------------------------------------------------------- def result_should_include_geometry(self): """Get the setting defining whether to include the geometry column.""" try: return self.parentWidget().parentWidget().parentWidget( ).do_include_geometry.isChecked() except Exception: return True # ---------------------------------------------------------------------- def update_app_status_bar(self, message): """Update app status bar with the execution result details.""" try: self.parentWidget().parentWidget().parentWidget().statusBar( ).showMessage(message) except Exception: pass return # ---------------------------------------------------------------------- def draw_result_table(self, res): """Draw table with the record set received from the geodatabase.""" geom_col_name = res.GetGeometryColumn( ) # shape col was in the sql query self.geometry_isin_query = bool(geom_col_name) self.table.draw_result(res, show_shapes=bool( self.result_should_include_geometry())) self.table.view.resizeColumnsToContents() return # ---------------------------------------------------------------------- def print_sql_execute_errors(self, err): """Print to a special panel errors that occurred during execution.""" self.table.hide() self.errors_panel.show() self.errors_panel.setPlainText(err) return # ---------------------------------------------------------------------- def _set_gdb_items_highlight(self): """Set completer and highlight properties for geodatabase items.""" self.gdb_items = self.gdb.get_items() self.highlighter.set_highlight_rules_gdb_items(self.gdb_items, 'Table') self.gdb_schemas = self.gdb.get_schemas() self.gdb_columns_names = sorted(list( set( itertools.chain.from_iterable( [i.keys() for i in self.gdb_schemas.values()]))), key=lambda x: x.lower()) # ---------------------------------------------------------------------- def _set_gdb_items_complete(self): """Update completer rules to include geodatabase items.""" self.completer.update_completer_string_list(self.gdb_items + self.gdb_columns_names) self.highlighter.set_highlight_rules_gdb_items(self.gdb_columns_names, 'Column') return # ---------------------------------------------------------------------- def _fill_toc(self): """Fill TOC with geodatabase datasets and columns.""" self.toc.clear() if not self.gdb_items: return for tbl_name in sorted(self.gdb_items, key=lambda i: i.lower()): if tbl_name.islower() or tbl_name.isupper(): item = QTreeWidgetItem([tbl_name.title()]) else: item = QTreeWidgetItem([tbl_name]) font = QFont() font.setBold(True) item.setFont(0, font) for col_name, col_type in sorted( self.gdb_schemas[tbl_name].items()): if col_name.islower() or col_name.isupper(): col_name = col_name.title() item_child = QTreeWidgetItem( ['{0} ({1})'.format(col_name, col_type)]) item.addChild(item_child) self.toc.addTopLevelItem(item) return # ---------------------------------------------------------------------- def _do_toc_hide_show(self): """Hide TOC with tables and columns.""" if self.toc.isVisible(): self.toc.setVisible(False) else: self.toc.setVisible(True) return # ---------------------------------------------------------------------- def _strip_block_comments(self, sql_query): """Strip the block comments in SQL query.""" start, mid, end = sql_query.group(1, 2, 3) if mid is None: # this is a single-line comment return '' elif start is not None or end is not None: # this is a multi-line comment at start/end of a line return '' elif '\n' in mid: # this is a multi-line comment with line break return '\n' else: # this is a multi-line comment without line break return ' ' # ---------------------------------------------------------------------- def _strip_single_comments(self, sql_query): """Strip the single line comments in SQL query.""" clean_query = [] for line in sql_query.rstrip().split('\n'): clean_line = line.split('--')[0] if clean_line: clean_query.append(clean_line) return ' '.join([line for line in clean_query])
class TextEditorApp: """ TextEditorApp class that works as "Caretaker" that actually uses Memento Pattern. This class is responsible for keeping a collection of "Memento", but never examines or operates on the contents of a "Memento". """ __slots__ = ['_text_editor', '_memento_list', '_prev_state_ptr'] def __init__(self): """ Default constructor. """ self._text_editor = TextEditor() self._memento_list = [] self._prev_state_ptr = -1 def write(self, text: str) -> None: """ Writes the given text to the text editor. :param text: str :return: None """ self._text_editor.text = text print(f"After writing '{text}': {self._text_editor}") def save(self) -> None: """ Saves the current state of the text editor. :return: None """ self._add_memento(self._text_editor.create_memento()) self._prev_state_ptr = len(self._memento_list) - 1 def _add_memento(self, memo: Memento) -> None: """ Private helper method to store the given memento. :param memo: Memento :return: None """ self._memento_list.append(memo) def undo(self) -> None: """ Undo the most recent change in the text editor. :return: None """ print('Undo operation...') if self._text_editor.text !=\ self._memento_list[self._prev_state_ptr].text: last_saved_state = self._memento_list[self._prev_state_ptr] self._text_editor.restore(last_saved_state) print(self._text_editor) elif self._prev_state_ptr <= 0: print('No more undo operation available!') else: self._prev_state_ptr -= 1 prev_state = self._get_memento(self._prev_state_ptr) self._text_editor.restore(prev_state) print(self._text_editor) def _get_memento(self, idx: int) -> Memento: """ Private helper method to get the memento at the given index. :param idx: int :return: Memento """ return self._memento_list[idx] def redo(self) -> None: """ Redoes the most recent change in the text editor. :return: None """ print('Redo operation...') if self._prev_state_ptr >= len(self._memento_list) - 1: print('No more redo operation available!') return self._prev_state_ptr += 1 next_state = self._get_memento(self._prev_state_ptr) self._text_editor.restore(next_state) print(self._text_editor)
class Quantumnik(QObject): def __init__(self, iface): QObject.__init__(self) self.iface = iface self.canvas = iface.mapCanvas() # Fake canvas to use in tab to overlay the quantumnik layer self.qCanvas = None self.qCanvasPan = None self.qCanvasZoomIn = None self.qCanvasZoomOut = None self.tabWidget = None self.mapnik_map = None self.using_mapnik = False self.from_mapfile = False self.loaded_mapfile = None self.been_warned = False self.last_image_path = None self.dock_window = None self.keyAction = None self.keyAction2 = None self.keyAction3 = None def initGui(self): self.action = QAction(QIcon(":/mapnikglobe.png"), QString("Create Mapnik Canvas"), self.iface.mainWindow()) self.action.setWhatsThis("Create Mapnik Canvas") self.action.setStatusTip("%s: render with Mapnik" % NAME) QObject.connect(self.action, SIGNAL("triggered()"), self.toggle) self.action4 = QAction(QString("View live xml"), self.iface.mainWindow()) QObject.connect(self.action4, SIGNAL("triggered()"), self.view_xml) self.action3 = QAction(QString("Export Mapnik xml"), self.iface.mainWindow()) QObject.connect(self.action3, SIGNAL("triggered()"), self.save_xml) self.action5 = QAction(QString("Load Mapnik xml"), self.iface.mainWindow()) QObject.connect(self.action5, SIGNAL("triggered()"), self.load_xml) self.action6 = QAction(QString("Load Cascadenik mml"), self.iface.mainWindow()) QObject.connect(self.action6, SIGNAL("triggered()"), self.load_mml) self.action7 = QAction(QString("Export Map Graphics"), self.iface.mainWindow()) QObject.connect(self.action7, SIGNAL("triggered()"), self.export_image_gui) self.helpaction = QAction(QIcon(":/mapnikhelp.png"), "About", self.iface.mainWindow()) self.helpaction.setWhatsThis("%s Help" % NAME) QObject.connect(self.helpaction, SIGNAL("triggered()"), self.helprun) self.iface.addToolBarIcon(self.action) self.iface.addPluginToMenu("&%s" % NAME, self.action) self.iface.addPluginToMenu("&%s" % NAME, self.helpaction) self.iface.addPluginToMenu("&%s" % NAME, self.action3) self.iface.addPluginToMenu("&%s" % NAME, self.action4) self.iface.addPluginToMenu("&%s" % NAME, self.action5) self.iface.addPluginToMenu("&%s" % NAME, self.action6) self.iface.addPluginToMenu("&%s" % NAME, self.action7) # > QGIS 1.2 if hasattr(self.iface, 'registerMainWindowAction'): self.keyAction2 = QAction(QString("Switch to QGIS"), self.iface.mainWindow()) self.iface.registerMainWindowAction(self.keyAction2, "Ctrl+[") self.iface.addPluginToMenu("&%s" % NAME, self.keyAction2) QObject.connect(self.keyAction2, SIGNAL("triggered()"), self.switch_tab_qgis) self.keyAction3 = QAction(QString("Switch to Mapnik"), self.iface.mainWindow()) self.iface.registerMainWindowAction(self.keyAction3, "Ctrl+]") self.iface.addPluginToMenu("&%s" % NAME, self.keyAction3) QObject.connect(self.keyAction3, SIGNAL("triggered()"), self.switch_tab_mapnik) def unload(self): self.iface.removePluginMenu("&%s" % NAME, self.action) self.iface.removePluginMenu("&%s" % NAME, self.helpaction) self.iface.removePluginMenu("&%s" % NAME, self.action3) self.iface.removePluginMenu("&%s" % NAME, self.action4) self.iface.removePluginMenu("&%s" % NAME, self.action5) self.iface.removePluginMenu("&%s" % NAME, self.action6) self.iface.removePluginMenu("&%s" % NAME, self.action7) self.iface.removeToolBarIcon(self.action) if self.keyAction: self.iface.unregisterMainWindowAction(self.keyAction) if self.keyAction2: self.iface.unregisterMainWindowAction(self.keyAction2) if self.keyAction3: self.iface.unregisterMainWindowAction(self.keyAction3) def export_image_gui(self): flags = Qt.WindowTitleHint | Qt.WindowSystemMenuHint | Qt.WindowStaysOnTopHint export = ImageExport(self, flags) export.show() def view_xml(self, m=None): if not self.dock_window: self.dock_window = TextEditor(self) self.iface.mainWindow().addDockWidget(Qt.BottomDockWidgetArea, self.dock_window) if not self.using_mapnik: # http://trac.osgeo.org/qgis/changeset/12955 - render starting signal QObject.connect(self.canvas, SIGNAL("renderComplete(QPainter *)"), self.checkLayers) self.dock_window.show() if self.loaded_mapfile: # if we have loaded a map xml or mml # so lets just display the active file xml = open(self.loaded_mapfile, 'rb').read() else: if not m: # regenerate from qgis objects e_c = sync.EasyCanvas(self, self.canvas) m = e_c.to_mapnik() if hasattr(mapnik, 'save_map_to_string'): xml = mapnik.save_map_to_string(m) else: (handle, mapfile) = tempfile.mkstemp('.xml', 'quantumnik-map-') os.close(handle) mapnik.save_map(m, str(mapfile)) xml = open(mapfile, 'rb').read() e = self.canvas.extent() bbox = '%s %s %s %s' % (e.xMinimum(), e.yMinimum(), e.xMaximum(), e.yMaximum()) cmd = '\n<!-- nik2img.py mapnik.xml out.png -d %s %s -e %s -->\n' % ( self.canvas.width(), self.canvas.height(), bbox) try: if self.mapnik_map: cmd += '<!-- <MinScaleDenominator>%s</MinScaleDenominator> -->\n' % ( self.mapnik_map.scale_denominator()) except: pass code = xml + cmd if HIGHLIGHTING: highlighted = highlight( code, XmlLexer(), HtmlFormatter(linenos=False, nowrap=False, full=True)) self.dock_window.textEdit.setHtml(highlighted) else: self.dock_window.textEdit.setText(xml + cmd) def helprun(self): infoString = QString( "Written by Dane Springmeyer\nhttps://github.com/springmeyer/quantumnik" ) QMessageBox.information(self.iface.mainWindow(), "About %s" % NAME, infoString) def toggle(self): if self.using_mapnik: self.stop_rendering() else: self.start_rendering() def proj_warning(self): self.been_warned = True ren = self.canvas.mapRenderer() if not ren.hasCrsTransformEnabled() and self.canvas.layerCount() > 1: if hasattr(self.canvas.layer(0), 'crs'): if not self.canvas.layer( 0).crs().toProj4() == ren.destinationCrs().toProj4(): QMessageBox.information( self.iface.mainWindow(), "Warning", "The projection of the map and the first layer do not match. Mapnik may not render the layer(s) correctly.\n\nYou likely need to either enable 'On-the-fly' CRS transformation or set the Map projection in your Project Properties to the projection of your layer(s)." ) else: if not self.canvas.layer( 0).srs().toProj4() == ren.destinationSrs().toProj4(): QMessageBox.information( self.iface.mainWindow(), "Warning", "The projection of the map and the first layer do not match. Mapnik may not render the layer(s) correctly.\n\nYou likely need to either enable 'On-the-fly' CRS transformation or set the Map projection in your Project Properties to the projection of your layer(s)." ) def save_xml(self): # need to expose as an option! relative_paths = True mapfile = QFileDialog.getSaveFileName(None, "Save file dialog", 'mapnik.xml', "Mapfile (*.xml)") if mapfile: e_c = sync.EasyCanvas(self, self.canvas) mapfile_ = str(mapfile) base_path = os.path.dirname(mapfile_) e_c.base_path = base_path m = e_c.to_mapnik() mapnik.save_map(m, mapfile_) if relative_paths: relativism.fix_paths(mapfile_, base_path) def make_bundle(self): pass # todo: accept directory name # move mapfile and all file based datasources # into that folder and stash some docs inside # provide option to zip and upload to url on the fly def set_canvas_from_mapnik(self): # set up keyboard shortcut # > QGIS 1.2 if hasattr(self.iface, 'registerMainWindowAction'): if not self.keyAction: # TODO - hotkey does not work on linux.... self.keyAction = QAction(QString("Refresh " + NAME), self.iface.mainWindow()) self.iface.registerMainWindowAction(self.keyAction, "Ctrl+r") self.iface.addPluginToMenu("&%s" % NAME, self.keyAction) QObject.connect(self.keyAction, SIGNAL("triggered()"), self.toggle) self.mapnik_map.zoom_all() e = self.mapnik_map.envelope() crs = QgsCoordinateReferenceSystem() srs = self.mapnik_map.srs if srs == '+init=epsg:900913': # until we can look it up in srs.db... merc = "+init=EPSG:900913" crs.createFromProj4(QString(merc)) elif 'init' in srs: # TODO - quick hack, needs regex and fallbacks epsg = srs.split(':')[1] crs.createFromEpsg(int(epsg)) else: if srs == '+proj=latlong +datum=WGS84': # expand the Mapnik srs a bit # http://trac.mapnik.org/ticket/333 srs = '+proj=longlat +ellps=WGS84 +datum=WGS84 +no_defs' crs.createFromProj4(QString(srs)) if hasattr(self.canvas.mapRenderer(), 'setDestinationCrs'): self.canvas.mapRenderer().setDestinationCrs(crs) else: self.canvas.mapRenderer().setDestinationSrs(crs) if not crs.isValid(): QMessageBox.information(self.iface.mainWindow(), "Warning", "Projection not understood") return QObject.connect(self.canvas, SIGNAL("renderComplete(QPainter *)"), self.render_dynamic) self.canvas.setExtent(QgsRectangle(e.minx, e.miny, e.maxx, e.maxy)) self.canvas.refresh() def set_mapnik_to_canvas(self): QObject.connect(self.canvas, SIGNAL("renderComplete(QPainter *)"), self.render_dynamic) self.canvas.refresh() def refresh_loaded_mapfile(self): if self.mapfile_format == 'Cascadenik mml': self.load_mml(refresh=True) else: self.load_xml(refresh=True) def load_mml(self, refresh=False): self.from_mapfile = True self.mapfile_format = 'Cascadenik mml' if self.loaded_mapfile and refresh: mapfile = self.loaded_mapfile else: mapfile = QFileDialog.getOpenFileName(None, "Open file dialog", '', "Cascadenik MML (*.mml)") if mapfile: self.mapnik_map = mapnik.Map(1, 1) import cascadenik if hasattr(cascadenik, 'VERSION'): major = int(cascadenik.VERSION.split('.')[0]) if major < 1: from cascadenik import compile compiled = '%s_compiled.xml' % os.path.splitext( str(mapfile))[0] open(compiled, 'w').write(compile(str(mapfile))) mapnik.load_map(self.mapnik_map, compiled) elif major == 1: output_dir = os.path.dirname(str(mapfile)) cascadenik.load_map(self.mapnik_map, str(mapfile), output_dir, verbose=False) elif major > 1: raise NotImplementedError( 'This nik2img version does not yet support Cascadenik > 1.x, please upgrade nik2img to the latest release' ) else: from cascadenik import compile compiled = '%s_compiled.xml' % os.path.splitext( str(mapfile))[0] #if os.path.exits(compiled): #pass open(compiled, 'w').write(compile(str(mapfile))) mapnik.load_map(self.mapnik_map, compiled) if self.loaded_mapfile and refresh: self.set_mapnik_to_canvas() else: self.set_canvas_from_mapnik() self.loaded_mapfile = str(mapfile) def load_xml(self, refresh=False): # TODO - consider putting into its own layer: # https://trac.osgeo.org/qgis/ticket/2392#comment:4 self.from_mapfile = True self.mapfile_format = 'xml mapfile' if self.loaded_mapfile and refresh: mapfile = self.loaded_mapfile else: mapfile = QFileDialog.getOpenFileName(None, "Open file dialog", '', "XML Mapfile (*.xml)") if mapfile: self.mapnik_map = mapnik.Map(1, 1) mapnik.load_map(self.mapnik_map, str(mapfile)) if self.loaded_mapfile and refresh: self.set_mapnik_to_canvas() else: self.set_canvas_from_mapnik() self.loaded_mapfile = str(mapfile) def finishStopRender(self): self.iface.mapCanvas().setMinimumSize(QSize(0, 0)) def stop_rendering(self): # Disconnect all the signals as we disable the tool QObject.disconnect(self.qCanvas, SIGNAL("renderComplete(QPainter *)"), self.render_dynamic) QObject.disconnect(self.qCanvas, SIGNAL("xyCoordinates(const QgsPoint&)"), self.updateCoordsDisplay) QObject.disconnect(self.canvas, SIGNAL("renderComplete(QPainter *)"), self.checkLayers) QObject.disconnect(self.canvas, SIGNAL("extentsChanged()"), self.checkExtentsChanged) QObject.disconnect(self.canvas, SIGNAL("mapToolSet(QgsMapTool *)"), self.mapToolSet) self.using_mapnik = False # If the current tab is quantumnik then we need to update the extent # of the main map when exiting to make sure they are in sync if self.tabWidget.currentIndex() == 1: self.mapnikMapCoordChange() # Need to restore the main map instead of the mapnik tab tabWidgetSize = self.tabWidget.size() mapCanvasExtent = self.iface.mapCanvas().extent() self.iface.mapCanvas().setMinimumSize(tabWidgetSize) self.iface.mainWindow().setCentralWidget(self.iface.mapCanvas()) self.iface.mapCanvas().show() # Set the canvas extent to the same place it was before getting # rid of the tabs self.iface.mapCanvas().setExtent(mapCanvasExtent) self.canvas.refresh() # null out some vars self.qCanvasPan = None self.qCanvasZoomIn = None self.qCanvasZoomOut = None # We have to let the main app swizzle the screen and then # hammer it back to the size we want QTimer.singleShot(1, self.finishStopRender) def create_mapnik_map(self): if not self.been_warned: self.proj_warning() self.easyCanvas = sync.EasyCanvas(self, self.canvas) self.mapnik_map = self.easyCanvas.to_mapnik() if self.dock_window: self.view_xml(self.mapnik_map) @property def background(self): return sync.css_color(self.canvas.backgroundBrush().color()) # Provide a hack to try and find the map coordinate status bar element # to take over while the mapnik canvas is in play. def findMapCoordsStatus(self): coordStatusWidget = None sb = self.iface.mainWindow().statusBar() for x in sb.children(): # Check if we have a line edit if isinstance(x, QLineEdit): # Now check if the text does not contain a ':' if not ':' in x.text(): # we have our coord status widget coordStatusWidget = x return coordStatusWidget def finishStartRendering(self): self.tabWidget.setMinimumSize(QSize(0, 0)) self.canvas.refresh() def start_rendering(self): if self.from_mapfile and not self.canvas.layerCount(): self.refresh_loaded_mapfile() else: self.from_mapfile = False # http://trac.osgeo.org/qgis/changeset/12926 # TODO - if not dirty we don't need to create a new map from scratch... self.create_mapnik_map() # Need to create a tab widget to toss into the main window # to hold both the main canvas as well as the mapnik rendering mapCanvasSize = self.canvas.size() mapCanvasExtent = self.iface.mapCanvas().extent() newWidget = QTabWidget(self.iface.mainWindow()) sizePolicy = QSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( newWidget.sizePolicy().hasHeightForWidth()) newWidget.setSizePolicy(sizePolicy) newWidget.setSizeIncrement(QSize(0, 0)) newWidget.setBaseSize(mapCanvasSize) newWidget.resize(mapCanvasSize) # Very important: Set the min size of the tabs to the size of the # original canvas. We will then let the main app take control # and then use a one shot timer to set the min size back down. It # is a hack, but allows us to keep the canvas and tab size correct. newWidget.setMinimumSize(mapCanvasSize) # This is the new blank canvas that we will use the qpainter # from to draw the mapnik image over. self.qCanvas = QgsMapCanvas(self.iface.mainWindow()) self.qCanvas.setCanvasColor(QColor(255, 255, 255)) self.qCanvas.enableAntiAliasing(True) self.qCanvas.useImageToRender(False) self.qCanvas.show() # A set of map tools for the mapnik canvas self.qCanvasPan = QgsMapToolPan(self.qCanvas) self.qCanvasZoomIn = QgsMapToolZoom(self.qCanvas, False) self.qCanvasZoomOut = QgsMapToolZoom(self.qCanvas, True) self.mapToolSet(self.canvas.mapTool()) # Add the canvas items to the tabs newWidget.addTab(self.canvas, "Main Map") newWidget.addTab(self.qCanvas, "Mapnik Rendered Map") self.tabWidget = newWidget # Add the tabs as the central widget self.iface.mainWindow().setCentralWidget(newWidget) # Need to set the extent of both canvases as we have just resized # things self.canvas.setExtent(mapCanvasExtent) self.qCanvas.setExtent(mapCanvasExtent) # Hook up to the tabs changing so we can make sure to update the # rendering in a lazy way... i.e. a pan in the main canvas will # not cause a redraw in the mapnik tab until the mapnik tab # is selected. self.connect(self.tabWidget, SIGNAL("currentChanged(int)"), self.tabChanged) # Grab the maptool change signal so the mapnik canvas tool # can stay in sync. # TODO: We need to get the in/out property for the zoom tool # exposed to the python bindings. As it stands now, we can # not tell what direction the tool is going when we get this # signal and it is a zoom tool. QObject.connect(self.canvas, SIGNAL("mapToolSet(QgsMapTool *)"), self.mapToolSet) # Catch any mouse movements over the mapnik canvas and # sneek in and update the cord display ## This is a hack to find the status element to populate with xy self.mapCoords = self.findMapCoordsStatus() QObject.connect(self.qCanvas, SIGNAL("xyCoordinates(const QgsPoint&)"), self.updateCoordsDisplay) # Get the renderComplete signal for the qCanvas to allow us to # render the mapnik image over it. QObject.connect(self.qCanvas, SIGNAL("renderComplete(QPainter *)"), self.render_dynamic) # Get the renderComplete signal for the main canvas so we can tell # if there have been any layer changes and if we need to re-draw # the mapnik image. This is mainly for when the mapnik tab is # active but layer changes are happening. QObject.connect(self.canvas, SIGNAL("renderComplete(QPainter *)"), self.checkLayers) QObject.connect(self.canvas, SIGNAL("extentsChanged()"), self.checkExtentsChanged) self.using_mapnik = True # We use a single shot timer to let the main app resize the main # window with us holding a minsize we want, then we reset the # allowable min size after the main app has its turn. Hack, but # allows for the window to be rezised with a new main widget. QTimer.singleShot(1, self.finishStartRendering) def updateCoordsDisplay(self, p): if self.mapCoords: capturePyString = "%.5f,%.5f" % (p.x(), p.y()) capture_string = QString(capturePyString) self.mapCoords.setText(capture_string) def mapToolSet(self, tool): # something changed here in recent QGIS versions causing: # exceptions when closing QGIS because these objects are None if tool: if isinstance(tool, QgsMapToolPan): self.qCanvas.setMapTool(self.qCanvasPan) elif isinstance(tool, QgsMapToolZoom): # Yet another hack to find out if the tool we are using is a # zoom in or out if tool.action().text() == QString("Zoom In"): self.qCanvas.setMapTool(self.qCanvasZoomIn) else: self.qCanvas.setMapTool(self.qCanvasZoomOut) else: self.qCanvas.setMapTool(self.qCanvasPan) def switch_tab_qgis(self): if self.tabWidget: self.tabWidget.setCurrentIndex(0) def switch_tab_mapnik(self): if self.tabWidget: self.tabWidget.setCurrentIndex(1) def tabChanged(self, index): if index == 0: self.mapnikMapCoordChange() else: self.mainMapCoordChange() def mainMapCoordChange(self): # print "coordChange" self.mapnik_map = self.easyCanvas.to_mapnik(self.mapnik_map) self.qCanvas.setExtent(self.iface.mapCanvas().extent()) self.qCanvas.refresh() def mapnikMapCoordChange(self): # print "coordChange" self.canvas.setExtent(self.qCanvas.extent()) self.canvas.refresh() # Here we are checking to see if we got a new extent on the main # canvas even though we are in the mapnik tab... in that case we have # done something like zoom to full extent etc. def checkExtentsChanged(self): if self.tabWidget: if self.tabWidget.currentIndex() == 1: self.mainMapCoordChange() # Here we are checking to see if we got a render complete on the main # canvas even though we are in the mapnik tab... in that case we have # a new layer etc. def checkLayers(self, painter=None): if self.tabWidget: if self.tabWidget.currentIndex() == 1: # There was a change in the main canvas while we are viewing # the mapnik canvas (i.e. layer added/removed etc) so we # need to refresh the mapnik map self.mapnik_map = self.easyCanvas.to_mapnik(self.mapnik_map) self.qCanvas.refresh() # We also make sure the main map canvas gets put back to the # current extent of the qCanvas incase the main map got changed # as a side effect since updates to it are lazy loaded on tab # change. self.canvas.setExtent(self.qCanvas.extent()) # We make sure to update the XML viewer if # if is open if self.dock_window: self.view_xml(self.mapnik_map) if self.dock_window: self.view_xml() def render_dynamic(self, painter): if self.mapnik_map: w = painter.device().width() h = painter.device().height() # using canvas dims leads to shift in QGIS < 1.3... #w = self.canvas.width() #h = self.canvas.height() try: self.mapnik_map.resize(w, h) except: self.mapnik_map.width = w self.mapnik_map.height = h if self.qCanvas: can = self.qCanvas else: can = self.canvas try: e = can.extent() except: can = self.canvas e = can.extent() bbox = mapnik.Envelope(e.xMinimum(), e.yMinimum(), e.xMaximum(), e.yMaximum()) self.mapnik_map.zoom_to_box(bbox) im = mapnik.Image(w, h) mapnik.render(self.mapnik_map, im) if os.name == 'nt': qim = QImage() qim.loadFromData(QByteArray(im.tostring('png'))) painter.drawImage(0, 0, qim) else: qim = QImage(im.tostring(), w, h, QImage.Format_ARGB32) painter.drawImage(0, 0, qim.rgbSwapped()) can.refresh()