Example #1
0
class ServiceMonitor(Applet):


  def __init__(self, parent, args=None):
    Applet.__init__(self, parent)
    self.widgets = {}      ##< [dict] Place for all widgets, so they can be addressed nicely.
    self.mainLayout = None ##< [QLayout] The layout containing all the widgets.
    

  def init(self):
    self.setHasConfigurationInterface(True)
    
    self.configDialog = ConfigDialog(self)
    self.configDialog.configurationChanged.connect(self.setupUi)
    self.passwordDialog = PasswordDialog(self.configDialog)

    # set up layout and widgets
    self.setupUi()
    

  ## Sets up all widgets in a popup which can be opened when clicking the applet.
  def setupUi(self):

    # remove old contents on successive calls
    deleteContentsRecursively(self.applet.layout())
    deleteContentsRecursively(self.mainLayout)

    # determine mode
    if self.formFactor() == Plasma.Planar:
      self.mode = 'desktop'
    elif self.configDialog.panelBehavior() == 0:
      self.mode = 'icons'
    else:
      self.mode = 'popup'

    if self.mode == 'popup':

      # QGraphicsView initialisieren, in das alles gezeichnet wird
      self.scene = QGraphicsScene()
      self.view = QGraphicsView()
      self.view.setScene(self.scene)
      self.view.setFrameStyle(QFrame.NoFrame)
      self.view.setStyleSheet('background-color: transparent;')
      self.view.setAlignment(Qt.AlignHCenter | Qt.AlignTop)
      self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
      self.view.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)

      # Layout-Container-Widget erzeugen und in die Szene einfügen
      self.container = QGraphicsWidget()
      self.mainLayout = QGraphicsGridLayout()
      self.container.setLayout(self.mainLayout)
      self.scene.addItem(self.container)
      self.mainLayout.setPreferredWidth(270)

      # Popup erzeugen
      self.popup = Plasma.Dialog()
      self.popup.setWindowFlags(Qt.Popup)
      self.popupLayout = QVBoxLayout()
      self.popup.setLayout(self.popupLayout)
      self.popupLayout.addWidget(self.view)
      self.popup.resize(250, 300)

      # Im Applet ein Icon anzeigen
      self.icon = Plasma.IconWidget(KIcon(":/panel-icon.png"), "")
      self.iconLayout = QGraphicsLinearLayout()
      self.iconLayout.addItem(self.icon)
      self.setAspectRatioMode(Plasma.ConstrainedSquare)
      self.applet.setLayout(self.iconLayout)
      self.icon.clicked.connect(self.togglePopup)

    elif self.mode in ['desktop', 'icons']:

      # Set up all widgets directly in the main applet.
      self.verticalLayout = QGraphicsLinearLayout(Qt.Vertical)
      self.applet.setLayout(self.verticalLayout)
      self.mainLayout = QGraphicsGridLayout()
      self.verticalLayout.addItem(self.mainLayout)
      self.verticalLayout.addStretch(1)
      self.applet.setAspectRatioMode(Plasma.IgnoreAspectRatio)

    # this seems to be necessary to avoid display bugs
    QTimer.singleShot(0, self.mainLayout.invalidate)

    activeServices = self.configDialog.activeServices()

    # Falls Services eingerichtet: Service-Widgets anzeigen
    if activeServices:
      for i, service in enumerate(activeServices):
        nameLabel = Plasma.Label()
        statusIcon = Plasma.IconWidget('')
        self.widgets[service.id] = { 'name': nameLabel, 'status': statusIcon }
        nameLabel.setText(u'<strong>%s</strong>' % service.name)
        nameLabel.nativeWidget().setWordWrap(False)
        statusIcon.setMinimumSize(22, 22)
        statusIcon.setMaximumSize(22, 22)
        nameLabel.setMinimumHeight(22)
        nameLabel.setMaximumHeight(22)
        self.refreshStateIcon(service)
        if self.mode == 'icons' and self.formFactor() == Plasma.Vertical:
          self.mainLayout.addItem(statusIcon, i, 0)
        elif self.mode == 'icons' and self.formFactor() == Plasma.Horizontal:
          self.mainLayout.addItem(statusIcon, 0, i)
        else:
          self.mainLayout.addItem(statusIcon, i, 0)
          self.mainLayout.addItem(nameLabel, i, 1)
        statusIcon.clicked.connect(partial(self.iconClicked, service))

    # Falls keine Services eingerichtet: Einleitungstext anzeigen
    if not activeServices:
      self.widgets['intro'] = Plasma.Label()
      if self.mode != 'icons':
        self.widgets['intro'].setText(self.tr('<b>Thank you for downloading<br/>Service Monitor!</b><br/><br/>Right click to open the<br/>settings dialog.'))
      else:
        self.widgets['intro'].setText(self.tr('Right click to add services.'))
        self.widgets['intro'].setMinimumWidth(180)
      self.widgets['intro'].setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
      self.mainLayout.addItem(self.widgets['intro'], 0, 0)

    # in icons mode, squeeze as much as possible
    if self.mode == 'icons' and self.formFactor() == Plasma.Horizontal:
      self.mainLayout.setMaximumWidth(self.mainLayout.minimumWidth())
    elif self.mode == 'icons' and self.formFactor() == Plasma.Vertical:
      self.mainLayout.setMaximumHeight(self.mainLayout.minimumHeight())
      
    # Hier ist das Layout eingerichtet - aktivieren
    self.mainLayout.activate()

    # Scrollbarer Bereich verkleinert sich nicht automatisch im Popup, also von Hand
    if hasattr(self, 'popup'):
      self.container.adjustSize()
      self.scene.setSceneRect(self.container.geometry())

    # Alle Polling-Prozesse anhalten und alte Connections trennen
    for service in self.configDialog.allServices():
      service.setPolling(False)
      try: service.runningStateChanged.disconnect()
      except: pass

    # Aktive Prozesse neu einrichten und Polling starten
    interval = self.configDialog.pollingInterval()
    sleepTime = self.configDialog.sleepTime()
    for service in activeServices:
      service.runningStateChanged.connect(partial(self.refreshStateIcon, service))
      service.wrongPassword[str].connect(partial(self.askPasswordAndRetry, service))
      service.setSleepTime(sleepTime)
      service.setErrorReporting(not self.configDialog.suppressStdout())
      service.setPolling(True, interval)


  ## Triggered on wrongPassword signal. Retries the last command.
  def askPasswordAndRetry(self, service):
    try: self.passwordDialog.newPasswordAvailable.disconnect()
    except: pass # if no slots are connected
    self.passwordDialog.setWindowTitle(service.name)
    self.passwordDialog.setCommandInfo(getattr(service, service.lastCommand()))
    self.passwordDialog.focusPasswordField()
    self.passwordDialog.setVisible(True)
    def retry(pw):
      self.passwordDialog.setVisible(False)
      QTimer.singleShot(0, partial(service.retryLastCommand, pw))
    self.passwordDialog.newPasswordAvailable[QString].connect(retry)
    

  ## Open the config dialog; called by plasma.
  def showConfigurationInterface(self):
    self.configDialog.show()


  ## Starts or stops a service corresponding to the icon clicked.
  def iconClicked(self, service):
    if service.state[0] == 'missing':
      QMessageBox.warning(None, self.tr("Error"), self.tr('Service "%1" not installed. Aborting.').arg(service.id))
      return
    if service.state[1] in ['running', 'starting']: command = "stopcommand"
    if service.state[1] in ['stopped', 'stopping']: command = "startcommand"
    service.execute(command, 'requested', self.passwordDialog.password())


  ## Updates the icon corresponding to the service argument.
  def refreshStateIcon(self, service, reason = ''):
    icon = self.configDialog.runningStateIndicator(service)
    self.widgets[service.id]['status'].setIcon(icon)
    self.widgets[service.id]['status'].setToolTip("%s\nStatus: %s" % (service.name, service.state[1]))

    # send a KNotify notification if the change wasn't issued by the user
    if self.configDialog.useKNotify() and service.state[1] in ["running", "stopped"] and reason == 'polling':
      if service.state[1] == "running":
        message = self.tr("%1 is now running.").arg(service.name)
      else:
        message = self.tr("%1 has been stopped.").arg(service.name)
      self.knotify(icon, message)


  ## The call to knotify must be made from an external script, because python allows no self-connections
  def knotify(self, icon, message):
    byteArray = QByteArray()
    buffer = QBuffer(byteArray)
    icon.pixmap(QSize(32, 32)).toImage().rgbSwapped().save(buffer, "PNG") # KNotify seems to expect a BGR file
    tmp = file('icon.png', 'w')
    tmp.write(byteArray.data())
    tmp.close()
    self.knotifyProcess = KProcess()
    self.knotifyProcess.setProgram(QStringList() << "/usr/bin/python" << ("%s/KNotify.py" % codedir) << message)
    self.knotifyProcess.start()


  ## Shows/hides popup dialog.
  @pyqtSlot()
  def togglePopup(self):
    if self.popup.isVisible():
      self.popup.animatedHide(Plasma.Direction(0))
    else:
      self.popup.move(self.popupPosition(self.popup.sizeHint()))
      self.popup.animatedShow(Plasma.Direction(0))
