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