]> git.lyx.org Git - lyx.git/blob - src/insets/InsetLabel.cpp
Display equation/theorem numbers in insert cross reference dialog.
[lyx.git] / src / insets / InsetLabel.cpp
1 /**
2  * \file InsetLabel.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Lars Gullik Bjønnes
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "InsetLabel.h"
14
15 #include "InsetRef.h"
16
17 #include "Buffer.h"
18 #include "BufferParams.h"
19 #include "Cursor.h"
20 #include "CutAndPaste.h"
21 #include "FuncRequest.h"
22 #include "FuncStatus.h"
23 #include "Language.h"
24 #include "LyX.h"
25 #include "ParIterator.h"
26 #include "xml.h"
27 #include "texstream.h"
28 #include "Text.h"
29 #include "TextClass.h"
30 #include "TocBackend.h"
31
32 #include "mathed/InsetMathRef.h"
33
34 #include "frontends/alert.h"
35
36 #include "support/convert.h"
37 #include "support/gettext.h"
38 #include "support/lstrings.h"
39
40 using namespace std;
41 using namespace lyx::support;
42
43 namespace lyx {
44
45
46 InsetLabel::InsetLabel(Buffer * buf, InsetCommandParams const & p)
47         : InsetCommand(buf, p)
48 {}
49
50
51 void InsetLabel::initView()
52 {
53         // This seems to be used only for inset creation.
54         // Therefore we do not update refs here, since this would
55         // erroneously change refs from existing duplicate labels
56         // (#8141).
57         updateLabel(getParam("name"));
58 }
59
60
61 void InsetLabel::uniqueLabel(docstring & label) const
62 {
63         docstring const new_label = label;
64         int i = 1;
65         bool ambiguous = false;
66         while (buffer().activeLabel(label)) {
67                 label = new_label + '-' + convert<docstring>(i);
68                 ++i;
69                 ambiguous = true;
70         }
71         if (ambiguous) {
72                 // Warn the user that the label has been changed to something else.
73                 frontend::Alert::warning(_("Label names must be unique!"),
74                         bformat(_("The label %1$s already exists,\n"
75                         "it will be changed to %2$s."), new_label, label));
76         }
77 }
78
79
80 void InsetLabel::updateLabel(docstring const & new_label, bool const active)
81 {
82         docstring label = new_label;
83         if (active)
84                 uniqueLabel(label);
85         setParam("name", label);
86 }
87
88
89 void InsetLabel::updateLabelAndRefs(docstring const & new_label,
90                 Cursor * cursor)
91 {
92         docstring const old_label = getParam("name");
93         docstring label = new_label;
94         uniqueLabel(label);
95         if (label == old_label)
96                 return;
97
98         // This handles undo groups automagically
99         UndoGroupHelper ugh(&buffer());
100         if (cursor)
101                 cursor->recordUndo();
102         bool const changes = buffer().masterParams().track_changes;
103         if (changes) {
104                 // With change tracking, we insert a new label and
105                 // delete the old one
106                 InsetCommandParams p(LABEL_CODE, "label");
107                 p["name"] = label;
108                 string const data = InsetCommand::params2string(p);
109                 lyx::dispatch(FuncRequest(LFUN_INSET_INSERT, data));
110                 lyx::dispatch(FuncRequest(LFUN_CHAR_DELETE_FORWARD));
111         } else
112                 setParam("name", label);
113         updateReferences(old_label, label, changes);
114 }
115
116
117 void InsetLabel::updateReferences(docstring const & old_label,
118                 docstring const & new_label, bool const changes)
119 {
120         UndoGroupHelper ugh(nullptr);
121         if (changes) {
122                 // With change tracking, we insert a new ref and
123                 // delete the old one
124                 lyx::dispatch(FuncRequest(LFUN_MASTER_BUFFER_FORALL,
125                                           "inset-forall Ref inset-modify ref changetarget "
126                                           + old_label + " " + new_label));
127         } else {
128                 for (auto const & p: buffer().references(old_label)) {
129                         ugh.resetBuffer(p.second.buffer());
130                         CursorData(p.second).recordUndo();
131                         if (p.first->lyxCode() == MATH_REF_CODE) {
132                                 InsetMathRef * mi = p.first->asInsetMath()->asRefInset();
133                                 mi->changeTarget(new_label);
134                         } else {
135                                 InsetCommand * ref = p.first->asInsetCommand();
136                                 ref->setParam("reference", new_label);
137                         }
138                 }
139         }
140 }
141
142
143 ParamInfo const & InsetLabel::findInfo(string const & /* cmdName */)
144 {
145         static ParamInfo param_info_;
146         if (param_info_.empty())
147                 param_info_.add("name", ParamInfo::LATEX_REQUIRED,
148                                 ParamInfo::HANDLING_ESCAPE);
149         return param_info_;
150 }
151
152
153 docstring InsetLabel::screenLabel() const
154 {
155         return screen_label_;
156 }
157
158
159 void InsetLabel::updateBuffer(ParIterator const & it, UpdateType, bool const /*deleted*/)
160 {
161         docstring const & label = getParam("name");
162
163         // Check if this one is active (i.e., neither deleted with change-tracking
164         // nor in an inset that does not produce output, such as notes or inactive branches)
165         Paragraph const & para = it.paragraph();
166         bool active = !para.isDeleted(it.pos()) && para.inInset().producesOutput();
167         // If not, check whether we are in a deleted/non-outputting inset
168         if (active) {
169                 for (size_type sl = 0 ; sl < it.depth() ; ++sl) {
170                         Paragraph const & outer_par = it[sl].paragraph();
171                         if (outer_par.isDeleted(it[sl].pos())
172                             || !outer_par.inInset().producesOutput()) {
173                                 active = false;
174                                 break;
175                         }
176                 }
177         }
178
179         if (buffer().activeLabel(label) && active) {
180                 // Problem: We already have an active InsetLabel with the same name!
181                 screen_label_ = _("DUPLICATE: ") + label;
182                 return;
183         }
184         buffer().setInsetLabel(label, this, active);
185         screen_label_ = label;
186
187         // save info on the active counter
188         Counters & cnts =
189                 buffer().masterBuffer()->params().documentClass().counters();
190         active_counter_ = cnts.currentCounter();
191         Language const * lang = it->getParLanguage(buffer().params());
192         if (lang && !active_counter_.empty()) {
193                 if (active_counter_ != from_ascii("equation")) {
194                         counter_value_ = cnts.theCounter(active_counter_, lang->code());
195                         pretty_counter_ = cnts.prettyCounter(active_counter_, lang->code());
196                 } else {
197                         // For equations, the counter value and pretty counter
198                         // value will be set by the parent InsetMathHull.
199                         counter_value_ = from_ascii("#");
200                         pretty_counter_ = from_ascii("");
201                 }
202         } else {
203                 counter_value_ = from_ascii("#");
204                 pretty_counter_ = from_ascii("#");
205         }
206 }
207
208
209 void InsetLabel::addToToc(DocIterator const & cpit, bool output_active,
210                           UpdateType, TocBackend & backend) const
211 {
212         docstring const & label = getParam("name");
213
214         // inactive labels get a cross mark
215         if (buffer().insetLabel(label, true) != this)
216                 output_active = false;
217
218         // We put both  active and inactive labels to the outliner
219         shared_ptr<Toc> toc = backend.toc("label");
220         TocItem toc_item = TocItem(cpit, 0, screen_label_, output_active);
221         toc_item.prettyStr(pretty_counter_);
222         toc->push_back(toc_item);
223         // The refs get assigned only to the active label. If no active one exists,
224         // assign the (BROKEN) refs to the first inactive one.
225         if (buffer().insetLabel(label, true) == this || !buffer().activeLabel(label)) {
226                 for (auto const & p : buffer().references(label)) {
227                         DocIterator const ref_pit(p.second);
228                         if (p.first->lyxCode() == MATH_REF_CODE)
229                                 toc->push_back(TocItem(ref_pit, 1,
230                                                 p.first->asInsetMath()->asRefInset()->screenLabel(),
231                                                 output_active));
232                         else
233                                 toc->push_back(TocItem(ref_pit, 1,
234                                                 static_cast<InsetRef *>(p.first)->getTOCString(),
235                                                 static_cast<InsetRef *>(p.first)->outputActive()));
236                 }
237         }
238 }
239
240
241 bool InsetLabel::getStatus(Cursor & cur, FuncRequest const & cmd,
242                            FuncStatus & status) const
243 {
244         bool enabled;
245         switch (cmd.action()) {
246         case LFUN_LABEL_INSERT_AS_REFERENCE:
247         case LFUN_LABEL_COPY_AS_REFERENCE:
248                 enabled = true;
249                 break;
250         case LFUN_INSET_MODIFY:
251                 if (cmd.getArg(0) == "changetype") {
252                         // this is handled by InsetCommand,
253                         // but not by InsetLabel.
254                         enabled = false;
255                         break;
256                 }
257                 // no "changetype":
258                 // fall through
259         default:
260                 return InsetCommand::getStatus(cur, cmd, status);
261         }
262
263         status.setEnabled(enabled);
264         return true;
265 }
266
267
268 void InsetLabel::doDispatch(Cursor & cur, FuncRequest & cmd)
269 {
270         switch (cmd.action()) {
271
272         case LFUN_INSET_MODIFY: {
273                 // the only other option here is "changetype", and we
274                 // do not have different types.
275                 if (cmd.getArg(0) != "label") {
276                         cur.undispatched();
277                         return;
278                 }
279                 InsetCommandParams p(LABEL_CODE);
280                 // FIXME UNICODE
281                 InsetCommand::string2params(to_utf8(cmd.argument()), p);
282                 if (p.getCmdName().empty()) {
283                         cur.noScreenUpdate();
284                         break;
285                 }
286                 if (p["name"] != params()["name"]) {
287                         // undo is handled in updateLabelAndRefs
288                         updateLabelAndRefs(p["name"], &cur);
289                 }
290                 cur.forceBufferUpdate();
291                 break;
292         }
293
294         case LFUN_LABEL_COPY_AS_REFERENCE: {
295                 InsetCommandParams p(REF_CODE, "ref");
296                 p["reference"] = getParam("name");
297                 cap::clearSelection();
298                 cap::copyInset(cur, new InsetRef(buffer_, p), getParam("name"));
299                 break;
300         }
301
302         case LFUN_LABEL_INSERT_AS_REFERENCE: {
303                 InsetCommandParams p(REF_CODE, "ref");
304                 p["reference"] = getParam("name");
305                 string const data = InsetCommand::params2string(p);
306                 lyx::dispatch(FuncRequest(LFUN_INSET_INSERT, data));
307                 break;
308         }
309
310         default:
311                 InsetCommand::doDispatch(cur, cmd);
312                 break;
313         }
314 }
315
316
317 void InsetLabel::latex(otexstream & os, OutputParams const & runparams_in) const
318 {
319         OutputParams runparams = runparams_in;
320         docstring command = getCommand(runparams);
321         docstring const label = getParam("name");
322         if (buffer().params().output_changes
323             && buffer().activeLabel(label)
324             && buffer().insetLabel(label, true) != this) {
325                 // this is a deleted label and we have a non-deleted with the same id
326                 // rename it for output to prevent wrong references
327                 docstring newlabel = label;
328                 int i = 1;
329                 while (buffer().insetLabel(newlabel)) {
330                         newlabel = label + "-DELETED-" + convert<docstring>(i);
331                         ++i;
332                 }
333                 command = subst(command, label, newlabel);
334         }
335         // In macros with moving arguments, such as \section,
336         // we store the label and output it after the macro (#2154)
337         if (runparams_in.postpone_fragile_stuff)
338                 runparams_in.post_macro += command;
339         else {
340                 // protect label in moving argument (#9404),
341                 // but not in subfloat caption (#11950)
342                 if (runparams.moving_arg
343                     && runparams.inFloat != OutputParams::SUBFLOAT)
344                         os << "\\protect";
345                 os << command;
346         }
347 }
348
349
350 int InsetLabel::plaintext(odocstringstream & os,
351         OutputParams const &, size_t) const
352 {
353         docstring const str = getParam("name");
354         os << '<' << str << '>';
355         return 2 + str.size();
356 }
357
358
359 void InsetLabel::docbook(XMLStream & xs, OutputParams const & runparams) const
360 {
361         // Output an anchor only if it has not been processed before.
362         docstring id = getParam("name");
363         docstring cleaned_id = xml::cleanID(id);
364         if (runparams.docbook_anchors_to_ignore.find(id) == runparams.docbook_anchors_to_ignore.end() &&
365                 runparams.docbook_anchors_to_ignore.find(cleaned_id) == runparams.docbook_anchors_to_ignore.end()) {
366                 docstring attr = from_utf8("xml:id=\"") + cleaned_id + from_utf8("\"");
367                 xs << xml::CompTag("anchor", to_utf8(attr));
368         }
369 }
370
371
372 docstring InsetLabel::xhtml(XMLStream & xs, OutputParams const &) const
373 {
374         // FIXME XHTML
375         // Unfortunately, the name attribute has been deprecated, so we have to use
376         // id here to get the document to validate as XHTML 1.1. This will cause a
377         // problem with some browsers, though, I'm sure. (Guess which!) So we will
378         // have to figure out what to do about this later.
379         docstring const attr = "id=\"" + xml::cleanAttr(getParam("name")) + '"';
380         xs << xml::CompTag("a", to_utf8(attr));
381         return docstring();
382 }
383
384
385 } // namespace lyx