def _horizontalGradientHelper(cls, painter: QPainter, spanRect: QRect, rect: QRect, lightColored: bool): if lightColored: shadowGradient = QLinearGradient(rect.topLeft(), rect.bottomLeft()) shadowGradient.setColorAt(0, 0xf0f0f0) shadowGradient.setColorAt(1, 0xcfcfcf) painter.fillRect(rect, shadowGradient) return base = cls.baseColor(lightColored) highlight = cls.highlightColor(lightColored) shadow = cls.shadowColor(lightColored) grad = QLinearGradient(rect.topLeft(), rect.bottomLeft()) grad.setColorAt(0, highlight.lighter(120)) if rect.height() == cls.navigationWidgetHeight: grad.setColorAt(0.4, highlight) grad.setColorAt(0.401, base) grad.setColorAt(1, shadow) painter.fillRect(rect, grad) shadowGradient = QLinearGradient(spanRect.topLeft(), spanRect.topRight()) shadowGradient.setColorAt(0, QColor(0, 0, 0, 30)) lighterHighlight = highlight.lighter(130) lighterHighlight.setAlpha(100) shadowGradient.setColorAt(0.7, lighterHighlight) shadowGradient.setColorAt(1, QColor(0, 0, 0, 40)) painter.fillRect(rect, shadowGradient)
def decorate_welcome_icon(icon, background_color): """Return a `QIcon` with a circle shaped background. """ welcome_icon = QIcon() sizes = [32, 48, 64, 80] background_color = NAMED_COLORS.get(background_color, background_color) background_color = QColor(background_color) grad = radial_gradient(background_color) for size in sizes: icon_pixmap = icon.pixmap(5 * size / 8, 5 * size / 8) icon_size = icon_pixmap.size() icon_rect = QRect(QPoint(0, 0), icon_size) pixmap = QPixmap(size, size) pixmap.fill(QColor(0, 0, 0, 0)) p = QPainter(pixmap) p.setRenderHint(QPainter.Antialiasing, True) p.setBrush(QBrush(grad)) p.setPen(Qt.NoPen) ellipse_rect = QRect(0, 0, size, size) p.drawEllipse(ellipse_rect) icon_rect.moveCenter(ellipse_rect.center()) p.drawPixmap(icon_rect.topLeft(), icon_pixmap) p.end() welcome_icon.addPixmap(pixmap) return welcome_icon
def paintEvent( self, event ): """ Das automatisch ausgelöste paintEvent, das das Widget bei jeder Fensterveränderung neu zeichnet. """ if self.__maximum > 0: frameWidth = 1 separatorWidth = 1 # Damit der Rahmen nicht irgendwie abgeschnitten wird, muß das Quadrat entsprechend kleiner sein. squareSideLength = 10 framePen = QPen( frameWidth ) framePen.setColor( self.__colorFrame ) squareSideLengthPlus = squareSideLength + 2 * frameWidth painter = QPainter( self ) windowWidth = self.width() / min( self.__maximum, self.__columnMax ) windowHeight = self.__maximum / self.__columnMax windowHeight = math.ceil( windowHeight ) windowHeight = self.height() / windowHeight side = min( windowWidth, windowHeight ) painter.setRenderHint( QPainter.Antialiasing ) # Wenn das Widget disabled ist, muß ich den Alphakanal meiner Farben verändern. if ( not self.isEnabled() ): painter.setOpacity( .5 ) #painter.translate( float( windowWidth ), float( windowHeight ) ) painter.scale( side / squareSideLengthPlus, side / squareSideLengthPlus ) painter.setPen( framePen ) painter.setBrush( self.__colorEmpty ) painter.save() squareColumnIter = 0 squareLineIter = 0 squareCount = 0 for squareCount in range(self.__maximum): square = QRect( ( squareSideLength + separatorWidth ) * squareColumnIter + frameWidth * ( squareColumnIter + 1 ), ( squareSideLength + separatorWidth ) * squareLineIter + frameWidth * ( squareLineIter + 1 ), squareSideLength, squareSideLength ) painter.drawRect( square ) # Wir zeichnen die ausgekreuzten Quadrate if (self.__value > (self.__columnMax * squareLineIter + squareColumnIter)): painter.drawLine(square.bottomLeft(), square.topRight()) painter.drawLine(square.topLeft(), square.bottomRight()) squareColumnIter += 1 if ( squareColumnIter >= self.__columnMax ): squareColumnIter = 0 squareLineIter += 1 painter.restore()
def horizontalGradient(self, painter, spanRect, clipRect): key = 'fancy vertical gradient %d %d %d %d %d'%(spanRect.width(), spanRect.height(), clipRect.width(), clipRect.height(), self.baseColor.rgb()) pixmap = QPixmap() p = painter rect = QRect(clipRect) if self._usePixmapCache and not QPixmapCache.find(key, pixmap): pixmap = QPixmap(clipRect.size()) p = QPainter(pixmap) rect = QRect(0, 0, clipRect.width(), clipRect.height()) base = self.baseColor grad = QLinearGradient(QPointF(rect.topLeft()), QPointF(rect.bottomLeft())) grad.setColorAt(0, self.highlightColor.lighter(120)) if rect.height() == self.navigationWidgetHeight: grad.setColorAt(0.4, self.highlightColor) grad.setColorAt(0.401, base) grad.setColorAt(1, self.shadowColor) p.fillRect(rect, grad) shadowGradient = QLinearGradient(QPointF(spanRect.topLeft()), QPointF(spanRect.topRight())) shadowGradient.setColorAt(0, QColor(0, 0, 0, 30)) highlight = self.highlightColor.lighter(130) highlight.setAlpha(100) shadowGradient.setColorAt(0.7, highlight) shadowGradient.setColorAt(1, QColor(0, 0, 0, 40)) p.fillRect(rect, shadowGradient) if self._usePixmapCache and not QPixmapCache.find(key, pixmap): painter.drawPixmap(clipRect.topLeft(), pixmap) p.end() del p QPixmapCache.insert(key, pixmap)
def _drawStyledBar(self, painter, option): rect = option.rect key = "fancy styledbar %d %d %d" % (rect.width(), rect.height(), theme.baseColor.rgb()) pixmap = QPixmap() p = painter if theme.usePixmapCache() and not QPixmapCache.find(key, pixmap): pixmap = QPixmap(rect.size()) p = QPainter(pixmap) rect = QRect(0, 0, rect.width(), rect.height()) horizontal = option.state & QStyle.State_Horizontal offset = self.window().mapToGlobal( option.rect.topLeft()) - self.mapToGlobal(option.rect.topLeft()) gradientSpan = QRect(offset, self.window().size()) if horizontal: theme.horizontalGradient(p, gradientSpan, rect) else: theme.verticalGradient(p, gradientSpan, rect) painter.setPen(theme.borderColor) if horizontal: lighter = QColor(255, 255, 255, 40) if self._topBorder: p.drawLine(rect.topLeft(), rect.topRight()) p.setPen(lighter) p.drawLine(rect.topLeft() + QPoint(0, 1), rect.topRight() + QPoint(0, 1)) else: p.drawLine(rect.bottomLeft(), rect.bottomRight()) p.setPen(lighter) p.drawLine(rect.topLeft(), rect.topRight()) else: p.drawLine(rect.topLeft(), rect.bottomLeft()) p.drawLine(rect.topRight(), rect.bottomRight()) if theme.usePixmapCache() and not QPixmapCache.find(key, pixmap): painter.drawPixmap(rect.topLeft(), pixmap) p.end() del p QPixmapCache.insert(key, pixmap)
def _verticalGradientHelper(cls, painter: QPainter, spanRect: QRect, rect: QRect, lightColored): """ :type painter: QPainter :type spanRect: QRect :type rect: QRect :type lightColored: bool """ highlight = StyleHelper.highlightColor(lightColored) shadow = StyleHelper.shadowColor(lightColored) grad = QLinearGradient(spanRect.topRight(), spanRect.topLeft()) grad.setColorAt(0, highlight.lighter(117)) grad.setColorAt(1, shadow.darker(109)) painter.fillRect(rect, grad) light = QColor(255, 255, 255, 80) painter.setPen(light) painter.drawLine(rect.topRight() - QPoint(1, 0), rect.bottomRight() - QPoint(1, 0)) dark = QColor(0, 0, 0, 90) painter.setPen(dark) painter.drawLine(rect.topLeft(), rect.bottomLeft())
def add_item(self, thumbnail_image, date, info, day_index=-1, archive_path=None): """ @param thumbnail_image: Image to be used in thumbnail @param date: Date of image @param info: Copyright info for image @param day_index: Day index of image @param archive_path: Path to the local file, or None if image source is the RSS feed. @type thumbnail_image: QImage @type date: QDate @type info: str @type day_index: int @type archive_path: unicode or None """ if date.year() == QDate.currentDate().year(): date_label = str(date.toString('dddd dd MMMM')) else: date_label = str(date.toString('dddd dd MMMM, yyyy')) if date_label in self.added_dates: # This date has already been added. Don't bother adding it again. print 'Ignored', date_label return if archive_path: pixmap = QPixmap.fromImage(thumbnail_image) painter = QPainter(pixmap) painter.setRenderHint(QPainter.Antialiasing) circle_area = QRect(pixmap.width() - 35, pixmap.height() - 35, 25, 25) painter.setOpacity(0.7) painter.setPen(Qt.lightGray) painter.setBrush(Qt.lightGray) painter.drawEllipse(circle_area) painter.drawPixmap(circle_area.topLeft(), self.pixmap_hd) painter.end() else: pixmap = QPixmap.fromImage( thumbnail_image.scaled(QSize(200, 125), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)) icon = QIcon(pixmap) widget_item = ListWidgetItem(icon, date_label, self) widget_item.setToolTip(info) widget_item.image_day_index = day_index widget_item.archive_path = archive_path widget_item.image_date = date self.added_dates.add(date_label)
def verticalGradient(cls, painter: QPainter, spanRect: QRect, clipRect: QRect, lightColored=False): if StyleHelper.usePixmapCache(): keyColor = cls.baseColor(lightColored) key = "mh_vertical {1} {2} {3} {4} {5}".format( spanRect.width(), spanRect.height(), clipRect.width(), clipRect.height(), keyColor.rgb()) pixmap = QPixmap() if not QPixmapCache.find(key, pixmap): pixmap = QPixmap(clipRect.size()) p = QPainter(pixmap) rect = QRect(0, 0, clipRect.width(), clipRect.height()) StyleHelper._verticalGradientHelper(p, spanRect, rect, lightColored) p.end() QPixmapCache.insert(key, pixmap) painter.drawPixmap(clipRect.topLeft(), pixmap) else: StyleHelper._verticalGradientHelper(painter, spanRect, clipRect, lightColored)
class PlottingThread(QThread): def __init__(self, parent): QThread.__init__(self) self.result = None self.parent = parent self._stopped = False self.mutex = QMutex() self.filePrefix = None self.fileFormat = None self.wallColoring = None self.cellColoring = None self.pointColoring = None self.extraDrawing = [] self.pointSize = None self.pointLineColor = None self.pointLineThickness = None self.ellipsisDraw = None self.overSampling = None self.wallThickness = None self.bgColor = None self.loading = False self._crop = QRect(0,0,1,1) self._pix = None self._end_image_plot = False self._loading_arguments = {} self.retryObject = None def end_step(self): return len(self.result)+1 def stop(self, value = True): self.mutex.lock() self._stopped = value self.mutex.unlock() def stopped(self): self.mutex.lock() val = self._stopped self.mutex.unlock() return val def nextImage(self): QCoreApplication.postEvent(self.parent, NextImageEvent()) def abort(self, reason, **others): e = AbortPlottingEvent(reason) if others: e.others = others QCoreApplication.postEvent(self.parent, e) def finished(self): if self.loading: QCoreApplication.postEvent(self.parent, FinishLoadingEvent()) self.loading = False else: QCoreApplication.postEvent(self.parent, FinishPlottingEvent()) def image_ready(self): QCoreApplication.postEvent(self.parent, ImageReadyPlottingEvent()) def update_nb_images(self, nb): QCoreApplication.postEvent(self.parent, UpdateNbImageEvent(nb)) @property def crop_left(self): return self._crop.left() @crop_left.setter def crop_left(self, value): self._crop.moveLeft(int(value)) @property def crop_top(self): return self._crop.top() @crop_top.setter def crop_top(self, value): self._crop.moveTop(int(value)) @property def crop_width(self): return self._crop.width() @crop_width.setter def crop_width(self, value): self._crop.setWidth(int(value)) @property def crop_height(self): return self._crop.height() @crop_height.setter def crop_height(self, value): self._crop.setHeight(int(value)) def reset_crop(self): self._crop = QRect(QPoint(0,0), self.img_size) @property def crop(self): return QRect(self._crop) @crop.deleter def crop(self): self.reset_crop() @property def end_image_plot(self): ''' If true, plot the growth data on the end image rather than the start image of the growth calculation. ''' return self._end_image_plot @end_image_plot.setter def end_image_plot(self, value): self._end_image_plot = bool(value) @property def pix(self): '''Thread-safe image storage.''' self.mutex.lock() pix = self._pix self.mutex.unlock() return pix @pix.setter def pix(self, value): self.mutex.lock() self._pix = value self.mutex.unlock() def render_valid(self): if self.result is None: log_debug("result is None") return False if self.parent is None: log_debug("parent is None") return False if self.ellipsisDraw is None: log_debug("ellipsisDraw is None") return False if self.cellColoring is None: log_debug("cellColoring is None") return False if self.wallColoring is None: log_debug("wallColoring is None") return False if self.pointColoring is None: log_debug("pointColoring is None") return False if self.pointSize is None: log_debug("pointSize is None") return False if self.pointLineThickness is None: log_debug("pointSize is None") return False if self.pointLineColor is None: log_debug("pointSize is None") return False if self.wallThickness is None: log_debug("wallThickness is None") return False if self.overSampling is None: log_debug("overSampling is None") return False if self.bgColor is None: log_debug("bgColor is None") return False return True def valid(self): if self.filePrefix is None: log_debug("filePrefix is None") return False if not self.filePrefix: log_debug("filePrefix is Empty") return False if self.fileFormat is None: log_debug("fileFormat is None") return False return self.render_valid() def drawImage(self, imageid): cache = image_cache.cache cellColoring = self.cellColoring wallColoring = self.wallColoring pointColoring = self.pointColoring ellipsisDraw = self.ellipsisDraw overSampling = self.overSampling extraDrawing = self.extraDrawing bgColor = self.bgColor.rgb() result = self.result if self.result_type == "Data": data = result img_name = result.images_name[imageid] else: data = result.data img_name = result.images[imageid] #scale = data.images_scale[img_name] min_scale = data.minScale() img = cache.image(data.image_path(img_name)) img_data = data[img_name] size = self._crop.size() pix = QImage(size*overSampling, QImage.Format_ARGB32) pix.fill(bgColor) painter = QPainter() if not painter.begin(pix): self.abort("Cannot create painter on QImage") return None, None, None painter.setRenderHints(QPainter.SmoothPixmapTransform, True) painter.setRenderHints(QPainter.Antialiasing, True) if overSampling > 1: painter.scale(overSampling, overSampling) painter.translate(-self._crop.topLeft()) painter.save() painter.translate(self.translate) log_debug("Translating: %gx%g" % (self.translate.x(), self.translate.y()) ) painter.scale(1/min_scale, 1/min_scale) painter.save() matrix = img_data.matrix() painter.setWorldTransform(matrix, True) painter.drawImage(QPoint(0,0), img) painter.restore() #pt_matrix = QTransform() #pt_matrix.scale(1/min_scale, 1/min_scale) #painter.setTransform(pt_matrix, True) cellColoring.startImage(painter, imageid) wallColoring.startImage(painter, imageid) for ed in extraDrawing: ed.startImage(painter, imageid) if self.result_type == "Growth": cells = result.cells[imageid] walls = result.walls[imageid] else: cells = img_data.cells walls = set() for cid in img_data.cells: pts = [ pt for pt in data.cells[cid] if pt in img_data ] if len(pts) > 1: for i in range(len(pts)): walls.add(data.wallId(pts[i-1], pts[i])) # Now, draw the cells and the ellipsis for cid in cells: painter.setPen(Qt.NoPen) color = cellColoring(imageid, cid) painter.setBrush(color) pts = data.cellAtTime(cid, img_data.index) if pts: pts.append(pts[0]) ppts = [] for p1,p2 in zip(pts[:-1], pts[1:]): ppts.append(img_data[p1]) ppts.extend(img_data.walls[p1,p2]) ppts.append(ppts[0]) poly = QPolygonF(ppts) painter.drawPolygon(poly) # And draw the walls wallThickness = self.wallThickness*min_scale for wid in walls: color = wallColoring(imageid, wid) if color.alpha() > 0: pen = QPen(color) pen.setWidthF(wallThickness) painter.setPen(pen) pts = [img_data[wid[0]]] + img_data.walls[wid[0], wid[1]] + [img_data[wid[1]]] #painter.drawLine(img_data[wid[0]], img_data[wid[1]]) painter.drawPolyline(*pts) # Then, draw the points pointSize = self.pointSize*min_scale pointLineColor = self.pointLineColor pointLineThickness = self.pointLineThickness*min_scale log_debug("pointSize = %g" % pointSize) for pid in img_data: color = pointColoring(imageid, pid) if color.alpha() > 0: pen = QPen(pointLineColor) pen.setWidthF(pointLineThickness) brush = QBrush(color) painter.setPen(pen) painter.setBrush(brush) pos = img_data[pid] rect = QRectF(pos.x()-pointSize, pos.y()-pointSize, 2*pointSize, 2*pointSize) painter.drawEllipse(rect) if ellipsisDraw.plot: for cid in cells: pts = data.cellAtTime(cid, img_data.index) if pts: pts.append(pts[0]) ppts = [] for p1,p2 in zip(pts[:-1], pts[1:]): ppts.append(img_data[p1]) ppts.extend(img_data.walls[p1,p2]) ppts.append(ppts[0]) #poly = QPolygonF(ppts) #painter.drawPolygon(poly) ellipsisDraw(painter, imageid, cid, ppts, min_scale) # At last, draw the extra data for ed in extraDrawing: ed(painter, imageid) tr = painter.worldTransform() painter.restore() pic_w = wallColoring.finalizeImage(painter, imageid, tr, self.crop) pic_c = cellColoring.finalizeImage(painter, imageid, tr, self.crop) for ed in extraDrawing: ed.finalizeImage(painter, imageid, tr, self.crop) painter.end() return pix, pic_w, pic_c def start(self): if self.isRunning(): assert not self.rendering_all, "Cannot run twice the rendering of all images with the same object." return if parameters.instance.use_thread: log_debug("Starting rendering thread.") QThread.start(self) return False else: self.run() return True def render_all(self): self.rendering_all = True return self.start() def render_single(self, img_id, retry=False): if retry: while self.isRunning(): self.wait(10000) elif self.isRunning(): return self.rendering_all = False self.current_image = img_id return self.start() def load(self, filename): self.loading = True self.result = filename return self.start() def run(self): if self.loading: self.run_loader() elif self.rendering_all: self.run_full() else: self.run_single() def run_single(self): img = self.current_image self.cellColoring.init() self.wallColoring.init() self.pointColoring.init() log_debug("Rendering image %d" % img) self.pix, self.pic_w, self.pic_c = self.drawImage(img) if self.pic_w is not None: log_debug("Has wall image") if self.pic_c is not None: log_debug("Has cell image") if self.pix is not None: log_debug("Pix correctly rendered") log_debug("Rendered image %d = %s" % (img, self.pix)) self.image_ready() def reload(self): if self.retryObject is None: return self._loading_arguments.update(self.retryObject.method_args) self.load(self.retryObject.filename) def run_loader(self): filename = self.result try: self.retryObject = None # First, prepare the data by getting the images and computing how big they # should be f = open(filename) first_line = f.readline() f.close() if first_line.startswith("TRKR_VERSION"): result = Result(None) result.load(self.result, **self._loading_arguments) result_type = "Growth" else: result = TrackingData() result.load(self.result, **self._loading_arguments) result_type = "Data" self.result = result self.result_type = result_type if result_type == "Data": data = result images = data.images_name if data.cells: self.has_cells = True self.has_walls = True else: self.has_cells = False self.has_walls = False self.has_points = bool(data.cell_points) else: data = result.data images = result.images self.has_cells = False self.has_walls = False self.has_points = False self.images = images cache = image_cache.cache self.update_nb_images(len(result)) bbox = QRectF() ms = data.minScale() for i in range(len(result)): img_name = images[i] img_data = data[img_name] img = cache.image(data.image_path(img_name)) matrix = QTransform() matrix = img_data.matrix() sc = QTransform() sc.scale(1.0/ms, 1.0/ms) matrix *= sc r = QRectF(img.rect()) rbox = matrix.map(QPolygonF(r)).boundingRect() bbox |= rbox log_debug("Image '%s':\n\tSize = %gx%g\n\tTransformed = %gx%g %+g %+g\n\tGlobal bbox = %gx%g %+g %+g\n" % (img_name, r.width(), r.height(), rbox.width(), rbox.height(), rbox.left(), rbox.top(), bbox.width(), bbox.height(), bbox.left(), bbox.top())) log_debug("Matrix:\n%g\t%g\t%g\n%g\t%g\t%g\n" % (matrix.m11(), matrix.m12(), matrix.dx(), matrix.m21(), matrix.m22(), matrix.dy())) if result_type == "Growth": if result.cells[i]: self.has_cells = True if result.walls[i]: self.has_walls = True self.has_points = bool(result.data.cell_points) self.nextImage() translate = bbox.topLeft() translate *= -1 self.translate = translate size = bbox.size().toSize() self.img_size = size self._crop = QRect(QPoint(0,0), size) self.finished() self._loading_arguments = {} # All done, we don't need that anymore except RetryTrackingDataException as ex: ex.filename = filename self.retryObject = ex self.finished() return except Exception as ex: _, _, exceptionTraceback = sys.exc_info() self.abort(ex, traceback=exceptionTraceback) raise def run_full(self): if not self.valid(): self.abort("Object was not correctly initialized") return self.stop(False) painter = None try: result = self.result self.update_nb_images(len(result)) # if self.result_type == "Data": # data = result # images = result.images_name # else: # data = result.data # images = result.images # cache = image_cache.cache cellColoring = self.cellColoring wallColoring = self.wallColoring pointColoring = self.pointColoring file_format = self.fileFormat file_pattern = "%s%%0%dd.%s" % (self.filePrefix, len(str(len(result))), file_format) wall_file_pattern = "%s%%0%dd_wall.%s" % (self.filePrefix, len(str(len(result))), file_format) cell_file_pattern = "%s%%0%dd_cell.%s" % (self.filePrefix, len(str(len(result))), file_format) cellColoring.init() wallColoring.init() pointColoring.init() self.nextImage() for i in range(len(result)): if self.stopped(): self.abort("User interruption") return pix, pic_w, pic_c = self.drawImage(i) pix.save(file_pattern % (i+1), file_format) if pic_w is not None: self.saveExtra(pic_w, wall_file_pattern % (i+1), file_format) if pic_c is not None: self.saveExtra(pic_c, cell_file_pattern % (i+1), file_format) self.nextImage() self.finished() except Exception as ex: if painter is not None: painter.end() _, _, exceptionTraceback = sys.exc_info() self.abort(ex, traceback=exceptionTraceback) raise def saveExtra(self, picture, file_name, file_format): rect = picture.boundingRect() pix = QImage(rect.size(), QImage.Format_ARGB32) pix.fill(QColor(0, 0, 0, 0).rgba()) paint = QPainter() paint.begin(pix) paint.drawPicture(rect.topLeft()*-1, picture) paint.end() pix.save(file_name, file_format)
class PlottingThread(QThread): def __init__(self, parent): QThread.__init__(self) self.result = None self.parent = parent self._stopped = False self.mutex = QMutex() self.filePrefix = None self.fileFormat = None self.wallColoring = None self.cellColoring = None self.pointColoring = None self.extraDrawing = [] self.pointSize = None self.pointLineColor = None self.pointLineThickness = None self.ellipsisDraw = None self.overSampling = None self.wallThickness = None self.bgColor = None self.loading = False self._crop = QRect(0, 0, 1, 1) self._pix = None self._end_image_plot = False self._loading_arguments = {} self.retryObject = None def end_step(self): return len(self.result) + 1 def stop(self, value=True): self.mutex.lock() self._stopped = value self.mutex.unlock() def stopped(self): self.mutex.lock() val = self._stopped self.mutex.unlock() return val def nextImage(self): QCoreApplication.postEvent(self.parent, NextImageEvent()) def abort(self, reason, **others): e = AbortPlottingEvent(reason) if others: e.others = others QCoreApplication.postEvent(self.parent, e) def finished(self): if self.loading: QCoreApplication.postEvent(self.parent, FinishLoadingEvent()) self.loading = False else: QCoreApplication.postEvent(self.parent, FinishPlottingEvent()) def image_ready(self): QCoreApplication.postEvent(self.parent, ImageReadyPlottingEvent()) def update_nb_images(self, nb): QCoreApplication.postEvent(self.parent, UpdateNbImageEvent(nb)) @property def crop_left(self): return self._crop.left() @crop_left.setter def crop_left(self, value): self._crop.moveLeft(int(value)) @property def crop_top(self): return self._crop.top() @crop_top.setter def crop_top(self, value): self._crop.moveTop(int(value)) @property def crop_width(self): return self._crop.width() @crop_width.setter def crop_width(self, value): self._crop.setWidth(int(value)) @property def crop_height(self): return self._crop.height() @crop_height.setter def crop_height(self, value): self._crop.setHeight(int(value)) def reset_crop(self): self._crop = QRect(QPoint(0, 0), self.img_size) @property def crop(self): return QRect(self._crop) @crop.deleter def crop(self): self.reset_crop() @property def end_image_plot(self): ''' If true, plot the growth data on the end image rather than the start image of the growth calculation. ''' return self._end_image_plot @end_image_plot.setter def end_image_plot(self, value): self._end_image_plot = bool(value) @property def pix(self): '''Thread-safe image storage.''' self.mutex.lock() pix = self._pix self.mutex.unlock() return pix @pix.setter def pix(self, value): self.mutex.lock() self._pix = value self.mutex.unlock() def render_valid(self): if self.result is None: log_debug("result is None") return False if self.parent is None: log_debug("parent is None") return False if self.ellipsisDraw is None: log_debug("ellipsisDraw is None") return False if self.cellColoring is None: log_debug("cellColoring is None") return False if self.wallColoring is None: log_debug("wallColoring is None") return False if self.pointColoring is None: log_debug("pointColoring is None") return False if self.pointSize is None: log_debug("pointSize is None") return False if self.pointLineThickness is None: log_debug("pointSize is None") return False if self.pointLineColor is None: log_debug("pointSize is None") return False if self.wallThickness is None: log_debug("wallThickness is None") return False if self.overSampling is None: log_debug("overSampling is None") return False if self.bgColor is None: log_debug("bgColor is None") return False return True def valid(self): if self.filePrefix is None: log_debug("filePrefix is None") return False if not self.filePrefix: log_debug("filePrefix is Empty") return False if self.fileFormat is None: log_debug("fileFormat is None") return False return self.render_valid() def drawImage(self, imageid): cache = image_cache.cache cellColoring = self.cellColoring wallColoring = self.wallColoring pointColoring = self.pointColoring ellipsisDraw = self.ellipsisDraw overSampling = self.overSampling extraDrawing = self.extraDrawing bgColor = self.bgColor.rgb() result = self.result if self.result_type == "Data": data = result img_name = result.images_name[imageid] else: data = result.data img_name = result.images[imageid] #scale = data.images_scale[img_name] min_scale = data.minScale() img = cache.image(data.image_path(img_name)) img_data = data[img_name] size = self._crop.size() pix = QImage(size * overSampling, QImage.Format_ARGB32) pix.fill(bgColor) painter = QPainter() if not painter.begin(pix): self.abort("Cannot create painter on QImage") return None, None, None painter.setRenderHints(QPainter.SmoothPixmapTransform, True) painter.setRenderHints(QPainter.Antialiasing, True) if overSampling > 1: painter.scale(overSampling, overSampling) painter.translate(-self._crop.topLeft()) painter.save() painter.translate(self.translate) log_debug("Translating: %gx%g" % (self.translate.x(), self.translate.y())) painter.scale(1 / min_scale, 1 / min_scale) painter.save() matrix = img_data.matrix() painter.setWorldTransform(matrix, True) painter.drawImage(QPoint(0, 0), img) painter.restore() #pt_matrix = QTransform() #pt_matrix.scale(1/min_scale, 1/min_scale) #painter.setTransform(pt_matrix, True) cellColoring.startImage(painter, imageid) wallColoring.startImage(painter, imageid) for ed in extraDrawing: ed.startImage(painter, imageid) if self.result_type == "Growth": cells = result.cells[imageid] walls = result.walls[imageid] else: cells = img_data.cells walls = set() for cid in img_data.cells: pts = [pt for pt in data.cells[cid] if pt in img_data] if len(pts) > 1: for i in range(len(pts)): walls.add(data.wallId(pts[i - 1], pts[i])) # Now, draw the cells and the ellipsis for cid in cells: painter.setPen(Qt.NoPen) color = cellColoring(imageid, cid) painter.setBrush(color) pts = data.cellAtTime(cid, img_data.index) if pts: pts.append(pts[0]) ppts = [] for p1, p2 in zip(pts[:-1], pts[1:]): ppts.append(img_data[p1]) ppts.extend(img_data.walls[p1, p2]) ppts.append(ppts[0]) poly = QPolygonF(ppts) painter.drawPolygon(poly) # And draw the walls wallThickness = self.wallThickness * min_scale for wid in walls: color = wallColoring(imageid, wid) if color.alpha() > 0: pen = QPen(color) pen.setWidthF(wallThickness) painter.setPen(pen) pts = [img_data[wid[0]] ] + img_data.walls[wid[0], wid[1]] + [img_data[wid[1]]] #painter.drawLine(img_data[wid[0]], img_data[wid[1]]) painter.drawPolyline(*pts) # Then, draw the points pointSize = self.pointSize * min_scale pointLineColor = self.pointLineColor pointLineThickness = self.pointLineThickness * min_scale log_debug("pointSize = %g" % pointSize) for pid in img_data: color = pointColoring(imageid, pid) if color.alpha() > 0: pen = QPen(pointLineColor) pen.setWidthF(pointLineThickness) brush = QBrush(color) painter.setPen(pen) painter.setBrush(brush) pos = img_data[pid] rect = QRectF(pos.x() - pointSize, pos.y() - pointSize, 2 * pointSize, 2 * pointSize) painter.drawEllipse(rect) if ellipsisDraw.plot: for cid in cells: pts = data.cellAtTime(cid, img_data.index) if pts: pts.append(pts[0]) ppts = [] for p1, p2 in zip(pts[:-1], pts[1:]): ppts.append(img_data[p1]) ppts.extend(img_data.walls[p1, p2]) ppts.append(ppts[0]) #poly = QPolygonF(ppts) #painter.drawPolygon(poly) ellipsisDraw(painter, imageid, cid, ppts, min_scale) # At last, draw the extra data for ed in extraDrawing: ed(painter, imageid) tr = painter.worldTransform() painter.restore() pic_w = wallColoring.finalizeImage(painter, imageid, tr, self.crop) pic_c = cellColoring.finalizeImage(painter, imageid, tr, self.crop) for ed in extraDrawing: ed.finalizeImage(painter, imageid, tr, self.crop) painter.end() return pix, pic_w, pic_c def start(self): if self.isRunning(): assert not self.rendering_all, "Cannot run twice the rendering of all images with the same object." return if parameters.instance.use_thread: log_debug("Starting rendering thread.") QThread.start(self) return False else: self.run() return True def render_all(self): self.rendering_all = True return self.start() def render_single(self, img_id, retry=False): if retry: while self.isRunning(): self.wait(10000) elif self.isRunning(): return self.rendering_all = False self.current_image = img_id return self.start() def load(self, filename): self.loading = True self.result = filename return self.start() def run(self): if self.loading: self.run_loader() elif self.rendering_all: self.run_full() else: self.run_single() def run_single(self): img = self.current_image self.cellColoring.init() self.wallColoring.init() self.pointColoring.init() log_debug("Rendering image %d" % img) self.pix, self.pic_w, self.pic_c = self.drawImage(img) if self.pic_w is not None: log_debug("Has wall image") if self.pic_c is not None: log_debug("Has cell image") if self.pix is not None: log_debug("Pix correctly rendered") log_debug("Rendered image %d = %s" % (img, self.pix)) self.image_ready() def reload(self): if self.retryObject is None: return self._loading_arguments.update(self.retryObject.method_args) self.load(self.retryObject.filename) def run_loader(self): filename = self.result try: self.retryObject = None # First, prepare the data by getting the images and computing how big they # should be f = open(filename) first_line = f.readline() f.close() if first_line.startswith("TRKR_VERSION"): result = Result(None) result.load(self.result, **self._loading_arguments) result_type = "Growth" else: result = TrackingData() result.load(self.result, **self._loading_arguments) result_type = "Data" self.result = result self.result_type = result_type if result_type == "Data": data = result images = data.images_name if data.cells: self.has_cells = True self.has_walls = True else: self.has_cells = False self.has_walls = False self.has_points = bool(data.cell_points) else: data = result.data images = result.images self.has_cells = False self.has_walls = False self.has_points = False self.images = images cache = image_cache.cache self.update_nb_images(len(result)) bbox = QRectF() ms = data.minScale() for i in range(len(result)): img_name = images[i] img_data = data[img_name] img = cache.image(data.image_path(img_name)) matrix = QTransform() matrix = img_data.matrix() sc = QTransform() sc.scale(1.0 / ms, 1.0 / ms) matrix *= sc r = QRectF(img.rect()) rbox = matrix.map(QPolygonF(r)).boundingRect() bbox |= rbox log_debug( "Image '%s':\n\tSize = %gx%g\n\tTransformed = %gx%g %+g %+g\n\tGlobal bbox = %gx%g %+g %+g\n" % (img_name, r.width(), r.height(), rbox.width(), rbox.height(), rbox.left(), rbox.top(), bbox.width(), bbox.height(), bbox.left(), bbox.top())) log_debug("Matrix:\n%g\t%g\t%g\n%g\t%g\t%g\n" % (matrix.m11(), matrix.m12(), matrix.dx(), matrix.m21(), matrix.m22(), matrix.dy())) if result_type == "Growth": if result.cells[i]: self.has_cells = True if result.walls[i]: self.has_walls = True self.has_points = bool(result.data.cell_points) self.nextImage() translate = bbox.topLeft() translate *= -1 self.translate = translate size = bbox.size().toSize() self.img_size = size self._crop = QRect(QPoint(0, 0), size) self.finished() self._loading_arguments = { } # All done, we don't need that anymore except RetryTrackingDataException as ex: ex.filename = filename self.retryObject = ex self.finished() return except Exception as ex: _, _, exceptionTraceback = sys.exc_info() self.abort(ex, traceback=exceptionTraceback) raise def run_full(self): if not self.valid(): self.abort("Object was not correctly initialized") return self.stop(False) painter = None try: result = self.result self.update_nb_images(len(result)) # if self.result_type == "Data": # data = result # images = result.images_name # else: # data = result.data # images = result.images # cache = image_cache.cache cellColoring = self.cellColoring wallColoring = self.wallColoring pointColoring = self.pointColoring file_format = self.fileFormat file_pattern = "%s%%0%dd.%s" % (self.filePrefix, len(str(len(result))), file_format) wall_file_pattern = "%s%%0%dd_wall.%s" % ( self.filePrefix, len(str(len(result))), file_format) cell_file_pattern = "%s%%0%dd_cell.%s" % ( self.filePrefix, len(str(len(result))), file_format) cellColoring.init() wallColoring.init() pointColoring.init() self.nextImage() for i in range(len(result)): if self.stopped(): self.abort("User interruption") return pix, pic_w, pic_c = self.drawImage(i) pix.save(file_pattern % (i + 1), file_format) if pic_w is not None: self.saveExtra(pic_w, wall_file_pattern % (i + 1), file_format) if pic_c is not None: self.saveExtra(pic_c, cell_file_pattern % (i + 1), file_format) self.nextImage() self.finished() except Exception as ex: if painter is not None: painter.end() _, _, exceptionTraceback = sys.exc_info() self.abort(ex, traceback=exceptionTraceback) raise def saveExtra(self, picture, file_name, file_format): rect = picture.boundingRect() pix = QImage(rect.size(), QImage.Format_ARGB32) pix.fill(QColor(0, 0, 0, 0).rgba()) paint = QPainter() paint.begin(pix) paint.drawPicture(rect.topLeft() * -1, picture) paint.end() pix.save(file_name, file_format)
class Page(object): """Represents a page from a Poppler.Document. It maintains its own size and can draw itself using the cache. It also can maintain a list of links and return links at certain points or rectangles. The visible attribute (setVisible and visible) defaults to True but can be set to False to hide the page from a Surface (this is done by the Layout). """ def __init__(self, document, pageNumber): self._document = document self._pageNumber = pageNumber self._pageSize = document.page(pageNumber).pageSize() self._rotation = popplerqt4.Poppler.Page.Rotate0 self._rect = QRect() self._scale = 1.0 self._visible = True self._layout = lambda: None self._waiting = True # whether image still needs to be generated def document(self): """Returns the document.""" return self._document def pageNumber(self): """Returns the page number.""" return self._pageNumber def pageSize(self): """The page size in points (1/72 inch), taking rotation into account.""" return self._pageSize def layout(self): """Returns the Layout if we are part of one.""" return self._layout() def visible(self): """Returns True if this page is visible (will be displayed).""" return self._visible def setVisible(self, visible): """Sets whether this page is visible (will be displayed).""" self._visible = visible def rect(self): """Returns our QRect(), with position and size.""" return self._rect def size(self): """Returns our size.""" return self._rect.size() def height(self): """Returns our height.""" return self._rect.height() def width(self): """Returns our width.""" return self._rect.width() def pos(self): """Returns our position.""" return self._rect.topLeft() def setPos(self, point): """Sets our position (affects the Layout).""" self._rect.moveTopLeft(point) def setRotation(self, rotation): """Sets our Poppler.Page.Rotation.""" old, self._rotation = self._rotation, rotation if (old ^ rotation) & 1: self._pageSize.transpose() self.computeSize() def rotation(self): """Returns our rotation.""" return self._rotation def computeSize(self): """Recomputes our size.""" xdpi, ydpi = self.layout().dpi() if self.layout() else (72.0, 72.0) x = round(self._pageSize.width() * xdpi / 72.0 * self._scale) y = round(self._pageSize.height() * ydpi / 72.0 * self._scale) self._rect.setSize(QSize(x, y)) def setScale(self, scale): """Changes the display scale.""" self._scale = scale self.computeSize() def scale(self): """Returns our display scale.""" return self._scale def scaleForWidth(self, width): """Returns the scale we need to display ourselves at the given width.""" if self.layout(): return width * 72.0 / self.layout().dpi()[0] / self._pageSize.width() else: return float(width) / self._pageSize.width() def scaleForHeight(self, height): """Returns the scale we need to display ourselves at the given height.""" if self.layout(): return height * 72.0 / self.layout().dpi()[1] / self._pageSize.height() else: return float(height) / self._pageSize.height() def setWidth(self, width): """Change our scale to force our width to the given value.""" self.setScale(self.scaleForWidth(width)) def setHeight(self, height): """Change our scale to force our height to the given value.""" self.setScale(self.scaleForHeight(height)) def paint(self, painter, rect): update_rect = rect & self.rect() if not update_rect: return image_rect = QRect(update_rect.topLeft() - self.rect().topLeft(), update_rect.size()) image = cache.image(self) self._waiting = not image if image: painter.drawImage(update_rect, image, image_rect) else: # schedule an image to be generated, if done our update() method is called cache.generate(self) # find suitable image to be scaled from other size image = cache.image(self, False) if image: hscale = float(image.width()) / self.width() vscale = float(image.height()) / self.height() image_rect = QRectF(image_rect.x() * hscale, image_rect.y() * vscale, image_rect.width() * hscale, image_rect.height() * vscale) painter.drawImage(QRectF(update_rect), image, image_rect) else: # draw blank paper, using the background color of the cache rendering (if set) # or from the document itself. color = (cache.options(self.document()).paperColor() or cache.options().paperColor() or self.document().paperColor()) painter.fillRect(update_rect, color) def update(self): """Called when an image is drawn.""" # only redraw when we were waiting for a correctly sized image. if self._waiting and self.layout(): self.layout().updatePage(self) def repaint(self): """Call this to force a repaint (e.g. when the rendering options are changed).""" self._waiting = True cache.generate(self) def image(self, rect, xdpi=72.0, ydpi=None, options=None): """Returns a QImage of the specified rectangle (relative to our layout). xdpi defaults to 72.0 and ydpi defaults to xdpi. options may be a render.RenderOptions instance that will set some document rendering options just before rendering the image. """ rect = rect.normalized().intersected(self.rect()) if not rect: return rect.translate(-self.pos()) if ydpi is None: ydpi = xdpi hscale = (xdpi * self.pageSize().width()) / (72.0 * self.width()) vscale = (ydpi * self.pageSize().height()) / (72.0 * self.height()) x = rect.x() * hscale y = rect.y() * vscale w = rect.width() * hscale h = rect.height() * vscale with lock(self.document()): options and options.write(self.document()) page = self.document().page(self._pageNumber) image = page.renderToImage(xdpi, ydpi, x, y, w, h, self._rotation) image.setDotsPerMeterX(int(xdpi * 39.37)) image.setDotsPerMeterY(int(ydpi * 39.37)) return image def linksAt(self, point): """Returns a list() of zero or more links touched by point (relative to surface). The list is sorted with the smallest rectangle first. """ # Poppler.Link objects have their linkArea() ranging in width and height # from 0.0 to 1.0, so divide by resp. height and width of the Page. point = point - self.pos() x = float(point.x()) / self.width() y = float(point.y()) / self.height() # rotate if self._rotation: if self._rotation == popplerqt4.Poppler.Page.Rotate90: x, y = y, 1-x elif self._rotation == popplerqt4.Poppler.Page.Rotate180: x, y = 1-x, 1-y else: # 270 x, y = 1-y, x return list(sorted(cache.links(self).at(x, y), key=lambda link: link.linkArea().width())) def linksIn(self, rect): """Returns an unordered set() of links enclosed in rectangle (relative to surface).""" rect = rect.normalized() rect.translate(-self.pos()) left = float(rect.left()) / self.width() top = float(rect.top()) / self.height() right = float(rect.right()) / self.width() bottom = float(rect.bottom()) / self.height() # rotate if self._rotation: if self._rotation == popplerqt4.Poppler.Page.Rotate90: left, top, right, bottom = top, 1-right, bottom, 1-left elif self._rotation == popplerqt4.Poppler.Page.Rotate180: left, top, right, bottom = 1-right, 1-bottom, 1-left, 1-top else: # 270 left, top, right, bottom = 1-bottom, left, 1-top, right return cache.links(self).inside(left, top, right, bottom) def linkRect(self, linkarea): """Returns a QRect encompassing the linkArea (of a link) in coordinates of our rect().""" left, top, right, bottom = linkarea.normalized().getCoords() # rotate if self._rotation: if self._rotation == popplerqt4.Poppler.Page.Rotate90: left, top, right, bottom = 1-bottom, left, 1-top, right elif self._rotation == popplerqt4.Poppler.Page.Rotate180: left, top, right, bottom = 1-right, 1-bottom, 1-left, 1-top else: # 270 left, top, right, bottom = top, 1-right, bottom, 1-left rect = QRect() rect.setCoords(left * self.width(), top * self.height(), right * self.width(), bottom * self.height()) rect.translate(self.pos()) return rect def text(self, rect): """Returns text inside rectangle (relative to surface).""" rect = rect.normalized() rect.translate(-self.pos()) w, h = self.pageSize().width(), self.pageSize().height() left = float(rect.left()) / self.width() * w top = float(rect.top()) / self.height() * h right = float(rect.right()) / self.width() * w bottom = float(rect.bottom()) / self.height() * h if self._rotation: if self._rotation == popplerqt4.Poppler.Page.Rotate90: left, top, right, bottom = top, w-right, bottom, w-left elif self._rotation == popplerqt4.Poppler.Page.Rotate180: left, top, right, bottom = w-right, h-bottom, w-left, h-top else: # 270 left, top, right, bottom = h-bottom, left, h-top, right rect = QRectF() rect.setCoords(left, top, right, bottom) with lock(self.document()): page = self.document().page(self._pageNumber) return page.text(rect) def searchRect(self, rectF): """Returns a QRect encompassing the given rect (in points) to our position, size and rotation.""" rect = rectF.normalized() left, top, right, bottom = rect.getCoords() w, h = self.pageSize().width(), self.pageSize().height() hscale = self.width() / float(w) vscale = self.height() / float(h) if self._rotation: if self._rotation == popplerqt4.Poppler.Page.Rotate90: left, top, right, bottom = w-bottom, left, w-top, right elif self._rotation == popplerqt4.Poppler.Page.Rotate180: left, top, right, bottom = w-right, h-bottom, w-left, h-top else: # 270 left, top, right, bottom = top, h-right, bottom, h-left rect = QRect() rect.setCoords(left * hscale, top * vscale, right * hscale, bottom * vscale) return rect
class SelectArea(QLabel): doneSignal = pyqtSignal(QPixmap) def __init__(self, *args, **kwargs): super(SelectArea, self).__init__(*args, **kwargs) self.pixmap = take_screenshot() self.setPixmap(self.pixmap) self.setCursor(Qt.CrossCursor) self.setWindowFlags(Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint) self.showFullScreen() self.move(0, 0) self.resize(self.pixmap.size()) self.selected = QRect(0, 0, 0, 0) self.first_point = None def paintEvent(self, event): super(SelectArea, self).paintEvent(event) painter = QPainter(self) drawRegion = QRegion(self.pixmap.rect()) drawRegion = drawRegion.subtract(QRegion(self.selected)) painter.setClipRegion(drawRegion) painter.setBrush(QBrush(QColor(0, 0, 0, 120))) painter.drawRect(self.pixmap.rect()) painter.setClipping(False) if self.selected: # направляющие линии painter.setPen(QPen(QColor(255, 255, 255))) painter.setBrush(QBrush(QColor(0, 0, 0, 0))) vertical = self.selected.normalized() vertical.setLeft(-1) vertical.setRight(self.pixmap.rect().right() + 1) horizontal = self.selected.normalized() horizontal.setTop(-1) horizontal.setBottom(self.pixmap.rect().bottom() + 1) painter.drawRects( vertical, horizontal ) # координаты начала bound = self.pixmap.rect() bound.setBottomRight(self.selected.topLeft() - QPoint(5, 5)) painter.drawText( bound, Qt.AlignBottom | Qt.AlignRight, '(%d, %d)' % (self.selected.topLeft().x(), self.selected.topLeft().y()) ) # ширина/высота bound = self.pixmap.rect() bound.setTopLeft(self.selected.bottomRight() + QPoint(5, 5)) painter.drawText( bound, Qt.AlignTop | Qt.AlignLeft, '(%d, %d)' % (self.selected.width(), self.selected.height()) ) def keyPressEvent(self, event): key = event.key() if key == Qt.Key_Escape: self.close() def mousePressEvent(self, event): self.first_point = event.pos() self.selected.setTopLeft(self.first_point) def mouseMoveEvent(self, event): self.move_selection(event.pos()) def mouseReleaseEvent(self, event): self.move_selection(event.pos()) self.finish() def move_selection(self, now_point): self.selected = QRect(self.first_point, now_point).normalized() self.update() def finish(self): self.doneSignal.emit(self.pixmap.copy(self.selected)) self.close()
class StartupScreen(VispaWidget): # inherited parameters BACKGROUND_SHAPE = 'ROUNDRECT' SELECTABLE_FLAG = False AUTOSIZE = True AUTOSIZE_KEEP_ASPECT_RATIO = False PROTOTYPING_DESCRIPTION = """Prototyping""" EXECUTING_DESCRIPTION = """Executing""" VERIFYING_DESCRIPTION = """Verifying""" def __init__(self, parent): self._descriptionWidgets = [] self._descriptionActiveRects = [ QRect(), QRect(), QRect() ] # descriptions will be visible if mouse cursor is in the rect VispaWidget.__init__(self, parent) self._filenewIcon = QIcon(QPixmap(":/resources/filenew.svg")) self._fileopenIcon = QIcon(QPixmap(":/resources/fileopen.svg")) self.setImage( QSvgRenderer(":/resources/startup_development_cycle.svg")) self.setDragable(False) self.setMouseTracking( True) # receive mouse events even if no button is pressed self._hideDescriptions = False self.createPrototypingWidget() self.createExecutionWidget() self.createVerifyingWidget() def createDescriptionWidget(self, arrowDirection, description): widget = VispaWidget(self.parent()) widget.enableAutosizing(True, False) widget.setSelectable(False) widget.setArrowShape(arrowDirection) widget.setVisible(not self._hideDescriptions) widget.setDragable(False) self._descriptionWidgets.append(widget) return widget def createPrototypingWidget(self): self._prototypingDescriptionWidget = self.createDescriptionWidget( VispaWidget.ARROW_SHAPE_BOTTOM, self.PROTOTYPING_DESCRIPTION) bodyWidget = QWidget(self._prototypingDescriptionWidget) bodyWidget.setLayout(QGridLayout()) bodyWidget.layout().setContentsMargins(0, 0, 0, 0) bodyWidget.layout().addWidget(QLabel("Design physics analysis:"), 0, 0) analysisDesignerButton = QToolButton() analysisDesignerButton.setText("Analysis Designer") analysisDesignerButton.setIcon(self._filenewIcon) self.connect(analysisDesignerButton, SIGNAL("clicked(bool)"), self.parent().newAnalysisDesignerSlot) bodyWidget.layout().addWidget(analysisDesignerButton, 0, 1) bodyWidget.layout().addWidget(QLabel("Create physics event:"), 1, 0) pxlButton = QToolButton() pxlButton.setText("PXL Editor") pxlButton.setIcon(self._filenewIcon) self.connect(pxlButton, SIGNAL("clicked(bool)"), self.parent().newPxlSlot) bodyWidget.layout().addWidget(pxlButton, 1, 1) self._prototypingDescriptionWidget.setBodyWidget(bodyWidget) def createExecutionWidget(self): self._executionDescriptionWidget = self.createDescriptionWidget( VispaWidget.ARROW_SHAPE_RIGHT, self.EXECUTING_DESCRIPTION) bodyWidget = QWidget(self._executionDescriptionWidget) bodyWidget.setLayout(QGridLayout()) bodyWidget.layout().setContentsMargins(0, 0, 0, 0) label = QLabel("Open and run existing analysis:") bodyWidget.layout().addWidget(label, 0, 0) analysisDesignerButton = QToolButton() analysisDesignerButton.setText("Open analysis file") analysisDesignerButton.setIcon(self._fileopenIcon) self.connect(analysisDesignerButton, SIGNAL("clicked(bool)"), self.parent().openAnalysisFileSlot) bodyWidget.layout().addWidget(analysisDesignerButton, 0, 1) self._analysisDesignerRecentFilesList = QListWidget() self._analysisDesignerRecentFilesList.setFixedSize( label.sizeHint().width() + analysisDesignerButton.sizeHint().width(), 150) self.connect(self._analysisDesignerRecentFilesList, SIGNAL("doubleClicked(QModelIndex)"), self.parent().openAnalysisFileSlot) bodyWidget.layout().addWidget(self._analysisDesignerRecentFilesList, 1, 0, 1, 2) self._executionDescriptionWidget.setBodyWidget(bodyWidget) def analysisDesignerRecentFilesList(self): return self._analysisDesignerRecentFilesList def createVerifyingWidget(self): self._verifyingDescriptionWidget = self.createDescriptionWidget( VispaWidget.ARROW_SHAPE_LEFT, self.VERIFYING_DESCRIPTION) bodyWidget = QWidget(self._verifyingDescriptionWidget) bodyWidget.setLayout(QGridLayout()) bodyWidget.layout().setContentsMargins(0, 0, 0, 0) label = QLabel("Browse an existing PXL data file:") bodyWidget.layout().addWidget(label, 0, 0) analysisDesignerButton = QToolButton() analysisDesignerButton.setText("Open PXL file") analysisDesignerButton.setIcon(self._fileopenIcon) self.connect(analysisDesignerButton, SIGNAL("clicked(bool)"), self.parent().openPxlFileSlot) bodyWidget.layout().addWidget(analysisDesignerButton, 0, 1) self._pxlEditorRecentFilesList = QListWidget() self._pxlEditorRecentFilesList.setFixedSize( label.sizeHint().width() + analysisDesignerButton.sizeHint().width(), 150) self.connect(self._pxlEditorRecentFilesList, SIGNAL("doubleClicked(QModelIndex)"), self.parent().openPxlFileSlot) bodyWidget.layout().addWidget(self._pxlEditorRecentFilesList, 1, 0, 1, 2) self._verifyingDescriptionWidget.setBodyWidget(bodyWidget) def pxlEditorRecentFilesList(self): return self._pxlEditorRecentFilesList def mouseMoveEvent(self, event): if bool(event.buttons()): VispaWidget.mouseMoveEvent(self, event) elif self._hideDescriptions: for i in range(len(self._descriptionWidgets)): self._descriptionWidgets[i].setVisible( self._descriptionActiveRects[i].contains(event.pos())) def moveEvent(self, event): VispaWidget.moveEvent(self, event) self.rearangeDescriptionWidgets() def rearangeContent(self): VispaWidget.rearangeContent(self) self.rearangeDescriptionWidgets() def rearangeDescriptionWidgets(self): self._activeSize = QSize(0.3 * self.width(), 0.1 * self.height()) self._prototypingRect = QRect( QPoint(0.5 * (self.width() - self._activeSize.width()), 0), self._activeSize) self._executionRect = QRect(QPoint(0, 0.635 * self.height()), self._activeSize) self._verifyingRect = QRect( QPoint(self.width() - self._activeSize.width(), 0.635 * self.height()), self._activeSize) self._descriptionActiveRects[0] = self._prototypingRect self._descriptionActiveRects[1] = self._executionRect self._descriptionActiveRects[2] = self._verifyingRect self._prototypingDescriptionWidget.move( self.mapToParent(self._prototypingRect.topLeft()) + QPoint((self._prototypingRect.width() - self._prototypingDescriptionWidget.width()) * 0.5, -self._prototypingDescriptionWidget.height())) self._executionDescriptionWidget.move( self.mapToParent(self._executionRect.topLeft()) - QPoint( self._executionDescriptionWidget.width(), -0.5 * (self._executionRect.height() - self._executionDescriptionWidget.height()))) self._verifyingDescriptionWidget.move( self.mapToParent(self._verifyingRect.topRight()) - QPoint( 0, -0.5 * (self._verifyingRect.height() - self._verifyingDescriptionWidget.height()))) def boundingRect(self): br = VispaWidget.boundingRect(self) for w in self._descriptionWidgets: br = br.united(w.boundingRect()) return br def setVisible(self, visible): VispaWidget.setVisible(self, visible) self._executionDescriptionWidget.setVisible( visible and not self._hideDescriptions) self._prototypingDescriptionWidget.setVisible( visible and not self._hideDescriptions) self._verifyingDescriptionWidget.setVisible( visible and not self._hideDescriptions)
def __layout(self): # position itself over `widget` widget = self.__widget if widget is None: return alignment = self.__alignment policy = self.sizePolicy() if widget.isWindow(): bounds = widget.geometry() else: bounds = QRect(widget.mapToGlobal(QPoint(0, 0)), widget.size()) if self.isWindow(): bounds = bounds else: bounds = QRect(self.parent().mapFromGlobal(bounds.topLeft()), bounds.size()) sh = self.sizeHint() minsh = self.minimumSizeHint() minsize = self.minimumSize() if minsize.isNull(): minsize = minsh maxsize = bounds.size().boundedTo(self.maximumSize()) minsize = minsize.boundedTo(maxsize) effectivesh = sh.expandedTo(minsize).boundedTo(maxsize) hpolicy = policy.horizontalPolicy() vpolicy = policy.verticalPolicy() def getsize(hint, minimum, maximum, policy): if policy == QSizePolicy.Ignored: return maximum elif policy & QSizePolicy.ExpandFlag: return maximum else: return max(hint, minimum) width = getsize(effectivesh.width(), minsize.width(), maxsize.width(), hpolicy) heightforw = self.heightForWidth(width) if heightforw > 0: height = getsize(heightforw, minsize.height(), maxsize.height(), vpolicy) else: height = getsize(effectivesh.height(), minsize.height(), maxsize.height(), vpolicy) size = QSize(width, height) if alignment & Qt.AlignLeft: x = bounds.x() elif alignment & Qt.AlignRight: x = bounds.right() - size.width() else: x = bounds.x() + max(0, bounds.width() - size.width()) // 2 if alignment & Qt.AlignTop: y = bounds.y() elif alignment & Qt.AlignBottom: y = bounds.bottom() - size.height() else: y = bounds.y() + max(0, bounds.height() - size.height()) // 2 geom = QRect(QPoint(x, y), size) self.setGeometry(geom)
class ImageAreaSelector (QtGui.QWidget): '''This widget provides means to visually crop a portion of an image by selecting it''' # pylint: disable=W0612 NAME = 'ImageAreaSelector' DESCRIPTION = 'A widget used to select part of an image' AUTHOR = 'Gabriele "Whisky" Visconti' WEBSITE = '' # pylint: enable=W0612 selection_changed = QtCore.pyqtSignal() def __init__(self, pixmap, parent=None): '''Constructor''' QtGui.QWidget.__init__(self, parent) self._pixmap = pixmap self._selection_rect = QRect() self._image_origin = QPoint() self._resize_start = None self._drag_start = None self._handle_size = QSize(-10, -10) self._painter = QtGui.QPainter() self._hl_color1 = QtGui.QPalette().color(QtGui.QPalette.Highlight) self._hl_color2 = QtGui.QPalette().color(QtGui.QPalette.Highlight) self._hl_color2.setAlpha(150) self._zoom = 1.0 self.adjust_minum_size() self.setBackgroundRole(QtGui.QPalette.Dark) self.setMouseTracking(True) self.setCursor(Qt.CrossCursor) # -------------------- [BEGIN] QT_OVERRIDE def mousePressEvent (self, event): '''Overrides QWidget's mousePressEvent. Handles starting a new selection, starting a drag operation''' # pylint: disable=C0103 mouse_pos = event.pos() / self._zoom sel_rect = self._selection_rect if not event.button() == Qt.LeftButton: return if (not sel_rect.isNull()) and sel_rect.contains(mouse_pos, True): handle_rect = QRect(sel_rect.bottomRight(), self._handle_size) if handle_rect.contains(mouse_pos): self._resize_start = mouse_pos else: self._drag_start = mouse_pos else: self._resize_start = mouse_pos sel_rect.setTopLeft (mouse_pos) self._selection_rect.setSize(QSize(0, 0)) def mouseMoveEvent (self, event): '''Overrides QWidget's mouseMoveEvent. Handles resizing and dragging operations on selection''' # pylint: disable=C0103 sel_rect = self._selection_rect if self._resize_start: resize_end = event.pos() / self._zoom sel_rect.setBottomRight(sel_rect.bottomRight() + (resize_end - self._resize_start)) self._resize_start = resize_end self.make_selection_square() self.update() elif self._drag_start is not None: drag_end = event.pos() / self._zoom sel_rect.translate(drag_end - self._drag_start) self._drag_start = drag_end self.update() # cursor shape: mouse_pos = event.pos() / self._zoom if (not sel_rect.isNull()) and sel_rect.contains(mouse_pos, True): handle_rect = QRect(sel_rect.bottomRight(), self._handle_size) if handle_rect.contains(mouse_pos): self.setCursor(Qt.SizeFDiagCursor) else: self.setCursor(Qt.OpenHandCursor) else: self.setCursor(Qt.CrossCursor) def mouseReleaseEvent (self, event): '''Overrides QWidget's mouseReleaseEvent. Handles ending a resizing or draggin operation on the selection''' # pylint: disable=C0103 self._selection_rect = self._selection_rect.normalized() self._resize_start = None self._drag_start = None self.update() if not self._selection_rect.isNull(): self.selection_changed.emit() def paintEvent(self, event): '''Overrides QWidtget's paintEvent.''' # pylint: disable=C0103 QtGui.QWidget.paintEvent(self, event) self._painter.begin(self) pixmap_dest_rect = QRect(self._image_origin * self._zoom, self._pixmap.size()*self._zoom) self._painter.drawPixmap(pixmap_dest_rect, self._pixmap) if not self._selection_rect.isNull(): # preparing the darkened frame: sel_rect = self._selection_rect.normalized() frame = QtGui.QPixmap(event.rect().size()) frame.fill(QtGui.QColor(0, 0, 0, 127)) frame_painter = QtGui.QPainter(frame) # erase the selected area from the frame: frame_painter.setCompositionMode( QtGui.QPainter.CompositionMode_DestinationIn) sel_rect_scaled = QRect(sel_rect.topLeft() * self._zoom, sel_rect.size() * self._zoom) frame_painter.fillRect(sel_rect_scaled, QtGui.QColor(0, 0, 0, 0)) # draw selection border : frame_painter.setCompositionMode( QtGui.QPainter.CompositionMode_SourceOver) frame_painter.setPen(self._hl_color1) frame_painter.drawRect(sel_rect_scaled) # draw the resize grip (if possible) if sel_rect_scaled.width() > 20 and sel_rect_scaled.height() > 20: handle_rect = QRect(sel_rect_scaled.bottomRight(), self._handle_size) frame_painter.fillRect(handle_rect, self._hl_color2) frame_painter.drawRect(handle_rect) frame_painter.end() # painting the darkened frame: self._painter.drawPixmap(0, 0, frame) self._painter.end() def resizeEvent(self, event): '''Overrides QWidget's resizeEvent. Handles image centering.''' # pylint: disable=C0103 self.adjust_image_origin() # -------------------- [END] QT_OVERRIDE def adjust_image_origin(self): '''Recalculates the top left corner's image position, so the image is painted centered''' # pylint: disable=C0103 new_size = self.size() / self._zoom pix_size = self._pixmap.size() dx = (new_size.width() - pix_size.width() ) /2 dy = (new_size.height() - pix_size.height()) /2 new_image_origin = QPoint(dx, dy) self._selection_rect.translate(new_image_origin - self._image_origin) self._image_origin = new_image_origin log.info('image origin: %s' % new_image_origin) def select_unscaled(self): '''Selects, if possible, a 96 x 96 square centered around the original image. In this way the image won't be scaled but won't take up all the 96 x 96 area.''' # pylint: disable=C0103 pix_size = self._pixmap.size() if pix_size.width() <= 96 and pix_size.height() <= 96: viewport_size = self.size() x = (viewport_size.width () - 96) / 2 y = (viewport_size.height() - 96) / 2 self._selection_rect.setTopLeft(QPoint(x, y)) self._selection_rect.setSize(QSize(96, 96)) self.update() self.selection_changed.emit() def select_all(self): '''Selects the whole image. Currently broken for images taller than wide.''' # TODO: make me work! self._selection_rect.setTopLeft(self._image_origin) self._selection_rect.setSize(self._pixmap.size()) self.update() self.selection_changed.emit() def rotate_left(self): '''Rotates the image counterclockwise.''' self._pixmap = self._pixmap.transformed(QtGui.QTransform().rotate(-90)) self.adjust_minum_size() self.adjust_image_origin() self.update() self.selection_changed.emit() def rotate_right(self): '''Rotates the image clockwise''' self._pixmap = self._pixmap.transformed(QtGui.QTransform().rotate(90)) self.adjust_minum_size() self.adjust_image_origin() self.update() self.selection_changed.emit() def adjust_minum_size(self): '''Sets the new minimum size, calculated upon the image size and the _zoom factor.''' pixmap = self._pixmap if pixmap.width() < 96 or pixmap.height() < 96: min_size = QSize(96, 96) else: min_size = pixmap.size() self.setMinimumSize(min_size*self._zoom) def make_selection_square(self): '''Modify the selected area making it square''' wid = self._selection_rect.width () self._selection_rect.setSize(QSize(wid, wid)) def set_zoom(self, zoomlevel): '''Sets the specified zoomlevel''' self._zoom = zoomlevel self.adjust_minum_size() self.adjust_image_origin() self.update() self.selection_changed.emit() def fit_zoom(self): '''Chooses a zoomlevel that makes visible the entire image. Currently broken.''' widget_wid = self.size().width () widget_hei = self.size().height() pixmap_wid = self._pixmap.width () pixmap_hei = self._pixmap.height() self._zoom = (min(widget_wid, widget_hei) / min(pixmap_wid, pixmap_hei)) self.adjust_minum_size() self.adjust_image_origin() self.update() self.selection_changed.emit() def get_selected_pixmap(self): '''Returns the pixmap contained in the selection rect. Currently doesn't handle transparency correctly''' sel_rect_scaled = QRect(self._selection_rect.topLeft() * self._zoom, self._selection_rect.size() * self._zoom) return QtGui.QPixmap.grabWidget(self, sel_rect_scaled)
class StartupScreen(VispaWidget): # inherited parameters BACKGROUND_SHAPE = 'ROUNDRECT' SELECTABLE_FLAG = False AUTOSIZE = True AUTOSIZE_KEEP_ASPECT_RATIO = False PROTOTYPING_DESCRIPTION = """Prototyping""" EXECUTING_DESCRIPTION = """Executing""" VERIFYING_DESCRIPTION = """Verifying""" def __init__(self, parent): self._descriptionWidgets = [] self._descriptionActiveRects = [QRect(), QRect(), QRect()] # descriptions will be visible if mouse cursor is in the rect VispaWidget.__init__(self, parent) self._filenewIcon = QIcon(QPixmap(":/resources/filenew.svg")) self._fileopenIcon = QIcon(QPixmap(":/resources/fileopen.svg")) self.setImage(QSvgRenderer(":/resources/startup_development_cycle.svg")) self.setDragable(False) self.setMouseTracking(True) # receive mouse events even if no button is pressed self._hideDescriptions = False self.createPrototypingWidget() self.createExecutionWidget() self.createVerifyingWidget() def createDescriptionWidget(self, arrowDirection, description): widget = VispaWidget(self.parent()) widget.enableAutosizing(True, False) widget.setSelectable(False) widget.setArrowShape(arrowDirection) widget.setVisible(not self._hideDescriptions) widget.setDragable(False) self._descriptionWidgets.append(widget) return widget def createPrototypingWidget(self): self._prototypingDescriptionWidget = self.createDescriptionWidget(VispaWidget.ARROW_SHAPE_BOTTOM, self.PROTOTYPING_DESCRIPTION) bodyWidget = QWidget(self._prototypingDescriptionWidget) bodyWidget.setLayout(QGridLayout()) bodyWidget.layout().setContentsMargins(0, 0, 0, 0) bodyWidget.layout().addWidget(QLabel("Design physics analysis:"), 0, 0) analysisDesignerButton = QToolButton() analysisDesignerButton.setText("Analysis Designer") analysisDesignerButton.setIcon(self._filenewIcon) self.connect(analysisDesignerButton, SIGNAL("clicked(bool)"), self.parent().newAnalysisDesignerSlot) bodyWidget.layout().addWidget(analysisDesignerButton, 0, 1) bodyWidget.layout().addWidget(QLabel("Create physics event:"), 1, 0) pxlButton = QToolButton() pxlButton.setText("PXL Editor") pxlButton.setIcon(self._filenewIcon) self.connect(pxlButton, SIGNAL("clicked(bool)"), self.parent().newPxlSlot) bodyWidget.layout().addWidget(pxlButton, 1, 1) self._prototypingDescriptionWidget.setBodyWidget(bodyWidget) def createExecutionWidget(self): self._executionDescriptionWidget = self.createDescriptionWidget(VispaWidget.ARROW_SHAPE_RIGHT, self.EXECUTING_DESCRIPTION) bodyWidget = QWidget(self._executionDescriptionWidget) bodyWidget.setLayout(QGridLayout()) bodyWidget.layout().setContentsMargins(0, 0, 0, 0) label=QLabel("Open and run existing analysis:") bodyWidget.layout().addWidget(label, 0, 0) analysisDesignerButton = QToolButton() analysisDesignerButton.setText("Open analysis file") analysisDesignerButton.setIcon(self._fileopenIcon) self.connect(analysisDesignerButton, SIGNAL("clicked(bool)"), self.parent().openAnalysisFileSlot) bodyWidget.layout().addWidget(analysisDesignerButton, 0, 1) self._analysisDesignerRecentFilesList=QListWidget() self._analysisDesignerRecentFilesList.setFixedSize(label.sizeHint().width()+analysisDesignerButton.sizeHint().width(),150) self.connect(self._analysisDesignerRecentFilesList, SIGNAL("doubleClicked(QModelIndex)"), self.parent().openAnalysisFileSlot) bodyWidget.layout().addWidget(self._analysisDesignerRecentFilesList, 1, 0, 1, 2) self._executionDescriptionWidget.setBodyWidget(bodyWidget) def analysisDesignerRecentFilesList(self): return self._analysisDesignerRecentFilesList def createVerifyingWidget(self): self._verifyingDescriptionWidget = self.createDescriptionWidget(VispaWidget.ARROW_SHAPE_LEFT, self.VERIFYING_DESCRIPTION) bodyWidget = QWidget(self._verifyingDescriptionWidget) bodyWidget.setLayout(QGridLayout()) bodyWidget.layout().setContentsMargins(0, 0, 0, 0) label=QLabel("Browse an existing PXL data file:") bodyWidget.layout().addWidget(label, 0, 0) analysisDesignerButton = QToolButton() analysisDesignerButton.setText("Open PXL file") analysisDesignerButton.setIcon(self._fileopenIcon) self.connect(analysisDesignerButton, SIGNAL("clicked(bool)"), self.parent().openPxlFileSlot) bodyWidget.layout().addWidget(analysisDesignerButton, 0, 1) self._pxlEditorRecentFilesList=QListWidget() self._pxlEditorRecentFilesList.setFixedSize(label.sizeHint().width()+analysisDesignerButton.sizeHint().width(),150) self.connect(self._pxlEditorRecentFilesList, SIGNAL("doubleClicked(QModelIndex)"), self.parent().openPxlFileSlot) bodyWidget.layout().addWidget(self._pxlEditorRecentFilesList, 1, 0, 1, 2) self._verifyingDescriptionWidget.setBodyWidget(bodyWidget) def pxlEditorRecentFilesList(self): return self._pxlEditorRecentFilesList def mouseMoveEvent(self, event): if bool(event.buttons()): VispaWidget.mouseMoveEvent(self, event) elif self._hideDescriptions: for i in range(len(self._descriptionWidgets)): self._descriptionWidgets[i].setVisible(self._descriptionActiveRects[i].contains(event.pos())) def moveEvent(self, event): VispaWidget.moveEvent(self, event) self.rearangeDescriptionWidgets() def rearangeContent(self): VispaWidget.rearangeContent(self) self.rearangeDescriptionWidgets() def rearangeDescriptionWidgets(self): self._activeSize = QSize(0.3 * self.width(), 0.1 * self.height()) self._prototypingRect = QRect(QPoint(0.5 * (self.width() - self._activeSize.width()), 0), self._activeSize) self._executionRect = QRect(QPoint(0, 0.635 * self.height()), self._activeSize) self._verifyingRect = QRect(QPoint(self.width() -self._activeSize.width(), 0.635 * self.height()), self._activeSize) self._descriptionActiveRects[0] = self._prototypingRect self._descriptionActiveRects[1] = self._executionRect self._descriptionActiveRects[2] = self._verifyingRect self._prototypingDescriptionWidget.move(self.mapToParent(self._prototypingRect.topLeft()) + QPoint((self._prototypingRect.width() - self._prototypingDescriptionWidget.width()) * 0.5, - self._prototypingDescriptionWidget.height())) self._executionDescriptionWidget.move(self.mapToParent(self._executionRect.topLeft()) - QPoint(self._executionDescriptionWidget.width(), - 0.5 * (self._executionRect.height() - self._executionDescriptionWidget.height()))) self._verifyingDescriptionWidget.move(self.mapToParent(self._verifyingRect.topRight()) - QPoint(0, - 0.5 * (self._verifyingRect.height() - self._verifyingDescriptionWidget.height()))) def boundingRect(self): br = VispaWidget.boundingRect(self) for w in self._descriptionWidgets: br = br.united(w.boundingRect()) return br def setVisible(self, visible): VispaWidget.setVisible(self, visible) self._executionDescriptionWidget.setVisible(visible and not self._hideDescriptions) self._prototypingDescriptionWidget.setVisible(visible and not self._hideDescriptions) self._verifyingDescriptionWidget.setVisible(visible and not self._hideDescriptions)
class ArkMapToolInteractive(QgsMapTool): _active = False _dragging = False _panningEnabled = False _zoomingEnabled = False _zoomRubberBand = None #QgsRubberBand() _zoomRect = None # QRect() _snappingEnabled = False _snapper = None #QgsMapCanvasSnapper() _snappingMarker = None # QgsVertexMarker() _showSnappableVertices = False _snappableVertices = [] # [QgsPoint()] _snappableMarkers = [] # [QgsVertexMarker()] def __init__(self, canvas, snappingEnabled=False, showSnappableVertices=False): super(ArkMapToolInteractive, self).__init__(canvas) self._snappingEnabled = snappingEnabled self._showSnappableVertices = showSnappableVertices def __del__(self): if self._active: self.deactivate() def isActive(self): return self._active def activate(self): super(ArkMapToolInteractive, self).activate() self._active = True self._startSnapping() def deactivate(self): self._active = False if self._snappingEnabled: self._stopSnapping() if (self._zoomRubberBand is not None): self.canvas().scene().removeItem(self._zoomRubberBand) self._zoomRubberBand = None super(ArkMapToolInteractive, self).deactivate() def setAction(self, action): super(ArkMapToolInteractive, self).setAction(action) self.action().triggered.connect(self._activate) def _activate(self): self.canvas().setMapTool(self) def panningEnabled(self): return self._panningEnabled def setPanningEnabled(self, enabled): self._panningEnabled = enabled def zoomingEnabled(self): return self._zoomingEnabled def setZoomingEnabled(self, enabled): self._zoomingEnabled = enabled def snappingEnabled(self): return self._snappingEnabled def setSnappingEnabled(self, enabled): if (self._snappingEnabled == enabled): return self._snappingEnabled = enabled if not self._active: return if enabled: self._startSnapping() else: self._stopSnapping() def _startSnapping(self): self._snapper = QgsMapCanvasSnapper() self._snapper.setMapCanvas(self.canvas()) if self._showSnappableVertices: self._startSnappableVertices() def _stopSnapping(self): self._deleteSnappingMarker() self._snapper = None if self._showSnappableVertices: self._stopSnappableVertices() def showSnappableVertices(self): return self._showSnappableVertices def setShowSnappableVertices(self, show): if (self._showSnappableVertices == show): return self._showSnappableVertices = show if not self._active: return if show: self._startSnappableVertices() else: self._stopSnappableVertices() def _startSnappableVertices(self): self.canvas().layersChanged.connect(self._layersChanged) self.canvas().extentsChanged.connect(self._redrawSnappableMarkers) QgsProject.instance().snapSettingsChanged.connect(self._layersChanged) self._layersChanged() def _stopSnappableVertices(self): self._deleteSnappableMarkers() self._snappableLayers = [] self.canvas().layersChanged.disconnect(self._layersChanged) self.canvas().extentsChanged.disconnect(self._redrawSnappableMarkers) QgsProject.instance().snapSettingsChanged.disconnect(self._layersChanged) def canvasMoveEvent(self, e): super(ArkMapToolInteractive, self).canvasMoveEvent(e) if not self._active: return e.ignore() if (self._panningEnabled and e.buttons() & Qt.LeftButton): # Pan map mode if not self._dragging: self._dragging = True self.setCursor(QCursor(Qt.ClosedHandCursor)) self.canvas().panAction(e) e.accept() elif (self._zoomingEnabled and e.buttons() & Qt.RightButton): # Zoom map mode if not self._dragging: self._dragging = True self.setCursor(QCursor(Qt.ClosedHandCursor)) self._zoomRubberBand = QgsRubberBand(self.canvas(), QGis.Polygon) color = QColor(Qt.blue) color.setAlpha(63) self._zoomRubberBand.setColor(color) self._zoomRect = QRect(0, 0, 0, 0) self._zoomRect.setTopLeft(e.pos()) self._zoomRect.setBottomRight(e.pos()) if self._zoomRubberBand is not None: self._zoomRubberBand.setToCanvasRectangle(self._zoomRect) self._zoomRubberBand.show() e.accept() elif self._snappingEnabled: mapPoint, snapped = self._snapCursorPoint(e.pos()) if (snapped): self._createSnappingMarker(mapPoint) else: self._deleteSnappingMarker() def canvasReleaseEvent(self, e): super(ArkMapToolInteractive, self).canvasReleaseEvent(e) e.ignore() if (e.button() == Qt.LeftButton): if self._dragging: # Pan map mode self.canvas().panActionEnd(e.pos()) self.setCursor(capture_point_cursor) self._dragging = False e.accept() elif (e.button() == Qt.RightButton): if self._dragging: # Zoom mode self._zoomRect.setBottomRight(e.pos()) if (self._zoomRect.topLeft() != self._zoomRect.bottomRight()): coordinateTransform = self.canvas().getCoordinateTransform() ll = coordinateTransform.toMapCoordinates(self._zoomRect.left(), self._zoomRect.bottom()) ur = coordinateTransform.toMapCoordinates(self._zoomRect.right(), self._zoomRect.top()) r = QgsRectangle() r.setXMinimum(ll.x()) r.setYMinimum(ll.y()) r.setXMaximum(ur.x()) r.setYMaximum(ur.y()) r.normalize() if (r.width() != 0 and r.height() != 0): self.canvas().setExtent(r) self.canvas().refresh() self._dragging = False if (self._zoomRubberBand is not None): self.canvas().scene().removeItem(self._zoomRubberBand) self._zoomRubberBand = None e.accept() def keyPressEvent(self, e): super(ArkMapToolInteractive, self).keyPressEvent(e) if (e.key() == Qt.Key_Escape): self.canvas().unsetMapTool(self) e.accept() def _snapCursorPoint(self, cursorPoint): res, snapResults = self._snapper.snapToBackgroundLayers(cursorPoint) if (res != 0 or len(snapResults) < 1): return self.toMapCoordinates(cursorPoint), False else: # Take a copy as QGIS will delete the result! snappedVertex = QgsPoint(snapResults[0].snappedVertex) return snappedVertex, True def _createSnappingMarker(self, snapPoint): if (self._snappingMarker is None): self._snappingMarker = QgsVertexMarker(self.canvas()) self._snappingMarker.setIconType(QgsVertexMarker.ICON_CROSS) self._snappingMarker.setColor(Qt.magenta) self._snappingMarker.setPenWidth(3) self._snappingMarker.setCenter(snapPoint) def _deleteSnappingMarker(self): if (self._snappingMarker is not None): self.canvas().scene().removeItem(self._snappingMarker) self._snappingMarker = None def _createSnappableMarkers(self): if (not self._showSnappableVertices or not self._snappingEnabled): return extent = self.canvas().extent() for vertex in self._snappableVertices.asMultiPoint(): if (extent.contains(vertex)): marker = QgsVertexMarker(self.canvas()) marker.setIconType(QgsVertexMarker.ICON_X) marker.setColor(Qt.gray) marker.setPenWidth(1) marker.setCenter(vertex) self._snappableMarkers.append(marker) def _deleteSnappableMarkers(self): for marker in self._snappableMarkers: self.canvas().scene().removeItem(marker) del self._snappableMarkers[:] def _layersChanged(self): if (not self._showSnappableVertices or not self._snappingEnabled): return self._buildSnappableLayers() self._deleteSnappableMarkers() self._createSnappableMarkers() def _redrawSnappableMarkers(self): if (not self._showSnappableVertices or not self._snappingEnabled): return self._deleteSnappableMarkers() self._createSnappableMarkers() def _buildSnappableLayers(self): if (not self._showSnappableVertices or not self._snappingEnabled): return vertices = [] for layer in self.canvas().layers(): ok, enabled, type, units, tolerance, avoid = QgsProject.instance().snapSettingsForLayer(layer.id()) if (ok and enabled and not layer.isEditable()): for feature in layer.getFeatures(): geometry = feature.geometry() if geometry is None: pass elif geometry.type() == QGis.Point: vertices.extend([geometry.asPoint()]) elif geometry.type() == QGis.Line: vertices.extend(geometry.asPolyline()) elif geometry.type() == QGis.Polygon: lines = geometry.asPolygon() for line in lines: vertices.extend(line) self._snappableVertices = QgsGeometry.fromMultiPoint(vertices) self._snappableVertices.simplify(0)
class ImageAreaSelector(QtGui.QWidget): '''This widget provides means to visually crop a portion of an image by selecting it''' # pylint: disable=W0612 NAME = 'ImageAreaSelector' DESCRIPTION = 'A widget used to select part of an image' AUTHOR = 'Gabriele "Whisky" Visconti' WEBSITE = '' # pylint: enable=W0612 selection_changed = QtCore.pyqtSignal() def __init__(self, pixmap, parent=None): '''Constructor''' QtGui.QWidget.__init__(self, parent) self._pixmap = pixmap self._selection_rect = QRect() self._image_origin = QPoint() self._resize_start = None self._drag_start = None self._handle_size = QSize(-10, -10) self._painter = QtGui.QPainter() self._hl_color1 = QtGui.QPalette().color(QtGui.QPalette.Highlight) self._hl_color2 = QtGui.QPalette().color(QtGui.QPalette.Highlight) self._hl_color2.setAlpha(150) self._zoom = 1.0 self.adjust_minum_size() self.setBackgroundRole(QtGui.QPalette.Dark) self.setMouseTracking(True) self.setCursor(Qt.CrossCursor) # -------------------- [BEGIN] QT_OVERRIDE def mousePressEvent(self, event): '''Overrides QWidget's mousePressEvent. Handles starting a new selection, starting a drag operation''' # pylint: disable=C0103 mouse_pos = event.pos() / self._zoom sel_rect = self._selection_rect if not event.button() == Qt.LeftButton: return if (not sel_rect.isNull()) and sel_rect.contains(mouse_pos, True): handle_rect = QRect(sel_rect.bottomRight(), self._handle_size) if handle_rect.contains(mouse_pos): self._resize_start = mouse_pos else: self._drag_start = mouse_pos else: self._resize_start = mouse_pos sel_rect.setTopLeft(mouse_pos) self._selection_rect.setSize(QSize(0, 0)) def mouseMoveEvent(self, event): '''Overrides QWidget's mouseMoveEvent. Handles resizing and dragging operations on selection''' # pylint: disable=C0103 sel_rect = self._selection_rect if self._resize_start: resize_end = event.pos() / self._zoom sel_rect.setBottomRight(sel_rect.bottomRight() + (resize_end - self._resize_start)) self._resize_start = resize_end self.make_selection_square() self.update() elif self._drag_start is not None: drag_end = event.pos() / self._zoom sel_rect.translate(drag_end - self._drag_start) self._drag_start = drag_end self.update() # cursor shape: mouse_pos = event.pos() / self._zoom if (not sel_rect.isNull()) and sel_rect.contains(mouse_pos, True): handle_rect = QRect(sel_rect.bottomRight(), self._handle_size) if handle_rect.contains(mouse_pos): self.setCursor(Qt.SizeFDiagCursor) else: self.setCursor(Qt.OpenHandCursor) else: self.setCursor(Qt.CrossCursor) def mouseReleaseEvent(self, event): '''Overrides QWidget's mouseReleaseEvent. Handles ending a resizing or draggin operation on the selection''' # pylint: disable=C0103 self._selection_rect = self._selection_rect.normalized() self._resize_start = None self._drag_start = None self.update() if not self._selection_rect.isNull(): self.selection_changed.emit() def paintEvent(self, event): '''Overrides QWidtget's paintEvent.''' # pylint: disable=C0103 QtGui.QWidget.paintEvent(self, event) self._painter.begin(self) pixmap_dest_rect = QRect(self._image_origin * self._zoom, self._pixmap.size() * self._zoom) self._painter.drawPixmap(pixmap_dest_rect, self._pixmap) if not self._selection_rect.isNull(): # preparing the darkened frame: sel_rect = self._selection_rect.normalized() frame = QtGui.QPixmap(event.rect().size()) frame.fill(QtGui.QColor(0, 0, 0, 127)) frame_painter = QtGui.QPainter(frame) # erase the selected area from the frame: frame_painter.setCompositionMode( QtGui.QPainter.CompositionMode_DestinationIn) sel_rect_scaled = QRect(sel_rect.topLeft() * self._zoom, sel_rect.size() * self._zoom) frame_painter.fillRect(sel_rect_scaled, QtGui.QColor(0, 0, 0, 0)) # draw selection border : frame_painter.setCompositionMode( QtGui.QPainter.CompositionMode_SourceOver) frame_painter.setPen(self._hl_color1) frame_painter.drawRect(sel_rect_scaled) # draw the resize grip (if possible) if sel_rect_scaled.width() > 20 and sel_rect_scaled.height() > 20: handle_rect = QRect(sel_rect_scaled.bottomRight(), self._handle_size) frame_painter.fillRect(handle_rect, self._hl_color2) frame_painter.drawRect(handle_rect) frame_painter.end() # painting the darkened frame: self._painter.drawPixmap(0, 0, frame) self._painter.end() def resizeEvent(self, event): '''Overrides QWidget's resizeEvent. Handles image centering.''' # pylint: disable=C0103 self.adjust_image_origin() # -------------------- [END] QT_OVERRIDE def adjust_image_origin(self): '''Recalculates the top left corner's image position, so the image is painted centered''' # pylint: disable=C0103 new_size = self.size() / self._zoom pix_size = self._pixmap.size() dx = (new_size.width() - pix_size.width()) / 2 dy = (new_size.height() - pix_size.height()) / 2 new_image_origin = QPoint(dx, dy) self._selection_rect.translate(new_image_origin - self._image_origin) self._image_origin = new_image_origin log.info('image origin: %s' % new_image_origin) def select_unscaled(self): '''Selects, if possible, a 96 x 96 square centered around the original image. In this way the image won't be scaled but won't take up all the 96 x 96 area.''' # pylint: disable=C0103 pix_size = self._pixmap.size() if pix_size.width() <= 96 and pix_size.height() <= 96: viewport_size = self.size() x = (viewport_size.width() - 96) / 2 y = (viewport_size.height() - 96) / 2 self._selection_rect.setTopLeft(QPoint(x, y)) self._selection_rect.setSize(QSize(96, 96)) self.update() self.selection_changed.emit() def select_all(self): '''Selects the whole image. Currently broken for images taller than wide.''' # TODO: make me work! self._selection_rect.setTopLeft(self._image_origin) self._selection_rect.setSize(self._pixmap.size()) self.update() self.selection_changed.emit() def rotate_left(self): '''Rotates the image counterclockwise.''' self._pixmap = self._pixmap.transformed(QtGui.QTransform().rotate(-90)) self.adjust_minum_size() self.adjust_image_origin() self.update() self.selection_changed.emit() def rotate_right(self): '''Rotates the image clockwise''' self._pixmap = self._pixmap.transformed(QtGui.QTransform().rotate(90)) self.adjust_minum_size() self.adjust_image_origin() self.update() self.selection_changed.emit() def adjust_minum_size(self): '''Sets the new minimum size, calculated upon the image size and the _zoom factor.''' pixmap = self._pixmap if pixmap.width() < 96 or pixmap.height() < 96: min_size = QSize(96, 96) else: min_size = pixmap.size() self.setMinimumSize(min_size * self._zoom) def make_selection_square(self): '''Modify the selected area making it square''' wid = self._selection_rect.width() self._selection_rect.setSize(QSize(wid, wid)) def set_zoom(self, zoomlevel): '''Sets the specified zoomlevel''' self._zoom = zoomlevel self.adjust_minum_size() self.adjust_image_origin() self.update() self.selection_changed.emit() def fit_zoom(self): '''Chooses a zoomlevel that makes visible the entire image. Currently broken.''' widget_wid = self.size().width() widget_hei = self.size().height() pixmap_wid = self._pixmap.width() pixmap_hei = self._pixmap.height() self._zoom = (min(widget_wid, widget_hei) / min(pixmap_wid, pixmap_hei)) self.adjust_minum_size() self.adjust_image_origin() self.update() self.selection_changed.emit() def get_selected_pixmap(self): '''Returns the pixmap contained in the selection rect. Currently doesn't handle transparency correctly''' sel_rect_scaled = QRect(self._selection_rect.topLeft() * self._zoom, self._selection_rect.size() * self._zoom) return QtGui.QPixmap.grabWidget(self, sel_rect_scaled)
class Page(object): """Represents a page from a Poppler.Document. It maintains its own size and can draw itself using the cache. It also can maintain a list of links and return links at certain points or rectangles. The visible attribute (setVisible and visible) defaults to True but can be set to False to hide the page from a Surface (this is done by the Layout). """ def __init__(self, document, pageNumber): self._document = document self._pageNumber = pageNumber self._pageSize = document.page(pageNumber).pageSize() self._rotation = popplerqt4.Poppler.Page.Rotate0 self._rect = QRect() self._scale = 1.0 self._visible = True self._layout = lambda: None self._waiting = True # whether image still needs to be generated def document(self): """Returns the document.""" return self._document def pageNumber(self): """Returns the page number.""" return self._pageNumber def pageSize(self): """The page size in points (1/72 inch), taking rotation into account.""" return self._pageSize def layout(self): """Returns the Layout if we are part of one.""" return self._layout() def visible(self): """Returns True if this page is visible (will be displayed).""" return self._visible def setVisible(self, visible): """Sets whether this page is visible (will be displayed).""" self._visible = visible def rect(self): """Returns our QRect(), with position and size.""" return self._rect def size(self): """Returns our size.""" return self._rect.size() def height(self): """Returns our height.""" return self._rect.height() def width(self): """Returns our width.""" return self._rect.width() def pos(self): """Returns our position.""" return self._rect.topLeft() def setPos(self, point): """Sets our position (affects the Layout).""" self._rect.moveTopLeft(point) def setRotation(self, rotation): """Sets our Poppler.Page.Rotation.""" old, self._rotation = self._rotation, rotation if (old ^ rotation) & 1: self._pageSize.transpose() self.computeSize() def rotation(self): """Returns our rotation.""" return self._rotation def computeSize(self): """Recomputes our size.""" xdpi, ydpi = self.layout().dpi() if self.layout() else (72.0, 72.0) x = round(self._pageSize.width() * xdpi / 72.0 * self._scale) y = round(self._pageSize.height() * ydpi / 72.0 * self._scale) self._rect.setSize(QSize(x, y)) def setScale(self, scale): """Changes the display scale.""" self._scale = scale self.computeSize() def scale(self): """Returns our display scale.""" return self._scale def scaleForWidth(self, width): """Returns the scale we need to display ourselves at the given width.""" if self.layout(): return width * 72.0 / self.layout().dpi( )[0] / self._pageSize.width() else: return float(width) / self._pageSize.width() def scaleForHeight(self, height): """Returns the scale we need to display ourselves at the given height.""" if self.layout(): return height * 72.0 / self.layout().dpi( )[1] / self._pageSize.height() else: return float(height) / self._pageSize.height() def setWidth(self, width): """Change our scale to force our width to the given value.""" self.setScale(self.scaleForWidth(width)) def setHeight(self, height): """Change our scale to force our height to the given value.""" self.setScale(self.scaleForHeight(height)) def image(self): """Render the page as an image or our size. Return a QImage.""" d = self._document w, h, r = self.width(), self.height(), self.rotation() page = d.page(self._pageNumber) pageSize = page.pageSize() if r & 1: pageSize.transpose() xres = 72.0 * w / pageSize.width() yres = 72.0 * h / pageSize.height() threshold = cache.options().oversampleThreshold() or cache.options( d).oversampleThreshold() multiplier = 2 if xres < threshold else 1 with lock(d): cache.options().write(d) cache.options(d).write(d) image = page.renderToImage(xres * multiplier, yres * multiplier, 0, 0, w * multiplier, h * multiplier, r) if multiplier == 2: image = image.scaledToWidth(w, Qt.SmoothTransformation) return image def paint(self, painter, rect): update_rect = rect & self.rect() if not update_rect: return image_rect = QRect(update_rect.topLeft() - self.rect().topLeft(), update_rect.size()) image = cache.image(self) self._waiting = not image if image: painter.drawImage(update_rect, image, image_rect) else: # schedule an image to be generated, if done our update() method is called cache.generate(self) # find suitable image to be scaled from other size image = cache.image(self, False) if image: hscale = float(image.width()) / self.width() vscale = float(image.height()) / self.height() image_rect = QRectF(image_rect.x() * hscale, image_rect.y() * vscale, image_rect.width() * hscale, image_rect.height() * vscale) painter.drawImage(QRectF(update_rect), image, image_rect) else: # draw blank paper, using the background color of the cache rendering (if set) # or from the document itself. color = (cache.options(self.document()).paperColor() or cache.options().paperColor() or self.document().paperColor()) painter.fillRect(update_rect, color) def update(self): """Called when an image is drawn.""" # only redraw when we were waiting for a correctly sized image. if self._waiting and self.layout(): self.layout().updatePage(self) def repaint(self): """Call this to force a repaint (e.g. when the rendering options are changed).""" self._waiting = True cache.generate(self) def image(self, rect, xdpi=72.0, ydpi=None, options=None): """Returns a QImage of the specified rectangle (relative to our layout). xdpi defaults to 72.0 and ydpi defaults to xdpi. options may be a render.RenderOptions instance that will set some document rendering options just before rendering the image. """ rect = rect.normalized().intersected(self.rect()) if not rect: return rect.translate(-self.pos()) if ydpi is None: ydpi = xdpi hscale = (xdpi * self.pageSize().width()) / (72.0 * self.width()) vscale = (ydpi * self.pageSize().height()) / (72.0 * self.height()) x = rect.x() * hscale y = rect.y() * vscale w = rect.width() * hscale h = rect.height() * vscale with lock(self.document()): options and options.write(self.document()) page = self.document().page(self._pageNumber) image = page.renderToImage(xdpi, ydpi, x, y, w, h, self._rotation) image.setDotsPerMeterX(int(xdpi * 39.37)) image.setDotsPerMeterY(int(ydpi * 39.37)) return image def linksAt(self, point): """Returns a list() of zero or more links touched by point (relative to surface). The list is sorted with the smallest rectangle first. """ # Poppler.Link objects have their linkArea() ranging in width and height # from 0.0 to 1.0, so divide by resp. height and width of the Page. point = point - self.pos() x = float(point.x()) / self.width() y = float(point.y()) / self.height() # rotate if self._rotation: if self._rotation == popplerqt4.Poppler.Page.Rotate90: x, y = y, 1 - x elif self._rotation == popplerqt4.Poppler.Page.Rotate180: x, y = 1 - x, 1 - y else: # 270 x, y = 1 - y, x return list( sorted(cache.links(self).at(x, y), key=lambda link: link.linkArea().width())) def linksIn(self, rect): """Returns an unordered set() of links enclosed in rectangle (relative to surface).""" rect = rect.normalized() rect.translate(-self.pos()) left = float(rect.left()) / self.width() top = float(rect.top()) / self.height() right = float(rect.right()) / self.width() bottom = float(rect.bottom()) / self.height() # rotate if self._rotation: if self._rotation == popplerqt4.Poppler.Page.Rotate90: left, top, right, bottom = top, 1 - right, bottom, 1 - left elif self._rotation == popplerqt4.Poppler.Page.Rotate180: left, top, right, bottom = 1 - right, 1 - bottom, 1 - left, 1 - top else: # 270 left, top, right, bottom = 1 - bottom, left, 1 - top, right return cache.links(self).inside(left, top, right, bottom) def linkRect(self, linkarea): """Returns a QRect encompassing the linkArea (of a link) in coordinates of our rect().""" left, top, right, bottom = linkarea.normalized().getCoords() # rotate if self._rotation: if self._rotation == popplerqt4.Poppler.Page.Rotate90: left, top, right, bottom = 1 - bottom, left, 1 - top, right elif self._rotation == popplerqt4.Poppler.Page.Rotate180: left, top, right, bottom = 1 - right, 1 - bottom, 1 - left, 1 - top else: # 270 left, top, right, bottom = top, 1 - right, bottom, 1 - left rect = QRect() rect.setCoords(left * self.width(), top * self.height(), right * self.width(), bottom * self.height()) rect.translate(self.pos()) return rect def text(self, rect): """Returns text inside rectangle (relative to surface).""" rect = rect.normalized() rect.translate(-self.pos()) w, h = self.pageSize().width(), self.pageSize().height() left = float(rect.left()) / self.width() * w top = float(rect.top()) / self.height() * h right = float(rect.right()) / self.width() * w bottom = float(rect.bottom()) / self.height() * h if self._rotation: if self._rotation == popplerqt4.Poppler.Page.Rotate90: left, top, right, bottom = top, w - right, bottom, w - left elif self._rotation == popplerqt4.Poppler.Page.Rotate180: left, top, right, bottom = w - right, h - bottom, w - left, h - top else: # 270 left, top, right, bottom = h - bottom, left, h - top, right rect = QRectF() rect.setCoords(left, top, right, bottom) with lock(self.document()): page = self.document().page(self._pageNumber) return page.text(rect) def searchRect(self, rectF): """Returns a QRect encompassing the given rect (in points) to our position, size and rotation.""" rect = rectF.normalized() left, top, right, bottom = rect.getCoords() w, h = self.pageSize().width(), self.pageSize().height() hscale = self.width() / float(w) vscale = self.height() / float(h) if self._rotation: if self._rotation == popplerqt4.Poppler.Page.Rotate90: left, top, right, bottom = w - bottom, left, w - top, right elif self._rotation == popplerqt4.Poppler.Page.Rotate180: left, top, right, bottom = w - right, h - bottom, w - left, h - top else: # 270 left, top, right, bottom = top, h - right, bottom, h - left rect = QRect() rect.setCoords(left * hscale, top * vscale, right * hscale, bottom * vscale) return rect
class MapToolInteractive(QgsMapTool): """Tool to interact with map, including panning, zooming, and snapping""" def __init__(self, canvas, snappingEnabled=False): super(MapToolInteractive, self).__init__(canvas) self._active = False self._dragging = False self._panningEnabled = False self._zoomingEnabled = False self._zoomRubberBand = None # QgsRubberBand() self._zoomRect = None # QRect() self._snappingEnabled = snappingEnabled self._snapper = None # QgsMapCanvasSnapper() self._snappingMarker = None # QgsVertexMarker() def __del__(self): if self._active: self.deactivate() def isActive(self): return self._active def activate(self): super(MapToolInteractive, self).activate() self._active = True self._startSnapping() def deactivate(self): self._active = False if self._snappingEnabled: self._stopSnapping() if (self._zoomRubberBand is not None): self.canvas().scene().removeItem(self._zoomRubberBand) self._zoomRubberBand = None super(MapToolInteractive, self).deactivate() def setAction(self, action): super(MapToolInteractive, self).setAction(action) self.action().triggered.connect(self._activate) def _activate(self): self.canvas().setMapTool(self) def panningEnabled(self): return self._panningEnabled def setPanningEnabled(self, enabled): self._panningEnabled = enabled def zoomingEnabled(self): return self._zoomingEnabled def setZoomingEnabled(self, enabled): self._zoomingEnabled = enabled def snappingEnabled(self): return self._snappingEnabled def setSnappingEnabled(self, enabled): if (self._snappingEnabled == enabled): return self._snappingEnabled = enabled if not self._active: return if enabled: self._startSnapping() else: self._stopSnapping() def _startSnapping(self): self._snapper = QgsMapCanvasSnapper() self._snapper.setMapCanvas(self.canvas()) def _stopSnapping(self): self._deleteSnappingMarker() self._snapper = None def canvasMoveEvent(self, e): super(MapToolInteractive, self).canvasMoveEvent(e) if not self._active: return e.ignore() if (self._panningEnabled and e.buttons() & Qt.LeftButton): # Pan map mode if not self._dragging: self._dragging = True self.setCursor(QCursor(Qt.ClosedHandCursor)) self.canvas().panAction(e) e.accept() elif (self._zoomingEnabled and e.buttons() & Qt.RightButton): # Zoom map mode if not self._dragging: self._dragging = True self.setCursor(QCursor(Qt.ClosedHandCursor)) self._zoomRubberBand = QgsRubberBand(self.canvas(), QGis.Polygon) color = QColor(Qt.blue) color.setAlpha(63) self._zoomRubberBand.setColor(color) self._zoomRect = QRect(0, 0, 0, 0) self._zoomRect.setTopLeft(e.pos()) self._zoomRect.setBottomRight(e.pos()) if self._zoomRubberBand is not None: self._zoomRubberBand.setToCanvasRectangle(self._zoomRect) self._zoomRubberBand.show() e.accept() elif self._snappingEnabled: mapPoint, mapPointV2, snapped = self._snapCursorPoint(e.pos()) if (snapped): self._createSnappingMarker(mapPoint) else: self._deleteSnappingMarker() def canvasReleaseEvent(self, e): super(MapToolInteractive, self).canvasReleaseEvent(e) e.ignore() if (e.button() == Qt.LeftButton): if self._dragging: # Pan map mode self.canvas().panActionEnd(e.pos()) self.setCursor(CapturePointCursor) self._dragging = False e.accept() elif (e.button() == Qt.RightButton): if self._dragging: # Zoom mode self._zoomRect.setBottomRight(e.pos()) if (self._zoomRect.topLeft() != self._zoomRect.bottomRight()): coordinateTransform = self.canvas().getCoordinateTransform( ) ll = coordinateTransform.toMapCoordinates( self._zoomRect.left(), self._zoomRect.bottom()) ur = coordinateTransform.toMapCoordinates( self._zoomRect.right(), self._zoomRect.top()) r = QgsRectangle() r.setXMinimum(ll.x()) r.setYMinimum(ll.y()) r.setXMaximum(ur.x()) r.setYMaximum(ur.y()) r.normalize() if (r.width() != 0 and r.height() != 0): self.canvas().setExtent(r) self.canvas().refresh() self._dragging = False if (self._zoomRubberBand is not None): self.canvas().scene().removeItem(self._zoomRubberBand) self._zoomRubberBand = None e.accept() def keyPressEvent(self, e): super(MapToolInteractive, self).keyPressEvent(e) if (e.key() == Qt.Key_Escape): self.canvas().unsetMapTool(self) e.accept() def _snapCursorPoint(self, cursorPoint): res, snapResults = self._snapper.snapToBackgroundLayers(cursorPoint) if (res != 0 or len(snapResults) < 1): clicked = self.toMapCoordinates(cursorPoint) clickedV2 = QgsPointV2(clicked) return clicked, clickedV2, False else: # Take a copy as QGIS will delete the result! snapped = QgsPoint(snapResults[0].snappedVertex) snappedV2 = QgsPointV2(snapped) return snapped, snappedV2, True def _createSnappingMarker(self, snapPoint): if (self._snappingMarker is None): self._snappingMarker = QgsVertexMarker(self.canvas()) self._snappingMarker.setIconType(QgsVertexMarker.ICON_CROSS) self._snappingMarker.setColor(Qt.magenta) self._snappingMarker.setPenWidth(3) self._snappingMarker.setCenter(snapPoint) def _deleteSnappingMarker(self): if (self._snappingMarker is not None): self.canvas().scene().removeItem(self._snappingMarker) self._snappingMarker = None