#!/usr/bin/env python3

# ABOUT
# Artisan Roast Properties Dialog

# LICENSE
# This program or module is free software: you can redistribute it and/or
# modify it under the terms of the GNU General Public License as published
# by the Free Software Foundation, either version 2 of the License, or
# version 3 of the License, or (at your option) any later versison. It is
# provided for educational purposes and is distributed in the hope that
# it will be useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See
# the GNU General Public License for more details.

# AUTHOR
# Marko Luther, 2020

import sys
import math
import platform
import prettytable

# import artisan.plus module
import plus.config  # @UnusedImport
import plus.util

from artisanlib.suppress_errors import suppress_stdout_stderr
from artisanlib.util import deltaLabelUTF8,stringfromseconds,stringtoseconds, appFrozen
from artisanlib.dialogs import ArtisanDialog, ArtisanResizeablDialog
from artisanlib.widgets import MyQComboBox, ClickableQLabel, ClickableTextEdit

from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot, QRegExp, QSettings, QTimer, QEvent
from PyQt5.QtGui import QColor, QIntValidator, QRegExpValidator, QKeySequence, QPalette
from PyQt5.QtWidgets import (QApplication, QWidget, QCheckBox, QComboBox, QDialogButtonBox, QGridLayout,
                             QHBoxLayout, QVBoxLayout, QHeaderView, QLabel, QLineEdit, QTextEdit, QListView, 
                             QPushButton, QSpinBox, QTableWidget, QTableWidgetItem, QTabWidget, QSizePolicy,
                             QGroupBox)
                             
if sys.platform.startswith("darwin"):
    import darkdetect # @UnresolvedImport


########################################################################################
#####################  Volume Calculator DLG  ##########################################
########################################################################################

class volumeCalculatorDlg(ArtisanDialog):
    def __init__(self, parent = None, aw = None, weightIn=None, weightOut=None,
            weightunit=0,volumeunit=0,
            inlineedit=None,outlineedit=None,tare=0): # weight in and out expected in g (int)
        
        self.parent_dialog = parent
        # weightunit 0:g, 1:Kg  volumeunit 0:ml, 1:l
        super(volumeCalculatorDlg,self).__init__(parent, aw)
        self.setModal(True)
        self.setWindowTitle(QApplication.translate("Form Caption","Volume Calculator",None))

        if self.aw.scale.device is not None and self.aw.scale.device != "" and self.aw.scale.device != "None":
            self.scale_connected = True
        else:
            self.scale_connected = False

        self.weightIn = weightIn
        self.weightOut = weightOut
        
        # the units
        self.weightunit = weightunit
        self.volumeunit = volumeunit
        
        # the results
        self.inVolume = None
        self.outVolume = None
        
        # the QLineedits of the RoastProperties dialog to be updated
        self.inlineedit = inlineedit
        self.outlineedit = outlineedit
        
        # the current active tare
        self.tare = tare
        
        # Scale Weight
        self.scale_weight = self.parent_dialog.scale_weight
        self.scaleWeight = QLabel() # displays the current reading - tare of the connected scale
        if self.parent_dialog.ble is not None:
            self.update_scale_weight()
            self.parent_dialog.ble.weightChanged.connect(self.ble_weight_changed)
            self.parent_dialog.ble.deviceDisconnected.connect(self.ble_scan_failed)
        
        # Unit Group
        unitvolumeLabel = QLabel("<b>" + QApplication.translate("Label","Unit", None) + "</b>")
        self.unitvolumeEdit = QLineEdit("%g" % self.aw.qmc.volumeCalcUnit)
#        self.unitvolumeEdit.setValidator(QIntValidator(0, 99999,self.unitvolumeEdit))
        self.unitvolumeEdit.setValidator(self.aw.createCLocaleDoubleValidator(0., 9999999., 4, self.unitvolumeEdit))
        self.unitvolumeEdit.setMinimumWidth(70)
        self.unitvolumeEdit.setMaximumWidth(70)
        self.unitvolumeEdit.setAlignment(Qt.AlignRight)
        unitvolumeUnit = QLabel(QApplication.translate("Label","ml", None))
        
        # unit button
        unitButton = QPushButton(QApplication.translate("Button", "unit",None))
        unitButton.clicked.connect(self.unitWeight)
        unitButton.setFocusPolicy(Qt.NoFocus)

        unitLayout = QHBoxLayout()
        if self.scale_connected:
            unitLayout.addStretch()
        unitLayout.addWidget(self.scaleWeight)
        unitLayout.addStretch()
        unitLayout.addWidget(unitvolumeLabel)
        unitLayout.addWidget(self.unitvolumeEdit)
        unitLayout.addWidget(unitvolumeUnit)
        unitLayout.addStretch()
        if self.scale_connected:
            unitLayout.addWidget(unitButton)
        
        # In Group
        coffeeinunitweightLabel = QLabel("<b>" + QApplication.translate("Label","Unit Weight", None) + "</b>")
        self.coffeeinweightEdit = QLineEdit(self.aw.qmc.volumeCalcWeightInStr)
        self.coffeeinweightEdit.setMinimumWidth(70)
        self.coffeeinweightEdit.setMaximumWidth(70)
        self.coffeeinweightEdit.setAlignment(Qt.AlignRight)
        coffeeinunitweightUnit = QLabel(QApplication.translate("Label","g", None))

        coffeeinweightLabel = QLabel("<b>" + QApplication.translate("Label","Weight", None) + "</b>")
        self.coffeeinweight = QLineEdit()
        if self.weightIn:
            self.coffeeinweight.setText("%g" % self.aw.float2floatWeightVolume(self.weightIn))
        self.coffeeinweight.setMinimumWidth(70)
        self.coffeeinweight.setMaximumWidth(70)
        self.coffeeinweight.setAlignment(Qt.AlignRight)
        self.coffeeinweight.setReadOnly(True)
        self.coffeeinweight.setFocusPolicy(Qt.NoFocus)
        if sys.platform.startswith("darwin"):
            self.coffeeinweight.setStyleSheet("border: 0.5px solid lightgrey; background-color:'lightgrey'")
        else:
            self.coffeeinweight.setStyleSheet("background-color:'lightgrey'")
        coffeeinweightUnit = QLabel(self.aw.qmc.weight_units[weightunit])
        
        coffeeinvolumeLabel = QLabel("<b>" + QApplication.translate("Label","Volume", None) + "</b>")
        self.coffeeinvolume = QLineEdit()
        self.coffeeinvolume.setMinimumWidth(70)
        self.coffeeinvolume.setMaximumWidth(70)
        
        palette = QPalette()
        palette.setColor(self.coffeeinvolume.foregroundRole(), QColor('red'))
        self.coffeeinvolume.setPalette(palette)
        
        self.coffeeinvolume.setAlignment(Qt.AlignRight)
        self.coffeeinvolume.setReadOnly(True)
        self.coffeeinvolume.setFocusPolicy(Qt.NoFocus)
        coffeeinvolumeUnit = QLabel(self.aw.qmc.volume_units[volumeunit])
            
        # in button
        inButton = QPushButton(QApplication.translate("Button", "in",None))
        inButton.clicked.connect(self.inWeight)
        inButton.setFocusPolicy(Qt.NoFocus)
        
        inGrid = QGridLayout()
        inGrid.addWidget(coffeeinweightLabel,0,0)
        inGrid.addWidget(self.coffeeinweight,0,1)
        inGrid.addWidget(coffeeinweightUnit,0,2)
        inGrid.addWidget(coffeeinvolumeLabel,1,0)
        inGrid.addWidget(self.coffeeinvolume,1,1)
        inGrid.addWidget(coffeeinvolumeUnit,1,2)
        
        volumeInLayout = QHBoxLayout()
        volumeInLayout.addWidget(coffeeinunitweightLabel)
        volumeInLayout.addWidget(self.coffeeinweightEdit)
        volumeInLayout.addWidget(coffeeinunitweightUnit)
        volumeInLayout.addSpacing(15)
        volumeInLayout.addLayout(inGrid)
        
        inButtonLayout = QHBoxLayout()
        inButtonLayout.addWidget(inButton)
        inButtonLayout.addStretch()
        
        volumeInVLayout = QVBoxLayout()
        volumeInVLayout.addLayout(volumeInLayout)
        if self.scale_connected:
            volumeInVLayout.addLayout(inButtonLayout)
        
        volumeInGroupLayout = QGroupBox(QApplication.translate("Label","Green", None))
        volumeInGroupLayout.setLayout(volumeInVLayout)
        if weightIn is None:
            volumeInGroupLayout.setDisabled(True)

        self.resetInVolume()

        # Out Group
        coffeeoutunitweightLabel = QLabel("<b>" + QApplication.translate("Label","Unit Weight", None) + "</b>")
        self.coffeeoutweightEdit = QLineEdit(self.aw.qmc.volumeCalcWeightOutStr)
        self.coffeeoutweightEdit.setMinimumWidth(60)
        self.coffeeoutweightEdit.setMaximumWidth(60)
        self.coffeeoutweightEdit.setAlignment(Qt.AlignRight)
        coffeeoutunitweightUnit = QLabel(QApplication.translate("Label","g", None))

        coffeeoutweightLabel = QLabel("<b>" + QApplication.translate("Label","Weight", None) + "</b>")
        self.coffeeoutweight = QLineEdit()
        if self.weightOut:
            self.coffeeoutweight.setText("%g" % self.aw.float2floatWeightVolume(self.weightOut))
        self.coffeeoutweight.setMinimumWidth(60)
        self.coffeeoutweight.setMaximumWidth(60)
        self.coffeeoutweight.setAlignment(Qt.AlignRight)
        self.coffeeoutweight.setReadOnly(True)
        if sys.platform.startswith("darwin"):
            self.coffeeoutweight.setStyleSheet("border: 0.5px solid lightgrey; background-color:'lightgrey'")
        else:
            self.coffeeoutweight.setStyleSheet("background-color:'lightgrey'")
        self.coffeeoutweight.setFocusPolicy(Qt.NoFocus)
        coffeeoutweightUnit = QLabel(self.aw.qmc.weight_units[weightunit])

        coffeeoutvolumeLabel = QLabel("<b>" + QApplication.translate("Label","Volume", None) + "</b>")
        self.coffeeoutvolume = QLineEdit()
        self.coffeeoutvolume.setMinimumWidth(60)
        self.coffeeoutvolume.setMaximumWidth(60)
        
        palette = QPalette()
        palette.setColor(self.coffeeoutvolume.foregroundRole(), QColor('red'))
        self.coffeeoutvolume.setPalette(palette)

        self.coffeeoutvolume.setAlignment(Qt.AlignRight)
        self.coffeeoutvolume.setReadOnly(True)
        self.coffeeoutvolume.setFocusPolicy(Qt.NoFocus)
        coffeeoutvolumeUnit = QLabel(self.aw.qmc.volume_units[volumeunit])

        # out button
        outButton = QPushButton(QApplication.translate("Button", "out",None))
        outButton.clicked.connect(self.outWeight)
        outButton.setFocusPolicy(Qt.NoFocus)
                
        outGrid = QGridLayout()
        outGrid.addWidget(coffeeoutweightLabel,0,0)
        outGrid.addWidget(self.coffeeoutweight,0,1)
        outGrid.addWidget(coffeeoutweightUnit,0,2)
        outGrid.addWidget(coffeeoutvolumeLabel,1,0)
        outGrid.addWidget(self.coffeeoutvolume,1,1)
        outGrid.addWidget(coffeeoutvolumeUnit,1,2)
        
        volumeOutLayout = QHBoxLayout()
        volumeOutLayout.addWidget(coffeeoutunitweightLabel)
        volumeOutLayout.addWidget(self.coffeeoutweightEdit)
        volumeOutLayout.addWidget(coffeeoutunitweightUnit)
        volumeOutLayout.addSpacing(15)
        volumeOutLayout.addLayout(outGrid)
        
        outButtonLayout = QHBoxLayout()
        outButtonLayout.addWidget(outButton)
        outButtonLayout.addStretch()
        
        volumeOutVLayout = QVBoxLayout()
        volumeOutVLayout.addLayout(volumeOutLayout)
        if self.scale_connected:
            volumeOutVLayout.addLayout(outButtonLayout)
        
        volumeOutGroupLayout = QGroupBox(QApplication.translate("Label","Roasted", None))
        volumeOutGroupLayout.setLayout(volumeOutVLayout)
        if weightOut is None:
            volumeOutGroupLayout.setDisabled(True)

        self.resetOutVolume()
        
        self.coffeeinweightEdit.editingFinished.connect(self.resetInVolume)
        self.coffeeoutweightEdit.editingFinished.connect(self.resetOutVolume)
        self.unitvolumeEdit.editingFinished.connect(self.resetVolume)

        # connect the ArtisanDialog standard OK/Cancel buttons
        self.dialogbuttons.accepted.connect(self.updateVolumes)
        self.dialogbuttons.rejected.connect(self.close)
        
        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(self.dialogbuttons)

        mainlayout = QVBoxLayout()
        mainlayout.addLayout(unitLayout)
        mainlayout.addWidget(volumeInGroupLayout)
        mainlayout.addWidget(volumeOutGroupLayout)
        mainlayout.addLayout(buttonLayout)
        self.setLayout(mainlayout)
        self.coffeeinweightEdit.setFocus()
        
        self.parent_dialog.scaleWeightUpdated.connect(self.update_scale_weight)
        

    pyqtSlot()
    def ble_scan_failed(self):
        self.scale_weight = None
        self.scale_battery = None
        self.scaleWeight.setText("")

    pyqtSlot(float)
    def ble_weight_changed(self,w):
        if w is not None:
            self.scale_weight = w
            self.update_scale_weight()

    @pyqtSlot(float)
    def update_scale_weight(self,weight=None):
        try:
            if weight is not None:
                self.scale_weight = weight
            if self.scale_weight is not None and self.tare is not None:
                self.scaleWeight.setText("{0:.1f}g".format(self.scale_weight - self.tare))
            else:
                self.scaleWeight.setText("")
        except: # the dialog might have been closed already and thus the qlabel might not exist anymore
            pass
        
    #keyboard presses. There must not be widgets (pushbuttons, comboboxes, etc) in focus in order to work 
    def keyPressEvent(self,event):
        key = int(event.key())
        if key == 16777220 and self.scale_connected: # ENTER key pressed
            v = self.retrieveWeight()
            if v and v != 0:
                if self.unitvolumeEdit.hasFocus():
                    self.unitvolumeEdit.setText("%g" % self.aw.float2float(v))
                elif self.coffeeinweightEdit.hasFocus():
                    self.coffeeinweightEdit.setText("%g" % self.aw.float2float(v))
                elif self.coffeeoutweightEdit.hasFocus():
                    self.coffeeoutweightEdit.setText("%g" % self.aw.float2float(v))
                    
    def widgetWeight(self,widget):
        w = self.retrieveWeight()
        if w is not None:
            v = self.aw.float2floatWeightVolume(w)
#            widget.setText("%g" % self.aw.float2float(v))
            # updating this widget in a separate thread seems to be important on OS X 10.14 to avoid delayed updates and widget redraw problesm
            QTimer.singleShot(2,lambda : widget.setText("%g" % self.aw.float2float(v)))
    
    pyqtSlot(bool)
    def unitWeight(self,_):
        self.widgetWeight(self.unitvolumeEdit)
        
    pyqtSlot(bool)
    def inWeight(self,_):
        QTimer.singleShot(1,lambda : self.widgetWeight(self.coffeeinweightEdit))
        QTimer.singleShot(10,lambda : self.resetInVolume())
        QApplication.processEvents()
        
    pyqtSlot(bool)
    def outWeight(self,_):
        QTimer.singleShot(1,lambda : self.widgetWeight(self.coffeeoutweightEdit))
        QTimer.singleShot(10,lambda : self.resetOutVolume())
        QApplication.processEvents()
        
    def retrieveWeight(self):
        v = self.scale_weight
        if v is not None: # value received
            # substruct tare
            return v - self.tare
        else:
            return None

    @pyqtSlot()
    def resetVolume(self):
        self.resetInVolume()
        self.resetOutVolume()

    @pyqtSlot()
    def resetInVolume(self):
        try:
            line = self.coffeeinweightEdit.text()
            if line is None or str(line).strip() == "":
                self.coffeeinvolume.setText("")
                self.inVolume = None
            else:
                self.inVolume = self.aw.convertVolume(self.aw.convertWeight(self.weightIn,self.weightunit,0) * float(self.aw.comma2dot(str(self.unitvolumeEdit.text()))) / float(self.aw.comma2dot(str(self.coffeeinweightEdit.text()))),5,self.volumeunit)
                self.coffeeinvolume.setText("%g" % self.aw.float2floatWeightVolume(self.inVolume))
        except Exception:
            self.inVolume = None
            self.coffeeinvolume.setText("")

    @pyqtSlot()
    def resetOutVolume(self):
        try:
            line = self.coffeeoutweightEdit.text()
            if line is None or str(line).strip() == "":
                self.coffeeoutvolume.setText("")
                self.outVolume = None
            else:
                self.outVolume = self.aw.convertVolume(self.aw.convertWeight(self.weightOut,self.weightunit,0) * float(self.aw.comma2dot(str(self.unitvolumeEdit.text()))) / float(self.aw.comma2dot(str(self.coffeeoutweightEdit.text()))),5,self.volumeunit)
                self.coffeeoutvolume.setText("%g" % self.aw.float2floatWeightVolume(self.outVolume))
        except Exception:
            self.outVolume = None
            self.coffeeoutvolume.setText("")

    @pyqtSlot()
    def updateVolumes(self):
        if self.inVolume and self.inVolume != "":
            if self.volumeunit == 0:
                self.inlineedit.setText("%g" % self.aw.float2floatWeightVolume(self.inVolume))
            else:
                self.inlineedit.setText("%g" % self.aw.float2floatWeightVolume(self.inVolume))
        if self.outVolume and self.outVolume != "":
            if self.volumeunit == 0:
                self.outlineedit.setText("%g" % self.aw.float2floatWeightVolume(self.outVolume))
            else:
                self.outlineedit.setText("%g" % self.aw.float2floatWeightVolume(self.outVolume))
        self.parent_dialog.volume_percent()
        self.closeEvent(None)
        
    def closeEvent(self,_):
        try:
            self.parent_dialog.volumedialog = None
        except:
            pass
        if self.unitvolumeEdit.text() and self.unitvolumeEdit.text() != "":
            self.aw.qmc.volumeCalcUnit = float(self.unitvolumeEdit.text())
            self.aw.qmc.volumeCalcWeightInStr = self.aw.comma2dot(str(self.coffeeinweightEdit.text()))
            self.aw.qmc.volumeCalcWeightOutStr = self.aw.comma2dot(str(self.coffeeoutweightEdit.text()))
            self.parent_dialog.calculated_density()
        self.accept()

    @pyqtSlot()
    def close(self):
        self.closeEvent(None)


##########################################################################
#####################  VIEW Tare  ########################################
##########################################################################


class tareDlg(ArtisanDialog):
    def __init__(self, parent = None, aw = None, tarePopup = None):
        super(tareDlg,self).__init__(parent, aw)
        self.parent = parent
        self.tarePopup = tarePopup
        self.setModal(True)
        self.setWindowTitle(QApplication.translate("Form Caption","Tare Setup", None))

        self.taretable = QTableWidget()
        self.taretable.setTabKeyNavigation(True)
        self.createTareTable()
        
        self.taretable.itemSelectionChanged.connect(self.selectionChanged)
        
        addButton = QPushButton(QApplication.translate("Button","Add", None))
        addButton.setFocusPolicy(Qt.NoFocus)
        self.delButton = QPushButton(QApplication.translate("Button","Delete", None))
        self.delButton.setDisabled(True)
        self.delButton.setFocusPolicy(Qt.NoFocus)
        
        addButton.clicked.connect(self.addTare)
        self.delButton.clicked.connect(self.delTare)
        
        okButton = QPushButton(QApplication.translate("Button","OK", None))
        cancelButton = QPushButton(QApplication.translate("Button","Cancel",None))
        cancelButton.setFocusPolicy(Qt.NoFocus)
        okButton.clicked.connect(self.close)
        cancelButton.clicked.connect(self.reject)
        contentbuttonLayout = QHBoxLayout()
        contentbuttonLayout.addStretch()
        contentbuttonLayout.addWidget(addButton)
        contentbuttonLayout.addWidget(self.delButton)
        contentbuttonLayout.addStretch()

        buttonLayout = QHBoxLayout()
        buttonLayout.addStretch()
        buttonLayout.addWidget(cancelButton)
        buttonLayout.addWidget(okButton)
        layout = QVBoxLayout()
        layout.addWidget(self.taretable)
        layout.addLayout(contentbuttonLayout)
        layout.addLayout(buttonLayout)
        self.setLayout(layout)
    
    @pyqtSlot()
    def selectionChanged(self):
        if len(self.taretable.selectedRanges()) > 0:
            self.delButton.setDisabled(False)
        else:
            self.delButton.setDisabled(False)
        
    def closeEvent(self,_):
        self.saveTareTable()
        # update popup
        self.tarePopup.tarePopupEnabled = False
        self.tarePopup.tareComboBox.clear()
        self.tarePopup.tareComboBox.addItem("<edit> TARE")
        self.tarePopup.tareComboBox.insertSeparator(2)
        self.tarePopup.tareComboBox.addItem("")
        self.tarePopup.tareComboBox.addItems(self.aw.qmc.container_names)
        self.tarePopup.tareComboBox.setCurrentIndex(2) # reset to the empty entry
        self.aw.qmc.container_idx = -1
        self.tarePopup.tarePopupEnabled = True
        self.accept()

    @pyqtSlot(bool)
    def addTare(self,_):
        rows = self.taretable.rowCount()
        self.taretable.setRowCount(rows + 1)
        #add widgets to the table
        name = QLineEdit()
        name.setAlignment(Qt.AlignRight)
        name.setText("name")
        w,_,_ = self.aw.scale.readWeight(self.parent.scale_weight) # read value from scale in 'g'
        weight = QLineEdit()
        weight.setAlignment(Qt.AlignRight)
        if w > -1:
            weight.setText(str(w))
        else:
            weight.setText(str(0))
        weight.setValidator(QIntValidator(0,999,weight))
        self.taretable.setCellWidget(rows,0,name)
        self.taretable.setCellWidget(rows,1,weight)
        
    @pyqtSlot(bool)
    def delTare(self,_):
        selected = self.taretable.selectedRanges()
        if len(selected) > 0:
            bindex = selected[0].topRow()
            if bindex >= 0:
                self.taretable.removeRow(bindex)

    def saveTareTable(self):
        tars = self.taretable.rowCount() 
        names = []
        weights = []
        for i in range(tars):
            name = self.taretable.cellWidget(i,0).text()
            weight = int(round(float(self.taretable.cellWidget(i,1).text())))
            names.append(name)
            weights.append(weight)
        self.aw.qmc.container_names = names
        self.aw.qmc.container_weights = weights
        
    def createTareTable(self):
        self.taretable.clear()
        self.taretable.setRowCount(len(self.aw.qmc.container_names))
        self.taretable.setColumnCount(2)
        self.taretable.setHorizontalHeaderLabels([QApplication.translate("Table","Name",None),
                                                         QApplication.translate("Table","Weight",None)])
        self.taretable.setAlternatingRowColors(True)
        self.taretable.setEditTriggers(QTableWidget.NoEditTriggers)
        self.taretable.setSelectionBehavior(QTableWidget.SelectRows)
        self.taretable.setSelectionMode(QTableWidget.SingleSelection)
        self.taretable.setShowGrid(True)
        self.taretable.verticalHeader().setSectionResizeMode(2)
        for i in range(len(self.aw.qmc.container_names)):
            #add widgets to the table
            name = QLineEdit()
            name.setAlignment(Qt.AlignRight)
            name.setText(self.aw.qmc.container_names[i])
            weight = QLineEdit()
            weight.setAlignment(Qt.AlignRight)
            weight.setText(str(self.aw.qmc.container_weights[i]))
            weight.setValidator(QIntValidator(0,999,weight))
            
            self.taretable.setCellWidget(i,0,name)
            self.taretable.setCellWidget(i,1,weight)
        header = self.taretable.horizontalHeader()
        header.setSectionResizeMode(0, QHeaderView.Stretch)
        header.setSectionResizeMode(1, QHeaderView.Fixed)
        self.taretable.setColumnWidth(1,65)
        
