From f16141f1846d507e02cc1f9ee940a41a07056a4e Mon Sep 17 00:00:00 2001 From: Harry Jeffery Date: Tue, 18 Apr 2017 17:08:30 +0100 Subject: Start work on refactored imv --- src/imv.c | 470 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/imv.h | 36 +++++ 2 files changed, 506 insertions(+) create mode 100644 src/imv.c create mode 100644 src/imv.h (limited to 'src') diff --git a/src/imv.c b/src/imv.c new file mode 100644 index 0000000..c713dbc --- /dev/null +++ b/src/imv.c @@ -0,0 +1,470 @@ +/* Copyright (c) 2017 imv authors + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#include "imv.h" + +#include +#include +#include +#include +#include +#include + +#include "commands.h" +#include "list.h" +#include "loader.h" +#include "texture.h" +#include "navigator.h" +#include "viewport.h" +#include "util.h" + +enum scaling_mode { + SCALING_NONE, + SCALING_DOWN, + SCALING_FULL, + SCALING_MODE_COUNT +}; + +static const char *scaling_label[] = { + "actual size", + "best fit", + "perfect fit" +}; + +enum background_type { + BACKGROUND_SOLID, + BACKGROUND_CHEQUERED, + BACKGROUND_TYPE_COUNT +}; + +struct imv { + bool quit; + bool fullscreen; + bool overlay_enabled; + bool nearest_neighbour; + bool need_redraw; + bool need_rescale; + bool recursive_load; + bool cycle_input; + bool list_at_exit; + enum scaling_mode scaling_mode; + enum background_type background_type; + struct { unsigned char r, g, b; } background_color; + unsigned long slideshow_image_duration; + unsigned long slideshow_time_elapsed; + char *font_name; + struct imv_navigator *navigator; + struct imv_loader *loader; + struct imv_commands *commands; + struct imv_texture *texture; + struct imv_viewport *view; + char *input_buffer; + char *starting_path; + + SDL_Window *window; + SDL_Renderer *renderer; + TTF_Font *font; + SDL_Texture *background_texture; +}; + +static void handle_event(struct imv *imv, SDL_Event *event); +static void render_window(struct imv *imv); + +struct imv *imv_create(void) +{ + struct imv *imv = malloc(sizeof(struct imv)); + imv->quit = false; + imv->fullscreen = false; + imv->overlay_enabled = false; + imv->nearest_neighbour = false; + imv->need_redraw = true; + imv->need_rescale = true; + imv->recursive_load = false; + imv->scaling_mode = SCALING_NONE; + imv->cycle_input = true; + imv->list_at_exit = false; + imv->background_color.r = imv->background_color.g = imv->background_color.b = 0; + imv->slideshow_image_duration = 0; + imv->slideshow_time_elapsed = 0; + imv->font_name = "Monospace:24"; + imv->navigator = imv_navigator_create(); + imv->loader = imv_loader_create(); + imv->commands = imv_commands_create(); + imv->input_buffer = NULL; + imv->starting_path = NULL; + imv->window = NULL; + imv->renderer = NULL; + imv->font = NULL; + imv->background_texture = NULL; + return imv; +} + +void imv_free(struct imv *imv) +{ + free(imv->font_name); + imv_navigator_free(imv->navigator); + imv_loader_free(imv->loader); + imv_commands_free(imv->commands); + if(imv->input_buffer) { + free(imv->input_buffer); + } + free(imv); +} + +bool imv_parse_args(struct imv *imv, int argc, char **argv) +{ + /* Do not print getopt errors */ + opterr = 0; + + char *argp, *ep = *argv; + int o; + + while((o = getopt(argc, argv, "frasSudxhln:b:e:t:")) != -1) { + switch(o) { + case 'f': imv->fullscreen = true; break; + case 'r': imv->recursive_load = true; break; + case 'a': imv->scaling_mode = SCALING_NONE; break; + case 's': imv->scaling_mode = SCALING_DOWN; break; + case 'S': imv->scaling_mode = SCALING_FULL; break; + case 'u': imv->nearest_neighbour = true; break; + case 'd': imv->overlay_enabled = true; break; + case 'x': imv->cycle_input = false; break; + case 'l': imv->list_at_exit = true; break; + case 'n': imv->starting_path = optarg; break; + case 'e': imv->font_name = strdup(optarg); break; + case 'h': + fprintf(stdout, + "imv %s\n" + "See manual for usage information.\n" + "\n" + "Legal:\n" + "This program is free software; you can redistribute it and/or\n" + "modify it under the terms of the GNU General Public License\n" + "as published by the Free Software Foundation; either version 2\n" + "of the License, or (at your option) any later version.\n" + "\n" + "This software uses the FreeImage open source image library.\n" + "See http://freeimage.sourceforge.net for details.\n" + "FreeImage is used under the GNU GPLv2.\n" + , IMV_VERSION); + imv->quit = true; + return true; + case 'b': + if(strcmp("checks", optarg) == 0) { + imv->background_type = BACKGROUND_CHEQUERED; + } else { + imv->background_type = BACKGROUND_SOLID; + argp = (*optarg == '#') ? optarg + 1 : optarg; + uint32_t n = strtoul(argp, &ep, 16); + if(*ep != '\0' || ep - argp != 6 || n > 0xFFFFFF) { + fprintf(stderr, "Invalid hex color: '%s'\n", optarg); + return false; + } + imv->background_color.b = n & 0xFF; + imv->background_color.g = (n >> 8) & 0xFF; + imv->background_color.r = (n >> 16); + } + break; + case 't': + imv->slideshow_image_duration = strtoul(optarg, &argp, 10); + imv->slideshow_image_duration *= 1000; + if (*argp == '.') { + long delay = strtoul(++argp, &ep, 10); + for (int i = 3 - (ep - argp); i; i--) { + delay *= 10; + } + if (delay < 1000) { + imv->slideshow_image_duration += delay; + } else { + imv->slideshow_image_duration = ULONG_MAX; + } + } + if (imv->slideshow_image_duration == ULONG_MAX) { + fprintf(stderr, "Wrong slideshow delay '%s'. Aborting.\n", optarg); + return false; + } + break; + case '?': + fprintf(stderr, "Unknown argument '%c'. Aborting.\n", optopt); + return false; + } + } + + argc -= optind; + argv += optind; + + /* TODO parse paths ? */ + return true; +} + +void imv_add_path(struct imv *imv, const char *path) +{ + (void)imv; + (void)path; +} + +int imv_run(struct imv *imv) +{ + imv->quit = 0; + + while(!imv->quit) { + + SDL_Event e; + while(!imv->quit && SDL_PollEvent(&e)) { + handle_event(imv, &e); + } + + if(imv->need_redraw) { + render_window(imv); + SDL_RenderPresent(imv->renderer); + } + + /* sleep a little bit so we don't waste CPU time */ + SDL_Delay(10); + } + + return 0; +} + +static void handle_event(struct imv *imv, SDL_Event *event) +{ + const int command_buffer_len = 1024; + switch(event->type) { + case SDL_QUIT: + imv_command_exec(imv->commands, "quit", NULL); + break; + + case SDL_TEXTINPUT: + strncat(imv->input_buffer, event->text.text, command_buffer_len - 1); + imv->need_redraw = true; + break; + + case SDL_KEYDOWN: + SDL_ShowCursor(SDL_DISABLE); + + if(imv->input_buffer) { + /* in command mode, update the buffer */ + if(event->key.keysym.sym == SDLK_ESCAPE) { + SDL_StopTextInput(); + free(imv->input_buffer); + imv->input_buffer = NULL; + imv->need_redraw = true; + } else if(event->key.keysym.sym == SDLK_RETURN) { + imv_command_exec(imv->commands, imv->input_buffer, NULL); + SDL_StopTextInput(); + free(imv->input_buffer); + imv->input_buffer = NULL; + imv->need_redraw = true; + } else if(event->key.keysym.sym == SDLK_BACKSPACE) { + const size_t len = strlen(imv->input_buffer); + if(len > 0) { + imv->input_buffer[len - 1] = '\0'; + imv->need_redraw = true; + } + } + + return; + } + + switch (event->key.keysym.sym) { + case SDLK_SEMICOLON: + if(event->key.keysym.mod & KMOD_SHIFT) { + SDL_StartTextInput(); + imv->input_buffer = malloc(command_buffer_len); + imv->input_buffer[0] = '\0'; + imv->need_redraw = true; + } + break; + case SDLK_q: + imv_command_exec(imv->commands, "quit", NULL); + break; + case SDLK_LEFTBRACKET: + case SDLK_LEFT: + imv_command_exec(imv->commands, "select_rel -1", NULL); + break; + case SDLK_RIGHTBRACKET: + case SDLK_RIGHT: + imv_command_exec(imv->commands, "select_rel 1", NULL); + break; + case SDLK_EQUALS: + case SDLK_PLUS: + case SDLK_i: + case SDLK_UP: + imv_viewport_zoom(imv->view, imv->texture, IMV_ZOOM_KEYBOARD, 1); + break; + case SDLK_MINUS: + case SDLK_o: + case SDLK_DOWN: + imv_viewport_zoom(imv->view, imv->texture, IMV_ZOOM_KEYBOARD, -1); + break; + case SDLK_s: + if(!event->key.repeat) { + imv->scaling_mode++; + imv->scaling_mode %= SCALING_MODE_COUNT; + } + /* FALLTHROUGH */ + case SDLK_r: + if(!event->key.repeat) { + imv->need_rescale = true; + imv->need_redraw = true; + } + break; + case SDLK_a: + if(!event->key.repeat) { + imv_viewport_scale_to_actual(imv->view, imv->texture); + } + break; + case SDLK_c: + if(!event->key.repeat) { + imv_viewport_center(imv->view, imv->texture); + } + break; + case SDLK_j: + imv_command_exec(imv->commands, "pan 0 -50", NULL); + break; + case SDLK_k: + imv_command_exec(imv->commands, "pan 0 50", NULL); + break; + case SDLK_h: + imv_command_exec(imv->commands, "pan 50 0", NULL); + break; + case SDLK_l: + imv_command_exec(imv->commands, "pan -50 0", NULL); + break; + case SDLK_x: + if(!event->key.repeat) { + imv_command_exec(imv->commands, "remove", NULL); + } + break; + case SDLK_f: + if(!event->key.repeat) { + imv_command_exec(imv->commands, "fullscreen", NULL); + } + break; + case SDLK_PERIOD: + imv_loader_load_next_frame(imv->loader); + break; + case SDLK_SPACE: + if(!event->key.repeat) { + imv_viewport_toggle_playing(imv->view); + } + break; + case SDLK_p: + if(!event->key.repeat) { + puts(imv_navigator_selection(imv->navigator)); + } + break; + case SDLK_d: + if(!event->key.repeat) { + imv_command_exec(imv->commands, "overlay", NULL); + } + break; + case SDLK_t: + if(event->key.keysym.mod & (KMOD_SHIFT|KMOD_CAPS)) { + if(imv->slideshow_image_duration >= 1000) { + imv->slideshow_image_duration -= 1000; + } + } else { + imv->slideshow_image_duration += 1000; + } + imv->need_redraw = true; + break; + } + break; + case SDL_MOUSEWHEEL: + imv_viewport_zoom(imv->view, imv->texture, IMV_ZOOM_MOUSE, event->wheel.y); + SDL_ShowCursor(SDL_ENABLE); + break; + case SDL_MOUSEMOTION: + if(event->motion.state & SDL_BUTTON_LMASK) { + imv_viewport_move(imv->view, event->motion.xrel, event->motion.yrel); + } + SDL_ShowCursor(SDL_ENABLE); + break; + case SDL_WINDOWEVENT: + imv_viewport_update(imv->view, imv->texture); + break; + } +} + +static void render_window(struct imv *imv) +{ + char title[1024]; + int ww, wh; + SDL_GetWindowSize(imv->window, &ww, &wh); + + /* update window title */ + const char *current_path = imv_navigator_selection(imv->navigator); + int len = snprintf(title, sizeof(title), "imv - [%i/%i] [%ix%i] [%.2f%%] %s [%s]", + imv->navigator->cur_path + 1, imv->navigator->num_paths, imv->texture->width, imv->texture->height, + 100.0 * imv->view->scale, + current_path, scaling_label[imv->scaling_mode]); + if(imv->slideshow_image_duration >= 1000) { + len += snprintf(title + len, sizeof(title) - len, "[%lu/%lus]", + imv->slideshow_time_elapsed / 1000 + 1, imv->slideshow_image_duration / 1000); + } + imv_viewport_set_title(imv->view, title); + + /* first we draw the background */ + if(imv->background_type == BACKGROUND_SOLID) { + /* solid background */ + SDL_SetRenderDrawColor(imv->renderer, + imv->background_color.r, + imv->background_color.g, + imv->background_color.b, + 255); + SDL_RenderClear(imv->renderer); + } else { + /* chequered background */ + int img_w, img_h; + SDL_QueryTexture(imv->background_texture, NULL, NULL, &img_w, &img_h); + /* tile the texture so it fills the window */ + for(int y = 0; y < wh; y += img_h) { + for(int x = 0; x < ww; x += img_w) { + SDL_Rect dst_rect = {x,y,img_w,img_h}; + SDL_RenderCopy(imv->renderer, imv->background_texture, NULL, &dst_rect); + } + } + } + + /* draw our actual texture */ + imv_texture_draw(imv->texture, imv->view->x, imv->view->y, imv->view->scale); + + /* if the overlay needs to be drawn, draw that too */ + if(imv->overlay_enabled && imv->font) { + SDL_Color fg = {255,255,255,255}; + SDL_Color bg = {0,0,0,160}; + imv_printf(imv->renderer, imv->font, 0, 0, &fg, &bg, "%s", + title + strlen("imv - ")); + } + + /* draw command entry bar if needed */ + if(imv->input_buffer && imv->font) { + SDL_Color fg = {255,255,255,255}; + SDL_Color bg = {0,0,0,160}; + imv_printf(imv->renderer, + imv->font, + 0, wh - TTF_FontHeight(imv->font), + &fg, &bg, + ":%s", imv->input_buffer); + } + + /* redraw complete, unset the flag */ + imv->need_redraw = false; +} + +/* vim:set ts=2 sts=2 sw=2 et: */ diff --git a/src/imv.h b/src/imv.h new file mode 100644 index 0000000..7c00bb7 --- /dev/null +++ b/src/imv.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2017 imv authors + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation; either version 2 +of the License, or (at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +*/ + +#ifndef IMV_H +#define IMV_H + +#include + +struct imv; + +struct imv *imv_create(void); +void imv_free(struct imv *imv); + +bool imv_parse_args(struct imv *imv, int argc, char **argv); + +void imv_add_path(struct imv *imv, const char *path); + +int imv_run(struct imv *imv); + +#endif + +/* vim:set ts=2 sts=2 sw=2 et: */ -- cgit v1.2.3