1 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
4 % Shortcut generator v1.0
6 % This program is known to work with SWI-Prolog version 2.9.5, which you
9 % http://www.swi.psy.uva.nl/usr/jan/SWI-Prolog.html
11 % Purpose: to generate shortcuts for labels in menus and dialogs that are
12 % guaranteed to be unique within a set of labels. The shortcuts are
13 % generated in a prioritized manner, such that characters at the beginning
14 % of words are preferred to characters in the middle of words.
16 % You might find this progam useful if you are translating LyX.
18 % Don't use this file in the development/tools-directory; copy it
19 % somewhere out of the LyX-distribution tree, unless you're improving
20 % the tool itself. The input is to be hard-coded into the file and that
21 % may cause unnecessary garbage to appear in any patch you're putting
24 % The labels are supposed to be entered at "LABELS" below,
25 % and you get one solution (hopefully) with "go.".
26 % If there are no solutions, the Prolog-interpretator will reply with a
29 % You can get all candidate solutions with "all.", but this requires
32 % You can inspect the correspondance between the original strings and
33 % the prioritized ones with "inspect.".
35 % I have no idea what the big O for the algorithm is, but I suspect it's
37 % Asger speculates that the algorithm is O(n^m), where n is the cardinality
38 % of the candidate sets, while m is the number of sets. Since we do an
39 % exhausitive search, this has to be the case.
43 % print out one solution, i.e. a set of label/shortcut's
44 % writef/2 may be specific for SWI-prolog
45 show_one_alternative([]).
46 show_one_alternative([L/_/C|Rest]):-
47 writef('%s|#%n\n',[L,C]),
48 show_one_alternative(Rest).
50 % printout predicate for "all."
52 show_solutions([H|T]):-
53 write('-----------\n'),
54 show_one_alternative(H),
57 % print out correspondance between original strings and prioritized ones.
59 show_priority([L/P/_|Rest]):-
60 writef('%s|%n\n',[L,P]),
63 % character is from a set of allowed characters.
64 % "AZaz09"=[65, 90, 97, 122, 48, 57]
66 C >= 97, C =< 122. % a-z
68 C >= 65, C =< 90. % A-Z
70 C >= 48, C =< 57. % 0-9
72 % turn lowercase to uppercase; alt-<key> is case insensitive
74 (L >= 97, L =< 122) -> U is L - 32;
77 % possible_char/2: Gets all characters in label, one after one.
78 possible_char(_/Label,Char):-
79 member(Char,Label). % the character is part of the label
81 % prepare_labels/2: Prepares all labels. Constructs a new list of pairs
82 % where the original string is coupled with the prepared string.
83 prepare_labels([], []).
84 prepare_labels([H1|T1], [H1/H2|T2]):-
85 prepare_string(H1, H2),
86 prepare_labels(T1, T2).
88 % prepare_string/2: Prepares a string by removing duplicate characters,
89 % prioritizing initials letters, removing illegal characters and turning
90 % lowercase to uppercase characters.
91 prepare_string(Label,Result):-
92 string_to_list(Label,List1), % make a list of the string
93 prioritize(List1, List2), % Prioritize string
94 filter_chars(List2, List3), % Filter out unwanted chars
95 unique(List3, Result). % Remove duplicates
97 % prioritize/2: This predicate rearranges a list, such that
98 % chars at the beginning of words are put first in the list.
99 % i.e. prioritize("Foo bar", "Fboo ar")" is true.
101 initial_chars(L1, I), % Find all initial characters
102 subtract(L1, I, Rest), % and the others
103 append(I, Rest, L2). % and we have the result.
105 % initial_chars/2: Returns a list of characters that appear at the beginning
106 % of words. i.e. initial_chars("Foo bar", "Fb") is true.
107 initial_chars([],[]).
108 initial_chars([A|T1], [A|T3]):-
109 rest_after_space(T1, T2), % Return rest of list after space
110 initial_chars(T2, T3).
112 % rest_after_space/2: Returns the list after the first space.
113 % i.e. "rest_after_space("Foo bar", "bar") is true.
114 rest_after_space([], []).
115 rest_after_space([32, H1|T1], [H1|T1]):- !.
116 rest_after_space([_|T1], T2):-
117 rest_after_space(T1, T2).
119 % filterchars/2: Filter outs non-allowed characters from list, and turns
120 % lowercase to uppercase.
121 filter_chars([], []).
122 filter_chars([H|T1], [C|T2]):-
123 allowed_char(H), !, uppercase(H, C),
124 filter_chars(T1, T2).
125 filter_chars([_|T1], T2):-
126 filter_chars(T1, T2).
128 % unique/2: This predicate removes duplicate characters without reordering.
129 % i.e. unique("Foo bar", "Fo bar") is true.
131 unique([H|T1], [H|T3]):-
132 delete(T1, H, T2), % Remove duplicates.
135 % working_shortcuts/2 instantiates Char in the first argument for a whole
137 working_shortcuts([],_). % end of the list
139 working_shortcuts([Label/Char|Rest],Used):-
140 possible_char(Label,Char), % i.e. part of the label string
141 \+member(Char,Used), % not used by any other shortcut
142 working_shortcuts(Rest,[Char|Used]). % and all the other labels have
144 % The prolog motor will backtrack up and down the list of labels
145 % until it finds a set with unique characters for each label
148 % Label strings goes here.
149 % Just cut&paste the strings from the LyX-source, and remove any
150 % control sequences for menus. We could add a couple of predicates to scan
151 % a file, but re-consulting this file takes only a fraction of a second
152 % so I didn't bother to add a fancy user interface here. The solution is
153 % printed like <label> '|#' <key>, allthough this isn't nessecarily useful,
154 % for menus for instance.
156 % The characters are picked with priority from left to right within
157 % strings, and the labels are listed in falling priority.
158 % If you want a certain label to have a certain shortcut, try adding that
159 % character in front of the string (like 'Exit' -> 'xExit') and move it
160 % higher up in the list.
161 % If this doesn't work, replace the string with only that character
162 % ('Exit' -> 'x'). If you get a "No." then, you lose.
163 % Use "inspect." to inspect the resulting priotized strings.
169 "New from template...",
183 % NB, no comma on the last one. Easy to forget.
185 prepare_labels(Strings, Str),
186 bagof(L/_,member(L,Str),Labels).
188 % Inspect mapping between original string and prioritized ones.
191 show_priority(Labels).
193 % Find ALL solutions (they are often legion - don't bother ;)
194 all:- % May (probably, on large dialogs) run out on memory.
196 setof(Labels,working_shortcuts(Labels,[]),Solutions),
197 show_solutions(Solutions).
202 working_shortcuts(Labels,[]),
203 show_one_alternative(Labels).