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