'''
/** 
 * 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

 
 This wraps the channel table of the QSk5
 It works with the eeprom, but it has enough peculiarities to have a specific implementation
 
 Quanto salva canale mette BW a 5khz
 Il salva radio config abbisogna della versione
 
 Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,RxDtcsCode,CrossMode,Mode,TStep,Skip,Power,Comment,URCALL,RPT1CALL,RPT2CALL,DVCODE
1,LPD Da,433.075000,,0.000000,,88.5,88.5,023,NN,023,Tone->Tone,FM,12.50,,1.5W,,,,,

  
''' 
# ------------------------------------------------------------------------- 
# There is only ONE poll, this is a GUI and if there is something to send it should be set to the poll
# ====================================================================
# Unfortunately, channels have a config that is spread in three pieces
# a channel info, start at 0x0000 up to 214 * 16
# a channel name, start at 0x0F50 up to 200 * 16
# channel attributes, start at 0x0D60 and take 214 bytes
# It is possibly best to read and write the whole info and then map it, instead of reading one channel at the time
# and, while I am at it I may as well read the full eeprom....
# -------------------------- NOTES
# See https://realpython.com/iterate-through-dictionary-python/
# they are global to make it ieasy to write....

from __future__ import annotations

import struct
from tkinter import ttk, StringVar, messagebox
import typing

from app_csv import CsvTable, Csv_Writer
from glob_eeprom import GlobEeprom, EEblock
from glob_fun import bits_setBooleanBit, bits_setBitsValue, bits_getBooleanBit, \
    bits_getBitsValue, string_to_bool, bool_to_string, \
    clipboard_copy, clipboard_paste
from glob_gui import JtkTableColdef, EE_events_listener, JtkPanelGrid, JtkCheckbox, \
    JtkCombo, frequency_1x_to_Mhz_str, TV_EntryFrequency_MHz_1x, JtkTable, \
    table_row_selected_background_color, JtkPanelPackLeft, JtkLabel, TV_Entry
import qpystat


# The # type: ignore is for mypy to stop complaining that it cannot find the correct tyep
qschcol_idx="qschcol_idx"
qschcol_chname="qschcol_chname"
qschcol_frequency="qschcol_frequency"
qschcol_offset="qschcol_offset"
qschcol_xCSS_RX_type="qschcol_xCSS_RX_type" # it can be either Digital OR analog, NOT both
qschcol_xCSS_TX_type="qschcol_xCSS_TX_type" # it can be either Digital OR analog, NOT both
qschcol_SCL_a="qschcol_SCL_a"               # scanlist 
qschcol_SCL_b="qschcol_SCL_b"              
qschcol_SCL_c="qschcol_SCL_c"              
qschcol_SCL_d="qschcol_SCL_d"              
qschcol_AMmodu="qschcol_AMmodu"         # Frequency use type
    
# -------------------------------------------------------
# Generic way to define a map from a column name to some properties        
qschcols_map = { 
    qschcol_idx         : JtkTableColdef(qschcol_idx        ,'Nr.',True, 40), 
    qschcol_chname      : JtkTableColdef(qschcol_chname     ,"Name",False,100),
    qschcol_frequency   : JtkTableColdef(qschcol_frequency  ,"Frequency",False, 100),
    qschcol_offset      : JtkTableColdef(qschcol_offset     ,"Offset",False, 90),
    qschcol_xCSS_RX_type : JtkTableColdef(qschcol_xCSS_RX_type,"Tone RX", False, 120),
    qschcol_xCSS_TX_type : JtkTableColdef(qschcol_xCSS_TX_type,"Tone TX",False,120),
    qschcol_SCL_a       : JtkTableColdef(qschcol_SCL_a     ,"Sc1",False,40),
    qschcol_SCL_b       : JtkTableColdef(qschcol_SCL_b     ,"Sc2",False,40),
    qschcol_SCL_c       : JtkTableColdef(qschcol_SCL_c     ,"Sc3",False,40),
    qschcol_SCL_d       : JtkTableColdef(qschcol_SCL_d     ,"Sc4",False,40),
    qschcol_AMmodu      : JtkTableColdef(qschcol_AMmodu    ,"AM mod",False,50),
    }


# -----------------------------------------------------------------------------------
# could go into the EEprom, but it is not a general method
# no optimization, I pick up the right block every time
# @return the byte holding the attributes for this qchan_idx
def qschan_getAttributeByte(eeprom : GlobEeprom , qchan_idx : int ) -> int:
    ee_block = eeprom.get_block_from_address(0xD60+qchan_idx)
    byte_offset = int(qchan_idx % EEblock.block_bytes_len)
    return ee_block.get_byte(byte_offset)

# -----------------------------------------------------------------------------------
# could go into the EEprom, but it is not a general method
def qschan_setAttributeByte(eeprom : GlobEeprom , qchan_idx : int , n_val : int ):
    ee_block : EEblock = eeprom.get_block_from_address(0xD60+qchan_idx)
    byte_offset = int(qchan_idx % EEblock.block_bytes_len)
    ee_block.set_user_byte(byte_offset, n_val)


qsch_combo_xCSS_types = [ 'OFF', 'CTCSS', 'DCSS', 'Rev.DCSS']              # cannot have spaces in options for xCSS
qsch_tx_power_level_str = ["x_LPD","p_PMR","Low","Middle","High"]
qsch_chirp_power_level_str = ["0.5W","2.5W","3W","5W"]
qsch_bandwidth_list_str = ["6.25kHz","12.5kHz","25kHz"]
qsch_offdir_str = ["OFF","+","-"]
qsch_compander_list_str = ["OFF","When TX","When RX",'TX + RX']
qsch_ptt_send_what_str = ["OFF","UP code","DOWN code"]
qsch_banduse_type_str = ['Null','Unlic','Ham','Lic']

# this is taken directly from the 'c' source and it is an array of values
qsch_step_f_1x = [ 250, 500, 625, 1000, 1250, 2500, 833, 1, 5, 10, 25, 50, 100, 125, 1500, 3000, 5000, 10000, 12500, 25000, 50000 ]
qsch_step_f_str = ["%u.%ukHz" % ( int(element/100), int(element%100) ) for element in qsch_step_f_1x]

# -----------------------------------------------------------------
# generic function to return theindex of a string in the given stringlist
# the default value is a value that is possibly the current filed value
# you can have "optional" parameters that do not mess with the current value
def qschan_index_from_string( string_list : typing.List[str], a_str : str, def_value : int ) -> int:
    
    if not a_str:
        return def_value
    
    for index,val in enumerate(string_list):
        if a_str == val:
            return index

    return def_value


qsch_CTCSS_options = [ 670  ,693  ,719  ,744  ,770  ,797  ,825  ,854  ,885  ,915, 948  ,974  ,1000 ,1035 ,1072 ,1109 ,1148 ,1188 ,1230 ,1273, 1318 ,1365 ,1413 ,1462 ,1514 ,1567 ,1598 ,1622 ,1655 ,1679, 1713 ,1738 ,1773 ,1799 ,1835 ,1862 ,1899 ,1928 ,1966 ,1995,    2035 ,2065 ,2107 ,2181 ,2257 ,2291 ,2336 ,2418 ,2503 ,2541 ]
# sprintf(DCS_string, "%u.%uHz", hz_1x / 10, hz_1x % 10);
qsch_CTCSS_options_str = [ "%u.%uHz" % ( int(element/10), int(element%10) ) for element in qsch_CTCSS_options]

