Source code for qtpyvcp.widgets.input_widgets.tool_table
from qtpy.QtCore import Qt, Slot, Signal, Property, QModelIndex, QSortFilterProxyModel
from qtpy.QtGui import QStandardItemModel, QColor, QBrush
from qtpy.QtWidgets import QTableView, QStyledItemDelegate, QDoubleSpinBox, \
QSpinBox, QLineEdit, QMessageBox
from qtpyvcp.actions.machine_actions import issue_mdi
from qtpyvcp.utilities.logger import getLogger
from qtpyvcp.plugins import getPlugin
from qtpyvcp.utilities.settings import connectSetting, getSetting
LOG = getLogger(__name__)
[docs]class ItemDelegate(QStyledItemDelegate):
def __init__(self, columns):
super(ItemDelegate, self).__init__()
self._columns = columns
self._padding = ' ' * 2
def setColumns(self, columns):
self._columns = columns
[docs] def displayText(self, value, locale):
if type(value) == float:
return f"{value:.4f}"
if type(value) == str:
return f"{self._padding}{value}"
return f"{self._padding}{value}"
[docs] def createEditor(self, parent, option, index):
# ToDo: set dec placed for IN and MM machines
col = self._columns[index.column()]
if col == 'R':
editor = QLineEdit(parent)
editor.setFrame(False)
margins = editor.textMargins()
padding = editor.fontMetrics().width(self._padding) + 1
margins.setLeft(margins.left() + padding)
editor.setTextMargins(margins)
return editor
elif col in 'TPQ':
editor = QSpinBox(parent)
editor.setFrame(False)
editor.setAlignment(Qt.AlignCenter)
if col == 'Q':
editor.setMaximum(9)
else:
editor.setMaximum(99999)
return editor
elif col in 'XYZABCUVWD':
editor = QDoubleSpinBox(parent)
editor.setFrame(False)
editor.setAlignment(Qt.AlignCenter)
editor.setDecimals(4)
# editor.setStepType(QSpinBox.AdaptiveDecimalStepType)
editor.setProperty('stepType', 1) # stepType was added in 5.12
min_range = getSetting('offset_table.min_range').value
max_range = getSetting('offset_table.max_range').value
if min_range and max_range:
editor.setRange(min_range, max_range)
else:
editor.setRange(-1000, 1000)
return editor
elif col in 'IJ':
editor = QDoubleSpinBox(parent)
editor.setFrame(False)
editor.setAlignment(Qt.AlignCenter)
editor.setMaximum(360.0)
editor.setMinimum(0.0)
editor.setDecimals(4)
# editor.setStepType(QSpinBox.AdaptiveDecimalStepType)
editor.setProperty('stepType', 1) # stepType was added in 5.12
return editor
return None
[docs]class ToolModel(QStandardItemModel):
def __init__(self, parent=None):
super(ToolModel, self).__init__(parent)
self.status = getPlugin('status')
self.stat = self.status.stat
self.tt = getPlugin('tooltable')
self.current_tool_color = QColor(Qt.darkGreen)
self.current_tool_bg = None
self._columns = self.tt.columns
self._column_labels = self.tt.COLUMN_LABELS
self._tool_table = self.tt.getToolTable()
self.setColumnCount(self.columnCount())
self.setRowCount(1000) # (self.rowCount())
self.status.tool_in_spindle.notify(self.refreshModel)
self.tt.tool_table_changed.connect(self.updateModel)
def refreshModel(self):
# refresh model so current tool gets highlighted
self.beginResetModel()
self.endResetModel()
def updateModel(self, tool_table):
# update model with new data
self.beginResetModel()
self._tool_table = tool_table
self.endResetModel()
def setColumns(self, columns):
self._columns = columns
self.setColumnCount(len(columns))
[docs] def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
return self._column_labels[self._columns[section]]
return QStandardItemModel.headerData(self, section, orientation, role)
[docs] def data(self, index, role=Qt.DisplayRole):
if role == Qt.DisplayRole or role == Qt.EditRole:
key = self._columns[index.column()]
tnum = sorted(self._tool_table)[index.row() + 1]
return self._tool_table[tnum][key]
elif role == Qt.TextAlignmentRole:
col = self._columns[index.column()]
if col == 'R': # Remark
return Qt.AlignVCenter | Qt.AlignLeft
elif col in 'TPQ': # Integers (Tool, Pocket, Orient)
return Qt.AlignVCenter | Qt.AlignCenter
else: # All the other floats
return Qt.AlignVCenter | Qt.AlignRight
elif role == Qt.TextColorRole:
tnum = sorted(self._tool_table)[index.row() + 1]
if self.stat.tool_in_spindle == tnum:
return QBrush(self.current_tool_color)
else:
return QStandardItemModel.data(self, index, role)
elif role == Qt.BackgroundRole and self.current_tool_bg is not None:
tnum = sorted(self._tool_table)[index.row() + 1]
if self.stat.tool_in_spindle == tnum:
return QBrush(self.current_tool_bg)
else:
return QStandardItemModel.data(self, index, role)
return QStandardItemModel.data(self, index, role)
[docs] def setData(self, index, value, role):
key = self._columns[index.column()]
tnum = sorted(self._tool_table)[index.row() + 1]
self._tool_table[tnum][key] = value
return True
def removeTool(self, row):
self.beginRemoveRows(QModelIndex(), row, row)
tnum = sorted(self._tool_table)[row + 1]
del self._tool_table[tnum]
self.endRemoveRows()
return True
def addTool(self):
try:
tnum = sorted(self._tool_table)[-1] + 1
except IndexError:
tnum = 1
row = len(self._tool_table) - 1
if row == 1000:
# max 1000 tools
return False
self.beginInsertRows(QModelIndex(), row, row)
self._tool_table[tnum] = self.tt.newTool(tnum=tnum)
self.endInsertRows()
return True
[docs] def toolDataFromRow(self, row):
"""Returns dictionary of tool data"""
tnum = sorted(self._tool_table)[row + 1]
return self._tool_table[tnum]
def saveToolTable(self):
self.tt.saveToolTable(self._tool_table, self._columns)
return True
def clearToolTable(self):
self.beginRemoveRows(QModelIndex(), 0, 100)
# delete all but the spindle, which can't be deleted
self._tool_table = {0: self._tool_table[0]}
self.endRemoveRows()
return True
def loadToolTable(self):
# the tooltable plugin will emit the tool_table_changed signal
# so we don't need to do any more here
self.tt.loadToolTable()
return True
[docs]class ToolTable(QTableView):
toolSelected = Signal(int)
def __init__(self, parent=None):
super(ToolTable, self).__init__(parent)
self.clicked.connect(self.onClick)
self.tool_model = ToolModel(self)
self.item_delegate = ItemDelegate(columns=self.tool_model._columns)
self.setItemDelegate(self.item_delegate)
self.proxy_model = QSortFilterProxyModel()
self.proxy_model.setFilterKeyColumn(0)
self.proxy_model.setSourceModel(self.tool_model)
self.setModel(self.proxy_model)
# Properties
self._columns = self.tool_model._columns
self._confirm_actions = False
self._current_tool_color = QColor('sage')
self._current_tool_bg = None
# Appearance/Behaviour settings
self.setSortingEnabled(True)
self.verticalHeader().hide()
self.setAlternatingRowColors(True)
self.setSelectionBehavior(QTableView.SelectRows)
self.setSelectionMode(QTableView.SingleSelection)
self.horizontalHeader().setStretchLastSection(True)
self.horizontalHeader().setSortIndicator(0, Qt.AscendingOrder)
@Slot()
def saveToolTable(self):
if not self.confirmAction("Do you want to save changes and\n"
"load tool table into LinuxCNC?"):
return
self.tool_model.saveToolTable()
@Slot()
def loadToolTable(self):
if not self.confirmAction("Do you want to re-load the tool table?\n"
"All unsaved changes will be lost."):
return
self.tool_model.loadToolTable()
[docs] @Slot()
def deleteSelectedTool(self):
"""Delete the currently selected item"""
current_row = self.selectedRow()
if current_row == -1:
# no row selected
return
tdata = self.tool_model.toolDataFromRow(current_row)
tnum = tdata['T']
# should not delete tool if currently loaded in spindle. Warn user
if tnum == self.tool_model.stat.tool_in_spindle:
box = QMessageBox(QMessageBox.Warning,
"Can't delete current tool!",
"Tool #{} is currently loaded in the spindle.\n"
"Please remove tool from spindle and try again.".format(tnum),
QMessageBox.Ok,
parent=self)
box.show()
return False
if not self.confirmAction('Are you sure you want to delete T{tdata[T]}?\n'
'"{tdata[R]}"'.format(tdata=tdata)):
return
self.tool_model.removeTool(current_row)
[docs] @Slot()
def selectPrevious(self):
"""Select the previous item in the view."""
self.selectRow(self.selectedRow() - 1)
return True
[docs] @Slot()
def selectNext(self):
"""Select the next item in the view."""
self.selectRow(self.selectedRow() + 1)
return True
[docs] @Slot()
def clearToolTable(self, confirm=True):
"""Remove all items from the model"""
if confirm:
if not self.confirmAction("Do you want to delete the whole tool table?"):
return
self.tool_model.clearToolTable()
[docs] @Slot()
def addTool(self):
"""Appends a new item to the model"""
self.tool_model.addTool()
self.selectRow(self.tool_model.rowCount() - 1)
[docs] @Slot()
def loadSelectedTool(self):
"""Loads the currently selected tool"""
# see: https://forum.linuxcnc.org/41-guis/36042?start=50#151820
current_row = self.selectedRow()
if current_row == -1:
# no row selected
return
tnum = self.tool_model.toolDataFromRow(current_row)['T']
issue_mdi("T%s M6" % tnum)
[docs] def selectedRow(self):
"""Returns the row number of the currently selected row, or 0"""
tool_no = self.selectionModel().currentIndex().row()
return tool_no
def onClick(self, index):
row = index.row()
tnum = self.tool_model.toolDataFromRow(row)['T']
self.toolSelected.emit(tnum)
def confirmAction(self, message):
if not self._confirm_actions:
return True
box = QMessageBox.question(self,
'Confirm Action',
message,
QMessageBox.Yes,
QMessageBox.No)
if box == QMessageBox.Yes:
return True
else:
return False
@Property(bool)
def confirmActions(self):
return self._confirm_actions
@confirmActions.setter
def confirmActions(self, confirm):
self._confirm_actions = confirm
@Property(QColor)
def currentToolColor(self):
return self.tool_model.current_tool_color
@currentToolColor.setter
def currentToolColor(self, color):
self.tool_model.current_tool_color = color
@Property(QColor)
def currentToolBackground(self):
return self.tool_model.current_tool_bg or QColor()
@currentToolBackground.setter
def currentToolBackground(self, color):
self.tool_model.current_tool_bg = color
def insertToolAbove(self):
# it does not make sense to insert tools, since the numbering
# of all the other tools would have to change.
self.addTool()
raise DeprecationWarning("insertToolAbove() will be removed in "
"the future, use addTool() instead")
def insertToolBelow(self):
self.addTool()
raise DeprecationWarning("insertToolBelow() will be removed in "
"the future, use addTool() instead")