Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions plugins_src/commands/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ MODULES= \
wpc_intersect_vertex \
wpc_isometric_view \
wpc_magnet_mask \
wpc_metadata \
wpc_metadata_dc \
wpc_metadata_units \
wpc_move_planar \
wpc_numeric_camera \
wpc_plane_cut \
Expand Down
223 changes: 223 additions & 0 deletions plugins_src/commands/wpc_metadata.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
%%
%% wpc_metadata.erl --
%%
%% Metadata functionality
%%
%% Copyright (c) 2026 Edward Blake
%%
%% See the file "license.terms" for information on usage and redistribution
%% of this file, and for a DISCLAIMER OF ALL WARRANTIES.
%%
%% $Id$
%%

-module(wpc_metadata).
-export([init/0,menu/2,command/2]).

-include_lib("wings/src/wings.hrl").

init() ->
true.

menu({tools},Menu) ->
metadata_submenu(Menu);
menu({tools,metadata},Menu) ->
metadata_menu_entry(Menu);
menu(_,Menu) -> Menu.

metadata_submenu([]) ->
[{?__(1,"Metadata"), {metadata, []}}];
metadata_submenu([A|Menu]) ->
[A|metadata_submenu(Menu)].

metadata_menu_entry([]) ->
[{?__(1,"Scene"),scene,
?__(2,"Set scene metadata")},
{?__(3,"Object"),object,
?__(4,"Set object metadata")}];
metadata_menu_entry([A|Menu]) ->
[A|metadata_menu_entry(Menu)].

command({tools,{metadata,scene}},St) ->
set_scene_metadata(St);
command({tools,{metadata,object}},St) ->
set_obj_metadata(St);
command(_,_) ->
next.

%%%
%%%

%%
%% Set Scene Metadata
%%

