aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/imv.c470
-rw-r--r--src/imv.h36
2 files changed, 506 insertions, 0 deletions
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 <getopt.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <SDL2/SDL.h>
+#include <SDL2/SDL_ttf.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",
+ "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 <stdbool.h>
+
+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: */