/* This file is part of Bolixo. Bolixo 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. Bolixo 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 Bolixo. If not, see . */ #include #include #include #include #include #include #include #include #include #include "bolixo.h" #include "proto/bod_client.protodef" #include "documentd.h" #include "documentd_menu.h" #include "bolixo.m" using namespace std; struct IMAGE{ string image; string caption; IMAGE(PARAM_STRING _image) :image(_image.ptr){ } IMAGE(PARAM_STRING _image, PARAM_STRING _caption) :image(_image.ptr), caption(_caption.ptr){ } }; class PHOTOS: public GAME{ vector images; unsigned main_image=0; // Image currently displayed std::string define_styles(bool mobile); std::string define_functions(const DOC_CONTEXT &ctx); std::string draw_main_image (bool mobile, unsigned docnum, bool editmode, std::string &script); std::string draw_main_caption (bool mobile, unsigned docnum, std::string &script); std::string draw_small_images (bool mobile, unsigned docnum, bool editmode, unsigned height, std::string &script); void resetgame(); void setfocus(VARVAL &var); void update_main_image(VARVAL ¬ify_var); void update_mini_images(const set &updates, unsigned height, VARVAL ¬ify_var); void updateif (unsigned old, set &updimages, VARVAL ¬ify_var); public: PHOTOS(); const char *getclass() const{ return "PHOT"; } void save(DOC_WRITER &w, bool save_session_info); void load(DOC_READER &r, std::string &msg); void exec (const char *var, const char *val, const DOC_CONTEXT &ctx, const DOC_UI_SPECS_receive &sp, std::vector &res, std::vector &unotifies); }; GAME_P make_PHOTOS() { return make_shared(); } #include "proto/documentd_photos.protoh" #include "proto/documentd_photos.protoch" void PHOTOS::resetgame() { images.clear(); } PHOTOS::PHOTOS() { resetgame(); } void PHOTOS::save(DOC_WRITER &w, bool save_session_info) { documentd_photos_header (&w,revision,main_image); vector simages; for (auto &im:images){ PHOTOS_IMAGE s; s.image = im.image; s.caption = im.caption; simages.emplace_back(move(s)); } documentd_photos_images(&w,simages); vector schat; documentd_copychat (schat,chat); documentd_photos_chat(&w,schat); } void PHOTOS::load(DOC_READER &r, std::string &msg) { glocal revision; glocal main_image; glocal images; glocal msg; glocal chat; images.clear(); chat.clear(); (&r); glocal.revision = revision; glocal.main_image = main_image; for (auto s:images) glocal.images.emplace_back(s.image,s.caption); for (auto l:lines) glocal.chat.emplace_back(l.time,l.line); glocal.msg = "Invalid format for photo file"; } string PHOTOS::define_styles(bool mobile) { string lines; lines += ".photos{\n"; lines += "\timage-orientation:from-image;\n"; lines += "\tobject-fit:contain;\n"; lines += "\tmax-height:95%;\n"; lines += "}\n"; return lines; } string PHOTOS::define_functions(const DOC_CONTEXT &ctx) { string lines; wordproc_set_gamepress(lines,"photos_gamepress",true); lines += documentd_js_loop_function("main","photos",ctx.docnum); lines += documentd_js_loop_function("caption","caption",1); lines += documentd_js_loop_function("mini","mini",1); // Update the message field lines += "window.PHOT_updmsg = function(color,msg){\n"; lines += string_f("\tvar elm = document.getElementById('msg-%s');\n",gameid.c_str()); lines += "\tif (elm != null){\n"; lines += "\t\telm.style.color=color;\n"; lines += "\t\telm.innerHTML=msg;\n"; lines += "\t}\n"; lines += "}\n"; lines += "window.pho_gameselect = function(gameid,docnum,event){\n"; lines += "\tvar elm = document.getElementById('main-'+gameid+','+docnum);\n"; lines += "\tvar rect = elm.getBoundingClientRect();\n"; lines += "\tvar x = event.clientX-rect.left;\n"; lines += "\tvar y = event.clientY-rect.top;\n"; lines += "\tgameaction(event,'select:'+x+','+y+ ','+event.which+','+event.shiftKey+','+event.ctrlKey);\n"; lines += "}\n"; lines += "window.pho_mini_gameselect = function(gameid,docnum,event){\n"; lines += "\tvar elm = document.getElementById('mini-'+gameid+','+docnum);\n"; lines += "\tvar rect = elm.getBoundingClientRect();\n"; lines += "\tvar h = elm.clientHeight;\n"; //rect.bottom - rect.top;\n"; lines += "\tvar x = (event.clientX-rect.left + elm.scrollLeft);\n"; lines += "\tvar y = event.clientY-rect.top;\n"; lines += "\tif (y < h) gameaction(event,'mselect:' +x+ ',' +y+ ',' +event.which+ ',' +event.shiftKey+ ',' +event.ctrlKey);\n"; lines += "}\n"; return lines; } static string photos_converturl(PARAM_STRING gameid, PARAM_STRING url) { string ret = url.ptr; if (!is_start_any_ofnc(url.ptr,NONEED,"http://","https://")){ string abspath = documentd_rel2abs(gameid,url); ret = documentd_escape(string_f("/index.hc?webstep=5&image=%s",abspath.c_str())); } return ret; } string PHOTOS::draw_main_image( bool mobile, unsigned docnum, bool editmode, // Enable onXXXX function string &script) { string lines; script += string_f( "var pho_%u={\n" "\tgameid:'%s',docnum:%u\n" "};\n" "pho_%u.gameselect=function(event){\n" "\tpho_gameselect(this.gameid,this.docnum,event);\n" "};\n" ,docnum ,gameid.c_str(),docnum ,docnum); script += string_f("doc_lst.push(pho_%u);\n",docnum); string src; if (images.size() > main_image){ src=string_f("src=%s",photos_converturl(gameid,images[main_image].image).c_str()); } string onfuncs; if (editmode){ onfuncs = string_f(" onmousedown='pho_%u.gameselect(event);'",docnum); } lines += string_f("\n" ,onfuncs.c_str(),gameid.c_str(),docnum,src.c_str()); return lines; } string PHOTOS::draw_main_caption( bool mobile, unsigned docnum, string &script) { string lines; script += string_f( "var pho_caption_%u={\n" "\tgameid:'%s',docnum:%u\n" "};\n" ,docnum ,gameid.c_str(),docnum); script += string_f("doc_lst.push(pho_caption_%u);\n",docnum); lines += string_f("
\n",gameid.c_str(),docnum); if (images.size() > main_image){ auto tb = str_cnv2lines(images[main_image].caption); for (auto &l:tb){ lines += documentd_escape_html(l) + "
"; } } lines += "
\n"; return lines; } string PHOTOS::draw_small_images( bool mobile, unsigned docnum, bool editmode, // Enable onXXXX function unsigned height, string &script) { string lines; unsigned no = 0; script += string_f( "var pho_mini_%u={\n" "\tgameid:'%s',docnum:%u\n" "};\n" "pho_mini_%u.gameselect=function(event){\n" "\tpho_mini_gameselect(this.gameid,this.docnum,event);\n" "};\n" ,docnum ,gameid.c_str(),docnum ,docnum); script += string_f("doc_lst.push(pho_mini_%u);\n",docnum); string onfuncs; if (editmode){ onfuncs = string_f(" onmousedown='pho_mini_%u.gameselect(event);'",docnum); } lines += string_f("
\n",onfuncs.c_str(),gameid.c_str(),docnum,height); for (auto &im:images){ lines += string_f("
\n",no==main_image ? "red" : "black"); lines += string_f("\n" ,no,gameid.c_str(),photos_converturl(gameid,im.image).c_str(),height-6); lines += "
"; no++; } lines += "
"; return lines; } // Some lexical validation for the last part of a URL or a path static bool photos_checkpath(PARAM_STRING path) { bool ret = false; const char *pt = path.ptr; if (*pt != '\0'){ while (*pt != '\0'){ char carac = *pt; if (isalpha(carac) || isdigit(carac) || is_any_of(carac,'/','-','.','_')){ pt++; }else{ break; } } ret = *pt == '\0'; } return ret; } // Some lexical validation for a URL or a path static bool photos_checkurlpath(PARAM_STRING image) { const char *pt; return (is_start_any_ofnc(image,pt,"http://","https://") && photos_checkpath(pt)) || photos_checkpath(image); } void PHOTOS::setfocus(VARVAL &var) { documentd_setfocus(var,string_f("text-%s",gameid.c_str())); } void PHOTOS::update_main_image(VARVAL ¬ify_var) { auto &im = images[main_image]; notify_var.val += "photos_loop_board(function(elm){\n"; notify_var.val += string_f("\telm.src='%s';\n",photos_converturl(gameid,im.image).c_str()); notify_var.val += "});\n"; auto tb = str_cnv2lines(im.caption); string lines; for (auto &l:tb){ lines += documentd_escape_html(l) + "
"; } notify_var.val += "caption_loop_board(function(elm){\n"; notify_var.val += string_f("\telm.innerHTML='%s';\n",lines.c_str()); notify_var.val += "});\n"; } // Update the images in the mini images area void PHOTOS::update_mini_images(const set &updates, unsigned height, VARVAL ¬ify_var) { if (updates.size() == 0) return; bool update_main = false; notify_var.val += "mini_loop_board(function(elm){\n"; notify_var.val += "\tvar divs=elm.getElementsByTagName('div');\n"; // Make sure divs is large enough notify_var.val += string_f("\tfor (var i=divs.length; i<%zu; i++){\n",images.size()); notify_var.val += "\t\tvar newdiv = document.createElement('div');\n"; notify_var.val += "\t\tvar newimg = document.createElement('img');\n"; notify_var.val += string_f("\t\tconsole.log('mini-'+i+'-%s');\n",gameid.c_str()); notify_var.val += string_f("\t\tnewimg.id='mini-'+i+'-%s';\n",gameid.c_str()); notify_var.val += string_f("\t\tnewimg.style.width=%u;\n",height-4); notify_var.val += "\t\tnewimg.className='photos';\n"; notify_var.val += "\t\tnewdiv.style.border='2px solid black';\n"; notify_var.val += string_f("\t\tnewdiv.style.height=%u;\n",height-4); notify_var.val += "\t\tnewdiv.appendChild(newimg);\n"; notify_var.val += "\t\telm.appendChild(newdiv);\n"; notify_var.val += "\t}\n"; // Make sure divs is not to large notify_var.val += string_f("\tfor (var i=divs.length-1; i>=%zu; i--){\n",images.size()); notify_var.val += "\t\telm.removeChild(divs[i]);\n"; notify_var.val += "\t}\n"; notify_var.val += "\tdivs=elm.getElementsByTagName('div');\n"; for (auto im:updates){ const char *color = "black"; if (im == main_image){ update_main = true; color = "red"; } notify_var.val += string_f("\tdivs[%u].style.borderColor='%s';\n",im,color); notify_var.val += string_f("\tvar imgs=divs[%u].getElementsByTagName('img');\n",im); notify_var.val += string_f("\timgs[0].src='%s';\n",photos_converturl(gameid,images[im].image).c_str()); } notify_var.val += "});\n"; if (update_main) update_main_image(notify_var); } // Update the displayed image there was a change in selection void PHOTOS::updateif (unsigned old, set &updimages, VARVAL ¬ify_var) { if (old != main_image){ update_main_image(notify_var); updimages.insert (old); updimages.insert (main_image); } } void PHOTOS::exec ( const char *var, const char *val, const DOC_CONTEXT &ctx, const DOC_UI_SPECS_receive &sp, vector &res, vector &unotifies) { const unsigned mini_height = sp.mobile ? 150 : 100; // Size of the mini photos const unsigned caption_height = sp.mobile ? 100 : 50; // Size of the caption text unsigned waiting_users_width = sp.mobile ? 62 : 42; unsigned board_width = sp.content_width - waiting_users_width; // Space for user list unsigned board_height = sp.content_height; string error,status,api_error; setactivity(); VARVAL notify_var; notify_var.var = VAR_NOTIFY; notify_var.val += string_f("doc_cur_gameid='%s';\n",gameid.c_str()); size_t base_notify_size = notify_var.val.size(); VARVAL script_var; script_var.var = VAR_SCRIPT; set updimages; string tmpvar,tmpval; if (is_eq(var,"kbd")){ unsigned lastline = 1000; unsigned lastcol=1000; MOD_KBD mod; wordproc_kbd(val,mod,tmpvar,tmpval,lastline,lastcol); var = tmpvar.c_str(); val = tmpval.c_str(); } if (is_eq(var,REQ_PRINT)){ VARVAL v; v.var = VAR_CONTENT; v.val += "\n"; (this,sp.mobile,ctx.maywrite,v.val); #define BUTTON_CONFIG 0 #define BUTTON_TXTEDIT 1 PHOTOS_MENU menu(specs); documentd_bar_button (lines,BUTTON_CONFIG,menu.svg_config,specs,false,MSG_U(I_DEFINEIMAGES,"Specify images to display")); documentd_bar_button (lines,BUTTON_TXTEDIT,menu.svg_txtedit,specs,false,MSG_U(I_TXTEDIT,"Edit image caption")); // Main image v.val += "\n"; v.val += string_f("
\n",val); v.val += "
\n"; v.val += string_f("
\n",gameid.c_str(),board_width,board_height-mini_height-caption_height); string script; v.val += draw_main_image(sp.mobile,ctx.docnum,ctx.maywrite,script); v.val += "
\n"; v.val += string_f("
\n",gameid.c_str(),board_width,caption_height); v.val += draw_main_caption(sp.mobile,ctx.docnum,script); v.val += "
\n"; v.val += string_f("
\n",gameid.c_str(),board_width,mini_height); v.val += draw_small_images(sp.mobile,ctx.docnum,ctx.maywrite,mini_height,script); v.val += "
\n"; if (script.size() > 0){ v.val += "\n"; } v.val += "
\n"; draw_waiting_users(v.val,waiting_users_width,board_height,"flex:0 0 auto;"); v.val += "
\n"; // Status line v.val += "
\n"; v.val += string_f("
 