set_scene_metadata(#st{pst=Pst}=St) ->
List = metadata_plugins(scene),
QsFrames = metadata_tab_frames(scene,
fun (Name) -> get_metadata(Name,Pst) end, List),
Frame = [{oframe, QsFrames, 1, [{style, buttons}]}],
wings_dialog:dialog(?__(1,"Scene Metadata"), {preview,Frame},
fun
({dialog_preview,Args}) ->
{preview,St,set_scene_metadata_1(Args, List, St)};
(cancel) ->
St;
(Args) ->
{commit,St,set_scene_metadata_1(Args, List, St)}
end).

set_scene_metadata_1(Args, List, #st{pst=Pst}=St) ->
Metadata = get_pst_metadata(Pst),
Metadata_2 = lists:foldl(
fun({Name, _, Mod}, Metadata_1) ->
update_metadata(Name, Mod, scene, Args, Metadata_1)
end, Metadata, List),
St#st{pst=update_pst_metadata(Metadata_2, Pst)}.


%%
%% Object metadata.
%%
set_obj_metadata(#st{sel=[_|_],selmode=body}=St) ->
List = metadata_plugins(object),
QsFrames = metadata_tab_frames(object,
fun (Name) -> get_obj_metadata(Name, St) end, List),
Frame = [{oframe, QsFrames, 1, [{style, buttons}]}],
wings_dialog:dialog(?__(1,"Object Metadata: ") ++ obj_names(St), {preview,Frame},
fun
({dialog_preview,Args}) ->
{preview,St,set_obj_metadata_1(Args, List, St)};
(cancel) ->
St;
(Args) ->
{commit,St,set_obj_metadata_1(Args, List, St)}
end);
set_obj_metadata(#st{sel=[]}=_St) ->
wings_u:error_msg(?__(3,"Need at least one object selected."));
set_obj_metadata(#st{selmode=SelMode}=St) when SelMode =/= body ->
set_obj_metadata(wings_sel_conv:mode(body, St)).

set_obj_metadata_1(Args, List, St) ->
wings_sel:map(fun (_, #we{pst=Pst}=We) ->
Metadata = get_pst_metadata(Pst),
Metadata_2 = lists:foldl(
fun({Name, _, Mod}, Metadata_1) ->
update_metadata(Name, Mod, object, Args, Metadata_1)
end, Metadata, List),
We#we{pst=update_pst_metadata(Metadata_2, Pst)}
end, St).

obj_names(St) ->
{Extra, StrList} =
wings_sel:fold(
fun
(_, #we{name=ObjName}=_We, {0, Acc}) when length(Acc) < 3 ->
{0, [ObjName|Acc]};
(_, #we{name=_}=_We, {C, Acc}) ->
{C + 1, Acc}
end, {0, []}, St),
lists:flatten(lists:join(?__(1,", "), lists:reverse(StrList)) ++
if
Extra =:= 1 -> io_lib:format(?__(2,", ~w more object"), [Extra]);
Extra > 1 -> io_lib:format(?__(3,", ~w more objects"), [Extra]);
true -> ""
end).

%%%
%%%

%% Find the metadata plugins
%%
metadata_plugins(Scope) ->
Plugins = get(wings_plugins),
lists:append([ try_metadata_plugin(Scope, Pl) || Pl <- Plugins])
++ [{comments, ?__(2,"Comments"), 0}].

%% Create Tab frames for dialog
%%
metadata_tab_frames(Scope, Fun, List) ->
[{TabName, module_metadata_dialog(Mod, Name, Scope, Fun(Name))}
|| {Name, TabName, Mod} <- List].

%%%
%%%

update_metadata(Name, Mod, Scope, Args, Metadata) ->
MetadataC = case proplists:get_value(Name, Metadata, []) of
MetadataC_0 when is_list(MetadataC_0) -> MetadataC_0;
_ -> []
end,
MetadataC_1 = module_metadata_update(Mod, Name, Scope, MetadataC, Args),
case length(MetadataC_1) =:= 0 of
true ->
proplists:delete(Name, Metadata);
false ->
orddict:store(Name, MetadataC_1, orddict:from_list(proplists:delete(Name, Metadata)))
end.

get_pst_metadata(Pst) ->
case gb_trees:lookup(metadata,Pst) of
none -> [];
{value, Metadata} when is_list(Metadata) -> Metadata;
{value, _} -> []
end.

update_pst_metadata(Metadata, Pst) ->
gb_trees:enter(metadata, Metadata, Pst).

get_metadata(Name, Pst) ->
Metadata = get_pst_metadata(Pst),
case proplists:get_value(Name, Metadata, []) of
MetadataC when is_list(MetadataC) -> MetadataC;
_ -> []
end.


get_obj_metadata(Name, St) ->
wings_sel:fold(fun (_, #we{pst=Pst}=_We, Acc) ->
Metadata = get_pst_metadata(Pst),
case proplists:get_value(Name,Metadata,Acc) of
MetadataC when is_list(MetadataC) -> MetadataC;
_ -> []
end
end, [], St).


module_metadata_dialog(0, comments, _Scope, MetadataC) ->
metadata_comments_dialog(MetadataC);
module_metadata_dialog(Mod, Name, Scope, MetadataC) when is_atom(Mod) ->
Mod:metadata_dialog(Scope, Name, MetadataC).

module_metadata_update(0, comments, _Scope, MetadataC, Args) ->
metadata_comments_update(MetadataC, Args);
module_metadata_update(Mod, Name, Scope, MetadataC, Args) when is_atom(Mod) ->
Mod:metadata_update(Scope, Name, MetadataC, Args).

try_metadata_plugin(Scope, Pl) ->
try Pl:metadata_names(Scope) of
Ret -> Ret
catch
error:_ -> []
end.


%% Free-form comments
%%

metadata_comments_dialog(MetadataComments) when is_list(MetadataComments) ->
Val = proplists:get_value(comments, MetadataComments, ""),
{vframe,[
{label,?__(12,"Comments")},
{text,Val,[{key,comments},{width,50}]}
]}.

metadata_comments_update(MetadataComments, Args) when is_list(MetadataComments) ->
case proplists:get_value(comments, Args, "") of
"" ->
proplists:delete(comments, MetadataComments);
Creator ->
orddict:store(comments, Creator, orddict:from_list(proplists:delete(comments, MetadataComments)))
end.


121 changes: 121 additions & 0 deletions plugins_src/commands/wpc_metadata_dc.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
%%
%% wpc_metadata_dc.erl --
%%
%% Plugin that implements Dublin Core metadata
%%
%% Copyright (c) 2026 Edward Blake
%%
%% See the file "license.terms" for information on usage and redistribution
%% of this file, and for a DISCLAIMER OF ALL WARRANTIES.
%%
%% $Id$
%%

-module(wpc_metadata_dc).
-export([init/0,menu/2,command/2]).
-export([metadata_names/1]).
-export([metadata_dialog/3]).
-export([metadata_update/4]).

-include_lib("wings/src/wings.hrl").

init() ->
true.
menu(_,Menu) -> Menu.
command(_,_) ->
next.

%%%
%%%

%% The dublin core elements
%%
elements() ->
[
{text,{title,
?__(1,"Title:"),
?__(2,"The name of the resource")}},
{text,{creator,
?__(3,"Creator:"),
?__(4,"The person or entity that primarily created the resource.")}},
{text,{subject,
?__(5,"Subject:"),
?__(6,"The topic or keywords of the resource.")}},
{text,{description,
?__(7,"Description:"),
?__(8,"A description of the resource")}},
{text,{publisher,
?__(9,"Publisher:"),
?__(10,"The publisher of the resource")}},
{text,{contributor,
?__(11,"Other Contributors:"),
?__(12,"Other persons or entities that contributed towards the creation of the resource")}},
{text,{date,
?__(13,"Date:"),
?__(14,"A date specifying the creation or availability of the resource")}},
{text,{identifier,
?__(15,"Identifier:"),
?__(16,"An identifier (such as a URI, string or number) that distinguishes the resource.")}},
{text,{format,
?__(17,"Format:"),
?__(18,"The data format and dimensions of the resource. Software and hardware related to the resource can be specified here.")}},
{text,{source,
?__(19,"Source:"),
?__(20,"Identifiers to other resources that are a source for the current resource.")}},
{text,{coverage,
?__(21,"Coverage:"),
?__(22,"The geographic or temporal coverage of the resource.")}},
{text,{language,
?__(23,"Language:"),
?__(24,"The language of the resource.")}},
{text,{relation,
?__(25,"Relation:"),
?__(26,"Identifiers to other resources with a relation to the current resource.")}},
{text,{rights,
?__(27,"Rights:"),
?__(28,"Information on rights of the resource.")}},
{text,{type,
?__(29,"Type:"),
?__(30,"A category of media of the resource.")}}
].


%%%
%%%

metadata_names(_Scope) ->
[{dc,?__(1,"General"),?MODULE}].

metadata_dialog(_Scope, _Name, MetadataDC) when is_list(MetadataDC) ->
List_0 = [V || {C,V} <- elements(), C =:= text],
{vframe,[{label_column,[
text_field(Name, FieldStr, FieldInfo, MetadataDC)
|| {Name,FieldStr,FieldInfo} <- List_0]}]}.

metadata_update(_Scope, _Name, MetadataDC, Args) when is_list(MetadataDC) ->
List = [Name || {Name,_,_} <- [V || {C,V} <- elements(), C =:= text]],
lists:foldl(
fun (FieldName, Acc) ->
update(FieldName, Acc, Args)
end, MetadataDC, List).

%%%
%%%

text_field(Name, FieldName, FieldInfo, MetadataDC) when is_list(MetadataDC) ->
Val = proplists:get_value(Name, MetadataDC, ""),
{FieldName,{text,Val,[{key,Name},{width,50},{info,FieldInfo}]}}.


update(Name, Acc, Args) ->
case proplists:get_value(Name, Args, 1) of
"" ->
proplists:delete(Name, Acc);
Creator ->
orddict:store(Name, Creator, orddict:from_list(proplists:delete(Name, Acc)))
end.





Loading
Loading