########################################################################################
#####################  RECENT ROAST POPUP  #############################################

class RoastsComboBox(QComboBox):
    def __init__(self, parent = None, aw = None, selection = None):
        super(RoastsComboBox, self).__init__(parent)
        self.aw = aw
        self.installEventFilter(self)
        self.selection = selection # just the roast title
        self.edited = selection
        self.updateMenu()
        self.editTextChanged.connect(self.textEdited)
        self.setEditable(True)
#        self.setMouseTracking(False)

    @pyqtSlot("QString")
    def textEdited(self,txt):
        cleaned = ' '.join(txt.split())
        self.edited = cleaned

    def getSelection(self):
        return self.edited or self.selection

    def setSelection(self,i):
        if i >= 0:
            try:
                self.edited = None # reset the user text editing
            except Exception:
                pass

    def eventFilter(self, obj, event):
# the next prevents correct setSelection on Windows
#        if event.type() == QEvent.FocusIn:
#            self.setSelection(self.currentIndex())
        if event.type() == QEvent.MouseButtonPress:
            self.updateMenu()
#            return True # stops processing # popup not drawn if this line is added
#        return super(RoastsComboBox, self).eventFilter(obj, event) # this seems to slow down things on Windows and not necessary anyhow
        return False # cont processing

    # the first entry is always just the current text edit line
    def updateMenu(self):
        self.blockSignals(True)
        try:
            roasts = self.aw.recentRoastsMenuList()
            self.clear()
            self.addItems([self.edited] + roasts)
        except:
            pass
        self.blockSignals(False)

########################################################################################
#####################  Roast Properties Dialog  ########################################

class editGraphDlg(ArtisanResizeablDialog):
    scaleWeightUpdated = pyqtSignal(float)
    connectScaleSignal = pyqtSignal()
    readScaleSignal = pyqtSignal()

    def __init__(self, parent = None, aw = None):
        super(editGraphDlg,self).__init__(parent, aw)
        self.setModal(True)
        self.setWindowTitle(QApplication.translate("Form Caption","Roast Properties",None))
        
        # we remember user modifications to revert to them on deselecting a plus element
        self.modified_beans = self.aw.qmc.beans
        self.modified_density_in_text = str(self.aw.float2float(self.aw.qmc.density[0]))
        self.modified_volume_in_text = str(self.aw.float2float(self.aw.qmc.volume[0]))
        self.modified_beansize_min_text = str(self.aw.qmc.beansize_min)
        self.modified_beansize_max_text = str(self.aw.qmc.beansize_max)
        self.modified_moisture_greens_text = str(self.aw.qmc.moisture_greens)
        
        # remember parameters set by plus_coffee/plus_blend on entering the dialog to enable a Cancel action
        self.org_beans = self.aw.qmc.beans
        self.org_density = self.aw.qmc.density
        self.org_density_roasted = self.aw.qmc.density_roasted
        self.org_beansize_min = self.aw.qmc.beansize_min
        self.org_beansize_max = self.aw.qmc.beansize_max
        self.org_moisture_greens = self.aw.qmc.moisture_greens
        
        self.org_title = self.aw.qmc.title
        self.org_title_show_always = self.aw.qmc.title_show_always
        
        self.org_weight = self.aw.qmc.weight[:]
        self.org_volume = self.aw.qmc.volume[:]
        
        self.batcheditmode = False # a click to the batch label enables the batcheditmode
        
        self.ble = None # the BLE interface
        self.scale_weight = None # weight received from a connected scale
        self.scale_battery = None # battery level of the connected scale in %
        self.scale_set = None # set weight for accumulation in g
        
        self.disconnecting = False # this is set to True to terminate the scale connection
        self.volumedialog = None # link forward to the the Volume Calculator
        
        # other parameters remembered for Cancel operation
        self.org_specialevents = self.aw.qmc.specialevents[:]
        self.org_specialeventstype = self.aw.qmc.specialeventstype[:]
        self.org_specialeventsStrings = self.aw.qmc.specialeventsStrings[:]
        self.org_specialeventsvalue = self.aw.qmc.specialeventsvalue[:]
        self.org_timeindex = self.aw.qmc.timeindex[:]
        
        self.org_ambientTemp = self.aw.qmc.ambientTemp
        self.org_ambient_humidity = self.aw.qmc.ambient_humidity
        self.org_ambient_pressure = self.aw.qmc.ambient_pressure
        
        self.org_roastpropertiesAutoOpenFlag = self.aw.qmc.roastpropertiesAutoOpenFlag
        self.org_roastpropertiesAutoOpenDropFlag = self.aw.qmc.roastpropertiesAutoOpenDropFlag
        
        # propulated by selecting a recent roast from the popup via recentRoastActivated()
        self.template_file = None
        self.template_name = None
        self.template_uuid = None
        self.template_batchnr = None
        self.template_batchprefix = None
        
        regextime = QRegExp(r"^-?[0-9]?[0-9]?[0-9]:[0-5][0-9]$")
        #MARKERS
        chargelabel = QLabel("<b>" + QApplication.translate("Label", "CHARGE",None) + "</b>")
        chargelabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        chargelabel.setStyleSheet("background-color:'#f07800';")
        self.chargeedit = QLineEdit(stringfromseconds(0))
#        self.chargeedit.setFocusPolicy(Qt.NoFocus)
        self.chargeedit.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.chargeeditcopy = stringfromseconds(0)
        self.chargeedit.setValidator(QRegExpValidator(regextime,self))
        self.chargeedit.setMaximumWidth(50)
        self.chargeedit.setMinimumWidth(50)
        chargelabel.setBuddy(self.chargeedit)
        self.charge_idx = 0
        self.drop_idx = 0
        #charge_str = ""
        drop_str = ""
        if len(self.aw.qmc.timex):
            TP_index = self.aw.findTP()
            if self.aw.qmc.timeindex[1]:
                #manual dryend available
                dryEndIndex = self.aw.qmc.timeindex[1]
            else:
                #find when dry phase ends 
                dryEndIndex = self.aw.findDryEnd(TP_index)
            self.charge_idx = self.aw.findBTbreak(0,dryEndIndex,offset=0.5)
            self.drop_idx = self.aw.findBTbreak(dryEndIndex,offset=0.2)
            if self.drop_idx != 0 and self.drop_idx != self.aw.qmc.timeindex[6]:
                drop_str = stringfromseconds(self.aw.qmc.timex[self.drop_idx]-self.aw.qmc.timex[self.aw.qmc.timeindex[0]])
        drylabel = QLabel("<b>" + QApplication.translate("Label", "DRY END",None) + "</b>")
        drylabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        drylabel.setStyleSheet("background-color:'orange';")
        if self.aw.qmc.timeindex[1] and self.aw.qmc.timeindex[1] < len(self.aw.qmc.timex):
            t2 = self.aw.qmc.timex[self.aw.qmc.timeindex[1]]-self.aw.qmc.timex[self.aw.qmc.timeindex[0]]
        else:
            t2 = 0
        self.dryedit = QLineEdit(stringfromseconds(t2))
        self.dryedit.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.dryeditcopy = stringfromseconds(t2)
        self.dryedit.setValidator(QRegExpValidator(regextime,self))
        self.dryedit.setMaximumWidth(50)
        self.dryedit.setMinimumWidth(50)
        drylabel.setBuddy(self.dryedit)
        Cstartlabel = QLabel("<b>" + QApplication.translate("Label","FC START",None) + "</b>")
        Cstartlabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        Cstartlabel.setStyleSheet("background-color:'orange';")
        if self.aw.qmc.timeindex[2] and self.aw.qmc.timeindex[2] < len(self.aw.qmc.timex):
            t3 = self.aw.qmc.timex[self.aw.qmc.timeindex[2]]-self.aw.qmc.timex[self.aw.qmc.timeindex[0]]
        else:
            t3 = 0
        self.Cstartedit = QLineEdit(stringfromseconds(t3))
#        self.Cstartedit.setFocusPolicy(Qt.NoFocus)
        self.Cstartedit.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.Cstarteditcopy = stringfromseconds(t3)
        self.Cstartedit.setValidator(QRegExpValidator(regextime,self))
        self.Cstartedit.setMaximumWidth(50)
        self.Cstartedit.setMinimumWidth(50)
        Cstartlabel.setBuddy(self.Cstartedit)
        
        Cendlabel = QLabel("<b>" + QApplication.translate("Label","FC END",None) + "</b>")
        Cendlabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        Cendlabel.setStyleSheet("background-color:'orange';")
        if self.aw.qmc.timeindex[3] and self.aw.qmc.timeindex[3] < len(self.aw.qmc.timex):
            t4 = self.aw.qmc.timex[self.aw.qmc.timeindex[3]]-self.aw.qmc.timex[self.aw.qmc.timeindex[0]]
        else:
            t4 = 0
        self.Cendedit = QLineEdit(stringfromseconds(t4))
#        self.Cendedit.setFocusPolicy(Qt.NoFocus)
        self.Cendedit.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.Cendeditcopy = stringfromseconds(t4)
        self.Cendedit.setValidator(QRegExpValidator(regextime,self))
        self.Cendedit.setMaximumWidth(50)
        self.Cendedit.setMinimumWidth(50)
        Cendlabel.setBuddy(self.Cendedit)
        CCstartlabel = QLabel("<b>" + QApplication.translate("Label","SC START",None) + "</b>")
        CCstartlabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        CCstartlabel.setStyleSheet("background-color:'orange';")
        if self.aw.qmc.timeindex[4] and self.aw.qmc.timeindex[4] < len(self.aw.qmc.timex):
            t5 = self.aw.qmc.timex[self.aw.qmc.timeindex[4]]-self.aw.qmc.timex[self.aw.qmc.timeindex[0]]
        else:
            t5 = 0
        self.CCstartedit = QLineEdit(stringfromseconds(t5))
#        self.CCstartedit.setFocusPolicy(Qt.NoFocus)
        self.CCstartedit.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.CCstarteditcopy = stringfromseconds(t5)
        self.CCstartedit.setValidator(QRegExpValidator(regextime,self))
        self.CCstartedit.setMaximumWidth(50)
        self.CCstartedit.setMinimumWidth(50)
        CCstartlabel.setBuddy(self.CCstartedit)
        CCendlabel = QLabel("<b>" + QApplication.translate("Label","SC END",None) + "</b>")
        CCendlabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        CCendlabel.setStyleSheet("background-color:'orange';")
        if self.aw.qmc.timeindex[5] and self.aw.qmc.timeindex[5] < len(self.aw.qmc.timex):
            t6 = self.aw.qmc.timex[self.aw.qmc.timeindex[5]]-self.aw.qmc.timex[self.aw.qmc.timeindex[0]]
        else:
            t6 = 0
        self.CCendedit = QLineEdit(stringfromseconds(t6))
#        self.CCendedit.setFocusPolicy(Qt.NoFocus)
        self.CCendedit.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.CCendeditcopy = stringfromseconds(t6)
        self.CCendedit.setValidator(QRegExpValidator(regextime,self))
        self.CCendedit.setMaximumWidth(50)
        self.CCendedit.setMinimumWidth(50)
        CCendlabel.setBuddy(self.CCendedit)
        droplabel = QLabel("<b>" + QApplication.translate("Label", "DROP",None) + "</b>")
        droplabel.setStyleSheet("background-color:'#f07800';")
        droplabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        if self.aw.qmc.timeindex[6] and self.aw.qmc.timeindex[6] < len(self.aw.qmc.timex):
            t7 = self.aw.qmc.timex[self.aw.qmc.timeindex[6]]-self.aw.qmc.timex[self.aw.qmc.timeindex[0]]
        else:
            t7 = 0
        self.dropedit = QLineEdit(stringfromseconds(t7))
#        self.dropedit.setFocusPolicy(Qt.NoFocus)
        self.dropedit.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.dropeditcopy = stringfromseconds(t7)
        self.dropedit.setValidator(QRegExpValidator(regextime,self))
        self.dropedit.setMaximumWidth(50)
        self.dropedit.setMinimumWidth(50)
        droplabel.setBuddy(self.dropedit)
        self.dropestimate = QLabel(drop_str)
        coollabel = QLabel("<b>" + QApplication.translate("Label", "COOL",None) + "</b>")
        coollabel.setStyleSheet("background-color:'#6666ff';")
        coollabel.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        if self.aw.qmc.timeindex[7] and self.aw.qmc.timeindex[7] < len(self.aw.qmc.timex):
            t8 = self.aw.qmc.timex[self.aw.qmc.timeindex[7]]-self.aw.qmc.timex[self.aw.qmc.timeindex[0]]
        else:
            t8 = 0
        self.cooledit = QLineEdit(stringfromseconds(t8))
