1 /**
2  * \file InsetInfo.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Bo Peng
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10 #include <config.h>
11 
12 #include "InsetInfo.h"
13 #include "LyX.h"
14 #include "Buffer.h"
15 #include "BufferParams.h"
16 #include "BufferView.h"
17 #include "CutAndPaste.h"
18 #include "FuncRequest.h"
19 #include "FuncStatus.h"
20 #include "InsetGraphics.h"
21 #include "InsetSpecialChar.h"
22 #include "KeyMap.h"
23 #include "LaTeXFeatures.h"
24 #include "Language.h"
25 #include "LayoutFile.h"
26 #include "Length.h"
27 #include "LyXAction.h"
28 #include "LyXRC.h"
29 #include "LyXVC.h"
30 #include "Lexer.h"
31 #include "ParagraphParameters.h"
32 #include "version.h"
33 
34 #include "frontends/Application.h"
35 
36 #include "support/convert.h"
37 #include "support/debug.h"
38 #include "support/docstream.h"
39 #include "support/docstring_list.h"
40 #include "support/ExceptionMessage.h"
41 #include "support/FileName.h"
42 #include "support/filetools.h"
43 #include "support/gettext.h"
44 #include "support/lstrings.h"
45 #include "support/qstring_helpers.h"
46 #include "support/Translator.h"
47 
48 #include <sstream>
49 
50 #include <QtGui/QImage>
51 
52 using namespace std;
53 using namespace lyx::support;
54 
55 namespace lyx {
56 
57 namespace {
58 
59 typedef Translator<InsetInfo::info_type, string> NameTranslator;
60 
initTranslator()61 NameTranslator const initTranslator()
62 {
63 	NameTranslator translator(InsetInfo::UNKNOWN_INFO, "unknown");
64 
65 	translator.addPair(InsetInfo::SHORTCUTS_INFO, "shortcuts");
66 	translator.addPair(InsetInfo::SHORTCUT_INFO, "shortcut");
67 	translator.addPair(InsetInfo::LYXRC_INFO, "lyxrc");
68 	translator.addPair(InsetInfo::PACKAGE_INFO, "package");
69 	translator.addPair(InsetInfo::TEXTCLASS_INFO, "textclass");
70 	translator.addPair(InsetInfo::MENU_INFO, "menu");
71 	translator.addPair(InsetInfo::ICON_INFO, "icon");
72 	translator.addPair(InsetInfo::BUFFER_INFO, "buffer");
73 	translator.addPair(InsetInfo::LYX_INFO, "lyxinfo");
74 
75 	return translator;
76 }
77 
78 /// The translator between the information type enum and corresponding string.
nameTranslator()79 NameTranslator const & nameTranslator()
80 {
81 	static NameTranslator const translator = initTranslator();
82 	return translator;
83 }
84 
85 } // namespace
86 
87 /////////////////////////////////////////////////////////////////////////
88 //
89 // InsetInfo
90 //
91 /////////////////////////////////////////////////////////////////////////
92 
93 
94 
InsetInfo(Buffer * buf,string const & name)95 InsetInfo::InsetInfo(Buffer * buf, string const & name)
96 	: InsetCollapsible(buf), type_(UNKNOWN_INFO), name_(),
97 	force_ltr_(false)
98 {
99 	setInfo(name);
100 	status_ = Collapsed;
101 }
102 
103 
editXY(Cursor & cur,int x,int y)104 Inset * InsetInfo::editXY(Cursor & cur, int x, int y)
105 {
106 	// do not allow the cursor to be set in this Inset
107 	return Inset::editXY(cur, x, y);
108 }
109 
110 
infoType() const111 string InsetInfo::infoType() const
112 {
113 	return nameTranslator().find(type_);
114 }
115 
116 
layoutName() const117 docstring InsetInfo::layoutName() const
118 {
119 	return from_ascii("Info:" + infoType());
120 }
121 
122 
toolTip(BufferView const &,int,int) const123 docstring InsetInfo::toolTip(BufferView const &, int, int) const
124 {
125 	return bformat(_("Information regarding %1$s '%2$s'"),
126 			_(infoType()), from_utf8(name_));
127 }
128 
129 
read(Lexer & lex)130 void InsetInfo::read(Lexer & lex)
131 {
132 	string token;
133 	while (lex.isOK()) {
134 		lex.next();
135 		token = lex.getString();
136 		if (token == "type") {
137 			lex.next();
138 			token = lex.getString();
139 			type_ = nameTranslator().find(token);
140 		} else if (token == "arg") {
141 			lex.next(true);
142 			name_ = lex.getString();
143 		} else if (token == "\\end_inset")
144 			break;
145 	}
146 	if (token != "\\end_inset") {
147 		lex.printError("Missing \\end_inset at this point");
148 		throw ExceptionMessage(WarningException,
149 			_("Missing \\end_inset at this point."),
150 			from_utf8(token));
151 	}
152 	updateInfo();
153 }
154 
155 
write(ostream & os) const156 void InsetInfo::write(ostream & os) const
157 {
158 	os << "Info\ntype  \"" << infoType()
159 	   << "\"\narg   " << Lexer::quoteString(name_);
160 }
161 
162 
validateModifyArgument(docstring const & arg) const163 bool InsetInfo::validateModifyArgument(docstring const & arg) const
164 {
165 	string type;
166 	string const name = trim(split(to_utf8(arg), type, ' '));
167 
168 	switch (nameTranslator().find(type)) {
169 	case UNKNOWN_INFO:
170 		return false;
171 
172 	case SHORTCUT_INFO:
173 	case SHORTCUTS_INFO:
174 	case MENU_INFO: {
175 		FuncRequest func = lyxaction.lookupFunc(name);
176 		return func.action() != LFUN_UNKNOWN_ACTION;
177 	}
178 
179 	case ICON_INFO: {
180 		FuncCode const action = lyxaction.lookupFunc(name).action();
181 		if (action == LFUN_UNKNOWN_ACTION) {
182 			string dir = "images";
183 			return !imageLibFileSearch(dir, name, "svgz,png").empty();
184 		}
185 		return true;
186 	}
187 
188 	case LYXRC_INFO: {
189 		ostringstream oss;
190 		lyxrc.write(oss, true, name);
191 		return !oss.str().empty();
192 	}
193 
194 	case PACKAGE_INFO:
195 	case TEXTCLASS_INFO:
196 		return true;
197 
198 	case BUFFER_INFO:
199 		if (name == "name" || name == "path" || name == "class")
200 			return true;
201 		if (name == "vcs-revision" || name == "vcs-tree-revision" ||
202 		       name == "vcs-author" || name == "vcs-date" || name == "vcs-time")
203 			return buffer().lyxvc().inUse();
204 		return false;
205 
206 	case LYX_INFO:
207 		return name == "version";
208 	}
209 
210 	return false;
211 }
212 
213 
showInsetDialog(BufferView * bv) const214 bool InsetInfo::showInsetDialog(BufferView * bv) const
215 {
216 	bv->showDialog("info");
217 	return true;
218 }
219 
220 
getStatus(Cursor & cur,FuncRequest const & cmd,FuncStatus & flag) const221 bool InsetInfo::getStatus(Cursor & cur, FuncRequest const & cmd,
222 		FuncStatus & flag) const
223 {
224 	switch (cmd.action()) {
225 	case LFUN_INSET_SETTINGS:
226 		return InsetCollapsible::getStatus(cur, cmd, flag);
227 
228 	case LFUN_INSET_DIALOG_UPDATE:
229 	case LFUN_INSET_COPY_AS:
230 		flag.setEnabled(true);
231 		return true;
232 
233 	case LFUN_INSET_MODIFY:
234 		if (validateModifyArgument(cmd.argument())) {
235 			flag.setEnabled(true);
236 			return true;
237 		}
238 		//fall through
239 
240 	default:
241 		return false;
242 	}
243 }
244 
245 
doDispatch(Cursor & cur,FuncRequest & cmd)246 void InsetInfo::doDispatch(Cursor & cur, FuncRequest & cmd)
247 {
248 	switch (cmd.action()) {
249 	case LFUN_INSET_MODIFY:
250 		cur.recordUndo();
251 		setInfo(to_utf8(cmd.argument()));
252 		break;
253 
254 	case LFUN_INSET_COPY_AS: {
255 		cap::clearSelection();
256 		Cursor copy(cur);
257 		copy.pushBackward(*this);
258 		copy.pit() = 0;
259 		copy.pos() = 0;
260 		copy.resetAnchor();
261 		copy.pit() = copy.lastpit();
262 		copy.pos() = copy.lastpos();
263 		copy.setSelection();
264 		cap::copySelection(copy);
265 		break;
266 	}
267 
268 	default:
269 		InsetCollapsible::doDispatch(cur, cmd);
270 		break;
271 	}
272 }
273 
274 
setInfo(string const & name)275 void InsetInfo::setInfo(string const & name)
276 {
277 	if (name.empty())
278 		return;
279 	// info_type name
280 	string type;
281 	name_ = trim(split(name, type, ' '));
282 	type_ = nameTranslator().find(type);
283 	updateInfo();
284 }
285 
286 
error(string const & err)287 void InsetInfo::error(string const & err)
288 {
289 	setText(bformat(_(err), from_utf8(name_)),
290 		Font(inherit_font, buffer().params().language), false);
291 }
292 
293 
setText(docstring const & str)294 void InsetInfo::setText(docstring const & str)
295 {
296 	setText(str, Font(inherit_font, buffer().params().language), false);
297 }
298 
299 
forceLTR(OutputParams const &) const300 bool InsetInfo::forceLTR(OutputParams const &) const
301 {
302 	return !buffer().params().language->rightToLeft() || force_ltr_;
303 }
304 
305 
updateInfo()306 void InsetInfo::updateInfo()
307 {
308 	BufferParams const & bp = buffer().params();
309 
310 	force_ltr_ = false;
311 	switch (type_) {
312 	case UNKNOWN_INFO:
313 		error("Unknown Info: %1$s");
314 		break;
315 	case SHORTCUT_INFO:
316 	case SHORTCUTS_INFO: {
317 		FuncRequest const func = lyxaction.lookupFunc(name_);
318 		if (func.action() == LFUN_UNKNOWN_ACTION) {
319 			error("Unknown action %1$s");
320 			break;
321 		}
322 		KeyMap::Bindings bindings = theTopLevelKeymap().findBindings(func);
323 		if (bindings.empty()) {
324 			// It is impropriate to use error() for undefined shortcut
325 			setText(_("undefined"));
326 			break;
327 		}
328 		docstring sequence;
329 		if (type_ == SHORTCUT_INFO)
330 			sequence = bindings.begin()->print(KeySequence::ForGui);
331 		else
332 			sequence = theTopLevelKeymap().printBindings(func, KeySequence::ForGui);
333 		// QKeySequence returns special characters for keys on the mac
334 		// Since these are not included in many fonts, we
335 		// re-translate them to textual names (see #10641)
336 		odocstringstream ods;
337 		docstring gui;
338 		string const lcode = bp.language->code();
339 		for (size_t n = 0; n < sequence.size(); ++n) {
340 			char_type const c = sequence[n];
341 			switch(c) {
342 			case 0x21b5://Return
343 				gui = _("Return[[Key]]");
344 				ods << translateIfPossible(from_ascii("Return[[Key]]"), lcode);
345 				break;
346 			case 0x21b9://Tab both directions (Win)
347 				gui = _("Tab[[Key]]");
348 				ods << translateIfPossible(from_ascii("Tab[[Key]]"), lcode);
349 				break;
350 			case 0x21de://Qt::Key_PageUp
351 				gui = _("PgUp");
352 				ods << translateIfPossible(from_ascii("PgUp"), lcode);
353 				break;
354 			case 0x21df://Qt::Key_PageDown
355 				gui = _("PgDown");
356 				ods << translateIfPossible(from_ascii("PgDown"), lcode);
357 				break;
358 			case 0x21e4://Qt::Key_Backtab
359 				gui = _("Backtab");
360 				ods << translateIfPossible(from_ascii("Backtab"), lcode);
361 				break;
362 			case 0x21e5://Qt::Key_Tab
363 				gui = _("Tab");
364 				ods << translateIfPossible(from_ascii("Tab"), lcode);
365 				break;
366 			case 0x21e7://Shift
367 				gui = _("Shift");
368 				ods << translateIfPossible(from_ascii("Shift"), lcode);
369 				break;
370 			case 0x21ea://Qt::Key_CapsLock
371 				gui = _("CapsLock");
372 				ods << translateIfPossible(from_ascii("CapsLock"), lcode);
373 				break;
374 			case 0x2303://Control
375 				gui = _("Control[[Key]]");
376 				ods << translateIfPossible(from_ascii("Control[[Key]]"), lcode);
377 				break;
378 			case 0x2318://CMD
379 				gui = _("Command[[Key]]");
380 				ods << translateIfPossible(from_ascii("Command[[Key]]"), lcode);
381 				break;
382 			case 0x2324://Qt::Key_Enter
383 				gui = _("Return[[Key]]");
384 				ods << translateIfPossible(from_ascii("Return[[Key]]"), lcode);
385 				break;
386 			case 0x2325://Option key
387 				gui = _("Option[[Key]]");
388 				ods << translateIfPossible(from_ascii("Option[[Key]]"), lcode);
389 				break;
390 			case 0x2326://Qt::Key_Delete
391 				gui = _("Delete[[Key]]");
392 				ods << translateIfPossible(from_ascii("Delete[[Key]]"), lcode);
393 				break;
394 			case 0x232b://Qt::Key_Backspace
395 				gui = _("Fn+Del");
396 				ods << translateIfPossible(from_ascii("Fn+Delete"), lcode);
397 				break;
398 			case 0x238b://Qt::Key_Escape
399 				gui = _("Esc");
400 				ods << translateIfPossible(from_ascii("Esc"), lcode);
401 				break;
402 			default:
403 				ods.put(c);
404 			}
405 		}
406 		setText(ods.str());
407 		force_ltr_ = !bp.language->rightToLeft();
408 		break;
409 	}
410 	case LYXRC_INFO: {
411 		ostringstream oss;
412 		if (name_.empty()) {
413 			setText(_("undefined"));
414 			break;
415 		}
416 		lyxrc.write(oss, true, name_);
417 		string result = oss.str();
418 		if (result.size() < 2) {
419 			setText(_("undefined"));
420 			break;
421 		}
422 		string::size_type loc = result.rfind("\n", result.size() - 2);
423 		loc = loc == string::npos ? 0 : loc + 1;
424 		if (result.size() < loc + name_.size() + 1
425 			  || result.substr(loc + 1, name_.size()) != name_) {
426 			setText(_("undefined"));
427 			break;
428 		}
429 		// remove leading comments and \\name and space
430 		result = result.substr(loc + name_.size() + 2);
431 
432 		// remove \n and ""
433 		result = rtrim(result, "\n");
434 		result = trim(result, "\"");
435 		setText(from_utf8(result));
436 		break;
437 	}
438 	case PACKAGE_INFO:
439 		// check in packages.lst
440 		setText(LaTeXFeatures::isAvailable(name_) ? _("yes") : _("no"));
441 		break;
442 
443 	case TEXTCLASS_INFO: {
444 		// name_ is the class name
445 		LayoutFileList const & list = LayoutFileList::get();
446 		bool available = false;
447 		if (list.haveClass(name_))
448 			available = list[name_].isTeXClassAvailable();
449 		setText(available ? _("yes") : _("no"));
450 		break;
451 	}
452 	case MENU_INFO: {
453 		docstring_list names;
454 		FuncRequest const func = lyxaction.lookupFunc(name_);
455 		if (func.action() == LFUN_UNKNOWN_ACTION) {
456 			error("Unknown action %1$s");
457 			break;
458 		}
459 		// iterate through the menubackend to find it
460 		if (!theApp()) {
461 			error("Can't determine menu entry for action %1$s in batch mode");
462 			break;
463 		}
464 		if (!theApp()->searchMenu(func, names)) {
465 			error("No menu entry for action %1$s");
466 			break;
467 		}
468 		// if found, return its path.
469 		clear();
470 		Paragraph & par = paragraphs().front();
471 		Font const f(inherit_font, buffer().params().language);
472 		//Font fu = f;
473 		//fu.fontInfo().setUnderbar(FONT_ON);
474 		docstring_list::const_iterator beg = names.begin();
475 		docstring_list::const_iterator end = names.end();
476 		for (docstring_list::const_iterator it = beg ;
477 		     it != end ; ++it) {
478 			// do not insert > for the top level menu item
479 			if (it != beg)
480 				par.insertInset(par.size(), new InsetSpecialChar(InsetSpecialChar::MENU_SEPARATOR),
481 						f, Change(Change::UNCHANGED));
482 			//FIXME: add proper underlines here. This
483 			// involves rewriting searchMenu used above to
484 			// return a vector of menus. If we do not do
485 			// that, we might as well use below
486 			// Paragraph::insert on each string (JMarc)
487 			for (size_type i = 0; i != it->length(); ++i)
488 				par.insertChar(par.size(), (*it)[i],
489 					       f, Change(Change::UNCHANGED));
490 		}
491 		break;
492 	}
493 	case ICON_INFO: {
494 		FuncRequest func = lyxaction.lookupFunc(name_);
495 		docstring icon_name = frontend::Application::iconName(func, true);
496 		// FIXME: We should use the icon directly instead of
497 		// going through FileName. The code below won't work
498 		// if the icon is embedded in the executable through
499 		// the Qt resource system.
500 		// This is only a negligible performance problem:
501 		// If the installed icon differs from the resource icon the
502 		// installed one is preferred anyway, and all icons that are
503 		// embedded in the resources are installed as well.
504 		FileName file(to_utf8(icon_name));
505 		if (file.onlyFileNameWithoutExt() == "unknown") {
506 			string dir = "images";
507 			FileName file2(imageLibFileSearch(dir, name_, "svgz,png"));
508 			if (!file2.empty())
509 				file = file2;
510 		}
511 		if (!file.exists())
512 			break;
513 		int percent_scale = 100;
514 		if (use_gui) {
515 			// Compute the scale factor for the icon such that its
516 			// width on screen is equal to 1em in pixels.
517 			// The scale factor is rounded to the integer nearest
518 			// to the float value of the ratio 100*iconsize/imgsize.
519 			int imgsize = QImage(toqstr(file.absFileName())).width();
520 			if (imgsize > 0) {
521 				int iconsize = Length(1, Length::EM).inPixels(1);
522 				percent_scale = (100 * iconsize + imgsize / 2)/imgsize;
523 			}
524 		}
525 		InsetGraphics * inset = new InsetGraphics(buffer_);
526 		InsetGraphicsParams igp;
527 		igp.filename = file;
528 		igp.lyxscale = percent_scale;
529 		igp.scale = string();
530 		igp.width = Length(1, Length::EM);
531 		inset->setParams(igp);
532 		clear();
533 		Font const f(inherit_font, buffer().params().language);
534 		paragraphs().front().insertInset(0, inset, f,
535 						 Change(Change::UNCHANGED));
536 		break;
537 	}
538 	case BUFFER_INFO: {
539 		if (name_ == "name") {
540 			setText(from_utf8(buffer().fileName().onlyFileName()));
541 			break;
542 		}
543 		if (name_ == "path") {
544 			setText(from_utf8(os::latex_path(buffer().filePath())));
545 			break;
546 		}
547 		if (name_ == "class") {
548 			setText(from_utf8(bp.documentClass().name()));
549 			break;
550 		}
551 
552 		// everything that follows is for version control.
553 		// nothing that isn't version control should go below this line.
554 		if (!buffer().lyxvc().inUse()) {
555 			setText(_("No version control"));
556 			break;
557 		}
558 		LyXVC::RevisionInfo itype = LyXVC::Unknown;
559 		if (name_ == "vcs-revision")
560 			itype = LyXVC::File;
561 		else if (name_ == "vcs-tree-revision")
562 			itype = LyXVC::Tree;
563 		else if (name_ == "vcs-author")
564 			itype = LyXVC::Author;
565 		else if (name_ == "vcs-time")
566 			itype = LyXVC::Time;
567 		else if (name_ == "vcs-date")
568 			itype = LyXVC::Date;
569 		string binfo = buffer().lyxvc().revisionInfo(itype);
570 		if (binfo.empty())
571 			setText(from_ascii(name_) + " unknown");
572 		else
573 			setText(from_utf8(binfo));
574 		break;
575 	}
576 	case LYX_INFO:
577 		if (name_ == "version")
578 			setText(from_ascii(lyx_version));
579 		break;
580 	}
581 }
582 
583 
contextMenu(BufferView const &,int,int) const584 string InsetInfo::contextMenu(BufferView const &, int, int) const
585 {
586 	//FIXME: We override the implementation of InsetCollapsible,
587 	//because this inset is not a collapsible inset.
588 	return contextMenuName();
589 }
590 
591 
contextMenuName() const592 string InsetInfo::contextMenuName() const
593 {
594 	return "context-info";
595 }
596 
597 
598 } // namespace lyx
599