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