diff -Nru fotoxx-16.10.1/debian/changelog fotoxx-16.10.3/debian/changelog --- fotoxx-16.10.1/debian/changelog 2016-10-12 05:43:22.000000000 +0000 +++ fotoxx-16.10.3/debian/changelog 2016-10-16 14:53:20.000000000 +0000 @@ -1,3 +1,9 @@ +fotoxx (16.10.3-1dhor~trusty) trusty; urgency=medium + + * 16.10.3 - few fixes + + -- Dariusz Duma Sun, 16 Oct 2016 16:51:30 +0200 + fotoxx (16.10.1-1dhor~trusty) trusty; urgency=medium * Bugfix: memory management bug resulting in possible crash. diff -Nru fotoxx-16.10.1/debian-control fotoxx-16.10.3/debian-control --- fotoxx-16.10.1/debian-control 2016-10-08 19:16:36.000000000 +0000 +++ fotoxx-16.10.3/debian-control 2016-10-14 09:02:02.000000000 +0000 @@ -1,5 +1,5 @@ Package: fotoxx -Version: 16.10.1 +Version: 16.10.3 Architecture: amd64 Section: graphics Installed-Size: 8948 diff -Nru fotoxx-16.10.1/doc/changelog fotoxx-16.10.3/doc/changelog --- fotoxx-16.10.1/doc/changelog 2016-10-08 19:16:36.000000000 +0000 +++ fotoxx-16.10.3/doc/changelog 2016-10-14 09:02:02.000000000 +0000 @@ -1,6 +1,14 @@ Fotoxx Change Log http://www.kornelix.net ================= +2016 Oct 14 v.16.10.3 +---------------------- + + Bugfix: occaisional crash when saving to new file name or new version. + +2016 Oct 12 v.16.10.2 +---------------------- + + Improved traceback dump to help bug hunting. + 2016 Oct 08 v.16.10.1 ---------------------- + Bugfix: memory management bug resulting in possible crash. diff -Nru fotoxx-16.10.1/f.meta.cc fotoxx-16.10.3/f.meta.cc --- fotoxx-16.10.1/f.meta.cc 2016-10-08 19:16:36.000000000 +0000 +++ fotoxx-16.10.3/f.meta.cc 2016-10-14 09:02:02.000000000 +0000 @@ -7878,10 +7878,9 @@ int put_xxrec(xxrec_t *xxrec, cchar *file) // 16.09 { char indexfile[200]; - int ii, iix, nn, err, cc; + int ii, iix, nn, err; int Finsert, Fdelete, Freplace, Fdone; xxrec_t *xxrec_old, *xxrec_new = 0; - xxrec_t **xxrec_tab_new; FILE *fid; STATB statb; @@ -7897,7 +7896,7 @@ err = stat(file,&statb); // check image file exists if (err || ! S_ISREG(statb.st_mode)) { zmessageACK(Mwin,"file %s not found \n",file); - return 1; + return 0; } } @@ -7909,7 +7908,7 @@ if (! xxrec && xxrec_old) Fdelete = 1; // delete old record if (xxrec && ! xxrec_old) Finsert = 1; // insert new record if (xxrec && xxrec_old) Freplace = 1; // replace old record - + if (Finsert || Freplace) { xxrec_new = (xxrec_t *) zmalloc(sizeof(xxrec_t)); // make new xxrec with data from caller @@ -7961,42 +7960,37 @@ } if (iix < 0 && (Fdelete || Freplace)) goto badbug; // not found - if (iix < 0) iix = Nxxrec; // add to end of table - if (Finsert) { - cc = (Nxxrec + 1) * sizeof(xxrec_t *); // copy to new table, leaving a hole - xxrec_tab_new = (xxrec_t **) zmalloc(cc); - for (ii = 0; ii < iix; ii++) - xxrec_tab_new[ii] = xxrec_tab[ii]; - for (ii = iix; ii < Nxxrec; ii++) - xxrec_tab_new[ii+1] = xxrec_tab[ii]; - zfree(xxrec_tab); - xxrec_tab = xxrec_tab_new; - Nxxrec++; + if (Nxxrec == maximages) { + zmessageACK(Mwin,"exceed %d max files, cannot continue",maximages); + quitxx(); + } + + if (Finsert) // 16.10.3 + { + for (ii = Nxxrec; ii > iix; ii--) // make empty slo1 + xxrec_tab[ii] = xxrec_tab[ii-1]; xxrec_tab[iix] = xxrec_new; // insert new entry + Nxxrec++; } - if (Fdelete) { + if (Fdelete) + { zfree(xxrec_tab[iix]->file); // delete old entry zfree(xxrec_tab[iix]->tags); zfree(xxrec_tab[iix]->capt); zfree(xxrec_tab[iix]->comms); zfree(xxrec_tab[iix]->gtags); zfree(xxrec_tab[iix]); - cc = (Nxxrec - 1) * sizeof(xxrec_t *); // copy to new table, omitting one - xxrec_tab_new = (xxrec_t **) zmalloc(cc); - for (ii = 0; ii < iix; ii++) - xxrec_tab_new[ii] = xxrec_tab[ii]; - for (ii = iix + 1; ii < Nxxrec; ii++) - xxrec_tab_new[ii-1] = xxrec_tab[ii]; - zfree(xxrec_tab); - xxrec_tab = xxrec_tab_new; Nxxrec--; + for (ii = iix; ii < Nxxrec; ii++) // pack down + xxrec_tab[ii] = xxrec_tab[ii+1]; } - if (Freplace) { - zfree(xxrec_tab[iix]->file); // delete old entry + if (Freplace) + { + zfree(xxrec_tab[iix]->file); // replace old entry zfree(xxrec_tab[iix]->tags); zfree(xxrec_tab[iix]->capt); zfree(xxrec_tab[iix]->comms); diff -Nru fotoxx-16.10.1/fotoxx-16.10.1.cc fotoxx-16.10.3/fotoxx-16.10.1.cc --- fotoxx-16.10.1/fotoxx-16.10.1.cc 2016-10-08 19:16:36.000000000 +0000 +++ fotoxx-16.10.3/fotoxx-16.10.1.cc 1970-01-01 00:00:00.000000000 +0000 @@ -1,4539 +0,0 @@ -/******************************************************************************** - - Fotoxx edit photos and manage collections - - Copyright 2007-2016 Michael Cornelison - Source URL: http://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. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see http://www.gnu.org/licenses/. - -********************************************************************************* - - 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 - - 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) - - 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_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 = 0; - int Fclone=0, cloxx=0, cloyy=0, cloww=0, clohh=0; - int ii, jj, err; - STATB statb; - - if (argc > 1 && strmatchV(argv[1],"-ver","-v",0)) { - printz(Frelease "\n"); // print version and exit - exit(0); - } - - if (gtk_clutter_init(&argc,&argv) != CLUTTER_INIT_SUCCESS) return 1; // init. gtk and clutter (OSM) 16.05 - - if (argc > 2 && strmatch(argv[1],"-home")) homedir = argv[2]; // relocate user directory 16.09 - - zinitapp("fotoxx",homedir); // initz. app directories - err = chdir(get_zhomedir()); // set to /home//.fotoxx - if (err) { - printz("user directory not found: %s \n",homedir); // 16.09 - exit(1); - } - - // initialize externals to default values (saved parameters will override) - - strcpy(zfuncs::zappname,Frelease); // app name and version - Ffirsttime = 1; // first time startup (params override) - Findexlev = 2; // full image index processing 16.09 - FMindexlev = 2; // " also if start via file manager 16.09 - Pindexlev = -1; // no -index command parameter 16.09 - xxrec_tab = 0; // no image index yet 16.09 - Nxxrec = Findexvalid = 0; // 16.09 - 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 - trimsize[0] = 1600; // default initial image trim size - trimsize[1] = 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) - Fbgcolor[0] = Fbgcolor[1] = Fbgcolor[2] = 50; // F view background color 16.07 - Gbgcolor[0] = Gbgcolor[1] = Gbgcolor[2] = 200; // G view background color 16.07 - dialog_font = zstrdup("Sans 11"); // default dialog font 16.05 - iconsize = 32; // default icon size 16.07 - splcurve_minx = 5; // default curve node separation % 15.10 - startdisplay = zstrdup("prevI"); // start with previous image - Fdragopt = 1; // image drag with mouse 15.02 - zoomcount = 2; // zooms to reach 2x image size - zoomratio = sqrtf(2); // corresp. zoom ratio - map_dotsize = 8; // map dot size, mouse capture dist 16.05 - curr_file = curr_dirk = 0; // no curr. file or directory 15.11 - copymove_loc = 0; // copy/move target directory 16.07 - thumbdirk = 0; // no thumbnail directory - thumbfilesize = 256; // thumbnail files, default pixels 15.02 - navi::thumbsize = 128; // gallery window thumbnail size 16.07 - startmenu = 0; // start with menu function - startalbum = 0; // start with 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 - OSM_map_source = zstrdup("mapnik"); // default OSM map source 16.09 - mapbox_access_key = zstrdup("undefined"); // mapbox map source access key 16.09 - - colormapfile = zstrdup("undefined"); // printer calibration color map 15.10 - 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 "); - - 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++) Nval[ii] = ii; // static integer values 0-99 - - // 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(filecachefile,199,"%s/albums/file_cache",get_zhomedir()); // albums, file cache file 16.07 - snprintf(saved_curves_dirk,199,"%s/saved_curves",get_zhomedir()); // saved curves directory - snprintf(writetext_dirk,199,"%s/write_text",get_zhomedir()); // write_text 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 15.10 - snprintf(edit_scripts_dirk,199,"%s/edit_scripts",get_zhomedir()); // edit script files directory 15.10 - 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 15.12 - - 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(writetext_dirk,&statb); - if (err) mkdir(writetext_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); - - 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 16.09 - 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 new added 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 16.09 - 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 start with menu func - startmenu = zstrdup(argv[++ii]); - else if (strmatchV(pp,"-album","-a",0) && argc > ii+1) // -a -album start with album 16.02 - startalbum = zstrdup(argv[++ii]); - else if (strmatch(pp,"-cycledesktop") && argc > ii+2) { // -cycledesktop album seconds 15.06 - run_cycledesktop(argv[ii+1],argv[ii+2]); // run in background without GUI - gtk_main(); - return 0; - } - else initial_file = zstrdup(pp); // must be initial file or directory - } - - ZTXinit(lang); // setup locale and translations - setlocale(LC_NUMERIC,"en_US.UTF-8"); // stop comma decimal points - - build_widgets(); // build window widgets and menus - - 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 - - if (! zfuncs::screen || ! zfuncs::mouse) { - zpopup_message(0,"GTK/GDK failure to find hardware"); // 15.09 - m_quit(0,0); - exit(1); - } - - m_viewmode(0,"F"); // set F mode initially - - 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 - } - - 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; - int Fdirk = 0; - FTYPE ftype; - char *pp, *pp2; - char procfile[20], buff[200]; - char KBshortsU[200], KBshortsI[200]; - char tonefile[200], badnews[200]; - char metalistfile[200], albumfile[200]; - char favoritesfile[200], images_dir[200]; - char indexfile[200], oldindexfile[200]; - double freememory, cachememory; - float exifver = 0; - FILE *fid; - STATB statb; - - printz(Frelease "\n"); // 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"); // check for xdg-open - if (! err) Fxdgopen = 1; - err = shell_quiet("which rawtherapee"); // check for Raw Therapee - if (! err) Frawtherapee = 1; - err = shell_quiet("which growisofs"); // check for growisofs 16.02 - if (! err) Fgrowisofs = 1; - - err = shell_quiet("which hugin"); // check for pano tools (hugin versions) - if (! err) { - PTtools = 1; - err = shell_quiet("which hugin_executor"); // 2015 release - if (! err) PTtools = 2; - } - - if (Fexiftool + Fxdgopen < 2) { // check mandatory dependencies - strcpy(badnews,ZTX("Please install missing programs:")); - if (! Fexiftool) strcat(badnews,"\n exiftool 8.6 or later"); // refuse to start if any are missing - if (! Fxdgopen) strcat(badnews,"\n xdg-utils"); - zmessageACK(Mwin,badnews); - m_quit(0,0); - } - - if (! Frawtherapee) printz("Raw Therapee not installed \n"); - if (! Fgrowisofs) printz("growisofs 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 - - // delete fotoxx tempdir files if owner process is no longer running - - contx = 0; - while ((pp = command_output(contx,"find /tmp/fotoxx-*",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 - - // update KB shortcuts if needed - - err = locale_filespec("user","KB-shortcuts",KBshortsU); // user KB shortcuts - if (err) { // not found - locale_filespec("data","KB-shortcuts",KBshortsI); - shell_quiet("cp %s %s",KBshortsI,KBshortsU); // copy installed KB shortcuts - printz("keyboard shortcuts installed \n"); - } - KBshortcuts_load(); // load KB shortcuts from file - - // copy default data files to user directories if not already - - err = stat(pattern_dirk,&statb); // pattern files - if (err) { - printz("copy default pattern files \n"); - mkdir(pattern_dirk,0750); - shell_quiet("cp -n %s/patterns/* %s",get_zdatadir(),pattern_dirk); - } - - err = stat(retouch_combo_dirk,&statb); // retouch combo settings - if (err) { - printz("copy default retouch_combo files \n"); - mkdir(retouch_combo_dirk,0750); - shell_quiet("cp -n %s/retouch_combo/* %s",get_zdatadir(),retouch_combo_dirk); - } - - err = stat(custom_kernel_dirk,&statb); // custom convolution kernels - if (err) { - printz("copy default custom_kernel files \n"); - mkdir(custom_kernel_dirk,0750); - shell_quiet("cp -n %s/custom_kernel/* %s",get_zdatadir(),custom_kernel_dirk); - } - - snprintf(favoritesfile,200,"%s/menu-config",favorites_dirk); // favorites menu 16.08 - err = stat(favoritesfile,&statb); - if (err) { - printz("copy default favorites menu \n"); - mkdir(favorites_dirk,0750); - shell_quiet("cp -n %s/favorites/* %s",get_zdatadir(),favorites_dirk); - } - - err = stat(tags_defined_file,&statb); // tags_defined file - if (err) { - printz("create default tags_defined file \n"); - shell_quiet("cp %s/tags_defined %s",get_zdatadir(),tags_defined_file); - } - - snprintf(tonefile,200,"%s/slideshow-tone.oga",slideshow_dirk); // default slide show pause tone - err = stat(tonefile,&statb); - if (err) shell_quiet("cp %s/slideshow-tone.oga %s",get_zdatadir(),tonefile); - - snprintf(metalistfile,200,"%s/metadata_short_list",get_zhomedir()); // metadata keys short list 15.10 - err = stat(metalistfile,&statb); - if (err) shell_quiet("cp %s/metadata_short_list %s",get_zdatadir(),metalistfile); - - // remove some unneeded files copied from zdatadir to zhomedir by zinitapp() - - snprintf(images_dir,200,"%s/images",get_zhomedir()); // 16.09 - err = stat(images_dir,&statb); - if (! err) - shell_quiet("cd %s; rm -R images; rm -f edit-menus-* quickstart-* " - "userguide-* slideshow-tone.*",get_zhomedir()); - - // miscellaneous - - screen = gtk_window_get_screen(MWIN); // get monitor pixel dimensions - screenww = gdk_screen_get_width(screen); - screenhh = gdk_screen_get_height(screen); - printz("screen width: %d height: %d \n",screenww,screenhh); - - 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); - - zdialog_inputs("load"); // load saved dialog inputs - zdialog_positions("load"); // load saved dialog positions - - exif_server(0,0,0); // kill orphan exiftool process - - // if indexB file not present and old format index file is present, 16.09 - // convert old formal file to new format file - - snprintf(indexfile,200,"%s/indexB",index_dirk); // new format image index file - err = stat(indexfile,&statb); - if (err) { // not found - snprintf(oldindexfile,200,"%s/index_001",index_dirk); - err = stat(oldindexfile,&statb); // look for old format index file - if (! err) { - printz("convert to new image index (old one is not deleted) \n"); // if present, convert to new format - shell_quiet("cat %s/index_* > %s/indexB",index_dirk,index_dirk); - } - } - - // create or update image index file and memory table - - if (Pindexlev >= 0) index_rebuild(Pindexlev,0); // -index command parameter given 16.09 - else if (initial_file) index_rebuild(FMindexlev,0); // likely a file manager call - else index_rebuild(Findexlev,0); // normal index level - - // set current file and gallery from command line if present - - if (topdirks[0]) curr_dirk = zstrdup(topdirks[0]); // default 1st top image directory 15.11 - else curr_dirk = zstrdup(getenv("HOME")); - - if (startalbum) { // 16.02 - snprintf(albumfile,200,"%s/albums/%s",get_zhomedir(),startalbum); - err = stat(albumfile,&statb); - if (err) { - printz("invalid album file: %s \n",albumfile); - startalbum = 0; - } - } - - if (initial_file) { // from command line - pp = realpath(initial_file,0); // prepend directory if needed - if (pp) { - zfree(initial_file); - initial_file = zstrdup(pp); - free(pp); - } - else { - printz("invalid file: %s \n",initial_file); // invalid file path - zfree(initial_file); - initial_file = 0; - } - } - - if (initial_file) { - ftype = image_file_type(initial_file); - if (ftype == FNF) { // non-existent file - printz("invalid file: %s \n",initial_file); - zfree(initial_file); - initial_file = 0; - } - else if (ftype == FDIR) { // is a directory - if (curr_dirk) zfree(curr_dirk); - curr_dirk = initial_file; - initial_file = 0; - Fdirk = 1; - } - else { - if (curr_dirk) zfree(curr_dirk); - curr_dirk = zstrdup(initial_file); // set current directory from file - pp = strrchr(curr_dirk,'/'); - if (pp) *pp = 0; - } - } - - if (startalbum) { // open initial album gallery 16.02 - navi::gallerytype = ALBUM; - gallery(albumfile,"initF"); - m_viewmode(0,"G"); - } - - else if (initial_file) // open initial image file - f_open(initial_file); - - else if (Fdirk) { // open initial directory gallery - gallery(curr_dirk,"init"); - m_viewmode(0,"G"); - gallery(0,"paint",0); - } - - else if (Fprev) { // start with previous file - if (last_curr_file && *last_curr_file == '/') // 15.07 - 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 15.07 - curr_file = curr_dirk = 0; - navi::galleryname = 0; - navi::gallerytype = NONE; - set_mwin_title(); - } - - // if no command line option, get startup display from user options - - else if (strmatch(startdisplay,"recent")) // start with recent files gallery - m_recentfiles(0,0); - - else if (strmatch(startdisplay,"newest")) // start with newest files gallery 15.07 - m_newfiles(0,"file"); // by file mode date - - else if (strmatch(startdisplay,"prevG")) { // start with previous gallery 15.07 - if (last_gallerytype != NONE) { - navi::gallerytype = last_gallerytype; - if (last_gallerytype == GDIR) - gallery(last_galleryname,"init"); - else gallery(last_galleryname,"initF"); - m_viewmode(0,"G"); - gallery(0,"paint",0); - } - } - - else if (strmatch(startdisplay,"prevI")) { // start with previous image file - if (last_curr_file && *last_curr_file == '/') - f_open(last_curr_file); - } - - else if (strmatch(startdisplay,"dirk")) { // start with given directory - if (startdirk && *startdirk == '/') { - if (curr_dirk) zfree(curr_dirk); - curr_dirk = zstrdup(startdirk); - gallery(curr_dirk,"init",0); - m_viewmode(0,"G"); - gallery(0,"paint",0); - } - } - - else if (strmatch(startdisplay,"file")) // start with given image file - f_open(startfile); - - if (startmenu) { // startup menu on command line - printz("start menu: %s \n",startmenu); - for (ii = 0; ii < Nmenus; ii++) { // convert menu name to menu function - if (! menutab[ii].menu) continue; // separator, null menu 15.12 - if (strmatchcase(startmenu,ZTX(menutab[ii].menu))) break; - } - if (ii < Nmenus) menutab[ii].func(0,menutab[ii].arg); // call the menu function - } - - save_params(); // save parameters now 15.07 - - g_timeout_add(20,gtimefunc,0); // start periodic function (20 ms) 16.02 - - return 0; // don't come back -} - - -/********************************************************************************/ - -// functions for main window event signals - -int delete_event() // main window closed -{ - int yn; - - printz("main window delete event \n"); - if (checkpend("mods")) return 1; // allow user bailout - if (zfuncs::zdialog_busy) { - yn = zmessageYN(Mwin,ZTX("Kill active dialog?")); // allow user bailout - 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 Ffullscreen = 0; - return 0; -} - - -/********************************************************************************/ - -// Periodic function (20 milliseconds) - -int gtimefunc(void *) -{ - static int domore = 0; - - if (Fshutdown) return 0; // shutdown underway - - if (Fpaintlock && gdkwin) { // thread request to freeze 16.02 - gdk_window_freeze_updates(gdkwin); // window updates - zadd_locked(Fpaintlock,-1); - } - - if (Fpaintunlock && gdkwin) { // thread request to thaw 16.02 - gdk_window_thaw_updates(gdkwin); // window updates - zadd_locked(Fpaintunlock,-1); - } - - if (Fpaintrequest && Cdrawin) // 16.02 - 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 16.01 - zd_thread_event = 0; - } - } - - if (--domore > 0) return 1; // do rest every 200 milliseconds 15.05 - 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; - char text1[300], text2[100]; - 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; - - if (! Fpanelshow) return; // panel currently hidden 16.02 - - if (ftf) { - ftf = 0; - reduced = ZTX("(reduced)"); - areaactive = ZTX("area active"); - dialogopen = ZTX("dialog open"); - blocked = ZTX("blocked"); - modified = "mod"; - } - - if (Fslideshow) return; // 16.01 - 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; - } - - snprintf(text1,300,"CPU %03.0f%c",cpuload,'%'); // CPU 023% MB 2345 - - 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) { // 15.03 - 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 (! 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. // overhauled 16.01 -// Show progress bar if Fbusy_goal > 0. -// added to top panel: BUSY |||||||||||..........| - -update_busy: - - static GtkWidget *busylabel = 0, *donelabel = 0; - static cchar *busytext = " BUSY"; - static char donetext[] = "|::::::::::::::::::::|"; - GtkWidget *FGpanel; - int Nbars = 0, pbcc = 34; - - if (FGWM == 'F') FGpanel = Fpanel; // 16.01 - 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) { - Nbars = 20.0 * Fbusy_done / Fbusy_goal + 0.5; // N = 0-20 - if (Nbars > 20) Nbars = 20; - memset(donetext+pbcc,':',Nbars); // [ N bars + 20-N blanks ] - memset(donetext+pbcc+Nbars,' ',20-Nbars); - 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) // 16.02 -{ - 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 15.10 - Fpaintrequest = 0; - return 1; - } - - 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 15.09 - Fpaintrequest = 0; // window updated as of NOW 15.09 - 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 16.01 - - 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 * Fbgcolor[0]; // window background color 16.07 - rgba.green = 0.00392 * Fbgcolor[1]; // 0 - 255 --> 0.0 - 1.0 - rgba.blue = 0.00392 * Fbgcolor[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 16.01 - 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] = Fbgcolor[0]; // use background color 16.07 - bgpix[1] = Fbgcolor[1]; - bgpix[2] = Fbgcolor[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 16.01 - 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); - - mwcr = cr; // cairo context for draw funcs below - cairo_set_line_width(mwcr,1); - - if (Cstate == &Fstate) { // view mode is image - if (Ntoplines) draw_toplines(1); // draw line overlays - if (gridsettings[currgrid][GON]) draw_gridlines(); // draw grid lines - if (Ntoptext) draw_toptext(); // draw text strings - if (Ntopcircles) draw_topcircles(); // draw circles - if (Fshowarea) sa_show(1); // draw select area outline - if (refresh && zdbrdist) m_show_brdist(0,0); // update brightness dist. moved 15.12.1 - } - - if (Cstate == &Wstate) // view mode is world maps - geomap_paint_dots(); // 15.01 - - mwcr = 0; // cairo context invalid - return 1; -} - - -/********************************************************************************/ - -// Repaint modified image synchrounously. -// May NOT be called from threads. - -void Fpaintnow() -{ - if (! Cdrawin || ! gdkwin || ! Cstate || ! Cstate->fpxb) { // no image 15.10 - printf("Fpaintnow(), no image \n"); - return; - } - - Fpaintrequest = 1; // request repaint of changed image 15.09 - 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 15.09 - 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) -{ - 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); // update drawing window from Mpxb - return; -} - - -// Do the same using E0pxm instead of E3pxm - -void Fpaint0(int px3, int py3, int ww3, int hh3) // 16.07 -{ - PXM_PXB_update(E0pxm,Fpxb,px3,py3,ww3,hh3); - PXB_PXB_update(Fpxb,Mpxb,px3,py3,ww3,hh3); - Fpaint4(px3,py3,ww3,hh3); - 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) -{ - PIXBUF *pixbuf, *bgpixbuf; - int px1, py1, px2, py2; - int ww1, hh1, ww2, hh2; - int crflag = 0; - - zthreadcrash(); - - 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) - ww2 = Mscale * ww3 + 2 / Mscale + 4; - hh2 = Mscale * hh3 + 2 / Mscale + 4; - - if (px2 + ww2 > Mpxb->ww) ww2 = Mpxb->ww - px2; // avoid overshoot - if (py2 + hh2 > Mpxb->hh) hh2 = Mpxb->hh - py2; - - if (px2 < Morgx) { // reduce to currently visible window - ww2 = ww2 - (Morgx - px2); - px2 = Morgx; - } - - if (py2 < Morgy) { - hh2 = hh2 - (Morgy - py2); - py2 = Morgy; - } - - if (ww2 <= 0 || hh2 <= 0) return; // completely outside visible window - - paintlock(1); // 16.03 - - if (! mwcr) { - mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already - crflag = 1; - } - - pixbuf = gdk_pixbuf_new_subpixbuf(Mpxb->pixbuf,px2,py2,ww2,hh2); // Mpxb area to paint 15.08 - if (! pixbuf) { - zmessageACK(Mwin,"Fpaint4, pixbuf_new_subpixbuf() failed"); // 16.09 - return; - } - - px1 = px2 - Morgx; // corresp. position in background image - py1 = py2 - Morgy; - - px2 = px2 - Morgx + Dorgx; // corresp. position in drawing window - py2 = py2 - Morgy + Dorgy; - - if (Mpxb->nc > 3) { // alpha channel present 16.01 - ww1 = ww2; // draw background image to area - hh1 = hh2; - if (px1 + ww1 > dww) ww1 = dww - px1; - if (py1 + hh1 > dhh) hh1 = dhh - py1; - bgpixbuf = gdk_pixbuf_new_subpixbuf(BGpixbuf,px1,py1,ww1,hh1); - gdk_cairo_set_source_pixbuf(mwcr,bgpixbuf,px2,py2); - cairo_paint(mwcr); - g_object_unref(bgpixbuf); - } - - gdk_cairo_set_source_pixbuf(mwcr,pixbuf,px2,py2); // draw area to window - cairo_paint(mwcr); - - 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); // refresh select area outline - } - - if (crflag) { - cairo_destroy(mwcr); - mwcr = 0; - } - - paintlock(0); // 16.03 - 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; - static int bdtime = 0, butime = 0; - static int mdragx0, mdragy0; - static int busy = 0; - - 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; - mouse_convert(Mwxposn,Mwyposn,Mxposn,Myposn); // convert to image space - - scroll = ((GdkEventScroll *) event)->direction; // scroll wheel event - - 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 && 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; - } - - 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 - if (Mxposn == Mxdown && Myposn == Mydown) { // and not moving - 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 - if (! busy) { - busy = 1; // stop re-entrance 15.12 - (* mouseCBfunc)(); - busy = 0; - Fmousemain = 1; // click/drag params are processed here - } // unless reset by callback func. - } - - if (FGWM == 'W') geomap_mousefunc(); // geomap mouse function - - if (! Fmousemain) return; // curr. function handles mouse - - 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; - - zthreadcrash(); - - 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); } - -int KBpress(GtkWidget *win, GdkEventKey *event, void *) // keyboard key was pressed 16.09 -{ - int ii, jj, cc; - char matchkey[20]; - - 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; - - KBkey = event->keyval; - - if (! KBcontrolkey && ! KBshiftkey && ! KBaltkey) { // f/g/w/m keys set corresp. window mode - if (KBkey == GDK_KEY_f) { m_viewmode(0,"F"); goto KBend; } // Ctrl/Alt + f/g/w/m usable for shortcuts - if (KBkey == GDK_KEY_g) { m_viewmode(0,"G"); goto KBend; } - if (KBkey == GDK_KEY_w) { m_viewmode(0,"W"); goto KBend; } - if (KBkey == GDK_KEY_m) { m_viewmode(0,"M"); goto KBend; } // OSM 16.05 - } - - if (KBcapture) return 1; // let current function handle it - - if (KBkey == GDK_KEY_h && FGWM == 'G') { // H: toggle hidden files 16.09 - Fshowhidden = 1 - Fshowhidden; // in gallery view - gallery(0,"init"); - gallery(0,"paint"); - goto KBend; - } - - if (KBcontrolkey + KBshiftkey + KBaltkey) goto KBcustom; // other combinations are custom 15.06 - -// reserved shortcuts (not user configurable) - - if (KBkey == GDK_KEY_F1) { // F1 >> user guide - showz_userguide(F1_help_topic); // show topic if there, or page 1 - goto KBend; - } - - if (KBkey == GDK_KEY_F10) { // F10: fullscreen toggle with menu 16.02 - if (Fslideshow) { - ss_escape = 1; // quit slideshow if active - goto KBend; - } - if (! Ffullscreen) win_fullscreen(0); // toggle full-screen mode and back - else win_unfullscreen(); - goto KBend; - } - - if (KBkey == GDK_KEY_F11) { // F11: fullscreen toggle no menu 16.02 - if (Fslideshow) { - ss_escape = 1; // quit slideshow if active - goto KBend; - } - if (! Ffullscreen) win_fullscreen(1); // toggle full-screen mode and back - else win_unfullscreen(); - goto KBend; - } - - if (KBkey == GDK_KEY_Escape) { // ESC key - if (Fslideshow) ss_escape = 1; // quit slideshow if active 16.01 - else if (Ffullscreen) win_unfullscreen(); // exit full screen mode - goto KBend; - } - - if (KBkey == GDK_KEY_s) { // 's' shortcut for Sync Gallery 15.02 - navi::menufuncx(0,"Sync Gallery"); // 15.12 - goto KBend; - } - - if (FGWM == 'G') { // window mode = G - navi::KBpress(win,event,0); // pass to gallery KB function - goto KBend; // (keys below not for gallery) - } - - if (KBkey == GDK_KEY_z) { // 'z' key - if (! KBcontrolkey) m_zoom(0,"100"); // if no CTRL, toggle zoom 1x - goto KBend; - } - - if (FGWM == 'W' || FGWM == 'M') goto KBend; // map view mode, no other KB events - - if (KBkey == GDK_KEY_plus) m_zoom(0,"in"); // + key >> zoom in - if (KBkey == GDK_KEY_minus) m_zoom(0,"fit"); // - key >> fit to window - if (KBkey == GDK_KEY_equal) m_zoom(0,"in"); // = key: same as + - if (KBkey == GDK_KEY_KP_Add) m_zoom(0,"in"); // keypad + - if (KBkey == GDK_KEY_KP_Subtract) m_zoom(0,"fit"); // keypad - - - if (Fslideshow) { // slide show mode - if (KBkey == GDK_KEY_Left) ss_Larrow = 1; // arrow keys = prev/next image - if (KBkey == GDK_KEY_Right) ss_Rarrow = 1; - if (KBkey == GDK_KEY_space) ss_spacebar = 1; // spacebar = pause/resume - if (KBkey == GDK_KEY_b) ss_Bkey = 1; // B = blank screen + pause - if (KBkey == GDK_KEY_x) ss_Xkey = 1; // X = shortcut for Magnify - if (KBkey == GDK_KEY_n) ss_Nkey = 1; // N = do transition + next image 16.01 - goto KBend; - } - - if (Fmashup) { // mashup active, pass KB key - mashup::KBfunc(KBkey); - goto KBend; - } - - if (CEF && CEF->menufunc == m_trimrotate) { // trim_rotate active, pass KB key - trimrotate::KBfunc(KBkey); - goto KBend; - } - - if (CEF && CEF->menufunc == m_perspective) { // perspective active, pass KB key - perspective::KBfunc(KBkey); - goto KBend; - } - - if (KBkey == GDK_KEY_Left) m_prev(0,0); // arrow keys = prev/next image - if (KBkey == GDK_KEY_Right) m_next(0,0); - if (KBkey == GDK_KEY_Page_Up) m_prev(0,0); // page up/down = prev/next image - if (KBkey == GDK_KEY_Page_Down) m_next(0,0); - if (KBkey == GDK_KEY_Delete) m_delete_trash(0,0); // delete >> trash 16.06 - if (KBkey == GDK_KEY_x) m_magnify(0,0); // 'x' Magnify 16.05 - -// look for custom shortcut keys in shortcut table - -KBcustom: - - if (KBkey >= GDK_KEY_F1 && KBkey <= GDK_KEY_F9) { // input key is F1 to F9 - ii = KBkey - GDK_KEY_F1; - strcpy(matchkey,"F1"); - matchkey[1] += ii; - } - - else if (KBkey > 255) goto KBend; // not a single letter or digit - - else { - *matchkey = 0; // build input key combination - if (KBcontrolkey) strcat(matchkey,"Ctrl+"); // [Ctrl+] [Alt+] [Shift+] key - if (KBaltkey) strcat(matchkey,"Alt+"); - if (KBshiftkey) strcat(matchkey,"Shift+"); - cc = strlen(matchkey); - matchkey[cc] = KBkey; - matchkey[cc+1] = 0; - } - - for (ii = 0; ii < Nshortcuts; ii++) // convert key combination to menu name - if (strmatchcase(matchkey,shortcutkey[ii])) break; - - if (ii < Nshortcuts) { // convert menu name to menu function - for (jj = 0; jj < Nmenus; jj++) { - if (! menutab[jj].menu) continue; // ignore separator - if (strmatchcase(shortcutmenu[ii],ZTX(menutab[jj].menu))) break; - } - - if (jj < Nmenus) menutab[jj].func(0,menutab[jj].arg); // call the menu function - } - -KBend: - KBkey = 0; - return 1; -} - - -/********************************************************************************/ - -// set the main window to fullscreen status -// (with no menu or panel) - -void win_fullscreen(int hidemenu) -{ - zthreadcrash(); - - if (FGWM == 'F' && hidemenu) { // if F window, hide menu and panel - gtk_widget_hide(Fmenu); - gtk_widget_hide(Fpanel); - Fpanelshow = 0; // 16.02 - } - - gtk_window_fullscreen(MWIN); - while (! Ffullscreen) zmainloop(); // 15.09 - return; -} - - -// restore window to former size and restore menu etc. - -void win_unfullscreen() -{ - zthreadcrash(); - - gtk_window_unfullscreen(MWIN); // restore old window size - gtk_widget_show(Fmenu); - gtk_widget_show(Fpanel); - Fpanelshow = 1; // 16.02 - while (Ffullscreen) zmainloop(); // 16.01 - 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[20]; - char fname[100], gname[100], fdirk[100]; - - zthreadcrash(); - - if (! curr_file || *curr_file != '/') { - gtk_window_set_title(MWIN,Frelease); - 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 = gallery_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 15.03 - strncpy0(pdatetime,pdate,11); // yyyy-mm-dd hh:mm - strncpy0(pdatetime+11,ptime,6); - pdatetime[10] = ' '; - } - else strcpy(pdatetime,"(undated)"); - - gtype = navi::gallerytype; - - if (gtype == GDIR) // gallery name = directory - snprintf(titlebar,250," %d/%d %s %s %s", // 16.04 - fposn,Nimages,fdirk,fname,pdatetime); - else { - if (gtype == SEARCH || gtype == METADATA) - 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 16.04 - gname,fposn,Nimages,fdirk,fname,pdatetime); - else - snprintf(titlebar,250,"%s (*)/%d %s %s %s", // image not in gallery 16.04 - 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) -{ - int qx, qy, crflag = 0; - static int pqx, pqy; - static uint8 pixel[12]; // 2x2 block of pixels - static PIXBUF *pixbuf1 = 0, *pixbuf4 = 0; - - zthreadcrash(); - - 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 15.09 - 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 (! mwcr) { - mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already - crflag = 1; - } - - if (Mscale <= 1) { // write 1x1 pixels 15.08 - pixel[0] = LINE_COLOR[0]; - pixel[1] = LINE_COLOR[1]; - pixel[2] = LINE_COLOR[2]; - - gdk_cairo_set_source_pixbuf(mwcr,pixbuf1,qx+Dorgx,qy+Dorgy); - cairo_paint(mwcr); - } - - 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(mwcr,pixbuf4,qx+Dorgx,qy+Dorgy); - cairo_paint(mwcr); - } - - if (crflag) { - cairo_destroy(mwcr); - mwcr = 0; - } - - return; -} - - -// erase one drawn pixel - restore from window image Mpxb. -// px, py are image space. - -void erase_pixel(int px, int py) -{ - GdkPixbuf *pixbuf; - static int pqx, pqy; - int qx, qy, crflag = 0; - - zthreadcrash(); - - 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; - - if (! mwcr) { - mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already - crflag = 1; - } - - pixbuf = gdk_pixbuf_new_subpixbuf(Mpxb->pixbuf,qx,qy,2,2); // 2x2 Mpxb area to copy 15.08 - qx = qx - Morgx + Dorgx; // target pixel in window - qy = qy - Morgy + Dorgy; - gdk_cairo_set_source_pixbuf(mwcr,pixbuf,qx,qy); - cairo_paint(mwcr); - - g_object_unref(pixbuf); - - if (crflag) { - cairo_destroy(mwcr); - mwcr = 0; - } - - 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) -{ - float px1, py1, px2, py2; - double dashes[2] = { 4, 4 }; - int crflag = 0; - double R, G, B; - - zthreadcrash(); - - 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; - - if (! mwcr) { - mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already - cairo_set_line_width(mwcr,1); - crflag = 1; - } - - R = LINE_COLOR[0] / 255.0; // use line color - G = LINE_COLOR[1] / 255.0; - B = LINE_COLOR[2] / 255.0; - - cairo_set_source_rgb(mwcr,R,G,B); - if (type == 2) cairo_set_dash(mwcr,dashes,2,0); // dotted line - else cairo_set_dash(mwcr,dashes,0,0); - - cairo_move_to(mwcr,px1,py1); // draw line - cairo_line_to(mwcr,px2,py2); - cairo_stroke(mwcr); - - if (crflag) { - cairo_destroy(mwcr); - mwcr = 0; - } - - 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) -{ - float pxm, pym, slope; - int crflag = 0; - - zthreadcrash(); - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->fpxb) return; // no image - - if (! mwcr) { - mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already - 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); - } - } - - else { - slope = 1.0 * (y2 - y1) / (x2 - x1); - for (pxm = x1; pxm <= x2; pxm++) { - pym = y1 + slope * (pxm - x1); - erase_pixel(pxm,pym); - } - } - - if (crflag) { - cairo_destroy(mwcr); - mwcr = 0; - } - - 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) -{ - int ii, crflag = 0; - - zthreadcrash(); - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->fpxb) return; // no image - - if (! mwcr) { - mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already - cairo_set_line_width(mwcr,1); - 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); - - 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); - - if (crflag) { - cairo_destroy(mwcr); - mwcr = 0; - } - - 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() -{ - int crflag = 0, G = currgrid; - int px, py, gww, ghh; - int startx, starty, endx, endy, stepx, stepy; - int startx1, starty1; - - zthreadcrash(); - - 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 * (trimrect[2] - trimrect[0]); // fit grid box to trim rectangle - ghh = Mscale * (trimrect[3] - trimrect[1]); - startx = Mscale * trimrect[0] - Morgx + Dorgx; - starty = Mscale * trimrect[1] - 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 (! mwcr) { - mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already - cairo_set_line_width(mwcr,1); - crflag = 1; - } - - cairo_set_source_rgb(mwcr,1,1,1); // white lines - - if (gridsettings[G][GX] && stepx) - for (px = startx1; px < endx; px += stepx) { - cairo_move_to(mwcr,px,starty); - cairo_line_to(mwcr,px,endy); - } - - if (gridsettings[G][GY] && stepy) - for (py = starty1; py < endy; py += stepy) { - cairo_move_to(mwcr,startx,py); - cairo_line_to(mwcr,endx,py); - } - - cairo_stroke(mwcr); - - cairo_set_source_rgb(mwcr,0,0,0); // adjacent black lines - - if (gridsettings[G][GX] && stepx) - for (px = startx1+1; px < endx+1; px += stepx) { - cairo_move_to(mwcr,px,starty); - cairo_line_to(mwcr,px,endy); - } - - if (gridsettings[G][GY] && stepy) - for (py = starty1+1; py < endy+1; py += stepy) { - cairo_move_to(mwcr,startx,py); - cairo_line_to(mwcr,endx,py); - } - - cairo_stroke(mwcr); - - if (crflag) { - cairo_destroy(mwcr); - mwcr = 0; - } - - 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) -{ - zthreadcrash(); - - 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() -{ - int crflag = 0; - - zthreadcrash(); - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->fpxb) return; // no image - - if (! mwcr) { - mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already - cairo_set_line_width(mwcr,1); - crflag = 1; - } - - for (int ii = 0; ii < Ntoptext; ii++) - draw_text(toptext[ii].px,toptext[ii].py,toptext[ii].text,toptext[ii].font); - - if (crflag) { - cairo_destroy(mwcr); - mwcr = 0; - } - - 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) -{ - static PangoFontDescription *pangofont = 0; - static PangoLayout *pangolayout = 0; - static char priorfont[40] = ""; - int ww, hh, crflag = 0; - - zthreadcrash(); - - 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 (! mwcr) { - mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already - cairo_set_line_width(mwcr,1); - crflag = 1; - } - - cairo_set_source_rgb(mwcr,1,1,1); // draw white background - cairo_rectangle(mwcr,px,py,ww,hh); - cairo_fill(mwcr); - - cairo_move_to(mwcr,px,py); // draw layout with text - cairo_set_source_rgb(mwcr,0,0,0); - pango_cairo_show_layout(mwcr,pangolayout); - - if (crflag) { - cairo_destroy(mwcr); - mwcr = 0; - } - - 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() -{ - int ii, px, py, rad; - int crflag = 0; - double R, G, B; - - zthreadcrash(); - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->fpxb) return; // no image - - if (! mwcr) { - mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already - cairo_set_line_width(mwcr,1); - crflag = 1; - } - - R = LINE_COLOR[0] / 255.0; // use LINE_COLOR - G = LINE_COLOR[1] / 255.0; - B = LINE_COLOR[2] / 255.0; - - 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(mwcr,R,G,B); - cairo_arc(mwcr,px,py,rad,0,2*PI); // draw 360 deg. arc - cairo_stroke(mwcr); - } - - if (crflag) { - cairo_destroy(mwcr); - mwcr = 0; - } - - 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, cr: center and radius of circle in image space. -// if Ferase, then erase previous circle only. - -void draw_mousecircle(int cx, int cy, int cr, int Ferase) // 15.09 -{ - int px3, py3, ww3, hh3; - static int ppx3, ppy3, pww3 = 0, phh3; - int crflag = 0; - int px, py, pok; - double R, G, B; - double t, dt, t1, t2; - - zthreadcrash(); - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->mpxb) return; // no image - - if (pww3 > 0) { // erase prior - Fpaint4(ppx3,ppy3,pww3,phh3); // refresh from Mpxb - pww3 = 0; - } - - if (Ferase) return; // erase only, done - - px3 = cx - cr - 2; // convert pointer center + radius - py3 = cy - cr - 2; // to block position, width, length - ww3 = 2 * cr + 4; - hh3 = 2 * cr + 4; - - ppx3 = px3; // remember pixel block area - ppy3 = py3; // to erase in next call - pww3 = ww3; - phh3 = hh3; - - if (! mwcr) { - mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already - crflag = 1; - } - - cx = cx * Mscale - Morgx + Dorgx; // convert to window coordinates - cy = cy * Mscale - Morgy + Dorgy; - cr = cr * Mscale; - - R = LINE_COLOR[0] / 255.0; // use LINE_COLOR - G = LINE_COLOR[1] / 255.0; - B = LINE_COLOR[2] / 255.0; - - cairo_set_source_rgba(mwcr,R,G,B,1.0); // 16.08 - - t1 = t2 = -1; // angle limits of arc to draw - dt = 1.0 / cr; - - for (t = 0; t < 2*PI; t += dt) // loop 0-360 degrees - { - px = cx + cr * cos(t); // pixel on mouse circle - py = cy + cr * 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(mwcr,cx,cy,cr,t1,t2); // draw accumulated arc - cairo_stroke(mwcr); - t1 = t2 = -1; // start over - } - } - - if (t1 >= 0) { - cairo_arc(mwcr,cx,cy,cr,t1,t2); // draw rest of arc - cairo_stroke(mwcr); - } - - if (crflag) { - cairo_destroy(mwcr); - mwcr = 0; - } - - 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 cr, int Ferase) // 15.09 -{ - int px3, py3, ww3, hh3; - static int ppx3, ppy3, pww3 = 0, phh3; - int crflag = 0; - int px, py, pok; - double R, G, B; - double t, dt, t1, t2; - - zthreadcrash(); - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->mpxb) return; // no image - - if (pww3 > 0) { // erase prior - Fpaint4(ppx3,ppy3,pww3,phh3); // refresh from Mpxb - pww3 = 0; - } - - if (Ferase) return; // erase only, done - - px3 = cx - cr - 2; // convert pointer center + radius - py3 = cy - cr - 2; // to block position, width, length - ww3 = 2 * cr + 4; - hh3 = 2 * cr + 4; - - ppx3 = px3; // remember pixel block area - ppy3 = py3; // to erase in next call - pww3 = ww3; - phh3 = hh3; - - if (! mwcr) { - mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already - crflag = 1; - } - - cx = cx * Mscale - Morgx + Dorgx; // convert to window coordinates - cy = cy * Mscale - Morgy + Dorgy; - cr = cr * Mscale; - - R = LINE_COLOR[0] / 255.0; // use LINE_COLOR - G = LINE_COLOR[1] / 255.0; - B = LINE_COLOR[2] / 255.0; - - cairo_set_source_rgba(mwcr,R,G,B,1.0); // 16.08 - - t1 = t2 = -1; // angle limits of arc to draw - dt = 1.0 / cr; - - for (t = 0; t < 2*PI; t += dt) // loop 0-360 degrees - { - px = cx + cr * cos(t); // pixel on mouse circle - py = cy + cr * 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(mwcr,cx,cy,cr,t1,t2); // draw accumulated arc - cairo_stroke(mwcr); - t1 = t2 = -1; // start over - } - } - - if (t1 >= 0) { - cairo_arc(mwcr,cx,cy,cr,t1,t2); // draw rest of arc - cairo_stroke(mwcr); - } - - if (crflag) { - cairo_destroy(mwcr); - mwcr = 0; - } - - return; -} - - -/********************************************************************************/ - -// Draw ellipse around the mouse pointer. -// Prior ellipse will be erased first. -// cx, cy, cww, chh: center and axes of ellipse -// if Ferase, then erase previous ellipse only. - -void draw_mousearc(int cx, int cy, int cww, int chh, int Ferase) -{ - int px3, py3, ww3, hh3; - static int ppx3, ppy3, pww3 = 0, phh3; - int px, py; - int crflag = 0; - float a, b, a2, b2; - float x, y, x2, y2; - - zthreadcrash(); - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Cstate || ! Cstate->fpxb) return; // no image - - paintlock(1); // 16.03 - - if (pww3 > 0) { // erase prior - Fpaint4(ppx3,ppy3,pww3,phh3); // refresh from Mpxb - pww3 = 0; - } - - if (Ferase) { - paintlock(0); // 16.03 - 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; - - if (! mwcr) { - mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already - crflag = 1; - } - - 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); // draw 2 points on ellipse - px = cx + x + 0.5; - draw_pixel(px,py); - } - - 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); // draw 2 points on ellipse - py = cy + y + 0.5; - draw_pixel(px,py); - } - - if (crflag) { - cairo_destroy(mwcr); - mwcr = 0; - } - - paintlock(0); // 16.03 - 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 15.10 - 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)) -{ - zthreadcrash(); - - 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,"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 15.10 - - zthreadcrash(); - - 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 15.10 - } - - 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 15.10 - - 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 15.10 - } - - 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 15.10 - - 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 15.10 - } - } - - if (evtype == GDK_MOTION_NOTIFY) Fdrag = 1; // remember drag is underway - - if (apset) // 15.10 - { - 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) // 15.07 -{ - int ap, kk; - float minx = 0.01 * splcurve_minx; // % to absolute distance 15.10 - - 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; -} - - -// 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; - - zthreadcrash(); - - 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 15.10 - - 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) // 15.10 -{ - 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]; - - zthreadcrash(); - - 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 15.10 - 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; - int Fpreview; - - if (! curr_file) return 0; // no image file - if (FGWM != 'F') m_viewmode(0,"F"); // insure file view mode 16.06 - - if (checkpend("busy block")) return 0; // blocking function 15.11 - - 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 15.10 - zmessageACK(Mwin,ZTX("this function cannot be scripted")); - return 0; - } - - free_geomap(); // 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 16.02 - - 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); // hide select area if present - } - else E1pxm = PXM_copy(E0pxm); // else use full size image - - 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 16.02 - Fpaintnow(); // update image synchronous - return 1; -} - - -/********************************************************************************/ - -// print error message if CEF invalid - -int CEF_invalid() -{ - if (CEF) return 0; - printz("CEF invalid \n"); // 15.05 - return 1; -} - - -/********************************************************************************/ - -// process edit cancel -// keep: retain zdialog, mousefunc, curves - -void edit_cancel(int keep) // 15.04 -{ - if (CEF_invalid()) return; - wrapup_thread(9); // tell thread to quit, wait 15.05 - if (CEF_invalid()) return; - paintlock(1); // block window updates 16.02 - - 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 sequence 16.01 - } - - CEF = 0; // no current edit func - paintlock(0); // unblock window updates 16.02 - Fpaintnow(); // update image synchronous - return; -} - - -/********************************************************************************/ - -// process edit done -// keep: retain zdialog, mousefunc, curves - -void edit_done(int keep) // 15.04 -{ - if (CEF_invalid()) return; - wrapup_thread(8); // tell thread to finish, wait 15.05 - if (CEF_invalid()) return; - paintlock(1); // block window updates 16.02 - - 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 15.10 - } - - 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 sequence 16.02 - } - - CEF = 0; // no current edit func - paintlock(0); // unblock window updates 16.02 - 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() // 15.04 -{ - 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 16.02 - 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 16.02 - 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 16.02 - - 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 16.02 - 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 16.02 - - PXM_free(E3pxm); // redo copy >> E3 - E3pxm = ERpxm; - ERpxm = 0; - CEF->Fmods = 1; // image modified - paintlock(0); // unblock window updates 16.02 - 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 16.02 - - 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 16.02 - 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) // 15.10 -{ - 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) // 15.10 -{ - 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 edit text togbutt check combo comboE " // widget types to save - "radio spin 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) // 15.10 -{ - 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) // 15.10 -{ - 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,"w"); - if (! fid) { - zmessageACK(Mwin,"%s \n %s",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, undo or redo all edits. -// 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 15.07 - { - 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) // 15.07 -{ - 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 16.03 -{ - 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); - shell_quiet("rm -R %s",tempdir); - exit(1); -} - - -// Load E0 from undo/redo file stack -// stack position = URS_pos - -void load_undo() // overhauled 16.03 -{ - char *pp, buff[24]; - FILE *fid; - int ww, hh, nc, nn; - uint cc1, cc2; - uint ccmax = 256 * MEGA; - - pp = strstr(URS_filename,"undo_"); - if (! pp) goto readfail; - snprintf(pp+5,3,"%02d",URS_pos); - - fid = fopen(URS_filename,"r"); - if (! fid) goto readfail; - - nn = fread(buff,20,1,fid); - if (nn != 1) goto readfail; - nn = sscanf(buff,"fotoxx %d %d %d",&ww,&hh,&nc); - if (nn != 3) goto readfail; - - 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 readfail; - break; - } - else { - pp = (char *) E0pxm->pixels; - nn = fread(pp+cc2,ccmax,1,fid); - if (nn != 1) goto readfail; - cc1 -= ccmax; - cc2 += ccmax; - } - } - - fclose(fid); - - sa_validate(); // delete area if invalid - if (Cdrawin) Fpaintnow(); // update image synchronous - return; - -readfail: - zmessageACK(Mwin,"undo/redo stack read failure: %d \n",errno); - shell_quiet("rm -R %s",tempdir); - 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 // 15.04 -// block edit function mutex flag // 15.04 -// 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(Mwin,2,activemess); // 16.03 - } - - 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 15.03 - 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 -{ - zthreadcrash(); - - 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 -{ - zthreadcrash(); - - if (! Cdrawin) return; - if (! gdkwin) return; - if (! Mcapture) return; - mouseCBfunc = 0; - Mcapture = 0; - draw_mousearc(0,0,0,0,1); // remove mouse circle - 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(); // 15.04 - 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(); // 15.04 - 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.002); // 16.05 - if (block) continue; - zmainloop(); - } - if (CEF_invalid()) return; - } - else { - while (wthreads_busy) { - zsleep(0.002); // 16.05 - if (block) continue; - zmainloop(); - } - } - return; -} - - -/******************************************************************************** - other support functions -*********************************************************************************/ - -// table for loading and saving adjustable parameters between sessions - -typedef struct { - char name[20]; - char type[12]; - int count; - void *location; -} param; - -#define Nparms 42 -param paramTab[Nparms] = { -// name type count location -{ "fotoxx release", "char", 1, &Prelease }, -{ "first time", "int", 1, &Ffirsttime }, -{ "window geometry", "int", 4, &mwgeom }, -{ "thumbnail size", "int", 1, &navi::thumbsize }, -{ "menu style", "char", 1, &menu_style }, -{ "F-base-color", "int", 3, &Fbgcolor }, -{ "G-base-color", "int", 3, &Gbgcolor }, -{ "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 }, -{ "curve node dist %", "float", 1, &splcurve_minx }, -{ "startup display", "char", 1, &startdisplay }, -{ "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 }, -{ "OSM map source", "char", 1, &OSM_map_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 }, -{ "trim size", "int", 2, &trimsize }, -{ "trim buttons", "char", 6, &trimbuttons }, -{ "trim ratios", "char", 6, &trimratios }, -{ "edit resize", "int", 2, &editresize }, -{ "jpeg def quality", "int", 1, &jpeg_def_quality }, -{ "thumb file size", "int", 1, &thumbfilesize }, -{ "lens mm", "float", 1, &lens_mm }, -{ "cycledesktop_album", "char", 1, &cycledesktop_album }, -{ "cycledesktop_index", "int", 1, &cycledesktop_index }, -{ "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 15.07 - last_gallerytype = NONE; // 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 16.02 - - 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 15.03 - sa_unselect(); // unselect select area - - draw_toplines(2); // no toplines - erase_topcircles(); // no topcircles - Ntoptext = 0; // no toptext - Fbusy_goal = 0; - - 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,Frelease); // sparse title 15.03 - - 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 16.02 - 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-16.10.1/fotoxx-16.10.3.cc fotoxx-16.10.3/fotoxx-16.10.3.cc --- fotoxx-16.10.1/fotoxx-16.10.3.cc 1970-01-01 00:00:00.000000000 +0000 +++ fotoxx-16.10.3/fotoxx-16.10.3.cc 2016-10-14 09:02:02.000000000 +0000 @@ -0,0 +1,4539 @@ +/******************************************************************************** + + Fotoxx edit photos and manage collections + + Copyright 2007-2016 Michael Cornelison + Source URL: http://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. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/. + +********************************************************************************* + + 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 + + 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) + + 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_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 = 0; + int Fclone=0, cloxx=0, cloyy=0, cloww=0, clohh=0; + int ii, jj, err; + STATB statb; + + if (argc > 1 && strmatchV(argv[1],"-ver","-v",0)) { + printz(Frelease "\n"); // print version and exit + exit(0); + } + + if (gtk_clutter_init(&argc,&argv) != CLUTTER_INIT_SUCCESS) return 1; // init. gtk and clutter (OSM) 16.05 + + if (argc > 2 && strmatch(argv[1],"-home")) homedir = argv[2]; // relocate user directory 16.09 + + zinitapp("fotoxx",homedir); // initz. app directories + err = chdir(get_zhomedir()); // set to /home//.fotoxx + if (err) { + printz("user directory not found: %s \n",homedir); // 16.09 + exit(1); + } + + // initialize externals to default values (saved parameters will override) + + strcpy(zfuncs::zappname,Frelease); // app name and version + Ffirsttime = 1; // first time startup (params override) + Findexlev = 2; // full image index processing 16.09 + FMindexlev = 2; // " also if start via file manager 16.09 + Pindexlev = -1; // no -index command parameter 16.09 + xxrec_tab = 0; // no image index yet 16.09 + Nxxrec = Findexvalid = 0; // 16.09 + 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 + trimsize[0] = 1600; // default initial image trim size + trimsize[1] = 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) + Fbgcolor[0] = Fbgcolor[1] = Fbgcolor[2] = 50; // F view background color 16.07 + Gbgcolor[0] = Gbgcolor[1] = Gbgcolor[2] = 200; // G view background color 16.07 + dialog_font = zstrdup("Sans 11"); // default dialog font 16.05 + iconsize = 32; // default icon size 16.07 + splcurve_minx = 5; // default curve node separation % 15.10 + startdisplay = zstrdup("prevI"); // start with previous image + Fdragopt = 1; // image drag with mouse 15.02 + zoomcount = 2; // zooms to reach 2x image size + zoomratio = sqrtf(2); // corresp. zoom ratio + map_dotsize = 8; // map dot size, mouse capture dist 16.05 + curr_file = curr_dirk = 0; // no curr. file or directory 15.11 + copymove_loc = 0; // copy/move target directory 16.07 + thumbdirk = 0; // no thumbnail directory + thumbfilesize = 256; // thumbnail files, default pixels 15.02 + navi::thumbsize = 128; // gallery window thumbnail size 16.07 + startmenu = 0; // start with menu function + startalbum = 0; // start with 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 + OSM_map_source = zstrdup("mapnik"); // default OSM map source 16.09 + mapbox_access_key = zstrdup("undefined"); // mapbox map source access key 16.09 + + colormapfile = zstrdup("undefined"); // printer calibration color map 15.10 + 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 "); + + 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++) Nval[ii] = ii; // static integer values 0-99 + + // 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(filecachefile,199,"%s/albums/file_cache",get_zhomedir()); // albums, file cache file 16.07 + snprintf(saved_curves_dirk,199,"%s/saved_curves",get_zhomedir()); // saved curves directory + snprintf(writetext_dirk,199,"%s/write_text",get_zhomedir()); // write_text 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 15.10 + snprintf(edit_scripts_dirk,199,"%s/edit_scripts",get_zhomedir()); // edit script files directory 15.10 + 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 15.12 + + 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(writetext_dirk,&statb); + if (err) mkdir(writetext_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); + + 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 16.09 + 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 new added 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 16.09 + 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 start with menu func + startmenu = zstrdup(argv[++ii]); + else if (strmatchV(pp,"-album","-a",0) && argc > ii+1) // -a -album start with album 16.02 + startalbum = zstrdup(argv[++ii]); + else if (strmatch(pp,"-cycledesktop") && argc > ii+2) { // -cycledesktop album seconds 15.06 + run_cycledesktop(argv[ii+1],argv[ii+2]); // run in background without GUI + gtk_main(); + return 0; + } + else initial_file = zstrdup(pp); // must be initial file or directory + } + + ZTXinit(lang); // setup locale and translations + setlocale(LC_NUMERIC,"en_US.UTF-8"); // stop comma decimal points + + build_widgets(); // build window widgets and menus + + 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 + + if (! zfuncs::screen || ! zfuncs::mouse) { + zpopup_message(0,"GTK/GDK failure to find hardware"); // 15.09 + m_quit(0,0); + exit(1); + } + + m_viewmode(0,"F"); // set F mode initially + + 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 + } + + 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; + int Fdirk = 0; + FTYPE ftype; + char *pp, *pp2; + char procfile[20], buff[200]; + char KBshortsU[200], KBshortsI[200]; + char tonefile[200], badnews[200]; + char metalistfile[200], albumfile[200]; + char favoritesfile[200], images_dir[200]; + char indexfile[200], oldindexfile[200]; + double freememory, cachememory; + float exifver = 0; + FILE *fid; + STATB statb; + + printz(Frelease "\n"); // 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"); // check for xdg-open + if (! err) Fxdgopen = 1; + err = shell_quiet("which rawtherapee"); // check for Raw Therapee + if (! err) Frawtherapee = 1; + err = shell_quiet("which growisofs"); // check for growisofs 16.02 + if (! err) Fgrowisofs = 1; + + err = shell_quiet("which hugin"); // check for pano tools (hugin versions) + if (! err) { + PTtools = 1; + err = shell_quiet("which hugin_executor"); // 2015 release + if (! err) PTtools = 2; + } + + if (Fexiftool + Fxdgopen < 2) { // check mandatory dependencies + strcpy(badnews,ZTX("Please install missing programs:")); + if (! Fexiftool) strcat(badnews,"\n exiftool 8.6 or later"); // refuse to start if any are missing + if (! Fxdgopen) strcat(badnews,"\n xdg-utils"); + zmessageACK(Mwin,badnews); + m_quit(0,0); + } + + if (! Frawtherapee) printz("Raw Therapee not installed \n"); + if (! Fgrowisofs) printz("growisofs 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 + + // delete fotoxx tempdir files if owner process is no longer running + + contx = 0; + while ((pp = command_output(contx,"find /tmp/fotoxx-*",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 + + // update KB shortcuts if needed + + err = locale_filespec("user","KB-shortcuts",KBshortsU); // user KB shortcuts + if (err) { // not found + locale_filespec("data","KB-shortcuts",KBshortsI); + shell_quiet("cp %s %s",KBshortsI,KBshortsU); // copy installed KB shortcuts + printz("keyboard shortcuts installed \n"); + } + KBshortcuts_load(); // load KB shortcuts from file + + // copy default data files to user directories if not already + + err = stat(pattern_dirk,&statb); // pattern files + if (err) { + printz("copy default pattern files \n"); + mkdir(pattern_dirk,0750); + shell_quiet("cp -n %s/patterns/* %s",get_zdatadir(),pattern_dirk); + } + + err = stat(retouch_combo_dirk,&statb); // retouch combo settings + if (err) { + printz("copy default retouch_combo files \n"); + mkdir(retouch_combo_dirk,0750); + shell_quiet("cp -n %s/retouch_combo/* %s",get_zdatadir(),retouch_combo_dirk); + } + + err = stat(custom_kernel_dirk,&statb); // custom convolution kernels + if (err) { + printz("copy default custom_kernel files \n"); + mkdir(custom_kernel_dirk,0750); + shell_quiet("cp -n %s/custom_kernel/* %s",get_zdatadir(),custom_kernel_dirk); + } + + snprintf(favoritesfile,200,"%s/menu-config",favorites_dirk); // favorites menu 16.08 + err = stat(favoritesfile,&statb); + if (err) { + printz("copy default favorites menu \n"); + mkdir(favorites_dirk,0750); + shell_quiet("cp -n %s/favorites/* %s",get_zdatadir(),favorites_dirk); + } + + err = stat(tags_defined_file,&statb); // tags_defined file + if (err) { + printz("create default tags_defined file \n"); + shell_quiet("cp %s/tags_defined %s",get_zdatadir(),tags_defined_file); + } + + snprintf(tonefile,200,"%s/slideshow-tone.oga",slideshow_dirk); // default slide show pause tone + err = stat(tonefile,&statb); + if (err) shell_quiet("cp %s/slideshow-tone.oga %s",get_zdatadir(),tonefile); + + snprintf(metalistfile,200,"%s/metadata_short_list",get_zhomedir()); // metadata keys short list 15.10 + err = stat(metalistfile,&statb); + if (err) shell_quiet("cp %s/metadata_short_list %s",get_zdatadir(),metalistfile); + + // remove some unneeded files copied from zdatadir to zhomedir by zinitapp() + + snprintf(images_dir,200,"%s/images",get_zhomedir()); // 16.09 + err = stat(images_dir,&statb); + if (! err) + shell_quiet("cd %s; rm -R images; rm -f edit-menus-* quickstart-* " + "userguide-* slideshow-tone.*",get_zhomedir()); + + // miscellaneous + + screen = gtk_window_get_screen(MWIN); // get monitor pixel dimensions + screenww = gdk_screen_get_width(screen); + screenhh = gdk_screen_get_height(screen); + printz("screen width: %d height: %d \n",screenww,screenhh); + + 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); + + zdialog_inputs("load"); // load saved dialog inputs + zdialog_positions("load"); // load saved dialog positions + + exif_server(0,0,0); // kill orphan exiftool process + + // if indexB file not present and old format index file is present, 16.09 + // convert old formal file to new format file + + snprintf(indexfile,200,"%s/indexB",index_dirk); // new format image index file + err = stat(indexfile,&statb); + if (err) { // not found + snprintf(oldindexfile,200,"%s/index_001",index_dirk); + err = stat(oldindexfile,&statb); // look for old format index file + if (! err) { + printz("convert to new image index (old one is not deleted) \n"); // if present, convert to new format + shell_quiet("cat %s/index_* > %s/indexB",index_dirk,index_dirk); + } + } + + // create or update image index file and memory table + + if (Pindexlev >= 0) index_rebuild(Pindexlev,0); // -index command parameter given 16.09 + else if (initial_file) index_rebuild(FMindexlev,0); // likely a file manager call + else index_rebuild(Findexlev,0); // normal index level + + // set current file and gallery from command line if present + + if (topdirks[0]) curr_dirk = zstrdup(topdirks[0]); // default 1st top image directory 15.11 + else curr_dirk = zstrdup(getenv("HOME")); + + if (startalbum) { // 16.02 + snprintf(albumfile,200,"%s/albums/%s",get_zhomedir(),startalbum); + err = stat(albumfile,&statb); + if (err) { + printz("invalid album file: %s \n",albumfile); + startalbum = 0; + } + } + + if (initial_file) { // from command line + pp = realpath(initial_file,0); // prepend directory if needed + if (pp) { + zfree(initial_file); + initial_file = zstrdup(pp); + free(pp); + } + else { + printz("invalid file: %s \n",initial_file); // invalid file path + zfree(initial_file); + initial_file = 0; + } + } + + if (initial_file) { + ftype = image_file_type(initial_file); + if (ftype == FNF) { // non-existent file + printz("invalid file: %s \n",initial_file); + zfree(initial_file); + initial_file = 0; + } + else if (ftype == FDIR) { // is a directory + if (curr_dirk) zfree(curr_dirk); + curr_dirk = initial_file; + initial_file = 0; + Fdirk = 1; + } + else { + if (curr_dirk) zfree(curr_dirk); + curr_dirk = zstrdup(initial_file); // set current directory from file + pp = strrchr(curr_dirk,'/'); + if (pp) *pp = 0; + } + } + + if (startalbum) { // open initial album gallery 16.02 + navi::gallerytype = ALBUM; + gallery(albumfile,"initF"); + m_viewmode(0,"G"); + } + + else if (initial_file) // open initial image file + f_open(initial_file); + + else if (Fdirk) { // open initial directory gallery + gallery(curr_dirk,"init"); + m_viewmode(0,"G"); + gallery(0,"paint",0); + } + + else if (Fprev) { // start with previous file + if (last_curr_file && *last_curr_file == '/') // 15.07 + 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 15.07 + curr_file = curr_dirk = 0; + navi::galleryname = 0; + navi::gallerytype = NONE; + set_mwin_title(); + } + + // if no command line option, get startup display from user options + + else if (strmatch(startdisplay,"recent")) // start with recent files gallery + m_recentfiles(0,0); + + else if (strmatch(startdisplay,"newest")) // start with newest files gallery 15.07 + m_newfiles(0,"file"); // by file mode date + + else if (strmatch(startdisplay,"prevG")) { // start with previous gallery 15.07 + if (last_gallerytype != NONE) { + navi::gallerytype = last_gallerytype; + if (last_gallerytype == GDIR) + gallery(last_galleryname,"init"); + else gallery(last_galleryname,"initF"); + m_viewmode(0,"G"); + gallery(0,"paint",0); + } + } + + else if (strmatch(startdisplay,"prevI")) { // start with previous image file + if (last_curr_file && *last_curr_file == '/') + f_open(last_curr_file); + } + + else if (strmatch(startdisplay,"dirk")) { // start with given directory + if (startdirk && *startdirk == '/') { + if (curr_dirk) zfree(curr_dirk); + curr_dirk = zstrdup(startdirk); + gallery(curr_dirk,"init",0); + m_viewmode(0,"G"); + gallery(0,"paint",0); + } + } + + else if (strmatch(startdisplay,"file")) // start with given image file + f_open(startfile); + + if (startmenu) { // startup menu on command line + printz("start menu: %s \n",startmenu); + for (ii = 0; ii < Nmenus; ii++) { // convert menu name to menu function + if (! menutab[ii].menu) continue; // separator, null menu 15.12 + if (strmatchcase(startmenu,ZTX(menutab[ii].menu))) break; + } + if (ii < Nmenus) menutab[ii].func(0,menutab[ii].arg); // call the menu function + } + + save_params(); // save parameters now 15.07 + + g_timeout_add(20,gtimefunc,0); // start periodic function (20 ms) 16.02 + + return 0; // don't come back +} + + +/********************************************************************************/ + +// functions for main window event signals + +int delete_event() // main window closed +{ + int yn; + + printz("main window delete event \n"); + if (checkpend("mods")) return 1; // allow user bailout + if (zfuncs::zdialog_busy) { + yn = zmessageYN(Mwin,ZTX("Kill active dialog?")); // allow user bailout + 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 Ffullscreen = 0; + return 0; +} + + +/********************************************************************************/ + +// Periodic function (20 milliseconds) + +int gtimefunc(void *) +{ + static int domore = 0; + + if (Fshutdown) return 0; // shutdown underway + + if (Fpaintlock && gdkwin) { // thread request to freeze 16.02 + gdk_window_freeze_updates(gdkwin); // window updates + zadd_locked(Fpaintlock,-1); + } + + if (Fpaintunlock && gdkwin) { // thread request to thaw 16.02 + gdk_window_thaw_updates(gdkwin); // window updates + zadd_locked(Fpaintunlock,-1); + } + + if (Fpaintrequest && Cdrawin) // 16.02 + 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 16.01 + zd_thread_event = 0; + } + } + + if (--domore > 0) return 1; // do rest every 200 milliseconds 15.05 + 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; + char text1[300], text2[100]; + 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; + + if (! Fpanelshow) return; // panel currently hidden 16.02 + + if (ftf) { + ftf = 0; + reduced = ZTX("(reduced)"); + areaactive = ZTX("area active"); + dialogopen = ZTX("dialog open"); + blocked = ZTX("blocked"); + modified = "mod"; + } + + if (Fslideshow) return; // 16.01 + 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; + } + + snprintf(text1,300,"CPU %03.0f%c",cpuload,'%'); // CPU 023% MB 2345 + + 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) { // 15.03 + 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 (! 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. // overhauled 16.01 +// Show progress bar if Fbusy_goal > 0. +// added to top panel: BUSY |||||||||||..........| + +update_busy: + + static GtkWidget *busylabel = 0, *donelabel = 0; + static cchar *busytext = " BUSY"; + static char donetext[] = "|::::::::::::::::::::|"; + GtkWidget *FGpanel; + int Nbars = 0, pbcc = 34; + + if (FGWM == 'F') FGpanel = Fpanel; // 16.01 + 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) { + Nbars = 20.0 * Fbusy_done / Fbusy_goal + 0.5; // N = 0-20 + if (Nbars > 20) Nbars = 20; + memset(donetext+pbcc,':',Nbars); // [ N bars + 20-N blanks ] + memset(donetext+pbcc+Nbars,' ',20-Nbars); + 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) // 16.02 +{ + 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 15.10 + Fpaintrequest = 0; + return 1; + } + + 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 15.09 + Fpaintrequest = 0; // window updated as of NOW 15.09 + 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 16.01 + + 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 * Fbgcolor[0]; // window background color 16.07 + rgba.green = 0.00392 * Fbgcolor[1]; // 0 - 255 --> 0.0 - 1.0 + rgba.blue = 0.00392 * Fbgcolor[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 16.01 + 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] = Fbgcolor[0]; // use background color 16.07 + bgpix[1] = Fbgcolor[1]; + bgpix[2] = Fbgcolor[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 16.01 + 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); + + mwcr = cr; // cairo context for draw funcs below + cairo_set_line_width(mwcr,1); + + if (Cstate == &Fstate) { // view mode is image + if (Ntoplines) draw_toplines(1); // draw line overlays + if (gridsettings[currgrid][GON]) draw_gridlines(); // draw grid lines + if (Ntoptext) draw_toptext(); // draw text strings + if (Ntopcircles) draw_topcircles(); // draw circles + if (Fshowarea) sa_show(1); // draw select area outline + if (refresh && zdbrdist) m_show_brdist(0,0); // update brightness dist. moved 15.12.1 + } + + if (Cstate == &Wstate) // view mode is world maps + geomap_paint_dots(); // 15.01 + + mwcr = 0; // cairo context invalid + return 1; +} + + +/********************************************************************************/ + +// Repaint modified image synchrounously. +// May NOT be called from threads. + +void Fpaintnow() +{ + if (! Cdrawin || ! gdkwin || ! Cstate || ! Cstate->fpxb) { // no image 15.10 + printf("Fpaintnow(), no image \n"); + return; + } + + Fpaintrequest = 1; // request repaint of changed image 15.09 + 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 15.09 + 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) +{ + 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); // update drawing window from Mpxb + return; +} + + +// Do the same using E0pxm instead of E3pxm + +void Fpaint0(int px3, int py3, int ww3, int hh3) // 16.07 +{ + PXM_PXB_update(E0pxm,Fpxb,px3,py3,ww3,hh3); + PXB_PXB_update(Fpxb,Mpxb,px3,py3,ww3,hh3); + Fpaint4(px3,py3,ww3,hh3); + 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) +{ + PIXBUF *pixbuf, *bgpixbuf; + int px1, py1, px2, py2; + int ww1, hh1, ww2, hh2; + int crflag = 0; + + zthreadcrash(); + + 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) + ww2 = Mscale * ww3 + 2 / Mscale + 4; + hh2 = Mscale * hh3 + 2 / Mscale + 4; + + if (px2 + ww2 > Mpxb->ww) ww2 = Mpxb->ww - px2; // avoid overshoot + if (py2 + hh2 > Mpxb->hh) hh2 = Mpxb->hh - py2; + + if (px2 < Morgx) { // reduce to currently visible window + ww2 = ww2 - (Morgx - px2); + px2 = Morgx; + } + + if (py2 < Morgy) { + hh2 = hh2 - (Morgy - py2); + py2 = Morgy; + } + + if (ww2 <= 0 || hh2 <= 0) return; // completely outside visible window + + paintlock(1); // 16.03 + + if (! mwcr) { + mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already + crflag = 1; + } + + pixbuf = gdk_pixbuf_new_subpixbuf(Mpxb->pixbuf,px2,py2,ww2,hh2); // Mpxb area to paint 15.08 + if (! pixbuf) { + zmessageACK(Mwin,"Fpaint4, pixbuf_new_subpixbuf() failed"); // 16.09 + return; + } + + px1 = px2 - Morgx; // corresp. position in background image + py1 = py2 - Morgy; + + px2 = px2 - Morgx + Dorgx; // corresp. position in drawing window + py2 = py2 - Morgy + Dorgy; + + if (Mpxb->nc > 3) { // alpha channel present 16.01 + ww1 = ww2; // draw background image to area + hh1 = hh2; + if (px1 + ww1 > dww) ww1 = dww - px1; + if (py1 + hh1 > dhh) hh1 = dhh - py1; + bgpixbuf = gdk_pixbuf_new_subpixbuf(BGpixbuf,px1,py1,ww1,hh1); + gdk_cairo_set_source_pixbuf(mwcr,bgpixbuf,px2,py2); + cairo_paint(mwcr); + g_object_unref(bgpixbuf); + } + + gdk_cairo_set_source_pixbuf(mwcr,pixbuf,px2,py2); // draw area to window + cairo_paint(mwcr); + + 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); // refresh select area outline + } + + if (crflag) { + cairo_destroy(mwcr); + mwcr = 0; + } + + paintlock(0); // 16.03 + 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; + static int bdtime = 0, butime = 0; + static int mdragx0, mdragy0; + static int busy = 0; + + 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; + mouse_convert(Mwxposn,Mwyposn,Mxposn,Myposn); // convert to image space + + scroll = ((GdkEventScroll *) event)->direction; // scroll wheel event + + 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 && 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; + } + + 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 + if (Mxposn == Mxdown && Myposn == Mydown) { // and not moving + 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 + if (! busy) { + busy = 1; // stop re-entrance 15.12 + (* mouseCBfunc)(); + busy = 0; + Fmousemain = 1; // click/drag params are processed here + } // unless reset by callback func. + } + + if (FGWM == 'W') geomap_mousefunc(); // geomap mouse function + + if (! Fmousemain) return; // curr. function handles mouse + + 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; + + zthreadcrash(); + + 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); } + +int KBpress(GtkWidget *win, GdkEventKey *event, void *) // keyboard key was pressed 16.09 +{ + int ii, jj, cc; + char matchkey[20]; + + 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; + + KBkey = event->keyval; + + if (! KBcontrolkey && ! KBshiftkey && ! KBaltkey) { // f/g/w/m keys set corresp. window mode + if (KBkey == GDK_KEY_f) { m_viewmode(0,"F"); goto KBend; } // Ctrl/Alt + f/g/w/m usable for shortcuts + if (KBkey == GDK_KEY_g) { m_viewmode(0,"G"); goto KBend; } + if (KBkey == GDK_KEY_w) { m_viewmode(0,"W"); goto KBend; } + if (KBkey == GDK_KEY_m) { m_viewmode(0,"M"); goto KBend; } // OSM 16.05 + } + + if (KBcapture) return 1; // let current function handle it + + if (KBkey == GDK_KEY_h && FGWM == 'G') { // H: toggle hidden files 16.09 + Fshowhidden = 1 - Fshowhidden; // in gallery view + gallery(0,"init"); + gallery(0,"paint"); + goto KBend; + } + + if (KBcontrolkey + KBshiftkey + KBaltkey) goto KBcustom; // other combinations are custom 15.06 + +// reserved shortcuts (not user configurable) + + if (KBkey == GDK_KEY_F1) { // F1 >> user guide + showz_userguide(F1_help_topic); // show topic if there, or page 1 + goto KBend; + } + + if (KBkey == GDK_KEY_F10) { // F10: fullscreen toggle with menu 16.02 + if (Fslideshow) { + ss_escape = 1; // quit slideshow if active + goto KBend; + } + if (! Ffullscreen) win_fullscreen(0); // toggle full-screen mode and back + else win_unfullscreen(); + goto KBend; + } + + if (KBkey == GDK_KEY_F11) { // F11: fullscreen toggle no menu 16.02 + if (Fslideshow) { + ss_escape = 1; // quit slideshow if active + goto KBend; + } + if (! Ffullscreen) win_fullscreen(1); // toggle full-screen mode and back + else win_unfullscreen(); + goto KBend; + } + + if (KBkey == GDK_KEY_Escape) { // ESC key + if (Fslideshow) ss_escape = 1; // quit slideshow if active 16.01 + else if (Ffullscreen) win_unfullscreen(); // exit full screen mode + goto KBend; + } + + if (KBkey == GDK_KEY_s) { // 's' shortcut for Sync Gallery 15.02 + navi::menufuncx(0,"Sync Gallery"); // 15.12 + goto KBend; + } + + if (FGWM == 'G') { // window mode = G + navi::KBpress(win,event,0); // pass to gallery KB function + goto KBend; // (keys below not for gallery) + } + + if (KBkey == GDK_KEY_z) { // 'z' key + if (! KBcontrolkey) m_zoom(0,"100"); // if no CTRL, toggle zoom 1x + goto KBend; + } + + if (FGWM == 'W' || FGWM == 'M') goto KBend; // map view mode, no other KB events + + if (KBkey == GDK_KEY_plus) m_zoom(0,"in"); // + key >> zoom in + if (KBkey == GDK_KEY_minus) m_zoom(0,"fit"); // - key >> fit to window + if (KBkey == GDK_KEY_equal) m_zoom(0,"in"); // = key: same as + + if (KBkey == GDK_KEY_KP_Add) m_zoom(0,"in"); // keypad + + if (KBkey == GDK_KEY_KP_Subtract) m_zoom(0,"fit"); // keypad - + + if (Fslideshow) { // slide show mode + if (KBkey == GDK_KEY_Left) ss_Larrow = 1; // arrow keys = prev/next image + if (KBkey == GDK_KEY_Right) ss_Rarrow = 1; + if (KBkey == GDK_KEY_space) ss_spacebar = 1; // spacebar = pause/resume + if (KBkey == GDK_KEY_b) ss_Bkey = 1; // B = blank screen + pause + if (KBkey == GDK_KEY_x) ss_Xkey = 1; // X = shortcut for Magnify + if (KBkey == GDK_KEY_n) ss_Nkey = 1; // N = do transition + next image 16.01 + goto KBend; + } + + if (Fmashup) { // mashup active, pass KB key + mashup::KBfunc(KBkey); + goto KBend; + } + + if (CEF && CEF->menufunc == m_trimrotate) { // trim_rotate active, pass KB key + trimrotate::KBfunc(KBkey); + goto KBend; + } + + if (CEF && CEF->menufunc == m_perspective) { // perspective active, pass KB key + perspective::KBfunc(KBkey); + goto KBend; + } + + if (KBkey == GDK_KEY_Left) m_prev(0,0); // arrow keys = prev/next image + if (KBkey == GDK_KEY_Right) m_next(0,0); + if (KBkey == GDK_KEY_Page_Up) m_prev(0,0); // page up/down = prev/next image + if (KBkey == GDK_KEY_Page_Down) m_next(0,0); + if (KBkey == GDK_KEY_Delete) m_delete_trash(0,0); // delete >> trash 16.06 + if (KBkey == GDK_KEY_x) m_magnify(0,0); // 'x' Magnify 16.05 + +// look for custom shortcut keys in shortcut table + +KBcustom: + + if (KBkey >= GDK_KEY_F1 && KBkey <= GDK_KEY_F9) { // input key is F1 to F9 + ii = KBkey - GDK_KEY_F1; + strcpy(matchkey,"F1"); + matchkey[1] += ii; + } + + else if (KBkey > 255) goto KBend; // not a single letter or digit + + else { + *matchkey = 0; // build input key combination + if (KBcontrolkey) strcat(matchkey,"Ctrl+"); // [Ctrl+] [Alt+] [Shift+] key + if (KBaltkey) strcat(matchkey,"Alt+"); + if (KBshiftkey) strcat(matchkey,"Shift+"); + cc = strlen(matchkey); + matchkey[cc] = KBkey; + matchkey[cc+1] = 0; + } + + for (ii = 0; ii < Nshortcuts; ii++) // convert key combination to menu name + if (strmatchcase(matchkey,shortcutkey[ii])) break; + + if (ii < Nshortcuts) { // convert menu name to menu function + for (jj = 0; jj < Nmenus; jj++) { + if (! menutab[jj].menu) continue; // ignore separator + if (strmatchcase(shortcutmenu[ii],ZTX(menutab[jj].menu))) break; + } + + if (jj < Nmenus) menutab[jj].func(0,menutab[jj].arg); // call the menu function + } + +KBend: + KBkey = 0; + return 1; +} + + +/********************************************************************************/ + +// set the main window to fullscreen status +// (with no menu or panel) + +void win_fullscreen(int hidemenu) +{ + zthreadcrash(); + + if (FGWM == 'F' && hidemenu) { // if F window, hide menu and panel + gtk_widget_hide(Fmenu); + gtk_widget_hide(Fpanel); + Fpanelshow = 0; // 16.02 + } + + gtk_window_fullscreen(MWIN); + while (! Ffullscreen) zmainloop(); // 15.09 + return; +} + + +// restore window to former size and restore menu etc. + +void win_unfullscreen() +{ + zthreadcrash(); + + gtk_window_unfullscreen(MWIN); // restore old window size + gtk_widget_show(Fmenu); + gtk_widget_show(Fpanel); + Fpanelshow = 1; // 16.02 + while (Ffullscreen) zmainloop(); // 16.01 + 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[20]; + char fname[100], gname[100], fdirk[100]; + + zthreadcrash(); + + if (! curr_file || *curr_file != '/') { + gtk_window_set_title(MWIN,Frelease); + 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 = gallery_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 15.03 + strncpy0(pdatetime,pdate,11); // yyyy-mm-dd hh:mm + strncpy0(pdatetime+11,ptime,6); + pdatetime[10] = ' '; + } + else strcpy(pdatetime,"(undated)"); + + gtype = navi::gallerytype; + + if (gtype == GDIR) // gallery name = directory + snprintf(titlebar,250," %d/%d %s %s %s", // 16.04 + fposn,Nimages,fdirk,fname,pdatetime); + else { + if (gtype == SEARCH || gtype == METADATA) + 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 16.04 + gname,fposn,Nimages,fdirk,fname,pdatetime); + else + snprintf(titlebar,250,"%s (*)/%d %s %s %s", // image not in gallery 16.04 + 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) +{ + int qx, qy, crflag = 0; + static int pqx, pqy; + static uint8 pixel[12]; // 2x2 block of pixels + static PIXBUF *pixbuf1 = 0, *pixbuf4 = 0; + + zthreadcrash(); + + 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 15.09 + 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 (! mwcr) { + mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already + crflag = 1; + } + + if (Mscale <= 1) { // write 1x1 pixels 15.08 + pixel[0] = LINE_COLOR[0]; + pixel[1] = LINE_COLOR[1]; + pixel[2] = LINE_COLOR[2]; + + gdk_cairo_set_source_pixbuf(mwcr,pixbuf1,qx+Dorgx,qy+Dorgy); + cairo_paint(mwcr); + } + + 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(mwcr,pixbuf4,qx+Dorgx,qy+Dorgy); + cairo_paint(mwcr); + } + + if (crflag) { + cairo_destroy(mwcr); + mwcr = 0; + } + + return; +} + + +// erase one drawn pixel - restore from window image Mpxb. +// px, py are image space. + +void erase_pixel(int px, int py) +{ + GdkPixbuf *pixbuf; + static int pqx, pqy; + int qx, qy, crflag = 0; + + zthreadcrash(); + + 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; + + if (! mwcr) { + mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already + crflag = 1; + } + + pixbuf = gdk_pixbuf_new_subpixbuf(Mpxb->pixbuf,qx,qy,2,2); // 2x2 Mpxb area to copy 15.08 + qx = qx - Morgx + Dorgx; // target pixel in window + qy = qy - Morgy + Dorgy; + gdk_cairo_set_source_pixbuf(mwcr,pixbuf,qx,qy); + cairo_paint(mwcr); + + g_object_unref(pixbuf); + + if (crflag) { + cairo_destroy(mwcr); + mwcr = 0; + } + + 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) +{ + float px1, py1, px2, py2; + double dashes[2] = { 4, 4 }; + int crflag = 0; + double R, G, B; + + zthreadcrash(); + + 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; + + if (! mwcr) { + mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already + cairo_set_line_width(mwcr,1); + crflag = 1; + } + + R = LINE_COLOR[0] / 255.0; // use line color + G = LINE_COLOR[1] / 255.0; + B = LINE_COLOR[2] / 255.0; + + cairo_set_source_rgb(mwcr,R,G,B); + if (type == 2) cairo_set_dash(mwcr,dashes,2,0); // dotted line + else cairo_set_dash(mwcr,dashes,0,0); + + cairo_move_to(mwcr,px1,py1); // draw line + cairo_line_to(mwcr,px2,py2); + cairo_stroke(mwcr); + + if (crflag) { + cairo_destroy(mwcr); + mwcr = 0; + } + + 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) +{ + float pxm, pym, slope; + int crflag = 0; + + zthreadcrash(); + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->fpxb) return; // no image + + if (! mwcr) { + mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already + 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); + } + } + + else { + slope = 1.0 * (y2 - y1) / (x2 - x1); + for (pxm = x1; pxm <= x2; pxm++) { + pym = y1 + slope * (pxm - x1); + erase_pixel(pxm,pym); + } + } + + if (crflag) { + cairo_destroy(mwcr); + mwcr = 0; + } + + 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) +{ + int ii, crflag = 0; + + zthreadcrash(); + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->fpxb) return; // no image + + if (! mwcr) { + mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already + cairo_set_line_width(mwcr,1); + 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); + + 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); + + if (crflag) { + cairo_destroy(mwcr); + mwcr = 0; + } + + 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() +{ + int crflag = 0, G = currgrid; + int px, py, gww, ghh; + int startx, starty, endx, endy, stepx, stepy; + int startx1, starty1; + + zthreadcrash(); + + 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 * (trimrect[2] - trimrect[0]); // fit grid box to trim rectangle + ghh = Mscale * (trimrect[3] - trimrect[1]); + startx = Mscale * trimrect[0] - Morgx + Dorgx; + starty = Mscale * trimrect[1] - 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 (! mwcr) { + mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already + cairo_set_line_width(mwcr,1); + crflag = 1; + } + + cairo_set_source_rgb(mwcr,1,1,1); // white lines + + if (gridsettings[G][GX] && stepx) + for (px = startx1; px < endx; px += stepx) { + cairo_move_to(mwcr,px,starty); + cairo_line_to(mwcr,px,endy); + } + + if (gridsettings[G][GY] && stepy) + for (py = starty1; py < endy; py += stepy) { + cairo_move_to(mwcr,startx,py); + cairo_line_to(mwcr,endx,py); + } + + cairo_stroke(mwcr); + + cairo_set_source_rgb(mwcr,0,0,0); // adjacent black lines + + if (gridsettings[G][GX] && stepx) + for (px = startx1+1; px < endx+1; px += stepx) { + cairo_move_to(mwcr,px,starty); + cairo_line_to(mwcr,px,endy); + } + + if (gridsettings[G][GY] && stepy) + for (py = starty1+1; py < endy+1; py += stepy) { + cairo_move_to(mwcr,startx,py); + cairo_line_to(mwcr,endx,py); + } + + cairo_stroke(mwcr); + + if (crflag) { + cairo_destroy(mwcr); + mwcr = 0; + } + + 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) +{ + zthreadcrash(); + + 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() +{ + int crflag = 0; + + zthreadcrash(); + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->fpxb) return; // no image + + if (! mwcr) { + mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already + cairo_set_line_width(mwcr,1); + crflag = 1; + } + + for (int ii = 0; ii < Ntoptext; ii++) + draw_text(toptext[ii].px,toptext[ii].py,toptext[ii].text,toptext[ii].font); + + if (crflag) { + cairo_destroy(mwcr); + mwcr = 0; + } + + 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) +{ + static PangoFontDescription *pangofont = 0; + static PangoLayout *pangolayout = 0; + static char priorfont[40] = ""; + int ww, hh, crflag = 0; + + zthreadcrash(); + + 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 (! mwcr) { + mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already + cairo_set_line_width(mwcr,1); + crflag = 1; + } + + cairo_set_source_rgb(mwcr,1,1,1); // draw white background + cairo_rectangle(mwcr,px,py,ww,hh); + cairo_fill(mwcr); + + cairo_move_to(mwcr,px,py); // draw layout with text + cairo_set_source_rgb(mwcr,0,0,0); + pango_cairo_show_layout(mwcr,pangolayout); + + if (crflag) { + cairo_destroy(mwcr); + mwcr = 0; + } + + 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() +{ + int ii, px, py, rad; + int crflag = 0; + double R, G, B; + + zthreadcrash(); + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->fpxb) return; // no image + + if (! mwcr) { + mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already + cairo_set_line_width(mwcr,1); + crflag = 1; + } + + R = LINE_COLOR[0] / 255.0; // use LINE_COLOR + G = LINE_COLOR[1] / 255.0; + B = LINE_COLOR[2] / 255.0; + + 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(mwcr,R,G,B); + cairo_arc(mwcr,px,py,rad,0,2*PI); // draw 360 deg. arc + cairo_stroke(mwcr); + } + + if (crflag) { + cairo_destroy(mwcr); + mwcr = 0; + } + + 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, cr: center and radius of circle in image space. +// if Ferase, then erase previous circle only. + +void draw_mousecircle(int cx, int cy, int cr, int Ferase) // 15.09 +{ + int px3, py3, ww3, hh3; + static int ppx3, ppy3, pww3 = 0, phh3; + int crflag = 0; + int px, py, pok; + double R, G, B; + double t, dt, t1, t2; + + zthreadcrash(); + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->mpxb) return; // no image + + if (pww3 > 0) { // erase prior + Fpaint4(ppx3,ppy3,pww3,phh3); // refresh from Mpxb + pww3 = 0; + } + + if (Ferase) return; // erase only, done + + px3 = cx - cr - 2; // convert pointer center + radius + py3 = cy - cr - 2; // to block position, width, length + ww3 = 2 * cr + 4; + hh3 = 2 * cr + 4; + + ppx3 = px3; // remember pixel block area + ppy3 = py3; // to erase in next call + pww3 = ww3; + phh3 = hh3; + + if (! mwcr) { + mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already + crflag = 1; + } + + cx = cx * Mscale - Morgx + Dorgx; // convert to window coordinates + cy = cy * Mscale - Morgy + Dorgy; + cr = cr * Mscale; + + R = LINE_COLOR[0] / 255.0; // use LINE_COLOR + G = LINE_COLOR[1] / 255.0; + B = LINE_COLOR[2] / 255.0; + + cairo_set_source_rgba(mwcr,R,G,B,1.0); // 16.08 + + t1 = t2 = -1; // angle limits of arc to draw + dt = 1.0 / cr; + + for (t = 0; t < 2*PI; t += dt) // loop 0-360 degrees + { + px = cx + cr * cos(t); // pixel on mouse circle + py = cy + cr * 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(mwcr,cx,cy,cr,t1,t2); // draw accumulated arc + cairo_stroke(mwcr); + t1 = t2 = -1; // start over + } + } + + if (t1 >= 0) { + cairo_arc(mwcr,cx,cy,cr,t1,t2); // draw rest of arc + cairo_stroke(mwcr); + } + + if (crflag) { + cairo_destroy(mwcr); + mwcr = 0; + } + + 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 cr, int Ferase) // 15.09 +{ + int px3, py3, ww3, hh3; + static int ppx3, ppy3, pww3 = 0, phh3; + int crflag = 0; + int px, py, pok; + double R, G, B; + double t, dt, t1, t2; + + zthreadcrash(); + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->mpxb) return; // no image + + if (pww3 > 0) { // erase prior + Fpaint4(ppx3,ppy3,pww3,phh3); // refresh from Mpxb + pww3 = 0; + } + + if (Ferase) return; // erase only, done + + px3 = cx - cr - 2; // convert pointer center + radius + py3 = cy - cr - 2; // to block position, width, length + ww3 = 2 * cr + 4; + hh3 = 2 * cr + 4; + + ppx3 = px3; // remember pixel block area + ppy3 = py3; // to erase in next call + pww3 = ww3; + phh3 = hh3; + + if (! mwcr) { + mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already + crflag = 1; + } + + cx = cx * Mscale - Morgx + Dorgx; // convert to window coordinates + cy = cy * Mscale - Morgy + Dorgy; + cr = cr * Mscale; + + R = LINE_COLOR[0] / 255.0; // use LINE_COLOR + G = LINE_COLOR[1] / 255.0; + B = LINE_COLOR[2] / 255.0; + + cairo_set_source_rgba(mwcr,R,G,B,1.0); // 16.08 + + t1 = t2 = -1; // angle limits of arc to draw + dt = 1.0 / cr; + + for (t = 0; t < 2*PI; t += dt) // loop 0-360 degrees + { + px = cx + cr * cos(t); // pixel on mouse circle + py = cy + cr * 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(mwcr,cx,cy,cr,t1,t2); // draw accumulated arc + cairo_stroke(mwcr); + t1 = t2 = -1; // start over + } + } + + if (t1 >= 0) { + cairo_arc(mwcr,cx,cy,cr,t1,t2); // draw rest of arc + cairo_stroke(mwcr); + } + + if (crflag) { + cairo_destroy(mwcr); + mwcr = 0; + } + + return; +} + + +/********************************************************************************/ + +// Draw ellipse around the mouse pointer. +// Prior ellipse will be erased first. +// cx, cy, cww, chh: center and axes of ellipse +// if Ferase, then erase previous ellipse only. + +void draw_mousearc(int cx, int cy, int cww, int chh, int Ferase) +{ + int px3, py3, ww3, hh3; + static int ppx3, ppy3, pww3 = 0, phh3; + int px, py; + int crflag = 0; + float a, b, a2, b2; + float x, y, x2, y2; + + zthreadcrash(); + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Cstate || ! Cstate->fpxb) return; // no image + + paintlock(1); // 16.03 + + if (pww3 > 0) { // erase prior + Fpaint4(ppx3,ppy3,pww3,phh3); // refresh from Mpxb + pww3 = 0; + } + + if (Ferase) { + paintlock(0); // 16.03 + 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; + + if (! mwcr) { + mwcr = gdk_cairo_create(gdkwin); // create cairo context if not already + crflag = 1; + } + + 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); // draw 2 points on ellipse + px = cx + x + 0.5; + draw_pixel(px,py); + } + + 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); // draw 2 points on ellipse + py = cy + y + 0.5; + draw_pixel(px,py); + } + + if (crflag) { + cairo_destroy(mwcr); + mwcr = 0; + } + + paintlock(0); // 16.03 + 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 15.10 + 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)) +{ + zthreadcrash(); + + 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,"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 15.10 + + zthreadcrash(); + + 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 15.10 + } + + 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 15.10 + + 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 15.10 + } + + 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 15.10 + + 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 15.10 + } + } + + if (evtype == GDK_MOTION_NOTIFY) Fdrag = 1; // remember drag is underway + + if (apset) // 15.10 + { + 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) // 15.07 +{ + int ap, kk; + float minx = 0.01 * splcurve_minx; // % to absolute distance 15.10 + + 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; +} + + +// 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; + + zthreadcrash(); + + 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 15.10 + + 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) // 15.10 +{ + 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]; + + zthreadcrash(); + + 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 15.10 + 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; + int Fpreview; + + if (! curr_file) return 0; // no image file + if (FGWM != 'F') m_viewmode(0,"F"); // insure file view mode 16.06 + + if (checkpend("busy block")) return 0; // blocking function 15.11 + + 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 15.10 + zmessageACK(Mwin,ZTX("this function cannot be scripted")); + return 0; + } + + free_geomap(); // 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 16.02 + + 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); // hide select area if present + } + else E1pxm = PXM_copy(E0pxm); // else use full size image + + 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 16.02 + Fpaintnow(); // update image synchronous + return 1; +} + + +/********************************************************************************/ + +// print error message if CEF invalid + +int CEF_invalid() +{ + if (CEF) return 0; + printz("CEF invalid \n"); // 15.05 + return 1; +} + + +/********************************************************************************/ + +// process edit cancel +// keep: retain zdialog, mousefunc, curves + +void edit_cancel(int keep) // 15.04 +{ + if (CEF_invalid()) return; + wrapup_thread(9); // tell thread to quit, wait 15.05 + if (CEF_invalid()) return; + paintlock(1); // block window updates 16.02 + + 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 sequence 16.01 + } + + CEF = 0; // no current edit func + paintlock(0); // unblock window updates 16.02 + Fpaintnow(); // update image synchronous + return; +} + + +/********************************************************************************/ + +// process edit done +// keep: retain zdialog, mousefunc, curves + +void edit_done(int keep) // 15.04 +{ + if (CEF_invalid()) return; + wrapup_thread(8); // tell thread to finish, wait 15.05 + if (CEF_invalid()) return; + paintlock(1); // block window updates 16.02 + + 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 15.10 + } + + 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 sequence 16.02 + } + + CEF = 0; // no current edit func + paintlock(0); // unblock window updates 16.02 + 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() // 15.04 +{ + 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 16.02 + 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 16.02 + 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 16.02 + + 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 16.02 + 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 16.02 + + PXM_free(E3pxm); // redo copy >> E3 + E3pxm = ERpxm; + ERpxm = 0; + CEF->Fmods = 1; // image modified + paintlock(0); // unblock window updates 16.02 + 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 16.02 + + 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 16.02 + 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) // 15.10 +{ + 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) // 15.10 +{ + 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 edit text togbutt check combo comboE " // widget types to save + "radio spin 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) // 15.10 +{ + 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) // 15.10 +{ + 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,"w"); + if (! fid) { + zmessageACK(Mwin,"%s \n %s",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, undo or redo all edits. +// 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 15.07 + { + 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) // 15.07 +{ + 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 16.03 +{ + 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); + shell_quiet("rm -R %s",tempdir); + exit(1); +} + + +// Load E0 from undo/redo file stack +// stack position = URS_pos + +void load_undo() // overhauled 16.03 +{ + char *pp, buff[24]; + FILE *fid; + int ww, hh, nc, nn; + uint cc1, cc2; + uint ccmax = 256 * MEGA; + + pp = strstr(URS_filename,"undo_"); + if (! pp) goto readfail; + snprintf(pp+5,3,"%02d",URS_pos); + + fid = fopen(URS_filename,"r"); + if (! fid) goto readfail; + + nn = fread(buff,20,1,fid); + if (nn != 1) goto readfail; + nn = sscanf(buff,"fotoxx %d %d %d",&ww,&hh,&nc); + if (nn != 3) goto readfail; + + 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 readfail; + break; + } + else { + pp = (char *) E0pxm->pixels; + nn = fread(pp+cc2,ccmax,1,fid); + if (nn != 1) goto readfail; + cc1 -= ccmax; + cc2 += ccmax; + } + } + + fclose(fid); + + sa_validate(); // delete area if invalid + if (Cdrawin) Fpaintnow(); // update image synchronous + return; + +readfail: + zmessageACK(Mwin,"undo/redo stack read failure: %d \n",errno); + shell_quiet("rm -R %s",tempdir); + 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 // 15.04 +// block edit function mutex flag // 15.04 +// 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(Mwin,2,activemess); // 16.03 + } + + 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 15.03 + 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 +{ + zthreadcrash(); + + 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 +{ + zthreadcrash(); + + if (! Cdrawin) return; + if (! gdkwin) return; + if (! Mcapture) return; + mouseCBfunc = 0; + Mcapture = 0; + draw_mousearc(0,0,0,0,1); // remove mouse circle + 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(); // 15.04 + 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(); // 15.04 + 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.002); // 16.05 + if (block) continue; + zmainloop(); + } + if (CEF_invalid()) return; + } + else { + while (wthreads_busy) { + zsleep(0.002); // 16.05 + if (block) continue; + zmainloop(); + } + } + return; +} + + +/******************************************************************************** + other support functions +*********************************************************************************/ + +// table for loading and saving adjustable parameters between sessions + +typedef struct { + char name[20]; + char type[12]; + int count; + void *location; +} param; + +#define Nparms 42 +param paramTab[Nparms] = { +// name type count location +{ "fotoxx release", "char", 1, &Prelease }, +{ "first time", "int", 1, &Ffirsttime }, +{ "window geometry", "int", 4, &mwgeom }, +{ "thumbnail size", "int", 1, &navi::thumbsize }, +{ "menu style", "char", 1, &menu_style }, +{ "F-base-color", "int", 3, &Fbgcolor }, +{ "G-base-color", "int", 3, &Gbgcolor }, +{ "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 }, +{ "curve node dist %", "float", 1, &splcurve_minx }, +{ "startup display", "char", 1, &startdisplay }, +{ "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 }, +{ "OSM map source", "char", 1, &OSM_map_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 }, +{ "trim size", "int", 2, &trimsize }, +{ "trim buttons", "char", 6, &trimbuttons }, +{ "trim ratios", "char", 6, &trimratios }, +{ "edit resize", "int", 2, &editresize }, +{ "jpeg def quality", "int", 1, &jpeg_def_quality }, +{ "thumb file size", "int", 1, &thumbfilesize }, +{ "lens mm", "float", 1, &lens_mm }, +{ "cycledesktop_album", "char", 1, &cycledesktop_album }, +{ "cycledesktop_index", "int", 1, &cycledesktop_index }, +{ "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 15.07 + last_gallerytype = NONE; // 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 16.02 + + 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 15.03 + sa_unselect(); // unselect select area + + draw_toplines(2); // no toplines + erase_topcircles(); // no topcircles + Ntoptext = 0; // no toptext + Fbusy_goal = 0; + + 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,Frelease); // sparse title 15.03 + + 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 16.02 + 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-16.10.1/fotoxx.h fotoxx-16.10.3/fotoxx.h --- fotoxx-16.10.1/fotoxx.h 2016-10-08 19:16:36.000000000 +0000 +++ fotoxx-16.10.3/fotoxx.h 2016-10-14 09:02:02.000000000 +0000 @@ -36,7 +36,7 @@ // Fotoxx definitions -#define Frelease "Fotoxx 16.10.1" // Fotoxx release version +#define Frelease "Fotoxx 16.10.3" // Fotoxx release version #define Flicense "Free software - GNU General Public License v.3" #define Fhomepage "http://kornelix.net" #define Fcontact "Questions and bugs: kornelix@posteo.de" diff -Nru fotoxx-16.10.1/f.tools.cc fotoxx-16.10.3/f.tools.cc --- fotoxx-16.10.1/f.tools.cc 2016-10-08 19:16:36.000000000 +0000 +++ fotoxx-16.10.3/f.tools.cc 2016-10-14 09:02:02.000000000 +0000 @@ -700,7 +700,7 @@ if (Nnew) { - cc = Nnew * sizeof(xxrec_t *); // create new table + cc = maximages * sizeof(xxrec_t *); // make new table with max. capacity 16.10.3 xxrec_tab = (xxrec_t **) zmalloc(cc); for (nrec = 0; nrec < Nnew; nrec++) diff -Nru fotoxx-16.10.1/f.widgets.cc fotoxx-16.10.3/f.widgets.cc --- fotoxx-16.10.1/f.widgets.cc 2016-10-08 19:16:36.000000000 +0000 +++ fotoxx-16.10.3/f.widgets.cc 2016-10-14 09:02:02.000000000 +0000 @@ -369,7 +369,7 @@ cchar * untranslated_tip = ZTX("Report missing translations"); cchar * calibrate_printer_tip = ZTX("Calibrate printer colors"); cchar * resources_tip = ZTX("Memory and CPU (to terminal/logfile)"); -// cchar * zappcrash_tip = "deliberate crash with traceback dump"; + cchar * zappcrash_tip = "deliberate crash with traceback dump"; // batch popup menu cchar * batch_convert_tip = ZTX("Rename/convert/resize/move multiple files"); @@ -567,7 +567,7 @@ MENUENT(mTools, ZTX("Missing Translations"), 0, untranslated_tip, m_untranslated, 0 ); MENUENT(mTools, ZTX("Calibrate Printer"), 0, calibrate_printer_tip, m_calibrate_printer, 0 ); MENUENT(mTools, ZTX("Resources"), 0, resources_tip, m_resources, 0 ); -// MENUENT(mTools, ZTX("zappcrash test"), 0, zappcrash_tip, m_zappcrash, 0 ); + MENUENT(mTools, ZTX("zappcrash test"), 0, zappcrash_tip, m_zappcrash, 0 ); mBatch = create_popmenu(); MENUENT(mBatch, ZTX("Batch Convert"), 0, batch_convert_tip, m_batch_convert, 0 ); diff -Nru fotoxx-16.10.1/Makefile fotoxx-16.10.3/Makefile --- fotoxx-16.10.1/Makefile 2016-10-08 19:16:36.000000000 +0000 +++ fotoxx-16.10.3/Makefile 2016-10-14 09:02:02.000000000 +0000 @@ -1,6 +1,6 @@ # fotoxx makefile -FOTOXX = fotoxx-16.10.1.cc +FOTOXX = fotoxx-16.10.3.cc # defaults for parameters that may be pre-defined CXXFLAGS += -Wall -ggdb diff -Nru fotoxx-16.10.1/zfuncs.cc fotoxx-16.10.3/zfuncs.cc --- fotoxx-16.10.1/zfuncs.cc 2016-10-08 19:16:36.000000000 +0000 +++ fotoxx-16.10.3/zfuncs.cc 2016-10-14 09:02:02.000000000 +0000 @@ -58,7 +58,7 @@ shell_quiet format and run a shell command, return status shell_ack "" + popup error message if error shell_asynch "" + return immediately and query status later - comand_output start shell command and read the output as records + command_output start shell command and read the output as records signalProc pause, resume, or kill a child process runroot run a command or program as root user fgets_trim fgets() with trim of trailing \r \n and optionally blanks @@ -135,7 +135,8 @@ GTK Utility Functions --------------------- - zmainloop do a main loop to process menu events, etc. + zmainloop do main loop to process menu events, etc. + zmainsleep loop zmainloop and zsleep for designated time zthreadcrash crash if called from a thread other than main() wprintx etc. printf() to text widget at specified row or next row wscroll scroll text window to put line on screen @@ -451,7 +452,7 @@ void *stacklist[100]; char message[300], progexe[300]; char buff1[300], buff2[300], hexaddr[20]; - char *arch, *pp1, *pp2, *pfunc; + char *arch, *pp1, *pp2, dlim, *pfunc; if (crash++) return; // re-entry or multiple threads crash @@ -514,16 +515,21 @@ for (ii = 0; ii < nstack; ii++) // loop backtrace records { + pp1 = pp2 = 0; fgets_trim(buff1,300,fid1); // read backtrace line if (! Flinenos) goto output; pfunc = 0; - pp1 = strstr(buff1,"[0x"); // look for hex address [0x.....] - if (! pp1) goto output; - pp2 = strchr(pp1,']'); - if (! pp2) goto output; + pp1 = strstr(buff1,"+0x"); // new format (+0x12345...) 6.5 + if (pp1) pp2 = strchr(pp1,')'); + else { + pp1 = strstr(buff1,"[0x"); // old format [0x12345...] + if (pp1) pp2 = strchr(pp1,']'); + } + if (! pp1 || ! pp2) goto output; // cannot parse + dlim = *pp2; *pp2 = 0; strncpy0(hexaddr,pp1+1,20); - *pp2 = ']'; + *pp2 = dlim; snprintf(buff2,300,"addr2line -e %s %s",progexe,hexaddr); // convert to source program fid3 = popen(buff2,"r"); // and line number if (! fid3) goto output; @@ -5064,6 +5070,20 @@ } +// Iterate the main loop and sleep for designated time + +void zmainsleep(float secs) +{ + while (secs > 0) { + zmainloop(); + zsleep(0.01); + secs = secs - 0.01; + } + + return; +} + + /********************************************************************************/ // crash if current execution is not the main() thread @@ -8547,8 +8567,7 @@ gtk_window_move(GTK_WINDOW(widget),posx,posy); pwidget = 0; } - for (int ii = 0; ii < 10; ii++) - { zmainloop(); zsleep(0.01); } // GTK bug: make focus work FIXME 6.5 + zmainsleep(0.1); // GTK bug: make focus work FIXME 6.5 gtk_window_present(GTK_WINDOW(widget)); // set focus on restored window 6.5 } else { // hide window