From 62da22c1d0334594f7eb9971b6ed1a40c5a04d36 Mon Sep 17 00:00:00 2001 From: dgis Date: Thu, 8 Aug 2019 22:50:56 +0200 Subject: [PATCH] - Add the KML Icon if present in the navigation menu header (only support PNG or 32bits BMP in the ICO file). - Allow an optional overlapping LCD part stuck to the screen when swiping the 2 calc parts (not yet working). --- app/src/main/cpp/emu-jni.c | 121 +++++-- app/src/main/cpp/win32-layer.c | 204 ++++++++++- app/src/main/cpp/win32-layer.h | 12 +- .../calculator/LCDOverlappingView.java | 316 ++++++++++++++++++ .../emulator/calculator/MainScreenView.java | 35 +- .../org/emulator/calculator/NativeLib.java | 8 +- .../emulator/calculator/PanAndScaleView.java | 4 +- .../emulator/forty/eight/MainActivity.java | 218 +++++++----- app/src/main/res/layout/content_main.xml | 17 +- app/src/main/res/layout/nav_header_main.xml | 49 ++- app/src/main/res/values/arrays.xml | 11 + app/src/main/res/values/strings.xml | 5 +- app/src/main/res/xml/pref_general.xml | 11 + 13 files changed, 849 insertions(+), 162 deletions(-) create mode 100644 app/src/main/java/org/emulator/calculator/LCDOverlappingView.java diff --git a/app/src/main/cpp/emu-jni.c b/app/src/main/cpp/emu-jni.c index 5b53da5..14ae503 100644 --- a/app/src/main/cpp/emu-jni.c +++ b/app/src/main/cpp/emu-jni.c @@ -25,9 +25,8 @@ #include "win32-layer.h" extern AAssetManager * assetManager; -static jobject viewToUpdate = NULL; static jobject mainActivity = NULL; -jobject bitmapMainScreen; +jobject bitmapMainScreen = NULL; AndroidBitmapInfo androidBitmapInfo; enum DialogBoxMode currentDialogBoxMode; enum ChooseKmlMode chooseCurrentKmlMode; @@ -86,16 +85,16 @@ enum CALLBACK_TYPE { // https://stackoverflow.com/questions/9630134/jni-how-to-callback-from-c-or-c-to-java int mainViewCallback(int type, int param1, int param2, const TCHAR * param3, const TCHAR * param4) { - if (viewToUpdate) { + if (mainActivity) { JNIEnv *jniEnv = getJNIEnvironment(); if(jniEnv) { - jclass viewToUpdateClass = (*jniEnv)->GetObjectClass(jniEnv, viewToUpdate); - if(viewToUpdateClass) { - jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, viewToUpdateClass, "updateCallback", "(IIILjava/lang/String;Ljava/lang/String;)I"); + jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity); + if(mainActivityClass) { + jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "updateCallback", "(IIILjava/lang/String;Ljava/lang/String;)I"); jstring utfParam3 = (*jniEnv)->NewStringUTF(jniEnv, param3); jstring utfParam4 = (*jniEnv)->NewStringUTF(jniEnv, param4); - int result = (*jniEnv)->CallIntMethod(jniEnv, viewToUpdate, midStr, type, param1, param2, utfParam3, utfParam4); - (*jniEnv)->DeleteLocalRef(jniEnv, viewToUpdateClass); + int result = (*jniEnv)->CallIntMethod(jniEnv, mainActivity, midStr, type, param1, param2, utfParam3, utfParam4); + (*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass); //if(needDetach) ret = (*java_machine)->DetachCurrentThread(java_machine); return result; } @@ -283,20 +282,30 @@ void sendByteUdp(unsigned char byteSent) { } } -JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_start(JNIEnv *env, jobject thisz, jobject assetMgr, jobject bitmapMainScreen0, jobject activity, jobject view) { +void setKMLIcon(int imageWidth, int imageHeight, LPBYTE buffer, int bufferSize) { + JNIEnv *jniEnv = getJNIEnvironment(); + if(jniEnv) { + jclass mainActivityClass = (*jniEnv)->GetObjectClass(jniEnv, mainActivity); + if(mainActivityClass) { + jmethodID midStr = (*jniEnv)->GetMethodID(jniEnv, mainActivityClass, "setKMLIcon", "(II[B)V"); + + jbyteArray pixels = NULL; + if(buffer) { + pixels = (*jniEnv)->NewByteArray(jniEnv, bufferSize); + (*jniEnv)->SetByteArrayRegion(jniEnv, pixels, 0, bufferSize, (jbyte *) buffer); + } + (*jniEnv)->CallVoidMethod(jniEnv, mainActivity, midStr, imageWidth, imageHeight, pixels); + (*jniEnv)->DeleteLocalRef(jniEnv, mainActivityClass); + } + } +} + +JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_start(JNIEnv *env, jobject thisz, jobject assetMgr, jobject activity) { chooseCurrentKmlMode = ChooseKmlMode_UNKNOWN; szChosenCurrentKml[0] = '\0'; - bitmapMainScreen = (*env)->NewGlobalRef(env, bitmapMainScreen0); mainActivity = (*env)->NewGlobalRef(env, activity); - viewToUpdate = (*env)->NewGlobalRef(env, view); - - - int ret = AndroidBitmap_getInfo(env, bitmapMainScreen, &androidBitmapInfo); - if (ret < 0) { - LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret); - } assetManager = AAssetManager_fromJava(env, assetMgr); @@ -394,10 +403,6 @@ JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_stop(JNIEnv *env, SoundClose(); // close waveform-audio output device soundEnabled = FALSE; - if (viewToUpdate) { - (*env)->DeleteGlobalRef(env, viewToUpdate); - viewToUpdate = NULL; - } if(bitmapMainScreen) { (*env)->DeleteGlobalRef(env, bitmapMainScreen); bitmapMainScreen = NULL; @@ -419,6 +424,74 @@ JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_changeBitmap(JNIEn } } +JNIEXPORT jboolean JNICALL Java_org_emulator_calculator_NativeLib_copyLCD(JNIEnv *env, jobject thisz, jobject bitmapLCD) { + + if(!bitmapLCD) + return JNI_FALSE; + + AndroidBitmapInfo bitmapLCDInfo; + int ret = AndroidBitmap_getInfo(env, bitmapLCD, &bitmapLCDInfo); + if (ret < 0) { + LOGE("AndroidBitmap_getInfo() failed ! error=%d", ret); + return JNI_FALSE; + } + +// INT nxSize, nySize; +// GetSizeLcdBitmap(&nxSize,&nySize); // get LCD size + + HDC hSrcDC = hLcdDC; // use display HDC as source + if(!hSrcDC) + return JNI_FALSE; + + + + HBITMAP hBmp = hSrcDC->selectedBitmap; + if (hBmp && hBmp->bitmapInfoHeader && hBmp->bitmapInfoHeader->biWidth == bitmapLCDInfo.width && + abs(hBmp->bitmapInfoHeader->biHeight) == bitmapLCDInfo.height) { + INT nxO = 0, nyO = 0; // origin in HDC + void *pixelsDestination; + if ((ret = AndroidBitmap_lockPixels(env, bitmapLCD, &pixelsDestination)) < 0) { + LOGE("AndroidBitmap_lockPixels() failed ! error=%d", ret); + return JNI_FALSE; + } + + // if (nCurrentHardware == HDW_SACA) + // { + // TCHAR cBuffer[32]; // temp. buffer for text + // MSG msg; + // + // // calculate bitmap origins from hWindowDC + // nxO = nLcdX; if (nxO > 1) { nxO -= 2; nxSize += 4; }; + // nyO = nLcdY; if (nyO > 1) { nyO -= 2; nySize += 4; }; + // + // hSrcDC = hWindowDC; // use output HDC as source + // } + + EnterCriticalSection(&csGDILock); // solving NT GDI problems + + // copy display area + //BitBlt(hBmpDC,0,0,nxSize,nySize,hSrcDC,nxO,nyO,SRCCOPY); + + size_t strideSource = ((unsigned int) (4 * ((hBmp->bitmapInfoHeader->biWidth * + hBmp->bitmapInfoHeader->biBitCount + 31) / + 32))); + size_t strideDestination = bitmapLCDInfo.stride; + VOID *bitmapBitsSource = (VOID *) hBmp->bitmapBits; + VOID *bitmapBitsDestination = pixelsDestination; + int biHeight = abs(hBmp->bitmapInfoHeader->biHeight); + for (int y = 0; y < biHeight; y++) { + memcpy(bitmapBitsDestination, bitmapBitsSource, strideSource); + bitmapBitsSource += strideSource; + bitmapBitsDestination += strideDestination; + } + LeaveCriticalSection(&csGDILock); + AndroidBitmap_unlockPixels(env, bitmapLCD); + return JNI_TRUE; + } + + return JNI_FALSE; +} + JNIEXPORT void JNICALL Java_org_emulator_calculator_NativeLib_draw(JNIEnv *env, jobject thisz) { draw(); @@ -1117,6 +1190,12 @@ JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_getState(JNIEnv *e return nState; } +JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_getScreenPositionX(JNIEnv *env, jobject thisz) { + return nLcdX; +} +JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_getScreenPositionY(JNIEnv *env, jobject thisz) { + return nLcdY; +} JNIEXPORT jint JNICALL Java_org_emulator_calculator_NativeLib_getScreenWidth(JNIEnv *env, jobject thisz) { return 131*nLcdZoom*nGdiXZoom; } diff --git a/app/src/main/cpp/win32-layer.c b/app/src/main/cpp/win32-layer.c index 60fcc46..84f0959 100644 --- a/app/src/main/cpp/win32-layer.c +++ b/app/src/main/cpp/win32-layer.c @@ -23,6 +23,8 @@ #include "core/resource.h" #include "win32-layer.h" #include "emu.h" +#include "core/lodepng.h" + extern JavaVM *java_machine; extern jobject bitmapMainScreen; @@ -837,9 +839,189 @@ BOOL GetSaveFileName(LPOPENFILENAME openFilename) { return FALSE; } +// Almost the same function as the private core function DibNumColors() +static __inline WORD DibNumColors(BITMAPINFOHEADER CONST *lpbi) +{ + if (lpbi->biClrUsed != 0) return (WORD) lpbi->biClrUsed; + + /* a 24 bitcount DIB has no color table */ + return (lpbi->biBitCount <= 8) ? (1 << lpbi->biBitCount) : 0; +} + +static HBITMAP DecodeBMPIcon(LPBYTE imageBuffer, size_t imageSize) { + // size of bitmap header information + DWORD dwFileSize = sizeof(BITMAPINFOHEADER); + if (imageSize < dwFileSize) + return NULL; + + LPBITMAPINFO pBmi = (LPBITMAPINFO)imageBuffer; + + // size with color table + if (pBmi->bmiHeader.biCompression == BI_BITFIELDS) + dwFileSize += 3 * sizeof(DWORD); + else + dwFileSize += DibNumColors(&pBmi->bmiHeader) * sizeof(RGBQUAD); + + pBmi->bmiHeader.biHeight /= 2; + + DWORD stride = (((pBmi->bmiHeader.biWidth * (DWORD)pBmi->bmiHeader.biBitCount) + 31) / 32 * 4); + DWORD height = (DWORD) abs(pBmi->bmiHeader.biHeight); + + // size with bitmap data + if (pBmi->bmiHeader.biCompression != BI_RGB) + dwFileSize += pBmi->bmiHeader.biSizeImage; + else { + pBmi->bmiHeader.biSizeImage = stride * height; + dwFileSize += pBmi->bmiHeader.biSizeImage; + } + if (imageSize < dwFileSize) + return NULL; + + HBITMAP hBitmap = CreateDIBitmap(hWindowDC, &pBmi->bmiHeader, CBM_INIT, imageBuffer, pBmi, DIB_RGB_COLORS); + if(hBitmap) { + // Inverse the height + BYTE *source = imageBuffer + dwFileSize - stride; + BYTE *destination = hBitmap->bitmapBits; + for (int i = 0; i < height; ++i) { + memcpy(destination, source, stride); + source -= stride; + destination += stride; + } + } + // Only support 32bits RGBA BMP for now. + return hBitmap; +} + +static HBITMAP DecodePNGIcon(LPBYTE imageBuffer, size_t imageSize) { + HBITMAP hBitmap = NULL; + UINT uWidth,uHeight; + LPBYTE pbyImage = NULL; + UINT uError = lodepng_decode_memory(&pbyImage, &uWidth, &uHeight, imageBuffer, imageSize, LCT_RGBA, 8); + if (uError == 0) { + BITMAPINFO bmi; + ZeroMemory(&bmi, sizeof(bmi)); // init bitmap info + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = (LONG) uWidth; + bmi.bmiHeader.biHeight = (LONG) uHeight; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; // create a true color DIB + bmi.bmiHeader.biCompression = BI_RGB; + + // bitmap dimensions + LONG lBytesPerLine = (((bmi.bmiHeader.biWidth * bmi.bmiHeader.biBitCount) + 31) / 32 * 4); + bmi.bmiHeader.biSizeImage = (DWORD) (lBytesPerLine * bmi.bmiHeader.biHeight); + + // allocate buffer for pixels + LPBYTE pbyPixels; // BMP buffer + hBitmap = CreateDIBSection(hWindowDC, &bmi, DIB_RGB_COLORS, (VOID **)&pbyPixels, NULL, 0); + if (hBitmap) + memcpy(pbyPixels, pbyImage, bmi.bmiHeader.biSizeImage); + } + + if (pbyImage != NULL) + free(pbyImage); + return hBitmap; +} + +// ICO (file format) https://en.wikipedia.org/wiki/ICO_(file_format) +// https://docs.microsoft.com/en-us/previous-versions/ms997538(v=msdn.10) +typedef struct { + WORD idReserved; + WORD idType; + WORD idCount; +} ICONDIR, *LPICONDIR; +typedef struct ICONDIRENTRY { + BYTE bWidth; // Specifies image width in pixels. Can be any number between 0 and 255. Value 0 means image width is 256 pixels. + BYTE bHeight; // Specifies image height in pixels. Can be any number between 0 and 255. Value 0 means image height is 256 pixels. + BYTE bColorCount; // Specifies number of colors in the color palette. Should be 0 if the image does not use a color palette. + BYTE bReserved; // Reserved. Should be 0. + WORD wPlanes; // In ICO format: Specifies color planes. Should be 0 or 1. + WORD wBitCount; // In ICO format: Specifies bits per pixel. + DWORD dwBytesInRes; // Specifies the size of the image's data in bytes + DWORD dwImageOffset; // Specifies the offset of BMP or PNG data from the beginning of the ICO/CUR file +} ICONDIRENTRY, *LPICONDIRENTRY; + +typedef struct { + BITMAPINFOHEADER icHeader; // DIB header + RGBQUAD icColors[1]; // Color table + BYTE icXOR[1]; // DIB bits for XOR mask + BYTE icAND[1]; // DIB bits for AND mask +} ICONIMAGE, *LPICONIMAGE; + HANDLE LoadImage(HINSTANCE hInst, LPCSTR name, UINT type, int cx, int cy, UINT fuLoad) { - //TODO - return NULL; + + HANDLE hIconFile = CreateFile(name, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); + if (hIconFile == INVALID_HANDLE_VALUE) + return NULL; + + ICONDIR fileHeader; + DWORD nNumberOfBytesToRead = 0; + if(ReadFile(hIconFile, &fileHeader, sizeof(ICONDIR), &nNumberOfBytesToRead, NULL) == FALSE || sizeof(ICONDIR) != nNumberOfBytesToRead) { + CloseHandle(hIconFile); + return NULL; + } + + size_t iconsBufferSize = fileHeader.idCount * sizeof(ICONDIRENTRY); + ICONDIRENTRY * iconHeaderArray = malloc(iconsBufferSize); + nNumberOfBytesToRead = 0; + if(ReadFile(hIconFile, iconHeaderArray, iconsBufferSize, &nNumberOfBytesToRead, NULL) == FALSE || iconsBufferSize != nNumberOfBytesToRead) { + CloseHandle(hIconFile); + return NULL; + } + + int maxWidth = -1; + int maxHeight = -1; + int maxBitPerPixel = -1; + int maxIconIndex = -1; + for (int i = 0; i < fileHeader.idCount; ++i) { + ICONDIRENTRY * iconHeader = &(iconHeaderArray[i]); + int width = iconHeader->bWidth == 0 ? 256 : iconHeader->bWidth; + int height = iconHeader->bHeight == 0 ? 256 : iconHeader->bHeight; + if(width >= maxWidth && height >= maxHeight && iconHeader->wBitCount > maxBitPerPixel) { + maxWidth = width; + maxHeight = height; + maxBitPerPixel = iconHeader->wBitCount; + maxIconIndex = i; + } + } + maxIconIndex = 1; // To test BMP + if(maxIconIndex == -1) { + CloseHandle(hIconFile); + return NULL; + } + + DWORD dwBytesInRes = iconHeaderArray[maxIconIndex].dwBytesInRes; + LPBYTE iconBuffer = malloc(dwBytesInRes); + + SetFilePointer(hIconFile, iconHeaderArray[maxIconIndex].dwImageOffset, 0, FILE_BEGIN); + if(ReadFile(hIconFile, iconBuffer, dwBytesInRes, &nNumberOfBytesToRead, NULL) == FALSE || dwBytesInRes != nNumberOfBytesToRead) { + CloseHandle(hIconFile); + free(iconHeaderArray); + free(iconBuffer); + return NULL; + } + CloseHandle(hIconFile); + + HBITMAP icon = NULL; + if (dwBytesInRes >= 8 && memcmp(iconBuffer, "\x89PNG\r\n\x1a\n", 8) == 0) + // It is a PNG image + icon = DecodePNGIcon(iconBuffer, dwBytesInRes); + else + // It is a BMP image + icon = DecodeBMPIcon(iconBuffer, dwBytesInRes); + + + free(iconHeaderArray); + free(iconBuffer); + + if(!icon) + return NULL; + + HANDLE handle = malloc(sizeof(struct _HANDLE)); + memset(handle, 0, sizeof(struct _HANDLE)); + handle->handleType = HANDLE_TYPE_ICON; + handle->icon = icon; + return handle; } @@ -871,8 +1053,17 @@ LRESULT SendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) { return selItemDataIndex[wParam]; } } - if(Msg == WM_SETICON) { - + if(Msg == WM_SETICON && wParam == ICON_BIG) { + if(lParam != NULL) { + HANDLE hIcon = (HANDLE)lParam; + if(hIcon->handleType == HANDLE_TYPE_ICON && hIcon->icon != NULL) { + HBITMAP icon = hIcon->icon; + if(icon && icon->bitmapInfoHeader && icon->bitmapBits) + setKMLIcon(icon->bitmapInfoHeader->biWidth, icon->bitmapInfoHeader->biHeight, icon->bitmapBits, icon->bitmapInfoHeader->biSizeImage); + } + } else { + setKMLIcon(0, 0, NULL, 0); + } } return NULL; } @@ -1964,7 +2155,7 @@ BOOL StretchBlt(HDC hdcDest, int xDest, int yDest, int wDest, int hDest, HDC hdc } } - if(jniEnv && (ret = AndroidBitmap_unlockPixels(jniEnv, bitmapMainScreen)) < 0) { + if(jniEnv && hdcDest->hdcCompatible == NULL && (ret = AndroidBitmap_unlockPixels(jniEnv, bitmapMainScreen)) < 0) { LOGD("AndroidBitmap_unlockPixels() failed ! error=%d", ret); return FALSE; } @@ -1996,8 +2187,7 @@ HBITMAP CreateBitmap( int nWidth, int nHeight, UINT nPlanes, UINT nBitCount, CON BITMAPINFO * newBitmapInfo = malloc(sizeof(BITMAPINFO)); memset(newBitmapInfo, 0, sizeof(BITMAPINFO)); - //newBitmapInfo->bmiHeader.biBitCount = 32; //TODO should be nBitCount - newBitmapInfo->bmiHeader.biBitCount = nBitCount; //TODO should be nBitCount + newBitmapInfo->bmiHeader.biBitCount = (WORD) nBitCount; newBitmapInfo->bmiHeader.biClrUsed = 0; newBitmapInfo->bmiHeader.biWidth = nWidth; newBitmapInfo->bmiHeader.biHeight = -nHeight; diff --git a/app/src/main/cpp/win32-layer.h b/app/src/main/cpp/win32-layer.h index ae4fd05..370f9bd 100644 --- a/app/src/main/cpp/win32-layer.h +++ b/app/src/main/cpp/win32-layer.h @@ -507,20 +507,24 @@ enum HANDLE_TYPE { HANDLE_TYPE_EVENT, HANDLE_TYPE_THREAD, HANDLE_TYPE_WINDOW, + HANDLE_TYPE_ICON, }; struct _HANDLE { enum HANDLE_TYPE handleType; + // HANDLE_TYPE_FILE* int fileDescriptor; BOOL fileOpenFileFromContentResolver; AAsset* fileAsset; + // HANDLE_TYPE_FILE_MAPPING* off_t fileMappingOffset; size_t fileMappingSize; void* fileMappingAddress; DWORD fileMappingProtect; + // HANDLE_TYPE_THREAD pthread_t threadId; DWORD (*threadStartAddress)(LPVOID); LPVOID threadParameter; @@ -528,12 +532,17 @@ struct _HANDLE { struct tagMSG threadMessage; int threadIndex; + // HANDLE_TYPE_EVENT pthread_cond_t eventCVariable; pthread_mutex_t eventMutex; BOOL eventAutoReset; BOOL eventState; - HDC windowDC; + // HANDLE_TYPE_WINDOW + HDC windowDC; + + // HANDLE_TYPE_ICON + HBITMAP icon; }; typedef struct _HANDLE * HANDLE; @@ -1227,6 +1236,7 @@ void clipboardCopyText(const TCHAR * text); const TCHAR * clipboardPasteText(); void performHapticFeedback(); void sendByteUdp(unsigned char byteSent); +void setKMLIcon(int imageWidth, int imageHeight, LPBYTE buffer, int bufferSize); typedef int SOCKET; diff --git a/app/src/main/java/org/emulator/calculator/LCDOverlappingView.java b/app/src/main/java/org/emulator/calculator/LCDOverlappingView.java new file mode 100644 index 0000000..a8dfe51 --- /dev/null +++ b/app/src/main/java/org/emulator/calculator/LCDOverlappingView.java @@ -0,0 +1,316 @@ +package org.emulator.calculator; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.preference.PreferenceManager; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.widget.FrameLayout; + +public class LCDOverlappingView extends View { + + protected static final String TAG = "LCDOverlappingView"; + protected final boolean debug = false; + + private SharedPreferences sharedPreferences; + private Paint paint = new Paint(); + private Bitmap bitmapLCD; + private float bitmapRatio = -1; + private float minViewSize = 200.0f; + private int overlappingLCDMode = 1; + private MainScreenView mainScreenView; + + public LCDOverlappingView(Context context, MainScreenView mainScreenView) { + super(context); + + this.mainScreenView = mainScreenView; + this.mainScreenView.setOnUpdateLayoutListener(() -> this.updateLayout(this.mainScreenView.viewPanOffsetX, this.mainScreenView.viewPanOffsetY, this.mainScreenView.viewScaleFactorX, this.mainScreenView.viewScaleFactorY)); + + sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext()); + + paint.setFilterBitmap(true); + paint.setAntiAlias(true); + + DisplayMetrics displayMetrics = new DisplayMetrics(); + ((Activity)context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); + bitmapLCD = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); + bitmapLCD.eraseColor(Color.BLACK); + + this.setFocusable(true); + this.setFocusableInTouchMode(true); + } + + private float previousX0 = -1.0f, previousY0 = -1.0f, previousX1 = -1.0f, previousY1 = -1.0f; + private float previousDownX0 = 0, previousDownY0 = 0; + + private float distanceBetweenTwoPoints(float fromPointX, float fromPointY, float toPointX, float toPointY) { + float x = toPointX - fromPointX; + float y = toPointY - fromPointY; + return (float)Math.sqrt(x * x + y * y); + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + if(debug) Log.d(TAG, "onTouchEvent() getAction(): " + event.getAction() + ", getPointerCount(): " + event.getPointerCount()); + + int touchCount = event.getPointerCount(); + float currentX0 = 0f, currentY0 = 0f, currentX1 = 0f, currentY1 = 0f; + + + if(touchCount > 0) { + currentX0 = event.getX(0); + currentY0 = event.getY(0); + } + if(touchCount > 1) { + currentX1 = event.getX(1); + currentY1 = event.getY(1); + } + if(debug) { + Log.d(TAG, "onTouchEvent() currentX0: " + currentX0 + ", currentY0: " + currentY0); + Log.d(TAG, "onTouchEvent() currentX1: " + currentX1 + ", currentY1: " + currentY1); + Log.d(TAG, "onTouchEvent() touchCount: " + touchCount); + } + + int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_DOWN: + if(debug) Log.d(TAG, "onTouchEvent() touchesBegan count: " + touchCount); + + previousDownX0 = currentX0; + previousDownY0 = currentY0; + + break; + case MotionEvent.ACTION_MOVE: + //if(debug) Log.d(TAG, "touchesMoved count: " + touchCount); + + if (touchCount == 1) { + FrameLayout.LayoutParams viewFlowLayout = (FrameLayout.LayoutParams)getLayoutParams(); + viewFlowLayout.leftMargin += currentX0 - previousDownX0; + viewFlowLayout.topMargin += currentY0 - previousDownY0; + setLayoutParams(viewFlowLayout); + changeOverlappingLCDMode(); + } else if (touchCount == 2) { + if(previousX0 != -1.0f) { + FrameLayout.LayoutParams viewFlowLayout = (FrameLayout.LayoutParams)getLayoutParams(); + float scaleFactor = distanceBetweenTwoPoints(currentX0, currentY0, currentX1, currentY1) / distanceBetweenTwoPoints(previousX0, previousY0, previousX1, previousY1); + float scaledViewWidth = (float)viewFlowLayout.width * scaleFactor; + float scaledViewHeight = (float)viewFlowLayout.height * scaleFactor; + + float deltaFactor = (scaleFactor - 1.0f) / 2.0f; + int deltaW = (int) ((float) viewFlowLayout.width * deltaFactor); + int deltaH = (int) ((float) viewFlowLayout.height * deltaFactor); + + if (debug) + Log.d(TAG, "onTouchEvent() scaleFactor: " + scaleFactor + ", currentWidth: " + viewFlowLayout.width + ", currentHeight: " + viewFlowLayout.height + + ", deltaW: " + deltaW + ", deltaH: " + deltaH); + if (debug) + Log.d(TAG, "onTouchEvent() BEFORE leftMargin: " + viewFlowLayout.leftMargin + ", topMargin: " + viewFlowLayout.topMargin + + ", width: " + viewFlowLayout.width + ", height: " + viewFlowLayout.height); + + viewFlowLayout.leftMargin -= deltaW; + viewFlowLayout.topMargin -= deltaH; + int newViewWidth, newViewHeight; + if(bitmapRatio > 0.0f) { + if(bitmapRatio < 1.0f) { + newViewWidth = (int) scaledViewWidth; + newViewHeight = (int) (newViewWidth * bitmapRatio); + } else { + newViewHeight = (int) scaledViewHeight; + newViewWidth = (int) (newViewHeight / bitmapRatio); + } + } else { + newViewWidth = (int) scaledViewWidth; + newViewHeight = (int) scaledViewHeight; + } + + if(newViewWidth >= minViewSize && newViewWidth >= minViewSize) { + viewFlowLayout.width = newViewWidth; + viewFlowLayout.height = newViewHeight; + } + + if (debug) + Log.d(TAG, "onTouchEvent() AFTER leftMargin: " + viewFlowLayout.leftMargin + ", topMargin: " + viewFlowLayout.topMargin + + ", width: " + viewFlowLayout.width + ", height: " + viewFlowLayout.height); + + setLayoutParams(viewFlowLayout); + changeOverlappingLCDMode(); + } + previousX0 = currentX0; + previousY0 = currentY0; + previousX1 = currentX1; + previousY1 = currentY1; + } + + break; + case MotionEvent.ACTION_UP: + previousX0 = -1.0f; + previousY0 = -1.0f; + previousX1 = -1.0f; + previousY1 = -1.0f; + break; + case MotionEvent.ACTION_CANCEL: + break; + case MotionEvent.ACTION_OUTSIDE: + break; + default: + } + return true; // processed + } + + @Override + protected void onDraw(Canvas canvas) { + //if(debug) Log.d(TAG, "onDraw()"); + + canvas.drawColor(Color.RED); + + if(this.overlappingLCDMode > 0 && bitmapLCD != null) { + canvas.save(); + if (bitmapLCD.getWidth() > 0 && bitmapLCD.getHeight() > 0) + canvas.scale((float) getWidth() / (float) bitmapLCD.getWidth(), (float) getHeight() / (float) bitmapLCD.getHeight()); + canvas.drawBitmap(bitmapLCD, 0, 0, paint); + canvas.restore(); + } + } + + public int updateCallback(int type, int param1, int param2, String param3, String param4) { + if(this.overlappingLCDMode == 0) + return -1; + switch (type) { + case NativeLib.CALLBACK_TYPE_INVALIDATE: + //if(debug) Log.d(TAG, "PAINT updateCallback() postInvalidate()"); + if(debug) Log.d(TAG, "updateCallback() CALLBACK_TYPE_INVALIDATE"); + NativeLib.copyLCD(bitmapLCD); + postInvalidate(); + break; + case NativeLib.CALLBACK_TYPE_WINDOW_RESIZE: + // New Bitmap size + int newLCDWidth = NativeLib.getScreenWidth(); + int newLCDHeight = NativeLib.getScreenHeight(); + if(bitmapLCD == null || bitmapLCD.getWidth() != newLCDWidth || bitmapLCD.getHeight() != newLCDHeight) { + int newWidth = Math.max(1, newLCDWidth); + int newHeight = Math.max(1, newLCDHeight); + + if(debug) Log.d(TAG, "updateCallback() Bitmap.createBitmap(x: " + newWidth + ", y: " + newHeight + ")"); + Bitmap oldBitmapLCD = bitmapLCD; + bitmapLCD = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888); + bitmapRatio = (float)newHeight / (float)newWidth; + if(oldBitmapLCD != null) + oldBitmapLCD.recycle(); + + if(this.overlappingLCDMode != 1) { + setVisibility(View.VISIBLE); + float scale = sharedPreferences.getFloat("settings_lcd_overlapping_scale", 1.0f); + if (scale < 0.01f) + scale = 0.01f; + int viewWidth = (int) (newWidth * scale); + int viewHeight = (int) (newHeight * scale); + if (viewWidth < minViewSize && viewHeight < minViewSize) { + if (bitmapRatio > 0.0f) { + if (bitmapRatio < 1.0f) { + viewWidth = (int) minViewSize; + viewHeight = (int) (viewWidth * bitmapRatio); + } else { + viewHeight = (int) minViewSize; + viewWidth = (int) (viewHeight / bitmapRatio); + } + } else { + viewWidth = (int) minViewSize; + viewHeight = (int) minViewSize; + } + } + + FrameLayout.LayoutParams viewFlowLayout = new FrameLayout.LayoutParams(viewWidth, viewHeight); + viewFlowLayout.leftMargin = sharedPreferences.getInt("settings_lcd_overlapping_x", 20); + viewFlowLayout.topMargin = sharedPreferences.getInt("settings_lcd_overlapping_y", 80); + if (viewFlowLayout.leftMargin + viewWidth < 0) + viewFlowLayout.leftMargin = 0; + if (viewFlowLayout.topMargin + viewHeight < 0) + viewFlowLayout.topMargin = 0; + setLayoutParams(viewFlowLayout); + } + } + break; + } + return -1; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + if(debug) Log.d(TAG, "onSizeChanged() width: " + w + ", height: " + h); + } + + public void updateLayout(float viewPanOffsetX, float viewPanOffsetY, float viewScaleFactorX, float viewScaleFactorY) { + if(debug) Log.d(TAG, "updateLayout()"); + if(this.overlappingLCDMode == 0) // No Overlapping LCD + return; + if(this.overlappingLCDMode == 1) { // Auto + int newLCDWidth = NativeLib.getScreenWidth(); + int newLCDHeight = NativeLib.getScreenHeight(); + int newWidth = Math.max(1, newLCDWidth); + int newHeight = Math.max(1, newLCDHeight); + + post(() -> { + FrameLayout.LayoutParams viewFlowLayout = new FrameLayout.LayoutParams((int) (newWidth * viewScaleFactorX), (int) (newHeight * viewScaleFactorY)); + viewFlowLayout.leftMargin = (int)(viewScaleFactorX * NativeLib.getScreenPositionX() + viewPanOffsetX); + viewFlowLayout.topMargin = (int)(viewScaleFactorY * NativeLib.getScreenPositionY() + viewPanOffsetY); + if(debug) Log.d(TAG, "updateLayout() leftMargin: " + viewFlowLayout.leftMargin + ", topMargin: " + viewFlowLayout.topMargin + + ", width: " + viewFlowLayout.width + ", height: " + viewFlowLayout.height); + setLayoutParams(viewFlowLayout); + setVisibility(View.VISIBLE); + }); + } + } + + public void saveViewLayout() { + if(this.overlappingLCDMode > 0) + return; + FrameLayout.LayoutParams viewFlowLayout = (FrameLayout.LayoutParams)getLayoutParams(); + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString("settings_lcd_overlapping_mode", Integer.toString(this.overlappingLCDMode)); + editor.putInt("settings_lcd_overlapping_x", viewFlowLayout.leftMargin); + editor.putInt("settings_lcd_overlapping_y", viewFlowLayout.topMargin); + editor.putFloat("settings_lcd_overlapping_scale", bitmapLCD != null && bitmapLCD.getWidth() > 0 ? (float)viewFlowLayout.width / (float)bitmapLCD.getWidth() : 1.0f); + editor.apply(); + } + + private void changeOverlappingLCDMode() { + if(this.overlappingLCDMode == 1) { // Mode Auto + this.overlappingLCDMode = 2; // We change the mode to Manual + SharedPreferences.Editor editor = sharedPreferences.edit(); + editor.putString("settings_lcd_overlapping_mode", Integer.toString(this.overlappingLCDMode)); + editor.apply(); + Context context = getContext(); + if(context != null) + Utils.showAlert(context, context.getString(Utils.resId(context, "string", "message_change_overlapping_lcd_mode_to_manual"))); + } + } + + public void setOverlappingLCDMode(int overlappingLCDMode, boolean isDynamic) { + if(debug) Log.d(TAG, "setOverlappingLCDMode(" + overlappingLCDMode + ")"); + int previousOverlappingLCDMode = this.overlappingLCDMode; + this.overlappingLCDMode = overlappingLCDMode; + if(previousOverlappingLCDMode == 0) { // Off 0 + if(overlappingLCDMode == 1) // Auto 1 + this.updateLayout(this.mainScreenView.viewPanOffsetX, this.mainScreenView.viewPanOffsetY, this.mainScreenView.viewScaleFactorX, this.mainScreenView.viewScaleFactorY); + else if(overlappingLCDMode == 2) // Manual 2 + setVisibility(VISIBLE); + } else if(previousOverlappingLCDMode == 1) { // Auto 1 + if(overlappingLCDMode == 0) // Off 0 + setVisibility(GONE); + } else if(previousOverlappingLCDMode == 2) { // Manual 2 + if(overlappingLCDMode == 0) // Off 0 + setVisibility(GONE); + else if(overlappingLCDMode == 1) // Auto 1 + this.updateLayout(this.mainScreenView.viewPanOffsetX, this.mainScreenView.viewPanOffsetY, this.mainScreenView.viewScaleFactorX, this.mainScreenView.viewScaleFactorY); + } + } +} diff --git a/app/src/main/java/org/emulator/calculator/MainScreenView.java b/app/src/main/java/org/emulator/calculator/MainScreenView.java index 6bbf5ce..4491c54 100644 --- a/app/src/main/java/org/emulator/calculator/MainScreenView.java +++ b/app/src/main/java/org/emulator/calculator/MainScreenView.java @@ -61,7 +61,7 @@ public class MainScreenView extends PanAndScaleView { DisplayMetrics displayMetrics = new DisplayMetrics(); ((Activity)context).getWindowManager().getDefaultDisplay().getMetrics(displayMetrics); - bitmapMainScreen = Bitmap.createBitmap(displayMetrics.widthPixels, displayMetrics.heightPixels, Bitmap.Config.ARGB_8888); + bitmapMainScreen = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); bitmapMainScreen.eraseColor(Color.BLACK); enableZoomKeyboard = false; @@ -153,10 +153,6 @@ public class MainScreenView extends PanAndScaleView { this.setFocusable(true); this.setFocusableInTouchMode(true); - - // This call is necessary, or else the - // draw method will not be called. - setWillNotDraw(false); } // Prevent accidental scroll when taping a calc button @@ -285,6 +281,8 @@ public class MainScreenView extends PanAndScaleView { viewPanOffsetY = translateY; constrainPan(); + if(this.onUpdateLayoutListener != null) + this.onUpdateLayoutListener.run(); return; } } @@ -292,13 +290,19 @@ public class MainScreenView extends PanAndScaleView { } // Else, the screens orientations are the same, so we set the calculator in fullscreen resetViewport(); + + if(this.onUpdateLayoutListener != null) + this.onUpdateLayoutListener.run(); } } - /** - * Draw the score. - * @param canvas The canvas to draw to coming from the View.onDraw() method. - */ + private Runnable onUpdateLayoutListener = null; + + public void setOnUpdateLayoutListener(Runnable onUpdateLayoutListener) { + this.onUpdateLayoutListener = onUpdateLayoutListener; + } + + @Override protected void onCustomDraw(Canvas canvas) { //Log.d(TAG, "onCustomDraw()"); @@ -307,17 +311,13 @@ public class MainScreenView extends PanAndScaleView { canvas.drawBitmap(bitmapMainScreen, 0, 0, paint); } - final int CALLBACK_TYPE_INVALIDATE = 0; - final int CALLBACK_TYPE_WINDOW_RESIZE = 1; - - @SuppressWarnings("unused") public int updateCallback(int type, int param1, int param2, String param3, String param4) { switch (type) { - case CALLBACK_TYPE_INVALIDATE: + case NativeLib.CALLBACK_TYPE_INVALIDATE: //Log.d(TAG, "PAINT updateCallback() postInvalidate()"); postInvalidate(); break; - case CALLBACK_TYPE_WINDOW_RESIZE: + case NativeLib.CALLBACK_TYPE_WINDOW_RESIZE: // New Bitmap size if(bitmapMainScreen == null || bitmapMainScreen.getWidth() != param1 || bitmapMainScreen.getHeight() != param2) { if(debug) Log.d(TAG, "updateCallback() Bitmap.createBitmap(x: " + Math.max(1, param1) + ", y: " + Math.max(1, param2) + ")"); @@ -337,16 +337,11 @@ public class MainScreenView extends PanAndScaleView { if(viewSized) updateLayout(); } - //postInvalidate(); break; } return -1; } - public Bitmap getBitmapMainScreen() { - return bitmapMainScreen; - } - public void setRotationMode(int rotationMode, boolean isDynamic) { this.rotationMode = rotationMode; if(isDynamic) { diff --git a/app/src/main/java/org/emulator/calculator/NativeLib.java b/app/src/main/java/org/emulator/calculator/NativeLib.java index 2c22b01..a3b57ac 100644 --- a/app/src/main/java/org/emulator/calculator/NativeLib.java +++ b/app/src/main/java/org/emulator/calculator/NativeLib.java @@ -24,9 +24,13 @@ public class NativeLib { System.loadLibrary("native-lib"); } - public static native void start(AssetManager mgr, Bitmap bitmapMainScreen, Activity activity, MainScreenView view); + public static final int CALLBACK_TYPE_INVALIDATE = 0; + public static final int CALLBACK_TYPE_WINDOW_RESIZE = 1; + + public static native void start(AssetManager mgr, Activity activity); public static native void stop(); public static native void changeBitmap(Bitmap bitmapMainScreen); + public static native boolean copyLCD(Bitmap bitmapLCD); public static native void draw(); public static native boolean buttonDown(int x, int y); public static native void buttonUp(int x, int y); @@ -71,6 +75,8 @@ public class NativeLib { public static native void setConfiguration(String key, int isDynamic, int intValue1, int intValue2, String stringValue); public static native boolean isPortExtensionPossible(); public static native int getState(); + public static native int getScreenPositionX(); + public static native int getScreenPositionY(); public static native int getScreenWidth(); public static native int getScreenHeight(); } diff --git a/app/src/main/java/org/emulator/calculator/PanAndScaleView.java b/app/src/main/java/org/emulator/calculator/PanAndScaleView.java index bd4cd3c..507eb2e 100644 --- a/app/src/main/java/org/emulator/calculator/PanAndScaleView.java +++ b/app/src/main/java/org/emulator/calculator/PanAndScaleView.java @@ -528,13 +528,13 @@ public class PanAndScaleView extends View { // Keep the panning limits and the image centered. float viewWidth = viewSizeWidth; float viewHeight = viewSizeHeight; - if(viewWidth == 0.0f){ + if(viewWidth == 0.0f) { viewWidth = 1.0f; viewHeight = 1.0f; } float virtualWidth = virtualSizeWidth; float virtualHeight = virtualSizeHeight; - if(virtualWidth == 0.0f){ + if(virtualWidth == 0.0f) { virtualWidth = 1.0f; virtualHeight = 1.0f; } diff --git a/app/src/main/java/org/emulator/forty/eight/MainActivity.java b/app/src/main/java/org/emulator/forty/eight/MainActivity.java index 57aaa06..2f44482 100644 --- a/app/src/main/java/org/emulator/forty/eight/MainActivity.java +++ b/app/src/main/java/org/emulator/forty/eight/MainActivity.java @@ -37,7 +37,9 @@ import android.view.MenuItem; import android.view.SubMenu; import android.view.View; import android.view.ViewGroup; +import android.widget.FrameLayout; import android.widget.ImageButton; +import android.widget.ImageView; import android.widget.TextView; import android.widget.Toast; @@ -56,6 +58,7 @@ import com.google.android.material.navigation.NavigationView; import org.emulator.calculator.InfoActivity; import org.emulator.calculator.InfoWebActivity; +import org.emulator.calculator.LCDOverlappingView; import org.emulator.calculator.MainScreenView; import org.emulator.calculator.NativeLib; import org.emulator.calculator.PrinterSimulator; @@ -69,6 +72,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -92,6 +96,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On private NavigationView navigationView; private DrawerLayout drawer; private MainScreenView mainScreenView; + private LCDOverlappingView lcdOverlappingView; private ImageButton imageButtonMenu; public static final int INTENT_GETOPENFILENAME = 1; @@ -127,6 +132,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On private PrinterSimulator printerSimulator = new PrinterSimulator(); private PrinterSimulatorFragment fragmentPrinterSimulator = new PrinterSimulatorFragment(); + private Bitmap bitmapIcon; @Override @@ -151,23 +157,23 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On mainScreenView = new MainScreenView(this); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) mainScreenView.setStatusBarColor(getWindow().getStatusBarColor()); - mainScreenView.setLayoutParams(new ViewGroup.LayoutParams( + mainScreenContainer.addView(mainScreenView, 0, new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - mainScreenContainer.addView(mainScreenView, 0); + + lcdOverlappingView = new LCDOverlappingView(this, mainScreenView); + lcdOverlappingView.setVisibility(View.GONE); + mainScreenContainer.addView(lcdOverlappingView, 1, new FrameLayout.LayoutParams(0, 0)); imageButtonMenu = findViewById(R.id.button_menu); - imageButtonMenu.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - if(drawer != null) - drawer.openDrawer(GravityCompat.START); - } + imageButtonMenu.setOnClickListener(v -> { + if(drawer != null) + drawer.openDrawer(GravityCompat.START); }); showCalculatorView(false); AssetManager assetManager = getResources().getAssets(); - NativeLib.start(assetManager, mainScreenView.getBitmapMainScreen(), this, mainScreenView); + NativeLib.start(assetManager, this); // By default Port1 is set setPort1Settings(true, true); @@ -278,6 +284,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On editor.putStringSet("MRU", mruLinkedHashMap.keySet()); editor.apply(); + if(lcdOverlappingView != null) + lcdOverlappingView.saveViewLayout(); + super.onStop(); } @@ -842,9 +851,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On new AlertDialog.Builder(MainActivity.this) .setTitle(getString(R.string.message_kml_folder_selection_need_api_lollipop)) .setMessage(getString(R.string.message_kml_folder_selection_need_api_lollipop_description)) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - } + .setPositiveButton(android.R.string.ok, (dialog1, which1) -> { }).show(); } else { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); @@ -880,47 +887,44 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On String[] stringArrayRAMCards = getResources().getStringArray(R.array.ram_cards); new AlertDialog.Builder(MainActivity.this) .setTitle(getResources().getString(R.string.create_ram_card_title)) - .setItems(stringArrayRAMCards, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setType("*/*"); - String sizeTitle = "2mb"; - selectedRAMSize = -1; - switch (which) { - case 0: // 32kb (1 port: 2) - sizeTitle = "32kb"; - selectedRAMSize = 32; - break; - case 1: // 128kb (1 port: 2) - sizeTitle = "128kb"; - selectedRAMSize = 128; - break; - case 2: // 256kb (2 ports: 2,3) - sizeTitle = "256kb"; - selectedRAMSize = 256; - break; - case 3: // 512kb (4 ports: 2 through 5) - sizeTitle = "512kb"; - selectedRAMSize = 512; - break; - case 4: // 1mb (8 ports: 2 through 9) - sizeTitle = "1mb"; - selectedRAMSize = 1024; - break; - case 5: // 2mb (16 ports: 2 through 17) - sizeTitle = "2mb"; - selectedRAMSize = 2048; - break; - case 6: // 4mb (32 ports: 2 through 33) - sizeTitle = "4mb"; - selectedRAMSize = 4096; - break; - } - intent.putExtra(Intent.EXTRA_TITLE, "shared-" + sizeTitle + ".bin"); - startActivityForResult(intent, INTENT_CREATE_RAM_CARD); + .setItems(stringArrayRAMCards, (dialog, which) -> { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("*/*"); + String sizeTitle = "2mb"; + selectedRAMSize = -1; + switch (which) { + case 0: // 32kb (1 port: 2) + sizeTitle = "32kb"; + selectedRAMSize = 32; + break; + case 1: // 128kb (1 port: 2) + sizeTitle = "128kb"; + selectedRAMSize = 128; + break; + case 2: // 256kb (2 ports: 2,3) + sizeTitle = "256kb"; + selectedRAMSize = 256; + break; + case 3: // 512kb (4 ports: 2 through 5) + sizeTitle = "512kb"; + selectedRAMSize = 512; + break; + case 4: // 1mb (8 ports: 2 through 9) + sizeTitle = "1mb"; + selectedRAMSize = 1024; + break; + case 5: // 2mb (16 ports: 2 through 17) + sizeTitle = "2mb"; + selectedRAMSize = 2048; + break; + case 6: // 4mb (32 ports: 2 through 33) + sizeTitle = "4mb"; + selectedRAMSize = 4096; + break; } + intent.putExtra(Intent.EXTRA_TITLE, "shared-" + sizeTitle + ".bin"); + startActivityForResult(intent, INTENT_CREATE_RAM_CARD); }).show(); } @@ -941,12 +945,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On } private void OnMacroStop() { - runOnUiThread(new Runnable() { - @Override - public void run() { - NativeLib.onToolMacroStop(); - updateNavigationDrawerItems(); - } + runOnUiThread(() -> { + NativeLib.onToolMacroStop(); + updateNavigationDrawerItems(); }); } @@ -1003,11 +1004,9 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On new AlertDialog.Builder(this) .setTitle(getString(R.string.message_open_security)) .setMessage(getString(R.string.message_open_security_description)) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - startActivityForResult(intent, INTENT_PICK_KML_FOLDER_FOR_SECURITY); - } + .setPositiveButton(android.R.string.ok, (dialog, which) -> { + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + startActivityForResult(intent, INTENT_PICK_KML_FOLDER_FOR_SECURITY); }).show(); } break; @@ -1059,9 +1058,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On new AlertDialog.Builder(this) .setTitle(getString(R.string.message_open_security_retry)) .setMessage(getString(R.string.message_open_security_retry_description)) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - } + .setPositiveButton(android.R.string.ok, (dialog, which) -> { }).show(); break; } @@ -1122,11 +1119,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On if(url != null && !url.isEmpty()) mruLinkedHashMap.put(url, null); - navigationView.post(new Runnable() { - public void run() { - updateMRU(); - } - }); + navigationView.post(this::updateMRU); } private void makeUriPersistable(Intent data, Uri uri) { @@ -1167,10 +1160,12 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On private void displayFilename(String url) { String displayName = getFilenameFromURL(url); - View header = displayKMLTitle(); - TextView textViewSubtitle = header.findViewById(R.id.nav_header_subtitle); - if(textViewSubtitle != null) - textViewSubtitle.setText(displayName); + View headerView = displayKMLTitle(); + if(headerView != null) { + TextView textViewSubtitle = headerView.findViewById(R.id.nav_header_subtitle); + if (textViewSubtitle != null) + textViewSubtitle.setText(displayName); + } } private String getFilenameFromURL(String url) { @@ -1184,12 +1179,32 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On } private View displayKMLTitle() { + View headerView = null; NavigationView navigationView = findViewById(R.id.nav_view); - View header = navigationView.getHeaderView(0); - TextView textViewTitle = header.findViewById(R.id.nav_header_title); - if(textViewTitle != null) - textViewTitle.setText(NativeLib.getKMLTitle()); - return header; + if (navigationView != null) { + headerView = navigationView.getHeaderView(0); + if (headerView != null) { + TextView textViewTitle = headerView.findViewById(R.id.nav_header_title); + if (textViewTitle != null) + textViewTitle.setText(NativeLib.getKMLTitle()); + changeHeaderIcon(); + } + } + return headerView; + } + + private void changeHeaderIcon() { + NavigationView navigationView = findViewById(R.id.nav_view); + View headerView = navigationView.getHeaderView(0); + if (headerView != null) { + ImageView imageViewIcon = headerView.findViewById(R.id.nav_header_icon); + if (imageViewIcon != null) { + if (bitmapIcon != null) + imageViewIcon.setImageBitmap(bitmapIcon); + else + imageViewIcon.setImageDrawable(null); + } + } } private void showKMLLog() { @@ -1203,14 +1218,21 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On new AlertDialog.Builder(this) .setTitle(getString(R.string.message_kml_script_compilation_result)) .setMessage(kmlLog) - .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - } + .setPositiveButton(android.R.string.ok, (dialog, which) -> { }).show(); } // Method used from JNI! + @SuppressWarnings("unused") + public int updateCallback(int type, int param1, int param2, String param3, String param4) { + + mainScreenView.updateCallback(type, param1, param2, param3, param4); + lcdOverlappingView.updateCallback(type, param1, param2, param3, param4); + return -1; + } + + final int GENERIC_READ = 1; final int GENERIC_WRITE = 2; SparseArray parcelFileDescriptorPerFd = null; @@ -1421,6 +1443,25 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On printerSimulator.write(byteSent); } + @SuppressWarnings("unused") + public synchronized void setKMLIcon(int imageWidth, int imageHeight, byte[] pixels) { + if(imageWidth > 0 && imageHeight > 0 && pixels != null) { + try { + bitmapIcon = Bitmap.createBitmap(imageWidth, imageHeight, Bitmap.Config.ARGB_8888); + ByteBuffer buffer = ByteBuffer.wrap(pixels); + bitmapIcon.copyPixelsFromBuffer(buffer); + } catch (Exception ex) { + // Cannot load the icon + bitmapIcon.recycle(); + bitmapIcon = null; + } + } else if(bitmapIcon != null) { + bitmapIcon.recycle(); + bitmapIcon = null; + } + changeHeaderIcon(); + } + private void setPort1Settings(boolean port1Plugged, boolean port1Writable) { SharedPreferences.Editor editor = sharedPreferences.edit(); editor.putBoolean("settings_port1en", port1Plugged); @@ -1433,7 +1474,7 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On int isDynamicValue = isDynamic ? 1 : 0; if(key == null) { String[] settingKeys = { - "settings_realspeed", "settings_grayscale", "settings_rotation", "settings_auto_layout", "settings_allow_pinch_zoom", + "settings_realspeed", "settings_grayscale", "settings_rotation", "settings_auto_layout", "settings_allow_pinch_zoom", "settings_lcd_overlapping_mode", "settings_hide_bar", "settings_hide_button_menu", "settings_sound_volume", "settings_haptic_feedback", "settings_background_kml_color", "settings_background_fallback_color", "settings_printer_model", "settings_printer_prevent_line_wrap", "settings_macro", @@ -1471,6 +1512,15 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On case "settings_allow_pinch_zoom": mainScreenView.setAllowPinchZoom(sharedPreferences.getBoolean("settings_allow_pinch_zoom", true)); break; + case "settings_lcd_overlapping_mode": + int overlappingLCDMode = 0; + try { + overlappingLCDMode = Integer.parseInt(sharedPreferences.getString("settings_lcd_overlapping_mode", "0")); + } catch (NumberFormatException ex) { + // Catch bad number format + } + lcdOverlappingView.setOverlappingLCDMode(overlappingLCDMode, isDynamic); + break; case "settings_hide_bar": case "settings_hide_bar_status": case "settings_hide_bar_nav": diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml index d1fc952..e0fd7a5 100644 --- a/app/src/main/res/layout/content_main.xml +++ b/app/src/main/res/layout/content_main.xml @@ -1,14 +1,9 @@ - - + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:background="#00808080"> - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/nav_header_main.xml b/app/src/main/res/layout/nav_header_main.xml index c493d4f..938c9db 100644 --- a/app/src/main/res/layout/nav_header_main.xml +++ b/app/src/main/res/layout/nav_header_main.xml @@ -1,29 +1,52 @@ - - + app:layout_constraintBottom_toTopOf="@+id/nav_header_title" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintVertical_chainStyle="packed" /> + android:textAppearance="@style/TextAppearance.AppCompat.Body1" + app:layout_constraintBottom_toBottomOf="@+id/nav_header_icon" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@+id/nav_header_icon" /> - + + + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 02bd6ab..8c72c14 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -36,6 +36,17 @@ 2 + + No Overlapping LCD + Auto + Manual + + + 0 + 1 + 2 + + 32kb (1 port: 2) 128kb (1 port: 2) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fc4fa1f..35986c9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -20,6 +20,7 @@ Settings Open navigation drawer Close navigation drawer + Navigation header Open the main menu New... Open... @@ -84,7 +85,7 @@ Save printer paper as text Save printer paper as image Out of paper error for the graphic printer (max line: %d, max pixel line: %d). - + The overlapping LCD mode has been changed to "Manual". General @@ -102,6 +103,8 @@ Allow to rotate, or force Portrait or Landscape orientation Allow to pinch to zoom + Overlapping LCD mode + Select the overlapping LCD mode. A pan or pinch gesture will change the mode to manual. Hide the status bar Hide the navigation bar diff --git a/app/src/main/res/xml/pref_general.xml b/app/src/main/res/xml/pref_general.xml index 99d900d..757c3fe 100644 --- a/app/src/main/res/xml/pref_general.xml +++ b/app/src/main/res/xml/pref_general.xml @@ -48,6 +48,17 @@ android:key="settings_allow_pinch_zoom" android:title="@string/settings_allow_pinch_zoom_title" android:defaultValue="true" /> + + +