]> git.lyx.org Git - features.git/blob - src/insets/InsetCollapsable.cpp
Further inset configurability, moving charstyle stuff to collapsable
[features.git] / src / insets / InsetCollapsable.cpp
1 /**
2  * \file InsetCollapsable.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Alejandro Aguilar Sierra
7  * \author Jürgen Vigna
8  * \author Lars Gullik Bjønnes
9  *
10  * Full author contact details are available in file CREDITS.
11  */
12
13 #include <config.h>
14
15 #include "InsetCollapsable.h"
16
17 #include "Buffer.h"
18 #include "BufferParams.h"
19 #include "BufferView.h"
20 #include "Cursor.h"
21 #include "debug.h"
22 #include "DispatchResult.h"
23 #include "FloatList.h"
24 #include "FuncStatus.h"
25 #include "gettext.h"
26 #include "Color.h"
27 #include "Lexer.h"
28 #include "FuncRequest.h"
29 #include "MetricsInfo.h"
30
31 #include "frontends/FontMetrics.h"
32 #include "frontends/Painter.h"
33
34
35 namespace lyx {
36
37 using graphics::PreviewLoader;
38
39 using std::endl;
40 using std::string;
41 using std::max;
42 using std::min;
43 using std::ostream;
44
45
46 InsetCollapsable::CollapseStatus InsetCollapsable::status() const
47 {
48         return autoOpen_ ? Open : status_;
49 }
50
51
52 InsetCollapsable::Geometry InsetCollapsable::geometry() const
53 {
54         switch (decoration()) {
55         case Classic:
56                 if (status_ == Open || autoOpen_) {
57                         if (openinlined_)
58                                 return LeftButton;
59                         else
60                                 return TopButton;
61                 } else
62                         return ButtonOnly;
63
64         case Minimalistic:
65                 return NoButton;
66
67         case Conglomerate:
68                 return status_ == Open ? SubLabel : Corners;
69         }
70
71         // dummy return value to shut down a warning,
72         // this is dead code.
73         return NoButton;
74 }
75
76
77 InsetCollapsable::InsetCollapsable
78                 (BufferParams const & bp, CollapseStatus status)
79         : InsetText(bp), label(from_ascii("Label")), status_(status),
80           openinlined_(false), autoOpen_(false), mouse_hover_(false)
81 {
82         setAutoBreakRows(true);
83         setDrawFrame(true);
84         setFrameColor(Color::collapsableframe);
85         setButtonLabel();
86 }
87
88
89 InsetCollapsable::InsetCollapsable(InsetCollapsable const & rhs)
90         : InsetText(rhs),
91                 button_dim(rhs.button_dim),
92                 topx(rhs.topx),
93                 topbaseline(rhs.topbaseline),
94                 label(rhs.label),
95                 layout_(rhs.layout_),
96                 status_(rhs.status_),
97                 openinlined_(rhs.openinlined_),
98                 autoOpen_(rhs.autoOpen_),
99                 textdim_(rhs.textdim_),
100                 // the sole purpose of this copy constructor
101                 mouse_hover_(false)
102 {
103 }
104
105
106 void  InsetCollapsable::setLayout(BufferParams const & bp)
107 {
108         setLabelFont(getLayout(bp).labelfont);
109         setLabel(getLayout(bp).labelstring);
110 }
111
112
113 void InsetCollapsable::write(Buffer const & buf, ostream & os) const
114 {
115         os << "status ";
116         switch (status_) {
117         case Open:
118                 os << "open";
119                 break;
120         case Collapsed:
121                 os << "collapsed";
122                 break;
123         }
124         os << "\n";
125         text_.write(buf, os);
126 }
127
128
129 void InsetCollapsable::read(Buffer const & buf, Lexer & lex)
130 {
131         bool token_found = false;
132         if (lex.isOK()) {
133                 lex.next();
134                 string const token = lex.getString();
135                 if (token == "status") {
136                         lex.next();
137                         string const tmp_token = lex.getString();
138
139                         if (tmp_token == "collapsed") {
140                                 status_ = Collapsed;
141                                 token_found = true;
142                         } else if (tmp_token == "open") {
143                                 status_ = Open;
144                                 token_found = true;
145                         } else {
146                                 lyxerr << "InsetCollapsable::read: Missing status!"
147                                        << endl;
148                                 // Take countermeasures
149                                 lex.pushToken(token);
150                         }
151                 } else {
152                         lyxerr << "InsetCollapsable::read: Missing 'status'-tag!"
153                                    << endl;
154                         // take countermeasures
155                         lex.pushToken(token);
156                 }
157         }
158         InsetText::read(buf, lex);
159
160         if (!token_found)
161                 status_ = isOpen() ? Open : Collapsed;
162
163         setButtonLabel();
164 }
165
166
167 Dimension InsetCollapsable::dimensionCollapsed() const
168 {
169         Dimension dim;
170         theFontMetrics(layout_.labelfont).buttonText(
171                 label, dim.wid, dim.asc, dim.des);
172         return dim;
173 }
174
175
176 bool InsetCollapsable::metrics(MetricsInfo & mi, Dimension & dim) const
177 {
178         autoOpen_ = mi.base.bv->cursor().isInside(this);
179         mi.base.textwidth -= (int) (1.5 * TEXT_TO_INSET_OFFSET);
180
181         switch (decoration()) {
182         case Minimalistic:
183         case Conglomerate:
184                 InsetText::metrics(mi, dim);
185                 break;
186         case Classic:
187                 dim = dimensionCollapsed();
188                 if (geometry() == TopButton
189                  || geometry() == LeftButton) {
190                         InsetText::metrics(mi, textdim_);
191                         // This expression should not contain mi.base.texwidth
192                         openinlined_ = !hasFixedWidth()
193                                 && textdim_.wid < 0.5 * mi.base.bv->workWidth();
194                         if (openinlined_) {
195                                 // Correct for button width, and re-fit
196                                 mi.base.textwidth -= dim.wid;
197                                 InsetText::metrics(mi, textdim_);
198                                 dim.wid += textdim_.wid;
199                                 dim.des = max(dim.des - textdim_.asc + dim.asc, textdim_.des);
200                                 dim.asc = textdim_.asc;
201                         } else {
202                                 dim.des += textdim_.height() + TEXT_TO_BOTTOM_OFFSET;
203                                 dim.wid = max(dim.wid, textdim_.wid);
204                                 if (hasFixedWidth())
205                                         dim.wid = max(dim.wid, mi.base.textwidth);
206                         }
207                 }
208                 break;
209         }
210         dim.asc += TEXT_TO_INSET_OFFSET;
211         dim.des += TEXT_TO_INSET_OFFSET;
212         dim.wid += (int) (1.5 * TEXT_TO_INSET_OFFSET);
213         mi.base.textwidth += (int) (1.5 * TEXT_TO_INSET_OFFSET);
214         bool const changed = dim_ != dim;
215         dim_ = dim;
216         return changed;
217 }
218
219
220 bool InsetCollapsable::setMouseHover(bool mouse_hover)
221 {
222         mouse_hover_ = mouse_hover;
223         return true;
224 }
225
226
227 void InsetCollapsable::draw(PainterInfo & pi, int x, int y) const
228 {
229         const int xx = x + TEXT_TO_INSET_OFFSET;
230
231         // Draw button first -- top, left or only
232         Dimension dimc = dimensionCollapsed();
233         int const top  = y - ascent() + TEXT_TO_INSET_OFFSET;
234         if (decoration() == Classic) {
235                 button_dim.x1 = xx + 0;
236                 button_dim.x2 = xx + dimc.width();
237                 button_dim.y1 = top;
238                 button_dim.y2 = top + dimc.height();
239
240                 pi.pain.buttonText(xx, top + dimc.asc, label, layout_.labelfont, mouse_hover_);
241         }
242
243         int textx, texty;
244         switch (geometry()) {
245         case LeftButton:
246                 textx = xx + dimc.width();
247                 texty = top + textdim_.asc;
248                 InsetText::draw(pi, textx, texty);
249                 break;
250         case TopButton:
251                 textx = xx;
252                 texty = top + dimc.height() + textdim_.asc;
253                 InsetText::draw(pi, textx, texty);
254                 break;
255         case ButtonOnly:
256                 break;
257         case NoButton:
258                 textx = xx;
259                 texty = y + textdim_.asc;
260                 InsetText::draw(pi, textx, texty);
261                 break;
262         case SubLabel:
263         case Corners:
264                 // FIXME add handling of SubLabel, Corners
265                 // still in CharStyle
266                 textx = xx;
267                 texty = y + textdim_.asc;
268                 const_cast<InsetCollapsable *>(this)->setDrawFrame(false);
269                 InsetText::draw(pi, textx, texty);
270                 const_cast<InsetCollapsable *>(this)->setDrawFrame(true);
271                 break;
272         }
273         setPosCache(pi, x, y);
274 }
275
276
277 void InsetCollapsable::drawSelection(PainterInfo & pi, int x, int y) const
278 {
279         x += TEXT_TO_INSET_OFFSET;
280         switch (geometry()) {
281         case LeftButton:
282                 x += dimensionCollapsed().wid;
283                 InsetText::drawSelection(pi, x, y);
284                 break;
285         case TopButton:
286                 y += dimensionCollapsed().des + textdim_.asc;
287                 InsetText::drawSelection(pi, x, y);
288                 break;
289         case ButtonOnly:
290                 break;
291         case NoButton:
292         case SubLabel:
293         case Corners:
294                 InsetText::drawSelection(pi, x, y);
295                 break;
296         }
297 }
298
299
300 void InsetCollapsable::cursorPos(BufferView const & bv,
301                 CursorSlice const & sl, bool boundary, int & x, int & y) const
302 {
303         BOOST_ASSERT(geometry() != ButtonOnly);
304
305         InsetText::cursorPos(bv, sl, boundary, x, y);
306
307         switch (geometry()) {
308         case LeftButton:
309                 x += dimensionCollapsed().wid;
310                 break;
311         case TopButton:
312                 y += dimensionCollapsed().height() - ascent()
313                         + TEXT_TO_INSET_OFFSET + textdim_.asc;
314                 break;
315         case NoButton:
316         case SubLabel:
317         case Corners:
318                 // Do nothing
319                 break;
320         case ButtonOnly:
321                 // Cannot get here
322                 break;
323         }
324         x += TEXT_TO_INSET_OFFSET;
325 }
326
327
328 Inset::EDITABLE InsetCollapsable::editable() const
329 {
330         return geometry() != ButtonOnly? HIGHLY_EDITABLE : IS_EDITABLE;
331 }
332
333
334 bool InsetCollapsable::descendable() const
335 {
336         return geometry() != ButtonOnly;
337 }
338
339
340 bool InsetCollapsable::hitButton(FuncRequest const & cmd) const
341 {
342         return button_dim.contains(cmd.x, cmd.y);
343 }
344
345
346 docstring const InsetCollapsable::getNewLabel(docstring const & l) const
347 {
348         docstring label;
349         pos_type const max_length = 15;
350         pos_type const p_siz = paragraphs().begin()->size();
351         pos_type const n = min(max_length, p_siz);
352         pos_type i = 0;
353         pos_type j = 0;
354         for (; i < n && j < p_siz; ++j) {
355                 if (paragraphs().begin()->isInset(j))
356                         continue;
357                 label += paragraphs().begin()->getChar(j);
358                 ++i;
359         }
360         if (paragraphs().size() > 1 || (i > 0 && j < p_siz)) {
361                 label += "...";
362         }
363         return label.empty() ? l : label;
364 }
365
366
367 void InsetCollapsable::edit(Cursor & cur, bool left)
368 {
369         //lyxerr << "InsetCollapsable: edit left/right" << endl;
370         cur.push(*this);
371         InsetText::edit(cur, left);
372 }
373
374
375 Inset * InsetCollapsable::editXY(Cursor & cur, int x, int y)
376 {
377         //lyxerr << "InsetCollapsable: edit xy" << endl;
378         if (geometry() == ButtonOnly
379          || (button_dim.contains(x, y) 
380           && decoration() != Minimalistic))
381                 return this;
382         cur.push(*this);
383         return InsetText::editXY(cur, x, y);
384 }
385
386
387 void InsetCollapsable::doDispatch(Cursor & cur, FuncRequest & cmd)
388 {
389         //lyxerr << "InsetCollapsable::doDispatch (begin): cmd: " << cmd
390         //      << " cur: " << cur << " bvcur: " << cur.bv().cursor() << endl;
391
392         switch (cmd.action) {
393         case LFUN_MOUSE_PRESS:
394                 if (cmd.button() == mouse_button::button1 
395                  && hitButton(cmd) 
396                  && decoration() != Minimalistic) {
397                         // reset selection if necessary (see bug 3060)
398                         if (cur.selection())
399                                 cur.bv().cursor().clearSelection();
400                         else
401                                 cur.noUpdate();
402                         cur.dispatched();
403                         break;
404                 }
405                 if (decoration() == Minimalistic)
406                         InsetText::doDispatch(cur, cmd);
407                 else if (geometry() != ButtonOnly 
408                      && !hitButton(cmd))
409                         InsetText::doDispatch(cur, cmd);
410                 else
411                         cur.undispatched();
412                 break;
413
414         case LFUN_MOUSE_MOTION:
415         case LFUN_MOUSE_DOUBLE:
416         case LFUN_MOUSE_TRIPLE:
417                 if (decoration() == Minimalistic)
418                         InsetText::doDispatch(cur, cmd);
419                 else if (geometry() != ButtonOnly
420                      && !hitButton(cmd))
421                         InsetText::doDispatch(cur, cmd);
422                 else
423                         cur.undispatched();
424                 break;
425
426         case LFUN_MOUSE_RELEASE:
427                 if (cmd.button() == mouse_button::button3) {
428                         // Open the Inset configuration dialog
429                         showInsetDialog(&cur.bv());
430                         break;
431                 }
432
433                 if (decoration() == Minimalistic) {
434                         // The mouse click has to be within the inset!
435                         InsetText::doDispatch(cur, cmd);
436                         break;
437                 }
438
439                 if (cmd.button() == mouse_button::button1 && hitButton(cmd)) {
440                         // if we are selecting, we do not want to
441                         // toggle the inset.
442                         if (cur.selection())
443                                 break;
444                         // Left button is clicked, the user asks to
445                         // toggle the inset visual state.
446                         cur.dispatched();
447                         cur.updateFlags(Update::Force | Update::FitCursor);
448                         if (geometry() == ButtonOnly) {
449                                 setStatus(cur, Open);
450                                 edit(cur, true);
451                         }
452                         else {
453                                 setStatus(cur, Collapsed);
454                         }
455                         cur.bv().cursor() = cur;
456                         break;
457                 }
458
459                 // The mouse click is within the opened inset.
460                 if (geometry() == TopButton
461                  || geometry() == LeftButton)
462                         InsetText::doDispatch(cur, cmd);
463                 break;
464
465         case LFUN_INSET_TOGGLE:
466                 if (cmd.argument() == "open")
467                         setStatus(cur, Open);
468                 else if (cmd.argument() == "close")
469                         setStatus(cur, Collapsed);
470                 else if (cmd.argument() == "toggle" || cmd.argument().empty())
471                         if (isOpen()) {
472                                 setStatus(cur, Collapsed);
473                                 cur.top().forwardPos();
474                         }
475                         else
476                                 setStatus(cur, Open);
477                 else // if assign or anything else
478                         cur.undispatched();
479                 cur.dispatched();
480                 break;
481
482         default:
483                 InsetText::doDispatch(cur, cmd);
484                 break;
485         }
486 }
487
488
489 bool InsetCollapsable::getStatus(Cursor & cur, FuncRequest const & cmd,
490                 FuncStatus & flag) const
491 {
492         switch (cmd.action) {
493
494         case LFUN_INSET_TOGGLE:
495                 if (cmd.argument() == "open" || cmd.argument() == "close" ||
496                     cmd.argument() == "toggle")
497                         flag.enabled(true);
498                 else
499                         flag.enabled(false);
500                 return true;
501
502         default:
503                 return InsetText::getStatus(cur, cmd, flag);
504         }
505 }
506
507
508 void InsetCollapsable::setLabel(docstring const & l)
509 {
510         label = l;
511 }
512
513
514 void InsetCollapsable::setStatus(Cursor & cur, CollapseStatus status)
515 {
516         status_ = status;
517         setButtonLabel();
518         if (status_ == Collapsed)
519                 cur.leaveInset(*this);
520         // Because the collapse status is part of the inset and thus an
521         // integral part of the Buffer contents a changed status must be
522         // signaled to all views of current buffer.
523         cur.bv().buffer()->changed();
524 }
525
526
527 void InsetCollapsable::setLabelFont(Font const & font)
528 {
529         layout_.labelfont = font;
530 }
531
532 docstring InsetCollapsable::floatName(string const & type, BufferParams const & bp) const
533 {
534         FloatList const & floats = bp.getTextClass().floats();
535         FloatList::const_iterator it = floats[type];
536         // FIXME UNICODE
537         return (it == floats.end()) ? from_ascii(type) : bp.B_(it->second.name());
538 }
539
540
541 } // namespace lyx