/* * Copyright (C) 2019-2025 Roger Clark, VK3KYY / G4KYF * Daniel Caujolle-Bert, F1RMB * * * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the distribution. * * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * 4. Use of this source code or binary releases for commercial purposes is strictly forbidden. This includes, without limitation, * incorporation in a commercial product or incorporation into a product or project which allows commercial use. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * */ #include "user_interface/uiGlobals.h" #include "user_interface/menuSystem.h" #include "user_interface/uiUtilities.h" #include "user_interface/uiLocalisation.h" typedef enum { MENU_CONTACT_LIST_DISPLAY = 0, MENU_CONTACT_LIST_CONFIRM, MENU_CONTACT_LIST_DELETED, MENU_CONTACT_LIST_TG_IN_RXGROUP } contactListState_t; typedef enum { MENU_CONTACT_LIST_CONTACT_DIGITAL = 0, MENU_CONTACT_LIST_CONTACT_DTMF } contactListContactType_t; static void updateScreen(bool isFirstRun); static void handleEvent(uiEvent_t *ev); static CodeplugContact_t contact; static CodeplugDTMFContact_t dtmfContact; static contactListContactType_t contactListType = MENU_CONTACT_LIST_CONTACT_DIGITAL; static uint32_t contactCallType = CONTACT_CALLTYPE_TG; static contactListState_t contactListDisplayState = MENU_CONTACT_LIST_DISPLAY; static contactListState_t contactListOverrideState = MENU_CONTACT_LIST_DISPLAY; static int menuContactListTimeout = 0; // Action result screen autohide timeout (or it will instantly disappear if RED or GREEN is pressed) static menuStatus_t menuContactListExitCode = MENU_STATUS_SUCCESS; static menuStatus_t menuContactListSubMenuExitCode = MENU_STATUS_SUCCESS; static const char *calltypeVoices[3] = { NULL, NULL, NULL }; // Pseudo call-type for the digital list: show every contact (groups, private // calls and all-calls) together. This is the default; HASH still cycles the // per-type filters. #define CONTACT_LIST_COMBINED 0xFFU // Number of digital contacts under the current filter (all types when combined). static int digitalContactsCount(void) { if (contactCallType == CONTACT_LIST_COMBINED) { return codeplugContactsGetCount(CONTACT_CALLTYPE_TG) + codeplugContactsGetCount(CONTACT_CALLTYPE_PC) + codeplugContactsGetCount(CONTACT_CALLTYPE_ALL); } return codeplugContactsGetCount(contactCallType); } // Fetch the (1-based) digital contact under the current filter. When combined, // the list runs groups first, then private calls, then all-calls. static int digitalContactGetForNumber(int number, CodeplugContact_t *c) { if (contactCallType != CONTACT_LIST_COMBINED) { return codeplugContactGetDataForNumberInType(number, contactCallType, c); } int row = number - 1; int count = codeplugContactsGetCount(CONTACT_CALLTYPE_TG); if (row < count) { return codeplugContactGetDataForNumberInType(row + 1, CONTACT_CALLTYPE_TG, c); } row -= count; count = codeplugContactsGetCount(CONTACT_CALLTYPE_PC); if (row < count) { return codeplugContactGetDataForNumberInType(row + 1, CONTACT_CALLTYPE_PC, c); } row -= count; return codeplugContactGetDataForNumberInType(row + 1, CONTACT_CALLTYPE_ALL, c); } // Voice/title label for the current filter. static const char *contactCallTypeLabel(void) { return ((contactCallType == CONTACT_LIST_COMBINED) ? currentLanguage->contacts : calltypeVoices[contactCallType]); } // Apply contact + its TS on selection for TX (contact list of quick list). static void overrideWithSelectedContact(void) { menuPrivateCallClear(); setOverrideTGorPC(contactListContactData.tgNumber, (contactListContactData.callType == CONTACT_CALLTYPE_PC)); // Contact has a TS override set if ((contactListContactData.reserve1 & CODEPLUG_CONTACT_FLAG_NO_TS_OVERRIDE) == 0x00) { int ts = ((contactListContactData.reserve1 & CODEPLUG_CONTACT_FLAG_TS_OVERRIDE_TIMESLOT_MASK) >> 1); trxSetDMRTimeSlot(ts, true); tsSetManualOverride(((menuSystemGetRootMenuNumber() == UI_CHANNEL_MODE) ? CHANNEL_CHANNEL : (CHANNEL_VFO_A + nonVolatileSettings.currentVFONumber)), (ts + 1)); } } static void reloadContactList(contactListContactType_t type) { menuDataGlobal.numItems = (type == MENU_CONTACT_LIST_CONTACT_DIGITAL) ? digitalContactsCount() : codeplugDTMFContactsGetCount(); if (menuDataGlobal.numItems > 0) { if (menuDataGlobal.currentItemIndex >= menuDataGlobal.numItems) { menuDataGlobal.currentItemIndex = 0; } uiDataGlobal.currentSelectedContactIndex = (type == MENU_CONTACT_LIST_CONTACT_DIGITAL) ? digitalContactGetForNumber(menuDataGlobal.currentItemIndex + 1, &contactListContactData) : codeplugDTMFContactGetDataForNumber(menuDataGlobal.currentItemIndex + 1, &contactListDTMFContactData); } else { uiDataGlobal.currentSelectedContactIndex = 0; } } menuStatus_t menuContactList(uiEvent_t *ev, bool isFirstRun) { if (isFirstRun) { calltypeVoices[0] = currentLanguage->group_call; calltypeVoices[1] = currentLanguage->private_call; calltypeVoices[2] = currentLanguage->all_call; // Do not override the timeout on error (e.g. "already in tg list") if (contactListOverrideState != MENU_CONTACT_LIST_TG_IN_RXGROUP) { menuContactListTimeout = 0; } if (contactListOverrideState == MENU_CONTACT_LIST_DISPLAY) { if (uiDataGlobal.currentSelectedContactIndex == 0) { int currentMenu = menuSystemGetCurrentMenuNumber(); // Shows digital contact list if called from "contact list" menu entry, or from +# in digital. // Otherwise displays DTMF contact list contactListType = ((currentMenu == MENU_CONTACT_LIST) || ((currentMenu == MENU_CONTACT_QUICKLIST) && (trxGetMode() != RADIO_MODE_ANALOG))) ? MENU_CONTACT_LIST_CONTACT_DIGITAL : MENU_CONTACT_LIST_CONTACT_DTMF; contactCallType = CONTACT_LIST_COMBINED; // default: show all contact types together dtmfSequenceReset(); } else { if (contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) { codeplugContactGetDataForIndex(uiDataGlobal.currentSelectedContactIndex, &contactListContactData); contactCallType = contactListContactData.callType; } else { codeplugDTMFContactGetDataForIndex(uiDataGlobal.currentSelectedContactIndex, &contactListDTMFContactData); } } reloadContactList(contactListType); contactListDisplayState = MENU_CONTACT_LIST_DISPLAY; voicePromptsInit(); if (menuSystemGetCurrentMenuNumber() == MENU_CONTACT_QUICKLIST) { if (contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) { voicePromptsAppendLanguageString(currentLanguage->dmr_contacts); voicePromptsAppendPrompt(PROMPT_SILENCE); voicePromptsAppendLanguageString(contactCallTypeLabel()); } else { voicePromptsAppendLanguageString(currentLanguage->dtmf_contact_list); } voicePromptsAppendPrompt(PROMPT_SILENCE); } else { if (contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) { voicePromptsAppendLanguageString(contactCallTypeLabel()); voicePromptsAppendPrompt(PROMPT_SILENCE); } } } else { if (contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) { codeplugContactGetDataForIndex(uiDataGlobal.currentSelectedContactIndex, &contactListContactData); } else { codeplugDTMFContactGetDataForIndex(uiDataGlobal.currentSelectedContactIndex, &contactListDTMFContactData); } contactListDisplayState = contactListOverrideState; contactListOverrideState = MENU_CONTACT_LIST_DISPLAY; } updateScreen(true); menuContactListExitCode = (MENU_STATUS_LIST_TYPE | MENU_STATUS_SUCCESS); } else { menuContactListExitCode = MENU_STATUS_SUCCESS; if (ev->hasEvent || (menuContactListTimeout > 0)) { handleEvent(ev); } } // Play the DTMF contact sequence from Quicklist dtmfSequenceTick(false); return menuContactListExitCode; } static void updateScreen(bool isFirstRun) { char nameBuf[33]; int mNum; int idx; const char *calltypeName[] = { currentLanguage->group_call, currentLanguage->private_call, currentLanguage->all_call, "DTMF" }; displayClearBuf(); switch (contactListDisplayState) { case MENU_CONTACT_LIST_DISPLAY: menuDisplayTitle((contactListType != MENU_CONTACT_LIST_CONTACT_DIGITAL) ? (char *) calltypeName[3] // "DTMF" : ((contactCallType == CONTACT_LIST_COMBINED) ? (char *) currentLanguage->contacts : (char *) calltypeName[contactCallType])); if (menuDataGlobal.numItems == 0) { displayThemeApply(THEME_ITEM_FG_WARNING_NOTIFICATION, THEME_ITEM_BG); displayPrintCentered((DISPLAY_SIZE_Y / 2), currentLanguage->list_empty, FONT_SIZE_3); displayThemeResetToDefault(); voicePromptsAppendLanguageString(currentLanguage->list_empty); } else { for (int i = MENU_START_ITERATION_VALUE; i <= MENU_END_ITERATION_VALUE; i++) { mNum = menuGetMenuOffset(menuDataGlobal.numItems, i); if (mNum == MENU_OFFSET_BEFORE_FIRST_ENTRY) { continue; } else if (mNum == MENU_OFFSET_AFTER_LAST_ENTRY) { break; } idx = (contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) ? digitalContactGetForNumber(mNum + 1, &contact) : codeplugDTMFContactGetDataForNumber(mNum + 1, &dtmfContact); if (idx > 0) { codeplugUtilConvertBufToString(((contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) ? contact.name : dtmfContact.name), nameBuf, 16); // need to convert to zero terminated string menuDisplayEntry(i, mNum, (char*) nameBuf, 0, THEME_ITEM_FG_CHANNEL_CONTACT, THEME_ITEM_COLOUR_NONE, THEME_ITEM_BG); } if (i == 0) { if (strlen(nameBuf)) { voicePromptsAppendString(nameBuf); } else { voicePromptsAppendLanguageString(currentLanguage->name); voicePromptsAppendPrompt(PROMPT_SILENCE); voicePromptsAppendLanguageString(currentLanguage->none); } } } } promptsPlayNotAfterTx(); break; case MENU_CONTACT_LIST_CONFIRM: codeplugUtilConvertBufToString(contactListContactData.name, nameBuf, 16); menuDisplayTitle(nameBuf); displayPrintCentered(16, currentLanguage->delete_contact_qm, FONT_SIZE_3); displayDrawChoice(CHOICE_YESNO, false); break; case MENU_CONTACT_LIST_DELETED: codeplugUtilConvertBufToString(contactListContactData.name, nameBuf, 16); displayPrintCentered(16, currentLanguage->contact_deleted, FONT_SIZE_3); displayDrawChoice(CHOICE_DISMISS, false); break; case MENU_CONTACT_LIST_TG_IN_RXGROUP: codeplugUtilConvertBufToString(contactListContactData.name, nameBuf, 16); menuDisplayTitle(nameBuf); displayPrintCentered(16, currentLanguage->contact_used, FONT_SIZE_3); displayPrintCentered((DISPLAY_SIZE_Y/2), currentLanguage->in_tg_list, FONT_SIZE_3); displayDrawChoice(CHOICE_DISMISS, false); break; } displayRender(); } static void handleEvent(uiEvent_t *ev) { if (ev->events & BUTTON_EVENT) { if (repeatVoicePromptOnSK1(ev)) { return; } } if ((ev->events & FUNCTION_EVENT) && (ev->function == FUNC_REDRAW)) { updateScreen(false); return; } // DTMF sequence is playing, stop it. if (dtmfSequenceIsKeying() && ((ev->keys.key != 0) || BUTTONCHECK_DOWN(ev, BUTTON_PTT) #if ! defined(PLATFORM_RD5R) || BUTTONCHECK_DOWN(ev, BUTTON_ORANGE) #endif )) { dtmfSequenceStop(); keyboardReset(); return; } switch (contactListDisplayState) { case MENU_CONTACT_LIST_DISPLAY: if (KEYCHECK_PRESS(ev->keys, KEY_DOWN)) { menuSystemMenuIncrement(&menuDataGlobal.currentItemIndex, menuDataGlobal.numItems); uiDataGlobal.currentSelectedContactIndex = (contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) ? digitalContactGetForNumber(menuDataGlobal.currentItemIndex + 1, &contactListContactData) : codeplugDTMFContactGetDataForNumber(menuDataGlobal.currentItemIndex + 1, &contactListDTMFContactData); voicePromptsInit(); updateScreen(false); menuContactListExitCode |= MENU_STATUS_LIST_TYPE; } else if (KEYCHECK_PRESS(ev->keys, KEY_UP)) { menuSystemMenuDecrement(&menuDataGlobal.currentItemIndex, menuDataGlobal.numItems); uiDataGlobal.currentSelectedContactIndex = (contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) ? digitalContactGetForNumber(menuDataGlobal.currentItemIndex + 1, &contactListContactData) : codeplugDTMFContactGetDataForNumber(menuDataGlobal.currentItemIndex + 1, &contactListDTMFContactData); voicePromptsInit(); updateScreen(false); menuContactListExitCode |= MENU_STATUS_LIST_TYPE; } else if (KEYCHECK_SHORTUP(ev->keys, KEY_HASH)) { if (contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) { // cycle: all combined -> group -> private -> all-call -> combined if (contactCallType == CONTACT_LIST_COMBINED) { contactCallType = CONTACT_CALLTYPE_TG; } else if (contactCallType == CONTACT_CALLTYPE_ALL) { contactCallType = CONTACT_LIST_COMBINED; } else { contactCallType = contactCallType + 1; } menuDataGlobal.currentItemIndex = 0; reloadContactList(contactListType); voicePromptsInit(); voicePromptsAppendLanguageString(contactCallTypeLabel()); voicePromptsAppendPrompt(PROMPT_SILENCE); updateScreen(false); } } else if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN)) { int currentMenu = menuSystemGetCurrentMenuNumber(); int currentMode = trxGetMode(); if (currentMenu == MENU_CONTACT_QUICKLIST) { if (contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) { if (menuDataGlobal.numItems > 0) { if (currentMode == RADIO_MODE_DIGITAL) { overrideWithSelectedContact(); uiDataGlobal.currentSelectedContactIndex = 0; announceItem(PROMPT_SEQUENCE_CONTACT_TG_OR_PC, PROMPT_THRESHOLD_3); menuSystemPopAllAndDisplayRootMenu(); } else { menuContactListExitCode |= MENU_STATUS_ERROR; } } return; } else // MENU_CONTACT_LIST_CONTACT_DTMF { if (currentMode == RADIO_MODE_ANALOG) { dtmfSequencePrepare(contactListDTMFContactData.code, true); } else { menuContactListExitCode |= MENU_STATUS_ERROR; } return; } } // Display submenu for DTMF contact list if (menuDataGlobal.numItems > 0) // display action list only if contact list is non empty { if ((currentMenu == MENU_CONTACT_LIST) || ((currentMenu == MENU_DTMF_CONTACT_LIST) && (currentMode == RADIO_MODE_ANALOG))) { menuSystemPushNewMenu(MENU_CONTACT_LIST_SUBMENU); } else { menuContactListExitCode |= MENU_STATUS_ERROR; } } return; } else if (KEYCHECK_SHORTUP(ev->keys, KEY_RED)) { uiDataGlobal.currentSelectedContactIndex = 0; menuSystemPopPreviousMenu(); return; } if ((contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) && (KEYCHECK_SHORTUP_NUMBER(ev->keys) && (BUTTONCHECK_DOWN(ev, BUTTON_SK2)))) { saveQuickkeyContactIndex(ev->keys.key, (uint16_t)contactListContactData.NOT_IN_CODEPLUGDATA_indexNumber); return; } break; case MENU_CONTACT_LIST_CONFIRM: if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN)) { memset(contact.name, 0xff, 16); contact.tgNumber = 0; contact.callType = 0xff; codeplugContactSaveDataForIndex(uiDataGlobal.currentSelectedContactIndex, &contact); uiDataGlobal.currentSelectedContactIndex = 0; menuContactListTimeout = 2000; contactListDisplayState = MENU_CONTACT_LIST_DELETED; reloadContactList(contactListType); updateScreen(false); voicePromptsAppendLanguageString(currentLanguage->contact_deleted); voicePromptsPlay(); } else if (KEYCHECK_SHORTUP(ev->keys, KEY_RED)) { contactListDisplayState = MENU_CONTACT_LIST_DISPLAY; voicePromptsInit(); // flush the VP buffer reloadContactList(contactListType); updateScreen(false); } break; case MENU_CONTACT_LIST_DELETED: case MENU_CONTACT_LIST_TG_IN_RXGROUP: // Countdown after the VP has finished to play, if VP is enabled. if (((nonVolatileSettings.audioPromptMode >= AUDIO_PROMPT_MODE_VOICE_THRESHOLD) && (voicePromptsIsPlaying() == false)) || (nonVolatileSettings.audioPromptMode < AUDIO_PROMPT_MODE_VOICE_THRESHOLD)) { menuContactListTimeout -= ((menuContactListTimeout > 0) ? 1 : 0); } if ((menuContactListTimeout == 0) || KEYCHECK_SHORTUP(ev->keys, KEY_GREEN) || KEYCHECK_SHORTUP(ev->keys, KEY_RED)) { menuContactListTimeout = 0; contactListDisplayState = MENU_CONTACT_LIST_DISPLAY; voicePromptsInit(); // flush the VP buffer reloadContactList(contactListType); updateScreen(false); } break; } } enum CONTACT_LIST_QUICK_MENU_ITEMS { CONTACT_LIST_QUICK_MENU_SELECT = 0, CONTACT_LIST_QUICK_MENU_EDIT, CONTACT_LIST_QUICK_MENU_DELETE, NUM_CONTACT_LIST_QUICK_MENU_ITEMS // The last item in the list is used so that we automatically get a total number of items in the list }; static void updateSubMenuScreen(void) { int mNum = 0; char buf[SCREEN_LINE_BUFFER_SIZE]; const char *langTextConst = NULL;// initialise to please the compiler voicePromptsInit(); displayClearBuf(); codeplugUtilConvertBufToString((contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) ? contactListContactData.name : contactListDTMFContactData.name, buf, 16); menuDisplayTitle(buf); for (int i = MENU_START_ITERATION_VALUE; i <= MENU_END_ITERATION_VALUE; i++) { mNum = menuGetMenuOffset(((contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) ? NUM_CONTACT_LIST_QUICK_MENU_ITEMS : 1), i); if (mNum == MENU_OFFSET_BEFORE_FIRST_ENTRY) { continue; } else if (mNum == MENU_OFFSET_AFTER_LAST_ENTRY) { break; } buf[0] = 0; switch(mNum) { case CONTACT_LIST_QUICK_MENU_SELECT: langTextConst = currentLanguage->select_tx; break; case CONTACT_LIST_QUICK_MENU_EDIT: langTextConst = (contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) ? currentLanguage->edit_contact : NULL; break; case CONTACT_LIST_QUICK_MENU_DELETE: langTextConst = (contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) ? currentLanguage->delete_contact : NULL; break; } if (langTextConst != NULL) { strncpy(buf, langTextConst, SCREEN_LINE_BUFFER_SIZE); } else { strncpy(buf, " ", SCREEN_LINE_BUFFER_SIZE); } if ((i == 0) && (langTextConst != NULL)) { voicePromptsAppendLanguageString(langTextConst); promptsPlayNotAfterTx(); } menuDisplayEntry(i, mNum, buf, 0, THEME_ITEM_FG_MENU_ITEM, THEME_ITEM_COLOUR_NONE, THEME_ITEM_BG); } displayRender(); } static void handleSubMenuEvent(uiEvent_t *ev) { if (ev->events & BUTTON_EVENT) { if (repeatVoicePromptOnSK1(ev)) { return; } } if ((ev->events & FUNCTION_EVENT) && (ev->function == FUNC_REDRAW)) { updateSubMenuScreen(); return; } // DTMF sequence is playing, stop it. if (dtmfSequenceIsKeying() && ((ev->keys.key != 0) || BUTTONCHECK_DOWN(ev, BUTTON_PTT) #if ! defined(PLATFORM_RD5R) || BUTTONCHECK_DOWN(ev, BUTTON_ORANGE) #endif )) { dtmfSequenceStop(); keyboardReset(); return; } if (KEYCHECK_SHORTUP(ev->keys, KEY_RED)) { menuSystemPopPreviousMenu(); } else if (KEYCHECK_SHORTUP(ev->keys, KEY_GREEN)) { contactListOverrideState = MENU_CONTACT_LIST_DISPLAY; switch (menuDataGlobal.currentItemIndex) { case CONTACT_LIST_QUICK_MENU_SELECT: if (contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) { if (trxGetMode() == RADIO_MODE_DIGITAL) { overrideWithSelectedContact(); uiDataGlobal.currentSelectedContactIndex = 0; announceItem(PROMPT_SEQUENCE_CONTACT_TG_OR_PC, PROMPT_THRESHOLD_3); uiDataGlobal.VoicePrompts.inhibitInitial = true; menuSystemPopAllAndDisplayRootMenu(); } else { menuContactListSubMenuExitCode |= MENU_STATUS_ERROR; } return; } else { dtmfSequencePrepare(contactListDTMFContactData.code, true); } break; case CONTACT_LIST_QUICK_MENU_EDIT: if (contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) { menuSystemSetCurrentMenu(MENU_CONTACT_DETAILS); } break; case CONTACT_LIST_QUICK_MENU_DELETE: if (uiDataGlobal.currentSelectedContactIndex > 0) { if (contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) { voicePromptsInit(); if ((contactListContactData.callType == CONTACT_CALLTYPE_TG) && codeplugContactGetRXGroup(contactListContactData.NOT_IN_CODEPLUGDATA_indexNumber)) { menuContactListTimeout = 2000; contactListOverrideState = MENU_CONTACT_LIST_TG_IN_RXGROUP; voicePromptsAppendLanguageString(currentLanguage->contact_used); voicePromptsAppendLanguageString(currentLanguage->in_tg_list); } else { contactListOverrideState = MENU_CONTACT_LIST_CONFIRM; voicePromptsAppendLanguageString(currentLanguage->delete_contact_qm); } voicePromptsPlay(); } menuSystemPopPreviousMenu(); } break; } } else if (KEYCHECK_PRESS(ev->keys, KEY_DOWN)) { menuSystemMenuIncrement(&menuDataGlobal.currentItemIndex, ((contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) ? NUM_CONTACT_LIST_QUICK_MENU_ITEMS : 1)); updateSubMenuScreen(); } else if (KEYCHECK_PRESS(ev->keys, KEY_UP)) { menuSystemMenuDecrement(&menuDataGlobal.currentItemIndex, ((contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) ? NUM_CONTACT_LIST_QUICK_MENU_ITEMS : 1)); updateSubMenuScreen(); } if ((menuDataGlobal.currentItemIndex == CONTACT_LIST_QUICK_MENU_SELECT) && (contactListType == MENU_CONTACT_LIST_CONTACT_DIGITAL) && (KEYCHECK_SHORTUP_NUMBER(ev->keys) && (BUTTONCHECK_DOWN(ev, BUTTON_SK2)))) { saveQuickkeyContactIndex(ev->keys.key, (uint16_t)contactListContactData.NOT_IN_CODEPLUGDATA_indexNumber); return; } } menuStatus_t menuContactListSubMenu(uiEvent_t *ev, bool isFirstRun) { if (isFirstRun) { menuDataGlobal.currentItemIndex = 0; updateSubMenuScreen(); keyboardInit(); menuContactListSubMenuExitCode = (MENU_STATUS_LIST_TYPE | MENU_STATUS_SUCCESS); } else { menuContactListSubMenuExitCode = MENU_STATUS_SUCCESS; if (ev->hasEvent) { handleSubMenuEvent(ev); } } dtmfSequenceTick(true); return menuContactListSubMenuExitCode; }