'''
/** Original work Copyright 2024 damiano IU3QGD
 * https://to-be-defined
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *     Unless required by applicable law or agreed to in writing, software
 *     distributed under the License is distributed on an "AS IS" BASIS,
 *     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *     See the License for the specific language governing permissions and
 *     limitations under the License.

 * Addition to the licence: NO company with more than 50employees can use this SW and you are NOT entitled to store this software 
 * on ANY storage that in some way attempts to monetize it (eg: github)
 * corporations and banks are too rich and too powerful, they MUST be split and ALL the money given back to the state

 
    VOX logic, not easy
    1) There is an ENABLE VOX bit that is used to enable the whole mechanism
       VOX can be enable/disabled by F - 7 in the main screen
       can also be enabled using PerpperDock, there is a checkbox
       
    2) VOX depends on the MIC level, and there is 0xE70 MIC SENS idx from 0 to 4 that adjust the mic sensitivity
       However, it is not clear if the VOX levels are bound to this sensisivity or not
       
    3) There is a VOX voice level, from 1 to 100 that should define the level at which the "voice" is detected
       by default the initial chip value has 8 as a value
    
    4) There is a VOX silcence level, from 1 to 100 that defines when silence is detected
       by default the initial chip value is 6 as a value
       
    5) There is a VOX Detection Delay, in units of 128ms, initial value 8 and max value 15
       It seems like that the vox status is reported with a delay
    
    6) There is a VOX Detection Interval, possibly in units of 128ms, initial value 8 and max value 15
       Meaning that this time interval is vaited to decide what to report
       
     7) There is a VOX Hunkhup delay time (units of tents of second) that define the time to wait before switch to RX since vox detected silence.
     
     8) VOX should NOT kill a TX that has started by someone else (PTT, DTMF, or others) and you can see the TX source in the main panel
        The label S1 will appear if you press PTT, when using vox S4 will appear
        
     9) It is possible to see VOX reactions in real time, in the status bar, when vox detect voice a >< symbol will appear
        It will disappear when detect silcence    
        
     A user should have all information to adjust VOX to suit the needs    
     
     NOTE: MIC gain is now a direct value into the chip, meaning that it has the following range
     0=min; 0x1F=max; 0.5dB/step initial value 0x10
 
 
'''


# -------------------------------------------------------------------------
# There is only ONE poll, this is a GUI and if there is something to send it should be set to the poll

from __future__ import annotations

from tkinter import IntVar, StringVar, BooleanVar
from tkinter.filedialog import asksaveasfilename, askopenfilename
import traceback
from typing import List, cast
import typing

from app_config import AppConfig, ConfigSavable
from app_csv import CsvTable, Csv_Writer
from app_version import PREPPERDOCK_version
import edsquelch_gui
import fmeetable
from glob_eeprom import GlobEeprom, EEblock
from glob_fun import string_to_bool
from glob_gui import JtkWinToplevel, JtkCheckbox, TV_Entry, JtkPanelGrid, \
     LogPanel, GUI_hide_show_window, JtkPanelPackTop, JtkPanelTabbed, JtkLabel
from glob_ippqs import Qsk5_req_read_eeprom, Qsk5_req_GUI_command
from glob_mlang import PrepperMlang, MlangLabel
from ipp_parser import  Qsk5_req_syscmd, Qsk5_req_identity
import qfscanlist_gui
import qpystat
import qsagc_gui
import qsbeep_gui
import qschannels_gui
import qscontacts_gui
import rabands_gui
import tkinter.ttk as ttk