#        self.cooledit.setFocusPolicy(Qt.NoFocus)
        self.cooledit.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.cooleditcopy = stringfromseconds(t8)
        self.cooledit.setValidator(QRegExpValidator(regextime,self))
        self.cooledit.setMaximumWidth(50)
        self.cooledit.setMinimumWidth(50)
        coollabel.setBuddy(self.cooledit)
        self.roastproperties = QCheckBox(QApplication.translate("CheckBox","Delete roast properties on RESET", None))
        self.roastproperties.setChecked(bool(self.aw.qmc.roastpropertiesflag))
        self.roastproperties.stateChanged.connect(self.roastpropertiesChanged)
        self.roastpropertiesAutoOpen = QCheckBox(QApplication.translate("CheckBox","Open on CHARGE", None))
        self.roastpropertiesAutoOpen.setChecked(bool(self.aw.qmc.roastpropertiesAutoOpenFlag))
        self.roastpropertiesAutoOpen.stateChanged.connect(self.roastpropertiesAutoOpenChanged)
        self.roastpropertiesAutoOpenDROP = QCheckBox(QApplication.translate("CheckBox","Open on DROP", None))
        self.roastpropertiesAutoOpenDROP.setChecked(bool(self.aw.qmc.roastpropertiesAutoOpenDropFlag))
        self.roastpropertiesAutoOpenDROP.stateChanged.connect(self.roastpropertiesAutoOpenDROPChanged)
        # EVENTS
        #table for showing events
        self.eventtable = QTableWidget()
        self.eventtable.setTabKeyNavigation(True)
        self.clusterEventsButton = QPushButton(QApplication.translate("Button", "Cluster",None))
        self.clusterEventsButton.setFocusPolicy(Qt.NoFocus)
        self.clusterEventsButton.setMaximumSize(self.clusterEventsButton.sizeHint())
        self.clusterEventsButton.setMinimumSize(self.clusterEventsButton.minimumSizeHint())
        self.clusterEventsButton.clicked.connect(self.clusterEvents)
        self.clearEventsButton = QPushButton(QApplication.translate("Button", "Clear",None))
        self.clearEventsButton.setFocusPolicy(Qt.NoFocus)
        self.clearEventsButton.setMaximumSize(self.clearEventsButton.sizeHint())
        self.clearEventsButton.setMinimumSize(self.clearEventsButton.minimumSizeHint())
        self.clearEventsButton.clicked.connect(self.clearEvents) 
        self.createalarmTableButton = QPushButton(QApplication.translate("Button", "Create Alarms",None))
        self.createalarmTableButton.setFocusPolicy(Qt.NoFocus)
        self.createalarmTableButton.setMaximumSize(self.createalarmTableButton.sizeHint())
        self.createalarmTableButton.setMinimumSize(self.createalarmTableButton.minimumSizeHint())
        self.createalarmTableButton.clicked.connect(self.createAlarmEventTable)
        self.ordereventTableButton = QPushButton(QApplication.translate("Button", "Order",None))
        self.ordereventTableButton.setFocusPolicy(Qt.NoFocus)
        self.ordereventTableButton.setMaximumSize(self.ordereventTableButton.sizeHint())
        self.ordereventTableButton.setMinimumSize(self.ordereventTableButton.minimumSizeHint())
        self.ordereventTableButton.clicked.connect(self.orderEventTable)
        self.neweventTableButton = QPushButton(QApplication.translate("Button", "Add",None))
        self.neweventTableButton.setFocusPolicy(Qt.NoFocus)
        self.neweventTableButton.setMaximumSize(self.neweventTableButton.sizeHint())
        self.neweventTableButton.setMinimumSize(self.neweventTableButton.minimumSizeHint())
        self.neweventTableButton.clicked.connect(self.addEventTable)
        self.deleventTableButton = QPushButton(QApplication.translate("Button", "Delete",None))
        self.deleventTableButton.setFocusPolicy(Qt.NoFocus)
        self.deleventTableButton.setMaximumSize(self.deleventTableButton.sizeHint())
        self.deleventTableButton.setMinimumSize(self.deleventTableButton.minimumSizeHint())
        self.deleventTableButton.clicked.connect(self.deleteEventTable)
        self.copyeventTableButton = QPushButton(QApplication.translate("Button", "Copy Table",None))
        self.copyeventTableButton.setToolTip(QApplication.translate("Tooltip","Copy table to clipboard, OPTION or ALT click for tabular text",None))
        self.copyeventTableButton.setFocusPolicy(Qt.NoFocus)
        self.copyeventTableButton.setMaximumSize(self.copyeventTableButton.sizeHint())
        self.copyeventTableButton.setMinimumSize(self.copyeventTableButton.minimumSizeHint())
        self.copyeventTableButton.clicked.connect(self.copyEventTabletoClipboard)
        
        #DATA Table
        self.datatable = QTableWidget()
        self.datatable.setTabKeyNavigation(True)
        self.copydataTableButton = QPushButton(QApplication.translate("Button", "Copy Table",None))
        self.copydataTableButton.setToolTip(QApplication.translate("Tooltip","Copy table to clipboard, OPTION or ALT click for tabular text",None))
        self.copydataTableButton.setFocusPolicy(Qt.NoFocus)
        self.copydataTableButton.setMaximumSize(self.copydataTableButton.sizeHint())
        self.copydataTableButton.setMinimumSize(self.copydataTableButton.minimumSizeHint())
        self.copydataTableButton.clicked.connect(self.copyDataTabletoClipboard)
        #TITLE
        titlelabel = QLabel("<b>" + QApplication.translate("Label", "Title",None) + "</b>")
        self.titleedit = RoastsComboBox(self,self.aw,selection = self.aw.qmc.title)
        self.titleedit.setMinimumWidth(100)
        self.titleedit.setSizePolicy(QSizePolicy.MinimumExpanding,QSizePolicy.Fixed)
        self.titleedit.activated.connect(self.recentRoastActivated)
        self.titleedit.editTextChanged.connect(self.recentRoastEnabled)
        if sys.platform.startswith("darwin") and darkdetect.isDark() and appFrozen():
            if self.aw.qmc.palette["canvas"] is None or self.aw.qmc.palette["canvas"] == "None":
                canvas_color = "white"
            else:
                canvas_color = self.aw.qmc.palette["canvas"]
            brightness_title = self.aw.QColorBrightness(QColor(self.aw.qmc.palette["title"]))
            brightness_canvas = self.aw.QColorBrightness(QColor(canvas_color))
            # in dark mode we choose the darker color as background
            if brightness_title > brightness_canvas:
                backgroundcolor = QColor(canvas_color).name()
                color = QColor(self.aw.qmc.palette["title"]).name()
            else:
                backgroundcolor = QColor(self.aw.qmc.palette["title"]).name()
                color = QColor(canvas_color).name()
            self.titleedit.setStyleSheet(
                "QComboBox {font-weight: bold; background-color: " + backgroundcolor + "; color: " + color + ";} QComboBox QAbstractItemView {font-weight: normal;}")
        else:
            color = ""
            if self.aw.qmc.palette["title"] != None and self.aw.qmc.palette["title"] != "None":
                color = " color: " + QColor(self.aw.qmc.palette["title"]).name() + ";"
            backgroundcolor = ""
            if self.aw.qmc.palette["canvas"] != None and self.aw.qmc.palette["canvas"] != "None":
                backgroundcolor = " background-color: " + QColor(self.aw.qmc.palette["canvas"]).name() + ";"
            self.titleedit.setStyleSheet(
                "QComboBox {font-weight: bold;" + color + backgroundcolor + "} QComboBox QAbstractItemView {font-weight: normal;}")
        self.titleedit.setView(QListView())
        self.titleShowAlwaysFlag = QCheckBox(QApplication.translate("CheckBox","Show Always", None))
        self.titleShowAlwaysFlag.setChecked(self.aw.qmc.title_show_always)
        
        #Date
        datelabel1 = QLabel("<b>" + QApplication.translate("Label", "Date",None) + "</b>")
        date = self.aw.qmc.roastdate.date().toString()
        date += ", " + self.aw.qmc.roastdate.time().toString()[:-3]
        dateedit = QLineEdit(date)
        dateedit.setFocusPolicy(Qt.NoFocus)
        dateedit.setReadOnly(True)
        if sys.platform.startswith("darwin") and darkdetect.isDark() and appFrozen():
            dateedit.setStyleSheet("background-color: #757575; color : white;")
        else:
            dateedit.setStyleSheet("background-color: #eeeeee;")
        #Batch
        batchlabel = ClickableQLabel("<b>" + QApplication.translate("Label", "Batch",None) + "</b>")
        batchlabel.right_clicked.connect(self.enableBatchEdit)
        self.batchLayout = QHBoxLayout()
        if self.aw.superusermode: # and self.aw.qmc.batchcounter > -1:
            self.defineBatchEditor()
        else:
            batch = ""
            if self.aw.qmc.roastbatchnr != 0:
                roastpos = " (" + str(self.aw.qmc.roastbatchpos) + ")"
            else:
                roastpos = ""
            if self.aw.qmc.roastbatchnr == 0:
                batch = ""
            else:
                batch = self.aw.qmc.roastbatchprefix + str(self.aw.qmc.roastbatchnr) + roastpos
            self.batchedit = QLineEdit(batch)
            self.batchedit.setReadOnly(True)
            if sys.platform.startswith("darwin") and darkdetect.isDark() and appFrozen():
                self.batchedit.setStyleSheet("background-color: #757575; color : white;")
            else:
                self.batchedit.setStyleSheet("background-color: #eeeeee;")
            self.batchedit.setFocusPolicy(Qt.NoFocus)
            
        #Beans
        beanslabel = QLabel("<b>" + QApplication.translate("Label", "Beans",None) + "</b>")
        self.beansedit = ClickableTextEdit()
        self.beansedit.editingFinished.connect(self.beansEdited)
        
        self.beansedit.setMaximumHeight(60)
        if self.aw.qmc.beans is not None:
            self.beansedit.setNewPlainText(self.aw.qmc.beans)
                    
        #roaster
        self.roaster = QLineEdit(self.aw.qmc.roastertype)
        self.roaster.setCursorPosition(0)
        #operator
        self.operator = QLineEdit(self.aw.qmc.operator)
        self.operator.setCursorPosition(0)
        #organization
        self.organization = QLineEdit(self.aw.qmc.organization)
        self.organization.setCursorPosition(0)
        #drum speed
        self.drumspeed = QLineEdit(self.aw.qmc.drumspeed)
        self.drumspeed.setAlignment(Qt.AlignCenter)
        self.drumspeed.setCursorPosition(0)
        #weight
        weightlabel = QLabel("<b>" + QApplication.translate("Label", "Weight",None) + "</b>")
        green_label = QLabel("<b>" + QApplication.translate("Label", "Green",None) + "</b>")
        roasted_label = QLabel("<b>" + QApplication.translate("Label", "Roasted",None) + "</b>")
        inw = "%g" % self.aw.float2floatWeightVolume(self.aw.qmc.weight[0])
        outw = "%g" % self.aw.float2floatWeightVolume(self.aw.qmc.weight[1])
        self.weightinedit = QLineEdit(inw)
        self.weightinedit.setValidator(self.aw.createCLocaleDoubleValidator(0., 9999999., 4, self.weightinedit))  # the max limit has to be high enough otherwise the connected signals are not send!
        self.weightinedit.setMinimumWidth(70)
        self.weightinedit.setMaximumWidth(70)
        self.weightinedit.setAlignment(Qt.AlignRight)
        self.weightoutedit = QLineEdit(outw)
        self.weightoutedit.setValidator(self.aw.createCLocaleDoubleValidator(0., 9999999., 4, self.weightoutedit))  # the max limit has to be high enough otherwise the connected signals are not send!
        self.weightoutedit.setMinimumWidth(70)
        self.weightoutedit.setMaximumWidth(70)
        self.weightoutedit.setAlignment(Qt.AlignRight)
        self.weightpercentlabel = QLabel(QApplication.translate("Label", "",None))
        self.weightpercentlabel.setMinimumWidth(55)
        self.weightpercentlabel.setMaximumWidth(55)
        self.weightpercentlabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        self.roastdegreelabel = QLabel("")
        self.roastdegreelabel.setMinimumWidth(80)
        self.roastdegreelabel.setMaximumWidth(80)
        self.percent()
        self.weightinedit.editingFinished.connect(self.weightineditChanged)
        self.weightoutedit.editingFinished.connect(self.weightouteditChanged)
        self.unitsComboBox = QComboBox()
        self.unitsComboBox.setMaximumWidth(60)
        self.unitsComboBox.setMinimumWidth(60)
        self.unitsComboBox.addItems(self.aw.qmc.weight_units)
        self.unitsComboBox.setCurrentIndex(self.aw.qmc.weight_units.index(self.aw.qmc.weight[2]))
        self.unitsComboBox.currentIndexChanged.connect(self.changeWeightUnit)
        #volume
        volumelabel = QLabel("<b>" + QApplication.translate("Label", "Volume",None) + "</b>")
        inv = "%g" %  self.aw.float2floatWeightVolume(self.aw.qmc.volume[0])
        outv = "%g" % self.aw.float2floatWeightVolume(self.aw.qmc.volume[1])
        self.volumeinedit = QLineEdit(inv)
        self.volumeinedit.setValidator(self.aw.createCLocaleDoubleValidator(0., 999999., 4, self.volumeinedit)) # the max limit has to be high enough otherwise the connected signals are not send!
        self.volumeinedit.setMinimumWidth(70)
        self.volumeinedit.setMaximumWidth(70)
        self.volumeinedit.setAlignment(Qt.AlignRight)
        self.volumeoutedit = QLineEdit(outv)
        self.volumeoutedit.setValidator(self.aw.createCLocaleDoubleValidator(0., 999999., 4, self.volumeoutedit)) # the max limit has to be high enough otherwise the connected signals are not send!
        self.volumeoutedit.setMinimumWidth(70)
        self.volumeoutedit.setMaximumWidth(70)
        self.volumeoutedit.setAlignment(Qt.AlignRight)
        self.volumepercentlabel = QLabel(QApplication.translate("Label", " %",None))
        self.volumepercentlabel.setMinimumWidth(55)
        self.volumepercentlabel.setMaximumWidth(55)
        self.volumepercentlabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        self.volumeoutedit.editingFinished.connect(self.volume_percent)
        self.volumeinedit.editingFinished.connect(self.volume_percent)
        self.volumeUnitsComboBox = QComboBox()
        self.volumeUnitsComboBox.setMaximumWidth(60)
        self.volumeUnitsComboBox.setMinimumWidth(60)
        self.volumeUnitsComboBox.addItems(self.aw.qmc.volume_units)
        self.volumeUnitsComboBox.setCurrentIndex(self.aw.qmc.volume_units.index(self.aw.qmc.volume[2]))
        self.volumeUnitsComboBox.currentIndexChanged.connect(self.changeVolumeUnit)
        self.unitsComboBox.currentIndexChanged.connect(self.calculated_density)
        #density
        bean_density_label = QLabel("<b>" + QApplication.translate("Label", "Density",None) + "</b>")
        density_unit_label = QLabel("g/l")
        self.bean_density_in_edit = QLineEdit("%g" % self.aw.float2float(self.aw.qmc.density[0]))
        self.bean_density_in_edit.setValidator(self.aw.createCLocaleDoubleValidator(0., 999999., 1,self.bean_density_in_edit))
        self.bean_density_in_edit.setMinimumWidth(70)
        self.bean_density_in_edit.setMaximumWidth(70)
        self.bean_density_in_edit.setAlignment(Qt.AlignRight)
        self.bean_density_out_edit = QLineEdit("%g" % self.aw.float2float(self.aw.qmc.density_roasted[0]))
        self.bean_density_out_edit.setValidator(self.aw.createCLocaleDoubleValidator(0., 999999., 1,self.bean_density_out_edit))
        self.bean_density_out_edit.setMinimumWidth(70)
        self.bean_density_out_edit.setMaximumWidth(70)
        self.bean_density_out_edit.setAlignment(Qt.AlignRight)
        self.bean_density_in_edit.editingFinished.connect(self.density_in_editing_finished)
        self.bean_density_out_edit.editingFinished.connect(self.density_out_editing_finished)
        self.densitypercentlabel = QLabel(QApplication.translate("Label", "",None))
        self.densitypercentlabel.setMinimumWidth(55)
        self.densitypercentlabel.setMaximumWidth(55)
        self.densitypercentlabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        
        self.organicpercentlabel = QLabel(QApplication.translate("Label", "",None))
        self.organicpercentlabel.setMinimumWidth(55)
        self.organicpercentlabel.setMaximumWidth(55)
        self.organicpercentlabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        
        # volume calc button
        volumeCalcButton = QPushButton(QApplication.translate("Button", "calc",None))
        volumeCalcButton.clicked.connect(self.volumeCalculatorTimer)
        #the size of Buttons on the Mac is too small with 70,30 and ok with sizeHint/minimumSizeHint
        volumeCalcButton.setFocusPolicy(Qt.NoFocus)
        
        # add to recent
        self.addRecentButton = QPushButton("+")
        self.addRecentButton.clicked.connect(self.addRecentRoast)
        self.addRecentButton.setFocusPolicy(Qt.NoFocus)
        
        # delete from recent
        self.delRecentButton = QPushButton("-")
        self.delRecentButton.clicked.connect(self.delRecentRoast)
        self.delRecentButton.setFocusPolicy(Qt.NoFocus)

        self.recentRoastEnabled()
        
        #bean size
        bean_size_label = QLabel("<b>" + QApplication.translate("Label", "Screen",None) + "</b>")
        self.bean_size_min_edit = QLineEdit(str(int(round(self.aw.qmc.beansize_min))))
        self.bean_size_min_edit.editingFinished.connect(self.beanSizeMinEdited)
        self.bean_size_min_edit.setValidator(QIntValidator(0,50,self.bean_size_min_edit))
        self.bean_size_min_edit.setMinimumWidth(25)
        self.bean_size_min_edit.setMaximumWidth(25)
        self.bean_size_min_edit.setAlignment(Qt.AlignRight)
        bean_size_sep_label = QLabel("/")
        self.bean_size_max_edit = QLineEdit(str(int(round(self.aw.qmc.beansize_max))))
        self.bean_size_max_edit.editingFinished.connect(self.beanSizeMaxEdited)
        self.bean_size_max_edit.setValidator(QIntValidator(0,50,self.bean_size_max_edit))
        self.bean_size_max_edit.setMinimumWidth(25)
        self.bean_size_max_edit.setMaximumWidth(25)
        self.bean_size_max_edit.setAlignment(Qt.AlignRight)
        bean_size_unit_label = QLabel(QApplication.translate("Label", "18/64\u2033",None))
        #bean color
        color_label = QLabel("<b>" + QApplication.translate("Label", "Color",None) + "</b>")
        whole_color_label = QLabel("<b>" + QApplication.translate("Label", "Whole",None) + "</b>")
        self.whole_color_edit = QLineEdit(str(self.aw.qmc.whole_color))
        self.whole_color_edit.setValidator(QIntValidator(0, 1000, self.whole_color_edit))
        self.whole_color_edit.setMinimumWidth(70)
        self.whole_color_edit.setMaximumWidth(70)
        self.whole_color_edit.setAlignment(Qt.AlignRight)
        ground_color_label = QLabel("<b>" + QApplication.translate("Label", "Ground",None) + "</b>")
        self.ground_color_edit = QLineEdit(str(self.aw.qmc.ground_color))
        self.ground_color_edit.setValidator(QIntValidator(0, 1000, self.ground_color_edit))
        self.ground_color_edit.setMinimumWidth(70)
        self.ground_color_edit.setMaximumWidth(70)
        self.ground_color_edit.setAlignment(Qt.AlignRight)
        self.bean_size_min_edit.setAlignment(Qt.AlignRight)
        self.bean_size_max_edit.setAlignment(Qt.AlignRight)
        self.colorSystemComboBox = QComboBox()
        self.colorSystemComboBox.addItems(self.aw.qmc.color_systems)
        self.colorSystemComboBox.setCurrentIndex(self.aw.qmc.color_system_idx)
        #Greens Temp
        greens_temp_label = QLabel("<b>" + QApplication.translate("Label", "Beans",None) + "</b>")
        greens_temp_unit_label = QLabel(self.aw.qmc.mode)
        self.greens_temp_edit = QLineEdit()
        self.greens_temp_edit.setText("%g" % self.aw.float2float(self.aw.qmc.greens_temp))
        self.greens_temp_edit.setMaximumWidth(60)
        self.greens_temp_edit.setValidator(self.aw.createCLocaleDoubleValidator(-9999., 999999., 1, self.greens_temp_edit)) # range to 1000 needed to trigger editing_finished on input "12,2"
        self.greens_temp_edit.setAlignment(Qt.AlignRight)
        self.greens_temp_edit.editingFinished.connect(self.greens_temp_editing_finished)
        greens_temp = QHBoxLayout()
        greens_temp.addStretch()
        #Moisture Greens
        moisture_label = QLabel("<b>" + QApplication.translate("Label", "Moisture",None) + "</b>")
        moisture_greens_unit_label = QLabel(QApplication.translate("Label", "%",None))
        self.moisture_greens_edit = QLineEdit()
        self.moisture_greens_edit.setText("%g" % self.aw.float2float(self.aw.qmc.moisture_greens))
        self.moisture_greens_edit.setMaximumWidth(70)
        self.moisture_greens_edit.setValidator(self.aw.createCLocaleDoubleValidator(0., 100., 1, self.moisture_greens_edit))
        self.moisture_greens_edit.setAlignment(Qt.AlignRight)
        #Moisture Roasted
        #bag humidity
        moisture_roasted_label = QLabel("<b>" + QApplication.translate("Label", "Roasted",None) + "</b>")
        moisture_roasted_unit_label = QLabel(QApplication.translate("Label", "%",None))
        self.moisture_roasted_edit = QLineEdit()
        self.moisture_roasted_edit.setText("%g" % self.aw.float2float(self.aw.qmc.moisture_roasted))
        self.moisture_roasted_edit.setMaximumWidth(70)
        self.moisture_roasted_edit.setValidator(self.aw.createCLocaleDoubleValidator(0., 100., 1, self.moisture_roasted_edit))
        self.moisture_roasted_edit.setAlignment(Qt.AlignRight)
        self.moisturepercentlabel = QLabel(QApplication.translate("Label", "",None))
        self.moisturepercentlabel.setMinimumWidth(55)
        self.moisturepercentlabel.setMaximumWidth(55)
        self.moisturepercentlabel.setAlignment(Qt.AlignRight | Qt.AlignVCenter)
        self.moisture_greens_edit.editingFinished.connect(self.moistureEdited)
        self.moisture_roasted_edit.editingFinished.connect(self.moistureEdited)

        moisture_roasted = QHBoxLayout()
        moisture_roasted.addWidget(moisture_roasted_label)
        moisture_roasted.addWidget(moisture_roasted_unit_label)
        moisture_roasted.addStretch()
        #Ambient temperature (uses display mode as unit (F or C)
        ambientlabel = QLabel("<b>" + QApplication.translate("Label", "Ambient Conditions",None) + "</b>")
        ambientunitslabel = QLabel(self.aw.qmc.mode)
        ambient_humidity_unit_label = QLabel(QApplication.translate("Label", "%",None))
        self.ambient_humidity_edit = QLineEdit()
        self.ambient_humidity_edit.setText("%g" % self.aw.float2float(self.aw.qmc.ambient_humidity))
        self.ambient_humidity_edit.setMinimumWidth(50)
        self.ambient_humidity_edit.setMaximumWidth(50)
        self.ambient_humidity_edit.setValidator(self.aw.createCLocaleDoubleValidator(0., 9999999., 1, self.ambient_humidity_edit))
        self.ambient_humidity_edit.setAlignment(Qt.AlignRight)
        self.ambient_humidity_edit.editingFinished.connect(self.ambient_humidity_editing_finished)
        self.ambientedit = QLineEdit()
        self.ambientedit.setText("%g" % self.aw.float2float(self.aw.qmc.ambientTemp))
        self.ambientedit.setMinimumWidth(50)
        self.ambientedit.setMaximumWidth(50)
        self.ambientedit.setValidator(self.aw.createCLocaleDoubleValidator(-9999., 9999999., 1, self.ambientedit))  # larger range needed to triger editing_finished
        self.ambientedit.setAlignment(Qt.AlignRight)
        self.ambientedit.editingFinished.connect(self.ambientedit_editing_finished)
        pressureunitslabel = QLabel("hPa")
        self.pressureedit = QLineEdit()
        self.pressureedit.setText("%g" % self.aw.float2float(self.aw.qmc.ambient_pressure))
        self.pressureedit.setMinimumWidth(55)
        self.pressureedit.setMaximumWidth(55)
        self.pressureedit.setValidator(self.aw.createCLocaleDoubleValidator(0, 9999999., 1, self.pressureedit))
        self.pressureedit.setAlignment(Qt.AlignRight)
        self.pressureedit.editingFinished.connect(self.pressureedit_editing_finished)
        ambient = QHBoxLayout()
        ambient.addWidget(self.ambient_humidity_edit)
        ambient.addSpacing(1)
        ambient.addWidget(ambient_humidity_unit_label)
        ambient.addSpacing(7)
        ambient.addWidget(self.ambientedit)
        ambient.addSpacing(1)
        ambient.addWidget(ambientunitslabel)
        ambient.addSpacing(7)
        ambient.addWidget(self.pressureedit)
        ambient.addSpacing(1)
        ambient.addWidget(pressureunitslabel)
        ambient.addStretch()
        self.organiclosslabel = QLabel()
        self.scaleWeight = QLabel()
        self.scaleWeightAccumulated = ClickableQLabel("")
        self.scaleWeightAccumulated.clicked.connect(self.resetScaleSet)
        # NOTES
        roastertypelabel = QLabel()
        roastertypelabel.setText("<b>" + QApplication.translate("Label", "Machine",None) + "</b>")
        operatorlabel = QLabel()
        operatorlabel.setText("<b> " + QApplication.translate("Label", "Operator",None) + "</b>")
        organizationlabel = QLabel()
        organizationlabel.setText("<b> " + QApplication.translate("Label", "Organization",None) + "</b>")
        drumspeedlabel = QLabel()
        drumspeedlabel.setText("<b> " + QApplication.translate("Label", "Drum Speed",None) + "</b>")
        roastinglabel = QLabel("<b>" + QApplication.translate("Label", "Roasting Notes",None) + "</b>")
        self.roastingeditor = QTextEdit()
#        self.roastingeditor.setMaximumHeight(125)
        if self.aw.qmc.roastingnotes is not None:
            self.roastingeditor.setPlainText(self.aw.qmc.roastingnotes)
        cuppinglabel = QLabel("<b>" + QApplication.translate("Label", "Cupping Notes",None) + "</b>")
        self.cuppingeditor =  QTextEdit()
#        self.cuppingeditor.setMaximumHeight(125)
        if self.aw.qmc.cuppingnotes is not None:
            self.cuppingeditor.setPlainText(self.aw.qmc.cuppingnotes)
        # Flags
        self.heavyFC = QCheckBox(QApplication.translate("CheckBox","Heavy FC", None))
        self.heavyFC.setChecked(self.aw.qmc.heavyFC_flag)
        self.heavyFC.stateChanged.connect(self.roastflagHeavyFCChanged)
        self.lowFC = QCheckBox(QApplication.translate("CheckBox","Low FC", None))
        self.lowFC.setChecked(self.aw.qmc.lowFC_flag)
        self.lowFC.stateChanged.connect(self.roastflagLowFCChanged)
        self.lightCut = QCheckBox(QApplication.translate("CheckBox","Light Cut", None))
        self.lightCut.setChecked(self.aw.qmc.lightCut_flag)
        self.lightCut.stateChanged.connect(self.roastflagLightCutChanged)
        self.darkCut = QCheckBox(QApplication.translate("CheckBox","Dark Cut", None))
        self.darkCut.setChecked(self.aw.qmc.darkCut_flag)
        self.darkCut.stateChanged.connect(self.roastflagDarkCutChanged)        
        self.drops = QCheckBox(QApplication.translate("CheckBox","Drops", None))
        self.drops.setChecked(self.aw.qmc.drops_flag)
        self.drops.stateChanged.connect(self.roastflagDropsChanged)
        self.oily = QCheckBox(QApplication.translate("CheckBox","Oily", None))
        self.oily.setChecked(self.aw.qmc.oily_flag)
        self.oily.stateChanged.connect(self.roastflagOilyChanged)
        self.uneven = QCheckBox(QApplication.translate("CheckBox","Uneven", None))
        self.uneven.setChecked(self.aw.qmc.uneven_flag)
        self.tipping = QCheckBox(QApplication.translate("CheckBox","Tipping", None))
        self.tipping.setChecked(self.aw.qmc.tipping_flag)
        self.scorching = QCheckBox(QApplication.translate("CheckBox","Scorching", None))
        self.scorching.setChecked(self.aw.qmc.scorching_flag)
        self.divots = QCheckBox(QApplication.translate("CheckBox","Divots", None))
        self.divots.setChecked(self.aw.qmc.divots_flag)

        # connect the ArtisanDialog standard OK/Cancel buttons
        self.dialogbuttons.accepted.connect(self.accept)
        self.dialogbuttons.rejected.connect(self.cancel_dialog)
        
        # container tare
        self.tareComboBox = QComboBox()
        self.tareComboBox.addItem("<edit> TARE")
        self.tareComboBox.addItem("")
        self.tareComboBox.insertSeparator(1)
        self.tareComboBox.addItems(self.aw.qmc.container_names)
        self.tareComboBox.setMaximumWidth(80)
        self.tareComboBox.setMinimumWidth(80)
        self.tareComboBox.setCurrentIndex(self.aw.qmc.container_idx + 3)
        self.tareComboBox.currentIndexChanged.connect(self.tareChanged)
        self.tarePopupEnabled = True # controls if the popup will process tareChange events
        
        # in button
        inButton = QPushButton(QApplication.translate("Button", "in",None))
        inButton.clicked.connect(self.inWeight)
        #the size of Buttons on the Mac is too small with 70,30 and ok with sizeHint/minimumSizeHint
        inButton.setFocusPolicy(Qt.NoFocus)
        inButton.setMinimumWidth(70)
        inButtonLayout = QHBoxLayout()
        inButtonLayout.addStretch()
        inButtonLayout.addWidget(inButton)
        inButtonLayout.addStretch()
        # out button
        outButton = QPushButton(QApplication.translate("Button", "out",None))
        outButton.clicked.connect(self.outWeight)
        #the size of Buttons on the Mac is too small with 70,30 and ok with sizeHint/minimumSizeHint
        outButton.setFocusPolicy(Qt.NoFocus)
        outButton.setMinimumWidth(70)
        outButtonLayout = QHBoxLayout()
        outButtonLayout.addStretch()
        outButtonLayout.addWidget(outButton)
        outButtonLayout.addStretch()
        # scan whole button
        scanWholeButton = QPushButton(QApplication.translate("Button", "scan",None))
        scanWholeButton.clicked.connect(self.scanWholeColor)
        scanWholeButton.setMinimumWidth(80)
        #the size of Buttons on the Mac is too small with 70,30 and ok with sizeHint/minimumSizeHint
        scanWholeButton.setFocusPolicy(Qt.NoFocus)
        # scan ground button
        scanGroundButton = QPushButton(QApplication.translate("Button", "scan",None))
        scanGroundButton.setMinimumWidth(80)
        scanGroundButton.clicked.connect(self.scanGroundColor)
        #the size of Buttons on the Mac is too small with 70,30 and ok with sizeHint/minimumSizeHint
        scanGroundButton.setFocusPolicy(Qt.NoFocus)
        # Ambient Temperature Source Selector
        self.ambientComboBox = QComboBox()
        self.ambientComboBox.addItems(self.buildAmbientTemperatureSourceList())
        self.ambientComboBox.setCurrentIndex(self.aw.qmc.ambientTempSource)
        self.ambientComboBox.currentIndexChanged.connect(self.ambientComboBoxIndexChanged)
        ambientSourceLabel = QLabel(QApplication.translate("Label", "Ambient Source",None))
        updateAmbientTemp = QPushButton(QApplication.translate("Button", "update",None))
        updateAmbientTemp.setFocusPolicy(Qt.NoFocus)
        updateAmbientTemp.clicked.connect(self.updateAmbientTemp)
        ##### LAYOUTS
        timeLayout = QGridLayout()
        timeLayout.setVerticalSpacing(3)
        timeLayout.setHorizontalSpacing(3)
        timeLayout.addWidget(chargelabel,0,0)
        timeLayout.addWidget(drylabel,0,1)
        timeLayout.addWidget(Cstartlabel,0,2)
        timeLayout.addWidget(Cendlabel,0,3)
        timeLayout.addWidget(CCstartlabel,0,4)
        timeLayout.addWidget(CCendlabel,0,5)
        timeLayout.addWidget(droplabel,0,6)
        timeLayout.addWidget(coollabel,0,7)
        timeLayout.addWidget(self.chargeedit,1,0,Qt.AlignHCenter)
        timeLayout.addWidget(self.dryedit,1,1,Qt.AlignHCenter)
        timeLayout.addWidget(self.Cstartedit,1,2,Qt.AlignHCenter)
        timeLayout.addWidget(self.Cendedit,1,3,Qt.AlignHCenter)
        timeLayout.addWidget(self.CCstartedit,1,4,Qt.AlignHCenter)
        timeLayout.addWidget(self.CCendedit,1,5,Qt.AlignHCenter)
        timeLayout.addWidget(self.dropedit,1,6,Qt.AlignHCenter)
        timeLayout.addWidget(self.cooledit,1,7,Qt.AlignHCenter)
        textLayout = QGridLayout()
        textLayout.setHorizontalSpacing(3)
        textLayout.setVerticalSpacing(2)
        textLayout.setContentsMargins(0,0,0,0)
        textLayout.addWidget(datelabel1,0,0)
        datebatch = QHBoxLayout()
        datebatch.addWidget(dateedit)
        datebatch.addSpacing(15)
        datebatch.addWidget(batchlabel)
        datebatch.addSpacing(7)
        datebatch.addLayout(self.batchLayout)
        if not self.aw.superusermode: # and self.aw.qmc.batchcounter > -1:
            self.batchLayout.addWidget(self.batchedit)
        textLayout.addLayout(datebatch,0,1)
        
        titleLine = QHBoxLayout()
        titleLine.addWidget(self.titleedit)
        titleLine.addWidget(self.addRecentButton)
        titleLine.addWidget(self.delRecentButton)
        titleLine.addSpacing(2)
        titleLine.addWidget(self.titleShowAlwaysFlag) 
        
        self.template_line = QLabel("P249 Guatemala")
        template_font = self.template_line.font()
        template_font.setPointSize(template_font.pointSize() -1)
        self.template_line.setFont(template_font)
        
