]> git.lyx.org Git - lyx.git/blob - src/MenuBackend.C
Alfredo's second patch
[lyx.git] / src / MenuBackend.C
1 /* This file is part of
2  * ======================================================
3  *
4  *           LyX, The Document Processor
5  *
6  *           Copyright 1995 Matthias Ettrich
7  *           Copyright 1995-2001 The LyX Team.
8  *
9  *
10  * ====================================================== */
11
12 #include <config.h>
13
14 #include <algorithm>
15 #include "MenuBackend.h"
16 #include "lyxlex.h"
17 #include "LyXAction.h"
18 #include "debug.h"
19 #include "gettext.h"
20 #include "kbmap.h"
21 #include "lastfiles.h"
22 #include "lyxfunc.h"
23 #include "lyx_main.h" // for lastfiles
24 #include "bufferlist.h"
25 #include "buffer.h"
26 #include "format.h"
27 #include "exporter.h"
28 #include "importer.h"
29 #include "FloatList.h"
30 #include "toc.h"
31 #include "frontends/LyXView.h"
32 #include "support/LAssert.h"
33 #include "support/filetools.h"
34 #include "support/lyxfunctional.h"
35 #include "support/lstrings.h"
36
37 extern BufferList bufferlist;
38 extern boost::scoped_ptr<kb_keymap> toplevel_keymap;
39
40 using std::endl;
41 using std::vector;
42 using std::max;
43 using std::pair;
44 using std::find_if;
45 using std::sort;
46
47 // This is the global menu definition
48 MenuBackend menubackend;
49
50
51 MenuItem::MenuItem(Kind kind, string const & label,
52                    string const & command, bool optional)
53         : kind_(kind), label_(label), optional_(optional)
54 {
55         switch (kind) {
56         case Separator:
57         case Documents:
58         case Lastfiles:
59         case Toc:
60         case ViewFormats:
61         case UpdateFormats:
62         case ExportFormats:
63         case ImportFormats:
64         case FloatListInsert:
65         case FloatInsert:
66                 break;
67         case Command:
68                 action_ = lyxaction.LookupFunc(command);
69
70                 if (action_ == LFUN_UNKNOWN_ACTION) {
71                         lyxerr << "MenuItem(): LyX command `"
72                                << command << "' does not exist." << endl;
73                 }
74                 if (optional_)
75                         lyxerr[Debug::GUI] << "Optional item "
76                                            << command << endl;
77                 break;
78         case Submenu:
79                 submenuname_ = command;
80                 break;
81         }
82 }
83
84
85 MenuItem::MenuItem(Kind kind, string const & label, int action, bool optional)
86         : kind_(kind), label_(label), action_(action), submenuname_(),
87           optional_(optional)
88 {}
89
90
91 MenuItem::~MenuItem()
92 {}
93
94
95 void MenuItem::submenu(Menu * menu)
96 {
97         submenu_.reset(menu);
98 }
99
100
101 string const MenuItem::label() const
102 {
103         return token(label_, '|', 0);
104 }
105
106
107 string const MenuItem::shortcut() const
108 {
109         return token(label_, '|', 1);
110 }
111
112 string const MenuItem::binding() const
113 {
114         if (kind_ != Command)
115                 return string();
116
117         // Get the keys bound to this action, but keep only the
118         // first one later
119         string bindings = toplevel_keymap->findbinding(action_);
120
121         if (!bindings.empty()) {
122                 return bindings.substr(1, bindings.find(']') - 1);
123         } else
124                 return string();
125 }
126
127
128 Menu & Menu::add(MenuItem const & i, LyXView const * view)
129 {
130         if (!view) {
131                 items_.push_back(i);
132                 return *this;
133         }
134
135         switch (i.kind()) {
136         case MenuItem::Command:
137         {
138                 FuncStatus status =
139                         view->getLyXFunc().getStatus(i.action());
140                 if (status.unknown()
141                     || (status.disabled() && i.optional()))
142                         break;
143                 items_.push_back(i);
144                 items_.back().status(status);
145                 break;
146         }
147         case MenuItem::Submenu:
148         {
149                 if (i.submenu()) {
150                         bool disabled = true;
151                         for (const_iterator cit = i.submenu()->begin();
152                              cit != i.submenu()->end(); ++cit) {
153                                 if ((cit->kind() == MenuItem::Command
154                                      || cit->kind() == MenuItem::Submenu)
155                                     && !cit->status().disabled()) {
156                                         disabled = false;
157                                         break;
158                                 }
159                         }
160                         if (!disabled || !i.optional()) {
161                                 items_.push_back(i);
162                                 items_.back().status().disabled(disabled);
163                         }
164                 }
165                 else
166                         items_.push_back(i);
167                 break;
168         }
169         case MenuItem::Separator:
170                 if (!items_.empty()
171                     && items_.back().kind() != MenuItem::Separator)
172                         items_.push_back(i);
173                 break;
174         default:
175                 items_.push_back(i);
176         }
177
178         return *this;
179 }
180
181
182 Menu & Menu::read(LyXLex & lex)
183 {
184         enum Menutags {
185                 md_item = 1,
186                 md_documents,
187                 md_endmenu,
188                 md_exportformats,
189                 md_importformats,
190                 md_lastfiles,
191                 md_optitem,
192                 md_optsubmenu,
193                 md_separator,
194                 md_submenu,
195                 md_toc,
196                 md_updateformats,
197                 md_viewformats,
198                 md_floatlistinsert,
199                 md_floatinsert,
200                 md_last
201         };
202
203         struct keyword_item menutags[md_last - 1] = {
204                 { "documents", md_documents },
205                 { "end", md_endmenu },
206                 { "exportformats", md_exportformats },
207                 { "floatinsert", md_floatinsert },
208                 { "floatlistinsert", md_floatlistinsert },
209                 { "importformats", md_importformats },
210                 { "item", md_item },
211                 { "lastfiles", md_lastfiles },
212                 { "optitem", md_optitem },
213                 { "optsubmenu", md_optsubmenu },
214                 { "separator", md_separator },
215                 { "submenu", md_submenu },
216                 { "toc", md_toc },
217                 { "updateformats", md_updateformats },
218                 { "viewformats", md_viewformats }
219         };
220
221         lex.pushTable(menutags, md_last - 1);
222         if (lyxerr.debugging(Debug::PARSER))
223                 lex.printTable(lyxerr);
224
225         bool quit = false;
226         bool optional = false;
227
228         while (lex.isOK() && !quit) {
229                 switch (lex.lex()) {
230                 case md_optitem:
231                         optional = true;
232                         // fallback to md_item
233                 case md_item: {
234                         lex.next(true);
235                         string const name = _(lex.getString());
236                         lex.next(true);
237                         string const command = lex.getString();
238                         add(MenuItem(MenuItem::Command, name,
239                                      command, optional));
240                         optional = false;
241                         break;
242                 }
243
244                 case md_separator:
245                         add(MenuItem(MenuItem::Separator));
246                         break;
247
248                 case md_lastfiles:
249                         add(MenuItem(MenuItem::Lastfiles));
250                         break;
251
252                 case md_documents:
253                         add(MenuItem(MenuItem::Documents));
254                         break;
255
256                 case md_toc:
257                         add(MenuItem(MenuItem::Toc));
258                         break;
259
260                 case md_viewformats:
261                         add(MenuItem(MenuItem::ViewFormats));
262                         break;
263
264                 case md_updateformats:
265                         add(MenuItem(MenuItem::UpdateFormats));
266                         break;
267
268                 case md_exportformats:
269                         add(MenuItem(MenuItem::ExportFormats));
270                         break;
271
272                 case md_importformats:
273                         add(MenuItem(MenuItem::ImportFormats));
274                         break;
275
276                 case md_floatlistinsert:
277                         add(MenuItem(MenuItem::FloatListInsert));
278                         break;
279
280                 case md_floatinsert:
281                         add(MenuItem(MenuItem::FloatInsert));
282                         break;
283
284                 case md_optsubmenu:
285                         optional = true;
286                         // fallback to md_submenu
287                 case md_submenu: {
288                         lex.next(true);
289                         string const mlabel = _(lex.getString());
290                         lex.next(true);
291                         string const mname = lex.getString();
292                         add(MenuItem(MenuItem::Submenu, mlabel, mname,
293                                      optional));
294                         optional = false;
295                         break;
296                 }
297
298                 case md_endmenu:
299                         quit = true;
300                         break;
301
302                 default:
303                         lex.printError("Menu::read: "
304                                        "Unknown menu tag: `$$Token'");
305                         break;
306                 }
307         }
308         lex.popTable();
309         return *this;
310 }
311
312
313 void Menu::checkShortcuts() const
314 {
315         // This is a quadratic algorithm, but we do not care because
316         // it is used for debugging only.
317         for (const_iterator it1 = begin(); it1 != end(); ++it1) {
318                 string shortcut = it1->shortcut();
319                 if (shortcut.empty())
320                         continue;
321                 if (!contains(it1->label(), shortcut))
322                         lyxerr << "Menu warning: menu entry \""
323                                << it1->label()
324                                << "\" does not contain shortcut `"
325                                << shortcut << '\'' << endl;
326                 for (const_iterator it2 = begin(); it2 != it1 ; ++it2) {
327                         if (!compare_ascii_no_case(it2->shortcut(), shortcut)) {
328                                 lyxerr << "Menu warning: menu entries "
329                                        << '"' << it1->fulllabel()
330                                        << "\" and \"" << it2->fulllabel()
331                                        << "\" share the same shortcut."
332                                        << endl;
333                         }
334                 }
335         }
336 }
337
338
339 namespace {
340
341 class compare_format {
342 public:
343         bool operator()(Format const * p1, Format const * p2) {
344                 return *p1 < *p2;
345         }
346 };
347
348 string const limit_string_length(string const & str)
349 {
350         string::size_type const max_item_length = 45;
351
352         if (str.size() > max_item_length)
353                 return str.substr(0, max_item_length - 3) + "...";
354         else
355                 return str;
356 }
357
358
359 void expandLastfiles(Menu & tomenu, LyXView const * view)
360 {
361         int ii = 1;
362         LastFiles::const_iterator lfit = lastfiles->begin();
363         LastFiles::const_iterator end = lastfiles->end();
364
365         for (; lfit != end && ii < 10; ++lfit, ++ii) {
366                 string const label = tostr(ii) + ". "
367                         + MakeDisplayPath((*lfit), 30)
368                         + '|' + tostr(ii);
369                 int const action = lyxaction.
370                         getPseudoAction(LFUN_FILE_OPEN,
371                                         (*lfit));
372                 tomenu.add(MenuItem(MenuItem::Command, label, action), view);
373         }
374 }
375
376 void expandDocuments(Menu & tomenu, LyXView const * view)
377 {
378         typedef vector<string> Strings;
379         Strings const names = bufferlist.getFileNames();
380
381         if (names.empty()) {
382                 tomenu.add(MenuItem(MenuItem::Command, _("No Documents Open!"),
383                                     LFUN_NOACTION), view);
384                 return;
385         }
386
387         int ii = 1;
388         Strings::const_iterator docit = names.begin();
389         Strings::const_iterator end = names.end();
390         for (; docit != end; ++docit, ++ii) {
391                 int const action =
392                         lyxaction.getPseudoAction(LFUN_SWITCHBUFFER, *docit);
393                 string label = MakeDisplayPath(*docit, 20);
394                 if (ii < 10)
395                         label = tostr(ii) + ". " + label + '|' + tostr(ii);
396                 tomenu.add(MenuItem(MenuItem::Command, label, action), view);
397         }
398 }
399
400
401 void expandFormats(MenuItem::Kind kind, Menu & tomenu, LyXView const * view)
402 {
403         if (!view->buffer() && kind != MenuItem::ImportFormats) {
404                 tomenu.add(MenuItem(MenuItem::Command,
405                                     _("No Documents Open!"), LFUN_NOACTION),
406                                     view);
407                 return;
408         }
409
410         typedef vector<Format const *> Formats;
411         Formats formats;
412         kb_action action;
413
414         switch (kind) {
415         case MenuItem::ImportFormats:
416                 formats = Importer::GetImportableFormats();
417                 action = LFUN_IMPORT;
418                 break;
419         case MenuItem::ViewFormats:
420                 formats = Exporter::GetExportableFormats(view->buffer(), true);
421                 action = LFUN_PREVIEW;
422                 break;
423         case MenuItem::UpdateFormats:
424                 formats = Exporter::GetExportableFormats(view->buffer(), true);
425                 action = LFUN_UPDATE;
426                 break;
427         default:
428                 formats = Exporter::GetExportableFormats(view->buffer(), false);
429                 action = LFUN_EXPORT;
430         }
431         sort(formats.begin(), formats.end(), compare_format());
432
433         Formats::const_iterator fit = formats.begin();
434         Formats::const_iterator end = formats.end();
435         for (; fit != end ; ++fit) {
436                 if ((*fit)->dummy())
437                         continue;
438                 string label = (*fit)->prettyname();
439                 // we need to hide the default graphic export formats
440                 // from the external menu, because we need them only
441                 // for the internal lyx-view and external latex run
442                 if (label == "EPS" || label == "XPM" || label == "PNG")
443                         continue;
444
445                 if (kind == MenuItem::ImportFormats) {
446                         if ((*fit)->name() == "text")
447                                 label = _("ASCII text as lines");
448                         else if ((*fit)->name() == "textparagraph")
449                                 label = _("ASCII text as paragraphs");
450                         label += "...";
451                 }
452                 if (!(*fit)->shortcut().empty())
453                         label += '|' + (*fit)->shortcut();
454                 int const action2 = lyxaction.
455                         getPseudoAction(action, (*fit)->name());
456                 tomenu.add(MenuItem(MenuItem::Command, label, action2),
457                            view);
458         }
459 }
460
461
462 void expandFloatListInsert(Menu & tomenu, LyXView const * view)
463 {
464         if (!view->buffer()) {
465                 tomenu.add(MenuItem(MenuItem::Command,
466                                     _("No Documents Open!"), LFUN_NOACTION),
467                            view);
468                 return;
469         }
470
471         FloatList const & floats =
472                 view->buffer()->params.getLyXTextClass().floats();
473         FloatList::const_iterator cit = floats.begin();
474         FloatList::const_iterator end = floats.end();
475         for (; cit != end; ++cit) {
476                 int const action =  lyxaction
477                         .getPseudoAction(LFUN_FLOAT_LIST, cit->second.type());
478                 tomenu.add(MenuItem(MenuItem::Command,
479                                     _(cit->second.listName()), action),
480                            view);
481         }
482 }
483
484
485 void expandFloatInsert(Menu & tomenu, LyXView const * view)
486 {
487         if (!view->buffer()) {
488                 tomenu.add(MenuItem(MenuItem::Command,
489                                     _("No Documents Open!"), LFUN_NOACTION),
490                            view);
491                 return;
492         }
493
494         FloatList const & floats =
495                 view->buffer()->params.getLyXTextClass().floats();
496         FloatList::const_iterator cit = floats.begin();
497         FloatList::const_iterator end = floats.end();
498         for (; cit != end; ++cit) {
499                 // normal float
500                 int const action =
501                         lyxaction.getPseudoAction(LFUN_INSET_FLOAT,
502                                                   cit->second.type());
503                 string const label = _(cit->second.name());
504                 tomenu.add(MenuItem(MenuItem::Command, label, action),
505                            view);
506         }
507 }
508
509
510 Menu::size_type const max_number_of_items = 25;
511
512 void expandToc2(Menu & tomenu, toc::Toc const & toc_list,
513                 toc::Toc::size_type from, toc::Toc::size_type to, int depth)
514 {
515         int shortcut_count = 0;
516         if (to - from <= max_number_of_items) {
517                 for (toc::Toc::size_type i = from; i < to; ++i) {
518                         int const action = toc_list[i].action();
519                         string label(4 * max(0, toc_list[i].depth - depth),' ');
520                         label += limit_string_length(toc_list[i].str);
521                         if (toc_list[i].depth == depth
522                             && ++shortcut_count <= 9) {
523                                 label += '|' + tostr(shortcut_count);
524                         }
525                         tomenu.add(MenuItem(MenuItem::Command, label, action));
526                 }
527         } else {
528                 toc::Toc::size_type pos = from;
529                 while (pos < to) {
530                         toc::Toc::size_type new_pos = pos + 1;
531                         while (new_pos < to &&
532                                toc_list[new_pos].depth > depth)
533                                 ++new_pos;
534
535                         int const action = toc_list[pos].action();
536                         string label(4 * max(0, toc_list[pos].depth - depth), ' ');
537                         label += limit_string_length(toc_list[pos].str);
538                         if (toc_list[pos].depth == depth &&
539                             ++shortcut_count <= 9)
540                                 label += '|' + tostr(shortcut_count);
541
542                         if (new_pos == pos + 1) {
543                                 tomenu.add(MenuItem(MenuItem::Command,
544                                                     label, action));
545                         } else {
546                                 MenuItem item(MenuItem::Submenu, label);
547                                 item.submenu(new Menu);
548                                 expandToc2(*item.submenu(),
549                                            toc_list, pos, new_pos, depth + 1);
550                                 tomenu.add(item);
551                         }
552                         pos = new_pos;
553                 }
554         }
555 }
556
557
558 void expandToc(Menu & tomenu, LyXView const * view)
559 {
560         // To make things very cleanly, we would have to pass view to
561         // all MenuItem constructors and to expandToc2. However, we
562         // know that all the entries in a TOC will be have status_ ==
563         // OK, so we avoid this unnecessary overhead (JMarc)
564
565         if (!view->buffer()) {
566                 tomenu.add(MenuItem(MenuItem::Command,
567                                     _("No Documents Open!"), LFUN_NOACTION),
568                            view);
569                 return;
570         }
571
572         toc::TocList toc_list = toc::getTocList(view->buffer());
573         toc::TocList::const_iterator cit = toc_list.begin();
574         toc::TocList::const_iterator end = toc_list.end();
575         for (; cit != end; ++cit) {
576                 // Handle this later
577                 if (cit->first == "TOC")
578                         continue;
579
580                 // All the rest is for floats
581                 Menu * menu = new Menu;
582                 toc::Toc::const_iterator ccit = cit->second.begin();
583                 toc::Toc::const_iterator eend = cit->second.end();
584                 for (; ccit != eend; ++ccit) {
585                         string const label = limit_string_length(ccit->str);
586                         menu->add(MenuItem(MenuItem::Command,
587                                            label, ccit->action()));
588                 }
589                 string const & floatName = cit->first;
590                 // Is the _(...) really needed here? (Lgb)
591                 MenuItem item(MenuItem::Submenu, _(floatName));
592                 item.submenu(menu);
593                 tomenu.add(item);
594         }
595
596         // Handle normal TOC
597         cit = toc_list.find("TOC");
598         if (cit == end) {
599                 tomenu.add(MenuItem(MenuItem::Command,
600                                     _("No Table of contents")),
601                            view);
602         } else {
603                 expandToc2(tomenu, cit->second, 0, cit->second.size(), 0);
604         }
605 }
606
607
608 } // namespace anon
609
610
611 void MenuBackend::expand(Menu const & frommenu, Menu & tomenu,
612                          LyXView const * view) const
613 {
614         for (Menu::const_iterator cit = frommenu.begin();
615              cit != frommenu.end() ; ++cit) {
616                 switch (cit->kind()) {
617                 case MenuItem::Lastfiles:
618                         expandLastfiles(tomenu, view);
619                         break;
620
621                 case MenuItem::Documents:
622                         expandDocuments(tomenu, view);
623                         break;
624
625                 case MenuItem::ImportFormats:
626                 case MenuItem::ViewFormats:
627                 case MenuItem::UpdateFormats:
628                 case MenuItem::ExportFormats:
629                         expandFormats(cit->kind(), tomenu, view);
630                         break;
631
632                 case MenuItem::FloatListInsert:
633                         expandFloatListInsert(tomenu, view);
634                         break;
635
636                 case MenuItem::FloatInsert:
637                         expandFloatInsert(tomenu, view);
638                         break;
639
640                 case MenuItem::Toc:
641                         expandToc(tomenu, view);
642                         break;
643
644                 case MenuItem::Submenu: {
645                         MenuItem item(*cit);
646                         item.submenu(new Menu(cit->submenuname()));
647                         expand(getMenu(cit->submenuname()),
648                                *item.submenu(), view);
649                         tomenu.add(item, view);
650                 }
651                 break;
652
653                 default:
654                         tomenu.add(*cit, view);
655                 }
656         }
657
658         // we do not want the menu to end with a separator
659         if (!tomenu.empty()
660             && tomenu.items_.back().kind() == MenuItem::Separator)
661                 tomenu.items_.pop_back();
662
663         // Check whether the shortcuts are unique
664         if (lyxerr.debugging(Debug::GUI))
665                 tomenu.checkShortcuts();
666 }
667
668
669 bool Menu::hasSubmenu(string const & name) const
670 {
671         return find_if(begin(), end(),
672                        lyx::compare_memfun(&MenuItem::submenuname,
673                                            name)) != end();
674 }
675
676
677 void MenuBackend::read(LyXLex & lex)
678 {
679         enum Menutags {
680                 md_menu = 1,
681                 md_menubar,
682                 md_endmenuset,
683                 md_last
684         };
685
686         struct keyword_item menutags[md_last - 1] = {
687                 { "end", md_endmenuset },
688                 { "menu", md_menu },
689                 { "menubar", md_menubar }
690         };
691
692         //consistency check
693         if (compare_ascii_no_case(lex.getString(), "menuset")) {
694                 lyxerr << "Menubackend::read: ERROR wrong token:`"
695                        << lex.getString() << '\'' << endl;
696         }
697
698         lex.pushTable(menutags, md_last - 1);
699         if (lyxerr.debugging(Debug::PARSER))
700                 lex.printTable(lyxerr);
701
702         bool quit = false;
703
704         while (lex.isOK() && !quit) {
705                 switch (lex.lex()) {
706                 case md_menubar:
707                         menubar_.read(lex);
708                         break;
709                 case md_menu: {
710                         lex.next(true);
711                         string const name = lex.getString();
712                         if (hasMenu(name)) {
713                                 getMenu(name).read(lex);
714                         } else {
715                                 Menu menu(name);
716                                 menu.read(lex);
717                                 add(menu);
718                         }
719                         break;
720                 }
721                 case md_endmenuset:
722                         quit = true;
723                         break;
724                 default:
725                         lex.printError("menubackend::read: "
726                                        "Unknown menu tag: `$$Token'");
727                         break;
728                 }
729         }
730         lex.popTable();
731 }
732
733
734 void MenuBackend::add(Menu const & menu)
735 {
736         menulist_.push_back(menu);
737 }
738
739
740 bool MenuBackend::hasMenu(string const & name) const
741 {
742         return find_if(begin(), end(),
743                        lyx::compare_memfun(&Menu::name, name)) != end();
744 }
745
746
747 Menu const & MenuBackend::getMenu(string const & name) const
748 {
749         const_iterator cit = find_if(begin(), end(),
750                                      lyx::compare_memfun(&Menu::name, name));
751         if (cit == end())
752                 lyxerr << "No submenu named " << name << endl;
753         lyx::Assert(cit != end());
754         return (*cit);
755 }
756
757
758 Menu & MenuBackend::getMenu(string const & name)
759 {
760         MenuList::iterator it =
761                 find_if(menulist_.begin(), menulist_.end(),
762                         lyx::compare_memfun(&Menu::name, name));
763         lyx::Assert(it != menulist_.end());
764         return (*it);
765 }
766
767
768 Menu const & MenuBackend::getMenubar() const
769 {
770         return menubar_;
771 }