diff -Nru fotoxx-18.01/debian/changelog fotoxx-18.01.1/debian/changelog --- fotoxx-18.01/debian/changelog 2018-01-01 13:20:14.000000000 +0000 +++ fotoxx-18.01.1/debian/changelog 2018-01-02 22:57:29.000000000 +0000 @@ -1,3 +1,10 @@ +fotoxx (18.01.1-1dhor~zesty) zesty; urgency=medium + + * Crash in metadata related functions if the default tag category 'nocatg' + has been deleted. This is now replaced automatically. + + -- Dariusz Duma Tue, 02 Jan 2018 23:56:19 +0100 + fotoxx (18.01-1dhor~zesty) zesty; urgency=medium * The image index format was revised, requiring a new index initialization. diff -Nru fotoxx-18.01/debian-control fotoxx-18.01.1/debian-control --- fotoxx-18.01/debian-control 2017-12-30 20:12:21.000000000 +0000 +++ fotoxx-18.01.1/debian-control 2018-01-02 20:42:33.000000000 +0000 @@ -1,5 +1,5 @@ Package: fotoxx -Version: 18.01 +Version: 18.01.1 Architecture: amd64 Section: graphics Installed-Size: 8948 diff -Nru fotoxx-18.01/doc/changelog fotoxx-18.01.1/doc/changelog --- fotoxx-18.01/doc/changelog 2017-12-30 20:12:21.000000000 +0000 +++ fotoxx-18.01.1/doc/changelog 2018-01-02 20:42:33.000000000 +0000 @@ -1,6 +1,10 @@ Fotoxx Change Log https://kornelix.net ================= +2018 Jan 02 v.18.01.1 +Bug Fix: Crash in metadata related functions if the default tag category +'nocatg' has been deleted. This is now replaced automatically. + 2018 Jan 01 v.18.01 - changes from prior release 17.08 (August 2017) ------------------- Technical Changes: diff -Nru fotoxx-18.01/f.meta.cc fotoxx-18.01.1/f.meta.cc --- fotoxx-18.01/f.meta.cc 2017-12-30 20:12:21.000000000 +0000 +++ fotoxx-18.01.1/f.meta.cc 2018-01-02 20:42:33.000000000 +0000 @@ -6053,9 +6053,10 @@ } // if not already there, add category "nocatg" to the end of the list - - pp1 = tags_deftags[ncats-1]; - if (strmatchN(pp1,"nocatg:",7)) { // already there 18.01 + + pp1 = 0; + if (ncats > 0) pp1 = tags_deftags[ncats-1]; // last tag category bugfix 18.01.1 + if (pp1 && strmatchN(pp1,"nocatg:",7)) { // already 'nocatg' nocat = ncats - 1; nocatcc = strlen(pp1); pp2 = (char *) zmalloc(tagGcc); // re-allocate max. size diff -Nru fotoxx-18.01/fotoxx-18.01.1.cc fotoxx-18.01.1/fotoxx-18.01.1.cc --- fotoxx-18.01/fotoxx-18.01.1.cc 1970-01-01 00:00:00.000000000 +0000 +++ fotoxx-18.01.1/fotoxx-18.01.1.cc 2018-01-02 20:42:33.000000000 +0000 @@ -0,0 +1,4667 @@ +/******************************************************************************** + + Fotoxx edit photos and manage collections + + Copyright 2007-2018 Michael Cornelison + source code URL: https://kornelix.net + contact: kornelix@posteo.de + + 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 3 of the License, or + (at your option) any later version. See https://www.gnu.org/licenses + + 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. + +********************************************************************************* + + main main program, set up defaults + initzfunc initializations, carry out command line options + + delete_event response function for main window delete event + destroy_event response function for main window destroy event + state_event response function for main window fullscreen state change + drop_event response function for main window file drag-drop event + + gtimefunc periodic function + update_Fpanel update status parameters on F window top panel + + paintlock block window updates from main thread or created thread + Fpaint main / drawing window refresh (draw signal response function) + Fpaintnow immediate Fpaint, not callable from threads + Fpaint2 queue Fpaint, callable from threads + Fpaint3 update drawing window section from updated E3 section + Fpaint4 update drawing window section (direct write) + Fpaint3_thread Fpaint3, callable from threads + + mouse_event mouse event response function + mouse_convert convert mouse/window space to image space + m_zoom main window zoom in/out function + KBevent send KB key from dialog to main window + KBpress KB key press event function + win_fullscreen set main window full screen status + win_unfullscreen restore main window to former size + set_mwin_title update the main window title bar + + draw_pixel draw one overlay pixel using image space + erase_pixel erase one pixel + draw_line draw overlay line in image space + erase_line erase line + draw_toplines draw or redraw a set of overlay lines + draw_gridlines draw grid lines over image + add_toptext add to set of overlay text strings + draw_toptext draw set of overlay text strings + erase_toptext remove text from set of overlay text strings + draw_text draw text on window in image space + add_topcircle add a circle to set of overlay circles + draw_topcircles draw the set of overlay circles in window space + erase_topcircles erase the set of overlay circles + draw_mousecircle draw a circle around pointer in image space + draw_mousecircle2 2nd instance for paint/clone tracking circle + draw_mousearc draw an ellipse around pointer in image space + + splcurve_init set up a spline curve drawing area + splcurve_adjust mouse event function to manipulate curve nodes + splcurve_addnode add an anchor point to a curve + splcurve_resize resize drawing area if too small + splcurve_draw draw curve through nodes + splcurve_generate generate x/y table of values from curve + splcurve_yval get curve y-value for given x-value + splcurve_load load curve data from a saved file + splcurve_save save curve data to a file + + edit_setup start an image edit function + CEF_invalid diagnostic + edit_cancel cancle image edit + edit_done finish image edit + edit_fullsize convert preview to full size edit + edit_undo undo current edit (reset) + edit_redo redo current edit + edit_reset reset all edit changes + edit_load_widgets load zdialog widgets and curves from a file + edit_save_widgets save last-used widgets (for [done] buttons) + edit_load_prev_widgets load last-used widgets (for [prev] buttons) + edit_save_last_widgets save last-used widgets (for [prev] buttons) + + m_undo_redo undo/redo depending on mouse button + undo_redo_choice popup menu response function + m_undo restore previous edit in undo/redo stack + m_redo restore next edit in undo/redo stack + undo_all undo all edits for image + redo_all redo all edits for image + save_undo save image in the undo stack + load_undo load image from the undo stack + checkpend check status: edit mods busy block any quiet + takeMouse set mouse event function and special cursor + freeMouse remove mouse event function, set normal cursor + + start_thread start thread running + signal_thread signal thread that work is pending + wait_thread_idle wait for pending work complete + wrapup_thread wait for thread exit or command thread exit + thread_idle_loop wait for pending work, exit if commanded + thread_exit exit thread unconditionally + start_wthread start a working thread per SMP processor + exit_wthread exit a working thread + wait_wthreads wait for all working threads to exit + + save_params save parameters when fotoxx exits + load_params load parameters at fotoxx startup + free_resources free resources for the current image file + sigdiff compare 2 floats with margin of significance + +*********************************************************************************/ + +#define EX // disable extern declarations +#include "fotoxx.h" // (variables in fotoxx.h are defined) + +/********************************************************************************/ + + +// fotoxx main program + +using namespace zfuncs; + +int main(int argc, char *argv[]) +{ + char lang[8] = "", *homedir, *temp; + int Fclone=0, cloxx=0, cloyy=0, cloww=0, clohh=0; + int ii, jj, cc, err; + char cssfile[200]; + STATB statb; + + appimage = getenv("APPIMAGE"); // detect appimage package 17.04 + Arelease = Frelease; // "fotoxx=NN.N" + if (appimage) Arelease = Frelease "-appimage"; // "fotoxx-NN.N-appimage" + printz("%s \n",Arelease); + if (appimage) make_appimage_desktop(); // add desktop menu/launcher 17.08 + + if (argc > 1 && strmatchV(argv[1],"-ver","-v",0)) exit(0); // if fotoxx -ver, exit now + + setenv("GTK_THEME","default",0); // set theme if missing (KDE etc.) 17.08 + setenv("GDK_BACKEND","x11",1); // wayland 18.01 + + printz("initz. clutter and GTK \n"); + if (gtk_clutter_init(&argc,&argv) != CLUTTER_INIT_SUCCESS) { + printz("failure \n"); + exit(1); + } + + homedir = 0; + if (argc > 2 && strmatch(argv[1],"-home")) homedir = argv[2]; // relocate user directory + + zinitapp("fotoxx",homedir); // initz. app directories + + // modify GTK widgets to take less screen space + + snprintf(cssfile,200,"%s/widgets.css",get_zhomedir()); // 18.01 + GtkStyleProvider *provider = (GtkStyleProvider *) gtk_css_provider_new(); + gtk_style_context_add_provider_for_screen(zfuncs::screen,provider,999); + gtk_css_provider_load_from_path(GTK_CSS_PROVIDER(provider),cssfile,0); + + // initialize externals to default values (saved parameters will override) + + strcpy(zfuncs::zappname,Frelease); // app name and version + Ffirsttime = 1; // first startup (params override) + Findexlev = 2; // full image index processing + FMindexlev = 2; // " also if start via file manager + Pindexlev = -1; // no -index command parameter + xxrec_tab = 0; // no image index yet + Nxxrec = Findexvalid = 0; + xmeta_changed = 0; // flag, indexed metadata changed 18.01 + Prelease = zstrdup("unknown"); // prev. fotoxx release (params override) + mwgeom[0] = mwgeom[1] = 100; // default main window geometry + mwgeom[2] = 1200; mwgeom[3] = 800; + *paneltext = 0; // no status bar text + trimww = 1600; // default initial image trim size 17.04 + trimhh = 1000; + trimbuttons[0] = zstrdup("5:4"); trimratios[0] = zstrdup("5:4"); // default trim ratio buttons + trimbuttons[1] = zstrdup("4:3"); trimratios[1] = zstrdup("4:3"); + trimbuttons[2] = zstrdup("8:5"); trimratios[2] = zstrdup("8:5"); + trimbuttons[3] = zstrdup("16:9"); trimratios[3] = zstrdup("16:9"); + trimbuttons[4] = zstrdup("2:1"); trimratios[4] = zstrdup("2:1"); + trimbuttons[5] = zstrdup("gold"); trimratios[5] = zstrdup("1.62:1"); + editresize[0] = 1600; // default initial resize size + editresize[1] = 1200; + currgrid = 0; // default initial grid settings + gridsettings[0][GON] = 0; // grid off + gridsettings[0][GX] = gridsettings[0][GY] = 1; // x/y grid lines enabled + gridsettings[0][GXS] = gridsettings[0][GYS] = 200; // x/y spacing + gridsettings[0][GXC] = gridsettings[0][GYC] = 5; // x/y count + menu_style = zstrdup("icons"); // default menu style (icons only) + FBrgb[0] = FBrgb[1] = FBrgb[2] = 50; // F view background color + GBrgb[0] = GBrgb[1] = GBrgb[2] = 200; // G view background color + MFrgb[0] = MFrgb[1] = MFrgb[2] = 250; // menu font color 18.01 + MBrgb[0] = MBrgb[1] = MBrgb[2] = 80; // menu background color 18.01 + dialog_font = zstrdup("Sans 11"); // default dialog font + iconsize = 32; // default icon size + splcurve_minx = 5; // default curve node separation % + startdisplay = zstrdup("prevF"); // start with previous image 17.08 + Fdragopt = 1; // image drag with mouse + zoomcount = 2; // zooms to reach 2x image size + zoomratio = sqrtf(2); // corresp. zoom ratio + Nshortcuts = 0; // KB shortcut list is empty 18.01 + map_dotsize = 8; // map dot size, mouse capture dist + curr_file = curr_dirk = 0; // no curr. file or directory + copymove_loc = 0; // copy/move target directory + color_palette_file = 0; // user's color palette file 17.04 + thumbdirk = 0; // no thumbnail directory + navi::thumbsize = 256; // gallery default thumbnail size + commandmenu = 0; // command line menu function + commandalbum = 0; // command line album gallery + initial_file = 0; // start with image file or directory + jpeg_def_quality = 90; // default .jpeg save quality + jpeg_1x_quality = 90; // default for 1-time jpeg quality + lens_mm = 35; // pano lens parameter + netmap_source = zstrdup("mapnik"); // default net map source + mapbox_access_key = zstrdup("undefined"); // mapbox map source access key + colormapfile = zstrdup("undefined"); // printer calibration color map + ss_KBkeys = zstrdup("BNPX"); // default slide show control keys 18.01 + + RAWfiletypes = zstrdup(".arw .crw .cr2 .dng .erf .iiq .mef .mos " // some of the known RAW file types + ".mpo .nef .nrw .orf .pef .ptx .raw " // (case is not significant here) + ".rw2 .rwl .srf .srw .sr2 "); + imagefiletypes = zstrdup(".jpg .jpeg .png .tif .tiff .bmp .ico " // supported image file types + ".gif .svg .xpm .tga "); + VIDEOfiletypes = zstrdup(".avi .wmv .mov .flv .mpeg .mp4 " // some known video file types 17.08.3 + ".3gp .3g2 .vob .h264 .webm .ogv "); + + BLACK[0] = BLACK[1] = BLACK[2] = 0; // define RGB colors + WHITE[0] = WHITE[1] = WHITE[2] = 255; + RED[0] = 255; RED[1] = RED[2] = 0; + GREEN[1] = 255; GREEN[0] = GREEN[2] = 0; + BLUE[2] = 255; BLUE[0] = BLUE[1] = 0; + LINE_COLOR = RED; // initial foreground drawing color + + for (int ii = 0; ii < 100; ii++) // static integer values 0-99 + Nval[ii] = ii; + + // file and directory names in user directory /home//.fotoxx/* + + snprintf(index_dirk,199,"%s/image_index",get_zhomedir()); // image index directory + snprintf(tags_defined_file,199,"%s/tags_defined",get_zhomedir()); // defined tags file + snprintf(recentfiles_file,199,"%s/recent_files",get_zhomedir()); // recent files file (index func) + snprintf(saved_areas_dirk,199,"%s/saved_areas",get_zhomedir()); // saved areas directory + snprintf(albums_dirk,199,"%s/albums",get_zhomedir()); // albums directory + snprintf(gallerymem_file,199,"%s/gallery_memory",get_zhomedir()); // recent gallery memory 18.01 + snprintf(saved_curves_dirk,199,"%s/saved_curves",get_zhomedir()); // saved curves directory + snprintf(addtext_dirk,199,"%s/add_text",get_zhomedir()); // add_text directory + snprintf(addline_dirk,199,"%s/add_line",get_zhomedir()); // add_line directory + snprintf(favorites_dirk,199,"%s/favorites",get_zhomedir()); // favorites directory + snprintf(mashup_dirk,199,"%s/mashup",get_zhomedir()); // mashup projects directory + locale_filespec("data","quickstart.html",quickstart_file); // quickstart html file + snprintf(slideshow_dirk,199,"%s/slideshows",get_zhomedir()); // slide show directory + snprintf(slideshow_trans_dirk,199,"%s/slideshow_trans",get_zhomedir()); // slide show transitions + snprintf(pattern_dirk,199,"%s/patterns",get_zhomedir()); // pattern files directory + snprintf(retouch_combo_dirk,199,"%s/retouch_combo",get_zhomedir()); // retouch combo settings directory + snprintf(custom_kernel_dirk,199,"%s/custom_kernel",get_zhomedir()); // custom kernel files directory + snprintf(printer_color_dirk,199,"%s/printer_color",get_zhomedir()); // printer calibration directory + snprintf(edit_scripts_dirk,199,"%s/edit_scripts",get_zhomedir()); // edit script files directory + snprintf(searchresults_file,199,"%s/search_results",get_zhomedir()); // output of image search function + snprintf(maps_dirk,199,"/usr/share/fotoxx-maps/data"); // map files in fotoxx-maps package + snprintf(user_maps_dirk,199,"%s/user_maps",get_zhomedir()); // map files made by user + snprintf(montage_maps_dirk,199,"%s/montage_maps",get_zhomedir()); // montage map files made by user 17.04 + + err = stat(index_dirk,&statb); // create missing directories + if (err) mkdir(index_dirk,0750); + err = stat(saved_areas_dirk,&statb); + if (err) mkdir(saved_areas_dirk,0750); + err = stat(albums_dirk,&statb); + if (err) mkdir(albums_dirk,0750); + err = stat(saved_curves_dirk,&statb); + if (err) mkdir(saved_curves_dirk,0750); + err = stat(addtext_dirk,&statb); + if (err) mkdir(addtext_dirk,0750); + err = stat(addline_dirk,&statb); + if (err) mkdir(addline_dirk,0750); + err = stat(mashup_dirk,&statb); + if (err) mkdir(mashup_dirk,0750); + err = stat(slideshow_dirk,&statb); + if (err) mkdir(slideshow_dirk,0750); + err = stat(slideshow_trans_dirk,&statb); + if (err) mkdir(slideshow_trans_dirk,0750); + err = stat(printer_color_dirk,&statb); + if (err) mkdir(printer_color_dirk,0750); + err = stat(edit_scripts_dirk,&statb); + if (err) mkdir(edit_scripts_dirk,0750); + err = stat(maps_dirk,&statb); + if (err) mkdir(maps_dirk,0750); + err = stat(user_maps_dirk,&statb); + if (err) mkdir(user_maps_dirk,0750); + err = stat(montage_maps_dirk,&statb); + if (err) mkdir(montage_maps_dirk,0750); + + load_params(); // restore parameters from last session + + for (ii = 1; ii < argc; ii++) // command line parameters + { + char *pp = argv[ii]; + + if (strmatch(pp,"-home")) ii++; // -home homedir skip, see above + else if (strmatchV(pp,"-debug","-d",0)) // -d -debug + Fdebug = 1; + else if (strmatchV(pp,"-lang","-l",0) && argc > ii+1) // -l -lang lc_RC language/region code + strncpy0(lang,argv[++ii],7); + else if (strmatchV(pp,"-clone","-c",0) && argc > ii+4) { // -c -clone clone new instance + Fclone = 1; + cloxx = atoi(argv[ii+1]); // window position and size + cloyy = atoi(argv[ii+2]); // passed from parent instance + cloww = atoi(argv[ii+3]); + clohh = atoi(argv[ii+4]); + ii += 4; + } + else if (strmatchV(pp,"-recent","-r",0)) // -r -recent recent files gallery + Frecent = 1; + else if (strmatchV(pp,"-new","-n",0)) // -n -new newest files gallery + Fnew = 1; + else if (strmatchV(pp,"-prev","-p",0)) // -p -prev open previous file + Fprev = 1; + else if (strmatchV(pp,"-blank","-b",0)) // -b -blank start with blank window + Fblank = 1; + else if (strmatch(pp,"-index") && argc > ii+1) { // -index N index level + jj = atoi(argv[++ii]); + if (jj >= 0 && jj <= 2) Pindexlev = jj; // 0/1/2 = none/old/old+new image files + } + else if (strmatchV(pp,"-menu","-m",0) && argc > ii+1) // -m -menu func command line menu func + commandmenu = zstrdup(argv[++ii]); + else if (strmatchV(pp,"-album","-a",0) && argc > ii+1) // -a -album command line album + commandalbum = zstrdup(argv[++ii]); + else { // must be initial file or directory + initial_file = combine_argvs(argc,argv,ii); // combine remaining argv[] elements 17.08 + initial_file = zstrdup(initial_file); // (fix file paths with blanks) + if (*initial_file != '/') { // relative to initial directory + cc = strlen(initial_file); + temp = zstrdup(getcwd(0,0),cc+4); + strncatv(temp,200,"/",initial_file,0); // /initial-directory/initial-file 17.08 + initial_file = temp; + } + break; + } + } + + ZTXinit(lang); // setup locale, translations + setlocale(LC_NUMERIC,"en_US.UTF-8"); // stop comma decimal points + + zsetfont(dialog_font); // set default font for widgets + + build_widgets(); // build window widgets and menus + + if (Fclone) { // clone: open new window + gtk_window_move(MWIN,cloxx+10,cloyy+10); // slightly offset from old window + gtk_window_resize(MWIN,cloww,clohh); + } + else { + gtk_window_move(MWIN,mwgeom[0],mwgeom[1]); // main window geometry + gtk_window_resize(MWIN,mwgeom[2],mwgeom[3]); // defaults or last session params + } + + gtk_widget_show_all(Mwin); + + arrowcursor = gdk_cursor_new_for_display(display,GDK_TOP_LEFT_ARROW); // cursor for selection + dragcursor = gdk_cursor_new_for_display(display,GDK_CROSSHAIR); // cursor for dragging + drawcursor = gdk_cursor_new_for_display(display,GDK_PENCIL); // cursor for drawing lines + blankcursor = gdk_cursor_new_for_display(display,GDK_BLANK_CURSOR); // invisible cursor + + m_viewmode(0,"F"); // set F mode initially + + g_timeout_add(100,initzfunc,0); // initz. call from gtk_main() + gtk_main(); // start processing window events + return 0; +} + + +/********************************************************************************/ + +// Initial function called from gtk_main() at startup. +// This function MUST return 0. + +int initzfunc(void *) +{ + int Fexiftool = 0, Fxdgopen = 0; + int ii, err, npid, contx; + FTYPE ftype; + char *pp, *pp2; + char procfile[20], buff[200]; + char badnews[200], albumfile[200]; + char colorwheelfile[200]; + double freememory, cachememory; + float exifver = 0; + FILE *fid; + STATB statb; + double startsecs; + struct timeb startime2; + + printz("%s \n",Arelease); // print Fotoxx release version + + int v1 = gtk_get_major_version(); // get GTK release version + int v2 = gtk_get_minor_version(); + int v3 = gtk_get_micro_version(); + printz("GTK version: %d.%02d.%02d \n",v1,v2,v3); + + // check that necessary programs are installed + + fid = popen("exiftool -ver","r"); // check exiftool version + if (fid) { + ii = fscanf(fid,"%s",buff); + pclose(fid); // accept period or comma + convSF(buff,exifver); + printz("exiftool version: %.2f \n",exifver); + if (exifver >= 8.60) Fexiftool = 1; + } + + err = shell_quiet("which xdg-open >/dev/null 2>&1"); // check for xdg-open + if (! err) Fxdgopen = 1; + + err = shell_quiet("which rawtherapee >/dev/null 2>&1"); // check for Raw Therapee + if (! err) Frawtherapee = 1; + + err = shell_quiet("which growisofs >/dev/null 2>&1"); // check for growisofs + if (! err) Fgrowisofs = 1; + + Fvideo = 1; + err = shell_quiet("which ffmpeg >/dev/null 2>&1"); // check for ffmpeg 17.08 + if (err) Fvideo = 0; + err = shell_quiet("which totem >/dev/null 2>&1"); // check for totem 17.08 + if (err) Fvideo = 0; + + err = shell_quiet("which hugin >/dev/null 2>&1"); // need all of it 17.08.3 + if (! err) PTtools = 1; + + if (Fexiftool + Fxdgopen < 2) { // check mandatory dependencies + strcpy(badnews,ZTX("Please install missing programs:")); + if (! Fexiftool) strcat(badnews,"\n exiftool (or libimage-exiftool-perl)"); + if (! Fxdgopen) strcat(badnews,"\n xdg-utils"); + zmessageACK(Mwin,badnews); + m_quit(0,0); + } + + if (! Frawtherapee) printz("Raw Therapee not installed \n"); // optional dependencies + if (! Fgrowisofs) printz("growisofs not installed \n"); + if (! Fvideo) printz("ffmpeg and Totem not installed \n"); + if (! PTtools) printz("Panorama Tools (Hugin) not installed \n"); + + // check for first time Fotoxx install or new release install + + if (Ffirsttime) { // new fotoxx install + showz_html(quickstart_file); + Prelease = zstrdup(Frelease); + } + + if (! Ffirsttime && ! strmatch(Prelease,Frelease)) { // Fotoxx release change + zmessageACK(Mwin,"fotoxx new release %s",Frelease); + Prelease = zstrdup(Frelease); + showz_textfile("doc","changelog"); + } + + Ffirsttime = 0; // reset first time flag + + // copy add text/line files from old directories to new // FIXME remove after 18.01 + + snprintf(buff,200,"%s/write_text",get_zhomedir()); + err = stat(buff,&statb); + if (! err) { + snprintf(buff,200,"%s/write_text",get_zhomedir()); + shell_quiet("mv -n %s/* %s ",buff,addtext_dirk); + rmdir(buff); + snprintf(buff,200,"%s/write_line",get_zhomedir()); + shell_quiet("mv -n %s/* %s ",buff,addline_dirk); + rmdir(buff); + } + + // delete fotoxx tempdir files if owner process is no longer running + + contx = 0; + while ((pp = command_output(contx,"find /tmp/fotoxx-* 2>/dev/null",0))) { + pp2 = strchr(pp,'-'); + if (! pp2) continue; + npid = atoi(pp2+1); // pid of fotoxx owner process + snprintf(procfile,20,"/proc/%d",npid); + err = stat(procfile,&statb); + if (! err) continue; // pid is active, keep + shell_quiet("rm -R -f -v %s",pp); // delete + } + + // set up temp directory /tmp/fotoxx-nnnnnn-XXXXXX + + snprintf(tempdir,100,"/tmp/fotoxx-%06d-XXXXXX",getpid()); // use PID + random chars. + pp = mkdtemp(tempdir); + if (! pp) { + zmessageACK(Mwin,"%s \n %s",tempdir,strerror(errno)); + exit(1); + } + strncpy0(tempdir,pp,100); + printz("tempdir: %s \n",tempdir); + + // file name template for undo/redo files + + snprintf(URS_filename,100,"%s/undo_nn",tempdir); // /tmp/fotoxx-nnnnnn/undo_nn + + // check free memory and suggest image size limits + + parseprocfile("/proc/meminfo","MemFree:",&freememory,0); // get amount of free memory + parseprocfile("/proc/meminfo","Cached:",&cachememory,0); + freememory = (freememory + cachememory) / 1024; // megabytes + printz("free memory: %.0f MB \n",freememory); + printz("image size limits for good performance: \n"); + printz(" view: %.0f megapixels \n",(freememory-100)/6); // F + preview, 3 bytes/pixel each + printz(" edit: %.0f megapixels \n",(freememory-100)/54); // + E0/E1/E3/ER, 12 bytes/pixel each + + // miscellaneous + + printz("screen width: %d height: %d \n", // monitor pixel size + zfuncs::monitor_ww,zfuncs::monitor_hh); + + NWT = get_nprocs(); // get SMP CPU count + if (NWT <= 0) NWT = 2; + if (NWT > max_threads) NWT = max_threads; // compile time limit + printz("using %d threads \n",NWT); + + exif_server(0,0,0); // kill orphan exiftool process + + zdialog_inputs("load"); // load saved dialog inputs + zdialog_positions("load"); // load saved dialog positions + gallery_memory("load"); // load recent gallery positions 17.08 + KBshortcuts_load(); // load KB shortcuts from file + + if (! color_palette_file) err = 1; // initialize color palette file 17.08 + else err = stat(color_palette_file,&statb); // if this is missing + if (err) { + snprintf(colorwheelfile,199,"%s/colorwheel.jpg",get_zhomedir()); + err = stat(colorwheelfile,&statb); + if (! err) color_palette_file = zstrdup(colorwheelfile); + } + + // create or update image index file and memory table + + if (xmeta_changed) { // indexed metadata changed 18.01 + printz("indexed metadata list changed, full index required \n"); + index_rebuild(2,0); + } + + else if (Pindexlev >= 0) index_rebuild(Pindexlev,0); // -index command parameter given + else if (initial_file) index_rebuild(FMindexlev,0); // likely a file manager call + else index_rebuild(Findexlev,0); // index level from user setting + + // set current file and gallery from command line if present + + if (topdirks[0]) curr_dirk = zstrdup(topdirks[0]); // default 1st top image directory + else curr_dirk = zstrdup(getenv("HOME")); + + if (initial_file) { // file parameter + printz("initial file: %s \n",initial_file); + ftype = image_file_type(initial_file); + if (ftype == FNF) { // non-existent file + printz(" -invalid file \n"); + zfree(initial_file); + initial_file = 0; + } + else if (ftype == FDIR) { // directory + if (curr_dirk) zfree(curr_dirk); + curr_dirk = initial_file; + initial_file = 0; + gallery(curr_dirk,"init",0); + gallery(0,"sort",-2); // recall sort and position 18.01 + m_viewmode(0,"G"); + } + else if (ftype == IMAGE || ftype == RAW || ftype == VIDEO) { // image file 17.08 + if (curr_dirk) zfree(curr_dirk); + curr_dirk = zstrdup(initial_file); // set current directory from file + pp = strrchr(curr_dirk,'/'); + if (pp) *pp = 0; + f_open(initial_file); + } + else { + printz(" -invalid file \n"); + zfree(initial_file); + initial_file = 0; + } + } + + else if (commandalbum) { // -album parameter + printz("initial album: %s \n",commandalbum); // 17.04 + snprintf(albumfile,200,"%s/albums/%s",get_zhomedir(),commandalbum); + err = stat(albumfile,&statb); + if (err) { + printz("invalid album file: %s \n",commandalbum); + commandalbum = 0; + } + else { + navi::gallerytype = ALBUM; + gallery(albumfile,"initF",0); + gallery(0,"sort",-2); // recall sort and position 18.01 + m_viewmode(0,"G"); + } + } + + else if (Fprev) { // start with previous file + if (last_curr_file && *last_curr_file == '/') + f_open(last_curr_file); + } + + else if (Frecent) // start with recent files gallery + m_recentfiles(0,0); + + else if (Fnew) // start with newest files gallery + m_newfiles(0,"file"); // by file mod date + + else if (Fblank) { // blank window, no gallery + curr_file = curr_dirk = 0; + navi::galleryname = 0; + navi::gallerytype = TNONE; + set_mwin_title(); + } + + // if no command line option, get startup display from user settings + + else if (strmatch(startdisplay,"album")) { + printz("initial album: %s \n",startalbum); // 17.04 + err = stat(startalbum,&statb); + if (err) { + printz("invalid album file: %s \n",startalbum); + commandalbum = 0; + } + else { + navi::gallerytype = ALBUM; + gallery(startalbum,"initF",0); + gallery(0,"sort",-2); // recall sort and position 18.01 + m_viewmode(0,"G"); + } + } + + else if (strmatch(startdisplay,"recent")) // start with recent files gallery + m_recentfiles(0,0); + + else if (strmatch(startdisplay,"newest")) // start with newest files gallery + m_newfiles(0,"file"); // by file mode date + + else if (strmatch(startdisplay,"prevG")) { // start with previous gallery + if (last_gallerytype != TNONE) { + navi::gallerytype = last_gallerytype; + if (last_gallerytype == GDIR) + gallery(last_galleryname,"init",0); + else gallery(last_galleryname,"initF",0); + gallery(0,"sort",-2); // recall sort and position 18.01 + m_viewmode(0,"G"); + } + } + + else if (strmatch(startdisplay,"prevF")) { // start with previous image file + if (last_curr_file && *last_curr_file == '/') + f_open(last_curr_file); + } + + else if (strmatch(startdisplay,"specG")) { // start with specified gallery (dirk) + if (startdirk && *startdirk == '/') { + if (curr_dirk) zfree(curr_dirk); + curr_dirk = zstrdup(startdirk); + gallery(curr_dirk,"init",0); + gallery(0,"sort",-2); // recall sort and position 18.01 + m_viewmode(0,"G"); + } + } + + else if (strmatch(startdisplay,"specF")) // start with given image file + f_open(startfile); + + if (commandmenu) { // startup menu on command line + printz("start menu: %s \n",commandmenu); + for (ii = 0; ii < Nmenus; ii++) { // convert menu name to menu function + if (! menutab[ii].menu) continue; // separator, null menu + if (strmatchcase(commandmenu,ZTX(menutab[ii].menu))) break; + } + if (ii < Nmenus) menutab[ii].func(0,menutab[ii].arg); // call the menu function + } + + save_params(); // save parameters now + + g_timeout_add(20,gtimefunc,0); // start periodic function (20 ms) + + ftime(&startime2); // time to startup and initialize 17.08 + startsecs = startime2.time - startime.time + + 0.001 * (startime2.millitm - startime.millitm); + printz("startup time: %.1f secs.\n",startsecs); + + return 0; // don't come back +} + + +/********************************************************************************/ + +// functions for main window event signals + +int delete_event() // main window closed +{ + int yn; + zdialog *zd = 0; + cchar *title = "unknown"; + + printz("main window delete event \n"); + if (checkpend("mods")) return 1; // allow user bailout + if (zfuncs::zdialog_busy) { + zd = zdialog_list[0]; + if (zd) title = zd->title; + yn = zmessageYN(Mwin,ZTX("Kill active dialog? %s"),title); // allow user bailout 18.01 + if (! yn) return 1; + } + quitxx(); + return 0; +} + +int destroy_event() // main window destroyed +{ + printz("main window destroy event \n"); + quitxx(); + return 0; +} + +int state_event(GtkWidget *, GdkEvent *event) // main window state changed +{ + int state = ((GdkEventWindowState *) event)->new_window_state; // track window fullscreen status + if (state & GDK_WINDOW_STATE_FULLSCREEN) Ffullscreen = 1; + else if (state & GDK_WINDOW_STATE_MAXIMIZED) Ffullscreen = 1; // 17.08 + else Ffullscreen = 0; + return 0; +} + +void drop_event(int mousex, int mousey, char *file) // file drag-drop event +{ + if (! file) return; + printf("drag-drop file: %s \n",file); + f_open(file,0,0,1); + return; +} + + +/********************************************************************************/ + +// Periodic function (20 milliseconds) + +int gtimefunc(void *) +{ + static int domore = 0; + + if (Fshutdown) return 0; // shutdown underway + + if (Fpaintlock && gdkwin) { // thread request to freeze + gdk_window_freeze_updates(gdkwin); // window updates + zadd_locked(Fpaintlock,-1); + } + + if (Fpaintunlock && gdkwin) { // thread request to thaw + gdk_window_thaw_updates(gdkwin); // window updates + zadd_locked(Fpaintunlock,-1); + } + + if (Fpaintrequest && Cdrawin) + gtk_widget_queue_draw(Cdrawin); + + if (zd_thread && zd_thread_event) { // send dialog event from thread + if (! CEF || CEF->thread_status < 2) { + zdialog_send_event(zd_thread,zd_thread_event); // only if thread done or not busy + zd_thread_event = 0; + } + } + + void update_window_area(); // update window area 17.01 + update_window_area(); // pending from a thread + + if (--domore > 0) return 1; // do rest every 200 milliseconds + domore = 10; + + update_Fpanel(); // update top panel information + return 1; +} + + +/********************************************************************************/ + +// update F window top panel with current status information +// called from timer function + +void update_Fpanel() +{ + static double time1 = 0, time2, cpu1 = 0, cpu2, cpuload; + static double pagesize, mem, MB; + char *pp, text1[300], text2[200]; + static char ptext1[300] = ""; + int ww, hh, scale, bpc; + static int ftf = 1; + double file_MB = 1.0 / MEGA * curr_file_size; + static cchar *reduced, *areaactive, *dialogopen; + static cchar *blocked, *modified; + FILE *fid; + + if (! Fpanelshow) return; // panel currently hidden + + if (ftf) { + ftf = 0; + reduced = ZTX("(reduced)"); + areaactive = ZTX("area active"); + dialogopen = ZTX("dialog open"); + blocked = ZTX("blocked"); + modified = "mod"; + pagesize = getpagesize(); // 17.08 + } + + if (Fslideshow) return; + if (FGWM == 'G') goto update_busy; + if (FGWM != 'F') return; + + *text1 = *text2 = 0; + + if (! time1) { + time1 = get_seconds(); + cpu1 = jobtime(); + } + + time2 = get_seconds(); // compute process cpu load % + if (time2 - time1 > 1.0) { // at 1 second intervals + cpu2 = jobtime(); + cpuload = 100.0 * (cpu2 - cpu1) / (time2 - time1); + time1 = time2; + cpu1 = cpu2; + } + + mem = 0; // get memory MB in use 17.08 + fid = fopen("/proc/self/stat","r"); + if (fid) { + pp = fgets(text2,200,fid); + fclose(fid); + if (pp) { + pp = strchr(pp,')'); // closing ')' after (short) filename + if (pp) parseprocrec(pp+1,22,&mem,null); // get memory usage + } + } + + MB = mem * pagesize / MEGA; + + snprintf(text1,300,"CPU %03.0f%c MB %.0f",cpuload,'%',MB); // CPU 23% MB 123 + + if (curr_file && Fpxb) + { + if (E3pxm) { + ww = E3pxm->ww; + hh = E3pxm->hh; + } + else { + ww = Fpxb->ww; + hh = Fpxb->hh; + } + + bpc = curr_file_bpc; + + snprintf(text2,100," %dx%dx%d",ww,hh,bpc); // 2345x1234x16 (preview) 1.56MB 45% + strncatv(text1,300,text2,0); + if (CEF && CEF->Fpreview) strncatv(text1,300," ",reduced,0); + snprintf(text2,100," %.2fM",file_MB); + strncatv(text1,300,text2,0); + scale = Mscale * 100 + 0.5; + snprintf(text2,100," %d%c",scale,'%'); + strncatv(text1,300,text2,0); + + if (URS_pos) { // edit undo/redo stack depth + snprintf(text2,100," %s: %d",ZTX("edits"),URS_pos); + strncatv(text1,300,text2,0); + } + + if (Fmetamod) strncatv(text1,300," ","metadata",0); + } + + else if (Fpxb) { + snprintf(text2,100," %dx%d",Fpxb->ww,Fpxb->hh); + strncatv(text1,300,text2,0); + } + + if (sa_stat == 3) strncatv(text1,300," ",areaactive,0); + if (zfuncs::zdialog_busy) strncatv(text1,300," ",dialogopen,0); + + if (Fblock) strncatv(text1,300," ",blocked,0); // "blocked" + if (CEF && CEF->Fmods) strncatv(text1,300," ",modified,0); // "mod" + if (*paneltext) strncatv(text1,300," ",paneltext,0); // application text + + if (curr_file) { + pp = strrchr(curr_file,'/'); // "filename.jpg" 17.01 + if (pp && Ffullscreen && ! (Ffuncbusy | Fthreadbusy)) { // 17.08 + strncpy0(text2,pp+1,100); + strncatv(text1,300," ",text2,0); + } + } + + if (! strmatch(text1,ptext1)) { // if text changed, update panel bar + gtk_label_set_label(GTK_LABEL(Fpanlab),text1); + gtk_widget_show_all(Fpanel); + strcpy(ptext1,text1); + } + +// Show BUSY label if Ffuncbusy or Fthreadbusy active. +// Show progress counter if Fbusy_goal > 0 +// added to top panel: BUSY NN% + +update_busy: + + static GtkWidget *busylabel = 0, *donelabel = 0; + static cchar *busytext = " BUSY "; + static char donetext[] = "xx% "; + GtkWidget *FGpanel; + int pct; + char nn[4]; + + if (FGWM == 'F') FGpanel = Fpanel; + else if (FGWM == 'G') FGpanel = Gpanel; + else return; + + if (Ffuncbusy | Fthreadbusy) { + if (! busylabel) { + busylabel = gtk_label_new(null); + gtk_label_set_markup(GTK_LABEL(busylabel),busytext); + gtk_box_pack_start(GTK_BOX(FGpanel),busylabel,0,0,5); + } + } + else { + if (busylabel) gtk_widget_destroy(busylabel); + busylabel = 0; + } + + if (Fbusy_done > 0 && Fbusy_done < Fbusy_goal) { // 17.04 + pct = 100 * Fbusy_done / Fbusy_goal; + if (pct > 99) pct = 99; + snprintf(nn,4,"%02d",pct); + strncpy(donetext+33,nn,2); // watch out: hidden dependency + if (! donelabel) { + donelabel = gtk_label_new(""); + gtk_box_pack_start(GTK_BOX(FGpanel),donelabel,0,0,0); + } + gtk_label_set_markup(GTK_LABEL(donelabel),donetext); + } + else { + if (donelabel) gtk_widget_destroy(donelabel); + donelabel = 0; + } + + gtk_widget_show_all(FGpanel); + + return; +} + + +// block image updated by created threads during painting by main thread +// block main thread window updates during changes to image data by created threads +// lock = 1: block updates lock = 0: unblock + +void paintlock(int lock) +{ + static int freezecount = 0; + int nn; + + if (! gdkwin) return; + + if (pthread_equal(pthread_self(),zfuncs::tid_main)) { // main thread caller + if (lock) { + gdk_window_freeze_updates(gdkwin); // freeze window updates + nn = zadd_locked(freezecount,+1); // increment freeze counter + if (nn <= 0) zappcrash("paintlock() freezecount: %d",freezecount); + } + else { + gdk_window_thaw_updates(gdkwin); // thaw window updates + nn = zadd_locked(freezecount,-1); // decrement freeze counter + if (nn < 0) zappcrash("paintlock() freezecount: %d",freezecount); + } + } + + else { // created thread caller + if (lock) { + zadd_locked(Fpaintlock,+1); // set flag for gtimefunc() + while (zadd_locked(Fpaintlock,0)) zsleep(0.01); // wait for freeze done + nn = zadd_locked(freezecount,+1); // increment freeze counter + if (nn <= 0) zappcrash("paintlock() freezecount: %d",freezecount); + } + else { + zadd_locked(Fpaintunlock,+1); // set flag for gtimefunc() + while (zadd_locked(Fpaintunlock,0)) zsleep(0.01); // wait for thaw done + nn = zadd_locked(freezecount,-1); // decrement freeze counter + if (nn < 0) zappcrash("paintlock() freezecount: %d",freezecount); + } + } + + return; +} + + +/********************************************************************************/ + +// GTK3 "draw" function for F and W mode drawing windows. +// Paint window when created, exposed, resized, or image modified (edited). +// Update window image if scale change or window size change. +// Otherwise do only the draw function (triggered from GTK). +// Draw the image section currently within the visible window. +// May NOT be called from threads. See Fpaint2() for threads. + +int Fpaint(GtkWidget *Cdrawin, cairo_t *cr) +{ + PIXBUF *pixbuf; + PXB *pxb1; + GdkRGBA rgba; + static int pdww = 0, pdhh = 0; // prior window size + float wscale, hscale; + int fww, fhh; // current image size at 1x + int mww, mhh; // scaled image size + int morgx, morgy; + int dorgx, dorgy; + int centerx, centery; + int refresh = 0; + int mousex, mousey; // mouse position after zoom + float magx, magy; // mouse drag, magnification ratios + uint8 *pixels, *pix, bgpix[3]; + int rs, px, py; + + if (Fshutdown) return 1; // shutdown underway + + if (! Cdrawin || ! gdkwin || ! Cstate || ! Cstate->fpxb) { // no image + Fpaintrequest = 0; + return 1; + } + + if (Fview360) return 1; // 18.01 + + Dww = gdk_window_get_width(gdkwin); // (new) drawing window size + Dhh = gdk_window_get_height(gdkwin); + if (Dww < 20 || Dhh < 20) return 1; // too small + + if (Dww != pdww || Dhh != pdhh) { // window size changed + refresh = 1; // image refresh needed + pdww = Dww; + pdhh = Dhh; + } + + if (Fpaintrequest) { // window image changed + Fpaintrequest = 0; // window updated as of NOW + refresh = 1; // image refresh needed + if (FGWM == 'F' && (E0pxm || E3pxm)) { // insure F-view and E0 or E3 + if (E3pxm) pxb1 = PXM_PXB_copy(E3pxm); // update fpxb from E0/E3 image + else pxb1 = PXM_PXB_copy(E0pxm); // or use already edited image + PXB_free(Cstate->fpxb); + Cstate->fpxb = pxb1; + } + } + + centerx = (Cstate->morgx + 0.5 * dww) / Cstate->mscale; // center of window, image space + centery = (Cstate->morgy + 0.5 * dhh) / Cstate->mscale; // (before window or scale change) + + fww = Cstate->fpxb->ww; // 1x image size + fhh = Cstate->fpxb->hh; + + if (Cstate->fzoom == 0) { // scale to fit window + wscale = 1.0 * Dww / fww; + hscale = 1.0 * Dhh / fhh; + if (wscale < hscale) Cstate->mscale = wscale; // use greatest ww/hh ratio + else Cstate->mscale = hscale; + if (fww <= Dww && fhh <= Dhh && ! Fblowup) Cstate->mscale = 1.0; // small image 1x unless Fblowup + zoomx = zoomy = 0; // no zoom target + } + else Cstate->mscale = Cstate->fzoom; // scale to fzoom level + + mww = fww * Cstate->mscale; // scaled image size for window + mhh = fhh * Cstate->mscale; + + dww = Dww; // image fitting inside drawing window + if (dww > mww) dww = mww; // image < window size + dhh = Dhh; + if (dhh > mhh) dhh = mhh; + + if (Cstate->mscale != Cstate->pscale) { // scale changed + Cstate->morgx = Cstate->mscale * centerx - 0.5 * dww; // change origin to keep same center + Cstate->morgy = Cstate->mscale * centery - 0.5 * dhh; // (subject to later rules) + Cstate->pscale = Cstate->mscale; // remember scale + refresh = 1; // image refresh needed + } + + if (! Cstate->mpxb) refresh++; // need to make mpxb + + if (refresh) { // image refresh needed + if (Cstate->mpxb) PXB_free(Cstate->mpxb); + if (Cstate->mscale == 1) Cstate->mpxb = PXB_copy(Cstate->fpxb); // fast 1x image + else Cstate->mpxb = PXB_rescale(Cstate->fpxb,mww,mhh); // rescaled image + } + + if ((Mxdrag || Mydrag)) { // pan/scroll via mouse drag + zoomx = zoomy = 0; // no zoom target + magx = 1.0 * (mww - dww) / dww; + magy = 1.0 * (mhh - dhh) / dhh; // needed magnification of mouse movement + if (magx < 1) magx = 1; // retain a minimum speed + if (magy < 1) magy = 1; + + if (Fdragopt == 1) { + Cstate->morgx -= round(Mwdragx); // same direction + Cstate->morgy -= round(Mwdragy); + } + if (Fdragopt == 2) { + Cstate->morgx += round(Mwdragx); // opposite direction + Cstate->morgy += round(Mwdragy); + } + if (Fdragopt == 3) { + Cstate->morgx -= round(Mwdragx * magx); // same direction, magnified + Cstate->morgy -= round(Mwdragy * magy); + } + if (Fdragopt == 4) { + Cstate->morgx += round(Mwdragx * magx); // opposite direction, magnified + Cstate->morgy += round(Mwdragy * magy); + } + } + + if (dww < Dww) Cstate->dorgx = 0.5 * (Dww - dww); // if scaled image < window, + else Cstate->dorgx = 0; // center image in window + if (dhh < Dhh) Cstate->dorgy = 0.5 * (Dhh - dhh); + else Cstate->dorgy = 0; + + if (Fshiftright && dww < Dww-1) Cstate->dorgx = Dww-1 - dww; // shift image to right margin + + if (zoomx || zoomy) { // requested zoom center + Cstate->morgx = Cstate->mscale * zoomx - 0.5 * dww; // corresp. window position within image + Cstate->morgy = Cstate->mscale * zoomy - 0.5 * dhh; + } + + if (Cstate->morgx < 0) Cstate->morgx = 0; // maximize image within window + if (Cstate->morgy < 0) Cstate->morgy = 0; // (no unused margins) + if (Cstate->morgx + dww > mww) Cstate->morgx = mww - dww; + if (Cstate->morgy + dhh > mhh) Cstate->morgy = mhh - dhh; + + if (zoomx || zoomy) { // zoom target + mousex = zoomx * Cstate->mscale - Cstate->morgx + Cstate->dorgx; + mousey = zoomy * Cstate->mscale - Cstate->morgy + Cstate->dorgy; // mouse pointer follows target + move_pointer(Cdrawin,mousex,mousey); + zoomx = zoomy = 0; // reset zoom target + } + + if (zddarkbrite) darkbrite_paint(); // update dark/bright pixels + + rgba.red = 0.00392 * FBrgb[0]; // window background color + rgba.green = 0.00392 * FBrgb[1]; // 0 - 255 --> 0.0 - 1.0 + rgba.blue = 0.00392 * FBrgb[2]; + rgba.alpha = 1.0; + gdk_cairo_set_source_rgba(cr,&rgba); + cairo_paint(cr); + + morgx = Cstate->morgx; // window position in (larger) image + morgy = Cstate->morgy; + dorgx = Cstate->dorgx; + dorgy = Cstate->dorgy; + + if (refresh) { // renew background image + if (BGpixbuf) g_object_unref(BGpixbuf); + BGpixbuf = gdk_pixbuf_new(GDKRGB,0,8,dww,dhh); + pixels = gdk_pixbuf_get_pixels(BGpixbuf); + rs = gdk_pixbuf_get_rowstride(BGpixbuf); + + bgpix[0] = FBrgb[0]; // use background color + bgpix[1] = FBrgb[1]; + bgpix[2] = FBrgb[2]; + + for (py = 0; py < dhh; py++) + for (px = 0; px < dww; px++) { + pix = pixels + py * rs + px * 3; + if (py % 10 < 2 && px % 10 < 2) // with periodic black dots + memset(pix,0,3); + else memcpy(pix,bgpix,3); + } + } + + gdk_cairo_set_source_pixbuf(cr,BGpixbuf,dorgx,dorgy); // paint background image + cairo_paint(cr); + + pixbuf = Cstate->mpxb->pixbuf; // get image section within window + pixbuf = gdk_pixbuf_new_subpixbuf(pixbuf,morgx,morgy,dww,dhh); + gdk_cairo_set_source_pixbuf(cr,pixbuf,dorgx,dorgy); // paint section + cairo_paint(cr); + g_object_unref(pixbuf); + + if (Cstate == &Fstate) { // view mode is image + if (Ntoplines) draw_toplines(1,cr); // draw line overlays + if (gridsettings[currgrid][GON]) draw_gridlines(cr); // draw grid lines + if (Ntoptext) draw_toptext(cr); // draw text strings + if (Ntopcircles) draw_topcircles(cr); // draw circles + if (Fshowarea) sa_show(1,cr); // draw select area outline + if (refresh && zdbrdist) m_show_brdist(0,0); // update brightness dist. + } + + if (Cstate == &Wstate) // view mode is world maps + filemap_paint_dots(); + + return 1; +} + + +/********************************************************************************/ + +// Repaint modified image synchrounously. +// May NOT be called from threads. + +void Fpaintnow() +{ + if (! Cdrawin || ! gdkwin || ! Cstate || ! Cstate->fpxb) { // no image + printf("Fpaintnow(), no image \n"); + return; + } + + Fpaintrequest = 1; // request repaint of changed image + gtk_widget_queue_draw(Cdrawin); + while (Fpaintrequest) { + zsleep(0.001); + zmainloop(); + } + + return; +} + + +// Cause (modified) output image to get repainted soon. +// Fpaint() will be called by gtimefunc() next timer cycle. +// MAY be called from threads. + +void Fpaint2() +{ + Fpaintrequest = 1; // request repaint of changed image + return; +} + + +// Update a section of Fpxb and Mpxb from an updated section of E3pxm, +// then update the corresponding section of the drawing window. +// This avoids a full image refresh, E3pxm > fpxb > mpxb > drawing window. +// px3, py3, ww3, hh3: modified section within E3pxm to be propagated. +// May NOT be called from threads. + +void Fpaint3(int px3, int py3, int ww3, int hh3, cairo_t *cr) +{ + int crflag = 0; + + if (! cr) { + cr = draw_context_create(gdkwin,draw_context); + crflag = 1; + } + + PXM_PXB_update(E3pxm,Fpxb,px3,py3,ww3,hh3); // E3pxm > Fpxb, both 1x scale + PXB_PXB_update(Fpxb,Mpxb,px3,py3,ww3,hh3); // Fpxb > Mpxb, scaled up or down + Fpaint4(px3,py3,ww3,hh3,cr); // update drawing window from Mpxb + + if (crflag) draw_context_destroy(draw_context); + return; +} + + +// Do the same using E0pxm instead of E3pxm + +void Fpaint0(int px3, int py3, int ww3, int hh3, cairo_t *cr) +{ + int crflag = 0; + + if (! cr) { + cr = draw_context_create(gdkwin,draw_context); + crflag = 1; + } + + PXM_PXB_update(E0pxm,Fpxb,px3,py3,ww3,hh3); + PXB_PXB_update(Fpxb,Mpxb,px3,py3,ww3,hh3); + Fpaint4(px3,py3,ww3,hh3,cr); + + if (crflag) draw_context_destroy(draw_context); + return; +} + + +// Repaint a section of the Mpxb image in the visible window. +// px3, py3, ww3, hh3: area to be repainted (in 1x image space). +// May NOT be called from threads. +// Writes directly on the window (cairo pixbuf paint). + +void Fpaint4(int px3, int py3, int ww3, int hh3, cairo_t *cr) +{ + PIXBUF *pixbuf, *bgpixbuf; + int px1, py1, ww1, hh1; + int px2, py2, ww2, hh2; + int crflag = 0; + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->fpxb) return; // no image + + px2 = Mscale * px3 - 2; // 1x image space to Mpxb space + py2 = Mscale * py3 - 2; // (expanded a few pixels) + ww2 = Mscale * ww3 + 2 / Mscale + 4; + hh2 = Mscale * hh3 + 2 / Mscale + 4; + + if (px2 < Morgx) { // reduce to currently visible window + ww2 = ww2 - (Morgx - px2); + px2 = Morgx; + } + + if (py2 < Morgy) { + hh2 = hh2 - (Morgy - py2); + py2 = Morgy; + } + + if (px2 + ww2 >= Mpxb->ww) ww2 = Mpxb->ww - px2 - 1; // stay within image bugfix 17.04.3 + if (py2 + hh2 >= Mpxb->hh) hh2 = Mpxb->hh - py2 - 1; + if (ww2 <= 0 || hh2 <= 0) return; + + px1 = px2 - Morgx + Dorgx; // corresp. position in drawing window + py1 = py2 - Morgy + Dorgy; + + if (px1 + ww2 >= Dww) ww2 = Dww - px1 - 1; // stay within window bugfix 17.04.3 + if (py1 + hh2 >= Dhh) hh2 = Dhh - py1 - 1; + if (ww2 <= 0 || hh2 <= 0) return; + + pixbuf = gdk_pixbuf_new_subpixbuf(Mpxb->pixbuf,px2,py2,ww2,hh2); // Mpxb area to paint + if (! pixbuf) { + printz("Fpaint4() pixbuf failure \n"); + return; + } + + px2 = px1; // corresp. position in drawing window + py2 = py1; + + px1 = px2 - Dorgx; // corresp. position in background image + py1 = py2 - Dorgy; + + paintlock(1); + + if (! cr) { + cr = draw_context_create(gdkwin,draw_context); + crflag = 1; + } + + if (Mpxb->nc > 3) { // alpha channel present + ww1 = ww2; // draw background image to area + hh1 = hh2; + if (px1 + ww1 > dww) ww1 = dww - px1; + if (py1 + hh1 > dhh) hh1 = dhh - py1; + if (ww1 > 0 && hh1 > 0) { + bgpixbuf = gdk_pixbuf_new_subpixbuf(BGpixbuf,px1,py1,ww1,hh1); + if (bgpixbuf) { + gdk_cairo_set_source_pixbuf(cr,bgpixbuf,px2,py2); + cairo_paint(cr); + g_object_unref(bgpixbuf); + } + else printz("Fpaint4() bgpixbuf failure \n"); + } + } + + gdk_cairo_set_source_pixbuf(cr,pixbuf,px2,py2); // draw area to window + cairo_paint(cr); + + g_object_unref(pixbuf); + + if (Fshowarea) { + px3 = (px2 - Dorgx + Morgx) / Mscale; // back to image scale, expanded + py3 = (py2 - Dorgy + Morgy) / Mscale; + ww3 = ww2 / Mscale + 2; + hh3 = hh2 / Mscale + 2; + sa_show_rect(px3,py3,ww3,hh3,cr); // refresh select area outline + } + + if (crflag) draw_context_destroy(draw_context); + paintlock(0); + + return; +} + + +// update window area pending from a thread +// update_window_area() will be called by gtimefunc() next timer cycle. + +namespace update_window_area_names +{ + int lock = 0; + int pending = 0; + int px3a, py3a, ww3a, hh3a; +} + +void update_window_area() // 17.01 +{ + using namespace update_window_area_names; + + if (! pending) return; + resource_lock(lock); + cairo_t *cr = draw_context_create(gdkwin,draw_context); // 17.04 + Fpaint3(px3a,py3a,ww3a,hh3a,cr); + draw_context_destroy(draw_context); // 17.04 + pending = 0; + resource_unlock(lock); + return; +} + + +// Fpaint3 callable from threads. +// Prepare data about region to update. +// Main thread (above) does the window update. + +void Fpaint3_thread(int px3, int py3, int ww3, int hh3) // 17.01 +{ + using namespace update_window_area_names; + + resource_lock(lock); + + if (pending) { + if (px3 < px3a) { + ww3a += (px3a - px3); + px3a = px3; + } + if (py3 < py3a) { + hh3a += (py3a - py3); + py3a = py3; + } + if (px3 + ww3 > px3a + ww3a) + ww3a += px3 + ww3 - (px3a + ww3a); + if (py3 + hh3 > py3a + hh3a) + hh3a += py3 + hh3 - (py3a + hh3a); + } + + pending = 1; + resource_unlock(lock); + return; +} + + +/********************************************************************************/ + +// F/W view window mouse event function - capture buttons and drag movements + +void mouse_event(GtkWidget *widget, GdkEventButton *event, void *) +{ + void mouse_convert(int xpos1, int ypos1, int &xpos2, int &ypos2); + + int button, time, type, scroll; + int mxdist, mydist, mdist; + static int bdtime = 0, butime = 0; + static int mdragx0, mdragy0; + char *pp; + + #define GAPR GDK_AXIS_PRESSURE + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->fpxb) return; // no image + + type = event->type; + button = event->button; // button, 1/2/3 = left/center/right + time = event->time; + Mwxposn = event->x; // mouse position in window + Mwyposn = event->y; + scroll = ((GdkEventScroll *) event)->direction; // scroll wheel event + + mouse_convert(Mwxposn,Mwyposn,Mxposn,Myposn); // convert to image space + + KBcontrolkey = KBshiftkey = KBaltkey = 0; + if (event->state & GDK_CONTROL_MASK) KBcontrolkey = 1; + if (event->state & GDK_SHIFT_MASK) KBshiftkey = 1; + if (event->state & GDK_MOD1_MASK) KBaltkey = 1; + + if (type == GDK_SCROLL) { // scroll wheel = zoom + zoomx = Mxposn; // zoom center = mouse position + zoomy = Myposn; + if (scroll == GDK_SCROLL_UP) m_zoom(0,"in"); + if (scroll == GDK_SCROLL_DOWN) m_zoom(0,"out"); + return; + } + + if (type == GDK_BUTTON_PRESS) { // button down + Mdrag++; // possible drag start + bdtime = time; // time of button down + Mbutton = button; + mdragx0 = Mwxposn; // window position at button down + mdragy0 = Mwyposn; + Mxdown = Mxposn; // image position at button down + Mydown = Myposn; + Mxdrag = Mydrag = 0; + } + + if (type == GDK_MOTION_NOTIFY) { + if (Mdrag) { // drag underway + Mwdragx = Mwxposn - mdragx0; // drag increment, window space + Mwdragy = Mwyposn - mdragy0; + mdragx0 = Mwxposn; // new drag origin = current position + mdragy0 = Mwyposn; + Mxdrag = Mxposn; // drag position, image space + Mydrag = Myposn; + mouse_dragtime = time - bdtime; // track drag duration + gdk_event_get_axis((GdkEvent *) event, GAPR, &wacom_pressure); // wacom tablet stylus pressure 17.04 + } + else Mwdragx = Mwdragy = Mxdrag = Mydrag = 0; // 18.01 + } + + if (type == GDK_BUTTON_RELEASE) { // button up + Mxclick = Myclick = 0; // reset click status + butime = time; // time of button up + if (butime - bdtime < 400) { // less than 0.4 secs down + mxdist = Mxposn - Mxdown; // get mouse movement between + mydist = Myposn - Mydown; // button press and release + mdist = sqrtf(mxdist * mxdist + mydist * mydist); // if < 10 pixels, 17.04 + if (mdist < 10) { // call this a mouse click + if (Mbutton == 1) LMclick++; // left mouse click + if (Mbutton == 3) RMclick++; // right mouse click + Mxclick = Mxdown; // click = button down position + Myclick = Mydown; + if (button == 2) { // center button click + zoomx = Mxposn; // re-center at mouse (Doriano) + zoomy = Myposn; + gtk_widget_queue_draw(Cdrawin); + } + } + } + Mxdown = Mydown = Mxdrag = Mydrag = Mdrag = Mbutton = 0; // forget buttons and drag + } + + Fmousemain = 1; // mouse acts on main window + if (Mcapture) Fmousemain = 0; // curr. function handles mouse + if (mouseCBfunc) Fmousemain = 0; // mouse owned by callback function + if (KBcontrolkey) Fmousemain = 1; // mouse acts on main window + + if (mouseCBfunc && ! Fmousemain) { // pass to callback function + (* mouseCBfunc)(); // remove busy test 17.04 + Fmousemain = 1; // click/drag params are processed here + } // unless reset by callback func. + + if (FGWM == 'W') filemap_mousefunc(); // geomap mouse function + + if (! Fmousemain) return; // curr. function handles mouse + + if (curr_file && LMclick && FGWM == 'F') { // F-view, left click on image 17.04 + pp = strrchr(curr_file,'/'); + if (! pp) pp = curr_file; + if (strstr(pp,"(fotoxx montage)")) { // click on image montage file, + montage_Lclick_func(Mxclick,Myclick); // popup corresp. image file + LMclick = 0; + } + } + + if (LMclick) { // left click = zoom request + LMclick = 0; + zoomx = Mxclick; // zoom center = mouse + zoomy = Myclick; + m_zoom(0,"in"); + } + + if (RMclick) { // right click + RMclick = 0; + if (Cstate->fzoom) { // if zoomed image, reset to fit window + zoomx = zoomy = 0; + m_zoom(0,"fit"); + } + else if (curr_file && FGWM == 'F') + image_Rclick_popup(); // image right-click popup menu + } + + if (Mxdrag || Mydrag) // drag = scroll by mouse + gtk_widget_queue_draw(Cdrawin); + + return; +} + + +// convert mouse position from window space to image space + +void mouse_convert(int xpos1, int ypos1, int &xpos2, int &ypos2) +{ + xpos2 = (xpos1 - Cstate->dorgx + Cstate->morgx) / Cstate->mscale + 0.5; + ypos2 = (ypos1 - Cstate->dorgy + Cstate->morgy) / Cstate->mscale + 0.5; + + if (xpos2 < 0) xpos2 = 0; // if outside image put at edge + if (ypos2 < 0) ypos2 = 0; + + if (E3pxm) { + if (xpos2 >= E3pxm->ww) xpos2 = E3pxm->ww-1; + if (ypos2 >= E3pxm->hh) ypos2 = E3pxm->hh-1; + } + else { + if (xpos2 >= Cstate->fpxb->ww) xpos2 = Cstate->fpxb->ww-1; + if (ypos2 >= Cstate->fpxb->hh) ypos2 = Cstate->fpxb->hh-1; + } + + return; +} + + +/********************************************************************************/ + +// set new image zoom level or magnification +// zoom: "in" zoom-in in steps +// "out" zoom-out in steps +// "fit" zoom to fit window +// "100" toggle 100% and fit window + +void m_zoom(GtkWidget *, cchar *zoom) +{ + int fww, fhh; + float scalew, scaleh, fitscale, fzoom2, zratio = 1; + float Rzoom, pixels; + + if (! Cstate || ! Cstate->fpxb) return; + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->fpxb) return; // no image + + Rzoom = Cstate->fzoom; // current zoom ratio + + if (E3pxm) { + fww = E3pxm->ww; // 1x image size + fhh = E3pxm->hh; + } + else { + fww = Cstate->fpxb->ww; + fhh = Cstate->fpxb->hh; + } + + if (fww > Dww || fhh > Dhh) { // get window fit scale + scalew = 1.0 * Dww / fww; + scaleh = 1.0 * Dhh / fhh; + if (scalew < scaleh) fitscale = scalew; + else fitscale = scaleh; // window/image, < 1.0 + } + else fitscale = 1.0; // if image < window use 100% + + if (strmatch(zoom,"Zoom+")) zoom = "in"; // menu button: + = zoom in + if (strmatch(zoom,"Zoom-")) zoom = "fit"; // - = fit window + + if (strmatch(zoom,"fit")) Cstate->fzoom = 0; // zoom to fit window + + if (strmatch(zoom,"100")) { + if (Cstate->fzoom != 0) Cstate->fzoom = 0; // toggle 100% and fit window + else Cstate->fzoom = 1; + } + + if (strstr("in out",zoom)) // caller: zoom in or out + { + if (! Cstate->fzoom) Cstate->fzoom = fitscale; // current zoom scale + for (fzoom2 = 0.125; fzoom2 < 4.0; fzoom2 *= zoomratio) // find nearest natural ratio + if (Cstate->fzoom < fzoom2 * sqrt(zoomratio)) break; + if (strmatch(zoom,"in")) zratio = zoomratio; // zoom in, make image larger + if (strmatch(zoom,"out")) zratio = 1.0 / zoomratio; // zoom out, make image smaller + Cstate->fzoom = fzoom2 * zratio; + + if (Cstate->fzoom > 0.124 && Cstate->fzoom < 0.126) // hit these ratios exactly + Cstate->fzoom = 0.125; + if (Cstate->fzoom > 0.24 && Cstate->fzoom < 0.26) + Cstate->fzoom = 0.25; + if (Cstate->fzoom > 0.49 && Cstate->fzoom < 0.51) + Cstate->fzoom = 0.50; + if (Cstate->fzoom > 0.99 && Cstate->fzoom < 1.01) + Cstate->fzoom = 1.00; + if (Cstate->fzoom > 1.99 && Cstate->fzoom < 2.01) + Cstate->fzoom = 2.00; + if (Cstate->fzoom > 3.99) Cstate->fzoom = 4.0; // max. allowed zoom + if (Cstate->fzoom < fitscale) Cstate->fzoom = 0; // image < window + } + + if (FGWM == 'W') { // optimize for geomaps + if (strmatch(zoom,"in") && Cstate->fzoom < 1.0) + Cstate->fzoom = 1.0; // zoom to 100% directly + if (strmatch(zoom,"out")) Cstate->fzoom = 0.0; // zoom out = fit window directly + if (Cstate->fzoom == 1.0 && Rzoom == 1.0) { + gtk_widget_queue_draw(Cdrawin); // if already, move mouse pointer only + return; + } + } + + if (Cstate->fzoom > 1.0) { // limit image in window to 1 gigabyte + pixels = Cstate->fzoom * fww * Cstate->fzoom * fhh; // (333 megapixels x 3 bytes/pixel) + if (pixels > 333 * MEGA) { // if size is at maximum, + Cstate->fzoom = Rzoom; // move mouse pointer only + gtk_widget_queue_draw(Cdrawin); + return; + } + } + + if (! Cstate->fzoom) zoomx = zoomy = 0; // no requested zoom center + Fpaint2(); // refresh window + return; +} + + +/********************************************************************************/ + +// function for dialogs to call to send KB keys for processing by main app + +void KBevent(GdkEventKey *event) +{ + KBpress(0,event,0); + return; +} + + +// keyboard event functions +// GDK key symbols: /usr/include/gtk-3.0/gdk/gdkkeysyms.h + +namespace trimrotate { void KBfunc(int key); } // keyboard functions called from here +namespace perspective { void KBfunc(int key); } +namespace mashup { void KBfunc(int key); } +namespace view360 { void KB_func(int key); } + +int KBpress(GtkWidget *win, GdkEventKey *event, void *) // keyboard key was pressed +{ + int ii, jj, cc; + char shortkey[20] = ""; + cchar *action = 0; + + KBkey = event->keyval; // input key + + KBcontrolkey = KBshiftkey = KBaltkey = 0; // look for combination keys + if (event->state & GDK_CONTROL_MASK) KBcontrolkey = 1; + if (event->state & GDK_SHIFT_MASK) KBshiftkey = 1; + if (event->state & GDK_MOD1_MASK) KBaltkey = 1; + + if (KBshiftkey && KBkey == GDK_KEY_plus) KBshiftkey = 0; // treat Shift [+] same as [+] 18.01 + if (KBkey == GDK_KEY_equal) KBkey = GDK_KEY_plus; // treat [=] same as [+] + if (KBkey == GDK_KEY_KP_Add) KBkey = GDK_KEY_plus; // treat keypad [+] same as [+] + if (KBkey == GDK_KEY_KP_Subtract) KBkey = GDK_KEY_minus; // treat keypad [-] same as [-] + + if (KBkey == GDK_KEY_F1) { // F1 >> user guide + showz_userguide(F1_help_topic); // show topic if there, or page 1 + return 1; + } + + if (Fslideshow) { // slide show active 18.01 + if (KBkey == GDK_KEY_F10) ss_escape = 1; // tell slide show to quit + if (KBkey == GDK_KEY_F11) ss_escape = 1; + if (KBkey == GDK_KEY_Escape) ss_escape = 1; + if (ss_escape) return 1; + ss_KBfunc(KBkey); // pass other keys to slide show + return 1; + } + + if (Fview360) { // view360 active + view360::KB_func(KBkey); // pass KB keys to view360 + return 1; + } + + if (KBkey == GDK_KEY_F10) { // F10: fullscreen toggle with menu + if (! Ffullscreen) win_fullscreen(0); // toggle full-screen mode and back + else win_unfullscreen(); + return 1; + } + + if (KBkey == GDK_KEY_F11) { // F11: fullscreen toggle no menu + if (! Ffullscreen) win_fullscreen(1); // toggle full-screen mode and back + else win_unfullscreen(); + return 1; + } + + if (KBkey == GDK_KEY_Escape) { // ESC key + if (Ffullscreen) win_unfullscreen(); // exit full screen mode + else delete_event(); // quit fotoxx 17.04 + return 1; + } + + if (KBkey == GDK_KEY_p && image_file_type(curr_file) == VIDEO) { // 'P' for totem, OK to use elsewhere +/// shell_quiet("ffplay -loglevel 8 -autoexit \"%s\" ",curr_file); FIXME // best UI, fails appimage + Wayland +/// shell_quiet("gst-launch-1.0 playbin uri=file:\"///%s\" ",curr_file); // works everywhere, bad UI + shell_quiet("totem \"%s\" &",curr_file); // works everywhere, adequate UI + return 1; + } + + if (KBkey == GDK_KEY_Delete) action = (char *) "Delete"; // reserved shortcuts 18.01 + if (KBkey == GDK_KEY_Left) action = (char *) "Left"; + if (KBkey == GDK_KEY_Right) action = (char *) "Right"; + if (KBkey == GDK_KEY_Up) action = (char *) "Up"; + if (KBkey == GDK_KEY_Down) action = (char *) "Down"; + if (KBkey == GDK_KEY_Home) action = (char *) "First"; + if (KBkey == GDK_KEY_End) action = (char *) "Last"; + if (KBkey == GDK_KEY_Page_Up) action = (char *) "Page_Up"; + if (KBkey == GDK_KEY_Page_Down) action = (char *) "Page_Down"; + if (KBkey == GDK_KEY_h && KBcontrolkey) action = (char *) "Show Hidden"; + + if (! action) + { + if (KBkey >= GDK_KEY_F2 && KBkey <= GDK_KEY_F9) { // input key is F2 to F9 + ii = KBkey - GDK_KEY_F1; + strcpy(shortkey,"F1"); + shortkey[1] += ii; + } + + if (! *shortkey && KBkey < 256) // single ascii character + { + if (KBcontrolkey) strcat(shortkey,"Ctrl+"); // build input key combination + if (KBaltkey) strcat(shortkey,"Alt+"); // [Ctrl+] [Alt+] [Shift+] key + if (KBshiftkey) strcat(shortkey,"Shift+"); + cc = strlen(shortkey); + shortkey[cc] = KBkey; + shortkey[cc+1] = 0; + } + + if (*shortkey) { // find shortcut key in shortcut list + for (ii = 0; ii < Nshortcuts; ii++) + if (strmatchcase(shortkey,shortcutkey[ii])) break; + if (ii < Nshortcuts) action = shortcutmenu[ii]; // corresp. action or function + } + } + + if (! action) return 1; // no match + + if (strmatch(action,ZTX("File View"))) { // use translations 18.01 + m_viewmode(0,"F"); + return 1; + } + + if (strmatch(action,ZTX("Gallery View"))) { + m_viewmode(0,"G"); + return 1; + } + + if (strmatch(action,ZTX("World Map View"))) { + m_viewmode(0,"W"); + return 1; + } + + if (strmatch(action,ZTX("Net Map View"))) { + m_viewmode(0,"M"); + return 1; + } + + if (strmatch(action,ZTX("Sync Gallery"))) { + m_sync_gallery(0,0); + return 1; + } + + if (FGWM == 'G') { // G view mode + navi::KBaction(action); // pass KB action to gallery + return 1; + } + + if (strmatch(action,ZTX("Show Hidden"))) return 1; // only meaningful in G view + + if (FGWM == 'W' || FGWM == 'M') return 1; // map view mode, no other KB actions + + if (KBcapture) return 1; // let current function handle it + + if (Fmashup) { // mashup active, pass KB key + mashup::KBfunc(KBkey); + return 1; + } + + if (CEF && CEF->menufunc == m_trim_rotate) { // trim_rotate active, pass KB key + trimrotate::KBfunc(KBkey); + return 1; + } + + if (CEF && CEF->menufunc == m_perspective) { // perspective active, pass KB key + perspective::KBfunc(KBkey); + return 1; + } + + if (strmatch(action,"Left")) { // left arrow - previous image + m_prev(0,0); + return 1; + } + + if (strmatch(action,"Right")) { // right arrow - next image + m_next(0,0); + return 1; + } + + if (KBkey == GDK_KEY_Delete) { // delete key - delete/trash dialog + m_delete_trash(0,0); + return 1; + } + + if (strmatch(action,"Zoom-in")) { // zoom center = mouse position + zoomx = Mxposn; + zoomy = Myposn; + m_zoom(0,"in"); + return 1; + } + + if (strmatch(action,"Zoom-out")) { // zoom to fit window + m_zoom(0,"fit"); + return 1; + } + + if (strmatch(action,"Zoom-1x toggle")) { // zoom to 1x / fit window toggle + m_zoom(0,"100"); + return 1; + } + +// check for menu function shortcut + + for (jj = 0; jj < Nmenus; jj++) { + if (! menutab[jj].menu) continue; // ignore separator 'menu' + if (strmatchcase(action,ZTX(menutab[jj].menu))) break; + } + + if (jj == Nmenus) { + printz("shortcut not found: %s %s \n",shortkey,action); + return 1; + } + + menutab[jj].func(0,menutab[jj].arg); // call the menu function + return 1; +} + + +int KBrelease(GtkWidget *win, GdkEventKey *event, void *) // KB key released +{ + KBkey = 0; // reset current active key + return 1; +} + + +/********************************************************************************/ + +// set the main window to fullscreen status +// (with no menu or panel) + +void win_fullscreen(int hidemenu) +{ + if (FGWM == 'F' && hidemenu) { // if F window, hide menu and panel + gtk_widget_hide(Fmenu); + gtk_widget_hide(Fpanel); + Fpanelshow = 0; + } + + if (hidemenu) gtk_window_fullscreen(MWIN); + else gtk_window_maximize(MWIN); // 17.08 + while (! Ffullscreen) zmainloop(); + return; +} + + +// restore window to former size and restore menu etc. + +void win_unfullscreen() +{ + gtk_window_unfullscreen(MWIN); // restore old window size + gtk_window_unmaximize(MWIN); + gtk_widget_show(Fmenu); + gtk_widget_show(Fpanel); + Fpanelshow = 1; + while (Ffullscreen) zmainloop(); + return; +} + + +/********************************************************************************/ + +// update information in main window title bar + +namespace meta_names +{ + extern char meta_pdate[16]; // image (photo) date, yyyymmddhhmmss +} + + +void set_mwin_title() +{ + GTYPE gtype; + int cc, fposn, Nfiles, Nimages; + char *pp, titlebar[250]; + char pdate[12], ptime[12], pdatetime[24]; + char fname[100], gname[100], fdirk[100]; + + if (FGWM != 'F') return; // 18.01 + + if (! curr_file || *curr_file != '/') { + gtk_window_set_title(MWIN,Arelease); + return; + } + + pp = (char *) strrchr(curr_file,'/'); + strncpy0(fname,pp+1,99); // file name + cc = pp - curr_file; + if (cc < 99) strncpy0(fdirk,curr_file,cc+2); // get dirk/path/ if short enough + else { + strncpy(fdirk,curr_file,96); // or use /dirk/path... + strcpy(fdirk+95,"..."); + } + + Nfiles = navi::Nfiles; // total gallery files (incl. directories) + Nimages = navi::Nimages; // total image files + + fposn = file_position(curr_file,curr_file_posn); // curr. file in curr. gallery? + if (fposn >= 0) { + curr_file_posn = fposn; + fposn = fposn + 1 - Nfiles + Nimages; // position among images, 1-based + } + + if (*meta_names::meta_pdate) { + metadate_pdate(meta_names::meta_pdate,pdate,ptime); // get formatted date and time + strncpy0(pdatetime,pdate,11); // yyyy-mm-dd hh:mm:ss 18.01 + strncpy0(pdatetime+11,ptime,9); + pdatetime[10] = ' '; + } + else strcpy(pdatetime,"(undated)"); + + gtype = navi::gallerytype; + + if (gtype == GDIR) // gallery name = directory + snprintf(titlebar,250," %d/%d %s %s %s", + fposn,Nimages,fdirk,fname,pdatetime); + else { + if (gtype == SEARCH || gtype == META) + strcpy(gname,"SEARCH RESULTS"); + else if (gtype == ALBUM) { + pp = strrchr(navi::galleryname,'/'); + if (! pp) pp = navi::galleryname; + else pp++; + strcpy(gname,"ALBUM: "); + strncpy0(gname+7,pp,87); + } + else if (gtype == RECENT) + strcpy(gname,"RECENT FILES"); + else if (gtype == NEWEST) + strcpy(gname,"NEWEST FILES"); + else + strcpy(gname,"NO GALLERY"); + + if (fposn > 0) + snprintf(titlebar,250,"%s %d/%d %s %s %s", // window title bar + gname,fposn,Nimages,fdirk,fname,pdatetime); + else + snprintf(titlebar,250,"%s (*)/%d %s %s %s", // image not in gallery + gname,Nimages,fdirk,fname,pdatetime); + } + + gtk_window_set_title(MWIN,titlebar); + return; +} + + +/********************************************************************************/ + +// draw a pixel using foreground color. +// px, py are image space. + +void draw_pixel(int px, int py, cairo_t *cr, int fat) +{ + int qx, qy; + static int pqx, pqy; + static uint8 pixel[12]; // 2x2 block of pixels + static PIXBUF *pixbuf1 = 0, *pixbuf4 = 0; + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->fpxb) return; // no image + + if (! pixbuf1) { + pixbuf1 = gdk_pixbuf_new_from_data(pixel,GDKRGB,0,8,1,1,3,0,0); // 1x1 pixels + pixbuf4 = gdk_pixbuf_new_from_data(pixel,GDKRGB,0,8,2,2,6,0,0); // 2x2 pixels + } + + if (Cstate->fpxb->nc > 3) // omit transparent pixels + if (PXBpix(Cstate->fpxb,px,py)[3] < 128) return; + + qx = Mscale * px - Morgx; // image to window space + qy = Mscale * py - Morgy; + + if (qx == pqx && qy == pqy) return; // avoid redundant points + + pqx = qx; + pqy = qy; + + if (qx < 0 || qx > dww-2) return; // keep off image edges + if (qy < 0 || qy > dhh-2) return; + + if (Mscale <= 1 && ! fat) { // write 1x1 pixels 17.08 + pixel[0] = LINE_COLOR[0]; + pixel[1] = LINE_COLOR[1]; + pixel[2] = LINE_COLOR[2]; + + gdk_cairo_set_source_pixbuf(cr,pixbuf1,qx+Dorgx,qy+Dorgy); + cairo_paint(cr); + } + + else { // write 2x2 fat pixels + pixel[0] = pixel[3] = pixel[6] = pixel[9] = LINE_COLOR[0]; + pixel[1] = pixel[4] = pixel[7] = pixel[10] = LINE_COLOR[1]; + pixel[2] = pixel[5] = pixel[8] = pixel[11] = LINE_COLOR[2]; + + gdk_cairo_set_source_pixbuf(cr,pixbuf4,qx+Dorgx,qy+Dorgy); + cairo_paint(cr); + } + + return; +} + + +// erase one drawn pixel - restore from window image Mpxb. +// px, py are image space. + +void erase_pixel(int px, int py, cairo_t *cr) +{ + GdkPixbuf *pixbuf; + static int pqx, pqy; + int qx, qy; + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->fpxb) return; // no image + + qx = Mscale * px; // image to window space + qy = Mscale * py; + + if (qx == pqx && qy == pqy) return; // avoid same target pixel + + pqx = qx; + pqy = qy; + + if (qx < 0 || qx > Mpxb->ww-2) return; // pixel outside scaled image + if (qy < 0 || qy > Mpxb->hh-2) return; + + if (qx < Morgx || qx > Morgx + dww-2) return; // pixel outside drawing window + if (qy < Morgy || qy > Morgy + dhh-2) return; + + pixbuf = gdk_pixbuf_new_subpixbuf(Mpxb->pixbuf,qx,qy,2,2); // 2x2 Mpxb area to copy + qx = qx - Morgx + Dorgx; // target pixel in window + qy = qy - Morgy + Dorgy; + gdk_cairo_set_source_pixbuf(cr,pixbuf,qx,qy); + cairo_paint(cr); + + g_object_unref(pixbuf); + + return; +} + + +/********************************************************************************/ + +// draw line. +// coordinates are image space. +// type = 1/2 for solid/dotted line + +void draw_line(int x1, int y1, int x2, int y2, int type, cairo_t *cr) +{ + float px1, py1, px2, py2; + double dashes[2] = { 4, 4 }; + double R, G, B; + int crflag = 0; + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->fpxb) return; // no image + + px1 = Mscale * x1 - Morgx + Dorgx; // image to window space + py1 = Mscale * y1 - Morgy + Dorgy; + px2 = Mscale * x2 - Morgx + Dorgx; + py2 = Mscale * y2 - Morgy + Dorgy; + + if (px1 > Dww-2) px1 = Dww-2; // play nice + if (py1 > Dhh-2) py1 = Dhh-2; + if (px2 > Dww-2) px2 = Dww-2; + if (py2 > Dhh-2) py2 = Dhh-2; + + R = LINE_COLOR[0] / 255.0; // use line color + G = LINE_COLOR[1] / 255.0; + B = LINE_COLOR[2] / 255.0; + + if (! cr) { + cr = draw_context_create(gdkwin,draw_context); + crflag = 1; + } + + cairo_set_source_rgb(cr,R,G,B); + if (type == 2) cairo_set_dash(cr,dashes,2,0); // dotted line + else cairo_set_dash(cr,dashes,0,0); + + cairo_move_to(cr,px1,py1); // draw line + cairo_line_to(cr,px2,py2); + cairo_stroke(cr); + + if (crflag) draw_context_destroy(draw_context); + return; +} + + +// erase line. refresh line path from mpxb window image. +// double line width is erased. +// coordinates are image space. + +void erase_line(int x1, int y1, int x2, int y2, cairo_t *cr) +{ + float pxm, pym, slope; + int crflag = 0; + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->fpxb) return; // no image + + if (! cr) { + cr = draw_context_create(gdkwin,draw_context); + crflag = 1; + } + + if (abs(y2 - y1) > abs(x2 - x1)) { + slope = 1.0 * (x2 - x1) / (y2 - y1); + for (pym = y1; pym <= y2; pym++) { + pxm = x1 + slope * (pym - y1); + erase_pixel(pxm,pym,cr); + } + } + + else { + slope = 1.0 * (y2 - y1) / (x2 - x1); + for (pxm = x1; pxm <= x2; pxm++) { + pym = y1 + slope * (pxm - x1); + erase_pixel(pxm,pym,cr); + } + } + + if (crflag) draw_context_destroy(draw_context); + return; +} + + +/********************************************************************************/ + +// draw pre-set overlay lines on top of image +// arg = 1: paint lines only (because window repainted) +// 2: erase lines and forget them +// 3: erase old lines, paint new lines, save new in old + +void draw_toplines(int arg, cairo_t *cr) +{ + int ii; + int crflag = 0; + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->fpxb) return; // no image + + if (! cr) { + cr = draw_context_create(gdkwin,draw_context); + crflag = 1; + } + + if (arg == 2 || arg == 3) // erase old lines + for (ii = 0; ii < Nptoplines; ii++) + erase_line(ptoplines[ii].x1,ptoplines[ii].y1, + ptoplines[ii].x2,ptoplines[ii].y2,cr); + + if (arg == 1 || arg == 3) // draw new lines + for (ii = 0; ii < Ntoplines; ii++) + draw_line(toplines[ii].x1,toplines[ii].y1, + toplines[ii].x2,toplines[ii].y2,toplines[ii].type,cr); + + if (crflag) draw_context_destroy(draw_context); + + if (arg == 2) { + Nptoplines = Ntoplines = 0; // forget lines + return; + } + + for (ii = 0; ii < Ntoplines; ii++) // save for future erase + ptoplines[ii] = toplines[ii]; + + Nptoplines = Ntoplines; + return; +} + + +/********************************************************************************/ + +// draw a grid of horizontal and vertical lines. +// grid line spacings are in window space. + +void draw_gridlines(cairo_t *cr) +{ + int G = currgrid; + int px, py, gww, ghh; + int startx, starty, endx, endy, stepx, stepy; + int startx1, starty1; + int crflag = 0; + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->fpxb) return; // no image + + if (! gridsettings[G][GON]) return; // grid lines off + + gww = dww; // grid box size + ghh = dhh; + startx = Dorgx; // starting corner (top left) + starty = Dorgy; + + if (CEF && strmatch(CEF->funcname,"trim_rotate")) { // trim/rotate function is active + gww = Mscale * (trimx2 - trimx1); // fit grid box to trim rectangle + ghh = Mscale * (trimy2 - trimy1); + startx = Mscale * trimx1 - Morgx + Dorgx; + starty = Mscale * trimy1 - Morgy + Dorgy; + } + + endx = startx + gww; + endy = starty + ghh; + + stepx = gridsettings[G][GXS]; // space between grid lines + stepy = gridsettings[G][GYS]; // (window space) + + if (gridsettings[G][GXC]) + stepx = gww / (1 + gridsettings[G][GXC]); // if line counts specified, + if (gridsettings[G][GYC]) // set spacing accordingly + stepy = ghh / ( 1 + gridsettings[G][GYC]); + + startx1 = startx + stepx * gridsettings[G][GXF] / 100; // variable starting offsets + starty1 = starty + stepy * gridsettings[G][GYF] / 100; + + if (! cr) { + cr = draw_context_create(gdkwin,draw_context); + crflag = 1; + } + + cairo_set_source_rgb(cr,1,1,1); // white lines + + if (gridsettings[G][GX] && stepx) + for (px = startx1; px < endx; px += stepx) { + cairo_move_to(cr,px,starty); + cairo_line_to(cr,px,endy); + } + + if (gridsettings[G][GY] && stepy) + for (py = starty1; py < endy; py += stepy) { + cairo_move_to(cr,startx,py); + cairo_line_to(cr,endx,py); + } + + cairo_stroke(cr); + + cairo_set_source_rgb(cr,0,0,0); // adjacent black lines + + if (gridsettings[G][GX] && stepx) + for (px = startx1+1; px < endx+1; px += stepx) { + cairo_move_to(cr,px,starty); + cairo_line_to(cr,px,endy); + } + + if (gridsettings[G][GY] && stepy) + for (py = starty1+1; py < endy+1; py += stepy) { + cairo_move_to(cr,startx,py); + cairo_line_to(cr,endx,py); + } + + cairo_stroke(cr); + + if (crflag) draw_context_destroy(draw_context); + return; +} + + +/********************************************************************************/ + +// maintain a set of text strings written over the image in the window. +// add a new text string to the list. +// multiple text strings can be added with the same ID. +// px and py are image space. + +void add_toptext(int ID, int px, int py, cchar *text, cchar *font) +{ + if (Ntoptext == maxtoptext) { + printz("*** maxtoptext exceeded \n"); + return; + } + + int ii = Ntoptext++; + toptext[ii].ID = ID; + toptext[ii].px = px; + toptext[ii].py = py; + toptext[ii].text = text; + toptext[ii].font = font; + + return; +} + + +// draw current text strings over the image in window. +// called from Fpaint(). + +void draw_toptext(cairo_t *cr) +{ + int crflag = 0; + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->fpxb) return; // no image + + if (! cr) { + cr = draw_context_create(gdkwin,draw_context); + crflag = 1; + } + + for (int ii = 0; ii < Ntoptext; ii++) + draw_text(toptext[ii].px,toptext[ii].py,toptext[ii].text,toptext[ii].font,cr); + + if (crflag) draw_context_destroy(draw_context); + return; +} + + +// delete text strings having the given ID from the list + +void erase_toptext(int ID) +{ + int ii, jj; + + for (ii = jj = 0; ii < Ntoptext; ii++) + { + if (toptext[ii].ID == ID) continue; + else toptext[jj++] = toptext[ii]; + } + + Ntoptext = jj; + return; +} + + +// draw text on window, black on white background +// coordinates are image space + +void draw_text(int px, int py, cchar *text, cchar *font, cairo_t *cr) +{ + static PangoFontDescription *pangofont = 0; + static PangoLayout *pangolayout = 0; + static char priorfont[40] = ""; + int ww, hh; + int crflag = 0; + + if (! Cdrawin) return; + if (! gdkwin) return; + + px = Mscale * px - Morgx + Dorgx; // image to window space + py = Mscale * py - Morgy + Dorgy; + + if (! strmatch(font,priorfont)) { // change font + strncpy0(priorfont,font,40); + if (pangofont) pango_font_description_free(pangofont); + if (pangolayout) g_object_unref(pangolayout); + pangofont = pango_font_description_from_string(font); // make pango layout for font + pangolayout = gtk_widget_create_pango_layout(Cdrawin,0); + pango_layout_set_font_description(pangolayout,pangofont); + } + + pango_layout_set_text(pangolayout,text,-1); // add text to layout + pango_layout_get_pixel_size(pangolayout,&ww,&hh); + + if (! cr) { + cr = draw_context_create(gdkwin,draw_context); + crflag = 1; + } + + cairo_set_source_rgb(cr,1,1,1); // draw white background + cairo_rectangle(cr,px,py,ww,hh); + cairo_fill(cr); + + cairo_move_to(cr,px,py); // draw layout with text + cairo_set_source_rgb(cr,0,0,0); + pango_cairo_show_layout(cr,pangolayout); + + if (crflag) draw_context_destroy(draw_context); + return; +} + + +/********************************************************************************/ + +// maintain a set of circles drawn over the image in the window +// px, py are image space, radius is window space + +void add_topcircle(int px, int py, int radius) +{ + if (Ntopcircles == maxtopcircles) { + printz("*** maxtopcircles exceeded \n"); + return; + } + + int ii = Ntopcircles++; + topcircles[ii].px = px; + topcircles[ii].py = py; + topcircles[ii].radius = radius; + + return; +} + + +// draw current circles over the image in the window +// called from window repaint function Fpaint() + +void draw_topcircles(cairo_t *cr) +{ + int ii, px, py, rad; + double R, G, B; + int crflag = 0; + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->fpxb) return; // no image + + R = LINE_COLOR[0] / 255.0; // use LINE_COLOR + G = LINE_COLOR[1] / 255.0; + B = LINE_COLOR[2] / 255.0; + + if (! cr) { + cr = draw_context_create(gdkwin,draw_context); + crflag = 1; + } + + for (ii = 0; ii < Ntopcircles; ii++) + { + px = topcircles[ii].px * Mscale - Morgx + Dorgx; // image to window space + py = topcircles[ii].py * Mscale - Morgy + Dorgy; + rad = topcircles[ii].radius; // radius is window space + + cairo_set_source_rgb(cr,R,G,B); + cairo_arc(cr,px,py,rad,0,2*PI); // draw 360 deg. arc + cairo_stroke(cr); + } + + if (crflag) draw_context_destroy(draw_context); + return; +} + + +// erase top circles (next window repaint) + +void erase_topcircles() +{ + Ntopcircles = 0; + return; +} + + +/********************************************************************************/ + +// Draw circle around the mouse pointer. +// Prior circle will be erased first. +// Used for mouse/brush radius in select and paint functions. +// cx, cy, rad: center and radius of circle in image space. +// if Ferase, then erase previous circle only. + +void draw_mousecircle(int cx, int cy, int rad, int Ferase, cairo_t *cr) +{ + int px3, py3, ww3, hh3; + static int ppx3, ppy3, pww3 = 0, phh3; + int px, py, pok; + double R, G, B; + double t, dt, t1, t2; + int crflag = 0; + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->mpxb) return; // no image + + if (pww3 > 0) { // erase prior + Fpaint4(ppx3,ppy3,pww3,phh3,cr); // refresh from Mpxb + pww3 = 0; + } + + if (Ferase) return; // erase only, done + + px3 = cx - rad - 2; // convert pointer center + radius + py3 = cy - rad - 2; // to block position, width, length + ww3 = 2 * rad + 4; + hh3 = 2 * rad + 4; + + ppx3 = px3; // remember pixel block area + ppy3 = py3; // to erase in next call + pww3 = ww3; + phh3 = hh3; + + cx = cx * Mscale - Morgx + Dorgx; // convert to window coordinates + cy = cy * Mscale - Morgy + Dorgy; + rad = rad * Mscale; + + R = LINE_COLOR[0] / 255.0; // use LINE_COLOR + G = LINE_COLOR[1] / 255.0; + B = LINE_COLOR[2] / 255.0; + + if (! cr) { + cr = draw_context_create(gdkwin,draw_context); + crflag = 1; + } + + cairo_set_source_rgba(cr,R,G,B,1.0); + + t1 = t2 = -1; // angle limits of arc to draw + dt = 1.0 / rad; + + for (t = 0; t < 2*PI; t += dt) // loop 0-360 degrees + { + px = cx + rad * cos(t); // pixel on mouse circle + py = cy + rad * sin(t); + + pok = 1; // assume pixel OK to draw + + if (px < Dorgx || py < Dorgy) pok = 0; // outside image limits + if (px >= Dorgx+dww || py >= Dorgy+dhh) pok = 0; + + if (pok) { // pixel ok, add to arc + if (t1 < 0) t1 = t; // start of arc to draw + t2 = t; // end of arc, so far + } + + else if (t1 >= 0) { // pixel not ok + cairo_arc(cr,cx,cy,rad,t1,t2); // draw accumulated arc + cairo_stroke(cr); + t1 = t2 = -1; // start over + } + } + + if (t1 >= 0) { + cairo_arc(cr,cx,cy,rad,t1,t2); // draw rest of arc + cairo_stroke(cr); + } + + if (crflag) draw_context_destroy(draw_context); + return; +} + + +// duplicate for drawing and tracking a 2nd mouse circle +// (used by paint/clone to track source pixels being cloned) + +void draw_mousecircle2(int cx, int cy, int rad, int Ferase, cairo_t *cr) +{ + int px3, py3, ww3, hh3; + static int ppx3, ppy3, pww3 = 0, phh3; + int px, py, pok; + double R, G, B; + double t, dt, t1, t2; + int crflag = 0; + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->mpxb) return; // no image + + if (pww3 > 0) { // erase prior + Fpaint4(ppx3,ppy3,pww3,phh3,cr); // refresh from Mpxb + pww3 = 0; + } + + if (Ferase) return; // erase only, done + + px3 = cx - rad - 2; // convert pointer center + radius + py3 = cy - rad - 2; // to block position, width, length + ww3 = 2 * rad + 4; + hh3 = 2 * rad + 4; + + ppx3 = px3; // remember pixel block area + ppy3 = py3; // to erase in next call + pww3 = ww3; + phh3 = hh3; + + cx = cx * Mscale - Morgx + Dorgx; // convert to window coordinates + cy = cy * Mscale - Morgy + Dorgy; + rad = rad * Mscale; + + R = LINE_COLOR[0] / 255.0; // use LINE_COLOR + G = LINE_COLOR[1] / 255.0; + B = LINE_COLOR[2] / 255.0; + + if (! cr) { + cr = draw_context_create(gdkwin,draw_context); + crflag = 1; + } + + cairo_set_source_rgba(cr,R,G,B,1.0); + + t1 = t2 = -1; // angle limits of arc to draw + dt = 1.0 / rad; + + for (t = 0; t < 2*PI; t += dt) // loop 0-360 degrees + { + px = cx + rad * cos(t); // pixel on mouse circle + py = cy + rad * sin(t); + + pok = 1; // assume pixel OK to draw + + if (px < Dorgx || py < Dorgy) pok = 0; // outside image limits + if (px >= Dorgx+dww || py >= Dorgy+dhh) pok = 0; + + if (pok) { // pixel ok, add to arc + if (t1 < 0) t1 = t; // start of arc to draw + t2 = t; // end of arc, so far + } + + else if (t1 >= 0) { // pixel not ok + cairo_arc(cr,cx,cy,rad,t1,t2); // draw accumulated arc + cairo_stroke(cr); + t1 = t2 = -1; // start over + } + } + + if (t1 >= 0) { + cairo_arc(cr,cx,cy,rad,t1,t2); // draw rest of arc + cairo_stroke(cr); + } + + if (crflag) draw_context_destroy(draw_context); + return; +} + + +/********************************************************************************/ + +// Draw ellipse around the mouse pointer. +// Prior ellipse will be erased first. +// cx, cy, cww, chh: center and axes of ellipse in image space. +// if Ferase, then erase previous ellipse only. + +void draw_mousearc(int cx, int cy, int cww, int chh, int Ferase, cairo_t *cr) +{ + int px3, py3, ww3, hh3; + static int ppx3, ppy3, pww3 = 0, phh3; + int px, py; + float a, b, a2, b2; + float x, y, x2, y2; + int crflag = 0; + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->fpxb) return; // no image + + paintlock(1); + + if (! cr) { + cr = draw_context_create(gdkwin,draw_context); + crflag = 1; + } + + if (pww3 > 0) { // erase prior + Fpaint4(ppx3,ppy3,pww3,phh3,cr); // refresh from Mpxb + pww3 = 0; + } + + if (Ferase) { + if (crflag) draw_context_destroy(draw_context); + paintlock(0); + return; + } + + px3 = cx - (cww + 2) / 2; // convert pointer center + radius + py3 = cy - (chh + 2) / 2; // to block position, width, length + ww3 = cww + 2; + hh3 = chh + 2; + + ppx3 = px3; // remember pixel block area + ppy3 = py3; // to erase in next call + pww3 = ww3; + phh3 = hh3; + + a = cww / 2; // ellipse constants from + b = chh / 2; // enclosing rectangle + a2 = a * a; + b2 = b * b; + + for (y = -b; y < b; y++) // step through y values, omitting + { // curve points covered by x values + y2 = y * y; + x2 = a2 * (1 - y2 / b2); + x = sqrtf(x2); // corresp. x values, + and - + py = y + cy; + px = cx - x + 0.5; + draw_pixel(px,py,cr); // draw 2 points on ellipse + px = cx + x + 0.5; + draw_pixel(px,py,cr); + } + + for (x = -a; x < a; x++) // step through x values, omitting + { // curve points covered by y values + x2 = x * x; + y2 = b2 * (1 - x2 / a2); + y = sqrtf(y2); // corresp. y values, + and - + px = x + cx; + py = cy - y + 0.5; + draw_pixel(px,py,cr); // draw 2 points on ellipse + py = cy + y + 0.5; + draw_pixel(px,py,cr); + } + + if (crflag) draw_context_destroy(draw_context); + + paintlock(0); + return; +} + + +/******************************************************************************** + + spline curve setup and edit functions + + Support multiple frames with curves in parallel + (edit curve(s) and image mask curve) + + Usage: + Add frame widget to dialog or zdialog. + Set up drawing in frame: + sd = splcurve_init(frame, callback_func) + Initialize no. of curves in frame (1-10): + sd->Nspc = n + Initialize active flag for curve spc: + sd->fact[spc] = 1 + Initialize vert/horz flag for curve spc: + sd->vert[spc] = hv + Initialize anchor points for curve spc: + sd->nap[spc], sd->apx[spc][xx], sd->apy[spc][yy] + Generate data for curve spc: + splcurve_generate(sd,spc) + Curves will now be shown and edited inside the frame when window is realized. + The callback_func(spc) will be called when curve spc is edited. + Change curve in program: + set anchor points, call splcurve_generate(sd,spc) + Get y-value (0-1) for curve spc and given x-value (0-1): + yval = splcurve_yval(sd,spc,xval) + If faster access to curve is needed (no interpolation): + kk = 1000 * xval; + if (kk > 999) kk = 999; + yval = sd->yval[spc][kk]; + +***/ + +// initialize for spline curve editing +// initial anchor points are pre-loaded into spldat before window is realized + +spldat * splcurve_init(GtkWidget *frame, void func(int spc)) +{ + int cc = sizeof(spldat); // allocate spc curve data area + spldat * sd = (spldat *) zmalloc(cc); + memset(sd,0,cc); + + sd->drawarea = gtk_drawing_area_new(); // drawing area for curves + gtk_container_add(GTK_CONTAINER(frame),sd->drawarea); + sd->spcfunc = func; // user callback function + + gtk_widget_add_events(sd->drawarea,GDK_BUTTON_PRESS_MASK); // connect mouse events to drawing area + gtk_widget_add_events(sd->drawarea,GDK_BUTTON_RELEASE_MASK); + gtk_widget_add_events(sd->drawarea,GDK_BUTTON1_MOTION_MASK); + G_SIGNAL(sd->drawarea,"motion-notify-event",splcurve_adjust,sd); + G_SIGNAL(sd->drawarea,"button-press-event",splcurve_adjust,sd); + G_SIGNAL(sd->drawarea,"realize",splcurve_resize,sd); // 17.04 + G_SIGNAL(sd->drawarea,"draw",splcurve_draw,sd); + + return sd; +} + + +// modify anchor points in curve using mouse + +int splcurve_adjust(void *, GdkEventButton *event, spldat *sd) +{ + int ww, hh, kk; + int mx, my, button, evtype; + static int spc, ap, mbusy = 0, Fdrag = 0; // drag continuation logic + int minspc, minap, apset = 0; + float mxval, myval, cxval, cyval; + float dist2, mindist2 = 0; + float dist, dx, dy; + float minx = 0.01 * splcurve_minx; // % to absolute distance + + mx = event->x; // mouse position in drawing area + my = event->y; + evtype = event->type; + button = event->button; + + if (evtype == GDK_MOTION_NOTIFY) { + if (mbusy) return 0; // discard excess motion events + mbusy++; + zmainloop(); + mbusy = 0; + } + + if (evtype == GDK_BUTTON_RELEASE) { + Fdrag = 0; + return 0; + } + + ww = gtk_widget_get_allocated_width(sd->drawarea); // drawing area size + hh = gtk_widget_get_allocated_height(sd->drawarea); + + if (mx < 0) mx = 0; // limit edge excursions + if (mx > ww) mx = ww; + if (my < 0) my = 0; + if (my > hh) my = hh; + + if (evtype == GDK_BUTTON_PRESS) Fdrag = 0; // left or right click + + if (Fdrag) // continuation of drag + { + if (sd->vert[spc]) { + mxval = 1.0 * my / hh; // mouse position in curve space + myval = 1.0 * mx / ww; + } + else { + mxval = 1.0 * mx / ww; + myval = 1.0 * (hh - my) / hh; + } + + if (ap < sd->nap[spc] - 1) { // not the last anchor point + dx = sd->apx[spc][ap+1] - mxval; // get distance to next anchor point + if (dx < 0.01) return 0; // x-value not increasing, forbid + dy = sd->apy[spc][ap+1] - myval; + dist = sqrtf(dx * dx + dy * dy); + if (dist < minx) return 0; // too close, forbid + } + if (ap > 0) { // not the first anchor point + dx = mxval - sd->apx[spc][ap-1]; // get distance to prior anchor point + if (dx < 0.01) return 0; // x-value not increasing, forbid + dy = myval - sd->apy[spc][ap-1]; + dist = sqrtf(dx * dx + dy * dy); + if (dist < minx) return 0; // too close, forbid + } + + apset = 1; // mxval/myval = new node position + } + + else // mouse click or new drag begin + { + minspc = minap = -1; // find closest curve/anchor point + mindist2 = 999999; + + for (spc = 0; spc < sd->Nspc; spc++) // loop curves + { + if (! sd->fact[spc]) continue; // not active + + if (sd->vert[spc]) { + mxval = 1.0 * my / hh; // mouse position in curve space + myval = 1.0 * mx / ww; + } + else { + mxval = 1.0 * mx / ww; + myval = 1.0 * (hh - my) / hh; + } + + for (ap = 0; ap < sd->nap[spc]; ap++) // loop anchor points + { + cxval = sd->apx[spc][ap]; + cyval = sd->apy[spc][ap]; + dist2 = (mxval-cxval)*(mxval-cxval) + + (myval-cyval)*(myval-cyval); + if (dist2 < mindist2) { + mindist2 = dist2; // remember closest anchor point + minspc = spc; + minap = ap; + } + } + } + + if (minspc < 0) return 0; // impossible + spc = minspc; // nearest curve + ap = minap; // nearest anchor point + } + + if (evtype == GDK_BUTTON_PRESS && button == 3) // right click, remove anchor point + { + if (sqrtf(mindist2) > minx) return 0; // not close enough + if (sd->nap[spc] < 3) return 0; // < 2 anchor points would remain + sd->nap[spc]--; // decr. before loop + for (kk = ap; kk < sd->nap[spc]; kk++) { + sd->apx[spc][kk] = sd->apx[spc][kk+1]; + sd->apy[spc][kk] = sd->apy[spc][kk+1]; + } + splcurve_generate(sd,spc); // regenerate data for modified curve + gtk_widget_queue_draw(sd->drawarea); + sd->spcfunc(spc); // call user function + return 0; + } + + if (! Fdrag) // new drag or left click + { + if (sd->vert[spc]) { + mxval = 1.0 * my / hh; // mouse position in curve space + myval = 1.0 * mx / ww; + } + else { + mxval = 1.0 * mx / ww; + myval = 1.0 * (hh - my) / hh; + } + + if (sqrtf(mindist2) < minx) // anchor point close enough, + { // move this one to mouse position + if (ap < sd->nap[spc]-1) { // not the last anchor point + dx = sd->apx[spc][ap+1] - mxval; // get distance to next anchor point + if (dx < 0.01) return 0; // x-value not increasing, forbid + dy = sd->apy[spc][ap+1] - myval; + dist = sqrtf(dx * dx + dy * dy); + if (dist < minx) return 0; // too close, forbid + } + if (ap > 0) { // not the first anchor point + dx = mxval - sd->apx[spc][ap-1]; // get distance to prior anchor point + if (dx < 0.01) return 0; // x-value not increasing, forbid + dy = myval - sd->apy[spc][ap-1]; + dist = sqrtf(dx * dx + dy * dy); + if (dist < minx) return 0; // too close, forbid + } + + apset = 1; // mxval/myval = new node position + } + + else // none close, add new anchor point + { + minspc = -1; // find closest curve to mouse + mindist2 = 999999; + + for (spc = 0; spc < sd->Nspc; spc++) // loop curves + { + if (! sd->fact[spc]) continue; // not active + + if (sd->vert[spc]) { + mxval = 1.0 * my / hh; // mouse position in curve space + myval = 1.0 * mx / ww; + } + else { + mxval = 1.0 * mx / ww; + myval = 1.0 * (hh - my) / hh; + } + + cyval = splcurve_yval(sd,spc,mxval); + dist2 = fabsf(myval - cyval); + if (dist2 < mindist2) { + mindist2 = dist2; // remember closest curve + minspc = spc; + } + } + + if (minspc < 0) return 0; // impossible + if (mindist2 > minx) return 0; // not close enough to any curve + spc = minspc; + + if (sd->nap[spc] > 49) { + zmessageACK(Mwin,ZTX("Exceed 50 anchor points")); + return 0; + } + + if (sd->vert[spc]) { + mxval = 1.0 * my / hh; // mouse position in curve space + myval = 1.0 * mx / ww; + } + else { + mxval = 1.0 * mx / ww; + myval = 1.0 * (hh - my) / hh; + } + + for (ap = 0; ap < sd->nap[spc]; ap++) // find anchor point with next higher x + if (mxval <= sd->apx[spc][ap]) break; // (ap may come out 0 or nap) + + if (ap < sd->nap[spc] && sd->apx[spc][ap] - mxval < minx) // disallow < minx from next + return 0; // or prior anchor point + if (ap > 0 && mxval - sd->apx[spc][ap-1] < minx) return 0; + + for (kk = sd->nap[spc]; kk > ap; kk--) { // make hole for new point + sd->apx[spc][kk] = sd->apx[spc][kk-1]; + sd->apy[spc][kk] = sd->apy[spc][kk-1]; + } + + sd->nap[spc]++; // up point count + apset = 1; // mxval/myval = new node position + } + } + + if (evtype == GDK_MOTION_NOTIFY) Fdrag = 1; // remember drag is underway + + if (apset) + { + sd->apx[spc][ap] = mxval; // new or moved anchor point + sd->apy[spc][ap] = myval; // at mouse position + + splcurve_generate(sd,spc); // regenerate data for modified curve + if (sd->drawarea) gtk_widget_queue_draw(sd->drawarea); // redraw graph + if (sd->spcfunc) sd->spcfunc(spc); // call user function + } + + return 0; +} + + +// add a new anchor point to a curve +// spc: curve number +// px, py: node coordinates in the range 0-1 + +int splcurve_addnode(spldat *sd, int spc, float px, float py) +{ + int ap, kk; + float minx = 0.01 * splcurve_minx; // % to absolute distance + + for (ap = 0; ap < sd->nap[spc]; ap++) // find anchor point with next higher x + if (px <= sd->apx[spc][ap]) break; // (ap may come out 0 or nap) + + if (ap < sd->nap[spc] && sd->apx[spc][ap] - px < minx) // disallow < minx from next + return 0; // or prior anchor point + if (ap > 0 && px - sd->apx[spc][ap-1] < minx) return 0; + + for (kk = sd->nap[spc]; kk > ap; kk--) { // make hole for new point + sd->apx[spc][kk] = sd->apx[spc][kk-1]; + sd->apy[spc][kk] = sd->apy[spc][kk-1]; + } + + sd->apx[spc][ap] = px; // add node coordinates + sd->apy[spc][ap] = py; + + sd->nap[spc]++; // up point count + return 1; +} + + +// if height/width too small, make bigger + +int splcurve_resize(GtkWidget *drawarea) // 17.04 +{ + int ww = gtk_widget_get_allocated_width(drawarea); + int hh = gtk_widget_get_allocated_height(drawarea); + if (hh < ww/2) gtk_widget_set_size_request(drawarea,ww,ww/2); + return 1; +} + + +// for expose event or when a curve is changed +// draw all curves based on current anchor points + +int splcurve_draw(GtkWidget *drawarea, cairo_t *cr, spldat *sd) +{ + int ww, hh, spc, ap; + float xval, yval, px, py, qx, qy; + + ww = gtk_widget_get_allocated_width(sd->drawarea); // drawing area size + hh = gtk_widget_get_allocated_height(sd->drawarea); + if (ww < 50 || hh < 50) return 0; + + cairo_set_line_width(cr,1); + cairo_set_source_rgb(cr,0.7,0.7,0.7); + + for (int ii = 0; ii < sd->Nscale; ii++) // draw y-scale lines if any + { + px = ww * sd->xscale[0][ii]; + py = hh - hh * sd->yscale[0][ii]; + qx = ww * sd->xscale[1][ii]; + qy = hh - hh * sd->yscale[1][ii]; + cairo_move_to(cr,px,py); + cairo_line_to(cr,qx,qy); + } + cairo_stroke(cr); + + cairo_set_source_rgb(cr,0,0,0); + + for (spc = 0; spc < sd->Nspc; spc++) // loop all curves + { + if (! sd->fact[spc]) continue; // not active + + if (sd->vert[spc]) // vert. curve + { + for (py = 0; py < hh; py++) // generate all points for curve + { + xval = 1.0 * py / hh; + yval = splcurve_yval(sd,spc,xval); + px = ww * yval; + if (py == 0) cairo_move_to(cr,px,py); + cairo_line_to(cr,px,py); + } + cairo_stroke(cr); + + for (ap = 0; ap < sd->nap[spc]; ap++) // draw boxes at anchor points + { + xval = sd->apx[spc][ap]; + yval = sd->apy[spc][ap]; + px = ww * yval; + py = hh * xval; + cairo_rectangle(cr,px-2,py-2,4,4); + } + cairo_fill(cr); + } + else // horz. curve + { + for (px = 0; px < ww; px++) // generate all points for curve + { + xval = 1.0 * px / ww; + yval = splcurve_yval(sd,spc,xval); + py = hh - hh * yval; + if (px == 0) cairo_move_to(cr,px,py); + cairo_line_to(cr,px,py); + } + cairo_stroke(cr); + + for (ap = 0; ap < sd->nap[spc]; ap++) // draw boxes at anchor points + { + xval = sd->apx[spc][ap]; + yval = sd->apy[spc][ap]; + px = ww * xval; + py = hh - hh * yval; + cairo_rectangle(cr,px-2,py-2,4,4); + } + cairo_fill(cr); + } + } + + return 0; +} + + +// generate all curve data points when anchor points are modified + +int splcurve_generate(spldat *sd, int spc) +{ + int kk, kklo, kkhi; + float xval, yvalx; + + spline1(sd->nap[spc],sd->apx[spc],sd->apy[spc]); // compute curve fitting anchor points + + kklo = 1000 * sd->apx[spc][0] - 30; // xval range = anchor point range + if (kklo < 0) kklo = 0; // + 0.03 extra below/above + kkhi = 1000 * sd->apx[spc][sd->nap[spc]-1] + 30; + if (kkhi > 1000) kkhi = 1000; + + for (kk = 0; kk < 1000; kk++) // generate all points for curve + { + xval = 0.001 * kk; // remove anchor point limits + yvalx = spline2(xval); + if (yvalx < 0) yvalx = 0; // yval < 0 not allowed, > 1 OK + sd->yval[spc][kk] = yvalx; + } + + return 0; +} + + +// Retrieve curve data using interpolation of saved table of values + +float splcurve_yval(spldat *sd, int spc, float xval) +{ + int ii; + float x1, x2, y1, y2, y3; + + if (xval <= 0) return sd->yval[spc][0]; + if (xval >= 0.999) return sd->yval[spc][999]; + + x2 = 1000.0 * xval; + ii = x2; + x1 = ii; + y1 = sd->yval[spc][ii]; + y2 = sd->yval[spc][ii+1]; + y3 = y1 + (y2 - y1) * (x2 - x1); + return y3; +} + + +// load curve data from a file +// returns 0 if succcess, sd is initialized from file data +// returns 1 if fail (invalid file data), sd not modified + +int splcurve_load(spldat *sd, FILE *fid) +{ + char *pfile, *pp, buff[300]; + int nn, ii, jj, err, myfid = 0; + int Nspc, fact[10], vert[10], nap[10]; + float apx[10][50], apy[10][50]; + + if (! fid) // request file from user + { + pfile = zgetfile(ZTX("load curve from a file"),MWIN,"file",saved_curves_dirk); + if (! pfile) return 1; + fid = fopen(pfile,"r"); + zfree(pfile); + if (! fid) goto fail; + myfid = 1; + } + + pp = fgets_trim(buff,300,fid,1); + if (! pp) goto fail; + nn = sscanf(pp,"%d",&Nspc); // no. of curves + if (nn != 1) goto fail; + if (Nspc < 1 || Nspc > 10) goto fail; + if (Nspc != sd->Nspc) goto fail; + + for (ii = 0; ii < Nspc; ii++) // loop each curve + { + pp = fgets_trim(buff,300,fid,1); + if (! pp) goto fail; + nn = sscanf(pp,"%d %d %d",&fact[ii],&vert[ii],&nap[ii]); // active flag, vert flag, anchors + if (nn != 3) goto fail; + if (fact[ii] < 0 || fact[ii] > 1) goto fail; + if (vert[ii] < 0 || vert[ii] > 1) goto fail; + if (nap[ii] < 2 || nap[ii] > 50) goto fail; + + pp = fgets_trim(buff,300,fid,1); // anchor points: nnn/nnn nnn/nnn ... + + for (jj = 0; jj < nap[ii]; jj++) // anchor point values + { + pp = (char *) strField(buff,"/ ",2*jj+1); + if (! pp) goto fail; + err = convSF(pp,apx[ii][jj],0,1); + if (err) goto fail; + pp = (char *) strField(buff,"/ ",2*jj+2); + err = convSF(pp,apy[ii][jj],0,1); + if (err) goto fail; + } + } + + if (myfid) fclose(fid); + + sd->Nspc = Nspc; // copy curve data to caller's arg + + for (ii = 0; ii < Nspc; ii++) + { + sd->fact[ii] = fact[ii]; + sd->vert[ii] = vert[ii]; + sd->nap[ii] = nap[ii]; + + for (jj = 0; jj < nap[ii]; jj++) + { + sd->apx[ii][jj] = apx[ii][jj]; + sd->apy[ii][jj] = apy[ii][jj]; + } + } + + for (ii = 0; ii < Nspc; ii++) // generate curve data from anchor points + splcurve_generate(sd,ii); + + if (sd->drawarea) // redraw all curves + gtk_widget_queue_draw(sd->drawarea); + + return 0; // success + +fail: + if (fid && myfid) fclose(fid); + zmessageACK(Mwin,ZTX("curve file is invalid")); + return 1; +} + + +// save curve data to a file + +int splcurve_save(spldat *sd, FILE *fid) +{ + char *pfile, *pp; + int ii, jj, myfid = 0; + + if (! fid) + { + pp = zgetfile(ZTX("save curve to a file"),MWIN,"save",saved_curves_dirk); + if (! pp) return 1; + pfile = zstrdup(pp,8); + zfree(pp); + pp = strrchr(pfile,'/'); // force .curve extension + if (pp) pp = strrchr(pp,'.'); + if (pp) strcpy(pp,".curve"); + else strcat(pfile,".curve"); + + fid = fopen(pfile,"w"); + zfree(pfile); + if (! fid) return 1; + myfid = 1; + } + + fprintf(fid,"%d \n",sd->Nspc); // no. of curves + + for (ii = 0; ii < sd->Nspc; ii++) // loop each curve + { + fprintf(fid,"%d %d %d \n",sd->fact[ii],sd->vert[ii],sd->nap[ii]); // activ flag, vert flag, anchors + for (jj = 0; jj < sd->nap[ii]; jj++) // anchor point values + fprintf(fid,"%.4f/%.4f ",sd->apx[ii][jj],sd->apy[ii][jj]); + fprintf(fid,"\n"); + } + + if (myfid) fclose(fid); + return 0; +} + + +/******************************************************************************** + + edit transaction management + + edit_setup() get E0 if none, E0 > E1 > E3 + edit_cancel() free (E1 E3 ER) + edit_done() E3 > E0, free (E1 ER) add to undo stack + edit_undo() E3 > ER, E1 > E3 + edit_redo() ER > E3 + edit_reset() free ER, E1 > E3 + edit_fullsize() free (E1 E3) E0 > E1 > E3 + +********************************************************************************* + + Setup for a new edit transaction + Create E1 (edit input) and E3 (edit output) pixmaps from + previous edit (E0) or image file (new E0). + + FprevReq 0 edit full-size image + 1 edit preview image unless select area exists + + Farea 0 select_area is invalid and will be deleted (e.g. rotate) + 1 select_area not used but remains valid (e.g. red-eye) + 2 select_area can be used and remains valid (e.g. gamma) + +*********************************************************************************/ + +int edit_setup(editfunc &EF) +{ + int yn, rww, rhh, ftype; + int Fpreview; + char *pp; + + if (! curr_file) return 0; // no image file + + ftype = image_file_type(curr_file); + if (ftype != IMAGE && ftype != RAW) { // not editable 17.08 + pp = strrchr(curr_file,'/'); + if (pp) pp++; + zmessageACK(Mwin,ZTX("File cannot be edited \n %s"),pp); + return 0; + } + + if (FGWM != 'F') m_viewmode(0,"F"); // insure file view mode + + if (checkpend("busy block")) return 0; // blocking function + + if (CEF && CEF->zd) // if pending edit, complete it + zdialog_send_event(CEF->zd,"done"); + if (checkpend("edit")) return 0; // failed (HDR etc.) + + if (URS_pos > maxedits-2) { // undo/redo stack capacity reached + zmessageACK(Mwin,ZTX("Too many edits, please save image")); + return 0; + } + + if (Fscriptbuild && ! EF.Fscript) { // this function not scriptable + zmessageACK(Mwin,ZTX("this function cannot be scripted")); + return 0; + } + + free_filemap(); // free map memory for edit usage + + sa_validate(); // delete area if not valid + + if (EF.Farea == 0 && sa_stat) { // select area will be lost, warn user + yn = zmessageYN(Mwin,ZTX("Select area cannot be kept.\n" + "Continue?")); + if (! yn) return 0; + sa_unselect(); // unselect area + if (zdsela) zdialog_free(zdsela); + } + + if (EF.Farea == 2 && sa_stat && sa_stat != 3) { // select area exists and can be used, + yn = zmessageYN(Mwin,ZTX("Select area not active.\n" // but not active, ask user + "Continue?")); + if (! yn) return 0; + } + + if (! E0pxm) { // first edit for this file + E0pxm = PXM_load(curr_file,1); // get E0 image (poss. 16-bit color) + if (! E0pxm) return 0; + curr_file_bpc = f_load_bpc; + } + + paintlock(1); // block window updates + + if (URS_pos == 0) save_undo(); // initial image >> undo/redo stack + + Fpreview = 0; // assume no preview + + if (EF.FprevReq && ! Fzoom) // preview requested by edit func. + Fpreview = 1; + + if (EF.Farea == 2 && sa_stat == 3) // not if select area active + Fpreview = 0; + + if (E0pxm->ww * E0pxm->hh < 2000000) // if image is small, don't use preview + Fpreview = 0; + + if (E0pxm->ww < 1.4 * Dww && E0pxm->hh < 1.4 * Dhh) // if image slightly larger than window, + Fpreview = 0; // don't use preview + + if (Fpreview) { + if (Fpxb->ww * Dhh > Fpxb->hh * Dww) { // use preview image 1.4 * window size + rww = 1.4 * Dww; + if (rww < 1200) rww = 1200; // at least 1200 on one side + rhh = 1.0 * rww * Fpxb->hh / Fpxb->ww + 0.5; + } + else { + rhh = 1.4 * Dhh; + if (rhh < 1200) rhh = 1200; + rww = 1.0 * rhh * Fpxb->ww / Fpxb->hh + 0.5; + } + if (rww > Fpxb->ww) Fpreview = 0; + } + + if (Fpreview) { + E1pxm = PXM_rescale(E0pxm,rww,rhh); // scale image to preview size + sa_show(0,0); // hide select area if present + } + else E1pxm = PXM_copy(E0pxm); // else use full size imagez + + E3pxm = PXM_copy(E1pxm); // E1 >> E3 + + CEF = &EF; // set current edit function + CEF->Fmods = 0; // image not modified yet + CEF->Fpreview = Fpreview; + + CEF->thread_command = CEF->thread_status = 0; // no thread running + CEF->thread_pend = CEF->thread_done = CEF->thread_hiwater = 0; // no work pending or done + if (CEF->threadfunc) start_thread(CEF->threadfunc,0); // start thread func if any + + paintlock(0); // unblock window updates + Fpaintnow(); // update image synchronous + return 1; +} + + +/********************************************************************************/ + +// print error message if CEF invalid + +int CEF_invalid() +{ + if (CEF) return 0; + printz("CEF invalid \n"); + return 1; +} + + +/********************************************************************************/ + +// process edit cancel +// keep: retain zdialog, mousefunc, curves + +void edit_cancel(int keep) +{ + if (CEF_invalid()) return; + wrapup_thread(9); // tell thread to quit, wait + if (CEF_invalid()) return; + paintlock(1); // block window updates + + PXM_free(E1pxm); // free E1, E3, ER + PXM_free(E3pxm); + PXM_free(ERpxm); + PXM_free(E8pxm); + PXM_free(E9pxm); + + if (! keep) + { + if (CEF->zd == zd_thread) zd_thread = 0; // thread -> zdialog event + if (CEF->curves) zfree(CEF->curves); // free curves data + if (CEF->mousefunc == mouseCBfunc) freeMouse(); // if my mouse, free mouse + if (CEF->zd) zdialog_free(CEF->zd); // kill dialog + } + + CEF = 0; // no current edit func + paintlock(0); // unblock window updates + Fpaintnow(); // update image synchronous + return; +} + + +/********************************************************************************/ + +// process edit done +// keep: retain zdialog, mousefunc, curves + +void edit_done(int keep) +{ + if (CEF_invalid()) return; + wrapup_thread(8); // tell thread to finish, wait + if (CEF_invalid()) return; + paintlock(1); // block window updates + + if (CEF->Fmods) { // image was modified + PXM_free(E0pxm); + E0pxm = E3pxm; // E3 updated image >> E0 + E3pxm = 0; + PXM_free(E1pxm); // free E1, ER + PXM_free(ERpxm); + PXM_free(E8pxm); + PXM_free(E9pxm); + URS_pos++; // next undo/redo stack position + URS_max = URS_pos; // image modified - higher mods now obsolete + save_undo(); // save undo state (for following undo) + if (Fscriptbuild) addscript(CEF); // add to script file + } + + else { // not modified + PXM_free(E1pxm); // free E1, E3, ER + PXM_free(E3pxm); + PXM_free(ERpxm); + PXM_free(E8pxm); + PXM_free(E9pxm); + } + + if (! keep) + { + if (CEF->zd == zd_thread) zd_thread = 0; // thread -> zdialog event + if (CEF->curves) zfree(CEF->curves); // free curves data + if (CEF->mousefunc == mouseCBfunc) freeMouse(); // if my mouse, free mouse + if (CEF->zd) zdialog_free(CEF->zd); // kill dialog + } + + CEF = 0; // no current edit func + paintlock(0); // unblock window updates + Fpaintnow(); // update image synchronous + return; +} + + +/********************************************************************************/ + +// Convert from preview mode (window-size pixmaps) to full-size pixmaps. +// Called by the edit function prior to edit_done(). + +void edit_fullsize() +{ + if (CEF_invalid()) return; + wait_thread_idle(); // wait for thread idle + if (CEF_invalid()) return; + if (! CEF->Fpreview) return; // FprevReq ignored if small image + paintlock(1); // block window updates + PXM_free(E1pxm); // free preview pixmaps + PXM_free(E3pxm); + E1pxm = PXM_copy(E0pxm); // E0 >> E1, full size image + E3pxm = PXM_copy(E1pxm); // E1 >> E3 + PXB_free(Cstate->fpxb); + Cstate->fpxb = PXM_PXB_copy(E3pxm); // update Fpxb from image E3 + Fzoom = 0; + paintlock(0); // unblock window updates + CEF->Fpreview = 0; + return; +} + + +// edit undo, redo, reset functions +// these apply within an active edit function + +void edit_undo() +{ + F1_help_topic = "undo_redo"; + + if (CEF_invalid()) return; + wait_thread_idle(); // wait for thread idle + if (CEF_invalid()) return; + if (! CEF->Fmods) return; // not modified + paintlock(1); // block window updates + + PXM_free(ERpxm); // E3 >> redo copy + ERpxm = E3pxm; + E3pxm = PXM_copy(E1pxm); // E1 >> E3 + CEF->Fmods = 0; // reset image modified status + paintlock(0); // unblock window updates + Fpaintnow(); // update image synchronous + return; +} + + +void edit_redo() +{ + F1_help_topic = "undo_redo"; + + if (CEF_invalid()) return; + wait_thread_idle(); // wait for thread idle + if (CEF_invalid()) return; + if (! ERpxm) return; // no prior undo + paintlock(1); // block window updates + + PXM_free(E3pxm); // redo copy >> E3 + E3pxm = ERpxm; + ERpxm = 0; + CEF->Fmods = 1; // image modified + paintlock(0); // unblock window updates + Fpaintnow(); // update image synchronous + return; +} + + +void edit_reset() // reset E3 to E1 status +{ + if (CEF_invalid()) return; + wait_thread_idle(); // wait for thread idle + if (CEF_invalid()) return; + if (! CEF->Fmods) return; // not modified + paintlock(1); // block window updates + + PXM_free(ERpxm); // delete redo copy + PXM_free(E3pxm); + E3pxm = PXM_copy(E1pxm); // E1 >> E3 + CEF->Fmods = 0; // reset image modified status + paintlock(0); // unblock window updates + Fpaintnow(); // update image synchronous + return; +} + + +/********************************************************************************/ + +// Load/save all edit zdialog widgets data from/to a file. +// dirname for edit data files: /home//.fotoxx/funcname +// where zdialog data is saved for the respective function. +// return 0 = OK, +N = error + +int edit_load_widgets(editfunc *EF, FILE *fid) +{ + using namespace zfuncs; + + cchar *mess = ZTX("Load settings from file"); + zdialog *zd; + spldat *sd; + int myfid = 0; + char *filename, dirname[200], buff[1000]; + char *wname, *wdata, wdata2[1000]; + char *pp, *pp1, *pp2; + int ii, kk, err, cc1, cc2; + + zd = EF->zd; + sd = EF->curves; + + if (! fid) // fid from script + { + snprintf(dirname,200,"%s/%s",get_zhomedir(),EF->funcname); // directory for data files + filename = zgetfile(mess,MWIN,"file",dirname,0); // open data file + if (! filename) return 1; // user cancel + fid = fopen(filename,"r"); + zfree(filename); + if (! fid) { + zmessageACK(Mwin,"%s \n %s",filename,strerror(errno)); + return 1; + } + myfid = 1; + } + + for (ii = 0; ii < zdmaxwidgets; ii++) // read widget data recs + { + pp = fgets_trim(buff,1000,fid,1); + if (! pp) break; + if (strmatch(pp,"curves")) { + if (! sd) goto baddata; + err = splcurve_load(sd,fid); // load curves data + if (err) goto baddata; + continue; + } + if (strmatch(pp,"end")) break; + pp1 = pp; + pp2 = strstr(pp1," =="); + if (! pp2) continue; // widget has no data + cc1 = pp2 - pp1; + if (cc1 > 100) continue; + pp1[cc1] = 0; + wname = pp1; // widget name + pp2 += 3; + if (*pp2 == ' ') pp2++; + wdata = pp2; // widget data + cc2 = strlen(wdata); + if (cc2 < 1) wdata = (char *) ""; + if (cc2 > 300) continue; + repl_1str(wdata,wdata2,"\\n","\n"); // replace "\n" with newline chars. + kk = zdialog_put_data(zd,wname,wdata2); + if (! kk) goto baddata; + } + + if (myfid) fclose(fid); + return 0; + +baddata: + zmessageACK(Mwin,ZTX("file data does not fit dialog")); + if (myfid) fclose(fid); + return 1; +} + + +int edit_save_widgets(editfunc *EF, FILE *fid) +{ + using namespace zfuncs; + + cchar *mess = ZTX("Save settings to a file"); + zdialog *zd; + spldat *sd; + int myfid = 0; + char *filename, dirname[200]; + char *wtype, *wname, *wdata, wdata2[1000]; + int ii, cc; + + cchar *editwidgets = "entry zentry edit text togbutt check combo comboE " // widget types to save 17.08 + "radio spin zspin hscale vscale colorbutt"; + zd = EF->zd; + sd = EF->curves; + + if (! fid) // fid from script + { + snprintf(dirname,200,"%s/%s",get_zhomedir(),EF->funcname); // directory for data files + filename = zgetfile(mess,MWIN,"save",dirname,0); // open data file + if (! filename) return 1; // user cancel + fid = fopen(filename,"w"); + zfree(filename); + if (! fid) { + zmessageACK(Mwin,"%s \n %s",filename,strerror(errno)); + return 1; + } + myfid = 1; + } + + for (ii = 0; ii < zdmaxwidgets; ii++) + { + wtype = (char *) zd->widget[ii].type; + if (! wtype) break; + if (! strstr(editwidgets,wtype)) continue; + wname = (char *) zd->widget[ii].name; // write widget data recs: + wdata = zd->widget[ii].data; // widgetname == widgetdata + if (! wdata) continue; + cc = strlen(wdata); + if (cc > 900) continue; + repl_1str(wdata,wdata2,"\n","\\n"); + fprintf(fid,"%s == %s \n",wname,wdata); + } + + if (sd) { + fprintf(fid,"curves\n"); + splcurve_save(sd,fid); + } + + fprintf(fid,"end\n"); + + if (myfid) fclose(fid); + return 0; +} + + +// functions to support [prev] buttons in edit dialogs +// load or save last-used widgets + +int edit_load_prev_widgets(editfunc *EF) +{ + using namespace zfuncs; + + char filename[200]; + FILE *fid; + int err; + + snprintf(filename,200,"%s/%s/%s",get_zhomedir(),EF->funcname,"last-used"); + fid = fopen(filename,"r"); + if (! fid) { + zmessageACK(Mwin,"%s \n %s",filename,strerror(errno)); + return 1; + } + + err = edit_load_widgets(EF,fid); + fclose(fid); + return err; +} + + +int edit_save_last_widgets(editfunc *EF) +{ + using namespace zfuncs; + + char filename[200], dirname[200]; + FILE *fid; + int err; + + snprintf(filename,200,"%s/%s/%s",get_zhomedir(),EF->funcname,"last-used"); + fid = fopen(filename,"w"); + if (! fid) { + snprintf(dirname,200,"%s/%s",get_zhomedir(),EF->funcname); // create missing directory 17.08 + err = mkdir(dirname,0760); + if (err) { + printz("%s \n %s \n",dirname,strerror(errno)); + return 1; + } + fid = fopen(filename,"w"); // open again + } + + if (! fid) { + printz("%s \n %s \n",filename,strerror(errno)); + return 1; + } + + err = edit_save_widgets(EF,fid); + fclose(fid); + return err; +} + + +/******************************************************************************** + undo / redo menu buttons +*********************************************************************************/ + +// [undo/redo] menu function +// Call m_undo() / m_redo() if left / right mouse click on menu. +// If A key is pressed, call undo_all() or redo_all(). +// If middle mouse button is clicked, pop-up a list of all edit steps +// and choose a step to go back to. + +void m_undo_redo(GtkWidget *, cchar *) +{ + void undo_redo_choice(GtkWidget *, cchar *menu); + + GtkWidget *popmenu; + int button = zfuncs::vmenuclickbutton; + char menuitem[40], flag; + + F1_help_topic = "undo_redo"; + + if (button == 1) { + if (KBkey == GDK_KEY_a) undo_all(); // undo all steps + else m_undo(0,0); // undo one step + } + + if (button == 2) // go back to selected edit step + { + if (URS_max == 0) return; + popmenu = create_popmenu(); + for (int ii = 0; ii < 30; ii++) { // insert all edit steps + if (ii > URS_max) break; + if (ii == URS_pos) flag = '*'; // flag step matching current status + else flag = ' '; + snprintf(menuitem,40,"%d %s %c",ii,URS_funcs[ii],flag); + add_popmenu_item(popmenu,menuitem,undo_redo_choice,(char *) &Nval[ii],0); + } + popup_menu(Mwin,popmenu); + } + + if (button == 3) { + if (KBkey == GDK_KEY_a) redo_all(); // redo all steps + else m_redo(0,0); // redo one step + } + + return; +} + + +// popup menu response function + +void undo_redo_choice(GtkWidget *, cchar *menu) +{ + int nn = *((int *) menu); + if (nn < 0 || nn > URS_max) return; + URS_pos = nn; + load_undo(); + return; +} + + +// [undo] menu function - reinstate previous edit in undo/redo stack + +void m_undo(GtkWidget *, cchar *) +{ + if (CEF) { // undo active edit + edit_undo(); + return; + } + if (URS_pos == 0) return; // undo past edit + URS_pos--; + load_undo(); + return; +} + + +// [redo] menu function - reinstate next edit in undo/redo stack + +void m_redo(GtkWidget *, cchar *) +{ + if (CEF) { // redo active edit + edit_redo(); + return; + } + if (URS_pos == URS_max) return; // redo past edit + URS_pos++; + load_undo(); + return; +} + + +// undo all edits of the current image +// (discard modifications) + +void undo_all() +{ + if (CEF) return; // not if edit active + if (URS_pos == 0) return; + URS_pos = 0; // original image + load_undo(); + return; +} + + +// redo all edits of the current image +// (reinstate all modifications) + +void redo_all() +{ + if (CEF) return; // not if edit active + if (URS_pos == URS_max) return; + URS_pos = URS_max;; // image with last edit applied + load_undo(); + return; +} + + +// Save E0 to undo/redo file stack +// stack position = URS_pos + +void save_undo() // overhauled for 4 GB files +{ + char *pp, buff[24]; + FILE *fid; + int ww, hh, nc, nn; + uint cc1, cc2; + uint ccmax = 256 * MEGA; + + ww = E0pxm->ww; + hh = E0pxm->hh; + nc = E0pxm->nc; + + pp = strstr(URS_filename,"undo_"); // get undo/redo stack filename to use + if (! pp) zappcrash("undo/redo stack corrupted"); + snprintf(pp+5,3,"%02d",URS_pos); + + fid = fopen(URS_filename,"w"); + if (! fid) goto writefail; + + snprintf(buff,24,"fotoxx %05d %05d %d",ww,hh,nc); // write header + nn = fwrite(buff,20,1,fid); + if (nn != 1) goto writefail; + + cc1 = ww * hh * nc * sizeof(float); // write ww * hh RGB(A) pixels + cc2 = 0; + while (cc1) { + if (cc1 <= ccmax) { + pp = (char *) E0pxm->pixels; + nn = fwrite(pp+cc2,cc1,1,fid); + if (nn != 1) goto writefail; + break; + } + else { + pp = (char *) E0pxm->pixels; + nn = fwrite(pp+cc2,ccmax,1,fid); + if (nn != 1) goto writefail; + cc1 -= ccmax; + cc2 += ccmax; + } + } + + fclose(fid); + + if (URS_pos == 0) { // stack posn. 0 = original image + strcpy(URS_funcs[0],"original"); // function name for original image + URS_saved[0] = 1; // original image already on disk + } + else { // stack position + if (CEF_invalid()) return; // must have an edit function + strncpy0(URS_funcs[URS_pos],CEF->funcname,32); // edit function name + URS_saved[URS_pos] = 0; // not yet saved to disk + } + + return; + +writefail: + zmessageACK(Mwin,"undo/redo stack write failure: %d \n" + "(may be out of disk space)",errno); + exit(1); +} + + +// Load E0 from undo/redo file stack +// stack position = URS_pos + +void load_undo() // overhauled +{ + char *pp, buff[24]; + FILE *fid; + int ww, hh, nc, nn; + uint cc1, cc2; + uint ccmax = 128 * MEGA; // reduced from 256 17.04 + + pp = strstr(URS_filename,"undo_"); + if (! pp) goto err1; + snprintf(pp+5,3,"%02d",URS_pos); + + fid = fopen(URS_filename,"r"); + if (! fid) goto err2; + + nn = fread(buff,20,1,fid); + if (nn != 1) goto err3; + nn = sscanf(buff,"fotoxx %d %d %d",&ww,&hh,&nc); + if (nn != 3) goto err4; + + PXM_free(E0pxm); + E0pxm = PXM_make(ww,hh,nc); + + cc1 = ww * hh * nc * sizeof(float); + cc2 = 0; + while (cc1) { + if (cc1 <= ccmax) { + pp = (char *) E0pxm->pixels; + nn = fread(pp+cc2,cc1,1,fid); + if (nn != 1) goto err3; + break; + } + else { + pp = (char *) E0pxm->pixels; + nn = fread(pp+cc2,ccmax,1,fid); + if (nn == 0 && errno == 11) { + zmessage_post(Mwin,3,"fread() fail, resource unavailable",0); // this shit does happen 17.04 + exit(11); // there is no recovery + } + if (nn != 1) goto err3; + cc1 -= ccmax; + cc2 += ccmax; + } + } + + fclose(fid); + + sa_validate(); // delete area if invalid + if (Cdrawin) Fpaintnow(); // update image synchronous + return; + +err1: + printz("err1: %s \n",URS_filename); // extended diagnostics 17.01 + goto readfail; + +err2: + printz("err2: open failure, errno: %d \n",errno); + goto readfail; + +err3: + printz("err3: fread() failure, errno: %d \n",errno); + goto readfail; + +err4: + printz("err4: %s \n",buff); + goto readfail; + +readfail: + zmessageACK(Mwin,"undo/redo stack read failure \n"); + exit(1); +} + + +/********************************************************************************/ + +// check for busy or pending conditions and message the user +// returns 1 if busy or pending condition +// 0 if nothing is pending or user decides to discard mods +// conditions: +// edit edit function is active (CEF not null) +// mods current file has unsaved mods (image or metadata edits) +// busy function is still running +// block edit function mutex flag +// all check all the above +// quiet suppress user message + +int checkpend(cchar *list) +{ + int edit, mods, busy, block, all, quiet, pend, choice; + cchar *modmess = ZTX("This action will discard changes to current image"); + cchar *activemess = ZTX("prior function still active"); + cchar *keep = ZTX("Keep"); + cchar *discard = ZTX("Discard"); + + edit = mods = busy = block = all = quiet = pend = 0; + + if (strstr(list,"edit")) edit = 1; + if (strstr(list,"mods")) mods = 1; + if (strstr(list,"busy")) busy = 1; + if (strstr(list,"block")) block = 1; + if (strstr(list,"all")) all = 1; + if (strstr(list,"quiet")) quiet = 1; + + if (all) edit = mods = busy = block = 1; + + if ((edit && CEF) || (busy && (Ffuncbusy | Fthreadbusy)) || (block && Fblock)) { + pend = 1; + if (! quiet) zmessage_post_bold(Mwin,2,activemess); + } + + if (! pend && mods) { + if (CEF && CEF->Fmods && ! CEF->Fsaved) pend = 1; // active edits unsaved + if (URS_pos > 0 && URS_saved[URS_pos] == 0) pend = 1; // completed edits unsaved + if (Fmetamod) pend = 1; // metadata edits unsaved + if (pend && ! quiet) { + choice = zdialog_choose(Mwin,modmess,keep,discard,null); // ask user + if (choice == 2) { // choice is discard + if (CEF && CEF->zd) + zdialog_send_event(CEF->zd,"cancel"); + if (URS_pos > 0) undo_all(); // undo prior edits + Fmetamod = 0; // discard metadata edits + pend = 0; + } + else m_viewmode(0,"F"); // keep - back to current image + } + } + + return pend; // 1 if something pending +} + + +/********************************************************************************/ + +// zdialog mouse capture and release + +void takeMouse(mcbFunc func, GdkCursor *cursor) // capture mouse for dialog +{ + if (! Cdrawin) return; + if (! gdkwin) return; + freeMouse(); + mouseCBfunc = func; + Mcapture = 1; + gdk_window_set_cursor(gdkwin,cursor); + return; +} + +void freeMouse() // free mouse for main window +{ + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Mcapture) return; + mouseCBfunc = 0; + Mcapture = 0; + gdk_window_set_cursor(gdkwin,0); // set normal cursor + return; +} + + +/******************************************************************************** + + functions to manage working threads + + main level thread management + start_thread(func,arg) start thread running + signal_thread() signal thread that work is pending + wait_thread_idle() wait for pending work complete + wrapup_thread(command) wait for exit or command thread exit + + thread function + thread_idle_loop() wait for pending work, exit if commanded + thread_exit() exit thread unconditionally + + thread_status (thread ownership + 0 no thread is running + 1 thread is running and idle (no work) + 2 thread is working + 0 thread has exited + + thread_command (main program ownership) + 0 idle, initial status + 8 exit when pending work is done + 9 exit now, unconditionally + + thread_pend work requested counter + thread_done work done counter + thread_hiwater high water mark + +*********************************************************************************/ + +// start thread that does the edit work + +void start_thread(threadfunc func, void *arg) +{ + CEF->thread_status = 1; // thread is running + CEF->thread_command = CEF->thread_pend = CEF->thread_done + = CEF->thread_hiwater = 0; // nothing pending + start_detached_thread(func,arg); + return; +} + + +// signal thread that new work is pending + +void signal_thread() +{ + if (CEF_invalid()) return; + if (CEF->thread_status > 0) CEF->thread_pend++; + return; +} + + +// wait for edit thread to complete pending work and become idle + +void wait_thread_idle() +{ + if (CEF_invalid()) return; + if (! CEF->thread_status) return; + if (CEF->thread_pend == CEF->thread_done) return; + while (CEF->thread_status && CEF->thread_pend > CEF->thread_done) { + zmainloop(); + zsleep(0.01); + if (CEF_invalid()) return; + } + return; +} + + +// wait for thread exit or command thread exit +// command = 0 wait for normal completion +// 8 finish pending work and exit +// 9 quit, exit now + +void wrapup_thread(int command) +{ + if (CEF_invalid()) return; + if (! CEF->thread_status) return; + CEF->thread_command = command; // tell thread to quit or finish + while (CEF->thread_status > 0) { // wait for thread to finish + zmainloop(); + zsleep(0.01); + if (CEF_invalid()) return; + } + return; +} + + +// called only from edit threads +// idle loop - wait for work request or exit command + +void thread_idle_loop() +{ + if (CEF_invalid()) thread_exit(); + if (CEF->thread_status == 2) zadd_locked(Fthreadbusy,-1); + CEF->thread_status = 1; // status = idle + CEF->thread_done = CEF->thread_hiwater; // work done = high-water mark + + while (true) + { + if (CEF->thread_command == 9) thread_exit(); // quit now command + if (CEF->thread_command == 8) // finish work and exit + if (CEF->thread_pend <= CEF->thread_done) thread_exit(); + if (CEF->thread_pend > CEF->thread_done) break; // wait for work request + zsleep(0.01); + } + + CEF->thread_hiwater = CEF->thread_pend; // set high-water mark + CEF->thread_status = 2; // thread is working + zadd_locked(Fthreadbusy,+1); + return; // perform edit +} + + +// exit thread unconditionally, called only from edit threads + +void thread_exit() +{ + wait_wthreads(); // wait for worker threads if any + if (CEF_invalid()) pthread_exit(0); + if (CEF->thread_status == 2) zadd_locked(Fthreadbusy,-1); + CEF->thread_pend = CEF->thread_done = CEF->thread_hiwater = 0; + CEF->thread_status = 0; + pthread_exit(0); // "return" cannot be used here +} + + +/********************************************************************************/ + +// edit support functions for worker threads (per processor core) + +void start_wthread(threadfunc func, void *arg) // start thread and increment busy count +{ // may be called from a thread + zadd_locked(Fthreadbusy,+1); + zadd_locked(wthreads_busy,+1); + start_detached_thread(func,arg); + return; +} + + +void exit_wthread() // decrement busy count and exit thread +{ + zadd_locked(Fthreadbusy,-1); + zadd_locked(wthreads_busy,-1); + pthread_exit(0); // "return" cannot be used here +} + + +void wait_wthreads(int block) // wait for all worker threads done +{ // may be called from thread or non-thread + if (CEF) { + while (wthreads_busy) { + zsleep(0.001); + if (block) continue; + zmainloop(); + } + if (CEF_invalid()) return; + } + else { + while (wthreads_busy) { + zsleep(0.0003); + if (block) continue; + zmainloop(); + } + } + return; +} + + +/********************************************************************************/ + +// table for loading and saving adjustable parameters between sessions + +typedef struct { + char name[20]; + char type[12]; + int count; + void *location; +} param; + +#define Nparms 49 +param paramTab[Nparms] = { +// name type count location +{ "fotoxx release", "char", 1, &Prelease }, +{ "first time", "int", 1, &Ffirsttime }, +{ "session count", "int", 1, &sessioncount }, +{ "window geometry", "int", 4, &mwgeom }, +{ "thumbnail size", "int", 1, &navi::thumbsize }, +{ "menu style", "char", 1, &menu_style }, +{ "F-base-color", "int", 3, &FBrgb }, +{ "G-base-color", "int", 3, &GBrgb }, +{ "menu font color", "int", 3, &MFrgb }, +{ "menu background", "int", 3, &MBrgb }, +{ "index level", "int", 1, &Findexlev }, +{ "FM index level", "int", 1, &FMindexlev }, +{ "icon size", "int", 1, &iconsize }, +{ "dialog font", "char", 1, &dialog_font }, +{ "drag option", "int", 1, &Fdragopt }, +{ "zoom count", "int", 1, &zoomcount }, +{ "map_dotsize", "int", 1, &map_dotsize }, +{ "last version", "int", 1, &Flastversion }, +{ "shift right", "int", 1, &Fshiftright }, +{ "xmeta changed", "int", 1, &xmeta_changed }, +{ "curve node dist %", "float", 1, &splcurve_minx }, +{ "start display", "char", 1, &startdisplay }, +{ "start album", "char", 1, &startalbum }, +{ "start image file", "char", 1, &startfile }, +{ "start directory", "char", 1, &startdirk }, +{ "last curr file", "char", 1, &last_curr_file }, +{ "last galleryname", "char", 1, &last_galleryname }, +{ "last gallerytype", "int", 1, &last_gallerytype }, +{ "copy/move location", "char", 1, ©move_loc }, +{ "color palette file", "char", 1, &color_palette_file }, +{ "netmap source", "char", 1, &netmap_source }, +{ "mapbox access key", "char", 1, &mapbox_access_key }, +{ "grid base", "int", 10, &gridsettings[0] }, +{ "grid trim/rotate", "int", 10, &gridsettings[1] }, +{ "grid unbend", "int", 10, &gridsettings[2] }, +{ "grid warp curved", "int", 10, &gridsettings[3] }, +{ "grid warp linear", "int", 10, &gridsettings[4] }, +{ "grid warp affine", "int", 10, &gridsettings[5] }, +{ "RAW file types", "char", 1, &RAWfiletypes }, +{ "video file types", "char", 1, &VIDEOfiletypes }, // bugfix 17.08.3 +{ "trim width", "int", 1, &trimww }, +{ "trim height", "int", 1, &trimhh }, +{ "trim buttons", "char", 6, &trimbuttons }, +{ "trim ratios", "char", 6, &trimratios }, +{ "edit resize", "int", 2, &editresize }, +{ "jpeg def quality", "int", 1, &jpeg_def_quality }, +{ "lens mm", "float", 1, &lens_mm }, +{ "SS KB keys", "char", 1, &ss_KBkeys }, // 18.01 +{ "printer color map", "char", 1, &colormapfile } }; + + +// save parameters to file /.../.fotoxx/parameters + +void save_params() +{ + FILE *fid; + char buff[1050], text[1000]; // limit for character data cc + char *name, *type; + int count; + void *location; + char **charloc; + int *intloc; + float *floatloc; + + last_curr_file = last_galleryname = 0; // save curr_file and gallery poop + last_gallerytype = TNONE; // for next session startup + if (curr_file && *curr_file == '/') + last_curr_file = zstrdup(curr_file); + if (navi::galleryname) { + last_galleryname = zstrdup(navi::galleryname); + last_gallerytype = navi::gallerytype; + } + + snprintf(buff,199,"%s/parameters",get_zhomedir()); // open output file + fid = fopen(buff,"w"); + if (! fid) return; + + for (int ii = 0; ii < Nparms; ii++) // write table of state data + { + name = paramTab[ii].name; + type = paramTab[ii].type; + count = paramTab[ii].count; + location = paramTab[ii].location; + charloc = (char **) location; + intloc = (int *) location; + floatloc = (float *) location; + + fprintf(fid,"%-20s %-8s %02d ",name,type,count); // write parm name, type, count + + for (int kk = 0; kk < count; kk++) // write "value" "value" ... + { + if (strmatch(type,"char")) { + if (! *charloc) break; // missing, discontinue this parameter + repl_1str(*charloc++,text,"\n","\\n"); // replace newlines with "\n" + fprintf(fid," \"%s\"",text); + } + if (strmatch(type,"int")) + fprintf(fid," \"%d\"",*intloc++); + + if (strmatch(type,"float")) + fprintf(fid," \"%.2f\"",*floatloc++); + } + + fprintf(fid,"\n"); // write EOR + } + + fprintf(fid,"\n"); + fclose(fid); // close file + + return; +} + + +// load parameters from file /.../.fotoxx/parameters + +void load_params() +{ + FILE *fid; + int ii, err, pcount; + int Idata; + float Fdata; + char buff[1000], text[1000], *pp; + char name[20], type[12], count[8], data[1000]; + void *location; + char **charloc; + int *intloc; + float *floatloc; + + snprintf(buff,199,"%s/parameters",get_zhomedir()); // open parameters file + fid = fopen(buff,"r"); + if (! fid) return; // none, defaults are used + + while (true) // read parameters + { + pp = fgets_trim(buff,999,fid,1); + if (! pp) break; + if (*pp == '#') continue; // comment + if (strlen(pp) < 40) continue; // rubbish + + err = 0; + + strncpy0(name,pp,20); // parm name + strTrim2(name); + + strncpy0(type,pp+22,8); // parm type + strTrim2(type); + + strncpy0(count,pp+32,4); // parm count + strTrim2(count); + err = convSI(count,pcount); + + strncpy0(data,pp+38,1000); // parm value(s) + strTrim2(data); + + for (ii = 0; ii < Nparms; ii++) // match file record to param table + { + if (! strmatch(name,paramTab[ii].name)) continue; // parm name + if (! strmatch(type,paramTab[ii].type)) continue; // parm type + if (paramTab[ii].count != pcount) continue; // parm count + break; + } + + if (ii == Nparms) continue; // not found, ignore file param + + location = paramTab[ii].location; // get parameter memory location + charloc = (char **) location; + intloc = (int *) location; + floatloc = (float *) location; + + for (ii = 1; ii <= pcount; ii++) // get parameter value(s) + { + pp = (char *) strField(data,' ',ii); + if (! pp) break; + + if (strmatch(type,"char")) { + repl_1str(pp,text,"\\n","\n"); // replace "\n" with real newlines + *charloc++ = zstrdup(text); + } + + if (strmatch(type,"int")) { + err = convSI(pp,Idata); + if (err) continue; + *intloc++ = Idata; + } + + if (strmatch(type,"float")) { + err = convSF(pp,Fdata); + if (err) continue; + *floatloc++ = Fdata; + } + } + } + + fclose(fid); + + for (ii = 0; ii < Nparms; ii++) // set any null strings to "" + { + if (! strmatch(paramTab[ii].type,"char")) continue; + charloc = (char **) paramTab[ii].location; + pcount = paramTab[ii].count; + for (int jj = 0; jj < pcount; jj++) + if (! charloc[jj]) + charloc[jj] = zstrdup("",20); + } + + zoomratio = pow( 2.0, 1.0 / zoomcount); // set zoom ratio from zoom count + + return; +} + + +/********************************************************************************/ + +// free all resources associated with the current image file + +void free_resources(int fkeepundo) +{ + paintlock(1); // block window updates + + if (! fkeepundo) + shell_quiet("rm -f %s/undo_*",tempdir); // remove undo/redo files + + if (Fshutdown) return; // stop here if shutdown mode + + URS_pos = URS_max = 0; // reset undo/redo stack + Fmetamod = 0; // no unsaved metadata changes + sa_unselect(); // unselect select area + + Nptoplines = Ntoplines = 0; // no toplines + Ntopcircles = 0; // no topcircles + Ntoptext = 0; // no toptext + Fbusy_goal = 0; // not busy + + if (curr_file) { + if (zdsela) zdialog_free(zdsela); // kill select area dialog if active + freeMouse(); // free mouse + zfree(curr_file); // free image file + curr_file = 0; + *paneltext = 0; + } + + gtk_window_set_title(MWIN,Arelease); // sparse title + + PXB_free(Fpxb); + PXM_free(E0pxm); + PXM_free(E1pxm); + PXM_free(E3pxm); + PXM_free(ERpxm); + PXM_free(E8pxm); + PXM_free(E9pxm); + + paintlock(0); // unblock window updates + return; +} + + +/********************************************************************************/ + +// compare two floats for significant difference +// signf: e.g. 0.01 for 1% threshold of significance +// return: 0 difference not significant +// +1 d1 > d2 +// -1 d1 < d2 + +int sigdiff(float d1, float d2, float signf) +{ + float diff = fabsf(d1-d2); + if (diff == 0.0) return 0; + diff = diff / (fabsf(d1) + fabsf(d2)); + if (diff < signf) return 0; + if (d1 > d2) return 1; + else return -1; +} + + + diff -Nru fotoxx-18.01/fotoxx-18.01.cc fotoxx-18.01.1/fotoxx-18.01.cc --- fotoxx-18.01/fotoxx-18.01.cc 2017-12-30 20:12:21.000000000 +0000 +++ fotoxx-18.01.1/fotoxx-18.01.cc 1970-01-01 00:00:00.000000000 +0000 @@ -1,4667 +0,0 @@ -/******************************************************************************** - - Fotoxx edit photos and manage collections - - Copyright 2007-2018 Michael Cornelison - source code URL: https://kornelix.net - contact: kornelix@posteo.de - - 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 3 of the License, or - (at your option) any later version. See https://www.gnu.org/licenses - - 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. - -********************************************************************************* - - main main program, set up defaults - initzfunc initializations, carry out command line options - - delete_event response function for main window delete event - destroy_event response function for main window destroy event - state_event response function for main window fullscreen state change - drop_event response function for main window file drag-drop event - - gtimefunc periodic function - update_Fpanel update status parameters on F window top panel - - paintlock block window updates from main thread or created thread - Fpaint main / drawing window refresh (draw signal response function) - Fpaintnow immediate Fpaint, not callable from threads - Fpaint2 queue Fpaint, callable from threads - Fpaint3 update drawing window section from updated E3 section - Fpaint4 update drawing window section (direct write) - Fpaint3_thread Fpaint3, callable from threads - - mouse_event mouse event response function - mouse_convert convert mouse/window space to image space - m_zoom main window zoom in/out function - KBevent send KB key from dialog to main window - KBpress KB key press event function - win_fullscreen set main window full screen status - win_unfullscreen restore main window to former size - set_mwin_title update the main window title bar - - draw_pixel draw one overlay pixel using image space - erase_pixel erase one pixel - draw_line draw overlay line in image space - erase_line erase line - draw_toplines draw or redraw a set of overlay lines - draw_gridlines draw grid lines over image - add_toptext add to set of overlay text strings - draw_toptext draw set of overlay text strings - erase_toptext remove text from set of overlay text strings - draw_text draw text on window in image space - add_topcircle add a circle to set of overlay circles - draw_topcircles draw the set of overlay circles in window space - erase_topcircles erase the set of overlay circles - draw_mousecircle draw a circle around pointer in image space - draw_mousecircle2 2nd instance for paint/clone tracking circle - draw_mousearc draw an ellipse around pointer in image space - - splcurve_init set up a spline curve drawing area - splcurve_adjust mouse event function to manipulate curve nodes - splcurve_addnode add an anchor point to a curve - splcurve_resize resize drawing area if too small - splcurve_draw draw curve through nodes - splcurve_generate generate x/y table of values from curve - splcurve_yval get curve y-value for given x-value - splcurve_load load curve data from a saved file - splcurve_save save curve data to a file - - edit_setup start an image edit function - CEF_invalid diagnostic - edit_cancel cancle image edit - edit_done finish image edit - edit_fullsize convert preview to full size edit - edit_undo undo current edit (reset) - edit_redo redo current edit - edit_reset reset all edit changes - edit_load_widgets load zdialog widgets and curves from a file - edit_save_widgets save last-used widgets (for [done] buttons) - edit_load_prev_widgets load last-used widgets (for [prev] buttons) - edit_save_last_widgets save last-used widgets (for [prev] buttons) - - m_undo_redo undo/redo depending on mouse button - undo_redo_choice popup menu response function - m_undo restore previous edit in undo/redo stack - m_redo restore next edit in undo/redo stack - undo_all undo all edits for image - redo_all redo all edits for image - save_undo save image in the undo stack - load_undo load image from the undo stack - checkpend check status: edit mods busy block any quiet - takeMouse set mouse event function and special cursor - freeMouse remove mouse event function, set normal cursor - - start_thread start thread running - signal_thread signal thread that work is pending - wait_thread_idle wait for pending work complete - wrapup_thread wait for thread exit or command thread exit - thread_idle_loop wait for pending work, exit if commanded - thread_exit exit thread unconditionally - start_wthread start a working thread per SMP processor - exit_wthread exit a working thread - wait_wthreads wait for all working threads to exit - - save_params save parameters when fotoxx exits - load_params load parameters at fotoxx startup - free_resources free resources for the current image file - sigdiff compare 2 floats with margin of significance - -*********************************************************************************/ - -#define EX // disable extern declarations -#include "fotoxx.h" // (variables in fotoxx.h are defined) - -/********************************************************************************/ - - -// fotoxx main program - -using namespace zfuncs; - -int main(int argc, char *argv[]) -{ - char lang[8] = "", *homedir, *temp; - int Fclone=0, cloxx=0, cloyy=0, cloww=0, clohh=0; - int ii, jj, cc, err; - char cssfile[200]; - STATB statb; - - appimage = getenv("APPIMAGE"); // detect appimage package 17.04 - Arelease = Frelease; // "fotoxx=NN.N" - if (appimage) Arelease = Frelease "-appimage"; // "fotoxx-NN.N-appimage" - printz("%s \n",Arelease); - if (appimage) make_appimage_desktop(); // add desktop menu/launcher 17.08 - - if (argc > 1 && strmatchV(argv[1],"-ver","-v",0)) exit(0); // if fotoxx -ver, exit now - - setenv("GTK_THEME","default",0); // set theme if missing (KDE etc.) 17.08 - setenv("GDK_BACKEND","x11",1); // wayland 18.01 - - printz("initz. clutter and GTK \n"); - if (gtk_clutter_init(&argc,&argv) != CLUTTER_INIT_SUCCESS) { - printz("failure \n"); - exit(1); - } - - homedir = 0; - if (argc > 2 && strmatch(argv[1],"-home")) homedir = argv[2]; // relocate user directory - - zinitapp("fotoxx",homedir); // initz. app directories - - // modify GTK widgets to take less screen space - - snprintf(cssfile,200,"%s/widgets.css",get_zhomedir()); // 18.01 - GtkStyleProvider *provider = (GtkStyleProvider *) gtk_css_provider_new(); - gtk_style_context_add_provider_for_screen(zfuncs::screen,provider,999); - gtk_css_provider_load_from_path(GTK_CSS_PROVIDER(provider),cssfile,0); - - // initialize externals to default values (saved parameters will override) - - strcpy(zfuncs::zappname,Frelease); // app name and version - Ffirsttime = 1; // first startup (params override) - Findexlev = 2; // full image index processing - FMindexlev = 2; // " also if start via file manager - Pindexlev = -1; // no -index command parameter - xxrec_tab = 0; // no image index yet - Nxxrec = Findexvalid = 0; - xmeta_changed = 0; // flag, indexed metadata changed 18.01 - Prelease = zstrdup("unknown"); // prev. fotoxx release (params override) - mwgeom[0] = mwgeom[1] = 100; // default main window geometry - mwgeom[2] = 1200; mwgeom[3] = 800; - *paneltext = 0; // no status bar text - trimww = 1600; // default initial image trim size 17.04 - trimhh = 1000; - trimbuttons[0] = zstrdup("5:4"); trimratios[0] = zstrdup("5:4"); // default trim ratio buttons - trimbuttons[1] = zstrdup("4:3"); trimratios[1] = zstrdup("4:3"); - trimbuttons[2] = zstrdup("8:5"); trimratios[2] = zstrdup("8:5"); - trimbuttons[3] = zstrdup("16:9"); trimratios[3] = zstrdup("16:9"); - trimbuttons[4] = zstrdup("2:1"); trimratios[4] = zstrdup("2:1"); - trimbuttons[5] = zstrdup("gold"); trimratios[5] = zstrdup("1.62:1"); - editresize[0] = 1600; // default initial resize size - editresize[1] = 1200; - currgrid = 0; // default initial grid settings - gridsettings[0][GON] = 0; // grid off - gridsettings[0][GX] = gridsettings[0][GY] = 1; // x/y grid lines enabled - gridsettings[0][GXS] = gridsettings[0][GYS] = 200; // x/y spacing - gridsettings[0][GXC] = gridsettings[0][GYC] = 5; // x/y count - menu_style = zstrdup("icons"); // default menu style (icons only) - FBrgb[0] = FBrgb[1] = FBrgb[2] = 50; // F view background color - GBrgb[0] = GBrgb[1] = GBrgb[2] = 200; // G view background color - MFrgb[0] = MFrgb[1] = MFrgb[2] = 250; // menu font color 18.01 - MBrgb[0] = MBrgb[1] = MBrgb[2] = 80; // menu background color 18.01 - dialog_font = zstrdup("Sans 11"); // default dialog font - iconsize = 32; // default icon size - splcurve_minx = 5; // default curve node separation % - startdisplay = zstrdup("prevF"); // start with previous image 17.08 - Fdragopt = 1; // image drag with mouse - zoomcount = 2; // zooms to reach 2x image size - zoomratio = sqrtf(2); // corresp. zoom ratio - Nshortcuts = 0; // KB shortcut list is empty 18.01 - map_dotsize = 8; // map dot size, mouse capture dist - curr_file = curr_dirk = 0; // no curr. file or directory - copymove_loc = 0; // copy/move target directory - color_palette_file = 0; // user's color palette file 17.04 - thumbdirk = 0; // no thumbnail directory - navi::thumbsize = 256; // gallery default thumbnail size - commandmenu = 0; // command line menu function - commandalbum = 0; // command line album gallery - initial_file = 0; // start with image file or directory - jpeg_def_quality = 90; // default .jpeg save quality - jpeg_1x_quality = 90; // default for 1-time jpeg quality - lens_mm = 35; // pano lens parameter - netmap_source = zstrdup("mapnik"); // default net map source - mapbox_access_key = zstrdup("undefined"); // mapbox map source access key - colormapfile = zstrdup("undefined"); // printer calibration color map - ss_KBkeys = zstrdup("BNPX"); // default slide show control keys 18.01 - - RAWfiletypes = zstrdup(".arw .crw .cr2 .dng .erf .iiq .mef .mos " // some of the known RAW file types - ".mpo .nef .nrw .orf .pef .ptx .raw " // (case is not significant here) - ".rw2 .rwl .srf .srw .sr2 "); - imagefiletypes = zstrdup(".jpg .jpeg .png .tif .tiff .bmp .ico " // supported image file types - ".gif .svg .xpm .tga "); - VIDEOfiletypes = zstrdup(".avi .wmv .mov .flv .mpeg .mp4 " // some known video file types 17.08.3 - ".3gp .3g2 .vob .h264 .webm .ogv "); - - BLACK[0] = BLACK[1] = BLACK[2] = 0; // define RGB colors - WHITE[0] = WHITE[1] = WHITE[2] = 255; - RED[0] = 255; RED[1] = RED[2] = 0; - GREEN[1] = 255; GREEN[0] = GREEN[2] = 0; - BLUE[2] = 255; BLUE[0] = BLUE[1] = 0; - LINE_COLOR = RED; // initial foreground drawing color - - for (int ii = 0; ii < 100; ii++) // static integer values 0-99 - Nval[ii] = ii; - - // file and directory names in user directory /home//.fotoxx/* - - snprintf(index_dirk,199,"%s/image_index",get_zhomedir()); // image index directory - snprintf(tags_defined_file,199,"%s/tags_defined",get_zhomedir()); // defined tags file - snprintf(recentfiles_file,199,"%s/recent_files",get_zhomedir()); // recent files file (index func) - snprintf(saved_areas_dirk,199,"%s/saved_areas",get_zhomedir()); // saved areas directory - snprintf(albums_dirk,199,"%s/albums",get_zhomedir()); // albums directory - snprintf(gallerymem_file,199,"%s/gallery_memory",get_zhomedir()); // recent gallery memory 18.01 - snprintf(saved_curves_dirk,199,"%s/saved_curves",get_zhomedir()); // saved curves directory - snprintf(addtext_dirk,199,"%s/add_text",get_zhomedir()); // add_text directory - snprintf(addline_dirk,199,"%s/add_line",get_zhomedir()); // add_line directory - snprintf(favorites_dirk,199,"%s/favorites",get_zhomedir()); // favorites directory - snprintf(mashup_dirk,199,"%s/mashup",get_zhomedir()); // mashup projects directory - locale_filespec("data","quickstart.html",quickstart_file); // quickstart html file - snprintf(slideshow_dirk,199,"%s/slideshows",get_zhomedir()); // slide show directory - snprintf(slideshow_trans_dirk,199,"%s/slideshow_trans",get_zhomedir()); // slide show transitions - snprintf(pattern_dirk,199,"%s/patterns",get_zhomedir()); // pattern files directory - snprintf(retouch_combo_dirk,199,"%s/retouch_combo",get_zhomedir()); // retouch combo settings directory - snprintf(custom_kernel_dirk,199,"%s/custom_kernel",get_zhomedir()); // custom kernel files directory - snprintf(printer_color_dirk,199,"%s/printer_color",get_zhomedir()); // printer calibration directory - snprintf(edit_scripts_dirk,199,"%s/edit_scripts",get_zhomedir()); // edit script files directory - snprintf(searchresults_file,199,"%s/search_results",get_zhomedir()); // output of image search function - snprintf(maps_dirk,199,"/usr/share/fotoxx-maps/data"); // map files in fotoxx-maps package - snprintf(user_maps_dirk,199,"%s/user_maps",get_zhomedir()); // map files made by user - snprintf(montage_maps_dirk,199,"%s/montage_maps",get_zhomedir()); // montage map files made by user 17.04 - - err = stat(index_dirk,&statb); // create missing directories - if (err) mkdir(index_dirk,0750); - err = stat(saved_areas_dirk,&statb); - if (err) mkdir(saved_areas_dirk,0750); - err = stat(albums_dirk,&statb); - if (err) mkdir(albums_dirk,0750); - err = stat(saved_curves_dirk,&statb); - if (err) mkdir(saved_curves_dirk,0750); - err = stat(addtext_dirk,&statb); - if (err) mkdir(addtext_dirk,0750); - err = stat(addline_dirk,&statb); - if (err) mkdir(addline_dirk,0750); - err = stat(mashup_dirk,&statb); - if (err) mkdir(mashup_dirk,0750); - err = stat(slideshow_dirk,&statb); - if (err) mkdir(slideshow_dirk,0750); - err = stat(slideshow_trans_dirk,&statb); - if (err) mkdir(slideshow_trans_dirk,0750); - err = stat(printer_color_dirk,&statb); - if (err) mkdir(printer_color_dirk,0750); - err = stat(edit_scripts_dirk,&statb); - if (err) mkdir(edit_scripts_dirk,0750); - err = stat(maps_dirk,&statb); - if (err) mkdir(maps_dirk,0750); - err = stat(user_maps_dirk,&statb); - if (err) mkdir(user_maps_dirk,0750); - err = stat(montage_maps_dirk,&statb); - if (err) mkdir(montage_maps_dirk,0750); - - load_params(); // restore parameters from last session - - for (ii = 1; ii < argc; ii++) // command line parameters - { - char *pp = argv[ii]; - - if (strmatch(pp,"-home")) ii++; // -home homedir skip, see above - else if (strmatchV(pp,"-debug","-d",0)) // -d -debug - Fdebug = 1; - else if (strmatchV(pp,"-lang","-l",0) && argc > ii+1) // -l -lang lc_RC language/region code - strncpy0(lang,argv[++ii],7); - else if (strmatchV(pp,"-clone","-c",0) && argc > ii+4) { // -c -clone clone new instance - Fclone = 1; - cloxx = atoi(argv[ii+1]); // window position and size - cloyy = atoi(argv[ii+2]); // passed from parent instance - cloww = atoi(argv[ii+3]); - clohh = atoi(argv[ii+4]); - ii += 4; - } - else if (strmatchV(pp,"-recent","-r",0)) // -r -recent recent files gallery - Frecent = 1; - else if (strmatchV(pp,"-new","-n",0)) // -n -new newest files gallery - Fnew = 1; - else if (strmatchV(pp,"-prev","-p",0)) // -p -prev open previous file - Fprev = 1; - else if (strmatchV(pp,"-blank","-b",0)) // -b -blank start with blank window - Fblank = 1; - else if (strmatch(pp,"-index") && argc > ii+1) { // -index N index level - jj = atoi(argv[++ii]); - if (jj >= 0 && jj <= 2) Pindexlev = jj; // 0/1/2 = none/old/old+new image files - } - else if (strmatchV(pp,"-menu","-m",0) && argc > ii+1) // -m -menu func command line menu func - commandmenu = zstrdup(argv[++ii]); - else if (strmatchV(pp,"-album","-a",0) && argc > ii+1) // -a -album command line album - commandalbum = zstrdup(argv[++ii]); - else { // must be initial file or directory - initial_file = combine_argvs(argc,argv,ii); // combine remaining argv[] elements 17.08 - initial_file = zstrdup(initial_file); // (fix file paths with blanks) - if (*initial_file != '/') { // relative to initial directory - cc = strlen(initial_file); - temp = zstrdup(getcwd(0,0),cc+4); - strncatv(temp,200,"/",initial_file,0); // /initial-directory/initial-file 17.08 - initial_file = temp; - } - break; - } - } - - ZTXinit(lang); // setup locale, translations - setlocale(LC_NUMERIC,"en_US.UTF-8"); // stop comma decimal points - - zsetfont(dialog_font); // set default font for widgets - - build_widgets(); // build window widgets and menus - - if (Fclone) { // clone: open new window - gtk_window_move(MWIN,cloxx+10,cloyy+10); // slightly offset from old window - gtk_window_resize(MWIN,cloww,clohh); - } - else { - gtk_window_move(MWIN,mwgeom[0],mwgeom[1]); // main window geometry - gtk_window_resize(MWIN,mwgeom[2],mwgeom[3]); // defaults or last session params - } - - gtk_widget_show_all(Mwin); - - arrowcursor = gdk_cursor_new_for_display(display,GDK_TOP_LEFT_ARROW); // cursor for selection - dragcursor = gdk_cursor_new_for_display(display,GDK_CROSSHAIR); // cursor for dragging - drawcursor = gdk_cursor_new_for_display(display,GDK_PENCIL); // cursor for drawing lines - blankcursor = gdk_cursor_new_for_display(display,GDK_BLANK_CURSOR); // invisible cursor - - m_viewmode(0,"F"); // set F mode initially - - g_timeout_add(100,initzfunc,0); // initz. call from gtk_main() - gtk_main(); // start processing window events - return 0; -} - - -/********************************************************************************/ - -// Initial function called from gtk_main() at startup. -// This function MUST return 0. - -int initzfunc(void *) -{ - int Fexiftool = 0, Fxdgopen = 0; - int ii, err, npid, contx; - FTYPE ftype; - char *pp, *pp2; - char procfile[20], buff[200]; - char badnews[200], albumfile[200]; - char colorwheelfile[200]; - double freememory, cachememory; - float exifver = 0; - FILE *fid; - STATB statb; - double startsecs; - struct timeb startime2; - - printz("%s \n",Arelease); // print Fotoxx release version - - int v1 = gtk_get_major_version(); // get GTK release version - int v2 = gtk_get_minor_version(); - int v3 = gtk_get_micro_version(); - printz("GTK version: %d.%02d.%02d \n",v1,v2,v3); - - // check that necessary programs are installed - - fid = popen("exiftool -ver","r"); // check exiftool version - if (fid) { - ii = fscanf(fid,"%s",buff); - pclose(fid); // accept period or comma - convSF(buff,exifver); - printz("exiftool version: %.2f \n",exifver); - if (exifver >= 8.60) Fexiftool = 1; - } - - err = shell_quiet("which xdg-open >/dev/null 2>&1"); // check for xdg-open - if (! err) Fxdgopen = 1; - - err = shell_quiet("which rawtherapee >/dev/null 2>&1"); // check for Raw Therapee - if (! err) Frawtherapee = 1; - - err = shell_quiet("which growisofs >/dev/null 2>&1"); // check for growisofs - if (! err) Fgrowisofs = 1; - - Fvideo = 1; - err = shell_quiet("which ffmpeg >/dev/null 2>&1"); // check for ffmpeg 17.08 - if (err) Fvideo = 0; - err = shell_quiet("which totem >/dev/null 2>&1"); // check for totem 17.08 - if (err) Fvideo = 0; - - err = shell_quiet("which hugin >/dev/null 2>&1"); // need all of it 17.08.3 - if (! err) PTtools = 1; - - if (Fexiftool + Fxdgopen < 2) { // check mandatory dependencies - strcpy(badnews,ZTX("Please install missing programs:")); - if (! Fexiftool) strcat(badnews,"\n exiftool (or libimage-exiftool-perl)"); - if (! Fxdgopen) strcat(badnews,"\n xdg-utils"); - zmessageACK(Mwin,badnews); - m_quit(0,0); - } - - if (! Frawtherapee) printz("Raw Therapee not installed \n"); // optional dependencies - if (! Fgrowisofs) printz("growisofs not installed \n"); - if (! Fvideo) printz("ffmpeg and Totem not installed \n"); - if (! PTtools) printz("Panorama Tools (Hugin) not installed \n"); - - // check for first time Fotoxx install or new release install - - if (Ffirsttime) { // new fotoxx install - showz_html(quickstart_file); - Prelease = zstrdup(Frelease); - } - - if (! Ffirsttime && ! strmatch(Prelease,Frelease)) { // Fotoxx release change - zmessageACK(Mwin,"fotoxx new release %s",Frelease); - Prelease = zstrdup(Frelease); - showz_textfile("doc","changelog"); - } - - Ffirsttime = 0; // reset first time flag - - // copy add text/line files from old directories to new // FIXME remove after 18.01 - - snprintf(buff,200,"%s/write_text",get_zhomedir()); - err = stat(buff,&statb); - if (! err) { - snprintf(buff,200,"%s/write_text",get_zhomedir()); - shell_quiet("mv -n %s/* %s ",buff,addtext_dirk); - rmdir(buff); - snprintf(buff,200,"%s/write_line",get_zhomedir()); - shell_quiet("mv -n %s/* %s ",buff,addline_dirk); - rmdir(buff); - } - - // delete fotoxx tempdir files if owner process is no longer running - - contx = 0; - while ((pp = command_output(contx,"find /tmp/fotoxx-* 2>/dev/null",0))) { - pp2 = strchr(pp,'-'); - if (! pp2) continue; - npid = atoi(pp2+1); // pid of fotoxx owner process - snprintf(procfile,20,"/proc/%d",npid); - err = stat(procfile,&statb); - if (! err) continue; // pid is active, keep - shell_quiet("rm -R -f -v %s",pp); // delete - } - - // set up temp directory /tmp/fotoxx-nnnnnn-XXXXXX - - snprintf(tempdir,100,"/tmp/fotoxx-%06d-XXXXXX",getpid()); // use PID + random chars. - pp = mkdtemp(tempdir); - if (! pp) { - zmessageACK(Mwin,"%s \n %s",tempdir,strerror(errno)); - exit(1); - } - strncpy0(tempdir,pp,100); - printz("tempdir: %s \n",tempdir); - - // file name template for undo/redo files - - snprintf(URS_filename,100,"%s/undo_nn",tempdir); // /tmp/fotoxx-nnnnnn/undo_nn - - // check free memory and suggest image size limits - - parseprocfile("/proc/meminfo","MemFree:",&freememory,0); // get amount of free memory - parseprocfile("/proc/meminfo","Cached:",&cachememory,0); - freememory = (freememory + cachememory) / 1024; // megabytes - printz("free memory: %.0f MB \n",freememory); - printz("image size limits for good performance: \n"); - printz(" view: %.0f megapixels \n",(freememory-100)/6); // F + preview, 3 bytes/pixel each - printz(" edit: %.0f megapixels \n",(freememory-100)/54); // + E0/E1/E3/ER, 12 bytes/pixel each - - // miscellaneous - - printz("screen width: %d height: %d \n", // monitor pixel size - zfuncs::monitor_ww,zfuncs::monitor_hh); - - NWT = get_nprocs(); // get SMP CPU count - if (NWT <= 0) NWT = 2; - if (NWT > max_threads) NWT = max_threads; // compile time limit - printz("using %d threads \n",NWT); - - exif_server(0,0,0); // kill orphan exiftool process - - zdialog_inputs("load"); // load saved dialog inputs - zdialog_positions("load"); // load saved dialog positions - gallery_memory("load"); // load recent gallery positions 17.08 - KBshortcuts_load(); // load KB shortcuts from file - - if (! color_palette_file) err = 1; // initialize color palette file 17.08 - else err = stat(color_palette_file,&statb); // if this is missing - if (err) { - snprintf(colorwheelfile,199,"%s/colorwheel.jpg",get_zhomedir()); - err = stat(colorwheelfile,&statb); - if (! err) color_palette_file = zstrdup(colorwheelfile); - } - - // create or update image index file and memory table - - if (xmeta_changed) { // indexed metadata changed 18.01 - printz("indexed metadata list changed, full index required \n"); - index_rebuild(2,0); - } - - else if (Pindexlev >= 0) index_rebuild(Pindexlev,0); // -index command parameter given - else if (initial_file) index_rebuild(FMindexlev,0); // likely a file manager call - else index_rebuild(Findexlev,0); // index level from user setting - - // set current file and gallery from command line if present - - if (topdirks[0]) curr_dirk = zstrdup(topdirks[0]); // default 1st top image directory - else curr_dirk = zstrdup(getenv("HOME")); - - if (initial_file) { // file parameter - printz("initial file: %s \n",initial_file); - ftype = image_file_type(initial_file); - if (ftype == FNF) { // non-existent file - printz(" -invalid file \n"); - zfree(initial_file); - initial_file = 0; - } - else if (ftype == FDIR) { // directory - if (curr_dirk) zfree(curr_dirk); - curr_dirk = initial_file; - initial_file = 0; - gallery(curr_dirk,"init",0); - gallery(0,"sort",-2); // recall sort and position 18.01 - m_viewmode(0,"G"); - } - else if (ftype == IMAGE || ftype == RAW || ftype == VIDEO) { // image file 17.08 - if (curr_dirk) zfree(curr_dirk); - curr_dirk = zstrdup(initial_file); // set current directory from file - pp = strrchr(curr_dirk,'/'); - if (pp) *pp = 0; - f_open(initial_file); - } - else { - printz(" -invalid file \n"); - zfree(initial_file); - initial_file = 0; - } - } - - else if (commandalbum) { // -album parameter - printz("initial album: %s \n",commandalbum); // 17.04 - snprintf(albumfile,200,"%s/albums/%s",get_zhomedir(),commandalbum); - err = stat(albumfile,&statb); - if (err) { - printz("invalid album file: %s \n",commandalbum); - commandalbum = 0; - } - else { - navi::gallerytype = ALBUM; - gallery(albumfile,"initF",0); - gallery(0,"sort",-2); // recall sort and position 18.01 - m_viewmode(0,"G"); - } - } - - else if (Fprev) { // start with previous file - if (last_curr_file && *last_curr_file == '/') - f_open(last_curr_file); - } - - else if (Frecent) // start with recent files gallery - m_recentfiles(0,0); - - else if (Fnew) // start with newest files gallery - m_newfiles(0,"file"); // by file mod date - - else if (Fblank) { // blank window, no gallery - curr_file = curr_dirk = 0; - navi::galleryname = 0; - navi::gallerytype = TNONE; - set_mwin_title(); - } - - // if no command line option, get startup display from user settings - - else if (strmatch(startdisplay,"album")) { - printz("initial album: %s \n",startalbum); // 17.04 - err = stat(startalbum,&statb); - if (err) { - printz("invalid album file: %s \n",startalbum); - commandalbum = 0; - } - else { - navi::gallerytype = ALBUM; - gallery(startalbum,"initF",0); - gallery(0,"sort",-2); // recall sort and position 18.01 - m_viewmode(0,"G"); - } - } - - else if (strmatch(startdisplay,"recent")) // start with recent files gallery - m_recentfiles(0,0); - - else if (strmatch(startdisplay,"newest")) // start with newest files gallery - m_newfiles(0,"file"); // by file mode date - - else if (strmatch(startdisplay,"prevG")) { // start with previous gallery - if (last_gallerytype != TNONE) { - navi::gallerytype = last_gallerytype; - if (last_gallerytype == GDIR) - gallery(last_galleryname,"init",0); - else gallery(last_galleryname,"initF",0); - gallery(0,"sort",-2); // recall sort and position 18.01 - m_viewmode(0,"G"); - } - } - - else if (strmatch(startdisplay,"prevF")) { // start with previous image file - if (last_curr_file && *last_curr_file == '/') - f_open(last_curr_file); - } - - else if (strmatch(startdisplay,"specG")) { // start with specified gallery (dirk) - if (startdirk && *startdirk == '/') { - if (curr_dirk) zfree(curr_dirk); - curr_dirk = zstrdup(startdirk); - gallery(curr_dirk,"init",0); - gallery(0,"sort",-2); // recall sort and position 18.01 - m_viewmode(0,"G"); - } - } - - else if (strmatch(startdisplay,"specF")) // start with given image file - f_open(startfile); - - if (commandmenu) { // startup menu on command line - printz("start menu: %s \n",commandmenu); - for (ii = 0; ii < Nmenus; ii++) { // convert menu name to menu function - if (! menutab[ii].menu) continue; // separator, null menu - if (strmatchcase(commandmenu,ZTX(menutab[ii].menu))) break; - } - if (ii < Nmenus) menutab[ii].func(0,menutab[ii].arg); // call the menu function - } - - save_params(); // save parameters now - - g_timeout_add(20,gtimefunc,0); // start periodic function (20 ms) - - ftime(&startime2); // time to startup and initialize 17.08 - startsecs = startime2.time - startime.time - + 0.001 * (startime2.millitm - startime.millitm); - printz("startup time: %.1f secs.\n",startsecs); - - return 0; // don't come back -} - - -/********************************************************************************/ - -// functions for main window event signals - -int delete_event() // main window closed -{ - int yn; - zdialog *zd = 0; - cchar *title = "unknown"; - - printz("main window delete event \n"); - if (checkpend("mods")) return 1; // allow user bailout - if (zfuncs::zdialog_busy) { - zd = zdialog_list[0]; - if (zd) title = zd->title; - yn = zmessageYN(Mwin,ZTX("Kill active dialog? %s"),title); // allow user bailout 18.01 - if (! yn) return 1; - } - quitxx(); - return 0; -} - -int destroy_event() // main window destroyed -{ - printz("main window destroy event \n"); - quitxx(); - return 0; -} - -int state_event(GtkWidget *, GdkEvent *event) // main window state changed -{ - int state = ((GdkEventWindowState *) event)->new_window_state; // track window fullscreen status - if (state & GDK_WINDOW_STATE_FULLSCREEN) Ffullscreen = 1; - else if (state & GDK_WINDOW_STATE_MAXIMIZED) Ffullscreen = 1; // 17.08 - else Ffullscreen = 0; - return 0; -} - -void drop_event(int mousex, int mousey, char *file) // file drag-drop event -{ - if (! file) return; - printf("drag-drop file: %s \n",file); - f_open(file,0,0,1); - return; -} - - -/********************************************************************************/ - -// Periodic function (20 milliseconds) - -int gtimefunc(void *) -{ - static int domore = 0; - - if (Fshutdown) return 0; // shutdown underway - - if (Fpaintlock && gdkwin) { // thread request to freeze - gdk_window_freeze_updates(gdkwin); // window updates - zadd_locked(Fpaintlock,-1); - } - - if (Fpaintunlock && gdkwin) { // thread request to thaw - gdk_window_thaw_updates(gdkwin); // window updates - zadd_locked(Fpaintunlock,-1); - } - - if (Fpaintrequest && Cdrawin) - gtk_widget_queue_draw(Cdrawin); - - if (zd_thread && zd_thread_event) { // send dialog event from thread - if (! CEF || CEF->thread_status < 2) { - zdialog_send_event(zd_thread,zd_thread_event); // only if thread done or not busy - zd_thread_event = 0; - } - } - - void update_window_area(); // update window area 17.01 - update_window_area(); // pending from a thread - - if (--domore > 0) return 1; // do rest every 200 milliseconds - domore = 10; - - update_Fpanel(); // update top panel information - return 1; -} - - -/********************************************************************************/ - -// update F window top panel with current status information -// called from timer function - -void update_Fpanel() -{ - static double time1 = 0, time2, cpu1 = 0, cpu2, cpuload; - static double pagesize, mem, MB; - char *pp, text1[300], text2[200]; - static char ptext1[300] = ""; - int ww, hh, scale, bpc; - static int ftf = 1; - double file_MB = 1.0 / MEGA * curr_file_size; - static cchar *reduced, *areaactive, *dialogopen; - static cchar *blocked, *modified; - FILE *fid; - - if (! Fpanelshow) return; // panel currently hidden - - if (ftf) { - ftf = 0; - reduced = ZTX("(reduced)"); - areaactive = ZTX("area active"); - dialogopen = ZTX("dialog open"); - blocked = ZTX("blocked"); - modified = "mod"; - pagesize = getpagesize(); // 17.08 - } - - if (Fslideshow) return; - if (FGWM == 'G') goto update_busy; - if (FGWM != 'F') return; - - *text1 = *text2 = 0; - - if (! time1) { - time1 = get_seconds(); - cpu1 = jobtime(); - } - - time2 = get_seconds(); // compute process cpu load % - if (time2 - time1 > 1.0) { // at 1 second intervals - cpu2 = jobtime(); - cpuload = 100.0 * (cpu2 - cpu1) / (time2 - time1); - time1 = time2; - cpu1 = cpu2; - } - - mem = 0; // get memory MB in use 17.08 - fid = fopen("/proc/self/stat","r"); - if (fid) { - pp = fgets(text2,200,fid); - fclose(fid); - if (pp) { - pp = strchr(pp,')'); // closing ')' after (short) filename - if (pp) parseprocrec(pp+1,22,&mem,null); // get memory usage - } - } - - MB = mem * pagesize / MEGA; - - snprintf(text1,300,"CPU %03.0f%c MB %.0f",cpuload,'%',MB); // CPU 23% MB 123 - - if (curr_file && Fpxb) - { - if (E3pxm) { - ww = E3pxm->ww; - hh = E3pxm->hh; - } - else { - ww = Fpxb->ww; - hh = Fpxb->hh; - } - - bpc = curr_file_bpc; - - snprintf(text2,100," %dx%dx%d",ww,hh,bpc); // 2345x1234x16 (preview) 1.56MB 45% - strncatv(text1,300,text2,0); - if (CEF && CEF->Fpreview) strncatv(text1,300," ",reduced,0); - snprintf(text2,100," %.2fM",file_MB); - strncatv(text1,300,text2,0); - scale = Mscale * 100 + 0.5; - snprintf(text2,100," %d%c",scale,'%'); - strncatv(text1,300,text2,0); - - if (URS_pos) { // edit undo/redo stack depth - snprintf(text2,100," %s: %d",ZTX("edits"),URS_pos); - strncatv(text1,300,text2,0); - } - - if (Fmetamod) strncatv(text1,300," ","metadata",0); - } - - else if (Fpxb) { - snprintf(text2,100," %dx%d",Fpxb->ww,Fpxb->hh); - strncatv(text1,300,text2,0); - } - - if (sa_stat == 3) strncatv(text1,300," ",areaactive,0); - if (zfuncs::zdialog_busy) strncatv(text1,300," ",dialogopen,0); - - if (Fblock) strncatv(text1,300," ",blocked,0); // "blocked" - if (CEF && CEF->Fmods) strncatv(text1,300," ",modified,0); // "mod" - if (*paneltext) strncatv(text1,300," ",paneltext,0); // application text - - if (curr_file) { - pp = strrchr(curr_file,'/'); // "filename.jpg" 17.01 - if (pp && Ffullscreen && ! (Ffuncbusy | Fthreadbusy)) { // 17.08 - strncpy0(text2,pp+1,100); - strncatv(text1,300," ",text2,0); - } - } - - if (! strmatch(text1,ptext1)) { // if text changed, update panel bar - gtk_label_set_label(GTK_LABEL(Fpanlab),text1); - gtk_widget_show_all(Fpanel); - strcpy(ptext1,text1); - } - -// Show BUSY label if Ffuncbusy or Fthreadbusy active. -// Show progress counter if Fbusy_goal > 0 -// added to top panel: BUSY NN% - -update_busy: - - static GtkWidget *busylabel = 0, *donelabel = 0; - static cchar *busytext = " BUSY "; - static char donetext[] = "xx% "; - GtkWidget *FGpanel; - int pct; - char nn[4]; - - if (FGWM == 'F') FGpanel = Fpanel; - else if (FGWM == 'G') FGpanel = Gpanel; - else return; - - if (Ffuncbusy | Fthreadbusy) { - if (! busylabel) { - busylabel = gtk_label_new(null); - gtk_label_set_markup(GTK_LABEL(busylabel),busytext); - gtk_box_pack_start(GTK_BOX(FGpanel),busylabel,0,0,5); - } - } - else { - if (busylabel) gtk_widget_destroy(busylabel); - busylabel = 0; - } - - if (Fbusy_done > 0 && Fbusy_done < Fbusy_goal) { // 17.04 - pct = 100 * Fbusy_done / Fbusy_goal; - if (pct > 99) pct = 99; - snprintf(nn,4,"%02d",pct); - strncpy(donetext+33,nn,2); // watch out: hidden dependency - if (! donelabel) { - donelabel = gtk_label_new(""); - gtk_box_pack_start(GTK_BOX(FGpanel),donelabel,0,0,0); - } - gtk_label_set_markup(GTK_LABEL(donelabel),donetext); - } - else { - if (donelabel) gtk_widget_destroy(donelabel); - donelabel = 0; - } - - gtk_widget_show_all(FGpanel); - - return; -} - - -// block image updated by created threads during painting by main thread -// block main thread window updates during changes to image data by created threads -// lock = 1: block updates lock = 0: unblock - -void paintlock(int lock) -{ - static int freezecount = 0; - int nn; - - if (! gdkwin) return; - - if (pthread_equal(pthread_self(),zfuncs::tid_main)) { // main thread caller - if (lock) { - gdk_window_freeze_updates(gdkwin); // freeze window updates - nn = zadd_locked(freezecount,+1); // increment freeze counter - if (nn <= 0) zappcrash("paintlock() freezecount: %d",freezecount); - } - else { - gdk_window_thaw_updates(gdkwin); // thaw window updates - nn = zadd_locked(freezecount,-1); // decrement freeze counter - if (nn < 0) zappcrash("paintlock() freezecount: %d",freezecount); - } - } - - else { // created thread caller - if (lock) { - zadd_locked(Fpaintlock,+1); // set flag for gtimefunc() - while (zadd_locked(Fpaintlock,0)) zsleep(0.01); // wait for freeze done - nn = zadd_locked(freezecount,+1); // increment freeze counter - if (nn <= 0) zappcrash("paintlock() freezecount: %d",freezecount); - } - else { - zadd_locked(Fpaintunlock,+1); // set flag for gtimefunc() - while (zadd_locked(Fpaintunlock,0)) zsleep(0.01); // wait for thaw done - nn = zadd_locked(freezecount,-1); // decrement freeze counter - if (nn < 0) zappcrash("paintlock() freezecount: %d",freezecount); - } - } - - return; -} - - -/********************************************************************************/ - -// GTK3 "draw" function for F and W mode drawing windows. -// Paint window when created, exposed, resized, or image modified (edited). -// Update window image if scale change or window size change. -// Otherwise do only the draw function (triggered from GTK). -// Draw the image section currently within the visible window. -// May NOT be called from threads. See Fpaint2() for threads. - -int Fpaint(GtkWidget *Cdrawin, cairo_t *cr) -{ - PIXBUF *pixbuf; - PXB *pxb1; - GdkRGBA rgba; - static int pdww = 0, pdhh = 0; // prior window size - float wscale, hscale; - int fww, fhh; // current image size at 1x - int mww, mhh; // scaled image size - int morgx, morgy; - int dorgx, dorgy; - int centerx, centery; - int refresh = 0; - int mousex, mousey; // mouse position after zoom - float magx, magy; // mouse drag, magnification ratios - uint8 *pixels, *pix, bgpix[3]; - int rs, px, py; - - if (Fshutdown) return 1; // shutdown underway - - if (! Cdrawin || ! gdkwin || ! Cstate || ! Cstate->fpxb) { // no image - Fpaintrequest = 0; - return 1; - } - - if (Fview360) return 1; // 18.01 - - Dww = gdk_window_get_width(gdkwin); // (new) drawing window size - Dhh = gdk_window_get_height(gdkwin); - if (Dww < 20 || Dhh < 20) return 1; // too small - - if (Dww != pdww || Dhh != pdhh) { // window size changed - refresh = 1; // image refresh needed - pdww = Dww; - pdhh = Dhh; - } - - if (Fpaintrequest) { // window image changed - Fpaintrequest = 0; // window updated as of NOW - refresh = 1; // image refresh needed - if (FGWM == 'F' && (E0pxm || E3pxm)) { // insure F-view and E0 or E3 - if (E3pxm) pxb1 = PXM_PXB_copy(E3pxm); // update fpxb from E0/E3 image - else pxb1 = PXM_PXB_copy(E0pxm); // or use already edited image - PXB_free(Cstate->fpxb); - Cstate->fpxb = pxb1; - } - } - - centerx = (Cstate->morgx + 0.5 * dww) / Cstate->mscale; // center of window, image space - centery = (Cstate->morgy + 0.5 * dhh) / Cstate->mscale; // (before window or scale change) - - fww = Cstate->fpxb->ww; // 1x image size - fhh = Cstate->fpxb->hh; - - if (Cstate->fzoom == 0) { // scale to fit window - wscale = 1.0 * Dww / fww; - hscale = 1.0 * Dhh / fhh; - if (wscale < hscale) Cstate->mscale = wscale; // use greatest ww/hh ratio - else Cstate->mscale = hscale; - if (fww <= Dww && fhh <= Dhh && ! Fblowup) Cstate->mscale = 1.0; // small image 1x unless Fblowup - zoomx = zoomy = 0; // no zoom target - } - else Cstate->mscale = Cstate->fzoom; // scale to fzoom level - - mww = fww * Cstate->mscale; // scaled image size for window - mhh = fhh * Cstate->mscale; - - dww = Dww; // image fitting inside drawing window - if (dww > mww) dww = mww; // image < window size - dhh = Dhh; - if (dhh > mhh) dhh = mhh; - - if (Cstate->mscale != Cstate->pscale) { // scale changed - Cstate->morgx = Cstate->mscale * centerx - 0.5 * dww; // change origin to keep same center - Cstate->morgy = Cstate->mscale * centery - 0.5 * dhh; // (subject to later rules) - Cstate->pscale = Cstate->mscale; // remember scale - refresh = 1; // image refresh needed - } - - if (! Cstate->mpxb) refresh++; // need to make mpxb - - if (refresh) { // image refresh needed - if (Cstate->mpxb) PXB_free(Cstate->mpxb); - if (Cstate->mscale == 1) Cstate->mpxb = PXB_copy(Cstate->fpxb); // fast 1x image - else Cstate->mpxb = PXB_rescale(Cstate->fpxb,mww,mhh); // rescaled image - } - - if ((Mxdrag || Mydrag)) { // pan/scroll via mouse drag - zoomx = zoomy = 0; // no zoom target - magx = 1.0 * (mww - dww) / dww; - magy = 1.0 * (mhh - dhh) / dhh; // needed magnification of mouse movement - if (magx < 1) magx = 1; // retain a minimum speed - if (magy < 1) magy = 1; - - if (Fdragopt == 1) { - Cstate->morgx -= round(Mwdragx); // same direction - Cstate->morgy -= round(Mwdragy); - } - if (Fdragopt == 2) { - Cstate->morgx += round(Mwdragx); // opposite direction - Cstate->morgy += round(Mwdragy); - } - if (Fdragopt == 3) { - Cstate->morgx -= round(Mwdragx * magx); // same direction, magnified - Cstate->morgy -= round(Mwdragy * magy); - } - if (Fdragopt == 4) { - Cstate->morgx += round(Mwdragx * magx); // opposite direction, magnified - Cstate->morgy += round(Mwdragy * magy); - } - } - - if (dww < Dww) Cstate->dorgx = 0.5 * (Dww - dww); // if scaled image < window, - else Cstate->dorgx = 0; // center image in window - if (dhh < Dhh) Cstate->dorgy = 0.5 * (Dhh - dhh); - else Cstate->dorgy = 0; - - if (Fshiftright && dww < Dww-1) Cstate->dorgx = Dww-1 - dww; // shift image to right margin - - if (zoomx || zoomy) { // requested zoom center - Cstate->morgx = Cstate->mscale * zoomx - 0.5 * dww; // corresp. window position within image - Cstate->morgy = Cstate->mscale * zoomy - 0.5 * dhh; - } - - if (Cstate->morgx < 0) Cstate->morgx = 0; // maximize image within window - if (Cstate->morgy < 0) Cstate->morgy = 0; // (no unused margins) - if (Cstate->morgx + dww > mww) Cstate->morgx = mww - dww; - if (Cstate->morgy + dhh > mhh) Cstate->morgy = mhh - dhh; - - if (zoomx || zoomy) { // zoom target - mousex = zoomx * Cstate->mscale - Cstate->morgx + Cstate->dorgx; - mousey = zoomy * Cstate->mscale - Cstate->morgy + Cstate->dorgy; // mouse pointer follows target - move_pointer(Cdrawin,mousex,mousey); - zoomx = zoomy = 0; // reset zoom target - } - - if (zddarkbrite) darkbrite_paint(); // update dark/bright pixels - - rgba.red = 0.00392 * FBrgb[0]; // window background color - rgba.green = 0.00392 * FBrgb[1]; // 0 - 255 --> 0.0 - 1.0 - rgba.blue = 0.00392 * FBrgb[2]; - rgba.alpha = 1.0; - gdk_cairo_set_source_rgba(cr,&rgba); - cairo_paint(cr); - - morgx = Cstate->morgx; // window position in (larger) image - morgy = Cstate->morgy; - dorgx = Cstate->dorgx; - dorgy = Cstate->dorgy; - - if (refresh) { // renew background image - if (BGpixbuf) g_object_unref(BGpixbuf); - BGpixbuf = gdk_pixbuf_new(GDKRGB,0,8,dww,dhh); - pixels = gdk_pixbuf_get_pixels(BGpixbuf); - rs = gdk_pixbuf_get_rowstride(BGpixbuf); - - bgpix[0] = FBrgb[0]; // use background color - bgpix[1] = FBrgb[1]; - bgpix[2] = FBrgb[2]; - - for (py = 0; py < dhh; py++) - for (px = 0; px < dww; px++) { - pix = pixels + py * rs + px * 3; - if (py % 10 < 2 && px % 10 < 2) // with periodic black dots - memset(pix,0,3); - else memcpy(pix,bgpix,3); - } - } - - gdk_cairo_set_source_pixbuf(cr,BGpixbuf,dorgx,dorgy); // paint background image - cairo_paint(cr); - - pixbuf = Cstate->mpxb->pixbuf; // get image section within window - pixbuf = gdk_pixbuf_new_subpixbuf(pixbuf,morgx,morgy,dww,dhh); - gdk_cairo_set_source_pixbuf(cr,pixbuf,dorgx,dorgy); // paint section - cairo_paint(cr); - g_object_unref(pixbuf); - - if (Cstate == &Fstate) { // view mode is image - if (Ntoplines) draw_toplines(1,cr); // draw line overlays - if (gridsettings[currgrid][GON]) draw_gridlines(cr); // draw grid lines - if (Ntoptext) draw_toptext(cr); // draw text strings - if (Ntopcircles) draw_topcircles(cr); // draw circles - if (Fshowarea) sa_show(1,cr); // draw select area outline - if (refresh && zdbrdist) m_show_brdist(0,0); // update brightness dist. - } - - if (Cstate == &Wstate) // view mode is world maps - filemap_paint_dots(); - - return 1; -} - - -/********************************************************************************/ - -// Repaint modified image synchrounously. -// May NOT be called from threads. - -void Fpaintnow() -{ - if (! Cdrawin || ! gdkwin || ! Cstate || ! Cstate->fpxb) { // no image - printf("Fpaintnow(), no image \n"); - return; - } - - Fpaintrequest = 1; // request repaint of changed image - gtk_widget_queue_draw(Cdrawin); - while (Fpaintrequest) { - zsleep(0.001); - zmainloop(); - } - - return; -} - - -// Cause (modified) output image to get repainted soon. -// Fpaint() will be called by gtimefunc() next timer cycle. -// MAY be called from threads. - -void Fpaint2() -{ - Fpaintrequest = 1; // request repaint of changed image - return; -} - - -// Update a section of Fpxb and Mpxb from an updated section of E3pxm, -// then update the corresponding section of the drawing window. -// This avoids a full image refresh, E3pxm > fpxb > mpxb > drawing window. -// px3, py3, ww3, hh3: modified section within E3pxm to be propagated. -// May NOT be called from threads. - -void Fpaint3(int px3, int py3, int ww3, int hh3, cairo_t *cr) -{ - int crflag = 0; - - if (! cr) { - cr = draw_context_create(gdkwin,draw_context); - crflag = 1; - } - - PXM_PXB_update(E3pxm,Fpxb,px3,py3,ww3,hh3); // E3pxm > Fpxb, both 1x scale - PXB_PXB_update(Fpxb,Mpxb,px3,py3,ww3,hh3); // Fpxb > Mpxb, scaled up or down - Fpaint4(px3,py3,ww3,hh3,cr); // update drawing window from Mpxb - - if (crflag) draw_context_destroy(draw_context); - return; -} - - -// Do the same using E0pxm instead of E3pxm - -void Fpaint0(int px3, int py3, int ww3, int hh3, cairo_t *cr) -{ - int crflag = 0; - - if (! cr) { - cr = draw_context_create(gdkwin,draw_context); - crflag = 1; - } - - PXM_PXB_update(E0pxm,Fpxb,px3,py3,ww3,hh3); - PXB_PXB_update(Fpxb,Mpxb,px3,py3,ww3,hh3); - Fpaint4(px3,py3,ww3,hh3,cr); - - if (crflag) draw_context_destroy(draw_context); - return; -} - - -// Repaint a section of the Mpxb image in the visible window. -// px3, py3, ww3, hh3: area to be repainted (in 1x image space). -// May NOT be called from threads. -// Writes directly on the window (cairo pixbuf paint). - -void Fpaint4(int px3, int py3, int ww3, int hh3, cairo_t *cr) -{ - PIXBUF *pixbuf, *bgpixbuf; - int px1, py1, ww1, hh1; - int px2, py2, ww2, hh2; - int crflag = 0; - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->fpxb) return; // no image - - px2 = Mscale * px3 - 2; // 1x image space to Mpxb space - py2 = Mscale * py3 - 2; // (expanded a few pixels) - ww2 = Mscale * ww3 + 2 / Mscale + 4; - hh2 = Mscale * hh3 + 2 / Mscale + 4; - - if (px2 < Morgx) { // reduce to currently visible window - ww2 = ww2 - (Morgx - px2); - px2 = Morgx; - } - - if (py2 < Morgy) { - hh2 = hh2 - (Morgy - py2); - py2 = Morgy; - } - - if (px2 + ww2 >= Mpxb->ww) ww2 = Mpxb->ww - px2 - 1; // stay within image bugfix 17.04.3 - if (py2 + hh2 >= Mpxb->hh) hh2 = Mpxb->hh - py2 - 1; - if (ww2 <= 0 || hh2 <= 0) return; - - px1 = px2 - Morgx + Dorgx; // corresp. position in drawing window - py1 = py2 - Morgy + Dorgy; - - if (px1 + ww2 >= Dww) ww2 = Dww - px1 - 1; // stay within window bugfix 17.04.3 - if (py1 + hh2 >= Dhh) hh2 = Dhh - py1 - 1; - if (ww2 <= 0 || hh2 <= 0) return; - - pixbuf = gdk_pixbuf_new_subpixbuf(Mpxb->pixbuf,px2,py2,ww2,hh2); // Mpxb area to paint - if (! pixbuf) { - printz("Fpaint4() pixbuf failure \n"); - return; - } - - px2 = px1; // corresp. position in drawing window - py2 = py1; - - px1 = px2 - Dorgx; // corresp. position in background image - py1 = py2 - Dorgy; - - paintlock(1); - - if (! cr) { - cr = draw_context_create(gdkwin,draw_context); - crflag = 1; - } - - if (Mpxb->nc > 3) { // alpha channel present - ww1 = ww2; // draw background image to area - hh1 = hh2; - if (px1 + ww1 > dww) ww1 = dww - px1; - if (py1 + hh1 > dhh) hh1 = dhh - py1; - if (ww1 > 0 && hh1 > 0) { - bgpixbuf = gdk_pixbuf_new_subpixbuf(BGpixbuf,px1,py1,ww1,hh1); - if (bgpixbuf) { - gdk_cairo_set_source_pixbuf(cr,bgpixbuf,px2,py2); - cairo_paint(cr); - g_object_unref(bgpixbuf); - } - else printz("Fpaint4() bgpixbuf failure \n"); - } - } - - gdk_cairo_set_source_pixbuf(cr,pixbuf,px2,py2); // draw area to window - cairo_paint(cr); - - g_object_unref(pixbuf); - - if (Fshowarea) { - px3 = (px2 - Dorgx + Morgx) / Mscale; // back to image scale, expanded - py3 = (py2 - Dorgy + Morgy) / Mscale; - ww3 = ww2 / Mscale + 2; - hh3 = hh2 / Mscale + 2; - sa_show_rect(px3,py3,ww3,hh3,cr); // refresh select area outline - } - - if (crflag) draw_context_destroy(draw_context); - paintlock(0); - - return; -} - - -// update window area pending from a thread -// update_window_area() will be called by gtimefunc() next timer cycle. - -namespace update_window_area_names -{ - int lock = 0; - int pending = 0; - int px3a, py3a, ww3a, hh3a; -} - -void update_window_area() // 17.01 -{ - using namespace update_window_area_names; - - if (! pending) return; - resource_lock(lock); - cairo_t *cr = draw_context_create(gdkwin,draw_context); // 17.04 - Fpaint3(px3a,py3a,ww3a,hh3a,cr); - draw_context_destroy(draw_context); // 17.04 - pending = 0; - resource_unlock(lock); - return; -} - - -// Fpaint3 callable from threads. -// Prepare data about region to update. -// Main thread (above) does the window update. - -void Fpaint3_thread(int px3, int py3, int ww3, int hh3) // 17.01 -{ - using namespace update_window_area_names; - - resource_lock(lock); - - if (pending) { - if (px3 < px3a) { - ww3a += (px3a - px3); - px3a = px3; - } - if (py3 < py3a) { - hh3a += (py3a - py3); - py3a = py3; - } - if (px3 + ww3 > px3a + ww3a) - ww3a += px3 + ww3 - (px3a + ww3a); - if (py3 + hh3 > py3a + hh3a) - hh3a += py3 + hh3 - (py3a + hh3a); - } - - pending = 1; - resource_unlock(lock); - return; -} - - -/********************************************************************************/ - -// F/W view window mouse event function - capture buttons and drag movements - -void mouse_event(GtkWidget *widget, GdkEventButton *event, void *) -{ - void mouse_convert(int xpos1, int ypos1, int &xpos2, int &ypos2); - - int button, time, type, scroll; - int mxdist, mydist, mdist; - static int bdtime = 0, butime = 0; - static int mdragx0, mdragy0; - char *pp; - - #define GAPR GDK_AXIS_PRESSURE - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->fpxb) return; // no image - - type = event->type; - button = event->button; // button, 1/2/3 = left/center/right - time = event->time; - Mwxposn = event->x; // mouse position in window - Mwyposn = event->y; - scroll = ((GdkEventScroll *) event)->direction; // scroll wheel event - - mouse_convert(Mwxposn,Mwyposn,Mxposn,Myposn); // convert to image space - - KBcontrolkey = KBshiftkey = KBaltkey = 0; - if (event->state & GDK_CONTROL_MASK) KBcontrolkey = 1; - if (event->state & GDK_SHIFT_MASK) KBshiftkey = 1; - if (event->state & GDK_MOD1_MASK) KBaltkey = 1; - - if (type == GDK_SCROLL) { // scroll wheel = zoom - zoomx = Mxposn; // zoom center = mouse position - zoomy = Myposn; - if (scroll == GDK_SCROLL_UP) m_zoom(0,"in"); - if (scroll == GDK_SCROLL_DOWN) m_zoom(0,"out"); - return; - } - - if (type == GDK_BUTTON_PRESS) { // button down - Mdrag++; // possible drag start - bdtime = time; // time of button down - Mbutton = button; - mdragx0 = Mwxposn; // window position at button down - mdragy0 = Mwyposn; - Mxdown = Mxposn; // image position at button down - Mydown = Myposn; - Mxdrag = Mydrag = 0; - } - - if (type == GDK_MOTION_NOTIFY) { - if (Mdrag) { // drag underway - Mwdragx = Mwxposn - mdragx0; // drag increment, window space - Mwdragy = Mwyposn - mdragy0; - mdragx0 = Mwxposn; // new drag origin = current position - mdragy0 = Mwyposn; - Mxdrag = Mxposn; // drag position, image space - Mydrag = Myposn; - mouse_dragtime = time - bdtime; // track drag duration - gdk_event_get_axis((GdkEvent *) event, GAPR, &wacom_pressure); // wacom tablet stylus pressure 17.04 - } - else Mwdragx = Mwdragy = Mxdrag = Mydrag = 0; // 18.01 - } - - if (type == GDK_BUTTON_RELEASE) { // button up - Mxclick = Myclick = 0; // reset click status - butime = time; // time of button up - if (butime - bdtime < 400) { // less than 0.4 secs down - mxdist = Mxposn - Mxdown; // get mouse movement between - mydist = Myposn - Mydown; // button press and release - mdist = sqrtf(mxdist * mxdist + mydist * mydist); // if < 10 pixels, 17.04 - if (mdist < 10) { // call this a mouse click - if (Mbutton == 1) LMclick++; // left mouse click - if (Mbutton == 3) RMclick++; // right mouse click - Mxclick = Mxdown; // click = button down position - Myclick = Mydown; - if (button == 2) { // center button click - zoomx = Mxposn; // re-center at mouse (Doriano) - zoomy = Myposn; - gtk_widget_queue_draw(Cdrawin); - } - } - } - Mxdown = Mydown = Mxdrag = Mydrag = Mdrag = Mbutton = 0; // forget buttons and drag - } - - Fmousemain = 1; // mouse acts on main window - if (Mcapture) Fmousemain = 0; // curr. function handles mouse - if (mouseCBfunc) Fmousemain = 0; // mouse owned by callback function - if (KBcontrolkey) Fmousemain = 1; // mouse acts on main window - - if (mouseCBfunc && ! Fmousemain) { // pass to callback function - (* mouseCBfunc)(); // remove busy test 17.04 - Fmousemain = 1; // click/drag params are processed here - } // unless reset by callback func. - - if (FGWM == 'W') filemap_mousefunc(); // geomap mouse function - - if (! Fmousemain) return; // curr. function handles mouse - - if (curr_file && LMclick && FGWM == 'F') { // F-view, left click on image 17.04 - pp = strrchr(curr_file,'/'); - if (! pp) pp = curr_file; - if (strstr(pp,"(fotoxx montage)")) { // click on image montage file, - montage_Lclick_func(Mxclick,Myclick); // popup corresp. image file - LMclick = 0; - } - } - - if (LMclick) { // left click = zoom request - LMclick = 0; - zoomx = Mxclick; // zoom center = mouse - zoomy = Myclick; - m_zoom(0,"in"); - } - - if (RMclick) { // right click - RMclick = 0; - if (Cstate->fzoom) { // if zoomed image, reset to fit window - zoomx = zoomy = 0; - m_zoom(0,"fit"); - } - else if (curr_file && FGWM == 'F') - image_Rclick_popup(); // image right-click popup menu - } - - if (Mxdrag || Mydrag) // drag = scroll by mouse - gtk_widget_queue_draw(Cdrawin); - - return; -} - - -// convert mouse position from window space to image space - -void mouse_convert(int xpos1, int ypos1, int &xpos2, int &ypos2) -{ - xpos2 = (xpos1 - Cstate->dorgx + Cstate->morgx) / Cstate->mscale + 0.5; - ypos2 = (ypos1 - Cstate->dorgy + Cstate->morgy) / Cstate->mscale + 0.5; - - if (xpos2 < 0) xpos2 = 0; // if outside image put at edge - if (ypos2 < 0) ypos2 = 0; - - if (E3pxm) { - if (xpos2 >= E3pxm->ww) xpos2 = E3pxm->ww-1; - if (ypos2 >= E3pxm->hh) ypos2 = E3pxm->hh-1; - } - else { - if (xpos2 >= Cstate->fpxb->ww) xpos2 = Cstate->fpxb->ww-1; - if (ypos2 >= Cstate->fpxb->hh) ypos2 = Cstate->fpxb->hh-1; - } - - return; -} - - -/********************************************************************************/ - -// set new image zoom level or magnification -// zoom: "in" zoom-in in steps -// "out" zoom-out in steps -// "fit" zoom to fit window -// "100" toggle 100% and fit window - -void m_zoom(GtkWidget *, cchar *zoom) -{ - int fww, fhh; - float scalew, scaleh, fitscale, fzoom2, zratio = 1; - float Rzoom, pixels; - - if (! Cstate || ! Cstate->fpxb) return; - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->fpxb) return; // no image - - Rzoom = Cstate->fzoom; // current zoom ratio - - if (E3pxm) { - fww = E3pxm->ww; // 1x image size - fhh = E3pxm->hh; - } - else { - fww = Cstate->fpxb->ww; - fhh = Cstate->fpxb->hh; - } - - if (fww > Dww || fhh > Dhh) { // get window fit scale - scalew = 1.0 * Dww / fww; - scaleh = 1.0 * Dhh / fhh; - if (scalew < scaleh) fitscale = scalew; - else fitscale = scaleh; // window/image, < 1.0 - } - else fitscale = 1.0; // if image < window use 100% - - if (strmatch(zoom,"Zoom+")) zoom = "in"; // menu button: + = zoom in - if (strmatch(zoom,"Zoom-")) zoom = "fit"; // - = fit window - - if (strmatch(zoom,"fit")) Cstate->fzoom = 0; // zoom to fit window - - if (strmatch(zoom,"100")) { - if (Cstate->fzoom != 0) Cstate->fzoom = 0; // toggle 100% and fit window - else Cstate->fzoom = 1; - } - - if (strstr("in out",zoom)) // caller: zoom in or out - { - if (! Cstate->fzoom) Cstate->fzoom = fitscale; // current zoom scale - for (fzoom2 = 0.125; fzoom2 < 4.0; fzoom2 *= zoomratio) // find nearest natural ratio - if (Cstate->fzoom < fzoom2 * sqrt(zoomratio)) break; - if (strmatch(zoom,"in")) zratio = zoomratio; // zoom in, make image larger - if (strmatch(zoom,"out")) zratio = 1.0 / zoomratio; // zoom out, make image smaller - Cstate->fzoom = fzoom2 * zratio; - - if (Cstate->fzoom > 0.124 && Cstate->fzoom < 0.126) // hit these ratios exactly - Cstate->fzoom = 0.125; - if (Cstate->fzoom > 0.24 && Cstate->fzoom < 0.26) - Cstate->fzoom = 0.25; - if (Cstate->fzoom > 0.49 && Cstate->fzoom < 0.51) - Cstate->fzoom = 0.50; - if (Cstate->fzoom > 0.99 && Cstate->fzoom < 1.01) - Cstate->fzoom = 1.00; - if (Cstate->fzoom > 1.99 && Cstate->fzoom < 2.01) - Cstate->fzoom = 2.00; - if (Cstate->fzoom > 3.99) Cstate->fzoom = 4.0; // max. allowed zoom - if (Cstate->fzoom < fitscale) Cstate->fzoom = 0; // image < window - } - - if (FGWM == 'W') { // optimize for geomaps - if (strmatch(zoom,"in") && Cstate->fzoom < 1.0) - Cstate->fzoom = 1.0; // zoom to 100% directly - if (strmatch(zoom,"out")) Cstate->fzoom = 0.0; // zoom out = fit window directly - if (Cstate->fzoom == 1.0 && Rzoom == 1.0) { - gtk_widget_queue_draw(Cdrawin); // if already, move mouse pointer only - return; - } - } - - if (Cstate->fzoom > 1.0) { // limit image in window to 1 gigabyte - pixels = Cstate->fzoom * fww * Cstate->fzoom * fhh; // (333 megapixels x 3 bytes/pixel) - if (pixels > 333 * MEGA) { // if size is at maximum, - Cstate->fzoom = Rzoom; // move mouse pointer only - gtk_widget_queue_draw(Cdrawin); - return; - } - } - - if (! Cstate->fzoom) zoomx = zoomy = 0; // no requested zoom center - Fpaint2(); // refresh window - return; -} - - -/********************************************************************************/ - -// function for dialogs to call to send KB keys for processing by main app - -void KBevent(GdkEventKey *event) -{ - KBpress(0,event,0); - return; -} - - -// keyboard event functions -// GDK key symbols: /usr/include/gtk-3.0/gdk/gdkkeysyms.h - -namespace trimrotate { void KBfunc(int key); } // keyboard functions called from here -namespace perspective { void KBfunc(int key); } -namespace mashup { void KBfunc(int key); } -namespace view360 { void KB_func(int key); } - -int KBpress(GtkWidget *win, GdkEventKey *event, void *) // keyboard key was pressed -{ - int ii, jj, cc; - char shortkey[20] = ""; - cchar *action = 0; - - KBkey = event->keyval; // input key - - KBcontrolkey = KBshiftkey = KBaltkey = 0; // look for combination keys - if (event->state & GDK_CONTROL_MASK) KBcontrolkey = 1; - if (event->state & GDK_SHIFT_MASK) KBshiftkey = 1; - if (event->state & GDK_MOD1_MASK) KBaltkey = 1; - - if (KBshiftkey && KBkey == GDK_KEY_plus) KBshiftkey = 0; // treat Shift [+] same as [+] 18.01 - if (KBkey == GDK_KEY_equal) KBkey = GDK_KEY_plus; // treat [=] same as [+] - if (KBkey == GDK_KEY_KP_Add) KBkey = GDK_KEY_plus; // treat keypad [+] same as [+] - if (KBkey == GDK_KEY_KP_Subtract) KBkey = GDK_KEY_minus; // treat keypad [-] same as [-] - - if (KBkey == GDK_KEY_F1) { // F1 >> user guide - showz_userguide(F1_help_topic); // show topic if there, or page 1 - return 1; - } - - if (Fslideshow) { // slide show active 18.01 - if (KBkey == GDK_KEY_F10) ss_escape = 1; // tell slide show to quit - if (KBkey == GDK_KEY_F11) ss_escape = 1; - if (KBkey == GDK_KEY_Escape) ss_escape = 1; - if (ss_escape) return 1; - ss_KBfunc(KBkey); // pass other keys to slide show - return 1; - } - - if (Fview360) { // view360 active - view360::KB_func(KBkey); // pass KB keys to view360 - return 1; - } - - if (KBkey == GDK_KEY_F10) { // F10: fullscreen toggle with menu - if (! Ffullscreen) win_fullscreen(0); // toggle full-screen mode and back - else win_unfullscreen(); - return 1; - } - - if (KBkey == GDK_KEY_F11) { // F11: fullscreen toggle no menu - if (! Ffullscreen) win_fullscreen(1); // toggle full-screen mode and back - else win_unfullscreen(); - return 1; - } - - if (KBkey == GDK_KEY_Escape) { // ESC key - if (Ffullscreen) win_unfullscreen(); // exit full screen mode - else delete_event(); // quit fotoxx 17.04 - return 1; - } - - if (KBkey == GDK_KEY_p && image_file_type(curr_file) == VIDEO) { // 'P' for totem, OK to use elsewhere -/// shell_quiet("ffplay -loglevel 8 -autoexit \"%s\" ",curr_file); FIXME // best UI, fails appimage + Wayland -/// shell_quiet("gst-launch-1.0 playbin uri=file:\"///%s\" ",curr_file); // works everywhere, bad UI - shell_quiet("totem \"%s\" &",curr_file); // works everywhere, adequate UI - return 1; - } - - if (KBkey == GDK_KEY_Delete) action = (char *) "Delete"; // reserved shortcuts 18.01 - if (KBkey == GDK_KEY_Left) action = (char *) "Left"; - if (KBkey == GDK_KEY_Right) action = (char *) "Right"; - if (KBkey == GDK_KEY_Up) action = (char *) "Up"; - if (KBkey == GDK_KEY_Down) action = (char *) "Down"; - if (KBkey == GDK_KEY_Home) action = (char *) "First"; - if (KBkey == GDK_KEY_End) action = (char *) "Last"; - if (KBkey == GDK_KEY_Page_Up) action = (char *) "Page_Up"; - if (KBkey == GDK_KEY_Page_Down) action = (char *) "Page_Down"; - if (KBkey == GDK_KEY_h && KBcontrolkey) action = (char *) "Show Hidden"; - - if (! action) - { - if (KBkey >= GDK_KEY_F2 && KBkey <= GDK_KEY_F9) { // input key is F2 to F9 - ii = KBkey - GDK_KEY_F1; - strcpy(shortkey,"F1"); - shortkey[1] += ii; - } - - if (! *shortkey && KBkey < 256) // single ascii character - { - if (KBcontrolkey) strcat(shortkey,"Ctrl+"); // build input key combination - if (KBaltkey) strcat(shortkey,"Alt+"); // [Ctrl+] [Alt+] [Shift+] key - if (KBshiftkey) strcat(shortkey,"Shift+"); - cc = strlen(shortkey); - shortkey[cc] = KBkey; - shortkey[cc+1] = 0; - } - - if (*shortkey) { // find shortcut key in shortcut list - for (ii = 0; ii < Nshortcuts; ii++) - if (strmatchcase(shortkey,shortcutkey[ii])) break; - if (ii < Nshortcuts) action = shortcutmenu[ii]; // corresp. action or function - } - } - - if (! action) return 1; // no match - - if (strmatch(action,ZTX("File View"))) { // use translations 18.01 - m_viewmode(0,"F"); - return 1; - } - - if (strmatch(action,ZTX("Gallery View"))) { - m_viewmode(0,"G"); - return 1; - } - - if (strmatch(action,ZTX("World Map View"))) { - m_viewmode(0,"W"); - return 1; - } - - if (strmatch(action,ZTX("Net Map View"))) { - m_viewmode(0,"M"); - return 1; - } - - if (strmatch(action,ZTX("Sync Gallery"))) { - m_sync_gallery(0,0); - return 1; - } - - if (FGWM == 'G') { // G view mode - navi::KBaction(action); // pass KB action to gallery - return 1; - } - - if (strmatch(action,ZTX("Show Hidden"))) return 1; // only meaningful in G view - - if (FGWM == 'W' || FGWM == 'M') return 1; // map view mode, no other KB actions - - if (KBcapture) return 1; // let current function handle it - - if (Fmashup) { // mashup active, pass KB key - mashup::KBfunc(KBkey); - return 1; - } - - if (CEF && CEF->menufunc == m_trim_rotate) { // trim_rotate active, pass KB key - trimrotate::KBfunc(KBkey); - return 1; - } - - if (CEF && CEF->menufunc == m_perspective) { // perspective active, pass KB key - perspective::KBfunc(KBkey); - return 1; - } - - if (strmatch(action,"Left")) { // left arrow - previous image - m_prev(0,0); - return 1; - } - - if (strmatch(action,"Right")) { // right arrow - next image - m_next(0,0); - return 1; - } - - if (KBkey == GDK_KEY_Delete) { // delete key - delete/trash dialog - m_delete_trash(0,0); - return 1; - } - - if (strmatch(action,"Zoom-in")) { // zoom center = mouse position - zoomx = Mxposn; - zoomy = Myposn; - m_zoom(0,"in"); - return 1; - } - - if (strmatch(action,"Zoom-out")) { // zoom to fit window - m_zoom(0,"fit"); - return 1; - } - - if (strmatch(action,"Zoom-1x toggle")) { // zoom to 1x / fit window toggle - m_zoom(0,"100"); - return 1; - } - -// check for menu function shortcut - - for (jj = 0; jj < Nmenus; jj++) { - if (! menutab[jj].menu) continue; // ignore separator 'menu' - if (strmatchcase(action,ZTX(menutab[jj].menu))) break; - } - - if (jj == Nmenus) { - printz("shortcut not found: %s %s \n",shortkey,action); - return 1; - } - - menutab[jj].func(0,menutab[jj].arg); // call the menu function - return 1; -} - - -int KBrelease(GtkWidget *win, GdkEventKey *event, void *) // KB key released -{ - KBkey = 0; // reset current active key - return 1; -} - - -/********************************************************************************/ - -// set the main window to fullscreen status -// (with no menu or panel) - -void win_fullscreen(int hidemenu) -{ - if (FGWM == 'F' && hidemenu) { // if F window, hide menu and panel - gtk_widget_hide(Fmenu); - gtk_widget_hide(Fpanel); - Fpanelshow = 0; - } - - if (hidemenu) gtk_window_fullscreen(MWIN); - else gtk_window_maximize(MWIN); // 17.08 - while (! Ffullscreen) zmainloop(); - return; -} - - -// restore window to former size and restore menu etc. - -void win_unfullscreen() -{ - gtk_window_unfullscreen(MWIN); // restore old window size - gtk_window_unmaximize(MWIN); - gtk_widget_show(Fmenu); - gtk_widget_show(Fpanel); - Fpanelshow = 1; - while (Ffullscreen) zmainloop(); - return; -} - - -/********************************************************************************/ - -// update information in main window title bar - -namespace meta_names -{ - extern char meta_pdate[16]; // image (photo) date, yyyymmddhhmmss -} - - -void set_mwin_title() -{ - GTYPE gtype; - int cc, fposn, Nfiles, Nimages; - char *pp, titlebar[250]; - char pdate[12], ptime[12], pdatetime[24]; - char fname[100], gname[100], fdirk[100]; - - if (FGWM != 'F') return; // 18.01 - - if (! curr_file || *curr_file != '/') { - gtk_window_set_title(MWIN,Arelease); - return; - } - - pp = (char *) strrchr(curr_file,'/'); - strncpy0(fname,pp+1,99); // file name - cc = pp - curr_file; - if (cc < 99) strncpy0(fdirk,curr_file,cc+2); // get dirk/path/ if short enough - else { - strncpy(fdirk,curr_file,96); // or use /dirk/path... - strcpy(fdirk+95,"..."); - } - - Nfiles = navi::Nfiles; // total gallery files (incl. directories) - Nimages = navi::Nimages; // total image files - - fposn = file_position(curr_file,curr_file_posn); // curr. file in curr. gallery? - if (fposn >= 0) { - curr_file_posn = fposn; - fposn = fposn + 1 - Nfiles + Nimages; // position among images, 1-based - } - - if (*meta_names::meta_pdate) { - metadate_pdate(meta_names::meta_pdate,pdate,ptime); // get formatted date and time - strncpy0(pdatetime,pdate,11); // yyyy-mm-dd hh:mm:ss 18.01 - strncpy0(pdatetime+11,ptime,9); - pdatetime[10] = ' '; - } - else strcpy(pdatetime,"(undated)"); - - gtype = navi::gallerytype; - - if (gtype == GDIR) // gallery name = directory - snprintf(titlebar,250," %d/%d %s %s %s", - fposn,Nimages,fdirk,fname,pdatetime); - else { - if (gtype == SEARCH || gtype == META) - strcpy(gname,"SEARCH RESULTS"); - else if (gtype == ALBUM) { - pp = strrchr(navi::galleryname,'/'); - if (! pp) pp = navi::galleryname; - else pp++; - strcpy(gname,"ALBUM: "); - strncpy0(gname+7,pp,87); - } - else if (gtype == RECENT) - strcpy(gname,"RECENT FILES"); - else if (gtype == NEWEST) - strcpy(gname,"NEWEST FILES"); - else - strcpy(gname,"NO GALLERY"); - - if (fposn > 0) - snprintf(titlebar,250,"%s %d/%d %s %s %s", // window title bar - gname,fposn,Nimages,fdirk,fname,pdatetime); - else - snprintf(titlebar,250,"%s (*)/%d %s %s %s", // image not in gallery - gname,Nimages,fdirk,fname,pdatetime); - } - - gtk_window_set_title(MWIN,titlebar); - return; -} - - -/********************************************************************************/ - -// draw a pixel using foreground color. -// px, py are image space. - -void draw_pixel(int px, int py, cairo_t *cr, int fat) -{ - int qx, qy; - static int pqx, pqy; - static uint8 pixel[12]; // 2x2 block of pixels - static PIXBUF *pixbuf1 = 0, *pixbuf4 = 0; - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->fpxb) return; // no image - - if (! pixbuf1) { - pixbuf1 = gdk_pixbuf_new_from_data(pixel,GDKRGB,0,8,1,1,3,0,0); // 1x1 pixels - pixbuf4 = gdk_pixbuf_new_from_data(pixel,GDKRGB,0,8,2,2,6,0,0); // 2x2 pixels - } - - if (Cstate->fpxb->nc > 3) // omit transparent pixels - if (PXBpix(Cstate->fpxb,px,py)[3] < 128) return; - - qx = Mscale * px - Morgx; // image to window space - qy = Mscale * py - Morgy; - - if (qx == pqx && qy == pqy) return; // avoid redundant points - - pqx = qx; - pqy = qy; - - if (qx < 0 || qx > dww-2) return; // keep off image edges - if (qy < 0 || qy > dhh-2) return; - - if (Mscale <= 1 && ! fat) { // write 1x1 pixels 17.08 - pixel[0] = LINE_COLOR[0]; - pixel[1] = LINE_COLOR[1]; - pixel[2] = LINE_COLOR[2]; - - gdk_cairo_set_source_pixbuf(cr,pixbuf1,qx+Dorgx,qy+Dorgy); - cairo_paint(cr); - } - - else { // write 2x2 fat pixels - pixel[0] = pixel[3] = pixel[6] = pixel[9] = LINE_COLOR[0]; - pixel[1] = pixel[4] = pixel[7] = pixel[10] = LINE_COLOR[1]; - pixel[2] = pixel[5] = pixel[8] = pixel[11] = LINE_COLOR[2]; - - gdk_cairo_set_source_pixbuf(cr,pixbuf4,qx+Dorgx,qy+Dorgy); - cairo_paint(cr); - } - - return; -} - - -// erase one drawn pixel - restore from window image Mpxb. -// px, py are image space. - -void erase_pixel(int px, int py, cairo_t *cr) -{ - GdkPixbuf *pixbuf; - static int pqx, pqy; - int qx, qy; - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->fpxb) return; // no image - - qx = Mscale * px; // image to window space - qy = Mscale * py; - - if (qx == pqx && qy == pqy) return; // avoid same target pixel - - pqx = qx; - pqy = qy; - - if (qx < 0 || qx > Mpxb->ww-2) return; // pixel outside scaled image - if (qy < 0 || qy > Mpxb->hh-2) return; - - if (qx < Morgx || qx > Morgx + dww-2) return; // pixel outside drawing window - if (qy < Morgy || qy > Morgy + dhh-2) return; - - pixbuf = gdk_pixbuf_new_subpixbuf(Mpxb->pixbuf,qx,qy,2,2); // 2x2 Mpxb area to copy - qx = qx - Morgx + Dorgx; // target pixel in window - qy = qy - Morgy + Dorgy; - gdk_cairo_set_source_pixbuf(cr,pixbuf,qx,qy); - cairo_paint(cr); - - g_object_unref(pixbuf); - - return; -} - - -/********************************************************************************/ - -// draw line. -// coordinates are image space. -// type = 1/2 for solid/dotted line - -void draw_line(int x1, int y1, int x2, int y2, int type, cairo_t *cr) -{ - float px1, py1, px2, py2; - double dashes[2] = { 4, 4 }; - double R, G, B; - int crflag = 0; - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->fpxb) return; // no image - - px1 = Mscale * x1 - Morgx + Dorgx; // image to window space - py1 = Mscale * y1 - Morgy + Dorgy; - px2 = Mscale * x2 - Morgx + Dorgx; - py2 = Mscale * y2 - Morgy + Dorgy; - - if (px1 > Dww-2) px1 = Dww-2; // play nice - if (py1 > Dhh-2) py1 = Dhh-2; - if (px2 > Dww-2) px2 = Dww-2; - if (py2 > Dhh-2) py2 = Dhh-2; - - R = LINE_COLOR[0] / 255.0; // use line color - G = LINE_COLOR[1] / 255.0; - B = LINE_COLOR[2] / 255.0; - - if (! cr) { - cr = draw_context_create(gdkwin,draw_context); - crflag = 1; - } - - cairo_set_source_rgb(cr,R,G,B); - if (type == 2) cairo_set_dash(cr,dashes,2,0); // dotted line - else cairo_set_dash(cr,dashes,0,0); - - cairo_move_to(cr,px1,py1); // draw line - cairo_line_to(cr,px2,py2); - cairo_stroke(cr); - - if (crflag) draw_context_destroy(draw_context); - return; -} - - -// erase line. refresh line path from mpxb window image. -// double line width is erased. -// coordinates are image space. - -void erase_line(int x1, int y1, int x2, int y2, cairo_t *cr) -{ - float pxm, pym, slope; - int crflag = 0; - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->fpxb) return; // no image - - if (! cr) { - cr = draw_context_create(gdkwin,draw_context); - crflag = 1; - } - - if (abs(y2 - y1) > abs(x2 - x1)) { - slope = 1.0 * (x2 - x1) / (y2 - y1); - for (pym = y1; pym <= y2; pym++) { - pxm = x1 + slope * (pym - y1); - erase_pixel(pxm,pym,cr); - } - } - - else { - slope = 1.0 * (y2 - y1) / (x2 - x1); - for (pxm = x1; pxm <= x2; pxm++) { - pym = y1 + slope * (pxm - x1); - erase_pixel(pxm,pym,cr); - } - } - - if (crflag) draw_context_destroy(draw_context); - return; -} - - -/********************************************************************************/ - -// draw pre-set overlay lines on top of image -// arg = 1: paint lines only (because window repainted) -// 2: erase lines and forget them -// 3: erase old lines, paint new lines, save new in old - -void draw_toplines(int arg, cairo_t *cr) -{ - int ii; - int crflag = 0; - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->fpxb) return; // no image - - if (! cr) { - cr = draw_context_create(gdkwin,draw_context); - crflag = 1; - } - - if (arg == 2 || arg == 3) // erase old lines - for (ii = 0; ii < Nptoplines; ii++) - erase_line(ptoplines[ii].x1,ptoplines[ii].y1, - ptoplines[ii].x2,ptoplines[ii].y2,cr); - - if (arg == 1 || arg == 3) // draw new lines - for (ii = 0; ii < Ntoplines; ii++) - draw_line(toplines[ii].x1,toplines[ii].y1, - toplines[ii].x2,toplines[ii].y2,toplines[ii].type,cr); - - if (crflag) draw_context_destroy(draw_context); - - if (arg == 2) { - Nptoplines = Ntoplines = 0; // forget lines - return; - } - - for (ii = 0; ii < Ntoplines; ii++) // save for future erase - ptoplines[ii] = toplines[ii]; - - Nptoplines = Ntoplines; - return; -} - - -/********************************************************************************/ - -// draw a grid of horizontal and vertical lines. -// grid line spacings are in window space. - -void draw_gridlines(cairo_t *cr) -{ - int G = currgrid; - int px, py, gww, ghh; - int startx, starty, endx, endy, stepx, stepy; - int startx1, starty1; - int crflag = 0; - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->fpxb) return; // no image - - if (! gridsettings[G][GON]) return; // grid lines off - - gww = dww; // grid box size - ghh = dhh; - startx = Dorgx; // starting corner (top left) - starty = Dorgy; - - if (CEF && strmatch(CEF->funcname,"trim_rotate")) { // trim/rotate function is active - gww = Mscale * (trimx2 - trimx1); // fit grid box to trim rectangle - ghh = Mscale * (trimy2 - trimy1); - startx = Mscale * trimx1 - Morgx + Dorgx; - starty = Mscale * trimy1 - Morgy + Dorgy; - } - - endx = startx + gww; - endy = starty + ghh; - - stepx = gridsettings[G][GXS]; // space between grid lines - stepy = gridsettings[G][GYS]; // (window space) - - if (gridsettings[G][GXC]) - stepx = gww / (1 + gridsettings[G][GXC]); // if line counts specified, - if (gridsettings[G][GYC]) // set spacing accordingly - stepy = ghh / ( 1 + gridsettings[G][GYC]); - - startx1 = startx + stepx * gridsettings[G][GXF] / 100; // variable starting offsets - starty1 = starty + stepy * gridsettings[G][GYF] / 100; - - if (! cr) { - cr = draw_context_create(gdkwin,draw_context); - crflag = 1; - } - - cairo_set_source_rgb(cr,1,1,1); // white lines - - if (gridsettings[G][GX] && stepx) - for (px = startx1; px < endx; px += stepx) { - cairo_move_to(cr,px,starty); - cairo_line_to(cr,px,endy); - } - - if (gridsettings[G][GY] && stepy) - for (py = starty1; py < endy; py += stepy) { - cairo_move_to(cr,startx,py); - cairo_line_to(cr,endx,py); - } - - cairo_stroke(cr); - - cairo_set_source_rgb(cr,0,0,0); // adjacent black lines - - if (gridsettings[G][GX] && stepx) - for (px = startx1+1; px < endx+1; px += stepx) { - cairo_move_to(cr,px,starty); - cairo_line_to(cr,px,endy); - } - - if (gridsettings[G][GY] && stepy) - for (py = starty1+1; py < endy+1; py += stepy) { - cairo_move_to(cr,startx,py); - cairo_line_to(cr,endx,py); - } - - cairo_stroke(cr); - - if (crflag) draw_context_destroy(draw_context); - return; -} - - -/********************************************************************************/ - -// maintain a set of text strings written over the image in the window. -// add a new text string to the list. -// multiple text strings can be added with the same ID. -// px and py are image space. - -void add_toptext(int ID, int px, int py, cchar *text, cchar *font) -{ - if (Ntoptext == maxtoptext) { - printz("*** maxtoptext exceeded \n"); - return; - } - - int ii = Ntoptext++; - toptext[ii].ID = ID; - toptext[ii].px = px; - toptext[ii].py = py; - toptext[ii].text = text; - toptext[ii].font = font; - - return; -} - - -// draw current text strings over the image in window. -// called from Fpaint(). - -void draw_toptext(cairo_t *cr) -{ - int crflag = 0; - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->fpxb) return; // no image - - if (! cr) { - cr = draw_context_create(gdkwin,draw_context); - crflag = 1; - } - - for (int ii = 0; ii < Ntoptext; ii++) - draw_text(toptext[ii].px,toptext[ii].py,toptext[ii].text,toptext[ii].font,cr); - - if (crflag) draw_context_destroy(draw_context); - return; -} - - -// delete text strings having the given ID from the list - -void erase_toptext(int ID) -{ - int ii, jj; - - for (ii = jj = 0; ii < Ntoptext; ii++) - { - if (toptext[ii].ID == ID) continue; - else toptext[jj++] = toptext[ii]; - } - - Ntoptext = jj; - return; -} - - -// draw text on window, black on white background -// coordinates are image space - -void draw_text(int px, int py, cchar *text, cchar *font, cairo_t *cr) -{ - static PangoFontDescription *pangofont = 0; - static PangoLayout *pangolayout = 0; - static char priorfont[40] = ""; - int ww, hh; - int crflag = 0; - - if (! Cdrawin) return; - if (! gdkwin) return; - - px = Mscale * px - Morgx + Dorgx; // image to window space - py = Mscale * py - Morgy + Dorgy; - - if (! strmatch(font,priorfont)) { // change font - strncpy0(priorfont,font,40); - if (pangofont) pango_font_description_free(pangofont); - if (pangolayout) g_object_unref(pangolayout); - pangofont = pango_font_description_from_string(font); // make pango layout for font - pangolayout = gtk_widget_create_pango_layout(Cdrawin,0); - pango_layout_set_font_description(pangolayout,pangofont); - } - - pango_layout_set_text(pangolayout,text,-1); // add text to layout - pango_layout_get_pixel_size(pangolayout,&ww,&hh); - - if (! cr) { - cr = draw_context_create(gdkwin,draw_context); - crflag = 1; - } - - cairo_set_source_rgb(cr,1,1,1); // draw white background - cairo_rectangle(cr,px,py,ww,hh); - cairo_fill(cr); - - cairo_move_to(cr,px,py); // draw layout with text - cairo_set_source_rgb(cr,0,0,0); - pango_cairo_show_layout(cr,pangolayout); - - if (crflag) draw_context_destroy(draw_context); - return; -} - - -/********************************************************************************/ - -// maintain a set of circles drawn over the image in the window -// px, py are image space, radius is window space - -void add_topcircle(int px, int py, int radius) -{ - if (Ntopcircles == maxtopcircles) { - printz("*** maxtopcircles exceeded \n"); - return; - } - - int ii = Ntopcircles++; - topcircles[ii].px = px; - topcircles[ii].py = py; - topcircles[ii].radius = radius; - - return; -} - - -// draw current circles over the image in the window -// called from window repaint function Fpaint() - -void draw_topcircles(cairo_t *cr) -{ - int ii, px, py, rad; - double R, G, B; - int crflag = 0; - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->fpxb) return; // no image - - R = LINE_COLOR[0] / 255.0; // use LINE_COLOR - G = LINE_COLOR[1] / 255.0; - B = LINE_COLOR[2] / 255.0; - - if (! cr) { - cr = draw_context_create(gdkwin,draw_context); - crflag = 1; - } - - for (ii = 0; ii < Ntopcircles; ii++) - { - px = topcircles[ii].px * Mscale - Morgx + Dorgx; // image to window space - py = topcircles[ii].py * Mscale - Morgy + Dorgy; - rad = topcircles[ii].radius; // radius is window space - - cairo_set_source_rgb(cr,R,G,B); - cairo_arc(cr,px,py,rad,0,2*PI); // draw 360 deg. arc - cairo_stroke(cr); - } - - if (crflag) draw_context_destroy(draw_context); - return; -} - - -// erase top circles (next window repaint) - -void erase_topcircles() -{ - Ntopcircles = 0; - return; -} - - -/********************************************************************************/ - -// Draw circle around the mouse pointer. -// Prior circle will be erased first. -// Used for mouse/brush radius in select and paint functions. -// cx, cy, rad: center and radius of circle in image space. -// if Ferase, then erase previous circle only. - -void draw_mousecircle(int cx, int cy, int rad, int Ferase, cairo_t *cr) -{ - int px3, py3, ww3, hh3; - static int ppx3, ppy3, pww3 = 0, phh3; - int px, py, pok; - double R, G, B; - double t, dt, t1, t2; - int crflag = 0; - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->mpxb) return; // no image - - if (pww3 > 0) { // erase prior - Fpaint4(ppx3,ppy3,pww3,phh3,cr); // refresh from Mpxb - pww3 = 0; - } - - if (Ferase) return; // erase only, done - - px3 = cx - rad - 2; // convert pointer center + radius - py3 = cy - rad - 2; // to block position, width, length - ww3 = 2 * rad + 4; - hh3 = 2 * rad + 4; - - ppx3 = px3; // remember pixel block area - ppy3 = py3; // to erase in next call - pww3 = ww3; - phh3 = hh3; - - cx = cx * Mscale - Morgx + Dorgx; // convert to window coordinates - cy = cy * Mscale - Morgy + Dorgy; - rad = rad * Mscale; - - R = LINE_COLOR[0] / 255.0; // use LINE_COLOR - G = LINE_COLOR[1] / 255.0; - B = LINE_COLOR[2] / 255.0; - - if (! cr) { - cr = draw_context_create(gdkwin,draw_context); - crflag = 1; - } - - cairo_set_source_rgba(cr,R,G,B,1.0); - - t1 = t2 = -1; // angle limits of arc to draw - dt = 1.0 / rad; - - for (t = 0; t < 2*PI; t += dt) // loop 0-360 degrees - { - px = cx + rad * cos(t); // pixel on mouse circle - py = cy + rad * sin(t); - - pok = 1; // assume pixel OK to draw - - if (px < Dorgx || py < Dorgy) pok = 0; // outside image limits - if (px >= Dorgx+dww || py >= Dorgy+dhh) pok = 0; - - if (pok) { // pixel ok, add to arc - if (t1 < 0) t1 = t; // start of arc to draw - t2 = t; // end of arc, so far - } - - else if (t1 >= 0) { // pixel not ok - cairo_arc(cr,cx,cy,rad,t1,t2); // draw accumulated arc - cairo_stroke(cr); - t1 = t2 = -1; // start over - } - } - - if (t1 >= 0) { - cairo_arc(cr,cx,cy,rad,t1,t2); // draw rest of arc - cairo_stroke(cr); - } - - if (crflag) draw_context_destroy(draw_context); - return; -} - - -// duplicate for drawing and tracking a 2nd mouse circle -// (used by paint/clone to track source pixels being cloned) - -void draw_mousecircle2(int cx, int cy, int rad, int Ferase, cairo_t *cr) -{ - int px3, py3, ww3, hh3; - static int ppx3, ppy3, pww3 = 0, phh3; - int px, py, pok; - double R, G, B; - double t, dt, t1, t2; - int crflag = 0; - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->mpxb) return; // no image - - if (pww3 > 0) { // erase prior - Fpaint4(ppx3,ppy3,pww3,phh3,cr); // refresh from Mpxb - pww3 = 0; - } - - if (Ferase) return; // erase only, done - - px3 = cx - rad - 2; // convert pointer center + radius - py3 = cy - rad - 2; // to block position, width, length - ww3 = 2 * rad + 4; - hh3 = 2 * rad + 4; - - ppx3 = px3; // remember pixel block area - ppy3 = py3; // to erase in next call - pww3 = ww3; - phh3 = hh3; - - cx = cx * Mscale - Morgx + Dorgx; // convert to window coordinates - cy = cy * Mscale - Morgy + Dorgy; - rad = rad * Mscale; - - R = LINE_COLOR[0] / 255.0; // use LINE_COLOR - G = LINE_COLOR[1] / 255.0; - B = LINE_COLOR[2] / 255.0; - - if (! cr) { - cr = draw_context_create(gdkwin,draw_context); - crflag = 1; - } - - cairo_set_source_rgba(cr,R,G,B,1.0); - - t1 = t2 = -1; // angle limits of arc to draw - dt = 1.0 / rad; - - for (t = 0; t < 2*PI; t += dt) // loop 0-360 degrees - { - px = cx + rad * cos(t); // pixel on mouse circle - py = cy + rad * sin(t); - - pok = 1; // assume pixel OK to draw - - if (px < Dorgx || py < Dorgy) pok = 0; // outside image limits - if (px >= Dorgx+dww || py >= Dorgy+dhh) pok = 0; - - if (pok) { // pixel ok, add to arc - if (t1 < 0) t1 = t; // start of arc to draw - t2 = t; // end of arc, so far - } - - else if (t1 >= 0) { // pixel not ok - cairo_arc(cr,cx,cy,rad,t1,t2); // draw accumulated arc - cairo_stroke(cr); - t1 = t2 = -1; // start over - } - } - - if (t1 >= 0) { - cairo_arc(cr,cx,cy,rad,t1,t2); // draw rest of arc - cairo_stroke(cr); - } - - if (crflag) draw_context_destroy(draw_context); - return; -} - - -/********************************************************************************/ - -// Draw ellipse around the mouse pointer. -// Prior ellipse will be erased first. -// cx, cy, cww, chh: center and axes of ellipse in image space. -// if Ferase, then erase previous ellipse only. - -void draw_mousearc(int cx, int cy, int cww, int chh, int Ferase, cairo_t *cr) -{ - int px3, py3, ww3, hh3; - static int ppx3, ppy3, pww3 = 0, phh3; - int px, py; - float a, b, a2, b2; - float x, y, x2, y2; - int crflag = 0; - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->fpxb) return; // no image - - paintlock(1); - - if (! cr) { - cr = draw_context_create(gdkwin,draw_context); - crflag = 1; - } - - if (pww3 > 0) { // erase prior - Fpaint4(ppx3,ppy3,pww3,phh3,cr); // refresh from Mpxb - pww3 = 0; - } - - if (Ferase) { - if (crflag) draw_context_destroy(draw_context); - paintlock(0); - return; - } - - px3 = cx - (cww + 2) / 2; // convert pointer center + radius - py3 = cy - (chh + 2) / 2; // to block position, width, length - ww3 = cww + 2; - hh3 = chh + 2; - - ppx3 = px3; // remember pixel block area - ppy3 = py3; // to erase in next call - pww3 = ww3; - phh3 = hh3; - - a = cww / 2; // ellipse constants from - b = chh / 2; // enclosing rectangle - a2 = a * a; - b2 = b * b; - - for (y = -b; y < b; y++) // step through y values, omitting - { // curve points covered by x values - y2 = y * y; - x2 = a2 * (1 - y2 / b2); - x = sqrtf(x2); // corresp. x values, + and - - py = y + cy; - px = cx - x + 0.5; - draw_pixel(px,py,cr); // draw 2 points on ellipse - px = cx + x + 0.5; - draw_pixel(px,py,cr); - } - - for (x = -a; x < a; x++) // step through x values, omitting - { // curve points covered by y values - x2 = x * x; - y2 = b2 * (1 - x2 / a2); - y = sqrtf(y2); // corresp. y values, + and - - px = x + cx; - py = cy - y + 0.5; - draw_pixel(px,py,cr); // draw 2 points on ellipse - py = cy + y + 0.5; - draw_pixel(px,py,cr); - } - - if (crflag) draw_context_destroy(draw_context); - - paintlock(0); - return; -} - - -/******************************************************************************** - - spline curve setup and edit functions - - Support multiple frames with curves in parallel - (edit curve(s) and image mask curve) - - Usage: - Add frame widget to dialog or zdialog. - Set up drawing in frame: - sd = splcurve_init(frame, callback_func) - Initialize no. of curves in frame (1-10): - sd->Nspc = n - Initialize active flag for curve spc: - sd->fact[spc] = 1 - Initialize vert/horz flag for curve spc: - sd->vert[spc] = hv - Initialize anchor points for curve spc: - sd->nap[spc], sd->apx[spc][xx], sd->apy[spc][yy] - Generate data for curve spc: - splcurve_generate(sd,spc) - Curves will now be shown and edited inside the frame when window is realized. - The callback_func(spc) will be called when curve spc is edited. - Change curve in program: - set anchor points, call splcurve_generate(sd,spc) - Get y-value (0-1) for curve spc and given x-value (0-1): - yval = splcurve_yval(sd,spc,xval) - If faster access to curve is needed (no interpolation): - kk = 1000 * xval; - if (kk > 999) kk = 999; - yval = sd->yval[spc][kk]; - -***/ - -// initialize for spline curve editing -// initial anchor points are pre-loaded into spldat before window is realized - -spldat * splcurve_init(GtkWidget *frame, void func(int spc)) -{ - int cc = sizeof(spldat); // allocate spc curve data area - spldat * sd = (spldat *) zmalloc(cc); - memset(sd,0,cc); - - sd->drawarea = gtk_drawing_area_new(); // drawing area for curves - gtk_container_add(GTK_CONTAINER(frame),sd->drawarea); - sd->spcfunc = func; // user callback function - - gtk_widget_add_events(sd->drawarea,GDK_BUTTON_PRESS_MASK); // connect mouse events to drawing area - gtk_widget_add_events(sd->drawarea,GDK_BUTTON_RELEASE_MASK); - gtk_widget_add_events(sd->drawarea,GDK_BUTTON1_MOTION_MASK); - G_SIGNAL(sd->drawarea,"motion-notify-event",splcurve_adjust,sd); - G_SIGNAL(sd->drawarea,"button-press-event",splcurve_adjust,sd); - G_SIGNAL(sd->drawarea,"realize",splcurve_resize,sd); // 17.04 - G_SIGNAL(sd->drawarea,"draw",splcurve_draw,sd); - - return sd; -} - - -// modify anchor points in curve using mouse - -int splcurve_adjust(void *, GdkEventButton *event, spldat *sd) -{ - int ww, hh, kk; - int mx, my, button, evtype; - static int spc, ap, mbusy = 0, Fdrag = 0; // drag continuation logic - int minspc, minap, apset = 0; - float mxval, myval, cxval, cyval; - float dist2, mindist2 = 0; - float dist, dx, dy; - float minx = 0.01 * splcurve_minx; // % to absolute distance - - mx = event->x; // mouse position in drawing area - my = event->y; - evtype = event->type; - button = event->button; - - if (evtype == GDK_MOTION_NOTIFY) { - if (mbusy) return 0; // discard excess motion events - mbusy++; - zmainloop(); - mbusy = 0; - } - - if (evtype == GDK_BUTTON_RELEASE) { - Fdrag = 0; - return 0; - } - - ww = gtk_widget_get_allocated_width(sd->drawarea); // drawing area size - hh = gtk_widget_get_allocated_height(sd->drawarea); - - if (mx < 0) mx = 0; // limit edge excursions - if (mx > ww) mx = ww; - if (my < 0) my = 0; - if (my > hh) my = hh; - - if (evtype == GDK_BUTTON_PRESS) Fdrag = 0; // left or right click - - if (Fdrag) // continuation of drag - { - if (sd->vert[spc]) { - mxval = 1.0 * my / hh; // mouse position in curve space - myval = 1.0 * mx / ww; - } - else { - mxval = 1.0 * mx / ww; - myval = 1.0 * (hh - my) / hh; - } - - if (ap < sd->nap[spc] - 1) { // not the last anchor point - dx = sd->apx[spc][ap+1] - mxval; // get distance to next anchor point - if (dx < 0.01) return 0; // x-value not increasing, forbid - dy = sd->apy[spc][ap+1] - myval; - dist = sqrtf(dx * dx + dy * dy); - if (dist < minx) return 0; // too close, forbid - } - if (ap > 0) { // not the first anchor point - dx = mxval - sd->apx[spc][ap-1]; // get distance to prior anchor point - if (dx < 0.01) return 0; // x-value not increasing, forbid - dy = myval - sd->apy[spc][ap-1]; - dist = sqrtf(dx * dx + dy * dy); - if (dist < minx) return 0; // too close, forbid - } - - apset = 1; // mxval/myval = new node position - } - - else // mouse click or new drag begin - { - minspc = minap = -1; // find closest curve/anchor point - mindist2 = 999999; - - for (spc = 0; spc < sd->Nspc; spc++) // loop curves - { - if (! sd->fact[spc]) continue; // not active - - if (sd->vert[spc]) { - mxval = 1.0 * my / hh; // mouse position in curve space - myval = 1.0 * mx / ww; - } - else { - mxval = 1.0 * mx / ww; - myval = 1.0 * (hh - my) / hh; - } - - for (ap = 0; ap < sd->nap[spc]; ap++) // loop anchor points - { - cxval = sd->apx[spc][ap]; - cyval = sd->apy[spc][ap]; - dist2 = (mxval-cxval)*(mxval-cxval) - + (myval-cyval)*(myval-cyval); - if (dist2 < mindist2) { - mindist2 = dist2; // remember closest anchor point - minspc = spc; - minap = ap; - } - } - } - - if (minspc < 0) return 0; // impossible - spc = minspc; // nearest curve - ap = minap; // nearest anchor point - } - - if (evtype == GDK_BUTTON_PRESS && button == 3) // right click, remove anchor point - { - if (sqrtf(mindist2) > minx) return 0; // not close enough - if (sd->nap[spc] < 3) return 0; // < 2 anchor points would remain - sd->nap[spc]--; // decr. before loop - for (kk = ap; kk < sd->nap[spc]; kk++) { - sd->apx[spc][kk] = sd->apx[spc][kk+1]; - sd->apy[spc][kk] = sd->apy[spc][kk+1]; - } - splcurve_generate(sd,spc); // regenerate data for modified curve - gtk_widget_queue_draw(sd->drawarea); - sd->spcfunc(spc); // call user function - return 0; - } - - if (! Fdrag) // new drag or left click - { - if (sd->vert[spc]) { - mxval = 1.0 * my / hh; // mouse position in curve space - myval = 1.0 * mx / ww; - } - else { - mxval = 1.0 * mx / ww; - myval = 1.0 * (hh - my) / hh; - } - - if (sqrtf(mindist2) < minx) // anchor point close enough, - { // move this one to mouse position - if (ap < sd->nap[spc]-1) { // not the last anchor point - dx = sd->apx[spc][ap+1] - mxval; // get distance to next anchor point - if (dx < 0.01) return 0; // x-value not increasing, forbid - dy = sd->apy[spc][ap+1] - myval; - dist = sqrtf(dx * dx + dy * dy); - if (dist < minx) return 0; // too close, forbid - } - if (ap > 0) { // not the first anchor point - dx = mxval - sd->apx[spc][ap-1]; // get distance to prior anchor point - if (dx < 0.01) return 0; // x-value not increasing, forbid - dy = myval - sd->apy[spc][ap-1]; - dist = sqrtf(dx * dx + dy * dy); - if (dist < minx) return 0; // too close, forbid - } - - apset = 1; // mxval/myval = new node position - } - - else // none close, add new anchor point - { - minspc = -1; // find closest curve to mouse - mindist2 = 999999; - - for (spc = 0; spc < sd->Nspc; spc++) // loop curves - { - if (! sd->fact[spc]) continue; // not active - - if (sd->vert[spc]) { - mxval = 1.0 * my / hh; // mouse position in curve space - myval = 1.0 * mx / ww; - } - else { - mxval = 1.0 * mx / ww; - myval = 1.0 * (hh - my) / hh; - } - - cyval = splcurve_yval(sd,spc,mxval); - dist2 = fabsf(myval - cyval); - if (dist2 < mindist2) { - mindist2 = dist2; // remember closest curve - minspc = spc; - } - } - - if (minspc < 0) return 0; // impossible - if (mindist2 > minx) return 0; // not close enough to any curve - spc = minspc; - - if (sd->nap[spc] > 49) { - zmessageACK(Mwin,ZTX("Exceed 50 anchor points")); - return 0; - } - - if (sd->vert[spc]) { - mxval = 1.0 * my / hh; // mouse position in curve space - myval = 1.0 * mx / ww; - } - else { - mxval = 1.0 * mx / ww; - myval = 1.0 * (hh - my) / hh; - } - - for (ap = 0; ap < sd->nap[spc]; ap++) // find anchor point with next higher x - if (mxval <= sd->apx[spc][ap]) break; // (ap may come out 0 or nap) - - if (ap < sd->nap[spc] && sd->apx[spc][ap] - mxval < minx) // disallow < minx from next - return 0; // or prior anchor point - if (ap > 0 && mxval - sd->apx[spc][ap-1] < minx) return 0; - - for (kk = sd->nap[spc]; kk > ap; kk--) { // make hole for new point - sd->apx[spc][kk] = sd->apx[spc][kk-1]; - sd->apy[spc][kk] = sd->apy[spc][kk-1]; - } - - sd->nap[spc]++; // up point count - apset = 1; // mxval/myval = new node position - } - } - - if (evtype == GDK_MOTION_NOTIFY) Fdrag = 1; // remember drag is underway - - if (apset) - { - sd->apx[spc][ap] = mxval; // new or moved anchor point - sd->apy[spc][ap] = myval; // at mouse position - - splcurve_generate(sd,spc); // regenerate data for modified curve - if (sd->drawarea) gtk_widget_queue_draw(sd->drawarea); // redraw graph - if (sd->spcfunc) sd->spcfunc(spc); // call user function - } - - return 0; -} - - -// add a new anchor point to a curve -// spc: curve number -// px, py: node coordinates in the range 0-1 - -int splcurve_addnode(spldat *sd, int spc, float px, float py) -{ - int ap, kk; - float minx = 0.01 * splcurve_minx; // % to absolute distance - - for (ap = 0; ap < sd->nap[spc]; ap++) // find anchor point with next higher x - if (px <= sd->apx[spc][ap]) break; // (ap may come out 0 or nap) - - if (ap < sd->nap[spc] && sd->apx[spc][ap] - px < minx) // disallow < minx from next - return 0; // or prior anchor point - if (ap > 0 && px - sd->apx[spc][ap-1] < minx) return 0; - - for (kk = sd->nap[spc]; kk > ap; kk--) { // make hole for new point - sd->apx[spc][kk] = sd->apx[spc][kk-1]; - sd->apy[spc][kk] = sd->apy[spc][kk-1]; - } - - sd->apx[spc][ap] = px; // add node coordinates - sd->apy[spc][ap] = py; - - sd->nap[spc]++; // up point count - return 1; -} - - -// if height/width too small, make bigger - -int splcurve_resize(GtkWidget *drawarea) // 17.04 -{ - int ww = gtk_widget_get_allocated_width(drawarea); - int hh = gtk_widget_get_allocated_height(drawarea); - if (hh < ww/2) gtk_widget_set_size_request(drawarea,ww,ww/2); - return 1; -} - - -// for expose event or when a curve is changed -// draw all curves based on current anchor points - -int splcurve_draw(GtkWidget *drawarea, cairo_t *cr, spldat *sd) -{ - int ww, hh, spc, ap; - float xval, yval, px, py, qx, qy; - - ww = gtk_widget_get_allocated_width(sd->drawarea); // drawing area size - hh = gtk_widget_get_allocated_height(sd->drawarea); - if (ww < 50 || hh < 50) return 0; - - cairo_set_line_width(cr,1); - cairo_set_source_rgb(cr,0.7,0.7,0.7); - - for (int ii = 0; ii < sd->Nscale; ii++) // draw y-scale lines if any - { - px = ww * sd->xscale[0][ii]; - py = hh - hh * sd->yscale[0][ii]; - qx = ww * sd->xscale[1][ii]; - qy = hh - hh * sd->yscale[1][ii]; - cairo_move_to(cr,px,py); - cairo_line_to(cr,qx,qy); - } - cairo_stroke(cr); - - cairo_set_source_rgb(cr,0,0,0); - - for (spc = 0; spc < sd->Nspc; spc++) // loop all curves - { - if (! sd->fact[spc]) continue; // not active - - if (sd->vert[spc]) // vert. curve - { - for (py = 0; py < hh; py++) // generate all points for curve - { - xval = 1.0 * py / hh; - yval = splcurve_yval(sd,spc,xval); - px = ww * yval; - if (py == 0) cairo_move_to(cr,px,py); - cairo_line_to(cr,px,py); - } - cairo_stroke(cr); - - for (ap = 0; ap < sd->nap[spc]; ap++) // draw boxes at anchor points - { - xval = sd->apx[spc][ap]; - yval = sd->apy[spc][ap]; - px = ww * yval; - py = hh * xval; - cairo_rectangle(cr,px-2,py-2,4,4); - } - cairo_fill(cr); - } - else // horz. curve - { - for (px = 0; px < ww; px++) // generate all points for curve - { - xval = 1.0 * px / ww; - yval = splcurve_yval(sd,spc,xval); - py = hh - hh * yval; - if (px == 0) cairo_move_to(cr,px,py); - cairo_line_to(cr,px,py); - } - cairo_stroke(cr); - - for (ap = 0; ap < sd->nap[spc]; ap++) // draw boxes at anchor points - { - xval = sd->apx[spc][ap]; - yval = sd->apy[spc][ap]; - px = ww * xval; - py = hh - hh * yval; - cairo_rectangle(cr,px-2,py-2,4,4); - } - cairo_fill(cr); - } - } - - return 0; -} - - -// generate all curve data points when anchor points are modified - -int splcurve_generate(spldat *sd, int spc) -{ - int kk, kklo, kkhi; - float xval, yvalx; - - spline1(sd->nap[spc],sd->apx[spc],sd->apy[spc]); // compute curve fitting anchor points - - kklo = 1000 * sd->apx[spc][0] - 30; // xval range = anchor point range - if (kklo < 0) kklo = 0; // + 0.03 extra below/above - kkhi = 1000 * sd->apx[spc][sd->nap[spc]-1] + 30; - if (kkhi > 1000) kkhi = 1000; - - for (kk = 0; kk < 1000; kk++) // generate all points for curve - { - xval = 0.001 * kk; // remove anchor point limits - yvalx = spline2(xval); - if (yvalx < 0) yvalx = 0; // yval < 0 not allowed, > 1 OK - sd->yval[spc][kk] = yvalx; - } - - return 0; -} - - -// Retrieve curve data using interpolation of saved table of values - -float splcurve_yval(spldat *sd, int spc, float xval) -{ - int ii; - float x1, x2, y1, y2, y3; - - if (xval <= 0) return sd->yval[spc][0]; - if (xval >= 0.999) return sd->yval[spc][999]; - - x2 = 1000.0 * xval; - ii = x2; - x1 = ii; - y1 = sd->yval[spc][ii]; - y2 = sd->yval[spc][ii+1]; - y3 = y1 + (y2 - y1) * (x2 - x1); - return y3; -} - - -// load curve data from a file -// returns 0 if succcess, sd is initialized from file data -// returns 1 if fail (invalid file data), sd not modified - -int splcurve_load(spldat *sd, FILE *fid) -{ - char *pfile, *pp, buff[300]; - int nn, ii, jj, err, myfid = 0; - int Nspc, fact[10], vert[10], nap[10]; - float apx[10][50], apy[10][50]; - - if (! fid) // request file from user - { - pfile = zgetfile(ZTX("load curve from a file"),MWIN,"file",saved_curves_dirk); - if (! pfile) return 1; - fid = fopen(pfile,"r"); - zfree(pfile); - if (! fid) goto fail; - myfid = 1; - } - - pp = fgets_trim(buff,300,fid,1); - if (! pp) goto fail; - nn = sscanf(pp,"%d",&Nspc); // no. of curves - if (nn != 1) goto fail; - if (Nspc < 1 || Nspc > 10) goto fail; - if (Nspc != sd->Nspc) goto fail; - - for (ii = 0; ii < Nspc; ii++) // loop each curve - { - pp = fgets_trim(buff,300,fid,1); - if (! pp) goto fail; - nn = sscanf(pp,"%d %d %d",&fact[ii],&vert[ii],&nap[ii]); // active flag, vert flag, anchors - if (nn != 3) goto fail; - if (fact[ii] < 0 || fact[ii] > 1) goto fail; - if (vert[ii] < 0 || vert[ii] > 1) goto fail; - if (nap[ii] < 2 || nap[ii] > 50) goto fail; - - pp = fgets_trim(buff,300,fid,1); // anchor points: nnn/nnn nnn/nnn ... - - for (jj = 0; jj < nap[ii]; jj++) // anchor point values - { - pp = (char *) strField(buff,"/ ",2*jj+1); - if (! pp) goto fail; - err = convSF(pp,apx[ii][jj],0,1); - if (err) goto fail; - pp = (char *) strField(buff,"/ ",2*jj+2); - err = convSF(pp,apy[ii][jj],0,1); - if (err) goto fail; - } - } - - if (myfid) fclose(fid); - - sd->Nspc = Nspc; // copy curve data to caller's arg - - for (ii = 0; ii < Nspc; ii++) - { - sd->fact[ii] = fact[ii]; - sd->vert[ii] = vert[ii]; - sd->nap[ii] = nap[ii]; - - for (jj = 0; jj < nap[ii]; jj++) - { - sd->apx[ii][jj] = apx[ii][jj]; - sd->apy[ii][jj] = apy[ii][jj]; - } - } - - for (ii = 0; ii < Nspc; ii++) // generate curve data from anchor points - splcurve_generate(sd,ii); - - if (sd->drawarea) // redraw all curves - gtk_widget_queue_draw(sd->drawarea); - - return 0; // success - -fail: - if (fid && myfid) fclose(fid); - zmessageACK(Mwin,ZTX("curve file is invalid")); - return 1; -} - - -// save curve data to a file - -int splcurve_save(spldat *sd, FILE *fid) -{ - char *pfile, *pp; - int ii, jj, myfid = 0; - - if (! fid) - { - pp = zgetfile(ZTX("save curve to a file"),MWIN,"save",saved_curves_dirk); - if (! pp) return 1; - pfile = zstrdup(pp,8); - zfree(pp); - pp = strrchr(pfile,'/'); // force .curve extension - if (pp) pp = strrchr(pp,'.'); - if (pp) strcpy(pp,".curve"); - else strcat(pfile,".curve"); - - fid = fopen(pfile,"w"); - zfree(pfile); - if (! fid) return 1; - myfid = 1; - } - - fprintf(fid,"%d \n",sd->Nspc); // no. of curves - - for (ii = 0; ii < sd->Nspc; ii++) // loop each curve - { - fprintf(fid,"%d %d %d \n",sd->fact[ii],sd->vert[ii],sd->nap[ii]); // activ flag, vert flag, anchors - for (jj = 0; jj < sd->nap[ii]; jj++) // anchor point values - fprintf(fid,"%.4f/%.4f ",sd->apx[ii][jj],sd->apy[ii][jj]); - fprintf(fid,"\n"); - } - - if (myfid) fclose(fid); - return 0; -} - - -/******************************************************************************** - - edit transaction management - - edit_setup() get E0 if none, E0 > E1 > E3 - edit_cancel() free (E1 E3 ER) - edit_done() E3 > E0, free (E1 ER) add to undo stack - edit_undo() E3 > ER, E1 > E3 - edit_redo() ER > E3 - edit_reset() free ER, E1 > E3 - edit_fullsize() free (E1 E3) E0 > E1 > E3 - -********************************************************************************* - - Setup for a new edit transaction - Create E1 (edit input) and E3 (edit output) pixmaps from - previous edit (E0) or image file (new E0). - - FprevReq 0 edit full-size image - 1 edit preview image unless select area exists - - Farea 0 select_area is invalid and will be deleted (e.g. rotate) - 1 select_area not used but remains valid (e.g. red-eye) - 2 select_area can be used and remains valid (e.g. gamma) - -*********************************************************************************/ - -int edit_setup(editfunc &EF) -{ - int yn, rww, rhh, ftype; - int Fpreview; - char *pp; - - if (! curr_file) return 0; // no image file - - ftype = image_file_type(curr_file); - if (ftype != IMAGE && ftype != RAW) { // not editable 17.08 - pp = strrchr(curr_file,'/'); - if (pp) pp++; - zmessageACK(Mwin,ZTX("File cannot be edited \n %s"),pp); - return 0; - } - - if (FGWM != 'F') m_viewmode(0,"F"); // insure file view mode - - if (checkpend("busy block")) return 0; // blocking function - - if (CEF && CEF->zd) // if pending edit, complete it - zdialog_send_event(CEF->zd,"done"); - if (checkpend("edit")) return 0; // failed (HDR etc.) - - if (URS_pos > maxedits-2) { // undo/redo stack capacity reached - zmessageACK(Mwin,ZTX("Too many edits, please save image")); - return 0; - } - - if (Fscriptbuild && ! EF.Fscript) { // this function not scriptable - zmessageACK(Mwin,ZTX("this function cannot be scripted")); - return 0; - } - - free_filemap(); // free map memory for edit usage - - sa_validate(); // delete area if not valid - - if (EF.Farea == 0 && sa_stat) { // select area will be lost, warn user - yn = zmessageYN(Mwin,ZTX("Select area cannot be kept.\n" - "Continue?")); - if (! yn) return 0; - sa_unselect(); // unselect area - if (zdsela) zdialog_free(zdsela); - } - - if (EF.Farea == 2 && sa_stat && sa_stat != 3) { // select area exists and can be used, - yn = zmessageYN(Mwin,ZTX("Select area not active.\n" // but not active, ask user - "Continue?")); - if (! yn) return 0; - } - - if (! E0pxm) { // first edit for this file - E0pxm = PXM_load(curr_file,1); // get E0 image (poss. 16-bit color) - if (! E0pxm) return 0; - curr_file_bpc = f_load_bpc; - } - - paintlock(1); // block window updates - - if (URS_pos == 0) save_undo(); // initial image >> undo/redo stack - - Fpreview = 0; // assume no preview - - if (EF.FprevReq && ! Fzoom) // preview requested by edit func. - Fpreview = 1; - - if (EF.Farea == 2 && sa_stat == 3) // not if select area active - Fpreview = 0; - - if (E0pxm->ww * E0pxm->hh < 2000000) // if image is small, don't use preview - Fpreview = 0; - - if (E0pxm->ww < 1.4 * Dww && E0pxm->hh < 1.4 * Dhh) // if image slightly larger than window, - Fpreview = 0; // don't use preview - - if (Fpreview) { - if (Fpxb->ww * Dhh > Fpxb->hh * Dww) { // use preview image 1.4 * window size - rww = 1.4 * Dww; - if (rww < 1200) rww = 1200; // at least 1200 on one side - rhh = 1.0 * rww * Fpxb->hh / Fpxb->ww + 0.5; - } - else { - rhh = 1.4 * Dhh; - if (rhh < 1200) rhh = 1200; - rww = 1.0 * rhh * Fpxb->ww / Fpxb->hh + 0.5; - } - if (rww > Fpxb->ww) Fpreview = 0; - } - - if (Fpreview) { - E1pxm = PXM_rescale(E0pxm,rww,rhh); // scale image to preview size - sa_show(0,0); // hide select area if present - } - else E1pxm = PXM_copy(E0pxm); // else use full size imagez - - E3pxm = PXM_copy(E1pxm); // E1 >> E3 - - CEF = &EF; // set current edit function - CEF->Fmods = 0; // image not modified yet - CEF->Fpreview = Fpreview; - - CEF->thread_command = CEF->thread_status = 0; // no thread running - CEF->thread_pend = CEF->thread_done = CEF->thread_hiwater = 0; // no work pending or done - if (CEF->threadfunc) start_thread(CEF->threadfunc,0); // start thread func if any - - paintlock(0); // unblock window updates - Fpaintnow(); // update image synchronous - return 1; -} - - -/********************************************************************************/ - -// print error message if CEF invalid - -int CEF_invalid() -{ - if (CEF) return 0; - printz("CEF invalid \n"); - return 1; -} - - -/********************************************************************************/ - -// process edit cancel -// keep: retain zdialog, mousefunc, curves - -void edit_cancel(int keep) -{ - if (CEF_invalid()) return; - wrapup_thread(9); // tell thread to quit, wait - if (CEF_invalid()) return; - paintlock(1); // block window updates - - PXM_free(E1pxm); // free E1, E3, ER - PXM_free(E3pxm); - PXM_free(ERpxm); - PXM_free(E8pxm); - PXM_free(E9pxm); - - if (! keep) - { - if (CEF->zd == zd_thread) zd_thread = 0; // thread -> zdialog event - if (CEF->curves) zfree(CEF->curves); // free curves data - if (CEF->mousefunc == mouseCBfunc) freeMouse(); // if my mouse, free mouse - if (CEF->zd) zdialog_free(CEF->zd); // kill dialog - } - - CEF = 0; // no current edit func - paintlock(0); // unblock window updates - Fpaintnow(); // update image synchronous - return; -} - - -/********************************************************************************/ - -// process edit done -// keep: retain zdialog, mousefunc, curves - -void edit_done(int keep) -{ - if (CEF_invalid()) return; - wrapup_thread(8); // tell thread to finish, wait - if (CEF_invalid()) return; - paintlock(1); // block window updates - - if (CEF->Fmods) { // image was modified - PXM_free(E0pxm); - E0pxm = E3pxm; // E3 updated image >> E0 - E3pxm = 0; - PXM_free(E1pxm); // free E1, ER - PXM_free(ERpxm); - PXM_free(E8pxm); - PXM_free(E9pxm); - URS_pos++; // next undo/redo stack position - URS_max = URS_pos; // image modified - higher mods now obsolete - save_undo(); // save undo state (for following undo) - if (Fscriptbuild) addscript(CEF); // add to script file - } - - else { // not modified - PXM_free(E1pxm); // free E1, E3, ER - PXM_free(E3pxm); - PXM_free(ERpxm); - PXM_free(E8pxm); - PXM_free(E9pxm); - } - - if (! keep) - { - if (CEF->zd == zd_thread) zd_thread = 0; // thread -> zdialog event - if (CEF->curves) zfree(CEF->curves); // free curves data - if (CEF->mousefunc == mouseCBfunc) freeMouse(); // if my mouse, free mouse - if (CEF->zd) zdialog_free(CEF->zd); // kill dialog - } - - CEF = 0; // no current edit func - paintlock(0); // unblock window updates - Fpaintnow(); // update image synchronous - return; -} - - -/********************************************************************************/ - -// Convert from preview mode (window-size pixmaps) to full-size pixmaps. -// Called by the edit function prior to edit_done(). - -void edit_fullsize() -{ - if (CEF_invalid()) return; - wait_thread_idle(); // wait for thread idle - if (CEF_invalid()) return; - if (! CEF->Fpreview) return; // FprevReq ignored if small image - paintlock(1); // block window updates - PXM_free(E1pxm); // free preview pixmaps - PXM_free(E3pxm); - E1pxm = PXM_copy(E0pxm); // E0 >> E1, full size image - E3pxm = PXM_copy(E1pxm); // E1 >> E3 - PXB_free(Cstate->fpxb); - Cstate->fpxb = PXM_PXB_copy(E3pxm); // update Fpxb from image E3 - Fzoom = 0; - paintlock(0); // unblock window updates - CEF->Fpreview = 0; - return; -} - - -// edit undo, redo, reset functions -// these apply within an active edit function - -void edit_undo() -{ - F1_help_topic = "undo_redo"; - - if (CEF_invalid()) return; - wait_thread_idle(); // wait for thread idle - if (CEF_invalid()) return; - if (! CEF->Fmods) return; // not modified - paintlock(1); // block window updates - - PXM_free(ERpxm); // E3 >> redo copy - ERpxm = E3pxm; - E3pxm = PXM_copy(E1pxm); // E1 >> E3 - CEF->Fmods = 0; // reset image modified status - paintlock(0); // unblock window updates - Fpaintnow(); // update image synchronous - return; -} - - -void edit_redo() -{ - F1_help_topic = "undo_redo"; - - if (CEF_invalid()) return; - wait_thread_idle(); // wait for thread idle - if (CEF_invalid()) return; - if (! ERpxm) return; // no prior undo - paintlock(1); // block window updates - - PXM_free(E3pxm); // redo copy >> E3 - E3pxm = ERpxm; - ERpxm = 0; - CEF->Fmods = 1; // image modified - paintlock(0); // unblock window updates - Fpaintnow(); // update image synchronous - return; -} - - -void edit_reset() // reset E3 to E1 status -{ - if (CEF_invalid()) return; - wait_thread_idle(); // wait for thread idle - if (CEF_invalid()) return; - if (! CEF->Fmods) return; // not modified - paintlock(1); // block window updates - - PXM_free(ERpxm); // delete redo copy - PXM_free(E3pxm); - E3pxm = PXM_copy(E1pxm); // E1 >> E3 - CEF->Fmods = 0; // reset image modified status - paintlock(0); // unblock window updates - Fpaintnow(); // update image synchronous - return; -} - - -/********************************************************************************/ - -// Load/save all edit zdialog widgets data from/to a file. -// dirname for edit data files: /home//.fotoxx/funcname -// where zdialog data is saved for the respective function. -// return 0 = OK, +N = error - -int edit_load_widgets(editfunc *EF, FILE *fid) -{ - using namespace zfuncs; - - cchar *mess = ZTX("Load settings from file"); - zdialog *zd; - spldat *sd; - int myfid = 0; - char *filename, dirname[200], buff[1000]; - char *wname, *wdata, wdata2[1000]; - char *pp, *pp1, *pp2; - int ii, kk, err, cc1, cc2; - - zd = EF->zd; - sd = EF->curves; - - if (! fid) // fid from script - { - snprintf(dirname,200,"%s/%s",get_zhomedir(),EF->funcname); // directory for data files - filename = zgetfile(mess,MWIN,"file",dirname,0); // open data file - if (! filename) return 1; // user cancel - fid = fopen(filename,"r"); - zfree(filename); - if (! fid) { - zmessageACK(Mwin,"%s \n %s",filename,strerror(errno)); - return 1; - } - myfid = 1; - } - - for (ii = 0; ii < zdmaxwidgets; ii++) // read widget data recs - { - pp = fgets_trim(buff,1000,fid,1); - if (! pp) break; - if (strmatch(pp,"curves")) { - if (! sd) goto baddata; - err = splcurve_load(sd,fid); // load curves data - if (err) goto baddata; - continue; - } - if (strmatch(pp,"end")) break; - pp1 = pp; - pp2 = strstr(pp1," =="); - if (! pp2) continue; // widget has no data - cc1 = pp2 - pp1; - if (cc1 > 100) continue; - pp1[cc1] = 0; - wname = pp1; // widget name - pp2 += 3; - if (*pp2 == ' ') pp2++; - wdata = pp2; // widget data - cc2 = strlen(wdata); - if (cc2 < 1) wdata = (char *) ""; - if (cc2 > 300) continue; - repl_1str(wdata,wdata2,"\\n","\n"); // replace "\n" with newline chars. - kk = zdialog_put_data(zd,wname,wdata2); - if (! kk) goto baddata; - } - - if (myfid) fclose(fid); - return 0; - -baddata: - zmessageACK(Mwin,ZTX("file data does not fit dialog")); - if (myfid) fclose(fid); - return 1; -} - - -int edit_save_widgets(editfunc *EF, FILE *fid) -{ - using namespace zfuncs; - - cchar *mess = ZTX("Save settings to a file"); - zdialog *zd; - spldat *sd; - int myfid = 0; - char *filename, dirname[200]; - char *wtype, *wname, *wdata, wdata2[1000]; - int ii, cc; - - cchar *editwidgets = "entry zentry edit text togbutt check combo comboE " // widget types to save 17.08 - "radio spin zspin hscale vscale colorbutt"; - zd = EF->zd; - sd = EF->curves; - - if (! fid) // fid from script - { - snprintf(dirname,200,"%s/%s",get_zhomedir(),EF->funcname); // directory for data files - filename = zgetfile(mess,MWIN,"save",dirname,0); // open data file - if (! filename) return 1; // user cancel - fid = fopen(filename,"w"); - zfree(filename); - if (! fid) { - zmessageACK(Mwin,"%s \n %s",filename,strerror(errno)); - return 1; - } - myfid = 1; - } - - for (ii = 0; ii < zdmaxwidgets; ii++) - { - wtype = (char *) zd->widget[ii].type; - if (! wtype) break; - if (! strstr(editwidgets,wtype)) continue; - wname = (char *) zd->widget[ii].name; // write widget data recs: - wdata = zd->widget[ii].data; // widgetname == widgetdata - if (! wdata) continue; - cc = strlen(wdata); - if (cc > 900) continue; - repl_1str(wdata,wdata2,"\n","\\n"); - fprintf(fid,"%s == %s \n",wname,wdata); - } - - if (sd) { - fprintf(fid,"curves\n"); - splcurve_save(sd,fid); - } - - fprintf(fid,"end\n"); - - if (myfid) fclose(fid); - return 0; -} - - -// functions to support [prev] buttons in edit dialogs -// load or save last-used widgets - -int edit_load_prev_widgets(editfunc *EF) -{ - using namespace zfuncs; - - char filename[200]; - FILE *fid; - int err; - - snprintf(filename,200,"%s/%s/%s",get_zhomedir(),EF->funcname,"last-used"); - fid = fopen(filename,"r"); - if (! fid) { - zmessageACK(Mwin,"%s \n %s",filename,strerror(errno)); - return 1; - } - - err = edit_load_widgets(EF,fid); - fclose(fid); - return err; -} - - -int edit_save_last_widgets(editfunc *EF) -{ - using namespace zfuncs; - - char filename[200], dirname[200]; - FILE *fid; - int err; - - snprintf(filename,200,"%s/%s/%s",get_zhomedir(),EF->funcname,"last-used"); - fid = fopen(filename,"w"); - if (! fid) { - snprintf(dirname,200,"%s/%s",get_zhomedir(),EF->funcname); // create missing directory 17.08 - err = mkdir(dirname,0760); - if (err) { - printz("%s \n %s \n",dirname,strerror(errno)); - return 1; - } - fid = fopen(filename,"w"); // open again - } - - if (! fid) { - printz("%s \n %s \n",filename,strerror(errno)); - return 1; - } - - err = edit_save_widgets(EF,fid); - fclose(fid); - return err; -} - - -/******************************************************************************** - undo / redo menu buttons -*********************************************************************************/ - -// [undo/redo] menu function -// Call m_undo() / m_redo() if left / right mouse click on menu. -// If A key is pressed, call undo_all() or redo_all(). -// If middle mouse button is clicked, pop-up a list of all edit steps -// and choose a step to go back to. - -void m_undo_redo(GtkWidget *, cchar *) -{ - void undo_redo_choice(GtkWidget *, cchar *menu); - - GtkWidget *popmenu; - int button = zfuncs::vmenuclickbutton; - char menuitem[40], flag; - - F1_help_topic = "undo_redo"; - - if (button == 1) { - if (KBkey == GDK_KEY_a) undo_all(); // undo all steps - else m_undo(0,0); // undo one step - } - - if (button == 2) // go back to selected edit step - { - if (URS_max == 0) return; - popmenu = create_popmenu(); - for (int ii = 0; ii < 30; ii++) { // insert all edit steps - if (ii > URS_max) break; - if (ii == URS_pos) flag = '*'; // flag step matching current status - else flag = ' '; - snprintf(menuitem,40,"%d %s %c",ii,URS_funcs[ii],flag); - add_popmenu_item(popmenu,menuitem,undo_redo_choice,(char *) &Nval[ii],0); - } - popup_menu(Mwin,popmenu); - } - - if (button == 3) { - if (KBkey == GDK_KEY_a) redo_all(); // redo all steps - else m_redo(0,0); // redo one step - } - - return; -} - - -// popup menu response function - -void undo_redo_choice(GtkWidget *, cchar *menu) -{ - int nn = *((int *) menu); - if (nn < 0 || nn > URS_max) return; - URS_pos = nn; - load_undo(); - return; -} - - -// [undo] menu function - reinstate previous edit in undo/redo stack - -void m_undo(GtkWidget *, cchar *) -{ - if (CEF) { // undo active edit - edit_undo(); - return; - } - if (URS_pos == 0) return; // undo past edit - URS_pos--; - load_undo(); - return; -} - - -// [redo] menu function - reinstate next edit in undo/redo stack - -void m_redo(GtkWidget *, cchar *) -{ - if (CEF) { // redo active edit - edit_redo(); - return; - } - if (URS_pos == URS_max) return; // redo past edit - URS_pos++; - load_undo(); - return; -} - - -// undo all edits of the current image -// (discard modifications) - -void undo_all() -{ - if (CEF) return; // not if edit active - if (URS_pos == 0) return; - URS_pos = 0; // original image - load_undo(); - return; -} - - -// redo all edits of the current image -// (reinstate all modifications) - -void redo_all() -{ - if (CEF) return; // not if edit active - if (URS_pos == URS_max) return; - URS_pos = URS_max;; // image with last edit applied - load_undo(); - return; -} - - -// Save E0 to undo/redo file stack -// stack position = URS_pos - -void save_undo() // overhauled for 4 GB files -{ - char *pp, buff[24]; - FILE *fid; - int ww, hh, nc, nn; - uint cc1, cc2; - uint ccmax = 256 * MEGA; - - ww = E0pxm->ww; - hh = E0pxm->hh; - nc = E0pxm->nc; - - pp = strstr(URS_filename,"undo_"); // get undo/redo stack filename to use - if (! pp) zappcrash("undo/redo stack corrupted"); - snprintf(pp+5,3,"%02d",URS_pos); - - fid = fopen(URS_filename,"w"); - if (! fid) goto writefail; - - snprintf(buff,24,"fotoxx %05d %05d %d",ww,hh,nc); // write header - nn = fwrite(buff,20,1,fid); - if (nn != 1) goto writefail; - - cc1 = ww * hh * nc * sizeof(float); // write ww * hh RGB(A) pixels - cc2 = 0; - while (cc1) { - if (cc1 <= ccmax) { - pp = (char *) E0pxm->pixels; - nn = fwrite(pp+cc2,cc1,1,fid); - if (nn != 1) goto writefail; - break; - } - else { - pp = (char *) E0pxm->pixels; - nn = fwrite(pp+cc2,ccmax,1,fid); - if (nn != 1) goto writefail; - cc1 -= ccmax; - cc2 += ccmax; - } - } - - fclose(fid); - - if (URS_pos == 0) { // stack posn. 0 = original image - strcpy(URS_funcs[0],"original"); // function name for original image - URS_saved[0] = 1; // original image already on disk - } - else { // stack position - if (CEF_invalid()) return; // must have an edit function - strncpy0(URS_funcs[URS_pos],CEF->funcname,32); // edit function name - URS_saved[URS_pos] = 0; // not yet saved to disk - } - - return; - -writefail: - zmessageACK(Mwin,"undo/redo stack write failure: %d \n" - "(may be out of disk space)",errno); - exit(1); -} - - -// Load E0 from undo/redo file stack -// stack position = URS_pos - -void load_undo() // overhauled -{ - char *pp, buff[24]; - FILE *fid; - int ww, hh, nc, nn; - uint cc1, cc2; - uint ccmax = 128 * MEGA; // reduced from 256 17.04 - - pp = strstr(URS_filename,"undo_"); - if (! pp) goto err1; - snprintf(pp+5,3,"%02d",URS_pos); - - fid = fopen(URS_filename,"r"); - if (! fid) goto err2; - - nn = fread(buff,20,1,fid); - if (nn != 1) goto err3; - nn = sscanf(buff,"fotoxx %d %d %d",&ww,&hh,&nc); - if (nn != 3) goto err4; - - PXM_free(E0pxm); - E0pxm = PXM_make(ww,hh,nc); - - cc1 = ww * hh * nc * sizeof(float); - cc2 = 0; - while (cc1) { - if (cc1 <= ccmax) { - pp = (char *) E0pxm->pixels; - nn = fread(pp+cc2,cc1,1,fid); - if (nn != 1) goto err3; - break; - } - else { - pp = (char *) E0pxm->pixels; - nn = fread(pp+cc2,ccmax,1,fid); - if (nn == 0 && errno == 11) { - zmessage_post(Mwin,3,"fread() fail, resource unavailable",0); // this shit does happen 17.04 - exit(11); // there is no recovery - } - if (nn != 1) goto err3; - cc1 -= ccmax; - cc2 += ccmax; - } - } - - fclose(fid); - - sa_validate(); // delete area if invalid - if (Cdrawin) Fpaintnow(); // update image synchronous - return; - -err1: - printz("err1: %s \n",URS_filename); // extended diagnostics 17.01 - goto readfail; - -err2: - printz("err2: open failure, errno: %d \n",errno); - goto readfail; - -err3: - printz("err3: fread() failure, errno: %d \n",errno); - goto readfail; - -err4: - printz("err4: %s \n",buff); - goto readfail; - -readfail: - zmessageACK(Mwin,"undo/redo stack read failure \n"); - exit(1); -} - - -/********************************************************************************/ - -// check for busy or pending conditions and message the user -// returns 1 if busy or pending condition -// 0 if nothing is pending or user decides to discard mods -// conditions: -// edit edit function is active (CEF not null) -// mods current file has unsaved mods (image or metadata edits) -// busy function is still running -// block edit function mutex flag -// all check all the above -// quiet suppress user message - -int checkpend(cchar *list) -{ - int edit, mods, busy, block, all, quiet, pend, choice; - cchar *modmess = ZTX("This action will discard changes to current image"); - cchar *activemess = ZTX("prior function still active"); - cchar *keep = ZTX("Keep"); - cchar *discard = ZTX("Discard"); - - edit = mods = busy = block = all = quiet = pend = 0; - - if (strstr(list,"edit")) edit = 1; - if (strstr(list,"mods")) mods = 1; - if (strstr(list,"busy")) busy = 1; - if (strstr(list,"block")) block = 1; - if (strstr(list,"all")) all = 1; - if (strstr(list,"quiet")) quiet = 1; - - if (all) edit = mods = busy = block = 1; - - if ((edit && CEF) || (busy && (Ffuncbusy | Fthreadbusy)) || (block && Fblock)) { - pend = 1; - if (! quiet) zmessage_post_bold(Mwin,2,activemess); - } - - if (! pend && mods) { - if (CEF && CEF->Fmods && ! CEF->Fsaved) pend = 1; // active edits unsaved - if (URS_pos > 0 && URS_saved[URS_pos] == 0) pend = 1; // completed edits unsaved - if (Fmetamod) pend = 1; // metadata edits unsaved - if (pend && ! quiet) { - choice = zdialog_choose(Mwin,modmess,keep,discard,null); // ask user - if (choice == 2) { // choice is discard - if (CEF && CEF->zd) - zdialog_send_event(CEF->zd,"cancel"); - if (URS_pos > 0) undo_all(); // undo prior edits - Fmetamod = 0; // discard metadata edits - pend = 0; - } - else m_viewmode(0,"F"); // keep - back to current image - } - } - - return pend; // 1 if something pending -} - - -/********************************************************************************/ - -// zdialog mouse capture and release - -void takeMouse(mcbFunc func, GdkCursor *cursor) // capture mouse for dialog -{ - if (! Cdrawin) return; - if (! gdkwin) return; - freeMouse(); - mouseCBfunc = func; - Mcapture = 1; - gdk_window_set_cursor(gdkwin,cursor); - return; -} - -void freeMouse() // free mouse for main window -{ - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Mcapture) return; - mouseCBfunc = 0; - Mcapture = 0; - gdk_window_set_cursor(gdkwin,0); // set normal cursor - return; -} - - -/******************************************************************************** - - functions to manage working threads - - main level thread management - start_thread(func,arg) start thread running - signal_thread() signal thread that work is pending - wait_thread_idle() wait for pending work complete - wrapup_thread(command) wait for exit or command thread exit - - thread function - thread_idle_loop() wait for pending work, exit if commanded - thread_exit() exit thread unconditionally - - thread_status (thread ownership - 0 no thread is running - 1 thread is running and idle (no work) - 2 thread is working - 0 thread has exited - - thread_command (main program ownership) - 0 idle, initial status - 8 exit when pending work is done - 9 exit now, unconditionally - - thread_pend work requested counter - thread_done work done counter - thread_hiwater high water mark - -*********************************************************************************/ - -// start thread that does the edit work - -void start_thread(threadfunc func, void *arg) -{ - CEF->thread_status = 1; // thread is running - CEF->thread_command = CEF->thread_pend = CEF->thread_done - = CEF->thread_hiwater = 0; // nothing pending - start_detached_thread(func,arg); - return; -} - - -// signal thread that new work is pending - -void signal_thread() -{ - if (CEF_invalid()) return; - if (CEF->thread_status > 0) CEF->thread_pend++; - return; -} - - -// wait for edit thread to complete pending work and become idle - -void wait_thread_idle() -{ - if (CEF_invalid()) return; - if (! CEF->thread_status) return; - if (CEF->thread_pend == CEF->thread_done) return; - while (CEF->thread_status && CEF->thread_pend > CEF->thread_done) { - zmainloop(); - zsleep(0.01); - if (CEF_invalid()) return; - } - return; -} - - -// wait for thread exit or command thread exit -// command = 0 wait for normal completion -// 8 finish pending work and exit -// 9 quit, exit now - -void wrapup_thread(int command) -{ - if (CEF_invalid()) return; - if (! CEF->thread_status) return; - CEF->thread_command = command; // tell thread to quit or finish - while (CEF->thread_status > 0) { // wait for thread to finish - zmainloop(); - zsleep(0.01); - if (CEF_invalid()) return; - } - return; -} - - -// called only from edit threads -// idle loop - wait for work request or exit command - -void thread_idle_loop() -{ - if (CEF_invalid()) thread_exit(); - if (CEF->thread_status == 2) zadd_locked(Fthreadbusy,-1); - CEF->thread_status = 1; // status = idle - CEF->thread_done = CEF->thread_hiwater; // work done = high-water mark - - while (true) - { - if (CEF->thread_command == 9) thread_exit(); // quit now command - if (CEF->thread_command == 8) // finish work and exit - if (CEF->thread_pend <= CEF->thread_done) thread_exit(); - if (CEF->thread_pend > CEF->thread_done) break; // wait for work request - zsleep(0.01); - } - - CEF->thread_hiwater = CEF->thread_pend; // set high-water mark - CEF->thread_status = 2; // thread is working - zadd_locked(Fthreadbusy,+1); - return; // perform edit -} - - -// exit thread unconditionally, called only from edit threads - -void thread_exit() -{ - wait_wthreads(); // wait for worker threads if any - if (CEF_invalid()) pthread_exit(0); - if (CEF->thread_status == 2) zadd_locked(Fthreadbusy,-1); - CEF->thread_pend = CEF->thread_done = CEF->thread_hiwater = 0; - CEF->thread_status = 0; - pthread_exit(0); // "return" cannot be used here -} - - -/********************************************************************************/ - -// edit support functions for worker threads (per processor core) - -void start_wthread(threadfunc func, void *arg) // start thread and increment busy count -{ // may be called from a thread - zadd_locked(Fthreadbusy,+1); - zadd_locked(wthreads_busy,+1); - start_detached_thread(func,arg); - return; -} - - -void exit_wthread() // decrement busy count and exit thread -{ - zadd_locked(Fthreadbusy,-1); - zadd_locked(wthreads_busy,-1); - pthread_exit(0); // "return" cannot be used here -} - - -void wait_wthreads(int block) // wait for all worker threads done -{ // may be called from thread or non-thread - if (CEF) { - while (wthreads_busy) { - zsleep(0.001); - if (block) continue; - zmainloop(); - } - if (CEF_invalid()) return; - } - else { - while (wthreads_busy) { - zsleep(0.0003); - if (block) continue; - zmainloop(); - } - } - return; -} - - -/********************************************************************************/ - -// table for loading and saving adjustable parameters between sessions - -typedef struct { - char name[20]; - char type[12]; - int count; - void *location; -} param; - -#define Nparms 49 -param paramTab[Nparms] = { -// name type count location -{ "fotoxx release", "char", 1, &Prelease }, -{ "first time", "int", 1, &Ffirsttime }, -{ "session count", "int", 1, &sessioncount }, -{ "window geometry", "int", 4, &mwgeom }, -{ "thumbnail size", "int", 1, &navi::thumbsize }, -{ "menu style", "char", 1, &menu_style }, -{ "F-base-color", "int", 3, &FBrgb }, -{ "G-base-color", "int", 3, &GBrgb }, -{ "menu font color", "int", 3, &MFrgb }, -{ "menu background", "int", 3, &MBrgb }, -{ "index level", "int", 1, &Findexlev }, -{ "FM index level", "int", 1, &FMindexlev }, -{ "icon size", "int", 1, &iconsize }, -{ "dialog font", "char", 1, &dialog_font }, -{ "drag option", "int", 1, &Fdragopt }, -{ "zoom count", "int", 1, &zoomcount }, -{ "map_dotsize", "int", 1, &map_dotsize }, -{ "last version", "int", 1, &Flastversion }, -{ "shift right", "int", 1, &Fshiftright }, -{ "xmeta changed", "int", 1, &xmeta_changed }, -{ "curve node dist %", "float", 1, &splcurve_minx }, -{ "start display", "char", 1, &startdisplay }, -{ "start album", "char", 1, &startalbum }, -{ "start image file", "char", 1, &startfile }, -{ "start directory", "char", 1, &startdirk }, -{ "last curr file", "char", 1, &last_curr_file }, -{ "last galleryname", "char", 1, &last_galleryname }, -{ "last gallerytype", "int", 1, &last_gallerytype }, -{ "copy/move location", "char", 1, ©move_loc }, -{ "color palette file", "char", 1, &color_palette_file }, -{ "netmap source", "char", 1, &netmap_source }, -{ "mapbox access key", "char", 1, &mapbox_access_key }, -{ "grid base", "int", 10, &gridsettings[0] }, -{ "grid trim/rotate", "int", 10, &gridsettings[1] }, -{ "grid unbend", "int", 10, &gridsettings[2] }, -{ "grid warp curved", "int", 10, &gridsettings[3] }, -{ "grid warp linear", "int", 10, &gridsettings[4] }, -{ "grid warp affine", "int", 10, &gridsettings[5] }, -{ "RAW file types", "char", 1, &RAWfiletypes }, -{ "video file types", "char", 1, &VIDEOfiletypes }, // bugfix 17.08.3 -{ "trim width", "int", 1, &trimww }, -{ "trim height", "int", 1, &trimhh }, -{ "trim buttons", "char", 6, &trimbuttons }, -{ "trim ratios", "char", 6, &trimratios }, -{ "edit resize", "int", 2, &editresize }, -{ "jpeg def quality", "int", 1, &jpeg_def_quality }, -{ "lens mm", "float", 1, &lens_mm }, -{ "SS KB keys", "char", 1, &ss_KBkeys }, // 18.01 -{ "printer color map", "char", 1, &colormapfile } }; - - -// save parameters to file /.../.fotoxx/parameters - -void save_params() -{ - FILE *fid; - char buff[1050], text[1000]; // limit for character data cc - char *name, *type; - int count; - void *location; - char **charloc; - int *intloc; - float *floatloc; - - last_curr_file = last_galleryname = 0; // save curr_file and gallery poop - last_gallerytype = TNONE; // for next session startup - if (curr_file && *curr_file == '/') - last_curr_file = zstrdup(curr_file); - if (navi::galleryname) { - last_galleryname = zstrdup(navi::galleryname); - last_gallerytype = navi::gallerytype; - } - - snprintf(buff,199,"%s/parameters",get_zhomedir()); // open output file - fid = fopen(buff,"w"); - if (! fid) return; - - for (int ii = 0; ii < Nparms; ii++) // write table of state data - { - name = paramTab[ii].name; - type = paramTab[ii].type; - count = paramTab[ii].count; - location = paramTab[ii].location; - charloc = (char **) location; - intloc = (int *) location; - floatloc = (float *) location; - - fprintf(fid,"%-20s %-8s %02d ",name,type,count); // write parm name, type, count - - for (int kk = 0; kk < count; kk++) // write "value" "value" ... - { - if (strmatch(type,"char")) { - if (! *charloc) break; // missing, discontinue this parameter - repl_1str(*charloc++,text,"\n","\\n"); // replace newlines with "\n" - fprintf(fid," \"%s\"",text); - } - if (strmatch(type,"int")) - fprintf(fid," \"%d\"",*intloc++); - - if (strmatch(type,"float")) - fprintf(fid," \"%.2f\"",*floatloc++); - } - - fprintf(fid,"\n"); // write EOR - } - - fprintf(fid,"\n"); - fclose(fid); // close file - - return; -} - - -// load parameters from file /.../.fotoxx/parameters - -void load_params() -{ - FILE *fid; - int ii, err, pcount; - int Idata; - float Fdata; - char buff[1000], text[1000], *pp; - char name[20], type[12], count[8], data[1000]; - void *location; - char **charloc; - int *intloc; - float *floatloc; - - snprintf(buff,199,"%s/parameters",get_zhomedir()); // open parameters file - fid = fopen(buff,"r"); - if (! fid) return; // none, defaults are used - - while (true) // read parameters - { - pp = fgets_trim(buff,999,fid,1); - if (! pp) break; - if (*pp == '#') continue; // comment - if (strlen(pp) < 40) continue; // rubbish - - err = 0; - - strncpy0(name,pp,20); // parm name - strTrim2(name); - - strncpy0(type,pp+22,8); // parm type - strTrim2(type); - - strncpy0(count,pp+32,4); // parm count - strTrim2(count); - err = convSI(count,pcount); - - strncpy0(data,pp+38,1000); // parm value(s) - strTrim2(data); - - for (ii = 0; ii < Nparms; ii++) // match file record to param table - { - if (! strmatch(name,paramTab[ii].name)) continue; // parm name - if (! strmatch(type,paramTab[ii].type)) continue; // parm type - if (paramTab[ii].count != pcount) continue; // parm count - break; - } - - if (ii == Nparms) continue; // not found, ignore file param - - location = paramTab[ii].location; // get parameter memory location - charloc = (char **) location; - intloc = (int *) location; - floatloc = (float *) location; - - for (ii = 1; ii <= pcount; ii++) // get parameter value(s) - { - pp = (char *) strField(data,' ',ii); - if (! pp) break; - - if (strmatch(type,"char")) { - repl_1str(pp,text,"\\n","\n"); // replace "\n" with real newlines - *charloc++ = zstrdup(text); - } - - if (strmatch(type,"int")) { - err = convSI(pp,Idata); - if (err) continue; - *intloc++ = Idata; - } - - if (strmatch(type,"float")) { - err = convSF(pp,Fdata); - if (err) continue; - *floatloc++ = Fdata; - } - } - } - - fclose(fid); - - for (ii = 0; ii < Nparms; ii++) // set any null strings to "" - { - if (! strmatch(paramTab[ii].type,"char")) continue; - charloc = (char **) paramTab[ii].location; - pcount = paramTab[ii].count; - for (int jj = 0; jj < pcount; jj++) - if (! charloc[jj]) - charloc[jj] = zstrdup("",20); - } - - zoomratio = pow( 2.0, 1.0 / zoomcount); // set zoom ratio from zoom count - - return; -} - - -/********************************************************************************/ - -// free all resources associated with the current image file - -void free_resources(int fkeepundo) -{ - paintlock(1); // block window updates - - if (! fkeepundo) - shell_quiet("rm -f %s/undo_*",tempdir); // remove undo/redo files - - if (Fshutdown) return; // stop here if shutdown mode - - URS_pos = URS_max = 0; // reset undo/redo stack - Fmetamod = 0; // no unsaved metadata changes - sa_unselect(); // unselect select area - - Nptoplines = Ntoplines = 0; // no toplines - Ntopcircles = 0; // no topcircles - Ntoptext = 0; // no toptext - Fbusy_goal = 0; // not busy - - if (curr_file) { - if (zdsela) zdialog_free(zdsela); // kill select area dialog if active - freeMouse(); // free mouse - zfree(curr_file); // free image file - curr_file = 0; - *paneltext = 0; - } - - gtk_window_set_title(MWIN,Arelease); // sparse title - - PXB_free(Fpxb); - PXM_free(E0pxm); - PXM_free(E1pxm); - PXM_free(E3pxm); - PXM_free(ERpxm); - PXM_free(E8pxm); - PXM_free(E9pxm); - - paintlock(0); // unblock window updates - return; -} - - -/********************************************************************************/ - -// compare two floats for significant difference -// signf: e.g. 0.01 for 1% threshold of significance -// return: 0 difference not significant -// +1 d1 > d2 -// -1 d1 < d2 - -int sigdiff(float d1, float d2, float signf) -{ - float diff = fabsf(d1-d2); - if (diff == 0.0) return 0; - diff = diff / (fabsf(d1) + fabsf(d2)); - if (diff < signf) return 0; - if (d1 > d2) return 1; - else return -1; -} - - - diff -Nru fotoxx-18.01/fotoxx.h fotoxx-18.01.1/fotoxx.h --- fotoxx-18.01/fotoxx.h 2017-12-30 20:12:21.000000000 +0000 +++ fotoxx-18.01.1/fotoxx.h 2018-01-02 20:42:33.000000000 +0000 @@ -31,7 +31,7 @@ // Fotoxx definitions -#define Frelease "fotoxx-18.01" // Fotoxx release version +#define Frelease "fotoxx-18.01.1" // Fotoxx release version #define Flicense "Free software - GNU General Public License v.3" #define Fhomepage "https://kornelix.net" #define Fcontact "kornelix@posteo.de" diff -Nru fotoxx-18.01/Makefile fotoxx-18.01.1/Makefile --- fotoxx-18.01/Makefile 2017-12-30 20:12:21.000000000 +0000 +++ fotoxx-18.01.1/Makefile 2018-01-02 20:42:33.000000000 +0000 @@ -1,6 +1,6 @@ # fotoxx makefile -FOTOXX = fotoxx-18.01.cc +FOTOXX = fotoxx-18.01.1.cc # defaults for parameters that may be pre-defined CXXFLAGS += -Wall -g -rdynamic -fstack-protector-strong