# ====================================================================
# the EEprom container should be somewhere else and this gui pick up the pieces it needs
class Qseeprom_gui (CsvTable,ConfigSavable,GUI_hide_show_window):


    # ----------------------------------------------------------------
    def __init__(self, stat : qpystat.Qpystat):
        self._stat = stat
        
        self._stat.appconfig.addMyselfToSavables(self)
        self._stat.appcsv.add_to_tablesList(self)

        self._toplevel = JtkWinToplevel("EEPROM window")
        
        aframe=JtkPanelPackTop(self._toplevel)     
    
        aframe.addItem(MlangLabel(aframe,"First Read, then Write",self._stat.glob_mlang, style='Header1.TLabel' ))
        aframe.addItem(self._new_ButtonsPanel_all(aframe))
        
        self._loadingBar = ttk.Progressbar(aframe, orient='horizontal', mode='determinate', maximum=GlobEeprom.ee_blocks_max)
        aframe.addItem(self._loadingBar)
        
        self._newAllVars()
        
        aframe.addItem(self._newCenterPanel(aframe),fill='both',expand=True)
        
        aframe.pack(fill='both',expand=True)
        
        self.GUI_hide_window()
        
        self._println("Qseeprom_gui init complete")

    # --------------------------------------------------------------------------------------------------------------------
    # MUST override this method
    def csv_tableName(self) -> str:
        return 'qsk5_parameters'
    
    # ----------------------------------------------------------
    def csv_write_header(self, c_writer : Csv_Writer ):
        c_writer.writerow(EE_gui_var.csv_header_list)
    
    # ---------------------------------------------------------
    # in a different thread than swing, write the table to this csv writer
    def csv_write_table(self, c_writer : Csv_Writer ):
        
        for row in self._ee_gui_vars:
            csv_vals = row.csv_getRow()
            c_writer.writerow(csv_vals) 

    # -------------------------------------------------------------
    def _get_gui_var(self, v_name : str ) -> EE_gui_var:
        for row in self._ee_gui_vars:
            if row.csv_match_rowid(v_name):
                return row   
            
        return typing.cast(EE_gui_var, None)

    # -------------------------------------------------------------
    # I need to find the right "variable" to set the value back
    def csv_parse_row(self, row_values : typing.List[str] ):

        p_name : str = row_values[0]
        p_type : str = row_values[1]
        p_value : str = row_values[3]

        a_var : EE_gui_var = self._get_gui_var(p_name)
        
        if not a_var:
            self._println("CSV_parse_row: unknown "+p_name+" type "+p_type)
            return
        
        a_var.csv_setValue(p_value)
        
        

    # --------------------------------------------------------------------------------------------------------------------
    # vars are stored in a way that allows to save and load from CSV
    def _newAllVars(self):
        
        self.is_EEPROM_loaded=False
        
        self._ee_gui_vars : List[EE_gui_var] = [] 

        #self._nation_code   = self._addToVarlist(EE_var_uint8   (0xF40, 0, 'nation_code', 'Nation Code', 0))
        
        self._en_pmr          = self._addToVarlist(EE_var_bool_bit(0xF40, 1, 'en_pmr_tx'       , 'Enable PMR446 TX' ,0 ,True))
        self._en_memch_wr     = self._addToVarlist(EE_var_bool_bit(0xF40, 1, 'en_memch_wr'     ,'Write Memory Name' ,1 ,True))
        self._en_jto_callch   = self._addToVarlist(EE_var_bool_bit(0xF40, 1, 'en_jto_callch'   ,'Switch on, select Call Channel', 2 ,False))
        self._en_FILTER_Msgs  = self._addToVarlist(EE_var_bool_bit(0xF40, 1, 'en_filter_msgs'  ,'Filter Messages'   ,3 ,False))
        self._en_FL_BLIP      = self._addToVarlist(EE_var_bool_bit(0xF40, 1, 'en_white_blip'   ,'White LED Blip'    ,4 ,False))
        self._en_hambands     = self._addToVarlist(EE_var_bool_bit(0xF40, 1, 'en_ham_tx'       ,'Enable Ham TX'     ,5 ,False))
        self._en_dtmf_xCSS    = self._addToVarlist(EE_var_bool_bit(0xF40, 1, 'en_DTMF_xCSS'    ,'DTMF with SubTone' ,6 ,False))
        self._en_racmd_0nonce = self._addToVarlist(EE_var_bool_bit(0xF40, 1, 'en_racmd_0nonce' ,'Zero Nonce Enable' ,7 ,True))
        
        self._en_MIC_agc    = self._addToVarlist(EE_var_bool_bit(0xF40, 2, 'en_mic_agc'    ,'Mic AGC (Automatic Gain Control)'     ,0 ,True))
        self._en_AF_VOXc    = self._addToVarlist(EE_var_bool_bit(0xF40, 2, 'en_AF_vox'     ,'Active VOX'   ,1 ,False))
        self._en_BFSK_SYLED = self._addToVarlist(EE_var_bool_bit(0xF40, 2, 'en_FSK_sysled' ,'White LED blink on SYNC FSK'   ,2 ,False))
        self._KEYS_locked   = self._addToVarlist(EE_var_bool_bit(0xF40, 2, 'en_keys_locked','Keys Locked' ,3 ,False))
        self._KEYS_autolock = self._addToVarlist(EE_var_bool_bit(0xF40, 2, 'en_keys_autolock','Keys Autolock', 4 ,False))
        self._DTMF_side_tone= self._addToVarlist(EE_var_bool_bit(0xF40, 2, 'en_dtmf_sidetone','DTMF sidetone ON', 5 ,False))
        self._en_battlow_TX = self._addToVarlist(EE_var_bool_bit(0xF40, 2, 'en_battlow_TX'   ,'TX with Low Battery' , 6 ,False))

        self._en_BEEP_keybd = self._addToVarlist(EE_var_bool_bit(0xF40, 3, 'en_beep_keybd','Beep Keys'    ,0 ,False))
        self._en_BEEP_msgr  = self._addToVarlist(EE_var_bool_bit(0xF40, 3, 'en_beep_msgr','Ringtone Message',1 ,False))
        self._en_BEEP_calls = self._addToVarlist(EE_var_bool_bit(0xF40, 3, 'en_beep_calls','Ringtone Selective Calls',2 ,False))

        # ---------------------
        self._vox_DDelayTime    = self._addToVarlist(EE_var_uint8     (0x0EA0, 0,  'vox_ddelay_t', 'Detect delay (max 15)', 8))
        self._selcall_exp_s     = self._addToVarlist(EE_var_uint8     (0x0EA0, 2,  'selcall_exp_s', 'Back to Call Ch. (s) (10-254)', 15))
        self._msg_ack           = self._addToVarlist(EE_var_bool_bit  (0x0EA0, 3,  'msg_ack','Message ACK (Acknowledge)',1 ,True))
        self._msg_from          = self._addToVarlist(EE_var_uint8     (0x0EA0, 4,  'msg_from', 'Message From', 0))
        self._vox_DIntervalTime = self._addToVarlist(EE_var_uint8     (0x0EA0, 6,  'vox_dinterval_t', 'Detect interval (max 15)', 8))
        self._LCD_contrast      = self._addToVarlist(EE_var_uint8     (0x0EA0, 8,  'LCD_contrast', 'LCD contrast (def. 31)', 31))
        self._en_show_bs        = self._addToVarlist(EE_var_bool_bit  (0x0EA0, 9,  'en_show_bs','Show Batt. save level',0 ,False))
        self._tail_tone_len     = self._addToVarlist(EE_var_uint8     (0x0EA0, 10, 'tail_tone_len', 'Tail Tone length (cent.s)', 30))
        self._scall_ring_count  = self._addToVarlist(EE_var_uint8     (0x0EA0, 12, 'scall_ring_count','Sel Call ring count(0..20)', 4))
        self._EE_revision       = self._addToVarlist(EE_var_uint16    (0x0EA0, 14, 'EE_revision','EEPROM structure version',0))
        
        # ---------------------
        
        self._ee_callch         = self._addToVarlist(EE_var_uint8     (0x0E70, 0,  'ecall_ch'       ,'Call Channel', 0))
        self._keys_lock_6s      = self._addToVarlist(EE_var_uint8     (0x0E70, 3,  'keys_lock_6s'   ,'Keys lock time out (x6s)', 10))  # also needs to be enabled
        self._vox_off_level     = self._addToVarlist(EE_var_uint8     (0x0E70, 5,  'vox_off_lvl'    ,'Off level threshold', 1))
        self._vox_on_level      = self._addToVarlist(EE_var_uint8     (0x0E70, 6,  'vox_on_lvl'     ,'On level threshold', 2))
        self._mic_gain          = self._addToVarlist(EE_var_uint8     (0x0E70, 7,  'mic_gain'       ,'MIC gain (0.5dB by step, max 31)', 0x10))
        self._TXbias_PMR_sub    = self._addToVarlist(EE_var_uint8     (0x0E70, 9,  'TXbias_PMR_sub' ,'TX bias PMR subtract (max -95)', 55))
        self._TXbias_LPD_sub    = self._addToVarlist(EE_var_uint8     (0x0E70, 10, 'TXbias_LPD_sub' ,'TX bias LPD subtract (max -95)', 85))
        self._vox_minT_ds       = self._addToVarlist(EE_var_uint8     (0x0E70, 14, 'vox_minT_ds'    ,'Minimum active time (dec.s)', 20))  # tenth of seconds
        self._batt_save_hyst_s  = self._addToVarlist(EE_var_uint8     (0x0E70, 15, 'batts_hyst_s'   ,'Batt Save Hysteresis (s)', 5)) 
         
        self._ledw2_on_ts       = self._addToVarlist(EE_var_uint8     (0x0E80, 8,  'LEDW_2_on_ts'   ,'White LED2 ON time (dec.s)', 1))
        self._ledw2_off_ts      = self._addToVarlist(EE_var_uint8     (0x0E80, 9,  'LEDW_2_off_ts'  ,'White LED2 OFF time (dec.s)',1))
        self._scl_nolink_cs     = self._addToVarlist(EE_var_uint8     (0x0E80, 12, 'SCL_Squelch_cs' ,'Scan Squelch time (1 to 255, cent.s)',1))
        self._scl_link_max_s    = self._addToVarlist(EE_var_uint8     (0x0E80, 13, 'SCL_Timeout_s'  ,'Scan TimeOut (1s to 255s)',5))

        self._PON_time_ds       = self._addToVarlist(EE_var_uint8     (0x0E90, 0, 'pon_time_ds', 'Power On Time (dec.s)', 15))
        self._BEEP_level        = self._addToVarlist(EE_var_uint8     (0x0E90, 6, 'beep_volume', 'Beeps volume (20 to 85)', 20)) 

        self._radio_name        = self._addToVarlist(EE_var_string_ascii (0x0EB0, 0, 'radio_name_1' ,'Radio Name' ,16))  
        self._radio_name._csv_rowalias='wecome_1'
        
        self._radio_welcome     = self._addToVarlist(EE_var_string_ascii (0x0EC0, 0, 'radio_wecome' ,'Welcome' ,16))
        self._radio_welcome._csv_rowalias='wecome_2'
        
        self._aes_key           = self._addToVarlist(EE_var_string_hex   (0x0F30, 0, 'aes_key' ,'AES KEY' ,16))
        
        self._DTMF_auto_reset_s = self._addToVarlist(EE_var_uint8     (0x0ED0, 4, 'DTMF_auto_reser_s' ,'Auto Clear DTMF (3..60s)', 4))
        self._DTMF_preload_cs   = self._addToVarlist(EE_var_uint8     (0x0ED0, 5, 'DTMF_preload_cs'   ,'Preload TX time (cent.s)', 10))
        self._DTMF_tone_time_cs = self._addToVarlist(EE_var_uint8     (0x0ED0, 8, 'DTMF_tone_time_cs' ,'Tone length (cent.s)', 10))
        self._DTMF_mute_time_cs = self._addToVarlist(EE_var_uint8     (0x0ED0, 9, 'DTMF_mute_time_cs' ,'Mute length (cent.s)', 10))

        self._DTMF_myID         = self._addToVarlist(EE_var_uint32     (0x0EE0, 0,  'dtmf_my_id', 'My ID', 0))
        self._aes_nonce         = self._addToVarlist(EE_var_uint32     (0x0EE0, 4,  'aes_nonce', 'AES nonce', 0))
        
        self._upcode            = self._addToVarlist(EE_var_string_dtmf (0x0F00, 0, 'dtmf_upcode', 'UpCode', 16))  
        self._downcode          = self._addToVarlist(EE_var_string_dtmf (0x0F10, 0, 'dtmf_downcode', 'DownCode', 16))
        
        self._bs1_off           = self._addToVarlist(EE_var_uint8     (0x0F20, 0, 'battsave_v1_off', 'Battery Save 1 off', 0))
        self._bs1_on            = self._addToVarlist(EE_var_uint8     (0x0F20, 1, 'battsave_v1_on',  'Battery Save 1 on', 1))
        self._bs2_off           = self._addToVarlist(EE_var_uint8     (0x0F20, 2, 'battsave_v2_off', 'Battery Save 2 off', 2))
        self._bs2_on            = self._addToVarlist(EE_var_uint8     (0x0F20, 3, 'battsave_v2_on',  'Battery Save 2 on', 3))
        self._bs3_off           = self._addToVarlist(EE_var_uint8     (0x0F20, 4, 'battsave_v3_off', 'Battery Save 3 off', 4))
        self._bs3_on            = self._addToVarlist(EE_var_uint8     (0x0F20, 5, 'battsave_v3_on',  'Battery Save 3 on', 5))
        self._bs4_off           = self._addToVarlist(EE_var_uint8     (0x0F20, 6, 'battsave_v4_off', 'Battery Save 4 off', 5))
        self._bs4_on            = self._addToVarlist(EE_var_uint8     (0x0F20, 7, 'battsave_v4_on',  'Battery Save 4 on', 6))

        
    # -------------------------------------------------------------
    def _addToVarlist(self, avar : EE_gui_var ) -> EE_gui_var:
        self._ee_gui_vars.append(avar)
        return avar
    
    # -------------------------------------------------------------
    def update_CPU_id(self, cpuid_hex ):
        self._rabands_table.update_CPU_id(cpuid_hex)

    # -------------------------------------------------------------
    def checkVersionCompatible(self, f_version : str ):
        self.cur_radio_fw_version = int(f_version)
        
        app_v = int(PREPPERDOCK_version)
        
        if self.cur_radio_fw_version > app_v:
            self._println("FW version="+f_version)
            self._println("PD version="+PREPPERDOCK_version)
            
            self._stat.showMessageBox("Versione Radio più recente di PrepperDock\nProbabili errori di configurazione\nAggiornare PrepperDock")


    # ---------------------------------------------------
    # MUST be called from swing thread
    def _setLoadingProgressSwing(self, level : int):
        self._loadingBar['value'] = level

    # --------------------------------------------------------------------
    # This can be called from non swing threads
    def setLoadingProgress(self, level : int):
        self._runOnGuiIdle(self._setLoadingProgressSwing, level)

    # ---------------------------------------------------------------------
    # MUST be called in swing thread
    # Goes to all registered listener to eeprom update
    def _signalEepromLoadedToGui(self):
            
        self._csvButtonsEnable()

        try:

            for eegui_var in self._ee_gui_vars:
                eegui_var.eeprom_loadFrom(self._stat.globeeprom)
                
            self._qschannels_table.updateGuiFromEeprom()
            self._qcontacts_table.updateGuiFromEeprom()
            self._qsfmtable.updateGuiFromEeprom()
            self._qsquelch_table.updateGuiFromEeprom()
            self._qsagc_table.updateGuiFromEeprom()
            self._qfscanlist_table.updateGuiFromEeprom()
            self._qsbeep_table.updateGuiFromEeprom()
            
            self._stat.qscalibrate_gui.updateGuiFromEeprom()
            # NEWPANEL
        except Exception as _exc:
            self._println(traceback.format_exc())
        
        self._setLoadingProgressSwing(0)
        
        
    # -----------------------------------------------------------------------
    # Called from NON swing thread
    def signalEEpromLoaded(self):
        self._runOnGuiIdle(self._signalEepromLoadedToGui)

    # --------------------------------------------------------------------
    # The center panel MUST be a grid layout where ALL subpanels are placed at 0,0
    # then, raise is used to show a specific one
    def _newCenterPanel(self, parentPanel ) -> ttk.Frame:
        
        apanel=ttk.Frame(parentPanel,padding='3')

        apanel.grid_rowconfigure(0, weight = 1)
        apanel.grid_columnconfigure(0, weight = 1)

        self._newLogPanel(apanel)
        self._newQscntcPanel(apanel)
        self._newFMtablePanel(apanel)
        self._newParametersPanel(apanel).grid(row=0, column=0, sticky ="nsew")
        self._newQsquelch_panel(apanel)
        self._newQsagc_panel(apanel)
        self._newQfscanlistPanel(apanel)
        self._newQsbeep_panel(apanel)
        self._newRabands_panel(apanel)
        self._newQschannelsPanel(apanel)
        
        # NEWPANEL
        
        return apanel

    # -------------------------------------------------------------------------------
    def _newQfscanlistPanel(self, parentPanel):
        self._qfscanlist_table=qfscanlist_gui.QFscan_gui(self._stat,parentPanel)
        self._qfscanlist_table._work_panel.grid(row=0, column=0, sticky ="nsew")

    # -------------------------------------------------------------------------------
    # Need to leave like this since grid does not return itself
    def _newQschannelsPanel(self, parentPanel):
        self._qschannels_table=qschannels_gui.Qchannels_gui(self._stat,parentPanel)
        self._qschannels_table.work_panel.grid(row=0, column=0, sticky ="nsew")
    
    # -------------------------------------------------------------------------------
    # Need to leave like this since grid does not return itself
    def _newQsquelch_panel(self, parentPanel : ttk.Frame):
        #self._qsquelch_table=qsquelch_gui.Qsquelch_gui(self._stat , parentPanel)
        #self._qsquelch_table.work_panel.grid(row=0, column=0, sticky ="nsew")
        self._qsquelch_table=edsquelch_gui.Edsquelch_gui(self, parentPanel)
        self._qsquelch_table.work_panel.grid(row=0, column=0, sticky ="nsew")
    
    # -------------------------------------------------------------------------------
    # Need to leave like this since grid does not return itself
    def _newQsagc_panel(self, parentPanel : ttk.Frame):
        self._qsagc_table=qsagc_gui.Qsagc_gui(self, parentPanel)
        self._qsagc_table.work_panel.grid(row=0, column=0, sticky ="nsew")

    # -------------------------------------------------------------------------------
    # Need to leave like this since grid does not return itself
    def _newQscntcPanel(self, parentPanel):
        self._qcontacts_table=qscontacts_gui.EE_qscntc_gui(self._stat,parentPanel)
        self._qcontacts_table.work_panel.grid(row=0, column=0, sticky ="nsew")

    # -------------------------------------------------------------------------------
    # Need to leave like this since grid does not return itself
    def _newFMtablePanel(self, parentPanel):
        self._qsfmtable=fmeetable.QFMtable_gui(self._stat,parentPanel)
        self._qsfmtable.work_panel.grid(row=0, column=0, sticky ="nsew")

    # -------------------------------------------------------------------------------
    # Need to leave like this since grid does not return itself
    def _newQsbeep_panel(self, parentPanel : ttk.Frame):
        self._qsbeep_table=qsbeep_gui.Qsbeep_gui(self, parentPanel)
        self._qsbeep_table.work_panel.grid(row=0, column=0, sticky ="nsew")

    # -------------------------------------------------------------------------------
    # Need to leave like this since grid does not return itself
    def _newRabands_panel(self, parentPanel : ttk.Frame):
        self._rabands_table=rabands_gui.Rabands_gui(self, parentPanel) 
        self._rabands_table.work_panel.grid(row=0, column=0, sticky ="nsew")

        
    # -------------------------------------------------------------------------------
    # Need to leave like this since grid does not return itself
    def _newLogPanel(self, parentPanel ):
        self._log_gui = LogPanel(parentPanel,"EEPROM Log")
