Exemple #1
0
class StatusCell(CellItem):
    def __init__(self, controller, parent=None, state=None):
        CellItem.__init__(self, controller, parent, state)
        self.label = QLabel('%s' % self.state)
        self.label.setAlignment(Qt.AlignCenter)
        self.widgets.append(self.label)
        self.set_layout()
        self.setMinimumWidth(20)
Exemple #2
0
    def addExperiment(self):
        textLabel = QLabel('Experiment:')
        textLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        textLabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        experimentLabel = QLabel('Unknown')
        experimentLabel.setSizePolicy(QSizePolicy.Expanding,
                                      QSizePolicy.Preferred)
        self.toolBarMain.addWidget(textLabel)
        self.toolBarMain.addWidget(experimentLabel)

        # if INSTRUMENT is defined add the logo/name of the instrument
        experiment = os.getenv('EXPERIMENT')
        if experiment:
            experimentLabel.setText(experiment)
Exemple #3
0
 def __init__(self, title, xlabel, ylabel, name='unknown', parent=None,
              **kwds):
     QWidget.__init__(self, parent)
     self.name = name
     parent.setLayout(QVBoxLayout())
     self.plot = MiniPlot(xlabel, ylabel, self, color1=COLOR_BLACK,
                          color2=COLOR_RED)
     titleLabel = QLabel(title)
     titleLabel.setAlignment(Qt.AlignCenter)
     titleLabel.setStyleSheet('QLabel {font-weight: 600}')
     parent.layout().insertWidget(0, titleLabel)
     self.plot.setSizePolicy(QSizePolicy.MinimumExpanding,
                             QSizePolicy.MinimumExpanding)
     parent.layout().insertWidget(1, self.plot)
Exemple #4
0
    def addInstrument(self):
        textLabel = QLabel('Instrument:')
        textLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        textLabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        instrumentLabel = QLabel('Unknown')
        instrumentLabel.setSizePolicy(QSizePolicy.Expanding,
                                      QSizePolicy.Preferred)
        self.toolBarMain.addWidget(textLabel)
        self.toolBarMain.addWidget(instrumentLabel)

        instrument = os.getenv('INSTRUMENT')
        if instrument:
            instrument = instrument.split('.')[-1]
            logo = decolor_logo(QPixmap('resources/%s-logo.svg' % instrument),
                                Qt.white)
            if logo.isNull():
                instrumentLabel.setText(instrument.upper())
                return
            instrumentLabel.setPixmap(
                logo.scaledToHeight(self.toolBarMain.height(),
                                    Qt.SmoothTransformation))
Exemple #5
0
class PictureDisplay(NicosWidget, QWidget):
    """A display widget to show a picture."""

    designer_description = 'Widget to display a picture file'

    filepath = PropDef('filepath', str, '', 'Path to the picture that should '
                       'be displayed')
    name = PropDef('name', str, '', 'Name (caption) to be displayed above '
                   'the picture')
    refresh = PropDef('refresh', int, 0, 'Interval to check for updates '
                      'in seconds')
    height = PropDef('height', int, 0)
    width = PropDef('width', int, 0)

    def __init__(self, parent=None, designMode=False, **kwds):
        QWidget.__init__(self, parent, **kwds)
        NicosWidget.__init__(self)
        self._last_mtime = None
        self.namelabel = QLabel(self)
        self.namelabel.setAlignment(Qt.AlignHCenter)
        self.piclabel = QLabel(self)
        self.piclabel.setScaledContents(True)
        layout = QVBoxLayout()
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.piclabel, 1)
        self.setLayout(layout)

    def registerKeys(self):
        pass

    def setPicture(self):
        size = QSize(self.props['width'] * self._scale,
                     self.props['height'] * self._scale)
        if isfile(self._filePath):
            pixmap = QPixmap(self._filePath)
        else:
            pixmap = QPixmap(size)
            pixmap.fill()
        if size.isEmpty():
            self.piclabel.setPixmap(pixmap)
        else:
            self.piclabel.setPixmap(pixmap.scaled(size))
            self.piclabel.resize(self.piclabel.sizeHint())

    def updatePicture(self):
        if not isfile(self._filePath):
            return

        # on first iteration self._last_mtime is None -> always setPicture()
        mtime = getmtime(self._filePath)
        if self._last_mtime != mtime:
            self._last_mtime = mtime
            self.setPicture()

    def propertyUpdated(self, pname, value):
        NicosWidget.propertyUpdated(self, pname, value)
        if pname == 'filepath':
            self._filePath = findResource(value)
            self.setPicture()
        elif pname == 'name':
            layout = QVBoxLayout()
            if value:
                layout.addWidget(self.namelabel)
                layout.addSpacing(5)
            layout.addWidget(self.piclabel, 1)
            sip.delete(self.layout())
            self.setLayout(layout)
            self.namelabel.setText(value)
        elif pname in ('width', 'height'):
            self.setPicture()
        elif pname == 'refresh':
            if value:
                self._refreshTimer = QTimer()
                self._refreshTimer.setInterval(value * 1000)
                self._refreshTimer.timeout.connect(self.updatePicture)
                self._refreshTimer.start()