class ServiceMonitor(Applet):


  def __init__(self, parent, args=None):
    Applet.__init__(self, parent)

    ## [dict] Place for all widgets, so they can be addressed nicely.
    self.widgets = {}

    ## [QSignalMapper] Maps every QIcon to its corresponding service.
    self.iconMapper = None

    ## [QLayout] The layout containing all the widgets.
    self.mainLayout = None


  def init(self):

    # Konfig-Dilog einrichten
    self.setHasConfigurationInterface(True)
    self.configDialog = ConfigDialog(self)
    QObject.connect(self.configDialog, SIGNAL('configurationChanged()'), self, SLOT('setupServicesAndWidgets()'))

    # Benutzeroberfläche einrichten
    self.setupAppletUi() if self.formFactor() == Plasma.Planar else self.setupPopupUi()

    # SignalMapper für die Buttons (Icons) einrichten
    self.iconMapper = QSignalMapper()
    QObject.connect(self.iconMapper, SIGNAL('mapped(QObject*)'), self, SLOT('iconClicked(QObject*)'))

    # Widgets im Main-Layout erzeugen, Timer starten
    self.setupServicesAndWidgets()


  ## Sets up all widgets in a popup which can be opened when clicking the applet.
  def setupPopupUi(self):

    # QGraphicsView initialisieren, in das alles gezeichnet wird
    self.scene = QGraphicsScene()
    self.view = QGraphicsView()
    self.view.setScene(self.scene)
    self.view.setFrameStyle(QFrame.NoFrame)
    self.view.setStyleSheet('background-color: transparent;')
    self.view.setAlignment(Qt.AlignHCenter | Qt.AlignTop)
    self.view.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
    self.view.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)

    # Layout-Container-Widget erzeugen und in die Szene einfügen
    self.container = QGraphicsWidget()
    self.mainLayout = QGraphicsGridLayout()
    self.container.setLayout(self.mainLayout)
    self.scene.addItem(self.container)
    self.mainLayout.setPreferredWidth(270)

    # Popup erzeugen
    self.popup = Plasma.Dialog()
    self.popup.setWindowFlags(Qt.Popup)
    self.popupLayout = QVBoxLayout()
    self.popup.setLayout(self.popupLayout)
    self.popupLayout.addWidget(self.view)
    self.popup.resize(250, 300)

    # Im Applet ein Icon anzeigen
    self.icon = Plasma.IconWidget(KIcon(":/panel-icon.png"), "")
    self.iconLayout = QGraphicsLinearLayout()
    self.iconLayout.addItem(self.icon)
    self.setAspectRatioMode(Plasma.ConstrainedSquare)
    self.applet.setLayout(self.iconLayout)
    QObject.connect(self.icon, SIGNAL("clicked()"), self, SLOT('togglePopup()'))


  ## Sets up all widgets directly in the main applet.
  def setupAppletUi(self):
    self.verticalLayout = QGraphicsLinearLayout(Qt.Vertical)
    self.setLayout(self.verticalLayout)
    self.mainLayout = QGraphicsGridLayout()
    self.verticalLayout.addItem(self.mainLayout)
    self.verticalLayout.addStretch(1)
    self.applet.setAspectRatioMode(Plasma.IgnoreAspectRatio)


  ## Open the config dialog; called by plasma.
  def showConfigurationInterface(self):
    self.configDialog.show()


  ## [slot] Create all widgets inside the main layout and set up the services for monitoring.
  # This function is called as slot whenever the configuration has changed.
  @pyqtSlot()
  def setupServicesAndWidgets(self):

    # Alte Widgets löschen und Szene leeren
    while self.mainLayout.count():
      self.mainLayout.itemAt(0).graphicsItem().deleteLater()
      self.mainLayout.removeAt(0)
    self.widgets = {}

    activeServices = self.configDialog.activeServices()

    # Falls Services eingerichtet: Service-Widgets anzeigen
    if activeServices:
      for i, service in enumerate(activeServices):
        nameLabel = Plasma.Label()
        statusIcon = Plasma.IconWidget('')
        self.widgets[service.id] = { 'name': nameLabel, 'status': statusIcon }
        nameLabel.setText(u'<strong>%s</strong>' % service.name)
        nameLabel.nativeWidget().setWordWrap(False)
        statusIcon.setMinimumHeight(22)
        statusIcon.setMaximumHeight(22)
        statusIcon.setSizePolicy(QSizePolicy.Maximum, QSizePolicy.Maximum)
        self.refreshStateIcon(service)
        self.mainLayout.addItem(statusIcon, i, 0)
        self.mainLayout.addItem(nameLabel, i, 1)
        self.iconMapper.setMapping(statusIcon, service)
        QObject.connect(statusIcon, SIGNAL('clicked()'), self.iconMapper, SLOT('map()'))

    # Falls keine Services eingerichtet: Einleitungstext anzeigen
    if not activeServices:
      self.widgets['intro'] = Plasma.Label()
      self.widgets['intro'].setText(self.tr('<b>Thank you for downloading<br/>Service Monitor!</b><br/><br/>Right click to open the<br/>settings dialog.'))
      self.mainLayout.addItem(self.widgets['intro'], 0, 0)

    # Hier ist das Layout eingerichtet - aktivieren
    self.mainLayout.activate()

    # Scrollbarer Bereich verkleinert sich nicht automatisch im Popup, also von Hand
    if hasattr(self, 'popup'):
      self.container.adjustSize()
      self.scene.setSceneRect(self.container.geometry())

    # Alle Polling-Prozesse anhalten
    for service in self.configDialog.allServices():
      service.stopPolling()

    # Aktive Prozesse neu einrichten und Polling starten
    env = self.configDialog.processEnvironment()
    interval = self.configDialog.pollingInterval()
    sleepTime = self.configDialog.sleepTime()
    for service in activeServices:
      QObject.connect(service, SIGNAL('stateChanged()'), self.serviceStateChanged)
      service.setProcessEnvironment(env)
      service.setSleepTime(sleepTime)
      service.setPollingInterval(interval)
      service.startPolling()


  ## [slot] Starts or stops a service corresponding to the icon clicked.
  # @param service The service corresponding to the clicked item, provided by self.iconMapper
  @pyqtSlot('QObject*')
  def iconClicked(self, service):
    if service.state[0] == 'unavailable':
      print 'Service %s not installed. Aborting.' % service.id
      return
    elif service.state[1] in ['active',   'starting']:
      service.execStopCommand()
    elif service.state[1] in ['inactive', 'stopping']:
      service.execStartCommand()
    self.refreshStateIcon(service)


  ## [slot] Updates the icon corresponding to the service which triggered this slot.
  @pyqtSlot()
  def serviceStateChanged(self):
    service = self.sender()
    self.refreshStateIcon(service)


  ## Updates the icon corresponding to the service argument.
  def refreshStateIcon(self, service):
    icon = KIcon(':/status-%s-%s.png' % service.state)
    self.widgets[service.id]['status'].setIcon(icon)

  ## [slot] Shows/hides popup dialog.
  @pyqtSlot()
  def togglePopup(self):
    if self.popup.isVisible():
      self.popup.animatedHide(Plasma.Direction(0))
    else:
      self.popup.move(self.popupPosition(self.popup.sizeHint()))
      self.popup.animatedShow(Plasma.Direction(0))