#        self._log_gui.pack(fill='both',expand=True)
        self._log_gui.grid(row=0, column=0, sticky ="nsew")
        
       
        
    # -------------------------------------------------------------------
    # prints something on the application log
    def _println(self, msg):
        self._log_gui.println(msg)    
        
    # --------------------------------------------------------------------
    # show a window that has been hidden using the withdraw method
    def GUI_show_window(self):
        self._toplevel.deiconify()    

    # --------------------------------------------------------------------
    # hide completely a window
    def GUI_hide_window(self):
        self._toplevel.withdraw()    
        
        
    # -----------------------------------------------------------
    # it needs to be done in swing thread
    def _GUI_refresh(self):
        self._qschannels_table.GUI_refresh()
        self._qsquelch_table.GUI_refresh()
        self._qsagc_table.GUI_refresh()
        self._qcontacts_table.GUI_refresh()
        self._qfscanlist_table.GUI_refresh()
#        self._rabands_table.GUI_refresh()
        # NEWPANEL
        
    # -----------------------------------------------------------
    # CSV import need to refresh the content on load end
    def GUI_refresh(self):
        self._runOnGuiIdle(self._GUI_refresh)

    # -----------------------------------------------------------
    # Mostly a reminder that this method is available everywhere    
    def _runOnGuiIdle(self, func, *args ):
        self._toplevel.after_idle(func, *args)

    # -------------------------------------------------------------------------------
    # make a new frame and put buttons into it, but stay in this class
    def _new_ButtonsPanel_all(self, container):

        cframe = JtkPanelGrid(container)

        cframe.addItem(ttk.Button(cframe,text="Read" , command=self._click_readEeprom ))
        
        self._writeButton=ttk.Button(cframe,text="Write" , command=self._click_writeEeprom, state='disabled')
        cframe.addItem(self._writeButton)
        
        self._writeAllButton=ttk.Button(cframe,text="Write All" , command=self._click_writeAllEeprom, state='disabled')
        cframe.addItem(self._writeAllButton)

        cframe.addItem(ttk.Button(cframe,text="Load Cfg" , command=self._clickLoadConfiguration ))
        
        self._saveCfgButton=ttk.Button(cframe,text="Save Cfg" , command=self._clickSaveConfiguration, state='disabled')
        cframe.addItem(self._saveCfgButton)

        self._importCsvButton=ttk.Button(cframe,text="Import CSV" , command=self._clicImportCsv , state='disabled')
        cframe.addItem(self._importCsvButton)
        
        self._exportCsvButton=ttk.Button(cframe,text="Export CSV" , command=self._clickExportCsv, state='disabled')
        cframe.addItem(self._exportCsvButton)

        cframe.nextRow()
        
        combo_choiches = ["Do nothing on Write","Reboot on Write","Reload on Write","Halt CPU"]

        self._on_write_end=ttk.Combobox(cframe,values=combo_choiches,width=15)
        self._on_write_end.current(0)
        cframe.addItem(self._on_write_end)
        
        self._en_write_calibration = JtkCheckbox(cframe,"Write calibration")
        cframe.addItem(self._en_write_calibration)

        cframe.nextRow()

        cframe.addItem(ttk.Button(cframe,text="Channels"    ,style="EEPanels.TButton" ,command=self._clicShowRadioChannels))
        cframe.addItem(ttk.Button(cframe,text="Contacts"    ,style="EEPanels.TButton" ,command=self._clicShowContacts ))
        cframe.addItem(ttk.Button(cframe,text="Parameters"  ,style="EEPanels.TButton" ,command=self._clicShowParameters ))
        cframe.addItem(ttk.Button(cframe,text="Frq Scan"    ,style="EEPanels.TButton" ,command=self._clicShowQfscanlist ))
        cframe.addItem(ttk.Button(cframe,text="FM Channels" ,style="EEPanels.TButton" ,command=self._clicShowFmchannels))
        cframe.addItem(ttk.Button(cframe,text="Squelch"     ,style="EEPanels.TButton" ,command=self._clicShowSquelch))
        cframe.addItem(ttk.Button(cframe,text="AGC"         ,style="EEPanels.TButton" ,command=self._clicShowAgc))

        cframe.nextRow()

        cframe.addItem(ttk.Button(cframe,text="Beeps"       ,style="EEPanels.TButton" ,command=self._clicShowQsbeep))
        cframe.addItem(ttk.Button(cframe,text="Radio Bands" ,style="EEPanels.TButton" ,command=self._clicShowRabands))
        cframe.addItem(ttk.Button(cframe,text="Log Panel"   ,style="EEPanels.TButton" ,command=self._clicShowLog))

        return cframe
    
    
    # --------------------------------------------------------------------------------------
    # if you ever wish to disable a button, use the keyword state='disabled'
    def _csvButtonsEnable(self):
        
        self.is_EEPROM_loaded=True
        
        state = 'normal'
        
        self._writeButton['state'] = state
        self._writeAllButton['state'] = state
        self._saveCfgButton['state'] = state    
        self._importCsvButton['state'] = state
        self._exportCsvButton['state'] = state

    # --------------------------------------------------------------------------------------
    # This is on GUI thread !
    def _clickLoadConfiguration(self):
        self._println("CLICK _clickLoadConfiguration")

        filetypes = ( ('EEprom file', '*.eeprom'),   ('All files', '*.*')   )

        initdir = self._stat.glob_mlang.getBandplanDir()

        filename = askopenfilename(parent=self._toplevel, title='Open EEPROM file',  filetypes=filetypes, initialdir=str(initdir))

        a_fname = str(filename)
        
        self._println("eepromLoadFromFile: file "+str(a_fname))
        
        if not a_fname:
            return

        self._stat.globeeprom.eepromLoadFromFile(a_fname)
        
        self._println("loaded EEPROM len 0x%x" % (self._stat.globeeprom.get_loadedLen() ) )
        
        self._signalEepromLoadedToGui()

    # --------------------------------------------------------------------------------------
    # the eeprom is actually saved in the global eeprom...
    # Should be run on a separate thread
    def _clickSaveConfiguration(self):
        self._println("CLICK _clickSaveConfiguration")
        
        self._click_update_EEblock_from_GUI()
        
        fw_version = self._stat.qconnect.cur_radio_fw_version
        
        if not fw_version:
            self._stat.showMessageBox("Reading Firmware Version")
            # so, if you try a second time, it will not popup
            self._stat.qconnect.cur_radio_fw_version='unknown'
            return
        
        initial_fname='qsk5-eeprom-'+fw_version+'.eeprom'
        
        files = [('EEprom Image', '.eeprom'), ('All Files', '*.*')]
        ffname = asksaveasfilename(parent=self._toplevel, title="Save EEPROM to File", initialfile=initial_fname, filetypes=files, defaultextension = '.eeprom' ) 
        
        self._stat.globeeprom.ee_saveToFile(ffname)

    # --------------------------------------------------------------------------------------
    def _clicImportCsv(self):
        filetypes = ( ('CSV files', '*.csv'),   ('All files', '*.*')   )

        ffname = askopenfilename(parent=self._toplevel, title='Open n CSV file',  filetypes=filetypes)

        self._stat.appcsv.import_from_file(ffname) 


    # --------------------------------------------------------------------------------------
    def _clickExportCsv(self):
        
        self._click_update_EEblock_from_GUI()

        fw_version = self._stat.qconnect.cur_radio_fw_version
        
        if not fw_version:
            self._stat.showMessageBox("Please Identify the radio")
            # so, if you try a second time, it will not popup
            self._stat.qconnect.cur_radio_fw_version='unknown'
            return

        initial_fname='qsk5-eeprom-'+fw_version+'.csv'
        
        files = [('CSV files', '.csv'), ('All Files', '*.*')]
        ffname = asksaveasfilename(parent=self._toplevel, title="Export EEPROM to CSV", initialfile=initial_fname, filetypes=files, defaultextension = '.csv' )
        
        self._stat.appcsv.export_to_file(ffname) 

    # --------------------------------------------------------------------------------------
    # MUST be called even when saving bin !!
    def _click_update_EEblock_from_GUI(self):
        for arow in self._ee_gui_vars:
            arow.update_EEblock_from_GUI(self._stat.globeeprom)
        
        self._qschannels_table.update_EEblock_from_GUI()
        self._qcontacts_table.update_EEblock_from_GUI()
        self._qsquelch_table.update_EEblock_from_GUI()
        self._qsagc_table.update_EEblock_from_GUI()
        self._qfscanlist_table.update_EEblock_from_GUI()
        self._qsbeep_table.update_EEblock_from_GUI()
        # NEWPANEL
        
    # --------------------------------------------------------------------------------------
    # this will first mark all eeprom buffers as changed and then calla  write
    def _click_writeAllEeprom(self):
        self._println("CLICK Write ALL Eeprom")
        
        self._stat.globeeprom.markAllBlocksUpdated()
        
        self._click_writeEeprom()
    
    # --------------------------------------------------------------------------------------
    # Every block will update the eeprom container and then the global write request is run
    # note that you can reset the work bar only if you ask for reset of something at the end
    def _click_writeEeprom(self):
        self._println("CLICK Write Eeprom")

        self._click_update_EEblock_from_GUI()
                                
        # this will queue write request to radio, not actually DO them
        self._stat.globeeprom.ee_queueWriteToRadio(self._en_write_calibration.isChecked())
 
        write_end_cmd=self._on_write_end.current()
        
        if write_end_cmd: 
            cmd = Qsk5_req_syscmd(write_end_cmd)
            self._stat.qconnect.queuePut(cmd)
            self._println("Write end cmd="+str(write_end_cmd))

            
        self._stat.qconnect.queuePut(Qsk5_req_GUI_command('resetLoadingBar'))    
            

    # --------------------------------------------------------------------------------------
    # I need to make it a command that goes to the polling window
    # Polling, will eventually call me back with the data
    def _click_readEeprom(self):
        self._println("CLICK Read Eeprom")

        cmd = Qsk5_req_identity()
        self._stat.qconnect.queuePut(cmd)

        cmdb = Qsk5_req_read_eeprom(0,GlobEeprom.ee_blocks_max)
        self._stat.qconnect.queuePut(cmdb)

    def _clicShowQfscanlist(self):
        self._qfscanlist_table.tkraise()

    def _clicShowContacts(self):
        self._qcontacts_table.tkraise()

    def _clicShowParameters(self):
        self._parametersPanel.tkraise()

    def _clicShowFmchannels(self):
        self._qsfmtable.tkraise()

    def _clicShowSquelch(self):
        self._qsquelch_table.tkraise()

    def _clicShowAgc(self):
        self._qsagc_table.tkraise()

    def _clicShowQsbeep(self):
        self._qsbeep_table.tkraise()

    def _clicShowRabands(self):
        self._rabands_table.tkraise()

    def _clicShowLog(self):
        self._log_gui.tkraise()

    def _clicShowRadioChannels(self):
        self._qschannels_table.tkraise()


    # -------------------------------------------------------------------
    # implements the config savable
    def appImportConfig(self, cfg : AppConfig ):
        adict = cfg.getDictValue("qseeprom_cfg", {})
        
        try:
            self._println("geometry "+self._toplevel.geometry())

            self._toplevel.setGeometry(adict['gui_cfg'])
            self._on_write_end.current(adict['on_write_end_cmd'])
        except Exception as _exc :
            pass            

    # -------------------------------------------------------------------
    # implement the config savable
    def appSaveConfig(self, cfg : AppConfig ):
        adict : dict[str,typing.Any] = {}

        adict['gui_cfg'] = self._toplevel.getGeometry()
        adict['on_write_end_cmd'] = self._on_write_end.current()

        cfg.setDictValue("qseeprom_cfg", adict )            

    # -------------------------------------------------------------------------------
    # I need a few disjoined panels
    def _newParametersPanel(self, parent_panel) -> ttk.Widget:

        a_panel=JtkPanelTabbed(parent_panel)
        self._parametersPanel=a_panel
        
        a_panel.addItem(self._newParametersPanel_Ident(a_panel),"ID + String")
        a_panel.addItem(self._newBooleansPanel(a_panel),"Options")
        a_panel.addItem(self._new_VOX_params_Panel(a_panel),"VOX")
        a_panel.addItem(self._newBatterySavePanel(a_panel),"Battery")
        a_panel.addItem(self._newDTMF_params_panel(a_panel),"DTMF")
        
        return a_panel

    # -------------------------------------------------------------------------------
    def _new_VOX_params_Panel(self, parent_panel) -> ttk.Frame:
        pp_frame=JtkPanelGrid(parent_panel, borderwidth=1, relief='solid', padding=3)

        pp_frame.addItem(JtkLabel(pp_frame,"VOX", style='Bold.TLabel'),columnspan=3)

        pp_frame.nextRow()

        pp_frame.addItem(self._en_AF_VOXc.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._en_AF_VOXc.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._vox_DDelayTime.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._vox_DDelayTime.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._vox_DIntervalTime.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._vox_DIntervalTime.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._vox_off_level.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._vox_off_level.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._vox_on_level.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._vox_on_level.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._vox_minT_ds.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._vox_minT_ds.gui_getComponent(pp_frame))

        return pp_frame


        
    # -------------------------------------------------------------------------------
    def _newParametersPanel_Ident(self, parent_panel) -> ttk.Frame:
        pp_frame=JtkPanelGrid(parent_panel, borderwidth=1, relief='solid', padding=3)

        pp_frame.addItem(self._DTMF_myID.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._DTMF_myID.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._msg_from.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._msg_from.gui_getComponent(pp_frame))
        pp_frame.nextRow()
        
        pp_frame.addItem(self._ee_callch.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._ee_callch.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._radio_name.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._radio_name.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._radio_welcome.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._radio_welcome.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._upcode.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._upcode.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._downcode.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._downcode.gui_getComponent(pp_frame))
        
        pp_frame.nextRow()

        pp_frame.addItem(self._aes_key.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._aes_key.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._aes_nonce.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._aes_nonce.gui_getComponent(pp_frame))
        
        return pp_frame


    # -------------------------------------------------------------------------------
    def _newBooleansPanel(self, parent_panel) -> ttk.Frame:
        pp_frame=JtkPanelGrid(parent_panel, borderwidth=1, relief='solid', padding=3)

        pp_frame.addItem(self._en_pmr.gui_getComponent(pp_frame))
        pp_frame.addItem(self._PON_time_ds.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._PON_time_ds.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._en_hambands.gui_getComponent(pp_frame))
        pp_frame.addItem(self._tail_tone_len.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._tail_tone_len.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._en_memch_wr.gui_getComponent(pp_frame))
        pp_frame.addItem(self._BEEP_level.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._BEEP_level.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._en_jto_callch.gui_getComponent(pp_frame))
        pp_frame.addItem(self._LCD_contrast.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._LCD_contrast.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._en_FILTER_Msgs.gui_getComponent(pp_frame))
        pp_frame.addItem(self._mic_gain.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._mic_gain.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._en_FL_BLIP.gui_getComponent(pp_frame))
        pp_frame.addItem(self._keys_lock_6s.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._keys_lock_6s.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._en_MIC_agc.gui_getComponent(pp_frame))
        pp_frame.addItem(self._ledw2_on_ts.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._ledw2_on_ts.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._en_BFSK_SYLED.gui_getComponent(pp_frame))
        pp_frame.addItem(self._ledw2_off_ts.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._ledw2_off_ts.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._KEYS_locked.gui_getComponent(pp_frame))
        pp_frame.addItem(self._scl_nolink_cs.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._scl_nolink_cs.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._KEYS_autolock.gui_getComponent(pp_frame))
        pp_frame.addItem(self._scl_link_max_s.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._scl_link_max_s.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._msg_ack.gui_getComponent(pp_frame))
        pp_frame.addItem(self._EE_revision.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._EE_revision.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._en_BEEP_keybd.gui_getComponent(pp_frame))
        pp_frame.addItem(self._TXbias_PMR_sub.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._TXbias_PMR_sub.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._en_BEEP_msgr.gui_getComponent(pp_frame))
        pp_frame.addItem(self._TXbias_LPD_sub.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._TXbias_LPD_sub.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._en_BEEP_calls.gui_getComponent(pp_frame))

        pp_frame.addItem(self._selcall_exp_s.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._selcall_exp_s.gui_getComponent(pp_frame))
        
        pp_frame.nextRow()

        pp_frame.addItem(self._en_show_bs.gui_getComponent(pp_frame))

        return pp_frame


    # -------------------------------------------------------------------------------
    def _newBatterySavePanel(self, parent_panel) -> ttk.Frame:
        pp_frame=JtkPanelGrid(parent_panel, borderwidth=1, relief='solid', padding=3)
        
        pp_frame.addItem(JtkLabel(pp_frame,"Battery Save", style='Bold.TLabel'),columnspan=3)

        pp_frame.nextRow()

        pp_frame.addItem(self._bs1_off.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._bs1_off.gui_getComponent(pp_frame))

        pp_frame.addItem(self._bs1_on.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._bs1_on.gui_getComponent(pp_frame))
        pp_frame.nextRow()
        
        pp_frame.addItem(self._bs2_off.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._bs2_off.gui_getComponent(pp_frame))

        pp_frame.addItem(self._bs2_on.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._bs2_on.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._bs3_off.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._bs3_off.gui_getComponent(pp_frame))

        pp_frame.addItem(self._bs3_on.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._bs3_on.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._bs4_off.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._bs4_off.gui_getComponent(pp_frame))

        pp_frame.addItem(self._bs4_on.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._bs4_on.gui_getComponent(pp_frame))
        pp_frame.nextRow()
        
        pp_frame.addItem(self._en_battlow_TX.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._en_battlow_TX.gui_getComponent(pp_frame),columnspan=2)

        pp_frame.nextRow()
        pp_frame.addItem(self._batt_save_hyst_s.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._batt_save_hyst_s.gui_getComponent(pp_frame))


        return pp_frame 

    # -------------------------------------------------------------------------------
    def _newDTMF_params_panel(self, parent_panel) -> ttk.Frame:
        pp_frame=JtkPanelGrid(parent_panel, borderwidth=1, relief='solid', padding=3)
        
        pp_frame.addItem(JtkLabel(pp_frame,"DTMF Parameters", style='Bold.TLabel'),columnspan=3)

        pp_frame.nextRow()

        pp_frame.addItem(self._DTMF_auto_reset_s.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._DTMF_auto_reset_s.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._DTMF_preload_cs.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._DTMF_preload_cs.gui_getComponent(pp_frame))
        pp_frame.nextRow()
        
        pp_frame.addItem(self._DTMF_tone_time_cs.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._DTMF_tone_time_cs.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._DTMF_mute_time_cs.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._DTMF_mute_time_cs.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._scall_ring_count.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._scall_ring_count.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._en_dtmf_xCSS.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._en_dtmf_xCSS.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._en_racmd_0nonce.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._en_racmd_0nonce.gui_getComponent(pp_frame))
        pp_frame.nextRow()

        pp_frame.addItem(self._DTMF_side_tone.gui_getMLabel(pp_frame,self._stat.glob_mlang))
        pp_frame.addItem(self._DTMF_side_tone.gui_getComponent(pp_frame))

        return pp_frame 