Exemple #6
0
class ValueDisplay(NicosWidget, QWidget):
    """Value display widget with two labels."""

    designer_description = 'A widget with name/value labels'
    designer_icon = ':/table'

    widgetInfo = pyqtSignal(str)

    dev = PropDef('dev', str, '', 'NICOS device name, if set, display '
                  'value of this device')
    key = PropDef('key', str, '', 'Cache key to display (without "nicos/"'
                  ' prefix), set either "dev" or this')
    statuskey = PropDef('statuskey', str, '', 'Cache key to extract status '
                        'information  for coloring value, if "dev" is '
                        'given this is set automatically')
    name = PropDef('name', str, '', 'Name of the value to display above/'
                   'left of the value; if "dev" is given this '
                   'defaults to the device name')
    unit = PropDef('unit', str, '', 'Unit of the value to display next to '
                   'the name; if "dev" is given this defaults to '
                   'the unit set in NICOS')
    format = PropDef('format', str, '', 'Python format string to use for the '
                     'value; if "dev" is given this defaults to the '
                     '"fmtstr" set in NICOS')
    maxlen = PropDef('maxlen', int, -1, 'Maximum length of the value string to '
                     'allow; defaults to no limit')
    width = PropDef('width', int, 8, 'Width of the widget in units of the '
                    'width of one character')
    istext = PropDef('istext', bool, False, 'If given, a "text" font will be '
                     'used for the value instead of the monospaced '
                     'font used for numeric values')
    showName = PropDef('showName', bool, True, 'If false, do not display the '
                       'label for the value name')
    showStatus = PropDef('showStatus', bool, True, 'If false, do not display '
                         'the device status as a color of the value text')
    showExpiration = PropDef('showExpiration', bool, True, 'If true, display '
                             'expired cache values as "n/a"')
    horizontal = PropDef('horizontal', bool, False, 'If true, display name '
                         'label left of the value instead of above it')

    def __init__(self, parent, designMode=False, colorScheme=None, **kwds):
        # keys being watched
        self._mainkeyid = None
        self._statuskeyid = None

        # other current values
        self._isfixed = ''
        # XXX could be taken from devinfo
        self._lastvalue = designMode and '1.4' or None
        self._laststatus = (OK, '')
        self._lastchange = 0
        self._mouseover = False
        self._mousetimer = None
        self._expired = True

        self._colorscheme = colorScheme or defaultColorScheme

        QWidget.__init__(self, parent, **kwds)
        NicosWidget.__init__(self)
        self._statuscolors = self._colorscheme['fore'][UNKNOWN], \
            self._colorscheme['back'][UNKNOWN]
        self._labelcolor = None

    def propertyUpdated(self, pname, value):
        if pname == 'dev':
            if value:
                self.key = value + '.value'
                self.statuskey = value + '.status'
        elif pname == 'width':
            if value < 0:
                self.reinitLayout()
            else:
                onechar = QFontMetrics(self.valueFont).width('0')
                self.valuelabel.setMinimumSize(QSize(onechar * (value + .5), 0))
        elif pname == 'istext':
            self.valuelabel.setFont(value and self.font() or self.valueFont)
            self.width = self.width
        elif pname == 'valueFont':
            self.valuelabel.setFont(self.valueFont)
            self.width = self.width  # update char width calculation
        elif pname == 'showName':
            self.namelabel.setVisible(value)
        elif pname == 'showStatus':
            if not value:
                setBothColors(self.valuelabel,
                              (self._colorscheme['fore'][UNKNOWN],
                               self._colorscheme['back'][UNKNOWN]))
        elif pname == 'horizontal':
            self.reinitLayout()
        if pname in ('dev', 'name', 'unit'):
            self.update_namelabel()
        NicosWidget.propertyUpdated(self, pname, value)

    def initUi(self):
        self.namelabel = QLabel(' ', self, textFormat=Qt.RichText)
        self.update_namelabel()

        valuelabel = SensitiveSMLabel('----', self, self._label_entered,
                                      self._label_left)
        valuelabel.setFrameShape(QFrame.Panel)
        valuelabel.setAlignment(Qt.AlignHCenter)
        valuelabel.setFrameShadow(QFrame.Sunken)
        valuelabel.setAutoFillBackground(True)
        setBothColors(valuelabel, (self._colorscheme['fore'][UNKNOWN],
                                   self._colorscheme['back'][UNKNOWN]))
        valuelabel.setLineWidth(2)
        self.valuelabel = valuelabel
        self.width = 8

        self.reinitLayout()

    def reinitLayout(self):
        # reinitialize UI after switching horizontal/vertical layout
        if self.props['horizontal']:
            new_layout = QHBoxLayout()
            new_layout.addWidget(self.namelabel)
            new_layout.addStretch()
            new_layout.addWidget(self.valuelabel)
            self.namelabel.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
        else:
            new_layout = QVBoxLayout()
            new_layout.addWidget(self.namelabel)
            tmplayout = QHBoxLayout()
            if self.width >= 0:
                tmplayout.addStretch()
            tmplayout.addWidget(self.valuelabel)
            if self.width >= 0:
                tmplayout.addStretch()
            new_layout.addLayout(tmplayout)
            self.namelabel.setAlignment(Qt.AlignHCenter)
        if self.layout():
            sip.delete(self.layout())
        new_layout.setContentsMargins(1, 1, 1, 1)  # save space
        self.setLayout(new_layout)

    def registerKeys(self):
        if self.props['dev']:
            self.registerDevice(self.props['dev'],
                                self.props['unit'], self.props['format'])
        else:
            self.registerKey(self.props['key'], self.props['statuskey'],
                             self.props['unit'], self.props['format'])

    def on_devValueChange(self, dev, value, strvalue, unitvalue, expired):
        # check expired values
        self._expired = expired
        self._lastvalue = value
        self._lastchange = currenttime()
        if self.props['maxlen'] > -1:
            self.valuelabel.setText(strvalue[:self.props['maxlen']])
        else:
            self.valuelabel.setText(strvalue)
        if self._expired:
            setBothColors(self.valuelabel, (self._colorscheme['fore'][UNKNOWN],
                                            self._colorscheme['expired']))
            if self.props['showExpiration']:
                self.valuelabel.setText(NOT_AVAILABLE)
        elif not self.props['istext']:
            setBothColors(self.valuelabel, (self._colorscheme['fore'][BUSY],
                                            self._colorscheme['back'][BUSY]))
            QTimer.singleShot(1000, self._applystatuscolor)
        else:
            self._applystatuscolor()

    def _applystatuscolor(self):
        if self._expired:
            setBothColors(self.valuelabel, (self._colorscheme['fore'][UNKNOWN],
                                            self._colorscheme['expired']))
        else:
            setBothColors(self.valuelabel, self._statuscolors)
            if self._labelcolor:
                self.namelabel.setAutoFillBackground(True)
                setBackgroundColor(self.namelabel, self._labelcolor)
            else:
                self.namelabel.setAutoFillBackground(False)

    def on_devStatusChange(self, dev, code, status, expired):
        if self.props['showStatus']:
            self._statuscolors = self._colorscheme['fore'][code], \
                self._colorscheme['back'][code]
            self._labelcolor = self._colorscheme['label'][code]
            self._laststatus = code, status
            self._applystatuscolor()

    def on_devMetaChange(self, dev, fmtstr, unit, fixed):
        self._isfixed = fixed and ' (F)'
        self.format = fmtstr
        self.unit = unit or ''

    def update_namelabel(self):
        name = self.props['name'] or self.props['dev'] or self.props['key']
        self.namelabel.setText(
            html.escape(str(name)) +
            ' <font color="#888888">%s</font><font color="#0000ff">%s</font> '
            % (html.escape(self.props['unit'].strip()), self._isfixed))

    def _label_entered(self, widget, event, from_mouse=True):
        infotext = '%s = %s' % (self.props['name'] or self.props['dev']
                                or self.props['key'], self.valuelabel.text())
        if self.props['unit'].strip():
            infotext += ' %s' % self.props['unit']
        if self.props['statuskey']:
            try:
                const, msg = self._laststatus
            except ValueError:
                const, msg = self._laststatus, ''
            infotext += ', status is %s: %s' % (statuses.get(const, '?'), msg)
        infotext += ', changed %s ago' % (
            nicedelta(currenttime() - self._lastchange))
        self.widgetInfo.emit(infotext)
        if from_mouse:
            self._mousetimer = QTimer(self, timeout=lambda:
                                      self._label_entered(widget, event, False)
                                      )
            self._mousetimer.start(1000)

    def _label_left(self, widget, event):
        if self._mousetimer:
            self._mousetimer.stop()
            self._mousetimer = None
            self.widgetInfo.emit('')
