PDA

View Full Version : Project: Hypertext links



celestian
December 14th, 2020, 23:03
I've been planning to do this for a while to help reduce overhead in FG for various record lists. The project centers around the idea of having a simple string text field listing the names of the objects you want to have clickable from and open up a window.

I want to make the template generic enough so that I can use it for any records (skills, attacks, powers, languages, npcs, etc) so that it can be used practically anywhere. The reason being is that creating controls for all the parts of records uses various resources. Sometimes that can bog down FG depending on record count. This will allow a developer to only use a single string for all of the records and still have functionality. Perhaps in the future this won't be all that useful (Woot!) but for now I've a need and I suspect rulesets like PF could as well.

Hopefully others can find this useful.

I will update the code snippets here as I improve upon the various pieces of the code. The requirements are: must have datasource set (it's relative to the current window using the template). Must have activatewindow set (the window used when clicking on the highlighted text). Must have a "name" field in the records to list.

So far this seems to work fairly well.

Here is the current template.


<template name="hyperlink_text">
<stringcontrol name="hypertext">
<script file="common/scripts/hyperlink_text.lua" />
<font>sheettext</font>
<lineoffset default="on" mergerule="replace">1</lineoffset>
<cursor hover="arrow" />
<readonly />
</stringcontrol>
</template>


Here is the current code that associates the template.


--
--
--
--
--

---
-- Script to manage text that can be clicked on an open a record window
---

-- when using an array
aSourceRecords = {}
-- list of entries and their locations
aHyperEntries = {}

dragging = false;
local hoverOnEntry = nil;
local clickOnEntry = nil;

-- options in the xml
local sActivateWindow = nil; -- <activatewindow>
local dataSource = nil; -- <datasource>
local sTooltipField = nil -- <tooltipfield>
local bOpenOnMouse = true; -- <openonmouse>
local sLabelText = nil; -- <labeltext>
---

function onInit()
--Debug.console("hyperlink_Text.lua","onInit");
-- Debug.console("hyperlink_Text.lua","onInit","datasource",datasource);

-- use activatewindow option if set
if activatewindow and activatewindow[1] then
sActivateWindow = activatewindow[1];
end
-- use datasource option if set
if datasource and datasource[1] then
dataSource = datasource[1];
end
-- use labeltext option if set
if labeltext and labeltext[1] then
sLabelText = labeltext[1];
end
-- use tooltipfield option if set
if tooltipfield and tooltipfield[1] then
sTooltipField = tooltipfield[1];
end
-- use datasource option if set
if datasource and datasource[1] then
dataSource = datasource[1];
end
--

if dataSource and sActivateWindow then
--local node = getDatabaseNode().getParent();
--Debug.console("hyperlink_Text.lua","onInit","node",node);

refreshText();
else
--Debug.chat("ERROR: hyperlink_text template requires activation window and a datasource.")
end
end

function onClose()
--Debug.console("hyperlink_Text.lua","onClose");
end

--[[

Rebuild text list

]]--
function refreshText()
local node = window.getDatabaseNode();
local sText = "";
local nCount = 0;
local aList = aSourceRecords;
if (#aSourceRecords < 1) and node and dataSource then
aList = DB.getChildren(node, dataSource);
end

for _,nodeEntry in pairs(aList) do
local rRecord = {};

local sName = DB.getValue(nodeEntry,"name","no-name-set");
local sTooltip = "";
if sTooltipField then
sTooltip = DB.getValue(nodeEntry,sTooltipField,"");
end
if sTooltip then
rRecord.tooltiptext = sTooltip;
end
rRecord.name = sName;

local sSep = "";
if sText:len() > 0 then
sSep = ", ";
end
-- stick a label if in they defined one
if sLabelText and sText:len() < 1 then
sText = sLabelText;
end

rRecord.recordpath = nodeEntry.getPath();

sText = sText .. sSep;

rRecord.nStart = sText:len();

sText = sText .. sName;

rRecord.nEnd = sText:len() + 1;

nCount = nCount + 1;
rRecord.index = nCount;
table.insert(aHyperEntries,nCount,rRecord);
end
if sText == "" then
sText = "Empty";
end
-- initialize value
setValue(sText);
end

--[[
When hovering over the template control
]]
function onHover(oncontrol)
if dragging then
return;
end

-- Clear selection when no longer hovering over us
if not oncontrol then
-- Clear hover tracking
hoverOnEntry = nil;

-- Clear any selections
setSelectionPosition(0);
setTooltipText('');
end
end
--[[
When hovering over the control and mouse moves
]]
function onHoverUpdate(x, y)
-- we do not mess about when we are dragging a record
if dragging then
return;
end

-- store the index of the mouse position
local nMouseIndex = getIndexAt(x, y);
-- Clear any memory of the last hover update
hoverOnEntry = nil;

-- Find the entry they have hovered over
if #aHyperEntries > 0 then
for _, v in pairs(aHyperEntries) do
if (nMouseIndex >= v.nStart) and (nMouseIndex <= v.nEnd) then
hoverOnEntry = v.recordpath;
if v.tooltiptext then
setTooltipText(UtilityManagerADND.stripFormattedTe xt(v.tooltiptext));
else
setTooltipText('');
end
setCursorPosition(v.nStart);
setSelectionPosition(v.nEnd);
break;
end
end
end

-- Reset the cursor
setHoverCursor("arrow");
end

--[[
When clicking on the highlighted text run this
]]
function onClickDown(button, x, y)
if hoverOnEntry then
clickOnEntry = hoverOnEntry;
end
end

function onClickRelease(button, x, y)
if hoverOnEntry then
clickOnEntry = hoverOnEntry;
local win = Interface.openWindow(sActivateWindow,DB.findNode(h overOnEntry));
if win then
if bOpenOnMouse then
win.setPosition(x,y);
end
return true;
end
end
return false;
end
function onDoubleClick(x, y)
return onClickRelease(nil,x, y);
end


--[[
Not implemented yet
]]
function onDragStart(button, x, y, draginfo)
dragging = false;
if clickOnEntry then
draginfo.setShortcutData(sActivateWindow,clickOnEn try);
draginfo.setType("shortcut");
draginfo.setDescription(DB.getValue(DB.findNode(cl ickOnEntry),"name"));
draginfo.setIcon("button_link");

local base = draginfo.createBaseData();
base.setShortcutData(sActivateWindow,clickOnEntry) ;

dragging = true;
end
return dragging;
end
function onDragEnd(dragdata)
setCursorPosition(0);
setTooltipText('');
dragging = false;
end

--[[
Add the entire table
]]
function setTable(aTable)
aSourceRecords = aTable;
refreshText();
end

--[[
Convenience function to sort by name
]]
function sortByNames(aTable)
local function sortByName(a,b)
return DB.getValue(a,"name","") < DB.getValue(b,"name","");
end
local aSorted = {};
for _,v in pairs(aTable) do
table.insert(aSorted,v);
end
table.sort(aSorted,sortByName);
return aSorted;
end

--[[
set the activation window class
]]
function setActivateWindow(sWindowClass)
if sWindowClass then
sActivateWindow = sWindowClass;
refreshText();
end
end
--[[
set the tooltip text field
]]
function setTooltipTextField(sTTField)
if sTTField then
sTooltipField = sTTField;
refreshText();
end
end


Here is an example entry in a window that uses the template.


<hyperlink_text name="ability_notes">
<anchored>
<top parent="columnanchor" anchor="bottom" relation="relative" offset="9" />
<left offset="10" />
<right offset="-5" />
</anchored>
<activatewindow>quicknote</activatewindow>
<datasource>.abilitynoteslist</datasource>
<tooltipfield>text</tooltipfield>
<openonmouse>true</openonmouse>
<multilinespacing>16</multilinespacing>
<font>sheetlabelmini</font>
<invisible />
</hyperlink_text>


Example in the 2E ruleset.
https://i.imgur.com/VJE0bar.gif

celestian
December 14th, 2020, 23:28
I'm going to experiment with using a table method instead of a datasource. The reason for that is that then you can use any record types and not have to worry about the datasource path. You can still use a data point, you will just flip through the list as you need and put it into an array and set that during initialization.

damned
December 15th, 2020, 01:08
Keenly watching this.

celestian
December 21st, 2020, 18:28
I've updated the template and the source. I added some functions that could be used incase you need to manipulate the datasource instead of just pointing it at a node (specifically if you need to sort it a particular way).

For example, you could comment out the <datasource>... section and use something like this:



function onInit()
buildAbilityNotes();
end

function buildAbilityNotes()
local aTable = DB.getChildren(getDatabaseNode(), "abilitynoteslist");
aTable = ability_notes.sortByNames(aTable);
ability_notes.setTable(aTable);
end


You can also sort by names using the windowlist functionality using <sortby><field>name</field></sortby> but if you needed a specific sorting or some other reason to tweak the records listing the options are many and varied.

I ended up writting a customized version of the hypertext to work with actions/powers for the 2E ruleset and used it for the new CT for the DM in the next version. It required a bit more unique complexity than I could "generalize" than I had hoped. However, for a general purpose and "howto" this should be very useful. If you want to have a list of items and pop up a window when clicked this should work well.

Milmoor
December 21st, 2020, 18:59
Very interesting. I'll keep an eye on this.

LordEntrails
December 21st, 2020, 20:14
Cool.

Don't know if this applies, but thought I would mention it... With the current actions parsing and highlighting of actions, it makes editing actions (E for example) difficult since the mouse/cursor behavior changes with the recognized action terms. Anyway, if you can avoid that with your hyperlinked text, it would be a benefit :)

celestian
December 21st, 2020, 22:01
Cool.

Don't know if this applies, but thought I would mention it... With the current actions parsing and highlighting of actions, it makes editing actions (E for example) difficult since the mouse/cursor behavior changes with the recognized action terms. Anyway, if you can avoid that with your hyperlinked text, it would be a benefit :)

The style I use preclude that sorta behavior. You'd need to add a [Edit] option that would popup a window to edit the associated record. This style is based around the records, not the text generating a record or method. At least that's how this works and how I went with the entries in the 2E ruleset. I find having to use a markup language is beyond a lot of users, certainly most that just want to use the platform.