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