qsch_DCSS_options = [ 0o23  ,0o25  ,0o26  ,0o31  ,0o32  ,0o36  ,0o43  ,0o47  ,0o51  ,0o53, 0o54  ,0o65  ,0o71  ,0o72  ,0o73  ,0o74  ,0o114 ,0o115 ,0o116 ,0o122, 0o125 ,0o131 ,0o132 ,0o134 ,0o143 ,0o145 ,0o152 ,0o155 ,0o156 ,0o162, 0o165 ,0o172 ,0o174 ,0o205 ,0o212 ,0o223 ,0o225 ,0o226 ,0o243 ,0o245, 0o246 ,0o251 ,0o252 ,0o255 ,0o261 ,0o263 ,0o265 ,0o266 ,0o271 ,0o274, 0o306 ,0o311 ,0o315 ,0o325 ,0o331 ,0o332 ,0o343 ,0o346 ,0o351 ,0o356, 0o364 ,0o365 ,0o371 ,0o411 ,0o412 ,0o413 ,0o423 ,0o431 ,0o432 ,0o445, 0o446 ,0o452 ,0o454 ,0o455 ,0o462 ,0o464 ,0o465 ,0o466 ,0o503 ,0o506, 0o516 ,0o523 ,0o526 ,0o532 ,0o546 ,0o565 ,0o606 ,0o612 ,0o624 ,0o627, 0o631 ,0o632 ,0o645 ,0o654 ,0o662 ,0o664 ,0o703 ,0o712 ,0o723 ,0o731, 0o732 ,0o734 ,0o743 ,0o754 ]
# sprintf(DCS_string, "D%03oN", DCS_Options[dcs_code]);
qsch_DCSS_options_str = ["D%03o" % (element) for element in qsch_DCSS_options]

qsch_DCSS_options_N_str = ["%sN" % (element) for element in qsch_DCSS_options_str]

# -----------------------------------------------------------------
# the incoming string is one produced by qschan_get_xCSS_option_string
# it should return the type and the value


def qschan_get_xCSS_from_string( a_string : str ):

    if not a_string:
        return 0,0
    
    a_split = a_string.split(' ')
    if len(a_split) < 2:
        return 0,0
    
    x_type = qschan_index_from_string(qsch_combo_xCSS_types, a_split[0],0)
    x_code = 0
    
    if x_type == 1:
        s_match = a_split[1]
        x_code = qschan_index_from_string(qsch_CTCSS_options_str,s_match,0)

    if x_type == 2:
        s_match = a_split[1]
        x_code = qschan_index_from_string(qsch_DCSS_options_str,s_match[:-1],0)
    
    if x_type == 3:
        s_match = a_split[1]
        x_code = qschan_index_from_string(qsch_DCSS_options_str,s_match[:-1],0)
    
    return x_type,x_code



# -----------------------------------------------------------------
# Apparently the code index starts from 0
# NOTE that the Normal is suffix N, the reverse is suffix R
# TODO the N and R are really redundant, since the type is already specified
def qschan_get_xCSS_option_string(x_type : int, x_index : int ) -> str:
    if x_type < 1 or x_index < 0:
        return ''
    
    stype_str = qsch_combo_xCSS_types[x_type]
    
    if x_type == 1:
        return stype_str+' '+qsch_CTCSS_options_str[x_index]

    if x_type == 2:
        return stype_str+' '+qsch_DCSS_options_str[x_index]+'N'
    
    if x_type == 3:
        return stype_str+' '+qsch_DCSS_options_str[x_index]+'I'
    
    return '?'+str(x_type)


    
# -------------------------------------------------------------------------
# the usual crap, because unsigned is missing
# note that v_max is an accepted value
def ensure_max ( v_in : int, v_max : int, v_default ) -> int:
    
    if v_in < 0:
        return v_default
    
    if v_in > v_max:
        return v_default

    return v_in

