Example #1
0
 def _loadRecentFileActions(self):
     for i in range(self.MaxRecentFiles):
         action = QAction(self)
         action.setVisible(False)
         self.connect(action, SIGNAL("triggered()"), self.openRecent)
         self.recentFileActions.append(action)
     self._updateRecentFileActions()
 def __init__(self):
     QMainWindow.__init__(self)
     self.ui = Ui_MainWindow()
     self.ui.setupUi(self)
     self.loadTranslations()
     self.settings = QSettings("LVK Inc", "DataCenters")
     self.resourcesGraphEditor = ResourcesGraphEditor()
     self.demandGraphEditor = DemandGraphEditor()
     self.randomDialog = RandomDemandDialog()
     self.Vis = Vis()
     self.graphvis = GraphVis(self)
     self.demands = {}
     self.project = Project()
     # TODO: Captain, we have a problem!
     # For some reason, in Python 2.7 QSettings converts dicts to QVariant
     # So the ini file is undecypherable
     # This works fine in Python 3.2 by the way
     #if self.settings.value("vis"):
     #self.Vis.canvas.settings = self.settings.value("vis")
     #self.graphvis.settings = self.settings.value("graphVis")
     self.settingsDialog = SettingsDialog(self.Vis.canvas.settings,
                                          self.graphvis.settings)
     self.settingsDialog.ui.backup.setChecked(
         self.settings.value("backup").toBool())
     self.settingsDialog.ui.autosave.setChecked(
         self.settings.value("autosave").toBool())
     self.settingsDialog.ui.interval.setValue(
         self.settings.value("interval").toInt()[0])
     i = 0
     for s in self.languages:
         self.settingsDialog.ui.languages.addItem(s)
         if s == str(self.settings.value("language").toString()):
             self.settingsDialog.ui.languages.setCurrentIndex(i)
         i += 1
     self.resourcesGraphEditor.setData(self.project.resources)
     for i in range(self.MaxRecentFiles):
         a = QAction(self)
         a.setVisible(False)
         a.setEnabled(False)
         if i <= 9:
             a.setShortcut(QKeySequence(self.tr("Alt+") + str(i + 1)))
         QObject.connect(a, SIGNAL("triggered()"), self.OpenRecentFile)
         self.ui.menuFile.insertAction(self.ui.actionExit, a)
         self.recentFileActions.append(a)
     self.UpdateRecentFileActions()
     self.basename = self.windowTitle()
     self.demandGraphEditor.demand_changed.connect(self.demandChanged)
     self.backupTimer = QTimer()
     self.backupTimer.setInterval(60000)
     self.backupTimer.setSingleShot(False)
     QObject.connect(self.backupTimer, SIGNAL("timeout()"), self.Backup)
     self.autosaveTimer = QTimer()
     self.autosaveTimer.setInterval(60000)
     self.autosaveTimer.setSingleShot(False)
     QObject.connect(self.autosaveTimer, SIGNAL("timeout()"), self.Autosave)
     self.Translate(
         str(self.settings.value("language", "English").toString()))
     self.projFilter = self.tr("Data centers projects (*.dcxml)")
     self.setWindowTitle(self.tr("Untitled") + " - " + self.basename)
     self.loadPlugins()
Example #3
0
    def loadpages(self, pages):
        def safe_connect(method, to):
            try:
                method.connect(to)
            except AttributeError:
                pass

        for PageClass in pages:
            action = QAction(self.menutoolbar)
            text = PageClass.title.ljust(13)
            action.setIconText(text)
            action.setIcon(QIcon(PageClass.icon))
            action.setCheckable(True)
            if PageClass.projectpage:
                action.setVisible(False)
                self.projectbuttons.append(action)
                self.menutoolbar.insertAction(self.spaceraction, action)
            else:
                self.menutoolbar.insertAction(self.actionProject, action)

            iface = RoamInterface(RoamEvents, GPS, self)
            pagewidget = PageClass(iface, self)

            safe_connect(RoamEvents.selectionchanged, pagewidget.selection_changed)
            safe_connect(RoamEvents.projectloaded, pagewidget.project_loaded)

            pageindex = self.stackedWidget.insertWidget(-1, pagewidget)
            action.setProperty('page', pageindex)
            self.pluginactions.append(action)
            self.menuGroup.addAction(action)
    def loadpages(self, pages):
        def safe_connect(method, to):
            try:
                method.connect(to)
            except AttributeError:
                pass

        for PageClass in pages:
            action = QAction(self.menutoolbar)
            text = PageClass.title.ljust(13)
            action.setIconText(text)
            action.setIcon(QIcon(PageClass.icon))
            action.setCheckable(True)
            if PageClass.projectpage:
                action.setVisible(False)
                self.projectbuttons.append(action)
                self.menutoolbar.insertAction(self.spaceraction, action)
            else:
                self.menutoolbar.insertAction(self.actionProject, action)

            iface = RoamInterface(RoamEvents, GPS, self)
            pagewidget = PageClass(iface, self)

            safe_connect(RoamEvents.selectionchanged, pagewidget.selection_changed)
            safe_connect(RoamEvents.projectloaded, pagewidget.project_loaded)

            pageindex = self.stackedWidget.insertWidget(-1, pagewidget)
            action.setProperty('page', pageindex)
            self.pluginactions.append(action)
            self.menuGroup.addAction(action)
Example #5
0
class Tool(object):
    def __init__(self,
                 name,
                 help_link="",
                 icon=None,
                 enabled=True,
                 checkable=False,
                 popup_menu=False):
        super(Tool, self).__init__()
        self.__icon = icon
        self.__name = name
        self.__parent = None
        self.__enabled = enabled
        self.__checkable = checkable
        self.__help_link = help_link
        self.__is_popup_menu = popup_menu

        self.__action = QAction(self.getIcon(), self.getName(), None)
        self.__action.setIconText(self.getName())
        self.__action.setEnabled(self.isEnabled())
        self.__action.setCheckable(checkable)
        self.__action.triggered.connect(self.trigger)

        HelpCenter.addHelpToAction(self.__action, self.getHelpLink())

    def getIcon(self):
        return self.__icon

    def getName(self):
        return self.__name

    def trigger(self):
        raise NotImplementedError()

    def setParent(self, parent):
        self.__parent = parent
        self.__action.setParent(parent)

    def parent(self):
        return self.__parent

    def isEnabled(self):
        return self.__enabled

    def getHelpLink(self):
        return self.__help_link

    def getAction(self):
        return self.__action

    def setVisible(self, visible):
        self.__action.setVisible(visible)

    def setEnabled(self, enabled):
        self.__action.setEnabled(enabled)

    def isPopupMenu(self):
        return self.__is_popup_menu
 def __init__(self):
     QMainWindow.__init__(self)
     self.ui = Ui_MainWindow()
     self.ui.setupUi(self)
     self.loadTranslations()
     self.settings = QSettings("LVK Inc", "DataCenters")   
     self.resourcesGraphEditor = ResourcesGraphEditor()
     self.demandGraphEditor = DemandGraphEditor()
     self.randomDialog = RandomDemandDialog()
     self.Vis = Vis()
     self.graphvis = GraphVis(self)
     self.demands = {}
     self.project = Project()
     # TODO: Captain, we have a problem!
     # For some reason, in Python 2.7 QSettings converts dicts to QVariant
     # So the ini file is undecypherable
     # This works fine in Python 3.2 by the way
     #if self.settings.value("vis"):
         #self.Vis.canvas.settings = self.settings.value("vis")
     #self.graphvis.settings = self.settings.value("graphVis")
     self.settingsDialog = SettingsDialog(self.Vis.canvas.settings, self.graphvis.settings)
     self.settingsDialog.ui.backup.setChecked(self.settings.value("backup").toBool())
     self.settingsDialog.ui.autosave.setChecked(self.settings.value("autosave").toBool())
     self.settingsDialog.ui.interval.setValue(self.settings.value("interval").toInt()[0])
     i = 0
     for s in self.languages:
         self.settingsDialog.ui.languages.addItem(s)
         if s == str(self.settings.value("language").toString()):
             self.settingsDialog.ui.languages.setCurrentIndex(i)
         i += 1
     self.resourcesGraphEditor.setData(self.project.resources)
     for i in range(self.MaxRecentFiles):
         a = QAction(self)
         a.setVisible(False)
         a.setEnabled(False)
         if i <= 9:
             a.setShortcut(QKeySequence(self.tr("Alt+") + str(i + 1)))
         QObject.connect(a, SIGNAL("triggered()"), self.OpenRecentFile);
         self.ui.menuFile.insertAction(self.ui.actionExit, a)
         self.recentFileActions.append(a)
     self.UpdateRecentFileActions()
     self.basename = self.windowTitle()
     self.demandGraphEditor.demand_changed.connect(self.demandChanged)
     self.backupTimer = QTimer()
     self.backupTimer.setInterval(60000)
     self.backupTimer.setSingleShot(False)
     QObject.connect(self.backupTimer, SIGNAL("timeout()"), self.Backup)
     self.autosaveTimer = QTimer()
     self.autosaveTimer.setInterval(60000)
     self.autosaveTimer.setSingleShot(False)
     QObject.connect(self.autosaveTimer, SIGNAL("timeout()"), self.Autosave)
     self.Translate(str(self.settings.value("language", "English").toString()))
     self.projFilter = self.tr("Data centers projects (*.dcxml)")
     self.setWindowTitle(self.tr("Untitled") + " - " + self.basename)
     self.loadPlugins()
Example #7
0
 def create_recentFileActs(self):
     """
     Create Actions to open recent files
     """
     self.recentFileActs = deque()
     for i in range(MAX_RECENT_FILES):
         a = QAction(self)
         a.setVisible(False)
         QtCore.QObject.connect(a, QtCore.SIGNAL(_fromUtf8("triggered()")), self.on_open_menu_file)
         self.menuRecent.addAction(a)
         self.recentFileActs.append(a)
Example #8
0
class Tool(object):
    def __init__(self, name, help_link="", icon=None, enabled=True, checkable=False, popup_menu=False):
        super(Tool, self).__init__()
        self.__icon = icon
        self.__name = name
        self.__parent = None
        self.__enabled = enabled
        self.__checkable = checkable
        self.__help_link = help_link
        self.__is_popup_menu = popup_menu

        self.__action = QAction(self.getIcon(), self.getName(), None)
        self.__action.setIconText(self.getName())
        self.__action.setEnabled(self.isEnabled())
        self.__action.setCheckable(checkable)
        self.__action.triggered.connect(self.trigger)

        HelpCenter.addHelpToAction(self.__action, self.getHelpLink())

    def getIcon(self):
        return self.__icon

    def getName(self):
        return self.__name

    def trigger(self):
        raise NotImplementedError()

    def setParent(self, parent):
        self.__parent = parent
        self.__action.setParent(parent)

    def parent(self):
        return self.__parent

    def isEnabled(self):
        return self.__enabled

    def getHelpLink(self):
        return self.__help_link


    def getAction(self):
        return self.__action

    def setVisible(self, visible):
        self.__action.setVisible(visible)

    def setEnabled(self, enabled):
        self.__action.setEnabled(enabled)

    def isPopupMenu(self):
        return self.__is_popup_menu
    def __createLayout( self ):
        " Creates the toolbar and layout "

        # Buttons
        printButton = QAction( PixmapCache().getIcon( 'printer.png' ),
                               'Print', self )
        printButton.triggered.connect( self.__onPrint )
        printButton.setEnabled( False )
        printButton.setVisible( False )

        printPreviewButton = QAction(
                PixmapCache().getIcon( 'printpreview.png' ),
                'Print preview', self )
        printPreviewButton.triggered.connect( self.__onPrintPreview )
        printPreviewButton.setEnabled( False )
        printPreviewButton.setVisible( False )

        spacer = QWidget()
        spacer.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding )

        self.lineCounterButton = QAction(
            PixmapCache().getIcon( 'linecounter.png' ),
            'Line counter', self )
        self.lineCounterButton.triggered.connect( self.onLineCounter )

        # The toolbar
        toolbar = QToolBar( self )
        toolbar.setOrientation( Qt.Vertical )
        toolbar.setMovable( False )
        toolbar.setAllowedAreas( Qt.RightToolBarArea )
        toolbar.setIconSize( QSize( 16, 16 ) )
        toolbar.setFixedWidth( 28 )
        toolbar.setContentsMargins( 0, 0, 0, 0 )

        toolbar.addAction( printPreviewButton )
        toolbar.addAction( printButton )
        toolbar.addWidget( spacer )
        toolbar.addAction( self.lineCounterButton )

        self.__importsBar = ImportListWidget( self.__viewer )
        self.__importsBar.hide()

        hLayout = QHBoxLayout()
        hLayout.setContentsMargins( 0, 0, 0, 0 )
        hLayout.setSpacing( 0 )
        hLayout.addWidget( self.__viewer )
        hLayout.addWidget( toolbar )

        self.setLayout( hLayout )
        return
Example #10
0
 def _NH_SIPAccountManagerDidAddAccount(self, notification):
     account = notification.data.account
     action = QAction(account.id if account is not BonjourAccount() else u'Bonjour', None)
     action.setEnabled(True if account is not BonjourAccount() else BonjourAccount.mdns_available)
     action.setCheckable(True)
     action.setChecked(account.enabled)
     action.setData(account)
     action.triggered.connect(partial(self._AH_AccountActionTriggered, action))
     self.accounts_menu.addAction(action)
     action = QAction(self.mwi_icons[0], account.id, None)
     action.setVisible(False if account is BonjourAccount() else account.enabled and account.message_summary.enabled)
     action.setEnabled(False if account is BonjourAccount() else account.voicemail_uri is not None)
     action.setData(account)
     action.triggered.connect(partial(self._AH_VoicemailActionTriggered, action))
     self.voicemail_menu.addAction(action)
Example #11
0
 def _NH_SIPAccountManagerDidAddAccount(self, notification):
     account = notification.data.account
     action = QAction(account.id if account is not BonjourAccount() else u'Bonjour', None)
     action.setEnabled(True if account is not BonjourAccount() else BonjourAccount.mdns_available)
     action.setCheckable(True)
     action.setChecked(account.enabled)
     action.setData(account)
     action.triggered.connect(partial(self._AH_AccountActionTriggered, action))
     self.accounts_menu.addAction(action)
     action = QAction(self.mwi_icons[0], account.id, None)
     action.setVisible(False if account is BonjourAccount() else account.enabled and account.message_summary.enabled)
     action.setEnabled(False if account is BonjourAccount() else account.voicemail_uri is not None)
     action.setData(account)
     action.triggered.connect(partial(self._AH_VoicemailActionTriggered, action))
     self.voicemail_menu.addAction(action)
Example #12
0
    def __createLayout(self):
        " Creates the toolbar and layout "

        # Buttons
        printButton = QAction(PixmapCache().getIcon('printer.png'), 'Print',
                              self)
        printButton.triggered.connect(self.__onPrint)
        printButton.setEnabled(False)
        printButton.setVisible(False)

        printPreviewButton = QAction(PixmapCache().getIcon('printpreview.png'),
                                     'Print preview', self)
        printPreviewButton.triggered.connect(self.__onPrintPreview)
        printPreviewButton.setEnabled(False)
        printPreviewButton.setVisible(False)

        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.lineCounterButton = QAction(
            PixmapCache().getIcon('linecounter.png'), 'Line counter', self)
        self.lineCounterButton.triggered.connect(self.onLineCounter)

        # The toolbar
        toolbar = QToolBar(self)
        toolbar.setOrientation(Qt.Vertical)
        toolbar.setMovable(False)
        toolbar.setAllowedAreas(Qt.RightToolBarArea)
        toolbar.setIconSize(QSize(16, 16))
        toolbar.setFixedWidth(28)
        toolbar.setContentsMargins(0, 0, 0, 0)

        toolbar.addAction(printPreviewButton)
        toolbar.addAction(printButton)
        toolbar.addWidget(spacer)
        toolbar.addAction(self.lineCounterButton)

        self.__importsBar = ImportListWidget(self.__viewer)
        self.__importsBar.hide()

        hLayout = QHBoxLayout()
        hLayout.setContentsMargins(0, 0, 0, 0)
        hLayout.setSpacing(0)
        hLayout.addWidget(self.__viewer)
        hLayout.addWidget(toolbar)

        self.setLayout(hLayout)
        return
Example #13
0
class MapWidget(Ui_CanvasWidget, QMainWindow):
    def __init__(self, parent=None):
        super(MapWidget, self).__init__(parent)
        self.setupUi(self)

        self.firstshow = True
        self.layerbuttons = []
        self.editfeaturestack = []
        self.lastgpsposition = None
        self.project = None
        self.gps = None
        self.gpslogging = None
        self.selectionbands = defaultdict(partial(QgsRubberBand, self.canvas))

        self.canvas.setCanvasColor(Qt.white)
        self.canvas.enableAntiAliasing(True)
        self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor)

        if hasattr(self.canvas, 'setParallelRenderingEnabled'):
            self.canvas.setParallelRenderingEnabled(True)

        pal = QgsPalLabeling()
        self.canvas.mapRenderer().setLabelingEngine(pal)
        self.canvas.setFrameStyle(QFrame.NoFrame)

        self.editgroup = QActionGroup(self)
        self.editgroup.setExclusive(True)
        self.editgroup.addAction(self.actionPan)
        self.editgroup.addAction(self.actionZoom_In)
        self.editgroup.addAction(self.actionZoom_Out)
        self.editgroup.addAction(self.actionInfo)

        self.actionGPS = GPSAction(":/icons/gps", self.canvas, self)
        self.projecttoolbar.addAction(self.actionGPS)

        gpsspacewidget= QWidget()
        gpsspacewidget.setMinimumWidth(30)
        gpsspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.topspaceraction = self.projecttoolbar.insertWidget(self.actionGPS, gpsspacewidget)

        self.dataentryselection = QAction(self.projecttoolbar)
        self.dataentryaction = self.projecttoolbar.insertAction(self.topspaceraction, self.dataentryselection)
        self.dataentryselection.triggered.connect(self.select_data_entry)

        self.marker = GPSMarker(self.canvas)
        self.marker.hide()

        self.currentfeatureband = QgsRubberBand(self.canvas)
        self.currentfeatureband.setIconSize(20)
        self.currentfeatureband.setWidth(10)
        self.currentfeatureband.setColor(QColor(186, 93, 212, 76))

        self.gpsband = QgsRubberBand(self.canvas)
        self.gpsband.setColor(QColor(0, 0, 212, 76))
        self.gpsband.setWidth(5)

        RoamEvents.editgeometry.connect(self.queue_feature_for_edit)
        RoamEvents.selectioncleared.connect(self.clear_selection)
        RoamEvents.selectionchanged.connect(self.highlight_selection)
        RoamEvents.featureformloaded.connect(self.feature_form_loaded)

        self.connectButtons()

    def init_qgisproject(self, doc):
        parser = ProjectParser(doc)
        canvasnode = parser.canvasnode
        self.canvas.freeze()
        self.canvas.mapRenderer().readXML(canvasnode)
        self.canvaslayers = parser.canvaslayers()
        self.canvas.setLayerSet(self.canvaslayers)
        #red = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorRedPart", 255 )[0];
        #green = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorGreenPart", 255 )[0];
        #blue = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorBluePart", 255 )[0];
        #color = QColor(red, green, blue);
        #self.canvas.setCanvasColor(color)
        self.canvas.updateScale()
        return self.canvas.mapRenderer().destinationCrs()

    def showEvent(self, *args, **kwargs):
        if self.firstshow:
            self.canvas.refresh()
            self.canvas.repaint()
            self.firstshow = False

    def feature_form_loaded(self, form, feature, project, editmode):
        self.currentfeatureband.setToGeometry(feature.geometry(), form.QGISLayer)

    def highlight_selection(self, results):
        self.clear_selection()
        for layer, features in results.iteritems():
            band = self.selectionbands[layer]
            band.setColor(QColor(255, 0, 0, 200))
            band.setIconSize(20)
            band.setWidth(2)
            band.setBrushStyle(Qt.NoBrush)
            band.reset(layer.geometryType())
            for feature in features:
                band.addGeometry(feature.geometry(), layer)

    def highlight_active_selection(self, layer, feature, features):
        self.clear_selection()
        self.highlight_selection({layer: features})
        self.currentfeatureband.setToGeometry(feature.geometry(), layer)

    def clear_selection(self):
        # Clear the main selection rubber band
        self.currentfeatureband.reset()
        # Clear the rest
        for band in self.selectionbands.itervalues():
            band.reset()

        self.editfeaturestack = []

    def queue_feature_for_edit(self, form, feature):
        def trigger_default_action():
            for action in self.projecttoolbar.actions():
                if action.property('dataentry') and action.isdefault:
                    action.trigger()
                    break

        self.editfeaturestack.append((form, feature))
        self.load_form(form)
        trigger_default_action()

    def clear_temp_objects(self):
        def clear_tool_band():
            """
            Clear the rubber band of the active tool if it has one
            """
            tool = self.canvas.mapTool()
            try:
                tool.clearBand()
            except AttributeError:
                # No clearBand method found, but that's cool.
                pass

        self.currentfeatureband.reset()
        clear_tool_band()


    def settings_updated(self, settings):
        self.actionGPS.updateGPSPort()
        gpslogging = settings.get('gpslogging', True)
        if self.gpslogging:
            self.gpslogging.logging = gpslogging

    def set_gps(self, gps, logging):
        self.gps = gps
        self.gpslogging = logging
        self.gps.gpsposition.connect(self.gps_update_canvas)
        self.gps.firstfix.connect(self.gps_first_fix)
        self.gps.gpsdisconnected.connect(self.gps_disconnected)

    def gps_update_canvas(self, position, gpsinfo):
        # Recenter map if we go outside of the 95% of the area
        if self.gpslogging.logging:
            self.gpsband.addPoint(position)
            self.gpsband.show()

        if roam.config.settings.get('gpscenter', True):
            if not self.lastgpsposition == position:
                self.lastposition = position
                rect = QgsRectangle(position, position)
                extentlimt = QgsRectangle(self.canvas.extent())
                extentlimt.scale(0.95)

                if not extentlimt.contains(position):
                    self.zoom_to_location(position)

        self.marker.show()
        self.marker.setCenter(position)

    def gps_first_fix(self, postion, gpsinfo):
        zoomtolocation = roam.config.settings.get('gpszoomonfix', True)
        if zoomtolocation:
            self.canvas.zoomScale(1000)
            self.zoom_to_location(postion)

    def zoom_to_location(self, position):
        rect = QgsRectangle(position, position)
        self.canvas.setExtent(rect)
        self.canvas.refresh()

    def gps_disconnected(self):
        self.marker.hide()

    def select_data_entry(self):
        def showformerror(form):
            pass

        def actions():
            for form in self.project.forms:
                action = form.createuiaction()
                valid, failreasons = form.valid
                if not valid:
                    roam.utils.warning("Form {} failed to load".format(form.label))
                    roam.utils.warning("Reasons {}".format(failreasons))
                    action.triggered.connect(partial(showformerror, form))
                else:
                    action.triggered.connect(partial(self.load_form, form))
                yield action

        formpicker = PickActionDialog(msg="Select data entry form")
        formpicker.addactions(actions())
        formpicker.exec_()

    def project_loaded(self, project):
        self.project = project
        self.actionPan.trigger()
        try:
            firstform = project.forms[0]
            self.load_form(firstform)
            self.dataentryselection.setVisible(True)
        except IndexError:
            self.dataentryselection.setVisible(False)

        # Enable the raster layers button only if the project contains a raster layer.
        layers = QgsMapLayerRegistry.instance().mapLayers().values()
        hasrasters = any(layer.type() == QgsMapLayer.RasterLayer for layer in layers)
        self.actionRaster.setEnabled(hasrasters)
        self.defaultextent = self.canvas.extent()
        roam.utils.info("Extent: {}".format(self.defaultextent.toString()))

        self.infoTool.selectionlayers = project.selectlayersmapping()

        self.canvas.freeze(False)
        self.canvas.refresh()

    def setMapTool(self, tool, *args):
        self.canvas.setMapTool(tool)

    def connectButtons(self):
        def connectAction(action, tool):
            action.toggled.connect(partial(self.setMapTool, tool))

        def cursor(name):
            pix = QPixmap(name)
            pix = pix.scaled(QSize(24,24))
            return QCursor(pix)

        self.zoomInTool = QgsMapToolZoom(self.canvas, False)
        self.zoomOutTool = QgsMapToolZoom(self.canvas, True)
        self.panTool = PanTool(self.canvas)
        self.infoTool = InfoTool(self.canvas)

        connectAction(self.actionZoom_In, self.zoomInTool)
        connectAction(self.actionZoom_Out, self.zoomOutTool)
        connectAction(self.actionPan, self.panTool)
        connectAction(self.actionInfo, self.infoTool)

        self.zoomInTool.setCursor(cursor(':/icons/in'))
        self.zoomOutTool.setCursor(cursor(':/icons/out'))
        self.infoTool.setCursor(cursor(':/icons/info'))

        self.actionRaster.triggered.connect(self.toggleRasterLayers)

        self.infoTool.infoResults.connect(RoamEvents.selectionchanged.emit)

        self.actionHome.triggered.connect(self.homeview)

    def homeview(self):
        """
        Zoom the mapview canvas to the extents the project was opened at i.e. the
        default extent.
        """
        self.canvas.setExtent(self.defaultextent)
        self.canvas.refresh()

    def load_form(self, form):
        self.clearCapatureTools()
        self.dataentryselection.setIcon(QIcon(form.icon))
        self.dataentryselection.setText(form.icontext)
        self.create_capture_buttons(form)

    def create_capture_buttons(self, form):
        tool = form.getMaptool()(self.canvas)
        for action in tool.actions:
            # Create the action here.
            if action.ismaptool:
                action.toggled.connect(partial(self.setMapTool, tool))

            # Set the action as a data entry button so we can remove it later.
            action.setProperty("dataentry", True)
            self.editgroup.addAction(action)
            self.layerbuttons.append(action)
            self.projecttoolbar.insertAction(self.topspaceraction, action)
            action.setChecked(action.isdefault)

        if hasattr(tool, 'geometryComplete'):
            add = partial(self.add_new_feature, form)
            tool.geometryComplete.connect(add)
        else:
            tool.finished.connect(self.openForm)
            tool.error.connect(partial(self.showUIMessage, form.label))

    def add_new_feature(self, form, geometry):
        """
        Add a new new feature to the given layer
        """
        # TODO Extract into function.
        # NOTE This function is doing too much, acts as add and also edit.
        layer = form.QGISLayer
        if layer.geometryType() in [QGis.WKBMultiLineString, QGis.WKBMultiPoint, QGis.WKBMultiPolygon]:
            geometry.convertToMultiType()

        try:
            form, feature = self.editfeaturestack.pop()
            self.editfeaturegeometry(form, feature, newgeometry=geometry)
            return
        except IndexError:
            pass

        layer = form.QGISLayer
        fields = layer.pendingFields()

        feature = QgsFeature(fields)
        feature.setGeometry(geometry)

        for index in xrange(fields.count()):
            pkindexes = layer.dataProvider().pkAttributeIndexes()
            if index in pkindexes and layer.dataProvider().name() == 'spatialite':
                continue

            value = layer.dataProvider().defaultValue(index)
            feature[index] = value

        RoamEvents.open_feature_form(form, feature, editmode=False)

    def editfeaturegeometry(self, form, feature, newgeometry):
        # TODO Extract into function.
        layer = form.QGISLayer
        layer.startEditing()
        feature.setGeometry(newgeometry)
        layer.updateFeature(feature)
        saved = layer.commitChanges()
        if not saved:
            map(roam.utils.error, layer.commitErrors())
        self.canvas.refresh()
        self.currentfeatureband.setToGeometry(feature.geometry(), layer)
        RoamEvents.editgeometry_complete.emit(form, feature)

    def clearCapatureTools(self):
        captureselected = False
        for action in self.projecttoolbar.actions():
            if action.objectName() == "capture" and action.isChecked():
                captureselected = True

            if action.property('dataentry'):
                self.projecttoolbar.removeAction(action)
        return captureselected

    def toggleRasterLayers(self):
        """
        Toggle all raster layers on or off.
        """
        if not self.canvaslayers:
            return

        #Freeze the canvas to save on UI refresh
        self.canvas.freeze()
        for layer in self.canvaslayers:
            if layer.layer().type() == QgsMapLayer.RasterLayer:
                layer.setVisible(not layer.isVisible())
                # Really!? We have to reload the whole layer set every time?
            # WAT?
        self.canvas.setLayerSet(self.canvaslayers)
        self.canvas.freeze(False)
        self.canvas.refresh()

    def cleanup(self):
        self.gpsband.reset()
        self.gpsband.hide()
        self.clear_selection()
        self.clear_temp_objects()
        self.clearCapatureTools()
        self.canvas.freeze()
        self.canvas.clear()
        self.canvas.freeze(False)
        for action in self.layerbuttons:
            self.editgroup.removeAction(action)
class RecentFiles():
    """Keeps track of last opened files."""

    def __init__(self, parent = None):
        """Constructor"""
        self.parent = parent
        self.max_recent = 5
        self.recent_files = []
        self.recent_actions = []
        self.action_clear_history = QAction(self.parent)
        self.create()
        self.update()

    def create(self):
        """Creates menu actions."""
        for i in range(self.max_recent):
            action = QAction(self.parent)
            action.setIcon(QIcon(QPixmap(":/icons/action_rom.png")))
            self.recent_actions.append(action)
            self.recent_actions[i].setVisible(False)
            self.parent.connect(
                self.recent_actions[i], SIGNAL("triggered()"), self.parent.file_open)
            self.parent.menuRecent.addAction(self.recent_actions[i])
        self.parent.menuRecent.addSeparator()
        self.action_clear_history.setText("&Clear history")
        self.action_clear_history.setEnabled(False)
        self.action_clear_history.setVisible(True)
        self.action_clear_history.setIcon(
            QIcon(QPixmap(":/icons/action_clear.png")))
        self.parent.connect(
            self.action_clear_history, SIGNAL("triggered()"), self.clear)
        self.parent.menuRecent.addAction(self.action_clear_history)

    def update(self):
        """Updates list of recent files."""
        self.recent_files = self.parent.settings.qset.value("recent_files", [])
        num_files = min(len(self.recent_files), self.max_recent)
        for i in range(num_files):
            text = QFileInfo(self.recent_files[i]).fileName()
            self.recent_actions[i].setText(text)
            self.recent_actions[i].setData(self.recent_files[i])
            self.recent_actions[i].setVisible(True)
            self.recent_actions[i].setToolTip(QFileInfo(
                self.recent_files[i]).filePath())
        for j in range(num_files, self.max_recent):
            self.recent_actions[j].setVisible(False)
        self.action_clear_history.setEnabled((num_files > 0))

    def add(self, filepath):
        """Adds file to recent files list."""
        if filepath in self.recent_files:
            self.recent_files.remove(filepath)
        self.recent_files.insert(0, filepath)
        while len(self.recent_files) > 5:
            self.recent_files.pop(len(self.recent_files) - 1)
        self.parent.settings.qset.setValue(
            "recent_files", self.recent_files)
        self.update()

    def clear(self):
        """Clears list of recent files."""
        self.parent.settings.qset.remove("recent_files")
        self.update()
Example #15
0
class MainWindow(QMainWindow):
    def __init__(self, doc):
        QMainWindow.__init__(self, None)
        self.doc = doc
        self.app = doc.app
        
        self._setupUi()
        
        # Create base elements
        self.model = MainWindowModel(document=doc.model)
        self.model2view = {}
        self.apanel = AccountPanel(mainwindow=self)
        self.tpanel = TransactionPanel(mainwindow=self)
        self.mepanel = MassEditionPanel(mainwindow=self)
        self.scpanel = SchedulePanel(mainwindow=self)
        self.bpanel = BudgetPanel(mainwindow=self)
        self.cdrpanel = CustomDateRangePanel(mainwindow=self)
        self.arpanel = AccountReassignPanel(mainwindow=self)
        self.expanel = ExportPanel(mainwindow=self)
        self.alookup = Lookup(self, model=self.model.account_lookup)
        self.clookup = Lookup(self, model=self.model.completion_lookup)
        self.drsel = DateRangeSelector(mainwindow=self, view=self.dateRangeSelectorView)
        self.sfield = SearchField(model=self.model.search_field, view=self.searchLineEdit)
        self.recentDocuments = Recent(self.app, 'recentDocuments')
        self.recentDocuments.addMenu(self.menuOpenRecent)
        
        self.model.view = self
        self.model.connect()
        
        self._updateUndoActions()
        self._bindSignals()
    
    def _setupUi(self): # has to take place *before* base elements creation
        self.setWindowTitle("moneyGuru")
        self.resize(700, 580)
        self.centralwidget = QtGui.QWidget(self)
        self.verticalLayout = QtGui.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setSpacing(0)
        self.verticalLayout.setMargin(0)
        self.topBar = QtGui.QWidget(self.centralwidget)
        self.horizontalLayout_2 = QHBoxLayout(self.topBar)
        self.horizontalLayout_2.setContentsMargins(2, 0, 2, 0)
        spacerItem = QtGui.QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.horizontalLayout_2.addItem(spacerItem)
        self.dateRangeSelectorView = DateRangeSelectorView(self.topBar)
        self.dateRangeSelectorView.setMinimumSize(QSize(220, 0))
        self.horizontalLayout_2.addWidget(self.dateRangeSelectorView)
        spacerItem1 = QtGui.QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)
        self.horizontalLayout_2.addItem(spacerItem1)
        self.searchLineEdit = SearchEdit(self.topBar)
        self.searchLineEdit.setMaximumSize(QSize(240, 16777215))
        self.horizontalLayout_2.addWidget(self.searchLineEdit)
        self.verticalLayout.addWidget(self.topBar)
        self.tabBar = QTabBar(self.centralwidget)
        self.tabBar.setMinimumSize(QSize(0, 20))
        self.verticalLayout.addWidget(self.tabBar)
        self.mainView = QtGui.QStackedWidget(self.centralwidget)
        self.verticalLayout.addWidget(self.mainView)
        
        # Bottom buttons & status label
        self.bottomBar = QtGui.QWidget(self.centralwidget)
        self.horizontalLayout = QHBoxLayout(self.bottomBar)
        self.horizontalLayout.setMargin(2)
        self.horizontalLayout.setMargin(0)
        self.newItemButton = QPushButton(self.bottomBar)
        buttonSizePolicy = QSizePolicy(QSizePolicy.Maximum, QSizePolicy.Fixed)
        buttonSizePolicy.setHorizontalStretch(0)
        buttonSizePolicy.setVerticalStretch(0)
        buttonSizePolicy.setHeightForWidth(self.newItemButton.sizePolicy().hasHeightForWidth())
        self.newItemButton.setSizePolicy(buttonSizePolicy)
        self.newItemButton.setIcon(QIcon(QPixmap(':/plus_8')))
        self.horizontalLayout.addWidget(self.newItemButton)
        self.deleteItemButton = QPushButton(self.bottomBar)
        self.deleteItemButton.setSizePolicy(buttonSizePolicy)
        self.deleteItemButton.setIcon(QIcon(QPixmap(':/minus_8')))
        self.horizontalLayout.addWidget(self.deleteItemButton)
        self.editItemButton = QPushButton(self.bottomBar)
        self.editItemButton.setSizePolicy(buttonSizePolicy)
        self.editItemButton.setIcon(QIcon(QPixmap(':/info_gray_12')))
        self.horizontalLayout.addWidget(self.editItemButton)
        self.horizontalLayout.addItem(horizontalSpacer(size=20))
        self.graphVisibilityButton = QPushButton()
        self.graphVisibilityButton.setSizePolicy(buttonSizePolicy)
        self.graphVisibilityButton.setIcon(QIcon(QPixmap(':/graph_visibility_on_16')))
        self.horizontalLayout.addWidget(self.graphVisibilityButton)
        self.piechartVisibilityButton = QPushButton()
        self.piechartVisibilityButton.setSizePolicy(buttonSizePolicy)
        self.piechartVisibilityButton.setIcon(QIcon(QPixmap(':/piechart_visibility_on_16')))
        self.horizontalLayout.addWidget(self.piechartVisibilityButton)
        self.columnsVisibilityButton = QPushButton()
        self.columnsVisibilityButton.setSizePolicy(buttonSizePolicy)
        self.columnsVisibilityButton.setIcon(QIcon(QPixmap(':/columns_16')))
        self.horizontalLayout.addWidget(self.columnsVisibilityButton)
        
        self.statusLabel = QtGui.QLabel(tr("Status"))
        self.statusLabel.setAlignment(Qt.AlignCenter)
        self.horizontalLayout.addWidget(self.statusLabel)
        self.verticalLayout.addWidget(self.bottomBar)
        
        
        self.setCentralWidget(self.centralwidget)
        self.menubar = QMenuBar(self)
        self.menubar.setGeometry(QRect(0, 0, 700, 20))
        self.menuFile = QMenu(tr("File"))
        self.menuOpenRecent = QMenu(tr("Open Recent"))
        self.menuView = QMenu(tr("View"))
        self.menuDateRange = QMenu(tr("Date Range"))
        self.menuEdit = QMenu(tr("Edit"))
        self.menuHelp = QMenu(tr("Help"))
        self.setMenuBar(self.menubar)
        self.actionOpenDocument = QAction(tr("Open..."), self)
        self.actionOpenDocument.setShortcut("Ctrl+O")
        self.actionShowNetWorth = QAction(tr("Net Worth"), self)
        self.actionShowNetWorth.setShortcut("Ctrl+1")
        self.actionShowNetWorth.setIcon(QIcon(QPixmap(':/balance_sheet_48')))
        self.actionShowProfitLoss = QAction(escapeamp(tr("Profit & Loss")), self)
        self.actionShowProfitLoss.setShortcut("Ctrl+2")
        self.actionShowProfitLoss.setIcon(QIcon(QPixmap(':/income_statement_48')))
        self.actionShowTransactions = QAction(tr("Transactions"), self)
        self.actionShowTransactions.setShortcut("Ctrl+3")
        self.actionShowTransactions.setIcon(QIcon(QPixmap(':/transaction_table_48')))
        self.actionShowSelectedAccount = QAction(tr("Show Account"), self)
        self.actionShowSelectedAccount.setShortcut("Ctrl+]")
        self.actionNewItem = QAction(tr("New Item"), self)
        self.actionNewItem.setShortcut("Ctrl+N")
        self.actionDeleteItem = QAction(tr("Remove Selected"), self)
        self.actionEditItem = QAction(tr("Show Info"), self)
        self.actionEditItem.setShortcut("Ctrl+I")
        self.actionToggleGraph = QAction(tr("Toggle Graph"), self)
        self.actionToggleGraph.setShortcut("Ctrl+Alt+G")
        self.actionTogglePieChart = QAction(tr("Toggle Pie Chart"), self)
        self.actionTogglePieChart.setShortcut("Ctrl+Alt+P")
        self.actionMoveUp = QAction(tr("Move Up"), self)
        self.actionMoveUp.setShortcut("Ctrl++")
        self.actionMoveDown = QAction(tr("Move Down"), self)
        self.actionMoveDown.setShortcut("Ctrl+-")
        self.actionNavigateBack = QAction(tr("Go Back"), self)
        self.actionNavigateBack.setShortcut("Ctrl+[")
        self.actionNewAccountGroup = QAction(tr("New Account Group"), self)
        self.actionNewAccountGroup.setShortcut("Ctrl+Shift+N")
        self.actionShowNextView = QAction(tr("Next View"), self)
        self.actionShowNextView.setShortcut("Ctrl+Shift+]")
        self.actionShowPreviousView = QAction(tr("Previous View"), self)
        self.actionShowPreviousView.setShortcut("Ctrl+Shift+[")
        self.actionNewDocument = QAction(tr("New Document"), self)
        self.actionOpenExampleDocument = QAction(tr("Open Example Document"), self)
        self.actionOpenPluginFolder = QAction(tr("Open Plugin Folder"), self)
        self.actionImport = QAction(tr("Import..."), self)
        self.actionImport.setShortcut("Ctrl+Alt+I")
        self.actionExport = QAction(tr("Export..."), self)
        self.actionExport.setShortcut("Ctrl+Alt+E")
        self.actionSave = QAction(tr("Save"), self)
        self.actionSave.setShortcut("Ctrl+S")
        self.actionSaveAs = QAction(tr("Save As..."), self)
        self.actionSaveAs.setShortcut("Ctrl+Shift+S")
        self.actionAbout = QAction(tr("About moneyGuru"), self)
        self.actionToggleReconciliationMode = QAction(tr("Toggle Reconciliation Mode"), self)
        self.actionToggleReconciliationMode.setShortcut("Ctrl+Shift+R")
        self.actionToggleAccountExclusion = QAction(tr("Toggle Exclusion Status of Account"), self)
        self.actionToggleAccountExclusion.setShortcut("Ctrl+Shift+X")
        self.actionShowSchedules = QAction(tr("Schedules"), self)
        self.actionShowSchedules.setShortcut("Ctrl+4")
        self.actionShowSchedules.setIcon(QIcon(QPixmap(':/schedules_48')))
        self.actionShowBudgets = QAction(tr("Budgets"), self)
        self.actionShowBudgets.setShortcut("Ctrl+5")
        self.actionShowBudgets.setIcon(QIcon(QPixmap(':/budget_48')))
        self.actionReconcileSelected = QAction(tr("Reconcile Selection"), self)
        self.actionReconcileSelected.setShortcut("Ctrl+R")
        self.actionMakeScheduleFromSelected = QAction(tr("Make Schedule from Selected"), self)
        self.actionMakeScheduleFromSelected.setShortcut("Ctrl+M")
        self.actionShowPreferences = QAction(tr("Preferences..."), self)
        self.actionPrint = QAction(tr("Print..."), self)
        self.actionPrint.setShortcut("Ctrl+P")
        self.actionQuit = QAction(tr("Quit moneyGuru"), self)
        self.actionQuit.setShortcut("Ctrl+Q")
        self.actionUndo = QAction(tr("Undo"), self)
        self.actionUndo.setShortcut("Ctrl+Z")
        self.actionRedo = QAction(tr("Redo"), self)
        self.actionRedo.setShortcut("Ctrl+Y")
        self.actionShowHelp = QAction(tr("moneyGuru Help"), self)
        self.actionShowHelp.setShortcut("F1")
        self.actionCheckForUpdate = QAction(tr("Check for update"), self)
        self.actionOpenDebugLog = QAction(tr("Open Debug Log"), self)
        self.actionDuplicateTransaction = QAction(tr("Duplicate Transaction"), self)
        self.actionDuplicateTransaction.setShortcut("Ctrl+D")
        self.actionJumpToAccount = QAction(tr("Jump to Account..."), self)
        self.actionJumpToAccount.setShortcut("Ctrl+Shift+A")
        self.actionNewTab = QAction(tr("New Tab"), self)
        self.actionNewTab.setShortcut("Ctrl+T")
        self.actionCloseTab = QAction(tr("Close Tab"), self)
        self.actionCloseTab.setShortcut("Ctrl+W")
        
        self.menuFile.addAction(self.actionNewDocument)
        self.menuFile.addAction(self.actionNewTab)
        self.menuFile.addAction(self.actionOpenDocument)
        self.menuFile.addAction(self.menuOpenRecent.menuAction())
        self.menuFile.addAction(self.actionOpenExampleDocument)
        self.menuFile.addAction(self.actionOpenPluginFolder)
        self.menuFile.addAction(self.actionImport)
        self.menuFile.addSeparator()
        self.menuFile.addAction(self.actionCloseTab)
        self.menuFile.addAction(self.actionSave)
        self.menuFile.addAction(self.actionSaveAs)
        self.menuFile.addAction(self.actionExport)
        self.menuFile.addAction(self.actionPrint)
        self.menuFile.addAction(self.actionQuit)
        self.menuView.addAction(self.actionShowNetWorth)
        self.menuView.addAction(self.actionShowProfitLoss)
        self.menuView.addAction(self.actionShowTransactions)
        self.menuView.addAction(self.actionShowSchedules)
        self.menuView.addAction(self.actionShowBudgets)
        self.menuView.addAction(self.actionShowPreviousView)
        self.menuView.addAction(self.actionShowNextView)
        self.menuView.addAction(self.menuDateRange.menuAction())
        self.menuView.addAction(self.actionShowPreferences)
        self.menuView.addAction(self.actionToggleGraph)
        self.menuView.addAction(self.actionTogglePieChart)
        self.menuEdit.addAction(self.actionNewItem)
        self.menuEdit.addAction(self.actionNewAccountGroup)
        self.menuEdit.addAction(self.actionDeleteItem)
        self.menuEdit.addAction(self.actionEditItem)
        self.menuEdit.addSeparator()
        self.menuEdit.addAction(self.actionMoveUp)
        self.menuEdit.addAction(self.actionMoveDown)
        self.menuEdit.addAction(self.actionDuplicateTransaction)
        self.menuEdit.addAction(self.actionMakeScheduleFromSelected)
        self.menuEdit.addAction(self.actionReconcileSelected)
        self.menuEdit.addAction(self.actionToggleReconciliationMode)
        self.menuEdit.addAction(self.actionToggleAccountExclusion)
        self.menuEdit.addSeparator()
        self.menuEdit.addAction(self.actionShowSelectedAccount)
        self.menuEdit.addAction(self.actionNavigateBack)
        self.menuEdit.addAction(self.actionJumpToAccount)
        self.menuEdit.addSeparator()
        self.menuEdit.addAction(self.actionUndo)
        self.menuEdit.addAction(self.actionRedo)
        self.menuHelp.addAction(self.actionShowHelp)
        self.menuHelp.addAction(self.actionCheckForUpdate)
        self.menuHelp.addAction(self.actionOpenDebugLog)
        self.menuHelp.addAction(self.actionAbout)
        mainmenus = [self.menuFile, self.menuEdit, self.menuView, self.menuHelp]
        for menu in mainmenus:
            self.menubar.addAction(menu.menuAction())
            setAccelKeys(menu)
        setAccelKeys(self.menubar)
        self.tabBar.setMovable(True)
        self.tabBar.setTabsClosable(True)
        
        seq = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_Right)
        self._shortcutNextTab = QShortcut(seq, self)
        seq = QKeySequence(Qt.CTRL + Qt.SHIFT + Qt.Key_Left)
        self._shortcutPrevTab = QShortcut(seq, self)
        
        # Linux setup
        if ISLINUX:
            self.actionCheckForUpdate.setVisible(False) # This only works on Windows
    
    def _bindSignals(self):
        self.newItemButton.clicked.connect(self.actionNewItem.trigger)
        self.deleteItemButton.clicked.connect(self.actionDeleteItem.trigger)
        self.editItemButton.clicked.connect(self.actionEditItem.trigger)
        self.graphVisibilityButton.clicked.connect(self.actionToggleGraph.trigger)
        self.piechartVisibilityButton.clicked.connect(self.actionTogglePieChart.trigger)
        self.columnsVisibilityButton.clicked.connect(self.columnsVisibilityButtonClicked)
        self.recentDocuments.mustOpenItem.connect(self.doc.open)
        self.doc.documentOpened.connect(self.recentDocuments.insertItem)
        self.doc.documentSavedAs.connect(self.recentDocuments.insertItem)
        self.doc.documentPathChanged.connect(self.documentPathChanged)
        self.tabBar.currentChanged.connect(self.currentTabChanged)
        self.tabBar.tabCloseRequested.connect(self.tabCloseRequested)
        self.tabBar.tabMoved.connect(self.tabMoved)
        
        # Views
        self.actionShowNetWorth.triggered.connect(self.showNetWorthTriggered)        
        self.actionShowProfitLoss.triggered.connect(self.showProfitLossTriggered)        
        self.actionShowTransactions.triggered.connect(self.showTransactionsTriggered)        
        self.actionShowSchedules.triggered.connect(self.showSchedulesTriggered)        
        self.actionShowBudgets.triggered.connect(self.showBudgetsTriggered)        
        self.actionShowPreviousView.triggered.connect(self.showPreviousViewTriggered)        
        self.actionShowNextView.triggered.connect(self.showNextViewTriggered)        
        self.actionShowPreferences.triggered.connect(self.app.showPreferences)
        self.actionToggleGraph.triggered.connect(self.toggleGraphTriggered)
        self.actionTogglePieChart.triggered.connect(self.togglePieChartTriggered)
        
        # Document Edition
        self.actionNewItem.triggered.connect(self.newItemTriggered)
        self.actionNewAccountGroup.triggered.connect(self.newAccountGroupTriggered)
        self.actionDeleteItem.triggered.connect(self.deleteItemTriggered)
        self.actionEditItem.triggered.connect(self.editItemTriggered)
        self.actionMoveUp.triggered.connect(self.moveUpTriggered)
        self.actionMoveDown.triggered.connect(self.moveDownTriggered)
        self.actionDuplicateTransaction.triggered.connect(self.model.duplicate_item)
        self.actionUndo.triggered.connect(self.doc.model.undo)
        self.actionRedo.triggered.connect(self.doc.model.redo)
        
        # Open / Save / Import / Export / New
        self.actionNewDocument.triggered.connect(self.doc.new)
        self.actionOpenDocument.triggered.connect(self.doc.openDocument)
        self.actionOpenExampleDocument.triggered.connect(self.doc.openExampleDocument)
        self.actionOpenPluginFolder.triggered.connect(self.model.app.open_plugin_folder)
        self.actionImport.triggered.connect(self.doc.importDocument)
        self.actionSave.triggered.connect(self.doc.save)
        self.actionSaveAs.triggered.connect(self.doc.saveAs)
        self.actionExport.triggered.connect(self.model.export)
        
        # Misc
        self.actionNewTab.triggered.connect(self.model.new_tab)
        self.actionCloseTab.triggered.connect(self.closeTabTriggered)
        self.actionShowSelectedAccount.triggered.connect(self.model.show_account)
        self.actionNavigateBack.triggered.connect(self.navigateBackTriggered)
        self.actionJumpToAccount.triggered.connect(self.jumpToAccountTriggered)
        self.actionMakeScheduleFromSelected.triggered.connect(self.makeScheduleFromSelectedTriggered)
        self.actionReconcileSelected.triggered.connect(self.reconcileSelectedTriggered)
        self.actionToggleReconciliationMode.triggered.connect(self.toggleReconciliationModeTriggered)
        self.actionToggleAccountExclusion.triggered.connect(self.toggleAccountExclusionTriggered)
        self.actionPrint.triggered.connect(self._print)
        self.actionShowHelp.triggered.connect(self.app.showHelp)
        self.actionCheckForUpdate.triggered.connect(self.checkForUpdateTriggered)
        self.actionAbout.triggered.connect(self.aboutTriggered)
        self.actionOpenDebugLog.triggered.connect(self.openDebugLogTriggered)
        self.actionQuit.triggered.connect(self.close)
        
        # Extra Shortcuts
        self._shortcutNextTab.activated.connect(self.showNextViewTriggered)
        self._shortcutPrevTab.activated.connect(self.showPreviousViewTriggered)
    
    #--- QWidget overrides
    def closeEvent(self, event):
        if self.doc.confirmDestructiveAction():
            event.accept()
        else:
            event.ignore()
    
    #--- Private
    def _print(self):
        dialog = QPrintDialog(self)
        if dialog.exec_() != QPrintDialog.Accepted:
            return
        printer = dialog.printer()
        currentView = self.mainView.currentWidget()
        viewPrinter = ViewPrinter(printer, currentView)
        currentView.fitViewsForPrint(viewPrinter)
        viewPrinter.render()
    
    def _getViewforPane(self, pane_type, pane_view):
        if pane_view in self.model2view:
            view = self.model2view[pane_view]
        else:
            view = PANETYPE2VIEWCLASS[pane_type](model=pane_view)
            self.model2view[pane_view] = view
            self.mainView.addWidget(view)
            view.restoreSubviewsSize()
        return view
    
    def _setTabIndex(self, index):
        if not self.tabBar.count():
            return
        self.tabBar.setCurrentIndex(index)
        self._updateActionsState()
        pane_type = self.model.pane_type(index)
        pane_view = self.model.pane_view(index)
        view = self._getViewforPane(pane_type, pane_view)
        self.mainView.setCurrentWidget(view)
        view.setFocus()
    
    def _activeView(self):
        paneIndex = self.model.current_pane_index
        return self.model.pane_view(paneIndex)
    
    def _updateActionsState(self):
        # Updates enable/disable checked/unchecked state of all actions. These state can change
        # under various conditions: main view change, date range type change and when reconciliation
        # mode is toggled
        
        # Determine what actions are enabled
        view = self._activeView()
        viewType = view.VIEW_TYPE
        isSheet = viewType in {PaneType.NetWorth, PaneType.Profit}
        isTransactionOrEntryTable = viewType in {PaneType.Transaction, PaneType.Account}
        canToggleReconciliation = viewType == PaneType.Account and view.can_toggle_reconciliation_mode
        
        newItemLabel = {
            PaneType.NetWorth: tr("New Account"),
            PaneType.Profit: tr("New Account"),
            PaneType.Transaction: tr("New Transaction"),
            PaneType.Account: tr("New Transaction"),
            PaneType.Schedule: tr("New Schedule"),
            PaneType.Budget: tr("New Budget"),
            PaneType.GeneralLedger: tr("New Transaction"),
        }.get(viewType, tr("New Item")) #XXX make "New Item" disabled
        self.actionNewItem.setText(newItemLabel)
        self.actionNewAccountGroup.setEnabled(isSheet)
        self.actionMoveDown.setEnabled(isTransactionOrEntryTable)
        self.actionMoveUp.setEnabled(isTransactionOrEntryTable)
        self.actionDuplicateTransaction.setEnabled(isTransactionOrEntryTable)
        self.actionMakeScheduleFromSelected.setEnabled(isTransactionOrEntryTable)
        self.actionReconcileSelected.setEnabled(viewType == PaneType.Account and view.reconciliation_mode)
        self.actionShowNextView.setEnabled(self.model.current_pane_index < self.model.pane_count-1)
        self.actionShowPreviousView.setEnabled(self.model.current_pane_index > 0)
        self.actionShowSelectedAccount.setEnabled(isSheet or isTransactionOrEntryTable)
        self.actionNavigateBack.setEnabled(viewType == PaneType.Account)
        self.actionToggleReconciliationMode.setEnabled(canToggleReconciliation)
        self.actionToggleAccountExclusion.setEnabled(isSheet)
    
    def _updateUndoActions(self):
        if self.doc.model.can_undo():
            self.actionUndo.setEnabled(True)
            self.actionUndo.setText(tr("Undo {0}").format(self.doc.model.undo_description()))
        else:
            self.actionUndo.setEnabled(False)
            self.actionUndo.setText(tr("Undo"))
        if self.doc.model.can_redo():
            self.actionRedo.setEnabled(True)
            self.actionRedo.setText(tr("Redo {0}").format(self.doc.model.redo_description()))
        else:
            self.actionRedo.setEnabled(False)
            self.actionRedo.setText(tr("Redo"))
    
    #--- Actions
    # Views
    def showNetWorthTriggered(self):
        self.model.select_pane_of_type(PaneType.NetWorth)
    
    def showProfitLossTriggered(self):
        self.model.select_pane_of_type(PaneType.Profit)
    
    def showTransactionsTriggered(self):
        self.model.select_pane_of_type(PaneType.Transaction)
    
    def showSchedulesTriggered(self):
        self.model.select_pane_of_type(PaneType.Schedule)
    
    def showBudgetsTriggered(self):
        self.model.select_pane_of_type(PaneType.Budget)
    
    def showPreviousViewTriggered(self):
        self.model.select_previous_view()
    
    def showNextViewTriggered(self):
        self.model.select_next_view()
    
    # Document Edition
    def newItemTriggered(self):
        self.model.new_item()
    
    def newAccountGroupTriggered(self):
        self.model.new_group()
    
    def deleteItemTriggered(self):
        self.model.delete_item()
    
    def editItemTriggered(self):
        self.model.edit_item()
    
    def moveUpTriggered(self):
        self.model.move_up()
    
    def moveDownTriggered(self):
        self.model.move_down()
    
    # Misc
    def closeTabTriggered(self):
        self.model.close_pane(self.model.current_pane_index)
    
    def navigateBackTriggered(self):
        self.model.navigate_back()
    
    def jumpToAccountTriggered(self):
        self.model.jump_to_account()
    
    def makeScheduleFromSelectedTriggered(self):
        self.model.make_schedule_from_selected()
    
    def reconcileSelectedTriggered(self):
        self._activeView().etable.toggle_reconciled()
    
    def toggleReconciliationModeTriggered(self):
        self._activeView().toggle_reconciliation_mode()
        self._updateActionsState()
    
    def toggleAccountExclusionTriggered(self):
        viewType = self.model.pane_type(self.model.current_pane_index)
        if viewType in {PaneType.NetWorth, PaneType.Profit}:
            self._activeView().sheet.toggle_excluded()
    
    def toggleGraphTriggered(self):
        self.model.toggle_area_visibility(PaneArea.BottomGraph)
    
    def togglePieChartTriggered(self):
        self.model.toggle_area_visibility(PaneArea.RightChart)
    
    def columnsVisibilityButtonClicked(self):
        items = self.model.column_menu_items()
        if not items:
            return
        menu = QMenu()
        for i, (display, marked) in enumerate(items):
            action = menu.addAction(display)
            action.setCheckable(True)
            action.setChecked(marked)
            action.setData(i)
            action.triggered.connect(self.columnsMenuItemWasClicked)
        self._columnMenuHolder = menu # we need to hold a reference to it while it popups
        button = self.columnsVisibilityButton
        menu.popup(button.parentWidget().mapToGlobal(button.geometry().topLeft()))
    
    def columnsMenuItemWasClicked(self):
        action = self.sender()
        if action is not None:
            index = action.data()
            self.model.toggle_column_menu_item(index)
    
    def checkForUpdateTriggered(self):
        QProcess.execute('updater.exe', ['/checknow'])
    
    def aboutTriggered(self):
        self.app.showAboutBox()
    
    def openDebugLogTriggered(self):
        debugLogPath = op.join(getAppData(), 'debug.log')
        url = QUrl.fromLocalFile(debugLogPath)
        QDesktopServices.openUrl(url)
    
    #--- Other Signals
    def currentTabChanged(self, index):
        self.model.current_pane_index = index
        self._setTabIndex(index)
    
    def documentPathChanged(self):
        if self.doc.documentPath:
            title = "moneyGuru ({})".format(self.doc.documentPath)
        else:
            title = "moneyGuru"
        self.setWindowTitle(title)
    
    def tabCloseRequested(self, index):
        self.model.close_pane(index)
    
    def tabMoved(self, fromIndex, toIndex):
        self.model.move_pane(fromIndex, toIndex)
    
    #--- model --> view
    def change_current_pane(self):
        self._setTabIndex(self.model.current_pane_index)
    
    def refresh_panes(self):
        while self.tabBar.count() < self.model.pane_count:
            self.tabBar.addTab('')
        for i in range(self.model.pane_count):
            pane_label = self.model.pane_label(i)
            pane_label = escapeamp(pane_label)
            self.tabBar.setTabText(i, pane_label)
            pane_type = self.model.pane_type(i)
            pane_view = self.model.pane_view(i)
            # Ensure that the view's "view" has been created and bound
            self._getViewforPane(pane_type, pane_view)
            iconname = PANETYPE2ICON.get(pane_type)
            icon = QIcon(QPixmap(':/{0}'.format(iconname))) if iconname else QIcon()
            self.tabBar.setTabIcon(i, icon)
        # It's important that we proceed with tab removal *after* we've completed tab initialization.
        # We're walking on eggshells here. refresh_panes() can be called in multiple situations, one
        # of them is during the opening of a document. When that happens when another document was
        # previously opened, all views' model are uninitalized and don't have their "view" attribute
        # set yet. If we proceed with the setCurrentIndex() call below before _getViewforPane()
        # could be called above, we get a crash.
        if self.tabBar.currentIndex() < self.model.pane_count:
            # Normally, we don't touch the tabBar index here and wait for change_current_pane,
            # but when we remove tabs, it's possible that currentTabChanged end up being called and
            # then the tab selection is bugged. I tried disconnecting/reconnecting the signal, but
            # this is buggy. So when a selected tab is about to be removed, we change the selection
            # to the model's one immediately.
            self.tabBar.setCurrentIndex(self.model.current_pane_index)
        while self.tabBar.count() > self.model.pane_count:
            self.tabBar.removeTab(self.tabBar.count()-1)
        self.tabBar.setTabsClosable(self.model.pane_count > 1)
    
    def refresh_status_line(self):
        self.statusLabel.setText(self.model.status_line)
    
    def refresh_undo_actions(self):
        self._updateUndoActions()
    
    def restore_window_frame(self, frame):
        self.setGeometry(*frame)
    
    def save_window_frame(self):
        r = self.geometry()
        return (r.x(), r.y(), r.width(), r.height())
    
    def show_message(self, msg):
        title = tr("Warning")
        QMessageBox.warning(self, title, msg)
    
    def update_area_visibility(self):
        hidden = self.model.hidden_areas
        graphimg = ':/graph_visibility_{}_16'.format('off' if PaneArea.BottomGraph in hidden else 'on')
        pieimg = ':/piechart_visibility_{}_16'.format('off' if PaneArea.RightChart in hidden else 'on')
        self.graphVisibilityButton.setIcon(QIcon(QPixmap(graphimg)))
        self.piechartVisibilityButton.setIcon(QIcon(QPixmap(pieimg)))
    
    def view_closed(self, index):
        self.tabBar.removeTab(index)
        self.tabBar.setTabsClosable(self.model.pane_count > 1)
Example #16
0
    def __createLayout( self ):
        " Creates the toolbar and layout "

        # Buttons
        printButton = QAction( PixmapCache().getIcon( 'printer.png' ),
                               'Print', self )
        #printButton.setShortcut( 'Ctrl+' )
        printButton.triggered.connect( self.__onPrint )
        printButton.setVisible( False )

        printPreviewButton = QAction(
                PixmapCache().getIcon( 'printpreview.png' ),
                'Print preview', self )
        #printPreviewButton.setShortcut( 'Ctrl+' )
        printPreviewButton.triggered.connect( self.__onPrintPreview )
        printPreviewButton.setVisible( False )

        fixedSpacer = QWidget()
        fixedSpacer.setFixedHeight( 16 )

        zoomInButton = QAction( PixmapCache().getIcon( 'zoomin.png' ),
                                'Zoom in (Ctrl+=)', self )
        zoomInButton.setShortcut( 'Ctrl+=' )
        zoomInButton.triggered.connect( self.onZoomIn )
        self.__zoomInSynonim = QShortcut( "Ctrl++", self )
        self.__zoomInSynonim.activated.connect( self.onZoomIn )

        zoomOutButton = QAction( PixmapCache().getIcon( 'zoomout.png' ),
                                'Zoom out (Ctrl+-)', self )
        zoomOutButton.setShortcut( 'Ctrl+-' )
        zoomOutButton.triggered.connect( self.onZoomOut )

        zoomResetButton = QAction( PixmapCache().getIcon( 'zoomreset.png' ),
                                   'Zoom reset (Ctrl+0)', self )
        zoomResetButton.setShortcut( 'Ctrl+0' )
        zoomResetButton.triggered.connect( self.onZoomReset )


        # Toolbar
        toolbar = QToolBar( self )
        toolbar.setOrientation( Qt.Vertical )
        toolbar.setMovable( False )
        toolbar.setAllowedAreas( Qt.RightToolBarArea )
        toolbar.setIconSize( QSize( 16, 16 ) )
        toolbar.setFixedWidth( 28 )
        toolbar.setContentsMargins( 0, 0, 0, 0 )
        toolbar.addAction( printPreviewButton )
        toolbar.addAction( printButton )
        toolbar.addWidget( fixedSpacer )
        toolbar.addAction( zoomInButton )
        toolbar.addAction( zoomOutButton )
        toolbar.addAction( zoomResetButton )

        hLayout = QHBoxLayout()
        hLayout.setContentsMargins( 0, 0, 0, 0 )
        hLayout.setSpacing( 0 )
        hLayout.addWidget( self.__viewer )
        hLayout.addWidget( toolbar )

        self.__outsideChangesBar = OutsideChangeWidget( self.__viewer )
        self.__outsideChangesBar.reloadRequest.connect( self.__onReload )
        self.__outsideChangesBar.reloadAllNonModifiedRequest.connect( self.reloadAllNonModified )
        self.__outsideChangesBar.hide()

        self.setLayout( hLayout )
        return
Example #17
0
class DocumentViewManager(QMainWindow):
    """
    MDI area for displaying supporting documents within a given context e.g.
    supporting documents for a specific household based on the lifetime of the
    'SourceDocumentManager' instance.
    """
    def __init__(self, parent = None):
        QMainWindow.__init__(self, parent)
        self.setWindowFlags(Qt.Window)

        self._mdi_area = QMdiArea()
        self.setCentralWidget(self._mdi_area)

        self._mdi_area.subWindowActivated.connect(self.update_actions)
        self._viewer_mapper = QSignalMapper(self)
        self._viewer_mapper.mapped[QWidget].connect(self.set_active_sub_window)

        win_title = QApplication.translate("DocumentViewManager","Document Viewer")
        self.setWindowTitle(win_title)
        self.setUnifiedTitleAndToolBarOnMac(True)
        self.statusBar().showMessage(QApplication.translate("DocumentViewManager","Ready"))

        self._doc_viewers = {}

        self._create_menu_actions()
        self.update_actions()

    def _create_menu_actions(self):
        self._window_menu = self.menuBar().addMenu(QApplication.translate("DocumentViewManager","&Windows"))

        self._close_act = QAction(QApplication.translate("DocumentViewManager",
                                                         "Cl&ose"), self)
        self._close_act.setStatusTip(QApplication.translate("DocumentViewManager",
                                                            "Close the active document viewer"))
        self._close_act.triggered.connect(self._mdi_area.closeActiveSubWindow)

        self._close_all_act = QAction(QApplication.translate("DocumentViewManager",
                                                         "Close &All"), self)
        self._close_all_act.setStatusTip(QApplication.translate("DocumentViewManager",
                                                            "Close all the document viewers"))
        self._close_all_act.triggered.connect(self._mdi_area.closeAllSubWindows)

        self._tile_act = QAction(QApplication.translate("DocumentViewManager",
                                                         "&Tile"), self)
        self._tile_act.setStatusTip(QApplication.translate("DocumentViewManager",
                                                            "Tile the document viewers"))
        self._tile_act.triggered.connect(self._mdi_area.tileSubWindows)

        self._cascade_act = QAction(QApplication.translate("DocumentViewManager",
                                                         "&Cascade"), self)
        self._cascade_act.setStatusTip(QApplication.translate("DocumentViewManager",
                                                            "Cascade the document viewers"))
        self._cascade_act.triggered.connect(self._mdi_area.cascadeSubWindows)

        self._next_act = QAction(QApplication.translate("DocumentViewManager",
                                                         "Ne&xt"), self)
        self._next_act.setStatusTip(QApplication.translate("DocumentViewManager",
                                                            "Move the focus to the next document viewer"))
        self._next_act.triggered.connect(self._mdi_area.activateNextSubWindow)

        self._previous_act = QAction(QApplication.translate("DocumentViewManager",
                                                         "Pre&vious"), self)
        self._previous_act.setStatusTip(QApplication.translate("DocumentViewManager",
                                                            "Move the focus to the previous document viewer"))
        self._previous_act.triggered.connect(self._mdi_area.activatePreviousSubWindow)

        self._separator_act = QAction(self)
        self._separator_act.setSeparator(True)

        self.update_window_menu()
        self._window_menu.aboutToShow.connect(self.update_window_menu)

    def update_actions(self):
        if self._mdi_area.activeSubWindow():
            has_mdi_child = True

        else:
            has_mdi_child = False

        self._close_act.setEnabled(has_mdi_child)
        self._close_all_act.setEnabled(has_mdi_child)
        self._tile_act.setEnabled(has_mdi_child)
        self._cascade_act.setEnabled(has_mdi_child)
        self._previous_act.setEnabled(has_mdi_child)
        self._next_act.setEnabled(has_mdi_child)
        self._separator_act.setVisible(has_mdi_child)

    def update_window_menu(self):
        self._window_menu.clear()
        self._window_menu.addAction(self._close_act)
        self._window_menu.addAction(self._close_all_act)
        self._window_menu.addSeparator()
        self._window_menu.addAction(self._tile_act)
        self._window_menu.addAction(self._cascade_act)
        self._window_menu.addSeparator()
        self._window_menu.addAction(self._next_act)
        self._window_menu.addAction(self._previous_act)
        self._window_menu.addAction(self._separator_act)

        windows = self._mdi_area.subWindowList()
        self._separator_act.setVisible(len(windows) != 0)

        for i, window in enumerate(windows):
            text = "%d. %s" % (i + 1, window.windowTitle())

            win_action = self._window_menu.addAction(text)
            win_action.setCheckable(True)
            win_action.setChecked(window is self._mdi_area.activeSubWindow())
            win_action.triggered.connect(self._viewer_mapper.map)
            self._viewer_mapper.setMapping(win_action, window)

    def load_viewer(self, document_widget):
        """
        Open a new instance of the viewer or activate an existing one if the
        document had been previously loaded.
        :param document_widget: Contains all the necessary information required
        to load the specific document.
        """
        doc_identifier = document_widget.file_identifier()

        if doc_identifier in self._doc_viewers:
            doc_sw = self._doc_viewers[doc_identifier]
            self._mdi_area.setActiveSubWindow(doc_sw)
            doc_sw.showNormal()

        else:
            doc_viewer = self._create_viewer(document_widget)
            abs_doc_path = self.absolute_document_path(document_widget)

            if not QFile.exists(abs_doc_path):
                msg = QApplication.translate("DocumentViewManager",
                                             "The selected document does not exist."
                                             "\nPlease check the supporting documents' "
                                             "repository setting.")
                QMessageBox.critical(self, QApplication.translate("DocumentViewManager","Invalid Document"),
                                     msg)

                return

            doc_viewer.load_document(abs_doc_path)

            self._doc_viewers[doc_identifier] = doc_viewer

            self._mdi_area.addSubWindow(doc_viewer)
            doc_viewer.show()

        if not self.isVisible():
            self.setVisible(True)

        if self.isMinimized():
            self.showNormal()

    def set_active_sub_window(self, viewer):
        if viewer:
            self._mdi_area.setActiveSubWindow(viewer)

    def absolute_document_path(self,document_widget):
        """
        Build the absolute document path using info from the document widget.
        :param document_widget: Instance of document widget.
        :return: Absolute path of the supporting document.
        :rtype: str
        """
        abs_path = ""

        file_manager = document_widget.fileManager
        if not file_manager is None:
            network_repository = file_manager.networkPath
            file_id = document_widget.file_identifier()
            doc_type = "%d"%(document_widget.documentType())
            file_name, file_extension = guess_extension(document_widget.displayName())

            abs_path = network_repository + "/" + doc_type + "/" +\
                       file_id + file_extension

        return abs_path

    def reset(self):
        """
        Removes all document viewers in the view area.
        The QCloseEvent sent to each sub-window will decrement the register.
        """
        self._mdi_area.closeAllSubWindows()

    def _create_viewer(self, document_widget):
        """
        Creates a new instance of a document viewer.
        :param document_widget: Contains all the necessary information required
        to load the specific document.
        :return: Document viewer object
        :rtype: DocumentViewer
        """
        doc_viewer = DocumentViewer(self._mdi_area, document_widget.file_identifier())
        doc_viewer.setAttribute(Qt.WA_DeleteOnClose)
        doc_viewer.setWindowTitle(document_widget.displayName())

        #TODO: Incorporate logic for determining viewer based on document type
        ph_viewer = PhotoViewer()
        doc_viewer.set_view_widget(ph_viewer)

        doc_viewer.closed.connect(self._on_viewer_closed)

        return doc_viewer

    def remove_viewer(self, viewer_id):
        """
        Close and remove the viewer with the specified viewer ID.
        """
        if viewer_id in self._doc_viewers:
            viewer = self._doc_viewers[viewer_id]
            self._mdi_area.setActiveSubWindow(viewer)
            self._mdi_area.closeActiveSubWindow()

        self._on_viewer_closed(viewer_id)

    def _on_viewer_closed(self,file_id):
        """
        Slot raised when a document viewer is closed.
        """
        if file_id in self._doc_viewers:
            del self._doc_viewers[file_id]
Example #18
0
class Plugin(QObject):
    """Plugin interface implementation.
    """
    def __init__(self):
        """Create and install the plugin
        """
        QObject.__init__(self)

        self._dock = None
        self._saveAction = None
        self._dockInstalled = False
        core.workspace().currentDocumentChanged.connect(
            self._onDocumentChanged)  # Disconnected.
        core.workspace().languageChanged.connect(
            self._onDocumentChanged)  # Disconnected.

        # Install our CodeChat page into the settings dialog.
        core.uiSettingsManager().aboutToExecute.connect(
            self._onSettingsDialogAboutToExecute)  # Disconnected.
        # Update preview dock when the settings dialog (which contains the
        # CodeChat enable checkbox) is changed.
        core.uiSettingsManager().dialogAccepted.connect(
            self._onDocumentChanged)  # Disconnected.

        # Provide a "Set Sphinx Path" menu item.
        core.uiSettingsManager().dialogAccepted.connect(
            self._setSphinxActionVisibility)
        self._sphinxAction = QAction('Set Sphinx path', self._dock)
        self._sphinxAction.setShortcut(QKeySequence('Alt+Shift+S'))
        core.actionManager().addAction('mTools/aSetSphinxPath',
                                       self._sphinxAction)
        self._sphinxAction.triggered.connect(self.onSphinxPath)

        # If user's config .json file lacks it, populate CodeChat's default
        # config key and Sphinx's default config key.
        if not 'CodeChat' in core.config():
            core.config()['CodeChat'] = {}
            core.config()['CodeChat']['Enabled'] = False
            core.config().flush()
        if not 'Sphinx' in core.config():
            core.config()['Sphinx'] = {}
            core.config()['Sphinx']['Enabled'] = False
            core.config()['Sphinx']['Executable'] = u'sphinx-build'
            core.config()['Sphinx']['ProjectPath'] = u''
            core.config()['Sphinx']['BuildOnSave'] = False
            core.config()['Sphinx']['OutputPath'] = os.path.join(
                '_build', 'html')
            core.config()['Sphinx']['AdvancedMode'] = False
            core.config()['Sphinx']['Cmdline'] = (
                u'sphinx-build -d ' + os.path.join('_build', 'doctrees') +
                ' . ' + os.path.join('_build', 'html'))
            core.config().flush()

        self._setSphinxActionVisibility()

    def del_(self):
        """Uninstall the plugin
        """
        core.actionManager().removeAction('mTools/aSetSphinxPath')

        if self._dockInstalled:
            self._removeDock()

        if self._dock is not None:
            self._dock.del_()

        core.workspace().currentDocumentChanged.disconnect(
            self._onDocumentChanged)
        core.workspace().languageChanged.disconnect(self._onDocumentChanged)
        core.uiSettingsManager().aboutToExecute.disconnect(
            self._onSettingsDialogAboutToExecute)
        core.uiSettingsManager().dialogAccepted.disconnect(
            self._onDocumentChanged)
        if self._dock:
            self._dock.closed.disconnect(self._onDockClosed)
            self._dock.shown.disconnect(self._onDockShown)
            self._saveAction.triggered.disconnect(self._dock.onPreviewSave)

    def _onDocumentChanged(self):
        """Document or Language changed.
        Create dock, if necessary
        """
        if self._canPreview(core.workspace().currentDocument()):
            if not self._dockInstalled:
                self._createDock()
        else:
            if self._dockInstalled:
                self._removeDock()

    def _canPreview(self, document):
        """Check if the given document can be shown in the Preview dock.
        """
        if document is None:
            return False

        if document.qutepart.language() in ('Markdown', 'Restructured Text') or \
           isHtmlFile(document):
            return True

        if canUseCodeChat(document.filePath()):
            return True

        if sphinxEnabledForFile(document.filePath()):
            return True

        return False

    def _createDock(self):
        """Install dock
        """
        # create dock
        if self._dock is None:
            from enki.plugins.preview.preview import PreviewDock
            self._dock = PreviewDock()
            self._dock.closed.connect(self._onDockClosed)  # Disconnected.
            self._dock.shown.connect(self._onDockShown)  # Disconnected.

            self._saveAction = QAction(QIcon(':enkiicons/save.png'),
                                       'Save Preview as HTML', self._dock)
            self._saveAction.setShortcut(QKeySequence("Alt+Shift+P"))
            self._saveAction.triggered.connect(
                self._dock.onPreviewSave)  # Disconnected.

        core.mainWindow().addDockWidget(Qt.RightDockWidgetArea, self._dock)

        core.actionManager().addAction("mView/aPreview",
                                       self._dock.showAction())
        core.actionManager().addAction("mFile/aSavePreview", self._saveAction)
        self._dockInstalled = True
        if core.config()['Preview']['Enabled']:
            self._dock.show()

    def _onDockClosed(self):
        """Dock has been closed by user. Change Enabled option
        """
        if core.config()['Preview']['Enabled']:
            core.config()['Preview']['Enabled'] = False
            core.config().flush()

    def _onDockShown(self):
        """Dock has been shown by user. Change Enabled option
        """
        if not core.config()['Preview']['Enabled']:
            core.config()['Preview']['Enabled'] = True
            core.config().flush()

    def _removeDock(self):
        """Remove dock from GUI
        """
        core.actionManager().removeAction("mView/aPreview")
        core.actionManager().removeAction("mFile/aSavePreview")
        core.mainWindow().removeDockWidget(self._dock)
        self._dockInstalled = False

    def _onSettingsDialogAboutToExecute(self, dialog):
        """The UI settings dialog is about to execute. Install preview-related
           settings."""
        CodeChatSettingsWidget(dialog)
        SphinxSettingsWidget(dialog)

    @pyqtSlot()
    def _setSphinxActionVisibility(self):
        self._sphinxAction.setVisible(core.config()['Sphinx']['Enabled'])

    @pyqtSlot()
    def onSphinxPath(self):
        core.config()['Sphinx']['ProjectPath'] = os.getcwd()
        core.config().flush()
        if core.config()['Preview']['Enabled']:
            self._dock._scheduleDocumentProcessing()
Example #19
0
class PylintViewer(QWidget):
    " Pylint tab widget "

    # Limits to colorize the final score
    BadLimit = 8.5
    GoodLimit = 9.5

    # Options of providing a report
    SingleFile = 0
    DirectoryFiles = 1
    ProjectFiles = 2
    SingleBuffer = 3

    updatePylintTooltip = pyqtSignal(str)

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        self.__reportUUID = ""
        self.__reportFileName = ""
        self.__reportOption = -1
        self.__reportShown = False
        self.__report = None

        self.__widgets = []

        # Prepare members for reuse
        if GlobalData().pylintAvailable:
            self.__noneLabel = QLabel("\nNo results available")
        else:
            self.__noneLabel = QLabel("\nPylint is not available")
        self.__noneLabel.setAutoFillBackground(True)
        noneLabelPalette = self.__noneLabel.palette()
        noneLabelPalette.setColor(QPalette.Background,
                                  GlobalData().skin.nolexerPaper)
        self.__noneLabel.setPalette(noneLabelPalette)

        self.__noneLabel.setFrameShape(QFrame.StyledPanel)
        self.__noneLabel.setAlignment(Qt.AlignHCenter)
        self.__headerFont = self.__noneLabel.font()
        self.__headerFont.setPointSize(self.__headerFont.pointSize() + 4)
        self.__noneLabel.setFont(self.__headerFont)

        self.__createLayout(parent)

        self.__updateButtonsStatus()
        self.resizeEvent()
        return

    def __createLayout(self, parent):
        " Creates the toolbar and layout "

        # Buttons
        self.printButton = QAction(PixmapCache().getIcon('printer.png'),
                                   'Print', self)
        #printButton.setShortcut( 'Ctrl+' )
        self.printButton.triggered.connect(self.__onPrint)
        self.printButton.setVisible(False)

        self.printPreviewButton = QAction(
            PixmapCache().getIcon('printpreview.png'), 'Print preview', self)
        #printPreviewButton.setShortcut( 'Ctrl+' )
        self.printPreviewButton.triggered.connect(self.__onPrintPreview)
        self.printPreviewButton.setVisible(False)

        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.clearButton = QAction(PixmapCache().getIcon('trash.png'), 'Clear',
                                   self)
        self.clearButton.triggered.connect(self.__clear)

        # The toolbar
        self.toolbar = QToolBar(self)
        self.toolbar.setOrientation(Qt.Vertical)
        self.toolbar.setMovable(False)
        self.toolbar.setAllowedAreas(Qt.RightToolBarArea)
        self.toolbar.setIconSize(QSize(16, 16))
        self.toolbar.setFixedWidth(28)
        self.toolbar.setContentsMargins(0, 0, 0, 0)

        self.toolbar.addAction(self.printPreviewButton)
        self.toolbar.addAction(self.printButton)
        self.toolbar.addWidget(spacer)
        self.toolbar.addAction(self.clearButton)

        self.__vLayout = QVBoxLayout()
        self.__vLayout.setContentsMargins(5, 5, 5, 5)
        self.__vLayout.setSpacing(0)
        self.__vLayout.setSizeConstraint(QLayout.SetFixedSize)

        self.__bodyFrame = QFrame(self)
        #        self.__bodyFrame.setFrameShape( QFrame.StyledPanel )
        self.__bodyFrame.setFrameShape(QFrame.NoFrame)

        #        self.__bodyFrame.setSizePolicy( QSizePolicy.Maximum,
        #                                        QSizePolicy.Expanding )
        self.__bodyFrame.setLayout(self.__vLayout)
        self.bodyWidget = QScrollArea(self)
        self.bodyWidget.setFocusPolicy(Qt.NoFocus)
        self.bodyWidget.setWidget(self.__bodyFrame)
        self.bodyWidget.hide()

        self.__hLayout = QHBoxLayout()
        self.__hLayout.setContentsMargins(0, 0, 0, 0)
        self.__hLayout.setSpacing(0)
        self.__hLayout.addWidget(self.toolbar)
        self.__hLayout.addWidget(self.__noneLabel)
        self.__hLayout.addWidget(self.bodyWidget)

        self.setLayout(self.__hLayout)
        return

    def __updateButtonsStatus(self):
        " Updates the buttons status "
        self.printButton.setEnabled(self.__reportShown)
        self.printPreviewButton.setEnabled(self.__reportShown)
        self.clearButton.setEnabled(self.__reportShown)
        return

    def __onPrint(self):
        " Triggered when the print button is pressed "
        pass

    def __onPrintPreview(self):
        " triggered when the print preview button is pressed "
        pass

    def setFocus(self):
        " Overridden setFocus "
        self.__vLayout.setFocus()
        return

    def __clear(self):
        " Clears the content of the vertical layout "
        if not self.__reportShown:
            return

        self.__removeAll()
        self.bodyWidget.hide()
        self.__noneLabel.show()

        self.__report = None
        self.__reportShown = False
        self.__updateButtonsStatus()
        self.resizeEvent()

        self.__updateTooltip()
        return

    def __removeAll(self):
        " Removes all the items from the report "
        for item in self.__widgets:
            item.hide()
            self.__vLayout.removeWidget(item)
            del item

        self.__widgets = []
        return

    def __createScoreLabel(self, score, previousScore, showFileName, fileName):
        " Creates the score label "

        txt = "Score: " + str(score)
        if previousScore != "":
            txt += " / Previous score: " + str(previousScore)
        if not showFileName:
            txt += " for " + os.path.basename(fileName)

        scoreLabel = QLabel(txt)
        scoreLabel.setFrameShape(QFrame.StyledPanel)
        scoreLabel.setFont(self.__headerFont)
        scoreLabel.setAutoFillBackground(True)
        palette = scoreLabel.palette()

        if score < self.BadLimit:
            palette.setColor(QPalette.Background, QColor(255, 127, 127))
            palette.setColor(QPalette.Foreground, QColor(0, 0, 0))
        elif score > self.GoodLimit:
            palette.setColor(QPalette.Background, QColor(220, 255, 220))
            palette.setColor(QPalette.Foreground, QColor(0, 0, 0))
        else:
            palette.setColor(QPalette.Background, QColor(255, 255, 127))
            palette.setColor(QPalette.Foreground, QColor(0, 0, 0))

        scoreLabel.setPalette(palette)
        return scoreLabel

    @staticmethod
    def __setTableHeight(table):
        " Auxiliary function to set the table height "

        # Height - it is ugly and approximate however I am tired of
        # calculating the proper height. Why is this so hard, huh?
        lastRowHeight = table.itemDelegate().lastHeight
        height = lastRowHeight * (table.topLevelItemCount() + 1) + 10
        table.setFixedHeight(height)
        return

    @staticmethod
    def __shouldShowFileName(messages):
        " Decides if the file name column should be supressed "
        if len(messages) == 0:
            return False
        firstName = messages[0].fileName
        for index in range(1, len(messages)):
            if firstName != messages[index].fileName:
                return True
        return False

    def __addErrorsTable(self, messages, showFileName):
        " Creates the messages table "

        errTable = QTreeWidget(self.bodyWidget)
        errTable.setAlternatingRowColors(True)
        errTable.setRootIsDecorated(False)
        errTable.setItemsExpandable(False)
        errTable.setSortingEnabled(True)
        errTable.setItemDelegate(NoOutlineHeightDelegate(4))
        errTable.setUniformRowHeights(True)
        errTable.itemActivated.connect(self.__errorActivated)

        headerLabels = ["File name", "Line", "Message ID", "Object", "Message"]
        errTable.setHeaderLabels(headerLabels)

        for item in messages:
            if item.position is None:
                lineNumber = str(item.lineNumber)
            else:
                lineNumber = str(item.lineNumber) + ":" + str(item.position)
            values = [
                item.fileName, lineNumber, item.messageID, item.objectName,
                item.message
            ]
            errTable.addTopLevelItem(ErrorTableItem(values, 1))

        # Hide the file name column if required
        if not showFileName:
            errTable.setColumnHidden(0, True)

        # Resizing
        errTable.header().resizeSections(QHeaderView.ResizeToContents)
        errTable.header().setStretchLastSection(True)

        # Sort indicator
        if showFileName:
            sortIndex = 0  # By file names
        else:
            sortIndex = 1  # By line number because this is from the same file
        errTable.header().setSortIndicator(sortIndex, Qt.AscendingOrder)
        errTable.sortItems(sortIndex, errTable.header().sortIndicatorOrder())

        # Height
        self.__setTableHeight(errTable)

        self.__vLayout.addWidget(errTable)
        self.__widgets.append(errTable)
        return

    def __addSimilarity(self, similarity, titleText):
        " Adds a similarity "

        # Label
        title = QLabel(titleText)
        title.setFont(self.__headerFont)

        self.__vLayout.addWidget(title)
        self.__widgets.append(title)

        # List of files
        simTable = QTreeWidget(self.bodyWidget)
        simTable.setAlternatingRowColors(True)
        simTable.setRootIsDecorated(False)
        simTable.setItemsExpandable(False)
        simTable.setSortingEnabled(False)
        simTable.setItemDelegate(NoOutlineHeightDelegate(4))
        simTable.setUniformRowHeights(True)
        simTable.itemActivated.connect(self.__similarityActivated)
        simTable.setHeaderLabels(["File name", "Line"])

        for item in similarity.files:
            values = [item[0], str(item[1])]
            simTable.addTopLevelItem(QTreeWidgetItem(values))

        # Resizing
        simTable.header().resizeSections(QHeaderView.ResizeToContents)
        simTable.header().setStretchLastSection(True)

        # Height
        self.__setTableHeight(simTable)

        self.__vLayout.addWidget(simTable)
        self.__widgets.append(simTable)

        # The fragment itself
        if len(similarity.fragment) > 10:
            # Take first 9 lines
            text = "\n".join(similarity.fragment[:9]) + "\n ..."
            toolTip = "\n".join(similarity.fragment)
        else:
            text = "\n".join(similarity.fragment)
            toolTip = ""
        fragmentLabel = QLabel("<pre>" + self.__htmlEncode(text) + "</pre>")
        if toolTip != "":
            fragmentLabel.setToolTip("<pre>" + self.__htmlEncode(toolTip) +
                                     "</pre>")
        palette = fragmentLabel.palette()
        palette.setColor(QPalette.Background, QColor(250, 250, 175))
        palette.setColor(QPalette.Foreground, QColor(0, 0, 0))
        fragmentLabel.setPalette(palette)
        fragmentLabel.setFrameShape(QFrame.StyledPanel)
        fragmentLabel.setAutoFillBackground(True)

        labelFont = fragmentLabel.font()
        labelFont.setFamily(GlobalData().skin.baseMonoFontFace)
        fragmentLabel.setFont(labelFont)

        self.__vLayout.addWidget(fragmentLabel)
        self.__widgets.append(fragmentLabel)
        return

    @staticmethod
    def __htmlEncode(string):
        " Encodes HTML "
        return string.replace( "&", "&amp;" ) \
                     .replace( ">", "&gt;" ) \
                     .replace( "<", "&lt;" )

    def __addSectionSpacer(self):
        " Adds a fixed height spacer to the VBox layout "
        spacer = QWidget()
        spacer.setFixedHeight(10)
        self.__vLayout.addWidget(spacer)
        self.__widgets.append(spacer)
        return

    def __addGenericTable(self, table):
        " Adds a generic table to the report "

        theTable = QTreeWidget(self.bodyWidget)
        theTable.setAlternatingRowColors(True)
        theTable.setRootIsDecorated(False)
        theTable.setItemsExpandable(False)
        theTable.setSortingEnabled(False)
        theTable.setItemDelegate(NoOutlineHeightDelegate(4))
        theTable.setUniformRowHeights(True)

        headerLabels = []
        for index in range(0, len(table.header)):
            headerLabels.append(table.header[index])
        theTable.setHeaderLabels(headerLabels)

        for item in table.body:
            row = []
            for index in range(0, len(table.header)):
                row.append(item[index])
            theTable.addTopLevelItem(QTreeWidgetItem(row))

        theTable.setFocusPolicy(Qt.NoFocus)

        # Resizing
        theTable.header().resizeSections(QHeaderView.ResizeToContents)
        theTable.header().setStretchLastSection(True)

        # Height
        self.__setTableHeight(theTable)

        self.__vLayout.addWidget(theTable)
        self.__widgets.append(theTable)
        return

    def __addGenericTableTitle(self, table):
        " Adds a generic table title "
        tableTitle = QLabel(table.title)
        tableTitle.setFont(self.__headerFont)

        self.__vLayout.addWidget(tableTitle)
        self.__widgets.append(tableTitle)
        return

    def __updateTooltip(self):
        " Generates a signal with appropriate string message "
        if not self.__reportShown:
            tooltip = "No results available"
        elif self.__reportOption == self.DirectoryFiles:
            tooltip = "Report generated for directory: " + \
                      self.__reportFileName
        elif self.__reportOption == self.ProjectFiles:
            tooltip = "Report generated for the whole project"
        elif self.__reportOption == self.SingleFile:
            tooltip = "Report generated for file: " + self.__reportFileName
        elif self.__reportOption == self.SingleBuffer:
            tooltip = "Report generated for unsaved file: " + \
                      self.__reportFileName
        else:
            tooltip = ""
        self.updatePylintTooltip.emit(tooltip)
        return

    def showReport(self, lint, reportOption, fileName, uuid):
        " Shows the pylint results "
        self.__removeAll()
        self.__noneLabel.hide()

        self.__report = lint
        self.__reportUUID = uuid
        self.__reportFileName = fileName
        self.__reportOption = reportOption

        showFileName = self.__shouldShowFileName(lint.errorMessages)

        scoreLabel = self.__createScoreLabel(lint.score, lint.previousScore,
                                             showFileName, fileName)
        self.__vLayout.addWidget(scoreLabel)
        self.__widgets.append(scoreLabel)

        if len(lint.errorMessages) > 0:
            self.__addSectionSpacer()
            self.__addErrorsTable(lint.errorMessages, showFileName)

        index = 0
        for similarity in lint.similarities:
            self.__addSectionSpacer()
            self.__addSimilarity(similarity, "Similarity #" + str(index))
            index += 1

        for table in lint.tables:
            self.__addSectionSpacer()
            self.__addGenericTableTitle(table)
            self.__addGenericTable(table)

        self.bodyWidget.show()
        self.bodyWidget.ensureVisible(0, 0, 0, 0)
        self.__reportShown = True
        self.__updateButtonsStatus()
        self.__updateTooltip()

        # It helps, but why do I have flickering?
        QApplication.processEvents()
        self.__resizeBodyFrame()
        return

    def __errorActivated(self, item, column):
        " Handles the double click (or Enter) on the item "

        linePos = str(item.text(1))
        if ":" in linePos:
            parts = linePos.split(":")
            lineNumber = int(parts[0])
            pos = int(parts[1])
        else:
            lineNumber = int(linePos)
            pos = 0

        if self.__reportOption in [
                self.SingleFile, self.DirectoryFiles, self.ProjectFiles
        ]:
            fileName = str(item.text(0))
        else:
            # SingleBuffer
            if self.__reportFileName != "":
                if os.path.isabs(self.__reportFileName):
                    fileName = self.__reportFileName
                else:
                    # Could be unsaved buffer, so try to search by the
                    mainWindow = GlobalData().mainWindow
                    widget = mainWindow.getWidgetByUUID(self.__reportUUID)
                    if widget is None:
                        logging.error("The unsaved buffer has been closed")
                        return
                    # The widget was found, so jump to the required
                    editor = widget.getEditor()
                    editor.gotoLine(lineNumber, pos)
                    editor.setFocus()
                    return

        GlobalData().mainWindow.openFile(fileName, lineNumber, pos)
        return

    def __resizeBodyFrame(self):
        " Resizing the frame to occupy all available width "
        size = self.bodyWidget.maximumViewportSize()
        self.__bodyFrame.setMinimumWidth(size.width() - 16)
        self.__bodyFrame.setMinimumHeight(size.height())
        return

    def showEvent(self, showEv=None):
        " Called when the widget is shown "
        self.__resizeBodyFrame()
        return

    def resizeEvent(self, resizeEv=None):
        " Called when the main window gets resized "
        self.__resizeBodyFrame()
        return

    def onFileUpdated(self, fileName, uuid):
        " Called when a buffer is saved or saved as "

        if not self.__reportShown:
            return
        if self.__reportUUID != uuid:
            return

        # Currently shown report is for the saved buffer
        # File name is expected being absolute
        self.__reportFileName = fileName
        self.updatePylintTooltip.emit("Report generated for buffer saved as " +
                                      fileName)
        return

    def __similarityActivated(self, item, column):
        " Triggered when a similarity is activated "
        fileName = str(item.text(0))
        lineNumber = int(item.text(1))
        GlobalData().mainWindow.openFile(fileName, lineNumber)
        return
Example #20
0
class MainWindow(ui_mainwindow.Ui_MainWindow, QMainWindow):
    """
    Main application window
    """
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setupUi(self)
        self.canvaslayers = []
        self.layerbuttons = []
        self.project = None
        self.selectionbands = defaultdict(partial(QgsRubberBand, self.canvas))
        self.canvas.setCanvasColor(Qt.white)
        self.canvas.enableAntiAliasing(True)
        self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor)
        self.bar = roam.messagebaritems.MessageBar(self.centralwidget)

        self.actionMap.setVisible(False)
        self.actionLegend.setVisible(False)

        pal = QgsPalLabeling()
        self.canvas.mapRenderer().setLabelingEngine(pal)
        self.canvas.setFrameStyle(QFrame.NoFrame)

        self.menuGroup = QActionGroup(self)
        self.menuGroup.setExclusive(True)
        self.menuGroup.addAction(self.actionMap)
        self.menuGroup.addAction(self.actionDataEntry)
        self.menuGroup.addAction(self.actionLegend)
        self.menuGroup.addAction(self.actionProject)
        self.menuGroup.addAction(self.actionSync)
        self.menuGroup.addAction(self.actionSettings)
        self.menuGroup.addAction(self.actionGPS)
        self.menuGroup.triggered.connect(self.updatePage)

        self.editgroup = QActionGroup(self)
        self.editgroup.setExclusive(True)
        self.editgroup.addAction(self.actionPan)
        self.editgroup.addAction(self.actionZoom_In)
        self.editgroup.addAction(self.actionZoom_Out)
        self.editgroup.addAction(self.actionInfo)

        self.actionLegend.triggered.connect(self.updatelegend)

        self.actionGPS = GPSAction(":/icons/gps", self.canvas, self)
        self.projecttoolbar.addAction(self.actionGPS)

        self.projectwidget.requestOpenProject.connect(self.loadProject)
        QgsProject.instance().readProject.connect(self._readProject)

        self.gpswidget.setgps(GPS)

        self.actionSettings.toggled.connect(
            self.settingswidget.populateControls)
        self.actionSettings.toggled.connect(self.settingswidget.readSettings)
        self.settingswidget.settingsupdated.connect(self.settingsupdated)

        self.dataentrywidget = DataEntryWidget(self.canvas, self.bar)
        self.widgetpage.layout().addWidget(self.dataentrywidget)

        self.dataentrywidget.rejected.connect(self.formrejected)
        self.dataentrywidget.featuresaved.connect(self.featureSaved)
        self.dataentrywidget.featuredeleted.connect(self.featuredeleted)
        self.dataentrywidget.failedsave.connect(self.failSave)
        self.dataentrywidget.helprequest.connect(self.showhelp)

        def createSpacer(width=0, height=0):
            widget = QWidget()
            widget.setMinimumWidth(width)
            widget.setMinimumHeight(height)
            return widget

        gpsspacewidget = createSpacer(30)
        sidespacewidget = createSpacer(30)
        sidespacewidget2 = createSpacer(height=20)

        sidespacewidget.setSizePolicy(QSizePolicy.Expanding,
                                      QSizePolicy.Expanding)
        sidespacewidget2.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        gpsspacewidget.setSizePolicy(QSizePolicy.Expanding,
                                     QSizePolicy.Expanding)

        self.topspaceraction = self.projecttoolbar.insertWidget(
            self.actionGPS, gpsspacewidget)

        def createlabel(text):
            style = """
                QLabel {
                        color: #706565;
                        font: 14px "Calibri" ;
                        }"""
            label = QLabel(text)
            label.setStyleSheet(style)

            return label

        self.projectlabel = createlabel("Project: {project}")
        self.userlabel = createlabel(
            "User: {user}".format(user=getpass.getuser()))
        self.positionlabel = createlabel('')
        self.gpslabel = createlabel("GPS: Not active")
        self.statusbar.addWidget(self.projectlabel)
        self.statusbar.addWidget(self.userlabel)
        spacer = createSpacer()
        spacer2 = createSpacer()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        spacer2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        self.statusbar.addWidget(spacer)
        self.statusbar.addWidget(self.positionlabel)
        self.statusbar.addWidget(spacer2)
        self.statusbar.addWidget(self.gpslabel)

        self.menutoolbar.insertWidget(self.actionQuit, sidespacewidget2)
        self.menutoolbar.insertWidget(self.actionProject, sidespacewidget)

        self.panels = []

        self.connectButtons()

        self.currentfeatureband = QgsRubberBand(self.canvas)
        self.currentfeatureband.setIconSize(20)
        self.currentfeatureband.setWidth(10)
        self.currentfeatureband.setColor(QColor(186, 93, 212, 76))

        self.canvas_page.layout().insertWidget(0, self.projecttoolbar)
        self.dataentryselection = QAction(self.projecttoolbar)
        self.dataentryaction = self.projecttoolbar.insertAction(
            self.topspaceraction, self.dataentryselection)
        self.dataentryselection.triggered.connect(self.selectdataentry)

        self.centralwidget.layout().addWidget(self.statusbar)

        self.actionGPSFeature.setProperty('dataentry', True)

        self.infodock = InfoDock(self.canvas)
        self.infodock.featureupdated.connect(self.highlightfeature)
        self.infodock.hide()
        self.hidedataentry()
        self.canvas.extentsChanged.connect(self.updatestatuslabel)

        RoamEvents.openimage.connect(self.openimage)
        RoamEvents.openurl.connect(self.viewurl)
        RoamEvents.openfeatureform.connect(self.openForm)
        RoamEvents.openkeyboard.connect(self.openkeyboard)
        RoamEvents.selectioncleared.connect(self.clearselection)
        RoamEvents.editgeometry.connect(self.addforedit)
        RoamEvents.editgeometry_complete.connect(self.on_geometryedit)
        RoamEvents.onShowMessage.connect(self.showUIMessage)
        RoamEvents.selectionchanged.connect(self.highlightselection)
        RoamEvents.selectionchanged.connect(self.showInfoResults)

        GPS.gpspostion.connect(self.updatecanvasfromgps)
        GPS.firstfix.connect(self.gpsfirstfix)
        GPS.gpsdisconnected.connect(self.gpsdisconnected)

        self.lastgpsposition = None
        self.marker = GPSMarker(self.canvas)
        self.marker.hide()

        self.legendpage.showmap.connect(self.showmap)

        self.editfeaturestack = []
        self.currentselection = {}

    def showUIMessage(self,
                      label,
                      message,
                      level=QgsMessageBar.INFO,
                      time=0,
                      extra=''):
        self.bar.pushMessage(label,
                             message,
                             level,
                             duration=time,
                             extrainfo=extra)

    def addforedit(self, form, feature):
        self.editfeaturestack.append((form, feature))
        self.loadform(form)
        actions = self.getcaptureactions()
        for action in actions:
            if action.isdefault:
                action.trigger()
                break

    def updatelegend(self):
        self.legendpage.updatecanvas(self.canvas)

    def gpsfirstfix(self, postion, gpsinfo):
        zoomtolocation = roam.config.settings.get('gpszoomonfix', True)
        if zoomtolocation:
            self.canvas.zoomScale(1000)

    def updatecanvasfromgps(self, position, gpsinfo):
        # Recenter map if we go outside of the 95% of the area
        if not self.lastgpsposition == position:
            self.lastposition = position
            rect = QgsRectangle(position, position)
            extentlimt = QgsRectangle(self.canvas.extent())
            extentlimt.scale(0.95)

            if not extentlimt.contains(position):
                self.canvas.setExtent(rect)
                self.canvas.refresh()

        self.marker.show()
        self.marker.setCenter(position)
        self.gpslabel.setText("GPS: PDOP {}   HDOP {}    VDOP {}".format(
            gpsinfo.pdop, gpsinfo.hdop, gpsinfo.vdop))

    def gpsdisconnected(self):
        self.marker.hide()
        self.gpslabel.setText("GPS Not Active")

    def openkeyboard(self):
        if not roam.config.settings.get('keyboard', True):
            return

        if sys.platform == 'win32':
            try:
                programfiles = os.environ['ProgramW6432']
            except KeyError:
                programfiles = os.environ['ProgramFiles']

            cmd = r'{path}\Common Files\Microsoft Shared\ink\TabTip.exe'.format(
                path=programfiles)
            try:
                os.startfile(cmd)
            except WindowsError:
                roam.config.settings['keyboard'] = False
                roam.config.save()
        else:
            cmd = 'onboard'
            Popen(cmd)

    def selectdataentry(self):
        forms = self.project.forms
        formpicker = PickActionDialog(msg="Select data entry form")
        for form in forms:
            action = form.createuiaction()
            valid, failreasons = form.valid
            if not valid:
                roam.utils.warning("Form {} failed to load".format(form.label))
                roam.utils.warning("Reasons {}".format(failreasons))
                action.triggered.connect(partial(self.showformerror, form))
            else:
                action.triggered.connect(partial(self.loadform, form))
            formpicker.addAction(action)

        formpicker.exec_()

    def showformerror(self, form):
        pass

    def viewurl(self, url):
        """
        Open a URL in Roam
        :param url:
        :return:
        """
        key = url.toString().lstrip('file://')
        try:
            # Hack. Eww fix me.
            data, imagetype = roam.htmlviewer.images[os.path.basename(key)]
            pix = QPixmap()
            if imagetype == 'base64':
                pix.loadFromData(data)
            else:
                pix.load(data)
            self.openimage(pix)
        except KeyError:
            pix = QPixmap()
            pix.load(key)
            if pix.isNull():
                QDesktopServices.openUrl(url)
                return
            self.openimage(pix)

    def openimage(self, pixmap):
        viewer = ImageViewer(self.stackedWidget)
        viewer.resize(self.stackedWidget.size())
        viewer.openimage(pixmap)

    def settingsupdated(self, settings):
        self.show()
        self.actionGPS.updateGPSPort()

    def updatestatuslabel(self):
        extent = self.canvas.extent()
        self.positionlabel.setText("Map Center: {}".format(
            extent.center().toString()))

    def on_geometryedit(self, form, feature):
        layer = form.QGISLayer
        self.reloadselection(layer, updated=[feature])

        self.currentfeatureband.setToGeometry(feature.geometry(), layer)

    def reloadselection(self, layer, deleted=[], updated=[]):
        """
        Reload the selection after features have been updated or deleted.
        :param layer:
        :param deleted:
        :param updated:
        :return:
        """
        selectedfeatures = self.currentselection[layer]

        # Update any features that have changed.
        for updatedfeature in updated:
            oldfeatures = [
                f for f in selectedfeatures if f.id() == updatedfeature.id()
            ]
            for feature in oldfeatures:
                self.currentselection[layer].remove(feature)
                self.currentselection[layer].append(updatedfeature)

        # Delete any old ones
        for deletedid in deleted:
            oldfeatures = [f for f in selectedfeatures if f.id() == deletedid]
            for feature in oldfeatures:
                self.currentselection[layer].remove(feature)

        RoamEvents.selectionchanged.emit(self.currentselection)

    def highlightselection(self, results):
        self.clearselection()
        for layer, features in results.iteritems():
            band = self.selectionbands[layer]
            band.setColor(QColor(255, 0, 0, 200))
            band.setIconSize(20)
            band.setWidth(2)
            band.setBrushStyle(Qt.NoBrush)
            band.reset(layer.geometryType())
            for feature in features:
                band.addGeometry(feature.geometry(), layer)

    def clearselection(self):
        # Clear the main selection rubber band
        self.currentfeatureband.reset()
        # Clear the rest
        for band in self.selectionbands.itervalues():
            band.reset()

        self.editfeaturestack = []

    def highlightfeature(self, layer, feature, features):
        self.clearselection()
        self.highlightselection({layer: features})
        self.currentfeatureband.setToGeometry(feature.geometry(), layer)

    def showmap(self):
        self.actionMap.setVisible(True)
        self.actionLegend.setVisible(True)
        self.actionMap.trigger()

    def hidedataentry(self):
        self.actionDataEntry.setVisible(False)

    def showdataentry(self):
        self.actionDataEntry.setVisible(True)
        self.actionDataEntry.trigger()

    def dataentrychanged(self, index):
        self.clearCapatureTools()

        if not index.isValid():
            return

        modelindex = index
        # modelindex = self.dataentrymodel.index(index, 0)
        form = modelindex.data(Qt.UserRole + 1)
        self.dataentryselection.setCurrentIndex(index.row())
        self.createCaptureButtons(form)

    def raiseerror(self, *exinfo):
        info = traceback.format_exception(*exinfo)
        item = self.bar.pushError(
            QApplication.translate(
                'MainWindowPy',
                'Seems something has gone wrong. Press for more details', None,
                QApplication.UnicodeUTF8), info)

    def setMapTool(self, tool, *args):
        self.canvas.setMapTool(tool)

    def homeview(self):
        """
        Zoom the mapview canvas to the extents the project was opened at i.e. the
        default extent.
        """
        self.canvas.setExtent(self.defaultextent)
        self.canvas.refresh()

    def connectButtons(self):
        def connectAction(action, tool):
            action.toggled.connect(partial(self.setMapTool, tool))

        def cursor(name):
            pix = QPixmap(name)
            pix = pix.scaled(QSize(24, 24))
            return QCursor(pix)

        self.zoomInTool = QgsMapToolZoom(self.canvas, False)
        self.zoomOutTool = QgsMapToolZoom(self.canvas, True)
        self.panTool = PanTool(self.canvas)
        self.infoTool = InfoTool(self.canvas)

        connectAction(self.actionZoom_In, self.zoomInTool)
        connectAction(self.actionZoom_Out, self.zoomOutTool)
        connectAction(self.actionPan, self.panTool)
        connectAction(self.actionInfo, self.infoTool)

        self.zoomInTool.setCursor(cursor(':/icons/in'))
        self.zoomOutTool.setCursor(cursor(':/icons/out'))
        self.infoTool.setCursor(cursor(':/icons/info'))

        self.actionRaster.triggered.connect(self.toggleRasterLayers)

        self.infoTool.infoResults.connect(RoamEvents.selectionchanged.emit)

        self.actionHome.triggered.connect(self.homeview)
        self.actionQuit.triggered.connect(self.exit)

    def getcaptureactions(self):
        for action in self.projecttoolbar.actions():
            if action.property('dataentry'):
                yield action

    def clearCapatureTools(self):
        captureselected = False
        for action in self.projecttoolbar.actions():
            if action.objectName() == "capture" and action.isChecked():
                captureselected = True

            if action.property('dataentry'):
                self.projecttoolbar.removeAction(action)
        return captureselected

    def createCaptureButtons(self, form):
        tool = form.getMaptool()(self.canvas)
        for action in tool.actions:
            # Create the action here.
            if action.ismaptool:
                action.toggled.connect(partial(self.setMapTool, tool))

            # Set the action as a data entry button so we can remove it later.
            action.setProperty("dataentry", True)
            self.editgroup.addAction(action)
            self.layerbuttons.append(action)
            self.projecttoolbar.insertAction(self.topspaceraction, action)
            action.setChecked(action.isdefault)

        if hasattr(tool, 'geometryComplete'):
            add = partial(self.addNewFeature, form)
            tool.geometryComplete.connect(add)
        else:
            tool.finished.connect(self.openForm)
            tool.error.connect(partial(self.showUIMessage, form.label))

    def loadform(self, form):
        self.clearCapatureTools()
        self.dataentryselection.setIcon(QIcon(form.icon))
        self.dataentryselection.setText(form.icontext)
        self.createCaptureButtons(form)

    def clearToolRubberBand(self):
        """
        Clear the rubber band of the active tool if it has one
        """
        tool = self.canvas.mapTool()
        try:
            tool.clearBand()
        except AttributeError:
            # No clearBand method found, but that's cool.
            pass

    def showhelp(self, url):
        help = HelpPage(self.stackedWidget)
        help.setHelpPage(url)
        help.show()

    def dataentryfinished(self):
        self.hidedataentry()
        self.showmap()
        self.cleartempobjects()
        self.infodock.refreshcurrent()

    def featuredeleted(self, layer, featureid):
        self.dataentryfinished()
        self.reloadselection(layer, deleted=[featureid])
        self.canvas.refresh()

    def featureSaved(self):
        self.dataentryfinished()
        self.canvas.refresh()

    def failSave(self, messages):
        self.bar.pushError("Error when saving changes.", messages)

    def cleartempobjects(self):
        self.currentfeatureband.reset()
        self.clearToolRubberBand()

    def formrejected(self, message, level):
        self.dataentryfinished()
        if message:
            RoamEvents.raisemessage("Form Message", message, level, duration=2)

        self.cleartempobjects()

    def openForm(self, form, feature, editmode):
        """
        Open the form that is assigned to the layer
        """
        self.currentfeatureband.setToGeometry(feature.geometry(),
                                              form.QGISLayer)
        self.showdataentry()
        self.dataentrywidget.openform(feature=feature,
                                      form=form,
                                      project=self.project,
                                      editmode=editmode)

    def editfeaturegeometry(self, form, feature, newgeometry):
        layer = form.QGISLayer
        layer.startEditing()
        feature.setGeometry(newgeometry)
        layer.updateFeature(feature)
        saved = layer.commitChanges()
        map(roam.utils.error, layer.commitErrors())
        self.canvas.refresh()
        RoamEvents.editgeometry_complete.emit(form, feature)

    def addNewFeature(self, form, geometry):
        """
        Add a new new feature to the given layer
        """
        layer = form.QGISLayer
        if layer.geometryType() in [
                QGis.WKBMultiLineString, QGis.WKBMultiPoint,
                QGis.WKBMultiPolygon
        ]:
            geometry.convertToMultiType()

        try:
            form, feature = self.editfeaturestack.pop()
            self.editfeaturegeometry(form, feature, newgeometry=geometry)
            return
        except IndexError:
            pass

        layer = form.QGISLayer
        fields = layer.pendingFields()

        feature = QgsFeature(fields)
        feature.setGeometry(geometry)

        for index in xrange(fields.count()):
            pkindexes = layer.dataProvider().pkAttributeIndexes()
            if index in pkindexes and layer.dataProvider().name(
            ) == 'spatialite':
                continue

            value = layer.dataProvider().defaultValue(index)
            feature[index] = value

        self.openForm(form, feature, editmode=False)

    def exit(self):
        """
        Exit the application.
        """
        QApplication.exit(0)

    def showInfoResults(self, results):
        forms = {}
        for layer in results.keys():
            layername = layer.name()
            if not layername in forms:
                forms[layername] = list(self.project.formsforlayer(layername))

        self.currentselection = results
        self.infodock.setResults(results, forms)
        self.infodock.show()

    def toggleRasterLayers(self):
        """
        Toggle all raster layers on or off.
        """
        if not self.canvaslayers:
            return

        #Freeze the canvas to save on UI refresh
        self.canvas.freeze()
        for layer in self.canvaslayers:
            if layer.layer().type() == QgsMapLayer.RasterLayer:
                layer.setVisible(not layer.isVisible())
            # Really!? We have to reload the whole layer set every time?
        # WAT?
        self.canvas.setLayerSet(self.canvaslayers)
        self.canvas.freeze(False)
        self.canvas.refresh()

    def missingLayers(self, layers):
        """
        Called when layers have failed to load from the current project
        """
        roam.utils.warning("Missing layers")
        map(roam.utils.warning, layers)

        missinglayers = roam.messagebaritems.MissingLayerItem(layers,
                                                              parent=self.bar)
        self.bar.pushItem(missinglayers)

    def loadprojects(self, projects):
        """
        Load the given projects into the project
        list
        """
        projects = list(projects)
        self.projectwidget.loadProjectList(projects)
        self.syncwidget.loadprojects(projects)

    def updatePage(self, action):
        """
        Update the current stack page based on the current selected
        action
        """
        page = action.property("page")
        self.stackedWidget.setCurrentIndex(page)

    def show(self):
        """
        Override show method. Handles showing the app in fullscreen
        mode or just maximized
        """
        fullscreen = roam.config.settings.get("fullscreen", False)
        if fullscreen:
            self.showFullScreen()
        else:
            self.showMaximized()

    def viewprojects(self):
        self.stackedWidget.setCurrentIndex(1)

    @roam.utils.timeit
    def _readProject(self, doc):
        """
        readProject is called by QgsProject once the map layer has been
        populated with all the layers
        """
        parser = ProjectParser(doc)
        canvasnode = parser.canvasnode
        self.canvas.freeze()
        self.canvas.mapRenderer().readXML(canvasnode)
        self.canvaslayers = parser.canvaslayers()
        self.canvas.setLayerSet(self.canvaslayers)
        #red = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorRedPart", 255 )[0];
        #green = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorGreenPart", 255 )[0];
        #blue = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorBluePart", 255 )[0];
        #color = QColor(red, green, blue);
        #self.canvas.setCanvasColor(color)
        self.canvas.updateScale()
        self.projectOpened()
        self.canvas.freeze(False)
        self.canvas.refresh()
        GPS.crs = self.canvas.mapRenderer().destinationCrs()
        self.showmap()

    @roam.utils.timeit
    def projectOpened(self):
        """
            Called when a new project is opened in QGIS.
        """
        projectpath = QgsProject.instance().fileName()
        self.project = Project.from_folder(os.path.dirname(projectpath))
        self.projectlabel.setText("Project: {}".format(self.project.name))
        try:
            firstform = self.project.forms[0]
            self.loadform(self.project.forms[0])
            self.dataentryselection.setVisible(True)
        except IndexError:
            self.dataentryselection.setVisible(False)

        # Enable the raster layers button only if the project contains a raster layer.
        layers = QgsMapLayerRegistry.instance().mapLayers().values()
        hasrasters = any(layer.type() == QgsMapLayer.RasterLayer
                         for layer in layers)
        self.actionRaster.setEnabled(hasrasters)
        self.defaultextent = self.canvas.extent()
        roam.utils.info("Extent: {}".format(self.defaultextent.toString()))

        # Show panels
        for panel in self.project.getPanels():
            self.mainwindow.addDockWidget(Qt.BottomDockWidgetArea, panel)
            self.panels.append(panel)

        self.infoTool.selectionlayers = self.project.selectlayersmapping()
        layers = self.project.legendlayersmapping().values()
        self.legendpage.updateitems(layers)
        self.actionPan.trigger()

    #noinspection PyArgumentList
    @roam.utils.timeit
    def loadProject(self, project):
        """
        Load a project into the application .
        """
        roam.utils.log(project)
        roam.utils.log(project.name)
        roam.utils.log(project.projectfile)
        roam.utils.log(project.valid)

        (passed, message) = project.onProjectLoad()

        if not passed:
            self.bar.pushMessage("Project load rejected",
                                 "Sorry this project couldn't"
                                 "be loaded.  Click for me details.",
                                 QgsMessageBar.WARNING,
                                 extrainfo=message)
            return

        self.actionMap.trigger()

        self.closeProject()

        self.canvas.refresh()
        self.canvas.repaint()

        RoamEvents.selectioncleared.emit()

        # No idea why we have to set this each time.  Maybe QGIS deletes it for
        # some reason.
        self.badLayerHandler = BadLayerHandler(callback=self.missingLayers)
        QgsProject.instance().setBadLayerHandler(self.badLayerHandler)

        self.stackedWidget.setCurrentIndex(3)
        self.projectloading_label.setText("Project {} Loading".format(
            project.name))
        pixmap = QPixmap(project.splash)
        w = self.projectimage.width()
        h = self.projectimage.height()
        self.projectimage.setPixmap(pixmap.scaled(w, h, Qt.KeepAspectRatio))
        QApplication.processEvents()

        QDir.setCurrent(os.path.dirname(project.projectfile))
        fileinfo = QFileInfo(project.projectfile)
        QgsProject.instance().read(fileinfo)

    def closeProject(self):
        """
        Close the current open project
        """
        self.clearCapatureTools()
        self.canvas.freeze()
        QgsMapLayerRegistry.instance().removeAllMapLayers()
        self.canvas.clear()
        self.canvas.freeze(False)
        for panel in self.panels:
            self.removeDockWidget(panel)
            del panel
            # Remove all the old buttons
        for action in self.layerbuttons:
            self.editgroup.removeAction(action)

        self.panels = []
        self.project = None
        self.dataentrywidget.clear()
        self.hidedataentry()
        self.infodock.close()
Example #21
0
class DockTitleBar(QToolBar):
    def __init__(self, parent):
        QToolBar.__init__(self, parent)
        assert parent
        self.dock = parent

        # a fake spacer widget
        w = QWidget(self)
        l = QHBoxLayout(w)
        l.setMargin(0)
        l.setSpacing(0)
        l.addStretch()

        frame = QFrame()
        layout = QBoxLayout(QBoxLayout.LeftToRight, frame)
        layout.setContentsMargins(4, 4, 0, 0)
        layout.setSpacing(2)
        self.aDockFrame = self.addWidget(frame)

        self.__icon = QLabel()

        layout.addWidget(self.__icon)
        layout.addWidget(QLabel(self.dock.windowTitle()))

        self.dock.windowIconChanged.connect(self.__setWindowIcon)

        # fake spacer item
        spacer = QWidgetAction(self)
        spacer.setDefaultWidget(w)

        self.setMovable(False)
        self.setFloatable(False)
        self.setIconSize(QSize(12, 12))

        self.aFloat = QAction(self)
        self.aClose = QAction(self)

        QToolBar.addAction(self, spacer)
        self.separator = QToolBar.addSeparator(self)
        QToolBar.addAction(self, self.aFloat)
        QToolBar.addAction(self, self.aClose)

        self.updateStandardIcons()

        self.dockWidgetFeaturesChanged(self.dock.features())

        self.dock.featuresChanged.connect(self.dockWidgetFeaturesChanged)
        self.aFloat.triggered.connect(self._floatTriggered)
        self.aClose.triggered.connect(self.dock.close)

    def addAction(self, action):
        self.insertAction(self.separator, action)
        return action

    def __setWindowIcon(self, icon):
        if not icon.isNull():
            self.__icon.setPixmap(self.dock.windowIcon().pixmap(16))

    def __setWindowTitle(self, title):
        self.__title.setText(title)

    def paintEvent(self, _):
        painter = QStylePainter(self)
        options = QStyleOptionToolBar()

        # init style options
        options.initFrom(self.dock)
        options.rect = self.rect()
        textRect = self.rect().adjusted(3, 3, 0, 0)
        msh = self.minimumSizeHint()

        # need to rotate if vertical state
        if self.dock.features() & QDockWidget.DockWidgetVerticalTitleBar:
            painter.rotate(-90)
            painter.translate(QPointF(-self.rect().height(), 0))
            self.transposeSize(options.rect)
            self.transposeSize(textRect)
            msh.transpose()

        # draw toolbar
        painter.drawControl(QStyle.CE_ToolBar, options)

        # restore rotation
        if self.dock.features() & QDockWidget.DockWidgetVerticalTitleBar:
            painter.rotate(90)

    @staticmethod
    def transposeSize(rect):
        size = rect.size()
        size.transpose()
        rect.setSize(size)

    def updateStandardIcons(self):
        size = QSize(16, 16)
        rect = QRect(QPoint(), self.iconSize())

        transform = QTransform()
        transform.rotate(90)

        pixmap = self.style().standardIcon(QStyle.SP_TitleBarNormalButton, None, self.widgetForAction(self.aFloat)).pixmap(size)
        rect.moveCenter(pixmap.rect().center())
        pixmap = pixmap.copy(rect)
        self.aFloat.setIcon(QIcon(pixmap))

        pixmap = self.style().standardIcon(QStyle.SP_TitleBarCloseButton, None, self.widgetForAction(self.aClose)).pixmap(size)
        rect.moveCenter(pixmap.rect().center())
        pixmap = pixmap.copy(rect)
        self.aClose.setIcon(QIcon(pixmap))

    def event(self, event):
        if event.type() == QEvent.StyleChange:
            self.updateStandardIcons()
        return QToolBar.event(self, event)

    def minimumSizeHint(self):
        return QToolBar.sizeHint(self)

    def sizeHint(self):
        size = QToolBar.sizeHint(self)
        fm = QFontMetrics(self.font())

        if self.dock.features() & QDockWidget.DockWidgetVerticalTitleBar:
            size.setHeight(size.height() + fm.width(self.dock.windowTitle()))
        else:
            size.setWidth(size.width() + fm.width(self.dock.windowTitle()))

        return size

    def _floatTriggered(self):
        self.dock.setFloating(not self.dock.isFloating())

    def dockWidgetFeaturesChanged(self, features):
        self.aFloat.setVisible(features & QDockWidget.DockWidgetFloatable)
        self.aClose.setVisible(features & QDockWidget.DockWidgetClosable)

        # update toolbar orientation
        if features & QDockWidget.DockWidgetVerticalTitleBar:
            if self.orientation() & Qt.Vertical:
                return
            self.setOrientation(Qt.Vertical)
        else:
            if self.orientation() & Qt.Horizontal:
                return
            self.setOrientation(Qt.Horizontal)

        # reorder the actions
        items = self.actions()
        items.reverse()
        self.clear()
        self.addActions(items)
Example #22
0
class RunConsoleTabWidget( QWidget, MainWindowTabWidgetBase ):
    " IO console tab widget "

    textEditorZoom = pyqtSignal( int )
    settingUpdated = pyqtSignal()

    def __init__( self, threadID, parent = None ):

        MainWindowTabWidgetBase.__init__( self )
        QWidget.__init__( self, parent )

        self.__viewer = RedirectedIOConsole( self )
        self.connect( self.__viewer, SIGNAL( 'UserInput' ), self.__onUserInput )

        self.__threadID = threadID
        self.__showstdin = True
        self.__showstdout = True
        self.__showstderr = True

        self.__createLayout()
        return

    def threadID( self ):
        " Provides the thread ID the console linked to "
        return self.__threadID

    def __onUserInput( self, userInput ):
        " Triggered when the user finished input in the redirected IO console "
        self.emit( SIGNAL( 'UserInput' ), self.__threadID, userInput )
        self.__clearButton.setEnabled( True )
        return

    def __createLayout( self ):
        " Creates the toolbar and layout "

        # Buttons
        self.__printButton = QAction( PixmapCache().getIcon( 'printer.png' ),
                                      'Print', self )
        self.__printButton.triggered.connect( self.__onPrint )
        self.__printButton.setEnabled( False )
        self.__printButton.setVisible( False )

        self.__printPreviewButton = QAction(
                                PixmapCache().getIcon( 'printpreview.png' ),
                                'Print preview', self )
        self.__printPreviewButton.triggered.connect( self.__onPrintPreview )
        self.__printPreviewButton.setEnabled( False )
        self.__printPreviewButton.setVisible( False )

        # self.__sendUpButton = QAction( PixmapCache().getIcon('sendioup.png'),
        #                                'Send to Main Editing Area', self )
        # self.__sendUpButton.triggered.connect( self.__sendUp )

        self.__filterMenu = QMenu( self )
        self.__filterMenu.aboutToShow.connect( self.__filterAboutToShow )
        self.__filterGroup = QActionGroup( self )
        self.__filterShowAllAct = self.__filterMenu.addAction( "Show all" )
        self.__filterShowAllAct.setCheckable( True )
        self.__filterShowAllAct.setActionGroup( self.__filterGroup )
        self.__filterShowAllAct.triggered.connect( self.__onFilterShowAll )
        self.__filterShowStdoutAct = self.__filterMenu.addAction(
                                                "Show stdin and stdout" )
        self.__filterShowStdoutAct.setCheckable( True )
        self.__filterShowStdoutAct.setActionGroup( self.__filterGroup )
        self.__filterShowStdoutAct.triggered.connect( self.__onFilterShowStdout )
        self.__filterShowStderrAct = self.__filterMenu.addAction(
                                                "Show stdin and stderr" )
        self.__filterShowStderrAct.setCheckable( True )
        self.__filterShowStderrAct.setActionGroup( self.__filterGroup )
        self.__filterShowStderrAct.triggered.connect( self.__onFilterShowStderr )
        self.__filterButton = QToolButton( self )
        self.__filterButton.setIcon( PixmapCache().getIcon( 'iofilter.png' ) )
        self.__filterButton.setToolTip( 'Filtering settings' )
        self.__filterButton.setPopupMode( QToolButton.InstantPopup )
        self.__filterButton.setMenu( self.__filterMenu )
        self.__filterButton.setFocusPolicy( Qt.NoFocus )

        self.__settingsMenu = QMenu( self )
        self.__settingsMenu.aboutToShow.connect( self.__settingsAboutToShow )
        self.__wrapLongLinesAct = self.__settingsMenu.addAction(
                                                "Wrap long lines" )
        self.__wrapLongLinesAct.setCheckable( True )
        self.__wrapLongLinesAct.triggered.connect( self.__onWrapLongLines )
        self.__showEOLAct = self.__settingsMenu.addAction( "Show EOL" )
        self.__showEOLAct.setCheckable( True )
        self.__showEOLAct.triggered.connect( self.__onShowEOL )
        self.__showWhitespacesAct = self.__settingsMenu.addAction(
                                                "Show whitespaces" )
        self.__showWhitespacesAct.setCheckable( True )
        self.__showWhitespacesAct.triggered.connect( self.__onShowWhitespaces )
        self.__autoscrollAct = self.__settingsMenu.addAction( "Autoscroll" )
        self.__autoscrollAct.setCheckable( True )
        self.__autoscrollAct.triggered.connect( self.__onAutoscroll )
        self.__showMarginAct = self.__settingsMenu.addAction(
                                                "Show timestamp margin" )
        self.__showMarginAct.setCheckable( True )
        self.__showMarginAct.triggered.connect( self.__onShowMargin )

        self.__settingsButton = QToolButton( self )
        self.__settingsButton.setIcon(
                            PixmapCache().getIcon( 'iosettings.png' ) )
        self.__settingsButton.setToolTip( 'View settings' )
        self.__settingsButton.setPopupMode( QToolButton.InstantPopup )
        self.__settingsButton.setMenu( self.__settingsMenu )
        self.__settingsButton.setFocusPolicy( Qt.NoFocus )

        fixedSpacer = QWidget()
        fixedSpacer.setFixedHeight( 8 )

        self.__stopButton = QAction( PixmapCache().getIcon( 'runconsolestop.png' ),
                                     'Stop process', self )
        self.__stopButton.triggered.connect( self.stop )

        self.__stopAndCloseButton = QAction( PixmapCache().getIcon( 'runconsolestopclose.png' ),
                                             'Stop process and close tab', self )
        self.__stopAndCloseButton.triggered.connect( self.stopAndClose )

        spacer = QWidget()
        spacer.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding )
        self.__clearButton = QAction( PixmapCache().getIcon( 'trash.png' ),
                                      'Clear', self )
        self.__clearButton.triggered.connect( self.clear )

        # The toolbar
        toolbar = QToolBar( self )
        toolbar.setOrientation( Qt.Vertical )
        toolbar.setMovable( False )
        toolbar.setAllowedAreas( Qt.RightToolBarArea )
        toolbar.setIconSize( QSize( 16, 16 ) )
        toolbar.setFixedWidth( 28 )
        toolbar.setContentsMargins( 0, 0, 0, 0 )

        # toolbar.addAction( self.__sendUpButton )
        toolbar.addAction( self.__printPreviewButton )
        toolbar.addAction( self.__printButton )
        toolbar.addWidget( self.__filterButton )
        toolbar.addWidget( self.__settingsButton )
        toolbar.addWidget( fixedSpacer )
        toolbar.addAction( self.__stopButton )
        toolbar.addAction( self.__stopAndCloseButton )
        toolbar.addWidget( spacer )
        toolbar.addAction( self.__clearButton )

        hLayout = QHBoxLayout()
        hLayout.setContentsMargins( 0, 0, 0, 0 )
        hLayout.setSpacing( 0 )
        hLayout.addWidget( toolbar )
        hLayout.addWidget( self.__viewer )

        self.setLayout( hLayout )
        return

    def onZoomReset( self ):
        " Triggered when the zoom reset button is pressed "
        if self.__viewer.zoom != 0:
            self.textEditorZoom.emit( 0 )
        return True

    def onZoomIn( self ):
        " Triggered when the zoom in button is pressed "
        if self.__viewer.zoom < 20:
            self.textEditorZoom.emit( self.__viewer.zoom + 1 )
        return True

    def onZoomOut( self ):
        " Triggered when the zoom out button is pressed "
        if self.__viewer.zoom > -10:
            self.textEditorZoom.emit( self.__viewer.zoom - 1 )
        return True

    def __onPrint( self ):
        " Triggered when the print button is pressed "
        pass

    def __onPrintPreview( self ):
        " triggered when the print preview button is pressed "
        pass

    def setFocus( self ):
        " Overridden setFocus "
        self.__viewer.setFocus()
        return

    def onOpenImport( self ):
        " Triggered when Ctrl+I is received "
        return True

    def __sendUp( self ):
        " Triggered when requested to move the console up "
        return

    def __filterAboutToShow( self ):
        " Triggered when filter menu is about to show "
        showAll = self.__showstdin and \
                  self.__showstdout and \
                  self.__showstderr
        onlyStdout = self.__showstdin and \
                     self.__showstdout and \
                     not self.__showstderr
        onlyStderr = self.__showstdin and \
                     not self.__showstdout and \
                     self.__showstderr
        self.__filterShowAllAct.setChecked( showAll )
        self.__filterShowStdoutAct.setChecked( onlyStdout )
        self.__filterShowStderrAct.setChecked( onlyStderr )
        return

    def __onFilterShowAll( self ):
        " Triggered when filter show all is clicked "
        if self.__showstdin == True and \
           self.__showstdout == True and \
           self.__showstderr == True:
            return

        self.__showstdin = True
        self.__showstdout = True
        self.__showstderr = True
        self.__viewer.renderContent()
        return

    def __onFilterShowStdout( self ):
        " Triggered when filter show stdout only is clicked "
        if self.__showstdin == True and \
           self.__showstdout == True and \
           self.__showstderr == False:
            return

        self.__showstdin = True
        self.__showstdout = True
        self.__showstderr = False
        self.__viewer.renderContent()
        return

    def __onFilterShowStderr( self ):
        " Triggered when filter show stderr only is clicked "
        if self.__showstdin == True and \
           self.__showstdout == False and \
           self.__showstderr == True:
            return

        self.__showstdin = True
        self.__showstdout = False
        self.__showstderr = True
        self.__viewer.renderContent()
        return

    def __settingsAboutToShow( self ):
        " Settings menu is about to show "
        self.__wrapLongLinesAct.setChecked( Settings().ioconsolelinewrap )
        self.__showEOLAct.setChecked( Settings().ioconsoleshoweol )
        self.__showWhitespacesAct.setChecked( Settings().ioconsoleshowspaces )
        self.__autoscrollAct.setChecked( Settings().ioconsoleautoscroll )
        self.__showMarginAct.setChecked( Settings().ioconsoleshowmargin )
        return

    def __onWrapLongLines( self ):
        " Triggered when long lines setting is changed "
        Settings().ioconsolelinewrap = not Settings().ioconsolelinewrap
        self.settingUpdated.emit()
        return

    def __onShowEOL( self ):
        " Triggered when show EOL is changed "
        Settings().ioconsoleshoweol = not Settings().ioconsoleshoweol
        self.settingUpdated.emit()
        return

    def __onShowWhitespaces( self ):
        " Triggered when show whitespaces is changed "
        Settings().ioconsoleshowspaces = not Settings().ioconsoleshowspaces
        self.settingUpdated.emit()
        return

    def __onAutoscroll( self ):
        " Triggered when autoscroll is changed "
        Settings().ioconsoleautoscroll = not Settings().ioconsoleautoscroll
        return

    def __onShowMargin( self ):
        " Triggered when show margin is changed "
        Settings().ioconsoleshowmargin = not Settings().ioconsoleshowmargin
        self.settingUpdated.emit()
        return

    def clear( self ):
        " Triggered when requested to clear the console "
        self.__viewer.clearAll()
        return

    def consoleSettingsUpdated( self ):
        " Triggered when one of the consoles updated a common setting "
        if Settings().ioconsolelinewrap:
            self.__viewer.setWrapMode( QsciScintilla.WrapWord )
        else:
            self.__viewer.setWrapMode( QsciScintilla.WrapNone )
        self.__viewer.setEolVisibility( Settings().ioconsoleshoweol )
        if Settings().ioconsoleshowspaces:
            self.__viewer.setWhitespaceVisibility( QsciScintilla.WsVisible )
        else:
            self.__viewer.setWhitespaceVisibility( QsciScintilla.WsInvisible )
        self.__viewer.setTimestampMarginWidth()
        return


    def resizeEvent( self, event ):
        QWidget.resizeEvent( self, event )
        return
    def onPylint( self ):
        return True
    def onPymetrics( self ):
        return True
    def onRunScript( self, action = False ):
        return True
    def onRunScriptSettings( self ):
        return True
    def onProfileScript( self, action = False ):
        return True
    def onProfileScriptSettings( self ):
        return True
    def onImportDgm( self, action = None ):
        return True
    def onImportDgmTuned( self ):
        return True
    def shouldAcceptFocus( self ):
        return True

    def writeFile( self, fileName ):
        " Writes the text to a file "
        return self.__viewer.writeFile( fileName )

    def updateModificationTime( self, fileName ):
        return

    def appendIDEMessage( self, text ):
        " Appends an IDE message "
        self.__viewer.appendIDEMessage( text )
        return

    def appendStdoutMessage( self, text ):
        " Appends an stdout message "
        self.__viewer.appendStdoutMessage( text )
        return

    def appendStderrMessage( self, text ):
        " Appends an stderr message "
        self.__viewer.appendStderrMessage( text )
        return

    def hiddenMessage( self, msg ):
        " Returns True if the message should not be shown "
        if msg.msgType == IOConsoleMsg.STDERR_MESSAGE and \
           not self.__showstderr:
            return True
        if msg.msgType == IOConsoleMsg.STDOUT_MESSAGE and \
           not self.__showstdout:
            return True
        return False

    def zoomTo( self, zoomValue ):
        " Sets the new zoom value "
        self.__viewer.zoomTo( zoomValue )
        self.__viewer.setTimestampMarginWidth()
        return

    def rawInput( self, prompt, echo ):
        " Triggered when raw input is requested "
        echo = int( echo )
        if echo == 0:
            self.__viewer.inputEcho = False
        else:
            self.__viewer.inputEcho = True

        if prompt:
            self.appendStdoutMessage( prompt )
        self.__clearButton.setEnabled( False )
        self.__viewer.switchMode( self.__viewer.MODE_INPUT )
        return

    def sessionStopped( self ):
        " Triggered when redirecting session is stopped "
        self.__viewer.switchMode( self.__viewer.MODE_OUTPUT )
        self.__clearButton.setEnabled( True )
        return

    def stop( self ):
        " Triggered when the user requesed to stop the process "
        self.emit( SIGNAL( 'KillIOConsoleProcess' ), self.__threadID )
        return

    def stopAndClose( self ):
        " Triggered when the user requested to stop the process and close console "
        self.stop()
        self.close()
        return

    def close( self ):
        " Triggered when the console should be closed "
        self.emit( SIGNAL( 'CloseIOConsole' ), self.__threadID )
        return

    def scriptFinished( self ):
        " Triggered when the script process finished "
        self.__stopButton.setEnabled( False )
        self.__stopAndCloseButton.setToolTip( "Close tab" )
        self.__viewer.switchMode( self.__viewer.MODE_OUTPUT )
        self.__clearButton.setEnabled( True )
        return


    # Mandatory interface part is below

    def getEditor( self ):
        " Provides the editor widget "
        return self.__viewer

    def isModified( self ):
        " Tells if the file is modified "
        return False

    def getRWMode( self ):
        " Tells if the file is read only "
        return "IO"

    def getFileType( self ):
        " Provides the file type "
        return TexFileType

    def setFileType( self, typeToSet ):
        """ Sets the file type explicitly.
            It needs e.g. for .cgi files which can change its type """
        raise Exception( "Setting a file type is not supported by the "
                         "IO console widget" )

    def getType( self ):
        " Tells the widget type "
        return MainWindowTabWidgetBase.IOConsole

    def getLanguage( self ):
        " Tells the content language "
        return "IO console"

    def getFileName( self ):
        " Tells what file name of the widget content "
        return "n/a"

    def setFileName( self, name ):
        " Sets the file name "
        raise Exception( "Setting a file name for IO console "
                         "is not applicable" )

    def getEol( self ):
        " Tells the EOL style "
        return self.__viewer.getEolIndicator()

    def getLine( self ):
        " Tells the cursor line "
        line, _ = self.__viewer.getCursorPosition()
        return int( line )

    def getPos( self ):
        " Tells the cursor column "
        _, pos = self.__viewer.getCursorPosition()
        return int( pos )

    def getEncoding( self ):
        " Tells the content encoding "
        return self.__viewer.encoding

    def setEncoding( self, newEncoding ):
        " Sets the new editor encoding "
        raise Exception( "Setting encoding is not supported by the "
                         "IO console widget" )

    def getShortName( self ):
        " Tells the display name "
        return "IO console"

    def setShortName( self, name ):
        " Sets the display name "
        raise Exception( "Setting short name is not supported by the "
                         "IO console widget" )
Example #23
0
class DisassemblerResultsWidget( QWidget, MainWindowTabWidgetBase ):
    " Disassembling results widget "

    escapePressed = pyqtSignal()
    textEditorZoom = pyqtSignal( int )

    def __init__( self, scriptName, name, code, reportTime, parent = None ):

        MainWindowTabWidgetBase.__init__( self )
        QWidget.__init__( self, parent )

        self.__createLayout( scriptName, name, code, reportTime )
        return

    def __createLayout( self, scriptName, name, code, reportTime ):
        " Creates the toolbar and layout "

        # Buttons
        self.__printButton = QAction( PixmapCache().getIcon( 'printer.png' ),
                                      'Print', self )
        self.__printButton.triggered.connect( self.__onPrint )
        self.__printButton.setEnabled( False )
        self.__printButton.setVisible( False )

        self.__printPreviewButton = QAction(
                PixmapCache().getIcon( 'printpreview.png' ),
                'Print preview', self )
        self.__printPreviewButton.triggered.connect( self.__onPrintPreview )
        self.__printPreviewButton.setEnabled( False )
        self.__printPreviewButton.setVisible( False )

        # Zoom buttons
        self.__zoomInButton = QAction( PixmapCache().getIcon( 'zoomin.png' ),
                                       'Zoom in (Ctrl+=)', self )
        self.__zoomInButton.triggered.connect( self.onZoomIn )

        self.__zoomOutButton = QAction( PixmapCache().getIcon( 'zoomout.png' ),
                                        'Zoom out (Ctrl+-)', self )
        self.__zoomOutButton.triggered.connect( self.onZoomOut )

        self.__zoomResetButton = QAction( PixmapCache().getIcon( 'zoomreset.png' ),
                                          'Zoom reset (Ctrl+0)', self )
        self.__zoomResetButton.triggered.connect( self.onZoomReset )

        spacer = QWidget()
        spacer.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding )

        # Toolbar
        toolbar = QToolBar( self )
        toolbar.setOrientation( Qt.Vertical )
        toolbar.setMovable( False )
        toolbar.setAllowedAreas( Qt.RightToolBarArea )
        toolbar.setIconSize( QSize( 16, 16 ) )
        toolbar.setFixedWidth( 28 )
        toolbar.setContentsMargins( 0, 0, 0, 0 )

        toolbar.addAction( self.__printPreviewButton )
        toolbar.addAction( self.__printButton )
        toolbar.addWidget( spacer )
        toolbar.addAction( self.__zoomInButton )
        toolbar.addAction( self.__zoomOutButton )
        toolbar.addAction( self.__zoomResetButton )

        summary = QLabel( "<b>Script:</b> " + scriptName + "<br>"
                          "<b>Name:</b> " + name + "<br>"
                          "<b>Disassembled at:</b> " + reportTime )
        summary.setFrameStyle( QFrame.StyledPanel )
        summary.setAutoFillBackground( True )
        summaryPalette = summary.palette()
        summaryBackground = summaryPalette.color( QPalette.Background )
        summaryBackground.setRgb( min( summaryBackground.red() + 30, 255 ),
                                  min( summaryBackground.green() + 30, 255 ),
                                  min( summaryBackground.blue() + 30, 255 ) )
        summaryPalette.setColor( QPalette.Background, summaryBackground )
        summary.setPalette( summaryPalette )

        self.__text = DisasmWidget( self )
        self.__text.setAcceptRichText( False )
        self.__text.setLineWrapMode( QTextEdit.NoWrap )
        self.__text.setFont( GlobalData().skin.nolexerFont )
        self.zoomTo( Settings().zoom )
        self.__text.setReadOnly( True )
        self.__text.setPlainText( code )

        vLayout = QVBoxLayout()
        vLayout.addWidget( summary )
        vLayout.addWidget( self.__text )

        hLayout = QHBoxLayout()
        hLayout.setContentsMargins( 0, 0, 0, 0 )
        hLayout.setSpacing( 0 )
        hLayout.addLayout( vLayout )
        hLayout.addWidget( toolbar )

        self.setLayout( hLayout )
        return

    def setFocus( self ):
        " Overriden setFocus "
        self.__text.setFocus()
        return

    def __onPrint( self ):
        " Triggered on the 'print' button "
        pass

    def __onPrintPreview( self ):
        " Triggered on the 'print preview' button "
        pass

    def onZoomReset( self ):
        " Triggered when the zoom reset button is pressed "
        zoom = Settings().zoom
        if zoom != 0:
            self.textEditorZoom.emit( 0 )
        return True

    def onZoomIn( self ):
        " Triggered when the zoom in button is pressed "
        zoom = Settings().zoom
        if zoom < 20:
            self.textEditorZoom.emit( zoom + 1 )
        return True

    def onZoomOut( self ):
        " Triggered when the zoom out button is pressed "
        zoom = Settings().zoom
        if zoom > -10:
            self.textEditorZoom.emit( zoom - 1 )
        return True

    def keyPressEvent( self, event ):
        " Handles the key press events "
        if event.key() == Qt.Key_Escape:
            self.escapePressed.emit()
            event.accept()
        else:
            QWidget.keyPressEvent( self, event )
        return

    def zoomTo( self, zoomFactor ):
        " Scales the font in accordance to the given zoom factor "
        font = QFont( GlobalData().skin.nolexerFont )
        newPointSize = font.pointSize() + zoomFactor
        font.setPointSize( newPointSize )
        self.__text.setFont( font )
        return



    # Mandatory interface part is below

    def isModified( self ):
        " Tells if the file is modified "
        return False

    def getRWMode( self ):
        " Tells if the file is read only "
        return "RO"

    def getType( self ):
        " Tells the widget type "
        return MainWindowTabWidgetBase.DisassemblerViewer

    def getLanguage( self ):
        " Tells the content language "
        return "Disassembler"

    def getFileName( self ):
        " Tells what file name of the widget content "
        return "N/A"

    def setFileName( self, name ):
        " Sets the file name - not applicable"
        raise Exception( "Setting a file name for disassembler results is not applicable" )

    def getEol( self ):
        " Tells the EOL style "
        return "N/A"

    def getLine( self ):
        " Tells the cursor line "
        return "N/A"

    def getPos( self ):
        " Tells the cursor column "
        return "N/A"

    def getEncoding( self ):
        " Tells the content encoding "
        return "N/A"

    def setEncoding( self, newEncoding ):
        " Sets the new encoding - not applicable for the disassembler results viewer "
        return

    def getShortName( self ):
        " Tells the display name "
        return "Disassembling results"

    def setShortName( self, name ):
        " Sets the display name - not applicable "
        raise Exception( "Setting a file name for disassembler results is not applicable" )
Example #24
0
class MainUI(remembering.RememberingMainWindow):
    """Main UI."""

    _programs_file = os.path.join(multiplatform.data_dir, 'encuentro.data')

    def __init__(self, version, app_quit):
        super(MainUI, self).__init__()
        self.app_quit = app_quit
        self.finished = False
        self.version = version
        self.setWindowTitle('Encuentro')

        self.programs_data = data.ProgramsData(self, self._programs_file)
        self._touch_config()

        self.downloaders = {}
        for downtype, dloader_class in all_downloaders.iteritems():
            self.downloaders[downtype] = dloader_class()

        # finish all gui stuff
        self.big_panel = central_panel.BigPanel(self)
        self.episodes_list = self.big_panel.episodes
        self.episodes_download = self.big_panel.downloads_widget
        self.setCentralWidget(self.big_panel)

        # the setting of menubar should be almost in the end, because it may
        # trigger the wizard, which needs big_panel and etc.
        self.action_play = self.action_download = None
        self.filter_line = self.filter_cbox = self.needsomething_alert = None
        self._menubar()

        systray.show(self)

        if config.get('autorefresh'):
            ue = update.UpdateEpisodes(self)
            ue.background()
        else:
            # refresh data if never done before or if last
            # update was 7 days ago
            last_refresh = config.get('autorefresh_last_time')
            if last_refresh is None or (dt.datetime.now() - last_refresh >
                                        dt.timedelta(7)):
                ue = update.UpdateEpisodes(self)
                ue.background()

        self.show()

        self.episodes_download.load_pending()
        logger.debug("Main UI started ok")

    def _touch_config(self):
        """Do some config processing."""
        # log the config, but without user and pass
        safecfg = config.sanitized_config()
        logger.debug("Configuration loaded: %s", safecfg)

        # we have a default for download dir
        if not config.get('downloaddir'):
            config['downloaddir'] = multiplatform.get_download_dir()

        # maybe clean some config
        if self.programs_data.reset_config_from_migration:
            config['user'] = ''
            config['password'] = ''
            config.pop('cols_width', None)
            config.pop('cols_order', None)
            config.pop('selected_row', None)

    def have_config(self):
        """Return if some config is needed."""
        return config.get('user') and config.get('password')

    def have_metadata(self):
        """Return if metadata is needed."""
        return bool(self.programs_data)

    def _menubar(self):
        """Set up the menu bar."""
        menubar = self.menuBar()

        # applications menu
        menu_appl = menubar.addMenu(u'&Aplicación')

        icon = self.style().standardIcon(QStyle.SP_BrowserReload)
        action_reload = QAction(icon, '&Refrescar', self)
        action_reload.setShortcut('Ctrl+R')
        action_reload.setToolTip(u'Recarga la lista de programas')
        action_reload.triggered.connect(self.refresh_episodes)
        menu_appl.addAction(action_reload)

        icon = self.style().standardIcon(QStyle.SP_FileDialogDetailedView)
        action_preferences = QAction(icon, u'&Preferencias', self)
        action_preferences.triggered.connect(self.open_preferences)
        action_preferences.setToolTip(
            u'Configurar distintos parámetros del programa')
        menu_appl.addAction(action_preferences)

        menu_appl.addSeparator()

        icon = self.style().standardIcon(QStyle.SP_MessageBoxInformation)
        _act = QAction(icon, '&Acerca de', self)
        _act.triggered.connect(self.open_about_dialog)
        _act.setToolTip(u'Muestra información de la aplicación')
        menu_appl.addAction(_act)

        icon = self.style().standardIcon(QStyle.SP_DialogCloseButton)
        _act = QAction(icon, '&Salir', self)
        _act.setShortcut('Ctrl+Q')
        _act.setToolTip(u'Sale de la aplicación')
        _act.triggered.connect(self.on_close)
        menu_appl.addAction(_act)

        # program menu
        menu_prog = menubar.addMenu(u'&Programa')

        icon = self.style().standardIcon(QStyle.SP_ArrowDown)
        self.action_download = QAction(icon, '&Descargar', self)
        self.action_download.setShortcut('Ctrl+D')
        self.action_download.setEnabled(False)
        self.action_download.setToolTip(TTIP_DOWNLOAD_D)
        self.action_download.triggered.connect(self.download_episode)
        menu_prog.addAction(self.action_download)

        icon = self.style().standardIcon(QStyle.SP_MediaPlay)
        self.action_play = QAction(icon, '&Reproducir', self)
        self.action_play.setEnabled(False)
        self.action_play.setToolTip(TTIP_PLAY_D)
        self.action_play.triggered.connect(self.on_play_action)
        menu_prog.addAction(self.action_play)

        # toolbar for buttons
        toolbar = self.addToolBar('main')
        toolbar.addAction(self.action_download)
        toolbar.addAction(self.action_play)
        toolbar.addSeparator()
        toolbar.addAction(action_reload)
        toolbar.addAction(action_preferences)

        # filter text and button, to the right
        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        toolbar.addWidget(spacer)
        toolbar.addWidget(QLabel(u"Filtro: "))
        self.filter_line = QLineEdit()
        self.filter_line.setMaximumWidth(150)
        self.filter_line.textChanged.connect(self.on_filter_changed)
        toolbar.addWidget(self.filter_line)
        self.filter_cbox = QCheckBox(u"Sólo descargados")
        self.filter_cbox.stateChanged.connect(self.on_filter_changed)
        toolbar.addWidget(self.filter_cbox)

        # if needed, a warning that stuff needs to be configured
        icon = self.style().standardIcon(QStyle.SP_MessageBoxWarning)
        m = u"Necesita configurar algo; haga click aquí para abrir el wizard"
        self.needsomething_alert = QAction(icon, m, self)
        self.needsomething_alert.triggered.connect(self._start_wizard)
        toolbar.addAction(self.needsomething_alert)
        if not config.get('nowizard'):
            self._start_wizard()
        self._review_need_something_indicator()

    def _start_wizard(self, _=None):
        """Start the wizard if needed."""
        if not self.have_config() or not self.have_metadata():
            dlg = wizard.WizardDialog(self)
            dlg.exec_()
        self._review_need_something_indicator()

    def on_filter_changed(self, _):
        """The filter text has changed, apply it in the episodes list."""
        text = self.filter_line.text()
        cbox = self.filter_cbox.checkState()
        self.episodes_list.set_filter(text, cbox)

        # after applying filter, nothing is selected, so check buttons
        # (easiest way to clean them all)
        self.check_download_play_buttons()

    def _review_need_something_indicator(self):
        """Hide/show/enable/disable different indicators if need sth."""
        needsomething = bool(not self.have_config()
                             or not self.have_metadata())
        self.needsomething_alert.setVisible(needsomething)

    def shutdown(self):
        """Stop everything and quit.

        This shutdown con be called at any time, even on init, so we have
        extra precautions about which attributes we have.
        """
        signal.emit('save_state')
        config.save()
        self.finished = True

        programs_data = getattr(self, 'programs_data', None)
        if programs_data is not None:
            programs_data.save()

        downloaders = getattr(self, 'downloaders', {})
        for downloader in downloaders.itervalues():
            downloader.shutdown()

        # bye bye
        self.app_quit()

    def on_close(self, _):
        """Close signal."""
        if self._should_close():
            self.shutdown()

    def closeEvent(self, event):
        """All is being closed."""
        if self._should_close():
            self.shutdown()
        else:
            event.ignore()

    def _should_close(self):
        """Still time to decide if want to close or not."""
        logger.info("Attempt to close the program")
        pending = self.episodes_download.pending()
        if not pending:
            # all fine, save all and quit
            logger.info("Saving states and quitting")
            return True
        logger.debug("Still %d active downloads when trying to quit", pending)

        # stuff pending
        m = (u"Hay programas todavía en proceso de descarga!\n"
             u"¿Seguro quiere salir del programa?")
        QMB = QMessageBox
        dlg = QMB(u"Guarda!", m, QMB.Question, QMB.Yes, QMB.No, QMB.NoButton)
        opt = dlg.exec_()
        if opt != QMB.Yes:
            logger.info("Quit cancelled")
            return False

        # quit anyway, put all downloading and pending episodes to none
        logger.info("Fixing episodes, saving state and exiting")
        for program in self.programs_data.values():
            state = program.state
            if state == Status.waiting or state == Status.downloading:
                program.state = Status.none
        return True

    def show_message(self, err_type, text):
        """Show different messages to the user."""
        if self.finished:
            logger.debug("Ignoring message: %r", text)
            return
        logger.debug("Showing a message: %r", text)

        # error text can be produced by windows, try to to sanitize it
        if isinstance(text, str):
            try:
                text = text.decode("utf8")
            except UnicodeDecodeError:
                try:
                    text = text.decode("latin1")
                except UnicodeDecodeError:
                    text = repr(text)

        QMB = QMessageBox
        dlg = QMB(u"Atención: " + err_type, text, QMB.Warning, QMB.Ok,
                  QMB.NoButton, QMB.NoButton)
        dlg.exec_()

    def refresh_episodes(self, _=None):
        """Update and refresh episodes."""
        ue = update.UpdateEpisodes(self)
        ue.interactive()

    def download_episode(self, _=None):
        """Download the episode(s)."""
        episode_ids = self.episodes_list.selected_items()
        for episode_id in episode_ids:
            episode = self.programs_data[episode_id]
            self.queue_download(episode)

    @defer.inline_callbacks
    def queue_download(self, episode):
        """User indicated to download something."""
        logger.debug("Download requested of %s", episode)
        if episode.state != Status.none:
            logger.debug(
                "Download denied, episode %s is not in downloadeable "
                "state.", episode.episode_id)
            return

        # queue
        self.episodes_download.append(episode)
        self.episodes_list.episode_info.update(episode)
        self.check_download_play_buttons()
        if self.episodes_download.downloading:
            return

        logger.debug("Downloads: starting")
        while self.episodes_download.pending():
            episode = self.episodes_download.prepare()
            try:
                filename, episode = yield self._episode_download(episode)
            except CancelledError:
                logger.debug("Got a CancelledError!")
                self.episodes_download.end(error=u"Cancelado")
            except BadCredentialsError:
                logger.debug("Bad credentials error!")
                msg = (u"Error con las credenciales: hay que configurar "
                       u"usuario y clave correctos")
                self.show_message('BadCredentialsError', msg)
                self.episodes_download.end(error=msg)
            except EncuentroError, e:
                orig_exc = e.orig_exc
                msg = "%s(%s)" % (orig_exc, e)
                err_type = e.__class__.__name__
                logger.exception("Custom Encuentro error: %s (%r)", e,
                                 orig_exc)
                notify(err_type, msg)
                self.episodes_download.end(error=u"Error: " + msg)
            except Exception, e:
                logger.exception("Unknown download error: %s (%r)", e, e)
                err_type = e.__class__.__name__
                notify(err_type, str(e))
                self.episodes_download.end(error=u"Error: " + str(e))
            else:
class FindInFilesViewer(QWidget):
    " Find in files viewer tab widget "

    lastEntered = None

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        global searchTooltip
        searchTooltip = Tooltip()

        self.__reportRegexp = None
        self.__reportResults = []
        self.__reportShown = False
        self.__bufferChangeconnected = False

        # Prepare members for reuse
        self.__noneLabel = QLabel("\nNo results available")

        self.__noneLabel.setFrameShape(QFrame.StyledPanel)
        self.__noneLabel.setAlignment(Qt.AlignHCenter)
        self.__headerFont = self.__noneLabel.font()
        self.__headerFont.setPointSize(self.__headerFont.pointSize() + 4)
        self.__noneLabel.setFont(self.__headerFont)
        self.__noneLabel.setAutoFillBackground(True)
        noneLabelPalette = self.__noneLabel.palette()
        noneLabelPalette.setColor(QPalette.Background, GlobalData().skin.nolexerPaper)
        self.__noneLabel.setPalette(noneLabelPalette)

        # Keep pylint happy
        self.printButton = None
        self.clearButton = None
        self.printPreviewButton = None

        self.__createLayout(parent)

        self.__updateButtonsStatus()

        GlobalData().project.projectChanged.connect(self.__onProjectChanged)
        return

    def __createLayout(self, parent):
        " Creates the toolbar and layout "

        # Buttons
        self.printButton = QAction(PixmapCache().getIcon("printer.png"), "Print", self)
        # printButton.setShortcut( 'Ctrl+' )
        self.printButton.triggered.connect(self.__onPrint)
        self.printButton.setVisible(False)

        self.printPreviewButton = QAction(PixmapCache().getIcon("printpreview.png"), "Print preview", self)
        # printPreviewButton.setShortcut( 'Ctrl+' )
        self.printPreviewButton.triggered.connect(self.__onPrintPreview)
        self.printPreviewButton.setVisible(False)

        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.clearButton = QAction(PixmapCache().getIcon("trash.png"), "Clear", self)
        self.clearButton.triggered.connect(self.__clear)

        # The toolbar
        self.toolbar = QToolBar(self)
        self.toolbar.setOrientation(Qt.Vertical)
        self.toolbar.setMovable(False)
        self.toolbar.setAllowedAreas(Qt.RightToolBarArea)
        self.toolbar.setIconSize(QSize(16, 16))
        self.toolbar.setFixedWidth(28)
        self.toolbar.setContentsMargins(0, 0, 0, 0)

        self.toolbar.addAction(self.printPreviewButton)
        self.toolbar.addAction(self.printButton)
        self.toolbar.addWidget(spacer)
        self.toolbar.addAction(self.clearButton)

        self.__resultsTree = FindResultsTreeWidget()
        self.__resultsTree.setAlternatingRowColors(True)
        self.__resultsTree.setRootIsDecorated(True)
        self.__resultsTree.setItemsExpandable(True)
        self.__resultsTree.setUniformRowHeights(True)
        self.__resultsTree.setItemDelegate(NoOutlineHeightDelegate(4))
        headerLabels = ["File name / line", "Text"]
        self.__resultsTree.setHeaderLabels(headerLabels)
        self.__resultsTree.itemActivated.connect(self.__resultActivated)
        self.__resultsTree.itemClicked.connect(self.__resultClicked)
        self.__resultsTree.setMouseTracking(True)
        self.__resultsTree.itemEntered.connect(self.__itemEntered)
        self.__resultsTree.hide()

        self.__hLayout = QHBoxLayout()
        self.__hLayout.setContentsMargins(0, 0, 0, 0)
        self.__hLayout.setSpacing(0)
        self.__hLayout.addWidget(self.toolbar)
        self.__hLayout.addWidget(self.__noneLabel)
        self.__hLayout.addWidget(self.__resultsTree)

        self.setLayout(self.__hLayout)
        return

    def getResultsTree(self):
        " Provides a reference to the results tree "
        return self.__resultsTree

    def __updateButtonsStatus(self):
        " Updates the buttons status "
        self.printButton.setEnabled(self.__reportShown)
        self.printPreviewButton.setEnabled(self.__reportShown)
        self.clearButton.setEnabled(self.__reportShown)
        return

    def __onPrint(self):
        " Triggered when the print button is pressed "
        pass

    def __onPrintPreview(self):
        " triggered when the print preview button is pressed "
        pass

    def setFocus(self):
        " Overridden setFocus "
        self.__hLayout.setFocus()
        return

    def __onProjectChanged(self, what):
        " Triggered when a project is changed "
        if what == CodimensionProject.CompleteProject:
            self.__clear()
        return

    def __clear(self):
        " Clears the content of the vertical layout "
        if not self.__reportShown:
            return

        # Disconnect the buffer change signal if it is connected
        if self.__bufferChangeconnected:
            self.__bufferChangeconnected = False
            mainWindow = GlobalData().mainWindow
            editorsManager = mainWindow.editorsManagerWidget.editorsManager
            editorsManager.bufferModified.disconnect(self.__resultsTree.onBufferModified)

        self.__resultsTree.resetCache()
        self.__resultsTree.clear()
        self.__resultsTree.hide()
        self.__noneLabel.show()

        self.__reportRegexp = None
        self.__reportResults = []
        self.__reportShown = False
        self.__updateButtonsStatus()
        return

    def showReport(self, regexp, results):
        " Shows the find in files results "
        self.__clear()
        self.__noneLabel.hide()

        self.__reportRegexp = regexp
        self.__reportResults = results

        # Add the complete information
        totalMatched = 0
        for item in results:
            matched = len(item.matches)
            totalMatched += matched
            if matched == 1:
                matchText = " (1 match)"
            else:
                matchText = " (" + str(matched) + " matches)"
            columns = [item.fileName, matchText]
            fileItem = MatchTableFileItem(columns, item.bufferUUID)
            fileItem.setIcon(0, getFileIcon(detectFileType(item.fileName)))
            if item.tooltip != "":
                fileItem.setToolTip(0, item.tooltip)
            self.__resultsTree.addTopLevelItem(fileItem)

            # Matches
            for match in item.matches:
                columns = [str(match.line), match.text]
                matchItem = MatchTableItem(columns, match.tooltip)
                fileItem.addChild(matchItem)
            fileItem.setExpanded(True)

        # Update the header with the total number of matches
        headerLabels = [
            "File name / line (total files: " + str(len(results)) + ")",
            "Text (total matches: " + str(totalMatched) + ")",
        ]
        self.__resultsTree.setHeaderLabels(headerLabels)

        # Resizing the table
        self.__resultsTree.header().resizeSections(QHeaderView.ResizeToContents)

        # Show the complete information
        self.__resultsTree.show()
        self.__resultsTree.buildCache()

        self.__reportShown = True
        self.__updateButtonsStatus()

        # Connect the buffer change signal if not connected yet
        if not self.__bufferChangeconnected:
            self.__bufferChangeconnected = True
            mainWindow = GlobalData().mainWindow
            editorsManager = mainWindow.editorsManagerWidget.editorsManager
            editorsManager.bufferModified.connect(self.__resultsTree.onBufferModified)
        return

    def __resultClicked(self, item, column):
        " Handles the single click "
        hideSearchTooltip()
        return

    def __resultActivated(self, item, column):
        " Handles the double click (or Enter) on a match "
        if type(item) == MatchTableItem:
            fileName = str(item.parent().data(0, Qt.DisplayRole).toString())
            lineNumber = int(item.data(0, Qt.DisplayRole).toString())
            GlobalData().mainWindow.openFile(fileName, lineNumber)
            hideSearchTooltip()
            return

    def __itemEntered(self, item, column):
        " Triggered when the mouse cursor entered a row "

        if type(item) != MatchTableItem:
            self.lastEntered = item
            hideSearchTooltip()
            return

        if column != 1:
            # Show the tooltip only for the column with results
            self.lastEntered = None
            hideSearchTooltip()
            return

        # Memorize the row height for proper tooltip displaying later
        global cellHeight
        cellHeight = self.__resultsTree.visualItemRect(item).height()

        if self.lastEntered != item or not inside:
            item.itemEntered()
            self.lastEntered = item
        return
Example #26
0
class QtAction(QtToolkitObject, ProxyAction):
    """ A Qt implementation of an Enaml ProxyAction.

    """
    #: A reference to the widget created by the proxy.
    widget = Typed(QAction)

    #: Cyclic notification guard. This a bitfield of multiple guards.
    _guard = Int(0)

    # FIXME Currently, the checked state of the action is lost when
    # switching from checkable to non-checkable and back again.
    #--------------------------------------------------------------------------
    # Initialization API
    #--------------------------------------------------------------------------
    def create_widget(self):
        """ Create the underlying QAction object.

        """
        self.widget = QAction(self.parent_widget())

    def init_widget(self):
        """ Initialize the underlying control.

        """
        super(QtAction, self).init_widget()
        d = self.declaration
        if d.text:
            self.set_text(d.text)
        if d.tool_tip:
            self.set_tool_tip(d.tool_tip)
        if d.status_tip:
            self.set_status_tip(d.status_tip)
        if d.icon:
            self.set_icon(d.icon)
        self.set_checkable(d.checkable)
        self.set_checked(d.checked)
        self.set_enabled(d.enabled)
        self.set_visible(d.visible)
        self.set_separator(d.separator)
        widget = self.widget
        widget.triggered.connect(self.on_triggered)
        widget.toggled.connect(self.on_toggled)

    #--------------------------------------------------------------------------
    # Signal Handlers
    #--------------------------------------------------------------------------
    def on_triggered(self, checked):
        """ The signal handler for the 'triggered' signal.

        """
        if not self._guard & CHECKED_GUARD:
            self.declaration.checked = checked
            self.declaration.triggered(checked)

    def on_toggled(self, checked):
        """ The signal handler for the 'toggled' signal.

        """
        if not self._guard & CHECKED_GUARD:
            self.declaration.checked = checked
            self.declaration.toggled(checked)

    #--------------------------------------------------------------------------
    # ProxyAction API
    #--------------------------------------------------------------------------
    def set_text(self, text):
        """ Set the text on the underlying control.

        """
        widget = self.widget
        widget.setText(text)
        parts = text.split('\t')
        if len(parts) > 1:
            shortcut = QKeySequence(parts[-1])
            widget.setShortcut(shortcut)

    def set_tool_tip(self, tool_tip):
        """ Set the tool tip on the underlying control.

        """
        self.widget.setToolTip(tool_tip)

    def set_status_tip(self, status_tip):
        """ Set the status tip on the underyling control.

        """
        self.widget.setStatusTip(status_tip)

    def set_icon(self, icon):
        """ Set the icon for the action.

        """
        if icon:
            qicon = get_cached_qicon(icon)
        else:
            qicon = QIcon()
        self.widget.setIcon(qicon)

    def set_checkable(self, checkable):
        """ Set the checkable state on the underlying control.

        """
        self.widget.setCheckable(checkable)

    def set_checked(self, checked):
        """ Set the checked state on the underlying control.

        """
        self._guard |= CHECKED_GUARD
        try:
            self.widget.setChecked(checked)
        finally:
            self._guard &= ~CHECKED_GUARD

    def set_enabled(self, enabled):
        """ Set the enabled state on the underlying control.

        """
        self.widget.setEnabled(enabled)

    def set_visible(self, visible):
        """ Set the visible state on the underlying control.

        """
        self.widget.setVisible(visible)

    def set_separator(self, separator):
        """ Set the separator state on the underlying control.

        """
        self.widget.setSeparator(separator)
Example #27
0
class IgnoredExceptionsViewer(QWidget):
    " Implements the client exceptions viewer for a debugger "

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        self.__createPopupMenu()
        self.__createLayout()
        self.__ignored = []
        self.__currentItem = None

        GlobalData().project.projectChanged.connect(self.__onProjectChanged)

        if Settings().showIgnoredExcViewer == False:
            self.__onShowHide(True)
        return

    def __createPopupMenu(self):
        " Creates the popup menu "
        self.__excptMenu = QMenu()
        self.__removeMenuItem = self.__excptMenu.addAction(
            PixmapCache().getIcon('ignexcptdel.png'),
            "Remove from ignore list", self.__onRemoveFromIgnore)
        return

    def __createLayout(self):
        " Creates the widget layout "

        verticalLayout = QVBoxLayout(self)
        verticalLayout.setContentsMargins(0, 0, 0, 0)
        verticalLayout.setSpacing(0)

        self.headerFrame = QFrame()
        self.headerFrame.setFrameStyle(QFrame.StyledPanel)
        self.headerFrame.setAutoFillBackground(True)
        headerPalette = self.headerFrame.palette()
        headerBackground = headerPalette.color(QPalette.Background)
        headerBackground.setRgb(min(headerBackground.red() + 30, 255),
                                min(headerBackground.green() + 30, 255),
                                min(headerBackground.blue() + 30, 255))
        headerPalette.setColor(QPalette.Background, headerBackground)
        self.headerFrame.setPalette(headerPalette)
        self.headerFrame.setFixedHeight(24)

        self.__excptLabel = QLabel("Ignored exception types")

        expandingSpacer = QSpacerItem(10, 10, QSizePolicy.Expanding)
        fixedSpacer = QSpacerItem(3, 3)

        self.__showHideButton = QToolButton()
        self.__showHideButton.setAutoRaise(True)
        self.__showHideButton.setIcon(PixmapCache().getIcon('less.png'))
        self.__showHideButton.setFixedSize(20, 20)
        self.__showHideButton.setToolTip("Hide ignored exceptions list")
        self.__showHideButton.setFocusPolicy(Qt.NoFocus)
        self.__showHideButton.clicked.connect(self.__onShowHide)

        headerLayout = QHBoxLayout()
        headerLayout.setContentsMargins(1, 1, 1, 1)
        headerLayout.addSpacerItem(fixedSpacer)
        headerLayout.addWidget(self.__excptLabel)
        headerLayout.addSpacerItem(expandingSpacer)
        headerLayout.addWidget(self.__showHideButton)
        self.headerFrame.setLayout(headerLayout)

        self.exceptionsList = QTreeWidget(self)
        self.exceptionsList.setSortingEnabled(False)
        self.exceptionsList.setAlternatingRowColors(True)
        self.exceptionsList.setRootIsDecorated(False)
        self.exceptionsList.setItemsExpandable(True)
        self.exceptionsList.setUniformRowHeights(True)
        self.exceptionsList.setSelectionMode(QAbstractItemView.SingleSelection)
        self.exceptionsList.setSelectionBehavior(QAbstractItemView.SelectRows)
        self.exceptionsList.setItemDelegate(NoOutlineHeightDelegate(4))
        self.exceptionsList.setContextMenuPolicy(Qt.CustomContextMenu)

        self.exceptionsList.customContextMenuRequested.connect(
            self.__showContextMenu)
        self.exceptionsList.itemSelectionChanged.connect(
            self.__onSelectionChanged)
        self.exceptionsList.setHeaderLabels(["Exception type"])

        self.__excTypeEdit = QLineEdit()
        self.__excTypeEdit.setFixedHeight(26)
        self.__excTypeEdit.textChanged.connect(self.__onNewFilterChanged)
        self.__excTypeEdit.returnPressed.connect(self.__onAddExceptionFilter)
        self.__addButton = QPushButton("Add")
        # self.__addButton.setFocusPolicy( Qt.NoFocus )
        self.__addButton.setEnabled(False)
        self.__addButton.clicked.connect(self.__onAddExceptionFilter)

        expandingSpacer2 = QWidget()
        expandingSpacer2.setSizePolicy(QSizePolicy.Expanding,
                                       QSizePolicy.Expanding)

        self.__removeButton = QAction(PixmapCache().getIcon('delitem.png'),
                                      "Remove selected exception type", self)
        self.__removeButton.triggered.connect(self.__onRemoveFromIgnore)
        self.__removeButton.setEnabled(False)

        fixedSpacer1 = QWidget()
        fixedSpacer1.setFixedWidth(5)

        self.__removeAllButton = QAction(
            PixmapCache().getIcon('ignexcptdelall.png'),
            "Remove all the exception types", self)
        self.__removeAllButton.triggered.connect(self.__onRemoveAllFromIgnore)
        self.__removeAllButton.setEnabled(False)

        self.toolbar = QToolBar()
        self.toolbar.setOrientation(Qt.Horizontal)
        self.toolbar.setMovable(False)
        self.toolbar.setAllowedAreas(Qt.TopToolBarArea)
        self.toolbar.setIconSize(QSize(16, 16))
        self.toolbar.setFixedHeight(28)
        self.toolbar.setContentsMargins(0, 0, 0, 0)
        self.toolbar.addWidget(expandingSpacer2)
        self.toolbar.addAction(self.__removeButton)
        self.toolbar.addWidget(fixedSpacer1)
        self.toolbar.addAction(self.__removeAllButton)

        addLayout = QHBoxLayout()
        addLayout.setContentsMargins(1, 1, 1, 1)
        addLayout.setSpacing(1)
        addLayout.addWidget(self.__excTypeEdit)
        addLayout.addWidget(self.__addButton)

        verticalLayout.addWidget(self.headerFrame)
        verticalLayout.addWidget(self.toolbar)
        verticalLayout.addWidget(self.exceptionsList)
        verticalLayout.addLayout(addLayout)
        return

    def clear(self):
        " Clears the content "
        self.exceptionsList.clear()
        self.__excTypeEdit.clear()
        self.__addButton.setEnabled(False)
        self.__ignored = []
        self.__currentItem = None
        self.__updateTitle()
        return

    def __onShowHide(self, startup=False):
        " Triggered when show/hide button is clicked "
        if startup or self.exceptionsList.isVisible():
            self.exceptionsList.setVisible(False)
            self.__excTypeEdit.setVisible(False)
            self.__addButton.setVisible(False)
            self.__removeButton.setVisible(False)
            self.__removeAllButton.setVisible(False)
            self.__showHideButton.setIcon(PixmapCache().getIcon('more.png'))
            self.__showHideButton.setToolTip("Show ignored exceptions list")

            self.__minH = self.minimumHeight()
            self.__maxH = self.maximumHeight()

            self.setMinimumHeight(self.headerFrame.height())
            self.setMaximumHeight(self.headerFrame.height())

            Settings().showIgnoredExcViewer = False
        else:
            self.exceptionsList.setVisible(True)
            self.__excTypeEdit.setVisible(True)
            self.__addButton.setVisible(True)
            self.__removeButton.setVisible(True)
            self.__removeAllButton.setVisible(True)
            self.__showHideButton.setIcon(PixmapCache().getIcon('less.png'))
            self.__showHideButton.setToolTip("Hide ignored exceptions list")

            self.setMinimumHeight(self.__minH)
            self.setMaximumHeight(self.__maxH)

            Settings().showIgnoredExcViewer = True
        return

    def __onSelectionChanged(self):
        " Triggered when the current item is changed "
        selected = list(self.exceptionsList.selectedItems())
        if selected:
            self.__currentItem = selected[0]
            self.__removeButton.setEnabled(True)
        else:
            self.__currentItem = None
            self.__removeButton.setEnabled(False)
        return

    def __showContextMenu(self, coord):
        " Shows the frames list context menu "
        contextItem = self.exceptionsList.itemAt(coord)
        if contextItem is not None:
            self.__currentItem = contextItem
            self.__excptMenu.popup(QCursor.pos())
        return

    def __updateTitle(self):
        " Updates the section title "
        count = self.exceptionsList.topLevelItemCount()
        if count == 0:
            self.__excptLabel.setText("Ignored exception types")
        else:
            self.__excptLabel.setText("Ignored exception types (total: " +
                                      str(count) + ")")
        self.__removeAllButton.setEnabled(count != 0)
        return

    def __onProjectChanged(self, what):
        " Triggered when a project is changed "
        if what != CodimensionProject.CompleteProject:
            return

        self.clear()
        project = GlobalData().project
        if project.isLoaded():
            self.__ignored = list(project.ignoredExcpt)
        else:
            self.__ignored = list(Settings().ignoredExceptions)

        for exceptionType in self.__ignored:
            item = QTreeWidgetItem(self.exceptionsList)
            item.setText(0, exceptionType)
        self.__updateTitle()
        return

    def __onNewFilterChanged(self, text):
        " Triggered when the text is changed "
        text = str(text).strip()
        if text == "":
            self.__addButton.setEnabled(False)
            return
        if " " in text:
            self.__addButton.setEnabled(False)
            return

        if text in self.__ignored:
            self.__addButton.setEnabled(False)
            return

        self.__addButton.setEnabled(True)
        return

    def __onAddExceptionFilter(self):
        " Adds an item into the ignored exceptions list "
        text = self.__excTypeEdit.text().strip()
        self.addExceptionFilter(text)

    def addExceptionFilter(self, excType):
        " Adds a new item into the ignored exceptions list "
        if excType == "":
            return
        if " " in excType:
            return
        if excType in self.__ignored:
            return

        item = QTreeWidgetItem(self.exceptionsList)
        item.setText(0, excType)

        project = GlobalData().project
        if project.isLoaded():
            project.addExceptionFilter(excType)
        else:
            Settings().addExceptionFilter(excType)
        self.__ignored.append(excType)
        self.__updateTitle()
        return

    def __onRemoveFromIgnore(self):
        " Removes an item from the ignored exception types list "
        if self.__currentItem is None:
            return

        text = self.__currentItem.text(0)

        # Find the item index and remove it
        index = 0
        while True:
            if self.exceptionsList.topLevelItem(index).text(0) == text:
                self.exceptionsList.takeTopLevelItem(index)
                break
            index += 1

        project = GlobalData().project
        if project.isLoaded():
            project.deleteExceptionFilter(text)
        else:
            Settings().deleteExceptionFilter(text)
        self.__ignored.remove(text)
        self.__updateTitle()
        return

    def __onRemoveAllFromIgnore(self):
        " Triggered when all the ignored exceptions should be deleted "
        self.clear()

        project = GlobalData().project
        if project.isLoaded():
            project.setExceptionFilters([])
        else:
            Settings().setExceptionFilters([])
        return

    def isIgnored(self, exceptionType):
        " Returns True if this exception type should be ignored "
        return exceptionType in self.__ignored
Example #28
0
class MapWidget(Ui_CanvasWidget, QMainWindow):
    def __init__(self, parent=None):
        super(MapWidget, self).__init__(parent)
        self.setupUi(self)
        self.snapping = True

        icon = roam_style.iconsize()
        self.projecttoolbar.setIconSize(QSize(icon, icon))

        self.current_form = None
        self.last_form = None
        self.firstshow = True
        self.layerbuttons = []
        self.editfeaturestack = []
        self.lastgpsposition = None
        self.project = None
        self.gps = None
        self.gpslogging = None
        self.selectionbands = defaultdict(partial(QgsRubberBand, self.canvas))

        self.bridge = QgsLayerTreeMapCanvasBridge(
            QgsProject.instance().layerTreeRoot(), self.canvas)
        self.bridge.setAutoSetupOnFirstLayer(False)
        QgsProject.instance().writeProject.connect(self.bridge.writeProject)
        QgsProject.instance().readProject.connect(self.bridge.readProject)

        # self.canvas.setInteractive(False)
        self.canvas.setCanvasColor(Qt.white)
        self.canvas.enableAntiAliasing(True)
        self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor)

        self.snappingutils = SnappingUtils(self.canvas, self)
        self.canvas.setSnappingUtils(self.snappingutils)
        QgsProject.instance().readProject.connect(
            self.snappingutils.readConfigFromProject)

        if hasattr(self.canvas, 'setParallelRenderingEnabled'):
            threadcount = QThread.idealThreadCount()
            threadcount = 2 if threadcount > 2 else 1
            QgsApplication.setMaxThreads(threadcount)
            self.canvas.setParallelRenderingEnabled(True)

        pal = QgsPalLabeling()
        self.canvas.mapRenderer().setLabelingEngine(pal)
        self.canvas.setFrameStyle(QFrame.NoFrame)

        self.editgroup = QActionGroup(self)
        self.editgroup.setExclusive(True)
        self.editgroup.addAction(self.actionPan)
        self.editgroup.addAction(self.actionZoom_In)
        self.editgroup.addAction(self.actionZoom_Out)
        self.editgroup.addAction(self.actionInfo)

        self.actionGPS = GPSAction(":/icons/gps", self.canvas, self)
        self.projecttoolbar.addAction(self.actionGPS)

        if roam.config.settings.get('north_arrow', False):
            self.northarrow = NorthArrow(":/icons/north", self.canvas)
            self.northarrow.setPos(10, 10)
            self.canvas.scene().addItem(self.northarrow)

        smallmode = roam.config.settings.get("smallmode", False)
        self.projecttoolbar.setSmallMode(smallmode)

        self.scalebar_enabled = roam.config.settings.get('scale_bar', False)
        if self.scalebar_enabled:
            self.scalebar = ScaleBarItem(self.canvas)
            self.canvas.scene().addItem(self.scalebar)

        self.projecttoolbar.setContextMenuPolicy(Qt.CustomContextMenu)

        gpsspacewidget = QWidget()
        gpsspacewidget.setMinimumWidth(30)
        gpsspacewidget.setSizePolicy(QSizePolicy.Expanding,
                                     QSizePolicy.Expanding)

        self.topspaceraction = self.projecttoolbar.insertWidget(
            self.actionGPS, gpsspacewidget)

        self.dataentryselection = QAction(self.projecttoolbar)
        self.dataentryaction = self.projecttoolbar.insertAction(
            self.topspaceraction, self.dataentryselection)
        self.dataentryselection.triggered.connect(self.select_data_entry)

        self.marker = GPSMarker(self.canvas)
        self.marker.hide()

        self.currentfeatureband = CurrentSelection(self.canvas)
        self.currentfeatureband.setIconSize(30)
        self.currentfeatureband.setWidth(10)
        self.currentfeatureband.setColor(QColor(186, 93, 212, 50))
        self.currentfeatureband.setOutlineColour(QColor(186, 93, 212))

        self.gpsband = QgsRubberBand(self.canvas)
        self.gpsband.setColor(QColor(165, 111, 212, 75))
        self.gpsband.setWidth(5)

        RoamEvents.editgeometry.connect(self.queue_feature_for_edit)
        RoamEvents.selectioncleared.connect(self.clear_selection)
        RoamEvents.selectionchanged.connect(self.highlight_selection)
        RoamEvents.openfeatureform.connect(self.feature_form_loaded)
        RoamEvents.sync_complete.connect(self.refresh_map)
        RoamEvents.snappingChanged.connect(self.snapping_changed)

        self.snappingbutton = QToolButton()
        self.snappingbutton.setText("Snapping: On")
        self.snappingbutton.setAutoRaise(True)
        self.snappingbutton.pressed.connect(self.toggle_snapping)

        spacer = QWidget()
        spacer2 = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        spacer2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        self.scalewidget = QgsScaleComboBox()

        self.scalebutton = QToolButton()
        self.scalebutton.setAutoRaise(True)
        self.scalebutton.setMaximumHeight(self.statusbar.height())
        self.scalebutton.pressed.connect(self.selectscale)
        self.scalebutton.setText("Scale")

        self.scalelist = BigList(parent=self.canvas,
                                 centeronparent=True,
                                 showsave=False)
        self.scalelist.hide()
        self.scalelist.setlabel("Map Scale")
        self.scalelist.setmodel(self.scalewidget.model())
        self.scalelist.closewidget.connect(self.scalelist.close)
        self.scalelist.itemselected.connect(self.update_scale_from_item)
        self.scalelist.itemselected.connect(self.scalelist.close)

        self.positionlabel = QLabel('')
        self.gpslabel = QLabel("GPS: Not active")
        self.gpslabelposition = QLabel("")

        self.statusbar.addWidget(self.snappingbutton)
        self.statusbar.addWidget(spacer2)
        self.statusbar.addWidget(self.gpslabel)
        self.statusbar.addWidget(self.gpslabelposition)
        self.statusbar.addPermanentWidget(self.scalebutton)

        self.canvas.extentsChanged.connect(self.updatestatuslabel)
        self.canvas.scaleChanged.connect(self.updatestatuslabel)

        GPS.gpsposition.connect(self.update_gps_label)
        GPS.gpsdisconnected.connect(self.gps_disconnected)

        self.connectButtons()

    def clear_plugins(self):
        toolbars = self.findChildren(QToolBar)
        for toolbar in toolbars:
            if toolbar.property("plugin_toolbar"):
                toolbar.unload()
                self.removeToolBar(toolbar)
                toolbar.deleteLater()

    def add_plugins(self, pluginnames):
        for name in pluginnames:
            # Get the plugin
            try:
                plugin_mod = plugins.loaded_plugins[name]
            except KeyError:
                continue

            if not hasattr(plugin_mod, 'toolbars'):
                roam.utils.warning(
                    "No toolbars() function found in {}".format(name))
                continue

            toolbars = plugin_mod.toolbars()
            self.load_plugin_toolbars(toolbars)

    def load_plugin_toolbars(self, toolbars):
        for ToolBarClass in toolbars:
            toolbar = ToolBarClass(plugins.api, self)
            self.addToolBar(Qt.BottomToolBarArea, toolbar)
            toolbar.setProperty("plugin_toolbar", True)

    def snapping_changed(self, snapping):
        """
        Called when the snapping settings have changed. Updates the label in the status bar.
        :param snapping:
        """
        if snapping:
            self.snappingbutton.setText("Snapping: On")
        else:
            self.snappingbutton.setText("Snapping: Off")

    def toggle_snapping(self):
        """
        Toggle snapping on or off.
        """
        self.snapping = not self.snapping
        try:
            self.canvas.mapTool().toggle_snapping()
        except AttributeError:
            pass

        RoamEvents.snappingChanged.emit(self.snapping)

    def selectscale(self):
        """
        Show the select scale widget.
        :return:
        """
        self.scalelist.show()

    def update_scale_from_item(self, index):
        """
        Update the canvas scale from the selected scale item.
        :param index: The index of the selected item.
        """
        scale, _ = self.scalewidget.toDouble(index.data(Qt.DisplayRole))
        self.canvas.zoomScale(1.0 / scale)

    def update_gps_label(self, position, gpsinfo):
        """
        Update the GPS label in the status bar with the GPS status.
        :param position: The current GPS position.
        :param gpsinfo: The current extra GPS information.
        """
        self.gpslabel.setText(
            "GPS: PDOP <b>{0:.2f}</b> HDOP <b>{1:.2f}</b>    VDOP <b>{2:.2f}</b>"
            .format(gpsinfo.pdop, gpsinfo.hdop, gpsinfo.vdop))

        places = roam.config.settings.get("gpsplaces", 8)
        self.gpslabelposition.setText(
            "X <b>{x:.{places}f}</b> Y <b>{y:.{places}f}</b>".format(
                x=position.x(), y=position.y(), places=places))

    def gps_disconnected(self):
        """
        Called when the GPS is disconnected. Updates the label in the status bar with the message.
        :return:
        """
        self.gpslabel.setText("GPS Not Active")
        self.gpslabelposition.setText("")

    def zoom_to_feature(self, feature):
        box = feature.geometry().boundingBox()
        xmin, xmax, ymin, ymax = box.xMinimum(), box.xMaximum(), box.yMinimum(
        ), box.yMaximum()
        xmin -= 5
        xmax += 5
        ymin -= 5
        ymax += 5
        box = QgsRectangle(xmin, ymin, xmax, ymax)
        self.canvas.setExtent(box)
        self.canvas.refresh()

    def updatestatuslabel(self, *args):
        """
        Update the status bar labels when the information has changed.
        """
        extent = self.canvas.extent()
        self.positionlabel.setText("Map Center: {}".format(
            extent.center().toString()))
        scale = 1.0 / self.canvas.scale()
        scale = self.scalewidget.toString(scale)
        self.scalebutton.setText(scale)

    def refresh_map(self):
        """
        Refresh the map
        """
        self.canvas.refresh()

    def updatescale(self):
        """
        Update the scale of the map with the current scale from the scale widget
        :return:
        """
        self.canvas.zoomScale(1.0 / self.scalewidget.scale())

    def init_qgisproject(self, doc):
        """
        Called when the project file is read for the firs time.
        :param doc: The XML doc.
        :return: The current canvas CRS
        :note: This method is old and needs to be refactored into something else.
        """
        return self.canvas.mapSettings().destinationCrs()

    def showEvent(self, *args, **kwargs):
        """
        Handle the show event of the of the map widget. We have to do a little hack here to make the QGIS map refresh.
        """
        if QGis.QGIS_VERSION_INT == 20200 and self.firstshow:
            self.canvas.refresh()
            self.canvas.repaint()
            self.firstshow = False

    def feature_form_loaded(self, form, feature, *args):
        """
        Called when the feature form is loaded.
        :param form: The Form object. Holds a reference to the forms layer.
        :param feature: The current capture feature
        """
        self.currentfeatureband.setToGeometry(feature.geometry(),
                                              form.QGISLayer)

    def highlight_selection(self, results):
        """
        Highlight the selection on the canvas.  This updates all selected objects based on the result set.
        :param results: A dict-of-list of layer-features.
        """
        self.clear_selection()
        for layer, features in results.iteritems():
            band = self.selectionbands[layer]
            band.setColor(QColor(255, 0, 0))
            band.setIconSize(25)
            band.setWidth(5)
            band.setBrushStyle(Qt.NoBrush)
            band.reset(layer.geometryType())
            band.setZValue(self.currentfeatureband.zValue() - 1)
            for feature in features:
                band.addGeometry(feature.geometry(), layer)
        self.canvas.update()

    def highlight_active_selection(self, layer, feature, features):
        """
        Update the current active selected feature.
        :param layer: The layer of the active feature.
        :param feature: The active feature.
        :param features: The other features in the set to show as non active selection.
        :return:
        """
        self.clear_selection()
        self.highlight_selection({layer: features})
        self.currentfeatureband.setToGeometry(feature.geometry(), layer)
        self.canvas.update()

    def clear_selection(self):
        """
        Clear the selection from the canvas.   Resets all selection rubbber bands.
        :return:
        """
        # Clear the main selection rubber band
        self.canvas.scene().update()
        self.currentfeatureband.reset()
        # Clear the rest
        for band in self.selectionbands.itervalues():
            band.reset()

        self.canvas.update()
        self.editfeaturestack = []

    def queue_feature_for_edit(self, form, feature):
        """
        Push a feature on the edit stack so the feature can have the geometry edited.
        :note: This is a big hack and I don't like it!
        :param form: The form for the current feature
        :param feature: The active feature.
        """
        def trigger_default_action():
            for action in self.projecttoolbar.actions():
                if action.property('dataentry') and action.isdefault:
                    action.trigger()
                    self.canvas.mapTool().setEditMode(True, feature.geometry())
                    break

        self.editfeaturestack.append((form, feature))
        self.save_current_form()
        self.load_form(form)
        trigger_default_action()

    def save_current_form(self):
        self.last_form = self.current_form

    def restore_last_form(self):
        self.load_form(self.last_form)

    def clear_temp_objects(self):
        """
        Clear all temp objects from the canvas.
        :return:
        """
        def clear_tool_band():
            """
            Clear the rubber band of the active tool if it has one
            """
            tool = self.canvas.mapTool()
            try:
                tool.clearBand()
            except AttributeError:
                # No clearBand method found, but that's cool.
                pass

        self.currentfeatureband.reset()
        clear_tool_band()

    def settings_updated(self, settings):
        """
        Called when the settings have been updated in the Roam config.
        :param settings: A dict of the settings.
        """
        self.actionGPS.updateGPSPort()
        gpslogging = settings.get('gpslogging', True)
        if self.gpslogging:
            self.gpslogging.logging = gpslogging

    def set_gps(self, gps, logging):
        """
        Set the GPS for the map widget.  Connects GPS signals
        """
        self.gps = gps
        self.gpslogging = logging
        self.gps.gpsposition.connect(self.gps_update_canvas)
        self.gps.firstfix.connect(self.gps_first_fix)
        self.gps.gpsdisconnected.connect(self.gps_disconnected)

    def gps_update_canvas(self, position, gpsinfo):
        """
        Updates the map canvas based on the GPS position.  By default if the GPS is outside the canvas
        extent the canvas will move to center on the GPS.  Can be turned off in settings.
        :param postion: The current GPS position.
        :param gpsinfo: The extra GPS information
        """
        # Recenter map if we go outside of the 95% of the area
        if self.gpslogging.logging:
            self.gpsband.addPoint(position)
            self.gpsband.show()

        if roam.config.settings.get('gpscenter', True):
            if not self.lastgpsposition == position:
                self.lastposition = position
                rect = QgsRectangle(position, position)
                extentlimt = QgsRectangle(self.canvas.extent())
                extentlimt.scale(0.95)

                if not extentlimt.contains(position):
                    self.zoom_to_location(position)

        self.marker.show()
        self.marker.setCenter(position, gpsinfo)

    def gps_first_fix(self, postion, gpsinfo):
        """
        Called the first time the GPS gets a fix.  If set this will zoom to the GPS after the first fix
        :param postion: The current GPS position.
        :param gpsinfo: The extra GPS information
        """
        zoomtolocation = roam.config.settings.get('gpszoomonfix', True)
        if zoomtolocation:
            self.canvas.zoomScale(1000)
            self.zoom_to_location(postion)

    def zoom_to_location(self, position):
        """
        Zoom to ta given position on the map..
        """
        rect = QgsRectangle(position, position)
        self.canvas.setExtent(rect)
        self.canvas.refresh()

    def gps_disconnected(self):
        """
        Called when the GPS is disconnected
        """
        self.marker.hide()

    def select_data_entry(self):
        """
        Open the form selection widget to allow the user to pick the active capture form.
        """
        def showformerror(form):
            pass

        def actions():
            for form in self.project.forms:
                if not self.form_valid_for_capture(form):
                    continue

                action = form.createuiaction()
                valid, failreasons = form.valid
                if not valid:
                    roam.utils.warning("Form {} failed to load".format(
                        form.label))
                    roam.utils.warning("Reasons {}".format(failreasons))
                    action.triggered.connect(partial(showformerror, form))
                else:
                    action.triggered.connect(partial(self.load_form, form))
                yield action

        formpicker = PickActionDialog(msg="Select data entry form", wrap=5)
        formpicker.addactions(actions())
        formpicker.exec_()

    def project_loaded(self, project):
        """
        Called when the project is loaded. Main entry point for a loade project.
        :param project: The Roam project that has been loaded.
        """
        self.project = project
        self.actionPan.trigger()
        firstform = self.first_capture_form()
        if firstform:
            self.load_form(firstform)
            self.dataentryselection.setVisible(True)
        else:
            self.dataentryselection.setVisible(False)

        # Enable the raster layers button only if the project contains a raster layer.
        layers = QgsMapLayerRegistry.instance().mapLayers().values()
        hasrasters = any(layer.type() == QgsMapLayer.RasterLayer
                         for layer in layers)
        self.actionRaster.setEnabled(hasrasters)
        self.defaultextent = self.canvas.extent()
        roam.utils.info("Extent: {}".format(self.defaultextent.toString()))

        self.infoTool.selectionlayers = project.selectlayersmapping()

        self.canvas.refresh()

        projectscales, _ = QgsProject.instance().readBoolEntry(
            "Scales", "/useProjectScales")
        if projectscales:
            projectscales, _ = QgsProject.instance().readListEntry(
                "Scales", "/ScalesList")

            self.scalewidget.updateScales(projectscales)
        else:
            scales = [
                "1:50000", "1:25000", "1:10000", "1:5000", "1:2500", "1:1000",
                "1:500", "1:250", "1:200", "1:100"
            ]
            scales = roam.config.settings.get('scales', scales)
            self.scalewidget.updateScales(scales)

        if self.scalebar_enabled:
            self.scalebar.update()

        self.actionPan.toggle()
        self.clear_plugins()
        self.add_plugins(project.enabled_plugins)

    def setMapTool(self, tool, *args):
        """
        Set the active map tool in the canvas.
        :param tool: The QgsMapTool to set.
        """
        if tool == self.canvas.mapTool():
            return

        if hasattr(tool, "setSnapping"):
            tool.setSnapping(self.snapping)
        self.canvas.setMapTool(tool)

    def connectButtons(self):
        """
        Connect the default buttons in the interface. Zoom, pan, etc
        """
        def connectAction(action, tool):
            action.toggled.connect(partial(self.setMapTool, tool))

        def cursor(name):
            pix = QPixmap(name)
            pix = pix.scaled(QSize(24, 24))
            return QCursor(pix)

        self.zoomInTool = QgsMapToolZoom(self.canvas, False)
        self.zoomOutTool = QgsMapToolZoom(self.canvas, True)
        self.panTool = PanTool(self.canvas)
        self.infoTool = InfoTool(self.canvas)

        self.infoTool.setAction(self.actionInfo)
        self.zoomInTool.setAction(self.actionZoom_In)
        self.zoomOutTool.setAction(self.actionZoom_Out)
        self.panTool.setAction(self.actionPan)

        connectAction(self.actionZoom_In, self.zoomInTool)
        connectAction(self.actionZoom_Out, self.zoomOutTool)
        connectAction(self.actionPan, self.panTool)
        connectAction(self.actionInfo, self.infoTool)

        self.zoomInTool.setCursor(cursor(':/icons/in'))
        self.zoomOutTool.setCursor(cursor(':/icons/out'))
        self.infoTool.setCursor(cursor(':/icons/select'))

        self.actionRaster.triggered.connect(self.toggleRasterLayers)
        self.actionHome.triggered.connect(self.homeview)

    def homeview(self):
        """
        Zoom the mapview canvas to the extents the project was opened at i.e. the
        default extent.
        """
        self.canvas.setExtent(self.defaultextent)
        self.canvas.refresh()

    def form_valid_for_capture(self, form):
        """
        Check if the given form is valid for capture.
        :param form: The form to check.
        :return: True if valid form for capture
        """
        return form.has_geometry and self.project.layer_can_capture(
            form.QGISLayer)

    def first_capture_form(self):
        """
        Return the first valid form for capture.
        """
        for form in self.project.forms:
            if self.form_valid_for_capture(form):
                return form

    def load_form(self, form):
        """
        Load the given form so it's the active one for capture
        :param form: The form to load
        """
        self.clearCaptureTools()
        self.dataentryselection.setIcon(QIcon(form.icon))
        self.dataentryselection.setText(form.icontext)
        self.create_capture_buttons(form)
        self.current_form = form

    def create_capture_buttons(self, form):
        """
        Create the capture buttons in the toolbar for the given form.
        :param form: The active form.
        """
        layer = form.QGISLayer
        tool = form.getMaptool()(self.canvas, form.settings)
        for action in tool.actions:
            # Create the action here.
            if action.ismaptool:
                action.toggled.connect(partial(self.setMapTool, tool))

            # Set the action as a data entry button so we can remove it later.
            action.setProperty("dataentry", True)
            self.editgroup.addAction(action)
            self.layerbuttons.append(action)
            self.projecttoolbar.insertAction(self.topspaceraction, action)
            action.setChecked(action.isdefault)

        if hasattr(tool, 'geometryComplete'):
            add = partial(self.add_new_feature, form)
            tool.geometryComplete.connect(add)
        else:
            tool.finished.connect(self.openForm)

        tool.error.connect(self.show_invalid_geometry_message)

    def show_invalid_geometry_message(self, message):
        RoamEvents.raisemessage("Invalid geometry capture",
                                message,
                                level=RoamEvents.CRITICAL)

    def add_new_feature(self, form, geometry):
        """
        Add a new new feature to the given layer
        """
        # TODO Extract into function.
        # NOTE This function is doing too much, acts as add and also edit.
        layer = form.QGISLayer
        if layer.geometryType() in [
                QGis.WKBMultiLineString, QGis.WKBMultiPoint,
                QGis.WKBMultiPolygon
        ]:
            geometry.convertToMultiType()

        try:
            form, feature = self.editfeaturestack.pop()
            self.editfeaturegeometry(form, feature, newgeometry=geometry)
            return
        except IndexError:
            pass

        feature = form.new_feature(geometry=geometry)
        RoamEvents.load_feature_form(form, feature, editmode=False)

    def editfeaturegeometry(self, form, feature, newgeometry):
        # TODO Extract into function.
        layer = form.QGISLayer
        layer.startEditing()
        feature.setGeometry(newgeometry)
        layer.updateFeature(feature)
        saved = layer.commitChanges()
        if not saved:
            map(roam.utils.error, layer.commitErrors())
        self.canvas.refresh()
        self.currentfeatureband.setToGeometry(feature.geometry(), layer)
        RoamEvents.editgeometry_complete.emit(form, feature)
        self.canvas.mapTool().setEditMode(False, None)
        self.restore_last_form()

    def clearCaptureTools(self):
        """
        Clear the capture tools from the toolbar.
        :return: True if the capture button was active at the time of clearing.
        """
        captureselected = False
        for action in self.projecttoolbar.actions():
            if action.objectName() == "capture" and action.isChecked():
                captureselected = True

            if action.property('dataentry'):
                self.projecttoolbar.removeAction(action)
        return captureselected

    def toggleRasterLayers(self):
        """
        Toggle all raster layers on or off.
        """
        # Freeze the canvas to save on UI refresh
        self.canvas.freeze()
        tree = QgsProject.instance().layerTreeRoot()
        for node in tree.findLayers():
            if node.layer().type() == QgsMapLayer.RasterLayer:
                if node.isVisible() == Qt.Checked:
                    state = Qt.Unchecked
                else:
                    state = Qt.Checked
                node.setVisible(state)

        self.canvas.freeze(False)
        self.canvas.refresh()

    def cleanup(self):
        """
        Clean up when the project has changed.
        :return:
        """
        self.bridge.clear()
        self.gpsband.reset()
        self.gpsband.hide()
        self.clear_selection()
        self.clear_temp_objects()
        self.clearCaptureTools()
        self.canvas.freeze()
        self.canvas.clear()
        self.canvas.freeze(False)
        for action in self.layerbuttons:
            self.editgroup.removeAction(action)
Example #29
0
    def add_action(
        self,
        name,
        icon_path,
        text,
        callback,
        toggle_flag=False,
        enabled_flag=True,
        checkable_flag=False,
        visible_flag=True,
        add_to_menu=True,
        add_to_toolbar=True,
        status_tip=None,
        whats_this=None,
        parent=None):
        """Add a toolbar icon to the toolbar.
        :param name: Objectname of the action. Serves also as key for the stored actions.
        :type name: str

        :param icon_path: Path to the icon for this action. Can be a resource
            path (e.g. ':/plugins/foo/bar.png') or a normal file system path.
        :type icon_path: str

        :param text: Text that should be shown in menu items for this action.
        :type text: str

        :param callback: Function to be called when the action is triggered.
        :type callback: function

        :param enabled_flag: A flag indicating if the action should be enabled
            by default. Defaults to True.
        :type enabled_flag: bool

        :param toggle_flag: A flag indicating if the action should connect
            the toggled or triggered signal by default.
            Defaults to triggered (False)
        :type toggle_flag: bool

        :param checkable_flag: A flag indicating if the action should be checkable
            by default. Defaults to False.
        :type checkable: bool

        :param visible_flag: A flag indicating if the action should be displayed
            by default. Defaults to True.
        :type visible: bool

        :param add_to_menu: Flag indicating whether the action should also
            be added to the menu. Defaults to True.
        :type add_to_menu: bool

        :param add_to_toolbar: Flag indicating whether the action should also
            be added to the toolbar. Defaults to True.
        :type add_to_toolbar: bool

        :param status_tip: Optional text to show in a popup when mouse pointer
            hovers over the action.
        :type status_tip: str

        :param parent: Parent widget for the new action. Defaults None.
        :type parent: QWidget

        :param whats_this: Optional text to show in the status bar when the
            mouse pointer hovers over the action.

        :returns: The action that was created. Note that the action is also
            added to self.actions list.
        :rtype: QAction
        """

        icon = QIcon(icon_path)
        action = QAction(icon, text, parent)
        action.setObjectName(name)
        if toggle_flag:
            action.toggled.connect(callback)
        else:
            action.triggered.connect(callback)
        action.setEnabled(enabled_flag)
        action.setCheckable(checkable_flag)
        action.setVisible(visible_flag)

        if status_tip is not None:
            action.setStatusTip(status_tip)

        if whats_this is not None:
            action.setWhatsThis(whats_this)

        if add_to_toolbar:
            self.toolbar.addAction(action)

        if add_to_menu:
            self.iface.addPluginToMenu(
                self.menu,
                action)

        self.actions[name] = action

        return action
Example #30
0
class QMap():
    def __init__(self, iface):
        self.iface = iface
        self.actions = []
        self.panels= []
        self.navtoolbar = self.iface.mapNavToolToolBar()
        self.mainwindow = self.iface.mainWindow()
        self.iface.projectRead.connect(self.projectOpened)
        self.iface.initializationCompleted.connect(self.setupUI)
        self.actionGroup = QActionGroup(self.mainwindow)
        self.actionGroup.setExclusive(True)
        self.menuGroup = QActionGroup(self.mainwindow)
        self.menuGroup.setExclusive(True)
                
        self.movetool = MoveTool(self.iface.mapCanvas(), [])
        self.infotool = InfoTool(self.iface.mapCanvas())
        self.infotool.infoResults.connect(self.showInfoResults)
        
        self.report = PopDownReport(self.iface.messageBar())
        
        self.dialogprovider = DialogProvider(iface.mapCanvas(), iface)
        self.dialogprovider.accepted.connect(self.clearToolRubberBand)
        self.dialogprovider.rejected.connect(self.clearToolRubberBand)
        
        self.edittool = EditTool(self.iface.mapCanvas(),[])
        self.edittool.finished.connect(self.openForm)
        self.edittool.featuresfound.connect(self.showFeatureSelection)

        self.infodock = InfoDock(self.iface.mainWindow())
        self.iface.addDockWidget(Qt.RightDockWidgetArea, self.infodock)
        self.infodock.hide()

        self.band = QgsRubberBand(self.iface.mapCanvas())
        self.band.setIconSize(20)
        self.band.setWidth(10)
        self.band.setColor(QColor(186, 93, 212, 76))

    def showFeatureSelection(self, features):
        listUi = ListFeaturesForm(self.mainwindow)
        listUi.loadFeatureList(features)
        listUi.openFeatureForm.connect(self.openForm)
        listUi.exec_()

    def showInfoResults(self, results):
        self.infodock.clearResults()
        self.infodock.setResults(results)
        self.infodock.show()
        self.infodock.repaint()
        
    @property
    def _mapLayers(self):
        return QgsMapLayerRegistry.instance().mapLayers()
        
    def clearToolRubberBand(self):
        tool = self.iface.mapCanvas().mapTool()
        try:
            tool.clearBand()
        except AttributeError:
            # No clearBand method found, but that's cool.
            pass
        
    def missingLayers(self, layers):
        def showError():
            html = ["<h1>Missing Layers</h1>", "<ul>"]
            for layer in layers:
                html.append("<li>{}</li>".format(layer))
            html.append("</ul>")
            
            self.errorreport.updateHTML("".join(html))
        
        message = "Seems like {} didn't load correctly".format(utils._pluralstring('layer', len(layers)))
        
        utils.warning("Missing layers")
        map(utils.warning, layers)
            
        self.widget = self.iface.messageBar().createMessage("Missing Layers",
                                                 message, 
                                                 QIcon(":/icons/sad"))
        button = QPushButton(self.widget)
        button.setCheckable(True)
        button.setChecked(self.errorreport.isVisible())
        button.setText("Show missing layers")
        button.toggled.connect(showError)
        button.toggled.connect(functools.partial(self.errorreport.setVisible))
        self.widget.destroyed.connect(self.hideReports)
        self.widget.layout().addWidget(button)
        self.iface.messageBar().pushWidget(self.widget, QgsMessageBar.WARNING)
        
    def excepthook(self, ex_type, value, tb):
        """ 
        Custom exception hook so that we can handle errors in a
        nicer way
        """
        where = ''.join(traceback.format_tb(tb))
        msg = '{}'.format(value)
        utils.critical(msg)
        
        def showError():
            html = """
                <html>
                <body bgcolor="#FFEDED">
                <p><b>{}</b></p>
                <p align="left"><small>{}</small></p>
                </body>
                </html>
            """.format(msg, where)
            self.errorreport.updateHTML(html)
        
        self.widget = self.iface.messageBar().createMessage("oops", "Looks like an error occurred", QIcon(":/icons/sad"))
        button = QPushButton(self.widget)
        button.setCheckable(True)
        button.setChecked(self.errorreport.isVisible())
        button.setText("Show error")
        button.toggled.connect(showError)
        button.toggled.connect(functools.partial(self.errorreport.setVisible))
        self.widget.destroyed.connect(self.hideReports)
        self.widget.layout().addWidget(button)
        self.messageBar.pushWidget(self.widget, QgsMessageBar.CRITICAL)
    
    def hideReports(self):
        self.errorreport.setVisible(False)
        self.report.setVisible(False)
    
    def setupUI(self):
        """
        Set up the main QGIS interface items.  Called after QGIS has loaded
        the plugin.
        """
        self.updateAppSize()
        
        utils.settings_notify.settings_changed.connect(self.updateAppSize)

        self.navtoolbar.setMovable(False)
        self.navtoolbar.setAllowedAreas(Qt.TopToolBarArea)
        
        self.mainwindow.insertToolBar(self.toolbar, self.navtoolbar)
        self.openProjectAction.trigger()
        
    def updateAppSize(self):
        fullscreen = utils.settings.get("fullscreen", False)
        if fullscreen:
            self.mainwindow.showFullScreen()
        else:
            self.mainwindow.showMaximized()
            
    def setMapTool(self, tool):
        """
        Set the current mapview canvas tool

        tool -- The QgsMapTool to set
        """
        self.iface.mapCanvas().setMapTool(tool)

    def createToolBars(self):
        """
        Create all the needed toolbars
        """
        
        self.menutoolbar = QToolBar("Menu", self.mainwindow)
        self.menutoolbar.setMovable(False)
        self.menutoolbar.setAllowedAreas(Qt.LeftToolBarArea)
        self.mainwindow.addToolBar(Qt.LeftToolBarArea, self.menutoolbar)
        
        self.toolbar = QToolBar("QMap", self.mainwindow)
        self.mainwindow.addToolBar(Qt.TopToolBarArea, self.toolbar)
        self.toolbar.setMovable(False)

        self.editingtoolbar = FloatingToolBar("Editing", self.toolbar)
        self.extraaddtoolbar = FloatingToolBar("Extra Add Tools", self.toolbar)
        self.syncactionstoolbar = FloatingToolBar("Syncing", self.toolbar)
        self.syncactionstoolbar.setOrientation(Qt.Vertical)

    def createActions(self):
        """
        Create all the actions
        """

        self.homeAction = (QAction(QIcon(":/icons/zoomfull"),
                                  "Default View", self.mainwindow))
        self.gpsAction = (GPSAction(QIcon(":/icons/gps"), self.iface.mapCanvas(),
                                   self.mainwindow))
        
        self.openProjectAction = (QAction(QIcon(":/icons/open"), "Projects",
                                         self.mainwindow))
        self.openProjectAction.setCheckable(True)
        
        self.configAction = (QAction(QIcon(":/icons/config"), "Settings",
                                         self.mainwindow))
        self.configAction.setCheckable(True)
        
        self.toggleRasterAction = (QAction(QIcon(":/icons/photo"), "Aerial Photos",
                                          self.mainwindow))
        self.syncAction = QAction(QIcon(":/icons/sync"), "Sync", self.mainwindow)
        self.syncAction.setVisible(False)
        
        self.editattributesaction = QAction(QIcon(":/icons/edit"), "Edit Attributes", self.mainwindow)
        self.editattributesaction.setCheckable(True)
        self.editattributesaction.toggled.connect(functools.partial(self.setMapTool, self.edittool))

        self.moveaction = QAction(QIcon(":/icons/move"), "Move Feature", self.mainwindow)
        self.moveaction.setCheckable(True)

        self.editingmodeaction = QAction(QIcon(":/icons/edittools"), "Edit Tools", self.mainwindow)
        self.editingmodeaction.setCheckable(True)
        
        self.infoaction = QAction(QIcon(":/icons/info"), "Info", self.mainwindow)
        self.infoaction.setCheckable(True)

        self.addatgpsaction = QAction(QIcon(":/icons/gpsadd"), "Add at GPS", self.mainwindow)
        
        self.edittool.layersupdated.connect(self.editattributesaction.setVisible)
        self.movetool.layersupdated.connect(self.moveaction.setVisible)
        self.movetool.layersupdated.connect(self.editingmodeaction.setVisible)
        
    def initGui(self):
        """
        Create all the icons and setup the tool bars.  Called by QGIS when
        loading. This is called before setupUI.
        """
        QApplication.setWindowIcon(QIcon(":/branding/logo"))
        self.mainwindow.findChildren(QMenuBar)[0].setVisible(False)
        self.mainwindow.setContextMenuPolicy(Qt.PreventContextMenu)
        self.mainwindow.setWindowTitle("IntraMaps Roam: Mobile Data Collection")
        
        # Disable QGIS logging window popups. We do our own logging
        QgsMessageLog.instance().messageReceived.disconnect()
        
        s = """
        QToolButton { 
            padding: 6px;
            color: #4f4f4f;
         }
        
        QToolButton:hover { 
            padding: 6px;
            background-color: rgb(211, 228, 255);
         }
         
        QToolBar {
         background: white;
        }
        
        QCheckBox::indicator {
             width: 40px;
             height: 40px;
         }
        
        QLabel {
            color: #4f4f4f;
        }
        
        QDialog { background-color: rgb(255, 255, 255); }
        
        QPushButton { 
            border: 1px solid #e1e1e1;
             padding: 6px;
            color: #4f4f4f;
         }
        
        QPushButton:hover { 
            border: 1px solid #e1e1e1;
             padding: 6px;
            background-color: rgb(211, 228, 255);
         }
        
        QCheckBox {
            color: #4f4f4f;
        }
        
        QComboBox::drop-down {
            width: 30px;
        }
        
        QComboBox {
            border: 1px solid #d3d3d3;
        }

        QStackedWidget {
             background-color: rgb(255, 255, 255);
        }
        """
        self.mainwindow.setStyleSheet(s)
        
        mainwidget = self.mainwindow.centralWidget()
        mainwidget.setLayout(QGridLayout())
        mainwidget.layout().setContentsMargins(0,0,0,0)
        
        newlayout = QGridLayout()
        newlayout.setContentsMargins(0,0,0,0)
        newlayout.addWidget(self.iface.mapCanvas(), 0,0,2,1)
        newlayout.addWidget(self.iface.messageBar(), 0,0,1,1)
        
        wid = QWidget()
        wid.setLayout(newlayout)
        
        self.stack = QStackedWidget(self.mainwindow)
        self.messageBar = QgsMessageBar(wid)
        self.messageBar.setSizePolicy( QSizePolicy.Minimum, QSizePolicy.Fixed )
        self.errorreport = PopDownReport(self.messageBar)

        mainwidget.layout().addWidget(self.stack, 0,0,2,1)
        mainwidget.layout().addWidget(self.messageBar, 0,0,1,1)

        self.helppage = HelpPage()
        helppath = os.path.join(os.path.dirname(__file__) , 'help',"help.html")
        self.helppage.setHelpPage(helppath)
        
        self.settingswidget = SettingsWidget(self.stack)
        
        self.projectwidget = ProjectsWidget()   
        self.projectwidget.requestOpenProject.connect(self.loadProject)
        self.stack.addWidget(wid)
        self.stack.addWidget(self.projectwidget)
        self.stack.addWidget(self.helppage)
        self.stack.addWidget(self.settingswidget)
                
        sys.excepthook = self.excepthook

        def createSpacer(width=30):
            widget = QWidget()
            widget.setMinimumWidth(width)
            return widget

        self.createToolBars()
        self.createActions()

        spacewidget = createSpacer(60)
        gpsspacewidget = createSpacer()
        gpsspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.moveaction.toggled.connect(functools.partial(self.setMapTool, self.movetool))
        self.infoaction.toggled.connect(functools.partial(self.setMapTool, self.infotool))
        
        showediting = (functools.partial(self.editingtoolbar.showToolbar, 
                                         self.editingmodeaction,
                                         self.moveaction))
        self.editingmodeaction.toggled.connect(showediting)

        self.addatgpsaction.triggered.connect(self.addAtGPS)
        self.addatgpsaction.setEnabled(self.gpsAction.isConnected)
        self.gpsAction.gpsfixed.connect(self.addatgpsaction.setEnabled)

        self.editingtoolbar.addToActionGroup(self.moveaction)

        self.actionGroup.addAction(self.editingmodeaction)
        self.actionGroup.addAction(self.editattributesaction)
        self.actionGroup.addAction(self.infoaction)

        self.homeAction.triggered.connect(self.zoomToDefaultView)
        
        self.openProjectAction.triggered.connect(self.showOpenProjectDialog)
        self.openProjectAction.triggered.connect(functools.partial(self.stack.setCurrentIndex, 1))
        
        self.configAction.triggered.connect(functools.partial(self.stack.setCurrentIndex, 3))
        self.configAction.triggered.connect(self.settingswidget.populateControls)
        self.configAction.triggered.connect(self.settingswidget.readSettings)
        
        self.toggleRasterAction.triggered.connect(self.toggleRasterLayers)

        self.navtoolbar.insertAction(self.iface.actionZoomIn(), self.iface.actionTouch())
        self.navtoolbar.insertAction(self.iface.actionTouch(), self.homeAction)
        self.navtoolbar.insertAction(self.iface.actionTouch(), self.iface.actionZoomFullExtent())
        self.navtoolbar.insertAction(self.homeAction, self.iface.actionZoomFullExtent())

        self.navtoolbar.addAction(self.toggleRasterAction)
        self.navtoolbar.insertWidget(self.iface.actionZoomFullExtent(), spacewidget)
        self.toolbar.addAction(self.infoaction)
        self.toolbar.addAction(self.editingmodeaction)
        self.toolbar.addAction(self.editattributesaction)
        self.toolbar.addAction(self.syncAction)
        self.toolbar.addAction(self.gpsAction)
        self.toolbar.insertWidget(self.syncAction, gpsspacewidget)
        self.toolbar.insertSeparator(self.gpsAction)

        self.extraaddtoolbar.addAction(self.addatgpsaction)

        self.editingtoolbar.addAction(self.moveaction)
        
        self.mapview = QAction(QIcon(":/icons/map"), "Map", self.menutoolbar)
        self.mapview.setCheckable(True)
        self.mapview.triggered.connect(functools.partial(self.stack.setCurrentIndex, 0))
        
        self.help = QAction(QIcon(":/icons/help"), "Help", self.menutoolbar)
        self.help.setCheckable(True)
        self.help.triggered.connect(functools.partial(self.stack.setCurrentIndex, 2))
        self.help.setVisible(False)
        
        self.projectlabel = QLabel("Project: <br> None")
        self.projectlabel.setAlignment(Qt.AlignCenter)
        self.projectlabel.setStyleSheet("""
            QLabel {
                    color: #8c8c8c;
                    font: 10px "Calibri" ;
                    }""")

        self.userlabel = QLabel("User: <br> {user}".format(user=getpass.getuser()))
        self.userlabel.setAlignment(Qt.AlignCenter)
        self.userlabel.setStyleSheet("""
            QLabel {
                    color: #8c8c8c;
                    font: 10px "Calibri" ;
                    }""")
        
        self.quit = QAction(QIcon(":/icons/quit"), "Quit", self.menutoolbar)
        self.quit.triggered.connect(self.iface.actionExit().trigger)

        self.menuGroup.addAction(self.mapview)
        self.menuGroup.addAction(self.openProjectAction)
        self.menuGroup.addAction(self.help)
        self.menuGroup.addAction(self.configAction)
        
        self.menutoolbar.addAction(self.mapview)
        self.menutoolbar.addAction(self.openProjectAction)
        self.menutoolbar.addAction(self.help)
        self.menutoolbar.addAction(self.configAction)
        self.menutoolbar.addAction(self.quit)
        
        quitspacewidget = createSpacer()
        quitspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        
        labelaction = self.menutoolbar.insertWidget(self.configAction, self.userlabel)
        
        self.menutoolbar.insertWidget(labelaction, quitspacewidget)
        self.menutoolbar.insertWidget(labelaction, self.projectlabel)
        self.setupIcons()
        self.stack.currentChanged.connect(self.updateUIState)
        
    def updateUIState(self, page):
        """
        Update the UI state to reflect the currently selected
        page in the stacked widget
        """
        def setToolbarsActive(enabled):
            toolbars = self.mainwindow.findChildren(QToolBar)
            for toolbar in toolbars:
                if toolbar == self.menutoolbar:
                    continue
                toolbar.setEnabled(enabled)
        
        def setPanelsVisible(visible):
            for panel in self.panels:
                panel.setVisible(visible)
                
        ismapview = page == 0
        setToolbarsActive(ismapview)
        setPanelsVisible(ismapview)
        self.infodock.hide()

    def addAtGPS(self):
        """
        Add a record at the current GPS location.
        """
        action = self.actionGroup.checkedAction()
        if not action:
            return
        layer = action.data()
        if not layer:
            return
        
        point = self.gpsAction.position
        self.addNewFeature(layer=layer, geometry=point)
        
    def zoomToDefaultView(self):
        """
        Zoom the mapview canvas to the extents the project was opened at i.e. the
        default extent.
        """
        self.iface.mapCanvas().setExtent(self.defaultextent)
        self.iface.mapCanvas().refresh()

    def toggleRasterLayers(self):
        """
        Toggle all raster layers on or off.
        """
        legend = self.iface.legendInterface()
        #Freeze the canvas to save on UI refresh
        self.iface.mapCanvas().freeze()
        for layer in self._mapLayers.values():
            if layer.type() == QgsMapLayer.RasterLayer:
                isvisible = legend.isLayerVisible(layer)
                legend.setLayerVisible(layer, not isvisible)
        self.iface.mapCanvas().freeze(False)
        self.iface.mapCanvas().refresh()

    def setupIcons(self):
        """
        Update toolbars to have text and icons, change normal QGIS
        icons to new style
        """
        toolbars = self.mainwindow.findChildren(QToolBar)
        for toolbar in toolbars:
            toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
            toolbar.setIconSize(QSize(32, 32))

        self.iface.actionTouch().setIconText("Pan")
        self.iface.actionTouch().setIcon(QIcon(":/icons/pan"))
        self.iface.actionZoomIn().setIcon(QIcon(":/icons/in"))
        self.iface.actionZoomOut().setIcon(QIcon(":/icons/out"))
        self.iface.actionPan().setIcon(QIcon(":/icons/pan"))
        self.iface.actionZoomFullExtent().setIcon(QIcon(":/icons/home"))
        self.iface.actionZoomFullExtent().setIconText("Home View")

        self.actionGroup.addAction(self.iface.actionZoomIn())
        self.actionGroup.addAction(self.iface.actionZoomOut())
        self.actionGroup.addAction(self.iface.actionTouch())

    def projectOpened(self):
        """
            Called when a new project is opened in QGIS.
        """
        for panel in self.panels:
            self.mainwindow.removeDockWidget(panel)
            del panel

        projectpath = QgsProject.instance().fileName()
        project = QMapProject(os.path.dirname(projectpath), self.iface)
        self.projectlabel.setText("Project: <br> {}".format(project.name))
        self.createFormButtons(projectlayers = project.getConfiguredLayers())
        
        # Enable the raster layers button only if the project contains a raster layer.
        hasrasters = any(layer.type() for layer in self._mapLayers.values())
        self.toggleRasterAction.setEnabled(hasrasters)
        self.defaultextent = self.iface.mapCanvas().extent()
        self.connectSyncProviders(project)
        
        # Show panels
        self.panels = list(project.getPanels())
        for panel in self.panels:
            self.mainwindow.addDockWidget(Qt.BottomDockWidgetArea , panel)
            
        self.iface.messageBar().popWidget()

    def captureLayer(self, layer):
        text = layer.icontext
        tool = layer.getMaptool(self.iface.mapCanvas())

        # Hack until I fix it later
        if isinstance(tool, PointTool):
            add = functools.partial(self.addNewFeature, qgslayer)
            tool.geometryComplete.connect(add)
        else:
            tool.finished.connect(self.openForm)
            tool.error.connect(functools.partial(self.showToolError, text))

        action = QAction(QIcon(layer.icon), text, self.mainwindow)
        action.setData(layer)
        action.setCheckable(True)
        action.toggled.connect(functools.partial(self.setMapTool, tool))

        self.toolbar.insertAction(self.editingmodeaction, action)

        if not tool.isEditTool():
            # Connect the GPS tools strip to the action pressed event.
            showgpstools = (functools.partial(self.extraaddtoolbar.showToolbar,
                                         action,
                                         None))

            action.toggled.connect(showgpstools)

        self.actionGroup.addAction(action)
        self.actions.append(action)

    def editLayer(self, layer):
        self.edittool.addLayer(layer.QGISLayer)
        self.edittool.searchRadius = 10

    def moveLayer(self, layer):
        self.movetool.addLayer(layer.QGISLayer)

    def createFormButtons(self, projectlayers):
        """
            Create buttons for each form that is definded
        """
        # Remove all the old buttons
        for action in self.actions:
            self.actionGroup.removeAction(action)
            self.toolbar.removeAction(action)
                
        self.edittool.layers = []
        self.movetool.layers = []
        capabilitityhandlers = { "capture" : self.captureLayer,
                                 "edit" : self.editLayer,
                                 "move" : self.moveLayer}

        for layer in projectlayers:
            try:
                qgslayer = QgsMapLayerRegistry.instance().mapLayersByName(layer.name)[0]
                if qgslayer.type() == QgsMapLayer.RasterLayer:
                    utils.log("We can't support raster layers for data entry")
                    continue
                       
                layer.QGISLayer = qgslayer
            except IndexError:
                utils.log("Layer {} not found in project".format(layer.name))
                continue

            for capability in layer.capabilities:
                try:
                    capabilitityhandlers[capability](layer)
                except NoMapToolConfigured:
                    utils.log("No map tool configured")
                    continue
                except ErrorInMapTool as error:
                    self.iface.messageBar().pushMessage("Error configuring map tool", error.message, level=QgsMessageBar.WARNING)
                    continue

    def showToolError(self, label, message):
        self.iface.messageBar().pushMessage(label, message, QgsMessageBar.WARNING)
            
    def openForm(self, layer, feature):
        if not layer.isEditable():
            layer.startEditing()

        self.band.setToGeometry(feature.geometry(), layer)
        self.dialogprovider.openDialog(feature=feature, layer=layer)
        self.band.reset()

    def addNewFeature(self, layer, geometry):
        fields = layer.pendingFields()
        
        feature = QgsFeature()
        feature.setGeometry( geometry )
        feature.initAttributes(fields.count())
        feature.setFields(fields)
        
        for indx in xrange(fields.count()):
            feature[indx] = layer.dataProvider().defaultValue(indx)

        self.openForm(layer, feature)

    def showOpenProjectDialog(self):
        """
        Show the project selection dialog.
        """
        self.stack.setCurrentIndex(1)
        self.infodock.hide()
        path = os.path.join(os.path.dirname(__file__), '..' , 'projects/')
        projects = getProjects(path, self.iface)
        self.projectwidget.loadProjectList(projects)
        
    def loadProject(self, project):
        """
        Load a project into QGIS.
        """
        utils.log(project)
        utils.log(project.name)
        utils.log(project.projectfile)
        utils.log(project.vaild)
        
        (passed, message) = project.onProjectLoad()
        
        if not passed:
            QMessageBox.warning(self.mainwindow, "Project Load Rejected", 
                                "Project couldn't be loaded because {}".format(message))
            return
        
        self.mapview.trigger()
        self.iface.newProject(False)        
        self.iface.mapCanvas().freeze()
        self.infodock.clearResults()
        # No idea why we have to set this each time.  Maybe QGIS deletes it for
        # some reason.
        self.badLayerHandler = BadLayerHandler(callback=self.missingLayers)
        QgsProject.instance().setBadLayerHandler( self.badLayerHandler )
        
        self.iface.messageBar().pushMessage("Project Loading","", QgsMessageBar.INFO)
        QApplication.processEvents()

        fileinfo = QFileInfo(project.projectfile)
        QgsProject.instance().read(fileinfo)

        self.iface.mapCanvas().updateScale()
        self.iface.mapCanvas().freeze(False)
        self.iface.mapCanvas().refresh()
        self.mainwindow.setWindowTitle("IntraMaps Roam: Mobile Data Collection")
        self.iface.projectRead.emit()
        
    def unload(self):
        del self.toolbar
        
    def connectSyncProviders(self, project):
        self.syncactionstoolbar.clear()
        
        syncactions = list(project.syncprovders())
        
        # Don't show the sync button if there is no sync providers
        if not syncactions:
            self.syncAction.setVisible(False)
            return
        
        self.syncAction.setVisible(True)
        
        for provider in syncactions:
            action = QAction(QIcon(":/icons/sync"), "Sync {}".format(provider.name), self.mainwindow)
            action.triggered.connect(functools.partial(self.syncProvider, provider))
            self.syncactionstoolbar.addAction(action)
        
        try:
            self.syncAction.toggled.disconnect()
        except TypeError:
            pass
        try:
            self.syncAction.triggered.disconnect()
        except TypeError:
            pass
        
        if len(syncactions) == 1:
            # If one provider is set then we just connect the main button.    
            self.syncAction.setCheckable(False)
            self.syncAction.setText("Sync")
            self.syncAction.triggered.connect(functools.partial(self.syncProvider, syncactions[0]))
        else:
            # the sync button because a sync menu
            self.syncAction.setCheckable(True)
            self.syncAction.setText("Sync Menu")
            showsyncoptions = (functools.partial(self.syncactionstoolbar.showToolbar, 
                                                 self.syncAction, None))
            self.syncAction.toggled.connect(showsyncoptions)

    def syncstarted(self):                   
        # Remove the old widget if it's still there.
        # I don't really like this. Seems hacky.
        try:
            self.iface.messageBar().popWidget(self.syncwidget)
        except RuntimeError:
            pass
        except AttributeError:
            pass
        
        self.iface.messageBar().findChildren(QToolButton)[0].setVisible(False)
        self.syncwidget = self.iface.messageBar().createMessage("Syncing", "Sync in progress", QIcon(":/icons/syncing"))
        button = QPushButton(self.syncwidget)
        button.setCheckable(True)
        button.setText("Status")
        button.setIcon(QIcon(":/icons/syncinfo"))
        button.toggled.connect(functools.partial(self.report.setVisible))
        pro = QProgressBar()
        pro.setMaximum(0)
        pro.setMinimum(0)
        self.syncwidget.layout().addWidget(pro)
        self.syncwidget.layout().addWidget(button)
        self.iface.messageBar().pushWidget(self.syncwidget, QgsMessageBar.INFO)
        
    def synccomplete(self):
        try:
            self.iface.messageBar().popWidget(self.syncwidget)
        except RuntimeError:
            pass
        
        stylesheet = ("QgsMessageBar { background-color: rgba(239, 255, 233); border: 0px solid #b9cfe4; } "
                     "QLabel,QTextEdit { color: #057f35; } ")
        
        closebutton = self.iface.messageBar().findChildren(QToolButton)[0]
        closebutton.setVisible(True)
        closebutton.clicked.connect(functools.partial(self.report.setVisible, False))
        self.syncwidget = self.iface.messageBar().createMessage("Syncing", "Sync Complete", QIcon(":/icons/syncdone"))
        button = QPushButton(self.syncwidget)
        button.setCheckable(True)
        button.setChecked(self.report.isVisible())
        button.setText("Sync Report")
        button.setIcon(QIcon(":/icons/syncinfo"))
        button.toggled.connect(functools.partial(self.report.setVisible))      
        pro = QProgressBar()
        pro.setMaximum(100)
        pro.setValue(100)
        self.syncwidget.layout().addWidget(pro)      
        self.syncwidget.layout().addWidget(button)
        self.iface.messageBar().pushWidget(self.syncwidget)
        self.iface.messageBar().setStyleSheet(stylesheet)
        self.iface.mapCanvas().refresh()
        
    def syncerror(self):
        try:
            self.iface.messageBar().popWidget(self.syncwidget)
        except RuntimeError:
            pass
        
        closebutton = self.iface.messageBar().findChildren(QToolButton)[0]
        closebutton.setVisible(True)
        closebutton.clicked.connect(functools.partial(self.report.setVisible, False))
        self.syncwidget = self.iface.messageBar().createMessage("Syncing", "Sync Error", QIcon(":/icons/syncfail"))
        button = QPushButton(self.syncwidget)
        button.setCheckable(True)
        button.setChecked(self.report.isVisible())
        button.setText("Sync Report")
        button.setIcon(QIcon(":/icons/syncinfo"))
        button.toggled.connect(functools.partial(self.report.setVisible))            
        self.syncwidget.layout().addWidget(button)
        self.iface.messageBar().pushWidget(self.syncwidget, QgsMessageBar.CRITICAL)
        self.iface.mapCanvas().refresh()
        
    def syncProvider(self, provider):
        self.syncAction.toggle()
        provider.syncStarted.connect(functools.partial(self.syncAction.setEnabled, False))
        provider.syncStarted.connect(self.syncstarted)
        
        provider.syncComplete.connect(self.synccomplete)
        provider.syncComplete.connect(functools.partial(self.syncAction.setEnabled, True))
        provider.syncComplete.connect(functools.partial(self.report.updateHTML))
        
        provider.syncMessage.connect(self.report.updateHTML)
        
        provider.syncError.connect(self.report.updateHTML)
        provider.syncError.connect(self.syncerror)
        provider.syncError.connect(functools.partial(self.syncAction.setEnabled, True))
        
        provider.startSync()
Example #31
0
class DocumentViewManager(QMainWindow):
    """
    MDI area for displaying supporting documents within a given context e.g.
    supporting documents for a specific household based on the lifetime of the
    'SourceDocumentManager' instance.
    """
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.setWindowFlags(Qt.Window)

        self._mdi_area = QMdiArea()
        self._mdi_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self._mdi_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
        self.setCentralWidget(self._mdi_area)
        # set the size of mid_area and DocumentViewManager based on the
        # screen size.
        screen = QDesktopWidget().availableGeometry()
        self._mdi_area.resize(screen.width() - 30, screen.height() - 80)
        self.resize(self._mdi_area.size())
        self._mdi_area.subWindowActivated.connect(self.update_actions)
        self._viewer_mapper = QSignalMapper(self)
        self._viewer_mapper.mapped[QWidget].connect(self.set_active_sub_window)

        win_title = QApplication.translate(
            "DocumentViewManager",
            "Document Viewer"
        )
        self.setWindowTitle(win_title)
        self.setUnifiedTitleAndToolBarOnMac(True)
        self.statusBar().showMessage(
            QApplication.translate(
                "DocumentViewManager",
                "Ready"
            )
        )
        self._doc_viewers = {}

        self._create_menu_actions()
        self.update_actions()

    def center(self):
        """
        Move the Document viewer to the center of the screen.
        """
        # Get the current screens' dimensions...
        screen = QDesktopWidget().availableGeometry()
        # ... and get this windows' dimensions
        mdi_area_size = self.frameGeometry()
        # The horizontal position
        hpos = (screen.width() - mdi_area_size.width()) / 2
        # vertical position
        vpos = (screen.height() - mdi_area_size.height()) / 2
        # repositions the window
        self.move(hpos, vpos)

    def _create_menu_actions(self):
        self._window_menu = self.menuBar().addMenu(
            QApplication.translate(
                "DocumentViewManager","&Windows"))

        self._close_act = QAction(
            QApplication.translate("DocumentViewManager",
                                                         "Cl&ose"), self)
        self._close_act.setStatusTip(
            QApplication.translate("DocumentViewManager",
                                   "Close the active document viewer"))
        self._close_act.triggered.connect(self._mdi_area.closeActiveSubWindow)

        self._close_all_act = QAction(QApplication.translate(
            "DocumentViewManager",
            "Close &All"), self
        )
        self._close_all_act.setStatusTip(
            QApplication.translate("DocumentViewManager",
                                   "Close all the document viewers")
        )
        self._close_all_act.triggered.connect(
            self._mdi_area.closeAllSubWindows
        )

        self._tile_act = QAction(QApplication.translate(
            "DocumentViewManager",
            "&Tile"), self
        )
        self._tile_act.setStatusTip(
            QApplication.translate("DocumentViewManager",
                                   "Tile the document viewers"))
        self._tile_act.triggered.connect(self.tile_windows)

        self._cascade_act = QAction(QApplication.translate(
            "DocumentViewManager",
            "&Cascade"), self)
        self._cascade_act.setStatusTip(QApplication.translate(
            "DocumentViewManager",
            "Cascade the document viewers"))
        self._cascade_act.triggered.connect(self.cascade_windows)

        self._next_act = QAction(QApplication.translate(
            "DocumentViewManager",
            "Ne&xt"), self)
        self._next_act.setStatusTip(
            QApplication.translate(
                "DocumentViewManager",
                "Move the focus to the next document viewer"
            )
        )
        self._next_act.triggered.connect(self._mdi_area.activateNextSubWindow)

        self._previous_act = QAction(QApplication.translate(
            "DocumentViewManager",
            "Pre&vious"), self)
        self._previous_act.setStatusTip(
            QApplication.translate(
                "DocumentViewManager",
                "Move the focus to the previous document viewer"
            )
        )
        self._previous_act.triggered.connect(
            self._mdi_area.activatePreviousSubWindow
        )

        self._separator_act = QAction(self)
        self._separator_act.setSeparator(True)

        self.update_window_menu()
        self._window_menu.aboutToShow.connect(self.update_window_menu)

    def cascade_windows(self):
        #Cascade document windows
        self._mdi_area.cascadeSubWindows()

    def tile_windows(self):
        #Arrange document windows to occupy the available space in mdi area
        self._mdi_area.tileSubWindows()

    def update_actions(self):
        if self._mdi_area.activeSubWindow():
            has_mdi_child = True

        else:
            has_mdi_child = False

        self._close_act.setEnabled(has_mdi_child)
        self._close_all_act.setEnabled(has_mdi_child)
        self._tile_act.setEnabled(has_mdi_child)
        self._cascade_act.setEnabled(has_mdi_child)
        self._previous_act.setEnabled(has_mdi_child)
        self._next_act.setEnabled(has_mdi_child)
        self._separator_act.setVisible(has_mdi_child)

    def update_window_menu(self):
        self._window_menu.clear()
        self._window_menu.addAction(self._close_act)
        self._window_menu.addAction(self._close_all_act)
        self._window_menu.addSeparator()
        self._window_menu.addAction(self._tile_act)
        self._window_menu.addAction(self._cascade_act)
        self._window_menu.addSeparator()
        self._window_menu.addAction(self._next_act)
        self._window_menu.addAction(self._previous_act)
        self._window_menu.addAction(self._separator_act)

        windows = self._mdi_area.subWindowList()
        self._separator_act.setVisible(len(windows) != 0)

        for i, window in enumerate(windows):
            text = "%d. %s" % (i + 1, window.windowTitle())

            win_action = self._window_menu.addAction(text)
            win_action.setCheckable(True)
            win_action.setChecked(window is self._mdi_area.activeSubWindow())
            win_action.triggered.connect(self._viewer_mapper.map)
            self._viewer_mapper.setMapping(win_action, window)

    def load_viewer(self, document_widget, visible=True):
        """
        Open a new instance of the viewer or activate an existing one if the
        document had been previously loaded.
        :param document_widget: Contains all the necessary information required
        to load the specific document.
        :type document_widget: DocumentWidget
        :param visible: True to show the view manager after the viewer has
        been loaded, otherwise it will be the responsibility of the caller to
        enable visibility.
        :type visible: bool
        :returns: True if the document was successfully loaded, else False.
        :rtype: bool
        """
        doc_identifier = document_widget.file_identifier()

        if doc_identifier in self._doc_viewers:

            doc_sw = self._doc_viewers[doc_identifier]

            self._mdi_area.setActiveSubWindow(doc_sw)
            doc_sw.showNormal()

        else:
            doc_viewer = self._create_viewer(document_widget)

            abs_doc_path = self.absolute_document_path(document_widget)

            if not QFile.exists(abs_doc_path):
                msg = QApplication.translate(
                    "DocumentViewManager",
                    "The selected document does not exist."
                    "\nPlease check the supporting documents' "
                    "repository setting."
                )
                QMessageBox.critical(
                    self,
                    QApplication.translate(
                        "DocumentViewManager","Invalid Document"
                    ),
                    msg
                )

                return False

            doc_viewer.load_document(abs_doc_path)

            self._doc_viewers[doc_identifier] = doc_viewer

            self._mdi_area.addSubWindow(doc_viewer)

            doc_viewer.show()

        if not self.isVisible() and visible:
            self.setVisible(True)

        if self.isMinimized():
            self.showNormal()

        self.center()

        return True

    def set_active_sub_window(self, viewer):
        if viewer:
            self._mdi_area.setActiveSubWindow(viewer)

    def absolute_document_path(self, document_widget):
        """
        Build the absolute document path using info from the document widget.
        :param document_widget: Instance of document widget.
        :return: Absolute path of the supporting document.
        :rtype: str
        """
        abs_path = ''

        file_manager = document_widget.fileManager
        if not file_manager is None:
            network_repository = file_manager.networkPath
            file_id = document_widget.file_identifier()
            source_entity = document_widget.doc_source_entity()
            profile_name = current_profile().name
            doc_type = document_widget.doc_type_value().lower().replace(
                ' ', '_'
            )
            file_name, file_extension = guess_extension(
                document_widget.displayName()
            )

            abs_path = network_repository + "/" +profile_name + '/' +\
                       unicode(source_entity) + "/" + unicode(doc_type) + "/" +\
                       unicode(file_id) + unicode(file_extension)

        return abs_path

    def reset(self):
        """
        Removes all document viewers in the view area.
        The QCloseEvent sent to each sub-window will decrement the register.
        """
        self._mdi_area.closeAllSubWindows()

    def _create_viewer(self, document_widget):
        """
        Creates a new instance of a document viewer.
        :param document_widget: Contains all
        the necessary information required
        to load the specific document.
        :return: Document viewer object
        :rtype: DocumentViewer
        """
        doc_viewer = DocumentViewer(
            self._mdi_area, document_widget.file_identifier()
        )
        doc_viewer.setAttribute(Qt.WA_DeleteOnClose)
        doc_viewer.setWindowTitle(
            document_widget.displayName()
        )

        # TODO: Incorporate logic for determining
        # TODO: viewer based on document type
        ph_viewer = PhotoViewer()

        # v_layout = QVBoxLayout()
        # v_layout.addWidget(ph_viewer)
        # doc_viewer.setLayout(v_layout)

        doc_viewer.set_view_widget(ph_viewer)

        doc_viewer.closed.connect(self._on_viewer_closed)

        return doc_viewer

    def remove_viewer(self, viewer_id):
        """
        Close and remove the viewer with the specified viewer ID.
        """
        if viewer_id in self._doc_viewers:
            viewer = self._doc_viewers[viewer_id]
            self._mdi_area.setActiveSubWindow(viewer)
            self._mdi_area.closeActiveSubWindow()

        self._on_viewer_closed(viewer_id)

    def _on_viewer_closed(self,file_id):
        """
        Slot raised when a document viewer is closed.
        """
        if file_id in self._doc_viewers:
            del self._doc_viewers[file_id]
Example #32
0
class MainWindow(ui_mainwindow.Ui_MainWindow, QMainWindow):
    """
    Main application window
    """

    def __init__(self):
        super(MainWindow, self).__init__()
        self.setupUi(self)
        self.canvaslayers = []
        self.layerbuttons = []
        self.project = None
        self.selectionbands = defaultdict(partial(QgsRubberBand, self.canvas))
        self.canvas.setCanvasColor(Qt.white)
        self.canvas.enableAntiAliasing(True)
        self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor)
        self.bar = roam.messagebaritems.MessageBar(self.centralwidget)

        self.actionMap.setVisible(False)
        self.actionLegend.setVisible(False)

        pal = QgsPalLabeling()
        self.canvas.mapRenderer().setLabelingEngine(pal)
        self.canvas.setFrameStyle(QFrame.NoFrame)

        self.menuGroup = QActionGroup(self)
        self.menuGroup.setExclusive(True)
        self.menuGroup.addAction(self.actionMap)
        self.menuGroup.addAction(self.actionDataEntry)
        self.menuGroup.addAction(self.actionLegend)
        self.menuGroup.addAction(self.actionProject)
        self.menuGroup.addAction(self.actionSync)
        self.menuGroup.addAction(self.actionSettings)
        self.menuGroup.addAction(self.actionGPS)
        self.menuGroup.triggered.connect(self.updatePage)

        self.editgroup = QActionGroup(self)
        self.editgroup.setExclusive(True)
        self.editgroup.addAction(self.actionPan)
        self.editgroup.addAction(self.actionZoom_In)
        self.editgroup.addAction(self.actionZoom_Out)
        self.editgroup.addAction(self.actionInfo)

        self.actionLegend.triggered.connect(self.updatelegend)

        self.actionGPS = GPSAction(":/icons/gps", self.canvas, self)
        self.projecttoolbar.addAction(self.actionGPS)

        self.projectwidget.requestOpenProject.connect(self.loadProject)
        QgsProject.instance().readProject.connect(self._readProject)

        self.gpswidget.setgps(GPS)

        self.actionSettings.toggled.connect(self.settingswidget.populateControls)
        self.actionSettings.toggled.connect(self.settingswidget.readSettings)
        self.settingswidget.settingsupdated.connect(self.settingsupdated)

        self.dataentrywidget = DataEntryWidget(self.canvas, self.bar)
        self.widgetpage.layout().addWidget(self.dataentrywidget)

        self.dataentrywidget.rejected.connect(self.formrejected)
        self.dataentrywidget.featuresaved.connect(self.featureSaved)
        self.dataentrywidget.featuredeleted.connect(self.featuredeleted)
        self.dataentrywidget.failedsave.connect(self.failSave)
        self.dataentrywidget.helprequest.connect(self.showhelp)

        def createSpacer(width=0, height=0):
            widget = QWidget()
            widget.setMinimumWidth(width)
            widget.setMinimumHeight(height)
            return widget

        gpsspacewidget = createSpacer(30)
        sidespacewidget = createSpacer(30)
        sidespacewidget2 = createSpacer(height=20)

        sidespacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        sidespacewidget2.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
        gpsspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.topspaceraction = self.projecttoolbar.insertWidget(self.actionGPS, gpsspacewidget)

        def createlabel(text):
            style = """
                QLabel {
                        color: #706565;
                        font: 14px "Calibri" ;
                        }"""
            label = QLabel(text)
            label.setStyleSheet(style)

            return label

        self.projectlabel = createlabel("Project: {project}")
        self.userlabel = createlabel("User: {user}".format(user=getpass.getuser()))
        self.positionlabel = createlabel('')
        self.gpslabel = createlabel("GPS: Not active")
        self.statusbar.addWidget(self.projectlabel)
        self.statusbar.addWidget(self.userlabel)
        spacer = createSpacer()
        spacer2 = createSpacer()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        spacer2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        self.statusbar.addWidget(spacer)
        self.statusbar.addWidget(self.positionlabel)
        self.statusbar.addWidget(spacer2)
        self.statusbar.addWidget(self.gpslabel)

        self.menutoolbar.insertWidget(self.actionQuit, sidespacewidget2)
        self.menutoolbar.insertWidget(self.actionProject, sidespacewidget)

        self.panels = []

        self.connectButtons()

        self.currentfeatureband = QgsRubberBand(self.canvas)
        self.currentfeatureband.setIconSize(20)
        self.currentfeatureband.setWidth(10)
        self.currentfeatureband.setColor(QColor(186, 93, 212, 76))

        self.canvas_page.layout().insertWidget(0, self.projecttoolbar)
        self.dataentryselection = QAction(self.projecttoolbar)
        self.dataentryaction = self.projecttoolbar.insertAction(self.topspaceraction, self.dataentryselection)
        self.dataentryselection.triggered.connect(self.selectdataentry)

        self.centralwidget.layout().addWidget(self.statusbar)

        self.actionGPSFeature.setProperty('dataentry', True)

        self.infodock = InfoDock(self.canvas)
        self.infodock.featureupdated.connect(self.highlightfeature)
        self.infodock.hide()
        self.hidedataentry()
        self.canvas.extentsChanged.connect(self.updatestatuslabel)

        RoamEvents.openimage.connect(self.openimage)
        RoamEvents.openurl.connect(self.viewurl)
        RoamEvents.openfeatureform.connect(self.openForm)
        RoamEvents.openkeyboard.connect(self.openkeyboard)
        RoamEvents.selectioncleared.connect(self.clearselection)
        RoamEvents.editgeometry.connect(self.addforedit)
        RoamEvents.editgeometry_complete.connect(self.on_geometryedit)
        RoamEvents.onShowMessage.connect(self.showUIMessage)
        RoamEvents.selectionchanged.connect(self.highlightselection)
        RoamEvents.selectionchanged.connect(self.showInfoResults)

        GPS.gpspostion.connect(self.updatecanvasfromgps)
        GPS.firstfix.connect(self.gpsfirstfix)
        GPS.gpsdisconnected.connect(self.gpsdisconnected)

        self.lastgpsposition = None
        self.marker = GPSMarker(self.canvas)
        self.marker.hide()

        self.legendpage.showmap.connect(self.showmap)

        self.editfeaturestack = []
        self.currentselection = {}

    def showUIMessage(self, label, message, level=QgsMessageBar.INFO, time=0, extra=''):
        self.bar.pushMessage(label, message, level, duration=time, extrainfo=extra)

    def addforedit(self, form, feature):
        self.editfeaturestack.append((form, feature))
        self.loadform(form)
        actions = self.getcaptureactions()
        for action in actions:
            if action.isdefault:
                action.trigger()
                break

    def updatelegend(self):
        self.legendpage.updatecanvas(self.canvas)

    def gpsfirstfix(self, postion, gpsinfo):
        zoomtolocation = roam.config.settings.get('gpszoomonfix', True)
        if zoomtolocation:
            self.canvas.zoomScale(1000)

    def updatecanvasfromgps(self, position, gpsinfo):
        # Recenter map if we go outside of the 95% of the area
        if not self.lastgpsposition == position:
            self.lastposition = position
            rect = QgsRectangle(position, position)
            extentlimt = QgsRectangle(self.canvas.extent())
            extentlimt.scale(0.95)

            if not extentlimt.contains(position):
                self.canvas.setExtent(rect)
                self.canvas.refresh()

        self.marker.show()
        self.marker.setCenter(position)
        self.gpslabel.setText("GPS: PDOP {}   HDOP {}    VDOP {}".format(gpsinfo.pdop,
                                                                        gpsinfo.hdop,
                                                                        gpsinfo.vdop))

    def gpsdisconnected(self):
        self.marker.hide()
        self.gpslabel.setText("GPS Not Active")

    def openkeyboard(self):
        if not roam.config.settings.get('keyboard', True):
            return

        if sys.platform == 'win32':
            try:
                programfiles = os.environ['ProgramW6432']
            except KeyError:
                programfiles = os.environ['ProgramFiles']

            cmd = r'{path}\Common Files\Microsoft Shared\ink\TabTip.exe'.format(path=programfiles)
            try:
                os.startfile(cmd)
            except WindowsError:
                roam.config.settings['keyboard'] = False
                roam.config.save()
        else:
            cmd = 'onboard'
            Popen(cmd)


    def selectdataentry(self):
        forms = self.project.forms
        formpicker = PickActionDialog(msg="Select data entry form")
        for form in forms:
            action = form.createuiaction()
            valid, failreasons = form.valid
            if not valid:
                roam.utils.warning("Form {} failed to load".format(form.label))
                roam.utils.warning("Reasons {}".format(failreasons))
                action.triggered.connect(partial(self.showformerror, form))
            else:
                action.triggered.connect(partial(self.loadform, form))
            formpicker.addAction(action)

        formpicker.exec_()

    def showformerror(self, form):
        pass

    def viewurl(self, url):
        """
        Open a URL in Roam
        :param url:
        :return:
        """
        key = url.toString().lstrip('file://')
        try:
            # Hack. Eww fix me.
            data, imagetype = roam.htmlviewer.images[os.path.basename(key)]
            pix = QPixmap()
            if imagetype == 'base64':
                pix.loadFromData(data)
            else:
                pix.load(data)
            self.openimage(pix)
        except KeyError:
            pix = QPixmap()
            pix.load(key)
            if pix.isNull():
                QDesktopServices.openUrl(url)
                return
            self.openimage(pix)

    def openimage(self, pixmap):
        viewer = ImageViewer(self.stackedWidget)
        viewer.resize(self.stackedWidget.size())
        viewer.openimage(pixmap)

    def settingsupdated(self, settings):
        self.show()
        self.actionGPS.updateGPSPort()

    def updatestatuslabel(self):
        extent = self.canvas.extent()
        self.positionlabel.setText("Map Center: {}".format(extent.center().toString()))

    def on_geometryedit(self, form, feature):
        layer = form.QGISLayer
        self.reloadselection(layer, updated=[feature])

        self.currentfeatureband.setToGeometry(feature.geometry(), layer)

    def reloadselection(self, layer, deleted=[], updated=[]):
        """
        Reload the selection after features have been updated or deleted.
        :param layer:
        :param deleted:
        :param updated:
        :return:
        """
        selectedfeatures = self.currentselection[layer]

        # Update any features that have changed.
        for updatedfeature in updated:
            oldfeatures = [f for f in selectedfeatures if f.id() == updatedfeature.id()]
            for feature in oldfeatures:
                self.currentselection[layer].remove(feature)
                self.currentselection[layer].append(updatedfeature)

        # Delete any old ones
        for deletedid in deleted:
            oldfeatures = [f for f in selectedfeatures if f.id() == deletedid]
            for feature in oldfeatures:
                self.currentselection[layer].remove(feature)

        RoamEvents.selectionchanged.emit(self.currentselection)

    def highlightselection(self, results):
        self.clearselection()
        for layer, features in results.iteritems():
            band = self.selectionbands[layer]
            band.setColor(QColor(255, 0, 0, 200))
            band.setIconSize(20)
            band.setWidth(2)
            band.setBrushStyle(Qt.NoBrush)
            band.reset(layer.geometryType())
            for feature in features:
                band.addGeometry(feature.geometry(), layer)

    def clearselection(self):
        # Clear the main selection rubber band
        self.currentfeatureband.reset()
        # Clear the rest
        for band in self.selectionbands.itervalues():
            band.reset()

        self.editfeaturestack = []

    def highlightfeature(self, layer, feature, features):
        self.clearselection()
        self.highlightselection({layer: features})
        self.currentfeatureband.setToGeometry(feature.geometry(), layer)

    def showmap(self):
        self.actionMap.setVisible(True)
        self.actionLegend.setVisible(True)
        self.actionMap.trigger()

    def hidedataentry(self):
        self.actionDataEntry.setVisible(False)

    def showdataentry(self):
        self.actionDataEntry.setVisible(True)
        self.actionDataEntry.trigger()

    def dataentrychanged(self, index):
        self.clearCapatureTools()

        if not index.isValid():
            return

        modelindex = index
        # modelindex = self.dataentrymodel.index(index, 0)
        form = modelindex.data(Qt.UserRole + 1)
        self.dataentryselection.setCurrentIndex(index.row())
        self.createCaptureButtons(form)

    def raiseerror(self, *exinfo):
        info = traceback.format_exception(*exinfo)
        item = self.bar.pushError(QApplication.translate('MainWindowPy','Seems something has gone wrong. Press for more details', None, QApplication.UnicodeUTF8),
                                  info)

    def setMapTool(self, tool, *args):
        self.canvas.setMapTool(tool)

    def homeview(self):
        """
        Zoom the mapview canvas to the extents the project was opened at i.e. the
        default extent.
        """
        self.canvas.setExtent(self.defaultextent)
        self.canvas.refresh()

    def connectButtons(self):
        def connectAction(action, tool):
            action.toggled.connect(partial(self.setMapTool, tool))

        def cursor(name):
            pix = QPixmap(name)
            pix = pix.scaled(QSize(24,24))
            return QCursor(pix)

        self.zoomInTool = QgsMapToolZoom(self.canvas, False)
        self.zoomOutTool = QgsMapToolZoom(self.canvas, True)
        self.panTool = PanTool(self.canvas)
        self.infoTool = InfoTool(self.canvas)

        connectAction(self.actionZoom_In, self.zoomInTool)
        connectAction(self.actionZoom_Out, self.zoomOutTool)
        connectAction(self.actionPan, self.panTool)
        connectAction(self.actionInfo, self.infoTool)

        self.zoomInTool.setCursor(cursor(':/icons/in'))
        self.zoomOutTool.setCursor(cursor(':/icons/out'))
        self.infoTool.setCursor(cursor(':/icons/info'))

        self.actionRaster.triggered.connect(self.toggleRasterLayers)

        self.infoTool.infoResults.connect(RoamEvents.selectionchanged.emit)

        self.actionHome.triggered.connect(self.homeview)
        self.actionQuit.triggered.connect(self.exit)

    def getcaptureactions(self):
        for action in self.projecttoolbar.actions():
            if action.property('dataentry'):
                yield action

    def clearCapatureTools(self):
        captureselected = False
        for action in self.projecttoolbar.actions():
            if action.objectName() == "capture" and action.isChecked():
                captureselected = True

            if action.property('dataentry'):
                self.projecttoolbar.removeAction(action)
        return captureselected

    def createCaptureButtons(self, form):
        tool = form.getMaptool()(self.canvas)
        for action in tool.actions:
            # Create the action here.
            if action.ismaptool:
                action.toggled.connect(partial(self.setMapTool, tool))

            # Set the action as a data entry button so we can remove it later.
            action.setProperty("dataentry", True)
            self.editgroup.addAction(action)
            self.layerbuttons.append(action)
            self.projecttoolbar.insertAction(self.topspaceraction, action)
            action.setChecked(action.isdefault)

        if hasattr(tool, 'geometryComplete'):
            add = partial(self.addNewFeature, form)
            tool.geometryComplete.connect(add)
        else:
            tool.finished.connect(self.openForm)
            tool.error.connect(partial(self.showUIMessage, form.label))

    def loadform(self, form):
        self.clearCapatureTools()
        self.dataentryselection.setIcon(QIcon(form.icon))
        self.dataentryselection.setText(form.icontext)
        self.createCaptureButtons(form)

    def clearToolRubberBand(self):
        """
        Clear the rubber band of the active tool if it has one
        """
        tool = self.canvas.mapTool()
        try:
            tool.clearBand()
        except AttributeError:
            # No clearBand method found, but that's cool.
            pass

    def showhelp(self, url):
        help = HelpPage(self.stackedWidget)
        help.setHelpPage(url)
        help.show()

    def dataentryfinished(self):
        self.hidedataentry()
        self.showmap()
        self.cleartempobjects()
        self.infodock.refreshcurrent()

    def featuredeleted(self, layer, featureid):
        self.dataentryfinished()
        self.reloadselection(layer, deleted=[featureid])
        self.canvas.refresh()

    def featureSaved(self):
        self.dataentryfinished()
        self.canvas.refresh()

    def failSave(self, messages):
        self.bar.pushError("Error when saving changes.", messages)

    def cleartempobjects(self):
        self.currentfeatureband.reset()
        self.clearToolRubberBand()

    def formrejected(self, message, level):
        self.dataentryfinished()
        if message:
            RoamEvents.raisemessage("Form Message", message, level, duration=2)

        self.cleartempobjects()

    def openForm(self, form, feature, editmode):
        """
        Open the form that is assigned to the layer
        """
        self.currentfeatureband.setToGeometry(feature.geometry(), form.QGISLayer)
        self.showdataentry()
        self.dataentrywidget.openform(feature=feature, form=form, project=self.project, editmode=editmode)

    def editfeaturegeometry(self, form, feature, newgeometry):
        layer = form.QGISLayer
        layer.startEditing()
        feature.setGeometry(newgeometry)
        layer.updateFeature(feature)
        saved = layer.commitChanges()
        map(roam.utils.error, layer.commitErrors())
        self.canvas.refresh()
        RoamEvents.editgeometry_complete.emit(form, feature)

    def addNewFeature(self, form, geometry):
        """
        Add a new new feature to the given layer
        """
        layer = form.QGISLayer
        if layer.geometryType() in [QGis.WKBMultiLineString, QGis.WKBMultiPoint, QGis.WKBMultiPolygon]:
            geometry.convertToMultiType()

        try:
            form, feature = self.editfeaturestack.pop()
            self.editfeaturegeometry(form, feature, newgeometry=geometry)
            return
        except IndexError:
            pass

        layer = form.QGISLayer
        fields = layer.pendingFields()

        feature = QgsFeature(fields)
        feature.setGeometry(geometry)

        for index in xrange(fields.count()):
            pkindexes = layer.dataProvider().pkAttributeIndexes()
            if index in pkindexes and layer.dataProvider().name() == 'spatialite':
                continue

            value = layer.dataProvider().defaultValue(index)
            feature[index] = value

        self.openForm(form, feature, editmode=False)

    def exit(self):
        """
        Exit the application.
        """
        QApplication.exit(0)

    def showInfoResults(self, results):
        forms = {}
        for layer in results.keys():
            layername = layer.name()
            if not layername in forms:
                forms[layername] = list(self.project.formsforlayer(layername))

        self.currentselection = results
        self.infodock.setResults(results, forms)
        self.infodock.show()

    def toggleRasterLayers(self):
        """
        Toggle all raster layers on or off.
        """
        if not self.canvaslayers:
            return

        #Freeze the canvas to save on UI refresh
        self.canvas.freeze()
        for layer in self.canvaslayers:
            if layer.layer().type() == QgsMapLayer.RasterLayer:
                layer.setVisible(not layer.isVisible())
            # Really!? We have to reload the whole layer set every time?
        # WAT?
        self.canvas.setLayerSet(self.canvaslayers)
        self.canvas.freeze(False)
        self.canvas.refresh()

    def missingLayers(self, layers):
        """
        Called when layers have failed to load from the current project
        """
        roam.utils.warning("Missing layers")
        map(roam.utils.warning, layers)

        missinglayers = roam.messagebaritems.MissingLayerItem(layers,
                                                              parent=self.bar)
        self.bar.pushItem(missinglayers)

    def loadprojects(self, projects):
        """
        Load the given projects into the project
        list
        """
        projects = list(projects)
        self.projectwidget.loadProjectList(projects)
        self.syncwidget.loadprojects(projects)

    def updatePage(self, action):
        """
        Update the current stack page based on the current selected
        action
        """
        page = action.property("page")
        self.stackedWidget.setCurrentIndex(page)

    def show(self):
        """
        Override show method. Handles showing the app in fullscreen
        mode or just maximized
        """
        fullscreen = roam.config.settings.get("fullscreen", False)
        if fullscreen:
            self.showFullScreen()
        else:
            self.showMaximized()

    def viewprojects(self):
        self.stackedWidget.setCurrentIndex(1)

    @roam.utils.timeit
    def _readProject(self, doc):
        """
        readProject is called by QgsProject once the map layer has been
        populated with all the layers
        """
        parser = ProjectParser(doc)
        canvasnode = parser.canvasnode
        self.canvas.freeze()
        self.canvas.mapRenderer().readXML(canvasnode)
        self.canvaslayers = parser.canvaslayers()
        self.canvas.setLayerSet(self.canvaslayers)
        #red = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorRedPart", 255 )[0];
        #green = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorGreenPart", 255 )[0];
        #blue = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorBluePart", 255 )[0];
        #color = QColor(red, green, blue);
        #self.canvas.setCanvasColor(color)
        self.canvas.updateScale()
        self.projectOpened()
        self.canvas.freeze(False)
        self.canvas.refresh()
        GPS.crs = self.canvas.mapRenderer().destinationCrs()
        self.showmap()

    @roam.utils.timeit
    def projectOpened(self):
        """
            Called when a new project is opened in QGIS.
        """
        projectpath = QgsProject.instance().fileName()
        self.project = Project.from_folder(os.path.dirname(projectpath))
        self.projectlabel.setText("Project: {}".format(self.project.name))
        try:
            firstform = self.project.forms[0]
            self.loadform(self.project.forms[0])
            self.dataentryselection.setVisible(True)
        except IndexError:
            self.dataentryselection.setVisible(False)

        # Enable the raster layers button only if the project contains a raster layer.
        layers = QgsMapLayerRegistry.instance().mapLayers().values()
        hasrasters = any(layer.type() == QgsMapLayer.RasterLayer for layer in layers)
        self.actionRaster.setEnabled(hasrasters)
        self.defaultextent = self.canvas.extent()
        roam.utils.info("Extent: {}".format(self.defaultextent.toString()))

        # Show panels
        for panel in self.project.getPanels():
            self.mainwindow.addDockWidget(Qt.BottomDockWidgetArea, panel)
            self.panels.append(panel)

        self.infoTool.selectionlayers = self.project.selectlayersmapping()
        layers = self.project.legendlayersmapping().values()
        self.legendpage.updateitems(layers)
        self.actionPan.trigger()

    #noinspection PyArgumentList
    @roam.utils.timeit
    def loadProject(self, project):
        """
        Load a project into the application .
        """
        roam.utils.log(project)
        roam.utils.log(project.name)
        roam.utils.log(project.projectfile)
        roam.utils.log(project.valid)

        (passed, message) = project.onProjectLoad()

        if not passed:
            self.bar.pushMessage("Project load rejected", "Sorry this project couldn't"
                                                          "be loaded.  Click for me details.",
                                 QgsMessageBar.WARNING, extrainfo=message)
            return

        self.actionMap.trigger()

        self.closeProject()

        self.canvas.refresh()
        self.canvas.repaint()

        RoamEvents.selectioncleared.emit()

        # No idea why we have to set this each time.  Maybe QGIS deletes it for
        # some reason.
        self.badLayerHandler = BadLayerHandler(callback=self.missingLayers)
        QgsProject.instance().setBadLayerHandler(self.badLayerHandler)

        self.stackedWidget.setCurrentIndex(3)
        self.projectloading_label.setText("Project {} Loading".format(project.name))
        pixmap = QPixmap(project.splash)
        w = self.projectimage.width()
        h = self.projectimage.height()
        self.projectimage.setPixmap(pixmap.scaled(w,h, Qt.KeepAspectRatio))
        QApplication.processEvents()

        QDir.setCurrent(os.path.dirname(project.projectfile))
        fileinfo = QFileInfo(project.projectfile)
        QgsProject.instance().read(fileinfo)

    def closeProject(self):
        """
        Close the current open project
        """
        self.clearCapatureTools()
        self.canvas.freeze()
        QgsMapLayerRegistry.instance().removeAllMapLayers()
        self.canvas.clear()
        self.canvas.freeze(False)
        for panel in self.panels:
            self.removeDockWidget(panel)
            del panel
            # Remove all the old buttons
        for action in self.layerbuttons:
            self.editgroup.removeAction(action)

        self.panels = []
        self.project = None
        self.dataentrywidget.clear()
        self.hidedataentry()
        self.infodock.close()
Example #33
0
class PymetricsViewer( QWidget ):
    " Pymetrics tab widget "

    # Limits to colorize the McCabe score
    LittleRiskLimit = 10
    ModerateRiskLimit = 20
    HighRiskLimit = 50

    # Options of providing a report
    SingleFile     = 0
    DirectoryFiles = 1
    ProjectFiles   = 2
    SingleBuffer   = 3

    def __init__( self, parent = None ):
        QWidget.__init__( self, parent )

        self.__reportUUID = ""
        self.__reportFileName = ""
        self.__reportOption = -1
        self.__reportShown = False
        self.__report = None

        # Prepare members for reuse
        self.__noneLabel = QLabel( "\nNo results available" )

        self.__noneLabel.setFrameShape( QFrame.StyledPanel )
        self.__noneLabel.setAlignment( Qt.AlignHCenter )
        self.__headerFont = self.__noneLabel.font()
        self.__headerFont.setPointSize( self.__headerFont.pointSize() + 4 )
        self.__noneLabel.setFont( self.__headerFont )
        self.__noneLabel.setAutoFillBackground( True )
        noneLabelPalette = self.__noneLabel.palette()
        noneLabelPalette.setColor( QPalette.Background,
                                   GlobalData().skin.nolexerPaper )
        self.__noneLabel.setPalette( noneLabelPalette )

        self.__createLayout( parent )

        self.__updateButtonsStatus()
        return

    def __createLayout( self, parent ):
        " Creates the toolbar and layout "

        # Buttons
        self.__mcCabeButton = QAction( PixmapCache().getIcon( 'tableview.png' ),
                                       'Switch to McCabe only table view',
                                       self )
        self.__mcCabeButton.setCheckable( True )
        self.connect( self.__mcCabeButton, SIGNAL( 'toggled(bool)' ),
                      self.__onMcCabe )

        self.printButton = QAction( PixmapCache().getIcon( 'printer.png' ),
                                    'Print', self )
        #printButton.setShortcut( 'Ctrl+' )
        self.connect( self.printButton, SIGNAL( 'triggered()' ),
                      self.__onPrint )
        self.printButton.setVisible( False )

        self.printPreviewButton = QAction(
                PixmapCache().getIcon( 'printpreview.png' ),
                'Print preview', self )
        #printPreviewButton.setShortcut( 'Ctrl+' )
        self.connect( self.printPreviewButton, SIGNAL( 'triggered()' ),
                      self.__onPrintPreview )
        self.printPreviewButton.setVisible( False )

        spacer = QWidget()
        spacer.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding )

        self.clearButton = QAction(
            PixmapCache().getIcon( 'trash.png' ),
            'Clear', self )
        self.connect( self.clearButton, SIGNAL( 'triggered()' ),
                      self.__clear )

        # The toolbar
        self.toolbar = QToolBar( self )
        self.toolbar.setOrientation( Qt.Vertical )
        self.toolbar.setMovable( False )
        self.toolbar.setAllowedAreas( Qt.RightToolBarArea )
        self.toolbar.setIconSize( QSize( 16, 16 ) )
        self.toolbar.setFixedWidth( 28 )
        self.toolbar.setContentsMargins( 0, 0, 0, 0 )

        self.toolbar.addAction( self.__mcCabeButton )
        self.toolbar.addAction( self.printPreviewButton )
        self.toolbar.addAction( self.printButton )
        self.toolbar.addWidget( spacer )
        self.toolbar.addAction( self.clearButton )

        self.__totalResultsTree = QTreeWidget()
        self.__totalResultsTree.setAlternatingRowColors( True )
        self.__totalResultsTree.setRootIsDecorated( True )
        self.__totalResultsTree.setItemsExpandable( True )
        self.__totalResultsTree.setUniformRowHeights( True )
        self.__totalResultsTree.setItemDelegate( NoOutlineHeightDelegate( 4 ) )
        headerLabels = [ "Path / name", "Value", "" ]
        self.__totalResultsTree.setHeaderLabels( headerLabels )
        self.connect( self.__totalResultsTree,
                      SIGNAL( "itemActivated(QTreeWidgetItem *, int)" ),
                      self.__allItemActivated )
        self.connect( self.__totalResultsTree,
                      SIGNAL( "itemExpanded(QTreeWidgetItem *)" ),
                      self.__onResultsExpanded )
        self.__totalResultsTree.setColumnHidden( 2, True )
        self.__totalResultsTree.hide()

        self.__mcCabeTable = QTreeWidget()
        self.__mcCabeTable.setAlternatingRowColors( True )
        self.__mcCabeTable.setRootIsDecorated( False )
        self.__mcCabeTable.setItemsExpandable( False )
        self.__mcCabeTable.setSortingEnabled( True )
        self.__mcCabeTable.setItemDelegate( NoOutlineHeightDelegate( 4 ) )
        self.__mcCabeTable.setUniformRowHeights( True )
        headerLabels = [ "", "File name", "Object", "McCabe Complexity" ]
        self.__mcCabeTable.setHeaderLabels( headerLabels )
        self.connect( self.__mcCabeTable,
                      SIGNAL( "itemActivated(QTreeWidgetItem *, int)" ),
                      self.__mcCabeActivated )
        self.__mcCabeTable.hide()

        self.__hLayout = QHBoxLayout()
        self.__hLayout.setContentsMargins( 0, 0, 0, 0 )
        self.__hLayout.setSpacing( 0 )
        self.__hLayout.addWidget( self.toolbar )
        self.__hLayout.addWidget( self.__noneLabel )
        self.__hLayout.addWidget( self.__totalResultsTree )
        self.__hLayout.addWidget( self.__mcCabeTable )

        self.setLayout( self.__hLayout )
        return

    def getTotalResultsWidget( self ):
        " Provides a reference to the total results widget "
        return self.__totalResultsTree

    def getMcCabeResultsWidget( self ):
        " Provides a reference to the McCabe results widget "
        return self.__mcCabeTable

    def __updateButtonsStatus( self ):
        " Updates the buttons status "
        self.__mcCabeButton.setEnabled( self.__reportShown )
        self.printButton.setEnabled( self.__reportShown )
        self.printPreviewButton.setEnabled( self.__reportShown )
        self.clearButton.setEnabled( self.__reportShown )
        return

    def __onResultsExpanded( self, item ):
        " An item has been expanded, so the column width should be adjusted "
        self.__totalResultsTree.header().resizeSections(
                                            QHeaderView.ResizeToContents )
        return

    def __onPrint( self ):
        " Triggered when the print button is pressed "
        pass

    def __onPrintPreview( self ):
        " triggered when the print preview button is pressed "
        pass

    def __onMcCabe( self, state ):
        " Triggered when the metrics view is switched "

        if not self.__reportShown:
            return

        if state:
            self.__totalResultsTree.hide()
            self.__mcCabeTable.show()
            self.__mcCabeButton.setIcon(
                            PixmapCache().getIcon( 'treeview.png' ) )
            self.__mcCabeButton.setToolTip( "Switch to complete "
                                            "results tree view" )
        else:
            self.__mcCabeTable.hide()
            self.__totalResultsTree.show()
            self.__mcCabeButton.setIcon(
                            PixmapCache().getIcon( 'tableview.png' ) )
            self.__mcCabeButton.setToolTip( "Switch to McCabe only table view" )
        return

    def setFocus( self ):
        " Overridden setFocus "
        self.__hLayout.setFocus()
        return

    def __clear( self ):
        " Clears the content of the vertical layout "
        if not self.__reportShown:
            return

        self.__totalResultsTree.clear()
        self.__totalResultsTree.hide()
        self.__mcCabeTable.clear()
        self.__mcCabeTable.hide()
        self.__noneLabel.show()

        self.__report = None
        self.__reportShown = False
        self.__updateButtonsStatus()
#        self.resizeEvent()
        self.__mcCabeButton.setIcon( PixmapCache().getIcon( 'tableview.png' ) )
        self.__mcCabeButton.setToolTip( "Switch to McCabe only table view" )
        self.__mcCabeButton.setChecked( False )

        self.__updateTooltip()
        return

    def __updateTooltip( self ):
        " Generates a signal with appropriate string message "
        if not self.__reportShown:
            tooltip = "No metrics available"
        elif self.__reportOption == self.DirectoryFiles:
            tooltip = "Metrics generated for directory: " + \
                      self.__reportFileName
        elif self.__reportOption == self.ProjectFiles:
            tooltip = "Metrics generated for the whole project"
        elif self.__reportOption == self.SingleFile:
            tooltip = "Metrics generated for file: " + self.__reportFileName
        elif self.__reportOption == self.SingleBuffer:
            tooltip = "Metrics generated for unsaved file: " + \
                      self.__reportFileName
        else:
            tooltip = ""
        self.emit( SIGNAL( 'updatePymetricsTooltip' ), tooltip )
        return

    @staticmethod
    def __shouldShowFileName( table, column ):
        " Checks if the file name is the same "

        size = table.topLevelItemCount()
        if size == 0:
            return False

        index = size - 1
        firstName = table.topLevelItem( index ).text( column )
        index -= 1
        while index >= 0:
            if table.topLevelItem( index ).text( column ) != firstName:
                return True
            index -= 1
        return False

    def showReport( self, metrics, reportOption, fileName, uuid ):
        " Shows the pymetrics results "
        self.__clear()
        self.__noneLabel.hide()

        self.__report = metrics
        self.__reportUUID = uuid
        self.__reportFileName = fileName
        self.__reportOption = reportOption

        if len( metrics.report ) > 1:
            accumulatedBasic = self.__accumulateBasicMetrics()
            accItem = QTreeWidgetItem( [ "Cumulative basic metrics" ] )
            self.__totalResultsTree.addTopLevelItem( accItem )
            for key in accumulatedBasic:
                bmItem = [ BasicMetrics.metricsOfInterest[ key ],
                           splitThousands( str( accumulatedBasic[ key ] ) ) ]
                basicMetric = QTreeWidgetItem( bmItem )
                accItem.addChild( basicMetric )

        # Add the complete information
        for fileName in metrics.report:
            if reportOption == self.SingleBuffer:
                fileItem = QTreeWidgetItem( [ "Editor buffer" ] )
            else:
                fileItem = QTreeWidgetItem( [ fileName ] )
                info = GlobalData().briefModinfoCache.get( fileName )
                if info.docstring is not None:
                    fileItem.setToolTip( 0, info.docstring.text )
                else:
                    fileItem.setToolTip( 0, "" )
            self.__totalResultsTree.addTopLevelItem( fileItem )

            # Messages part
            messages = metrics.report[ fileName ].messages
            if len( messages ) > 0:
                messagesItem = QTreeWidgetItem( [ "Messages" ] )
                fileItem.addChild( messagesItem )
                for message in messages:
                    mItem = [ message, "", "E" ]
                    messagesItem.addChild( QTreeWidgetItem( mItem ) )

            # Basic metrics part
            basicItem = QTreeWidgetItem( [ "Basic metrics" ] )
            fileItem.addChild( basicItem )
            basic = metrics.report[ fileName ].basicMetrics
            for key in basic.metrics:
                bmItem = [ BasicMetrics.metricsOfInterest[ key ],
                           str( basic.metrics[ key ] ) ]
                basicMetric = QTreeWidgetItem( bmItem )
                basicItem.addChild( basicMetric )

            # McCabe part
            mccabeItem = QTreeWidgetItem( [ "McCabe metrics" ] )
            fileItem.addChild( mccabeItem )
            mccabe = metrics.report[ fileName ].mcCabeMetrics.metrics
            for objName in mccabe:
                objItem = [ objName, str( mccabe[ objName ] ), "M" ]
                mccabeMetric = QTreeWidgetItem( objItem )
                mccabeItem.addChild( mccabeMetric )


            # COCOMO 2 part
            cocomo = [ "COCOMO 2", str( metrics.report[ fileName ].cocomo2Metrics.value ) ]
            cocomoItem = QTreeWidgetItem( cocomo )
            fileItem.addChild( cocomoItem )



        # Resizing the table
        self.__totalResultsTree.header().resizeSections(
                                            QHeaderView.ResizeToContents )


        # Add McCabe complexity information
        for fileName in metrics.report:
            mccabe = metrics.report[ fileName ].mcCabeMetrics.metrics
            for objName in mccabe:
                values = [ "", fileName, objName, str( mccabe[ objName ] ) ]
                self.__mcCabeTable.addTopLevelItem( McCabeTableItem( values ) )

        if not self.__shouldShowFileName( self.__mcCabeTable, 1 ):
            self.__mcCabeTable.setColumnHidden( 1, True )

        # Resizing and sorting the table
        self.__mcCabeTable.header().setSortIndicator( 3, Qt.DescendingOrder )
        self.__mcCabeTable.sortItems( 3,
                          self.__mcCabeTable.header().sortIndicatorOrder() )
        self.__mcCabeTable.header().resizeSections(
                          QHeaderView.ResizeToContents )

        # Show the complete information
        self.__mcCabeTable.hide()
        self.__totalResultsTree.show()

        self.__reportShown = True
        self.__updateButtonsStatus()
        self.__updateTooltip()

        # It helps, but why do I have flickering?
        QApplication.processEvents()
        return

    def __accumulateBasicMetrics( self ):
        " Accumulates basic metrics for all the processed files "
        basic = {}
        for fileName in self.__report.report:
            singleBasic = self.__report.report[ fileName ].basicMetrics.metrics
            for key in singleBasic:
                if not key.startswith( 'num' ):
                    continue
                if key in basic:
                    basic[ key ] += int( singleBasic[ key ] )
                else:
                    basic[ key ] = int( singleBasic[ key ] )
        return basic

    def __mcCabeActivated( self, item, column ):
        " Handles the double click (or Enter) on the mccabe table item "

        objName = str( item.text( 2 ) )
        if self.__reportOption == self.SingleBuffer:
            if os.path.isabs( self.__reportFileName ):
                fileName = self.__reportFileName
            else:
                fileName = ""
        else:
            fileName = str( item.text( 1 ) )
        self.__onMcCabeObject( objName, fileName )
        return

    def __allItemActivated( self, item, column ):
        " Handles the double click (or Enter) in the total results tree "

        # We process only the error messages and McCabe items
        hiddenColumnText = str( item.text( 2 ) )
        if not hiddenColumnText in [ "M", "E" ]:
            return

        fileName = self.__getTreeItemFileName( item )
        lineNumber = 0
        if hiddenColumnText == "M":
            # This is McCabe item
            objName = str( item.text( 0 ) )
            self.__onMcCabeObject( objName, fileName )
            return
        elif hiddenColumnText == "E":
            # This is an error message
            message = str( item.text( 0 ) )
            pos = message.find( "at line" )
            if pos == -1:
                logging.error( "Unknown format of the message. "
                               "Please inform the developers." )
                return
            parts = message[ pos: ].split()
            try:
                lineNumber = int( parts[ 2 ].replace( ',', '' ) )
            except:
                logging.error( "Unknown format of the message. "
                               "Please inform the developers." )
                return

            if fileName == "":
                # This is an unsaved buffer, try to find the editor by UUID
                mainWindow = GlobalData().mainWindow
                widget = mainWindow.getWidgetByUUID( self.__reportUUID )
                if widget is None:
                    logging.error( "The unsaved buffer has been closed" )
                    return
                # The widget was found, so jump to the required
                editor = widget.getEditor()
                editor.gotoLine( lineNumber )
                editor.setFocus()
                return

        GlobalData().mainWindow.openFile( fileName, lineNumber )
        return

    def __getTreeItemFileName( self, item ):
        " Identifies the tree view item file name "
        if self.__reportOption == self.SingleBuffer:
            if os.path.isabs( self.__reportFileName ):
                return self.__reportFileName
            return ""

        # The file name is always two levels up
        fileItem = item.parent().parent()
        return str( fileItem.text( 0 ) )

    def __onMcCabeObject( self, objName, fileName ):
        " Called when the user activated McCabe item "

        info = None

        mainWindow = GlobalData().mainWindow
        widget = mainWindow.getWidgetByUUID( self.__reportUUID )
        if widget is None:
            if fileName == "":
                logging.error( "The unsaved buffer has been closed" )
                return
            # No widget, but we know the file name
            info = getBriefModuleInfoFromFile( fileName )
        else:
            # The widget was found
            editor = widget.getEditor()
            # The editor content has been modified, so re-parse the buffer
            info = getBriefModuleInfoFromMemory( editor.text() )

        parts = objName.split( '.' )
        currentIndex = 0
        functionsContainer = info.functions
        classesContainer = info.classes
        line = -1

        if objName == "__main__" and len( parts ) == 1:
            # Special case - global file scope
            line = 1
            currentIndex = 1

        while currentIndex < len( parts ):
            found = False
            for func in functionsContainer:
                if func.name == parts[ currentIndex ]:
                    if currentIndex == len( parts ) - 1:
                        # Found, jump to the line
                        line = func.line
                        break
                    functionsContainer = func.functions
                    classesContainer = func.classes
                    found = True
                    break
            if line != -1:
                break
            if found:
                currentIndex += 1
                continue
            for klass in classesContainer:
                if klass.name == parts[ currentIndex ]:
                    if currentIndex == len( parts ) - 1:
                        # Found, jump to the line
                        line = klass.line
                        break
                    functionsContainer = klass.functions
                    classesContainer = klass.classes
                    found = True
            if line != -1:
                break
            if found:
                currentIndex += 1
                continue

            # Not found
            logging.error( "Cannot find the " + objName )
            return

        # Here we have the line number
        if widget is None:
            GlobalData().mainWindow.openFile( fileName, line )
        else:
            editor = widget.getEditor()
            editor.gotoLine( line )
            editor.setFocus()
        return

    def onFileUpdated( self, fileName, uuid ):
        " Called when a buffer is saved or saved as "

        if not self.__reportShown:
            return
        if self.__reportUUID != uuid:
            return

        # Currently shown report is for the saved buffer
        # File name is expected being absolute
        self.__reportFileName = fileName
        self.emit( SIGNAL( 'updatePymetricsTooltip' ),
                   "Metrics generated for buffer saved as " + fileName )
        return
Example #34
0
File: qmap.py Project: s-chand/qmap
class QMap():
    def __init__(self, iface):
        self.iface = iface
        self.actions = []
        self.panels= []
        self.navtoolbar = self.iface.mapNavToolToolBar()
        self.mainwindow = self.iface.mainWindow()
        self.iface.projectRead.connect(self.projectOpened)
        self.iface.initializationCompleted.connect(self.setupUI)
        self.actionGroup = QActionGroup(self.mainwindow)
        self.actionGroup.setExclusive(True)
        self.menuGroup = QActionGroup(self.mainwindow)
        self.menuGroup.setExclusive(True)
                
        self.movetool = MoveTool(self.iface.mapCanvas(), [])
        self.report = PopDownReport(self.iface.messageBar())
        
        self.dialogprovider = DialogProvider(iface.mapCanvas(), iface)
        self.dialogprovider.accepted.connect(self.clearToolRubberBand)
        self.dialogprovider.rejected.connect(self.clearToolRubberBand)
        
        self.edittool = EditTool(self.iface.mapCanvas(),[])
        self.edittool.finished.connect(self.openForm)

    @property
    def _mapLayers(self):
        return QgsMapLayerRegistry.instance().mapLayers()
        
    def clearToolRubberBand(self):
        tool = self.iface.mapCanvas().mapTool()
        try:
            tool.clearBand()
        except AttributeError:
            # No clearBand method found, but that's cool.
            pass
        
    def missingLayers(self, layers):
        def showError():
            html = ["<h1>Missing Layers</h1>", "<ul>"]
            for layer in layers:
                html.append("<li>{}</li>".format(layer))
            html.append("</ul>")
            
            self.errorreport.updateHTML("".join(html))
        
        message = "Seems like {} didn't load correctly".format(utils._pluralstring('layer', len(layers)))
        
        utils.warning("Missing layers")
        map(utils.warning, layers)
            
        self.widget = self.messageBar.createMessage("Missing Layers", 
                                                 message, 
                                                 QIcon(":/icons/sad"))
        button = QPushButton(self.widget)
        button.setCheckable(True)
        button.setChecked(self.errorreport.isVisible())
        button.setText("Show missing layers")
        button.toggled.connect(showError)
        button.toggled.connect(functools.partial(self.errorreport.setVisible))
        self.widget.destroyed.connect(self.hideReports)
        self.widget.layout().addWidget(button)
        self.messageBar.pushWidget(self.widget, QgsMessageBar.WARNING)
        
    def excepthook(self, ex_type, value, tb):
        """ 
        Custom exception hook so that we can handle errors in a
        nicer way
        """
        where = ''.join(traceback.format_tb(tb))
        msg = '{}'.format(value)
        utils.critical(msg)
        
        def showError():
            html = """
                <html>
                <body bgcolor="#FFEDED">
                <p><b>{}</b></p>
                <p align="left"><small>{}</small></p>
                </body>
                </html>
            """.format(msg, where)
            self.errorreport.updateHTML(html)
        
        self.widget = self.messageBar.createMessage("oops", "Looks like an error occurred", QIcon(":/icons/sad"))
        button = QPushButton(self.widget)
        button.setCheckable(True)
        button.setChecked(self.errorreport.isVisible())
        button.setText("Show error")
        button.toggled.connect(showError)
        button.toggled.connect(functools.partial(self.errorreport.setVisible))
        self.widget.destroyed.connect(self.hideReports)
        self.widget.layout().addWidget(button)
        self.messageBar.pushWidget(self.widget, QgsMessageBar.CRITICAL)
    
    def hideReports(self):
        self.errorreport.setVisible(False)
        self.report.setVisible(False)
    
    def setupUI(self):
        """
        Set up the main QGIS interface items.  Called after QGIS has loaded
        the plugin.
        """
        fullscreen = utils.settings["fullscreen"]
        if fullscreen:
            self.mainwindow.showFullScreen()
        else:
            self.mainwindow.showMaximized()

        self.navtoolbar.setMovable(False)
        self.navtoolbar.setAllowedAreas(Qt.TopToolBarArea)
        
        self.mainwindow.insertToolBar(self.toolbar, self.navtoolbar)
        self.openProjectAction.trigger()
            
    def setMapTool(self, tool):
        """
        Set the current mapview canvas tool

        tool -- The QgsMapTool to set
        """
        self.iface.mapCanvas().setMapTool(tool)

    def createToolBars(self):
        """
        Create all the needed toolbars
        """
        
        self.menutoolbar = QToolBar("Menu", self.mainwindow)
        self.menutoolbar.setMovable(False)
        self.menutoolbar.setAllowedAreas(Qt.LeftToolBarArea)
        self.mainwindow.addToolBar(Qt.LeftToolBarArea, self.menutoolbar)
        
        self.toolbar = QToolBar("QMap", self.mainwindow)
        self.mainwindow.addToolBar(Qt.TopToolBarArea, self.toolbar)
        self.toolbar.setMovable(False)

        self.editingtoolbar = FloatingToolBar("Editing", self.toolbar)
        self.extraaddtoolbar = FloatingToolBar("Extra Add Tools", self.toolbar)
        self.syncactionstoolbar = FloatingToolBar("Syncing", self.toolbar)
        self.syncactionstoolbar.setOrientation(Qt.Vertical)

    def createActions(self):
        """
        Create all the actions
        """

        self.homeAction = (QAction(QIcon(":/icons/zoomfull"),
                                  "Default View", self.mainwindow))
        self.gpsAction = (GPSAction(QIcon(":/icons/gps"), self.iface.mapCanvas(),
                                   self.mainwindow))
        self.openProjectAction = (QAction(QIcon(":/icons/open"), "Projects",
                                         self.mainwindow))
        self.openProjectAction.setCheckable(True)
        self.toggleRasterAction = (QAction(QIcon(":/icons/photo"), "Aerial Photos",
                                          self.mainwindow))
        self.syncAction = QAction(QIcon(":/icons/sync"), "Sync", self.mainwindow)
        self.syncAction.setVisible(False)
        
        self.editattributesaction = QAction(QIcon(":/icons/edit"), "Edit Attributes", self.mainwindow)
        self.editattributesaction.setCheckable(True)
        self.editattributesaction.toggled.connect(functools.partial(self.setMapTool, self.edittool))

        self.moveaction = QAction(QIcon(":/icons/move"), "Move Feature", self.mainwindow)
        self.moveaction.setCheckable(True)

        self.editingmodeaction = QAction(QIcon(":/icons/edittools"), "Editing Tools", self.mainwindow)
        self.editingmodeaction.setCheckable(True)

        self.addatgpsaction = QAction(QIcon(":/icons/gpsadd"), "Add at GPS", self.mainwindow)
        
        self.edittool.layersupdated.connect(self.editattributesaction.setVisible)
        self.movetool.layersupdated.connect(self.moveaction.setVisible)
        self.edittool.layersupdated.connect(self.updateEditTools)
        self.movetool.layersupdated.connect(self.updateEditTools)
        
    def updateEditTools(self, *args):
        """
            Show or hide the Editing Tools button based on the sub tools.
        """
        if self.edittool.layers and self.movetool.layers:  
            self.editingmodeaction.setVisible(True)
        else:
            self.editingmodeaction.setVisible(False)

    def initGui(self):
        """
        Create all the icons and setup the tool bars.  Called by QGIS when
        loading. This is called before setupUI.
        """
        QApplication.setWindowIcon(QIcon(":/branding/logo"))
        self.mainwindow.findChildren(QMenuBar)[0].setVisible(False)
        self.mainwindow.setContextMenuPolicy(Qt.PreventContextMenu)
        self.mainwindow.setWindowTitle("IntraMaps Roam: Mobile Data Collection")
        
        # Disable QGIS logging window popups. We do our own logging
        QgsMessageLog.instance().messageReceived.disconnect()
        
        s = """
        QToolButton { 
            padding: 6px;
            color: #4f4f4f;
         }
        
        QToolButton:hover { 
            padding: 6px;
            background-color: rgb(211, 228, 255);
         }
         
        QToolBar {
         background: white;
        }
        
        QCheckBox::indicator {
             width: 40px;
             height: 40px;
         }
        
        QLabel {
            color: #4f4f4f;
        }
        
        QDialog { background-color: rgb(255, 255, 255); }
        
        QPushButton { 
            border: 1px solid #e1e1e1;
             padding: 6px;
            color: #4f4f4f;
         }
        
        QPushButton:hover { 
            border: 1px solid #e1e1e1;
             padding: 6px;
            background-color: rgb(211, 228, 255);
         }
        
        QCheckBox {
            color: #4f4f4f;
        }
        
        QComboBox::drop-down {
            width: 30px;
        }


        """
        self.mainwindow.setStyleSheet(s)
        
        mainwidget = self.mainwindow.centralWidget()
        mainwidget.setLayout(QGridLayout())
        mainwidget.layout().setContentsMargins(0,0,0,0)
        
        newlayout = QGridLayout()
        newlayout.setContentsMargins(0,0,0,0)
        newlayout.addWidget(self.iface.mapCanvas(), 0,0,2,1)
        newlayout.addWidget(self.iface.messageBar(), 0,0,1,1)
        
        wid = QWidget()
        wid.setLayout(newlayout)
        
        self.stack = QStackedWidget()
        self.messageBar = QgsMessageBar(wid)
        self.messageBar.setSizePolicy( QSizePolicy.Minimum, QSizePolicy.Fixed )
        self.errorreport = PopDownReport(self.messageBar)
         
        mainwidget.layout().addWidget(self.stack, 0,0,2,1)
        mainwidget.layout().addWidget(self.messageBar, 0,0,1,1)
        
        self.helppage = HelpPage()
        helppath = os.path.join(os.path.dirname(__file__) , 'help',"help.html")
        self.helppage.setHelpPage(helppath)
        
        self.projectwidget = ProjectsWidget()   
        self.projectwidget.requestOpenProject.connect(self.loadProject)
        self.stack.addWidget(wid)
        self.stack.addWidget(self.projectwidget)
        self.stack.addWidget(self.helppage)
                
        sys.excepthook = self.excepthook

        def createSpacer(width=30):
            widget = QWidget()
            widget.setMinimumWidth(width)
            return widget

        self.createToolBars()
        self.createActions()

        spacewidget = createSpacer(60)
        gpsspacewidget = createSpacer()
        gpsspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.moveaction.toggled.connect(functools.partial(self.setMapTool, self.movetool))
        showediting = (functools.partial(self.editingtoolbar.showToolbar, 
                                         self.editingmodeaction,
                                         self.editattributesaction))
        self.editingmodeaction.toggled.connect(showediting)

        self.addatgpsaction.triggered.connect(self.addAtGPS)
        self.addatgpsaction.setEnabled(self.gpsAction.isConnected)
        self.gpsAction.gpsfixed.connect(self.addatgpsaction.setEnabled)

        self.editingtoolbar.addToActionGroup(self.editattributesaction)
        self.editingtoolbar.addToActionGroup(self.moveaction)

        self.actionGroup.addAction(self.editingmodeaction)

        self.homeAction.triggered.connect(self.zoomToDefaultView)
        
        self.openProjectAction.triggered.connect(self.showOpenProjectDialog)
        self.openProjectAction.triggered.connect(functools.partial(self.stack.setCurrentIndex, 1))
        
        self.toggleRasterAction.triggered.connect(self.toggleRasterLayers)

        self.navtoolbar.insertAction(self.iface.actionZoomIn(), self.iface.actionTouch())
        self.navtoolbar.insertAction(self.iface.actionTouch(), self.homeAction)
        self.navtoolbar.insertAction(self.iface.actionTouch(), self.iface.actionZoomFullExtent())
        self.navtoolbar.insertAction(self.homeAction, self.iface.actionZoomFullExtent())

        self.navtoolbar.addAction(self.toggleRasterAction)
        self.navtoolbar.insertWidget(self.iface.actionZoomFullExtent(), spacewidget)
        self.toolbar.addAction(self.editingmodeaction)
        self.toolbar.addAction(self.syncAction)
        self.toolbar.addAction(self.gpsAction)
        self.toolbar.insertWidget(self.syncAction, gpsspacewidget)
        self.toolbar.insertSeparator(self.gpsAction)

        self.extraaddtoolbar.addAction(self.addatgpsaction)

        self.editingtoolbar.addAction(self.editattributesaction)
        self.editingtoolbar.addAction(self.moveaction)
        
        self.mapview = QAction(QIcon(":/icons/map"), "Map", self.menutoolbar)
        self.mapview.setCheckable(True)
        self.mapview.triggered.connect(functools.partial(self.stack.setCurrentIndex, 0))
        
        self.help = QAction(QIcon(":/icons/help"), "Help", self.menutoolbar)
        self.help.setCheckable(True)
        self.help.triggered.connect(functools.partial(self.stack.setCurrentIndex, 2))
        self.help.setVisible(False)
        
        self.userlabel = QLabel("Current User <br> {user}".format(user=getpass.getuser()))
        self.userlabel.setAlignment(Qt.AlignCenter)
        self.userlabel.setStyleSheet("""
            QLabel {
                    color: #8c8c8c;
                    font: 10px "Calibri" ;
                    }""")
        
        self.quit = QAction(QIcon(":/icons/quit"), "Quit", self.menutoolbar)
        self.quit.triggered.connect(self.iface.actionExit().trigger)

        self.menuGroup.addAction(self.mapview)
        self.menuGroup.addAction(self.openProjectAction)
        self.menuGroup.addAction(self.help)
        
        self.menutoolbar.addAction(self.mapview)
        self.menutoolbar.addAction(self.openProjectAction)
        self.menutoolbar.addAction(self.help)
        self.menutoolbar.addAction(self.quit)
        
        quitspacewidget = createSpacer()
        quitspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        
        labelaction = self.menutoolbar.insertWidget(self.quit, self.userlabel)
        self.menutoolbar.insertWidget(labelaction, quitspacewidget)
        
        self.setupIcons()
        self.stack.currentChanged.connect(self.updateUIState)
        
    def updateUIState(self, page):
        """
        Update the UI state to reflect the currently selected
        page in the stacked widget
        """
        def setToolbarsActive(enabled):
            toolbars = self.mainwindow.findChildren(QToolBar)
            for toolbar in toolbars:
                if toolbar == self.menutoolbar:
                    continue
                toolbar.setEnabled(enabled)
        
        def setPanelsVisible(visible):
            for panel in self.panels:
                panel.setVisible(visible)
                
        ismapview = page == 0
        setToolbarsActive(ismapview)
        setPanelsVisible(ismapview)

    def addAtGPS(self):
        """
        Add a record at the current GPS location.
        """
        action = self.actionGroup.checkedAction()
        if not action:
            return
        layer = action.data()
        if not layer:
            return
        
        point = self.gpsAction.position
        self.addNewFeature(layer=layer, geometry=point)
        
    def zoomToDefaultView(self):
        """
        Zoom the mapview canvas to the extents the project was opened at i.e. the
        default extent.
        """
        self.iface.mapCanvas().setExtent(self.defaultextent)
        self.iface.mapCanvas().refresh()

    def toggleRasterLayers(self):
        """
        Toggle all raster layers on or off.
        """
        legend = self.iface.legendInterface()
        #Freeze the canvas to save on UI refresh
        self.iface.mapCanvas().freeze()
        for layer in self._mapLayers.values():
            if layer.type() == QgsMapLayer.RasterLayer:
                isvisible = legend.isLayerVisible(layer)
                legend.setLayerVisible(layer, not isvisible)
        self.iface.mapCanvas().freeze(False)
        self.iface.mapCanvas().refresh()

    def setupIcons(self):
        """
        Update toolbars to have text and icons, change normal QGIS
        icons to new style
        """
        toolbars = self.mainwindow.findChildren(QToolBar)
        for toolbar in toolbars:
            toolbar.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
            toolbar.setIconSize(QSize(32, 32))

        self.iface.actionTouch().setIconText("Pan")
        self.iface.actionTouch().setIcon(QIcon(":/icons/pan"))
        self.iface.actionZoomIn().setIcon(QIcon(":/icons/in"))
        self.iface.actionZoomOut().setIcon(QIcon(":/icons/out"))
        self.iface.actionPan().setIcon(QIcon(":/icons/pan"))
        self.iface.actionZoomFullExtent().setIcon(QIcon(":/icons/home"))
        self.iface.actionZoomFullExtent().setIconText("Home View")

        self.actionGroup.addAction(self.iface.actionZoomIn())
        self.actionGroup.addAction(self.iface.actionZoomOut())
        self.actionGroup.addAction(self.iface.actionTouch())

    def projectOpened(self):
        """
            Called when a new project is opened in QGIS.
        """
        for panel in self.panels:
            self.mainwindow.removeDockWidget(panel)
            del panel

        projectpath = QgsProject.instance().fileName()
        project = QMapProject(os.path.dirname(projectpath), self.iface)
        self.createFormButtons(projectlayers = project.getConfiguredLayers())
        
        # Enable the raster layers button only if the project contains a raster layer.
        hasrasters = any(layer.type() for layer in self._mapLayers.values())
        self.toggleRasterAction.setEnabled(hasrasters)
        self.defaultextent = self.iface.mapCanvas().extent()
        self.connectSyncProviders(project)
        
        # Show panels
        self.panels = list(project.getPanels())
        for panel in self.panels:
            self.mainwindow.addDockWidget(Qt.BottomDockWidgetArea , panel)
            
        self.iface.messageBar().popWidget()
        
    def createFormButtons(self, projectlayers):
        """
            Create buttons for each form that is definded
        """
        # Remove all the old buttons
        for action in self.actions:
            self.actionGroup.removeAction(action)
            self.toolbar.removeAction(action)
                
        self.edittool.layers = []
        self.movetool.layers = []
        for layer in projectlayers:
            try:
                qgslayer = QgsMapLayerRegistry.instance().mapLayersByName(layer.name)[0]
                if qgslayer.type() == QgsMapLayer.RasterLayer:
                    utils.log("We can't support raster layers for data entry")
                    continue
                       
                layer.QGISLayer = qgslayer
            except KeyError:
                utils.log("Layer not found in project")
                continue
            
            if 'capture' in layer.capabilities:
                text = layer.icontext
                try:
                    tool = layer.getMaptool(self.iface.mapCanvas())
                except NoMapToolConfigured:
                    utils.log("No map tool configured")
                    continue
                except ErrorInMapTool as error:
                    self.messageBar.pushMessage("Error configuring map tool", error.message, level=QgsMessageBar.WARNING)
                    continue
                    
                # Hack until I fix it later
                if isinstance(tool, PointTool):
                    add = functools.partial(self.addNewFeature, qgslayer)
                    tool.geometryComplete.connect(add)
                else:
                    tool.finished.connect(self.openForm)
         
                action = QAction(QIcon(layer.icon), text, self.mainwindow)
                action.setData(layer)
                action.setCheckable(True)
                action.toggled.connect(functools.partial(self.setMapTool, tool))
                
                self.toolbar.insertAction(self.editingmodeaction, action)
                
                if not tool.isEditTool():
                    # Connect the GPS tools strip to the action pressed event.                
                    showgpstools = (functools.partial(self.extraaddtoolbar.showToolbar, 
                                                 action,
                                                 None))
                    
                    action.toggled.connect(showgpstools)
                    
                self.actionGroup.addAction(action)
                self.actions.append(action)
            
            if 'edit' in layer.capabilities:
                # TODO Use snapping options from project
                radius = (QgsTolerance.toleranceInMapUnits( 10, qgslayer,
                                                            self.iface.mapCanvas().mapRenderer(), 
                                                            QgsTolerance.Pixels))
                self.edittool.addLayer(qgslayer)
                self.edittool.searchRadius = radius
                
            if 'move' in layer.capabilities:
                self.movetool.addLayer(qgslayer)
            
    def openForm(self, layer, feature):
        if not layer.isEditable():
            layer.startEditing()
            
        self.dialogprovider.openDialog(feature=feature, layer=layer)
            
    def addNewFeature(self, layer, geometry):
        fields = layer.pendingFields()
        
        if not layer.isEditable():
            layer.startEditing()
    
        feature = QgsFeature()
        feature.setGeometry( geometry )
        feature.initAttributes(fields.count())
        feature.setFields(fields)
        
        for indx in xrange(fields.count()):
            feature[indx] = layer.dataProvider().defaultValue(indx)

        self.dialogprovider.openDialog(feature=feature, layer=layer)

    def showOpenProjectDialog(self):
        """
        Show the project selection dialog.
        """
        self.stack.setCurrentIndex(1)
        path = os.path.join(os.path.dirname(__file__), '..' , 'projects/')
        projects = getProjects(path, self.iface)
        self.projectwidget.loadProjectList(projects)
        
    def loadProject(self, project):
        """
        Load a project into QGIS.
        """
        utils.log(project)
        utils.log(project.name)
        utils.log(project.projectfile)
        utils.log(project.vaild)
        
        (passed, message) = project.onProjectLoad()
        
        if not passed:
            QMessageBox.warning(self.mainwindow, "Project Load Rejected", 
                                "Project couldn't be loaded because {}".format(message))
            return
        
        self.mapview.trigger()
        self.iface.newProject(False)        
        self.iface.mapCanvas().freeze()
        fileinfo = QFileInfo(project.projectfile)
        self.badLayerHandler = BadLayerHandler(callback=self.missingLayers)
        QgsProject.instance().setBadLayerHandler( self.badLayerHandler )
        
        self.iface.messageBar().pushMessage("Project Loading","", QgsMessageBar.INFO)
        QgsProject.instance().read(fileinfo)
        
        self.iface.mapCanvas().updateScale()
        self.iface.mapCanvas().freeze(False)
        self.iface.mapCanvas().refresh()
        self.mainwindow.setWindowTitle("IntraMaps Roam: Mobile Data Collection")
        self.iface.projectRead.emit()
        
    def unload(self):
        del self.toolbar
        
    def connectSyncProviders(self, project):
        self.syncactionstoolbar.clear()
        
        syncactions = list(project.getSyncProviders())
        
        # Don't show the sync button if there is no sync providers
        if not syncactions:
            self.syncAction.setVisible(False)
            return
        
        self.syncAction.setVisible(True)
        
        for provider in syncactions:
            action = QAction(QIcon(":/icons/sync"), "Sync {}".format(provider.name), self.mainwindow)
            action.triggered.connect(functools.partial(self.syncProvider, provider))
            self.syncactionstoolbar.addAction(action)
        
        try:
            self.syncAction.toggled.disconnect()
        except TypeError:
            pass
        try:
            self.syncAction.triggered.disconnect()
        except TypeError:
            pass
        
        if len(syncactions) == 1:
            # If one provider is set then we just connect the main button.    
            self.syncAction.setCheckable(False)
            self.syncAction.setText("Sync")
            self.syncAction.triggered.connect(functools.partial(self.syncProvider, syncactions[0]))
        else:
            # the sync button because a sync menu
            self.syncAction.setCheckable(True)
            self.syncAction.setText("Sync Menu")
            showsyncoptions = (functools.partial(self.syncactionstoolbar.showToolbar, 
                                                 self.syncAction, None))
            self.syncAction.toggled.connect(showsyncoptions)

    def syncstarted(self):                   
        # Remove the old widget if it's still there.
        # I don't really like this. Seems hacky.
        try:
            self.iface.messageBar().popWidget(self.syncwidget)
        except RuntimeError:
            pass
        except AttributeError:
            pass
        
        self.iface.messageBar().findChildren(QToolButton)[0].setVisible(False)        
        self.syncwidget = self.iface.messageBar().createMessage("Syncing", "Sync in progress", QIcon(":/icons/syncing"))
        button = QPushButton(self.syncwidget)
        button.setCheckable(True)
        button.setText("Status")
        button.setIcon(QIcon(":/icons/syncinfo"))
        button.toggled.connect(functools.partial(self.report.setVisible))
        pro = QProgressBar()
        pro.setMaximum(0)
        pro.setMinimum(0)
        self.syncwidget.layout().addWidget(pro)
        self.syncwidget.layout().addWidget(button)
        self.iface.messageBar().pushWidget(self.syncwidget, QgsMessageBar.INFO)
        
    def synccomplete(self):
        try:
            self.iface.messageBar().popWidget(self.syncwidget)
        except RuntimeError:
            pass
        
        stylesheet = ("QgsMessageBar { background-color: rgba(239, 255, 233); border: 0px solid #b9cfe4; } "
                     "QLabel,QTextEdit { color: #057f35; } ")
        
        closebutton = self.iface.messageBar().findChildren(QToolButton)[0]
        closebutton.setVisible(True)
        closebutton.clicked.connect(functools.partial(self.report.setVisible, False))
        self.syncwidget = self.iface.messageBar().createMessage("Syncing", "Sync Complete", QIcon(":/icons/syncdone"))
        button = QPushButton(self.syncwidget)
        button.setCheckable(True)
        button.setChecked(self.report.isVisible())
        button.setText("Sync Report")
        button.setIcon(QIcon(":/icons/syncinfo"))
        button.toggled.connect(functools.partial(self.report.setVisible))      
        pro = QProgressBar()
        pro.setMaximum(100)
        pro.setValue(100)
        self.syncwidget.layout().addWidget(pro)      
        self.syncwidget.layout().addWidget(button)
        self.iface.messageBar().pushWidget(self.syncwidget)
        self.iface.messageBar().setStyleSheet(stylesheet)
        self.iface.mapCanvas().refresh()
        
    def syncerror(self):
        try:
            self.iface.messageBar().popWidget(self.syncwidget)
        except RuntimeError:
            pass
        
        closebutton = self.iface.messageBar().findChildren(QToolButton)[0]
        closebutton.setVisible(True)
        closebutton.clicked.connect(functools.partial(self.report.setVisible, False))
        self.syncwidget = self.iface.messageBar().createMessage("Syncing", "Sync Error", QIcon(":/icons/syncfail"))
        button = QPushButton(self.syncwidget)
        button.setCheckable(True)
        button.setChecked(self.report.isVisible())
        button.setText("Sync Report")
        button.setIcon(QIcon(":/icons/syncinfo"))
        button.toggled.connect(functools.partial(self.report.setVisible))            
        self.syncwidget.layout().addWidget(button)
        self.iface.messageBar().pushWidget(self.syncwidget, QgsMessageBar.CRITICAL)
        self.iface.mapCanvas().refresh()
        
    def syncProvider(self, provider):
        self.syncAction.toggle()
        provider.syncStarted.connect(functools.partial(self.syncAction.setEnabled, False))
        provider.syncStarted.connect(self.syncstarted)
        
        provider.syncComplete.connect(self.synccomplete)
        provider.syncComplete.connect(functools.partial(self.syncAction.setEnabled, True))
        provider.syncComplete.connect(functools.partial(self.report.updateHTML))
        
        provider.syncMessage.connect(self.report.updateHTML)
        
        provider.syncError.connect(self.report.updateHTML)
        provider.syncError.connect(self.syncerror)
        provider.syncComplete.connect(functools.partial(self.syncAction.setEnabled, True))
        
        provider.startSync()
class IgnoredExceptionsViewer( QWidget ):
    " Implements the client exceptions viewer for a debugger "

    def __init__( self, parent = None ):
        QWidget.__init__( self, parent )

        self.__createPopupMenu()
        self.__createLayout()
        self.__ignored = []
        self.__currentItem = None

        GlobalData().project.projectChanged.connect( self.__onProjectChanged )

        if Settings().showIgnoredExcViewer == False:
            self.__onShowHide( True )
        return

    def __createPopupMenu( self ):
        " Creates the popup menu "
        self.__excptMenu = QMenu()
        self.__removeMenuItem = self.__excptMenu.addAction(
                    PixmapCache().getIcon( 'ignexcptdel.png' ),
                    "Remove from ignore list", self.__onRemoveFromIgnore )
        return

    def __createLayout( self ):
        " Creates the widget layout "

        verticalLayout = QVBoxLayout( self )
        verticalLayout.setContentsMargins( 0, 0, 0, 0 )
        verticalLayout.setSpacing( 0 )

        self.headerFrame = QFrame()
        self.headerFrame.setFrameStyle( QFrame.StyledPanel )
        self.headerFrame.setAutoFillBackground( True )
        headerPalette = self.headerFrame.palette()
        headerBackground = headerPalette.color( QPalette.Background )
        headerBackground.setRgb( min( headerBackground.red() + 30, 255 ),
                                 min( headerBackground.green() + 30, 255 ),
                                 min( headerBackground.blue() + 30, 255 ) )
        headerPalette.setColor( QPalette.Background, headerBackground )
        self.headerFrame.setPalette( headerPalette )
        self.headerFrame.setFixedHeight( 24 )

        self.__excptLabel = QLabel( "Ignored exception types" )

        expandingSpacer = QSpacerItem( 10, 10, QSizePolicy.Expanding )
        fixedSpacer = QSpacerItem( 3, 3 )

        self.__showHideButton = QToolButton()
        self.__showHideButton.setAutoRaise( True )
        self.__showHideButton.setIcon( PixmapCache().getIcon( 'less.png' ) )
        self.__showHideButton.setFixedSize( 20, 20 )
        self.__showHideButton.setToolTip( "Hide ignored exceptions list" )
        self.__showHideButton.setFocusPolicy( Qt.NoFocus )
        self.__showHideButton.clicked.connect( self.__onShowHide )

        headerLayout = QHBoxLayout()
        headerLayout.setContentsMargins( 1, 1, 1, 1 )
        headerLayout.addSpacerItem( fixedSpacer )
        headerLayout.addWidget( self.__excptLabel )
        headerLayout.addSpacerItem( expandingSpacer )
        headerLayout.addWidget( self.__showHideButton )
        self.headerFrame.setLayout( headerLayout )

        self.exceptionsList = QTreeWidget( self )
        self.exceptionsList.setSortingEnabled( False )
        self.exceptionsList.setAlternatingRowColors( True )
        self.exceptionsList.setRootIsDecorated( False )
        self.exceptionsList.setItemsExpandable( True )
        self.exceptionsList.setUniformRowHeights( True )
        self.exceptionsList.setSelectionMode( QAbstractItemView.SingleSelection )
        self.exceptionsList.setSelectionBehavior( QAbstractItemView.SelectRows )
        self.exceptionsList.setItemDelegate( NoOutlineHeightDelegate( 4 ) )
        self.exceptionsList.setContextMenuPolicy( Qt.CustomContextMenu )

        self.exceptionsList.customContextMenuRequested.connect( self.__showContextMenu )
        self.exceptionsList.itemSelectionChanged.connect( self.__onSelectionChanged )
        self.exceptionsList.setHeaderLabels( [ "Exception type" ] )

        self.__excTypeEdit = QLineEdit()
        self.__excTypeEdit.setFixedHeight( 26 )
        self.__excTypeEdit.textChanged.connect( self.__onNewFilterChanged )
        self.__excTypeEdit.returnPressed.connect( self.__onAddExceptionFilter )
        self.__addButton = QPushButton( "Add" )
        # self.__addButton.setFocusPolicy( Qt.NoFocus )
        self.__addButton.setEnabled( False )
        self.__addButton.clicked.connect( self.__onAddExceptionFilter )

        expandingSpacer2 = QWidget()
        expandingSpacer2.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding )

        self.__removeButton = QAction(
            PixmapCache().getIcon( 'delitem.png' ),
            "Remove selected exception type", self )
        self.__removeButton.triggered.connect( self.__onRemoveFromIgnore )
        self.__removeButton.setEnabled( False )

        fixedSpacer1 = QWidget()
        fixedSpacer1.setFixedWidth( 5 )

        self.__removeAllButton = QAction(
            PixmapCache().getIcon( 'ignexcptdelall.png' ),
            "Remove all the exception types", self )
        self.__removeAllButton.triggered.connect( self.__onRemoveAllFromIgnore )
        self.__removeAllButton.setEnabled( False )


        self.toolbar = QToolBar()
        self.toolbar.setOrientation( Qt.Horizontal )
        self.toolbar.setMovable( False )
        self.toolbar.setAllowedAreas( Qt.TopToolBarArea )
        self.toolbar.setIconSize( QSize( 16, 16 ) )
        self.toolbar.setFixedHeight( 28 )
        self.toolbar.setContentsMargins( 0, 0, 0, 0 )
        self.toolbar.addWidget( expandingSpacer2 )
        self.toolbar.addAction( self.__removeButton )
        self.toolbar.addWidget( fixedSpacer1 )
        self.toolbar.addAction( self.__removeAllButton )

        addLayout = QHBoxLayout()
        addLayout.setContentsMargins( 1, 1, 1, 1 )
        addLayout.setSpacing( 1 )
        addLayout.addWidget( self.__excTypeEdit )
        addLayout.addWidget( self.__addButton )

        verticalLayout.addWidget( self.headerFrame )
        verticalLayout.addWidget( self.toolbar )
        verticalLayout.addWidget( self.exceptionsList )
        verticalLayout.addLayout( addLayout )
        return

    def clear( self ):
        " Clears the content "
        self.exceptionsList.clear()
        self.__excTypeEdit.clear()
        self.__addButton.setEnabled( False )
        self.__ignored = []
        self.__currentItem = None
        self.__updateTitle()
        return

    def __onShowHide( self, startup = False ):
        " Triggered when show/hide button is clicked "
        if startup or self.exceptionsList.isVisible():
            self.exceptionsList.setVisible( False )
            self.__excTypeEdit.setVisible( False )
            self.__addButton.setVisible( False )
            self.__removeButton.setVisible( False )
            self.__removeAllButton.setVisible( False )
            self.__showHideButton.setIcon( PixmapCache().getIcon( 'more.png' ) )
            self.__showHideButton.setToolTip( "Show ignored exceptions list" )

            self.__minH = self.minimumHeight()
            self.__maxH = self.maximumHeight()

            self.setMinimumHeight( self.headerFrame.height() )
            self.setMaximumHeight( self.headerFrame.height() )

            Settings().showIgnoredExcViewer = False
        else:
            self.exceptionsList.setVisible( True )
            self.__excTypeEdit.setVisible( True )
            self.__addButton.setVisible( True )
            self.__removeButton.setVisible( True )
            self.__removeAllButton.setVisible( True )
            self.__showHideButton.setIcon( PixmapCache().getIcon( 'less.png' ) )
            self.__showHideButton.setToolTip( "Hide ignored exceptions list" )

            self.setMinimumHeight( self.__minH )
            self.setMaximumHeight( self.__maxH )

            Settings().showIgnoredExcViewer = True
        return

    def __onSelectionChanged( self ):
        " Triggered when the current item is changed "
        selected = list( self.exceptionsList.selectedItems() )
        if selected:
            self.__currentItem = selected[ 0 ]
            self.__removeButton.setEnabled( True )
        else:
            self.__currentItem = None
            self.__removeButton.setEnabled( False )
        return

    def __showContextMenu( self, coord ):
        " Shows the frames list context menu "
        contextItem = self.exceptionsList.itemAt( coord )
        if contextItem is not None:
            self.__currentItem = contextItem
            self.__excptMenu.popup( QCursor.pos() )
        return

    def __updateTitle( self ):
        " Updates the section title "
        count = self.exceptionsList.topLevelItemCount()
        if count == 0:
            self.__excptLabel.setText( "Ignored exception types" )
        else:
            self.__excptLabel.setText( "Ignored exception types (total: " +
                                       str( count ) + ")" )
        self.__removeAllButton.setEnabled( count != 0 )
        return

    def __onProjectChanged( self, what ):
        " Triggered when a project is changed "
        if what != CodimensionProject.CompleteProject:
            return

        self.clear()
        project = GlobalData().project
        if project.isLoaded():
            self.__ignored = list( project.ignoredExcpt )
        else:
            self.__ignored = list( Settings().ignoredExceptions )

        for exceptionType in self.__ignored:
            item = QTreeWidgetItem( self.exceptionsList )
            item.setText( 0, exceptionType )
        self.__updateTitle()
        return

    def __onNewFilterChanged( self, text ):
        " Triggered when the text is changed "
        text = str( text ).strip()
        if text == "":
            self.__addButton.setEnabled( False )
            return
        if " " in text:
            self.__addButton.setEnabled( False )
            return

        if text in self.__ignored:
            self.__addButton.setEnabled( False )
            return

        self.__addButton.setEnabled( True )
        return

    def __onAddExceptionFilter( self ):
        " Adds an item into the ignored exceptions list "
        text = self.__excTypeEdit.text().strip()
        self.addExceptionFilter( text )

    def addExceptionFilter( self, excType ):
        " Adds a new item into the ignored exceptions list "
        if excType == "":
            return
        if " " in excType:
            return
        if excType in self.__ignored:
            return

        item = QTreeWidgetItem( self.exceptionsList )
        item.setText( 0, excType )

        project = GlobalData().project
        if project.isLoaded():
            project.addExceptionFilter( excType )
        else:
            Settings().addExceptionFilter( excType )
        self.__ignored.append( excType )
        self.__updateTitle()
        return


    def __onRemoveFromIgnore( self ):
        " Removes an item from the ignored exception types list "
        if self.__currentItem is None:
            return

        text = self.__currentItem.text( 0 )

        # Find the item index and remove it
        index = 0
        while True:
            if self.exceptionsList.topLevelItem( index ).text( 0 ) == text:
                self.exceptionsList.takeTopLevelItem( index )
                break
            index += 1

        project = GlobalData().project
        if project.isLoaded():
            project.deleteExceptionFilter( text )
        else:
            Settings().deleteExceptionFilter( text )
        self.__ignored.remove( text )
        self.__updateTitle()
        return

    def __onRemoveAllFromIgnore( self ):
        " Triggered when all the ignored exceptions should be deleted "
        self.clear()

        project = GlobalData().project
        if project.isLoaded():
            project.setExceptionFilters( [] )
        else:
            Settings().setExceptionFilters( [] )
        return

    def isIgnored( self, exceptionType ):
        " Returns True if this exception type should be ignored "
        return exceptionType in self.__ignored
Example #36
0
class PymetricsViewer(QWidget):
    " Pymetrics tab widget "

    # Limits to colorize the McCabe score
    LittleRiskLimit = 10
    ModerateRiskLimit = 20
    HighRiskLimit = 50

    # Options of providing a report
    SingleFile = 0
    DirectoryFiles = 1
    ProjectFiles = 2
    SingleBuffer = 3

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        self.__reportUUID = ""
        self.__reportFileName = ""
        self.__reportOption = -1
        self.__reportShown = False
        self.__report = None

        # Prepare members for reuse
        self.__noneLabel = QLabel("\nNo results available")

        self.__noneLabel.setFrameShape(QFrame.StyledPanel)
        self.__noneLabel.setAlignment(Qt.AlignHCenter)
        self.__headerFont = self.__noneLabel.font()
        self.__headerFont.setPointSize(self.__headerFont.pointSize() + 4)
        self.__noneLabel.setFont(self.__headerFont)
        self.__noneLabel.setAutoFillBackground(True)
        noneLabelPalette = self.__noneLabel.palette()
        noneLabelPalette.setColor(QPalette.Background,
                                  GlobalData().skin.nolexerPaper)
        self.__noneLabel.setPalette(noneLabelPalette)

        self.__createLayout(parent)

        self.__updateButtonsStatus()
        return

    def __createLayout(self, parent):
        " Creates the toolbar and layout "

        # Buttons
        self.__mcCabeButton = QAction(PixmapCache().getIcon('tableview.png'),
                                      'Switch to McCabe only table view', self)
        self.__mcCabeButton.setCheckable(True)
        self.connect(self.__mcCabeButton, SIGNAL('toggled(bool)'),
                     self.__onMcCabe)

        self.printButton = QAction(PixmapCache().getIcon('printer.png'),
                                   'Print', self)
        #printButton.setShortcut( 'Ctrl+' )
        self.connect(self.printButton, SIGNAL('triggered()'), self.__onPrint)
        self.printButton.setVisible(False)

        self.printPreviewButton = QAction(
            PixmapCache().getIcon('printpreview.png'), 'Print preview', self)
        #printPreviewButton.setShortcut( 'Ctrl+' )
        self.connect(self.printPreviewButton, SIGNAL('triggered()'),
                     self.__onPrintPreview)
        self.printPreviewButton.setVisible(False)

        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.clearButton = QAction(PixmapCache().getIcon('trash.png'), 'Clear',
                                   self)
        self.connect(self.clearButton, SIGNAL('triggered()'), self.__clear)

        # The toolbar
        self.toolbar = QToolBar(self)
        self.toolbar.setOrientation(Qt.Vertical)
        self.toolbar.setMovable(False)
        self.toolbar.setAllowedAreas(Qt.RightToolBarArea)
        self.toolbar.setIconSize(QSize(16, 16))
        self.toolbar.setFixedWidth(28)
        self.toolbar.setContentsMargins(0, 0, 0, 0)

        self.toolbar.addAction(self.__mcCabeButton)
        self.toolbar.addAction(self.printPreviewButton)
        self.toolbar.addAction(self.printButton)
        self.toolbar.addWidget(spacer)
        self.toolbar.addAction(self.clearButton)

        self.__totalResultsTree = QTreeWidget()
        self.__totalResultsTree.setAlternatingRowColors(True)
        self.__totalResultsTree.setRootIsDecorated(True)
        self.__totalResultsTree.setItemsExpandable(True)
        self.__totalResultsTree.setUniformRowHeights(True)
        self.__totalResultsTree.setItemDelegate(NoOutlineHeightDelegate(4))
        headerLabels = ["Path / name", "Value", ""]
        self.__totalResultsTree.setHeaderLabels(headerLabels)
        self.connect(self.__totalResultsTree,
                     SIGNAL("itemActivated(QTreeWidgetItem *, int)"),
                     self.__allItemActivated)
        self.connect(self.__totalResultsTree,
                     SIGNAL("itemExpanded(QTreeWidgetItem *)"),
                     self.__onResultsExpanded)
        self.__totalResultsTree.setColumnHidden(2, True)
        self.__totalResultsTree.hide()

        self.__mcCabeTable = QTreeWidget()
        self.__mcCabeTable.setAlternatingRowColors(True)
        self.__mcCabeTable.setRootIsDecorated(False)
        self.__mcCabeTable.setItemsExpandable(False)
        self.__mcCabeTable.setSortingEnabled(True)
        self.__mcCabeTable.setItemDelegate(NoOutlineHeightDelegate(4))
        self.__mcCabeTable.setUniformRowHeights(True)
        headerLabels = ["", "File name", "Object", "McCabe Complexity"]
        self.__mcCabeTable.setHeaderLabels(headerLabels)
        self.connect(self.__mcCabeTable,
                     SIGNAL("itemActivated(QTreeWidgetItem *, int)"),
                     self.__mcCabeActivated)
        self.__mcCabeTable.hide()

        self.__hLayout = QHBoxLayout()
        self.__hLayout.setContentsMargins(0, 0, 0, 0)
        self.__hLayout.setSpacing(0)
        self.__hLayout.addWidget(self.toolbar)
        self.__hLayout.addWidget(self.__noneLabel)
        self.__hLayout.addWidget(self.__totalResultsTree)
        self.__hLayout.addWidget(self.__mcCabeTable)

        self.setLayout(self.__hLayout)
        return

    def getTotalResultsWidget(self):
        " Provides a reference to the total results widget "
        return self.__totalResultsTree

    def getMcCabeResultsWidget(self):
        " Provides a reference to the McCabe results widget "
        return self.__mcCabeTable

    def __updateButtonsStatus(self):
        " Updates the buttons status "
        self.__mcCabeButton.setEnabled(self.__reportShown)
        self.printButton.setEnabled(self.__reportShown)
        self.printPreviewButton.setEnabled(self.__reportShown)
        self.clearButton.setEnabled(self.__reportShown)
        return

    def __onResultsExpanded(self, item):
        " An item has been expanded, so the column width should be adjusted "
        self.__totalResultsTree.header().resizeSections(
            QHeaderView.ResizeToContents)
        return

    def __onPrint(self):
        " Triggered when the print button is pressed "
        pass

    def __onPrintPreview(self):
        " triggered when the print preview button is pressed "
        pass

    def __onMcCabe(self, state):
        " Triggered when the metrics view is switched "

        if not self.__reportShown:
            return

        if state:
            self.__totalResultsTree.hide()
            self.__mcCabeTable.show()
            self.__mcCabeButton.setIcon(PixmapCache().getIcon('treeview.png'))
            self.__mcCabeButton.setToolTip("Switch to complete "
                                           "results tree view")
        else:
            self.__mcCabeTable.hide()
            self.__totalResultsTree.show()
            self.__mcCabeButton.setIcon(PixmapCache().getIcon('tableview.png'))
            self.__mcCabeButton.setToolTip("Switch to McCabe only table view")
        return

    def setFocus(self):
        " Overridden setFocus "
        self.__hLayout.setFocus()
        return

    def __clear(self):
        " Clears the content of the vertical layout "
        if not self.__reportShown:
            return

        self.__totalResultsTree.clear()
        self.__totalResultsTree.hide()
        self.__mcCabeTable.clear()
        self.__mcCabeTable.hide()
        self.__noneLabel.show()

        self.__report = None
        self.__reportShown = False
        self.__updateButtonsStatus()
        #        self.resizeEvent()
        self.__mcCabeButton.setIcon(PixmapCache().getIcon('tableview.png'))
        self.__mcCabeButton.setToolTip("Switch to McCabe only table view")
        self.__mcCabeButton.setChecked(False)

        self.__updateTooltip()
        return

    def __updateTooltip(self):
        " Generates a signal with appropriate string message "
        if not self.__reportShown:
            tooltip = "No metrics available"
        elif self.__reportOption == self.DirectoryFiles:
            tooltip = "Metrics generated for directory: " + \
                      self.__reportFileName
        elif self.__reportOption == self.ProjectFiles:
            tooltip = "Metrics generated for the whole project"
        elif self.__reportOption == self.SingleFile:
            tooltip = "Metrics generated for file: " + self.__reportFileName
        elif self.__reportOption == self.SingleBuffer:
            tooltip = "Metrics generated for unsaved file: " + \
                      self.__reportFileName
        else:
            tooltip = ""
        self.emit(SIGNAL('updatePymetricsTooltip'), tooltip)
        return

    @staticmethod
    def __shouldShowFileName(table, column):
        " Checks if the file name is the same "

        size = table.topLevelItemCount()
        if size == 0:
            return False

        index = size - 1
        firstName = table.topLevelItem(index).text(column)
        index -= 1
        while index >= 0:
            if table.topLevelItem(index).text(column) != firstName:
                return True
            index -= 1
        return False

    def showReport(self, metrics, reportOption, fileName, uuid):
        " Shows the pymetrics results "
        self.__clear()
        self.__noneLabel.hide()

        self.__report = metrics
        self.__reportUUID = uuid
        self.__reportFileName = fileName
        self.__reportOption = reportOption

        if len(metrics.report) > 1:
            accumulatedBasic = self.__accumulateBasicMetrics()
            accItem = QTreeWidgetItem(["Cumulative basic metrics"])
            self.__totalResultsTree.addTopLevelItem(accItem)
            for key in accumulatedBasic:
                bmItem = [
                    BasicMetrics.metricsOfInterest[key],
                    splitThousands(str(accumulatedBasic[key]))
                ]
                basicMetric = QTreeWidgetItem(bmItem)
                accItem.addChild(basicMetric)

        # Add the complete information
        for fileName in metrics.report:
            if reportOption == self.SingleBuffer:
                fileItem = QTreeWidgetItem(["Editor buffer"])
            else:
                fileItem = QTreeWidgetItem([fileName])
                info = GlobalData().briefModinfoCache.get(fileName)
                if info.docstring is not None:
                    fileItem.setToolTip(0, info.docstring.text)
                else:
                    fileItem.setToolTip(0, "")
            self.__totalResultsTree.addTopLevelItem(fileItem)

            # Messages part
            messages = metrics.report[fileName].messages
            if len(messages) > 0:
                messagesItem = QTreeWidgetItem(["Messages"])
                fileItem.addChild(messagesItem)
                for message in messages:
                    mItem = [message, "", "E"]
                    messagesItem.addChild(QTreeWidgetItem(mItem))

            # Basic metrics part
            basicItem = QTreeWidgetItem(["Basic metrics"])
            fileItem.addChild(basicItem)
            basic = metrics.report[fileName].basicMetrics
            for key in basic.metrics:
                bmItem = [
                    BasicMetrics.metricsOfInterest[key],
                    str(basic.metrics[key])
                ]
                basicMetric = QTreeWidgetItem(bmItem)
                basicItem.addChild(basicMetric)

            # McCabe part
            mccabeItem = QTreeWidgetItem(["McCabe metrics"])
            fileItem.addChild(mccabeItem)
            mccabe = metrics.report[fileName].mcCabeMetrics.metrics
            for objName in mccabe:
                objItem = [objName, str(mccabe[objName]), "M"]
                mccabeMetric = QTreeWidgetItem(objItem)
                mccabeItem.addChild(mccabeMetric)

            # COCOMO 2 part
            cocomo = [
                "COCOMO 2",
                str(metrics.report[fileName].cocomo2Metrics.value)
            ]
            cocomoItem = QTreeWidgetItem(cocomo)
            fileItem.addChild(cocomoItem)

        # Resizing the table
        self.__totalResultsTree.header().resizeSections(
            QHeaderView.ResizeToContents)

        # Add McCabe complexity information
        for fileName in metrics.report:
            mccabe = metrics.report[fileName].mcCabeMetrics.metrics
            for objName in mccabe:
                values = ["", fileName, objName, str(mccabe[objName])]
                self.__mcCabeTable.addTopLevelItem(McCabeTableItem(values))

        if not self.__shouldShowFileName(self.__mcCabeTable, 1):
            self.__mcCabeTable.setColumnHidden(1, True)

        # Resizing and sorting the table
        self.__mcCabeTable.header().setSortIndicator(3, Qt.DescendingOrder)
        self.__mcCabeTable.sortItems(
            3,
            self.__mcCabeTable.header().sortIndicatorOrder())
        self.__mcCabeTable.header().resizeSections(
            QHeaderView.ResizeToContents)

        # Show the complete information
        self.__mcCabeTable.hide()
        self.__totalResultsTree.show()

        self.__reportShown = True
        self.__updateButtonsStatus()
        self.__updateTooltip()

        # It helps, but why do I have flickering?
        QApplication.processEvents()
        return

    def __accumulateBasicMetrics(self):
        " Accumulates basic metrics for all the processed files "
        basic = {}
        for fileName in self.__report.report:
            singleBasic = self.__report.report[fileName].basicMetrics.metrics
            for key in singleBasic:
                if not key.startswith('num'):
                    continue
                if key in basic:
                    basic[key] += int(singleBasic[key])
                else:
                    basic[key] = int(singleBasic[key])
        return basic

    def __mcCabeActivated(self, item, column):
        " Handles the double click (or Enter) on the mccabe table item "

        objName = str(item.text(2))
        if self.__reportOption == self.SingleBuffer:
            if os.path.isabs(self.__reportFileName):
                fileName = self.__reportFileName
            else:
                fileName = ""
        else:
            fileName = str(item.text(1))
        self.__onMcCabeObject(objName, fileName)
        return

    def __allItemActivated(self, item, column):
        " Handles the double click (or Enter) in the total results tree "

        # We process only the error messages and McCabe items
        hiddenColumnText = str(item.text(2))
        if not hiddenColumnText in ["M", "E"]:
            return

        fileName = self.__getTreeItemFileName(item)
        lineNumber = 0
        if hiddenColumnText == "M":
            # This is McCabe item
            objName = str(item.text(0))
            self.__onMcCabeObject(objName, fileName)
            return
        elif hiddenColumnText == "E":
            # This is an error message
            message = str(item.text(0))
            pos = message.find("at line")
            if pos == -1:
                logging.error("Unknown format of the message. "
                              "Please inform the developers.")
                return
            parts = message[pos:].split()
            try:
                lineNumber = int(parts[2].replace(',', ''))
            except:
                logging.error("Unknown format of the message. "
                              "Please inform the developers.")
                return

            if fileName == "":
                # This is an unsaved buffer, try to find the editor by UUID
                mainWindow = GlobalData().mainWindow
                widget = mainWindow.getWidgetByUUID(self.__reportUUID)
                if widget is None:
                    logging.error("The unsaved buffer has been closed")
                    return
                # The widget was found, so jump to the required
                editor = widget.getEditor()
                editor.gotoLine(lineNumber)
                editor.setFocus()
                return

        GlobalData().mainWindow.openFile(fileName, lineNumber)
        return

    def __getTreeItemFileName(self, item):
        " Identifies the tree view item file name "
        if self.__reportOption == self.SingleBuffer:
            if os.path.isabs(self.__reportFileName):
                return self.__reportFileName
            return ""

        # The file name is always two levels up
        fileItem = item.parent().parent()
        return str(fileItem.text(0))

    def __onMcCabeObject(self, objName, fileName):
        " Called when the user activated McCabe item "

        info = None

        mainWindow = GlobalData().mainWindow
        widget = mainWindow.getWidgetByUUID(self.__reportUUID)
        if widget is None:
            if fileName == "":
                logging.error("The unsaved buffer has been closed")
                return
            # No widget, but we know the file name
            info = getBriefModuleInfoFromFile(fileName)
        else:
            # The widget was found
            editor = widget.getEditor()
            # The editor content has been modified, so re-parse the buffer
            info = getBriefModuleInfoFromMemory(editor.text())

        parts = objName.split('.')
        currentIndex = 0
        functionsContainer = info.functions
        classesContainer = info.classes
        line = -1

        if objName == "__main__" and len(parts) == 1:
            # Special case - global file scope
            line = 1
            currentIndex = 1

        while currentIndex < len(parts):
            found = False
            for func in functionsContainer:
                if func.name == parts[currentIndex]:
                    if currentIndex == len(parts) - 1:
                        # Found, jump to the line
                        line = func.line
                        break
                    functionsContainer = func.functions
                    classesContainer = func.classes
                    found = True
                    break
            if line != -1:
                break
            if found:
                currentIndex += 1
                continue
            for klass in classesContainer:
                if klass.name == parts[currentIndex]:
                    if currentIndex == len(parts) - 1:
                        # Found, jump to the line
                        line = klass.line
                        break
                    functionsContainer = klass.functions
                    classesContainer = klass.classes
                    found = True
            if line != -1:
                break
            if found:
                currentIndex += 1
                continue

            # Not found
            logging.error("Cannot find the " + objName)
            return

        # Here we have the line number
        if widget is None:
            GlobalData().mainWindow.openFile(fileName, line)
        else:
            editor = widget.getEditor()
            editor.gotoLine(line)
            editor.setFocus()
        return

    def onFileUpdated(self, fileName, uuid):
        " Called when a buffer is saved or saved as "

        if not self.__reportShown:
            return
        if self.__reportUUID != uuid:
            return

        # Currently shown report is for the saved buffer
        # File name is expected being absolute
        self.__reportFileName = fileName
        self.emit(SIGNAL('updatePymetricsTooltip'),
                  "Metrics generated for buffer saved as " + fileName)
        return
Example #37
0
class FindInFilesViewer(QWidget):
    " Find in files viewer tab widget "

    lastEntered = None

    def __init__(self, parent=None):
        QWidget.__init__(self, parent)

        global searchTooltip
        searchTooltip = Tooltip()

        self.__reportRegexp = None
        self.__reportResults = []
        self.__reportShown = False
        self.__bufferChangeconnected = False

        # Prepare members for reuse
        self.__noneLabel = QLabel("\nNo results available")

        self.__noneLabel.setFrameShape(QFrame.StyledPanel)
        self.__noneLabel.setAlignment(Qt.AlignHCenter)
        self.__headerFont = self.__noneLabel.font()
        self.__headerFont.setPointSize(self.__headerFont.pointSize() + 4)
        self.__noneLabel.setFont(self.__headerFont)
        self.__noneLabel.setAutoFillBackground(True)
        noneLabelPalette = self.__noneLabel.palette()
        noneLabelPalette.setColor(QPalette.Background,
                                  GlobalData().skin.nolexerPaper)
        self.__noneLabel.setPalette(noneLabelPalette)

        # Keep pylint happy
        self.printButton = None
        self.clearButton = None
        self.printPreviewButton = None

        self.__createLayout(parent)

        self.__updateButtonsStatus()

        GlobalData().project.projectChanged.connect(self.__onProjectChanged)
        return

    def __createLayout(self, parent):
        " Creates the toolbar and layout "

        # Buttons
        self.printButton = QAction(PixmapCache().getIcon('printer.png'),
                                   'Print', self)
        #printButton.setShortcut( 'Ctrl+' )
        self.printButton.triggered.connect(self.__onPrint)
        self.printButton.setVisible(False)

        self.printPreviewButton = QAction(
            PixmapCache().getIcon('printpreview.png'), 'Print preview', self)
        #printPreviewButton.setShortcut( 'Ctrl+' )
        self.printPreviewButton.triggered.connect(self.__onPrintPreview)
        self.printPreviewButton.setVisible(False)

        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.clearButton = QAction(PixmapCache().getIcon('trash.png'), 'Clear',
                                   self)
        self.clearButton.triggered.connect(self.__clear)

        # The toolbar
        self.toolbar = QToolBar(self)
        self.toolbar.setOrientation(Qt.Vertical)
        self.toolbar.setMovable(False)
        self.toolbar.setAllowedAreas(Qt.RightToolBarArea)
        self.toolbar.setIconSize(QSize(16, 16))
        self.toolbar.setFixedWidth(28)
        self.toolbar.setContentsMargins(0, 0, 0, 0)

        self.toolbar.addAction(self.printPreviewButton)
        self.toolbar.addAction(self.printButton)
        self.toolbar.addWidget(spacer)
        self.toolbar.addAction(self.clearButton)

        self.__resultsTree = FindResultsTreeWidget()
        self.__resultsTree.setAlternatingRowColors(True)
        self.__resultsTree.setRootIsDecorated(True)
        self.__resultsTree.setItemsExpandable(True)
        self.__resultsTree.setUniformRowHeights(True)
        self.__resultsTree.setItemDelegate(NoOutlineHeightDelegate(4))
        headerLabels = ["File name / line", "Text"]
        self.__resultsTree.setHeaderLabels(headerLabels)
        self.__resultsTree.itemActivated.connect(self.__resultActivated)
        self.__resultsTree.itemClicked.connect(self.__resultClicked)
        self.__resultsTree.setMouseTracking(True)
        self.__resultsTree.itemEntered.connect(self.__itemEntered)
        self.__resultsTree.hide()

        self.__hLayout = QHBoxLayout()
        self.__hLayout.setContentsMargins(0, 0, 0, 0)
        self.__hLayout.setSpacing(0)
        self.__hLayout.addWidget(self.toolbar)
        self.__hLayout.addWidget(self.__noneLabel)
        self.__hLayout.addWidget(self.__resultsTree)

        self.setLayout(self.__hLayout)
        return

    def getResultsTree(self):
        " Provides a reference to the results tree "
        return self.__resultsTree

    def __updateButtonsStatus(self):
        " Updates the buttons status "
        self.printButton.setEnabled(self.__reportShown)
        self.printPreviewButton.setEnabled(self.__reportShown)
        self.clearButton.setEnabled(self.__reportShown)
        return

    def __onPrint(self):
        " Triggered when the print button is pressed "
        pass

    def __onPrintPreview(self):
        " triggered when the print preview button is pressed "
        pass

    def setFocus(self):
        " Overridden setFocus "
        self.__hLayout.setFocus()
        return

    def __onProjectChanged(self, what):
        " Triggered when a project is changed "
        if what == CodimensionProject.CompleteProject:
            self.__clear()
        return

    def __clear(self):
        " Clears the content of the vertical layout "
        if not self.__reportShown:
            return

        # Disconnect the buffer change signal if it is connected
        if self.__bufferChangeconnected:
            self.__bufferChangeconnected = False
            mainWindow = GlobalData().mainWindow
            editorsManager = mainWindow.editorsManagerWidget.editorsManager
            editorsManager.bufferModified.disconnect(
                self.__resultsTree.onBufferModified)

        self.__resultsTree.resetCache()
        self.__resultsTree.clear()
        self.__resultsTree.hide()
        self.__noneLabel.show()

        self.__reportRegexp = None
        self.__reportResults = []
        self.__reportShown = False
        self.__updateButtonsStatus()
        return

    def showReport(self, regexp, results):
        " Shows the find in files results "
        self.__clear()
        self.__noneLabel.hide()

        self.__reportRegexp = regexp
        self.__reportResults = results

        # Add the complete information
        totalMatched = 0
        for item in results:
            matched = len(item.matches)
            totalMatched += matched
            if matched == 1:
                matchText = " (1 match)"
            else:
                matchText = " (" + str(matched) + " matches)"
            columns = [item.fileName, matchText]
            fileItem = MatchTableFileItem(columns, item.bufferUUID)
            fileItem.setIcon(0, getFileIcon(detectFileType(item.fileName)))
            if item.tooltip != "":
                fileItem.setToolTip(0, item.tooltip)
            self.__resultsTree.addTopLevelItem(fileItem)

            # Matches
            for match in item.matches:
                columns = [str(match.line), match.text]
                matchItem = MatchTableItem(columns, match.tooltip)
                fileItem.addChild(matchItem)
            fileItem.setExpanded(True)

        # Update the header with the total number of matches
        headerLabels = [
            "File name / line (total files: " + str(len(results)) + ")",
            "Text (total matches: " + str(totalMatched) + ")"
        ]
        self.__resultsTree.setHeaderLabels(headerLabels)

        # Resizing the table
        self.__resultsTree.header().resizeSections(
            QHeaderView.ResizeToContents)

        # Show the complete information
        self.__resultsTree.show()
        self.__resultsTree.buildCache()

        self.__reportShown = True
        self.__updateButtonsStatus()

        # Connect the buffer change signal if not connected yet
        if not self.__bufferChangeconnected:
            self.__bufferChangeconnected = True
            mainWindow = GlobalData().mainWindow
            editorsManager = mainWindow.editorsManagerWidget.editorsManager
            editorsManager.bufferModified.connect(
                self.__resultsTree.onBufferModified)
        return

    def __resultClicked(self, item, column):
        " Handles the single click "
        hideSearchTooltip()
        return

    def __resultActivated(self, item, column):
        " Handles the double click (or Enter) on a match "
        if type(item) == MatchTableItem:
            fileName = str(item.parent().data(0, Qt.DisplayRole).toString())
            lineNumber = int(item.data(0, Qt.DisplayRole).toString())
            GlobalData().mainWindow.openFile(fileName, lineNumber)
            hideSearchTooltip()
            return

    def __itemEntered(self, item, column):
        " Triggered when the mouse cursor entered a row "

        if type(item) != MatchTableItem:
            self.lastEntered = item
            hideSearchTooltip()
            return

        if column != 1:
            # Show the tooltip only for the column with results
            self.lastEntered = None
            hideSearchTooltip()
            return

        # Memorize the row height for proper tooltip displaying later
        global cellHeight
        cellHeight = self.__resultsTree.visualItemRect(item).height()

        if self.lastEntered != item or not inside:
            item.itemEntered()
            self.lastEntered = item
        return
Example #38
0
class DockTitleBar(QToolBar):
    def __init__(self, parent):
        QToolBar.__init__(self, parent)
        assert parent
        self.dock = parent

        # a fake spacer widget
        w = QWidget(self)
        l = QHBoxLayout(w)
        l.setMargin(0)
        l.setSpacing(0)
        l.addStretch()

        frame = QFrame()
        layout = QBoxLayout(QBoxLayout.LeftToRight, frame)
        layout.setContentsMargins(4, 4, 0, 0)
        layout.setSpacing(2)
        self.aDockFrame = self.addWidget(frame)

        self.__icon = QLabel()

        layout.addWidget(self.__icon)
        layout.addWidget(QLabel(self.dock.windowTitle()))

        self.dock.windowIconChanged.connect(self.__setWindowIcon)

        # fake spacer item
        spacer = QWidgetAction(self)
        spacer.setDefaultWidget(w)

        self.setMovable(False)
        self.setFloatable(False)
        self.setIconSize(QSize(12, 12))

        self.aFloat = QAction(self)
        self.aClose = QAction(self)

        QToolBar.addAction(self, spacer)
        self.separator = QToolBar.addSeparator(self)
        QToolBar.addAction(self, self.aFloat)
        QToolBar.addAction(self, self.aClose)

        self.updateStandardIcons()

        self.dockWidgetFeaturesChanged(self.dock.features())

        self.dock.featuresChanged.connect(self.dockWidgetFeaturesChanged)
        self.aFloat.triggered.connect(self._floatTriggered)
        self.aClose.triggered.connect(self.dock.close)

    def addAction(self, action):
        self.insertAction(self.separator, action)
        return action

    def __setWindowIcon(self, icon):
        if not icon.isNull():
            self.__icon.setPixmap(self.dock.windowIcon().pixmap(16))

    def __setWindowTitle(self, title):
        self.__title.setText(title)

    def paintEvent(self, _):
        painter = QStylePainter(self)
        options = QStyleOptionToolBar()

        # init style options
        options.initFrom(self.dock)
        options.rect = self.rect()
        textRect = self.rect().adjusted(3, 3, 0, 0)
        msh = self.minimumSizeHint()

        # need to rotate if vertical state
        if self.dock.features() & QDockWidget.DockWidgetVerticalTitleBar:
            painter.rotate(-90)
            painter.translate(QPointF(-self.rect().height(), 0))
            self.transposeSize(options.rect)
            self.transposeSize(textRect)
            msh.transpose()

        # draw toolbar
        painter.drawControl(QStyle.CE_ToolBar, options)

        # restore rotation
        if self.dock.features() & QDockWidget.DockWidgetVerticalTitleBar:
            painter.rotate(90)

    @staticmethod
    def transposeSize(rect):
        size = rect.size()
        size.transpose()
        rect.setSize(size)

    def updateStandardIcons(self):
        size = QSize(16, 16)
        rect = QRect(QPoint(), self.iconSize())

        transform = QTransform()
        transform.rotate(90)

        pixmap = self.style().standardIcon(
            QStyle.SP_TitleBarNormalButton, None,
            self.widgetForAction(self.aFloat)).pixmap(size)
        rect.moveCenter(pixmap.rect().center())
        pixmap = pixmap.copy(rect)
        self.aFloat.setIcon(QIcon(pixmap))

        pixmap = self.style().standardIcon(QStyle.SP_TitleBarCloseButton, None,
                                           self.widgetForAction(
                                               self.aClose)).pixmap(size)
        rect.moveCenter(pixmap.rect().center())
        pixmap = pixmap.copy(rect)
        self.aClose.setIcon(QIcon(pixmap))

    def event(self, event):
        if event.type() == QEvent.StyleChange:
            self.updateStandardIcons()
        return QToolBar.event(self, event)

    def minimumSizeHint(self):
        return QToolBar.sizeHint(self)

    def sizeHint(self):
        size = QToolBar.sizeHint(self)
        fm = QFontMetrics(self.font())

        if self.dock.features() & QDockWidget.DockWidgetVerticalTitleBar:
            size.setHeight(size.height() + fm.width(self.dock.windowTitle()))
        else:
            size.setWidth(size.width() + fm.width(self.dock.windowTitle()))

        return size

    def _floatTriggered(self):
        self.dock.setFloating(not self.dock.isFloating())

    def dockWidgetFeaturesChanged(self, features):
        self.aFloat.setVisible(features & QDockWidget.DockWidgetFloatable)
        self.aClose.setVisible(features & QDockWidget.DockWidgetClosable)

        # update toolbar orientation
        if features & QDockWidget.DockWidgetVerticalTitleBar:
            if self.orientation() & Qt.Vertical:
                return
            self.setOrientation(Qt.Vertical)
        else:
            if self.orientation() & Qt.Horizontal:
                return
            self.setOrientation(Qt.Horizontal)

        # reorder the actions
        items = self.actions()
        items.reverse()
        self.clear()
        self.addActions(items)
Example #39
0
class IOConsoleTabWidget(QWidget, MainWindowTabWidgetBase):
    " IO console tab widget "

    textEditorZoom = pyqtSignal(int)
    settingUpdated = pyqtSignal()

    def __init__(self, parent):

        MainWindowTabWidgetBase.__init__(self)
        QWidget.__init__(self, parent)

        self.__viewer = RedirectedIOConsole(self)
        self.connect(self.__viewer, SIGNAL('UserInput'), self.__onUserInput)

        self.__createLayout()
        return

    def __onUserInput(self, userInput):
        " Triggered when the user finished input in the redirected IO console "
        self.emit(SIGNAL('UserInput'), userInput)
        self.__clearButton.setEnabled(True)
        return

    def __createLayout(self):
        " Creates the toolbar and layout "

        # Buttons
        self.__printButton = QAction(PixmapCache().getIcon('printer.png'),
                                     'Print', self)
        self.__printButton.triggered.connect(self.__onPrint)
        self.__printButton.setEnabled(False)
        self.__printButton.setVisible(False)

        self.__printPreviewButton = QAction(
            PixmapCache().getIcon('printpreview.png'), 'Print preview', self)
        self.__printPreviewButton.triggered.connect(self.__onPrintPreview)
        self.__printPreviewButton.setEnabled(False)
        self.__printPreviewButton.setVisible(False)

        # self.__sendUpButton = QAction( PixmapCache().getIcon('sendioup.png'),
        #                                'Send to Main Editing Area', self )
        # self.__sendUpButton.triggered.connect( self.__sendUp )

        self.__filterMenu = QMenu(self)
        self.__filterMenu.aboutToShow.connect(self.__filterAboutToShow)
        self.__filterGroup = QActionGroup(self)
        self.__filterShowAllAct = self.__filterMenu.addAction("Show all")
        self.__filterShowAllAct.setCheckable(True)
        self.__filterShowAllAct.setActionGroup(self.__filterGroup)
        self.__filterShowAllAct.triggered.connect(self.__onFilterShowAll)
        self.__filterShowStdoutAct = self.__filterMenu.addAction(
            "Show stdin and stdout")
        self.__filterShowStdoutAct.setCheckable(True)
        self.__filterShowStdoutAct.setActionGroup(self.__filterGroup)
        self.__filterShowStdoutAct.triggered.connect(self.__onFilterShowStdout)
        self.__filterShowStderrAct = self.__filterMenu.addAction(
            "Show stdin and stderr")
        self.__filterShowStderrAct.setCheckable(True)
        self.__filterShowStderrAct.setActionGroup(self.__filterGroup)
        self.__filterShowStderrAct.triggered.connect(self.__onFilterShowStderr)
        self.__filterButton = QToolButton(self)
        self.__filterButton.setIcon(PixmapCache().getIcon('iofilter.png'))
        self.__filterButton.setToolTip('Filtering settings')
        self.__filterButton.setPopupMode(QToolButton.InstantPopup)
        self.__filterButton.setMenu(self.__filterMenu)
        self.__filterButton.setFocusPolicy(Qt.NoFocus)

        self.__settingsMenu = QMenu(self)
        self.__settingsMenu.aboutToShow.connect(self.__settingsAboutToShow)
        self.__wrapLongLinesAct = self.__settingsMenu.addAction(
            "Wrap long lines")
        self.__wrapLongLinesAct.setCheckable(True)
        self.__wrapLongLinesAct.triggered.connect(self.__onWrapLongLines)
        self.__showEOLAct = self.__settingsMenu.addAction("Show EOL")
        self.__showEOLAct.setCheckable(True)
        self.__showEOLAct.triggered.connect(self.__onShowEOL)
        self.__showWhitespacesAct = self.__settingsMenu.addAction(
            "Show whitespaces")
        self.__showWhitespacesAct.setCheckable(True)
        self.__showWhitespacesAct.triggered.connect(self.__onShowWhitespaces)
        self.__autoscrollAct = self.__settingsMenu.addAction("Autoscroll")
        self.__autoscrollAct.setCheckable(True)
        self.__autoscrollAct.triggered.connect(self.__onAutoscroll)
        self.__showMarginAct = self.__settingsMenu.addAction(
            "Show timestamp margin")
        self.__showMarginAct.setCheckable(True)
        self.__showMarginAct.triggered.connect(self.__onShowMargin)

        self.__settingsButton = QToolButton(self)
        self.__settingsButton.setIcon(PixmapCache().getIcon('iosettings.png'))
        self.__settingsButton.setToolTip('View settings')
        self.__settingsButton.setPopupMode(QToolButton.InstantPopup)
        self.__settingsButton.setMenu(self.__settingsMenu)
        self.__settingsButton.setFocusPolicy(Qt.NoFocus)

        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        self.__clearButton = QAction(PixmapCache().getIcon('trash.png'),
                                     'Clear', self)
        self.__clearButton.triggered.connect(self.clear)

        # The toolbar
        toolbar = QToolBar(self)
        toolbar.setOrientation(Qt.Vertical)
        toolbar.setMovable(False)
        toolbar.setAllowedAreas(Qt.RightToolBarArea)
        toolbar.setIconSize(QSize(16, 16))
        toolbar.setFixedWidth(28)
        toolbar.setContentsMargins(0, 0, 0, 0)

        # toolbar.addAction( self.__sendUpButton )
        toolbar.addAction(self.__printPreviewButton)
        toolbar.addAction(self.__printButton)
        toolbar.addWidget(self.__filterButton)
        toolbar.addWidget(self.__settingsButton)
        toolbar.addWidget(spacer)
        toolbar.addAction(self.__clearButton)

        hLayout = QHBoxLayout()
        hLayout.setContentsMargins(0, 0, 0, 0)
        hLayout.setSpacing(0)
        hLayout.addWidget(toolbar)
        hLayout.addWidget(self.__viewer)

        self.setLayout(hLayout)
        return

    def onZoomReset(self):
        " Triggered when the zoom reset button is pressed "
        if self.__viewer.zoom != 0:
            self.textEditorZoom.emit(0)
        return True

    def onZoomIn(self):
        " Triggered when the zoom in button is pressed "
        if self.__viewer.zoom < 20:
            self.textEditorZoom.emit(self.__viewer.zoom + 1)
        return True

    def onZoomOut(self):
        " Triggered when the zoom out button is pressed "
        if self.__viewer.zoom > -10:
            self.textEditorZoom.emit(self.__viewer.zoom - 1)
        return True

    def __onPrint(self):
        " Triggered when the print button is pressed "
        pass

    def __onPrintPreview(self):
        " triggered when the print preview button is pressed "
        pass

    def setFocus(self):
        " Overridden setFocus "
        self.__viewer.setFocus()
        return

    def onOpenImport(self):
        " Triggered when Ctrl+I is received "
        return True

    def __sendUp(self):
        " Triggered when requested to move the console up "
        return

    def __filterAboutToShow(self):
        " Triggered when filter menu is about to show "
        showAll = Settings().ioconsoleshowstdin and \
                  Settings().ioconsoleshowstdout and \
                  Settings().ioconsoleshowstderr
        onlyStdout = Settings().ioconsoleshowstdin and \
                     Settings().ioconsoleshowstdout and \
                     not Settings().ioconsoleshowstderr
        onlyStderr = Settings().ioconsoleshowstdin and \
                     not Settings().ioconsoleshowstdout and \
                     Settings().ioconsoleshowstderr
        self.__filterShowAllAct.setChecked(showAll)
        self.__filterShowStdoutAct.setChecked(onlyStdout)
        self.__filterShowStderrAct.setChecked(onlyStderr)
        return

    def __onFilterShowAll(self):
        " Triggered when filter show all is clicked "
        if Settings().ioconsoleshowstdin == True and \
           Settings().ioconsoleshowstdout == True and \
           Settings().ioconsoleshowstderr == True:
            return

        Settings().ioconsoleshowstdin = True
        Settings().ioconsoleshowstdout = True
        Settings().ioconsoleshowstderr = True
        self.__viewer.renderContent()
        return

    def __onFilterShowStdout(self):
        " Triggered when filter show stdout only is clicked "
        if Settings().ioconsoleshowstdin == True and \
           Settings().ioconsoleshowstdout == True and \
           Settings().ioconsoleshowstderr == False:
            return

        Settings().ioconsoleshowstdin = True
        Settings().ioconsoleshowstdout = True
        Settings().ioconsoleshowstderr = False
        self.__viewer.renderContent()
        return

    def __onFilterShowStderr(self):
        " Triggered when filter show stderr only is clicked "
        if Settings().ioconsoleshowstdin == True and \
           Settings().ioconsoleshowstdout == False and \
           Settings().ioconsoleshowstderr == True:
            return

        Settings().ioconsoleshowstdin = True
        Settings().ioconsoleshowstdout = False
        Settings().ioconsoleshowstderr = True
        self.__viewer.renderContent()
        return

    def __settingsAboutToShow(self):
        " Settings menu is about to show "
        self.__wrapLongLinesAct.setChecked(Settings().ioconsolelinewrap)
        self.__showEOLAct.setChecked(Settings().ioconsoleshoweol)
        self.__showWhitespacesAct.setChecked(Settings().ioconsoleshowspaces)
        self.__autoscrollAct.setChecked(Settings().ioconsoleautoscroll)
        self.__showMarginAct.setChecked(Settings().ioconsoleshowmargin)
        return

    def __onWrapLongLines(self):
        " Triggered when long lines setting is changed "
        Settings().ioconsolelinewrap = not Settings().ioconsolelinewrap
        self.settingUpdated.emit()
        return

    def __onShowEOL(self):
        " Triggered when show EOL is changed "
        Settings().ioconsoleshoweol = not Settings().ioconsoleshoweol
        self.settingUpdated.emit()
        return

    def __onShowWhitespaces(self):
        " Triggered when show whitespaces is changed "
        Settings().ioconsoleshowspaces = not Settings().ioconsoleshowspaces
        self.settingUpdated.emit()
        return

    def __onAutoscroll(self):
        " Triggered when autoscroll is changed "
        Settings().ioconsoleautoscroll = not Settings().ioconsoleautoscroll
        return

    def __onShowMargin(self):
        " Triggered when show margin is changed "
        Settings().ioconsoleshowmargin = not Settings().ioconsoleshowmargin
        self.settingUpdated.emit()
        return

    def clear(self):
        " Triggered when requested to clear the console "
        self.__viewer.clearAll()
        return

    def consoleSettingsUpdated(self):
        " Triggered when one of the consoles updated a common setting "
        if Settings().ioconsolelinewrap:
            self.__viewer.setWrapMode(QsciScintilla.WrapWord)
        else:
            self.__viewer.setWrapMode(QsciScintilla.WrapNone)
        self.__viewer.setEolVisibility(Settings().ioconsoleshoweol)
        if Settings().ioconsoleshowspaces:
            self.__viewer.setWhitespaceVisibility(QsciScintilla.WsVisible)
        else:
            self.__viewer.setWhitespaceVisibility(QsciScintilla.WsInvisible)
        self.__viewer.setTimestampMarginWidth()
        return

    def resizeEvent(self, event):
        QWidget.resizeEvent(self, event)
        return

    def onPylint(self):
        return True

    def onPymetrics(self):
        return True

    def onRunScript(self, action=False):
        return True

    def onRunScriptSettings(self):
        return True

    def onProfileScript(self, action=False):
        return True

    def onProfileScriptSettings(self):
        return True

    def onImportDgm(self, action=None):
        return True

    def onImportDgmTuned(self):
        return True

    def shouldAcceptFocus(self):
        return True

    def onNavigationBar(self):
        pass

    def writeFile(self, fileName):
        " Writes the text to a file "
        return self.__viewer.writeFile(fileName)

    def updateModificationTime(self, fileName):
        return

    def appendIDEMessage(self, text):
        " Appends an IDE message "
        self.__viewer.appendIDEMessage(text)
        return

    def appendStdoutMessage(self, text):
        " Appends an stdout message "
        self.__viewer.appendStdoutMessage(text)
        return

    def appendStderrMessage(self, text):
        " Appends an stderr message "
        self.__viewer.appendStderrMessage(text)
        return

    def hiddenMessage(self, msg):
        " Returns True if the message should not be shown "
        if msg.msgType == IOConsoleMsg.STDERR_MESSAGE and \
           not Settings().ioconsoleshowstderr:
            return True
        if msg.msgType == IOConsoleMsg.STDOUT_MESSAGE and \
           not Settings().ioconsoleshowstdout:
            return True
        return False

    def zoomTo(self, zoomValue):
        " Sets the new zoom value "
        self.__viewer.zoomTo(zoomValue)
        self.__viewer.setTimestampMarginWidth()
        return

    def rawInput(self, prompt, echo):
        " Triggered when raw input is requested "
        echo = int(echo)
        if echo == 0:
            self.__viewer.inputEcho = False
        else:
            self.__viewer.inputEcho = True

        if prompt:
            self.appendStdoutMessage(prompt)
        self.__clearButton.setEnabled(False)
        self.__viewer.switchMode(self.__viewer.MODE_INPUT)
        return

    def sessionStopped(self):
        " Triggered when redirecting session is stopped "
        self.__viewer.switchMode(self.__viewer.MODE_OUTPUT)
        self.__clearButton.setEnabled(True)
        return

    # Mandatory interface part is below

    def getEditor(self):
        " Provides the editor widget "
        return self.__viewer

    def isModified(self):
        " Tells if the file is modified "
        return False

    def getRWMode(self):
        " Tells if the file is read only "
        return "IO"

    def getFileType(self):
        " Provides the file type "
        return TexFileType

    def setFileType(self, typeToSet):
        """ Sets the file type explicitly.
            It needs e.g. for .cgi files which can change its type """
        raise Exception("Setting a file type is not supported by the "
                        "IO console widget")

    def getType(self):
        " Tells the widget type "
        return MainWindowTabWidgetBase.IOConsole

    def getLanguage(self):
        " Tells the content language "
        return "IO console"

    def getFileName(self):
        " Tells what file name of the widget content "
        return "n/a"

    def setFileName(self, name):
        " Sets the file name "
        raise Exception("Setting a file name for IO console "
                        "is not applicable")

    def getEol(self):
        " Tells the EOL style "
        return self.__viewer.getEolIndicator()

    def getLine(self):
        " Tells the cursor line "
        line, _ = self.__viewer.getCursorPosition()
        return int(line)

    def getPos(self):
        " Tells the cursor column "
        _, pos = self.__viewer.getCursorPosition()
        return int(pos)

    def getEncoding(self):
        " Tells the content encoding "
        return self.__viewer.encoding

    def setEncoding(self, newEncoding):
        " Sets the new editor encoding "
        raise Exception("Setting encoding is not supported by the "
                        "IO console widget")

    def getShortName(self):
        " Tells the display name "
        return "IO console"

    def setShortName(self, name):
        " Sets the display name "
        raise Exception("Setting short name is not supported by the "
                        "IO console widget")
Example #40
0
class MapWidget(Ui_CanvasWidget, QMainWindow):
    def __init__(self, parent=None):
        super(MapWidget, self).__init__(parent)
        self.setupUi(self)

        self.firstshow = True
        self.layerbuttons = []
        self.editfeaturestack = []
        self.lastgpsposition = None
        self.project = None
        self.gps = None
        self.gpslogging = None
        self.selectionbands = defaultdict(partial(QgsRubberBand, self.canvas))

        self.canvas.setCanvasColor(Qt.white)
        self.canvas.enableAntiAliasing(True)
        self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor)

        if hasattr(self.canvas, 'setParallelRenderingEnabled'):
            self.canvas.setParallelRenderingEnabled(True)

        pal = QgsPalLabeling()
        self.canvas.mapRenderer().setLabelingEngine(pal)
        self.canvas.setFrameStyle(QFrame.NoFrame)

        self.editgroup = QActionGroup(self)
        self.editgroup.setExclusive(True)
        self.editgroup.addAction(self.actionPan)
        self.editgroup.addAction(self.actionZoom_In)
        self.editgroup.addAction(self.actionZoom_Out)
        self.editgroup.addAction(self.actionInfo)

        self.actionGPS = GPSAction(":/icons/gps", self.canvas, self)
        self.projecttoolbar.addAction(self.actionGPS)

        gpsspacewidget = QWidget()
        gpsspacewidget.setMinimumWidth(30)
        gpsspacewidget.setSizePolicy(QSizePolicy.Expanding,
                                     QSizePolicy.Expanding)

        self.topspaceraction = self.projecttoolbar.insertWidget(
            self.actionGPS, gpsspacewidget)

        self.dataentryselection = QAction(self.projecttoolbar)
        self.dataentryaction = self.projecttoolbar.insertAction(
            self.topspaceraction, self.dataentryselection)
        self.dataentryselection.triggered.connect(self.select_data_entry)

        self.marker = GPSMarker(self.canvas)
        self.marker.hide()

        self.currentfeatureband = QgsRubberBand(self.canvas)
        self.currentfeatureband.setIconSize(20)
        self.currentfeatureband.setWidth(10)
        self.currentfeatureband.setColor(QColor(186, 93, 212, 76))

        self.gpsband = QgsRubberBand(self.canvas)
        self.gpsband.setColor(QColor(0, 0, 212, 76))
        self.gpsband.setWidth(5)

        RoamEvents.editgeometry.connect(self.queue_feature_for_edit)
        RoamEvents.selectioncleared.connect(self.clear_selection)
        RoamEvents.selectionchanged.connect(self.highlight_selection)
        RoamEvents.featureformloaded.connect(self.feature_form_loaded)

        self.connectButtons()

    def init_qgisproject(self, doc):
        parser = ProjectParser(doc)
        canvasnode = parser.canvasnode
        self.canvas.freeze()
        self.canvas.mapRenderer().readXML(canvasnode)
        self.canvaslayers = parser.canvaslayers()
        self.canvas.setLayerSet(self.canvaslayers)
        #red = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorRedPart", 255 )[0];
        #green = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorGreenPart", 255 )[0];
        #blue = QgsProject.instance().readNumEntry( "Gui", "/CanvasColorBluePart", 255 )[0];
        #color = QColor(red, green, blue);
        #self.canvas.setCanvasColor(color)
        self.canvas.updateScale()
        return self.canvas.mapRenderer().destinationCrs()

    def showEvent(self, *args, **kwargs):
        if self.firstshow:
            self.canvas.refresh()
            self.canvas.repaint()
            self.firstshow = False

    def feature_form_loaded(self, form, feature, project, editmode):
        self.currentfeatureband.setToGeometry(feature.geometry(),
                                              form.QGISLayer)

    def highlight_selection(self, results):
        self.clear_selection()
        for layer, features in results.iteritems():
            band = self.selectionbands[layer]
            band.setColor(QColor(255, 0, 0, 200))
            band.setIconSize(20)
            band.setWidth(2)
            band.setBrushStyle(Qt.NoBrush)
            band.reset(layer.geometryType())
            for feature in features:
                band.addGeometry(feature.geometry(), layer)

    def highlight_active_selection(self, layer, feature, features):
        self.clear_selection()
        self.highlight_selection({layer: features})
        self.currentfeatureband.setToGeometry(feature.geometry(), layer)

    def clear_selection(self):
        # Clear the main selection rubber band
        self.currentfeatureband.reset()
        # Clear the rest
        for band in self.selectionbands.itervalues():
            band.reset()

        self.editfeaturestack = []

    def queue_feature_for_edit(self, form, feature):
        def trigger_default_action():
            for action in self.projecttoolbar.actions():
                if action.property('dataentry') and action.isdefault:
                    action.trigger()
                    break

        self.editfeaturestack.append((form, feature))
        self.load_form(form)
        trigger_default_action()

    def clear_temp_objects(self):
        def clear_tool_band():
            """
            Clear the rubber band of the active tool if it has one
            """
            tool = self.canvas.mapTool()
            try:
                tool.clearBand()
            except AttributeError:
                # No clearBand method found, but that's cool.
                pass

        self.currentfeatureband.reset()
        clear_tool_band()

    def settings_updated(self, settings):
        self.actionGPS.updateGPSPort()
        gpslogging = settings.get('gpslogging', True)
        if self.gpslogging:
            self.gpslogging.logging = gpslogging

    def set_gps(self, gps, logging):
        self.gps = gps
        self.gpslogging = logging
        self.gps.gpsposition.connect(self.gps_update_canvas)
        self.gps.firstfix.connect(self.gps_first_fix)
        self.gps.gpsdisconnected.connect(self.gps_disconnected)

    def gps_update_canvas(self, position, gpsinfo):
        # Recenter map if we go outside of the 95% of the area
        if self.gpslogging.logging:
            self.gpsband.addPoint(position)
            self.gpsband.show()

        if roam.config.settings.get('gpscenter', True):
            if not self.lastgpsposition == position:
                self.lastposition = position
                rect = QgsRectangle(position, position)
                extentlimt = QgsRectangle(self.canvas.extent())
                extentlimt.scale(0.95)

                if not extentlimt.contains(position):
                    self.zoom_to_location(position)

        self.marker.show()
        self.marker.setCenter(position)

    def gps_first_fix(self, postion, gpsinfo):
        zoomtolocation = roam.config.settings.get('gpszoomonfix', True)
        if zoomtolocation:
            self.canvas.zoomScale(1000)
            self.zoom_to_location(postion)

    def zoom_to_location(self, position):
        rect = QgsRectangle(position, position)
        self.canvas.setExtent(rect)
        self.canvas.refresh()

    def gps_disconnected(self):
        self.marker.hide()

    def select_data_entry(self):
        def showformerror(form):
            pass

        def actions():
            for form in self.project.forms:
                action = form.createuiaction()
                valid, failreasons = form.valid
                if not valid:
                    roam.utils.warning("Form {} failed to load".format(
                        form.label))
                    roam.utils.warning("Reasons {}".format(failreasons))
                    action.triggered.connect(partial(showformerror, form))
                else:
                    action.triggered.connect(partial(self.load_form, form))
                yield action

        formpicker = PickActionDialog(msg="Select data entry form")
        formpicker.addactions(actions())
        formpicker.exec_()

    def project_loaded(self, project):
        self.project = project
        self.actionPan.trigger()
        try:
            firstform = project.forms[0]
            self.load_form(firstform)
            self.dataentryselection.setVisible(True)
        except IndexError:
            self.dataentryselection.setVisible(False)

        # Enable the raster layers button only if the project contains a raster layer.
        layers = QgsMapLayerRegistry.instance().mapLayers().values()
        hasrasters = any(layer.type() == QgsMapLayer.RasterLayer
                         for layer in layers)
        self.actionRaster.setEnabled(hasrasters)
        self.defaultextent = self.canvas.extent()
        roam.utils.info("Extent: {}".format(self.defaultextent.toString()))

        self.infoTool.selectionlayers = project.selectlayersmapping()

        self.canvas.freeze(False)
        self.canvas.refresh()

    def setMapTool(self, tool, *args):
        self.canvas.setMapTool(tool)

    def connectButtons(self):
        def connectAction(action, tool):
            action.toggled.connect(partial(self.setMapTool, tool))

        def cursor(name):
            pix = QPixmap(name)
            pix = pix.scaled(QSize(24, 24))
            return QCursor(pix)

        self.zoomInTool = QgsMapToolZoom(self.canvas, False)
        self.zoomOutTool = QgsMapToolZoom(self.canvas, True)
        self.panTool = PanTool(self.canvas)
        self.infoTool = InfoTool(self.canvas)

        connectAction(self.actionZoom_In, self.zoomInTool)
        connectAction(self.actionZoom_Out, self.zoomOutTool)
        connectAction(self.actionPan, self.panTool)
        connectAction(self.actionInfo, self.infoTool)

        self.zoomInTool.setCursor(cursor(':/icons/in'))
        self.zoomOutTool.setCursor(cursor(':/icons/out'))
        self.infoTool.setCursor(cursor(':/icons/info'))

        self.actionRaster.triggered.connect(self.toggleRasterLayers)

        self.infoTool.infoResults.connect(RoamEvents.selectionchanged.emit)

        self.actionHome.triggered.connect(self.homeview)

    def homeview(self):
        """
        Zoom the mapview canvas to the extents the project was opened at i.e. the
        default extent.
        """
        self.canvas.setExtent(self.defaultextent)
        self.canvas.refresh()

    def load_form(self, form):
        self.clearCapatureTools()
        self.dataentryselection.setIcon(QIcon(form.icon))
        self.dataentryselection.setText(form.icontext)
        self.create_capture_buttons(form)

    def create_capture_buttons(self, form):
        tool = form.getMaptool()(self.canvas)
        for action in tool.actions:
            # Create the action here.
            if action.ismaptool:
                action.toggled.connect(partial(self.setMapTool, tool))

            # Set the action as a data entry button so we can remove it later.
            action.setProperty("dataentry", True)
            self.editgroup.addAction(action)
            self.layerbuttons.append(action)
            self.projecttoolbar.insertAction(self.topspaceraction, action)
            action.setChecked(action.isdefault)

        if hasattr(tool, 'geometryComplete'):
            add = partial(self.add_new_feature, form)
            tool.geometryComplete.connect(add)
        else:
            tool.finished.connect(self.openForm)
            tool.error.connect(partial(self.showUIMessage, form.label))

    def add_new_feature(self, form, geometry):
        """
        Add a new new feature to the given layer
        """
        # TODO Extract into function.
        # NOTE This function is doing too much, acts as add and also edit.
        layer = form.QGISLayer
        if layer.geometryType() in [
                QGis.WKBMultiLineString, QGis.WKBMultiPoint,
                QGis.WKBMultiPolygon
        ]:
            geometry.convertToMultiType()

        try:
            form, feature = self.editfeaturestack.pop()
            self.editfeaturegeometry(form, feature, newgeometry=geometry)
            return
        except IndexError:
            pass

        layer = form.QGISLayer
        fields = layer.pendingFields()

        feature = QgsFeature(fields)
        feature.setGeometry(geometry)

        for index in xrange(fields.count()):
            pkindexes = layer.dataProvider().pkAttributeIndexes()
            if index in pkindexes and layer.dataProvider().name(
            ) == 'spatialite':
                continue

            value = layer.dataProvider().defaultValue(index)
            feature[index] = value

        RoamEvents.open_feature_form(form, feature, editmode=False)

    def editfeaturegeometry(self, form, feature, newgeometry):
        # TODO Extract into function.
        layer = form.QGISLayer
        layer.startEditing()
        feature.setGeometry(newgeometry)
        layer.updateFeature(feature)
        saved = layer.commitChanges()
        if not saved:
            map(roam.utils.error, layer.commitErrors())
        self.canvas.refresh()
        self.currentfeatureband.setToGeometry(feature.geometry(), layer)
        RoamEvents.editgeometry_complete.emit(form, feature)

    def clearCapatureTools(self):
        captureselected = False
        for action in self.projecttoolbar.actions():
            if action.objectName() == "capture" and action.isChecked():
                captureselected = True

            if action.property('dataentry'):
                self.projecttoolbar.removeAction(action)
        return captureselected

    def toggleRasterLayers(self):
        """
        Toggle all raster layers on or off.
        """
        if not self.canvaslayers:
            return

        #Freeze the canvas to save on UI refresh
        self.canvas.freeze()
        for layer in self.canvaslayers:
            if layer.layer().type() == QgsMapLayer.RasterLayer:
                layer.setVisible(not layer.isVisible())
                # Really!? We have to reload the whole layer set every time?
            # WAT?
        self.canvas.setLayerSet(self.canvaslayers)
        self.canvas.freeze(False)
        self.canvas.refresh()

    def cleanup(self):
        self.gpsband.reset()
        self.gpsband.hide()
        self.clear_selection()
        self.clear_temp_objects()
        self.clearCapatureTools()
        self.canvas.freeze()
        self.canvas.clear()
        self.canvas.freeze(False)
        for action in self.layerbuttons:
            self.editgroup.removeAction(action)
Example #41
0
class MapWidget(Ui_CanvasWidget, QMainWindow):
    def __init__(self, parent=None):
        super(MapWidget, self).__init__(parent)
        self.setupUi(self)

        icon = roam_style.iconsize()
        self.projecttoolbar.setIconSize(QSize(icon, icon))

        self.firstshow = True
        self.layerbuttons = []
        self.editfeaturestack = []
        self.lastgpsposition = None
        self.project = None
        self.gps = None
        self.gpslogging = None
        self.selectionbands = defaultdict(partial(QgsRubberBand, self.canvas))

        self.bridge = QgsLayerTreeMapCanvasBridge(QgsProject.instance().layerTreeRoot(), self.canvas)
        self.bridge.setAutoSetupOnFirstLayer(False)
        QgsProject.instance().writeProject.connect(self.bridge.writeProject)
        QgsProject.instance().readProject.connect(self.bridge.readProject)

        # self.canvas.setInteractive(False)
        self.canvas.setCanvasColor(Qt.white)
        self.canvas.enableAntiAliasing(True)
        self.canvas.setWheelAction(QgsMapCanvas.WheelZoomToMouseCursor)

        self.snapping = SnappingUtils(self.canvas, self)
        self.canvas.setSnappingUtils(self.snapping)
        QgsProject.instance().readProject.connect(self.snapping.readConfigFromProject)

        if hasattr(self.canvas, 'setParallelRenderingEnabled'):
            threadcount = QThread.idealThreadCount()
            threadcount = 2 if threadcount > 2 else 1
            QgsApplication.setMaxThreads(threadcount)
            self.canvas.setParallelRenderingEnabled(True)

        pal = QgsPalLabeling()
        self.canvas.mapRenderer().setLabelingEngine(pal)
        self.canvas.setFrameStyle(QFrame.NoFrame)

        self.editgroup = QActionGroup(self)
        self.editgroup.setExclusive(True)
        self.editgroup.addAction(self.actionPan)
        self.editgroup.addAction(self.actionZoom_In)
        self.editgroup.addAction(self.actionZoom_Out)
        self.editgroup.addAction(self.actionInfo)

        self.actionGPS = GPSAction(":/icons/gps", self.canvas, self)
        self.projecttoolbar.addAction(self.actionGPS)

        if roam.config.settings.get('north_arrow', False):
            self.northarrow = NorthArrow(":/icons/north", self.canvas)
            self.northarrow.setPos(10, 10)
            self.canvas.scene().addItem(self.northarrow)

        self.scalebar_enabled = roam.config.settings.get('scale_bar', False)
        if self.scalebar_enabled:
            self.scalebar = ScaleBarItem(self.canvas)
            self.canvas.scene().addItem(self.scalebar)

        self.projecttoolbar.setContextMenuPolicy(Qt.CustomContextMenu)

        gpsspacewidget = QWidget()
        gpsspacewidget.setMinimumWidth(30)
        gpsspacewidget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        self.topspaceraction = self.projecttoolbar.insertWidget(self.actionGPS, gpsspacewidget)

        self.dataentryselection = QAction(self.projecttoolbar)
        self.dataentryaction = self.projecttoolbar.insertAction(self.topspaceraction, self.dataentryselection)
        self.dataentryselection.triggered.connect(self.select_data_entry)

        self.marker = GPSMarker(self.canvas)
        self.marker.hide()

        self.currentfeatureband = CurrentSelection(self.canvas)
        self.currentfeatureband.setIconSize(30)
        self.currentfeatureband.setWidth(10)
        self.currentfeatureband.setColor(QColor(186, 93, 212, 50))
        self.currentfeatureband.setOutlineColour(QColor(186, 93, 212))

        self.gpsband = QgsRubberBand(self.canvas)
        self.gpsband.setColor(QColor(165, 111, 212, 75))
        self.gpsband.setWidth(5)

        RoamEvents.editgeometry.connect(self.queue_feature_for_edit)
        RoamEvents.selectioncleared.connect(self.clear_selection)
        RoamEvents.selectionchanged.connect(self.highlight_selection)
        RoamEvents.openfeatureform.connect(self.feature_form_loaded)
        RoamEvents.sync_complete.connect(self.refresh_map)
        RoamEvents.snappingChanged.connect(self.snapping_changed)

        self.snappingbutton = QToolButton()
        self.snappingbutton.setText("Snapping: On")
        self.snappingbutton.setAutoRaise(True)
        self.snappingbutton.pressed.connect(self.toggle_snapping)

        spacer = QWidget()
        spacer2 = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
        spacer2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)

        self.scalewidget = QgsScaleComboBox()

        self.scalebutton = QToolButton()
        self.scalebutton.setAutoRaise(True)
        self.scalebutton.setMaximumHeight(self.statusbar.height())
        self.scalebutton.pressed.connect(self.selectscale)
        self.scalebutton.setText("Scale")

        self.scalelist = BigList(parent=self.canvas, centeronparent=True, showsave=False)
        self.scalelist.hide()
        self.scalelist.setlabel("Map Scale")
        self.scalelist.setmodel(self.scalewidget.model())
        self.scalelist.closewidget.connect(self.scalelist.close)
        self.scalelist.itemselected.connect(self.update_scale_from_item)
        self.scalelist.itemselected.connect(self.scalelist.close)

        self.positionlabel = QLabel('')
        self.gpslabel = QLabel("GPS: Not active")

        self.statusbar.addWidget(self.snappingbutton)
        self.statusbar.addWidget(spacer2)
        self.statusbar.addWidget(self.gpslabel)
        self.statusbar.addPermanentWidget(self.scalebutton)

        self.canvas.extentsChanged.connect(self.updatestatuslabel)
        self.canvas.scaleChanged.connect(self.updatestatuslabel)

        GPS.gpsposition.connect(self.update_gps_label)
        GPS.gpsdisconnected.connect(self.gps_disconnected)

        self.connectButtons()

    def clear_plugins(self):
        toolbars = self.findChildren(QToolBar)
        for toolbar in toolbars:
            if toolbar.property("plugin_toolbar"):
                self.removeToolBar(toolbar)
                toolbar.deleteLater()

    def add_plugins(self, pluginnames):
        for name in pluginnames:
            # Get the plugin
            try:
                plugin_mod = plugins.loaded_plugins[name]
            except KeyError:
                continue

            if not hasattr(plugin_mod, 'toolbars'):
                roam.utils.warning("No toolbars() function found in {}".format(name))
                continue

            toolbars = plugin_mod.toolbars()
            self.load_plugin_toolbars(toolbars)

    def load_plugin_toolbars(self, toolbars):
        for ToolBarClass in toolbars:
            toolbar = ToolBarClass(plugins.api, self)
            self.addToolBar(Qt.BottomToolBarArea, toolbar)
            toolbar.setProperty("plugin_toolbar", True)

    def snapping_changed(self, snapping):
        """
        Called when the snapping settings have changed. Updates the label in the status bar.
        :param snapping:
        """
        if snapping:
            self.snappingbutton.setText("Snapping: On")
        else:
            self.snappingbutton.setText("Snapping: Off")

    def toggle_snapping(self):
        """
        Toggle snapping on or off.
        """
        snap = not snapping
        global snapping
        snapping = snap
        RoamEvents.snappingChanged.emit(snapping)

    def selectscale(self):
        """
        Show the select scale widget.
        :return:
        """
        self.scalelist.show()

    def update_scale_from_item(self, index):
        """
        Update the canvas scale from the selected scale item.
        :param index: The index of the selected item.
        """
        scale, _ = self.scalewidget.toDouble(index.data(Qt.DisplayRole))
        self.canvas.zoomScale(1.0 / scale)

    def update_gps_label(self, position, gpsinfo):
        """
        Update the GPS label in the status bar with the GPS status.
        :param position: The current GPS position.
        :param gpsinfo: The current extra GPS information.
        """
        self.gpslabel.setText("GPS: PDOP {0:.2f}   HDOP {1:.2f}    VDOP {2:.2f}".format(gpsinfo.pdop,
                                                                                        gpsinfo.hdop,
                                                                                        gpsinfo.vdop))

    def gps_disconnected(self):
        """
        Called when the GPS is disconnected. Updates the label in the status bar with the message.
        :return:
        """
        self.gpslabel.setText("GPS Not Active")

    def updatestatuslabel(self, *args):
        """
        Update the status bar labels when the information has changed.
        """
        extent = self.canvas.extent()
        self.positionlabel.setText("Map Center: {}".format(extent.center().toString()))
        scale = 1.0 / self.canvas.scale()
        scale = self.scalewidget.toString(scale)
        self.scalebutton.setText(scale)

    def refresh_map(self):
        """
        Refresh the map
        """
        self.canvas.refresh()

    def updatescale(self):
        """
        Update the scale of the map with the current scale from the scale widget
        :return:
        """
        self.canvas.zoomScale(1.0 / self.scalewidget.scale())

    def init_qgisproject(self, doc):
        """
        Called when the project file is read for the firs time.
        :param doc: The XML doc.
        :return: The current canvas CRS
        :note: This method is old and needs to be refactored into something else.
        """
        return self.canvas.mapSettings().destinationCrs()

    def showEvent(self, *args, **kwargs):
        """
        Handle the show event of the of the map widget. We have to do a little hack here to make the QGIS map refresh.
        """
        if QGis.QGIS_VERSION_INT == 20200 and self.firstshow:
            self.canvas.refresh()
            self.canvas.repaint()
            self.firstshow = False

    def feature_form_loaded(self, form, feature, *args):
        """
        Called when the feature form is loaded.
        :param form: The Form object. Holds a reference to the forms layer.
        :param feature: The current capture feature
        """
        self.currentfeatureband.setToGeometry(feature.geometry(), form.QGISLayer)

    def highlight_selection(self, results):
        """
        Highlight the selection on the canvas.  This updates all selected objects based on the result set.
        :param results: A dict-of-list of layer-features.
        """
        self.clear_selection()
        for layer, features in results.iteritems():
            band = self.selectionbands[layer]
            band.setColor(QColor(255, 0, 0))
            band.setIconSize(25)
            band.setWidth(5)
            band.setBrushStyle(Qt.NoBrush)
            band.reset(layer.geometryType())
            band.setZValue(self.currentfeatureband.zValue() - 1)
            for feature in features:
                band.addGeometry(feature.geometry(), layer)
        self.canvas.update()

    def highlight_active_selection(self, layer, feature, features):
        """
        Update the current active selected feature.
        :param layer: The layer of the active feature.
        :param feature: The active feature.
        :param features: The other features in the set to show as non active selection.
        :return:
        """
        self.clear_selection()
        self.highlight_selection({layer: features})
        self.currentfeatureband.setToGeometry(feature.geometry(), layer)
        self.canvas.update()

    def clear_selection(self):
        """
        Clear the selection from the canvas.   Resets all selection rubbber bands.
        :return:
        """
        # Clear the main selection rubber band
        self.canvas.scene().update()
        self.currentfeatureband.reset()
        # Clear the rest
        for band in self.selectionbands.itervalues():
            band.reset()

        self.canvas.update()
        self.editfeaturestack = []

    def queue_feature_for_edit(self, form, feature):
        """
        Push a feature on the edit stack so the feature can have the geometry edited.
        :note: This is a big hack and I don't like it!
        :param form: The form for the current feature
        :param feature: The active feature.
        """
        def trigger_default_action():
            for action in self.projecttoolbar.actions():
                if action.property('dataentry') and action.isdefault:
                    action.trigger()
                    self.canvas.mapTool().setEditMode(True, feature.geometry())
                    break

        self.editfeaturestack.append((form, feature))
        self.load_form(form)
        trigger_default_action()

    def clear_temp_objects(self):
        """
        Clear all temp objects from the canvas.
        :return:
        """
        def clear_tool_band():
            """
            Clear the rubber band of the active tool if it has one
            """
            tool = self.canvas.mapTool()
            try:
                tool.clearBand()
            except AttributeError:
                # No clearBand method found, but that's cool.
                pass

        self.currentfeatureband.reset()
        clear_tool_band()

    def settings_updated(self, settings):
        """
        Called when the settings have been updated in the Roam config.
        :param settings: A dict of the settings.
        """
        self.actionGPS.updateGPSPort()
        gpslogging = settings.get('gpslogging', True)
        if self.gpslogging:
            self.gpslogging.logging = gpslogging

    def set_gps(self, gps, logging):
        """
        Set the GPS for the map widget.  Connects GPS signals
        """
        self.gps = gps
        self.gpslogging = logging
        self.gps.gpsposition.connect(self.gps_update_canvas)
        self.gps.firstfix.connect(self.gps_first_fix)
        self.gps.gpsdisconnected.connect(self.gps_disconnected)

    def gps_update_canvas(self, position, gpsinfo):
        """
        Updates the map canvas based on the GPS position.  By default if the GPS is outside the canvas
        extent the canvas will move to center on the GPS.  Can be turned off in settings.
        :param postion: The current GPS position.
        :param gpsinfo: The extra GPS information
        """
        # Recenter map if we go outside of the 95% of the area
        if self.gpslogging.logging:
            self.gpsband.addPoint(position)
            self.gpsband.show()

        if roam.config.settings.get('gpscenter', True):
            if not self.lastgpsposition == position:
                self.lastposition = position
                rect = QgsRectangle(position, position)
                extentlimt = QgsRectangle(self.canvas.extent())
                extentlimt.scale(0.95)

                if not extentlimt.contains(position):
                    self.zoom_to_location(position)

        self.marker.show()
        self.marker.setCenter(position, gpsinfo)

    def gps_first_fix(self, postion, gpsinfo):
        """
        Called the first time the GPS gets a fix.  If set this will zoom to the GPS after the first fix
        :param postion: The current GPS position.
        :param gpsinfo: The extra GPS information
        """
        zoomtolocation = roam.config.settings.get('gpszoomonfix', True)
        if zoomtolocation:
            self.canvas.zoomScale(1000)
            self.zoom_to_location(postion)

    def zoom_to_location(self, position):
        """
        Zoom to ta given position on the map..
        """
        rect = QgsRectangle(position, position)
        self.canvas.setExtent(rect)
        self.canvas.refresh()

    def gps_disconnected(self):
        """
        Called when the GPS is disconnected
        """
        self.marker.hide()

    def select_data_entry(self):
        """
        Open the form selection widget to allow the user to pick the active capture form.
        """
        def showformerror(form):
            pass

        def actions():
            for form in self.project.forms:
                if not self.form_valid_for_capture(form):
                    continue

                action = form.createuiaction()
                valid, failreasons = form.valid
                if not valid:
                    roam.utils.warning("Form {} failed to load".format(form.label))
                    roam.utils.warning("Reasons {}".format(failreasons))
                    action.triggered.connect(partial(showformerror, form))
                else:
                    action.triggered.connect(partial(self.load_form, form))
                yield action

        formpicker = PickActionDialog(msg="Select data entry form", wrap=5)
        formpicker.addactions(actions())
        formpicker.exec_()

    def project_loaded(self, project):
        """
        Called when the project is loaded. Main entry point for a loade project.
        :param project: The Roam project that has been loaded.
        """
        self.project = project
        self.actionPan.trigger()
        firstform = self.first_capture_form()
        if firstform:
            self.load_form(firstform)
            self.dataentryselection.setVisible(True)
        else:
            self.dataentryselection.setVisible(False)

        # Enable the raster layers button only if the project contains a raster layer.
        layers = QgsMapLayerRegistry.instance().mapLayers().values()
        hasrasters = any(layer.type() == QgsMapLayer.RasterLayer for layer in layers)
        self.actionRaster.setEnabled(hasrasters)
        self.defaultextent = self.canvas.extent()
        roam.utils.info("Extent: {}".format(self.defaultextent.toString()))

        self.infoTool.selectionlayers = project.selectlayersmapping()

        self.canvas.refresh()

        projectscales, _ = QgsProject.instance().readBoolEntry("Scales", "/useProjectScales")
        if projectscales:
            projectscales, _ = QgsProject.instance().readListEntry("Scales", "/ScalesList")

            self.scalewidget.updateScales(projectscales)
        else:
            scales = ["1:50000", "1:25000", "1:10000", "1:5000",
                      "1:2500", "1:1000", "1:500", "1:250", "1:200", "1:100"]
            scales = roam.config.settings.get('scales', scales)
            self.scalewidget.updateScales(scales)

        if self.scalebar_enabled:
            self.scalebar.update()

        self.actionPan.toggle()
        self.clear_plugins()
        self.add_plugins(project.enabled_plugins)

    def setMapTool(self, tool, *args):
        """
        Set the active map tool in the canvas.
        :param tool: The QgsMapTool to set.
        """
        if tool == self.canvas.mapTool():
            return

        if hasattr(tool, "setSnapping"):
            tool.setSnapping(snapping)
        self.canvas.setMapTool(tool)

    def connectButtons(self):
        """
        Connect the default buttons in the interface. Zoom, pan, etc
        """
        def connectAction(action, tool):
            action.toggled.connect(partial(self.setMapTool, tool))

        def cursor(name):
            pix = QPixmap(name)
            pix = pix.scaled(QSize(24, 24))
            return QCursor(pix)

        self.zoomInTool = QgsMapToolZoom(self.canvas, False)
        self.zoomOutTool = QgsMapToolZoom(self.canvas, True)
        self.panTool = PanTool(self.canvas)
        self.infoTool = InfoTool(self.canvas)

        connectAction(self.actionZoom_In, self.zoomInTool)
        connectAction(self.actionZoom_Out, self.zoomOutTool)
        connectAction(self.actionPan, self.panTool)
        connectAction(self.actionInfo, self.infoTool)

        self.zoomInTool.setCursor(cursor(':/icons/in'))
        self.zoomOutTool.setCursor(cursor(':/icons/out'))
        self.infoTool.setCursor(cursor(':/icons/select'))

        self.actionRaster.triggered.connect(self.toggleRasterLayers)
        self.actionHome.triggered.connect(self.homeview)

    def homeview(self):
        """
        Zoom the mapview canvas to the extents the project was opened at i.e. the
        default extent.
        """
        self.canvas.setExtent(self.defaultextent)
        self.canvas.refresh()

    def form_valid_for_capture(self, form):
        """
        Check if the given form is valid for capture.
        :param form: The form to check.
        :return: True if valid form for capture
        """
        return form.has_geometry and self.project.layer_can_capture(form.QGISLayer)

    def first_capture_form(self):
        """
        Return the first valid form for capture.
        """
        for form in self.project.forms:
            if self.form_valid_for_capture(form):
                return form

    def load_form(self, form):
        """
        Load the given form so it's the active one for capture
        :param form: The form to load
        """
        self.clearCapatureTools()
        self.dataentryselection.setIcon(QIcon(form.icon))
        self.dataentryselection.setText(form.icontext)
        self.create_capture_buttons(form)

    def create_capture_buttons(self, form):
        """
        Create the capture buttons in the toolbar for the given form.
        :param form: The active form.
        """
        layer = form.QGISLayer
        tool = form.getMaptool()(self.canvas)
        for action in tool.actions:
            # Create the action here.
            if action.ismaptool:
                action.toggled.connect(partial(self.setMapTool, tool))

            # Set the action as a data entry button so we can remove it later.
            action.setProperty("dataentry", True)
            self.editgroup.addAction(action)
            self.layerbuttons.append(action)
            self.projecttoolbar.insertAction(self.topspaceraction, action)
            action.setChecked(action.isdefault)

        if hasattr(tool, 'geometryComplete'):
            add = partial(self.add_new_feature, form)
            tool.geometryComplete.connect(add)
        else:
            tool.finished.connect(self.openForm)
            tool.error.connect(partial(self.showUIMessage, form.label))

    def add_new_feature(self, form, geometry):
        """
        Add a new new feature to the given layer
        """
        # TODO Extract into function.
        # NOTE This function is doing too much, acts as add and also edit.
        layer = form.QGISLayer
        if layer.geometryType() in [QGis.WKBMultiLineString, QGis.WKBMultiPoint, QGis.WKBMultiPolygon]:
            geometry.convertToMultiType()

        try:
            form, feature = self.editfeaturestack.pop()
            self.editfeaturegeometry(form, feature, newgeometry=geometry)
            return
        except IndexError:
            pass

        feature = form.new_feature(geometry=geometry)
        RoamEvents.load_feature_form(form, feature, editmode=False)

    def editfeaturegeometry(self, form, feature, newgeometry):
        # TODO Extract into function.
        layer = form.QGISLayer
        layer.startEditing()
        feature.setGeometry(newgeometry)
        layer.updateFeature(feature)
        saved = layer.commitChanges()
        if not saved:
            map(roam.utils.error, layer.commitErrors())
        self.canvas.refresh()
        self.currentfeatureband.setToGeometry(feature.geometry(), layer)
        RoamEvents.editgeometry_complete.emit(form, feature)
        self.canvas.mapTool().setEditMode(False, None)

    def clearCapatureTools(self):
        """
        Clear the capture tools from the toolbar.
        :return: True if the capture button was active at the time of clearing.
        """
        captureselected = False
        for action in self.projecttoolbar.actions():
            if action.objectName() == "capture" and action.isChecked():
                captureselected = True

            if action.property('dataentry'):
                self.projecttoolbar.removeAction(action)
        return captureselected

    def toggleRasterLayers(self):
        """
        Toggle all raster layers on or off.
        """
        # Freeze the canvas to save on UI refresh
        self.canvas.freeze()
        tree = QgsProject.instance().layerTreeRoot()
        for node in tree.findLayers():
            if node.layer().type() == QgsMapLayer.RasterLayer:
                if node.isVisible() == Qt.Checked:
                    state = Qt.Unchecked
                else:
                    state = Qt.Checked
                node.setVisible(state)

        self.canvas.freeze(False)
        self.canvas.refresh()

    def cleanup(self):
        """
        Clean up when the project has changed.
        :return:
        """
        self.bridge.clear()
        self.gpsband.reset()
        self.gpsband.hide()
        self.clear_selection()
        self.clear_temp_objects()
        self.clearCapatureTools()
        self.canvas.freeze()
        self.canvas.clear()
        self.canvas.freeze(False)
        for action in self.layerbuttons:
            self.editgroup.removeAction(action)
class quickFinder(QObject):

    name = u"&Quick Finder"
    actions = None
    toolbar = None
    finders = {}

    loadingIcon = None

    def __init__(self, iface):
        QObject.__init__(self)
        self.iface = iface
        self.actions = {}
        self.finders = {}
        self.settings = MySettings()

        self._initFinders()

        self.iface.projectRead.connect(self._reloadFinders)
        self.iface.newProjectCreated.connect(self._reloadFinders)

        # translation environment
        self.plugin_dir = os.path.dirname(__file__)
        locale = QSettings().value("locale/userLocale")[0:2]
        localePath = os.path.join(self.plugin_dir, 'i18n', 'quickfinder_{0}.qm'.format(locale))
        if os.path.exists(localePath):
            self.translator = QTranslator()
            self.translator.load(localePath)
            QCoreApplication.installTranslator(self.translator)

    def initGui(self):
        self.actions['showSettings'] = QAction(
            QIcon(":/plugins/quickfinder/icons/settings.svg"),
            self.tr(u"&Settings"),
            self.iface.mainWindow())
        self.actions['showSettings'].triggered.connect(self.showSettings)
        self.iface.addPluginToMenu(self.name, self.actions['showSettings'])
        self.actions['help'] = QAction(
            QIcon(":/plugins/quickfinder/icons/help.svg"),
            self.tr("Help"),
            self.iface.mainWindow())
        self.actions['help'].triggered.connect(
            lambda: QDesktopServices().openUrl(
                QUrl("http://3nids.github.io/quickfinder")))
        self.iface.addPluginToMenu(self.name, self.actions['help'])
        self._initToolbar()
        self.rubber = QgsRubberBand(self.iface.mapCanvas())
        self.rubber.setColor(QColor(255, 255, 50, 200))
        self.rubber.setIcon(self.rubber.ICON_CIRCLE)
        self.rubber.setIconSize(15)
        self.rubber.setWidth(4)
        self.rubber.setBrushStyle(Qt.NoBrush)

    def unload(self):
        """ Unload plugin """
        for key in self.finders.keys():
            self.finders[key].close()
        for action in self.actions.itervalues():
            self.iface.removePluginMenu(self.name, action)
        if self.toolbar:
            del self.toolbar
        if self.rubber:
            self.iface.mapCanvas().scene().removeItem(self.rubber)
            del self.rubber

    def _initToolbar(self):
        """setup the plugin toolbar."""
        self.toolbar = self.iface.addToolBar(self.name)
        self.toolbar.setObjectName('mQuickFinderToolBar')
        self.searchAction = QAction(QIcon(":/plugins/quickfinder/icons/magnifier13.svg"),
                                     self.tr("Search"),
                                     self.toolbar)
        self.stopAction = QAction(
            QIcon(":/plugins/quickfinder/icons/wrong2.svg"),
            self.tr("Cancel"),
            self.toolbar)
        self.finderBox = FinderBox(self.finders, self.iface, self.toolbar)
        self.finderBox.searchStarted.connect(self.searchStarted)
        self.finderBox.searchFinished.connect(self.searchFinished)
        self.finderBoxAction = self.toolbar.addWidget(self.finderBox)
        self.finderBoxAction.setVisible(True)
        self.searchAction.triggered.connect(self.finderBox.search)
        self.toolbar.addAction(self.searchAction)
        self.stopAction.setVisible(False)
        self.stopAction.triggered.connect(self.finderBox.stop)
        self.toolbar.addAction(self.stopAction)
        self.toolbar.setVisible(True)

    def _initFinders(self):
        self.finders['geomapfish'] = GeomapfishFinder(self)
        self.finders['osm'] = OsmFinder(self)
        self.finders['project'] = ProjectFinder(self)
        for key in self.finders.keys():
            self.finders[key].message.connect(self.displayMessage)
        self.refreshProject()

    def _reloadFinders(self):
        for key in self.finders.keys():
            self.finders[key].close()
            self.finders[key].reload()
        self.refreshProject()

    @pyqtSlot(str, QgsMessageBar.MessageLevel)
    def displayMessage(self, message, level):
        self.iface.messageBar().pushMessage("QuickFinder", message, level)

    def showSettings(self):
        if ConfigurationDialog().exec_():
            self._reloadFinders()

    def searchStarted(self):
        self.searchAction.setVisible(False)
        self.stopAction.setVisible(True)

    def searchFinished(self):
        self.searchAction.setVisible(True)
        self.stopAction.setVisible(False)

    def refreshProject(self):
        if not self.finders['project'].activated:
            return
        if not self.settings.value("refreshAuto"):
            return
        nDays = self.settings.value("refreshDelay")
        # do not ask more ofen than 3 days
        askLimit = min(3, nDays)
        recentlyAsked = self.settings.value("refreshLastAsked") >= nDaysAgoIsoDate(askLimit)
        if recentlyAsked:
            return
        threshDate = nDaysAgoIsoDate(nDays)
        uptodate = True
        for search in self.finders['project'].searches.values():
            if search.dateEvaluated <= threshDate:
                uptodate = False
                break
        if uptodate:
            return
        self.settings.setValue("refreshLastAsked", nDaysAgoIsoDate(0))
        ret = QMessageBox(QMessageBox.Warning,
                          "Quick Finder",
                          QCoreApplication.translate("Auto Refresh", "Some searches are outdated. Do you want to refresh them ?"),
                          QMessageBox.Cancel | QMessageBox.Yes).exec_()
        if ret == QMessageBox.Yes:
            RefreshDialog(self.finders['project']).exec_()
class RecentFiles():
    """Keeps track of last opened files."""
    def __init__(self, parent=None):
        """Constructor"""
        self.parent = parent
        self.max_recent = 5
        self.recent_files = []
        self.recent_actions = []
        self.action_clear_history = QAction(self.parent)
        self.create()
        self.update()

    def create(self):
        """Creates menu actions."""
        for i in range(self.max_recent):
            action = QAction(self.parent)
            action.setIcon(QIcon(QPixmap(":/icons/action_rom.png")))
            self.recent_actions.append(action)
            self.recent_actions[i].setVisible(False)
            self.parent.connect(self.recent_actions[i], SIGNAL("triggered()"),
                                self.parent.file_open)
            self.parent.menuRecent.addAction(self.recent_actions[i])
        self.parent.menuRecent.addSeparator()
        self.action_clear_history.setText("&Clear history")
        self.action_clear_history.setEnabled(False)
        self.action_clear_history.setVisible(True)
        self.action_clear_history.setIcon(
            QIcon(QPixmap(":/icons/action_clear.png")))
        self.parent.connect(self.action_clear_history, SIGNAL("triggered()"),
                            self.clear)
        self.parent.menuRecent.addAction(self.action_clear_history)

    def update(self):
        """Updates list of recent files."""
        self.recent_files = self.parent.settings.qset.value("recent_files", [])
        num_files = min(len(self.recent_files), self.max_recent)
        for i in range(num_files):
            text = QFileInfo(self.recent_files[i]).fileName()
            self.recent_actions[i].setText(text)
            self.recent_actions[i].setData(self.recent_files[i])
            self.recent_actions[i].setVisible(True)
            self.recent_actions[i].setToolTip(
                QFileInfo(self.recent_files[i]).filePath())
        for j in range(num_files, self.max_recent):
            self.recent_actions[j].setVisible(False)
        self.action_clear_history.setEnabled((num_files > 0))

    def add(self, filepath):
        """Adds file to recent files list."""
        if filepath in self.recent_files:
            self.recent_files.remove(filepath)
        self.recent_files.insert(0, filepath)
        while len(self.recent_files) > 5:
            self.recent_files.pop(len(self.recent_files) - 1)
        self.parent.settings.qset.setValue("recent_files", self.recent_files)
        self.update()

    def clear(self):
        """Clears list of recent files."""
        self.parent.settings.qset.remove("recent_files")
        self.update()
Example #44
0
class PylintViewer( QWidget ):
    " Pylint tab widget "

    # Limits to colorize the final score
    BadLimit = 8.5
    GoodLimit = 9.5

    # Options of providing a report
    SingleFile     = 0
    DirectoryFiles = 1
    ProjectFiles   = 2
    SingleBuffer   = 3

    updatePylintTooltip = pyqtSignal( str )

    def __init__( self, parent = None ):
        QWidget.__init__( self, parent )

        self.__reportUUID = ""
        self.__reportFileName = ""
        self.__reportOption = -1
        self.__reportShown = False
        self.__report = None

        self.__widgets = []

        # Prepare members for reuse
        if GlobalData().pylintAvailable:
            self.__noneLabel = QLabel( "\nNo results available" )
        else:
            self.__noneLabel = QLabel( "\nPylint is not available" )
        self.__noneLabel.setAutoFillBackground( True )
        noneLabelPalette = self.__noneLabel.palette()
        noneLabelPalette.setColor( QPalette.Background,
                                   GlobalData().skin.nolexerPaper )
        self.__noneLabel.setPalette( noneLabelPalette )

        self.__noneLabel.setFrameShape( QFrame.StyledPanel )
        self.__noneLabel.setAlignment( Qt.AlignHCenter )
        self.__headerFont = self.__noneLabel.font()
        self.__headerFont.setPointSize( self.__headerFont.pointSize() + 4 )
        self.__noneLabel.setFont( self.__headerFont )

        self.__createLayout( parent )

        self.__updateButtonsStatus()
        self.resizeEvent()
        return

    def __createLayout( self, parent ):
        " Creates the toolbar and layout "

        # Buttons
        self.printButton = QAction( PixmapCache().getIcon( 'printer.png' ),
                                    'Print', self )
        #printButton.setShortcut( 'Ctrl+' )
        self.printButton.triggered.connect( self.__onPrint )
        self.printButton.setVisible( False )

        self.printPreviewButton = QAction(
                PixmapCache().getIcon( 'printpreview.png' ),
                'Print preview', self )
        #printPreviewButton.setShortcut( 'Ctrl+' )
        self.printPreviewButton.triggered.connect( self.__onPrintPreview )
        self.printPreviewButton.setVisible( False )

        spacer = QWidget()
        spacer.setSizePolicy( QSizePolicy.Expanding, QSizePolicy.Expanding )

        self.clearButton = QAction(
            PixmapCache().getIcon( 'trash.png' ),
            'Clear', self )
        self.clearButton.triggered.connect( self.__clear )

        # The toolbar
        self.toolbar = QToolBar( self )
        self.toolbar.setOrientation( Qt.Vertical )
        self.toolbar.setMovable( False )
        self.toolbar.setAllowedAreas( Qt.RightToolBarArea )
        self.toolbar.setIconSize( QSize( 16, 16 ) )
        self.toolbar.setFixedWidth( 28 )
        self.toolbar.setContentsMargins( 0, 0, 0, 0 )

        self.toolbar.addAction( self.printPreviewButton )
        self.toolbar.addAction( self.printButton )
        self.toolbar.addWidget( spacer )
        self.toolbar.addAction( self.clearButton )

        self.__vLayout = QVBoxLayout()
        self.__vLayout.setContentsMargins( 5, 5, 5, 5 )
        self.__vLayout.setSpacing( 0 )
        self.__vLayout.setSizeConstraint( QLayout.SetFixedSize )

        self.__bodyFrame = QFrame( self )
#        self.__bodyFrame.setFrameShape( QFrame.StyledPanel )
        self.__bodyFrame.setFrameShape( QFrame.NoFrame )

#        self.__bodyFrame.setSizePolicy( QSizePolicy.Maximum,
#                                        QSizePolicy.Expanding )
        self.__bodyFrame.setLayout( self.__vLayout )
        self.bodyWidget = QScrollArea( self )
        self.bodyWidget.setFocusPolicy( Qt.NoFocus )
        self.bodyWidget.setWidget( self.__bodyFrame )
        self.bodyWidget.hide()

        self.__hLayout = QHBoxLayout()
        self.__hLayout.setContentsMargins( 0, 0, 0, 0 )
        self.__hLayout.setSpacing( 0 )
        self.__hLayout.addWidget( self.toolbar )
        self.__hLayout.addWidget( self.__noneLabel )
        self.__hLayout.addWidget( self.bodyWidget )

        self.setLayout( self.__hLayout )
        return

    def __updateButtonsStatus( self ):
        " Updates the buttons status "
        self.printButton.setEnabled( self.__reportShown )
        self.printPreviewButton.setEnabled( self.__reportShown )
        self.clearButton.setEnabled( self.__reportShown )
        return

    def __onPrint( self ):
        " Triggered when the print button is pressed "
        pass

    def __onPrintPreview( self ):
        " triggered when the print preview button is pressed "
        pass

    def setFocus( self ):
        " Overridden setFocus "
        self.__vLayout.setFocus()
        return

    def __clear( self ):
        " Clears the content of the vertical layout "
        if not self.__reportShown:
            return

        self.__removeAll()
        self.bodyWidget.hide()
        self.__noneLabel.show()

        self.__report = None
        self.__reportShown = False
        self.__updateButtonsStatus()
        self.resizeEvent()

        self.__updateTooltip()
        return

    def __removeAll( self ):
        " Removes all the items from the report "
        for item in self.__widgets:
            item.hide()
            self.__vLayout.removeWidget( item )
            del item

        self.__widgets = []
        return

    def __createScoreLabel( self, score, previousScore,
                            showFileName, fileName ):
        " Creates the score label "

        txt = "Score: " + str( score )
        if previousScore != "":
            txt += " / Previous score: " + str( previousScore )
        if not showFileName:
            txt += " for " + os.path.basename( fileName )

        scoreLabel = QLabel( txt )
        scoreLabel.setFrameShape( QFrame.StyledPanel )
        scoreLabel.setFont( self.__headerFont )
        scoreLabel.setAutoFillBackground( True )
        palette = scoreLabel.palette()

        if score < self.BadLimit:
            palette.setColor( QPalette.Background, QColor( 255, 127, 127 ) )
            palette.setColor( QPalette.Foreground, QColor( 0, 0, 0 ) )
        elif score > self.GoodLimit:
            palette.setColor( QPalette.Background, QColor( 220, 255, 220 ) )
            palette.setColor( QPalette.Foreground, QColor( 0, 0, 0 ) )
        else:
            palette.setColor( QPalette.Background, QColor( 255, 255, 127 ) )
            palette.setColor( QPalette.Foreground, QColor( 0, 0, 0 ) )

        scoreLabel.setPalette( palette )
        return scoreLabel

    @staticmethod
    def __setTableHeight( table ):
        " Auxiliary function to set the table height "

        # Height - it is ugly and approximate however I am tired of
        # calculating the proper height. Why is this so hard, huh?
        lastRowHeight = table.itemDelegate().lastHeight
        height = lastRowHeight * ( table.topLevelItemCount() + 1 ) + 10
        table.setFixedHeight( height )
        return

    @staticmethod
    def __shouldShowFileName( messages ):
        " Decides if the file name column should be supressed "
        if len( messages ) == 0:
            return False
        firstName = messages[ 0 ].fileName
        for index in range( 1, len( messages ) ):
            if firstName != messages[ index ].fileName:
                return True
        return False

    def __addErrorsTable( self, messages, showFileName ):
        " Creates the messages table "

        errTable = QTreeWidget( self.bodyWidget )
        errTable.setAlternatingRowColors( True )
        errTable.setRootIsDecorated( False )
        errTable.setItemsExpandable( False )
        errTable.setSortingEnabled( True )
        errTable.setItemDelegate( NoOutlineHeightDelegate( 4 ) )
        errTable.setUniformRowHeights( True )
        errTable.itemActivated.connect( self.__errorActivated )

        headerLabels = [ "File name", "Line", "Message ID", "Object", "Message" ]
        errTable.setHeaderLabels( headerLabels )

        for item in messages:
            if item.position is None:
                lineNumber = str( item.lineNumber )
            else:
                lineNumber = str( item.lineNumber ) + ":" + str( item.position )
            values = [ item.fileName, lineNumber, item.messageID,
                       item.objectName, item.message ]
            errTable.addTopLevelItem( ErrorTableItem( values, 1 ) )

        # Hide the file name column if required
        if not showFileName:
            errTable.setColumnHidden( 0, True )

        # Resizing
        errTable.header().resizeSections( QHeaderView.ResizeToContents )
        errTable.header().setStretchLastSection( True )

        # Sort indicator
        if showFileName:
            sortIndex = 0   # By file names
        else:
            sortIndex = 1   # By line number because this is from the same file
        errTable.header().setSortIndicator( sortIndex, Qt.AscendingOrder )
        errTable.sortItems( sortIndex, errTable.header().sortIndicatorOrder() )

        # Height
        self.__setTableHeight( errTable )

        self.__vLayout.addWidget( errTable )
        self.__widgets.append( errTable )
        return

    def __addSimilarity( self, similarity, titleText ):
        " Adds a similarity "

        # Label
        title = QLabel( titleText )
        title.setFont( self.__headerFont )

        self.__vLayout.addWidget( title )
        self.__widgets.append( title )

        # List of files
        simTable = QTreeWidget( self.bodyWidget )
        simTable.setAlternatingRowColors( True )
        simTable.setRootIsDecorated( False )
        simTable.setItemsExpandable( False )
        simTable.setSortingEnabled( False )
        simTable.setItemDelegate( NoOutlineHeightDelegate( 4 ) )
        simTable.setUniformRowHeights( True )
        simTable.itemActivated.connect( self.__similarityActivated )
        simTable.setHeaderLabels( [ "File name", "Line" ] )

        for item in similarity.files:
            values = [ item[ 0 ], str( item[ 1 ] ) ]
            simTable.addTopLevelItem( QTreeWidgetItem( values )  )

        # Resizing
        simTable.header().resizeSections( QHeaderView.ResizeToContents )
        simTable.header().setStretchLastSection( True )

        # Height
        self.__setTableHeight( simTable )

        self.__vLayout.addWidget( simTable )
        self.__widgets.append( simTable )

        # The fragment itself
        if len( similarity.fragment ) > 10:
            # Take first 9 lines
            text = "\n".join( similarity.fragment[ : 9 ] ) + "\n ..."
            toolTip = "\n".join( similarity.fragment )
        else:
            text = "\n".join( similarity.fragment )
            toolTip = ""
        fragmentLabel = QLabel( "<pre>" + self.__htmlEncode( text ) + "</pre>" )
        if toolTip != "":
            fragmentLabel.setToolTip( "<pre>" + self.__htmlEncode( toolTip ) +
                                      "</pre>" )
        palette = fragmentLabel.palette()
        palette.setColor( QPalette.Background, QColor( 250, 250, 175 ) )
        palette.setColor( QPalette.Foreground, QColor( 0, 0, 0 ) )
        fragmentLabel.setPalette( palette )
        fragmentLabel.setFrameShape( QFrame.StyledPanel )
        fragmentLabel.setAutoFillBackground( True )

        labelFont = fragmentLabel.font()
        labelFont.setFamily( GlobalData().skin.baseMonoFontFace )
        fragmentLabel.setFont( labelFont )

        self.__vLayout.addWidget( fragmentLabel )
        self.__widgets.append( fragmentLabel )
        return

    @staticmethod
    def __htmlEncode( string ):
        " Encodes HTML "
        return string.replace( "&", "&amp;" ) \
                     .replace( ">", "&gt;" ) \
                     .replace( "<", "&lt;" )

    def __addSectionSpacer( self ):
        " Adds a fixed height spacer to the VBox layout "
        spacer = QWidget()
        spacer.setFixedHeight( 10 )
        self.__vLayout.addWidget( spacer )
        self.__widgets.append( spacer )
        return

    def __addGenericTable( self, table ):
        " Adds a generic table to the report "

        theTable = QTreeWidget( self.bodyWidget )
        theTable.setAlternatingRowColors( True )
        theTable.setRootIsDecorated( False )
        theTable.setItemsExpandable( False )
        theTable.setSortingEnabled( False )
        theTable.setItemDelegate( NoOutlineHeightDelegate( 4 ) )
        theTable.setUniformRowHeights( True )

        headerLabels = []
        for index in range( 0, len( table.header ) ):
            headerLabels.append( table.header[ index ] )
        theTable.setHeaderLabels( headerLabels )

        for item in table.body:
            row = []
            for index in range( 0, len( table.header ) ):
                row.append( item[ index ] )
            theTable.addTopLevelItem( QTreeWidgetItem( row ) )

        theTable.setFocusPolicy( Qt.NoFocus )

        # Resizing
        theTable.header().resizeSections( QHeaderView.ResizeToContents )
        theTable.header().setStretchLastSection( True )

        # Height
        self.__setTableHeight( theTable )

        self.__vLayout.addWidget( theTable )
        self.__widgets.append( theTable )
        return

    def __addGenericTableTitle( self, table ):
        " Adds a generic table title "
        tableTitle = QLabel( table.title )
        tableTitle.setFont( self.__headerFont )

        self.__vLayout.addWidget( tableTitle )
        self.__widgets.append( tableTitle )
        return

    def __updateTooltip( self ):
        " Generates a signal with appropriate string message "
        if not self.__reportShown:
            tooltip = "No results available"
        elif self.__reportOption == self.DirectoryFiles:
            tooltip = "Report generated for directory: " + \
                      self.__reportFileName
        elif self.__reportOption == self.ProjectFiles:
            tooltip = "Report generated for the whole project"
        elif self.__reportOption == self.SingleFile:
            tooltip = "Report generated for file: " + self.__reportFileName
        elif self.__reportOption == self.SingleBuffer:
            tooltip = "Report generated for unsaved file: " + \
                      self.__reportFileName
        else:
            tooltip = ""
        self.updatePylintTooltip.emit( tooltip )
        return

    def showReport( self, lint, reportOption, fileName, uuid ):
        " Shows the pylint results "
        self.__removeAll()
        self.__noneLabel.hide()

        self.__report = lint
        self.__reportUUID = uuid
        self.__reportFileName = fileName
        self.__reportOption = reportOption

        showFileName = self.__shouldShowFileName( lint.errorMessages )

        scoreLabel = self.__createScoreLabel( lint.score, lint.previousScore,
                                              showFileName, fileName )
        self.__vLayout.addWidget( scoreLabel )
        self.__widgets.append( scoreLabel )

        if len( lint.errorMessages ) > 0:
            self.__addSectionSpacer()
            self.__addErrorsTable( lint.errorMessages, showFileName )

        index = 0
        for similarity in lint.similarities:
            self.__addSectionSpacer()
            self.__addSimilarity( similarity, "Similarity #" + str( index ) )
            index += 1

        for table in lint.tables:
            self.__addSectionSpacer()
            self.__addGenericTableTitle( table )
            self.__addGenericTable( table )

        self.bodyWidget.show()
        self.bodyWidget.ensureVisible( 0, 0, 0, 0 )
        self.__reportShown = True
        self.__updateButtonsStatus()
        self.__updateTooltip()

        # It helps, but why do I have flickering?
        QApplication.processEvents()
        self.__resizeBodyFrame()
        return

    def __errorActivated( self, item, column ):
        " Handles the double click (or Enter) on the item "

        linePos = str( item.text( 1 ) )
        if ":" in linePos:
            parts = linePos.split( ":" )
            lineNumber = int( parts[ 0 ] )
            pos = int( parts[ 1 ] )
        else:
            lineNumber = int( linePos )
            pos = 0

        if self.__reportOption in [ self.SingleFile, self.DirectoryFiles,
                                    self.ProjectFiles ]:
            fileName = str( item.text( 0 ) )
        else:
            # SingleBuffer
            if self.__reportFileName != "":
                if os.path.isabs( self.__reportFileName ):
                    fileName = self.__reportFileName
                else:
                    # Could be unsaved buffer, so try to search by the
                    mainWindow = GlobalData().mainWindow
                    widget = mainWindow.getWidgetByUUID( self.__reportUUID )
                    if widget is None:
                        logging.error( "The unsaved buffer has been closed" )
                        return
                    # The widget was found, so jump to the required
                    editor = widget.getEditor()
                    editor.gotoLine( lineNumber, pos )
                    editor.setFocus()
                    return

        GlobalData().mainWindow.openFile( fileName, lineNumber, pos )
        return

    def __resizeBodyFrame( self ):
        " Resizing the frame to occupy all available width "
        size = self.bodyWidget.maximumViewportSize()
        self.__bodyFrame.setMinimumWidth( size.width() - 16 )
        self.__bodyFrame.setMinimumHeight( size.height() )
        return

    def showEvent( self, showEv = None ):
        " Called when the widget is shown "
        self.__resizeBodyFrame()
        return

    def resizeEvent( self, resizeEv = None ):
        " Called when the main window gets resized "
        self.__resizeBodyFrame()
        return

    def onFileUpdated( self, fileName, uuid ):
        " Called when a buffer is saved or saved as "

        if not self.__reportShown:
            return
        if self.__reportUUID != uuid:
            return

        # Currently shown report is for the saved buffer
        # File name is expected being absolute
        self.__reportFileName = fileName
        self.updatePylintTooltip.emit( "Report generated for buffer saved as " +
                                       fileName )
        return

    def __similarityActivated( self, item, column ):
        " Triggered when a similarity is activated "
        fileName = str( item.text( 0 ) )
        lineNumber = int( item.text( 1 ) )
        GlobalData().mainWindow.openFile( fileName, lineNumber )
        return
Example #45
0
    def __init__(self, argv):
        QMainWindow.__init__(self)
        self.setWindowTitle("SimSo: Real-Time Scheduling Simulator")

        # Possible actions:
        style = qApp.style()

        # New
        self._newAction = QAction(
            style.standardIcon(QStyle.SP_FileDialogNewFolder), '&New', None)
        self._newAction.setShortcut(Qt.CTRL + Qt.Key_N)
        self._newAction.triggered.connect(self.fileNew)

        # Open
        self._openAction = QAction(
            style.standardIcon(QStyle.SP_DialogOpenButton), '&Open', None)
        self._openAction.setShortcut(Qt.CTRL + Qt.Key_O)
        self._openAction.triggered.connect(self.fileOpen)

        # Save
        self._saveAction = QAction(
            style.standardIcon(QStyle.SP_DialogSaveButton), '&Save', None)
        self._saveAction.setShortcut(Qt.CTRL + Qt.Key_S)
        self._saveAction.triggered.connect(self.fileSave)

        # Save As
        self._saveAsAction = QAction(
            style.standardIcon(QStyle.SP_DialogSaveButton), 'Save &As', None)
        self._saveAsAction.setShortcut(Qt.CTRL + Qt.SHIFT + Qt.Key_S)
        self._saveAsAction.triggered.connect(self.fileSaveAs)

        # Run
        self._runAction = QAction(style.standardIcon(QStyle.SP_MediaPlay),
                                  '&Run', None)
        self._runAction.setShortcut(Qt.CTRL + Qt.Key_R)
        self._runAction.triggered.connect(self.fileRun)

        # Show Model data
        self._modelAction = QAction('&Model data', None)
        self._modelAction.setShortcut(Qt.CTRL + Qt.Key_M)
        #self._ganttAction.setCheckable(True)
        self._modelAction.triggered.connect(self.showModelWindow)

        # Show Gantt
        self._ganttAction = QAction('&Gantt', None)
        self._ganttAction.setShortcut(Qt.CTRL + Qt.Key_G)
        self._ganttAction.setEnabled(False)
        #self._ganttAction.setCheckable(True)
        self._ganttAction.triggered.connect(self.showGantt)

        # Show Results
        self._metricsAction = QAction('&Results', None)
        self._metricsAction.setShortcut(Qt.CTRL + Qt.Key_I)
        self._metricsAction.setEnabled(False)
        #self._metricsAction.setCheckable(True)
        self._metricsAction.triggered.connect(self.showResults)

        # Show Doc
        self._docAction = QAction('&Documentation', None)
        self._docAction.triggered.connect(self.showDocumentation)

        self._aboutAction = QAction('&About SimSo', None)
        self._aboutAction.triggered.connect(self.showAbout)

        # Recent files
        self._recentFileActions = []
        for i in range(5):
            act = QAction(self)
            act.setVisible(False)
            act.triggered.connect(self.openRecentFile)
            self._recentFileActions.append(act)

        # File Menu:
        file_menu = QMenu('&File', self)
        file_menu.addAction(self._newAction)
        file_menu.addAction(self._openAction)
        file_menu.addAction(self._saveAction)
        file_menu.addAction(self._saveAsAction)
        file_menu.addAction(self._runAction)
        file_menu.addSeparator()
        for act in self._recentFileActions:
            file_menu.addAction(act)
        file_menu.addSeparator()
        file_menu.addAction('&Quit', self.fileQuit, Qt.CTRL + Qt.Key_Q)
        self.updateRecentFileActions()

        # View Menu:
        view_menu = QMenu('&View', self)
        view_menu.addAction(self._modelAction)
        view_menu.addAction(self._ganttAction)
        view_menu.addAction(self._metricsAction)

        # Help Menu:
        help_menu = QMenu('&Help', self)
        help_menu.addAction(self._docAction)
        help_menu.addAction(self._aboutAction)

        # Add menus to menuBar:
        self.menuBar().addMenu(file_menu)
        self.menuBar().addMenu(view_menu)
        self.menuBar().addMenu(help_menu)

        # ToolBar:
        self.toolBar = QToolBar("Main ToolBar")
        self.addToolBar(self.toolBar)
        self.toolBar.addAction(self._newAction)
        self.toolBar.addAction(self._openAction)
        self.toolBar.addAction(self._saveAction)
        self.toolBar.addAction(self._runAction)
        self.toolBar.addAction(self._ganttAction)
        self.toolBar.addAction(self._metricsAction)

        # Tab:
        self.main_tab = QTabWidget()
        self.main_tab.setTabsClosable(True)
        self.main_tab.setMovable(True)
        self.main_tab.tabCloseRequested.connect(self.tabCloseRequested)
        self.main_tab.currentChanged.connect(self.tabChanged)
        self.setCentralWidget(self.main_tab)

        # Init statusBar:
        self.statusBar().showMessage("", 2000)

        self._documentation = None

        if argv:
            for arg in argv:
                try:
                    self.open_file(arg)
                except Exception as e:
                    print(e)
        else:
            self.fileNew()
Example #46
0
class MainUI(remembering.RememberingMainWindow):
    """Main UI."""

    _programs_file = os.path.join(multiplatform.data_dir, 'encuentro.data')

    def __init__(self, version, app_quit):
        super(MainUI, self).__init__()
        self.app_quit = app_quit
        self.finished = False
        self.version = version
        self.setWindowTitle('Encuentro')

        self.programs_data = data.ProgramsData(self, self._programs_file)
        self._touch_config()

        self.downloaders = {}
        for downtype, dloader_class in all_downloaders.iteritems():
            self.downloaders[downtype] = dloader_class()

        # finish all gui stuff
        self.big_panel = central_panel.BigPanel(self)
        self.episodes_list = self.big_panel.episodes
        self.episodes_download = self.big_panel.downloads_widget
        self.setCentralWidget(self.big_panel)

        # the setting of menubar should be almost in the end, because it may
        # trigger the wizard, which needs big_panel and etc.
        self.action_play = self.action_download = None
        self.filter_line = self.filter_cbox = self.needsomething_alert = None
        self._menubar()

        systray.show(self)

        if config.get('autorefresh'):
            ue = update.UpdateEpisodes(self)
            ue.background()
        else:
            # refresh data if never done before or if last
            # update was 7 days ago
            last_refresh = config.get('autorefresh_last_time')
            if last_refresh is None or (
                    dt.datetime.now() - last_refresh > dt.timedelta(7)):
                ue = update.UpdateEpisodes(self)
                ue.background()

        self.show()

        self.episodes_download.load_pending()
        logger.debug("Main UI started ok")

    def _touch_config(self):
        """Do some config processing."""
        # log the config, but without user and pass
        safecfg = config.sanitized_config()
        logger.debug("Configuration loaded: %s", safecfg)

        # we have a default for download dir
        if not config.get('downloaddir'):
            config['downloaddir'] = multiplatform.get_download_dir()

        # maybe clean some config
        if self.programs_data.reset_config_from_migration:
            config['user'] = ''
            config['password'] = ''
            config.pop('cols_width', None)
            config.pop('cols_order', None)
            config.pop('selected_row', None)

    def have_config(self):
        """Return if some config is needed."""
        return config.get('user') and config.get('password')

    def have_metadata(self):
        """Return if metadata is needed."""
        return bool(self.programs_data)

    def _menubar(self):
        """Set up the menu bar."""
        menubar = self.menuBar()

        # applications menu
        menu_appl = menubar.addMenu(u'&Aplicación')

        icon = self.style().standardIcon(QStyle.SP_BrowserReload)
        action_reload = QAction(icon, '&Refrescar', self)
        action_reload.setShortcut('Ctrl+R')
        action_reload.setToolTip(u'Recarga la lista de programas')
        action_reload.triggered.connect(self.refresh_episodes)
        menu_appl.addAction(action_reload)

        icon = self.style().standardIcon(QStyle.SP_FileDialogDetailedView)
        action_preferences = QAction(icon, u'&Preferencias', self)
        action_preferences.triggered.connect(self.open_preferences)
        action_preferences.setToolTip(
            u'Configurar distintos parámetros del programa')
        menu_appl.addAction(action_preferences)

        menu_appl.addSeparator()

        icon = self.style().standardIcon(QStyle.SP_MessageBoxInformation)
        _act = QAction(icon, '&Acerca de', self)
        _act.triggered.connect(self.open_about_dialog)
        _act.setToolTip(u'Muestra información de la aplicación')
        menu_appl.addAction(_act)

        icon = self.style().standardIcon(QStyle.SP_DialogCloseButton)
        _act = QAction(icon, '&Salir', self)
        _act.setShortcut('Ctrl+Q')
        _act.setToolTip(u'Sale de la aplicación')
        _act.triggered.connect(self.on_close)
        menu_appl.addAction(_act)

        # program menu
        menu_prog = menubar.addMenu(u'&Programa')

        icon = self.style().standardIcon(QStyle.SP_ArrowDown)
        self.action_download = QAction(icon, '&Descargar', self)
        self.action_download.setShortcut('Ctrl+D')
        self.action_download.setEnabled(False)
        self.action_download.setToolTip(TTIP_DOWNLOAD_D)
        self.action_download.triggered.connect(self.download_episode)
        menu_prog.addAction(self.action_download)

        icon = self.style().standardIcon(QStyle.SP_MediaPlay)
        self.action_play = QAction(icon, '&Reproducir', self)
        self.action_play.setEnabled(False)
        self.action_play.setToolTip(TTIP_PLAY_D)
        self.action_play.triggered.connect(self.on_play_action)
        menu_prog.addAction(self.action_play)

        # toolbar for buttons
        toolbar = self.addToolBar('main')
        toolbar.addAction(self.action_download)
        toolbar.addAction(self.action_play)
        toolbar.addSeparator()
        toolbar.addAction(action_reload)
        toolbar.addAction(action_preferences)

        # filter text and button, to the right
        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
        toolbar.addWidget(spacer)
        toolbar.addWidget(QLabel(u"Filtro: "))
        self.filter_line = QLineEdit()
        self.filter_line.setMaximumWidth(150)
        self.filter_line.textChanged.connect(self.on_filter_changed)
        toolbar.addWidget(self.filter_line)
        self.filter_cbox = QCheckBox(u"Sólo descargados")
        self.filter_cbox.stateChanged.connect(self.on_filter_changed)
        toolbar.addWidget(self.filter_cbox)

        # if needed, a warning that stuff needs to be configured
        icon = self.style().standardIcon(QStyle.SP_MessageBoxWarning)
        m = u"Necesita configurar algo; haga click aquí para abrir el wizard"
        self.needsomething_alert = QAction(icon, m, self)
        self.needsomething_alert.triggered.connect(self._start_wizard)
        toolbar.addAction(self.needsomething_alert)
        if not config.get('nowizard'):
            self._start_wizard()
        self._review_need_something_indicator()

    def _start_wizard(self, _=None):
        """Start the wizard if needed."""
        if not self.have_config() or not self.have_metadata():
            dlg = wizard.WizardDialog(self)
            dlg.exec_()
        self._review_need_something_indicator()

    def on_filter_changed(self, _):
        """The filter text has changed, apply it in the episodes list."""
        text = self.filter_line.text()
        cbox = self.filter_cbox.checkState()
        self.episodes_list.set_filter(text, cbox)

        # after applying filter, nothing is selected, so check buttons
        # (easiest way to clean them all)
        self.check_download_play_buttons()

    def _review_need_something_indicator(self):
        """Hide/show/enable/disable different indicators if need sth."""
        needsomething = bool(not self.have_config() or
                             not self.have_metadata())
        self.needsomething_alert.setVisible(needsomething)

    def shutdown(self):
        """Stop everything and quit.

        This shutdown con be called at any time, even on init, so we have
        extra precautions about which attributes we have.
        """
        signal.emit('save_state')
        config.save()
        self.finished = True

        programs_data = getattr(self, 'programs_data', None)
        if programs_data is not None:
            programs_data.save()

        downloaders = getattr(self, 'downloaders', {})
        for downloader in downloaders.itervalues():
            downloader.shutdown()

        # bye bye
        self.app_quit()

    def on_close(self, _):
        """Close signal."""
        if self._should_close():
            self.shutdown()

    def closeEvent(self, event):
        """All is being closed."""
        if self._should_close():
            self.shutdown()
        else:
            event.ignore()

    def _should_close(self):
        """Still time to decide if want to close or not."""
        logger.info("Attempt to close the program")
        pending = self.episodes_download.pending()
        if not pending:
            # all fine, save all and quit
            logger.info("Saving states and quitting")
            return True
        logger.debug("Still %d active downloads when trying to quit", pending)

        # stuff pending
        m = (u"Hay programas todavía en proceso de descarga!\n"
             u"¿Seguro quiere salir del programa?")
        QMB = QMessageBox
        dlg = QMB(u"Guarda!", m, QMB.Question, QMB.Yes, QMB.No, QMB.NoButton)
        opt = dlg.exec_()
        if opt != QMB.Yes:
            logger.info("Quit cancelled")
            return False

        # quit anyway, put all downloading and pending episodes to none
        logger.info("Fixing episodes, saving state and exiting")
        for program in self.programs_data.values():
            state = program.state
            if state == Status.waiting or state == Status.downloading:
                program.state = Status.none
        return True

    def show_message(self, err_type, text):
        """Show different messages to the user."""
        if self.finished:
            logger.debug("Ignoring message: %r", text)
            return
        logger.debug("Showing a message: %r", text)

        # error text can be produced by windows, try to to sanitize it
        if isinstance(text, str):
            try:
                text = text.decode("utf8")
            except UnicodeDecodeError:
                try:
                    text = text.decode("latin1")
                except UnicodeDecodeError:
                    text = repr(text)

        QMB = QMessageBox
        dlg = QMB(u"Atención: " + err_type, text, QMB.Warning,
                  QMB.Ok, QMB.NoButton, QMB.NoButton)
        dlg.exec_()

    def refresh_episodes(self, _=None):
        """Update and refresh episodes."""
        ue = update.UpdateEpisodes(self)
        ue.interactive()

    def download_episode(self, _=None):
        """Download the episode(s)."""
        episode_ids = self.episodes_list.selected_items()
        for episode_id in episode_ids:
            episode = self.programs_data[episode_id]
            self.queue_download(episode)

    @defer.inline_callbacks
    def queue_download(self, episode):
        """User indicated to download something."""
        logger.debug("Download requested of %s", episode)
        if episode.state != Status.none:
            logger.debug("Download denied, episode %s is not in downloadeable "
                         "state.", episode.episode_id)
            return

        # queue
        self.episodes_download.append(episode)
        self.episodes_list.episode_info.update(episode)
        self.check_download_play_buttons()
        if self.episodes_download.downloading:
            return

        logger.debug("Downloads: starting")
        while self.episodes_download.pending():
            episode = self.episodes_download.prepare()
            try:
                filename, episode = yield self._episode_download(episode)
            except CancelledError:
                logger.debug("Got a CancelledError!")
                self.episodes_download.end(error=u"Cancelado")
            except BadCredentialsError:
                logger.debug("Bad credentials error!")
                msg = (u"Error con las credenciales: hay que configurar "
                       u"usuario y clave correctos")
                self.show_message('BadCredentialsError', msg)
                self.episodes_download.end(error=msg)
            except EncuentroError, e:
                orig_exc = e.orig_exc
                msg = "%s(%s)" % (orig_exc, e)
                err_type = e.__class__.__name__
                logger.exception("Custom Encuentro error: %s (%r)",
                                 e, orig_exc)
                notify(err_type, msg)
                self.episodes_download.end(error=u"Error: " + msg)
            except Exception, e:
                logger.exception("Unknown download error: %s (%r)", e, e)
                err_type = e.__class__.__name__
                notify(err_type, str(e))
                self.episodes_download.end(error=u"Error: " + str(e))
            else:
Example #47
0
class DisassemblerResultsWidget(QWidget, MainWindowTabWidgetBase):
    " Disassembling results widget "

    escapePressed = pyqtSignal()
    textEditorZoom = pyqtSignal(int)

    def __init__(self, scriptName, name, code, reportTime, parent=None):

        MainWindowTabWidgetBase.__init__(self)
        QWidget.__init__(self, parent)

        self.__createLayout(scriptName, name, code, reportTime)
        return

    def __createLayout(self, scriptName, name, code, reportTime):
        " Creates the toolbar and layout "

        # Buttons
        self.__printButton = QAction(PixmapCache().getIcon('printer.png'),
                                     'Print', self)
        self.__printButton.triggered.connect(self.__onPrint)
        self.__printButton.setEnabled(False)
        self.__printButton.setVisible(False)

        self.__printPreviewButton = QAction(
            PixmapCache().getIcon('printpreview.png'), 'Print preview', self)
        self.__printPreviewButton.triggered.connect(self.__onPrintPreview)
        self.__printPreviewButton.setEnabled(False)
        self.__printPreviewButton.setVisible(False)

        # Zoom buttons
        self.__zoomInButton = QAction(PixmapCache().getIcon('zoomin.png'),
                                      'Zoom in (Ctrl+=)', self)
        self.__zoomInButton.triggered.connect(self.onZoomIn)

        self.__zoomOutButton = QAction(PixmapCache().getIcon('zoomout.png'),
                                       'Zoom out (Ctrl+-)', self)
        self.__zoomOutButton.triggered.connect(self.onZoomOut)

        self.__zoomResetButton = QAction(
            PixmapCache().getIcon('zoomreset.png'), 'Zoom reset (Ctrl+0)',
            self)
        self.__zoomResetButton.triggered.connect(self.onZoomReset)

        spacer = QWidget()
        spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)

        # Toolbar
        toolbar = QToolBar(self)
        toolbar.setOrientation(Qt.Vertical)
        toolbar.setMovable(False)
        toolbar.setAllowedAreas(Qt.RightToolBarArea)
        toolbar.setIconSize(QSize(16, 16))
        toolbar.setFixedWidth(28)
        toolbar.setContentsMargins(0, 0, 0, 0)

        toolbar.addAction(self.__printPreviewButton)
        toolbar.addAction(self.__printButton)
        toolbar.addWidget(spacer)
        toolbar.addAction(self.__zoomInButton)
        toolbar.addAction(self.__zoomOutButton)
        toolbar.addAction(self.__zoomResetButton)

        summary = QLabel("<b>Script:</b> " + scriptName + "<br>"
                         "<b>Name:</b> " + name + "<br>"
                         "<b>Disassembled at:</b> " + reportTime)
        summary.setFrameStyle(QFrame.StyledPanel)
        summary.setAutoFillBackground(True)
        summaryPalette = summary.palette()
        summaryBackground = summaryPalette.color(QPalette.Background)
        summaryBackground.setRgb(min(summaryBackground.red() + 30, 255),
                                 min(summaryBackground.green() + 30, 255),
                                 min(summaryBackground.blue() + 30, 255))
        summaryPalette.setColor(QPalette.Background, summaryBackground)
        summary.setPalette(summaryPalette)

        self.__text = DisasmWidget(self)
        self.__text.setAcceptRichText(False)
        self.__text.setLineWrapMode(QTextEdit.NoWrap)
        self.__text.setFont(GlobalData().skin.nolexerFont)
        self.zoomTo(Settings().zoom)
        self.__text.setReadOnly(True)
        self.__text.setPlainText(code)

        vLayout = QVBoxLayout()
        vLayout.addWidget(summary)
        vLayout.addWidget(self.__text)

        hLayout = QHBoxLayout()
        hLayout.setContentsMargins(0, 0, 0, 0)
        hLayout.setSpacing(0)
        hLayout.addLayout(vLayout)
        hLayout.addWidget(toolbar)

        self.setLayout(hLayout)
        return

    def setFocus(self):
        " Overriden setFocus "
        self.__text.setFocus()
        return

    def __onPrint(self):
        " Triggered on the 'print' button "
        pass

    def __onPrintPreview(self):
        " Triggered on the 'print preview' button "
        pass

    def onZoomReset(self):
        " Triggered when the zoom reset button is pressed "
        zoom = Settings().zoom
        if zoom != 0:
            self.textEditorZoom.emit(0)
        return True

    def onZoomIn(self):
        " Triggered when the zoom in button is pressed "
        zoom = Settings().zoom
        if zoom < 20:
            self.textEditorZoom.emit(zoom + 1)
        return True

    def onZoomOut(self):
        " Triggered when the zoom out button is pressed "
        zoom = Settings().zoom
        if zoom > -10:
            self.textEditorZoom.emit(zoom - 1)
        return True

    def keyPressEvent(self, event):
        " Handles the key press events "
        if event.key() == Qt.Key_Escape:
            self.escapePressed.emit()
            event.accept()
        else:
            QWidget.keyPressEvent(self, event)
        return

    def zoomTo(self, zoomFactor):
        " Scales the font in accordance to the given zoom factor "
        font = QFont(GlobalData().skin.nolexerFont)
        newPointSize = font.pointSize() + zoomFactor
        font.setPointSize(newPointSize)
        self.__text.setFont(font)
        return

    # Mandatory interface part is below

    def isModified(self):
        " Tells if the file is modified "
        return False

    def getRWMode(self):
        " Tells if the file is read only "
        return "RO"

    def getType(self):
        " Tells the widget type "
        return MainWindowTabWidgetBase.DisassemblerViewer

    def getLanguage(self):
        " Tells the content language "
        return "Disassembler"

    def getFileName(self):
        " Tells what file name of the widget content "
        return "N/A"

    def setFileName(self, name):
        " Sets the file name - not applicable"
        raise Exception(
            "Setting a file name for disassembler results is not applicable")

    def getEol(self):
        " Tells the EOL style "
        return "N/A"

    def getLine(self):
        " Tells the cursor line "
        return "N/A"

    def getPos(self):
        " Tells the cursor column "
        return "N/A"

    def getEncoding(self):
        " Tells the content encoding "
        return "N/A"

    def setEncoding(self, newEncoding):
        " Sets the new encoding - not applicable for the disassembler results viewer "
        return

    def getShortName(self):
        " Tells the display name "
        return "Disassembling results"

    def setShortName(self, name):
        " Sets the display name - not applicable "
        raise Exception(
            "Setting a file name for disassembler results is not applicable")
Example #48
0
class QuickFinder(QObject):

    name = u"&Quick Finder"
    actions = None
    toolbar = None
    finders = {}

    loadingIcon = None

    def __init__(self, iface):
        QObject.__init__(self)
        self.iface = iface
        self.actions = {}
        self.finders = {}
        self.settings = MySettings()
        self.rubber = None

        self._init_finders()

        self.iface.projectRead.connect(self._reload_finders)
        self.iface.newProjectCreated.connect(self._reload_finders)

        # translation environment
        self.plugin_dir = os.path.dirname(__file__)
        locale = QSettings().value("locale/userLocale")[0:2]
        localePath = os.path.join(self.plugin_dir, 'i18n', 'quickfinder_{0}.qm'.format(locale))
        if os.path.exists(localePath):
            self.translator = QTranslator()
            self.translator.load(localePath)
            QCoreApplication.installTranslator(self.translator)

    def initGui(self):
        self.actions['showSettings'] = QAction(
            QIcon(":/plugins/quickfinder/icons/settings.svg"),
            self.tr(u"&Settings"),
            self.iface.mainWindow())
        self.actions['showSettings'].triggered.connect(self.show_settings)
        self.iface.addPluginToMenu(self.name, self.actions['showSettings'])
        self.actions['help'] = QAction(
            QIcon(":/plugins/quickfinder/icons/help.svg"),
            self.tr("Help"),
            self.iface.mainWindow())
        self.actions['help'].triggered.connect(
            lambda: QDesktopServices().openUrl(
                QUrl("http://3nids.github.io/quickfinder")))
        self.iface.addPluginToMenu(self.name, self.actions['help'])
        self._init_toolbar()
        self.rubber = QgsRubberBand(self.iface.mapCanvas())
        self.rubber.setColor(QColor(255, 255, 50, 200))
        self.rubber.setIcon(self.rubber.ICON_CIRCLE)
        self.rubber.setIconSize(15)
        self.rubber.setWidth(4)
        self.rubber.setBrushStyle(Qt.NoBrush)

    def unload(self):
        """ Unload plugin """
        for key in self.finders.keys():
            self.finders[key].close()
        for action in self.actions.itervalues():
            self.iface.removePluginMenu(self.name, action)
        if self.toolbar:
            del self.toolbar
        if self.rubber:
            self.iface.mapCanvas().scene().removeItem(self.rubber)
            del self.rubber

    def _init_toolbar(self):
        """setup the plugin toolbar."""
        self.toolbar = self.iface.addToolBar(self.name)
        self.toolbar.setObjectName('mQuickFinderToolBar')
        self.search_action = QAction(QIcon(":/plugins/quickfinder/icons/magnifier13.svg"), self.tr("Search"), self.toolbar)
        self.stop_action = QAction(QIcon(":/plugins/quickfinder/icons/wrong2.svg"), self.tr("Cancel"), self.toolbar)
        self.finder_box = FinderBox(self.finders, self.iface, self.toolbar)
        self.finder_box.search_started.connect(self.search_started)
        self.finder_box.search_finished.connect(self.search_finished)
        self.finder_box_action = self.toolbar.addWidget(self.finder_box)
        self.finder_box_action.setVisible(True)
        self.search_action.triggered.connect(self.finder_box.search)
        self.toolbar.addAction(self.search_action)
        self.stop_action.setVisible(False)
        self.stop_action.triggered.connect(self.finder_box.stop)
        self.toolbar.addAction(self.stop_action)
        self.toolbar.setVisible(True)

    def _init_finders(self):
        self.finders['geomapfish'] = GeomapfishFinder(self)
        self.finders['osm'] = OsmFinder(self)
        self.finders['project'] = ProjectFinder(self)
        for key in self.finders.keys():
            self.finders[key].message.connect(self.display_message)
        self.refresh_project()

    def _reload_finders(self):
        for key in self.finders.keys():
            self.finders[key].close()
            self.finders[key].reload()
        self.refresh_project()

    @pyqtSlot(str, QgsMessageBar.MessageLevel)
    def display_message(self, message, level):
        self.iface.messageBar().pushMessage("QuickFinder", message, level)

    def show_settings(self):
        if ConfigurationDialog().exec_():
            self._reload_finders()

    def search_started(self):
        self.search_action.setVisible(False)
        self.stop_action.setVisible(True)

    def search_finished(self):
        self.search_action.setVisible(True)
        self.stop_action.setVisible(False)

    def refresh_project(self):
        if not self.finders['project'].activated:
            return
        if not self.settings.value("refreshAuto"):
            return
        n_days = self.settings.value("refreshDelay")
        # do not ask more ofen than 3 days
        ask_limit = min(3, n_days)
        recently_asked = self.settings.value("refreshLastAsked") >= n_days_ago_iso_date(ask_limit)
        if recently_asked:
            return
        thresh_date = n_days_ago_iso_date(n_days)
        uptodate = True
        for search in self.finders['project'].searches.values():
            if search.dateEvaluated <= thresh_date:
                uptodate = False
                break
        if uptodate:
            return
        self.settings.setValue("refreshLastAsked", n_days_ago_iso_date(0))
        ret = QMessageBox(QMessageBox.Warning,
                          "Quick Finder",
                          QCoreApplication.translate("Auto Refresh", "Some searches are outdated. Do you want to refresh them ?"),
                          QMessageBox.Cancel | QMessageBox.Yes).exec_()
        if ret == QMessageBox.Yes:
            RefreshDialog(self.finders['project']).exec_()
class DateRangeSelector(QObject):
    def __init__(self, mainwindow, view):
        QObject.__init__(self)
        self.mainwindow = mainwindow
        self.view = view
        self.model = mainwindow.model.daterange_selector
        self.model.view = self
        self._setupUi()

    def _setupUi(self):
        # Create actions
        self.actionNext = QAction(tr("Next"), self)
        self.actionNext.setShortcut("Ctrl+Alt+]")
        self.actionNext.triggered.connect(self.model.select_next_date_range)
        self.actionPrevious = QAction(tr("Previous"), self)
        self.actionPrevious.setShortcut("Ctrl+Alt+[")
        self.actionPrevious.triggered.connect(self.model.select_prev_date_range)
        self.actionToday = QAction(tr("Today"), self)
        self.actionToday.setShortcut("Ctrl+Alt+T")
        self.actionToday.triggered.connect(self.model.select_today_date_range)
        self.actionChangeToMonth = QAction(tr("Month"), self)
        self.actionChangeToMonth.setShortcut("Ctrl+Alt+1")
        self.actionChangeToMonth.triggered.connect(self.model.select_month_range)
        self.actionChangeToQuarter = QAction(tr("Quarter"), self)
        self.actionChangeToQuarter.setShortcut("Ctrl+Alt+2")
        self.actionChangeToQuarter.triggered.connect(self.model.select_quarter_range)
        self.actionChangeToYear = QAction(tr("Year"), self)
        self.actionChangeToYear.setShortcut("Ctrl+Alt+3")
        self.actionChangeToYear.triggered.connect(self.model.select_year_range)
        self.actionChangeToYearToDate = QAction(tr("Year To Date"), self)
        self.actionChangeToYearToDate.setShortcut("Ctrl+Alt+4")
        self.actionChangeToYearToDate.triggered.connect(self.model.select_year_to_date_range)
        self.actionChangeToRunningYear = QAction(tr("Running Year"), self)
        self.actionChangeToRunningYear.setShortcut("Ctrl+Alt+5")
        self.actionChangeToRunningYear.triggered.connect(self.model.select_running_year_range)
        self.actionChangeToAllTransactions = QAction(tr("All Transactions"), self)
        self.actionChangeToAllTransactions.setShortcut("Ctrl+Alt+6")
        self.actionChangeToAllTransactions.triggered.connect(self.model.select_all_transactions_range)
        self.actionChangeToCustom = QAction(tr("Custom..."), self)
        self.actionChangeToCustom.setShortcut("Ctrl+Alt+7")
        self.actionChangeToCustom.triggered.connect(self.model.select_custom_date_range)
        self.actionChangeToCustom1 = QAction("Custom1", self)
        self.actionChangeToCustom1.setShortcut("Ctrl+Alt+8")
        self.actionChangeToCustom1.setVisible(False)
        self.actionChangeToCustom1.triggered.connect(self.custom1Triggered)
        self.actionChangeToCustom2 = QAction("Custom2", self)
        self.actionChangeToCustom2.setShortcut("Ctrl+Alt+9")
        self.actionChangeToCustom2.setVisible(False)
        self.actionChangeToCustom2.triggered.connect(self.custom2Triggered)
        self.actionChangeToCustom3 = QAction("Custom3", self)
        self.actionChangeToCustom3.setShortcut("Ctrl+Alt+0")
        self.actionChangeToCustom3.setVisible(False)
        self.actionChangeToCustom3.triggered.connect(self.custom3Triggered)

        # set typeButton menu
        menu = QMenu(self.view.typeButton)
        menu.addAction(self.actionChangeToMonth)
        menu.addAction(self.actionChangeToQuarter)
        menu.addAction(self.actionChangeToYear)
        menu.addAction(self.actionChangeToYearToDate)
        menu.addAction(self.actionChangeToRunningYear)
        menu.addAction(self.actionChangeToAllTransactions)
        menu.addAction(self.actionChangeToCustom)
        menu.addAction(self.actionChangeToCustom1)
        menu.addAction(self.actionChangeToCustom2)
        menu.addAction(self.actionChangeToCustom3)
        self.view.typeButton.setMenu(menu)

        # set mainwindow's date range menu
        m = self.mainwindow.menuDateRange
        m.addAction(self.actionChangeToMonth)
        m.addAction(self.actionChangeToQuarter)
        m.addAction(self.actionChangeToYear)
        m.addAction(self.actionChangeToYearToDate)
        m.addAction(self.actionChangeToRunningYear)
        m.addAction(self.actionChangeToAllTransactions)
        m.addAction(self.actionChangeToCustom)
        m.addAction(self.actionChangeToCustom1)
        m.addAction(self.actionChangeToCustom2)
        m.addAction(self.actionChangeToCustom3)
        m.addAction(self.actionPrevious)
        m.addAction(self.actionNext)
        m.addAction(self.actionToday)

        # bind prev/next button
        self.view.prevButton.clicked.connect(self.model.select_prev_date_range)
        self.view.nextButton.clicked.connect(self.model.select_next_date_range)

    #--- Event Handlers
    def custom1Triggered(self):
        self.model.select_saved_range(0)

    def custom2Triggered(self):
        self.model.select_saved_range(1)

    def custom3Triggered(self):
        self.model.select_saved_range(2)

    #--- model --> view
    def animate_backward(self):
        # I didn't find a way to have a nice fading effect like we do on the Cocoa side in Qt.
        # The animation framework seems to be mainly for the QGraphicsScene framework. Since QWidget
        # doesn't have an opacity property, we're kind of screwed. However, since the date range
        # widget now displays the dates of the current range, this animation is less important than
        # it used to be.
        pass

    def animate_forward(self):
        pass

    def refresh(self):
        self.view.typeButton.setText(self.model.display)
        canNavigateDateRange = self.model.can_navigate
        self.actionNext.setEnabled(canNavigateDateRange)
        self.actionPrevious.setEnabled(canNavigateDateRange)
        self.actionToday.setEnabled(canNavigateDateRange)
        self.view.prevButton.setEnabled(canNavigateDateRange)
        self.view.nextButton.setEnabled(canNavigateDateRange)

    def refresh_custom_ranges(self):
        customActions = [
            self.actionChangeToCustom1,
            self.actionChangeToCustom2,
            self.actionChangeToCustom3
        ]
        for i, name in enumerate(self.model.custom_range_names):
            action = customActions[i]
            if name is not None:
                action.setText(name)
                action.setVisible(True)
            else:
                action.setVisible(False)
Example #50
0
class QtAction(QtToolkitObject, ProxyAction):
    """ A Qt implementation of an Enaml ProxyAction.

    """
    #: A reference to the widget created by the proxy.
    widget = Typed(QAction)

    #: Cyclic notification guard. This a bitfield of multiple guards.
    _guard = Int(0)

    # FIXME Currently, the checked state of the action is lost when
    # switching from checkable to non-checkable and back again.
    #--------------------------------------------------------------------------
    # Initialization API
    #--------------------------------------------------------------------------
    def create_widget(self):
        """ Create the underlying QAction object.

        """
        self.widget = QAction(self.parent_widget())

    def init_widget(self):
        """ Initialize the underlying control.

        """
        super(QtAction, self).init_widget()
        d = self.declaration
        if d.text:
            self.set_text(d.text)
        if d.tool_tip:
            self.set_tool_tip(d.tool_tip)
        if d.status_tip:
            self.set_status_tip(d.status_tip)
        if d.icon:
            self.set_icon(d.icon)
        self.set_checkable(d.checkable)
        self.set_checked(d.checked)
        self.set_enabled(d.enabled)
        self.set_visible(d.visible)
        self.set_separator(d.separator)
        widget = self.widget
        widget.triggered.connect(self.on_triggered)
        widget.toggled.connect(self.on_toggled)

    #--------------------------------------------------------------------------
    # Signal Handlers
    #--------------------------------------------------------------------------
    def on_triggered(self, checked):
        """ The signal handler for the 'triggered' signal.

        """
        if not self._guard & CHECKED_GUARD:
            self.declaration.checked = checked
            self.declaration.triggered(checked)

    def on_toggled(self, checked):
        """ The signal handler for the 'toggled' signal.

        """
        if not self._guard & CHECKED_GUARD:
            self.declaration.checked = checked
            self.declaration.toggled(checked)

    #--------------------------------------------------------------------------
    # ProxyAction API
    #--------------------------------------------------------------------------
    def set_text(self, text):
        """ Set the text on the underlying control.

        """
        widget = self.widget
        widget.setText(text)
        parts = text.split('\t')
        if len(parts) > 1:
            shortcut = QKeySequence(parts[-1])
            widget.setShortcut(shortcut)

    def set_tool_tip(self, tool_tip):
        """ Set the tool tip on the underlying control.

        """
        self.widget.setToolTip(tool_tip)

    def set_status_tip(self, status_tip):
        """ Set the status tip on the underyling control.

        """
        self.widget.setStatusTip(status_tip)

    def set_icon(self, icon):
        """ Set the icon for the action.

        """
        if icon:
            qicon = get_cached_qicon(icon)
        else:
            qicon = QIcon()
        self.widget.setIcon(qicon)

    def set_checkable(self, checkable):
        """ Set the checkable state on the underlying control.

        """
        self.widget.setCheckable(checkable)

    def set_checked(self, checked):
        """ Set the checked state on the underlying control.

        """
        self._guard |= CHECKED_GUARD
        try:
            self.widget.setChecked(checked)
        finally:
            self._guard &= ~CHECKED_GUARD

    def set_enabled(self, enabled):
        """ Set the enabled state on the underlying control.

        """
        self.widget.setEnabled(enabled)

    def set_visible(self, visible):
        """ Set the visible state on the underlying control.

        """
        self.widget.setVisible(visible)

    def set_separator(self, separator):
        """ Set the separator state on the underlying control.

        """
        self.widget.setSeparator(separator)
Example #51
0
class FileOutlineViewer( QWidget ):
    """ The file outline viewer widget """

    def __init__( self, editorsManager, parent = None ):
        QWidget.__init__( self, parent )

        self.__editorsManager = editorsManager
        self.__mainWindow = parent
        self.__editorsManager.currentChanged.connect( self.__onTabChanged )
        self.connect( self.__editorsManager, SIGNAL( "tabClosed" ),
                      self.__onTabClosed )
        self.connect( self.__editorsManager, SIGNAL( 'bufferSavedAs' ),
                      self.__onSavedBufferAs )
        self.connect( self.__editorsManager, SIGNAL( 'fileTypeChanged' ),
                      self.__onFileTypeChanged )

        self.__outlineBrowsers = {}  # UUID -> OutlineAttributes
        self.__currentUUID = None
        self.__updateTimer = QTimer( self )
        self.__updateTimer.setSingleShot( True )
        self.__updateTimer.timeout.connect( self.__updateView )

        self.findButton = None
        self.outlineViewer = None
        self.toolbar = None
        self.__createLayout()

        self.__modifiedFormat = Settings().modifiedFormat

        # create the context menu
        self.__menu = QMenu( self )
        self.__findMenuItem = self.__menu.addAction(
                                PixmapCache().getIcon( 'findusage.png' ),
                                'Find where used', self.__findWhereUsed )
        return

    def setTooltips( self, switchOn ):
        " Sets the tooltips mode "
        for key in self.__outlineBrowsers:
            self.__outlineBrowsers[ key ].browser.setTooltips( switchOn )
        return

    def __connectOutlineBrowser( self, browser ):
        " Connects a new buffer signals "
        browser.setContextMenuPolicy( Qt.CustomContextMenu )
        browser.customContextMenuRequested.connect( self.__handleShowContextMenu )

        self.connect( browser,
                      SIGNAL( "selectionChanged" ),
                      self.__selectionChanged )
        return

    def __createLayout( self ):
        " Helper to create the viewer layout "

        # Toolbar part - buttons
        self.findButton = QAction(
                PixmapCache().getIcon( 'findusage.png' ),
                'Find where highlighted item is used', self )
        self.findButton.setVisible( False )
        self.findButton.triggered.connect( self.__findWhereUsed )
        self.showParsingErrorsButton = QAction(
                PixmapCache().getIcon( 'showparsingerrors.png' ),
                'Show lexer/parser errors', self )
        self.showParsingErrorsButton.triggered.connect( self.__showParserError )
        self.showParsingErrorsButton.setEnabled( False )

        self.toolbar = QToolBar( self )
        self.toolbar.setMovable( False )
        self.toolbar.setAllowedAreas( Qt.TopToolBarArea )
        self.toolbar.setIconSize( QSize( 16, 16 ) )
        self.toolbar.setFixedHeight( 28 )
        self.toolbar.setContentsMargins( 0, 0, 0, 0 )
        self.toolbar.addAction( self.findButton )
        self.toolbar.addAction( self.showParsingErrorsButton )

        # Prepare members for reuse
        self.__noneLabel = QLabel( "\nNot a python file" )
        self.__noneLabel.setFrameShape( QFrame.StyledPanel )
        self.__noneLabel.setAlignment( Qt.AlignHCenter )
        headerFont = self.__noneLabel.font()
        headerFont.setPointSize( headerFont.pointSize() + 2 )
        self.__noneLabel.setFont( headerFont )
        self.__noneLabel.setAutoFillBackground( True )
        noneLabelPalette = self.__noneLabel.palette()
        noneLabelPalette.setColor( QPalette.Background,
                                   GlobalData().skin.nolexerPaper )
        self.__noneLabel.setPalette( noneLabelPalette )

        self.__layout = QVBoxLayout()
        self.__layout.setContentsMargins( 0, 0, 0, 0 )
        self.__layout.setSpacing( 0 )
        self.__layout.addWidget( self.toolbar )
        self.__layout.addWidget( self.__noneLabel )

        self.setLayout( self.__layout )
        return

    def __selectionChanged( self, index ):
        " Handles the changed selection "
        if index is None:
            self.__outlineBrowsers[ self.__currentUUID ].contentItem = None
        else:
            self.__outlineBrowsers[ self.__currentUUID ].contentItem = \
                self.__outlineBrowsers[
                        self.__currentUUID ].browser.model().item( index )

        self.__updateButtons()
        return

    def __handleShowContextMenu( self, coord ):
        """ Show the context menu """

        browser = self.__outlineBrowsers[ self.__currentUUID ].browser
        index = browser.indexAt( coord )
        if not index.isValid():
            return

        # This will update the contextItem
        self.__selectionChanged( index )

        contextItem = self.__outlineBrowsers[ self.__currentUUID ].contentItem
        if contextItem is None:
            return

        self.__findMenuItem.setEnabled( self.findButton.isEnabled() )

        self.__menu.popup( QCursor.pos() )
        return

    def __goToDefinition( self ):
        " Jump to definition context menu handler "
        contextItem = self.__outlineBrowsers[ self.__currentUUID ].contentItem
        if contextItem is not None:
            self.__outlineBrowsers[
                        self.__currentUUID ].browser.openItem( contextItem )
        return

    def __findWhereUsed( self ):
        """ Find where used context menu handler """
        contextItem = self.__outlineBrowsers[ self.__currentUUID ].contentItem
        if contextItem is not None:
            GlobalData().mainWindow.findWhereUsed(
                    contextItem.getPath(),
                    contextItem.sourceObj )
        return

    def __updateButtons( self ):
        " Updates the toolbar buttons depending on what is selected "

        self.findButton.setEnabled( False )

        contextItem = self.__outlineBrowsers[ self.__currentUUID ].contentItem
        if contextItem is None:
            return

        if contextItem.itemType in [ FunctionItemType, ClassItemType,
                                     AttributeItemType, GlobalItemType ]:
            self.findButton.setEnabled( True )
        return

    def __onTabChanged( self, index ):
        " Triggered when another tab becomes active "

        # If the timer is still active that means the tab was switched before
        # the handler had a chance to work. Therefore update the previous tab
        # first if so.
        if self.__updateTimer.isActive():
            self.__updateTimer.stop()
            self.__updateView()

        # Now, switch the outline browser to the new tab
        if index == -1:
            widget = self.__editorsManager.currentWidget()
        else:
            widget = self.__editorsManager.getWidgetByIndex( index )
        if widget is None:
            if self.__currentUUID is not None:
                self.__outlineBrowsers[ self.__currentUUID ].browser.hide()
                self.__currentUUID = None
            self.__noneLabel.show()
            self.showParsingErrorsButton.setEnabled( False )
            return
        if widget.getType() not in [ MainWindowTabWidgetBase.PlainTextEditor,
                                     MainWindowTabWidgetBase.VCSAnnotateViewer ]:
            if self.__currentUUID is not None:
                self.__outlineBrowsers[ self.__currentUUID ].browser.hide()
                self.__currentUUID = None
            self.__noneLabel.show()
            self.showParsingErrorsButton.setEnabled( False )
            return

        # This is text editor, detect the file type
        if widget.getFileType() not in [ PythonFileType, Python3FileType ]:
            if self.__currentUUID is not None:
                self.__outlineBrowsers[ self.__currentUUID ].browser.hide()
                self.__currentUUID = None
            self.__noneLabel.show()
            self.showParsingErrorsButton.setEnabled( False )
            return


        # This is a python file, check if we already have the parsed info in
        # the cache
        uuid = widget.getUUID()
        if uuid in self.__outlineBrowsers:
            # We have it, hide the current and show the existed
            if self.__currentUUID is not None:
                self.__outlineBrowsers[ self.__currentUUID ].browser.hide()
                self.__currentUUID = None
            else:
                self.__noneLabel.hide()
            self.__currentUUID = uuid
            self.__outlineBrowsers[ self.__currentUUID ].browser.show()

            info = self.__outlineBrowsers[ self.__currentUUID ].info
            self.showParsingErrorsButton.setEnabled( info.isOK != True )
            return

        # It is first time we are here, create a new
        editor = widget.getEditor()
        editor.SCEN_CHANGE.connect( self.__onBufferChanged )
        editor.cursorPositionChanged.connect( self.__cursorPositionChanged )
        info = getBriefModuleInfoFromMemory( editor.text() )

        self.showParsingErrorsButton.setEnabled( info.isOK != True )

        shortFileName = widget.getShortName()
        browser = OutlineBrowser( uuid, shortFileName, info, self )
        browser.setHeaderHighlight( info.isOK != True )
        self.__connectOutlineBrowser( browser )
        self.__layout.addWidget( browser )
        if self.__currentUUID is not None:
            self.__outlineBrowsers[ self.__currentUUID ].browser.hide()
            self.__currentUUID = None
        else:
            self.__noneLabel.hide()

        self.__currentUUID = uuid
        attributes = OutlineAttributes()
        attributes.browser = browser
        attributes.contextItem = None
        attributes.info = info
        attributes.shortFileName = shortFileName
        attributes.changed = False
        self.__outlineBrowsers[ self.__currentUUID ] = attributes
        self.__outlineBrowsers[ self.__currentUUID ].browser.show()
        return

    def getCurrentUsedInfo( self ):
        " Provides the info used to show the current outline window "
        if self.__currentUUID in self.__outlineBrowsers:
            return self.__outlineBrowsers[ self.__currentUUID ].info
        return None

    def __cursorPositionChanged( self, xpos, ypos ):
        " Triggered when a cursor position is changed "
        if self.__updateTimer.isActive():
            # If a file is very large and the cursor is moved
            # straight after changes this will delay the update till
            # the real pause.
            self.__updateTimer.stop()
            self.__updateTimer.start( 1500 )
        return

    def __onBufferChanged( self ):
        " Triggered when a change in the buffer is identified "
        if self.__currentUUID is None:
            return
        widget = self.__editorsManager.getWidgetByUUID(
                                        self.__currentUUID )
        if widget is None:
            return
        if widget.getEditor().ignoreBufferChangedSignal:
            return
        if self.__mainWindow.debugMode:
            return

        self.__updateTimer.stop()
        if self.__currentUUID in self.__outlineBrowsers:
            if self.__outlineBrowsers[ self.__currentUUID ].changed == False:
                self.__outlineBrowsers[ self.__currentUUID ].changed = True
                browser = self.__outlineBrowsers[ self.__currentUUID ].browser
                fName = self.__outlineBrowsers[ self.__currentUUID ].shortFileName
                title = self.__modifiedFormat % fName
                browser.model().sourceModel().updateRootData( 0, title )
        self.__updateTimer.start( 1500 )
        return

    def __updateView( self ):
        " Updates the view when a file is changed "
        self.__updateTimer.stop()
        info = self.getCurrentBufferInfo()
        if info is None:
            return

        self.showParsingErrorsButton.setEnabled( info.isOK != True )
        browser = self.__outlineBrowsers[ self.__currentUUID ].browser
        fName = self.__outlineBrowsers[ self.__currentUUID ].shortFileName
        browser.setHeaderHighlight( info.isOK != True )

        if not info.isOK:
            title = self.__modifiedFormat % fName
            browser.model().sourceModel().updateRootData( 0, title )
            return

        browser.model().sourceModel().updateRootData( 0, fName )
        self.__outlineBrowsers[ self.__currentUUID ].changed = False

        browser.updateFileItem( browser.model().sourceModel().rootItem, info )
        self.__outlineBrowsers[ self.__currentUUID ].info = info

        return

    def getCurrentBufferInfo( self ):
        " Provides the current buffer parsed info "
        if self.__currentUUID is None:
            return None
        widget = self.__editorsManager.getWidgetByUUID( self.__currentUUID )
        if widget is None:
            return None

        editor = widget.getEditor()
        info = getBriefModuleInfoFromMemory( editor.text() )
        return info

    def __onTabClosed( self, uuid ):
        " Triggered when a tab is closed "

        if uuid in self.__outlineBrowsers:
            del self.__outlineBrowsers[ uuid ]
        return

    def __onSavedBufferAs( self, fileName, uuid ):
        " Triggered when a file is saved with a new name "

        if uuid in self.__outlineBrowsers:

            baseName = os.path.basename( fileName )
            if detectFileType( fileName ) not in [ PythonFileType,
                                                   Python3FileType ]:
                # It's not a python file anymore
                if uuid == self.__currentUUID:
                    self.__outlineBrowsers[ uuid ].browser.hide()
                    self.__noneLabel.show()
                    self.__currentUUID = None

                del self.__outlineBrowsers[ uuid ]
                self.showParsingErrorsButton.setEnabled( False )
                self.findButton.setEnabled( False )
                return

            # Still python file with a different name
            browser = self.__outlineBrowsers[ uuid ].browser
            self.__outlineBrowsers[ uuid ].shortFileName = baseName
            if self.__outlineBrowsers[ uuid ].changed:
                title = self.__modifiedFormat % baseName
            else:
                title = baseName
            browser.model().sourceModel().updateRootData( 0, title )
        return

    def __onFileTypeChanged( self, fileName, uuid, newFileType ):
        " Triggered when the current buffer file type is changed, e.g. .cgi "
        if newFileType in [ PythonFileType, Python3FileType ]:
            # The file became a python one
            if uuid not in self.__outlineBrowsers:
                self.__onTabChanged( -1 )
        else:
            if uuid in self.__outlineBrowsers:
                # It's not a python file any more
                if uuid == self.__currentUUID:
                    self.__outlineBrowsers[ uuid ].browser.hide()
                    self.__noneLabel.show()
                    self.__currentUUID = None

                del self.__outlineBrowsers[ uuid ]
                self.showParsingErrorsButton.setEnabled( False )
                self.findButton.setEnabled( False )
        return

    def __showParserError( self ):
        " Shows the parser errors window "
        if self.__currentUUID is None:
            return

        try:
            fName = self.__outlineBrowsers[ self.__currentUUID ].shortFileName

            widget = self.__editorsManager.getWidgetByUUID( self.__currentUUID )
            if widget is None:
                return

            editor = widget.getEditor()
            info = getBriefModuleInfoFromMemory( editor.text() )
            dialog = ParserErrorsDialog( fName, info )
            dialog.exec_()
        except Exception, ex:
            logging.error( str( ex ) )
        return