510 lines
14 KiB
Perl
510 lines
14 KiB
Perl
|
/* Part of SWI-Prolog
|
||
|
|
||
|
Author: Jan Wielemaker
|
||
|
E-mail: J.Wielemaker@uva.nl
|
||
|
WWW: http://www.swi-prolog.org
|
||
|
Copyright (C): 2010, University of Amsterdam
|
||
|
|
||
|
This program 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 2
|
||
|
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 General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public
|
||
|
License along with this library; if not, write to the Free Software
|
||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||
|
|
||
|
As a special exception, if you link this library with other files,
|
||
|
compiled with a Free Software compiler, to produce an executable, this
|
||
|
library does not by itself cause the resulting executable to be covered
|
||
|
by the GNU General Public License. This exception does not however
|
||
|
invalidate any other reasons why the executable file might be covered by
|
||
|
the GNU General Public License.
|
||
|
*/
|
||
|
|
||
|
:- module(rdf_diagram,
|
||
|
[ rdf_diagram_from_file/1 % +File
|
||
|
]).
|
||
|
:- use_module(library(pce)).
|
||
|
:- use_module(library(pce_tagged_connection)).
|
||
|
:- use_module(library(autowin)).
|
||
|
:- use_module(library(pce_report)).
|
||
|
:- use_module(library(print_graphics)).
|
||
|
:- use_module(library(rdf_parser)). % get access to declared namespaces
|
||
|
|
||
|
|
||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||
|
This file defines the class rdf_diagram, a window capable of showing a
|
||
|
set of triples.
|
||
|
|
||
|
The predicate rdf_diagram_from_file(+File) is a simple demo and useful
|
||
|
tool to show RDF from simple RDF files.
|
||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||
|
|
||
|
|
||
|
/*******************************
|
||
|
* SIMPLE ENTRY *
|
||
|
*******************************/
|
||
|
|
||
|
% rdf_diagram_from_file(+File)
|
||
|
%
|
||
|
% Show the triples from File in a window.
|
||
|
|
||
|
rdf_diagram_from_file(File) :-
|
||
|
absolute_file_name(File,
|
||
|
[ access(read),
|
||
|
extensions([rdf,rdfs,owl,''])
|
||
|
], AbsFile),
|
||
|
load_rdf(AbsFile, Triples,
|
||
|
[ expand_foreach(true)
|
||
|
]),
|
||
|
new(D, rdf_diagram(string('RDF diagram for %s', File))),
|
||
|
send(new(report_dialog), below, D),
|
||
|
send(D, triples, Triples),
|
||
|
send(D, open).
|
||
|
|
||
|
|
||
|
/*******************************
|
||
|
* CLASS RDF-DIAGRAM *
|
||
|
*******************************/
|
||
|
|
||
|
:- pce_begin_class(rdf_diagram, auto_sized_picture,
|
||
|
"Show set of RDF triples in a window").
|
||
|
:- use_class_template(print_graphics).
|
||
|
|
||
|
variable(auto_layout, bool := @on, both, "Automatically layout on resize").
|
||
|
variable(type_in_node, bool := @on, both, "Display type inside node").
|
||
|
|
||
|
initialise(D, Label:[name]) :->
|
||
|
send_super(D, initialise, Label),
|
||
|
send(D, scrollbars, both),
|
||
|
send(D, fill_popup),
|
||
|
send(D, resize_message,
|
||
|
if(and(D?auto_layout == @on,
|
||
|
D?focus_recogniser == @nil),
|
||
|
message(D, layout))).
|
||
|
|
||
|
fill_popup(D) :->
|
||
|
send(D, popup, new(P, popup)),
|
||
|
send_list(P, append,
|
||
|
[ menu_item(layout, message(D, layout)),
|
||
|
gap,
|
||
|
menu_item(print, message(D, print))
|
||
|
]).
|
||
|
|
||
|
:- pce_group(triples).
|
||
|
|
||
|
append(D, Triple:prolog) :->
|
||
|
"Append and rdf(Subject, Predicate, Object) triple"::
|
||
|
( subject_name(Triple, SubjectName),
|
||
|
get(D, resource, SubjectName, Subject),
|
||
|
( get(D, type_in_node, @on),
|
||
|
is_type(Triple)
|
||
|
-> object_resource(Triple, ObjectName),
|
||
|
send(Subject, type, ObjectName)
|
||
|
; predicate_name(Triple, PredName),
|
||
|
( object_resource(Triple, ObjectName)
|
||
|
-> get(D, resource, ObjectName, Object)
|
||
|
; object_literal(Triple, Literal)
|
||
|
-> get(D, literal, Literal, Object)
|
||
|
),
|
||
|
send(Subject, connect, PredName, Object)
|
||
|
)
|
||
|
-> true
|
||
|
; term_to_atom(Triple, Atom),
|
||
|
ignore(send(D, report, error,
|
||
|
'Failed to display triple: %s', Atom))
|
||
|
).
|
||
|
|
||
|
triples(D, Triples:prolog) :->
|
||
|
"Show disgram from Prolog triples"::
|
||
|
send(D, clear),
|
||
|
forall(member(T, Triples),
|
||
|
send(D, append, T)),
|
||
|
send(D, layout).
|
||
|
|
||
|
resource(D, Resource:name) :->
|
||
|
"Add Resource to diagram"::
|
||
|
get(D, resource, Resource, @on, _).
|
||
|
|
||
|
resource(D, Resource:name, Create:[bool], Subject:rdf_resource) :<-
|
||
|
"Get reference for a subject or create one"::
|
||
|
( get(D, member, Resource, Subject)
|
||
|
-> true
|
||
|
; Create \== @off,
|
||
|
get(D, create_resource, Resource, Subject),
|
||
|
send(D, display, Subject, D?visible?center)
|
||
|
).
|
||
|
|
||
|
literal(D, Value:prolog, Gr:rdf_literal) :<-
|
||
|
"Display a literal. Don't try to re-use"::
|
||
|
( literal_name(Value, Name),
|
||
|
get(D, member, Name, Gr)
|
||
|
-> true
|
||
|
; get(D, create_literal, Value, Gr),
|
||
|
send(D, display, Gr, D?visible?center)
|
||
|
).
|
||
|
|
||
|
|
||
|
create_resource(D, Resource:name, Subject:rdf_resource) :<-
|
||
|
"Create visualisation of Resource"::
|
||
|
new(Subject, rdf_resource(Resource, D)).
|
||
|
|
||
|
|
||
|
create_literal(_D, Value:prolog, Gr:rdf_literal) :<-
|
||
|
"Create visualisation of literal"::
|
||
|
new(Gr, rdf_literal(Value)).
|
||
|
|
||
|
|
||
|
node_label(_D, Resource:name, Label:name) :<-
|
||
|
"Generate label to show for a node"::
|
||
|
local_name(Resource, Label).
|
||
|
|
||
|
|
||
|
:- pce_group(layout).
|
||
|
|
||
|
layout(D) :->
|
||
|
"Produce automatic layout"::
|
||
|
new(Nodes, chain),
|
||
|
send(D?graphicals, for_all,
|
||
|
if(message(@arg1, instance_of, rdf_any),
|
||
|
message(Nodes, append, @arg1))),
|
||
|
send(Nodes?head, layout, 2, 40,
|
||
|
iterations := 200,
|
||
|
area := D?visible,
|
||
|
network := Nodes).
|
||
|
|
||
|
copy_layout(D, From:rdf_diagram, Subst:prolog) :->
|
||
|
"Copy the layout from another windows"::
|
||
|
send(D?graphicals, for_some,
|
||
|
message(D, copy_location, @arg1, From, prolog(Subst))).
|
||
|
|
||
|
copy_location(_D, Obj:graphical, From:rdf_diagram, Subst:prolog) :->
|
||
|
"Copy location of a single RDF object"::
|
||
|
( send(Obj, instance_of, rdf_any)
|
||
|
-> ( get(Obj, name, Name),
|
||
|
find(From, Name, Subst, FromObj)
|
||
|
-> format('Copied location of ~p from ~p~n', [Obj, FromObj]),
|
||
|
get(FromObj, center, Center),
|
||
|
send(Obj, center, Center)
|
||
|
)
|
||
|
; true
|
||
|
).
|
||
|
|
||
|
find(D, Name, _Subst, Obj) :-
|
||
|
get(D, member, Name, Obj).
|
||
|
find(D, Name, Subst, Obj) :-
|
||
|
member(Name=AltName, Subst),
|
||
|
atom_concat('_:', AltName, FullAltName),
|
||
|
get(D, member, FullAltName, Obj).
|
||
|
find(D, Name, Subst, _) :-
|
||
|
format('Cannot find ~w in ~p, Subst =~n', [Name, D]),
|
||
|
pp(Subst),
|
||
|
fail.
|
||
|
|
||
|
|
||
|
:- pce_end_class(rdf_diagram).
|
||
|
|
||
|
|
||
|
/*******************************
|
||
|
* SHAPES *
|
||
|
*******************************/
|
||
|
|
||
|
:- pce_begin_class(rdf_connection, tagged_connection,
|
||
|
"Represents a triple").
|
||
|
|
||
|
:- pce_global(@rdf_link, new(link(link, link,
|
||
|
line(0,0,0,0,second)))).
|
||
|
|
||
|
initialise(C, Gr1:graphical, Gr2:graphical, Pred:name, Ctx:[object]) :->
|
||
|
"Create from predicate"::
|
||
|
send_super(C, initialise, Gr1, Gr2, @rdf_link),
|
||
|
send(C, tag, rdf_label(Pred, italic, Ctx)).
|
||
|
|
||
|
ideal_length(C, Len:int) :<-
|
||
|
"Layout: compute the desired length"::
|
||
|
get(C, height, H),
|
||
|
( H < 40
|
||
|
-> get(C, tag, Tag),
|
||
|
get(Tag, width, W),
|
||
|
Len is W + 30
|
||
|
; Len = 40
|
||
|
).
|
||
|
|
||
|
:- pce_end_class(rdf_connection).
|
||
|
|
||
|
:- pce_begin_class(rdf_any(name), figure,
|
||
|
"Represent an RDF resource or literal").
|
||
|
|
||
|
handle(w/2, 0, link, north).
|
||
|
handle(w, h/2, link, east).
|
||
|
handle(w/2, h, link, south).
|
||
|
handle(0, h/2, link, west).
|
||
|
|
||
|
initialise(F, Ref:name) :->
|
||
|
"Create visualisation"::
|
||
|
send_super(F, initialise),
|
||
|
send(F, name, Ref).
|
||
|
|
||
|
connect(F, Pred:name, Object:graphical) :->
|
||
|
new(_C, rdf_connection(F, Object, Pred, F)).
|
||
|
|
||
|
:- pce_global(@rdf_any_recogniser,
|
||
|
make_rdf_any_recogniser).
|
||
|
:- pce_global(@rdf_any_popup,
|
||
|
make_rdf_any_popup).
|
||
|
|
||
|
make_rdf_any_recogniser(G) :-
|
||
|
new(M1, move_gesture(left)),
|
||
|
new(M2, move_network_gesture(left, c)),
|
||
|
new(P, popup_gesture(@receiver?popup)),
|
||
|
new(G, handler_group(M1, M2, P)).
|
||
|
|
||
|
popup(_F, Popup:popup) :<-
|
||
|
"Create popup menu"::
|
||
|
Popup = @rdf_any_popup.
|
||
|
|
||
|
make_rdf_any_popup(Popup) :-
|
||
|
new(Popup, popup),
|
||
|
Gr = @arg1,
|
||
|
send(Popup, append,
|
||
|
menu_item(layout, message(Gr, layout))).
|
||
|
|
||
|
event(F, Ev:event) :->
|
||
|
( \+ send(Ev, is_a, ms_right_down),
|
||
|
send_super(F, event, Ev)
|
||
|
-> true
|
||
|
; send(@rdf_any_recogniser, event, Ev)
|
||
|
).
|
||
|
|
||
|
node_label(F, Resource:name, Label:name) :<-
|
||
|
"Return label to use for a resource"::
|
||
|
get(F, device, Dev),
|
||
|
( send(Dev, has_get_method, node_label)
|
||
|
-> get(Dev, node_label, Resource, Label)
|
||
|
; local_name(Resource, Label)
|
||
|
).
|
||
|
|
||
|
:- pce_end_class(rdf_any).
|
||
|
|
||
|
|
||
|
:- pce_begin_class(move_network_gesture, move_gesture,
|
||
|
"Move network of connected graphicals").
|
||
|
|
||
|
variable(outline, box, get,
|
||
|
"Box used to indicate move").
|
||
|
variable(network, chain*, both,
|
||
|
"Stored value of the network").
|
||
|
variable(origin, point, get,
|
||
|
"Start origin of network").
|
||
|
|
||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||
|
The gesture maintains an outline, the selection to be moved and the
|
||
|
positon where the move orginiated. The outline itself is given a
|
||
|
normal move_gesture to make it move on dragging. This move_gesture
|
||
|
should operate on the same button and modifier.
|
||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||
|
|
||
|
initialise(G, B:[button_name], M:[modifier]) :->
|
||
|
send(G, send_super, initialise, B, M),
|
||
|
send(G, slot, outline, new(Box, box(0,0))),
|
||
|
send(G, slot, origin, point(0,0)),
|
||
|
send(Box, texture, dotted),
|
||
|
send(Box, recogniser, move_gesture(G?button, G?modifier)).
|
||
|
|
||
|
initiate(G, Ev:event) :->
|
||
|
get(Ev, receiver, Gr),
|
||
|
get(Gr, device, Dev),
|
||
|
get(G, outline, Outline),
|
||
|
get(Gr, network, Network),
|
||
|
send(G, network, Network),
|
||
|
new(Union, area(0,0,0,0)),
|
||
|
send(Network, for_all, message(Union, union, @arg1?area)),
|
||
|
send(G?origin, copy, Union?position),
|
||
|
send(Outline, area, Union),
|
||
|
send(Union, done),
|
||
|
send(Dev, display, Outline),
|
||
|
ignore(send(Ev, post, Outline)).
|
||
|
|
||
|
drag(G, Ev) :->
|
||
|
send(Ev, post, G?outline).
|
||
|
|
||
|
|
||
|
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||
|
Terminate. First undisplay the outline. Next calculate by how much
|
||
|
the outline has been dragged and move all objects of the selection by
|
||
|
this amount.
|
||
|
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
||
|
|
||
|
terminate(G, Ev:event) :->
|
||
|
ignore(send(G, drag, Ev)),
|
||
|
get(G, outline, Outline),
|
||
|
send(Outline, device, @nil),
|
||
|
get(Outline?area?position, difference, G?origin, Offset),
|
||
|
get(G, network, Network),
|
||
|
send(Network, for_all, message(@arg1, relative_move, Offset)),
|
||
|
send(G, network, @nil).
|
||
|
|
||
|
:- pce_end_class(move_network_gesture).
|
||
|
|
||
|
|
||
|
|
||
|
:- pce_begin_class(rdf_label, text,
|
||
|
"Label for an RDF relation").
|
||
|
|
||
|
variable(resource, name, get, "Represented predicate").
|
||
|
|
||
|
initialise(L, Pred:name, Font:font, Context:[object]) :->
|
||
|
( Context == @default
|
||
|
-> local_name(Pred, Label)
|
||
|
; get(Context, node_label, Pred, Label)
|
||
|
),
|
||
|
send_super(L, initialise, Label, center, Font),
|
||
|
send(L, slot, resource, Pred),
|
||
|
send(L, background, @default).
|
||
|
|
||
|
:- pce_global(@rdf_label_recogniser,
|
||
|
make_rdf_label_recogniser).
|
||
|
|
||
|
make_rdf_label_recogniser(G) :-
|
||
|
new(G, handler_group),
|
||
|
send(G, append,
|
||
|
handler(area_enter, message(@receiver, identify))),
|
||
|
send(G, append,
|
||
|
handler(area_exit, message(@receiver, report, status, ''))),
|
||
|
send(G, append, popup_gesture(new(P, popup))),
|
||
|
send_list(P, append,
|
||
|
[ menu_item(copy,
|
||
|
message(@display, copy, @arg1?resource))
|
||
|
]).
|
||
|
|
||
|
event(F, Ev:event) :->
|
||
|
( send_super(F, event, Ev)
|
||
|
-> true
|
||
|
; send(@rdf_label_recogniser, event, Ev)
|
||
|
).
|
||
|
|
||
|
identify(L) :->
|
||
|
send(L, report, status, '%s', L?resource).
|
||
|
|
||
|
:- pce_end_class.
|
||
|
|
||
|
|
||
|
|
||
|
:- pce_begin_class(rdf_resource, rdf_any,
|
||
|
"Represent an RDF resource").
|
||
|
|
||
|
initialise(F, Ref:name, Ctx:[object]) :->
|
||
|
"Create visualisation"::
|
||
|
send_super(F, initialise, Ref),
|
||
|
send(F, display, ellipse(100, 50), point(-50,-25)),
|
||
|
send(F, display, new(T, rdf_label(Ref, normal, Ctx))),
|
||
|
send(T, center, point(0,0)).
|
||
|
|
||
|
type(F, Type:name) :->
|
||
|
send(F, display, new(TL, rdf_label(Type, small, F))),
|
||
|
send(TL, center, point(0,14)),
|
||
|
get(F, member, ellipse, E),
|
||
|
send(E, shadow, 2).
|
||
|
|
||
|
identify(F) :->
|
||
|
send(F, report, status, 'Resource %s', F?name).
|
||
|
|
||
|
:- pce_end_class(rdf_resource).
|
||
|
|
||
|
|
||
|
:- pce_begin_class(rdf_literal, rdf_any,
|
||
|
"Represent an RDF literal value").
|
||
|
|
||
|
variable(value, prolog, get, "Represented literal value").
|
||
|
|
||
|
initialise(F, Value:prolog) :->
|
||
|
"Create visualisation"::
|
||
|
send(F, slot, value, Value),
|
||
|
literal_label(Value, Label),
|
||
|
atom_concat('__lit:', Label, Id),
|
||
|
send_super(F, initialise, Id),
|
||
|
send(F, display, new(B, box)),
|
||
|
send(B, fill_pattern, colour(grey80)),
|
||
|
send(B, pen, 0),
|
||
|
send(F, display, new(T, text(Label, center))),
|
||
|
send(T, center, point(0,0)),
|
||
|
send(F, fit).
|
||
|
|
||
|
literal_label(literal(Value0), Value) :- !,
|
||
|
literal_label(Value0, Value).
|
||
|
literal_label(xml(Value0), Value) :- !,
|
||
|
literal_label(Value0, Value).
|
||
|
literal_label(Value, Value) :-
|
||
|
atomic(Value), !.
|
||
|
literal_label(Value, Label) :-
|
||
|
term_to_atom(Value, Label).
|
||
|
|
||
|
literal_name(Value, Name) :-
|
||
|
literal_label(Value, Label),
|
||
|
atom_concat('__lit:', Label, Name).
|
||
|
|
||
|
fit(F) :->
|
||
|
"Make box fit contents"::
|
||
|
get(F, member, text, Text),
|
||
|
get(Text?area, clone, Area),
|
||
|
send(Area, increase, 3),
|
||
|
get(F, member, box, Box),
|
||
|
send(Box, area, Area).
|
||
|
|
||
|
:- pce_end_class(rdf_literal).
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
/*******************************
|
||
|
* PRIMITIVES *
|
||
|
*******************************/
|
||
|
|
||
|
subject_name(rdf(Name0, _, _), Name) :-
|
||
|
resource_name(Name0, Name).
|
||
|
predicate_name(rdf(_, Name0, _), Name) :-
|
||
|
resource_name(Name0, Name).
|
||
|
object_resource(rdf(_, _, Name0), Name) :-
|
||
|
resource_name(Name0, Name).
|
||
|
object_literal(rdf(_,_,Literal), Literal).
|
||
|
|
||
|
|
||
|
resource_name(Name, Name) :-
|
||
|
atom(Name), !.
|
||
|
resource_name(rdf:Local, Name) :- !, % known namespaces
|
||
|
atomic_list_concat([rdf, :, Local], Name).
|
||
|
resource_name(NS:Local, Name) :- !,
|
||
|
atom_concat(NS, Local, Name).
|
||
|
resource_name(node(Anon), Name) :- % Not for predicates
|
||
|
atom_concat('_:', Anon, Name).
|
||
|
|
||
|
is_type(rdf(_, rdf:type, _)) :- !. % our parser
|
||
|
is_type(rdf(_, Pred, _)) :- % our parser
|
||
|
atom(Pred),
|
||
|
rdf_name_space(NS),
|
||
|
atom_concat(NS, type, Pred), !.
|
||
|
|
||
|
% local_name(+Resource, -Label)
|
||
|
%
|
||
|
% Return easy readable local name
|
||
|
|
||
|
local_name(Resource, Local) :-
|
||
|
sub_atom(Resource, _, _, A, #),
|
||
|
sub_atom(Resource, _, A, 0, Local),
|
||
|
\+ sub_atom(Local, _, _, _, #), !.
|
||
|
local_name(Resource, Local) :-
|
||
|
atom_concat('rdf:', Local, Resource), !.
|
||
|
local_name(Resource, Local) :-
|
||
|
file_base_name(Resource, Local),
|
||
|
Local \== ''.
|
||
|
local_name(Resource, Resource).
|
||
|
|