#PLUS
        self.plus_store_selected = None # holds the hr_id of the store of the selected coffee or blend
        self.plus_store_selected_label = None # the label of the selected store
        self.plus_coffee_selected = None # holds the hr_id of the selected coffee
        self.plus_coffee_selected_label = None # the label of the selected coffee
        self.plus_blend_selected_label = None # the name of the selected blend
        self.plus_blend_selected_spec = None # holds the blend dict specification of the selected blend
        self.plus_blend_selected_spec_labels = None # the list of coffee labels of the selected blend specification
        if self.aw.plus_account is not None:
            # variables populated by stock data as rendered in the corresponding popups
            self.plus_stores = None
            self.plus_coffees = None
            self.plus_blends = None
            self.plus_default_store = self.aw.qmc.plus_default_store
            # current selected stock/coffee/blend _id
            if self.aw.qmc.plus_store is not None:
                self.plus_store_selected = self.aw.qmc.plus_store # holds the store corresponding to the plus_coffee_selected/plus_blend_selected
                self.plus_store_selected_label = self.aw.qmc.plus_store_label
            if self.aw.qmc.plus_coffee is not None:
                self.plus_coffee_selected = self.aw.qmc.plus_coffee
                self.plus_coffee_selected_label = self.aw.qmc.plus_coffee_label
            else:
                if self.aw.qmc.plus_blend_spec is not None:
                    self.plus_blend_selected_label = self.aw.qmc.plus_blend_label
                    self.plus_blend_selected_spec = self.aw.qmc.plus_blend_spec
                    self.plus_blend_selected_spec_labels = self.aw.qmc.plus_blend_spec_labels
            self.plus_amount_selected = None # holds the amount of the selected coffee/blend if known
            plusCoffeeslabel = QLabel("<b>" + QApplication.translate("Label", "Stock",None) + "</b>")
            self.plusStoreslabel = QLabel("<b>" + QApplication.translate("Label", "Store",None) + "</b>")
            self.plusBlendslabel = QLabel("<b>" + QApplication.translate("Label", "Blend",None) + "</b>")
            self.plus_stores_combo = QComboBox() 
            self.plus_coffees_combo = QComboBox()
            self.plus_blends_combo = QComboBox()
            self.plus_stores_combo.currentIndexChanged.connect(self.storeSelectionChanged)
            self.plus_coffees_combo.currentIndexChanged.connect(self.coffeeSelectionChanged)
            self.plus_blends_combo.currentIndexChanged.connect(self.blendSelectionChanged)
            self.plus_selected_line = QLabel()
            self.plus_selected_line.setOpenExternalLinks(True)
            label_font = self.plus_selected_line.font()
            label_font.setPointSize(label_font.pointSize() -2)
            self.plus_selected_line.setFont(label_font)
            self.populatePlusCoffeeBlendCombos()
            # layouting
            self.plus_coffees_combo.setMinimumContentsLength(15)
            self.plus_blends_combo.setMinimumContentsLength(10)
            self.plus_stores_combo.setMinimumContentsLength(10)
            self.plus_stores_combo.setMaximumWidth(120)            
            self.plus_coffees_combo.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum)
            self.plus_coffees_combo.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLength)
            self.plus_blends_combo.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum)
            self.plus_blends_combo.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLength)
            self.plus_stores_combo.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Maximum)
            self.plus_stores_combo.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLength)
            # plus widget row
            plusLine = QHBoxLayout()
            plusLine.addWidget(self.plus_coffees_combo)
            plusLine.addSpacing(15)
            plusLine.addWidget(self.plusBlendslabel)
            plusLine.addSpacing(5)
            plusLine.addWidget(self.plus_blends_combo)
            plusLine.addSpacing(15)
            plusLine.addWidget(self.plusStoreslabel)
            plusLine.addSpacing(5)
            plusLine.addWidget(self.plus_stores_combo)
            textLayout.addWidget(self.plus_selected_line,4,1)
            textLayout.addWidget(plusCoffeeslabel,5,0)
            textLayout.addLayout(plusLine,5,1)
            textLayoutPlusOffset = 2 # to insert the plus widget row, we move the remaining ones one step lower
        else:
            textLayoutPlusOffset = 0
        textLayout.addWidget(self.template_line,2,1)
        textLayout.addWidget(titlelabel,3,0)
        textLayout.addLayout(titleLine,3,1)
        textLayout.addWidget(beanslabel,4+textLayoutPlusOffset,0)
        textLayout.addWidget(self.beansedit,4+textLayoutPlusOffset,1)
        textLayout.addWidget(operatorlabel,5+textLayoutPlusOffset,0)
            
        roasteroperator = QHBoxLayout()
        roasteroperator.addWidget(self.operator, stretch=3)
        roasteroperator.addSpacing(8)
        roasteroperator.addWidget(organizationlabel)
        roasteroperator.addSpacing(2)
        roasteroperator.addWidget(self.organization, stretch=3)
        roasteroperator.addSpacing(8)
        roasteroperator.addWidget(roastertypelabel)
        roasteroperator.addSpacing(2)
        roasteroperator.addWidget(self.roaster,stretch=3)
        roasteroperator.addSpacing(8)
        roasteroperator.addWidget(drumspeedlabel)
        roasteroperator.addSpacing(2)
        roasteroperator.addWidget(self.drumspeed,stretch=1)
        textLayout.addLayout(roasteroperator,5+textLayoutPlusOffset,1)
        
        beanSizeLayout = QHBoxLayout()
        beanSizeLayout.setSpacing(2)
        beanSizeLayout.addStretch()
        beanSizeLayout.addWidget(self.bean_size_min_edit)
        beanSizeLayout.addWidget(bean_size_sep_label)
        beanSizeLayout.addWidget(self.bean_size_max_edit)
        beanSizeLayout.addStretch()
        
        propGrid = QGridLayout()
        propGrid.setContentsMargins(0,0,0,0)
        propGrid.setHorizontalSpacing(3)
        propGrid.setVerticalSpacing(0)
        propGrid.addWidget(green_label,0,1,Qt.AlignCenter | Qt.AlignBottom)
        propGrid.addWidget(roasted_label,0,2,Qt.AlignCenter | Qt.AlignBottom)
        propGrid.addWidget(self.organicpercentlabel,0,4,Qt.AlignRight)
        propGrid.addWidget(self.organiclosslabel,0,5,1,3,Qt.AlignLeft)
        propGrid.addWidget(self.scaleWeight,0,8,1,2,Qt.AlignCenter)
        
        propGrid.addWidget(weightlabel,1,0)
        propGrid.addWidget(self.weightinedit,1,1,Qt.AlignRight)
        propGrid.addWidget(self.weightoutedit,1,2,Qt.AlignRight)
        propGrid.addWidget(self.unitsComboBox,1,3)
        propGrid.addWidget(self.weightpercentlabel,1,4,Qt.AlignRight)
        
        propGrid.setColumnStretch(5,10)
        
        if self.aw.scale.device is not None and self.aw.scale.device != "" and self.aw.scale.device != "None":
            propGrid.addWidget(self.tareComboBox,1,7)
            propGrid.addLayout(inButtonLayout,1,8)
            propGrid.addLayout(outButtonLayout,1,9)
            
            if self.aw.scale.device == "acaia":
                try:
                    with suppress_stdout_stderr():
                        # if selected scale is the Acaia, start the BLE interface
                        from artisanlib.ble import BleInterface
                        from artisanlib.acaia import AcaiaBLE
                        acaia = AcaiaBLE()
                        self.ble = BleInterface(
                            acaia.SERVICE_UUID,
                            acaia.CHAR_UUID,
                            acaia.processData,
                            acaia.sendHeartbeat,
                            acaia.sendStop,
                            acaia.reset)
                            
                    # start BLE loop
                    self.ble.deviceDisconnected.connect(self.ble_scan_failed)
                    self.ble.weightChanged.connect(self.ble_weight_changed)
                    self.ble.batteryChanged.connect(self.ble_battery_changed)
                    self.ble.scanDevices()
                except:
                    pass
            elif self.aw.scale.device in ["KERN NDE","Shore 930"]:
                self.connectScaleSignal.connect(self.connectScaleLoop)
                QTimer.singleShot(2,lambda : self.connectScaleSignal.emit())
        
        propGrid.addWidget(volumelabel,2,0)
        propGrid.addWidget(self.volumeinedit,2,1,Qt.AlignRight)
        propGrid.addWidget(self.volumeoutedit,2,2,Qt.AlignRight)
        propGrid.addWidget(self.volumeUnitsComboBox,2,3)
        propGrid.addWidget(self.volumepercentlabel,2,4,Qt.AlignRight)
        propGrid.addWidget(self.scaleWeightAccumulated,2,7,1,2,Qt.AlignCenter)
        propGrid.addWidget(volumeCalcButton,2,9)
        
        propGrid.setRowMinimumHeight(3,self.volumeUnitsComboBox.minimumSizeHint().height())
        propGrid.addWidget(bean_density_label,3,0)
        propGrid.addWidget(self.bean_density_in_edit,3,1,Qt.AlignRight)
        propGrid.addWidget(self.bean_density_out_edit,3,2,Qt.AlignRight)
        propGrid.addWidget(density_unit_label,3,3,Qt.AlignCenter)
        propGrid.addWidget(self.densitypercentlabel,3,4,Qt.AlignRight)
        
        propGrid.addWidget(bean_size_label,3,7)
        propGrid.addLayout(beanSizeLayout,3,8,Qt.AlignRight)
        propGrid.addWidget(bean_size_unit_label,3,9,Qt.AlignCenter)
        
        propGrid.addWidget(moisture_label,4,0)
        propGrid.addWidget(self.moisture_greens_edit,4,1,Qt.AlignRight)
        propGrid.addWidget(self.moisture_roasted_edit,4,2,Qt.AlignRight)
        propGrid.addWidget(moisture_greens_unit_label,4,3,Qt.AlignCenter)
        propGrid.addWidget(self.moisturepercentlabel,4,4,Qt.AlignRight)
        propGrid.addWidget(greens_temp_label,4,7)
        propGrid.addWidget(self.greens_temp_edit,4,8,Qt.AlignRight)
        propGrid.addWidget(greens_temp_unit_label,4,9,Qt.AlignCenter)
        
        propGrid.setRowMinimumHeight(7,30)
        propGrid.addWidget(whole_color_label,7,1,Qt.AlignCenter | Qt.AlignBottom)
        propGrid.addWidget(ground_color_label,7,2,Qt.AlignCenter | Qt.AlignBottom)
        
        propGrid.addWidget(color_label,8,0)
        propGrid.addWidget(self.whole_color_edit,8,1,Qt.AlignRight)
        propGrid.addWidget(self.ground_color_edit,8,2,Qt.AlignRight)
        propGrid.addWidget(self.colorSystemComboBox,8,3,1, 2)
                
        if self.aw.color.device is not None and self.aw.color.device != "" and self.aw.color.device not in ["None","Tiny Tonino", "Classic Tonino"]:
            propGrid.addWidget(scanWholeButton,8,6)
        if self.aw.color.device is not None and self.aw.color.device != "" and self.aw.color.device != "None":
            propGrid.addWidget(scanGroundButton,8,7)
            
        propGrid.addWidget(ambientSourceLabel,8,8,1,2,Qt.AlignRight | Qt.AlignBottom)
        
        ambientGrid = QGridLayout()
        ambientGrid.setContentsMargins(0,0,0,0)
        ambientGrid.setHorizontalSpacing(3)
        ambientGrid.setVerticalSpacing(0)
        ambientGrid.addWidget(ambientlabel,2,0)
        ambientGrid.addLayout(ambient,2,2,1,5)
        ambientGrid.addWidget(updateAmbientTemp,2,10)
        ambientGrid.addWidget(self.ambientComboBox,2,11,Qt.AlignRight)
        ambientGrid.setColumnMinimumWidth(3, 11)
        ambientGrid.setColumnMinimumWidth(5, 11)
        ambientGrid.setColumnMinimumWidth(8, 11)
        roastFlagsLayout = QHBoxLayout()
        roastFlagsGrid = QGridLayout()
        roastFlagsGrid.addWidget(self.lowFC,0,0)
        roastFlagsGrid.addWidget(self.heavyFC,1,0)
        roastFlagsGrid.addWidget(self.lightCut,0,1)
        roastFlagsGrid.addWidget(self.darkCut,1,1)
        roastFlagsGrid.addWidget(self.drops,0,2)
        roastFlagsGrid.addWidget(self.oily,1,2)
        roastFlagsGrid.addWidget(self.uneven,0,3)
        roastFlagsGrid.addWidget(self.tipping,1,3)
        roastFlagsGrid.addWidget(self.scorching,0,4)
        roastFlagsGrid.addWidget(self.divots,1,4)
        roastFlagsLayout.addLayout(roastFlagsGrid)
        roastFlagsLayout.addStretch()
        anotationLayout = QVBoxLayout()
        anotationLayout.addWidget(roastinglabel)
        anotationLayout.addWidget(self.roastingeditor)
        anotationLayout.addLayout(roastFlagsLayout)
        anotationLayout.addWidget(cuppinglabel)
        anotationLayout.addWidget(self.cuppingeditor)
        okLayout = QHBoxLayout()
        okLayout.addWidget(self.roastproperties)
        okLayout.addStretch()
        okLayout.addSpacing(3)
        okLayout.addWidget(self.roastpropertiesAutoOpen)
        okLayout.addStretch()
        okLayout.addSpacing(3)
        okLayout.addWidget(self.roastpropertiesAutoOpenDROP)
        okLayout.addStretch()
        okLayout.addWidget(self.dialogbuttons)
        okLayout.setSpacing(10)
        okLayout.setContentsMargins(5, 15, 5, 15) # left, top, right, bottom
        timeLayoutBox = QHBoxLayout()
        timeLayoutBox.addStretch()
        timeLayoutBox.addLayout(timeLayout)
        timeLayoutBox.addStretch()
        mainLayout = QVBoxLayout()
        mainLayout.setContentsMargins(3, 3, 3, 3)
        eventbuttonLayout = QHBoxLayout()
        eventbuttonLayout.addWidget(self.copyeventTableButton)
        eventbuttonLayout.addWidget(self.createalarmTableButton)
        eventbuttonLayout.addStretch()
        eventbuttonLayout.addWidget(self.clusterEventsButton)
        eventbuttonLayout.addWidget(self.ordereventTableButton)
        eventbuttonLayout.addStretch()
        eventbuttonLayout.addWidget(self.clearEventsButton)
        eventbuttonLayout.addStretch()
        eventbuttonLayout.addWidget(self.deleventTableButton)
        eventbuttonLayout.addWidget(self.neweventTableButton)
        databuttonLayout = QHBoxLayout()
        databuttonLayout.addWidget(self.copydataTableButton)
        databuttonLayout.addStretch()
        #tab 1
        self.tab1aLayout = QVBoxLayout()
        self.tab1aLayout.setContentsMargins(0,0,0,0)
        self.tab1aLayout.setSpacing(0)
#        self.tab1aLayout.addLayout(mainLayout)
#        self.tab1aLayout.addStretch()
        self.tab1aLayout.addLayout(textLayout)
        self.tab1aLayout.addStretch()
        self.tab1aLayout.setSpacing(8)
        self.tab1aLayout.addLayout(propGrid)
        self.tab1aLayout.addLayout(ambientGrid)
        tab1Layout = QVBoxLayout()
#        tab1Layout.addStretch()
        tab1Layout.setContentsMargins(5, 5, 5, 5) # left, top, right, bottom
        tab1Layout.addLayout(self.tab1aLayout)
        tab1Layout.setSpacing(0)
        tab1Layout.addStretch()
        # set volume from density if given
        self.density_in_editing_finished()
        self.density_out_editing_finished()
        # set density from volume if given
        #tab 2
        tab2Layout = QVBoxLayout()
        tab2Layout.addLayout(anotationLayout)
        tab2Layout.setContentsMargins(5, 5, 5, 5) # left, top, right, bottom
        #tab3 events
        tab3Layout = QVBoxLayout()
        tab3Layout.addLayout(timeLayoutBox)
        tab3Layout.addWidget(self.eventtable)
        tab3Layout.addLayout(eventbuttonLayout)
        tab3Layout.setContentsMargins(5, 5, 5, 5) # left, top, right, bottom
        #tab 4 data
        tab4Layout = QVBoxLayout()
        tab4Layout.addWidget(self.datatable) 
        tab4Layout.addLayout(databuttonLayout)
        tab4Layout.setContentsMargins(5, 5, 5, 5) # left, top, right, bottom 
        #tabwidget
        self.TabWidget = QTabWidget()
        self.TabWidget.setContentsMargins(0,0,0,0)
        C1Widget = QWidget()
        C1Widget.setLayout(tab1Layout)
        self.TabWidget.addTab(C1Widget,QApplication.translate("Tab", "General",None))
        C2Widget = QWidget()
        C2Widget.setLayout(tab2Layout)
        self.TabWidget.addTab(C2Widget,QApplication.translate("Tab", "Notes",None))
        C3Widget = QWidget()
        C3Widget.setLayout(tab3Layout)
        self.TabWidget.addTab(C3Widget,QApplication.translate("Tab", "Events",None))
        C4Widget = QWidget()
        C4Widget.setLayout(tab4Layout)
        self.TabWidget.addTab(C4Widget,QApplication.translate("Tab", "Data",None)) 
        self.TabWidget.currentChanged.connect(self.tabSwitched)
        #incorporate layouts
        totallayout = QVBoxLayout()
        totallayout.addWidget(self.TabWidget)
        totallayout.addLayout(okLayout)
        totallayout.setContentsMargins(10,10,10,0)
        totallayout.setSpacing(0)
        self.volume_percent()
        self.setLayout(totallayout)

        self.titleedit.setFocus()

        self.updateTemplateLine()
        
        settings = QSettings()
        if settings.contains("RoastGeometry"):
            self.restoreGeometry(settings.value("RoastGeometry"))
        else:
            self.resize(self.minimumSizeHint())