# ==================================================================================
# So, I have a bunch of vars that are saved into EEPROM and need to be put into GUI
# AND need to be import/export from CSV, the usual mess. So I need
# a way to get/set the var from EEPROM
# a way to get/set the var from GUI
# a way to get/set the var from CSV
# and I have three type of vars, String, int (signed and unsigned), boolean
# and the GUI label can/should be different than the CSV label
# So I need subclasses for the four types .. .... 
# Note that all edited vars should go into a list of vars to load/save on request
# Note that there should be a way to tell if a var has been changed or not by the user OR csv import
class EE_gui_var():
    
    # this is how the variable will be exported, param_name is the "index" to pick the var again
    csv_header_list = ['param_name','p_type','p_len','p_value']
    
    # ----------------------------------------------------------------------------------------
    # this is the base minimum to load/save from eeprom
    # the ee_type is mostly for debugging since the actual logic will be in subclasses
    def __init__(self, ee_address : int, ee_type : str, csv_rowid : str, gui_label : str ):
        self._ee_address=ee_address
        self._ee_type=ee_type
        self._csv_rowid=csv_rowid
        self.gui_label=gui_label          # so, you can create a ttk.Label, if you need it
        self._csv_rowalias=''             # if you need an alias, set it here

    

    # ---------------------------------------------------------------------------------------
    # depending on the type of the var the operation will be different
    # this save the CURRENT gui content into the flash
    # so, if you load from csv, you then have to go trough all vars and do a save to eeprom
    # NOTE that it is NOT a WRITE EEPROM, that is in the EEPROM class !!!
    def update_EEblock_from_GUI(self, eeprom : GlobEeprom):
        pass
    
    # ---------------------------------------------------------------------------------------
    # depending on the type of the var the operation will be different
    # this should be called by the EEPROM loader, once the whole eeprom is loaded
    # NOTE that an EEPROM can be read or written FROM/TO disk
    def eeprom_loadFrom(self, eeprom : GlobEeprom):
        pass
    
    # ---------------------------------------------------------------------------------------
    # subclasses MUST override this
    def csv_getLength(self) -> str:
        return ''
    
    # ---------------------------------------------------------------------------------------
    # I need to support alias definition pippo
    def csv_match_rowid(self, want_rowid : str ) -> bool:
        if self._csv_rowid == want_rowid:
            return True 
        
        if self._csv_rowalias == want_rowid:
            return True 

        return False
    
    # ---------------------------------------------------------------------------------------
    # subclasses MUST override this
    def csv_getValue(self) -> str:
        return ''
    
    # ---------------------------------------------------------------------------------------
    # subclasses MUST override this
    def csv_setValue(self, a_value : str ):
        pass

    # ---------------------------------------------------------------------------------------
    # Need to report the var row
    def csv_getRow(self) -> typing.List[str]:
        risul : typing.List[str] = []
        risul.append(self._csv_rowid)
        risul.append(self._ee_type)
        risul.append(self.csv_getLength())
        risul.append(self.csv_getValue())
        
        return risul
    
    # ---------------------------------------------------------------------------------------
    # NOT used, left here for reference
    def gui_getLabel(self, parent_frame : ttk.Frame ) -> ttk.Label:
        return ttk.Label(parent_frame, text=self.gui_label)
    
    # ---------------------------------------------------------------------------------------
    # get a multilingual label
    def gui_getMLabel(self, parent_frame : ttk.Frame, mlang : PrepperMlang ) -> ttk.Label:
        alabel=MlangLabel(parent_frame,self.gui_label, mlang)
        return alabel
    
    # ---------------------------------------------------------------------------------------
    # should return a component that can be put into a "panel"
    # NOTE that there is only ONE component and can be put on a panel ONCE only
    # possibly ttk.Frame is also a ttk.Widget
    # https://docs.python.org/3/library/tkinter.ttk.html#tkinter.ttk.Widget
    # the component will be created ONLY when this method is called
    def gui_getComponent(self, parent_frame : ttk.Frame) -> ttk.Widget:
        return ttk.Label(parent_frame, text='gui_getComponent: null')
    
    # --------------------------------------------------
    # this is the equivalent of toString()
    def __str__(self):
        return str(self._ee_address)
    
    

