solid-app-set
Version:
Solid-compatible App set
1,314 lines (1,199 loc) • 74.1 kB
JavaScript
/* -*- coding: utf-8-dos -*-
Outline Mode Manager
*/
var YAHOO = require('./dragDrop.js')
var outlineIcons = require('./outlineIcons.js')
var UserInput = require('./userInput.js')
var UI = require('solid-ui')
var queryByExample = require('./queryByExample.js')
/* global Components, alert XPathResult sourceWidget */
// XPathResult?
module.exports = function (doc) {
var dom
if (UI.isExtension) {
var wm = Components.classes['@mozilla.org/appshell/window-mediator;1']
.getService(Components.interfaces.nsIWindowMediator)
var window = wm.getMostRecentWindow('navigator:browser')
var gBrowser = window.getBrowser()
dom = doc || window.document
} else {
// window = document.window;
dom = doc
}
this.document = doc
this.outlineIcons = outlineIcons
this.labeller = this.labeller || {}
this.labeller.LanguagePreference = '' // for now
var outline = this // Kenny: do we need this?
var thisOutline = this
var selection = []
this.selection = selection
this.ancestor = UI.utils.ancestor // make available as outline.ancestor in callbacks
this.sparql = UI.rdf.UpdateManager
this.kb = UI.store
var kb = UI.store
var sf = UI.store.fetcher
dom.outline = this
this.qs = new queryByExample.QuerySource() // Track queries in queryByExample
// var selection = [] // Array of statements which have been selected
// this.focusTd // the <td> that is being observed
this.UserInput = new UserInput(this)
this.clipboardAddress = 'tabulator:clipboard' // Weird
this.UserInput.clipboardInit(this.clipboardAddress)
var outlineElement = this.outlineElement
this.init = function () {
var table = dom.getElementById('outline')
table.outline = this
}
/** benchmark a function **/
benchmark.lastkbsize = 0
function benchmark (f) {
var args = []
for (var i = arguments.length - 1; i > 0; i--) args[i - 1] = arguments[i]
// UI.log.debug('BENCHMARK: args=' + args.join());
var begin = new Date().getTime()
var returnValue = f.apply(f, args)
var end = new Date().getTime()
UI.log.info('BENCHMARK: kb delta: ' + (kb.statements.length - benchmark.lastkbsize) + ', time elapsed for ' + f + ' was ' + (end - begin) + 'ms')
benchmark.lastkbsize = kb.statements.length
return returnValue
} // benchmark
/// ////////////////////// Representing data
// Represent an object in summary form as a table cell
function appendRemoveIcon (node, subject, removeNode) {
var image = UI.utils.AJARImage(outlineIcons.src.icon_remove_node, 'remove', undefined, dom)
image.addEventListener('click', removeNodeIconMouseDownListener)
// image.setAttribute('align', 'right') Causes icon to be moved down
image.node = removeNode
image.setAttribute('about', subject.toNT())
image.style.marginLeft = '5px'
image.style.marginRight = '10px'
// image.style.border='solid #777 1px';
node.appendChild(image)
return image
}
this.appendAccessIcons = function (kb, node, obj) {
if (obj.termType !== 'NamedNode') return
var uris = kb.uris(obj)
uris.sort()
var last = null
for (var i = 0; i < uris.length; i++) {
if (uris[i] === last) continue
last = uris[i]
thisOutline.appendAccessIcon(node, last)
}
}
this.appendAccessIcon = function (node, uri) {
if (!uri) return ''
var docuri = UI.rdf.uri.docpart(uri)
if (docuri.slice(0, 5) !== 'http:') return ''
var state = sf.getState(docuri)
var icon, alt, listener
switch (state) {
case 'unrequested':
icon = outlineIcons.src.icon_unrequested
alt = 'fetch'
listener = unrequestedIconMouseDownListener
break
case 'requested':
icon = outlineIcons.src.icon_requested
alt = 'fetching'
listener = failedIconMouseDownListener // new: can retry yello blob
break
case 'fetched':
icon = outlineIcons.src.icon_fetched
listener = fetchedIconMouseDownListener
alt = 'loaded'
break
case 'failed':
icon = outlineIcons.src.icon_failed
alt = 'failed'
listener = failedIconMouseDownListener
break
case 'unpermitted':
icon = outlineIcons.src.icon_failed
listener = failedIconMouseDownListener
alt = 'no perm'
break
case 'unfetchable':
icon = outlineIcons.src.icon_failed
listener = failedIconMouseDownListener
alt = 'cannot fetch'
break
default:
UI.log.error('?? state = ' + state)
break
} // switch
var img = UI.utils.AJARImage(icon, alt,
outlineIcons.tooltips[icon].replace(/[Tt]his resource/, docuri), dom)
img.setAttribute('uri', uri)
img.addEventListener('click', listener) // @@ seemed to be missing 2017-08
addButtonCallbacks(img, docuri)
node.appendChild(img)
return img
} // appendAccessIcon
// Six different Creative Commons Licenses:
// 1. http://creativecommons.org/licenses/by-nc-nd/3.0/
// 2. http://creativecommons.org/licenses/by-nc-sa/3.0/
// 3. http://creativecommons.org/licenses/by-nc/3.0/
// 4. http://creativecommons.org/licenses/by-nd/3.0/
// 5. http://creativecommons.org/licenses/by-sa/3.0/
// 6. http://creativecommons.org/licenses/by/3.0/
/** make the td for an object (grammatical object)
* @param obj - an RDF term
* @param view - a VIEW function (rather than a bool asImage)
**/
this.outlineObjectTD = function outlineObjectTD (obj, view, deleteNode, statement) {
var td = dom.createElement('td')
td.setAttribute('style', 'margin: 0.2em; border: none; padding: 0; vertical-align: top;')
td.setAttribute('notSelectable', 'false')
var theClass = 'obj'
// check the IPR on the data. Ok if there is any checked license which is one the document has.
if (statement && statement.why) {
if (UI.licenceOptions && (UI.licenceOptions.checkLicence())) {
theClass += ' licOkay' // flag as light green etc .licOkay {background-color: #dfd}
}
}
// set about and put 'expand' icon
if ((obj.termType === 'NamedNode') || (obj.termType === 'BlankNode') ||
(obj.termType === 'Literal' && obj.value.slice && (
obj.value.slice(0, 6) === 'ftp://' ||
obj.value.slice(0, 8) === 'https://' ||
obj.value.slice(0, 7) === 'http://'))) {
td.setAttribute('about', obj.toNT())
td.appendChild(UI.utils.AJARImage(
(UI.icons.originalIconBase + 'tbl-expand-trans.png'), 'expand', undefined, dom)).addEventListener('click', expandMouseDownListener)
}
td.setAttribute('class', theClass) // this is how you find an object
// @@ TAKE CSS OUT OF STYLE SHEET
if (kb.whether(obj, UI.ns.rdf('type'), UI.ns.link('Request'))) { td.className = 'undetermined' } // @@? why-timbl
if (!view) { // view should be a function pointer
view = viewAsBoringDefault
}
td.appendChild(view(obj))
if (deleteNode) {
appendRemoveIcon(td, obj, deleteNode)
}
try {
// new YAHOO.util.DDExternalProxy(td)
} catch (e) {
UI.log.error('YAHOO Drag and drop not supported:\n' + e)
}
// set DOM methods
td.tabulatorSelect = function () {
setSelected(this, true)
}
td.tabulatorDeselect = function () {
setSelected(this, false)
}
// td.appendChild( iconBox.construct(document.createTextNode('bla')) );
// Create an inquiry icon if there is proof about this triple
if (statement) {
var oneStatementFormula = new UI.rdf.IndexedFormula()
oneStatementFormula.statements.push(statement) // st.asFormula()
// The following works because Formula.hashString works fine for
// one statement formula
var reasons = kb.each(oneStatementFormula,
kb.sym('http://dig.csail.mit.edu/TAMI/2007/amord/tms#justification'))
if (reasons.length) {
var inquirySpan = dom.createElement('span')
if (reasons.length > 1) { inquirySpan.innerHTML = ' × ' + reasons.length }
inquirySpan.setAttribute('class', 'inquiry')
inquirySpan.insertBefore(UI.utils.AJARImage(outlineIcons.src.icon_display_reasons, 'explain', undefined, dom), inquirySpan.firstChild)
td.appendChild(inquirySpan)
}
}
td.addEventListener('click', selectableTDClickListener)
return td
} // outlineObjectTD
this.outlinePredicateTD = function outlinePredicateTD (predicate, newTr, inverse, internal) {
var predicateTD = dom.createElement('TD')
predicateTD.setAttribute('about', predicate.toNT())
predicateTD.setAttribute('class', internal ? 'pred internal' : 'pred')
switch (predicate.termType) {
case 'BlankNode': // TBD
predicateTD.className = 'undetermined'
break
case 'NamedNode':
var lab = UI.utils.predicateLabelForXML(predicate, inverse)
break
case 'Collection': // some choices of predicate
lab = UI.utils.predicateLabelForXML(predicate.elements[0], inverse)
}
lab = lab.slice(0, 1).toUpperCase() + lab.slice(1)
// if (kb.statementsMatching(predicate,rdf('type'), UI.ns.link('Request')).length) predicateTD.className='undetermined';
var labelTD = dom.createElement('TD')
labelTD.setAttribute('style', 'margin: 0.2em; border: none; padding: 0; vertical-align: top;')
labelTD.setAttribute('notSelectable', 'true')
labelTD.appendChild(dom.createTextNode(lab))
predicateTD.appendChild(labelTD)
labelTD.style.width = '100%'
predicateTD.appendChild(termWidget.construct(dom)) // termWidget is global???
for (var w in outlineIcons.termWidgets) {
if (!newTr || !newTr.AJAR_statement) break // case for TBD as predicate
// alert(Icon.termWidgets[w]+' '+Icon.termWidgets[w].filter)
if (outlineIcons.termWidgets[w].filter && outlineIcons.termWidgets[w].filter(newTr.AJAR_statement, 'pred',
inverse)) { termWidget.addIcon(predicateTD, outlineIcons.termWidgets[w]) }
}
try {
// new YAHOO.util.DDExternalProxy(predicateTD)
} catch (e) {
UI.log.error('drag and drop not supported')
}
// set DOM methods
predicateTD.tabulatorSelect = function () {
setSelected(this, true)
}
predicateTD.tabulatorDeselect = function () {
setSelected(this, false)
}
predicateTD.addEventListener('click', selectableTDClickListener)
return predicateTD
} // outlinePredicateTD
function expandedHeaderTR (subject, requiredPane, options) {
var tr = dom.createElement('tr')
tr.setAttribute('class', 'hoverControl')
var td = tr.appendChild(dom.createElement('td'))
td.setAttribute('style', 'margin: 0.2em; border: none; padding: 0; vertical-align: top;')
td.setAttribute('notSelectable', 'false')
td.setAttribute('about', subject.toNT())
td.setAttribute('colspan', '2')
var icon = td.appendChild(UI.utils.AJARImage(UI.icons.originalIconBase +
'tbl-collapse.png', 'collapse', undefined, dom))
icon.addEventListener('click', collapseMouseDownListener)
var strong = td.appendChild(dom.createElement('strong'))
strong.appendChild(dom.createTextNode(UI.utils.label(subject)))
UI.widgets.makeDraggable(strong, subject) // 2017
tr.firstPane = null
var paneNumber = 0
var relevantPanes = []
var labels = []
if (requiredPane) {
tr.firstPane = requiredPane
};
for (var i = 0; i < UI.panes.list.length; i++) {
let pane = UI.panes.list[i]
var lab = pane.label(subject, dom)
if (!lab) continue
relevantPanes.push(pane)
if (pane === requiredPane) {
paneNumber = relevantPanes.length - 1 // point to this one
}
labels.push(lab)
// steal the focus
if (!tr.firstPane && pane.shouldGetFocus && pane.shouldGetFocus(subject)) {
tr.firstPane = pane
paneNumber = relevantPanes.length - 1
UI.log.info('the ' + i + 'th pane steals the focus')
}
}
if (!relevantPanes.length) relevantPanes.push(UI.panes.internalPane)
tr.firstPane = tr.firstPane || relevantPanes[0]
if (relevantPanes.length !== 1) { // if only one, simplify interface
for (let i = 0; i < relevantPanes.length; i++) {
let pane = relevantPanes[i]
var ico = UI.utils.AJARImage(pane.icon, labels[i], labels[i], dom)
// ico.setAttribute('align','right'); @@ Should be better, but ffox bug pushes them down
ico.style.maxWidth = '24px'
ico.style.maxHeight = '24px'
var listen = function (ico, pane) { // Freeze scope for event time
ico.addEventListener('click', function (event) {
// Find the containing table for this subject
for (var t = td; t.parentNode; t = t.parentNode) {
if (t.nodeName === 'TABLE') break
}
if (t.nodeName !== 'TABLE') throw new Error('outline: internal error.')
var removePanes = function (specific) {
for (var d = t.firstChild; d; d = d.nextSibling) {
if (typeof d.pane !== 'undefined') {
if (!specific || d.pane === specific) {
if (d.paneButton) {
d.paneButton.setAttribute('class', 'paneHidden')
}
removeAndRefresh(d)
// If we just delete the node d, ffox doesn't refresh the display properly.
// state = 'paneHidden';
if (d.pane.requireQueryButton && t.parentNode.className /* outer table */ &&
numberOfPanesRequiringQueryButton === 1 && dom.getElementById('queryButton')) {
dom.getElementById('queryButton').setAttribute('style', 'display:none;')
}
}
}
}
}
var renderPane = function (pane) {
var paneDiv
UI.log.info('outline: Rendering pane (2): ' + pane.name)
if (UI.no_catch_pane_errors) { // for debugging
paneDiv = pane.render(subject, dom, options)
} else {
try {
paneDiv = pane.render(subject, dom, options)
} catch (e) { // Easier debugging for pane developers
paneDiv = dom.createElement('div')
paneDiv.setAttribute('class', 'exceptionPane')
var pre = dom.createElement('pre')
paneDiv.appendChild(pre)
pre.appendChild(dom.createTextNode(UI.utils.stackString(e)))
}
}
if (pane.requireQueryButton && dom.getElementById('queryButton')) {
dom.getElementById('queryButton').removeAttribute('style')
}
var second = t.firstChild.nextSibling
if (second) t.insertBefore(paneDiv, second)
else t.appendChild(paneDiv)
paneDiv.pane = pane
paneDiv.paneButton = ico
}
var state
state = ico.getAttribute('class')
if (state === 'paneHidden') {
if (!event.shiftKey) { // shift means multiple select
removePanes()
}
renderPane(pane)
ico.setAttribute('class', 'paneShown')
} else {
removePanes(pane)
ico.setAttribute('class', 'paneHidden')
}
// If the view already exists, remove it
state = 'paneShown'
var numberOfPanesRequiringQueryButton = 0
for (var d = t.firstChild; d; d = d.nextSibling) {
if (d.pane && d.pane.requireQueryButton) numberOfPanesRequiringQueryButton++
}
// paneEventClick();
}, false)
} // listen
listen(ico, pane)
ico.setAttribute('class', (i !== paneNumber) ? 'paneHidden' : 'paneShown')
if (i === paneNumber) tr.paneButton = ico
tr.firstChild.childNodes[1].appendChild(ico)
}
}
// set DOM methods
tr.firstChild.tabulatorSelect = function () {
setSelected(this, true)
}
tr.firstChild.tabulatorDeselect = function () {
setSelected(this, false)
}
return tr
} // expandedHeaderTR
/// //////////////////////////////////////////////////////////////////////////
/* PANES
**
** Panes are regions of the outline view in which a particular subject is
** displayed in a particular way. They are like views but views are for query results.
** subject panes are currently stacked vertically.
*/
/// //////////////////// Specific panes are in panes/*.js
//
// The defaultPaneis the first one registerd for which the label
// method
// Those registered first take priority as a default pane.
// That is, those earlier in this file
/**
* Pane registration
*/
// the second argument indicates whether the query button is required
/// ///////////////////////////////////////////////////////////////////////////
// Remove a node from the DOM so that Firefox refreshes the screen OK
// Just deleting it cause whitespace to accumulate.
function removeAndRefresh (d) {
var table = d.parentNode
var par = table.parentNode
var placeholder = dom.createElement('table')
par.replaceChild(placeholder, table)
table.removeChild(d)
par.replaceChild(table, placeholder) // Attempt to
}
var propertyTable = this.propertyTable = function propertyTable (subject, table, pane, options) {
UI.log.debug('Property table for: ' + subject)
subject = kb.canon(subject)
// if (!pane) pane = UI.panes.defaultPane;
if (!table) { // Create a new property table
table = dom.createElement('table')
var tr1 = expandedHeaderTR(subject, pane, options)
table.appendChild(tr1)
if (tr1.firstPane) {
var paneDiv
try {
UI.log.info('outline: Rendering pane (1): ' + tr1.firstPane.name)
paneDiv = tr1.firstPane.render(subject, dom, options)
} catch (e) { // Easier debugging for pane developers
paneDiv = dom.createElement('div')
paneDiv.setAttribute('class', 'exceptionPane')
var pre = dom.createElement('pre')
paneDiv.appendChild(pre)
pre.appendChild(dom.createTextNode(UI.utils.stackString(e)))
}
if (tr1.firstPane.requireQueryButton && dom.getElementById('queryButton')) {
dom.getElementById('queryButton').removeAttribute('style')
}
table.appendChild(paneDiv)
paneDiv.pane = tr1.firstPane
paneDiv.paneButton = tr1.paneButton
}
return table
} else { // New display of existing table, keeping expanded bits
UI.log.info('Re-expand: ' + table)
// do some other stuff here
return table
}
} /* propertyTable */
function propertyTR (doc, st, inverse) {
var tr = doc.createElement('TR')
tr.AJAR_statement = st
tr.AJAR_inverse = inverse
// tr.AJAR_variable = null; // @@ ?? was just 'tr.AJAR_variable'
tr.setAttribute('predTR', 'true')
var predicateTD = thisOutline.outlinePredicateTD(st.predicate, tr, inverse)
tr.appendChild(predicateTD) // @@ add 'internal' to predicateTD's class for style? mno
return tr
}
this.propertyTR = propertyTR
/// ////////// Property list
function appendPropertyTRs (parent, plist, inverse, predicateFilter) {
// UI.log.info('@appendPropertyTRs, 'this' is %s, dom is %s, '+ // Gives 'can't access dead object'
// 'thisOutline.document is %s', this, dom.location, thisOutline.document.location);
// UI.log.info('@appendPropertyTRs, dom is now ' + this.document.location);
// UI.log.info('@appendPropertyTRs, dom is now ' + thisOutline.document.location);
UI.log.debug('Property list length = ' + plist.length)
if (plist.length === 0) return ''
var sel, j, k
if (inverse) {
sel = function (x) {
return x.subject
}
plist = plist.sort(UI.utils.RDFComparePredicateSubject)
} else {
sel = function (x) {
return x.object
}
plist = plist.sort(UI.utils.RDFComparePredicateObject)
}
var max = plist.length
for (j = 0; j < max; j++) { // squishing together equivalent properties I think
var s = plist[j]
// if (s.object == parentSubject) continue; // that we knew
// Avoid predicates from other panes
if (predicateFilter && !predicateFilter(s.predicate, inverse)) continue
var tr = propertyTR(dom, s, inverse)
parent.appendChild(tr)
var predicateTD = tr.firstChild // we need to kludge the rowspan later
var defaultpropview = views.defaults[s.predicate.uri]
// LANGUAGE PREFERENCES WAS AVAILABLE WITH FF EXTENSION - get from elsewhere?
var dups = 0 // How many rows have the same predicate, -1?
var langTagged = 0 // how many objects have language tags?
var myLang = 0 // Is there one I like?
for (k = 0;
(k + j < max) && (plist[j + k].predicate.sameTerm(s.predicate)); k++) {
if (k > 0 && (sel(plist[j + k]).sameTerm(sel(plist[j + k - 1])))) dups++
if (sel(plist[j + k]).lang && outline.labeller.LanguagePreference) {
langTagged += 1
if (sel(plist[j + k]).lang.indexOf(outline.labeller.LanguagePreference) >= 0) myLang++
}
}
/* Display only the one in the preferred language
ONLY in the case (currently) when all the values are tagged.
Then we treat them as alternatives. */
if (myLang > 0 && langTagged === dups + 1) {
for (let k = j; k <= j + dups; k++) {
if (outline.labeller.LanguagePreference && sel(plist[k]).lang.indexOf(outline.labeller.LanguagePreference) >= 0) {
tr.appendChild(thisOutline.outlineObjectTD(sel(plist[k]), defaultpropview, undefined, s))
break
}
}
j += dups // extra push
continue
}
tr.appendChild(thisOutline.outlineObjectTD(sel(s), defaultpropview, undefined, s))
/* Note: showNobj shows between n to 2n objects.
* This is to prevent the case where you have a long list of objects
* shown, and dangling at the end is '1 more' (which is easily ignored)
* Therefore more objects are shown than hidden.
*/
tr.showNobj = function (n) {
var predDups = k - dups
var show = ((2 * n) < predDups) ? n : predDups
var showLaterArray = []
if (predDups !== 1) {
predicateTD.setAttribute('rowspan', (show === predDups) ? predDups : n + 1)
var l
if ((show < predDups) && (show === 1)) { // what case is this...
predicateTD.setAttribute('rowspan', 2)
}
var displayed = 0 // The number of cells generated-1,
// all duplicate thing removed
for (l = 1; l < k; l++) {
// This detects the same things
if (!kb.canon(sel(plist[j + l])).sameTerm(kb.canon(sel(plist[j + l - 1])))) {
displayed++
s = plist[j + l]
defaultpropview = views.defaults[s.predicate.uri]
var trObj = dom.createElement('tr')
trObj.style.colspan = '1'
trObj.appendChild(thisOutline.outlineObjectTD(
sel(plist[j + l]), defaultpropview, undefined, s))
trObj.AJAR_statement = s
trObj.AJAR_inverse = inverse
parent.appendChild(trObj)
if (displayed >= show) {
trObj.style.display = 'none'
showLaterArray.push(trObj)
}
} else {
// ToDo: show all the data sources of this statement
UI.log.info('there are duplicates here: %s', plist[j + l - 1])
}
}
// @@a quick fix on the messing problem.
if (show === predDups) { predicateTD.setAttribute('rowspan', displayed + 1) }
} // end of if (predDups!==1)
if (show < predDups) { // Add the x more <TR> here
var moreTR = dom.createElement('tr')
var moreTD = moreTR.appendChild(dom.createElement('td'))
moreTD.setAttribute('style', 'margin: 0.2em; border: none; padding: 0; vertical-align: top;')
moreTD.setAttribute('notSelectable', 'false')
if (predDups > n) { // what is this for??
var small = dom.createElement('a')
moreTD.appendChild(small)
var predToggle = (function (f) {
return f(predicateTD, k, dups, n)
})(function (predicateTD, k, dups, n) {
return function (display) {
small.innerHTML = ''
if (display === 'none') {
small.appendChild(UI.utils.AJARImage(UI.icons.originalIconBase + 'tbl-more-trans.png', 'more', 'See all', dom))
small.appendChild(dom.createTextNode((predDups - n) + ' more...'))
predicateTD.setAttribute('rowspan', n + 1)
} else {
small.appendChild(UI.utils.AJARImage(UI.icons.originalIconBase + 'tbl-shrink.png', '(less)', undefined, dom))
predicateTD.setAttribute('rowspan', predDups + 1)
}
for (var i = 0; i < showLaterArray.length; i++) {
var trObj = showLaterArray[i]
trObj.style.display = display
}
}
}) // ???
var current = 'none'
var toggleObj = function (event) {
predToggle(current)
current = (current === 'none') ? '' : 'none'
if (event) event.stopPropagation()
return false // what is this for?
}
toggleObj()
small.addEventListener('click', toggleObj, false)
} // if(predDups>n)
parent.appendChild(moreTR)
} // if
} // tr.showNobj
tr.showAllobj = function () {
tr.showNobj(k - dups)
}
tr.showNobj(10)
j += k - 1 // extra push
}
} // appendPropertyTRs
this.appendPropertyTRs = appendPropertyTRs
/* termWidget
**
*/
var termWidget = {} // @@@@@@ global
global.termWidget = termWidget
termWidget.construct = function (dom) {
dom = dom || document
var td = dom.createElement('TD')
td.setAttribute('style', 'margin: 0.2em; border: none; padding: 0; vertical-align: top;')
td.setAttribute('class', 'iconTD')
td.setAttribute('notSelectable', 'true')
td.style.width = '0px'
return td
}
termWidget.addIcon = function (td, icon, listener) {
var iconTD = td.childNodes[1]
if (!iconTD) return
var width = iconTD.style.width
var img = UI.utils.AJARImage(icon.src, icon.alt, icon.tooltip, dom)
width = parseInt(width)
width = width + icon.width
iconTD.style.width = width + 'px'
iconTD.appendChild(img)
if (listener) {
img.addEventListener('click', listener)
}
}
termWidget.removeIcon = function (td, icon) {
var iconTD = td.childNodes[1]
var baseURI
if (!iconTD) return
var width = iconTD.style.width
width = parseInt(width)
width = width - icon.width
iconTD.style.width = width + 'px'
for (var x = 0; x < iconTD.childNodes.length; x++) {
var elt = iconTD.childNodes[x]
var eltSrc = elt.src
// ignore first '?' and everything after it //Kenny doesn't know what this is for
try {
baseURI = dom.location.href.split('?')[0]
} catch (e) {
console.log(e)
baseURI = ''
}
var relativeIconSrc = UI.rdf.uri.join(icon.src, baseURI)
if (eltSrc === relativeIconSrc) {
iconTD.removeChild(elt)
}
}
}
termWidget.replaceIcon = function (td, oldIcon, newIcon, listener) {
termWidget.removeIcon(td, oldIcon)
termWidget.addIcon(td, newIcon, listener)
}
/// /////////////////////////////////////////////////// VALUE BROWSER VIEW
/// /////////////////////////////////////////////////////// TABLE VIEW
// Summarize a thing as a table cell
/**********************
query global vars
***********************/
// const doesn't work in Opera
// const BLANK_QUERY = { pat: kb.formula(), vars: [], orderBy: [] };
// @ pat: the query pattern in an RDFIndexedFormula. Statements are in pat.statements
// @ vars: the free variables in the query
// @ orderBy: the variables to order the table
function QueryObj () {
this.pat = kb.formula()
this.vars = []
// this.orderBy = []
}
var queries = []
queries[0] = new QueryObj()
/*
function querySave () {
queries.push(queries[0])
var choices = dom.getElementById('queryChoices')
var next = dom.createElement('option')
var box = dom.createElement('input')
var index = queries.length - 1
box.setAttribute('type', 'checkBox')
box.setAttribute('value', index)
choices.appendChild(box)
choices.appendChild(dom.createTextNode('Saved query #' + index))
choices.appendChild(dom.createElement('br'))
next.setAttribute('value', index)
next.appendChild(dom.createTextNode('Saved query #' + index))
dom.getElementById('queryJump').appendChild(next)
}
*/
/*
function resetQuery () {
function resetOutliner (pat) {
var n = pat.statements.length
var pattern, tr
for (let i = 0; i < n; i++) {
pattern = pat.statements[i]
tr = pattern.tr
// UI.log.debug('tr: ' + tr.AJAR_statement);
if (typeof tr !== 'undefined') {
delete tr.AJAR_pattern
delete tr.AJAR_variable
}
}
for (let x in pat.optional) { resetOutliner(pat.optional[x]) }
}
resetOutliner(myQuery.pat)
UI.utils.clearVariableNames()
queries[0] = myQuery = new QueryObj()
}
*/
function addButtonCallbacks (target, fireOn) {
UI.log.debug('Button callbacks for ' + fireOn + ' added')
var makeIconCallback = function (icon) {
return function IconCallback (req) {
if (req.indexOf('#') >= 0) {
console.log('@@ makeIconCallback: Not expecting # in URI whose state changed: ' + req)
// alert('Should have no hash in '+req)
}
if (!target) {
return false
}
if (!outline.ancestor(target, 'DIV')) return false
// if (term.termType != 'symbol') { return true } // should always ve
if (req === fireOn) {
target.src = icon
target.title = outlineIcons.tooltips[icon]
}
return true
}
}
sf.addCallback('request', makeIconCallback(outlineIcons.src.icon_requested))
sf.addCallback('done', makeIconCallback(outlineIcons.src.icon_fetched))
sf.addCallback('fail', makeIconCallback(outlineIcons.src.icon_failed))
}
// Selection support
function selected (node) {
var a = node.getAttribute('class')
if (a && (a.indexOf('selected') >= 0)) return true
return false
}
// These woulkd be simpler using closer variables below
function optOnIconMouseDownListener (e) { // outlineIcons.src.icon_opton needed?
var target = thisOutline.targetOf(e)
var p = target.parentNode
termWidget.replaceIcon(p.parentNode,
outlineIcons.termWidgets.optOn,
outlineIcons.termWidgets.optOff, optOffIconMouseDownListener)
p.parentNode.parentNode.removeAttribute('optional')
}
function optOffIconMouseDownListener (e) { // outlineIcons.src.icon_optoff needed?
var target = thisOutline.targetOf(e)
var p = target.parentNode
termWidget.replaceIcon(p.parentNode,
outlineIcons.termWidgets.optOff,
outlineIcons.termWidgets.optOn, optOnIconMouseDownListener)
p.parentNode.parentNode.setAttribute('optional', 'true')
}
function setSelectedParent (node, inc) {
var onIcon = outlineIcons.termWidgets.optOn
var offIcon = outlineIcons.termWidgets.optOff
for (var n = node; n.parentNode; n = n.parentNode) {
while (true) {
if (n.getAttribute('predTR')) {
var num = n.getAttribute('parentOfSelected')
if (!num) num = 0
else num = parseInt(num)
if (num === 0 && inc > 0) {
termWidget.addIcon(n.childNodes[0],
n.getAttribute('optional') ? onIcon : offIcon,
n.getAttribute('optional') ? optOnIconMouseDownListener : optOffIconMouseDownListener)
}
num = num + inc
n.setAttribute('parentOfSelected', num)
if (num === 0) {
n.removeAttribute('parentOfSelected')
termWidget.removeIcon(n.childNodes[0], n.getAttribute('optional') ? onIcon : offIcon)
}
break
} else if (n.previousSibling && n.previousSibling.nodeName === 'TR') { n = n.previousSibling } else break
}
}
}
this.statusBarClick = function (event) {
var target = UI.utils.getTarget(event)
if (target.label) {
window.content.location = target.label
// The following alternative does not work in the extension.
// var s = UI.store.sym(target.label);
// outline.GotoSubject(s, true);
}
}
this.showURI = function showURI (about) {
if (about && dom.getElementById('UserURI')) {
dom.getElementById('UserURI').value =
(about.termType === 'NamedNode') ? about.uri : '' // blank if no URI
} else if (about && UI.isExtension) {
var tabStatusBar = gBrowser.ownerDocument.getElementById('tabulator-display')
tabStatusBar.setAttribute('style', 'display:block')
tabStatusBar.label = (about.termType === 'NamedNode') ? about.uri : '' // blank if no URI
if (tabStatusBar.label === '') {
tabStatusBar.setAttribute('style', 'display:none')
} else {
tabStatusBar.addEventListener('click', this.statusBarClick, false)
}
}
}
this.showSource = function showSource () {
if (typeof sourceWidget === 'undefined') return
// deselect all before going on, this is necessary because you would switch tab,
// close tab or so on...
for (var uri in sourceWidget.sources) { sourceWidget.sources[uri].setAttribute('class', '') } // .class doesn't work. Be careful!
for (var i = 0; i < selection.length; i++) {
if (!selection[i].parentNode) {
console.log('showSource: EH? no parentNode? ' + selection[i] + '\n')
continue
}
var st = selection[i].parentNode.AJAR_statement
if (!st) continue // for root TD
var source = st.why
if (source && source.uri) {
sourceWidget.highlight(source, true)
} else if (UI.isExtension && source.termType === 'BlankNode') {
sourceWidget.highlight(kb.sym('resource://tabulator/'), true) // see extension
}
}
}
this.getSelection = function getSelection () {
return selection
}
function setSelected (node, newValue) {
// UI.log.info('selection has ' +selection.map(function(item){return item.textContent;}).join(', '));
// UI.log.debug('@outline setSelected, intended to '+(newValue?'select ':'deselect ')+node+node.textContent);
// if (newValue === selected(node)) return; //we might not need this anymore...
if (node.nodeName !== 'TD') {
UI.log.debug('down' + node.nodeName)
throw new Error('Expected TD in setSelected: ' + node.nodeName + ' : ' + node.textContent)
}
UI.log.debug('pass')
var cla = node.getAttribute('class')
if (!cla) cla = ''
if (newValue) {
cla += ' selected'
if (cla.indexOf('pred') >= 0 || cla.indexOf('obj') >= 0) setSelectedParent(node, 1)
selection.push(node)
// UI.log.info('Selecting '+node.textContent)
var about = UI.utils.getTerm(node) // show uri for a newly selectedTd
thisOutline.showURI(about)
// if(UI.isExtension && about && about.termType=='NamedNode') gURLBar.value = about.uri;
// about==null when node is a TBD
var st = node.AJAR_statement // show blue cross when the why of that triple is editable
if (typeof st === 'undefined') st = node.parentNode.AJAR_statement
// if (typeof st === 'undefined') return; // @@ Kludge? Click in the middle of nowhere
if (st) { // don't do these for headers or base nodes
var source = st.why
// var target = st.why
var editable = UI.store.updater.editable(source.uri, kb)
if (!editable) {
// let target = node.parentNode.AJAR_inverse ? st.object : st.subject
} // left hand side
// think about this later. Because we update to the why for now.
// alert('Target='+target+', editable='+editable+'\nselected statement:' + st)
if (editable && (cla.indexOf('pred') >= 0)) { termWidget.addIcon(node, outlineIcons.termWidgets.addTri) } // Add blue plus
}
} else {
UI.log.debug('cla=$' + cla + '$')
if (cla === 'selected') cla = '' // for header <TD>
cla = cla.replace(' selected', '')
if (cla.indexOf('pred') >= 0 || cla.indexOf('obj') >= 0) setSelectedParent(node, -1)
if (cla.indexOf('pred') >= 0) { termWidget.removeIcon(node, outlineIcons.termWidgets.addTri) }
selection = selection.filter(function (x) {
return x === node
})
UI.log.info('Deselecting ' + node.textContent)
}
if (typeof sourceWidget !== 'undefined') thisOutline.showSource() // Update the data sources display
// UI.log.info('selection becomes [' +selection.map(function(item){return item.textContent;}).join(', ')+']');
// UI.log.info('Setting className ' + cla);
node.setAttribute('class', cla)
}
function deselectAll () {
var n = selection.length
for (let i = n - 1; i >= 0; i--) setSelected(selection[i], false)
selection = []
}
/** Get the target of an event **/
this.targetOf = function (e) {
var target
if (!e) e = window.event
if (e.target) { target = e.target } else if (e.srcElement) { target = e.srcElement } else {
UI.log.error('can\'t get target for event ' + e)
return false
} // fail
if (target.nodeType === 3) { // defeat Safari bug [sic]
target = target.parentNode
}
return target
} // targetOf
this.walk = function walk (directionCode, inputTd) {
var selectedTd = inputTd || selection[0]
var newSelTd
switch (directionCode) {
case 'down':
try {
newSelTd = selectedTd.parentNode.nextSibling.lastChild
} catch (e) {
this.walk('up')
return
} // end
deselectAll()
setSelected(newSelTd, true)
break
case 'up':
try {
newSelTd = selectedTd.parentNode.previousSibling.lastChild
} catch (e) {
return
} // top
deselectAll()
setSelected(newSelTd, true)
break
case 'right':
deselectAll()
if (selectedTd.nextSibling || selectedTd.lastChild.tagName === 'strong') { setSelected(selectedTd.nextSibling, true) } else {
var newSelected = dom.evaluate('table/div/tr/td[2]', selectedTd,
null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
setSelected(newSelected, true)
}
break
case 'left':
deselectAll()
if (selectedTd.previousSibling && selectedTd.previousSibling.className === 'undetermined') {
setSelected(selectedTd.previousSibling, true)
return true // do not shrink signal
} else { setSelected(UI.utils.ancestor(selectedTd.parentNode, 'TD'), true) } // supplied by thieOutline.focusTd
break
case 'moveTo':
// UI.log.info(selection[0].textContent+'->'+inputTd.textContent);
deselectAll()
setSelected(inputTd, true)
break
}
if (directionCode === 'down' || directionCode === 'up') { if (!newSelTd.tabulatorSelect) this.walk(directionCode) }
// return newSelTd;
}
// Keyboard Input: we can consider this as...
// 1. a fast way to modify data - enter will go to next predicate
// 2. an alternative way to input - enter at the end of a predicate will create a new statement
this.OutlinerKeypressPanel = function OutlinerKeypressPanel (e) {
UI.log.info('Key ' + e.keyCode + ' pressed')
function showURI (about) {
if (about && dom.getElementById('UserURI')) {
dom.getElementById('UserURI').value =
(about.termType === 'NamedNode') ? about.uri : '' // blank if no URI
}
}
function setSelectedAfterward (uri) {
if (arguments[3]) return true
walk('right', selectedTd)
showURI(UI.utils.getAbout(kb, selection[0]))
return true
}
var target, editable
if (UI.utils.getTarget(e).tagName === 'TEXTAREA') return
if (UI.utils.getTarget(e).id === 'UserURI') return
if (selection.length > 1) return
if (selection.length === 0) {
if (e.keyCode === 13 || e.keyCode === 38 || e.keyCode === 40 || e.keyCode === 37 || e.keyCode === 39) {
this.walk('right', thisOutline.focusTd)
showURI(UI.utils.getAbout(kb, selection[0]))
}
return
}
var selectedTd = selection[0]
// if not done, Have to deal with redraw...
sf.removeCallback('done', 'setSelectedAfterward')
sf.removeCallback('fail', 'setSelectedAfterward')
switch (e.keyCode) {
case 13: // enter
if (UI.utils.getTarget(e).tagName === 'HTML') { // I don't know why 'HTML'
var object = UI.utils.getAbout(kb, selectedTd)
target = selectedTd.parentNode.AJAR_statement.why
editable = UI.store.updater.editable(target.uri, kb)
if (object) {
// <Feature about='enterToExpand'>
outline.GotoSubject(object, true)
/* //deal with this later
deselectAll();
var newTr=dom.getElementById('outline').lastChild;
setSelected(newTr.firstChild.firstChild.childNodes[1].lastChild,true);
function setSelectedAfterward(uri){
deselectAll();
setSelected(newTr.firstChild.firstChild.childNodes[1].lastChild,true);
showURI(getAbout(kb,selection[0]));
return true;
}
sf.insertCallback('done',setSelectedAfterward);
sf.insertCallback('fail',setSelectedAfterward);
*/
// </Feature>
} else if (editable) { // this is a text node and editable
thisOutline.UserInput.Enter(selectedTd)
}
} else {
// var newSelTd=thisOutline.UserInput.lastModified.parentNode.parentNode.nextSibling.lastChild;
this.UserInput.Keypress(e)
this.walk('down') // bug with input at the end
// dom.getElementById('docHTML').focus(); //have to set this or focus blurs
e.stopPropagation()
}
return
case 38: // up
// thisOutline.UserInput.clearInputAndSave();
// ^^^ does not work because up and down not captured...
this.walk('up')
e.stopPropagation()
e.preventDefault()
break
case 40: // down
// thisOutline.UserInput.clearInputAndSave();
this.walk('down')
e.stopPropagation()
e.preventDefault()
} // switch
if (UI.utils.getTarget(e).tagName === 'INPUT') return
switch (e.keyCode) {
case 46: // delete
case 8: // backspace
target = selectedTd.parentNode.AJAR_statement.why
editable = UI.store.updater.editable(target.uri, kb)
if (editable) {
e.preventDefault() // prevent from going back
this.UserInput.Delete(selectedTd)
}
break
case 37: // left
if (this.walk('left')) return
var titleTd = UI.utils.ancestor(selectedTd.parentNode, 'TD')
outlineCollapse(selectedTd, UI.utils.getAbout(kb, titleTd))
break
case 39: // right
let obj = UI.utils.getAbout(kb, selectedTd)
if (obj) {
var walk = this.walk
if (selectedTd.nextSibling) { // when selectedTd is a predicate
this.walk('right')
return
}
if (selectedTd.firstChild.tagName !== 'TABLE') { // not expanded
sf.addCallback('done', setSelectedAfterward)
sf.addCallback('fail', setSelectedAfterward)
outlineExpand(selectedTd, obj, {
'pane': UI.panes.defaultPane
})
}
setSelectedAfterward()
}
break
case 38: // up
case 40: // down
break
default:
switch (e.charCode) {
case 99: // c for Copy
if (e.ctrlKey) {
thisOutline.UserInput.copyToClipboard(thisOutline.clipboardAddress, selectedTd)
break
}
break
case 118: // v
case 112: // p for Paste
if (e.ctrlKey) {
thisOutline.UserInput.pasteFromClipboard(thisOutline.clipboardAddress, selectedTd)
// dom.getElementById('docHTML').focus(); //have to set this or focus blurs
// window.focus();
// e.stopPropagation();
break
}
break
default:
if (UI.utils.getTarget(e).tagName === 'HTML') {
/*
//<Feature about='typeOnSelectedToInput'>
thisOutline.UserInput.Click(e,selectedTd);
thisOutline.UserInput.lastModified.value=String.fromCharCode(e.charCode);
if (selectedTd.className==='undetermined selected') thisOutline.UserInput.AutoComplete(e.charCode)
//</Feature>
*/
// Events are not reliable...
// var e2=document.createEvent('KeyboardEvent');
// e2.initKeyEvent('keypress',true,true,null,false,false,false,false,e.keyCode,0);
// UserInput.lastModified.dispatchEvent(e2);
}
}
} // end of switch
showURI(UI.utils.getAbout(kb, selection[0]))
// alert(window);alert(doc);
/*
var wm = Components.classes['@mozilla.org/appshell/window-mediator;1']
.getService(Components.interfaces.nsIWindowMediator);
var gBrowser = wm.getMostRecentWindow('navigator:browser') */
// gBrowser.addTab('http://www.w3.org/');
// alert(gBrowser.addTab);alert(gBrowser.scroll);alert(gBrowser.scrollBy)
// gBrowser.scrollBy(0,100);
// var thisHtml=selection[0].owner
if (selection[0]) {
var PosY = UI.utils.findPos(selection[0])[1]
if (PosY + selection[0].clientHeight > window.scrollY + window.innerHeight) UI.utils.getEyeFocus(selection[0], true, true, window)
if (PosY < window.scrollY + 54) UI.utils.getEyeFocus(selection[0], true, undefined, window)
}
}
this.OutlinerMouseclickPanel = function (e) {
switch (thisOutline.UserInput._tabulatorMode) {
case 0:
TabulatorMousedown(e)
break
case 1:
thisOutline.UserInput.Click(e)
break
default:
}
}
/** things to do onmousedown in outline view **/
/*
** To Do: This big event hander needs to be replaced by lots
** of little ones individually connected to each icon. This horrible
** switch below isn't modular. (Sorry!) - Tim
*/
// expand
// collapse
// refocus
// select
// visit/open a page
function expandMouseDownListener (e) { // For icon (UI.icons.originalIconBase + 'tbl-expand-trans.png')
var target = thisOutline.targetOf(e)
var p = target.parentNode
var subject = UI.utils.getAbout(kb, target)
var pane = e.altKey ? UI.panes.internalPane : undefined // set later: was UI.panes.defaultPane
if (e.shiftKey) { // Shift forces a refocuss - bring this to the top
outlineRefocus(p, subject, pane)
} else {
if (e.altKey) { // To investigate screwups, dont wait show internals
outlineExpand(p, subject, {
'pane': UI.panes.internalPane,
'immediate': true
})
} else {
outlineExpand(p, subject)
}
}
}
function collapseMouseDownListener (e) { // for icon UI.icons.originalIconBase + 'tbl-collapse.png'
var target = thisOutline.targetOf(e)
var subject = UI.utils.getAbout(kb, target)
var pane = e.altKey ? UI.panes.internalPane : undefined
var p = target.parentNode
outlineCollapse(p, subject, pane)
}
function failedIconMouseDownListener (e) { // outlineIcons.src.icon_failed
var target = thisOutline.targetOf(e)
var uri = target.getAttribute('uri') // Put on access buttons
if (e.altKey) {
sf.fetch(UI.rdf.uri.docpart(uri), {
'force': true
}) // Add 'force' bit?
} else {
sf.refresh(kb.sym(UI.rdf.uri.docpart(uri))) // just one
}
}
function fetchedIconMouseDownListener (e) { // outlineIcons.src.icon_fetched
var target = thisOutline.targetOf(e)
var uri = target.getAttribute('uri') // Put on access buttons
if (e.altKey) {
sf.fetch(UI.rdf.uri.docpart(uri), {
'force': true
})
} else {
sf.refresh(kb.sym(UI.rdf.uri.docpart(uri))) // just one
}
}
function unrequestedIconMouseDownListener (e) {
var target = thisOutline.targetOf(e)
var uri = target.getAttribute('uri') // Put on access buttons
sf.fetch(UI.rdf.uri.docpart(uri))
}
function removeNodeIconMouseDownListener (e) { // icon_remove_node
var target = thisOutline.targetOf(e)
var node = target.node
if (node.childNodes.length > 1) node = target.parentNode // parallel outline view @@ Hack
removeAndRefresh(node) // @@ update icons for pane?
}
function selectableTDClickListener (e) {
// Is we are in editing mode already
if (thisOutline.UserInput._tabulatorMode) {
return thisOutline.UserInput.Click(e)
}