3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Asger Alstrup
8 * \author Angus Leeming
10 * Full author contact details are available in file CREDITS.
15 #include "FormExternal.h"
16 #include "forms/form_external.h"
18 #include "checkedwidgets.h"
19 #include "input_validators.h"
21 #include "xforms_helpers.h"
24 #include "controllers/ControlExternal.h"
26 #include "lengthcommon.h"
29 #include "insets/ExternalTemplate.h"
30 #include "insets/insetexternal.h"
32 #include "support/lstrings.h"
33 #include "support/lyxlib.h"
34 #include "support/tostr.h"
36 #include "lyx_forms.h"
38 namespace external = lyx::external;
40 using lyx::support::bformat;
41 using lyx::support::float_equal;
42 using lyx::support::getStringFromVector;
43 using lyx::support::isStrDbl;
44 using lyx::support::strToDbl;
45 using lyx::support::strToInt;
46 using lyx::support::token;
47 using lyx::support::trim;
56 LyXLength::UNIT defaultUnit()
58 LyXLength::UNIT default_unit = LyXLength::CM;
59 switch (lyxrc.default_papersize) {
61 case PAPER_LEGALPAPER:
62 case PAPER_EXECUTIVEPAPER:
63 default_unit = LyXLength::IN;
72 void setDisplay(FL_OBJECT * displayCB, FL_OBJECT * showCO, FL_OBJECT * scaleED,
73 external::DisplayType display, unsigned int scale,
76 BOOST_ASSERT(displayCB && displayCB->objclass == FL_CHECKBUTTON);
77 BOOST_ASSERT(showCO && showCO->objclass == FL_CHOICE);
78 BOOST_ASSERT(scaleED && scaleED->objclass == FL_INPUT);
82 case external::DefaultDisplay:
85 case external::MonochromeDisplay:
88 case external::GrayscaleDisplay:
91 case external::ColorDisplay:
94 case external::PreviewDisplay:
97 case external::NoDisplay:
102 fl_set_choice(showCO, item);
104 bool const no_display = display == lyx::external::NoDisplay;
105 setEnabled(showCO, !no_display && !read_only);
107 fl_set_button(displayCB, !no_display);
109 fl_set_input(scaleED, tostr(scale).c_str());
110 setEnabled(scaleED, !no_display && !read_only);
114 void getDisplay(external::DisplayType & display,
115 unsigned int & scale,
116 FL_OBJECT * displayCB,
120 BOOST_ASSERT(displayCB && displayCB->objclass == FL_CHECKBUTTON);
121 BOOST_ASSERT(showCO && showCO->objclass == FL_CHOICE);
122 BOOST_ASSERT(scaleED && scaleED->objclass == FL_INPUT);
124 switch (fl_get_choice(showCO)) {
126 display = external::DefaultDisplay;
129 display = external::MonochromeDisplay;
132 display = external::GrayscaleDisplay;
135 display = external::ColorDisplay;
138 display = external::PreviewDisplay;
142 if (!fl_get_button(displayCB))
143 display = external::NoDisplay;
145 scale = strToInt(getString(scaleED));
149 void setRotation(FL_OBJECT * angleED, FL_OBJECT * originCO,
150 external::RotationData const & data)
152 BOOST_ASSERT(angleED && angleED->objclass == FL_INPUT);
153 BOOST_ASSERT(originCO && originCO->objclass == FL_CHOICE);
155 fl_set_choice(originCO, 1 + int(data.origin()));
156 fl_set_input(angleED, tostr(data.angle()).c_str());
160 void getRotation(external::RotationData & data,
161 FL_OBJECT * angleED, FL_OBJECT * originCO)
163 BOOST_ASSERT(angleED && angleED->objclass == FL_INPUT);
164 BOOST_ASSERT(originCO && originCO->objclass == FL_CHOICE);
166 typedef external::RotationData::OriginType OriginType;
168 data.origin(static_cast<OriginType>(fl_get_choice(originCO) - 1));
169 data.angle(strToDbl(getString(angleED)));
173 void setSize(FL_OBJECT * widthED, FL_OBJECT * widthUnitCO,
174 FL_OBJECT * heightED, FL_OBJECT * heightUnitCO,
175 FL_OBJECT * aspectratioCB,
176 external::ResizeData const & data)
178 BOOST_ASSERT(widthED && widthED->objclass == FL_INPUT);
179 BOOST_ASSERT(widthUnitCO && widthUnitCO->objclass == FL_CHOICE);
180 BOOST_ASSERT(heightED && heightED->objclass == FL_INPUT);
181 BOOST_ASSERT(heightUnitCO && heightUnitCO->objclass == FL_CHOICE);
182 BOOST_ASSERT(aspectratioCB &&
183 aspectratioCB->objclass == FL_CHECKBUTTON);
185 bool using_scale = data.usingScale();
186 double scale = data.scale;
187 if (data.no_resize()) {
188 // Everything is zero, so default to this!
194 fl_set_input(widthED, tostr(scale).c_str());
195 fl_set_choice(widthUnitCO, 1);
197 fl_set_input(widthED, tostr(data.width.value()).c_str());
198 // Because 'Scale' is position 1...
199 // Note also that width cannot be zero here, so
200 // we don't need to worry about the default unit.
201 fl_set_choice(widthUnitCO, data.width.unit() + 2);
204 string const h = data.height.zero() ? string() : data.height.asString();
205 LyXLength::UNIT default_unit = data.width.zero() ?
206 defaultUnit() : data.width.unit();
207 updateWidgetsFromLengthString(heightED, heightUnitCO,
208 h, stringFromUnit(default_unit));
210 setEnabled(heightED, !using_scale);
211 setEnabled(heightUnitCO, !using_scale);
213 fl_set_button(aspectratioCB, data.keepAspectRatio);
215 bool const disable_aspectRatio = using_scale ||
216 data.width.zero() || data.height.zero();
217 setEnabled(aspectratioCB, !disable_aspectRatio);
221 void getSize(external::ResizeData & data,
222 FL_OBJECT * widthED, FL_OBJECT * widthUnitCO,
223 FL_OBJECT * heightED, FL_OBJECT * heightUnitCO,
224 FL_OBJECT * aspectratioCB)
226 BOOST_ASSERT(widthED && widthED->objclass == FL_INPUT);
227 BOOST_ASSERT(widthUnitCO && widthUnitCO->objclass == FL_CHOICE);
228 BOOST_ASSERT(heightED && heightED->objclass == FL_INPUT);
229 BOOST_ASSERT(heightUnitCO && heightUnitCO->objclass == FL_CHOICE);
230 BOOST_ASSERT(aspectratioCB &&
231 aspectratioCB->objclass == FL_CHECKBUTTON);
233 string const width = getString(widthED);
235 if (fl_get_choice(widthUnitCO) > 1) {
236 // Subtract one, because scale is 1.
237 int const unit = fl_get_choice(widthUnitCO) - 1;
240 if (isValidLength(width, &w))
242 else if (isStrDbl(width))
243 data.width = LyXLength(strToDbl(width),
244 static_cast<LyXLength::UNIT>(unit));
246 data.width = LyXLength();
251 // scaling instead of a width
252 data.scale = strToDbl(width);
253 data.width = LyXLength();
256 data.height = LyXLength(getLengthFromWidgets(heightED, heightUnitCO));
258 data.keepAspectRatio = fl_get_button(aspectratioCB);
262 void setCrop(FL_OBJECT * clipCB,
263 FL_OBJECT * xlED, FL_OBJECT * ybED,
264 FL_OBJECT * xrED, FL_OBJECT * ytED,
265 external::ClipData const & data)
267 BOOST_ASSERT(clipCB && clipCB->objclass == FL_CHECKBUTTON);
268 BOOST_ASSERT(xlED && xlED->objclass == FL_INPUT);
269 BOOST_ASSERT(ybED && ybED->objclass == FL_INPUT);
270 BOOST_ASSERT(xrED && xrED->objclass == FL_INPUT);
271 BOOST_ASSERT(ytED && ytED->objclass == FL_INPUT);
273 fl_set_button(clipCB, data.clip);
274 lyx::graphics::BoundingBox const & bbox = data.bbox;
275 fl_set_input(xlED, tostr(bbox.xl).c_str());
276 fl_set_input(ybED, tostr(bbox.yb).c_str());
277 fl_set_input(xrED, tostr(bbox.xr).c_str());
278 fl_set_input(ytED, tostr(bbox.yt).c_str());
282 void getCrop(external::ClipData & data,
284 FL_OBJECT * xlED, FL_OBJECT * ybED,
285 FL_OBJECT * xrED, FL_OBJECT * ytED,
288 BOOST_ASSERT(clipCB && clipCB->objclass == FL_CHECKBUTTON);
289 BOOST_ASSERT(xlED && xlED->objclass == FL_INPUT);
290 BOOST_ASSERT(ybED && ybED->objclass == FL_INPUT);
291 BOOST_ASSERT(xrED && xrED->objclass == FL_INPUT);
292 BOOST_ASSERT(ytED && ytED->objclass == FL_INPUT);
294 data.clip = fl_get_button(clipCB);
299 data.bbox.xl = strToInt(getString(xlED));
300 data.bbox.yb = strToInt(getString(ybED));
301 data.bbox.xr = strToInt(getString(xrED));
302 data.bbox.yt = strToInt(getString(ytED));
306 void getExtra(external::ExtraData & data,
307 FormExternal::MapType const & extra)
309 typedef FormExternal::MapType MapType;
310 MapType::const_iterator it = extra.begin();
311 MapType::const_iterator const end = extra.end();
312 for (; it != end; ++it)
313 data.set(it->first, trim(it->second));
319 typedef FormController<ControlExternal, FormView<FD_external> > base_class;
321 FormExternal::FormExternal(Dialog & parent)
322 : base_class(parent, _("External Material"))
326 void FormExternal::build()
328 dialog_.reset(build_external(this));
329 file_.reset(build_external_file(this));
330 lyxview_.reset(build_external_lyxview(this));
331 rotate_.reset(build_external_rotate(this));
332 scale_.reset(build_external_scale(this));
333 crop_.reset(build_external_crop(this));
334 options_.reset(build_external_options(this));
336 bcview().setOK(dialog_->button_ok);
337 bcview().setApply(dialog_->button_apply);
338 bcview().setCancel(dialog_->button_close);
340 bcview().addReadOnly(file_->input_file);
341 bcview().addReadOnly(file_->button_browse);
342 bcview().addReadOnly(file_->button_edit);
343 bcview().addReadOnly(file_->choice_template);
344 bcview().addReadOnly(file_->check_draft);
346 bcview().addReadOnly(lyxview_->check_show);
347 bcview().addReadOnly(lyxview_->choice_show);
348 bcview().addReadOnly(lyxview_->input_displayscale);
350 bcview().addReadOnly(rotate_->input_angle);
351 bcview().addReadOnly(rotate_->choice_origin);
353 bcview().addReadOnly(scale_->input_width);
354 bcview().addReadOnly(scale_->choice_width);
355 bcview().addReadOnly(scale_->input_height);
356 bcview().addReadOnly(scale_->choice_height);
357 bcview().addReadOnly(scale_->check_aspectratio);
359 bcview().addReadOnly(crop_->check_bbox);
360 bcview().addReadOnly(crop_->button_get_bbox);
361 bcview().addReadOnly(crop_->input_xr);
362 bcview().addReadOnly(crop_->input_yt);
363 bcview().addReadOnly(crop_->input_xl);
364 bcview().addReadOnly(crop_->input_yb);
366 bcview().addReadOnly(options_->choice_option);
367 bcview().addReadOnly(options_->input_option);
370 // addCheckedPositiveFloat(bcview(), scale_->input_width);
371 // As I haven't written addCheckedPositiveFloat, we default to
372 // always checking that it is a valide LyXLength, even when
373 // I'm 'scaling'. No harm done, just not as strict as it might be.
374 addCheckedLyXLength(bcview(), scale_->input_width);
375 addCheckedLyXLength(bcview(), scale_->input_height);
377 // addCheckedPositiveFloat(bcview(), input_displayscale);
378 fl_set_input_filter(lyxview_->input_displayscale,
379 fl_unsigned_int_filter);
381 fl_set_input_filter(crop_->input_xr, fl_unsigned_int_filter);
382 fl_set_input_filter(crop_->input_yt, fl_unsigned_int_filter);
383 fl_set_input_filter(crop_->input_xl, fl_unsigned_int_filter);
384 fl_set_input_filter(crop_->input_yb, fl_unsigned_int_filter);
386 fl_set_input_return(file_->input_file, FL_RETURN_CHANGED);
387 fl_set_input_return(lyxview_->input_displayscale, FL_RETURN_CHANGED);
388 fl_set_input_return(rotate_->input_angle, FL_RETURN_CHANGED);
389 fl_set_input_return(scale_->input_width, FL_RETURN_CHANGED);
390 fl_set_input_return(scale_->input_height, FL_RETURN_CHANGED);
391 fl_set_input_return(crop_->input_xr, FL_RETURN_CHANGED);
392 fl_set_input_return(crop_->input_yt, FL_RETURN_CHANGED);
393 fl_set_input_return(crop_->input_xl, FL_RETURN_CHANGED);
394 fl_set_input_return(crop_->input_yb, FL_RETURN_CHANGED);
395 fl_set_input_return(options_->input_option, FL_RETURN_CHANGED);
397 // Trigger an input event for cut&paste with middle mouse button.
398 setPrehandler(file_->input_file);
399 setPrehandler(lyxview_->input_displayscale);
400 setPrehandler(rotate_->input_angle);
401 setPrehandler(scale_->input_width);
402 setPrehandler(scale_->input_height);
403 setPrehandler(crop_->input_xr);
404 setPrehandler(crop_->input_yt);
405 setPrehandler(crop_->input_xl);
406 setPrehandler(crop_->input_yb);
407 setPrehandler(options_->input_option);
409 string const choice =
410 ' ' + getStringFromVector(controller().getTemplates(), " | ") +
412 fl_addto_choice(file_->choice_template, choice.c_str());
414 string const display_list =
415 _("Default|Monochrome|Grayscale|Color|Preview");
416 fl_addto_choice(lyxview_->choice_show, display_list.c_str());
418 // Fill the origins combo
419 typedef vector<external::RotationDataType> Origins;
420 Origins const & all_origins = external::all_origins();
421 for (Origins::size_type i = 0; i != all_origins.size(); ++i)
422 fl_addto_choice(rotate_->choice_origin,
423 external::origin_gui_str(i).c_str());
425 string const width_list = bformat(_("Scale%%%%|%1$s"),
427 fl_addto_choice(scale_->choice_width, width_list.c_str());
429 fl_addto_choice(scale_->choice_height, choice_Length_All.c_str());
431 // Set up the tooltips.
432 string str = _("The file you want to insert.");
433 tooltips().init(file_->input_file, str);
434 str = _("Browse the directories.");
435 tooltips().init(file_->button_browse, str);
437 str = _("Scale the image to inserted percentage value.");
438 tooltips().init(lyxview_->input_displayscale, str);
439 str = _("Select display mode for this image.");
440 tooltips().init(options_->choice_option, str);
444 fl_addto_tabfolder(dialog_->tabfolder, _("File").c_str(),
447 tabmap_[LYXVIEWTAB] =
448 fl_addto_tabfolder(dialog_->tabfolder, _("LyX View").c_str(),
451 fl_addto_tabfolder(dialog_->tabfolder, _("Rotate").c_str(),
454 fl_addto_tabfolder(dialog_->tabfolder, _("Scale").c_str(),
457 fl_addto_tabfolder(dialog_->tabfolder, _("Crop").c_str(),
459 tabmap_[OPTIONSTAB] =
460 fl_addto_tabfolder(dialog_->tabfolder, _("Options").c_str(),
465 void FormExternal::update()
467 fl_set_folder_bynumber(dialog_->tabfolder, 1);
468 InsetExternalParams const & params = controller().params();
470 string const buffer_path = kernel().bufferFilepath();
471 string const name = params.filename.outputFilename(buffer_path);
472 fl_set_input(file_->input_file, name.c_str());
474 int ID = controller().getTemplateNumber(params.templatename());
476 fl_set_choice(file_->choice_template, ID+1);
480 fl_set_button(file_->check_draft, params.draft);
482 setDisplay(lyxview_->check_show, lyxview_->choice_show,
483 lyxview_->input_displayscale,
484 params.display, params.lyxscale,
485 kernel().isBufferReadonly());
487 setRotation(rotate_->input_angle, rotate_->choice_origin,
488 params.rotationdata);
490 setSize(scale_->input_width, scale_->choice_width,
491 scale_->input_height, scale_->choice_height,
492 scale_->check_aspectratio,
495 setCrop(crop_->check_bbox,
496 crop_->input_xl, crop_->input_yb,
497 crop_->input_xr, crop_->input_yt,
499 controller().bbChanged(!params.clipdata.bbox.empty());
503 void FormExternal::updateComboChange()
505 namespace external = lyx::external;
507 int const choice = fl_get_choice(file_->choice_template) - 1;
508 external::Template templ = controller().getTemplate(choice);
510 // Update the help text
511 string const txt = formatted(templ.helpText,
512 file_->browser_template->w - 20);
513 fl_clear_browser(file_->browser_template);
514 fl_addto_browser(file_->browser_template, txt.c_str());
515 fl_set_browser_topline(file_->browser_template, 0);
517 // Ascertain which (if any) transformations the template supports
518 // and disable tabs hosting unsupported transforms.
519 typedef vector<external::TransformID> TransformIDs;
520 TransformIDs const transformIds = templ.transformIds;
521 TransformIDs::const_iterator tr_begin = transformIds.begin();
522 TransformIDs::const_iterator const tr_end = transformIds.end();
531 bool found = find(tr_begin, tr_end, external::Rotate) != tr_end;
532 setEnabled(tabmap_[ROTATETAB], found);
534 found = find(tr_begin, tr_end, external::Resize) != tr_end;
535 setEnabled(tabmap_[SCALETAB], found);
537 found = find(tr_begin, tr_end, external::Clip) != tr_end;
538 setEnabled(tabmap_[CROPTAB], found);
540 found = find(tr_begin, tr_end, external::Extra) != tr_end;
541 setEnabled(tabmap_[OPTIONSTAB], found);
546 // Ascertain whether the template has any formats supporting
547 // the 'Extra' option
548 FL_OBJECT * const ob_input = options_->input_option;
549 FL_OBJECT * const ob_choice = options_->choice_option;
551 fl_set_input(ob_input, "");
552 fl_clear_choice(ob_choice);
554 external::Template::Formats::const_iterator it = templ.formats.begin();
555 external::Template::Formats::const_iterator end = templ.formats.end();
556 for (; it != end; ++it) {
557 if (it->second.option_transformers.find(external::Extra) ==
558 it->second.option_transformers.end())
560 string const format = it->first;
561 string const opt = controller().params().extradata.get(format);
562 fl_addto_choice(ob_choice, format.c_str());
563 extra_[format] = opt;
566 bool const enabled = fl_get_choice_maxitems(ob_choice) > 0;
568 setEnabled(tabmap_[OPTIONSTAB], enabled);
569 setEnabled(ob_input, enabled && !kernel().isBufferReadonly());
570 setEnabled(ob_choice, enabled);
573 fl_set_choice(ob_choice, 1);
574 string const format = fl_get_choice_text(ob_choice);
575 fl_set_input(ob_input, extra_[format].c_str());
580 void FormExternal::apply()
582 InsetExternalParams params = controller().params();
584 string const buffer_path = kernel().bufferFilepath();
585 params.filename.set(getString(file_->input_file), buffer_path);
587 int const choice = fl_get_choice(file_->choice_template) - 1;
588 params.settemplate(controller().getTemplate(choice).lyxName);
590 params.draft = fl_get_button(file_->check_draft);
592 getDisplay(params.display, params.lyxscale,
593 lyxview_->check_show, lyxview_->choice_show,
594 lyxview_->input_displayscale);
596 if (isActive(tabmap_[ROTATETAB]))
597 getRotation(params.rotationdata,
598 rotate_->input_angle, rotate_->choice_origin);
600 if (isActive(tabmap_[SCALETAB]))
601 getSize(params.resizedata,
602 scale_->input_width, scale_->choice_width,
603 scale_->input_height, scale_->choice_height,
604 scale_->check_aspectratio);
606 if (isActive(tabmap_[CROPTAB]))
607 getCrop(params.clipdata,
609 crop_->input_xl, crop_->input_yb,
610 crop_->input_xr, crop_->input_yt,
611 controller().bbChanged());
613 if (isActive(tabmap_[OPTIONSTAB]))
614 getExtra(params.extradata, extra_);
616 controller().setParams(params);
620 ButtonPolicy::SMInput FormExternal::input(FL_OBJECT * ob, long)
622 ButtonPolicy::SMInput result = ButtonPolicy::SMI_VALID;
624 if (ob == file_->choice_template) {
626 // set to the chosen template
629 } else if (ob == file_->button_browse) {
631 string const in_name = fl_get_input(file_->input_file);
633 int const choice = fl_get_choice(file_->choice_template) - 1;
634 string const template_name =
635 controller().getTemplate(choice).lyxName;
636 string const out_name =
637 controller().browse(in_name, template_name);
638 fl_set_input(file_->input_file, out_name.c_str());
640 } else if (ob == file_->button_edit) {
641 controller().editExternal();
642 result = ButtonPolicy::SMI_NOOP;
644 } else if (ob == lyxview_->check_show) {
646 bool const checked = fl_get_button(ob);
647 setEnabled(lyxview_->choice_show, checked);
648 setEnabled(lyxview_->input_displayscale, checked);
650 } else if (ob == crop_->button_get_bbox) {
654 } else if (ob == scale_->input_width ||
655 ob == scale_->input_height) {
657 setEnabled(scale_->check_aspectratio,
658 activateAspectratio());
660 } else if (ob == scale_->choice_width) {
664 } else if (ob == crop_->input_xr ||
665 ob == crop_->input_yt ||
666 ob == crop_->input_xl ||
667 ob == crop_->input_yb) {
669 controller().bbChanged(true);
671 } else if (ob == options_->input_option) {
673 string const format =
674 fl_get_choice_text(options_->choice_option);
675 extra_[format] = getString(options_->input_option);
677 } else if (ob == options_->choice_option) {
679 string const format =
680 fl_get_choice_text(options_->choice_option);
681 fl_set_input(options_->input_option, extra_[format].c_str());
682 result = ButtonPolicy::SMI_NOOP;
689 bool FormExternal::activateAspectratio() const
691 if (fl_get_choice(scale_->choice_width) == 1)
694 string const wstr = getString(scale_->input_width);
697 bool const wIsDbl = isStrDbl(wstr);
698 if (wIsDbl && float_equal(strToDbl(wstr), 0.0, 0.05))
701 if (!wIsDbl && (!isValidLength(wstr, &l) || l.zero()))
704 string const hstr = getString(scale_->input_height);
707 bool const hIsDbl = isStrDbl(hstr);
708 if (hIsDbl && float_equal(strToDbl(hstr), 0.0, 0.05))
710 if (!hIsDbl && (!isValidLength(hstr, &l) || l.zero()))
717 void FormExternal::getBB()
719 fl_set_input(crop_->input_xl, "0");
720 fl_set_input(crop_->input_yb, "0");
721 fl_set_input(crop_->input_xr, "0");
722 fl_set_input(crop_->input_yt, "0");
724 string const filename = getString(file_->input_file);
725 if (filename.empty())
728 string const bb = controller().readBB(filename);
732 fl_set_input(crop_->input_xl, token(bb, ' ', 0).c_str());
733 fl_set_input(crop_->input_yb, token(bb, ' ', 1).c_str());
734 fl_set_input(crop_->input_xr, token(bb, ' ', 2).c_str());
735 fl_set_input(crop_->input_yt, token(bb, ' ', 3).c_str());
737 controller().bbChanged(false);
741 void FormExternal::widthUnitChanged()
743 if (fl_get_choice(scale_->choice_width) == 1)
746 bool useHeight = fl_get_choice(scale_->choice_width) > 1;
749 // widthED->setValidator(unsignedLengthValidator(widthED));
751 // widthED->setValidator(new QDoubleValidator(0, 1000, 2, widthED));
753 setEnabled(scale_->input_height, useHeight);
754 setEnabled(scale_->choice_height, useHeight);