# ==================================================================================
# This is the easiste (there are even few of it
# a simple string that is coded as ASCII

class EE_var_string_ascii(EE_gui_var):

    def __init__(self, ee_address : int, ee_byte_index : int, csv_colname : str, gui_label : str,  ee_len : int, init_value='' ):
        EE_gui_var.__init__(self, ee_address, 's_ascii', csv_colname, gui_label)
        
        self._ee_byte_index=ee_byte_index
        self._ee_len=ee_len
        self._init_value=init_value
        
        self._avar : StringVar = cast(StringVar,None)

    # ---------------------------------------------------------------------------------------
    # subclasses should override this
    def csv_getLength(self) -> str:
        return str(self._ee_len)
    
    # ---------------------------------------------------------------------------------------
    # subclasses should override this
    def csv_getValue(self) -> str:
        return self._avar.get()

    # ---------------------------------------------------------------------------------------
    def csv_setValue(self, a_value : str ):
        self._avar.set(a_value)
    
    # --------------------------------------------------------------------------
    def update_EEblock_from_GUI(self, eeprom : GlobEeprom):
        ee_blk : EEblock =eeprom.get_block_from_address(self._ee_address)
        
        if not self._avar:
            raise Exception(self._csv_rowid+" MISSING gui component")
         
        ee_blk.set_user_string_new(self._avar.get(), 0, self._ee_len)

    # ---------------------------------------------------------------------------------------
    def eeprom_loadFrom(self, eeprom : GlobEeprom):
        ee_blk : EEblock =eeprom.get_block_from_address(self._ee_address)

        if not self._avar:
            raise Exception(self._csv_rowid+" MISSING gui component")

        self._avar.set(ee_blk.get_string_new(0, self._ee_len))
        
        
    # --------------------------------------------------------------------------
    def gui_getComponent(self, parent_frame : ttk.Frame) -> ttk.Widget:
        if self._avar:
            raise Exception(self._csv_rowid+" is already into GUI")
        
        self._avar = StringVar(parent_frame, value=self._init_value)
        a_entry = TV_Entry(parent_frame, self._avar)
        a_entry['width'] = self._ee_len
        return a_entry
    
