diff --git a/data/images/ui/pict_export.png b/data/images/ui/pict_export.png
new file mode 100644
index 000000000..180fa7bcf
Binary files /dev/null and b/data/images/ui/pict_export.png differ
diff --git a/docs/CHANGES.txt b/docs/CHANGES.txt
index aa673f3e3..a3a23dd23 100644
--- a/docs/CHANGES.txt
+++ b/docs/CHANGES.txt
@@ -11,15 +11,22 @@ $Id$
2020.July.25 (0.9.25)
* New Features
------------
- * [WIP] GIF export option from Open -> Slideshow dialog.
- * Utilizes "gifenc", public domain by Marcel Rodrigues:
- https://github.com/lecram/gifenc
- * Depends on "libimagequant", from the "pngquant2" project:
- https://github.com/ImageOptim/libimagequant
- (GPL v3 or later, for Free/Libre Open Source Software)
- * Uses XDG user dirs settings, if available, to determine
- where to store them, or "~/Pictures", otherwise.
- "--exportdir" option can be used to override this.
+ * Export drawings:
+ * GIF export option from the Open -> Slideshow dialog.
+ * Utilizes "gifenc", public domain by Marcel Rodrigues:
+ https://github.com/lecram/gifenc
+ * Depends on "libimagequant", from the "pngquant2" project:
+ https://github.com/ImageOptim/libimagequant
+ (GPL v3 or later, for Free/Libre Open Source Software)
+ * WORK IN PROGRESS
+ (Closes https://sourceforge.net/p/tuxpaint/feature-requests/191/)
+ * Single image (PNG) export from the Open dialog.
+ (Closes https://sourceforge.net/p/tuxpaint/feature-requests/192/)
+ * Both utilize XDG user dirs settings, if available, to determine
+ where to store them, or "~/Pictures", otherwise. In those cases,
+ files are stored in a new "TuxPaint" subdirectory.
+ * The "--exportdir" option can be used to override the default
+ location (but a "TuxPaint" subdir. will NOT be placed there).
* Ports & Building
----------------
diff --git a/docs/en/OPTIONS.txt b/docs/en/OPTIONS.txt
index 0278144b5..59077f898 100644
--- a/docs/en/OPTIONS.txt
+++ b/docs/en/OPTIONS.txt
@@ -6,7 +6,7 @@ Options Documentation
Copyright (c) 2002-2020 by various contributors; see AUTHORS.txt
http://www.tuxpaint.org/
- July 25, 2020
+ July 26, 2020
----------------------------------------------------------------------
@@ -581,7 +581,8 @@ Windows Users
Use this option to change where Tux Paint exports files —
single images, or animated GIF slideshows — for external use.
- If you do not override it, the default location is:
+ If you do not override it, the default location is determined
+ as follows:
* Linux & Unix — If available, wherever your desktop
environment is configured for pictures to be stored,
based on your XDG (X Desktop Group) configuration. (Try
@@ -599,6 +600,14 @@ Windows Users
* Windows — TBD!
* macOS — TBD!
+ Note: When the defaults are used, a new "TuxPaint"
+ subdirectory will be created and used. (e.g.,
+ "~/Pictures/TuxPaint") When the "--exportdir" option is used,
+ the exact path specified will be used (no "TuxPaint"
+ subdirectory is created). It is expected that the parent
+ directory exists. (The directory itself will be created, if it
+ doesn't.)
+
Example: exportdir=/home/penguin/TuxPaintExports
datadir=DIRECTORY
@@ -1050,6 +1059,7 @@ Windows Users
--keyboard
--savedir DIRECTORY
--datadir DIRECTORY
+ --exportdir DIRECTORY
--saveover
--saveovernew
--nosave
diff --git a/docs/en/html/OPTIONS.html b/docs/en/html/OPTIONS.html
index f183c94bc..00b725fad 100644
--- a/docs/en/html/OPTIONS.html
+++ b/docs/en/html/OPTIONS.html
@@ -19,7 +19,7 @@ version
Copyright (c) 2002-2020 by various contributors; see AUTHORS.txt
http://www.tuxpaint.org/
-July 25, 2020
+July 26, 2020
@@ -715,7 +715,8 @@ version
Use this option to change where Tux Paint exports files — single
images, or animated GIF slideshows — for external use.
-
If you do not override it, the default location is:
+
If you do not override it, the default location is
+ determined as follows:
- Linux & Unix — If available, wherever your desktop environment
is configured for pictures to be stored, based on your XDG
@@ -737,6 +738,14 @@ version
+ Note: When the defaults are used, a new "TuxPaint"
+ subdirectory will be created and used. (e.g., "~/Pictures/TuxPaint")
+ When the "--exportdir" option is used, the exact path specified
+ will be used (no "TuxPaint" subdirectory is created).
+ It is expected that the parent directory exists. (The directory itself
+ will be created, if it doesn't.)
+
+
Example: exportdir=/home/penguin/TuxPaintExports
@@ -1606,6 +1615,7 @@ version
--keyboard
--savedir DIRECTORY
--datadir DIRECTORY
+ --exportdir DIRECTORY
--saveover
--saveovernew
--nosave
diff --git a/src/tuxpaint.c b/src/tuxpaint.c
index 946319502..83ad45cc0 100644
--- a/src/tuxpaint.c
+++ b/src/tuxpaint.c
@@ -1985,6 +1985,7 @@ static void play_slideshow(int *selected, int num_selected, char *dirname, char
static void draw_selection_digits(int right, int bottom, int n);
static int export_gif(int *selected, int num_selected, char *dirname, char **d_names, char **d_exts, int speed);
+static int export_pict(char * fname);
static char * get_export_filepath(const char * ext);
static void wait_for_sfx(void);
@@ -13989,7 +13990,8 @@ static int do_open(void)
int *d_places;
FILE *fi;
char fname[1024];
- int num_files, i, done, slideshow, update_list, want_erase, cur, which, num_files_in_dirs, j, any_saved_files;
+ int num_files, i, done, slideshow, update_list, want_erase, want_export;
+ int cur, which, num_files_in_dirs, j, any_saved_files;
SDL_Rect dest;
SDL_Event event;
SDLKey key;
@@ -14323,16 +14325,15 @@ static int do_open(void)
/* Let user choose an image: */
/* Instructions for 'Open' file dialog */
- char *freeme = textdir(gettext_noop("Choose the picture you want, " "then click “Open”."));
-
- draw_tux_text(TUX_BORED, freeme, 1);
- free(freeme);
+ char *instructions = textdir(gettext_noop("Choose the picture you want, then click “Open”."));
+ draw_tux_text(TUX_BORED, instructions, 1);
/* NOTE: cur is now set above; if file_id'th file is found, it's
set to that file's index; otherwise, we default to '0' */
update_list = 1;
want_erase = 0;
+ want_export = 0;
done = 0;
slideshow = 0;
@@ -14447,6 +14448,25 @@ static int do_open(void)
SDL_BlitSurface(img_openlabels_back, NULL, screen, &dest);
+ /* "Export" button: */
+
+ dest.x = WINDOW_WIDTH - 96 - 48 - 48 - 48;
+ dest.y = (48 * 7 + 40 + HEIGHTOFFSET) - 48;
+
+ if (d_places[which] != PLACE_STARTERS_DIR && d_places[which] != PLACE_PERSONAL_STARTERS_DIR)
+ SDL_BlitSurface(img_btn_up, NULL, screen, &dest);
+ else
+ SDL_BlitSurface(img_btn_off, NULL, screen, &dest);
+
+ dest.x = WINDOW_WIDTH - 96 - 48 - 48 - 48 + (48 - img_pict_export->w) / 2;
+ dest.y = (48 * 7 + 40 + HEIGHTOFFSET) - 48;
+ SDL_BlitSurface(img_pict_export, NULL, screen, &dest);
+
+ dest.x = WINDOW_WIDTH - 96 - 48 - 48 - 48 + (48 - img_openlabels_pict_export->w) / 2;
+ dest.y = (48 * 7 + 40 + HEIGHTOFFSET) - img_openlabels_pict_export->h;
+ SDL_BlitSurface(img_openlabels_pict_export, NULL, screen, &dest);
+
+
/* "Erase" button: */
dest.x = WINDOW_WIDTH - 96 - 48 - 48;
@@ -14683,6 +14703,16 @@ static int do_open(void)
want_erase = 1;
}
+ else if (event.button.x >= (WINDOW_WIDTH - 96 - 48 - 48 - 48) &&
+ event.button.x < (WINDOW_WIDTH - 48 - 48 - 96) &&
+ event.button.y >= (48 * 7 + 40 + HEIGHTOFFSET) - 48 &&
+ event.button.y < (48 * 7 + 40 + HEIGHTOFFSET) &&
+ d_places[which] != PLACE_STARTERS_DIR && d_places[which] != PLACE_PERSONAL_STARTERS_DIR)
+ {
+ /* Export */
+
+ want_export = 1;
+ }
}
else if (event.type == SDL_MOUSEBUTTONDOWN &&
event.button.button >= 4 && event.button.button <= 5 && wheely)
@@ -14738,8 +14768,9 @@ static int do_open(void)
else if (((event.button.x >= 96 && event.button.x < 96 + 48 + 48) ||
(event.button.x >= (WINDOW_WIDTH - 96 - 48) &&
event.button.x < (WINDOW_WIDTH - 96)) ||
- (event.button.x >= (WINDOW_WIDTH - 96 - 48 - 48) &&
+ (event.button.x >= (WINDOW_WIDTH - 96 - 48 - 48 - 48) &&
event.button.x < (WINDOW_WIDTH - 48 - 96) &&
+ /* Both "Erase" and "Export" only work on our own files... */
d_places[which] != PLACE_STARTERS_DIR &&
d_places[which] != PLACE_PERSONAL_STARTERS_DIR)) &&
event.button.y >= (48 * 7 + 40 + HEIGHTOFFSET) - 48 &&
@@ -14897,6 +14928,21 @@ static int do_open(void)
update_list = 1;
}
}
+
+ if (want_export)
+ {
+ want_export = 0;
+
+ snprintf(fname, sizeof(fname), "saved/%s%s", d_names[which], d_exts[which]);
+ rfname = get_fname(fname, DIR_SAVE);
+ if (export_pict(rfname))
+ do_prompt_snd(PROMPT_PICT_EXPORT_TXT, PROMPT_EXPORT_YES, "", SND_TUXOK, screen->w / 2, screen->h / 2);
+ else
+ do_prompt_snd(PROMPT_PICT_EXPORT_FAILED_TXT, PROMPT_EXPORT_YES, "", SND_YOUCANNOT, screen->w / 2, screen->h / 2);
+
+ draw_tux_text(TUX_BORED, instructions, 1);
+ update_list = 1;
+ }
}
while (!done);
@@ -15011,6 +15057,8 @@ static int do_open(void)
update_canvas(0, 0, WINDOW_WIDTH - 96 - 96, 48 * 7 + 40 + HEIGHTOFFSET);
+
+ free(instructions);
}
@@ -15711,27 +15759,27 @@ static int do_slideshow(void)
}
else
{
- export_successful = export_gif(selected, num_selected, dirname, d_names, d_exts, speed);
+ export_successful = export_gif(selected, num_selected, dirname, d_names, d_exts, speed);
- /* Redraw entire screen, after export: */
- SDL_FillRect(screen, NULL, SDL_MapRGB(canvas->format, 255, 255, 255));
- draw_toolbar();
- draw_colors(COLORSEL_CLOBBER_WIPE);
- draw_none();
-
- /* Show a message depending on success */
- if (export_successful)
- do_prompt_snd(PROMPT_GIF_EXPORT_TXT, PROMPT_EXPORT_YES, "", SND_TUXOK, screen->w / 2, screen->h / 2);
- else
- do_prompt_snd(PROMPT_GIF_EXPORT_FAILED_TXT, PROMPT_EXPORT_YES, "", SND_YOUCANNOT, screen->w / 2, screen->h / 2);
-
- freeme = textdir(TUX_TIP_SLIDESHOW);
- draw_tux_text(TUX_BORED, freeme, 1);
- free(freeme);
-
- SDL_Flip(screen);
-
- update_list = 1;
+ /* Redraw entire screen, after export: */
+ SDL_FillRect(screen, NULL, SDL_MapRGB(canvas->format, 255, 255, 255));
+ draw_toolbar();
+ draw_colors(COLORSEL_CLOBBER_WIPE);
+ draw_none();
+
+ /* Show a message depending on success */
+ if (export_successful)
+ do_prompt_snd(PROMPT_GIF_EXPORT_TXT, PROMPT_EXPORT_YES, "", SND_TUXOK, screen->w / 2, screen->h / 2);
+ else
+ do_prompt_snd(PROMPT_GIF_EXPORT_FAILED_TXT, PROMPT_EXPORT_YES, "", SND_YOUCANNOT, screen->w / 2, screen->h / 2);
+
+ freeme = textdir(TUX_TIP_SLIDESHOW);
+ draw_tux_text(TUX_BORED, freeme, 1);
+ free(freeme);
+
+ SDL_Flip(screen);
+
+ update_list = 1;
}
}
else if (event.button.x >= (WINDOW_WIDTH - 96 - 48) &&
@@ -19054,7 +19102,7 @@ static int do_new_dialog(void)
if (thumbs[num_files] == NULL)
{
fprintf(stderr,
- "\nError: Couldn't create a thumbnail of " "saved image!\n" "%s\n", fname);
+ "\nError: Couldn't create a thumbnail of saved image!\n" "%s\n", fname);
}
num_files++;
@@ -19149,7 +19197,7 @@ static int do_new_dialog(void)
if (thumbs[num_files] == NULL)
{
fprintf(stderr,
- "\nError: Couldn't create a thumbnail of " "saved image!\n" "%s\n", fname);
+ "\nError: Couldn't create a thumbnail of saved image!\n" "%s\n", fname);
}
SDL_FreeSurface(img);
@@ -19682,7 +19730,7 @@ static int do_new_dialog(void)
fprintf(stderr,
"\nWarning: Couldn't load the saved image! (3)\n"
"%s\n"
- "The Simple DirectMedia Layer error that occurred " "was:\n" "%s\n\n", fname, SDL_GetError());
+ "The Simple DirectMedia Layer error that occurred was:\n" "%s\n\n", fname, SDL_GetError());
do_prompt(PROMPT_OPEN_UNOPENABLE_TXT, PROMPT_OPEN_UNOPENABLE_YES, "", 0, 0);
}
@@ -19740,7 +19788,7 @@ static int do_new_dialog(void)
fprintf(stderr,
"\nWarning: Couldn't load the saved image! (4)\n"
"%s\n"
- "The Simple DirectMedia Layer error that occurred " "was:\n" "%s\n\n", fname, SDL_GetError());
+ "The Simple DirectMedia Layer error that occurred was:\n" "%s\n\n", fname, SDL_GetError());
do_prompt(PROMPT_OPEN_UNOPENABLE_TXT, PROMPT_OPEN_UNOPENABLE_YES, "", 0, 0);
}
@@ -22594,6 +22642,7 @@ static void tmpcfg_merge(struct cfginfo *loser, const struct cfginfo *winner)
static void setup_config(char *argv[])
{
char str[128];
+ char * picturesdir;
#ifndef _WIN32
const char *home = getenv("HOME");
@@ -22673,7 +22722,10 @@ static void setup_config(char *argv[])
* __HAIKU__
* __APPLE__
*/
- exportdir = get_xdg_user_dir("PICTURES", "Pictures");
+ picturesdir = get_xdg_user_dir("PICTURES", "Pictures");
+ snprintf(str, sizeof(str), "%s/TuxPaint", picturesdir);
+ free(picturesdir);
+ exportdir = strdup(str);
}
/* Load options from user's own configuration (".rc" / ".cfg") file: */
@@ -24235,10 +24287,11 @@ static void setup(void)
small_font = TuxPaint_Font_OpenFont(PANGO_DEFAULT_FONT, DATA_PREFIX "fonts/default_font.ttf",
#ifdef __APPLE__
- 12 - (only_uppercase * 2));
+ 12 - (only_uppercase * 2)
#else
- 13 - (only_uppercase * 2));
+ 13 - (only_uppercase * 2)
#endif
+ );
if (small_font == NULL)
{
@@ -24793,10 +24846,10 @@ static int trash(char *path)
}
while (!feof(fi))
{
- len = fread(buf, sizeof(buf), 1, fi);
+ len = fread(buf, sizeof(unsigned char), sizeof(buf), fi);
if (len > 0)
{
- fwrite(buf, sizeof(buf), 1, fo);
+ fwrite(buf, sizeof(unsigned char), sizeof(buf), fo);
}
}
fclose(fi);
@@ -25336,6 +25389,12 @@ static int export_gif(int *selected, int num_selected, char *dirname, char **d_n
show_progress_bar(screen);
gif_fname = get_export_filepath("gif");
+ if (gif_fname == NULL)
+ {
+ /* Can't create export dir! Abort! */
+ return FALSE;
+ }
+
/* FIXME: Open GIF */
@@ -25426,9 +25485,69 @@ static int export_gif(int *selected, int num_selected, char *dirname, char **d_n
return(!done);
}
+/**
+ * Copy an image (just the main PNG) from Tux Paint's "saved"
+ * directory to the user's chosen export directory
+ * (e.g., ~/Pictures, or whatever "--exportdir" says).
+ *
+ * Used when exporting a single image from the Open dialog.
+ *
+ * @param char * fname -- full path to the image to export
+ * @return int 1 = success, 0 = failed
+ */
+static int export_pict(char * fname) {
+ FILE * fi, * fo;
+ size_t len;
+ unsigned char buf[1024];
+ char * pict_fname;
+
+ fi = fopen(fname, "rb");
+ if (fi == NULL)
+ {
+ fprintf(stderr, "Cannot export from saved Tux Paint file '%s'\nThe error that occurred was:\n%s\n\n", fname, strerror(errno));
+ return FALSE;
+ }
+
+ pict_fname = get_export_filepath("png");
+ if (pict_fname == NULL)
+ {
+ fclose(fi);
+ return FALSE;
+ }
+
+ fo = fopen(pict_fname, "wb");
+ if (fo == NULL)
+ {
+ fprintf(stderr, "Cannot export to new file '%s'\nThe error that occurred was:\n%s\n\n", pict_fname, strerror(errno));
+ free(pict_fname);
+ fclose(fi);
+ return FALSE;
+ }
+
+ while (!feof(fi))
+ {
+ len = fread(buf, sizeof(unsigned char), sizeof(buf), fi);
+ if (len > 0)
+ {
+ fwrite(buf, sizeof(unsigned char), sizeof(buf), fo);
+ }
+ }
+
+ /* FIXME: Probably good to check for errors here and respond accordingly -bjk 2020.07.26 */
+
+ fclose(fi);
+ fclose(fo);
+
+ free(pict_fname);
+
+ return TRUE;
+}
+
/**
* Returns the name of a new file, located in the user's chosen
- * export directory (e.g., ~/Pictures, or whatever "--exportdir" says).
+ * export directory (e.g., ~/Pictures/TuxPaint, or whatever "--exportdir" says).
+ *
+ * Also ensures that the directory exists, in the first place.
*
* Used when exporting animated GIFs (via "Export GIF" in the
* Open->Slideshow dialog) and static PNGs (via "Export" in the
@@ -25436,7 +25555,8 @@ static int export_gif(int *selected, int num_selected, char *dirname, char **d_n
*
* @param const char * ext -- extnesion of the file (e.g., "png" or "gif")
* @return char * -- filepath for the new file to be created
- * (e.g., /home/username/Pictures/2020072620110100.gif")
+ * (e.g., /home/username/Pictures/TuxPaint/2020072620110100.gif")
+ * Or NULL if the directory cannot be created.
*/
static char * get_export_filepath(const char * ext) {
char *rname;
@@ -25444,6 +25564,14 @@ static char * get_export_filepath(const char * ext) {
char timestamp[16];
time_t t;
+
+ /* Make sure the export dir exists */
+ if (!make_directory(DIR_EXPORT, "", "Can't create export directory"))
+ {
+ return NULL;
+ }
+
+ /* Create a unique filename, within that dir */
t = time(NULL);
strftime(timestamp, sizeof(timestamp), "%Y%m%d%H%M%S", localtime(&t));
snprintf(fname, sizeof(fname), "%s.%s", timestamp, ext);