# ===================================================================
# It is safer to deal with objects instead of a generic map
class Qchannel_row:
    
    # ----------------------------------------------------------------
    # The table is made of "empty" rows that will be populated in a few runs
    # do NOT detach the object from the list, just clear it
    def __init__(self, index : int):
        self.qsch_idx_po=index+1   # the index to show to the user is Plus One
        self.clear()

    # ----------------------------------------------------------------
    # Clearring is NOT detaching from list  
    # ALSO this clears the values BUT do not mark the row user changed !          
    def clear(self):

        self.qsch_frequency_1x=0
        self.qsch_chname=''
        self.qsch_Fstep_code=0         # byte 6 of main chan table
        self.qsch_bandwidth_index=0
        self.qsch_txpowlvl_index=0
        
        self.qsch_offset_dir=0      # offset direction, first two bit of byte 3
        self.qsch_offset_freq_1x=0  # an ONLY positive number
        self.qsch_xCSS_RX_type=0    # OFF = 0,CTONE=1, DIGITAL=2, REVERSE_DIGITAL=3
        self.qsch_xCSS_RX_code=0

        self.qsch_xCSS_TX_type=0
        self.qsch_xCSS_TX_code=0
         
        self.qsch_PTT_send_what=0        # multiple choices

        self.qsch_SCL_a=False       
        self.qsch_SCL_b=False
        self.qsch_SCL_c=False      
        self.qsch_SCL_d=False      

        self.qsch_compander_index=0      # multiple choices
        self.qsch_freq_use_type=0         
        self.qsch_AM_modu_en=False

        self.qsch_dtmf_decode_en=False    
        self.qsch_freq_reverse_en=False   
        self.qsch_busy_chanlock_en=False  
        self.qsch_beepOnVoiceSta_en=False
        self.qsch_beepOnVoiceEnd_en=False
                
        self.show=True
        self.isUserModified=False
    
    # -----------------------------------------------------------------
    # must return the values as a list of strings in the same order as the header
    def csv_get_values(self) -> typing.List[str]:
        return self.clipb_get_values()


    csv_heading=['ch_nri',
                 'ch_name (str)',
                 'ch_freq_1x (int)',
                 'ch_step (str)',
                 'ch_bw (str)',
                 'ch_txpw (str)',
                 'ch_offdir (str)',
                 'ch_off_freq_1x (int)',
                 'ch_RX_xCSS (str)',
                 'ch_TX_xCSS (str)',
                 'ch_ptt_sw (int)',
                 'ch_scanlist_a (bool)',
                 'ch_scanlist_b (bool)',
                 'ch_compander (str)',
                 'ch_dtmf_decode  (str)',
                 'ch_rxtx_rev  (bool)',
                 'ch_busy_lock  (bool)',
                 'ch_beep_start  (bool)',
                 'ch_beep_end  (bool)',
                 'ch_F_use_type (str)',
                 'ch_scanlist_c (bool)',
                 'ch_scanlist_d (bool)',
                 'ch_AM_modu (bool)',
                 ]
    
    # ------------------------------------------------------------------
    # pretty much similar to the csv, I keep it different since I need a simpler way
    # @return a list of strings that you can easly concatenate
    def clipb_get_values(self) -> typing.List[str]:
        
        risul : typing.List[str] = []
        
        risul.append(str(self.qsch_idx_po))
        risul.append(str(self.qsch_chname))
        risul.append(str(self.qsch_frequency_1x))
        risul.append(qsch_step_f_str[self.qsch_Fstep_code])  # 3
        risul.append(qsch_bandwidth_list_str[self.qsch_bandwidth_index]) 
        risul.append(qsch_tx_power_level_str[self.qsch_txpowlvl_index])

        risul.append(qsch_offdir_str[self.qsch_offset_dir])   
        risul.append(str(self.qsch_offset_freq_1x))
        
        risul.append(qschan_get_xCSS_option_string(self.qsch_xCSS_RX_type, self.qsch_xCSS_RX_code))    
        risul.append(qschan_get_xCSS_option_string(self.qsch_xCSS_TX_type, self.qsch_xCSS_TX_code))    

        risul.append(qsch_ptt_send_what_str[self.qsch_PTT_send_what])  # 8
        risul.append(str(self.qsch_SCL_a))
        risul.append(str(self.qsch_SCL_b))
        risul.append(qsch_compander_list_str[self.qsch_compander_index])

        risul.append(str(self.qsch_dtmf_decode_en))
        risul.append(str(self.qsch_freq_reverse_en))
        risul.append(str(self.qsch_busy_chanlock_en))
        risul.append(str(self.qsch_beepOnVoiceSta_en))
        risul.append(str(self.qsch_beepOnVoiceEnd_en))
        risul.append(qsch_banduse_type_str[self.qsch_freq_use_type])

        risul.append(str(self.qsch_SCL_c))   # added jan 2025
        risul.append(str(self.qsch_SCL_d))   # added feb 2025
        
        risul.append(str(self.qsch_AM_modu_en))   # added jan 2026
        
        return risul

    # ------------------------------------------------------------------
    # this assumes tha values are in the SAME order and same format as the export !
    # AH, the snake is crap again
    # apparently, defining an iterator of strings is "complicated" (as a type), no, really, who could have guessed it
    # note that s_split is an iterator for strings and the FIRST item (the row index) has already been taken out
    def csv_parse_row(self, r_iter ):

        if not r_iter:
            return 

        self.isUserModified=True
        
        self.qsch_chname          = next(r_iter)
        self.qsch_frequency_1x    = int(next(r_iter))
        self.qsch_Fstep_code      = qschan_index_from_string(qsch_step_f_str, next(r_iter), self.qsch_Fstep_code)
        self.qsch_bandwidth_index = qschan_index_from_string(qsch_bandwidth_list_str, next(r_iter), self.qsch_bandwidth_index) 
        self.qsch_txpowlvl_index  = qschan_index_from_string(qsch_tx_power_level_str, next(r_iter), self.qsch_txpowlvl_index)
        self.qsch_offset_dir      = qschan_index_from_string(qsch_offdir_str, next(r_iter), self.qsch_offset_dir)
        self.qsch_offset_freq_1x  = int(next(r_iter))
        
        self.qsch_xCSS_RX_type, self.qsch_xCSS_RX_code = qschan_get_xCSS_from_string(next(r_iter))
        self.qsch_xCSS_TX_type, self.qsch_xCSS_TX_code = qschan_get_xCSS_from_string(next(r_iter))
        
        self.qsch_PTT_send_what  = qschan_index_from_string(qsch_ptt_send_what_str, next(r_iter), self.qsch_PTT_send_what)

        self.qsch_SCL_a=string_to_bool(next(r_iter), self.qsch_SCL_a)
        self.qsch_SCL_b=string_to_bool(next(r_iter), self.qsch_SCL_b)
        
        self.qsch_compander_index   = qschan_index_from_string(qsch_compander_list_str,next(r_iter), self.qsch_compander_index)
        self.qsch_dtmf_decode_en    = string_to_bool(next(r_iter), self.qsch_dtmf_decode_en)
        self.qsch_freq_reverse_en   = string_to_bool(next(r_iter), self.qsch_freq_reverse_en)
        self.qsch_busy_chanlock_en  = string_to_bool(next(r_iter), self.qsch_busy_chanlock_en)
        self.qsch_beepOnVoiceSta_en = string_to_bool(next(r_iter), self.qsch_beepOnVoiceSta_en)
        self.qsch_beepOnVoiceEnd_en = string_to_bool(next(r_iter), self.qsch_beepOnVoiceEnd_en)
        self.qsch_freq_use_type     = qschan_index_from_string(qsch_banduse_type_str, next(r_iter), self.qsch_freq_use_type)

        self.qsch_SCL_c=string_to_bool(next(r_iter), self.qsch_SCL_c)
        self.qsch_SCL_d=string_to_bool(next(r_iter), self.qsch_SCL_d)
        self.qsch_AM_modu_en=string_to_bool(next(r_iter), self.qsch_AM_modu_en)

        self.isUserModified=True
        
        
    # ------------------------------------------------------------------
    # pretty much similar to the csv, I keep it different since I need a simpler way
    # given a string, split it up and merge in
    # note that the row index is of course ignored !
    # @return an EMPTY string if the import is OK or a message to display
    def clipb_set_values(self, tab_row : str ) -> str:
        
        if not tab_row:
            return 'empty clipboard'

        s_split = iter(tab_row.split('\t')) 

        next(s_split)  # the first column is the row index
        
        self.csv_parse_row(s_split) 
        
        return ''

        
    # -----------------------------------------------------------------
    # because chan 0 starts at beginning of block, chan 1 the other eight bytes
    def _calc_name_start_offset(self, ch_idx : int ):
        if ch_idx & 1:
            return 8
        else:
            return 0
        
    # -----------------------------------------------------------------
    # somebody ask this channel to update the backing eeprom
    # it means updating the attributes, main block and the name
    # but ONLY if the data has changed
    def update_EEblock_from_GUI(self, eeprom : GlobEeprom ):
        
        if not self.isUserModified:
            return

        # once written to EEPROM I can consider it clear
        self.isUserModified=False

        qchan_idx=self.qsch_idx_po-1

        # the easy one, changing the channel Name....
        ee_nameblock = eeprom.get_block_from_address(0xF50+qchan_idx*8)
        ee_nameblock.set_user_string_new(self.qsch_chname, self._calc_name_start_offset(qchan_idx), 8)
        
        # now, more difficult, the attributes
        a_byte = 0  # let me start with cleared content, it also means chan valid
        a_byte = bits_setBooleanBit(a_byte ,7 ,self.qsch_SCL_a)
        a_byte = bits_setBooleanBit(a_byte ,6 ,self.qsch_SCL_b)
        a_byte = bits_setBitsValue (a_byte ,4 ,0b11, self.qsch_compander_index)
        a_byte = bits_setBooleanBit(a_byte ,3 ,self.qsch_SCL_c)
        a_byte = bits_setBooleanBit(a_byte ,2 ,self.qsch_SCL_d)
        qschan_setAttributeByte(eeprom, qchan_idx, a_byte)
        
        # the core channel config
        data_0 = self.qsch_xCSS_RX_code
        data_1 = self.qsch_xCSS_TX_code
        data_2 = self.qsch_xCSS_RX_type & 0x0F | (self.qsch_xCSS_TX_type << 4) & 0xF0
         
        data_3 = self.qsch_offset_dir & 0b11
        data_3 = bits_setBooleanBit(data_3,2,self.qsch_beepOnVoiceSta_en)
        data_3 = bits_setBooleanBit(data_3,3,self.qsch_beepOnVoiceEnd_en)
        data_3 = bits_setBooleanBit(data_3,4,self.qsch_AM_modu_en)
        
        # -------------------------------------------------------------------
        data_4 = bits_setBooleanBit(0     ,0        ,self.qsch_freq_reverse_en)
        data_4 = bits_setBitsValue(data_4 ,1, 0b111 ,self.qsch_txpowlvl_index)
        data_4 = bits_setBitsValue(data_4 ,4, 0b11  ,self.qsch_bandwidth_index)

        # -------------------------------------------------------------------
        data_5 = bits_setBooleanBit(0      ,0 ,self.qsch_dtmf_decode_en)
        data_5 = bits_setBitsValue(data_5  ,1 ,0b111, self.qsch_PTT_send_what)
        data_5 = bits_setBitsValue(data_5  ,4 ,0b111, self.qsch_freq_use_type)
        data_5 = bits_setBooleanBit(data_5 ,7 ,self.qsch_busy_chanlock_en)
        
        # -------------------------------------------------------------------
        data_6 = bits_setBitsValue(0, 0, 0b11111, self.qsch_Fstep_code)

        # -------------------------------------------------------------------
        data_7 = 0
        
        n_barray=struct.pack('<IIBBBBBBBB',self.qsch_frequency_1x,self.qsch_offset_freq_1x,data_0,data_1,data_2,data_3,data_4,data_5,data_6,data_7)
        
        ee_cblock = eeprom.get_block_from_address(qchan_idx*EEblock.block_bytes_len)
        ee_cblock.set_user_bytearray(bytearray(n_barray))
        
        
    # ------------------------------------------------------------------
    # set the channel vars from Eeprom attributes
    def _setAttributeFromEeprom(self, a_byte : int ):
        
        self.qsch_SCL_a = bits_getBooleanBit(a_byte, 7)
        self.qsch_SCL_b = bits_getBooleanBit(a_byte, 6)
        self.qsch_compander_index = bits_getBitsValue(a_byte, 4, 0b11)
        self.qsch_SCL_c = bits_getBooleanBit(a_byte, 3)
        self.qsch_SCL_d = bits_getBooleanBit(a_byte, 2)

    # ------------------------------------------------------------------
    # set the core data of the channel, taking value from the bytearray
    def _setCoreFromEeprom(self, byte_array : bytearray ):
        
        self.clear()
        
        if len(byte_array) < 16:
            return
        
        rx_f_1x,tx_off_1x = struct.unpack_from('<II',byte_array)
        
        if rx_f_1x == 0 or rx_f_1x == 0xFFFFFFFF:
            return
            
        self.qsch_frequency_1x = rx_f_1x
        self.qsch_offset_freq_1x = tx_off_1x

        # ---------------------
        data_0 = byte_array[8] & 0xFF
        self.qsch_xCSS_RX_code = data_0;
        
        # ---------------------
        data_1 = byte_array[9] & 0xFF
        self.qsch_xCSS_TX_code = data_1

        # ---------------------
        data_2 = byte_array[10] & 0xFF
        
        self.qsch_xCSS_RX_type = bits_getBitsValue(data_2 ,0 ,0x0F)
        self.qsch_xCSS_TX_type = bits_getBitsValue(data_2 ,4 ,0x0F)

        # ---------------------
        data_3 = byte_array[11] & 0xFF
        
        self.qsch_offset_dir = data_3 & 0b11
        self.qsch_beepOnVoiceSta_en = bits_getBooleanBit(data_3 , 2 )
        self.qsch_beepOnVoiceEnd_en = bits_getBooleanBit(data_3 , 3 )
        self.qsch_AM_modu_en        = bits_getBooleanBit(data_3 , 4 )
        
        # ---------------------
        data_4 = byte_array[12] & 0xFF
        
        self.qsch_freq_reverse_en  = bits_getBooleanBit(data_4 ,0 )
        self.qsch_txpowlvl_index   = ensure_max(bits_getBitsValue (data_4 ,1 ,0b111), len(qsch_tx_power_level_str)-1,0)
        self.qsch_bandwidth_index  = bits_getBitsValue (data_4 ,4 ,0b11)
        
        # ---------------------
        data_5 = byte_array[13] & 0xFF
        
        self.qsch_dtmf_decode_en   = bits_getBooleanBit(data_5 ,0)
        self.qsch_PTT_send_what    = bits_getBitsValue (data_5 ,1 ,0b111)
        self.qsch_freq_use_type    = bits_getBitsValue (data_5 ,4 ,0b111)
        self.qsch_busy_chanlock_en = bits_getBooleanBit(data_5 ,7)
        
        # ---------------------
        data_6 = byte_array[14] & 0xFF
        
        self.qsch_Fstep_code = ensure_max(data_6 & 0b11111, len(qsch_step_f_1x)-1, 0)
        
    # ------------------------------------------------------------------
    # set the Name the channel, taking value from the given block
    def _setNameFromEeprom(self, qchan_idx : int, ee_block : EEblock ):
        
        if self.qsch_frequency_1x == 0:
            return 

        a_name = ee_block.get_string_new(self._calc_name_start_offset(qchan_idx), 8)
        
        self.qsch_chname = a_name.strip() 
                    
    # -----------------------------------------------------------------
    # return the list of values to show into the table, the GUI table
    # NOTE that the order has to be consistent with the GUI definition
    def getValuesForTreeview(self):
        
        risul = (
            self.qsch_idx_po,
            self.qsch_chname,
            frequency_1x_to_Mhz_str(self.qsch_frequency_1x),
            self.qsch_offset_freq_1x,
            qschan_get_xCSS_option_string(self.qsch_xCSS_RX_type,self.qsch_xCSS_RX_code),
            qschan_get_xCSS_option_string(self.qsch_xCSS_TX_type,self.qsch_xCSS_TX_code),
            bool_to_string(self.qsch_SCL_a,'','1'),
            bool_to_string(self.qsch_SCL_b,'','1'),
            bool_to_string(self.qsch_SCL_c,'','1'),
            bool_to_string(self.qsch_SCL_d,'','1'),
            bool_to_string(self.qsch_AM_modu_en,'','1')
            )
            
        return risul         
    
    
