2
* Debian menu system -- install-menu
3
* install-menus/menu-tree.cc
5
* Copyright (C) 1996-2003 Joost Witteveen,
6
* Copyright (C) 2002-2004 Bill Allombert and Morten Brix Pedersen.
8
* This program is free software; you can redistribute it and/or modify
9
* it under the terms of the GNU General Public License as published by
10
* the Free Software Foundation; either version 2 of the License, or
11
* (at your option) any later version.
13
* This program is distributed in the hope that it will be useful,
14
* but WITHOUT ANY WARRANTY; without even the implied warranty of
15
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
* GNU General Public License for more details.
18
* You should have received a copy of the GNU General Public License with
19
* the Debian GNU/Linux distribution in file /usr/share/common-licenses/GPL;
20
* if not, write to the Free Software Foundation, Inc., 59 Temple Place,
21
* Suite 330, Boston, MA 02111-1307 USA
24
* Written by Joost Witteveen.
1
30
#include "menu-tree.h"
2
31
#include "install-menu.h"
8
bool operator< (const StrVec &left, const StrVec &right){
11
for(i=left.begin(), j=right.begin();
12
(i!=left.end()) && (j!=right.end());
24
extern menuentry menu;
26
void menuentry::debug(int level){
27
submenu_container::iterator i;
30
for(li=level ;li>0; li--){
33
cout<<vars[TITLE_VAR]<<":"<<vars[SECTION_VAR]<<":"<<vars["icon"]<<endl;
34
for(i=submenus.begin(); i!=submenus.end(); i++){
35
(*i).second->debug(level+1);
39
void menuentry::add_entry(StrVec s,
40
map <String, String, less<String> > &v){
41
submenu_container::iterator f,firstsec_i;
42
map <String, String, less<String> >::iterator vi;
43
map <String, String, less<String> >::iterator vj;
47
bool use_forced=false;
49
//there are two ways to store the menuentries in the `tree'
50
// - `flat', all menuentries in the toplevel submenus map.
51
// This is used when working with `hints'. In this case, we
52
// will later sort out the tree.
54
//However, even in the `flat', hint-processing case, if there already
55
//exists a submenu with exactly the right s[0], then we do desent,
56
//if it has a v[FORCED_VAR] set.
59
for(vi=v.begin(); vi!=v.end(); vi++){
60
if((*vi).first == TITLE_VAR)
62
if((*vi).first == SECTION_VAR)
64
vj=vars.find((*vi).first);
66
(((*vj).second).size()==0)||
67
(((*vj).second)=="none")){
68
vars[(*vi).first]=(*vi).second;
33
#include <stringtoolbox.h>
41
/** Return prioritized string of useful shortcut keys
43
* As input takes a string, usually a menu entry title, and then parses it
44
* and returns the best possible characters for a shortcut key.
47
string sort_hotkey(const string& str)
50
string::size_type size = str.length();
51
char *s = strdup(str.c_str());
58
// Adds all uppercase letters which appears after a space or punctation
60
for (string::size_type i = 1; i != size; ++i)
61
if ((isspace(s[i-1]) || ispunct(s[i-1])) && isupper(s[i])) {
65
// Adds letters or digit which appears after a space or punctation mark.
66
for (string::size_type i = 1; i != size; ++i)
67
if ((isspace(s[i-1]) || ispunct(s[i-1])) && isalnum(s[i])) {
71
// Adds uppercase letters.
72
for (string::size_type i = 1; i != size; ++i)
78
for (string::size_type i = 1; i != size; ++i)
83
// Adds letters and digits.
84
for (string::size_type i = 1; i != size; ++i)
93
/** Adds a new entry in the menu hierarchy.
97
* sections: A vector of strings, holding the section names.
98
* entry_vars: Variables of the entry to be added.
100
* There are two ways to store the menuentries in the `tree'
101
* - `flat', all menuentries in the toplevel submenus map.
102
* This is used when working with `hints'. In this case, we
103
* will later sort out the tree.
105
* However, even in the `flat', hint-processing case, if there already
106
* exists a submenu with exactly the right sections[0], then we do desent,
107
* if it has a entry_vars[FORCED_VAR] set.
109
void menuentry::add_entry(std::vector<std::string> sections, std::map<std::string, std::string> &entry_vars)
111
map<string, string>::iterator vi;
112
map<string, string>::iterator vj;
113
bool use_forced = false;
115
if (sections.empty()) {
116
// No sections defined, this means that we probably are at the last
117
// node, so we just need to fill in variables that aren't already
119
for (vi = entry_vars.begin(); vi != entry_vars.end(); ++vi)
121
if (vi->first == TITLE_VAR)
123
if (vi->first == SECTION_VAR)
126
vj = vars.find(vi->first);
127
if ((vj == vars.end()) || vj->second.empty() || (vj->second == "none"))
128
vars[vi->first] = vi->second;
74
firstsec.push_back(s[0]);
75
firstsec_i=submenus.find(firstsec);
77
if(firstsec_i!=submenus.end())
78
if((*firstsec_i).second->forced)
81
if(((!config->hint_optimize)||use_forced)&&
84
for(i=s.begin()+1;i!=s.end(); i++)
88
if(firstsec_i==submenus.end()){
89
submenus[firstsec]=new menuentry;
90
submenus[firstsec]->section=subs;
91
(submenus[firstsec]->vars)[TITLE_VAR]=firstsec[0];
133
vector<string> firstsection;
134
firstsection.push_back(sections[0]);
135
submenu_container::iterator firstsection_i = submenus.find(firstsection);
137
if (firstsection_i != submenus.end())
138
if (firstsection_i->second->forced)
141
if ((!menumethod->hint_optimize || use_forced) && (sections.size() > 1)) {
142
// We are either not using hint_optimize, or we have to add this
143
// section, because it's forced to be created by the forcetree.
145
// And because sections.size() > 1, there are more sections to add.
147
// Add new subsections to our new section (but strip the first one)
148
vector<string> subsections;
149
for (vector<string>::iterator i = sections.begin()+1; i != sections.end(); ++i)
151
subsections.push_back(*i);
153
// If the first subsection doesn't already exist, then create a new one.
154
if (firstsection_i == submenus.end()) {
155
submenus[firstsection] = new menuentry;
156
submenus[firstsection]->vars[TITLE_VAR] = firstsection[0];
93
submenus[firstsec]->add_entry(subs,v);
96
if(f==submenus.end()){
97
// Adding a new entry.
159
submenus[firstsection]->add_entry(subsections, entry_vars);
161
submenu_container::iterator f = submenus.find(sections);
162
if (f == submenus.end()) {
163
// Add a new entry, because these sections doesn't exist.
164
submenus[sections] = new menuentry;
165
submenus[sections]->vars = entry_vars;
104
// Modifying an already existing entry. Add all varables
167
// Modifying an already existing entry. Add all variables
105
168
// that aren't already defined.
106
for(vi=v.begin(); vi!=v.end(); vi++){
109
vj=m->vars.find((*vi).first);
110
if((vj==m->vars.end())||
111
(((*vj).second).size()==0)||
112
(((*vj).second)=="none")){
113
m->vars[(*vi).first]=(*vi).second;
169
for (vi = entry_vars.begin(); vi != entry_vars.end(); ++vi)
171
menuentry *m = f->second;
172
vj = m->vars.find(vi->first);
173
if ((vj==m->vars.end()) || vj->second.empty() || (vj->second == "none"))
174
m->vars[vi->first] = vi->second;
120
void menuentry::add_menuentry_ptr(StrVec s,
122
submenu_container::iterator f;
123
StrVec subs,firstsec;
127
for(i=s.begin()+1;i!=s.end(); i++)
129
subs.push_back((*i));
131
firstsec.push_back(s[0]);
132
f=submenus.find(firstsec);
133
if(f==submenus.end()){
134
submenus[firstsec]=new menuentry;
135
submenus[firstsec]->section=subs;
136
submenus[firstsec]->forced=m->forced;
137
(submenus[firstsec]->vars)[TITLE_VAR]=firstsec[0];
180
/** Adds a new entry in the menu hierarchy, using an already created
185
* sections: A vector of strings, holding the section names.
186
* entry: a pointer to a menuentry.
188
void menuentry::add_entry_ptr(std::vector<std::string> sections, menuentry *entry)
190
if (sections.size() > 1) {
191
// There are more sections to add...
193
// Add new subsections to our new section (but strip the first one)
194
vector<string> subsections;
195
for (vector<string>::iterator i = sections.begin()+1; i != sections.end(); ++i)
197
subsections.push_back(*i);
199
// If the first subsection doesn't already exist, then create a new one.
200
vector<string> firstsection;
201
firstsection.push_back(sections[0]);
202
if (submenus.find(firstsection) == submenus.end()) {
203
submenus[firstsection] = new menuentry;
204
submenus[firstsection]->forced = entry->forced;
205
submenus[firstsection]->vars[TITLE_VAR] = firstsection[0];
139
submenus[firstsec]->add_menuentry_ptr(subs,m);
142
if(f==submenus.end())
207
submenus[firstsection]->add_entry_ptr(subsections, entry);
208
} else if (submenus.find(sections) == submenus.end()) {
209
// Add a new entry, because this section doesn't exist.
210
submenus[sections] = entry;
147
void menuentry::output(){
148
String treew=config->treewalk();
149
// map <String, menuentry *, less<String> >::iterator i;
150
submenu_container::iterator j;
151
multimap <String, menuentry *, less<String> > sorted;
152
multimap <String, menuentry *, less<String> >::iterator i;
155
//sort the submenus in sorted:
156
for(j=submenus.begin(); j!=submenus.end(); j++){
159
s=config->sort->soutput((*j).second->vars);
216
bool operator()(std::string s1, std::string s2) const {
217
return strcoll(s1.c_str(), s2.c_str())<0;
221
/** Output menu tree.
223
* Uses the 'treewalk' variable to define what to output in which order,
224
* this documentation was taken from the menu manual:
227
* This string defines in what order to dump the `$startmenu',
228
* `$endmenu', and `$submenutitle' (and its children). Each char in
229
* the string refers to:
231
* c : dump children of menu.
232
* m : dump this menu's $submenutitles
233
* ( : dump $startmenu
235
* M : dump all $submenutitles of this menu and this menu's children.
237
* The default is "c(m)". For olvwm, one needs: "(M)"
239
void menuentry::output()
241
string treew = menumethod->treewalk();
242
submenu_container::iterator sub_i;
243
std::multimap<string, menuentry *, menusort> sorted;
244
std::multimap<string, menuentry *>::iterator i;
246
// Sort the submenus in sorted:
247
for (sub_i = submenus.begin(); sub_i != submenus.end(); ++sub_i)
250
if (menumethod->sort)
251
s = menumethod->sort->soutput(sub_i->second->vars);
161
s=(*j).second->vars[SORT_VAR] + ":" + (*j).second->vars[TITLE_VAR];
162
cout<<"sorting on: "<<s<<endl;
163
sorted.insert(pair<String,menuentry *>(s,(*j).second));
166
for(string::size_type j=0;j<treew.length(); j++){
167
bool children_too=false;
170
for(i=sorted.begin(); i!=sorted.end(); i++)
171
if((*i).second->submenus.size())
172
(*i).second->output();
176
if(config->startmenu&& testuniqueness(vars))
177
config->startmenu->output(vars);
181
if(config->endmenu && testuniqueness(vars))
182
config->endmenu->output(vars);
187
for(i=sorted.begin(); i!=sorted.end(); i++)
188
if(((*i).second->vars[COMMAND_VAR].length())&&
189
testuniqueness((*i).second->vars))
190
supported->subst((*i).second->vars);
192
if((config->submenutitle)&&
193
((*i).second->submenus.size())&&
194
testuniqueness((*i).second->vars))
195
config->submenutitle->output((*i).second->vars);
197
(*i).second->output();
253
s = sub_i->second->vars[SORT_VAR] + ':' + sub_i->second->vars[TITLE_VAR];
255
sorted.insert(std::pair<string, menuentry *>(s, sub_i->second));
258
// Output the menu according to the treewalk variable.
259
for (string::size_type j = 0; j < treew.length(); ++j)
261
bool children_too = false;
265
for (i = sorted.begin(); i != sorted.end(); ++i)
266
if (!i->second->submenus.empty())
270
if (!submenus.empty())
271
if (menumethod->startmenu && testuniqueness(vars))
272
menumethod->startmenu->output(vars);
275
if (!submenus.empty())
276
if (menumethod->endmenu && testuniqueness(vars))
277
menumethod->endmenu->output(vars);
282
for (i = sorted.begin(); i != sorted.end(); ++i)
284
if (!i->second->vars[COMMAND_VAR].empty() &&
285
testuniqueness(i->second->vars)) {
286
supported->subst(i->second->vars);
288
if (menumethod->submenutitle && !i->second->submenus.empty() &&
289
testuniqueness(i->second->vars))
290
menumethod->submenutitle->output(i->second->vars);
203
void menuentry::store_hints(){
204
/* put the $hint variables contents in the hint of
205
the submenu[] map. For submenu entries (without a command),
206
put the $hint variable in all menuentries that lie below
209
submenu_container::iterator i,j;
299
/** Put the $hint variable contents in the hint of the submenu[] map. For
300
* submenu entries (without a command), put the $hint variable in all
301
* menuentries that lie below that one.
303
void menuentry::store_hints()
305
submenu_container::iterator i, j;
213
// Make sure menuhints are empty:
214
for(i=submenus.begin(); i!=submenus.end(); i++){
215
(*i).second->menuhints.erase((*i).second->menuhints.begin(),
216
(*i).second->menuhints.end());
219
for(i=submenus.begin(); i!=submenus.end(); i++){
220
const String &hs=(*i).second->vars[HINTS_VAR];
228
for(k=h.begin(); k!=h.end(); k++)
229
(*j).second->menuhints.push_back(*k);
231
for(l=0; (l!=(*i).first.size()); l++)
232
if((*i).first[l]!=(*j).first[l])
234
} while (l==(*i).first.size());
308
// Make sure menuhints are empty
309
for(i = submenus.begin(); i != submenus.end(); ++i)
310
i->second->menuhints.erase(i->second->menuhints.begin(), i->second->menuhints.end());
312
for (i = submenus.begin(); i != submenus.end(); ++i)
314
const string &hints_str = i->second->vars[HINTS_VAR];
316
if (!hints_str.empty()) {
317
vector<string> hints;
319
break_char(hints_str, hints, ',');
324
for (vector<string>::iterator k = hints.begin(); k != hints.end(); ++k)
325
j->second->menuhints.push_back(*k);
328
if (j == submenus.end())
331
for (l = 0; l != i->first.size(); ++l)
332
if (i->first[l] != j->first[l])
334
} while (l == i->first.size());
240
void menuentry::process_hints(const StrVec &pref){
241
vector <StrVec> hint_list, hint_out;
242
vector <const StrVec>::iterator k,m;
340
/** Process the menu hierarchy through the hints algorithms.
342
* Basically, it has 4 steps (also marked as comments in the code):
344
* Step 1: Fetch appropriate sections to put in hint_list vector.
345
* Step 2: Initialize hints class and calculate a new tree, using hint_list.
346
* Step 3: Remove all submenus.
347
* Step 4: Using the new hint_out vector, initialize our new sorted tree,
348
* thus re-creating the previously removed submenus (but in a different
351
void menuentry::process_hints()
353
vector<vector<string> > hint_list, hint_out;
354
vector<vector<string> >::iterator k, m;
355
vector<string>::const_iterator l;
244
356
submenu_container::iterator i;
245
submenu_container oldsubmenus;
358
// First, process hints of children.
359
for (i = submenus.begin(); i != submenus.end(); ++i)
360
if (i->second->vars[COMMAND_VAR].empty())
361
i->second->process_hints();
366
// Go through all submenus, and add their sections to hint_list.
367
for (i = submenus.begin(); i != submenus.end(); ++i)
369
vector<string> sections;
371
for (l = i->first.begin(); l != i->first.end(); ++l)
372
sections.push_back(*l);
374
// If there are more than one element in 'sections', pop off the last
375
// one, since that's the title of the application.
376
if (sections.size() > 1) {
378
if (menumethod->hint_debug)
379
std::cout << "Adding to hint_list: " << i->first << ", hints="
380
<< i->second->menuhints << std::endl;
381
for (l=i->second->menuhints.begin(); l!=i->second->menuhints.end(); ++l)
382
sections.push_back(*l);
383
hint_list.push_back(sections);
385
vector<string> empty; // to make sure hint_list[i] and submenus[i] match
386
hint_list.push_back(empty);
248
// first, process hints of children.
249
for(i=submenus.begin(); i!=submenus.end(); i++){
250
if(((*i).second->vars[COMMAND_VAR]).size()==0){
253
for(j=(*i).first.begin(); j!=(*i).first.end(); j++)
255
(*i).second->process_hints(v);
260
for(i=submenus.begin(); i!=submenus.end(); i++){
261
//if(((*i).second->vars[COMMAND_VAR]).size()!=0){
264
for(l=(*i).first.begin(); l!=(*i).first.end(); l++)
266
//if(((*i).second->vars[COMMAND_VAR]).size()!=0)
269
if(config->hint_debug)
270
cout<<"Adding to hint_list: "<<(*i).first
271
<<", hints="<<(*i).second->menuhints<<endl;
272
for(l=(*i).second->menuhints.begin();
273
l!=(*i).second->menuhints.end();
276
hint_list.push_back(v);
278
StrVec empty; // to make sure hint_list[i] and submenus[i] match
279
hint_list.push_back(empty);
283
h.set_nentry( config->hint_nentry);
284
h.set_topnentry(config->hint_topnentry);
285
h.set_mixedpenalty(config->hint_mixedpenalty);
286
h.set_minhintfreq(config->hint_minhintfreq);
287
h.set_max_local_penalty(config->hint_mlpenalty);
288
h.set_max_ntry(config->hint_max_ntry);
289
h.set_max_iter_hint(config->hint_max_iter_hint);
290
h.set_debug( config->hint_debug);
292
h.calc_tree(hint_list,hint_out);
294
oldsubmenus=submenus;
391
h.set_nentry(menumethod->hint_nentry);
392
h.set_topnentry(menumethod->hint_topnentry);
393
h.set_mixedpenalty(menumethod->hint_mixedpenalty);
394
h.set_minhintfreq(menumethod->hint_minhintfreq);
395
h.set_max_local_penalty(menumethod->hint_mlpenalty);
396
h.set_max_ntry(menumethod->hint_max_ntry);
397
h.set_max_iter_hint(menumethod->hint_max_iter_hint);
398
h.set_debug(menumethod->hint_debug);
400
// Calculate our new tree, using hints. A lot of magic happens here.
401
h.calc_tree(hint_list, hint_out);
404
submenu_container oldsubmenus = submenus;
295
405
submenus.erase(submenus.begin(), submenus.end());
297
for(k=hint_list.begin(), m=hint_out.begin(), i=oldsubmenus.begin();
303
v.push_back((*i).first[(*i).first.size()-1]); // restore title
305
add_menuentry_ptr(v,(*i).second);
408
for (k = hint_list.begin(), m = hint_out.begin(), i = oldsubmenus.begin();
409
k != hint_list.end();
412
vector<string> sections = *m;
414
// Restore title (we popped it of earlier in this function).
415
sections.push_back(i->first.back());
417
// Finally re-add the entry...
418
add_entry_ptr(sections, i->second);
309
void menuentry::postprocess(int n_parent, int level, String prev){
310
// Postprocess will set or correct some variables in the
311
// menuentry tree (vars), that were not known at the time of
312
// creation of the menuentry classes (due to hints, or other reasons)
314
// n_parent: number of elements in menu of parent
315
// level: how deep this entry is nested in menu tree.
422
/** Postprocess will set or correct some variables in the
423
* menuentry tree (vars), that were not known at the time of
424
* creation of the menuentry classes (due to hints, or other reasons)
428
* n_parent: number of elements in menu of parent
429
* level: how deep this entry is nested in menu tree.
430
* prev_section: parent section name
432
void menuentry::postprocess(int n_parent, int level, const std::string& prev_section)
317
434
submenu_container::iterator i, i_next;
321
vars[PRIVATE_LEVEL_VAR]=itoString(level);
323
vars[SECTION_VAR]=prev;
324
for(i=submenus.begin(), index=0;
437
vars[PRIVATE_LEVEL_VAR] = itostring(level);
439
// If we are at the bottom of the hierarchy, use the "parents" section name.
441
vars[SECTION_VAR] = prev_section;
443
for (i = submenus.begin(); i != submenus.end(); ++index)
334
for(j=(*i).first.begin(); j!=(*i).first.end(); j++)
335
title += String("/") + (*j);
338
me->vars[SECTION_VAR]=title;
340
me->vars[PRIVATE_ENTRYINDEX_VAR]=itoString(index);
342
if(me->submenus.size())
343
me->postprocess(submenus.size(), level+1, title);
344
/* number of entries may have been changed by above call, need
346
if(!me->submenus.size()){
347
if(me->vars.find(COMMAND_VAR)==me->vars.end()){
348
/* this is an empty menu (without comand or submenus), so
448
string newsection = prev_section;
449
for(vector<string>::const_iterator j = i->first.begin(); j != i->first.end(); ++j)
450
newsection += '/' + *j;
452
menuentry *me = i->second;
453
me->vars[SECTION_VAR] = newsection;
455
// Get the real section name by removing title of the section name. This
456
// is useful when for example the title is "Foo version/456".
457
me->vars[BASESECTION_VAR] = newsection.substr(0, newsection.rfind(me->vars[TITLE_VAR]) - 1);
459
me->vars[PRIVATE_ENTRYINDEX_VAR] = itostring(index);
461
if (!me->submenus.empty())
462
me->postprocess(submenus.size(), level+1, newsection);
464
// Number of entries may have been changed by above call, so we need to
466
if (me->submenus.empty()) {
467
if (me->vars.find(COMMAND_VAR) == me->vars.end()) {
468
// This is an empty menu (without comand or submenus), so delete it.
354
(*i).second->vars[PRIVATE_ENTRYCOUNT_VAR]=itoString(submenus.size());
355
(*i).second->vars[PRIVATE_LEVEL_VAR]=itoString(level+1);
473
i->second->vars[PRIVATE_ENTRYCOUNT_VAR] = itostring(submenus.size());
474
i->second->vars[PRIVATE_LEVEL_VAR] = itostring(level+1);
358
/* don't use i here any more, it may have been erased above */
359
i=i_next; /* don't do i++, as *i now may not be defined */
477
// don't use i here any more, it may have been erased above
478
i = i_next; // don't do i++, as *i now may not be defined
363
482
generate_hotkeys();
365
vars[PRIVATE_ENTRYCOUNT_VAR]=itoString(n_parent);
484
vars[PRIVATE_ENTRYCOUNT_VAR] = itostring(n_parent);
368
char menuentry::hotkeyconv(char h){
369
if (config->hotkeycase)
487
char menuentry::hotkeyconv(char h)
489
if (menumethod->hotkeycase)
372
492
return tolower(h);
374
void menuentry::generate_hotkeys(){
495
void menuentry::generate_hotkeys()
375
497
string::size_type i,j;
376
498
list<int>::iterator k, old_k;
377
499
submenu_container::iterator subi;
378
map <String, String, less<String> >::iterator l;
500
map<string, string>::iterator l;
381
set<char, less<char> > used_chars;
503
set<char> used_chars;
388
if(config->hkexclude)
389
s=config->hkexclude->soutput(vars);
390
for(i=0;i!=s.length();i++)
509
if (menumethod->hkexclude)
510
s = menumethod->hkexclude->soutput(vars);
511
for (i = 0; i != s.length(); i++)
391
512
used_chars.insert(hotkeyconv(s[i]));
393
for(subi=submenus.begin(), i=0; subi!=submenus.end(); subi++, i++){
514
for(subi = submenus.begin(), i = 0; subi != submenus.end(); subi++, i++)
394
516
todo.push_back(i);
395
l=(*subi).second->vars.find(HOTKEY_VAR);
396
if(l!=(*subi).second->vars.end())
397
keys.push_back((*l).second);
517
l = subi->second->vars.find(HOTKEY_VAR);
518
if (l != subi->second->vars.end())
519
keys.push_back(l->second);
399
keys.push_back(str0);
400
keys[i]+=sort_hotkey((*subi).second->vars[TITLE_VAR]);
521
keys.push_back(str0);
522
keys[i] += sort_hotkey(subi->second->vars[TITLE_VAR]);
404
for(k=todo.begin();k!=todo.end();){
406
old_k=k++; //k++ here, to be able to todo.erase(old_k) safely.
407
if(j>=keys[i].length()){
409
todo.erase(old_k); //no hotkey found -- give up on this entry.
525
while (!todo.empty())
527
for (k = todo.begin();k != todo.end();)
530
old_k = k++; //k++ here, to be able to todo.erase(old_k) safely.
531
if (j >= keys[i].length()) {
533
todo.erase(old_k); //no hotkey found -- give up on this entry.
414
if(used_chars.find(hotkeyconv(c))==used_chars.end()){
415
todo.erase(old_k); //found a hotkey for this entry.
416
keys[i]=String("")+c;
417
used_chars.insert(hotkeyconv(c));
421
keys[i].replace(j,1,str0);
538
if (used_chars.find(hotkeyconv(c)) == used_chars.end()) {
539
todo.erase(old_k); //found a hotkey for this entry.
541
used_chars.insert(hotkeyconv(c));
544
keys[i].replace(j,1,str0);
426
for(subi=submenus.begin(), i=0; subi!=submenus.end(); subi++, i++){
429
(*subi).second->vars[HOTKEY_VAR]=String("")+c;
550
for (subi = submenus.begin(), i = 0; subi != submenus.end(); subi++, i++)
554
subi->second->vars[HOTKEY_VAR] = c;