aboutsummaryrefslogtreecommitdiff
path: root/src/imv.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/imv.c')
-rw-r--r--src/imv.c872
1 files changed, 872 insertions, 0 deletions
diff --git a/src/imv.c b/src/imv.c
new file mode 100644
index 0000000..368973b
--- /dev/null
+++ b/src/imv.c
@@ -0,0 +1,872 @@
+/* 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 <getopt.h>
+#include <limits.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_ttf.h>
+#include <unistd.h>
+
+#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",
+ "shrink to fit",
+ "scale to 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;
+ bool paths_from_stdin;
+ 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;
+ void *stdin_image_data;
+ size_t stdin_image_data_len;
+ char *input_buffer;
+ char *starting_path;
+ struct pollfd stdin_fd;
+
+ SDL_Window *window;
+ SDL_Renderer *renderer;
+ TTF_Font *font;
+ SDL_Texture *background_texture;
+ bool sdl_init;
+ bool ttf_init;
+};
+
+void command_quit(struct imv_list *args, void *data);
+void command_pan(struct imv_list *args, void *data);
+void command_select_rel(struct imv_list *args, void *data);
+void command_select_abs(struct imv_list *args, void *data);
+void command_zoom(struct imv_list *args, void *data);
+void command_remove(struct imv_list *args, void *data);
+void command_fullscreen(struct imv_list *args, void *data);
+void command_overlay(struct imv_list *args, void *data);
+
+static bool setup_window(struct imv *imv);
+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_FULL;
+ imv->cycle_input = true;
+ imv->list_at_exit = false;
+ imv->paths_from_stdin = 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 = strdup("Monospace:24");
+ imv->navigator = imv_navigator_create();
+ imv->loader = imv_loader_create();
+ imv->commands = imv_commands_create();
+ imv->stdin_image_data = NULL;
+ imv->stdin_image_data_len = 0;
+ imv->input_buffer = NULL;
+ imv->starting_path = NULL;
+ imv->window = NULL;
+ imv->renderer = NULL;
+ imv->font = NULL;
+ imv->background_texture = NULL;
+ imv->sdl_init = false;
+ imv->ttf_init = false;
+
+ imv_command_register(imv->commands, "quit", &command_quit);
+ imv_command_register(imv->commands, "pan", &command_pan);
+ imv_command_register(imv->commands, "select_rel", &command_select_rel);
+ imv_command_register(imv->commands, "select_abs", &command_select_abs);
+ imv_command_register(imv->commands, "zoom", &command_zoom);
+ imv_command_register(imv->commands, "remove", &command_remove);
+ imv_command_register(imv->commands, "fullscreen", &command_fullscreen);
+ imv_command_register(imv->commands, "overlay", &command_overlay);
+
+ imv_command_alias(imv->commands, "q", "quit");
+ imv_command_alias(imv->commands, "next", "select_rel 1");
+ imv_command_alias(imv->commands, "previous", "select_rel -1");
+ imv_command_alias(imv->commands, "n", "select_rel 1");
+ imv_command_alias(imv->commands, "p", "select_rel -1");
+
+ 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->stdin_image_data) {
+ free(imv->stdin_image_data);
+ }
+ if(imv->input_buffer) {
+ free(imv->input_buffer);
+ }
+ if(imv->renderer) {
+ SDL_DestroyRenderer(imv->renderer);
+ }
+ if(imv->window) {
+ SDL_DestroyWindow(imv->window);
+ }
+ if(imv->background_texture) {
+ SDL_DestroyTexture(imv->background_texture);
+ }
+ if(imv->font) {
+ TTF_CloseFont(imv->font);
+ }
+ if(imv->ttf_init) {
+ TTF_Quit();
+ }
+ if(imv->sdl_init) {
+ SDL_Quit();
+ }
+ 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;
+
+ /* if no paths are given as args, expect them from stdin */
+ if(argc == 0) {
+ imv->paths_from_stdin = true;
+ } else {
+ /* otherwise, add the paths */
+ bool data_from_stdin = false;
+ for(int i = 0; i < argc; ++i) {
+
+ /* Special case: '-' denotes reading image data from stdin */
+ if(!strcmp("-", argv[i])) {
+ if(imv->paths_from_stdin) {
+ fprintf(stderr, "Can't read paths AND image data from stdin. Aborting.\n");
+ return false;
+ } else if(data_from_stdin) {
+ fprintf(stderr, "Can't read image data from stdin twice. Aborting.\n");
+ return false;
+ }
+ data_from_stdin = true;
+
+ imv->stdin_image_data_len = read_from_stdin(&imv->stdin_image_data);
+ }
+
+ imv_add_path(imv, argv[i]);
+ }
+ }
+
+ if(imv->paths_from_stdin) {
+ imv->stdin_fd.fd = STDIN_FILENO;
+ imv->stdin_fd.events = POLLIN;
+ fprintf(stderr, "Reading paths from stdin...");
+
+ char buf[PATH_MAX];
+ char *stdin_ok;
+ while((stdin_ok = fgets(buf, sizeof(buf), stdin)) != NULL) {
+ size_t len = strlen(buf);
+ if(buf[len-1] == '\n') {
+ buf[--len] = 0;
+ }
+ if(len > 0) {
+ imv_add_path(imv, buf);
+ break;
+ }
+ }
+ if(!stdin_ok) {
+ fprintf(stderr, " no input!\n");
+ return false;
+ }
+ fprintf(stderr, "\n");
+ }
+ return true;
+}
+
+static void check_stdin_for_paths(struct imv *imv)
+{
+ /* check stdin to see if we've been given any new paths to load */
+ if(poll(&imv->stdin_fd, 1, 10) != 1 || imv->stdin_fd.revents & (POLLERR|POLLNVAL)) {
+ fprintf(stderr, "error polling stdin");
+ imv->quit = true;
+ return;
+ }
+
+ if(imv->stdin_fd.revents & (POLLIN|POLLHUP)) {
+ char buf[PATH_MAX];
+ if(fgets(buf, sizeof(buf), stdin) == NULL && ferror(stdin)) {
+ clearerr(stdin);
+ return;
+ }
+ if(feof(stdin)) {
+ imv->paths_from_stdin = false;
+ fprintf(stderr, "done with stdin\n");
+ return;
+ }
+
+ size_t len = strlen(buf);
+ if(buf[len-1] == '\n') {
+ buf[--len] = 0;
+ }
+ if(len > 0) {
+ imv_add_path(imv, buf);
+ imv->need_redraw = true;
+ }
+ }
+}
+
+void imv_add_path(struct imv *imv, const char *path)
+{
+ imv_navigator_add(imv->navigator, path, imv->recursive_load);
+}
+
+bool imv_run(struct imv *imv)
+{
+ if(!setup_window(imv))
+ return false;
+
+ imv->quit = false;
+
+ /* cache current image's dimensions */
+ int iw = 0;
+ int ih = 0;
+
+ /* time keeping */
+ unsigned int last_time = SDL_GetTicks();
+ unsigned int current_time;
+
+ while(!imv->quit) {
+
+ SDL_Event e;
+ while(!imv->quit && SDL_PollEvent(&e)) {
+ handle_event(imv, &e);
+ }
+
+ /* if we're quitting, don't bother drawing any more images */
+ if(imv->quit) {
+ break;
+ }
+
+ /* check if an image failed to load, if so, remove it from our image list */
+ char *err_path = imv_loader_get_error(imv->loader);
+ if(err_path) {
+ imv_navigator_remove(imv->navigator, err_path);
+
+ /* special case: the image came from stdin */
+ if(strncmp(err_path, "-", 2) == 0) {
+ if(imv->stdin_image_data) {
+ free(imv->stdin_image_data);
+ imv->stdin_image_data = NULL;
+ imv->stdin_image_data_len = 0;
+ }
+ fprintf(stderr, "Failed to load image from stdin.\n");
+ }
+ free(err_path);
+ }
+
+ /* Check if navigator wrapped around paths lists */
+ if(!imv->cycle_input && imv_navigator_wrapped(imv->navigator)) {
+ break;
+ }
+
+ /* if the user has changed image, start loading the new one */
+ if(imv_navigator_poll_changed(imv->navigator)) {
+ const char *current_path = imv_navigator_selection(imv->navigator);
+ if(!current_path) {
+ if(!imv->paths_from_stdin) {
+ fprintf(stderr, "No input files left. Exiting.\n");
+ imv->quit = true;
+ }
+ continue;
+ }
+
+ char title[1024];
+ snprintf(title, sizeof(title), "imv - [%i/%i] [LOADING] %s [%s]",
+ imv->navigator->cur_path + 1, imv->navigator->num_paths, current_path,
+ scaling_label[imv->scaling_mode]);
+ imv_viewport_set_title(imv->view, title);
+
+ imv_loader_load(imv->loader, current_path,
+ imv->stdin_image_data, imv->stdin_image_data_len);
+ imv->view->playing = true;
+ }
+
+
+ /* check if a new image is available to display */
+ FIBITMAP *bmp;
+ int is_new_image;
+ if(imv_loader_get_image(imv->loader, &bmp, &is_new_image)) {
+ imv_texture_set_image(imv->texture, bmp);
+ iw = FreeImage_GetWidth(bmp);
+ ih = FreeImage_GetHeight(bmp);
+ FreeImage_Unload(bmp);
+ imv->need_redraw = true;
+ imv->need_rescale += is_new_image;
+ }
+
+ if(imv->need_rescale) {
+ int ww, wh;
+ SDL_GetWindowSize(imv->window, &ww, &wh);
+
+ imv->need_rescale = false;
+ if(imv->scaling_mode == SCALING_NONE ||
+ (imv->scaling_mode == SCALING_DOWN && ww > iw && wh > ih)) {
+ imv_viewport_scale_to_actual(imv->view, imv->texture);
+ } else {
+ imv_viewport_scale_to_window(imv->view, imv->texture);
+ }
+ }
+
+ /* tell the loader time has passed (for gifs) */
+ current_time = SDL_GetTicks();
+
+ /* if we're playing an animated gif, tell the loader how much time has
+ * passed */
+ if(imv->view->playing) {
+ unsigned int dt = current_time - last_time;
+ /* We cap the delta-time to 100 ms so that if imv is asleep for several
+ * seconds or more (e.g. suspended), upon waking up it doesn't try to
+ * catch up all the time it missed by playing through the gif quickly. */
+ if(dt > 100) {
+ dt = 100;
+ }
+ imv_loader_time_passed(imv->loader, dt / 1000.0);
+ }
+
+ /* handle slideshow */
+ if(imv->slideshow_image_duration != 0) {
+ unsigned int dt = current_time - last_time;
+
+ imv->slideshow_time_elapsed += dt;
+ imv->need_redraw = true; /* need to update display */
+ if(imv->slideshow_time_elapsed >= imv->slideshow_image_duration) {
+ imv_navigator_select_rel(imv->navigator, 1);
+ imv->slideshow_time_elapsed = 0;
+ }
+ }
+
+ last_time = current_time;
+
+
+ /* check if the viewport needs a redraw */
+ if(imv_viewport_needs_redraw(imv->view)) {
+ imv->need_redraw = true;
+ }
+
+ if(imv->need_redraw) {
+ render_window(imv);
+ SDL_RenderPresent(imv->renderer);
+ }
+
+ if(imv->paths_from_stdin) {
+ /* check stdin for any more paths */
+ check_stdin_for_paths(imv);
+ } else {
+ /* sleep a little bit so we don't waste CPU time */
+ SDL_Delay(10);
+ }
+ }
+
+ return false;
+}
+
+static bool setup_window(struct imv *imv)
+{
+ if(SDL_Init(SDL_INIT_VIDEO) != 0) {
+ fprintf(stderr, "SDL Failed to Init: %s\n", SDL_GetError());
+ return false;
+ }
+ imv->sdl_init = true;
+
+ /* width and height arbitrarily chosen. Perhaps there's a smarter way to
+ * set this */
+ const int width = 1280;
+ const int height = 720;
+
+ imv->window = SDL_CreateWindow(
+ "imv",
+ SDL_WINDOWPOS_CENTERED,
+ SDL_WINDOWPOS_CENTERED,
+ width, height,
+ SDL_WINDOW_RESIZABLE);
+
+ if(!imv->window) {
+ fprintf(stderr, "SDL Failed to create window: %s\n", SDL_GetError());
+ return false;
+ }
+
+ /* we'll use SDL's built-in renderer, hardware accelerated if possible */
+ imv->renderer = SDL_CreateRenderer(imv->window, -1, 0);
+ if(!imv->renderer) {
+ fprintf(stderr, "SDL Failed to create renderer: %s\n", SDL_GetError());
+ return false;
+ }
+
+ /* use the appropriate resampling method */
+ SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY,
+ imv->nearest_neighbour ? "0" : "1");
+
+ /* construct a chequered background texture */
+ if(imv->background_type == BACKGROUND_CHEQUERED) {
+ imv->background_texture = create_chequered(imv->renderer);
+ }
+
+ /* set up the required fonts and surfaces for displaying the overlay */
+ TTF_Init();
+ imv->ttf_init = true;
+ imv->font = load_font(imv->font_name);
+ if(!imv->font) {
+ fprintf(stderr, "Error loading font: %s\n", TTF_GetError());
+ return false;
+ }
+
+ imv->texture = imv_texture_create(imv->renderer);
+ imv->view = imv_viewport_create(imv->window);
+
+ /* put us in fullscren mode to begin with if requested */
+ if(imv->fullscreen) {
+ imv_viewport_toggle_fullscreen(imv->view);
+ }
+
+ /* start outside of command mode */
+ SDL_StopTextInput();
+
+ return true;
+}
+
+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", imv);
+ 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, imv);
+ 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->quit = true;
+ imv_command_exec(imv->commands, "quit", imv);
+ break;
+ case SDLK_LEFTBRACKET:
+ case SDLK_LEFT:
+ imv_command_exec(imv->commands, "select_rel -1", imv);
+ break;
+ case SDLK_RIGHTBRACKET:
+ case SDLK_RIGHT:
+ imv_command_exec(imv->commands, "select_rel 1", imv);
+ 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", imv);
+ break;
+ case SDLK_k:
+ imv_command_exec(imv->commands, "pan 0 50", imv);
+ break;
+ case SDLK_h:
+ imv_command_exec(imv->commands, "pan 50 0", imv);
+ break;
+ case SDLK_l:
+ imv_command_exec(imv->commands, "pan -50 0", imv);
+ break;
+ case SDLK_x:
+ if(!event->key.repeat) {
+ imv_command_exec(imv->commands, "remove", imv);
+ }
+ break;
+ case SDLK_f:
+ if(!event->key.repeat) {
+ imv_command_exec(imv->commands, "fullscreen", imv);
+ }
+ 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", imv);
+ }
+ 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;
+}
+
+void command_quit(struct imv_list *args, void *data)
+{
+ (void)args;
+ struct imv *imv = data;
+ imv->quit = true;
+}
+
+void command_pan(struct imv_list *args, void *data)
+{
+ struct imv *imv = data;
+ if(args->len != 3) {
+ return;
+ }
+
+ long int x = strtol(args->items[1], NULL, 10);
+ long int y = strtol(args->items[2], NULL, 10);
+
+ imv_viewport_move(imv->view, x, y);
+}
+
+void command_select_rel(struct imv_list *args, void *data)
+{
+ struct imv *imv = data;
+ if(args->len != 2) {
+ return;
+ }
+
+ long int index = strtol(args->items[1], NULL, 10);
+ imv_navigator_select_rel(imv->navigator, index);
+
+ imv->slideshow_time_elapsed = 0;
+}
+
+void command_select_abs(struct imv_list *args, void *data)
+{
+ (void)args;
+ (void)data;
+}
+
+void command_zoom(struct imv_list *args, void *data)
+{
+ (void)args;
+ (void)data;
+}
+
+void command_remove(struct imv_list *args, void *data)
+{
+ (void)args;
+ struct imv *imv = data;
+ char* path = strdup(imv_navigator_selection(imv->navigator));
+ imv_navigator_remove(imv->navigator, path);
+ free(path);
+
+ imv->slideshow_time_elapsed = 0;
+}
+
+void command_fullscreen(struct imv_list *args, void *data)
+{
+ (void)args;
+ struct imv *imv = data;
+ imv_viewport_toggle_fullscreen(imv->view);
+}
+
+void command_overlay(struct imv_list *args, void *data)
+{
+ (void)args;
+ struct imv *imv = data;
+ imv->overlay_enabled = !imv->overlay_enabled;
+ imv->need_redraw = true;
+}
+
+/* vim:set ts=2 sts=2 sw=2 et: */