#PLUS
        try:
            if self.aw.plus_account is not None:
                plus.stock.update()
                QTimer.singleShot(1500,lambda : self.populatePlusCoffeeBlendCombos())
        except:
            pass
        if platform.system() == 'Windows':
            self.dialogbuttons.button(QDialogButtonBox.Ok)
        else:
            self.dialogbuttons.button(QDialogButtonBox.Ok).setFocus()
    
    def enableBatchEdit(self):
        if not self.aw.superusermode and not self.batcheditmode:
            self.batcheditmode = True
            self.batchLayout.removeWidget(self.batchedit)
            self.defineBatchEditor()
    
    def defineBatchEditor(self):
        self.batchprefixedit = QLineEdit(self.aw.qmc.roastbatchprefix)
        self.batchcounterSpinBox = QSpinBox()
        self.batchcounterSpinBox.setRange(0,999999)
        self.batchcounterSpinBox.setSingleStep(1)
        self.batchcounterSpinBox.setValue(self.aw.qmc.roastbatchnr)
        self.batchcounterSpinBox.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter) 
        self.batchposSpinBox = QSpinBox()
        self.batchposSpinBox.setRange(1,99)
        self.batchposSpinBox.setSingleStep(1)
        self.batchposSpinBox.setValue(self.aw.qmc.roastbatchpos)
        self.batchposSpinBox.setAlignment(Qt.AlignRight|Qt.AlignTrailing|Qt.AlignVCenter)
        self.batchLayout.addWidget(self.batchprefixedit)
        self.batchLayout.addWidget(self.batchcounterSpinBox)
        self.batchLayout.addWidget(self.batchposSpinBox)
        
    def readScale(self):
        if self.disconnecting:
            self.aw.scale.closeport()
            self.scale_weight = None
            self.scale_battery = None
        else:
            if self.aw.scale.SP is None or not self.aw.scale.SP.isOpen():
                self.connectScaleSignal.emit()
            else:
                w,_,_ = self.aw.scale.readWeight()
                if w != -1:
                    self.scale_weight = w
                else:
                    self.scale_weight = None
                self.update_scale_weight()
                if self.volumedialog is not None:
                    self.scaleWeightUpdated.emit(w)
                self.readScaleSignal.emit()
    
    @pyqtSlot()
    def readScaleLoop(self):
        QTimer.singleShot(1000,lambda : self.readScale())
    
    @pyqtSlot()
    def connectScaleLoop(self):
        QTimer.singleShot(2000,lambda : self.connectScale())
        
    def connectScale(self):
        if self.disconnecting:
            self.aw.scale.closeport()
        else:
            res = self.aw.scale.connect(error=False)
            if res:
                self.readScaleSignal.connect(self.readScaleLoop)
                QTimer.singleShot(2,lambda : self.readScaleSignal.emit())
            else:
                self.connectScaleSignal.emit()

    @pyqtSlot()
    def resetScaleSet(self):
        self.scale_set = None
        self.updateScaleWeightAccumulated()
    
    def updateScaleWeightAccumulated(self,weight=None):
        if self.scale_set is None or weight is None:
            self.scaleWeightAccumulated.setText("")
        else:
            v = weight + self.scale_set
            if self.aw.qmc.weight_units.index(self.aw.qmc.weight[2]) in [0,1]:
                if v > 1000:
                    v_formatted = "{0:.2f}kg".format(v/1000)
                else:
                    v_formatted = "{0:.1f}g".format(v)
            # non-metric
            else:
                v = self.aw.convertWeight(v,0,self.aw.qmc.weight_units.index(self.aw.qmc.weight[2]))
                v_formatted = "{0:.2f}{1}".format(v,self.aw.qmc.weight[2])
            self.scaleWeightAccumulated.setText(v_formatted)

    def ble_scan_failed(self):
#        import datetime
#        ts = libtime.time()
#        st = datetime.datetime.fromtimestamp(ts).strftime('%Y-%m-%d %H:%M:%S')
#        print(st,"ble_scan_failed")
        self.scale_weight = None
        self.scale_battery = None
        self.scaleWeight.setText("")
        if self.ble is not None:
            QTimer.singleShot(200,lambda : self.ble.scanDevices())

    def ble_weight_changed(self,w):
        if w is not None:
            self.scale_weight = w
            self.update_scale_weight()
    
    def ble_battery_changed(self,b):
        if b is not None:
            self.scale_battery = b
            self.update_scale_weight()
            
    def update_scale_weight(self):
        tare = 0
        try:
            tare_idx = self.tareComboBox.currentIndex() - 3
            if tare_idx > -1:
                tare = self.aw.qmc.container_weights[tare_idx]
        except Exception:
            pass
        if self.scale_weight is not None and tare is not None:
            v = self.scale_weight - tare # weight in g
            unit = self.aw.qmc.weight_units.index(self.aw.qmc.weight[2])
            if unit == 0: # g selected
                # metric
                if v > 1000:
                    v_formatted = "{0:.0f}g".format(v)
                else:
                    v_formatted = "{0:.1f}g".format(v)
            elif unit == 1: # kg selected
                # metric (always keep the accuracy to the g
                v_formatted = "{0:.3f}kg".format(v/1000)
            # non-metric
            else:
                v = self.aw.convertWeight(v,0,self.aw.qmc.weight_units.index(self.aw.qmc.weight[2]))
                v_formatted = "{0:.2f}{1}".format(v,self.aw.qmc.weight[2])
            self.scaleWeight.setText(v_formatted)
            self.updateScaleWeightAccumulated(self.scale_weight - tare)
        else:
            self.scaleWeight.setText("")
            self.updateScaleWeightAccumulated()
            
    def updateTemplateLine(self):
        line = ""
        if self.template_file:
            if self.template_batchprefix:
                line = self.template_batchprefix
            if self.template_batchnr:
                line = line + str(self.template_batchnr)
            if self.template_name:
                if len(line) != 0:
                    line = line + " "
                line = line + self.template_name
        if len(line) > 0:
            line = QApplication.translate("Label", "Template",None) + ": " + line
        self.template_line.setText(line)
            
    def updatePlusSelectedLine(self):
        try:
            if sys.platform.startswith("darwin") and darkdetect.isDark() and appFrozen():
                dark_mode_link_color = " style=\"color: #e5e9ec;\""
            else:
                dark_mode_link_color = ""
            line = ""
            if self.plus_coffee_selected is not None and self.plus_coffee_selected_label:
                line = '<a href="{0}"{2}>{1}</a>'.format(plus.util.coffeeLink(self.plus_coffee_selected),self.plus_coffee_selected_label,dark_mode_link_color)
            elif self.plus_blend_selected_spec and self.plus_blend_selected_spec_labels:
                for i,l in sorted(zip(self.plus_blend_selected_spec["ingredients"],self.plus_blend_selected_spec_labels), key=lambda tup:tup[0]["ratio"],reverse = True):
                    if line:
                        line = line + ", "
                    c = '<a href="{0}"{2}>{1}</a>'.format(plus.util.coffeeLink(i["coffee"]),l,dark_mode_link_color)
                    line = line + str(int(round(i["ratio"]*100))) + "% " + c
            if line and len(line)>0 and self.plus_store_selected is not None and self.plus_store_selected_label is not None:
                line = line + ', <a href="{0}"{2}>{1}</a>'.format(plus.util.storeLink(self.plus_store_selected),self.plus_store_selected_label,dark_mode_link_color)
            self.plus_selected_line.setText(line)
        except Exception:
            pass
    
    @pyqtSlot()
    def beansEdited(self):
        self.modified_beans = self.beansedit.toPlainText()
    
    @pyqtSlot()
    def beanSizeMinEdited(self):
        self.modified_beansize_min_text = self.bean_size_min_edit.text()
    
    @pyqtSlot()
    def beanSizeMaxEdited(self):
        self.modified_beansize_max_text = self.bean_size_max_edit.text()

    @pyqtSlot()
    def moistureEdited(self):
        self.moisture_greens_edit.setText(self.aw.comma2dot(str(self.moisture_greens_edit.text())))
        self.moisture_roasted_edit.setText(self.aw.comma2dot(str(self.moisture_roasted_edit.text())))
        self.modified_moisture_greens_text = self.moisture_greens_edit.text()
        self.calculated_organic_loss()
        
    def plus_popups_set_enabled(self,b):
        try:
            self.plus_stores_combo.setEnabled(b)
            self.plus_coffees_combo.setEnabled(b)
            self.plus_blends_combo.setEnabled(b)
        except:
            pass

    # storeIndex is the index of the selected entry in the popup
    def populatePlusCoffeeBlendCombos(self,storeIndex=None):
        try: # this can crash if dialog got closed while this is processed in a different thread!
            self.plus_popups_set_enabled(False)
            
            #---- Stores
            
            if storeIndex is None or storeIndex == -1:
                self.plus_stores = plus.stock.getStores()
                try:
                    if len(self.plus_stores) == 1:
                        self.plus_default_store = plus.stock.getStoreId(self.plus_stores[0])
                    if len(self.plus_stores) < 2:
                        self.plusStoreslabel.setVisible(False)
                        self.plus_stores_combo.setVisible(False)
                    else:
                        self.plusStoreslabel.setVisible(True)
                        self.plus_stores_combo.setVisible(True)
                except:
                    pass
                self.plus_stores_combo.blockSignals(True)       
                self.plus_stores_combo.clear()
                self.plus_stores_combo.addItems([""] + plus.stock.getStoreLabels(self.plus_stores))
                p = plus.stock.getStorePosition(self.plus_default_store,self.plus_stores)
                if p is None:
                    self.plus_stores_combo.setCurrentIndex(0)
                else:
                    # we set to the default_store if available
                    self.plus_stores_combo.setCurrentIndex(p+1)
                self.plus_stores_combo.blockSignals(False)
            
            storeIdx = self.plus_stores_combo.currentIndex()
            
            # we reset the store if a coffee or blend is selected and the selected store is not equal to the default store
            # we clean the coffee/blend selection as it does not fit
            if storeIdx > 0 and (self.plus_coffee_selected or self.plus_blend_selected_spec) and self.plus_store_selected != plus.stock.getStoreId(self.plus_stores[storeIdx-1]):
                self.defaultCoffeeData()
                self.plus_amount_selected = None
                self.plus_store_selected_label = None
                if self.plus_coffee_selected:
                    self.plus_coffee_selected = None
                    self.plus_coffee_selected_label = None
                if self.plus_blend_selected_spec:
                    self.plus_blend_selected_label = None
                    self.plus_blend_selected_spec = None
                    self.plus_blend_selected_spec_labels = None
            
            if storeIdx:
                self.plus_default_store = plus.stock.getStoreId(self.plus_stores[storeIdx-1])
            else:
                self.plus_default_store = None
            
            mark_coffee_fields = False
            
            #---- Coffees
            
            self.plus_coffees = plus.stock.getCoffees(self.unitsComboBox.currentIndex(),self.plus_default_store)
            self.plus_coffees_combo.blockSignals(True)  
            self.plus_coffees_combo.clear()
            self.plus_coffees_combo.addItems([""] + plus.stock.getCoffeesLabels(self.plus_coffees))
            
            p = None
            if self.plus_coffee_selected:
                p = plus.stock.getCoffeeStockPosition(self.plus_coffee_selected,self.plus_store_selected,self.plus_coffees)
            if p is None:
                # not in the current stock
                self.plus_coffees_combo.setCurrentIndex(0)
                #self.plus_coffee_selected = None # we don't "deselect" a coffee just because it is not in the popup!
                self.plus_coffees_combo.blockSignals(False)
            else:
                # if roast is complete (charge and drop are set) 
                if self.aw.qmc.timeindex[0] > -1 and self.aw.qmc.timeindex[6] > 0:
                    # we first change the index and then unblock signals to avoid properties being overwritten from the selected coffee
                    self.plus_coffees_combo.setCurrentIndex(p+1)
                    self.plus_coffees_combo.blockSignals(False)
                else:
                    # if roast is not yet complete we unblock the signals before changing the index to get the coffee data be filled in
                    self.plus_coffees_combo.blockSignals(False)
                    self.plus_coffees_combo.setCurrentIndex(p+1)
                mark_coffee_fields = True
            
            
            #---- Blends  
    
            self.plus_blends = plus.stock.getBlends(self.unitsComboBox.currentIndex(),self.plus_default_store)
            self.plus_blends_combo.blockSignals(True)  
            self.plus_blends_combo.clear()
            self.plus_blends_combo.addItems([""] + plus.stock.getBlendLabels(self.plus_blends)) 
            
            if len(self.plus_blends) == 0:
                self.plusBlendslabel.setVisible(False)
                self.plus_blends_combo.setVisible(False)
            else:
                self.plusBlendslabel.setVisible(True)
                self.plus_blends_combo.setVisible(True)
            
            p = None
            if self.plus_blend_selected_spec:
                p = plus.stock.getBlendSpecStockPosition(self.plus_blend_selected_spec,self.plus_store_selected,self.plus_blends)
            if p is None:
                self.plus_blends_combo.setCurrentIndex(0)
                #self.plus_blend_selected_spec = None # we don't deselect a blend just because it is not in the popup
                self.plus_blends_combo.blockSignals(False)
            else:
                # if roast is complete (charge and drop are set) 
                if self.aw.qmc.timeindex[0] > -1 and self.aw.qmc.timeindex[6] > 0:
                    # we first change the index and then unblock signals to avoid properties being overwritten from the selected blend
                    self.plus_blends_combo.setCurrentIndex(p+1)
                    self.plus_blends_combo.blockSignals(False)
                else:
                    # if roast is not yet complete we unblock the signals before changing the index to get the blend data be filled in
                    self.plus_blends_combo.blockSignals(False)
                    self.plus_blends_combo.setCurrentIndex(p+1)
                mark_coffee_fields = True

            self.markPlusCoffeeFields(mark_coffee_fields)
            self.updatePlusSelectedLine() 
        except:
#            import traceback
#            traceback.print_exc(file=sys.stdout)
            pass     
        finally:
            self.plus_popups_set_enabled(True)

    def markPlusCoffeeFields(self,b):
        # for QTextEdit
        if b:
            if sys.platform.startswith("darwin") and darkdetect.isDark() and appFrozen():
                self.beansedit.setStyleSheet("QTextEdit { background-color: #0D658F; selection-background-color: darkgray; }")
            else:
                self.beansedit.setStyleSheet("QTextEdit { background-color: #e4f3f8; selection-background-color: darkgray;  }")
        else:
            self.beansedit.setStyleSheet("")
        # for QLineEdit
        if b:
            if sys.platform.startswith("darwin") and darkdetect.isDark() and appFrozen():
                qlineedit_marked_style = "QLineEdit { background-color: #0D658F; selection-background-color: darkgray; }"
            else:
                qlineedit_marked_style = "QLineEdit { background-color: #e4f3f8; selection-background-color: #424242; }"
            self.bean_density_in_edit.setStyleSheet(qlineedit_marked_style)
            self.bean_size_min_edit.setStyleSheet(qlineedit_marked_style)
            self.bean_size_max_edit.setStyleSheet(qlineedit_marked_style)
            self.moisture_greens_edit.setStyleSheet(qlineedit_marked_style)
        else:
            background_white_style = "" 
            self.bean_density_in_edit.setStyleSheet(background_white_style)
            self.bean_size_min_edit.setStyleSheet(background_white_style)
            self.bean_size_max_edit.setStyleSheet(background_white_style)
            self.moisture_greens_edit.setStyleSheet(background_white_style)
        
    def updateTitle(self,prev_coffee_label,prev_blend_label):
        titles_to_be_overwritten = [ "", QApplication.translate("Scope Title", "Roaster Scope",None) ]
        if prev_coffee_label is not None:
            titles_to_be_overwritten.append(prev_coffee_label)
        if prev_blend_label is not None:
            titles_to_be_overwritten.append(prev_blend_label)
        if self.titleedit.currentText() in titles_to_be_overwritten:
            if self.plus_blend_selected_label is not None:
                self.titleedit.textEdited(self.plus_blend_selected_label)
                self.titleedit.setEditText(self.plus_blend_selected_label)
            elif self.plus_coffee_selected_label is not None:
                self.titleedit.textEdited(self.plus_coffee_selected_label)
                self.titleedit.setEditText(self.plus_coffee_selected_label)
            else:
                default_title = QApplication.translate("Scope Title", "Roaster Scope",None) 
                self.titleedit.textEdited(default_title)
                self.titleedit.setEditText(default_title)
    
    def updateBlendLines(self,blend):
        if self.weightinedit.text() != "":
            weightIn = float(str(self.weightinedit.text()))
        else:
            weightIn = 0.0
        blend_lines = plus.stock.blend2beans(blend,self.unitsComboBox.currentIndex(),weightIn)
        self.beansedit.clear()
        for l in blend_lines:
            self.beansedit.append(l)
    
    def fillBlendData(self,blend,prev_coffee_label,prev_blend_label):
        try:
            self.updateBlendLines(blend)
            keep_modified_moisture = self.modified_moisture_greens_text
            keep_modified_density = self.modified_density_in_text
            blend_dict = plus.stock.getBlendBlendDict(blend)
            if "moisture" in blend_dict:
                self.moisture_greens_edit.setText("%g" % blend_dict["moisture"])
            else:
                self.moisture_greens_edit.setText(str(0))
            if "density" in blend_dict:
                self.bean_density_in_edit.setText("%g" % self.aw.float2float(blend_dict["density"]))
            else:
                self.bean_density_in_edit.setText(str(0))
            if "screen_min" in blend_dict:
                self.bean_size_min_edit.setText(str(int(blend_dict["screen_min"])))
            else:
                self.bean_size_min_edit.setText("0")
            if "screen_max" in blend_dict:
                self.bean_size_max_edit.setText(str(int(blend_dict["screen_max"])))
            else:
                self.bean_size_max_edit.setText("0")
            # check if title should be changed (if still default, or equal to the previous selection:
            self.updateTitle(prev_coffee_label,prev_blend_label)
            self.markPlusCoffeeFields(True)
            self.density_in_editing_finished()
            self.moistureEdited()
            self.modified_density_in_text = keep_modified_density
            self.modified_moisture_greens_text = keep_modified_moisture
        except:
            pass
        
    # if current title is equal to default title or prev_coffee/blend_label, we set title from selected label
    def fillCoffeeData(self,coffee,prev_coffee_label,prev_blend_label):
        try:
            cd = plus.stock.getCoffeeCoffeeDict(coffee)
            self.beansedit.setPlainText(plus.stock.coffee2beans(coffee))
            keep_modified_moisture = self.modified_moisture_greens_text
            keep_modified_density = self.modified_density_in_text
            if "moisture" in cd:
                self.moisture_greens_edit.setText("%g" % cd["moisture"])
            else:
                self.moisture_greens_edit.setText(str(0))
            if "density" in cd:
                self.bean_density_in_edit.setText("%g" % self.aw.float2float(cd["density"]))
            else:
                self.bean_density_in_edit.setText(str(0)) 
            if "screen_size" in cd:
                screen = cd["screen_size"]
                if "min" in screen:
                    self.bean_size_min_edit.setText(str(int(screen["min"])))
                else:
                    self.bean_size_min_edit.setText("0")
                if "max" in screen:
                    self.bean_size_max_edit.setText(str(int(screen["max"])))
                else:
                    self.bean_size_max_edit.setText("0")
            else:
                self.bean_size_min_edit.setText("0")
                self.bean_size_max_edit.setText("0")
            self.updateTitle(prev_coffee_label,prev_blend_label)
            self.markPlusCoffeeFields(True)
            self.density_in_editing_finished()
            self.moistureEdited()
            self.modified_density_in_text = keep_modified_density
            self.modified_moisture_greens_text = keep_modified_moisture
        except:
            pass
        
    def defaultCoffeeData(self):
        if self.modified_beans is None:
            self.beansedit.clear()
        else:
            self.beansedit.setPlainText(self.modified_beans)
        self.bean_density_in_edit.setText(self.modified_density_in_text)
        self.volumeinedit.setText(self.modified_volume_in_text)
        self.bean_size_min_edit.setText(self.modified_beansize_min_text)
        self.bean_size_max_edit.setText(self.modified_beansize_max_text)
        self.moisture_greens_edit.setText(self.modified_moisture_greens_text)
        self.markPlusCoffeeFields(False)
        self.density_in_editing_finished()
        self.moistureEdited()
    
    @pyqtSlot(int)
    def storeSelectionChanged(self,n):
        if n != -1:
            prev_coffee_label = self.plus_coffee_selected_label
            prev_blend_label = self.plus_blend_selected_label
            self.populatePlusCoffeeBlendCombos(n)
            self.updateTitle(prev_coffee_label,prev_blend_label)
 
    @pyqtSlot(int)
    def coffeeSelectionChanged(self,n):
        # check for previously selected blend label
        prev_coffee_label = self.plus_coffee_selected_label
        prev_blend_label = self.plus_blend_selected_label
        if n < 1:
            self.defaultCoffeeData()
            self.plus_store_selected = None
            self.plus_store_selected_label = None
            self.plus_coffee_selected = None
            self.plus_coffee_selected_label = None
            self.plus_amount_selected = None
            self.updateTitle(prev_coffee_label,prev_blend_label)
        else:
            # reset blend and set new coffee            
            self.plus_blends_combo.setCurrentIndex(0)
            selected_coffee = self.plus_coffees[n-1]
            sd = plus.stock.getCoffeeStockDict(selected_coffee)
            self.plus_store_selected = sd["location_hr_id"]
            self.plus_store_selected_label = sd["location_label"]
            cd = plus.stock.getCoffeeCoffeeDict(selected_coffee)
            self.plus_coffee_selected = cd["hr_id"]
            origin = ""
            if "origin" in cd:
                origin = cd["origin"] + " "
            self.plus_coffee_selected_label = origin + cd["label"]
            self.plus_blend_selected_label = None
            self.plus_blend_selected_spec = None
            self.plus_blend_selected_spec_labels = None
            if "amount" in plus.stock.getCoffeeStockDict(selected_coffee):
                self.plus_amount_selected = plus.stock.getCoffeeStockDict(selected_coffee)["amount"]
            else:
                self.pus_amount_selected = None
            self.fillCoffeeData(selected_coffee,prev_coffee_label,prev_blend_label)
        self.checkWeightIn()
        self.updatePlusSelectedLine()
    
    @pyqtSlot(int)
    def blendSelectionChanged(self,n):
        # check for previously selected blend label
        prev_coffee_label = self.plus_coffee_selected_label
        prev_blend_label = self.plus_blend_selected_label
        if n < 1:
            self.defaultCoffeeData()
            self.plus_store_selected = None
            self.plus_store_selected_label = None
            self.plus_blend_selected_label = None
            self.plus_blend_selected_spec = None
            self.plus_blend_selected_spec_labels = None
            self.pus_amount_selected = None
            self.updateTitle(prev_coffee_label,prev_blend_label)
        else:
            # reset coffee and set new blend
            self.plus_coffees_combo.setCurrentIndex(0)
            selected_blend = self.plus_blends[n-1]
            bsd = plus.stock.getBlendStockDict(selected_blend)
            self.plus_store_selected = bsd["location_hr_id"]    
            self.plus_store_selected_label = bsd["location_label"]
            bd = plus.stock.getBlendBlendDict(selected_blend)
            self.plus_coffee_selected = None
            self.plus_blend_selected_label = bd["label"]
            self.plus_blend_selected_spec = dict(bd) # make a copy of the blend dict
            # we trim the blend_spec to the external from
            self.plus_blend_selected_spec.pop("hr_id", None) # remove the hr_id
            self.plus_blend_selected_spec_labels = [i["label"] for i in self.plus_blend_selected_spec["ingredients"]]
            # remove labels from ingredients
            ingredients = []
            for i in self.plus_blend_selected_spec["ingredients"]:
                entry = {}
                entry["ratio"] = i["ratio"]
                entry["coffee"] = i["coffee"]
                if "ratio_num" in i and i["ratio_num"] is not None:
                    entry["ratio_num"] = i["ratio_num"]
                if "ratio_denom" in i and i["ratio_denom"] is not None:
                    entry["ratio_denom"] = i["ratio_denom"]
                ingredients.append(entry)
            self.plus_blend_selected_spec["ingredients"] = ingredients
            if "amount" in bsd:
                self.plus_amount_selected = plus.stock.getBlendMaxAmount(selected_blend)
            else:
                self.pus_amount_selected = None
            self.fillBlendData(selected_blend,prev_coffee_label,prev_blend_label)
        self.checkWeightIn()
        self.updatePlusSelectedLine()

    # recentRoast activated from within RoastProperties dialog
    def recentRoastActivated(self,n):
        # note, the first item is the edited text!
        if n > 0 and n <= len(self.aw.recentRoasts):
            rr = self.aw.recentRoasts[n-1]
            if "title" in rr and rr["title"] is not None:
                self.titleedit.textEdited(rr["title"])
                self.titleedit.setEditText(rr["title"])
            if "weightUnit" in rr and rr["weightUnit"] is not None:
                self.unitsComboBox.setCurrentIndex(self.aw.qmc.weight_units.index(rr["weightUnit"]))
            if "weightIn" in rr and rr["weightIn"] is not None:
                self.weightinedit.setText("%g" % rr["weightIn"])
            # all of the following items might not be in the dict
            if "beans" in rr and rr["beans"] is not None:
                self.beansedit.setPlainText(rr["beans"])
            if "weightOut" in rr and rr["weightOut"] is not None:
                self.weightoutedit.setText("%g" % rr["weightOut"])
            else:
                self.weightoutedit.setText("%g" % 0)
            if "volumeIn" in rr and rr["volumeIn"] is not None:
                self.volumeinedit.setText("%g" % rr["volumeIn"])
            if "volumeOut" in rr and rr["volumeOut"] is not None:
                self.volumeoutedit.setText("%g" % rr["volumeOut"])
            else:
                self.volumeoutedit.setText("%g" % 0)
            if "volumeUnit" in rr and rr["volumeUnit"] is not None:
                self.volumeUnitsComboBox.setCurrentIndex(self.aw.qmc.volume_units.index(rr["volumeUnit"]))
            if "densityWeight" in rr and rr["densityWeight"] is not None:
                self.bean_density_in_edit.setText("%g" % self.aw.float2float(rr["densityWeight"]))
            if "densityRoasted" in rr and rr["densityRoasted"] is not None:
                self.bean_density_out_edit.setText("%g" % self.aw.float2float(rr["densityRoasted"]))
            else:
                self.bean_density_out_edit.setText("%g" % 0)
            if "moistureGreen" in rr and rr["moistureGreen"] is not None:
                self.moisture_greens_edit.setText("%g" % self.aw.float2float(rr["moistureGreen"]))
            if "moistureRoasted" in rr and rr["moistureRoasted"] is not None:
                self.moisture_roasted_edit.setText("%g" % self.aw.float2float(rr["moistureRoasted"]))
            else:
                self.moisture_roasted_edit.setText("%g" % 0)
            if "wholeColor" in rr and rr["wholeColor"] is not None:
                self.whole_color_edit.setText(str(rr["wholeColor"]))
            else:
                self.whole_color_edit.setText(str(0))
            if "groundColor" in rr and rr["groundColor"] is not None:
                self.ground_color_edit.setText(str(rr["groundColor"]))
            else:
                self.ground_color_edit.setText(str(0))
            if "colorSystem" in rr and rr["colorSystem"] is not None:
                self.colorSystemComboBox.setCurrentIndex(rr["colorSystem"])
            # items added in v1.4 might not be in the data set of previous stored recent roasts
            if "beanSize_min" in rr and rr["beanSize_min"] is not None:
                self.bean_size_min_edit.setText(str(int(rr["beanSize_min"])))
            if "beanSize_max" in rr and rr["beanSize_max"] is not None:
                self.bean_size_max_edit.setText(str(int(rr["beanSize_max"])))
            # Note: the background profile will not be changed if recent roast is activated from Roast Properties
            if "background" in rr and rr["background"] is not None:
                self.template_file = rr["background"]
                if "title" in rr and rr["title"] is not None:
                    self.template_name = rr["title"]
                if "roastUUID" in rr and rr["roastUUID"] is not None:
                    self.template_uuid = rr["roastUUID"]
                if "batchnr" in rr and rr["batchnr"] is not None:
                    self.template_batchnr = rr["batchnr"]
                if "batchprefix" in rr and rr["batchprefix"] is not None:
                    self.template_batchprefix = rr["batchprefix"]
            else:
                self.template_file = None
                self.template_name = None
                self.template_uuid = None
                self.template_batchnr = None
                self.template_batchprefix = None
            self.updateTemplateLine()
            self.percent()