# ========================================================================
# Let me try to have some decent OO wrapping in this shitty language
# The raw background data is always allocated
class Qchannel_list(CsvTable):

    qschans_max_len=214     # from 0 to 213, included 


    # -----------------------------------------------------------------
    # I need to preallocate dhte chans and never deallocate them
    def __init__(self):
        
        self.qchanlist : typing.List[Qchannel_row] = []
        
        for idx in range(self.qschans_max_len):
            self.qchanlist.append(Qchannel_row(idx))
        
    # -----------------------------------------------------------------------
    # if idx is out of range, return None
    def get_row_using_idx_po(self, idx_po : int ) -> Qchannel_row:
        
        if idx_po < 1:
            return typing.cast(Qchannel_row,None)
        
        if idx_po > self.qschans_max_len:
            return typing.cast(Qchannel_row,None)
        
        return self.qchanlist[idx_po-1]
        
    # ----------------------------------------------------------
    # override CSV
    def csv_tableName(self) -> str:
        return 'radio_rxtx_channels'
    
    # ----------------------------------------------------------
    # override CSV
    def csv_write_header(self, a_writer : Csv_Writer):
        a_writer.writerow(Qchannel_row.csv_heading)

    # ---------------------------------------------------------
    # in a different thread than swing, write the table to this csv writer
    def csv_write_table(self, a_writer : Csv_Writer):
        
        for row in self.qchanlist:
            v_list = row.csv_get_values()
            a_writer.writerow(v_list)

    # -------------------------------------------------------------
    # the difference is that clipboard will insert from a given point
    # for CSV, instead, I am given the row index            
    def csv_parse_row(self, row_values : typing.List[str] ):
        r_iter = iter(row_values)
        
        idx_po = int(next(r_iter))
        
        a_row : Qchannel_row = self.qchanlist[idx_po-1]
        
        a_row.csv_parse_row(r_iter)

        
    # ------------------------------------------------------------
    # You can call this one with a list of idx_po and a fun that will be applied for each element and the value to be applied
    # the fun will receive the row and the value and must set the proper "content"
    def edit_set_rowlist_value_using_fun(self, list_idx_po : typing.List[int], set_fun, set_value ):
            
        for idx_po in list_idx_po:
            a_row : Qchannel_row = self.qchanlist[idx_po-1]
            
            set_fun(a_row, set_value)
            a_row.isUserModified=True

                    
    # ------------------------------------------------------------
    # given the list of iid to pick up, return the list of rows that can be sent out
    def clipb_get_rowlist(self, list_idx_po : typing.List[int]):
        
        risul : typing.List[str] = []

        for idx_po in list_idx_po:
            a_row : Qchannel_row = self.qchanlist[idx_po-1]
            
            row_vals = a_row.clipb_get_values()
            
            row_s = '\t'.join(row_vals)
            
            risul.append(row_s)
            
        return risul    
        
    # ------------------------------------------------------------
    # start pasting clipboard content starting from the given row
    # @return possible import errors or empty string
    def clipb_set_rowlist(self, first_idx_po : int, source_split : typing.List[str]) -> str:
        if first_idx_po < 1 :
            return 'invalid first row='+str(first_idx_po)
        
        idx_int : int = first_idx_po-1
        
        errors = ''
        
        for tab_row in source_split:
            if idx_int >= self.qschans_max_len:
                return 'invalid row index '+str(idx_int)
            
            a_row : Qchannel_row = self.qchanlist[idx_int]
            
            idx_int += 1
            
            errmsg = a_row.clipb_set_values(tab_row)
        
            if errmsg:
                errors += errmsg
     
        return errors
    
    # ------------------------------------------------------------
    # given the list of iid_po clear them up
    def clear_rowlist(self, list_idx_po : typing.List[int]):
        
        for idx_po in list_idx_po:
            a_row : Qchannel_row = self.qchanlist[idx_po-1]
            
            a_row.clear()
            
            # need to be set, since I am actually clearing from clipboard
            a_row.isUserModified=True
     