\n",gameid.c_str()); documentd_chat (v.val,ctx.username,sp.mobile,chat,sp.width-20,sp.mobile ? 200 : 100); v.val += "
\n"; res.emplace_back(move(v)); }else if (is_eq(var,REQ_FUNCTIONS)){ VARVAL var; var.var = VAR_DEFSCRIPT; var.val = define_functions (ctx); res.emplace_back(move(var)); }else if (is_eq(var,REQ_STYLES)){ VARVAL var; var.var = VAR_STYLES; var.val += define_styles(sp.mobile); res.emplace_back(move(var)); }else if (is_eq(var,REQ_REGION)){ // For embedding VARVAL var,var_script; var.var = VAR_CONTENT; var_script.var = VAR_DEFSCRIPT; if (is_eq(val,"main")){ var.val = draw_main_image(sp.mobile,ctx.docnum,false,var_script.val); }else if (is_eq(val,"caption")){ var.val = draw_main_caption(sp.mobile,ctx.docnum,var_script.val); }else if (is_eq(val,"mini")){ var.val = draw_small_images(sp.mobile,ctx.docnum,false,mini_height,var_script.val); }else if (is_any_of(val,"","all")){ var.val += "
\n"; var.val += string_f("
\n",gameid.c_str()); var.val += draw_main_image(sp.mobile,ctx.docnum,ctx.maywrite,var_script.val); var.val += "
\n"; var.val += string_f("
\n",gameid.c_str()); var.val += draw_main_caption(sp.mobile,ctx.docnum,var_script.val); var.val += "
\n"; var.val += string_f("
\n",gameid.c_str()); var.val += draw_small_images(sp.mobile,ctx.docnum,ctx.maywrite,mini_height,var_script.val); var.val += "
\n"; var.val += "
\n"; } res.emplace_back(move(var)); res.emplace_back(move(var_script)); }else if (is_eq(var,REQ_LISTREGION)){ VARVAL var,var_script; var.var = VAR_CONTENT; var.val += string_f("main:%s\n",MSG_U(I_PHOTOREGION_MAIN,"Large image")); var.val += string_f("mini:%s\n",MSG_U(I_PHOTOREGION_MINI,"Small images")); var.val += string_f("caption:%s\n",MSG_U(I_PHOTOREGION_CAPTION,"Image caption")); var.val += string_f("all:%s\n",MSG_U(I_PHOTOREGION_ALL,"All regions")); res.emplace_back(move(var)); }else if (is_eq(var,REQ_CHAT)){ appendchat(val,notify_var.val,res,ctx); }else if (is_eq(var,REQ_GETFIELDS)){ VARVAL var; var.var = VAR_FIELDS; if (strcmp(val,DIALOG_PHOTOS_CONFIG)==0){ for (auto &im:images) var.val += string_f("img:%s\n",im.image.c_str()); }else if (strcmp(val,DIALOG_PHOTOS_TXTEDIT)==0){ if (main_image < images.size()){ auto &im = images[main_image]; auto lines = str_cnv2lines(im.caption); var.val += string_f("img:%s\n",im.image.c_str()); for (auto &l:lines){ var.val += string_f("line:%s\n",l.c_str()); } } } res.emplace_back(var); }else if (is_eq(var,REQ_FOCUS)){ setfocus(script_var); }else if (ctx.maywrite){ if (is_eq(var,KBD_HMOVE)){ unsigned old = main_image; if (is_eq(val,"1")){ if (images.size() > 0 && main_image < images.size()-1) main_image++; }else{ if (main_image > 0) main_image--; } updateif (old,updimages,notify_var); }else if (is_any_of(var,"select","mselect")){ double dx,dy; unsigned button; bool shiftkey,ctrlkey; if (splitline(val,',',dx,dy,limits(button,1u,2u),shiftkey,ctrlkey)){ // tlmp_warning ("dx=%lf dy=%lf val=%s\n",dx,dy,val); unsigned old = main_image; if (is_eq(var,"select")){ if (dx > board_width/2){ if (images.size() > 0 && main_image < images.size()-1) main_image++; }else{ if (main_image > 0) main_image--; } }else{ unsigned new_main_image = dx/(mini_height-4); if (new_main_image < images.size()) main_image = new_main_image; } updateif (old,updimages,notify_var); } }else if (is_eq(var,"newgame")){ int uval = atoi(val); if (uval == BUTTON_CONFIG){ VARVAL var; var.var = VAR_DIALOG; var.val = DIALOG_PHOTOS_CONFIG; res.emplace_back(var); }else if (uval == BUTTON_TXTEDIT){ VARVAL var; var.var = VAR_DIALOG; var.val = DIALOG_PHOTOS_TXTEDIT; res.emplace_back(var); } }else if (is_eq(var,"resetgame")){ resetgame(); setmodified(ctx.username); }else if (is_eq(var,"config")){ }else if (is_eq(var,"addimage")){ string image; bool ok = false; if (splitlineq(val,image)){ if (images.size() < 100 && photos_checkurlpath(image)){ updimages.insert(images.size()); images.push_back(IMAGE(image)); ok = true; setmodified(ctx.username); } } if (!ok){ api_error = MSG_U(E_IVLADDIMAGE,"Invalid addimage command"); } }else if (is_eq(var,"setimage")){ unsigned no; string image; bool ok = false; if (splitlineq(val,no,image)){ if (no < 100 && photos_checkurlpath(image)){ while (images.size() <= no){ updimages.insert(images.size()); images.emplace_back(""); } images[no] = IMAGE(image); updimages.insert(no); ok = true; setmodified(ctx.username); } } if (!ok){ api_error = MSG_U(E_IVLSETIMAGE,"Invalid setimage command"); } }else if (is_eq(var,"setcaption")){ string image,caption; bool ok = false; if (splitlineq(val,image,caption)){ unsigned no = 0; for (auto &im:images){ if (im.image == image){ im.caption = caption; ok = true; setmodified(ctx.username); break; } no++; } } if (!ok){ api_error = MSG_U(E_IVLSETCAPTION,"Invalid setcaption command"); } }else if (is_eq(var,"images")){ // Receives the image list from the dialog // Receives the images from the configuration dialog vector fields; documentd_parsefields(val,fields); unsigned no = 0; // We receive a new image list, but we must preserve the captions vector newimages; for (auto &v:fields){ if (v.var == "img"){ updimages.insert(no); newimages.emplace_back(v.val); no++; } } for (auto &im:newimages){ for (auto &s:images){ if (s.image == im.image){ im.caption = s.caption; break; } } } images = newimages; if (main_image >= images.size()) main_image = 0; setmodified(ctx.username); }else if (is_eq(var,"caption")){ // Receive the caption from the dialog vector fields; documentd_parsefields(val,fields); string image,lines; for (auto &v:fields){ if (v.var == "img"){ image = v.val; }else if (v.var == "line"){ lines += v.val + "\n"; } } unsigned no = 0; for (auto &im:images){ if (im.image == image){ im.caption = lines; setmodified(ctx.username); break; } no++; } if (no == main_image){ update_main_image(notify_var); } }else if (is_eq(var,"dump")){ VARVAL var; var.var = "images"; for (auto &im:images) var.val += string_f("%s: %s\n",im.image.c_str(),im.caption.c_str()); res.emplace_back(var); } }else{ error = MSG_R(E_READONLY); } update_mini_images(updimages,mini_height,notify_var); if (notify_var.val.size() > base_notify_size) res.emplace_back(move(notify_var)); if (script_var.val.size() > 0) res.emplace_back(move(script_var)); if (api_error.size() > 0){ VARVAL var; var.var = VAR_ERROR; var.val = move(api_error); res.emplace_back(move(var)); } if (error.size() > 0){ update_msg(false,error,"red",res); }else if (status.size() > 0){ update_msg(true,status,"red",res); }else{ update_msg(false," ","white",res); } }