gui.mjs

import { decodeUrl, settings, constants, gStyle, internals, browser,
         findFunction, parse, isFunc, isStr, isObject, isBatchMode, setBatchMode } from './core.mjs';
import { select as d3_select } from './d3.mjs';
import { HierarchyPainter } from './gui/HierarchyPainter.mjs';
import { setStoragePrefix, readSettings, readStyle } from './gui/utils.mjs';
import { setDefaultDrawOpt } from './draw.mjs';
import { createMenu, closeMenu } from './gui/menu.mjs';


/** @summary Read style and settings from URL
  * @private */
function readStyleFromURL(url) {
   // first try to read settings from local storage
   const d = decodeUrl(url),
         prefix = d.get('storage_prefix');

   if (isStr(prefix) && prefix)
      setStoragePrefix(prefix);

   if (readSettings())
      setDefaultDrawOpt(settings._dflt_drawopt);

   readStyle();

   function get_bool(name, field, special) {
      if (d.has(name)) {
         const val = d.get(name);
         if (special && (val === special))
            settings[field] = special;
         else
            settings[field] = (val !== '0') && (val !== 'false') && (val !== 'off');
      }
   }

   if (d.has('optimize')) {
      settings.OptimizeDraw = 2;
      let optimize = d.get('optimize');
      if (optimize) {
         optimize = parseInt(optimize);
         if (Number.isInteger(optimize))
            settings.OptimizeDraw = optimize;
      }
   }

   const b = d.get('batch');
   if (b !== undefined) {
      setBatchMode(d !== 'off');
      if (b === 'png')
         internals.batch_png = true;
   }

   get_bool('lastcycle', 'OnlyLastCycle');
   get_bool('usestamp', 'UseStamp');
   get_bool('dark', 'DarkMode');
   get_bool('approx_text_size', 'ApproxTextSize');

   let mr = d.get('maxranges');
   if (mr) {
      mr = parseInt(mr);
      if (Number.isInteger(mr)) settings.MaxRanges = mr;
   }

   if (d.has('wrong_http_response'))
      settings.HandleWrongHttpResponse = true;

   if (d.has('prefer_saved_points'))
      settings.PreferSavedPoints = true;

   const tf1_style = d.get('tf1');
   if (tf1_style === 'curve')
      settings.FuncAsCurve = true;
   else if (tf1_style === 'line')
      settings.FuncAsCurve = false;

   if (d.has('with_credentials'))
      settings.WithCredentials = true;

   let inter = d.get('interactive');
   if (inter === 'nomenu')
      settings.ContextMenu = false;
   else if (inter !== undefined) {
      if (!inter || (inter === '1'))
         inter = '111111';
      else if (inter === '0')
         inter = '000000';
      if (inter.length === 6) {
         switch (inter[0]) {
            case '0': settings.ToolBar = false; break;
            case '1': settings.ToolBar = 'popup'; break;
            case '2': settings.ToolBar = true; break;
         }
         inter = inter.slice(1);
      }
      if (inter.length === 5) {
         settings.Tooltip = parseInt(inter[0]);
         settings.ContextMenu = (inter[1] !== '0');
         settings.Zooming = (inter[2] !== '0');
         settings.MoveResize = (inter[3] !== '0');
         settings.DragAndDrop = (inter[4] !== '0');
      }
   }

   get_bool('tooltip', 'Tooltip');

   const mathjax = d.get('mathjax', null);
   let latex = d.get('latex', null);
   if ((mathjax !== null) && (mathjax !== '0') && (latex === null))
      latex = 'math';
   if (latex !== null)
      settings.Latex = constants.Latex.fromString(latex);

   if (d.has('nomenu')) settings.ContextMenu = false;
   if (d.has('noprogress'))
      settings.ProgressBox = false;
   else
      get_bool('progress', 'ProgressBox', 'modal');

   if (d.has('notouch')) browser.touches = false;
   if (d.has('adjframe')) settings.CanAdjustFrame = true;

   const has_toolbar = d.has('toolbar');
   if (has_toolbar) {
      const toolbar = d.get('toolbar', '');
      let val = null;
      if (toolbar.indexOf('popup') >= 0) val = 'popup';
      if (toolbar.indexOf('left') >= 0) { settings.ToolBarSide = 'left'; val = 'popup'; }
      if (toolbar.indexOf('right') >= 0) { settings.ToolBarSide = 'right'; val = 'popup'; }
      if (toolbar.indexOf('vert') >= 0) { settings.ToolBarVert = true; val = 'popup'; }
      if (toolbar.indexOf('show') >= 0) val = true;
      settings.ToolBar = val || ((toolbar.indexOf('0') < 0) && (toolbar.indexOf('false') < 0) && (toolbar.indexOf('off') < 0));
   }

   get_bool('skipsi', 'SkipStreamerInfos');
   get_bool('skipstreamerinfos', 'SkipStreamerInfos');

   if (d.has('nodraggraphs'))
      settings.DragGraphs = false;

   if (d.has('palette')) {
      const palette = parseInt(d.get('palette'));
      if (Number.isInteger(palette) && (palette > 0) && (palette < 113)) settings.Palette = palette;
   }

   const render3d = d.get('render3d'), embed3d = d.get('embed3d'), geosegm = d.get('geosegm');
   if (render3d) settings.Render3D = constants.Render3D.fromString(render3d);
   if (embed3d) settings.Embed3D = constants.Embed3D.fromString(embed3d);
   if (geosegm) settings.GeoGradPerSegm = Math.max(2, parseInt(geosegm));
   get_bool('geocomp', 'GeoCompressComp');

   if (d.has('hlimit')) settings.HierarchyLimit = parseInt(d.get('hlimit'));

   function get_int_style(name, field, dflt) {
      if (!d.has(name)) return;
      const val = d.get(name);
      if (!val || (val === 'true') || (val === 'on'))
         gStyle[field] = dflt;
      else if ((val === 'false') || (val === 'off'))
         gStyle[field] = 0;
      else
         gStyle[field] = parseInt(val);
      return gStyle[field] !== 0;
   }
   function get_float_style(name, field) {
      if (!d.has(name)) return;
      const val = d.get(name),
            flt = Number.parseFloat(val);
      if (Number.isFinite(flt))
         gStyle[field] = flt;
   }

   if (d.has('histzero')) gStyle.fHistMinimumZero = true;
   if (d.has('histmargin')) gStyle.fHistTopMargin = parseFloat(d.get('histmargin'));
   get_int_style('optstat', 'fOptStat', 1111);
   get_int_style('optfit', 'fOptFit', 0);
   const has_date = get_int_style('optdate', 'fOptDate', 1),
         has_file = get_int_style('optfile', 'fOptFile', 1);
   if ((has_date || has_file) && !has_toolbar)
      settings.ToolBarVert = true;
   get_float_style('datex', 'fDateX');
   get_float_style('datey', 'fDateY');

   get_int_style('opttitle', 'fOptTitle', 1);
   if (d.has('utc'))
      settings.TimeZone = 'UTC';
   if (d.has('cet'))
      settings.TimeZone = 'Europe/Berlin';
   else if (d.has('timezone')) {
      settings.TimeZone = d.get('timezone');
      if ((settings.TimeZone === 'default') || (settings.TimeZone === 'dflt'))
         settings.TimeZone = 'Europe/Berlin';
      else if (settings.TimeZone === 'local')
         settings.TimeZone = '';
   }

   gStyle.fStatFormat = d.get('statfmt', gStyle.fStatFormat);
   gStyle.fFitFormat = d.get('fitfmt', gStyle.fFitFormat);
}