# =====================================================================
# the table with the list of FM channels
# It should take the data from the global EEPROM repository
# It will be signalled when new EEPROM data is received
class Qchannels_gui (EE_events_listener):

    # ----------------------------------------------------------------
    def __init__(self, stat : qpystat.Qpystat, parent_frame : ttk.Frame ):
        self.stat = stat

        self.work_panel = ttk.Frame(parent_frame)
        
        self._qschan_list : Qchannel_list = Qchannel_list()
        self.stat.appcsv.add_to_tablesList(self._qschan_list)
        
        self.c_panel=self._newCenterPanel(self.work_panel)
        
        self.c_panel.pack(fill='both', expand=True)


    # ------------------------------------------------------------------------
    def tkraise(self):
        self.work_panel.tkraise()

    # ------------------------------------------------------------------------
    def _clearTable(self):

        for row in self._qschan_list.qchanlist:
            e_row : Qchannel_row = row
            
            if self._chlist_tview.row_exist(e_row.qsch_idx_po):
                self._chlist_tview.row_delete(e_row.qsch_idx_po) 
                
    # ------------------------------------------------------------------------
    def _println(self, msg : str ):
        self.stat.app_println(msg)
        
    # ------------------------------------------------------------------------
    def showInfo(self, a_message : str ):
        messagebox.showinfo("Channels", a_message, parent=self.work_panel, type='ok')    
        
    # ------------------------------------------------------------------------
    # Here I should first pick up the attributes, then the channels, and then channel names
    def updateGuiFromEeprom(self):

        self._clearTable()

        for qchan_idx in range(Qchannel_list.qschans_max_len):
            achannel : Qchannel_row = self._qschan_list.qchanlist[qchan_idx]

            ee_block = self.stat.globeeprom.get_block_from_address(qchan_idx*EEblock.block_bytes_len)
            achannel._setCoreFromEeprom(ee_block.ee_bytearray)    

            a_byte = qschan_getAttributeByte(self.stat.globeeprom, qchan_idx)
            achannel._setAttributeFromEeprom(a_byte)

            b_addr : int = 0xF50+qchan_idx*8
            ee_block = self.stat.globeeprom.get_block_from_address(b_addr)
            achannel._setNameFromEeprom(qchan_idx, ee_block)    
                
            achannel.isUserModified=False
        
        self.GUI_refresh()


    # -------------------------------------------------------------------
    # At any time it should be possible to filter the entries and show them
    # Note that this may be called at the beginning , when there is nothing in the list...
    def GUI_refresh(self ):    
        
        for row in self._qschan_list.qchanlist:
            e_row : Qchannel_row = row
            
            if e_row.show:
                self._jtable_ensureVisible(e_row)
            elif self._chlist_tview.row_exist(e_row.qsch_idx_po):
                self._chlist_tview.row_detach(e_row.qsch_idx_po)

    # -----------------------------------------------------------------
    # When this is called, it means the user pressed the write button
    def update_EEblock_from_GUI(self):

        self._println("qschannels.uupdate_EEblock_from_GUI CALL")

        for row in self._qschan_list.qchanlist:
            row.update_EEblock_from_GUI(self.stat.globeeprom)
            
        self.GUI_refresh()
            
        
    # ------------------------------------------------------------------------
    # Utility, to make code cleaner
    # NOTE that part of the magic works because the channel number is a unique key
    # This MUST be run from swing thread
    def _jtable_ensureVisible(self, a_row : Qchannel_row ):

        if self._chlist_tview.row_exist(a_row.qsch_idx_po):
            
            self._jtable_refresh_using_row(a_row)
                
            #self._chlist_tview.see_row(a_row.qsch_idx_po)
        else:
            v_values=a_row.getValuesForTreeview()
            self._chlist_tview.row_append(a_row.qsch_idx_po,v_values)

    # ------------------------------------------------------------------------------
    def _jtable_refresh_using_iid(self, iid_po : int ):
        a_row : Qchannel_row = self._qschan_list.get_row_using_idx_po(iid_po)
        self._jtable_refresh_using_row(a_row)

    # ------------------------------------------------------------------------------
    # this should update the swing table content for the given row
    def _jtable_refresh_using_row(self, a_row : Qchannel_row ):

        if not a_row:
            return

        #self._chlist_tview.focus_row(a_row.qsch_idx_po)
        
        updated_str=''
        
        if a_row.isUserModified:
            updated_str='Updated'
            
        self._chlist_tview.set_row(a_row.qsch_idx_po,a_row.getValuesForTreeview(),updated_str)   
        

    # -------------------------------------------------------------------------------
    # On center panel there is the table and scroll bar
    # NOTE: The layout of this is described by caller
    # NOTE: The style for this part is defined in the main gui, using style
    # https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/ttk-Treeview.html
    def _newCenterPanel(self, parent):
        apanel = ttk.Frame( parent) #, borderwidth=3, relief='ridge')
        
        #apanel.grid_rowconfigure(0, weight = 1)
        #apanel.grid_columnconfigure(0, weight = 1)
        
        self._chlist_tview = JtkTable(apanel, qschcols_map );
        self._chlist_tview.getComponent().pack(fill='both', expand=True)

        tk_tbl : ttk.Treeview = self._chlist_tview.getTable()
        
        tk_tbl.tag_configure('Updated',  background=table_row_selected_background_color)
        tk_tbl.bind('<<TreeviewSelect>>', self._jtable_row_selected)
        tk_tbl.bind('<Control-c>', self._jtable_ctrl_c)
        tk_tbl.bind('<Control-x>', self._jtable_ctrl_x)
        tk_tbl.bind('<Control-v>', self._jtable_ctrl_v)
        tk_tbl.bind('<Delete>', self._jtable_delete)
        
        # --------------------------------------------------- now, the editing panel
        self._editingPanel = Qchannel_edit_gui(self ,apanel)
        self._editingPanel.getComponent().pack(fill='x')
        
        return apanel

    # -------------------------------------------------------------------
    # ok, use the _po version, I may need it
    def _get_selected_idx_po(self) -> list[int]:

        a_sel = self._chlist_tview.getSelected_iid()
        
        if not a_sel:
            self._println('_jtable_ctrl_x: NO rows selected')
            return [] 

        # this is an experiment, basically, shugar, useless code.... 
        # the pythonic way to get diabetes
        seleted_idx_po : list[int] = [int(iid_po) for iid_po in a_sel]
        
        return seleted_idx_po


    # -------------------------------------------------------------------
    # standard delete 
    def _jtable_delete(self, sel_coord):

        list_idx_po : list[int] = self._get_selected_idx_po()

        if not list_idx_po:
            return
        
        # now requesta a series of rows clear
        self._qschan_list.clear_rowlist(list_idx_po)

        # here, I should update the entries that are changed !
        for idx_po in list_idx_po:
            self._jtable_refresh_using_iid(idx_po)


    # -------------------------------------------------------------------

    def _jtable_copy_to_clipboard(self, list_idx_po : list[int]):

        if not list_idx_po:
            return

        csv_rows : typing.List[str] = self._qschan_list.clipb_get_rowlist(list_idx_po)

        csv_string = '\n'.join(csv_rows)

        # now, I should ask to the list to return the whole rows...
        if not clipboard_copy(csv_string):
            self._println("pyperclip NOT available")


    # -------------------------------------------------------------------
    # should pick up all the selected rows and move them to "clipboard"
    # the parameters received are not interesting....
    # apparently pyperclip.is_available() declare it not available even if it is available....
    # <KeyPress event send_event=True state=Control|Mod1 keysym=x keycode=88 char='\x18' x=534 y=127>
    def _jtable_ctrl_c(self, sel_coord):
        #self._println(str(sel_coord))
        
        list_idx_po : list[int] = self._get_selected_idx_po()

        self._jtable_copy_to_clipboard(list_idx_po)
        
        
    # -------------------------------------------------------------------
    # apply the given fun and the given value to the list of selected rows
    def _jtable_selected_set_value_using_fun(self, set_fun, set_value ):        

        list_idx_po : list[int] = self._get_selected_idx_po()

        if not list_idx_po:
            return

        self._qschan_list.edit_set_rowlist_value_using_fun(list_idx_po, set_fun, set_value)
        
        self.GUI_refresh()
        
        
    # -------------------------------------------------------------------
    # it is a control c followed by a clear of the row 

    def _jtable_ctrl_x(self, sel_coord):

        list_idx_po : list[int] = self._get_selected_idx_po()

        if not list_idx_po:
            return
        
        self._jtable_copy_to_clipboard(list_idx_po)
        
        # now requesta a series of rows clear
        self._qschan_list.clear_rowlist(list_idx_po)

        # here, I should update the entries that are changed !
        for idx_po in list_idx_po:
            self._jtable_refresh_using_iid(idx_po)
        

    # -------------------------------------------------------------------
    # should paste the clipboard starting from the first selected row
    # apparently pyperclip.is_available() declare it not available even if it is available....
    # once pasted, show the first row that was selected !
    def _jtable_ctrl_v(self, sel_coord):
        
        # I just need to get the first one !
        a_sel = self._chlist_tview.getSelected_iid()
        
        if not a_sel:
            self._println('_jtable_ctrl_v: you MUST select the first row where to paste clipboard')
            return 
            
        first_idx_po=int(a_sel[0])
            
        clip_source : str = clipboard_paste()   
        
        if not clip_source:
            self._println('_jtable_ctrl_v: NO clipboard content to parse')

        self._println('_jtable_ctrl_v: '+clip_source)
        
        source_split = clip_source.split('\n')       
        
        errmsg=self._qschan_list.clipb_set_rowlist(first_idx_po, source_split)
        
        if errmsg:
            self._println('_jtable_ctrl_v '+str(errmsg))
            
        self.GUI_refresh()
        
        # show the line where insert should have occourred
        self._jtable_ensureVisible(self._qschan_list.qchanlist[first_idx_po-1])
        
    
    # -------------------------------------------------------------------
    # Called when a row is selected in the tree
    # note that you NEED selectmode='browse' to have a single selection
    # IF multiple rows can be selected with option selectmode='extended' you get a list of selected items that can be not in sequence
    # you can use SHIFT or CTRRL + click to add or remove selected elements
    def _jtable_row_selected(self, sel_coord):
        
        a_sel = self._chlist_tview.getSelected_iid()
        
        if not a_sel:
            return 
        
        #self._println(str(a_sel))
        
        row_idx_po : int = int(a_sel[0])
        
        sel_item = self._qschan_list.get_row_using_idx_po(row_idx_po)

        self._editingPanel.showThisRow(sel_item)
            
            