#PLUS
            if self.aw.plus_account is not None and "plus_account" in rr and self.aw.plus_account == rr["plus_account"]:
                if "plus_store" in rr:
                    self.plus_store_selected = rr["plus_store"]
                if "plus_store_label" in rr:
                    self.plus_store_selected_label = rr["plus_store_label"]
                if "plus_coffee" in rr:
                    self.plus_coffee_selected = rr["plus_coffee"]
                if "plus_coffee_label" in rr:
                    self.plus_coffee_selected_label = rr["plus_coffee_label"]
                if "plus_blend_spec" in rr:
                    self.plus_blend_selected_label = rr["plus_blend_label"]
                    self.plus_blend_selected_spec = rr["plus_blend_spec"]
                    if "plus_blend_spec_labels":
                        self.plus_blend_selected_spec_labels = rr["plus_blend_spec_labels"]
                if self.plus_store_selected is not None and self.plus_default_store is not None and self.plus_default_store != self.plus_store_selected:
                    self.plus_default_store = None # we reset the defaultstore
                # we now set the actual values from the stock
                self.populatePlusCoffeeBlendCombos()
            
            self.aw.sendmessage(QApplication.translate("Message","Recent roast properties '{0}' set".format(self.aw.recentRoastLabel(rr))))
        self.recentRoastEnabled()
    
    @pyqtSlot("QString")
    def recentRoastEnabled(self,_=""):
        try:
            title = self.titleedit.currentText()
            weightIn = float(str(self.weightinedit.text()))
            # add new recent roast entry only if title is not default, beans is not empty and weight-in is not 0
            if title != QApplication.translate("Scope Title", "Roaster Scope",None) and weightIn != 0:
                # enable "+" addRecentRoast button
                self.addRecentButton.setEnabled(True)
                self.delRecentButton.setEnabled(True)
            else:
                self.addRecentButton.setEnabled(False)
                self.delRecentButton.setEnabled(False)
        except:
            self.addRecentButton.setEnabled(False)
            self.delRecentButton.setEnabled(False)
        
    @pyqtSlot(bool)
    def delRecentRoast(self,_):
        try:
            title = ' '.join(self.titleedit.currentText().split())
            weightIn = float(str(self.weightinedit.text()))
            weightUnit = self.unitsComboBox.currentText()
            self.aw.recentRoasts = self.aw.delRecentRoast(title,weightIn,weightUnit)
        except:
            pass
    
    @pyqtSlot(bool)
    def addRecentRoast(self,_):
        try:
            title = ' '.join(self.titleedit.currentText().split())
            weightIn = float(self.aw.comma2dot(str(self.weightinedit.text())))
            # add new recent roast entry only if title is not default, beans is not empty and weight-in is not 0
            if title != QApplication.translate("Scope Title", "Roaster Scope",None) and weightIn != 0:
                beans = self.beansedit.toPlainText()
                weightUnit = self.unitsComboBox.currentText()
                if self.volumeinedit.text() != "":
                    volumeIn = float(self.aw.comma2dot(str(self.volumeinedit.text())))
                else:
                    volumeIn = 0
                volumeUnit = self.volumeUnitsComboBox.currentText()
                if self.bean_density_in_edit.text() != "":
                    densityWeight = float(self.aw.comma2dot(str(self.bean_density_in_edit.text())))
                else:
                    densityWeight = 0
                if self.bean_size_min_edit.text() != "":
                    beanSize_min = int(round(float(str(self.bean_size_min_edit.text()))))
                else:
                    beanSize_min = 0
                if self.bean_size_max_edit.text() != "":
                    beanSize_max = int(round(float(str(self.bean_size_max_edit.text()))))
                else:
                    beanSize_max = 0  
                if self.moisture_greens_edit.text() != "":
                    moistureGreen = float(self.aw.comma2dot(self.moisture_greens_edit.text()))
                else:
                    moistureGreen = 0.0
                colorSystem = self.colorSystemComboBox.currentIndex()
                
                modifiers = QApplication.keyboardModifiers()
                weightOut = volumeOut = densityRoasted = moistureRoasted = wholeColor = groundColor = None
                if modifiers == Qt.AltModifier:  #alt click
                    # we add weightOut, volumeOut, moistureRoasted, wholeColor, groundColor
                    weightOut = float(self.aw.comma2dot(str(self.weightoutedit.text())))
                    volumeOut = float(self.aw.comma2dot(str(self.volumeoutedit.text())))
                    densityRoasted = float(self.aw.comma2dot(str(self.bean_density_out_edit.text())))
                    moistureRoasted = float(self.aw.comma2dot(self.moisture_roasted_edit.text()))
                    wholeColor = int(self.whole_color_edit.text())
                    groundColor = int(self.ground_color_edit.text())
                                 
                rr = self.aw.createRecentRoast(
                    title,
                    beans,
                    weightIn,
                    weightUnit,
                    volumeIn,
                    volumeUnit,
                    densityWeight,
                    beanSize_min,
                    beanSize_max,
                    moistureGreen,
                    colorSystem,
                    self.aw.curFile, # could be empty
                    self.aw.qmc.roastUUID, # could be empty
                    self.aw.qmc.roastbatchnr, #self.batchcounterSpinBox # self.aw.superusermode and self.aw.qmc.batchcounter > -1
                    self.aw.qmc.roastbatchprefix,  #self.batchprefixedit
                    self.aw.plus_account,
                    self.plus_store_selected,
                    self.plus_store_selected_label,
                    self.plus_coffee_selected, 
                    self.plus_coffee_selected_label,
                    self.plus_blend_selected_label,
                    self.plus_blend_selected_spec,
                    self.plus_blend_selected_spec_labels,
                    weightOut,
                    volumeOut,
                    densityRoasted,
                    moistureRoasted, 
                    wholeColor, 
                    groundColor
                    )
                self.aw.addRecentRoast(rr)
        except Exception as e:
            #import traceback
            #traceback.print_exc(file=sys.stdout)
            _, _, exc_tb = sys.exc_info()
            self.aw.qmc.adderror((QApplication.translate("Error Message", "Exception:",None) + " addRecentRoast(): {0}").format(str(e)),exc_tb.tb_lineno)

    # triggered if dialog is closed via its windows close box
    # and called from accept if dialog is closed via OK
    def closeEvent(self, _):
        self.disconnecting = True
        if self.ble is not None:
            try:
                self.ble.batteryChanged.disconnect()
                self.ble.weightChanged.disconnect()
                self.ble.deviceDisconnected.disconnect()
            except:
                pass
            try:
                self.ble.disconnectDevice()
            except:
                pass
        settings = QSettings()
        #save window geometry
        settings.setValue("RoastGeometry",self.saveGeometry())

    # triggered via the cancel button
    @pyqtSlot()
    def cancel_dialog(self):
        self.disconnecting = True
        if self.ble is not None:
            try:
                self.ble.batteryChanged.disconnect()
                self.ble.weightChanged.disconnect()
                self.ble.deviceDisconnected.disconnect()
            except:
                pass
            try:
                self.ble.disconnectDevice()
            except:
                pass
        settings = QSettings()
        #save window geometry
        settings.setValue("RoastGeometry",self.saveGeometry())
        
        self.aw.qmc.beans = self.org_beans
        self.aw.qmc.density = self.org_density
        self.aw.qmc.density_roasted = self.org_density_roasted
        self.aw.qmc.beansize_min = self.org_beansize_min
        self.aw.qmc.beansize_max = self.org_beansize_max
        self.aw.qmc.moisture_greens = self.org_moisture_greens
        
        self.aw.qmc.weight = self.org_weight
        self.aw.qmc.volume = self.org_volume
        
        self.aw.qmc.specialevents = self.org_specialevents
        self.aw.qmc.specialeventstype = self.org_specialeventstype
        self.aw.qmc.specialeventsStrings = self.org_specialeventsStrings
        self.aw.qmc.specialeventsvalue = self.org_specialeventsvalue
        self.aw.qmc.timeindex = self.org_timeindex
        
        self.aw.qmc.ambientTemp = self.org_ambientTemp
        self.aw.qmc.ambient_humidity = self.org_ambient_humidity
        self.aw.qmc.ambient_pressure = self.org_ambient_pressure
        
        self.aw.qmc.roastpropertiesAutoOpenFlag = self.org_roastpropertiesAutoOpenFlag
        self.aw.qmc.roastpropertiesAutoOpenDropFlag = self.org_roastpropertiesAutoOpenDropFlag
        
        self.reject()

    # calcs volume (in ml) from density (in g/l) and weight (in g)
    def calc_volume(self,density,weight):
        return (1./density) * weight * 1000

    #keyboard presses. There must not be widgets (pushbuttons, comboboxes, etc) in focus in order to work 
    def keyPressEvent(self,event):
        key = int(event.key())
        if event.matches(QKeySequence.Copy):
            if self.TabWidget.currentIndex() == 3: # datatable
                self.aw.copy_cells_to_clipboard(self.datatable,adjustment=1)
                self.aw.sendmessage(QApplication.translate("Message","Data table copied to clipboard",None))
        if key == 16777220 and self.aw.scale.device is not None and self.aw.scale.device != "" and self.aw.scale.device != "None": # ENTER key pressed and scale connected
            if self.weightinedit.hasFocus():
                self.inWeight(True,overwrite=True) # we don't add to current reading but overwrite
            elif self.weightoutedit.hasFocus():
                self.outWeight(True,overwrite=True) # we don't add to current reading but overwrite
    
    @pyqtSlot(int)
    def tareChanged(self,i):
        if i == 0 and self.tarePopupEnabled:
            tareDLG = tareDlg(self,self.aw,tarePopup=self)
            tareDLG.show()
            # reset index and popup
            self.tareComboBox.setCurrentIndex(self.aw.qmc.container_idx + 3)
        # update displayed scale weight
        self.update_scale_weight()
    
    @pyqtSlot(int)
    def changeWeightUnit(self,i):
        o = self.aw.qmc.weight_units.index(self.aw.qmc.weight[2]) # previous unit index
        self.aw.qmc.weight[2] = self.unitsComboBox.currentText()
        for le in [self.weightinedit,self.weightoutedit]:
            if le.text() and le.text() != "":
                wi = float(le.text())
                if wi != 0.0:
                    converted = self.aw.convertWeight(wi,o,i)
                    le.setText("%g" % self.aw.float2floatWeightVolume(converted))
        self.calculated_density()
#PLUS
        try:
            # weight unit changed, we update the coffee/blend lists in plus mode
            if self.aw.plus_account is not None:
                self.populatePlusCoffeeBlendCombos(self.plus_stores_combo.currentIndex())
        except:
            pass

    @pyqtSlot(int)
    def changeVolumeUnit(self,i):
        o = self.aw.qmc.volume_units.index(self.aw.qmc.volume[2]) # previous unit index
        self.aw.qmc.volume[2] = self.volumeUnitsComboBox.currentText()
        for le in [self.volumeinedit,self.volumeoutedit]:
            if le.text() and le.text() != "":
                wi = float(le.text())
                if wi != 0.0:
                    converted = self.aw.convertVolume(wi,o,i)
                    le.setText("%g" % self.aw.float2floatWeightVolume(converted))
#        self.calculated_density() # if just the unit changes, the density will not change as it is fixed now

    @pyqtSlot(int)
    def tabSwitched(self,i):
        if i == 0:
            self.saveEventTable()
        elif i == 1:
            self.saveEventTable()
        elif i == 2:
            self.createEventTable()
        elif i == 3:
            self.saveEventTable()
            self.createDataTable()

    @pyqtSlot(int)
    def roastflagHeavyFCChanged(self,i):
        if i:
            self.lowFC.setChecked(False)
    @pyqtSlot(int)
    def roastflagLowFCChanged(self,i):
        if i:
            self.heavyFC.setChecked(False)
    @pyqtSlot(int)
    def roastflagLightCutChanged(self,i):
        if i:
            self.darkCut.setChecked(False)
    @pyqtSlot(int)
    def roastflagDarkCutChanged(self,i):
        if i:
            self.lightCut.setChecked(False)
    @pyqtSlot(int)
    def roastflagDropsChanged(self,i):
        if i:
            self.oily.setChecked(False)
    @pyqtSlot(int)
    def roastflagOilyChanged(self,i):
        if i:
            self.drops.setChecked(False)

    @pyqtSlot(int)
    def ambientComboBoxIndexChanged(self,i):
        self.aw.qmc.ambientTempSource = i

    def buildAmbientTemperatureSourceList(self):
        extra_names = []
        for i in range(len(self.aw.qmc.extradevices)):
            extra_names.append(str(i) + "xT1: " + self.aw.qmc.extraname1[i])
            extra_names.append(str(i) + "xT2: " + self.aw.qmc.extraname2[i])
        return ["",
                QApplication.translate("ComboBox","ET",None),
                QApplication.translate("ComboBox","BT",None)] + extra_names

    @pyqtSlot(bool)
    def updateAmbientTemp(self,_):
        self.aw.qmc.updateAmbientTemp()
        self.ambientedit.setText("%g" % self.aw.float2float(self.aw.qmc.ambientTemp))
        self.ambientedit.repaint() # seems to be necessary in some PyQt versions!?
        self.ambient_humidity_edit.setText("%g" % self.aw.float2float(self.aw.qmc.ambient_humidity))
        self.ambient_humidity_edit.repaint() # seems to be necessary in some PyQt versions!?
        self.pressureedit.setText("%g" % self.aw.float2float(self.aw.qmc.ambient_pressure))
        self.pressureedit.repaint() # seems to be necessary in some PyQt versions!?

    @pyqtSlot(bool)
    def scanWholeColor(self,_):
        v = self.aw.color.readColor()
        if v is not None and v > -1:
            if v >= 0 and v <= 250:
                self.aw.qmc.whole_color = v
                self.whole_color_edit.setText(str(v))

    @pyqtSlot(bool)
    def scanGroundColor(self,_):
        v = self.aw.color.readColor()
        if v is not None and v > -1:
            v = max(0,min(250,v))
            self.aw.qmc.ground_color = v
            self.ground_color_edit.setText(str(v))

    @pyqtSlot(bool)
    def volumeCalculatorTimer(self,_):
        QTimer.singleShot(1,lambda : self.volumeCalculator())

    def volumeCalculator(self):
        weightin = None
        weightout = None
        try:
            weightin = float(self.weightinedit.text())
        except Exception:
            pass
        try:
            weightout = float(self.weightoutedit.text())
        except Exception:
            pass
        k = 1.
        if weightin is not None:
            weightin = weightin * k
        else:
            weightin = None
        if weightout is not None:
            weightout = weightout * k
        else:
            weightout = None
        tare = 0
        try:
            tare_idx = self.tareComboBox.currentIndex() - 3
            if tare_idx > -1:
                tare = self.aw.qmc.container_weights[tare_idx]
        except Exception:
            pass
        self.volumedialog = volumeCalculatorDlg(self,self.aw,
            weightIn=weightin,
            weightOut=weightout,
            weightunit=self.unitsComboBox.currentIndex(),
            volumeunit=self.volumeUnitsComboBox.currentIndex(),
            inlineedit=self.volumeinedit,
            outlineedit=self.volumeoutedit,
            tare=tare)
        self.volumedialog.show()
        self.volumedialog.setFixedSize(self.volumedialog.size())

    @pyqtSlot(bool)
    def inWeight(self,_,overwrite=False):
        QTimer.singleShot(1,lambda : self.setWeight(self.weightinedit,self.bean_density_in_edit,self.moisture_greens_edit,overwrite))
    
    @pyqtSlot(bool)
    def outWeight(self,_=False,overwrite=False):
        QTimer.singleShot(1,lambda : self.setWeight(self.weightoutedit,self.bean_density_out_edit,self.moisture_roasted_edit,overwrite))

    def setWeight(self,weight_edit,density_edit,moisture_edit,overwrite=False):
        tare = 0
        try:
            tare_idx = self.tareComboBox.currentIndex() - 3
            if tare_idx > -1:
                tare = self.aw.qmc.container_weights[tare_idx]
        except Exception:
            pass
        #w,d,m = self.aw.scale.readWeight(self.scale_weight) # read value from scale in 'g'
        w,d,m = self.scale_weight,-1,-1
        if w is not None and w > -1:
            w = w - tare
            w = self.aw.convertWeight(w,0,self.aw.qmc.weight_units.index(self.aw.qmc.weight[2])) # convert to weight units
            current_w = 0
            try:
                current_w = float(weight_edit.text())
            except:
                pass
            if overwrite:
                new_w = w
            else:
                new_w = current_w + w # we add the new weight to the already existing one!
                self.scale_set = self.aw.convertWeight(new_w,self.aw.qmc.weight_units.index(self.aw.qmc.weight[2]),0) # convert to weight units
