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