123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- #!/usr/bin/env python3
-
- # OctoTray Linux Qt System Tray OctoPrint client
- #
- # SettingsWindow.py
- #
- # UI for changes to application configuration.
-
- import string
- import pprint
- from PyQt5 import QtWidgets
- from PyQt5.QtWidgets import QWidget, QLabel, QVBoxLayout, QHBoxLayout, QTableWidget, QTableWidgetItem, QPushButton, QLineEdit, QGridLayout, QComboBox
- from PyQt5.QtGui import QFontDatabase, QIntValidator
- from PyQt5.QtCore import Qt
-
- class Printer(object):
- # field 'api' for actual I/O
- # field 'host' etc. for settings
-
- def __repr__(self):
- return pprint.pformat(vars(self))
-
- class SettingsWindow(QWidget):
- genericColumns = [
- ( "Hostname", "octopi.local" ),
- ( "Interface", "OctoPrint" ),
- ( "Tool Temp", "0" ),
- ( "Bed Temp", "0" ),
- ( "Jog Speed", "600" ),
- ( "Jog Length", "10" ),
- ]
-
- apiColumns = [
- ( "OctoPrint", [
- ( "API Key", "000000000_API_KEY_HERE_000000000", [] )
- ]),
- ( "Moonraker", [
- ( "Webcam", "0", [] )
- ]),
- ]
-
- def __init__(self, parent, *args, **kwargs):
- super(SettingsWindow, self).__init__(*args, **kwargs)
- self.parent = parent
-
- self.setWindowTitle(parent.name + " Settings")
- self.setWindowIcon(parent.icon)
-
- box = QVBoxLayout()
- self.setLayout(box)
-
- self.openWeb = QPushButton("&Open Web UI of selected")
- self.openWeb.clicked.connect(self.openWebUI)
- box.addWidget(self.openWeb, 0)
-
- buttons = QHBoxLayout()
- box.addLayout(buttons, 0)
-
- self.add = QPushButton("&Add Printer")
- self.add.clicked.connect(self.addPrinter)
- buttons.addWidget(self.add)
-
- self.remove = QPushButton("&Remove Printer")
- self.remove.clicked.connect(self.removePrinter)
- buttons.addWidget(self.remove)
-
- buttons2 = QHBoxLayout()
- box.addLayout(buttons2, 0)
-
- self.up = QPushButton("Move &Up")
- self.up.clicked.connect(self.moveUp)
- buttons2.addWidget(self.up)
-
- self.down = QPushButton("Move &Down")
- self.down.clicked.connect(self.moveDown)
- buttons2.addWidget(self.down)
-
- # Printer data from OctoTray settings
- self.data = self.parent.readSettings()
- self.originalData = self.data.copy()
-
- # Table of printers
- self.printerCount = len(self.data)
- self.printers = QTableWidget(self.printerCount, len(self.genericColumns))
- box.addWidget(self.printers, 1)
-
- # Populate table of printers
- for i in range(0, self.printerCount):
- p = self.data[i]
-
- # hostname in first column
- item = QTableWidgetItem(p.host)
- self.printers.setItem(i, 0, item)
-
- font = item.font()
- font.setFamily(QFontDatabase.systemFont(QFontDatabase.FixedFont).family())
- item.setFont(font)
-
- # API selection in second column
- item = QComboBox()
- for api, options in self.apiColumns:
- item.addItem(api)
- if p.apiType == api:
- item.setCurrentText(api)
- item.currentIndexChanged.connect(self.selectionChanged)
- self.printers.setCellWidget(i, 1, item)
-
- # Tool Temp in third column
- item = QTableWidgetItem(p.tempTool)
- self.printers.setItem(i, 2, item)
-
- # Bed Temp in fourth column
- item = QTableWidgetItem(p.tempBed)
- self.printers.setItem(i, 3, item)
-
- # Jog Speed in fifth column
- item = QTableWidgetItem(p.jogSpeed)
- self.printers.setItem(i, 4, item)
-
- # Jog Length in sixth column
- item = QTableWidgetItem(p.jogLength)
- self.printers.setItem(i, 5, item)
-
- self.apiColumns[0][1][0][2].append(p.key)
- self.apiColumns[1][1][0][2].append(p.webcam)
-
- # Table of settings
- self.settings = QTableWidget(1, 2)
- box.addWidget(self.settings, 1)
-
- # Callback to update settings when printers selection changes
- self.printers.itemSelectionChanged.connect(self.selectionChanged)
- self.settings.itemChanged.connect(self.settingsChanged)
-
- # Put usage hint in settings table
- self.populateDefaultSettings()
-
- # Setup tables
- self.setupTableHeaders()
-
- # Initialize empty entry when none are available
- if len(self.data) <= 0:
- self.addPrinter()
-
- def setupTableHeaders(self):
- for t, tc in [
- ( self.printers, [ i[0] for i in self.genericColumns ] ),
- ( self.settings, [ "Option", "Value" ] )
- ]:
- t.setHorizontalHeaderLabels(tc)
- t.setSizeAdjustPolicy(QtWidgets.QAbstractScrollArea.AdjustToContents)
- t.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows);
- t.resizeColumnsToContents()
-
- def settingsChanged(self, item):
- printer = self.printers.currentRow()
- if (printer < 0) or (item.column() < 1):
- return
-
- apiType = self.printers.cellWidget(printer, 1).currentText()
- if apiType == self.apiColumns[0][0]:
- self.apiColumns[0][1][item.row()][2][printer] = item.text()
- elif apiType == self.apiColumns[1][0]:
- self.apiColumns[1][1][item.row()][2][printer] = item.text()
-
- def populateDefaultSettings(self):
- self.settings.clear()
- self.settings.setRowCount(1)
- self.settings.setColumnCount(2)
-
- item = QTableWidgetItem("Select printer for")
- self.settings.setItem(0, 0, item)
- item = QTableWidgetItem("detailed settings")
- self.settings.setItem(0, 1, item)
-
- self.setupTableHeaders()
- self.settings.resizeColumnsToContents()
-
- def selectionChanged(self):
- i = self.printers.currentRow()
- apiType = self.printers.cellWidget(i, 1).currentText()
- for api, nv in self.apiColumns:
- if api == apiType:
- self.settings.clear()
- self.settings.setRowCount(len(nv))
- self.settings.setColumnCount(2)
-
- n = 0
- for name, value, data in nv:
- item = QTableWidgetItem(name)
- self.settings.setItem(n, 0, item)
- item = QTableWidgetItem(data[i])
- self.settings.setItem(n, 1, item)
- n += 1
-
- self.setupTableHeaders()
- self.settings.resizeColumnsToContents()
- return
-
- self.populateDefaultSettings()
-
- def printersToList(self):
- printers = []
- for i in range(0, self.printerCount):
- p = Printer()
-
- p.host = self.printers.item(i, 0).text()
- p.apiType = self.printers.cellWidget(i, 1).currentText()
- p.tempTool = self.printers.item(i, 2).text()
- p.tempBed = self.printers.item(i, 3).text()
- p.jogSpeed = self.printers.item(i, 4).text()
- p.jogLength = self.printers.item(i, 5).text()
- p.key = self.apiColumns[0][1][0][2][i]
- p.webcam = self.apiColumns[1][1][0][2][i]
-
- printers.append(p)
- return printers
-
- def settingsValid(self, printers):
- for p in printers:
- # p.host needs to be valid hostname or IP
- # TODO
-
- # p.apiType
- # TODO
-
- if p.apiType == self.apiColumns[0][0]:
- # p.key only for octoprint
- # p.key needs to be valid API key (hexadecimal, 32 chars)
- if (len(p.key) != 32) or not all(c in string.hexdigits for c in p.key):
- return (False, "API Key not 32-digit hexadecimal")
- elif p.apiType == self.apiColumns[1][0]:
- # p.webcam only for moonraker
- if (len(p.webcam) < 1) or (len(p.webcam) > 1) or not all(c in string.digits for c in p.webcam):
- return (False, "Webcam ID not a number from 0...9")
-
- # p.tempTool and p.tempBed need to be integer temperatures (0...999)
- for s in [ p.tempTool, p.tempBed ]:
- if s == None:
- s = "0"
- if (len(s) < 1) or (len(s) > 3) or not all(c in string.digits for c in s):
- return (False, "Temperature not a number from 0...999")
-
- js = p.jogSpeed
- if js == None:
- js = "0"
- if (len(js) < 1) or (len(js) > 3) or not all(c in string.digits for c in js) or (int(js) < 0) or (int(js) > 6000):
- return (False, "Jog Speed not a number from 0...6000")
-
- jl = p.jogLength
- if jl == None:
- jl = "0"
- if (len(jl) < 1) or (len(jl) > 3) or not all(c in string.digits for c in jl) or (int(jl) < 0) or (int(jl) > 100):
- return (False, "Jog Length not a number from 0...100")
-
- return (True, "")
-
- def printerDiffers(self, a, b):
- if (a.host != b.host) or (a.key != b.key) or (a.tempTool != b.tempTool) or (a.tempBed != b.tempBed) or (a.jogSpeed != b.jogSpeed) or (a.jogLength != b.jogLength) or (a.webcam != b.webcam):
- return True
- return False
-
- def printersDiffer(self, a, b):
- if (len(a) != len(b)):
- return True
-
- for i in range(0, len(a)):
- if self.printerDiffers(a[i], b[i]):
- return True
-
- return False
-
- def closeEvent(self, event):
- oldPrinters = self.parent.printers
- newPrinters = self.printersToList()
-
- valid, errorText = self.settingsValid(newPrinters)
- if valid == False:
- r = self.parent.showDialog(self.parent.name + " Settings Invalid", errorText + "!", "Do you want to edit it again?", True, True, False)
- if r == True:
- event.ignore()
- return
- else:
- self.parent.removeSettingsWindow()
- return
-
- if self.printersDiffer(oldPrinters, newPrinters):
- r = self.parent.showDialog(self.parent.name + " Settings Changed", "Do you want to save the new configuration?", "This will restart the application!", True, False, False)
- if r == True:
- self.parent.writeSettings(newPrinters)
- self.parent.removeSettingsWindow()
- self.parent.restartApp()
-
- self.parent.removeSettingsWindow()
-
- def addPrinter(self):
- self.printerCount += 1
- self.printers.setRowCount(self.printerCount)
- for i in range(0, len(self.genericColumns)):
- if i != 1:
- item = QTableWidgetItem(self.genericColumns[i][1])
- self.printers.setItem(self.printerCount - 1, i, item)
- if i == 0:
- font = item.font()
- font.setFamily(QFontDatabase.systemFont(QFontDatabase.FixedFont).family())
- item.setFont(font)
- else:
- item = QComboBox()
- for api, options in self.apiColumns:
- item.addItem(api)
- if self.genericColumns[i][1] == api:
- item.setCurrentText(api)
- item.currentIndexChanged.connect(self.selectionChanged)
- self.printers.setCellWidget(self.printerCount - 1, i, item)
-
- # add default values for api specific settings
- self.apiColumns[0][1][0][2].append(self.apiColumns[0][1][0][1])
- self.apiColumns[1][1][0][2].append(self.apiColumns[1][1][0][1])
-
- self.printers.resizeColumnsToContents()
- self.printers.setCurrentItem(self.printers.item(self.printerCount - 1, 0))
-
- def removePrinter(self):
- r = self.printers.currentRow()
- if (r >= 0) and (r < self.printerCount):
- self.printerCount -= 1
- self.printers.removeRow(r)
- self.printers.setCurrentItem(self.printers.item(min(r, self.printerCount - 1), 0))
-
- # also remove values for api specific settings
- del self.apiColumns[0][1][0][2][r]
- del self.apiColumns[1][1][0][2][r]
-
- def moveUp(self):
- i = self.printers.currentRow()
- if i <= 0:
- return
-
- for c in range(0, self.printers.columnCount()):
- if c != 1:
- a = self.printers.takeItem(i, c)
- b = self.printers.takeItem(i - 1, c)
- self.printers.setItem(i, c, b)
- self.printers.setItem(i - 1, c, a)
- else:
- a = self.printers.cellWidget(i, c).currentText()
- b = self.printers.cellWidget(i - 1, c).currentText()
- self.printers.cellWidget(i, c).setCurrentText(b)
- self.printers.cellWidget(i - 1, c).setCurrentText(a)
-
- # also move values for api specific settings
- for v in [ self.apiColumns[0][1][0][2], self.apiColumns[1][1][0][2] ]:
- a = v[i]
- b = v[i - 1]
- v[i] = b
- v[i - 1] = a
-
- self.printers.setCurrentItem(self.printers.item(i - 1, 0))
-
- def moveDown(self):
- i = self.printers.currentRow()
- if i >= (self.printerCount - 1):
- return
-
- for c in range(0, self.printers.columnCount()):
- if c != 1:
- a = self.printers.takeItem(i, c)
- b = self.printers.takeItem(i + 1, c)
- self.printers.setItem(i, c, b)
- self.printers.setItem(i + 1, c, a)
- else:
- a = self.printers.cellWidget(i, c).currentText()
- b = self.printers.cellWidget(i + 1, c).currentText()
- self.printers.cellWidget(i, c).setCurrentText(b)
- self.printers.cellWidget(i + 1, c).setCurrentText(a)
-
- # also move values for api specific settings
- for v in [ self.apiColumns[0][1][0][2], self.apiColumns[1][1][0][2] ]:
- a = v[i]
- b = v[i + 1]
- v[i] = b
- v[i + 1] = a
-
- self.printers.setCurrentItem(self.printers.item(i + 1, 0))
-
- def openWebUI(self):
- host = self.printers.item(self.printers.currentRow(), 0).text()
- self.parent.openBrowser(host)
|