/** @summary Build main GUI
  * @desc Used in many HTML files to create JSROOT GUI elements
  * @param {String} gui_element - id of the `<div>` element
  * @param {String} gui_kind - either 'online', 'nobrowser', 'draw'
  * @return {Promise} with {@link HierarchyPainter} instance
  * @example
  * import { buildGUI } from 'https://root.cern/js/latest/modules/gui.mjs';
  * buildGUI('guiDiv'); */
async function buildGUI(gui_element, gui_kind = '') {
   const myDiv = d3_select(isStr(gui_element) ? `#${gui_element}` : gui_element);
   if (myDiv.empty())
      return Promise.reject(Error('no div for gui found'));

   myDiv.html(''); // clear element

   const d = decodeUrl(), getSize = name => {
      const res = d.has(name) ? d.get(name).split('x') : [];
      if (res.length !== 2)
         return null;
      res[0] = parseInt(res[0]);
      res[1] = parseInt(res[1]);
      return res[0] > 0 && res[1] > 0 ? res : null;
   };
   let online = (gui_kind === 'online'), nobrowser = false, drawing = false;

   if (gui_kind === 'draw')
      online = drawing = nobrowser = true;
   else if ((gui_kind === 'nobrowser') || d.has('nobrowser') || (myDiv.attr('nobrowser') && myDiv.attr('nobrowser') !== 'false'))
      nobrowser = true;

   if (myDiv.attr('ignoreurl') === 'true')
      settings.IgnoreUrlOptions = true;

   readStyleFromURL();

   if (isBatchMode())
      nobrowser = true;

   const divsize = getSize('divsize'), canvsize = getSize('canvsize'), smallpad = getSize('smallpad');
   if (divsize)
      myDiv.style('position', 'relative').style('width', divsize[0] + 'px').style('height', divsize[1] + 'px');
   else if (!isBatchMode()) {
      d3_select('html').style('height', '100%');
      d3_select('body').style('min-height', '100%').style('margin', 0).style('overflow', 'hidden');
      myDiv.style('position', 'absolute').style('left', 0).style('top', 0).style('bottom', 0).style('right', 0).style('padding', '1px');
   }
   if (canvsize) {
      settings.CanvasWidth = canvsize[0];
      settings.CanvasHeight = canvsize[1];
   }
   if (smallpad) {
      settings.SmallPad.width = smallpad[0];
      settings.SmallPad.height = smallpad[1];
   }

   const hpainter = new HierarchyPainter('root', null);
   if (online) hpainter.is_online = drawing ? 'draw' : 'online';
   if (drawing || isBatchMode())
      hpainter.exclude_browser = true;
   hpainter.start_without_browser = nobrowser;

   return hpainter.startGUI(myDiv).then(() => {
      if (!nobrowser)
         return hpainter.initializeBrowser();
      if (!drawing)
         return;
      const func = internals.getCachedObject || findFunction('GetCachedObject'),
            obj = isFunc(func) ? parse(func()) : undefined;
      if (isObject(obj))
         hpainter._cached_draw_object = obj;
      let opt = d.get('opt', '');
      if (d.has('websocket'))
         opt += ';websocket';
      return hpainter.display('', opt);
   }).then(() => hpainter);
}

export { buildGUI, internals, readStyleFromURL, HierarchyPainter, createMenu, closeMenu };