Exemple #7
0
class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        uic.loadUi(
            path.join(path.dirname(path.abspath(__file__)), 'ui',
                      'mainwindow.ui'), self)

        logging.basicConfig()
        self.log = logging.getLogger()

        setupcontroller.init(self.log)
        classparser.init(self.log)

        # dictionary of absolute path to a setup - setupWidget
        self.setupWidgets = {}
        # dictionary of absolute path to setup - dict
        # which is deviceName - deviceWidget
        self.deviceWidgets = {}

        self.labelHeader = QLabel('Select Setup or device...')
        self.labelHeader.setAlignment(Qt.AlignCenter)

        # signal/slot connections
        self.treeWidget.itemActivated.connect(self.loadSelection)
        self.treeWidget.deviceRemoved.connect(self.deviceRemovedSlot)
        self.treeWidget.deviceAdded.connect(self.deviceAddedSlot)
        self.treeWidget.newDeviceAdded.connect(self.newDeviceAddedSlot)

        # setup the menu bar
        self.actionNewFile.triggered.connect(self.treeWidget.newSetup)
        self.actionSave.triggered.connect(self.actionSaveSlot)
        self.actionSaveAs.triggered.connect(self.actionSaveAsSlot)
        self.actionExit.triggered.connect(self.close)
        self.instrumentMenu = self.menuView.addMenu('Instrument')
        self.actionShowAllInstrument = self.menuView.addAction(
            'Show all instruments')
        self.actionShowAllInstrument.triggered.connect(
            self.treeWidget.setAllInstrumentsVisible)
        self.menuView.addAction(self.dockWidget.toggleViewAction())
        self.dockWidget.toggleViewAction().setText('Show Tree')
        self.actionAboutSetupFileTool.triggered.connect(
            self.aboutSetupFileTool)
        self.actionAboutQt.triggered.connect(QApplication.aboutQt)

        self.workarea.addWidget(self.labelHeader)

        self.treeWidget.loadNicosData()
        for directory in sorted(setupcontroller.setup_directories):
            instrumentAction = self.instrumentMenu.addAction(directory)
            instrumentAction.triggered.connect(
                self.treeWidget.setInstrumentMode)
        if config.instrument and config.instrument not in ('jcns', 'demo'):
            self.treeWidget.setSingleInstrument(config.instrument)

    def loadSetup(self, setup, instrument):
        # load a previously not loaded setup's data and initialize their
        # dict in self.setupWidgets.
        # returns index of the setup's widget in self.workarea.
        setups = [
            i.name for i in setupcontroller.setup_directories[instrument]
        ]
        setupWidget = SetupWidget(setup, setups)
        setupWidget.editedSetup.connect(self.editedSetupSlot)
        self.workarea.addWidget(setupWidget)
        self.setupWidgets[setup.abspath] = setupWidget
        # initialize device widgets dictionary for this setup
        self.deviceWidgets[setup.abspath] = {}
        for deviceName, device in iteritems(setup.devices):
            deviceWidget = DeviceWidget(setupWidget)
            deviceWidget.editedDevice.connect(self.editedSetupSlot)
            deviceWidget.loadDevice(device)
            self.workarea.addWidget(deviceWidget)
            self.deviceWidgets[setup.abspath][deviceName] = deviceWidget
        return self.workarea.indexOf(setupWidget)

    def loadSelection(self, curItem, column):
        self.treeWidget.setCurrentItem(curItem)

        if curItem.type() == ItemTypes.Directory:
            self.workarea.setCurrentIndex(0)

        elif curItem.type() == ItemTypes.Setup:
            if curItem.setup.abspath in self.setupWidgets:
                # if setup was loaded previously:
                self.workarea.setCurrentIndex(
                    self.workarea.indexOf(
                        self.setupWidgets[curItem.setup.abspath]))
            else:
                # if setup hasn't been loaded before:
                i = self.loadSetup(curItem.setup, curItem.parent().text(0))
                self.workarea.setCurrentIndex(i)

        elif curItem.type() == ItemTypes.Device:
            setup = curItem.parent().setup
            if setup.abspath not in self.setupWidgets:
                # if the setup, this device belongs to, hasn't been loaded yet:
                self.loadSetup(setup, curItem.parent().parent().text(0))

            self.workarea.setCurrentIndex(
                self.workarea.indexOf(
                    self.deviceWidgets[setup.abspath][curItem.device.name]))

    def editedSetupSlot(self):
        setupItem = self.getCurrentSetupItem()
        if setupItem is None:
            return
        if not setupItem.setup.edited:
            setupItem.setText(0, '*' + setupItem.text(0))
            setupItem.setup.edited = True

    def newDeviceAddedSlot(self, deviceName, _classString):
        setupItem = self.getCurrentSetupItem()
        if setupItem.setup.abspath not in self.setupWidgets.keys():
            self.loadSetup(setupItem.setup, setupItem.parent().text(0))

        uncombinedModule = _classString.split('.')
        classname = uncombinedModule.pop()
        module = '.'.join(uncombinedModule)
        classes = inspect.getmembers(classparser.modules[module],
                                     predicate=inspect.isclass)
        _class = [_class[1] for _class in classes if _class[0] == classname][0]
        parameters = {
            key: ''
            for key in _class.parameters.keys()
            if _class.parameters[key].mandatory is True
        }
        device = setupcontroller.Device(deviceName,
                                        _classString,
                                        parameters=parameters)
        setupItem.setup.devices[deviceName] = device
        deviceWidget = DeviceWidget(self.setupWidgets[setupItem.setup.abspath])
        deviceWidget.editedDevice.connect(self.editedSetupSlot)
        deviceWidget.loadDevice(device)
        self.workarea.addWidget(deviceWidget)
        self.deviceWidgets[setupItem.setup.abspath][deviceName] = deviceWidget
        deviceItem = QTreeWidgetItem([deviceName], ItemTypes.Device)
        deviceItem.setFlags(deviceItem.flags() & ~Qt.ItemIsDropEnabled)
        deviceItem.device = setupItem.setup.devices[deviceName]
        deviceItem.setIcon(0, QIcon(path.join(getResDir(), 'device.png')))
        setupItem.insertChild(0, deviceItem)
        self.treeWidget.itemActivated.emit(deviceItem, 0)

        if not setupItem.setup.edited:
            setupItem.setText(0, '*' + setupItem.text(0))
            setupItem.setup.edited = True

    def deviceRemovedSlot(self, setupItem, deviceName):
        if setupItem.setup.abspath not in self.setupWidgets.keys():
            self.loadSetup(setupItem.setup, setupItem.parent().text(0))

        try:
            deviceWidget = self.deviceWidgets[
                setupItem.setup.abspath][deviceName]
            if self.workarea.currentWidget() == deviceWidget:
                self.workarea.setCurrentIndex(
                    self.workarea.indexOf(
                        self.setupWidgets[setupItem.setup.abspath]))
                self.treeWidget.setCurrentItem(setupItem)
                # when the device's widget was loaded, switch to the setup the
                # device belonged to.
            del self.deviceWidgets[setupItem.setup.abspath][deviceName]
        except KeyError:
            # setup was never loaded
            pass
        if not setupItem.setup.edited:
            setupItem.setText(0, '*' + setupItem.text(0))
            setupItem.setup.edited = True

    def deviceAddedSlot(self, setupItem, newDeviceName):
        if setupItem.setup.abspath not in self.setupWidgets.keys():
            self.loadSetup(setupItem.setup, setupItem.parent().text(0))
        else:
            for deviceName, device in iteritems(setupItem.setup.devices):
                if deviceName == newDeviceName:
                    deviceWidget = DeviceWidget(
                        self.setupWidgets[setupItem.setup.abspath])
                    deviceWidget.editedDevice.connect(self.editedSetupSlot)
                    deviceWidget.loadDevice(device)
                    self.workarea.addWidget(deviceWidget)
                    self.deviceWidgets[
                        setupItem.setup.abspath][deviceName] = deviceWidget
        if not setupItem.setup.edited:
            setupItem.setText(0, '*' + setupItem.text(0))
            setupItem.setup.edited = True

    def aboutSetupFileTool(self):
        QMessageBox.information(
            self, 'About SetupFileTool',
            'A tool designed to optimize editing' + ' setup files for NICOS.')

    def closeEvent(self, event):
        # Find all setups that have been edited
        setupItemsToBeSaved = []
        for item in self.treeWidget.topLevelItems:
            for index in range(item.childCount()):
                if item.child(index).setup.edited:
                    setupItemsToBeSaved.append(item.child(index))
        if setupItemsToBeSaved:
            reply = QMessageBox.question(self, 'Unsaved changes',
                                         'Do you want to save your changes?',
                                         QMessageBox.Yes, QMessageBox.No,
                                         QMessageBox.Cancel)
            if reply == QMessageBox.Yes:
                self.saveSetups(setupItemsToBeSaved)
                event.accept()
            elif reply == QMessageBox.No:
                event.accept()
            elif reply == QMessageBox.Cancel:
                event.ignore()
        else:
            event.accept()

    def getCurrentSetupItem(self):
        # if a setup is loaded, returns the item in the tree to it.
        # if a device is loaded, returns the item of the setup the device
        # belongs to.
        # returns None for directories and the manual directory.
        curItem = self.treeWidget.currentItem()
        if curItem.type() == ItemTypes.Directory:
            return None
        if curItem.type() == ItemTypes.Device:
            return curItem.parent()
        elif curItem.type() == ItemTypes.Setup:
            return curItem

    def saveSetups(self, setupsToSave):
        # setupsToSave should be a list of QTreeWidgetItems.
        # saves all the setups in that list.
        # useful when exiting the application and saving all changed setups.
        for setup in setupsToSave:
            self.save(setup)

    def actionSaveSlot(self):
        # saves the currently selected setup. Or, if a device is loaded,
        # saves the setup the device belongs to.
        if self.getCurrentSetupItem() is None:
            return
        if not self.getCurrentSetupItem().setup.edited:
            return  # setup to be saved hasn't been edited
        self.save(self.getCurrentSetupItem())

    def actionSaveAsSlot(self):
        # asks the user where to save the current setup to.
        if self.getCurrentSetupItem() is None:
            return
        filepath = QFileDialog.getSaveFileName(self, 'Save as...',
                                               getNicosDir(),
                                               'Python script (*.py)')[0]

        if filepath:
            if not str(filepath).endswith('.py'):
                filepath += '.py'
            self.save(self.getCurrentSetupItem(), filepath)

    def save(self, setupItem, setupPath=None):
        # one may provide a path different from the current file to save to.
        if not setupPath:
            setupPath = setupItem.setup.abspath

        setupData = self.setupWidgets[setupItem.setup.abspath]
        output = []
        add = output.append
        add(self.saveDescription(setupData))
        add(self.saveGroup(setupData))
        add(self.saveIncludes(setupData))
        add(self.saveExcludes(setupData))
        add(self.saveModules(setupData))
        add(self.saveSysconfig(setupData))
        add(self.saveDevices(setupItem))
        add(self.saveStartupcode(setupData))

        with open(setupPath, 'w') as outputFile:
            outputStringWithNewlines = ''.join(output)
            outputStringListWithNewLines = \
                outputStringWithNewlines.splitlines()
            output = '\n'.join(outputStringListWithNewLines) + '\n'
            output = format_setup_text(output)

            outputFile.write(output)

        if setupItem.setup.abspath == setupPath:
            # saved setup with same name as before; can unmark it
            setupItem.setText(0, setupItem.text(0)[1:])
            setupItem.setup.edited = False

    def saveDescription(self, setupData):
        output = []
        descriptionString = repr(str(setupData.lineEditDescription.text()))
        if not descriptionString:
            return ''
        output.append('description = ')
        output.append(descriptionString + '\n\n')
        return ''.join(output)

    def saveGroup(self, setupData):
        output = []
        groupString = repr(str(setupData.comboBoxGroup.currentText()))
        if not groupString:
            return ''
        output.append('group = ')
        output.append(groupString + '\n\n')
        return ''.join(output)

    def saveIncludes(self, setupData):
        output = []
        includes = []
        includeIndex = 0
        while includeIndex < setupData.listWidgetIncludes.count():
            includes.append(
                str(setupData.listWidgetIncludes.item(includeIndex).text()))
            includeIndex += 1
        if not includes:
            return ''
        output.append('includes = ')
        output.append(repr(includes) + '\n\n')
        return ''.join(output)

    def saveExcludes(self, setupData):
        output = []
        excludes = []
        excludeIndex = 0
        while excludeIndex < setupData.listWidgetExcludes.count():
            excludes.append(
                str(setupData.listWidgetExcludes.item(excludeIndex).text()))
            excludeIndex += 1
        if not excludes:
            return ''
        output.append('excludes = ')
        output.append(repr(excludes) + '\n\n')
        return ''.join(output)

    def saveModules(self, setupData):
        output = []
        modules = []
        moduleIndex = 0
        while moduleIndex < setupData.listWidgetModules.count():
            modules.append(
                str(setupData.listWidgetModules.item(moduleIndex).text()))
            moduleIndex += 1
        if not modules:
            return ''
        output.append('modules = ')
        output.append(repr(modules) + '\n\n')
        return ''.join(output)

    def saveSysconfig(self, setupData):
        output = []
        if setupData.treeWidgetSysconfig.topLevelItemCount() > 0:
            output.append('sysconfig = dict(\n')
            for key, value in iteritems(
                    setupData.treeWidgetSysconfig.getData()):
                output.append('    ' + key + ' = ' + repr(value) + ',\n')
            output.append(')\n\n')
            return ''.join(output)
        return ''

    def saveDevices(self, setupItem):
        output = []
        childIndex = 0
        deviceItems = []
        while childIndex < setupItem.childCount():
            deviceItems.append(setupItem.child(childIndex))
            childIndex += 1

        if not deviceItems:
            return ''

        output.append('devices = dict(\n')
        for name, info in iteritems(
                self.deviceWidgets[setupItem.setup.abspath]):
            output.append('    ' + name + ' = device(')
            # class string must be first parameter. Also mustn't have a key.
            output.append(repr(info.parameters['Class'].getValue()) + ',\n')
            indent = len(name) + 14
            for _, params in iteritems(info.parameters):
                # skip class as it has already been added
                if not params.param == 'Class':
                    if isinstance(params.getValue(), string_types):
                        prepend = indent * ' ' + str(params.param) + ' = '
                        if params.isUnknownValue:
                            param = str(params.getValue()) + ',\n'
                        else:
                            param = repr(str(params.getValue())) + ',\n'
                    else:
                        prepend = indent * ' ' + str(params.param) + ' = '
                        param = str(params.getValue()) + ',\n'
                    output.append(prepend + param)
            output.append((indent - 1) * ' ' + '),\n')
        output.append(')\n\n')
        return ''.join(output)

    def saveStartupcode(self, setupData):
        output = []
        startupcode = setupData.textEditStartupCode.toPlainText()
        if startupcode:
            output.append("startupcode = '''\n")
            output.append(startupcode + '\n')
            output.append("'''\n")
            return ''.join(output)
        return ''