# ==============================================================================
# it is kind of easier to have a separate class            
class Qchannel_edit_gui():



    # ------------------------------------------------------------
    # I make this a frame for ease of use, it should a bit hidden...    
    def __init__(self, parent_class : Qchannels_gui , parent_panel : ttk.Frame ):
        
        self.parent_class = parent_class

        # need an empty row, not really, but for xCss....
        self.sel_item : Qchannel_row = Qchannel_row(0)

        self._work_panel = self._newGridPanel(parent_panel)

    # ------------------------------------------------------------
    def getComponent(self) -> ttk.Frame:
        return self._work_panel

    # ------------------------------------------------------------
    def _println(self, msg : str ):
        self.parent_class._println(msg)

    # -------------------------------------------------------------------------------
    def _on_RX_css_type_change(self, _v1, _v2, _v3):
        c_type=self._edit_xCSS_RX_type.current()
        
        #self._println("RX xCSS change type "+str(c_type))
        
        self._showCorrect_xCSSCombo(self._edit_xCSS_RX_code, c_type, self.sel_item.qsch_xCSS_RX_code )

    # -------------------------------------------------------------------------------
    def _on_TX_css_type_change(self, v1, v2, v3):
        c_type=self._edit_xCSS_TX_type.current()
        
        #self._println("TX xCSS change type "+str(c_type))
        
        self._showCorrect_xCSSCombo(self._edit_xCSS_TX_code, c_type, self.sel_item.qsch_xCSS_TX_code )

    # ------------------------------------------------------------------------------
    # element sof the gui can call this one to apply the given set fun to all selected rows and apply the given value
    def _on_edit_value_changed(self, set_fun, set_value):
        
        self.parent_class._jtable_selected_set_value_using_fun(set_fun, set_value)


    # -------------------------------------------------------------------------------
    def _newGridPanel(self, parent_panel) -> JtkPanelGrid:
        gridpanel = JtkPanelGrid(parent_panel, borderwidth=1, relief='solid', padding=3)

        self._edit_chan_name = TV_Entry_onChanged(gridpanel, StringVar(gridpanel,value=''), width=10, on_focus_out_fun=self._onChangedName)

        self._edit_chan_freq = TV_EntryFrequency_MHz_1x(gridpanel,on_focus_out_fun=self._onChanged_chan_freq)

        self._edit_offset_dir = JtkCombo(gridpanel, values=qsch_offdir_str, on_selected_fun=self._onChanged_offset_dir)
        self._edit_offset     = TV_EntryFrequency_MHz_1x(gridpanel   ,on_focus_out_fun=self._onChanged_chan_offset)

        self._edit_xCSS_RX_type = JtkCombo(gridpanel, values=qsch_combo_xCSS_types, on_selected_fun=self._onChanged_qsch_xCSS_RX_type)
        self._edit_xCSS_RX_type.setWriteCallback(self._on_RX_css_type_change)
        self._edit_xCSS_RX_code = JtkCombo(gridpanel, on_selected_fun=self._onChanged_qsch_xCSS_RX_code) 

        self._edit_xCSS_TX_type = JtkCombo(gridpanel, values=qsch_combo_xCSS_types, on_selected_fun=self._onChanged_qsch_xCSS_TX_type)
        self._edit_xCSS_TX_type.setWriteCallback(self._on_TX_css_type_change)
        self._edit_xCSS_TX_code = JtkCombo(gridpanel, on_selected_fun=self._onChanged_qsch_xCSS_TX_code)

        self._edit_PTT_send_what = JtkCombo(gridpanel, values=qsch_ptt_send_what_str, on_selected_fun=self._onChanged_qsch_PTT_send_what)
        self._edit_txpow_level   = JtkCombo(gridpanel, values=qsch_tx_power_level_str, on_selected_fun=self._onChanged_qsch_txpowlvl_index)
        self._edit_compander     = JtkCombo(gridpanel, values=qsch_compander_list_str, on_selected_fun=self._onChanged_qsch_compander_index)
        self._edit_bandwidth     = JtkCombo(gridpanel, values=qsch_bandwidth_list_str, on_selected_fun=self._onChanged_qsch_bandwidth_index)
        self._edit_freq_step     = JtkCombo(gridpanel, values=qsch_step_f_str, on_selected_fun=self._onChanged_qsch_Fstep_code)

        self._edit_en_DTMF_decode = JtkCheckbox(gridpanel,"DTMF decode", on_changed_fun=self._onChanged_qsch_dtmf_decode_en)
        self._edit_freq_reverse   = JtkCheckbox(gridpanel,"Frq. Reverse", on_changed_fun=self._onChanged_qsch_freq_reverse_en)
        self._edit_busy_chanlock  = JtkCheckbox(gridpanel,"TX Busy Lock", on_changed_fun=self._onChanged_qsch_busy_chanlock_en)
        self._edit_beepOnVStart   = JtkCheckbox(gridpanel,"Beep Voice Start", on_changed_fun=self._onChanged_qsch_beepOnVoiceSta_en)
        self._edit_beepOnVEnd     = JtkCheckbox(gridpanel,"Beep Voice End", on_changed_fun=self._onChanged_qsch_beepOnVoiceEnd_en)
        self._edit_AM_modu        = JtkCheckbox(gridpanel,"AM modulation", on_changed_fun=self._onChanged_qsch_AM_modu_en)

        # ------------------------------------------------------------
        gridpanel.addItem(JtkLabel(gridpanel,"Name"))
        gridpanel.addItem(self._edit_chan_name)
        gridpanel.addItem(JtkLabel(gridpanel,"(Max 8 characters)"))

        # ------------------------------------------------------------
        gridpanel.nextRow()
        gridpanel.addItem(JtkLabel(gridpanel,"Frequency"))
        gridpanel.addItem(self._edit_chan_freq)

        # ------------------------------------------------------------
        gridpanel.nextRow()
        gridpanel.addItem(JtkLabel(gridpanel,"TX power "))
        gridpanel.addItem(self._edit_txpow_level)
        gridpanel.addItem(self._newFusetypePanel(gridpanel))
        gridpanel.addItem(self._edit_beepOnVStart)

        # ------------------------------------------------------------
        gridpanel.nextRow()
        gridpanel.addItem(JtkLabel(gridpanel,"xCSS RX"))
        gridpanel.addItem(self._edit_xCSS_RX_type)
        gridpanel.addItem(self._edit_xCSS_RX_code)
        gridpanel.addItem(self._edit_beepOnVEnd)

        # ------------------------------------------------------------
        gridpanel.nextRow()
        gridpanel.addItem(JtkLabel(gridpanel,"xCSS TX"))
        gridpanel.addItem(self._edit_xCSS_TX_type)
        gridpanel.addItem(self._edit_xCSS_TX_code)
        gridpanel.addItem(self._edit_freq_reverse)

        # ------------------------------------------------------------
        gridpanel.nextRow()
        gridpanel.addItem(JtkLabel(gridpanel,"Offset "))
        gridpanel.addItem(self._edit_offset_dir)
        gridpanel.addItem(self._edit_offset)
        gridpanel.addItem(self._edit_busy_chanlock)

        # ------------------------------------------------------------
        gridpanel.nextRow()
        gridpanel.addItem(JtkLabel(gridpanel,"F Step"))
        gridpanel.addItem(self._edit_freq_step)
        gridpanel.nextColumn()
        gridpanel.addItem(self._edit_en_DTMF_decode)

        # ------------------------------------------------------------
        gridpanel.nextRow()
        gridpanel.addItem(JtkLabel(gridpanel,"Bandwidth"))
        gridpanel.addItem(self._edit_bandwidth)
        gridpanel.nextColumn()
        gridpanel.addItem(self._edit_AM_modu)

        # ------------------------------------------------------------
        gridpanel.nextRow()
        gridpanel.addItem(JtkLabel(gridpanel,"PTT Send"))
        gridpanel.addItem(self._edit_PTT_send_what)

        # ------------------------------------------------------------
        gridpanel.nextRow()
        gridpanel.addItem(JtkLabel(gridpanel,"Compander"))
        gridpanel.addItem(self._edit_compander)
        gridpanel.addItem(self._newCheckboxPanel(gridpanel),columnspan=2, sticky='e')


        return gridpanel


 
    # -------------------------------------------------------------------------------
    def _newCheckboxPanel(self, parent_panel) -> ttk.Frame:
        apanel = JtkPanelPackLeft(parent_panel)
        
        apanel.p_padx = 4
        
        self._edit_en_SCL_a = JtkCheckbox(apanel,"Scanlist 1",on_changed_fun=self._onChanged_qsch_SCL_a)
        apanel.addItem(self._edit_en_SCL_a)
        
        self._edit_en_SCL_b = JtkCheckbox(apanel,"Scanlist 2",on_changed_fun=self._onChanged_qsch_SCL_b)
        apanel.addItem(self._edit_en_SCL_b)

        self._edit_en_SCL_c = JtkCheckbox(apanel,"Scanlist 3",on_changed_fun=self._onChanged_qsch_SCL_c)
        apanel.addItem(self._edit_en_SCL_c)

        self._edit_en_SCL_d = JtkCheckbox(apanel,"Scanlist 4",on_changed_fun=self._onChanged_qsch_SCL_d)
        apanel.addItem(self._edit_en_SCL_d)

        return apanel


    # -----------------------------------------------------------------------------
    # this will be called, eventually, with the row and the value to set
    def _onChangedName(self, i_value ):
    
        def _on_changed_row_name(arow : Qchannel_row, i_value ):
            arow.qsch_chname=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_on_changed_row_name, i_value)
    
    # -----------------------------------------------------------------------------
    # this will be called, eventually, with the row and the value to set
    def _onChanged_chan_freq(self, value ):
    
        def _on_changed_F_chanel(arow : Qchannel_row, i_value ):
            arow.qsch_frequency_1x=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_on_changed_F_chanel, value)

    # -----------------------------------------------------------------------------
    # this will be called, eventually, with the row and the value to set
    def _onChanged_offset_dir(self, v_value ):
    
        def _row_change_chan_offset(arow : Qchannel_row, i_value ):
            arow.qsch_offset_dir=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_chan_offset, v_value)
    
    # -----------------------------------------------------------------------------
    def _onChanged_chan_offset(self, v_value ):
    
        def _row_change_chan_offset(arow : Qchannel_row, i_value ):
            arow.qsch_offset_freq_1x=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_chan_offset, v_value)

    # -----------------------------------------------------------------------------
    def _onChanged_qsch_xCSS_RX_type(self, v_value ):
    
        def _row_change_qsch_xCSS_RX_type(arow : Qchannel_row, i_value ):
            arow.qsch_xCSS_RX_type=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_xCSS_RX_type, v_value)

    # -----------------------------------------------------------------------------
    def _onChanged_qsch_xCSS_RX_code(self, v_value ):
    
        def _row_change_qsch_xCSS_RX_code(arow : Qchannel_row, i_value ):
            arow.qsch_xCSS_RX_code=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_xCSS_RX_code, v_value)

    # -----------------------------------------------------------------------------
    def _onChanged_qsch_xCSS_TX_type(self, v_value ):
    
        def _row_change_qsch_xCSS_TX_type(arow : Qchannel_row, i_value ):
            arow.qsch_xCSS_TX_type=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_xCSS_TX_type, v_value)

    # -----------------------------------------------------------------------------
    def _onChanged_qsch_xCSS_TX_code(self, v_value ):
    
        def _row_change_qsch_xCSS_TX_code(arow : Qchannel_row, i_value ):
            arow.qsch_xCSS_TX_code=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_xCSS_TX_code, v_value)

    # -----------------------------------------------------------------------------
    def _onChanged_qsch_PTT_send_what(self, v_value ):
    
        def _row_change_qsch_PTT_send_what(arow : Qchannel_row, i_value ):
            arow.qsch_PTT_send_what=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_PTT_send_what, v_value)

    # -----------------------------------------------------------------------------
    def _onChanged_qsch_txpowlvl_index(self, v_value ):
    
        def _row_change_qsch_txpowlvl_index(arow : Qchannel_row, i_value ):
            arow.qsch_txpowlvl_index=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_txpowlvl_index, v_value)

    # -----------------------------------------------------------------------------
    def _onChanged_qsch_compander_index(self, v_value ):
    
        def _row_change_qsch_compander_index(arow : Qchannel_row, i_value ):
            arow.qsch_compander_index=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_compander_index, v_value)

    # -----------------------------------------------------------------------------
    def _onChanged_qsch_bandwidth_index(self, v_value ):
    
        def _row_change_qsch_bandwidth_index(arow : Qchannel_row, i_value ):
            arow.qsch_bandwidth_index=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_bandwidth_index, v_value)

    # -----------------------------------------------------------------------------
    def _onChanged_qsch_Fstep_code(self, v_value ):
    
        def _row_change_qsch_Fstep_code(arow : Qchannel_row, i_value ):
            arow.qsch_Fstep_code=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_Fstep_code, v_value)

    # -----------------------------------------------------------------------------
    def _onChanged_qsch_dtmf_decode_en(self, v_value ):
    
        def _row_change_qsch_dtmf_decode_en(arow : Qchannel_row, i_value ):
            arow.qsch_dtmf_decode_en=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_dtmf_decode_en, v_value)

    # -----------------------------------------------------------------------------
    def _onChanged_qsch_freq_reverse_en(self, v_value ):
    
        def _row_change_qsch_freq_reverse_en(arow : Qchannel_row, i_value ):
            arow.qsch_freq_reverse_en=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_freq_reverse_en, v_value)

    # -----------------------------------------------------------------------------
    def _onChanged_qsch_busy_chanlock_en(self, v_value ):
    
        def _row_change_qsch_busy_chanlock_en(arow : Qchannel_row, i_value ):
            arow.qsch_busy_chanlock_en=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_busy_chanlock_en, v_value)

    # -----------------------------------------------------------------------------
    def _onChanged_qsch_beepOnVoiceSta_en(self, v_value ):
    
        def _row_change_qsch_beepOnVoiceSta_en(arow : Qchannel_row, i_value ):
            arow.qsch_beepOnVoiceSta_en=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_beepOnVoiceSta_en, v_value)
         
    # -----------------------------------------------------------------------------
    def _onChanged_qsch_beepOnVoiceEnd_en(self, v_value ):
    
        def _row_change_qsch_beepOnVoiceEnd_en(arow : Qchannel_row, i_value ):
            arow.qsch_beepOnVoiceEnd_en=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_beepOnVoiceEnd_en, v_value)

    # -----------------------------------------------------------------------------
    def _onChanged_qsch_AM_modu_en(self, v_value ):
    
        def _row_change_qsch_AM_modu_en(arow : Qchannel_row, i_value ):
            arow.qsch_AM_modu_en=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_AM_modu_en, v_value)

    # -----------------------------------------------------------------------------
    def _onChanged_qsch_freq_use_type(self, v_value ):
    
        def _row_change_qsch_freq_use_type(arow : Qchannel_row, i_value ):
            arow.qsch_freq_use_type=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_freq_use_type, v_value)

    # -----------------------------------------------------------------------------
    def _onChanged_qsch_SCL_a(self, v_value ):
    
        def _row_change_qsch_SCL_a(arow : Qchannel_row, i_value ):
            arow.qsch_SCL_a=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_SCL_a, v_value)

    # -----------------------------------------------------------------------------
    def _onChanged_qsch_SCL_b(self, v_value ):
    
        def _row_change_qsch_SCL_b(arow : Qchannel_row, i_value ):
            arow.qsch_SCL_b=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_SCL_b, v_value)

    # -----------------------------------------------------------------------------
    def _onChanged_qsch_SCL_c(self, v_value ):
    
        def _row_change_qsch_SCL_c(arow : Qchannel_row, i_value ):
            arow.qsch_SCL_c=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_SCL_c, v_value)
         
    # -----------------------------------------------------------------------------
    def _onChanged_qsch_SCL_d(self, v_value ):
    
        def _row_change_qsch_SCL_d(arow : Qchannel_row, i_value ):
            arow.qsch_SCL_d=i_value
            
        self.parent_class._jtable_selected_set_value_using_fun(_row_change_qsch_SCL_d, v_value)

    # ------------------------------------------------------------
    def _newFusetypePanel(self, parent_panel ) -> ttk.Frame:
        a_panel = JtkPanelPackLeft(parent_panel)

        a_panel.addItem(JtkLabel(a_panel,"Frq use "))
        
        self._edit_freq_usetype  = JtkCombo(a_panel, values=qsch_banduse_type_str, on_selected_fun=self._onChanged_qsch_freq_use_type)
        a_panel.addItem(self._edit_freq_usetype)
   
        return a_panel


    # -------------------------------------------------------------------------------
    # TODO there is all the issue on the "reverse digital" to clear up
    def _showCorrect_xCSSCombo(self, c_box : JtkCombo, c_type : int, c_code : int):
        if not c_type:
            c_box.grid_remove()
            return

        c_box.grid()
        
        if c_type == 1:
            c_box['values'] = qsch_CTCSS_options_str
        else:
            c_box['values'] = qsch_DCSS_options_N_str
            
        if c_code >= 0:
            # if you pass a value to current it will SET the current INDEX
            c_box.current(c_code)

    # -------------------------------------------------------------------------------
    # Parent request to show this row
    def showThisRow(self, sel_item : Qchannel_row ):
        if not sel_item:
            self.parent_class.showInfo("showThisRow: Please select a row")
            return;
        
        self.sel_item=sel_item  # will be used in the save
        
        self._edit_chan_name.set_value(sel_item.qsch_chname)

        self._edit_chan_freq.set_value(sel_item.qsch_frequency_1x)
        
        self._edit_offset_dir.current(sel_item.qsch_offset_dir)
        self._edit_offset.set_value(sel_item.qsch_offset_freq_1x)
        
        self._edit_en_SCL_a.setChecked(sel_item.qsch_SCL_a)
        self._edit_en_SCL_b.setChecked(sel_item.qsch_SCL_b)
        self._edit_en_SCL_c.setChecked(sel_item.qsch_SCL_c)
        self._edit_en_SCL_d.setChecked(sel_item.qsch_SCL_d)
        
        self._edit_en_DTMF_decode.setChecked(sel_item.qsch_dtmf_decode_en)
        self._edit_freq_reverse.setChecked(sel_item.qsch_freq_reverse_en)
        self._edit_busy_chanlock.setChecked(sel_item.qsch_busy_chanlock_en)

        self._edit_beepOnVStart.setChecked(sel_item.qsch_beepOnVoiceSta_en)
        self._edit_beepOnVEnd.setChecked(sel_item.qsch_beepOnVoiceEnd_en)
        
        self._edit_xCSS_RX_type.current(sel_item.qsch_xCSS_RX_type)
        self._showCorrect_xCSSCombo(self._edit_xCSS_RX_code, sel_item.qsch_xCSS_RX_type, sel_item.qsch_xCSS_RX_code )
        
        self._edit_xCSS_TX_type.current(sel_item.qsch_xCSS_TX_type)
        self._showCorrect_xCSSCombo(self._edit_xCSS_TX_code, sel_item.qsch_xCSS_TX_type, sel_item.qsch_xCSS_TX_code )
       
        self._edit_PTT_send_what.current(sel_item.qsch_PTT_send_what)
        self._edit_txpow_level.current(sel_item.qsch_txpowlvl_index)
        self._edit_compander.current(sel_item.qsch_compander_index)
        self._edit_bandwidth.current(sel_item.qsch_bandwidth_index)
        self._edit_freq_step.current(sel_item.qsch_Fstep_code)
        self._edit_freq_usetype.current(sel_item.qsch_freq_use_type)

        self._edit_AM_modu.setChecked(sel_item.qsch_AM_modu_en)

                