#            weight_edit.setText("%g" % self.aw.float2float(new_w))
            # updating this widget in a separate thread seems to be important on OS X 10.14 to avoid delayed updates and widget redraw problems
            # a QApplication.processEvents() or an weight_edit.update() seems not to help
            # no issue on OS X 10.13
            QTimer.singleShot(2,lambda : self.updateWeightEdits(weight_edit,new_w))
        if d is not None and d > -1:
            density_edit.setText("%g" % self.aw.float2float(d))
        if m is not None and m > -1:
            moisture_edit.setText("%g" % self.aw.float2float(m))
    
    def updateWeightEdits(self,weight_edit,w):
        unit = self.aw.qmc.weight_units.index(self.aw.qmc.weight[2])
        if unit == 0: # g selected
            decimals = 1
        elif unit == 1: # kg selected
            decimals = 3
        else:
            decimals = 2
        weight_edit.setText("%g" % self.aw.float2float(w,decimals))
        self.updateScaleWeightAccumulated(w)
        self.weightouteditChanged()
    
    @pyqtSlot(int)
    def roastpropertiesChanged(self,_=0):
        if self.roastproperties.isChecked():
            self.aw.qmc.roastpropertiesflag = 1
        else:
            self.aw.qmc.roastpropertiesflag = 0
            
    @pyqtSlot(int)
    def roastpropertiesAutoOpenChanged(self,_=0):
        if self.roastpropertiesAutoOpen.isChecked():
            self.aw.qmc.roastpropertiesAutoOpenFlag = 1
        else:
            self.aw.qmc.roastpropertiesAutoOpenFlag = 0
            
    @pyqtSlot(int)
    def roastpropertiesAutoOpenDROPChanged(self,_=0):
        if self.roastpropertiesAutoOpenDROP.isChecked():
            self.aw.qmc.roastpropertiesAutoOpenDropFlag = 1
        else:
            self.aw.qmc.roastpropertiesAutoOpenDropFlag = 0

    def createDataTable(self):
        self.datatable.clear()
        ndata = len(self.aw.qmc.timex)
        self.datatable.setRowCount(ndata)
        columns = [QApplication.translate("Table", "Time",None),
                                                  QApplication.translate("Table", "ET",None),
                                                  QApplication.translate("Table", "BT",None),
                                                  deltaLabelUTF8 + QApplication.translate("Label", "ET",None),
                                                  deltaLabelUTF8 + QApplication.translate("Label", "BT",None)]
        for i in range(len(self.aw.qmc.extratimex)):
            en1 = self.aw.qmc.extraname1[i]
            en2 = self.aw.qmc.extraname2[i]
            try:
                en1 = en1.format(self.aw.qmc.etypes[0],self.aw.qmc.etypes[1],self.aw.qmc.etypes[2],self.aw.qmc.etypes[3])
                en2 = en2.format(self.aw.qmc.etypes[0],self.aw.qmc.etypes[1],self.aw.qmc.etypes[2],self.aw.qmc.etypes[3])
            except:
                pass
            columns.append(en1)
            columns.append(en2)
        columns.append("") # add a last dummy table that extends
        self.datatable.setColumnCount(len(columns))
        self.datatable.setHorizontalHeaderLabels(columns)
        self.datatable.setAlternatingRowColors(True)
        self.datatable.setEditTriggers(QTableWidget.NoEditTriggers)
        self.datatable.setSelectionBehavior(QTableWidget.SelectRows)
        self.datatable.setSelectionMode(QTableWidget.ExtendedSelection) # QTableWidget.SingleSelection, ContiguousSelection, MultiSelection
        self.datatable.setShowGrid(True)
        self.datatable.verticalHeader().setSectionResizeMode(2)
        offset = 0
        if self.aw.qmc.timeindex[0] > -1:
            offset = self.aw.qmc.timex[self.aw.qmc.timeindex[0]]
        
        for i in range(ndata):
            Rtime = QTableWidgetItem(stringfromseconds(self.aw.qmc.timex[i]-offset))
            Rtime.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)
            if self.aw.qmc.LCDdecimalplaces:
                fmtstr = "%.1f"
            else:
                fmtstr = "%.0f"
            ET = QTableWidgetItem(fmtstr%self.aw.qmc.temp1[i])
            BT = QTableWidgetItem(fmtstr%self.aw.qmc.temp2[i])
            ET.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)
            BT.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)
            if i > 0 and (self.aw.qmc.timex[i]-self.aw.qmc.timex[i-1]) and self.aw.qmc.temp1[i] != -1 and self.aw.qmc.temp1[i-1] != -1:
                deltaET = QTableWidgetItem("%.1f"%(60*(self.aw.qmc.temp1[i]-self.aw.qmc.temp1[i-1])/(self.aw.qmc.timex[i]-self.aw.qmc.timex[i-1])))
            else:
                deltaET = QTableWidgetItem("--")
            if i > 0 and (self.aw.qmc.timex[i]-self.aw.qmc.timex[i-1]) and self.aw.qmc.temp2[i] != -1 and self.aw.qmc.temp2[i-1] != -1:
                deltaBT = QTableWidgetItem("%.1f"%(60*(self.aw.qmc.temp2[i]-self.aw.qmc.temp2[i-1])/(self.aw.qmc.timex[i]-self.aw.qmc.timex[i-1])))
            else:
                deltaBT = QTableWidgetItem("--")
            deltaET.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)
            deltaBT.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)
            if i in self.aw.qmc.specialevents:
                index = self.aw.qmc.specialevents.index(i)
                text = QApplication.translate("Table", "#{0} {1}{2}",None).format(str(index+1),self.aw.qmc.etypesf(self.aw.qmc.specialeventstype[index])[0],self.aw.qmc.eventsvalues(self.aw.qmc.specialeventsvalue[index]))
                Rtime.setText(text + " " + Rtime.text())
            self.datatable.setItem(i,0,Rtime)
            if i in self.aw.qmc.specialevents:
                self.datatable.item(i,0).setBackground(QColor('yellow'))
            
            if i:
                    #identify by color and add notation
                if i == self.aw.qmc.timeindex[0]:
                    self.datatable.item(i,0).setBackground(QColor('#f07800'))
                    text = QApplication.translate("Table", "CHARGE",None)
                elif i == self.aw.qmc.timeindex[1]:
                    self.datatable.item(i,0).setBackground(QColor('orange'))
                    text = QApplication.translate("Table", "DRY END",None)
                elif i == self.aw.qmc.timeindex[2]:
                    self.datatable.item(i,0).setBackground(QColor('orange'))
                    text = QApplication.translate("Table", "FC START",None)
                elif i == self.aw.qmc.timeindex[3]:
                    self.datatable.item(i,0).setBackground(QColor('orange'))
                    text = QApplication.translate("Table", "FC END",None)
                elif i == self.aw.qmc.timeindex[4]:
                    self.datatable.item(i,0).setBackground(QColor('orange'))
                    text = QApplication.translate("Table", "SC START",None)
                elif i == self.aw.qmc.timeindex[5]:
                    self.datatable.item(i,0).setBackground(QColor('orange'))
                    text = QApplication.translate("Table", "SC END",None)
                elif i == self.aw.qmc.timeindex[6]:
                    self.datatable.item(i,0).setBackground(QColor('#f07800'))
                    text = QApplication.translate("Table", "DROP",None)
                elif i == self.aw.qmc.timeindex[7]:
                    self.datatable.item(i,0).setBackground(QColor('orange'))
                    text = QApplication.translate("Table", "COOL",None)
                else:
                    text = ""
                Rtime.setText(text + " " + Rtime.text())
            else:
                Rtime.setText(" " + Rtime.text())
                            
            self.datatable.setItem(i,1,ET)
            self.datatable.setItem(i,2,BT)
            self.datatable.setItem(i,3,deltaET)
            self.datatable.setItem(i,4,deltaBT)
            j = 5
            for k in range(len(self.aw.qmc.extratimex)):
                if len(self.aw.qmc.extratemp1) > k and len(self.aw.qmc.extratemp1[k]) > i:
                    extra_qtw1 = QTableWidgetItem(fmtstr%self.aw.qmc.extratemp1[k][i])
                    extra_qtw1.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)
                    self.datatable.setItem(i,j,extra_qtw1)
                    j = j + 1
                if len(self.aw.qmc.extratemp2) > k and len(self.aw.qmc.extratemp2[k]) > i:
                    extra_qtw2 = QTableWidgetItem(fmtstr%self.aw.qmc.extratemp2[k][i])
                    extra_qtw2.setTextAlignment(Qt.AlignRight|Qt.AlignVCenter)
                    self.datatable.setItem(i,j,extra_qtw2)
                    j = j + 1

    def createEventTable(self):
        try:
            #### lock shared resources #####
            self.aw.qmc.samplingsemaphore.acquire(1)
            
            nevents = len(self.aw.qmc.specialevents)
            
            #self.eventtable.clear() # this crashes Ubuntu 16.04
    #        if nevents != 0:
    #            self.eventtable.clearContents() # this crashes Ubuntu 16.04 if device table is empty and also sometimes else
            self.eventtable.clearSelection() # this seems to work also for Ubuntu 16.04
            
            self.eventtable.setRowCount(nevents)
            self.eventtable.setColumnCount(6)
            self.eventtable.setHorizontalHeaderLabels([QApplication.translate("Table", "Time", None),
                                                       QApplication.translate("Table", "ET", None),
                                                       QApplication.translate("Table", "BT", None),
                                                       QApplication.translate("Table", "Description", None),
                                                       QApplication.translate("Table", "Type", None),
                                                       QApplication.translate("Table", "Value", None)])
            self.eventtable.setAlternatingRowColors(True)
            self.eventtable.setEditTriggers(QTableWidget.NoEditTriggers)
            self.eventtable.setSelectionBehavior(QTableWidget.SelectRows)
            self.eventtable.setSelectionMode(QTableWidget.ExtendedSelection)

            self.eventtable.setShowGrid(True)
            
            self.eventtable.verticalHeader().setSectionResizeMode(2)
            regextime = QRegExp(r"^-?[0-9]?[0-9]?[0-9]:[0-5][0-9]$")
            etypes = self.aw.qmc.getetypes()
            #populate table
            for i in range(nevents):
                #create widgets
                typeComboBox = MyQComboBox()
                typeComboBox.setSizeAdjustPolicy(QComboBox.AdjustToContents)
                typeComboBox.addItems(etypes)
                typeComboBox.setCurrentIndex(self.aw.qmc.specialeventstype[i])
    
                if self.aw.qmc.LCDdecimalplaces:
                    fmtstr = "%.1f"
                else:
                    fmtstr = "%.0f"
    
                etline = QLineEdit()
                etline.setReadOnly(True)
                etline.setAlignment(Qt.AlignRight)
                ettemp = fmtstr%(self.aw.qmc.temp1[self.aw.qmc.specialevents[i]]) + self.aw.qmc.mode
                etline.setText(ettemp)
                    
                btline = QLineEdit()
                btline.setReadOnly(True)
                btline.setAlignment(Qt.AlignRight)
                bttemp = fmtstr%(self.aw.qmc.temp2[self.aw.qmc.specialevents[i]]) + self.aw.qmc.mode
                btline.setText(bttemp)
                
                valueEdit = QLineEdit()
                valueEdit.setAlignment(Qt.AlignRight)
                valueEdit.setText(self.aw.qmc.eventsvalues(self.aw.qmc.specialeventsvalue[i]))
                
                timeline = QLineEdit()
                timeline.setAlignment(Qt.AlignRight)
                if self.aw.qmc.timeindex[0] > -1 and len(self.aw.qmc.timex) > self.aw.qmc.timeindex[0]:
                    timez = stringfromseconds(self.aw.qmc.timex[self.aw.qmc.specialevents[i]]-self.aw.qmc.timex[self.aw.qmc.timeindex[0]])
                else:
                    timez = stringfromseconds(self.aw.qmc.timex[self.aw.qmc.specialevents[i]])
                timeline.setText(timez)
                timeline.setValidator(QRegExpValidator(regextime,self))
                
                stringline = QLineEdit(self.aw.qmc.specialeventsStrings[i])
                #add widgets to the table
                self.eventtable.setCellWidget(i,0,timeline)
                self.eventtable.setCellWidget(i,1,etline)
                self.eventtable.setCellWidget(i,2,btline)
                self.eventtable.setCellWidget(i,3,stringline)
                self.eventtable.setCellWidget(i,4,typeComboBox)
                self.eventtable.setCellWidget(i,5,valueEdit)
                valueEdit.setValidator(QIntValidator(0,self.aw.eventsMaxValue,self.eventtable.cellWidget(i,5)))
            header = self.eventtable.horizontalHeader()
            #header.setStretchLastSection(True)
            header.setSectionResizeMode(0, QHeaderView.Fixed)
            header.setSectionResizeMode(1, QHeaderView.Fixed)
            header.setSectionResizeMode(2, QHeaderView.Fixed)
            header.setSectionResizeMode(3, QHeaderView.Stretch)
            header.setSectionResizeMode(4, QHeaderView.ResizeToContents)
            header.setSectionResizeMode(5, QHeaderView.Fixed)
            # improve width of Time column
            self.eventtable.setColumnWidth(0,60)
            self.eventtable.setColumnWidth(1,65)
            self.eventtable.setColumnWidth(2,65)
            self.eventtable.setColumnWidth(5,55)
            # header.setSectionResizeMode(QHeaderView.Stretch)
        finally:
            if self.aw.qmc.samplingsemaphore.available() < 1:
                self.aw.qmc.samplingsemaphore.release(1)


    def saveEventTable(self):
        try:
            #### lock shared resources #####
            self.aw.qmc.samplingsemaphore.acquire(1)
            nevents = self.eventtable.rowCount()
            for i in range(nevents):
                timez = self.eventtable.cellWidget(i,0)
                if self.aw.qmc.timeindex[0] > -1:
                    self.aw.qmc.specialevents[i] = self.aw.qmc.time2index(self.aw.qmc.timex[self.aw.qmc.timeindex[0]]+ stringtoseconds(str(timez.text())))
                else:
                    self.aw.qmc.specialevents[i] = self.aw.qmc.time2index(stringtoseconds(str(timez.text())))
                description = self.eventtable.cellWidget(i,3)
                self.aw.qmc.specialeventsStrings[i] = description.text()
                etype = self.eventtable.cellWidget(i,4)
                self.aw.qmc.specialeventstype[i] = etype.currentIndex()
                evalue = self.eventtable.cellWidget(i,5).text()
                self.aw.qmc.specialeventsvalue[i] = self.aw.qmc.str2eventsvalue(str(evalue))
        finally:
            if self.aw.qmc.samplingsemaphore.available() < 1:
                self.aw.qmc.samplingsemaphore.release(1)

    @pyqtSlot(bool)
    def copyDataTabletoClipboard(self,_=False):
        self.aw.copy_cells_to_clipboard(self.datatable,adjustment=5)
        self.aw.sendmessage(QApplication.translate("Message","Data table copied to clipboard",None))

    @pyqtSlot(bool)
    def copyEventTabletoClipboard(self,_=False):
        nrows = self.eventtable.rowCount() 
        ncols = self.eventtable.columnCount()
        clipboard = ""
        modifiers = QApplication.keyboardModifiers()
        if modifiers == Qt.AltModifier:  #alt click
            tbl = prettytable.PrettyTable()
            fields = []
            for c in range(ncols):
                fields.append(self.eventtable.horizontalHeaderItem(c).text())
            tbl.field_names = fields
            for i in range(nrows):
                rows = []
                rows.append(self.eventtable.cellWidget(i,0).text())
                rows.append(self.eventtable.cellWidget(i,1).text())
                rows.append(self.eventtable.cellWidget(i,2).text())
                rows.append(self.eventtable.cellWidget(i,3).text())
                rows.append(self.eventtable.cellWidget(i,4).currentText())
                rows.append(self.eventtable.cellWidget(i,5).text())
                tbl.add_row(rows)
            clipboard = tbl.get_string()
        else:
            for c in range(ncols):
                clipboard += self.eventtable.horizontalHeaderItem(c).text()
                if c != (ncols-1):
                    clipboard += '\t'
            clipboard += '\n'
            for r in range(nrows):
                clipboard += self.eventtable.cellWidget(r,0).text() + "\t"
                clipboard += self.eventtable.cellWidget(r,1).text() + "\t"
                clipboard += self.eventtable.cellWidget(r,2).text() + "\t"
                clipboard += self.eventtable.cellWidget(r,3).text() + "\t"
                clipboard += self.eventtable.cellWidget(r,4).currentText() + "\t"
                clipboard += self.eventtable.cellWidget(r,5).text() + "\n"
        # copy to the system clipboard
        sys_clip = QApplication.clipboard()
        sys_clip.setText(clipboard)
        self.aw.sendmessage(QApplication.translate("Message","Event table copied to clipboard",None))

    def createAlarmEventRows(self,rows):
        for r in rows:
            TP = self.aw.findTP()
            if TP:
                self.aw.qmc.alarmflag.append(1)
                self.aw.qmc.alarmguard.append(-1)
                self.aw.qmc.alarmnegguard.append(-1)
                tx = self.aw.qmc.timex[self.aw.qmc.specialevents[r]] - self.aw.qmc.timex[TP]
                ev = 8 # TP
                if tx < 0: # events before TP are moved to CHARGE
                    tx = 1 # set to one second after
                    ev = 0 # CHARGE
                self.aw.qmc.alarmoffset.append(tx) # seconds after TP
                self.aw.qmc.alarmtime.append(ev)
                self.aw.qmc.alarmcond.append(1) # rises above (we assume that BT always rises after TP)
                self.aw.qmc.alarmstate.append(-1) # not yet triggered
                self.aw.qmc.alarmsource.append(1) # 1=BT
                self.aw.qmc.alarmtemperature.append(float(round(self.aw.qmc.temp2[self.aw.qmc.specialevents[r]]))) # the BT trigger temperature
                self.aw.qmc.alarmaction.append(self.aw.qmc.specialeventstype[r] + 3) # 3,4,5,6 for slider 0-3
                self.aw.qmc.alarmbeep.append(0)
                self.aw.qmc.alarmstrings.append(str(int(self.aw.qmc.specialeventsvalue[r]*10 - 10)))
        
    @pyqtSlot(bool)
    def clusterEvents(self,_=False):
        nevents = len(self.aw.qmc.specialevents)
        if nevents:
            self.aw.clusterEvents()
            self.createEventTable()
            self.aw.qmc.redraw(recomputeAllDeltas=False)
            self.aw.qmc.fileDirty()
            
    @pyqtSlot(bool)
    def clearEvents(self,_=False):
        try:
            #### lock shared resources #####
            self.aw.qmc.samplingsemaphore.acquire(1)
            nevents = len(self.aw.qmc.specialevents)
            if nevents:
                self.aw.qmc.specialevents = []
                self.aw.qmc.specialeventstype = []
                self.aw.qmc.specialeventsStrings = []
                self.aw.qmc.specialeventsvalue = []
        finally:
            if self.aw.qmc.samplingsemaphore.available() < 1:
                self.aw.qmc.samplingsemaphore.release(1)
        self.createEventTable()
        self.aw.qmc.redraw(recomputeAllDeltas=False)
        self.aw.qmc.fileDirty()
    
    @pyqtSlot(bool)
    def createAlarmEventTable(self,_=False):
        if len(self.aw.qmc.specialevents):
            # check for selection
            selected = self.eventtable.selectedRanges()
            if selected and len(selected) > 0:
                rows = []
                for s in selected:
                    top = s.topRow()
                    for x in range(s.rowCount()):
                        rows.append(top + x)
                #rows = [s.topRow() for s in selected]
                self.createAlarmEventRows(rows)
                message = QApplication.translate("Message","Alarms from events #{0} created", None).format(str([r+1 for r in rows]))
            else:
                rows = range(self.eventtable.rowCount())
                self.createAlarmEventRows(rows)
                message = QApplication.translate("Message","Alarms from events #{0} created", None).format(str([r+1 for r in rows]))
            self.aw.sendmessage(message)
        else:
            message = QApplication.translate("Message","No events found", None)
            self.aw.sendmessage(message)
    
    @pyqtSlot(bool)
    def orderEventTable(self,_=False):
        self.saveEventTable()
        self.orderEventTableLoop()
        self.aw.qmc.fileDirty()
        
    def orderEventTableLoop(self):
        nevents = len(self.aw.qmc.specialevents)
        if nevents:
            self.aw.orderEvents()
            self.createEventTable()
            self.aw.qmc.redraw(recomputeAllDeltas=False)

    @pyqtSlot(bool)
    def addEventTable(self,_=False):
        if len(self.aw.qmc.timex):
            self.saveEventTable()
            self.aw.qmc.specialevents.append(len(self.aw.qmc.timex)-1)   #qmc.specialevents holds indexes in qmx.timex. Initialize event index
            self.aw.qmc.specialeventstype.append(0)
            self.aw.qmc.specialeventsStrings.append(str(len(self.aw.qmc.specialevents)))
            self.aw.qmc.specialeventsvalue.append(0)
            self.createEventTable()
            self.aw.qmc.redraw(recomputeAllDeltas=False)
            message = QApplication.translate("Message","Event #{0} added", None).format(str(len(self.aw.qmc.specialevents))) 
            self.aw.sendmessage(message)
        else:
            message = QApplication.translate("Message","No profile found", None)
            self.aw.sendmessage(message)
            
    def deleteEventRows(self,rows):
        specialevents = []
        specialeventstype = []
        specialeventsStrings = []
        specialeventsvalue = []
        for r in range(len(self.aw.qmc.specialevents)):
            if not (r in rows):
                specialevents.append(self.aw.qmc.specialevents[r])
                specialeventstype.append(self.aw.qmc.specialeventstype[r])
                specialeventsStrings.append(self.aw.qmc.specialeventsStrings[r])
                specialeventsvalue.append(self.aw.qmc.specialeventsvalue[r])
        self.aw.qmc.specialevents = specialevents
        self.aw.qmc.specialeventstype = specialeventstype
        self.aw.qmc.specialeventsStrings = specialeventsStrings
        self.aw.qmc.specialeventsvalue = specialeventsvalue
    
    @pyqtSlot(bool)
    def deleteEventTable(self,_=False):
        if len(self.aw.qmc.specialevents):
            self.saveEventTable()
            # check for selection
            selected = self.eventtable.selectedRanges()
            if selected and len(selected) > 0:
                rows = []
                for s in selected:
                    top = s.topRow()
                    for x in range(s.rowCount()):
                        rows.append(top + x)
                self.deleteEventRows(rows)
                message = QApplication.translate("Message"," Events #{0} deleted", None).format(str([r+1 for r in rows]))
            else:
                self.aw.qmc.specialevents.pop()
                self.aw.qmc.specialeventstype.pop()
                self.aw.qmc.specialeventsStrings.pop()
                self.aw.qmc.specialeventsvalue.pop()
                message = QApplication.translate("Message"," Event #{0} deleted", None).format(str(len(self.aw.qmc.specialevents)+1))
            self.aw.qmc.fileDirty()
            self.createEventTable()
            self.aw.qmc.redraw(recomputeAllDeltas=False)
            self.aw.sendmessage(message)
        else:
            message = QApplication.translate("Message","No events found", None)
            self.aw.sendmessage(message)
    
    @pyqtSlot()
    def weightouteditChanged(self):
        self.weightoutedit.setText(self.aw.comma2dot(str(self.weightoutedit.text())))
        self.percent()
        self.calculated_density()
        self.density_out_editing_finished() # recalc volume_out

    def checkWeightIn(self):
        enough = True
        if self.plus_amount_selected is not None:
            weightIn = 0.0
            try:
                weightIn = float(str(self.weightinedit.text()))
                # convert weight to kg
                wc = self.aw.convertWeight(weightIn,self.aw.qmc.weight_units.index(self.unitsComboBox.currentText()),self.aw.qmc.weight_units.index("Kg"))
                if wc > self.plus_amount_selected:
                    enough = False 
            except:
                pass
        if enough:
            self.weightinedit.setStyleSheet("")
        else:
            if sys.platform.startswith("darwin") and darkdetect.isDark() and appFrozen():
                self.weightinedit.setStyleSheet("""QLineEdit { background-color: #ad0427;  }""")
            else:
                self.weightinedit.setStyleSheet("""QLineEdit { color: red; }""")

    @pyqtSlot()
    def weightineditChanged(self):
        self.weightinedit.setText(self.aw.comma2dot(str(self.weightinedit.text())))
        self.percent()
        self.calculated_density()
        keep_modified_density = self.modified_density_in_text
        self.density_in_editing_finished() # recalc volume_in
        self.modified_density_in_text = keep_modified_density
        self.recentRoastEnabled()
        if self.aw.plus_account is not None:
            self.checkWeightIn()
            if self.plus_blends_combo.currentIndex() > 0:
                self.updateBlendLines(self.plus_blends[self.plus_blends_combo.currentIndex()-1])
        
    def density_percent(self):
        percent = 0.
        try:
            if self.bean_density_out_edit.text() != "" and float(str(self.bean_density_out_edit.text())) != 0.0:
                percent = self.aw.weight_loss(float(self.aw.comma2dot(str(self.bean_density_in_edit.text()))),float(self.aw.comma2dot(str(self.bean_density_out_edit.text()))))
        except Exception:
            pass
        if percent <= 0:
            self.densitypercentlabel.setText("")
        else:
            percentstring =  "-%.1f%%" % percent
            self.densitypercentlabel.setText(percentstring)    #density percent loss
        
    def moisture_percent(self):
        percent = 0.
        try:
            m_roasted = float(self.aw.comma2dot(str(self.moisture_roasted_edit.text())))
            if m_roasted != 0.0:
                percent = float(self.aw.comma2dot(str(self.moisture_greens_edit.text()))) - m_roasted
        except Exception:
            pass
        if percent <= 0:
            self.moisturepercentlabel.setText("")
        else:
            percentstring =  "-%.1f" %(percent) + "%"
            self.moisturepercentlabel.setText(percentstring)    #density percent loss
                
    def percent(self):
        percent = 0.
        try:
            if self.weightoutedit.text() != "" and float(str(self.weightoutedit.text())) != 0.0:
                percent = self.aw.weight_loss(float(str(self.weightinedit.text())),float(str(self.weightoutedit.text())))
        except Exception:
            pass
        if percent > 0:
            percentstring =  "-%.1f" %(percent) + "%"
            self.weightpercentlabel.setText(percentstring)    #weight percent loss
        else:
            self.weightpercentlabel.setText("")

    @pyqtSlot()
    def volume_percent(self):
        self.volumeinedit.setText(self.aw.comma2dot(str(self.volumeinedit.text())))
        self.volumeoutedit.setText(self.aw.comma2dot(str(self.volumeoutedit.text())))
        self.modified_volume_in_text = str(self.volumeinedit.text())
        percent = 0.
        try:
            if self.volumeoutedit.text() != "" and float(str(self.volumeoutedit.text())) != 0.0:
                percent = self.aw.volume_increase(float(str(self.volumeinedit.text()).replace(",",".")),float(str(self.volumeoutedit.text().replace(",","."))))
        except Exception:
            pass
        if percent == 0:
            self.volumepercentlabel.setText("")
        else:
            percentstring =  "%.1f" %(percent) + "%"
            self.volumepercentlabel.setText(percentstring)    #volume percent gain
        self.calculated_density()
        
    # calculates density in g/l from weightin/weightout and volumein/volumeout
    def calc_density(self):
        din = dout = 0.0
        try:
            if self.volumeinedit.text() != "" and self.weightinedit.text() != "":
                volumein = float(str(self.volumeinedit.text()))
                weightin = float(str(self.weightinedit.text()))
                if volumein != 0.0 and weightin != 0.0:
                    vol_idx = self.aw.qmc.volume_units.index(self.volumeUnitsComboBox.currentText())
                    volumein = self.aw.convertVolume(volumein,vol_idx,0)
                    weight_idx = self.aw.qmc.weight_units.index(self.unitsComboBox.currentText())
                    weightin = self.aw.convertWeight(weightin,weight_idx,0)
                    din = (weightin / volumein) 
            if self.volumeoutedit.text() != ""  and self.weightoutedit.text() != "":
                volumeout = float(str(self.volumeoutedit.text()))
                weightout = float(str(self.weightoutedit.text()))
                if volumeout != 0.0 and weightout != 0.0:
                    vol_idx = self.aw.qmc.volume_units.index(self.volumeUnitsComboBox.currentText())
                    volumeout = self.aw.convertVolume(volumeout,vol_idx,0)
                    weight_idx = self.aw.qmc.weight_units.index(self.unitsComboBox.currentText())
                    weightout = self.aw.convertWeight(weightout,weight_idx,0)
                    dout = (weightout / volumeout)
        except:
            pass
        return din,dout

    @pyqtSlot(int)
    def calculated_density(self,_=0):
        din, dout = self.calc_density()
        if din > 0.:
            # set also the green density if not yet set
            self.bean_density_in_edit.setText("%g" % self.aw.float2float(din))
        if  dout > 0.:
            # set also the roasted density if not yet set
            self.bean_density_out_edit.setText("%g" % self.aw.float2float(dout))
        self.density_percent()
        self.calculated_organic_loss()
            
    def calc_organic_loss(self):
        wloss = 0. # weight (moisture + organic)
        mloss = 0. # moisture
        try:
            if self.weightpercentlabel.text() and self.weightpercentlabel.text() != "":
                wloss = abs(float(self.weightpercentlabel.text().split("%")[0]))
        except Exception:
            pass
        try:
            if self.moisturepercentlabel.text() and self.moisturepercentlabel.text() != "":
                mloss = abs(float(self.moisturepercentlabel.text().split("%")[0]))
        except Exception:
            pass
        if mloss != 0. and wloss != 0.:
            return mloss, self.aw.float2float(max(min(wloss - mloss,100),0))
        else:
            return 0., 0.

    def calculated_organic_loss(self):
        self.moisture_percent()
        mloss, oloss = self.calc_organic_loss()
        if oloss > 0. and mloss > 0.:
            self.organiclosslabel.setText(QApplication.translate("Label", "organic material",None))
            self.organicpercentlabel.setText("-{}%".format(oloss))
        else:
            self.organiclosslabel.setText("")
            self.organicpercentlabel.setText("")

    @pyqtSlot()
    def greens_temp_editing_finished(self):
        self.greens_temp_edit.setText(self.aw.comma2dot(str(self.greens_temp_edit.text())))
        
    @pyqtSlot()
    def ambientedit_editing_finished(self):
        self.ambientedit.setText(self.aw.comma2dot(str(self.ambientedit.text())))
    
    @pyqtSlot()
    def ambient_humidity_editing_finished(self):
        self.ambient_humidity_edit.setText(self.aw.comma2dot(str(self.ambient_humidity_edit.text())))
        
    @pyqtSlot()
    def pressureedit_editing_finished(self):
        self.pressureedit.setText(self.aw.comma2dot(str(self.pressureedit.text())))
    
    @pyqtSlot()
    def density_in_editing_finished(self):
        self.bean_density_in_edit.setText(self.aw.comma2dot(str(self.bean_density_in_edit.text())))
        self.modified_density_in_text = str(self.bean_density_in_edit.text())
        # if density-in and weight-in is given, we re-calc volume-in:
        if self.bean_density_in_edit.text() != "" and self.weightinedit.text() != "":
            density_in = float(str(self.bean_density_in_edit.text()))
            weight_in = float(str(self.weightinedit.text()))
            if density_in != 0 and weight_in != 0:
                weight_in = self.aw.convertWeight(weight_in,self.unitsComboBox.currentIndex(),self.aw.qmc.weight_units.index("g"))
                volume_in = weight_in / density_in # in g/l
                # convert to selected volume unit
                volume_in = self.aw.convertVolume(volume_in,self.aw.qmc.volume_units.index("l"),self.volumeUnitsComboBox.currentIndex())
            else:
                volume_in = 0
            self.volumeinedit.setText("%g" % self.aw.float2floatWeightVolume(volume_in))
            self.volume_percent()
    
    @pyqtSlot()
    def density_out_editing_finished(self):
        self.bean_density_out_edit.setText(self.aw.comma2dot(str(self.bean_density_out_edit.text())))
        # if density-out and weight-out is given, we re-calc volume-out:
        if self.bean_density_out_edit.text() != "" and self.weightoutedit.text() != "":
            density_out = float(str(self.bean_density_out_edit.text()))
            weight_out = float(str(self.weightoutedit.text()))
            if density_out != 0 and weight_out != 0:
                weight_out = self.aw.convertWeight(weight_out,self.unitsComboBox.currentIndex(),self.aw.qmc.weight_units.index("g"))
                volume_out = weight_out / density_out # in g/l
                # convert to selected volume unit
                volume_out = self.aw.convertVolume(volume_out,self.aw.qmc.volume_units.index("l"),self.volumeUnitsComboBox.currentIndex())
            else:
                volume_out = 0
            self.volumeoutedit.setText("%g" % self.aw.float2floatWeightVolume(volume_out))
            self.volume_percent()
    
    @pyqtSlot()
    def accept(self):
        #check for graph
        if len(self.aw.qmc.timex):
            #prevents accidentally deleting a modified profile.
            self.aw.qmc.fileDirty()
            if self.chargeedit.text() == "":
                self.aw.qmc.timeindex[0] = -1
                self.aw.qmc.xaxistosm(redraw=False)
            elif self.chargeeditcopy != str(self.chargeedit.text()):
                #if there is a CHARGE recorded and the time entered is positive. Use relative time
                if stringtoseconds(str(self.chargeedit.text())) > 0 and self.aw.qmc.timeindex[0] != -1:
                    startindex = self.aw.qmc.time2index(self.aw.qmc.timex[self.aw.qmc.timeindex[0]] + stringtoseconds(str(self.chargeedit.text())))
                    self.aw.qmc.timeindex[0] = startindex
                    self.aw.qmc.xaxistosm(redraw=False)
                #if there is a CHARGE recorded and the time entered is negative. Use relative time
                elif stringtoseconds(str(self.chargeedit.text())) < 0 and self.aw.qmc.timeindex[0] != -1:
                    relativetime = self.aw.qmc.timex[self.aw.qmc.timeindex[0]]-abs(stringtoseconds(str(self.chargeedit.text())))
                    startindex = self.aw.qmc.time2index(relativetime)
                    self.aw.qmc.timeindex[0] = startindex
                    self.aw.qmc.xaxistosm(redraw=False)
                #if there is _no_ CHARGE recorded and the time entered is positive. Use absolute time 
                elif stringtoseconds(str(self.chargeedit.text())) > 0 and self.aw.qmc.timeindex[0] == -1:
                    startindex = self.aw.qmc.time2index(stringtoseconds(str(self.chargeedit.text())))
                    self.aw.qmc.timeindex[0] = startindex
                    self.aw.qmc.xaxistosm(redraw=False)
                #if there is _no_ CHARGE recorded and the time entered is negative. ERROR
                elif stringtoseconds(str(self.chargeedit.text())) < 0 and self.aw.qmc.timeindex[0] == -1:
                    self.aw.qmc.adderror(QApplication.translate("Error Message", "Unable to move CHARGE to a value that does not exist",None))
                    return
            # check CHARGE (with index self.aw.qmc.timeindex[0])
            if self.aw.qmc.timeindex[0] == -1:
                start = 0                   #relative start time
            else:
                start = self.aw.qmc.timex[self.aw.qmc.timeindex[0]]
            if self.dryeditcopy != str(self.dryedit.text()):
                s = stringtoseconds(str(self.dryedit.text()))
                if s <= 0:
                    self.aw.qmc.timeindex[1] = 0
                else:
                    dryindex = self.aw.qmc.time2index(start + s)
                    self.aw.qmc.timeindex[1] = dryindex
            if self.Cstarteditcopy != str(self.Cstartedit.text()):
                s = stringtoseconds(str(self.Cstartedit.text()))
                if s <= 0:
                    self.aw.qmc.timeindex[2] = 0
                else:
                    fcsindex = self.aw.qmc.time2index(start + s)
                    self.aw.qmc.timeindex[2] = fcsindex
            if self.Cendeditcopy != str(self.Cendedit.text()):
                s = stringtoseconds(str(self.Cendedit.text()))
                if s <= 0:
                    self.aw.qmc.timeindex[3] = 0
                else:
                    fceindex = self.aw.qmc.time2index(start + s)
                    self.aw.qmc.timeindex[3] = fceindex
            if self.CCstarteditcopy != str(self.CCstartedit.text()):
                s = stringtoseconds(str(self.CCstartedit.text()))
                if s <= 0:
                    self.aw.qmc.timeindex[4] = 0
                else:
                    scsindex = self.aw.qmc.time2index(start + s)
                    self.aw.qmc.timeindex[4] = scsindex
            if self.CCendeditcopy != str(self.CCendedit.text()):
                s = stringtoseconds(str(self.CCendedit.text()))
                if s <= 0:
                    self.aw.qmc.timeindex[5] = 0
                elif stringtoseconds(str(self.CCendedit.text())) > 0:
                    sceindex = self.aw.qmc.time2index(start + s)
                    self.aw.qmc.timeindex[5] = sceindex
            if self.dropeditcopy != str(self.dropedit.text()):
                s = stringtoseconds(str(self.dropedit.text()))
                if s <= 0:
                    self.aw.qmc.timeindex[6] = 0
                else:
                    dropindex = self.aw.qmc.time2index(start + s)
                    self.aw.qmc.timeindex[6] = dropindex
            if self.cooleditcopy != str(self.cooledit.text()):
                s = stringtoseconds(str(self.cooledit.text()))
                if s <= 0:
                    self.aw.qmc.timeindex[7] = 0
                else:
                    coolindex = self.aw.qmc.time2index(start + s)
                    self.aw.qmc.timeindex[7] = coolindex
            if self.aw.qmc.phasesbuttonflag:   
                # adjust phases by DryEnd and FCs events
                if self.aw.qmc.timeindex[1]:
                    self.aw.qmc.phases[1] = int(round(self.aw.qmc.temp2[self.aw.qmc.timeindex[1]]))
                if self.aw.qmc.timeindex[2]:
                    self.aw.qmc.phases[2] = int(round(self.aw.qmc.temp2[self.aw.qmc.timeindex[2]]))
            
            self.saveEventTable()
        # Update Title
        self.aw.qmc.title = ' '.join(self.titleedit.currentText().split())
        self.aw.qmc.title_show_always = self.titleShowAlwaysFlag.isChecked()
        self.aw.qmc.container_idx = self.tareComboBox.currentIndex() - 3