# =========================================================================
# this is a string that limits the content to dtmf chars
class EE_var_string_dtmf(EE_gui_var):

    def __init__(self, ee_address : int, ee_byte_index : int, csv_colname : str, gui_label : str,  ee_len : int, init_value='' ):
        EE_gui_var.__init__(self, ee_address, 's_dtmf', csv_colname, gui_label)
        
        self._ee_byte_index=ee_byte_index
        self._ee_len=ee_len
        self._init_value=init_value
        
        self._avar : StringVar = cast(StringVar,None)

    # ---------------------------------------------------------------------------------------
    # subclasses should override this
    def csv_getLength(self) -> str:
        return str(self._ee_len)
    
    # ---------------------------------------------------------------------------------------
    # subclasses should override this
    def csv_getValue(self) -> str:
        return self._avar.get()

    # ---------------------------------------------------------------------------------------
    def csv_setValue(self, a_value : str ):
        self._avar.set(a_value)
        
        
    # -------------------------------------------------------------------------------
    # there is nothing useful passed in args...
    # the main issue is that I cannot get the variable back
    def _callbackWrite(self, var_name, var_type, optionsl):
        
        source : str = self._avar.get()
        
        destination : str = ''
        
        dtmf_digits = set("#*ABCD0123456789")
        for achar in source:
            if achar in dtmf_digits:
                destination = destination + achar
                
        self._avar.set(destination)
    
    
    # --------------------------------------------------------------------------
    def update_EEblock_from_GUI(self, eeprom : GlobEeprom):
        ee_blk : EEblock =eeprom.get_block_from_address(self._ee_address)
        
        if not self._avar:
            raise Exception(self._csv_rowid+" MISSING gui component")
        
        ee_blk.set_user_string_new(self._avar.get(), 0, self._ee_len)

    # ---------------------------------------------------------------------------------------
    def eeprom_loadFrom(self, eeprom : GlobEeprom):
        ee_blk : EEblock =eeprom.get_block_from_address(self._ee_address)

        if not self._avar:
            raise Exception(self._csv_rowid+" MISSING gui component")

        self._avar.set(ee_blk.get_string_new(0,self._ee_len))
        
        
    # --------------------------------------------------------------------------
    def gui_getComponent(self, parent_frame : ttk.Frame) -> ttk.Widget:
        if self._avar:
            raise Exception(self._csv_rowid+" is already into GUI")
        
        self._avar = StringVar(parent_frame, value=self._init_value)
        self._avar.trace_variable('w', self._callbackWrite)
        
        a_entry = TV_Entry(parent_frame, self._avar)
        a_entry['width'] = self._ee_len
        return a_entry

