<?php
/**
* Copyright (C) 2018-2023 Graham Breach
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
* For more information, please contact <[email protected]>
*/
namespace Goat1000\SVGGraph;
/**
* Right-click (or long touch) context menu
*/
class ContextMenu {
private $graph;
private $js;
private $function_added = false;
private $callback;
private $use_structure = false;
private $namespace = '';
/**
* Constructor sets up options and root menu
*/
public function __construct(&$graph, &$javascript)
{
if(!$graph->getOption('show_context_menu'))
return;
$this->graph =& $graph;
$this->js =& $javascript;
$this->callback = $graph->getOption('context_callback');
$structure = $graph->getOption('structure');
if(is_array($structure) && isset($structure['context_menu']))
$this->use_structure = true;
if($graph->getOption('namespace'))
$this->namespace = 'svg:';
$global = $graph->getOption('context_global');
if($global !== false) {
if($global === null)
$global = [ [SVGGraph::VERSION, null] ];
$entries = '';
foreach($global as $entry) {
$attr = ['name' => $entry[0], 'link' => $entry[1]];
$entries .= $graph->element('svggraph:menuitem', $attr);
}
$menu = $graph->element('svggraph:menu', null, null, $entries);
$xml = $graph->element('svggraph:data',
['xmlns:svggraph' => 'http://www.goat1000.com/svggraph'], null, $menu);
$graph->defs->add($xml);
}
}
/**
* Adds the javascript function
*/
public function addFunction()
{
$this->js->addFuncs('getE', 'finditem', 'newel', 'newtext',
'svgNode', 'setattr', 'getData', 'svgCursorCoords');
$this->js->addInitFunction('contextMenuInit');
$opts = ['link_target', 'link_underline', 'stroke_width', 'round', 'font',
'font_weight', 'document_menu', 'spacing', 'min_width',
'shadow_opacity', 'mouseleave'];
$colours = ['colour', 'link_colour', 'link_hover_colour', 'back_colour'];
$vars = [];
foreach($opts as $opt)
$vars[$opt] = $this->graph->getOption('context_' . $opt);
$vars['font_size'] = Number::units($this->graph->getOption('context_font_size'));
foreach($colours as $opt)
$vars[$opt] = new Colour($this->graph, $this->graph->getOption('context_' . $opt));
$svg_text = new Text($this->graph, $vars['font']);
list(, $text_height) = $svg_text->measure('Test', $vars['font_size']);
$text_baseline = $svg_text->baseline($vars['font_size']);
$vars['pad_x'] = $this->graph->getOption('context_padding_x', 'context_padding');
$vars['pad_y'] = $this->graph->getOption('context_padding_y', 'context_padding');
$vars['text_start'] = $vars['pad_y'] + $text_baseline;
$vars['rect_start'] = $vars['pad_y'] - $vars['spacing'] / 2;
$vars['spacing'] += $text_height;
$vars['round_part'] = $vars['mouseleave'] = $vars['underline_part'] = '';
if($vars['link_underline'])
$vars['underline_part'] = ", 'text-decoration': 'underline'";
if($vars['round']) {
$rnum = new Number($vars['round']);
$vars['round_part'] = ', rx:"' . $rnum . 'px", ry:"' . $rnum . 'px"';
}
$cmoffs = 0;
$half_stroke = $vars['stroke_width'] / 2;
$vars['pad_x'] += $half_stroke;
$vars['pad_y'] += $half_stroke;
$vars['off_right'] = $vars['stroke_width'];
$vars['off_bottom'] = $vars['stroke_width'];
if(is_numeric($vars['shadow_opacity'])) {
$cmoffs = 4;
$vars['off_right'] += $cmoffs;
$vars['off_bottom'] += $cmoffs;
}
$vars['cmoffs'] = $cmoffs;
if($vars['document_menu']) {
$this->js->insertFunction('rootContextMenu',
"function rootContextMenu(){closeContextMenu();}\n");
} else {
$this->js->insertTemplate('rootContextMenu');
}
if((int)$vars['mouseleave'] > 0) {
$mlnum = new Number($mouseleave);
$vars['mouseleave'] = 'e[c].addEventListener("mouseleave",function(e) {' .
'setTimeout(closeContextMenu,' . $mlnum . ');}, false);';
}
$vars['namespace'] = $this->namespace;
$this->js->insertTemplate('contextMenu', $vars);
$this->function_added = true;
}
/**
* Adds context menu for item
*/
public function setMenu(&$element, $dataset, &$item, $duplicate = false)
{
$menu = null;
if(is_callable($this->callback)) {
$menu = call_user_func($this->callback, $dataset, $item->key, $item->value);
} elseif($this->use_structure) {
$menu = $item->context_menu;
}
if(is_array($menu)) {
if(!isset($element['id']))
$element['id'] = $this->graph->newID();
$var = json_encode($menu);
$this->js->insertVariable('menus', $element['id'], $var, false);
if($duplicate)
$this->js->addOverlay($element['id'], $this->graph->newID());
} else {
// add a placeholder to make sure the variable exists
$ignore_id = $this->graph->newID();
$this->js->insertVariable('menus', $ignore_id, "''", false);
}
// set up menus after duplication
if(!$this->function_added)
$this->addFunction();
}
}
|