]> git.lyx.org Git - lyx.git/blob - src/insets/insetgraphics.C
cbcbe2f0fcda1156c499bca7c5b9aeb2a6ae819d
[lyx.git] / src / insets / insetgraphics.C
1 /* This file is part of
2  * ======================================================
3  *
4  *           LyX, The Document Processor
5  *
6  *           Copyright 1995-2002 the LyX Team.
7  *
8  * \author Baruch Even
9  * \author Herbert Voss <voss@lyx.org>
10  * ====================================================== */
11
12 /*
13 Known BUGS:
14
15     * If the image is from the clipart, and the document is moved to another
16       directory, the user is screwed. Need a way to handle it.
17       This amounts to a problem of when to use relative or absolute file paths
18       We should probably use what the user asks to use... but when he chooses
19       by the file dialog we normally get an absolute path and this may not be
20       what the user meant.
21
22       Note that browseRelFile in helper_funcs.* provides a file name
23       which is relative if it is at reference path (here puffer path)
24       level or below, and an absolute path if the file name is not a
25       `natural' relative file name. In any case,
26               MakeAbsPath(filename, buf->filePath())
27       is guaranteed to provide the correct absolute path. This is what is
28       done know for include insets. Feel free to ask me -- JMarc
29       14/01/2002
30
31 TODO Before initial production release:
32
33     * What advanced features the users want to do?
34       Implement them in a non latex dependent way, but a logical way.
35       LyX should translate it to latex or any other fitting format.
36     * Add a way to roll the image file into the file format.
37     * When loading, if the image is not found in the expected place, try
38       to find it in the clipart, or in the same directory with the image.
39     * Keep a tab on the image file, if it changes, update the lyx view.
40     * The image choosing dialog could show thumbnails of the image formats
41       it knows of, thus selection based on the image instead of based on
42       filename.
43     * Add support for the 'picins' package.
44     * Add support for the 'picinpar' package.
45     * Improve support for 'subfigure' - Allow to set the various options
46       that are possible.
47 */
48
49 /* NOTES:
50  * Fileformat:
51  * Current version is 1 (inset file format version), when changing it
52  * it should be changed in the Write() function when writing in one place
53  * and when reading one should change the version check and the error message.
54  * The filename is kept in  the lyx file in a relative way, so as to allow
55  * moving the document file and its images with no problem.
56  *
57  *
58  * Conversions:
59  *   Postscript output means EPS figures.
60  *
61  *   PDF output is best done with PDF figures if it's a direct conversion
62  *   or PNG figures otherwise.
63  *      Image format
64  *      from        to
65  *      EPS         epstopdf
66  *      PS          ps2pdf
67  *      JPG/PNG     direct
68  *      PDF         direct
69  *      others      PNG
70  */
71
72 #include <config.h>
73
74 #ifdef __GNUG__
75 #pragma implementation
76 #endif
77
78 #include "insets/insetgraphics.h"
79 #include "insets/insetgraphicsParams.h"
80
81 #include "graphics/GraphicsCache.h"
82 #include "graphics/GraphicsImage.h"
83
84 #include "LyXView.h"
85 #include "lyxtext.h"
86 #include "buffer.h"
87 #include "BufferView.h"
88 #include "converter.h"
89 #include "Painter.h"
90 #include "lyxrc.h"
91 #include "font.h"    // For the lyxfont class.
92 #include "debug.h"
93 #include "gettext.h"
94 #include "LaTeXFeatures.h"
95
96 #include "frontends/Dialogs.h"
97 #include "frontends/controllers/helper_funcs.h" // getVectorFromString
98
99 #include "support/LAssert.h"
100 #include "support/filetools.h"
101 #include "support/lyxalgo.h" // lyx::count
102
103 #include <algorithm> // For the std::max
104
105 extern string system_tempdir;
106
107 using std::ostream;
108 using std::endl;
109
110 ///////////////////////////////////////////////////////////////////////////
111 int const VersionNumber = 1;
112 ///////////////////////////////////////////////////////////////////////////
113
114 namespace {
115
116 // This function is a utility function
117 // ... that should be with ChangeExtension ...
118 inline
119 string const RemoveExtension(string const & filename)
120 {
121         return ChangeExtension(filename, string());
122 }
123
124 } // namespace anon
125
126
127 namespace {
128
129 string const unique_id()
130 {
131         static unsigned int seed = 1000;
132
133         ostringstream ost;
134         ost << "graph" << ++seed;
135
136         // Needed if we use lyxstring.
137         return ost.str().c_str();
138 }
139
140 } // namespace anon
141
142
143 InsetGraphics::InsetGraphics()
144         : graphic_label(unique_id()),
145           cached_status_(grfx::ErrorUnknown), cache_filled_(false), old_asc(0)
146
147 {}
148
149
150 InsetGraphics::InsetGraphics(InsetGraphics const & ig, bool same_id)
151         : Inset(ig, same_id),
152           SigC::Object(),
153           graphic_label(unique_id()),
154           cached_status_(grfx::ErrorUnknown), cache_filled_(false), old_asc(0)
155 {
156         setParams(ig.params());
157 }
158
159
160 InsetGraphics::~InsetGraphics()
161 {
162         cached_image_.reset(0);
163         grfx::GCache & gc = grfx::GCache::get();
164         gc.remove(*this);
165
166         // Emits the hide signal to the dialog connected (if any)
167         hideDialog();
168 }
169
170
171 string const InsetGraphics::statusMessage() const
172 {
173         string msg;
174
175         switch (cached_status_) {
176         case grfx::WaitingToLoad:
177                 msg = _("Waiting for draw request to start loading...");
178                 break;
179         case grfx::Loading:
180                 msg = _("Loading...");
181                 break;
182         case grfx::Converting:
183                 msg = _("Converting to loadable format...");
184                 break;
185         case grfx::ScalingEtc:
186                 msg = _("Loaded. Scaling etc...");
187                 break;
188         case grfx::ErrorNoFile:
189                 msg = _("No file found!");
190                 break;
191         case grfx::ErrorLoading:
192                 msg = _("Error loading file into memory");
193                 break;
194         case grfx::ErrorConverting:
195                 msg = _("Error converting to loadable format");
196                 break;
197         case grfx::ErrorScalingEtc:
198                 msg = _("Error scaling etc");
199                 break;
200         case grfx::ErrorUnknown:
201                 msg = _("No image");
202                 break;
203         case grfx::Loaded:
204                 msg = _("Loaded but not displaying");
205                 break;
206         }
207
208         return msg;
209 }
210
211
212 void InsetGraphics::setCache() const
213 {
214         if (cache_filled_)
215                 return;
216
217         grfx::GCache & gc = grfx::GCache::get();
218         cached_status_ = gc.status(*this);
219         cached_image_  = gc.image(*this);
220 }
221
222
223 bool InsetGraphics::drawImage() const
224 {
225         setCache();
226         Pixmap const pixmap =
227                 (cached_status_ == grfx::Loaded && cached_image_.get() != 0) ?
228                 cached_image_->getPixmap() : 0;
229
230         return pixmap != 0;
231 }
232
233
234 int InsetGraphics::ascent(BufferView *, LyXFont const &) const
235 {
236         old_asc = 50;
237         if (drawImage())
238                 old_asc = cached_image_->getHeight();
239         return old_asc;
240 }
241
242
243 int InsetGraphics::descent(BufferView *, LyXFont const &) const
244 {
245         return 0;
246 }
247
248
249 int InsetGraphics::width(BufferView *, LyXFont const & font) const
250 {
251         if (drawImage())
252                 return cached_image_->getWidth();
253         else {
254                 int font_width = 0;
255
256                 LyXFont msgFont(font);
257                 msgFont.setFamily(LyXFont::SANS_FAMILY);
258
259                 string const justname = OnlyFilename (params().filename);
260                 if (!justname.empty()) {
261                         msgFont.setSize(LyXFont::SIZE_FOOTNOTE);
262                         font_width = lyxfont::width(justname, msgFont);
263                 }
264
265                 string const msg = statusMessage();
266                 if (!msg.empty()) {
267                         msgFont.setSize(LyXFont::SIZE_TINY);
268                         int const msg_width = lyxfont::width(msg, msgFont);
269                         font_width = std::max(font_width, msg_width);
270                 }
271
272                 return std::max(50, font_width + 15);
273         }
274 }
275
276
277 void InsetGraphics::draw(BufferView * bv, LyXFont const & font,
278                          int baseline, float & x, bool) const
279 {
280         int oasc = old_asc;
281         grfx::ImageStatus old_status_ = cached_status_;
282
283         int ldescent = descent(bv, font);
284         int lascent  = ascent(bv, font);
285         int lwidth   = width(bv, font);
286
287         // we may have changed while someone other was drawing us so better
288         // to not draw anything as we surely call to redraw ourself soon.
289         // This is not a nice thing to do and should be fixed properly somehow.
290         // But I still don't know the best way to go. So let's do this like this
291         // for now (Jug 20020311)
292         if (lascent != oasc) {
293 //              lyxerr << "IG(" << this << "): " << x << endl;
294                 return;
295         }
296
297         // Make sure now that x is updated upon exit from this routine
298         int old_x = int(x);
299         x += lwidth;
300
301         // Initiate the loading of the graphics file
302         if (cached_status_ == grfx::WaitingToLoad) {
303                 grfx::GCache & gc = grfx::GCache::get();
304                 gc.startLoading(*this);
305         }
306
307         // This will draw the graphics. If the graphics has not been loaded yet,
308         // we draw just a rectangle.
309         Painter & paint = bv->painter();
310
311         if (drawImage()) {
312 //              lyxerr << "IG(" << this << "): " << old_x << endl;
313                 paint.image(old_x + 2, baseline - lascent,
314                             lwidth - 4, lascent + ldescent,
315                             *cached_image_.get());
316
317         } else {
318
319                 paint.rectangle(old_x + 2, baseline - lascent,
320                                 lwidth - 4,
321                                 lascent + ldescent);
322
323                 // Print the file name.
324                 LyXFont msgFont(font);
325                 msgFont.setFamily(LyXFont::SANS_FAMILY);
326                 string const justname = OnlyFilename (params().filename);
327                 if (!justname.empty()) {
328                         msgFont.setSize(LyXFont::SIZE_FOOTNOTE);
329                         paint.text(old_x + 8,
330                                    baseline - lyxfont::maxAscent(msgFont) - 4,
331                                    justname, msgFont);
332                 }
333
334                 // Print the message.
335                 string const msg = statusMessage();
336                 if (!msg.empty()) {
337                         msgFont.setSize(LyXFont::SIZE_TINY);
338                         paint.text(old_x + 8, baseline - 4, msg, msgFont);
339                 }
340         }
341
342         // the status message may mean we changed size, so indicate
343         // we need a row redraw
344         if (old_status_ != grfx::ErrorUnknown && old_status_ != cached_status_) {
345                 bv->getLyXText()->status(bv, LyXText::CHANGED_IN_DRAW);
346         }
347
348         // Reset the cache, ready for the next draw request
349         cached_status_ = grfx::ErrorUnknown;
350         cached_image_.reset(0);
351         cache_filled_ = false;
352 }
353
354
355 // Update the inset after parameters changed (read from file or changed in
356 // dialog. The grfx::GCache makes the decisions about whether or not to draw
357 // (interogates lyxrc, ascertains whether file exists etc)
358 void InsetGraphics::updateInset() const
359 {
360         grfx::GCache & gc = grfx::GCache::get();
361         gc.update(*this);
362 }
363
364
365 void InsetGraphics::edit(BufferView *bv, int, int, unsigned int)
366 {
367         bv->owner()->getDialogs()->showGraphics(this);
368 }
369
370
371 void InsetGraphics::edit(BufferView * bv, bool)
372 {
373         edit(bv, 0, 0, 0);
374 }
375
376
377 Inset::EDITABLE InsetGraphics::editable() const
378 {
379         return IS_EDITABLE;
380 }
381
382
383 void InsetGraphics::write(Buffer const * buf, ostream & os) const
384 {
385         os << "Graphics FormatVersion " << VersionNumber << '\n';
386         params().Write(buf, os);
387 }
388
389
390 void InsetGraphics::read(Buffer const * buf, LyXLex & lex)
391 {
392         string const token = lex.getString();
393
394         if (token == "Graphics")
395                 readInsetGraphics(buf, lex);
396         else if (token == "Figure") // Compatibility reading of FigInset figures.
397                 readFigInset(buf, lex);
398         else
399                 lyxerr[Debug::GRAPHICS] << "Not a Graphics or Figure inset!\n";
400
401         updateInset();
402 }
403
404 void InsetGraphics::readInsetGraphics(Buffer const * buf, LyXLex & lex)
405 {
406         bool finished = false;
407
408         while (lex.isOK() && !finished) {
409                 lex.next();
410
411                 string const token = lex.getString();
412                 lyxerr[Debug::GRAPHICS] << "Token: '" << token << '\''
413                                     << std::endl;
414
415                 if (token.empty()) {
416                         continue;
417                 } else if (token == "\\end_inset") {
418                         finished = true;
419                 } else if (token == "FormatVersion") {
420                         lex.next();
421                         int version = lex.getInteger();
422                         if (version > VersionNumber)
423                                 lyxerr
424                                 << "This document was created with a newer Graphics widget"
425                                 ", You should use a newer version of LyX to read this"
426                                 " file."
427                                 << std::endl;
428                         // TODO: Possibly open up a dialog?
429                 }
430                 else {
431                         if (! params_.Read(buf, lex, token))
432                                 lyxerr << "Unknown token, " << token << ", skipping."
433                                         << std::endl;
434                 }
435         }
436 }
437
438 // FormatVersion < 1.0  (LyX < 1.2)
439 void InsetGraphics::readFigInset(Buffer const * buf, LyXLex & lex)
440 {
441         std::vector<string> const oldUnits =
442                 getVectorFromString("pt,cm,in,p%,c%");
443         bool finished = false;
444         // set the display default
445         if (lyxrc.display_graphics == "mono")
446             params_.display = InsetGraphicsParams::MONOCHROME;
447         else if (lyxrc.display_graphics == "gray")
448             params_.display = InsetGraphicsParams::GRAYSCALE;
449         else if (lyxrc.display_graphics == "color")
450             params_.display = InsetGraphicsParams::COLOR;
451         else
452             params_.display = InsetGraphicsParams::NONE;
453         while (lex.isOK() && !finished) {
454                 lex.next();
455
456                 string const token = lex.getString();
457                 lyxerr[Debug::GRAPHICS] << "Token: " << token << endl;
458
459                 if (token.empty())
460                         continue;
461                 else if (token == "\\end_inset") {
462                         finished = true;
463                 } else if (token == "file") {
464                         if (lex.next()) {
465                                 string const name = lex.getString();
466                                 string const path = buf->filePath();
467                                 params_.filename = MakeAbsPath(name, path);
468                         }
469                 } else if (token == "extra") {
470                         if (lex.next());
471                         // kept for backwards compability. Delete in 0.13.x
472                 } else if (token == "subcaption") {
473                         if (lex.eatLine())
474                                 params_.subcaptionText = lex.getString();
475                         params_.subcaption = true;
476                 } else if (token == "label") {
477                         if (lex.next());
478                         // kept for backwards compability. Delete in 0.13.x
479                 } else if (token == "angle") {
480                         if (lex.next())
481                                 params_.rotate = true;
482                                 params_.rotateAngle = lex.getFloat();
483                 } else if (token == "size") {
484                         if (lex.next())
485                                 params_.lyxwidth = LyXLength(lex.getString()+"pt");
486                         if (lex.next())
487                                 params_.lyxheight = LyXLength(lex.getString()+"pt");
488                 } else if (token == "flags") {
489                         if (lex.next())
490                                 switch (lex.getInteger()) {
491                                 case 1: params_.display = InsetGraphicsParams::MONOCHROME;
492                                     break;
493                                 case 2: params_.display = InsetGraphicsParams::GRAYSCALE;
494                                     break;
495                                 case 3: params_.display = InsetGraphicsParams::COLOR;
496                                     break;
497                                 }
498                 } else if (token == "subfigure") {
499                         params_.subcaption = true;
500                 } else if (token == "width") {
501                     if (lex.next()) {
502                         int i = lex.getInteger();
503                         if (lex.next()) {
504                             if (i == 5) {
505                                 params_.scale = lex.getInteger();
506                                 params_.size_type = InsetGraphicsParams::SCALE;
507                             } else {
508                                 params_.width = LyXLength(lex.getString()+oldUnits[i]);
509                                 params_.size_type = InsetGraphicsParams::WH;
510                             }
511                         }
512                     }
513                 } else if (token == "height") {
514                     if (lex.next()) {
515                         int i = lex.getInteger();
516                         if (lex.next()) {
517                             params_.height = LyXLength(lex.getString()+oldUnits[i]);
518                             params_.size_type = InsetGraphicsParams::WH;
519                         }
520                     }
521                 }
522         }
523 }
524
525 string const InsetGraphics::createLatexOptions() const
526 {
527         // Calculate the options part of the command, we must do it to a string
528         // stream since we might have a trailing comma that we would like to remove
529         // before writing it to the output stream.
530         ostringstream options;
531         if (!params().bb.empty())
532             options << "  bb=" << strip(params().bb) << ",\n";
533         if (params().draft)
534             options << "  draft,\n";
535         if (params().clip)
536             options << "  clip,\n";
537         if (params().size_type == InsetGraphicsParams::WH) {
538             if (!params().width.zero())
539                 options << "  width=" << params().width.asLatexString() << ",\n";
540             if (!params().height.zero())
541                 options << "  height=" << params().height.asLatexString() << ",\n";
542         } else if (params().size_type == InsetGraphicsParams::SCALE) {
543             if (params().scale > 0)
544                 options << "  scale=" << double(params().scale)/100.0 << ",\n";
545         }
546         if (params().keepAspectRatio)
547             options << "  keepaspectratio,\n";
548         // Make sure it's not very close to zero, a float can be effectively
549         // zero but not exactly zero.
550         if (!lyx::float_equal(params().rotateAngle, 0, 0.001) && params().rotate) {
551             options << "  angle=" << params().rotateAngle << ",\n";
552             if (!params().rotateOrigin.empty()) {
553                 options << "  origin=" << params().rotateOrigin[0];
554                 if (contains(params().rotateOrigin,"Top"))
555                     options << 't';
556                 else if (contains(params().rotateOrigin,"Bottom"))
557                     options << 'b';
558                 else if (contains(params().rotateOrigin,"Baseline"))
559                     options << 'B';
560                 options << ",\n";
561             }
562         }
563         if (!params().special.empty())
564             options << params().special << ",\n";
565         string opts = options.str().c_str();
566         return opts.substr(0,opts.size()-2);    // delete last ",\n"
567 }
568
569 namespace {
570 string findTargetFormat(string const & suffix)
571 {
572         // lyxrc.pdf_mode means:
573         // Are we creating a PDF or a PS file?
574         // (Should actually mean, are we using latex or pdflatex).
575         lyxerr[Debug::GRAPHICS] << "decideOutput: lyxrc.pdf_mode = "
576                             << lyxrc.pdf_mode << std::endl;
577         if (lyxrc.pdf_mode) {
578                 if (contains(suffix,"ps") || suffix == "pdf")
579                         return "pdf";
580                 else if (suffix == "jpg")
581                         return suffix;
582                 else
583                         return "png";
584         }
585         // If it's postscript, we always do eps.
586         lyxerr[Debug::GRAPHICS] << "decideOutput: we have PostScript mode\n";
587         if (suffix != "ps")
588             return "eps";
589         else
590             return "ps";
591 }
592
593 } // Anon. namespace
594
595
596 string const InsetGraphics::prepareFile(Buffer const *buf) const
597 {
598         // do_convert = Do we need to convert the file?
599         // nice = Do we create a nice version?
600         //        This is used when exporting the latex file only.
601         // if (!do_convert)
602         //   return original filename
603         // if (!nice)
604         //   convert_place = temp directory
605         //   return new filename in temp directory
606         // else
607         //   convert_place = original file directory
608         //   return original filename without the extension
609         //
610         // if it's a zipped one, than let LaTeX do the rest!!!
611         string filename_  = params().filename;
612         bool const zipped = zippedFile(filename_);
613
614         if ((zipped && params().noUnzip) || buf->niceFile) {
615                 lyxerr[Debug::GRAPHICS] << "don't unzip file or export latex"
616                                     << filename_ << endl;
617                 return filename_;
618         }
619
620         if (zipped)
621                 filename_ = unzipFile(filename_);
622
623         string const from = getExtFromContents(filename_);
624         string const to   = findTargetFormat(from);
625
626         if (from == to) {
627                 // No conversion needed!
628                 return filename_;
629         }
630
631         string const temp = AddName(buf->tmppath, filename_);
632         string const outfile_base = RemoveExtension(temp);
633
634         lyxerr[Debug::GRAPHICS] << "tempname = " << temp << "\n";
635         lyxerr[Debug::GRAPHICS] << "buf::tmppath = " << buf->tmppath << "\n";
636         lyxerr[Debug::GRAPHICS] << "filename_ = " << filename_ << "\n";
637         lyxerr[Debug::GRAPHICS] << "outfile_base = " << outfile_base << endl;
638
639         converters.convert(buf, filename_, outfile_base, from, to);
640         return outfile_base;
641 }
642
643
644 int InsetGraphics::latex(Buffer const *buf, ostream & os,
645                          bool /*fragile*/, bool/*fs*/) const
646 {
647         // If there is no file specified, just output a message about it in
648         // the latex output.
649         if (params().filename.empty()) {
650                 os  << "\\fbox{\\rule[-0.5in]{0pt}{1in}"
651                         << _("empty figure path") << "}\n";
652                 return 1; // One end of line marker added to the stream.
653         }
654         // These variables collect all the latex code that should be before and
655         // after the actual includegraphics command.
656         string before;
657         string after;
658         // Do we want subcaptions?
659         if (params().subcaption) {
660                 before += "\\subfigure[" + params().subcaptionText + "]{";
661                 after = '}';
662         }
663         // We never use the starred form, we use the "clip" option instead.
664         before += "\\includegraphics";
665         // Write the options if there are any.
666         string const opts = createLatexOptions();
667         if (!opts.empty()) {
668                 before += ("[%\n" + opts +']');
669         }
670         // Make the filename relative to the lyx file
671         // and remove the extension so the LaTeX will use whatever is
672         // appropriate (when there are several versions in different formats)
673         string const latex_str = before + '{' + prepareFile(buf) + '}' + after;
674         os << latex_str;
675
676         // Return how many newlines we issued.
677         int const newlines =
678                 int(lyx::count(latex_str.begin(), latex_str.end(),'\n') + 1);
679
680         // lyxerr << "includegraphics: " << newlines << " lines of text"
681         //        << endl;
682         return newlines;
683 }
684
685
686 int InsetGraphics::ascii(Buffer const *, ostream & os, int) const
687 {
688         // No graphics in ascii output. Possible to use gifscii to convert
689         // images to ascii approximation.
690         // 1. Convert file to ascii using gifscii
691         // 2. Read ascii output file and add it to the output stream.
692         // at least we send the filename
693         os << '<' << _("Graphic file:") << params().filename << ">\n";
694         return 0;
695 }
696
697
698 int InsetGraphics::linuxdoc(Buffer const *, ostream &) const
699 {
700         // No graphics in LinuxDoc output. Should check how/what to add.
701         return 0;
702 }
703
704
705 // For explanation on inserting graphics into DocBook checkout:
706 // http://linuxdoc.org/LDP/LDP-Author-Guide/inserting-pictures.html
707 // See also the docbook guide at http://www.docbook.org/
708 int InsetGraphics::docbook(Buffer const *, ostream & os) const
709 {
710         // In DocBook v5.0, the graphic tag will be eliminated from DocBook, will
711         // need to switch to MediaObject. However, for now this is sufficient and
712         // easier to use.
713         os << "<graphic fileref=\"&" << graphic_label << ";\">";
714         return 0;
715 }
716
717
718 void InsetGraphics::validate(LaTeXFeatures & features) const
719 {
720         // If we have no image, we should not require anything.
721         if (params().filename.empty())
722                 return ;
723
724         features.includeFile(graphic_label, RemoveExtension(params_.filename));
725
726         features.require("graphicx");
727
728         if (params().subcaption)
729                 features.require("subfigure");
730 }
731
732
733 bool InsetGraphics::setParams(InsetGraphicsParams const & p)
734 {
735         // If nothing is changed, just return and say so.
736         if (params() == p && !p.filename.empty()) {
737                 return false;
738         }
739
740         // Copy the new parameters.
741         params_ = p;
742
743         // Update the inset with the new parameters.
744         updateInset();
745
746         // We have changed data, report it.
747         return true;
748 }
749
750
751 InsetGraphicsParams const & InsetGraphics::params() const
752 {
753         return params_;
754 }
755
756
757 Inset * InsetGraphics::clone(Buffer const &, bool same_id) const
758 {
759         return new InsetGraphics(*this, same_id);
760 }