342 lines
14 KiB
C
342 lines
14 KiB
C
/**************************************************************************************************************************************
|
|
* Project: discoverpixy
|
|
* Website: https://github.com/t-moe/discoverpixy
|
|
* Authors: Aaron Schmocker, Timo Lang
|
|
* Institution: BFH Bern University of Applied Sciences
|
|
* File: common/app/screen_photomode_save.c
|
|
*
|
|
* Version History:
|
|
* Date Autor Email SHA Changes
|
|
* 2015-05-16 timolang@gmail.com 62006e0 Documented pixy_helper and implemented/finished photo-mode screens! Snap some shots!
|
|
* 2015-06-07 timolang@gmail.com c87220d Renamed pixy_helper to pixy_frame. Updated docu of appliaction. added doxygen comments to pixy_{frame,control}.h
|
|
*
|
|
**************************************************************************************************************************************/
|
|
|
|
#include "screen_photomode_save.h"
|
|
#include "filesystem.h"
|
|
#include "button.h"
|
|
#include "tft.h"
|
|
#include "touch.h"
|
|
#include "pixy.h"
|
|
#include "pixy_frame.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
|
|
static BUTTON_STRUCT b_back; //Button to navigate back
|
|
static TOUCH_AREA_STRUCT a_area; //Touch area to select the save-file
|
|
|
|
//Callback for when the user presses the "back" button
|
|
static void b_back_cb(void* button)
|
|
{
|
|
gui_screen_back();
|
|
}
|
|
|
|
static int num_files_ok; //number of files into which we can write the image (size, flags ok)
|
|
static enum {init, error, showlist, picking, saving, done} state; //Current state of the screen state machine
|
|
static int fontheight; //The space between one line of text to the next
|
|
static int liststart; //The y-Coordinate of the Start of the File-List
|
|
static const char* picked_file; //The filename picked by the user, to save the image to
|
|
|
|
//Linked list structure to save all files which are suitable for saving.
|
|
typedef struct FILE_LIST_ENTRY_S {
|
|
char* filename; //Name of the file
|
|
struct FILE_LIST_ENTRY_S* next; //Pointer to the next entry in the list or NULL
|
|
} FILE_LIST_ENTRY;
|
|
|
|
static FILE_LIST_ENTRY* files_ok; //Pointer to the head of the list
|
|
|
|
//Callback for when the user selects a file to save the image into
|
|
static void touchCB(void* touchArea, TOUCH_ACTION triggeredAction)
|
|
{
|
|
|
|
int y = touch_get_last_point().y - liststart; //Calculate the y-Coordinate of the touch point relative to the start of the file-list
|
|
int elem = y / fontheight; //Calculate the file index
|
|
|
|
if (elem < 0 | elem >= num_files_ok) {
|
|
return; //Check if the file index is valid (0,1,..,num_files_ok-1)
|
|
}
|
|
|
|
//Search for the corresponding entry in the linked list
|
|
FILE_LIST_ENTRY* current_entry = files_ok; //Start walking through the list, starting by the head of the list
|
|
|
|
for (int i = 0; i < elem; i++) { //Until we have reached the file (index)
|
|
current_entry = current_entry->next; //traverse to the next file
|
|
}
|
|
|
|
picked_file = current_entry->filename; //save the picked filename. It will be used by the statemachine in the main loop
|
|
touch_unregister_area(&a_area); //unregister the touch area, we no longer need it. No more interrupts will be fired.
|
|
state = saving; //Change the state of the statemachine
|
|
}
|
|
|
|
//Text-Lines to show if we have no matching files (num_files_ok==0)
|
|
static const char* nomatch_text [] = {
|
|
"Due to limitations of the filesystem",
|
|
"implementation you can only write to",
|
|
"existing files.",
|
|
"",
|
|
"The files need to have a .bmp",
|
|
"extension and must be at least",
|
|
"189410 bytes (185kb) large.",
|
|
"Unfortunately there were no such",
|
|
"files found in the root directory.",
|
|
"",
|
|
"Please create some files and come",
|
|
"back again.",
|
|
NULL
|
|
};
|
|
|
|
|
|
//Bitmap header for a 318x198x24bit windows bitmap. data starts at 0x7A (= after this header)
|
|
//This header has been taken from a white bitmap saved with gimp.
|
|
//Wikipedia has a pretty good description on the header: http://de.wikipedia.org/wiki/Windows_Bitmap
|
|
static unsigned char bmpheader_data[0x7A] = {
|
|
0x42, 0x4d, 0xe2, 0xe3, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7a, 0x00,
|
|
0x00, 0x00, 0x6c, 0x00, 0x00, 0x00, 0x3e, 0x01, 0x00, 0x00, 0xc6, 0x00,
|
|
0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x68, 0xe3,
|
|
0x02, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x13, 0x0b, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, 0x47, 0x52, 0x73, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00
|
|
};
|
|
|
|
//Callback for when the screen is entered/loaded
|
|
static void enter(void* screen)
|
|
{
|
|
tft_clear(WHITE);
|
|
|
|
|
|
#define X_OFS 5
|
|
|
|
//Back button
|
|
b_back.base.x1 = X_OFS; //Start X of Button
|
|
b_back.base.y1 = 210; //Start Y of Button
|
|
b_back.base.x2 = AUTO; //Auto Calculate X2 with String Width
|
|
b_back.base.y2 = AUTO; //Auto Calculate Y2 with String Height
|
|
b_back.txtcolor = WHITE; //Set foreground color
|
|
b_back.bgcolor = HEX(0xAE1010); //Set background color (Don't take 255 or 0 on at least one channel, to make shadows possible)
|
|
b_back.font = 0; //Select Font
|
|
b_back.text = "Back"; //Set Text (For formatted strings take sprintf)
|
|
b_back.callback = b_back_cb; //Call b_back_cb as Callback
|
|
gui_button_add(&b_back); //Register Button (and run the callback from now on)
|
|
|
|
state = init; //Start with the init state
|
|
fontheight = tft_font_height(0) + 2; //Save the height of the used font, for fast access
|
|
files_ok = NULL; //initialize the linked list with 0 elements
|
|
num_files_ok = 0; //we have zero! elements
|
|
}
|
|
|
|
//Callback for when the screen should be updated
|
|
//This is the main loop of the screen. This method will be called repeatedly
|
|
static void update(void* screen)
|
|
{
|
|
switch (state) {
|
|
case init: { //Init State: The user just entered the screen
|
|
DIRECTORY_STRUCT* dir = filesystem_dir_open("."); //open root directory
|
|
|
|
if (dir == NULL) { //error while opening root directory
|
|
tft_print_line(X_OFS, 5, BLACK, TRANSPARENT, 0, "Error accessing Filesystem");
|
|
state = error;
|
|
break;
|
|
}
|
|
|
|
bool nomatch = true; //whether or not we have zero files which are suitable for saving
|
|
|
|
for (int i = 0; i < dir->num_files; i++) { //walk through all files in the directory
|
|
FILE_STRUCT* file = &(dir->files[i]); //Pointer to the current file/subdirectory
|
|
|
|
//Ignore directories, archives, hidden files, system files and files we cannot write to
|
|
if (file->fattrib & (F_SYS | F_HID | F_ARC | F_DIR | F_RDO)) {
|
|
continue;
|
|
}
|
|
|
|
//ignore files which are not large enough
|
|
if (file->fsize < 189410) {
|
|
continue; //size taken from an example bitmap (318x198x24)
|
|
}
|
|
|
|
nomatch = false; //at least one file matches
|
|
break;
|
|
}
|
|
|
|
if (nomatch) { //not one file is suitable for writing
|
|
int y = 5; //y-Coordinate where to start writing the error text
|
|
int i = 0;
|
|
|
|
while (nomatch_text[i] != NULL) { //for every line in the big error array
|
|
//Write the line's text and go to the next line
|
|
tft_print_line(X_OFS, y + i * fontheight, BLACK, TRANSPARENT, 0, nomatch_text[i]);
|
|
i++;
|
|
}
|
|
|
|
state = error;
|
|
} else { //we have a least one suitable file
|
|
state = showlist;
|
|
}
|
|
|
|
filesystem_dir_close(dir); //free directory struct
|
|
}
|
|
break;
|
|
|
|
case showlist: { //Show List State: Where we load and present the suitable file's to the user in a list
|
|
DIRECTORY_STRUCT* dir2 = filesystem_dir_open("."); //Open the directory again
|
|
|
|
if (dir2 == NULL) {
|
|
return; //Error on opening? This should never happen, since it's handled in the previous state
|
|
}
|
|
|
|
int y = 5; //y-Coordinate where to start drawing/writing text/list-elements
|
|
|
|
tft_print_line(X_OFS, y, BLACK, TRANSPARENT, 0, "Pick a file to save the image to");
|
|
y += fontheight + 5;
|
|
|
|
tft_print_line(X_OFS, y, BLUE, TRANSPARENT, 0, "Name Modified Size");
|
|
y += fontheight;
|
|
|
|
liststart = y; //store the y coordinate of the start of the list away (used in toucharea callback)
|
|
num_files_ok = 0; //we start with 0 matching files
|
|
|
|
FILE_LIST_ENTRY* current_entry = NULL; //We start with an empty list
|
|
|
|
for (int i = 0; i < dir2->num_files && num_files_ok < 10; i++) { //go through all the files of the directory, abort if we have 10 matches
|
|
FILE_STRUCT* file = &(dir2->files[i]);
|
|
|
|
//Ignore directories, archives, hidden files, system files and files we cannot write to
|
|
if (file->fattrib & (F_SYS | F_HID | F_ARC | F_DIR | F_RDO)) {
|
|
continue;
|
|
}
|
|
|
|
//ignore files which are not large enough
|
|
if (file->fsize < 189410) {
|
|
continue; //size taken from an example bitmap (318x198x24)
|
|
}
|
|
|
|
//Print out filename, modified date,time and file size
|
|
tft_print_formatted(X_OFS, y, BLACK,
|
|
TRANSPARENT, 0, "%-16s %02u.%02u.%02u %02u:%02u:%02u %u",
|
|
file->fname,
|
|
file->fdate.day,
|
|
file->fdate.month,
|
|
(file->fdate.year + 1980) % 100,
|
|
file->ftime.hour,
|
|
file->ftime.min,
|
|
file->ftime.sec * 2,
|
|
file->fsize);
|
|
|
|
if (current_entry == NULL) { //The list is empty
|
|
current_entry = malloc(sizeof(FILE_LIST_ENTRY)); //create new entry
|
|
files_ok = current_entry; //assign it to the list head
|
|
} else { //there's a least one entry in the list
|
|
current_entry->next = malloc(sizeof(FILE_LIST_ENTRY)); //append entry to previous entry
|
|
current_entry = current_entry->next; //newly created entry is the current now.
|
|
}
|
|
|
|
current_entry->next = NULL; //we're at the end of the list (for now)
|
|
current_entry->filename = malloc(strlen(file->fname) + 1); //allocate space for the filename + zero-termination
|
|
strcpy(current_entry->filename, file->fname); //copy filename (so that we can close the directory after scanning)
|
|
|
|
//since we have found a suitable file we need to increment the position in the list
|
|
num_files_ok++;
|
|
y += fontheight;
|
|
}
|
|
|
|
//Touch area for file-selection (in the list)
|
|
a_area.hookedActions = PEN_UP; //we're only interested in PEN_UP events
|
|
a_area.x1 = X_OFS; //Left border
|
|
a_area.y1 = liststart; //Start where the list started
|
|
a_area.x2 = 320 - X_OFS; //Right border
|
|
a_area.y2 = liststart + fontheight * num_files_ok; //stop at the end of the list
|
|
a_area.callback = touchCB; //execute our callback when PEN_UP occurs
|
|
touch_register_area(&a_area); //register the touch area and receive events from now on
|
|
|
|
filesystem_dir_close(dir2); //we no longer need the directory struct, since we have our own linked list now
|
|
|
|
state = picking;
|
|
}
|
|
break;
|
|
|
|
case picking: //Picking State: Where we wait on the users file choice
|
|
pixy_service(); //Handle pending pixy events
|
|
//do nothing and wait on user to pick a file
|
|
break;
|
|
|
|
case saving: { //Saving State: Where we save the image to the selected file
|
|
FILE_HANDLE* file = filesystem_file_open(picked_file); //try to open the selected file
|
|
|
|
if (file == NULL) { //opening the file failed
|
|
tft_print_formatted(X_OFS, 190, BLUE, TRANSPARENT, 0, "Could not open %s", picked_file);
|
|
state = error;
|
|
break;
|
|
}
|
|
|
|
filesystem_file_seek(file, 0); //seek to the start of the file (optional?)
|
|
|
|
if (filesystem_file_write(file, bmpheader_data, 0x7A) != F_OK) { //Writing the header failed
|
|
tft_print_formatted(X_OFS, 190, BLUE, TRANSPARENT, 0, "Error while writing to %s", picked_file);
|
|
filesystem_file_close(file);
|
|
state = error;
|
|
break;
|
|
}
|
|
|
|
if (pixy_save_full_frame(file) != 0) { //Writing the imagedata failed
|
|
tft_print_formatted(X_OFS, 190, BLUE, TRANSPARENT, 0, "Error while writing to %s", picked_file);
|
|
filesystem_file_close(file);
|
|
state = error;
|
|
break;
|
|
}
|
|
|
|
//if we reach this point, we have written all data out successfully
|
|
|
|
filesystem_file_close(file); //close/finalize the file
|
|
tft_print_formatted(X_OFS, 190, BLUE, TRANSPARENT, 0, "Image saved to %s", picked_file);
|
|
state = done;
|
|
}
|
|
break;
|
|
|
|
case error: //Error State: Where we show an error message and leave the user no other choice than to click the backbutton
|
|
case done: //Done State: When saving the file was successful
|
|
pixy_service(); //Handle pending pixy events
|
|
//wait on user to click the back button
|
|
break;
|
|
|
|
}
|
|
}
|
|
|
|
//Callback for when the screen is left/unloaded
|
|
static void leave(void* screen)
|
|
{
|
|
gui_button_remove(&b_back); //Remove/Free the back button
|
|
|
|
if (state == picking) { //The user left the screen in the "picking"-phase
|
|
touch_unregister_area(&a_area); //remove the touch area (for the list)
|
|
}
|
|
|
|
if (state == picking || state == saving || state == done) { //the user left the screen after we created the linked list
|
|
//Iterate through the linked list and free all resources
|
|
FILE_LIST_ENTRY* current_entry = files_ok; //start with the list head
|
|
|
|
while (current_entry != NULL) { //while we're not at the end
|
|
FILE_LIST_ENTRY* temp = current_entry->next; //save the next pointer because we free the current element on the next line
|
|
free((void*)(current_entry->filename)); //free filename
|
|
free(current_entry); //free element itself
|
|
current_entry = temp; //advance
|
|
}
|
|
}
|
|
}
|
|
|
|
//Declare screen callbacks
|
|
static SCREEN_STRUCT screen = {
|
|
enter,
|
|
leave,
|
|
update
|
|
};
|
|
|
|
SCREEN_STRUCT* get_screen_photomodesave()
|
|
{
|
|
return &screen;
|
|
}
|