croptool

Image cropping tool
git clone git://lumidify.org/croptool.git
Log | Files | Refs | README | LICENSE

commit de33336bdf4a91940a960149135a220fdd28ed20
parent 7bd0d9adca219f37e471c3e2c6a80c88532557b8
Author: lumidify <nobody@lumidify.org>
Date:   Sat,  2 Jan 2021 21:14:47 +0100

Exit when no loadable images specified; clean up a bit

Diffstat:
Mcroptool.c | 161++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------------
1 file changed, 102 insertions(+), 59 deletions(-)

diff --git a/croptool.c b/croptool.c @@ -1,4 +1,3 @@ -/* FIXME: show error if there are no loadable images */ /* * Copyright (c) 2021 lumidify <nobody[at]lumidify.org> * @@ -103,6 +102,7 @@ static struct { XColor col1; XColor col2; int cur_col; + Atom wm_delete_msg; Imlib_Image cur_image; Imlib_Updates updates; } state; @@ -119,6 +119,9 @@ static struct { Cursor grab; } cursors; +static void mainloop(void); +static void setup(int argc, char *argv[]); +static void cleanup(void); static void sort_coordinates(int *x0, int *y0, int *x1, int *y1); static void swap(int *a, int *b); static void redraw(); @@ -146,12 +149,7 @@ static void queue_update(int x, int y, int w, int h); static int parse_small_positive_int(const char *str, int *value); int -main(int argc, char **argv) { - XEvent event; - int running = 1; - Atom wm_delete_msg; - XSetWindowAttributes attrs; - XGCValues gcv; +main(int argc, char *argv[]) { char c; while ((c = getopt(argc, argv, "f:w:c:mrp:s:")) != -1) { @@ -198,6 +196,69 @@ main(int argc, char **argv) { fprintf(stderr, "No file given\n"); exit(1); } + setup(argc, argv); + + mainloop(); + + for (int i = 0; i < argc; i++) { + if (state.selections[i].valid) { + print_selection(&state.selections[i], state.filenames[i]); + } + } + + cleanup(); + + return 0; +} + +static void +mainloop(void) { + XEvent event; + int running = 1; + + while (running) { + do { + XNextEvent(state.dpy, &event); + switch (event.type) { + case Expose: + if (RESIZE_REDRAW) + queue_update(event.xexpose.x, event.xexpose.y, + event.xexpose.width, event.xexpose.height); + break; + case ConfigureNotify: + if (RESIZE_REDRAW) + resize_window(event.xconfigure.width, event.xconfigure.height); + break; + case ButtonPress: + if (event.xbutton.button == Button1) + button_press(event); + break; + case ButtonRelease: + if (event.xbutton.button == Button1) + button_release(event); + break; + case MotionNotify: + drag_motion(event); + break; + case KeyPress: + key_press(event); + break; + case ClientMessage: + if (event.xclient.data.l[0] == state.wm_delete_msg) + running = 0; + default: + break; + } + } while (XPending(state.dpy)); + + redraw(); + } +} + +static void +setup(int argc, char *argv[]) { + XSetWindowAttributes attrs; + XGCValues gcv; state.cur_image = NULL; state.selections = malloc(argc * sizeof(struct Selection)); @@ -246,10 +307,9 @@ main(int argc, char **argv) { XAllocColor(state.dpy, state.cm, &state.col2); XSelectInput(state.dpy, state.win, StructureNotifyMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ExposureMask); - XMapWindow(state.dpy, state.win); - wm_delete_msg = XInternAtom(state.dpy, "WM_DELETE_WINDOW", False); - XSetWMProtocols(state.dpy, state.win, &wm_delete_msg, 1); + state.wm_delete_msg = XInternAtom(state.dpy, "WM_DELETE_WINDOW", False); + XSetWMProtocols(state.dpy, state.win, &state.wm_delete_msg, 1); cursors.top = XCreateFontCursor(state.dpy, XC_top_side); cursors.bottom = XCreateFontCursor(state.dpy, XC_bottom_side); @@ -271,51 +331,15 @@ main(int argc, char **argv) { state.updates = imlib_updates_init(); next_picture(0); + /* Only map window here so the program exits immediately if + there are no loadable images, without first opening the + window and closing it again immediately */ + XMapWindow(state.dpy, state.win); redraw(); +} - while (running) { - do { - XNextEvent(state.dpy, &event); - switch (event.type) { - case Expose: - if (RESIZE_REDRAW) - queue_update(event.xexpose.x, event.xexpose.y, - event.xexpose.width, event.xexpose.height); - break; - case ConfigureNotify: - if (RESIZE_REDRAW) - resize_window(event.xconfigure.width, event.xconfigure.height); - break; - case ButtonPress: - if (event.xbutton.button == Button1) - button_press(event); - break; - case ButtonRelease: - if (event.xbutton.button == Button1) - button_release(event); - break; - case MotionNotify: - drag_motion(event); - break; - case KeyPress: - key_press(event); - break; - case ClientMessage: - if (event.xclient.data.l[0] == wm_delete_msg) - running = 0; - default: - break; - } - } while (XPending(state.dpy)); - - redraw(); - } - - for (int i = 0; i < argc; i++) { - if (state.selections[i].valid) { - print_selection(&state.selections[i], argv[i]); - } - } +static void +cleanup(void) { if (state.cur_image) { imlib_context_set_image(state.cur_image); imlib_free_image(); @@ -323,8 +347,6 @@ main(int argc, char **argv) { free(state.selections); XDestroyWindow(state.dpy, state.win); XCloseDisplay(state.dpy); - - return 0; } /* TODO: Allow printing filename without ending */ @@ -446,15 +468,19 @@ redraw(void) { imlib_updates_free(state.updates); state.updates = imlib_updates_init(); + /* wipe the black area around the image */ XSetForeground(state.dpy, state.gc, BlackPixel(state.dpy, state.screen)); XFillRectangle(state.dpy, state.win, state.gc, 0, sel->scaled_h, sel->scaled_w, state.window_h - sel->scaled_h); XFillRectangle(state.dpy, state.win, state.gc, sel->scaled_w, 0, state.window_w - sel->scaled_w, state.window_h); - XColor col = state.cur_col == 1 ? state.col1 : state.col2; - XSetForeground(state.dpy, state.gc, col.pixel); + /* draw the rectangle */ struct Rect rect = sel->rect; - sort_coordinates(&rect.x0, &rect.y0, &rect.x1, &rect.y1); - XDrawRectangle(state.dpy, state.win, state.gc, rect.x0, rect.y0, rect.x1 - rect.x0, rect.y1 - rect.y0); + if (rect.x0 != -200) { + XColor col = state.cur_col == 1 ? state.col1 : state.col2; + XSetForeground(state.dpy, state.gc, col.pixel); + sort_coordinates(&rect.x0, &rect.y0, &rect.x1, &rect.y1); + XDrawRectangle(state.dpy, state.win, state.gc, rect.x0, rect.y0, rect.x1 - rect.x0, rect.y1 - rect.y0); + } } static void @@ -585,6 +611,8 @@ button_release(XEvent event) { state.resizing = 0; state.lock_x = 0; state.lock_y = 0; + /* redraw everything if automatic redrawing of the rectangle + is disabled (so it's redrawn when the mouse is released */ if (!SELECTION_REDRAW) queue_update(0, 0, state.window_w, state.window_h); } @@ -648,6 +676,7 @@ drag_motion(XEvent event) { int x0 = rect->x0, x1 = rect->x1; int y0 = rect->y0, y1 = rect->y1; sort_coordinates(&x0, &y0, &x1, &y1); + /* redraw the old rectangle */ if (SELECTION_REDRAW && (state.moving || state.resizing)) queue_rectangle_redraw(x0, y0, x1, y1); if (state.moving) { @@ -689,6 +718,7 @@ drag_motion(XEvent event) { return; } + /* redraw the new rectangle */ if (SELECTION_REDRAW) queue_rectangle_redraw(rect->x0, rect->y0, rect->x1, rect->y1); } @@ -776,6 +806,15 @@ next_picture(int copy_box) { while (!tmp_image && tmp_cur_selection + 1 < state.num_files) { tmp_cur_selection++; tmp_image = imlib_load_image(state.filenames[tmp_cur_selection]); + if (!tmp_image) { + fprintf(stderr, "Warning: Unable to load image '%s'.\n", + state.filenames[tmp_cur_selection]); + } + } + if (state.cur_selection < 0 && !tmp_image) { + fprintf(stderr, "No loadable images found.\n"); + cleanup(); + exit(1); } if (!tmp_image) return; @@ -793,6 +832,10 @@ last_picture(void) { while (!tmp_image && tmp_cur_selection > 0) { tmp_cur_selection--; tmp_image = imlib_load_image(state.filenames[tmp_cur_selection]); + if (!tmp_image) { + fprintf(stderr, "Warning: Unable to load image '%s'.\n", + state.filenames[tmp_cur_selection]); + } } if (!tmp_image)