# ==================================================================================
# An unsigned byte

class EE_var_uint8(EE_gui_var):

    def __init__(self, ee_address : int, ee_byte_index : int, csv_colname : str, gui_label : str, init_value: int ):
        EE_gui_var.__init__(self, ee_address, 'uint8', csv_colname, gui_label)
        
        self._ee_byte_index=ee_byte_index
        self._init_value : int =init_value
        
        self._avar : IntVar = cast(IntVar,None)

    # ---------------------------------------------------------------------------------------
    # subclasses should override this
    def csv_getLength(self) -> str:
        return '1'
    
    # ---------------------------------------------------------------------------------------
    # subclasses should override this
    def csv_getValue(self) -> str:
        return str(self._avar.get())

    # ---------------------------------------------------------------------------------------
    def csv_setValue(self, a_value : str ):
        self._avar.set(int(a_value))

    
    # --------------------------------------------------------------------------
    def update_EEblock_from_GUI(self, eeprom : GlobEeprom):
        ee_blk : EEblock =eeprom.get_block_from_address(self._ee_address)
        
        if not self._avar:
            raise Exception(self._csv_rowid+" MISSING gui component")
        
        ee_blk.set_user_byte(self._ee_byte_index, self._avar.get())

    # ---------------------------------------------------------------------------------------
    def eeprom_loadFrom(self, eeprom : GlobEeprom):
        ee_blk : EEblock =eeprom.get_block_from_address(self._ee_address)

        if not self._avar:
            raise Exception(self._csv_rowid+" MISSING gui component")

        self._avar.set(ee_blk.get_byte(self._ee_byte_index))
        
    # --------------------------------------------------------------------------
    def gui_getComponent(self, parent_frame : ttk.Frame) -> ttk.Widget:
        if self._avar:
            raise Exception(self._csv_rowid+" is already into GUI")
        
        self._avar = IntVar(parent_frame, value=self._init_value)
        a_entry = TV_Entry(parent_frame, self._avar)
        a_entry['width'] = 4
        return a_entry


# ======================================================================================================================
# this is an uint32, so, I know it is 4 bytes long

class EE_var_uint32(EE_gui_var):

    def __init__(self, ee_address : int, ee_byte_index : int, csv_colname : str, gui_label : str, init_value=0 ):
        EE_gui_var.__init__(self, ee_address, 'uint32', csv_colname, gui_label)
        
        self._ee_byte_index=ee_byte_index
        self._init_value : int =init_value
        
        self._avar : IntVar  = cast(IntVar,None)
    
    # ---------------------------------------------------------------------------------------
    # subclasses should override this
    def csv_getLength(self) -> str:
        return '4'
    
    # ---------------------------------------------------------------------------------------
    # subclasses should override this
    def csv_getValue(self) -> str:
        return str(self._avar.get())

    # ---------------------------------------------------------------------------------------
    def csv_setValue(self, a_value : str ):
        self._avar.set(int(a_value))

    # --------------------------------------------------------------------------
    def update_EEblock_from_GUI(self, eeprom : GlobEeprom):
        ee_blk : EEblock =eeprom.get_block_from_address(self._ee_address)
        
        if not self._avar:
            raise Exception(self._csv_rowid+" MISSING gui component")
        
        avalue = self._avar.get()
        
        ee_blk.eeprom_pack_into('<I', self._ee_byte_index, avalue)
        
    # ---------------------------------------------------------------------------------------
    def eeprom_loadFrom(self, eeprom : GlobEeprom):
        ee_blk : EEblock =eeprom.get_block_from_address(self._ee_address)

        if not self._avar:
            raise Exception(self._csv_rowid+" MISSING gui component")

        touples=ee_blk.eeprom_unpack('<I',self._ee_byte_index)

        self._avar.set(touples[0])
        
    # --------------------------------------------------------------------------
    def gui_getComponent(self, parent_frame : ttk.Frame) -> ttk.Widget:
        if self._avar:
            raise Exception(self._csv_rowid+" is already into GUI")
        
        self._avar = IntVar(parent_frame, value=self._init_value)
        a_entry = TV_Entry(parent_frame, self._avar)
        a_entry['width'] = 10
        return a_entry

# ======================================================================================================================
# this is an uint16, so, I know it is 2 bytes long

class EE_var_uint16(EE_gui_var):

    def __init__(self, ee_address : int, ee_byte_index : int, csv_colname : str, gui_label : str, init_value=0 ):
        EE_gui_var.__init__(self, ee_address, 'uint16', csv_colname, gui_label)
        
        self._ee_byte_index=ee_byte_index
        self._init_value : int =init_value
        
        self._avar : IntVar  = cast(IntVar,None)
    
    # ---------------------------------------------------------------------------------------
    # subclasses should override this
    def csv_getLength(self) -> str:
        return '2'
    
    # ---------------------------------------------------------------------------------------
    # subclasses should override this
    def csv_getValue(self) -> str:
        return str(self._avar.get())

    # ---------------------------------------------------------------------------------------
    def csv_setValue(self, a_value : str ):
        self._avar.set(int(a_value))

    # --------------------------------------------------------------------------
    def update_EEblock_from_GUI(self, eeprom : GlobEeprom):
        ee_blk : EEblock=eeprom.get_block_from_address(self._ee_address)
        
        if not self._avar:
            raise Exception(self._csv_rowid+" MISSING gui component")
        
        avalue = self._avar.get()
        
        ee_blk.eeprom_pack_into('<H', self._ee_byte_index, avalue)
        
    # ---------------------------------------------------------------------------------------
    def eeprom_loadFrom(self, eeprom : GlobEeprom):
        ee_blk : EEblock =eeprom.get_block_from_address(self._ee_address)

        if not self._avar:
            raise Exception(self._csv_rowid+" MISSING gui component")

        touples=ee_blk.eeprom_unpack('<H',self._ee_byte_index)

        self._avar.set(touples[0])
        
    # --------------------------------------------------------------------------
    def gui_getComponent(self, parent_frame : ttk.Frame) -> ttk.Widget:
        if self._avar:
            raise Exception(self._csv_rowid+" is already into GUI")
        
        self._avar = IntVar(parent_frame, value=self._init_value)
        a_entry = TV_Entry(parent_frame, self._avar)
        a_entry['width'] = 6
        return a_entry




