/*  $Id$

    Part of SWI-Prolog SGML/XML parser

    Author:  Jan Wielemaker
    E-mail:  jan@swi.psy.uva.nl
    WWW:     http://www.swi.psy.uva.nl/projects/SWI-Prolog/
    Copying: LGPL-2.  See the file COPYING or http://www.gnu.org

    Copyright (C) 1990-2002 SWI, University of Amsterdam. All rights reserved.
*/

:- 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
	concat_atom([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).
	