# ==================================================================================
# An entry bound to a textvariable that will call the given fun when updated
# so, the called fun can be different for each "field", so I can set different values
# so, basically, this should iterate on all selected rows and call the given fun

class TV_Entry_onChanged(TV_Entry):
    # -------------------------------------------------------------------------------
    # @parent the parent panel where to bind this fentry
    # @param the text variable to bind to
    def __init__(self, parent_panel, edit_var, **varargs ):

        self._edit_ttk_var = edit_var
        
        # no focus out fun, by default
        self._on_focus_out_fun=None
        
        if 'on_focus_out_fun' in varargs:
            self.set_on_focus_out_fun(varargs['on_focus_out_fun'])
            varargs.pop('on_focus_out_fun')

        TV_Entry.__init__(self, parent_panel, self._edit_ttk_var, **varargs)

        self.bind("<FocusOut>", self._entry_focus_out)

        # need to know when something has changed
        self._edit_ttk_var.trace("w", self._textvar_changed)
        
        # I need a separate var since changes may come from User OR initial value set
        self._entry_user_changed=False

    # ----------------------------------------------------------------------------
    # this is the proper way to set a value for this component, from the system
    def set_value(self, i_value):
        self._edit_ttk_var.set(i_value)
        self._entry_user_changed=False 
        
    # ----------------------------------------------------------------------------
    def get_value(self):
        return self._edit_ttk_var.get()

    # ----------------------------------------------------------------------------
    def set_user_changed (self, is_changed : bool ):
        self._entry_user_changed=is_changed

    # ----------------------------------------------------------------------------
    # If needed you can set a focus out fun here
    def set_on_focus_out_fun(self, on_focus_out_fun ):
        self._on_focus_out_fun = on_focus_out_fun

    # ----------------------------------------------------------------------------
    # On focus out I should call the appropriate fun
    # there is no need to set the edited to False, it will be set to false when set_value is called
    def _entry_focus_out(self, event):
        #print(event,' ',self._entry_user_changed)
        
        if not self._entry_user_changed:
            return
    
        if not self._on_focus_out_fun:
            return 
            
        self._on_focus_out_fun(self.get_value())
            
    
    # ----------------------------------------------------------------------------
    # I just need to remember if anything changed
    def _textvar_changed(self, p1, p2, p3):
        #print(p1,' ',p2,' ',p3)
        self._entry_user_changed=True
        