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:
| M | croptool.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)