]> git.lyx.org Git - lyx.git/blob - src/insets/InsetLabel.cpp
e143f83e29b066f54e5a77b826450589908aec85
[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 utype, 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         if (utype == OutputUpdate) {
188                 // save info on the active counter
189                 Counters const & cnts =
190                         buffer().masterBuffer()->params().documentClass().counters();
191                 active_counter_ = cnts.currentCounter();
192                 Language const * lang = it->getParLanguage(buffer().params());
193                 if (lang && !active_counter_.empty()) {
194                         counter_value_ = cnts.theCounter(active_counter_, lang->code());
195                         pretty_counter_ = cnts.prettyCounter(active_counter_, lang->code());
196                 } else {
197                         counter_value_ = from_ascii("#");
198                         pretty_counter_ = from_ascii("#");
199                 }
200         }
201 }
202
203
204 void InsetLabel::addToToc(DocIterator const & cpit, bool output_active,
205                           UpdateType, TocBackend & backend) const
206 {
207         docstring const & label = getParam("name");
208
209         // inactive labels get a cross mark
210         if (buffer().insetLabel(label, true) != this)
211                 output_active = false;
212
213         // We put both  active and inactive labels to the outliner
214         shared_ptr<Toc> toc = backend.toc("label");
215         toc->push_back(TocItem(cpit, 0, screen_label_, output_active));
216         // The refs get assigned only to the active label. If no active one exists,
217         // assign the (BROKEN) refs to the first inactive one.
218         if (buffer().insetLabel(label, true) == this || !buffer().activeLabel(label)) {
219                 for (auto const & p : buffer().references(label)) {
220                         DocIterator const ref_pit(p.second);
221                         if (p.first->lyxCode() == MATH_REF_CODE)
222                                 toc->push_back(TocItem(ref_pit, 1,
223                                                 p.first->asInsetMath()->asRefInset()->screenLabel(),
224                                                 output_active));
225                         else
226                                 toc->push_back(TocItem(ref_pit, 1,
227                                                 static_cast<InsetRef *>(p.first)->getTOCString(),
228                                                 static_cast<InsetRef *>(p.first)->outputActive()));
229                 }
230         }
231 }
232
233
234 bool InsetLabel::getStatus(Cursor & cur, FuncRequest const & cmd,
235                            FuncStatus & status) const
236 {
237         bool enabled;
238         switch (cmd.action()) {
239         case LFUN_LABEL_INSERT_AS_REFERENCE:
240         case LFUN_LABEL_COPY_AS_REFERENCE:
241                 enabled = true;
242                 break;
243         case LFUN_INSET_MODIFY:
244                 if (cmd.getArg(0) == "changetype") {
245                         // this is handled by InsetCommand,
246                         // but not by InsetLabel.
247                         enabled = false;
248                         break;
249                 }
250                 // no "changetype":
251                 // fall through
252         default:
253                 return InsetCommand::getStatus(cur, cmd, status);
254         }
255
256         status.setEnabled(enabled);
257         return true;
258 }
259
260
261 void InsetLabel::doDispatch(Cursor & cur, FuncRequest & cmd)
262 {
263         switch (cmd.action()) {
264
265         case LFUN_INSET_MODIFY: {
266                 // the only other option here is "changetype", and we
267                 // do not have different types.
268                 if (cmd.getArg(0) != "label") {
269                         cur.undispatched();
270                         return;
271                 }
272                 InsetCommandParams p(LABEL_CODE);
273                 // FIXME UNICODE
274                 InsetCommand::string2params(to_utf8(cmd.argument()), p);
275                 if (p.getCmdName().empty()) {
276                         cur.noScreenUpdate();
277                         break;
278                 }
279                 if (p["name"] != params()["name"]) {
280                         // undo is handled in updateLabelAndRefs
281                         updateLabelAndRefs(p["name"], &cur);
282                 }
283                 cur.forceBufferUpdate();
284                 break;
285         }
286
287         case LFUN_LABEL_COPY_AS_REFERENCE: {
288                 InsetCommandParams p(REF_CODE, "ref");
289                 p["reference"] = getParam("name");
290                 cap::clearSelection();
291                 cap::copyInset(cur, new InsetRef(buffer_, p), getParam("name"));
292                 break;
293         }
294
295         case LFUN_LABEL_INSERT_AS_REFERENCE: {
296                 InsetCommandParams p(REF_CODE, "ref");
297                 p["reference"] = getParam("name");
298                 string const data = InsetCommand::params2string(p);
299                 lyx::dispatch(FuncRequest(LFUN_INSET_INSERT, data));
300                 break;
301         }
302
303         default:
304                 InsetCommand::doDispatch(cur, cmd);
305                 break;
306         }
307 }
308
309
310 void InsetLabel::latex(otexstream & os, OutputParams const & runparams_in) const
311 {
312         OutputParams runparams = runparams_in;
313         docstring command = getCommand(runparams);
314         docstring const label = getParam("name");
315         if (buffer().params().output_changes
316             && buffer().activeLabel(label)
317             && buffer().insetLabel(label, true) != this) {
318                 // this is a deleted label and we have a non-deleted with the same id
319                 // rename it for output to prevent wrong references
320                 docstring newlabel = label;
321                 int i = 1;
322                 while (buffer().insetLabel(newlabel)) {
323                         newlabel = label + "-DELETED-" + convert<docstring>(i);
324                         ++i;
325                 }
326                 command = subst(command, label, newlabel);
327         }
328         // In macros with moving arguments, such as \section,
329         // we store the label and output it after the macro (#2154)
330         if (runparams_in.postpone_fragile_stuff)
331                 runparams_in.post_macro += command;
332         else {
333                 // protect label in moving argument (#9404),
334                 // but not in subfloat caption (#11950)
335                 if (runparams.moving_arg
336                     && runparams.inFloat != OutputParams::SUBFLOAT)
337                         os << "\\protect";
338                 os << command;
339         }
340 }
341
342
343 int InsetLabel::plaintext(odocstringstream & os,
344         OutputParams const &, size_t) const
345 {
346         docstring const str = getParam("name");
347         os << '<' << str << '>';
348         return 2 + str.size();
349 }
350
351
352 void InsetLabel::docbook(XMLStream & xs, OutputParams const & runparams) const
353 {
354         // Output an anchor only if it has not been processed before.
355         docstring id = getParam("name");
356         docstring cleaned_id = xml::cleanID(id);
357         if (runparams.docbook_anchors_to_ignore.find(id) == runparams.docbook_anchors_to_ignore.end() &&
358                 runparams.docbook_anchors_to_ignore.find(cleaned_id) == runparams.docbook_anchors_to_ignore.end()) {
359                 docstring attr = from_utf8("xml:id=\"") + cleaned_id + from_utf8("\"");
360                 xs << xml::CompTag("anchor", to_utf8(attr));
361         }
362 }
363
364
365 docstring InsetLabel::xhtml(XMLStream & xs, OutputParams const &) const
366 {
367         // FIXME XHTML
368         // Unfortunately, the name attribute has been deprecated, so we have to use
369         // id here to get the document to validate as XHTML 1.1. This will cause a
370         // problem with some browsers, though, I'm sure. (Guess which!) So we will
371         // have to figure out what to do about this later.
372         docstring const attr = "id=\"" + xml::cleanAttr(getParam("name")) + '"';
373         xs << xml::CompTag("a", to_utf8(attr));
374         return docstring();
375 }
376
377
378 } // namespace lyx