1 /***************************************************************************
2  *                                                                         *
3  *   copyright : (C) 2008 The University of Toronto                        *
4  *                   <netterfield@astro.utoronto.ca>                       *
5  *   This program is free software; you can redistribute it and/or modify  *
6  *   it under the terms of the GNU General Public License as published by  *
7  *   the Free Software Foundation; either version 2 of the License, or     *
8  *   (at your option) any later version.                                   *
9  *                                                                         *
10  ***************************************************************************/
11 
12 #include "legenditem.h"
13 
14 #include <labelparser.h>
15 #include "labelrenderer.h"
16 
17 #include "debug.h"
18 #include "plotitem.h"
19 #include "legenditemdialog.h"
20 #include "objectstore.h"
21 #include "dialogdefaults.h"
22 #include "legendscriptinterface.h"
23 
24 #include <QDebug>
25 #include <QGraphicsItem>
26 #include <QGraphicsScene>
27 
28 #define LEGENDITEMMAXWIDTH 900
29 #define LEGENDITEMMAXHEIGHT 100
30 
31 namespace Kst {
32 
33 
LegendItem(PlotItem * parentPlot)34 LegendItem::LegendItem(PlotItem *parentPlot)
35   : ViewItem(parentPlot->view()), _plotItem(parentPlot), _auto(true), _verticalDisplay(true) {
36   setTypeName(tr("Legend", "a legend in a plot"));
37 
38   _initializeShortName();
39 
40   setFixedSize(true);
41   setAllowedGripModes(Move /*| Resize*/ /*| Rotate*/ /*| Scale*/);
42 
43   setViewRect(0.0, 0.0, 0.0, 0.0);
44   view()->scene()->addItem(this);
45   setParentViewItem(_plotItem->renderItem());
46 
47   QPointF origin = QPointF(_plotItem->plotRect().width() * 0.15, _plotItem->plotRect().height() * 0.15);
48   setPos(origin);
49 
50   applyDefaults();
51   applyDialogDefaultsStroke();
52   applyDialogDefaultsFill();
53   applyDialogDefaultsLockPosToData();
54 }
55 
_initializeShortName()56 void LegendItem::_initializeShortName() {
57   _shortName = 'L'+QString::number(_legendnum);
58   if (_legendnum>max_legendnum)
59     max_legendnum = _legendnum;
60   _legendnum++;
61 }
62 
63 
~LegendItem()64 LegendItem::~LegendItem() {
65 }
66 
67 
paint(QPainter * painter)68 void LegendItem::paint(QPainter *painter) {
69   if (!isVisible()) {
70     return;
71   }
72 
73   RelationList legendItems;
74   if (_auto) {
75     legendItems = plot()->renderItem(PlotRenderItem::Cartesian)->relationList();
76   } else {
77     legendItems = _relations;
78   }
79 
80   int count = legendItems.count();
81   if (count <= 0) { // no legend or box if there are no legend items
82     return;
83   }
84 
85 
86   QFont font(_font);
87   font.setPointSizeF(view()->scaledFontSize(_fontScale, *painter->device()));
88 
89   painter->setFont(font);
90 
91   // generate string list of relation names
92   QStringList names;
93   //bool allAuto = true;
94   bool sameX = true;
95   bool sameYUnits = true;
96 
97   LabelInfo label_info = legendItems.at(0)->xLabelInfo();
98   QString yUnits =  legendItems.at(0)->yLabelInfo().units;
99 
100   for (int i = 0; i<count; i++) {
101     RelationPtr relation = legendItems.at(i);
102     if (relation->descriptiveNameIsManual()) {
103       //allAuto = false;
104     }
105     if (relation->xLabelInfo() != label_info) {
106       sameX = false;
107     }
108     // sameYUnits is false if any non empty units are defined differently.
109     if (yUnits.isEmpty()) {
110       yUnits = relation->yLabelInfo().units;
111     } else if (relation->yLabelInfo().units != yUnits) {
112       if (!relation->yLabelInfo().units.isEmpty()) {
113         sameYUnits = false;
114       }
115     }
116   }
117 
118 //  if (!allAuto) {
119   //    for (int i = 0; i<count; i++) {
120   //      names.append(legendItems.at(i)->descriptiveName());
121   //    }
122   //  } else {
123 
124   // FIXME: move most of this into a new function, relation->legend_name
125   //        then create separate [legend_name] and Auto in the relation dialog.
126   //        show relation->legend_name in dialog when [x] Auto
127   for (int i = 0; i<count; i++) {
128     RelationPtr relation = legendItems.at(i);
129     QString label = relation->legendName(sameX, sameYUnits);
130 
131     int i_dup = names.indexOf(label);
132     if (i_dup<0) {
133       names.append(label);
134     } else {
135       RelationPtr dup_relation = legendItems.at(i_dup);
136       if (!dup_relation->yLabelInfo().file.isEmpty()) {
137         names.replace(i_dup, label + " (" + dup_relation->yLabelInfo().escapedFile() + ')');
138       }
139       if (!relation->yLabelInfo().file.isEmpty()) {
140         names.append(label + " (" + relation->yLabelInfo().escapedFile() + ')');
141       }
142     }
143   }
144 
145   QSize legendSize(0, 0);
146   QSize titleSize(0,0);
147   Label::Parsed *parsed = Label::parse(_title, _color);
148   int pad = painter->fontMetrics().ascent()/4;
149   Label::RenderContext rc(painter->font(), painter);
150   Label::renderLabel(rc, parsed->chunk, false, false);
151 
152   if (!_title.isEmpty()) {
153     titleSize.setWidth(rc.x+3*pad);
154     titleSize.setHeight(painter->fontMetrics().height()+pad);
155   }
156 
157   QList<QSize> sizes;
158   int max_w = 0;
159   int max_h = 0;
160   for (int i = 0; i<count; i++) {
161     RelationPtr relation = legendItems.at(i);
162     QSize size;
163     painter->save();
164     size = paintRelation(names.at(i), relation, painter, false);
165     painter->restore();
166     sizes.append(size);
167     max_w = qMax(max_w, size.width());
168     max_h = qMax(max_h, size.height());
169   }
170 
171   // determine number of rows and number of columns
172   int n_rows = 0;
173   int n_cols = 0;
174   if (_verticalDisplay) {
175     int h=titleSize.height();
176     for (int i = 0; i<count; i++) {
177       h+=sizes.at(i).height();
178     }
179     int max_legend_height = _plotItem->plotRect().height()*0.6+1;
180     n_cols = qMin(count, h / max_legend_height + 1);
181     n_rows = count / n_cols;
182     while (n_rows*n_cols<count) {
183       n_rows++;
184     }
185   } else {
186     int w = 0;
187     for (int i = 0; i<count; i++) {
188       w+=sizes.at(i).width();
189     }
190     int max_legend_width = _plotItem->plotRect().width()*0.8+1;
191     n_rows = qMin(count, w / max_legend_width+1);
192     n_cols = count/n_rows;
193     while (n_rows*n_cols<count) {
194       n_cols++;
195     }
196   }
197 
198   // determine the dimensions of each column
199   QList<QSize> col_sizes;
200   for (int i=0; i<n_cols; i++) {
201     col_sizes.append(QSize(0,0));
202   }
203   for (int i = 0; i<count; i++) {
204     int col = i/n_rows;
205     col_sizes[col].rheight()+= sizes.at(i).height();
206     col_sizes[col].setWidth(qMax(sizes.at(i).width(), col_sizes.at(col).width()));
207   }
208 
209   // determine the dimensions of the legend
210   int w = 0;
211   int h = 0;
212   for (int col = 0; col < n_cols; col++) {
213     w += col_sizes.at(col).width();
214     h = qMax(h, col_sizes.at(col).height());
215   }
216   legendSize.setHeight(h + titleSize.height());
217   legendSize.setWidth(qMax(titleSize.width(), w));
218   setViewRect(rect().x(), rect().y(), legendSize.width()+pad, legendSize.height()+pad);
219 
220   // Now paint everything
221   painter->drawRect(rect());
222 
223   int x=rect().x();
224   int y=rect().y();
225 
226   if (!_title.isEmpty()) {
227     rc.y = rect().y() + titleSize.height()-pad;
228     rc.x = qMax(rect().x()+pad, rect().x() + legendSize.width()/2 - titleSize.width()/2);
229     Label::renderLabel(rc, parsed->chunk, false, true);
230     y+= titleSize.height();
231   }
232 
233   legendSize.setWidth(0);
234   legendSize.setHeight(0);
235   for (int i = 0; i<count; i++) {
236     RelationPtr relation = legendItems.at(i);
237     painter->save();
238     painter->translate(x,y);
239     paintRelation(names.at(i), relation, painter, true);
240     painter->restore();
241 
242     int col = i/n_rows;
243     int row = i%n_rows;
244     if (row == n_rows-1) { // end of a column
245       x += col_sizes.at(col).width();
246       y = rect().y() + titleSize.height();
247     } else {
248       y += sizes.at(i).height();
249     }
250   }
251   delete parsed;
252 }
253 
254 
paintRelation(QString name,RelationPtr relation,QPainter * painter,bool draw)255 QSize LegendItem::paintRelation(QString name, RelationPtr relation, QPainter *painter, bool draw) {
256   Label::Parsed *parsed = Label::parse(name, _color);
257 
258   int fontHeight = painter->fontMetrics().height();
259   int fontAscent = painter->fontMetrics().ascent();
260 
261   QSize symbol_size = relation->legendSymbolSize(painter);
262   int label_width = 0;
263   int paddingValue = fontHeight / 4;
264 
265   if (relation->symbolLabelOnTop()) {
266     Label::RenderContext tmprc(painter->font(), painter);
267     Label::renderLabel(tmprc, parsed->chunk, false, false);
268     label_width = tmprc.x;
269     painter->translate(paddingValue, fontHeight+paddingValue / 2);
270     symbol_size.setWidth(qMax(label_width, symbol_size.width()));
271   } else {
272     painter->translate(paddingValue, paddingValue / 2);
273   }
274 
275   if (draw) {
276     relation->paintLegendSymbol(painter, symbol_size);
277   }
278 
279   if (relation->symbolLabelOnTop()) {
280     painter->translate((symbol_size.width()-label_width)/2, fontAscent - fontHeight);
281   } else {
282     painter->translate(symbol_size.width() + paddingValue, 0);
283   }
284   Label::RenderContext rc(painter->font(), painter);
285   if (relation->symbolLabelOnTop()) {
286     rc.y = 0;
287   } else {
288     rc.y = (symbol_size.height()+painter->fontMetrics().boundingRect('M').height())/2;
289   }
290   if (parsed) {
291     Label::renderLabel(rc, parsed->chunk, false, draw);
292 
293     delete parsed;
294     parsed = 0;
295   }
296 
297   double h = symbol_size.height() + paddingValue;
298   if (relation->symbolLabelOnTop()) {
299     h += fontHeight;
300   }
301   if (relation->symbolLabelOnTop()) {
302     return QSize(qMax(rc.x,(symbol_size.width())) + (paddingValue * 2), h);
303   } else {
304     return QSize((symbol_size.width()) + (paddingValue * 3) + rc.x, h);
305   }
306 
307 }
308 
309 
save(QXmlStreamWriter & xml)310 void LegendItem::save(QXmlStreamWriter &xml) {
311   Q_UNUSED(xml);
312 }
313 
legendColor() const314 QColor LegendItem::legendColor() const {
315   return _color;
316 }
317 
318 
setLegendColor(const QColor & color)319 void LegendItem::setLegendColor(const QColor &color) {
320   _color = color;
321 }
322 
323 
applyDefaults()324 void LegendItem::applyDefaults() {
325   _auto = dialogDefaults().value(defaultsGroupName()+"/auto",true).toBool();
326 
327   _color = dialogDefaults().value(defaultsGroupName()+"/color",QColor(Qt::black)).value<QColor>();
328 
329   QFont font;
330   font.fromString(dialogDefaults().value(defaultsGroupName()+"/font",font.toString()).toString());
331   setLegendFont(font);
332 
333   setFontScale(dialogDefaults().value(defaultsGroupName()+"/fontScale", 12.0).toDouble());
334   _verticalDisplay = dialogDefaults().value(defaultsGroupName()+"/verticalDisplay",true).toBool();
335 }
336 
setFont(const QFont & f,const QColor & c)337 void LegendItem::setFont(const QFont &f, const QColor &c) {
338   setLegendColor(c);
339   setLegendFont(f);
340   setFontScale(f.pointSize());
341 }
342 
saveAsDialogDefaults() const343 void LegendItem::saveAsDialogDefaults() const {
344   dialogDefaults().setValue(defaultsGroupName()+"/auto",_auto);
345   dialogDefaults().setValue(defaultsGroupName()+"/title", _title);
346   dialogDefaults().setValue(defaultsGroupName()+"/verticalDisplay", _verticalDisplay);
347 
348   QFont F = _font;
349   F.setPointSize(_fontScale);
350   saveDialogDefaultsFont(F, _color);
351   saveDialogDefaultsPen(defaultsGroupName(), pen());
352   saveDialogDefaultsBrush(defaultsGroupName(), brush());
353 }
354 
saveDialogDefaultsFont(const QFont & F,const QColor & C)355 void LegendItem::saveDialogDefaultsFont(const QFont &F, const QColor &C) {
356   dialogDefaults().setValue(staticDefaultsGroupName()+"/font", QVariant(F).toString());
357   dialogDefaults().setValue(staticDefaultsGroupName()+"/fontScale",F.pointSize());
358   dialogDefaults().setValue(staticDefaultsGroupName()+"/color", C.name());
359 }
360 
saveInPlot(QXmlStreamWriter & xml)361 void LegendItem::saveInPlot(QXmlStreamWriter &xml) {
362   xml.writeStartElement("legend");
363   xml.writeAttribute("auto", QVariant(_auto).toString());
364   xml.writeAttribute("title", QVariant(_title).toString());
365   xml.writeAttribute("font", QVariant(_font).toString());
366   xml.writeAttribute("fontscale", QVariant(_fontScale).toString());
367   xml.writeAttribute("color", QVariant(_color).toString());
368   xml.writeAttribute("verticaldisplay", QVariant(_verticalDisplay).toString());
369   ViewItem::save(xml);
370   foreach (const RelationPtr &relation, _relations) {
371     xml.writeStartElement("relation");
372     xml.writeAttribute("tag", relation->Name());
373     xml.writeEndElement();
374   }
375   xml.writeEndElement();
376 }
377 
378 
configureFromXml(QXmlStreamReader & xml,ObjectStore * store)379 bool LegendItem::configureFromXml(QXmlStreamReader &xml, ObjectStore *store) {
380   bool validTag = true;
381 
382   QString primaryTag = xml.name().toString();
383   QXmlStreamAttributes attrs = xml.attributes();
384   QStringRef av;
385   av = attrs.value("auto");
386   if (!av.isNull()) {
387     setAutoContents(QVariant(av.toString()).toBool());
388   }
389   av = attrs.value("title");
390   if (!av.isNull()) {
391     setTitle(av.toString());
392   }
393   av = attrs.value("font");
394   if (!av.isNull()) {
395     QFont font;
396     font.fromString(av.toString());
397     setLegendFont(font);
398   }
399   av = attrs.value("fontscale");
400   if (!av.isNull()) {
401     setFontScale(QVariant(av.toString()).toDouble());
402   }
403   av = attrs.value("color");
404   if (!av.isNull()) {
405     setLegendColor(QColor(av.toString()));
406   }
407   av = attrs.value("verticaldisplay");
408   if (!av.isNull()) {
409     setVerticalDisplay(QVariant(av.toString()).toBool());
410   }
411   QString expectedEnd;
412   while (!(xml.isEndElement() && (xml.name().toString() == primaryTag))) {
413    if (xml.isStartElement()) {
414     if (xml.name().toString() == "relation") {
415       expectedEnd = xml.name().toString();
416       attrs = xml.attributes();
417       QString tagName = attrs.value("tag").toString();
418       RelationPtr relation = kst_cast<Relation>(store->retrieveObject(tagName));
419       if (relation) {
420         _relations.append(relation);
421       }
422     } else {
423       parse(xml, validTag);
424     }
425    } else if (xml.isEndElement()) {
426       if (xml.name().toString() != expectedEnd) {
427         validTag = false;
428         break;
429       }
430     }
431     xml.readNext();
432   }
433 
434   return validTag;
435 }
436 
437 
edit()438 void LegendItem::edit() {
439   LegendItemDialog *editDialog = new LegendItemDialog(this);
440   editDialog->show();
441 }
442 
443 
remove()444 void LegendItem::remove() {
445   if (_plotItem) {
446     _plotItem->setShowLegend(false);
447   }
448   ViewItem::remove();
449 }
450 
451 
setAutoContents(const bool autoContents)452 void LegendItem::setAutoContents(const bool autoContents) {
453   _auto = autoContents;
454 }
455 
456 
autoContents() const457 bool LegendItem::autoContents() const {
458   return _auto;
459 }
460 
461 
setVerticalDisplay(const bool vertical)462 void LegendItem::setVerticalDisplay(const bool vertical) {
463   _verticalDisplay = vertical;
464 }
465 
466 
verticalDisplay() const467 bool LegendItem::verticalDisplay() const {
468   return _verticalDisplay;
469 }
470 
471 
setTitle(const QString & title)472 void LegendItem::setTitle(const QString &title) {
473   _title = title;
474 }
475 
476 
title() const477 QString LegendItem::title() const {
478   return _title;
479 }
480 
481 
font() const482 QFont LegendItem::font() const {
483   return _font;
484 }
485 
486 
setLegendFont(const QFont & font)487 void LegendItem::setLegendFont(const QFont &font) {
488   _font = font;
489 }
490 
491 
fontScale() const492 qreal LegendItem::fontScale() const {
493   return _fontScale;
494 }
495 
496 
setFontScale(const qreal scale)497 void LegendItem::setFontScale(const qreal scale) {
498   _fontScale = scale;
499 }
500 
_automaticDescriptiveName() const501 QString LegendItem::_automaticDescriptiveName() const {
502 
503   QString name = tr("Empty Legend");
504   if (_auto) {
505     name = _plotItem->descriptiveName();
506   } else if (_relations.size()>0) {
507     name = _relations.at(0)->descriptiveName();
508     if (_relations.size()>1) {
509       name += ", ...";
510     }
511   }
512   //qDebug() << "a desc name called: " << name << " relation.length: " << _relations.length();
513   return name;
514 }
515 
descriptionTip() const516 QString LegendItem::descriptionTip() const {
517   QString contents;
518   foreach (const RelationPtr &relation, _relations) {
519     contents += QString("  %1\n").arg(relation->Name());
520   }
521 
522   return tr("Plot: %1 \nContents:\n %2").arg(Name()).arg(contents);
523 }
524 
createScriptInterface()525 ScriptInterface* LegendItem::createScriptInterface() {
526   return new LegendSI(this);
527 }
528 
529 
530 }
531 
532 // vim: ts=2 sw=2 et
533