#PLUS        
        # Update Plus
        if self.aw.plus_account is not None:
            self.aw.qmc.plus_default_store = self.plus_default_store
            self.aw.qmc.plus_store = self.plus_store_selected
            self.aw.qmc.plus_store_label = self.plus_store_selected_label
            self.aw.qmc.plus_coffee = self.plus_coffee_selected
            self.aw.qmc.plus_coffee_label = self.plus_coffee_selected_label
            self.aw.qmc.plus_blend_label = self.plus_blend_selected_label
            self.aw.qmc.plus_blend_spec = self.plus_blend_selected_spec
            self.aw.qmc.plus_blend_spec_labels = self.plus_blend_selected_spec_labels
        
        # Update beans
        self.aw.qmc.beans = self.beansedit.toPlainText()
        #update ambient temperature source
        self.aw.qmc.ambientTempSource = self.ambientComboBox.currentIndex()
        #update weight
        try:
            self.aw.qmc.weight[0] = float(self.aw.comma2dot(str(self.weightinedit.text())))
        except Exception:
            self.aw.qmc.weight[0] = 0
        try:
            self.aw.qmc.weight[1] = float(self.aw.comma2dot(str(self.weightoutedit.text())))
        except Exception:
            self.aw.qmc.weight[1] = 0
        self.aw.qmc.weight[2] = self.unitsComboBox.currentText()
        #update volume
        try:
            self.aw.qmc.volume[0] = float(self.aw.comma2dot(str(self.volumeinedit.text())))
        except Exception:
            self.aw.qmc.volume[0] = 0
        try:
            self.aw.qmc.volume[1] = float(self.aw.comma2dot(str(self.volumeoutedit.text())))
        except Exception:
            self.aw.qmc.volume[1] = 0
        self.aw.qmc.volume[2] = self.volumeUnitsComboBox.currentText()
        #update density
        try:
            self.aw.qmc.density[0] = float(self.aw.comma2dot(str(self.bean_density_in_edit.text())))
        except Exception:
            self.aw.qmc.density[0] = 0
        self.aw.qmc.density[1] = "g"
        self.aw.qmc.density[2] = 1
        self.aw.qmc.density[3] = "l"
        try:
            self.aw.qmc.density_roasted[0] = float(self.aw.comma2dot(str(self.bean_density_out_edit.text())))
        except Exception:
            self.aw.qmc.density_roasted[0] = 0
        self.aw.qmc.density_roasted[1] = "g"
        self.aw.qmc.density_roasted[2] = 1
        self.aw.qmc.density_roasted[3] = "l"
        #update bean size
        try:
            self.aw.qmc.beansize_min = int(str(self.bean_size_min_edit.text()))
        except Exception:
            self.aw.qmc.beansize_min = 0
        try:
            self.aw.qmc.beansize_max = int(str(self.bean_size_max_edit.text()))
        except Exception:
            self.aw.qmc.beansize_max = 0
        #update roastflags
        self.aw.qmc.heavyFC_flag = self.heavyFC.isChecked()
        self.aw.qmc.lowFC_flag = self.lowFC.isChecked()
        self.aw.qmc.lightCut_flag = self.lightCut.isChecked()
        self.aw.qmc.darkCut_flag = self.darkCut.isChecked()
        self.aw.qmc.drops_flag = self.drops.isChecked()
        self.aw.qmc.oily_flag = self.oily.isChecked()
        self.aw.qmc.uneven_flag = self.uneven.isChecked()
        self.aw.qmc.tipping_flag = self.tipping.isChecked()
        self.aw.qmc.scorching_flag = self.scorching.isChecked()
        self.aw.qmc.divots_flag = self.divots.isChecked()
        #update color
        try:
            self.aw.qmc.whole_color = int(str(self.whole_color_edit.text()))
        except:
            self.aw.qmc.whole_color = 0
        try:
            self.aw.qmc.ground_color = int(str(self.ground_color_edit.text()))
        except:
            self.aw.qmc.ground_color = 0
        self.aw.qmc.color_system_idx = self.colorSystemComboBox.currentIndex()
        #update beans temperature
        try:
            self.aw.qmc.greens_temp = float(self.aw.comma2dot(str(self.greens_temp_edit.text())))
        except:
            self.aw.qmc.greens_temp = 0.
        #update greens moisture
        try:
            self.aw.qmc.moisture_greens = float(self.aw.comma2dot(str(self.moisture_greens_edit.text())))
        except Exception:
            self.aw.qmc.moisture_greens = 0.
        #update roasted moisture
        try:
            self.aw.qmc.moisture_roasted = float(self.aw.comma2dot(str(self.moisture_roasted_edit.text())))
        except Exception:
            self.aw.qmc.moisture_roasted = 0.
        #update ambient temperature
        try:
            self.aw.qmc.ambientTemp = float(str(self.ambientedit.text()))
            if math.isnan(self.aw.qmc.ambientTemp):
                self.aw.qmc.ambientTemp = 0.0
        except Exception:
            self.aw.qmc.ambientTemp = 0.0
        #update ambient humidity
        try:
            self.aw.qmc.ambient_humidity = float(self.aw.comma2dot(str(self.ambient_humidity_edit.text())))
        except Exception:
            self.aw.qmc.ambient_humidity = 0
        #update ambient pressure
        try:
            self.aw.qmc.ambient_pressure = float(self.aw.comma2dot(str(self.pressureedit.text())))
        except Exception:
            self.aw.qmc.ambient_pressure = 0
        #update notes
        self.aw.qmc.roastertype = self.roaster.text()
        self.aw.qmc.operator = self.operator.text()
        self.aw.qmc.organization = self.organization.text()
        self.aw.qmc.drumspeed = self.drumspeed.text()
        self.aw.qmc.roastingnotes = self.roastingeditor.toPlainText()
        self.aw.qmc.cuppingnotes = self.cuppingeditor.toPlainText()
        if self.aw.superusermode or self.batcheditmode:
            self.aw.qmc.roastbatchprefix = self.batchprefixedit.text()
            self.aw.qmc.roastbatchnr = self.batchcounterSpinBox.value()
            self.aw.qmc.roastbatchpos = self.batchposSpinBox.value()
            
        # if custom events were changed we clear the event flag position cache 
        if self.aw.qmc.specialevents != self.org_specialevents:
            self.aw.qmc.l_event_flags_dict = {}
        # if events were changed we clear the event flag position cache 
        if self.aw.qmc.timeindex != self.org_timeindex:
            self.aw.qmc.l_annotations_dict = {}
        
        # load selected recent roast template in the background
        if self.aw.loadbackgroundUUID(self.template_file,self.template_uuid):
            try:
                self.aw.qmc.background = True
                self.aw.qmc.timealign(redraw=False)
                self.aw.qmc.redraw()
            except:
                pass
        elif ((not self.aw.qmc.flagon) or
            (self.aw.qmc.specialevents != self.org_specialevents) or
            (self.aw.qmc.specialeventstype != self.org_specialeventstype) or
            (self.aw.qmc.specialeventsStrings != self.org_specialeventsStrings) or
            (self.aw.qmc.specialeventsvalue != self.org_specialeventsvalue) or
            (self.aw.qmc.timeindex != self.org_timeindex)):
            # we do a general redraw only if not sampling
            self.aw.qmc.redraw(recomputeAllDeltas=False)
        elif (self.org_title != self.aw.qmc.title) or self.org_title_show_always != self.aw.qmc.title_show_always:
            # if title changed we at least update that one
            if self.aw.qmc.flagstart and not self.aw.qmc.title_show_always:
                self.aw.qmc.setProfileTitle("")
                self.aw.qmc.setProfileBackgroundTitle("")
                self.aw.qmc.fig.canvas.draw()
            else:
                self.aw.qmc.setProfileTitle(self.aw.qmc.title)
                titleB = ""
                if self.aw.qmc.background and not (self.aw.qmc.title is None or self.aw.qmc.title == ""):
                    if self.aw.qmc.roastbatchnrB == 0:
                        titleB = self.aw.qmc.titleB
                    else:
                        titleB = self.aw.qmc.roastbatchprefixB + str(self.aw.qmc.roastbatchnrB) + " " + self.aw.qmc.titleB
                    self.aw.qmc.setProfileBackgroundTitle(titleB)
#                self.aw.qmc.updateBackground()
                self.aw.qmc.fig.canvas.draw()
        
        if not self.aw.qmc.flagon:
            self.aw.sendmessage(QApplication.translate("Message","Roast properties updated but profile not saved to disk", None))
        self.close()