# ======================================================================================================================
# this is an int8, one byte long

class EE_var_int8(EE_gui_var):

    def __init__(self, ee_address : int, ee_byte_index : int, csv_colname : str, gui_label : str, init_value=0 ):
        EE_gui_var.__init__(self, ee_address, 'int8', csv_colname, gui_label)
        
        self._ee_byte_index=ee_byte_index
        self._init_value : int =init_value
        
        self._avar : IntVar  = cast(IntVar,None)
    
    # ---------------------------------------------------------------------------------------
    # subclasses should override this
    def csv_getLength(self) -> str:
        return '1'
    
    # ---------------------------------------------------------------------------------------
    # subclasses should override this
    def csv_getValue(self) -> str:
        return str(self._avar.get())

    # ---------------------------------------------------------------------------------------
    def csv_setValue(self, a_value : str ):
        self._avar.set(int(a_value))

    # --------------------------------------------------------------------------
    def update_EEblock_from_GUI(self, eeprom : GlobEeprom):
        ee_blk : EEblock =eeprom.get_block_from_address(self._ee_address)
        
        if not self._avar:
            raise Exception(self._csv_rowid+" MISSING gui component")
        
        avalue = self._avar.get()
        
        ee_blk.eeprom_pack_into('<b', self._ee_byte_index, avalue)
        
    # ---------------------------------------------------------------------------------------
    def eeprom_loadFrom(self, eeprom : GlobEeprom):
        ee_blk : EEblock =eeprom.get_block_from_address(self._ee_address)

        if not self._avar:
            raise Exception(self._csv_rowid+" MISSING gui component")

        touples=ee_blk.eeprom_unpack('<b',self._ee_byte_index)

        self._avar.set(touples[0])
        
    # --------------------------------------------------------------------------
    def gui_getComponent(self, parent_frame : ttk.Frame) -> ttk.Widget:
        if self._avar:
            raise Exception(self._csv_rowid+" is already into GUI")
        
        self._avar = IntVar(parent_frame, value=self._init_value)
        a_entry = TV_Entry(parent_frame, self._avar)
        a_entry['width'] = 2
        return a_entry



    
    
    
# ==================================================================================
# A boolean bit

class EE_var_bool_bit(EE_gui_var):

    # ------------------------------------------------------------------------------------
    def __init__(self, ee_address : int, ee_byte_index : int, csv_colname : str, gui_label : str, ee_bit_index : int, init_value=False ):
        EE_gui_var.__init__(self, ee_address, 'boolean', csv_colname, gui_label)
        
        self._ee_byte_index=ee_byte_index
        self._ee_bit_index=ee_bit_index
        
        self._init_value=init_value
        
        self._avar : BooleanVar = cast(BooleanVar,None)
    
    # ---------------------------------------------------------------------------------------
    # subclasses should override this
    def csv_getLength(self) -> str:
        return ''
    
    # ---------------------------------------------------------------------------------------
    # subclasses should override this
    def csv_getValue(self) -> str:
        return str(self._avar.get())

    # ---------------------------------------------------------------------------------------
    def csv_setValue(self, a_value : str ):
        self._avar.set(string_to_bool(a_value, False))


    # --------------------------------------------------------------------------
    def update_EEblock_from_GUI(self, eeprom : GlobEeprom):
        ee_blk : EEblock=eeprom.get_block_from_address(self._ee_address)
        
        if not self._avar:
            raise Exception(self._csv_rowid+" MISSING gui component")
        
        ee_blk.set_user_boolean(self._ee_byte_index, self._ee_bit_index, self._avar.get())

    # ---------------------------------------------------------------------------------------
    def eeprom_loadFrom(self, eeprom : GlobEeprom):
        ee_blk : EEblock =eeprom.get_block_from_address(self._ee_address)

        if not self._avar:
            raise Exception(self._csv_rowid+" MISSING gui component")

        self._avar.set(ee_blk.get_boolean(self._ee_byte_index,self._ee_bit_index))
        
    # --------------------------------------------------------------------------
    def gui_getComponent(self, parent_frame : ttk.Frame) -> ttk.Widget:
        if self._avar:
            raise Exception(self._csv_rowid+" is already into GUI")
        
        self._avar = BooleanVar(parent_frame, value=self._init_value)
        
        a_entry = ttk.Checkbutton(parent_frame, text=self.gui_label, onvalue=True, offvalue=False, variable=self._avar)
        
        return a_entry




# =========================================================================
# this is a string that limits the content to 16 bytes HEX string
class EE_var_string_hex(EE_gui_var):

    # ---------------------------------------------------------------------------------------
    # NOTE that ee_len is the length in EEPROM, the gui len is double !
    def __init__(self, ee_address : int, ee_byte_index : int, csv_colname : str, gui_label : str,  ee_len : int, init_value='' ):
        EE_gui_var.__init__(self, ee_address, 's_hex', csv_colname, gui_label)
        
        self._ee_byte_index=ee_byte_index
        self._ee_len=ee_len
        self._init_value=init_value
        
        self._avar : StringVar = cast(StringVar,None)

    # ---------------------------------------------------------------------------------------
    # overriding superclass
    def csv_getLength(self) -> str:
        return str(self._ee_len)
    
    # ---------------------------------------------------------------------------------------
    # overriding superclass
    def csv_getValue(self) -> str:
        return self._avar.get()

    # ---------------------------------------------------------------------------------------
    def csv_setValue(self, a_value : str ):
        self._avar.set(a_value)
        
    # -------------------------------------------------------------------------------
    # there is nothing useful passed in args...
    # the main issue is that I cannot get the variable back
    def _callbackWrite(self, _var_name, _var_type, _optionsl):
        
        source : str = self._avar.get()
        
        destination : str = ''
        
        hex_digits = set("ABCDEFabcdef0123456789")
        for achar in source:
            if achar in hex_digits:
                destination = destination + achar
                
        self._avar.set(destination)
    
    
    # --------------------------------------------------------------------------
    def update_EEblock_from_GUI(self, eeprom : GlobEeprom):
        if not self._avar:
            raise Exception(self._csv_rowid+" MISSING gui component")

        ee_blk : EEblock=eeprom.get_block_from_address(self._ee_address)

        try:
            a_bytes=bytes.fromhex(self._avar.get())

            ee_blk.set_user_bytearray(bytearray(a_bytes))
        except Exception as _exc :
            print('update_EEblock_from_GUI update_EEblock_from_GUI',_exc)

    # ---------------------------------------------------------------------------------------
    def eeprom_loadFrom(self, eeprom : GlobEeprom):
        ee_blk : EEblock =eeprom.get_block_from_address(self._ee_address)

        if not self._avar:
            raise Exception(self._csv_rowid+" MISSING gui component")

        try:
            a_bytes : bytearray = ee_blk.ee_bytearray 
            self._avar.set(a_bytes.hex())
        except Exception as _exc :
            print('update_EEblock_from_GUI eeprom_loadFrom ',_exc)
        
    # --------------------------------------------------------------------------
    def gui_getComponent(self, parent_frame : ttk.Frame) -> ttk.Widget:
        if self._avar:
            raise Exception(self._csv_rowid+" is already into GUI")
        
        self._avar = StringVar(parent_frame, value=self._init_value)
        self._avar.trace_variable('w', self._callbackWrite)
        
        a_entry = TV_Entry(parent_frame, self._avar)
        a_entry['width'] = self._ee_len * 2
        return a_entry








































