From 5abf8776f10d612eec0dc443ddd3dede25536cd3 Mon Sep 17 00:00:00 2001 From: Mark Liversedge Date: Mon, 9 Dec 2013 10:02:47 +0000 Subject: [PATCH] QT5 -- Add Qwt 6.1-multiaxes --- qwt/COPYING | 543 ++ qwt/INSTALL | 1 + qwt/README | 34 + qwt/TODO | 38 + qwt/admin/svn2package.sh | 326 + qwt/designer/designer.pro | 132 + qwt/designer/pixmaps/qwtanalogclock.png | Bin 0 -> 422 bytes qwt/designer/pixmaps/qwtcompass.png | Bin 0 -> 992 bytes qwt/designer/pixmaps/qwtcounter.png | Bin 0 -> 1026 bytes qwt/designer/pixmaps/qwtdial.png | Bin 0 -> 433 bytes qwt/designer/pixmaps/qwtknob.png | Bin 0 -> 807 bytes qwt/designer/pixmaps/qwtplot.png | Bin 0 -> 543 bytes qwt/designer/pixmaps/qwtscale.png | Bin 0 -> 373 bytes qwt/designer/pixmaps/qwtslider.png | Bin 0 -> 452 bytes qwt/designer/pixmaps/qwtthermo.png | Bin 0 -> 580 bytes qwt/designer/pixmaps/qwtwheel.png | Bin 0 -> 298 bytes qwt/designer/pixmaps/qwtwidget.png | Bin 0 -> 1078 bytes qwt/designer/qwt_designer_plotdialog.cpp | 42 + qwt/designer/qwt_designer_plotdialog.h | 31 + qwt/designer/qwt_designer_plugin.cpp | 570 ++ qwt/designer/qwt_designer_plugin.h | 248 + qwt/designer/qwt_designer_plugin.qrc | 15 + qwt/doc/Doxyfile | 1799 +++++ qwt/doc/articles/TODO | 8 + qwt/doc/changes.dox | 306 + qwt/doc/doc.pro | 22 + qwt/doc/images/analogclock.png | Bin 0 -> 12649 bytes qwt/doc/images/cpuplot.png | Bin 0 -> 36517 bytes qwt/doc/images/curves.png | Bin 0 -> 20531 bytes qwt/doc/images/dials1.png | Bin 0 -> 72717 bytes qwt/doc/images/dials2.png | Bin 0 -> 28153 bytes qwt/doc/images/graph.png | Bin 0 -> 13144 bytes qwt/doc/images/histogram.png | Bin 0 -> 12524 bytes qwt/doc/images/knob.png | Bin 0 -> 6411 bytes qwt/doc/images/plot.png | Bin 0 -> 31037 bytes qwt/doc/images/radio.png | Bin 0 -> 19794 bytes qwt/doc/images/scatterplot.png | Bin 0 -> 23882 bytes qwt/doc/images/sinus.png | Bin 0 -> 29788 bytes qwt/doc/images/sliders.png | Bin 0 -> 9308 bytes qwt/doc/images/spectrogram1.png | Bin 0 -> 47416 bytes qwt/doc/images/spectrogram2.png | Bin 0 -> 16812 bytes qwt/doc/images/spectrogram3.png | Bin 0 -> 56165 bytes qwt/doc/images/sysinfo.png | Bin 0 -> 16508 bytes qwt/doc/install.dox | 305 + qwt/doc/qwt.dox | 153 + qwt/doc/tex/plotlayout.tex | 155 + qwt/doc/tex/qwtplot.tex | 234 + qwt/examples/animation/animation.pro | 19 + qwt/examples/animation/main.cpp | 46 + qwt/examples/animation/plot.cpp | 241 + qwt/examples/animation/plot.h | 21 + qwt/examples/barchart/barchart.cpp | 132 + qwt/examples/barchart/barchart.h | 25 + qwt/examples/barchart/barchart.pro | 19 + qwt/examples/barchart/main.cpp | 64 + qwt/examples/bode/bode.pro | 23 + qwt/examples/bode/complexnumber.h | 83 + qwt/examples/bode/main.cpp | 13 + qwt/examples/bode/mainwindow.cpp | 231 + qwt/examples/bode/mainwindow.h | 35 + qwt/examples/bode/pixmaps.h | 99 + qwt/examples/bode/plot.cpp | 208 + qwt/examples/bode/plot.h | 31 + qwt/examples/controls/controls.pro | 34 + qwt/examples/controls/dialbox.cpp | 163 + qwt/examples/controls/dialbox.h | 25 + qwt/examples/controls/dialtab.cpp | 17 + qwt/examples/controls/dialtab.h | 12 + qwt/examples/controls/knobbox.cpp | 119 + qwt/examples/controls/knobbox.h | 25 + qwt/examples/controls/knobtab.cpp | 17 + qwt/examples/controls/knobtab.h | 12 + qwt/examples/controls/main.cpp | 41 + qwt/examples/controls/sliderbox.cpp | 172 + qwt/examples/controls/sliderbox.h | 25 + qwt/examples/controls/slidertab.cpp | 37 + qwt/examples/controls/slidertab.h | 18 + qwt/examples/controls/wheelbox.cpp | 188 + qwt/examples/controls/wheelbox.h | 29 + qwt/examples/controls/wheeltab.cpp | 28 + qwt/examples/controls/wheeltab.h | 12 + qwt/examples/cpuplot/cpupiemarker.cpp | 55 + qwt/examples/cpuplot/cpupiemarker.h | 17 + qwt/examples/cpuplot/cpuplot.cpp | 254 + qwt/examples/cpuplot/cpuplot.h | 47 + qwt/examples/cpuplot/cpuplot.pro | 22 + qwt/examples/cpuplot/cpustat.cpp | 220 + qwt/examples/cpuplot/cpustat.h | 23 + qwt/examples/curvdemo1/curvdemo1.cpp | 202 + qwt/examples/curvdemo1/curvdemo1.pro | 15 + qwt/examples/dials/attitude_indicator.cpp | 127 + qwt/examples/dials/attitude_indicator.h | 39 + qwt/examples/dials/cockpit_grid.cpp | 186 + qwt/examples/dials/cockpit_grid.h | 28 + qwt/examples/dials/compass_grid.cpp | 226 + qwt/examples/dials/compass_grid.h | 11 + qwt/examples/dials/dials.cpp | 24 + qwt/examples/dials/dials.pro | 26 + qwt/examples/dials/speedo_meter.cpp | 52 + qwt/examples/dials/speedo_meter.h | 18 + qwt/examples/distrowatch/barchart.cpp | 192 + qwt/examples/distrowatch/barchart.h | 26 + qwt/examples/distrowatch/distrowatch.pro | 19 + qwt/examples/distrowatch/main.cpp | 54 + qwt/examples/event_filter/README | 27 + qwt/examples/event_filter/canvaspicker.cpp | 372 + qwt/examples/event_filter/canvaspicker.h | 33 + qwt/examples/event_filter/colorbar.cpp | 112 + qwt/examples/event_filter/colorbar.h | 33 + qwt/examples/event_filter/event_filter.cpp | 51 + qwt/examples/event_filter/event_filter.pro | 25 + qwt/examples/event_filter/plot.cpp | 173 + qwt/examples/event_filter/plot.h | 25 + qwt/examples/event_filter/scalepicker.cpp | 119 + qwt/examples/event_filter/scalepicker.h | 20 + qwt/examples/examples.pri | 77 + qwt/examples/examples.pro | 51 + qwt/examples/friedberg/friedberg.pro | 21 + qwt/examples/friedberg/friedberg2007.cpp | 384 + qwt/examples/friedberg/friedberg2007.h | 28 + qwt/examples/friedberg/main.cpp | 55 + qwt/examples/friedberg/plot.cpp | 209 + qwt/examples/friedberg/plot.h | 43 + qwt/examples/itemeditor/editor.cpp | 374 + qwt/examples/itemeditor/editor.h | 66 + qwt/examples/itemeditor/itemeditor.pro | 24 + qwt/examples/itemeditor/main.cpp | 54 + qwt/examples/itemeditor/plot.cpp | 120 + qwt/examples/itemeditor/plot.h | 33 + qwt/examples/itemeditor/shapefactory.cpp | 113 + qwt/examples/itemeditor/shapefactory.h | 21 + qwt/examples/legends/legends.pro | 24 + qwt/examples/legends/main.cpp | 14 + qwt/examples/legends/mainwindow.cpp | 61 + qwt/examples/legends/mainwindow.h | 20 + qwt/examples/legends/panel.cpp | 216 + qwt/examples/legends/panel.h | 52 + qwt/examples/legends/plot.cpp | 263 + qwt/examples/legends/plot.h | 32 + qwt/examples/legends/settings.h | 47 + qwt/examples/oscilloscope/curvedata.cpp | 27 + qwt/examples/oscilloscope/curvedata.h | 16 + qwt/examples/oscilloscope/knob.cpp | 95 + qwt/examples/oscilloscope/knob.h | 38 + qwt/examples/oscilloscope/main.cpp | 36 + qwt/examples/oscilloscope/mainwindow.cpp | 69 + qwt/examples/oscilloscope/mainwindow.h | 32 + qwt/examples/oscilloscope/osci.css | 55 + qwt/examples/oscilloscope/oscilloscope.pro | 31 + qwt/examples/oscilloscope/plot.cpp | 254 + qwt/examples/oscilloscope/plot.h | 44 + qwt/examples/oscilloscope/samplingthread.cpp | 54 + qwt/examples/oscilloscope/samplingthread.h | 25 + qwt/examples/oscilloscope/signaldata.cpp | 133 + qwt/examples/oscilloscope/signaldata.h | 33 + qwt/examples/oscilloscope/wheelbox.cpp | 102 + qwt/examples/oscilloscope/wheelbox.h | 40 + qwt/examples/radio/ampfrm.cpp | 201 + qwt/examples/radio/ampfrm.h | 29 + qwt/examples/radio/mainwindow.cpp | 50 + qwt/examples/radio/mainwindow.h | 15 + qwt/examples/radio/radio.cpp | 12 + qwt/examples/radio/radio.pro | 23 + qwt/examples/radio/tunerfrm.cpp | 113 + qwt/examples/radio/tunerfrm.h | 30 + qwt/examples/rasterview/main.cpp | 59 + qwt/examples/rasterview/plot.cpp | 112 + qwt/examples/rasterview/plot.h | 17 + qwt/examples/rasterview/rasterview.pro | 19 + qwt/examples/realtime/README | 25 + qwt/examples/realtime/clear.xpm | 51 + qwt/examples/realtime/incrementalplot.cpp | 124 + qwt/examples/realtime/incrementalplot.h | 28 + qwt/examples/realtime/main.cpp | 12 + qwt/examples/realtime/mainwindow.cpp | 193 + qwt/examples/realtime/mainwindow.h | 37 + qwt/examples/realtime/randomplot.cpp | 141 + qwt/examples/realtime/randomplot.h | 39 + qwt/examples/realtime/realtime.pro | 28 + qwt/examples/realtime/scrollbar.cpp | 170 + qwt/examples/realtime/scrollbar.h | 53 + qwt/examples/realtime/scrollzoomer.cpp | 478 ++ qwt/examples/realtime/scrollzoomer.h | 67 + qwt/examples/realtime/start.xpm | 266 + qwt/examples/refreshtest/circularbuffer.cpp | 73 + qwt/examples/refreshtest/circularbuffer.h | 35 + qwt/examples/refreshtest/main.cpp | 30 + qwt/examples/refreshtest/mainwindow.cpp | 76 + qwt/examples/refreshtest/mainwindow.h | 28 + qwt/examples/refreshtest/panel.cpp | 297 + qwt/examples/refreshtest/panel.h | 54 + qwt/examples/refreshtest/plot.cpp | 218 + qwt/examples/refreshtest/plot.h | 38 + qwt/examples/refreshtest/refreshtest.pro | 27 + qwt/examples/refreshtest/settings.h | 78 + qwt/examples/scatterplot/main.cpp | 13 + qwt/examples/scatterplot/mainwindow.cpp | 35 + qwt/examples/scatterplot/mainwindow.h | 22 + qwt/examples/scatterplot/plot.cpp | 91 + qwt/examples/scatterplot/plot.h | 23 + qwt/examples/scatterplot/scatterplot.pro | 22 + qwt/examples/simpleplot/simpleplot.cpp | 42 + qwt/examples/simpleplot/simpleplot.pro | 16 + qwt/examples/sinusplot/sinusplot.cpp | 218 + qwt/examples/sinusplot/sinusplot.pro | 16 + qwt/examples/spectrogram/main.cpp | 75 + qwt/examples/spectrogram/plot.cpp | 169 + qwt/examples/spectrogram/plot.h | 22 + qwt/examples/spectrogram/spectrogram.pro | 19 + qwt/examples/stockchart/griditem.cpp | 278 + qwt/examples/stockchart/griditem.h | 70 + qwt/examples/stockchart/legend.cpp | 353 + qwt/examples/stockchart/legend.h | 42 + qwt/examples/stockchart/main.cpp | 54 + qwt/examples/stockchart/plot.cpp | 253 + qwt/examples/stockchart/plot.h | 24 + qwt/examples/stockchart/quotefactory.cpp | 856 +++ qwt/examples/stockchart/quotefactory.h | 22 + qwt/examples/stockchart/stockchart.pro | 25 + qwt/examples/stylesheets/blue.css | 66 + qwt/examples/stylesheets/choco.css | 50 + qwt/examples/stylesheets/oily.css | 51 + qwt/examples/stylesheets/rosy.css | 50 + qwt/examples/sysinfo/sysinfo.cpp | 123 + qwt/examples/sysinfo/sysinfo.pro | 15 + qwt/examples/tvplot/main.cpp | 57 + qwt/examples/tvplot/tvplot.cpp | 169 + qwt/examples/tvplot/tvplot.h | 23 + qwt/examples/tvplot/tvplot.pro | 19 + qwt/playground/curvetracker/curvetracker.cpp | 129 + qwt/playground/curvetracker/curvetracker.h | 22 + qwt/playground/curvetracker/curvetracker.pro | 22 + qwt/playground/curvetracker/main.cpp | 13 + qwt/playground/curvetracker/plot.cpp | 107 + qwt/playground/curvetracker/plot.h | 21 + qwt/playground/graphicscale/canvas.cpp | 72 + qwt/playground/graphicscale/canvas.h | 33 + qwt/playground/graphicscale/graphicscale.pro | 23 + qwt/playground/graphicscale/main.cpp | 13 + qwt/playground/graphicscale/mainwindow.cpp | 109 + qwt/playground/graphicscale/mainwindow.h | 22 + qwt/playground/playground.pri | 78 + qwt/playground/playground.pro | 32 + qwt/playground/plotmatrix/main.cpp | 66 + qwt/playground/plotmatrix/plotmatrix.cpp | 428 ++ qwt/playground/plotmatrix/plotmatrix.h | 41 + qwt/playground/plotmatrix/plotmatrix.pro | 19 + qwt/playground/rescaler/main.cpp | 15 + qwt/playground/rescaler/mainwindow.cpp | 146 + qwt/playground/rescaler/mainwindow.h | 39 + qwt/playground/rescaler/plot.cpp | 181 + qwt/playground/rescaler/plot.h | 28 + qwt/playground/rescaler/rescaler.pro | 22 + qwt/playground/scaleengine/mainwindow.cpp | 120 + qwt/playground/scaleengine/mainwindow.h | 16 + qwt/playground/scaleengine/plot.cpp | 70 + qwt/playground/scaleengine/plot.h | 23 + qwt/playground/scaleengine/scaleengine.cpp | 13 + qwt/playground/scaleengine/scaleengine.pro | 24 + qwt/playground/scaleengine/transformplot.cpp | 108 + qwt/playground/scaleengine/transformplot.h | 27 + qwt/playground/shapes/shapes.cpp | 100 + qwt/playground/shapes/shapes.pro | 16 + qwt/playground/svgmap/main.cpp | 49 + qwt/playground/svgmap/plot.cpp | 79 + qwt/playground/svgmap/plot.h | 26 + qwt/playground/svgmap/svgmap.pro | 26 + qwt/playground/symbols/symbols.cpp | 212 + qwt/playground/symbols/symbols.pro | 16 + qwt/playground/timescale/main.cpp | 13 + qwt/playground/timescale/mainwindow.cpp | 58 + qwt/playground/timescale/mainwindow.h | 20 + qwt/playground/timescale/panel.cpp | 95 + qwt/playground/timescale/panel.h | 32 + qwt/playground/timescale/plot.cpp | 89 + qwt/playground/timescale/plot.h | 23 + qwt/playground/timescale/settings.h | 25 + qwt/playground/timescale/timescale.pro | 24 + qwt/qwt.prf | 38 + qwt/qwt.pro | 36 + qwt/qwtbuild.pri | 86 + qwt/qwtconfig.pri | 163 + qwt/qwtfunctions.pri | 71 + qwt/src/qwt.h | 22 + qwt/src/qwt_abstract_legend.cpp | 38 + qwt/src/qwt_abstract_legend.h | 71 + qwt/src/qwt_abstract_scale.cpp | 449 ++ qwt/src/qwt_abstract_scale.h | 105 + qwt/src/qwt_abstract_scale_draw.cpp | 416 ++ qwt/src/qwt_abstract_scale_draw.h | 141 + qwt/src/qwt_abstract_slider.cpp | 822 +++ qwt/src/qwt_abstract_slider.h | 167 + qwt/src/qwt_analog_clock.cpp | 244 + qwt/src/qwt_analog_clock.h | 93 + qwt/src/qwt_arrow_button.cpp | 333 + qwt/src/qwt_arrow_button.h | 52 + qwt/src/qwt_axes_mask.cpp | 82 + qwt/src/qwt_axes_mask.h | 30 + qwt/src/qwt_axis_id.cpp | 32 + qwt/src/qwt_axis_id.h | 106 + qwt/src/qwt_clipper.cpp | 510 ++ qwt/src/qwt_clipper.h | 40 + qwt/src/qwt_color_map.cpp | 444 ++ qwt/src/qwt_color_map.h | 200 + qwt/src/qwt_column_symbol.cpp | 293 + qwt/src/qwt_column_symbol.h | 161 + qwt/src/qwt_compass.cpp | 308 + qwt/src/qwt_compass.h | 83 + qwt/src/qwt_compass_rose.cpp | 269 + qwt/src/qwt_compass_rose.h | 89 + qwt/src/qwt_compat.h | 42 + qwt/src/qwt_counter.cpp | 785 +++ qwt/src/qwt_counter.h | 161 + qwt/src/qwt_curve_fitter.cpp | 453 ++ qwt/src/qwt_curve_fitter.h | 139 + qwt/src/qwt_date.cpp | 654 ++ qwt/src/qwt_date.h | 128 + qwt/src/qwt_date_scale_draw.cpp | 269 + qwt/src/qwt_date_scale_draw.h | 77 + qwt/src/qwt_date_scale_engine.cpp | 1300 ++++ qwt/src/qwt_date_scale_engine.h | 77 + qwt/src/qwt_dial.cpp | 872 +++ qwt/src/qwt_dial.h | 168 + qwt/src/qwt_dial_needle.cpp | 440 ++ qwt/src/qwt_dial_needle.h | 187 + qwt/src/qwt_dyngrid_layout.cpp | 591 ++ qwt/src/qwt_dyngrid_layout.h | 83 + qwt/src/qwt_event_pattern.cpp | 265 + qwt/src/qwt_event_pattern.h | 240 + qwt/src/qwt_global.h | 41 + qwt/src/qwt_graphic.cpp | 986 +++ qwt/src/qwt_graphic.h | 172 + qwt/src/qwt_interval.cpp | 354 + qwt/src/qwt_interval.h | 320 + qwt/src/qwt_interval_symbol.cpp | 319 + qwt/src/qwt_interval_symbol.h | 87 + qwt/src/qwt_knob.cpp | 845 +++ qwt/src/qwt_knob.h | 178 + qwt/src/qwt_legend.cpp | 801 +++ qwt/src/qwt_legend.h | 117 + qwt/src/qwt_legend_data.cpp | 129 + qwt/src/qwt_legend_data.h | 87 + qwt/src/qwt_legend_label.cpp | 421 ++ qwt/src/qwt_legend_label.h | 80 + qwt/src/qwt_magnifier.cpp | 492 ++ qwt/src/qwt_magnifier.h | 86 + qwt/src/qwt_math.cpp | 74 + qwt/src/qwt_math.h | 149 + qwt/src/qwt_matrix_raster_data.cpp | 298 + qwt/src/qwt_matrix_raster_data.h | 74 + qwt/src/qwt_null_paintdevice.cpp | 593 ++ qwt/src/qwt_null_paintdevice.h | 126 + qwt/src/qwt_painter.cpp | 1266 ++++ qwt/src/qwt_painter.h | 188 + qwt/src/qwt_painter_command.cpp | 237 + qwt/src/qwt_painter_command.h | 173 + qwt/src/qwt_panner.cpp | 538 ++ qwt/src/qwt_panner.h | 103 + qwt/src/qwt_picker.cpp | 1577 +++++ qwt/src/qwt_picker.h | 329 + qwt/src/qwt_picker_machine.cpp | 526 ++ qwt/src/qwt_picker_machine.h | 214 + qwt/src/qwt_pixel_matrix.cpp | 51 + qwt/src/qwt_pixel_matrix.h | 98 + qwt/src/qwt_plot.cpp | 1180 ++++ qwt/src/qwt_plot.h | 325 + qwt/src/qwt_plot_abstract_barchart.cpp | 367 + qwt/src/qwt_plot_abstract_barchart.h | 97 + qwt/src/qwt_plot_axis.cpp | 829 +++ qwt/src/qwt_plot_barchart.cpp | 455 ++ qwt/src/qwt_plot_barchart.h | 118 + qwt/src/qwt_plot_canvas.cpp | 1097 +++ qwt/src/qwt_plot_canvas.h | 171 + qwt/src/qwt_plot_curve.cpp | 1191 ++++ qwt/src/qwt_plot_curve.h | 337 + qwt/src/qwt_plot_dict.cpp | 191 + qwt/src/qwt_plot_dict.h | 58 + qwt/src/qwt_plot_directpainter.cpp | 317 + qwt/src/qwt_plot_directpainter.h | 100 + qwt/src/qwt_plot_glcanvas.cpp | 357 + qwt/src/qwt_plot_glcanvas.h | 136 + qwt/src/qwt_plot_grid.cpp | 438 ++ qwt/src/qwt_plot_grid.h | 87 + qwt/src/qwt_plot_histogram.cpp | 690 ++ qwt/src/qwt_plot_histogram.h | 139 + qwt/src/qwt_plot_intervalcurve.cpp | 603 ++ qwt/src/qwt_plot_intervalcurve.h | 132 + qwt/src/qwt_plot_item.cpp | 709 ++ qwt/src/qwt_plot_item.h | 308 + qwt/src/qwt_plot_layout.cpp | 1805 +++++ qwt/src/qwt_plot_layout.h | 113 + qwt/src/qwt_plot_legenditem.cpp | 865 +++ qwt/src/qwt_plot_legenditem.h | 136 + qwt/src/qwt_plot_magnifier.cpp | 141 + qwt/src/qwt_plot_magnifier.h | 55 + qwt/src/qwt_plot_marker.cpp | 610 ++ qwt/src/qwt_plot_marker.h | 130 + qwt/src/qwt_plot_multi_barchart.cpp | 740 ++ qwt/src/qwt_plot_multi_barchart.h | 127 + qwt/src/qwt_plot_panner.cpp | 272 + qwt/src/qwt_plot_panner.h | 61 + qwt/src/qwt_plot_picker.cpp | 423 ++ qwt/src/qwt_plot_picker.h | 116 + qwt/src/qwt_plot_rasteritem.cpp | 946 +++ qwt/src/qwt_plot_rasteritem.h | 152 + qwt/src/qwt_plot_renderer.cpp | 1033 +++ qwt/src/qwt_plot_renderer.h | 169 + qwt/src/qwt_plot_rescaler.cpp | 658 ++ qwt/src/qwt_plot_rescaler.h | 142 + qwt/src/qwt_plot_scaleitem.cpp | 458 ++ qwt/src/qwt_plot_scaleitem.h | 94 + qwt/src/qwt_plot_seriesitem.cpp | 112 + qwt/src/qwt_plot_seriesitem.h | 66 + qwt/src/qwt_plot_shapeitem.cpp | 491 ++ qwt/src/qwt_plot_shapeitem.h | 111 + qwt/src/qwt_plot_spectrocurve.cpp | 321 + qwt/src/qwt_plot_spectrocurve.h | 79 + qwt/src/qwt_plot_spectrogram.cpp | 660 ++ qwt/src/qwt_plot_spectrogram.h | 118 + qwt/src/qwt_plot_svgitem.cpp | 219 + qwt/src/qwt_plot_svgitem.h | 61 + qwt/src/qwt_plot_textlabel.cpp | 261 + qwt/src/qwt_plot_textlabel.h | 75 + qwt/src/qwt_plot_tradingcurve.cpp | 682 ++ qwt/src/qwt_plot_tradingcurve.h | 174 + qwt/src/qwt_plot_xml.cpp | 42 + qwt/src/qwt_plot_zoneitem.cpp | 315 + qwt/src/qwt_plot_zoneitem.h | 65 + qwt/src/qwt_plot_zoomer.cpp | 601 ++ qwt/src/qwt_plot_zoomer.h | 140 + qwt/src/qwt_point_3d.cpp | 22 + qwt/src/qwt_point_3d.h | 189 + qwt/src/qwt_point_data.cpp | 304 + qwt/src/qwt_point_data.h | 146 + qwt/src/qwt_point_mapper.cpp | 721 ++ qwt/src/qwt_point_mapper.h | 89 + qwt/src/qwt_point_polar.cpp | 121 + qwt/src/qwt_point_polar.h | 201 + qwt/src/qwt_raster_data.cpp | 404 ++ qwt/src/qwt_raster_data.h | 95 + qwt/src/qwt_round_scale_draw.cpp | 314 + qwt/src/qwt_round_scale_draw.h | 66 + qwt/src/qwt_samples.h | 239 + qwt/src/qwt_sampling_thread.cpp | 106 + qwt/src/qwt_sampling_thread.h | 50 + qwt/src/qwt_scale_div.cpp | 331 + qwt/src/qwt_scale_div.h | 110 + qwt/src/qwt_scale_draw.cpp | 926 +++ qwt/src/qwt_scale_draw.h | 120 + qwt/src/qwt_scale_engine.cpp | 1118 +++ qwt/src/qwt_scale_engine.h | 220 + qwt/src/qwt_scale_map.cpp | 248 + qwt/src/qwt_scale_map.h | 175 + qwt/src/qwt_scale_map_table.h | 41 + qwt/src/qwt_scale_widget.cpp | 942 +++ qwt/src/qwt_scale_widget.h | 136 + qwt/src/qwt_series_data.cpp | 346 + qwt/src/qwt_series_data.h | 355 + qwt/src/qwt_series_store.h | 199 + qwt/src/qwt_slider.cpp | 991 +++ qwt/src/qwt_slider.h | 130 + qwt/src/qwt_spline.cpp | 384 + qwt/src/qwt_spline.h | 101 + qwt/src/qwt_symbol.cpp | 1767 +++++ qwt/src/qwt_symbol.h | 258 + qwt/src/qwt_system_clock.cpp | 396 ++ qwt/src/qwt_system_clock.h | 47 + qwt/src/qwt_text.cpp | 684 ++ qwt/src/qwt_text.h | 223 + qwt/src/qwt_text_engine.cpp | 345 + qwt/src/qwt_text_engine.h | 172 + qwt/src/qwt_text_label.cpp | 324 + qwt/src/qwt_text_label.h | 77 + qwt/src/qwt_thermo.cpp | 1004 +++ qwt/src/qwt_thermo.h | 178 + qwt/src/qwt_transform.cpp | 165 + qwt/src/qwt_transform.h | 137 + qwt/src/qwt_wheel.cpp | 1299 ++++ qwt/src/qwt_wheel.h | 178 + qwt/src/qwt_widget_overlay.cpp | 373 + qwt/src/qwt_widget_overlay.h | 148 + qwt/src/src.pri | 253 + qwt/src/src.pro | 55 + qwt/textengines/mathml/mathml.pro | 53 + qwt/textengines/mathml/qtmmlwidget-license | 44 + .../mathml/qwt_mathml_text_engine.cpp | 132 + .../mathml/qwt_mathml_text_engine.h | 53 + qwt/textengines/mathml/qwt_mml_document.cpp | 6261 +++++++++++++++++ qwt/textengines/mathml/qwt_mml_document.h | 45 + qwt/textengines/mathml/qwtmathml.prf | 38 + qwt/textengines/textengines.pri | 47 + qwt/textengines/textengines.pro | 16 + 492 files changed, 97336 insertions(+) create mode 100644 qwt/COPYING create mode 100644 qwt/INSTALL create mode 100644 qwt/README create mode 100644 qwt/TODO create mode 100755 qwt/admin/svn2package.sh create mode 100644 qwt/designer/designer.pro create mode 100644 qwt/designer/pixmaps/qwtanalogclock.png create mode 100644 qwt/designer/pixmaps/qwtcompass.png create mode 100644 qwt/designer/pixmaps/qwtcounter.png create mode 100644 qwt/designer/pixmaps/qwtdial.png create mode 100644 qwt/designer/pixmaps/qwtknob.png create mode 100644 qwt/designer/pixmaps/qwtplot.png create mode 100644 qwt/designer/pixmaps/qwtscale.png create mode 100644 qwt/designer/pixmaps/qwtslider.png create mode 100644 qwt/designer/pixmaps/qwtthermo.png create mode 100644 qwt/designer/pixmaps/qwtwheel.png create mode 100644 qwt/designer/pixmaps/qwtwidget.png create mode 100644 qwt/designer/qwt_designer_plotdialog.cpp create mode 100644 qwt/designer/qwt_designer_plotdialog.h create mode 100644 qwt/designer/qwt_designer_plugin.cpp create mode 100644 qwt/designer/qwt_designer_plugin.h create mode 100644 qwt/designer/qwt_designer_plugin.qrc create mode 100644 qwt/doc/Doxyfile create mode 100644 qwt/doc/articles/TODO create mode 100644 qwt/doc/changes.dox create mode 100644 qwt/doc/doc.pro create mode 100644 qwt/doc/images/analogclock.png create mode 100644 qwt/doc/images/cpuplot.png create mode 100644 qwt/doc/images/curves.png create mode 100644 qwt/doc/images/dials1.png create mode 100644 qwt/doc/images/dials2.png create mode 100644 qwt/doc/images/graph.png create mode 100644 qwt/doc/images/histogram.png create mode 100644 qwt/doc/images/knob.png create mode 100644 qwt/doc/images/plot.png create mode 100644 qwt/doc/images/radio.png create mode 100644 qwt/doc/images/scatterplot.png create mode 100644 qwt/doc/images/sinus.png create mode 100644 qwt/doc/images/sliders.png create mode 100644 qwt/doc/images/spectrogram1.png create mode 100644 qwt/doc/images/spectrogram2.png create mode 100644 qwt/doc/images/spectrogram3.png create mode 100644 qwt/doc/images/sysinfo.png create mode 100644 qwt/doc/install.dox create mode 100644 qwt/doc/qwt.dox create mode 100644 qwt/doc/tex/plotlayout.tex create mode 100644 qwt/doc/tex/qwtplot.tex create mode 100644 qwt/examples/animation/animation.pro create mode 100644 qwt/examples/animation/main.cpp create mode 100644 qwt/examples/animation/plot.cpp create mode 100644 qwt/examples/animation/plot.h create mode 100644 qwt/examples/barchart/barchart.cpp create mode 100644 qwt/examples/barchart/barchart.h create mode 100644 qwt/examples/barchart/barchart.pro create mode 100644 qwt/examples/barchart/main.cpp create mode 100644 qwt/examples/bode/bode.pro create mode 100644 qwt/examples/bode/complexnumber.h create mode 100644 qwt/examples/bode/main.cpp create mode 100644 qwt/examples/bode/mainwindow.cpp create mode 100644 qwt/examples/bode/mainwindow.h create mode 100644 qwt/examples/bode/pixmaps.h create mode 100644 qwt/examples/bode/plot.cpp create mode 100644 qwt/examples/bode/plot.h create mode 100644 qwt/examples/controls/controls.pro create mode 100644 qwt/examples/controls/dialbox.cpp create mode 100644 qwt/examples/controls/dialbox.h create mode 100644 qwt/examples/controls/dialtab.cpp create mode 100644 qwt/examples/controls/dialtab.h create mode 100644 qwt/examples/controls/knobbox.cpp create mode 100644 qwt/examples/controls/knobbox.h create mode 100644 qwt/examples/controls/knobtab.cpp create mode 100644 qwt/examples/controls/knobtab.h create mode 100644 qwt/examples/controls/main.cpp create mode 100644 qwt/examples/controls/sliderbox.cpp create mode 100644 qwt/examples/controls/sliderbox.h create mode 100644 qwt/examples/controls/slidertab.cpp create mode 100644 qwt/examples/controls/slidertab.h create mode 100644 qwt/examples/controls/wheelbox.cpp create mode 100644 qwt/examples/controls/wheelbox.h create mode 100644 qwt/examples/controls/wheeltab.cpp create mode 100644 qwt/examples/controls/wheeltab.h create mode 100644 qwt/examples/cpuplot/cpupiemarker.cpp create mode 100644 qwt/examples/cpuplot/cpupiemarker.h create mode 100644 qwt/examples/cpuplot/cpuplot.cpp create mode 100644 qwt/examples/cpuplot/cpuplot.h create mode 100644 qwt/examples/cpuplot/cpuplot.pro create mode 100644 qwt/examples/cpuplot/cpustat.cpp create mode 100644 qwt/examples/cpuplot/cpustat.h create mode 100644 qwt/examples/curvdemo1/curvdemo1.cpp create mode 100644 qwt/examples/curvdemo1/curvdemo1.pro create mode 100644 qwt/examples/dials/attitude_indicator.cpp create mode 100644 qwt/examples/dials/attitude_indicator.h create mode 100644 qwt/examples/dials/cockpit_grid.cpp create mode 100644 qwt/examples/dials/cockpit_grid.h create mode 100644 qwt/examples/dials/compass_grid.cpp create mode 100644 qwt/examples/dials/compass_grid.h create mode 100644 qwt/examples/dials/dials.cpp create mode 100644 qwt/examples/dials/dials.pro create mode 100644 qwt/examples/dials/speedo_meter.cpp create mode 100644 qwt/examples/dials/speedo_meter.h create mode 100644 qwt/examples/distrowatch/barchart.cpp create mode 100644 qwt/examples/distrowatch/barchart.h create mode 100644 qwt/examples/distrowatch/distrowatch.pro create mode 100644 qwt/examples/distrowatch/main.cpp create mode 100644 qwt/examples/event_filter/README create mode 100644 qwt/examples/event_filter/canvaspicker.cpp create mode 100644 qwt/examples/event_filter/canvaspicker.h create mode 100644 qwt/examples/event_filter/colorbar.cpp create mode 100644 qwt/examples/event_filter/colorbar.h create mode 100644 qwt/examples/event_filter/event_filter.cpp create mode 100644 qwt/examples/event_filter/event_filter.pro create mode 100644 qwt/examples/event_filter/plot.cpp create mode 100644 qwt/examples/event_filter/plot.h create mode 100644 qwt/examples/event_filter/scalepicker.cpp create mode 100644 qwt/examples/event_filter/scalepicker.h create mode 100644 qwt/examples/examples.pri create mode 100644 qwt/examples/examples.pro create mode 100644 qwt/examples/friedberg/friedberg.pro create mode 100644 qwt/examples/friedberg/friedberg2007.cpp create mode 100644 qwt/examples/friedberg/friedberg2007.h create mode 100644 qwt/examples/friedberg/main.cpp create mode 100644 qwt/examples/friedberg/plot.cpp create mode 100644 qwt/examples/friedberg/plot.h create mode 100644 qwt/examples/itemeditor/editor.cpp create mode 100644 qwt/examples/itemeditor/editor.h create mode 100644 qwt/examples/itemeditor/itemeditor.pro create mode 100644 qwt/examples/itemeditor/main.cpp create mode 100644 qwt/examples/itemeditor/plot.cpp create mode 100644 qwt/examples/itemeditor/plot.h create mode 100644 qwt/examples/itemeditor/shapefactory.cpp create mode 100644 qwt/examples/itemeditor/shapefactory.h create mode 100644 qwt/examples/legends/legends.pro create mode 100644 qwt/examples/legends/main.cpp create mode 100644 qwt/examples/legends/mainwindow.cpp create mode 100644 qwt/examples/legends/mainwindow.h create mode 100644 qwt/examples/legends/panel.cpp create mode 100644 qwt/examples/legends/panel.h create mode 100644 qwt/examples/legends/plot.cpp create mode 100644 qwt/examples/legends/plot.h create mode 100644 qwt/examples/legends/settings.h create mode 100644 qwt/examples/oscilloscope/curvedata.cpp create mode 100644 qwt/examples/oscilloscope/curvedata.h create mode 100644 qwt/examples/oscilloscope/knob.cpp create mode 100644 qwt/examples/oscilloscope/knob.h create mode 100644 qwt/examples/oscilloscope/main.cpp create mode 100644 qwt/examples/oscilloscope/mainwindow.cpp create mode 100644 qwt/examples/oscilloscope/mainwindow.h create mode 100644 qwt/examples/oscilloscope/osci.css create mode 100644 qwt/examples/oscilloscope/oscilloscope.pro create mode 100644 qwt/examples/oscilloscope/plot.cpp create mode 100644 qwt/examples/oscilloscope/plot.h create mode 100644 qwt/examples/oscilloscope/samplingthread.cpp create mode 100644 qwt/examples/oscilloscope/samplingthread.h create mode 100644 qwt/examples/oscilloscope/signaldata.cpp create mode 100644 qwt/examples/oscilloscope/signaldata.h create mode 100644 qwt/examples/oscilloscope/wheelbox.cpp create mode 100644 qwt/examples/oscilloscope/wheelbox.h create mode 100644 qwt/examples/radio/ampfrm.cpp create mode 100644 qwt/examples/radio/ampfrm.h create mode 100644 qwt/examples/radio/mainwindow.cpp create mode 100644 qwt/examples/radio/mainwindow.h create mode 100644 qwt/examples/radio/radio.cpp create mode 100644 qwt/examples/radio/radio.pro create mode 100644 qwt/examples/radio/tunerfrm.cpp create mode 100644 qwt/examples/radio/tunerfrm.h create mode 100644 qwt/examples/rasterview/main.cpp create mode 100644 qwt/examples/rasterview/plot.cpp create mode 100644 qwt/examples/rasterview/plot.h create mode 100644 qwt/examples/rasterview/rasterview.pro create mode 100644 qwt/examples/realtime/README create mode 100644 qwt/examples/realtime/clear.xpm create mode 100644 qwt/examples/realtime/incrementalplot.cpp create mode 100644 qwt/examples/realtime/incrementalplot.h create mode 100644 qwt/examples/realtime/main.cpp create mode 100644 qwt/examples/realtime/mainwindow.cpp create mode 100644 qwt/examples/realtime/mainwindow.h create mode 100644 qwt/examples/realtime/randomplot.cpp create mode 100644 qwt/examples/realtime/randomplot.h create mode 100644 qwt/examples/realtime/realtime.pro create mode 100644 qwt/examples/realtime/scrollbar.cpp create mode 100644 qwt/examples/realtime/scrollbar.h create mode 100644 qwt/examples/realtime/scrollzoomer.cpp create mode 100644 qwt/examples/realtime/scrollzoomer.h create mode 100644 qwt/examples/realtime/start.xpm create mode 100644 qwt/examples/refreshtest/circularbuffer.cpp create mode 100644 qwt/examples/refreshtest/circularbuffer.h create mode 100644 qwt/examples/refreshtest/main.cpp create mode 100644 qwt/examples/refreshtest/mainwindow.cpp create mode 100644 qwt/examples/refreshtest/mainwindow.h create mode 100644 qwt/examples/refreshtest/panel.cpp create mode 100644 qwt/examples/refreshtest/panel.h create mode 100644 qwt/examples/refreshtest/plot.cpp create mode 100644 qwt/examples/refreshtest/plot.h create mode 100644 qwt/examples/refreshtest/refreshtest.pro create mode 100644 qwt/examples/refreshtest/settings.h create mode 100644 qwt/examples/scatterplot/main.cpp create mode 100644 qwt/examples/scatterplot/mainwindow.cpp create mode 100644 qwt/examples/scatterplot/mainwindow.h create mode 100644 qwt/examples/scatterplot/plot.cpp create mode 100644 qwt/examples/scatterplot/plot.h create mode 100644 qwt/examples/scatterplot/scatterplot.pro create mode 100644 qwt/examples/simpleplot/simpleplot.cpp create mode 100644 qwt/examples/simpleplot/simpleplot.pro create mode 100644 qwt/examples/sinusplot/sinusplot.cpp create mode 100644 qwt/examples/sinusplot/sinusplot.pro create mode 100644 qwt/examples/spectrogram/main.cpp create mode 100644 qwt/examples/spectrogram/plot.cpp create mode 100644 qwt/examples/spectrogram/plot.h create mode 100644 qwt/examples/spectrogram/spectrogram.pro create mode 100644 qwt/examples/stockchart/griditem.cpp create mode 100644 qwt/examples/stockchart/griditem.h create mode 100644 qwt/examples/stockchart/legend.cpp create mode 100644 qwt/examples/stockchart/legend.h create mode 100644 qwt/examples/stockchart/main.cpp create mode 100644 qwt/examples/stockchart/plot.cpp create mode 100644 qwt/examples/stockchart/plot.h create mode 100644 qwt/examples/stockchart/quotefactory.cpp create mode 100644 qwt/examples/stockchart/quotefactory.h create mode 100644 qwt/examples/stockchart/stockchart.pro create mode 100644 qwt/examples/stylesheets/blue.css create mode 100644 qwt/examples/stylesheets/choco.css create mode 100644 qwt/examples/stylesheets/oily.css create mode 100644 qwt/examples/stylesheets/rosy.css create mode 100644 qwt/examples/sysinfo/sysinfo.cpp create mode 100644 qwt/examples/sysinfo/sysinfo.pro create mode 100644 qwt/examples/tvplot/main.cpp create mode 100644 qwt/examples/tvplot/tvplot.cpp create mode 100644 qwt/examples/tvplot/tvplot.h create mode 100644 qwt/examples/tvplot/tvplot.pro create mode 100644 qwt/playground/curvetracker/curvetracker.cpp create mode 100644 qwt/playground/curvetracker/curvetracker.h create mode 100644 qwt/playground/curvetracker/curvetracker.pro create mode 100644 qwt/playground/curvetracker/main.cpp create mode 100644 qwt/playground/curvetracker/plot.cpp create mode 100644 qwt/playground/curvetracker/plot.h create mode 100644 qwt/playground/graphicscale/canvas.cpp create mode 100644 qwt/playground/graphicscale/canvas.h create mode 100644 qwt/playground/graphicscale/graphicscale.pro create mode 100644 qwt/playground/graphicscale/main.cpp create mode 100644 qwt/playground/graphicscale/mainwindow.cpp create mode 100644 qwt/playground/graphicscale/mainwindow.h create mode 100644 qwt/playground/playground.pri create mode 100644 qwt/playground/playground.pro create mode 100644 qwt/playground/plotmatrix/main.cpp create mode 100644 qwt/playground/plotmatrix/plotmatrix.cpp create mode 100644 qwt/playground/plotmatrix/plotmatrix.h create mode 100644 qwt/playground/plotmatrix/plotmatrix.pro create mode 100644 qwt/playground/rescaler/main.cpp create mode 100644 qwt/playground/rescaler/mainwindow.cpp create mode 100644 qwt/playground/rescaler/mainwindow.h create mode 100644 qwt/playground/rescaler/plot.cpp create mode 100644 qwt/playground/rescaler/plot.h create mode 100644 qwt/playground/rescaler/rescaler.pro create mode 100644 qwt/playground/scaleengine/mainwindow.cpp create mode 100644 qwt/playground/scaleengine/mainwindow.h create mode 100644 qwt/playground/scaleengine/plot.cpp create mode 100644 qwt/playground/scaleengine/plot.h create mode 100644 qwt/playground/scaleengine/scaleengine.cpp create mode 100644 qwt/playground/scaleengine/scaleengine.pro create mode 100644 qwt/playground/scaleengine/transformplot.cpp create mode 100644 qwt/playground/scaleengine/transformplot.h create mode 100644 qwt/playground/shapes/shapes.cpp create mode 100644 qwt/playground/shapes/shapes.pro create mode 100644 qwt/playground/svgmap/main.cpp create mode 100644 qwt/playground/svgmap/plot.cpp create mode 100644 qwt/playground/svgmap/plot.h create mode 100644 qwt/playground/svgmap/svgmap.pro create mode 100644 qwt/playground/symbols/symbols.cpp create mode 100644 qwt/playground/symbols/symbols.pro create mode 100644 qwt/playground/timescale/main.cpp create mode 100644 qwt/playground/timescale/mainwindow.cpp create mode 100644 qwt/playground/timescale/mainwindow.h create mode 100644 qwt/playground/timescale/panel.cpp create mode 100644 qwt/playground/timescale/panel.h create mode 100644 qwt/playground/timescale/plot.cpp create mode 100644 qwt/playground/timescale/plot.h create mode 100644 qwt/playground/timescale/settings.h create mode 100644 qwt/playground/timescale/timescale.pro create mode 100644 qwt/qwt.prf create mode 100644 qwt/qwt.pro create mode 100644 qwt/qwtbuild.pri create mode 100644 qwt/qwtconfig.pri create mode 100644 qwt/qwtfunctions.pri create mode 100644 qwt/src/qwt.h create mode 100644 qwt/src/qwt_abstract_legend.cpp create mode 100644 qwt/src/qwt_abstract_legend.h create mode 100644 qwt/src/qwt_abstract_scale.cpp create mode 100644 qwt/src/qwt_abstract_scale.h create mode 100644 qwt/src/qwt_abstract_scale_draw.cpp create mode 100644 qwt/src/qwt_abstract_scale_draw.h create mode 100644 qwt/src/qwt_abstract_slider.cpp create mode 100644 qwt/src/qwt_abstract_slider.h create mode 100644 qwt/src/qwt_analog_clock.cpp create mode 100644 qwt/src/qwt_analog_clock.h create mode 100644 qwt/src/qwt_arrow_button.cpp create mode 100644 qwt/src/qwt_arrow_button.h create mode 100644 qwt/src/qwt_axes_mask.cpp create mode 100644 qwt/src/qwt_axes_mask.h create mode 100644 qwt/src/qwt_axis_id.cpp create mode 100644 qwt/src/qwt_axis_id.h create mode 100644 qwt/src/qwt_clipper.cpp create mode 100644 qwt/src/qwt_clipper.h create mode 100644 qwt/src/qwt_color_map.cpp create mode 100644 qwt/src/qwt_color_map.h create mode 100644 qwt/src/qwt_column_symbol.cpp create mode 100644 qwt/src/qwt_column_symbol.h create mode 100644 qwt/src/qwt_compass.cpp create mode 100644 qwt/src/qwt_compass.h create mode 100644 qwt/src/qwt_compass_rose.cpp create mode 100644 qwt/src/qwt_compass_rose.h create mode 100644 qwt/src/qwt_compat.h create mode 100644 qwt/src/qwt_counter.cpp create mode 100644 qwt/src/qwt_counter.h create mode 100644 qwt/src/qwt_curve_fitter.cpp create mode 100644 qwt/src/qwt_curve_fitter.h create mode 100644 qwt/src/qwt_date.cpp create mode 100644 qwt/src/qwt_date.h create mode 100644 qwt/src/qwt_date_scale_draw.cpp create mode 100644 qwt/src/qwt_date_scale_draw.h create mode 100644 qwt/src/qwt_date_scale_engine.cpp create mode 100644 qwt/src/qwt_date_scale_engine.h create mode 100644 qwt/src/qwt_dial.cpp create mode 100644 qwt/src/qwt_dial.h create mode 100644 qwt/src/qwt_dial_needle.cpp create mode 100644 qwt/src/qwt_dial_needle.h create mode 100644 qwt/src/qwt_dyngrid_layout.cpp create mode 100644 qwt/src/qwt_dyngrid_layout.h create mode 100644 qwt/src/qwt_event_pattern.cpp create mode 100644 qwt/src/qwt_event_pattern.h create mode 100644 qwt/src/qwt_global.h create mode 100644 qwt/src/qwt_graphic.cpp create mode 100644 qwt/src/qwt_graphic.h create mode 100644 qwt/src/qwt_interval.cpp create mode 100644 qwt/src/qwt_interval.h create mode 100644 qwt/src/qwt_interval_symbol.cpp create mode 100644 qwt/src/qwt_interval_symbol.h create mode 100644 qwt/src/qwt_knob.cpp create mode 100644 qwt/src/qwt_knob.h create mode 100644 qwt/src/qwt_legend.cpp create mode 100644 qwt/src/qwt_legend.h create mode 100644 qwt/src/qwt_legend_data.cpp create mode 100644 qwt/src/qwt_legend_data.h create mode 100644 qwt/src/qwt_legend_label.cpp create mode 100644 qwt/src/qwt_legend_label.h create mode 100644 qwt/src/qwt_magnifier.cpp create mode 100644 qwt/src/qwt_magnifier.h create mode 100644 qwt/src/qwt_math.cpp create mode 100644 qwt/src/qwt_math.h create mode 100644 qwt/src/qwt_matrix_raster_data.cpp create mode 100644 qwt/src/qwt_matrix_raster_data.h create mode 100644 qwt/src/qwt_null_paintdevice.cpp create mode 100644 qwt/src/qwt_null_paintdevice.h create mode 100644 qwt/src/qwt_painter.cpp create mode 100644 qwt/src/qwt_painter.h create mode 100644 qwt/src/qwt_painter_command.cpp create mode 100644 qwt/src/qwt_painter_command.h create mode 100644 qwt/src/qwt_panner.cpp create mode 100644 qwt/src/qwt_panner.h create mode 100644 qwt/src/qwt_picker.cpp create mode 100644 qwt/src/qwt_picker.h create mode 100644 qwt/src/qwt_picker_machine.cpp create mode 100644 qwt/src/qwt_picker_machine.h create mode 100644 qwt/src/qwt_pixel_matrix.cpp create mode 100644 qwt/src/qwt_pixel_matrix.h create mode 100644 qwt/src/qwt_plot.cpp create mode 100644 qwt/src/qwt_plot.h create mode 100644 qwt/src/qwt_plot_abstract_barchart.cpp create mode 100644 qwt/src/qwt_plot_abstract_barchart.h create mode 100644 qwt/src/qwt_plot_axis.cpp create mode 100644 qwt/src/qwt_plot_barchart.cpp create mode 100644 qwt/src/qwt_plot_barchart.h create mode 100644 qwt/src/qwt_plot_canvas.cpp create mode 100644 qwt/src/qwt_plot_canvas.h create mode 100644 qwt/src/qwt_plot_curve.cpp create mode 100644 qwt/src/qwt_plot_curve.h create mode 100644 qwt/src/qwt_plot_dict.cpp create mode 100644 qwt/src/qwt_plot_dict.h create mode 100644 qwt/src/qwt_plot_directpainter.cpp create mode 100644 qwt/src/qwt_plot_directpainter.h create mode 100644 qwt/src/qwt_plot_glcanvas.cpp create mode 100644 qwt/src/qwt_plot_glcanvas.h create mode 100644 qwt/src/qwt_plot_grid.cpp create mode 100644 qwt/src/qwt_plot_grid.h create mode 100644 qwt/src/qwt_plot_histogram.cpp create mode 100644 qwt/src/qwt_plot_histogram.h create mode 100644 qwt/src/qwt_plot_intervalcurve.cpp create mode 100644 qwt/src/qwt_plot_intervalcurve.h create mode 100644 qwt/src/qwt_plot_item.cpp create mode 100644 qwt/src/qwt_plot_item.h create mode 100644 qwt/src/qwt_plot_layout.cpp create mode 100644 qwt/src/qwt_plot_layout.h create mode 100644 qwt/src/qwt_plot_legenditem.cpp create mode 100644 qwt/src/qwt_plot_legenditem.h create mode 100644 qwt/src/qwt_plot_magnifier.cpp create mode 100644 qwt/src/qwt_plot_magnifier.h create mode 100644 qwt/src/qwt_plot_marker.cpp create mode 100644 qwt/src/qwt_plot_marker.h create mode 100644 qwt/src/qwt_plot_multi_barchart.cpp create mode 100644 qwt/src/qwt_plot_multi_barchart.h create mode 100644 qwt/src/qwt_plot_panner.cpp create mode 100644 qwt/src/qwt_plot_panner.h create mode 100644 qwt/src/qwt_plot_picker.cpp create mode 100644 qwt/src/qwt_plot_picker.h create mode 100644 qwt/src/qwt_plot_rasteritem.cpp create mode 100644 qwt/src/qwt_plot_rasteritem.h create mode 100644 qwt/src/qwt_plot_renderer.cpp create mode 100644 qwt/src/qwt_plot_renderer.h create mode 100644 qwt/src/qwt_plot_rescaler.cpp create mode 100644 qwt/src/qwt_plot_rescaler.h create mode 100644 qwt/src/qwt_plot_scaleitem.cpp create mode 100644 qwt/src/qwt_plot_scaleitem.h create mode 100644 qwt/src/qwt_plot_seriesitem.cpp create mode 100644 qwt/src/qwt_plot_seriesitem.h create mode 100644 qwt/src/qwt_plot_shapeitem.cpp create mode 100644 qwt/src/qwt_plot_shapeitem.h create mode 100644 qwt/src/qwt_plot_spectrocurve.cpp create mode 100644 qwt/src/qwt_plot_spectrocurve.h create mode 100644 qwt/src/qwt_plot_spectrogram.cpp create mode 100644 qwt/src/qwt_plot_spectrogram.h create mode 100644 qwt/src/qwt_plot_svgitem.cpp create mode 100644 qwt/src/qwt_plot_svgitem.h create mode 100644 qwt/src/qwt_plot_textlabel.cpp create mode 100644 qwt/src/qwt_plot_textlabel.h create mode 100644 qwt/src/qwt_plot_tradingcurve.cpp create mode 100644 qwt/src/qwt_plot_tradingcurve.h create mode 100644 qwt/src/qwt_plot_xml.cpp create mode 100644 qwt/src/qwt_plot_zoneitem.cpp create mode 100644 qwt/src/qwt_plot_zoneitem.h create mode 100644 qwt/src/qwt_plot_zoomer.cpp create mode 100644 qwt/src/qwt_plot_zoomer.h create mode 100644 qwt/src/qwt_point_3d.cpp create mode 100644 qwt/src/qwt_point_3d.h create mode 100644 qwt/src/qwt_point_data.cpp create mode 100644 qwt/src/qwt_point_data.h create mode 100644 qwt/src/qwt_point_mapper.cpp create mode 100644 qwt/src/qwt_point_mapper.h create mode 100644 qwt/src/qwt_point_polar.cpp create mode 100644 qwt/src/qwt_point_polar.h create mode 100644 qwt/src/qwt_raster_data.cpp create mode 100644 qwt/src/qwt_raster_data.h create mode 100644 qwt/src/qwt_round_scale_draw.cpp create mode 100644 qwt/src/qwt_round_scale_draw.h create mode 100644 qwt/src/qwt_samples.h create mode 100644 qwt/src/qwt_sampling_thread.cpp create mode 100644 qwt/src/qwt_sampling_thread.h create mode 100644 qwt/src/qwt_scale_div.cpp create mode 100644 qwt/src/qwt_scale_div.h create mode 100644 qwt/src/qwt_scale_draw.cpp create mode 100644 qwt/src/qwt_scale_draw.h create mode 100644 qwt/src/qwt_scale_engine.cpp create mode 100644 qwt/src/qwt_scale_engine.h create mode 100644 qwt/src/qwt_scale_map.cpp create mode 100644 qwt/src/qwt_scale_map.h create mode 100644 qwt/src/qwt_scale_map_table.h create mode 100644 qwt/src/qwt_scale_widget.cpp create mode 100644 qwt/src/qwt_scale_widget.h create mode 100644 qwt/src/qwt_series_data.cpp create mode 100644 qwt/src/qwt_series_data.h create mode 100644 qwt/src/qwt_series_store.h create mode 100644 qwt/src/qwt_slider.cpp create mode 100644 qwt/src/qwt_slider.h create mode 100644 qwt/src/qwt_spline.cpp create mode 100644 qwt/src/qwt_spline.h create mode 100644 qwt/src/qwt_symbol.cpp create mode 100644 qwt/src/qwt_symbol.h create mode 100644 qwt/src/qwt_system_clock.cpp create mode 100644 qwt/src/qwt_system_clock.h create mode 100644 qwt/src/qwt_text.cpp create mode 100644 qwt/src/qwt_text.h create mode 100644 qwt/src/qwt_text_engine.cpp create mode 100644 qwt/src/qwt_text_engine.h create mode 100644 qwt/src/qwt_text_label.cpp create mode 100644 qwt/src/qwt_text_label.h create mode 100644 qwt/src/qwt_thermo.cpp create mode 100644 qwt/src/qwt_thermo.h create mode 100644 qwt/src/qwt_transform.cpp create mode 100644 qwt/src/qwt_transform.h create mode 100644 qwt/src/qwt_wheel.cpp create mode 100644 qwt/src/qwt_wheel.h create mode 100644 qwt/src/qwt_widget_overlay.cpp create mode 100644 qwt/src/qwt_widget_overlay.h create mode 100644 qwt/src/src.pri create mode 100644 qwt/src/src.pro create mode 100644 qwt/textengines/mathml/mathml.pro create mode 100644 qwt/textengines/mathml/qtmmlwidget-license create mode 100644 qwt/textengines/mathml/qwt_mathml_text_engine.cpp create mode 100644 qwt/textengines/mathml/qwt_mathml_text_engine.h create mode 100644 qwt/textengines/mathml/qwt_mml_document.cpp create mode 100644 qwt/textengines/mathml/qwt_mml_document.h create mode 100644 qwt/textengines/mathml/qwtmathml.prf create mode 100644 qwt/textengines/textengines.pri create mode 100644 qwt/textengines/textengines.pro diff --git a/qwt/COPYING b/qwt/COPYING new file mode 100644 index 000000000..9c01f7e21 --- /dev/null +++ b/qwt/COPYING @@ -0,0 +1,543 @@ + Qwt License + Version 1.0, January 1, 2003 + +The Qwt library and included programs are provided under the terms +of the GNU LESSER GENERAL PUBLIC LICENSE (LGPL) with the following +exceptions: + + 1. Widgets that are subclassed from Qwt widgets do not + constitute a derivative work. + + 2. Static linking of applications and widgets to the + Qwt library does not constitute a derivative work + and does not require the author to provide source + code for the application or widget, use the shared + Qwt libraries, or link their applications or + widgets against a user-supplied version of Qwt. + + If you link the application or widget to a modified + version of Qwt, then the changes to Qwt must be + provided under the terms of the LGPL in sections + 1, 2, and 4. + + 3. You do not have to provide a copy of the Qwt license + with programs that are linked to the Qwt library, nor + do you have to identify the Qwt license in your + program or documentation as required by section 6 + of the LGPL. + + + However, programs must still identify their use of Qwt. + The following example statement can be included in user + documentation to satisfy this requirement: + + [program/widget] is based in part on the work of + the Qwt project (http://qwt.sf.net). + +---------------------------------------------------------------------- + + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + + diff --git a/qwt/INSTALL b/qwt/INSTALL new file mode 100644 index 000000000..bafcbe039 --- /dev/null +++ b/qwt/INSTALL @@ -0,0 +1 @@ +see doc/html/qwtinstall.html diff --git a/qwt/README b/qwt/README new file mode 100644 index 000000000..46ee34e41 --- /dev/null +++ b/qwt/README @@ -0,0 +1,34 @@ + +The Qwt Widget Library +---------------------- + + Qwt is an extension to the libraries of the Qt Project. + + The Qwt library contains widgets and components which are + primarily useful for technical and scientifical purposes. + It includes a 2-D plotting widget, different kinds of sliders, + and much more. + + Qwt is hosted at http://qwt.sf.net + +Installation +------------ + + Read INSTALL how to build and install Qwt. + +Copyright +--------- + + Qwt Widget Library + Copyright (C) 1997 Josef Wilgen + Copyright (C) 2002 Uwe Rathmann + + Qwt is published under the Qwt License, Version 1.0. + You should have received a copy of this licence in the file + COPYING. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + + diff --git a/qwt/TODO b/qwt/TODO new file mode 100644 index 000000000..8abd45892 --- /dev/null +++ b/qwt/TODO @@ -0,0 +1,38 @@ +Qwt TODO list + +Ideas +------ +- Improve Documention +- QAbstractModel -> QwtSeriesData +- Box/Whisker plot item +- QwtSeriesData + functors +- QwtSeriesData/QwtPlotCurve + Level of details (Douglas Peucker) +- Common zoom stack for all navigation objects +- Watermark Item +- Contour algorithm for vectors: http://apptree.net/conrec.htm +- QwtPlotCanvas rendered via FBO, PBO +- Time/Date scale engine +- TeX texts +- Grid of QwtPlots +- Interval scale labels ( between 2 ticks ) +- More than 4 axes +- QwtIntervalSymbol + QPainterPath/... +- QwtPlotScene + breaking composite architecture +- Using QStaticText for markers ( and scales ? ) +- Scales/Grid item like in QwtPolarGrid +- Container for a 2D matrix +- Waterfall plots +- transform/invTransform for polygons and lines +- cursor item +- line marker with a line from the position to the axis +- quadtree +- QwtText supporting Qt::TextElideMode +- Multitouch events +- QwtKnob/QwtDial fixed contents size mode +- controls ( f.e QwtWheel ) with a very dark palette + +Bugs/Change requests +-------------------- +- Remove QwtScaleTransformation::copy() +- Reference value for QwtThermo +- Transparent canvas background + backingstore diff --git a/qwt/admin/svn2package.sh b/qwt/admin/svn2package.sh new file mode 100755 index 000000000..2e1bfd5d7 --- /dev/null +++ b/qwt/admin/svn2package.sh @@ -0,0 +1,326 @@ +#! /bin/sh +# +# Generates a Qwt package from sourceforge svn +# +# Usage: svn2package.sh [-b|--branch ] [packagename] +# + +########################## +# usage +########################## + +function usage() { + echo "Usage: $0 [-b|--branch ] [-s|--suffix ] [-html] [-pdf] [-qch] [packagename]" + exit 1 +} + +################################ +# checkout +################################ + +function checkoutQwt() { + + if [ -x $2 ] + then + rm -r $2 + if [ $? -ne 0 ] + then + exit $? + fi + fi + + svn -q co https://svn.code.sf.net/p/qwt/code/$1/$2 + if [ $? -ne 0 ] + then + echo "Can't access sourceforge SVN" + exit $? + fi + + if [ "$3" != "$2" ] + then + rm -rf $3 + mv $2 $3 + fi +} + +########################## +# cleanQwt dirname +########################## + +function cleanQwt { + + cd $1 + if [ $? -ne 0 ] + then + exit $? + fi + + find . -name .svn -print | xargs rm -r + + rm -f TODO + rm -rf admin + rm -rf doc/tex + + PROFILES="qwtbuild.pri" + for PROFILE in $PROFILES + do + sed -i -e 's/= debug/= release/' $PROFILE + sed -i -e 's/= release_and_release/= debug_and_release/' $PROFILE + done + + HEADERS=`find . -type f -name '*.h' -print` + SOURCES=`find . -type f -name '*.cpp' -print` + PROFILES=`find . -type f -name '*.pro' -print` + PRIFILES=`find . -type f -name '*.pri' -print` + + for EXPANDFILE in $HEADERS $SOURCES $PROFILES $PRIFILES + do + expand -4 $EXPANDFILE > $EXPANDFILE.expand + mv $EXPANDFILE.expand $EXPANDFILE + done + + for SRCFILE in $SOURCES $PROFILES $PRIFILES + do + sed -i -e '/#warning/d' $SRCFILE + done + + if [ "$SUFFIX" != "" ] + then + sed -i -e "s/\$\$QWT_VERSION-svn/\$\$QWT_VERSION-$SUFFIX/" qwtconfig.pri + sed -i -e "s/\$(QWTVERSION)/$VERSION-$SUFFIX/" doc/install.dox + else + sed -i -e "s/\$\$QWT_VERSION-svn/\$\$QWT_VERSION/" qwtconfig.pri + sed -i -e "s/\$(QWTVERSION)/$VERSION/" doc/install.dox + fi + + cd - > /dev/null +} + +########################## +# createDocs dirname +########################## + +function createDocs { + + ODIR=`pwd` + + cd $1 + if [ $? -ne 0 ] + then + exit $? + fi + + if [ "$SUFFIX" != "" ] + then + export QWTVERSION=$VERSION-$SUFFIX + else + export QWTVERSION=$VERSION + fi + cp Doxyfile Doxyfile.doc + + if [ $GENERATE_MAN -ne 0 ] + then + sed -i -e '/GENERATE_MAN/d' Doxyfile.doc + echo 'GENERATE_MAN = YES' >> Doxyfile.doc + fi + + if [ $GENERATE_PDF -ne 0 ] + then + # We need LateX for the qwtdoc.pdf + + sed -i -e '/GENERATE_LATEX/d' -e '/GENERATE_MAN/d' Doxyfile.doc + echo 'GENERATE_LATEX = YES' >> Doxyfile.doc + echo 'GENERATE_MAN = YES' >> Doxyfile.doc + +# sed -i -e '/INLINE_INHERITED_MEMB/d' Doxyfile.doc +# echo 'INLINE_INHERITED_MEMB = NO' >> Doxyfile.doc + fi + + if [ $GENERATE_QCH -ne 0 ] + then + sed -i -e '/GENERATE_QHP/d' Doxyfile.doc + echo "GENERATE_QHP = YES" >> Doxyfile.doc + fi + + cp ../INSTALL ../COPYING ./ + + doxygen Doxyfile.doc > /dev/null 2>&1 + if [ $? -ne 0 ] + then + exit $? + fi + + rm Doxyfile.doc Doxygen.log INSTALL COPYING + rm -r images + + if [ $GENERATE_PDF -ne 0 ] + then + cd latex + make > /dev/null 2>&1 + if [ $? -ne 0 ] + then + exit $? + fi + + cd .. + mkdir pdf + mv latex/refman.pdf pdf/qwtdoc-$VERSION.pdf + + rm -r latex + fi + + cd $ODIR +} + +########################## +# posix2dos filename +########################## + +function posix2dos { + # At least one unix2dos writes to stdout instead of overwriting the input. + # The -q option is always enabled in stdin->stdout mode. + unix2dos <$1 >$1.dos + mv $1.dos $1 +} + +########################## +# prepare4Win dirname +########################## + +function prepare4Win { + + cd $1 + if [ $? -ne 0 ] + then + exit $? + fi + + rm -rf doc/man 2> /dev/null + + # win files, but not uptodate + + BATCHES=`find . -type f -name '*.bat' -print` + HEADERS=`find . -type f -name '*.h' -print` + SOURCES=`find . -type f -name '*.cpp' -print` + PROFILES=`find . -type f -name '*.pro' -print` + PRIFILES=`find . -type f -name '*.pri' -print` + PRFFILES=`find . -type f -name '*.prf' -print` + + for FILE in $BATCHES $HEADERS $SOURCES $PROFILES $PRIFILES $PRFFILES + do + posix2dos $FILE + done + + cd - > /dev/null +} + +########################## +# prepare4Unix dirname +########################## + +function prepare4Unix { + + cd $1 + if [ $? -ne 0 ] + then + exit $? + fi + + cd - > /dev/null +} + +########################## +# main +########################## + +QWTDIR= +SVNDIR=trunk +BRANCH=qwt +SUFFIX= +VERSION= +GENERATE_DOC=0 +GENERATE_PDF=0 +GENERATE_QCH=0 +GENERATE_MAN=0 + +while [ $# -gt 0 ] ; do + case "$1" in + -h|--help) + usage; exit 1 ;; + -b|--branch) + shift; SVNDIR=branches; BRANCH=$1; shift;; + -s|--suffix) + shift; SUFFIX=$1; shift;; + -html) + GENERATE_DOC=1; shift;; + -pdf) + GENERATE_DOC=1; GENERATE_PDF=1; shift;; + -qch) + GENERATE_DOC=1; GENERATE_QCH=1; shift;; + *) + QWTDIR=qwt-$1 ; VERSION=$1; shift;; + esac +done + +if [ "$QWTDIR" == "" ] +then + usage + exit 2 +fi + +QWTNAME=$QWTDIR +if [ "$SUFFIX" != "" ] +then + QWTDIR=$QWTDIR-$SUFFIX +fi + +TMPDIR=/tmp/$QWTDIR-tmp + +echo -n "checkout to $TMPDIR ... " +checkoutQwt $SVNDIR $BRANCH $TMPDIR +cleanQwt $TMPDIR +echo done + +if [ $GENERATE_DOC -ne 0 ] +then + echo -n "generate documentation ... " + + export VERSION # used in the doxygen files + createDocs $TMPDIR/doc + + if [ $GENERATE_PDF -ne 0 ] + then + mv $TMPDIR/doc/pdf/qwtdoc-$VERSION.pdf $QWTDIR.pdf + rmdir $TMPDIR/doc/pdf + fi + + if [ $GENERATE_QCH -ne 0 ] + then + mv $TMPDIR/doc/html/qwtdoc.qch $QWTDIR.qch + fi +fi + +echo done + + +DIR=`pwd` +echo -n "create packages in $DIR ... " + +cd /tmp + +rm -rf $QWTDIR +cp -a $TMPDIR $QWTDIR +prepare4Unix $QWTDIR +tar cfj $QWTDIR.tar.bz2 $QWTDIR + +rm -rf $QWTDIR +cp -a $TMPDIR $QWTDIR +prepare4Win $QWTDIR +zip -r $QWTDIR.zip $QWTDIR > /dev/null + +rm -rf $TMPDIR $QWTDIR + +mv $QWTDIR.tar.bz2 $QWTDIR.zip $DIR/ +echo done + +exit 0 diff --git a/qwt/designer/designer.pro b/qwt/designer/designer.pro new file mode 100644 index 000000000..c269e9da2 --- /dev/null +++ b/qwt/designer/designer.pro @@ -0,0 +1,132 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +QWT_ROOT = $${PWD}/.. + +include ( $${QWT_ROOT}/qwtconfig.pri ) +include ( $${QWT_ROOT}/qwtbuild.pri ) +include ( $${QWT_ROOT}/qwtfunctions.pri ) + + +CONFIG( debug_and_release ) { + + # When building debug_and_release the designer plugin is built + # for release only. If you want to have a debug version it has to be + # done with "CONFIG += debug" only. + + message("debug_and_release: building the Qwt designer plugin in release mode only") + + CONFIG -= debug_and_release + CONFIG += release +} + +contains(QWT_CONFIG, QwtDesigner) { + + CONFIG += qt plugin + CONFIG += warn_on + + greaterThan(QT_MAJOR_VERSION, 4) { + + QT += designer + } + else { + + CONFIG += designer + } + + + TEMPLATE = lib + TARGET = qwt_designer_plugin + + DESTDIR = plugins/designer + + INCLUDEPATH += $${QWT_ROOT}/src + DEPENDPATH += $${QWT_ROOT}/src + + contains(QWT_CONFIG, QwtDll) { + + contains(QWT_CONFIG, QwtDesignerSelfContained) { + + QWT_CONFIG += include_src + } + + } else { + + # for linking against a static library the + # plugin will be self contained anyway + } + + contains(QWT_CONFIG, include_src) { + + # compile all qwt classes into the plugin + + include ( $${QWT_ROOT}/src/src.pri ) + + for( header, HEADERS) { + QWT_HEADERS += $${QWT_ROOT}/src/$${header} + } + + for( source, SOURCES ) { + QWT_SOURCES += $${QWT_ROOT}/src/$${source} + } + + HEADERS = $${QWT_HEADERS} + SOURCES = $${QWT_SOURCES} + + } else { + + # compile the path for finding the Qwt library + # into the plugin. Not supported on Windows ! + + QMAKE_RPATHDIR *= $${QWT_INSTALL_LIBS} + + contains(QWT_CONFIG, QwtFramework) { + + LIBS += -F$${QWT_ROOT}/lib + } + else { + + LIBS += -L$${QWT_ROOT}/lib + } + + qwtAddLibrary(qwt) + + contains(QWT_CONFIG, QwtDll) { + + win32 { + DEFINES += QT_DLL QWT_DLL + } + } + } + + !contains(QWT_CONFIG, QwtPlot) { + DEFINES += NO_QWT_PLOT + } + + !contains(QWT_CONFIG, QwtWidgets) { + DEFINES += NO_QWT_WIDGETS + } + + HEADERS += qwt_designer_plugin.h + SOURCES += qwt_designer_plugin.cpp + + contains(QWT_CONFIG, QwtPlot) { + + HEADERS += qwt_designer_plotdialog.h + SOURCES += qwt_designer_plotdialog.cpp + } + + RESOURCES += qwt_designer_plugin.qrc + + target.path = $${QWT_INSTALL_PLUGINS} + INSTALLS += target +} +else { + TEMPLATE = subdirs # do nothing +} diff --git a/qwt/designer/pixmaps/qwtanalogclock.png b/qwt/designer/pixmaps/qwtanalogclock.png new file mode 100644 index 0000000000000000000000000000000000000000..89f3451acce32fa5fd1b954abd0e057f1bdb0ef6 GIT binary patch literal 422 zcmV;X0a^ZuP)uR-nOI9FaSH($iKA+d?weX1u5tWuTOYB&c z5fSb8``vE$DZj{xu9a(=MjGsx*?Yg)Y;5MY$m|>rhwnlzm&# z6WnYztLyqt7PJbPP6q(v@i@Et)t8KhgarV^7hq5dk z{Opeyqb?O&k^SUywtUci(N+0`j1P8esR|+mNc$qkWTR4CnRkj`>L`sqP z7?Q*3f`TGjy({tz@44sRb2;eEUvJ=UX*%j5R~6H|p*e7fUH+F1y`suh(ld8dRg4 z^WCv)n)c}EQKeD=faBw1#+a0He{Y`lb_xr_n^ZDFyoL%QI#=VEV>6d8~MULb6 zzF**}Ol!3oj-sgDZWr^@b$z!!0CW$!VHmou3jhcqN~ux`0AdlhS}m-Vw8DQO1f}#| zvT2&_cAHWm92mpWp>$my?f2=gzbt2!(-A7>0wv zpx5i|wf1grZnG?-0HAWU`ns|#%QQ`d5IZ{Ze4l0+LP*zjLP!tK&4VqN~LM)`@UfqrBZ3LGNx%B9v-%ut+RJ$#o*S{R=JwZ zW+E0Ct&u_krBn#9y>bqB8nwpRyE7p~fqZLNN;w{lE0s#4)xdy7DwI;2eZv@Yn{&=@ zZ*I@upKq=#*u6VF&zsNZ)oQg~uXCFN0ZJ$#B#NTxbo%kb$9tO!0RI8&*17#PYCxj^ O0000<{OHEqRlnwx1R=`=IPIVajCikgZaOgJZ;{N_8~ z|DSVID3G~jJxTH$zC>ob6UnYys&{iO{b<;U5H-!zVwwxD*UO+5 zi=`weRYDMPt}}w|r-K-HHi~REi}^keD3fJOGp(<$XHH8MbsJAlf=^{}|M@un?McCA z)MJ6qJ5xNWQnz2|_QnGaFOqrq(T+ZNYi+Prn)BHgFP_55J3}7@owB6L3H3h zI9?4w76l+T3Fylak3|)*Tnzq{D0J<UK`eHY1|KL-&@i|FVb z$II7Q@XGT#o5NFX0-7|BV_TPh=$JapNo0C%`=tZpN7~r<=QH=P%xyu1L90ZeM~F*d zBtctEME_V4si7YDbv#}~9DpnrcSUz7ir>Fqz*>rR$#;Gnz7=BQIXW$TH5OP6I*`dI zG>Hf-+8kW9)u?tVj#cVn$E8Z89Exsx1$Jzh2b(s7N|k^gHU!{nQ06upU66N7&(oaJ zGLj@gqtTQEFVpMLR9A%tBY|Kw!sB+MuC@jvulPKep8WAtnw_blDCXOxMAsgt8ArCS z!)U|@TSYnSb~|ndd)RG~mvYemw*MfRtz=myf*=r%^}9TjfxYhI>r03KQ86$g#H5dZ)H07*qoM6N<$g0XSXIRF3v literal 0 HcmV?d00001 diff --git a/qwt/designer/pixmaps/qwtdial.png b/qwt/designer/pixmaps/qwtdial.png new file mode 100644 index 0000000000000000000000000000000000000000..46f079c71c8eb6cf72e2c7e1c794c2818ab23ff5 GIT binary patch literal 433 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 Sn7#D?0000EzOL+-xHv^DWcFyU-VPMv zEbxddW?Nn{1`)dPG&Tz_xo`2G8LX=&;8>(?JX zeE8|pr&q6D9Xobx@7}$eH*a3JaA8|pTWxLa>V-A2KrM_VL4Lsu4$p3+0Xfc|E{-7; zx6byP^D!t096oXJ^#A+b!aj-2ocwZgG`;RRNGy=FZ8rEk?NMQuI!h%IE9S(Hh%Cs4iw@n z@Q5sCVBi)4Va7{$>;3=*S<)SS9T^xl_H+M9WMyDrW(e>JasAG~@STI>yMVxdAd-;y zuc-K6Q}e%};eSiZ|4vT-gM$7?Mg31s{-2%wzqt5+b@l(2mj69H|EEm(KWEPWrAz;> zUHgCQ*8h9={y%o?|G9JjuU-3p@819C&;P%F|Nr~<|D#|u1O|5qC}w>X0Ok#ak|4ie z0xo5UiIIK_RNL$6;uunKYwv`+T!$5ST6-`23f{bJooMux&;Rmw?pU4sr6`)?$p=RZ z%fKBvUxV)~y120R(X$=fX5LqDUOV~Bs`vy>&QnkHI4?{|f!$Ruu<+$U$Z#eH{yewQXrE21&lhgm# cec=AVSea@v>vD5Q4bWi>p00i_>zopr05FqfegFUf literal 0 HcmV?d00001 diff --git a/qwt/designer/qwt_designer_plotdialog.cpp b/qwt/designer/qwt_designer_plotdialog.cpp new file mode 100644 index 000000000..1dba04e3e --- /dev/null +++ b/qwt/designer/qwt_designer_plotdialog.cpp @@ -0,0 +1,42 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include +#include +#include +#include +#include "qwt_designer_plotdialog.h" + +using namespace QwtDesignerPlugin; + +PlotDialog::PlotDialog( const QString &properties, QWidget *parent ): + QDialog( parent ) +{ + setWindowTitle( "Plot Properties" ); + + QLineEdit *lineEdit = new QLineEdit( properties ); + connect( lineEdit, SIGNAL( textChanged( const QString & ) ), + SIGNAL( edited( const QString & ) ) ); + + QTabWidget *tabWidget = new QTabWidget( this ); + tabWidget->addTab( lineEdit, "General" ); + + QPushButton *closeButton = new QPushButton( "Close" ); + connect( closeButton, SIGNAL( clicked() ), this, SLOT( accept() ) ); + + QHBoxLayout *buttonLayout = new QHBoxLayout; + buttonLayout->addStretch( 1 ); + buttonLayout->addWidget( closeButton ); + + QVBoxLayout *mainLayout = new QVBoxLayout; + mainLayout->addWidget( tabWidget ); + mainLayout->addLayout( buttonLayout ); + setLayout( mainLayout ); +} + diff --git a/qwt/designer/qwt_designer_plotdialog.h b/qwt/designer/qwt_designer_plotdialog.h new file mode 100644 index 000000000..82f7f02ce --- /dev/null +++ b/qwt/designer/qwt_designer_plotdialog.h @@ -0,0 +1,31 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_DESIGNER_PLOTDIALOG_H +#define QWT_DESIGNER_PLOTDIALOG_H + +#include + +namespace QwtDesignerPlugin +{ + + class PlotDialog: public QDialog + { + Q_OBJECT + + public: + PlotDialog( const QString &properties, QWidget *parent = NULL ); + + Q_SIGNALS: + void edited( const QString& ); + }; + +} + +#endif diff --git a/qwt/designer/qwt_designer_plugin.cpp b/qwt/designer/qwt_designer_plugin.cpp new file mode 100644 index 000000000..f46a11fa4 --- /dev/null +++ b/qwt/designer/qwt_designer_plugin.cpp @@ -0,0 +1,570 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#if defined(_MSC_VER) /* MSVC Compiler */ +#pragma warning ( disable : 4786 ) +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "qwt_designer_plugin.h" + +#ifndef NO_QWT_PLOT +#include "qwt_designer_plotdialog.h" +#include "qwt_plot.h" +#include "qwt_plot_canvas.h" +#include "qwt_scale_widget.h" +#endif + +#ifndef NO_QWT_WIDGETS +#include "qwt_counter.h" +#include "qwt_wheel.h" +#include "qwt_thermo.h" +#include "qwt_knob.h" +#include "qwt_slider.h" +#include "qwt_dial.h" +#include "qwt_dial_needle.h" +#include "qwt_analog_clock.h" +#include "qwt_compass.h" +#endif + +#include "qwt_text_label.h" + +using namespace QwtDesignerPlugin; + +CustomWidgetInterface::CustomWidgetInterface( QObject *parent ): + QObject( parent ), + d_isInitialized( false ) +{ +} + +bool CustomWidgetInterface::isContainer() const +{ + return false; +} + +bool CustomWidgetInterface::isInitialized() const +{ + return d_isInitialized; +} + +QIcon CustomWidgetInterface::icon() const +{ + return d_icon; +} + +QString CustomWidgetInterface::codeTemplate() const +{ + return d_codeTemplate; +} + +QString CustomWidgetInterface::domXml() const +{ + return d_domXml; +} + +QString CustomWidgetInterface::group() const +{ + return "Qwt Widgets"; +} + +QString CustomWidgetInterface::includeFile() const +{ + return d_include; +} + +QString CustomWidgetInterface::name() const +{ + return d_name; +} + +QString CustomWidgetInterface::toolTip() const +{ + return d_toolTip; +} + +QString CustomWidgetInterface::whatsThis() const +{ + return d_whatsThis; +} + +void CustomWidgetInterface::initialize( + QDesignerFormEditorInterface *formEditor ) +{ + if ( d_isInitialized ) + return; + + QExtensionManager *manager = formEditor->extensionManager(); + if ( manager ) + { + manager->registerExtensions( new TaskMenuFactory( manager ), + Q_TYPEID( QDesignerTaskMenuExtension ) ); + } + + d_isInitialized = true; +} + +#ifndef NO_QWT_PLOT + +PlotInterface::PlotInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtPlot"; + d_include = "qwt_plot.h"; + d_icon = QPixmap( ":/pixmaps/qwtplot.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 400\n" + " 200\n" + " \n" + " \n" + "\n"; +} + +QWidget *PlotInterface::createWidget( QWidget *parent ) +{ + return new QwtPlot( parent ); +} + + +PlotCanvasInterface::PlotCanvasInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtPlotCanvas"; + d_include = "qwt_plot_canvas.h"; + d_icon = QPixmap( ":/pixmaps/qwtplot.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 400\n" + " 200\n" + " \n" + " \n" + "\n"; +} + +QWidget *PlotCanvasInterface::createWidget( QWidget *parent ) +{ + return new QwtPlotCanvas( qobject_cast( parent ) ); +} + +#endif + +#ifndef NO_QWT_WIDGETS + +AnalogClockInterface::AnalogClockInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtAnalogClock"; + d_include = "qwt_analog_clock.h"; + d_icon = QPixmap( ":/pixmaps/qwtanalogclock.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 200\n" + " 200\n" + " \n" + " \n" + " \n" + " 4\n" + " \n" + "\n"; +} + +QWidget *AnalogClockInterface::createWidget( QWidget *parent ) +{ + return new QwtAnalogClock( parent ); +} + +#endif + +#ifndef NO_QWT_WIDGETS + +CompassInterface::CompassInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtCompass"; + d_include = "qwt_compass.h"; + d_icon = QPixmap( ":/pixmaps/qwtcompass.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 200\n" + " 200\n" + " \n" + " \n" + " \n" + " 4\n" + " \n" + "\n"; +} + +QWidget *CompassInterface::createWidget( QWidget *parent ) +{ + QwtCompass *compass = new QwtCompass( parent ); + compass->setNeedle( new QwtCompassMagnetNeedle( + QwtCompassMagnetNeedle::TriangleStyle, + compass->palette().color( QPalette::Mid ), + compass->palette().color( QPalette::Dark ) ) ); + + return compass; +} + +#endif + +#ifndef NO_QWT_WIDGETS + +CounterInterface::CounterInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtCounter"; + d_include = "qwt_counter.h"; + d_icon = QPixmap( ":/pixmaps/qwtcounter.png" ); + d_domXml = + "\n" + "\n"; +} + +QWidget *CounterInterface::createWidget( QWidget *parent ) +{ + return new QwtCounter( parent ); +} + +#endif + +#ifndef NO_QWT_WIDGETS + +DialInterface::DialInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtDial"; + d_include = "qwt_dial.h"; + d_icon = QPixmap( ":/pixmaps/qwtdial.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 200\n" + " 200\n" + " \n" + " \n" + " \n" + " 4\n" + " \n" + "\n"; +} + +QWidget *DialInterface::createWidget( QWidget *parent ) +{ + QwtDial *dial = new QwtDial( parent ); + dial->setNeedle( new QwtDialSimpleNeedle( + QwtDialSimpleNeedle::Arrow, true, + dial->palette().color( QPalette::Dark ), + dial->palette().color( QPalette::Mid ) ) ); + + return dial; +} + +#endif + +#ifndef NO_QWT_WIDGETS + +KnobInterface::KnobInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtKnob"; + d_include = "qwt_knob.h"; + d_icon = QPixmap( ":/pixmaps/qwtknob.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 150\n" + " 150\n" + " \n" + " \n" + "\n"; +} + +QWidget *KnobInterface::createWidget( QWidget *parent ) +{ + return new QwtKnob( parent ); +} + +#endif + +#ifndef NO_QWT_PLOT + +ScaleWidgetInterface::ScaleWidgetInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtScaleWidget"; + d_include = "qwt_scale_widget.h"; + d_icon = QPixmap( ":/pixmaps/qwtscale.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 60\n" + " 250\n" + " \n" + " \n" + "\n"; +} + +QWidget *ScaleWidgetInterface::createWidget( QWidget *parent ) +{ + return new QwtScaleWidget( QwtScaleDraw::LeftScale, parent ); +} + +#endif + +#ifndef NO_QWT_WIDGETS + +SliderInterface::SliderInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtSlider"; + d_include = "qwt_slider.h"; + d_icon = QPixmap( ":/pixmaps/qwtslider.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 60\n" + " 250\n" + " \n" + " \n" + "\n"; +} + +QWidget *SliderInterface::createWidget( QWidget *parent ) +{ + return new QwtSlider( parent ); +} + +#endif + +TextLabelInterface::TextLabelInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtTextLabel"; + d_include = "qwt_text_label.h"; + + d_icon = QPixmap( ":/pixmaps/qwtwidget.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 100\n" + " 20\n" + " \n" + " \n" + "\n"; +} + +QWidget *TextLabelInterface::createWidget( QWidget *parent ) +{ + return new QwtTextLabel( QwtText( "Label" ), parent ); +} + +#ifndef NO_QWT_WIDGETS + +ThermoInterface::ThermoInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtThermo"; + d_include = "qwt_thermo.h"; + d_icon = QPixmap( ":/pixmaps/qwtthermo.png" ); + d_domXml = + "\n" + " \n" + " \n" + " 0\n" + " 0\n" + " 60\n" + " 250\n" + " \n" + " \n" + "\n"; +} + +QWidget *ThermoInterface::createWidget( QWidget *parent ) +{ + return new QwtThermo( parent ); +} + +#endif + +#ifndef NO_QWT_WIDGETS + +WheelInterface::WheelInterface( QObject *parent ): + CustomWidgetInterface( parent ) +{ + d_name = "QwtWheel"; + d_include = "qwt_wheel.h"; + d_icon = QPixmap( ":/pixmaps/qwtwheel.png" ); + d_domXml = + "\n" + "\n"; +} + +QWidget *WheelInterface::createWidget( QWidget *parent ) +{ + return new QwtWheel( parent ); +} + +#endif + +CustomWidgetCollectionInterface::CustomWidgetCollectionInterface( + QObject *parent ): + QObject( parent ) +{ +#ifndef NO_QWT_PLOT + d_plugins.append( new PlotInterface( this ) ); + +#if 0 + // better not: the designer crashes TODO .. + d_plugins.append( new PlotCanvasInterface( this ) ); +#endif + + d_plugins.append( new ScaleWidgetInterface( this ) ); +#endif + +#ifndef NO_QWT_WIDGETS + d_plugins.append( new AnalogClockInterface( this ) ); + d_plugins.append( new CompassInterface( this ) ); + d_plugins.append( new CounterInterface( this ) ); + d_plugins.append( new DialInterface( this ) ); + d_plugins.append( new KnobInterface( this ) ); + d_plugins.append( new SliderInterface( this ) ); + d_plugins.append( new ThermoInterface( this ) ); + d_plugins.append( new WheelInterface( this ) ); +#endif + + d_plugins.append( new TextLabelInterface( this ) ); +} + +QList +CustomWidgetCollectionInterface::customWidgets( void ) const +{ + return d_plugins; +} + +TaskMenuFactory::TaskMenuFactory( QExtensionManager *parent ): + QExtensionFactory( parent ) +{ +} + +QObject *TaskMenuFactory::createExtension( + QObject *object, const QString &iid, QObject *parent ) const +{ + if ( iid == Q_TYPEID( QDesignerTaskMenuExtension ) ) + { +#ifndef NO_QWT_PLOT + if ( QwtPlot *plot = qobject_cast( object ) ) + return new TaskMenuExtension( plot, parent ); +#endif +#ifndef NO_QWT_WIDGETS + if ( QwtDial *dial = qobject_cast( object ) ) + return new TaskMenuExtension( dial, parent ); +#endif + } + + return QExtensionFactory::createExtension( object, iid, parent ); +} + + +TaskMenuExtension::TaskMenuExtension( QWidget *widget, QObject *parent ): + QObject( parent ), + d_widget( widget ) +{ + d_editAction = new QAction( tr( "Edit Qwt Attributes ..." ), this ); + connect( d_editAction, SIGNAL( triggered() ), + this, SLOT( editProperties() ) ); +} + +QList TaskMenuExtension::taskActions() const +{ + QList list; + list.append( d_editAction ); + return list; +} + +QAction *TaskMenuExtension::preferredEditAction() const +{ + return d_editAction; +} + +void TaskMenuExtension::editProperties() +{ + const QVariant v = d_widget->property( "propertiesDocument" ); + if ( v.type() != QVariant::String ) + return; + +#ifndef NO_QWT_PLOT + QString properties = v.toString(); + + if ( qobject_cast( d_widget ) ) + { + PlotDialog dialog( properties ); + connect( &dialog, SIGNAL( edited( const QString& ) ), + SLOT( applyProperties( const QString & ) ) ); + ( void )dialog.exec(); + return; + } +#endif + + static QErrorMessage *errorMessage = NULL; + if ( errorMessage == NULL ) + errorMessage = new QErrorMessage(); + errorMessage->showMessage( "Not implemented yet." ); +} + +void TaskMenuExtension::applyProperties( const QString &properties ) +{ + QDesignerFormWindowInterface *formWindow = + QDesignerFormWindowInterface::findFormWindow( d_widget ); + if ( formWindow && formWindow->cursor() ) + formWindow->cursor()->setProperty( "propertiesDocument", properties ); +} + +#if QT_VERSION < 0x050000 +Q_EXPORT_PLUGIN2( QwtDesignerPlugin, CustomWidgetCollectionInterface ) +#endif diff --git a/qwt/designer/qwt_designer_plugin.h b/qwt/designer/qwt_designer_plugin.h new file mode 100644 index 000000000..c24e65600 --- /dev/null +++ b/qwt/designer/qwt_designer_plugin.h @@ -0,0 +1,248 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_DESIGNER_PLUGIN_H +#define QWT_DESIGNER_PLUGIN_H + +#include +#include +#include + +namespace QwtDesignerPlugin +{ + class CustomWidgetInterface: public QObject, + public QDesignerCustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + CustomWidgetInterface( QObject *parent ); + + virtual bool isContainer() const; + virtual bool isInitialized() const; + virtual QIcon icon() const; + virtual QString codeTemplate() const; + virtual QString domXml() const; + virtual QString group() const; + virtual QString includeFile() const; + virtual QString name() const; + virtual QString toolTip() const; + virtual QString whatsThis() const; + virtual void initialize( QDesignerFormEditorInterface * ); + + protected: + QString d_name; + QString d_include; + QString d_toolTip; + QString d_whatsThis; + QString d_domXml; + QString d_codeTemplate; + QIcon d_icon; + + private: + bool d_isInitialized; + }; + + class CustomWidgetCollectionInterface: public QObject, + public QDesignerCustomWidgetCollectionInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetCollectionInterface ) + +#if QT_VERSION >= 0x050000 + Q_PLUGIN_METADATA(IID "org.qt-project.Qt.QDesignerCustomWidgetCollectionInterface" ) +#endif + + + public: + CustomWidgetCollectionInterface( QObject *parent = NULL ); + + virtual QList customWidgets() const; + + private: + QList d_plugins; + }; + +#ifndef NO_QWT_PLOT + class PlotInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + PlotInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; + + class PlotCanvasInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + PlotCanvasInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + +#ifndef NO_QWT_WIDGETS + class AnalogClockInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + AnalogClockInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + +#ifndef NO_QWT_WIDGETS + class CompassInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + CompassInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + +#ifndef NO_QWT_WIDGETS + class CounterInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + CounterInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + +#ifndef NO_QWT_WIDGETS + class DialInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + DialInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + +#ifndef NO_QWT_WIDGETS + class KnobInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + KnobInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + +#ifndef NO_QWT_PLOT + class ScaleWidgetInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + ScaleWidgetInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + +#ifndef NO_QWT_WIDGETS + class SliderInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + SliderInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + + class TextLabelInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + TextLabelInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; + +#ifndef NO_QWT_WIDGETS + class ThermoInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + ThermoInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + +#ifndef NO_QWT_WIDGETS + class WheelInterface: public CustomWidgetInterface + { + Q_OBJECT + Q_INTERFACES( QDesignerCustomWidgetInterface ) + + public: + WheelInterface( QObject *parent ); + virtual QWidget *createWidget( QWidget *parent ); + }; +#endif + + class TaskMenuFactory: public QExtensionFactory + { + Q_OBJECT + + public: + TaskMenuFactory( QExtensionManager *parent = 0 ); + + protected: + QObject *createExtension( QObject *object, + const QString &iid, QObject *parent ) const; + }; + + class TaskMenuExtension: public QObject, + public QDesignerTaskMenuExtension + { + Q_OBJECT + Q_INTERFACES( QDesignerTaskMenuExtension ) + + public: + TaskMenuExtension( QWidget *widget, QObject *parent ); + + QAction *preferredEditAction() const; + QList taskActions() const; + + private Q_SLOTS: + void editProperties(); + void applyProperties( const QString & ); + + private: + QAction *d_editAction; + QWidget *d_widget; + }; + +}; + +#endif diff --git a/qwt/designer/qwt_designer_plugin.qrc b/qwt/designer/qwt_designer_plugin.qrc new file mode 100644 index 000000000..01e4ffc8f --- /dev/null +++ b/qwt/designer/qwt_designer_plugin.qrc @@ -0,0 +1,15 @@ + + + pixmaps/qwtplot.png + pixmaps/qwtanalogclock.png + pixmaps/qwtcounter.png + pixmaps/qwtcompass.png + pixmaps/qwtdial.png + pixmaps/qwtknob.png + pixmaps/qwtscale.png + pixmaps/qwtslider.png + pixmaps/qwtthermo.png + pixmaps/qwtwheel.png + pixmaps/qwtwidget.png + + diff --git a/qwt/doc/Doxyfile b/qwt/doc/Doxyfile new file mode 100644 index 000000000..d1e55dd67 --- /dev/null +++ b/qwt/doc/Doxyfile @@ -0,0 +1,1799 @@ +# Doxyfile 1.8.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (" "). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# http://www.gnu.org/software/libiconv for the list of possible encodings. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or sequence of words) that should +# identify the project. Note that if you do not use Doxywizard you need +# to put quotes around the project name if it contains spaces. + +PROJECT_NAME = "Qwt User's Guide" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. +# This could be handy for archiving the generated documentation or +# if some version control system is used. + +PROJECT_NUMBER = $(QWTVERSION) + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer +# a quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify an logo or icon that is +# included in the documentation. The maximum height of the logo should not +# exceed 55 pixels and the maximum width should not exceed 200 pixels. +# Doxygen will copy the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) +# base path where the generated documentation will be put. +# If a relative path is entered, it will be relative to the location +# where doxygen was started. If left blank the current directory will be used. + +OUTPUT_DIRECTORY = + +# If the CREATE_SUBDIRS tag is set to YES, then doxygen will create +# 4096 sub-directories (in 2 levels) under the output directory of each output +# format and will distribute the generated files over these directories. +# Enabling this option can be useful when feeding doxygen a huge amount of +# source files, where putting all generated files in the same directory would +# otherwise cause performance problems for the file system. + +CREATE_SUBDIRS = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# The default language is English, other supported languages are: +# Afrikaans, Arabic, Brazilian, Catalan, Chinese, Chinese-Traditional, +# Croatian, Czech, Danish, Dutch, Esperanto, Farsi, Finnish, French, German, +# Greek, Hungarian, Italian, Japanese, Japanese-en (Japanese with English +# messages), Korean, Korean-en, Lithuanian, Norwegian, Macedonian, Persian, +# Polish, Portuguese, Romanian, Russian, Serbian, Serbian-Cyrillic, Slovak, +# Slovene, Spanish, Swedish, Ukrainian, and Vietnamese. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will +# include brief member descriptions after the members that are listed in +# the file and class documentation (similar to JavaDoc). +# Set to NO to disable this. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will prepend +# the brief description of a member or function before the detailed description. +# Note: if both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator +# that is used to form the text in various listings. Each string +# in this list, if found as the leading text of the brief description, will be +# stripped from the text and the result after processing the whole list, is +# used as the annotated text. Otherwise, the brief description is used as-is. +# If left blank, the following values are used ("$name" is automatically +# replaced with the name of the entity): "The $name class" "The $name widget" +# "The $name file" "is" "provides" "specifies" "contains" +# "represents" "a" "an" "the" + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# Doxygen will generate a detailed section even if there is only a brief +# description. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full +# path before files name in the file list and in the header files. If set +# to NO the shortest path that makes the file name unique will be used. + +FULL_PATH_NAMES = NO + +# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag +# can be used to strip a user-defined part of the path. Stripping is +# only done if one of the specified strings matches the left-hand part of +# the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the +# path to strip. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of +# the path mentioned in the documentation of a class, which tells +# the reader which header file to include in order to use a class. +# If left blank only the name of the header file containing the class +# definition is used. Otherwise one should specify the include paths that +# are normally passed to the compiler using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter +# (but less readable) file names. This can be useful if your file system +# doesn't support long names like on DOS, Mac, or CD-ROM. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen +# will interpret the first line (until the first dot) of a JavaDoc-style +# comment as the brief description. If set to NO, the JavaDoc +# comments will behave just like regular Qt-style comments +# (thus requiring an explicit @brief command for a brief description.) + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then Doxygen will +# interpret the first line (until the first dot) of a Qt-style +# comment as the brief description. If set to NO, the comments +# will behave just like regular Qt-style comments (thus requiring +# an explicit \brief command for a brief description.) + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen +# treat a multi-line C++ special comment block (i.e. a block of //! or /// +# comments) as a brief description. This used to be the default behaviour. +# The new default is to treat a multi-line C++ comment block as a detailed +# description. Set this tag to YES if you prefer the old behaviour instead. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented +# member inherits the documentation from any documented member that it +# re-implements. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES, then doxygen will produce +# a new page for each member. If set to NO, the documentation of a member will +# be part of the file/class/namespace that contains it. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. +# Doxygen uses this value to replace tabs by spaces in code fragments. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that acts +# as commands in the documentation. An alias has the form "name=value". +# For example adding "sideeffect=\par Side Effects:\n" will allow you to +# put the command \sideeffect (or @sideeffect) in the documentation, which +# will result in a user-defined paragraph with heading "Side Effects:". +# You can put \n's in the value part of an alias to insert newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding +# "class=itcl::class" will allow you to use the command class in the +# itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C +# sources only. Doxygen will then generate output that is more tailored for C. +# For instance, some of the names that are used will be different. The list +# of all members will be omitted, etc. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java +# sources only. Doxygen will then generate output that is more tailored for +# Java. For instance, namespaces will be presented as packages, qualified +# scopes will look different, etc. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources only. Doxygen will then generate output that is more tailored for +# Fortran. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for +# VHDL. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given extension. +# Doxygen has a built-in mapping, but you can override or extend it using this +# tag. The format is ext=language, where ext is a file extension, and language +# is one of the parsers supported by doxygen: IDL, Java, Javascript, CSharp, C, +# C++, D, PHP, Objective-C, Python, Fortran, VHDL, C, C++. For instance to make +# doxygen treat .inc files as Fortran files (default is PHP), and .f files as C +# (default is Fortran), use: inc=Fortran f=C. Note that for custom extensions +# you also need to set FILE_PATTERNS otherwise the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If MARKDOWN_SUPPORT is enabled (the default) then doxygen pre-processes all +# comments according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you +# can mix doxygen, HTML, and XML commands with Markdown formatting. +# Disable only in case of backward compatibilities issues. + +MARKDOWN_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should +# set this tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); v.s. +# func(std::string) {}). This also makes the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip sources only. +# Doxygen will parse them like normal C++ but will assume all classes use public +# instead of private inheritance when no explicit protection keyword is present. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate getter +# and setter methods for a property. Setting this option to YES (the default) +# will make doxygen replace the get and set methods by a property in the +# documentation. This will only work if the methods are indeed getting or +# setting a simple type. If this is not the case, or you want to show the +# methods anyway, you should set this option to NO. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES, then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. + +DISTRIBUTE_GROUP_DOC = NO + +# Set the SUBGROUPING tag to YES (the default) to allow class member groups of +# the same type (for instance a group of public functions) to be put as a +# subgroup of that type (e.g. under the Public Functions section). Set it to +# NO to prevent subgrouping. Alternatively, this can be done per class using +# the \nosubgrouping command. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and +# unions are shown inside the group in which they are included (e.g. using +# @ingroup) instead of on a separate page (for HTML and Man pages) or +# section (for LaTeX and RTF). + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and +# unions with only public data fields will be shown inline in the documentation +# of the scope in which they are defined (i.e. file, namespace, or group +# documentation), provided this scope is documented. If set to NO (the default), +# structs, classes, and unions are shown on a separate page (for HTML and Man +# pages) or section (for LaTeX and RTF). + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT is enabled, a typedef of a struct, union, or enum +# is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically +# be useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. + +TYPEDEF_HIDES_STRUCT = NO + +# The SYMBOL_CACHE_SIZE determines the size of the internal cache use to +# determine which symbols to keep in memory and which to flush to disk. +# When the cache is full, less often used symbols will be written to disk. +# For small to medium size projects (<1000 input files) the default value is +# probably good enough. For larger projects a too small cache size can cause +# doxygen to be busy swapping symbols to and from disk most of the time +# causing a significant performance penalty. +# If the system has enough physical memory increasing the cache will improve the +# performance by keeping more symbols in memory. Note that the value works on +# a logarithmic scale so increasing the size by one will roughly double the +# memory usage. The cache size is given by this formula: +# 2^(16+SYMBOL_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +SYMBOL_CACHE_SIZE = 0 + +# Similar to the SYMBOL_CACHE_SIZE the size of the symbol lookup cache can be +# set using LOOKUP_CACHE_SIZE. This cache is used to resolve symbols given +# their name and scope. Since this can be an expensive process and often the +# same symbol appear multiple times in the code, doxygen keeps a cache of +# pre-resolved symbols. If the cache is too small doxygen will become slower. +# If the cache is too large, memory is wasted. The cache size is given by this +# formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range is 0..9, the default is 0, +# corresponding to a cache size of 2^16 = 65536 symbols. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in +# documentation are documented, even if no documentation was available. +# Private class members and static file members will be hidden unless +# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES all private members of a class +# will be included in the documentation. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES all members with package or internal scope will be included in the documentation. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES all static members of a file +# will be included in the documentation. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) +# defined locally in source files will be included in the documentation. +# If set to NO only classes defined in header files are included. + +EXTRACT_LOCAL_CLASSES = NO + +# This flag is only useful for Objective-C code. When set to YES local +# methods, which are defined in the implementation section but not in +# the interface are included in the documentation. +# If set to NO (the default) only methods in the interface are included. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base +# name of the file that contains the anonymous namespace. By default +# anonymous namespaces are hidden. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all +# undocumented members of documented classes, files or namespaces. +# If set to NO (the default) these members will be included in the +# various overviews, but no documentation section is generated. +# This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. +# If set to NO (the default) these classes will be included in the various +# overviews. This option has no effect if EXTRACT_ALL is enabled. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all +# friend (class|struct|union) declarations. +# If set to NO (the default) these declarations will be included in the +# documentation. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any +# documentation blocks found inside the body of a function. +# If set to NO (the default) these blocks will be appended to the +# function's detailed documentation block. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation +# that is typed after a \internal command is included. If the tag is set +# to NO (the default) then the documentation will be excluded. +# Set it to YES to include the internal documentation. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate +# file names in lower-case letters. If set to YES upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. + +CASE_SENSE_NAMES = NO + +# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen +# will show members with their full class and namespace scopes in the +# documentation. If set to YES the scope will be hidden. + +HIDE_SCOPE_NAMES = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen +# will put a list of the files that are included by a file in the documentation +# of that file. + +SHOW_INCLUDE_FILES = YES + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then Doxygen +# will list include files with double quotes in the documentation +# rather than with sharp brackets. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] +# is inserted in the documentation for inline members. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen +# will sort the (detailed) documentation of file and class members +# alphabetically by member name. If set to NO the members will appear in +# declaration order. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the +# brief documentation of file, namespace and class members alphabetically +# by member name. If set to NO (the default) the members will appear in +# declaration order. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen +# will sort the (brief and detailed) documentation of class members so that +# constructors and destructors are listed first. If set to NO (the default) +# the constructors will appear in the respective orders defined by +# SORT_MEMBER_DOCS and SORT_BRIEF_DOCS. +# This tag will be ignored for brief docs if SORT_BRIEF_DOCS is set to NO +# and ignored for detailed docs if SORT_MEMBER_DOCS is set to NO. + +SORT_MEMBERS_CTORS_1ST = YES + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the +# hierarchy of group names into alphabetical order. If set to NO (the default) +# the group names will appear in their defined order. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be +# sorted by fully-qualified names, including namespaces. If set to +# NO (the default), the class list will be sorted only by class name, +# not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the +# alphabetical list. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to +# do proper type resolution of all parameters of a function it will reject a +# match between the prototype and the implementation of a member function even +# if there is only one candidate or it is obvious which candidate to choose +# by doing a simple string match. By disabling STRICT_PROTO_MATCHING doxygen +# will still accept a match between prototype and implementation in such cases. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or +# disable (NO) the todo list. This list is created by putting \todo +# commands in the documentation. + +GENERATE_TODOLIST = NO + +# The GENERATE_TESTLIST tag can be used to enable (YES) or +# disable (NO) the test list. This list is created by putting \test +# commands in the documentation. + +GENERATE_TESTLIST = NO + +# The GENERATE_BUGLIST tag can be used to enable (YES) or +# disable (NO) the bug list. This list is created by putting \bug +# commands in the documentation. + +GENERATE_BUGLIST = NO + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or +# disable (NO) the deprecated list. This list is created by putting +# \deprecated commands in the documentation. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional +# documentation sections, marked by \if sectionname ... \endif. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines +# the initial value of a variable or macro consists of for it to appear in +# the documentation. If the initializer consists of more lines than specified +# here it will be hidden. Use a value of 0 to hide initializers completely. +# The appearance of the initializer of individual variables and macros in the +# documentation can be controlled using \showinitializer or \hideinitializer +# command in the documentation regardless of this setting. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated +# at the bottom of the documentation of classes and structs. If set to YES the +# list will mention the files that were used to generate the documentation. + +SHOW_USED_FILES = NO + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. +# This will remove the Files entry from the Quick Index and from the +# Folder Tree View (if specified). The default is YES. + +SHOW_FILES = NO + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the +# Namespaces page. +# This will remove the Namespaces entry from the Quick Index +# and from the Folder Tree View (if specified). The default is YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command , where is the value of +# the FILE_VERSION_FILTER tag, and is the name of an input file +# provided by doxygen. Whatever the program writes to standard output +# is used as the file version. See the manual for examples. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. The create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. +# You can optionally specify a file name after the option, if omitted +# DoxygenLayout.xml will be used as the name of the layout file. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files +# containing the references data. This must be a list of .bib files. The +# .bib extension is automatically appended if omitted. Using this command +# requires the bibtex tool to be installed. See also +# http://en.wikipedia.org/wiki/BibTeX for more info. For LaTeX the style +# of the bibliography can be controlled using LATEX_BIB_STYLE. To use this +# feature you need bibtex and perl available in the search path. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated +# by doxygen. Possible values are YES and NO. If left blank NO is used. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated by doxygen. Possible values are YES and NO. If left blank +# NO is used. + +WARNINGS = YES + +# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings +# for undocumented members. If EXTRACT_ALL is set to YES then this flag will +# automatically be disabled. + +WARN_IF_UNDOCUMENTED = YES + +# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some +# parameters in a documented function, or documenting parameters that +# don't exist or using markup commands wrongly. + +WARN_IF_DOC_ERROR = YES + +# The WARN_NO_PARAMDOC option can be enabled to get warnings for +# functions that are documented, but have no documentation for their parameters +# or return value. If set to NO (the default) doxygen will only warn about +# wrong or incomplete parameter documentation, but not about the absence of +# documentation. + +WARN_NO_PARAMDOC = YES + +# The WARN_FORMAT tag determines the format of the warning messages that +# doxygen can produce. The string should contain the $file, $line, and $text +# tags, which will be replaced by the file and line number from which the +# warning originated and the warning text. Optionally the format may contain +# $version, which will be replaced by the version of the file (if it could +# be obtained via FILE_VERSION_FILTER) + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning +# and error messages should be written. If left blank the output is written +# to stderr. + +WARN_LOGFILE = Doxygen.log + +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag can be used to specify the files and/or directories that contain +# documented source files. You may enter file names like "myfile.cpp" or +# directories like "/usr/src/myproject". Separate the files or directories +# with spaces. + +INPUT = . \ + ../src \ + ../textengines/mathml + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding, which is +# also the default input encoding. Doxygen uses libiconv (or the iconv built +# into libc) for the transcoding. See http://www.gnu.org/software/libiconv for +# the list of possible encodings. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank the following patterns are tested: +# *.c *.cc *.cxx *.cpp *.c++ *.d *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh +# *.hxx *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.dox *.py +# *.f90 *.f *.for *.vhd *.vhdl + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to turn specify whether or not subdirectories +# should be searched for input files as well. Possible values are YES and NO. +# If left blank NO is used. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. Note that the wildcards are matched +# against the file with absolute path, so to exclude all test directories +# for example use the pattern */test/* + +EXCLUDE_PATTERNS = qwt.h + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test + +EXCLUDE_SYMBOLS = \ + QwtMathMLDocument \ + QwtPainterCommand::PixmapData \ + QwtPainterCommand::ImageData \ + QwtPainterCommand::StateData + +# The EXAMPLE_PATH tag can be used to specify one or more files or +# directories that contain example code fragments that are included (see +# the \include command). + +EXAMPLE_PATH = . + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp +# and *.h) to filter out the source-files in the directories. If left +# blank all files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude +# commands irrespective of the value of the RECURSIVE tag. +# Possible values are YES and NO. If left blank NO is used. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or +# directories that contain image that are included in the documentation (see +# the \image command). + +IMAGE_PATH = images + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command , where +# is the value of the INPUT_FILTER tag, and is the name of an +# input file. Doxygen will then use the output that the filter program writes +# to standard output. +# If FILTER_PATTERNS is specified, this tag will be +# ignored. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. +# Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. +# The filters are a list of the form: +# pattern=filter (like *.cpp=my_cpp_filter). See INPUT_FILTER for further +# info on how filters are used. If FILTER_PATTERNS is empty or if +# non of the patterns match the file name, INPUT_FILTER is applied. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will be used to filter the input files when producing source +# files to browse (i.e. when SOURCE_BROWSER is set to YES). + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) +# and it is also possible to disable source filtering for a specific pattern +# using *.ext= (so without naming a filter). This option only has effect when +# FILTER_SOURCE_FILES is enabled. + +FILTER_SOURCE_PATTERNS = + +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will +# be generated. Documented entities will be cross-referenced with these sources. +# Note: To get rid of all source code in the generated output, make sure also +# VERBATIM_HEADERS is set to NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body +# of functions and classes directly in the documentation. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct +# doxygen to hide any special comment blocks from generated source code +# fragments. Normal C and C++ comments will always remain visible. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES +# then for each documented function all documented +# functions referencing it will be listed. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES +# then for each documented function all documented entities +# called/used by that function will be listed. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES (the default) +# and SOURCE_BROWSER tag is set to YES, then the hyperlinks from +# functions in REFERENCES_RELATION and REFERENCED_BY_RELATION lists will +# link to the source code. +# Otherwise they will link to the documentation. + +REFERENCES_LINK_SOURCE = YES + +# If the USE_HTAGS tag is set to YES then the references to source code +# will point to the HTML generated by the htags(1) tool instead of doxygen +# built-in source browser. The htags tool is part of GNU's global source +# tagging system (see http://www.gnu.org/software/global/global.html). You +# will need version 4.8.6 or higher. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen +# will generate a verbatim copy of the header file for each class for +# which an include is specified. Set to NO to disable this. + +VERBATIM_HEADERS = YES + +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index +# of all compounds will be generated. Enable this if the project +# contains a lot of classes, structs, unions or interfaces. + +ALPHABETICAL_INDEX = YES + +# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then +# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns +# in which this list will be split (can be a number in the range [1..20]) + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all +# classes will be put under the same header in the alphabetical index. +# The IGNORE_PREFIX tag can be used to specify one or more prefixes that +# should be ignored while generating the index headers. + +IGNORE_PREFIX = Qwt \ + Q + +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES (the default) Doxygen will +# generate HTML output. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `html' will be used as the default path. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for +# each generated HTML page (for example: .htm,.php,.asp). If it is left blank +# doxygen will generate files with .html extension. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a personal HTML header for +# each generated HTML page. If it is left blank doxygen will generate a +# standard header. Note that when using a custom header you are responsible +# for the proper inclusion of any scripts and style sheets that doxygen +# needs, which is dependent on the configuration options used. +# It is advised to generate a default header using "doxygen -w html +# header.html footer.html stylesheet.css YourConfigFile" and then modify +# that header. Note that the header is subject to change so you typically +# have to redo this when upgrading to a newer version of doxygen or when +# changing the value of configuration settings such as GENERATE_TREEVIEW! + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a personal HTML footer for +# each generated HTML page. If it is left blank doxygen will generate a +# standard footer. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading +# style sheet that is used by each HTML page. It can be used to +# fine-tune the look of the HTML output. If the tag is left blank doxygen +# will generate a default style sheet. Note that doxygen will try to copy +# the style sheet file to the HTML output directory, so don't put your own +# style sheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath$ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that +# the files will be copied as-is; there are no commands or markers available. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. +# Doxygen will adjust the colors in the style sheet and background images +# according to this color. Hue is specified as an angle on a colorwheel, +# see http://en.wikipedia.org/wiki/Hue for more information. +# For instance the value 0 represents red, 60 is yellow, 120 is green, +# 180 is cyan, 240 is blue, 300 purple, and 360 is red again. +# The allowed range is 0 to 359. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of +# the colors in the HTML output. For a value of 0 the output will use +# grayscales only. A value of 255 will produce the most vivid colors. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to +# the luminance component of the colors in the HTML output. Values below +# 100 gradually make the output lighter, whereas values above 100 make +# the output darker. The value divided by 100 is the actual gamma applied, +# so 80 represents a gamma of 0.8, The value 220 represents a gamma of 2.2, +# and 100 does not change the gamma. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting +# this to NO can help when comparing the output of multiple runs. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of +# entries shown in the various tree structured indices initially; the user +# can expand and collapse entries dynamically later on. Doxygen will expand +# the tree to such a level that at most the specified number of entries are +# visible (unless a fully collapsed tree already exceeds this amount). +# So setting the number of entries 1 will produce a full collapsed tree by +# default. 0 is a special value representing an infinite number of entries +# and will result in a full expanded tree by default. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files +# will be generated that can be used as input for Apple's Xcode 3 +# integrated development environment, introduced with OSX 10.5 (Leopard). +# To create a documentation set, doxygen will generate a Makefile in the +# HTML output directory. Running make will produce the docset in that +# directory and running "make install" will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find +# it at startup. +# See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. + +GENERATE_DOCSET = NO + +# When GENERATE_DOCSET tag is set to YES, this tag determines the name of the +# feed. A documentation feed provides an umbrella under which multiple +# documentation sets from a single provider (such as a company or product suite) +# can be grouped. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# When GENERATE_DOCSET tag is set to YES, this tag specifies a string that +# should uniquely identify the documentation set bundle. This should be a +# reverse domain-name style string, e.g. com.mycompany.MyDocSet. Doxygen +# will append .docset to the name. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# When GENERATE_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The GENERATE_PUBLISHER_NAME tag identifies the documentation publisher. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES, additional index files +# will be generated that can be used as input for tools like the +# Microsoft HTML help workshop to generate a compiled HTML help file (.chm) +# of the generated HTML documentation. + +GENERATE_HTMLHELP = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can +# be used to specify the file name of the resulting .chm file. You +# can add a path in front of the file if the result should not be +# written to the html output directory. + +CHM_FILE = + +# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can +# be used to specify the location (absolute path including file name) of +# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run +# the HTML help compiler on the generated index.hhp. + +HHC_LOCATION = + +# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag +# controls if a separate .chi index file is generated (YES) or that +# it should be included in the master .chm file (NO). + +GENERATE_CHI = NO + +# If the GENERATE_HTMLHELP tag is set to YES, the CHM_INDEX_ENCODING +# is used to encode HtmlHelp index (hhk), content (hhc) and project file +# content. + +CHM_INDEX_ENCODING = + +# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag +# controls whether a binary table of contents is generated (YES) or a +# normal table of contents (NO) in the .chm file. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members +# to the contents of the HTML help documentation and to the tree view. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated +# that can be used as input for Qt's qhelpgenerator to generate a +# Qt Compressed Help (.qch) of the generated HTML documentation. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can +# be used to specify the file name of the resulting .qch file. +# The path specified is relative to the HTML output folder. + +QCH_FILE = qwtdoc.qch + +# The QHP_NAMESPACE tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#namespace + +QHP_NAMESPACE = net.sourceforge.qwt-svn + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating +# Qt Help Project output. For more information please see +# http://doc.trolltech.com/qthelpproject.html#virtual-folders + +QHP_VIRTUAL_FOLDER = qwt-svn + +# If QHP_CUST_FILTER_NAME is set, it specifies the name of a custom filter to +# add. For more information please see +# http://doc.trolltech.com/qthelpproject.html#custom-filters + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILT_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see +# +# Qt Help Project / Custom Filters. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's +# filter section matches. +# +# Qt Help Project / Filter Attributes. + +QHP_SECT_FILTER_ATTRS = + +# If the GENERATE_QHP tag is set to YES, the QHG_LOCATION tag can +# be used to specify the location of Qt's qhelpgenerator. +# If non-empty doxygen will try to run qhelpgenerator on the generated +# .qhp file. + +QHG_LOCATION = qhelpgenerator + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files +# will be generated, which together with the HTML files, form an Eclipse help +# plugin. To install this plugin and make it available under the help contents +# menu in Eclipse, the contents of the directory containing the HTML and XML +# files needs to be copied into the plugins directory of eclipse. The name of +# the directory within the plugins directory should be the same as +# the ECLIPSE_DOC_ID value. After copying Eclipse needs to be restarted before +# the help appears. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have +# this name. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) +# at top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. Since the tabs have the same information as the +# navigation tree you can set this option to NO if you already set +# GENERATE_TREEVIEW to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. +# If the tag value is set to YES, a side panel will be generated +# containing a tree-like index structure (just like the one that +# is generated for HTML Help). For this to work a browser that supports +# JavaScript, DHTML, CSS and frames is required (i.e. any modern browser). +# Windows users are probably better off using the HTML help feature. +# Since the tree basically has the same information as the tab index you +# could consider to set DISABLE_INDEX to NO when enabling this option. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values +# (range [0,1..20]) that doxygen will group on one line in the generated HTML +# documentation. Note that a value of 0 will completely suppress the enum +# values from appearing in the overview section. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be +# used to set the initial width (in pixels) of the frame in which the tree +# is shown. + +TREEVIEW_WIDTH = 250 + +# When the EXT_LINKS_IN_WINDOW option is set to YES doxygen will open +# links to external symbols imported via tag files in a separate window. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of Latex formulas included +# as images in the HTML documentation. The default is 10. Note that +# when you change the font size after a successful doxygen run you need +# to manually remove any form_*.png images from the HTML output directory +# to force them to be regenerated. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are +# not supported properly for IE 6.0, but are supported on all modern browsers. +# Note that when changing this option you need to delete any form_*.png files +# in the HTML output before the changes have effect. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax +# (see http://www.mathjax.org) which uses client side Javascript for the +# rendering instead of using prerendered bitmaps. Use this if you do not +# have LaTeX installed or if you want to formulas look prettier in the HTML +# output. When enabled you may also need to install MathJax separately and +# configure the path to it using the MATHJAX_RELPATH option. + +USE_MATHJAX = NO + +# When MathJax is enabled you need to specify the location relative to the +# HTML output directory using the MATHJAX_RELPATH option. The destination +# directory should contain the MathJax.js script. For instance, if the mathjax +# directory is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to +# the MathJax Content Delivery Network so you can quickly see the result without +# installing MathJax. +# However, it is strongly recommended to install a local +# copy of MathJax from http://www.mathjax.org before deployment. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or MathJax extension +# names that should be enabled during MathJax rendering. + +MATHJAX_EXTENSIONS = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box +# for the HTML output. The underlying search engine uses javascript +# and DHTML and should work on any modern browser. Note that when using +# HTML help (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets +# (GENERATE_DOCSET) there is already a search function so this one should +# typically be disabled. For large projects the javascript based search engine +# can be slow, then enabling SERVER_BASED_SEARCH may provide a better solution. + +SEARCHENGINE = YES + +# When the SERVER_BASED_SEARCH tag is enabled the search engine will be +# implemented using a PHP enabled web server instead of at the web client +# using Javascript. Doxygen will generate the search PHP script and index +# file to put on the web server. The advantage of the server +# based approach is that it scales better to large projects and allows +# full text search. The disadvantages are that it is more difficult to setup +# and does not have live searching capabilities. + +SERVER_BASED_SEARCH = NO + +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- + +# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will +# generate Latex output. + +GENERATE_LATEX = NO + +# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `latex' will be used as the default path. + +LATEX_OUTPUT = latex + +# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be +# invoked. If left blank `latex' will be used as the default command name. +# Note that when enabling USE_PDFLATEX this option is only used for +# generating bitmaps for formulas in the HTML output, but not in the +# Makefile that is written to the output directory. + +LATEX_CMD_NAME = latex + +# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to +# generate index for LaTeX. If left blank `makeindex' will be used as the +# default command name. + +MAKEINDEX_CMD_NAME = makeindex + +# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact +# LaTeX documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_LATEX = YES + +# The PAPER_TYPE tag can be used to set the paper type that is used +# by the printer. Possible values are: a4, letter, legal and +# executive. If left blank a4wide will be used. + +PAPER_TYPE = a4wide + +# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX +# packages that should be included in the LaTeX output. + +EXTRA_PACKAGES = + +# The LATEX_HEADER tag can be used to specify a personal LaTeX header for +# the generated latex document. The header should contain everything until +# the first chapter. If it is left blank doxygen will generate a +# standard header. Notice: only use this tag if you know what you are doing! + +LATEX_HEADER = + +# The LATEX_FOOTER tag can be used to specify a personal LaTeX footer for +# the generated latex document. The footer should contain everything after +# the last chapter. If it is left blank doxygen will generate a +# standard footer. Notice: only use this tag if you know what you are doing! + +LATEX_FOOTER = + +# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated +# is prepared for conversion to pdf (using ps2pdf). The pdf file will +# contain links (just like the HTML output) instead of page references +# This makes the output suitable for online browsing using a pdf viewer. + +PDF_HYPERLINKS = YES + +# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of +# plain latex in the generated Makefile. Set this option to YES to get a +# higher quality PDF documentation. + +USE_PDFLATEX = YES + +# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. +# command to the generated LaTeX files. This will instruct LaTeX to keep +# running if errors occur, instead of asking the user for help. +# This option is also used when generating formulas in HTML. + +LATEX_BATCHMODE = NO + +# If LATEX_HIDE_INDICES is set to YES then doxygen will not +# include the index chapters (such as File Index, Compound Index, etc.) +# in the output. + +LATEX_HIDE_INDICES = NO + +# If LATEX_SOURCE_CODE is set to YES then doxygen will include +# source code with syntax highlighting in the LaTeX output. +# Note that which sources are shown also depends on other settings +# such as SOURCE_BROWSER. + +LATEX_SOURCE_CODE = NO + +# The LATEX_BIB_STYLE tag can be used to specify the style to use for the +# bibliography, e.g. plainnat, or ieeetr. The default style is "plain". See +# http://en.wikipedia.org/wiki/BibTeX for more info. + +LATEX_BIB_STYLE = plain + +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- + +# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output +# The RTF output is optimized for Word 97 and may not look very pretty with +# other RTF readers or editors. + +GENERATE_RTF = NO + +# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `rtf' will be used as the default path. + +RTF_OUTPUT = rtf + +# If the COMPACT_RTF tag is set to YES Doxygen generates more compact +# RTF documents. This may be useful for small projects and may help to +# save some trees in general. + +COMPACT_RTF = NO + +# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated +# will contain hyperlink fields. The RTF file will +# contain links (just like the HTML output) instead of page references. +# This makes the output suitable for online browsing using WORD or other +# programs which support those fields. +# Note: wordpad (write) and others do not support links. + +RTF_HYPERLINKS = NO + +# Load style sheet definitions from file. Syntax is similar to doxygen's +# config file, i.e. a series of assignments. You only have to provide +# replacements, missing definitions are set to their default value. + +RTF_STYLESHEET_FILE = + +# Set optional variables used in the generation of an rtf document. +# Syntax is similar to doxygen's config file. + +RTF_EXTENSIONS_FILE = + +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- + +# If the GENERATE_MAN tag is set to YES (the default) Doxygen will +# generate man pages + +GENERATE_MAN = NO + +# The MAN_OUTPUT tag is used to specify where the man pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `man' will be used as the default path. + +MAN_OUTPUT = man + +# The MAN_EXTENSION tag determines the extension that is added to +# the generated man pages (default is the subroutine's section .3) + +MAN_EXTENSION = .3 + +# If the MAN_LINKS tag is set to YES and Doxygen generates man output, +# then it will generate one additional man file for each entity +# documented in the real man page(s). These additional files +# only source the real man page, but without them the man command +# would be unable to find the correct page. The default is NO. + +MAN_LINKS = NO + +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- + +# If the GENERATE_XML tag is set to YES Doxygen will +# generate an XML file that captures the structure of +# the code including all documentation. + +GENERATE_XML = NO + +# The XML_OUTPUT tag is used to specify where the XML pages will be put. +# If a relative path is entered the value of OUTPUT_DIRECTORY will be +# put in front of it. If left blank `xml' will be used as the default path. + +XML_OUTPUT = xml + +# The XML_SCHEMA tag can be used to specify an XML schema, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_SCHEMA = + +# The XML_DTD tag can be used to specify an XML DTD, +# which can be used by a validating XML parser to check the +# syntax of the XML files. + +XML_DTD = + +# If the XML_PROGRAMLISTING tag is set to YES Doxygen will +# dump the program listings (including syntax highlighting +# and cross-referencing information) to the XML output. Note that +# enabling this will significantly increase the size of the XML output. + +XML_PROGRAMLISTING = YES + +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- + +# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will +# generate an AutoGen Definitions (see autogen.sf.net) file +# that captures the structure of the code including all +# documentation. Note that this feature is still experimental +# and incomplete at the moment. + +GENERATE_AUTOGEN_DEF = NO + +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- + +# If the GENERATE_PERLMOD tag is set to YES Doxygen will +# generate a Perl module file that captures the structure of +# the code including all documentation. Note that this +# feature is still experimental and incomplete at the +# moment. + +GENERATE_PERLMOD = NO + +# If the PERLMOD_LATEX tag is set to YES Doxygen will generate +# the necessary Makefile rules, Perl scripts and LaTeX code to be able +# to generate PDF and DVI output from the Perl module output. + +PERLMOD_LATEX = NO + +# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be +# nicely formatted so it can be parsed by a human reader. +# This is useful +# if you want to understand what is going on. +# On the other hand, if this +# tag is set to NO the size of the Perl module output will be much smaller +# and Perl will parse it just the same. + +PERLMOD_PRETTY = YES + +# The names of the make variables in the generated doxyrules.make file +# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. +# This is useful so different doxyrules.make files included by the same +# Makefile don't overwrite each other's variables. + +PERLMOD_MAKEVAR_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- + +# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will +# evaluate all C-preprocessor directives found in the sources and include +# files. + +ENABLE_PREPROCESSING = YES + +# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro +# names in the source code. If set to NO (the default) only conditional +# compilation will be performed. Macro expansion can be done in a controlled +# way by setting EXPAND_ONLY_PREDEF to YES. + +MACRO_EXPANSION = YES + +# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES +# then the macro expansion is limited to the macros specified with the +# PREDEFINED and EXPAND_AS_DEFINED tags. + +EXPAND_ONLY_PREDEF = YES + +# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files +# pointed to by INCLUDE_PATH will be searched when a #include is found. + +SEARCH_INCLUDES = YES + +# The INCLUDE_PATH tag can be used to specify one or more directories that +# contain include files that are not input files but should be processed by +# the preprocessor. + +INCLUDE_PATH = + +# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard +# patterns (like *.h and *.hpp) to filter out the header-files in the +# directories. If left blank, the patterns specified with FILE_PATTERNS will +# be used. + +INCLUDE_FILE_PATTERNS = + +# The PREDEFINED tag can be used to specify one or more macro names that +# are defined before the preprocessor is started (similar to the -D option of +# gcc). The argument of the tag is a list of macros of the form: name +# or name=definition (no spaces). If the definition and the = are +# omitted =1 is assumed. To prevent a macro definition from being +# undefined via #undef or recursively expanded use the := operator +# instead of the = operator. + +PREDEFINED = Q_PROPERTY(x)= + +# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then +# this tag can be used to specify a list of macro names that should be expanded. +# The macro definition that is found in the sources will be used. +# Use the PREDEFINED tag if you want to use a different macro definition that +# overrules the definition found in the source code. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all references to function-like macros +# that are alone on a line, have an all uppercase name, and do not end with a +# semicolon, because these will confuse the parser if not removed. + +SKIP_FUNCTION_MACROS = YES + +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- + +# The TAGFILES option can be used to specify one or more tagfiles. For each +# tag file the location of the external documentation should be added. The +# format of a tag file without this location is as follows: +# +# TAGFILES = file1 file2 ... +# Adding location for the tag files is done as follows: +# +# TAGFILES = file1=loc1 "file2 = loc2" ... +# where "loc1" and "loc2" can be relative or absolute paths +# or URLs. Note that each tag file must have a unique name (where the name does +# NOT include the path). If a tag file is not located in the directory in which +# doxygen is run, you must also specify the path to the tagfile here. + +TAGFILES = + +# When a file name is specified after GENERATE_TAGFILE, doxygen will create +# a tag file that is based on the input files it reads. + +GENERATE_TAGFILE = + +# If the ALLEXTERNALS tag is set to YES all external classes will be listed +# in the class index. If set to NO only the inherited external classes +# will be listed. + +ALLEXTERNALS = NO + +# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed +# in the modules index. If set to NO, only the current project's groups will +# be listed. + +EXTERNAL_GROUPS = YES + +# The PERL_PATH should be the absolute path and name of the perl script +# interpreter (i.e. the result of `which perl'). + +PERL_PATH = /usr/bin/perl + +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- + +# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will +# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base +# or super classes. Setting the tag to NO turns the diagrams off. Note that +# this option also works with HAVE_DOT disabled, but it is recommended to +# install and use dot, since it yields more powerful graphs. + +CLASS_DIAGRAMS = YES + +# You can define message sequence charts within doxygen comments using the \msc +# command. Doxygen will then run the mscgen tool (see +# http://www.mcternan.me.uk/mscgen/) to produce the chart and insert it in the +# documentation. The MSCGEN_PATH tag allows you to specify the directory where +# the mscgen tool resides. If left empty the tool is assumed to be found in the +# default search path. + +MSCGEN_PATH = + +# If set to YES, the inheritance and collaboration graphs will hide +# inheritance and usage relations if the target is undocumented +# or is not a class. + +HIDE_UNDOC_RELATIONS = YES + +# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is +# available from the path. This tool is part of Graphviz, a graph visualization +# toolkit from AT&T and Lucent Bell Labs. The other options in this section +# have no effect if this option is set to NO (the default) + +HAVE_DOT = YES + +# The DOT_NUM_THREADS specifies the number of dot invocations doxygen is +# allowed to run in parallel. When set to 0 (the default) doxygen will +# base this on the number of processors available in the system. You can set it +# explicitly to a value larger than 0 to get control over the balance +# between CPU load and processing speed. + +DOT_NUM_THREADS = 0 + +# By default doxygen will use the Helvetica font for all dot files that +# doxygen generates. When you want a differently looking font you can specify +# the font name using DOT_FONTNAME. You need to make sure dot is able to find +# the font, which can be done by putting it in a standard location or by setting +# the DOTFONTPATH environment variable or by setting DOT_FONTPATH to the +# directory containing the font. + +DOT_FONTNAME = FreeSans + +# The DOT_FONTSIZE tag can be used to set the size of the font of dot graphs. +# The default size is 10pt. + +DOT_FONTSIZE = 10 + +# By default doxygen will tell dot to use the Helvetica font. +# If you specify a different font using DOT_FONTNAME you can use DOT_FONTPATH to +# set the path where dot can find it. + +DOT_FONTPATH = + +# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect inheritance relations. Setting this tag to YES will force the +# CLASS_DIAGRAMS tag to NO. + +CLASS_GRAPH = YES + +# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for each documented class showing the direct and +# indirect implementation dependencies (inheritance, containment, and +# class references variables) of the class with other documented classes. + +COLLABORATION_GRAPH = NO + +# If the GROUP_GRAPHS and HAVE_DOT tags are set to YES then doxygen +# will generate a graph for groups, showing the direct groups dependencies + +GROUP_GRAPHS = YES + +# If the UML_LOOK tag is set to YES doxygen will generate inheritance and +# collaboration diagrams in a style similar to the OMG's Unified Modeling +# Language. + +UML_LOOK = NO + +# If the UML_LOOK tag is enabled, the fields and methods are shown inside +# the class node. If there are many fields or methods and many nodes the +# graph may become too big to be useful. The UML_LIMIT_NUM_FIELDS +# threshold limits the number of items for each type to make the size more +# managable. Set this to 0 for no limit. Note that the threshold may be +# exceeded by 50% before the limit is enforced. + +UML_LIMIT_NUM_FIELDS = 10 + +# If set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = NO + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT +# tags are set to YES then doxygen will generate a graph for each documented +# file showing the direct and indirect include dependencies of the file with +# other documented files. + +INCLUDE_GRAPH = YES + +# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and +# HAVE_DOT tags are set to YES then doxygen will generate a graph for each +# documented header file showing the documented files that directly or +# indirectly include this file. + +INCLUDED_BY_GRAPH = NO + +# If the CALL_GRAPH and HAVE_DOT options are set to YES then +# doxygen will generate a call dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable call graphs +# for selected functions only using the \callgraph command. + +CALL_GRAPH = NO + +# If the CALLER_GRAPH and HAVE_DOT tags are set to YES then +# doxygen will generate a caller dependency graph for every global function +# or class method. Note that enabling this option will significantly increase +# the time of a run. So in most cases it will be better to enable caller +# graphs for selected functions only using the \callergraph command. + +CALLER_GRAPH = NO + +# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen +# will generate a graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH and HAVE_DOT tags are set to YES +# then doxygen will show the dependencies a directory has on other directories +# in a graphical way. The dependency relations are determined by the #include +# relations between the files in the directories. + +DIRECTORY_GRAPH = YES + +# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images +# generated by dot. Possible values are svg, png, jpg, or gif. +# If left blank png will be used. If you choose svg you need to set +# HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible in IE 9+ (other browsers do not have this requirement). + +DOT_IMAGE_FORMAT = png + +# If DOT_IMAGE_FORMAT is set to svg, then this option can be set to YES to +# enable generation of interactive SVG images that allow zooming and panning. +# Note that this requires a modern browser other than Internet Explorer. +# Tested and working are Firefox, Chrome, Safari, and Opera. For IE 9+ you +# need to set HTML_FILE_EXTENSION to xhtml in order to make the SVG files +# visible. Older versions of IE do not have SVG support. + +INTERACTIVE_SVG = NO + +# The tag DOT_PATH can be used to specify the path where the dot tool can be +# found. If left blank, it is assumed the dot tool can be found in the path. + +DOT_PATH = + +# The DOTFILE_DIRS tag can be used to specify one or more directories that +# contain dot files that are included in the documentation (see the +# \dotfile command). + +DOTFILE_DIRS = + +# The MSCFILE_DIRS tag can be used to specify one or more directories that +# contain msc files that are included in the documentation (see the +# \mscfile command). + +MSCFILE_DIRS = + +# The DOT_GRAPH_MAX_NODES tag can be used to set the maximum number of +# nodes that will be shown in the graph. If the number of nodes in a graph +# becomes larger than this value, doxygen will truncate the graph, which is +# visualized by representing a node as a red box. Note that doxygen if the +# number of direct children of the root node in a graph is already larger than +# DOT_GRAPH_MAX_NODES then the graph will not be shown at all. Also note +# that the size of a graph can be further restricted by MAX_DOT_GRAPH_DEPTH. + +DOT_GRAPH_MAX_NODES = 50 + +# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the +# graphs generated by dot. A depth value of 3 means that only nodes reachable +# from the root by following a path via at most 3 edges will be shown. Nodes +# that lay further from the root node will be omitted. Note that setting this +# option to 1 or 2 may greatly reduce the computation time needed for large +# code bases. Also note that the size of a graph can be further restricted by +# DOT_GRAPH_MAX_NODES. Using a depth of 0 means no depth restriction. + +MAX_DOT_GRAPH_DEPTH = 0 + +# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent +# background. This is disabled by default, because dot on Windows does not +# seem to support this out of the box. Warning: Depending on the platform used, +# enabling this option may lead to badly anti-aliased labels on the edges of +# a graph (i.e. they become hard to read). + +DOT_TRANSPARENT = NO + +# Set the DOT_MULTI_TARGETS tag to YES allow dot to generate multiple output +# files in one run (i.e. multiple -o and -T options on the command line). This +# makes dot run faster, but since only newer versions of dot (>1.8.10) +# support this, this feature is disabled by default. + +DOT_MULTI_TARGETS = NO + +# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will +# generate a legend page explaining the meaning of the various boxes and +# arrows in the dot generated graphs. + +GENERATE_LEGEND = YES + +# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will +# remove the intermediate dot files that are used to generate +# the various graphs. + +DOT_CLEANUP = YES diff --git a/qwt/doc/articles/TODO b/qwt/doc/articles/TODO new file mode 100644 index 000000000..373a767a5 --- /dev/null +++ b/qwt/doc/articles/TODO @@ -0,0 +1,8 @@ +1) Installation guides +2) QwtSeriesData + QAbstractModel +3) Navigation +4) Scales +5) Alive plots + sampling threads +6) Exporting + printing plots +7) Plot Items +8) Raster items diff --git a/qwt/doc/changes.dox b/qwt/doc/changes.dox new file mode 100644 index 000000000..ee13a965f --- /dev/null +++ b/qwt/doc/changes.dox @@ -0,0 +1,306 @@ +/*! +\page qwtchangelog What's new in Qwt 6.1 + +\tableofcontents + +\section ITEMS New plot items + + - QwtPlotBarChart\n + Bar chart, see "examples/distrowatch" + + - QwtPlotMultiBarChart\n + Chart of grouped bars - stacked or aligned side by side. + See "examples/barchart" + + - QwtPlotTradingCurve\n + Candlestick or OHLC charts typically used to describe + price movements over time. See "examples/stockchart" + + - QwtPlotShapeItem\n + A plot item to display rectangles, circles, polygons and all + other type of shapes ( built from intersections or unifications ), + that can be expressed by a QPainterPath. See "examples/itemeditor" + + - QwtPlotLegendItem\n + A legend on the plot canvas. See "examples/legends" + + - QwtPlotZoneItem\n + A horizontal or vertical section + + - QwtPlotTextLabel\n + In opposite to a QwtPlotMarker the text is not aligned to a plot coordinate + but according to the geometry of the canvas ( f.e top/centered for a title ). + See "playground/curvetracker". + + +\section SCALES Scales beyond linear and logarithmic transformations + +QwtScaleTransformation has been replaced by QwtTransform and its derived classes: + + - QwtTransform + - QwtNullTransform + - QwtLogTransform + - QwtPowerTransform + +Individual transformations ( f.e. different scaling for special sections ) +can be implemented by overloading QwtTransform ( see playground/scaleengine ). + +QwtLinearScaleEngine and QwtLogScaleEngine are not limited to +base 10 anymore. + +\subsection DATETIME Datetime scales + +A set of a new classes for displaying datetime values: + + - QwtDate\n + A collection of methods to convert between QDateTime and doubles + + - QwtDateScaleEngine\n + A scale engine that aligns and finds ticks in terms of datetime units. + + - QwtDateScaleDraw\n + A scale draw mapping values to datetime strings. + +Scales for Qt::UTC and Qt::LocalTime are supported. + +\section CONTROLS Redesign of the dial and meter widgets + +Many parts of the class design of the dial and meter widgets were left over +from the 90s ( Qwt 0.2, Qt 1.1 ). + +The derivation tree is simpler and more logical: + + - QwtAbstractScale is a QWidget + - QwtAbstractSlider is a QwtAbstractScale. + ( for sliders without scales QAbstractSlider should be the base class ) + - QwtThermo is also a QwtAbstractScale + - QwtDial, QwtKnob, QwtSlider are derived from QwtAbstractSlider + - QwtCounter is derived from QWidget + +QwtDoubleRange has been removed. + +All classes use the terminology known from QAbstractSlider - as far as possible. +The extended \ref SCALES "system for scales" is completely supported. + +\section OPENGL Basic support for an OpenGL plot canvas + +QwtPlotGLCanvas offers the option to draw plot items using an +OpenGL paint engine ( QPaintEngine::OpenGL/OpenGL2 ), +This is not what could be implemented with native OpenGL, +but it offers hardware acceleration in environments, +where the raster paint engine is the only option. +( f.e Qt4/Windows, or Qt5 on all platforms ). + +QwtPlotGLCanvas is in an experimental state and is not recommended for average +use cases. + +\section LEGEND A new system for plot legends + +QwtLegend has been decoupled from QwtPlot and can be replaced by +application specific implementations. Plot items and the legend +exchange the information using QwtLegendData. + +QwtPlotLegendItem is a new plot item that displays a legend on the +plot canvas. + +The following examples demonstrate how to use the new system: + + - examples/legends\n + shows how to use the new legend system + - examples/stockchart\n + implementats a QTreeView with checkable items as legend + +\section GRAPHIC Off-screen paint device for vector graphics + +QwtGraphic can be copied like QImage or QPixmap but is scalable like QSvgGenerator. +It is implemented as a record/replay paint device like QPicture. + +\section OVERLAY QwtWidgetOverlay + +QwtWidgetOverlay is a base class for implementing widget overlays - primarily +used for use cases like graphical editors or running cursors for the plot canvas. + +The following examples show how to use overlays: + + - examples/itemeditor + - examples/curvetracker + +QwtPicker ( -> QwtPlotPicker, QwtPlotZoomer ) internally uses +QwtWidgetOverlay now, making it easier to implement individual rubber bands. + +\section SYMBOL QwtSymbol + +New symbol types have been introduced: + + - QwtSymbol::Path + - QwtSymbol::Pixmap + - QwtSymbol::Graphic + - QwtSymbol::SvgDocument + +QwtSymbol autodetect the most performant paint strategy for a paint device +what is in most situations using a QPixmap cache. + +QwtSymbol::setPinPoint() allows to align the symbol individually, f.e to the position +of the peak of an arrow. + +\section PLOTCURVE QwtPlotCurve + +Some optimizations that got lost with introducing the floating point +based render code with Qwt 6.0 have been reenabled. Other specific optimizations +have been added. + +New paint attributes: + + - QwtPlotCurve::FilterPoints + - QwtPlotCurve::MinimizeMemory + - QwtPlotCurve::ImageBuffer + +QwtPlotCurve::CacheSymbols has been removed, as caching is implemented +in QwtSymbol now. + +QwtPlotCurve::drawLines(), QwtPlotCurve::drawDots(), +QwtPlotCurve::drawSteps() and QwtPlotCurve::drawSticks() are virtual now. + +\section PLOT QwtPlot + +A footer similar to a title has been added. + +QwtPlot::ExternalLegend is obsolete with the +new \ref LEGEND "system for legends". The signals +QwtPlot::legendClicked(), QwtPlot::legendChecked() have been +removed. Applications need to connect to QwtLegend::clicked() +and QwtLegend::checked(). + +To support using an OpenGL canvas QwtPlot::setCanvas has been added. +This has 2 important implications for the application code: + + - QwtPlot::canvas() returns QWidget and needs to be casted, when + using methods of QwtPlotCanvas. + - QwtPlotCanvas can be created and assigned in application code, + what makes it possible to derive and overload methods. + +The initialization of a plot canvas with Qwt 6.1 will probably look like +this: + +\code + QwtPlotCanvas* canvas = new QwtPlotCanvas(); + canvas->setXY( ... ); + ... + + plot->setCanvas( canvas ); +\endcode + +To have a consistent API QwtPlot::setPlotLayout() has been added, + + +\section OTHER Other + +\subsection SCALEDIV QwtScaleDiv + +The following methods have been added: + + - QwtScaleDiv::inverted() + - QwtScaleDiv::bounded() + - QwtScaleDiv::isEmpty() + - QwtScaleDiv::isIncreasing() + - QDebug operator + +The following methods have been removed: + + - QwtScaleDiv::isValid(), QwtScaleDiv::invalidate()\n + The valid state was left over from early Qwt versions indicating + a state of the autoscaler. + +\subsection SCALEENGINE QwtScaleEngine + +The following methods have been added: + + - QwtScaleEngine::setBase() + - QwtScaleEngine::setTransformation() + +\subsection PLOTLAYOUT QwtPlotLayout + +The following flags have been added: + + - QwtPlotLayout::IgnoreTitle + - QwtPlotLayout::IgnoreFooter + - QwtPlotLayout::setAlignCanvasToScale() + +\subsection PLOTCANVAS QwtPlotCanvas + +Rounded borders ( like with style sheets ) can configured +using QwtPlotCanvas::setBorderRadius(); + +\subsection OTHERS Other changes + + - QwtWeedingCurveFitter\n + QwtWeedingCurveFitter::setChunkSize() has been added, with drastic + performance improvements for huge sets of points. + + - QwtPlotRenderer + The frame of the plot canvas can be rendered, what makes the result + even closer to WYSWYG. QwtPlotRenderer::exportTo() has been added. + + - QwtSystemClock + For Qt >= 4.9 QwtSystemClock uses QElapsedTimer internally. As it doesn't + support a similar feature, QwtSystemClock::precision() has been removed. + + - QwtPlotAbstractSeriesItem\n + QwtPlotAbstractSeriesItem has been split into QwtPlotSeriesItem + and QwtPlotAbstractSeriesStore. + + - QwtText\n + A metatype declaration has been added, so that QwtText can be used + with QVariant. + + - QwtEventPattern, QwtPanner, QwtMagnifier\n + Forgotten Qt3 leftovers have been fixed: int -> Qt::KeyboardModifiers + + - QPen Qt5/Qt4 incompatibility + The default pen width for Qt5 is 1, what makes it a non cosmetic. + To hide this nasty incompatibility several setPen() methods have been added + the build pens with a width 0. See QPen::isCosmetic(), + + - qwtUpperSampleIndex()\n + A binary search algorithm for sorted samples + + - QwtMatrixRasterData + QwtMatrixRasterData::setValue() has been added + + - QwtPicker + QwtPicker::rubberBandWidget(), QwtPicker::trackerWidget() have been replaced by + QwtPicker::rubberBandOverlay(), QwtPicker::trackerOverlay(). + QwtPicker::rubberBandMask() has been added. QwtPicker::pickRect() has been + replaced by QwtPicker::pickArea() + + - QwtPlotItem + QwtPlotItem::ItemInterest has been added. QwtPlotItem::setRenderThreadCount() + was shifted from QwtPlotRasterItem. + + - ... + +\section CLASSES Summary of the new classes + + - QwtAbstractLegend + - QwtDate + - QwtDateScaleDraw + - QwtDateScaleEngine + - QwtGraphic + - QwtLegendData + - QwtLegendLabel + - QwtPainterCommand + - QwtPixelMatrix + - QwtPlotAbstractBarChart + - QwtPlotBarChart + - QwtPlotMultiBarChart + - QwtPlotGLCanvas + - QwtPlotLegendItem + - QwtPlotShapeItem + - QwtPlotTextLabel + - QwtPlotTradingCurve + - QwtPlotZoneItem + - QwtPointData + - QwtPointMapper + - QwtTransform, QwtNullTransform, QwtLogTransform, QwtPowerTransform + - QwtWidgetOverlay +*/ diff --git a/qwt/doc/doc.pro b/qwt/doc/doc.pro new file mode 100644 index 000000000..538d86dc0 --- /dev/null +++ b/qwt/doc/doc.pro @@ -0,0 +1,22 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +# qmake project file for installing the documentation + +QWT_ROOT = $${PWD}/.. +include( $${QWT_ROOT}/qwtconfig.pri ) + +TEMPLATE = subdirs + +doc.files = $${QWT_ROOT}/doc/html +unix:doc.files += $${QWT_ROOT}/doc/man +doc.path = $${QWT_INSTALL_DOCS} + +INSTALLS = doc + diff --git a/qwt/doc/images/analogclock.png b/qwt/doc/images/analogclock.png new file mode 100644 index 0000000000000000000000000000000000000000..a4c5cc21a1a1e2af561a0574509bd5da26b37f05 GIT binary patch literal 12649 zcmZ8|c{J2-{I8@?wlo+TYu1u=vQw53S;rEx?@N}#*yBr9_MYn6r) z{Ji-d)ki_WL!qsqY7&&Yfen6PHI6#lnrXK6AxaaK%i&*RWZtlAGCQQv8EGJpuYXX9 z_IqDrIaX#{Wq2ba?=k7?Vk{b|~T?y+^**{#{l)4isZywJ_2u~%j`@t&T&%*|u;)iv1=ORUUN z;#>Wkoz4d(dWR0>L|j#zdh1d?0|sT4^^_>U}NqsH(Z>PSc3 zTzM^)x33oGj&MKq|M+0#0yOrC0`~QdB+1<9^GT9d*rjL(qn6$*@xk?^=?G6?!BSo3 zlPFVD0qq<8q4*$|N(GSwUj-~&?|Q~}Q8oBwZz&{hC@8*8w69ili2Qg?f({8`LG&z% z5#!lQ^h=065=oua&b`t1v7>GJBHkEWtBtr4Q6kAnN}lja;riI>V>xrQfQ{qd1S;hb zI6XC3>|1B(#W0E>n#s>l!mTfoTo;npP@gOf1pV`es2N!h+JYI6x7j=W9|--)`K@Pg zIPQ?Niu_0|P^e2?XKp-?m_+Fj`#;^bb^7EOM_70)C!Ame5#UO7b=|s|=3c5FYY$_f zmUML6QDOT-b>1(IfQ)XqVoMA&BEq2~C1}pBEYkb0Umr%l;Mb1g(GAVk=SCSp5D@fN zqfb-QOTK#UYYERRs$%W`GdB*c6wB;e@^#0cbp=gLZ+ZA7e(^UH>VD$%Gg**5?7x6n zZ&`vwr?;)(Fx=}jInt;n;#Rt|MMHBl2QPm@ArB34Nb;>6$|0%a+O@5ZSA-LO1kaJS z7ol`FXf?`W?Mv|+C()Op@6nwT!pT|+N4kGk~*Y3>?4G)O*K}t4F1VKFC zgVq{RMGN5^=V?41U&!8PC-MI2(Vl<~S`Irn;Zpq}+U3`W@q1sP(t1hV0#HbzZZ`r={Fde}}Yu*vaV!pPOItu5cyO=mpvzS)9 zIc<-N`#j#WV&fDlFYxTyv-#s?*&f2g#6(_R-VfPly8a#hFAr^-E6GY*zDdHZ!Nhaj-SNZpZ={nYc47(8XHg*?Nfj;^!<8f z52YiFjgPBJnPeMlHe6j!<9*5b&LryOP6In1zb2|yH$7&SUm@?=%H zW2V_-Qrf_=F-1pRZ1*h?9g{D})F%3)P`8K30v;w8sVL8-c9Rork14s-!s0&X^irXn z-7yVR_d1>{Ug_{oYvmBV-{zaul10_yu99eNnNH-LJKEI zg%&zb^<~=CpOe=h5m_HSdUQVWWAtbR9v`w+Rx*0$j&#o-;(sqP4$sTdJu572ZdOQV zHhp;CPO)PI9wE#WSy$mo(NF%mnwy{PbmD&ZQdx1PEFEEMUnrx;Ny_5CE|@_|$i=D^jKa$f8l#cOr%#_2>k#|vX(KIg%P4DC zs7vjLM}f$D1>QpU6vM}`$*C#11?U$Xth(a8(8QaAK8wrc`hJTvApx0v<5N@S%j9M0 z+-(6~J05Nj>Y~Gx_|MG05Hw9zm39*5?-jo;UO_t#Y+kzJCaKt}R+dpH+^5ExKa}|n z9vT`dME6oRAlxN=-+*BC7?Mr(u)ea2r!g|vlFXhzWCqUldFV6*B7EPulI8V!hy+x0 zkVoWfjsIpOjZM`g+GU9{X84Ob^bVw3y{}GTVClv}($Gqd?+rH&!$J1tCdbOnXw4GR z`N+E+TRGj2EIf=Hl6du`QyYaXae5#8q+Y*2A54R|cj;tF>ej7W!A(p-{L83Tr6E~3 zd~xZA@u%8fV9U3f*DJ5zOM%qki-%VHT4t|bzrNLWw3%reZc%?EMMoxapG|E91p50! zZq3kNR9_6JW=OKn`gBWm(2|vgw&|IIe99wl@4>!T#D$VOC##=gE?-eQ+w(cwTh8%e z-DoTRVF0QRxQSTwuuy*3P7(J>X;x!Hg@J>G3~M57l;QKxr4m=!)Mz`8f|?phaWnl4 z$L^Glj*Z57n=6m({W@02v;IWA$H#M_TP%m0ASoPoFu22$Q%b%2E2>!j@aTK8%kCZ? zH+DRSJDyR4J(ukAZae}v7h8UtIMm=Z^UYhf>wRP6{&0ahqD^seJg41OkLcmyacMhw zB?M6#d9i!A1ndYTg28%n*EKKrVv4NKT&}olGo8D;`}BukY1kcdzjQHv)|plKPj1Zf z{>3+g+XVw_YimZ%T%32pkH^Z4AqYu2zZGmC`VIY|^XYNSz(Ad-(AEdC{+CWA)Y{lK zsat1M0^Z8AqqxZVQ(6oFUT#Cnh{N(!$nY-oC|vxZ(;VLrA!J)%;5EsO_= z3S!_U%CGiKO-)%@nS9#w8@-)|KhU=E%%_T>J%@mJ(7K6};8^i*bz zKQho4*VfjiE2#5h`v5eX??D@1-xYi5PE=U8=o8`E=^99Wp~)&+M(QDzBhMZ)9gJke zZgrDd8*JEfIYBf&$EK>WeiIjyl9B=v*32f@d26oK+2}UUbq)>=wp-2oX%Mr zi^pl#@1^&=nc0|Y3G}hp*wK{Hij9l4e{Usbf%ZH5EuDAs6EsJf3mTg(?V4iUdOh`c zo7)G~qFi8DT1W09uME9vmS1(qQ;&Kpc#)dg?}JO1a?7-b5oCOL*iJ_}b!=>GVr=Yr zVJZH8M1*ZwPR2Q>^Db}&+pMEE)-G%CxEB&#T&X2|H=qxg-oEupDD=Q^B%`en@5k=G z$yORe&90D_GwrTxD<255 z5}W)GOcu~icP@*%sLpDq!vR;2h_Y6xZE#q{6P5Ji_(G;UPAxw93% z57$ggUwNW?CHm}QqYqDl)?Anp`AWFZi*`QD zbuT;mhkZAD2{C*1vA`<@dV<7|r2;HvOj`Z+?c1Qd{F<)&U0qk_HzE4SqcJ(A$!}!@ zUhIzNr(GG!zlHJ>ys(E94AFAsc9zJmCQD9@=nlBvxMlgt#8@*eSHC29%NYsT2p$x1 zfAlCWWU9H6k&fVR5TT{VQ)8h_yqe3O6_T(zK+qb7g(F zOgghxFK-n`Gct$z8te%H!pg zDUy&R$=Z1-%Y4Y0+qsXTYa^hWwC&%&^0{f7pObb}Yh@6dqknxg(mt#G?5O=t*un1^ zpLQ<`S=l0xWtHuxLjX!vu8+9=Y28nG)r_Fw{apJCl&*)CmS+pxr@ZYbSbqv10tvWF znwIV%K7NuVyD#)^_Pelmz5rxillAuW@oE3NR!&V#ef0N+$cbWVg>|U>Pzy+ef7x3% z;^x0L3CY^q+l%80`MFkJj^h2Aynra)zjaAidbLo&Ryp^GzVfwPj+^Ze5hq7 z)n0tuA1|@qq*gM7oYRcB(mO$;>=p<&#nt5yHriijEo>H zF0=S9)b>3HRWilng%f1tK43e*beFfo+QTmXHryQ={28k7v+B!3g;@%j7-b#n(V^QCy1p*jtmbQCd`q3=;EBW zU$ibtZS`XnQw!ZBOcDzB_Vm*tWp0hYlardY+JO;DqA zZF1#jf#`&5Uos9-#iG9k9KO}_A$|?X#@~QCCed3@#>L*S0}J6r0$vVd=ys|sxqUQM zON`UoJUAH##W$Vb5iFMr_Dg(RP~G`Lw!;09CWGO+8GYAqi+6ZaG)pyWZN*f%#jqQS zT`sos^Hr6NPn9*xK_~U|uC-Y~N_o?DD_?5qI`^)|zvquPZue}?z^!W+)gTC3F^V}K zs>2TNe<}YDYvpnf^xQ7~raUZ#_C)*92V#H%`8i?1s@^S)U%UEJw?w_6+ncfE8*DtY z8E<+GeumV#RS+%k)?y_Bedt?e5Ed01?ev6SPe6Bb8Q?IlfznVZDkv~=hnHv3YbE2{ z-(=$4Lq45UR-i}oY=+qA2%^@_YE0=6#J{k3+uq(tvk&wM0OCkp$v4d^UpmJ)bJmKA zB!D8w&9<<)>KLFbbPZ~4D8znuxn2j!NgAKhv<^N-vh@WByVzW!F5^T~ z<~xQ2gx2SYFArE!@bVwr)Hafm-=&;RGOxDlm~7j7M}JLb7_N>?!mmM%p^wt}5k+Yo z9g(1w?)GOB4_n>CM^MMU9*0$ebY6)m!l#G{|&+du7?K zDT&8Y>;{4vwqe`T>s8zp@1Ht}o*V>q`zGDGm7zRw69Ytws!6uQPT_=4&@%kj*;(u= z;)BiBQl@QNFnL+tZ&^4Ymg!c4g4**!rl8BQ_Mb_TNQgdG;Sdm$XhH$yhgVUKha)Ll z%@*X!dwYXXO~i=_hS;l)75MvdFr#sQv*+r^VMN@Whqa5$+D2{|76d)-C0^ZM#g*$d z<|gQ3&o2VrttyB#DCco4|k;m0Yd`8N~np??kvx2r8o_?grB58pP<1)ZJjOU)+Zk0tj> zux<<8MMOK1Az=ASab`mqKB^{?DW=9A+V%Z=77GW7v_dn|tkPvf`MoYyjTZ@-4(dvNEbd?HRjlrOACQ?u5%ekV7ZAv@?jxenwB%p%Lj)};<9wsOrLu(up#m0 z%8Xl%M!UbtuHO%DKLa{7z=y_=C9qW?r-v&gPt$&gShR-hKHZ;nh5iH(6x=OdgSp|7 z3lvvS;C?@pJ-N%tqT+qe_E>#=y(a+S+dn#E?fw1zmG-~+l^B$)T>|JrWL8g0w$LB! zJtHGy#8&7$IY_A`VB;(3;sCK=2rZ>S0JYRTnq-vdf+vGKV-w4rCldEZ?g`z~jD1jH zzThtfANZ*tlO*|S@QNA+cUi5_SE$>V{QVhY+u7;S*+J@=8?@`6ve15Zid7869X^!E zUzT>C)^DG0cj!#;dAe>{C@jbv|4)f;nDNdr#3r(d+q8uRC$Q#%%pn@w(W`56zN|-_ zKRbO&vrtfwmX<~XC_I~eg*;YYPdZ)uoMQq`1nmrs1;AB;&RQ0(2XZxD?uhGIhaKP)fV$EV9=FV5a4~AeAMVw)=6(#otV|2FP&viDsI(&1cXf5q<~M+RYL;Xeb~}|)B-Wk-!QO&Ob4}iEZ1~%wd~J@=7;}XH5B#-XurpEWtCWB z8?ssq*;fqSaY{IQz!Yn5Vs>=&S1c&`1)X3*k6pDYx){RyXXJ_UC_IQEnTZ*JEi{yv zGPj+t57}LcEqDToNdNHgV*>-mtSKP=o}Qlm7z-LY359WS&9z6IT~Ad?|LczCZ>DvT z(x#J-QJ}tfG5k*vcgHqR0R?Arjz9=tFLLg`%vC_G_1>%kdcXwgmA%;5I*@GkUpqF4~*Ll3lgm356nF;0Yz@cV-_M{+>mzQ2FiIFWchU9Z&( z5#Y41JTX3gvfh5W4&)_V%bKj74eF-yQEr#=Wp3Y| z4I(8QDDRr!^z!wArcSLGc-rnHH9aZ&A5%b!a=b=v;m_FEi>g|WVe8vLJHAE8ObXu5 z9vlhX&MAK4RzW?5h|HhO2~56CK}g8a5%pl+hEnLB=zFR(Eq?L9p_f2w2bT4Qce6+1qT{!iC2pu&l=Sy{4+qr2wrt;oAYojBlY^tw7CSj8UUzfCU|GH2MXx z5hDTZc9^c!DaYg8F%L{%k>v6MY1Q~RlHXc2jc)8o(9v^;tL8*X;@lJL-7q+RYiT;{ z3KmL=jEqc48Qk0ygZk1lGO`J&C<8_VtT9(mG@^K5fUTLe|219va07$O$;s&nl?PD# z;$_cfVlb{I}#h}@c3Rak4JP=w{$L!sB8*VjIwnR5vXuidl0J!5aXOf5{-wK6tI)TDWY zxle=~bOVu?latdLRMgqo*4Ea}`Jj}QL!iIFeKo|v(*wi${$4aQUJF@?~*dSIx2F zahdW=Jqd50T;B}}^KMLB363MsUU<&Kg2Az)o$ba4f2!b3cgNNAvxJV0!r=vu^r_Tq0H;#`y3#O+SJD* zE5dOHQ3JY!-0E}FHOpr<-+Eh1lWS{KX_Bp1ad>>EMlCv+-x4Q-09u%`VyQmbIZ>fo zqZ2pp5T4`Pl^!PoB(Y{xlEMH&&C~Nza`19km>g}k^v@TZJM|)BVwpE%i1DJ~CBWHr zaClHsxlZELQ9uM>ll4?3kFbTpDUI0r!~>H&keGqS=WreD8L<`+eB*8{^^^*ou*jt_tCqnBtWH?4)Mqcu%E8E3o_ z&U+@&b{7ikyRo`QGyS@+sXy&_JxA?)P75$(D_xbi^2Y)mei-3fT%YNGj4v-A`RUdz zL8ra(*y4vN2l1PVhi{Q-c_9ae0Fe1)Xk<++=JvStJC*BU_t5>vqcsG-c~ z3WagzmVwB$<-5$k94udVP4_#wVFKh}Z@4t%VERaPpMJwx*lxXIV2+yI=7c*YMS~A? zZohd(ML28eQ6i(6>iP5MMSgvUqC7AzWiU$T(@UQL z5&}ZuVX{vcVTIfQjFtU=*289sOf-O|Ee#E2)t$wOB?s4)3!nDDilX_*2n+*R1 zFu{y}LX6Lc;^HMBoRdRZK`O`Oh~@(2gqEH*Y8bGAkRV4>p}rIg>TU_$@TaP1=SJ_@ zzaP&t0*VNE@O8cU6etubrw63`h|{K91BC$Rg04FsvVJRXMHLR$6Sw&gL|z_<;`w*Y zt@#T>;17xBmX^5KSfimMLm-wfWpzWA0$Sn~EYJe8-ZlrT1`*A69h6eeAwVc`&41d^ z(xRB!Sl#n-y3Td}NjImLm)BzG>d&7C3ycw2^DUwe>r3(YOP={SckZWukP#6PYwmvK z8yYkKVfp6n4`NpiCng?58%9K&T{Tdui8wn|J5rD_W=`x(;Z&R$8L4@^pr&OJak!L8 zR#CzNg|UY}MY9MLPvcEisYnIw6Q++6NlGcn3JMDUgt&h#x2z;@ZFRM0(%H+mq^fEQ z*fG^{eG#WC5zDgdrNzaaRu)Gzh$J*&7JP)5&R_!zNUZi@;S zd{5t9A`66D;B4twzYJFjZ|8P6g*swzp&=oo-yr@!W3!z zzgJ-U(JVOpKatd2T2K}-I4PPCm4Aj{2j)VdY=edr&nPGL6^ zm{Ys$XNBzMlsa5Es>4tI0x#g~bn9&Jrz!A~tp+X!80$-0-Cl(~lFEk1p{Z1X^YCuJ zh+o@4<#3TP;y^v9O*kQ9*DT`Ired{7C~%s*0XDqH75o`CsvQ?|nJvAC*Lw2k=!gV( zRuy(El7g%)T51n9_7-Ub=2_$XKk#z8XM4J28xRmM=i7H{wVgUgYUKz3YQG`nO!tT{ zWF?D6eI%e_AedLX4~>^fXrk;IdeZreq1P__L(cC5w}ASQ-m`dF{KIdMx5d*mIXkXB z=>j^LVzWSLR|?ziWbA)M>&c$tj{I@BS8a6pc1B%5 z)QNwTTh0HrgIhNmW^iHg-K0pU5Pw~2QW8INd{S?)?iG&zI$D}s2u?#u8hroSqIKXb z2yfs{56I3RgN3V{1Wf7PVHLNbqpmKt0ytMQ_NpFhvMiVi7)XigTf%s*ec9Td2ag9L z9fRXk4)O1uP4&V_M&7jpt6p6A`0*q6>F>C)Ltj$%LoBSj$x4hohFt4tNv2nG17%0F zk2#eL#9K}ntbgpG9_Jr3)A4Ujj!7lC9~!e4`Z2c?Npc?k))5pISsr`h9-p zg}eLEB*jg*mQg3_fyO2pd^_*fI2 z&4Q(8SHrSAgseZ%-lHUAM0!M#sT#)G z>^?0IvGm^qTCQ<$d^x2`D9lw3m0^xw^d0CQqupgj|qX|H5uPN$klL=P+Cg#`_yk$uu=Jd%Z?F0J{O>Ns^Xe6asr>{ z+}xac90M@-4i|wi3t$`6v2g~gzxVJ7kXuIz5pMMclkkl{tHc^}QSo!#o}kJnbdy}R zG73w5v*tYiFL&2@=UES1bLBsy_c3tVmUXRbDQi5ZrRtdf{{0KIYN-JPCP{Q0e7@6x zU!00JT2kzgY7}VEv=@2|unMT-gY8in924ySbAy}Z-GGCoUvd?l#v4tg`g3iqS0|sr zsY;(9A*v%wPY^D+;N{~3Mrv3@ZFpr|{+@eE*%bsV-;Iq80Z(lnCZ0|?``YD1@iwiJ zShc_4b8w`BRvF)G7faut!v&RE5UV-&Xr4n{ohe+$7U#u zOr3Hv+)wYQckV+kFS}?N1Oqyp5Vp@(=QFRO$|RiN;qBeF*}R@GPl3VNcQ9@L0{s?r zUGkE*%?IRW=5x&=^baN=B3is&pR9^|2KtH&_A3*Mo)Xuc$KNSnRn{#H2lVC*p6oT# zvG#X+Ds{1H)Ya7y zE#eTJ(4|*qz?`sjQm+o$c1R(8wBIY(yoIrqn%`XC_&<1Qw6Mwa@Q>Q=X9i#lzY7RTC<8@dRDK8 zSmRxQR&uniCa_`U{RCkQY=f_Qa}=(qp?`&8a0M_ME+2d28-Q$zmirB16~Rn^$?%Vl zhbF*~(Qwd)nN05bW@*;;#DI{nlh8sVc%nl>LjiT&fd=KjP%6L53t@;GP!EDiPxmR^ z$OL0VV0>nW$rlRVm1yWFaki!bMyPll6t(keXFJXKfDjI5s|*vEqIj!!Cw~8ad}Y(- z#j#ZqkUA^N$|NTZW$4t=OS3=#4)EB*C^W)%3^StmckXvZo#eZolBF3i5Du>-ik6|KH0n|8! zwf^nfWasItLi&pnA9LlN{{5pP-uh=$M;xmZTpK;`SOE(rzP9(<_FJjB!>ZfbPA%vE zJ_wiuI|yj9s|C80$?@CdjCmZ5BgW((CAh)kqwg3wUEEJ5dNZ}Rqi2gb{iN%8D zg~GrLy~f2gSYxG@0x`wX6qt(&(ohm4T$0r)REp#}i!*IP{{C5|gje$&u0))=w$Gn& zpKWsk)6ES$2GE)EmoM{n&cf2A>8=9^t{mDh?DuokP^q1mL|lzFEJdV=_UYGAayeh_ zl7rGQXV7+0I>O>LKm}AQFqH4#c%^x%R+N!}0Ss)^iatuocgMs^x>(5U+hyool1nMC zs%p&A{RbK&fdFvwFnT+A?V zvDSPiS1oF3X67oH4cChUD-?dY&+@9t`0tht@?}SdDyXttfb=C4zVy4$bq!c1={jP; zV921IjNK~5hX4U3vl&rV3Whp7J)NLkuunu3r_^nb(Y<<@Qj?-W+U>(In<_Rd19EUe z;TNg$+8=D~2b~7tI~l$=sb#rRyGi83{yX7Qm#(uWCMP9*CT-WQ4Gg>-pyv^HOXz(!w=+uNG| zH+h;_tqgzxprYF8SF|ZggzrN-!6y?t!H8(~=OiQJ6i~R>W3Mp8Vf9_X5F-M~no;JC zsa%&R6n}Mx<5JAW`TyeR2rs`eAR$ZpNA?-FeVNB-^IiGUzC6i^w2o(E}uw%YPdPgf;Ti~r>0KOhuA$!fYohv}ha0^|45M1{ygUN4xG zUh<6;9+=7Ii-yJmv>C6V2I+KzKeTA3x}lDx=zgM-&zL(xd;5~Jna*Z9>$!wND={{? z%+HOH@-lGDH~P&`B`)!%zO>uUYk~UD@%RG6=gtwLnRuC2>*#1h3cjDYR z?jsWmvvdUkeizsyk;;@%l+BgukEwRFzRE7eS+R9NqZDVCxF1A-6);zqzTT6Q0q*I7!r)RE}7M!Zz#q#Mz8>?S0h z4xNK>?R2VU2;zytDs!nPiZa&zqmyL!6Ys{ECEt!r*@eanLaJpF36wQxtFrIHR}uQq z?h6?}F)9?U6}=qAL!-9D5Q1}3D9w0{_keRGcRDByf$}8fi}7@qN-F%KUWjyP+!3=- zx+FRG`*P%2N@`(F>TSJex6RY};|t}k2JqxRSfi5Yl2G|iqOF^>Ajcrn7So>5`eN## zgT;clq~jZ(cViOjg3}!+(bj6jAO1hj1kL#6&nwHl&=tI(nzdKRud9w?mgoW_rrEku zbR>U;;Ji3W{#9X?Hl_Poc;)D-OuoZEJ!hP(uJTzeSB zX$6mF>Z=7n>~n%{@BzXtb{hUrSJ0i7<8Ak4!teWx=F4A_3`^KY5q#0+=nKy-A+I?I z4n&X{+#tEsat};dF8#idz^6S*XZ9u=%;8I8a6po;g+3?AO;GV4F~z07G!wA6^ZUl= zZG#D0OB-)!tPI*+>E~b?`&<3Xr1JE;KhvBoq}pQ7OQmT=9e@6oKF1Eb_uE_hBTK)N z_MEkVL%B4IAwy#nGhYe&(mP*5S69;i`(=c~vkyc54pJTeZG*2+P-r9cH7eEYqW%Xo C;X;@I literal 0 HcmV?d00001 diff --git a/qwt/doc/images/cpuplot.png b/qwt/doc/images/cpuplot.png new file mode 100644 index 0000000000000000000000000000000000000000..0f1bfc3628394b85561dd445a7a3319a633c31f6 GIT binary patch literal 36517 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 literal 0 HcmV?d00001 diff --git a/qwt/doc/images/dials1.png b/qwt/doc/images/dials1.png new file mode 100644 index 0000000000000000000000000000000000000000..690c663d627b0a3ede37c7ef1ea56eb0af86c7ac GIT binary patch literal 72717 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 F{{?03=;r_c literal 0 HcmV?d00001 diff --git a/qwt/doc/images/dials2.png b/qwt/doc/images/dials2.png new file mode 100644 index 0000000000000000000000000000000000000000..59d1a611dca7bd174bab5add048f694b33f22707 GIT binary patch literal 28153 zcmc$`byQSe6hAr$C?ICpN(fa*|ITlRkz(AWsldZ{I^850DTD3I@gl@Eyau&%qD~ z6$J4XuI!q!o9gDNEM9+JI5_HYCWM9|BSj?|rTtV~VxOwSC93SlgVxYCa$`rRc$C_+ z(%FxvEIMT53h;Q8MMX(dqY;#6?az3Ap`$&(q{f=zN90dcDmVEMk|oItYVj=E?)!6mw37oF|9xwjb=*qn z{{N6{3YD^%;Cs@oq60i%#K4!BsNA#nugZjB|Ns6&KRfDC?4I~XZMel7KMVK-MGCFT zlNcJ$t}*sL^jF}+HVlIzVF)OiMtt@u7Ci!;2u8DzkrXC8Zl(KKpCnFNyj`+=^;#_< za$#-Lv#|m`SgKkW_maJ{f=Pf32M%q$RHE;J{LdFXa<89JEl?)w`m`euz_N^mQ;Srg zO85-5C+o6H(J7_7duXIlqgjlEeOZ7=zYs%51i$`9yAqyeR-?i|)K?*`Hd5EMG1ip* z=gil4aQLPH0*N%g17P_wy5rRhTFF0gbt!Zl9{&;y?QCJ!Y>#H~yrtPxZ+S-CZ zk_SwxH9h=v>EW4vIqlDtV}j*&()2QS&@GMa@kPh9Zp< zzg1r8uTp+hE#xM__M`ebV4_Hvy`UcA@r|m0YURoIo}3XQj<42n(z*VkbNnB9BgQPM zNyBL`t+CMYMVH+uAXEqtM}spjdRgO)6OCt1P8|Ie)YOPm=Sdv?*IO8k8W|*mxA+1* zTD|$>rjtL$*Px0|oa^#Rx5JuHdc@e?)To_VIqI=;l-+}g9I;U|4#feU5rcC~)Kxxn zF4E7xpkKy=H`^+h`YIq0jrict-4~^+0Xez3`eCxPO3T|EOBsGSeqpbdKg2{ItI;*l z5)4@#3r3a>O3?f%>~JIr04q!Vt33J#6JPC3Sk;hlRY8+*q)fW*FvD)1cM7b;( zWrt2=gdJQ#%iam$2vR}8phlwj`|;0b_C2k7hGY<5=fW>d;43Z~?d#{JyQVN?7}E=Y z95e@6JW{hq#t{{#m?_Q(Ow_0@EZ{_=LE1^mkmSB>(?_dgMh1M0#1XA@J?jn!4F`Fa&3tZ<^@Rnn0YHIK@P6#8u zVibGeV~#A>Q6mJRr?9lS_xQTPxUb*D0czSwVkn1NW`$tf?DU&Fi!0{g$R8%`E!1maTaG<(*Kn1xvz2C?*fkT9mJ%LSY1+%Rv%(a|D6 zFDIn{?<7)=TIvX;t*od>^SQk!Dq@2C+?}a>Z8Z%YPBd0UXXh}*-s~q|NEA|Ecgn0G z%Dz3lqEu&l4jFDdjHKgughCSAMZtmdrnxO@QIiW#FQ=Rb!ikA&m5ZvgH(D_`hOoG9Dyd^X;iaunpRyp1{PC70o zW^806dNb}(mZ;xj4&h8cY%F2!i(ii@1#eEqAii3*O-@5;Lbr{vMygM!tMUk|iNdyf zDVQp^GrA^OqB74mN3u~Mb+aPxtc9$5$w{{n)P}DKr=OrRd(jp$56qk{8gWrPf|S|+ z7nH+!}#y(R=wFKD{# z25q=FL|`vpzNDd{39t_J!B>tt#3nKd`j_3a(iNF5=!S3D&({ALS9o*HDrm__&ajuE znDMG3l0Yr3Whpx1`ni!D0Y!2+`_FGaZECpvbqUH`9(sdKH_w|NkbL?ZZa{uHcL{e% zPip+$d}7nULR`*(jml~5GVe46GKlxhj;=r-s}1)v$!zNY{raf|LlRm>M#kIJC=Cq_ z_omxamj!A{_D&>h-eu9N+&b<-V|`XcMF+nnd|05Qvlf4h}K zAbH#A*RXfO2;N^{KzyO3*bh0fS#ZqF%s$riCa~(36cvT(dtCc(xGiTX@AHUZhj3!)mf!Vg712RT9nckJzQtKrZRWRYxvXlo*Ni`5QljJc>F+1La~|94mEfWZs+V&M zpN7-kyB6#&Tp}Al{H}fw{E1}}geL#lBS4?R>qK#MtKj?h-wiId*OnYCDXa5d!q{!A z>+78pj2z~ABof7{0`kkSFBlvg9Gg$*+FPh9D=XEC)DJtK+isq^8`BkBI2E+ z2vvY&%DR1vmb|QIUco<84oXk^A0oVx)kG4J6sd)VN?P)g`}Gj9_JY~RT{n&^N5?Op zCUHEXp7feO6*y>g`1A7ClRH?4KjeUwGN2ke^G)-gSPF2j4VpS~#1<{QbiP-&L z;u`VXq_WP=&hly9vGjg0ZXeZR&9z3Qf(yfH&2Wj8!Bhc|R&XGCBqm3zJ+n2|oQdzk z!^1(X+lqaqWH4qkqyTq+K`86& zgG72S2$#+fyjaAonO&J&%-Z$n5IPZQdEOfXvn6u!>n=&CGmM8aT z0|$ zba#V<%+1YhOs6mBO1c?S9cqDN(9)+E4>B=z6_wMi{B-9#RZj~J6V#x6VaoTJ zoz{0F*3Uk&f}6^mLkrLcO(_f^Jsb)fGd3}Cc66NHJNx*W#~V{B$}U1?fU;UswpTi; z<+oMM4{g=N$D@hE+`%Mr3b}$+Ti@7yV1uSvhQ+1B%-{FkwZEL3{{%&yR%Oc*GBtyr zF7yXvyDT~h*hR`9PZsZP7Sl!tmK9>ypIbM~y*v=6tYv}U-vspn8prc4nkOpAoRgCi z3hrO8sNkKPoF@IQH>Xn?@!HLve2IY;l{4M^Qmc?+)#pPyTw znq``<#>dB9gN`3{jTb0&g+G4;;k8?C-$?hN*jY!z?5lu^ZLA^?$oEQ0)c#LHX=GAN5&dF@$>*kgQ4xP1*^Ru(t49W?KiPcn8P#__pp{UO!5y7=*C#hS?+bjY9EC%QsqdNuhZY_ATTjen+cwrj;5NkKBT z6NIj0E=KAdl(3fey>u@e_Wq3+DG$^jRWd#Op(e)0=GDK=cwRP!QHprC2Z0_SZG&LS z>T_W;6&#u(Zq7!+8?m#y8)iNG;w=nq>y=67s{PLCWgMd#4>$MTV#{LREfp2FPdu~M z`cWfW=OY}luL3UnJEbi)Y$W@Q$Acm_Tk@ILPa(c?`5wFFU9k!FBtBona0y`;8Zji0 z{_mRZ@)rA?=yvQ_pELXQOa6!bzw551#>QKY^)3r; zrd=vERx@I^RZcsq*&{!^Z!Qyb+_#1^-+2F4W<5**WzZY!>Vl8<<_SIAZ6qwmysdc9!BXFHxOJjkgba0h9=hX9Vp>#Z7dnq%Ez;kwu!JIp)KRp3U$Y z?aN4}zV(fb^S=e;b{N6S5J-uNiD(d$oE{hHN{)o2dQcu!!-oA(5_eypx5$>xu_>q* zHj#vAO9QVLLD6{q*`k>-hE}$>yLF z^XDFeb|yJFnd@!=Nt7kErX2=-ksGO{rDgcLL?JksD(-`gy$JYA{%R#r_hws7tH#RQ z(vp#h3FeV?%!pKpqT-T$5pTOKP@Q`Y>+ckgV(D;|qjQ>=n)10m?DF(n;!)}f`I}gS zs4ri>ls27_ZZS#hslj5X;AKheB&Y!~$^pkOi3PY0<5KAkFFR4j4Ch0JeW8$PUcGvn zrXL3dB4P;NpUJ2qmq}rxA~?nX)bzk#GKsw0M_Ihap^Ds=_BDW@bZZVoGO!(j`PD?;6gP&zv6y z>qLZC&@Lr$TY@I4#`EHcEp1#Q6jUW7_ap1I-Z-O$GhC7DzpYrT?e%=ot;X6$5i9R# zVHa5_ojM#~nXgAdO z+}%QaUn%8zUaTc3{&)fT(;iHa!?c(ACNRsPV{0rw0YJOh*f3J=?Td}{$U$)w2p~Od zgdn~!1i)3+*4EPe5_9;B@XmPHx4V_pb|M(Y2Y@mA3!Y!eTJCZ3F+9oP{l(VVS`PWC zV|uqcuSG#F8!%&K7?#?E2JyYVzWx^Og-anQ)qi$#zIX?kz%eTx$)zkG&@bOzPPIrZ ziTQ)i_c&-uu)15JybJp6gqc&8{z6(K9%O)|3SomV@aeGWMxtiZe&#%vqdOAUY+1*fU zjk8%l7L<-@zS?gn`@k;NUPlF2NP1iJz22btO#=DFq~e3@Vm+%i-&yF+0JfxDj6Uxd zhX8bqAG9JOA}3=H2LN!5&0u|&)>+2BL5KF@TJmVZ(W@2I@;UHYD<@|pE2cg!6t*s|pV^~;N)c&f4PFH7p z11=Pwqyr2kf(k}gi)ZRjk|;f)%7GtoM{j-m+sr56MNdphx?GKs z_de>O1A%fUa<_4p!tb=*LzfQoNqTLoV`8!nupj6Sxy?s&*nX=)eBVDg$xF65>Iz?v zM4WX+@2?30BdOTnS__4Tm{Or1Z9i^Eea z{$Bu9>-6qH^HoVx6S-kZTAHxuzmMr%9_&4)Luof)IfO4(V`Av!1MJh5+5+?Q^9{ph zXJ^|;rfsaPz1HH??vY!C&+jVpgQjyGOUuFu3$K;%*9-1D%+PV`$k%W`QSP22XD{!Y zpR+F)n54g&ZF_|BHBlM}*2}k+^h7nzuxOw9`S~dfKGiYO@IIR{1tqqO8Zl~`?gjpl z-ce6&^M_Zir<(dsURHJwbjIE?V#GP~Ho73LIEMt^+$zpk)zGE6K7d3;M`w<0Sy)(f z>rYHfRGJJteiK^i2j&0x=cl&IJUZknhjDK(0o@#h#5)ei&rpQ0({}98`_$%yMET%WQTt6%eH|M33XD2^=t)7BvL>B=V=?c~r& zoJ8%jp{dO5v6xplrL>`~lPs~4EozrR?;Xo-*y)-C^V%LSDAuaAnXl(= z=N9u!>(XPRAIYWXb?u&8o12^KqV%~g@1l?aM+Tr^cv-EG92(|>shocE?-EQ%K4#fB zGBB#QDS7VGs6nUyKDHJ1Pz~^ibcjMdKwLrcA24x{lsX0f!P|mNPxSw2myM z`gNM*4{bi3)EW}GYn;XO2CQ-cYurt1{O{z5N&=z)bLJ#H#LkXTcd=DqR*znP&GBP8 zh%cz*yi8JWgEraIsqpzQs9Co0SEVz0t#PdAjRmGBOt^8M|69+Q`$p=L&nSI zvj5@puc-8NpN&+PhpYTL6-GVGwR84;%(bqy9|Lj7`5o5#JHwyfv!5TX+ByJZTMsk? zPSZhG*3ONMgCk6_%|E|BV?AYJBf$pm4SpK)rlw0FDkGVePA-MladUWdbQDnQYl8v@ zj@A#`G{!ESO`b%^&C*9_J9m>MPPrv@k+aA?+|}wLiNV7Ro+)FJnO`A%(8e~156jx! z+zi?jZ)546()GECmZZ42Iq+dcjDA9WRAW6Wab`PT?=+GnzB5&t05d<|pNIDc`$6SW z`H3K+qd5(RJjrUMI^<%rZJ;Mw0L2}oqDHbxqaOSthep=IKXf3&tJ)lZH;Y|H%bYv= zih=)QZ1tOB_FKhO-iVEhM-;AlAkjbPu~Nvw4O(J(k|y3YgYX3r8z{o{_us*%)sM-H z6i;Pafj00CWVDN;wOgLPS8vcDQ3(mewU*clS&=e=xMF5pq%Z4l?XoaNXS%&WC&y{!M!N+IE#9*7r03z)Kdq1L%K_7&I4{; zG%lLhCWnSp_K14%B!)K2Cp5Q{&5@#_A`qGw`ggZCpwWw<5F*`?K*!wj1LctzW@F70 zR8RHER5s~d9#HeJ{1jAGC1{TpKDIppW&7rOGqE1-f-1#W2*aI>ec`3XYO9S`;~AK^a?(nKRDJD~H&ySpfl5gCsi%H|Q6@htEuMCt0asg+58i+(NW|RA= zW|pw|?I+@&D!bfZ{Y!sJ7@drH%S1?smy2e>!3PAq_dSHzlv+ByAzznFBem@d;#N z&&|xhqvPYD6h1(4iW~cgCSm`y5}4ICMo<3VnppknK0}=UsryZD9Ak1)QmgvpSA1EL z0JqEEuiKpdmlt3wKaz~E!e*WyJeAo<7AVK1TD60HYOZN&V%#K#K%pEl+jaJEeZH<5 zRo-e^pC~5w+zMU9N8EL6(vOzk56#;WpZa7(k}~)*rE?@Qu<1!GZziXuR2q-c*o3I? zQz9avzduo5N`z6VdJZIUgL6#f^H@3NmfuwBr^E$Fr3n6%*~e|sEPvhw)6UGy@bU41 z9AB+jERk&o(3!9)QCneD6(Y=Tn)vG>tb221_p*5{UMs~*PKa#7?yTAC%3xYI*pCWM z1sWkkIVB}w;m)URy0Mb`0D2ZmmoEWk}^33h@hA4 zojrEJn_q4ziV4c&M1C4Wcx~)#;(w+~fr=8_X_3Lipgg{c9 zZgi{U&=oP0y%hNEtg|`0LH0Zs$u?pKd(3;zRi(LST{~cAX7;a3q&XOYY627?%gLg) zKwLm8&Y5vt9uo!VcMCpR#(G~k!4UV|<8=5BhE&wZFSq_Z(-eVMb`+*Q0A(F=3NdB< zxBGTU@y$w5f|Uqbno&J$;X)4FJ{435>2ubWyIYTWOPq)Kh((hIkDr}&W#6@8rCU{0 zRH(i^7$p1x@*=2}B%$EIyWCa}!%@-zJ^2cxt z4vS&-+GAdUg*-<|sVFK@S%U!UA#yp(tWz}Mf6We^kQ+&nn%k3oh8deJ;R0%2*~`IY zyGcs_;q++PAoP@1k3n<|313;Yc+)%$9UeClbp3lUVv9O-Ts!YPYwL40OLeJ(-NwtP z0=9gA07oopWEE`o12Ka!+zpb|*-|d0@AjqQsrui9!cAFO%WDa`&J_Gc_XHyUhJFlv2+Zd;O&@GET z25zc>wysikU|`_M)Ur$MqW2ZR5*UylfIqo9XgTo8%uMUC^*&CfeiIP#qLZYyimT~# zGWqm0TJu8v)dL6wAigo(+oOb!sm}~|cOM{)>&na7V}F_Xqan7a{k5Zh+RDBNuw+ge zp7!An8Zi==?iZy$J{r+c@zIC9e#3gljj%voDMUJKjsIm)mw9^f1^Sebut>dp`LK)9 zr2*eMa&5oifQ6NkwhtmUJu>2bv>J>tvkPFGX_}i!M+gyMJ>I_V)$G&%r4e!OWqI?8 z`57Ply*tJdU@hLp1SwXZB)*$p5yHcm#ggbt@yJF0ji3y=H%ok ztE%1?78pk$JMi29z7W$xNT7mBa3%n{eR}z4(mv=L6{?@|^#1;%H0Q$?bQo75p?ilL z^^uf<^G)CISm1uXT$qU^yYCT1w;ZgoXb>@MJ*D(ytRbJlZX5g#)X|*Rm3zpurwg?? zFvNYc50HyE)1x*vroIL5t39Q>cQf@)mhw2R8yg!UhwTJNfA)tM)PPKO+A)D+m?|gx zmzxfzY}H_hlCb7vm|=>28!=OlcS0L+?{)?6Z=Bp;#&wID(Y_h~e>&&;C~Ft>tKP%-n^5MKwQu$IFKL zdLFCkG7vNbbaGKb?oHmHKLTs`@W4XvHVOeOK6Od*|xN~x%sb8_ZDn0nV{>( zpS~y%-_Fj?%~jOiirq%RXPudUc_z+E*F{-6frv)x;$R z!FrfC7rxz*iFjW(95g3`cKh#Ao6FvBqOHCT>Piz8;DHH#5diJwu&{j%AQD2Ojy`M` ztUQ1vfSZ_Y@$qr=!yCM%#K@G+eOx7~lHmthhxH8jXVoHg{c25ORvk|O!U-In$5(Hq z^nJhHH5GAS5o@ltUCb|@jE#RfX)zE3vzF}I7V=mi7dY!`Z51s=zVp`!=-4y(43NM* zGWNNd@*x-TPBET&#%Ur5kn-J`?cKxw{fPbw<2hQRA*lR(G_TlAu}`WR>%r4)nmDE* z)+^{uSE*1AeD2PDa9IXphXJ8<1RDCN9LAxd>^9fcZV^jM*2`OyKiw7AK-KmJ6-ZHz zipp|c6vHhImkU!+0WO!!v)6RKU}A#bu2gM=i&8MX{wp6tAvrs^6>V;Ap2-!r90LG5=qf)TA)OFI7zVHxodH?q^f_qXWL5KRdOUKj>V!0X#+C3#AKkf9%)WKALmfAUe$X-LtA&v*$3oTO zcw?#2VK#}qy*-f9r1w9#r){V(oKM`=6MUmWuyVhCeGZbd50K7)b_kdwedwp^t$Q}3 z*7=)wv5umm9iMfR1oUX@L+N+m zs8!V012j=b_3aUP)P1^f+;lLGXZa*m=a(_?lI;E(taiJUJ6cW!IMR0*oCiyuWC4DDXMj}PPt5*Q@sRT| zWm%3Tx_YnuT_Uci>}V83h8q3kl}AztI`95!3%p1pei(uV8UkQ~#Qj-)s2ib!Q&aaL>`HI>A0a;)+qHH9) z>uhIg@!UnMvtzGx!U8qbV=tcyPwWvc#fLJzPrvlKdwY`r-UbNx$+b@MpdMS1c7rn@ zTLPk1hVlJ(q-9zdmPjc-m-|?4i%M_~uMq+NoAka(sUIB~vAaCk z3Z!tC${#Z}HXcY563BQ&4!|Dhki(4S_J&OqD`o2*`g7!i1mIZs*oSM2mL^=Eq{Av| z*;wC#X^lT5Bm|g)8A7J`KkV&UKf?8%n4X4?>{xImA(5lMJ_kmfyuP&Q>4{aD3;_Pk zmW-su*#Ml`{!C?8`av_GI3fj)x5Gd>f*{_4g=nKP2?4V7k`aA|sk zk2y_#%(6sR0?%rK&c+exX9Xl|0FW_iTj;XJwr*EMZr|jN_HaCeK+j#Rtp}dlUbDC! z{|W8yFFHEX7Z-0rheOeR+0<$kPxdA3SXz9~=2?j_)`*ivFD9?>vO(mXJ@F7aCYY#YIAdGHpdp`=B~Rhj=!&)P+{aGGoudws*s4=_Mww28v4s9 zL(@ag#FPTqK-t&xvC+{>ALvhj>R8%%Buz*P{jaHv)sP)1yBbJDZJXAUw*T2JcCk3c zu^)eQl7*;v@O8&*7W8Aj^vW7rpV+XE$Ccy@lrnbZ9`r>7y%VwY)pz$K- zcl_}y>P3i{scAm_W7WcerLuG(5B{KKh0H(rbt4wl3yVm0yj8#e@dHXZ*{0qcIA|ac z41GW~1Irpf1?MS(Zb!+s&3vB!*b3^NLUq^HosX^wpx?DLedmEg2BjNmoH~EI+U(^+ z!DDqNdhT7h+NQ_GyQpspC}QV>1uuXzHmhI?fQaS7M9ET7rMH1cRqi-c=kZVPr8mmU z)+bqaw0>+M3;Uu=D=RTtPFQ8ap!7R5bDm^Sr0=PreZyZMw!u|y*wuV})CW(w zcq`F~wYF9$m-@c88Xo$ky@-osqwh#a)6|1zmWAU8i^_~{+&?iQ6a>+y8in=6O0AF_*L@I@R` z$c7bDJtrlt0S$%Mm2*P3CB4dA;rsWhIM2_naYZ)yBDQwXAfgan$Po-N`@7y`s$@|< zMLl&lx&Sm>_?67y@Kih!dr1X<(83+8?qsSXj%$~$@Zh1Cg9&0Vx22(2eqTGrCS|qP zS+wI3{q1nRe7Yp%O|AoJ5>p>-%+_bz1*|@SQ2npXUZH>~EE(H+tGKBH z2y4riZR}5du{9=pk4l>_EzbY^^62gTc+`A|OU}>bcC^aca(x7xjBx+wtums1Xt$R5 zFO}9a*s7zE{_5}D;k&{G-3_$b12vV07+F%R9ayhky#lDRhzZp{Wq!q4*Rwpon)}~d zIzPGm=Um^4s;b__N5g`(wN3nu0^6uD#AIX~tgXZ6_w(e534m>+SBVXKrO8wJ1Lk|X zv_Nl$Z0S=+@s4duA0`8m=4v9nckil5eLXeO4#(uz5?EV+ny?NFf1fz84rrpTdLlJ| z%mMClHpY$`ui8ZKK!rZ&=09=MRMI{dai3asGD%)d{aiSYAf3i}340R0!R<$dckrll z+M*h`eaQtkO(Y8Lj7&GM$@v>IG8CX_g%h-W9AXvoHpJqY<>loiB|O7U;V;7JL*8n> zwbC()pYyBwIJd3e7Jqj#rWe1KoJ18I$AIIIZ8xw#e=z5|GBY=)Udz>UsCyrzz2hcI zp8CCHIqVZ}{%E>%G|-lE&XRRS2a8JERd@5$(H;J6!zCsQM}Q|t=W%o_gPxUl-j!7z!>^164Po6vh9QC=cTwFqeu8uGhE9>GCl<6twD}GiCzX1~>@$$TN zF$V`FQAXU(#~Y_xfWeFR{Fu3cVXw8=yvH!%1^v#V`!GxDayp8MoEGtWaB#xotP!~W zBENiLw=qL5=Tm+10Sv+$4ad#n)>$d(BWg2_j6h619j-0KRL7;#vZ$n_dVuc`e(mkZ z)CwjSr$j)K?`|N}_ zA2&AA5FvG<8XiC9clCY+0Y&Lg)dwO}QBpBde~OC@k(RC5^Sf{oYPy0I>HsuqsXrPq zP%%WR-&dF#6Acqay_J*N=Y%ZAdG0N{Dp@rsCV9k4n%nxbcon|V?L6;pQG5FW zj~b;3X<~XDt6d`ZoCDkRZEbB^To3&^3^3IM+|q^si!?Me)YtctyuPSt7mN6Nn^yI} ze*&`aJ!dH1L-M$yvC$KlTv!A|E;q9`NsyXN-s0jNSc^XCofYpS1CPHZqfnuPc1y^0 zce<=Ep*Eb9hSs}Vq{b!rPvn1G787fMl9?A*h)B>jSASVX@{DsKqQWb6Mpa^M+$2y6 z*!zLCfgGpE2oveECtO4k|-FJSmPZu z_I8thY80w@Pte`n{(ZyJby!>sfov?6KFAP7DmyvdXlYH!CymD@krq1B5(xi?>jT2R z$%TGUde!6q?Qrw!RtchFPx!cXC=5G93+SS1MXs8J__!|5 z(E!-gjv>JUGkMo<#&h7S&F$tcvPkQ}F}!GV{i8O!IUqY|#1o*+xM}9Y5Zc=7d$YCl z9Q>88RA%0Z9_nh(R8Z63+v*>l|9YHbuG;a9fpS_&}Qy)H- zHID$>LudsFD*T7@Lw_)X0kVMs`KsC)ez!mRGRATt4YiYei7}(hCXTWK0fBc~KhkUHSPO-YK-s!7=g&-#^ zs%NdS+qk&30ezYeGFo*tv7uJshs5rE&iB(}=$K0Zw>RMo6RZ#gZf+EWeX~-#ej0Tj zf5T0W*VV0{)%5KrRC5kx=s%!Qs(^goz(R-5oC9NgVf)dtM#y}$DJG?}=5q$fG{84R zj5f8aq)5om&)u|$_=wT=izX+7GI2#hv`;J3M@Q5^(CF*#{=2oTDz|@R zPmQY>Z>&hzZxTu$g3;sTR1I3q@5N2(y1J2G%smv`^&8<0WTNO>{gUG1036|Afasqt zZs~wzbnwc@A6zU2W`>79?~JS+!AwC}{;v|aYlcUQ*GYoqx|GaK5hMS3=U>Nak05|_}I4j;H}R_)&-d3WAThjp6xl77yV{qj^gZb z?z2V)xj^P@?Ru7HE)Dl5GCzDHD?0!*Jj3BhwD$aaou7%BnOw-dYPPM}mMt|{f$>#O zP=9?X!5_qZ*{ZzV)Ef062}oqqZ}mgRQf8>0VCd#BAn9fc8<}=KG>}zG=rE}SwucRG z{%87q6)Dlt>;~=Kjg5jBne+_gjEm79!q~;s3CVE+^{SJojegID1I>=l=k~fIbV5I6 zf*jz$V4M7(@8QlH|7WmMKA#5Q?(I%X-rcQraT~izCw|{YQtqMSyNh(&>%VW>O;2c0 z7SJ(ErdIti3BdU9#0^{6VZ{6+;qSp_GOrL{`ppH6WqwQ;u@U?P>OY0sYBb(U2@Jo# zwJkT+U_!#Kc|m!Vgxg}=FjAm(=ZQ#aFGrG!U1RutQf68W(NkcXsArFYbFDY z@miVw(B0q9X)9j`-Q7s}v2ZX!Y@Vg^jqY>HG1HOl?~YB0iTwx(l@k03#($(>of z421u?_%%TISkL_^FD%4z_1@PxfksiGK)$3UIKkiH%5`L*MfAHlA{!=h4&oB(k3nLu zt*H?P0oW2HAykILYJ%f4E?=_Q#jFE@c|5z?Ch|16pi5d z{(p{p`fhCOi{qkT;zR!K)h^alRsFb8^IS!e|8*?p32Esr>fG~|Pr`PsN{x?K)zP^H z7-ZIp2hc~xeeuAQI8;32tYtBsNS{EC!;0N@y{#6B;;W_k`}c2tw$9U2SrQpA%V)(S zAixDh;)(fDog9ehZ4T?ZH~nh(Mf-4J>4!Zg{YFEE$b7E<|ouvU);FOY<6%x^D>m_UXBuC2Gvlklg zYc!v2Ads=?2j1NOfcLY`VZ%Lf{vV))yCw?Rs5--*p@LV{R6F@v*2cAw916-m&Y^_JyebNblEfew=20iq*yjIyE4{6&4l(bD9;8<%zdSnS60|^(jd0 zfRykAqcSKEKdK_l>fWm_-S6I3>~YrYnbPZz=GGp?lZ@v6bWh|Sj%bid~B*4Nj8 z_CZexChxX)c0Pf4(Z>C+TU-6pjP4SVp)yK;Ymc}7T9tF2j>>g3=L#N1EM8Mn17>&! zfu#o|n1`SB0go-@U_S^{7{n51F2UQ|(y^@plTa9N_zg|m=xYKXMytMWcP86#UVmQH8kpPA z@jMj&b5Z9p^+we1#QVD2+by)q6#bHsUMPN`CQH7>k>Kzohdr{~nl7j!nsF!qiqK zEr!x5ibO*o``1}CB>bp8G^#tC?aFg=PYevm+ATL>-xO5W)PTi0uG>gE_Ws&;aH^xw zL7Bf+>U~-W=JKklt6$ZL@g#?jFVfjs6sztOOu8>y5dDXsg*huJH(ovoS>cTjAM?Lf z*uM({!qPyJ6R;Yu^(Dj%hvnaI9XRT)6R^};SxgKJ=;k{0qcrZV1;qXh^UV18$@TG2 z%pQ>LbbfsNwWn|Q-hx-m6Nu@r7kD#sb5-KS&n&3h&YZKE`q0t8kN|VDK6aZy82T@2 zjI54Pbja@Z_IJ>XsW|N~cUaW!_;#fHNz(+ADtFgIcXK{>H(B%#Ag*b(nqL3Tw6CM= zYb#Q3v5tL}Qyer7b$!*db@^8@60TE@6Z|GT{x4&l_Gh7JA6sY5` z`S~-~Lgn83ecqDvj&|dMH-vURvIb8~2mh6>+gjYcO$fMmfdC5FeNW&ceq-q-W)BQ! zxiWVH2IX8lJSe_S{7o}ftt3y0PMvvo!NwvC3=L7+*qE7_+po02yiDz^;r!pA;oraA zf%#1LdYPEg>m>S@9x!SMI&Matm;Pv^X%cwe2p(EP1Htnc3rp8yuNtILTnI3legetj zo+f|zc?oEF*JJ6ot5XLEuI=q@GKeLYh4FR*Cy60o#X_=r2qT3$GM3zKYi4bL5|t=} zOJcZtR_F0s{+{5fq^0&>Ol|r{%(Fa`9ShSte6r<|koQw(P0!Qqyd@2|k^xlil@yQp z^$y|zhM2%Fq2O>>2NR#b2uj&me8-kW<$rtbvj}8fGk16XR~kvFsf8nE#i|ab`G{gw zual9$(^Dt#$EINq@vzesnEvjWeZV<`2~05F@WXnR=Rnuado6lGwJ}}fM}YPJ%@&AY zyXs@c$dpay8A#-uvM3jOZET=uXgEKmdnY9KfPLu&Bcn|ISQBvK^-?GjB3uT>9L}=> zebF!*Es*>@uk-}wkM{9j?{TyL-?(X%#*aqh$2nSU2>p3>*-1j?< z{E)JF8y8-G)|R=6xjC|(ybb}up^de*SCm8PB8#J=%bpM-qYS~5;RhNL7Z+a`;-q`N zedHy3^5hg$|BuV8Q2vC02(goC&Z(!xw_W-M5l!O zVn}hwSd{ZS)}XJp-L>=0;dc1W+_&JFnPK&v#53Qm6V4nN z^nLq9@gEqDJ)b?!9*01XlFo)OhcLECQ={y-uJqYNU^*CMG4Riv_hdSwLZ5fkEOWbn z)g3M^$I&PQ<}3ePw_wcfb3sHzjX-~^g#M(so$Ut`&>}P>M-iu2cECy zuS6!9vqg%3ddrE68n;{$LEvvw%@R54->?17CH!_&Qd?v)LL(pe@P2cA1UfQYClAjm z506)x4Da(ltk|d+p$vwRuxm7rT5RZSJTKFS-?35DkfxSSICNL)=53Fbn(Hk%PF#X5 zDg>)?gbJTh5Xw1QOmQm;i{!IYVB~Z3W8~_4isD*0nclY^ah3;x7tBwwD~OXzX|@)!y64PG$FvEA=8(q+l6kD(nT@$aCVs zg2f?(yM{I;&et1el7yZea~B&32^bYg)cSFd(BCvQoB@; zVgT*419uc5GoP0XG`z)FwR647qE|fS>z_HYpH)kF@L=H4sR4(@bH{IMyz#|GB{i%X zC4@9#1)>jLQXmnkXhciag27{Pb>!oDgdT(Ui#MDt2+GJ;ra4sDpkRq$6r$UPi>if> z3lU;=7y+Yml<)KAwBlM3Fd>?=+4-klga_&E)Q#*{Li_|DBqW;G^j%fBLs#Ky;(hWX zFoxn6Om<&jShc^)bHr;P{U30HJ<%{V7OyIs%IC*O@1ugY3Bd-h(^4Sric=!8?VJ0V zKAs6qAOc~6GDNDKG6K;~@}Uq6MydUdd ztN2b#ob09743)2&0yOsJJjwg?{=$*h2r48NQRaBqcO$B*<#xamVO=Yy+KfB%`^KU9 z`e9U<`4Y*7>9sMpF71S9s~qKus5=d-@q84w35GdQ9JTKw zIq#+y@L&UU6iTJ~x__Tk{}1s9=4W*WhKN>FoUU=xi9ng3>SHlZNXD~3f;Ea2T=yHA zzmez}X`39vr28UU$)Rgu?}GoRsW53)SFzwNw_*zieSLdK zBbu7?N~A4#FlnzWFR%aHCZ8aRgF|$MLZSw(f7?*fPTW^?Hn5_JEjBom0{U_Q*ZYn~ zd`vX$AWi~4dhz(ml_`=2XeIvB)Q8&Vm^+-4io=h4d?fyW7l4U zrmgBflyN~%eDvRk)+NxK0&m^gfg&zcEvJTu(F>ihsxpv9dp?Owh)9 z>eZB6lzyN+CB>WzOavL1VQ6E^cz#rUpaO7f^~1{@Xl4(&5GvOpIaQpo{eL%;LDLea zccpthO5^gsI}hy-Gc)Q2rd*59pYkF5kBcUwX8o;Fx9BlIul~88ayObUD`2?@Y>pNL zIv?IW1U1;{u`%|g;@{MLjvJr@@Lio8oz4R78GK1W>i@txNP%8r)<4toL0{sgC|=c? zl#HA+N-A@lQfQ2js-0RjPE5v;?%&UIgU-_PsXgl*G1oBQsPaio<%o1Ducy5SeIpnK zk)>vm6I4)Eh}WZ{WWz$l(vJ91L^!oV6pbaRBbPcfwEe(z$bh}BYHF1{>84>U_K-NU zi<<#51!W^NpyjT;e>XaM2r=j^!@wa4v)#ytyamZf#g%h6RTe=$Ds&-p7f7hEvNEn^ zlbMX_U;Z{#r#uLLcAq+;fji=(&^bcIw4g?XFf=qwuHo2&{R>0El7}MO6Kd3dsM9Q^ zLE@y=V`_L*YQi#bBx2BBdP^Z#NMB8wapUL61?xsXFJKRXI*#nu#PN^N|1B(oW`Etz z_#4NKWxx#IW-hqJ)MRlllh--wNE#At^(t76a+{Q^K?5X{R(=Pta>6 zKga+yRMuv!8m9B#tZ*bkzj(A#T+GA&(HnNV(z}A^QxFI;Ayr>*_$;#brMG`}=C7Tt zY#_pX`8!s+<1p5j6Tbb^Eu6569|$&x(lPxv2brTL;wY5Fawa3j#!L>nL|W;*A(yXm ze4`ln=Q{w>wzOR6~Cn)oNV~EL3{K_ z!$~Kntc>4%b&bLTnQ-_gs+?GKUL?|SceQHv@_QfIA%1WPRK!cTVwC;zDnk{z=62%5ZC%8sjYb{>DY8UVp4 z8H^xEiR2YKllARX^VYpzt8k(PBEC*Nf0mb*S5pIaD8QY9Lnb{kkaN2~2-5EwCp9g^ ziBquMKFO~^EossJ%-*sdFvbiF5(A1v^GH}I4Sdzx>2bEMK7nl%W57h$og!er5oUUn zBHayGGxQ3MF3sjOM^cVYBb{4^6}evk`I&!j0*HxU)_|4dc~Ff z-7Uz0-#_=rV&?haI(Htcfw$$R)nHikxNPn+jwv37e^a&L?$ts3Y63D zAe5P$ zaTi=3%zys;6sT1ERHunL$2Yl{r#h=bh{XjB2_3G&%neD2Ndm-GKjpFvS&FkbA5ngc zJ;W}s>@YNX@$z7Qhg_i!*nDc$b#8bbVkVRwJ>1+Z>%0Yxe@A}4-`p~G@l9KrJe%#Ox>m&0C|v{eEG z)4Sah$tjxK#Oi&Ny;G&3Ngcf-AB{VouNODWlr-F!xo^`8d_!m0h+tz-_mfllmi={)<&#?T|pfsL>ZQcymD8^#E~}J(%0U< zfGza(!zEMG@%Qi3dzrOf4GxqsY||6mK4IIWQxz4o-{mK}o--4PA?aN;1V$lLSS3QkR z=g5dqiQ^q`X*gK*2tGNhkzhh>?JlJ!Acr*ZcC^CGDLrjHL+3~Z;*`hlX#p!hD+rob zvz}(q`@-{~@K1J;4FKwX!5$lU5`QLbsG+)4JSNBI=FP<>R!4UAv`9IoUYJ-$cahKX zYefZ_PfJSP!v%lw;^;uoud3&8eLyonG0z87a!^G24ZRYf7`scCrT`y&lz=1fb#0CJ z)HQC5J)9os_mi+SH8nu*wtQyjQs4(#uJZ=0`JcOs)Z1D-E=OS9l%jEZ?0Rwg-;;HO zM(e>6qf$IR?T833xd(A=vKqQst2di>rgJo7gSL9D^9_O`44_wFU}cr*^8D1m284Tq zEG5j+g|+kCj+k79!hwn8VX$^P##434!s2OhF}QP4Bq@=}khXA8feri__ILa2claNu zTh;bGXutDk&r;cMM{tk@bfDQ(0dXkH1?9&c6X3Kq3a@q6Dg)@u=^0Mf!XvPZNgp z=6coOKq)<4>qTn}-7WHfYLb@XaA94(e-hj2wETVyvaAfFiDqr!KSoB9Lqio11qwzc zz&^0QVGX43tMVCTWs9eLl21)7x<2TsA zfdHbH8W+T@w_eX2?B9ICAji6yI<;$bl>$SD>7*mPyN@8T@h z0SsG#_8h9#Vg!<+k`jQ?to>+I!^q$u;+QAH4z#@Pb)U>iOz5<=C9T2=WhmW*3b*ye zt~93D_SwS1LXhgDxEva3Q%8HF4Bx0bBqT&|2{Zrdc|};eqp~{-a*RyS{rFg2HZh^u zJolyYgruYi$I-K>mX%G{aT}oMpT4=4t|moA)X;jRgP~K5&Q%-JyY{a9adEL&)mJXp z0@=6B;on~x>Ynal+^!8Z6aTy!Fe)V! zt)Y&hb~~hw#3UQmm_9=B6NUjR>}+o{o-U$O4^Yko%Ilf+Zh;(;{e^#mmjJ#6-o0PS z1?m;DYmZJUfA2apfvke}Zmd4(1e>YnZWEP(Ti&FN`xlrEQyf?#uzpDzYj-8}92;{Q z6z`wE-Pu)-rF=U+4qz7VdLFs7wsx4Pt~IcCQmq^O4ghi6wMR0MOUGaq0Ff;J+Ybi% z%*Cu*Yjqb?_Nw#p9OgRj@y0vtZEr3gX&TLq#k3~_Mf4Ne@$?4Ko#KcBg+=4<3IoW-zUszDEcp-Ru1Wx#- zq929f#f!3#=aHqyPr%mc1Dtud7%%Qw<8L3CQn2zz8Lpm@F~WzPBG}9S^p<{9&NQM` zbE{l3S{d}Q%l0(dOTh;_GyD5N*FTpYJz?+fUpKJW;JrY<>kpN85F4-D_)a+w*$e3W z4IZpyK$#e(*hWck^LCu66&nd<_wQfg`DMXg&h`-seNSt2q}^Q+{^pw^*dv^f;z4@h zz-19c8LG;M!kv_;@=~v#z9wrbg2A%K`YZEZjxLs|8wUI;vnp9KIVik*B;dvC-HF3#6d@7WjX*4Imn1*WT4c2R%LrA zzC=<_4?5^+{%HffAaE$`fDCcMMup==$r+59KhXJXU{mq3M-NX(>Of=6mQk24%75iTo zU-*!>PaM$N8sGC@;yL%?UQZu{cx+>&Vg5oxTIulEzSb+zjVCM~)xhzB`vrPK;Fbu9xblr<J*`a+vlcq!PpgT7c3gE#xf)LNNO7R)LGVXn{WVD&9*sx`ZT%y zR4-pgZu&sS^X=H!c3=~9KOqQTy3DVR6a{h!05mS{(-57(S!a8DyCiMQEg=EW)Kw5n zxVzuSEZ#h8%Ydo zfbXVJ1FvWvTnjIJSJNJ+3XFuRVVGHLOz>fP>?i7#Qg!v#ZRzf`Pf{dBpn7R_%FHU- zW<5Q*Az7*0jhcu^fOZ=gi2ynHPCH~;^u@7f*P0K4x{o1(t(t?Tzx}TAjJ4OtcTkgm z8aKzuO3F?m#;EBnqiX3ekM;3NHp7$qk6>ufGc?q8F#k~H6o2s*E(`|UvKtCNl#`RQ zFeBC1D?YqO^}bJ|7q z=(tqpEDf;4cJx1A*#s!9c<<+6jtxPoGmhtyfdR#o{)62wPvQe^-O}xKkUH@d8T~NC z1h0+^C%;w;?k&8Q90to9-&@HzrQcj`Px=HU3^04_^0JgU>h9rjYU``e;Ls3-3pd$K z1GMZL4En~#GPZ5he%+C$VLrdCtXy%IJ1L;fuSKQaW^?>qdwv3ShB)V4U&H6+x->f; z)z&8_RN=D&(#f9@P&J}qU@R+p2LeJ%=ZBZK1QMg@oZOt9%|Orv5RTXHBL#~d$Kg8% zpkHhG1t-U?jjP5_nfJ!l(Wv(zPWuM$mP;#jh&@7sQIpNkJ;vUgyn+ZJ`68!xLJ`#A zYByT2jGh;;Xh*q=S*IL_l0D@uEiFq#V(R+|%Dle= zUQW{Id;QG~ZEwg@o|KZkE8GAaACPFl8cj*bNRT5use0$&&*qEKLTEA zoIHW{vG>hgLuM!Vo)g1zrT5wZ;8S!77WW!{M=T6)wek1&*XGdUPxP#_oMe1yPPVj7 z4nKA3)MAj?EsDC=qhqxIvxC3gn(5sukI7MGLtA|RI3s3S1|Zc2_i*QjM5{A~d2W!N z?v{L9L*Q0rDG~c6H>2V;;R;-~xzCJp7QgMN7upYIqP6)p#eB)A3;zDcv#V-aD z9&&mCt+~M;4^!<%QDZ>e$;{;CIf67gQnN83k~cj)eahnlBQ>RBX7>pl6nZGJk-Tes z+bx8df+vL{^tGu%Tfoj6`14njakH(fzAMA1YM>neSo}TKzlU@G;7bqjdUJCVLiW7> zeim@N!E+RNKb2?FMwUv7i^JU(aVn2c>)=>RLm3KSVE2pnNj1F2KlEyF2qx)!Gh}*b zv_-`kGlxGvIUkTs76YRqz&l8#YtFt4fE0-HfxGlqp&fPs<4EfD&+G(7cJxqe8+pIVg&q=q<2Pxtw7M!G zCT29wNNe+5Q!>{4_UcvZTVToKEqOU5T?O*itqqfm4AeT@e=SJ!sdh2)?GhCe zD=sS&is-InyclGj@|fOb1*0NhlaLT4$(jxO%l?>Fx=h2WASdJD)p9r!q};EE%`RUC z(q=AHT;?rhE*YAelkZU|mH#nHS@;@254oJ$IaTO`lgMsb;P$cbLxRM~hs|L)BnAN0 zoX8_A@kJW;Yn0ZL$qz@Jo$x|Q=GC~srSsO?z}9k492!JYhPD9RyB5?4&&DBo4gu2x z&X9u-b>2#(UyfAVVJD>xS6tDr z6Wx^%B6=5IJNLJ9G<`pGb*fNek)!#~D<~v@WQ-^F)&GI}w@&k305L-QYCkPWfv4ULFvZ45xVG=)zvZ%i$IWu3IQJqrD+g9JVEm^LZgy~s6&xa%`KNv zy&lV<`2!*#8yg!$ifOrILjp-8&*V+P32JJ!!qU={+Mr6J_JLz-#izFSHstEPRe#)TweJt{Nu$C|7YBE&TdPss0nW4=< zmiU8*YVTP`7*1K4$ur@%$LTPwHmJ1Q^yNI>W?#i}U#en8XftGJFUj|4%j&Yef~6OY zAr0FK^m_9rhOlFbU&~QK?W_q_5bWFHQ=_tEX4YC_UTf*2l*PS6OjyvIg&F5?YK)c|1 z@@r==A_0;!i_7V=UJcVQovvjxHT#^=8oNQkBCM55Id`m*Ih0n3yaBj?_N0cvKNeOfxW;Ae7r-i;PDr0 z$S|<^%p4A(;b6D?`t6%j?tZtp9GY}nAAea@_(n&ZUge+Fm>j>2&u^fK2Kh_!Pf8ND zr&1hA$>6^qc;+}Jx4s@IfLZL+wvgF?R@)VX?e&%ssJ0DdyC&S4I0%ZI^@tfsyqPjv!0HLoZ5M5}q>3>0& zt>x|xWGNt-op{<-sIT4o`gOMUW4X;1xe{p^9By!C#=6{pp|YJ_y^@aJ1@*;#?A3MlVZKM~*oQfzz;Ug`u-cHDGmqUR;euWuG7{*t0xg_YQq-_kI`ky6qLH?J*_9LHl8*%e4LEZ?>=5p zddNoN(Z?sL37-?pO0QM}xS_B8FX)HUC_~G%A~>)iG+_N6>Vd%51)DxB0hq6lVLN7K zpn#)2zH$TZJ2ej2 zGxUV9bcP2Uo%?D$F^m4!Bd=&Y83JFjR}m~suNJ%^!TM8Per`2)R$ej@9}+w3`LL*U zb&EP&-2zG9L-{2)CGf_up6#)yS5Uv{oBIJO7RF5yiqc9biNG0`=v2k&pZVKQ8DzlI zPvU|iUe}1k*x1T*OBS*vl~58jU_LYYJcL&{YC{2p#diTk$jhsucj2hqSrlVw1T&Yf zDSZCQgWd(-2DLM*U(O+hxwZJ2J*5)yX2!#mhyL>droyX7YeS)sj>lR`ngW7{7qBS8 zhG!^7puUrib;PVY(#$J+D`tf8sfON>kb9dZkch1QRi46?ovn(YqwnGTg6&ZfUgfE! zFkqz$4H;Py7Zv^Cr;KB;bAE}QBqA~JX+~*oW0InIAb42tE^6k!sNufHobnNtF#8G? zUD4|o(@6WD(FhA~VBDyB>?@x?e=sKxao*T?-n(usH(yR5+qHmbB#=EroRgP5MVaUG zE{i^p9dG+kM+j1NnLp(!R~{oEZ5UG(-U#2Nn?m%p#A-c(w_M!V3Or*rW%-E6x3Kx$ zdQdCZolm+j4=Kd!Jdh`Z8XMytm14EmH-~$y2V3%sD(i>dae7-?Re2Pll)*p+Pf1H z<@@cSsE$59LUxewi1gg{k|ry}-tbnwzS;1{ksk%^vw5~wMI-Osopf&ZN*_}$ZCbXy zGi~y_rJI>R%HFLo6L)AubmV6zAg^X8{o+4pms1rbGT)CtWiGa!e;9qlD*uX;qLYEx z{pEh!O9|CVqn~*SJJppgDDwio&Jj6WI>qmgppP!6QFP&;7j(O7z?;^7Uqu; zX9sYZxephN6a}26HI2rkJw4udzEtjMK+&_IeU3)rqQ7>LNDS`2{?U&4BXQ+~HTC?T zN71b49wr}>Ed6P>0-W*TxH%h16`i1uk$uFEv<82gt4|;% zxYmoYqNON(hP`oa@s!5f>$fcV?F9av$s}9JJ*F@|(0JXXY?mWHct^uH1>Yj=j^W${ q;6apoOZh$W6KW~`zj(sz0mUDQW>Yc07kofZLJ&0dG|JR%!u|*QuCHhS literal 0 HcmV?d00001 diff --git a/qwt/doc/images/graph.png b/qwt/doc/images/graph.png new file mode 100644 index 0000000000000000000000000000000000000000..55f05b876d2e0e1b70b1fef6a480c15ab362a0e0 GIT binary patch literal 13144 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

jPZcfhXguO4j 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 literal 0 HcmV?d00001 diff --git a/qwt/doc/images/plot.png b/qwt/doc/images/plot.png new file mode 100644 index 0000000000000000000000000000000000000000..d0881034dfe485190f5ba7719068eb02704d5b75 GIT binary patch literal 31037 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 YpY5E8-s1f81N*D{(n?ZU$R}_9A5*#v`~Uy| literal 0 HcmV?d00001 diff --git a/qwt/doc/images/scatterplot.png b/qwt/doc/images/scatterplot.png new file mode 100644 index 0000000000000000000000000000000000000000..fd157f0b243428812118384b7aab18d8174d5e2c GIT binary patch literal 23882 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 literal 0 HcmV?d00001 diff --git a/qwt/doc/images/spectrogram1.png b/qwt/doc/images/spectrogram1.png new file mode 100644 index 0000000000000000000000000000000000000000..e2774e93bbc1bf6392f19b79d2b7a4a2dd8ba4bf GIT binary patch literal 47416 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 b?_VlF-4^kTX@zt|z?Y_~wn~|jW$6C`GKOLq literal 0 HcmV?d00001 diff --git a/qwt/doc/images/spectrogram2.png b/qwt/doc/images/spectrogram2.png new file mode 100644 index 0000000000000000000000000000000000000000..dcc33857d74f3cadd6d41c23053558c7a94a228a GIT binary patch literal 16812 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

xz%C^#`-s4# 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 z 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

&Vs$g~B|@mjoPiErmW3#5u#X1$ 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

a) Assign curve properties
+
When a curve is created, it is configured to draw black solid lines + with in QwtPlotCurve::Lines style and no symbols. + You can change this by calling + setPen(), setStyle() and setSymbol().
+
b) Connect/Assign data.
+
QwtPlotCurve gets its points using a QwtSeriesData object offering + a bridge to the real storage of the points ( like QAbstractItemModel ). + There are several convenience classes derived from QwtSeriesData, that also store + the points inside ( like QStandardItemModel ). QwtPlotCurve also offers + a couple of variations of setSamples(), that build QwtSeriesData objects from + arrays internally.
+
c) Attach the curve to a plot
+
See QwtPlotItem::attach() +
+ + \par Example: + see examples/bode + + \sa QwtPointSeriesData, QwtSymbol, QwtScaleMap +*/ +class QWT_EXPORT QwtPlotCurve: + public QwtPlotSeriesItem, public QwtSeriesStore +{ +public: + /*! + Curve styles. + \sa setStyle(), style() + */ + enum CurveStyle + { + /*! + Don't draw a curve. Note: This doesn't affect the symbols. + */ + NoCurve = -1, + + /*! + Connect the points with straight lines. The lines might + be interpolated depending on the 'Fitted' attribute. Curve + fitting can be configured using setCurveFitter(). + */ + Lines, + + /*! + Draw vertical or horizontal sticks ( depending on the + orientation() ) from a baseline which is defined by setBaseline(). + */ + Sticks, + + /*! + Connect the points with a step function. The step function + is drawn from the left to the right or vice versa, + depending on the QwtPlotCurve::Inverted attribute. + */ + Steps, + + /*! + Draw dots at the locations of the data points. Note: + This is different from a dotted line (see setPen()), and faster + as a curve in QwtPlotCurve::NoStyle style and a symbol + painting a point. + */ + Dots, + + /*! + Styles >= QwtPlotCurve::UserCurve are reserved for derived + classes of QwtPlotCurve that overload drawCurve() with + additional application specific curve types. + */ + UserCurve = 100 + }; + + /*! + Attribute for drawing the curve + \sa setCurveAttribute(), testCurveAttribute(), curveFitter() + */ + enum CurveAttribute + { + /*! + For QwtPlotCurve::Steps only. + Draws a step function from the right to the left. + */ + Inverted = 0x01, + + /*! + Only in combination with QwtPlotCurve::Lines + A QwtCurveFitter tries to + interpolate/smooth the curve, before it is painted. + + \note Curve fitting requires temporary memory + for calculating coefficients and additional points. + If painting in QwtPlotCurve::Fitted mode is slow it might be better + to fit the points, before they are passed to QwtPlotCurve. + */ + Fitted = 0x02 + }; + + //! Curve attributes + typedef QFlags CurveAttributes; + + /*! + Attributes how to represent the curve on the legend + + \sa setLegendAttribute(), testLegendAttribute(), + QwtPlotItem::legendData(), legendIcon() + */ + + enum LegendAttribute + { + /*! + QwtPlotCurve tries to find a color representing the curve + and paints a rectangle with it. + */ + LegendNoAttribute = 0x00, + + /*! + If the style() is not QwtPlotCurve::NoCurve a line + is painted with the curve pen(). + */ + LegendShowLine = 0x01, + + /*! + If the curve has a valid symbol it is painted. + */ + LegendShowSymbol = 0x02, + + /*! + If the curve has a brush a rectangle filled with the + curve brush() is painted. + */ + LegendShowBrush = 0x04 + }; + + //! Legend attributes + typedef QFlags LegendAttributes; + + /*! + Attributes to modify the drawing algorithm. + The default setting enables ClipPolygons | FilterPoints + + \sa setPaintAttribute(), testPaintAttribute() + */ + enum PaintAttribute + { + /*! + Clip polygons before painting them. In situations, where points + are far outside the visible area (f.e when zooming deep) this + might be a substantial improvement for the painting performance + */ + ClipPolygons = 0x01, + + /*! + Tries to reduce the data that has to be painted, by sorting out + duplicates, or paintings outside the visible area. Might have a + notable impact on curves with many close points. + Only a couple of very basic filtering algorithms are implemented. + */ + FilterPoints = 0x02, + + /*! + Minimize memory usage that is temporarily needed for the + translated points, before they get painted. + This might slow down the performance of painting + */ + MinimizeMemory = 0x04, + + /*! + Render the points to a temporary image and paint the image. + This is a very special optimization for Dots style, when + having a huge amount of points. + With a reasonable number of points QPainter::drawPoints() + will be faster. + */ + ImageBuffer = 0x08 + }; + + //! Paint attributes + typedef QFlags PaintAttributes; + + explicit QwtPlotCurve( const QString &title = QString::null ); + explicit QwtPlotCurve( const QwtText &title ); + + virtual ~QwtPlotCurve(); + + virtual int rtti() const; + + void setPaintAttribute( PaintAttribute, bool on = true ); + bool testPaintAttribute( PaintAttribute ) const; + + void setLegendAttribute( LegendAttribute, bool on = true ); + bool testLegendAttribute( LegendAttribute ) const; + +#ifndef QWT_NO_COMPAT + void setRawSamples( const double *xData, const double *yData, int size ); + void setSamples( const double *xData, const double *yData, int size ); + void setSamples( const QVector &xData, const QVector &yData ); +#endif + void setSamples( const QVector & ); + void setSamples( QwtSeriesData * ); + + int closestPoint( const QPoint &pos, double *dist = NULL ) const; + + double minXValue() const; + double maxXValue() const; + double minYValue() const; + double maxYValue() const; + + void setCurveAttribute( CurveAttribute, bool on = true ); + bool testCurveAttribute( CurveAttribute ) const; + + void setPen( const QColor &, qreal width = 0.0, Qt::PenStyle = Qt::SolidLine ); + void setPen( const QPen & ); + const QPen &pen() const; + + void setBrush( const QBrush & ); + const QBrush &brush() const; + + void setBaseline( double ); + double baseline() const; + + void setStyle( CurveStyle style ); + CurveStyle style() const; + + void setSymbol( QwtSymbol * ); + const QwtSymbol *symbol() const; + + void setCurveFitter( QwtCurveFitter * ); + QwtCurveFitter *curveFitter() const; + + virtual void drawSeries( QPainter *, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual QwtGraphic legendIcon( int index, const QSizeF & ) const; + +protected: + + void init(); + + virtual void drawCurve( QPainter *p, int style, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual void drawSymbols( QPainter *p, const QwtSymbol &, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual void drawLines( QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual void drawSticks( QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual void drawDots( QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual void drawSteps( QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual void fillCurve( QPainter *, + const QwtScaleMap &, const QwtScaleMap &, + const QRectF &canvasRect, QPolygonF & ) const; + + void closePolyline( QPainter *, + const QwtScaleMap &, const QwtScaleMap &, QPolygonF & ) const; + +private: + class PrivateData; + PrivateData *d_data; +}; + +//! boundingRect().left() +inline double QwtPlotCurve::minXValue() const +{ + return boundingRect().left(); +} + +//! boundingRect().right() +inline double QwtPlotCurve::maxXValue() const +{ + return boundingRect().right(); +} + +//! boundingRect().top() +inline double QwtPlotCurve::minYValue() const +{ + return boundingRect().top(); +} + +//! boundingRect().bottom() +inline double QwtPlotCurve::maxYValue() const +{ + return boundingRect().bottom(); +} + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotCurve::PaintAttributes ) +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotCurve::LegendAttributes ) +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotCurve::CurveAttributes ) + +#endif diff --git a/qwt/src/qwt_plot_dict.cpp b/qwt/src/qwt_plot_dict.cpp new file mode 100644 index 000000000..17c61ed47 --- /dev/null +++ b/qwt/src/qwt_plot_dict.cpp @@ -0,0 +1,191 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_dict.h" + +class QwtPlotDict::PrivateData +{ +public: + + class ItemList: public QList + { + public: + void insertItem( QwtPlotItem *item ) + { + if ( item == NULL ) + return; + + QList::iterator it = + qUpperBound( begin(), end(), item, LessZThan() ); + insert( it, item ); + } + + void removeItem( QwtPlotItem *item ) + { + if ( item == NULL ) + return; + + QList::iterator it = + qLowerBound( begin(), end(), item, LessZThan() ); + + for ( ; it != end(); ++it ) + { + if ( item == *it ) + { + erase( it ); + break; + } + } + } + private: + class LessZThan + { + public: + inline bool operator()( const QwtPlotItem *item1, + const QwtPlotItem *item2 ) const + { + return item1->z() < item2->z(); + } + }; + }; + + ItemList itemList; + bool autoDelete; +}; + +/*! + Constructor + + Auto deletion is enabled. + \sa setAutoDelete(), QwtPlotItem::attach() +*/ +QwtPlotDict::QwtPlotDict() +{ + d_data = new QwtPlotDict::PrivateData; + d_data->autoDelete = true; +} + +/*! + Destructor + + If autoDelete() is on, all attached items will be deleted + \sa setAutoDelete(), autoDelete(), QwtPlotItem::attach() +*/ +QwtPlotDict::~QwtPlotDict() +{ + detachItems( QwtPlotItem::Rtti_PlotItem, d_data->autoDelete ); + delete d_data; +} + +/*! + En/Disable Auto deletion + + If Auto deletion is on all attached plot items will be deleted + in the destructor of QwtPlotDict. The default value is on. + + \sa autoDelete(), insertItem() +*/ +void QwtPlotDict::setAutoDelete( bool autoDelete ) +{ + d_data->autoDelete = autoDelete; +} + +/*! + \return true if auto deletion is enabled + \sa setAutoDelete(), insertItem() +*/ +bool QwtPlotDict::autoDelete() const +{ + return d_data->autoDelete; +} + +/*! + Insert a plot item + + \param item PlotItem + \sa removeItem() + */ +void QwtPlotDict::insertItem( QwtPlotItem *item ) +{ + d_data->itemList.insertItem( item ); +} + +/*! + Remove a plot item + + \param item PlotItem + \sa insertItem() + */ +void QwtPlotDict::removeItem( QwtPlotItem *item ) +{ + d_data->itemList.removeItem( item ); +} + +/*! + Detach items from the dictionary + + \param rtti In case of QwtPlotItem::Rtti_PlotItem detach all items + otherwise only those items of the type rtti. + \param autoDelete If true, delete all detached items +*/ +void QwtPlotDict::detachItems( int rtti, bool autoDelete ) +{ + PrivateData::ItemList list = d_data->itemList; + QwtPlotItemIterator it = list.begin(); + while ( it != list.end() ) + { + QwtPlotItem *item = *it; + + ++it; // increment before removing item from the list + + if ( rtti == QwtPlotItem::Rtti_PlotItem || item->rtti() == rtti ) + { + item->attach( NULL ); + if ( autoDelete ) + delete item; + } + } +} + +/*! + \brief A QwtPlotItemList of all attached plot items. + + Use caution when iterating these lists, as removing/detaching an item will + invalidate the iterator. Instead you can place pointers to objects to be + removed in a removal list, and traverse that list later. + + \return List of all attached plot items. +*/ +const QwtPlotItemList &QwtPlotDict::itemList() const +{ + return d_data->itemList; +} + +/*! + \return List of all attached plot items of a specific type. + \param rtti See QwtPlotItem::RttiValues + \sa QwtPlotItem::rtti() +*/ +QwtPlotItemList QwtPlotDict::itemList( int rtti ) const +{ + if ( rtti == QwtPlotItem::Rtti_PlotItem ) + return d_data->itemList; + + QwtPlotItemList items; + + PrivateData::ItemList list = d_data->itemList; + for ( QwtPlotItemIterator it = list.begin(); it != list.end(); ++it ) + { + QwtPlotItem *item = *it; + if ( item->rtti() == rtti ) + items += item; + } + + return items; +} diff --git a/qwt/src/qwt_plot_dict.h b/qwt/src/qwt_plot_dict.h new file mode 100644 index 000000000..5d34f0c05 --- /dev/null +++ b/qwt/src/qwt_plot_dict.h @@ -0,0 +1,58 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +/*! \file !*/ +#ifndef QWT_PLOT_DICT +#define QWT_PLOT_DICT + +#include "qwt_global.h" +#include "qwt_plot_item.h" +#include + +/// \var typedef QList< QwtPlotItem *> QwtPlotItemList +/// \brief See QT 4.x assistant documentation for QList +typedef QList QwtPlotItemList; +typedef QList::ConstIterator QwtPlotItemIterator; + +/*! + \brief A dictionary for plot items + + QwtPlotDict organizes plot items in increasing z-order. + If autoDelete() is enabled, all attached items will be deleted + in the destructor of the dictionary. + QwtPlotDict can be used to get access to all QwtPlotItem items - or all + items of a specific type - that are currently on the plot. + + \sa QwtPlotItem::attach(), QwtPlotItem::detach(), QwtPlotItem::z() +*/ +class QWT_EXPORT QwtPlotDict +{ +public: + explicit QwtPlotDict(); + virtual ~QwtPlotDict(); + + void setAutoDelete( bool ); + bool autoDelete() const; + + const QwtPlotItemList& itemList() const; + QwtPlotItemList itemList( int rtti ) const; + + void detachItems( int rtti = QwtPlotItem::Rtti_PlotItem, + bool autoDelete = true ); + +protected: + void insertItem( QwtPlotItem * ); + void removeItem( QwtPlotItem * ); + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_directpainter.cpp b/qwt/src/qwt_plot_directpainter.cpp new file mode 100644 index 000000000..d78352740 --- /dev/null +++ b/qwt/src/qwt_plot_directpainter.cpp @@ -0,0 +1,317 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_directpainter.h" +#include "qwt_scale_map.h" +#include "qwt_plot.h" +#include "qwt_plot_canvas.h" +#include "qwt_plot_seriesitem.h" +#include +#include +#include +#include + +static inline void qwtRenderItem( + QPainter *painter, const QRect &canvasRect, + QwtPlotSeriesItem *seriesItem, int from, int to ) +{ + // A minor performance improvement is possible + // with caching the maps. TODO ... + + QwtPlot *plot = seriesItem->plot(); + const QwtScaleMap xMap = plot->canvasMap( seriesItem->xAxis() ); + const QwtScaleMap yMap = plot->canvasMap( seriesItem->yAxis() ); + + painter->setRenderHint( QPainter::Antialiasing, + seriesItem->testRenderHint( QwtPlotItem::RenderAntialiased ) ); + seriesItem->drawSeries( painter, xMap, yMap, canvasRect, from, to ); +} + +static inline bool qwtHasBackingStore( const QwtPlotCanvas *canvas ) +{ + return canvas->testPaintAttribute( QwtPlotCanvas::BackingStore ) + && canvas->backingStore() && !canvas->backingStore()->isNull(); +} + +class QwtPlotDirectPainter::PrivateData +{ +public: + PrivateData(): + attributes( 0 ), + hasClipping(false), + seriesItem( NULL ) + { + } + + QwtPlotDirectPainter::Attributes attributes; + + bool hasClipping; + QRegion clipRegion; + + QPainter painter; + + QwtPlotSeriesItem *seriesItem; + int from; + int to; +}; + +//! Constructor +QwtPlotDirectPainter::QwtPlotDirectPainter( QObject *parent ): + QObject( parent ) +{ + d_data = new PrivateData; +} + +//! Destructor +QwtPlotDirectPainter::~QwtPlotDirectPainter() +{ + delete d_data; +} + +/*! + Change an attribute + + \param attribute Attribute to change + \param on On/Off + + \sa Attribute, testAttribute() +*/ +void QwtPlotDirectPainter::setAttribute( Attribute attribute, bool on ) +{ + if ( bool( d_data->attributes & attribute ) != on ) + { + if ( on ) + d_data->attributes |= attribute; + else + d_data->attributes &= ~attribute; + + if ( ( attribute == AtomicPainter ) && on ) + reset(); + } +} + +/*! + \return True, when attribute is enabled + \param attribute Attribute to be tested + \sa Attribute, setAttribute() +*/ +bool QwtPlotDirectPainter::testAttribute( Attribute attribute ) const +{ + return d_data->attributes & attribute; +} + +/*! + En/Disables clipping + + \param enable Enables clipping is true, disable it otherwise + \sa hasClipping(), clipRegion(), setClipRegion() +*/ +void QwtPlotDirectPainter::setClipping( bool enable ) +{ + d_data->hasClipping = enable; +} + +/*! + \return true, when clipping is enabled + \sa setClipping(), clipRegion(), setClipRegion() +*/ +bool QwtPlotDirectPainter::hasClipping() const +{ + return d_data->hasClipping; +} + +/*! + \brief Assign a clip region and enable clipping + + Depending on the environment setting a proper clip region might improve + the performance heavily. F.e. on Qt embedded only the clipped part of + the backing store will be copied to a ( maybe unaccelerated ) frame buffer + device. + + \param region Clip region + \sa clipRegion(), hasClipping(), setClipping() +*/ +void QwtPlotDirectPainter::setClipRegion( const QRegion ®ion ) +{ + d_data->clipRegion = region; + d_data->hasClipping = true; +} + +/*! + \return Currently set clip region. + \sa setClipRegion(), setClipping(), hasClipping() +*/ +QRegion QwtPlotDirectPainter::clipRegion() const +{ + return d_data->clipRegion; +} + +/*! + \brief Draw a set of points of a seriesItem. + + When observing an measurement while it is running, new points have to be + added to an existing seriesItem. drawSeries() can be used to display them avoiding + a complete redraw of the canvas. + + Setting plot()->canvas()->setAttribute(Qt::WA_PaintOutsidePaintEvent, true); + will result in faster painting, if the paint engine of the canvas widget + supports this feature. + + \param seriesItem Item to be painted + \param from Index of the first point to be painted + \param to Index of the last point to be painted. If to < 0 the + series will be painted to its last point. +*/ +void QwtPlotDirectPainter::drawSeries( + QwtPlotSeriesItem *seriesItem, int from, int to ) +{ + if ( seriesItem == NULL || seriesItem->plot() == NULL ) + return; + + QWidget *canvas = seriesItem->plot()->canvas(); + const QRect canvasRect = canvas->contentsRect(); + + QwtPlotCanvas *plotCanvas = qobject_cast( canvas ); + + if ( plotCanvas && qwtHasBackingStore( plotCanvas ) ) + { + QPainter painter( const_cast( plotCanvas->backingStore() ) ); + + if ( d_data->hasClipping ) + painter.setClipRegion( d_data->clipRegion ); + + qwtRenderItem( &painter, canvasRect, seriesItem, from, to ); + + if ( testAttribute( QwtPlotDirectPainter::FullRepaint ) ) + { + plotCanvas->repaint(); + return; + } + } + + bool immediatePaint = true; + if ( !canvas->testAttribute( Qt::WA_WState_InPaintEvent ) ) + { +#if QT_VERSION < 0x050000 + if ( !canvas->testAttribute( Qt::WA_PaintOutsidePaintEvent ) ) +#endif + immediatePaint = false; + } + + if ( immediatePaint ) + { + if ( !d_data->painter.isActive() ) + { + reset(); + + d_data->painter.begin( canvas ); + canvas->installEventFilter( this ); + } + + if ( d_data->hasClipping ) + { + d_data->painter.setClipRegion( + QRegion( canvasRect ) & d_data->clipRegion ); + } + else + { + if ( !d_data->painter.hasClipping() ) + d_data->painter.setClipRect( canvasRect ); + } + + qwtRenderItem( &d_data->painter, canvasRect, seriesItem, from, to ); + + if ( d_data->attributes & QwtPlotDirectPainter::AtomicPainter ) + { + reset(); + } + else + { + if ( d_data->hasClipping ) + d_data->painter.setClipping( false ); + } + } + else + { + reset(); + + d_data->seriesItem = seriesItem; + d_data->from = from; + d_data->to = to; + + QRegion clipRegion = canvasRect; + if ( d_data->hasClipping ) + clipRegion &= d_data->clipRegion; + + canvas->installEventFilter( this ); + canvas->repaint(clipRegion); + canvas->removeEventFilter( this ); + + d_data->seriesItem = NULL; + } +} + +//! Close the internal QPainter +void QwtPlotDirectPainter::reset() +{ + if ( d_data->painter.isActive() ) + { + QWidget *w = static_cast( d_data->painter.device() ); + if ( w ) + w->removeEventFilter( this ); + + d_data->painter.end(); + } +} + +//! Event filter +bool QwtPlotDirectPainter::eventFilter( QObject *, QEvent *event ) +{ + if ( event->type() == QEvent::Paint ) + { + reset(); + + if ( d_data->seriesItem ) + { + const QPaintEvent *pe = static_cast< QPaintEvent *>( event ); + + QWidget *canvas = d_data->seriesItem->plot()->canvas(); + + QPainter painter( canvas ); + painter.setClipRegion( pe->region() ); + + bool doCopyCache = testAttribute( CopyBackingStore ); + + if ( doCopyCache ) + { + QwtPlotCanvas *plotCanvas = + qobject_cast( canvas ); + if ( plotCanvas ) + { + doCopyCache = qwtHasBackingStore( plotCanvas ); + if ( doCopyCache ) + { + painter.drawPixmap( plotCanvas->contentsRect().topLeft(), + *plotCanvas->backingStore() ); + } + } + } + + if ( !doCopyCache ) + { + qwtRenderItem( &painter, canvas->contentsRect(), + d_data->seriesItem, d_data->from, d_data->to ); + } + + return true; // don't call QwtPlotCanvas::paintEvent() + } + } + + return false; +} diff --git a/qwt/src/qwt_plot_directpainter.h b/qwt/src/qwt_plot_directpainter.h new file mode 100644 index 000000000..b555c87c3 --- /dev/null +++ b/qwt/src/qwt_plot_directpainter.h @@ -0,0 +1,100 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_DIRECT_PAINTER_H +#define QWT_PLOT_DIRECT_PAINTER_H + +#include "qwt_global.h" +#include + +class QRegion; +class QwtPlotSeriesItem; + +/*! + \brief Painter object trying to paint incrementally + + Often applications want to display samples while they are + collected. When there are too many samples complete replots + will be expensive to be processed in a collection cycle. + + QwtPlotDirectPainter offers an API to paint + subsets ( f.e all additions points ) without erasing/repainting + the plot canvas. + + On certain environments it might be important to calculate a proper + clip region before painting. F.e. for Qt Embedded only the clipped part + of the backing store will be copied to a ( maybe unaccelerated ) + frame buffer. + + \warning Incremental painting will only help when no replot is triggered + by another operation ( like changing scales ) and nothing needs + to be erased. +*/ +class QWT_EXPORT QwtPlotDirectPainter: public QObject +{ +public: + /*! + \brief Paint attributes + \sa setAttribute(), testAttribute(), drawSeries() + */ + enum Attribute + { + /*! + Initializing a QPainter is an expensive operation. + When AtomicPainter is set each call of drawSeries() opens/closes + a temporary QPainter. Otherwise QwtPlotDirectPainter tries to + use the same QPainter as long as possible. + */ + AtomicPainter = 0x01, + + /*! + When FullRepaint is set the plot canvas is explicitly repainted + after the samples have been rendered. + */ + FullRepaint = 0x02, + + /*! + When QwtPlotCanvas::BackingStore is enabled the painter + has to paint to the backing store and the widget. In certain + situations/environments it might be faster to paint to + the backing store only and then copy the backing store to the canvas. + This flag can also be useful for settings, where Qt fills the + the clip region with the widget background. + */ + CopyBackingStore = 0x04 + }; + + //! Paint attributes + typedef QFlags Attributes; + + QwtPlotDirectPainter( QObject *parent = NULL ); + virtual ~QwtPlotDirectPainter(); + + void setAttribute( Attribute, bool on ); + bool testAttribute( Attribute ) const; + + void setClipping( bool ); + bool hasClipping() const; + + void setClipRegion( const QRegion & ); + QRegion clipRegion() const; + + void drawSeries( QwtPlotSeriesItem *, int from, int to ); + void reset(); + + virtual bool eventFilter( QObject *, QEvent * ); + +private: + class PrivateData; + PrivateData *d_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotDirectPainter::Attributes ) + +#endif diff --git a/qwt/src/qwt_plot_glcanvas.cpp b/qwt/src/qwt_plot_glcanvas.cpp new file mode 100644 index 000000000..87a4cfde7 --- /dev/null +++ b/qwt/src/qwt_plot_glcanvas.cpp @@ -0,0 +1,357 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_glcanvas.h" +#include "qwt_plot.h" +#include +#include +#include +#include +#include +#include "qwt_painter.h" + +static QWidget *qwtBGWidget( QWidget *widget ) +{ + QWidget *w = widget; + + for ( ; w->parentWidget() != NULL; w = w->parentWidget() ) + { + if ( w->autoFillBackground() || + w->testAttribute( Qt::WA_StyledBackground ) ) + { + return w; + } + } + + return w; +} + +static void qwtUpdateContentsRect( QwtPlotGLCanvas *canvas ) +{ + const int fw = canvas->frameWidth(); + canvas->setContentsMargins( fw, fw, fw, fw ); +} + +class QwtPlotGLCanvas::PrivateData +{ +public: + PrivateData(): + frameStyle( QFrame::Panel | QFrame::Sunken), + lineWidth( 2 ), + midLineWidth( 0 ) + { + } + + int frameStyle; + int lineWidth; + int midLineWidth; +}; + +/*! + \brief Constructor + + \param plot Parent plot widget + \sa QwtPlot::setCanvas() +*/ +QwtPlotGLCanvas::QwtPlotGLCanvas( QwtPlot *plot ): + QGLWidget( plot ) +{ + d_data = new PrivateData; + +#ifndef QT_NO_CURSOR + setCursor( Qt::CrossCursor ); +#endif + + setAutoFillBackground( true ); + qwtUpdateContentsRect( this ); +} + +//! Destructor +QwtPlotGLCanvas::~QwtPlotGLCanvas() +{ + delete d_data; +} + +/*! + Set the frame style + + \param style The bitwise OR between a shape and a shadow. + + \sa frameStyle(), QFrame::setFrameStyle(), + setFrameShadow(), setFrameShape() + */ +void QwtPlotGLCanvas::setFrameStyle( int style ) +{ + if ( style != d_data->frameStyle ) + { + d_data->frameStyle = style; + qwtUpdateContentsRect( this ); + + update(); + } +} + +/*! + \return The bitwise OR between a frameShape() and a frameShadow() + \sa setFrameStyle(), QFrame::frameStyle() + */ +int QwtPlotGLCanvas::frameStyle() const +{ + return d_data->frameStyle; +} + +/*! + Set the frame shadow + + \param shadow Frame shadow + \sa frameShadow(), setFrameShape(), QFrame::setFrameShadow() + */ +void QwtPlotGLCanvas::setFrameShadow( Shadow shadow ) +{ + setFrameStyle(( d_data->frameStyle & QFrame::Shape_Mask ) | shadow ); +} + +/*! + \return Frame shadow + \sa setFrameShadow(), QFrame::setFrameShadow() + */ +QwtPlotGLCanvas::Shadow QwtPlotGLCanvas::frameShadow() const +{ + return (Shadow) ( d_data->frameStyle & QFrame::Shadow_Mask ); +} + +/*! + Set the frame shape + + \param shape Frame shape + \sa frameShape(), setFrameShadow(), QFrame::frameShape() + */ +void QwtPlotGLCanvas::setFrameShape( Shape shape ) +{ + setFrameStyle( ( d_data->frameStyle & QFrame::Shadow_Mask ) | shape ); +} + +/*! + \return Frame shape + \sa setFrameShape(), QFrame::frameShape() + */ +QwtPlotGLCanvas::Shape QwtPlotGLCanvas::frameShape() const +{ + return (Shape) ( d_data->frameStyle & QFrame::Shape_Mask ); +} + +/*! + Set the frame line width + + The default line width is 2 pixels. + + \param width Line width of the frame + \sa lineWidth(), setMidLineWidth() +*/ +void QwtPlotGLCanvas::setLineWidth( int width ) +{ + width = qMax( width, 0 ); + if ( width != d_data->lineWidth ) + { + d_data->lineWidth = qMax( width, 0 ); + qwtUpdateContentsRect( this ); + update(); + } +} + +/*! + \return Line width of the frame + \sa setLineWidth(), midLineWidth() + */ +int QwtPlotGLCanvas::lineWidth() const +{ + return d_data->lineWidth; +} + +/*! + Set the frame mid line width + + The default midline width is 0 pixels. + + \param width Midline width of the frame + \sa midLineWidth(), setLineWidth() +*/ +void QwtPlotGLCanvas::setMidLineWidth( int width ) +{ + width = qMax( width, 0 ); + if ( width != d_data->midLineWidth ) + { + d_data->midLineWidth = width; + qwtUpdateContentsRect( this ); + update(); + } +} + +/*! + \return Midline width of the frame + \sa setMidLineWidth(), lineWidth() + */ +int QwtPlotGLCanvas::midLineWidth() const +{ + return d_data->midLineWidth; +} + +/*! + \return Frame width depending on the style, line width and midline width. + */ +int QwtPlotGLCanvas::frameWidth() const +{ + return ( frameStyle() != NoFrame ) ? d_data->lineWidth : 0; +} + +/*! + Paint event + + \param event Paint event + \sa QwtPlot::drawCanvas() +*/ +void QwtPlotGLCanvas::paintEvent( QPaintEvent *event ) +{ + Q_UNUSED( event ); + + QPainter painter( this ); + + drawBackground( &painter ); + drawItems( &painter ); + + if ( !testAttribute( Qt::WA_StyledBackground ) ) + { + if ( frameWidth() > 0 ) + drawBorder( &painter ); + } +} +/*! + Qt event handler for QEvent::PolishRequest and QEvent::StyleChange + \param event Qt Event + \return See QGLWidget::event() +*/ +bool QwtPlotGLCanvas::event( QEvent *event ) +{ + const bool ok = QGLWidget::event( event ); + + if ( event->type() == QEvent::PolishRequest || + event->type() == QEvent::StyleChange ) + { + // assuming, that we always have a styled background + // when we have a style sheet + + setAttribute( Qt::WA_StyledBackground, + testAttribute( Qt::WA_StyleSheet ) ); + } + + return ok; +} + +/*! + Draw the plot items + \param painter Painter + + \sa QwtPlot::drawCanvas() +*/ +void QwtPlotGLCanvas::drawItems( QPainter *painter ) +{ + painter->save(); + + painter->setClipRect( contentsRect(), Qt::IntersectClip ); + + QwtPlot *plot = qobject_cast< QwtPlot *>( parent() ); + if ( plot ) + plot->drawCanvas( painter ); + + painter->restore(); +} + +/*! + Draw the background of the canvas + \param painter Painter +*/ +void QwtPlotGLCanvas::drawBackground( QPainter *painter ) +{ + painter->save(); + + QWidget *w = qwtBGWidget( this ); + + const QPoint off = mapTo( w, QPoint() ); + painter->translate( -off ); + + const QRect fillRect = rect().translated( off ); + + if ( w->testAttribute( Qt::WA_StyledBackground ) ) + { + painter->setClipRect( fillRect ); + + QStyleOption opt; + opt.initFrom( w ); + w->style()->drawPrimitive( QStyle::PE_Widget, &opt, painter, w); + } + else + { + painter->fillRect( fillRect, + w->palette().brush( w->backgroundRole() ) ); + } + + painter->restore(); +} + +/*! + Draw the border of the canvas + \param painter Painter +*/ +void QwtPlotGLCanvas::drawBorder( QPainter *painter ) +{ + const int fw = frameWidth(); + if ( fw <= 0 ) + return; + + if ( frameShadow() == QwtPlotGLCanvas::Plain ) + { + qDrawPlainRect( painter, frameRect(), + palette().shadow().color(), lineWidth() ); + } + else + { + if ( frameShape() == QwtPlotGLCanvas::Box ) + { + qDrawShadeRect( painter, frameRect(), palette(), + frameShadow() == Sunken, lineWidth(), midLineWidth() ); + } + else + { + qDrawShadePanel( painter, frameRect(), palette(), + frameShadow() == Sunken, lineWidth() ); + } + } +} + +//! Calls repaint() +void QwtPlotGLCanvas::replot() +{ + repaint(); +} + +/*! + \return Empty path +*/ +QPainterPath QwtPlotGLCanvas::borderPath( const QRect &rect ) const +{ + Q_UNUSED( rect ); + return QPainterPath(); +} + +//! \return The rectangle where the frame is drawn in. +QRect QwtPlotGLCanvas::frameRect() const +{ + const int fw = frameWidth(); + return contentsRect().adjusted( -fw, -fw, fw, fw ); +} diff --git a/qwt/src/qwt_plot_glcanvas.h b/qwt/src/qwt_plot_glcanvas.h new file mode 100644 index 000000000..2ff1cf2e3 --- /dev/null +++ b/qwt/src/qwt_plot_glcanvas.h @@ -0,0 +1,136 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_GLCANVAS_H +#define QWT_PLOT_GLCANVAS_H + +#include "qwt_global.h" +#include +#include + +class QwtPlot; + +/*! + \brief An alternative canvas for a QwtPlot derived from QGLWidget + + QwtPlotGLCanvas implements the very basics to act as canvas + inside of a QwtPlot widget. It might be extended to a full + featured alternative to QwtPlotCanvas in a future version of Qwt. + + Even if QwtPlotGLCanvas is not derived from QFrame it imitates + its API. When using style sheets it supports the box model - beside + backgrounds with rounded borders. + + \sa QwtPlot::setCanvas(), QwtPlotCanvas + + \note You might want to use the QPaintEngine::OpenGL paint engine + ( see QGL::setPreferredPaintEngine() ). On a Linux test system + QPaintEngine::OpenGL2 shows very basic problems ( wrong + geometries of rectangles ) but also more advanced stuff + like antialiasing doesn't work. + + \note Another way to introduce OpenGL rendering to Qwt + is to use QGLPixelBuffer or QGLFramebufferObject. Both + type of buffers can be converted into a QImage and + used in combination with a regular QwtPlotCanvas. +*/ +class QWT_EXPORT QwtPlotGLCanvas: public QGLWidget +{ + Q_OBJECT + + Q_ENUMS( Shape Shadow ) + + Q_PROPERTY( Shadow frameShadow READ frameShadow WRITE setFrameShadow ) + Q_PROPERTY( Shape frameShape READ frameShape WRITE setFrameShape ) + Q_PROPERTY( int lineWidth READ lineWidth WRITE setLineWidth ) + Q_PROPERTY( int midLineWidth READ midLineWidth WRITE setMidLineWidth ) + Q_PROPERTY( int frameWidth READ frameWidth ) + Q_PROPERTY( QRect frameRect READ frameRect DESIGNABLE false ) + +public: + /*! + \brief Frame shadow + + Unfortunately it is not possible to use QFrame::Shadow + as a property of a widget that is not derived from QFrame. + The following enum is made for the designer only. It is safe + to use QFrame::Shadow instead. + */ + enum Shadow + { + //! QFrame::Plain + Plain = QFrame::Plain, + + //! QFrame::Raised + Raised = QFrame::Raised, + + //! QFrame::Sunken + Sunken = QFrame::Sunken + }; + + /*! + \brief Frame shape + + Unfortunately it is not possible to use QFrame::Shape + as a property of a widget that is not derived from QFrame. + The following enum is made for the designer only. It is safe + to use QFrame::Shadow instead. + + \note QFrame::StyledPanel and QFrame::WinPanel are unsuported + and will be displayed as QFrame::Panel. + */ + enum Shape + { + NoFrame = QFrame::NoFrame, + + Box = QFrame::Box, + Panel = QFrame::Panel + }; + + explicit QwtPlotGLCanvas( QwtPlot * = NULL ); + virtual ~QwtPlotGLCanvas(); + + void setFrameStyle( int style ); + int frameStyle() const; + + void setFrameShadow( Shadow ); + Shadow frameShadow() const; + + void setFrameShape( Shape ); + Shape frameShape() const; + + void setLineWidth( int ); + int lineWidth() const; + + void setMidLineWidth( int ); + int midLineWidth() const; + + int frameWidth() const; + QRect frameRect() const; + + Q_INVOKABLE QPainterPath borderPath( const QRect & ) const; + + virtual bool event( QEvent * ); + +public Q_SLOTS: + void replot(); + +protected: + virtual void paintEvent( QPaintEvent * ); + + virtual void drawBackground( QPainter * ); + virtual void drawBorder( QPainter * ); + virtual void drawItems( QPainter * ); + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_grid.cpp b/qwt/src/qwt_plot_grid.cpp new file mode 100644 index 000000000..4375e53d7 --- /dev/null +++ b/qwt/src/qwt_plot_grid.cpp @@ -0,0 +1,438 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_grid.h" +#include "qwt_painter.h" +#include "qwt_text.h" +#include "qwt_scale_map.h" +#include "qwt_scale_div.h" +#include "qwt_math.h" +#include +#include + +class QwtPlotGrid::PrivateData +{ +public: + PrivateData(): + xEnabled( true ), + yEnabled( true ), + xMinEnabled( false ), + yMinEnabled( false ) + { + } + + bool xEnabled; + bool yEnabled; + bool xMinEnabled; + bool yMinEnabled; + + QwtScaleDiv xScaleDiv; + QwtScaleDiv yScaleDiv; + + QPen majorPen; + QPen minorPen; +}; + +//! Enables major grid, disables minor grid +QwtPlotGrid::QwtPlotGrid(): + QwtPlotItem( QwtText( "Grid" ) ) +{ + d_data = new PrivateData; + + setItemInterest( QwtPlotItem::ScaleInterest, true ); + setZ( 10.0 ); +} + +//! Destructor +QwtPlotGrid::~QwtPlotGrid() +{ + delete d_data; +} + +//! \return QwtPlotItem::Rtti_PlotGrid +int QwtPlotGrid::rtti() const +{ + return QwtPlotItem::Rtti_PlotGrid; +} + +/*! + \brief Enable or disable vertical grid lines + \param on Enable (true) or disable + + \sa Minor grid lines can be enabled or disabled with + enableXMin() +*/ +void QwtPlotGrid::enableX( bool on ) +{ + if ( d_data->xEnabled != on ) + { + d_data->xEnabled = on; + + legendChanged(); + itemChanged(); + } +} + +/*! + \brief Enable or disable horizontal grid lines + \param on Enable (true) or disable + \sa Minor grid lines can be enabled or disabled with enableYMin() +*/ +void QwtPlotGrid::enableY( bool on ) +{ + if ( d_data->yEnabled != on ) + { + d_data->yEnabled = on; + + legendChanged(); + itemChanged(); + } +} + +/*! + \brief Enable or disable minor vertical grid lines. + \param on Enable (true) or disable + \sa enableX() +*/ +void QwtPlotGrid::enableXMin( bool on ) +{ + if ( d_data->xMinEnabled != on ) + { + d_data->xMinEnabled = on; + + legendChanged(); + itemChanged(); + } +} + +/*! + \brief Enable or disable minor horizontal grid lines + \param on Enable (true) or disable + \sa enableY() +*/ +void QwtPlotGrid::enableYMin( bool on ) +{ + if ( d_data->yMinEnabled != on ) + { + d_data->yMinEnabled = on; + + legendChanged(); + itemChanged(); + } +} + +/*! + Assign an x axis scale division + + \param scaleDiv Scale division +*/ +void QwtPlotGrid::setXDiv( const QwtScaleDiv &scaleDiv ) +{ + if ( d_data->xScaleDiv != scaleDiv ) + { + d_data->xScaleDiv = scaleDiv; + itemChanged(); + } +} + +/*! + Assign a y axis division + + \param scaleDiv Scale division +*/ +void QwtPlotGrid::setYDiv( const QwtScaleDiv &scaleDiv ) +{ + if ( d_data->yScaleDiv != scaleDiv ) + { + d_data->yScaleDiv = scaleDiv; + itemChanged(); + } +} + +/*! + Build and assign a pen for both major and minor grid lines + + In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it + non cosmetic ( see QPen::isCosmetic() ). This method has been introduced + to hide this incompatibility. + + \param color Pen color + \param width Pen width + \param style Pen style + + \sa pen(), brush() + */ +void QwtPlotGrid::setPen( const QColor &color, qreal width, Qt::PenStyle style ) +{ + setPen( QPen( color, width, style ) ); +} + +/*! + Assign a pen for both major and minor grid lines + + \param pen Pen + \sa setMajorPen(), setMinorPen() +*/ +void QwtPlotGrid::setPen( const QPen &pen ) +{ + if ( d_data->majorPen != pen || d_data->minorPen != pen ) + { + d_data->majorPen = pen; + d_data->minorPen = pen; + + legendChanged(); + itemChanged(); + } +} + +/*! + Build and assign a pen for both major grid lines + + In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it + non cosmetic ( see QPen::isCosmetic() ). This method has been introduced + to hide this incompatibility. + + \param color Pen color + \param width Pen width + \param style Pen style + + \sa pen(), brush() + */ +void QwtPlotGrid::setMajorPen( const QColor &color, qreal width, Qt::PenStyle style ) +{ + setMajorPen( QPen( color, width, style ) ); +} + +/*! + Assign a pen for the major grid lines + + \param pen Pen + \sa majorPen(), setMinorPen(), setPen() +*/ +void QwtPlotGrid::setMajorPen( const QPen &pen ) +{ + if ( d_data->majorPen != pen ) + { + d_data->majorPen = pen; + + legendChanged(); + itemChanged(); + } +} + +/*! + Build and assign a pen for the minor grid lines + + In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it + non cosmetic ( see QPen::isCosmetic() ). This method has been introduced + to hide this incompatibility. + + \param color Pen color + \param width Pen width + \param style Pen style + + \sa pen(), brush() + */ +void QwtPlotGrid::setMinorPen( const QColor &color, qreal width, Qt::PenStyle style ) +{ + setMinorPen( QPen( color, width, style ) ); +} + +/*! + Assign a pen for the minor grid lines + + \param pen Pen + \sa minorPen(), setMajorPen(), setPen() +*/ +void QwtPlotGrid::setMinorPen( const QPen &pen ) +{ + if ( d_data->minorPen != pen ) + { + d_data->minorPen = pen; + + legendChanged(); + itemChanged(); + } +} + +/*! + \brief Draw the grid + + The grid is drawn into the bounding rectangle such that + grid lines begin and end at the rectangle's borders. The X and Y + maps are used to map the scale divisions into the drawing region + screen. + + \param painter Painter + \param xMap X axis map + \param yMap Y axis + \param canvasRect Contents rectangle of the plot canvas +*/ +void QwtPlotGrid::draw( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect ) const +{ + // draw minor grid lines + QPen minorPen = d_data->minorPen; + minorPen.setCapStyle( Qt::FlatCap ); + + painter->setPen( minorPen ); + + if ( d_data->xEnabled && d_data->xMinEnabled ) + { + drawLines( painter, canvasRect, Qt::Vertical, xMap, + d_data->xScaleDiv.ticks( QwtScaleDiv::MinorTick ) ); + drawLines( painter, canvasRect, Qt::Vertical, xMap, + d_data->xScaleDiv.ticks( QwtScaleDiv::MediumTick ) ); + } + + if ( d_data->yEnabled && d_data->yMinEnabled ) + { + drawLines( painter, canvasRect, Qt::Horizontal, yMap, + d_data->yScaleDiv.ticks( QwtScaleDiv::MinorTick ) ); + drawLines( painter, canvasRect, Qt::Horizontal, yMap, + d_data->yScaleDiv.ticks( QwtScaleDiv::MediumTick ) ); + } + + // draw major grid lines + QPen majorPen = d_data->majorPen; + majorPen.setCapStyle( Qt::FlatCap ); + + painter->setPen( majorPen ); + + if ( d_data->xEnabled ) + { + drawLines( painter, canvasRect, Qt::Vertical, xMap, + d_data->xScaleDiv.ticks( QwtScaleDiv::MajorTick ) ); + } + + if ( d_data->yEnabled ) + { + drawLines( painter, canvasRect, Qt::Horizontal, yMap, + d_data->yScaleDiv.ticks( QwtScaleDiv::MajorTick ) ); + } +} + +void QwtPlotGrid::drawLines( QPainter *painter, const QRectF &canvasRect, + Qt::Orientation orientation, const QwtScaleMap &scaleMap, + const QList &values ) const +{ + const double x1 = canvasRect.left(); + const double x2 = canvasRect.right() - 1.0; + const double y1 = canvasRect.top(); + const double y2 = canvasRect.bottom() - 1.0; + + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + for ( int i = 0; i < values.count(); i++ ) + { + double value = scaleMap.transform( values[i] ); + if ( doAlign ) + value = qRound( value ); + + if ( orientation == Qt::Horizontal ) + { + if ( qwtFuzzyGreaterOrEqual( value, y1 ) && + qwtFuzzyLessOrEqual( value, y2 ) ) + { + QwtPainter::drawLine( painter, x1, value, x2, value ); + } + } + else + { + if ( qwtFuzzyGreaterOrEqual( value, x1 ) && + qwtFuzzyLessOrEqual( value, x2 ) ) + { + QwtPainter::drawLine( painter, value, y1, value, y2 ); + } + } + } +} + +/*! + \return the pen for the major grid lines + \sa setMajorPen(), setMinorPen(), setPen() +*/ +const QPen &QwtPlotGrid::majorPen() const +{ + return d_data->majorPen; +} + +/*! + \return the pen for the minor grid lines + \sa setMinorPen(), setMajorPen(), setPen() +*/ +const QPen &QwtPlotGrid::minorPen() const +{ + return d_data->minorPen; +} + +/*! + \return true if vertical grid lines are enabled + \sa enableX() +*/ +bool QwtPlotGrid::xEnabled() const +{ + return d_data->xEnabled; +} + +/*! + \return true if minor vertical grid lines are enabled + \sa enableXMin() +*/ +bool QwtPlotGrid::xMinEnabled() const +{ + return d_data->xMinEnabled; +} + +/*! + \return true if horizontal grid lines are enabled + \sa enableY() +*/ +bool QwtPlotGrid::yEnabled() const +{ + return d_data->yEnabled; +} + +/*! + \return true if minor horizontal grid lines are enabled + \sa enableYMin() +*/ +bool QwtPlotGrid::yMinEnabled() const +{ + return d_data->yMinEnabled; +} + + +/*! \return the scale division of the x axis */ +const QwtScaleDiv &QwtPlotGrid::xScaleDiv() const +{ + return d_data->xScaleDiv; +} + +/*! \return the scale division of the y axis */ +const QwtScaleDiv &QwtPlotGrid::yScaleDiv() const +{ + return d_data->yScaleDiv; +} + +/*! + Update the grid to changes of the axes scale division + + \param xScaleDiv Scale division of the x-axis + \param yScaleDiv Scale division of the y-axis + + \sa QwtPlot::updateAxes() +*/ +void QwtPlotGrid::updateScaleDiv( const QwtScaleDiv& xScaleDiv, + const QwtScaleDiv& yScaleDiv ) +{ + setXDiv( xScaleDiv ); + setYDiv( yScaleDiv ); +} diff --git a/qwt/src/qwt_plot_grid.h b/qwt/src/qwt_plot_grid.h new file mode 100644 index 000000000..16d984c72 --- /dev/null +++ b/qwt/src/qwt_plot_grid.h @@ -0,0 +1,87 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_GRID_H +#define QWT_PLOT_GRID_H + +#include "qwt_global.h" +#include "qwt_plot_item.h" +#include "qwt_scale_div.h" + +class QPainter; +class QPen; +class QwtScaleMap; +class QwtScaleDiv; + +/*! + \brief A class which draws a coordinate grid + + The QwtPlotGrid class can be used to draw a coordinate grid. + A coordinate grid consists of major and minor vertical + and horizontal grid lines. The locations of the grid lines + are determined by the X and Y scale divisions which can + be assigned with setXDiv() and setYDiv(). + The draw() member draws the grid within a bounding + rectangle. +*/ + +class QWT_EXPORT QwtPlotGrid: public QwtPlotItem +{ +public: + explicit QwtPlotGrid(); + virtual ~QwtPlotGrid(); + + virtual int rtti() const; + + void enableX( bool tf ); + bool xEnabled() const; + + void enableY( bool tf ); + bool yEnabled() const; + + void enableXMin( bool tf ); + bool xMinEnabled() const; + + void enableYMin( bool tf ); + bool yMinEnabled() const; + + void setXDiv( const QwtScaleDiv &sx ); + const QwtScaleDiv &xScaleDiv() const; + + void setYDiv( const QwtScaleDiv &sy ); + const QwtScaleDiv &yScaleDiv() const; + + void setPen( const QColor &, qreal width = 0.0, Qt::PenStyle = Qt::SolidLine ); + void setPen( const QPen & ); + + void setMajorPen( const QColor &, qreal width = 0.0, Qt::PenStyle = Qt::SolidLine ); + void setMajorPen( const QPen & ); + const QPen& majorPen() const; + + void setMinorPen( const QColor &, qreal width = 0.0, Qt::PenStyle = Qt::SolidLine ); + void setMinorPen( const QPen &p ); + const QPen& minorPen() const; + + virtual void draw( QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &rect ) const; + + virtual void updateScaleDiv( + const QwtScaleDiv &xMap, const QwtScaleDiv &yMap ); + +private: + void drawLines( QPainter *painter, const QRectF &, + Qt::Orientation orientation, const QwtScaleMap &, + const QList & ) const; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_histogram.cpp b/qwt/src/qwt_plot_histogram.cpp new file mode 100644 index 000000000..4464f03ff --- /dev/null +++ b/qwt/src/qwt_plot_histogram.cpp @@ -0,0 +1,690 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_histogram.h" +#include "qwt_plot.h" +#include "qwt_painter.h" +#include "qwt_column_symbol.h" +#include "qwt_scale_map.h" +#include +#include + +static inline bool qwtIsCombinable( const QwtInterval &d1, + const QwtInterval &d2 ) +{ + if ( d1.isValid() && d2.isValid() ) + { + if ( d1.maxValue() == d2.minValue() ) + { + if ( !( d1.borderFlags() & QwtInterval::ExcludeMaximum + && d2.borderFlags() & QwtInterval::ExcludeMinimum ) ) + { + return true; + } + } + } + + return false; +} + +class QwtPlotHistogram::PrivateData +{ +public: + PrivateData(): + baseline( 0.0 ), + style( Columns ), + symbol( NULL ) + { + } + + ~PrivateData() + { + delete symbol; + } + + double baseline; + + QPen pen; + QBrush brush; + QwtPlotHistogram::HistogramStyle style; + const QwtColumnSymbol *symbol; +}; + +/*! + Constructor + \param title Title of the histogram. +*/ +QwtPlotHistogram::QwtPlotHistogram( const QwtText &title ): + QwtPlotSeriesItem( title ) +{ + init(); +} + +/*! + Constructor + \param title Title of the histogram. +*/ +QwtPlotHistogram::QwtPlotHistogram( const QString &title ): + QwtPlotSeriesItem( title ) +{ + init(); +} + +//! Destructor +QwtPlotHistogram::~QwtPlotHistogram() +{ + delete d_data; +} + +//! Initialize data members +void QwtPlotHistogram::init() +{ + d_data = new PrivateData(); + setData( new QwtIntervalSeriesData() ); + + setItemAttribute( QwtPlotItem::AutoScale, true ); + setItemAttribute( QwtPlotItem::Legend, true ); + + setZ( 20.0 ); +} + +/*! + Set the histogram's drawing style + + \param style Histogram style + \sa HistogramStyle, style() +*/ +void QwtPlotHistogram::setStyle( HistogramStyle style ) +{ + if ( style != d_data->style ) + { + d_data->style = style; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Style of the histogram + \sa HistogramStyle, setStyle() +*/ +QwtPlotHistogram::HistogramStyle QwtPlotHistogram::style() const +{ + return d_data->style; +} + +/*! + Build and assign a pen + + In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it + non cosmetic ( see QPen::isCosmetic() ). This method has been introduced + to hide this incompatibility. + + \param color Pen color + \param width Pen width + \param style Pen style + + \sa pen(), brush() + */ +void QwtPlotHistogram::setPen( const QColor &color, qreal width, Qt::PenStyle style ) +{ + setPen( QPen( color, width, style ) ); +} + +/*! + Assign a pen, that is used in a style() depending way. + + \param pen New pen + \sa pen(), brush() +*/ +void QwtPlotHistogram::setPen( const QPen &pen ) +{ + if ( pen != d_data->pen ) + { + d_data->pen = pen; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Pen used in a style() depending way. + \sa setPen(), brush() +*/ +const QPen &QwtPlotHistogram::pen() const +{ + return d_data->pen; +} + +/*! + Assign a brush, that is used in a style() depending way. + + \param brush New brush + \sa pen(), brush() +*/ +void QwtPlotHistogram::setBrush( const QBrush &brush ) +{ + if ( brush != d_data->brush ) + { + d_data->brush = brush; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Brush used in a style() depending way. + \sa setPen(), brush() +*/ +const QBrush &QwtPlotHistogram::brush() const +{ + return d_data->brush; +} + +/*! + \brief Assign a symbol + + In Column style an optional symbol can be assigned, that is responsible + for displaying the rectangle that is defined by the interval and + the distance between baseline() and value. When no symbol has been + defined the area is displayed as plain rectangle using pen() and brush(). + + \sa style(), symbol(), drawColumn(), pen(), brush() + + \note In applications, where different intervals need to be displayed + in a different way ( f.e different colors or even using different symbols) + it is recommended to overload drawColumn(). +*/ +void QwtPlotHistogram::setSymbol( const QwtColumnSymbol *symbol ) +{ + if ( symbol != d_data->symbol ) + { + delete d_data->symbol; + d_data->symbol = symbol; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Current symbol or NULL, when no symbol has been assigned + \sa setSymbol() +*/ +const QwtColumnSymbol *QwtPlotHistogram::symbol() const +{ + return d_data->symbol; +} + +/*! + \brief Set the value of the baseline + + Each column representing an QwtIntervalSample is defined by its + interval and the interval between baseline and the value of the sample. + + The default value of the baseline is 0.0. + + \param value Value of the baseline + \sa baseline() +*/ +void QwtPlotHistogram::setBaseline( double value ) +{ + if ( d_data->baseline != value ) + { + d_data->baseline = value; + itemChanged(); + } +} + +/*! + \return Value of the baseline + \sa setBaseline() +*/ +double QwtPlotHistogram::baseline() const +{ + return d_data->baseline; +} + +/*! + \return Bounding rectangle of all samples. + For an empty series the rectangle is invalid. +*/ +QRectF QwtPlotHistogram::boundingRect() const +{ + QRectF rect = data()->boundingRect(); + if ( !rect.isValid() ) + return rect; + + if ( orientation() == Qt::Horizontal ) + { + rect = QRectF( rect.y(), rect.x(), + rect.height(), rect.width() ); + + if ( rect.left() > d_data->baseline ) + rect.setLeft( d_data->baseline ); + else if ( rect.right() < d_data->baseline ) + rect.setRight( d_data->baseline ); + } + else + { + if ( rect.bottom() < d_data->baseline ) + rect.setBottom( d_data->baseline ); + else if ( rect.top() > d_data->baseline ) + rect.setTop( d_data->baseline ); + } + + return rect; +} + +//! \return QwtPlotItem::Rtti_PlotHistogram +int QwtPlotHistogram::rtti() const +{ + return QwtPlotItem::Rtti_PlotHistogram; +} + +/*! + Initialize data with an array of samples. + \param samples Vector of points +*/ +void QwtPlotHistogram::setSamples( + const QVector &samples ) +{ + setData( new QwtIntervalSeriesData( samples ) ); +} + +/*! + Assign a series of samples + + setSamples() is just a wrapper for setData() without any additional + value - beside that it is easier to find for the developer. + + \param data Data + \warning The item takes ownership of the data object, deleting + it when its not used anymore. +*/ +void QwtPlotHistogram::setSamples( + QwtSeriesData *data ) +{ + setData( data ); +} + +/*! + Draw a subset of the histogram samples + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param canvasRect Contents rectangle of the canvas + \param from Index of the first sample to be painted + \param to Index of the last sample to be painted. If to < 0 the + series will be painted to its last sample. + + \sa drawOutline(), drawLines(), drawColumns +*/ +void QwtPlotHistogram::drawSeries( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &, int from, int to ) const +{ + if ( !painter || dataSize() <= 0 ) + return; + + if ( to < 0 ) + to = dataSize() - 1; + + switch ( d_data->style ) + { + case Outline: + drawOutline( painter, xMap, yMap, from, to ); + break; + case Lines: + drawLines( painter, xMap, yMap, from, to ); + break; + case Columns: + drawColumns( painter, xMap, yMap, from, to ); + break; + default: + break; + } +} + +/*! + Draw a histogram in Outline style() + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param from Index of the first sample to be painted + \param to Index of the last sample to be painted. If to < 0 the + histogram will be painted to its last point. + + \sa setStyle(), style() + \warning The outline style requires, that the intervals are in increasing + order and not overlapping. +*/ +void QwtPlotHistogram::drawOutline( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to ) const +{ + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + double v0 = ( orientation() == Qt::Horizontal ) ? + xMap.transform( baseline() ) : yMap.transform( baseline() ); + if ( doAlign ) + v0 = qRound( v0 ); + + QwtIntervalSample previous; + + QPolygonF polygon; + for ( int i = from; i <= to; i++ ) + { + const QwtIntervalSample sample = this->sample( i ); + + if ( !sample.interval.isValid() ) + { + flushPolygon( painter, v0, polygon ); + previous = sample; + continue; + } + + if ( previous.interval.isValid() ) + { + if ( !qwtIsCombinable( previous.interval, sample.interval ) ) + flushPolygon( painter, v0, polygon ); + } + + if ( orientation() == Qt::Vertical ) + { + double x1 = xMap.transform( sample.interval.minValue() ); + double x2 = xMap.transform( sample.interval.maxValue() ); + double y = yMap.transform( sample.value ); + if ( doAlign ) + { + x1 = qRound( x1 ); + x2 = qRound( x2 ); + y = qRound( y ); + } + + if ( polygon.size() == 0 ) + polygon += QPointF( x1, v0 ); + + polygon += QPointF( x1, y ); + polygon += QPointF( x2, y ); + } + else + { + double y1 = yMap.transform( sample.interval.minValue() ); + double y2 = yMap.transform( sample.interval.maxValue() ); + double x = xMap.transform( sample.value ); + if ( doAlign ) + { + y1 = qRound( y1 ); + y2 = qRound( y2 ); + x = qRound( x ); + } + + if ( polygon.size() == 0 ) + polygon += QPointF( v0, y1 ); + + polygon += QPointF( x, y1 ); + polygon += QPointF( x, y2 ); + } + previous = sample; + } + + flushPolygon( painter, v0, polygon ); +} + +/*! + Draw a histogram in Columns style() + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param from Index of the first sample to be painted + \param to Index of the last sample to be painted. If to < 0 the + histogram will be painted to its last point. + + \sa setStyle(), style(), setSymbol(), drawColumn() +*/ +void QwtPlotHistogram::drawColumns( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to ) const +{ + painter->setPen( d_data->pen ); + painter->setBrush( d_data->brush ); + + const QwtSeriesData *series = data(); + + for ( int i = from; i <= to; i++ ) + { + const QwtIntervalSample sample = series->sample( i ); + if ( !sample.interval.isNull() ) + { + const QwtColumnRect rect = columnRect( sample, xMap, yMap ); + drawColumn( painter, rect, sample ); + } + } +} + +/*! + Draw a histogram in Lines style() + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param from Index of the first sample to be painted + \param to Index of the last sample to be painted. If to < 0 the + histogram will be painted to its last point. + + \sa setStyle(), style(), setPen() +*/ +void QwtPlotHistogram::drawLines( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to ) const +{ + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + painter->setPen( d_data->pen ); + painter->setBrush( Qt::NoBrush ); + + const QwtSeriesData *series = data(); + + for ( int i = from; i <= to; i++ ) + { + const QwtIntervalSample sample = series->sample( i ); + if ( !sample.interval.isNull() ) + { + const QwtColumnRect rect = columnRect( sample, xMap, yMap ); + + QRectF r = rect.toRect(); + if ( doAlign ) + { + r.setLeft( qRound( r.left() ) ); + r.setRight( qRound( r.right() ) ); + r.setTop( qRound( r.top() ) ); + r.setBottom( qRound( r.bottom() ) ); + } + + switch ( rect.direction ) + { + case QwtColumnRect::LeftToRight: + { + QwtPainter::drawLine( painter, + r.topRight(), r.bottomRight() ); + break; + } + case QwtColumnRect::RightToLeft: + { + QwtPainter::drawLine( painter, + r.topLeft(), r.bottomLeft() ); + break; + } + case QwtColumnRect::TopToBottom: + { + QwtPainter::drawLine( painter, + r.bottomRight(), r.bottomLeft() ); + break; + } + case QwtColumnRect::BottomToTop: + { + QwtPainter::drawLine( painter, + r.topRight(), r.topLeft() ); + break; + } + } + } + } +} + +//! Internal, used by the Outline style. +void QwtPlotHistogram::flushPolygon( QPainter *painter, + double baseLine, QPolygonF &polygon ) const +{ + if ( polygon.size() == 0 ) + return; + + if ( orientation() == Qt::Horizontal ) + polygon += QPointF( baseLine, polygon.last().y() ); + else + polygon += QPointF( polygon.last().x(), baseLine ); + + if ( d_data->brush.style() != Qt::NoBrush ) + { + painter->setPen( Qt::NoPen ); + painter->setBrush( d_data->brush ); + + if ( orientation() == Qt::Horizontal ) + { + polygon += QPointF( polygon.last().x(), baseLine ); + polygon += QPointF( polygon.first().x(), baseLine ); + } + else + { + polygon += QPointF( baseLine, polygon.last().y() ); + polygon += QPointF( baseLine, polygon.first().y() ); + } + + QwtPainter::drawPolygon( painter, polygon ); + + polygon.pop_back(); + polygon.pop_back(); + } + if ( d_data->pen.style() != Qt::NoPen ) + { + painter->setBrush( Qt::NoBrush ); + painter->setPen( d_data->pen ); + QwtPainter::drawPolyline( painter, polygon ); + } + polygon.clear(); +} + +/*! + Calculate the area that is covered by a sample + + \param sample Sample + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + + \return Rectangle, that is covered by a sample +*/ +QwtColumnRect QwtPlotHistogram::columnRect( const QwtIntervalSample &sample, + const QwtScaleMap &xMap, const QwtScaleMap &yMap ) const +{ + QwtColumnRect rect; + + const QwtInterval &iv = sample.interval; + if ( !iv.isValid() ) + return rect; + + if ( orientation() == Qt::Horizontal ) + { + const double x0 = xMap.transform( baseline() ); + const double x = xMap.transform( sample.value ); + const double y1 = yMap.transform( iv.minValue() ); + const double y2 = yMap.transform( iv.maxValue() ); + + rect.hInterval.setInterval( x0, x ); + rect.vInterval.setInterval( y1, y2, iv.borderFlags() ); + rect.direction = ( x < x0 ) ? QwtColumnRect::RightToLeft : + QwtColumnRect::LeftToRight; + } + else + { + const double x1 = xMap.transform( iv.minValue() ); + const double x2 = xMap.transform( iv.maxValue() ); + const double y0 = yMap.transform( baseline() ); + const double y = yMap.transform( sample.value ); + + rect.hInterval.setInterval( x1, x2, iv.borderFlags() ); + rect.vInterval.setInterval( y0, y ); + rect.direction = ( y < y0 ) ? QwtColumnRect::BottomToTop : + QwtColumnRect::TopToBottom; + } + + return rect; +} + +/*! + Draw a column for a sample in Columns style(). + + When a symbol() has been set the symbol is used otherwise the + column is displayed as plain rectangle using pen() and brush(). + + \param painter Painter + \param rect Rectangle where to paint the column in paint device coordinates + \param sample Sample to be displayed + + \note In applications, where different intervals need to be displayed + in a different way ( f.e different colors or even using different symbols) + it is recommended to overload drawColumn(). +*/ +void QwtPlotHistogram::drawColumn( QPainter *painter, + const QwtColumnRect &rect, const QwtIntervalSample &sample ) const +{ + Q_UNUSED( sample ); + + if ( d_data->symbol && + ( d_data->symbol->style() != QwtColumnSymbol::NoStyle ) ) + { + d_data->symbol->draw( painter, rect ); + } + else + { + QRectF r = rect.toRect(); + if ( QwtPainter::roundingAlignment( painter ) ) + { + r.setLeft( qRound( r.left() ) ); + r.setRight( qRound( r.right() ) ); + r.setTop( qRound( r.top() ) ); + r.setBottom( qRound( r.bottom() ) ); + } + + QwtPainter::drawRect( painter, r ); + } +} + +/*! + A plain rectangle without pen using the brush() + + \param index Index of the legend entry + ( ignored as there is only one ) + \param size Icon size + \return A graphic displaying the icon + + \sa QwtPlotItem::setLegendIconSize(), QwtPlotItem::legendData() +*/ +QwtGraphic QwtPlotHistogram::legendIcon( int index, + const QSizeF &size ) const +{ + Q_UNUSED( index ); + return defaultIcon( d_data->brush, size ); +} diff --git a/qwt/src/qwt_plot_histogram.h b/qwt/src/qwt_plot_histogram.h new file mode 100644 index 000000000..b96bdddc0 --- /dev/null +++ b/qwt/src/qwt_plot_histogram.h @@ -0,0 +1,139 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_HISTOGRAM_H +#define QWT_PLOT_HISTOGRAM_H + +#include "qwt_global.h" +#include "qwt_plot_seriesitem.h" +#include "qwt_column_symbol.h" +#include +#include + +class QwtIntervalData; +class QString; +class QPolygonF; + +/*! + \brief QwtPlotHistogram represents a series of samples, where an interval + is associated with a value ( \f$y = f([x1,x2])\f$ ). + + The representation depends on the style() and an optional symbol() + that is displayed for each interval. + + \note The term "histogram" is used in a different way in the areas of + digital image processing and statistics. Wikipedia introduces the + terms "image histogram" and "color histogram" to avoid confusions. + While "image histograms" can be displayed by a QwtPlotCurve there + is no applicable plot item for a "color histogram" yet. + + \sa QwtPlotBarChart, QwtPlotMultiBarChart +*/ + +class QWT_EXPORT QwtPlotHistogram: + public QwtPlotSeriesItem, public QwtSeriesStore +{ +public: + /*! + Histogram styles. + The default style is QwtPlotHistogram::Columns. + + \sa setStyle(), style(), setSymbol(), symbol(), setBaseline() + */ + enum HistogramStyle + { + /*! + Draw an outline around the area, that is build by all intervals + using the pen() and fill it with the brush(). The outline style + requires, that the intervals are in increasing order and + not overlapping. + */ + Outline, + + /*! + Draw a column for each interval. When a symbol() has been set + the symbol is used otherwise the column is displayed as + plain rectangle using pen() and brush(). + */ + Columns, + + /*! + Draw a simple line using the pen() for each interval. + */ + Lines, + + /*! + Styles >= UserStyle are reserved for derived + classes that overload drawSeries() with + additional application specific ways to display a histogram. + */ + UserStyle = 100 + }; + + explicit QwtPlotHistogram( const QString &title = QString::null ); + explicit QwtPlotHistogram( const QwtText &title ); + virtual ~QwtPlotHistogram(); + + virtual int rtti() const; + + void setPen( const QColor &, qreal width = 0.0, Qt::PenStyle = Qt::SolidLine ); + void setPen( const QPen & ); + const QPen &pen() const; + + void setBrush( const QBrush & ); + const QBrush &brush() const; + + void setSamples( const QVector & ); + void setSamples( QwtSeriesData * ); + + void setBaseline( double reference ); + double baseline() const; + + void setStyle( HistogramStyle style ); + HistogramStyle style() const; + + void setSymbol( const QwtColumnSymbol * ); + const QwtColumnSymbol *symbol() const; + + virtual void drawSeries( QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual QRectF boundingRect() const; + + virtual QwtGraphic legendIcon( int index, const QSizeF & ) const; + +protected: + virtual QwtColumnRect columnRect( const QwtIntervalSample &, + const QwtScaleMap &, const QwtScaleMap & ) const; + + virtual void drawColumn( QPainter *, const QwtColumnRect &, + const QwtIntervalSample & ) const; + + void drawColumns( QPainter *, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to ) const; + + void drawOutline( QPainter *, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to ) const; + + void drawLines( QPainter *, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to ) const; + +private: + void init(); + void flushPolygon( QPainter *, double baseLine, QPolygonF & ) const; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_intervalcurve.cpp b/qwt/src/qwt_plot_intervalcurve.cpp new file mode 100644 index 000000000..200ea39b5 --- /dev/null +++ b/qwt/src/qwt_plot_intervalcurve.cpp @@ -0,0 +1,603 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_intervalcurve.h" +#include "qwt_interval_symbol.h" +#include "qwt_scale_map.h" +#include "qwt_clipper.h" +#include "qwt_painter.h" +#include + +#include + +static inline bool qwtIsHSampleInside( const QwtIntervalSample &sample, + double xMin, double xMax, double yMin, double yMax ) +{ + const double y = sample.value; + const double x1 = sample.interval.minValue(); + const double x2 = sample.interval.maxValue(); + + const bool isOffScreen = ( y < yMin ) || ( y > yMax ) + || ( x1 < xMin && x2 < xMin ) || ( x1 > xMax && x2 > xMax ); + + return !isOffScreen; +} + +static inline bool qwtIsVSampleInside( const QwtIntervalSample &sample, + double xMin, double xMax, double yMin, double yMax ) +{ + const double x = sample.value; + const double y1 = sample.interval.minValue(); + const double y2 = sample.interval.maxValue(); + + const bool isOffScreen = ( x < xMin ) || ( x > xMax ) + || ( y1 < yMin && y2 < yMin ) || ( y1 > yMax && y2 > yMax ); + + return !isOffScreen; +} + +class QwtPlotIntervalCurve::PrivateData +{ +public: + PrivateData(): + style( QwtPlotIntervalCurve::Tube ), + symbol( NULL ), + pen( Qt::black ), + brush( Qt::white ) + { + paintAttributes = QwtPlotIntervalCurve::ClipPolygons; + paintAttributes |= QwtPlotIntervalCurve::ClipSymbol; + + pen.setCapStyle( Qt::FlatCap ); + } + + ~PrivateData() + { + delete symbol; + } + + QwtPlotIntervalCurve::CurveStyle style; + const QwtIntervalSymbol *symbol; + + QPen pen; + QBrush brush; + + QwtPlotIntervalCurve::PaintAttributes paintAttributes; +}; + +/*! + Constructor + \param title Title of the curve +*/ +QwtPlotIntervalCurve::QwtPlotIntervalCurve( const QwtText &title ): + QwtPlotSeriesItem( title ) +{ + init(); +} + +/*! + Constructor + \param title Title of the curve +*/ +QwtPlotIntervalCurve::QwtPlotIntervalCurve( const QString &title ): + QwtPlotSeriesItem( QwtText( title ) ) +{ + init(); +} + +//! Destructor +QwtPlotIntervalCurve::~QwtPlotIntervalCurve() +{ + delete d_data; +} + +//! Initialize internal members +void QwtPlotIntervalCurve::init() +{ + setItemAttribute( QwtPlotItem::Legend, true ); + setItemAttribute( QwtPlotItem::AutoScale, true ); + + d_data = new PrivateData; + setData( new QwtIntervalSeriesData() ); + + setZ( 19.0 ); +} + +//! \return QwtPlotItem::Rtti_PlotIntervalCurve +int QwtPlotIntervalCurve::rtti() const +{ + return QwtPlotIntervalCurve::Rtti_PlotIntervalCurve; +} + +/*! + Specify an attribute how to draw the curve + + \param attribute Paint attribute + \param on On/Off + \sa testPaintAttribute() +*/ +void QwtPlotIntervalCurve::setPaintAttribute( + PaintAttribute attribute, bool on ) +{ + if ( on ) + d_data->paintAttributes |= attribute; + else + d_data->paintAttributes &= ~attribute; +} + +/*! + \return True, when attribute is enabled + \sa PaintAttribute, setPaintAttribute() +*/ +bool QwtPlotIntervalCurve::testPaintAttribute( + PaintAttribute attribute ) const +{ + return ( d_data->paintAttributes & attribute ); +} + +/*! + Initialize data with an array of samples. + \param samples Vector of samples +*/ +void QwtPlotIntervalCurve::setSamples( + const QVector &samples ) +{ + setData( new QwtIntervalSeriesData( samples ) ); +} + +/*! + Assign a series of samples + + setSamples() is just a wrapper for setData() without any additional + value - beside that it is easier to find for the developer. + + \param data Data + \warning The item takes ownership of the data object, deleting + it when its not used anymore. +*/ +void QwtPlotIntervalCurve::setSamples( + QwtSeriesData *data ) +{ + setData( data ); +} + +/*! + Set the curve's drawing style + + \param style Curve style + \sa CurveStyle, style() +*/ +void QwtPlotIntervalCurve::setStyle( CurveStyle style ) +{ + if ( style != d_data->style ) + { + d_data->style = style; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Style of the curve + \sa setStyle() +*/ +QwtPlotIntervalCurve::CurveStyle QwtPlotIntervalCurve::style() const +{ + return d_data->style; +} + +/*! + Assign a symbol. + + \param symbol Symbol + \sa symbol() +*/ +void QwtPlotIntervalCurve::setSymbol( const QwtIntervalSymbol *symbol ) +{ + if ( symbol != d_data->symbol ) + { + delete d_data->symbol; + d_data->symbol = symbol; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Current symbol or NULL, when no symbol has been assigned + \sa setSymbol() +*/ +const QwtIntervalSymbol *QwtPlotIntervalCurve::symbol() const +{ + return d_data->symbol; +} + +/*! + Build and assign a pen + + In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it + non cosmetic ( see QPen::isCosmetic() ). This method has been introduced + to hide this incompatibility. + + \param color Pen color + \param width Pen width + \param style Pen style + + \sa pen(), brush() + */ +void QwtPlotIntervalCurve::setPen( const QColor &color, qreal width, Qt::PenStyle style ) +{ + setPen( QPen( color, width, style ) ); +} + +/*! + \brief Assign a pen + \param pen New pen + \sa pen(), brush() +*/ +void QwtPlotIntervalCurve::setPen( const QPen &pen ) +{ + if ( pen != d_data->pen ) + { + d_data->pen = pen; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Pen used to draw the lines + \sa setPen(), brush() +*/ +const QPen& QwtPlotIntervalCurve::pen() const +{ + return d_data->pen; +} + +/*! + Assign a brush. + + The brush is used to fill the area in Tube style(). + + \param brush Brush + \sa brush(), pen(), setStyle(), CurveStyle +*/ +void QwtPlotIntervalCurve::setBrush( const QBrush &brush ) +{ + if ( brush != d_data->brush ) + { + d_data->brush = brush; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Brush used to fill the area in Tube style() + \sa setBrush(), setStyle(), CurveStyle +*/ +const QBrush& QwtPlotIntervalCurve::brush() const +{ + return d_data->brush; +} + +/*! + \return Bounding rectangle of all samples. + For an empty series the rectangle is invalid. +*/ +QRectF QwtPlotIntervalCurve::boundingRect() const +{ + QRectF rect = QwtPlotSeriesItem::boundingRect(); + if ( rect.isValid() && orientation() == Qt::Vertical ) + rect.setRect( rect.y(), rect.x(), rect.height(), rect.width() ); + + return rect; +} + +/*! + Draw a subset of the samples + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param canvasRect Contents rectangle of the canvas + \param from Index of the first sample to be painted + \param to Index of the last sample to be painted. If to < 0 the + series will be painted to its last sample. + + \sa drawTube(), drawSymbols() +*/ +void QwtPlotIntervalCurve::drawSeries( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + if ( to < 0 ) + to = dataSize() - 1; + + if ( from < 0 ) + from = 0; + + if ( from > to ) + return; + + switch ( d_data->style ) + { + case Tube: + drawTube( painter, xMap, yMap, canvasRect, from, to ); + break; + + case NoCurve: + default: + break; + } + + if ( d_data->symbol && + ( d_data->symbol->style() != QwtIntervalSymbol::NoSymbol ) ) + { + drawSymbols( painter, *d_data->symbol, + xMap, yMap, canvasRect, from, to ); + } +} + +/*! + Draw a tube + + Builds 2 curves from the upper and lower limits of the intervals + and draws them with the pen(). The area between the curves is + filled with the brush(). + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param canvasRect Contents rectangle of the canvas + \param from Index of the first sample to be painted + \param to Index of the last sample to be painted. If to < 0 the + series will be painted to its last sample. + + \sa drawSeries(), drawSymbols() +*/ +void QwtPlotIntervalCurve::drawTube( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + painter->save(); + + const size_t size = to - from + 1; + QPolygonF polygon( 2 * size ); + QPointF *points = polygon.data(); + + for ( uint i = 0; i < size; i++ ) + { + QPointF &minValue = points[i]; + QPointF &maxValue = points[2 * size - 1 - i]; + + const QwtIntervalSample intervalSample = sample( from + i ); + if ( orientation() == Qt::Vertical ) + { + double x = xMap.transform( intervalSample.value ); + double y1 = yMap.transform( intervalSample.interval.minValue() ); + double y2 = yMap.transform( intervalSample.interval.maxValue() ); + if ( doAlign ) + { + x = qRound( x ); + y1 = qRound( y1 ); + y2 = qRound( y2 ); + } + + minValue.rx() = x; + minValue.ry() = y1; + maxValue.rx() = x; + maxValue.ry() = y2; + } + else + { + double y = yMap.transform( intervalSample.value ); + double x1 = xMap.transform( intervalSample.interval.minValue() ); + double x2 = xMap.transform( intervalSample.interval.maxValue() ); + if ( doAlign ) + { + y = qRound( y ); + x1 = qRound( x1 ); + x2 = qRound( x2 ); + } + + minValue.rx() = x1; + minValue.ry() = y; + maxValue.rx() = x2; + maxValue.ry() = y; + } + } + + if ( d_data->brush.style() != Qt::NoBrush ) + { + painter->setPen( QPen( Qt::NoPen ) ); + painter->setBrush( d_data->brush ); + + if ( d_data->paintAttributes & ClipPolygons ) + { + const qreal m = 1.0; + const QPolygonF p = QwtClipper::clipPolygonF( + canvasRect.adjusted( -m, -m, m, m ), polygon, true ); + + QwtPainter::drawPolygon( painter, p ); + } + else + { + QwtPainter::drawPolygon( painter, polygon ); + } + } + + if ( d_data->pen.style() != Qt::NoPen ) + { + painter->setPen( d_data->pen ); + painter->setBrush( Qt::NoBrush ); + + if ( d_data->paintAttributes & ClipPolygons ) + { + qreal pw = qMax( qreal( 1.0 ), painter->pen().widthF() ); + const QRectF clipRect = canvasRect.adjusted( -pw, -pw, pw, pw ); + + QPolygonF p; + + p.resize( size ); + ::memcpy( p.data(), points, size * sizeof( QPointF ) ); + p = QwtClipper::clipPolygonF( clipRect, p ); + QwtPainter::drawPolyline( painter, p ); + + p.resize( size ); + ::memcpy( p.data(), points + size, size * sizeof( QPointF ) ); + p = QwtClipper::clipPolygonF( clipRect, p ); + QwtPainter::drawPolyline( painter, p ); + } + else + { + QwtPainter::drawPolyline( painter, points, size ); + QwtPainter::drawPolyline( painter, points + size, size ); + } + } + + painter->restore(); +} + +/*! + Draw symbols for a subset of the samples + + \param painter Painter + \param symbol Interval symbol + \param xMap x map + \param yMap y map + \param canvasRect Contents rectangle of the canvas + \param from Index of the first sample to be painted + \param to Index of the last sample to be painted + + \sa setSymbol(), drawSeries(), drawTube() +*/ +void QwtPlotIntervalCurve::drawSymbols( + QPainter *painter, const QwtIntervalSymbol &symbol, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + painter->save(); + + QPen pen = symbol.pen(); + pen.setCapStyle( Qt::FlatCap ); + + painter->setPen( pen ); + painter->setBrush( symbol.brush() ); + + const QRectF tr = QwtScaleMap::invTransform( xMap, yMap, canvasRect ); + + const double xMin = tr.left(); + const double xMax = tr.right(); + const double yMin = tr.top(); + const double yMax = tr.bottom(); + + const bool doClip = d_data->paintAttributes & ClipSymbol; + + for ( int i = from; i <= to; i++ ) + { + const QwtIntervalSample s = sample( i ); + + if ( orientation() == Qt::Vertical ) + { + if ( !doClip || qwtIsVSampleInside( s, xMin, xMax, yMin, yMax ) ) + { + const double x = xMap.transform( s.value ); + const double y1 = yMap.transform( s.interval.minValue() ); + const double y2 = yMap.transform( s.interval.maxValue() ); + + symbol.draw( painter, orientation(), + QPointF( x, y1 ), QPointF( x, y2 ) ); + } + } + else + { + if ( !doClip || qwtIsHSampleInside( s, xMin, xMax, yMin, yMax ) ) + { + const double y = yMap.transform( s.value ); + const double x1 = xMap.transform( s.interval.minValue() ); + const double x2 = xMap.transform( s.interval.maxValue() ); + + symbol.draw( painter, orientation(), + QPointF( x1, y ), QPointF( x2, y ) ); + } + } + } + + painter->restore(); +} + +/*! + \return Icon for the legend + + In case of Tube style() the icon is a plain rectangle filled with the brush(). + If a symbol is assigned it is scaled to size. + + \param index Index of the legend entry + ( ignored as there is only one ) + \param size Icon size + + \sa QwtPlotItem::setLegendIconSize(), QwtPlotItem::legendData() +*/ +QwtGraphic QwtPlotIntervalCurve::legendIcon( + int index, const QSizeF &size ) const +{ + Q_UNUSED( index ); + + if ( size.isEmpty() ) + return QwtGraphic(); + + QwtGraphic icon; + icon.setDefaultSize( size ); + icon.setRenderHint( QwtGraphic::RenderPensUnscaled, true ); + + QPainter painter( &icon ); + painter.setRenderHint( QPainter::Antialiasing, + testRenderHint( QwtPlotItem::RenderAntialiased ) ); + + if ( d_data->style == Tube ) + { + QRectF r( 0, 0, size.width(), size.height() ); + painter.fillRect( r, d_data->brush ); + } + + if ( d_data->symbol && + ( d_data->symbol->style() != QwtIntervalSymbol::NoSymbol ) ) + { + QPen pen = d_data->symbol->pen(); + pen.setWidthF( pen.widthF() ); + pen.setCapStyle( Qt::FlatCap ); + + painter.setPen( pen ); + painter.setBrush( d_data->symbol->brush() ); + + if ( orientation() == Qt::Vertical ) + { + const double x = 0.5 * size.width(); + + d_data->symbol->draw( &painter, orientation(), + QPointF( x, 0 ), QPointF( x, size.height() - 1.0 ) ); + } + else + { + const double y = 0.5 * size.height(); + + d_data->symbol->draw( &painter, orientation(), + QPointF( 0.0, y ), QPointF( size.width() - 1.0, y ) ); + } + } + + return icon; +} diff --git a/qwt/src/qwt_plot_intervalcurve.h b/qwt/src/qwt_plot_intervalcurve.h new file mode 100644 index 000000000..624d82f1b --- /dev/null +++ b/qwt/src/qwt_plot_intervalcurve.h @@ -0,0 +1,132 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_INTERVAL_CURVE_H +#define QWT_PLOT_INTERVAL_CURVE_H + +#include "qwt_global.h" +#include "qwt_plot_seriesitem.h" +#include "qwt_series_data.h" + +class QwtIntervalSymbol; + +/*! + \brief QwtPlotIntervalCurve represents a series of samples, where each value + is associated with an interval ( \f$[y1,y2] = f(x)\f$ ). + + The representation depends on the style() and an optional symbol() + that is displayed for each interval. QwtPlotIntervalCurve might be used + to display error bars or the area between 2 curves. +*/ +class QWT_EXPORT QwtPlotIntervalCurve: + public QwtPlotSeriesItem, public QwtSeriesStore +{ +public: + /*! + \brief Curve styles. + The default setting is QwtPlotIntervalCurve::Tube. + + \sa setStyle(), style() + */ + enum CurveStyle + { + /*! + Don't draw a curve. Note: This doesn't affect the symbols. + */ + NoCurve, + + /*! + Build 2 curves from the upper and lower limits of the intervals + and draw them with the pen(). The area between the curves is + filled with the brush(). + */ + Tube, + + /*! + Styles >= QwtPlotIntervalCurve::UserCurve are reserved for derived + classes that overload drawSeries() with + additional application specific curve types. + */ + UserCurve = 100 + }; + + /*! + Attributes to modify the drawing algorithm. + \sa setPaintAttribute(), testPaintAttribute() + */ + enum PaintAttribute + { + /*! + Clip polygons before painting them. In situations, where points + are far outside the visible area (f.e when zooming deep) this + might be a substantial improvement for the painting performance. + */ + ClipPolygons = 0x01, + + //! Check if a symbol is on the plot canvas before painting it. + ClipSymbol = 0x02 + }; + + //! Paint attributes + typedef QFlags PaintAttributes; + + explicit QwtPlotIntervalCurve( const QString &title = QString::null ); + explicit QwtPlotIntervalCurve( const QwtText &title ); + + virtual ~QwtPlotIntervalCurve(); + + virtual int rtti() const; + + void setPaintAttribute( PaintAttribute, bool on = true ); + bool testPaintAttribute( PaintAttribute ) const; + + void setSamples( const QVector & ); + void setSamples( QwtSeriesData * ); + + void setPen( const QColor &, qreal width = 0.0, Qt::PenStyle = Qt::SolidLine ); + void setPen( const QPen & ); + const QPen &pen() const; + + void setBrush( const QBrush & ); + const QBrush &brush() const; + + void setStyle( CurveStyle style ); + CurveStyle style() const; + + void setSymbol( const QwtIntervalSymbol * ); + const QwtIntervalSymbol *symbol() const; + + virtual void drawSeries( QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual QRectF boundingRect() const; + + virtual QwtGraphic legendIcon( int index, const QSizeF & ) const; + +protected: + + void init(); + + virtual void drawTube( QPainter *, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual void drawSymbols( QPainter *, const QwtIntervalSymbol &, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + +private: + class PrivateData; + PrivateData *d_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotIntervalCurve::PaintAttributes ) + +#endif diff --git a/qwt/src/qwt_plot_item.cpp b/qwt/src/qwt_plot_item.cpp new file mode 100644 index 000000000..d6265d614 --- /dev/null +++ b/qwt/src/qwt_plot_item.cpp @@ -0,0 +1,709 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_item.h" +#include "qwt_text.h" +#include "qwt_plot.h" +#include "qwt_legend_data.h" +#include "qwt_scale_div.h" +#include "qwt_graphic.h" +#include + +class QwtPlotItem::PrivateData +{ +public: + PrivateData(): + plot( NULL ), + isVisible( true ), + attributes( 0 ), + interests( 0 ), + renderHints( 0 ), + renderThreadCount( 1 ), + z( 0.0 ), + xAxis( QwtAxis::xBottom ), + yAxis( QwtAxis::yLeft ), + legendIconSize( 8, 8 ) + { + } + + mutable QwtPlot *plot; + + bool isVisible; + + QwtPlotItem::ItemAttributes attributes; + QwtPlotItem::ItemInterests interests; + + QwtPlotItem::RenderHints renderHints; + uint renderThreadCount; + + double z; + + QwtAxisId xAxis; + QwtAxisId yAxis; + + QwtText title; + QSize legendIconSize; +}; + +/*! + Constructor + \param title Title of the item +*/ +QwtPlotItem::QwtPlotItem( const QwtText &title ) +{ + d_data = new PrivateData; + d_data->title = title; +} + +//! Destroy the QwtPlotItem +QwtPlotItem::~QwtPlotItem() +{ + attach( NULL ); + delete d_data; +} + +/*! + \brief Attach the item to a plot. + + This method will attach a QwtPlotItem to the QwtPlot argument. It will first + detach the QwtPlotItem from any plot from a previous call to attach (if + necessary). If a NULL argument is passed, it will detach from any QwtPlot it + was attached to. + + \param plot Plot widget + \sa detach() +*/ +void QwtPlotItem::attach( QwtPlot *plot ) +{ + if ( plot == d_data->plot ) + return; + + if ( d_data->plot ) + d_data->plot->attachItem( this, false ); + + d_data->plot = plot; + + if ( d_data->plot ) + d_data->plot->attachItem( this, true ); +} + +/*! + \brief This method detaches a QwtPlotItem from any + QwtPlot it has been associated with. + + detach() is equivalent to calling attach( NULL ) + \sa attach() +*/ +void QwtPlotItem::detach() +{ + attach( NULL ); +} + +/*! + Return rtti for the specific class represented. QwtPlotItem is simply + a virtual interface class, and base classes will implement this method + with specific rtti values so a user can differentiate them. + + The rtti value is useful for environments, where the + runtime type information is disabled and it is not possible + to do a dynamic_cast<...>. + + \return rtti value + \sa RttiValues +*/ +int QwtPlotItem::rtti() const +{ + return Rtti_PlotItem; +} + +//! Return attached plot +QwtPlot *QwtPlotItem::plot() const +{ + return d_data->plot; +} + +/*! + Plot items are painted in increasing z-order. + + \return setZ(), QwtPlotDict::itemList() +*/ +double QwtPlotItem::z() const +{ + return d_data->z; +} + +/*! + \brief Set the z value + + Plot items are painted in increasing z-order. + + \param z Z-value + \sa z(), QwtPlotDict::itemList() +*/ +void QwtPlotItem::setZ( double z ) +{ + if ( d_data->z != z ) + { + if ( d_data->plot ) // update the z order + d_data->plot->attachItem( this, false ); + + d_data->z = z; + + if ( d_data->plot ) + d_data->plot->attachItem( this, true ); + + itemChanged(); + } +} + +/*! + Set a new title + + \param title Title + \sa title() +*/ +void QwtPlotItem::setTitle( const QString &title ) +{ + setTitle( QwtText( title ) ); +} + +/*! + Set a new title + + \param title Title + \sa title() +*/ +void QwtPlotItem::setTitle( const QwtText &title ) +{ + if ( d_data->title != title ) + { + d_data->title = title; + + legendChanged(); +#if 0 + itemChanged(); +#endif + } +} + +/*! + \return Title of the item + \sa setTitle() +*/ +const QwtText &QwtPlotItem::title() const +{ + return d_data->title; +} + +/*! + Toggle an item attribute + + \param attribute Attribute type + \param on true/false + + \sa testItemAttribute(), ItemInterest +*/ +void QwtPlotItem::setItemAttribute( ItemAttribute attribute, bool on ) +{ + if ( d_data->attributes.testFlag( attribute ) != on ) + { + if ( on ) + d_data->attributes |= attribute; + else + d_data->attributes &= ~attribute; + + if ( attribute == QwtPlotItem::Legend ) + legendChanged(); + + itemChanged(); + } +} + +/*! + Test an item attribute + + \param attribute Attribute type + \return true/false + \sa setItemAttribute(), ItemInterest +*/ +bool QwtPlotItem::testItemAttribute( ItemAttribute attribute ) const +{ + return d_data->attributes.testFlag( attribute ); +} + +/*! + Toggle an item interest + + \param interest Interest type + \param on true/false + + \sa testItemInterest(), ItemAttribute +*/ +void QwtPlotItem::setItemInterest( ItemInterest interest, bool on ) +{ + if ( d_data->interests.testFlag( interest ) != on ) + { + if ( on ) + d_data->interests |= interest; + else + d_data->interests &= ~interest; + + itemChanged(); + } +} + +/*! + Test an item interest + + \param interest Interest type + \return true/false + \sa setItemInterest(), ItemAttribute +*/ +bool QwtPlotItem::testItemInterest( ItemInterest interest ) const +{ + return d_data->interests.testFlag( interest ); +} + +/*! + Toggle an render hint + + \param hint Render hint + \param on true/false + + \sa testRenderHint(), RenderHint +*/ +void QwtPlotItem::setRenderHint( RenderHint hint, bool on ) +{ + if ( d_data->renderHints.testFlag( hint ) != on ) + { + if ( on ) + d_data->renderHints |= hint; + else + d_data->renderHints &= ~hint; + + itemChanged(); + } +} + +/*! + Test a render hint + + \param hint Render hint + \return true/false + \sa setRenderHint(), RenderHint +*/ +bool QwtPlotItem::testRenderHint( RenderHint hint ) const +{ + return d_data->renderHints.testFlag( hint ); +} + +/*! + On multi core systems rendering of certain plot item + ( f.e QwtPlotRasterItem ) can be done in parallel in + several threads. + + The default setting is set to 1. + + \param numThreads Number of threads to be used for rendering. + If numThreads is set to 0, the system specific + ideal thread count is used. + + The default thread count is 1 ( = no additional threads ) +*/ +void QwtPlotItem::setRenderThreadCount( uint numThreads ) +{ + d_data->renderThreadCount = numThreads; +} + +/*! + \return Number of threads to be used for rendering. + If numThreads() is set to 0, the system specific + ideal thread count is used. +*/ +uint QwtPlotItem::renderThreadCount() const +{ + return d_data->renderThreadCount; +} + +/*! + Set the size of the legend icon + + The default setting is 8x8 pixels + + \param size Size + \sa legendIconSize(), legendIcon() +*/ +void QwtPlotItem::setLegendIconSize( const QSize &size ) +{ + if ( d_data->legendIconSize != size ) + { + d_data->legendIconSize = size; + legendChanged(); + } +} + +/*! + \return Legend icon size + \sa setLegendIconSize(), legendIcon() +*/ +QSize QwtPlotItem::legendIconSize() const +{ + return d_data->legendIconSize; +} + +/*! + \return Icon representing the item on the legend + + The default implementation returns an invalid icon + + \param index Index of the legend entry + ( usually there is only one ) + \param size Icon size + + \sa setLegendIconSize(), legendData() + */ +QwtGraphic QwtPlotItem::legendIcon( + int index, const QSizeF &size ) const +{ + Q_UNUSED( index ) + Q_UNUSED( size ) + + return QwtGraphic(); +} + +/*! + \brief Return a default icon from a brush + + The default icon is a filled rectangle used + in several derived classes as legendIcon(). + + \param brush Fill brush + \param size Icon size + + \return A filled rectangle + */ +QwtGraphic QwtPlotItem::defaultIcon( + const QBrush &brush, const QSizeF &size ) const +{ + QwtGraphic icon; + if ( !size.isEmpty() ) + { + icon.setDefaultSize( size ); + + QRectF r( 0, 0, size.width(), size.height() ); + + QPainter painter( &icon ); + painter.fillRect( r, brush ); + } + + return icon; +} + +//! Show the item +void QwtPlotItem::show() +{ + setVisible( true ); +} + +//! Hide the item +void QwtPlotItem::hide() +{ + setVisible( false ); +} + +/*! + Show/Hide the item + + \param on Show if true, otherwise hide + \sa isVisible(), show(), hide() +*/ +void QwtPlotItem::setVisible( bool on ) +{ + if ( on != d_data->isVisible ) + { + d_data->isVisible = on; + itemChanged(); + } +} + +/*! + \return true if visible + \sa setVisible(), show(), hide() +*/ +bool QwtPlotItem::isVisible() const +{ + return d_data->isVisible; +} + +/*! + Update the legend and call QwtPlot::autoRefresh() for the + parent plot. + + \sa QwtPlot::legendChanged(), QwtPlot::autoRefresh() +*/ +void QwtPlotItem::itemChanged() +{ + if ( d_data->plot ) + d_data->plot->autoRefresh(); +} + +/*! + Update the legend of the parent plot. + \sa QwtPlot::updateLegend(), itemChanged() +*/ +void QwtPlotItem::legendChanged() +{ + if ( testItemAttribute( QwtPlotItem::Legend ) && d_data->plot ) + d_data->plot->updateLegend( this ); +} + +/*! + Set X and Y axis + + The item will painted according to the coordinates of its Axes. + + \param xAxis X Axis ( QwtAxis::xBottom or QwtAxis::xTop ) + \param yAxis Y Axis ( QwtAxis::yLeft or QwtAxis::yRight ) + + \sa setXAxis(), setYAxis(), xAxis(), yAxis() +*/ +void QwtPlotItem::setAxes( QwtAxisId xAxis, QwtAxisId yAxis ) +{ + if ( xAxis.id >= 0 && + ( xAxis.pos == QwtAxis::xBottom || xAxis.pos == QwtAxis::xTop ) ) + { + d_data->xAxis = xAxis; + } + + if ( yAxis.id >= 0 && QwtAxis::isYAxis( yAxis.pos ) ) + { + d_data->yAxis = yAxis; + } + + itemChanged(); +} + +/*! + Set the X axis + + The item will painted according to the coordinates its Axes. + + \param axis X Axis ( QwtAxis::xBottom or QwtAxis::xTop ) + \sa setAxes(), setYAxis(), xAxis() +*/ +void QwtPlotItem::setXAxis( QwtAxisId axisId ) +{ + if ( axisId.pos == QwtAxis::xBottom || axisId.pos == QwtAxis::xTop ) + { + if ( axisId.id >= 0 ) + { + d_data->xAxis = axisId; + itemChanged(); + } + } +} + +/*! + Set the Y axis + + The item will painted according to the coordinates its Axes. + + \param axis Y Axis ( QwtAxis::yLeft or QwtAxis::yRight ) + \sa setAxes(), setXAxis(), yAxis() +*/ +void QwtPlotItem::setYAxis( QwtAxisId axisId ) +{ + if ( QwtAxis::isYAxis( axisId.pos ) ) + { + if ( axisId.id >= 0 ) + { + d_data->yAxis = axisId; + itemChanged(); + } + } +} + +//! Return xAxis +QwtAxisId QwtPlotItem::xAxis() const +{ + return d_data->xAxis; +} + +//! Return yAxis +QwtAxisId QwtPlotItem::yAxis() const +{ + return d_data->yAxis; +} + +/*! + \return An invalid bounding rect: QRectF(1.0, 1.0, -2.0, -2.0) + \note A width or height < 0.0 is ignored by the autoscaler +*/ +QRectF QwtPlotItem::boundingRect() const +{ + return QRectF( 1.0, 1.0, -2.0, -2.0 ); // invalid +} + +/*! + \brief Calculate a hint for the canvas margin + + When the QwtPlotItem::Margins flag is enabled the plot item + indicates, that it needs some margins at the borders of the canvas. + This is f.e. used by bar charts to reserve space for displaying + the bars. + + The margins are in target device coordinates ( pixels on screen ) + + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param canvasRect Contents rectangle of the canvas in painter coordinates + \param left Returns the left margin + \param top Returns the top margin + \param right Returns the right margin + \param bottom Returns the bottom margin + + \return The default implementation returns 0 for all margins + + \sa QwtPlot::getCanvasMarginsHint(), QwtPlot::updateCanvasMargins() + */ +void QwtPlotItem::getCanvasMarginHint( const QwtScaleMap &xMap, + const QwtScaleMap &yMap, const QRectF &canvasRect, + double &left, double &top, double &right, double &bottom ) const +{ + Q_UNUSED( xMap ); + Q_UNUSED( yMap ); + Q_UNUSED( canvasRect ); + + // use QMargins, when we don't need to support Qt < 4.6 anymore + left = top = right = bottom = 0.0; +} + +/*! + \brief Return all information, that is needed to represent + the item on the legend + + Most items are represented by one entry on the legend + showing an icon and a text, but f.e. QwtPlotMultiBarChart + displays one entry for each bar. + + QwtLegendData is basically a list of QVariants that makes it + possible to overload and reimplement legendData() to + return almost any type of information, that is understood + by the receiver that acts as the legend. + + The default implementation returns one entry with + the title() of the item and the legendIcon(). + + \return Data, that is needed to represent the item on the legend + \sa title(), legendIcon(), QwtLegend, QwtPlotLegendItem + */ +QList QwtPlotItem::legendData() const +{ + QwtLegendData data; + + QwtText label = title(); + label.setRenderFlags( label.renderFlags() & Qt::AlignLeft ); + + QVariant titleValue; + qVariantSetValue( titleValue, label ); + data.setValue( QwtLegendData::TitleRole, titleValue ); + + const QwtGraphic graphic = legendIcon( 0, legendIconSize() ); + if ( !graphic.isNull() ) + { + QVariant iconValue; + qVariantSetValue( iconValue, graphic ); + data.setValue( QwtLegendData::IconRole, iconValue ); + } + + QList list; + list += data; + + return list; +} + +/*! + \brief Update the item to changes of the axes scale division + + Update the item, when the axes of plot have changed. + The default implementation does nothing, but items that depend + on the scale division (like QwtPlotGrid()) have to reimplement + updateScaleDiv() + + updateScaleDiv() is only called when the ScaleInterest interest + is enabled. The default implementation does nothing. + + \param xScaleDiv Scale division of the x-axis + \param yScaleDiv Scale division of the y-axis + + \sa QwtPlot::updateAxes(), ScaleInterest +*/ +void QwtPlotItem::updateScaleDiv( const QwtScaleDiv &xScaleDiv, + const QwtScaleDiv &yScaleDiv ) +{ + Q_UNUSED( xScaleDiv ); + Q_UNUSED( yScaleDiv ); +} + +/*! + \brief Update the item to changes of the legend info + + Plot items that want to display a legend ( not those, that want to + be displayed on a legend ! ) will have to implement updateLegend(). + + updateLegend() is only called when the LegendInterest interest + is enabled. The default implementation does nothing. + + \param item Plot item to be displayed on a legend + \param data Attributes how to display item on the legend + + \sa QwtPlotLegendItem + + \note Plot items, that want to be displayed on a legend + need to enable the QwtPlotItem::Legend flag and to implement + legendData() and legendIcon() + */ +void QwtPlotItem::updateLegend( const QwtPlotItem *item, + const QList &data ) +{ + Q_UNUSED( item ); + Q_UNUSED( data ); +} + +/*! + \brief Calculate the bounding scale rectangle of 2 maps + + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + + \return Bounding scale rect of the scale maps, not normalized +*/ +QRectF QwtPlotItem::scaleRect( const QwtScaleMap &xMap, + const QwtScaleMap &yMap ) const +{ + return QRectF( xMap.s1(), yMap.s1(), + xMap.sDist(), yMap.sDist() ); +} + +/*! + \brief Calculate the bounding paint rectangle of 2 maps + + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + + \return Bounding paint rectangle of the scale maps, not normalized +*/ +QRectF QwtPlotItem::paintRect( const QwtScaleMap &xMap, + const QwtScaleMap &yMap ) const +{ + const QRectF rect( xMap.p1(), yMap.p1(), + xMap.pDist(), yMap.pDist() ); + + return rect; +} diff --git a/qwt/src/qwt_plot_item.h b/qwt/src/qwt_plot_item.h new file mode 100644 index 000000000..f17201c8b --- /dev/null +++ b/qwt/src/qwt_plot_item.h @@ -0,0 +1,308 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_ITEM_H +#define QWT_PLOT_ITEM_H + +#include "qwt_global.h" +#include "qwt_text.h" +#include "qwt_legend_data.h" +#include "qwt_graphic.h" +#include "qwt_axis_id.h" +#include +#include +#include + +class QPainter; +class QwtScaleMap; +class QwtScaleDiv; +class QwtPlot; + +/*! + \brief Base class for items on the plot canvas + + A plot item is "something", that can be painted on the plot canvas, + or only affects the scales of the plot widget. They can be categorized as: + + - Representator\n + A "Representator" is an item that represents some sort of data + on the plot canvas. The different representator classes are organized + according to the characteristics of the data: + - QwtPlotMarker + Represents a point or a horizontal/vertical coordinate + - QwtPlotCurve + Represents a series of points + - QwtPlotSpectrogram ( QwtPlotRasterItem ) + Represents raster data + - ... + + - Decorators\n + A "Decorator" is an item, that displays additional information, that + is not related to any data: + - QwtPlotGrid + - QwtPlotScaleItem + - QwtPlotSvgItem + - ... + + Depending on the QwtPlotItem::ItemAttribute flags, an item is included + into autoscaling or has an entry on the legend. + + Before misusing the existing item classes it might be better to + implement a new type of plot item + ( don't implement a watermark as spectrogram ). + Deriving a new type of QwtPlotItem primarily means to implement + the YourPlotItem::draw() method. + + \sa The cpuplot example shows the implementation of additional plot items. +*/ + +class QWT_EXPORT QwtPlotItem +{ +public: + /*! + \brief Runtime type information + + RttiValues is used to cast plot items, without + having to enable runtime type information of the compiler. + */ + enum RttiValues + { + //! Unspecific value, that can be used, when it doesn't matter + Rtti_PlotItem = 0, + + //! For QwtPlotGrid + Rtti_PlotGrid, + + //! For QwtPlotScaleItem + Rtti_PlotScale, + + //! For QwtPlotLegendItem + Rtti_PlotLegend, + + //! For QwtPlotMarker + Rtti_PlotMarker, + + //! For QwtPlotCurve + Rtti_PlotCurve, + + //! For QwtPlotSpectroCurve + Rtti_PlotSpectroCurve, + + //! For QwtPlotIntervalCurve + Rtti_PlotIntervalCurve, + + //! For QwtPlotHistogram + Rtti_PlotHistogram, + + //! For QwtPlotSpectrogram + Rtti_PlotSpectrogram, + + //! For QwtPlotSvgItem + Rtti_PlotSVG, + + //! For QwtPlotTradingCurve + Rtti_PlotTradingCurve, + + //! For QwtPlotBarChart + Rtti_PlotBarChart, + + //! For QwtPlotMultiBarChart + Rtti_PlotMultiBarChart, + + //! For QwtPlotShapeItem + Rtti_PlotShape, + + //! For QwtPlotTextLabel + Rtti_PlotTextLabel, + + //! For QwtPlotZoneItem + Rtti_PlotZone, + + /*! + Values >= Rtti_PlotUserItem are reserved for plot items + not implemented in the Qwt library. + */ + Rtti_PlotUserItem = 1000 + }; + + /*! + \brief Plot Item Attributes + + Various aspects of a plot widget depend on the attributes of + the attached plot items. If and how a single plot item + participates in these updates depends on its attributes. + + \sa setItemAttribute(), testItemAttribute(), ItemInterest + */ + enum ItemAttribute + { + //! The item is represented on the legend. + Legend = 0x01, + + /*! + The boundingRect() of the item is included in the + autoscaling calculation as long as its width or height + is >= 0.0. + */ + AutoScale = 0x02, + + /*! + The item needs extra space to display something outside + its bounding rectangle. + \sa getCanvasMarginHint() + */ + Margins = 0x04 + }; + + //! Plot Item Attributes + typedef QFlags ItemAttributes; + + /*! + \brief Plot Item Interests + + Plot items might depend on the situation of the corresponding + plot widget. By enabling an interest the plot item will be + notified, when the corresponding attribute of the plot widgets + has changed. + + \sa setItemAttribute(), testItemAttribute(), ItemInterest + */ + enum ItemInterest + { + /*! + The item is interested in updates of the scales + \sa updateScaleDiv() + */ + ScaleInterest = 0x01, + + /*! + The item is interested in updates of the legend ( of other items ) + This flag is intended for items, that want to implement a legend + for displaying entries of other plot item. + + \note If the plot item wants to be represented on a legend + enable QwtPlotItem::Legend instead. + + \sa updateLegend() + */ + LegendInterest = 0x02 + }; + + //! Plot Item Interests + typedef QFlags ItemInterests; + + //! Render hints + enum RenderHint + { + //! Enable antialiasing + RenderAntialiased = 0x1 + }; + + //! Render hints + typedef QFlags RenderHints; + + explicit QwtPlotItem( const QwtText &title = QwtText() ); + virtual ~QwtPlotItem(); + + void attach( QwtPlot *plot ); + void detach(); + + QwtPlot *plot() const; + + void setTitle( const QString &title ); + void setTitle( const QwtText &title ); + const QwtText &title() const; + + virtual int rtti() const; + + void setItemAttribute( ItemAttribute, bool on = true ); + bool testItemAttribute( ItemAttribute ) const; + + void setItemInterest( ItemInterest, bool on = true ); + bool testItemInterest( ItemInterest ) const; + + void setRenderHint( RenderHint, bool on = true ); + bool testRenderHint( RenderHint ) const; + + void setRenderThreadCount( uint numThreads ); + uint renderThreadCount() const; + + void setLegendIconSize( const QSize & ); + QSize legendIconSize() const; + + double z() const; + void setZ( double z ); + + void show(); + void hide(); + virtual void setVisible( bool ); + bool isVisible () const; + + void setAxes( QwtAxisId xAxis, QwtAxisId yAxis ); + + void setXAxis( QwtAxisId ); + QwtAxisId xAxis() const; + + void setYAxis( QwtAxisId ); + QwtAxisId yAxis() const; + + virtual void itemChanged(); + virtual void legendChanged(); + + /*! + \brief Draw the item + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param canvasRect Contents rect of the canvas in painter coordinates + */ + virtual void draw( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect ) const = 0; + + virtual QRectF boundingRect() const; + + virtual void getCanvasMarginHint( + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasSize, + double &left, double &top, double &right, double &bottom) const; + + virtual void updateScaleDiv( + const QwtScaleDiv&, const QwtScaleDiv& ); + + virtual void updateLegend( const QwtPlotItem *, + const QList & ); + + QRectF scaleRect( const QwtScaleMap &, const QwtScaleMap & ) const; + QRectF paintRect( const QwtScaleMap &, const QwtScaleMap & ) const; + + virtual QList legendData() const; + + virtual QwtGraphic legendIcon( int index, const QSizeF & ) const; + +protected: + QwtGraphic defaultIcon( const QBrush &, const QSizeF & ) const; + +private: + // Disabled copy constructor and operator= + QwtPlotItem( const QwtPlotItem & ); + QwtPlotItem &operator=( const QwtPlotItem & ); + + class PrivateData; + PrivateData *d_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotItem::ItemAttributes ) +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotItem::ItemInterests ) +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotItem::RenderHints ) + +Q_DECLARE_METATYPE( QwtPlotItem * ) + +#endif diff --git a/qwt/src/qwt_plot_layout.cpp b/qwt/src/qwt_plot_layout.cpp new file mode 100644 index 000000000..f4418566e --- /dev/null +++ b/qwt/src/qwt_plot_layout.cpp @@ -0,0 +1,1805 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_layout.h" +#include "qwt_text.h" +#include "qwt_text_label.h" +#include "qwt_scale_widget.h" +#include "qwt_abstract_legend.h" +#include +#include + +class QwtPlotLayoutData +{ +public: + struct LegendData + { + void init( const QwtAbstractLegend *legend ) + { + if ( legend ) + { + frameWidth = legend->frameWidth(); + hScrollExtent = legend->scrollExtent( Qt::Horizontal ); + vScrollExtent = legend->scrollExtent( Qt::Vertical ); + + hint = legend->sizeHint(); + } + } + + QSize legendHint( const QwtAbstractLegend *legend, const QRectF &rect ) const + { + int w = qMin( hint.width(), qFloor( rect.width() ) ); + + int h = legend->heightForWidth( w ); + if ( h <= 0 ) + h = hint.height(); + + if ( h > rect.height() ) + w += hScrollExtent; + + return QSize( w, h ); + } + + int frameWidth; + int hScrollExtent; + int vScrollExtent; + QSize hint; + }; + + struct LabelData + { + void init( const QwtTextLabel *label ) + { + frameWidth = 0; + text = QwtText(); + + if ( label ) + { + text = label->text(); + if ( !( text.testPaintAttribute( QwtText::PaintUsingTextFont ) ) ) + text.setFont( label->font() ); + + frameWidth = label->frameWidth(); + } + } + + QwtText text; + int frameWidth; + }; + + struct ScaleData + { + void init( const QwtScaleWidget *axisWidget ) + { + if ( axisWidget ) + { + isVisible = true; + + scaleWidget = axisWidget; + scaleFont = axisWidget->font(); + + start = axisWidget->startBorderDist(); + end = axisWidget->endBorderDist(); + + baseLineOffset = axisWidget->margin(); + + dimWithoutTitle = axisWidget->dimForLength( + QWIDGETSIZE_MAX, scaleFont ); + + if ( !axisWidget->title().isEmpty() ) + { + dimWithoutTitle -= + axisWidget->titleHeightForWidth( QWIDGETSIZE_MAX ); + } + } + else + { + isVisible = false; + axisWidget = NULL; + start = 0; + end = 0; + baseLineOffset = 0; + dimWithoutTitle = 0; + } + + } + + bool isVisible; + const QwtScaleWidget *scaleWidget; + QFont scaleFont; + int start; + int end; + int baseLineOffset; + int dimWithoutTitle; + }; + + struct CanvasData + { + void init( const QWidget *canvas ) + { + canvas->getContentsMargins( + &contentsMargins[ QwtAxis::yLeft ], + &contentsMargins[ QwtAxis::xTop ], + &contentsMargins[ QwtAxis::yRight ], + &contentsMargins[ QwtAxis::xBottom ] ); + } + + int contentsMargins[ QwtAxis::PosCount ]; + }; + +public: + enum Label + { + Title, + Footer, + + NumLabels + }; + + QwtPlotLayoutData( const QwtPlot * ); + bool hasSymmetricYAxes() const; + + int numAxes( int axisPos ) const + { + return scaleData[ axisPos ].size(); + } + + ScaleData &axisData( QwtAxisId axisId ) + { + return scaleData[ axisId.pos ][ axisId.id ]; + } + + const ScaleData &axisData( QwtAxisId axisId ) const + { + return scaleData[ axisId.pos ][ axisId.id ]; + } + + LegendData legendData; + LabelData labelData[ NumLabels ]; + QVector scaleData[ QwtAxis::PosCount ]; + CanvasData canvasData; + + double tickOffset[ QwtAxis::PosCount ]; + int numVisibleScales[ QwtAxis::PosCount ]; +}; + +/* + Extract all layout relevant data from the plot components +*/ +QwtPlotLayoutData::QwtPlotLayoutData( const QwtPlot *plot ) +{ + legendData.init( plot->legend() ); + labelData[ Title ].init( plot->titleLabel() ); + labelData[ Footer ].init( plot->footerLabel() ); + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + numVisibleScales[ axisPos ] = 0; + + const int axesCount = plot->axesCount( axisPos ); + scaleData[ axisPos ].resize( axesCount ); + tickOffset[ axisPos ] = 0; + + for ( int i = 0; i < axesCount; i++ ) + { + const QwtAxisId axisId( axisPos, i ); + + ScaleData &sclData = axisData( axisId ); + + if ( plot->isAxisVisible( axisId ) ) + { + const QwtScaleWidget *axisWidget = plot->axisWidget( axisId ); + + if ( numVisibleScales[ axisPos ] == 0 ) + { + tickOffset[ axisPos ] = axisWidget->margin(); + + const QwtScaleDraw *scaleDraw = axisWidget->scaleDraw(); + if ( scaleDraw->hasComponent( QwtAbstractScaleDraw::Ticks ) ) + { + tickOffset[ axisPos ] += scaleDraw->maxTickLength(); + } + } + + numVisibleScales[ axisPos ]++; + sclData.init( axisWidget ); + } + else + { + sclData.init( NULL ); + } + } + } + + canvasData.init( plot->canvas() ); +} + +bool QwtPlotLayoutData::hasSymmetricYAxes() const +{ + return numVisibleScales[ QwtAxis::yLeft ] == + numVisibleScales[ QwtAxis::yRight ]; +} + +class QwtPlotLayoutHintData +{ +public: + QwtPlotLayoutHintData( const QwtPlot *plot ); + + bool hasSymmetricYAxes() const + { + return numVisibleScales[ QwtAxis::yLeft ] == + numVisibleScales[ QwtAxis::yRight ]; + } + + int yAxesWidth() const + { + return m_axesSize[ QwtAxis::yLeft ].width() + + m_axesSize[ QwtAxis::yRight ].width(); + } + + int yAxesHeight() const + { + return qMax( m_axesSize[ QwtAxis::yLeft ].height(), + m_axesSize[ QwtAxis::yRight ].height() ); + } + + int xAxesHeight() const + { + return m_axesSize[ QwtAxis::xTop ].height() + + m_axesSize[ QwtAxis::xBottom ].height(); + } + + int xAxesWidth() const + { + return qMax( m_axesSize[ QwtAxis::xTop ].width(), + m_axesSize[ QwtAxis::xBottom ].width() ); + } + + int alignedSize( const QwtAxisId ) const; + + struct ScaleData + { + ScaleData() + { + w = h = minLeft = minRight = 0; + } + + int w; + int h; + int minLeft; + int minRight; + }; + + int canvasBorder[QwtAxis::PosCount]; + int tickOffset[QwtAxis::PosCount]; + int numVisibleScales[ QwtAxis::PosCount ]; + +private: + const ScaleData &axisData( QwtAxisId axisId ) const + { + return scaleData[ axisId.pos ][ axisId.id ]; + } + + ScaleData &axisData( QwtAxisId axisId ) + { + return scaleData[ axisId.pos ][ axisId.id ]; + } + + int axesWidth( int axisPos ) const + { + return m_axesSize[axisPos].width(); + } + + int axesHeight( int axisPos ) const + { + return m_axesSize[axisPos].height(); + } + + QSize m_axesSize[ QwtAxis::PosCount ]; + + QVector scaleData[ QwtAxis::PosCount ]; +}; + +QwtPlotLayoutHintData::QwtPlotLayoutHintData( const QwtPlot *plot ) +{ + int contentsMargins[ 4 ]; + + plot->canvas()->getContentsMargins( + &contentsMargins[ QwtAxis::yLeft ], + &contentsMargins[ QwtAxis::xTop ], + &contentsMargins[ QwtAxis::yRight ], + &contentsMargins[ QwtAxis::xBottom ] ); + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + QSize &axesSize = m_axesSize[ axisPos ]; + axesSize = QSize( 0, 0 ); + + canvasBorder[ axisPos ] = contentsMargins[axisPos] + + plot->plotLayout()->canvasMargin( axisPos ) + 1; + + numVisibleScales[ axisPos ] = 0; + tickOffset[ axisPos ] = 0; + + const int axesCount = plot->axesCount( axisPos ); + scaleData[ axisPos ].resize( axesCount ); + + for ( int i = 0; i < axesCount; i++ ) + { + const QwtAxisId axisId( axisPos, i ); + + if ( plot->isAxisVisible( axisId ) ) + { + const QwtScaleWidget *scl = plot->axisWidget( axisId ); + + const QSize hint = scl->minimumSizeHint(); + + ScaleData &sd = axisData( axisId ); + sd.w = hint.width(); + sd.h = hint.height(); + scl->getBorderDistHint( sd.minLeft, sd.minRight ); + + if ( numVisibleScales[axisPos] == 0 ) + { + tickOffset[axisPos] = scl->margin(); + if ( scl->scaleDraw()->hasComponent( QwtAbstractScaleDraw::Ticks ) ) + tickOffset[axisPos] += qCeil( scl->scaleDraw()->maxTickLength() ); + } + + numVisibleScales[axisPos]++; + + if ( QwtAxis::isYAxis( axisId.pos ) ) + { + axesSize.setWidth( axesSize.width() + sd.w ); + axesSize.setHeight( qMax( axesSize.height(), sd.h ) ); + } + else + { + axesSize.setHeight( axesSize.height() + sd.h ); + axesSize.setWidth( qMax( axesSize.width(), sd.w ) ); + } + } + } + + if ( axesCount > 1 ) + { + // The width of the y axes and the height of the x axes depends + // on the line breaks in the scale title. So after knowning the + // bounding height/width we might have some to subtract some line + // breaks, we don't have anymore. + + for ( int i = 0; i < axesCount; i++ ) + { + QSize &axesSize = m_axesSize[ axisPos ]; + + const QwtAxisId axisId( axisPos, i ); + + if ( plot->isAxisVisible( axisId ) ) + { + const QwtScaleWidget *scl = plot->axisWidget( axisId ); + ScaleData &sd = axisData( axisId ); + + if ( QwtAxis::isYAxis( axisId.pos ) ) + { + int off = scl->titleHeightForWidth( sd.h ) - + scl->titleHeightForWidth( yAxesHeight() ); + + axesSize.setWidth( axesSize.width() - off ); + } + else + { + int off = scl->titleHeightForWidth( sd.w ) - + scl->titleHeightForWidth( xAxesWidth() ); + + axesSize.setHeight( axesSize.height() - off ); + } + } + } + } + } +} + +int QwtPlotLayoutHintData::alignedSize( const QwtAxisId axisId ) const +{ + const QwtPlotLayoutHintData::ScaleData &sd = axisData( axisId ); + if ( QwtAxis::isXAxis( axisId.pos ) && sd.w ) + { + int w = sd.w; + const int leftW = m_axesSize[QwtAxis::yLeft].width(); + const int rightW = m_axesSize[QwtAxis::yRight].width(); + + const int shiftLeft = sd.minLeft - canvasBorder[QwtAxis::yLeft]; + if ( shiftLeft > 0 && leftW ) + w -= qMin( shiftLeft, leftW ); + + const int shiftRight = sd.minRight - canvasBorder[QwtAxis::yRight]; + if ( shiftRight > 0 && rightW ) + w -= qMin( shiftRight, rightW ); + + return w; + } + if ( QwtAxis::isYAxis( axisId.pos ) && sd.h ) + { + int h = sd.h; + + const int topH = m_axesSize[QwtAxis::xTop].height(); + const int bottomH = m_axesSize[QwtAxis::xBottom].height(); + + const int shiftBottom = sd.minLeft - canvasBorder[QwtAxis::xBottom]; + if ( shiftBottom > 0 && bottomH ) + h -= qMin( shiftBottom, tickOffset[QwtAxis::xBottom] ); + + const int shiftTop = sd.minRight - canvasBorder[QwtAxis::xTop]; + if ( shiftTop > 0 && topH ) + h -= qMin( shiftTop, tickOffset[QwtAxis::xTop] ); + + return h; + } + + return 0; +} + +class QwtPlotLayoutEngine +{ +public: + class Dimensions + { + public: + Dimensions( const QwtPlotLayoutData& layoutData ) + { + dimTitle = dimFooter = 0; + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + dimAxisVector[axisPos] = + QVector( layoutData.numAxes( axisPos ), 0 ); + } + } + + int dimAxis( QwtAxisId axisId ) const + { + return dimAxisVector[ axisId.pos ].at( axisId.id ); + } + + void setDimAxis( QwtAxisId axisId, int dim ) + { + dimAxisVector[ axisId.pos ][ axisId.id ] = dim; + } + + inline int dimAxes( int axisPos ) const + { + const QVector &dims = dimAxisVector[ axisPos ]; + + int dim = 0; + for ( int i = 0; i < dims.size(); i++ ) + dim += dims[i]; + + return dim; + } + + inline int dimYAxes() const + { + return dimAxes( QwtAxis::yLeft ) + dimAxes( QwtAxis::yRight ); + } + + inline int dimXAxes() const + { + return dimAxes( QwtAxis::xTop ) + dimAxes( QwtAxis::xBottom ); + } + + inline QRectF centered( const QRectF &rect, const QRectF &labelRect ) const + { + QRectF r = labelRect; + r.setX( rect.left() + dimAxes( QwtAxis::yLeft ) ); + r.setWidth( rect.width() - dimYAxes() ); + + return r; + } + + inline QRectF innerRect( const QRectF &rect ) const + { + QRectF r( + rect.x() + dimAxes( QwtAxis::yLeft ), + rect.y() + dimAxes( QwtAxis::xTop ), + rect.width() - dimYAxes(), + rect.height() - dimXAxes() ); + + if ( r.width() < 0 ) + { + r.setX( rect.center().x() ); + r.setWidth( 0 ); + } + if ( r.height() < 0 ) + { + r.setY( rect.center().x() ); + r.setHeight( 0 ); + } + + return r; + } + + int dimTitle; + int dimFooter; + + private: + QVector dimAxisVector[QwtAxis::PosCount]; + }; + + QwtPlotLayoutEngine(): + d_spacing( 5 ) + { + } + + QRectF layoutLegend( QwtPlotLayout::Options, + const QwtPlotLayoutData::LegendData &, const QRectF &, const QSize & ) const; + + QRectF alignLegend( const QSize &legendHint, + const QRectF &canvasRect, const QRectF &legendRect ) const; + + void alignScales( QwtPlotLayout::Options, const QwtPlotLayoutData &, + QRectF &canvasRect, + QVector scaleRect[QwtAxis::PosCount] ) const; + + Dimensions layoutDimensions( QwtPlotLayout::Options, + const QwtPlotLayoutData &, const QRectF &rect ) const; + + inline void setSpacing( int spacing ) { d_spacing = spacing; } + inline int spacing() const { return d_spacing; } + + inline void setAlignCanvas( int axisPos, bool on ) { d_alignCanvas[ axisPos ] = on; } + inline bool alignCanvas( int axisPos ) const { return d_alignCanvas[ axisPos ]; } + + inline void setCanvasMargin( int axisPos, int margin ) { d_canvasMargin[ axisPos ] = margin; } + inline int canvasMargin( int axisPos ) const { return d_canvasMargin[ axisPos ]; } + + inline void setLegendPos( QwtPlot::LegendPosition pos ) { d_legendPos = pos; } + inline QwtPlot::LegendPosition legendPos() const { return d_legendPos; } + + inline void setLegendRatio( double ratio ) { d_legendRatio = ratio; } + inline double legendRatio() const { return d_legendRatio; } + +private: + int heightForWidth( QwtPlotLayoutData::Label, const QwtPlotLayoutData &, + QwtPlotLayout::Options, double width, int axesWidth ) const; + + QwtPlot::LegendPosition d_legendPos; + double d_legendRatio; + + int d_canvasMargin[QwtAxis::PosCount]; + bool d_alignCanvas[QwtAxis::PosCount]; + + int d_spacing; +}; + +QRectF QwtPlotLayoutEngine::layoutLegend( QwtPlotLayout::Options options, + const QwtPlotLayoutData::LegendData &legendData, + const QRectF &rect, const QSize &legendHint ) const +{ + int dim; + if ( d_legendPos == QwtPlot::LeftLegend + || d_legendPos == QwtPlot::RightLegend ) + { + // We don't allow vertical legends to take more than + // half of the available space. + + dim = qMin( legendHint.width(), int( rect.width() * d_legendRatio ) ); + + if ( !( options & QwtPlotLayout::IgnoreScrollbars ) ) + { + if ( legendHint.height() > rect.height() ) + { + // The legend will need additional + // space for the vertical scrollbar. + + dim += legendData.hScrollExtent; + } + } + } + else + { + dim = qMin( legendHint.height(), int( rect.height() * d_legendRatio ) ); + dim = qMax( dim, legendData.vScrollExtent ); + } + + QRectF legendRect = rect; + switch ( d_legendPos ) + { + case QwtPlot::LeftLegend: + { + legendRect.setWidth( dim ); + break; + } + case QwtPlot::RightLegend: + { + legendRect.setX( rect.right() - dim ); + legendRect.setWidth( dim ); + break; + } + case QwtPlot::TopLegend: + { + legendRect.setHeight( dim ); + break; + } + case QwtPlot::BottomLegend: + { + legendRect.setY( rect.bottom() - dim ); + legendRect.setHeight( dim ); + break; + } + } + + return legendRect; +} + +QRectF QwtPlotLayoutEngine::alignLegend( const QSize &legendHint, + const QRectF &canvasRect, const QRectF &legendRect ) const +{ + QRectF alignedRect = legendRect; + + if ( d_legendPos == QwtPlot::BottomLegend + || d_legendPos == QwtPlot::TopLegend ) + { + if ( legendHint.width() < canvasRect.width() ) + { + alignedRect.setX( canvasRect.x() ); + alignedRect.setWidth( canvasRect.width() ); + } + } + else + { + if ( legendHint.height() < canvasRect.height() ) + { + alignedRect.setY( canvasRect.y() ); + alignedRect.setHeight( canvasRect.height() ); + } + } + + return alignedRect; +} + +int QwtPlotLayoutEngine::heightForWidth( + QwtPlotLayoutData::Label labelType, const QwtPlotLayoutData &layoutData, + QwtPlotLayout::Options options, + double width, int axesWidth ) const +{ + const QwtPlotLayoutData::LabelData &labelData = layoutData.labelData[ labelType ]; + + if ( labelData.text.isEmpty() ) + return 0; + + double w = width; + + if ( !layoutData.hasSymmetricYAxes() ) + { + // center to the canvas + w -= axesWidth; + } + + int d = qCeil( labelData.text.heightForWidth( w ) ); + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + d += 2 * labelData.frameWidth; + + return d; +} + +QwtPlotLayoutEngine::Dimensions +QwtPlotLayoutEngine::layoutDimensions( QwtPlotLayout::Options options, + const QwtPlotLayoutData &layoutData, const QRectF &rect ) const +{ + Dimensions dimensions( layoutData ); + + int backboneOffset[QwtAxis::PosCount]; + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + backboneOffset[ axisPos ] = 0; + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + backboneOffset[ axisPos ] += layoutData.canvasData.contentsMargins[ axisPos ]; + + if ( !d_alignCanvas[ axisPos ] ) + backboneOffset[ axisPos ] += d_canvasMargin[ axisPos ]; + } + + bool done = false; + while ( !done ) + { + done = true; + + // the size for the 4 axis depend on each other. Expanding + // the height of a horizontal axis will shrink the height + // for the vertical axis, shrinking the height of a vertical + // axis will result in a line break what will expand the + // width and results in shrinking the width of a horizontal + // axis what might result in a line break of a horizontal + // axis ... . So we loop as long until no size changes. + + if ( !( options & QwtPlotLayout::IgnoreTitle ) ) + { + const int d = heightForWidth( QwtPlotLayoutData::Title, layoutData, options, + rect.width(), dimensions.dimYAxes() ); + + if ( d > dimensions.dimTitle ) + { + dimensions.dimTitle = d; + done = false; + } + } + + if ( !( options & QwtPlotLayout::IgnoreFooter ) ) + { + const int d = heightForWidth( QwtPlotLayoutData::Footer, layoutData, options, + rect.width(), dimensions.dimYAxes() ); + + if ( d > dimensions.dimFooter ) + { + dimensions.dimFooter = d; + done = false; + } + } + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + for ( int i = 0; i < layoutData.numAxes( axisPos ); i++ ) + { + const QwtAxisId axisId( axisPos, i ); + + const struct QwtPlotLayoutData::ScaleData &scaleData = layoutData.axisData( axisId ); + + if ( scaleData.isVisible ) + { + double length; + if ( QwtAxis::isXAxis( axisPos ) ) + { + length = rect.width() - dimensions.dimYAxes(); + length -= scaleData.start + scaleData.end; + + if ( dimensions.dimAxes( QwtAxis::yRight ) > 0 ) + length -= 1; + + length += qMin( dimensions.dimAxes( QwtAxis::yLeft ), + scaleData.start - backboneOffset[QwtAxis::yLeft] ); + + length += qMin( dimensions.dimAxes( QwtAxis::yRight ), + scaleData.end - backboneOffset[QwtAxis::yRight] ); + } + else // y axis + { + length = rect.height() - dimensions.dimXAxes(); + length -= scaleData.start + scaleData.end; + length -= 1; + + if ( dimensions.dimAxes( QwtAxis::xBottom ) <= 0 ) + length -= 1; + + if ( dimensions.dimAxes( QwtAxis::xTop ) <= 0 ) + length -= 1; + + /* + The tick labels of the y axes are always left/right from the + backbone/ticks of the x axes - but we have to take care, + that te labels don't overlap. + */ + if ( dimensions.dimAxes( QwtAxis::xBottom ) > 0 ) + { + length += qMin( + layoutData.tickOffset[QwtAxis::xBottom], + double( scaleData.start - backboneOffset[QwtAxis::xBottom] ) ); + } + if ( dimensions.dimAxes( QwtAxis::xTop ) > 0 ) + { + length += qMin( + layoutData.tickOffset[QwtAxis::xTop], + double( scaleData.end - backboneOffset[QwtAxis::xTop] ) ); + } + + if ( dimensions.dimTitle > 0 ) + length -= dimensions.dimTitle + d_spacing; + } + + int d = scaleData.dimWithoutTitle; + if ( !scaleData.scaleWidget->title().isEmpty() ) + { + d += scaleData.scaleWidget->titleHeightForWidth( qFloor( length ) ); + } + + + if ( d > dimensions.dimAxis( axisId ) ) + { + dimensions.setDimAxis( axisId, d ); + done = false; + } + } + } + } + } + + return dimensions; +} + +void QwtPlotLayoutEngine::alignScales( QwtPlotLayout::Options options, + const QwtPlotLayoutData &layoutData, QRectF &canvasRect, + QVector scaleRect[QwtAxis::PosCount] ) const +{ + int backboneOffset[QwtAxis::PosCount]; + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + backboneOffset[ axisPos ] = 0; + + if ( !d_alignCanvas[ axisPos ] ) + { + backboneOffset[ axisPos ] += d_canvasMargin[ axisPos ]; + } + + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + { + backboneOffset[ axisPos ] += + layoutData.canvasData.contentsMargins[ axisPos ]; + } + } + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + for ( int i = 0; i < layoutData.numAxes( axisPos ); i++ ) + { + QRectF &axisRect = scaleRect[ axisPos ][ i ]; + if ( !axisRect.isValid() ) + continue; + + const QwtAxisId axisId( axisPos, i ); + + const int startDist = layoutData.axisData( axisId ).start; + const int endDist = layoutData.axisData( axisId ).end; + + if ( QwtAxis::isXAxis( axisPos ) ) + { + const QRectF &leftScaleRect = scaleRect[QwtAxis::yLeft][ QWT_DUMMY_ID ]; + const int leftOffset = backboneOffset[QwtAxis::yLeft] - startDist; + + if ( leftScaleRect.isValid() ) + { + const double dx = leftOffset + leftScaleRect.width(); + if ( d_alignCanvas[QwtAxis::yLeft] && dx < 0.0 ) + { + /* + The axis needs more space than the width + of the left scale. + */ + const double cLeft = canvasRect.left(); // qreal -> double + canvasRect.setLeft( qMax( cLeft, axisRect.left() - dx ) ); + } + else + { + const double minLeft = leftScaleRect.left(); + const double left = axisRect.left() + leftOffset; + axisRect.setLeft( qMax( left, minLeft ) ); + } + } + else + { + if ( d_alignCanvas[QwtAxis::yLeft] && leftOffset < 0 ) + { + canvasRect.setLeft( qMax( canvasRect.left(), + axisRect.left() - leftOffset ) ); + } + else + { + if ( leftOffset > 0 ) + axisRect.setLeft( axisRect.left() + leftOffset ); + } + } + + const QRectF &rightScaleRect = scaleRect[QwtAxis::yRight][ QWT_DUMMY_ID ]; + const int rightOffset = + backboneOffset[QwtAxis::yRight] - endDist + 1; + + if ( rightScaleRect.isValid() ) + { + const double dx = rightOffset + rightScaleRect.width(); + if ( d_alignCanvas[QwtAxis::yRight] && dx < 0 ) + { + /* + The axis needs more space than the width + of the right scale. + */ + const double cRight = canvasRect.right(); // qreal -> double + canvasRect.setRight( qMin( cRight, axisRect.right() + dx ) ); + } + + const double maxRight = rightScaleRect.right(); + const double right = axisRect.right() - rightOffset; + axisRect.setRight( qMin( right, maxRight ) ); + } + else + { + if ( d_alignCanvas[QwtAxis::yRight] && rightOffset < 0 ) + { + canvasRect.setRight( qMin( canvasRect.right(), + axisRect.right() + rightOffset ) ); + } + else + { + if ( rightOffset > 0 ) + axisRect.setRight( axisRect.right() - rightOffset ); + } + } + } + else // y axes + { + const QRectF &bottomScaleRect = scaleRect[QwtAxis::xBottom][ QWT_DUMMY_ID ]; + const int bottomOffset = + backboneOffset[QwtAxis::xBottom] - endDist + 1; + + if ( bottomScaleRect.isValid() ) + { + const double dy = bottomOffset + bottomScaleRect.height(); + if ( d_alignCanvas[QwtAxis::xBottom] && dy < 0 ) + { + /* + The axis needs more space than the height + of the bottom scale. + */ + const double cBottom = canvasRect.bottom(); // qreal -> double + canvasRect.setBottom( qMin( cBottom, axisRect.bottom() + dy ) ); + } + else + { + const double maxBottom = bottomScaleRect.top() + + layoutData.tickOffset[QwtAxis::xBottom]; + const double bottom = axisRect.bottom() - bottomOffset; + axisRect.setBottom( qMin( bottom, maxBottom ) ); + } + } + else + { + if ( d_alignCanvas[QwtAxis::xBottom] && bottomOffset < 0 ) + { + canvasRect.setBottom( qMin( canvasRect.bottom(), + axisRect.bottom() + bottomOffset ) ); + } + else + { + if ( bottomOffset > 0 ) + axisRect.setBottom( axisRect.bottom() - bottomOffset ); + } + } + + const QRectF &topScaleRect = scaleRect[QwtAxis::xTop][ QWT_DUMMY_ID ]; + const int topOffset = backboneOffset[QwtAxis::xTop] - startDist; + + if ( topScaleRect.isValid() ) + { + const double dy = topOffset + topScaleRect.height(); + if ( d_alignCanvas[QwtAxis::xTop] && dy < 0 ) + { + /* + The axis needs more space than the height + of the top scale. + */ + const double cTop = canvasRect.top(); // qreal -> double + canvasRect.setTop( qMax( cTop, axisRect.top() - dy ) ); + } + else + { + const double minTop = topScaleRect.bottom() - + layoutData.tickOffset[QwtAxis::xTop]; + const double top = axisRect.top() + topOffset; + axisRect.setTop( qMax( top, minTop ) ); + } + } + else + { + if ( d_alignCanvas[QwtAxis::xTop] && topOffset < 0 ) + { + canvasRect.setTop( qMax( canvasRect.top(), + axisRect.top() - topOffset ) ); + } + else + { + if ( topOffset > 0 ) + axisRect.setTop( axisRect.top() + topOffset ); + } + } + } + } + } + + /* + The canvas has been aligned to the scale with largest + border distances. Now we have to realign the other scale. + */ + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + for ( int i = 0; i < layoutData.numAxes( axisPos ); i++ ) + { + const QwtAxisId axisId( axisPos, i ); + + QRectF &sRect = scaleRect[ axisId.pos ][ axisId.id ]; + const QwtPlotLayoutData::ScaleData &axisData = layoutData.axisData( axisId ); + + if ( !sRect.isValid() ) + continue; + + if ( QwtAxis::isXAxis( axisId.pos ) ) + { + if ( d_alignCanvas[QwtAxis::yLeft] ) + { + double y = canvasRect.left() - axisData.start; + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + y += layoutData.canvasData.contentsMargins[ QwtAxis::yLeft ]; + + sRect.setLeft( y ); + } + if ( d_alignCanvas[QwtAxis::yRight] ) + { + double y = canvasRect.right() - 1 + axisData.end; + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + y -= layoutData.canvasData.contentsMargins[ QwtAxis::yRight ]; + + sRect.setRight( y ); + } + + if ( d_alignCanvas[ axisId.pos ] ) + { + if ( axisId.pos == QwtAxis::xTop ) + sRect.setBottom( canvasRect.top() ); + else + sRect.setTop( canvasRect.bottom() ); + } + } + else + { + if ( d_alignCanvas[QwtAxis::xTop] ) + { + double x = canvasRect.top() - axisData.start; + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + x += layoutData.canvasData.contentsMargins[ QwtAxis::xTop ]; + + sRect.setTop( x ); + } + if ( d_alignCanvas[QwtAxis::xBottom] ) + { + double x = canvasRect.bottom() - 1 + axisData.end; + if ( !( options & QwtPlotLayout::IgnoreFrames ) ) + x -= layoutData.canvasData.contentsMargins[ QwtAxis::xBottom ]; + + sRect.setBottom( x ); + } + + if ( d_alignCanvas[ axisId.pos ] ) + { + if ( axisId.pos == QwtAxis::yLeft ) + sRect.setRight( canvasRect.left() ); + else + sRect.setLeft( canvasRect.right() ); + } + } + } + } +} + +class QwtPlotLayout::PrivateData +{ +public: + QRectF titleRect; + QRectF footerRect; + QRectF legendRect; + QVector scaleRects[QwtAxis::PosCount]; + QRectF canvasRect; + + QwtPlotLayoutEngine layoutEngine; +}; + +/*! + \brief Constructor + */ + +QwtPlotLayout::QwtPlotLayout() +{ + d_data = new PrivateData; + + setLegendPosition( QwtPlot::BottomLegend ); + setCanvasMargin( 4 ); + setAlignCanvasToScales( false ); + + invalidate(); +} + +//! Destructor +QwtPlotLayout::~QwtPlotLayout() +{ + delete d_data; +} + +/*! + Change a margin of the canvas. The margin is the space + above/below the scale ticks. A negative margin will + be set to -1, excluding the borders of the scales. + + \param margin New margin + \param axisPos One of QwtAxis::Position. Specifies where the position of the margin. + -1 means margin at all borders. + \sa canvasMargin() + + \warning The margin will have no effect when alignCanvasToScale() is true +*/ + +void QwtPlotLayout::setCanvasMargin( int margin, int axisPos ) +{ + if ( margin < -1 ) + margin = -1; + + if ( axisPos == -1 ) + { + for ( axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + d_data->layoutEngine.setCanvasMargin( axisPos, margin ); + } + else if ( QwtAxis::isValid( axisPos ) ) + { + d_data->layoutEngine.setCanvasMargin( axisPos, margin ); + } +} + +/*! + \param axisPos Axis position + \return Margin around the scale tick borders + \sa setCanvasMargin() +*/ +int QwtPlotLayout::canvasMargin( int axisPos ) const +{ + if ( !QwtAxis::isValid( axisPos ) ) + return 0; + + return d_data->layoutEngine.canvasMargin( axisPos ); +} + +/*! + \brief Set the align-canvas-to-axis-scales flag for all axes + + \param on True/False + \sa setAlignCanvasToScale(), alignCanvasToScale() +*/ +void QwtPlotLayout::setAlignCanvasToScales( bool on ) +{ + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + d_data->layoutEngine.setAlignCanvas( axisPos, on ); +} + +/*! + Change the align-canvas-to-axis-scales setting. The canvas may: + + - extend beyond the axis scale ends to maximize its size, + - align with the axis scale ends to control its size. + + The axisId parameter is somehow confusing as it identifies a border + of the plot and not the axes, that are aligned. F.e when QwtAxis::yLeft + is set, the left end of the the x-axes ( QwtAxis::xTop, QwtAxis::xBottom ) + is aligned. + + \param axisId Axis index + \param on New align-canvas-to-axis-scales setting + + \sa setCanvasMargin(), alignCanvasToScale(), setAlignCanvasToScales() + \warning In case of on == true canvasMargin() will have no effect +*/ +void QwtPlotLayout::setAlignCanvasToScale( int axisPos, bool on ) +{ + if ( QwtAxis::isValid( axisPos ) ) + d_data->layoutEngine.setAlignCanvas( axisPos, on ); +} + +/*! + Return the align-canvas-to-axis-scales setting. The canvas may: + - extend beyond the axis scale ends to maximize its size + - align with the axis scale ends to control its size. + + \param axisId Axis index + \return align-canvas-to-axis-scales setting + \sa setAlignCanvasToScale(), setAlignCanvasToScale(), setCanvasMargin() +*/ +bool QwtPlotLayout::alignCanvasToScale( int axisPos ) const +{ + if ( !QwtAxis::isValid( axisPos ) ) + return false; + + return d_data->layoutEngine.alignCanvas( axisPos ); +} + +/*! + Change the spacing of the plot. The spacing is the distance + between the plot components. + + \param spacing New spacing + \sa setCanvasMargin(), spacing() +*/ +void QwtPlotLayout::setSpacing( int spacing ) +{ + d_data->layoutEngine.setSpacing( qMax( 0, spacing ) ); +} + +/*! + \return Spacing + \sa margin(), setSpacing() +*/ +int QwtPlotLayout::spacing() const +{ + return d_data->layoutEngine.spacing(); +} + +/*! + \brief Specify the position of the legend + \param pos The legend's position. + \param ratio Ratio between legend and the bounding rectangle + of title, footer, canvas and axes. The legend will be shrunk + if it would need more space than the given ratio. + The ratio is limited to ]0.0 .. 1.0]. In case of <= 0.0 + it will be reset to the default ratio. + The default vertical/horizontal ratio is 0.33/0.5. + + \sa QwtPlot::setLegendPosition() +*/ + +void QwtPlotLayout::setLegendPosition( QwtPlot::LegendPosition pos, double ratio ) +{ + if ( ratio > 1.0 ) + ratio = 1.0; + + switch ( pos ) + { + case QwtPlot::TopLegend: + case QwtPlot::BottomLegend: + { + if ( ratio <= 0.0 ) + ratio = 0.33; + + d_data->layoutEngine.setLegendRatio( ratio ); + d_data->layoutEngine.setLegendPos( pos ); + break; + } + case QwtPlot::LeftLegend: + case QwtPlot::RightLegend: + { + if ( ratio <= 0.0 ) + ratio = 0.5; + + d_data->layoutEngine.setLegendRatio( ratio ); + d_data->layoutEngine.setLegendPos( pos ); + break; + } + default: + break; + } +} + +/*! + \brief Specify the position of the legend + \param pos The legend's position. Valid values are + \c QwtPlot::LeftLegend, \c QwtPlot::RightLegend, + \c QwtPlot::TopLegend, \c QwtPlot::BottomLegend. + + \sa QwtPlot::setLegendPosition() +*/ +void QwtPlotLayout::setLegendPosition( QwtPlot::LegendPosition pos ) +{ + setLegendPosition( pos, 0.0 ); +} + +/*! + \return Position of the legend + \sa setLegendPosition(), QwtPlot::setLegendPosition(), + QwtPlot::legendPosition() +*/ +QwtPlot::LegendPosition QwtPlotLayout::legendPosition() const +{ + return d_data->layoutEngine.legendPos(); +} + +/*! + Specify the relative size of the legend in the plot + \param ratio Ratio between legend and the bounding rectangle + of title, footer, canvas and axes. The legend will be shrunk + if it would need more space than the given ratio. + The ratio is limited to ]0.0 .. 1.0]. In case of <= 0.0 + it will be reset to the default ratio. + The default vertical/horizontal ratio is 0.33/0.5. +*/ +void QwtPlotLayout::setLegendRatio( double ratio ) +{ + setLegendPosition( legendPosition(), ratio ); +} + +/*! + \return The relative size of the legend in the plot. + \sa setLegendPosition() +*/ +double QwtPlotLayout::legendRatio() const +{ + return d_data->layoutEngine.legendRatio(); +} + +/*! + \brief Set the geometry for the title + + This method is intended to be used from derived layouts + overloading activate() + + \sa titleRect(), activate() + */ +void QwtPlotLayout::setTitleRect( const QRectF &rect ) +{ + d_data->titleRect = rect; +} + +/*! + \return Geometry for the title + \sa activate(), invalidate() +*/ +QRectF QwtPlotLayout::titleRect() const +{ + return d_data->titleRect; +} + +/*! + \brief Set the geometry for the footer + + This method is intended to be used from derived layouts + overloading activate() + + \sa footerRect(), activate() + */ +void QwtPlotLayout::setFooterRect( const QRectF &rect ) +{ + d_data->footerRect = rect; +} + +/*! + \return Geometry for the footer + \sa activate(), invalidate() +*/ +QRectF QwtPlotLayout::footerRect() const +{ + return d_data->footerRect; +} + +/*! + \brief Set the geometry for the legend + + This method is intended to be used from derived layouts + overloading activate() + + \param rect Rectangle for the legend + + \sa legendRect(), activate() + */ +void QwtPlotLayout::setLegendRect( const QRectF &rect ) +{ + d_data->legendRect = rect; +} + +/*! + \return Geometry for the legend + \sa activate(), invalidate() +*/ +QRectF QwtPlotLayout::legendRect() const +{ + return d_data->legendRect; +} + +/*! + \brief Set the geometry for an axis + + This method is intended to be used from derived layouts + overloading activate() + + \param axis Axis index + \param rect Rectangle for the scale + + \sa scaleRect(), activate() + */ +void QwtPlotLayout::setScaleRect( QwtAxisId axisId, const QRectF &rect ) +{ + if ( QwtAxis::isValid( axisId.pos ) ) + { + QVector &scaleRects = d_data->scaleRects[ axisId.pos ]; + + if ( axisId.id >= 0 && axisId.id < scaleRects.size() ) + scaleRects[axisId.id] = rect; + } +} + +/*! + \param axis Axis index + \return Geometry for the scale + \sa activate(), invalidate() +*/ +QRectF QwtPlotLayout::scaleRect( QwtAxisId axisId ) const +{ + if ( QwtAxis::isValid( axisId.pos ) ) + { + QVector &scaleRects = d_data->scaleRects[ axisId.pos ]; + if ( axisId.id >= 0 && axisId.id < scaleRects.size() ) + return scaleRects[axisId.id]; + } + + return QRectF(); +} + +/*! + \brief Set the geometry for the canvas + + This method is intended to be used from derived layouts + overloading activate() + + \sa canvasRect(), activate() + */ +void QwtPlotLayout::setCanvasRect( const QRectF &rect ) +{ + d_data->canvasRect = rect; +} + +/*! + \return Geometry for the canvas + \sa activate(), invalidate() +*/ +QRectF QwtPlotLayout::canvasRect() const +{ + return d_data->canvasRect; +} + +/*! + Invalidate the geometry of all components. + \sa activate() +*/ +void QwtPlotLayout::invalidate() +{ + d_data->titleRect = d_data->footerRect = + d_data->legendRect = d_data->canvasRect = QRectF(); + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + QVector &scaleRects = d_data->scaleRects[axisPos]; + + scaleRects.resize( 1 ); + scaleRects[0] = QRectF(); + } +} + +/*! + \return Minimum size hint + \param plot Plot widget + + \sa QwtPlot::minimumSizeHint() +*/ +QSize QwtPlotLayout::minimumSizeHint( const QwtPlot *plot ) const +{ + QwtPlotLayoutHintData hintData( plot ); + + /* + When having x and y axes, we try to use the empty + corners for the tick labels that are exceeding the + scale backbones. + */ + int xAxesWidth = 0; + int yAxesHeight = 0; + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + for ( int i = 0; i < plot->axesCount( axisPos ); i++ ) + { + const int sz = hintData.alignedSize( QwtAxisId( axisPos, i ) ); + + if ( QwtAxis::isXAxis( axisPos ) ) + xAxesWidth = qMax( xAxesWidth, sz ); + else + yAxesHeight = qMax( yAxesHeight, sz ); + } + } + + const QWidget *canvas = plot->canvas(); + + int left, top, right, bottom; + canvas->getContentsMargins( &left, &top, &right, &bottom ); + + const QSize minCanvasSize = canvas->minimumSize(); + + int w = hintData.yAxesWidth(); + int cw = xAxesWidth + left + 1 + right + 1; + w += qMax( cw, minCanvasSize.width() ); + + int h = hintData.xAxesHeight(); + int ch = yAxesHeight + top + 1 + bottom + 1; + h += qMax( ch, minCanvasSize.height() ); + + const QwtTextLabel *labels[2]; + labels[0] = plot->titleLabel(); + labels[1] = plot->footerLabel(); + + for ( int i = 0; i < 2; i++ ) + { + const QwtTextLabel *label = labels[i]; + if ( label && !label->text().isEmpty() ) + { + // we center on the plot canvas. + const bool centerOnCanvas = hintData.hasSymmetricYAxes(); + + int labelW = w; + if ( centerOnCanvas ) + { + labelW -= hintData.yAxesWidth(); + } + + int labelH = label->heightForWidth( labelW ); + if ( labelH > labelW ) // Compensate for a long title + { + w = labelW = labelH; + if ( centerOnCanvas ) + w += hintData.yAxesWidth(); + + labelH = label->heightForWidth( labelW ); + } + h += labelH + spacing(); + } + } + + // Compute the legend contribution + + const QwtAbstractLegend *legend = plot->legend(); + if ( legend && !legend->isEmpty() ) + { + if ( d_data->layoutEngine.legendPos() == QwtPlot::LeftLegend + || d_data->layoutEngine.legendPos() == QwtPlot::RightLegend ) + { + int legendW = legend->sizeHint().width(); + int legendH = legend->heightForWidth( legendW ); + + if ( legend->frameWidth() > 0 ) + w += spacing(); + + if ( legendH > h ) + legendW += legend->scrollExtent( Qt::Horizontal ); + + if ( d_data->layoutEngine.legendRatio() < 1.0 ) + legendW = qMin( legendW, int( w / ( 1.0 - d_data->layoutEngine.legendRatio() ) ) ); + + w += legendW + spacing(); + } + else + { + int legendW = qMin( legend->sizeHint().width(), w ); + int legendH = legend->heightForWidth( legendW ); + + if ( legend->frameWidth() > 0 ) + h += spacing(); + + if ( d_data->layoutEngine.legendRatio() < 1.0 ) + legendH = qMin( legendH, int( h / ( 1.0 - d_data->layoutEngine.legendRatio() ) ) ); + + h += legendH + spacing(); + } + } + + return QSize( w, h ); +} + +void QwtPlotLayout::update( const QwtPlot *plot, + const QRectF &plotRect, Options options ) +{ + invalidate(); + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + d_data->scaleRects[ axisPos ].resize( plot->axesCount( axisPos ) ); + + activate( plot, plotRect, options ); +} + +/*! + \brief Recalculate the geometry of all components. + + \param plot Plot to be layout + \param plotRect Rectangle where to place the components + \param options Layout options + + \sa invalidate(), titleRect(), footerRect() + legendRect(), scaleRect(), canvasRect() +*/ +void QwtPlotLayout::activate( const QwtPlot *plot, + const QRectF &plotRect, Options options ) +{ + QRectF rect( plotRect ); // undistributed rest of the plot rect + + // We extract all layout relevant parameters from the widgets, + // and save them to d_data->layoutData. + + QwtPlotLayoutData layoutData( plot ); + + QSize legendHint; + + if ( !( options & IgnoreLegend ) + && plot->legend() && !plot->legend()->isEmpty() ) + { + legendHint = layoutData.legendData.legendHint( plot->legend(), rect ); + + d_data->legendRect = d_data->layoutEngine.layoutLegend( + options, layoutData.legendData, rect, legendHint ); + + // subtract d_data->legendRect from rect + + const QRegion region( rect.toRect() ); + rect = region.subtracted( d_data->legendRect.toRect() ).boundingRect(); + + switch ( d_data->layoutEngine.legendPos() ) + { + case QwtPlot::LeftLegend: + { + rect.setLeft( rect.left() + spacing() ); + break; + } + case QwtPlot::RightLegend: + { + rect.setRight( rect.right() - spacing() ); + break; + } + case QwtPlot::TopLegend: + { + rect.setTop( rect.top() + spacing() ); + break; + } + case QwtPlot::BottomLegend: + { + rect.setBottom( rect.bottom() - spacing() ); + break; + } + } + } + + /* + +---+-----------+---+ + | Title | + +---+-----------+---+ + | | Axis | | + +---+-----------+---+ + | A | | A | + | x | Canvas | x | + | i | | i | + | s | | s | + +---+-----------+---+ + | | Axis | | + +---+-----------+---+ + | Footer | + +---+-----------+---+ + */ + + // title, footer and axes include text labels. The height of each + // label depends on its line breaks, that depend on the width + // for the label. A line break in a horizontal text will reduce + // the available width for vertical texts and vice versa. + // layoutDimensions finds the height/width for title, footer and axes + // including all line breaks. + + const QwtPlotLayoutEngine::Dimensions dimensions = + d_data->layoutEngine.layoutDimensions( options, layoutData, rect ); + + if ( dimensions.dimTitle > 0 ) + { + QRectF &labelRect = d_data->titleRect; + + labelRect.setRect( rect.left(), rect.top(), rect.width(), dimensions.dimTitle ); + + rect.setTop( labelRect.bottom() + spacing() ); + + if ( !layoutData.hasSymmetricYAxes() ) + { + // if only one of the y axes is missing we align + // the title centered to the canvas + + labelRect = dimensions.centered( rect, labelRect ); + } + } + + if ( dimensions.dimFooter > 0 ) + { + QRectF &labelRect = d_data->footerRect; + + labelRect.setRect( rect.left(), rect.bottom() - dimensions.dimFooter, + rect.width(), dimensions.dimFooter ); + + rect.setBottom( labelRect.top() - spacing() ); + + if ( !layoutData.hasSymmetricYAxes() ) + { + // if only one of the y axes is missing we align + // the footer centered to the canvas + + labelRect = dimensions.centered( rect, labelRect ); + } + } + + d_data->canvasRect = dimensions.innerRect( rect ); + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + // set the rects for the axes + + int pos = 0; + for ( int i = 0; i < d_data->scaleRects[ axisPos ].size(); i++ ) + { + const QwtAxisId axisId( axisPos, i ); + + if ( dimensions.dimAxis( axisId ) ) + { + const int dim = dimensions.dimAxis( axisId ); + + const QRectF &canvasRect = d_data->canvasRect; + + QRectF &scaleRect = d_data->scaleRects[ axisId.pos ][ axisId.id ]; + scaleRect = canvasRect; + + switch ( axisPos ) + { + case QwtAxis::yLeft: + { + scaleRect.setX( canvasRect.left() - pos - dim ); + scaleRect.setWidth( dim ); + break; + } + case QwtAxis::yRight: + { + scaleRect.setX( canvasRect.right() + pos ); + scaleRect.setWidth( dim ); + break; + } + case QwtAxis::xBottom: + { + scaleRect.setY( canvasRect.bottom() + pos ); + scaleRect.setHeight( dim ); + break; + } + case QwtAxis::xTop: + { + scaleRect.setY( canvasRect.top() - pos - dim ); + scaleRect.setHeight( dim ); + break; + } + } + scaleRect = scaleRect.normalized(); + pos += dim; + } + } + } + + // +---+-----------+---+ + // | <- Axis -> | + // +-^-+-----------+-^-+ + // | | | | | | + // | | | | + // | A | | A | + // | x | Canvas | x | + // | i | | i | + // | s | | s | + // | | | | + // | | | | | | + // +-V-+-----------+-V-+ + // | <- Axis -> | + // +---+-----------+---+ + + // The ticks of the axes - not the labels above - should + // be aligned to the canvas. So we try to use the empty + // corners to extend the axes, so that the label texts + // left/right of the min/max ticks are moved into them. + + d_data->layoutEngine.alignScales( options, layoutData, + d_data->canvasRect, d_data->scaleRects ); + + if ( !d_data->legendRect.isEmpty() ) + { + // We prefer to align the legend to the canvas - not to + // the complete plot - if possible. + + d_data->legendRect = d_data->layoutEngine.alignLegend( + legendHint, d_data->canvasRect, d_data->legendRect ); + } +} diff --git a/qwt/src/qwt_plot_layout.h b/qwt/src/qwt_plot_layout.h new file mode 100644 index 000000000..a25acb9cb --- /dev/null +++ b/qwt/src/qwt_plot_layout.h @@ -0,0 +1,113 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_LAYOUT_H +#define QWT_PLOT_LAYOUT_H + +#include "qwt_global.h" +#include "qwt_plot.h" +#include "qwt_axis_id.h" + +/*! + \brief Layout engine for QwtPlot. + + It is used by the QwtPlot widget to organize its internal widgets + or by QwtPlot::print() to render its content to a QPaintDevice like + a QPrinter, QPixmap/QImage or QSvgRenderer. + + \sa QwtPlot::setPlotLayout() +*/ + +class QWT_EXPORT QwtPlotLayout +{ +public: + /*! + Options to configure the plot layout engine + \sa update(), QwtPlotRenderer + */ + enum Option + { + //! Unused + AlignScales = 0x01, + + /*! + Ignore the dimension of the scrollbars. There are no + scrollbars, when the plot is not rendered to widgets. + */ + IgnoreScrollbars = 0x02, + + //! Ignore all frames. + IgnoreFrames = 0x04, + + //! Ignore the legend. + IgnoreLegend = 0x08, + + //! Ignore the title. + IgnoreTitle = 0x10, + + //! Ignore the footer. + IgnoreFooter = 0x20 + }; + + //! Layout options + typedef QFlags

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 literal 0 HcmV?d00001 diff --git a/qwt/doc/install.dox b/qwt/doc/install.dox new file mode 100644 index 000000000..774e4bad7 --- /dev/null +++ b/qwt/doc/install.dox @@ -0,0 +1,305 @@ +/*! +\page qwtinstall Installing Qwt + +\tableofcontents + +\section DOWNLOAD Download + + Stable Qwt releases are available from the + Qwt project page. + + Qwt-$(QWTVERSION) consists of 4 files: + + - qwt-$(QWTVERSION).zip\n + Zip file with the Qwt sources and the html documentation for Windows + + - qwt-$(QWTVERSION).tar.bz2\n + Compressed tar file with the Qwt sources and the html documentation + for UNIX systems ( Linux, Mac, ... ) + + - qwt-$(QWTVERSION).pdf\n + Qwt documentation as PDF document. + + - qwt-$(QWTVERSION).qch\n + Qwt documentation as Qt Compressed Help document, that can be loaded into + the Qt Assistant or Creator. In the Qt Creator context sensitive help will be + available like for Qt classes. + + Precompiled Qwt Designer plugins, that are compatible with some binary packages + of the Qt Creator: + + - qwtdesigner-$(QWTVERSION)-*.zip + + +\section INSTALL Installing Qwt + + Beside headers, libraries and the html version of the class documentation a proper + Qwt installation contains a Designer plugin and a Qwt features file for building + applications using Qwt. + + All files will be copied to an installation directory, that is configurable + by editing qwtconfig.pri. Its default settings is: + + - Windows\n + C:\\Qwt-$(QWTVERSION) + + - Unix like systems\n + /usr/local/qwt-$(QWTVERSION) + + For the rest of the document this install path will be written as ${QWT_ROOT} + and needs to be replaced by the real path in all commands below. + + It is not unlikely, to have more than one installation of Qwt + on the same system. F.e for using the Qwt Designer plugin in the Qt Creator + a version of Qwt is necessary with the same Qt and compiler combination, that had + been used for building the Qt Creator ( see "Help->About Qt Creator ..." ). + + Installing Qwt is done in 3 steps, that are quite common on UNIX systems. + + -# Configuration\n + In the configuration step all parameters are set to control how + to build and install Qwt + -# Build\n + In the build step binaries are built from the source files. + -# Installation\n + The installation copies and rearranges all files that are necessary to build + Qwt applications to a target directory. + + The installation doesn't modify the system beside copying files to a + directory in a proper way. After removing build and installation directories the + system is in the same state as it was before. + +\subsection CONFIGSUBSECTION Configuration + + Configuring Qwt has to be done by editing the Project files used for building: + + - qwtbuild.pri\n + qwtbuild.pri contains settings for how to build Qwt. All settings + of this file are only for building Qwt itself and doesn't have an impact + on how an application using Qwt is built. Usually its default settings + doesn't need to be modified. + + - qwtconfig.pri\n + qwtconfig.pri defines what modules of Qwt will be built and where to + install them. qwtconfig.pri gets installed together with the Qwt features + file qwt.prf and all its settings are known to project files for building + Qwt applications. + + In qwtconfig.pri the meaning of each option is explained in detail - it's worth + reading it before running into problems later. + +\subsection BUILDSUBSECTION Build and installation + + The Qt Creator is a graphical frontend for calling qmake/make and - technically - + it could be used for building and installing Qwt. But as this way requires a lot + more understanding of details the following step by step instructions are for + the easier way using the command line. + +\subsubsection qwtinstall-unix Unix-like systems + + The first step before creating the Makefile is to check that the correct version + of qmake is used. F.e. on older Linux distribution you often find a Qt3 qmake + and in the path. + + The default setting of qmake is to generate a makefile that builds Qwt for the + same environment where the version of qmake has been built for. + So creating a makefile usually means something like: + +\code + cd qwt-$(QWTVERSION) + /usr/local/Qt-5.0.1/bin/qmake qwt.pro +\endcode + + The generated Makefile includes all paths related to the chosen Qt version + and the next step is: + +\code + make +\endcode + ( On multicore systems you can speed up building the Qwt libraries with running several + jobs simultaneously: f.e. "make -j4" on a dual core. ) + + + Finally you have to install everything below the directories you have specified + in qwtconfig.pri. Usually this is one of the system directories ( /usr/local, /opt, ... ) + where you don't have write permission and then the installation + needs to be done as root: + +\code + sudo make install +\endcode + ( On systems where sudo is not supported you can do the same with: su -c "make install" ) + +\subsubsection qwtinstall-windows Windows + + Qt packages offer a command line interface, that can be found in the Qt application + menu: f.e "All Programs -> Qt -> Command Prompt". It is not mandatory to use it, but + probably the easiest way as it offers an environment, where everything is + initialized for a version of Qt ( f.e qmake is in the PATH ). + + Creating a makefile usually means something like: + +\code + cd qwt-$(QWTVERSION) + qmake qwt.pro +\endcode + + The generated makefile includes all paths related to the chosen Qt version. + +\paragraph qwtinstall-windows-mingw MinGW + + For MinGW builds the name of the make tool is "mingw32-make" + +\code + mingw32-make +\endcode + ( On multicore systems you can speed up building the Qwt libraries with running several + jobs simultaneously: "mingw32-make -j" ) + + Finally you have to install everything below the directories you have specified + in qwtconfig.pri. + +\code + mingw32-make install +\endcode + + +\paragraph qwtinstall-windows-msvc MSVC + + For MSVC builds the name of the make tool is "nmake". Alternatively + it is possible to use "jom" ( http://qt-project.org/wiki/jom ), + that is usually included in a Qt Creator package. + +\code + nmake +\endcode + + Finally you have to install everything below the directories you have specified + in qwtconfig.pri. + +\code + nmake install +\endcode + + +\section INTEGRATION Qwt and the Qt tool chain + +\subsection USEPLUGIN Designer plugin + + The Designer plugin and the corresponding Qwt library ( if the plugin has not + been built self containing ) have to be compatible with Qt version of the application + loading it ( usually the Qt Creator ) - what is often a different version of the + Qt libraries you want to build your application with. F.e on Windows the Qt Creator + is usually built with a MSVC compiler - even if included in a MinGW package ! + + To help Qt Designer/Creator with locating the Qwt Designer plugin + you have to set the environment variable QT_PLUGIN_PATH, modify qt.conf - + or install the plugin to one of the application default paths. + + The Qt documentation explains all options in detail: + + - http://qt-project.org/doc/qt-5.0/qtdoc/deployment-plugins.html + - http://qt-project.org/doc/qtcreator-2.7/adding-plugins.html. + + F.e. on a Linux system you could add the following lines to .bashrc: + +\code + QT_PLUGIN_PATH="${QWT_ROOT}/plugins:$QT_PLUGIN_PATH" + export QT_PLUGIN_PATH +\endcode + + When the plugin has not been built including the Qwt library + ( see QwtDesignerSelfContained in qwtconfig.pri ) + the Qt Designer/Creator also needs to locate the Qwt libraries. On Unix systems the + path to the installed library is compiled into the plugin ( see rpath, ldd ), but on + Windows the Qt Creator needs to be configured ( ( \ref RUNAPP ) in the same way as for + any application using Qwt. + + In case of problems the diagnostics of Qt Creator and Designer are very limited + ( usually none ), but setting the environment variable QT_DEBUG_PLUGINS might help. + In the Qt Creator it is possible to check which plugins were loaded + successfully and for certain problems it also lists those that were recognized + but failed ( Tools > Form Editor > About Qt Designer Plugins ). + +\subsection USEHELP Online Help + + The Qwt class documentation can be loaded into the Qt Creator: + + - open the settings dialog from the Tools->Options menu + - raise the tab "Help->Documentation". + - press the Add button and select qwt-$(QWTVERSION).qch. + + Now the context sensitive help ( F1 ) works for Qwt classes. + + For browsing the documentation in the Qt Assistant: + + - open the settings dialog from the Edit->Preferences menu + - raise the tab Documentation. + - press the Add button and select qwt-$(QWTVERSION).qch. + +\section COMPILEANDLINKAPP Building a Qwt application + +All flags and settings that are necessary to compile and link an application using Qwt +can be found in the file ${QWT_ROOT}/features/qwt.prf. + +When using qmake it can included from the application project file in 2 different ways: + + - Adding Qwt as qmake feature\n\n + When using the qmake feature mechanism you can bind a special version + of qmake to a special installation of Qwt without having to add + this dependency to the application project. + How to add Qwt as feature is documented in the + qmake docs. + + After adding Qwt as a feature f.e on Linux as a persistent property .... +@code + qmake -set QMAKEFEATURES ${QWT_ROOT}/features +@endcode + .. the following line can be added to the application project file: +\code + CONFIG += qwt +\endcode + + - Including qwt.prf in the application project file\n\n + Instead of using qwt.prf as qmake feature it can be included from + the application project file:\n\n +\code +include ( ${QWT_ROOT}/features/qwt.prf ) +\endcode \n + The advantage of using a direct include is, that all settings of qwt.prf + are known to the application project file ( qmake features are included after the + application project file has been parsed ) and it can be implemented depending on - + f.e. settings made in qwtconfig.pri. + +On Unix platforms it is possible to link a runtime path into the executable, so that the +location of the Qwt libraries can be found without having to configure a runtime environment: + - QMAKE_LFLAGS_RPATH + - QMAKE_RPATH + - QMAKE_RPATHDIR + +\section RUNAPP Running a Qwt application + + When using Qwt as shared library ( DLL ) the + dynamic linker has to find + it according to the rules of the operating system. + +\subsection RUNWINDOWS Windows + +The only reasonable way to configure the runtime environment - without having to copy the +Qwt libraries around - is to modify the PATH variable. F.e. this could be done by adding +the following line to some batch file: + +\code +set PATH=%PATH%;${QWT_ROOT}\lib +\endcode + +\subsection RUNLINUX GNU/Linux + + Read the documentation about: + + - ldconfig + - /etc/ld.so.conf + - LD_LIBRARY_PATH + + Using the ldd command a configuration can be tested. +*/ diff --git a/qwt/doc/qwt.dox b/qwt/doc/qwt.dox new file mode 100644 index 000000000..0d6dbc23f --- /dev/null +++ b/qwt/doc/qwt.dox @@ -0,0 +1,153 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +/* + This file contains NO source code, just some documentation for doxygen to + parse. +*/ + +/*! + \mainpage Qwt - Qt Widgets for Technical Applications + +The Qwt library contains GUI Components and utility classes which are primarily +useful for programs with a technical background. Beside a framework for 2D plots +it provides scales, sliders, dials, compasses, thermometers, wheels and knobs +to control or display values, arrays, or ranges of type double. + + \image html plot.png + + \if homepage + \section homepage Project page + The official project page is hosted at + sourceforge + \endif + + \section license License + + Qwt is distributed under the terms of the \ref qwtlicense. + + \section platforms Platforms + + Qwt 6.1 might be usable in all environments where you find + Qt. + It is compatible with Qt4 ( >= 4.4 ) and Qt5. + + \section changelogonmainpage What's new + Read the \ref qwtchangelog "summary" of the most important changes. + + \section screenshotsonmainpage Screenshots + - \ref curvescreenshots\n + - \ref scatterscreenshots\n + - \ref spectrogramscreenshots\n + - \ref histogramscreenshots\n + - \ref controlscreenshots\n + + \latexonly Screenshots are only available in the HTML docs.\endlatexonly + + \section downloads Downloads + Stable releases or prereleases are available at the Qwt project page. + + For getting a snapshot with all bugfixes for the latest 5.2 release: + \code svn checkout svn://svn.code.sf.net/p/qwt/code/branches/qwt-5.2 \endcode + + For getting a snapshot with all bugfixes for the latest 6.1 release: + \code svn checkout svn://svn.code.sf.net/p/qwt/code/branches/qwt-6.1 \endcode + + For getting a development snapshot from the SVN repository: + \code svn checkout svn://svn.code.sf.net/p/qwt/code/trunk/qwt \endcode + + \section installonmainpage Installation + + Qwt doesn't distribute binary packages, but today all major Linux distributors + offer one. Note, that these packages often don't include the examples. + + When no binary packages are available ( f.e. on Windows ) Qwt needs to be + \ref qwtinstall "compiled and installed" on the target system. + + \section support Support + - Mailing list\n + For all kind of Qwt related questions use the Qwt mailing list.\n + If you prefer newsgroups use the mail to news gateway of Gmane. + + - Forum\n + Qt Centre is a great resource for Qt + related questions. It has a sub forum, that is dedicated to + Qwt related questions. + + - Individual support\n + If you are looking for individual support, or need someone who implements + your Qwt component/application contact support@qwt-project.org. Sending + requests to this address without a good reason for not using public + support channels might be silently ignored. + + \section relatedprojects Related Projects + + QwtPolar, a polar plot widget.\n + QwtPlot3D, an OpenGL 3D plot widget.\n + + \section donations Donations + + Sourceforge offers a Donation System via PayPal. + You can use it, if you like to support the development of Qwt. + + \section credits Credits: + \par Authors: + Uwe Rathmann, Josef Wilgen ( <= Qwt 0.2 ) + \par Project admin: + Uwe Rathmann \ +*/ + +/*! + \page qwtlicense Qwt License, Version 1.0 + \include "COPYING" +*/ + +/*! + \page curvescreenshots Curve Plots + \image html plot.png + + \image html sinus.png + + \image html cpuplot.png + + \image html graph.png + + \image html curves.png +*/ + +/*! + \page scatterscreenshots Scatter Plot + \image html scatterplot.png +*/ + +/*! + \page spectrogramscreenshots Spectrogram, Contour Plot + \image html spectrogram1.png + + \image html spectrogram2.png + + \image html spectrogram3.png + +/*! + \page histogramscreenshots Histogram + \image html histogram.png +*/ + +/*! + \page controlscreenshots Dials, Compasses, Knobs, Wheels, Sliders, Thermos + \image html radio.png + + \image html sliders.png + + \image html dials1.png + + \image html dials2.png + + \image html sysinfo.png +*/ diff --git a/qwt/doc/tex/plotlayout.tex b/qwt/doc/tex/plotlayout.tex new file mode 100644 index 000000000..e875dd9f4 --- /dev/null +++ b/qwt/doc/tex/plotlayout.tex @@ -0,0 +1,155 @@ +\begin{tikzpicture}{0pt}{0pt}{500pt}{400pt} + \clip(0pt,400pt) -- (425.118pt,400pt) -- (425.118pt,63.8605pt) -- (0pt,63.8605pt) -- (0pt,400pt); + \color[rgb]{0.501961,0.501961,0.501961} + \fill(0pt,400pt) -- (424.267pt,400pt) -- (424.267pt,64.7008pt) -- (0pt,64.7008pt) -- (0pt,400pt); + \color[rgb]{0.752941,0.752941,0.752941} + \fill(130.086pt,326.89pt) -- (353.698pt,326.89pt) -- (353.698pt,114.281pt) -- (130.086pt,114.281pt) -- (130.086pt,326.89pt); + \color[rgb]{0,0,0} + \pgftext[center, base, at={\pgfpoint{242.317pt}{215.964pt}}]{\fontsize{14}{0}\selectfont{Canvas}} + \color[rgb]{1,1,1} + \fill(68.0188pt,391.597pt) -- (415.765pt,391.597pt) -- (415.765pt,373.109pt) -- (68.0188pt,373.109pt) -- (68.0188pt,391.597pt); + \color[rgb]{0,0,0} + \pgftext[center, base, at={\pgfpoint{242.317pt}{377.311pt}}]{\fontsize{14}{0}\selectfont{\textbf{Title}}} + \color[rgb]{1,1,1} + \fill(8.50235pt,326.89pt) -- (62.9174pt,326.89pt) -- (62.9174pt,114.281pt) -- (8.50235pt,114.281pt) -- (8.50235pt,326.89pt); +\begin{scope} + \clip(8.50235pt,326.89pt) -- (63.7676pt,326.89pt) -- (63.7676pt,305.881pt) -- (8.50235pt,305.881pt) -- (8.50235pt,326.89pt); + \color[rgb]{0,0,0} + \fill(10.2028pt,319.747pt) -- (17.0047pt,319.747pt) -- (17.0047pt,313.024pt) -- (10.2028pt,313.024pt) -- (10.2028pt,319.747pt); + \color[rgb]{0,0,0} + \pgftext[center, base, at={\pgfpoint{39.5359pt}{311.763pt}}]{\fontsize{14}{0}\selectfont{Item 1}} +\end{scope} +\begin{scope} + \clip(8.50235pt,300.839pt) -- (63.7676pt,300.839pt) -- (63.7676pt,279.83pt) -- (8.50235pt,279.83pt) -- (8.50235pt,300.839pt); + \color[rgb]{0,0,0} + \fill(10.2028pt,293.696pt) -- (17.0047pt,293.696pt) -- (17.0047pt,286.973pt) -- (10.2028pt,286.973pt) -- (10.2028pt,293.696pt); + \color[rgb]{0,0,0} + \pgftext[center, base, at={\pgfpoint{39.5359pt}{285.713pt}}]{\fontsize{14}{0}\selectfont{Item 2}} +\end{scope} +\begin{scope} + \color[rgb]{1,1,1} + \fill(68.0188pt,328.57pt) -- (129.236pt,328.57pt) -- (129.236pt,113.441pt) -- (68.0188pt,113.441pt) -- (68.0188pt,328.57pt); + \color[rgb]{0,0,0} + \pgftext[center, base, at={\pgfpoint{80.7724pt}{220.586pt}},rotate=90]{\fontsize{12}{0}\selectfont{\textbf{Axis: yLeft}}} + \pgftext[center, base, at={\pgfpoint{101.603pt}{113.861pt}}]{\fontsize{10}{0}\selectfont{-1,000}} + \pgftext[center, base, at={\pgfpoint{105.854pt}{165.333pt}}]{\fontsize{10}{0}\selectfont{-500}} + \pgftext[center, base, at={\pgfpoint{113.506pt}{216.804pt}}]{\fontsize{10}{0}\selectfont{0}} + \pgftext[center, base, at={\pgfpoint{107.555pt}{268.275pt}}]{\fontsize{10}{0}\selectfont{500}} + \pgftext[center, base, at={\pgfpoint{103.304pt}{319.747pt}}]{\fontsize{10}{0}\selectfont{1,000}} + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,127.727pt) -- (124.134pt,127.727pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,137.811pt) -- (124.134pt,137.811pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,148.736pt) -- (124.134pt,148.736pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,158.82pt) -- (124.134pt,158.82pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,178.988pt) -- (124.134pt,178.988pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,189.913pt) -- (124.134pt,189.913pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,199.997pt) -- (124.134pt,199.997pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,210.081pt) -- (124.134pt,210.081pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,231.09pt) -- (124.134pt,231.09pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,241.174pt) -- (124.134pt,241.174pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,251.258pt) -- (124.134pt,251.258pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,261.342pt) -- (124.134pt,261.342pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,282.351pt) -- (124.134pt,282.351pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,292.435pt) -- (124.134pt,292.435pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,302.52pt) -- (124.134pt,302.52pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,313.444pt) -- (124.134pt,313.444pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,117.643pt) -- (120.733pt,117.643pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,168.904pt) -- (120.733pt,168.904pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,220.165pt) -- (120.733pt,220.165pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,272.267pt) -- (120.733pt,272.267pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,323.528pt) -- (120.733pt,323.528pt); + \draw[line width=0pt, line join=bevel, line cap=rect](127.535pt,323.528pt) -- (127.535pt,117.643pt); + \color[rgb]{1,1,1} + \fill(354.548pt,328.57pt) -- (415.765pt,328.57pt) -- (415.765pt,113.441pt) -- (354.548pt,113.441pt) -- (354.548pt,328.57pt); + \color[rgb]{0,0,0} + \pgftext[center, base, at={\pgfpoint{403.862pt}{220.586pt}},rotate=-90]{\fontsize{12}{0}\selectfont{\textbf{Axis: yRight}}} + \pgftext[center, base, at={\pgfpoint{382.181pt}{113.861pt}}]{\fontsize{10}{0}\selectfont{-1,000}} + \pgftext[center, base, at={\pgfpoint{377.93pt}{165.333pt}}]{\fontsize{10}{0}\selectfont{-500}} + \pgftext[center, base, at={\pgfpoint{370.277pt}{216.804pt}}]{\fontsize{10}{0}\selectfont{0}} + \pgftext[center, base, at={\pgfpoint{376.229pt}{268.275pt}}]{\fontsize{10}{0}\selectfont{500}} + \pgftext[center, base, at={\pgfpoint{380.48pt}{319.747pt}}]{\fontsize{10}{0}\selectfont{1,000}} + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,127.727pt) -- (359.65pt,127.727pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,137.811pt) -- (359.65pt,137.811pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,148.736pt) -- (359.65pt,148.736pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,158.82pt) -- (359.65pt,158.82pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,178.988pt) -- (359.65pt,178.988pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,189.913pt) -- (359.65pt,189.913pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,199.997pt) -- (359.65pt,199.997pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,210.081pt) -- (359.65pt,210.081pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,231.09pt) -- (359.65pt,231.09pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,241.174pt) -- (359.65pt,241.174pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,251.258pt) -- (359.65pt,251.258pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,261.342pt) -- (359.65pt,261.342pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,282.351pt) -- (359.65pt,282.351pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,292.435pt) -- (359.65pt,292.435pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,302.52pt) -- (359.65pt,302.52pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,313.444pt) -- (359.65pt,313.444pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,117.643pt) -- (363.05pt,117.643pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,168.904pt) -- (363.05pt,168.904pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,220.165pt) -- (363.05pt,220.165pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,272.267pt) -- (363.05pt,272.267pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,323.528pt) -- (363.05pt,323.528pt); + \draw[line width=0pt, line join=bevel, line cap=rect](356.249pt,323.528pt) -- (356.249pt,117.643pt); + \color[rgb]{1,1,1} + \fill(118.183pt,113.441pt) -- (363.05pt,113.441pt) -- (363.05pt,73.1043pt) -- (118.183pt,73.1043pt) -- (118.183pt,113.441pt); + \color[rgb]{0,0,0} + \pgftext[center, base, at={\pgfpoint{241.042pt}{76.4657pt}}]{\fontsize{12}{0}\selectfont{\textbf{Axis: xBottom}}} + \pgftext[center, base, at={\pgfpoint{133.487pt}{92.4323pt}}]{\fontsize{10}{0}\selectfont{-1,000}} + \pgftext[center, base, at={\pgfpoint{187.689pt}{92.4323pt}}]{\fontsize{10}{0}\selectfont{-500}} + \pgftext[center, base, at={\pgfpoint{241.892pt}{92.4323pt}}]{\fontsize{10}{0}\selectfont{0}} + \pgftext[center, base, at={\pgfpoint{296.094pt}{92.4323pt}}]{\fontsize{10}{0}\selectfont{500}} + \pgftext[center, base, at={\pgfpoint{350.297pt}{92.4323pt}}]{\fontsize{10}{0}\selectfont{1,000}} + \draw[line width=0pt, line join=bevel, line cap=rect](144.54pt,111.76pt) -- (144.54pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](155.593pt,111.76pt) -- (155.593pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](165.796pt,111.76pt) -- (165.796pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](176.849pt,111.76pt) -- (176.849pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](198.955pt,111.76pt) -- (198.955pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](209.158pt,111.76pt) -- (209.158pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](220.211pt,111.76pt) -- (220.211pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](231.264pt,111.76pt) -- (231.264pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](252.52pt,111.76pt) -- (252.52pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](263.573pt,111.76pt) -- (263.573pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](274.626pt,111.76pt) -- (274.626pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](285.679pt,111.76pt) -- (285.679pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](306.935pt,111.76pt) -- (306.935pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](317.988pt,111.76pt) -- (317.988pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](329.041pt,111.76pt) -- (329.041pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](339.244pt,111.76pt) -- (339.244pt,108.399pt); + \draw[line width=0pt, line join=bevel, line cap=rect](133.487pt,111.76pt) -- (133.487pt,105.038pt); + \draw[line width=0pt, line join=bevel, line cap=rect](187.902pt,111.76pt) -- (187.902pt,105.038pt); + \draw[line width=0pt, line join=bevel, line cap=rect](242.317pt,111.76pt) -- (242.317pt,105.038pt); + \draw[line width=0pt, line join=bevel, line cap=rect](295.882pt,111.76pt) -- (295.882pt,105.038pt); + \draw[line width=0pt, line join=bevel, line cap=rect](350.297pt,111.76pt) -- (350.297pt,105.038pt); + \draw[line width=0pt, line join=bevel, line cap=rect](133.487pt,111.76pt) -- (350.297pt,111.76pt); + \color[rgb]{1,1,1} + \fill(118.183pt,368.067pt) -- (363.05pt,368.067pt) -- (363.05pt,327.73pt) -- (118.183pt,327.73pt) -- (118.183pt,368.067pt); + \color[rgb]{0,0,0} + \pgftext[center, base, at={\pgfpoint{241.042pt}{355.462pt}}]{\fontsize{12}{0}\selectfont{\textbf{Axis: xTop}}} + \pgftext[center, base, at={\pgfpoint{133.487pt}{341.176pt}}]{\fontsize{10}{0}\selectfont{-1,000}} + \pgftext[center, base, at={\pgfpoint{187.689pt}{341.176pt}}]{\fontsize{10}{0}\selectfont{-500}} + \pgftext[center, base, at={\pgfpoint{241.892pt}{341.176pt}}]{\fontsize{10}{0}\selectfont{0}} + \pgftext[center, base, at={\pgfpoint{296.094pt}{341.176pt}}]{\fontsize{10}{0}\selectfont{500}} + \pgftext[center, base, at={\pgfpoint{350.297pt}{341.176pt}}]{\fontsize{10}{0}\selectfont{1,000}} + \draw[line width=0pt, line join=bevel, line cap=rect](144.54pt,329.411pt) -- (144.54pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](155.593pt,329.411pt) -- (155.593pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](165.796pt,329.411pt) -- (165.796pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](176.849pt,329.411pt) -- (176.849pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](198.955pt,329.411pt) -- (198.955pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](209.158pt,329.411pt) -- (209.158pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](220.211pt,329.411pt) -- (220.211pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](231.264pt,329.411pt) -- (231.264pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](252.52pt,329.411pt) -- (252.52pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](263.573pt,329.411pt) -- (263.573pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](274.626pt,329.411pt) -- (274.626pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](285.679pt,329.411pt) -- (285.679pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](306.935pt,329.411pt) -- (306.935pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](317.988pt,329.411pt) -- (317.988pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](329.041pt,329.411pt) -- (329.041pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](339.244pt,329.411pt) -- (339.244pt,332.772pt); + \draw[line width=0pt, line join=bevel, line cap=rect](133.487pt,329.411pt) -- (133.487pt,336.133pt); + \draw[line width=0pt, line join=bevel, line cap=rect](187.902pt,329.411pt) -- (187.902pt,336.133pt); + \draw[line width=0pt, line join=bevel, line cap=rect](242.317pt,329.411pt) -- (242.317pt,336.133pt); + \draw[line width=0pt, line join=bevel, line cap=rect](295.882pt,329.411pt) -- (295.882pt,336.133pt); + \draw[line width=0pt, line join=bevel, line cap=rect](350.297pt,329.411pt) -- (350.297pt,336.133pt); + \draw[line width=0pt, line join=bevel, line cap=rect](133.487pt,329.411pt) -- (350.297pt,329.411pt); +\end{scope} +\end{tikzpicture} diff --git a/qwt/doc/tex/qwtplot.tex b/qwt/doc/tex/qwtplot.tex new file mode 100644 index 000000000..743331692 --- /dev/null +++ b/qwt/doc/tex/qwtplot.tex @@ -0,0 +1,234 @@ +\documentclass[12pt,a4paper]{book} +\usepackage[latin1]{inputenc} +\usepackage{amsmath} +\usepackage{amsfonts} +\usepackage{amssymb} +\usepackage{makeidx} +\usepackage{pdfpages} +\usepackage{tikz} +%\usepackage{fullpage} +\usepackage[left=1cm,right=2cm,top=2cm,footskip=1.5cm,bottom=1cm]{geometry} +\usepackage{listings} +\usepackage{color} + +%\setlength{\parskip}{\baselineskip} +\setlength{\parindent}{0pt} + +\definecolor{lightgray}{gray}{0.97} +\lstset{frame=single} +\lstset{framesep=5pt} +\lstset{language=C++} +\lstset{keywordstyle=\color{black}\bfseries\emph} +\lstset{backgroundcolor=\color{lightgray}} + +\author{Uwe Rathmann} +\title{Qwt Plot Framework} + + +\begin{document} + +\maketitle +\pagestyle{headings} + +\tableofcontents + +\chapter{Introduction} + +Introduction bla bla + +\chapter{Plot Widget} + +\section{Composite Widget Architecture} + +QwtPlot is a composite widget, that contains a QwtPlotCanvas, +4 QwtScaleWidgets, a QwtTextWidget for the title and an optional QwtLegend. +All widgets might be hidden or visible depending on the current configuration +of the plot widget. Many attributes of a plot are in fact +attributes of the internal child widgets, +\\ +\\ +QwtPlot has its own layout engine, that is implemented as QwtPlotLayout. +The following picture shows a layout of a plot widget, where all internal widgets are visible. + +\begin{center} +\input{plotlayout} +\end{center} + +\bigskip + +\begin{center} +\input{plotlayout} +\end{center} + +The composite widget architecture\footnote{Subject of redesign} +of QwtPlot has some consequences for the programming interface: + +\begin{itemize} + +\item Attributes\\ +Most attributes of child widgets or the layout object can't be +changed using QwtPlot methods. Instead the application code has to +use a getter method for the child and access it directly. +Beside making the programming interface less obvious it has the effect, +that the plot can't be configured in the designer ( or creator ) + +\footnote{A proof of concept has been made how to embed a special +editor for QwtPlot into the designer and how to pass the edited +properties using QwtPlot::applyProperties() - but it was never implemented.}. + +\item Events\\ +Overloading event callbacks of QwtPlot has no effect when an event is posted to a child widget. When the application wants to implement a specific event handling it needs to use a technique called event filtering. + +\end{itemize} + +\begin{lstlisting} +#include +#include +#include + +class MyPlot: public QwtPlot +{ +public: + MyPlot(QWidget *parent = NULL); + + virtual bool eventFilter(QObject *obj, QEvent *event) +}; + +MyPlot::MyPlot(QWidget *parent): + QwtPlot(parent) +{ + for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) + axisWidget(axis)->setFont( QFont( ... ) ); + + canvas()->setPalette( QPalette( Qt::darkBlue ) ); + canvas()->installEventFilter(this); +} + +bool MyPlot::eventFilter(QObject *object, QEvent *event) +{ + if ( object == canvas() ) + { + ... + } + return QwtPlot::eventFilter(object, event); +} +\end{lstlisting} + + +\section{Scale Widget} +\section{Legend} + + + +\chapter{Scales, Axes and Transformations} + +\section{Scale Divisions} + +\section{Scale Maps} + +\section{Scale Engine} + +\section{Scale Renderer} + +\chapter{Navigation and Selection} + +\section{Picking} +\section{Zooming} +\section{Panning} + +\chapter{Items on the Plot Canvas} + +Decorations + items representing some sort of data. + +\section{Overview} +\section{The Grid} +\section{Axes} + +\section{Markers for Coordinates or Points} +\section{Curves displaying a series of 2D Points} + +\subsection{Symbols} +\subsection{Styles} +\subsection{Curve Fitting} + + +\section{Curves displaying a series of 3D Points} +\section{Curves displaying a series of intervals} +\section{Histograms displaying ...} + +\section{Spectrograms and other items displaying raster data} + +\section{SVG Item} + +\chapter{Exporting and Printing} + +\chapter{Text Engines} + +All Qwt widgets use QwtText objects to specify text labels. +Their format attribute specifies the text engine, that is +responsible for interpreting and rendering the string. +Today there are 3 different text engines available. +\footnote{A text engine for TeX documents would be a great +solution to display labels in mathematical notation. +There are a couple Qt/KDE applications available, +that can display TeX documents. Most of them simply write +the TeX document to a file generate an image from it using the +TeX tools and load the image then - but maybe there is +somewhere an "real" implementation inside, that +translates a TeX document to QPainter calls. } + +\begin{itemize} + +\item Plain Text\\ +QwtPlainTextEngine doesn't take care of any syntax and lays out and renders +the string using QPainter and QFontMetrics. + +\item Rich Text\\ +QwtRichTextEngine is able to display a subset of HTML 4 markup using the renderer, +that is built into the Scribe classes of Qt. It is often used because of +sub- or superscripts to display very simple mathematical expressions. + +\item Mathematical Markup Language (MathML)\\ +QwtMathMLTextEngine uses the MathML renderer from the Qt solutions +\footnote{ +Unfortunately we don't know much about the quality of the MathML renderer yet +as it is not much in use because of earlier license issues. +Today the solution package is LGPL'd like the rest of Qt and in Qwt 6 it can be +installed and used easily. +} +package. In MathML it should be able to define almost any kind of formula + +\end{itemize} + +The text engines for plain and rich texts are always available. +The MathML text engine ( like any other homebrew text engine ) has to be +registered by the application code: + +\bigskip +\begin{lstlisting} +#include + +QwtText::setTextEngine(QwtText::MathMLText, new QwtMathMLTextEngine()); +\end{lstlisting} +\bigskip + +QwtText might also have a couple of additional properties, controlling how to display a text. + +\begin{itemize} +\item Font +\item Color +\item Background color and border pen +\end{itemize} + + +\chapter{Advanced Topics} + +\section{Incremental Painting} + +\section{Building Plot Grids} + +\section{Controlling the Aspect Ratio} + +\section{Levels of Detail} + +\end{document} diff --git a/qwt/examples/animation/animation.pro b/qwt/examples/animation/animation.pro new file mode 100644 index 000000000..d8cebc69e --- /dev/null +++ b/qwt/examples/animation/animation.pro @@ -0,0 +1,19 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = animation + +HEADERS = \ + plot.h + +SOURCES = \ + plot.cpp \ + main.cpp diff --git a/qwt/examples/animation/main.cpp b/qwt/examples/animation/main.cpp new file mode 100644 index 000000000..f5a4a9b9a --- /dev/null +++ b/qwt/examples/animation/main.cpp @@ -0,0 +1,46 @@ +#include +#include "plot.h" + +#ifndef QWT_NO_OPENGL +#define USE_OPENGL 1 +#endif + +#if USE_OPENGL +#include +#include +#else +#include +#endif + +int main ( int argc, char **argv ) +{ +#if USE_OPENGL +#if QT_VERSION >= 0x040600 && QT_VERSION < 0x050000 + // on my box QPaintEngine::OpenGL2 has serious problems, f.e: + // the lines of a simple drawRect are wrong. + + QGL::setPreferredPaintEngine( QPaintEngine::OpenGL ); +#endif +#endif + + QApplication a( argc, argv ); + + Plot plot; + +#if USE_OPENGL + QwtPlotGLCanvas *canvas = new QwtPlotGLCanvas(); + canvas->setFrameStyle( QwtPlotGLCanvas::NoFrame ); +#else + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setFrameStyle( QFrame::NoFrame ); + canvas->setPaintAttribute( QwtPlotCanvas::BackingStore, false ); +#endif + + plot.setCanvas( canvas ); + plot.setCanvasBackground( QColor( 30, 30, 50 ) ); + + plot.resize( 400, 400 ); + plot.show(); + + return a.exec(); +} diff --git a/qwt/examples/animation/plot.cpp b/qwt/examples/animation/plot.cpp new file mode 100644 index 000000000..c0e7a4e59 --- /dev/null +++ b/qwt/examples/animation/plot.cpp @@ -0,0 +1,241 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "plot.h" + +class Curve: public QwtPlotCurve +{ +public: + void setTransformation( const QTransform &transform ) + { + d_transform = transform; + } + + virtual void updateSamples( double phase ) + { + setSamples( d_transform.map( points( phase ) ) ); + } + +private: + virtual QPolygonF points( double phase ) const = 0; + +private: + QTransform d_transform; +}; + +class Curve1: public Curve +{ +public: + Curve1() + { + setPen( QColor( 150, 150, 200 ), 2 ); + setStyle( QwtPlotCurve::Lines ); + + QwtSplineCurveFitter *curveFitter = new QwtSplineCurveFitter(); + curveFitter->setSplineSize( 150 ); + setCurveFitter( curveFitter ); + + setCurveAttribute( QwtPlotCurve::Fitted, true ); + + QwtSymbol *symbol = new QwtSymbol( QwtSymbol::XCross ); + symbol->setPen( Qt::yellow ); + symbol->setSize( 7 ); + + setSymbol( symbol ); + + // somewhere to the left + QTransform transform; + transform.scale( 1.5, 1.0 ); + transform.translate( 1.5, 3.0 ); + + setTransformation( transform ); + } + + virtual QPolygonF points( double phase ) const + { + QPolygonF points; + + const int numSamples = 15; + for ( int i = 0; i < numSamples; i++ ) + { + const double v = 6.28 * double( i ) / double( numSamples - 1 ); + points += QPointF( qSin( v - phase ), v ); + } + + return points; + } +}; + +class Curve2: public Curve +{ +public: + Curve2() + { + setStyle( QwtPlotCurve::Sticks ); + setPen( QColor( 200, 150, 50 ) ); + + setSymbol( new QwtSymbol( QwtSymbol::Ellipse, + QColor( Qt::gray ), QColor( Qt::yellow ), QSize( 5, 5 ) ) ); + } + +private: + virtual QPolygonF points( double phase ) const + { + QPolygonF points; + + const int numSamples = 50; + for ( int i = 0; i < numSamples; i++ ) + { + const double v = 10.0 * i / double( numSamples - 1 ); + points += QPointF( v, qCos( 3.0 * ( v + phase ) ) ); + } + + return points; + } +}; + +class Curve3: public Curve +{ +public: + Curve3() + { + setStyle( QwtPlotCurve::Lines ); + setPen( QColor( 100, 200, 150 ), 2 ); + + QwtSplineCurveFitter* curveFitter = new QwtSplineCurveFitter(); + curveFitter->setFitMode( QwtSplineCurveFitter::ParametricSpline ); + curveFitter->setSplineSize( 200 ); + setCurveFitter( curveFitter ); + + setCurveAttribute( QwtPlotCurve::Fitted, true ); + + // somewhere in the top right corner + QTransform transform; + transform.translate( 7.0, 7.5 ); + transform.scale( 2.0, 2.0 ); + + setTransformation( transform ); + } + +private: + virtual QPolygonF points( double phase ) const + { + QPolygonF points; + + const int numSamples = 9; + for ( int i = 0; i < numSamples; i++ ) + { + const double v = i * 2.0 * M_PI / ( numSamples - 1 ); + points += QPointF( qSin( v - phase ), qCos( 3.0 * ( v + phase ) ) ); + } + + return points; + } +}; + +class Curve4: public Curve +{ +public: + Curve4() + { + setStyle( QwtPlotCurve::Lines ); + setPen( Qt::red, 2 ); + + initSamples(); + + // somewhere in the center + QTransform transform; + transform.translate( 7.0, 3.0 ); + transform.scale( 1.5, 1.5 ); + + setTransformation( transform ); + } + +private: + virtual QPolygonF points( double phase ) const + { + const double speed = 0.05; + + const double s = speed * qSin( phase ); + const double c = qSqrt( 1.0 - s * s ); + + for ( int i = 0; i < d_points.size(); i++ ) + { + const QPointF p = d_points[i]; + + const double u = p.x(); + const double v = p.y(); + + d_points[i].setX( u * c - v * s ); + d_points[i].setY( v * c + u * s ); + } + + return d_points; + } + + void initSamples() + { + const int numSamples = 15; + + for ( int i = 0; i < numSamples; i++ ) + { + const double angle = i * ( 2.0 * M_PI / ( numSamples - 1 ) ); + + QPointF p( qCos( angle ), qSin( angle ) ); + if ( i % 2 ) + p *= 0.4; + + d_points += p; + } + } + +private: + mutable QPolygonF d_points; +}; + +Plot::Plot( QWidget *parent ): + QwtPlot( parent) +{ + setAutoReplot( false ); + + setTitle( "Animated Curves" ); + + // hide all axes + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + setAxisVisible( axis, false ); + + plotLayout()->setCanvasMargin( 10 ); + + d_curves[0] = new Curve1(); + d_curves[1] = new Curve2(); + d_curves[2] = new Curve3(); + d_curves[3] = new Curve4(); + + updateCurves(); + + for ( int i = 0; i < CurveCount; i++ ) + d_curves[i]->attach( this ); + + d_time.start(); + ( void )startTimer( 40 ); +} + +void Plot::timerEvent( QTimerEvent * ) +{ + updateCurves(); + replot(); +} + +void Plot::updateCurves() +{ + const double speed = 2 * M_PI / 25000.0; // a cycle every 25 seconds + + const double phase = d_time.elapsed() * speed; + for ( int i = 0; i < CurveCount; i++ ) + d_curves[i]->updateSamples( phase ); +} diff --git a/qwt/examples/animation/plot.h b/qwt/examples/animation/plot.h new file mode 100644 index 000000000..e2d51acae --- /dev/null +++ b/qwt/examples/animation/plot.h @@ -0,0 +1,21 @@ +#include +#include + +class Curve; + +class Plot: public QwtPlot +{ +public: + Plot( QWidget * = NULL); + +protected: + virtual void timerEvent( QTimerEvent * ); + +private: + void updateCurves(); + + enum { CurveCount = 4 }; + Curve *d_curves[CurveCount]; + + QTime d_time; +}; diff --git a/qwt/examples/barchart/barchart.cpp b/qwt/examples/barchart/barchart.cpp new file mode 100644 index 000000000..8b684a712 --- /dev/null +++ b/qwt/examples/barchart/barchart.cpp @@ -0,0 +1,132 @@ +#include "barchart.h" +#include +#include +#include +#include +#include +#include +#include + +BarChart::BarChart( QWidget *parent ): + QwtPlot( parent ) +{ + setAutoFillBackground( true ); + + setPalette( Qt::white ); + canvas()->setPalette( QColor( "LemonChiffon" ) ); + + setTitle( "Bar Chart" ); + + setAxisTitle( QwtAxis::yLeft, "Whatever" ); + setAxisTitle( QwtAxis::xBottom, "Whatever" ); + + d_barChartItem = new QwtPlotMultiBarChart( "Bar Chart " ); + d_barChartItem->setLayoutPolicy( QwtPlotMultiBarChart::AutoAdjustSamples ); + d_barChartItem->setSpacing( 20 ); + d_barChartItem->setMargin( 3 ); + + d_barChartItem->attach( this ); + + insertLegend( new QwtLegend() ); + + populate(); + setOrientation( 0 ); + + setAutoReplot( true ); +} + +void BarChart::populate() +{ + static const char *colors[] = { "DarkOrchid", "SteelBlue", "Gold" }; + + const int numSamples = 5; + const int numBars = sizeof( colors ) / sizeof( colors[0] ); + + QList titles; + for ( int i = 0; i < numBars; i++ ) + { + QString title("Bar %1"); + titles += title.arg( i ); + } + d_barChartItem->setBarTitles( titles ); + d_barChartItem->setLegendIconSize( QSize( 10, 14 ) ); + + for ( int i = 0; i < numBars; i++ ) + { + QwtColumnSymbol *symbol = new QwtColumnSymbol( QwtColumnSymbol::Box ); + symbol->setLineWidth( 2 ); + symbol->setFrameStyle( QwtColumnSymbol::Raised ); + symbol->setPalette( QPalette( colors[i] ) ); + + d_barChartItem->setSymbol( i, symbol ); + } + + QVector< QVector > series; + for ( int i = 0; i < numSamples; i++ ) + { + QVector values; + for ( int j = 0; j < numBars; j++ ) + values += ( 2 + qrand() % 8 ); + + series += values; + } + + d_barChartItem->setSamples( series ); +} + +void BarChart::setMode( int mode ) +{ + if ( mode == 0 ) + { + d_barChartItem->setStyle( QwtPlotMultiBarChart::Grouped ); + } + else + { + d_barChartItem->setStyle( QwtPlotMultiBarChart::Stacked ); + } +} + +void BarChart::setOrientation( int orientation ) +{ + QwtAxis::Position axis1, axis2; + + if ( orientation == 0 ) + { + axis1 = QwtAxis::xBottom; + axis2 = QwtAxis::yLeft; + + d_barChartItem->setOrientation( Qt::Vertical ); + } + else + { + axis1 = QwtAxis::yLeft; + axis2 = QwtAxis::xBottom; + + d_barChartItem->setOrientation( Qt::Horizontal ); + } + + setAxisScale( axis1, 0, d_barChartItem->dataSize() - 1, 1.0 ); + setAxisAutoScale( axis2 ); + + QwtScaleDraw *scaleDraw1 = axisScaleDraw( axis1 ); + scaleDraw1->enableComponent( QwtScaleDraw::Backbone, false ); + scaleDraw1->enableComponent( QwtScaleDraw::Ticks, false ); + + QwtScaleDraw *scaleDraw2 = axisScaleDraw( axis2 ); + scaleDraw2->enableComponent( QwtScaleDraw::Backbone, true ); + scaleDraw2->enableComponent( QwtScaleDraw::Ticks, true ); + + plotLayout()->setAlignCanvasToScale( axis1, true ); + plotLayout()->setAlignCanvasToScale( axis2, false ); + + plotLayout()->setCanvasMargin( 0 ); + updateCanvasMargins(); + + replot(); +} + +void BarChart::exportChart() +{ + QwtPlotRenderer renderer; + renderer.exportTo( this, "barchart.pdf" ); +} diff --git a/qwt/examples/barchart/barchart.h b/qwt/examples/barchart/barchart.h new file mode 100644 index 000000000..ff5afd9d8 --- /dev/null +++ b/qwt/examples/barchart/barchart.h @@ -0,0 +1,25 @@ +#ifndef _BAR_CHART_H_ + +#include + +class QwtPlotMultiBarChart; + +class BarChart: public QwtPlot +{ + Q_OBJECT + +public: + BarChart( QWidget * = NULL ); + +public Q_SLOTS: + void setMode( int ); + void setOrientation( int ); + void exportChart(); + +private: + void populate(); + + QwtPlotMultiBarChart *d_barChartItem; +}; + +#endif diff --git a/qwt/examples/barchart/barchart.pro b/qwt/examples/barchart/barchart.pro new file mode 100644 index 000000000..ff069655b --- /dev/null +++ b/qwt/examples/barchart/barchart.pro @@ -0,0 +1,19 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = barchart + +SOURCES = \ + barchart.cpp \ + main.cpp + +HEADERS = \ + barchart.h diff --git a/qwt/examples/barchart/main.cpp b/qwt/examples/barchart/main.cpp new file mode 100644 index 000000000..a47da346a --- /dev/null +++ b/qwt/examples/barchart/main.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include +#include "barchart.h" + +class MainWindow: public QMainWindow +{ +public: + MainWindow( QWidget * = NULL ); + +private: + BarChart *d_chart; +}; + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + d_chart = new BarChart( this ); + setCentralWidget( d_chart ); + + QToolBar *toolBar = new QToolBar( this ); + + QComboBox *typeBox = new QComboBox( toolBar ); + typeBox->addItem( "Grouped" ); + typeBox->addItem( "Stacked" ); + typeBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + + QComboBox *orientationBox = new QComboBox( toolBar ); + orientationBox->addItem( "Vertical" ); + orientationBox->addItem( "Horizontal" ); + orientationBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + + QToolButton *btnExport = new QToolButton( toolBar ); + btnExport->setText( "Export" ); + btnExport->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + connect( btnExport, SIGNAL( clicked() ), d_chart, SLOT( exportChart() ) ); + + toolBar->addWidget( typeBox ); + toolBar->addWidget( orientationBox ); + toolBar->addWidget( btnExport ); + addToolBar( toolBar ); + + d_chart->setMode( typeBox->currentIndex() ); + connect( typeBox, SIGNAL( currentIndexChanged( int ) ), + d_chart, SLOT( setMode( int ) ) ); + + d_chart->setOrientation( orientationBox->currentIndex() ); + connect( orientationBox, SIGNAL( currentIndexChanged( int ) ), + d_chart, SLOT( setOrientation( int ) ) ); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow mainWindow; + + mainWindow.resize( 600, 400 ); + mainWindow.show(); + + return a.exec(); +} diff --git a/qwt/examples/bode/bode.pro b/qwt/examples/bode/bode.pro new file mode 100644 index 000000000..ab42e28b1 --- /dev/null +++ b/qwt/examples/bode/bode.pro @@ -0,0 +1,23 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = bode + +HEADERS = \ + mainwindow.h \ + plot.h \ + complexnumber.h \ + pixmaps.h + +SOURCES = \ + plot.cpp \ + mainwindow.cpp \ + main.cpp diff --git a/qwt/examples/bode/complexnumber.h b/qwt/examples/bode/complexnumber.h new file mode 100644 index 000000000..62dfe1acd --- /dev/null +++ b/qwt/examples/bode/complexnumber.h @@ -0,0 +1,83 @@ +#ifndef _COMPLEX_NUMBER_H_ +#define _COMPLEX_NUMBER_H_ + +#include + +class ComplexNumber +{ +public: + ComplexNumber() ; + ComplexNumber( double r, double i = 0.0 ); + + double real() const; + double imag() const; + + friend ComplexNumber operator*( + const ComplexNumber &, const ComplexNumber & ); + + friend ComplexNumber operator+( + const ComplexNumber &, const ComplexNumber & ); + + friend ComplexNumber operator-( + const ComplexNumber &, const ComplexNumber & ); + friend ComplexNumber operator/( + const ComplexNumber &, const ComplexNumber & ); + +private: + double d_real; + double d_imag; +}; + +inline ComplexNumber::ComplexNumber(): + d_real( 0.0 ), + d_imag( -0.0 ) +{ +} + +inline ComplexNumber::ComplexNumber( double re, double im ): + d_real( re ), + d_imag( im ) +{ +} + +inline double ComplexNumber::real() const +{ + return d_real; +} + +inline double ComplexNumber::imag() const +{ + return d_imag; +} + +inline ComplexNumber operator+( + const ComplexNumber &x1, const ComplexNumber &x2 ) +{ + return ComplexNumber( x1.d_real + x2.d_real, x1.d_imag + x2.d_imag ); +} + +inline ComplexNumber operator-( + const ComplexNumber &x1, const ComplexNumber &x2 ) +{ + return ComplexNumber( x1.d_real - x2.d_real, x1.d_imag - x2.d_imag ); +} + +inline ComplexNumber operator*( + const ComplexNumber &x1, const ComplexNumber &x2 ) +{ + return ComplexNumber( x1.d_real * x2.d_real - x1.d_imag * x2.d_imag, + x1.d_real * x2.d_imag + x2.d_real * x1.d_imag ); +} + +inline ComplexNumber operator/( + const ComplexNumber &x1, const ComplexNumber &x2 ) +{ + double denom = x2.d_real * x2.d_real + x2.d_imag * x2.d_imag; + + return ComplexNumber( + ( x1.d_real * x2.d_real + x1.d_imag * x2.d_imag ) / denom, + ( x1.d_imag * x2.d_real - x2.d_imag * x1.d_real ) / denom + ); +} + +#endif diff --git a/qwt/examples/bode/main.cpp b/qwt/examples/bode/main.cpp new file mode 100644 index 000000000..30fd1028c --- /dev/null +++ b/qwt/examples/bode/main.cpp @@ -0,0 +1,13 @@ +#include +#include "mainwindow.h" + +int main ( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow w; + w.resize( 1000, 800 ); + w.show(); + + return a.exec(); +} diff --git a/qwt/examples/bode/mainwindow.cpp b/qwt/examples/bode/mainwindow.cpp new file mode 100644 index 000000000..e160478c2 --- /dev/null +++ b/qwt/examples/bode/mainwindow.cpp @@ -0,0 +1,231 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pixmaps.h" +#include "plot.h" +#include "mainwindow.h" + +class Zoomer: public QwtPlotZoomer +{ +public: + Zoomer( int xAxis, int yAxis, QWidget *canvas ): + QwtPlotZoomer( xAxis, yAxis, canvas ) + { + setTrackerMode( QwtPicker::AlwaysOff ); + setRubberBand( QwtPicker::NoRubberBand ); + + // RightButton: zoom out by 1 + // Ctrl+RightButton: zoom out to full size + + setMousePattern( QwtEventPattern::MouseSelect2, + Qt::RightButton, Qt::ControlModifier ); + setMousePattern( QwtEventPattern::MouseSelect3, + Qt::RightButton ); + } +}; + +//----------------------------------------------------------------- +// +// bode.cpp -- A demo program featuring QwtPlot and QwtCounter +// +// This example demonstrates the mapping of different curves +// to different axes in a QwtPlot widget. It also shows how to +// display the cursor position and how to implement zooming. +// +//----------------------------------------------------------------- + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + d_plot = new Plot( this ); + + const int margin = 5; + d_plot->setContentsMargins( margin, margin, margin, 0 ); + + setContextMenuPolicy( Qt::NoContextMenu ); + + d_zoomer[0] = new Zoomer( + QwtAxis::xBottom, QwtAxis::yLeft, d_plot->canvas() ); + d_zoomer[0]->setRubberBand( QwtPicker::RectRubberBand ); + d_zoomer[0]->setRubberBandPen( QColor( Qt::green ) ); + d_zoomer[0]->setTrackerMode( QwtPicker::ActiveOnly ); + d_zoomer[0]->setTrackerPen( QColor( Qt::white ) ); + + d_zoomer[1] = new Zoomer( QwtAxis::xTop, QwtAxis::yRight, + d_plot->canvas() ); + + d_panner = new QwtPlotPanner( d_plot->canvas() ); + d_panner->setMouseButton( Qt::MidButton ); + + d_picker = new QwtPlotPicker( QwtAxis::xBottom, QwtAxis::yLeft, + QwtPlotPicker::CrossRubberBand, QwtPicker::AlwaysOn, + d_plot->canvas() ); + d_picker->setStateMachine( new QwtPickerDragPointMachine() ); + d_picker->setRubberBandPen( QColor( Qt::green ) ); + d_picker->setRubberBand( QwtPicker::CrossRubberBand ); + d_picker->setTrackerPen( QColor( Qt::white ) ); + + setCentralWidget( d_plot ); + + QToolBar *toolBar = new QToolBar( this ); + + QToolButton *btnZoom = new QToolButton( toolBar ); + btnZoom->setText( "Zoom" ); + btnZoom->setIcon( QPixmap( zoom_xpm ) ); + btnZoom->setCheckable( true ); + btnZoom->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolBar->addWidget( btnZoom ); + connect( btnZoom, SIGNAL( toggled( bool ) ), SLOT( enableZoomMode( bool ) ) ); + +#ifndef QT_NO_PRINTER + QToolButton *btnPrint = new QToolButton( toolBar ); + btnPrint->setText( "Print" ); + btnPrint->setIcon( QPixmap( print_xpm ) ); + btnPrint->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolBar->addWidget( btnPrint ); + connect( btnPrint, SIGNAL( clicked() ), SLOT( print() ) ); +#endif + + QToolButton *btnExport = new QToolButton( toolBar ); + btnExport->setText( "Export" ); + btnExport->setIcon( QPixmap( print_xpm ) ); + btnExport->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolBar->addWidget( btnExport ); + connect( btnExport, SIGNAL( clicked() ), SLOT( exportDocument() ) ); + + toolBar->addSeparator(); + + QWidget *hBox = new QWidget( toolBar ); + + QHBoxLayout *layout = new QHBoxLayout( hBox ); + layout->setSpacing( 0 ); + layout->addWidget( new QWidget( hBox ), 10 ); // spacer + layout->addWidget( new QLabel( "Damping Factor", hBox ), 0 ); + layout->addSpacing( 10 ); + + QwtCounter *cntDamp = new QwtCounter( hBox ); + cntDamp->setRange( 0.0, 5.0 ); + cntDamp->setSingleStep( 0.01 ); + cntDamp->setValue( 0.0 ); + + layout->addWidget( cntDamp, 0 ); + + ( void )toolBar->addWidget( hBox ); + + addToolBar( toolBar ); +#ifndef QT_NO_STATUSBAR + ( void )statusBar(); +#endif + + enableZoomMode( false ); + showInfo(); + + connect( cntDamp, SIGNAL( valueChanged( double ) ), + d_plot, SLOT( setDamp( double ) ) ); + + connect( d_picker, SIGNAL( moved( const QPoint & ) ), + SLOT( moved( const QPoint & ) ) ); + connect( d_picker, SIGNAL( selected( const QPolygon & ) ), + SLOT( selected( const QPolygon & ) ) ); +} + +#ifndef QT_NO_PRINTER + +void MainWindow::print() +{ + QPrinter printer( QPrinter::HighResolution ); + + QString docName = d_plot->title().text(); + if ( !docName.isEmpty() ) + { + docName.replace ( QRegExp ( QString::fromLatin1 ( "\n" ) ), tr ( " -- " ) ); + printer.setDocName ( docName ); + } + + printer.setCreator( "Bode example" ); + printer.setOrientation( QPrinter::Landscape ); + + QPrintDialog dialog( &printer ); + if ( dialog.exec() ) + { + QwtPlotRenderer renderer; + + if ( printer.colorMode() == QPrinter::GrayScale ) + { + renderer.setDiscardFlag( QwtPlotRenderer::DiscardBackground ); + renderer.setDiscardFlag( QwtPlotRenderer::DiscardCanvasBackground ); + renderer.setDiscardFlag( QwtPlotRenderer::DiscardCanvasFrame ); + renderer.setLayoutFlag( QwtPlotRenderer::FrameWithScales ); + } + + renderer.renderTo( d_plot, printer ); + } +} + +#endif + +void MainWindow::exportDocument() +{ + QwtPlotRenderer renderer; + renderer.exportTo( d_plot, "bode.pdf" ); +} + +void MainWindow::enableZoomMode( bool on ) +{ + d_panner->setEnabled( on ); + + d_zoomer[0]->setEnabled( on ); + d_zoomer[0]->zoom( 0 ); + + d_zoomer[1]->setEnabled( on ); + d_zoomer[1]->zoom( 0 ); + + d_picker->setEnabled( !on ); + + showInfo(); +} + +void MainWindow::showInfo( QString text ) +{ + if ( text == QString::null ) + { + if ( d_picker->rubberBand() ) + text = "Cursor Pos: Press left mouse button in plot region"; + else + text = "Zoom: Press mouse button and drag"; + } + +#ifndef QT_NO_STATUSBAR + statusBar()->showMessage( text ); +#endif +} + +void MainWindow::moved( const QPoint &pos ) +{ + QString info; + info.sprintf( "Freq=%g, Ampl=%g, Phase=%g", + d_plot->invTransform( QwtAxis::xBottom, pos.x() ), + d_plot->invTransform( QwtAxis::yLeft, pos.y() ), + d_plot->invTransform( QwtAxis::yRight, pos.y() ) + ); + showInfo( info ); +} + +void MainWindow::selected( const QPolygon & ) +{ + showInfo(); +} diff --git a/qwt/examples/bode/mainwindow.h b/qwt/examples/bode/mainwindow.h new file mode 100644 index 000000000..1373b52cb --- /dev/null +++ b/qwt/examples/bode/mainwindow.h @@ -0,0 +1,35 @@ +#include + +class QwtPlotZoomer; +class QwtPlotPicker; +class QwtPlotPanner; +class Plot; +class QPolygon; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow( QWidget *parent = 0 ); + +private Q_SLOTS: + void moved( const QPoint & ); + void selected( const QPolygon & ); + +#ifndef QT_NO_PRINTER + void print(); +#endif + + void exportDocument(); + void enableZoomMode( bool ); + +private: + void showInfo( QString text = QString::null ); + + Plot *d_plot; + + QwtPlotZoomer *d_zoomer[2]; + QwtPlotPicker *d_picker; + QwtPlotPanner *d_panner; +}; diff --git a/qwt/examples/bode/pixmaps.h b/qwt/examples/bode/pixmaps.h new file mode 100644 index 000000000..c3bc0b9bc --- /dev/null +++ b/qwt/examples/bode/pixmaps.h @@ -0,0 +1,99 @@ +#ifndef PIXMAPS_H +#define PIXMAPS_H + +static const char *print_xpm[] = +{ + "32 32 12 1", + "a c #ffffff", + "h c #ffff00", + "c c #ffffff", + "f c #dcdcdc", + "b c #c0c0c0", + "j c #a0a0a4", + "e c #808080", + "g c #808000", + "d c #585858", + "i c #00ff00", + "# c #000000", + ". c None", + "................................", + "................................", + "...........###..................", + "..........#abb###...............", + ".........#aabbbbb###............", + ".........#ddaaabbbbb###.........", + "........#ddddddaaabbbbb###......", + ".......#deffddddddaaabbbbb###...", + "......#deaaabbbddddddaaabbbbb###", + ".....#deaaaaaaabbbddddddaaabbbb#", + "....#deaaabbbaaaa#ddedddfggaaad#", + "...#deaaaaaaaaaa#ddeeeeafgggfdd#", + "..#deaaabbbaaaa#ddeeeeabbbbgfdd#", + ".#deeefaaaaaaa#ddeeeeabbhhbbadd#", + "#aabbbeeefaaa#ddeeeeabbbbbbaddd#", + "#bbaaabbbeee#ddeeeeabbiibbadddd#", + "#bbbbbaaabbbeeeeeeabbbbbbaddddd#", + "#bjbbbbbbaaabbbbeabbbbbbadddddd#", + "#bjjjjbbbbbbaaaeabbbbbbaddddddd#", + "#bjaaajjjbbbbbbaaabbbbadddddddd#", + "#bbbbbaaajjjbbbbbbaaaaddddddddd#", + "#bjbbbbbbaaajjjbbbbbbddddddddd#.", + "#bjjjjbbbbbbaaajjjbbbdddddddd#..", + "#bjaaajjjbbbbbbjaajjbddddddd#...", + "#bbbbbaaajjjbbbjbbaabdddddd#....", + "###bbbbbbaaajjjjbbbbbddddd#.....", + "...###bbbbbbaaajbbbbbdddd#......", + "......###bbbbbbjbbbbbddd#.......", + ".........###bbbbbbbbbdd#........", + "............###bbbbbbd#.........", + "...............###bbb#..........", + "..................###..........." +}; + + +static const char *zoom_xpm[] = +{ + "32 32 8 1", + "# c #000000", + "b c #c0c0c0", + "a c #ffffff", + "e c #585858", + "d c #a0a0a4", + "c c #0000ff", + "f c #00ffff", + ". c None", + "..######################........", + ".#a#baaaaaaaaaaaaaaaaaa#........", + "#aa#baaaaaaaaaaaaaccaca#........", + "####baaaaaaaaaaaaaaaaca####.....", + "#bbbbaaaaaaaaaaaacccaaa#da#.....", + "#aaaaaaaaaaaaaaaacccaca#da#.....", + "#aaaaaaaaaaaaaaaaaccaca#da#.....", + "#aaaaaaaaaabe###ebaaaaa#da#.....", + "#aaaaaaaaa#########aaaa#da#.....", + "#aaaaaaaa###dbbbb###aaa#da#.....", + "#aaaaaaa###aaaaffb###aa#da#.....", + "#aaaaaab##aaccaaafb##ba#da#.....", + "#aaaaaae#daaccaccaad#ea#da#.....", + "#aaaaaa##aaaaaaccaab##a#da#.....", + "#aaaaaa##aacccaaaaab##a#da#.....", + "#aaaaaa##aaccccaccab##a#da#.....", + "#aaaaaae#daccccaccad#ea#da#.....", + "#aaaaaab##aacccaaaa##da#da#.....", + "#aaccacd###aaaaaaa###da#da#.....", + "#aaaaacad###daaad#####a#da#.....", + "#acccaaaad##########da##da#.....", + "#acccacaaadde###edd#eda#da#.....", + "#aaccacaaaabdddddbdd#eda#a#.....", + "#aaaaaaaaaaaaaaaaaadd#eda##.....", + "#aaaaaaaaaaaaaaaaaaadd#eda#.....", + "#aaaaaaaccacaaaaaaaaadd#eda#....", + "#aaaaaaaaaacaaaaaaaaaad##eda#...", + "#aaaaaacccaaaaaaaaaaaaa#d#eda#..", + "########################dd#eda#.", + "...#dddddddddddddddddddddd##eda#", + "...#aaaaaaaaaaaaaaaaaaaaaa#.####", + "...########################..##." +}; + +#endif diff --git a/qwt/examples/bode/plot.cpp b/qwt/examples/bode/plot.cpp new file mode 100644 index 000000000..baa9e9b11 --- /dev/null +++ b/qwt/examples/bode/plot.cpp @@ -0,0 +1,208 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "complexnumber.h" +#include "plot.h" + +#if QT_VERSION < 0x040601 +#define qExp(x) ::exp(x) +#define qAtan2(y, x) ::atan2(y, x) +#endif + +static void logSpace( double *array, int size, double xmin, double xmax ) +{ + if ( ( xmin <= 0.0 ) || ( xmax <= 0.0 ) || ( size <= 0 ) ) + return; + + const int imax = size - 1; + + array[0] = xmin; + array[imax] = xmax; + + const double lxmin = log( xmin ); + const double lxmax = log( xmax ); + const double lstep = ( lxmax - lxmin ) / double( imax ); + + for ( int i = 1; i < imax; i++ ) + array[i] = qExp( lxmin + double( i ) * lstep ); +} + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + setAutoReplot( false ); + + setTitle( "Frequency Response of a Second-Order System" ); + setFooter( "Footer Response of a Second-Order System" ); + + setCanvasBackground( QColor( "MidnightBlue" ) ); + + // legend + QwtLegend *legend = new QwtLegend; + insertLegend( legend, QwtPlot::BottomLegend ); + + // grid + QwtPlotGrid *grid = new QwtPlotGrid; + grid->enableXMin( true ); + grid->setMajorPen( Qt::white, 0, Qt::DotLine ); + grid->setMinorPen( Qt::gray, 0 , Qt::DotLine ); + grid->setAxes( QwtAxisId( QwtAxis::xTop, 2 ), QwtAxisId( QwtAxis::yRight, 1 ) ); + grid->attach( this ); + + // axes + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + setAxesCount( axisPos, 3 ); + + for ( int i = 0; i < axesCount( axisPos ); i++ ) + { + QwtAxisId axisId( axisPos, i ); + + QString title( "Normalized Frequency or any other important stuff" ); + setAxisTitle( axisId, title + QString().setNum( i ) ); + + if ( i == 1 ) + { + setAxisScale( axisId, 1000000, 500000 ); + if ( QwtAxis::isYAxis( axisPos ) ) + setAxisLabelRotation( axisId, -60 ); + } + + if ( i == 2 ) + setAxisScale( axisId, 70.457895, 0.00000555 ); + } + } + + setAxisVisible( QwtAxis::yRight ); + setAxisVisible( QwtAxis::xTop ); + setAxisMaxMajor( QwtAxis::xBottom, 6 ); + setAxisMaxMinor( QwtAxis::xBottom, 9 ); + setAxisScaleEngine( QwtAxis::xBottom, new QwtLogScaleEngine ); + + // curves + d_curve1 = new QwtPlotCurve( "Amplitude" ); + d_curve1->setRenderHint( QwtPlotItem::RenderAntialiased ); + d_curve1->setPen( Qt::yellow ); + d_curve1->setLegendAttribute( QwtPlotCurve::LegendShowLine ); + d_curve1->setYAxis( QwtAxis::yLeft ); + d_curve1->attach( this ); + + d_curve2 = new QwtPlotCurve( "Phase" ); + d_curve2->setRenderHint( QwtPlotItem::RenderAntialiased ); + d_curve2->setPen( Qt::cyan ); + d_curve2->setLegendAttribute( QwtPlotCurve::LegendShowLine ); + d_curve2->setYAxis( QwtAxis::yRight ); + d_curve2->attach( this ); + + // marker + d_marker1 = new QwtPlotMarker(); + d_marker1->setValue( 0.0, 0.0 ); + d_marker1->setLineStyle( QwtPlotMarker::VLine ); + d_marker1->setLabelAlignment( Qt::AlignRight | Qt::AlignBottom ); + d_marker1->setLinePen( Qt::green, 0, Qt::DashDotLine ); + d_marker1->attach( this ); + + d_marker2 = new QwtPlotMarker(); + d_marker2->setLineStyle( QwtPlotMarker::HLine ); + d_marker2->setLabelAlignment( Qt::AlignRight | Qt::AlignBottom ); + d_marker2->setLinePen( QColor( 200, 150, 0 ), 0, Qt::DashDotLine ); + d_marker2->setSymbol( new QwtSymbol( QwtSymbol::Diamond, + QColor( Qt::yellow ), QColor( Qt::green ), QSize( 8, 8 ) ) ); + d_marker2->attach( this ); + + setDamp( 0.0 ); + + setAutoReplot( true ); +} + +void Plot::showData( const double *frequency, const double *amplitude, + const double *phase, int count ) +{ + d_curve1->setSamples( frequency, amplitude, count ); + d_curve2->setSamples( frequency, phase, count ); +} + +void Plot::showPeak( double freq, double amplitude ) +{ + QString label; + label.sprintf( "Peak: %.3g dB", amplitude ); + + QwtText text( label ); + text.setFont( QFont( "Helvetica", 10, QFont::Bold ) ); + text.setColor( QColor( 200, 150, 0 ) ); + + d_marker2->setValue( freq, amplitude ); + d_marker2->setLabel( text ); +} + +void Plot::show3dB( double freq ) +{ + QString label; + label.sprintf( "-3 dB at f = %.3g", freq ); + + QwtText text( label ); + text.setFont( QFont( "Helvetica", 10, QFont::Bold ) ); + text.setColor( Qt::green ); + + d_marker1->setValue( freq, 0.0 ); + d_marker1->setLabel( text ); +} + +// +// re-calculate frequency response +// +void Plot::setDamp( double damping ) +{ + const bool doReplot = autoReplot(); + setAutoReplot( false ); + + const int ArraySize = 200; + + double frequency[ArraySize]; + double amplitude[ArraySize]; + double phase[ArraySize]; + + // build frequency vector with logarithmic division + logSpace( frequency, ArraySize, 0.01, 100 ); + + int i3 = 1; + double fmax = 1; + double amax = -1000.0; + + for ( int i = 0; i < ArraySize; i++ ) + { + double f = frequency[i]; + const ComplexNumber g = + ComplexNumber( 1.0 ) / ComplexNumber( 1.0 - f * f, 2.0 * damping * f ); + + amplitude[i] = 20.0 * log10( qSqrt( g.real() * g.real() + g.imag() * g.imag() ) ); + phase[i] = qAtan2( g.imag(), g.real() ) * ( 180.0 / M_PI ); + + if ( ( i3 <= 1 ) && ( amplitude[i] < -3.0 ) ) + i3 = i; + if ( amplitude[i] > amax ) + { + amax = amplitude[i]; + fmax = frequency[i]; + } + + } + + double f3 = frequency[i3] - ( frequency[i3] - frequency[i3 - 1] ) + / ( amplitude[i3] - amplitude[i3 -1] ) * ( amplitude[i3] + 3 ); + + showPeak( fmax, amax ); + show3dB( f3 ); + showData( frequency, amplitude, phase, ArraySize ); + + setAutoReplot( doReplot ); + + replot(); +} diff --git a/qwt/examples/bode/plot.h b/qwt/examples/bode/plot.h new file mode 100644 index 000000000..4529468a6 --- /dev/null +++ b/qwt/examples/bode/plot.h @@ -0,0 +1,31 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ + +#include + +class QwtPlotCurve; +class QwtPlotMarker; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget *parent ); + +public Q_SLOTS: + void setDamp( double damping ); + +private: + void showData( const double *frequency, const double *amplitude, + const double *phase, int count ); + void showPeak( double freq, double amplitude ); + void show3dB( double freq ); + + QwtPlotCurve *d_curve1; + QwtPlotCurve *d_curve2; + QwtPlotMarker *d_marker1; + QwtPlotMarker *d_marker2; +}; + +#endif diff --git a/qwt/examples/controls/controls.pro b/qwt/examples/controls/controls.pro new file mode 100644 index 000000000..0c3e5f0e1 --- /dev/null +++ b/qwt/examples/controls/controls.pro @@ -0,0 +1,34 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = controls + +HEADERS = \ + sliderbox.h \ + slidertab.h \ + wheelbox.h \ + wheeltab.h \ + knobbox.h \ + knobtab.h \ + dialbox.h \ + dialtab.h + +SOURCES = \ + sliderbox.cpp \ + slidertab.cpp \ + wheelbox.cpp \ + wheeltab.cpp \ + knobbox.cpp \ + knobtab.cpp \ + dialbox.cpp \ + dialtab.cpp \ + main.cpp + diff --git a/qwt/examples/controls/dialbox.cpp b/qwt/examples/controls/dialbox.cpp new file mode 100644 index 000000000..4365c9ae9 --- /dev/null +++ b/qwt/examples/controls/dialbox.cpp @@ -0,0 +1,163 @@ +#include +#include +#include +#include +#include +#include +#include +#include "dialbox.h" + +DialBox::DialBox( QWidget *parent, int type ): + QWidget( parent ) +{ + d_dial = createDial( type ); + + d_label = new QLabel( this ); + d_label->setAlignment( Qt::AlignCenter ); + + QVBoxLayout *layout = new QVBoxLayout( this );; + layout->setSpacing( 0 ); + layout->addWidget( d_dial, 10 ); + layout->addWidget( d_label ); + + connect( d_dial, SIGNAL( valueChanged( double ) ), + this, SLOT( setNum( double ) ) ); + + setNum( d_dial->value() ); +} + +QwtDial *DialBox::createDial( int type ) const +{ + QwtDial *dial = new QwtDial(); + dial->setTracking( true ); + dial->setFocusPolicy( Qt::StrongFocus ); + dial->setObjectName( QString( "Dial %1" ).arg( type + 1 ) ); + + QColor needleColor( Qt::red ); + + switch( type ) + { + case 0: + { + dial->setOrigin( 135.0 ); + dial->setScaleArc( 0.0, 270.0 ); + dial->setScaleMaxMinor( 4 ); + dial->setScaleMaxMajor( 10 ); + dial->setScale( -100.0, 100.0 ); + + needleColor = QColor( "Goldenrod" ); + + break; + } + case 1: + { + dial->setOrigin( 135.0 ); + dial->setScaleArc( 0.0, 270.0 ); + dial->setScaleMaxMinor( 10 ); + dial->setScaleMaxMajor( 10 ); + dial->setScale( 10.0, 0.0 ); + + QwtRoundScaleDraw *scaleDraw = new QwtRoundScaleDraw(); + scaleDraw->setSpacing( 8 ); + scaleDraw->enableComponent( + QwtAbstractScaleDraw::Backbone, false ); + scaleDraw->setTickLength( QwtScaleDiv::MinorTick, 2 ); + scaleDraw->setTickLength( QwtScaleDiv::MediumTick, 4 ); + scaleDraw->setTickLength( QwtScaleDiv::MajorTick, 8 ); + dial->setScaleDraw( scaleDraw ); + + break; + } + case 2: + { + dial->setOrigin( 150.0 ); + dial->setScaleArc( 0.0, 240.0 ); + + QwtLinearScaleEngine *scaleEngine = new QwtLinearScaleEngine( 2 ); + scaleEngine->setTransformation( new QwtPowerTransform( 2 ) ); + dial->setScaleEngine( scaleEngine ); + + QList< double > ticks[ QwtScaleDiv::NTickTypes ]; + ticks[ QwtScaleDiv::MajorTick ] << 0 << 4 + << 16 << 32 << 64 << 96 << 128; + ticks[ QwtScaleDiv::MediumTick ] << 24 << 48 << 80 << 112; + ticks[ QwtScaleDiv::MinorTick ] + << 0.5 << 1 << 2 + << 7 << 10 << 13 + << 20 << 28 + << 40 << 56 + << 72 << 88 + << 104 << 120; + + dial->setScale( QwtScaleDiv( 0, 128, ticks ) ); + break; + } + case 3: + { + dial->setOrigin( 135.0 ); + dial->setScaleArc( 0.0, 270.0 ); + dial->setScaleMaxMinor( 9 ); + dial->setScaleEngine( new QwtLogScaleEngine ); + dial->setScale( 1.0e-2, 1.0e2 ); + + break; + } + case 4: + { + dial->setOrigin( 225.0 ); + dial->setScaleArc( 0.0, 360.0 ); + dial->setScaleMaxMinor( 5 ); + dial->setScaleStepSize( 20 ); + dial->setScale( 100.0, -100.0 ); + dial->setWrapping( true ); + dial->setTotalSteps( 40 ); + dial->setMode( QwtDial::RotateScale ); + dial->setValue( 70.0 ); + + needleColor = QColor( "DarkSlateBlue" ); + + break; + } + case 5: + { + dial->setOrigin( 45.0 ); + dial->setScaleArc( 0.0, 225.0 ); + dial->setScaleMaxMinor( 5 ); + dial->setScaleMaxMajor( 10 ); + dial->setScale( 0.0, 10.0 ); + + break; + } + } + + QwtDialSimpleNeedle *needle = new QwtDialSimpleNeedle( + QwtDialSimpleNeedle::Arrow, true, needleColor, + QColor( Qt::gray ).light( 130 ) ); + dial->setNeedle( needle ); + + //const QColor base( QColor( "DimGray" ) ); + const QColor base( QColor( Qt::darkGray ).dark( 150 ) ); + + QPalette palette; + palette.setColor( QPalette::Base, base ); + palette.setColor( QPalette::Window, base.dark( 150 ) ); + palette.setColor( QPalette::Mid, base.dark( 110 ) ); + palette.setColor( QPalette::Light, base.light( 170 ) ); + palette.setColor( QPalette::Dark, base.dark( 170 ) ); + palette.setColor( QPalette::Text, base.dark( 200 ).light( 800 ) ); + palette.setColor( QPalette::WindowText, base.dark( 200 ) ); + + dial->setPalette( palette ); + dial->setLineWidth( 4 ); + dial->setFrameShadow( QwtDial::Sunken ); + + return dial; +} + +void DialBox::setNum( double v ) +{ + QString text; + text.setNum( v, 'f', 2 ); + + d_label->setText( text ); +} diff --git a/qwt/examples/controls/dialbox.h b/qwt/examples/controls/dialbox.h new file mode 100644 index 000000000..f0a8268be --- /dev/null +++ b/qwt/examples/controls/dialbox.h @@ -0,0 +1,25 @@ +#ifndef _DIAL_BOX_H_ +#define _DIAL_BOX_H_ + +#include + +class QLabel; +class QwtDial; + +class DialBox: public QWidget +{ + Q_OBJECT +public: + DialBox( QWidget *parent, int type ); + +private Q_SLOTS: + void setNum( double v ); + +private: + QwtDial *createDial( int type ) const; + + QwtDial *d_dial; + QLabel *d_label; +}; + +#endif diff --git a/qwt/examples/controls/dialtab.cpp b/qwt/examples/controls/dialtab.cpp new file mode 100644 index 000000000..a49725276 --- /dev/null +++ b/qwt/examples/controls/dialtab.cpp @@ -0,0 +1,17 @@ +#include "dialtab.h" +#include "dialbox.h" +#include + +DialTab::DialTab( QWidget *parent ): + QWidget( parent ) +{ + QGridLayout *layout = new QGridLayout( this ); + + const int numRows = 3; + for ( int i = 0; i < 2 * numRows; i++ ) + { + DialBox *dialBox = new DialBox( this, i ); + layout->addWidget( dialBox, i / numRows, i % numRows ); + } +} + diff --git a/qwt/examples/controls/dialtab.h b/qwt/examples/controls/dialtab.h new file mode 100644 index 000000000..61812d24e --- /dev/null +++ b/qwt/examples/controls/dialtab.h @@ -0,0 +1,12 @@ +#ifndef _DIAL_TAB_H +#define _DIAL_TAB_H 1 + +#include + +class DialTab: public QWidget +{ +public: + DialTab( QWidget *parent = NULL ); +}; + +#endif diff --git a/qwt/examples/controls/knobbox.cpp b/qwt/examples/controls/knobbox.cpp new file mode 100644 index 000000000..45d82eb79 --- /dev/null +++ b/qwt/examples/controls/knobbox.cpp @@ -0,0 +1,119 @@ +#include +#include +#include +#include +#include +#include "knobbox.h" + +KnobBox::KnobBox( QWidget *parent, int knobType ): + QWidget( parent ) +{ + d_knob = createKnob( knobType ); + d_knob->setKnobWidth( 100 ); + + d_label = new QLabel( this ); + d_label->setAlignment( Qt::AlignCenter ); + + QVBoxLayout *layout = new QVBoxLayout( this );; + layout->setSpacing( 0 ); + layout->addWidget( d_knob, 10 ); + layout->addWidget( d_label ); + layout->addStretch( 10 ); + + connect( d_knob, SIGNAL( valueChanged( double ) ), + this, SLOT( setNum( double ) ) ); + + setNum( d_knob->value() ); +} + +QwtKnob *KnobBox::createKnob( int knobType ) const +{ + QwtKnob *knob = new QwtKnob(); + knob->setTracking( true ); + + switch( knobType ) + { + case 0: + { + knob->setKnobStyle( QwtKnob::Sunken ); + knob->setMarkerStyle( QwtKnob::Nub ); + knob->setWrapping( true ); + knob->setNumTurns( 4 ); + knob->setScaleStepSize( 10.0 ); + knob->setScale( 0, 400 ); + knob->setTotalSteps( 400 ); + break; + } + case 1: + { + knob->setKnobStyle( QwtKnob::Sunken ); + knob->setMarkerStyle( QwtKnob::Dot ); + break; + } + case 2: + { + knob->setKnobStyle( QwtKnob::Sunken ); + knob->setMarkerStyle( QwtKnob::Tick ); + + QwtLinearScaleEngine *scaleEngine = new QwtLinearScaleEngine( 2 ); + scaleEngine->setTransformation( new QwtPowerTransform( 2 ) ); + knob->setScaleEngine( scaleEngine ); + + QList< double > ticks[ QwtScaleDiv::NTickTypes ]; + ticks[ QwtScaleDiv::MajorTick ] << 0 << 4 + << 16 << 32 << 64 << 96 << 128; + ticks[ QwtScaleDiv::MediumTick ] << 24 << 48 << 80 << 112; + ticks[ QwtScaleDiv::MinorTick ] + << 0.5 << 1 << 2 + << 7 << 10 << 13 + << 20 << 28 + << 40 << 56 + << 72 << 88 + << 104 << 120; + + knob->setScale( QwtScaleDiv( 0, 128, ticks ) ); + + knob->setTotalSteps( 100 ); + knob->setStepAlignment( false ); + knob->setSingleSteps( 1 ); + knob->setPageSteps( 5 ); + + break; + } + case 3: + { + knob->setKnobStyle( QwtKnob::Flat ); + knob->setMarkerStyle( QwtKnob::Notch ); + knob->setScaleEngine( new QwtLogScaleEngine() ); + knob->setScaleStepSize( 1.0 ); + knob->setScale( 0.1, 1000.0 ); + knob->setScaleMaxMinor( 10 ); + break; + } + case 4: + { + knob->setKnobStyle( QwtKnob::Raised ); + knob->setMarkerStyle( QwtKnob::Dot ); + knob->setWrapping( true ); + break; + } + case 5: + { + knob->setKnobStyle( QwtKnob::Styled ); + knob->setMarkerStyle( QwtKnob::Triangle ); + knob->setTotalAngle( 180.0 ); + knob->setScale( 100, -100 ); + break; + } + } + + return knob; +} + +void KnobBox::setNum( double v ) +{ + QString text; + text.setNum( v, 'f', 2 ); + + d_label->setText( text ); +} diff --git a/qwt/examples/controls/knobbox.h b/qwt/examples/controls/knobbox.h new file mode 100644 index 000000000..5b5fcef37 --- /dev/null +++ b/qwt/examples/controls/knobbox.h @@ -0,0 +1,25 @@ +#ifndef _KNOB_BOX_H_ +#define _KNOB_BOX_H_ + +#include + +class QLabel; +class QwtKnob; + +class KnobBox: public QWidget +{ + Q_OBJECT +public: + KnobBox( QWidget *parent, int knobType ); + +private Q_SLOTS: + void setNum( double v ); + +private: + QwtKnob *createKnob( int knobType ) const; + + QwtKnob *d_knob; + QLabel *d_label; +}; + +#endif diff --git a/qwt/examples/controls/knobtab.cpp b/qwt/examples/controls/knobtab.cpp new file mode 100644 index 000000000..dc3ad5d38 --- /dev/null +++ b/qwt/examples/controls/knobtab.cpp @@ -0,0 +1,17 @@ +#include "knobtab.h" +#include "knobbox.h" +#include + +KnobTab::KnobTab( QWidget *parent ): + QWidget( parent ) +{ + QGridLayout *layout = new QGridLayout( this ); + + const int numRows = 3; + for ( int i = 0; i < 2 * numRows; i++ ) + { + KnobBox *knobBox = new KnobBox( this, i ); + layout->addWidget( knobBox, i / numRows, i % numRows ); + } +} + diff --git a/qwt/examples/controls/knobtab.h b/qwt/examples/controls/knobtab.h new file mode 100644 index 000000000..731668b12 --- /dev/null +++ b/qwt/examples/controls/knobtab.h @@ -0,0 +1,12 @@ +#ifndef _KNOB_TAB_H +#define _KNOB_TAB_H 1 + +#include + +class KnobTab: public QWidget +{ +public: + KnobTab( QWidget *parent = NULL ); +}; + +#endif diff --git a/qwt/examples/controls/main.cpp b/qwt/examples/controls/main.cpp new file mode 100644 index 000000000..755633aa2 --- /dev/null +++ b/qwt/examples/controls/main.cpp @@ -0,0 +1,41 @@ +#include +#include +#include "slidertab.h" +#include "wheeltab.h" +#include "knobtab.h" +#include "dialtab.h" + + +int main ( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + QTabWidget tabWidget; + + SliderTab *sliderTab = new SliderTab(); + sliderTab->setAutoFillBackground( true ); + sliderTab->setPalette( QColor( "DimGray" ) ); + + WheelTab *wheelTab = new WheelTab(); + wheelTab->setAutoFillBackground( true ); + wheelTab->setPalette( QColor( "Silver" ) ); + + KnobTab *knobTab = new KnobTab(); + knobTab->setAutoFillBackground( true ); + knobTab->setPalette( Qt::darkGray ); + + DialTab *dialTab = new DialTab(); + dialTab->setAutoFillBackground( true ); + dialTab->setPalette( Qt::darkGray ); + + tabWidget.addTab( new SliderTab, "Slider" ); + tabWidget.addTab( new WheelTab, "Wheel/Thermo" ); + tabWidget.addTab( knobTab, "Knob" ); + tabWidget.addTab( dialTab, "Dial" ); + + tabWidget.resize( 800, 600 ); + tabWidget.show(); + + return a.exec(); +} + diff --git a/qwt/examples/controls/sliderbox.cpp b/qwt/examples/controls/sliderbox.cpp new file mode 100644 index 000000000..ac059e348 --- /dev/null +++ b/qwt/examples/controls/sliderbox.cpp @@ -0,0 +1,172 @@ +#include +#include +#include +#include +#include +#include "sliderbox.h" + +SliderBox::SliderBox( int sliderType, QWidget *parent ): + QWidget( parent ) +{ + d_slider = createSlider( sliderType ); + + QFlags alignment; + + if ( d_slider->orientation() == Qt::Horizontal ) + { + if ( d_slider->scalePosition() == QwtSlider::TrailingScale ) + alignment = Qt::AlignBottom; + else + alignment = Qt::AlignTop; + + alignment |= Qt::AlignHCenter; + } + else + { + if ( d_slider->scalePosition() == QwtSlider::TrailingScale ) + alignment = Qt::AlignRight; + else + alignment = Qt::AlignLeft; + + alignment |= Qt::AlignVCenter; + } + + d_label = new QLabel( this ); + d_label->setAlignment( alignment ); + d_label->setFixedWidth( d_label->fontMetrics().width( "10000.9" ) ); + + connect( d_slider, SIGNAL( valueChanged( double ) ), SLOT( setNum( double ) ) ); + + QBoxLayout *layout; + if ( d_slider->orientation() == Qt::Horizontal ) + layout = new QHBoxLayout( this ); + else + layout = new QVBoxLayout( this ); + + layout->addWidget( d_slider ); + layout->addWidget( d_label ); + + setNum( d_slider->value() ); +} + +QwtSlider *SliderBox::createSlider( int sliderType ) const +{ + QwtSlider *slider = new QwtSlider(); + + switch( sliderType ) + { + case 0: + { + slider->setOrientation( Qt::Horizontal ); + slider->setScalePosition( QwtSlider::TrailingScale ); + slider->setTrough( true ); + slider->setGroove( false ); + slider->setSpacing( 0 ); + slider->setHandleSize( QSize( 30, 16 ) ); + slider->setScale( 10.0, -10.0 ); + slider->setTotalSteps( 8 ); + slider->setSingleSteps( 1 ); + slider->setPageSteps( 1 ); + slider->setWrapping( true ); + break; + } + case 1: + { + slider->setOrientation( Qt::Horizontal ); + slider->setScalePosition( QwtSlider::NoScale ); + slider->setTrough( true ); + slider->setGroove( true ); + slider->setScale( 0.0, 1.0 ); + slider->setTotalSteps( 100 ); + slider->setSingleSteps( 1 ); + slider->setPageSteps( 5 ); + break; + } + case 2: + { + slider->setOrientation( Qt::Horizontal ); + slider->setScalePosition( QwtSlider::LeadingScale ); + slider->setTrough( false ); + slider->setGroove( true ); + slider->setHandleSize( QSize( 12, 25 ) ); + slider->setScale( 1000.0, 3000.0 ); + slider->setTotalSteps( 200.0 ); + slider->setSingleSteps( 2 ); + slider->setPageSteps( 10 ); + break; + } + case 3: + { + slider->setOrientation( Qt::Horizontal ); + slider->setScalePosition( QwtSlider::TrailingScale ); + slider->setTrough( true ); + slider->setGroove( true ); + + QwtLinearScaleEngine *scaleEngine = new QwtLinearScaleEngine( 2 ); + scaleEngine->setTransformation( new QwtPowerTransform( 2 ) ); + slider->setScaleEngine( scaleEngine ); + slider->setScale( 0.0, 128.0 ); + slider->setTotalSteps( 100 ); + slider->setStepAlignment( false ); + slider->setSingleSteps( 1 ); + slider->setPageSteps( 5 ); + break; + } + case 4: + { + slider->setOrientation( Qt::Vertical ); + slider->setScalePosition( QwtSlider::TrailingScale ); + slider->setTrough( false ); + slider->setGroove( true ); + slider->setScale( 100.0, 0.0 ); + slider->setInvertedControls( true ); + slider->setTotalSteps( 100 ); + slider->setPageSteps( 5 ); + slider->setScaleMaxMinor( 5 ); + break; + } + case 5: + { + slider->setOrientation( Qt::Vertical ); + slider->setScalePosition( QwtSlider::NoScale ); + slider->setTrough( true ); + slider->setGroove( false ); + slider->setScale( 0.0, 100.0 ); + slider->setTotalSteps( 100 ); + slider->setPageSteps( 10 ); + break; + } + case 6: + { + slider->setOrientation( Qt::Vertical ); + slider->setScalePosition( QwtSlider::LeadingScale ); + slider->setTrough( true ); + slider->setGroove( true ); + slider->setScaleEngine( new QwtLogScaleEngine ); + slider->setStepAlignment( false ); + slider->setHandleSize( QSize( 20, 32 ) ); + slider->setBorderWidth( 1 ); + slider->setScale( 1.0, 1.0e4 ); + slider->setTotalSteps( 100 ); + slider->setPageSteps( 10 ); + slider->setScaleMaxMinor( 9 ); + break; + } + } + + if ( slider ) + { + QString name( "Slider %1" ); + slider->setObjectName( name.arg( sliderType ) ); + } + + return slider; +} + +void SliderBox::setNum( double v ) +{ + QString text; + text.setNum( v, 'f', 2 ); + + d_label->setText( text ); +} diff --git a/qwt/examples/controls/sliderbox.h b/qwt/examples/controls/sliderbox.h new file mode 100644 index 000000000..c79accdee --- /dev/null +++ b/qwt/examples/controls/sliderbox.h @@ -0,0 +1,25 @@ +#ifndef _SLIDER_BOX_H_ +#define _SLIDER_BOX_H_ 1 + +#include + +class QLabel; +class QwtSlider; + +class SliderBox: public QWidget +{ + Q_OBJECT +public: + SliderBox( int sliderType, QWidget *parent = NULL ); + +private Q_SLOTS: + void setNum( double v ); + +private: + QwtSlider *createSlider( int sliderType ) const; + + QwtSlider *d_slider; + QLabel *d_label; +}; + +#endif diff --git a/qwt/examples/controls/slidertab.cpp b/qwt/examples/controls/slidertab.cpp new file mode 100644 index 000000000..311d6433c --- /dev/null +++ b/qwt/examples/controls/slidertab.cpp @@ -0,0 +1,37 @@ +#include "slidertab.h" +#include "sliderbox.h" +#include + +SliderTab::SliderTab( QWidget *parent ): + QWidget( parent ) +{ + int i; + + QBoxLayout *hLayout = createLayout( Qt::Vertical ); + for ( i = 0; i < 4; i++ ) + hLayout->addWidget( new SliderBox( i ) ); + hLayout->addStretch(); + + QBoxLayout *vLayout = createLayout( Qt::Horizontal ); + for ( ; i < 7; i++ ) + vLayout->addWidget( new SliderBox( i ) ); + + QBoxLayout *mainLayout = createLayout( Qt::Horizontal, this ); + mainLayout->addLayout( vLayout ); + mainLayout->addLayout( hLayout, 10 ); +} + +QBoxLayout *SliderTab::createLayout( + Qt::Orientation orientation, QWidget *widget ) +{ + QBoxLayout *layout = + new QBoxLayout( QBoxLayout::LeftToRight, widget ); + + if ( orientation == Qt::Vertical ) + layout->setDirection( QBoxLayout::TopToBottom ); + + layout->setSpacing( 20 ); + layout->setMargin( 0 ); + + return layout; +} diff --git a/qwt/examples/controls/slidertab.h b/qwt/examples/controls/slidertab.h new file mode 100644 index 000000000..ffa243c07 --- /dev/null +++ b/qwt/examples/controls/slidertab.h @@ -0,0 +1,18 @@ +#ifndef _SLIDER_TAB_H +#define _SLIDER_TAB_H 1 + +#include + +class QBoxLayout; + +class SliderTab: public QWidget +{ +public: + SliderTab( QWidget *parent = NULL ); + +private: + QBoxLayout *createLayout( Qt::Orientation, + QWidget *widget = NULL ); +}; + +#endif diff --git a/qwt/examples/controls/wheelbox.cpp b/qwt/examples/controls/wheelbox.cpp new file mode 100644 index 000000000..141b00341 --- /dev/null +++ b/qwt/examples/controls/wheelbox.cpp @@ -0,0 +1,188 @@ +#include +#include +#include +#include +#include +#include +#include +#include "wheelbox.h" + +WheelBox::WheelBox( Qt::Orientation orientation, + int type, QWidget *parent ): + QWidget( parent ) +{ + QWidget *box = createBox( orientation, type ); + d_label = new QLabel( this ); + d_label->setAlignment( Qt::AlignHCenter | Qt::AlignTop ); + + QBoxLayout *layout = new QVBoxLayout( this ); + layout->addWidget( box ); + layout->addWidget( d_label ); + + setNum( d_wheel->value() ); + + connect( d_wheel, SIGNAL( valueChanged( double ) ), + this, SLOT( setNum( double ) ) ); +} + +QWidget *WheelBox::createBox( + Qt::Orientation orientation, int type ) +{ + d_wheel = new QwtWheel(); + d_wheel->setValue( 80 ); + d_wheel->setWheelWidth( 20 ); + d_wheel->setMass( 1.0 ); + + d_thermo = new QwtThermo(); + d_thermo->setOrientation( orientation ); + + if ( orientation == Qt::Horizontal ) + { + d_thermo->setScalePosition( QwtThermo::LeadingScale ); + d_wheel->setOrientation( Qt::Vertical ); + } + else + { + d_thermo->setScalePosition( QwtThermo::TrailingScale ); + d_wheel->setOrientation( Qt::Horizontal ); + } + + switch( type ) + { + case 0: + { + QwtLinearColorMap *colorMap = new QwtLinearColorMap(); + colorMap->setColorInterval( Qt::blue, Qt::red ); + d_thermo->setColorMap( colorMap ); + + break; + } + case 1: + { + QwtLinearColorMap *colorMap = new QwtLinearColorMap(); + colorMap->setMode( QwtLinearColorMap::FixedColors ); + + int idx = 4; + + colorMap->setColorInterval( Qt::GlobalColor( idx ), + Qt::GlobalColor( idx + 10 ) ); + for ( int i = 1; i < 10; i++ ) + { + colorMap->addColorStop( i / 10.0, + Qt::GlobalColor( idx + i ) ); + } + + d_thermo->setColorMap( colorMap ); + break; + } + case 2: + { + d_wheel->setRange( 10, 1000 ); + d_wheel->setSingleStep( 1.0 ); + + d_thermo->setScaleEngine( new QwtLogScaleEngine ); + d_thermo->setScaleMaxMinor( 10 ); + + d_thermo->setFillBrush( Qt::darkCyan ); + d_thermo->setAlarmBrush( Qt::magenta ); + d_thermo->setAlarmLevel( 500.0 ); + + d_wheel->setValue( 800 ); + + break; + } + case 3: + { + d_wheel->setRange( -1000, 1000 ); + d_wheel->setSingleStep( 1.0 ); + d_wheel->setPalette( QColor( "Tan" ) ); + + QwtLinearScaleEngine *scaleEngine = new QwtLinearScaleEngine(); + scaleEngine->setTransformation( new QwtPowerTransform( 2 ) ); + + d_thermo->setScaleMaxMinor( 5 ); + d_thermo->setScaleEngine( scaleEngine ); + + QPalette pal = palette(); + pal.setColor( QPalette::Base, Qt::darkGray ); + pal.setColor( QPalette::ButtonText, QColor( "darkKhaki" ) ); + + d_thermo->setPalette( pal ); + break; + } + case 4: + { + d_wheel->setRange( -100, 300 ); + d_wheel->setInverted( true ); + + QwtLinearColorMap *colorMap = new QwtLinearColorMap(); + colorMap->setColorInterval( Qt::darkCyan, Qt::yellow ); + d_thermo->setColorMap( colorMap ); + + d_wheel->setValue( 243 ); + + break; + } + case 5: + { + d_thermo->setFillBrush( Qt::darkCyan ); + d_thermo->setAlarmBrush( Qt::magenta ); + d_thermo->setAlarmLevel( 60.0 ); + + break; + } + case 6: + { + d_thermo->setOriginMode( QwtThermo::OriginMinimum ); + d_thermo->setFillBrush( QBrush( "DarkSlateBlue" ) ); + d_thermo->setAlarmBrush( QBrush( "DarkOrange" ) ); + d_thermo->setAlarmLevel( 60.0 ); + + break; + } + case 7: + { + d_wheel->setRange( -100, 100 ); + + d_thermo->setOriginMode( QwtThermo::OriginCustom ); + d_thermo->setOrigin( 0.0 ); + d_thermo->setFillBrush( Qt::darkBlue ); + + break; + } + } + + double min = d_wheel->minimum(); + double max = d_wheel->maximum(); + + if ( d_wheel->isInverted() ) + qSwap( min, max ); + + d_thermo->setScale( min, max ); + d_thermo->setValue( d_wheel->value() ); + + connect( d_wheel, SIGNAL( valueChanged( double ) ), + d_thermo, SLOT( setValue( double ) ) ); + + QWidget *box = new QWidget(); + + QBoxLayout *layout; + + if ( orientation == Qt::Horizontal ) + layout = new QHBoxLayout( box ); + else + layout = new QVBoxLayout( box ); + + layout->addWidget( d_thermo, Qt::AlignCenter ); + layout->addWidget( d_wheel ); + + return box; +} + +void WheelBox::setNum( double v ) +{ + QString text; + text.setNum( v, 'f', 2 ); + + d_label->setText( text ); +} diff --git a/qwt/examples/controls/wheelbox.h b/qwt/examples/controls/wheelbox.h new file mode 100644 index 000000000..4381fa2c8 --- /dev/null +++ b/qwt/examples/controls/wheelbox.h @@ -0,0 +1,29 @@ +#ifndef _WHEEL_BOX_H_ +#define _WHEEL_BOX_H_ 1 + +#include + +class QLabel; +class QwtThermo; +class QwtWheel; + +class WheelBox: public QWidget +{ + Q_OBJECT +public: + WheelBox( Qt::Orientation, + int type, QWidget *parent = NULL ); + +private Q_SLOTS: + void setNum( double v ); + +private: + QWidget *createBox( Qt::Orientation, int type ); + +private: + QwtWheel *d_wheel; + QwtThermo *d_thermo; + QLabel *d_label; +}; + +#endif diff --git a/qwt/examples/controls/wheeltab.cpp b/qwt/examples/controls/wheeltab.cpp new file mode 100644 index 000000000..a309a6f5a --- /dev/null +++ b/qwt/examples/controls/wheeltab.cpp @@ -0,0 +1,28 @@ +#include "wheeltab.h" +#include "wheelbox.h" +#include + +WheelTab::WheelTab( QWidget *parent ): + QWidget( parent ) +{ + const int numBoxes = 4; + + QGridLayout *layout1 = new QGridLayout(); + for ( int i = 0; i < numBoxes; i++ ) + { + WheelBox *box = new WheelBox( Qt::Vertical, i ); + layout1->addWidget( box, i / 2, i % 2 ); + } + + QGridLayout *layout2 = new QGridLayout(); + for ( int i = 0; i < numBoxes; i++ ) + { + WheelBox *box = new WheelBox( Qt::Horizontal, i + numBoxes ); + layout2->addWidget( box, i / 2, i % 2 ); + } + + QHBoxLayout *layout = new QHBoxLayout( this ); + layout->addLayout( layout1, 2 ); + layout->addLayout( layout2, 5 ); +} + diff --git a/qwt/examples/controls/wheeltab.h b/qwt/examples/controls/wheeltab.h new file mode 100644 index 000000000..4ea971789 --- /dev/null +++ b/qwt/examples/controls/wheeltab.h @@ -0,0 +1,12 @@ +#ifndef _WHEEL_TAB_H +#define _WHEEL_TAB_H 1 + +#include + +class WheelTab: public QWidget +{ +public: + WheelTab( QWidget *parent = NULL ); +}; + +#endif diff --git a/qwt/examples/cpuplot/cpupiemarker.cpp b/qwt/examples/cpuplot/cpupiemarker.cpp new file mode 100644 index 000000000..4f4c700be --- /dev/null +++ b/qwt/examples/cpuplot/cpupiemarker.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include "cpuplot.h" +#include "cpupiemarker.h" + +CpuPieMarker::CpuPieMarker() +{ + setZ( 1000 ); + setRenderHint( QwtPlotItem::RenderAntialiased, true ); +} + +int CpuPieMarker::rtti() const +{ + return QwtPlotItem::Rtti_PlotUserItem; +} + +void CpuPieMarker::draw( QPainter *painter, + const QwtScaleMap &, const QwtScaleMap &, + const QRectF &rect ) const +{ + const CpuPlot *cpuPlot = static_cast ( plot() ); + + const QwtScaleMap yMap = cpuPlot->canvasMap( QwtAxis::yLeft ); + + const int margin = 5; + + QRectF pieRect; + pieRect.setX( rect.x() + margin ); + pieRect.setY( rect.y() + margin ); + pieRect.setHeight( yMap.transform( 80.0 ) ); + pieRect.setWidth( pieRect.height() ); + + const int dataType[] = { CpuPlot::User, CpuPlot::System, CpuPlot::Idle }; + + int angle = static_cast( 5760 * 0.75 ); + for ( unsigned int i = 0; + i < sizeof( dataType ) / sizeof( dataType[0] ); i++ ) + { + const QwtPlotCurve *curve = cpuPlot->cpuCurve( dataType[i] ); + if ( curve->dataSize() > 0 ) + { + const int value = static_cast( 5760 * curve->sample( 0 ).y() / 100.0 ); + + painter->save(); + painter->setBrush( QBrush( curve->pen().color(), Qt::SolidPattern ) ); + if ( value != 0 ) + painter->drawPie( pieRect, -angle, -value ); + painter->restore(); + + angle += value; + } + } +} + diff --git a/qwt/examples/cpuplot/cpupiemarker.h b/qwt/examples/cpuplot/cpupiemarker.h new file mode 100644 index 000000000..366138a56 --- /dev/null +++ b/qwt/examples/cpuplot/cpupiemarker.h @@ -0,0 +1,17 @@ +//----------------------------------------------------------------- +// This class shows how to extend QwtPlotItems. It displays a +// pie chart of user/total/idle cpu usage in percent. +//----------------------------------------------------------------- + +#include + +class CpuPieMarker: public QwtPlotItem +{ +public: + CpuPieMarker(); + + virtual int rtti() const; + + virtual void draw( QPainter *, + const QwtScaleMap &, const QwtScaleMap &, const QRectF & ) const; +}; diff --git a/qwt/examples/cpuplot/cpuplot.cpp b/qwt/examples/cpuplot/cpuplot.cpp new file mode 100644 index 000000000..e8035492a --- /dev/null +++ b/qwt/examples/cpuplot/cpuplot.cpp @@ -0,0 +1,254 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "cpupiemarker.h" +#include "cpuplot.h" + +class TimeScaleDraw: public QwtScaleDraw +{ +public: + TimeScaleDraw( const QTime &base ): + baseTime( base ) + { + } + virtual QwtText label( double v ) const + { + QTime upTime = baseTime.addSecs( static_cast( v ) ); + return upTime.toString(); + } +private: + QTime baseTime; +}; + +class Background: public QwtPlotItem +{ +public: + Background() + { + setZ( 0.0 ); + } + + virtual int rtti() const + { + return QwtPlotItem::Rtti_PlotUserItem; + } + + virtual void draw( QPainter *painter, + const QwtScaleMap &, const QwtScaleMap &yMap, + const QRectF &canvasRect ) const + { + QColor c( Qt::white ); + QRectF r = canvasRect; + + for ( int i = 100; i > 0; i -= 10 ) + { + r.setBottom( yMap.transform( i - 10 ) ); + r.setTop( yMap.transform( i ) ); + painter->fillRect( r, c ); + + c = c.dark( 110 ); + } + } +}; + +class CpuCurve: public QwtPlotCurve +{ +public: + CpuCurve( const QString &title ): + QwtPlotCurve( title ) + { + setRenderHint( QwtPlotItem::RenderAntialiased ); + } + + void setColor( const QColor &color ) + { + QColor c = color; + c.setAlpha( 150 ); + + setPen( c ); + setBrush( c ); + } +}; + +CpuPlot::CpuPlot( QWidget *parent ): + QwtPlot( parent ), + dataCount( 0 ) +{ + setAutoReplot( false ); + + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setBorderRadius( 10 ); + + setCanvas( canvas ); + + plotLayout()->setAlignCanvasToScales( true ); + + QwtLegend *legend = new QwtLegend; + legend->setDefaultItemMode( QwtLegendData::Checkable ); + insertLegend( legend, QwtPlot::RightLegend ); + + setAxisTitle( QwtAxis::xBottom, " System Uptime [h:m:s]" ); + setAxisScaleDraw( QwtAxis::xBottom, + new TimeScaleDraw( cpuStat.upTime() ) ); + setAxisScale( QwtAxis::xBottom, 0, HISTORY ); + setAxisLabelRotation( QwtAxis::xBottom, -50.0 ); + setAxisLabelAlignment( QwtAxis::xBottom, Qt::AlignLeft | Qt::AlignBottom ); + + /* + In situations, when there is a label at the most right position of the + scale, additional space is needed to display the overlapping part + of the label would be taken by reducing the width of scale and canvas. + To avoid this "jumping canvas" effect, we add a permanent margin. + We don't need to do the same for the left border, because there + is enough space for the overlapping label below the left scale. + */ + + QwtScaleWidget *scaleWidget = axisWidget( QwtAxis::xBottom ); + const int fmh = QFontMetrics( scaleWidget->font() ).height(); + scaleWidget->setMinBorderDist( 0, fmh / 2 ); + + setAxisTitle( QwtAxis::yLeft, "Cpu Usage [%]" ); + setAxisScale( QwtAxis::yLeft, 0, 100 ); + + Background *bg = new Background(); + bg->attach( this ); + + CpuPieMarker *pie = new CpuPieMarker(); + pie->attach( this ); + + CpuCurve *curve; + + curve = new CpuCurve( "System" ); + curve->setColor( Qt::red ); + curve->attach( this ); + data[System].curve = curve; + + curve = new CpuCurve( "User" ); + curve->setColor( Qt::blue ); + curve->setZ( curve->z() - 1 ); + curve->attach( this ); + data[User].curve = curve; + + curve = new CpuCurve( "Total" ); + curve->setColor( Qt::black ); + curve->setZ( curve->z() - 2 ); + curve->attach( this ); + data[Total].curve = curve; + + curve = new CpuCurve( "Idle" ); + curve->setColor( Qt::darkCyan ); + curve->setZ( curve->z() - 3 ); + curve->attach( this ); + data[Idle].curve = curve; + + showCurve( data[System].curve, true ); + showCurve( data[User].curve, true ); + showCurve( data[Total].curve, false ); + showCurve( data[Idle].curve, false ); + + for ( int i = 0; i < HISTORY; i++ ) + timeData[HISTORY - 1 - i] = i; + + ( void )startTimer( 1000 ); // 1 second + + connect( legend, SIGNAL( checked( const QVariant &, bool, int ) ), + SLOT( legendChecked( const QVariant &, bool ) ) ); +} + +void CpuPlot::timerEvent( QTimerEvent * ) +{ + for ( int i = dataCount; i > 0; i-- ) + { + for ( int c = 0; c < NCpuData; c++ ) + { + if ( i < HISTORY ) + data[c].data[i] = data[c].data[i-1]; + } + } + + cpuStat.statistic( data[User].data[0], data[System].data[0] ); + + data[Total].data[0] = data[User].data[0] + data[System].data[0]; + data[Idle].data[0] = 100.0 - data[Total].data[0]; + + if ( dataCount < HISTORY ) + dataCount++; + + for ( int j = 0; j < HISTORY; j++ ) + timeData[j]++; + + setAxisScale( QwtAxis::xBottom, + timeData[HISTORY - 1], timeData[0] ); + + for ( int c = 0; c < NCpuData; c++ ) + { + data[c].curve->setRawSamples( + timeData, data[c].data, dataCount ); + } + + replot(); +} + +void CpuPlot::legendChecked( const QVariant &itemInfo, bool on ) +{ + QwtPlotItem *plotItem = infoToItem( itemInfo ); + if ( plotItem ) + showCurve( plotItem, on ); +} + +void CpuPlot::showCurve( QwtPlotItem *item, bool on ) +{ + item->setVisible( on ); + + QwtLegend *lgd = qobject_cast( legend() ); + + QList legendWidgets = + lgd->legendWidgets( itemToInfo( item ) ); + + if ( legendWidgets.size() == 1 ) + { + QwtLegendLabel *legendLabel = + qobject_cast( legendWidgets[0] ); + + if ( legendLabel ) + legendLabel->setChecked( on ); + } + + replot(); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + QWidget vBox; + vBox.setWindowTitle( "Cpu Plot" ); + + CpuPlot *plot = new CpuPlot( &vBox ); + plot->setTitle( "History" ); + + const int margin = 5; + plot->setContentsMargins( margin, margin, margin, margin ); + + QString info( "Press the legend to en/disable a curve" ); + + QLabel *label = new QLabel( info, &vBox ); + + QVBoxLayout *layout = new QVBoxLayout( &vBox ); + layout->addWidget( plot ); + layout->addWidget( label ); + + vBox.resize( 600, 400 ); + vBox.show(); + + return a.exec(); +} + diff --git a/qwt/examples/cpuplot/cpuplot.h b/qwt/examples/cpuplot/cpuplot.h new file mode 100644 index 000000000..a0c39a3f8 --- /dev/null +++ b/qwt/examples/cpuplot/cpuplot.h @@ -0,0 +1,47 @@ +#include +#include "cpustat.h" + +#define HISTORY 60 // seconds + +class QwtPlotCurve; + +class CpuPlot : public QwtPlot +{ + Q_OBJECT +public: + enum CpuData + { + User, + System, + Total, + Idle, + + NCpuData + }; + + CpuPlot( QWidget * = 0 ); + const QwtPlotCurve *cpuCurve( int id ) const + { + return data[id].curve; + } + +protected: + void timerEvent( QTimerEvent *e ); + +private Q_SLOTS: + void legendChecked( const QVariant &, bool on ); + +private: + void showCurve( QwtPlotItem *, bool on ); + + struct + { + QwtPlotCurve *curve; + double data[HISTORY]; + } data[NCpuData]; + + double timeData[HISTORY]; + + int dataCount; + CpuStat cpuStat; +}; diff --git a/qwt/examples/cpuplot/cpuplot.pro b/qwt/examples/cpuplot/cpuplot.pro new file mode 100644 index 000000000..1cc631299 --- /dev/null +++ b/qwt/examples/cpuplot/cpuplot.pro @@ -0,0 +1,22 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = cpuplot + +HEADERS = \ + cpuplot.h \ + cpustat.h \ + cpupiemarker.h + +SOURCES = \ + cpuplot.cpp \ + cpustat.cpp \ + cpupiemarker.cpp diff --git a/qwt/examples/cpuplot/cpustat.cpp b/qwt/examples/cpuplot/cpustat.cpp new file mode 100644 index 000000000..f9c7552ca --- /dev/null +++ b/qwt/examples/cpuplot/cpustat.cpp @@ -0,0 +1,220 @@ +#include +#include +#include +#include "cpustat.h" + +CpuStat::CpuStat() +{ + lookUp( procValues ); +} + +QTime CpuStat::upTime() const +{ + QTime t( 0, 0, 0 ); + for ( int i = 0; i < NValues; i++ ) + t = t.addSecs( int( procValues[i] / 100 ) ); + + return t; +} + +void CpuStat::statistic( double &user, double &system ) +{ + double values[NValues]; + + lookUp( values ); + + double userDelta = values[User] + values[Nice] + - procValues[User] - procValues[Nice]; + double systemDelta = values[System] - procValues[System]; + + double totalDelta = 0; + for ( int i = 0; i < NValues; i++ ) + totalDelta += values[i] - procValues[i]; + + user = userDelta / totalDelta * 100.0; + system = systemDelta / totalDelta * 100.0; + + for ( int j = 0; j < NValues; j++ ) + procValues[j] = values[j]; +} + +void CpuStat::lookUp( double values[NValues] ) const +{ + QFile file( "/proc/stat" ); + if ( !file.open( QIODevice::ReadOnly ) ) + { + static double dummyValues[][NValues] = + { + { 103726, 0, 23484, 819556 }, + { 103783, 0, 23489, 819604 }, + { 103798, 0, 23490, 819688 }, + { 103820, 0, 23490, 819766 }, + { 103840, 0, 23493, 819843 }, + { 103875, 0, 23499, 819902 }, + { 103917, 0, 23504, 819955 }, + { 103950, 0, 23508, 820018 }, + { 103987, 0, 23510, 820079 }, + { 104020, 0, 23513, 820143 }, + { 104058, 0, 23514, 820204 }, + { 104099, 0, 23520, 820257 }, + { 104121, 0, 23525, 820330 }, + { 104159, 0, 23530, 820387 }, + { 104176, 0, 23534, 820466 }, + { 104215, 0, 23538, 820523 }, + { 104245, 0, 23541, 820590 }, + { 104267, 0, 23545, 820664 }, + { 104311, 0, 23555, 820710 }, + { 104355, 0, 23565, 820756 }, + { 104367, 0, 23567, 820842 }, + { 104383, 0, 23572, 820921 }, + { 104396, 0, 23577, 821003 }, + { 104413, 0, 23579, 821084 }, + { 104446, 0, 23588, 821142 }, + { 104521, 0, 23594, 821161 }, + { 104611, 0, 23604, 821161 }, + { 104708, 0, 23607, 821161 }, + { 104804, 0, 23611, 821161 }, + { 104895, 0, 23620, 821161 }, + { 104993, 0, 23622, 821161 }, + { 105089, 0, 23626, 821161 }, + { 105185, 0, 23630, 821161 }, + { 105281, 0, 23634, 821161 }, + { 105379, 0, 23636, 821161 }, + { 105472, 0, 23643, 821161 }, + { 105569, 0, 23646, 821161 }, + { 105666, 0, 23649, 821161 }, + { 105763, 0, 23652, 821161 }, + { 105828, 0, 23661, 821187 }, + { 105904, 0, 23666, 821206 }, + { 105999, 0, 23671, 821206 }, + { 106094, 0, 23676, 821206 }, + { 106184, 0, 23686, 821206 }, + { 106273, 0, 23692, 821211 }, + { 106306, 0, 23700, 821270 }, + { 106341, 0, 23703, 821332 }, + { 106392, 0, 23709, 821375 }, + { 106423, 0, 23715, 821438 }, + { 106472, 0, 23721, 821483 }, + { 106531, 0, 23727, 821517 }, + { 106562, 0, 23732, 821582 }, + { 106597, 0, 23736, 821643 }, + { 106633, 0, 23737, 821706 }, + { 106666, 0, 23742, 821768 }, + { 106697, 0, 23744, 821835 }, + { 106730, 0, 23748, 821898 }, + { 106765, 0, 23751, 821960 }, + { 106799, 0, 23754, 822023 }, + { 106831, 0, 23758, 822087 }, + { 106862, 0, 23761, 822153 }, + { 106899, 0, 23763, 822214 }, + { 106932, 0, 23766, 822278 }, + { 106965, 0, 23768, 822343 }, + { 107009, 0, 23771, 822396 }, + { 107040, 0, 23775, 822461 }, + { 107092, 0, 23780, 822504 }, + { 107143, 0, 23787, 822546 }, + { 107200, 0, 23795, 822581 }, + { 107250, 0, 23803, 822623 }, + { 107277, 0, 23810, 822689 }, + { 107286, 0, 23810, 822780 }, + { 107313, 0, 23817, 822846 }, + { 107325, 0, 23818, 822933 }, + { 107332, 0, 23818, 823026 }, + { 107344, 0, 23821, 823111 }, + { 107357, 0, 23821, 823198 }, + { 107368, 0, 23823, 823284 }, + { 107375, 0, 23824, 823377 }, + { 107386, 0, 23825, 823465 }, + { 107396, 0, 23826, 823554 }, + { 107422, 0, 23830, 823624 }, + { 107434, 0, 23831, 823711 }, + { 107456, 0, 23835, 823785 }, + { 107468, 0, 23838, 823870 }, + { 107487, 0, 23840, 823949 }, + { 107515, 0, 23843, 824018 }, + { 107528, 0, 23846, 824102 }, + { 107535, 0, 23851, 824190 }, + { 107548, 0, 23853, 824275 }, + { 107562, 0, 23857, 824357 }, + { 107656, 0, 23863, 824357 }, + { 107751, 0, 23868, 824357 }, + { 107849, 0, 23870, 824357 }, + { 107944, 0, 23875, 824357 }, + { 108043, 0, 23876, 824357 }, + { 108137, 0, 23882, 824357 }, + { 108230, 0, 23889, 824357 }, + { 108317, 0, 23902, 824357 }, + { 108412, 0, 23907, 824357 }, + { 108511, 0, 23908, 824357 }, + { 108608, 0, 23911, 824357 }, + { 108704, 0, 23915, 824357 }, + { 108801, 0, 23918, 824357 }, + { 108891, 0, 23928, 824357 }, + { 108987, 0, 23932, 824357 }, + { 109072, 0, 23943, 824361 }, + { 109079, 0, 23943, 824454 }, + { 109086, 0, 23944, 824546 }, + { 109098, 0, 23950, 824628 }, + { 109108, 0, 23955, 824713 }, + { 109115, 0, 23957, 824804 }, + { 109122, 0, 23958, 824896 }, + { 109132, 0, 23959, 824985 }, + { 109142, 0, 23961, 825073 }, + { 109146, 0, 23962, 825168 }, + { 109153, 0, 23964, 825259 }, + { 109162, 0, 23966, 825348 }, + { 109168, 0, 23969, 825439 }, + { 109176, 0, 23971, 825529 }, + { 109185, 0, 23974, 825617 }, + { 109193, 0, 23977, 825706 }, + { 109198, 0, 23978, 825800 }, + { 109206, 0, 23978, 825892 }, + { 109212, 0, 23981, 825983 }, + { 109219, 0, 23981, 826076 }, + { 109225, 0, 23981, 826170 }, + { 109232, 0, 23984, 826260 }, + { 109242, 0, 23984, 826350 }, + { 109255, 0, 23986, 826435 }, + { 109268, 0, 23987, 826521 }, + { 109283, 0, 23990, 826603 }, + { 109288, 0, 23991, 826697 }, + { 109295, 0, 23993, 826788 }, + { 109308, 0, 23994, 826874 }, + { 109322, 0, 24009, 826945 }, + { 109328, 0, 24011, 827037 }, + { 109338, 0, 24012, 827126 }, + { 109347, 0, 24012, 827217 }, + { 109354, 0, 24017, 827305 }, + { 109367, 0, 24017, 827392 }, + { 109371, 0, 24019, 827486 }, + }; + static int counter = 0; + + for ( int i = 0; i < NValues; i++ ) + values[i] = dummyValues[counter][i]; + + counter = ( counter + 1 ) + % ( sizeof( dummyValues ) / sizeof( dummyValues[0] ) ); + } + else + { + QTextStream textStream( &file ); + do + { + QString line = textStream.readLine(); + line = line.trimmed(); + if ( line.startsWith( "cpu " ) ) + { + const QStringList valueList = + line.split( " ", QString::SkipEmptyParts ); + if ( valueList.count() >= 5 ) + { + for ( int i = 0; i < NValues; i++ ) + values[i] = valueList[i+1].toDouble(); + } + break; + } + } + while( !textStream.atEnd() ); + } +} diff --git a/qwt/examples/cpuplot/cpustat.h b/qwt/examples/cpuplot/cpustat.h new file mode 100644 index 000000000..019bea855 --- /dev/null +++ b/qwt/examples/cpuplot/cpustat.h @@ -0,0 +1,23 @@ +#include + +class CpuStat +{ +public: + CpuStat(); + void statistic( double &user, double &system ); + QTime upTime() const; + + enum Value + { + User, + Nice, + System, + Idle, + + NValues + }; + +private: + void lookUp( double[NValues] ) const; + double procValues[NValues]; +}; diff --git a/qwt/examples/curvdemo1/curvdemo1.cpp b/qwt/examples/curvdemo1/curvdemo1.cpp new file mode 100644 index 000000000..e813dc198 --- /dev/null +++ b/qwt/examples/curvdemo1/curvdemo1.cpp @@ -0,0 +1,202 @@ + +#include +#include +#include +#include +#include +#include +#include +#include + +//------------------------------------------------------------ +// curvdemo1 +// +// This example program features some of the different +// display styles of the QwtPlotCurve class +//------------------------------------------------------------ + + +// +// Array Sizes +// +const int Size = 27; +const int CurvCnt = 6; + +// +// Arrays holding the values +// +double xval[Size]; +double yval[Size]; +QwtScaleMap xMap; +QwtScaleMap yMap; + +class MainWin : public QFrame +{ +public: + MainWin(); + +protected: + virtual void paintEvent( QPaintEvent * ); + void drawContents( QPainter *p ); + +private: + void shiftDown( QRect &rect, int offset ) const; + + QwtPlotCurve d_curves[CurvCnt]; +}; + +MainWin::MainWin() +{ + int i; + + xMap.setScaleInterval( -0.5, 10.5 ); + yMap.setScaleInterval( -1.1, 1.1 ); + + // + // Frame style + // + setFrameStyle( QFrame::Box | QFrame::Raised ); + setLineWidth( 2 ); + setMidLineWidth( 3 ); + + // + // Calculate values + // + for( i = 0; i < Size; i++ ) + { + xval[i] = double( i ) * 10.0 / double( Size - 1 ); + yval[i] = qSin( xval[i] ) * qCos( 2.0 * xval[i] ); + } + + // + // define curve styles + // + i = 0; + + d_curves[i].setSymbol( new QwtSymbol( QwtSymbol::Cross, Qt::NoBrush, + QPen( Qt::black ), QSize( 5, 5 ) ) ); + d_curves[i].setPen( Qt::darkGreen ); + d_curves[i].setStyle( QwtPlotCurve::Lines ); + d_curves[i].setCurveAttribute( QwtPlotCurve::Fitted ); + i++; + + d_curves[i].setSymbol( new QwtSymbol( QwtSymbol::Ellipse, Qt::yellow, + QPen( Qt::blue ), QSize( 5, 5 ) ) ); + d_curves[i].setPen( Qt::red ); + d_curves[i].setStyle( QwtPlotCurve::Sticks ); + i++; + + d_curves[i].setPen( Qt::darkBlue ); + d_curves[i].setStyle( QwtPlotCurve::Lines ); + i++; + + d_curves[i].setPen( Qt::darkBlue ); + d_curves[i].setStyle( QwtPlotCurve::Lines ); + d_curves[i].setRenderHint( QwtPlotItem::RenderAntialiased ); + i++; + + d_curves[i].setPen( Qt::darkCyan ); + d_curves[i].setStyle( QwtPlotCurve::Steps ); + i++; + + d_curves[i].setSymbol( new QwtSymbol( QwtSymbol::XCross, Qt::NoBrush, + QPen( Qt::darkMagenta ), QSize( 5, 5 ) ) ); + d_curves[i].setStyle( QwtPlotCurve::NoCurve ); + i++; + + + // + // attach data + // + for( i = 0; i < CurvCnt; i++ ) + d_curves[i].setRawSamples( xval, yval, Size ); +} + +void MainWin::shiftDown( QRect &rect, int offset ) const +{ + rect.translate( 0, offset ); +} + +void MainWin::paintEvent( QPaintEvent *event ) +{ + QFrame::paintEvent( event ); + + QPainter painter( this ); + painter.setClipRect( contentsRect() ); + drawContents( &painter ); +} + + +// +// REDRAW CONTENTS +// +void MainWin::drawContents( QPainter *painter ) +{ + int deltay, i; + + QRect r = contentsRect(); + + deltay = r.height() / CurvCnt - 1; + + r.setHeight( deltay ); + + // + // draw curves + // + for ( i = 0; i < CurvCnt; i++ ) + { + xMap.setPaintInterval( r.left(), r.right() ); + yMap.setPaintInterval( r.top(), r.bottom() ); + + painter->setRenderHint( QPainter::Antialiasing, + d_curves[i].testRenderHint( QwtPlotItem::RenderAntialiased ) ); + d_curves[i].draw( painter, xMap, yMap, r ); + + shiftDown( r, deltay ); + } + + // + // draw titles + // + r = contentsRect(); // reset r + painter->setFont( QFont( "Helvetica", 8 ) ); + + const int alignment = Qt::AlignTop | Qt::AlignHCenter; + + painter->setPen( Qt::black ); + + painter->drawText( 0, r.top(), r.width(), painter->fontMetrics().height(), + alignment, "Style: Line/Fitted, Symbol: Cross" ); + shiftDown( r, deltay ); + + painter->drawText( 0, r.top(), r.width(), painter->fontMetrics().height(), + alignment, "Style: Sticks, Symbol: Ellipse" ); + shiftDown( r, deltay ); + + painter->drawText( 0 , r.top(), r.width(), painter->fontMetrics().height(), + alignment, "Style: Lines, Symbol: None" ); + shiftDown( r, deltay ); + + painter->drawText( 0 , r.top(), r.width(), painter->fontMetrics().height(), + alignment, "Style: Lines, Symbol: None, Antialiased" ); + shiftDown( r, deltay ); + + painter->drawText( 0, r.top(), r.width(), painter->fontMetrics().height(), + alignment, "Style: Steps, Symbol: None" ); + shiftDown( r, deltay ); + + painter->drawText( 0, r.top(), r.width(), painter->fontMetrics().height(), + alignment, "Style: NoCurve, Symbol: XCross" ); +} + +int main ( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWin w; + + w.resize( 300, 600 ); + w.show(); + + return a.exec(); +} diff --git a/qwt/examples/curvdemo1/curvdemo1.pro b/qwt/examples/curvdemo1/curvdemo1.pro new file mode 100644 index 000000000..298c97744 --- /dev/null +++ b/qwt/examples/curvdemo1/curvdemo1.pro @@ -0,0 +1,15 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = curvdemo1 + +SOURCES = \ + curvdemo1.cpp diff --git a/qwt/examples/dials/attitude_indicator.cpp b/qwt/examples/dials/attitude_indicator.cpp new file mode 100644 index 000000000..a5810be67 --- /dev/null +++ b/qwt/examples/dials/attitude_indicator.cpp @@ -0,0 +1,127 @@ +#include "attitude_indicator.h" +#include +#include +#include +#include +#include + +AttitudeIndicatorNeedle::AttitudeIndicatorNeedle( const QColor &color ) +{ + QPalette palette; + palette.setColor( QPalette::Text, color ); + setPalette( palette ); +} + +void AttitudeIndicatorNeedle::drawNeedle( QPainter *painter, + double length, QPalette::ColorGroup colorGroup ) const +{ + double triangleSize = length * 0.1; + double pos = length - 2.0; + + QPainterPath path; + path.moveTo( pos, 0 ); + path.lineTo( pos - 2 * triangleSize, triangleSize ); + path.lineTo( pos - 2 * triangleSize, -triangleSize ); + path.closeSubpath(); + + painter->setBrush( palette().brush( colorGroup, QPalette::Text ) ); + painter->drawPath( path ); + + double l = length - 2; + painter->setPen( QPen( palette().color( colorGroup, QPalette::Text ), 3 ) ); + painter->drawLine( QPointF( 0.0, -l ), QPointF( 0.0, l ) ); +} + +AttitudeIndicator::AttitudeIndicator( + QWidget *parent ): + QwtDial( parent ), + d_gradient( 0.0 ) +{ + QwtRoundScaleDraw *scaleDraw = new QwtRoundScaleDraw(); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Backbone, false ); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Labels, false ); + setScaleDraw( scaleDraw ); + + setMode( RotateScale ); + setWrapping( true ); + + setOrigin( 270.0 ); + + setScaleMaxMinor( 0 ); + setScaleStepSize( 30.0 ); + setScale( 0.0, 360.0 ); + + const QColor color = palette().color( QPalette::Text ); + setNeedle( new AttitudeIndicatorNeedle( color ) ); +} + +void AttitudeIndicator::setGradient( double gradient ) +{ + if ( gradient < -1.0 ) + gradient = -1.0; + else if ( gradient > 1.0 ) + gradient = 1.0; + + if ( d_gradient != gradient ) + { + d_gradient = gradient; + update(); + } +} + +void AttitudeIndicator::drawScale( QPainter *painter, + const QPointF ¢er, double radius ) const +{ + const double offset = 4.0; + + const QPointF p0 = qwtPolar2Pos( center, offset, 1.5 * M_PI ); + + const double w = innerRect().width(); + + QPainterPath path; + path.moveTo( qwtPolar2Pos( p0, w, 0.0 ) ); + path.lineTo( qwtPolar2Pos( path.currentPosition(), 2 * w, M_PI ) ); + path.lineTo( qwtPolar2Pos( path.currentPosition(), w, 0.5 * M_PI ) ); + path.lineTo( qwtPolar2Pos( path.currentPosition(), w, 0.0 ) ); + + painter->save(); + painter->setClipPath( path ); // swallow 180 - 360 degrees + + QwtDial::drawScale( painter, center, radius ); + + painter->restore(); +} + +void AttitudeIndicator::drawScaleContents( QPainter *painter, + const QPointF &, double ) const +{ + int dir = 360 - qRound( origin() - value() ); // counter clockwise + int arc = 90 + qRound( gradient() * 90 ); + + const QColor skyColor( 38, 151, 221 ); + + painter->save(); + painter->setBrush( skyColor ); + painter->drawChord( scaleInnerRect(), + ( dir - arc ) * 16, 2 * arc * 16 ); + painter->restore(); +} + +void AttitudeIndicator::keyPressEvent( QKeyEvent *event ) +{ + switch( event->key() ) + { + case Qt::Key_Plus: + { + setGradient( gradient() + 0.05 ); + break; + } + case Qt::Key_Minus: + { + setGradient( gradient() - 0.05 ); + break; + } + default: + QwtDial::keyPressEvent( event ); + } +} diff --git a/qwt/examples/dials/attitude_indicator.h b/qwt/examples/dials/attitude_indicator.h new file mode 100644 index 000000000..d4eb0c833 --- /dev/null +++ b/qwt/examples/dials/attitude_indicator.h @@ -0,0 +1,39 @@ +#include +#include + +class AttitudeIndicatorNeedle: public QwtDialNeedle +{ +public: + AttitudeIndicatorNeedle( const QColor & ); + +protected: + virtual void drawNeedle( QPainter *, + double length, QPalette::ColorGroup ) const; +}; + +class AttitudeIndicator: public QwtDial +{ + Q_OBJECT + +public: + AttitudeIndicator( QWidget *parent = NULL ); + + double angle() const { return value(); } + double gradient() const { return d_gradient; } + +public Q_SLOTS: + void setGradient( double ); + void setAngle( double angle ) { setValue( angle ); } + +protected: + virtual void keyPressEvent( QKeyEvent * ); + + virtual void drawScale( QPainter *, + const QPointF ¢er, double radius ) const; + + virtual void drawScaleContents( QPainter *painter, + const QPointF ¢er, double radius ) const; + +private: + double d_gradient; +}; diff --git a/qwt/examples/dials/cockpit_grid.cpp b/qwt/examples/dials/cockpit_grid.cpp new file mode 100644 index 000000000..f298fd452 --- /dev/null +++ b/qwt/examples/dials/cockpit_grid.cpp @@ -0,0 +1,186 @@ +#include +#include +#include +#include +#include "attitude_indicator.h" +#include "speedo_meter.h" +#include "cockpit_grid.h" + +CockpitGrid::CockpitGrid( QWidget *parent ): + QFrame( parent ) +{ + setAutoFillBackground( true ); + + setPalette( colorTheme( QColor( Qt::darkGray ).dark( 150 ) ) ); + + QGridLayout *layout = new QGridLayout( this ); + layout->setSpacing( 5 ); + layout->setMargin( 0 ); + + int i; + for ( i = 0; i < 3; i++ ) + { + QwtDial *dial = createDial( i ); + layout->addWidget( dial, 0, i ); + } + + for ( i = 0; i < layout->columnCount(); i++ ) + layout->setColumnStretch( i, 1 ); +} + +QwtDial *CockpitGrid::createDial( int pos ) +{ + QwtDial *dial = NULL; + switch( pos ) + { + case 0: + { + d_clock = new QwtAnalogClock( this ); +#if 0 + // disable minor ticks + d_clock->scaleDraw()->setTickLength( QwtScaleDiv::MinorTick, 0 ); +#endif + + const QColor knobColor = QColor( Qt::gray ).light( 130 ); + + for ( int i = 0; i < QwtAnalogClock::NHands; i++ ) + { + QColor handColor = QColor( Qt::gray ).light( 150 ); + int width = 8; + + if ( i == QwtAnalogClock::SecondHand ) + { + handColor = Qt::gray; + width = 5; + } + + QwtDialSimpleNeedle *hand = new QwtDialSimpleNeedle( + QwtDialSimpleNeedle::Arrow, true, handColor, knobColor ); + hand->setWidth( width ); + + d_clock->setHand( static_cast( i ), hand ); + } + + QTimer *timer = new QTimer( d_clock ); + timer->connect( timer, SIGNAL( timeout() ), + d_clock, SLOT( setCurrentTime() ) ); + timer->start( 1000 ); + + dial = d_clock; + break; + } + case 1: + { + d_speedo = new SpeedoMeter( this ); + d_speedo->setScaleStepSize( 20.0 ); + d_speedo->setScale( 0.0, 240.0 ); + d_speedo->scaleDraw()->setPenWidth( 2 ); + + QTimer *timer = new QTimer( d_speedo ); + timer->connect( timer, SIGNAL( timeout() ), + this, SLOT( changeSpeed() ) ); + timer->start( 50 ); + + dial = d_speedo; + break; + } + case 2: + { + d_ai = new AttitudeIndicator( this ); + d_ai->scaleDraw()->setPenWidth( 3 ); + + QTimer *gradientTimer = new QTimer( d_ai ); + gradientTimer->connect( gradientTimer, SIGNAL( timeout() ), + this, SLOT( changeGradient() ) ); + gradientTimer->start( 100 ); + + QTimer *angleTimer = new QTimer( d_ai ); + angleTimer->connect( angleTimer, SIGNAL( timeout() ), + this, SLOT( changeAngle() ) ); + angleTimer->start( 100 ); + + dial = d_ai; + break; + } + + } + + if ( dial ) + { + dial->setReadOnly( true ); + dial->setLineWidth( 4 ); + dial->setFrameShadow( QwtDial::Sunken ); + } + return dial; +} + +QPalette CockpitGrid::colorTheme( const QColor &base ) const +{ + QPalette palette; + palette.setColor( QPalette::Base, base ); + palette.setColor( QPalette::Window, base.dark( 150 ) ); + palette.setColor( QPalette::Mid, base.dark( 110 ) ); + palette.setColor( QPalette::Light, base.light( 170 ) ); + palette.setColor( QPalette::Dark, base.dark( 170 ) ); + palette.setColor( QPalette::Text, base.dark( 200 ).light( 800 ) ); + palette.setColor( QPalette::WindowText, base.dark( 200 ) ); + + return palette; +} + +void CockpitGrid::changeSpeed() +{ + static double offset = 0.8; + + double speed = d_speedo->value(); + + if ( ( speed < 7.0 && offset < 0.0 ) || + ( speed > 203.0 && offset > 0.0 ) ) + { + offset = -offset; + } + + static int counter = 0; + switch( counter++ % 12 ) + { + case 0: + case 2: + case 7: + case 8: + break; + default: + d_speedo->setValue( speed + offset ); + } +} + +void CockpitGrid::changeAngle() +{ + static double offset = 0.05; + + double angle = d_ai->angle(); + if ( angle > 180.0 ) + angle -= 360.0; + + if ( ( angle < -5.0 && offset < 0.0 ) || + ( angle > 5.0 && offset > 0.0 ) ) + { + offset = -offset; + } + + d_ai->setAngle( angle + offset ); +} + +void CockpitGrid::changeGradient() +{ + static double offset = 0.005; + + double gradient = d_ai->gradient(); + + if ( ( gradient < -0.05 && offset < 0.0 ) || + ( gradient > 0.05 && offset > 0.0 ) ) + { + offset = -offset; + } + + d_ai->setGradient( gradient + offset ); +} diff --git a/qwt/examples/dials/cockpit_grid.h b/qwt/examples/dials/cockpit_grid.h new file mode 100644 index 000000000..38139ee51 --- /dev/null +++ b/qwt/examples/dials/cockpit_grid.h @@ -0,0 +1,28 @@ +#include +#include + +class QwtDial; +class QwtAnalogClock; +class SpeedoMeter; +class AttitudeIndicator; + +class CockpitGrid: public QFrame +{ + Q_OBJECT + +public: + CockpitGrid( QWidget *parent = NULL ); + +private Q_SLOTS: + void changeSpeed(); + void changeGradient(); + void changeAngle(); + +private: + QPalette colorTheme( const QColor & ) const; + QwtDial *createDial( int pos ); + + QwtAnalogClock *d_clock; + SpeedoMeter *d_speedo; + AttitudeIndicator *d_ai; +}; diff --git a/qwt/examples/dials/compass_grid.cpp b/qwt/examples/dials/compass_grid.cpp new file mode 100644 index 000000000..02f66ed84 --- /dev/null +++ b/qwt/examples/dials/compass_grid.cpp @@ -0,0 +1,226 @@ +#include +#include +#include +#include +#include "compass_grid.h" + +CompassGrid::CompassGrid( QWidget *parent ): + QFrame( parent ) +{ + QPalette p = palette(); + p.setColor( backgroundRole(), Qt::gray ); + setPalette( p ); + + setAutoFillBackground( true ); + + QGridLayout *layout = new QGridLayout( this ); + layout->setSpacing( 5 ); + layout->setMargin( 0 ); + + int i; + for ( i = 0; i < 6; i++ ) + { + QwtCompass *compass = createCompass( i ); + layout->addWidget( compass, i / 3, i % 3 ); + } + + for ( i = 0; i < layout->columnCount(); i++ ) + layout->setColumnStretch( i, 1 ); +} + +QwtCompass *CompassGrid::createCompass( int pos ) +{ + int c; + + QPalette palette0; + for ( c = 0; c < QPalette::NColorRoles; c++ ) + { + const QPalette::ColorRole colorRole = + static_cast( c ); + + palette0.setColor( colorRole, QColor() ); + } + + palette0.setColor( QPalette::Base, + palette().color( backgroundRole() ).light( 120 ) ); + palette0.setColor( QPalette::WindowText, + palette0.color( QPalette::Base ) ); + + QwtCompass *compass = new QwtCompass( this ); + compass->setLineWidth( 4 ); + compass->setFrameShadow( + pos <= 2 ? QwtCompass::Sunken : QwtCompass::Raised ); + + switch( pos ) + { + case 0: + { + /* + A compass with a rose and no needle. Scale and rose are + rotating. + */ + compass->setMode( QwtCompass::RotateScale ); + + QwtSimpleCompassRose *rose = new QwtSimpleCompassRose( 16, 2 ); + rose->setWidth( 0.15 ); + + compass->setRose( rose ); + break; + } + case 1: + { + /* + A windrose, with a scale indicating the main directions only + */ + QMap map; + map.insert( 0.0, "N" ); + map.insert( 90.0, "E" ); + map.insert( 180.0, "S" ); + map.insert( 270.0, "W" ); + + compass->setScaleDraw( new QwtCompassScaleDraw( map ) ); + + QwtSimpleCompassRose *rose = new QwtSimpleCompassRose( 4, 1 ); + compass->setRose( rose ); + + compass->setNeedle( + new QwtCompassWindArrow( QwtCompassWindArrow::Style2 ) ); + compass->setValue( 60.0 ); + break; + } + case 2: + { + /* + A compass with a rotating needle in darkBlue. Shows + a ticks for each degree. + */ + + palette0.setColor( QPalette::Base, Qt::darkBlue ); + palette0.setColor( QPalette::WindowText, + QColor( Qt::darkBlue ).dark( 120 ) ); + palette0.setColor( QPalette::Text, Qt::white ); + + QwtCompassScaleDraw *scaleDraw = new QwtCompassScaleDraw(); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Ticks, true ); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Labels, true ); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Backbone, false ); + scaleDraw->setTickLength( QwtScaleDiv::MinorTick, 1 ); + scaleDraw->setTickLength( QwtScaleDiv::MediumTick, 1 ); + scaleDraw->setTickLength( QwtScaleDiv::MajorTick, 3 ); + + compass->setScaleDraw( scaleDraw ); + + compass->setScaleMaxMajor( 36 ); + compass->setScaleMaxMinor( 5 ); + + compass->setNeedle( + new QwtCompassMagnetNeedle( QwtCompassMagnetNeedle::ThinStyle ) ); + compass->setValue( 220.0 ); + + break; + } + case 3: + { + /* + A compass without a frame, showing numbers as tick labels. + The origin is at 220.0 + */ + palette0.setColor( QPalette::Base, + palette().color( backgroundRole() ) ); + palette0.setColor( QPalette::WindowText, Qt::blue ); + + compass->setLineWidth( 0 ); + + QMap map; + for ( double d = 0.0; d < 360.0; d += 60.0 ) + { + QString label; + label.sprintf( "%.0f", d ); + map.insert( d, label ); + } + + QwtCompassScaleDraw *scaleDraw = + new QwtCompassScaleDraw( map ); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Ticks, true ); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Labels, true ); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Backbone, true ); + scaleDraw->setTickLength( QwtScaleDiv::MinorTick, 0 ); + scaleDraw->setTickLength( QwtScaleDiv::MediumTick, 0 ); + scaleDraw->setTickLength( QwtScaleDiv::MajorTick, 3 ); + + compass->setScaleDraw( scaleDraw ); + + compass->setScaleMaxMajor( 36 ); + compass->setScaleMaxMinor( 5 ); + + compass->setNeedle( new QwtDialSimpleNeedle( QwtDialSimpleNeedle::Ray, + true, Qt::white ) ); + compass->setOrigin( 220.0 ); + compass->setValue( 20.0 ); + break; + } + case 4: + { + /* + A compass showing another needle + */ + QwtCompassScaleDraw *scaleDraw = new QwtCompassScaleDraw(); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Ticks, true ); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Labels, true ); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Backbone, false ); + scaleDraw->setTickLength( QwtScaleDiv::MinorTick, 0 ); + scaleDraw->setTickLength( QwtScaleDiv::MediumTick, 0 ); + scaleDraw->setTickLength( QwtScaleDiv::MajorTick, 3 ); + + compass->setScaleDraw( scaleDraw ); + + compass->setNeedle( new QwtCompassMagnetNeedle( + QwtCompassMagnetNeedle::TriangleStyle, Qt::white, Qt::red ) ); + compass->setValue( 220.0 ); + break; + } + case 5: + { + /* + A compass with a yellow on black ray + */ + palette0.setColor( QPalette::WindowText, Qt::black ); + + compass->setNeedle( new QwtDialSimpleNeedle( QwtDialSimpleNeedle::Ray, + false, Qt::yellow ) ); + compass->setValue( 315.0 ); + break; + } + } + + QPalette newPalette = compass->palette(); + for ( c = 0; c < QPalette::NColorRoles; c++ ) + { + const QPalette::ColorRole colorRole = + static_cast( c ); + + if ( palette0.color( colorRole ).isValid() ) + newPalette.setColor( colorRole, palette0.color( colorRole ) ); + } + + for ( int i = 0; i < QPalette::NColorGroups; i++ ) + { + const QPalette::ColorGroup colorGroup = + static_cast( i ); + + const QColor light = + newPalette.color( colorGroup, QPalette::Base ).light( 170 ); + const QColor dark = newPalette.color( colorGroup, QPalette::Base ).dark( 170 ); + const QColor mid = compass->frameShadow() == QwtDial::Raised + ? newPalette.color( colorGroup, QPalette::Base ).dark( 110 ) + : newPalette.color( colorGroup, QPalette::Base ).light( 110 ); + + newPalette.setColor( colorGroup, QPalette::Dark, dark ); + newPalette.setColor( colorGroup, QPalette::Mid, mid ); + newPalette.setColor( colorGroup, QPalette::Light, light ); + } + + compass->setPalette( newPalette ); + + return compass; +} diff --git a/qwt/examples/dials/compass_grid.h b/qwt/examples/dials/compass_grid.h new file mode 100644 index 000000000..386927009 --- /dev/null +++ b/qwt/examples/dials/compass_grid.h @@ -0,0 +1,11 @@ +#include +class QwtCompass; + +class CompassGrid: public QFrame +{ +public: + CompassGrid( QWidget *parent = NULL ); + +private: + QwtCompass *createCompass( int pos ); +}; diff --git a/qwt/examples/dials/dials.cpp b/qwt/examples/dials/dials.cpp new file mode 100644 index 000000000..e34a5828d --- /dev/null +++ b/qwt/examples/dials/dials.cpp @@ -0,0 +1,24 @@ +#include +#include +#include "compass_grid.h" +#include "cockpit_grid.h" + +//----------------------------------------------------------------- +// +// dials.cpp -- A demo program featuring QwtDial and friends +// +//----------------------------------------------------------------- + +int main ( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + QTabWidget tabWidget; + tabWidget.addTab( new CompassGrid, "Compass" ); + tabWidget.addTab( new CockpitGrid, "Cockpit" ); + + tabWidget.show(); + + return a.exec(); +} + diff --git a/qwt/examples/dials/dials.pro b/qwt/examples/dials/dials.pro new file mode 100644 index 000000000..11db8664e --- /dev/null +++ b/qwt/examples/dials/dials.pro @@ -0,0 +1,26 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = dials + +HEADERS = \ + attitude_indicator.h \ + speedo_meter.h \ + cockpit_grid.h \ + compass_grid.h + +SOURCES = \ + attitude_indicator.cpp \ + speedo_meter.cpp \ + cockpit_grid.cpp \ + compass_grid.cpp \ + dials.cpp + diff --git a/qwt/examples/dials/speedo_meter.cpp b/qwt/examples/dials/speedo_meter.cpp new file mode 100644 index 000000000..3205d4cc8 --- /dev/null +++ b/qwt/examples/dials/speedo_meter.cpp @@ -0,0 +1,52 @@ +#include +#include +#include +#include "speedo_meter.h" + +SpeedoMeter::SpeedoMeter( QWidget *parent ): + QwtDial( parent ), + d_label( "km/h" ) +{ + QwtRoundScaleDraw *scaleDraw = new QwtRoundScaleDraw(); + scaleDraw->setSpacing( 8 ); + scaleDraw->enableComponent( QwtAbstractScaleDraw::Backbone, false ); + scaleDraw->setTickLength( QwtScaleDiv::MinorTick, 0 ); + scaleDraw->setTickLength( QwtScaleDiv::MediumTick, 4 ); + scaleDraw->setTickLength( QwtScaleDiv::MajorTick, 8 ); + setScaleDraw( scaleDraw ); + + setWrapping( false ); + setReadOnly( true ); + + setOrigin( 135.0 ); + setScaleArc( 0.0, 270.0 ); + + QwtDialSimpleNeedle *needle = new QwtDialSimpleNeedle( + QwtDialSimpleNeedle::Arrow, true, Qt::red, + QColor( Qt::gray ).light( 130 ) ); + setNeedle( needle ); +} + +void SpeedoMeter::setLabel( const QString &label ) +{ + d_label = label; + update(); +} + +QString SpeedoMeter::label() const +{ + return d_label; +} + +void SpeedoMeter::drawScaleContents( QPainter *painter, + const QPointF ¢er, double radius ) const +{ + QRectF rect( 0.0, 0.0, 2.0 * radius, 2.0 * radius - 10.0 ); + rect.moveCenter( center ); + + const QColor color = palette().color( QPalette::Text ); + painter->setPen( color ); + + const int flags = Qt::AlignBottom | Qt::AlignHCenter; + painter->drawText( rect, flags, d_label ); +} diff --git a/qwt/examples/dials/speedo_meter.h b/qwt/examples/dials/speedo_meter.h new file mode 100644 index 000000000..3f49e28cb --- /dev/null +++ b/qwt/examples/dials/speedo_meter.h @@ -0,0 +1,18 @@ +#include +#include + +class SpeedoMeter: public QwtDial +{ +public: + SpeedoMeter( QWidget *parent = NULL ); + + void setLabel( const QString & ); + QString label() const; + +protected: + virtual void drawScaleContents( QPainter *painter, + const QPointF ¢er, double radius ) const; + +private: + QString d_label; +}; diff --git a/qwt/examples/distrowatch/barchart.cpp b/qwt/examples/distrowatch/barchart.cpp new file mode 100644 index 000000000..c8a3c4fcb --- /dev/null +++ b/qwt/examples/distrowatch/barchart.cpp @@ -0,0 +1,192 @@ +#include "barchart.h" +#include +#include +#include +#include +#include +#include +#include + +class DistroScaleDraw: public QwtScaleDraw +{ +public: + DistroScaleDraw( Qt::Orientation orientation, const QStringList &labels ): + d_labels( labels ) + { + setTickLength( QwtScaleDiv::MinorTick, 0 ); + setTickLength( QwtScaleDiv::MediumTick, 0 ); + setTickLength( QwtScaleDiv::MajorTick, 2 ); + + enableComponent( QwtScaleDraw::Backbone, false ); + + if ( orientation == Qt::Vertical ) + { + setLabelRotation( -60.0 ); + } + else + { + setLabelRotation( -20.0 ); + } + + setLabelAlignment( Qt::AlignLeft | Qt::AlignVCenter ); + } + + virtual QwtText label( double value ) const + { + QwtText lbl; + + const int index = qRound( value ); + if ( index >= 0 && index <= d_labels.size() ) + { + lbl = d_labels[ index ]; + } + + return lbl; + } + +private: + const QStringList d_labels; +}; + +class DistroChartItem: public QwtPlotBarChart +{ +public: + DistroChartItem(): + QwtPlotBarChart( "Page Hits" ) + { + setLegendMode( QwtPlotBarChart::LegendBarTitles ); + setLegendIconSize( QSize( 10, 14 ) ); + } + + void addDistro( const QString &distro, const QColor &color ) + { + d_colors += color; + d_distros += distro; + itemChanged(); + } + + virtual QwtColumnSymbol *specialSymbol( + int index, const QPointF& ) const + { + // we want to have individual colors for each bar + + QwtColumnSymbol *symbol = new QwtColumnSymbol( QwtColumnSymbol::Box ); + symbol->setLineWidth( 2 ); + symbol->setFrameStyle( QwtColumnSymbol::Raised ); + + QColor c( Qt::white ); + if ( index >= 0 && index < d_colors.size() ) + c = d_colors[ index ]; + + symbol->setPalette( c ); + + return symbol; + } + + virtual QwtText barTitle( int sampleIndex ) const + { + QwtText title; + if ( sampleIndex >= 0 && sampleIndex < d_distros.size() ) + title = d_distros[ sampleIndex ]; + + return title; + } + +private: + QList d_colors; + QList d_distros; +}; + +BarChart::BarChart( QWidget *parent ): + QwtPlot( parent ) +{ + const struct + { + const char *distro; + const int hits; + QColor color; + + } pageHits[] = + { + { "Arch", 1114, QColor( "DodgerBlue" ) }, + { "Debian", 1373, QColor( "#d70751" ) }, + { "Fedora", 1638, QColor( "SteelBlue" ) }, + { "Mageia", 1395, QColor( "Indigo" ) }, + { "Mint", 3874, QColor( 183, 255, 183 ) }, + { "openSuSE", 1532, QColor( 115, 186, 37 ) }, + { "Puppy", 1059, QColor( "LightSkyBlue" ) }, + { "Ubuntu", 2391, QColor( "FireBrick" ) } + }; + + setAutoFillBackground( true ); + setPalette( QColor( "Linen" ) ); + + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setLineWidth( 2 ); + canvas->setFrameStyle( QFrame::Box | QFrame::Sunken ); + canvas->setBorderRadius( 10 ); + + QPalette canvasPalette( QColor( "Plum" ) ); + canvasPalette.setColor( QPalette::Foreground, QColor( "Indigo" ) ); + canvas->setPalette( canvasPalette ); + + setCanvas( canvas ); + + setTitle( "DistroWatch Page Hit Ranking, April 2012" ); + + d_barChartItem = new DistroChartItem(); + + QVector< double > samples; + + for ( uint i = 0; i < sizeof( pageHits ) / sizeof( pageHits[ 0 ] ); i++ ) + { + d_distros += pageHits[ i ].distro; + samples += pageHits[ i ].hits; + + d_barChartItem->addDistro( + pageHits[ i ].distro, pageHits[ i ].color ); + } + + d_barChartItem->setSamples( samples ); + + d_barChartItem->attach( this ); + + insertLegend( new QwtLegend() ); + + setOrientation( 0 ); + setAutoReplot( false ); +} + +void BarChart::setOrientation( int o ) +{ + const Qt::Orientation orientation = + ( o == 0 ) ? Qt::Vertical : Qt::Horizontal; + + int axis1 = QwtAxis::xBottom; + int axis2 = QwtAxis::yLeft; + + if ( orientation == Qt::Horizontal ) + qSwap( axis1, axis2 ); + + d_barChartItem->setOrientation( orientation ); + + setAxisTitle( axis1, "Distros" ); + setAxisMaxMinor( axis1, 3 ); + setAxisScaleDraw( axis1, new DistroScaleDraw( orientation, d_distros ) ); + + setAxisTitle( axis2, "Hits per day ( HPD )" ); + setAxisMaxMinor( axis2, 3 ); + + QwtScaleDraw *scaleDraw = new QwtScaleDraw(); + scaleDraw->setTickLength( QwtScaleDiv::MediumTick, 4 ); + setAxisScaleDraw( axis2, scaleDraw ); + + plotLayout()->setCanvasMargin( 0 ); + replot(); +} + +void BarChart::exportChart() +{ + QwtPlotRenderer renderer; + renderer.exportTo( this, "distrowatch.pdf" ); +} diff --git a/qwt/examples/distrowatch/barchart.h b/qwt/examples/distrowatch/barchart.h new file mode 100644 index 000000000..33afa4028 --- /dev/null +++ b/qwt/examples/distrowatch/barchart.h @@ -0,0 +1,26 @@ +#ifndef _BAR_CHART_H_ + +#include +#include + +class DistroChartItem; + +class BarChart: public QwtPlot +{ + Q_OBJECT + +public: + BarChart( QWidget * = NULL ); + +public Q_SLOTS: + void setOrientation( int ); + void exportChart(); + +private: + void populate(); + + DistroChartItem *d_barChartItem; + QStringList d_distros; +}; + +#endif diff --git a/qwt/examples/distrowatch/distrowatch.pro b/qwt/examples/distrowatch/distrowatch.pro new file mode 100644 index 000000000..f24102387 --- /dev/null +++ b/qwt/examples/distrowatch/distrowatch.pro @@ -0,0 +1,19 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = distrowatch + +HEADERS = \ + barchart.h + +SOURCES = \ + barchart.cpp \ + main.cpp diff --git a/qwt/examples/distrowatch/main.cpp b/qwt/examples/distrowatch/main.cpp new file mode 100644 index 000000000..88d4e4809 --- /dev/null +++ b/qwt/examples/distrowatch/main.cpp @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include +#include "barchart.h" + +class MainWindow: public QMainWindow +{ +public: + MainWindow( QWidget * = NULL ); + +private: + BarChart *d_chart; +}; + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + d_chart = new BarChart( this ); + setCentralWidget( d_chart ); + + QToolBar *toolBar = new QToolBar( this ); + + QComboBox *orientationBox = new QComboBox( toolBar ); + orientationBox->addItem( "Vertical" ); + orientationBox->addItem( "Horizontal" ); + orientationBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + + QToolButton *btnExport = new QToolButton( toolBar ); + btnExport->setText( "Export" ); + btnExport->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + connect( btnExport, SIGNAL( clicked() ), d_chart, SLOT( exportChart() ) ); + + toolBar->addWidget( orientationBox ); + toolBar->addWidget( btnExport ); + addToolBar( toolBar ); + + d_chart->setOrientation( orientationBox->currentIndex() ); + connect( orientationBox, SIGNAL( currentIndexChanged( int ) ), + d_chart, SLOT( setOrientation( int ) ) ); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow mainWindow; + + mainWindow.resize( 600, 400 ); + mainWindow.show(); + + return a.exec(); +} diff --git a/qwt/examples/event_filter/README b/qwt/examples/event_filter/README new file mode 100644 index 000000000..05fc24205 --- /dev/null +++ b/qwt/examples/event_filter/README @@ -0,0 +1,27 @@ +QwtPlot is a composite widget consisting of a title label, +the canvas, the scales and a legend. Although all components +should be exchangable some day, the current design isn´t ready for it. + +In this situation event filtering is the mechanism to extend the behaviour +of the plot components. event_filter shows 3 examples how to use it: + +1) CanvasPicker + +The CanvasPicker implements a solution, how to move points on the canvas +with mouse and keyboard. + +2) ScalePicker + +The ScalePicker translates the position of mouse clicks on the scales +and emits them as signals. + +3) Plot: ColorBar, QSlider + +The Plot class shows how to add widgets to the scales. In this example +there is no filter class. The derived plot widget filters its components. + + +Please note that CanvasPicker and ScalePicker are standalone classes +that could be connected with your QwtPlot as well. + +Uwe diff --git a/qwt/examples/event_filter/canvaspicker.cpp b/qwt/examples/event_filter/canvaspicker.cpp new file mode 100644 index 000000000..f8bb9fcca --- /dev/null +++ b/qwt/examples/event_filter/canvaspicker.cpp @@ -0,0 +1,372 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "canvaspicker.h" + +CanvasPicker::CanvasPicker( QwtPlot *plot ): + QObject( plot ), + d_selectedCurve( NULL ), + d_selectedPoint( -1 ) +{ + QwtPlotCanvas *canvas = qobject_cast( plot->canvas() ); + canvas->installEventFilter( this ); + + // We want the focus, but no focus rect. The + // selected point will be highlighted instead. + + canvas->setFocusPolicy( Qt::StrongFocus ); +#ifndef QT_NO_CURSOR + canvas->setCursor( Qt::PointingHandCursor ); +#endif + canvas->setFocusIndicator( QwtPlotCanvas::ItemFocusIndicator ); + canvas->setFocus(); + + const char *text = + "All points can be moved using the left mouse button " + "or with these keys:\n\n" + "- Up:\t\tSelect next curve\n" + "- Down:\t\tSelect previous curve\n" + "- Left, ´-´:\tSelect next point\n" + "- Right, ´+´:\tSelect previous point\n" + "- 7, 8, 9, 4, 6, 1, 2, 3:\tMove selected point"; + canvas->setWhatsThis( text ); + + shiftCurveCursor( true ); +} + +QwtPlot *CanvasPicker::plot() +{ + return qobject_cast( parent() ); +} + +const QwtPlot *CanvasPicker::plot() const +{ + return qobject_cast( parent() ); +} + +bool CanvasPicker::event( QEvent *ev ) +{ + if ( ev->type() == QEvent::User ) + { + showCursor( true ); + return true; + } + return QObject::event( ev ); +} + +bool CanvasPicker::eventFilter( QObject *object, QEvent *event ) +{ + if ( plot() == NULL || object != plot()->canvas() ) + return false; + + switch( event->type() ) + { + case QEvent::FocusIn: + { + showCursor( true ); + break; + } + case QEvent::FocusOut: + { + showCursor( false ); + break; + } + case QEvent::Paint: + { + QApplication::postEvent( this, new QEvent( QEvent::User ) ); + break; + } + case QEvent::MouseButtonPress: + { + const QMouseEvent *mouseEvent = static_cast( event ); + select( mouseEvent->pos() ); + return true; + } + case QEvent::MouseMove: + { + const QMouseEvent *mouseEvent = static_cast( event ); + move( mouseEvent->pos() ); + return true; + } + case QEvent::KeyPress: + { + const QKeyEvent *keyEvent = static_cast( event ); + + const int delta = 5; + switch( keyEvent->key() ) + { + case Qt::Key_Up: + { + shiftCurveCursor( true ); + return true; + } + case Qt::Key_Down: + { + shiftCurveCursor( false ); + return true; + } + case Qt::Key_Right: + case Qt::Key_Plus: + { + if ( d_selectedCurve ) + shiftPointCursor( true ); + else + shiftCurveCursor( true ); + return true; + } + case Qt::Key_Left: + case Qt::Key_Minus: + { + if ( d_selectedCurve ) + shiftPointCursor( false ); + else + shiftCurveCursor( true ); + return true; + } + + // The following keys represent a direction, they are + // organized on the keyboard. + + case Qt::Key_1: + { + moveBy( -delta, delta ); + break; + } + case Qt::Key_2: + { + moveBy( 0, delta ); + break; + } + case Qt::Key_3: + { + moveBy( delta, delta ); + break; + } + case Qt::Key_4: + { + moveBy( -delta, 0 ); + break; + } + case Qt::Key_6: + { + moveBy( delta, 0 ); + break; + } + case Qt::Key_7: + { + moveBy( -delta, -delta ); + break; + } + case Qt::Key_8: + { + moveBy( 0, -delta ); + break; + } + case Qt::Key_9: + { + moveBy( delta, -delta ); + break; + } + default: + break; + } + } + default: + break; + } + + return QObject::eventFilter( object, event ); +} + +// Select the point at a position. If there is no point +// deselect the selected point + +void CanvasPicker::select( const QPoint &pos ) +{ + QwtPlotCurve *curve = NULL; + double dist = 10e10; + int index = -1; + + const QwtPlotItemList& itmList = plot()->itemList(); + for ( QwtPlotItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + if ( ( *it )->rtti() == QwtPlotItem::Rtti_PlotCurve ) + { + QwtPlotCurve *c = static_cast( *it ); + + double d; + int idx = c->closestPoint( pos, &d ); + if ( d < dist ) + { + curve = c; + index = idx; + dist = d; + } + } + } + + showCursor( false ); + d_selectedCurve = NULL; + d_selectedPoint = -1; + + if ( curve && dist < 10 ) // 10 pixels tolerance + { + d_selectedCurve = curve; + d_selectedPoint = index; + showCursor( true ); + } +} + +// Move the selected point +void CanvasPicker::moveBy( int dx, int dy ) +{ + if ( dx == 0 && dy == 0 ) + return; + + if ( !d_selectedCurve ) + return; + + const QPointF sample = + d_selectedCurve->sample( d_selectedPoint ); + + const double x = plot()->transform( + d_selectedCurve->xAxis(), sample.x() ); + const double y = plot()->transform( + d_selectedCurve->yAxis(), sample.y() ); + + move( QPoint( qRound( x + dx ), qRound( y + dy ) ) ); +} + +// Move the selected point +void CanvasPicker::move( const QPoint &pos ) +{ + if ( !d_selectedCurve ) + return; + + QVector xData( d_selectedCurve->dataSize() ); + QVector yData( d_selectedCurve->dataSize() ); + + for ( int i = 0; + i < static_cast( d_selectedCurve->dataSize() ); i++ ) + { + if ( i == d_selectedPoint ) + { + xData[i] = plot()->invTransform( + d_selectedCurve->xAxis(), pos.x() ); + yData[i] = plot()->invTransform( + d_selectedCurve->yAxis(), pos.y() ); + } + else + { + const QPointF sample = d_selectedCurve->sample( i ); + xData[i] = sample.x(); + yData[i] = sample.y(); + } + } + d_selectedCurve->setSamples( xData, yData ); + + /* + Enable QwtPlotCanvas::ImmediatePaint, so that the canvas has been + updated before we paint the cursor on it. + */ + QwtPlotCanvas *plotCanvas = + qobject_cast( plot()->canvas() ); + + plotCanvas->setPaintAttribute( QwtPlotCanvas::ImmediatePaint, true ); + plot()->replot(); + plotCanvas->setPaintAttribute( QwtPlotCanvas::ImmediatePaint, false ); + + showCursor( true ); +} + +// Hightlight the selected point +void CanvasPicker::showCursor( bool showIt ) +{ + if ( !d_selectedCurve ) + return; + + QwtSymbol *symbol = const_cast( d_selectedCurve->symbol() ); + + const QBrush brush = symbol->brush(); + if ( showIt ) + symbol->setBrush( symbol->brush().color().dark( 180 ) ); + + QwtPlotDirectPainter directPainter; + directPainter.drawSeries( d_selectedCurve, d_selectedPoint, d_selectedPoint ); + + if ( showIt ) + symbol->setBrush( brush ); // reset brush +} + +// Select the next/previous curve +void CanvasPicker::shiftCurveCursor( bool up ) +{ + QwtPlotItemIterator it; + + const QwtPlotItemList &itemList = plot()->itemList(); + + QwtPlotItemList curveList; + for ( it = itemList.begin(); it != itemList.end(); ++it ) + { + if ( ( *it )->rtti() == QwtPlotItem::Rtti_PlotCurve ) + curveList += *it; + } + if ( curveList.isEmpty() ) + return; + + it = curveList.begin(); + + if ( d_selectedCurve ) + { + for ( it = curveList.begin(); it != curveList.end(); ++it ) + { + if ( d_selectedCurve == *it ) + break; + } + if ( it == curveList.end() ) // not found + it = curveList.begin(); + + if ( up ) + { + ++it; + if ( it == curveList.end() ) + it = curveList.begin(); + } + else + { + if ( it == curveList.begin() ) + it = curveList.end(); + --it; + } + } + + showCursor( false ); + d_selectedPoint = 0; + d_selectedCurve = static_cast( *it ); + showCursor( true ); +} + +// Select the next/previous neighbour of the selected point +void CanvasPicker::shiftPointCursor( bool up ) +{ + if ( !d_selectedCurve ) + return; + + int index = d_selectedPoint + ( up ? 1 : -1 ); + index = ( index + d_selectedCurve->dataSize() ) % d_selectedCurve->dataSize(); + + if ( index != d_selectedPoint ) + { + showCursor( false ); + d_selectedPoint = index; + showCursor( true ); + } +} diff --git a/qwt/examples/event_filter/canvaspicker.h b/qwt/examples/event_filter/canvaspicker.h new file mode 100644 index 000000000..c5b0f2f05 --- /dev/null +++ b/qwt/examples/event_filter/canvaspicker.h @@ -0,0 +1,33 @@ +#include + +class QPoint; +class QCustomEvent; +class QwtPlot; +class QwtPlotCurve; + +class CanvasPicker: public QObject +{ + Q_OBJECT +public: + CanvasPicker( QwtPlot *plot ); + virtual bool eventFilter( QObject *, QEvent * ); + + virtual bool event( QEvent * ); + +private: + void select( const QPoint & ); + void move( const QPoint & ); + void moveBy( int dx, int dy ); + + void release(); + + void showCursor( bool enable ); + void shiftPointCursor( bool up ); + void shiftCurveCursor( bool up ); + + QwtPlot *plot(); + const QwtPlot *plot() const; + + QwtPlotCurve *d_selectedCurve; + int d_selectedPoint; +}; diff --git a/qwt/examples/event_filter/colorbar.cpp b/qwt/examples/event_filter/colorbar.cpp new file mode 100644 index 000000000..113377928 --- /dev/null +++ b/qwt/examples/event_filter/colorbar.cpp @@ -0,0 +1,112 @@ +#include +#include +#include +#include +#include "colorbar.h" + +ColorBar::ColorBar( Qt::Orientation o, QWidget *parent ): + QWidget( parent ), + d_orientation( o ), + d_light( Qt::white ), + d_dark( Qt::black ) +{ +#ifndef QT_NO_CURSOR + setCursor( Qt::PointingHandCursor ); +#endif +} + +void ColorBar::setOrientation( Qt::Orientation o ) +{ + d_orientation = o; + update(); +} + +void ColorBar::setLight( const QColor &light ) +{ + d_light = light; + update(); +} + +void ColorBar::setDark( const QColor &dark ) +{ + d_dark = dark; + update(); +} + +void ColorBar::setRange( const QColor &light, const QColor &dark ) +{ + d_light = light; + d_dark = dark; + update(); +} + +void ColorBar::mousePressEvent( QMouseEvent *e ) +{ + if( e->button() == Qt::LeftButton ) + { + // emit the color of the position where the mouse click + // happened + + const QPixmap pm = QPixmap::grabWidget( this ); + const QRgb rgb = pm.toImage().pixel( e->x(), e->y() ); + + Q_EMIT selected( QColor( rgb ) ); + e->accept(); + } +} + +void ColorBar::paintEvent( QPaintEvent * ) +{ + QPainter painter( this ); + drawColorBar( &painter, rect() ); +} + +void ColorBar::drawColorBar( QPainter *painter, const QRect &rect ) const +{ + int h1, s1, v1; + int h2, s2, v2; + + d_light.getHsv( &h1, &s1, &v1 ); + d_dark.getHsv( &h2, &s2, &v2 ); + + painter->save(); + painter->setClipRect( rect ); + painter->setClipping( true ); + + painter->fillRect( rect, d_dark ); + + const int sectionSize = 2; + + int numIntervals; + if ( d_orientation == Qt::Horizontal ) + numIntervals = rect.width() / sectionSize; + else + numIntervals = rect.height() / sectionSize; + + for ( int i = 0; i < numIntervals; i++ ) + { + QRect section; + if ( d_orientation == Qt::Horizontal ) + { + section.setRect( rect.x() + i * sectionSize, rect.y(), + sectionSize, rect.height() ); + } + else + { + section.setRect( rect.x(), rect.y() + i * sectionSize, + rect.width(), sectionSize ); + } + + const double ratio = i / static_cast( numIntervals ); + + QColor c; + c.setHsv( h1 + qRound( ratio * ( h2 - h1 ) ), + s1 + qRound( ratio * ( s2 - s1 ) ), + v1 + qRound( ratio * ( v2 - v1 ) ) ); + + painter->fillRect( section, c ); + } + + painter->restore(); +} + diff --git a/qwt/examples/event_filter/colorbar.h b/qwt/examples/event_filter/colorbar.h new file mode 100644 index 000000000..757581323 --- /dev/null +++ b/qwt/examples/event_filter/colorbar.h @@ -0,0 +1,33 @@ +#include + +class ColorBar: public QWidget +{ + Q_OBJECT + +public: + ColorBar( Qt::Orientation = Qt::Horizontal, QWidget * = NULL ); + + virtual void setOrientation( Qt::Orientation ); + Qt::Orientation orientation() const { return d_orientation; } + + void setRange( const QColor &light, const QColor &dark ); + void setLight( const QColor &light ); + void setDark( const QColor &dark ); + + QColor light() const { return d_light; } + QColor dark() const { return d_dark; } + +Q_SIGNALS: + void selected( const QColor & ); + +protected: + virtual void mousePressEvent( QMouseEvent * ); + virtual void paintEvent( QPaintEvent * ); + + void drawColorBar( QPainter *, const QRect & ) const; + +private: + Qt::Orientation d_orientation; + QColor d_light; + QColor d_dark; +}; diff --git a/qwt/examples/event_filter/event_filter.cpp b/qwt/examples/event_filter/event_filter.cpp new file mode 100644 index 000000000..efa8ba3cd --- /dev/null +++ b/qwt/examples/event_filter/event_filter.cpp @@ -0,0 +1,51 @@ +//----------------------------------------------------------------- +// A demo program showing how to use event filtering +//----------------------------------------------------------------- + +#include +#include +#include +#include +#include +#include "plot.h" +#include "canvaspicker.h" +#include "scalepicker.h" + +int main ( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + QMainWindow mainWindow; + QToolBar *toolBar = new QToolBar( &mainWindow ); + QAction *action = QWhatsThis::createAction( toolBar ); + toolBar->addAction( action ); + mainWindow.addToolBar( toolBar ); + + Plot *plot = new Plot( &mainWindow ); + + // The canvas picker handles all mouse and key + // events on the plot canvas + + ( void ) new CanvasPicker( plot ); + + // The scale picker translates mouse clicks + // int o clicked() signals + + ScalePicker *scalePicker = new ScalePicker( plot ); + a.connect( scalePicker, SIGNAL( clicked( int, double ) ), + plot, SLOT( insertCurve( int, double ) ) ); + + mainWindow.setCentralWidget( plot ); + + mainWindow.resize( 540, 400 ); + mainWindow.show(); + + const char *text = + "An useless plot to demonstrate how to use event filtering.\n\n" + "You can click on the color bar, the scales or move the wheel.\n" + "All points can be moved using the mouse or the keyboard."; + plot->setWhatsThis( text ); + + int rv = a.exec(); + return rv; +} diff --git a/qwt/examples/event_filter/event_filter.pro b/qwt/examples/event_filter/event_filter.pro new file mode 100644 index 000000000..c50daa895 --- /dev/null +++ b/qwt/examples/event_filter/event_filter.pro @@ -0,0 +1,25 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = event_filter + +HEADERS = \ + colorbar.h \ + scalepicker.h \ + canvaspicker.h \ + plot.h + +SOURCES = \ + colorbar.cpp \ + scalepicker.cpp \ + canvaspicker.cpp \ + plot.cpp \ + event_filter.cpp diff --git a/qwt/examples/event_filter/plot.cpp b/qwt/examples/event_filter/plot.cpp new file mode 100644 index 000000000..bfc9ece96 --- /dev/null +++ b/qwt/examples/event_filter/plot.cpp @@ -0,0 +1,173 @@ +#include "plot.h" +#include "colorbar.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + setTitle( "Interactive Plot" ); + + setCanvasColor( Qt::darkCyan ); + + QwtPlotGrid *grid = new QwtPlotGrid; + grid->setMajorPen( Qt::white, 0, Qt::DotLine ); + grid->attach( this ); + + // axes + + setAxisScale( QwtAxis::xBottom, 0.0, 100.0 ); + setAxisScale( QwtAxis::yLeft, 0.0, 100.0 ); + + // Avoid jumping when label with 3 digits + // appear/disappear when scrolling vertically + + QwtScaleDraw *sd = axisScaleDraw( QwtAxis::yLeft ); + sd->setMinimumExtent( sd->extent( axisWidget( QwtAxis::yLeft )->font() ) ); + + plotLayout()->setAlignCanvasToScales( true ); + + insertCurve( Qt::Vertical, Qt::blue, 30.0 ); + insertCurve( Qt::Vertical, Qt::magenta, 70.0 ); + insertCurve( Qt::Horizontal, Qt::yellow, 30.0 ); + insertCurve( Qt::Horizontal, Qt::white, 70.0 ); + + replot(); + + // ------------------------------------ + // We add a color bar to the left axis + // ------------------------------------ + + QwtScaleWidget *scaleWidget = axisWidget( QwtAxis::yLeft ); + scaleWidget->setMargin( 10 ); // area for the color bar + d_colorBar = new ColorBar( Qt::Vertical, scaleWidget ); + d_colorBar->setRange( Qt::red, Qt::darkBlue ); + d_colorBar->setFocusPolicy( Qt::TabFocus ); + + connect( d_colorBar, SIGNAL( selected( const QColor & ) ), + SLOT( setCanvasColor( const QColor & ) ) ); + + // we need the resize events, to lay out the color bar + scaleWidget->installEventFilter( this ); + + // ------------------------------------ + // We add a wheel to the canvas + // ------------------------------------ + + d_wheel = new QwtWheel( canvas() ); + d_wheel->setOrientation( Qt::Vertical ); + d_wheel->setRange( -100, 100 ); + d_wheel->setValue( 0.0 ); + d_wheel->setMass( 0.2 ); + d_wheel->setTotalAngle( 4 * 360.0 ); + + connect( d_wheel, SIGNAL( valueChanged( double ) ), + SLOT( scrollLeftAxis( double ) ) ); + + // we need the resize events, to lay out the wheel + canvas()->installEventFilter( this ); + + d_colorBar->setWhatsThis( + "Selecting a color will change the background of the plot." ); + scaleWidget->setWhatsThis( + "Selecting a value at the scale will insert a new curve." ); + d_wheel->setWhatsThis( + "With the wheel you can move the visible area." ); + axisWidget( QwtAxis::xBottom )->setWhatsThis( + "Selecting a value at the scale will insert a new curve." ); +} + +void Plot::setCanvasColor( const QColor &c ) +{ + setCanvasBackground( c ); + replot(); +} + +void Plot::scrollLeftAxis( double value ) +{ + setAxisScale( QwtAxis::yLeft, value, value + 100.0 ); + replot(); +} + +bool Plot::eventFilter( QObject *object, QEvent *e ) +{ + if ( e->type() == QEvent::Resize ) + { + const QSize size = static_cast( e )->size(); + if ( object == axisWidget( QwtAxis::yLeft ) ) + { + const QwtScaleWidget *scaleWidget = axisWidget( QwtAxis::yLeft ); + + const int margin = 2; + + // adjust the color bar to the scale backbone + const int x = size.width() - scaleWidget->margin() + margin; + const int w = scaleWidget->margin() - 2 * margin; + const int y = scaleWidget->startBorderDist(); + const int h = size.height() - + scaleWidget->startBorderDist() - scaleWidget->endBorderDist(); + + d_colorBar->setGeometry( x, y, w, h ); + } + if ( object == canvas() ) + { + const int w = 16; + const int h = 50; + const int margin = 2; + + const QRect cr = canvas()->contentsRect(); + d_wheel->setGeometry( + cr.right() - margin - w, cr.center().y() - h / 2, w, h ); + } + } + + return QwtPlot::eventFilter( object, e ); +} + +void Plot::insertCurve( int axis, double base ) +{ + const Qt::Orientation o = + QwtAxis::isYAxis( axis ) ? Qt::Horizontal : Qt::Vertical; + + QRgb rgb = static_cast( rand() ); + insertCurve( o, QColor( rgb ), base ); + replot(); +} + +void Plot::insertCurve( Qt::Orientation o, + const QColor &c, double base ) +{ + QwtPlotCurve *curve = new QwtPlotCurve(); + + curve->setPen( c ); + curve->setSymbol( new QwtSymbol( QwtSymbol::Ellipse, + Qt::gray, c, QSize( 8, 8 ) ) ); + + double x[10]; + double y[sizeof( x ) / sizeof( x[0] )]; + + for ( uint i = 0; i < sizeof( x ) / sizeof( x[0] ); i++ ) + { + double v = 5.0 + i * 10.0; + if ( o == Qt::Horizontal ) + { + x[i] = v; + y[i] = base; + } + else + { + x[i] = base; + y[i] = v; + } + } + + curve->setSamples( x, y, sizeof( x ) / sizeof( x[0] ) ); + curve->attach( this ); +} diff --git a/qwt/examples/event_filter/plot.h b/qwt/examples/event_filter/plot.h new file mode 100644 index 000000000..8f0798c65 --- /dev/null +++ b/qwt/examples/event_filter/plot.h @@ -0,0 +1,25 @@ +#include + +class ColorBar; +class QwtWheel; + +class Plot: public QwtPlot +{ + Q_OBJECT +public: + Plot( QWidget *parent = NULL ); + virtual bool eventFilter( QObject *, QEvent * ); + +public Q_SLOTS: + void setCanvasColor( const QColor & ); + void insertCurve( int axis, double base ); + +private Q_SLOTS: + void scrollLeftAxis( double ); + +private: + void insertCurve( Qt::Orientation, const QColor &, double base ); + + ColorBar *d_colorBar; + QwtWheel *d_wheel; +}; diff --git a/qwt/examples/event_filter/scalepicker.cpp b/qwt/examples/event_filter/scalepicker.cpp new file mode 100644 index 000000000..ee273a3f6 --- /dev/null +++ b/qwt/examples/event_filter/scalepicker.cpp @@ -0,0 +1,119 @@ +#include "scalepicker.h" +#include +#include +#include +#include + +ScalePicker::ScalePicker( QwtPlot *plot ): + QObject( plot ) +{ + for ( uint i = 0; i < QwtAxis::PosCount; i++ ) + { + QwtScaleWidget *scaleWidget = plot->axisWidget( i ); + if ( scaleWidget ) + scaleWidget->installEventFilter( this ); + } +} + +bool ScalePicker::eventFilter( QObject *object, QEvent *event ) +{ + if ( event->type() == QEvent::MouseButtonPress ) + { + QwtScaleWidget *scaleWidget = qobject_cast( object ); + if ( scaleWidget ) + { + QMouseEvent *mouseEvent = static_cast( event ); + mouseClicked( scaleWidget, mouseEvent->pos() ); + + return true; + } + } + + return QObject::eventFilter( object, event ); +} + +void ScalePicker::mouseClicked( const QwtScaleWidget *scale, const QPoint &pos ) +{ + QRect rect = scaleRect( scale ); + + int margin = 10; // 10 pixels tolerance + rect.setRect( rect.x() - margin, rect.y() - margin, + rect.width() + 2 * margin, rect.height() + 2 * margin ); + + if ( rect.contains( pos ) ) // No click on the title + { + // translate the position in a value on the scale + + double value = 0.0; + int axis = -1; + + const QwtScaleDraw *sd = scale->scaleDraw(); + switch( scale->alignment() ) + { + case QwtScaleDraw::LeftScale: + { + value = sd->scaleMap().invTransform( pos.y() ); + axis = QwtAxis::yLeft; + break; + } + case QwtScaleDraw::RightScale: + { + value = sd->scaleMap().invTransform( pos.y() ); + axis = QwtAxis::yRight; + break; + } + case QwtScaleDraw::BottomScale: + { + value = sd->scaleMap().invTransform( pos.x() ); + axis = QwtAxis::xBottom; + break; + } + case QwtScaleDraw::TopScale: + { + value = sd->scaleMap().invTransform( pos.x() ); + axis = QwtAxis::xTop; + break; + } + } + Q_EMIT clicked( axis, value ); + } +} + +// The rect of a scale without the title +QRect ScalePicker::scaleRect( const QwtScaleWidget *scale ) const +{ + const int bld = scale->margin(); + const int mjt = qCeil( scale->scaleDraw()->maxTickLength() ); + const int sbd = scale->startBorderDist(); + const int ebd = scale->endBorderDist(); + + QRect rect; + switch( scale->alignment() ) + { + case QwtScaleDraw::LeftScale: + { + rect.setRect( scale->width() - bld - mjt, sbd, + mjt, scale->height() - sbd - ebd ); + break; + } + case QwtScaleDraw::RightScale: + { + rect.setRect( bld, sbd, + mjt, scale->height() - sbd - ebd ); + break; + } + case QwtScaleDraw::BottomScale: + { + rect.setRect( sbd, bld, + scale->width() - sbd - ebd, mjt ); + break; + } + case QwtScaleDraw::TopScale: + { + rect.setRect( sbd, scale->height() - bld - mjt, + scale->width() - sbd - ebd, mjt ); + break; + } + } + return rect; +} diff --git a/qwt/examples/event_filter/scalepicker.h b/qwt/examples/event_filter/scalepicker.h new file mode 100644 index 000000000..bcdc12d9e --- /dev/null +++ b/qwt/examples/event_filter/scalepicker.h @@ -0,0 +1,20 @@ +#include +#include + +class QwtPlot; +class QwtScaleWidget; + +class ScalePicker: public QObject +{ + Q_OBJECT +public: + ScalePicker( QwtPlot *plot ); + virtual bool eventFilter( QObject *, QEvent * ); + +Q_SIGNALS: + void clicked( int axis, double value ); + +private: + void mouseClicked( const QwtScaleWidget *, const QPoint & ); + QRect scaleRect( const QwtScaleWidget * ) const; +}; diff --git a/qwt/examples/examples.pri b/qwt/examples/examples.pri new file mode 100644 index 000000000..33c7be821 --- /dev/null +++ b/qwt/examples/examples.pri @@ -0,0 +1,77 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################### + +QWT_ROOT = $${PWD}/.. +include( $${QWT_ROOT}/qwtconfig.pri ) +include( $${QWT_ROOT}/qwtbuild.pri ) +include( $${QWT_ROOT}/qwtfunctions.pri ) + +TEMPLATE = app + +INCLUDEPATH += $${QWT_ROOT}/src +DEPENDPATH += $${QWT_ROOT}/src + +!debug_and_release { + + DESTDIR = $${QWT_ROOT}/examples/bin +} +else { + CONFIG(debug, debug|release) { + + DESTDIR = $${QWT_ROOT}/examples/bin_debug + } + else { + + DESTDIR = $${QWT_ROOT}/examples/bin + } +} + +QMAKE_RPATHDIR *= $${QWT_ROOT}/lib + +contains(QWT_CONFIG, QwtFramework) { + + LIBS += -F$${QWT_ROOT}/lib +} +else { + + LIBS += -L$${QWT_ROOT}/lib +} + +qwtAddLibrary(qwt) + +greaterThan(QT_MAJOR_VERSION, 4) { + + QT += printsupport + QT += concurrent +} + +contains(QWT_CONFIG, QwtOpenGL ) { + + QT += opengl +} +else { + + DEFINES += QWT_NO_OPENGL +} + +contains(QWT_CONFIG, QwtSvg) { + + QT += svg +} +else { + + DEFINES += QWT_NO_SVG +} + + +win32 { + contains(QWT_CONFIG, QwtDll) { + DEFINES += QT_DLL QWT_DLL + } +} diff --git a/qwt/examples/examples.pro b/qwt/examples/examples.pro new file mode 100644 index 000000000..1f0ea4d9d --- /dev/null +++ b/qwt/examples/examples.pro @@ -0,0 +1,51 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../qwtconfig.pri ) + +TEMPLATE = subdirs + +contains(QWT_CONFIG, QwtPlot) { + + SUBDIRS += \ + animation \ + barchart \ + cpuplot \ + curvdemo1 \ + distrowatch \ + friedberg \ + itemeditor \ + legends \ + stockchart \ + simpleplot \ + sinusplot \ + realtime \ + refreshtest \ + scatterplot \ + spectrogram \ + rasterview \ + tvplot + + contains(QWT_CONFIG, QwtWidgets) { + + SUBDIRS += \ + bode \ + event_filter \ + oscilloscope + } +} + +contains(QWT_CONFIG, QwtWidgets) { + + SUBDIRS += \ + sysinfo \ + radio \ + dials \ + controls +} diff --git a/qwt/examples/friedberg/friedberg.pro b/qwt/examples/friedberg/friedberg.pro new file mode 100644 index 000000000..3a15c5b6b --- /dev/null +++ b/qwt/examples/friedberg/friedberg.pro @@ -0,0 +1,21 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = friedberg + +HEADERS = \ + plot.h \ + friedberg2007.h + +SOURCES = \ + friedberg2007.cpp \ + plot.cpp \ + main.cpp diff --git a/qwt/examples/friedberg/friedberg2007.cpp b/qwt/examples/friedberg/friedberg2007.cpp new file mode 100644 index 000000000..19f688e9e --- /dev/null +++ b/qwt/examples/friedberg/friedberg2007.cpp @@ -0,0 +1,384 @@ +#include "friedberg2007.h" + +// Temperature 2007 from Friedberg somewhere in Germany +// See: http://wetter61169.de + +Temperature friedberg2007[] = +{ + /* 01.01 */ Temperature( 2.6, 9.8, 7.07862 ), + /* 02.01 */ Temperature( 0.8, 5.8, 3.6993 ), + /* 03.01 */ Temperature( 2, 7, 5.02388 ), + /* 04.01 */ Temperature( 5.3, 7.8, 6.37778 ), + /* 05.01 */ Temperature( 5.6, 7.7, 6.83149 ), + /* 06.01 */ Temperature( 7.2, 8.9, 8.0816 ), + /* 07.01 */ Temperature( 4.2, 9.9, 7.54704 ), + /* 08.01 */ Temperature( 3.5, 8.9, 6.71951 ), + /* 09.01 */ Temperature( 8.2, 12.9, 10.8594 ), + /* 10.01 */ Temperature( 6.3, 11.9, 9.76424 ), + /* 11.01 */ Temperature( 3.9, 9.2, 6.18223 ), + /* 12.01 */ Temperature( 6.9, 9.7, 8.44236 ), + /* 13.01 */ Temperature( 9, 12.3, 10.6649 ), + /* 14.01 */ Temperature( 1.8, 10.8, 7.23438 ), + /* 15.01 */ Temperature( -2.8, 1.8, -0.518403 ), + /* 16.01 */ Temperature( -0.6, 4.5, 2.39479 ), + /* 17.01 */ Temperature( 4.3, 10.2, 7.23472 ), + /* 18.01 */ Temperature( 9.1, 13.6, 10.9316 ), + /* 19.01 */ Temperature( 6.9, 12.4, 9.4128 ), + /* 20.01 */ Temperature( 7.1, 13.3, 10.5083 ), + /* 21.01 */ Temperature( 3.5, 9.6, 6.10871 ), + /* 22.01 */ Temperature( -1.8, 6, 2.89028 ), + /* 23.01 */ Temperature( -5.4, 1.7, -2.46678 ), + /* 24.01 */ Temperature( -5.3, -1.3, -3.71483 ), + /* 25.01 */ Temperature( -7.5, 3.3, -3.36736 ), + /* 26.01 */ Temperature( -11.1, 0.3, -5.50662 ), + /* 27.01 */ Temperature( 0.2, 3.2, 1.95345 ), + /* 28.01 */ Temperature( 1.9, 5.2, 3.43633 ), + /* 29.01 */ Temperature( 4.4, 9.1, 6.24236 ), + /* 30.01 */ Temperature( 2.3, 11.5, 6.03114 ), + /* 31.01 */ Temperature( 4.6, 10.2, 6.04192 ), + + /* 01.02 */ Temperature( 4.8, 13.8, 7.87674 ), + /* 02.02 */ Temperature( 5.7, 10, 7.28646 ), + /* 03.02 */ Temperature( 2.9, 8.2, 5.71771 ), + /* 04.02 */ Temperature( -1.5, 7.2, 4.71319 ), + /* 05.02 */ Temperature( -2.6, 4.4, 1.23542 ), + /* 06.02 */ Temperature( 0.3, 9.2, 2.59965 ), + /* 07.02 */ Temperature( -0.4, 2.4, 0.641667 ), + /* 08.02 */ Temperature( -1.7, 3.8, 0.811458 ), + /* 09.02 */ Temperature( 0.7, 7, 3.58328 ), + /* 10.02 */ Temperature( 1, 6, 3.51181 ), + /* 11.02 */ Temperature( 4.7, 9.6, 6.14913 ), + /* 12.02 */ Temperature( 5.3, 8.7, 6.80552 ), + /* 13.02 */ Temperature( 4.4, 10.3, 6.84552 ), + /* 14.02 */ Temperature( 2.6, 6.5, 4.58681 ), + /* 15.02 */ Temperature( -0.8, 13.4, 6.38542 ), + /* 16.02 */ Temperature( -3, 14.4, 4.11336 ), + /* 17.02 */ Temperature( 0.5, 13, 5.87457 ), + /* 18.02 */ Temperature( -2.2, 14.1, 4.36528 ), + /* 19.02 */ Temperature( 3.9, 5.6, 4.63737 ), + /* 20.02 */ Temperature( -0.4, 9.2, 4.37014 ), + /* 21.02 */ Temperature( -1.9, 5.5, 1.85675 ), + /* 22.02 */ Temperature( 1, 13.1, 5.41176 ), + /* 23.02 */ Temperature( 1.9, 13.9, 7.74251 ), + /* 24.02 */ Temperature( 3.8, 9.6, 7.19306 ), + /* 25.02 */ Temperature( 5.8, 10.8, 7.80312 ), + /* 26.02 */ Temperature( 5.2, 10.4, 6.79481 ), + /* 27.02 */ Temperature( 3.2, 7.4, 5.22986 ), + /* 28.02 */ Temperature( 6.4, 13.4, 9.13356 ), + + /* 01.03 */ Temperature( 4.6, 11.4, 7.70554 ), + /* 02.03 */ Temperature( 3.4, 10.9, 5.98408 ), + /* 03.03 */ Temperature( 2.9, 10.5, 5.45675 ), + /* 04.03 */ Temperature( -0.7, 16.8, 7.29585 ), + /* 05.03 */ Temperature( 4.2, 13.4, 8.35862 ), + /* 06.03 */ Temperature( 3, 13, 7.76644 ), + /* 07.03 */ Temperature( 2, 13.3, 8.24618 ), + /* 08.03 */ Temperature( -0.8, 15, 6.11765 ), + /* 09.03 */ Temperature( -0.7, 11, 5.7568 ), + /* 10.03 */ Temperature( 1.2, 14.4, 6.61389 ), + /* 11.03 */ Temperature( -1.7, 18, 6.66146 ), + /* 12.03 */ Temperature( -0.6, 21.9, 8.9816 ), + /* 13.03 */ Temperature( -0.9, 19.6, 9.08299 ), + /* 14.03 */ Temperature( 5.3, 18.9, 10.5562 ), + /* 15.03 */ Temperature( 2, 20.5, 9.65156 ), + /* 16.03 */ Temperature( 0.2, 16.7, 7.8699 ), + /* 17.03 */ Temperature( 4.5, 10.6, 7.87535 ), + /* 18.03 */ Temperature( 2.7, 9.7, 6.71806 ), + /* 19.03 */ Temperature( 0.4, 10.9, 3.92404 ), + /* 20.03 */ Temperature( -2, 12.7, 4.01359 ), + /* 21.03 */ Temperature( 0.3, 6.8, 3.00382 ), + /* 22.03 */ Temperature( 0.9, 4.2, 2.2816 ), + /* 23.03 */ Temperature( 2, 5.7, 3.39233 ), + /* 24.03 */ Temperature( 3.9, 9.3, 6.41076 ), + /* 25.03 */ Temperature( 4.2, 19.1, 9.92182 ), + /* 26.03 */ Temperature( 2.3, 22, 12.5716 ), + /* 27.03 */ Temperature( 4.9, 20.6, 13.4568 ), + /* 28.03 */ Temperature( 0.3, 22.8, 10.755 ), + /* 29.03 */ Temperature( 1.8, 17.2, 9.43924 ), + /* 30.03 */ Temperature( 1.9, 19.8, 10.25 ), + /* 31.03 */ Temperature( 6.7, 17, 11.1324 ), + + /* 01.04 */ Temperature( 5.7, 22, 12.8457 ), + /* 02.04 */ Temperature( 6.4, 22.1, 13.3847 ), + /* 03.04 */ Temperature( 5.8, 17.5, 10.5614 ), + /* 04.04 */ Temperature( 2.8, 16.2, 8.06574 ), + /* 05.04 */ Temperature( -0.6, 20.8, 9.18062 ), + /* 06.04 */ Temperature( 2.1, 24, 13.0069 ), + /* 07.04 */ Temperature( 5.3, 16.2, 10.2771 ), + /* 08.04 */ Temperature( 0.1, 20.7, 9.79861 ), + /* 09.04 */ Temperature( 0.3, 18.9, 10.0087 ), + /* 10.04 */ Temperature( 4, 16.4, 11.4208 ), + /* 11.04 */ Temperature( 2.3, 23.4, 13.083 ), + /* 12.04 */ Temperature( 7, 29.4, 16.5826 ), + /* 13.04 */ Temperature( 10.6, 31.5, 19.2249 ), + /* 14.04 */ Temperature( 11.8, 34, 21.441 ), + /* 15.04 */ Temperature( 11.6, 33.8, 21.0201 ), + /* 16.04 */ Temperature( 8.7, 31.1, 18.7885 ), + /* 17.04 */ Temperature( 5.5, 27.2, 16.1432 ), + /* 18.04 */ Temperature( 6.1, 17.2, 10.6688 ), + /* 19.04 */ Temperature( -0.6, 21.3, 10.4806 ), + /* 20.04 */ Temperature( 5.9, 21.6, 12.6257 ), + /* 21.04 */ Temperature( 2.1, 21.6, 11.0858 ), + /* 22.04 */ Temperature( 3.9, 25.9, 14.2108 ), + /* 23.04 */ Temperature( 3.1, 27.8, 15.7111 ), + /* 24.04 */ Temperature( 13.7, 29, 19.6397 ), + /* 25.04 */ Temperature( 9.8, 31.6, 19.601 ), + /* 26.04 */ Temperature( 8.2, 32.4, 20.0389 ), + /* 27.04 */ Temperature( 11.8, 32.1, 21.0726 ), + /* 28.04 */ Temperature( 12.6, 33.3, 21.6993 ), + /* 29.04 */ Temperature( 10.5, 27.4, 19.1206 ), + /* 30.04 */ Temperature( 5.3, 26.4, 15.0972 ), + + /* 01.05 */ Temperature( 6.9, 25.3, 15.2802 ), + /* 02.05 */ Temperature( 4.3, 26.2, 14.8401 ), + /* 03.05 */ Temperature( 7.1, 28.5, 17.2145 ), + /* 04.05 */ Temperature( 11, 28.5, 18.537 ), + /* 05.05 */ Temperature( 12, 28, 18.1672 ), + /* 06.05 */ Temperature( 10.4, 29, 18.3844 ), + /* 07.05 */ Temperature( 13, 18.1, 15.0028 ), + /* 08.05 */ Temperature( 10.7, 18.3, 13.2014 ), + /* 09.05 */ Temperature( 10.8, 14.4, 12.5208 ), + /* 10.05 */ Temperature( 11.9, 23.5, 16.9632 ), + /* 11.05 */ Temperature( 9.8, 16.9, 15.0795 ), + /* 12.05 */ Temperature( 9.2, 19.6, 13.8521 ), + /* 13.05 */ Temperature( 8.9, 26.3, 16.2028 ), + /* 14.05 */ Temperature( 11.1, 17.5, 13.2934 ), + /* 15.05 */ Temperature( 6.5, 17, 11.7743 ), + /* 16.05 */ Temperature( 4.9, 13.6, 9.75625 ), + /* 17.05 */ Temperature( 6.8, 16.6, 9.96701 ), + /* 18.05 */ Temperature( 2.4, 21.2, 11.4311 ), + /* 19.05 */ Temperature( 8.2, 24.4, 15.4188 ), + /* 20.05 */ Temperature( 14.1, 31.7, 21.3303 ), + /* 21.05 */ Temperature( 11, 30.9, 21.5359 ), + /* 22.05 */ Temperature( 13.8, 31, 21.5177 ), + /* 23.05 */ Temperature( 16, 27.8, 21.0271 ), + /* 24.05 */ Temperature( 15, 34, 23.4142 ), + /* 25.05 */ Temperature( 14.3, 31.8, 22.8903 ), + /* 26.05 */ Temperature( 13.6, 33.1, 22.6156 ), + /* 27.05 */ Temperature( 11.2, 23.4, 16.6192 ), + /* 28.05 */ Temperature( 9.6, 13.1, 11.3222 ), + /* 29.05 */ Temperature( 8.3, 11.2, 10.3529 ), + /* 30.05 */ Temperature( 4.2, 20.8, 12.6218 ), + /* 31.05 */ Temperature( 9.2, 23.6, 15.1073 ), + + /* 01.06 */ Temperature( 10.8, 24.4, 16.3205 ), + /* 02.06 */ Temperature( 13, 26.5, 18.9649 ), + /* 03.06 */ Temperature( 14, 25.1, 18.5398 ), + /* 04.06 */ Temperature( 13, 28, 20.2139 ), + /* 05.06 */ Temperature( 14, 28.8, 20.438 ), + /* 06.06 */ Temperature( 14, 30.4, 21.7821 ), + /* 07.06 */ Temperature( 17, 34.8, 25.3087 ), + /* 08.06 */ Temperature( 17.9, 35.7, 25.7872 ), + /* 09.06 */ Temperature( 17.8, 31.6, 22.0788 ), + /* 10.06 */ Temperature( 15.5, 33.4, 22.4458 ), + /* 11.06 */ Temperature( 16.6, 28.3, 19.8797 ), + /* 12.06 */ Temperature( 14, 27.3, 20.2566 ), + /* 13.06 */ Temperature( 13.2, 28.2, 19.4233 ), + /* 14.06 */ Temperature( 12.7, 30, 20.1427 ), + /* 15.06 */ Temperature( 15.2, 22.6, 18.5917 ), + /* 16.06 */ Temperature( 13.2, 24, 17.7014 ), + /* 17.06 */ Temperature( 11.7, 27.9, 19.8229 ), + /* 18.06 */ Temperature( 15.9, 27.2, 20.3358 ), + /* 19.06 */ Temperature( 12.6, 33.7, 22.2427 ), + /* 20.06 */ Temperature( 15.7, 30.8, 23.7507 ), + /* 21.06 */ Temperature( 14.8, 22.6, 18.2538 ), + /* 22.06 */ Temperature( 12.4, 21.3, 15.9969 ), + /* 23.06 */ Temperature( 12.6, 21.6, 15.8149 ), + /* 24.06 */ Temperature( 13, 26, 18.4176 ), + /* 25.06 */ Temperature( 12.9, 24.4, 17.1299 ), + /* 26.06 */ Temperature( 10.8, 18.8, 13.2913 ), + /* 27.06 */ Temperature( 9.9, 18.8, 13.5465 ), + /* 28.06 */ Temperature( 12, 19.8, 14.8434 ), + /* 29.06 */ Temperature( 12, 19, 15.155 ), + /* 30.06 */ Temperature( 12.4, 22.4, 17.1354 ), + + /* 01.07 */ Temperature( 12.1, 24.9, 19.1639 ), + /* 02.07 */ Temperature( 15.7, 24.3, 18.4554 ), + /* 03.07 */ Temperature( 12.7, 17.2, 14.6564 ), + /* 04.07 */ Temperature( 11.2, 19, 13.9529 ), + /* 05.07 */ Temperature( 11.5, 19, 14.6422 ), + /* 06.07 */ Temperature( 12.4, 22, 16.6146 ), + /* 07.07 */ Temperature( 11.6, 24, 17.666 ), + /* 08.07 */ Temperature( 9, 28, 19.1351 ), + /* 09.07 */ Temperature( 11.3, 21.5, 16.5271 ), + /* 10.07 */ Temperature( 11.3, 20.2, 14.2326 ), + /* 11.07 */ Temperature( 10.2, 19.2, 14.0649 ), + /* 12.07 */ Temperature( 13.2, 23.1, 16.6346 ), + /* 13.07 */ Temperature( 15, 27, 19.6844 ), + /* 14.07 */ Temperature( 13.4, 32.4, 23.845 ), + /* 15.07 */ Temperature( 15, 38.2, 26.8559 ), + /* 16.07 */ Temperature( 16.1, 36.5, 26.4483 ), + /* 17.07 */ Temperature( 19.7, 30.5, 24.189 ), + /* 18.07 */ Temperature( 14.2, 29.3, 22.1363 ), + /* 19.07 */ Temperature( 16.4, 25.9, 19.0819 ), + /* 20.07 */ Temperature( 16.2, 30.8, 22.151 ), + /* 21.07 */ Temperature( 14, 24.3, 18.6573 ), + /* 22.07 */ Temperature( 13.2, 24.5, 18.3301 ), + /* 23.07 */ Temperature( 10.6, 23.4, 16.6903 ), + /* 24.07 */ Temperature( 13.2, 20.8, 16.2743 ), + /* 25.07 */ Temperature( 12.2, 25.8, 18.8267 ), + /* 26.07 */ Temperature( 11.9, 28.9, 20.5522 ), + /* 27.07 */ Temperature( 17.6, 25.8, 21.5691 ), + /* 28.07 */ Temperature( 16.6, 24.6, 19.2295 ), + /* 29.07 */ Temperature( 13, 19, 15.9021 ), + /* 30.07 */ Temperature( 9.6, 19.7, 13.875 ), + /* 31.07 */ Temperature( 8, 22, 14.5284 ), + + /* 01.08 */ Temperature( 7.6, 27.5, 17.5684 ), + /* 02.08 */ Temperature( 9.2, 22.2, 16.1035 ), + /* 03.08 */ Temperature( 12.7, 25.3, 18.2958 ), + /* 04.08 */ Temperature( 8.6, 31.3, 19.7941 ), + /* 05.08 */ Temperature( 10.3, 32.7, 21.492 ), + /* 06.08 */ Temperature( 10, 33.4, 22.4431 ), + /* 07.08 */ Temperature( 16.8, 22.6, 19.5583 ), + /* 08.08 */ Temperature( 13.5, 16.7, 15.0264 ), + /* 09.08 */ Temperature( 13.2, 18.8, 15.6003 ), + /* 10.08 */ Temperature( 14.6, 27.9, 18.8292 ), + /* 11.08 */ Temperature( 16.3, 26.4, 20.3837 ), + /* 12.08 */ Temperature( 12.1, 28.7, 19.9892 ), + /* 13.08 */ Temperature( 15, 27.4, 19.7542 ), + /* 14.08 */ Temperature( 11.3, 28.3, 20.5656 ), + /* 15.08 */ Temperature( 18.6, 28.4, 23.1215 ), + /* 16.08 */ Temperature( 16, 23.6, 19.491 ), + /* 17.08 */ Temperature( 12.6, 22, 17.0437 ), + /* 18.08 */ Temperature( 8.5, 25.7, 16.5589 ), + /* 19.08 */ Temperature( 13.4, 25.8, 18.0543 ), + /* 20.08 */ Temperature( 10.9, 21.5, 16.1306 ), + /* 21.08 */ Temperature( 10.6, 19.2, 14.6177 ), + /* 22.08 */ Temperature( 14, 24.6, 17.3841 ), + /* 23.08 */ Temperature( 13.8, 30.4, 20.6125 ), + /* 24.08 */ Temperature( 12.3, 30.3, 20.7622 ), + /* 25.08 */ Temperature( 12.8, 30.2, 21.6736 ), + /* 26.08 */ Temperature( 15, 29.3, 21.266 ), + /* 27.08 */ Temperature( 12.9, 25.9, 18.791 ), + /* 28.08 */ Temperature( 9.3, 24.6, 16.2833 ), + /* 29.08 */ Temperature( 10.8, 25, 16.8459 ), + /* 30.08 */ Temperature( 8.2, 24.4, 15.9267 ), + /* 31.08 */ Temperature( 14.1, 20.5, 16.6128 ), + + /* 01.09 */ Temperature( 13.4, 21.9, 16.2205 ), + /* 02.09 */ Temperature( 12, 20.7, 16.0882 ), + /* 03.09 */ Temperature( 10.8, 21.3, 14.7913 ), + /* 04.09 */ Temperature( 7.8, 18.2, 12.2747 ), + /* 05.09 */ Temperature( 8.1, 22.2, 12.9406 ), + /* 06.09 */ Temperature( 10, 23.8, 13.8785 ), + /* 07.09 */ Temperature( 10.7, 21.2, 15.4823 ), + /* 08.09 */ Temperature( 12.4, 21, 15.8194 ), + /* 09.09 */ Temperature( 12.7, 16.9, 14.7212 ), + /* 10.09 */ Temperature( 10.3, 17.7, 12.9271 ), + /* 11.09 */ Temperature( 10.6, 20.8, 14.4788 ), + /* 12.09 */ Temperature( 10.8, 21.9, 15.0184 ), + /* 13.09 */ Temperature( 6.9, 24.6, 14.5222 ), + /* 14.09 */ Temperature( 8.1, 24, 15.6583 ), + /* 15.09 */ Temperature( 8.8, 22.8, 15.941 ), + /* 16.09 */ Temperature( 3.1, 24.5, 14.1486 ), + /* 17.09 */ Temperature( 12.4, 21.2, 16.0497 ), + /* 18.09 */ Temperature( 7.8, 16.1, 12.024 ), + /* 19.09 */ Temperature( 5.3, 18.1, 10.3003 ), + /* 20.09 */ Temperature( 6.4, 20.3, 12.3177 ), + /* 21.09 */ Temperature( 6, 23.8, 13.6247 ), + /* 22.09 */ Temperature( 5.7, 27, 14.6847 ), + /* 23.09 */ Temperature( 7.8, 28, 16.6238 ), + /* 24.09 */ Temperature( 9.6, 24.9, 16.7191 ), + /* 25.09 */ Temperature( 8.4, 17.6, 12.636 ), + /* 26.09 */ Temperature( 4.3, 18.9, 10.0809 ), + /* 27.09 */ Temperature( 9.4, 11.2, 10.3344 ), + /* 28.09 */ Temperature( 7.7, 12.6, 10.5337 ), + /* 29.09 */ Temperature( 9.8, 15.3, 11.9306 ), + /* 30.09 */ Temperature( 9.6, 21.1, 13.6635 ), + + /* 01.10 */ Temperature( 8.9, 24.5, 14.8163 ), + /* 02.10 */ Temperature( 13.5, 20.2, 16.1628 ), + /* 03.10 */ Temperature( 12.5, 18, 15.4691 ), + /* 04.10 */ Temperature( 13.8, 25, 17.2073 ), + /* 05.10 */ Temperature( 9.1, 23.2, 14.6181 ), + /* 06.10 */ Temperature( 6.4, 23.4, 12.8625 ), + /* 07.10 */ Temperature( 4.6, 22.1, 11.0052 ), + /* 08.10 */ Temperature( 2, 22.2, 10.1677 ), + /* 09.10 */ Temperature( 7.8, 21.6, 12.2139 ), + /* 10.10 */ Temperature( 7.1, 22.7, 13.0115 ), + /* 11.10 */ Temperature( 6.1, 21.2, 11.4333 ), + /* 12.10 */ Temperature( 4.3, 15.2, 10.6104 ), + /* 13.10 */ Temperature( 5.8, 23, 12.8875 ), + /* 14.10 */ Temperature( 1, 23, 9.72986 ), + /* 15.10 */ Temperature( 1, 19.3, 9.33021 ), + /* 16.10 */ Temperature( 8.5, 20.4, 13.2639 ), + /* 17.10 */ Temperature( 6.8, 17.3, 11.8174 ), + /* 18.10 */ Temperature( 5.2, 15.6, 9.06076 ), + /* 19.10 */ Temperature( 2.7, 13.5, 7.1309 ), + /* 20.10 */ Temperature( -0.2, 15.8, 6.01667 ), + /* 21.10 */ Temperature( 2.6, 6.1, 4.9441 ), + /* 22.10 */ Temperature( -0.8, 13.2, 4.50694 ), + /* 23.10 */ Temperature( -0.4, 13.3, 4.71007 ), + /* 24.10 */ Temperature( 2.9, 8.1, 5.96979 ), + /* 25.10 */ Temperature( 6.3, 10.5, 8.01206 ), + /* 26.10 */ Temperature( 7, 10.8, 8.14965 ), + /* 27.10 */ Temperature( 6.6, 9.7, 7.7809 ), + /* 28.10 */ Temperature( 1.7, 10.8, 6.95728 ), + /* 29.10 */ Temperature( 2.2, 9.9, 6.62917 ), + /* 30.10 */ Temperature( 5.8, 15, 8.76181 ), + /* 31.10 */ Temperature( 0.7, 15, 6.01528 ), + + /* 01.11 */ Temperature( -0.2, 9.7, 3.75842 ), + /* 02.11 */ Temperature( 6.4, 9.6, 8.00138 ), + /* 03.11 */ Temperature( 8.7, 13.1, 10.5676 ), + /* 04.11 */ Temperature( 8, 11.8, 9.54306 ), + /* 05.11 */ Temperature( 5.8, 15.9, 8.52345 ), + /* 06.11 */ Temperature( 5.5, 10.8, 7.16493 ), + /* 07.11 */ Temperature( 5.5, 8.9, 7.30172 ), + /* 08.11 */ Temperature( 7, 11.7, 8.96701 ), + /* 09.11 */ Temperature( 2.5, 8.4, 4.86528 ), + /* 10.11 */ Temperature( 3.7, 9, 5.20828 ), + /* 11.11 */ Temperature( 2.8, 10.6, 6.80756 ), + /* 12.11 */ Temperature( 2.7, 9.5, 5.07647 ), + /* 13.11 */ Temperature( 0.1, 5.4, 3.3945 ), + /* 14.11 */ Temperature( -0.7, 7.9, 2.02234 ), + /* 15.11 */ Temperature( -1.8, 6.5, 1.07778 ), + /* 16.11 */ Temperature( -4.4, 5.1, -0.693772 ), + /* 17.11 */ Temperature( -0.3, 3.4, 1.33229 ), + /* 18.11 */ Temperature( -0.4, 4.3, 2.4622 ), + /* 19.11 */ Temperature( 1.8, 3.6, 2.78282 ), + /* 20.11 */ Temperature( 1.3, 5.6, 2.95979 ), + /* 21.11 */ Temperature( 1.6, 5.7, 3.62284 ), + /* 22.11 */ Temperature( 3.1, 7.3, 5.60277 ), + /* 23.11 */ Temperature( 4.2, 7.7, 6.28166 ), + /* 24.11 */ Temperature( -0.5, 11.5, 3.25931 ), + /* 25.11 */ Temperature( -1, 8.8, 2.86505 ), + /* 26.11 */ Temperature( 1.2, 6.8, 3.09414 ), + /* 27.11 */ Temperature( -0.8, 7.5, 3.17805 ), + /* 28.11 */ Temperature( -2.8, 3.1, -0.920139 ), + /* 29.11 */ Temperature( -2.6, 1.7, -0.491696 ), + /* 30.11 */ Temperature( 1.3, 6.5, 3.85 ), + + /* 01.12 */ Temperature( 4.1, 8.7, 5.88924 ), + /* 02.12 */ Temperature( 4.8, 9, 6.81667 ), + /* 03.12 */ Temperature( 3.5, 8.5, 6.23633 ), + /* 04.12 */ Temperature( 2.7, 6.6, 4.63045 ), + /* 05.12 */ Temperature( 4.3, 8.6, 6.85993 ), + /* 06.12 */ Temperature( 5.5, 9.3, 7.79201 ), + /* 07.12 */ Temperature( 3.1, 13.4, 8.79444 ), + /* 08.12 */ Temperature( 2.6, 6.3, 4.67093 ), + /* 09.12 */ Temperature( 3, 10.4, 5.75724 ), + /* 10.12 */ Temperature( 4.1, 6.8, 5.31834 ), + /* 11.12 */ Temperature( 4.1, 7.4, 5.28993 ), + /* 12.12 */ Temperature( 3.9, 6.4, 4.64479 ), + /* 13.12 */ Temperature( 1.7, 9.1, 4.15363 ), + /* 14.12 */ Temperature( 0.4, 1.8, 0.934602 ), + /* 15.12 */ Temperature( -4.5, 2.1, -1.17292 ), + /* 16.12 */ Temperature( -5, 4.8, -2.17431 ), + /* 17.12 */ Temperature( -5.6, 6.1, -1.35448 ), + /* 18.12 */ Temperature( -4.9, 6.4, -1.25502 ), + /* 19.12 */ Temperature( -4.4, 6.6, -1.02396 ), + /* 20.12 */ Temperature( -7.3, 5.2, -2.63854 ), + /* 21.12 */ Temperature( -8.5, 5.7, -3.58333 ), + /* 22.12 */ Temperature( -7.9, -5.3, -6.13438 ), + /* 23.12 */ Temperature( -6.1, -4.4, -5.23472 ), + /* 24.12 */ Temperature( -4.6, -3.3, -3.84291 ), + /* 25.12 */ Temperature( -4.9, -2.8, -3.9066 ), + /* 26.12 */ Temperature( -4.7, -1.9, -3.10379 ), + /* 27.12 */ Temperature( -1.9, -0.2, -0.679791 ), + /* 28.12 */ Temperature( -1.8, 0.5, -0.521875 ), + /* 29.12 */ Temperature( -2.2, 2.3, -0.430796 ), + /* 30.12 */ Temperature( 0.9, 5.2, 2.83437 ), + /* 31.12 */ Temperature( -1, 8.3, 2.27093 ) +}; diff --git a/qwt/examples/friedberg/friedberg2007.h b/qwt/examples/friedberg/friedberg2007.h new file mode 100644 index 000000000..691f10141 --- /dev/null +++ b/qwt/examples/friedberg/friedberg2007.h @@ -0,0 +1,28 @@ +#ifndef _FRIEDBERG_2007_H_ +#define _FRIEDBERG_2007_H_ + +class Temperature +{ +public: + Temperature(): + minValue( 0.0 ), + maxValue( 0.0 ), + averageValue( 0.0 ) + { + } + + Temperature( double min, double max, double average ): + minValue( min ), + maxValue( max ), + averageValue( average ) + { + } + + double minValue; + double maxValue; + double averageValue; +}; + +extern Temperature friedberg2007[]; + +#endif diff --git a/qwt/examples/friedberg/main.cpp b/qwt/examples/friedberg/main.cpp new file mode 100644 index 000000000..498c7144a --- /dev/null +++ b/qwt/examples/friedberg/main.cpp @@ -0,0 +1,55 @@ +#include +#include +#include +#include +#include +#include "plot.h" + +class MainWindow: public QMainWindow +{ +public: + MainWindow( QWidget * = NULL ); + +private: + Plot *d_plot; +}; + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + d_plot = new Plot( this ); + setCentralWidget( d_plot ); + + QToolBar *toolBar = new QToolBar( this ); + + QComboBox *typeBox = new QComboBox( toolBar ); + typeBox->addItem( "Bars" ); + typeBox->addItem( "Tube" ); + typeBox->setCurrentIndex( 1 ); + typeBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + + QToolButton *btnExport = new QToolButton( toolBar ); + btnExport->setText( "Export" ); + btnExport->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + connect( btnExport, SIGNAL( clicked() ), d_plot, SLOT( exportPlot() ) ); + + toolBar->addWidget( typeBox ); + toolBar->addWidget( btnExport ); + addToolBar( toolBar ); + + d_plot->setMode( typeBox->currentIndex() ); + connect( typeBox, SIGNAL( currentIndexChanged( int ) ), + d_plot, SLOT( setMode( int ) ) ); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow w; + w.setObjectName( "MainWindow" ); + w.resize( 600, 400 ); + w.show(); + + return a.exec(); +} diff --git a/qwt/examples/friedberg/plot.cpp b/qwt/examples/friedberg/plot.cpp new file mode 100644 index 000000000..331ba09bd --- /dev/null +++ b/qwt/examples/friedberg/plot.cpp @@ -0,0 +1,209 @@ +#include "plot.h" +#include "friedberg2007.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Grid: public QwtPlotGrid +{ +public: + Grid() + { + enableXMin( true ); + setMajorPen( Qt::white, 0, Qt::DotLine ); + setMinorPen( Qt::gray, 0, Qt::DotLine ); + } + + virtual void updateScaleDiv( const QwtScaleDiv &xScaleDiv, + const QwtScaleDiv &yScaleDiv ) + { + QwtScaleDiv scaleDiv( xScaleDiv.lowerBound(), + xScaleDiv.upperBound() ); + + scaleDiv.setTicks( QwtScaleDiv::MinorTick, + xScaleDiv.ticks( QwtScaleDiv::MinorTick ) ); + scaleDiv.setTicks( QwtScaleDiv::MajorTick, + xScaleDiv.ticks( QwtScaleDiv::MediumTick ) ); + + QwtPlotGrid::updateScaleDiv( scaleDiv, yScaleDiv ); + } +}; + +class YearScaleDraw: public QwtScaleDraw +{ +public: + YearScaleDraw() + { + setTickLength( QwtScaleDiv::MajorTick, 0 ); + setTickLength( QwtScaleDiv::MinorTick, 0 ); + setTickLength( QwtScaleDiv::MediumTick, 6 ); + + setLabelRotation( -60.0 ); + setLabelAlignment( Qt::AlignLeft | Qt::AlignVCenter ); + + setSpacing( 15 ); + } + + virtual QwtText label( double value ) const + { + return QDate::longMonthName( int( value / 30 ) + 1 ); + } +}; + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + setObjectName( "FriedbergPlot" ); + setTitle( "Temperature of Friedberg/Germany" ); + + setAxisTitle( QwtAxis::xBottom, "2007" ); + setAxisScaleDiv( QwtAxis::xBottom, yearScaleDiv() ); + setAxisScaleDraw( QwtAxis::xBottom, new YearScaleDraw() ); + + setAxisTitle( QwtAxis::yLeft, + QString( "Temperature [%1C]" ).arg( QChar( 0x00B0 ) ) ); + + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setPalette( Qt::darkGray ); + canvas->setBorderRadius( 10 ); + + setCanvas( canvas ); + + // grid + QwtPlotGrid *grid = new Grid; + grid->attach( this ); + + insertLegend( new QwtLegend(), QwtPlot::RightLegend ); + + const int numDays = 365; + QVector averageData( numDays ); + QVector rangeData( numDays ); + + for ( int i = 0; i < numDays; i++ ) + { + const Temperature &t = friedberg2007[i]; + averageData[i] = QPointF( double( i ), t.averageValue ); + rangeData[i] = QwtIntervalSample( double( i ), + QwtInterval( t.minValue, t.maxValue ) ); + } + + insertCurve( "Average", averageData, Qt::black ); + insertErrorBars( "Range", rangeData, Qt::blue ); + + // LeftButton for the zooming + // MidButton for the panning + // RightButton: zoom out by 1 + // Ctrl+RighButton: zoom out to full size + + QwtPlotZoomer* zoomer = new QwtPlotZoomer( canvas ); + zoomer->setRubberBandPen( QColor( Qt::black ) ); + zoomer->setTrackerPen( QColor( Qt::black ) ); + zoomer->setMousePattern( QwtEventPattern::MouseSelect2, + Qt::RightButton, Qt::ControlModifier ); + zoomer->setMousePattern( QwtEventPattern::MouseSelect3, + Qt::RightButton ); + + QwtPlotPanner *panner = new QwtPlotPanner( canvas ); + panner->setMouseButton( Qt::MidButton ); +} + +QwtScaleDiv Plot::yearScaleDiv() const +{ + const int days[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + + QList mediumTicks; + mediumTicks += 0.0; + for ( uint i = 0; i < sizeof( days ) / sizeof( days[0] ); i++ ) + mediumTicks += mediumTicks.last() + days[i]; + + QList minorTicks; + for ( int i = 1; i <= 365; i += 7 ) + minorTicks += i; + + QList majorTicks; + for ( int i = 0; i < 12; i++ ) + majorTicks += i * 30 + 15; + + QwtScaleDiv scaleDiv( mediumTicks.first(), mediumTicks.last() + 1, + minorTicks, mediumTicks, majorTicks ); + return scaleDiv; +} + +void Plot::insertCurve( const QString& title, + const QVector& samples, const QColor &color ) +{ + d_curve = new QwtPlotCurve( title ); + d_curve->setRenderHint( QwtPlotItem::RenderAntialiased ); + d_curve->setStyle( QwtPlotCurve::NoCurve ); + d_curve->setLegendAttribute( QwtPlotCurve::LegendShowSymbol ); + + QwtSymbol *symbol = new QwtSymbol( QwtSymbol::XCross ); + symbol->setSize( 4 ); + symbol->setPen( color ); + d_curve->setSymbol( symbol ); + + d_curve->setSamples( samples ); + d_curve->attach( this ); +} + +void Plot::insertErrorBars( + const QString &title, + const QVector& samples, + const QColor &color ) +{ + d_intervalCurve = new QwtPlotIntervalCurve( title ); + d_intervalCurve->setRenderHint( QwtPlotItem::RenderAntialiased ); + d_intervalCurve->setPen( Qt::white ); + + QColor bg( color ); + bg.setAlpha( 150 ); + d_intervalCurve->setBrush( QBrush( bg ) ); + d_intervalCurve->setStyle( QwtPlotIntervalCurve::Tube ); + + d_intervalCurve->setSamples( samples ); + d_intervalCurve->attach( this ); +} + +void Plot::setMode( int style ) +{ + if ( style == Tube ) + { + d_intervalCurve->setStyle( QwtPlotIntervalCurve::Tube ); + d_intervalCurve->setSymbol( NULL ); + d_intervalCurve->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + } + else + { + d_intervalCurve->setStyle( QwtPlotIntervalCurve::NoCurve ); + + QColor c( d_intervalCurve->brush().color().rgb() ); // skip alpha + + QwtIntervalSymbol *errorBar = + new QwtIntervalSymbol( QwtIntervalSymbol::Bar ); + errorBar->setWidth( 8 ); // should be something even + errorBar->setPen( c ); + + d_intervalCurve->setSymbol( errorBar ); + d_intervalCurve->setRenderHint( QwtPlotItem::RenderAntialiased, false ); + } + + replot(); +} + +void Plot::exportPlot() +{ + QwtPlotRenderer renderer; + renderer.exportTo( this, "friedberg.pdf" ); +} diff --git a/qwt/examples/friedberg/plot.h b/qwt/examples/friedberg/plot.h new file mode 100644 index 000000000..387655433 --- /dev/null +++ b/qwt/examples/friedberg/plot.h @@ -0,0 +1,43 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ + +#include +#include +#include + +class QwtPlotCurve; +class QwtPlotIntervalCurve; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + enum Mode + { + Bars, + Tube + }; + + Plot( QWidget * = NULL ); + +public Q_SLOTS: + void setMode( int ); + void exportPlot(); + +private: + void insertCurve( const QString &title, + const QVector &, const QColor & ); + + void insertErrorBars( const QString &title, + const QVector &, + const QColor &color ); + + + QwtScaleDiv yearScaleDiv() const; + + QwtPlotIntervalCurve *d_intervalCurve; + QwtPlotCurve *d_curve; +}; + +#endif diff --git a/qwt/examples/itemeditor/editor.cpp b/qwt/examples/itemeditor/editor.cpp new file mode 100644 index 000000000..236fb2a3f --- /dev/null +++ b/qwt/examples/itemeditor/editor.cpp @@ -0,0 +1,374 @@ +#include "editor.h" +#include +#include +#include +#include +#include + +class Overlay: public QwtWidgetOverlay +{ +public: + Overlay( QWidget *parent, Editor *editor ): + QwtWidgetOverlay( parent ), + d_editor( editor ) + { + switch( editor->mode() ) + { + case Editor::NoMask: + { + setMaskMode( QwtWidgetOverlay::NoMask ); + setRenderMode( QwtWidgetOverlay::AutoRenderMode ); + break; + } + case Editor::Mask: + { + setMaskMode( QwtWidgetOverlay::MaskHint ); + setRenderMode( QwtWidgetOverlay::AutoRenderMode ); + break; + } + case Editor::AlphaMask: + { + setMaskMode( QwtWidgetOverlay::AlphaMask ); + setRenderMode( QwtWidgetOverlay::AutoRenderMode ); + break; + } + case Editor::AlphaMaskRedraw: + { + setMaskMode( QwtWidgetOverlay::AlphaMask ); + setRenderMode( QwtWidgetOverlay::DrawOverlay ); + break; + } + case Editor::AlphaMaskCopyMask: + { + setMaskMode( QwtWidgetOverlay::AlphaMask ); + setRenderMode( QwtWidgetOverlay::CopyAlphaMask ); + break; + } + } + } + +protected: + virtual void drawOverlay( QPainter *painter ) const + { + d_editor->drawOverlay( painter ); + } + + virtual QRegion maskHint() const + { + return d_editor->maskHint(); + } + +private: + Editor *d_editor; +}; + +Editor::Editor( QwtPlot* plot ): + QObject( plot ), + d_isEnabled( false ), + d_overlay( NULL ), + d_mode( Mask ) +{ + setEnabled( true ); +} + + +Editor::~Editor() +{ + delete d_overlay; +} + +QwtPlot *Editor::plot() +{ + return qobject_cast( parent() ); +} + +const QwtPlot *Editor::plot() const +{ + return qobject_cast( parent() ); +} + +void Editor::setMode( Mode mode ) +{ + d_mode = mode; +} + +Editor::Mode Editor::mode() const +{ + return d_mode; +} + +void Editor::setEnabled( bool on ) +{ + if ( on == d_isEnabled ) + return; + + QwtPlot *plot = qobject_cast( parent() ); + if ( plot ) + { + d_isEnabled = on; + + if ( on ) + { + plot->canvas()->installEventFilter( this ); + } + else + { + plot->canvas()->removeEventFilter( this ); + + delete d_overlay; + d_overlay = NULL; + } + } +} + +bool Editor::isEnabled() const +{ + return d_isEnabled; +} + +bool Editor::eventFilter( QObject* object, QEvent* event ) +{ + QwtPlot *plot = qobject_cast( parent() ); + if ( plot && object == plot->canvas() ) + { + switch( event->type() ) + { + case QEvent::MouseButtonPress: + { + const QMouseEvent* mouseEvent = + dynamic_cast( event ); + + if ( d_overlay == NULL && + mouseEvent->button() == Qt::LeftButton ) + { + const bool accepted = pressed( mouseEvent->pos() ); + if ( accepted ) + { + d_overlay = new Overlay( plot->canvas(), this ); + + d_overlay->updateOverlay(); + d_overlay->show(); + } + } + + break; + } + case QEvent::MouseMove: + { + if ( d_overlay ) + { + const QMouseEvent* mouseEvent = + dynamic_cast< QMouseEvent* >( event ); + + const bool accepted = moved( mouseEvent->pos() ); + if ( accepted ) + d_overlay->updateOverlay(); + } + + break; + } + case QEvent::MouseButtonRelease: + { + const QMouseEvent* mouseEvent = + static_cast( event ); + + if ( d_overlay && mouseEvent->button() == Qt::LeftButton ) + { + released( mouseEvent->pos() ); + + delete d_overlay; + d_overlay = NULL; + } + + break; + } + default: + break; + } + + return false; + } + + return QObject::eventFilter( object, event ); +} + +bool Editor::pressed( const QPoint& pos ) +{ + d_editedItem = itemAt( pos ); + if ( d_editedItem ) + { + d_currentPos = pos; + setItemVisible( d_editedItem, false ); + + return true; + } + + return false; // don't accept the position +} + +bool Editor::moved( const QPoint& pos ) +{ + if ( plot() == NULL ) + return false; + + const QwtScaleMap xMap = plot()->canvasMap( d_editedItem->xAxis() ); + const QwtScaleMap yMap = plot()->canvasMap( d_editedItem->yAxis() ); + + const QPointF p1 = QwtScaleMap::invTransform( xMap, yMap, d_currentPos ); + const QPointF p2 = QwtScaleMap::invTransform( xMap, yMap, pos ); + +#if QT_VERSION >= 0x040600 + const QPainterPath shape = d_editedItem->shape().translated( p2 - p1 ); +#else + const double dx = p2.x() - p1.x(); + const double dy = p2.y() - p1.y(); + + QPainterPath shape = d_editedItem->shape(); + for ( int i = 0; i < shape.elementCount(); i++ ) + { + const QPainterPath::Element &el = shape.elementAt( i ); + shape.setElementPositionAt( i, el.x + dx, el.y + dy ); + } +#endif + + d_editedItem->setShape( shape ); + d_currentPos = pos; + + return true; +} + +void Editor::released( const QPoint& pos ) +{ + Q_UNUSED( pos ); + + if ( d_editedItem ) + { + raiseItem( d_editedItem ); + setItemVisible( d_editedItem, true ); + } +} + +QwtPlotShapeItem* Editor::itemAt( const QPoint& pos ) const +{ + const QwtPlot *plot = this->plot(); + if ( plot == NULL ) + return NULL; + + // translate pos into the plot coordinates + double coords[ QwtAxis::PosCount ]; + coords[ QwtAxis::xBottom ] = + plot->canvasMap( QwtAxis::xBottom ).invTransform( pos.x() ); + coords[ QwtAxis::xTop ] = + plot->canvasMap( QwtAxis::xTop ).invTransform( pos.x() ); + coords[ QwtAxis::yLeft ] = + plot->canvasMap( QwtAxis::yLeft ).invTransform( pos.y() ); + coords[ QwtAxis::yRight ] = + plot->canvasMap( QwtAxis::yRight ).invTransform( pos.y() ); + + QwtPlotItemList items = plot->itemList(); + for ( int i = items.size() - 1; i >= 0; i-- ) + { + QwtPlotItem *item = items[ i ]; + if ( item->isVisible() && + item->rtti() == QwtPlotItem::Rtti_PlotShape ) + { + QwtPlotShapeItem *shapeItem = static_cast( item ); + const QPointF p( coords[ item->xAxis().pos ], coords[ item->yAxis().pos ] ); + + if ( shapeItem->boundingRect().contains( p ) + && shapeItem->shape().contains( p ) ) + { + return shapeItem; + } + } + } + + return NULL; +} + +QRegion Editor::maskHint() const +{ + return maskHint( d_editedItem ); +} + +QRegion Editor::maskHint( QwtPlotShapeItem *shapeItem ) const +{ + const QwtPlot *plot = this->plot(); + if ( plot == NULL || shapeItem == NULL ) + return QRegion(); + + const QwtScaleMap xMap = plot->canvasMap( shapeItem->xAxis() ); + const QwtScaleMap yMap = plot->canvasMap( shapeItem->yAxis() ); + + QRect rect = QwtScaleMap::transform( xMap, yMap, + shapeItem->shape().boundingRect() ).toRect(); + + const int m = 5; // some margin for the pen + return rect.adjusted( -m, -m, m, m ); +} + +void Editor::drawOverlay( QPainter* painter ) const +{ + const QwtPlot *plot = this->plot(); + if ( plot == NULL || d_editedItem == NULL ) + return; + + const QwtScaleMap xMap = plot->canvasMap( d_editedItem->xAxis() ); + const QwtScaleMap yMap = plot->canvasMap( d_editedItem->yAxis() ); + + painter->setRenderHint( QPainter::Antialiasing, + d_editedItem->testRenderHint( QwtPlotItem::RenderAntialiased ) ); + d_editedItem->draw( painter, xMap, yMap, + plot->canvas()->contentsRect() ); +} + +void Editor::raiseItem( QwtPlotShapeItem *shapeItem ) +{ + const QwtPlot *plot = this->plot(); + if ( plot == NULL || shapeItem == NULL ) + return; + + const QwtPlotItemList items = plot->itemList(); + for ( int i = items.size() - 1; i >= 0; i-- ) + { + QwtPlotItem *item = items[ i ]; + if ( shapeItem == item ) + return; + + if ( item->isVisible() && + item->rtti() == QwtPlotItem::Rtti_PlotShape ) + { + shapeItem->setZ( item->z() + 1 ); + return; + } + } +} + +void Editor::setItemVisible( QwtPlotShapeItem *item, bool on ) +{ + if ( plot() == NULL || item == NULL || item->isVisible() == on ) + return; + + const bool doAutoReplot = plot()->autoReplot(); + plot()->setAutoReplot( false ); + + item->setVisible( on ); + + plot()->setAutoReplot( doAutoReplot ); + + /* + Avoid replot with a full repaint of the canvas. + For special combinations - f.e. using the + raster paint engine on a remote display - + this makes a difference. + */ + + QwtPlotCanvas *canvas = + qobject_cast( plot()->canvas() ); + if ( canvas ) + canvas->invalidateBackingStore(); + + plot()->canvas()->update( maskHint( item ) ); + +} + diff --git a/qwt/examples/itemeditor/editor.h b/qwt/examples/itemeditor/editor.h new file mode 100644 index 000000000..7f8c8965a --- /dev/null +++ b/qwt/examples/itemeditor/editor.h @@ -0,0 +1,66 @@ +#ifndef _EDITOR_H_ +#define _EDITOR_H_ + +#include +#include +#include +#include + +class QwtPlot; +class QwtPlotShapeItem; +class QPainter; +class QPoint; + +class Editor: public QObject +{ + Q_OBJECT + +public: + enum Mode + { + NoMask, + Mask, + AlphaMask, + AlphaMaskRedraw, + AlphaMaskCopyMask + }; + + Editor( QwtPlot * ); + virtual ~Editor(); + + const QwtPlot *plot() const; + QwtPlot *plot(); + + virtual void setEnabled( bool on ); + bool isEnabled() const; + + void drawOverlay( QPainter * ) const; + QRegion maskHint() const; + + virtual bool eventFilter( QObject *, QEvent *); + + void setMode( Mode mode ); + Mode mode() const; + +private: + bool pressed( const QPoint & ); + bool moved( const QPoint & ); + void released( const QPoint & ); + + QwtPlotShapeItem* itemAt( const QPoint& ) const; + void raiseItem( QwtPlotShapeItem * ); + + QRegion maskHint( QwtPlotShapeItem * ) const; + void setItemVisible( QwtPlotShapeItem *item, bool on ); + + bool d_isEnabled; + QPointer d_overlay; + + // Mouse positions + QPointF d_currentPos; + QwtPlotShapeItem* d_editedItem; + + Mode d_mode; +}; + +#endif diff --git a/qwt/examples/itemeditor/itemeditor.pro b/qwt/examples/itemeditor/itemeditor.pro new file mode 100644 index 000000000..734209115 --- /dev/null +++ b/qwt/examples/itemeditor/itemeditor.pro @@ -0,0 +1,24 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = itemeditor + +HEADERS = \ + editor.h \ + shapefactory.h \ + plot.h + +SOURCES = \ + editor.cpp \ + shapefactory.cpp \ + plot.cpp \ + main.cpp + diff --git a/qwt/examples/itemeditor/main.cpp b/qwt/examples/itemeditor/main.cpp new file mode 100644 index 000000000..d66fc6358 --- /dev/null +++ b/qwt/examples/itemeditor/main.cpp @@ -0,0 +1,54 @@ +#include "plot.h" +#include +#include +#include +#include +#include + +class MainWindow: public QMainWindow +{ +public: + MainWindow( QWidget * = NULL ); +}; + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + Plot *plot = new Plot( this ); + setCentralWidget( plot ); + + QToolBar *toolBar = new QToolBar( this ); + + QComboBox *modeBox = new QComboBox( toolBar ); + modeBox->addItem( "No Mask" ); + modeBox->addItem( "Mask" ); + modeBox->addItem( "Alpha Mask" ); + modeBox->addItem( "Alpha Mask/Redraw" ); + modeBox->addItem( "Alpha Mask/Copy Mask" ); + modeBox->setCurrentIndex( 1 ); + modeBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + + connect( modeBox, SIGNAL( currentIndexChanged( int ) ), + plot, SLOT( setMode( int ) ) ); + + QToolButton *btnExport = new QToolButton( toolBar ); + btnExport->setText( "Export" ); + btnExport->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + connect( btnExport, SIGNAL( clicked() ), plot, SLOT( exportPlot() ) ); + + toolBar->addWidget( modeBox ); + toolBar->addWidget( btnExport ); + addToolBar( toolBar ); + +} + +int main( int argc, char **argv ) +{ + QApplication app( argc, argv ); + + MainWindow window; + window.resize( 600, 400 ); + window.show(); + + return app.exec(); +} diff --git a/qwt/examples/itemeditor/plot.cpp b/qwt/examples/itemeditor/plot.cpp new file mode 100644 index 000000000..0aeb3f2ad --- /dev/null +++ b/qwt/examples/itemeditor/plot.cpp @@ -0,0 +1,120 @@ +#include "plot.h" +#include "editor.h" +#include +#include +#include +#include +#include + +class Legend: public QwtLegend +{ +protected: + virtual QWidget *createWidget( const QwtLegendData &data ) const + { + QWidget *w = QwtLegend::createWidget( data ); + if ( w ) + { + w->setStyleSheet( + "border-radius: 5px;" + "padding: 2px;" + "background: LemonChiffon;" + ); + } + + return w; + } +}; + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + setAutoReplot( false ); + + setTitle( "Movable Items" ); + + const int margin = 5; + setContentsMargins( margin, margin, margin, margin ); + + setAutoFillBackground( true ); + setPalette( QColor( "DimGray" ).lighter( 110 ) ); + + QwtPlotCanvas *canvas = new QwtPlotCanvas(); +#if 0 + // a gradient making a replot slow on X11 + canvas->setStyleSheet( + "border: 2px solid Black;" + "border-radius: 15px;" + "background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1," + "stop: 0 LemonChiffon, stop: 0.5 PaleGoldenrod, stop: 1 LemonChiffon );" + ); +#else + canvas->setStyleSheet( + "border: 2px inset DimGray;" + "border-radius: 15px;" + "background: LemonChiffon;" + ); +#endif + + setCanvas( canvas ); + insertLegend( new Legend(), QwtPlot::RightLegend ); + + populate(); + + updateAxes(); + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + setAxisAutoScale( axis, false ); + + d_editor = new Editor( this ); + ( void ) new QwtPlotMagnifier( canvas ); +} + +void Plot::populate() +{ + addShape( "Rectangle", ShapeFactory::Rect, "RoyalBlue", + QPointF( 30.0, 50.0 ), QSizeF( 40.0, 50.0 ) ); + addShape( "Ellipse", ShapeFactory::Ellipse, "IndianRed", + QPointF( 80.0, 130.0 ), QSizeF( 50.0, 40.0 ) ); + addShape( "Ring", ShapeFactory::Ring, "DarkOliveGreen", + QPointF( 30.0, 165.0 ), QSizeF( 40.0, 40.0 ) ); + addShape( "Triangle", ShapeFactory::Triangle, "SandyBrown", + QPointF( 165.0, 165.0 ), QSizeF( 60.0, 40.0 ) ); + addShape( "Star", ShapeFactory::Star, "DarkViolet", + QPointF( 165.0, 50.0 ), QSizeF( 40.0, 50.0 ) ); + addShape( "Hexagon", ShapeFactory::Hexagon, "DarkSlateGray", + QPointF( 120.0, 70.0 ), QSizeF( 50.0, 50.0 ) ); + +} + +void Plot::addShape( const QString &title, + ShapeFactory::Shape shape, const QColor &color, + const QPointF &pos, const QSizeF &size ) +{ + QwtPlotShapeItem *item = new QwtPlotShapeItem( title ); + item->setItemAttribute( QwtPlotItem::Legend, true ); + item->setLegendMode( QwtPlotShapeItem::LegendShape ); + item->setLegendIconSize( QSize( 20, 20 ) ); + item->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + item->setShape( ShapeFactory::path( shape, pos, size ) ); + + QColor fillColor = color; + fillColor.setAlpha( 200 ); + + QPen pen( color, 3 ); + pen.setJoinStyle( Qt::MiterJoin ); + item->setPen( pen ); + item->setBrush( fillColor ); + + item->attach( this ); +} + +void Plot::exportPlot() +{ + QwtPlotRenderer renderer; + renderer.exportTo( this, "shapes.pdf" ); +} + +void Plot::setMode( int mode ) +{ + d_editor->setMode( static_cast( mode ) ); +} + diff --git a/qwt/examples/itemeditor/plot.h b/qwt/examples/itemeditor/plot.h new file mode 100644 index 000000000..1652a6466 --- /dev/null +++ b/qwt/examples/itemeditor/plot.h @@ -0,0 +1,33 @@ +#ifndef _PLOT_H +#define _PLOT_H + +#include +#include "shapefactory.h" + +class QColor; +class QSizeF; +class QPointF; +class Editor; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget *parent = NULL ); + +public Q_SLOTS: + void exportPlot(); + void setMode( int ); + +private: + void populate(); + + void addShape( const QString &title, + ShapeFactory::Shape, const QColor &, + const QPointF &, const QSizeF & ); + + Editor *d_editor; +}; + +#endif diff --git a/qwt/examples/itemeditor/shapefactory.cpp b/qwt/examples/itemeditor/shapefactory.cpp new file mode 100644 index 000000000..2a9b01d46 --- /dev/null +++ b/qwt/examples/itemeditor/shapefactory.cpp @@ -0,0 +1,113 @@ +#include "shapefactory.h" + +QPainterPath ShapeFactory::path( Shape shape, + const QPointF &pos, const QSizeF &size ) +{ + QRectF rect; + rect.setSize( size ); + rect.moveCenter( pos ); + + QPainterPath path; + + switch( shape ) + { + case Rect: + { + path.addRect( rect ); + break; + } + case Triangle: + { + QPolygonF triangle; + triangle += rect.bottomLeft(); + triangle += QPointF( rect.center().x(), rect.top() ); + triangle += rect.bottomRight(); + + path.addPolygon( triangle ); + break; + } + case Ellipse: + { + path.addEllipse( rect ); + break; + } + case Ring: + { + path.addEllipse( rect ); + + const double w = 0.25 * rect.width(); + path.addEllipse( rect.adjusted( w, w, -w, -w ) ); + break; + } + case Star: + { + const double cos30 = 0.866025; + + const double dy = 0.25 * size.height(); + const double dx = 0.5 * size.width() * cos30 / 3.0; + + double x1 = pos.x() - 3 * dx; + double y1 = pos.y() - 2 * dy; + + const double x2 = x1 + 1 * dx; + const double x3 = x1 + 2 * dx; + const double x4 = x1 + 3 * dx; + const double x5 = x1 + 4 * dx; + const double x6 = x1 + 5 * dx; + const double x7 = x1 + 6 * dx; + + const double y2 = y1 + 1 * dy; + const double y3 = y1 + 2 * dy; + const double y4 = y1 + 3 * dy; + const double y5 = y1 + 4 * dy; + + QPolygonF star; + star += QPointF( x4, y1 ); + star += QPointF( x5, y2 ); + star += QPointF( x7, y2 ); + star += QPointF( x6, y3 ); + star += QPointF( x7, y4 ); + star += QPointF( x5, y4 ); + star += QPointF( x4, y5 ); + star += QPointF( x3, y4 ); + star += QPointF( x1, y4 ); + star += QPointF( x2, y3 ); + star += QPointF( x1, y2 ); + star += QPointF( x3, y2 ); + + path.addPolygon( star ); + break; + } + case Hexagon: + { + const double cos30 = 0.866025; + + const double dx = 0.5 * size.width() - cos30; + const double dy = 0.25 * size.height(); + + double x1 = pos.x() - dx; + double y1 = pos.y() - 2 * dy; + + const double x2 = x1 + 1 * dx; + const double x3 = x1 + 2 * dx; + + const double y2 = y1 + 1 * dy; + const double y3 = y1 + 3 * dy; + const double y4 = y1 + 4 * dy; + + QPolygonF hexagon; + hexagon += QPointF( x2, y1 ); + hexagon += QPointF( x3, y2 ); + hexagon += QPointF( x3, y3 ); + hexagon += QPointF( x2, y4 ); + hexagon += QPointF( x1, y3 ); + hexagon += QPointF( x1, y2 ); + + path.addPolygon( hexagon ); + break; + } + }; + + path.closeSubpath(); + return path; +} diff --git a/qwt/examples/itemeditor/shapefactory.h b/qwt/examples/itemeditor/shapefactory.h new file mode 100644 index 000000000..3dcd72535 --- /dev/null +++ b/qwt/examples/itemeditor/shapefactory.h @@ -0,0 +1,21 @@ +#ifndef _SHAPE_FACTORY_H_ +#define _SHAPE_FACTORY_H_ + +#include + +namespace ShapeFactory +{ + enum Shape + { + Rect, + Triangle, + Ellipse, + Ring, + Star, + Hexagon + }; + + QPainterPath path( Shape, const QPointF &, const QSizeF & ); +}; + +#endif diff --git a/qwt/examples/legends/legends.pro b/qwt/examples/legends/legends.pro new file mode 100644 index 000000000..a8e9e7821 --- /dev/null +++ b/qwt/examples/legends/legends.pro @@ -0,0 +1,24 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = legends + +HEADERS = \ + mainwindow.h \ + panel.h \ + settings.h \ + plot.h + +SOURCES = \ + mainwindow.cpp \ + panel.cpp \ + plot.cpp \ + main.cpp diff --git a/qwt/examples/legends/main.cpp b/qwt/examples/legends/main.cpp new file mode 100644 index 000000000..91b30b5a1 --- /dev/null +++ b/qwt/examples/legends/main.cpp @@ -0,0 +1,14 @@ +#include +#include "mainwindow.h" + +int main ( int argc, char **argv ) +{ + QApplication a( argc, argv ); + a.setStyle( "Windows" ); + + MainWindow w; + w.resize( 700, 500 ); + w.show(); + + return a.exec(); +} diff --git a/qwt/examples/legends/mainwindow.cpp b/qwt/examples/legends/mainwindow.cpp new file mode 100644 index 000000000..200d8e4c7 --- /dev/null +++ b/qwt/examples/legends/mainwindow.cpp @@ -0,0 +1,61 @@ +#include +#include +#include +#include +#include +#include "plot.h" +#include "panel.h" +#include "mainwindow.h" + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + d_plot = new Plot(); + + Settings settings; + settings.legend.isEnabled = true; + settings.legend.position = QwtPlot::BottomLegend; + + settings.legendItem.isEnabled = false; + settings.legendItem.numColumns = 1; + settings.legendItem.alignment = Qt::AlignRight | Qt::AlignVCenter; + settings.legendItem.backgroundMode = 0; + settings.legendItem.size = d_plot->canvas()->font().pointSize(); + + settings.curve.numCurves = 4; + settings.curve.title = "Curve"; + + d_panel = new Panel(); + d_panel->setSettings( settings ); + + QWidget *box = new QWidget( this ); + QHBoxLayout *layout = new QHBoxLayout( box ); + layout->addWidget( d_plot, 10 ); + layout->addWidget( d_panel ); + + setCentralWidget( box ); + + QToolBar *toolBar = new QToolBar( this ); + + QToolButton *btnExport = new QToolButton( toolBar ); + btnExport->setText( "Export" ); + toolBar->addWidget( btnExport ); + + addToolBar( toolBar ); + + updatePlot(); + + connect( d_panel, SIGNAL( edited() ), SLOT( updatePlot() ) ); + connect( btnExport, SIGNAL( clicked() ), SLOT( exportPlot() ) ); +} + +void MainWindow::updatePlot() +{ + d_plot->applySettings( d_panel->settings() ); +} + +void MainWindow::exportPlot() +{ + QwtPlotRenderer renderer; + renderer.exportTo( d_plot, "legends.pdf" ); +} diff --git a/qwt/examples/legends/mainwindow.h b/qwt/examples/legends/mainwindow.h new file mode 100644 index 000000000..26e36a00b --- /dev/null +++ b/qwt/examples/legends/mainwindow.h @@ -0,0 +1,20 @@ +#include + +class Plot; +class Panel; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow( QWidget *parent = 0 ); + +private Q_SLOTS: + void updatePlot(); + void exportPlot(); + +private: + Plot *d_plot; + Panel *d_panel; +}; diff --git a/qwt/examples/legends/panel.cpp b/qwt/examples/legends/panel.cpp new file mode 100644 index 000000000..256fc5b8a --- /dev/null +++ b/qwt/examples/legends/panel.cpp @@ -0,0 +1,216 @@ +#include "panel.h" +#include "settings.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Panel::Panel( QWidget *parent ): + QWidget( parent ) +{ + // create widgets + + d_legend.checkBox = new QCheckBox( "Enabled" ); + + d_legend.positionBox = new QComboBox(); + d_legend.positionBox->addItem( "Left", QwtPlot::LeftLegend ); + d_legend.positionBox->addItem( "Right", QwtPlot::RightLegend ); + d_legend.positionBox->addItem( "Bottom", QwtPlot::BottomLegend ); + d_legend.positionBox->addItem( "Top", QwtPlot::TopLegend ); + d_legend.positionBox->addItem( "External", QwtPlot::TopLegend + 1 ); + + d_legendItem.checkBox = new QCheckBox( "Enabled" ); + + d_legendItem.numColumnsBox = new QSpinBox(); + d_legendItem.numColumnsBox->setRange( 0, 10 ); + d_legendItem.numColumnsBox->setSpecialValueText( "Unlimited" ); + + d_legendItem.hAlignmentBox = new QComboBox(); + d_legendItem.hAlignmentBox->addItem( "Left", Qt::AlignLeft ); + d_legendItem.hAlignmentBox->addItem( "Centered", Qt::AlignHCenter ); + d_legendItem.hAlignmentBox->addItem( "Right", Qt::AlignRight ); + + d_legendItem.vAlignmentBox = new QComboBox(); + d_legendItem.vAlignmentBox->addItem( "Top", Qt::AlignTop ); + d_legendItem.vAlignmentBox->addItem( "Centered", Qt::AlignVCenter ); + d_legendItem.vAlignmentBox->addItem( "Bottom", Qt::AlignBottom ); + + d_legendItem.backgroundBox = new QComboBox(); + d_legendItem.backgroundBox->addItem( "Legend", + QwtPlotLegendItem::LegendBackground ); + d_legendItem.backgroundBox->addItem( "Items", + QwtPlotLegendItem::ItemBackground ); + + d_legendItem.sizeBox = new QSpinBox(); + d_legendItem.sizeBox->setRange( 8, 22 ); + + d_curve.numCurves = new QSpinBox(); + d_curve.numCurves->setRange( 0, 99 ); + + d_curve.title = new QLineEdit(); + + // layout + + QGroupBox *legendBox = new QGroupBox( "Legend" ); + QGridLayout *legendBoxLayout = new QGridLayout( legendBox ); + + int row = 0; + legendBoxLayout->addWidget( d_legend.checkBox, row, 0, 1, -1 ); + + row++; + legendBoxLayout->addWidget( new QLabel( "Position" ), row, 0 ); + legendBoxLayout->addWidget( d_legend.positionBox, row, 1 ); + + + QGroupBox *legendItemBox = new QGroupBox( "Legend Item" ); + QGridLayout *legendItemBoxLayout = new QGridLayout( legendItemBox ); + + row = 0; + legendItemBoxLayout->addWidget( d_legendItem.checkBox, row, 0, 1, -1 ); + + row++; + legendItemBoxLayout->addWidget( new QLabel( "Columns" ), row, 0 ); + legendItemBoxLayout->addWidget( d_legendItem.numColumnsBox, row, 1 ); + + row++; + legendItemBoxLayout->addWidget( new QLabel( "Horizontal" ), row, 0 ); + legendItemBoxLayout->addWidget( d_legendItem.hAlignmentBox, row, 1 ); + + row++; + legendItemBoxLayout->addWidget( new QLabel( "Vertical" ), row, 0 ); + legendItemBoxLayout->addWidget( d_legendItem.vAlignmentBox, row, 1 ); + + row++; + legendItemBoxLayout->addWidget( new QLabel( "Background" ), row, 0 ); + legendItemBoxLayout->addWidget( d_legendItem.backgroundBox, row, 1 ); + + row++; + legendItemBoxLayout->addWidget( new QLabel( "Size" ), row, 0 ); + legendItemBoxLayout->addWidget( d_legendItem.sizeBox, row, 1 ); + + QGroupBox *curveBox = new QGroupBox( "Curves" ); + QGridLayout *curveBoxLayout = new QGridLayout( curveBox ); + + row = 0; + curveBoxLayout->addWidget( new QLabel( "Number" ), row, 0 ); + curveBoxLayout->addWidget( d_curve.numCurves, row, 1 ); + + row++; + curveBoxLayout->addWidget( new QLabel( "Title" ), row, 0 ); + curveBoxLayout->addWidget( d_curve.title, row, 1 ); + + QVBoxLayout *layout = new QVBoxLayout( this ); + layout->addWidget( legendBox ); + layout->addWidget( legendItemBox ); + layout->addWidget( curveBox ); + layout->addStretch( 10 ); + + connect( d_legend.checkBox, + SIGNAL( stateChanged( int ) ), SIGNAL( edited() ) ); + connect( d_legend.positionBox, + SIGNAL( currentIndexChanged( int ) ), SIGNAL( edited() ) ); + + connect( d_legendItem.checkBox, + SIGNAL( stateChanged( int ) ), SIGNAL( edited() ) ); + connect( d_legendItem.numColumnsBox, + SIGNAL( valueChanged( int ) ), SIGNAL( edited() ) ); + connect( d_legendItem.hAlignmentBox, + SIGNAL( currentIndexChanged( int ) ), SIGNAL( edited() ) ); + connect( d_legendItem.vAlignmentBox, + SIGNAL( currentIndexChanged( int ) ), SIGNAL( edited() ) ); + connect( d_legendItem.backgroundBox, + SIGNAL( currentIndexChanged( int ) ), SIGNAL( edited() ) ); + connect( d_curve.numCurves, + SIGNAL( valueChanged( int ) ), SIGNAL( edited() ) ); + connect( d_legendItem.sizeBox, + SIGNAL( valueChanged( int ) ), SIGNAL( edited() ) ); + connect( d_curve.title, + SIGNAL( textEdited( const QString & ) ), SIGNAL( edited() ) ); +} + +void Panel::setSettings( const Settings &settings) +{ + blockSignals( true ); + + d_legend.checkBox->setCheckState( + settings.legend.isEnabled ? Qt::Checked : Qt::Unchecked ); + d_legend.positionBox->setCurrentIndex( settings.legend.position ); + + d_legendItem.checkBox->setCheckState( + settings.legendItem.isEnabled ? Qt::Checked : Qt::Unchecked ); + + d_legendItem.numColumnsBox->setValue( settings.legendItem.numColumns ); + + int align = settings.legendItem.alignment; + + if ( align & Qt::AlignLeft ) + d_legendItem.hAlignmentBox->setCurrentIndex( 0 ); + else if ( align & Qt::AlignRight ) + d_legendItem.hAlignmentBox->setCurrentIndex( 2 ); + else + d_legendItem.hAlignmentBox->setCurrentIndex( 1 ); + + if ( align & Qt::AlignTop ) + d_legendItem.vAlignmentBox->setCurrentIndex( 0 ); + else if ( align & Qt::AlignBottom ) + d_legendItem.vAlignmentBox->setCurrentIndex( 2 ); + else + d_legendItem.vAlignmentBox->setCurrentIndex( 1 ); + + d_legendItem.backgroundBox->setCurrentIndex( + settings.legendItem.backgroundMode ); + + d_legendItem.sizeBox->setValue( settings.legendItem.size ); + + d_curve.numCurves->setValue( settings.curve.numCurves ); + d_curve.title->setText( settings.curve.title ); + + blockSignals( false ); +} + +Settings Panel::settings() const +{ + Settings s; + + s.legend.isEnabled = + d_legend.checkBox->checkState() == Qt::Checked; + s.legend.position = d_legend.positionBox->currentIndex(); + + s.legendItem.isEnabled = + d_legendItem.checkBox->checkState() == Qt::Checked; + s.legendItem.numColumns = d_legendItem.numColumnsBox->value(); + + int align = 0; + + int hIndex = d_legendItem.hAlignmentBox->currentIndex(); + if ( hIndex == 0 ) + align |= Qt::AlignLeft; + else if ( hIndex == 2 ) + align |= Qt::AlignRight; + else + align |= Qt::AlignHCenter; + + int vIndex = d_legendItem.vAlignmentBox->currentIndex(); + if ( vIndex == 0 ) + align |= Qt::AlignTop; + else if ( vIndex == 2 ) + align |= Qt::AlignBottom; + else + align |= Qt::AlignVCenter; + + s.legendItem.alignment = align; + + s.legendItem.backgroundMode = + d_legendItem.backgroundBox->currentIndex(); + s.legendItem.size = d_legendItem.sizeBox->value(); + + s.curve.numCurves = d_curve.numCurves->value(); + s.curve.title = d_curve.title->text(); + + return s; +} diff --git a/qwt/examples/legends/panel.h b/qwt/examples/legends/panel.h new file mode 100644 index 000000000..570f4943f --- /dev/null +++ b/qwt/examples/legends/panel.h @@ -0,0 +1,52 @@ +#ifndef _PANEL_ +#define _PANEL_ + +#include "settings.h" +#include + +class QCheckBox; +class QComboBox; +class QSpinBox; +class QLineEdit; + +class Panel: public QWidget +{ + Q_OBJECT + +public: + Panel( QWidget *parent = NULL ); + + void setSettings( const Settings &); + Settings settings() const; + +Q_SIGNALS: + void edited(); + +private: + struct + { + QCheckBox *checkBox; + QComboBox *positionBox; + + } d_legend; + + struct + { + QCheckBox *checkBox; + QSpinBox *numColumnsBox; + QComboBox *hAlignmentBox; + QComboBox *vAlignmentBox; + QComboBox *backgroundBox; + QSpinBox *sizeBox; + + } d_legendItem; + + struct + { + QSpinBox *numCurves; + QLineEdit *title; + + } d_curve; +}; + +#endif diff --git a/qwt/examples/legends/plot.cpp b/qwt/examples/legends/plot.cpp new file mode 100644 index 000000000..6ff4ab0f3 --- /dev/null +++ b/qwt/examples/legends/plot.cpp @@ -0,0 +1,263 @@ +#include "plot.h" +#include "settings.h" +#include +#include +#include +#include +#include +#include + +class LegendItem: public QwtPlotLegendItem +{ +public: + LegendItem() + { + setRenderHint( QwtPlotItem::RenderAntialiased ); + + QColor color( Qt::white ); + + setTextPen( color ); +#if 1 + setBorderPen( color ); + + QColor c( Qt::gray ); + c.setAlpha( 200 ); + + setBackgroundBrush( c ); +#endif + } +}; + +class Curve: public QwtPlotCurve +{ +public: + Curve( int index ): + d_index( index ) + { + setRenderHint( QwtPlotItem::RenderAntialiased ); + initData(); + } + + void setCurveTitle( const QString &title ) + { + QString txt("%1 %2"); + setTitle( QString( "%1 %2" ).arg( title ).arg( d_index ) ); + } + + void initData() + { + QVector points; + + double y = qrand() % 1000; + + for ( double x = 0.0; x <= 1000.0; x += 100.0 ) + { + double off = qrand() % 200 - 100; + if ( y + off > 980.0 || y + off < 20.0 ) + off = -off; + + y += off; + + points += QPointF( x, y ); + } + + setSamples( points ); + } + +private: + const int d_index; +}; + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ), + d_externalLegend( NULL ), + d_legendItem( NULL ), + d_isDirty( false ) +{ + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setFocusIndicator( QwtPlotCanvas::CanvasFocusIndicator ); + canvas->setFocusPolicy( Qt::StrongFocus ); + canvas->setPalette( Qt::black ); + setCanvas( canvas ); + + setAutoReplot( false ); + + setTitle( "Legend Test" ); + setFooter( "Footer" ); + + // grid + QwtPlotGrid *grid = new QwtPlotGrid; + grid->enableXMin( true ); + grid->setMajorPen( Qt::gray, 0, Qt::DotLine ); + grid->setMinorPen( Qt::darkGray, 0, Qt::DotLine ); + grid->attach( this ); + + // axis + setAxisScale( QwtAxis::yLeft, 0.0, 1000.0 ); + setAxisScale( QwtAxis::xBottom, 0.0, 1000.0 ); +} + +Plot::~Plot() +{ + delete d_externalLegend; +} + +void Plot::insertCurve() +{ + static int counter = 1; + + const char *colors[] = + { + "LightSalmon", + "SteelBlue", + "Yellow", + "Fuchsia", + "PaleGreen", + "PaleTurquoise", + "Cornsilk", + "HotPink", + "Peru", + "Maroon" + }; + const int numColors = sizeof( colors ) / sizeof( colors[0] ); + + QwtPlotCurve *curve = new Curve( counter++ ); + curve->setPen( QColor( colors[ counter % numColors ] ), 2 ); + curve->attach( this ); +} + +void Plot::applySettings( const Settings &settings ) +{ + d_isDirty = false; + setAutoReplot( true ); + + if ( settings.legend.isEnabled ) + { + if ( settings.legend.position > QwtPlot::TopLegend ) + { + if ( legend() ) + { + // remove legend controlled by the plot + insertLegend( NULL ); + } + + if ( d_externalLegend == NULL ) + { + d_externalLegend = new QwtLegend(); + d_externalLegend->setWindowTitle("Plot Legend"); + + connect( + this, + SIGNAL( legendDataChanged( const QVariant &, + const QList & ) ), + d_externalLegend, + SLOT( updateLegend( const QVariant &, + const QList & ) ) ); + + d_externalLegend->show(); + + // populate the new legend + updateLegend(); + } + } + else + { + delete d_externalLegend; + d_externalLegend = NULL; + + if ( legend() == NULL || + plotLayout()->legendPosition() != settings.legend.position ) + { + insertLegend( new QwtLegend(), + QwtPlot::LegendPosition( settings.legend.position ) ); + } + } + } + else + { + insertLegend( NULL ); + + delete d_externalLegend; + d_externalLegend = NULL; + } + + if ( settings.legendItem.isEnabled ) + { + if ( d_legendItem == NULL ) + { + d_legendItem = new LegendItem(); + d_legendItem->attach( this ); + } + + d_legendItem->setMaxColumns( settings.legendItem.numColumns ); + d_legendItem->setAlignment( Qt::Alignment( settings.legendItem.alignment ) ); + d_legendItem->setBackgroundMode( + QwtPlotLegendItem::BackgroundMode( settings.legendItem.backgroundMode ) ); + if ( settings.legendItem.backgroundMode == + QwtPlotLegendItem::ItemBackground ) + { + d_legendItem->setBorderRadius( 4 ); + d_legendItem->setMargin( 0 ); + d_legendItem->setSpacing( 4 ); + d_legendItem->setItemMargin( 2 ); + } + else + { + d_legendItem->setBorderRadius( 8 ); + d_legendItem->setMargin( 4 ); + d_legendItem->setSpacing( 2 ); + d_legendItem->setItemMargin( 0 ); + } + + QFont font = d_legendItem->font(); + font.setPointSize( settings.legendItem.size ); + d_legendItem->setFont( font ); + } + else + { + delete d_legendItem; + d_legendItem = NULL; + } + + QwtPlotItemList curveList = itemList( QwtPlotItem::Rtti_PlotCurve ); + if ( curveList.size() != settings.curve.numCurves ) + { + while ( curveList.size() > settings.curve.numCurves ) + { + QwtPlotItem* curve = curveList.takeFirst(); + delete curve; + } + + for ( int i = curveList.size(); i < settings.curve.numCurves; i++ ) + insertCurve(); + } + + curveList = itemList( QwtPlotItem::Rtti_PlotCurve ); + for ( int i = 0; i < curveList.count(); i++ ) + { + Curve* curve = static_cast( curveList[i] ); + curve->setCurveTitle( settings.curve.title ); + + int sz = 0.5 * settings.legendItem.size; + curve->setLegendIconSize( QSize( sz, sz ) ); + } + + setAutoReplot( false ); + if ( d_isDirty ) + { + d_isDirty = false; + replot(); + } +} + +void Plot::replot() +{ + if ( autoReplot() ) + { + d_isDirty = true; + return; + } + + QwtPlot::replot(); +} + diff --git a/qwt/examples/legends/plot.h b/qwt/examples/legends/plot.h new file mode 100644 index 000000000..b29c9d3f1 --- /dev/null +++ b/qwt/examples/legends/plot.h @@ -0,0 +1,32 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ + +#include + +class Settings; +class LegendItem; +class QwtLegend; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget *parent = NULL ); + virtual ~Plot(); + +public Q_SLOTS: + void applySettings( const Settings & ); + +public: + virtual void replot(); + +private: + void insertCurve(); + + QwtLegend *d_externalLegend; + LegendItem *d_legendItem; + bool d_isDirty; +}; + +#endif diff --git a/qwt/examples/legends/settings.h b/qwt/examples/legends/settings.h new file mode 100644 index 000000000..2f9061b68 --- /dev/null +++ b/qwt/examples/legends/settings.h @@ -0,0 +1,47 @@ +#ifndef _SETTINGS_ +#define _SETTINGS_ + +#include + +class Settings +{ +public: + Settings() + { + legend.isEnabled = false; + legend.position = 0; + + legendItem.isEnabled = false; + legendItem.numColumns = 0; + legendItem.alignment = 0; + legendItem.backgroundMode = 0; + legendItem.size = 12; + + curve.numCurves = 0; + curve.title = "Curve"; + } + + struct + { + bool isEnabled; + int position; + } legend; + + struct + { + bool isEnabled; + int numColumns; + int alignment; + int backgroundMode; + int size; + + } legendItem; + + struct + { + int numCurves; + QString title; + } curve; +}; + +#endif diff --git a/qwt/examples/oscilloscope/curvedata.cpp b/qwt/examples/oscilloscope/curvedata.cpp new file mode 100644 index 000000000..28e02eb72 --- /dev/null +++ b/qwt/examples/oscilloscope/curvedata.cpp @@ -0,0 +1,27 @@ +#include "curvedata.h" +#include "signaldata.h" + +const SignalData &CurveData::values() const +{ + return SignalData::instance(); +} + +SignalData &CurveData::values() +{ + return SignalData::instance(); +} + +QPointF CurveData::sample( size_t i ) const +{ + return SignalData::instance().value( i ); +} + +size_t CurveData::size() const +{ + return SignalData::instance().size(); +} + +QRectF CurveData::boundingRect() const +{ + return SignalData::instance().boundingRect(); +} diff --git a/qwt/examples/oscilloscope/curvedata.h b/qwt/examples/oscilloscope/curvedata.h new file mode 100644 index 000000000..246eca583 --- /dev/null +++ b/qwt/examples/oscilloscope/curvedata.h @@ -0,0 +1,16 @@ +#include +#include + +class SignalData; + +class CurveData: public QwtSeriesData +{ +public: + const SignalData &values() const; + SignalData &values(); + + virtual QPointF sample( size_t i ) const; + virtual size_t size() const; + + virtual QRectF boundingRect() const; +}; diff --git a/qwt/examples/oscilloscope/knob.cpp b/qwt/examples/oscilloscope/knob.cpp new file mode 100644 index 000000000..8bb61edc8 --- /dev/null +++ b/qwt/examples/oscilloscope/knob.cpp @@ -0,0 +1,95 @@ +#include "knob.h" +#include +#include +#include +#include +#include +#include +#include + +Knob::Knob( const QString &title, double min, double max, QWidget *parent ): + QWidget( parent ) +{ + QFont font( "Helvetica", 10 ); + + d_knob = new QwtKnob( this ); + d_knob->setFont( font ); + + QwtScaleDiv scaleDiv = + d_knob->scaleEngine()->divideScale( min, max, 5, 3 ); + + QList ticks = scaleDiv.ticks( QwtScaleDiv::MajorTick ); + if ( ticks.size() > 0 && ticks[0] > min ) + { + if ( ticks.first() > min ) + ticks.prepend( min ); + if ( ticks.last() < max ) + ticks.append( max ); + } + scaleDiv.setTicks( QwtScaleDiv::MajorTick, ticks ); + d_knob->setScale( scaleDiv ); + + d_knob->setKnobWidth( 50 ); + + font.setBold( true ); + d_label = new QLabel( title, this ); + d_label->setFont( font ); + d_label->setAlignment( Qt::AlignTop | Qt::AlignHCenter ); + + setSizePolicy( QSizePolicy::MinimumExpanding, + QSizePolicy::MinimumExpanding ); + + connect( d_knob, SIGNAL( valueChanged( double ) ), + this, SIGNAL( valueChanged( double ) ) ); +} + +QSize Knob::sizeHint() const +{ + QSize sz1 = d_knob->sizeHint(); + QSize sz2 = d_label->sizeHint(); + + const int w = qMax( sz1.width(), sz2.width() ); + const int h = sz1.height() + sz2.height(); + + int off = qCeil( d_knob->scaleDraw()->extent( d_knob->font() ) ); + off -= 15; // spacing + + return QSize( w, h - off ); +} + +void Knob::setValue( double value ) +{ + d_knob->setValue( value ); +} + +double Knob::value() const +{ + return d_knob->value(); +} + +void Knob::setTheme( const QColor &color ) +{ + d_knob->setPalette( color ); +} + +QColor Knob::theme() const +{ + return d_knob->palette().color( QPalette::Window ); +} + +void Knob::resizeEvent( QResizeEvent *event ) +{ + const QSize sz = event->size(); + const QSize hint = d_label->sizeHint(); + + d_label->setGeometry( 0, sz.height() - hint.height(), + sz.width(), hint.height() ); + + const int knobHeight = d_knob->sizeHint().height(); + + int off = qCeil( d_knob->scaleDraw()->extent( d_knob->font() ) ); + off -= 15; // spacing + + d_knob->setGeometry( 0, d_label->pos().y() - knobHeight + off, + sz.width(), knobHeight ); +} diff --git a/qwt/examples/oscilloscope/knob.h b/qwt/examples/oscilloscope/knob.h new file mode 100644 index 000000000..bfc70d715 --- /dev/null +++ b/qwt/examples/oscilloscope/knob.h @@ -0,0 +1,38 @@ +#ifndef _KNOB_H_ +#define _KNOB_H_ + +#include + +class QwtKnob; +class QLabel; + +class Knob: public QWidget +{ + Q_OBJECT + + Q_PROPERTY( QColor theme READ theme WRITE setTheme ) + +public: + Knob( const QString &title, + double min, double max, QWidget *parent = NULL ); + + virtual QSize sizeHint() const; + + void setValue( double value ); + double value() const; + + void setTheme( const QColor & ); + QColor theme() const; + +Q_SIGNALS: + double valueChanged( double ); + +protected: + virtual void resizeEvent( QResizeEvent * ); + +private: + QwtKnob *d_knob; + QLabel *d_label; +}; + +#endif diff --git a/qwt/examples/oscilloscope/main.cpp b/qwt/examples/oscilloscope/main.cpp new file mode 100644 index 000000000..75ffbe4a7 --- /dev/null +++ b/qwt/examples/oscilloscope/main.cpp @@ -0,0 +1,36 @@ +#include +#include "mainwindow.h" +#include "samplingthread.h" + +int main( int argc, char **argv ) +{ + QApplication app( argc, argv ); + app.setPalette( Qt::darkGray ); + + MainWindow window; + window.resize( 800, 400 ); + + SamplingThread samplingThread; + samplingThread.setFrequency( window.frequency() ); + samplingThread.setAmplitude( window.amplitude() ); + samplingThread.setInterval( window.signalInterval() ); + + window.connect( &window, SIGNAL( frequencyChanged( double ) ), + &samplingThread, SLOT( setFrequency( double ) ) ); + window.connect( &window, SIGNAL( amplitudeChanged( double ) ), + &samplingThread, SLOT( setAmplitude( double ) ) ); + window.connect( &window, SIGNAL( signalIntervalChanged( double ) ), + &samplingThread, SLOT( setInterval( double ) ) ); + + window.show(); + + samplingThread.start(); + window.start(); + + bool ok = app.exec(); + + samplingThread.stop(); + samplingThread.wait( 1000 ); + + return ok; +} diff --git a/qwt/examples/oscilloscope/mainwindow.cpp b/qwt/examples/oscilloscope/mainwindow.cpp new file mode 100644 index 000000000..8c46f447b --- /dev/null +++ b/qwt/examples/oscilloscope/mainwindow.cpp @@ -0,0 +1,69 @@ +#include "mainwindow.h" +#include "plot.h" +#include "knob.h" +#include "wheelbox.h" +#include +#include +#include + +MainWindow::MainWindow( QWidget *parent ): + QWidget( parent ) +{ + const double intervalLength = 10.0; // seconds + + d_plot = new Plot( this ); + d_plot->setIntervalLength( intervalLength ); + + d_amplitudeKnob = new Knob( "Amplitude", 0.0, 200.0, this ); + d_amplitudeKnob->setValue( 160.0 ); + + d_frequencyKnob = new Knob( "Frequency [Hz]", 0.1, 20.0, this ); + d_frequencyKnob->setValue( 17.8 ); + + d_intervalWheel = new WheelBox( "Displayed [s]", 1.0, 100.0, 1.0, this ); + d_intervalWheel->setValue( intervalLength ); + + d_timerWheel = new WheelBox( "Sample Interval [ms]", 0.0, 20.0, 0.1, this ); + d_timerWheel->setValue( 10.0 ); + + QVBoxLayout* vLayout1 = new QVBoxLayout(); + vLayout1->addWidget( d_intervalWheel ); + vLayout1->addWidget( d_timerWheel ); + vLayout1->addStretch( 10 ); + vLayout1->addWidget( d_amplitudeKnob ); + vLayout1->addWidget( d_frequencyKnob ); + + QHBoxLayout *layout = new QHBoxLayout( this ); + layout->addWidget( d_plot, 10 ); + layout->addLayout( vLayout1 ); + + connect( d_amplitudeKnob, SIGNAL( valueChanged( double ) ), + SIGNAL( amplitudeChanged( double ) ) ); + connect( d_frequencyKnob, SIGNAL( valueChanged( double ) ), + SIGNAL( frequencyChanged( double ) ) ); + connect( d_timerWheel, SIGNAL( valueChanged( double ) ), + SIGNAL( signalIntervalChanged( double ) ) ); + + connect( d_intervalWheel, SIGNAL( valueChanged( double ) ), + d_plot, SLOT( setIntervalLength( double ) ) ); +} + +void MainWindow::start() +{ + d_plot->start(); +} + +double MainWindow::frequency() const +{ + return d_frequencyKnob->value(); +} + +double MainWindow::amplitude() const +{ + return d_amplitudeKnob->value(); +} + +double MainWindow::signalInterval() const +{ + return d_timerWheel->value(); +} diff --git a/qwt/examples/oscilloscope/mainwindow.h b/qwt/examples/oscilloscope/mainwindow.h new file mode 100644 index 000000000..8994f0488 --- /dev/null +++ b/qwt/examples/oscilloscope/mainwindow.h @@ -0,0 +1,32 @@ +#include + +class Plot; +class Knob; +class WheelBox; + +class MainWindow : public QWidget +{ + Q_OBJECT + +public: + MainWindow( QWidget * = NULL ); + + void start(); + + double amplitude() const; + double frequency() const; + double signalInterval() const; + +Q_SIGNALS: + void amplitudeChanged( double ); + void frequencyChanged( double ); + void signalIntervalChanged( double ); + +private: + Knob *d_frequencyKnob; + Knob *d_amplitudeKnob; + WheelBox *d_timerWheel; + WheelBox *d_intervalWheel; + + Plot *d_plot; +}; diff --git a/qwt/examples/oscilloscope/osci.css b/qwt/examples/oscilloscope/osci.css new file mode 100644 index 000000000..344a8c426 --- /dev/null +++ b/qwt/examples/oscilloscope/osci.css @@ -0,0 +1,55 @@ +MainWindow +{ + border: 1px solid white; + border-radius: 20px; + padding: 10px; + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #31312C, stop: 0.5 #808080 stop: 1 #31312C ); +} + +QwtPlotCanvas +{ + border: 1px solid White; + border-radius: 10px; + background-color: #101010; + color: yellow; /* used as curve color */ +} + +QwtScaleWidget +{ + color: white; +} + +WheelBox +{ + qproperty-theme: #878787; +} + +QwtWheel +{ + /* background-color: yellow; */ + qproperty-mass: 0.0; + qproperty-tickCount: 5; + qproperty-wheelWidth: 15; + qproperty-borderWidth: 2; + qproperty-wheelBorderWidth: 2; + qproperty-wrapping: true; +} + +Knob +{ + qproperty-theme: #606060; +} + +QwtKnob +{ + qproperty-knobStyle: Sunken; + qproperty-markerStyle: Nub; + qproperty-markerSize: 8; + qproperty-borderWidth: 2; +} + +QLCDNumber +{ + color: yellow; +} diff --git a/qwt/examples/oscilloscope/oscilloscope.pro b/qwt/examples/oscilloscope/oscilloscope.pro new file mode 100644 index 000000000..2fa8e1ea3 --- /dev/null +++ b/qwt/examples/oscilloscope/oscilloscope.pro @@ -0,0 +1,31 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = oscilloscope + +HEADERS = \ + signaldata.h \ + plot.h \ + knob.h \ + wheelbox.h \ + samplingthread.h \ + curvedata.h \ + mainwindow.h + +SOURCES = \ + signaldata.cpp \ + plot.cpp \ + knob.cpp \ + wheelbox.cpp \ + samplingthread.cpp \ + curvedata.cpp \ + mainwindow.cpp \ + main.cpp diff --git a/qwt/examples/oscilloscope/plot.cpp b/qwt/examples/oscilloscope/plot.cpp new file mode 100644 index 000000000..98a30824f --- /dev/null +++ b/qwt/examples/oscilloscope/plot.cpp @@ -0,0 +1,254 @@ +#include "plot.h" +#include "curvedata.h" +#include "signaldata.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Canvas: public QwtPlotCanvas +{ +public: + Canvas( QwtPlot *plot = NULL ): + QwtPlotCanvas( plot ) + { + // The backing store is important, when working with widget + // overlays ( f.e rubberbands for zooming ). + // Here we don't have them and the internal + // backing store of QWidget is good enough. + + setPaintAttribute( QwtPlotCanvas::BackingStore, false ); + setBorderRadius( 10 ); + + if ( QwtPainter::isX11GraphicsSystem() ) + { +#if QT_VERSION < 0x050000 + // Even if not liked by the Qt development, Qt::WA_PaintOutsidePaintEvent + // works on X11. This has a nice effect on the performance. + + setAttribute( Qt::WA_PaintOutsidePaintEvent, true ); +#endif + + // Disabling the backing store of Qt improves the performance + // for the direct painter even more, but the canvas becomes + // a native window of the window system, receiving paint events + // for resize and expose operations. Those might be expensive + // when there are many points and the backing store of + // the canvas is disabled. So in this application + // we better don't both backing stores. + + if ( testPaintAttribute( QwtPlotCanvas::BackingStore ) ) + { + setAttribute( Qt::WA_PaintOnScreen, true ); + setAttribute( Qt::WA_NoSystemBackground, true ); + } + } + + setupPalette(); + } + +private: + void setupPalette() + { + QPalette pal = palette(); + +#if QT_VERSION >= 0x040400 + QLinearGradient gradient; + gradient.setCoordinateMode( QGradient::StretchToDeviceMode ); + gradient.setColorAt( 0.0, QColor( 0, 49, 110 ) ); + gradient.setColorAt( 1.0, QColor( 0, 87, 174 ) ); + + pal.setBrush( QPalette::Window, QBrush( gradient ) ); +#else + pal.setBrush( QPalette::Window, QBrush( color ) ); +#endif + + // QPalette::WindowText is used for the curve color + pal.setColor( QPalette::WindowText, Qt::green ); + + setPalette( pal ); + } +}; + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ), + d_paintedPoints( 0 ), + d_interval( 0.0, 10.0 ), + d_timerId( -1 ) +{ + d_directPainter = new QwtPlotDirectPainter(); + + setAutoReplot( false ); + setCanvas( new Canvas() ); + + plotLayout()->setAlignCanvasToScales( true ); + + setAxisTitle( QwtAxis::xBottom, "Time [s]" ); + setAxisScale( QwtAxis::xBottom, d_interval.minValue(), d_interval.maxValue() ); + setAxisScale( QwtAxis::yLeft, -200.0, 200.0 ); + + QwtPlotGrid *grid = new QwtPlotGrid(); + grid->setPen( Qt::gray, 0.0, Qt::DotLine ); + grid->enableX( true ); + grid->enableXMin( true ); + grid->enableY( true ); + grid->enableYMin( false ); + grid->attach( this ); + + d_origin = new QwtPlotMarker(); + d_origin->setLineStyle( QwtPlotMarker::Cross ); + d_origin->setValue( d_interval.minValue() + d_interval.width() / 2.0, 0.0 ); + d_origin->setLinePen( Qt::gray, 0.0, Qt::DashLine ); + d_origin->attach( this ); + + d_curve = new QwtPlotCurve(); + d_curve->setStyle( QwtPlotCurve::Lines ); + d_curve->setPen( canvas()->palette().color( QPalette::WindowText ) ); + d_curve->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + d_curve->setPaintAttribute( QwtPlotCurve::ClipPolygons, false ); + d_curve->setData( new CurveData() ); + d_curve->attach( this ); +} + +Plot::~Plot() +{ + delete d_directPainter; +} + +void Plot::start() +{ + d_clock.start(); + d_timerId = startTimer( 10 ); +} + +void Plot::replot() +{ + CurveData *data = static_cast( d_curve->data() ); + data->values().lock(); + + QwtPlot::replot(); + d_paintedPoints = data->size(); + + data->values().unlock(); +} + +void Plot::setIntervalLength( double interval ) +{ + if ( interval > 0.0 && interval != d_interval.width() ) + { + d_interval.setMaxValue( d_interval.minValue() + interval ); + setAxisScale( QwtAxis::xBottom, + d_interval.minValue(), d_interval.maxValue() ); + + replot(); + } +} + +void Plot::updateCurve() +{ + CurveData *data = static_cast( d_curve->data() ); + data->values().lock(); + + const int numPoints = data->size(); + if ( numPoints > d_paintedPoints ) + { + const bool doClip = !canvas()->testAttribute( Qt::WA_PaintOnScreen ); + if ( doClip ) + { + /* + Depending on the platform setting a clip might be an important + performance issue. F.e. for Qt Embedded this reduces the + part of the backing store that has to be copied out - maybe + to an unaccelerated frame buffer device. + */ + + const QwtScaleMap xMap = canvasMap( d_curve->xAxis() ); + const QwtScaleMap yMap = canvasMap( d_curve->yAxis() ); + + QRectF br = qwtBoundingRect( *data, + d_paintedPoints - 1, numPoints - 1 ); + + const QRect clipRect = QwtScaleMap::transform( xMap, yMap, br ).toRect(); + d_directPainter->setClipRegion( clipRect ); + } + + d_directPainter->drawSeries( d_curve, + d_paintedPoints - 1, numPoints - 1 ); + d_paintedPoints = numPoints; + } + + data->values().unlock(); +} + +void Plot::incrementInterval() +{ + d_interval = QwtInterval( d_interval.maxValue(), + d_interval.maxValue() + d_interval.width() ); + + CurveData *data = static_cast( d_curve->data() ); + data->values().clearStaleValues( d_interval.minValue() ); + + // To avoid, that the grid is jumping, we disable + // the autocalculation of the ticks and shift them + // manually instead. + + QwtScaleDiv scaleDiv = axisScaleDiv( QwtAxis::xBottom ); + scaleDiv.setInterval( d_interval ); + + for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ ) + { + QList ticks = scaleDiv.ticks( i ); + for ( int j = 0; j < ticks.size(); j++ ) + ticks[j] += d_interval.width(); + scaleDiv.setTicks( i, ticks ); + } + setAxisScaleDiv( QwtAxis::xBottom, scaleDiv ); + + d_origin->setValue( d_interval.minValue() + d_interval.width() / 2.0, 0.0 ); + + d_paintedPoints = 0; + replot(); +} + +void Plot::timerEvent( QTimerEvent *event ) +{ + if ( event->timerId() == d_timerId ) + { + updateCurve(); + + const double elapsed = d_clock.elapsed() / 1000.0; + if ( elapsed > d_interval.maxValue() ) + incrementInterval(); + + return; + } + + QwtPlot::timerEvent( event ); +} + +void Plot::resizeEvent( QResizeEvent *event ) +{ + d_directPainter->reset(); + QwtPlot::resizeEvent( event ); +} + +void Plot::showEvent( QShowEvent * ) +{ + replot(); +} + +bool Plot::eventFilter( QObject *object, QEvent *event ) +{ + if ( object == canvas() && + event->type() == QEvent::PaletteChange ) + { + d_curve->setPen( canvas()->palette().color( QPalette::WindowText ) ); + } + + return QwtPlot::eventFilter( object, event ); +} diff --git a/qwt/examples/oscilloscope/plot.h b/qwt/examples/oscilloscope/plot.h new file mode 100644 index 000000000..16a53b8d7 --- /dev/null +++ b/qwt/examples/oscilloscope/plot.h @@ -0,0 +1,44 @@ +#include +#include +#include + +class QwtPlotCurve; +class QwtPlotMarker; +class QwtPlotDirectPainter; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget * = NULL ); + virtual ~Plot(); + + void start(); + virtual void replot(); + + virtual bool eventFilter( QObject *, QEvent * ); + +public Q_SLOTS: + void setIntervalLength( double ); + +protected: + virtual void showEvent( QShowEvent * ); + virtual void resizeEvent( QResizeEvent * ); + virtual void timerEvent( QTimerEvent * ); + +private: + void updateCurve(); + void incrementInterval(); + + QwtPlotMarker *d_origin; + QwtPlotCurve *d_curve; + int d_paintedPoints; + + QwtPlotDirectPainter *d_directPainter; + + QwtInterval d_interval; + int d_timerId; + + QwtSystemClock d_clock; +}; diff --git a/qwt/examples/oscilloscope/samplingthread.cpp b/qwt/examples/oscilloscope/samplingthread.cpp new file mode 100644 index 000000000..7c0733ccb --- /dev/null +++ b/qwt/examples/oscilloscope/samplingthread.cpp @@ -0,0 +1,54 @@ +#include "samplingthread.h" +#include "signaldata.h" +#include +#include + +#if QT_VERSION < 0x040600 +#define qFastSin(x) ::sin(x) +#endif + +SamplingThread::SamplingThread( QObject *parent ): + QwtSamplingThread( parent ), + d_frequency( 5.0 ), + d_amplitude( 20.0 ) +{ +} + +void SamplingThread::setFrequency( double frequency ) +{ + d_frequency = frequency; +} + +double SamplingThread::frequency() const +{ + return d_frequency; +} + +void SamplingThread::setAmplitude( double amplitude ) +{ + d_amplitude = amplitude; +} + +double SamplingThread::amplitude() const +{ + return d_amplitude; +} + +void SamplingThread::sample( double elapsed ) +{ + if ( d_frequency > 0.0 ) + { + const QPointF s( elapsed, value( elapsed ) ); + SignalData::instance().append( s ); + } +} + +double SamplingThread::value( double timeStamp ) const +{ + const double period = 1.0 / d_frequency; + + const double x = ::fmod( timeStamp, period ); + const double v = d_amplitude * qFastSin( x / period * 2 * M_PI ); + + return v; +} diff --git a/qwt/examples/oscilloscope/samplingthread.h b/qwt/examples/oscilloscope/samplingthread.h new file mode 100644 index 000000000..141611322 --- /dev/null +++ b/qwt/examples/oscilloscope/samplingthread.h @@ -0,0 +1,25 @@ +#include + +class SamplingThread: public QwtSamplingThread +{ + Q_OBJECT + +public: + SamplingThread( QObject *parent = NULL ); + + double frequency() const; + double amplitude() const; + +public Q_SLOTS: + void setAmplitude( double ); + void setFrequency( double ); + +protected: + virtual void sample( double elapsed ); + +private: + virtual double value( double timeStamp ) const; + + double d_frequency; + double d_amplitude; +}; diff --git a/qwt/examples/oscilloscope/signaldata.cpp b/qwt/examples/oscilloscope/signaldata.cpp new file mode 100644 index 000000000..b5ef07f98 --- /dev/null +++ b/qwt/examples/oscilloscope/signaldata.cpp @@ -0,0 +1,133 @@ +#include "signaldata.h" +#include +#include +#include + +class SignalData::PrivateData +{ +public: + PrivateData(): + boundingRect( 1.0, 1.0, -2.0, -2.0 ) // invalid + { + values.reserve( 1000 ); + } + + inline void append( const QPointF &sample ) + { + values.append( sample ); + + // adjust the bounding rectangle + + if ( boundingRect.width() < 0 || boundingRect.height() < 0 ) + { + boundingRect.setRect( sample.x(), sample.y(), 0.0, 0.0 ); + } + else + { + boundingRect.setRight( sample.x() ); + + if ( sample.y() > boundingRect.bottom() ) + boundingRect.setBottom( sample.y() ); + + if ( sample.y() < boundingRect.top() ) + boundingRect.setTop( sample.y() ); + } + } + + QReadWriteLock lock; + + QVector values; + QRectF boundingRect; + + QMutex mutex; // protecting pendingValues + QVector pendingValues; +}; + +SignalData::SignalData() +{ + d_data = new PrivateData(); +} + +SignalData::~SignalData() +{ + delete d_data; +} + +int SignalData::size() const +{ + return d_data->values.size(); +} + +QPointF SignalData::value( int index ) const +{ + return d_data->values[index]; +} + +QRectF SignalData::boundingRect() const +{ + return d_data->boundingRect; +} + +void SignalData::lock() +{ + d_data->lock.lockForRead(); +} + +void SignalData::unlock() +{ + d_data->lock.unlock(); +} + +void SignalData::append( const QPointF &sample ) +{ + d_data->mutex.lock(); + d_data->pendingValues += sample; + + const bool isLocked = d_data->lock.tryLockForWrite(); + if ( isLocked ) + { + const int numValues = d_data->pendingValues.size(); + const QPointF *pendingValues = d_data->pendingValues.data(); + + for ( int i = 0; i < numValues; i++ ) + d_data->append( pendingValues[i] ); + + d_data->pendingValues.clear(); + + d_data->lock.unlock(); + } + + d_data->mutex.unlock(); +} + +void SignalData::clearStaleValues( double limit ) +{ + d_data->lock.lockForWrite(); + + d_data->boundingRect = QRectF( 1.0, 1.0, -2.0, -2.0 ); // invalid + + const QVector values = d_data->values; + d_data->values.clear(); + d_data->values.reserve( values.size() ); + + int index; + for ( index = values.size() - 1; index >= 0; index-- ) + { + if ( values[index].x() < limit ) + break; + } + + if ( index > 0 ) + d_data->append( values[index++] ); + + while ( index < values.size() - 1 ) + d_data->append( values[index++] ); + + d_data->lock.unlock(); +} + +SignalData &SignalData::instance() +{ + static SignalData valueVector; + return valueVector; +} diff --git a/qwt/examples/oscilloscope/signaldata.h b/qwt/examples/oscilloscope/signaldata.h new file mode 100644 index 000000000..61b05ade4 --- /dev/null +++ b/qwt/examples/oscilloscope/signaldata.h @@ -0,0 +1,33 @@ +#ifndef _SIGNAL_DATA_H_ +#define _SIGNAL_DATA_H_ 1 + +#include + +class SignalData +{ +public: + static SignalData &instance(); + + void append( const QPointF &pos ); + void clearStaleValues( double min ); + + int size() const; + QPointF value( int index ) const; + + QRectF boundingRect() const; + + void lock(); + void unlock(); + +private: + SignalData(); + SignalData( const SignalData & ); + SignalData &operator=( const SignalData & ); + + virtual ~SignalData(); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/examples/oscilloscope/wheelbox.cpp b/qwt/examples/oscilloscope/wheelbox.cpp new file mode 100644 index 000000000..44ea3a0e7 --- /dev/null +++ b/qwt/examples/oscilloscope/wheelbox.cpp @@ -0,0 +1,102 @@ +#include "wheelbox.h" +#include +#include +#include +#include +#include +#include + +class Wheel: public QwtWheel +{ +public: + Wheel( WheelBox *parent ): + QwtWheel( parent ) + { + setFocusPolicy( Qt::WheelFocus ); + parent->installEventFilter( this ); + } + + virtual bool eventFilter( QObject *object, QEvent *event ) + { + if ( event->type() == QEvent::Wheel ) + { + const QWheelEvent *we = static_cast( event ); + + QWheelEvent wheelEvent( QPoint( 5, 5 ), we->delta(), + we->buttons(), we->modifiers(), + we->orientation() ); + + QApplication::sendEvent( this, &wheelEvent ); + return true; + } + return QwtWheel::eventFilter( object, event ); + } +}; + +WheelBox::WheelBox( const QString &title, + double min, double max, double stepSize, QWidget *parent ): + QWidget( parent ) +{ + + d_number = new QLCDNumber( this ); + d_number->setSegmentStyle( QLCDNumber::Filled ); + d_number->setAutoFillBackground( true ); + d_number->setFixedHeight( d_number->sizeHint().height() * 2 ); + d_number->setFocusPolicy( Qt::WheelFocus ); + + QPalette pal( Qt::black ); + pal.setColor( QPalette::WindowText, Qt::green ); + d_number->setPalette( pal ); + + d_wheel = new Wheel( this ); + d_wheel->setOrientation( Qt::Vertical ); + d_wheel->setInverted( true ); + d_wheel->setRange( min, max ); + d_wheel->setSingleStep( stepSize ); + d_wheel->setPageStepCount( 5 ); + d_wheel->setFixedHeight( d_number->height() ); + + d_number->setFocusProxy( d_wheel ); + + QFont font( "Helvetica", 10 ); + font.setBold( true ); + + d_label = new QLabel( title, this ); + d_label->setFont( font ); + + QHBoxLayout *hLayout = new QHBoxLayout; + hLayout->setContentsMargins( 0, 0, 0, 0 ); + hLayout->setSpacing( 2 ); + hLayout->addWidget( d_number, 10 ); + hLayout->addWidget( d_wheel ); + + QVBoxLayout *vLayout = new QVBoxLayout( this ); + vLayout->addLayout( hLayout, 10 ); + vLayout->addWidget( d_label, 0, Qt::AlignTop | Qt::AlignHCenter ); + + connect( d_wheel, SIGNAL( valueChanged( double ) ), + d_number, SLOT( display( double ) ) ); + connect( d_wheel, SIGNAL( valueChanged( double ) ), + this, SIGNAL( valueChanged( double ) ) ); +} + +void WheelBox::setTheme( const QColor &color ) +{ + d_wheel->setPalette( color ); +} + +QColor WheelBox::theme() const +{ + return d_wheel->palette().color( QPalette::Window ); +} + +void WheelBox::setValue( double value ) +{ + d_wheel->setValue( value ); + d_number->display( value ); +} + +double WheelBox::value() const +{ + return d_wheel->value(); +} diff --git a/qwt/examples/oscilloscope/wheelbox.h b/qwt/examples/oscilloscope/wheelbox.h new file mode 100644 index 000000000..5331692a3 --- /dev/null +++ b/qwt/examples/oscilloscope/wheelbox.h @@ -0,0 +1,40 @@ +#ifndef _WHEELBOX_H_ +#define _WHEELBOX_H_ + +#include + +class QwtWheel; +class QLabel; +class QLCDNumber; + +class WheelBox: public QWidget +{ + Q_OBJECT + Q_PROPERTY( QColor theme READ theme WRITE setTheme ) + +public: + WheelBox( const QString &title, + double min, double max, double stepSize, + QWidget *parent = NULL ); + + void setTheme( const QColor & ); + QColor theme() const; + + void setUnit( const QString & ); + QString unit() const; + + void setValue( double value ); + double value() const; + +Q_SIGNALS: + double valueChanged( double ); + +private: + QLCDNumber *d_number; + QwtWheel *d_wheel; + QLabel *d_label; + + QString d_unit; +}; + +#endif diff --git a/qwt/examples/radio/ampfrm.cpp b/qwt/examples/radio/ampfrm.cpp new file mode 100644 index 000000000..7dffc749a --- /dev/null +++ b/qwt/examples/radio/ampfrm.cpp @@ -0,0 +1,201 @@ +#include "ampfrm.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if QT_VERSION < 0x040600 +#define qFastSin(x) ::sin(x) +#define qFastCos(x) ::cos(x) +#endif + +class Knob: public QWidget +{ +public: + Knob( const QString &title, double min, double max, QWidget *parent ): + QWidget( parent ) + { + d_knob = new QwtKnob( this ); + d_knob->setScale( min, max ); + d_knob->setTotalSteps( 0 ); // disable + d_knob->setScaleMaxMajor( 10 ); + + d_knob->setKnobStyle( QwtKnob::Raised ); + d_knob->setKnobWidth( 50 ); + d_knob->setBorderWidth( 2 ); + d_knob->setMarkerStyle( QwtKnob::Notch ); + d_knob->setMarkerSize( 8 ); + + d_knob->scaleDraw()->setTickLength( QwtScaleDiv::MinorTick, 4 ); + d_knob->scaleDraw()->setTickLength( QwtScaleDiv::MediumTick, 4 ); + d_knob->scaleDraw()->setTickLength( QwtScaleDiv::MajorTick, 6 ); + + d_label = new QLabel( title, this ); + d_label->setAlignment( Qt::AlignTop | Qt::AlignHCenter ); + + setSizePolicy( QSizePolicy::MinimumExpanding, + QSizePolicy::MinimumExpanding ); + } + + virtual QSize sizeHint() const + { + QSize sz1 = d_knob->sizeHint(); + QSize sz2 = d_label->sizeHint(); + + const int w = qMax( sz1.width(), sz2.width() ); + const int h = sz1.height() + sz2.height(); + + int off = qCeil( d_knob->scaleDraw()->extent( d_knob->font() ) ); + off -= 10; // spacing + + return QSize( w, h - off ); + } + + void setValue( double value ) + { + d_knob->setValue( value ); + } + + double value() const + { + return d_knob->value(); + } + +protected: + virtual void resizeEvent( QResizeEvent *e ) + { + const QSize sz = e->size(); + + int h = d_label->sizeHint().height(); + + d_label->setGeometry( 0, sz.height() - h, sz.width(), h ); + + h = d_knob->sizeHint().height(); + int off = qCeil( d_knob->scaleDraw()->extent( d_knob->font() ) ); + off -= 10; // spacing + + d_knob->setGeometry( 0, d_label->pos().y() - h + off, + sz.width(), h ); + } + +private: + QwtKnob *d_knob; + QLabel *d_label; +}; + +class Thermo: public QWidget +{ +public: + Thermo( const QString &title, QWidget *parent ): + QWidget( parent ) + { + d_thermo = new QwtThermo( this ); + d_thermo->setPipeWidth( 6 ); + d_thermo->setScale( -40, 10 ); + d_thermo->setFillBrush( Qt::green ); + d_thermo->setAlarmBrush( Qt::red ); + d_thermo->setAlarmLevel( 0.0 ); + d_thermo->setAlarmEnabled( true ); + + QLabel *label = new QLabel( title, this ); + label->setAlignment( Qt::AlignTop | Qt::AlignLeft ); + + QVBoxLayout *layout = new QVBoxLayout( this ); + layout->setMargin( 0 ); + layout->setSpacing( 0 ); + layout->addWidget( d_thermo, 10 ); + layout->addWidget( label ); + } + + void setValue( double value ) + { + d_thermo->setValue( value ); + } + +private: + QwtThermo *d_thermo; +}; + +AmpFrame::AmpFrame( QWidget *p ): + QFrame( p ) +{ + d_knbVolume = new Knob( "Volume", 0.0, 10.0, this ); + d_knbBalance = new Knob( "Balance", -10.0, 10.0, this ); + d_knbTreble = new Knob( "Treble", -10.0, 10.0, this ); + d_knbBass = new Knob( "Bass", -10.0, 10.0, this ); + + d_thmLeft = new Thermo( "Left [dB]", this ); + d_thmRight = new Thermo( "Right [dB]", this ); + + QHBoxLayout *layout = new QHBoxLayout( this ); + layout->setSpacing( 0 ); + layout->setMargin( 10 ); + layout->addWidget( d_knbVolume ); + layout->addWidget( d_knbBalance); + layout->addWidget( d_knbTreble); + layout->addWidget( d_knbBass ); + layout->addSpacing( 20 ); + layout->addStretch( 10 ); + layout->addWidget( d_thmLeft ); + layout->addSpacing( 10 ); + layout->addWidget( d_thmRight ); + + d_knbVolume->setValue( 7.0 ); + ( void )startTimer( 50 ); +} + +void AmpFrame::timerEvent( QTimerEvent * ) +{ + static double phs = 0; + + // + // This amplifier generates its own input signal... + // + + const double sig_bass = ( 1.0 + 0.1 * d_knbBass->value() ) + * qFastSin( 13.0 * phs ); + const double sig_mid_l = qFastSin( 17.0 * phs ); + const double sig_mid_r = qFastCos( 17.5 * phs ); + const double sig_trbl_l = 0.5 * ( 1.0 + 0.1 * d_knbTreble->value() ) + * qFastSin( 35.0 * phs ); + const double sig_trbl_r = 0.5 * ( 1.0 + 0.1 * d_knbTreble->value() ) + * qFastSin( 34.0 * phs ); + + double sig_l = 0.05 * d_master * d_knbVolume->value() + * qwtSqr( sig_bass + sig_mid_l + sig_trbl_l ); + double sig_r = 0.05 * d_master * d_knbVolume->value() + * qwtSqr( sig_bass + sig_mid_r + sig_trbl_r ); + + double balance = 0.1 * d_knbBalance->value(); + if ( balance > 0 ) + sig_l *= ( 1.0 - balance ); + else + sig_r *= ( 1.0 + balance ); + + if ( sig_l > 0.01 ) + sig_l = 20.0 * log10( sig_l ); + else + sig_l = -40.0; + + if ( sig_r > 0.01 ) + sig_r = 20.0 * log10( sig_r ); + else + sig_r = - 40.0; + + d_thmLeft->setValue( sig_l ); + d_thmRight->setValue( sig_r ); + + phs += M_PI / 100; + if ( phs > M_PI ) + phs = 0; +} + +void AmpFrame::setMaster( double v ) +{ + d_master = v; +} diff --git a/qwt/examples/radio/ampfrm.h b/qwt/examples/radio/ampfrm.h new file mode 100644 index 000000000..abec2a5d3 --- /dev/null +++ b/qwt/examples/radio/ampfrm.h @@ -0,0 +1,29 @@ +#include + +class Knob; +class Thermo; + +class AmpFrame : public QFrame +{ + Q_OBJECT +public: + AmpFrame( QWidget * ); + +public Q_SLOTS: + void setMaster( double v ); + +protected: + void timerEvent( QTimerEvent * ); + +private: + Knob *d_knbVolume; + Knob *d_knbBalance; + Knob *d_knbTreble; + Knob *d_knbBass; + Thermo *d_thmLeft; + Thermo *d_thmRight; + double d_master; +}; + + + diff --git a/qwt/examples/radio/mainwindow.cpp b/qwt/examples/radio/mainwindow.cpp new file mode 100644 index 000000000..ecae45734 --- /dev/null +++ b/qwt/examples/radio/mainwindow.cpp @@ -0,0 +1,50 @@ +#include +#include "tunerfrm.h" +#include "ampfrm.h" +#include "mainwindow.h" + +MainWindow::MainWindow(): + QWidget() +{ + TunerFrame *frmTuner = new TunerFrame( this ); + frmTuner->setFrameStyle( QFrame::Panel | QFrame::Raised ); + + AmpFrame *frmAmp = new AmpFrame( this ); + frmAmp->setFrameStyle( QFrame::Panel | QFrame::Raised ); + + QVBoxLayout *layout = new QVBoxLayout( this ); + layout->setMargin( 0 ); + layout->setSpacing( 0 ); + layout->addWidget( frmTuner ); + layout->addWidget( frmAmp ); + + connect( frmTuner, SIGNAL( fieldChanged( double ) ), + frmAmp, SLOT( setMaster( double ) ) ); + + frmTuner->setFreq( 90.0 ); + + setPalette( QPalette( QColor( 192, 192, 192 ) ) ); + updateGradient(); +} + +void MainWindow::resizeEvent( QResizeEvent * ) +{ + // Qt 4.7.1: QGradient::StretchToDeviceMode is buggy on X11 + updateGradient(); +} + +void MainWindow::updateGradient() +{ + QPalette pal = palette(); + + const QColor buttonColor = pal.color( QPalette::Button ); + const QColor midLightColor = pal.color( QPalette::Midlight ); + + QLinearGradient gradient( rect().topLeft(), rect().topRight() ); + gradient.setColorAt( 0.0, midLightColor ); + gradient.setColorAt( 0.7, buttonColor ); + gradient.setColorAt( 1.0, buttonColor ); + + pal.setBrush( QPalette::Window, gradient ); + setPalette( pal ); +} diff --git a/qwt/examples/radio/mainwindow.h b/qwt/examples/radio/mainwindow.h new file mode 100644 index 000000000..b3da23559 --- /dev/null +++ b/qwt/examples/radio/mainwindow.h @@ -0,0 +1,15 @@ +#include + +class MainWindow : public QWidget +{ +public: + MainWindow(); + +protected: + virtual void resizeEvent( QResizeEvent * ); + +private: + void updateGradient(); +}; + + diff --git a/qwt/examples/radio/radio.cpp b/qwt/examples/radio/radio.cpp new file mode 100644 index 000000000..cfa60841f --- /dev/null +++ b/qwt/examples/radio/radio.cpp @@ -0,0 +1,12 @@ +#include +#include "mainwindow.h" + +int main ( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/qwt/examples/radio/radio.pro b/qwt/examples/radio/radio.pro new file mode 100644 index 000000000..5af596e6c --- /dev/null +++ b/qwt/examples/radio/radio.pro @@ -0,0 +1,23 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = radio + +HEADERS = \ + mainwindow.h \ + ampfrm.h \ + tunerfrm.h + +SOURCES = \ + mainwindow.cpp \ + ampfrm.cpp \ + tunerfrm.cpp \ + radio.cpp diff --git a/qwt/examples/radio/tunerfrm.cpp b/qwt/examples/radio/tunerfrm.cpp new file mode 100644 index 000000000..fced7df25 --- /dev/null +++ b/qwt/examples/radio/tunerfrm.cpp @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include +#include +#include "tunerfrm.h" + +#if QT_VERSION < 0x040600 +#define qFastSin(x) ::sin(x) +#define qFastCos(x) ::cos(x) +#endif + +class TuningThermo: public QWidget +{ +public: + TuningThermo( QWidget *parent ): + QWidget( parent ) + { + d_thermo = new QwtThermo( this ); + d_thermo->setOrientation( Qt::Horizontal ); + d_thermo->setScalePosition( QwtThermo::NoScale ); + d_thermo->setScale( 0.0, 1.0 ); + d_thermo->setFillBrush( Qt::green ); + + QLabel *label = new QLabel( "Tuning", this ); + label->setAlignment( Qt::AlignCenter ); + + QVBoxLayout *layout = new QVBoxLayout( this ); + layout->setMargin( 0 ); + layout->addWidget( d_thermo ); + layout->addWidget( label ); + + setFixedWidth( 3 * label->sizeHint().width() ); + } + + void setValue( double value ) + { + d_thermo->setValue( value ); + } + +private: + QwtThermo *d_thermo; +}; + +TunerFrame::TunerFrame( QWidget *parent ): + QFrame( parent ) +{ + const double freqMin = 87.5; + const double freqMax = 108; + + d_sliderFrequency = new QwtSlider( this ); + d_sliderFrequency->setOrientation( Qt::Horizontal ); + d_sliderFrequency->setScalePosition( QwtSlider::TrailingScale ); + d_sliderFrequency->setScale( freqMin, freqMax ); + d_sliderFrequency->setTotalSteps( + qRound( ( freqMax - freqMin ) / 0.01 ) ); + d_sliderFrequency->setSingleSteps( 1 ); + d_sliderFrequency->setPageSteps( 10 ); + d_sliderFrequency->setScaleMaxMinor( 5 ); + d_sliderFrequency->setScaleMaxMajor( 12 ); + d_sliderFrequency->setHandleSize( QSize( 80, 20 ) ); + d_sliderFrequency->setBorderWidth( 1 ); + + d_thermoTune = new TuningThermo( this ); + + d_wheelFrequency = new QwtWheel( this ); + d_wheelFrequency->setMass( 0.5 ); + d_wheelFrequency->setRange( 87.5, 108 ); + d_wheelFrequency->setSingleStep( 0.01 ); + d_wheelFrequency->setPageStepCount( 10 ); + d_wheelFrequency->setTotalAngle( 3600.0 ); + d_wheelFrequency->setFixedHeight( 30 ); + + + connect( d_wheelFrequency, SIGNAL( valueChanged( double ) ), SLOT( adjustFreq( double ) ) ); + connect( d_sliderFrequency, SIGNAL( valueChanged( double ) ), SLOT( adjustFreq( double ) ) ); + + QVBoxLayout *mainLayout = new QVBoxLayout( this ); + mainLayout->setMargin( 10 ); + mainLayout->setSpacing( 5 ); + mainLayout->addWidget( d_sliderFrequency ); + + QHBoxLayout *hLayout = new QHBoxLayout; + hLayout->setMargin( 0 ); + hLayout->addWidget( d_thermoTune, 0 ); + hLayout->addStretch( 5 ); + hLayout->addWidget( d_wheelFrequency, 2 ); + + mainLayout->addLayout( hLayout ); +} + +void TunerFrame::adjustFreq( double frq ) +{ + const double factor = 13.0 / ( 108 - 87.5 ); + + const double x = ( frq - 87.5 ) * factor; + const double field = qwtSqr( qFastSin( x ) * qFastCos( 4.0 * x ) ); + + d_thermoTune->setValue( field ); + + if ( d_sliderFrequency->value() != frq ) + d_sliderFrequency->setValue( frq ); + if ( d_wheelFrequency->value() != frq ) + d_wheelFrequency->setValue( frq ); + + Q_EMIT fieldChanged( field ); +} + +void TunerFrame::setFreq( double frq ) +{ + d_wheelFrequency->setValue( frq ); +} diff --git a/qwt/examples/radio/tunerfrm.h b/qwt/examples/radio/tunerfrm.h new file mode 100644 index 000000000..48dd1bcd8 --- /dev/null +++ b/qwt/examples/radio/tunerfrm.h @@ -0,0 +1,30 @@ +#include + +class QwtWheel; +class QwtSlider; +class TuningThermo; + +class TunerFrame : public QFrame +{ + Q_OBJECT +public: + TunerFrame( QWidget *p ); + +Q_SIGNALS: + void fieldChanged( double f ); + +public Q_SLOTS: + void setFreq( double frq ); + +private Q_SLOTS: + void adjustFreq( double frq ); + +private: + QwtWheel *d_wheelFrequency; + TuningThermo *d_thermoTune; + QwtSlider *d_sliderFrequency; +}; + + + + diff --git a/qwt/examples/rasterview/main.cpp b/qwt/examples/rasterview/main.cpp new file mode 100644 index 000000000..da9d09f1b --- /dev/null +++ b/qwt/examples/rasterview/main.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include +#include "plot.h" + +class MainWindow: public QMainWindow +{ +public: + MainWindow( QWidget * = NULL ); +}; + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + Plot *plot = new Plot( this ); + setCentralWidget( plot ); + + QToolBar *toolBar = new QToolBar( this ); + + QComboBox *rasterBox = new QComboBox( toolBar ); + rasterBox->addItem( "Wikipedia" ); + + toolBar->addWidget( new QLabel( "Data ", toolBar ) ); + toolBar->addWidget( rasterBox ); + toolBar->addSeparator(); + + QComboBox *modeBox = new QComboBox( toolBar ); + modeBox->addItem( "Nearest Neighbour" ); + modeBox->addItem( "Bilinear Interpolation" ); + + toolBar->addWidget( new QLabel( "Resampling ", toolBar ) ); + toolBar->addWidget( modeBox ); + + toolBar->addSeparator(); + + QToolButton *btnExport = new QToolButton( toolBar ); + btnExport->setText( "Export" ); + btnExport->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolBar->addWidget( btnExport ); + + addToolBar( toolBar ); + + connect( modeBox, SIGNAL( activated( int ) ), plot, SLOT( setResampleMode( int ) ) ); + connect( btnExport, SIGNAL( clicked() ), plot, SLOT( exportPlot() ) ); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow mainWindow; + mainWindow.resize( 600, 400 ); + mainWindow.show(); + + return a.exec(); +} diff --git a/qwt/examples/rasterview/plot.cpp b/qwt/examples/rasterview/plot.cpp new file mode 100644 index 000000000..35568ad17 --- /dev/null +++ b/qwt/examples/rasterview/plot.cpp @@ -0,0 +1,112 @@ +#include "plot.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class RasterData: public QwtMatrixRasterData +{ +public: + RasterData() + { + const double matrix[] = + { + 1, 2, 4, 1, + 6, 3, 5, 2, + 4, 2, 1, 5, + 5, 4, 2, 3 + }; + + QVector values; + for ( uint i = 0; i < sizeof( matrix ) / sizeof( double ); i++ ) + values += matrix[i]; + + const int numColumns = 4; + setValueMatrix( values, numColumns ); + + setInterval( Qt::XAxis, + QwtInterval( -0.5, 3.5, QwtInterval::ExcludeMaximum ) ); + setInterval( Qt::YAxis, + QwtInterval( -0.5, 3.5, QwtInterval::ExcludeMaximum ) ); + setInterval( Qt::ZAxis, QwtInterval( 1.0, 6.0 ) ); + } +}; + +class ColorMap: public QwtLinearColorMap +{ +public: + ColorMap(): + QwtLinearColorMap( Qt::darkBlue, Qt::darkRed ) + { + addColorStop( 0.2, Qt::blue ); + addColorStop( 0.4, Qt::cyan ); + addColorStop( 0.6, Qt::yellow ); + addColorStop( 0.8, Qt::red ); + } +}; + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setBorderRadius( 10 ); + setCanvas( canvas ); + +#if 0 + QwtPlotGrid *grid = new QwtPlotGrid(); + grid->setPen( Qt::DotLine ); + grid->attach( this ); +#endif + + d_spectrogram = new QwtPlotSpectrogram(); + d_spectrogram->setRenderThreadCount( 0 ); // use system specific thread count + + d_spectrogram->setColorMap( new ColorMap() ); + + d_spectrogram->setData( new RasterData() ); + d_spectrogram->attach( this ); + + const QwtInterval zInterval = d_spectrogram->data()->interval( Qt::ZAxis ); + // A color bar on the right axis + QwtScaleWidget *rightAxis = axisWidget( QwtAxis::yRight ); + rightAxis->setColorBarEnabled( true ); + rightAxis->setColorBarWidth( 40 ); + rightAxis->setColorMap( zInterval, new ColorMap() ); + + setAxisScale( QwtAxis::yRight, zInterval.minValue(), zInterval.maxValue() ); + setAxisVisible( QwtAxis::yRight ); + + plotLayout()->setAlignCanvasToScales( true ); + + setAxisScale( QwtAxis::xBottom, 0.0, 3.0 ); + setAxisMaxMinor( QwtAxis::xBottom, 0 ); + setAxisScale( QwtAxis::yLeft, 0.0, 3.0 ); + setAxisMaxMinor( QwtAxis::yLeft, 0 ); + + QwtPlotMagnifier *magnifier = new QwtPlotMagnifier( canvas ); + magnifier->setAxisEnabled( QwtAxis::yRight, false ); + + QwtPlotPanner *panner = new QwtPlotPanner( canvas ); + panner->setAxisEnabled( QwtAxis::yRight, false ); +} + +void Plot::exportPlot() +{ + QwtPlotRenderer renderer; + renderer.exportTo( this, "rasterview.pdf" ); +} + +void Plot::setResampleMode( int mode ) +{ + RasterData *data = static_cast( d_spectrogram->data() ); + data->setResampleMode( + static_cast( mode ) ); + + replot(); +} diff --git a/qwt/examples/rasterview/plot.h b/qwt/examples/rasterview/plot.h new file mode 100644 index 000000000..140145f9f --- /dev/null +++ b/qwt/examples/rasterview/plot.h @@ -0,0 +1,17 @@ +#include +#include + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget * = NULL ); + +public Q_SLOTS: + void exportPlot(); + void setResampleMode( int ); + +private: + QwtPlotSpectrogram *d_spectrogram; +}; diff --git a/qwt/examples/rasterview/rasterview.pro b/qwt/examples/rasterview/rasterview.pro new file mode 100644 index 000000000..15fc8cc42 --- /dev/null +++ b/qwt/examples/rasterview/rasterview.pro @@ -0,0 +1,19 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = rasterview + +HEADERS = \ + plot.h + +SOURCES = \ + plot.cpp \ + main.cpp diff --git a/qwt/examples/realtime/README b/qwt/examples/realtime/README new file mode 100644 index 000000000..32c7f1393 --- /dev/null +++ b/qwt/examples/realtime/README @@ -0,0 +1,25 @@ +1) Incremental plots + +IncrementalPlot shows an example how to implement a plot that +displays growing data. + +The example produces random data when you push the start button. +With 'Timer' you can adjust the intervall between the +the generation of the points, with 'Points' you can set the number +of points to be generated. + +Unfortunately in Qt4 incremental painting is not possible with QPaintEngines +that doesn't support the QPaintEngine::PaintOutsidePaintEvent feature. +( These are all common paint engines beside the OpenGL engine, but this one +is not supported by Qwt yet. ) +That is the reason why you can see much faster repaints with Qt3. + +2) Stacked Zooming with scrollbars + +ScrollZoomer adds scrollbars for zooming. There are a couple of +reasons why the implementation is a hack and therefore the class +is not part of the Qwt lib, but it should be working with all +types of QwtPlots. Copy the code of scrollbar.[h|cpp] and +scrollzoomer.[h|cpp] to the application code. + +Uwe diff --git a/qwt/examples/realtime/clear.xpm b/qwt/examples/realtime/clear.xpm new file mode 100644 index 000000000..86c72b849 --- /dev/null +++ b/qwt/examples/realtime/clear.xpm @@ -0,0 +1,51 @@ +/* XPM */ +static const char *clear_xpm[] = { +/* width height num_colors chars_per_pixel */ +" 32 32 12 1", +/* colors */ +". c #000000", +"# c #004040", +"a c #303030", +"b c #400000", +"c c #404000", +"d c #585858", +"e c #808080", +"f c #a0a0a4", +"g c #bdbdbd", +"h c #c0c0c0", +"i c #dcdcdc", +"j c #ffffff", +/* pixels */ +"gggggggggggggggggggggggggggggggg", +"gggggggggggggg..gggggggggggggggg", +"gggggggggg....ficggggggggggggggg", +"ggggggg...fdad#ai......ggggggggg", +"gggg...fhjjidfbc#f.fffe...gggggg", +"ggg.fhjjjjihc#dhef.fhhhffe.ggggg", +"ggg.#jjjjjihhhhhe..ehhhfff.ggggg", +"ggg.#dffjjjjiihhcadehhfddd.ggggg", +"ggg.iiiffhfjjjjjhhhfdddddd.ggggg", +"ggg.#fjjiiffeeeeddddeeeddd.ggggg", +"gggg.#eeiiiiifffffffeee...gggggg", +"gggg.ffffffiifffffffddddd.gggggg", +"gggg.fffjfffeeeeddddeed.d.gggggg", +"gggg.fefiiiifhffeeeeded.d.gggggg", +"gggg.fefijhfhifefff.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fefijeffifeefe.ded.d.gggggg", +"gggg.fffijeffifeefe.deddd.gggggg", +"gggg.ffiijeffifeefeddedd#.gggggg", +"gggg.eiijjjeiifdffedded#..gggggg", +"ggggg..fjjiiiiffffedddd..ggggggg", +"ggggggg...fhhfffffdd...ggggggggg", +"gggggggggg..........gggggggggggg" +}; diff --git a/qwt/examples/realtime/incrementalplot.cpp b/qwt/examples/realtime/incrementalplot.cpp new file mode 100644 index 000000000..85a349afc --- /dev/null +++ b/qwt/examples/realtime/incrementalplot.cpp @@ -0,0 +1,124 @@ +#include +#include +#include +#include +#include +#include +#include "incrementalplot.h" +#include + +class CurveData: public QwtArraySeriesData +{ +public: + CurveData() + { + } + + virtual QRectF boundingRect() const + { + if ( d_boundingRect.width() < 0.0 ) + d_boundingRect = qwtBoundingRect( *this ); + + return d_boundingRect; + } + + inline void append( const QPointF &point ) + { + d_samples += point; + } + + void clear() + { + d_samples.clear(); + d_samples.squeeze(); + d_boundingRect = QRectF( 0.0, 0.0, -1.0, -1.0 ); + } +}; + +IncrementalPlot::IncrementalPlot( QWidget *parent ): + QwtPlot( parent ), + d_curve( NULL ) +{ + d_directPainter = new QwtPlotDirectPainter( this ); + + if ( QwtPainter::isX11GraphicsSystem() ) + { +#if QT_VERSION < 0x050000 + canvas()->setAttribute( Qt::WA_PaintOutsidePaintEvent, true ); +#endif + canvas()->setAttribute( Qt::WA_PaintOnScreen, true ); + } + + d_curve = new QwtPlotCurve( "Test Curve" ); + d_curve->setData( new CurveData() ); + showSymbols( true ); + + d_curve->attach( this ); + + setAutoReplot( false ); +} + +IncrementalPlot::~IncrementalPlot() +{ + delete d_curve; +} + +void IncrementalPlot::appendPoint( const QPointF &point ) +{ + CurveData *data = static_cast( d_curve->data() ); + data->append( point ); + + const bool doClip = !canvas()->testAttribute( Qt::WA_PaintOnScreen ); + if ( doClip ) + { + /* + Depending on the platform setting a clip might be an important + performance issue. F.e. for Qt Embedded this reduces the + part of the backing store that has to be copied out - maybe + to an unaccelerated frame buffer device. + */ + const QwtScaleMap xMap = canvasMap( d_curve->xAxis() ); + const QwtScaleMap yMap = canvasMap( d_curve->yAxis() ); + + QRegion clipRegion; + + const QSize symbolSize = d_curve->symbol()->size(); + QRect r( 0, 0, symbolSize.width() + 2, symbolSize.height() + 2 ); + + const QPointF center = + QwtScaleMap::transform( xMap, yMap, point ); + r.moveCenter( center.toPoint() ); + clipRegion += r; + + d_directPainter->setClipRegion( clipRegion ); + } + + d_directPainter->drawSeries( d_curve, + data->size() - 1, data->size() - 1 ); +} + +void IncrementalPlot::clearPoints() +{ + CurveData *data = static_cast( d_curve->data() ); + data->clear(); + + replot(); +} + +void IncrementalPlot::showSymbols( bool on ) +{ + if ( on ) + { + d_curve->setStyle( QwtPlotCurve::NoCurve ); + d_curve->setSymbol( new QwtSymbol( QwtSymbol::XCross, + Qt::NoBrush, QPen( Qt::white ), QSize( 4, 4 ) ) ); + } + else + { + d_curve->setPen( Qt::white ); + d_curve->setStyle( QwtPlotCurve::Dots ); + d_curve->setSymbol( NULL ); + } + + replot(); +} diff --git a/qwt/examples/realtime/incrementalplot.h b/qwt/examples/realtime/incrementalplot.h new file mode 100644 index 000000000..33f19a111 --- /dev/null +++ b/qwt/examples/realtime/incrementalplot.h @@ -0,0 +1,28 @@ +#ifndef _INCREMENTALPLOT_H_ +#define _INCREMENTALPLOT_H_ 1 + +#include + +class QwtPlotCurve; +class QwtPlotDirectPainter; + +class IncrementalPlot : public QwtPlot +{ + Q_OBJECT + +public: + IncrementalPlot( QWidget *parent = NULL ); + virtual ~IncrementalPlot(); + + void appendPoint( const QPointF & ); + void clearPoints(); + +public Q_SLOTS: + void showSymbols( bool ); + +private: + QwtPlotCurve *d_curve; + QwtPlotDirectPainter *d_directPainter; +}; + +#endif // _INCREMENTALPLOT_H_ diff --git a/qwt/examples/realtime/main.cpp b/qwt/examples/realtime/main.cpp new file mode 100644 index 000000000..3e33f781e --- /dev/null +++ b/qwt/examples/realtime/main.cpp @@ -0,0 +1,12 @@ +#include +#include "mainwindow.h" + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/qwt/examples/realtime/mainwindow.cpp b/qwt/examples/realtime/mainwindow.cpp new file mode 100644 index 000000000..b06be4c0b --- /dev/null +++ b/qwt/examples/realtime/mainwindow.cpp @@ -0,0 +1,193 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "randomplot.h" +#include "mainwindow.h" +#include "start.xpm" +#include "clear.xpm" + +class MyToolBar: public QToolBar +{ +public: + MyToolBar( MainWindow *parent ): + QToolBar( parent ) + { + } + void addSpacing( int spacing ) + { + QLabel *label = new QLabel( this ); + addWidget( label ); + label->setFixedWidth( spacing ); + } +}; + +class Counter: public QWidget +{ +public: + Counter( QWidget *parent, + const QString &prefix, const QString &suffix, + int min, int max, int step ): + QWidget( parent ) + { + QHBoxLayout *layout = new QHBoxLayout( this ); + + if ( !prefix.isEmpty() ) + layout->addWidget( new QLabel( prefix + " ", this ) ); + + d_counter = new QSpinBox( this ); + d_counter->setRange( min, max ); + d_counter->setSingleStep( step ); + layout->addWidget( d_counter ); + + if ( !suffix.isEmpty() ) + layout->addWidget( new QLabel( QString( " " ) + suffix, this ) ); + } + + void setValue( int value ) { d_counter->setValue( value ); } + int value() const { return d_counter->value(); } + +private: + QSpinBox *d_counter; +}; + +MainWindow::MainWindow() +{ + addToolBar( toolBar() ); +#ifndef QT_NO_STATUSBAR + ( void )statusBar(); +#endif + + d_plot = new RandomPlot( this ); + const int margin = 4; + d_plot->setContentsMargins( margin, margin, margin, margin ); + + setCentralWidget( d_plot ); + + connect( d_startAction, SIGNAL( toggled( bool ) ), this, SLOT( appendPoints( bool ) ) ); + connect( d_clearAction, SIGNAL( triggered() ), d_plot, SLOT( clear() ) ); + connect( d_symbolType, SIGNAL( toggled( bool ) ), d_plot, SLOT( showSymbols( bool ) ) ); + connect( d_plot, SIGNAL( running( bool ) ), this, SLOT( showRunning( bool ) ) ); + connect( d_plot, SIGNAL( elapsed( int ) ), this, SLOT( showElapsed( int ) ) ); + + initWhatsThis(); + + setContextMenuPolicy( Qt::NoContextMenu ); +} + +QToolBar *MainWindow::toolBar() +{ + MyToolBar *toolBar = new MyToolBar( this ); + + toolBar->setAllowedAreas( Qt::TopToolBarArea | Qt::BottomToolBarArea ); + setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + + d_startAction = new QAction( QPixmap( start_xpm ), "Start", toolBar ); + d_startAction->setCheckable( true ); + d_clearAction = new QAction( QPixmap( clear_xpm ), "Clear", toolBar ); + QAction *whatsThisAction = QWhatsThis::createAction( toolBar ); + whatsThisAction->setText( "Help" ); + + toolBar->addAction( d_startAction ); + toolBar->addAction( d_clearAction ); + toolBar->addAction( whatsThisAction ); + + setIconSize( QSize( 22, 22 ) ); + + QWidget *hBox = new QWidget( toolBar ); + + d_symbolType = new QCheckBox( "Symbols", hBox ); + d_symbolType->setChecked( true ); + + d_randomCount = + new Counter( hBox, "Points", QString::null, 1, 100000, 100 ); + d_randomCount->setValue( 1000 ); + + d_timerCount = new Counter( hBox, "Delay", "ms", 0, 100000, 100 ); + d_timerCount->setValue( 0 ); + + QHBoxLayout *layout = new QHBoxLayout( hBox ); + layout->setMargin( 0 ); + layout->setSpacing( 0 ); + layout->addSpacing( 10 ); + layout->addWidget( new QWidget( hBox ), 10 ); // spacer + layout->addWidget( d_symbolType ); + layout->addSpacing( 5 ); + layout->addWidget( d_randomCount ); + layout->addSpacing( 5 ); + layout->addWidget( d_timerCount ); + + showRunning( false ); + + toolBar->addWidget( hBox ); + + return toolBar; +} + +void MainWindow::appendPoints( bool on ) +{ + if ( on ) + d_plot->append( d_timerCount->value(), + d_randomCount->value() ); + else + d_plot->stop(); +} + +void MainWindow::showRunning( bool running ) +{ + d_randomCount->setEnabled( !running ); + d_timerCount->setEnabled( !running ); + d_startAction->setChecked( running ); + d_startAction->setText( running ? "Stop" : "Start" ); +} + +void MainWindow::showElapsed( int ms ) +{ + QString text; + text.setNum( ms ); + text += " ms"; + + statusBar()->showMessage( text ); +} + +void MainWindow::initWhatsThis() +{ + const char *text1 = + "Zooming is enabled until the selected area gets " + "too small for the significance on the axes.\n\n" + "You can zoom in using the left mouse button.\n" + "The middle mouse button is used to go back to the " + "previous zoomed area.\n" + "The right mouse button is used to unzoom completely."; + + const char *text2 = + "Number of random points that will be generated."; + + const char *text3 = + "Delay between the generation of two random points."; + + const char *text4 = + "Start generation of random points.\n\n" + "The intention of this example is to show how to implement " + "growing curves. The points will be generated and displayed " + "one after the other.\n" + "To check the performance, a small delay and a large number " + "of points are useful. To watch the curve growing, a delay " + " > 300 ms and less points are better.\n" + "To inspect the curve, stacked zooming is implemented using the " + "mouse buttons on the plot."; + + const char *text5 = "Remove all points."; + + d_plot->setWhatsThis( text1 ); + d_randomCount->setWhatsThis( text2 ); + d_timerCount->setWhatsThis( text3 ); + d_startAction->setWhatsThis( text4 ); + d_clearAction->setWhatsThis( text5 ); +} + diff --git a/qwt/examples/realtime/mainwindow.h b/qwt/examples/realtime/mainwindow.h new file mode 100644 index 000000000..bc6975515 --- /dev/null +++ b/qwt/examples/realtime/mainwindow.h @@ -0,0 +1,37 @@ +#ifndef _MAINWINDOW_H_ +#define _MAINWINDOW_H_ 1 + +#include +#include + +class QSpinBox; +class QPushButton; +class RandomPlot; +class Counter; +class QCheckBox; + +class MainWindow: public QMainWindow +{ + Q_OBJECT +public: + MainWindow(); + +private Q_SLOTS: + void showRunning( bool ); + void appendPoints( bool ); + void showElapsed( int ); + +private: + QToolBar *toolBar(); + void initWhatsThis(); + +private: + Counter *d_randomCount; + Counter *d_timerCount; + QCheckBox *d_symbolType; + QAction *d_startAction; + QAction *d_clearAction; + RandomPlot *d_plot; +}; + +#endif diff --git a/qwt/examples/realtime/randomplot.cpp b/qwt/examples/realtime/randomplot.cpp new file mode 100644 index 000000000..f08d6141a --- /dev/null +++ b/qwt/examples/realtime/randomplot.cpp @@ -0,0 +1,141 @@ +#include +#include +#include +#include +#include +#include +#include +#include "scrollzoomer.h" +#include "randomplot.h" + +const unsigned int c_rangeMax = 1000; + +class Zoomer: public ScrollZoomer +{ +public: + Zoomer( QWidget *canvas ): + ScrollZoomer( canvas ) + { +#if 0 + setRubberBandPen( QPen( Qt::red, 2, Qt::DotLine ) ); +#else + setRubberBandPen( QPen( Qt::red ) ); +#endif + } + + virtual QwtText trackerTextF( const QPointF &pos ) const + { + QColor bg( Qt::white ); + + QwtText text = QwtPlotZoomer::trackerTextF( pos ); + text.setBackgroundBrush( QBrush( bg ) ); + return text; + } + + virtual void rescale() + { + QwtScaleWidget *scaleWidget = plot()->axisWidget( yAxis() ); + QwtScaleDraw *sd = scaleWidget->scaleDraw(); + + double minExtent = 0.0; + if ( zoomRectIndex() > 0 ) + { + // When scrolling in vertical direction + // the plot is jumping in horizontal direction + // because of the different widths of the labels + // So we better use a fixed extent. + + minExtent = sd->spacing() + sd->maxTickLength() + 1; + minExtent += sd->labelSize( + scaleWidget->font(), c_rangeMax ).width(); + } + + sd->setMinimumExtent( minExtent ); + + ScrollZoomer::rescale(); + } +}; + +RandomPlot::RandomPlot( QWidget *parent ): + IncrementalPlot( parent ), + d_timer( 0 ), + d_timerCount( 0 ) +{ + setFrameStyle( QFrame::NoFrame ); + setLineWidth( 0 ); + + plotLayout()->setAlignCanvasToScales( true ); + + QwtPlotGrid *grid = new QwtPlotGrid; + grid->setMajorPen( Qt::gray, 0, Qt::DotLine ); + grid->attach( this ); + + setCanvasBackground( QColor( 29, 100, 141 ) ); // nice blue + + setAxisScale( QwtAxis::xBottom, 0, c_rangeMax ); + setAxisScale( QwtAxis::yLeft, 0, c_rangeMax ); + + replot(); + + // enable zooming + + ( void ) new Zoomer( canvas() ); +} + +QSize RandomPlot::sizeHint() const +{ + return QSize( 540, 400 ); +} + +void RandomPlot::appendPoint() +{ + double x = qrand() % c_rangeMax; + x += ( qrand() % 100 ) / 100; + + double y = qrand() % c_rangeMax; + y += ( qrand() % 100 ) / 100; + + IncrementalPlot::appendPoint( QPointF( x, y ) ); + + if ( --d_timerCount <= 0 ) + stop(); +} + +void RandomPlot::append( int timeout, int count ) +{ + if ( !d_timer ) + { + d_timer = new QTimer( this ); + connect( d_timer, SIGNAL( timeout() ), SLOT( appendPoint() ) ); + } + + d_timerCount = count; + + Q_EMIT running( true ); + d_timeStamp.start(); + + QwtPlotCanvas *plotCanvas = qobject_cast( canvas() ); + plotCanvas->setPaintAttribute( QwtPlotCanvas::BackingStore, false ); + + d_timer->start( timeout ); +} + +void RandomPlot::stop() +{ + Q_EMIT elapsed( d_timeStamp.elapsed() ); + + if ( d_timer ) + { + d_timer->stop(); + Q_EMIT running( false ); + } + + QwtPlotCanvas *plotCanvas = qobject_cast( canvas() ); + plotCanvas->setPaintAttribute( QwtPlotCanvas::BackingStore, true ); +} + +void RandomPlot::clear() +{ + clearPoints(); + replot(); +} diff --git a/qwt/examples/realtime/randomplot.h b/qwt/examples/realtime/randomplot.h new file mode 100644 index 000000000..44d1ba4bc --- /dev/null +++ b/qwt/examples/realtime/randomplot.h @@ -0,0 +1,39 @@ +#ifndef _RANDOMPLOT_H_ +#define _RANDOMPLOT_H_ 1 + +#include "incrementalplot.h" +#include + +class QTimer; + +class RandomPlot: public IncrementalPlot +{ + Q_OBJECT + +public: + RandomPlot( QWidget *parent ); + + virtual QSize sizeHint() const; + +Q_SIGNALS: + void running( bool ); + void elapsed( int ms ); + +public Q_SLOTS: + void clear(); + void stop(); + void append( int timeout, int count ); + +private Q_SLOTS: + void appendPoint(); + +private: + void initCurve(); + + QTimer *d_timer; + int d_timerCount; + + QTime d_timeStamp; +}; + +#endif // _RANDOMPLOT_H_ diff --git a/qwt/examples/realtime/realtime.pro b/qwt/examples/realtime/realtime.pro new file mode 100644 index 000000000..5f726d7b0 --- /dev/null +++ b/qwt/examples/realtime/realtime.pro @@ -0,0 +1,28 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = realtime + +HEADERS = \ + mainwindow.h \ + scrollzoomer.h \ + scrollbar.h \ + incrementalplot.h \ + randomplot.h + +SOURCES = \ + main.cpp \ + mainwindow.cpp \ + scrollzoomer.cpp \ + scrollbar.cpp \ + incrementalplot.cpp \ + randomplot.cpp + diff --git a/qwt/examples/realtime/scrollbar.cpp b/qwt/examples/realtime/scrollbar.cpp new file mode 100644 index 000000000..bd44c6c53 --- /dev/null +++ b/qwt/examples/realtime/scrollbar.cpp @@ -0,0 +1,170 @@ +#include +#include +#include "scrollbar.h" + +ScrollBar::ScrollBar( QWidget * parent ): + QScrollBar( parent ) +{ + init(); +} + +ScrollBar::ScrollBar( Qt::Orientation o, + QWidget *parent ): + QScrollBar( o, parent ) +{ + init(); +} + +ScrollBar::ScrollBar( double minBase, double maxBase, + Qt::Orientation o, QWidget *parent ): + QScrollBar( o, parent ) +{ + init(); + setBase( minBase, maxBase ); + moveSlider( minBase, maxBase ); +} + +void ScrollBar::init() +{ + d_inverted = orientation() == Qt::Vertical; + d_baseTicks = 1000000; + d_minBase = 0.0; + d_maxBase = 1.0; + moveSlider( d_minBase, d_maxBase ); + + connect( this, SIGNAL( sliderMoved( int ) ), SLOT( catchSliderMoved( int ) ) ); + connect( this, SIGNAL( valueChanged( int ) ), SLOT( catchValueChanged( int ) ) ); +} + +void ScrollBar::setInverted( bool inverted ) +{ + if ( d_inverted != inverted ) + { + d_inverted = inverted; + moveSlider( minSliderValue(), maxSliderValue() ); + } +} + +bool ScrollBar::isInverted() const +{ + return d_inverted; +} + +void ScrollBar::setBase( double min, double max ) +{ + if ( min != d_minBase || max != d_maxBase ) + { + d_minBase = min; + d_maxBase = max; + + moveSlider( minSliderValue(), maxSliderValue() ); + } +} + +void ScrollBar::moveSlider( double min, double max ) +{ + const int sliderTicks = qRound( ( max - min ) / + ( d_maxBase - d_minBase ) * d_baseTicks ); + + // setRange initiates a valueChanged of the scrollbars + // in some situations. So we block + // and unblock the signals. + + blockSignals( true ); + + setRange( sliderTicks / 2, d_baseTicks - sliderTicks / 2 ); + int steps = sliderTicks / 200; + if ( steps <= 0 ) + steps = 1; + + setSingleStep( steps ); + setPageStep( sliderTicks ); + + int tick = mapToTick( min + ( max - min ) / 2 ); + if ( isInverted() ) + tick = d_baseTicks - tick; + + setSliderPosition( tick ); + blockSignals( false ); +} + +double ScrollBar::minBaseValue() const +{ + return d_minBase; +} + +double ScrollBar::maxBaseValue() const +{ + return d_maxBase; +} + +void ScrollBar::sliderRange( int value, double &min, double &max ) const +{ + if ( isInverted() ) + value = d_baseTicks - value; + + const int visibleTicks = pageStep(); + + min = mapFromTick( value - visibleTicks / 2 ); + max = mapFromTick( value + visibleTicks / 2 ); +} + +double ScrollBar::minSliderValue() const +{ + double min, dummy; + sliderRange( value(), min, dummy ); + + return min; +} + +double ScrollBar::maxSliderValue() const +{ + double max, dummy; + sliderRange( value(), dummy, max ); + + return max; +} + +int ScrollBar::mapToTick( double v ) const +{ + const double pos = ( v - d_minBase ) / ( d_maxBase - d_minBase ) * d_baseTicks; + return static_cast( pos ); +} + +double ScrollBar::mapFromTick( int tick ) const +{ + return d_minBase + ( d_maxBase - d_minBase ) * tick / d_baseTicks; +} + +void ScrollBar::catchValueChanged( int value ) +{ + double min, max; + sliderRange( value, min, max ); + Q_EMIT valueChanged( orientation(), min, max ); +} + +void ScrollBar::catchSliderMoved( int value ) +{ + double min, max; + sliderRange( value, min, max ); + Q_EMIT sliderMoved( orientation(), min, max ); +} + +int ScrollBar::extent() const +{ + QStyleOptionSlider opt; + opt.init( this ); + opt.subControls = QStyle::SC_None; + opt.activeSubControls = QStyle::SC_None; + opt.orientation = orientation(); + opt.minimum = minimum(); + opt.maximum = maximum(); + opt.sliderPosition = sliderPosition(); + opt.sliderValue = value(); + opt.singleStep = singleStep(); + opt.pageStep = pageStep(); + opt.upsideDown = invertedAppearance(); + if ( orientation() == Qt::Horizontal ) + opt.state |= QStyle::State_Horizontal; + return style()->pixelMetric( QStyle::PM_ScrollBarExtent, &opt, this ); +} diff --git a/qwt/examples/realtime/scrollbar.h b/qwt/examples/realtime/scrollbar.h new file mode 100644 index 000000000..821cc79ef --- /dev/null +++ b/qwt/examples/realtime/scrollbar.h @@ -0,0 +1,53 @@ +#ifndef _SCROLLBAR_H +#define _SCROLLBAR_H 1 + +#include + +class ScrollBar: public QScrollBar +{ + Q_OBJECT + +public: + ScrollBar( QWidget *parent = NULL ); + ScrollBar( Qt::Orientation, QWidget *parent = NULL ); + ScrollBar( double minBase, double maxBase, + Qt::Orientation o, QWidget *parent = NULL ); + + void setInverted( bool ); + bool isInverted() const; + + double minBaseValue() const; + double maxBaseValue() const; + + double minSliderValue() const; + double maxSliderValue() const; + + int extent() const; + +Q_SIGNALS: + void sliderMoved( Qt::Orientation, double, double ); + void valueChanged( Qt::Orientation, double, double ); + +public Q_SLOTS: + virtual void setBase( double min, double max ); + virtual void moveSlider( double min, double max ); + +protected: + void sliderRange( int value, double &min, double &max ) const; + int mapToTick( double ) const; + double mapFromTick( int ) const; + +private Q_SLOTS: + void catchValueChanged( int value ); + void catchSliderMoved( int value ); + +private: + void init(); + + bool d_inverted; + double d_minBase; + double d_maxBase; + int d_baseTicks; +}; + +#endif diff --git a/qwt/examples/realtime/scrollzoomer.cpp b/qwt/examples/realtime/scrollzoomer.cpp new file mode 100644 index 000000000..dd219deaa --- /dev/null +++ b/qwt/examples/realtime/scrollzoomer.cpp @@ -0,0 +1,478 @@ +#include +#include +#include +#include +#include +#include "scrollbar.h" +#include "scrollzoomer.h" + +class ScrollData +{ +public: + ScrollData(): + scrollBar( NULL ), + position( ScrollZoomer::OppositeToScale ), + mode( Qt::ScrollBarAsNeeded ) + { + } + + ~ScrollData() + { + delete scrollBar; + } + + ScrollBar *scrollBar; + ScrollZoomer::ScrollBarPosition position; + Qt::ScrollBarPolicy mode; +}; + +ScrollZoomer::ScrollZoomer( QWidget *canvas ): + QwtPlotZoomer( canvas ), + d_cornerWidget( NULL ), + d_hScrollData( NULL ), + d_vScrollData( NULL ), + d_inZoom( false ) +{ + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + d_alignCanvasToScales[ axis ] = false; + + if ( !canvas ) + return; + + d_hScrollData = new ScrollData; + d_vScrollData = new ScrollData; +} + +ScrollZoomer::~ScrollZoomer() +{ + delete d_cornerWidget; + delete d_vScrollData; + delete d_hScrollData; +} + +void ScrollZoomer::rescale() +{ + QwtScaleWidget *xScale = plot()->axisWidget( xAxis() ); + QwtScaleWidget *yScale = plot()->axisWidget( yAxis() ); + + if ( zoomRectIndex() <= 0 ) + { + if ( d_inZoom ) + { + xScale->setMinBorderDist( 0, 0 ); + yScale->setMinBorderDist( 0, 0 ); + + QwtPlotLayout *layout = plot()->plotLayout(); + + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + layout->setAlignCanvasToScale( axis, d_alignCanvasToScales ); + + d_inZoom = false; + } + } + else + { + if ( !d_inZoom ) + { + /* + We set a minimum border distance. + Otherwise the canvas size changes when scrolling, + between situations where the major ticks are at + the canvas borders (requiring extra space for the label) + and situations where all labels can be painted below/top + or left/right of the canvas. + */ + int start, end; + + xScale->getBorderDistHint( start, end ); + xScale->setMinBorderDist( start, end ); + + yScale->getBorderDistHint( start, end ); + yScale->setMinBorderDist( start, end ); + + QwtPlotLayout *layout = plot()->plotLayout(); + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + { + d_alignCanvasToScales[axis] = + layout->alignCanvasToScale( axis ); + } + + layout->setAlignCanvasToScales( false ); + + d_inZoom = true; + } + } + + QwtPlotZoomer::rescale(); + updateScrollBars(); +} + +ScrollBar *ScrollZoomer::scrollBar( Qt::Orientation orientation ) +{ + ScrollBar *&sb = ( orientation == Qt::Vertical ) + ? d_vScrollData->scrollBar : d_hScrollData->scrollBar; + + if ( sb == NULL ) + { + sb = new ScrollBar( orientation, canvas() ); + sb->hide(); + connect( sb, + SIGNAL( valueChanged( Qt::Orientation, double, double ) ), + SLOT( scrollBarMoved( Qt::Orientation, double, double ) ) ); + } + return sb; +} + +ScrollBar *ScrollZoomer::horizontalScrollBar() const +{ + return d_hScrollData->scrollBar; +} + +ScrollBar *ScrollZoomer::verticalScrollBar() const +{ + return d_vScrollData->scrollBar; +} + +void ScrollZoomer::setHScrollBarMode( Qt::ScrollBarPolicy mode ) +{ + if ( hScrollBarMode() != mode ) + { + d_hScrollData->mode = mode; + updateScrollBars(); + } +} + +void ScrollZoomer::setVScrollBarMode( Qt::ScrollBarPolicy mode ) +{ + if ( vScrollBarMode() != mode ) + { + d_vScrollData->mode = mode; + updateScrollBars(); + } +} + +Qt::ScrollBarPolicy ScrollZoomer::hScrollBarMode() const +{ + return d_hScrollData->mode; +} + +Qt::ScrollBarPolicy ScrollZoomer::vScrollBarMode() const +{ + return d_vScrollData->mode; +} + +void ScrollZoomer::setHScrollBarPosition( ScrollBarPosition pos ) +{ + if ( d_hScrollData->position != pos ) + { + d_hScrollData->position = pos; + updateScrollBars(); + } +} + +void ScrollZoomer::setVScrollBarPosition( ScrollBarPosition pos ) +{ + if ( d_vScrollData->position != pos ) + { + d_vScrollData->position = pos; + updateScrollBars(); + } +} + +ScrollZoomer::ScrollBarPosition ScrollZoomer::hScrollBarPosition() const +{ + return d_hScrollData->position; +} + +ScrollZoomer::ScrollBarPosition ScrollZoomer::vScrollBarPosition() const +{ + return d_vScrollData->position; +} + +void ScrollZoomer::setCornerWidget( QWidget *w ) +{ + if ( w != d_cornerWidget ) + { + if ( canvas() ) + { + delete d_cornerWidget; + d_cornerWidget = w; + if ( d_cornerWidget->parent() != canvas() ) + d_cornerWidget->setParent( canvas() ); + + updateScrollBars(); + } + } +} + +QWidget *ScrollZoomer::cornerWidget() const +{ + return d_cornerWidget; +} + +bool ScrollZoomer::eventFilter( QObject *object, QEvent *event ) +{ + if ( object == canvas() ) + { + switch( event->type() ) + { + case QEvent::Resize: + { + int left, top, right, bottom; + canvas()->getContentsMargins( &left, &top, &right, &bottom ); + + QRect rect; + rect.setSize( static_cast( event )->size() ); + rect.adjust( left, top, -right, -bottom ); + + layoutScrollBars( rect ); + break; + } + case QEvent::ChildRemoved: + { + const QObject *child = + static_cast( event )->child(); + + if ( child == d_cornerWidget ) + d_cornerWidget = NULL; + else if ( child == d_hScrollData->scrollBar ) + d_hScrollData->scrollBar = NULL; + else if ( child == d_vScrollData->scrollBar ) + d_vScrollData->scrollBar = NULL; + break; + } + default: + break; + } + } + return QwtPlotZoomer::eventFilter( object, event ); +} + +bool ScrollZoomer::needScrollBar( Qt::Orientation orientation ) const +{ + Qt::ScrollBarPolicy mode; + double zoomMin, zoomMax, baseMin, baseMax; + + if ( orientation == Qt::Horizontal ) + { + mode = d_hScrollData->mode; + baseMin = zoomBase().left(); + baseMax = zoomBase().right(); + zoomMin = zoomRect().left(); + zoomMax = zoomRect().right(); + } + else + { + mode = d_vScrollData->mode; + baseMin = zoomBase().top(); + baseMax = zoomBase().bottom(); + zoomMin = zoomRect().top(); + zoomMax = zoomRect().bottom(); + } + + bool needed = false; + switch( mode ) + { + case Qt::ScrollBarAlwaysOn: + needed = true; + break; + case Qt::ScrollBarAlwaysOff: + needed = false; + break; + default: + { + if ( baseMin < zoomMin || baseMax > zoomMax ) + needed = true; + break; + } + } + return needed; +} + +void ScrollZoomer::updateScrollBars() +{ + if ( !canvas() ) + return; + + const int xAxis = QwtPlotZoomer::xAxis().pos; + const int yAxis = QwtPlotZoomer::yAxis().pos; + + int xScrollBarAxis = xAxis; + if ( hScrollBarPosition() == OppositeToScale ) + xScrollBarAxis = oppositeAxis( xScrollBarAxis ); + + int yScrollBarAxis = yAxis; + if ( vScrollBarPosition() == OppositeToScale ) + yScrollBarAxis = oppositeAxis( yScrollBarAxis ); + + + QwtPlotLayout *layout = plot()->plotLayout(); + + bool showHScrollBar = needScrollBar( Qt::Horizontal ); + if ( showHScrollBar ) + { + ScrollBar *sb = scrollBar( Qt::Horizontal ); + sb->setPalette( plot()->palette() ); + sb->setInverted( !plot()->axisScaleDiv( xAxis ).isIncreasing() ); + sb->setBase( zoomBase().left(), zoomBase().right() ); + sb->moveSlider( zoomRect().left(), zoomRect().right() ); + + if ( !sb->isVisibleTo( canvas() ) ) + { + sb->show(); + layout->setCanvasMargin( layout->canvasMargin( xScrollBarAxis ) + + sb->extent(), xScrollBarAxis ); + } + } + else + { + if ( horizontalScrollBar() ) + { + horizontalScrollBar()->hide(); + layout->setCanvasMargin( layout->canvasMargin( xScrollBarAxis ) + - horizontalScrollBar()->extent(), xScrollBarAxis ); + } + } + + bool showVScrollBar = needScrollBar( Qt::Vertical ); + if ( showVScrollBar ) + { + ScrollBar *sb = scrollBar( Qt::Vertical ); + sb->setPalette( plot()->palette() ); + sb->setInverted( !plot()->axisScaleDiv( yAxis ).isIncreasing() ); + sb->setBase( zoomBase().top(), zoomBase().bottom() ); + sb->moveSlider( zoomRect().top(), zoomRect().bottom() ); + + if ( !sb->isVisibleTo( canvas() ) ) + { + sb->show(); + layout->setCanvasMargin( layout->canvasMargin( yScrollBarAxis ) + + sb->extent(), yScrollBarAxis ); + } + } + else + { + if ( verticalScrollBar() ) + { + verticalScrollBar()->hide(); + layout->setCanvasMargin( layout->canvasMargin( yScrollBarAxis ) + - verticalScrollBar()->extent(), yScrollBarAxis ); + } + } + + if ( showHScrollBar && showVScrollBar ) + { + if ( d_cornerWidget == NULL ) + { + d_cornerWidget = new QWidget( canvas() ); + d_cornerWidget->setAutoFillBackground( true ); + d_cornerWidget->setPalette( plot()->palette() ); + } + d_cornerWidget->show(); + } + else + { + if ( d_cornerWidget ) + d_cornerWidget->hide(); + } + + layoutScrollBars( canvas()->contentsRect() ); + plot()->updateLayout(); +} + +void ScrollZoomer::layoutScrollBars( const QRect &rect ) +{ + int hPos = xAxis().pos; + if ( hScrollBarPosition() == OppositeToScale ) + hPos = oppositeAxis( hPos ); + + int vPos = yAxis().pos; + if ( vScrollBarPosition() == OppositeToScale ) + vPos = oppositeAxis( vPos ); + + ScrollBar *hScrollBar = horizontalScrollBar(); + ScrollBar *vScrollBar = verticalScrollBar(); + + const int hdim = hScrollBar ? hScrollBar->extent() : 0; + const int vdim = vScrollBar ? vScrollBar->extent() : 0; + + if ( hScrollBar && hScrollBar->isVisible() ) + { + int x = rect.x(); + int y = ( hPos == QwtAxis::xTop ) + ? rect.top() : rect.bottom() - hdim + 1; + int w = rect.width(); + + if ( vScrollBar && vScrollBar->isVisible() ) + { + if ( vPos == QwtAxis::yLeft ) + x += vdim; + w -= vdim; + } + + hScrollBar->setGeometry( x, y, w, hdim ); + } + if ( vScrollBar && vScrollBar->isVisible() ) + { + int pos = yAxis().pos; + if ( vScrollBarPosition() == OppositeToScale ) + pos = oppositeAxis( pos ); + + int x = ( vPos == QwtAxis::yLeft ) + ? rect.left() : rect.right() - vdim + 1; + int y = rect.y(); + + int h = rect.height(); + + if ( hScrollBar && hScrollBar->isVisible() ) + { + if ( hPos == QwtAxis::xTop ) + y += hdim; + + h -= hdim; + } + + vScrollBar->setGeometry( x, y, vdim, h ); + } + if ( hScrollBar && hScrollBar->isVisible() && + vScrollBar && vScrollBar->isVisible() ) + { + if ( d_cornerWidget ) + { + QRect cornerRect( + vScrollBar->pos().x(), hScrollBar->pos().y(), + vdim, hdim ); + d_cornerWidget->setGeometry( cornerRect ); + } + } +} + +void ScrollZoomer::scrollBarMoved( + Qt::Orientation o, double min, double max ) +{ + Q_UNUSED( max ); + + if ( o == Qt::Horizontal ) + moveTo( QPointF( min, zoomRect().top() ) ); + else + moveTo( QPointF( zoomRect().left(), min ) ); + + Q_EMIT zoomed( zoomRect() ); +} + +int ScrollZoomer::oppositeAxis( int axis ) const +{ + switch( axis ) + { + case QwtAxis::xBottom: + return QwtAxis::xTop; + case QwtAxis::xTop: + return QwtAxis::xBottom; + case QwtAxis::yLeft: + return QwtAxis::yRight; + case QwtAxis::yRight: + return QwtAxis::yLeft; + } + + return axis; +} diff --git a/qwt/examples/realtime/scrollzoomer.h b/qwt/examples/realtime/scrollzoomer.h new file mode 100644 index 000000000..1249906f5 --- /dev/null +++ b/qwt/examples/realtime/scrollzoomer.h @@ -0,0 +1,67 @@ +#ifndef _SCROLLZOOMER_H +#define _SCROLLZOOMER_H + +#include +#include +#include + +class ScrollData; +class ScrollBar; + +class ScrollZoomer: public QwtPlotZoomer +{ + Q_OBJECT +public: + enum ScrollBarPosition + { + AttachedToScale, + OppositeToScale + }; + + ScrollZoomer( QWidget * ); + virtual ~ScrollZoomer(); + + ScrollBar *horizontalScrollBar() const; + ScrollBar *verticalScrollBar() const; + + void setHScrollBarMode( Qt::ScrollBarPolicy ); + void setVScrollBarMode( Qt::ScrollBarPolicy ); + + Qt::ScrollBarPolicy vScrollBarMode () const; + Qt::ScrollBarPolicy hScrollBarMode () const; + + void setHScrollBarPosition( ScrollBarPosition ); + void setVScrollBarPosition( ScrollBarPosition ); + + ScrollBarPosition hScrollBarPosition() const; + ScrollBarPosition vScrollBarPosition() const; + + QWidget* cornerWidget() const; + virtual void setCornerWidget( QWidget * ); + + virtual bool eventFilter( QObject *, QEvent * ); + + virtual void rescale(); + +protected: + virtual ScrollBar *scrollBar( Qt::Orientation ); + virtual void updateScrollBars(); + virtual void layoutScrollBars( const QRect & ); + +private Q_SLOTS: + void scrollBarMoved( Qt::Orientation o, double min, double max ); + +private: + bool needScrollBar( Qt::Orientation ) const; + int oppositeAxis( int ) const; + + QWidget *d_cornerWidget; + + ScrollData *d_hScrollData; + ScrollData *d_vScrollData; + + bool d_inZoom; + bool d_alignCanvasToScales[ QwtAxis::PosCount ]; +}; + +#endif diff --git a/qwt/examples/realtime/start.xpm b/qwt/examples/realtime/start.xpm new file mode 100644 index 000000000..62146842a --- /dev/null +++ b/qwt/examples/realtime/start.xpm @@ -0,0 +1,266 @@ +/* XPM */ +static const char *start_xpm[] = { +/* width height num_colors chars_per_pixel */ +" 32 32 227 2", +/* colors */ +".. c #040204", +".# c #848684", +".a c #c4c2b4", +".b c #843a04", +".c c #444244", +".d c #ece2cc", +".e c #fca234", +".f c #c45e04", +".g c #bca27c", +".h c #646264", +".i c #e4c69c", +".j c #847254", +".k c #c4a684", +".l c #443e34", +".m c #a48e6c", +".n c #f4f2e4", +".o c #24261c", +".p c #a44a04", +".q c #c4825c", +".r c #644634", +".s c #b4b2ac", +".t c #747274", +".u c #844e2c", +".v c #ece6dc", +".w c #c4b6a4", +".x c #a49274", +".y c #343634", +".z c #fcd69c", +".A c #b4aa9c", +".B c #8c8e8c", +".C c #545254", +".D c #f4f2ec", +".E c #fcb67c", +".F c #e4965c", +".G c #e46634", +".H c #141614", +".I c #d4c2a4", +".J c #746a5c", +".K c #fcc2a4", +".L c #342a1c", +".M c #fc9204", +".N c #a45e2c", +".O c #94521c", +".P c #a4560c", +".Q c #645e54", +".R c #ec7a04", +".S c #f4deac", +".T c #5c462c", +".U c #bcaa8c", +".V c #d4be9c", +".W c #fcfaf4", +".X c #d4cab4", +".Y c #1c0a04", +".Z c #6c6a6c", +".0 c #e4caa4", +".1 c #2c2a1c", +".2 c #74462c", +".3 c #84562c", +".4 c #f4eee4", +".5 c #c4beb4", +".6 c #a49a84", +".7 c #f4ba7c", +".8 c #dc966c", +".9 c #948674", +"#. c #fc8a04", +"## c #f4eab4", +"#a c #fcb26c", +"#b c #c4ae94", +"#c c #f4e6d4", +"#d c #9c8e74", +"#e c #fc7e04", +"#f c #140604", +"#g c #b4a28c", +"#h c #6c625c", +"#i c #8c7e64", +"#j c #f4ae84", +"#k c #e4decc", +"#l c #ac5204", +"#m c #e48a4c", +"#n c #7c7a7c", +"#o c #ccba9c", +"#p c #fcd2b4", +"#q c #bcae9c", +"#r c #dcc6a4", +"#s c #ac723c", +"#t c #e4ceb4", +"#u c #ec9e74", +"#v c #8c8a8c", +"#w c #8c4204", +"#x c #4c4a34", +"#y c #7c3a04", +"#z c #fcfecc", +"#A c #2c221c", +"#B c #ac4e04", +"#C c #d48264", +"#D c #bcb2a4", +"#E c #a49684", +"#F c #b4aeac", +"#G c #5c5a5c", +"#H c #fcf2ec", +"#I c #fcb28c", +"#J c #7c6e5c", +"#K c #fcce9c", +"#L c #3c2e24", +"#M c #bc9e71", +"#N c #fc922c", +"#O c #bc622c", +"#P c #b45604", +"#Q c #f47a08", +"#R c #fcdeb8", +"#S c #544e44", +"#T c #fcfefc", +"#U c #e4ceaa", +"#V c #8c5a2c", +"#W c #e49e7c", +"#X c #f4eadb", +"#Y c #9c9284", +"#Z c #f4ae90", +"#0 c #c47e5c", +"#1 c #bc824c", +"#2 c #e47634", +"#3 c #e46e24", +"#4 c #b48e6c", +"#5 c #7c5a4c", +"#6 c #744e2c", +"#7 c #fcba9c", +"#8 c #cccacc", +"#9 c #f4722c", +"a. c #c46224", +"a# c #e47a54", +"aa c #ac663c", +"ab c #fce2cc", +"ac c #945634", +"ad c #fceacc", +"ae c #3c3e3c", +"af c #ec9e54", +"ag c #843e1c", +"ah c #fccab0", +"ai c #8c8274", +"aj c #4c4634", +"ak c #ecc2ac", +"al c #8c765c", +"am c #7c7264", +"an c #e49a7c", +"ao c #6c4e34", +"ap c #fc9a2c", +"aq c #4c4a4c", +"ar c #ccbea4", +"as c #fcf6dc", +"at c #3c3a3c", +"au c #949294", +"av c #fceebc", +"aw c #fcaa7c", +"ax c #ecdac8", +"ay c #0c0604", +"az c #fc8204", +"aA c #847664", +"aB c #e4d6c4", +"aC c #fcd2ac", +"aD c #1c1a14", +"aE c #342e2c", +"aF c #240e04", +"aG c #2c2e2c", +"aH c #fcbe7c", +"aI c #fc8e14", +"aJ c #fc7a14", +"aK c #944604", +"aL c #7c3e14", +"aM c #fcfadc", +"aN c #645244", +"aO c #bcb6b4", +"aP c #bc5604", +"aQ c #7c522c", +"aR c #cc8264", +"aS c #dccab0", +"aT c #ac9a84", +"aU c #f4e2cc", +"aV c #a45e3c", +"aW c #9c5634", +"aX c #fca634", +"aY c #c4aa89", +"aZ c #a44e07", +"a0 c #b4b6b4", +"a1 c #c4baa9", +"a2 c #a4967c", +"a3 c #b4aea4", +"a4 c #d4c6a8", +"a5 c #5c4a34", +"a6 c #bcae94", +"a7 c #845a2c", +"a8 c #948a7c", +"a9 c #c4b299", +"b. c #b4a690", +"b# c #6c6658", +"ba c #fcd6b4", +"bb c #2c261d", +"bc c #fcf6f0", +"bd c #fcb694", +"be c #fc9624", +"bf c #646664", +"bg c #747674", +"bh c #eceadc", +"bi c #545654", +"bj c #b49e7c", +"bk c #6c6e6c", +"bl c #fc8e04", +"bm c #fcb66c", +"bn c #7c7e7c", +"bo c #5c5e5c", +"bp c #8c8674", +"bq c #fc8604", +"br c #bc5a04", +"bs c #fca23c", +"bt c #443e3c", +"bu c #a4927c", +"bv c #b4aaa4", +"bw c #746a64", +"bx c #342a24", +"by c #fcfafc", +"bz c #2c2a24", +"bA c #a49a8c", +"bB c #bcbabc", +"bC c #9c8e7c", +"bD c #8c7e6c", +"bE c #ccbaa4", +"bF c #fcd2bc", +"bG c #fcb294", +/* pixels */ +"#Gbi#G.#bnbg.t.Zbfbf.hbo#G.Caqaq.c.C.C.C.C.C.C.C.C.C.C.Cbi#Gbi#G", +"#Gbi#Gbg#8#8.a#8#8#8#8#8#8#8#8.B#8#8#8#8#8#8#8#8#8#8#8.C#Gbi#Gbi", +"bi#Gbi#n#8#T#T#T#T#T#T#T#T#T#TbB#T#T#T#T#T#T#T#T#T#T#8aq#6afbm#z", +"#Gbi#Gbk#8#T#T#T#T#T#T#T#T#T#TbB#T#T#T#T#T#T#T#T#T#T#8#6af#aavaX", +"bi#Gbibk#8#T#T#T#T#T#T#T#T#T#TbB#T#T#T#T#T#T#T#T#T#T#6af#a##aX#.", +"#Gbi#Gbk#8#T#T#T#T#T#T#T#T#T#TbB#T#T#T#T#T#T#T#T#T.3af#a.S.e#.bq", +"#Gbi#G.Z#8#T#T#T#T#T#T#T#T#T#TbB#T#T#T#T#T#T#T#TaQaf#a#R.e#eazbq", +"bi#GbibkaubBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBa7af#aba.e#eazbq.M", +"#Gbi#G.Z#8#T#T#T#T#T#T#T#T#T#TbB#T#T#T#T#T#T#Vaf#ababs#ebqbq#.az", +"#Gbi#Gbf#8#T#T#T#T#T#T#T#T#T#TbB#T#T#Tby#T#saf#a#Kap#ebqbqbl#Q.f", +"bi#Gbi.Z#8#T#T#T#T#T#T#T#T#T#TbB#T#T#T#T.Naf#a.z#N#ebqbqbl.R.f#l", +"#Gbi#Gbf#8#T#T#T#T#T#T#T#T#T#TbB#T#T#T#1af.EaHbe#ebqbq#..Rbr#B#y", +"bi#Gbibf#8#T#T#T#T#T#T#T#T#T#TbB#T#T.F.7#jawaI#ebqbqbl.R#PaZ.b..", +"#Gbi#GbfaubBbBbBbBbBbBbBbBbBbBbBbBbG#RaMak#m#ebqbqbl#Q#P#B#w.Y.y", +"bi#Gbibf#8#T#T#T#T#T#T#T#T#T#TaObyaC.Wab#Z#2bqbq.M.RaP.p#way.y.y", +"#Gbi#G.h#8#T#T#T#T#T#T#T#T#Tbya0#I#Tad.K#j#2#QaJ.Rbr.p#yaF.y.yat", +"bi#Gbi.h#8#T#T#T#T#T#T#T#Tby.W.saCasba#Za#.G#9#3aPaZaK.Y.y.yat.c", +"#Gbi#Gbo#8#T#T#T#T#T#T#Tby.Wbc#I#T#p#7.8#0a.#O.P.paLay...yatbtaq", +"bi#Gbi.h#8#T#T#T#T#T#Tby.Wbc.DaCadah#W#0aa.O.2.ragaF#h..ataeaq.C", +"#Gbi#GboaubBbBbBbBbBaOa0.sa3bdasahanaRaV.u.Ta5ae#f.Q#S..aeaq.Cbi", +"bi#Gbibo#8#T#T#T#T.Wbcbc.D#HaCbF#uaRaWaQa5ajbt.HbDai#J..aq.Cbibi", +"#Gbi#Gbo#8#T#Tby.W.W.D#H.nbdab#u#Cac.uaN.o..bDaiaia8#i...Cbibi#G", +"bi#Gbibo#8#T#Tbybc.Dbc.n.4#4.8.q#5.r.l..#vbDaia8a2#g#d..bibi#Gbi", +"#Gbi#G#G#8#T.Wbc.D#H.D#X.j.Lao#5#L.H#vaibpbpbCaT.U#oa2..bi#Gbi#G", +"bi#Gbi#G#8.Wbc.D#H.n.4bjajaD#A...#bpai.9bC#E#ga9.V#r.gbb#Gbi#Gbi", +"#Gbi#Gbiaua0.s.s#Fa3bvaG....#vbwb#b#.JbwaA#i.9bC.m.xal.1bi#Gbi#G", +"bi#Gbi#G#8.D#H.4.4#X.v#x#v#qbAb##Y.6b.a6ar.I#r#r.0.i.g.Lbi#Gbi#G", +"bi#Gbibi#8.D.4.4#X#c.vax.X.AbAamb.#D#oa4aS#r.0.0.i.i#M#A#Gbi#Gbi", +"#Gbi#G.C#8.n.4#X#X.daUaBaS.wa6aiar#raS.0#U#U.0.i.0#r#Mbb#Gbi#Gbi", +"bi#Gbiaq#8.4#Xbh.v#c.d#kaB.Xa4buaS#U#t#U#U.0.0#r.i.i#Mbbbi#Gbi#G", +"#Gbi#Gae.a.a.5a1bE.w.w.w#ba6.U#iaYaYaY.k.g.g.g#M#M#M#M.Lbi#Gbi#G", +"bi#Gbi.HbxaEbxaEbz.LaEbzbzbbbzbbbbbxbb.Lbbbb.1.Lbb.1#Aay#Gbi#Gbi" +}; diff --git a/qwt/examples/refreshtest/circularbuffer.cpp b/qwt/examples/refreshtest/circularbuffer.cpp new file mode 100644 index 000000000..1fec47243 --- /dev/null +++ b/qwt/examples/refreshtest/circularbuffer.cpp @@ -0,0 +1,73 @@ +#include "circularbuffer.h" +#include + +CircularBuffer::CircularBuffer( double interval, size_t numPoints ): + d_y( NULL ), + d_referenceTime( 0.0 ), + d_startIndex( 0 ), + d_offset( 0.0 ) +{ + fill( interval, numPoints ); +} + +void CircularBuffer::fill( double interval, size_t numPoints ) +{ + if ( interval <= 0.0 || numPoints < 2 ) + return; + + d_values.resize( numPoints ); + d_values.fill( 0.0 ); + + if ( d_y ) + { + d_step = interval / ( numPoints - 2 ); + for ( size_t i = 0; i < numPoints; i++ ) + d_values[i] = d_y( i * d_step ); + } + + d_interval = interval; +} + +void CircularBuffer::setFunction( double( *y )( double ) ) +{ + d_y = y; +} + +void CircularBuffer::setReferenceTime( double timeStamp ) +{ + d_referenceTime = timeStamp; + + const double startTime = ::fmod( d_referenceTime, d_values.size() * d_step ); + + d_startIndex = int( startTime / d_step ); // floor + d_offset = ::fmod( startTime, d_step ); +} + +double CircularBuffer::referenceTime() const +{ + return d_referenceTime; +} + +size_t CircularBuffer::size() const +{ + return d_values.size(); +} + +QPointF CircularBuffer::sample( size_t i ) const +{ + const int size = d_values.size(); + + int index = d_startIndex + i; + if ( index >= size ) + index -= size; + + const double x = i * d_step - d_offset - d_interval; + const double y = d_values.data()[index]; + + return QPointF( x, y ); +} + +QRectF CircularBuffer::boundingRect() const +{ + return QRectF( -1.0, -d_interval, 2.0, d_interval ); +} \ No newline at end of file diff --git a/qwt/examples/refreshtest/circularbuffer.h b/qwt/examples/refreshtest/circularbuffer.h new file mode 100644 index 000000000..eae1a9f26 --- /dev/null +++ b/qwt/examples/refreshtest/circularbuffer.h @@ -0,0 +1,35 @@ +#ifndef _CIRCULAR_BUFFER_H_ +#define _CIRCULAR_BUFFER_H_ + +#include +#include + +class CircularBuffer: public QwtSeriesData +{ +public: + CircularBuffer( double interval = 10.0, size_t numPoints = 1000 ); + void fill( double interval, size_t numPoints ); + + void setReferenceTime( double ); + double referenceTime() const; + + virtual size_t size() const; + virtual QPointF sample( size_t i ) const; + + virtual QRectF boundingRect() const; + + void setFunction( double( *y )( double ) ); + +private: + double ( *d_y )( double ); + + double d_referenceTime; + double d_interval; + QVector d_values; + + double d_step; + int d_startIndex; + double d_offset; +}; + +#endif diff --git a/qwt/examples/refreshtest/main.cpp b/qwt/examples/refreshtest/main.cpp new file mode 100644 index 000000000..0a306848e --- /dev/null +++ b/qwt/examples/refreshtest/main.cpp @@ -0,0 +1,30 @@ +#include "mainwindow.h" +#include + +#ifndef QWT_NO_OPENGL +#if QT_VERSION >= 0x040600 && QT_VERSION < 0x050000 +#define USE_OPENGL 1 +#endif +#endif + +#if USE_OPENGL +#include +#endif + +int main( int argc, char **argv ) +{ +#if USE_OPENGL + // on my box QPaintEngine::OpenGL2 has serious problems, f.e: + // the lines of a simple drawRect are wrong. + + QGL::setPreferredPaintEngine( QPaintEngine::OpenGL ); +#endif + + QApplication a( argc, argv ); + + MainWindow mainWindow; + mainWindow.resize( 600, 400 ); + mainWindow.show(); + + return a.exec(); +} diff --git a/qwt/examples/refreshtest/mainwindow.cpp b/qwt/examples/refreshtest/mainwindow.cpp new file mode 100644 index 000000000..c3df4cfd5 --- /dev/null +++ b/qwt/examples/refreshtest/mainwindow.cpp @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include +#include +#include "panel.h" +#include "plot.h" +#include "mainwindow.h" + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + QWidget *w = new QWidget( this ); + + d_panel = new Panel( w ); + + d_plot = new Plot( w ); + + QHBoxLayout *hLayout = new QHBoxLayout( w ); + hLayout->addWidget( d_panel ); + hLayout->addWidget( d_plot, 10 ); + + setCentralWidget( w ); + + d_frameCount = new QLabel( this ); + statusBar()->addWidget( d_frameCount, 10 ); + + applySettings( d_panel->settings() ); + + connect( d_panel, SIGNAL( settingsChanged( const Settings & ) ), + this, SLOT( applySettings( const Settings & ) ) ); +} + +bool MainWindow::eventFilter( QObject *object, QEvent *event ) +{ + if ( object == d_plot->canvas() && event->type() == QEvent::Paint ) + { + static int counter; + static QTime timeStamp; + + if ( !timeStamp.isValid() ) + { + timeStamp.start(); + counter = 0; + } + else + { + counter++; + + const double elapsed = timeStamp.elapsed() / 1000.0; + if ( elapsed >= 1 ) + { + QString fps; + fps.setNum( qRound( counter / elapsed ) ); + fps += " Fps"; + + d_frameCount->setText( fps ); + + counter = 0; + timeStamp.start(); + } + } + } + + return QMainWindow::eventFilter( object, event ); +} + +void MainWindow::applySettings( const Settings &settings ) +{ + d_plot->setSettings( settings ); + + // the canvas might have been recreated + d_plot->canvas()->removeEventFilter( this ); + d_plot->canvas()->installEventFilter( this ); +} diff --git a/qwt/examples/refreshtest/mainwindow.h b/qwt/examples/refreshtest/mainwindow.h new file mode 100644 index 000000000..47d10bd79 --- /dev/null +++ b/qwt/examples/refreshtest/mainwindow.h @@ -0,0 +1,28 @@ +#ifndef _MAIN_WINDOW_H_ +#define _MAIN_WINDOW_H_ + +#include + +class Plot; +class Panel; +class QLabel; +class Settings; + +class MainWindow: public QMainWindow +{ + Q_OBJECT + +public: + MainWindow( QWidget *parent = NULL ); + virtual bool eventFilter( QObject *, QEvent * ); + +private Q_SLOTS: + void applySettings( const Settings & ); + +private: + Plot *d_plot; + Panel *d_panel; + QLabel *d_frameCount; +}; + +#endif diff --git a/qwt/examples/refreshtest/panel.cpp b/qwt/examples/refreshtest/panel.cpp new file mode 100644 index 000000000..4189998c2 --- /dev/null +++ b/qwt/examples/refreshtest/panel.cpp @@ -0,0 +1,297 @@ +#include "panel.h" +#include +#include +#include +#include +#include +#include + +class SpinBox: public QSpinBox +{ +public: + SpinBox( int min, int max, int step, QWidget *parent ): + QSpinBox( parent ) + { + setRange( min, max ); + setSingleStep( step ); + } +}; + +class CheckBox: public QCheckBox +{ +public: + CheckBox( const QString &title, QWidget *parent ): + QCheckBox( title, parent ) + { + } + + void setChecked( bool checked ) + { + setCheckState( checked ? Qt::Checked : Qt::Unchecked ); + } + + bool isChecked() const + { + return checkState() == Qt::Checked; + } +}; + +Panel::Panel( QWidget *parent ): + QTabWidget( parent ) +{ + setTabPosition( QTabWidget::West ); + + addTab( createPlotTab( this ), "Plot" ); + addTab( createCanvasTab( this ), "Canvas" ); + addTab( createCurveTab( this ), "Curve" ); + + setSettings( Settings() ); + + connect( d_numPoints, SIGNAL( valueChanged( int ) ), SLOT( edited() ) ); + connect( d_updateInterval, SIGNAL( valueChanged( int ) ), SLOT( edited() ) ); + connect( d_curveWidth, SIGNAL( valueChanged( int ) ), SLOT( edited() ) ); + + connect( d_paintCache, SIGNAL( stateChanged( int ) ), SLOT( edited() ) ); + connect( d_paintOnScreen, SIGNAL( stateChanged( int ) ), SLOT( edited() ) ); + connect( d_immediatePaint, SIGNAL( stateChanged( int ) ), SLOT( edited() ) ); +#ifndef QWT_NO_OPENGL + connect( d_openGL, SIGNAL( stateChanged( int ) ), SLOT( edited() ) ); +#endif + + connect( d_curveAntialiasing, SIGNAL( stateChanged( int ) ), SLOT( edited() ) ); + connect( d_curveClipping, SIGNAL( stateChanged( int ) ), SLOT( edited() ) ); + connect( d_curveFiltering, SIGNAL( stateChanged( int ) ), SLOT( edited() ) ); + connect( d_lineSplitting, SIGNAL( stateChanged( int ) ), SLOT( edited() ) ); + connect( d_curveFilled, SIGNAL( stateChanged( int ) ), SLOT( edited() ) ); + + connect( d_updateType, SIGNAL( currentIndexChanged( int ) ), SLOT( edited() ) ); + connect( d_gridStyle, SIGNAL( currentIndexChanged( int ) ), SLOT( edited() ) ); + connect( d_curveType, SIGNAL( currentIndexChanged( int ) ), SLOT( edited() ) ); + connect( d_curvePen, SIGNAL( currentIndexChanged( int ) ), SLOT( edited() ) ); +} + +QWidget *Panel::createPlotTab( QWidget *parent ) +{ + QWidget *page = new QWidget( parent ); + + d_updateInterval = new SpinBox( 0, 1000, 10, page ); + d_numPoints = new SpinBox( 10, 1000000, 1000, page ); + + d_updateType = new QComboBox( page ); + d_updateType->addItem( "Repaint" ); + d_updateType->addItem( "Replot" ); + + int row = 0; + + QGridLayout *layout = new QGridLayout( page ); + + layout->addWidget( new QLabel( "Updates", page ), row, 0 ); + layout->addWidget( d_updateInterval, row, 1 ); + layout->addWidget( new QLabel( "ms", page ), row++, 2 ); + + layout->addWidget( new QLabel( "Points", page ), row, 0 ); + layout->addWidget( d_numPoints, row++, 1 ); + + layout->addWidget( new QLabel( "Update", page ), row, 0 ); + layout->addWidget( d_updateType, row++, 1 ); + + layout->addLayout( new QHBoxLayout(), row++, 0 ); + + layout->setColumnStretch( 1, 10 ); + layout->setRowStretch( row, 10 ); + + return page; +} + +QWidget *Panel::createCanvasTab( QWidget *parent ) +{ + QWidget *page = new QWidget( parent ); + + d_gridStyle = new QComboBox( page ); + d_gridStyle->addItem( "None" ); + d_gridStyle->addItem( "Solid" ); + d_gridStyle->addItem( "Dashes" ); + + d_paintCache = new CheckBox( "Paint Cache", page ); + d_paintOnScreen = new CheckBox( "Paint On Screen", page ); + d_immediatePaint = new CheckBox( "Immediate Paint", page ); +#ifndef QWT_NO_OPENGL + d_openGL = new CheckBox( "OpenGL", page ); +#endif + + int row = 0; + + QGridLayout *layout = new QGridLayout( page ); + layout->addWidget( new QLabel( "Grid", page ), row, 0 ); + layout->addWidget( d_gridStyle, row++, 1 ); + + layout->addWidget( d_paintCache, row++, 0, 1, -1 ); + layout->addWidget( d_paintOnScreen, row++, 0, 1, -1 ); + layout->addWidget( d_immediatePaint, row++, 0, 1, -1 ); +#ifndef QWT_NO_OPENGL + layout->addWidget( d_openGL, row++, 0, 1, -1 ); +#endif + + layout->addLayout( new QHBoxLayout(), row++, 0 ); + + layout->setColumnStretch( 1, 10 ); + layout->setRowStretch( row, 10 ); + + return page; +} + +QWidget *Panel::createCurveTab( QWidget *parent ) +{ + QWidget *page = new QWidget( parent ); + + d_curveType = new QComboBox( page ); + d_curveType->addItem( "Wave" ); + d_curveType->addItem( "Noise" ); + + d_curveAntialiasing = new CheckBox( "Antialiasing", page ); + d_curveClipping = new CheckBox( "Clipping", page ); + d_curveFiltering = new CheckBox( "Filtering", page ); + d_lineSplitting = new CheckBox( "Split Lines", page ); + + d_curveWidth = new SpinBox( 0, 10, 1, page ); + + d_curvePen = new QComboBox( page ); + d_curvePen->addItem( "Solid" ); + d_curvePen->addItem( "Dotted" ); + + d_curveFilled = new CheckBox( "Filled", page ); + + int row = 0; + + QGridLayout *layout = new QGridLayout( page ); + layout->addWidget( new QLabel( "Type", page ), row, 0 ); + layout->addWidget( d_curveType, row++, 1 ); + + layout->addWidget( d_curveAntialiasing, row++, 0, 1, -1 ); + layout->addWidget( d_curveClipping, row++, 0, 1, -1 ); + layout->addWidget( d_curveFiltering, row++, 0, 1, -1 ); + layout->addWidget( d_lineSplitting, row++, 0, 1, -1 ); + + layout->addWidget( new QLabel( "Width", page ), row, 0 ); + layout->addWidget( d_curveWidth, row++, 1 ); + + layout->addWidget( new QLabel( "Style", page ), row, 0 ); + layout->addWidget( d_curvePen, row++, 1 ); + + layout->addWidget( d_curveFilled, row++, 0, 1, -1 ); + + layout->addLayout( new QHBoxLayout(), row++, 0 ); + + layout->setColumnStretch( 1, 10 ); + layout->setRowStretch( row, 10 ); + + return page; +} + +void Panel::edited() +{ + const Settings s = settings(); + Q_EMIT settingsChanged( s ); +} + + +Settings Panel::settings() const +{ + Settings s; + + s.grid.pen = QPen( Qt::black, 0 ); + + switch( d_gridStyle->currentIndex() ) + { + case 0: + s.grid.pen.setStyle( Qt::NoPen ); + break; + case 2: + s.grid.pen.setStyle( Qt::DashLine ); + break; + } + + s.curve.pen.setStyle( d_curvePen->currentIndex() == 0 ? + Qt::SolidLine : Qt::DotLine ); + s.curve.pen.setWidth( d_curveWidth->value() ); + s.curve.brush.setStyle( ( d_curveFilled->isChecked() ) ? + Qt::SolidPattern : Qt::NoBrush ); + s.curve.numPoints = d_numPoints->value(); + s.curve.functionType = static_cast( + d_curveType->currentIndex() ); + if ( d_curveClipping->isChecked() ) + s.curve.paintAttributes |= QwtPlotCurve::ClipPolygons; + else + s.curve.paintAttributes &= ~QwtPlotCurve::ClipPolygons; + if ( d_curveFiltering->isChecked() ) + s.curve.paintAttributes |= QwtPlotCurve::FilterPoints; + else + s.curve.paintAttributes &= ~QwtPlotCurve::FilterPoints; + + if ( d_curveAntialiasing->isChecked() ) + s.curve.renderHint |= QwtPlotItem::RenderAntialiased; + else + s.curve.renderHint &= ~QwtPlotItem::RenderAntialiased; + + s.curve.lineSplitting = ( d_lineSplitting->isChecked() ); + + s.canvas.useBackingStore = ( d_paintCache->isChecked() ); + s.canvas.paintOnScreen = ( d_paintOnScreen->isChecked() ); + s.canvas.immediatePaint = ( d_immediatePaint->isChecked() ); +#ifndef QWT_NO_OPENGL + s.canvas.openGL = ( d_openGL->isChecked() ); +#endif + + s.updateInterval = d_updateInterval->value(); + s.updateType = static_cast( d_updateType->currentIndex() ); + + return s; +} + +void Panel::setSettings( const Settings &s ) +{ + d_numPoints->setValue( s.curve.numPoints ); + d_updateInterval->setValue( s.updateInterval ); + d_updateType->setCurrentIndex( s.updateType ); + + switch( s.grid.pen.style() ) + { + case Qt::NoPen: + { + d_gridStyle->setCurrentIndex( 0 ); + break; + } + case Qt::DashLine: + { + d_gridStyle->setCurrentIndex( 2 ); + break; + } + default: + { + d_gridStyle->setCurrentIndex( 1 ); // Solid + } + } + + d_paintCache->setChecked( s.canvas.useBackingStore ); + d_paintOnScreen->setChecked( s.canvas.paintOnScreen ); + d_immediatePaint->setChecked( s.canvas.immediatePaint ); +#ifndef QWT_NO_OPENGL + d_openGL->setChecked( s.canvas.openGL ); +#endif + + d_curveType->setCurrentIndex( s.curve.functionType ); + d_curveAntialiasing->setChecked( + s.curve.renderHint & QwtPlotCurve::RenderAntialiased ); + + d_curveClipping->setChecked( + s.curve.paintAttributes & QwtPlotCurve::ClipPolygons ); + d_curveFiltering->setChecked( + s.curve.paintAttributes & QwtPlotCurve::FilterPoints ); + + d_lineSplitting->setChecked( s.curve.lineSplitting ); + + d_curveWidth->setValue( s.curve.pen.width() ); + d_curvePen->setCurrentIndex( + s.curve.pen.style() == Qt::SolidLine ? 0 : 1 ); + d_curveFilled->setChecked( s.curve.brush.style() != Qt::NoBrush ); +} diff --git a/qwt/examples/refreshtest/panel.h b/qwt/examples/refreshtest/panel.h new file mode 100644 index 000000000..9a0fcc5f3 --- /dev/null +++ b/qwt/examples/refreshtest/panel.h @@ -0,0 +1,54 @@ +#ifndef _PANEL_H_ +#define _PANEL_H_ 1 + +#include "settings.h" +#include + +class QComboBox; +class SpinBox; +class CheckBox; + +class Panel: public QTabWidget +{ + Q_OBJECT + +public: + Panel( QWidget * = NULL ); + + Settings settings() const; + void setSettings( const Settings & ); + +Q_SIGNALS: + void settingsChanged( const Settings & ); + +private Q_SLOTS: + void edited(); + +private: + QWidget *createPlotTab( QWidget * ); + QWidget *createCanvasTab( QWidget * ); + QWidget *createCurveTab( QWidget * ); + + SpinBox *d_numPoints; + SpinBox *d_updateInterval; + QComboBox *d_updateType; + + QComboBox *d_gridStyle; + CheckBox *d_paintCache; + CheckBox *d_paintOnScreen; + CheckBox *d_immediatePaint; +#ifndef QWT_NO_OPENGL + CheckBox *d_openGL; +#endif + + QComboBox *d_curveType; + CheckBox *d_curveAntialiasing; + CheckBox *d_curveClipping; + CheckBox *d_curveFiltering; + CheckBox *d_lineSplitting; + SpinBox *d_curveWidth; + QComboBox *d_curvePen; + CheckBox *d_curveFilled; +}; + +#endif diff --git a/qwt/examples/refreshtest/plot.cpp b/qwt/examples/refreshtest/plot.cpp new file mode 100644 index 000000000..8db9348c2 --- /dev/null +++ b/qwt/examples/refreshtest/plot.cpp @@ -0,0 +1,218 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef QWT_NO_OPENGL +#include +#include +#endif +#include "plot.h" +#include "circularbuffer.h" +#include "settings.h" + +static double wave( double x ) +{ + const double period = 1.0; + const double c = 5.0; + + double v = ::fmod( x, period ); + + const double amplitude = qAbs( x - qRound( x / c ) * c ) / ( 0.5 * c ); + v = amplitude * qSin( v / period * 2 * M_PI ); + + return v; +} + +static double noise( double ) +{ + return 2.0 * ( qrand() / ( static_cast( RAND_MAX ) + 1 ) ) - 1.0; +} + +#ifndef QWT_NO_OPENGL +class GLCanvas: public QwtPlotGLCanvas +{ +public: + GLCanvas( QwtPlot *parent = NULL ): + QwtPlotGLCanvas( parent ) + { + setContentsMargins( 1, 1, 1, 1 ); + } + +protected: + virtual void paintEvent( QPaintEvent *event ) + { + QPainter painter( this ); + painter.setClipRegion( event->region() ); + + QwtPlot *plot = qobject_cast< QwtPlot *>( parent() ); + if ( plot ) + plot->drawCanvas( &painter ); + + painter.setPen( palette().foreground().color() ); + painter.drawRect( rect().adjusted( 0, 0, -1, -1 ) ); + } +}; +#endif + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ), + d_interval( 10.0 ), // seconds + d_timerId( -1 ) +{ + // Assign a title + setTitle( "Testing Refresh Rates" ); + + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setFrameStyle( QFrame::Box | QFrame::Plain ); + canvas->setLineWidth( 1 ); + canvas->setPalette( Qt::white ); + + setCanvas( canvas ); + + alignScales(); + + // Insert grid + d_grid = new QwtPlotGrid(); + d_grid->attach( this ); + + // Insert curve + d_curve = new QwtPlotCurve( "Data Moving Right" ); + d_curve->setPen( Qt::black ); + d_curve->setData( new CircularBuffer( d_interval, 10 ) ); + d_curve->attach( this ); + + // Axis + setAxisTitle( QwtAxis::xBottom, "Seconds" ); + setAxisScale( QwtAxis::xBottom, -d_interval, 0.0 ); + + setAxisTitle( QwtAxis::yLeft, "Values" ); + setAxisScale( QwtAxis::yLeft, -1.0, 1.0 ); + + d_clock.start(); + + setSettings( d_settings ); +} + +// +// Set a plain canvas frame and align the scales to it +// +void Plot::alignScales() +{ + // The code below shows how to align the scales to + // the canvas frame, but is also a good example demonstrating + // why the spreaded API needs polishing. + + for ( int i = 0; i < QwtAxis::PosCount; i++ ) + { + QwtScaleWidget *scaleWidget = axisWidget( i ); + if ( scaleWidget ) + scaleWidget->setMargin( 0 ); + + QwtScaleDraw *scaleDraw = axisScaleDraw( i ); + if ( scaleDraw ) + scaleDraw->enableComponent( QwtAbstractScaleDraw::Backbone, false ); + } + + plotLayout()->setAlignCanvasToScales( true ); +} + +void Plot::setSettings( const Settings &s ) +{ + if ( d_timerId >= 0 ) + killTimer( d_timerId ); + + d_timerId = startTimer( s.updateInterval ); + + d_grid->setPen( s.grid.pen ); + d_grid->setVisible( s.grid.pen.style() != Qt::NoPen ); + + CircularBuffer *buffer = static_cast( d_curve->data() ); + if ( s.curve.numPoints != buffer->size() || + s.curve.functionType != d_settings.curve.functionType ) + { + switch( s.curve.functionType ) + { + case Settings::Wave: + buffer->setFunction( wave ); + break; + case Settings::Noise: + buffer->setFunction( noise ); + break; + default: + buffer->setFunction( NULL ); + } + + buffer->fill( d_interval, s.curve.numPoints ); + } + + d_curve->setPen( s.curve.pen ); + d_curve->setBrush( s.curve.brush ); + + d_curve->setPaintAttribute( QwtPlotCurve::ClipPolygons, + s.curve.paintAttributes & QwtPlotCurve::ClipPolygons ); + d_curve->setPaintAttribute( QwtPlotCurve::FilterPoints, + s.curve.paintAttributes & QwtPlotCurve::FilterPoints ); + + d_curve->setRenderHint( QwtPlotItem::RenderAntialiased, + s.curve.renderHint & QwtPlotItem::RenderAntialiased ); + +#ifndef QWT_NO_OPENGL + if ( s.canvas.openGL ) + { + QwtPlotGLCanvas *plotCanvas = qobject_cast( canvas() ); + if ( plotCanvas == NULL ) + { + plotCanvas = new GLCanvas(); + plotCanvas->setPalette( QColor( "khaki" ) ); + + setCanvas( plotCanvas ); + } + } + else +#endif + { + QwtPlotCanvas *plotCanvas = qobject_cast( canvas() ); + if ( plotCanvas == NULL ) + { + plotCanvas = new QwtPlotCanvas(); + plotCanvas->setFrameStyle( QFrame::Box | QFrame::Plain ); + plotCanvas->setLineWidth( 1 ); + plotCanvas->setPalette( Qt::white ); + + setCanvas( plotCanvas ); + } + + plotCanvas->setAttribute( Qt::WA_PaintOnScreen, s.canvas.paintOnScreen ); + + plotCanvas->setPaintAttribute( + QwtPlotCanvas::BackingStore, s.canvas.useBackingStore ); + plotCanvas->setPaintAttribute( + QwtPlotCanvas::ImmediatePaint, s.canvas.immediatePaint ); + } + + QwtPainter::setPolylineSplitting( s.curve.lineSplitting ); + + d_settings = s; +} + +void Plot::timerEvent( QTimerEvent * ) +{ + CircularBuffer *buffer = static_cast( d_curve->data() ); + buffer->setReferenceTime( d_clock.elapsed() / 1000.0 ); + + if ( d_settings.updateType == Settings::RepaintCanvas ) + { + // the axes in this example doesn't change. So all we need to do + // is to repaint the canvas. + + QMetaObject::invokeMethod( canvas(), "replot", Qt::DirectConnection ); + } + else + { + replot(); + } +} diff --git a/qwt/examples/refreshtest/plot.h b/qwt/examples/refreshtest/plot.h new file mode 100644 index 000000000..8e6345077 --- /dev/null +++ b/qwt/examples/refreshtest/plot.h @@ -0,0 +1,38 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ 1 + +#include +#include +#include "settings.h" + +class QwtPlotGrid; +class QwtPlotCurve; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget* = NULL ); + +public Q_SLOTS: + void setSettings( const Settings & ); + +protected: + virtual void timerEvent( QTimerEvent *e ); + +private: + void alignScales(); + + QwtPlotGrid *d_grid; + QwtPlotCurve *d_curve; + + QwtSystemClock d_clock; + double d_interval; + + int d_timerId; + + Settings d_settings; +}; + +#endif diff --git a/qwt/examples/refreshtest/refreshtest.pro b/qwt/examples/refreshtest/refreshtest.pro new file mode 100644 index 000000000..3dca7a251 --- /dev/null +++ b/qwt/examples/refreshtest/refreshtest.pro @@ -0,0 +1,27 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = refreshtest + +HEADERS = \ + settings.h \ + circularbuffer.h \ + panel.h \ + plot.h \ + mainwindow.h + +SOURCES = \ + circularbuffer.cpp \ + panel.cpp \ + plot.cpp \ + mainwindow.cpp \ + main.cpp + diff --git a/qwt/examples/refreshtest/settings.h b/qwt/examples/refreshtest/settings.h new file mode 100644 index 000000000..2140e9496 --- /dev/null +++ b/qwt/examples/refreshtest/settings.h @@ -0,0 +1,78 @@ +#ifndef _SETTINGS_H_ +#define _SETTINGS_H_ + +#include +#include + +class Settings +{ +public: + enum FunctionType + { + NoFunction = -1, + + Wave, + Noise + }; + + enum UpdateType + { + RepaintCanvas, + Replot + }; + + Settings() + { + grid.pen = Qt::NoPen; + grid.pen.setCosmetic( true ); + + curve.brush = Qt::NoBrush; + curve.numPoints = 1000; + curve.functionType = Wave; + curve.paintAttributes = 0; + curve.renderHint = 0; + curve.lineSplitting = true; + + canvas.useBackingStore = false; + canvas.paintOnScreen = false; + canvas.immediatePaint = true; +#ifndef QWT_NO_OPENGL + canvas.openGL = false; +#endif + + updateType = RepaintCanvas; + updateInterval = 20; + } + + struct gridSettings + { + QPen pen; + } grid; + + struct curveSettings + { + QPen pen; + QBrush brush; + uint numPoints; + FunctionType functionType; + int paintAttributes; + int renderHint; + bool lineSplitting; + } curve; + + struct canvasSettings + { + bool useBackingStore; + bool paintOnScreen; + bool immediatePaint; + +#ifndef QWT_NO_OPENGL + bool openGL; +#endif + } canvas; + + UpdateType updateType; + int updateInterval; +}; + +#endif diff --git a/qwt/examples/scatterplot/main.cpp b/qwt/examples/scatterplot/main.cpp new file mode 100644 index 000000000..bd70f997f --- /dev/null +++ b/qwt/examples/scatterplot/main.cpp @@ -0,0 +1,13 @@ +#include +#include "mainwindow.h" + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow w; + w.resize( 800, 600 ); + w.show(); + + return a.exec(); +} diff --git a/qwt/examples/scatterplot/mainwindow.cpp b/qwt/examples/scatterplot/mainwindow.cpp new file mode 100644 index 000000000..ef24d0c9e --- /dev/null +++ b/qwt/examples/scatterplot/mainwindow.cpp @@ -0,0 +1,35 @@ +#include "mainwindow.h" +#include "plot.h" +#include + +static double randomValue() +{ + // a number between [ 0.0, 1.0 ] + return ( qrand() % 100000 ) / 100000.0; +} + +MainWindow::MainWindow() +{ + d_plot = new Plot( this ); + d_plot->setTitle( "Scatter Plot" ); + setCentralWidget( d_plot ); + + // a million points + setSamples( 100000 ); +} + +void MainWindow::setSamples( int numPoints ) +{ + QPolygonF samples; + + for ( int i = 0; i < numPoints; i++ ) + { + const double x = randomValue() * 24.0 + 1.0; + const double y = ::log( 10.0 * ( x - 1.0 ) + 1.0 ) + * ( randomValue() * 0.5 + 0.9 ); + + samples += QPointF( x, y ); + } + + d_plot->setSamples( samples ); +} diff --git a/qwt/examples/scatterplot/mainwindow.h b/qwt/examples/scatterplot/mainwindow.h new file mode 100644 index 000000000..ed5f2ac39 --- /dev/null +++ b/qwt/examples/scatterplot/mainwindow.h @@ -0,0 +1,22 @@ +#ifndef _MAINWINDOW_H_ +#define _MAINWINDOW_H_ 1 + +#include + +class Plot; + +class MainWindow: public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(); + +private: + void setSamples( int samples ); + +private: + Plot *d_plot; +}; + +#endif diff --git a/qwt/examples/scatterplot/plot.cpp b/qwt/examples/scatterplot/plot.cpp new file mode 100644 index 000000000..1e65a399a --- /dev/null +++ b/qwt/examples/scatterplot/plot.cpp @@ -0,0 +1,91 @@ +#include "plot.h" +#include +#include +#include +#include +#include + +class DistancePicker: public QwtPlotPicker +{ +public: + DistancePicker( QWidget *canvas ): + QwtPlotPicker( canvas ) + { + setTrackerMode( QwtPicker::ActiveOnly ); + setStateMachine( new QwtPickerDragLineMachine() ); + setRubberBand( QwtPlotPicker::PolygonRubberBand ); + } + + virtual QwtText trackerTextF( const QPointF &pos ) const + { + QwtText text; + + const QPolygon points = selection(); + if ( !points.isEmpty() ) + { + QString num; + num.setNum( QLineF( pos, invTransform( points[0] ) ).length() ); + + QColor bg( Qt::white ); + bg.setAlpha( 200 ); + + text.setBackgroundBrush( QBrush( bg ) ); + text.setText( num ); + } + return text; + } +}; + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ), + d_curve( NULL ) +{ + canvas()->setStyleSheet( + "border: 2px solid Black;" + "border-radius: 15px;" + "background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1," + "stop: 0 LemonChiffon, stop: 1 PaleGoldenrod );" + ); + + // attach curve + d_curve = new QwtPlotCurve( "Scattered Points" ); + d_curve->setPen( QColor( "Purple" ) ); + + // when using QwtPlotCurve::ImageBuffer simple dots can be + // rendered in parallel on multicore systems. + d_curve->setRenderThreadCount( 0 ); // 0: use QThread::idealThreadCount() + + d_curve->attach( this ); + + setSymbol( NULL ); + + // panning with the left mouse button + (void )new QwtPlotPanner( canvas() ); + + // zoom in/out with the wheel + QwtPlotMagnifier *magnifier = new QwtPlotMagnifier( canvas() ); + magnifier->setMouseButton( Qt::NoButton ); + + // distanve measurement with the right mouse button + DistancePicker *picker = new DistancePicker( canvas() ); + picker->setMousePattern( QwtPlotPicker::MouseSelect1, Qt::RightButton ); + picker->setRubberBandPen( QPen( Qt::blue ) ); +} + +void Plot::setSymbol( QwtSymbol *symbol ) +{ + d_curve->setSymbol( symbol ); + + if ( symbol == NULL ) + { + d_curve->setStyle( QwtPlotCurve::Dots ); + } +} + +void Plot::setSamples( const QVector &samples ) +{ + d_curve->setPaintAttribute( + QwtPlotCurve::ImageBuffer, samples.size() > 1000 ); + + d_curve->setSamples( samples ); +} diff --git a/qwt/examples/scatterplot/plot.h b/qwt/examples/scatterplot/plot.h new file mode 100644 index 000000000..57cf4443a --- /dev/null +++ b/qwt/examples/scatterplot/plot.h @@ -0,0 +1,23 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ 1 + +#include + +class QwtPlotCurve; +class QwtSymbol; + +class Plot : public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget *parent = NULL ); + + void setSymbol( QwtSymbol * ); + void setSamples( const QVector &samples ); + +private: + QwtPlotCurve *d_curve; +}; + +#endif // _PLOT_H_ diff --git a/qwt/examples/scatterplot/scatterplot.pro b/qwt/examples/scatterplot/scatterplot.pro new file mode 100644 index 000000000..67ad530ce --- /dev/null +++ b/qwt/examples/scatterplot/scatterplot.pro @@ -0,0 +1,22 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = scatterplot + +HEADERS = \ + mainwindow.h \ + plot.h + +SOURCES = \ + main.cpp \ + mainwindow.cpp \ + plot.cpp + diff --git a/qwt/examples/simpleplot/simpleplot.cpp b/qwt/examples/simpleplot/simpleplot.cpp new file mode 100644 index 000000000..9796bc767 --- /dev/null +++ b/qwt/examples/simpleplot/simpleplot.cpp @@ -0,0 +1,42 @@ +#include +#include +#include +#include +#include +#include + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + QwtPlot plot; + plot.setTitle( "Plot Demo" ); + plot.setCanvasBackground( Qt::white ); + plot.setAxisScale( QwtAxis::yLeft, 0.0, 10.0 ); + plot.insertLegend( new QwtLegend() ); + + QwtPlotGrid *grid = new QwtPlotGrid(); + grid->attach( &plot ); + + QwtPlotCurve *curve = new QwtPlotCurve(); + curve->setTitle( "Some Points" ); + curve->setPen( Qt::blue, 4 ), + curve->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + + QwtSymbol *symbol = new QwtSymbol( QwtSymbol::Ellipse, + QBrush( Qt::yellow ), QPen( Qt::red, 2 ), QSize( 8, 8 ) ); + curve->setSymbol( symbol ); + + QPolygonF points; + points << QPointF( 0.0, 4.4 ) << QPointF( 1.0, 3.0 ) + << QPointF( 2.0, 4.5 ) << QPointF( 3.0, 6.8 ) + << QPointF( 4.0, 7.9 ) << QPointF( 5.0, 7.1 ); + curve->setSamples( points ); + + curve->attach( &plot ); + + plot.resize( 600, 400 ); + plot.show(); + + return a.exec(); +} diff --git a/qwt/examples/simpleplot/simpleplot.pro b/qwt/examples/simpleplot/simpleplot.pro new file mode 100644 index 000000000..62dcad194 --- /dev/null +++ b/qwt/examples/simpleplot/simpleplot.pro @@ -0,0 +1,16 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = simpleplot + +SOURCES = \ + simpleplot.cpp + diff --git a/qwt/examples/sinusplot/sinusplot.cpp b/qwt/examples/sinusplot/sinusplot.cpp new file mode 100644 index 000000000..215275b30 --- /dev/null +++ b/qwt/examples/sinusplot/sinusplot.cpp @@ -0,0 +1,218 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//----------------------------------------------------------------- +// simple.cpp +// +// A simple example which shows how to use QwtPlot connected +// to a data class without any storage, calculating each values +// on the fly. +//----------------------------------------------------------------- + +class FunctionData: public QwtSyntheticPointData +{ +public: + FunctionData( double( *y )( double ) ): + QwtSyntheticPointData( 100 ), + d_y( y ) + { + } + + virtual double y( double x ) const + { + return d_y( x ); + } + +private: + double( *d_y )( double ); +}; + +class ArrowSymbol: public QwtSymbol +{ +public: + ArrowSymbol() + { + QPen pen( Qt::black, 0 ); + pen.setJoinStyle( Qt::MiterJoin ); + + setPen( pen ); + setBrush( Qt::red ); + + QPainterPath path; + path.moveTo( 0, 8 ); + path.lineTo( 0, 5 ); + path.lineTo( -3, 5 ); + path.lineTo( 0, 0 ); + path.lineTo( 3, 5 ); + path.lineTo( 0, 5 ); + + QTransform transform; + transform.rotate( -30.0 ); + path = transform.map( path ); + + setPath( path ); + setPinPoint( QPointF( 0, 0 ) ); + + setSize( 10, 14 ); + } +}; + +class Plot : public QwtPlot +{ +public: + Plot( QWidget *parent = NULL ); + +protected: + virtual void resizeEvent( QResizeEvent * ); + +private: + void populate(); + void updateGradient(); +}; + + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + setAutoFillBackground( true ); + setPalette( QPalette( QColor( 165, 193, 228 ) ) ); + updateGradient(); + + setTitle( "A Simple QwtPlot Demonstration" ); + insertLegend( new QwtLegend(), QwtPlot::RightLegend ); + + // axes + setAxisTitle( QwtAxis::xBottom, "x -->" ); + setAxisScale( QwtAxis::xBottom, 0.0, 10.0 ); + + setAxisTitle( QwtAxis::yLeft, "y -->" ); + setAxisScale( QwtAxis::yLeft, -1.0, 1.0 ); + + // canvas + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setLineWidth( 1 ); + canvas->setFrameStyle( QFrame::Box | QFrame::Plain ); + canvas->setBorderRadius( 15 ); + + QPalette canvasPalette( Qt::white ); + canvasPalette.setColor( QPalette::Foreground, QColor( 133, 190, 232 ) ); + canvas->setPalette( canvasPalette ); + + setCanvas( canvas ); + + // panning with the left mouse button + ( void ) new QwtPlotPanner( canvas ); + + // zoom in/out with the wheel + ( void ) new QwtPlotMagnifier( canvas ); + + populate(); +} + +void Plot::populate() +{ + // Insert new curves + QwtPlotCurve *cSin = new QwtPlotCurve( "y = sin(x)" ); + cSin->setRenderHint( QwtPlotItem::RenderAntialiased ); + cSin->setLegendAttribute( QwtPlotCurve::LegendShowLine, true ); + cSin->setPen( Qt::red ); + cSin->attach( this ); + + QwtPlotCurve *cCos = new QwtPlotCurve( "y = cos(x)" ); + cCos->setRenderHint( QwtPlotItem::RenderAntialiased ); + cCos->setLegendAttribute( QwtPlotCurve::LegendShowLine, true ); + cCos->setPen( Qt::blue ); + cCos->attach( this ); + + // Create sin and cos data + cSin->setData( new FunctionData( ::sin ) ); + cCos->setData( new FunctionData( ::cos ) ); + + // Insert markers + + // ...a horizontal line at y = 0... + QwtPlotMarker *mY = new QwtPlotMarker(); + mY->setLabel( QString::fromLatin1( "y = 0" ) ); + mY->setLabelAlignment( Qt::AlignRight | Qt::AlignTop ); + mY->setLineStyle( QwtPlotMarker::HLine ); + mY->setYValue( 0.0 ); + mY->attach( this ); + + // ...a vertical line at x = 2 * pi + QwtPlotMarker *mX = new QwtPlotMarker(); + mX->setLabel( QString::fromLatin1( "x = 2 pi" ) ); + mX->setLabelAlignment( Qt::AlignLeft | Qt::AlignBottom ); + mX->setLabelOrientation( Qt::Vertical ); + mX->setLineStyle( QwtPlotMarker::VLine ); + mX->setLinePen( Qt::black, 0, Qt::DashDotLine ); + mX->setXValue( 2.0 * M_PI ); + mX->attach( this ); + + const double x = 7.7; + + // an arrow at a specific position + QwtPlotMarker *mPos = new QwtPlotMarker( "Marker" ); + mPos->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + mPos->setItemAttribute( QwtPlotItem::Legend, true ); + mPos->setSymbol( new ArrowSymbol() ); + mPos->setValue( QPointF( x, ::sin( x ) ) ); + mPos->setLabel( QString( "x = %1" ).arg( x ) ); + mPos->setLabelAlignment( Qt::AlignRight | Qt::AlignBottom ); + mPos->attach( this ); +} + +void Plot::updateGradient() +{ + QPalette pal = palette(); + + const QColor buttonColor = pal.color( QPalette::Button ); + + QLinearGradient gradient( rect().topLeft(), rect().bottomLeft() ); + gradient.setColorAt( 0.0, Qt::white ); + gradient.setColorAt( 0.7, buttonColor ); + gradient.setColorAt( 1.0, buttonColor ); + + pal.setBrush( QPalette::Window, gradient ); + setPalette( pal ); +} + +void Plot::resizeEvent( QResizeEvent *event ) +{ + QwtPlot::resizeEvent( event ); + + // Qt 4.7.1: QGradient::StretchToDeviceMode is buggy on X11 + updateGradient(); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + Plot *plot = new Plot(); + + // We put a dummy widget around to have + // so that Qt paints a widget background + // when resizing + + QWidget window; + QHBoxLayout *layout = new QHBoxLayout( &window ); + layout->setContentsMargins( 0, 0, 0, 0 ); + layout->addWidget( plot ); + + window.resize( 600, 400 ); + window.show(); + + return a.exec(); +} diff --git a/qwt/examples/sinusplot/sinusplot.pro b/qwt/examples/sinusplot/sinusplot.pro new file mode 100644 index 000000000..fc43e58fb --- /dev/null +++ b/qwt/examples/sinusplot/sinusplot.pro @@ -0,0 +1,16 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = sinusplot + +SOURCES = \ + sinusplot.cpp + diff --git a/qwt/examples/spectrogram/main.cpp b/qwt/examples/spectrogram/main.cpp new file mode 100644 index 000000000..e4d004198 --- /dev/null +++ b/qwt/examples/spectrogram/main.cpp @@ -0,0 +1,75 @@ +#include +#include +#include +#include +#include +#include "plot.h" + +class MainWindow: public QMainWindow +{ +public: + MainWindow( QWidget * = NULL ); + +private: + Plot *d_plot; +}; + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + d_plot = new Plot( this ); + + setCentralWidget( d_plot ); + + QToolBar *toolBar = new QToolBar( this ); + + QToolButton *btnSpectrogram = new QToolButton( toolBar ); + btnSpectrogram->setText( "Spectrogram" ); + btnSpectrogram->setCheckable( true ); + btnSpectrogram->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolBar->addWidget( btnSpectrogram ); + connect( btnSpectrogram, SIGNAL( toggled( bool ) ), + d_plot, SLOT( showSpectrogram( bool ) ) ); + + QToolButton *btnContour = new QToolButton( toolBar ); + btnContour->setText( "Contour" ); + btnContour->setCheckable( true ); + btnContour->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolBar->addWidget( btnContour ); + connect( btnContour, SIGNAL( toggled( bool ) ), + d_plot, SLOT( showContour( bool ) ) ); + +#ifndef QT_NO_PRINTER + QToolButton *btnPrint = new QToolButton( toolBar ); + btnPrint->setText( "Print" ); + btnPrint->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolBar->addWidget( btnPrint ); + connect( btnPrint, SIGNAL( clicked() ), + d_plot, SLOT( printPlot() ) ); +#endif + + QSlider *slider = new QSlider( Qt::Horizontal ); + slider->setRange( 0, 255 ); + slider->setValue( 255 ); + connect( slider, SIGNAL( valueChanged( int ) ), + d_plot, SLOT( setAlpha( int ) ) ); + + toolBar->addWidget( slider ); + + addToolBar( toolBar ); + + btnSpectrogram->setChecked( true ); + btnContour->setChecked( false ); + +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow mainWindow; + mainWindow.resize( 600, 400 ); + mainWindow.show(); + + return a.exec(); +} diff --git a/qwt/examples/spectrogram/plot.cpp b/qwt/examples/spectrogram/plot.cpp new file mode 100644 index 000000000..36cbc0b06 --- /dev/null +++ b/qwt/examples/spectrogram/plot.cpp @@ -0,0 +1,169 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "plot.h" + +class MyZoomer: public QwtPlotZoomer +{ +public: + MyZoomer( QWidget *canvas ): + QwtPlotZoomer( canvas ) + { + setTrackerMode( AlwaysOn ); + } + + virtual QwtText trackerTextF( const QPointF &pos ) const + { + QColor bg( Qt::white ); + bg.setAlpha( 200 ); + + QwtText text = QwtPlotZoomer::trackerTextF( pos ); + text.setBackgroundBrush( QBrush( bg ) ); + return text; + } +}; + +class SpectrogramData: public QwtRasterData +{ +public: + SpectrogramData() + { + setInterval( Qt::XAxis, QwtInterval( -1.5, 1.5 ) ); + setInterval( Qt::YAxis, QwtInterval( -1.5, 1.5 ) ); + setInterval( Qt::ZAxis, QwtInterval( 0.0, 10.0 ) ); + } + + virtual double value( double x, double y ) const + { + const double c = 0.842; + + const double v1 = x * x + ( y - c ) * ( y + c ); + const double v2 = x * ( y + c ) + x * ( y + c ); + + return 1.0 / ( v1 * v1 + v2 * v2 ); + } +}; + +class ColorMap: public QwtLinearColorMap +{ +public: + ColorMap(): + QwtLinearColorMap( Qt::darkCyan, Qt::red ) + { + addColorStop( 0.1, Qt::cyan ); + addColorStop( 0.6, Qt::green ); + addColorStop( 0.95, Qt::yellow ); + } +}; + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + d_spectrogram = new QwtPlotSpectrogram(); + d_spectrogram->setRenderThreadCount( 0 ); // use system specific thread count + + d_spectrogram->setColorMap( new ColorMap() ); + d_spectrogram->setCachePolicy( QwtPlotRasterItem::PaintCache ); + + d_spectrogram->setData( new SpectrogramData() ); + d_spectrogram->attach( this ); + + QList contourLevels; + for ( double level = 0.5; level < 10.0; level += 1.0 ) + contourLevels += level; + d_spectrogram->setContourLevels( contourLevels ); + + const QwtInterval zInterval = d_spectrogram->data()->interval( Qt::ZAxis ); + // A color bar on the right axis + QwtScaleWidget *rightAxis = axisWidget( QwtAxis::yRight ); + rightAxis->setTitle( "Intensity" ); + rightAxis->setColorBarEnabled( true ); + rightAxis->setColorMap( zInterval, new ColorMap() ); + + setAxisScale( QwtAxis::yRight, zInterval.minValue(), zInterval.maxValue() ); + setAxisVisible( QwtAxis::yRight ); + + plotLayout()->setAlignCanvasToScales( true ); + replot(); + + // LeftButton for the zooming + // MidButton for the panning + // RightButton: zoom out by 1 + // Ctrl+RighButton: zoom out to full size + + QwtPlotZoomer* zoomer = new MyZoomer( canvas() ); + zoomer->setMousePattern( QwtEventPattern::MouseSelect2, + Qt::RightButton, Qt::ControlModifier ); + zoomer->setMousePattern( QwtEventPattern::MouseSelect3, + Qt::RightButton ); + + QwtPlotPanner *panner = new QwtPlotPanner( canvas() ); + panner->setAxisEnabled( QwtAxis::yRight, false ); + panner->setMouseButton( Qt::MidButton ); + + // Avoid jumping when labels with more/less digits + // appear/disappear when scrolling vertically + + const QFontMetrics fm( axisWidget( QwtAxis::yLeft )->font() ); + QwtScaleDraw *sd = axisScaleDraw( QwtAxis::yLeft ); + sd->setMinimumExtent( fm.width( "100.00" ) ); + + const QColor c( Qt::darkBlue ); + zoomer->setRubberBandPen( c ); + zoomer->setTrackerPen( c ); +} + +void Plot::showContour( bool on ) +{ + d_spectrogram->setDisplayMode( QwtPlotSpectrogram::ContourMode, on ); + replot(); +} + +void Plot::showSpectrogram( bool on ) +{ + d_spectrogram->setDisplayMode( QwtPlotSpectrogram::ImageMode, on ); + d_spectrogram->setDefaultContourPen( + on ? QPen( Qt::black, 0 ) : QPen( Qt::NoPen ) ); + + replot(); +} + +void Plot::setAlpha( int alpha ) +{ + d_spectrogram->setAlpha( alpha ); + replot(); +} + +#ifndef QT_NO_PRINTER + +void Plot::printPlot() +{ + QPrinter printer( QPrinter::HighResolution ); + printer.setOrientation( QPrinter::Landscape ); + printer.setOutputFileName( "spectrogram.pdf" ); + + QPrintDialog dialog( &printer ); + if ( dialog.exec() ) + { + QwtPlotRenderer renderer; + + if ( printer.colorMode() == QPrinter::GrayScale ) + { + renderer.setDiscardFlag( QwtPlotRenderer::DiscardBackground ); + renderer.setDiscardFlag( QwtPlotRenderer::DiscardCanvasBackground ); + renderer.setDiscardFlag( QwtPlotRenderer::DiscardCanvasFrame ); + renderer.setLayoutFlag( QwtPlotRenderer::FrameWithScales ); + } + + renderer.renderTo( this, printer ); + } +} + +#endif diff --git a/qwt/examples/spectrogram/plot.h b/qwt/examples/spectrogram/plot.h new file mode 100644 index 000000000..c741a1748 --- /dev/null +++ b/qwt/examples/spectrogram/plot.h @@ -0,0 +1,22 @@ +#include +#include + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget * = NULL ); + +public Q_SLOTS: + void showContour( bool on ); + void showSpectrogram( bool on ); + void setAlpha( int ); + +#ifndef QT_NO_PRINTER + void printPlot(); +#endif + +private: + QwtPlotSpectrogram *d_spectrogram; +}; diff --git a/qwt/examples/spectrogram/spectrogram.pro b/qwt/examples/spectrogram/spectrogram.pro new file mode 100644 index 000000000..b35b101e8 --- /dev/null +++ b/qwt/examples/spectrogram/spectrogram.pro @@ -0,0 +1,19 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = spectrogram + +HEADERS = \ + plot.h + +SOURCES = \ + plot.cpp \ + main.cpp diff --git a/qwt/examples/stockchart/griditem.cpp b/qwt/examples/stockchart/griditem.cpp new file mode 100644 index 000000000..c73beffd4 --- /dev/null +++ b/qwt/examples/stockchart/griditem.cpp @@ -0,0 +1,278 @@ +#include "griditem.h" +#include +#include +#include + +GridItem::GridItem(): + QwtPlotItem( QwtText( "Grid" ) ), + m_orientations( Qt::Horizontal | Qt::Vertical ), + m_gridAttributes( AutoUpdate | FillCanvas ), + m_isXMinEnabled( false ), + m_isYMinEnabled( false ) +{ + setItemInterest( QwtPlotItem::ScaleInterest, true ); + setZ( 10.0 ); +} + +GridItem::~GridItem() +{ +} + +int GridItem::rtti() const +{ + return QwtPlotItem::Rtti_PlotUserItem + 99; // something +} + +void GridItem::setGridAttribute( GridAttribute attribute, bool on ) +{ + if ( bool( m_gridAttributes & attribute ) == on ) + return; + + if ( on ) + m_gridAttributes |= attribute; + else + m_gridAttributes &= ~attribute; + + itemChanged(); +} + +bool GridItem::testGridAttribute( GridAttribute attribute ) const +{ + return m_gridAttributes & attribute; +} + +void GridItem::setOrientations( Qt::Orientations orientations ) +{ + if ( m_orientations != orientations ) + { + m_orientations = orientations; + itemChanged(); + } +} + +Qt::Orientations GridItem::orientations() const +{ + return m_orientations; +} + +void GridItem::enableXMin( bool enabled ) +{ + if ( enabled != m_isXMinEnabled ) + { + m_isXMinEnabled = enabled; + itemChanged(); + } +} + +bool GridItem::isXMinEnabled() const +{ + return m_isXMinEnabled; +} + +void GridItem::enableYMin( bool enabled ) +{ + if ( enabled != m_isYMinEnabled ) + { + m_isYMinEnabled = enabled; + itemChanged(); + } +} + +bool GridItem::isYMinEnabled() const +{ + return m_isYMinEnabled; +} + +void GridItem::setXDiv( const QwtScaleDiv &scaleDiv ) +{ + if ( m_xScaleDiv != scaleDiv ) + { + m_xScaleDiv = scaleDiv; + itemChanged(); + } +} + +void GridItem::setYDiv( const QwtScaleDiv &scaleDiv ) +{ + if ( m_yScaleDiv != scaleDiv ) + { + m_yScaleDiv = scaleDiv; + itemChanged(); + } +} + +void GridItem::setPalette( const QPalette &palette ) +{ + if ( m_palette != palette ) + { + m_palette = palette; + itemChanged(); + } +} + +QPalette GridItem::palette() const +{ + return m_palette; +} + +void GridItem::draw( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect ) const +{ + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + const QRectF area = QwtScaleMap::invTransform( xMap, yMap, canvasRect ); + + QList xValues; + if ( m_orientations & Qt::Horizontal ) + { + xValues = m_xScaleDiv.ticks( QwtScaleDiv::MajorTick ); + + if ( m_isXMinEnabled ) + { + xValues += m_xScaleDiv.ticks( QwtScaleDiv::MediumTick ); + xValues += m_xScaleDiv.ticks( QwtScaleDiv::MinorTick ); + } + + if ( m_gridAttributes & FillCanvas ) + { + xValues += area.left(); + xValues += area.right(); + } + + qSort( xValues ); + } + + QList yValues; + if ( m_orientations & Qt::Vertical ) + { + yValues = m_yScaleDiv.ticks( QwtScaleDiv::MajorTick ); + + if ( m_isYMinEnabled ) + { + yValues += m_yScaleDiv.ticks( QwtScaleDiv::MediumTick ); + yValues += m_yScaleDiv.ticks( QwtScaleDiv::MinorTick ); + } + + if ( m_gridAttributes & FillCanvas ) + { + yValues += area.top(); + yValues += area.bottom(); + } + + qSort( yValues ); + } + + painter->setPen( Qt::NoPen ); + + if ( ( m_orientations & Qt::Horizontal ) && + ( m_orientations & Qt::Vertical ) ) + { + for ( int i = 1; i < xValues.size(); i++ ) + { + double x1 = xMap.transform( xValues[i - 1] ); + double x2 = xMap.transform( xValues[i] ); + + if ( doAlign ) + { + x1 = qRound( x1 ); + x2 = qRound( x2 ); + } + + for ( int j = 1; j < yValues.size(); j++ ) + { + const QRectF rect( xValues[i - 1], yValues[j - 1], + xValues[i] - xValues[i - 1], yValues[j] - yValues[j - 1] ); + + painter->setBrush( brush( i - 1, j - 1, rect ) ); + + double y1 = yMap.transform( yValues[j - 1] ); + double y2 = yMap.transform( yValues[j] ); + + if ( doAlign ) + { + y1 = qRound( y1 ); + y2 = qRound( y2 ); + } + + QwtPainter::drawRect( painter, x1, y1, x2 - x1, y2 - y1 ); + } + } + } + else if ( m_orientations & Qt::Horizontal ) + { + for ( int i = 1; i < xValues.size(); i++ ) + { + const QRectF rect( xValues[i - 1], area.top(), + xValues[i] - xValues[i - 1], area.bottom() ); + + painter->setBrush( brush( i - 1, 0, rect ) ); + + double x1 = xMap.transform( xValues[i - 1] ); + double x2 = xMap.transform( xValues[i] ); + + if ( doAlign ) + { + x1 = qRound( x1 ); + x2 = qRound( x2 ); + } + + QwtPainter::drawRect( painter, + x1, canvasRect.top(), x2 - x1, canvasRect.height() ); + } + } + else if ( m_orientations & Qt::Vertical ) + { + for ( int i = 1; i < yValues.size(); i++ ) + { + const QRectF rect( area.left(), yValues[i - 1], + area.width(), yValues[i] - yValues[i - 1] ); + + painter->setBrush( brush( 0, i - 1, rect ) ); + + double y1 = yMap.transform( yValues[i - 1] ); + double y2 = yMap.transform( yValues[i] ); + + if ( doAlign ) + { + y1 = qRound( y1 ); + y2 = qRound( y2 ); + } + + QwtPainter::drawRect( painter, canvasRect.left(), y1, + canvasRect.width(), y2 - y1 ); + } + } +} + +const QwtScaleDiv &GridItem::xScaleDiv() const +{ + return m_xScaleDiv; +} + +const QwtScaleDiv &GridItem::yScaleDiv() const +{ + return m_yScaleDiv; +} + +void GridItem::updateScaleDiv( + const QwtScaleDiv& xScaleDiv, const QwtScaleDiv& yScaleDiv ) +{ + if ( m_gridAttributes & AutoUpdate ) + { + setXDiv( xScaleDiv ); + setYDiv( yScaleDiv ); + } +} + +QBrush GridItem::brush( int row, int column, const QRectF & ) const +{ + /* + We need some sort of origin to avoid, that the brush + changes for the same rectangle when panning + */ + if ( ( row + column ) % 2 ) + return QBrush( m_palette.brush( QPalette::Base ) ); + else + return QBrush( m_palette.brush( QPalette::AlternateBase ) ); +} diff --git a/qwt/examples/stockchart/griditem.h b/qwt/examples/stockchart/griditem.h new file mode 100644 index 000000000..f506a64fd --- /dev/null +++ b/qwt/examples/stockchart/griditem.h @@ -0,0 +1,70 @@ +#ifndef _GRID_ITEM_H_ +#define _GRID_ITEM_H_ + +#include +#include +#include + +class GridItem: public QwtPlotItem +{ +public: + enum GridAttribute + { + AutoUpdate = 0x01, + FillCanvas = 0x02 + }; + + typedef QFlags GridAttributes; + + explicit GridItem(); + virtual ~GridItem(); + + virtual int rtti() const; + + void setGridAttribute( GridAttribute, bool on = true ); + bool testGridAttribute( GridAttribute ) const; + + void setOrientations( Qt::Orientations ); + Qt::Orientations orientations() const; + + void enableXMin( bool ); + bool isXMinEnabled() const; + + void enableYMin( bool ); + bool isYMinEnabled() const; + + void setXDiv( const QwtScaleDiv &sx ); + const QwtScaleDiv &xScaleDiv() const; + + void setYDiv( const QwtScaleDiv &sy ); + const QwtScaleDiv &yScaleDiv() const; + + void setPalette( const QPalette & ); + QPalette palette() const; + + virtual void draw( QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &rect ) const; + + virtual void updateScaleDiv( + const QwtScaleDiv &xMap, const QwtScaleDiv &yMap ); + +protected: + virtual QBrush brush( int row, int column, const QRectF & ) const; + +private: + Qt::Orientations m_orientations; + GridAttributes m_gridAttributes; + + QwtScaleDiv m_xScaleDiv; + QwtScaleDiv m_yScaleDiv; + + bool m_isXMinEnabled; + bool m_isYMinEnabled; + + QPalette m_palette; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( GridItem::GridAttributes ) + +#endif diff --git a/qwt/examples/stockchart/legend.cpp b/qwt/examples/stockchart/legend.cpp new file mode 100644 index 000000000..2b065d628 --- /dev/null +++ b/qwt/examples/stockchart/legend.cpp @@ -0,0 +1,353 @@ +#include "legend.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void qwtRenderBackground( QPainter *painter, + const QRectF &rect, const QWidget *widget ) +{ + if ( widget->testAttribute( Qt::WA_StyledBackground ) ) + { + QStyleOption opt; + opt.initFrom( widget ); + opt.rect = rect.toAlignedRect(); + + widget->style()->drawPrimitive( + QStyle::PE_Widget, &opt, painter, widget); + } + else + { + const QBrush brush = + widget->palette().brush( widget->backgroundRole() ); + + painter->fillRect( rect, brush ); + } +} + +class LegendTreeView: public QTreeView +{ +public: + LegendTreeView( Legend * ); + + QStandardItem *rootItem( int rtti ); + QStandardItem *insertRootItem( int rtti ); + + QList itemList( const QwtPlotItem * ); + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; +}; + +LegendTreeView::LegendTreeView( Legend *legend ): + QTreeView( legend ) +{ + setFrameStyle( NoFrame ); + viewport()->setBackgroundRole(QPalette::Background); + viewport()->setAutoFillBackground( false ); + + setRootIsDecorated( true ); + setHeaderHidden( true ); + + QStandardItemModel *model = new QStandardItemModel(); + + setModel( model ); + + // we want unstyled items + setItemDelegate( new QItemDelegate( this ) ); +} + +QStandardItem *LegendTreeView::rootItem( int rtti ) +{ + QStandardItemModel *mdl = + qobject_cast( model() ); + + for ( int row = 0; row < mdl->rowCount(); row++ ) + { + QStandardItem *item = mdl->item( row ); + if ( item->data() == rtti ) + return item; + } + + return NULL; +} + +QList LegendTreeView::itemList( + const QwtPlotItem *plotItem ) +{ + QList itemList; + + const QStandardItem *rootItem = this->rootItem( plotItem->rtti() ); + if ( rootItem ) + { + for ( int i = 0; i < rootItem->rowCount(); i++ ) + { + QStandardItem *item = rootItem->child( i ); + + const QVariant key = item->data(); + + if ( key.canConvert() ) + { + const qlonglong ptr = key.value(); + if ( ptr == qlonglong( plotItem ) ) + itemList += item; + } + } + } + + return itemList; +} + +QStandardItem *LegendTreeView::insertRootItem( int rtti ) +{ + QStandardItem *item = new QStandardItem(); + item->setEditable( false ); + item->setData( rtti ); + + switch( rtti ) + { + case QwtPlotItem::Rtti_PlotTradingCurve: + { + item->setText( "Curves" ); + break; + } + case QwtPlotItem::Rtti_PlotZone: + { + item->setText( "Zones" ); + break; + } + case QwtPlotItem::Rtti_PlotMarker: + { + item->setText( "Events" ); + break; + } + default: + break; + } + + QStandardItemModel *mdl = + qobject_cast( model() ); + + mdl->appendRow( item ); + setExpanded( mdl->index( mdl->rowCount() - 1, 0 ), true ); + + return item; +} + +QSize LegendTreeView::minimumSizeHint() const +{ + return QSize( -1, -1 ); +} + +QSize LegendTreeView::sizeHint() const +{ + QStyleOptionViewItem styleOption; + styleOption.initFrom( this ); + + const QAbstractItemDelegate *delegate = itemDelegate(); + + const QStandardItemModel *mdl = + qobject_cast( model() ); + + int w = 0; + int h = 0; + + for ( int row = 0; row < mdl->rowCount(); row++ ) + { + const QStandardItem *rootItem = mdl->item( row ); + + int wRow = 0; + for ( int i = 0; i < rootItem->rowCount(); i++ ) + { + const QSize hint = delegate->sizeHint( styleOption, + rootItem->child( i )->index() ); + + wRow = qMax( wRow, hint.width() ); + h += hint.height(); + } + + const QSize rootHint = delegate->sizeHint( + styleOption, rootItem->index() ); + + wRow = qMax( wRow + indentation(), rootHint.width() ); + if ( wRow > w ) + w = wRow; + + if ( rootIsDecorated() ) + w += indentation(); + + h += rootHint.height(); + } + + int left, right, top, bottom; + getContentsMargins( &left, &top, &right, &bottom ); + + w += left + right; + h += top + bottom; + + return QSize( w, h ); +} + +Legend::Legend( QWidget *parent ): + QwtAbstractLegend( parent ) +{ + d_treeView = new LegendTreeView( this ); + + QVBoxLayout *layout = new QVBoxLayout( this ); + layout->setContentsMargins( 0, 0, 0, 0 ); + layout->addWidget( d_treeView ); + + connect( d_treeView, SIGNAL( clicked( const QModelIndex & ) ), + this, SLOT( handleClick( const QModelIndex & ) ) ); +} + +Legend::~Legend() +{ +} + +void Legend::renderLegend( QPainter *painter, + const QRectF &rect, bool fillBackground ) const +{ + if ( fillBackground ) + { + if ( autoFillBackground() || + testAttribute( Qt::WA_StyledBackground ) ) + { + qwtRenderBackground( painter, rect, d_treeView ); + } + } + + QStyleOptionViewItem styleOption; + styleOption.initFrom( this ); + styleOption.decorationAlignment = Qt::AlignCenter; + + const QAbstractItemDelegate *delegate = d_treeView->itemDelegate(); + + const QStandardItemModel *mdl = + qobject_cast( d_treeView->model() ); + + painter->save(); + painter->translate( rect.topLeft() ); + + for ( int row = 0; row < mdl->rowCount(); row++ ) + { + const QStandardItem *rootItem = mdl->item( row ); + + styleOption.rect = d_treeView->visualRect( rootItem->index() ); + if ( !styleOption.rect.isEmpty() ) + delegate->paint( painter, styleOption, rootItem->index() ); + + for ( int i = 0; i < rootItem->rowCount(); i++ ) + { + const QStandardItem *item = rootItem->child( i ); + + styleOption.rect = d_treeView->visualRect( item->index() ); + if ( !styleOption.rect.isEmpty() ) + { + delegate->paint( painter, styleOption, item->index() ); + } + } + } + painter->restore(); +} + +bool Legend::isEmpty() const +{ + return d_treeView->model()->rowCount() == 0; +} + +int Legend::scrollExtent( Qt::Orientation orientation ) const +{ + Q_UNUSED( orientation ); + + return style()->pixelMetric( QStyle::PM_ScrollBarExtent ); +} + +void Legend::updateLegend( const QVariant &itemInfo, + const QList &data ) +{ + QwtPlotItem *plotItem = qvariant_cast( itemInfo ); + + QStandardItem *rootItem = d_treeView->rootItem( plotItem->rtti() ); + QList itemList = d_treeView->itemList( plotItem ); + + while ( itemList.size() > data.size() ) + { + QStandardItem *item = itemList.takeLast(); + rootItem->removeRow( item->row() ); + } + + if ( !data.isEmpty() ) + { + if ( rootItem == NULL ) + rootItem = d_treeView->insertRootItem( plotItem->rtti() ); + + while ( itemList.size() < data.size() ) + { + QStandardItem *item = new QStandardItem(); + item->setEditable( false ); + item->setData( qlonglong( plotItem ) ); + item->setCheckable( true ); + item->setCheckState( plotItem->isVisible() ? + Qt::Checked : Qt::Unchecked ); + + itemList += item; + rootItem->appendRow( item ); + } + + for ( int i = 0; i < itemList.size(); i++ ) + updateItem( itemList[i], data[i] ); + } + else + { + if ( rootItem && rootItem->rowCount() == 0 ) + d_treeView->model()->removeRow( rootItem->row() ); + } + + d_treeView->updateGeometry(); +} + +void Legend::updateItem( QStandardItem *item, const QwtLegendData &data ) +{ + const QVariant titleValue = data.value( QwtLegendData::TitleRole ); + + QwtText title; + if ( titleValue.canConvert() ) + { + item->setText( title.text() ); + title = titleValue.value(); + } + else if ( titleValue.canConvert() ) + { + title.setText( titleValue.value() ); + } + item->setText( title.text() ); + + const QVariant iconValue = data.value( QwtLegendData::IconRole ); + + QPixmap pm; + if ( iconValue.canConvert() ) + pm = iconValue.value(); + + item->setData(pm, Qt::DecorationRole); +} + +void Legend::handleClick( const QModelIndex &index ) +{ + const QStandardItemModel *model = + qobject_cast( d_treeView->model() ); + + const QStandardItem *item = model->itemFromIndex( index ); + if ( item->isCheckable() ) + { + const qlonglong ptr = item->data().value(); + + Q_EMIT checked( (QwtPlotItem *)ptr, + item->checkState() == Qt::Checked, 0 ); + } +} diff --git a/qwt/examples/stockchart/legend.h b/qwt/examples/stockchart/legend.h new file mode 100644 index 000000000..56cd7cdf2 --- /dev/null +++ b/qwt/examples/stockchart/legend.h @@ -0,0 +1,42 @@ +#ifndef _LEGEND_H_ +#define _LEGEND_H_ + +#include + +class LegendTreeView; +class QStandardItem; +class QModelIndex; +class QwtPlotItem; + +class Legend : public QwtAbstractLegend +{ + Q_OBJECT + +public: + explicit Legend( QWidget *parent = NULL ); + virtual ~Legend(); + + virtual void renderLegend( QPainter *, + const QRectF &, bool fillBackground ) const; + + virtual bool isEmpty() const; + + virtual int scrollExtent( Qt::Orientation ) const; + +Q_SIGNALS: + void checked( QwtPlotItem *plotItem, bool on, int index ); + +public Q_SLOTS: + virtual void updateLegend( const QVariant &, + const QList & ); + +private Q_SLOTS: + void handleClick( const QModelIndex & ); + +private: + void updateItem( QStandardItem *, const QwtLegendData & ); + + LegendTreeView *d_treeView; +}; + +#endif diff --git a/qwt/examples/stockchart/main.cpp b/qwt/examples/stockchart/main.cpp new file mode 100644 index 000000000..14e116a42 --- /dev/null +++ b/qwt/examples/stockchart/main.cpp @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include +#include "plot.h" + +class MainWindow: public QMainWindow +{ +public: + MainWindow( QWidget * = NULL ); + +private: + Plot *d_plot; +}; + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + d_plot = new Plot( this ); + setCentralWidget( d_plot ); + + QToolBar *toolBar = new QToolBar( this ); + + QComboBox *typeBox = new QComboBox( toolBar ); + typeBox->addItem( "Bars" ); + typeBox->addItem( "CandleSticks" ); + typeBox->setCurrentIndex( 1 ); + typeBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + + QToolButton *btnExport = new QToolButton( toolBar ); + btnExport->setText( "Export" ); + btnExport->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + connect( btnExport, SIGNAL( clicked() ), d_plot, SLOT( exportPlot() ) ); + + toolBar->addWidget( typeBox ); + toolBar->addWidget( btnExport ); + addToolBar( toolBar ); + + d_plot->setMode( typeBox->currentIndex() ); + connect( typeBox, SIGNAL( currentIndexChanged( int ) ), + d_plot, SLOT( setMode( int ) ) ); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow w; + w.resize( 600, 400 ); + w.show(); + + return a.exec(); +} diff --git a/qwt/examples/stockchart/plot.cpp b/qwt/examples/stockchart/plot.cpp new file mode 100644 index 000000000..5dc37c425 --- /dev/null +++ b/qwt/examples/stockchart/plot.cpp @@ -0,0 +1,253 @@ +#include "plot.h" +#include "legend.h" +#include "griditem.h" +#include "quotefactory.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Zoomer: public QwtPlotZoomer +{ +public: + Zoomer( QWidget *canvas ): + QwtPlotZoomer( canvas ) + { + setRubberBandPen( QColor( Qt::darkGreen ) ); + setTrackerMode( QwtPlotPicker::AlwaysOn ); + } + +protected: + virtual QwtText trackerTextF( const QPointF &pos ) const + { + const QDateTime dt = QwtDate::toDateTime( pos.x() ); + + QString s; + s += QwtDate::toString( QwtDate::toDateTime( pos.x() ), + "MMM dd hh:mm ", QwtDate::FirstThursday ); + + QwtText text( s ); + text.setColor( Qt::white ); + + QColor c = rubberBandPen().color(); + text.setBorderPen( QPen( c ) ); + text.setBorderRadius( 6 ); + c.setAlpha( 170 ); + text.setBackgroundBrush( c ); + + return text; + } +}; + +class DateScaleDraw: public QwtDateScaleDraw +{ +public: + DateScaleDraw( Qt::TimeSpec timeSpec ): + QwtDateScaleDraw( timeSpec ) + { + // as we have dates from 2010 only we use + // format strings without the year + + setDateFormat( QwtDate::Millisecond, "hh:mm:ss:zzz\nddd dd MMM" ); + setDateFormat( QwtDate::Second, "hh:mm:ss\nddd dd MMM" ); + setDateFormat( QwtDate::Minute, "hh:mm\nddd dd MMM" ); + setDateFormat( QwtDate::Hour, "hh:mm\nddd dd MMM" ); + setDateFormat( QwtDate::Day, "ddd dd MMM" ); + setDateFormat( QwtDate::Week, "Www" ); + setDateFormat( QwtDate::Month, "MMM" ); + } +}; + +class ZoneItem: public QwtPlotZoneItem +{ +public: + ZoneItem( const QString &title ) + { + setTitle( title ); + setZ( 11 ); // on top the the grid + setOrientation( Qt::Vertical ); + setItemAttribute( QwtPlotItem::Legend, true ); + } + + void setColor( const QColor &color ) + { + QColor c = color; + + c.setAlpha( 100 ); + setPen( c ); + + c.setAlpha( 20 ); + setBrush( c ); + } + + void setInterval( const QDate &date1, const QDate &date2 ) + { + const QDateTime dt1( date1, QTime(), Qt::UTC ); + const QDateTime dt2( date2, QTime(), Qt::UTC ); + + QwtPlotZoneItem::setInterval( QwtDate::toDouble( dt1 ), + QwtDate::toDouble( dt2 ) ); + } +}; + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + setTitle( "Trading Chart" ); + + QwtDateScaleDraw *scaleDraw = new DateScaleDraw( Qt::UTC ); + QwtDateScaleEngine *scaleEngine = new QwtDateScaleEngine( Qt::UTC ); + + setAxisTitle( QwtAxis::xBottom, QString( "2010" ) ); + setAxisScaleDraw( QwtAxis::xBottom, scaleDraw ); + setAxisScaleEngine( QwtAxis::xBottom, scaleEngine ); + setAxisLabelRotation( QwtAxis::xBottom, -50.0 ); + setAxisLabelAlignment( QwtAxis::xBottom, Qt::AlignLeft | Qt::AlignBottom ); + + setAxisTitle( QwtAxis::yLeft, QString( "Price [EUR]" ) ); + +#if 0 + QwtLegend *legend = new QwtLegend; + legend->setDefaultItemMode( QwtLegendData::Checkable ); + insertLegend( legend, QwtPlot::RightLegend ); +#else + Legend *legend = new Legend; + insertLegend( legend, QwtPlot::RightLegend ); +#endif + + populate(); + + // LeftButton for the zooming + // MidButton for the panning + // RightButton: zoom out by 1 + // Ctrl+RighButton: zoom out to full size + + Zoomer* zoomer = new Zoomer( canvas() ); + zoomer->setMousePattern( QwtEventPattern::MouseSelect2, + Qt::RightButton, Qt::ControlModifier ); + zoomer->setMousePattern( QwtEventPattern::MouseSelect3, + Qt::RightButton ); + + QwtPlotPanner *panner = new QwtPlotPanner( canvas() ); + panner->setMouseButton( Qt::MidButton ); + + connect( legend, SIGNAL( checked( QwtPlotItem *, bool, int ) ), + SLOT( showItem( QwtPlotItem *, bool ) ) ); +} + +void Plot::populate() +{ + GridItem *gridItem = new GridItem(); +#if 0 + gridItem->setOrientations( Qt::Horizontal ); +#endif + gridItem->attach( this ); + + const Qt::GlobalColor colors[] = + { + Qt::red, + Qt::blue, + Qt::darkCyan, + Qt::darkMagenta, + Qt::darkYellow + }; + + const int numColors = sizeof( colors ) / sizeof( colors[0] ); + + for ( int i = 0; i < QuoteFactory::NumStocks; i++ ) + { + QuoteFactory::Stock stock = static_cast( i ); + + QwtPlotTradingCurve *curve = new QwtPlotTradingCurve(); + curve->setTitle( QuoteFactory::title( stock ) ); + curve->setOrientation( Qt::Vertical ); + curve->setSamples( QuoteFactory::samples2010( stock ) ); + + // as we have one sample per day a symbol width of + // 12h avoids overlapping symbols. We also bound + // the width, so that is is not scaled below 3 and + // above 15 pixels. + + curve->setSymbolExtent( 12 * 3600 * 1000.0 ); + curve->setMinSymbolWidth( 3 ); + curve->setMaxSymbolWidth( 15 ); + + const Qt::GlobalColor color = colors[ i % numColors ]; + + curve->setSymbolPen( color ); + curve->setSymbolBrush( QwtPlotTradingCurve::Decreasing, color ); + curve->setSymbolBrush( QwtPlotTradingCurve::Increasing, Qt::white ); + curve->attach( this ); + + showItem( curve, true ); + } + + for ( int i = 0; i < 2; i++ ) + { + QwtPlotMarker *marker = new QwtPlotMarker(); + + marker->setTitle( QString( "Event %1" ).arg( i + 1 ) ); + marker->setLineStyle( QwtPlotMarker::VLine ); + marker->setLinePen( colors[ i % numColors ], 0, Qt::DashLine ); + marker->setVisible( false ); + + QDateTime dt( QDate( 2010, 1, 1 ) ); + dt = dt.addDays( 77 * ( i + 1 ) ); + + marker->setValue( QwtDate::toDouble( dt ), 0.0 ); + + marker->setItemAttribute( QwtPlotItem::Legend, true ); + + marker->attach( this ); + } + + // to show how QwtPlotZoneItem works + + ZoneItem *zone1 = new ZoneItem( "Zone 1"); + zone1->setColor( Qt::darkBlue ); + zone1->setInterval( QDate( 2010, 3, 10 ), QDate( 2010, 3, 27 ) ); + zone1->setVisible( false ); + zone1->attach( this ); + + ZoneItem *zone2 = new ZoneItem( "Zone 2"); + zone2->setColor( Qt::darkMagenta ); + zone2->setInterval( QDate( 2010, 8, 1 ), QDate( 2010, 8, 24 ) ); + zone2->setVisible( false ); + zone2->attach( this ); + +} + +void Plot::setMode( int style ) +{ + QwtPlotTradingCurve::SymbolStyle symbolStyle = + static_cast( style ); + + QwtPlotItemList curves = itemList( QwtPlotItem::Rtti_PlotTradingCurve ); + for ( int i = 0; i < curves.size(); i++ ) + { + QwtPlotTradingCurve *curve = + static_cast( curves[i] ); + curve->setSymbolStyle( symbolStyle ); + } + + replot(); +} + +void Plot::showItem( QwtPlotItem *item, bool on ) +{ + item->setVisible( on ); + replot(); +} + +void Plot::exportPlot() +{ + QwtPlotRenderer renderer; + renderer.exportTo( this, "stockchart.pdf" ); +} diff --git a/qwt/examples/stockchart/plot.h b/qwt/examples/stockchart/plot.h new file mode 100644 index 000000000..bc6f883c2 --- /dev/null +++ b/qwt/examples/stockchart/plot.h @@ -0,0 +1,24 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ + +#include + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget * = NULL ); + +public Q_SLOTS: + void setMode( int ); + void exportPlot(); + +private Q_SLOTS: + void showItem( QwtPlotItem *, bool on ); + +private: + void populate(); +}; + +#endif diff --git a/qwt/examples/stockchart/quotefactory.cpp b/qwt/examples/stockchart/quotefactory.cpp new file mode 100644 index 000000000..495660185 --- /dev/null +++ b/qwt/examples/stockchart/quotefactory.cpp @@ -0,0 +1,856 @@ +#include "quotefactory.h" +#include + +typedef struct +{ + int day; + + double open; + double high; + double low; + double close; + +} t_Data2010; + +static t_Data2010 bmwData[] = +{ + { 3, 31.82, 32.46, 31.82, 32.05 }, + { 4, 31.96, 32.41, 31.78, 32.31 }, + { 5, 32.45, 33.04, 32.36, 32.81 }, + { 6, 32.65, 33.20, 32.38, 33.10 }, + { 7, 33.33, 33.43, 32.51, 32.65 }, + { 10, 32.99, 33.05, 32.11, 32.17 }, + { 11, 32.26, 32.26, 31.10, 31.24 }, + { 12, 31.03, 31.52, 31.01, 31.42 }, + { 13, 31.61, 32.18, 31.50, 31.89 }, + { 14, 32.05, 32.13, 31.36, 31.63 }, + { 17, 31.82, 32.12, 31.43, 32.10 }, + { 18, 32.33, 32.45, 31.65, 32.43 }, + { 19, 32.30, 32.39, 31.67, 31.80 }, + { 20, 32.00, 32.19, 31.16, 31.16 }, + { 21, 31.14, 31.37, 30.32, 30.70 }, + { 24, 30.31, 30.79, 30.05, 30.14 }, + { 25, 30.00, 30.53, 29.40, 30.25 }, + { 26, 29.93, 30.14, 29.38, 29.59 }, + { 27, 29.95, 30.28, 29.49, 29.55 }, + { 28, 29.90, 31.30, 29.85, 30.96 }, + { 31, 30.69, 31.31, 30.56, 31.07 }, + { 32, 31.05, 31.28, 30.58, 31.17 }, + { 33, 31.28, 31.77, 31.01, 31.23 }, + { 34, 31.32, 31.53, 30.21, 30.33 }, + { 35, 30.25, 30.28, 29.43, 29.92 }, + { 38, 30.00, 30.45, 29.33, 29.61 }, + { 39, 29.75, 30.07, 29.35, 29.62 }, + { 40, 29.89, 30.12, 29.55, 29.67 }, + { 41, 29.81, 29.87, 29.02, 29.49 }, + { 42, 29.59, 29.84, 28.28, 29.00 }, + { 45, 29.00, 29.29, 28.46, 28.65 }, + { 46, 28.90, 29.45, 28.60, 29.41 }, + { 47, 29.68, 29.77, 29.35, 29.61 }, + { 48, 29.58, 29.76, 28.45, 29.42 }, + { 49, 29.22, 30.43, 29.01, 30.43 }, + { 52, 30.65, 30.67, 30.06, 30.26 }, + { 53, 30.35, 30.52, 29.53, 29.69 }, + { 54, 29.79, 29.87, 29.18, 29.49 }, + { 55, 29.25, 29.82, 29.06, 29.38 }, + { 56, 29.69, 30.00, 29.55, 29.78 }, + { 59, 30.20, 30.58, 29.95, 30.44 }, + { 60, 30.57, 31.47, 30.49, 31.34 }, + { 61, 31.40, 31.76, 31.08, 31.65 }, + { 62, 31.50, 31.80, 31.34, 31.56 }, + { 63, 31.63, 32.45, 31.63, 32.37 }, + { 66, 32.40, 32.54, 31.81, 31.99 }, + { 67, 31.83, 32.29, 31.58, 32.13 }, + { 68, 32.06, 32.33, 31.81, 32.26 }, + { 69, 32.17, 33.26, 32.16, 32.69 }, + { 70, 32.85, 32.94, 32.44, 32.54 }, + { 73, 32.62, 32.92, 32.54, 32.64 }, + { 74, 32.78, 32.97, 32.55, 32.76 }, + { 75, 32.83, 33.04, 32.45, 32.47 }, + { 76, 32.43, 32.56, 31.98, 32.10 }, + { 77, 32.42, 32.49, 32.02, 32.06 }, + { 80, 31.92, 32.65, 31.87, 32.50 }, + { 81, 32.69, 33.44, 32.61, 33.15 }, + { 82, 33.33, 33.51, 32.92, 33.38 }, + { 83, 33.50, 34.10, 33.49, 34.04 }, + { 84, 33.94, 34.35, 33.81, 34.20 }, + { 87, 34.40, 34.73, 34.01, 34.12 }, + { 88, 34.26, 34.43, 33.71, 33.78 }, + { 89, 33.88, 34.29, 33.78, 34.18 }, + { 90, 35.11, 35.49, 34.97, 35.15 }, + { 95, 35.40, 35.45, 35.15, 35.41 }, + { 96, 35.34, 35.41, 34.77, 34.80 }, + { 97, 34.80, 35.06, 34.44, 34.53 }, + { 98, 34.88, 35.05, 34.64, 34.86 }, + { 101, 35.25, 35.39, 34.99, 35.12 }, + { 102, 35.06, 35.38, 34.88, 35.35 }, + { 103, 35.06, 35.58, 34.88, 35.51 }, + { 104, 35.59, 35.61, 35.09, 35.33 }, + { 105, 35.15, 36.19, 35.15, 35.56 }, + { 108, 35.45, 35.78, 35.10, 35.31 }, + { 109, 36.56, 37.08, 36.41, 36.79 }, + { 110, 36.75, 36.99, 36.37, 36.58 }, + { 111, 36.63, 37.12, 35.93, 36.25 }, + { 112, 36.60, 37.40, 36.33, 37.28 }, + { 115, 37.60, 37.85, 37.26, 37.82 }, + { 116, 37.85, 37.96, 37.06, 37.06 }, + { 117, 36.80, 37.28, 36.14, 36.79 }, + { 118, 36.70, 36.90, 36.19, 36.78 }, + { 119, 36.83, 37.62, 36.70, 37.13 }, + { 122, 37.08, 37.50, 36.72, 37.38 }, + { 123, 37.51, 37.56, 35.38, 35.84 }, + { 124, 36.61, 36.62, 35.42, 35.98 }, + { 125, 35.45, 37.38, 35.45, 36.42 }, + { 126, 35.78, 36.90, 35.05, 35.48 }, + { 129, 36.23, 37.74, 36.20, 37.68 }, + { 130, 36.87, 38.19, 36.73, 38.18 }, + { 131, 37.97, 39.35, 37.74, 39.00 }, + { 132, 39.35, 40.06, 39.15, 39.52 }, + { 133, 39.42, 39.88, 38.46, 38.62 }, + { 136, 38.38, 39.59, 38.25, 38.72 }, + { 137, 39.10, 39.65, 38.90, 39.65 }, + { 138, 38.15, 38.70, 36.97, 37.00 }, + { 139, 37.44, 37.55, 35.43, 36.18 }, + { 140, 36.20, 36.57, 35.28, 36.03 }, + { 143, 36.30, 36.38, 35.41, 36.14 }, + { 144, 35.56, 35.67, 34.64, 35.29 }, + { 145, 35.80, 36.32, 35.50, 35.76 }, + { 146, 36.30, 37.33, 36.06, 37.21 }, + { 147, 37.42, 37.88, 37.02, 37.67 }, + { 150, 37.57, 38.09, 37.49, 37.97 }, + { 151, 37.96, 38.38, 36.98, 38.06 }, + { 152, 37.80, 38.46, 37.37, 38.46 }, + { 153, 39.24, 39.55, 38.94, 39.22 }, + { 154, 39.35, 39.40, 37.82, 38.10 }, + { 157, 37.40, 38.55, 37.40, 38.24 }, + { 158, 38.33, 38.54, 37.31, 37.68 }, + { 159, 37.85, 38.98, 37.76, 38.91 }, + { 160, 38.85, 40.92, 38.68, 40.65 }, + { 161, 40.95, 41.27, 39.72, 40.08 }, + { 164, 40.59, 40.85, 39.56, 39.76 }, + { 165, 39.35, 40.05, 39.34, 39.85 }, + { 166, 40.18, 40.41, 38.80, 39.03 }, + { 167, 38.91, 39.96, 38.74, 39.70 }, + { 168, 39.85, 40.87, 39.82, 40.71 }, + { 171, 41.70, 42.33, 41.43, 41.80 }, + { 172, 41.55, 41.88, 41.06, 41.51 }, + { 173, 41.11, 42.01, 41.07, 41.49 }, + { 174, 41.97, 42.19, 41.25, 41.36 }, + { 175, 41.36, 41.38, 40.22, 40.36 }, + { 178, 40.66, 41.64, 40.36, 41.26 }, + { 179, 40.84, 40.88, 39.87, 39.90 }, + { 180, 40.10, 40.61, 39.80, 40.06 }, + { 181, 39.56, 39.56, 38.08, 38.20 }, + { 182, 38.83, 39.20, 37.79, 37.88 }, + { 185, 38.10, 38.53, 37.91, 38.11 }, + { 186, 38.29, 39.27, 38.29, 39.00 }, + { 187, 38.70, 39.87, 38.62, 39.75 }, + { 188, 39.62, 39.97, 38.87, 38.91 }, + { 189, 39.30, 39.39, 38.60, 39.15 }, + { 192, 39.30, 39.30, 38.87, 38.90 }, + { 193, 39.00, 42.14, 39.00, 42.13 }, + { 194, 42.42, 42.71, 40.99, 41.54 }, + { 195, 41.75, 42.94, 41.36, 42.26 }, + { 196, 42.26, 43.29, 41.80, 42.15 }, + { 199, 41.85, 42.09, 41.17, 41.35 }, + { 200, 42.00, 42.12, 40.60, 41.07 }, + { 201, 41.30, 41.80, 40.61, 40.92 }, + { 202, 40.83, 42.35, 40.79, 41.97 }, + { 203, 41.95, 42.24, 41.58, 41.99 }, + { 206, 42.17, 42.29, 41.61, 42.11 }, + { 207, 42.24, 42.49, 41.21, 41.50 }, + { 208, 41.68, 41.88, 40.41, 40.72 }, + { 209, 40.77, 41.22, 40.40, 40.72 }, + { 210, 40.44, 41.40, 39.96, 41.31 }, + { 213, 41.46, 42.01, 41.02, 41.87 }, + { 214, 42.75, 44.04, 42.75, 43.16 }, + { 215, 43.14, 43.83, 42.49, 43.68 }, + { 216, 43.69, 44.99, 43.47, 44.51 }, + { 217, 44.90, 45.38, 43.72, 43.90 }, + { 220, 44.49, 44.60, 43.97, 44.31 }, + { 221, 44.35, 44.40, 43.15, 43.35 }, + { 222, 43.05, 43.08, 42.33, 42.40 }, + { 223, 42.30, 42.92, 40.78, 41.90 }, + { 224, 42.02, 42.22, 41.28, 41.88 }, + { 227, 42.08, 42.29, 41.40, 41.81 }, + { 228, 41.81, 43.10, 41.74, 43.10 }, + { 229, 43.02, 43.59, 42.76, 43.50 }, + { 230, 43.68, 44.07, 42.66, 42.84 }, + { 231, 42.84, 42.92, 41.74, 41.87 }, + { 234, 42.00, 42.31, 41.60, 41.86 }, + { 235, 41.56, 41.76, 41.10, 41.52 }, + { 236, 41.22, 41.97, 40.83, 41.44 }, + { 237, 41.56, 41.96, 41.35, 41.69 }, + { 238, 41.60, 41.81, 40.74, 41.76 }, + { 241, 41.76, 41.90, 40.94, 41.21 }, + { 242, 40.50, 41.67, 40.15, 41.67 }, + { 243, 42.00, 42.99, 41.38, 42.91 }, + { 244, 42.64, 43.89, 42.64, 43.60 }, + { 245, 43.60, 44.53, 43.26, 44.10 }, + { 248, 44.17, 44.20, 43.47, 44.03 }, + { 249, 43.97, 44.31, 43.51, 43.94 }, + { 250, 43.72, 44.99, 43.60, 44.99 }, + { 251, 44.70, 45.74, 44.51, 45.40 }, + { 252, 45.00, 46.87, 44.99, 46.21 }, + { 255, 46.65, 47.05, 45.91, 46.44 }, + { 256, 46.30, 47.12, 46.21, 47.12 }, + { 257, 46.98, 47.56, 46.88, 47.25 }, + { 258, 47.18, 47.45, 46.82, 47.35 }, + { 259, 47.81, 48.03, 47.10, 47.41 }, + { 262, 47.37, 49.12, 47.22, 49.12 }, + { 263, 48.85, 49.42, 48.45, 48.48 }, + { 264, 48.48, 48.70, 47.57, 48.08 }, + { 265, 48.49, 48.69, 47.49, 48.29 }, + { 266, 48.09, 50.53, 48.03, 50.35 }, + { 269, 50.15, 50.35, 49.60, 50.15 }, + { 270, 49.80, 50.69, 49.31, 50.67 }, + { 271, 51.00, 51.84, 50.64, 51.06 }, + { 272, 50.90, 52.15, 50.50, 51.44 }, + { 273, 51.44, 51.44, 49.12, 49.30 }, + { 276, 49.06, 49.19, 47.92, 48.22 }, + { 277, 48.37, 49.96, 47.82, 49.96 }, + { 278, 49.77, 50.05, 49.13, 49.49 }, + { 279, 49.31, 50.25, 48.81, 50.00 }, + { 280, 50.26, 50.29, 49.42, 50.07 }, + { 283, 50.20, 50.62, 49.82, 49.87 }, + { 284, 49.44, 50.49, 49.06, 50.20 }, + { 285, 50.40, 50.49, 49.88, 50.07 }, + { 286, 50.50, 50.50, 49.74, 50.00 }, + { 287, 50.08, 50.25, 49.19, 49.45 }, + { 290, 49.23, 49.42, 48.58, 49.00 }, + { 291, 48.99, 49.69, 48.84, 49.12 }, + { 292, 49.09, 49.60, 48.90, 49.60 }, + { 293, 49.54, 50.09, 49.31, 50.02 }, + { 294, 50.19, 50.44, 49.54, 50.03 }, + { 297, 50.31, 51.02, 50.20, 50.72 }, + { 298, 50.49, 50.94, 50.12, 50.44 }, + { 299, 50.04, 50.45, 49.10, 49.88 }, + { 300, 50.15, 50.48, 49.53, 49.85 }, + { 301, 49.49, 51.65, 49.44, 51.51 }, + { 304, 51.77, 52.99, 51.65, 52.96 }, + { 305, 52.70, 52.70, 52.10, 52.35 }, + { 306, 50.75, 52.38, 50.65, 51.64 }, + { 307, 52.05, 54.15, 52.00, 54.08 }, + { 308, 54.14, 54.99, 53.76, 54.06 }, + { 311, 53.69, 53.77, 52.86, 53.41 }, + { 312, 53.40, 54.98, 53.22, 54.91 }, + { 313, 54.60, 54.70, 53.33, 53.75 }, + { 314, 54.00, 54.49, 53.60, 54.42 }, + { 315, 53.33, 55.90, 52.85, 55.29 }, + { 318, 55.07, 56.52, 54.90, 56.06 }, + { 319, 55.68, 55.83, 54.62, 54.62 }, + { 320, 54.72, 54.73, 53.87, 54.30 }, + { 321, 54.96, 56.30, 54.94, 56.30 }, + { 322, 56.34, 56.73, 55.65, 56.67 }, + { 325, 57.33, 58.90, 57.30, 57.69 }, + { 326, 57.15, 58.62, 56.39, 56.47 }, + { 327, 57.01, 59.12, 56.48, 59.12 }, + { 328, 59.10, 60.00, 58.84, 59.90 }, + { 329, 59.31, 59.76, 58.13, 59.25 }, + { 332, 59.75, 59.91, 57.74, 57.74 }, + { 333, 57.70, 59.24, 57.22, 57.93 }, + { 334, 58.35, 60.90, 58.35, 60.90 }, + { 335, 61.69, 63.80, 61.55, 63.80 }, + { 336, 63.70, 65.49, 63.48, 63.69 }, + { 339, 64.00, 64.53, 62.75, 62.81 }, + { 340, 63.00, 64.49, 62.40, 63.98 }, + { 341, 63.50, 63.50, 61.90, 61.90 }, + { 342, 62.42, 62.66, 58.88, 60.20 }, + { 343, 60.50, 62.99, 60.39, 62.52 }, + { 346, 62.00, 63.44, 62.00, 63.44 }, + { 347, 63.40, 63.44, 62.14, 62.47 }, + { 348, 62.00, 62.83, 61.40, 62.49 }, + { 349, 62.40, 63.26, 61.79, 62.80 }, + { 350, 62.95, 63.15, 61.80, 61.95 }, + { 353, 61.90, 63.23, 61.64, 63.15 }, + { 354, 63.40, 64.80, 62.92, 64.80 }, + { 355, 64.98, 65.11, 64.30, 64.37 }, + { 356, 64.55, 64.69, 63.24, 63.26 }, + { 360, 62.70, 62.70, 59.12, 59.22 }, + { 361, 59.69, 59.98, 57.66, 58.25 }, + { 362, 58.10, 58.92, 58.08, 58.72 }, + { 363, 59.10, 59.47, 58.62, 58.85 } +}; + +static t_Data2010 porscheData[] = +{ + { 3, 43.00, 43.96, 42.80, 43.37 }, + { 4, 43.15, 45.00, 43.00, 44.77 }, + { 5, 45.75, 46.50, 45.41, 45.65 }, + { 6, 45.67, 48.56, 45.32, 48.28 }, + { 7, 48.78, 48.81, 47.39, 48.00 }, + { 10, 48.26, 49.18, 47.86, 48.35 }, + { 11, 48.35, 48.65, 46.73, 47.05 }, + { 12, 46.51, 47.65, 46.35, 47.37 }, + { 13, 48.10, 48.70, 47.00, 48.13 }, + { 14, 48.10, 48.20, 46.79, 47.85 }, + { 17, 47.85, 48.57, 47.58, 48.10 }, + { 18, 47.85, 48.00, 46.51, 47.65 }, + { 19, 47.24, 47.62, 45.86, 46.40 }, + { 20, 46.51, 46.61, 44.87, 45.00 }, + { 21, 45.00, 45.11, 42.92, 43.50 }, + { 24, 43.00, 43.83, 42.48, 42.97 }, + { 25, 42.47, 43.37, 41.90, 43.23 }, + { 26, 43.00, 43.00, 41.55, 42.28 }, + { 27, 42.80, 42.83, 41.65, 41.72 }, + { 28, 40.91, 41.50, 40.10, 41.11 }, + { 31, 40.85, 41.85, 40.81, 41.55 }, + { 32, 41.69, 43.16, 41.28, 42.87 }, + { 33, 43.47, 43.53, 42.30, 42.47 }, + { 34, 42.67, 42.85, 40.95, 41.15 }, + { 35, 40.81, 40.82, 39.56, 40.03 }, + { 38, 40.00, 40.94, 38.45, 38.95 }, + { 39, 38.65, 38.95, 37.83, 38.24 }, + { 40, 38.30, 38.65, 37.92, 38.30 }, + { 41, 38.40, 39.88, 37.91, 38.36 }, + { 42, 38.60, 38.84, 36.06, 36.99 }, + { 45, 37.31, 37.58, 35.85, 36.06 }, + { 46, 36.45, 36.78, 35.90, 36.78 }, + { 47, 37.01, 37.84, 36.14, 37.42 }, + { 48, 37.40, 37.73, 36.03, 37.16 }, + { 49, 36.90, 38.00, 36.72, 37.97 }, + { 52, 37.52, 38.12, 37.14, 37.14 }, + { 53, 37.22, 37.53, 36.34, 36.69 }, + { 54, 36.88, 36.93, 35.94, 36.55 }, + { 55, 36.35, 37.06, 35.75, 36.09 }, + { 56, 36.70, 37.05, 36.10, 36.90 }, + { 59, 37.10, 37.74, 36.78, 37.63 }, + { 60, 37.65, 38.58, 37.65, 38.56 }, + { 61, 38.35, 39.60, 38.35, 39.42 }, + { 62, 39.39, 40.15, 39.10, 39.70 }, + { 63, 39.75, 40.60, 39.10, 40.35 }, + { 66, 40.40, 40.40, 39.55, 39.97 }, + { 67, 40.05, 40.10, 39.13, 39.90 }, + { 68, 39.78, 40.55, 39.52, 40.37 }, + { 69, 39.86, 42.53, 39.62, 42.34 }, + { 70, 42.75, 44.73, 42.66, 43.03 }, + { 73, 43.27, 43.49, 42.60, 42.65 }, + { 74, 42.78, 43.78, 42.78, 43.78 }, + { 75, 43.73, 44.00, 42.57, 43.46 }, + { 76, 44.10, 44.51, 43.50, 44.51 }, + { 77, 44.40, 44.70, 44.04, 44.04 }, + { 80, 44.00, 44.05, 43.03, 43.69 }, + { 81, 43.13, 43.51, 42.08, 43.17 }, + { 82, 42.89, 44.71, 42.65, 44.20 }, + { 83, 44.31, 44.47, 43.59, 44.22 }, + { 84, 44.15, 45.15, 44.00, 45.13 }, + { 87, 45.45, 46.10, 45.20, 45.51 }, + { 88, 45.76, 46.10, 44.83, 45.17 }, + { 89, 45.60, 45.60, 44.90, 45.19 }, + { 90, 45.60, 46.46, 45.60, 46.37 }, + { 95, 46.00, 47.44, 46.00, 47.24 }, + { 96, 47.22, 47.48, 45.76, 46.04 }, + { 97, 45.05, 45.55, 44.04, 44.41 }, + { 98, 44.88, 45.44, 44.44, 44.99 }, + { 101, 45.20, 45.57, 44.88, 45.35 }, + { 102, 45.10, 46.03, 45.02, 45.71 }, + { 103, 46.02, 46.60, 45.54, 46.30 }, + { 104, 46.44, 46.60, 45.72, 46.04 }, + { 105, 45.80, 46.35, 44.67, 44.67 }, + { 108, 44.50, 45.17, 43.79, 43.83 }, + { 109, 45.39, 46.00, 44.66, 45.92 }, + { 110, 46.00, 46.46, 45.26, 46.26 }, + { 111, 46.29, 46.64, 44.94, 45.20 }, + { 112, 45.69, 46.22, 45.22, 45.97 }, + { 115, 46.30, 46.71, 45.85, 46.69 }, + { 116, 46.48, 46.48, 45.01, 45.01 }, + { 117, 44.60, 45.03, 42.97, 44.03 }, + { 118, 43.50, 44.38, 42.80, 43.76 }, + { 119, 43.40, 44.33, 42.85, 43.69 }, + { 122, 43.70, 43.70, 42.38, 42.71 }, + { 123, 42.95, 42.95, 40.39, 40.53 }, + { 124, 39.99, 40.15, 38.76, 39.95 }, + { 125, 39.05, 40.25, 37.17, 37.40 }, + { 126, 36.30, 37.25, 34.80, 35.58 }, + { 129, 37.54, 38.19, 37.12, 37.92 }, + { 130, 37.79, 38.08, 37.30, 38.08 }, + { 131, 37.94, 39.99, 37.78, 39.73 }, + { 132, 39.80, 40.20, 39.19, 39.73 }, + { 133, 39.35, 39.40, 36.61, 36.72 }, + { 136, 36.29, 38.48, 36.29, 37.58 }, + { 137, 38.33, 38.58, 37.64, 38.47 }, + { 138, 37.77, 38.42, 36.63, 36.67 }, + { 139, 36.40, 36.67, 33.83, 34.72 }, + { 140, 33.85, 34.69, 32.89, 34.07 }, + { 143, 34.49, 35.03, 33.15, 34.40 }, + { 144, 33.40, 33.42, 32.15, 32.54 }, + { 145, 33.28, 34.19, 32.76, 33.49 }, + { 146, 33.85, 35.77, 33.78, 35.49 }, + { 147, 35.99, 36.35, 35.08, 35.53 }, + { 150, 35.24, 35.81, 35.17, 35.34 }, + { 151, 35.21, 36.10, 34.42, 35.24 }, + { 152, 34.55, 35.20, 34.29, 34.85 }, + { 153, 35.63, 36.09, 35.29, 35.70 }, + { 154, 35.98, 35.98, 34.38, 34.50 }, + { 157, 34.45, 34.65, 32.94, 33.26 }, + { 158, 33.50, 33.65, 31.60, 31.91 }, + { 159, 32.42, 33.29, 32.00, 33.22 }, + { 160, 33.10, 33.97, 32.50, 33.58 }, + { 161, 33.97, 35.12, 33.83, 34.85 }, + { 164, 34.90, 35.70, 34.87, 34.97 }, + { 165, 34.40, 34.58, 32.86, 33.46 }, + { 166, 33.87, 33.87, 32.51, 32.87 }, + { 167, 33.17, 34.65, 32.62, 34.60 }, + { 168, 35.58, 35.64, 34.69, 35.06 }, + { 171, 36.52, 37.19, 36.00, 37.00 }, + { 172, 36.87, 37.48, 36.44, 36.85 }, + { 173, 36.38, 36.98, 36.05, 36.40 }, + { 174, 36.00, 36.50, 34.81, 35.08 }, + { 175, 35.21, 36.24, 34.53, 36.04 }, + { 178, 36.76, 37.24, 36.35, 37.16 }, + { 179, 36.49, 36.88, 35.75, 35.81 }, + { 180, 35.92, 36.28, 35.08, 35.29 }, + { 181, 35.00, 35.00, 33.49, 33.54 }, + { 182, 34.00, 34.40, 33.51, 33.51 }, + { 185, 33.80, 34.14, 33.60, 33.74 }, + { 186, 33.75, 35.79, 33.75, 34.96 }, + { 187, 34.85, 35.88, 34.48, 35.88 }, + { 188, 36.00, 36.64, 35.75, 35.96 }, + { 189, 36.41, 36.80, 35.85, 36.72 }, + { 192, 36.60, 37.22, 36.55, 37.08 }, + { 193, 37.10, 38.47, 37.10, 38.08 }, + { 194, 38.19, 38.35, 37.15, 37.49 }, + { 195, 37.35, 37.81, 36.60, 36.87 }, + { 196, 36.76, 37.22, 36.50, 36.74 }, + { 199, 36.51, 36.83, 35.95, 36.08 }, + { 200, 36.12, 36.35, 35.07, 35.42 }, + { 201, 35.40, 36.30, 34.87, 35.08 }, + { 202, 35.00, 37.66, 34.75, 37.47 }, + { 203, 37.70, 39.50, 37.55, 39.12 }, + { 206, 39.43, 39.46, 38.78, 39.18 }, + { 207, 39.30, 39.56, 38.69, 38.98 }, + { 208, 39.00, 39.19, 38.00, 38.23 }, + { 209, 38.10, 39.42, 37.13, 38.72 }, + { 210, 38.88, 39.38, 38.22, 38.82 }, + { 213, 39.26, 39.50, 38.72, 39.01 }, + { 214, 39.07, 40.04, 38.74, 39.10 }, + { 215, 38.85, 39.76, 38.71, 39.29 }, + { 216, 39.30, 39.99, 39.13, 39.53 }, + { 217, 39.50, 40.00, 38.06, 38.32 }, + { 220, 38.60, 39.55, 38.37, 39.38 }, + { 221, 39.48, 39.58, 38.18, 38.56 }, + { 222, 38.58, 38.58, 37.01, 37.31 }, + { 223, 37.32, 37.78, 36.42, 36.82 }, + { 224, 37.30, 37.30, 36.04, 36.53 }, + { 227, 37.00, 37.31, 36.30, 37.12 }, + { 228, 37.00, 38.17, 36.88, 38.00 }, + { 229, 38.10, 38.65, 37.60, 38.58 }, + { 230, 38.60, 39.25, 37.50, 37.88 }, + { 231, 37.85, 37.93, 36.92, 37.26 }, + { 234, 37.53, 38.09, 36.99, 37.64 }, + { 235, 37.59, 37.59, 36.35, 36.80 }, + { 236, 36.50, 36.88, 35.10, 35.93 }, + { 237, 36.40, 36.75, 36.00, 36.35 }, + { 238, 36.50, 36.96, 35.79, 36.78 }, + { 241, 36.91, 37.62, 36.80, 37.15 }, + { 242, 36.45, 36.78, 36.00, 36.74 }, + { 243, 36.82, 38.55, 36.34, 38.26 }, + { 244, 38.67, 39.39, 38.12, 39.26 }, + { 245, 39.28, 39.53, 38.83, 39.29 }, + { 248, 39.40, 39.49, 39.03, 39.28 }, + { 249, 39.30, 39.30, 38.56, 38.80 }, + { 250, 38.55, 39.04, 38.06, 38.89 }, + { 251, 39.00, 39.03, 38.50, 38.90 }, + { 252, 38.90, 39.43, 38.15, 38.30 }, + { 255, 38.76, 38.76, 37.88, 38.09 }, + { 256, 38.37, 38.60, 37.90, 38.42 }, + { 257, 38.69, 39.13, 38.11, 38.68 }, + { 258, 38.27, 38.46, 37.31, 37.41 }, + { 259, 37.88, 38.10, 37.31, 37.60 }, + { 262, 37.62, 37.75, 37.12, 37.52 }, + { 263, 37.50, 37.50, 36.82, 36.83 }, + { 264, 36.95, 37.33, 36.12, 36.12 }, + { 265, 36.07, 36.15, 34.55, 35.62 }, + { 266, 35.29, 36.61, 35.07, 36.53 }, + { 269, 36.60, 36.94, 36.12, 36.50 }, + { 270, 36.15, 36.15, 35.19, 35.51 }, + { 271, 35.51, 36.30, 35.13, 35.56 }, + { 272, 35.96, 36.85, 35.42, 36.33 }, + { 273, 36.50, 36.83, 36.04, 36.31 }, + { 276, 36.44, 36.51, 34.64, 34.74 }, + { 277, 34.75, 35.10, 34.45, 34.99 }, + { 278, 35.45, 35.45, 35.01, 35.20 }, + { 279, 35.50, 35.50, 34.72, 35.10 }, + { 280, 34.80, 35.33, 34.66, 35.25 }, + { 283, 35.12, 36.47, 35.12, 36.33 }, + { 284, 36.12, 37.65, 35.83, 37.16 }, + { 285, 37.40, 40.35, 37.18, 39.00 }, + { 286, 39.30, 40.75, 39.03, 40.34 }, + { 287, 40.80, 42.35, 40.30, 41.53 }, + { 290, 42.00, 42.80, 41.35, 42.70 }, + { 291, 42.70, 43.16, 38.75, 38.97 }, + { 292, 38.60, 40.00, 37.70, 39.79 }, + { 293, 39.61, 39.79, 38.00, 38.40 }, + { 294, 38.29, 38.29, 37.25, 37.60 }, + { 297, 37.73, 38.39, 37.49, 38.06 }, + { 298, 38.02, 38.19, 37.60, 37.99 }, + { 299, 37.90, 38.36, 37.31, 37.49 }, + { 300, 37.40, 37.81, 37.05, 37.19 }, + { 301, 37.00, 37.29, 35.92, 36.81 }, + { 304, 37.00, 37.21, 36.69, 36.90 }, + { 305, 37.00, 37.19, 36.83, 37.01 }, + { 306, 37.97, 38.08, 37.38, 37.65 }, + { 307, 38.39, 38.96, 38.01, 38.71 }, + { 308, 38.70, 40.38, 38.60, 40.02 }, + { 311, 40.02, 40.77, 40.01, 40.60 }, + { 312, 40.40, 43.71, 40.40, 43.54 }, + { 313, 43.00, 44.04, 41.28, 43.33 }, + { 314, 43.00, 44.54, 43.00, 43.94 }, + { 315, 43.30, 44.55, 42.54, 44.55 }, + { 318, 44.10, 47.18, 44.05, 46.60 }, + { 319, 46.45, 47.84, 45.97, 46.55 }, + { 320, 46.40, 47.57, 46.22, 47.38 }, + { 321, 47.85, 49.12, 47.62, 48.97 }, + { 322, 49.30, 50.00, 47.94, 50.00 }, + { 325, 50.20, 53.75, 50.10, 52.43 }, + { 326, 51.90, 54.94, 50.21, 53.07 }, + { 327, 53.20, 56.41, 53.20, 56.41 }, + { 328, 56.98, 59.63, 56.76, 59.27 }, + { 329, 59.00, 60.20, 53.65, 57.70 }, + { 332, 58.30, 59.40, 55.65, 55.88 }, + { 333, 55.85, 58.80, 54.50, 57.93 }, + { 334, 59.37, 61.72, 58.86, 61.43 }, + { 335, 62.93, 65.00, 62.20, 64.54 }, + { 336, 65.50, 66.50, 63.82, 64.80 }, + { 339, 66.00, 66.50, 65.05, 65.70 }, + { 340, 67.60, 69.88, 67.29, 68.57 }, + { 341, 67.90, 67.99, 63.65, 64.69 }, + { 342, 65.90, 66.18, 61.14, 62.02 }, + { 343, 61.42, 66.11, 61.29, 66.00 }, + { 346, 65.63, 67.60, 65.20, 67.19 }, + { 347, 66.99, 67.08, 65.07, 65.19 }, + { 348, 64.90, 65.66, 63.35, 65.50 }, + { 349, 65.07, 66.10, 63.21, 63.48 }, + { 350, 64.28, 64.72, 61.50, 62.06 }, + { 353, 62.00, 63.54, 61.88, 62.25 }, + { 354, 63.00, 63.80, 62.53, 63.75 }, + { 355, 63.79, 64.94, 63.00, 63.56 }, + { 356, 63.62, 64.25, 62.34, 62.48 }, + { 360, 62.21, 62.40, 57.51, 59.40 }, + { 361, 60.00, 60.81, 59.41, 60.07 }, + { 362, 60.10, 60.65, 60.00, 60.30 }, + { 363, 60.30, 60.51, 59.40, 59.66 } +}; + +static t_Data2010 daimlerData[] = +{ + { 3, 37.24, 37.60, 36.96, 37.55 }, + { 4, 37.50, 37.56, 36.87, 37.24 }, + { 5, 37.19, 37.33, 36.62, 37.25 }, + { 6, 36.85, 36.95, 36.35, 36.72 }, + { 7, 36.92, 37.15, 36.24, 36.94 }, + { 10, 37.19, 37.67, 37.04, 37.20 }, + { 11, 37.34, 37.40, 36.16, 36.28 }, + { 12, 36.00, 36.38, 35.60, 36.19 }, + { 13, 36.60, 37.19, 36.47, 37.10 }, + { 14, 37.00, 37.26, 36.31, 36.49 }, + { 17, 36.58, 37.25, 36.25, 37.12 }, + { 18, 36.65, 36.87, 35.73, 36.71 }, + { 19, 36.44, 36.52, 35.33, 35.60 }, + { 20, 35.90, 36.17, 34.69, 34.76 }, + { 21, 34.40, 34.66, 33.28, 34.18 }, + { 24, 33.56, 34.08, 33.22, 33.50 }, + { 25, 33.15, 33.95, 32.70, 33.86 }, + { 26, 33.46, 33.58, 32.60, 32.92 }, + { 27, 33.53, 33.80, 32.32, 32.32 }, + { 28, 32.85, 33.97, 32.69, 33.42 }, + { 31, 33.00, 33.74, 32.96, 33.57 }, + { 32, 33.51, 33.88, 32.92, 33.76 }, + { 33, 33.96, 34.95, 33.90, 34.34 }, + { 34, 34.49, 34.69, 33.01, 33.10 }, + { 35, 33.31, 33.31, 32.01, 32.32 }, + { 38, 32.79, 33.54, 32.60, 33.31 }, + { 39, 33.57, 33.85, 32.98, 33.33 }, + { 40, 33.42, 34.10, 33.34, 33.63 }, + { 41, 33.86, 33.90, 32.26, 32.77 }, + { 42, 32.93, 33.21, 31.74, 32.46 }, + { 45, 32.55, 33.11, 31.88, 32.01 }, + { 46, 32.25, 32.69, 31.82, 32.62 }, + { 47, 33.10, 33.47, 32.90, 33.04 }, + { 48, 33.04, 33.35, 29.92, 31.50 }, + { 49, 31.20, 32.32, 30.90, 32.30 }, + { 52, 32.60, 32.62, 31.36, 31.40 }, + { 53, 31.68, 31.90, 31.00, 31.25 }, + { 54, 31.45, 31.49, 30.43, 30.94 }, + { 55, 30.75, 31.23, 30.10, 30.35 }, + { 56, 30.56, 31.09, 30.26, 30.66 }, + { 59, 31.15, 31.55, 30.74, 31.34 }, + { 60, 31.40, 32.06, 31.24, 31.75 }, + { 61, 31.49, 32.25, 31.42, 31.95 }, + { 62, 31.75, 32.29, 31.57, 31.91 }, + { 63, 32.01, 33.10, 32.01, 32.99 }, + { 66, 32.97, 33.20, 32.73, 33.01 }, + { 67, 32.90, 32.99, 32.25, 32.76 }, + { 68, 32.83, 33.26, 32.58, 33.12 }, + { 69, 32.88, 33.56, 32.88, 33.19 }, + { 70, 33.20, 33.60, 33.03, 33.53 }, + { 73, 33.65, 33.83, 33.31, 33.33 }, + { 74, 33.60, 34.26, 33.51, 34.11 }, + { 75, 34.49, 34.60, 33.93, 34.35 }, + { 76, 34.35, 34.78, 34.17, 34.64 }, + { 77, 34.60, 34.89, 34.26, 34.38 }, + { 80, 34.19, 34.56, 33.92, 34.40 }, + { 81, 34.49, 34.74, 34.10, 34.45 }, + { 82, 34.55, 34.65, 33.78, 34.49 }, + { 83, 34.63, 35.19, 34.50, 35.01 }, + { 84, 34.85, 35.34, 34.78, 34.98 }, + { 87, 35.27, 35.53, 34.85, 34.95 }, + { 88, 35.28, 35.35, 34.34, 34.56 }, + { 89, 34.73, 34.99, 34.43, 34.85 }, + { 90, 35.08, 35.49, 35.06, 35.40 }, + { 95, 35.52, 35.84, 35.26, 35.51 }, + { 96, 35.60, 35.85, 35.28, 35.42 }, + { 97, 35.29, 35.36, 34.79, 35.18 }, + { 98, 35.50, 35.69, 35.10, 35.36 }, + { 101, 35.68, 35.74, 35.10, 35.41 }, + { 102, 35.43, 36.05, 34.98, 36.00 }, + { 103, 36.25, 36.74, 36.00, 36.67 }, + { 104, 36.80, 36.90, 36.19, 36.72 }, + { 105, 36.74, 37.38, 36.30, 36.54 }, + { 108, 36.49, 36.76, 36.12, 36.31 }, + { 109, 38.80, 39.24, 38.48, 39.00 }, + { 110, 39.06, 39.30, 38.38, 38.45 }, + { 111, 38.46, 38.96, 37.72, 37.85 }, + { 112, 38.21, 39.11, 37.81, 38.87 }, + { 115, 39.26, 39.52, 38.75, 39.47 }, + { 116, 39.26, 39.90, 37.93, 37.93 }, + { 117, 37.92, 38.18, 36.82, 37.76 }, + { 118, 37.85, 39.00, 37.68, 38.79 }, + { 119, 38.80, 39.30, 38.37, 38.81 }, + { 122, 38.31, 38.83, 38.08, 38.62 }, + { 123, 38.87, 38.88, 37.00, 37.37 }, + { 124, 37.72, 37.72, 36.60, 37.02 }, + { 125, 36.84, 37.74, 36.63, 36.93 }, + { 126, 36.25, 36.96, 35.30, 35.85 }, + { 129, 36.75, 38.01, 36.63, 38.01 }, + { 130, 37.33, 38.74, 37.17, 38.65 }, + { 131, 38.35, 40.17, 38.07, 39.79 }, + { 132, 40.20, 41.54, 40.02, 41.37 }, + { 133, 41.00, 41.38, 40.15, 40.58 }, + { 136, 40.21, 41.54, 40.01, 41.08 }, + { 137, 41.32, 41.92, 40.88, 41.92 }, + { 138, 41.40, 41.85, 39.85, 40.20 }, + { 139, 40.60, 40.64, 37.37, 38.40 }, + { 140, 38.15, 38.95, 37.11, 38.83 }, + { 143, 39.00, 39.13, 37.38, 38.49 }, + { 144, 37.26, 37.56, 36.67, 37.19 }, + { 145, 37.50, 38.87, 37.50, 38.24 }, + { 146, 38.60, 40.18, 38.60, 39.94 }, + { 147, 40.55, 40.67, 39.95, 40.30 }, + { 150, 40.35, 41.08, 40.31, 41.00 }, + { 151, 40.75, 41.43, 40.02, 40.99 }, + { 152, 40.50, 40.99, 39.97, 40.78 }, + { 153, 41.75, 41.99, 41.12, 41.26 }, + { 154, 41.55, 41.66, 39.87, 40.38 }, + { 157, 39.65, 40.61, 39.54, 40.08 }, + { 158, 40.15, 40.41, 39.47, 40.24 }, + { 159, 40.60, 42.05, 40.38, 41.94 }, + { 160, 41.72, 43.74, 41.64, 43.26 }, + { 161, 43.44, 43.88, 42.14, 42.74 }, + { 164, 43.20, 43.36, 42.38, 42.52 }, + { 165, 42.00, 42.79, 41.72, 42.33 }, + { 166, 42.46, 42.60, 40.78, 41.14 }, + { 167, 41.00, 42.44, 40.94, 42.31 }, + { 168, 42.47, 43.39, 42.35, 43.12 }, + { 171, 44.07, 44.79, 44.03, 44.50 }, + { 172, 44.15, 44.49, 43.74, 44.47 }, + { 173, 43.92, 44.65, 43.78, 43.95 }, + { 174, 44.49, 44.69, 43.25, 43.42 }, + { 175, 42.98, 42.98, 41.74, 42.01 }, + { 178, 42.14, 43.36, 41.86, 43.24 }, + { 179, 42.50, 42.72, 41.07, 41.28 }, + { 180, 41.62, 42.49, 41.32, 41.92 }, + { 181, 41.47, 41.82, 40.17, 40.42 }, + { 182, 40.90, 41.49, 40.17, 40.28 }, + { 185, 40.56, 40.85, 39.95, 39.95 }, + { 186, 40.15, 41.95, 40.15, 41.21 }, + { 187, 41.00, 41.99, 40.56, 41.94 }, + { 188, 42.01, 42.33, 41.15, 41.58 }, + { 189, 41.80, 41.97, 41.39, 41.63 }, + { 192, 41.80, 41.95, 41.38, 41.56 }, + { 193, 41.40, 43.81, 41.40, 43.81 }, + { 194, 44.03, 44.48, 43.06, 43.54 }, + { 195, 43.47, 44.35, 42.82, 43.28 }, + { 196, 43.39, 44.70, 42.94, 43.05 }, + { 199, 43.04, 43.22, 42.20, 42.63 }, + { 200, 42.65, 42.88, 41.30, 41.42 }, + { 201, 41.63, 42.05, 40.72, 40.96 }, + { 202, 40.79, 42.51, 40.76, 42.26 }, + { 203, 42.03, 42.83, 41.74, 42.49 }, + { 206, 42.74, 43.17, 42.36, 43.15 }, + { 207, 43.18, 43.38, 40.81, 41.34 }, + { 208, 41.90, 41.97, 41.27, 41.47 }, + { 209, 41.60, 42.11, 41.13, 41.44 }, + { 210, 41.31, 41.67, 40.79, 41.38 }, + { 213, 41.14, 41.58, 40.64, 41.40 }, + { 214, 41.33, 42.10, 41.33, 42.00 }, + { 215, 41.90, 42.10, 41.48, 41.68 }, + { 216, 41.63, 42.44, 41.56, 42.12 }, + { 217, 42.43, 42.75, 40.84, 40.97 }, + { 220, 41.53, 41.99, 41.10, 41.97 }, + { 221, 41.79, 41.79, 40.84, 41.22 }, + { 222, 40.85, 40.97, 39.92, 40.19 }, + { 223, 40.01, 40.40, 38.53, 39.07 }, + { 224, 39.48, 39.90, 38.89, 39.15 }, + { 227, 39.41, 40.14, 39.23, 39.96 }, + { 228, 40.26, 41.10, 40.13, 41.05 }, + { 229, 40.87, 41.10, 40.50, 40.62 }, + { 230, 40.97, 41.22, 39.65, 39.88 }, + { 231, 39.71, 39.95, 39.10, 39.23 }, + { 234, 39.18, 39.53, 38.90, 39.15 }, + { 235, 38.94, 39.05, 38.17, 38.66 }, + { 236, 38.46, 38.82, 37.64, 38.29 }, + { 237, 38.50, 38.63, 37.85, 38.08 }, + { 238, 37.99, 38.40, 37.60, 38.40 }, + { 241, 38.55, 38.83, 37.77, 38.12 }, + { 242, 37.50, 38.36, 37.03, 38.36 }, + { 243, 38.50, 40.49, 38.30, 40.46 }, + { 244, 40.29, 41.42, 40.22, 41.00 }, + { 245, 41.06, 42.24, 40.94, 41.65 }, + { 248, 41.72, 41.86, 41.08, 41.25 }, + { 249, 40.99, 41.36, 40.65, 41.35 }, + { 250, 41.25, 42.06, 41.12, 42.00 }, + { 251, 41.99, 43.15, 41.79, 43.08 }, + { 252, 43.00, 44.02, 42.86, 43.78 }, + { 255, 44.31, 44.65, 43.66, 43.67 }, + { 256, 43.57, 44.20, 43.54, 44.15 }, + { 257, 44.09, 44.49, 43.94, 44.12 }, + { 258, 43.70, 44.22, 43.62, 44.06 }, + { 259, 44.30, 44.74, 43.62, 44.47 }, + { 262, 44.47, 45.50, 44.35, 45.50 }, + { 263, 45.38, 45.90, 45.26, 45.63 }, + { 264, 45.01, 45.19, 44.23, 44.85 }, + { 265, 44.83, 45.13, 43.78, 44.35 }, + { 266, 44.08, 46.17, 44.00, 46.13 }, + { 269, 46.12, 46.69, 45.85, 46.41 }, + { 270, 46.25, 46.64, 45.55, 46.23 }, + { 271, 46.38, 46.85, 46.01, 46.28 }, + { 272, 45.98, 47.59, 45.88, 46.46 }, + { 273, 46.32, 46.86, 45.11, 45.51 }, + { 276, 45.19, 45.19, 43.66, 43.78 }, + { 277, 43.74, 44.88, 43.59, 44.69 }, + { 278, 45.05, 45.19, 44.03, 44.24 }, + { 279, 44.50, 45.49, 44.19, 45.37 }, + { 280, 45.20, 45.61, 44.62, 45.44 }, + { 283, 45.65, 46.30, 45.45, 45.92 }, + { 284, 46.02, 47.73, 45.66, 47.42 }, + { 285, 47.80, 48.21, 47.22, 47.94 }, + { 286, 48.00, 48.04, 47.17, 47.40 }, + { 287, 47.50, 48.13, 47.18, 47.72 }, + { 290, 47.64, 48.10, 47.24, 47.78 }, + { 291, 47.50, 47.80, 46.86, 47.04 }, + { 292, 46.80, 47.90, 46.75, 47.80 }, + { 293, 47.66, 49.12, 47.65, 49.03 }, + { 294, 48.97, 49.51, 48.63, 49.31 }, + { 297, 49.70, 50.05, 49.37, 49.70 }, + { 298, 49.22, 49.56, 48.30, 48.74 }, + { 299, 48.40, 49.10, 47.53, 47.62 }, + { 300, 48.10, 49.05, 46.74, 46.98 }, + { 301, 46.76, 47.73, 46.35, 47.43 }, + { 304, 48.10, 48.37, 47.33, 47.65 }, + { 305, 47.42, 48.62, 47.22, 48.41 }, + { 306, 48.50, 49.15, 48.10, 48.34 }, + { 307, 49.10, 50.14, 48.80, 50.00 }, + { 308, 50.08, 50.45, 48.85, 48.94 }, + { 311, 49.08, 49.10, 48.52, 48.94 }, + { 312, 48.72, 50.28, 48.65, 50.04 }, + { 313, 49.76, 49.91, 48.70, 49.17 }, + { 314, 49.56, 49.75, 48.98, 49.56 }, + { 315, 48.60, 50.23, 47.92, 49.88 }, + { 318, 49.26, 51.40, 49.26, 50.89 }, + { 319, 50.50, 50.93, 49.47, 49.47 }, + { 320, 49.46, 49.69, 48.98, 49.27 }, + { 321, 50.04, 50.96, 49.80, 50.81 }, + { 322, 50.88, 51.00, 50.18, 50.74 }, + { 325, 50.99, 51.59, 50.31, 50.56 }, + { 326, 50.16, 51.26, 49.51, 49.51 }, + { 327, 50.12, 52.04, 49.73, 52.04 }, + { 328, 52.00, 52.63, 51.52, 51.93 }, + { 329, 51.47, 51.93, 50.65, 51.54 }, + { 332, 51.93, 52.06, 49.79, 49.79 }, + { 333, 50.00, 50.93, 49.01, 49.87 }, + { 334, 50.51, 51.96, 50.09, 51.94 }, + { 335, 52.30, 53.64, 51.88, 53.49 }, + { 336, 53.00, 54.78, 52.68, 53.95 }, + { 339, 53.95, 54.18, 53.40, 53.62 }, + { 340, 53.68, 54.37, 52.61, 54.15 }, + { 341, 53.84, 54.05, 52.97, 53.26 }, + { 342, 53.85, 54.14, 52.15, 53.30 }, + { 343, 53.54, 54.93, 53.35, 54.87 }, + { 346, 55.00, 55.00, 54.42, 54.76 }, + { 347, 54.50, 54.87, 53.72, 54.11 }, + { 348, 53.81, 54.20, 53.17, 53.90 }, + { 349, 53.71, 54.58, 53.71, 54.41 }, + { 350, 54.07, 54.48, 53.57, 53.68 }, + { 353, 53.88, 54.50, 53.78, 53.88 }, + { 354, 54.02, 54.94, 53.95, 54.66 }, + { 355, 54.70, 55.05, 54.33, 54.33 }, + { 356, 54.30, 54.52, 54.04, 54.07 }, + { 360, 53.30, 53.45, 51.29, 51.57 }, + { 361, 51.67, 51.84, 51.02, 51.50 }, + { 362, 51.50, 51.62, 51.23, 51.32 }, + { 363, 51.50, 51.70, 50.61, 50.73 } +}; + +QVector QuoteFactory::samples2010( Stock stock ) +{ + const t_Data2010 *data = NULL; + int numSamples = 0; + + switch( stock ) + { + case BMW: + { + data = bmwData; + numSamples = sizeof( bmwData ) / sizeof( t_Data2010 ); + break; + } + case Daimler: + { + data = daimlerData; + numSamples = sizeof( daimlerData ) / sizeof( t_Data2010 ); + break; + } + case Porsche: + { + data = porscheData; + numSamples = sizeof( porscheData ) / sizeof( t_Data2010 ); + break; + } + default: + break; + } + + QVector samples; + samples.reserve( numSamples ); + + QDateTime year2010( QDate( 2010, 1, 1 ), QTime( 0, 0 ), Qt::UTC ); + + for ( int i = 0; i < numSamples; i++ ) + { + const t_Data2010 &ohlc = data[ i ]; + + samples += QwtOHLCSample( + QwtDate::toDouble( year2010.addDays( ohlc.day ) ), + ohlc.open, ohlc.high, ohlc.low, ohlc.close ); + } + + return samples; +} + +QString QuoteFactory::title( Stock stock ) +{ + switch( stock ) + { + case BMW: + return "BMW"; + case Daimler: + return "Daimler"; + case Porsche: + return "Porsche"; + default: + break; + } + + return "Unknown"; +} diff --git a/qwt/examples/stockchart/quotefactory.h b/qwt/examples/stockchart/quotefactory.h new file mode 100644 index 000000000..ddaac2682 --- /dev/null +++ b/qwt/examples/stockchart/quotefactory.h @@ -0,0 +1,22 @@ +#ifndef _QUOTE_FACTORY_H_ +#define _QUOTE_FACTORY_H_ + +#include + +class QuoteFactory +{ +public: + enum Stock + { + BMW, + Daimler, + Porsche, + + NumStocks + }; + + static QVector samples2010( Stock ); + static QString title( Stock ); +}; + +#endif diff --git a/qwt/examples/stockchart/stockchart.pro b/qwt/examples/stockchart/stockchart.pro new file mode 100644 index 000000000..36719d727 --- /dev/null +++ b/qwt/examples/stockchart/stockchart.pro @@ -0,0 +1,25 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = stockchart + +HEADERS = \ + legend.h \ + griditem.h \ + plot.h \ + quotefactory.h + +SOURCES = \ + legend.cpp \ + griditem.cpp \ + quotefactory.cpp \ + plot.cpp \ + main.cpp diff --git a/qwt/examples/stylesheets/blue.css b/qwt/examples/stylesheets/blue.css new file mode 100644 index 000000000..6a7c77367 --- /dev/null +++ b/qwt/examples/stylesheets/blue.css @@ -0,0 +1,66 @@ +QwtPlot +{ + border: 1px solid white; + border-radius: 10px; + padding: 10px; + background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #657383, stop: 0.4 #8395AA, stop: 1 #657383 ); + background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #657383, stop: 1 #8395AA ); + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #8395AA, stop: 1 #657383 ); + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #8395AA, stop: 0.4 #657383 stop: 1 #8395AA ); + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #7C8DA0, stop: 0.4 #657383 stop: 1 #7C8DA0 ); + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #657383, stop: 0.25 #7C8DA0 stop: 0.55 #7C8DA0 stop: 1 #657383 ); + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #8FA3B9, stop: 0.25 #7C8DA0 stop: 0.55 #7C8DA0 stop: 1 #8FA3B9 ); + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #7C8DA0, stop: 0.4 #8FA3B9 stop: 0.55 #8FA3B9 stop: 1 #7C8DA0 ); + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #7C8DA0, stop: 0.4 #8395AA stop: 0.55 #8395AA stop: 1 #7C8DA0 ); +} + +QwtPlotCanvas +{ + border: 1px solid white; + border-radius: 10px; + background-color: #616d7e; +} + +QwtPlotGLCanvas +{ + border: 1px solid white; + background-color: #616d7e; +} + +QwtScaleWidget +{ + color: white; +} + +QwtTextLabel#QwtPlotTitle +{ + color: white; +} + +QwtTextLabel#QwtPlotFooter +{ + color: white; +} + +QwtLegend +{ + border: 1px solid white; + border-radius: 10px; + padding: 2px; + background: #616d7e; +} + +QwtLegendLabel +{ + color: white; +} + diff --git a/qwt/examples/stylesheets/choco.css b/qwt/examples/stylesheets/choco.css new file mode 100644 index 000000000..e819fecc9 --- /dev/null +++ b/qwt/examples/stylesheets/choco.css @@ -0,0 +1,50 @@ +QwtPlot +{ + border: 1px solid white; + border-radius: 10px; + padding: 10px; + background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 Brown, stop: 0.5 Chocolate, stop: 1 Brown ); +} + +QwtPlotCanvas +{ + border: 1px solid White; + border-radius: 10px; + background-color: Tan; +} + +QwtPlotGLCanvas +{ + border: 1px solid White; + background-color: Tan; +} + +QwtScaleWidget +{ + color: white; +} + +QwtTextLabel#QwtPlotTitle +{ + color: white; +} + +QwtTextLabel#QwtPlotFooter +{ + color: white; +} + +QwtLegend +{ + border: 1px solid white; + border-radius: 10px; + padding: 2px; + background: brown; +} + +QwtLegendLabel +{ + color: white; +} + diff --git a/qwt/examples/stylesheets/oily.css b/qwt/examples/stylesheets/oily.css new file mode 100644 index 000000000..5c561e82c --- /dev/null +++ b/qwt/examples/stylesheets/oily.css @@ -0,0 +1,51 @@ +QwtPlot +{ + border: 1px solid white; + border-radius: 10px; + padding: 10px; + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #31312C, stop: 1 #808080 ); +} + +QwtPlotCanvas +{ + border: 1px solid White; + border-radius: 10px; + background-color: #101010; +} + +QwtPlotGLCanvas +{ + border: 1px solid White; + background-color: #101010; +} + +QwtScaleWidget +{ + color: white; +} + +QwtTextLabel#QwtPlotTitle +{ + color: white; +} + +QwtTextLabel#QwtPlotFooter +{ + color: white; +} + +QwtLegend +{ + border: 1px solid white; + border-radius: 10px; + padding: 2px; + background-color: qlineargradient( x1: 0, y1: 0, x2: 1, y2: 1, + stop: 0 #808080, stop: 1 #31312C ); +} + +QwtLegendLabel +{ + color: white; +} + diff --git a/qwt/examples/stylesheets/rosy.css b/qwt/examples/stylesheets/rosy.css new file mode 100644 index 000000000..0bb9f0526 --- /dev/null +++ b/qwt/examples/stylesheets/rosy.css @@ -0,0 +1,50 @@ +QwtPlot +{ + border: 1px solid white; + border-radius: 10px; + padding: 10px; + background-color: qlineargradient( x1: 0, y1: 0, x2: 0, y2: 1, + stop: 0 #7e354d, stop: 0.5 #7f5a58, stop: 1 #7e354d ); +} + +QwtPlotCanvas +{ + border: 1px solid White; + border-radius: 10px; + background-color: #7f5a58; +} + +QwtPlotGLCanvas +{ + border: 1px solid White; + background-color: #7f5a58; +} + +QwtScaleWidget +{ + color: white; +} + +QwtTextLabel#QwtPlotTitle +{ + color: white; +} + +QwtTextLabel#QwtPlotFooter +{ + color: white; +} + +QwtLegend +{ + border: 1px solid white; + border-radius: 10px; + padding: 2px; + background: #7f5a58; +} + +QwtLegendLabel +{ + color: white; +} + diff --git a/qwt/examples/sysinfo/sysinfo.cpp b/qwt/examples/sysinfo/sysinfo.cpp new file mode 100644 index 000000000..640202b8b --- /dev/null +++ b/qwt/examples/sysinfo/sysinfo.cpp @@ -0,0 +1,123 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +class ValueBar: public QWidget +{ +public: + ValueBar( Qt::Orientation orientation, + const QString &text, QWidget *parent, double value = 0.0 ): + QWidget( parent ) + { + d_label = new QLabel( text, this ); + d_label->setFont( QFont( "Helvetica", 10 ) ); + + d_thermo = new QwtThermo( this ); + d_thermo->setOrientation( orientation ); + d_thermo->setScale( 0.0, 100.0 ); + d_thermo->setValue( value ); + d_thermo->setFont( QFont( "Helvetica", 8 ) ); + d_thermo->setPipeWidth( 6 ); + d_thermo->setScaleMaxMajor( 6 ); + d_thermo->setScaleMaxMinor( 5 ); + d_thermo->setFillBrush( Qt::darkMagenta ); + +#if 0 + QwtLinearColorMap *colorMap = + new QwtLinearColorMap( Qt::blue, Qt::red ); + + colorMap->addColorStop( 0.2, Qt::yellow ); + colorMap->addColorStop( 0.3, Qt::cyan ); + colorMap->addColorStop( 0.4, Qt::green ); + colorMap->addColorStop( 0.5, Qt::magenta ); + colorMap->setMode( QwtLinearColorMap::FixedColors ); + d_thermo->setColorMap( colorMap ); +#endif + + QVBoxLayout *layout = new QVBoxLayout( this ); + layout->setMargin( 0 ); + layout->setSpacing( 0 ); + + if ( orientation == Qt::Horizontal ) + { + d_label->setAlignment( Qt::AlignCenter ); + d_thermo->setScalePosition( QwtThermo::LeadingScale ); + layout->addWidget( d_label ); + layout->addWidget( d_thermo ); + } + else + { + d_label->setAlignment( Qt::AlignRight ); + d_thermo->setScalePosition( QwtThermo::TrailingScale ); + layout->addWidget( d_thermo, 10, Qt::AlignHCenter ); + layout->addWidget( d_label, 0 ); + } + } + + void setValue( double value ) + { + d_thermo->setValue( value ); + } + +private: + QLabel *d_label; + QwtThermo *d_thermo; +}; + +class SysInfo : public QFrame +{ +public: + SysInfo( QWidget *parent = NULL ): + QFrame( parent ) + { + QGroupBox *memBox = new QGroupBox( "Memory Usage", this ); + memBox->setFont( QFont( "Helvetica", 10 ) ); + + QVBoxLayout *memLayout = new QVBoxLayout( memBox ); + memLayout->setMargin( 15 ); + memLayout->setSpacing( 5 ); + + Qt::Orientation o = Qt::Horizontal; + memLayout->addWidget( new ValueBar( o, "Used", memBox, 57 ) ); + memLayout->addWidget( new ValueBar( o, "Shared", memBox, 17 ) ); + memLayout->addWidget( new ValueBar( o, "Cache", memBox, 30 ) ); + memLayout->addWidget( new ValueBar( o, "Buffers", memBox, 22 ) ); + memLayout->addWidget( new ValueBar( o, "Swap Used", memBox, 57 ) ); + memLayout->addWidget( new QWidget( memBox ), 10 ); // spacer + + QGroupBox *cpuBox = new QGroupBox( "Cpu Usage", this ); + cpuBox->setFont( QFont( "Helvetica", 10 ) ); + + QHBoxLayout *cpuLayout = new QHBoxLayout( cpuBox ); + cpuLayout->setMargin( 15 ); + cpuLayout->setSpacing( 5 ); + + o = Qt::Vertical; + cpuLayout->addWidget( new ValueBar( o, "User", cpuBox, 57 ) ); + cpuLayout->addWidget( new ValueBar( o, "Total", cpuBox, 73 ) ); + cpuLayout->addWidget( new ValueBar( o, "System", cpuBox, 16 ) ); + cpuLayout->addWidget( new ValueBar( o, "Idle", cpuBox, 27 ) ); + + QHBoxLayout *layout = new QHBoxLayout( this ); + layout->setMargin( 10 ); + layout->addWidget( memBox, 10 ); + layout->addWidget( cpuBox, 0 ); + } +}; + +int main ( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + SysInfo info; + info.resize( info.sizeHint().expandedTo( QSize( 600, 400 ) ) ); + info.show(); + + int rv = a.exec(); + return rv; +} diff --git a/qwt/examples/sysinfo/sysinfo.pro b/qwt/examples/sysinfo/sysinfo.pro new file mode 100644 index 000000000..f93624b12 --- /dev/null +++ b/qwt/examples/sysinfo/sysinfo.pro @@ -0,0 +1,15 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = sysinfo + +SOURCES = \ + sysinfo.cpp diff --git a/qwt/examples/tvplot/main.cpp b/qwt/examples/tvplot/main.cpp new file mode 100644 index 000000000..2ce089613 --- /dev/null +++ b/qwt/examples/tvplot/main.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include +#include "tvplot.h" + +class MainWindow: public QMainWindow +{ +public: + MainWindow( QWidget * = NULL ); + +private: + TVPlot *d_plot; +}; + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + d_plot = new TVPlot( this ); + setCentralWidget( d_plot ); + + QToolBar *toolBar = new QToolBar( this ); + + QComboBox *typeBox = new QComboBox( toolBar ); + typeBox->addItem( "Outline" ); + typeBox->addItem( "Columns" ); + typeBox->addItem( "Lines" ); + typeBox->addItem( "Column Symbol" ); + typeBox->setCurrentIndex( typeBox->count() - 1 ); + typeBox->setSizePolicy( QSizePolicy::Fixed, QSizePolicy::Fixed ); + + QToolButton *btnExport = new QToolButton( toolBar ); + btnExport->setText( "Export" ); + btnExport->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + connect( btnExport, SIGNAL( clicked() ), d_plot, SLOT( exportPlot() ) ); + + toolBar->addWidget( typeBox ); + toolBar->addWidget( btnExport ); + addToolBar( toolBar ); + + d_plot->setMode( typeBox->currentIndex() ); + connect( typeBox, SIGNAL( currentIndexChanged( int ) ), + d_plot, SLOT( setMode( int ) ) ); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow mainWindow; + + mainWindow.resize( 600, 400 ); + mainWindow.show(); + + return a.exec(); +} diff --git a/qwt/examples/tvplot/tvplot.cpp b/qwt/examples/tvplot/tvplot.cpp new file mode 100644 index 000000000..9da1a839b --- /dev/null +++ b/qwt/examples/tvplot/tvplot.cpp @@ -0,0 +1,169 @@ +#include "tvplot.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Histogram: public QwtPlotHistogram +{ +public: + Histogram( const QString &, const QColor & ); + + void setColor( const QColor & ); + void setValues( uint numValues, const double * ); +}; + +Histogram::Histogram( const QString &title, const QColor &symbolColor ): + QwtPlotHistogram( title ) +{ + setStyle( QwtPlotHistogram::Columns ); + + setColor( symbolColor ); +} + +void Histogram::setColor( const QColor &color ) +{ + QColor c = color; + c.setAlpha( 180 ); + setBrush( QBrush( c ) ); +} + +void Histogram::setValues( uint numValues, const double *values ) +{ + QVector samples( numValues ); + for ( uint i = 0; i < numValues; i++ ) + { + QwtInterval interval( double( i ), i + 1.0 ); + interval.setBorderFlags( QwtInterval::ExcludeMaximum ); + + samples[i] = QwtIntervalSample( values[i], interval ); + } + + setData( new QwtIntervalSeriesData( samples ) ); +} + +TVPlot::TVPlot( QWidget *parent ): + QwtPlot( parent ) +{ + setTitle( "Watching TV during a weekend" ); + + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setPalette( Qt::gray ); + canvas->setBorderRadius( 10 ); + setCanvas( canvas ); + + plotLayout()->setAlignCanvasToScales( true ); + + setAxisTitle( QwtAxis::yLeft, "Number of People" ); + setAxisTitle( QwtAxis::xBottom, "Number of Hours" ); + + QwtLegend *legend = new QwtLegend; + legend->setDefaultItemMode( QwtLegendData::Checkable ); + insertLegend( legend, QwtPlot::RightLegend ); + + populate(); + + connect( legend, SIGNAL( checked( const QVariant &, bool, int ) ), + SLOT( showItem( const QVariant &, bool ) ) ); + + replot(); // creating the legend items + + QwtPlotItemList items = itemList( QwtPlotItem::Rtti_PlotHistogram ); + for ( int i = 0; i < items.size(); i++ ) + { + if ( i == 0 ) + { + const QVariant itemInfo = itemToInfo( items[i] ); + + QwtLegendLabel *legendLabel = + qobject_cast( legend->legendWidget( itemInfo ) ); + if ( legendLabel ) + legendLabel->setChecked( true ); + + items[i]->setVisible( true ); + } + else + { + items[i]->setVisible( false ); + } + } + + setAutoReplot( true ); +} + +void TVPlot::populate() +{ + QwtPlotGrid *grid = new QwtPlotGrid; + grid->enableX( false ); + grid->enableY( true ); + grid->enableXMin( false ); + grid->enableYMin( false ); + grid->setMajorPen( Qt::black, 0, Qt::DotLine ); + grid->attach( this ); + + const double juneValues[] = { 7, 19, 24, 32, 10, 5, 3 }; + const double novemberValues[] = { 4, 15, 22, 34, 13, 8, 4 }; + + Histogram *histogramJune = new Histogram( "Summer", Qt::red ); + histogramJune->setValues( + sizeof( juneValues ) / sizeof( double ), juneValues ); + histogramJune->attach( this ); + + Histogram *histogramNovember = new Histogram( "Winter", Qt::blue ); + histogramNovember->setValues( + sizeof( novemberValues ) / sizeof( double ), novemberValues ); + histogramNovember->attach( this ); +} + +void TVPlot::exportPlot() +{ + QwtPlotRenderer renderer; + renderer.exportTo( this, "tvplot.pdf" ); +} + +void TVPlot::setMode( int mode ) +{ + QwtPlotItemList items = itemList( QwtPlotItem::Rtti_PlotHistogram ); + + for ( int i = 0; i < items.size(); i++ ) + { + QwtPlotHistogram *histogram = static_cast( items[i] ); + if ( mode < 3 ) + { + histogram->setStyle( static_cast( mode ) ); + histogram->setSymbol( NULL ); + + QPen pen( Qt::black, 0 ); + if ( mode == QwtPlotHistogram::Lines ) + pen.setBrush( histogram->brush() ); + + histogram->setPen( pen ); + } + else + { + histogram->setStyle( QwtPlotHistogram::Columns ); + + QwtColumnSymbol *symbol = new QwtColumnSymbol( QwtColumnSymbol::Box ); + symbol->setFrameStyle( QwtColumnSymbol::Raised ); + symbol->setLineWidth( 2 ); + symbol->setPalette( QPalette( histogram->brush().color() ) ); + + histogram->setSymbol( symbol ); + } + } +} + +void TVPlot::showItem( const QVariant &itemInfo, bool on ) +{ + QwtPlotItem *plotItem = infoToItem( itemInfo ); + if ( plotItem ) + plotItem->setVisible( on ); +} + diff --git a/qwt/examples/tvplot/tvplot.h b/qwt/examples/tvplot/tvplot.h new file mode 100644 index 000000000..ee0995132 --- /dev/null +++ b/qwt/examples/tvplot/tvplot.h @@ -0,0 +1,23 @@ +#ifndef _TV_PLOT_H_ + +#include + +class TVPlot: public QwtPlot +{ + Q_OBJECT + +public: + TVPlot( QWidget * = NULL ); + +public Q_SLOTS: + void setMode( int ); + void exportPlot(); + +private: + void populate(); + +private Q_SLOTS: + void showItem( const QVariant &, bool on ); +}; + +#endif diff --git a/qwt/examples/tvplot/tvplot.pro b/qwt/examples/tvplot/tvplot.pro new file mode 100644 index 000000000..f91401551 --- /dev/null +++ b/qwt/examples/tvplot/tvplot.pro @@ -0,0 +1,19 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../examples.pri ) + +TARGET = tvplot + +SOURCES = \ + tvplot.cpp \ + main.cpp + +HEADERS = \ + tvplot.h diff --git a/qwt/playground/curvetracker/curvetracker.cpp b/qwt/playground/curvetracker/curvetracker.cpp new file mode 100644 index 000000000..ab540aac0 --- /dev/null +++ b/qwt/playground/curvetracker/curvetracker.cpp @@ -0,0 +1,129 @@ +#include "curvetracker.h" +#include +#include +#include +#include + +struct compareX +{ + inline bool operator()( const double x, const QPointF &pos ) const + { + return ( x < pos.x() ); + } +}; + +CurveTracker::CurveTracker( QWidget *canvas ): + QwtPlotPicker( canvas ) +{ + setTrackerMode( QwtPlotPicker::ActiveOnly ); + setRubberBand( VLineRubberBand ); + + setStateMachine( new QwtPickerDragPointMachine() ); +} + +QRect CurveTracker::trackerRect( const QFont &font ) const +{ + QRect r = QwtPlotPicker::trackerRect( font ); + + // align r to the first curve + + const QwtPlotItemList curves = plot()->itemList( QwtPlotItem::Rtti_PlotCurve ); + if ( curves.size() > 0 ) + { + QPointF pos = invTransform( trackerPosition() ); + + const QLineF line = curveLineAt( + static_cast( curves[0] ), pos.x() ); + if ( !line.isNull() ) + { + const double curveY = line.pointAt( + ( pos.x() - line.p1().x() ) / line.dx() ).y(); + + pos.setY( curveY ); + pos = transform( pos ); + + r.moveBottom( pos.y() ); + } + } + + return r; +} + +QwtText CurveTracker::trackerTextF( const QPointF &pos ) const +{ + QwtText trackerText; + + trackerText.setColor( Qt::black ); + + QColor c( "#333333" ); + trackerText.setBorderPen( QPen( c, 2 ) ); + c.setAlpha( 200 ); + trackerText.setBackgroundBrush( c ); + + QString info; + + const QwtPlotItemList curves = + plot()->itemList( QwtPlotItem::Rtti_PlotCurve ); + + for ( int i = 0; i < curves.size(); i++ ) + { + const QString curveInfo = curveInfoAt( + static_cast( curves[i] ), pos ); + + if ( !curveInfo.isEmpty() ) + { + if ( !info.isEmpty() ) + info += "
"; + + info += curveInfo; + } + } + + trackerText.setText( info ); + return trackerText; +} + +QString CurveTracker::curveInfoAt( + const QwtPlotCurve *curve, const QPointF &pos ) const +{ + const QLineF line = curveLineAt( curve, pos.x() ); + if ( line.isNull() ) + return QString::null; + + const double y = line.pointAt( + ( pos.x() - line.p1().x() ) / line.dx() ).y(); + + QString info( "%2" ); + return info.arg( curve->pen().color().name() ).arg( y ); +} + +QLineF CurveTracker::curveLineAt( + const QwtPlotCurve *curve, double x ) const +{ + QLineF line; + + if ( curve->dataSize() >= 2 ) + { + const QRectF br = curve->boundingRect(); + if ( br.isValid() && x >= br.left() && x <= br.right() ) + { + int index = qwtUpperSampleIndex( + *curve->data(), x, compareX() ); + + if ( index == -1 && + x == curve->sample( curve->dataSize() - 1 ).x() ) + { + // the last sample is excluded from qwtUpperSampleIndex + index = curve->dataSize() - 1; + } + + if ( index > 0 ) + { + line.setP1( curve->sample( index - 1 ) ); + line.setP2( curve->sample( index ) ); + } + } + } + + return line; +} diff --git a/qwt/playground/curvetracker/curvetracker.h b/qwt/playground/curvetracker/curvetracker.h new file mode 100644 index 000000000..3fddd4814 --- /dev/null +++ b/qwt/playground/curvetracker/curvetracker.h @@ -0,0 +1,22 @@ +#ifndef _CURVE_TRACKER_ +#define _CURVE_TRACKER_H_ + +#include + +class QwtPlotCurve; + +class CurveTracker: public QwtPlotPicker +{ +public: + CurveTracker( QWidget * ); + +protected: + virtual QwtText trackerTextF( const QPointF & ) const; + virtual QRect trackerRect( const QFont & ) const; + +private: + QString curveInfoAt( const QwtPlotCurve *, const QPointF & ) const; + QLineF curveLineAt( const QwtPlotCurve *, double x ) const; +}; + +#endif diff --git a/qwt/playground/curvetracker/curvetracker.pro b/qwt/playground/curvetracker/curvetracker.pro new file mode 100644 index 000000000..3f219d73a --- /dev/null +++ b/qwt/playground/curvetracker/curvetracker.pro @@ -0,0 +1,22 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../playground.pri ) + +TARGET = curvetracker + +HEADERS = \ + curvetracker.h \ + plot.h + +SOURCES = \ + curvetracker.cpp \ + plot.cpp \ + main.cpp + diff --git a/qwt/playground/curvetracker/main.cpp b/qwt/playground/curvetracker/main.cpp new file mode 100644 index 000000000..cbf2848ed --- /dev/null +++ b/qwt/playground/curvetracker/main.cpp @@ -0,0 +1,13 @@ +#include +#include "plot.h" + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + Plot plot; + plot.resize( 600, 400 ); + plot.show(); + + return a.exec(); +} diff --git a/qwt/playground/curvetracker/plot.cpp b/qwt/playground/curvetracker/plot.cpp new file mode 100644 index 000000000..4cc8b27ed --- /dev/null +++ b/qwt/playground/curvetracker/plot.cpp @@ -0,0 +1,107 @@ +#include "plot.h" +#include "curvetracker.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +Plot::Plot( QWidget *parent ): + QwtPlot( parent) +{ + setPalette( Qt::black ); + + // we want to have the axis scales like a frame around the + // canvas + plotLayout()->setAlignCanvasToScales( true ); + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + axisWidget( axis )->setMargin( 0 ); + + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setAutoFillBackground( false ); + canvas->setFrameStyle( QFrame::NoFrame ); + setCanvas( canvas ); + + setAxisScale( QwtAxis::yLeft, 0.0, 10.0 ); + + // a title + QwtText title( "Picker Demo" ); + title.setColor( Qt::white ); + title.setRenderFlags( Qt::AlignHCenter | Qt::AlignTop ); + + QFont font; + font.setBold( true ); + title.setFont( font ); + + QwtPlotTextLabel *titleItem = new QwtPlotTextLabel(); + titleItem->setText( title ); + titleItem->attach( this ); + +#if 1 + // section + + //QColor c( "PaleVioletRed" ); + + QwtPlotZoneItem* zone = new QwtPlotZoneItem(); + zone->setPen( Qt::darkGray ); + zone->setBrush( QColor( "#834358" ) ); + zone->setOrientation( Qt::Horizontal ); + zone->setInterval( 3.8, 5.7 ); + zone->attach( this ); + +#else + // grid + + QwtPlotGrid *grid = new QwtPlotGrid(); + grid->setMajorPen( Qt::white, 0, Qt::DotLine ); + grid->setMinorPen( Qt::gray, 0 , Qt::DotLine ); + grid->attach( this ); +#endif + + // curves + + QPolygonF points1; + points1 << QPointF( 0.2, 4.4 ) << QPointF( 1.2, 3.0 ) + << QPointF( 2.7, 4.5 ) << QPointF( 3.5, 6.8 ) + << QPointF( 4.7, 7.9 ) << QPointF( 5.8, 7.1 ); + + insertCurve( "Curve 1", "DarkOrange", points1 ); + + QPolygonF points2; + points2 << QPointF( 0.4, 8.7 ) << QPointF( 1.4, 7.8 ) + << QPointF( 2.3, 5.5 ) << QPointF( 3.3, 4.1 ) + << QPointF( 4.4, 5.2 ) << QPointF( 5.6, 5.7 ); + + insertCurve( "Curve 2", "DodgerBlue", points2 ); + + CurveTracker* tracker = new CurveTracker( this->canvas() ); + + // for the demo we want the tracker to be active without + // having to click on the canvas + tracker->setStateMachine( new QwtPickerTrackerMachine() ); + tracker->setRubberBandPen( QPen( "MediumOrchid" ) ); +} + +void Plot::insertCurve( const QString &title, + const QColor &color, const QPolygonF &points ) +{ + QwtPlotCurve *curve = new QwtPlotCurve(); + curve->setTitle( title ); + curve->setPen( color, 2 ), + curve->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + + QwtSymbol *symbol = new QwtSymbol( QwtSymbol::Ellipse, + QBrush( Qt::white ), QPen( color, 2 ), QSize( 8, 8 ) ); + curve->setSymbol( symbol ); + + curve->setSamples( points ); + + curve->attach( this ); +} + + diff --git a/qwt/playground/curvetracker/plot.h b/qwt/playground/curvetracker/plot.h new file mode 100644 index 000000000..c16d734ed --- /dev/null +++ b/qwt/playground/curvetracker/plot.h @@ -0,0 +1,21 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ + +#include + +class QPolygonF; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget * = NULL ); + +private: + void insertCurve( const QString &title, + const QColor &, const QPolygonF & ); +}; + +#endif + diff --git a/qwt/playground/graphicscale/canvas.cpp b/qwt/playground/graphicscale/canvas.cpp new file mode 100644 index 000000000..5cd44e6e3 --- /dev/null +++ b/qwt/playground/graphicscale/canvas.cpp @@ -0,0 +1,72 @@ +#include "canvas.h" +#include +#include + +Canvas::Canvas( Mode mode, QWidget *parent ): + QWidget( parent ), + d_mode( mode ) +{ + const int m = 10; + setContentsMargins( m, m, m, m ); + + if ( d_mode == Svg ) + d_renderer = new QSvgRenderer( this ); + else + d_graphic = new QwtGraphic(); +} + +Canvas::~Canvas() +{ + if ( d_mode == VectorGraphic ) + delete d_graphic; +} + +void Canvas::setSvg( const QByteArray &data ) +{ + if ( d_mode == VectorGraphic ) + { + d_graphic->reset(); + + QSvgRenderer renderer; + renderer.load( data ); + + QPainter p( d_graphic ); + renderer.render( &p, renderer.viewBoxF() ); + p.end(); + } + else + { + d_renderer->load( data ); + } + + update(); +} + +void Canvas::paintEvent( QPaintEvent * ) +{ + QPainter painter( this ); + + painter.save(); + + painter.setPen( Qt::black ); + painter.setBrush( Qt::white ); + painter.drawRect( contentsRect().adjusted( 0, 0, -1, -1 ) ); + + painter.restore(); + + painter.setPen( Qt::NoPen ); + painter.setBrush( Qt::NoBrush ); + render( &painter, contentsRect() ); +} + +void Canvas::render( QPainter *painter, const QRect &rect ) const +{ + if ( d_mode == Svg ) + { + d_renderer->render( painter, rect ); + } + else + { + d_graphic->render( painter, rect ); + } +} diff --git a/qwt/playground/graphicscale/canvas.h b/qwt/playground/graphicscale/canvas.h new file mode 100644 index 000000000..51f2a2988 --- /dev/null +++ b/qwt/playground/graphicscale/canvas.h @@ -0,0 +1,33 @@ +#include + +class QByteArray; +class QSvgRenderer; +class QwtGraphic; + +class Canvas: public QWidget +{ +public: + enum Mode + { + Svg, + VectorGraphic + }; + + Canvas( Mode, QWidget *parent = NULL ); + virtual ~Canvas(); + + void setSvg( const QByteArray & ); + +protected: + virtual void paintEvent( QPaintEvent * ); + +private: + void render( QPainter *, const QRect & ) const; + + const Mode d_mode; + union + { + QSvgRenderer *d_renderer; + QwtGraphic *d_graphic; + }; +}; diff --git a/qwt/playground/graphicscale/graphicscale.pro b/qwt/playground/graphicscale/graphicscale.pro new file mode 100644 index 000000000..64160c99c --- /dev/null +++ b/qwt/playground/graphicscale/graphicscale.pro @@ -0,0 +1,23 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../playground.pri ) + +TARGET = graphicscale +QT += svg + +HEADERS = \ + canvas.h \ + mainwindow.h + +SOURCES = \ + canvas.cpp \ + mainwindow.cpp \ + main.cpp + diff --git a/qwt/playground/graphicscale/main.cpp b/qwt/playground/graphicscale/main.cpp new file mode 100644 index 000000000..03397ff6c --- /dev/null +++ b/qwt/playground/graphicscale/main.cpp @@ -0,0 +1,13 @@ +#include +#include "mainwindow.h" + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow w; + w.resize( 600, 400 ); + w.show(); + + return a.exec(); +} diff --git a/qwt/playground/graphicscale/mainwindow.cpp b/qwt/playground/graphicscale/mainwindow.cpp new file mode 100644 index 000000000..cbcb398de --- /dev/null +++ b/qwt/playground/graphicscale/mainwindow.cpp @@ -0,0 +1,109 @@ +#include "mainwindow.h" +#include "canvas.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MainWindow::MainWindow() +{ + QWidget *w = new QWidget( this ); + + d_canvas[0] = new Canvas( Canvas::Svg, this ); + d_canvas[0]->setAutoFillBackground( true ); + d_canvas[0]->setPalette( Qt::gray ); + + d_canvas[1] = new Canvas( Canvas::VectorGraphic, this ); + d_canvas[1]->setAutoFillBackground( true ); + d_canvas[1]->setPalette( Qt::gray ); + + QVBoxLayout *vBox1 = new QVBoxLayout(); + vBox1->setContentsMargins( 0, 0, 0, 0 ); + vBox1->setSpacing( 5 ); + vBox1->addWidget( new QLabel( "SVG" ), 0, Qt::AlignCenter ); + vBox1->addWidget( d_canvas[0], 10 ); + + QVBoxLayout *vBox2 = new QVBoxLayout(); + vBox2->setContentsMargins( 0, 0, 0, 0 ); + vBox2->setSpacing( 5 ); + vBox2->addWidget( new QLabel( "Vector Graphic" ), 0, Qt::AlignCenter ); + vBox2->addWidget( d_canvas[1], 10 ); + + QHBoxLayout *layout = new QHBoxLayout( w ); + layout->addLayout( vBox1 ); + layout->addLayout( vBox2 ); + + setCentralWidget( w ); + + QToolBar *toolBar = new QToolBar( this ); + + QToolButton *btnLoad = new QToolButton( toolBar ); + + btnLoad->setText( "Load SVG" ); + btnLoad->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolBar->addWidget( btnLoad ); + + addToolBar( toolBar ); + + connect( btnLoad, SIGNAL( clicked() ), this, SLOT( loadSVG() ) ); + +#if 0 + QPainterPath path; + path.addRect( QRectF( 1.0, 1.0, 2.0, 2.0 ) ); + loadPath( path ); +#endif +}; + +MainWindow::~MainWindow() +{ +} + +void MainWindow::loadSVG() +{ + QString dir = "/home1/uwe/qwt/qwt/tests/svg"; + const QString fileName = QFileDialog::getOpenFileName( NULL, + "Load a Scaleable Vector Graphic (SVG) Document", + dir, "SVG Files (*.svg)" ); + + if ( !fileName.isEmpty() ) + loadSVG( fileName ); + + statusBar()->showMessage( fileName ); +} + +void MainWindow::loadSVG( const QString &fileName ) +{ + QFile file( fileName ); + if ( !file.open(QIODevice::ReadOnly | QIODevice::Text) ) + return; + + const QByteArray document = file.readAll(); + file.close(); + + d_canvas[0]->setSvg( document ); + d_canvas[1]->setSvg( document ); +} + + +void MainWindow::loadPath( const QPainterPath &path ) +{ + QBuffer buf; + + QSvgGenerator generator; + generator.setOutputDevice( &buf ); + + QPainter painter( &generator ); + painter.setRenderHint( QPainter::Antialiasing, false ); + painter.setPen( QPen( Qt::blue, 0 ) ); + painter.setBrush( Qt::darkCyan ); + painter.drawPath( path ); + painter.end(); + + d_canvas[0]->setSvg( buf.data() ); + d_canvas[1]->setSvg( buf.data() ); +} diff --git a/qwt/playground/graphicscale/mainwindow.h b/qwt/playground/graphicscale/mainwindow.h new file mode 100644 index 000000000..6e07ced14 --- /dev/null +++ b/qwt/playground/graphicscale/mainwindow.h @@ -0,0 +1,22 @@ +#include + +class Canvas; +class QPainterPath; + +class MainWindow: public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(); + virtual ~MainWindow(); + +private Q_SLOTS: + void loadSVG(); + +private: + void loadSVG( const QString & ); + void loadPath( const QPainterPath & ); + + Canvas *d_canvas[2]; +}; diff --git a/qwt/playground/playground.pri b/qwt/playground/playground.pri new file mode 100644 index 000000000..18c2c74de --- /dev/null +++ b/qwt/playground/playground.pri @@ -0,0 +1,78 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################### + +QWT_ROOT = $${PWD}/.. +include( $${QWT_ROOT}/qwtconfig.pri ) +include( $${QWT_ROOT}/qwtbuild.pri ) +include( $${QWT_ROOT}/qwtfunctions.pri ) + +TEMPLATE = app + +INCLUDEPATH += $${QWT_ROOT}/src +DEPENDPATH += $${QWT_ROOT}/src + +!debug_and_release { + + DESTDIR = $${QWT_ROOT}/playground/bin +} +else { + CONFIG(debug, debug|release) { + + DESTDIR = $${QWT_ROOT}/playground/bin_debug + } + else { + + DESTDIR = $${QWT_ROOT}/playground/bin + } +} + + +QMAKE_RPATHDIR *= $${QWT_ROOT}/lib + +contains(QWT_CONFIG, QwtFramework) { + + LIBS += -F$${QWT_ROOT}/lib +} +else { + + LIBS += -L$${QWT_ROOT}/lib +} + +qwtAddLibrary(qwt) + +greaterThan(QT_MAJOR_VERSION, 4) { + + QT += printsupport + QT += concurrent +} + +contains(QWT_CONFIG, QwtOpenGL ) { + + QT += opengl +} +else { + + DEFINES += QWT_NO_OPENGL +} + +contains(QWT_CONFIG, QwtSvg) { + + QT += svg +} +else { + + DEFINES += QWT_NO_SVG +} + + +win32 { + contains(QWT_CONFIG, QwtDll) { + DEFINES += QT_DLL QWT_DLL + } +} diff --git a/qwt/playground/playground.pro b/qwt/playground/playground.pro new file mode 100644 index 000000000..ec6b5d21d --- /dev/null +++ b/qwt/playground/playground.pro @@ -0,0 +1,32 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../qwtconfig.pri ) + +TEMPLATE = subdirs + +contains(QWT_CONFIG, QwtPlot) { + + SUBDIRS += \ + plotmatrix \ + timescale \ + scaleengine \ + graphicscale \ + rescaler \ + shapes \ + curvetracker \ + symbols + + contains(QWT_CONFIG, QwtSvg) { + + SUBDIRS += \ + svgmap + } + +} diff --git a/qwt/playground/plotmatrix/main.cpp b/qwt/playground/plotmatrix/main.cpp new file mode 100644 index 000000000..5754076cf --- /dev/null +++ b/qwt/playground/plotmatrix/main.cpp @@ -0,0 +1,66 @@ +#include "plotmatrix.h" +#include +#include +#include +#include +#include + +class MainWindow: public PlotMatrix +{ +public: + MainWindow(); +}; + +MainWindow::MainWindow(): + PlotMatrix( 3, 4 ) +{ + enableAxis( QwtAxis::yLeft ); + enableAxis( QwtAxis::yRight ); + enableAxis( QwtAxis::xBottom ); + + for ( int row = 0; row < numRows(); row++ ) + { + const double v = qPow( 10.0, row ); + setAxisScale( QwtAxis::yLeft, row, -v, v ); + setAxisScale( QwtAxis::yRight, row, -v, v ); + } + + for ( int col = 0; col < numColumns(); col++ ) + { + const double v = qPow( 10.0, col ); + setAxisScale( QwtAxis::xBottom, col, -v, v ); + setAxisScale( QwtAxis::xTop, col, -v, v ); + } + + for ( int row = 0; row < numRows(); row++ ) + { + for ( int col = 0; col < numColumns(); col++ ) + { + QwtPlot *plot = plotAt( row, col ); + plot->setCanvasBackground( QColor( Qt::darkGray ) ); + + QwtPlotGrid *grid = new QwtPlotGrid(); + grid->enableXMin( true ); + grid->setMajorPen( Qt::white, 0, Qt::DotLine ); + grid->setMinorPen( Qt::gray, 0 , Qt::DotLine ); + grid->attach( plot ); + } + } + + plotAt( 1, 0 )->axisWidget( QwtAxis::yLeft )->setLabelRotation( 45 ); + plotAt( 1, numColumns() - 1 )->axisWidget( QwtAxis::yRight )->setLabelRotation( -45 ); + + updateLayout(); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow mainWindow; + + mainWindow.resize( 800, 600 ); + mainWindow.show(); + + return a.exec(); +} diff --git a/qwt/playground/plotmatrix/plotmatrix.cpp b/qwt/playground/plotmatrix/plotmatrix.cpp new file mode 100644 index 000000000..81b2070a2 --- /dev/null +++ b/qwt/playground/plotmatrix/plotmatrix.cpp @@ -0,0 +1,428 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +// vim: expandtab + +#include +#include +#include +#include +#include +#include +#include "plotmatrix.h" + +static void enablePlotAxis( QwtPlot *plot, int axis, bool on ) +{ + // when false we still enable the axis to have an effect + // of the minimal extent active. Instead we hide all visible + // parts and margins/spacings. + + plot->setAxisVisible( axis, true ); + + QwtScaleDraw *sd = plot->axisScaleDraw( axis ); + sd->enableComponent( QwtScaleDraw::Backbone, on ); + sd->enableComponent( QwtScaleDraw::Ticks, on ); + sd->enableComponent( QwtScaleDraw::Labels, on ); + + QwtScaleWidget* sw = plot->axisWidget( axis ); + sw->setMargin( on ? 4 : 0 ); + sw->setSpacing( on ? 20 : 0 ); +} + +class Plot: public QwtPlot +{ +public: + Plot( QWidget *parent = NULL ): + QwtPlot( parent ) + { + QwtPlotCanvas *canvas = new QwtPlotCanvas(); + canvas->setLineWidth( 1 ); + canvas->setFrameStyle( QFrame::Box | QFrame::Plain ); + + setCanvas( canvas ); + } + + virtual QSize sizeHint() const + { + return minimumSizeHint(); + } +}; + +class PlotMatrix::PrivateData +{ +public: + PrivateData(): + inScaleSync( false ) + { + isAxisEnabled[QwtAxis::xBottom] = true; + isAxisEnabled[QwtAxis::xTop] = false; + isAxisEnabled[QwtAxis::yLeft] = true; + isAxisEnabled[QwtAxis::yRight] = false; + } + + bool isAxisEnabled[QwtAxis::PosCount]; + QVector plotWidgets; + mutable bool inScaleSync; +}; + +PlotMatrix::PlotMatrix( int numRows, int numColumns, QWidget *parent ): + QFrame( parent ) +{ + d_data = new PrivateData(); + d_data->plotWidgets.resize( numRows * numColumns ); + + QGridLayout *layout = new QGridLayout( this ); + layout->setSpacing( 5 ); + + for ( int row = 0; row < numRows; row++ ) + { + for ( int col = 0; col < numColumns; col++ ) + { + QwtPlot *plot = new Plot( this ); + layout->addWidget( plot, row, col ); + + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + { + connect( plot->axisWidget( axis ), + SIGNAL( scaleDivChanged() ), SLOT( scaleDivChanged() ) ); + } + d_data->plotWidgets[row * numColumns + col] = plot; + } + } + + updateLayout(); +} + +PlotMatrix::~PlotMatrix() +{ + delete d_data; +} + +int PlotMatrix::numRows() const +{ + const QGridLayout *l = qobject_cast( layout() ); + if ( l ) + return l->rowCount(); + + return 0; +} + +int PlotMatrix::numColumns() const +{ + const QGridLayout *l = qobject_cast( layout() ); + if ( l ) + return l->columnCount(); + return 0; +} + +QwtPlot* PlotMatrix::plotAt( int row, int column ) +{ + const int index = row * numColumns() + column; + if ( index < d_data->plotWidgets.size() ) + return d_data->plotWidgets[index]; + + return NULL; +} + +const QwtPlot* PlotMatrix::plotAt( int row, int column ) const +{ + const int index = row * numColumns() + column; + if ( index < d_data->plotWidgets.size() ) + return d_data->plotWidgets[index]; + + return NULL; +} + +void PlotMatrix::enableAxis( int axis, bool tf ) +{ + if ( QwtAxis::isValid( axis ) ) + { + if ( tf != d_data->isAxisEnabled[axis] ) + { + d_data->isAxisEnabled[axis] = tf; + updateLayout(); + } + } +} + +bool PlotMatrix::axisEnabled( int axis ) const +{ + if ( QwtAxis::isValid( axis ) ) + return d_data->isAxisEnabled[axis]; + + return false; +} + +void PlotMatrix::setAxisScale( int axis, int rowOrColumn, + double min, double max, double step ) +{ + int row = 0; + int col = 0; + + if ( axis == QwtAxis::xBottom || axis == QwtAxis::xTop ) + col = rowOrColumn; + else + row = rowOrColumn; + + QwtPlot *plt = plotAt( row, col ); + if ( plt ) + { + plt->setAxisScale( axis, min, max, step ); + plt->updateAxes(); + } +} + +void PlotMatrix::scaleDivChanged() +{ + if ( d_data->inScaleSync ) + return; + + d_data->inScaleSync = true; + + QwtPlot *plt = NULL; + int axisId = -1; + int rowOrColumn = -1; + + // find the changed axis + for ( int row = 0; row < numRows(); row++ ) + { + for ( int col = 0; col < numColumns(); col++ ) + { + QwtPlot *p = plotAt( row, col ); + if ( p ) + { + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + { + if ( p->axisWidget( axis ) == sender() ) + { + plt = p; + axisId = axis; + if ( axisId == QwtAxis::xBottom || axisId == QwtAxis::xTop ) + rowOrColumn = col; + else + rowOrColumn = row; + + } + } + } + } + } + + if ( plt ) + { + const QwtScaleDiv scaleDiv = plt->axisScaleDiv( axisId ); + + // synchronize the axes + if ( axisId == QwtAxis::xBottom || axisId == QwtAxis::xTop ) + { + for ( int row = 0; row < numRows(); row++ ) + { + QwtPlot *p = plotAt( row, rowOrColumn ); + if ( p != plt ) + { + p->setAxisScaleDiv( axisId, scaleDiv ); + } + } + } + else + { + for ( int col = 0; col < numColumns(); col++ ) + { + QwtPlot *p = plotAt( rowOrColumn, col ); + if ( p != plt ) + { + p->setAxisScaleDiv( axisId, scaleDiv ); + } + } + } + + updateLayout(); + } + + d_data->inScaleSync = false; +} + +void PlotMatrix::updateLayout() +{ + for ( int row = 0; row < numRows(); row++ ) + { + for ( int col = 0; col < numColumns(); col++ ) + { + QwtPlot *p = plotAt( row, col ); + if ( p ) + { + bool showAxis[QwtAxis::PosCount]; + showAxis[QwtAxis::xBottom] = + axisEnabled( QwtAxis::xBottom ) && row == numRows() - 1; + showAxis[QwtAxis::xTop] = + axisEnabled( QwtAxis::xTop ) && row == 0; + showAxis[QwtAxis::yLeft] = + axisEnabled( QwtAxis::yLeft ) && col == 0; + showAxis[QwtAxis::yRight] = + axisEnabled( QwtAxis::yRight ) && col == numColumns() - 1; + + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + { + enablePlotAxis( p, axis, showAxis[axis] ); + } + } + } + } + + for ( int row = 0; row < numRows(); row++ ) + { + alignAxes( row, QwtAxis::xTop ); + alignAxes( row, QwtAxis::xBottom ); + + alignScaleBorder( row, QwtAxis::yLeft ); + alignScaleBorder( row, QwtAxis::yRight ); + } + + for ( int col = 0; col < numColumns(); col++ ) + { + alignAxes( col, QwtAxis::yLeft ); + alignAxes( col, QwtAxis::yRight ); + + alignScaleBorder( col, QwtAxis::xBottom ); + alignScaleBorder( col, QwtAxis::xTop ); + } + + for ( int row = 0; row < numRows(); row++ ) + { + for ( int col = 0; col < numColumns(); col++ ) + { + QwtPlot *p = plotAt( row, col ); + if ( p ) + p->replot(); + } + } +} + +void PlotMatrix::alignAxes( int rowOrColumn, int axis ) +{ + if ( QwtAxis::isYAxis( axis ) ) + { + double maxExtent = 0; + + for ( int row = 0; row < numRows(); row++ ) + { + QwtPlot *p = plotAt( row, rowOrColumn ); + if ( p ) + { + QwtScaleWidget *scaleWidget = p->axisWidget( axis ); + + QwtScaleDraw *sd = scaleWidget->scaleDraw(); + sd->setMinimumExtent( 0.0 ); + + const double extent = sd->extent( scaleWidget->font() ); + if ( extent > maxExtent ) + maxExtent = extent; + } + } + + for ( int row = 0; row < numRows(); row++ ) + { + QwtPlot *p = plotAt( row, rowOrColumn ); + if ( p ) + { + QwtScaleWidget *scaleWidget = p->axisWidget( axis ); + scaleWidget->scaleDraw()->setMinimumExtent( maxExtent ); + } + } + } + else + { + double maxExtent = 0; + + for ( int col = 0; col < numColumns(); col++ ) + { + QwtPlot *p = plotAt( rowOrColumn, col ); + if ( p ) + { + QwtScaleWidget *scaleWidget = p->axisWidget( axis ); + + QwtScaleDraw *sd = scaleWidget->scaleDraw(); + sd->setMinimumExtent( 0.0 ); + + const double extent = sd->extent( scaleWidget->font() ); + if ( extent > maxExtent ) + maxExtent = extent; + } + } + + for ( int col = 0; col < numColumns(); col++ ) + { + QwtPlot *p = plotAt( rowOrColumn, col ); + if ( p ) + { + QwtScaleWidget *scaleWidget = p->axisWidget( axis ); + scaleWidget->scaleDraw()->setMinimumExtent( maxExtent ); + } + } + } +} + +void PlotMatrix::alignScaleBorder( int rowOrColumn, int axis ) +{ + int startDist = 0; + int endDist = 0; + + if ( axis == QwtAxis::yLeft ) + { + QwtPlot *p = plotAt( rowOrColumn, 0 ); + if ( p ) + p->axisWidget( axis )->getBorderDistHint( startDist, endDist ); + + for ( int col = 1; col < numColumns(); col++ ) + { + QwtPlot *p = plotAt( rowOrColumn, col ); + if ( p ) + p->axisWidget( axis )->setMinBorderDist( startDist, endDist ); + } + } + else if ( axis == QwtAxis::yRight ) + { + QwtPlot *p = plotAt( rowOrColumn, numColumns() - 1 ); + if ( p ) + p->axisWidget( axis )->getBorderDistHint( startDist, endDist ); + + for ( int col = 0; col < numColumns() - 1; col++ ) + { + QwtPlot *p = plotAt( rowOrColumn, col ); + if ( p ) + p->axisWidget( axis )->setMinBorderDist( startDist, endDist ); + } + } + if ( axis == QwtAxis::xTop ) + { + QwtPlot *p = plotAt( rowOrColumn, 0 ); + if ( p ) + p->axisWidget( axis )->getBorderDistHint( startDist, endDist ); + + for ( int row = 1; row < numRows(); row++ ) + { + QwtPlot *p = plotAt( row, rowOrColumn ); + if ( p ) + p->axisWidget( axis )->setMinBorderDist( startDist, endDist ); + } + } + else if ( axis == QwtAxis::xBottom ) + { + QwtPlot *p = plotAt( numRows() - 1, rowOrColumn ); + if ( p ) + p->axisWidget( axis )->getBorderDistHint( startDist, endDist ); + + for ( int row = 0; row < numRows() - 1; row++ ) + { + QwtPlot *p = plotAt( row, rowOrColumn ); + if ( p ) + p->axisWidget( axis )->setMinBorderDist( startDist, endDist ); + } + } +} diff --git a/qwt/playground/plotmatrix/plotmatrix.h b/qwt/playground/plotmatrix/plotmatrix.h new file mode 100644 index 000000000..81a779b50 --- /dev/null +++ b/qwt/playground/plotmatrix/plotmatrix.h @@ -0,0 +1,41 @@ +#ifndef _PLOT_MATRIX_H_ +#define _PLOT_MATRIX_H_ + +#include +#include + +class PlotMatrix: public QFrame +{ + Q_OBJECT + +public: + PlotMatrix( int rows, int columns, QWidget * parent = NULL ); + virtual ~PlotMatrix(); + + int numRows() const; + int numColumns() const; + + QwtPlot* plotAt( int row, int column ); + const QwtPlot* plotAt( int row, int column ) const; + + void enableAxis( int axisId, bool tf = true ); + bool axisEnabled( int axisId ) const; + + void setAxisScale( int axisId, int rowOrColumn, + double min, double max, double step = 0 ); + +protected: + void updateLayout(); + +private Q_SLOTS: + void scaleDivChanged(); + +private: + void alignAxes( int rowOrColumn, int axis ); + void alignScaleBorder( int rowOrColumn, int axis ); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/playground/plotmatrix/plotmatrix.pro b/qwt/playground/plotmatrix/plotmatrix.pro new file mode 100644 index 000000000..027907e24 --- /dev/null +++ b/qwt/playground/plotmatrix/plotmatrix.pro @@ -0,0 +1,19 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../playground.pri ) + +TARGET = plotmatrix + +HEADERS = \ + plotmatrix.h + +SOURCES = \ + plotmatrix.cpp \ + main.cpp diff --git a/qwt/playground/rescaler/main.cpp b/qwt/playground/rescaler/main.cpp new file mode 100644 index 000000000..928aa1208 --- /dev/null +++ b/qwt/playground/rescaler/main.cpp @@ -0,0 +1,15 @@ +#include +#include "mainwindow.h" + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow mainWindow; + + mainWindow.resize( 800, 600 ); + mainWindow.show(); + + return a.exec(); +} + diff --git a/qwt/playground/rescaler/mainwindow.cpp b/qwt/playground/rescaler/mainwindow.cpp new file mode 100644 index 000000000..8e578b6b3 --- /dev/null +++ b/qwt/playground/rescaler/mainwindow.cpp @@ -0,0 +1,146 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "plot.h" +#include "mainwindow.h" + +MainWindow::MainWindow() +{ + QFrame *w = new QFrame( this ); + + QWidget *panel = createPanel( w ); + panel->setFixedWidth( 2 * panel->sizeHint().width() ); + d_plot = createPlot( w ); + + QHBoxLayout *layout = new QHBoxLayout( w ); + layout->setMargin( 0 ); + layout->addWidget( panel, 0 ); + layout->addWidget( d_plot, 10 ); + + setCentralWidget( w ); + + setRescaleMode( 0 ); + + ( void )statusBar(); +} + +QWidget *MainWindow::createPanel( QWidget *parent ) +{ + QGroupBox *panel = new QGroupBox( "Navigation Panel", parent ); + + QComboBox *rescaleBox = new QComboBox( panel ); + rescaleBox->setEditable( false ); + rescaleBox->insertItem( KeepScales, "None" ); + rescaleBox->insertItem( Fixed, "Fixed" ); + rescaleBox->insertItem( Expanding, "Expanding" ); + rescaleBox->insertItem( Fitting, "Fitting" ); + + connect( rescaleBox, SIGNAL( activated( int ) ), SLOT( setRescaleMode( int ) ) ); + + d_rescaleInfo = new QLabel( panel ); + d_rescaleInfo->setSizePolicy( + QSizePolicy::Expanding, QSizePolicy::Expanding ); + d_rescaleInfo->setWordWrap( true ); + + QVBoxLayout *layout = new QVBoxLayout( panel ); + layout->addWidget( rescaleBox ); + layout->addWidget( d_rescaleInfo ); + layout->addStretch( 10 ); + + return panel; +} + +Plot *MainWindow::createPlot( QWidget *parent ) +{ + Plot *plot = new Plot( parent, QwtInterval( 0.0, 1000.0 ) ); + plot->replot(); + + d_rescaler = new QwtPlotRescaler( plot->canvas() ); + d_rescaler->setReferenceAxis( QwtAxis::xBottom ); + d_rescaler->setAspectRatio( QwtAxis::yLeft, 1.0 ); + d_rescaler->setAspectRatio( QwtAxis::yRight, 0.0 ); + d_rescaler->setAspectRatio( QwtAxis::xTop, 0.0 ); + + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + d_rescaler->setIntervalHint( axis, QwtInterval( 0.0, 1000.0 ) ); + + connect( plot, SIGNAL( resized( double, double ) ), + SLOT( showRatio( double, double ) ) ); + return plot; +} + +void MainWindow::setRescaleMode( int mode ) +{ + bool doEnable = true; + QString info; + QRectF rectOfInterest; + QwtPlotRescaler::ExpandingDirection direction = QwtPlotRescaler::ExpandUp; + + switch( mode ) + { + case KeepScales: + { + doEnable = false; + info = "All scales remain unchanged, when the plot is resized"; + break; + } + case Fixed: + { + d_rescaler->setRescalePolicy( QwtPlotRescaler::Fixed ); + info = "The scale of the bottom axis remains unchanged, " + "when the plot is resized. All other scales are changed, " + "so that a pixel on screen means the same distance for" + "all scales."; + break; + } + case Expanding: + { + d_rescaler->setRescalePolicy( QwtPlotRescaler::Expanding ); + info = "The scales of all axis are shrinked/expanded, when " + "resizing the plot, keeping the distance that is represented " + "by one pixel."; + d_rescaleInfo->setText( "Expanding" ); + break; + } + case Fitting: + { + d_rescaler->setRescalePolicy( QwtPlotRescaler::Fitting ); + const QwtInterval xIntv = + d_rescaler->intervalHint( QwtAxis::xBottom ); + const QwtInterval yIntv = + d_rescaler->intervalHint( QwtAxis::yLeft ); + + rectOfInterest = QRectF( xIntv.minValue(), yIntv.minValue(), + xIntv.width(), yIntv.width() ); + direction = QwtPlotRescaler::ExpandBoth; + + info = "Fitting"; + break; + } + } + + d_plot->setRectOfInterest( rectOfInterest ); + + d_rescaleInfo->setText( info ); + d_rescaler->setEnabled( doEnable ); + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + d_rescaler->setExpandingDirection( direction ); + + if ( doEnable ) + d_rescaler->rescale(); + else + d_plot->replot(); +} + +void MainWindow::showRatio( double xRatio, double yRatio ) +{ + const QString msg = QString( "%1, %2" ).arg( xRatio ).arg( yRatio ); + statusBar()->showMessage( msg ); +} + diff --git a/qwt/playground/rescaler/mainwindow.h b/qwt/playground/rescaler/mainwindow.h new file mode 100644 index 000000000..6f52afba2 --- /dev/null +++ b/qwt/playground/rescaler/mainwindow.h @@ -0,0 +1,39 @@ +#ifndef _MAINWINDOW_H_ +#define _MAINWINDOW_H_ 1 + +#include + +class QwtPlotRescaler; +class QLabel; +class Plot; + +class MainWindow: public QMainWindow +{ + Q_OBJECT + +public: + enum RescaleMode + { + KeepScales, + Fixed, + Expanding, + Fitting + }; + + MainWindow(); + +private Q_SLOTS: + void setRescaleMode( int ); + void showRatio( double, double ); + +private: + QWidget *createPanel( QWidget * ); + Plot *createPlot( QWidget * ); + + QwtPlotRescaler *d_rescaler; + QLabel *d_rescaleInfo; + + Plot *d_plot; +}; + +#endif diff --git a/qwt/playground/rescaler/plot.cpp b/qwt/playground/rescaler/plot.cpp new file mode 100644 index 000000000..75a0dfa78 --- /dev/null +++ b/qwt/playground/rescaler/plot.cpp @@ -0,0 +1,181 @@ +#include "plot.h" +#include +#include +#include +#include +#include +#include +#include +#include + +class TextItem: public QwtPlotItem +{ +public: + void setText( const QString &text ) + { + m_text = text; + } + + virtual void draw( QPainter *painter, + const QwtScaleMap &, const QwtScaleMap &, + const QRectF &canvasRect ) const + { + const int margin = 5; + const QRectF textRect = + canvasRect.adjusted( margin, margin, -margin, -margin ); + + painter->setPen( Qt::white ); + painter->drawText( textRect, + Qt::AlignBottom | Qt::AlignRight, m_text ); + } + +private: + QString m_text; +}; + + +// RectItem shows how to implement a simple plot item, +// what wouldn't be necessary as QwtPlotShapeItem +// would do the same +class RectItem: public QwtPlotItem +{ +public: + enum Type + { + Rect, + Ellipse + }; + + RectItem( Type type ): + d_type( type ) + { + } + + void setPen( const QPen &pen ) + { + if ( pen != d_pen ) + { + d_pen = pen; + itemChanged(); + } + } + + void setBrush( const QBrush &brush ) + { + if ( brush != d_brush ) + { + d_brush = brush; + itemChanged(); + } + } + void setRect( const QRectF &rect ) + { + if ( d_rect != rect ) + { + d_rect = rect; + itemChanged(); + } + } + + virtual QRectF boundingRect() const + { + return d_rect; + } + + virtual void draw( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF & ) const + { + if ( d_rect.isValid() ) + { + const QRectF rect = QwtScaleMap::transform( + xMap, yMap, d_rect ); + + painter->setPen( d_pen ); + painter->setBrush( d_brush ); + + if ( d_type == Ellipse ) + QwtPainter::drawEllipse( painter, rect ); + else + QwtPainter::drawRect( painter, rect ); + } + } + +private: + QPen d_pen; + QBrush d_brush; + QRectF d_rect; + Type d_type; +}; + +Plot::Plot( QWidget *parent, const QwtInterval &interval ): + QwtPlot( parent ) +{ + for ( int axis = 0; axis < QwtAxis::PosCount; axis ++ ) + setAxisScale( axis, interval.minValue(), interval.maxValue() ); + + setCanvasBackground( QColor( Qt::darkBlue ) ); + plotLayout()->setAlignCanvasToScales( true ); + + // grid + QwtPlotGrid *grid = new QwtPlotGrid; + //grid->enableXMin(true); + grid->setMajorPen( Qt::white, 0, Qt::DotLine ); + grid->setMinorPen( Qt::gray, 0 , Qt::DotLine ); + grid->attach( this ); + + const int numEllipses = 10; + + for ( int i = 0; i < numEllipses; i++ ) + { + const double x = interval.minValue() + + qrand() % qRound( interval.width() ); + const double y = interval.minValue() + + qrand() % qRound( interval.width() ); + const double r = interval.minValue() + + qrand() % qRound( interval.width() / 6 ); + + const QRectF area( x - r, y - r , 2 * r, 2 * r ); + + RectItem *item = new RectItem( RectItem::Ellipse ); + item->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + item->setRect( area ); + item->setPen( QPen( Qt::yellow ) ); + item->attach( this ); + } + + TextItem *textItem = new TextItem(); + textItem->setText( "Navigation Example" ); + textItem->attach( this ); + + d_rectOfInterest = new RectItem( RectItem::Rect ); + d_rectOfInterest->setPen( Qt::NoPen ); + QColor c = Qt::gray; + c.setAlpha( 100 ); + d_rectOfInterest->setBrush( QBrush( c ) ); + d_rectOfInterest->attach( this ); +} + +void Plot::updateLayout() +{ + QwtPlot::updateLayout(); + + const QwtScaleMap xMap = canvasMap( QwtAxis::xBottom ); + const QwtScaleMap yMap = canvasMap( QwtAxis::yLeft ); + + const QRect cr = canvas()->contentsRect(); + const double x1 = xMap.invTransform( cr.left() ); + const double x2 = xMap.invTransform( cr.right() ); + const double y1 = yMap.invTransform( cr.bottom() ); + const double y2 = yMap.invTransform( cr.top() ); + + const double xRatio = ( x2 - x1 ) / cr.width(); + const double yRatio = ( y2 - y1 ) / cr.height(); + + Q_EMIT resized( xRatio, yRatio ); +} + +void Plot::setRectOfInterest( const QRectF &rect ) +{ + d_rectOfInterest->setRect( rect ); +} diff --git a/qwt/playground/rescaler/plot.h b/qwt/playground/rescaler/plot.h new file mode 100644 index 000000000..0d9656ce4 --- /dev/null +++ b/qwt/playground/rescaler/plot.h @@ -0,0 +1,28 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ 1 + +#include + +class RectItem; +class QwtInterval; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget *parent, const QwtInterval & ); + virtual void updateLayout(); + + void setRectOfInterest( const QRectF & ); + +Q_SIGNALS: + void resized( double xRatio, double yRatio ); + +private: + RectItem *d_rectOfInterest; +}; + +#endif + + diff --git a/qwt/playground/rescaler/rescaler.pro b/qwt/playground/rescaler/rescaler.pro new file mode 100644 index 000000000..c29d9e98a --- /dev/null +++ b/qwt/playground/rescaler/rescaler.pro @@ -0,0 +1,22 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../playground.pri ) + +TARGET = rescaler + +HEADERS = \ + mainwindow.h \ + plot.h + +SOURCES = \ + mainwindow.cpp \ + plot.cpp \ + main.cpp + diff --git a/qwt/playground/scaleengine/mainwindow.cpp b/qwt/playground/scaleengine/mainwindow.cpp new file mode 100644 index 000000000..894d99149 --- /dev/null +++ b/qwt/playground/scaleengine/mainwindow.cpp @@ -0,0 +1,120 @@ +#include "mainwindow.h" +#include "plot.h" +#include "transformplot.h" +#include +#include +#include + +class TransformPos: public QwtTransform +{ +public: + TransformPos( double pos, double range, double factor ): + d_position( pos ), + d_range( range ), + d_factor( factor ), + d_powRange( qPow( d_range, d_factor ) ) + { + } + + virtual double transform( double value ) const + { + const double v1 = d_position - d_range; + const double v2 = v1 + 2 * d_range; + + if ( value <= v1 ) + { + return value; + } + + if ( value >= v2 ) + { + return v1 + 2 * d_powRange + value - v2; + } + + double v; + + if ( value <= d_position ) + { + v = v1 + qPow( value - v1, d_factor ); + } + else + { + v = v1 + 2 * d_powRange - qPow( v2 - value, d_factor ); + } + + return v; + } + + virtual double invTransform( double value ) const + { + const double v1 = d_position - d_range; + const double v2 = v1 + 2 * d_powRange; + + if ( value < v1 ) + { + return value; + } + + if ( value >= v2 ) + { + return value + 2 * ( d_range - d_powRange ); + } + + double v; + if ( value <= v1 + d_powRange ) + { + v = v1 + qPow( value - v1, 1.0 / d_factor ); + } + else + { + v = d_position + d_range - qPow( v2 - value, 1.0 / d_factor ); + } + + return v; + } + + virtual QwtTransform *copy() const + { + return new TransformPos( d_position, d_range, d_factor ); + } + +private: + const double d_position; + const double d_range; + const double d_factor; + const double d_powRange; +}; + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + QSplitter *splitter = new QSplitter( Qt::Vertical ); + + d_transformPlot = new TransformPlot( splitter ); + + d_transformPlot->insertTransformation( "Square Root", + QColor( "DarkSlateGray" ), new QwtPowerTransform( 0.5 ) ); + d_transformPlot->insertTransformation( "Linear", + QColor( "Peru" ), new QwtNullTransform() ); + d_transformPlot->insertTransformation( "Cubic", + QColor( "OliveDrab" ), new QwtPowerTransform( 3.0 ) ); + d_transformPlot->insertTransformation( "Power 10", + QColor( "Indigo" ), new QwtPowerTransform( 10.0 ) ); + d_transformPlot->insertTransformation( "Log", + QColor( "SteelBlue" ), new QwtLogTransform() ); + d_transformPlot->insertTransformation( "At 400", + QColor( "Crimson" ), new TransformPos( 400.0, 100.0, 1.4 ) ); + + const QwtPlotItemList curves = + d_transformPlot->itemList( QwtPlotItem::Rtti_PlotCurve ); + if ( !curves.isEmpty() ) + d_transformPlot->setLegendChecked( curves[ 2 ] ); + + d_plot = new Plot( splitter ); + d_plot->setTransformation( new QwtPowerTransform( 3.0 ) ); + + setCentralWidget( splitter ); + + connect( d_transformPlot, SIGNAL( selected( QwtTransform * ) ), + d_plot, SLOT( setTransformation( QwtTransform * ) ) ); +} diff --git a/qwt/playground/scaleengine/mainwindow.h b/qwt/playground/scaleengine/mainwindow.h new file mode 100644 index 000000000..6eea4844d --- /dev/null +++ b/qwt/playground/scaleengine/mainwindow.h @@ -0,0 +1,16 @@ +#include + +class Plot; +class TransformPlot; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow( QWidget *parent = 0 ); + +private: + Plot *d_plot; + TransformPlot *d_transformPlot; +}; diff --git a/qwt/playground/scaleengine/plot.cpp b/qwt/playground/scaleengine/plot.cpp new file mode 100644 index 000000000..35e2230c1 --- /dev/null +++ b/qwt/playground/scaleengine/plot.cpp @@ -0,0 +1,70 @@ +#include "plot.h" +#include +#include +#include +#include +#include + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + setCanvasBackground( Qt::white ); + + setAxisScale(QwtAxis::yLeft, 0.0, 10.0 ); + setTransformation( new QwtNullTransform() ); + + populate(); + + QwtPlotPicker *picker = new QwtPlotPicker( canvas() ); + picker->setTrackerMode( QwtPlotPicker::AlwaysOn ); +} + +void Plot::populate() +{ + QwtPlotGrid *grid = new QwtPlotGrid(); + grid->setMinorPen( Qt::black, 0, Qt::DashLine ); + grid->enableXMin( true ); + grid->attach( this ); + + QwtPlotCurve *curve = new QwtPlotCurve(); + curve->setTitle("Some Points"); + curve->setPen( Qt::blue, 4 ), + curve->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + + QwtSymbol *symbol = new QwtSymbol( QwtSymbol::Ellipse, + QBrush( Qt::yellow ), QPen( Qt::red, 2 ), QSize( 8, 8 ) ); + curve->setSymbol( symbol ); + + QPolygonF points; + points << QPointF( 10.0, 4.4 ) + << QPointF( 100.0, 3.0 ) << QPointF( 200.0, 4.5 ) + << QPointF( 300.0, 6.8 ) << QPointF( 400.0, 7.9 ) + << QPointF( 500.0, 7.1 ) << QPointF( 600.0, 7.9 ) + << QPointF( 700.0, 7.1 ) << QPointF( 800.0, 5.4 ) + << QPointF( 900.0, 2.8 ) << QPointF( 1000.0, 3.6 ); + curve->setSamples( points ); + curve->attach( this ); +} + +void Plot::setTransformation( QwtTransform *transform ) +{ + QwtLinearScaleEngine *scaleEngine = new QwtLinearScaleEngine(); + scaleEngine->setTransformation( transform ); + + setAxisScaleEngine( QwtAxis::xBottom, scaleEngine ); + + // we have to reassign the axis settinge, because they are + // invalidated, when the scale engine has changed + + QwtScaleDiv scaleDiv = + axisScaleEngine( QwtAxis::xBottom )->divideScale( 10.0, 1000.0, 8, 10 ); + + QList ticks; + ticks += 10.0; + ticks += scaleDiv.ticks( QwtScaleDiv::MajorTick ); + scaleDiv.setTicks( QwtScaleDiv::MajorTick, ticks ); + + setAxisScaleDiv( QwtAxis::xBottom, scaleDiv ); + + replot(); +} diff --git a/qwt/playground/scaleengine/plot.h b/qwt/playground/scaleengine/plot.h new file mode 100644 index 000000000..6548505b0 --- /dev/null +++ b/qwt/playground/scaleengine/plot.h @@ -0,0 +1,23 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ + +#include + +class QwtTransform; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget *parent = NULL ); + +public Q_SLOTS: + void setTransformation( QwtTransform * ); + +private: + void populate(); +}; + +#endif + diff --git a/qwt/playground/scaleengine/scaleengine.cpp b/qwt/playground/scaleengine/scaleengine.cpp new file mode 100644 index 000000000..6e02da2cb --- /dev/null +++ b/qwt/playground/scaleengine/scaleengine.cpp @@ -0,0 +1,13 @@ +#include +#include "mainwindow.h" + +int main(int argc, char **argv) +{ + QApplication a(argc, argv); + + MainWindow window; + window.resize(800,600); + window.show(); + + return a.exec(); +} diff --git a/qwt/playground/scaleengine/scaleengine.pro b/qwt/playground/scaleengine/scaleengine.pro new file mode 100644 index 000000000..b1704d002 --- /dev/null +++ b/qwt/playground/scaleengine/scaleengine.pro @@ -0,0 +1,24 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../playground.pri ) + +TARGET = scaleengine + +HEADERS = \ + transformplot.h \ + plot.h \ + mainwindow.h + +SOURCES = \ + transformplot.cpp \ + plot.cpp \ + mainwindow.cpp \ + scaleengine.cpp + diff --git a/qwt/playground/scaleengine/transformplot.cpp b/qwt/playground/scaleengine/transformplot.cpp new file mode 100644 index 000000000..60bcf0f69 --- /dev/null +++ b/qwt/playground/scaleengine/transformplot.cpp @@ -0,0 +1,108 @@ +#include "transformplot.h" +#include +#include +#include +#include +#include +#include + +class TransformData: public QwtSyntheticPointData +{ +public: + TransformData( QwtTransform *transform ): + QwtSyntheticPointData( 200 ), + d_transform( transform ) + { + } + + virtual ~TransformData() + { + delete d_transform; + } + + const QwtTransform *transform() const + { + return d_transform; + } + + virtual double y( double x ) const + { + const double min = 10.0; + const double max = 1000.0; + + const double value = min + x * ( max - min ); + + const double s1 = d_transform->transform( min ); + const double s2 = d_transform->transform( max ); + const double s = d_transform->transform( value ); + + return ( s - s1 ) / ( s2 - s1 ); + } + +private: + QwtTransform *d_transform; +}; + +TransformPlot::TransformPlot( QWidget *parent ): + QwtPlot( parent ) +{ + setTitle( "Transformations" ); + setCanvasBackground( Qt::white ); + + setAxisScale( QwtAxis::xBottom, 0.0, 1.0 ); + setAxisScale( QwtAxis::yLeft, 0.0, 1.0 ); + + QwtLegend *legend = new QwtLegend(); + legend->setDefaultItemMode( QwtLegendData::Checkable ); + insertLegend( legend, QwtPlot::RightLegend ); + + connect( legend, SIGNAL( checked( const QVariant &, bool, int ) ), + this, SLOT( legendChecked( const QVariant &, bool ) ) ); +} + +void TransformPlot::insertTransformation( + const QString &title, const QColor &color, QwtTransform *transform ) +{ + QwtPlotCurve *curve = new QwtPlotCurve( title ); + curve->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + curve->setPen( color, 2 ); + curve->setData( new TransformData( transform ) ); + curve->attach( this ); +} + +void TransformPlot::legendChecked( const QVariant &itemInfo, bool on ) +{ + QwtPlotItem *plotItem = infoToItem( itemInfo ); + + setLegendChecked( plotItem ); + + if ( on && plotItem->rtti() == QwtPlotItem::Rtti_PlotCurve ) + { + QwtPlotCurve *curve = static_cast( plotItem ); + TransformData *data = static_cast( curve->data() ); + + Q_EMIT selected( data->transform()->copy() ); + } +} + +void TransformPlot::setLegendChecked( QwtPlotItem *plotItem ) +{ + const QwtPlotItemList items = itemList(); + for ( int i = 0; i < items.size(); i++ ) + { + QwtPlotItem *item = items[ i ]; + if ( item->testItemAttribute( QwtPlotItem::Legend ) ) + { + QwtLegend *lgd = qobject_cast( legend() ); + + QwtLegendLabel *label = qobject_cast< QwtLegendLabel *>( + lgd->legendWidget( itemToInfo( item ) ) ); + if ( label ) + { + lgd->blockSignals( true ); + label->setChecked( item == plotItem ); + lgd->blockSignals( false ); + } + } + } +} diff --git a/qwt/playground/scaleengine/transformplot.h b/qwt/playground/scaleengine/transformplot.h new file mode 100644 index 000000000..8dd6c7121 --- /dev/null +++ b/qwt/playground/scaleengine/transformplot.h @@ -0,0 +1,27 @@ +#ifndef _TRANSFORM_PLOT_H_ +#define _TRANSFORM_PLOT_H_ + +#include + +class TransformPlot: public QwtPlot +{ + Q_OBJECT + +public: + TransformPlot( QWidget *parent = NULL ); + void insertTransformation( const QString &, + const QColor &, QwtTransform * ); + + void setLegendChecked( QwtPlotItem * ); + +Q_SIGNALS: + void selected( QwtTransform * ); + +private Q_SLOTS: + void legendChecked( const QVariant &, bool on ); + +private: +}; + +#endif + diff --git a/qwt/playground/shapes/shapes.cpp b/qwt/playground/shapes/shapes.cpp new file mode 100644 index 000000000..a998f2667 --- /dev/null +++ b/qwt/playground/shapes/shapes.cpp @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Plot : public QwtPlot +{ +public: + Plot( QWidget *parent = NULL ); + +private: + void populate(); +}; + + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + setPalette( QColor( 60, 60, 60 ) ); + canvas()->setPalette( Qt::white ); + + // panning with the left mouse button + ( void ) new QwtPlotPanner( canvas() ); + + // zoom in/out with the wheel + ( void ) new QwtPlotMagnifier( canvas() ); + + setTitle( "Shapes" ); + insertLegend( new QwtLegend(), QwtPlot::RightLegend ); + + // axes + setAxisTitle( QwtAxis::xBottom, "x -->" ); + setAxisTitle( QwtAxis::yLeft, "y -->" ); +#if 0 + setAxisScaleEngine( QwtAxis::xBottom, new QwtLog10ScaleEngine ); + setAxisScaleEngine( QwtAxis::yLeft, new QwtLog10ScaleEngine ); +#endif + + populate(); +} + +void Plot::populate() +{ + const double d = 900.0; + const QRectF rect( 1.0, 1.0, d, d ); + + QPainterPath path; + //path.setFillRule( Qt::WindingFill ); + path.addEllipse( rect ); + + const QRectF rect2 = rect.adjusted( 0.2 * d, 0.3 * d, -0.22 * d, 1.5 * d ); + path.addEllipse( rect2 ); + +#if 0 + QFont font; + font.setPointSizeF( 200 ); + QPainterPath textPath; + textPath.addText( rect.center(), font, "Seppi" ); + + QTransform transform; + transform.translate( rect.center().x() - 600, rect.center().y() + 50 ); + transform.rotate( 180.0, Qt::XAxis ); + + textPath = transform.map( textPath ); + + path.addPath( textPath ); +#endif + + QwtPlotShapeItem *item = new QwtPlotShapeItem( "Shape" ); + item->setItemAttribute( QwtPlotItem::Legend, true ); + item->setRenderHint( QwtPlotItem::RenderAntialiased, true ); +#if 1 + item->setRenderTolerance( 1.0 ); +#endif + item->setShape( path ); + item->setPen( Qt::yellow ); + + QColor c = Qt::darkRed; + c.setAlpha( 100 ); + item->setBrush( c ); + + item->attach( this ); +} + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + Plot plot; + plot.resize( 600, 400 ); + plot.show(); + + return a.exec(); +} diff --git a/qwt/playground/shapes/shapes.pro b/qwt/playground/shapes/shapes.pro new file mode 100644 index 000000000..2aaf01645 --- /dev/null +++ b/qwt/playground/shapes/shapes.pro @@ -0,0 +1,16 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../playground.pri ) + +TARGET = shapes + +SOURCES = \ + shapes.cpp + diff --git a/qwt/playground/svgmap/main.cpp b/qwt/playground/svgmap/main.cpp new file mode 100644 index 000000000..74c334e74 --- /dev/null +++ b/qwt/playground/svgmap/main.cpp @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include "plot.h" + +class MainWindow: public QMainWindow +{ +public: + MainWindow( const QString &fileName ) + { + Plot *plot = new Plot( this ); + if ( !fileName.isEmpty() ) + plot->loadSVG( fileName ); + + setCentralWidget( plot ); + +#ifndef QT_NO_FILEDIALOG + + QToolBar *toolBar = new QToolBar( this ); + + QToolButton *btnLoad = new QToolButton( toolBar ); + + btnLoad->setText( "Load SVG" ); + btnLoad->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); + toolBar->addWidget( btnLoad ); + + addToolBar( toolBar ); + + connect( btnLoad, SIGNAL( clicked() ), plot, SLOT( loadSVG() ) ); +#endif + } +}; + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + QString fileName; + if ( argc > 1 ) + fileName = argv[1]; + + MainWindow w( fileName ); + w.resize( 600, 400 ); + w.show(); + + int rv = a.exec(); + return rv; +} diff --git a/qwt/playground/svgmap/plot.cpp b/qwt/playground/svgmap/plot.cpp new file mode 100644 index 000000000..1d1884485 --- /dev/null +++ b/qwt/playground/svgmap/plot.cpp @@ -0,0 +1,79 @@ +#include +#include +#include +#include +#include +#include +#include +#include "plot.h" + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ), + d_mapItem( NULL ), + d_mapRect( 0.0, 0.0, 100.0, 100.0 ) // something +{ +#if 1 + /* + d_mapRect is only a reference for zooming, but + the ranges are nothing useful for the user. So we + hide the axes. + */ + plotLayout()->setCanvasMargin( 0 ); + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + setAxisVisible( axis, false ); +#else + QwtPlotGrid *grid = new QwtPlotGrid(); + grid->attach( this ); +#endif + + /* + Navigation: + + Left Mouse Button: Panning + Mouse Wheel: Zooming In/Out + Right Mouse Button: Reset to initial + */ + + ( void )new QwtPlotPanner( canvas() ); + ( void )new QwtPlotMagnifier( canvas() ); + + canvas()->setFocusPolicy( Qt::WheelFocus ); + rescale(); +} + +#ifndef QT_NO_FILEDIALOG + +void Plot::loadSVG() +{ + QString dir; + const QString fileName = QFileDialog::getOpenFileName( NULL, + "Load a Scaleable Vector Graphic (SVG) Map", + dir, "SVG Files (*.svg)" ); + + if ( !fileName.isEmpty() ) + loadSVG( fileName ); +} + +#endif + +void Plot::loadSVG( const QString &fileName ) +{ + if ( d_mapItem == NULL ) + { + d_mapItem = new QwtPlotSvgItem(); + d_mapItem->attach( this ); + } + + d_mapItem->loadFile( d_mapRect, fileName ); + rescale(); + + replot(); +} + +void Plot::rescale() +{ + setAxisScale( QwtAxis::xBottom, + d_mapRect.left(), d_mapRect.right() ); + setAxisScale( QwtAxis::yLeft, + d_mapRect.top(), d_mapRect.bottom() ); +} diff --git a/qwt/playground/svgmap/plot.h b/qwt/playground/svgmap/plot.h new file mode 100644 index 000000000..b05c32b3e --- /dev/null +++ b/qwt/playground/svgmap/plot.h @@ -0,0 +1,26 @@ +#include +#include + +class QwtPlotSvgItem; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget * = NULL ); + +public Q_SLOTS: + +#ifndef QT_NO_FILEDIALOG + void loadSVG(); +#endif + + void loadSVG( const QString & ); + +private: + void rescale(); + + QwtPlotSvgItem *d_mapItem; + const QRectF d_mapRect; +}; diff --git a/qwt/playground/svgmap/svgmap.pro b/qwt/playground/svgmap/svgmap.pro new file mode 100644 index 000000000..9f3ad44d1 --- /dev/null +++ b/qwt/playground/svgmap/svgmap.pro @@ -0,0 +1,26 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../playground.pri ) + +!contains(QWT_CONFIG, QwtSvg) { + + message(Are you trying to build Qwt with the Qt Creator as Shadow Build ?) + error(Qwt is configured without SVG support !) +} + +TARGET = svgmap +QT += svg + +HEADERS = \ + plot.h + +SOURCES = \ + plot.cpp \ + main.cpp diff --git a/qwt/playground/symbols/symbols.cpp b/qwt/playground/symbols/symbols.cpp new file mode 100644 index 000000000..9870afcc5 --- /dev/null +++ b/qwt/playground/symbols/symbols.cpp @@ -0,0 +1,212 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class MySymbol: public QwtSymbol +{ +public: + MySymbol( QwtSymbol::Style style, const QBrush &brush ) + { + QPen pen( Qt::black, 0 ); + pen.setJoinStyle( Qt::MiterJoin ); + pen.setCosmetic( true ); + + QPainterPath path = createArrow( QSize( 16, 24 ) ); + + const QSizeF pathSize = path.boundingRect().size(); + + setSize( 0.8 * pathSize.toSize() ); + + setPinPoint( QPointF( 0.0, 0.0 ) ); + + switch( style ) + { + case QwtSymbol::Pixmap: + { + const QSize sz = size(); + + const double ratio = qMin( sz.width() / pathSize.width(), + sz.height() / pathSize.height() ); + + QTransform transform; + transform.scale( ratio, ratio ); + + path = transform.map( path ); + + if ( isPinPointEnabled() ) + { + QPointF pos = transform.map( pinPoint() ); + setPinPoint( pos ); + } + + const QRectF br = path.boundingRect(); + + int m = 2 + qCeil( pen.widthF() ); + + QPixmap pm( sz + QSize( 2 * m, 2 * m ) ); + pm.fill( Qt::transparent ); + + QPainter painter( &pm ); + painter.setRenderHint( QPainter::Antialiasing, true ); + + painter.setPen( pen ); + painter.setBrush( brush ); + + painter.translate( m, m ); + painter.translate( -br.left(), br.top() ); + painter.drawPath( path ); + + setPixmap( pm ); + setSize( pm.size() ); + if ( isPinPointEnabled() ) + setPinPoint( pinPoint() + QPointF( m, m ) ); + + break; + } + case QwtSymbol::Graphic: + { + QwtGraphic graphic; + graphic.setRenderHint( QwtGraphic::RenderPensUnscaled ); + + QPainter painter( &graphic ); + painter.setRenderHint( QPainter::Antialiasing, true ); + painter.setPen( pen ); + painter.setBrush( brush ); + + painter.drawPath( path ); + painter.end(); + + setGraphic( graphic ); + break; + } + case QwtSymbol::SvgDocument: + { + QBuffer buf; + + QSvgGenerator generator; + generator.setOutputDevice( &buf ); + + QPainter painter( &generator ); + painter.setRenderHint( QPainter::Antialiasing, true ); + painter.setPen( pen ); + painter.setBrush( brush ); + + painter.drawPath( path ); + painter.end(); + + setSvgDocument( buf.data() ); + break; + } + case QwtSymbol::Path: + default: + { + setPen( pen ); + setBrush( brush ); + setPath( path ); + } + } + + } + +private: + QPainterPath createArrow( const QSizeF &size ) const + { + const double w = size.width(); + const double h = size.height(); + const double y0 = 0.6 * h; + + QPainterPath path; + path.moveTo( 0, h ); + path.lineTo( 0, y0 ); + path.lineTo( -0.5 * w, y0 ); + path.lineTo( 0, 0 ); + path.lineTo( 0.5 * w, y0 ); + path.lineTo( 0, y0 ); + + QTransform transform; + transform.rotate( -30.0 ); + path = transform.map( path ); + + return path; + } +}; + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + QwtPlot plot; + plot.setTitle( "Plot Demo" ); + plot.setCanvasBackground( Qt::white ); + + plot.setAxisScale( QwtAxis::xBottom, -1.0, 6.0 ); + + QwtLegend *legend = new QwtLegend(); + legend->setDefaultItemMode( QwtLegendData::Checkable ); + plot.insertLegend( legend ); + + for ( int i = 0; i < 4; i++ ) + { + QwtPlotCurve *curve = new QwtPlotCurve(); + curve->setRenderHint( QwtPlotItem::RenderAntialiased, true ); + curve->setPen( Qt::blue ); + + QBrush brush; + QwtSymbol::Style style = QwtSymbol::NoSymbol; + QString title; + if ( i == 0 ) + { + brush = Qt::magenta; + style = QwtSymbol::Path; + title = "Path"; + } + else if ( i == 2 ) + { + brush = Qt::red; + style = QwtSymbol::Graphic; + title = "Graphic"; + } + else if ( i == 1 ) + { + brush = Qt::yellow; + style = QwtSymbol::SvgDocument; + title = "Svg"; + } + else if ( i == 3 ) + { + brush = Qt::cyan; + style = QwtSymbol::Pixmap; + title = "Pixmap"; + } + + MySymbol *symbol = new MySymbol( style, brush ); + + curve->setSymbol( symbol ); + curve->setTitle( title ); + curve->setLegendAttribute( QwtPlotCurve::LegendShowSymbol, true ); + curve->setLegendIconSize( QSize( 15, 18 ) ); + + QPolygonF points; + points << QPointF( 0.0, 4.4 ) << QPointF( 1.0, 3.0 ) + << QPointF( 2.0, 4.5 ) << QPointF( 3.0, 6.8 ) + << QPointF( 4.0, 7.9 ) << QPointF( 5.0, 7.1 ); + + points.translate( 0.0, i * 2.0 ); + + curve->setSamples( points ); + curve->attach( &plot ); + } + + plot.resize( 600, 400 ); + plot.show(); + + return a.exec(); +} diff --git a/qwt/playground/symbols/symbols.pro b/qwt/playground/symbols/symbols.pro new file mode 100644 index 000000000..35f6d5450 --- /dev/null +++ b/qwt/playground/symbols/symbols.pro @@ -0,0 +1,16 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../playground.pri ) + +TARGET = symbols + +SOURCES = \ + symbols.cpp + diff --git a/qwt/playground/timescale/main.cpp b/qwt/playground/timescale/main.cpp new file mode 100644 index 000000000..14321f756 --- /dev/null +++ b/qwt/playground/timescale/main.cpp @@ -0,0 +1,13 @@ +#include +#include "mainwindow.h" + +int main( int argc, char **argv ) +{ + QApplication a( argc, argv ); + + MainWindow window; + window.resize( 800, 600 ); + window.show(); + + return a.exec(); +} diff --git a/qwt/playground/timescale/mainwindow.cpp b/qwt/playground/timescale/mainwindow.cpp new file mode 100644 index 000000000..809710afb --- /dev/null +++ b/qwt/playground/timescale/mainwindow.cpp @@ -0,0 +1,58 @@ +#include "plot.h" +#include "panel.h" +#include "mainwindow.h" +#include +#include +#include + +MainWindow::MainWindow( QWidget *parent ): + QMainWindow( parent ) +{ + Settings settings; +#if 1 + settings.startDateTime = QDateTime( QDate( 2012, 10, 27 ), QTime( 18, 5, 0, 0 ) ); + settings.endDateTime = QDateTime( QDate( 2012, 10, 28 ), QTime( 12, 12, 0, 0 ) ); +#else + settings.startDateTime = QDateTime( QDate( 2011, 5, 3 ), QTime( 0, 6, 0, 0 ) ); + settings.endDateTime = QDateTime( QDate( 2012, 3, 10 ), QTime( 0, 5, 0, 0 ) ); +#endif + settings.maxMajorSteps = 10; + settings.maxMinorSteps = 8; + settings.maxWeeks = -1; + + d_plot = new Plot(); + d_panel = new Panel(); + d_panel->setSettings( settings ); + + QWidget *box = new QWidget( this ); + + QHBoxLayout *layout = new QHBoxLayout( box ); + layout->addWidget( d_plot, 10 ); + layout->addWidget( d_panel ); + + setCentralWidget( box ); + + updatePlot(); + + connect( d_panel, SIGNAL( edited() ), SLOT( updatePlot() ) ); + connect( d_plot->axisWidget( QwtAxis::yLeft ), + SIGNAL( scaleDivChanged() ), SLOT( updatePanel() ) ); +} + +void MainWindow::updatePlot() +{ + d_plot->blockSignals( true ); + d_plot->applySettings( d_panel->settings() ); + d_plot->blockSignals( false ); +} + +void MainWindow::updatePanel() +{ + const QwtScaleDiv scaleDiv = d_plot->axisScaleDiv( QwtAxis::yLeft ); + + Settings settings = d_panel->settings(); + settings.startDateTime = QwtDate::toDateTime( scaleDiv.lowerBound(), Qt::LocalTime ); + settings.endDateTime = QwtDate::toDateTime( scaleDiv.upperBound(), Qt::LocalTime ); + + d_panel->setSettings( settings ); +} diff --git a/qwt/playground/timescale/mainwindow.h b/qwt/playground/timescale/mainwindow.h new file mode 100644 index 000000000..d8cef9671 --- /dev/null +++ b/qwt/playground/timescale/mainwindow.h @@ -0,0 +1,20 @@ +#include + +class Plot; +class Panel; + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow( QWidget *parent = 0 ); + +private Q_SLOTS: + void updatePlot(); + void updatePanel(); + +private: + Plot *d_plot; + Panel *d_panel; +}; diff --git a/qwt/playground/timescale/panel.cpp b/qwt/playground/timescale/panel.cpp new file mode 100644 index 000000000..65c97e367 --- /dev/null +++ b/qwt/playground/timescale/panel.cpp @@ -0,0 +1,95 @@ +#include "panel.h" +#include "settings.h" +#include +#include +#include +#include +#include + +Panel::Panel( QWidget *parent ): + QWidget( parent ) +{ + // create widgets + + d_startDateTime = new QDateTimeEdit(); + d_startDateTime->setDisplayFormat( "M/d/yyyy h:mm AP :zzz" ); + d_startDateTime->setCalendarPopup( true ); + + d_endDateTime = new QDateTimeEdit(); + d_endDateTime->setDisplayFormat( "M/d/yyyy h:mm AP :zzz" ); + d_endDateTime->setCalendarPopup( true ); + + d_maxMajorSteps = new QSpinBox(); + d_maxMajorSteps->setRange( 0, 50 ); + + d_maxMinorSteps = new QSpinBox(); + d_maxMinorSteps->setRange( 0, 50 ); + + d_maxWeeks = new QSpinBox(); + d_maxWeeks->setRange( -1, 100 ); + d_maxWeeks->setSpecialValueText( "Disabled" ); + + // layout + + QGridLayout *layout = new QGridLayout( this ); + layout->setAlignment( Qt::AlignLeft | Qt::AlignTop ); + + int row = 0; + layout->addWidget( new QLabel( "From" ), row, 0 ); + layout->addWidget( d_startDateTime, row, 1 ); + + row++; + layout->addWidget( new QLabel( "To" ), row, 0 ); + layout->addWidget( d_endDateTime, row, 1 ); + + row++; + layout->addWidget( new QLabel( "Max. Major Steps" ), row, 0 ); + layout->addWidget( d_maxMajorSteps, row, 1 ); + + row++; + layout->addWidget( new QLabel( "Max. Minor Steps" ), row, 0 ); + layout->addWidget( d_maxMinorSteps, row, 1 ); + + row++; + layout->addWidget( new QLabel( "Max Weeks" ), row, 0 ); + layout->addWidget( d_maxWeeks, row, 1 ); + + connect( d_startDateTime, + SIGNAL( dateTimeChanged( const QDateTime & ) ), SIGNAL( edited() ) ); + connect( d_endDateTime, + SIGNAL( dateTimeChanged( const QDateTime & ) ), SIGNAL( edited() ) ); + connect( d_maxMajorSteps, + SIGNAL( valueChanged( int ) ), SIGNAL( edited() ) ); + connect( d_maxMinorSteps, + SIGNAL( valueChanged( int ) ), SIGNAL( edited() ) ); + connect( d_maxWeeks, + SIGNAL( valueChanged( int ) ), SIGNAL( edited() ) ); +} + +void Panel::setSettings( const Settings &settings ) +{ + blockSignals( true ); + + d_startDateTime->setDateTime( settings.startDateTime ); + d_endDateTime->setDateTime( settings.endDateTime ); + + d_maxMajorSteps->setValue( settings.maxMajorSteps ); + d_maxMinorSteps->setValue( settings.maxMinorSteps ); + d_maxWeeks->setValue( settings.maxWeeks ); + + blockSignals( false ); +} + +Settings Panel::settings() const +{ + Settings settings; + + settings.startDateTime = d_startDateTime->dateTime(); + settings.endDateTime = d_endDateTime->dateTime(); + + settings.maxMajorSteps = d_maxMajorSteps->value(); + settings.maxMinorSteps = d_maxMinorSteps->value(); + settings.maxWeeks = d_maxWeeks->value(); + + return settings; +} diff --git a/qwt/playground/timescale/panel.h b/qwt/playground/timescale/panel.h new file mode 100644 index 000000000..458dfce9e --- /dev/null +++ b/qwt/playground/timescale/panel.h @@ -0,0 +1,32 @@ +#ifndef _PANEL_ +#define _PANEL_ + +#include "settings.h" +#include + +class QDateTimeEdit; +class QSpinBox; + +class Panel: public QWidget +{ + Q_OBJECT + +public: + Panel( QWidget *parent = NULL ); + + void setSettings( const Settings &); + Settings settings() const; + +Q_SIGNALS: + void edited(); + +private: + QDateTimeEdit* d_startDateTime; + QDateTimeEdit* d_endDateTime; + + QSpinBox *d_maxMajorSteps; + QSpinBox *d_maxMinorSteps; + QSpinBox *d_maxWeeks; +}; + +#endif diff --git a/qwt/playground/timescale/plot.cpp b/qwt/playground/timescale/plot.cpp new file mode 100644 index 000000000..f2dd39122 --- /dev/null +++ b/qwt/playground/timescale/plot.cpp @@ -0,0 +1,89 @@ +#include "plot.h" +#include "settings.h" +#include +#include +#include +#include +#include +#include +#include + +Plot::Plot( QWidget *parent ): + QwtPlot( parent ) +{ + setAutoFillBackground( true ); + setPalette( Qt::darkGray ); + setCanvasBackground( Qt::white ); + + plotLayout()->setAlignCanvasToScales( true ); + + initAxis( QwtAxis::yLeft, "Local Time", Qt::LocalTime ); + initAxis( QwtAxis::yRight, + "Coordinated Universal Time ( UTC )", Qt::UTC ); + + QwtPlotPanner *panner = new QwtPlotPanner( canvas() ); + QwtPlotMagnifier *magnifier = new QwtPlotMagnifier( canvas() ); + + for ( int axis = 0; axis < QwtAxis::PosCount; axis++ ) + { + const bool on = QwtAxis::isYAxis( axis ); + + setAxisVisible( axis, on ); + panner->setAxisEnabled( axis, on ); + magnifier->setAxisEnabled( axis, on ); + } + + QwtPlotGrid *grid = new QwtPlotGrid(); + grid->setMajorPen( Qt::black, 0, Qt::SolidLine ); + grid->setMinorPen( Qt::gray, 0 , Qt::SolidLine ); + grid->enableX( false ); + grid->enableXMin( false ); + grid->enableY( true ); + grid->enableYMin( true ); + + grid->attach( this ); +} + +void Plot::initAxis( int axis, + const QString& title, Qt::TimeSpec timeSpec ) +{ + setAxisTitle( axis, title ); + + QwtDateScaleDraw *scaleDraw = new QwtDateScaleDraw( timeSpec ); + QwtDateScaleEngine *scaleEngine = new QwtDateScaleEngine( timeSpec ); + +#if 0 + if ( timeSpec == Qt::LocalTime ) + { + scaleDraw->setTimeSpec( Qt::OffsetFromUTC ); + scaleDraw->setUtcOffset( 10 ); + + scaleEngine->setTimeSpec( Qt::OffsetFromUTC ); + scaleEngine->setUtcOffset( 10 ); + } +#endif + setAxisScaleDraw( axis, scaleDraw ); + setAxisScaleEngine( axis, scaleEngine ); +} + +void Plot::applySettings( const Settings &settings ) +{ + applyAxisSettings( QwtAxis::yLeft, settings ); + applyAxisSettings( QwtAxis::yRight, settings ); + + replot(); +} + +void Plot::applyAxisSettings( int axis, const Settings &settings ) +{ + QwtDateScaleEngine *scaleEngine = + static_cast( axisScaleEngine( axis ) ); + + scaleEngine->setMaxWeeks( settings.maxWeeks ); + setAxisMaxMinor( axis, settings.maxMinorSteps ); + setAxisMaxMajor( axis, settings.maxMajorSteps ); + + + setAxisScale( axis, QwtDate::toDouble( settings.startDateTime ), + QwtDate::toDouble( settings.endDateTime ) ); +} diff --git a/qwt/playground/timescale/plot.h b/qwt/playground/timescale/plot.h new file mode 100644 index 000000000..d92b9a676 --- /dev/null +++ b/qwt/playground/timescale/plot.h @@ -0,0 +1,23 @@ +#ifndef _PLOT_H_ +#define _PLOT_H_ + +#include + +class Settings; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot( QWidget *parent = NULL ); + +public Q_SLOTS: + void applySettings( const Settings & ); + +private: + void initAxis( int axis, const QString& title, Qt::TimeSpec ); + void applyAxisSettings( int axis, const Settings & ); +}; + +#endif diff --git a/qwt/playground/timescale/settings.h b/qwt/playground/timescale/settings.h new file mode 100644 index 000000000..524190faf --- /dev/null +++ b/qwt/playground/timescale/settings.h @@ -0,0 +1,25 @@ +#ifndef _SETTINGS_H_ +#define _SETTINGS_H_ 1 + +#include + +class Settings +{ +public: + Settings(): + maxMajorSteps( 10 ), + maxMinorSteps( 5 ), + maxWeeks( -1 ) + { + }; + + QDateTime startDateTime; + QDateTime endDateTime; + + int maxMajorSteps; + int maxMinorSteps; + + int maxWeeks; +}; + +#endif diff --git a/qwt/playground/timescale/timescale.pro b/qwt/playground/timescale/timescale.pro new file mode 100644 index 000000000..fae1e7f6f --- /dev/null +++ b/qwt/playground/timescale/timescale.pro @@ -0,0 +1,24 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( $${PWD}/../playground.pri ) + +TARGET = timescale + +HEADERS = \ + panel.h \ + plot.h \ + mainwindow.h + +SOURCES = \ + panel.cpp \ + plot.cpp \ + mainwindow.cpp \ + main.cpp + diff --git a/qwt/qwt.prf b/qwt/qwt.prf new file mode 100644 index 000000000..ea2737b6f --- /dev/null +++ b/qwt/qwt.prf @@ -0,0 +1,38 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include ( ./qwtconfig.pri ) +include ( ./qwtfunctions.pri ) + +contains(QWT_CONFIG, QwtDll) { + + DEFINES *= QWT_DLL +} + +contains(QWT_CONFIG, QwtSvg) { + + QT *= svg +} +else { + + DEFINES *= QWT_NO_SVG +} + +contains(QWT_CONFIG, QwtFramework) { + + INCLUDEPATH *= $${QWT_INSTALL_LIBS}/qwt.framework/Headers + LIBS *= -F$${QWT_INSTALL_LIBS} +} +else { + + INCLUDEPATH *= $${QWT_INSTALL_HEADERS} + LIBS *= -L$${QWT_INSTALL_LIBS} +} + +qwtAddLibrary(qwt) diff --git a/qwt/qwt.pro b/qwt/qwt.pro new file mode 100644 index 000000000..04b7443a7 --- /dev/null +++ b/qwt/qwt.pro @@ -0,0 +1,36 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +include( qwtconfig.pri ) + +TEMPLATE = subdirs +CONFIG += ordered + +SUBDIRS = \ + src \ + textengines \ + doc + +contains(QWT_CONFIG, QwtDesigner ) { + SUBDIRS += designer +} + +contains(QWT_CONFIG, QwtExamples ) { + SUBDIRS += examples +} + +contains(QWT_CONFIG, QwtPlayground ) { + SUBDIRS += playground +} + +qwtspec.files = qwtconfig.pri qwtfunctions.pri qwt.prf +qwtspec.path = $${QWT_INSTALL_FEATURES} + +INSTALLS += qwtspec + diff --git a/qwt/qwtbuild.pri b/qwt/qwtbuild.pri new file mode 100644 index 000000000..a0b865d22 --- /dev/null +++ b/qwt/qwtbuild.pri @@ -0,0 +1,86 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +###################################################################### +# qmake internal options +###################################################################### + +CONFIG += qt +CONFIG += warn_on +CONFIG += no_keywords +CONFIG += silent + +###################################################################### +# release/debug mode +###################################################################### + +win32 { + # On Windows you can't mix release and debug libraries. + # The designer is built in release mode. If you like to use it + # you need a release version. For your own application development you + # might need a debug version. + # Enable debug_and_release + build_all if you want to build both. + + CONFIG += debug_and_release + CONFIG += build_all +} +else { + + CONFIG += debug + + VER_MAJ = $${QWT_VER_MAJ} + VER_MIN = $${QWT_VER_MIN} + VER_PAT = $${QWT_VER_PAT} + VERSION = $${QWT_VERSION} +} + +linux-g++ | linux-g++-64 { + #CONFIG += separate_debug_info + #QMAKE_CXXFLAGS *= -Wfloat-equal + #QMAKE_CXXFLAGS *= -Wshadow + #QMAKE_CXXFLAGS *= -Wpointer-arith + #QMAKE_CXXFLAGS *= -Wconversion + #QMAKE_CXXFLAGS *= -Wsign-compare + #QMAKE_CXXFLAGS *= -Wsign-conversion + #QMAKE_CXXFLAGS *= -Wlogical-op + #QMAKE_CXXFLAGS *= -Werror=format-security + #QMAKE_CXXFLAGS *= -std=c++11 + + # when using the gold linker ( Qt < 4.8 ) - might be + # necessary on non linux systems too + #QMAKE_LFLAGS += -lrt +} + +###################################################################### +# paths for building qwt +###################################################################### + +MOC_DIR = moc +RCC_DIR = resources + +!debug_and_release { + + # in case of debug_and_release object files + # are built in the release and debug subdirectories + OBJECTS_DIR = obj +} + +unix { + + exists( $${QMAKE_LIBDIR_QT}/libqwt.* ) { + + # On some Linux distributions the Qwt libraries are installed + # in the same directory as the Qt libraries. Unfortunately + # qmake always adds QMAKE_LIBDIR_QT at the beginning of the + # linker path, so that the installed libraries will be + # used instead of the local ones. + + error( "local build will conflict with $${QMAKE_LIBDIR_QT}/libqwt.*" ) + } +} diff --git a/qwt/qwtconfig.pri b/qwt/qwtconfig.pri new file mode 100644 index 000000000..40d25659c --- /dev/null +++ b/qwt/qwtconfig.pri @@ -0,0 +1,163 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +QWT_VER_MAJ = 6 +QWT_VER_MIN = 1 +QWT_VER_PAT = 1 +QWT_VERSION = $${QWT_VER_MAJ}.$${QWT_VER_MIN}.$${QWT_VER_PAT} + +###################################################################### +# Install paths +###################################################################### + +QWT_INSTALL_PREFIX = $$[QT_INSTALL_PREFIX] + +unix { + QWT_INSTALL_PREFIX = /usr/local/qwt-$$QWT_VERSION-svn +} + +win32 { + QWT_INSTALL_PREFIX = C:/Qwt-$$QWT_VERSION-svn +} + +QWT_INSTALL_DOCS = $${QWT_INSTALL_PREFIX}/doc +QWT_INSTALL_HEADERS = $${QWT_INSTALL_PREFIX}/include +QWT_INSTALL_LIBS = $${QWT_INSTALL_PREFIX}/lib + +###################################################################### +# Designer plugin +# creator/designer load designer plugins from certain default +# directories ( f.e the path below QT_INSTALL_PREFIX ) and the +# directories listed in the QT_PLUGIN_PATH environment variable. +# When using the path below QWT_INSTALL_PREFIX you need to +# add $${QWT_INSTALL_PREFIX}/plugins to QT_PLUGIN_PATH in the +# runtime environment of designer/creator. +###################################################################### + +QWT_INSTALL_PLUGINS = $${QWT_INSTALL_PREFIX}/plugins/designer + +# linux distributors often organize the Qt installation +# their way and QT_INSTALL_PREFIX doesn't offer a good +# path. Also QT_INSTALL_PREFIX is only one of the default +# search paths of the designer - not the Qt creator + +#QWT_INSTALL_PLUGINS = $$[QT_INSTALL_PREFIX]/plugins/designer + +###################################################################### +# Features +# When building a Qwt application with qmake you might want to load +# the compiler/linker flags, that are required to build a Qwt application +# from qwt.prf. Therefore all you need to do is to add "CONFIG += qwt" +# to your project file and take care, that qwt.prf can be found by qmake. +# ( see http://doc.trolltech.com/4.7/qmake-advanced-usage.html#adding-new-configuration-features ) +# I recommend not to install the Qwt features together with the +# Qt features, because you will have to reinstall the Qwt features, +# with every Qt upgrade. +###################################################################### + +QWT_INSTALL_FEATURES = $${QWT_INSTALL_PREFIX}/features +# QWT_INSTALL_FEATURES = $$[QT_INSTALL_PREFIX]/features + +###################################################################### +# Build the static/shared libraries. +# If QwtDll is enabled, a shared library is built, otherwise +# it will be a static library. +###################################################################### + +QWT_CONFIG += QwtDll + +###################################################################### +# QwtPlot enables all classes, that are needed to use the QwtPlot +# widget. +###################################################################### + +QWT_CONFIG += QwtPlot + +###################################################################### +# QwtWidgets enables all classes, that are needed to use the all other +# widgets (sliders, dials, ...), beside QwtPlot. +###################################################################### + +QWT_CONFIG += QwtWidgets + +###################################################################### +# If you want to display svg images on the plot canvas, or +# export a plot to a SVG document +###################################################################### + +QWT_CONFIG += QwtSvg + +###################################################################### +# If you want to use a OpenGL plot canvas +###################################################################### + +QWT_CONFIG += QwtOpenGL + +###################################################################### +# You can use the MathML renderer of the Qt solutions package to +# enable MathML support in Qwt. Because of license implications +# the ( modified ) code of the MML Widget solution is included and +# linked together with the QwtMathMLTextEngine into an own library. +# To use it you will have to add "CONFIG += qwtmathml" +# to your qmake project file. +###################################################################### + +QWT_CONFIG += QwtMathML + +###################################################################### +# If you want to build the Qwt designer plugin, +# enable the line below. +# Otherwise you have to build it from the designer directory. +###################################################################### + +QWT_CONFIG += QwtDesigner + +###################################################################### +# Compile all Qwt classes into the designer plugin instead +# of linking it against the shared Qwt library. Has no effect +# when QwtDesigner or QwtDll are not both enabled. +# +# On systems where rpath is supported ( all Unixoids ) the +# location of the installed Qwt library is compiled into the plugin, +# but on Windows it might be easier to have a self contained +# plugin to avoid any hassle with configuring the runtime +# environment of the designer/creator. +###################################################################### + +win32 { + QWT_CONFIG += QwtDesignerSelfContained +} + +###################################################################### +# If you want to auto build the examples, enable the line below +# Otherwise you have to build them from the examples directory. +###################################################################### + +QWT_CONFIG += QwtExamples + +###################################################################### +# The playground is primarily intended for the Qwt development +# to explore and test new features. Nevertheless you might find +# ideas or code snippets that help for application development +# If you want to auto build the applications in playground, enable +# the line below. +# Otherwise you have to build them from the playground directory. +###################################################################### + +QWT_CONFIG += QwtPlayground + +###################################################################### +# When Qt has been built as framework qmake wants +# to link frameworks instead of regular libs +###################################################################### + +macx:!static:CONFIG(qt_framework, qt_framework|qt_no_framework) { + + QWT_CONFIG += QwtFramework +} diff --git a/qwt/qwtfunctions.pri b/qwt/qwtfunctions.pri new file mode 100644 index 000000000..022e28010 --- /dev/null +++ b/qwt/qwtfunctions.pri @@ -0,0 +1,71 @@ +################################################################ +# Qwt Widget Library +# Copyright (C) 1997 Josef Wilgen +# Copyright (C) 2002 Uwe Rathmann +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the Qwt License, Version 1.0 +################################################################ + +# Copied and modified from qt_functions.prf + +defineReplace(qwtLibraryTarget) { + + unset(LIBRARY_NAME) + LIBRARY_NAME = $$1 + + mac:contains(QWT_CONFIG, QwtFramework) { + + QMAKE_FRAMEWORK_BUNDLE_NAME = $$LIBRARY_NAME + export(QMAKE_FRAMEWORK_BUNDLE_NAME) + } + + contains(TEMPLATE, .*lib):CONFIG(debug, debug|release) { + + !debug_and_release|build_pass { + + mac:RET = $$member(LIBRARY_NAME, 0)_debug + win32:RET = $$member(LIBRARY_NAME, 0)d + } + } + + isEmpty(RET):RET = $$LIBRARY_NAME + return($$RET) +} + +defineTest(qwtAddLibrary) { + + LIB_NAME = $$1 + + unset(LINKAGE) + + mac:contains(QWT_CONFIG, QwtFramework) { + + LINKAGE = -framework $${LIB_NAME} + } + + isEmpty(LINKAGE) { + + if(!debug_and_release|build_pass):CONFIG(debug, debug|release) { + + mac:LINKAGE = -l$${LIB_NAME}_debug + win32:LINKAGE = -l$${LIB_NAME}d + } + } + + isEmpty(LINKAGE) { + + LINKAGE = -l$${LIB_NAME} + } + + !isEmpty(QMAKE_LSB) { + + QMAKE_LFLAGS *= --lsb-shared-libs=$${LIB_NAME} + } + + LIBS += $$LINKAGE + export(LIBS) + export(QMAKE_LFLAGS) + + return(true) +} diff --git a/qwt/src/qwt.h b/qwt/src/qwt.h new file mode 100644 index 000000000..37494933e --- /dev/null +++ b/qwt/src/qwt.h @@ -0,0 +1,22 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_H +#define QWT_H + +#include "qwt_global.h" + +/*! + Some constants for use within Qwt. +*/ +namespace Qwt +{ +}; + +#endif diff --git a/qwt/src/qwt_abstract_legend.cpp b/qwt/src/qwt_abstract_legend.cpp new file mode 100644 index 000000000..4ecaac61d --- /dev/null +++ b/qwt/src/qwt_abstract_legend.cpp @@ -0,0 +1,38 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_abstract_legend.h" + +/*! + Constructor + + \param parent Parent widget +*/ +QwtAbstractLegend::QwtAbstractLegend( QWidget *parent ): + QFrame( parent ) +{ +} + +//! Destructor +QwtAbstractLegend::~QwtAbstractLegend() +{ +} + +/*! + Return the extent, that is needed for elements to scroll + the legend ( usually scrollbars ), + + \param orientation Orientation + \return Extent of the corresponding scroll element +*/ +int QwtAbstractLegend::scrollExtent( Qt::Orientation orientation ) const +{ + Q_UNUSED( orientation ); + return 0; +} diff --git a/qwt/src/qwt_abstract_legend.h b/qwt/src/qwt_abstract_legend.h new file mode 100644 index 000000000..18bd3f4b9 --- /dev/null +++ b/qwt/src/qwt_abstract_legend.h @@ -0,0 +1,71 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_ABSTRACT_LEGEND_H +#define QWT_ABSTRACT_LEGEND_H + +#include "qwt_global.h" +#include "qwt_legend_data.h" +#include +#include + +class QVariant; + +/*! + \brief Abstract base class for legend widgets + + Legends, that need to be under control of the QwtPlot layout system + need to be derived from QwtAbstractLegend. + + \note Other type of legends can be implemented by connecting to + the QwtPlot::legendDataChanged() signal. But as these legends + are unknown to the plot layout system the layout code + ( on screen and for QwtPlotRenderer ) need to be organized + in application code. + + \sa QwtLegend + */ +class QWT_EXPORT QwtAbstractLegend : public QFrame +{ + Q_OBJECT + +public: + explicit QwtAbstractLegend( QWidget *parent = NULL ); + virtual ~QwtAbstractLegend(); + + /*! + Render the legend into a given rectangle. + + \param painter Painter + \param rect Bounding rectangle + \param fillBackground When true, fill rect with the widget background + + \sa renderLegend() is used by QwtPlotRenderer + */ + virtual void renderLegend( QPainter *painter, + const QRectF &rect, bool fillBackground ) const = 0; + + //! \return True, when no plot item is inserted + virtual bool isEmpty() const = 0; + + virtual int scrollExtent( Qt::Orientation ) const; + +public Q_SLOTS: + + /*! + \brief Update the entries for a plot item + + \param itemInfo Info about an item + \param data List of legend entry attributes for the item + */ + virtual void updateLegend( const QVariant &itemInfo, + const QList &data ) = 0; +}; + +#endif diff --git a/qwt/src/qwt_abstract_scale.cpp b/qwt/src/qwt_abstract_scale.cpp new file mode 100644 index 000000000..3018b854e --- /dev/null +++ b/qwt/src/qwt_abstract_scale.cpp @@ -0,0 +1,449 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_abstract_scale.h" +#include "qwt_scale_engine.h" +#include "qwt_scale_draw.h" +#include "qwt_scale_div.h" +#include "qwt_scale_map.h" +#include "qwt_interval.h" + +class QwtAbstractScale::PrivateData +{ +public: + PrivateData(): + maxMajor( 5 ), + maxMinor( 3 ), + stepSize( 0.0 ) + { + scaleEngine = new QwtLinearScaleEngine(); + scaleDraw = new QwtScaleDraw(); + } + + ~PrivateData() + { + delete scaleEngine; + delete scaleDraw; + } + + QwtScaleEngine *scaleEngine; + QwtAbstractScaleDraw *scaleDraw; + + int maxMajor; + int maxMinor; + double stepSize; +}; + +/*! + Constructor + + \param parent Parent widget + + Creates a default QwtScaleDraw and a QwtLinearScaleEngine. + The initial scale boundaries are set to [ 0.0, 100.0 ] + + The scaleStepSize() is initialized to 0.0, scaleMaxMajor() to 5 + and scaleMaxMajor to 3. +*/ + +QwtAbstractScale::QwtAbstractScale( QWidget *parent ): + QWidget( parent ) +{ + d_data = new PrivateData; + rescale( 0.0, 100.0, d_data->stepSize ); +} + +//! Destructor +QwtAbstractScale::~QwtAbstractScale() +{ + delete d_data; +} + +/*! + Set the lower bound of the scale + + \param value Lower bound + + \sa lowerBound(), setScale(), setUpperBound() + \note For inverted scales the lower bound + is greater than the upper bound +*/ +void QwtAbstractScale::setLowerBound( double value ) +{ + setScale( value, upperBound() ); +} + +/*! + \return Lower bound of the scale + \sa setLowerBound(), setScale(), upperBound() +*/ +double QwtAbstractScale::lowerBound() const +{ + return d_data->scaleDraw->scaleDiv().lowerBound(); +} + +/*! + Set the upper bound of the scale + + \param value Upper bound + + \sa upperBound(), setScale(), setLowerBound() + \note For inverted scales the lower bound + is greater than the upper bound +*/ +void QwtAbstractScale::setUpperBound( double value ) +{ + setScale( lowerBound(), value ); +} + +/*! + \return Upper bound of the scale + \sa setUpperBound(), setScale(), lowerBound() +*/ +double QwtAbstractScale::upperBound() const +{ + return d_data->scaleDraw->scaleDiv().upperBound(); +} + +/*! + \brief Specify a scale. + + Define a scale by an interval + + The ticks are calculated using scaleMaxMinor(), + scaleMaxMajor() and scaleStepSize(). + + \param lowerBound lower limit of the scale interval + \param upperBound upper limit of the scale interval + + \note For inverted scales the lower bound + is greater than the upper bound +*/ +void QwtAbstractScale::setScale( double lowerBound, double upperBound ) +{ + rescale( lowerBound, upperBound, d_data->stepSize ); +} + +/*! + \brief Specify a scale. + + Define a scale by an interval + + The ticks are calculated using scaleMaxMinor(), + scaleMaxMajor() and scaleStepSize(). + + \param interval Interval +*/ +void QwtAbstractScale::setScale( const QwtInterval &interval ) +{ + setScale( interval.minValue(), interval.maxValue() ); +} + +/*! + \brief Specify a scale. + + scaleMaxMinor(), scaleMaxMajor() and scaleStepSize() and have no effect. + + \param scaleDiv Scale division + \sa setAutoScale() +*/ +void QwtAbstractScale::setScale( const QwtScaleDiv &scaleDiv ) +{ + if ( scaleDiv != d_data->scaleDraw->scaleDiv() ) + { +#if 1 + if ( d_data->scaleEngine ) + { + d_data->scaleDraw->setTransformation( + d_data->scaleEngine->transformation() ); + } +#endif + + d_data->scaleDraw->setScaleDiv( scaleDiv ); + + scaleChange(); + } +} + +/*! + \brief Set the maximum number of major tick intervals. + + The scale's major ticks are calculated automatically such that + the number of major intervals does not exceed ticks. + + The default value is 5. + + \param ticks Maximal number of major ticks. + + \sa scaleMaxMajor(), setScaleMaxMinor(), + setScaleStepSize(), QwtScaleEngine::divideInterval() +*/ +void QwtAbstractScale::setScaleMaxMajor( int ticks ) +{ + if ( ticks != d_data->maxMajor ) + { + d_data->maxMajor = ticks; + updateScaleDraw(); + } +} + +/*! + \return Maximal number of major tick intervals + \sa setScaleMaxMajor(), scaleMaxMinor() +*/ +int QwtAbstractScale::scaleMaxMajor() const +{ + return d_data->maxMajor; +} + +/*! + \brief Set the maximum number of minor tick intervals + + The scale's minor ticks are calculated automatically such that + the number of minor intervals does not exceed ticks. + The default value is 3. + + \param ticks Maximal number of minor ticks. + + \sa scaleMaxMajor(), setScaleMaxMinor(), + setScaleStepSize(), QwtScaleEngine::divideInterval() +*/ +void QwtAbstractScale::setScaleMaxMinor( int ticks ) +{ + if ( ticks != d_data->maxMinor ) + { + d_data->maxMinor = ticks; + updateScaleDraw(); + } +} + +/*! + \return Maximal number of minor tick intervals + \sa setScaleMaxMinor(), scaleMaxMajor() +*/ +int QwtAbstractScale::scaleMaxMinor() const +{ + return d_data->maxMinor; +} + +/*! + \brief Set the step size used for calculating a scale division + + The step size is hint for calculating the intervals for + the major ticks of the scale. A value of 0.0 is interpreted + as no hint. + + \param stepSize Hint for the step size of the scale + + \sa scaleStepSize(), QwtScaleEngine::divideScale() + + \note Position and distance between the major ticks also + depends on scaleMaxMajor(). +*/ +void QwtAbstractScale::setScaleStepSize( double stepSize ) +{ + if ( stepSize != d_data->stepSize ) + { + d_data->stepSize = stepSize; + updateScaleDraw(); + } +} + +/*! + \return Hint for the step size of the scale + \sa setScaleStepSize(), QwtScaleEngine::divideScale() +*/ +double QwtAbstractScale::scaleStepSize() const +{ + return d_data->stepSize; +} + +/*! + \brief Set a scale draw + + scaleDraw has to be created with new and will be deleted in + the destructor or the next call of setAbstractScaleDraw(). + + \sa abstractScaleDraw() +*/ +void QwtAbstractScale::setAbstractScaleDraw( QwtAbstractScaleDraw *scaleDraw ) +{ + if ( scaleDraw == NULL || scaleDraw == d_data->scaleDraw ) + return; + + if ( d_data->scaleDraw != NULL ) + scaleDraw->setScaleDiv( d_data->scaleDraw->scaleDiv() ); + + delete d_data->scaleDraw; + d_data->scaleDraw = scaleDraw; +} + +/*! + \return Scale draw + \sa setAbstractScaleDraw() +*/ +QwtAbstractScaleDraw *QwtAbstractScale::abstractScaleDraw() +{ + return d_data->scaleDraw; +} + +/*! + \return Scale draw + \sa setAbstractScaleDraw() +*/ +const QwtAbstractScaleDraw *QwtAbstractScale::abstractScaleDraw() const +{ + return d_data->scaleDraw; +} + +/*! + \brief Set a scale engine + + The scale engine is responsible for calculating the scale division + and provides a transformation between scale and widget coordinates. + + scaleEngine has to be created with new and will be deleted in + the destructor or the next call of setScaleEngine. +*/ +void QwtAbstractScale::setScaleEngine( QwtScaleEngine *scaleEngine ) +{ + if ( scaleEngine != NULL && scaleEngine != d_data->scaleEngine ) + { + delete d_data->scaleEngine; + d_data->scaleEngine = scaleEngine; + } +} + +/*! + \return Scale engine + \sa setScaleEngine() +*/ +const QwtScaleEngine *QwtAbstractScale::scaleEngine() const +{ + return d_data->scaleEngine; +} + +/*! + \return Scale engine + \sa setScaleEngine() +*/ +QwtScaleEngine *QwtAbstractScale::scaleEngine() +{ + return d_data->scaleEngine; +} + +/*! + \return Scale boundaries and positions of the ticks + + The scale division might have been assigned explicitly + or calculated implicitly by rescale(). + */ +const QwtScaleDiv &QwtAbstractScale::scaleDiv() const +{ + return d_data->scaleDraw->scaleDiv(); +} + +/*! + \return Map to translate between scale and widget coordinates + */ +const QwtScaleMap &QwtAbstractScale::scaleMap() const +{ + return d_data->scaleDraw->scaleMap(); +} + +/*! + Translate a scale value into a widget coordinate + + \param value Scale value + \return Corresponding widget coordinate for value + \sa scaleMap(), invTransform() + */ +int QwtAbstractScale::transform( double value ) const +{ + return qRound( d_data->scaleDraw->scaleMap().transform( value ) ); +} + +/*! + Translate a widget coordinate into a scale value + + \param value Widget coordinate + \return Corresponding scale coordinate for value + \sa scaleMap(), transform() + */ +double QwtAbstractScale::invTransform( int value ) const +{ + return d_data->scaleDraw->scaleMap().invTransform( value ); +} + +/*! + \return True, when the scale is increasing in opposite direction + to the widget coordinates + */ +bool QwtAbstractScale::isInverted() const +{ + return d_data->scaleDraw->scaleMap().isInverting(); +} + +/*! + \return The boundary with the smaller value + \sa maximum(), lowerBound(), upperBound() + */ +double QwtAbstractScale::minimum() const +{ + return qMin( d_data->scaleDraw->scaleDiv().lowerBound(), + d_data->scaleDraw->scaleDiv().upperBound() ); +} + +/*! + \return The boundary with the larger value + \sa minimum(), lowerBound(), upperBound() + */ +double QwtAbstractScale::maximum() const +{ + return qMax( d_data->scaleDraw->scaleDiv().lowerBound(), + d_data->scaleDraw->scaleDiv().upperBound() ); +} + +//! Notify changed scale +void QwtAbstractScale::scaleChange() +{ +} + +/*! + Recalculate the scale division and update the scale. + + \param lowerBound Lower limit of the scale interval + \param upperBound Upper limit of the scale interval + \param stepSize Major step size + + \sa scaleChange() +*/ +void QwtAbstractScale::rescale( + double lowerBound, double upperBound, double stepSize ) +{ + const QwtScaleDiv scaleDiv = d_data->scaleEngine->divideScale( + lowerBound, upperBound, d_data->maxMajor, d_data->maxMinor, stepSize ); + + if ( scaleDiv != d_data->scaleDraw->scaleDiv() ) + { +#if 1 + d_data->scaleDraw->setTransformation( + d_data->scaleEngine->transformation() ); +#endif + + d_data->scaleDraw->setScaleDiv( scaleDiv ); + scaleChange(); + } +} + +void QwtAbstractScale::updateScaleDraw() +{ + rescale( d_data->scaleDraw->scaleDiv().lowerBound(), + d_data->scaleDraw->scaleDiv().upperBound(), d_data->stepSize ); +} diff --git a/qwt/src/qwt_abstract_scale.h b/qwt/src/qwt_abstract_scale.h new file mode 100644 index 000000000..4ed6616aa --- /dev/null +++ b/qwt/src/qwt_abstract_scale.h @@ -0,0 +1,105 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_ABSTRACT_SCALE_H +#define QWT_ABSTRACT_SCALE_H + +#include "qwt_global.h" +#include + +class QwtScaleEngine; +class QwtAbstractScaleDraw; +class QwtScaleDiv; +class QwtScaleMap; +class QwtInterval; + +/*! + \brief An abstract base class for widgets having a scale + + The scale of an QwtAbstractScale is determined by a QwtScaleDiv + definition, that contains the boundaries and the ticks of the scale. + The scale is painted using a QwtScaleDraw object. + + The scale division might be assigned explicitly - but usually + it is calculated from the boundaries using a QwtScaleEngine. + + The scale engine also decides the type of transformation of the scale + ( linear, logarithmic ... ). +*/ + +class QWT_EXPORT QwtAbstractScale: public QWidget +{ + Q_OBJECT + + Q_PROPERTY( double lowerBound READ lowerBound WRITE setLowerBound ) + Q_PROPERTY( double upperBound READ upperBound WRITE setUpperBound ) + + Q_PROPERTY( int scaleMaxMajor READ scaleMaxMajor WRITE setScaleMaxMajor ) + Q_PROPERTY( int scaleMaxMinor READ scaleMaxMinor WRITE setScaleMaxMinor ) + + Q_PROPERTY( double scaleStepSize READ scaleStepSize WRITE setScaleStepSize ) + +public: + QwtAbstractScale( QWidget *parent = NULL ); + virtual ~QwtAbstractScale(); + + void setScale( double lowerBound, double upperBound ); + void setScale( const QwtInterval & ); + void setScale( const QwtScaleDiv & ); + + const QwtScaleDiv& scaleDiv() const; + + void setLowerBound( double value ); + double lowerBound() const; + + void setUpperBound( double value ); + double upperBound() const; + + void setScaleStepSize( double stepSize ); + double scaleStepSize() const; + + void setScaleMaxMajor( int ticks ); + int scaleMaxMinor() const; + + void setScaleMaxMinor( int ticks ); + int scaleMaxMajor() const; + + void setScaleEngine( QwtScaleEngine * ); + const QwtScaleEngine *scaleEngine() const; + QwtScaleEngine *scaleEngine(); + + int transform( double ) const; + double invTransform( int ) const; + + bool isInverted() const; + + double minimum() const; + double maximum() const; + + const QwtScaleMap &scaleMap() const; + +protected: + void rescale( double lowerBound, + double upperBound, double stepSize ); + + void setAbstractScaleDraw( QwtAbstractScaleDraw * ); + + const QwtAbstractScaleDraw *abstractScaleDraw() const; + QwtAbstractScaleDraw *abstractScaleDraw(); + + virtual void scaleChange(); + +private: + void updateScaleDraw(); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_abstract_scale_draw.cpp b/qwt/src/qwt_abstract_scale_draw.cpp new file mode 100644 index 000000000..201173303 --- /dev/null +++ b/qwt/src/qwt_abstract_scale_draw.cpp @@ -0,0 +1,416 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_abstract_scale_draw.h" +#include "qwt_math.h" +#include "qwt_text.h" +#include "qwt_painter.h" +#include "qwt_scale_map.h" +#include +#include +#include +#include + +class QwtAbstractScaleDraw::PrivateData +{ +public: + PrivateData(): + spacing( 4.0 ), + penWidth( 0 ), + minExtent( 0.0 ) + { + components = QwtAbstractScaleDraw::Backbone + | QwtAbstractScaleDraw::Ticks + | QwtAbstractScaleDraw::Labels; + + tickLength[QwtScaleDiv::MinorTick] = 4.0; + tickLength[QwtScaleDiv::MediumTick] = 6.0; + tickLength[QwtScaleDiv::MajorTick] = 8.0; + } + + ScaleComponents components; + + QwtScaleMap map; + QwtScaleDiv scaleDiv; + + double spacing; + double tickLength[QwtScaleDiv::NTickTypes]; + int penWidth; + + double minExtent; + + QMap labelCache; +}; + +/*! + \brief Constructor + + The range of the scale is initialized to [0, 100], + The spacing (distance between ticks and labels) is + set to 4, the tick lengths are set to 4,6 and 8 pixels +*/ +QwtAbstractScaleDraw::QwtAbstractScaleDraw() +{ + d_data = new QwtAbstractScaleDraw::PrivateData; +} + +//! Destructor +QwtAbstractScaleDraw::~QwtAbstractScaleDraw() +{ + delete d_data; +} + +/*! + En/Disable a component of the scale + + \param component Scale component + \param enable On/Off + + \sa hasComponent() +*/ +void QwtAbstractScaleDraw::enableComponent( + ScaleComponent component, bool enable ) +{ + if ( enable ) + d_data->components |= component; + else + d_data->components &= ~component; +} + +/*! + Check if a component is enabled + + \param component Component type + \return true, when component is enabled + \sa enableComponent() +*/ +bool QwtAbstractScaleDraw::hasComponent( ScaleComponent component ) const +{ + return ( d_data->components & component ); +} + +/*! + Change the scale division + \param scaleDiv New scale division +*/ +void QwtAbstractScaleDraw::setScaleDiv( const QwtScaleDiv &scaleDiv ) +{ + d_data->scaleDiv = scaleDiv; + d_data->map.setScaleInterval( scaleDiv.lowerBound(), scaleDiv.upperBound() ); + d_data->labelCache.clear(); +} + +/*! + Change the transformation of the scale + \param transformation New scale transformation +*/ +void QwtAbstractScaleDraw::setTransformation( + QwtTransform *transformation ) +{ + d_data->map.setTransformation( transformation ); +} + +//! \return Map how to translate between scale and pixel values +const QwtScaleMap &QwtAbstractScaleDraw::scaleMap() const +{ + return d_data->map; +} + +//! \return Map how to translate between scale and pixel values +QwtScaleMap &QwtAbstractScaleDraw::scaleMap() +{ + return d_data->map; +} + +//! \return scale division +const QwtScaleDiv& QwtAbstractScaleDraw::scaleDiv() const +{ + return d_data->scaleDiv; +} + +/*! + \brief Specify the width of the scale pen + \param width Pen width + \sa penWidth() +*/ +void QwtAbstractScaleDraw::setPenWidth( int width ) +{ + if ( width < 0 ) + width = 0; + + if ( width != d_data->penWidth ) + d_data->penWidth = width; +} + +/*! + \return Scale pen width + \sa setPenWidth() +*/ +int QwtAbstractScaleDraw::penWidth() const +{ + return d_data->penWidth; +} + +/*! + \brief Draw the scale + + \param painter The painter + + \param palette Palette, text color is used for the labels, + foreground color for ticks and backbone +*/ +void QwtAbstractScaleDraw::draw( QPainter *painter, + const QPalette& palette ) const +{ + painter->save(); + + QPen pen = painter->pen(); + pen.setWidth( d_data->penWidth ); + pen.setCosmetic( false ); + painter->setPen( pen ); + + if ( hasComponent( QwtAbstractScaleDraw::Labels ) ) + { + painter->save(); + painter->setPen( palette.color( QPalette::Text ) ); // ignore pen style + + const QList &majorTicks = + d_data->scaleDiv.ticks( QwtScaleDiv::MajorTick ); + + for ( int i = 0; i < majorTicks.count(); i++ ) + { + const double v = majorTicks[i]; + if ( d_data->scaleDiv.contains( v ) ) + drawLabel( painter, v ); + } + + painter->restore(); + } + + if ( hasComponent( QwtAbstractScaleDraw::Ticks ) ) + { + painter->save(); + + QPen pen = painter->pen(); + pen.setColor( palette.color( QPalette::WindowText ) ); + pen.setCapStyle( Qt::FlatCap ); + + painter->setPen( pen ); + + for ( int tickType = QwtScaleDiv::MinorTick; + tickType < QwtScaleDiv::NTickTypes; tickType++ ) + { + const QList &ticks = d_data->scaleDiv.ticks( tickType ); + for ( int i = 0; i < ticks.count(); i++ ) + { + const double v = ticks[i]; + if ( d_data->scaleDiv.contains( v ) ) + drawTick( painter, v, d_data->tickLength[tickType] ); + } + } + + painter->restore(); + } + + if ( hasComponent( QwtAbstractScaleDraw::Backbone ) ) + { + painter->save(); + + QPen pen = painter->pen(); + pen.setColor( palette.color( QPalette::WindowText ) ); + pen.setCapStyle( Qt::FlatCap ); + + painter->setPen( pen ); + + drawBackbone( painter ); + + painter->restore(); + } + + painter->restore(); +} + +/*! + \brief Set the spacing between tick and labels + + The spacing is the distance between ticks and labels. + The default spacing is 4 pixels. + + \param spacing Spacing + + \sa spacing() +*/ +void QwtAbstractScaleDraw::setSpacing( double spacing ) +{ + if ( spacing < 0 ) + spacing = 0; + + d_data->spacing = spacing; +} + +/*! + \brief Get the spacing + + The spacing is the distance between ticks and labels. + The default spacing is 4 pixels. + + \return Spacing + \sa setSpacing() +*/ +double QwtAbstractScaleDraw::spacing() const +{ + return d_data->spacing; +} + +/*! + \brief Set a minimum for the extent + + The extent is calculated from the components of the + scale draw. In situations, where the labels are + changing and the layout depends on the extent (f.e scrolling + a scale), setting an upper limit as minimum extent will + avoid jumps of the layout. + + \param minExtent Minimum extent + + \sa extent(), minimumExtent() +*/ +void QwtAbstractScaleDraw::setMinimumExtent( double minExtent ) +{ + if ( minExtent < 0.0 ) + minExtent = 0.0; + + d_data->minExtent = minExtent; +} + +/*! + Get the minimum extent + \return Minimum extent + \sa extent(), setMinimumExtent() +*/ +double QwtAbstractScaleDraw::minimumExtent() const +{ + return d_data->minExtent; +} + +/*! + Set the length of the ticks + + \param tickType Tick type + \param length New length + + \warning the length is limited to [0..1000] +*/ +void QwtAbstractScaleDraw::setTickLength( + QwtScaleDiv::TickType tickType, double length ) +{ + if ( tickType < QwtScaleDiv::MinorTick || + tickType > QwtScaleDiv::MajorTick ) + { + return; + } + + if ( length < 0.0 ) + length = 0.0; + + const double maxTickLen = 1000.0; + if ( length > maxTickLen ) + length = maxTickLen; + + d_data->tickLength[tickType] = length; +} + +/*! + \return Length of the ticks + \sa setTickLength(), maxTickLength() +*/ +double QwtAbstractScaleDraw::tickLength( QwtScaleDiv::TickType tickType ) const +{ + if ( tickType < QwtScaleDiv::MinorTick || + tickType > QwtScaleDiv::MajorTick ) + { + return 0; + } + + return d_data->tickLength[tickType]; +} + +/*! + \return Length of the longest tick + + Useful for layout calculations + \sa tickLength(), setTickLength() +*/ +double QwtAbstractScaleDraw::maxTickLength() const +{ + double length = 0.0; + for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ ) + length = qMax( length, d_data->tickLength[i] ); + + return length; +} + +/*! + \brief Convert a value into its representing label + + The value is converted to a plain text using + QLocale().toString(value). + This method is often overloaded by applications to have individual + labels. + + \param value Value + \return Label string. +*/ +QwtText QwtAbstractScaleDraw::label( double value ) const +{ + return QLocale().toString( value ); +} + +/*! + \brief Convert a value into its representing label and cache it. + + The conversion between value and label is called very often + in the layout and painting code. Unfortunately the + calculation of the label sizes might be slow (really slow + for rich text in Qt4), so it's necessary to cache the labels. + + \param font Font + \param value Value + + \return Tick label +*/ +const QwtText &QwtAbstractScaleDraw::tickLabel( + const QFont &font, double value ) const +{ + QMap::const_iterator it = d_data->labelCache.find( value ); + if ( it == d_data->labelCache.end() ) + { + QwtText lbl = label( value ); + lbl.setRenderFlags( 0 ); + lbl.setLayoutAttribute( QwtText::MinimumLayout ); + + ( void )lbl.textSize( font ); // initialize the internal cache + + it = d_data->labelCache.insert( value, lbl ); + } + + return ( *it ); +} + +/*! + Invalidate the cache used by tickLabel() + + The cache is invalidated, when a new QwtScaleDiv is set. If + the labels need to be changed. while the same QwtScaleDiv is set, + invalidateCache() needs to be called manually. +*/ +void QwtAbstractScaleDraw::invalidateCache() +{ + d_data->labelCache.clear(); +} diff --git a/qwt/src/qwt_abstract_scale_draw.h b/qwt/src/qwt_abstract_scale_draw.h new file mode 100644 index 000000000..d0f1ec3c3 --- /dev/null +++ b/qwt/src/qwt_abstract_scale_draw.h @@ -0,0 +1,141 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_ABSTRACT_SCALE_DRAW_H +#define QWT_ABSTRACT_SCALE_DRAW_H + +#include "qwt_global.h" +#include "qwt_scale_div.h" +#include "qwt_text.h" + +class QPalette; +class QPainter; +class QFont; +class QwtTransform; +class QwtScaleMap; + +/*! + \brief A abstract base class for drawing scales + + QwtAbstractScaleDraw can be used to draw linear or logarithmic scales. + + After a scale division has been specified as a QwtScaleDiv object + using setScaleDiv(), the scale can be drawn with the draw() member. +*/ +class QWT_EXPORT QwtAbstractScaleDraw +{ +public: + + /*! + Components of a scale + \sa enableComponent(), hasComponent + */ + enum ScaleComponent + { + //! Backbone = the line where the ticks are located + Backbone = 0x01, + + //! Ticks + Ticks = 0x02, + + //! Labels + Labels = 0x04 + }; + + //! Scale components + typedef QFlags ScaleComponents; + + QwtAbstractScaleDraw(); + virtual ~QwtAbstractScaleDraw(); + + void setScaleDiv( const QwtScaleDiv &s ); + const QwtScaleDiv& scaleDiv() const; + + void setTransformation( QwtTransform * ); + const QwtScaleMap &scaleMap() const; + QwtScaleMap &scaleMap(); + + void enableComponent( ScaleComponent, bool enable = true ); + bool hasComponent( ScaleComponent ) const; + + void setTickLength( QwtScaleDiv::TickType, double length ); + double tickLength( QwtScaleDiv::TickType ) const; + double maxTickLength() const; + + void setSpacing( double margin ); + double spacing() const; + + void setPenWidth( int width ); + int penWidth() const; + + virtual void draw( QPainter *, const QPalette & ) const; + + virtual QwtText label( double ) const; + + /*! + Calculate the extent + + The extent is the distance from the baseline to the outermost + pixel of the scale draw in opposite to its orientation. + It is at least minimumExtent() pixels. + + \param font Font used for drawing the tick labels + \return Number of pixels + + \sa setMinimumExtent(), minimumExtent() + */ + virtual double extent( const QFont &font ) const = 0; + + void setMinimumExtent( double ); + double minimumExtent() const; + +protected: + /*! + Draw a tick + + \param painter Painter + \param value Value of the tick + \param len Length of the tick + + \sa drawBackbone(), drawLabel() + */ + virtual void drawTick( QPainter *painter, double value, double len ) const = 0; + + /*! + Draws the baseline of the scale + \param painter Painter + + \sa drawTick(), drawLabel() + */ + virtual void drawBackbone( QPainter *painter ) const = 0; + + /*! + Draws the label for a major scale tick + + \param painter Painter + \param value Value + + \sa drawTick(), drawBackbone() + */ + virtual void drawLabel( QPainter *painter, double value ) const = 0; + + void invalidateCache(); + const QwtText &tickLabel( const QFont &, double value ) const; + +private: + QwtAbstractScaleDraw( const QwtAbstractScaleDraw & ); + QwtAbstractScaleDraw &operator=( const QwtAbstractScaleDraw & ); + + class PrivateData; + PrivateData *d_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtAbstractScaleDraw::ScaleComponents ) + +#endif diff --git a/qwt/src/qwt_abstract_slider.cpp b/qwt/src/qwt_abstract_slider.cpp new file mode 100644 index 000000000..7bac22e61 --- /dev/null +++ b/qwt/src/qwt_abstract_slider.cpp @@ -0,0 +1,822 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_abstract_slider.h" +#include "qwt_abstract_scale_draw.h" +#include "qwt_math.h" +#include "qwt_scale_map.h" +#include + +#if QT_VERSION < 0x040601 +#define qFabs(x) ::fabs(x) +#endif + +static double qwtAlignToScaleDiv( + const QwtAbstractSlider *slider, double value ) +{ + const QwtScaleDiv &sd = slider->scaleDiv(); + + const int tValue = slider->transform( value ); + + if ( tValue == slider->transform( sd.lowerBound() ) ) + return sd.lowerBound(); + + if ( tValue == slider->transform( sd.lowerBound() ) ) + return sd.upperBound(); + + for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ ) + { + const QList ticks = sd.ticks( i ); + for ( int j = 0; j < ticks.size(); j++ ) + { + if ( slider->transform( ticks[ j ] ) == tValue ) + return ticks[ j ]; + } + } + + return value; +} + +class QwtAbstractSlider::PrivateData +{ +public: + PrivateData(): + isScrolling( false ), + isTracking( true ), + pendingValueChanged( false ), + readOnly( false ), + totalSteps( 100 ), + singleSteps( 1 ), + pageSteps( 10 ), + stepAlignment( true ), + isValid( false ), + value( 0.0 ), + wrapping( false ), + invertedControls( false ) + { + } + + bool isScrolling; + bool isTracking; + bool pendingValueChanged; + + bool readOnly; + + uint totalSteps; + uint singleSteps; + uint pageSteps; + bool stepAlignment; + + bool isValid; + double value; + + bool wrapping; + bool invertedControls; +}; + +/*! + \brief Constructor + + The scale is initialized to [0.0, 100.0], the + number of steps is set to 100 with 1 and 10 and single + an page step sizes. Step alignment is enabled. + + The initial value is invalid. + + \param parent Parent widget +*/ +QwtAbstractSlider::QwtAbstractSlider( QWidget *parent ): + QwtAbstractScale( parent ) +{ + d_data = new QwtAbstractSlider::PrivateData; + + setScale( 0.0, 100.0 ); + setFocusPolicy( Qt::StrongFocus ); +} + +//! Destructor +QwtAbstractSlider::~QwtAbstractSlider() +{ + delete d_data; +} + +/*! + Set the value to be valid/invalid + + \param on When true, the value is invalidated + + \sa setValue() +*/ +void QwtAbstractSlider::setValid( bool on ) +{ + if ( on != d_data->isValid ) + { + d_data->isValid = on; + sliderChange(); + + Q_EMIT valueChanged( d_data->value ); + } +} + +//! \return True, when the value is invalid +bool QwtAbstractSlider::isValid() const +{ + return d_data->isValid; +} + +/*! + En/Disable read only mode + + In read only mode the slider can't be controlled by mouse + or keyboard. + + \param on Enables in case of true + \sa isReadOnly() + + \warning The focus policy is set to Qt::StrongFocus or Qt::NoFocus +*/ +void QwtAbstractSlider::setReadOnly( bool on ) +{ + if ( d_data->readOnly != on ) + { + d_data->readOnly = on; + setFocusPolicy( on ? Qt::StrongFocus : Qt::NoFocus ); + + update(); + } +} + +/*! + In read only mode the slider can't be controlled by mouse + or keyboard. + + \return true if read only + \sa setReadOnly() +*/ +bool QwtAbstractSlider::isReadOnly() const +{ + return d_data->readOnly; +} + +/*! + \brief Enables or disables tracking. + + If tracking is enabled, the slider emits the valueChanged() + signal while the movable part of the slider is being dragged. + If tracking is disabled, the slider emits the valueChanged() signal + only when the user releases the slider. + + Tracking is enabled by default. + \param on \c true (enable) or \c false (disable) tracking. + + \sa isTracking(), sliderMoved() +*/ +void QwtAbstractSlider::setTracking( bool on ) +{ + d_data->isTracking = on; +} + +/*! + \return True, when tracking has been enabled + \sa setTracking() +*/ +bool QwtAbstractSlider::isTracking() const +{ + return d_data->isTracking; +} + +/*! + Mouse press event handler + \param event Mouse event +*/ +void QwtAbstractSlider::mousePressEvent( QMouseEvent *event ) +{ + if ( isReadOnly() ) + { + event->ignore(); + return; + } + + if ( !d_data->isValid || lowerBound() == upperBound() ) + return; + + d_data->isScrolling = isScrollPosition( event->pos() ); + + if ( d_data->isScrolling ) + { + d_data->pendingValueChanged = false; + + Q_EMIT sliderPressed(); + } +} + +/*! + Mouse Move Event handler + \param event Mouse event +*/ +void QwtAbstractSlider::mouseMoveEvent( QMouseEvent *event ) +{ + if ( isReadOnly() ) + { + event->ignore(); + return; + } + + if ( d_data->isValid && d_data->isScrolling ) + { + double value = scrolledTo( event->pos() ); + if ( value != d_data->value ) + { + value = boundedValue( value ); + + if ( d_data->stepAlignment ) + { + value = alignedValue( value ); + } + else + { + value = qwtAlignToScaleDiv( this, value ); + } + + if ( value != d_data->value ) + { + d_data->value = value; + + sliderChange(); + + Q_EMIT sliderMoved( d_data->value ); + + if ( d_data->isTracking ) + Q_EMIT valueChanged( d_data->value ); + else + d_data->pendingValueChanged = true; + } + } + } +} + +/*! + Mouse Release Event handler + \param event Mouse event +*/ +void QwtAbstractSlider::mouseReleaseEvent( QMouseEvent *event ) +{ + if ( isReadOnly() ) + { + event->ignore(); + return; + } + + if ( d_data->isScrolling && d_data->isValid ) + { + d_data->isScrolling = false; + + if ( d_data->pendingValueChanged ) + Q_EMIT valueChanged( d_data->value ); + + Q_EMIT sliderReleased(); + } +} + +/*! + Wheel Event handler + + In/decreases the value by s number of steps. The direction + depends on the invertedControls() property. + + When the control or shift modifier is pressed the wheel delta + ( divided by 120 ) is mapped to an increment according to + pageSteps(). Otherwise it is mapped to singleSteps(). + + \param event Wheel event +*/ +void QwtAbstractSlider::wheelEvent( QWheelEvent *event ) +{ + if ( isReadOnly() ) + { + event->ignore(); + return; + } + + if ( !d_data->isValid || d_data->isScrolling ) + return; + + int numSteps = 0; + + if ( ( event->modifiers() & Qt::ControlModifier) || + ( event->modifiers() & Qt::ShiftModifier ) ) + { + // one page regardless of delta + numSteps = d_data->pageSteps; + if ( event->delta() < 0 ) + numSteps = -numSteps; + } + else + { + const int numTurns = ( event->delta() / 120 ); + numSteps = numTurns * d_data->singleSteps; + } + + if ( d_data->invertedControls ) + numSteps = -numSteps; + + const double value = incrementedValue( d_data->value, numSteps ); + if ( value != d_data->value ) + { + d_data->value = value; + sliderChange(); + + Q_EMIT sliderMoved( d_data->value ); + Q_EMIT valueChanged( d_data->value ); + } +} + +/*! + Handles key events + + QwtAbstractSlider handles the following keys: + + - Qt::Key_Left\n + Add/Subtract singleSteps() in direction to lowerBound(); + - Qt::Key_Right\n + Add/Subtract singleSteps() in direction to upperBound(); + - Qt::Key_Down\n + Subtract singleSteps(), when invertedControls() is false + - Qt::Key_Up\n + Add singleSteps(), when invertedControls() is false + - Qt::Key_PageDown\n + Subtract pageSteps(), when invertedControls() is false + - Qt::Key_PageUp\n + Add pageSteps(), when invertedControls() is false + - Qt::Key_Home\n + Set the value to the minimum() + - Qt::Key_End\n + Set the value to the maximum() + + \param event Key event + \sa isReadOnly() +*/ +void QwtAbstractSlider::keyPressEvent( QKeyEvent *event ) +{ + if ( isReadOnly() ) + { + event->ignore(); + return; + } + + if ( !d_data->isValid || d_data->isScrolling ) + return; + + int numSteps = 0; + double value = d_data->value; + + switch ( event->key() ) + { + case Qt::Key_Left: + { + numSteps = -static_cast( d_data->singleSteps ); + if ( isInverted() ) + numSteps = -numSteps; + + break; + } + case Qt::Key_Right: + { + numSteps = d_data->singleSteps; + if ( isInverted() ) + numSteps = -numSteps; + + break; + } + case Qt::Key_Down: + { + numSteps = -static_cast( d_data->singleSteps ); + if ( d_data->invertedControls ) + numSteps = -numSteps; + break; + } + case Qt::Key_Up: + { + numSteps = d_data->singleSteps; + if ( d_data->invertedControls ) + numSteps = -numSteps; + + break; + } + case Qt::Key_PageUp: + { + numSteps = d_data->pageSteps; + if ( d_data->invertedControls ) + numSteps = -numSteps; + break; + } + case Qt::Key_PageDown: + { + numSteps = -static_cast( d_data->pageSteps ); + if ( d_data->invertedControls ) + numSteps = -numSteps; + break; + } + case Qt::Key_Home: + { + value = minimum(); + break; + } + case Qt::Key_End: + { + value = maximum(); + break; + } + default:; + { + event->ignore(); + } + } + + if ( numSteps != 0 ) + { + value = incrementedValue( d_data->value, numSteps ); + } + + if ( value != d_data->value ) + { + d_data->value = value; + sliderChange(); + + Q_EMIT sliderMoved( d_data->value ); + Q_EMIT valueChanged( d_data->value ); + } +} + +/*! + \brief Set the number of steps + + The range of the slider is divided into a number of steps from + which the value increments according to user inputs depend. + + The default setting is 100. + + \param stepCount Number of steps + + \sa totalSteps(), setSingleSteps(), setPageSteps() + */ +void QwtAbstractSlider::setTotalSteps( uint stepCount ) +{ + d_data->totalSteps = stepCount; +} + +/*! + \return Number of steps + \sa setTotalSteps(), singleSteps(), pageSteps() + */ +uint QwtAbstractSlider::totalSteps() const +{ + return d_data->totalSteps; +} + +/*! + \brief Set the number of steps for a single increment + + The range of the slider is divided into a number of steps from + which the value increments according to user inputs depend. + + \param stepCount Number of steps + + \sa singleSteps(), setTotalSteps(), setPageSteps() + */ + +void QwtAbstractSlider::setSingleSteps( uint stepCount ) +{ + d_data->singleSteps = stepCount; +} + +/*! + \return Number of steps + \sa setSingleSteps(), totalSteps(), pageSteps() + */ +uint QwtAbstractSlider::singleSteps() const +{ + return d_data->singleSteps; +} + +/*! + \brief Set the number of steps for a page increment + + The range of the slider is divided into a number of steps from + which the value increments according to user inputs depend. + + \param stepCount Number of steps + + \sa pageSteps(), setTotalSteps(), setSingleSteps() + */ + +void QwtAbstractSlider::setPageSteps( uint stepCount ) +{ + d_data->pageSteps = stepCount; +} + +/*! + \return Number of steps + \sa setPageSteps(), totalSteps(), singleSteps() + */ +uint QwtAbstractSlider::pageSteps() const +{ + return d_data->pageSteps; +} + +/*! + \brief Enable step alignment + + When step alignment is enabled values resulting from slider + movements are aligned to the step size. + + \param on Enable step alignment when true + \sa stepAlignment() +*/ +void QwtAbstractSlider::setStepAlignment( bool on ) +{ + if ( on != d_data->stepAlignment ) + { + d_data->stepAlignment = on; + } +} + +/*! + \return True, when step alignment is enabled + \sa setStepAlignment() + */ +bool QwtAbstractSlider::stepAlignment() const +{ + return d_data->stepAlignment; +} + +/*! + Set the slider to the specified value + + \param value New value + \sa setValid(), sliderChange(), valueChanged() +*/ +void QwtAbstractSlider::setValue( double value ) +{ + value = qBound( minimum(), value, maximum() ); + + const bool changed = ( d_data->value != value ) || !d_data->isValid; + + d_data->value = value; + d_data->isValid = true; + + if ( changed ) + { + sliderChange(); + Q_EMIT valueChanged( d_data->value ); + } +} + +//! Returns the current value. +double QwtAbstractSlider::value() const +{ + return d_data->value; +} + +/*! + If wrapping is true stepping up from upperBound() value will + take you to the minimum() value and vice versa. + + \param on En/Disable wrapping + \sa wrapping() +*/ +void QwtAbstractSlider::setWrapping( bool on ) +{ + d_data->wrapping = on; +} + +/*! + \return True, when wrapping is set + \sa setWrapping() + */ +bool QwtAbstractSlider::wrapping() const +{ + return d_data->wrapping; +} + +/*! + Invert wheel and key events + + Usually scrolling the mouse wheel "up" and using keys like page + up will increase the slider's value towards its maximum. + When invertedControls() is enabled the value is scrolled + towards its minimum. + + Inverting the controls might be f.e. useful for a vertical slider + with an inverted scale ( decreasing from top to bottom ). + + \param on Invert controls, when true + + \sa invertedControls(), keyEvent(), wheelEvent() + */ +void QwtAbstractSlider::setInvertedControls( bool on ) +{ + d_data->invertedControls = on; +} + +/*! + \return True, when the controls are inverted + \sa setInvertedControls() + */ +bool QwtAbstractSlider::invertedControls() const +{ + return d_data->invertedControls; +} + +/*! + Increment the slider + + The step size depends on the number of totalSteps() + + \param stepCount Number of steps + \sa setTotalSteps(), incrementedValue() + */ +void QwtAbstractSlider::incrementValue( int stepCount ) +{ + const double value = incrementedValue( + d_data->value, stepCount ); + + if ( value != d_data->value ) + { + d_data->value = value; + sliderChange(); + } +} + +/*! + Increment a value + + \param value Value + \param stepCount Number of steps + + \return Incremented value + */ +double QwtAbstractSlider::incrementedValue( + double value, int stepCount ) const +{ + if ( d_data->totalSteps == 0 ) + return value; + + const QwtTransform *transformation = + scaleMap().transformation(); + + if ( transformation == NULL ) + { + const double range = maximum() - minimum(); + value += stepCount * range / d_data->totalSteps; + } + else + { + QwtScaleMap map = scaleMap(); + map.setPaintInterval( 0, d_data->totalSteps ); + + // we need equidant steps according to + // paint device coordinates + const double range = transformation->transform( maximum() ) + - transformation->transform( minimum() ); + + const double stepSize = range / d_data->totalSteps; + + double v = transformation->transform( value ); + + v = qRound( v / stepSize ) * stepSize; + v += stepCount * range / d_data->totalSteps; + + value = transformation->invTransform( v ); + } + + value = boundedValue( value ); + + if ( d_data->stepAlignment ) + value = alignedValue( value ); + + return value; +} + +double QwtAbstractSlider::boundedValue( double value ) const +{ + const double vmin = minimum(); + const double vmax = maximum(); + + if ( d_data->wrapping && vmin != vmax ) + { + const int fullCircle = 360 * 16; + + const double pd = scaleMap().pDist(); + if ( int( pd / fullCircle ) * fullCircle == pd ) + { + // full circle scales: min and max are the same + const double range = vmax - vmin; + + if ( value < vmin ) + { + value += ::ceil( ( vmin - value ) / range ) * range; + } + else if ( value > vmax ) + { + value -= ::ceil( ( value - vmax ) / range ) * range; + } + } + else + { + if ( value < vmin ) + value = vmax; + else if ( value > vmax ) + value = vmin; + } + } + else + { + value = qBound( vmin, value, vmax ); + } + + return value; +} + +double QwtAbstractSlider::alignedValue( double value ) const +{ + if ( d_data->totalSteps == 0 ) + return value; + + double stepSize; + + if ( scaleMap().transformation() == NULL ) + { + stepSize = ( maximum() - minimum() ) / d_data->totalSteps; + if ( stepSize > 0.0 ) + { + value = lowerBound() + + qRound( ( value - lowerBound() ) / stepSize ) * stepSize; + } + } + else + { + stepSize = ( scaleMap().p2() - scaleMap().p1() ) / d_data->totalSteps; + + if ( stepSize > 0.0 ) + { + double v = scaleMap().transform( value ); + + v = scaleMap().p1() + + qRound( ( v - scaleMap().p1() ) / stepSize ) * stepSize; + + value = scaleMap().invTransform( v ); + } + } + + if ( qAbs( stepSize ) > 1e-12 ) + { + if ( qFuzzyCompare( value + 1.0, 1.0 ) ) + { + // correct rounding error if value = 0 + value = 0.0; + } + else + { + // correct rounding error at the border + if ( qFuzzyCompare( value, upperBound() ) ) + value = upperBound(); + else if ( qFuzzyCompare( value, lowerBound() ) ) + value = lowerBound(); + } + } + + return value; +} + +/*! + Update the slider according to modifications of the scale + */ +void QwtAbstractSlider::scaleChange() +{ + const double value = qBound( minimum(), d_data->value, maximum() ); + + const bool changed = ( value != d_data->value ); + if ( changed ) + { + d_data->value = value; + } + + if ( d_data->isValid || changed ) + Q_EMIT valueChanged( d_data->value ); + + updateGeometry(); + update(); +} + +//! Calling update() +void QwtAbstractSlider::sliderChange() +{ + update(); +} diff --git a/qwt/src/qwt_abstract_slider.h b/qwt/src/qwt_abstract_slider.h new file mode 100644 index 000000000..c91dcb604 --- /dev/null +++ b/qwt/src/qwt_abstract_slider.h @@ -0,0 +1,167 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_ABSTRACT_SLIDER_H +#define QWT_ABSTRACT_SLIDER_H + +#include "qwt_global.h" +#include "qwt_abstract_scale.h" + +/*! + \brief An abstract base class for slider widgets with a scale + + A slider widget displays a value according to a scale. + The class is designed as a common super class for widgets like + QwtKnob, QwtDial and QwtSlider. + + When the slider is nor readOnly() its value can be modified + by keyboard, mouse and wheel inputs. + + The range of the slider is divided into a number of steps from + which the value increments according to user inputs depend. + Only for linear scales the number of steps correspond with + a fixed step size. +*/ + +class QWT_EXPORT QwtAbstractSlider: public QwtAbstractScale +{ + Q_OBJECT + + Q_PROPERTY( double value READ value WRITE setValue ) + + Q_PROPERTY( uint totalSteps READ totalSteps WRITE setTotalSteps ) + Q_PROPERTY( uint singleSteps READ singleSteps WRITE setSingleSteps ) + Q_PROPERTY( uint pageSteps READ pageSteps WRITE setPageSteps ) + Q_PROPERTY( bool stepAlignment READ stepAlignment WRITE setStepAlignment ) + + Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly ) + Q_PROPERTY( bool tracking READ isTracking WRITE setTracking ) + Q_PROPERTY( bool wrapping READ wrapping WRITE setWrapping ) + + Q_PROPERTY( bool invertedControls READ invertedControls WRITE setInvertedControls ) + +public: + explicit QwtAbstractSlider( QWidget *parent = NULL ); + virtual ~QwtAbstractSlider(); + + void setValid( bool ); + bool isValid() const; + + double value() const; + + void setWrapping( bool ); + bool wrapping() const; + + void setTotalSteps( uint ); + uint totalSteps() const; + + void setSingleSteps( uint ); + uint singleSteps() const; + + void setPageSteps( uint ); + uint pageSteps() const; + + void setStepAlignment( bool ); + bool stepAlignment() const; + + void setTracking( bool ); + bool isTracking() const; + + void setReadOnly( bool ); + bool isReadOnly() const; + + void setInvertedControls( bool ); + bool invertedControls() const; + +public Q_SLOTS: + void setValue( double val ); + +Q_SIGNALS: + + /*! + \brief Notify a change of value. + + When tracking is enabled (default setting), + this signal will be emitted every time the value changes. + + \param value New value + + \sa setTracking(), sliderMoved() + */ + void valueChanged( double value ); + + /*! + This signal is emitted when the user presses the + movable part of the slider. + */ + void sliderPressed(); + + /*! + This signal is emitted when the user releases the + movable part of the slider. + */ + void sliderReleased(); + + /*! + This signal is emitted when the user moves the + slider with the mouse. + + \param value New value + + \sa valueChanged() + */ + void sliderMoved( double value ); + +protected: + virtual void mousePressEvent( QMouseEvent * ); + virtual void mouseReleaseEvent( QMouseEvent * ); + virtual void mouseMoveEvent( QMouseEvent * ); + virtual void keyPressEvent( QKeyEvent * ); + virtual void wheelEvent( QWheelEvent * ); + + /*! + \brief Determine what to do when the user presses a mouse button. + + \param pos Mouse position + + \retval True, when pos is a valid scroll position + \sa scrolledTo() + */ + virtual bool isScrollPosition( const QPoint &pos ) const = 0; + + /*! + \brief Determine the value for a new position of the + movable part of the slider + + \param pos Mouse position + + \return Value for the mouse position + \sa isScrollPosition() + */ + virtual double scrolledTo( const QPoint &pos ) const = 0; + + void incrementValue( int numSteps ); + + virtual void scaleChange(); + +protected: + virtual void sliderChange(); + + double incrementedValue( + double value, int stepCount ) const; + +private: + double alignedValue( double ) const; + double boundedValue( double ) const; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_analog_clock.cpp b/qwt/src/qwt_analog_clock.cpp new file mode 100644 index 000000000..1d44a95cb --- /dev/null +++ b/qwt/src/qwt_analog_clock.cpp @@ -0,0 +1,244 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_analog_clock.h" +#include "qwt_round_scale_draw.h" +#include +#include + +class QwtAnalogClockScaleDraw: public QwtRoundScaleDraw +{ +public: + QwtAnalogClockScaleDraw() + { + setSpacing( 8 ); + + enableComponent( QwtAbstractScaleDraw::Backbone, false ); + + setTickLength( QwtScaleDiv::MinorTick, 2 ); + setTickLength( QwtScaleDiv::MediumTick, 4 ); + setTickLength( QwtScaleDiv::MajorTick, 8 ); + + setPenWidth( 1 ); + } + + virtual QwtText label( double value ) const + { + if ( qFuzzyCompare( value + 1.0, 1.0 ) ) + value = 60.0 * 60.0 * 12.0; + + return QLocale().toString( qRound( value / ( 60.0 * 60.0 ) ) ); + } +}; + +/*! + Constructor + \param parent Parent widget +*/ +QwtAnalogClock::QwtAnalogClock( QWidget *parent ): + QwtDial( parent ) +{ + setWrapping( true ); + setReadOnly( true ); + + setOrigin( 270.0 ); + setScaleDraw( new QwtAnalogClockScaleDraw() ); + + setTotalSteps( 60 ); + + const int secondsPerHour = 60.0 * 60.0; + + QList majorTicks; + QList minorTicks; + + for ( int i = 0; i < 12; i++ ) + { + majorTicks += i * secondsPerHour; + + for ( int j = 1; j < 5; j++ ) + minorTicks += i * secondsPerHour + j * secondsPerHour / 5.0; + } + + QwtScaleDiv scaleDiv; + scaleDiv.setInterval( 0.0, 12.0 * secondsPerHour ); + scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks ); + scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks ); + setScale( scaleDiv ); + + QColor knobColor = palette().color( QPalette::Active, QPalette::Text ); + knobColor = knobColor.dark( 120 ); + + QColor handColor; + int width; + + for ( int i = 0; i < NHands; i++ ) + { + if ( i == SecondHand ) + { + width = 2; + handColor = knobColor.dark( 120 ); + } + else + { + width = 8; + handColor = knobColor; + } + + QwtDialSimpleNeedle *hand = new QwtDialSimpleNeedle( + QwtDialSimpleNeedle::Arrow, true, handColor, knobColor ); + hand->setWidth( width ); + + d_hand[i] = NULL; + setHand( static_cast( i ), hand ); + } +} + +//! Destructor +QwtAnalogClock::~QwtAnalogClock() +{ + for ( int i = 0; i < NHands; i++ ) + delete d_hand[i]; +} + +/*! + Nop method, use setHand() instead + \sa setHand() +*/ +void QwtAnalogClock::setNeedle( QwtDialNeedle * ) +{ + // no op + return; +} + +/*! + Set a clock hand + \param hand Specifies the type of hand + \param needle Hand + \sa hand() +*/ +void QwtAnalogClock::setHand( Hand hand, QwtDialNeedle *needle ) +{ + if ( hand >= 0 && hand < NHands ) + { + delete d_hand[hand]; + d_hand[hand] = needle; + } +} + +/*! + \return Clock hand + \param hd Specifies the type of hand + \sa setHand() +*/ +QwtDialNeedle *QwtAnalogClock::hand( Hand hd ) +{ + if ( hd < 0 || hd >= NHands ) + return NULL; + + return d_hand[hd]; +} + +/*! + \return Clock hand + \param hd Specifies the type of hand + \sa setHand() +*/ +const QwtDialNeedle *QwtAnalogClock::hand( Hand hd ) const +{ + return const_cast( this )->hand( hd ); +} + +/*! + \brief Set the current time +*/ +void QwtAnalogClock::setCurrentTime() +{ + setTime( QTime::currentTime() ); +} + +/*! + Set a time + \param time Time to display +*/ +void QwtAnalogClock::setTime( const QTime &time ) +{ + if ( time.isValid() ) + { + setValue( ( time.hour() % 12 ) * 60.0 * 60.0 + + time.minute() * 60.0 + time.second() ); + } + else + setValid( false ); +} + +/*! + \brief Draw the needle + + A clock has no single needle but three hands instead. drawNeedle() + translates value() into directions for the hands and calls + drawHand(). + + \param painter Painter + \param center Center of the clock + \param radius Maximum length for the hands + \param dir Dummy, not used. + \param colorGroup ColorGroup + + \sa drawHand() +*/ +void QwtAnalogClock::drawNeedle( QPainter *painter, const QPointF ¢er, + double radius, double dir, QPalette::ColorGroup colorGroup ) const +{ + Q_UNUSED( dir ); + + if ( isValid() ) + { + const double hours = value() / ( 60.0 * 60.0 ); + const double minutes = + ( value() - qFloor(hours) * 60.0 * 60.0 ) / 60.0; + const double seconds = value() - qFloor(hours) * 60.0 * 60.0 + - qFloor(minutes) * 60.0; + + double angle[NHands]; + angle[HourHand] = 360.0 * hours / 12.0; + angle[MinuteHand] = 360.0 * minutes / 60.0; + angle[SecondHand] = 360.0 * seconds / 60.0; + + for ( int hand = 0; hand < NHands; hand++ ) + { + const double d = 360.0 - angle[hand] - origin(); + drawHand( painter, static_cast( hand ), + center, radius, d, colorGroup ); + } + } +} + +/*! + Draw a clock hand + + \param painter Painter + \param hd Specify the type of hand + \param center Center of the clock + \param radius Maximum length for the hands + \param direction Direction of the hand in degrees, counter clockwise + \param cg ColorGroup +*/ +void QwtAnalogClock::drawHand( QPainter *painter, Hand hd, + const QPointF ¢er, double radius, double direction, + QPalette::ColorGroup cg ) const +{ + const QwtDialNeedle *needle = hand( hd ); + if ( needle ) + { + if ( hd == HourHand ) + radius = qRound( 0.8 * radius ); + + needle->draw( painter, center, radius, direction, cg ); + } +} diff --git a/qwt/src/qwt_analog_clock.h b/qwt/src/qwt_analog_clock.h new file mode 100644 index 000000000..ffe27e2cd --- /dev/null +++ b/qwt/src/qwt_analog_clock.h @@ -0,0 +1,93 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_ANALOG_CLOCK_H +#define QWT_ANALOG_CLOCK_H + +#include "qwt_global.h" +#include "qwt_dial.h" +#include "qwt_dial_needle.h" +#include + +/*! + \brief An analog clock + + \image html analogclock.png + + \par Example + \code + #include + + QwtAnalogClock *clock = new QwtAnalogClock(...); + clock->scaleDraw()->setPenWidth(3); + clock->setLineWidth(6); + clock->setFrameShadow(QwtDial::Sunken); + clock->setTime(); + + // update the clock every second + QTimer *timer = new QTimer(clock); + timer->connect(timer, SIGNAL(timeout()), clock, SLOT(setCurrentTime())); + timer->start(1000); + + \endcode + + \note The examples/dials example shows how to use QwtAnalogClock. +*/ + +class QWT_EXPORT QwtAnalogClock: public QwtDial +{ + Q_OBJECT + +public: + /*! + Hand type + \sa setHand(), hand() + */ + enum Hand + { + //! Needle displaying the seconds + SecondHand, + + //! Needle displaying the minutes + MinuteHand, + + //! Needle displaying the hours + HourHand, + + //! Number of needles + NHands + }; + + explicit QwtAnalogClock( QWidget* parent = NULL ); + virtual ~QwtAnalogClock(); + + void setHand( Hand, QwtDialNeedle * ); + + const QwtDialNeedle *hand( Hand ) const; + QwtDialNeedle *hand( Hand ); + +public Q_SLOTS: + void setCurrentTime(); + void setTime( const QTime & ); + +protected: + virtual void drawNeedle( QPainter *, const QPointF &, + double radius, double direction, QPalette::ColorGroup ) const; + + virtual void drawHand( QPainter *, Hand, const QPointF &, + double radius, double direction, QPalette::ColorGroup ) const; + +private: + // use setHand instead + void setNeedle( QwtDialNeedle * ); + + QwtDialNeedle *d_hand[NHands]; +}; + +#endif diff --git a/qwt/src/qwt_arrow_button.cpp b/qwt/src/qwt_arrow_button.cpp new file mode 100644 index 000000000..bd20f91e9 --- /dev/null +++ b/qwt/src/qwt_arrow_button.cpp @@ -0,0 +1,333 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_arrow_button.h" +#include "qwt_math.h" +#include +#include +#include +#include +#include + +static const int MaxNum = 3; +static const int Margin = 2; +static const int Spacing = 1; + +class QwtArrowButton::PrivateData +{ +public: + int num; + Qt::ArrowType arrowType; +}; + +static QStyleOptionButton styleOpt( const QwtArrowButton* btn ) +{ + QStyleOptionButton option; + option.init( btn ); + option.features = QStyleOptionButton::None; + if ( btn->isFlat() ) + option.features |= QStyleOptionButton::Flat; + if ( btn->menu() ) + option.features |= QStyleOptionButton::HasMenu; + if ( btn->autoDefault() || btn->isDefault() ) + option.features |= QStyleOptionButton::AutoDefaultButton; + if ( btn->isDefault() ) + option.features |= QStyleOptionButton::DefaultButton; + if ( btn->isDown() ) + option.state |= QStyle::State_Sunken; + if ( !btn->isFlat() && !btn->isDown() ) + option.state |= QStyle::State_Raised; + + return option; +} + +/*! + \param num Number of arrows + \param arrowType see Qt::ArrowType in the Qt docs. + \param parent Parent widget +*/ +QwtArrowButton::QwtArrowButton( int num, + Qt::ArrowType arrowType, QWidget *parent ): + QPushButton( parent ) +{ + d_data = new PrivateData; + d_data->num = qBound( 1, num, MaxNum ); + d_data->arrowType = arrowType; + + setAutoRepeat( true ); + setAutoDefault( false ); + + switch ( d_data->arrowType ) + { + case Qt::LeftArrow: + case Qt::RightArrow: + setSizePolicy( QSizePolicy::Expanding, + QSizePolicy::Fixed ); + break; + default: + setSizePolicy( QSizePolicy::Fixed, + QSizePolicy::Expanding ); + } +} + +//! Destructor +QwtArrowButton::~QwtArrowButton() +{ + delete d_data; + d_data = NULL; +} + +/*! + \brief The direction of the arrows +*/ +Qt::ArrowType QwtArrowButton::arrowType() const +{ + return d_data->arrowType; +} + +/*! + \brief The number of arrows +*/ +int QwtArrowButton::num() const +{ + return d_data->num; +} + +/*! + \return the bounding rectangle for the label +*/ +QRect QwtArrowButton::labelRect() const +{ + const int m = Margin; + + QRect r = rect(); + r.setRect( r.x() + m, r.y() + m, + r.width() - 2 * m, r.height() - 2 * m ); + + if ( isDown() ) + { + QStyleOptionButton option = styleOpt( this ); + const int ph = style()->pixelMetric( + QStyle::PM_ButtonShiftHorizontal, &option, this ); + const int pv = style()->pixelMetric( + QStyle::PM_ButtonShiftVertical, &option, this ); + + r.translate( ph, pv ); + } + + return r; +} + +/*! + Paint event handler + \param event Paint event +*/ +void QwtArrowButton::paintEvent( QPaintEvent *event ) +{ + QPushButton::paintEvent( event ); + QPainter painter( this ); + drawButtonLabel( &painter ); +} + +/*! + \brief Draw the button label + + \param painter Painter + \sa The Qt Manual for QPushButton +*/ +void QwtArrowButton::drawButtonLabel( QPainter *painter ) +{ + const bool isVertical = d_data->arrowType == Qt::UpArrow || + d_data->arrowType == Qt::DownArrow; + + const QRect r = labelRect(); + QSize boundingSize = labelRect().size(); + if ( isVertical ) + boundingSize.transpose(); + + const int w = + ( boundingSize.width() - ( MaxNum - 1 ) * Spacing ) / MaxNum; + + QSize arrow = arrowSize( Qt::RightArrow, + QSize( w, boundingSize.height() ) ); + + if ( isVertical ) + arrow.transpose(); + + QRect contentsSize; // aligned rect where to paint all arrows + if ( d_data->arrowType == Qt::LeftArrow || d_data->arrowType == Qt::RightArrow ) + { + contentsSize.setWidth( d_data->num * arrow.width() + + ( d_data->num - 1 ) * Spacing ); + contentsSize.setHeight( arrow.height() ); + } + else + { + contentsSize.setWidth( arrow.width() ); + contentsSize.setHeight( d_data->num * arrow.height() + + ( d_data->num - 1 ) * Spacing ); + } + + QRect arrowRect( contentsSize ); + arrowRect.moveCenter( r.center() ); + arrowRect.setSize( arrow ); + + painter->save(); + for ( int i = 0; i < d_data->num; i++ ) + { + drawArrow( painter, arrowRect, d_data->arrowType ); + + int dx = 0; + int dy = 0; + + if ( isVertical ) + dy = arrow.height() + Spacing; + else + dx = arrow.width() + Spacing; + + arrowRect.translate( dx, dy ); + } + painter->restore(); + + if ( hasFocus() ) + { + QStyleOptionFocusRect option; + option.init( this ); + option.backgroundColor = palette().color( QPalette::Window ); + + style()->drawPrimitive( QStyle::PE_FrameFocusRect, + &option, painter, this ); + } +} + +/*! + Draw an arrow int a bounding rectangle + + \param painter Painter + \param r Rectangle where to paint the arrow + \param arrowType Arrow type +*/ +void QwtArrowButton::drawArrow( QPainter *painter, + const QRect &r, Qt::ArrowType arrowType ) const +{ + QPolygon pa( 3 ); + + switch ( arrowType ) + { + case Qt::UpArrow: + pa.setPoint( 0, r.bottomLeft() ); + pa.setPoint( 1, r.bottomRight() ); + pa.setPoint( 2, r.center().x(), r.top() ); + break; + case Qt::DownArrow: + pa.setPoint( 0, r.topLeft() ); + pa.setPoint( 1, r.topRight() ); + pa.setPoint( 2, r.center().x(), r.bottom() ); + break; + case Qt::RightArrow: + pa.setPoint( 0, r.topLeft() ); + pa.setPoint( 1, r.bottomLeft() ); + pa.setPoint( 2, r.right(), r.center().y() ); + break; + case Qt::LeftArrow: + pa.setPoint( 0, r.topRight() ); + pa.setPoint( 1, r.bottomRight() ); + pa.setPoint( 2, r.left(), r.center().y() ); + break; + default: + break; + } + + painter->save(); + + painter->setRenderHint( QPainter::Antialiasing, true ); + painter->setPen( Qt::NoPen ); + painter->setBrush( palette().brush( QPalette::ButtonText ) ); + painter->drawPolygon( pa ); + + painter->restore(); +} + +/*! + \return a size hint +*/ +QSize QwtArrowButton::sizeHint() const +{ + const QSize hint = minimumSizeHint(); + return hint.expandedTo( QApplication::globalStrut() ); +} + +/*! + \brief Return a minimum size hint +*/ +QSize QwtArrowButton::minimumSizeHint() const +{ + const QSize asz = arrowSize( Qt::RightArrow, QSize() ); + + QSize sz( + 2 * Margin + ( MaxNum - 1 ) * Spacing + MaxNum * asz.width(), + 2 * Margin + asz.height() + ); + + if ( d_data->arrowType == Qt::UpArrow || d_data->arrowType == Qt::DownArrow ) + sz.transpose(); + + QStyleOption styleOption; + styleOption.init( this ); + + sz = style()->sizeFromContents( QStyle::CT_PushButton, + &styleOption, sz, this ); + + return sz; +} + +/*! + Calculate the size for a arrow that fits into a rectangle of a given size + + \param arrowType Arrow type + \param boundingSize Bounding size + \return Size of the arrow +*/ +QSize QwtArrowButton::arrowSize( Qt::ArrowType arrowType, + const QSize &boundingSize ) const +{ + QSize bs = boundingSize; + if ( arrowType == Qt::UpArrow || arrowType == Qt::DownArrow ) + bs.transpose(); + + const int MinLen = 2; + const QSize sz = bs.expandedTo( + QSize( MinLen, 2 * MinLen - 1 ) ); // minimum + + int w = sz.width(); + int h = 2 * w - 1; + + if ( h > sz.height() ) + { + h = sz.height(); + w = ( h + 1 ) / 2; + } + + QSize arrSize( w, h ); + if ( arrowType == Qt::UpArrow || arrowType == Qt::DownArrow ) + arrSize.transpose(); + + return arrSize; +} + +/*! + \brief autoRepeat for the space keys +*/ +void QwtArrowButton::keyPressEvent( QKeyEvent *event ) +{ + if ( event->isAutoRepeat() && event->key() == Qt::Key_Space ) + Q_EMIT clicked(); + + QPushButton::keyPressEvent( event ); +} diff --git a/qwt/src/qwt_arrow_button.h b/qwt/src/qwt_arrow_button.h new file mode 100644 index 000000000..ae436fec0 --- /dev/null +++ b/qwt/src/qwt_arrow_button.h @@ -0,0 +1,52 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_ARROW_BUTTON_H +#define QWT_ARROW_BUTTON_H + +#include "qwt_global.h" +#include + +/*! + \brief Arrow Button + + A push button with one or more filled triangles on its front. + An Arrow button can have 1 to 3 arrows in a row, pointing + up, down, left or right. +*/ +class QWT_EXPORT QwtArrowButton : public QPushButton +{ +public: + explicit QwtArrowButton ( int num, Qt::ArrowType, QWidget *parent = NULL ); + virtual ~QwtArrowButton(); + + Qt::ArrowType arrowType() const; + int num() const; + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + +protected: + virtual void paintEvent( QPaintEvent *event ); + + virtual void drawButtonLabel( QPainter *p ); + virtual void drawArrow( QPainter *, + const QRect &, Qt::ArrowType ) const; + virtual QRect labelRect() const; + virtual QSize arrowSize( Qt::ArrowType, + const QSize &boundingSize ) const; + + virtual void keyPressEvent( QKeyEvent * ); + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_axes_mask.cpp b/qwt/src/qwt_axes_mask.cpp new file mode 100644 index 000000000..46b038e5b --- /dev/null +++ b/qwt/src/qwt_axes_mask.cpp @@ -0,0 +1,82 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot.h" +#include "qwt_axes_mask.h" +#include +#include + +class QwtAxesMask::PrivateData +{ +public: + QList disabledAxes[ QwtAxis::PosCount ]; +}; + +QwtAxesMask::QwtAxesMask() +{ + d_data = new PrivateData(); +} + +QwtAxesMask::~QwtAxesMask() +{ + delete d_data; +} + +/*! + \brief En/Disable an axis + + Only Axes that are enabled will be zoomed. + All other axes will remain unchanged. + + \param axisId Axis id + \param on On/Off + + \sa isAxisEnabled() +*/ +void QwtAxesMask::setEnabled( QwtAxisId axisId, bool on ) +{ + if ( !QwtAxis::isValid( axisId.pos ) ) + return; + + QList &axes = d_data->disabledAxes[ axisId.pos ]; + + QList::iterator it = qLowerBound( axes.begin(), axes.end(), axisId.id ); + + const bool isEnabled = ( it != axes.end() ) && ( *it != axisId.id ); + + if ( on ) + { + if ( !isEnabled ) + axes.insert( it, axisId.id ); + } + else + { + if ( isEnabled ) + axes.erase( it ); + } +} + +/*! + Test if an axis is enabled + + \param axisId Axis id + \return True, if the axis is enabled + + \sa setAxisEnabled() +*/ +bool QwtAxesMask::isEnabled( QwtAxisId axisId ) const +{ + if ( QwtAxis::isValid( axisId.pos ) ) + { + const QList &axes = d_data->disabledAxes[ axisId.pos ]; + return qFind( axes, axisId.id ) != axes.end(); + } + + return true; +} diff --git a/qwt/src/qwt_axes_mask.h b/qwt/src/qwt_axes_mask.h new file mode 100644 index 000000000..2df9fd072 --- /dev/null +++ b/qwt/src/qwt_axes_mask.h @@ -0,0 +1,30 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_AXES_MASK_H +#define QWT_AXES_MASK_H 1 + +#include "qwt_global.h" +#include "qwt_axis_id.h" + +class QWT_EXPORT QwtAxesMask +{ +public: + QwtAxesMask(); + ~QwtAxesMask(); + + void setEnabled( QwtAxisId, bool ); + bool isEnabled( QwtAxisId ) const; + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_axis_id.cpp b/qwt/src/qwt_axis_id.cpp new file mode 100644 index 000000000..3e3cbf614 --- /dev/null +++ b/qwt/src/qwt_axis_id.cpp @@ -0,0 +1,32 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_axis_id.h" + +#ifndef QT_NO_DEBUG_STREAM + +QDebug operator<<( QDebug debug, const QwtAxisId &axisId ) +{ + static const char *posNames[] = { "yLeft", "yRight", "xBottom", "xTop" }; + + debug.nospace(); + + debug << "QwtAxisId("; + + if ( axisId.pos >= 0 && axisId.pos < 4 ) + debug << posNames[axisId.pos]; + else + debug << axisId.pos; + + debug << "," << axisId.id << ")"; + + return debug.space(); +} + +#endif diff --git a/qwt/src/qwt_axis_id.h b/qwt/src/qwt_axis_id.h new file mode 100644 index 000000000..1a779f78e --- /dev/null +++ b/qwt/src/qwt_axis_id.h @@ -0,0 +1,106 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_AXIS_ID_H +#define QWT_AXIS_ID_H + +#include "qwt_global.h" + +#ifndef QT_NO_DEBUG_STREAM +#include +#endif + +namespace QwtAxis +{ + //! \brief Axis position + enum Position + { + //! Y axis left of the canvas + yLeft, + + //! Y axis right of the canvas + yRight, + + //! X axis below the canvas + xBottom, + + //! X axis above the canvas + xTop + }; + + //! \brief Number of axis positions + enum { PosCount = xTop + 1 }; + + bool isValid( int axisPos ); + bool isYAxis( int axisPos ); + bool isXAxis( int axisPos ); +}; + +inline bool QwtAxis::isValid( int axisPos ) +{ + return ( axisPos >= 0 && axisPos < PosCount ); +} + +inline bool QwtAxis::isXAxis( int axisPos ) +{ + return ( axisPos == xBottom ) || ( axisPos == xTop ); +} + +inline bool QwtAxis::isYAxis( int axisPos ) +{ + return ( axisPos == yLeft ) || ( axisPos == yRight ); +} + +class QWT_EXPORT QwtAxisId +{ +public: + QwtAxisId( int position, int index = 0 ); + + bool operator==( const QwtAxisId & ) const; + bool operator!=( const QwtAxisId & ) const; + + bool isXAxis() const; + bool isYAxis() const; + +public: + int pos; + int id; +}; + +inline QwtAxisId::QwtAxisId( int position, int index ): + pos( position ), + id( index ) +{ +} + +inline bool QwtAxisId::operator==( const QwtAxisId &other ) const +{ + return ( pos == other.pos ) && ( id == other.id ); +} + +inline bool QwtAxisId::operator!=( const QwtAxisId &other ) const +{ + return !operator==( other ); +} + +inline bool QwtAxisId::isXAxis() const +{ + return QwtAxis::isXAxis( pos ); +} + +inline bool QwtAxisId::isYAxis() const +{ + return QwtAxis::isYAxis( pos ); +} + +#ifndef QT_NO_DEBUG_STREAM +QWT_EXPORT QDebug operator<<( QDebug, const QwtAxisId & ); +#endif + +#endif diff --git a/qwt/src/qwt_clipper.cpp b/qwt/src/qwt_clipper.cpp new file mode 100644 index 000000000..51614aa37 --- /dev/null +++ b/qwt/src/qwt_clipper.cpp @@ -0,0 +1,510 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_clipper.h" +#include "qwt_point_polar.h" +#include +#include +#include + +#if QT_VERSION < 0x040601 +#define qAtan(x) ::atan(x) +#endif + +namespace QwtClip +{ + // some templates used for inlining + template class LeftEdge; + template class RightEdge; + template class TopEdge; + template class BottomEdge; + + template class PointBuffer; +} + +template +class QwtClip::LeftEdge +{ +public: + inline LeftEdge( Value x1, Value, Value, Value ): + d_x1( x1 ) + { + } + + inline bool isInside( const Point &p ) const + { + return p.x() >= d_x1; + } + + inline Point intersection( const Point &p1, const Point &p2 ) const + { + double dy = ( p1.y() - p2.y() ) / double( p1.x() - p2.x() ); + return Point( d_x1, static_cast< Value >( p2.y() + ( d_x1 - p2.x() ) * dy ) ); + } +private: + const Value d_x1; +}; + +template +class QwtClip::RightEdge +{ +public: + inline RightEdge( Value, Value x2, Value, Value ): + d_x2( x2 ) + { + } + + inline bool isInside( const Point &p ) const + { + return p.x() <= d_x2; + } + + inline Point intersection( const Point &p1, const Point &p2 ) const + { + double dy = ( p1.y() - p2.y() ) / double( p1.x() - p2.x() ); + return Point( d_x2, static_cast( p2.y() + ( d_x2 - p2.x() ) * dy ) ); + } + +private: + const Value d_x2; +}; + +template +class QwtClip::TopEdge +{ +public: + inline TopEdge( Value, Value, Value y1, Value ): + d_y1( y1 ) + { + } + + inline bool isInside( const Point &p ) const + { + return p.y() >= d_y1; + } + + inline Point intersection( const Point &p1, const Point &p2 ) const + { + double dx = ( p1.x() - p2.x() ) / double( p1.y() - p2.y() ); + return Point( static_cast( p2.x() + ( d_y1 - p2.y() ) * dx ), d_y1 ); + } + +private: + const Value d_y1; +}; + +template +class QwtClip::BottomEdge +{ +public: + inline BottomEdge( Value, Value, Value, Value y2 ): + d_y2( y2 ) + { + } + + inline bool isInside( const Point &p ) const + { + return p.y() <= d_y2; + } + + inline Point intersection( const Point &p1, const Point &p2 ) const + { + double dx = ( p1.x() - p2.x() ) / double( p1.y() - p2.y() ); + return Point( static_cast( p2.x() + ( d_y2 - p2.y() ) * dx ), d_y2 ); + } + +private: + const Value d_y2; +}; + +template +class QwtClip::PointBuffer +{ +public: + PointBuffer( int capacity = 0 ): + m_capacity( 0 ), + m_size( 0 ), + m_buffer( NULL ) + { + if ( capacity > 0 ) + reserve( capacity ); + } + + ~PointBuffer() + { + if ( m_buffer ) + ::free( m_buffer ); + } + + inline void setPoints( int numPoints, const Point *points ) + { + reserve( numPoints ); + + m_size = numPoints; + ::memcpy( m_buffer, points, m_size * sizeof( Point ) ); + } + + inline void reset() + { + m_size = 0; + } + + inline int size() const + { + return m_size; + } + + inline Point *data() const + { + return m_buffer; + } + + inline Point &operator[]( int i ) + { + return m_buffer[i]; + } + + inline const Point &operator[]( int i ) const + { + return m_buffer[i]; + } + + inline void add( const Point &point ) + { + if ( m_capacity <= m_size ) + reserve( m_size + 1 ); + + m_buffer[m_size++] = point; + } + +private: + inline void reserve( int size ) + { + if ( m_capacity == 0 ) + m_capacity = 1; + + while ( m_capacity < size ) + m_capacity *= 2; + + m_buffer = static_cast( + ::realloc( m_buffer, m_capacity * sizeof( Point ) ) ); + } + + int m_capacity; + int m_size; + Point *m_buffer; +}; + +using namespace QwtClip; + +template +class QwtPolygonClipper +{ +public: + QwtPolygonClipper( const Rect &clipRect ): + d_clipRect( clipRect ) + { + } + + Polygon clipPolygon( const Polygon &polygon, bool closePolygon ) const + { +#if 0 + if ( d_clipRect.contains( polygon.boundingRect() ) ) + return polygon; +#endif + + PointBuffer points1; + PointBuffer points2( qMin( 256, polygon.size() ) ); + + points1.setPoints( polygon.size(), polygon.data() ); + + clipEdge< LeftEdge >( closePolygon, points1, points2 ); + clipEdge< RightEdge >( closePolygon, points2, points1 ); + clipEdge< TopEdge >( closePolygon, points1, points2 ); + clipEdge< BottomEdge >( closePolygon, points2, points1 ); + + Polygon p; + p.resize( points1.size() ); + ::memcpy( p.data(), points1.data(), points1.size() * sizeof( Point ) ); + + return p; + } + +private: + template + inline void clipEdge( bool closePolygon, + PointBuffer &points, PointBuffer &clippedPoints ) const + { + clippedPoints.reset(); + + if ( points.size() < 2 ) + { + if ( points.size() == 1 ) + clippedPoints.add( points[0] ); + return; + } + + const Edge edge( d_clipRect.x(), d_clipRect.x() + d_clipRect.width(), + d_clipRect.y(), d_clipRect.y() + d_clipRect.height() ); + + int lastPos, start; + if ( closePolygon ) + { + start = 0; + lastPos = points.size() - 1; + } + else + { + start = 1; + lastPos = 0; + + if ( edge.isInside( points[0] ) ) + clippedPoints.add( points[0] ); + } + + const uint nPoints = points.size(); + for ( uint i = start; i < nPoints; i++ ) + { + const Point &p1 = points[i]; + const Point &p2 = points[lastPos]; + + if ( edge.isInside( p1 ) ) + { + if ( edge.isInside( p2 ) ) + { + clippedPoints.add( p1 ); + } + else + { + clippedPoints.add( edge.intersection( p1, p2 ) ); + clippedPoints.add( p1 ); + } + } + else + { + if ( edge.isInside( p2 ) ) + { + clippedPoints.add( edge.intersection( p1, p2 ) ); + } + } + lastPos = i; + } + } + + const Rect d_clipRect; +}; + +class QwtCircleClipper +{ +public: + QwtCircleClipper( const QRectF &r ); + QVector clipCircle( const QPointF &, double radius ) const; + +private: + enum Edge + { + Left, + Top, + Right, + Bottom, + + NEdges + }; + + QList cuttingPoints( + Edge, const QPointF &pos, double radius ) const; + + double toAngle( const QPointF &, const QPointF & ) const; + + const QRectF d_rect; +}; + + +QwtCircleClipper::QwtCircleClipper( const QRectF &r ): + d_rect( r ) +{ +} + +QVector QwtCircleClipper::clipCircle( + const QPointF &pos, double radius ) const +{ + QList points; + for ( int edge = 0; edge < NEdges; edge++ ) + points += cuttingPoints( static_cast(edge), pos, radius ); + + QVector intv; + if ( points.size() <= 0 ) + { + QRectF cRect( 0, 0, 2 * radius, 2 * radius ); + cRect.moveCenter( pos ); + if ( d_rect.contains( cRect ) ) + intv += QwtInterval( 0.0, 2 * M_PI ); + } + else + { + QList angles; + for ( int i = 0; i < points.size(); i++ ) + angles += toAngle( pos, points[i] ); + qSort( angles ); + + const int in = d_rect.contains( qwtPolar2Pos( pos, radius, + angles[0] + ( angles[1] - angles[0] ) / 2 ) ); + + if ( in ) + { + for ( int i = 0; i < angles.size() - 1; i += 2 ) + intv += QwtInterval( angles[i], angles[i+1] ); + } + else + { + for ( int i = 1; i < angles.size() - 1; i += 2 ) + intv += QwtInterval( angles[i], angles[i+1] ); + intv += QwtInterval( angles.last(), angles.first() ); + } + } + + return intv; +} + +double QwtCircleClipper::toAngle( + const QPointF &from, const QPointF &to ) const +{ + if ( from.x() == to.x() ) + return from.y() <= to.y() ? M_PI / 2.0 : 3 * M_PI / 2.0; + + const double m = qAbs( ( to.y() - from.y() ) / ( to.x() - from.x() ) ); + + double angle = qAtan( m ); + if ( to.x() > from.x() ) + { + if ( to.y() > from.y() ) + angle = 2 * M_PI - angle; + } + else + { + if ( to.y() > from.y() ) + angle = M_PI + angle; + else + angle = M_PI - angle; + } + + return angle; +} + +QList QwtCircleClipper::cuttingPoints( + Edge edge, const QPointF &pos, double radius ) const +{ + QList points; + + if ( edge == Left || edge == Right ) + { + const double x = ( edge == Left ) ? d_rect.left() : d_rect.right(); + if ( qAbs( pos.x() - x ) < radius ) + { + const double off = qSqrt( qwtSqr( radius ) - qwtSqr( pos.x() - x ) ); + const double m_y1 = pos.y() + off; + if ( m_y1 >= d_rect.top() && m_y1 <= d_rect.bottom() ) + points += QPointF( x, m_y1 ); + + const double m_y2 = pos.y() - off; + if ( m_y2 >= d_rect.top() && m_y2 <= d_rect.bottom() ) + points += QPointF( x, m_y2 ); + } + } + else + { + const double y = ( edge == Top ) ? d_rect.top() : d_rect.bottom(); + if ( qAbs( pos.y() - y ) < radius ) + { + const double off = qSqrt( qwtSqr( radius ) - qwtSqr( pos.y() - y ) ); + const double x1 = pos.x() + off; + if ( x1 >= d_rect.left() && x1 <= d_rect.right() ) + points += QPointF( x1, y ); + + const double m_x2 = pos.x() - off; + if ( m_x2 >= d_rect.left() && m_x2 <= d_rect.right() ) + points += QPointF( m_x2, y ); + } + } + return points; +} + +/*! + Sutherland-Hodgman polygon clipping + + \param clipRect Clip rectangle + \param polygon Polygon + \param closePolygon True, when the polygon is closed + + \return Clipped polygon +*/ +QPolygon QwtClipper::clipPolygon( + const QRectF &clipRect, const QPolygon &polygon, bool closePolygon ) +{ + const int minX = qCeil( clipRect.left() ); + const int maxX = qFloor( clipRect.right() ); + const int minY = qCeil( clipRect.top() ); + const int maxY = qFloor( clipRect.bottom() ); + + const QRect r( minX, minY, maxX - minX, maxY - minY ); + + QwtPolygonClipper clipper( r ); + return clipper.clipPolygon( polygon, closePolygon ); +} +/*! + Sutherland-Hodgman polygon clipping + + \param clipRect Clip rectangle + \param polygon Polygon + \param closePolygon True, when the polygon is closed + + \return Clipped polygon +*/ +QPolygon QwtClipper::clipPolygon( + const QRect &clipRect, const QPolygon &polygon, bool closePolygon ) +{ + QwtPolygonClipper clipper( clipRect ); + return clipper.clipPolygon( polygon, closePolygon ); +} + +/*! + Sutherland-Hodgman polygon clipping + + \param clipRect Clip rectangle + \param polygon Polygon + \param closePolygon True, when the polygon is closed + + \return Clipped polygon +*/ +QPolygonF QwtClipper::clipPolygonF( + const QRectF &clipRect, const QPolygonF &polygon, bool closePolygon ) +{ + QwtPolygonClipper clipper( clipRect ); + return clipper.clipPolygon( polygon, closePolygon ); +} + +/*! + Circle clipping + + clipCircle() divides a circle into intervals of angles representing arcs + of the circle. When the circle is completely inside the clip rectangle + an interval [0.0, 2 * M_PI] is returned. + + \param clipRect Clip rectangle + \param center Center of the circle + \param radius Radius of the circle + + \return Arcs of the circle +*/ +QVector QwtClipper::clipCircle( const QRectF &clipRect, + const QPointF ¢er, double radius ) +{ + QwtCircleClipper clipper( clipRect ); + return clipper.clipCircle( center, radius ); +} diff --git a/qwt/src/qwt_clipper.h b/qwt/src/qwt_clipper.h new file mode 100644 index 000000000..1b1820bb0 --- /dev/null +++ b/qwt/src/qwt_clipper.h @@ -0,0 +1,40 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_CLIPPER_H +#define QWT_CLIPPER_H + +#include "qwt_global.h" +#include "qwt_interval.h" +#include +#include + +class QRect; +class QRectF; + +/*! + \brief Some clipping algorithms +*/ + +class QWT_EXPORT QwtClipper +{ +public: + static QPolygon clipPolygon( const QRect &, + const QPolygon &, bool closePolygon = false ); + static QPolygon clipPolygon( const QRectF &, + const QPolygon &, bool closePolygon = false ); + + static QPolygonF clipPolygonF( const QRectF &, + const QPolygonF &, bool closePolygon = false ); + + static QVector clipCircle( + const QRectF &, const QPointF &, double radius ); +}; + +#endif diff --git a/qwt/src/qwt_color_map.cpp b/qwt/src/qwt_color_map.cpp new file mode 100644 index 000000000..571c1380d --- /dev/null +++ b/qwt/src/qwt_color_map.cpp @@ -0,0 +1,444 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_color_map.h" +#include "qwt_math.h" +#include "qwt_interval.h" +#include + +class QwtLinearColorMap::ColorStops +{ +public: + ColorStops() + { + _stops.reserve( 256 ); + } + + void insert( double pos, const QColor &color ); + QRgb rgb( QwtLinearColorMap::Mode, double pos ) const; + + QVector stops() const; + +private: + + class ColorStop + { + public: + ColorStop(): + pos( 0.0 ), + rgb( 0 ) + { + }; + + ColorStop( double p, const QColor &c ): + pos( p ), + rgb( c.rgb() ) + { + r = qRed( rgb ); + g = qGreen( rgb ); + b = qBlue( rgb ); + } + + double pos; + QRgb rgb; + int r, g, b; + }; + + inline int findUpper( double pos ) const; + QVector _stops; +}; + +void QwtLinearColorMap::ColorStops::insert( double pos, const QColor &color ) +{ + // Lookups need to be very fast, insertions are not so important. + // Anyway, a balanced tree is what we need here. TODO ... + + if ( pos < 0.0 || pos > 1.0 ) + return; + + int index; + if ( _stops.size() == 0 ) + { + index = 0; + _stops.resize( 1 ); + } + else + { + index = findUpper( pos ); + if ( index == _stops.size() || + qAbs( _stops[index].pos - pos ) >= 0.001 ) + { + _stops.resize( _stops.size() + 1 ); + for ( int i = _stops.size() - 1; i > index; i-- ) + _stops[i] = _stops[i-1]; + } + } + + _stops[index] = ColorStop( pos, color ); +} + +inline QVector QwtLinearColorMap::ColorStops::stops() const +{ + QVector positions( _stops.size() ); + for ( int i = 0; i < _stops.size(); i++ ) + positions[i] = _stops[i].pos; + return positions; +} + +inline int QwtLinearColorMap::ColorStops::findUpper( double pos ) const +{ + int index = 0; + int n = _stops.size(); + + const ColorStop *stops = _stops.data(); + + while ( n > 0 ) + { + const int half = n >> 1; + const int middle = index + half; + + if ( stops[middle].pos <= pos ) + { + index = middle + 1; + n -= half + 1; + } + else + n = half; + } + + return index; +} + +inline QRgb QwtLinearColorMap::ColorStops::rgb( + QwtLinearColorMap::Mode mode, double pos ) const +{ + if ( pos <= 0.0 ) + return _stops[0].rgb; + if ( pos >= 1.0 ) + return _stops[ _stops.size() - 1 ].rgb; + + const int index = findUpper( pos ); + if ( mode == FixedColors ) + { + return _stops[index-1].rgb; + } + else + { + const ColorStop &s1 = _stops[index-1]; + const ColorStop &s2 = _stops[index]; + + const double ratio = ( pos - s1.pos ) / ( s2.pos - s1.pos ); + + const int r = s1.r + qRound( ratio * ( s2.r - s1.r ) ); + const int g = s1.g + qRound( ratio * ( s2.g - s1.g ) ); + const int b = s1.b + qRound( ratio * ( s2.b - s1.b ) ); + + return qRgb( r, g, b ); + } +} + +//! Constructor +QwtColorMap::QwtColorMap( Format format ): + d_format( format ) +{ +} + +//! Destructor +QwtColorMap::~QwtColorMap() +{ +} + +/*! + Build and return a color map of 256 colors + + The color table is needed for rendering indexed images in combination + with using colorIndex(). + + \param interval Range for the values + \return A color table, that can be used for a QImage +*/ +QVector QwtColorMap::colorTable( const QwtInterval &interval ) const +{ + QVector table( 256 ); + + if ( interval.isValid() ) + { + const double step = interval.width() / ( table.size() - 1 ); + for ( int i = 0; i < table.size(); i++ ) + table[i] = rgb( interval, interval.minValue() + step * i ); + } + + return table; +} + +class QwtLinearColorMap::PrivateData +{ +public: + ColorStops colorStops; + QwtLinearColorMap::Mode mode; +}; + +/*! + Build a color map with two stops at 0.0 and 1.0. The color + at 0.0 is Qt::blue, at 1.0 it is Qt::yellow. + + \param format Preferred format of the color map +*/ +QwtLinearColorMap::QwtLinearColorMap( QwtColorMap::Format format ): + QwtColorMap( format ) +{ + d_data = new PrivateData; + d_data->mode = ScaledColors; + + setColorInterval( Qt::blue, Qt::yellow ); +} + +/*! + Build a color map with two stops at 0.0 and 1.0. + + \param color1 Color used for the minimum value of the value interval + \param color2 Color used for the maximum value of the value interval + \param format Preferred format for the color map +*/ +QwtLinearColorMap::QwtLinearColorMap( const QColor &color1, + const QColor &color2, QwtColorMap::Format format ): + QwtColorMap( format ) +{ + d_data = new PrivateData; + d_data->mode = ScaledColors; + setColorInterval( color1, color2 ); +} + +//! Destructor +QwtLinearColorMap::~QwtLinearColorMap() +{ + delete d_data; +} + +/*! + \brief Set the mode of the color map + + FixedColors means the color is calculated from the next lower + color stop. ScaledColors means the color is calculated + by interpolating the colors of the adjacent stops. + + \sa mode() +*/ +void QwtLinearColorMap::setMode( Mode mode ) +{ + d_data->mode = mode; +} + +/*! + \return Mode of the color map + \sa setMode() +*/ +QwtLinearColorMap::Mode QwtLinearColorMap::mode() const +{ + return d_data->mode; +} + +/*! + Set the color range + + Add stops at 0.0 and 1.0. + + \param color1 Color used for the minimum value of the value interval + \param color2 Color used for the maximum value of the value interval + + \sa color1(), color2() +*/ +void QwtLinearColorMap::setColorInterval( + const QColor &color1, const QColor &color2 ) +{ + d_data->colorStops = ColorStops(); + d_data->colorStops.insert( 0.0, color1 ); + d_data->colorStops.insert( 1.0, color2 ); +} + +/*! + Add a color stop + + The value has to be in the range [0.0, 1.0]. + F.e. a stop at position 17.0 for a range [10.0,20.0] must be + passed as: (17.0 - 10.0) / (20.0 - 10.0) + + \param value Value between [0.0, 1.0] + \param color Color stop +*/ +void QwtLinearColorMap::addColorStop( double value, const QColor& color ) +{ + if ( value >= 0.0 && value <= 1.0 ) + d_data->colorStops.insert( value, color ); +} + +/*! + \return Positions of color stops in increasing order +*/ +QVector QwtLinearColorMap::colorStops() const +{ + return d_data->colorStops.stops(); +} + +/*! + \return the first color of the color range + \sa setColorInterval() +*/ +QColor QwtLinearColorMap::color1() const +{ + return QColor( d_data->colorStops.rgb( d_data->mode, 0.0 ) ); +} + +/*! + \return the second color of the color range + \sa setColorInterval() +*/ +QColor QwtLinearColorMap::color2() const +{ + return QColor( d_data->colorStops.rgb( d_data->mode, 1.0 ) ); +} + +/*! + Map a value of a given interval into a RGB value + + \param interval Range for all values + \param value Value to map into a RGB value + + \return RGB value for value +*/ +QRgb QwtLinearColorMap::rgb( + const QwtInterval &interval, double value ) const +{ + if ( qIsNaN(value) ) + return qRgba(0, 0, 0, 0); + + const double width = interval.width(); + + double ratio = 0.0; + if ( width > 0.0 ) + ratio = ( value - interval.minValue() ) / width; + + return d_data->colorStops.rgb( d_data->mode, ratio ); +} + +/*! + \brief Map a value of a given interval into a color index + + \param interval Range for all values + \param value Value to map into a color index + + \return Index, between 0 and 255 +*/ +unsigned char QwtLinearColorMap::colorIndex( + const QwtInterval &interval, double value ) const +{ + const double width = interval.width(); + + if ( qIsNaN(value) || width <= 0.0 || value <= interval.minValue() ) + return 0; + + if ( value >= interval.maxValue() ) + return 255; + + const double ratio = ( value - interval.minValue() ) / width; + + unsigned char index; + if ( d_data->mode == FixedColors ) + index = static_cast( ratio * 255 ); // always floor + else + index = static_cast( qRound( ratio * 255 ) ); + + return index; +} + +class QwtAlphaColorMap::PrivateData +{ +public: + QColor color; + QRgb rgb; +}; + + +/*! + Constructor + \param color Color of the map +*/ +QwtAlphaColorMap::QwtAlphaColorMap( const QColor &color ): + QwtColorMap( QwtColorMap::RGB ) +{ + d_data = new PrivateData; + d_data->color = color; + d_data->rgb = color.rgb() & qRgba( 255, 255, 255, 0 ); +} + +//! Destructor +QwtAlphaColorMap::~QwtAlphaColorMap() +{ + delete d_data; +} + +/*! + Set the color + + \param color Color + \sa color() +*/ +void QwtAlphaColorMap::setColor( const QColor &color ) +{ + d_data->color = color; + d_data->rgb = color.rgb(); +} + +/*! + \return the color + \sa setColor() +*/ +QColor QwtAlphaColorMap::color() const +{ + return d_data->color; +} + +/*! + \brief Map a value of a given interval into a alpha value + + alpha := (value - interval.minValue()) / interval.width(); + + \param interval Range for all values + \param value Value to map into a RGB value + \return RGB value, with an alpha value +*/ +QRgb QwtAlphaColorMap::rgb( const QwtInterval &interval, double value ) const +{ + const double width = interval.width(); + if ( !qIsNaN(value) && width >= 0.0 ) + { + const double ratio = ( value - interval.minValue() ) / width; + int alpha = qRound( 255 * ratio ); + if ( alpha < 0 ) + alpha = 0; + if ( alpha > 255 ) + alpha = 255; + + return d_data->rgb | ( alpha << 24 ); + } + return d_data->rgb; +} + +/*! + Dummy function, needed to be implemented as it is pure virtual + in QwtColorMap. Color indices make no sense in combination with + an alpha channel. + + \return Always 0 +*/ +unsigned char QwtAlphaColorMap::colorIndex( + const QwtInterval &, double ) const +{ + return 0; +} diff --git a/qwt/src/qwt_color_map.h b/qwt/src/qwt_color_map.h new file mode 100644 index 000000000..91a92bd0c --- /dev/null +++ b/qwt/src/qwt_color_map.h @@ -0,0 +1,200 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_COLOR_MAP_H +#define QWT_COLOR_MAP_H + +#include "qwt_global.h" +#include "qwt_interval.h" +#include +#include + +/*! + \brief QwtColorMap is used to map values into colors. + + For displaying 3D data on a 2D plane the 3rd dimension is often + displayed using colors, like f.e in a spectrogram. + + Each color map is optimized to return colors for only one of the + following image formats: + + - QImage::Format_Indexed8\n + - QImage::Format_ARGB32\n + + \sa QwtPlotSpectrogram, QwtScaleWidget +*/ + +class QWT_EXPORT QwtColorMap +{ +public: + /*! + Format for color mapping + \sa rgb(), colorIndex(), colorTable() + */ + + enum Format + { + //! The map is intended to map into RGB values. + RGB, + + /*! + The map is intended to map into 8 bit values, that + are indices into the color table. + */ + Indexed + }; + + QwtColorMap( Format = QwtColorMap::RGB ); + virtual ~QwtColorMap(); + + Format format() const; + + /*! + Map a value of a given interval into a RGB value. + + \param interval Range for the values + \param value Value + \return RGB value, corresponding to value + */ + virtual QRgb rgb( const QwtInterval &interval, + double value ) const = 0; + + /*! + Map a value of a given interval into a color index + + \param interval Range for the values + \param value Value + \return color index, corresponding to value + */ + virtual unsigned char colorIndex( + const QwtInterval &interval, double value ) const = 0; + + QColor color( const QwtInterval &, double value ) const; + virtual QVector colorTable( const QwtInterval & ) const; + +private: + Format d_format; +}; + +/*! + \brief QwtLinearColorMap builds a color map from color stops. + + A color stop is a color at a specific position. The valid + range for the positions is [0.0, 1.0]. When mapping a value + into a color it is translated into this interval according to mode(). +*/ +class QWT_EXPORT QwtLinearColorMap: public QwtColorMap +{ +public: + /*! + Mode of color map + \sa setMode(), mode() + */ + enum Mode + { + //! Return the color from the next lower color stop + FixedColors, + + //! Interpolating the colors of the adjacent stops. + ScaledColors + }; + + QwtLinearColorMap( QwtColorMap::Format = QwtColorMap::RGB ); + QwtLinearColorMap( const QColor &from, const QColor &to, + QwtColorMap::Format = QwtColorMap::RGB ); + + virtual ~QwtLinearColorMap(); + + void setMode( Mode ); + Mode mode() const; + + void setColorInterval( const QColor &color1, const QColor &color2 ); + void addColorStop( double value, const QColor& ); + QVector colorStops() const; + + QColor color1() const; + QColor color2() const; + + virtual QRgb rgb( const QwtInterval &, double value ) const; + virtual unsigned char colorIndex( + const QwtInterval &, double value ) const; + + class ColorStops; + +private: + // Disabled copy constructor and operator= + QwtLinearColorMap( const QwtLinearColorMap & ); + QwtLinearColorMap &operator=( const QwtLinearColorMap & ); + + class PrivateData; + PrivateData *d_data; +}; + +/*! + \brief QwtAlphaColorMap varies the alpha value of a color +*/ +class QWT_EXPORT QwtAlphaColorMap: public QwtColorMap +{ +public: + QwtAlphaColorMap( const QColor & = QColor( Qt::gray ) ); + virtual ~QwtAlphaColorMap(); + + void setColor( const QColor & ); + QColor color() const; + + virtual QRgb rgb( const QwtInterval &, double value ) const; + +private: + QwtAlphaColorMap( const QwtAlphaColorMap & ); + QwtAlphaColorMap &operator=( const QwtAlphaColorMap & ); + + virtual unsigned char colorIndex( + const QwtInterval &, double value ) const; + + class PrivateData; + PrivateData *d_data; +}; + + +/*! + Map a value into a color + + \param interval Valid interval for values + \param value Value + + \return Color corresponding to value + + \warning This method is slow for Indexed color maps. If it is + necessary to map many values, its better to get the + color table once and find the color using colorIndex(). +*/ +inline QColor QwtColorMap::color( + const QwtInterval &interval, double value ) const +{ + if ( d_format == RGB ) + { + return QColor( rgb( interval, value ) ); + } + else + { + const unsigned int index = colorIndex( interval, value ); + return colorTable( interval )[index]; // slow + } +} + +/*! + \return Intended format of the color map + \sa Format +*/ +inline QwtColorMap::Format QwtColorMap::format() const +{ + return d_format; +} + +#endif diff --git a/qwt/src/qwt_column_symbol.cpp b/qwt/src/qwt_column_symbol.cpp new file mode 100644 index 000000000..d6f0f1a63 --- /dev/null +++ b/qwt/src/qwt_column_symbol.cpp @@ -0,0 +1,293 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_column_symbol.h" +#include "qwt_math.h" +#include "qwt_painter.h" +#include +#include + +static void qwtDrawBox( QPainter *p, const QRectF &rect, + const QPalette &pal, double lw ) +{ + if ( lw > 0.0 ) + { + if ( rect.width() == 0.0 ) + { + p->setPen( pal.dark().color() ); + p->drawLine( rect.topLeft(), rect.bottomLeft() ); + return; + } + + if ( rect.height() == 0.0 ) + { + p->setPen( pal.dark().color() ); + p->drawLine( rect.topLeft(), rect.topRight() ); + return; + } + + lw = qMin( lw, rect.height() / 2.0 - 1.0 ); + lw = qMin( lw, rect.width() / 2.0 - 1.0 ); + + const QRectF outerRect = rect.adjusted( 0, 0, 1, 1 ); + QPolygonF polygon( outerRect ); + + if ( outerRect.width() > 2 * lw && + outerRect.height() > 2 * lw ) + { + const QRectF innerRect = outerRect.adjusted( lw, lw, -lw, -lw ); + polygon = polygon.subtracted( innerRect ); + } + + p->setPen( Qt::NoPen ); + + p->setBrush( pal.dark() ); + p->drawPolygon( polygon ); + } + + const QRectF windowRect = rect.adjusted( lw, lw, -lw + 1, -lw + 1 ); + if ( windowRect.isValid() ) + p->fillRect( windowRect, pal.window() ); +} + +static void qwtDrawPanel( QPainter *painter, const QRectF &rect, + const QPalette &pal, double lw ) +{ + if ( lw > 0.0 ) + { + if ( rect.width() == 0.0 ) + { + painter->setPen( pal.window().color() ); + painter->drawLine( rect.topLeft(), rect.bottomLeft() ); + return; + } + + if ( rect.height() == 0.0 ) + { + painter->setPen( pal.window().color() ); + painter->drawLine( rect.topLeft(), rect.topRight() ); + return; + } + + lw = qMin( lw, rect.height() / 2.0 - 1.0 ); + lw = qMin( lw, rect.width() / 2.0 - 1.0 ); + + const QRectF outerRect = rect.adjusted( 0, 0, 1, 1 ); + const QRectF innerRect = outerRect.adjusted( lw, lw, -lw, -lw ); + + QPolygonF lines[2]; + + lines[0] += outerRect.bottomLeft(); + lines[0] += outerRect.topLeft(); + lines[0] += outerRect.topRight(); + lines[0] += innerRect.topRight(); + lines[0] += innerRect.topLeft(); + lines[0] += innerRect.bottomLeft(); + + lines[1] += outerRect.topRight(); + lines[1] += outerRect.bottomRight(); + lines[1] += outerRect.bottomLeft(); + lines[1] += innerRect.bottomLeft(); + lines[1] += innerRect.bottomRight(); + lines[1] += innerRect.topRight(); + + painter->setPen( Qt::NoPen ); + + painter->setBrush( pal.light() ); + painter->drawPolygon( lines[0] ); + painter->setBrush( pal.dark() ); + painter->drawPolygon( lines[1] ); + } + + painter->fillRect( rect.adjusted( lw, lw, -lw + 1, -lw + 1 ), pal.window() ); +} + +class QwtColumnSymbol::PrivateData +{ +public: + PrivateData(): + style( QwtColumnSymbol::Box ), + frameStyle( QwtColumnSymbol::Raised ), + lineWidth( 2 ) + { + palette = QPalette( Qt::gray ); + } + + QwtColumnSymbol::Style style; + QwtColumnSymbol::FrameStyle frameStyle; + + QPalette palette; + int lineWidth; +}; + +/*! + Constructor + + \param style Style of the symbol + \sa setStyle(), style(), Style +*/ +QwtColumnSymbol::QwtColumnSymbol( Style style ) +{ + d_data = new PrivateData(); + d_data->style = style; +} + +//! Destructor +QwtColumnSymbol::~QwtColumnSymbol() +{ + delete d_data; +} + +/*! + Specify the symbol style + + \param style Style + \sa style(), setPalette() +*/ +void QwtColumnSymbol::setStyle( Style style ) +{ + d_data->style = style; +} + +/*! + \return Current symbol style + \sa setStyle() +*/ +QwtColumnSymbol::Style QwtColumnSymbol::style() const +{ + return d_data->style; +} + +/*! + Assign a palette for the symbol + + \param palette Palette + \sa palette(), setStyle() +*/ +void QwtColumnSymbol::setPalette( const QPalette &palette ) +{ + d_data->palette = palette; +} + +/*! + \return Current palette + \sa setPalette() +*/ +const QPalette& QwtColumnSymbol::palette() const +{ + return d_data->palette; +} + +/*! + Set the frame, that is used for the Box style. + + \param frameStyle Frame style + \sa frameStyle(), setLineWidth(), setStyle() +*/ +void QwtColumnSymbol::setFrameStyle( FrameStyle frameStyle ) +{ + d_data->frameStyle = frameStyle; +} + +/*! + \return Current frame style, that is used for the Box style. + \sa setFrameStyle(), lineWidth(), setStyle() +*/ +QwtColumnSymbol::FrameStyle QwtColumnSymbol::frameStyle() const +{ + return d_data->frameStyle; +} + +/*! + Set the line width of the frame, that is used for the Box style. + + \param width Width + \sa lineWidth(), setFrameStyle() +*/ +void QwtColumnSymbol::setLineWidth( int width ) +{ + if ( width < 0 ) + width = 0; + + d_data->lineWidth = width; +} + +/*! + \return Line width of the frame, that is used for the Box style. + \sa setLineWidth(), frameStyle(), setStyle() +*/ +int QwtColumnSymbol::lineWidth() const +{ + return d_data->lineWidth; +} + +/*! + Draw the symbol depending on its style. + + \param painter Painter + \param rect Directed rectangle + + \sa drawBox() +*/ +void QwtColumnSymbol::draw( QPainter *painter, + const QwtColumnRect &rect ) const +{ + painter->save(); + + switch ( d_data->style ) + { + case QwtColumnSymbol::Box: + { + drawBox( painter, rect ); + break; + } + default:; + } + + painter->restore(); +} + +/*! + Draw the symbol when it is in Box style. + + \param painter Painter + \param rect Directed rectangle + + \sa draw() +*/ +void QwtColumnSymbol::drawBox( QPainter *painter, + const QwtColumnRect &rect ) const +{ + QRectF r = rect.toRect(); + if ( QwtPainter::roundingAlignment( painter ) ) + { + r.setLeft( qRound( r.left() ) ); + r.setRight( qRound( r.right() ) ); + r.setTop( qRound( r.top() ) ); + r.setBottom( qRound( r.bottom() ) ); + } + + switch ( d_data->frameStyle ) + { + case QwtColumnSymbol::Raised: + { + qwtDrawPanel( painter, r, d_data->palette, d_data->lineWidth ); + break; + } + case QwtColumnSymbol::Plain: + { + qwtDrawBox( painter, r, d_data->palette, d_data->lineWidth ); + break; + } + default: + { + painter->fillRect( r, d_data->palette.window() ); + } + } +} diff --git a/qwt/src/qwt_column_symbol.h b/qwt/src/qwt_column_symbol.h new file mode 100644 index 000000000..918fe4a3c --- /dev/null +++ b/qwt/src/qwt_column_symbol.h @@ -0,0 +1,161 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_COLUMN_SYMBOL_H +#define QWT_COLUMN_SYMBOL_H + +#include "qwt_global.h" +#include "qwt_interval.h" +#include +#include +#include + +class QPainter; +class QPalette; +class QRect; +class QwtText; + +/*! + \brief Directed rectangle representing bounding rectangle and orientation + of a column. +*/ +class QWT_EXPORT QwtColumnRect +{ +public: + //! Direction of the column + enum Direction + { + //! From left to right + LeftToRight, + + //! From right to left + RightToLeft, + + //! From bottom to top + BottomToTop, + + //! From top to bottom + TopToBottom + }; + + //! Build an rectangle with invalid intervals directed BottomToTop. + QwtColumnRect(): + direction( BottomToTop ) + { + } + + //! \return A normalized QRect built from the intervals + QRectF toRect() const + { + QRectF r( hInterval.minValue(), vInterval.minValue(), + hInterval.maxValue() - hInterval.minValue(), + vInterval.maxValue() - vInterval.minValue() ); + r = r.normalized(); + + if ( hInterval.borderFlags() & QwtInterval::ExcludeMinimum ) + r.adjust( 1, 0, 0, 0 ); + if ( hInterval.borderFlags() & QwtInterval::ExcludeMaximum ) + r.adjust( 0, 0, -1, 0 ); + if ( vInterval.borderFlags() & QwtInterval::ExcludeMinimum ) + r.adjust( 0, 1, 0, 0 ); + if ( vInterval.borderFlags() & QwtInterval::ExcludeMaximum ) + r.adjust( 0, 0, 0, -1 ); + + return r; + } + + //! \return Orientation + Qt::Orientation orientation() const + { + if ( direction == LeftToRight || direction == RightToLeft ) + return Qt::Horizontal; + + return Qt::Vertical; + } + + //! Interval for the horizontal coordinates + QwtInterval hInterval; + + //! Interval for the vertical coordinates + QwtInterval vInterval; + + //! Direction + Direction direction; +}; + +//! A drawing primitive for columns +class QWT_EXPORT QwtColumnSymbol +{ +public: + /*! + Style + \sa setStyle(), style() + */ + enum Style + { + //! No Style, the symbol draws nothing + NoStyle = -1, + + /*! + The column is painted with a frame depending on the frameStyle() + and lineWidth() using the palette(). + */ + Box, + + /*! + Styles >= QwtColumnSymbol::UserStyle are reserved for derived + classes of QwtColumnSymbol that overload draw() with + additional application specific symbol types. + */ + UserStyle = 1000 + }; + + /*! + Frame Style used in Box style(). + \sa Style, setFrameStyle(), frameStyle(), setStyle(), setPalette() + */ + enum FrameStyle + { + //! No frame + NoFrame, + + //! A plain frame style + Plain, + + //! A raised frame style + Raised + }; + +public: + QwtColumnSymbol( Style = NoStyle ); + virtual ~QwtColumnSymbol(); + + void setFrameStyle( FrameStyle style ); + FrameStyle frameStyle() const; + + void setLineWidth( int width ); + int lineWidth() const; + + void setPalette( const QPalette & ); + const QPalette &palette() const; + + void setStyle( Style ); + Style style() const; + + virtual void draw( QPainter *, const QwtColumnRect & ) const; + +protected: + void drawBox( QPainter *, const QwtColumnRect & ) const; + +private: + class PrivateData; + PrivateData* d_data; +}; + +#endif diff --git a/qwt/src/qwt_compass.cpp b/qwt/src/qwt_compass.cpp new file mode 100644 index 000000000..4e2c9ffdf --- /dev/null +++ b/qwt/src/qwt_compass.cpp @@ -0,0 +1,308 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_compass.h" +#include "qwt_compass_rose.h" +#include "qwt_math.h" +#include "qwt_scale_draw.h" +#include "qwt_painter.h" +#include "qwt_dial_needle.h" +#include +#include +#include + +/*! + \brief Constructor + + Initializes a label map for multiples of 45 degrees + */ +QwtCompassScaleDraw::QwtCompassScaleDraw() +{ + enableComponent( QwtAbstractScaleDraw::Backbone, false ); + enableComponent( QwtAbstractScaleDraw::Ticks, false ); + + d_labelMap.insert( 0.0, QString::fromLatin1( "N" ) ); + d_labelMap.insert( 45.0, QString::fromLatin1( "NE" ) ); + d_labelMap.insert( 90.0, QString::fromLatin1( "E" ) ); + d_labelMap.insert( 135.0, QString::fromLatin1( "SE" ) ); + d_labelMap.insert( 180.0, QString::fromLatin1( "S" ) ); + d_labelMap.insert( 225.0, QString::fromLatin1( "SW" ) ); + d_labelMap.insert( 270.0, QString::fromLatin1( "W" ) ); + d_labelMap.insert( 315.0, QString::fromLatin1( "NW" ) ); + +#if 0 + d_labelMap.insert( 22.5, QString::fromLatin1( "NNE" ) ); + d_labelMap.insert( 67.5, QString::fromLatin1( "NEE" ) ); + d_labelMap.insert( 112.5, QString::fromLatin1( "SEE" ) ); + d_labelMap.insert( 157.5, QString::fromLatin1( "SSE" ) ); + d_labelMap.insert( 202.5, QString::fromLatin1( "SSW" ) ); + d_labelMap.insert( 247.5, QString::fromLatin1( "SWW" ) ); + d_labelMap.insert( 292.5, QString::fromLatin1( "NWW" ) ); + d_labelMap.insert( 337.5, QString::fromLatin1( "NNW" ) ); +#endif +} + +/*! + \brief Constructor + + \param map Value to label map + */ +QwtCompassScaleDraw::QwtCompassScaleDraw( const QMap &map ): + d_labelMap( map ) +{ + enableComponent( QwtAbstractScaleDraw::Backbone, false ); + enableComponent( QwtAbstractScaleDraw::Ticks, false ); +} + +/*! + \brief Set a map, mapping values to labels + \param map Value to label map + + The values of the major ticks are found by looking into this + map. The default map consists of the labels N, NE, E, SE, S, SW, W, NW. + + \warning The map will have no effect for values that are no major + tick values. Major ticks can be changed by QwtScaleDraw::setScale + + \sa labelMap(), scaleDraw(), setScale() +*/ +void QwtCompassScaleDraw::setLabelMap( const QMap &map ) +{ + d_labelMap = map; +} + + +/*! + \return map, mapping values to labels + \sa setLabelMap() +*/ +QMap QwtCompassScaleDraw::labelMap() const +{ + return d_labelMap; +} + +/*! + Map a value to a corresponding label + + \param value Value that will be mapped + + label() looks in the labelMap() for a corresponding label for value + or returns an null text. + + \return Label, or QString::null + \sa labelMap(), setLabelMap() +*/ + +QwtText QwtCompassScaleDraw::label( double value ) const +{ + if ( qFuzzyCompare( value + 1.0, 1.0 ) ) + value = 0.0; + + if ( value < 0.0 ) + value += 360.0; + + if ( d_labelMap.contains( value ) ) + return d_labelMap[value]; + + return QwtText(); +} + +class QwtCompass::PrivateData +{ +public: + PrivateData(): + rose( NULL ) + { + } + + ~PrivateData() + { + delete rose; + } + + QwtCompassRose *rose; +}; + +/*! + \brief Constructor + \param parent Parent widget + + Create a compass widget with a scale, no needle and no rose. + The default origin is 270.0 with no valid value. It accepts + mouse and keyboard inputs and has no step size. The default mode + is QwtDial::RotateNeedle. +*/ +QwtCompass::QwtCompass( QWidget* parent ): + QwtDial( parent ) +{ + d_data = new PrivateData; + + setScaleDraw( new QwtCompassScaleDraw() ); + + setOrigin( 270.0 ); + setWrapping( true ); + + setScaleMaxMajor( 36 ); + setScaleMaxMinor( 10 ); + + setScale( 0.0, 360.0 ); // degrees as default + setTotalSteps( 360 ); +} + +//! Destructor +QwtCompass::~QwtCompass() +{ + delete d_data; +} + + +/*! + Draw the contents of the scale + + \param painter Painter + \param center Center of the content circle + \param radius Radius of the content circle +*/ +void QwtCompass::drawScaleContents( QPainter *painter, + const QPointF ¢er, double radius ) const +{ + QPalette::ColorGroup cg; + if ( isEnabled() ) + cg = hasFocus() ? QPalette::Active : QPalette::Inactive; + else + cg = QPalette::Disabled; + + double north = origin(); + if ( isValid() ) + { + if ( mode() == RotateScale ) + north -= value(); + } + + const int margin = 4; + drawRose( painter, center, radius - margin, 360.0 - north, cg ); +} + +/*! + Draw the compass rose + + \param painter Painter + \param center Center of the compass + \param radius of the circle, where to paint the rose + \param north Direction pointing north, in degrees counter clockwise + \param cg Color group +*/ +void QwtCompass::drawRose( QPainter *painter, const QPointF ¢er, + double radius, double north, QPalette::ColorGroup cg ) const +{ + if ( d_data->rose ) + d_data->rose->draw( painter, center, radius, north, cg ); +} + +/*! + Set a rose for the compass + \param rose Compass rose + \warning The rose will be deleted, when a different rose is + set or in ~QwtCompass + \sa rose() +*/ +void QwtCompass::setRose( QwtCompassRose *rose ) +{ + if ( rose != d_data->rose ) + { + if ( d_data->rose ) + delete d_data->rose; + + d_data->rose = rose; + update(); + } +} + +/*! + \return rose + \sa setRose() +*/ +const QwtCompassRose *QwtCompass::rose() const +{ + return d_data->rose; +} + +/*! + \return rose + \sa setRose() +*/ +QwtCompassRose *QwtCompass::rose() +{ + return d_data->rose; +} + +/*! + Handles key events + + Beside the keys described in QwtDial::keyPressEvent numbers + from 1-9 (without 5) set the direction according to their + position on the num pad. + + \sa isReadOnly() +*/ +void QwtCompass::keyPressEvent( QKeyEvent *kev ) +{ + if ( isReadOnly() ) + return; + +#if 0 + if ( kev->key() == Key_5 ) + { + invalidate(); // signal ??? + return; + } +#endif + + double newValue = value(); + + if ( kev->key() >= Qt::Key_1 && kev->key() <= Qt::Key_9 ) + { + if ( mode() != RotateNeedle || kev->key() == Qt::Key_5 ) + return; + + switch ( kev->key() ) + { + case Qt::Key_6: + newValue = 180.0 * 0.0; + break; + case Qt::Key_3: + newValue = 180.0 * 0.25; + break; + case Qt::Key_2: + newValue = 180.0 * 0.5; + break; + case Qt::Key_1: + newValue = 180.0 * 0.75; + break; + case Qt::Key_4: + newValue = 180.0 * 1.0; + break; + case Qt::Key_7: + newValue = 180.0 * 1.25; + break; + case Qt::Key_8: + newValue = 180.0 * 1.5; + break; + case Qt::Key_9: + newValue = 180.0 * 1.75; + break; + } + newValue -= origin(); + setValue( newValue ); + } + else + { + QwtDial::keyPressEvent( kev ); + } +} diff --git a/qwt/src/qwt_compass.h b/qwt/src/qwt_compass.h new file mode 100644 index 000000000..b9a3c95bf --- /dev/null +++ b/qwt/src/qwt_compass.h @@ -0,0 +1,83 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_COMPASS_H +#define QWT_COMPASS_H 1 + +#include "qwt_global.h" +#include "qwt_dial.h" +#include "qwt_round_scale_draw.h" +#include +#include + +class QwtCompassRose; + +/*! + \brief A special scale draw made for QwtCompass + + QwtCompassScaleDraw maps values to strings using + a special map, that can be modified by the application + + The default map consists of the labels N, NE, E, SE, S, SW, W, NW. + + \sa QwtCompass +*/ +class QWT_EXPORT QwtCompassScaleDraw: public QwtRoundScaleDraw +{ +public: + explicit QwtCompassScaleDraw(); + explicit QwtCompassScaleDraw( const QMap &map ); + + void setLabelMap( const QMap &map ); + QMap labelMap() const; + + virtual QwtText label( double value ) const; + +private: + QMap d_labelMap; +}; + +/*! + \brief A Compass Widget + + QwtCompass is a widget to display and enter directions. It consists + of a scale, an optional needle and rose. + + \image html dials1.png + + \note The examples/dials example shows how to use QwtCompass. +*/ + +class QWT_EXPORT QwtCompass: public QwtDial +{ + Q_OBJECT + +public: + explicit QwtCompass( QWidget* parent = NULL ); + virtual ~QwtCompass(); + + void setRose( QwtCompassRose *rose ); + const QwtCompassRose *rose() const; + QwtCompassRose *rose(); + +protected: + virtual void drawRose( QPainter *, const QPointF ¢er, + double radius, double north, QPalette::ColorGroup ) const; + + virtual void drawScaleContents( QPainter *, + const QPointF ¢er, double radius ) const; + + virtual void keyPressEvent( QKeyEvent * ); + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_compass_rose.cpp b/qwt/src/qwt_compass_rose.cpp new file mode 100644 index 000000000..21a35f244 --- /dev/null +++ b/qwt/src/qwt_compass_rose.cpp @@ -0,0 +1,269 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_compass_rose.h" +#include "qwt_point_polar.h" +#include "qwt_painter.h" +#include + +static QPointF qwtIntersection( + QPointF p11, QPointF p12, QPointF p21, QPointF p22 ) +{ + const QLineF line1( p11, p12 ); + const QLineF line2( p21, p22 ); + + QPointF pos; + if ( line1.intersect( line2, &pos ) == QLineF::NoIntersection ) + return QPointF(); + + return pos; +} + +class QwtSimpleCompassRose::PrivateData +{ +public: + PrivateData(): + width( 0.2 ), + numThorns( 8 ), + numThornLevels( -1 ), + shrinkFactor( 0.9 ) + { + } + + double width; + int numThorns; + int numThornLevels; + double shrinkFactor; +}; + +/*! + Constructor + + \param numThorns Number of thorns + \param numThornLevels Number of thorn levels +*/ +QwtSimpleCompassRose::QwtSimpleCompassRose( + int numThorns, int numThornLevels ) +{ + d_data = new PrivateData(); + d_data->numThorns = numThorns; + d_data->numThornLevels = numThornLevels; + + const QColor dark( 128, 128, 255 ); + const QColor light( 192, 255, 255 ); + + QPalette palette; + palette.setColor( QPalette::Dark, dark ); + palette.setColor( QPalette::Light, light ); + + setPalette( palette ); +} + +//! Destructor +QwtSimpleCompassRose::~QwtSimpleCompassRose() +{ + delete d_data; +} + +/*! + Set the Factor how to shrink the thorns with each level + The default value is 0.9. + + \param factor Shrink factor + \sa shrinkFactor() +*/ +void QwtSimpleCompassRose::setShrinkFactor( double factor ) +{ + d_data->shrinkFactor = factor; +} + +/*! + \return Factor how to shrink the thorns with each level + \sa setShrinkFactor() +*/ +double QwtSimpleCompassRose::shrinkFactor() const +{ + return d_data->shrinkFactor; +} + +/*! + Draw the rose + + \param painter Painter + \param center Center point + \param radius Radius of the rose + \param north Position + \param cg Color group +*/ +void QwtSimpleCompassRose::draw( QPainter *painter, const QPointF ¢er, + double radius, double north, QPalette::ColorGroup cg ) const +{ + QPalette pal = palette(); + pal.setCurrentColorGroup( cg ); + + drawRose( painter, pal, center, radius, north, d_data->width, + d_data->numThorns, d_data->numThornLevels, d_data->shrinkFactor ); +} + +/*! + Draw the rose + + \param painter Painter + \param palette Palette + \param center Center of the rose + \param radius Radius of the rose + \param north Position pointing to north + \param width Width of the rose + \param numThorns Number of thorns + \param numThornLevels Number of thorn levels + \param shrinkFactor Factor to shrink the thorns with each level +*/ +void QwtSimpleCompassRose::drawRose( + QPainter *painter, + const QPalette &palette, + const QPointF ¢er, double radius, double north, double width, + int numThorns, int numThornLevels, double shrinkFactor ) +{ + if ( numThorns < 4 ) + numThorns = 4; + + if ( numThorns % 4 ) + numThorns += 4 - numThorns % 4; + + if ( numThornLevels <= 0 ) + numThornLevels = numThorns / 4; + + if ( shrinkFactor >= 1.0 ) + shrinkFactor = 1.0; + + if ( shrinkFactor <= 0.5 ) + shrinkFactor = 0.5; + + painter->save(); + + painter->setPen( Qt::NoPen ); + + for ( int j = 1; j <= numThornLevels; j++ ) + { + double step = qPow( 2.0, j ) * M_PI / numThorns; + if ( step > M_PI_2 ) + break; + + double r = radius; + for ( int k = 0; k < 3; k++ ) + { + if ( j + k < numThornLevels ) + r *= shrinkFactor; + } + + double leafWidth = r * width; + if ( 2.0 * M_PI / step > 32 ) + leafWidth = 16; + + const double origin = qwtRadians( north ); + for ( double angle = origin; + angle < 2.0 * M_PI + origin; angle += step ) + { + const QPointF p = qwtPolar2Pos( center, r, angle ); + const QPointF p1 = qwtPolar2Pos( center, leafWidth, angle + M_PI_2 ); + const QPointF p2 = qwtPolar2Pos( center, leafWidth, angle - M_PI_2 ); + const QPointF p3 = qwtPolar2Pos( center, r, angle + step / 2.0 ); + const QPointF p4 = qwtPolar2Pos( center, r, angle - step / 2.0 ); + + QPainterPath darkPath; + darkPath.moveTo( center ); + darkPath.lineTo( p ); + darkPath.lineTo( qwtIntersection( center, p3, p1, p ) ); + + painter->setBrush( palette.brush( QPalette::Dark ) ); + painter->drawPath( darkPath ); + + QPainterPath lightPath; + lightPath.moveTo( center ); + lightPath.lineTo( p ); + lightPath.lineTo( qwtIntersection( center, p4, p2, p ) ); + + painter->setBrush( palette.brush( QPalette::Light ) ); + painter->drawPath( lightPath ); + } + } + painter->restore(); +} + +/*! + Set the width of the rose heads. Lower value make thinner heads. + The range is limited from 0.03 to 0.4. + + \param width Width +*/ +void QwtSimpleCompassRose::setWidth( double width ) +{ + d_data->width = width; + if ( d_data->width < 0.03 ) + d_data->width = 0.03; + + if ( d_data->width > 0.4 ) + d_data->width = 0.4; +} + +/*! + \return Width of the rose + \sa setWidth() + */ +double QwtSimpleCompassRose::width() const +{ + return d_data->width; +} + +/*! + Set the number of thorns on one level + The number is aligned to a multiple of 4, with a minimum of 4 + + \param numThorns Number of thorns + \sa numThorns(), setNumThornLevels() +*/ +void QwtSimpleCompassRose::setNumThorns( int numThorns ) +{ + if ( numThorns < 4 ) + numThorns = 4; + + if ( numThorns % 4 ) + numThorns += 4 - numThorns % 4; + + d_data->numThorns = numThorns; +} + +/*! + \return Number of thorns + \sa setNumThorns(), setNumThornLevels() +*/ +int QwtSimpleCompassRose::numThorns() const +{ + return d_data->numThorns; +} + +/*! + Set the of thorns levels + + \param numThornLevels Number of thorns levels + \sa setNumThorns(), numThornLevels() +*/ +void QwtSimpleCompassRose::setNumThornLevels( int numThornLevels ) +{ + d_data->numThornLevels = numThornLevels; +} + +/*! + \return Number of thorn levels + \sa setNumThorns(), setNumThornLevels() +*/ +int QwtSimpleCompassRose::numThornLevels() const +{ + return d_data->numThornLevels; +} diff --git a/qwt/src/qwt_compass_rose.h b/qwt/src/qwt_compass_rose.h new file mode 100644 index 000000000..9b715dfe0 --- /dev/null +++ b/qwt/src/qwt_compass_rose.h @@ -0,0 +1,89 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_COMPASS_ROSE_H +#define QWT_COMPASS_ROSE_H 1 + +#include "qwt_global.h" +#include + +class QPainter; + +/*! + \brief Abstract base class for a compass rose +*/ +class QWT_EXPORT QwtCompassRose +{ +public: + //! Destructor + virtual ~QwtCompassRose() {}; + + //! Assign a palette + virtual void setPalette( const QPalette &p ) + { + d_palette = p; + } + + //! \return Current palette + const QPalette &palette() const + { + return d_palette; + } + + /*! + Draw the rose + + \param painter Painter + \param center Center point + \param radius Radius of the rose + \param north Position + \param colorGroup Color group + */ + virtual void draw( QPainter *painter, + const QPointF ¢er, double radius, double north, + QPalette::ColorGroup colorGroup = QPalette::Active ) const = 0; + +private: + QPalette d_palette; +}; + +/*! + \brief A simple rose for QwtCompass +*/ +class QWT_EXPORT QwtSimpleCompassRose: public QwtCompassRose +{ +public: + QwtSimpleCompassRose( int numThorns = 8, int numThornLevels = -1 ); + virtual ~QwtSimpleCompassRose(); + + void setWidth( double w ); + double width() const; + + void setNumThorns( int count ); + int numThorns() const; + + void setNumThornLevels( int count ); + int numThornLevels() const; + + void setShrinkFactor( double factor ); + double shrinkFactor() const; + + virtual void draw( QPainter *, const QPointF ¢er, double radius, + double north, QPalette::ColorGroup = QPalette::Active ) const; + + static void drawRose( QPainter *, const QPalette &, + const QPointF ¢er, double radius, double origin, double width, + int numThorns, int numThornLevels, double shrinkFactor ); + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_compat.h b/qwt/src/qwt_compat.h new file mode 100644 index 000000000..c97cf6b9c --- /dev/null +++ b/qwt/src/qwt_compat.h @@ -0,0 +1,42 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef _QWT_COMPAT_H_ +#define _QWT_COMPAT_H_ + +#include "qwt_global.h" +#include "qwt_interval.h" +#include "qwt_point_3d.h" +#include +#include +#include +#include +#include +#include + +// A couple of definition for Qwt5 compatibility + +#define qwtMax qMax +#define qwtMin qMin +#define qwtAbs qAbs +#define qwtRound qRound + +#define QwtArray QVector + +typedef QList QwtValueList; +typedef QPointF QwtDoublePoint; +typedef QSizeF QwtDoubleSize; +typedef QRectF QwtDoubleRect; + +typedef QPolygon QwtPolygon; +typedef QPolygonF QwtPolygonF; +typedef QwtInterval QwtDoubleInterval; +typedef QwtPoint3D QwtDoublePoint3D; + +#endif diff --git a/qwt/src/qwt_counter.cpp b/qwt/src/qwt_counter.cpp new file mode 100644 index 000000000..31c05c8df --- /dev/null +++ b/qwt/src/qwt_counter.cpp @@ -0,0 +1,785 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_arrow_button.h" +#include "qwt_math.h" +#include "qwt_counter.h" +#include +#include +#include +#include +#include + +class QwtCounter::PrivateData +{ +public: + PrivateData(): + minimum( 0.0 ), + maximum( 0.0 ), + singleStep( 1.0 ), + isValid( false ), + value( 0.0 ), + wrapping( false ) + { + increment[Button1] = 1; + increment[Button2] = 10; + increment[Button3] = 100; + } + + QwtArrowButton *buttonDown[ButtonCnt]; + QwtArrowButton *buttonUp[ButtonCnt]; + QLineEdit *valueEdit; + + int increment[ButtonCnt]; + int numButtons; + + double minimum; + double maximum; + double singleStep; + + bool isValid; + double value; + + bool wrapping; +}; + +/*! + The counter is initialized with a range is set to [0.0, 1.0] with + 0.01 as single step size. The value is invalid. + + The default number of buttons is set to 2. The default increments are: + \li Button 1: 1 step + \li Button 2: 10 steps + \li Button 3: 100 steps + + \param parent + */ +QwtCounter::QwtCounter( QWidget *parent ): + QWidget( parent ) +{ + initCounter(); +} + +void QwtCounter::initCounter() +{ + d_data = new PrivateData; + + QHBoxLayout *layout = new QHBoxLayout( this ); + layout->setSpacing( 0 ); + layout->setMargin( 0 ); + + for ( int i = ButtonCnt - 1; i >= 0; i-- ) + { + QwtArrowButton *btn = + new QwtArrowButton( i + 1, Qt::DownArrow, this ); + btn->setFocusPolicy( Qt::NoFocus ); + btn->installEventFilter( this ); + layout->addWidget( btn ); + + connect( btn, SIGNAL( released() ), SLOT( btnReleased() ) ); + connect( btn, SIGNAL( clicked() ), SLOT( btnClicked() ) ); + + d_data->buttonDown[i] = btn; + } + + d_data->valueEdit = new QLineEdit( this ); + d_data->valueEdit->setReadOnly( false ); + d_data->valueEdit->setValidator( new QDoubleValidator( d_data->valueEdit ) ); + layout->addWidget( d_data->valueEdit ); + + connect( d_data->valueEdit, SIGNAL( editingFinished() ), + SLOT( textChanged() ) ); + + layout->setStretchFactor( d_data->valueEdit, 10 ); + + for ( int i = 0; i < ButtonCnt; i++ ) + { + QwtArrowButton *btn = + new QwtArrowButton( i + 1, Qt::UpArrow, this ); + btn->setFocusPolicy( Qt::NoFocus ); + btn->installEventFilter( this ); + layout->addWidget( btn ); + + connect( btn, SIGNAL( released() ), SLOT( btnReleased() ) ); + connect( btn, SIGNAL( clicked() ), SLOT( btnClicked() ) ); + + d_data->buttonUp[i] = btn; + } + + setNumButtons( 2 ); + setRange( 0.0, 1.0 ); + setSingleStep( 0.001 ); + setValue( 0.0 ); + + setSizePolicy( + QSizePolicy( QSizePolicy::Preferred, QSizePolicy::Fixed ) ); + + setFocusProxy( d_data->valueEdit ); + setFocusPolicy( Qt::StrongFocus ); +} + +//! Destructor +QwtCounter::~QwtCounter() +{ + delete d_data; +} + +/*! + Set the counter to be in valid/invalid state + + When the counter is set to invalid, no numbers are displayed and + the buttons are disabled. + + \param on If true the counter will be set as valid + + \sa setValue(), isValid() +*/ +void QwtCounter::setValid( bool on ) +{ + if ( on != d_data->isValid ) + { + d_data->isValid = on; + + updateButtons(); + + if ( d_data->isValid ) + { + showNumber( value() ); + Q_EMIT valueChanged( value() ); + } + else + { + d_data->valueEdit->setText( QString::null ); + } + } +} + +/*! + \return True, if the value is valid + \sa setValid(), setValue() + */ +bool QwtCounter::isValid() const +{ + return d_data->isValid; +} + +/*! + \brief Allow/disallow the user to manually edit the value + + \param on True disable editing + \sa isReadOnly() +*/ +void QwtCounter::setReadOnly( bool on ) +{ + d_data->valueEdit->setReadOnly( on ); +} + +/*! + \return True, when the line line edit is read only. (default is no) + \sa setReadOnly() + */ +bool QwtCounter::isReadOnly() const +{ + return d_data->valueEdit->isReadOnly(); +} + +/*! + \brief Set a new value without adjusting to the step raster + + The state of the counter is set to be valid. + + \param value New value + + \sa isValid(), value(), valueChanged() + \warning The value is clipped when it lies outside the range. +*/ + +void QwtCounter::setValue( double value ) +{ + const double vmin = qMin( d_data->minimum, d_data->maximum ); + const double vmax = qMax( d_data->minimum, d_data->maximum ); + + value = qBound( vmin, value, vmax ); + + if ( !d_data->isValid || value != d_data->value ) + { + d_data->isValid = true; + d_data->value = value; + + showNumber( value ); + updateButtons(); + + Q_EMIT valueChanged( value ); + } +} + +/*! + \return Current value of the counter + \sa setValue(), valueChanged() + */ +double QwtCounter::value() const +{ + return d_data->value; +} + +/*! + \brief Set the minimum and maximum values + + The maximum is adjusted if necessary to ensure that the range remains valid. + The value might be modified to be inside of the range. + + \param min Minimum value + \param max Maximum value + + \sa minimum(), maximum() + */ +void QwtCounter::setRange( double min, double max ) +{ + max = qMax( min, max ); + + if ( d_data->maximum == max && d_data->minimum == min ) + return; + + d_data->minimum = min; + d_data->maximum = max; + + setSingleStep( singleStep() ); + + const double value = qBound( min, d_data->value, max ); + + if ( value != d_data->value ) + { + d_data->value = value; + + if ( d_data->isValid ) + { + showNumber( value ); + Q_EMIT valueChanged( value ); + } + } + + updateButtons(); +} + +/*! + Set the minimum value of the range + + \param value Minimum value + \sa setRange(), setMaximum(), minimum() + + \note The maximum is adjusted if necessary to ensure that the range remains valid. +*/ +void QwtCounter::setMinimum( double value ) +{ + setRange( value, maximum() ); +} + +/*! + \return The minimum of the range + \sa setRange(), setMinimum(), maximum() +*/ +double QwtCounter::minimum() const +{ + return d_data->minimum; +} + +/*! + Set the maximum value of the range + + \param value Maximum value + \sa setRange(), setMinimum(), maximum() +*/ +void QwtCounter::setMaximum( double value ) +{ + setRange( minimum(), value ); +} + +/*! + \return The maximum of the range + \sa setRange(), setMaximum(), minimum() +*/ +double QwtCounter::maximum() const +{ + return d_data->maximum; +} + +/*! + \brief Set the step size of the counter + + A value <= 0.0 disables stepping + + \param stepSize Single step size + \sa singleStep() +*/ +void QwtCounter::setSingleStep( double stepSize ) +{ + d_data->singleStep = qMax( stepSize, 0.0 ); +} + +/*! + \return Single step size + \sa setSingleStep() + */ +double QwtCounter::singleStep() const +{ + return d_data->singleStep; +} + +/*! + \brief En/Disable wrapping + + If wrapping is true stepping up from maximum() value will take + you to the minimum() value and vice versa. + + \param on En/Disable wrapping + \sa wrapping() + */ +void QwtCounter::setWrapping( bool on ) +{ + d_data->wrapping = on; +} + +/*! + \return True, when wrapping is set + \sa setWrapping() + */ +bool QwtCounter::wrapping() const +{ + return d_data->wrapping; +} + +/*! + Specify the number of buttons on each side of the label + + \param numButtons Number of buttons + \sa numButtons() +*/ +void QwtCounter::setNumButtons( int numButtons ) +{ + if ( numButtons < 0 || numButtons > QwtCounter::ButtonCnt ) + return; + + for ( int i = 0; i < QwtCounter::ButtonCnt; i++ ) + { + if ( i < numButtons ) + { + d_data->buttonDown[i]->show(); + d_data->buttonUp[i]->show(); + } + else + { + d_data->buttonDown[i]->hide(); + d_data->buttonUp[i]->hide(); + } + } + + d_data->numButtons = numButtons; +} + +/*! + \return The number of buttons on each side of the widget. + \sa setNumButtons() +*/ +int QwtCounter::numButtons() const +{ + return d_data->numButtons; +} + +/*! + Specify the number of steps by which the value + is incremented or decremented when a specified button + is pushed. + + \param button Button index + \param numSteps Number of steps + + \sa incSteps() +*/ +void QwtCounter::setIncSteps( QwtCounter::Button button, int numSteps ) +{ + if ( button >= 0 && button < QwtCounter::ButtonCnt ) + d_data->increment[ button ] = numSteps; +} + +/*! + \return The number of steps by which a specified button increments the value + or 0 if the button is invalid. + \param button Button index + + \sa setIncSteps() +*/ +int QwtCounter::incSteps( QwtCounter::Button button ) const +{ + if ( button >= 0 && button < QwtCounter::ButtonCnt ) + return d_data->increment[ button ]; + + return 0; +} + + +/*! + Set the number of increment steps for button 1 + \param nSteps Number of steps +*/ +void QwtCounter::setStepButton1( int nSteps ) +{ + setIncSteps( QwtCounter::Button1, nSteps ); +} + +//! returns the number of increment steps for button 1 +int QwtCounter::stepButton1() const +{ + return incSteps( QwtCounter::Button1 ); +} + +/*! + Set the number of increment steps for button 2 + \param nSteps Number of steps +*/ +void QwtCounter::setStepButton2( int nSteps ) +{ + setIncSteps( QwtCounter::Button2, nSteps ); +} + +//! returns the number of increment steps for button 2 +int QwtCounter::stepButton2() const +{ + return incSteps( QwtCounter::Button2 ); +} + +/*! + Set the number of increment steps for button 3 + \param nSteps Number of steps +*/ +void QwtCounter::setStepButton3( int nSteps ) +{ + setIncSteps( QwtCounter::Button3, nSteps ); +} + +//! returns the number of increment steps for button 3 +int QwtCounter::stepButton3() const +{ + return incSteps( QwtCounter::Button3 ); +} + +//! Set from lineedit +void QwtCounter::textChanged() +{ + bool converted = false; + + const double value = d_data->valueEdit->text().toDouble( &converted ); + if ( converted ) + setValue( value ); +} + +/*! + Handle QEvent::PolishRequest events + \param event Event + \return see QWidget::event() +*/ +bool QwtCounter::event( QEvent *event ) +{ + if ( event->type() == QEvent::PolishRequest ) + { + const int w = d_data->valueEdit->fontMetrics().width( "W" ) + 8; + for ( int i = 0; i < ButtonCnt; i++ ) + { + d_data->buttonDown[i]->setMinimumWidth( w ); + d_data->buttonUp[i]->setMinimumWidth( w ); + } + } + + return QWidget::event( event ); +} + +/*! + Handle key events + + - Ctrl + Qt::Key_Home\n + Step to minimum() + - Ctrl + Qt::Key_End\n + Step to maximum() + - Qt::Key_Up\n + Increment by incSteps(QwtCounter::Button1) + - Qt::Key_Down\n + Decrement by incSteps(QwtCounter::Button1) + - Qt::Key_PageUp\n + Increment by incSteps(QwtCounter::Button2) + - Qt::Key_PageDown\n + Decrement by incSteps(QwtCounter::Button2) + - Shift + Qt::Key_PageUp\n + Increment by incSteps(QwtCounter::Button3) + - Shift + Qt::Key_PageDown\n + Decrement by incSteps(QwtCounter::Button3) + + \param event Key event +*/ +void QwtCounter::keyPressEvent ( QKeyEvent *event ) +{ + bool accepted = true; + + switch ( event->key() ) + { + case Qt::Key_Home: + { + if ( event->modifiers() & Qt::ControlModifier ) + setValue( minimum() ); + else + accepted = false; + break; + } + case Qt::Key_End: + { + if ( event->modifiers() & Qt::ControlModifier ) + setValue( maximum() ); + else + accepted = false; + break; + } + case Qt::Key_Up: + { + incrementValue( d_data->increment[0] ); + break; + } + case Qt::Key_Down: + { + incrementValue( -d_data->increment[0] ); + break; + } + case Qt::Key_PageUp: + case Qt::Key_PageDown: + { + int increment = d_data->increment[0]; + if ( d_data->numButtons >= 2 ) + increment = d_data->increment[1]; + if ( d_data->numButtons >= 3 ) + { + if ( event->modifiers() & Qt::ShiftModifier ) + increment = d_data->increment[2]; + } + if ( event->key() == Qt::Key_PageDown ) + increment = -increment; + incrementValue( increment ); + break; + } + default: + { + accepted = false; + } + } + + if ( accepted ) + { + event->accept(); + return; + } + + QWidget::keyPressEvent ( event ); +} + +/*! + Handle wheel events + \param event Wheel event +*/ +void QwtCounter::wheelEvent( QWheelEvent *event ) +{ + event->accept(); + + if ( d_data->numButtons <= 0 ) + return; + + int increment = d_data->increment[0]; + if ( d_data->numButtons >= 2 ) + { + if ( event->modifiers() & Qt::ControlModifier ) + increment = d_data->increment[1]; + } + if ( d_data->numButtons >= 3 ) + { + if ( event->modifiers() & Qt::ShiftModifier ) + increment = d_data->increment[2]; + } + + for ( int i = 0; i < d_data->numButtons; i++ ) + { + if ( d_data->buttonDown[i]->geometry().contains( event->pos() ) || + d_data->buttonUp[i]->geometry().contains( event->pos() ) ) + { + increment = d_data->increment[i]; + } + } + + const int wheel_delta = 120; + +#if 1 + int delta = event->delta(); + if ( delta >= 2 * wheel_delta ) + delta /= 2; // Never saw an abs(delta) < 240 +#endif + + incrementValue( delta / wheel_delta * increment ); +} + +void QwtCounter::incrementValue( int numSteps ) +{ + const double min = d_data->minimum; + const double max = d_data->maximum; + double stepSize = d_data->singleStep; + + if ( !d_data->isValid || min >= max || stepSize <= 0.0 ) + return; + + +#if 1 + stepSize = qMax( stepSize, 1.0e-10 * ( max - min ) ); +#endif + + double value = d_data->value + numSteps * stepSize; + + if ( d_data->wrapping ) + { + const double range = max - min; + + if ( value < min ) + { + value += ::ceil( ( min - value ) / range ) * range; + } + else if ( value > max ) + { + value -= ::ceil( ( value - max ) / range ) * range; + } + } + else + { + value = qBound( min, value, max ); + } + + value = min + qRound( ( value - min ) / stepSize ) * stepSize; + + if ( stepSize > 1e-12 ) + { + if ( qFuzzyCompare( value + 1.0, 1.0 ) ) + { + // correct rounding error if value = 0 + value = 0.0; + } + else if ( qFuzzyCompare( value, max ) ) + { + // correct rounding error at the border + value = max; + } + } + + if ( value != d_data->value ) + { + d_data->value = value; + showNumber( d_data->value ); + updateButtons(); + + Q_EMIT valueChanged( d_data->value ); + } +} + + +/*! + \brief Update buttons according to the current value + + When the QwtCounter under- or over-flows, the focus is set to the smallest + up- or down-button and counting is disabled. + + Counting is re-enabled on a button release event (mouse or space bar). +*/ +void QwtCounter::updateButtons() +{ + if ( d_data->isValid ) + { + // 1. save enabled state of the smallest down- and up-button + // 2. change enabled state on under- or over-flow + + for ( int i = 0; i < QwtCounter::ButtonCnt; i++ ) + { + d_data->buttonDown[i]->setEnabled( value() > minimum() ); + d_data->buttonUp[i]->setEnabled( value() < maximum() ); + } + } + else + { + for ( int i = 0; i < QwtCounter::ButtonCnt; i++ ) + { + d_data->buttonDown[i]->setEnabled( false ); + d_data->buttonUp[i]->setEnabled( false ); + } + } +} +/*! + Display number string + + \param number Number +*/ +void QwtCounter::showNumber( double number ) +{ + QString text; + text.setNum( number ); + + const int cursorPos = d_data->valueEdit->cursorPosition(); + d_data->valueEdit->setText( text ); + d_data->valueEdit->setCursorPosition( cursorPos ); +} + +//! Button clicked +void QwtCounter::btnClicked() +{ + for ( int i = 0; i < ButtonCnt; i++ ) + { + if ( d_data->buttonUp[i] == sender() ) + incrementValue( d_data->increment[i] ); + + if ( d_data->buttonDown[i] == sender() ) + incrementValue( -d_data->increment[i] ); + } +} + +//! Button released +void QwtCounter::btnReleased() +{ + Q_EMIT buttonReleased( value() ); +} + +//! A size hint +QSize QwtCounter::sizeHint() const +{ + QString tmp; + + int w = tmp.setNum( minimum() ).length(); + int w1 = tmp.setNum( maximum() ).length(); + if ( w1 > w ) + w = w1; + w1 = tmp.setNum( minimum() + singleStep() ).length(); + if ( w1 > w ) + w = w1; + w1 = tmp.setNum( maximum() - singleStep() ).length(); + if ( w1 > w ) + w = w1; + + tmp.fill( '9', w ); + + QFontMetrics fm( d_data->valueEdit->font() ); + w = fm.width( tmp ) + 2; + if ( d_data->valueEdit->hasFrame() ) + w += 2 * style()->pixelMetric( QStyle::PM_DefaultFrameWidth ); + + // Now we replace default sizeHint contribution of d_data->valueEdit by + // what we really need. + + w += QWidget::sizeHint().width() - d_data->valueEdit->sizeHint().width(); + + const int h = qMin( QWidget::sizeHint().height(), + d_data->valueEdit->minimumSizeHint().height() ); + return QSize( w, h ); +} diff --git a/qwt/src/qwt_counter.h b/qwt/src/qwt_counter.h new file mode 100644 index 000000000..8799eddca --- /dev/null +++ b/qwt/src/qwt_counter.h @@ -0,0 +1,161 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_COUNTER_H +#define QWT_COUNTER_H + +#include "qwt_global.h" +#include + +/*! + \brief The Counter Widget + + A Counter consists of a label displaying a number and + one ore more (up to three) push buttons on each side + of the label which can be used to increment or decrement + the counter's value. + + A counter has a range from a minimum value to a maximum value + and a step size. When the wrapping property is set + the counter is circular. + + The number of steps by which a button increments or decrements the value + can be specified using setIncSteps(). The number of buttons can be + changed with setNumButtons(). + + Example: +\code +#include + +QwtCounter *counter = new QwtCounter(parent); + +counter->setRange(0.0, 100.0); // From 0.0 to 100 +counter->setSingleStep( 1.0 ); // Step size 1.0 +counter->setNumButtons(2); // Two buttons each side +counter->setIncSteps(QwtCounter::Button1, 1); // Button 1 increments 1 step +counter->setIncSteps(QwtCounter::Button2, 20); // Button 2 increments 20 steps + +connect(counter, SIGNAL(valueChanged(double)), myClass, SLOT(newValue(double))); +\endcode + */ + +class QWT_EXPORT QwtCounter : public QWidget +{ + Q_OBJECT + + Q_PROPERTY( double value READ value WRITE setValue ) + Q_PROPERTY( double minimum READ minimum WRITE setMinimum ) + Q_PROPERTY( double maximum READ maximum WRITE setMaximum ) + Q_PROPERTY( double singleStep READ singleStep WRITE setSingleStep ) + + Q_PROPERTY( int numButtons READ numButtons WRITE setNumButtons ) + Q_PROPERTY( int stepButton1 READ stepButton1 WRITE setStepButton1 ) + Q_PROPERTY( int stepButton2 READ stepButton2 WRITE setStepButton2 ) + Q_PROPERTY( int stepButton3 READ stepButton3 WRITE setStepButton3 ) + + Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly ) + Q_PROPERTY( bool wrapping READ wrapping WRITE setWrapping ) + +public: + //! Button index + enum Button + { + //! Button intended for minor steps + Button1, + + //! Button intended for medium steps + Button2, + + //! Button intended for large steps + Button3, + + //! Number of buttons + ButtonCnt + }; + + explicit QwtCounter( QWidget *parent = NULL ); + virtual ~QwtCounter(); + + void setValid( bool ); + bool isValid() const; + + void setWrapping( bool ); + bool wrapping() const; + + bool isReadOnly() const; + void setReadOnly( bool ); + + void setNumButtons( int n ); + int numButtons() const; + + void setIncSteps( QwtCounter::Button btn, int nSteps ); + int incSteps( QwtCounter::Button btn ) const; + + virtual QSize sizeHint() const; + + double singleStep() const; + void setSingleStep( double s ); + + void setRange( double min, double max ); + + double minimum() const; + void setMinimum( double min ); + + double maximum() const; + void setMaximum( double max ); + + void setStepButton1( int nSteps ); + int stepButton1() const; + + void setStepButton2( int nSteps ); + int stepButton2() const; + + void setStepButton3( int nSteps ); + int stepButton3() const; + + double value() const; + +public Q_SLOTS: + void setValue( double ); + + +Q_SIGNALS: + /*! + This signal is emitted when a button has been released + \param value The new value + */ + void buttonReleased ( double value ); + + /*! + This signal is emitted when the counter's value has changed + \param value The new value + */ + void valueChanged ( double value ); + +protected: + virtual bool event( QEvent * ); + virtual void wheelEvent( QWheelEvent * ); + virtual void keyPressEvent( QKeyEvent * ); + +private Q_SLOTS: + void btnReleased(); + void btnClicked(); + void textChanged(); + +private: + void incrementValue( int numSteps ); + void initCounter(); + void updateButtons(); + void showNumber( double ); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_curve_fitter.cpp b/qwt/src/qwt_curve_fitter.cpp new file mode 100644 index 000000000..5f09d5d8d --- /dev/null +++ b/qwt/src/qwt_curve_fitter.cpp @@ -0,0 +1,453 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_curve_fitter.h" +#include "qwt_math.h" +#include "qwt_spline.h" +#include +#include + +#if QT_VERSION < 0x040601 +#define qFabs(x) ::fabs(x) +#endif + +//! Constructor +QwtCurveFitter::QwtCurveFitter() +{ +} + +//! Destructor +QwtCurveFitter::~QwtCurveFitter() +{ +} + +class QwtSplineCurveFitter::PrivateData +{ +public: + PrivateData(): + fitMode( QwtSplineCurveFitter::Auto ), + splineSize( 250 ) + { + } + + QwtSpline spline; + QwtSplineCurveFitter::FitMode fitMode; + int splineSize; +}; + +//! Constructor +QwtSplineCurveFitter::QwtSplineCurveFitter() +{ + d_data = new PrivateData; +} + +//! Destructor +QwtSplineCurveFitter::~QwtSplineCurveFitter() +{ + delete d_data; +} + +/*! + Select the algorithm used for building the spline + + \param mode Mode representing a spline algorithm + \sa fitMode() +*/ +void QwtSplineCurveFitter::setFitMode( FitMode mode ) +{ + d_data->fitMode = mode; +} + +/*! + \return Mode representing a spline algorithm + \sa setFitMode() +*/ +QwtSplineCurveFitter::FitMode QwtSplineCurveFitter::fitMode() const +{ + return d_data->fitMode; +} + +/*! + Assign a spline + + \param spline Spline + \sa spline() +*/ +void QwtSplineCurveFitter::setSpline( const QwtSpline &spline ) +{ + d_data->spline = spline; + d_data->spline.reset(); +} + +/*! + \return Spline + \sa setSpline() +*/ +const QwtSpline &QwtSplineCurveFitter::spline() const +{ + return d_data->spline; +} + +/*! + \return Spline + \sa setSpline() +*/ +QwtSpline &QwtSplineCurveFitter::spline() +{ + return d_data->spline; +} + +/*! + Assign a spline size ( has to be at least 10 points ) + + \param splineSize Spline size + \sa splineSize() +*/ +void QwtSplineCurveFitter::setSplineSize( int splineSize ) +{ + d_data->splineSize = qMax( splineSize, 10 ); +} + +/*! + \return Spline size + \sa setSplineSize() +*/ +int QwtSplineCurveFitter::splineSize() const +{ + return d_data->splineSize; +} + +/*! + Find a curve which has the best fit to a series of data points + + \param points Series of data points + \return Curve points +*/ +QPolygonF QwtSplineCurveFitter::fitCurve( const QPolygonF &points ) const +{ + const int size = points.size(); + if ( size <= 2 ) + return points; + + FitMode fitMode = d_data->fitMode; + if ( fitMode == Auto ) + { + fitMode = Spline; + + const QPointF *p = points.data(); + for ( int i = 1; i < size; i++ ) + { + if ( p[i].x() <= p[i-1].x() ) + { + fitMode = ParametricSpline; + break; + } + }; + } + + if ( fitMode == ParametricSpline ) + return fitParametric( points ); + else + return fitSpline( points ); +} + +QPolygonF QwtSplineCurveFitter::fitSpline( const QPolygonF &points ) const +{ + d_data->spline.setPoints( points ); + if ( !d_data->spline.isValid() ) + return points; + + QPolygonF fittedPoints( d_data->splineSize ); + + const double x1 = points[0].x(); + const double x2 = points[int( points.size() - 1 )].x(); + const double dx = x2 - x1; + const double delta = dx / ( d_data->splineSize - 1 ); + + for ( int i = 0; i < d_data->splineSize; i++ ) + { + QPointF &p = fittedPoints[i]; + + const double v = x1 + i * delta; + const double sv = d_data->spline.value( v ); + + p.setX( v ); + p.setY( sv ); + } + d_data->spline.reset(); + + return fittedPoints; +} + +QPolygonF QwtSplineCurveFitter::fitParametric( const QPolygonF &points ) const +{ + int i; + const int size = points.size(); + + QPolygonF fittedPoints( d_data->splineSize ); + QPolygonF splinePointsX( size ); + QPolygonF splinePointsY( size ); + + const QPointF *p = points.data(); + QPointF *spX = splinePointsX.data(); + QPointF *spY = splinePointsY.data(); + + double param = 0.0; + for ( i = 0; i < size; i++ ) + { + const double x = p[i].x(); + const double y = p[i].y(); + if ( i > 0 ) + { + const double delta = qSqrt( qwtSqr( x - spX[i-1].y() ) + + qwtSqr( y - spY[i-1].y() ) ); + param += qMax( delta, 1.0 ); + } + spX[i].setX( param ); + spX[i].setY( x ); + spY[i].setX( param ); + spY[i].setY( y ); + } + + d_data->spline.setPoints( splinePointsX ); + if ( !d_data->spline.isValid() ) + return points; + + const double deltaX = + splinePointsX[size - 1].x() / ( d_data->splineSize - 1 ); + for ( i = 0; i < d_data->splineSize; i++ ) + { + const double dtmp = i * deltaX; + fittedPoints[i].setX( d_data->spline.value( dtmp ) ); + } + + d_data->spline.setPoints( splinePointsY ); + if ( !d_data->spline.isValid() ) + return points; + + const double deltaY = + splinePointsY[size - 1].x() / ( d_data->splineSize - 1 ); + for ( i = 0; i < d_data->splineSize; i++ ) + { + const double dtmp = i * deltaY; + fittedPoints[i].setY( d_data->spline.value( dtmp ) ); + } + + return fittedPoints; +} + +class QwtWeedingCurveFitter::PrivateData +{ +public: + PrivateData(): + tolerance( 1.0 ), + chunkSize( 0 ) + { + } + + double tolerance; + uint chunkSize; +}; + +class QwtWeedingCurveFitter::Line +{ +public: + Line( int i1 = 0, int i2 = 0 ): + from( i1 ), + to( i2 ) + { + } + + int from; + int to; +}; + +/*! + Constructor + + \param tolerance Tolerance + \sa setTolerance(), tolerance() +*/ +QwtWeedingCurveFitter::QwtWeedingCurveFitter( double tolerance ) +{ + d_data = new PrivateData; + setTolerance( tolerance ); +} + +//! Destructor +QwtWeedingCurveFitter::~QwtWeedingCurveFitter() +{ + delete d_data; +} + +/*! + Assign the tolerance + + The tolerance is the maximum distance, that is acceptable + between the original curve and the smoothed curve. + + Increasing the tolerance will reduce the number of the + resulting points. + + \param tolerance Tolerance + + \sa tolerance() +*/ +void QwtWeedingCurveFitter::setTolerance( double tolerance ) +{ + d_data->tolerance = qMax( tolerance, 0.0 ); +} + +/*! + \return Tolerance + \sa setTolerance() +*/ +double QwtWeedingCurveFitter::tolerance() const +{ + return d_data->tolerance; +} + +/*! + Limit the number of points passed to a run of the algorithm + + The runtime of the Douglas Peucker algorithm increases non linear + with the number of points. For a chunk size > 0 the polygon + is split into pieces passed to the algorithm one by one. + + \param numPoints Maximum for the number of points passed to the algorithm + + \sa chunkSize() +*/ +void QwtWeedingCurveFitter::setChunkSize( uint numPoints ) +{ + if ( numPoints > 0 ) + numPoints = qMax( numPoints, 3U ); + + d_data->chunkSize = numPoints; +} + +/*! + + \return Maximum for the number of points passed to a run + of the algorithm - or 0, when unlimited + \sa setChunkSize() +*/ +uint QwtWeedingCurveFitter::chunkSize() const +{ + return d_data->chunkSize; +} + +/*! + \param points Series of data points + \return Curve points +*/ +QPolygonF QwtWeedingCurveFitter::fitCurve( const QPolygonF &points ) const +{ + QPolygonF fittedPoints; + + if ( d_data->chunkSize == 0 ) + { + fittedPoints = simplify( points ); + } + else + { + for ( int i = 0; i < points.size(); i += d_data->chunkSize ) + { + const QPolygonF p = points.mid( i, d_data->chunkSize ); + fittedPoints += simplify( p ); + } + } + + return fittedPoints; +} + +QPolygonF QwtWeedingCurveFitter::simplify( const QPolygonF &points ) const +{ + const double toleranceSqr = d_data->tolerance * d_data->tolerance; + + QStack stack; + stack.reserve( 500 ); + + const QPointF *p = points.data(); + const int nPoints = points.size(); + + QVector usePoint( nPoints, false ); + + stack.push( Line( 0, nPoints - 1 ) ); + + while ( !stack.isEmpty() ) + { + const Line r = stack.pop(); + + // initialize line segment + const double vecX = p[r.to].x() - p[r.from].x(); + const double vecY = p[r.to].y() - p[r.from].y(); + + const double vecLength = qSqrt( vecX * vecX + vecY * vecY ); + + const double unitVecX = ( vecLength != 0.0 ) ? vecX / vecLength : 0.0; + const double unitVecY = ( vecLength != 0.0 ) ? vecY / vecLength : 0.0; + + double maxDistSqr = 0.0; + int nVertexIndexMaxDistance = r.from + 1; + for ( int i = r.from + 1; i < r.to; i++ ) + { + //compare to anchor + const double fromVecX = p[i].x() - p[r.from].x(); + const double fromVecY = p[i].y() - p[r.from].y(); + + double distToSegmentSqr; + if ( fromVecX * unitVecX + fromVecY * unitVecY < 0.0 ) + { + distToSegmentSqr = fromVecX * fromVecX + fromVecY * fromVecY; + } + else + { + const double toVecX = p[i].x() - p[r.to].x(); + const double toVecY = p[i].y() - p[r.to].y(); + const double toVecLength = toVecX * toVecX + toVecY * toVecY; + + const double s = toVecX * ( -unitVecX ) + toVecY * ( -unitVecY ); + if ( s < 0.0 ) + { + distToSegmentSqr = toVecLength; + } + else + { + distToSegmentSqr = qFabs( toVecLength - s * s ); + } + } + + if ( maxDistSqr < distToSegmentSqr ) + { + maxDistSqr = distToSegmentSqr; + nVertexIndexMaxDistance = i; + } + } + if ( maxDistSqr <= toleranceSqr ) + { + usePoint[r.from] = true; + usePoint[r.to] = true; + } + else + { + stack.push( Line( r.from, nVertexIndexMaxDistance ) ); + stack.push( Line( nVertexIndexMaxDistance, r.to ) ); + } + } + + QPolygonF stripped; + for ( int i = 0; i < nPoints; i++ ) + { + if ( usePoint[i] ) + stripped += p[i]; + } + + return stripped; +} diff --git a/qwt/src/qwt_curve_fitter.h b/qwt/src/qwt_curve_fitter.h new file mode 100644 index 000000000..eac376a34 --- /dev/null +++ b/qwt/src/qwt_curve_fitter.h @@ -0,0 +1,139 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_CURVE_FITTER_H +#define QWT_CURVE_FITTER_H + +#include "qwt_global.h" +#include +#include + +class QwtSpline; + +/*! + \brief Abstract base class for a curve fitter +*/ +class QWT_EXPORT QwtCurveFitter +{ +public: + virtual ~QwtCurveFitter(); + + /*! + Find a curve which has the best fit to a series of data points + + \param polygon Series of data points + \return Curve points + */ + virtual QPolygonF fitCurve( const QPolygonF &polygon ) const = 0; + +protected: + QwtCurveFitter(); + +private: + QwtCurveFitter( const QwtCurveFitter & ); + QwtCurveFitter &operator=( const QwtCurveFitter & ); +}; + +/*! + \brief A curve fitter using cubic splines +*/ +class QWT_EXPORT QwtSplineCurveFitter: public QwtCurveFitter +{ +public: + /*! + Spline type + The default setting is Auto + \sa setFitMode(), FitMode() + */ + enum FitMode + { + /*! + Use the default spline algorithm for polygons with + increasing x values ( p[i-1] < p[i] ), otherwise use + a parametric spline algorithm. + */ + Auto, + + //! Use a default spline algorithm + Spline, + + //! Use a parametric spline algorithm + ParametricSpline + }; + + QwtSplineCurveFitter(); + virtual ~QwtSplineCurveFitter(); + + void setFitMode( FitMode ); + FitMode fitMode() const; + + void setSpline( const QwtSpline& ); + const QwtSpline &spline() const; + QwtSpline &spline(); + + void setSplineSize( int size ); + int splineSize() const; + + virtual QPolygonF fitCurve( const QPolygonF & ) const; + +private: + QPolygonF fitSpline( const QPolygonF & ) const; + QPolygonF fitParametric( const QPolygonF & ) const; + + class PrivateData; + PrivateData *d_data; +}; + +/*! + \brief A curve fitter implementing Douglas and Peucker algorithm + + The purpose of the Douglas and Peucker algorithm is that given a 'curve' + composed of line segments to find a curve not too dissimilar but that + has fewer points. The algorithm defines 'too dissimilar' based on the + maximum distance (tolerance) between the original curve and the + smoothed curve. + + The runtime of the algorithm increases non linear ( worst case O( n*n ) ) + and might be very slow for huge polygons. To avoid performance issues + it might be useful to split the polygon ( setChunkSize() ) and to run the algorithm + for these smaller parts. The disadvantage of having no interpolation + at the borders is for most use cases irrelevant. + + The smoothed curve consists of a subset of the points that defined the + original curve. + + In opposite to QwtSplineCurveFitter the Douglas and Peucker algorithm reduces + the number of points. By adjusting the tolerance parameter according to the + axis scales QwtSplineCurveFitter can be used to implement different + level of details to speed up painting of curves of many points. +*/ +class QWT_EXPORT QwtWeedingCurveFitter: public QwtCurveFitter +{ +public: + QwtWeedingCurveFitter( double tolerance = 1.0 ); + virtual ~QwtWeedingCurveFitter(); + + void setTolerance( double ); + double tolerance() const; + + void setChunkSize( uint ); + uint chunkSize() const; + + virtual QPolygonF fitCurve( const QPolygonF & ) const; + +private: + virtual QPolygonF simplify( const QPolygonF & ) const; + + class Line; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_date.cpp b/qwt/src/qwt_date.cpp new file mode 100644 index 000000000..0cf5ca0d2 --- /dev/null +++ b/qwt/src/qwt_date.cpp @@ -0,0 +1,654 @@ +#include "qwt_date.h" +#include +#include +#include +#include +#include + +#if QT_VERSION >= 0x050000 + +typedef qint64 QwtJulianDay; +static const QwtJulianDay minJulianDayD = Q_INT64_C( -784350574879 ); +static const QwtJulianDay maxJulianDayD = Q_INT64_C( 784354017364 ); + +#else + +// QDate stores the Julian day as unsigned int, but +// but it is QDate::fromJulianDay( int ). That's why +// we have the range [ 1, INT_MAX ] +typedef int QwtJulianDay; +static const QwtJulianDay minJulianDayD = 1; +static const QwtJulianDay maxJulianDayD = std::numeric_limits::max(); + +#endif + +static inline Qt::DayOfWeek qwtFirstDayOfWeek() +{ +#if QT_VERSION >= 0x040800 + return QLocale().firstDayOfWeek(); +#else + + switch( QLocale().country() ) + { + case QLocale::Maldives: + return Qt::Friday; + + case QLocale::Afghanistan: + case QLocale::Algeria: + case QLocale::Bahrain: + case QLocale::Djibouti: + case QLocale::Egypt: + case QLocale::Eritrea: + case QLocale::Ethiopia: + case QLocale::Iran: + case QLocale::Iraq: + case QLocale::Jordan: + case QLocale::Kenya: + case QLocale::Kuwait: + case QLocale::LibyanArabJamahiriya: + case QLocale::Morocco: + case QLocale::Oman: + case QLocale::Qatar: + case QLocale::SaudiArabia: + case QLocale::Somalia: + case QLocale::Sudan: + case QLocale::Tunisia: + case QLocale::Yemen: + return Qt::Saturday; + + case QLocale::AmericanSamoa: + case QLocale::Argentina: + case QLocale::Azerbaijan: + case QLocale::Botswana: + case QLocale::Canada: + case QLocale::China: + case QLocale::FaroeIslands: + case QLocale::Georgia: + case QLocale::Greenland: + case QLocale::Guam: + case QLocale::HongKong: + case QLocale::Iceland: + case QLocale::India: + case QLocale::Ireland: + case QLocale::Israel: + case QLocale::Jamaica: + case QLocale::Japan: + case QLocale::Kyrgyzstan: + case QLocale::Lao: + case QLocale::Malta: + case QLocale::MarshallIslands: + case QLocale::Macau: + case QLocale::Mongolia: + case QLocale::NewZealand: + case QLocale::NorthernMarianaIslands: + case QLocale::Pakistan: + case QLocale::Philippines: + case QLocale::RepublicOfKorea: + case QLocale::Singapore: + case QLocale::SyrianArabRepublic: + case QLocale::Taiwan: + case QLocale::Thailand: + case QLocale::TrinidadAndTobago: + case QLocale::UnitedStates: + case QLocale::UnitedStatesMinorOutlyingIslands: + case QLocale::USVirginIslands: + case QLocale::Uzbekistan: + case QLocale::Zimbabwe: + return Qt::Sunday; + + default: + return Qt::Monday; + } +#endif +} + +static inline void qwtFloorTime( + QwtDate::IntervalType intervalType, QDateTime &dt ) +{ + // when dt is inside the special hour where DST is ending + // an hour is no unique. Therefore we have to + // use UTC time. + + const Qt::TimeSpec timeSpec = dt.timeSpec(); + + if ( timeSpec == Qt::LocalTime ) + dt = dt.toTimeSpec( Qt::UTC ); + + const QTime t = dt.time(); + switch( intervalType ) + { + case QwtDate::Second: + { + dt.setTime( QTime( t.hour(), t.minute(), t.second() ) ); + break; + } + case QwtDate::Minute: + { + dt.setTime( QTime( t.hour(), t.minute(), 0 ) ); + break; + } + case QwtDate::Hour: + { + dt.setTime( QTime( t.hour(), 0, 0 ) ); + break; + } + default: + break; + } + + if ( timeSpec == Qt::LocalTime ) + dt = dt.toTimeSpec( Qt::LocalTime ); +} + +static inline QDateTime qwtToTimeSpec( + const QDateTime &dt, Qt::TimeSpec spec ) +{ + if ( dt.timeSpec() == spec ) + return dt; + + const qint64 jd = dt.date().toJulianDay(); + if ( jd < 0 || jd >= INT_MAX ) + { + // the conversion between local time and UTC + // is internally limited. To avoid + // overflows we simply ignore the difference + // for those dates + + QDateTime dt2 = dt; + dt2.setTimeSpec( spec ); + return dt2; + } + + return dt.toTimeSpec( spec ); +} + +static inline double qwtToJulianDay( int year, int month, int day ) +{ + // code from QDate but using doubles to avoid overflows + // for large values + + const int m1 = ( month - 14 ) / 12; + const int m2 = ( 367 * ( month - 2 - 12 * m1 ) ) / 12; + const double y1 = ::floor( ( 4900.0 + year + m1 ) / 100 ); + + return ::floor( ( 1461.0 * ( year + 4800 + m1 ) ) / 4 ) + m2 + - ::floor( ( 3 * y1 ) / 4 ) + day - 32075; +} + +static inline qint64 qwtFloorDiv64( qint64 a, int b ) +{ + if ( a < 0 ) + a -= b - 1; + + return a / b; +} + +static inline qint64 qwtFloorDiv( int a, int b ) +{ + if ( a < 0 ) + a -= b - 1; + + return a / b; +} + +static inline QDate qwtToDate( int year, int month = 1, int day = 1 ) +{ +#if QT_VERSION >= 0x050000 + return QDate( year, month, day ); +#else + if ( year > 100000 ) + { + // code from QDate but using doubles to avoid overflows + // for large values + + const int m1 = ( month - 14 ) / 12; + const int m2 = ( 367 * ( month - 2 - 12 * m1 ) ) / 12; + const double y1 = ::floor( ( 4900.0 + year + m1 ) / 100 ); + + const double jd = ::floor( ( 1461.0 * ( year + 4800 + m1 ) ) / 4 ) + m2 + - ::floor( ( 3 * y1 ) / 4 ) + day - 32075; + + if ( jd > maxJulianDayD ) + { + qWarning() << "qwtToDate: overflow"; + return QDate(); + } + + return QDate::fromJulianDay( static_cast( jd ) ); + } + else + { + return QDate( year, month, day ); + } +#endif +} + +/*! + Translate from double to QDateTime + + \param value Number of milliseconds since the epoch, + 1970-01-01T00:00:00 UTC + \param timeSpec Time specification + \return Datetime value + + \sa toDouble(), QDateTime::setMSecsSinceEpoch() + \note The return datetime for Qt::OffsetFromUTC will be Qt::UTC + */ +QDateTime QwtDate::toDateTime( double value, Qt::TimeSpec timeSpec ) +{ + const int msecsPerDay = 86400000; + + const double days = static_cast( ::floor( value / msecsPerDay ) ); + + const double jd = QwtDate::JulianDayForEpoch + days; + if ( ( jd > maxJulianDayD ) || ( jd < minJulianDayD ) ) + { + qWarning() << "QwtDate::toDateTime: overflow"; + return QDateTime(); + } + + const QDate d = QDate::fromJulianDay( static_cast( jd ) ); + + const int msecs = static_cast( value - days * msecsPerDay ); + + static const QTime timeNull( 0, 0, 0, 0 ); + + QDateTime dt( d, timeNull.addMSecs( msecs ), Qt::UTC ); + + if ( timeSpec == Qt::LocalTime ) + dt = qwtToTimeSpec( dt, timeSpec ); + + return dt; +} + +/*! + Translate from QDateTime to double + + \param dateTime Datetime value + \return Number of milliseconds since 1970-01-01T00:00:00 UTC has passed. + + \sa toDateTime(), QDateTime::toMSecsSinceEpoch() + \warning For values very far below or above 1970-01-01 UTC rounding errors + will happen due to the limited significance of a double. + */ +double QwtDate::toDouble( const QDateTime &dateTime ) +{ + const int msecsPerDay = 86400000; + + const QDateTime dt = qwtToTimeSpec( dateTime, Qt::UTC ); + + const double days = dt.date().toJulianDay() - QwtDate::JulianDayForEpoch; + + const QTime time = dt.time(); + const double secs = 3600.0 * time.hour() + + 60.0 * time.minute() + time.second(); + + return days * msecsPerDay + time.msec() + 1000.0 * secs; +} + +/*! + Ceil a datetime according the interval type + + \param dateTime Datetime value + \param intervalType Interval type, how to ceil. + F.e. when intervalType = QwtDate::Months, the result + will be ceiled to the next beginning of a month + \return Ceiled datetime + \sa floor() + */ +QDateTime QwtDate::ceil( const QDateTime &dateTime, IntervalType intervalType ) +{ + if ( dateTime.date() >= QwtDate::maxDate() ) + return dateTime; + + QDateTime dt = dateTime; + + switch ( intervalType ) + { + case QwtDate::Millisecond: + { + break; + } + case QwtDate::Second: + { + qwtFloorTime( QwtDate::Second, dt ); + if ( dt < dateTime ) + dt.addSecs( 1 ); + + break; + } + case QwtDate::Minute: + { + qwtFloorTime( QwtDate::Minute, dt ); + if ( dt < dateTime ) + dt.addSecs( 60 ); + + break; + } + case QwtDate::Hour: + { + qwtFloorTime( QwtDate::Hour, dt ); + if ( dt < dateTime ) + dt.addSecs( 3600 ); + + break; + } + case QwtDate::Day: + { + dt.setTime( QTime( 0, 0 ) ); + if ( dt < dateTime ) + dt = dt.addDays( 1 ); + + break; + } + case QwtDate::Week: + { + dt.setTime( QTime( 0, 0 ) ); + if ( dt < dateTime ) + dt = dt.addDays( 1 ); + + int days = qwtFirstDayOfWeek() - dt.date().dayOfWeek(); + if ( days < 0 ) + days += 7; + + dt = dt.addDays( days ); + + break; + } + case QwtDate::Month: + { + dt.setTime( QTime( 0, 0 ) ); + dt.setDate( qwtToDate( dateTime.date().year(), + dateTime.date().month() ) ); + + if ( dt < dateTime ) + dt.addMonths( 1 ); + + break; + } + case QwtDate::Year: + { + dt.setTime( QTime( 0, 0 ) ); + + const QDate d = dateTime.date(); + + int year = d.year(); + if ( d.month() > 1 || d.day() > 1 || !dateTime.time().isNull() ) + year++; + + if ( year == 0 ) + year++; // there is no year 0 + + dt.setDate( qwtToDate( year ) ); + break; + } + } + + return dt; +} + +/*! + Floor a datetime according the interval type + + \param dateTime Datetime value + \param intervalType Interval type, how to ceil. + F.e. when intervalType = QwtDate::Months, + the result will be ceiled to the next + beginning of a month + \return Floored datetime + \sa floor() + */ +QDateTime QwtDate::floor( const QDateTime &dateTime, + IntervalType intervalType ) +{ + if ( dateTime.date() <= QwtDate::minDate() ) + return dateTime; + + QDateTime dt = dateTime; + + switch ( intervalType ) + { + case QwtDate::Millisecond: + { + break; + } + case QwtDate::Second: + case QwtDate::Minute: + case QwtDate::Hour: + { + qwtFloorTime( intervalType, dt ); + break; + } + case QwtDate::Day: + { + dt.setTime( QTime( 0, 0 ) ); + break; + } + case QwtDate::Week: + { + dt.setTime( QTime( 0, 0 ) ); + + int days = dt.date().dayOfWeek() - qwtFirstDayOfWeek(); + if ( days < 0 ) + days += 7; + + dt = dt.addDays( -days ); + + break; + } + case QwtDate::Month: + { + dt.setTime( QTime( 0, 0 ) ); + + const QDate date = qwtToDate( dt.date().year(), + dt.date().month() ); + dt.setDate( date ); + + break; + } + case QwtDate::Year: + { + dt.setTime( QTime( 0, 0 ) ); + + const QDate date = qwtToDate( dt.date().year() ); + dt.setDate( date ); + + break; + } + } + + return dt; +} + +/*! + Minimum for the supported date range + + The range of valid dates depends on how QDate stores the + Julian day internally. + + - For Qt4 it is "Tue Jan 2 -4713" + - For Qt5 it is "Thu Jan 1 -2147483648" + + \return minimum of the date range + \sa maxDate() + */ +QDate QwtDate::minDate() +{ + static QDate date; + if ( !date.isValid() ) + date = QDate::fromJulianDay( minJulianDayD ); + + return date; +} + +/*! + Maximum for the supported date range + + The range of valid dates depends on how QDate stores the + Julian day internally. + + - For Qt4 it is "Tue Jun 3 5874898" + - For Qt5 it is "Tue Dec 31 2147483647" + + \return maximum of the date range + \sa minDate() + \note The maximum differs between Qt4 and Qt5 + */ +QDate QwtDate::maxDate() +{ + static QDate date; + if ( !date.isValid() ) + date = QDate::fromJulianDay( maxJulianDayD ); + + return date; +} + +/*! + \brief Date of the first day of the first week for a year + + The first day of a week depends on the current locale + ( QLocale::firstDayOfWeek() ). + + \param year Year + \param type Option how to identify the first week + \return First day of week 0 + + \sa QLocale::firstDayOfWeek(), weekNumber() + */ +QDate QwtDate::dateOfWeek0( int year, Week0Type type ) +{ + const Qt::DayOfWeek firstDayOfWeek = qwtFirstDayOfWeek(); + + QDate dt0( year, 1, 1 ); + + // floor to the first day of the week + int days = dt0.dayOfWeek() - firstDayOfWeek; + if ( days < 0 ) + days += 7; + + dt0 = dt0.addDays( -days ); + + if ( type == QwtDate::FirstThursday ) + { + // according to ISO 8601 the first week is defined + // by the first thursday. + + int d = Qt::Thursday - firstDayOfWeek; + if ( d < 0 ) + d += 7; + + if ( dt0.addDays( d ).year() < year ) + dt0 = dt0.addDays( 7 ); + } + + return dt0; +} + +/*! + Find the week number of a date + + - QwtDate::FirstThursday\n + Corresponding to ISO 8601 ( see QDate::weekNumber() ). + + - QwtDate::FirstDay\n + Number of weeks that have begun since dateOfWeek0(). + + \param date Date + \param type Option how to identify the first week + + \return Week number, starting with 1 + */ +int QwtDate::weekNumber( const QDate &date, Week0Type type ) +{ + int weekNo; + + if ( type == QwtDate::FirstDay ) + { + const QDate day0 = dateOfWeek0( date.year(), type ); + weekNo = day0.daysTo( date ) / 7 + 1; + } + else + { + weekNo = date.weekNumber(); + } + + return weekNo; +} + +/*! + Offset in seconds from Coordinated Universal Time + + The offset depends on the time specification of dateTime: + + - Qt::UTC + 0, dateTime has no offset + - Qt::OffsetFromUTC + returns dateTime.utcOffset() + - Qt::LocalTime: + number of seconds from the UTC + + For Qt::LocalTime the offset depends on the timezone and + daylight savings. + + \param dateTime Datetime value + \return Offset in seconds + */ +int QwtDate::utcOffset( const QDateTime &dateTime ) +{ + int seconds = 0; + + switch( dateTime.timeSpec() ) + { + case Qt::UTC: + { + break; + } + case Qt::OffsetFromUTC: + { + seconds = dateTime.utcOffset(); + } + default: + { + const QDateTime dt1( dateTime.date(), dateTime.time(), Qt::UTC ); + seconds = dateTime.secsTo( dt1 ); + } + } + + return seconds; +} + +/*! + Translate a datetime into a string + + Beside the format expressions documented in QDateTime::toString() + the following expressions are supported: + + - w\n + week number: ( 1 - 53 ) + - ww\n + week number with a leading zero ( 01 - 53 ) + + \param dateTime Datetime value + \param format Format string + \param week0Type Specification of week 0 + + \return Datetime string + \sa QDateTime::toString(), weekNumber(), QwtDateScaleDraw + */ +QString QwtDate::toString( const QDateTime &dateTime, + const QString & format, Week0Type week0Type ) +{ + QString weekNo; + weekNo.setNum( QwtDate::weekNumber( dateTime.date(), week0Type ) ); + + QString weekNoWW; + if ( weekNo.length() == 1 ) + weekNoWW += "0"; + weekNoWW += weekNo; + + QString fmt = format; + fmt.replace( "ww", weekNoWW ); + fmt.replace( "w", weekNo ); + + return dateTime.toString( fmt ); +} diff --git a/qwt/src/qwt_date.h b/qwt/src/qwt_date.h new file mode 100644 index 000000000..30422a1c1 --- /dev/null +++ b/qwt/src/qwt_date.h @@ -0,0 +1,128 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef _QWT_DATE_H_ +#define _QWT_DATE_H_ + +#include "qwt_global.h" +#include + +/*! + \brief A collection of methods around date/time values + + Qt offers convenient classes for dealing with date/time values, + but Qwt uses coordinate systems that are based on doubles. + QwtDate offers methods to translate from QDateTime to double and v.v. + + A double is interpreted as the number of milliseconds since + 1970-01-01T00:00:00 Universal Coordinated Time - also known + as "The Epoch". + + While the range of the Julian day in Qt4 is limited to [0, MAX_INT], + Qt5 stores it as qint64 offering a huge range of valid dates. + As the significance of a double is below this ( assuming a + fraction of 52 bits ) the translation is not + bijective with rounding errors for dates very far from Epoch. + For a resolution of 1 ms those start to happen for dates above the + year 144683. + + An axis for a date/time interval is expected to be aligned + and divided in time/date units like seconds, minutes, ... + QwtDate offers several algorithms that are needed to + calculate these axes. + + \sa QwtDateScaleEngine, QwtDateScaleDraw, QDate, QTime +*/ +class QWT_EXPORT QwtDate +{ +public: + /*! + How to identify the first week of year differs between + countries. + */ + enum Week0Type + { + /*! + According to ISO 8601 the first week of a year is defined + as "the week with the year's first Thursday in it". + + FirstThursday corresponds to the numbering that is + implemented in QDate::weekNumber(). + */ + FirstThursday, + + /*! + "The week with January 1.1 in it." + + In the U.S. this definition is more common than + FirstThursday. + */ + FirstDay + }; + + /*! + Classification of an time interval + + Time intervals needs to be classified to decide how to + align and divide it. + */ + enum IntervalType + { + //! The interval is related to milliseconds + Millisecond, + + //! The interval is related to seconds + Second, + + //! The interval is related to minutes + Minute, + + //! The interval is related to hours + Hour, + + //! The interval is related to days + Day, + + //! The interval is related to weeks + Week, + + //! The interval is related to months + Month, + + //! The interval is related to years + Year + }; + + enum + { + //! The Julian day of "The Epoch" + JulianDayForEpoch = 2440588 + }; + + static QDate minDate(); + static QDate maxDate(); + + static QDateTime toDateTime( double value, + Qt::TimeSpec = Qt::UTC ); + + static double toDouble( const QDateTime & ); + + static QDateTime ceil( const QDateTime &, IntervalType ); + static QDateTime floor( const QDateTime &, IntervalType ); + + static QDate dateOfWeek0( int year, Week0Type ); + static int weekNumber( const QDate &, Week0Type ); + + static int utcOffset( const QDateTime & ); + + static QString toString( const QDateTime &, + const QString & format, Week0Type ); +}; + +#endif diff --git a/qwt/src/qwt_date_scale_draw.cpp b/qwt/src/qwt_date_scale_draw.cpp new file mode 100644 index 000000000..7cfc6dec0 --- /dev/null +++ b/qwt/src/qwt_date_scale_draw.cpp @@ -0,0 +1,269 @@ +#include "qwt_date_scale_draw.h" + +class QwtDateScaleDraw::PrivateData +{ +public: + PrivateData( Qt::TimeSpec spec ): + timeSpec( spec ), + utcOffset( 0 ), + week0Type( QwtDate::FirstThursday ) + { + dateFormats[ QwtDate::Millisecond ] = "hh:mm:ss:zzz\nddd dd MMM yyyy"; + dateFormats[ QwtDate::Second ] = "hh:mm:ss\nddd dd MMM yyyy"; + dateFormats[ QwtDate::Minute ] = "hh:mm\nddd dd MMM yyyy"; + dateFormats[ QwtDate::Hour ] = "hh:mm\nddd dd MMM yyyy"; + dateFormats[ QwtDate::Day ] = "ddd dd MMM yyyy"; + dateFormats[ QwtDate::Week ] = "Www yyyy"; + dateFormats[ QwtDate::Month ] = "MMM yyyy"; + dateFormats[ QwtDate::Year ] = "yyyy"; + } + + Qt::TimeSpec timeSpec; + int utcOffset; + QwtDate::Week0Type week0Type; + QString dateFormats[ QwtDate::Year + 1 ]; +}; + +/*! + \brief Constructor + + The default setting is to display tick labels for the + given time specification. The first week of a year is defined like + for QwtDate::FirstThursday. + + \param timeSpec Time specification + + \sa setTimeSpec(), setWeek0Type() + */ +QwtDateScaleDraw::QwtDateScaleDraw( Qt::TimeSpec timeSpec ) +{ + d_data = new PrivateData( timeSpec ); +} + +//! Destructor +QwtDateScaleDraw::~QwtDateScaleDraw() +{ + delete d_data; +} + +/*! + Set the time specification used for the tick labels + + \param timeSpec Time specification + \sa timeSpec(), setUtcOffset(), toDateTime() + */ +void QwtDateScaleDraw::setTimeSpec( Qt::TimeSpec timeSpec ) +{ + d_data->timeSpec = timeSpec; +} + +/*! + \return Time specification used for the tick labels + \sa setTimeSpec(), utcOffset(), toDateTime() + */ +Qt::TimeSpec QwtDateScaleDraw::timeSpec() const +{ + return d_data->timeSpec; +} + +/*! + Set the offset in seconds from Coordinated Universal Time + + \param seconds Offset in seconds + + \note The offset has no effect beside for the time specification + Qt::OffsetFromUTC. + + \sa QDate::utcOffset(), setTimeSpec(), toDateTime() + */ +void QwtDateScaleDraw::setUtcOffset( int seconds ) +{ + d_data->utcOffset = seconds; +} + +/*! + \return Offset in seconds from Coordinated Universal Time + \note The offset has no effect beside for the time specification + Qt::OffsetFromUTC. + + \sa QDate::setUtcOffset(), setTimeSpec(), toDateTime() + */ +int QwtDateScaleDraw::utcOffset() const +{ + return d_data->utcOffset; +} + +/*! + Sets how to identify the first week of a year. + + \param week0Type Mode how to identify the first week of a year + + \sa week0Type(). + \note week0Type has no effect beside for intervals classified as + QwtDate::Week. + */ +void QwtDateScaleDraw::setWeek0Type( QwtDate::Week0Type week0Type ) +{ + d_data->week0Type = week0Type; +} + +/*! + \return Setting how to identify the first week of a year. + \sa setWeek0Type() + */ +QwtDate::Week0Type QwtDateScaleDraw::week0Type() const +{ + return d_data->week0Type; +} + +/*! + Set the default format string for an datetime interval type + + \param intervalType Interval type + \param format Default format string + + \sa dateFormat(), dateFormatOfDate(), QwtDate::toString() + */ +void QwtDateScaleDraw::setDateFormat( + QwtDate::IntervalType intervalType, const QString &format ) +{ + if ( intervalType >= QwtDate::Millisecond && + intervalType <= QwtDate::Year ) + { + d_data->dateFormats[ intervalType ] = format; + } +} + +/*! + \param intervalType Interval type + \return Default format string for an datetime interval type + \sa setDateFormat(), dateFormatOfDate() + */ +QString QwtDateScaleDraw::dateFormat( + QwtDate::IntervalType intervalType ) const +{ + if ( intervalType >= QwtDate::Millisecond && + intervalType <= QwtDate::Year ) + { + return d_data->dateFormats[ intervalType ]; + } + + return QString::null; +} + +/*! + Format string for the representation of a datetime + + dateFormatOfDate() is intended to be overloaded for + situations, where formats are individual for specific + datetime values. + + The default setting ignores dateTime and return + the default format for the interval type. + + \param dateTime Datetime value + \param intervalType Interval type + \return Format string + + \sa setDateFormat(), QwtDate::toString() + */ +QString QwtDateScaleDraw::dateFormatOfDate( const QDateTime &dateTime, + QwtDate::IntervalType intervalType ) const +{ + Q_UNUSED( dateTime ) + + if ( intervalType >= QwtDate::Millisecond && + intervalType <= QwtDate::Year ) + { + return d_data->dateFormats[ intervalType ]; + } + + return d_data->dateFormats[ QwtDate::Second ]; +} + +/*! + \brief Convert a value into its representing label + + The value is converted to a datetime value using toDateTime() + and converted to a plain text using QwtDate::toString(). + + \param value Value + \return Label string. + + \sa dateFormatOfDate() +*/ +QwtText QwtDateScaleDraw::label( double value ) const +{ + const QDateTime dt = toDateTime( value ); + const QString fmt = dateFormatOfDate( + dt, intervalType( scaleDiv() ) ); + + return QwtDate::toString( dt, fmt, d_data->week0Type ); +} + +/*! + Find the less detailed datetime unit, where no rounding + errors happen. + + \param scaleDiv Scale division + \return Interval type + + \sa dateFormatOfDate() + */ +QwtDate::IntervalType QwtDateScaleDraw::intervalType( + const QwtScaleDiv &scaleDiv ) const +{ + int intvType = QwtDate::Year; + + bool alignedToWeeks = true; + + const QList ticks = scaleDiv.ticks( QwtScaleDiv::MajorTick ); + for ( int i = 0; i < ticks.size(); i++ ) + { + const QDateTime dt = toDateTime( ticks[i] ); + for ( int j = QwtDate::Second; j <= intvType; j++ ) + { + const QDateTime dt0 = QwtDate::floor( dt, + static_cast( j ) ); + + if ( dt0 != dt ) + { + if ( j == QwtDate::Week ) + { + alignedToWeeks = false; + } + else + { + intvType = j - 1; + break; + } + } + } + + if ( intvType == QwtDate::Millisecond ) + break; + } + + if ( intvType == QwtDate::Week && !alignedToWeeks ) + intvType = QwtDate::Day; + + return static_cast( intvType ); +} + +/*! + Translate a double value into a QDateTime object. + + \return QDateTime object initialized with timeSpec() and utcOffset(). + \sa timeSpec(), utcOffset(), QwtDate::toDateTime() + */ +QDateTime QwtDateScaleDraw::toDateTime( double value ) const +{ + QDateTime dt = QwtDate::toDateTime( value, d_data->timeSpec ); + if ( d_data->timeSpec == Qt::OffsetFromUTC ) + { + dt = dt.addSecs( d_data->utcOffset ); + dt.setUtcOffset( d_data->utcOffset ); + } + + return dt; +} diff --git a/qwt/src/qwt_date_scale_draw.h b/qwt/src/qwt_date_scale_draw.h new file mode 100644 index 000000000..92589e890 --- /dev/null +++ b/qwt/src/qwt_date_scale_draw.h @@ -0,0 +1,77 @@ +#ifndef _QWT_DATE_SCALE_DRAW_H_ +#define _QWT_DATE_SCALE_DRAW_H_ 1 + +#include "qwt_global.h" +#include "qwt_scale_draw.h" +#include "qwt_date.h" + +/*! + \brief A class for drawing datetime scales + + QwtDateScaleDraw displays values as datetime labels. + The format of the labels depends on the alignment of + the major tick labels. + + The default format strings are: + + - Millisecond\n + "hh:mm:ss:zzz\nddd dd MMM yyyy" + - Second\n + "hh:mm:ss\nddd dd MMM yyyy" + - Minute\n + "hh:mm\nddd dd MMM yyyy" + - Hour\n + "hh:mm\nddd dd MMM yyyy" + - Day\n + "ddd dd MMM yyyy" + - Week\n + "Www yyyy" + - Month\n + "MMM yyyy" + - Year\n + "yyyy" + + The format strings can be modified using setDateFormat() + or individually for each tick label by overloading dateFormatOfDate(), + + Usually QwtDateScaleDraw is used in combination with + QwtDateScaleEngine, that calculates scales for datetime + intervals. + + \sa QwtDateScaleEngine, QwtPlot::setAxisScaleDraw() +*/ +class QWT_EXPORT QwtDateScaleDraw: public QwtScaleDraw +{ +public: + QwtDateScaleDraw( Qt::TimeSpec = Qt::LocalTime ); + virtual ~QwtDateScaleDraw(); + + void setDateFormat( QwtDate::IntervalType, const QString & ); + QString dateFormat( QwtDate::IntervalType ) const; + + void setTimeSpec( Qt::TimeSpec ); + Qt::TimeSpec timeSpec() const; + + void setUtcOffset( int seconds ); + int utcOffset() const; + + void setWeek0Type( QwtDate::Week0Type ); + QwtDate::Week0Type week0Type() const; + + virtual QwtText label( double ) const; + + QDateTime toDateTime( double ) const; + +protected: + virtual QwtDate::IntervalType + intervalType( const QwtScaleDiv & ) const; + + virtual QString dateFormatOfDate( const QDateTime &, + QwtDate::IntervalType ) const; + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_date_scale_engine.cpp b/qwt/src/qwt_date_scale_engine.cpp new file mode 100644 index 000000000..85c1f175a --- /dev/null +++ b/qwt/src/qwt_date_scale_engine.cpp @@ -0,0 +1,1300 @@ +#include "qwt_date_scale_engine.h" +#include "qwt_math.h" +#include "qwt_transform.h" +#include +#include + +static inline double qwtMsecsForType( QwtDate::IntervalType type ) +{ + static const double msecs[] = + { + 1.0, + 1000.0, + 60.0 * 1000.0, + 3600.0 * 1000.0, + 24.0 * 3600.0 * 1000.0, + 7.0 * 24.0 * 3600.0 * 1000.0, + 30.0 * 24.0 * 3600.0 * 1000.0, + 365.0 * 24.0 * 3600.0 * 1000.0, + }; + + if ( type < 0 || type >= static_cast( sizeof( msecs ) / sizeof( msecs[0] ) ) ) + return 1.0; + + return msecs[ type ]; +} + +static inline int qwtAlignValue( + double value, double stepSize, bool up ) +{ + double d = value / stepSize; + d = up ? ::ceil( d ) : ::floor( d ); + + return static_cast( d * stepSize ); +} + +static double qwtIntervalWidth( const QDateTime &minDate, + const QDateTime &maxDate, QwtDate::IntervalType intervalType ) +{ + switch( intervalType ) + { + case QwtDate::Millisecond: + { + const double secsTo = minDate.secsTo( maxDate ); + const double msecs = maxDate.time().msec() - + minDate.time().msec(); + + return secsTo * 1000 + msecs; + } + case QwtDate::Second: + { + return minDate.secsTo( maxDate ); + } + case QwtDate::Minute: + { + const double secsTo = minDate.secsTo( maxDate ); + return ::floor( secsTo / 60 ); + } + case QwtDate::Hour: + { + const double secsTo = minDate.secsTo( maxDate ); + return ::floor( secsTo / 3600 ); + } + case QwtDate::Day: + { + return minDate.daysTo( maxDate ); + } + case QwtDate::Week: + { + return ::floor( minDate.daysTo( maxDate ) / 7.0 ); + } + case QwtDate::Month: + { + const double years = + double( maxDate.date().year() ) - minDate.date().year(); + + int months = maxDate.date().month() - minDate.date().month(); + if ( maxDate.date().day() < minDate.date().day() ) + months--; + + return years * 12 + months; + } + case QwtDate::Year: + { + double years = + double( maxDate.date().year() ) - minDate.date().year(); + + if ( maxDate.date().month() < minDate.date().month() ) + years -= 1.0; + + return years; + } + } + + return 0.0; +} + +static double qwtRoundedIntervalWidth( + const QDateTime &minDate, const QDateTime &maxDate, + QwtDate::IntervalType intervalType ) +{ + const QDateTime minD = QwtDate::floor( minDate, intervalType ); + const QDateTime maxD = QwtDate::ceil( maxDate, intervalType ); + + return qwtIntervalWidth( minD, maxD, intervalType ); +} + +static inline int qwtStepCount( int intervalSize, int maxSteps, + const int limits[], size_t numLimits ) +{ + for ( uint i = 0; i < numLimits; i++ ) + { + const int numSteps = intervalSize / limits[ i ]; + + if ( numSteps > 1 && numSteps <= maxSteps && + numSteps * limits[ i ] == intervalSize ) + { + return numSteps; + } + } + + return 0; +} + +static int qwtStepSize( int intervalSize, int maxSteps, uint base ) +{ + if ( maxSteps <= 0 ) + return 0; + + if ( maxSteps > 2 ) + { + for ( int numSteps = maxSteps; numSteps > 1; numSteps-- ) + { + const double stepSize = double( intervalSize ) / numSteps; + + const double p = ::floor( ::log( stepSize ) / ::log( double( base ) ) ); + const double fraction = qPow( base, p ); + + for ( uint n = base; n >= 1; n /= 2 ) + { + if ( qFuzzyCompare( stepSize, n * fraction ) ) + return qRound( stepSize ); + + if ( n == 3 && ( base % 2 ) == 0 ) + { + if ( qFuzzyCompare( stepSize, 2 * fraction ) ) + return qRound( stepSize ); + } + } + } + } + + return 0; +} + +static int qwtDivideInterval( double intervalSize, int numSteps, + const int limits[], size_t numLimits ) +{ + const int v = qCeil( intervalSize / double( numSteps ) ); + + for ( uint i = 0; i < numLimits - 1; i++ ) + { + if ( v <= limits[i] ) + return limits[i]; + } + + return limits[ numLimits - 1 ]; +} + +static double qwtDivideScale( double intervalSize, int numSteps, + QwtDate::IntervalType intervalType ) +{ + if ( intervalType != QwtDate::Day ) + { + if ( ( intervalSize > numSteps ) && + ( intervalSize <= 2 * numSteps ) ) + { + return 2.0; + } + } + + double stepSize; + + switch( intervalType ) + { + case QwtDate::Second: + case QwtDate::Minute: + { + static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 }; + + stepSize = qwtDivideInterval( intervalSize, numSteps, + limits, sizeof( limits ) / sizeof( int ) ); + + break; + } + case QwtDate::Hour: + { + static int limits[] = { 1, 2, 3, 4, 6, 12, 24 }; + + stepSize = qwtDivideInterval( intervalSize, numSteps, + limits, sizeof( limits ) / sizeof( int ) ); + + break; + } + case QwtDate::Day: + { + const double v = intervalSize / double( numSteps ); + if ( v <= 5.0 ) + stepSize = qCeil( v ); + else + stepSize = qCeil( v / 7 ) * 7; + + break; + } + case QwtDate::Week: + { + static int limits[] = { 1, 2, 4, 8, 12, 26, 52 }; + + stepSize = qwtDivideInterval( intervalSize, numSteps, + limits, sizeof( limits ) / sizeof( int ) ); + + break; + } + case QwtDate::Month: + { + static int limits[] = { 1, 2, 3, 4, 6, 12 }; + + stepSize = qwtDivideInterval( intervalSize, numSteps, + limits, sizeof( limits ) / sizeof( int ) ); + + break; + } + case QwtDate::Year: + case QwtDate::Millisecond: + default: + { + stepSize = QwtScaleArithmetic::divideInterval( + intervalSize, numSteps, 10 ); + } + } + + return stepSize; +} + +static double qwtDivideMajorStep( double stepSize, int maxMinSteps, + QwtDate::IntervalType intervalType ) +{ + double minStepSize = 0.0; + + switch( intervalType ) + { + case QwtDate::Second: + { + minStepSize = qwtStepSize( stepSize, maxMinSteps, 10 ); + if ( minStepSize == 0.0 ) + minStepSize = 0.5 * stepSize; + + break; + } + case QwtDate::Minute: + { + static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 }; + + int numSteps; + + if ( stepSize > maxMinSteps ) + { + numSteps = qwtStepCount( stepSize, maxMinSteps, + limits, sizeof( limits ) / sizeof( int ) ); + + } + else + { + numSteps = qwtStepCount( stepSize * 60, maxMinSteps, + limits, sizeof( limits ) / sizeof( int ) ); + } + + if ( numSteps > 0 ) + minStepSize = double( stepSize ) / numSteps; + + break; + } + case QwtDate::Hour: + { + int numSteps = 0; + + if ( stepSize > maxMinSteps ) + { + static int limits[] = { 1, 2, 3, 4, 6, 12, 24, 48, 72 }; + + numSteps = qwtStepCount( stepSize, maxMinSteps, + limits, sizeof( limits ) / sizeof( int ) ); + } + else + { + static int limits[] = { 1, 2, 5, 10, 15, 20, 30, 60 }; + + numSteps = qwtStepCount( stepSize * 60, maxMinSteps, + limits, sizeof( limits ) / sizeof( int ) ); + } + + if ( numSteps > 0 ) + minStepSize = double( stepSize ) / numSteps; + + break; + } + case QwtDate::Day: + { + int numSteps = 0; + + if ( stepSize > maxMinSteps ) + { + static int limits[] = { 1, 2, 3, 7, 14, 28 }; + + numSteps = qwtStepCount( stepSize, maxMinSteps, + limits, sizeof( limits ) / sizeof( int ) ); + } + else + { + static int limits[] = { 1, 2, 3, 4, 6, 12, 24, 48, 72 }; + + numSteps = qwtStepCount( stepSize * 24, maxMinSteps, + limits, sizeof( limits ) / sizeof( int ) ); + } + + if ( numSteps > 0 ) + minStepSize = double( stepSize ) / numSteps; + + break; + } + case QwtDate::Week: + { + const int daysInStep = stepSize * 7; + + if ( maxMinSteps >= daysInStep ) + { + // we want to have one tick per day + minStepSize = 1.0 / 7.0; + } + else + { + // when the stepSize is more than a week we want to + // have a tick for each week + + const int stepSizeInWeeks = stepSize; + + if ( stepSizeInWeeks <= maxMinSteps ) + { + minStepSize = 1; + } + else + { + minStepSize = QwtScaleArithmetic::divideInterval( + stepSizeInWeeks, maxMinSteps, 10 ); + } + } + break; + } + case QwtDate::Month: + { + // fractions of months doesn't make any sense + + if ( stepSize < maxMinSteps ) + maxMinSteps = static_cast( stepSize ); + + static int limits[] = { 1, 2, 3, 4, 6, 12 }; + + int numSteps = qwtStepCount( stepSize, maxMinSteps, + limits, sizeof( limits ) / sizeof( int ) ); + + if ( numSteps > 0 ) + minStepSize = double( stepSize ) / numSteps; + + break; + } + case QwtDate::Year: + { + if ( stepSize >= maxMinSteps ) + { + minStepSize = QwtScaleArithmetic::divideInterval( + stepSize, maxMinSteps, 10 ); + } + else + { + // something in months + + static int limits[] = { 1, 2, 3, 4, 6, 12 }; + + int numSteps = qwtStepCount( 12 * stepSize, maxMinSteps, + limits, sizeof( limits ) / sizeof( int ) ); + + if ( numSteps > 0 ) + minStepSize = double( stepSize ) / numSteps; + } + + break; + } + default: + break; + } + + if ( intervalType != QwtDate::Month + && minStepSize == 0.0 ) + { + minStepSize = 0.5 * stepSize; + } + + return minStepSize; +} + +static QList qwtDstTicks( const QDateTime &dateTime, + int secondsMajor, int secondsMinor ) +{ + if ( secondsMinor <= 0 ) + QList(); + + QDateTime minDate = dateTime.addSecs( -secondsMajor ); + minDate = QwtDate::floor( minDate, QwtDate::Hour ); + + const double utcOffset = QwtDate::utcOffset( dateTime ); + + // find the hours where daylight saving time happens + + double dstMin = QwtDate::toDouble( minDate ); + while ( minDate < dateTime && + QwtDate::utcOffset( minDate ) != utcOffset ) + { + minDate = minDate.addSecs( 3600 ); + dstMin += 3600 * 1000.0; + } + + QList ticks; + for ( int i = 0; i < 3600; i += secondsMinor ) + ticks += dstMin + i * 1000.0; + + return ticks; +} + +static QwtScaleDiv qwtDivideToSeconds( + const QDateTime &minDate, const QDateTime &maxDate, + double stepSize, int maxMinSteps, + QwtDate::IntervalType intervalType ) +{ + // calculate the min step size + double minStepSize = 0; + + if ( maxMinSteps > 1 ) + { + minStepSize = qwtDivideMajorStep( stepSize, + maxMinSteps, intervalType ); + } + + bool daylightSaving = false; + if ( minDate.timeSpec() == Qt::LocalTime ) + { + daylightSaving = intervalType > QwtDate::Hour; + if ( intervalType == QwtDate::Hour ) + { + daylightSaving = stepSize > 1; + } + } + + const double s = qwtMsecsForType( intervalType ) / 1000; + const int secondsMajor = static_cast( stepSize * s ); + const double secondsMinor = minStepSize * s; + + // UTC excludes daylight savings. So from the difference + // of a date and its UTC counterpart we can find out + // the daylight saving hours + + const double utcOffset = QwtDate::utcOffset( minDate ); + double dstOff = 0; + + QList majorTicks; + QList mediumTicks; + QList minorTicks; + + for ( QDateTime dt = minDate; dt <= maxDate; + dt = dt.addSecs( secondsMajor ) ) + { + if ( !dt.isValid() ) + break; + + double majorValue = QwtDate::toDouble( dt ); + + if ( daylightSaving ) + { + const double offset = utcOffset - QwtDate::utcOffset( dt ); + majorValue += offset * 1000.0; + + if ( offset > dstOff ) + { + // we add some minor ticks for the DST hour, + // otherwise the ticks will be unaligned: 0, 2, 3, 5 ... + minorTicks += qwtDstTicks( + dt, secondsMajor, qRound( secondsMinor ) ); + } + + dstOff = offset; + } + + if ( majorTicks.isEmpty() || majorTicks.last() != majorValue ) + majorTicks += majorValue; + + if ( secondsMinor > 0.0 ) + { + const int numMinorSteps = qFloor( secondsMajor / secondsMinor ); + + for ( int i = 1; i < numMinorSteps; i++ ) + { + const QDateTime mt = dt.addMSecs( + qRound64( i * secondsMinor * 1000 ) ); + + double minorValue = QwtDate::toDouble( mt ); + if ( daylightSaving ) + { + const double offset = utcOffset - QwtDate::utcOffset( mt ); + minorValue += offset * 1000.0; + } + + if ( minorTicks.isEmpty() || minorTicks.last() != minorValue ) + { + const bool isMedium = ( numMinorSteps % 2 == 0 ) + && ( i != 1 ) && ( i == numMinorSteps / 2 ); + + if ( isMedium ) + mediumTicks += minorValue; + else + minorTicks += minorValue; + } + } + } + } + + QwtScaleDiv scaleDiv; + + scaleDiv.setInterval( QwtDate::toDouble( minDate ), + QwtDate::toDouble( maxDate ) ); + + scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks ); + scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks ); + scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks ); + + return scaleDiv; +} + +static QwtScaleDiv qwtDivideToMonths( + QDateTime &minDate, const QDateTime &maxDate, + double stepSize, int maxMinSteps ) +{ + // months are intervals with non + // equidistant ( in ms ) steps: we have to build the + // scale division manually + + int minStepDays = 0; + int minStepSize = 0.0; + + if ( maxMinSteps > 1 ) + { + if ( stepSize == 1 ) + { + if ( maxMinSteps >= 30 ) + minStepDays = 1; + else if ( maxMinSteps >= 6 ) + minStepDays = 5; + else if ( maxMinSteps >= 3 ) + minStepDays = 10; + + minStepDays = 15; + } + else + { + minStepSize = qwtDivideMajorStep( + stepSize, maxMinSteps, QwtDate::Month ); + } + } + + QList majorTicks; + QList mediumTicks; + QList minorTicks; + + for ( QDateTime dt = minDate; + dt <= maxDate; dt = dt.addMonths( stepSize ) ) + { + if ( !dt.isValid() ) + break; + + majorTicks += QwtDate::toDouble( dt ); + + if ( minStepDays > 0 ) + { + for ( int days = minStepDays; + days < 30; days += minStepDays ) + { + const double tick = QwtDate::toDouble( dt.addDays( days ) ); + + if ( days == 15 && minStepDays != 15 ) + mediumTicks += tick; + else + minorTicks += tick; + } + } + else if ( minStepSize > 0.0 ) + { + const int numMinorSteps = qRound( stepSize / (double) minStepSize ); + + for ( int i = 1; i < numMinorSteps; i++ ) + { + const double minorValue = + QwtDate::toDouble( dt.addMonths( i * minStepSize ) ); + + if ( ( numMinorSteps % 2 == 0 ) && ( i == numMinorSteps / 2 ) ) + mediumTicks += minorValue; + else + minorTicks += minorValue; + } + } + } + + QwtScaleDiv scaleDiv; + scaleDiv.setInterval( QwtDate::toDouble( minDate ), + QwtDate::toDouble( maxDate ) ); + + scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks ); + scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks ); + scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks ); + + return scaleDiv; +} + +static QwtScaleDiv qwtDivideToYears( + const QDateTime &minDate, const QDateTime &maxDate, + double stepSize, int maxMinSteps ) +{ + QList majorTicks; + QList mediumTicks; + QList minorTicks; + + double minStepSize = 0.0; + + if ( maxMinSteps > 1 ) + { + minStepSize = qwtDivideMajorStep( + stepSize, maxMinSteps, QwtDate::Year ); + } + + int numMinorSteps = 0; + if ( minStepSize > 0.0 ) + numMinorSteps = qFloor( stepSize / minStepSize ); + + bool dateBC = minDate.date().year() < -1; + + for ( QDateTime dt = minDate; dt <= maxDate; + dt = dt.addYears( stepSize ) ) + { + if ( dateBC && dt.date().year() > 1 ) + { + // there is no year 0 in the Julian calendar + dt = dt.addYears( -1 ); + dateBC = false; + } + + if ( !dt.isValid() ) + break; + + majorTicks += QwtDate::toDouble( dt ); + + for ( int i = 1; i < numMinorSteps; i++ ) + { + QDateTime tickDate; + + const double years = qRound( i * minStepSize ); + if ( years >= INT_MAX / 12 ) + { + tickDate = dt.addYears( years ); + } + else + { + tickDate = dt.addMonths( qRound( years * 12 ) ); + } + + const bool isMedium = ( numMinorSteps > 2 ) && + ( numMinorSteps % 2 == 0 ) && ( i == numMinorSteps / 2 ); + + const double minorValue = QwtDate::toDouble( tickDate ); + if ( isMedium ) + mediumTicks += minorValue; + else + minorTicks += minorValue; + } + + if ( QwtDate::maxDate().addYears( -stepSize ) < dt.date() ) + { + break; + } + } + + QwtScaleDiv scaleDiv; + scaleDiv.setInterval( QwtDate::toDouble( minDate ), + QwtDate::toDouble( maxDate ) ); + + scaleDiv.setTicks( QwtScaleDiv::MajorTick, majorTicks ); + scaleDiv.setTicks( QwtScaleDiv::MediumTick, mediumTicks ); + scaleDiv.setTicks( QwtScaleDiv::MinorTick, minorTicks ); + + return scaleDiv; +} + +class QwtDateScaleEngine::PrivateData +{ +public: + PrivateData( Qt::TimeSpec spec ): + timeSpec( spec ), + utcOffset( 0 ), + week0Type( QwtDate::FirstThursday ), + maxWeeks( 4 ) + { + } + + Qt::TimeSpec timeSpec; + int utcOffset; + QwtDate::Week0Type week0Type; + int maxWeeks; +}; + + +/*! + \brief Constructor + + The engine is initialized to build scales for the + given time specification. It classifies intervals > 4 weeks + as >= Qt::Month. The first week of a year is defined like + for QwtDate::FirstThursday. + + \param timeSpec Time specification + + \sa setTimeSpec(), setMaxWeeks(), setWeek0Type() + */ +QwtDateScaleEngine::QwtDateScaleEngine( Qt::TimeSpec timeSpec ): + QwtLinearScaleEngine( 10 ) +{ + d_data = new PrivateData( timeSpec ); +} + +//! Destructor +QwtDateScaleEngine::~QwtDateScaleEngine() +{ + delete d_data; +} + +/*! + Set the time specification used by the engine + + \param timeSpec Time specification + \sa timeSpec(), setUtcOffset(), toDateTime() + */ +void QwtDateScaleEngine::setTimeSpec( Qt::TimeSpec timeSpec ) +{ + d_data->timeSpec = timeSpec; +} + +/*! + \return Time specification used by the engine + \sa setTimeSpec(), utcOffset(), toDateTime() + */ +Qt::TimeSpec QwtDateScaleEngine::timeSpec() const +{ + return d_data->timeSpec; +} + +/*! + Set the offset in seconds from Coordinated Universal Time + + \param seconds Offset in seconds + + \note The offset has no effect beside for the time specification + Qt::OffsetFromUTC. + + \sa QDate::utcOffset(), setTimeSpec(), toDateTime() + */ +void QwtDateScaleEngine::setUtcOffset( int seconds ) +{ + d_data->utcOffset = seconds; +} + +/*! + \return Offset in seconds from Coordinated Universal Time + \note The offset has no effect beside for the time specification + Qt::OffsetFromUTC. + + \sa QDate::setUtcOffset(), setTimeSpec(), toDateTime() + */ +int QwtDateScaleEngine::utcOffset() const +{ + return d_data->utcOffset; +} + +/*! + Sets how to identify the first week of a year. + + \param week0Type Mode how to identify the first week of a year + + \sa week0Type(), setMaxWeeks() + \note week0Type has no effect beside for intervals classified as + QwtDate::Week. + */ +void QwtDateScaleEngine::setWeek0Type( QwtDate::Week0Type week0Type ) +{ + d_data->week0Type = week0Type; +} + +/*! + \return Setting how to identify the first week of a year. + \sa setWeek0Type(), maxWeeks() + */ +QwtDate::Week0Type QwtDateScaleEngine::week0Type() const +{ + return d_data->week0Type; +} + +/*! + Set a upper limit for the number of weeks, when an interval + can be classified as Qt::Week. + + The default setting is 4 weeks. + + \param weeks Upper limit for the number of weeks + + \note In business charts a year is often devided + into weeks [1-52] + \sa maxWeeks(), setWeek0Type() + */ +void QwtDateScaleEngine::setMaxWeeks( int weeks ) +{ + d_data->maxWeeks = qMax( weeks, 0 ); +} + +/*! + \return Upper limit for the number of weeks, when an interval + can be classified as Qt::Week. + \sa setMaxWeeks(), week0Type() + */ +int QwtDateScaleEngine::maxWeeks() const +{ + return d_data->maxWeeks; +} + +/*! + Classification of a date/time interval division + + \param minDate Minimum ( = earlier ) of the interval + \param maxDate Maximum ( = later ) of the interval + \param maxSteps Maximum for the number of steps + + \return Interval classification + */ +QwtDate::IntervalType QwtDateScaleEngine::intervalType( + const QDateTime &minDate, const QDateTime &maxDate, + int maxSteps ) const +{ + const double jdMin = minDate.date().toJulianDay(); + const double jdMax = maxDate.date().toJulianDay(); + + if ( ( jdMax - jdMin ) / 365 > maxSteps ) + return QwtDate::Year; + + const int months = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Month ); + if ( months > maxSteps * 6 ) + return QwtDate::Year; + + const int days = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Day ); + const int weeks = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Week ); + + if ( weeks > d_data->maxWeeks ) + { + if ( days > 4 * maxSteps * 7 ) + return QwtDate::Month; + } + + if ( days > maxSteps * 7 ) + return QwtDate::Week; + + const int hours = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Hour ); + if ( hours > maxSteps * 24 ) + return QwtDate::Day; + + const int seconds = qwtRoundedIntervalWidth( minDate, maxDate, QwtDate::Second ); + + if ( seconds >= maxSteps * 3600 ) + return QwtDate::Hour; + + if ( seconds >= maxSteps * 60 ) + return QwtDate::Minute; + + if ( seconds >= maxSteps ) + return QwtDate::Second; + + return QwtDate::Millisecond; +} + +/*! + Align and divide an interval + + The algorithm aligns and divides the interval into steps. + + Datetime interval divisions are usually not equidistant and the + calculated stepSize can only be used as an approximation + for the steps calculated by divideScale(). + + \param maxNumSteps Max. number of steps + \param x1 First limit of the interval (In/Out) + \param x2 Second limit of the interval (In/Out) + \param stepSize Step size (Out) + + \sa QwtScaleEngine::setAttribute() +*/ +void QwtDateScaleEngine::autoScale( int maxNumSteps, + double &x1, double &x2, double &stepSize ) const +{ + stepSize = 0.0; + + QwtInterval interval( x1, x2 ); + interval = interval.normalized(); + + interval.setMinValue( interval.minValue() - lowerMargin() ); + interval.setMaxValue( interval.maxValue() + upperMargin() ); + + if ( testAttribute( QwtScaleEngine::Symmetric ) ) + interval = interval.symmetrize( reference() ); + + if ( testAttribute( QwtScaleEngine::IncludeReference ) ) + interval = interval.extend( reference() ); + + if ( interval.width() == 0.0 ) + interval = buildInterval( interval.minValue() ); + + const QDateTime from = toDateTime( interval.minValue() ); + const QDateTime to = toDateTime( interval.maxValue() ); + + if ( from.isValid() && to.isValid() ) + { + if ( maxNumSteps < 1 ) + maxNumSteps = 1; + + const QwtDate::IntervalType intvType = + intervalType( from, to, maxNumSteps ); + + const double width = qwtIntervalWidth( from, to, intvType ); + + const double stepWidth = qwtDivideScale( width, maxNumSteps, intvType ); + if ( stepWidth != 0.0 && !testAttribute( QwtScaleEngine::Floating ) ) + { + const QDateTime d1 = alignDate( from, stepWidth, intvType, false ); + const QDateTime d2 = alignDate( to, stepWidth, intvType, true ); + + interval.setMinValue( QwtDate::toDouble( d1 ) ); + interval.setMaxValue( QwtDate::toDouble( d2 ) ); + } + + stepSize = stepWidth * qwtMsecsForType( intvType ); + } + + x1 = interval.minValue(); + x2 = interval.maxValue(); + + if ( testAttribute( QwtScaleEngine::Inverted ) ) + { + qSwap( x1, x2 ); + stepSize = -stepSize; + } +} + +/*! + \brief Calculate a scale division for a date/time interval + + \param x1 First interval limit + \param x2 Second interval limit + \param maxMajorSteps Maximum for the number of major steps + \param maxMinorSteps Maximum number of minor steps + \param stepSize Step size. If stepSize == 0, the scaleEngine + calculates one. + \return Calculated scale division +*/ +QwtScaleDiv QwtDateScaleEngine::divideScale( double x1, double x2, + int maxMajorSteps, int maxMinorSteps, double stepSize ) const +{ + if ( maxMajorSteps < 1 ) + maxMajorSteps = 1; + + const double min = qMin( x1, x2 ); + const double max = qMax( x1, x2 ); + + const QDateTime from = toDateTime( min ); + const QDateTime to = toDateTime( max ); + + if ( from == to ) + return QwtScaleDiv(); + + stepSize = qAbs( stepSize ); + if ( stepSize > 0.0 ) + { + // as interval types above hours are not equidistant + // ( even days might have 23/25 hours because of daylight saving ) + // the stepSize is used as a hint only + + maxMajorSteps = qCeil( ( max - min ) / stepSize ); + } + + const QwtDate::IntervalType intvType = + intervalType( from, to, maxMajorSteps ); + + QwtScaleDiv scaleDiv; + + if ( intvType == QwtDate::Millisecond ) + { + // for milliseconds and below we can use the decimal system + scaleDiv = QwtLinearScaleEngine::divideScale( min, max, + maxMajorSteps, maxMinorSteps, stepSize ); + } + else + { + const QDateTime minDate = QwtDate::floor( from, intvType ); + const QDateTime maxDate = QwtDate::ceil( to, intvType ); + + scaleDiv = buildScaleDiv( minDate, maxDate, + maxMajorSteps, maxMinorSteps, intvType ); + + // scaleDiv has been calculated from an extended interval + // adjusted to the step size. We have to shrink it again. + + scaleDiv = scaleDiv.bounded( min, max ); + } + + if ( x1 > x2 ) + scaleDiv.invert(); + + return scaleDiv; +} + +QwtScaleDiv QwtDateScaleEngine::buildScaleDiv( + const QDateTime &minDate, const QDateTime &maxDate, + int maxMajorSteps, int maxMinorSteps, + QwtDate::IntervalType intervalType ) const +{ + // calculate the step size + const double stepSize = qwtDivideScale( + qwtIntervalWidth( minDate, maxDate, intervalType ), + maxMajorSteps, intervalType ); + + // align minDate to the step size + QDateTime dt0 = alignDate( minDate, stepSize, intervalType, false ); + if ( !dt0.isValid() ) + { + // the floored date is out of the range of a + // QDateTime - we ceil instead. + dt0 = alignDate( minDate, stepSize, intervalType, true ); + } + + QwtScaleDiv scaleDiv; + + if ( intervalType <= QwtDate::Week ) + { + scaleDiv = qwtDivideToSeconds( dt0, maxDate, + stepSize, maxMinorSteps, intervalType ); + } + else + { + if( intervalType == QwtDate::Month ) + { + scaleDiv = qwtDivideToMonths( dt0, maxDate, + stepSize, maxMinorSteps ); + } + else if ( intervalType == QwtDate::Year ) + { + scaleDiv = qwtDivideToYears( dt0, maxDate, + stepSize, maxMinorSteps ); + } + } + + + return scaleDiv; +} + +/*! + Align a date/time value for a step size + + For Qt::Day alignments there is no "natural day 0" - + instead the first day of the year is used to avoid jumping + major ticks positions when panning a scale. For other alignments + ( f.e according to the first day of the month ) alignDate() + has to be overloaded. + + \param dateTime Date/time value + \param stepSize Step size + \param intervalType Interval type + \param up When true dateTime is ceiled - otherwise it is floored + + \return Aligned date/time value + */ +QDateTime QwtDateScaleEngine::alignDate( + const QDateTime &dateTime, double stepSize, + QwtDate::IntervalType intervalType, bool up ) const +{ + // what about: (year == 1582 && month == 10 && day > 4 && day < 15) ?? + + QDateTime dt = dateTime; + + if ( dateTime.timeSpec() == Qt::OffsetFromUTC ) + { + dt.setUtcOffset( 0 ); + } + + switch( intervalType ) + { + case QwtDate::Millisecond: + { + const int ms = qwtAlignValue( + dt.time().msec(), stepSize, up ) ; + + dt = QwtDate::floor( dateTime, QwtDate::Second ); + dt = dt.addMSecs( ms ); + + break; + } + case QwtDate::Second: + { + int second = dt.time().second(); + if ( up ) + { + if ( dt.time().msec() > 0 ) + second++; + } + + const int s = qwtAlignValue( second, stepSize, up ); + + dt = QwtDate::floor( dt, QwtDate::Minute ); + dt = dt.addSecs( s ); + + break; + } + case QwtDate::Minute: + { + int minute = dt.time().minute(); + if ( up ) + { + if ( dt.time().msec() > 0 || dt.time().second() > 0 ) + minute++; + } + + const int m = qwtAlignValue( minute, stepSize, up ); + + dt = QwtDate::floor( dt, QwtDate::Hour ); + dt = dt.addSecs( m * 60 ); + + break; + } + case QwtDate::Hour: + { + int hour = dt.time().hour(); + if ( up ) + { + if ( dt.time().msec() > 0 || dt.time().second() > 0 + || dt.time().minute() > 0 ) + { + hour++; + } + } + const int h = qwtAlignValue( hour, stepSize, up ); + + dt = QwtDate::floor( dt, QwtDate::Day ); + dt = dt.addSecs( h * 3600 ); + + break; + } + case QwtDate::Day: + { + // What date do we expect f.e. from an alignment of 5 days ?? + // Aligning them to the beginning of the year avoids at least + // jumping major ticks when panning + + int day = dt.date().dayOfYear(); + if ( up ) + { + if ( dt.time() > QTime( 0, 0 ) ) + day++; + } + + const int d = qwtAlignValue( day, stepSize, up ); + + dt = QwtDate::floor( dt, QwtDate::Year ); + dt = dt.addDays( d - 1 ); + + break; + } + case QwtDate::Week: + { + const QDate date = QwtDate::dateOfWeek0( + dt.date().year(), d_data->week0Type ); + + int numWeeks = date.daysTo( dt.date() ) / 7; + if ( up ) + { + if ( dt.time() > QTime( 0, 0 ) || + date.daysTo( dt.date() ) % 7 ) + { + numWeeks++; + } + } + + const int d = qwtAlignValue( numWeeks, stepSize, up ) * 7; + + dt = QwtDate::floor( dt, QwtDate::Day ); + dt.setDate( date ); + dt = dt.addDays( d ); + + break; + } + case QwtDate::Month: + { + int month = dt.date().month(); + if ( up ) + { + if ( dt.date().day() > 1 || + dt.time() > QTime( 0, 0 ) ) + { + month++; + } + } + + const int m = qwtAlignValue( month - 1, stepSize, up ); + + dt = QwtDate::floor( dt, QwtDate::Year ); + dt = dt.addMonths( m ); + + break; + } + case QwtDate::Year: + { + int year = dateTime.date().year(); + if ( up ) + { + if ( dateTime.date().dayOfYear() > 1 || + dt.time() > QTime( 0, 0 ) ) + { + year++; + } + } + + const int y = qwtAlignValue( year, stepSize, up ); + + dt = QwtDate::floor( dt, QwtDate::Day ); + if ( y == 0 ) + { + // there is no year 0 in the Julian calendar + dt.setDate( QDate( stepSize, 1, 1 ).addYears( -stepSize ) ); + } + else + { + dt.setDate( QDate( y, 1, 1 ) ); + } + + break; + } + } + + if ( dateTime.timeSpec() == Qt::OffsetFromUTC ) + { + dt.setUtcOffset( dateTime.utcOffset() ); + } + + return dt; +} + +/*! + Translate a double value into a QDateTime object. + + For QDateTime result is bounded by QwtDate::minDate() and QwtDate::maxDate() + + \return QDateTime object initialized with timeSpec() and utcOffset(). + \sa timeSpec(), utcOffset(), QwtDate::toDateTime() + */ +QDateTime QwtDateScaleEngine::toDateTime( double value ) const +{ + QDateTime dt = QwtDate::toDateTime( value, d_data->timeSpec ); + if ( !dt.isValid() ) + { + const QDate date = ( value <= 0.0 ) + ? QwtDate::minDate() : QwtDate::maxDate(); + + dt = QDateTime( date, QTime( 0, 0 ), d_data->timeSpec ); + } + + if ( d_data->timeSpec == Qt::OffsetFromUTC ) + { + dt = dt.addSecs( d_data->utcOffset ); + dt.setUtcOffset( d_data->utcOffset ); + } + + return dt; +} + diff --git a/qwt/src/qwt_date_scale_engine.h b/qwt/src/qwt_date_scale_engine.h new file mode 100644 index 000000000..db1d0f134 --- /dev/null +++ b/qwt/src/qwt_date_scale_engine.h @@ -0,0 +1,77 @@ +#ifndef _QWT_DATE_SCALE_ENGINE_H_ +#define _QWT_DATE_SCALE_ENGINE_H_ 1 + +#include "qwt_date.h" +#include "qwt_scale_engine.h" + +/*! + \brief A scale engine for date/time values + + QwtDateScaleEngine builds scales from a time intervals. + Together with QwtDateScaleDraw it can be used for + axes according to date/time values. + + Years, months, weeks, days, hours and minutes are organized + in steps with non constant intervals. QwtDateScaleEngine + classifies intervals and aligns the boundaries and tick positions + according to this classification. + + QwtDateScaleEngine supports representations depending + on Qt::TimeSpec specifications. The valid range for scales + is limited by the range of QDateTime, that differs + between Qt4 and Qt5. + + Datetime values are expected as the number of milliseconds since + 1970-01-01T00:00:00 Universal Coordinated Time - also known + as "The Epoch", that can be converted to QDateTime using + QwtDate::toDateTime(). + + \sa QwtDate, QwtPlot::setAxisScaleEngine(), + QwtAbstractScale::setScaleEngine() +*/ +class QWT_EXPORT QwtDateScaleEngine: public QwtLinearScaleEngine +{ +public: + QwtDateScaleEngine( Qt::TimeSpec = Qt::LocalTime ); + virtual ~QwtDateScaleEngine(); + + void setTimeSpec( Qt::TimeSpec ); + Qt::TimeSpec timeSpec() const; + + void setUtcOffset( int seconds ); + int utcOffset() const; + + void setWeek0Type( QwtDate::Week0Type ); + QwtDate::Week0Type week0Type() const; + + void setMaxWeeks( int ); + int maxWeeks() const; + + virtual void autoScale( int maxNumSteps, + double &x1, double &x2, double &stepSize ) const; + + virtual QwtScaleDiv divideScale( + double x1, double x2, + int maxMajorSteps, int maxMinorSteps, + double stepSize = 0.0 ) const; + + virtual QwtDate::IntervalType intervalType( + const QDateTime &, const QDateTime &, int maxSteps ) const; + + QDateTime toDateTime( double ) const; + +protected: + virtual QDateTime alignDate( const QDateTime &, double stepSize, + QwtDate::IntervalType, bool up ) const; + +private: + QwtScaleDiv buildScaleDiv( const QDateTime &, const QDateTime &, + int maxMajorSteps, int maxMinorSteps, + QwtDate::IntervalType ) const; + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_dial.cpp b/qwt/src/qwt_dial.cpp new file mode 100644 index 000000000..1440eb14a --- /dev/null +++ b/qwt/src/qwt_dial.cpp @@ -0,0 +1,872 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_dial.h" +#include "qwt_dial_needle.h" +#include "qwt_math.h" +#include "qwt_scale_engine.h" +#include "qwt_scale_map.h" +#include "qwt_round_scale_draw.h" +#include "qwt_painter.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static inline double qwtAngleDist( double a1, double a2 ) +{ + double dist = qAbs( a2 - a1 ); + if ( dist > 360.0 ) + dist -= 360.0; + + return dist; +} + +static inline bool qwtIsOnArc( double angle, double min, double max ) +{ + if ( min < max ) + { + return ( angle >= min ) && ( angle <= max ); + } + else + { + return ( angle >= min ) || ( angle <= max ); + } +} + +static inline double qwtBoundedAngle( double min, double angle, double max ) +{ + double from = qwtNormalizeDegrees( min ); + double to = qwtNormalizeDegrees( max ); + + double a; + + if ( qwtIsOnArc( angle, from, to ) ) + { + a = angle; + if ( a < min ) + a += 360.0; + } + else + { + if ( qwtAngleDist( angle, from ) < + qwtAngleDist( angle, to ) ) + { + a = min; + } + else + { + a = max; + } + } + + return a; +} + +class QwtDial::PrivateData +{ +public: + PrivateData(): + frameShadow( Sunken ), + lineWidth( 0 ), + mode( RotateNeedle ), + origin( 90.0 ), + minScaleArc( 0.0 ), + maxScaleArc( 0.0 ), + needle( NULL ), + arcOffset( 0.0 ), + mouseOffset( 0.0 ) + { + } + + ~PrivateData() + { + delete needle; + } + Shadow frameShadow; + int lineWidth; + + QwtDial::Mode mode; + + double origin; + double minScaleArc; + double maxScaleArc; + + double scalePenWidth; + QwtDialNeedle *needle; + + double arcOffset; + double mouseOffset; + + QPixmap pixmapCache; +}; + +/*! + \brief Constructor + \param parent Parent widget + + Create a dial widget with no needle. The scale is initialized + to [ 0.0, 360.0 ] and 360 steps ( QwtAbstractSlider::setTotalSteps() ). + The origin of the scale is at 90°, + + The value is set to 0.0. + + The default mode is QwtDial::RotateNeedle. +*/ +QwtDial::QwtDial( QWidget* parent ): + QwtAbstractSlider( parent ) +{ + d_data = new PrivateData; + + setFocusPolicy( Qt::TabFocus ); + + QPalette p = palette(); + for ( int i = 0; i < QPalette::NColorGroups; i++ ) + { + const QPalette::ColorGroup colorGroup = + static_cast( i ); + + // Base: background color of the circle inside the frame. + // WindowText: background color of the circle inside the scale + + p.setColor( colorGroup, QPalette::WindowText, + p.color( colorGroup, QPalette::Base ) ); + } + setPalette( p ); + + QwtRoundScaleDraw* scaleDraw = new QwtRoundScaleDraw(); + scaleDraw->setRadius( 0 ); + + setScaleDraw( scaleDraw ); + + setScaleArc( 0.0, 360.0 ); // scale as a full circle + + setScaleMaxMajor( 10 ); + setScaleMaxMinor( 5 ); + + setValue( 0.0 ); +} + +//! Destructor +QwtDial::~QwtDial() +{ + delete d_data; +} + +/*! + Sets the frame shadow value from the frame style. + + \param shadow Frame shadow + \sa setLineWidth(), QFrame::setFrameShadow() +*/ +void QwtDial::setFrameShadow( Shadow shadow ) +{ + if ( shadow != d_data->frameShadow ) + { + invalidateCache(); + + d_data->frameShadow = shadow; + if ( lineWidth() > 0 ) + update(); + } +} + +/*! + \return Frame shadow + /sa setFrameShadow(), lineWidth(), QFrame::frameShadow() +*/ +QwtDial::Shadow QwtDial::frameShadow() const +{ + return d_data->frameShadow; +} + +/*! + Sets the line width of the frame + + \param lineWidth Line width + \sa setFrameShadow() +*/ +void QwtDial::setLineWidth( int lineWidth ) +{ + if ( lineWidth < 0 ) + lineWidth = 0; + + if ( d_data->lineWidth != lineWidth ) + { + invalidateCache(); + + d_data->lineWidth = lineWidth; + update(); + } +} + +/*! + \return Line width of the frame + \sa setLineWidth(), frameShadow(), lineWidth() +*/ +int QwtDial::lineWidth() const +{ + return d_data->lineWidth; +} + +/*! + \return bounding rectangle of the circle inside the frame + \sa setLineWidth(), scaleInnerRect(), boundingRect() +*/ +QRect QwtDial::innerRect() const +{ + const int lw = lineWidth(); + return boundingRect().adjusted( lw, lw, -lw, -lw ); +} + +/*! + \return bounding rectangle of the dial including the frame + \sa setLineWidth(), scaleInnerRect(), innerRect() +*/ +QRect QwtDial::boundingRect() const +{ + const QRect cr = contentsRect(); + + const double dim = qMin( cr.width(), cr.height() ); + + QRect inner( 0, 0, dim, dim ); + inner.moveCenter( cr.center() ); + + return inner; +} + +/*! + \return rectangle inside the scale + \sa setLineWidth(), boundingRect(), innerRect() +*/ +QRect QwtDial::scaleInnerRect() const +{ + QRect rect = innerRect(); + + const QwtAbstractScaleDraw *sd = scaleDraw(); + if ( sd ) + { + int scaleDist = qCeil( sd->extent( font() ) ); + scaleDist++; // margin + + rect.adjust( scaleDist, scaleDist, -scaleDist, -scaleDist ); + } + + return rect; +} + +/*! + \brief Change the mode of the dial. + \param mode New mode + + In case of QwtDial::RotateNeedle the needle is rotating, in case of + QwtDial::RotateScale, the needle points to origin() + and the scale is rotating. + + The default mode is QwtDial::RotateNeedle. + + \sa mode(), setValue(), setOrigin() +*/ +void QwtDial::setMode( Mode mode ) +{ + if ( mode != d_data->mode ) + { + invalidateCache(); + + d_data->mode = mode; + sliderChange(); + } +} + +/*! + \return Mode of the dial. + \sa setMode(), origin(), setScaleArc(), value() +*/ +QwtDial::Mode QwtDial::mode() const +{ + return d_data->mode; +} + +/*! + Invalidate the internal caches used to speed up repainting + */ +void QwtDial::invalidateCache() +{ + d_data->pixmapCache = QPixmap(); +} + +/*! + Paint the dial + \param event Paint event +*/ +void QwtDial::paintEvent( QPaintEvent *event ) +{ + QPainter painter( this ); + painter.setClipRegion( event->region() ); + + QStyleOption opt; + opt.init(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); + + if ( d_data->mode == QwtDial::RotateScale ) + { + painter.save(); + painter.setRenderHint( QPainter::Antialiasing, true ); + + drawContents( &painter ); + + painter.restore(); + } + + const QRect r = contentsRect(); + if ( r.size() != d_data->pixmapCache.size() ) + { + d_data->pixmapCache = QwtPainter::backingStore( this, r.size() ); + d_data->pixmapCache.fill( Qt::transparent ); + + QPainter p( &d_data->pixmapCache ); + p.setRenderHint( QPainter::Antialiasing, true ); + p.translate( -r.topLeft() ); + + if ( d_data->mode != QwtDial::RotateScale ) + drawContents( &p ); + + if ( lineWidth() > 0 ) + drawFrame( &p ); + + if ( d_data->mode != QwtDial::RotateNeedle ) + drawNeedle( &p ); + } + + painter.drawPixmap( r.topLeft(), d_data->pixmapCache ); + + if ( d_data->mode == QwtDial::RotateNeedle ) + drawNeedle( &painter ); + + if ( hasFocus() ) + drawFocusIndicator( &painter ); +} + +/*! + Draw the focus indicator + \param painter Painter +*/ +void QwtDial::drawFocusIndicator( QPainter *painter ) const +{ + QwtPainter::drawFocusRect( painter, this, boundingRect() ); +} + +/*! + Draw the frame around the dial + + \param painter Painter + \sa lineWidth(), frameShadow() +*/ +void QwtDial::drawFrame( QPainter *painter ) +{ + QwtPainter::drawRoundFrame( painter, boundingRect(), + palette(), lineWidth(), d_data->frameShadow ); +} + +/*! + \brief Draw the contents inside the frame + + QPalette::Window is the background color outside of the frame. + QPalette::Base is the background color inside the frame. + QPalette::WindowText is the background color inside the scale. + + \param painter Painter + + \sa boundingRect(), innerRect(), + scaleInnerRect(), QWidget::setPalette() +*/ +void QwtDial::drawContents( QPainter *painter ) const +{ + if ( testAttribute( Qt::WA_NoSystemBackground ) || + palette().brush( QPalette::Base ) != + palette().brush( QPalette::Window ) ) + { + const QRectF br = boundingRect(); + + painter->save(); + painter->setPen( Qt::NoPen ); + painter->setBrush( palette().brush( QPalette::Base ) ); + painter->drawEllipse( br ); + painter->restore(); + } + + const QRectF insideScaleRect = scaleInnerRect(); + if ( palette().brush( QPalette::WindowText ) != + palette().brush( QPalette::Base ) ) + { + painter->save(); + painter->setPen( Qt::NoPen ); + painter->setBrush( palette().brush( QPalette::WindowText ) ); + painter->drawEllipse( insideScaleRect ); + painter->restore(); + } + + const QPointF center = insideScaleRect.center(); + const double radius = 0.5 * insideScaleRect.width(); + + painter->save(); + drawScale( painter, center, radius ); + painter->restore(); + + painter->save(); + drawScaleContents( painter, center, radius ); + painter->restore(); +} + +/*! + Draw the needle + + \param painter Painter + \param center Center of the dial + \param radius Length for the needle + \param direction Direction of the needle in degrees, counter clockwise + \param colorGroup ColorGroup +*/ +void QwtDial::drawNeedle( QPainter *painter, const QPointF ¢er, + double radius, double direction, QPalette::ColorGroup colorGroup ) const +{ + if ( d_data->needle ) + { + direction = 360.0 - direction; // counter clockwise + d_data->needle->draw( painter, center, radius, direction, colorGroup ); + } +} + +void QwtDial::drawNeedle( QPainter *painter ) const +{ + if ( !isValid() ) + return; + + QPalette::ColorGroup colorGroup; + if ( isEnabled() ) + colorGroup = hasFocus() ? QPalette::Active : QPalette::Inactive; + else + colorGroup = QPalette::Disabled; + + const QRectF sr = scaleInnerRect(); + + painter->save(); + painter->setRenderHint( QPainter::Antialiasing, true ); + drawNeedle( painter, sr.center(), 0.5 * sr.width(), + transform( value() ) + 270.0, colorGroup ); + painter->restore(); +} + +/*! + Draw the scale + + \param painter Painter + \param center Center of the dial + \param radius Radius of the scale +*/ +void QwtDial::drawScale( QPainter *painter, + const QPointF ¢er, double radius ) const +{ + QwtRoundScaleDraw *sd = const_cast( scaleDraw() ); + if ( sd == NULL ) + return; + + sd->setRadius( radius ); + sd->moveCenter( center ); + + QPalette pal = palette(); + + const QColor textColor = pal.color( QPalette::Text ); + pal.setColor( QPalette::WindowText, textColor ); // ticks, backbone + + painter->setFont( font() ); + painter->setPen( QPen( textColor, sd->penWidth() ) ); + + painter->setBrush( Qt::red ); + sd->draw( painter, pal ); +} + +/*! + Draw the contents inside the scale + + Paints nothing. + + \param painter Painter + \param center Center of the contents circle + \param radius Radius of the contents circle +*/ +void QwtDial::drawScaleContents( QPainter *painter, + const QPointF ¢er, double radius ) const +{ + Q_UNUSED(painter); + Q_UNUSED(center); + Q_UNUSED(radius); +} + +/*! + Set a needle for the dial + + \param needle Needle + + \warning The needle will be deleted, when a different needle is + set or in ~QwtDial() +*/ +void QwtDial::setNeedle( QwtDialNeedle *needle ) +{ + if ( needle != d_data->needle ) + { + if ( d_data->needle ) + delete d_data->needle; + + d_data->needle = needle; + update(); + } +} + +/*! + \return needle + \sa setNeedle() +*/ +const QwtDialNeedle *QwtDial::needle() const +{ + return d_data->needle; +} + +/*! + \return needle + \sa setNeedle() +*/ +QwtDialNeedle *QwtDial::needle() +{ + return d_data->needle; +} + +//! \return the scale draw +QwtRoundScaleDraw *QwtDial::scaleDraw() +{ + return static_cast( abstractScaleDraw() ); +} + +//! \return the scale draw +const QwtRoundScaleDraw *QwtDial::scaleDraw() const +{ + return static_cast( abstractScaleDraw() ); +} + +/*! + Set an individual scale draw + + The motivation for setting a scale draw is often + to overload QwtRoundScaleDraw::label() to return + individual tick labels. + + \param scaleDraw Scale draw + \warning The previous scale draw is deleted +*/ +void QwtDial::setScaleDraw( QwtRoundScaleDraw *scaleDraw ) +{ + setAbstractScaleDraw( scaleDraw ); + sliderChange(); +} + +/*! + Change the arc of the scale + + \param minArc Lower limit + \param maxArc Upper limit + + \sa minScaleArc(), maxScaleArc() +*/ +void QwtDial::setScaleArc( double minArc, double maxArc ) +{ + if ( minArc != 360.0 && minArc != -360.0 ) + minArc = ::fmod( minArc, 360.0 ); + if ( maxArc != 360.0 && maxArc != -360.0 ) + maxArc = ::fmod( maxArc, 360.0 ); + + double minScaleArc = qMin( minArc, maxArc ); + double maxScaleArc = qMax( minArc, maxArc ); + + if ( maxScaleArc - minScaleArc > 360.0 ) + maxScaleArc = minScaleArc + 360.0; + + if ( ( minScaleArc != d_data->minScaleArc ) || + ( maxScaleArc != d_data->maxScaleArc ) ) + { + d_data->minScaleArc = minScaleArc; + d_data->maxScaleArc = maxScaleArc; + + invalidateCache(); + sliderChange(); + } +} + +/*! + Set the lower limit for the scale arc + + \param min Lower limit of the scale arc + \sa setScaleArc(), setMaxScaleArc() + */ +void QwtDial::setMinScaleArc( double min ) +{ + setScaleArc( min, d_data->maxScaleArc ); +} + +/*! + \return Lower limit of the scale arc + \sa setScaleArc() +*/ +double QwtDial::minScaleArc() const +{ + return d_data->minScaleArc; +} + +/*! + Set the upper limit for the scale arc + + \param max Upper limit of the scale arc + \sa setScaleArc(), setMinScaleArc() + */ +void QwtDial::setMaxScaleArc( double max ) +{ + setScaleArc( d_data->minScaleArc, max ); +} + +/*! + \return Upper limit of the scale arc + \sa setScaleArc() +*/ +double QwtDial::maxScaleArc() const +{ + return d_data->maxScaleArc; +} + +/*! + \brief Change the origin + + The origin is the angle where scale and needle is relative to. + + \param origin New origin + \sa origin() +*/ +void QwtDial::setOrigin( double origin ) +{ + invalidateCache(); + + d_data->origin = origin; + sliderChange(); +} + +/*! + The origin is the angle where scale and needle is relative to. + + \return Origin of the dial + \sa setOrigin() +*/ +double QwtDial::origin() const +{ + return d_data->origin; +} + +/*! + \return Size hint + \sa minimumSizeHint() +*/ +QSize QwtDial::sizeHint() const +{ + int sh = 0; + if ( scaleDraw() ) + sh = qCeil( scaleDraw()->extent( font() ) ); + + const int d = 6 * sh + 2 * lineWidth(); + + QSize hint( d, d ); + if ( !isReadOnly() ) + hint = hint.expandedTo( QApplication::globalStrut() ); + + return hint; +} + +/*! + \return Minimum size hint + \sa sizeHint() +*/ +QSize QwtDial::minimumSizeHint() const +{ + int sh = 0; + if ( scaleDraw() ) + sh = qCeil( scaleDraw()->extent( font() ) ); + + const int d = 3 * sh + 2 * lineWidth(); + + return QSize( d, d ); +} + +/*! + \brief Determine what to do when the user presses a mouse button. + + \param pos Mouse position + + \retval True, when the inner circle contains pos + \sa scrolledTo() +*/ +bool QwtDial::isScrollPosition( const QPoint &pos ) const +{ + const QRegion region( innerRect(), QRegion::Ellipse ); + if ( region.contains( pos ) && ( pos != innerRect().center() ) ) + { + double angle = QLineF( rect().center(), pos ).angle(); + if ( d_data->mode == QwtDial::RotateScale ) + angle = 360.0 - angle; + + double valueAngle = + qwtNormalizeDegrees( 90.0 - transform( value() ) ); + + d_data->mouseOffset = qwtNormalizeDegrees( angle - valueAngle ); + d_data->arcOffset = scaleMap().p1(); + + return true; + } + + return false; +} + +/*! + \brief Determine the value for a new position of the + slider handle. + + \param pos Mouse position + + \return Value for the mouse position + \sa isScrollPosition() +*/ +double QwtDial::scrolledTo( const QPoint &pos ) const +{ + double angle = QLineF( rect().center(), pos ).angle(); + if ( d_data->mode == QwtDial::RotateScale ) + { + angle += scaleMap().p1() - d_data->arcOffset; + angle = 360.0 - angle; + } + + angle = qwtNormalizeDegrees( angle - d_data->mouseOffset ); + angle = qwtNormalizeDegrees( 90.0 - angle ); + + if ( scaleMap().pDist() >= 360.0 ) + { + if ( angle < scaleMap().p1() ) + angle += 360.0; + + if ( !wrapping() ) + { + double boundedAngle = angle; + + const double arc = angle - transform( value() ); + if ( qAbs( arc ) > 180.0 ) + { + boundedAngle = ( arc > 0 ) + ? scaleMap().p1() : scaleMap().p2(); + } + + d_data->mouseOffset += ( boundedAngle - angle ); + + angle = boundedAngle; + } + } + else + { + const double boundedAngle = + qwtBoundedAngle( scaleMap().p1(), angle, scaleMap().p2() ); + + if ( !wrapping() ) + d_data->mouseOffset += ( boundedAngle - angle ); + + angle = boundedAngle; + } + + return invTransform( angle ); +} + +/*! + Change Event handler + \param event Change event + + Invalidates internal paint caches if necessary +*/ +void QwtDial::changeEvent( QEvent *event ) +{ + switch( event->type() ) + { + case QEvent::EnabledChange: + case QEvent::FontChange: + case QEvent::StyleChange: + case QEvent::PaletteChange: + case QEvent::LanguageChange: + case QEvent::LocaleChange: + { + invalidateCache(); + break; + } + default: + break; + } + + QwtAbstractSlider::changeEvent( event ); +} + +/*! + Wheel Event handler + \param event Wheel event +*/ +void QwtDial::wheelEvent( QWheelEvent *event ) +{ + const QRegion region( innerRect(), QRegion::Ellipse ); + if ( region.contains( event->pos() ) ) + QwtAbstractSlider::wheelEvent( event ); +} + +void QwtDial::setAngleRange( double angle, double span ) +{ + QwtRoundScaleDraw *sd = const_cast( scaleDraw() ); + if ( sd ) + { + angle = qwtNormalizeDegrees( angle - 270.0 ); + sd->setAngleRange( angle, angle + span ); + } +} + +/*! + Invalidate the internal caches and call + QwtAbstractSlider::scaleChange() + */ +void QwtDial::scaleChange() +{ + invalidateCache(); + QwtAbstractSlider::scaleChange(); +} + +void QwtDial::sliderChange() +{ + setAngleRange( d_data->origin + d_data->minScaleArc, + d_data->maxScaleArc - d_data->minScaleArc ); + + if ( mode() == RotateScale ) + { + const double arc = transform( value() ) - scaleMap().p1(); + setAngleRange( d_data->origin - arc, + d_data->maxScaleArc - d_data->minScaleArc ); + } + + QwtAbstractSlider::sliderChange(); +} diff --git a/qwt/src/qwt_dial.h b/qwt/src/qwt_dial.h new file mode 100644 index 000000000..a409b4be6 --- /dev/null +++ b/qwt/src/qwt_dial.h @@ -0,0 +1,168 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_DIAL_H +#define QWT_DIAL_H 1 + +#include "qwt_global.h" +#include "qwt_abstract_slider.h" +#include "qwt_abstract_scale_draw.h" +#include +#include + +class QwtDialNeedle; +class QwtRoundScaleDraw; + +/*! + \brief QwtDial class provides a rounded range control. + + QwtDial is intended as base class for dial widgets like + speedometers, compass widgets, clocks ... + + \image html dials2.png + + A dial contains a scale and a needle indicating the current value + of the dial. Depending on Mode one of them is fixed and the + other is rotating. If not isReadOnly() the + dial can be rotated by dragging the mouse or using keyboard inputs + (see QwtAbstractSlider::keyPressEvent()). A dial might be wrapping, what means + a rotation below/above one limit continues on the other limit (f.e compass). + The scale might cover any arc of the dial, its values are related to + the origin() of the dial. + + Often dials have to be updated very often according to values from external + devices. For these high refresh rates QwtDial caches as much as possible. + For derived classes it might be necessary to clear these caches manually + according to attribute changes using invalidateCache(). + + \sa QwtCompass, QwtAnalogClock, QwtDialNeedle + \note The controls and dials examples shows different types of dials. + \note QDial is more similar to QwtKnob than to QwtDial +*/ + +class QWT_EXPORT QwtDial: public QwtAbstractSlider +{ + Q_OBJECT + + Q_ENUMS( Shadow Mode Direction ) + + Q_PROPERTY( int lineWidth READ lineWidth WRITE setLineWidth ) + Q_PROPERTY( Shadow frameShadow READ frameShadow WRITE setFrameShadow ) + Q_PROPERTY( Mode mode READ mode WRITE setMode ) + Q_PROPERTY( double origin READ origin WRITE setOrigin ) + Q_PROPERTY( double minScaleArc READ minScaleArc WRITE setMinScaleArc ) + Q_PROPERTY( double maxScaleArc READ maxScaleArc WRITE setMaxScaleArc ) + +public: + + /*! + \brief Frame shadow + + Unfortunately it is not possible to use QFrame::Shadow + as a property of a widget that is not derived from QFrame. + The following enum is made for the designer only. It is safe + to use QFrame::Shadow instead. + */ + enum Shadow + { + //! QFrame::Plain + Plain = QFrame::Plain, + + //! QFrame::Raised + Raised = QFrame::Raised, + + //! QFrame::Sunken + Sunken = QFrame::Sunken + }; + + //! Mode controlling whether the needle or the scale is rotating + enum Mode + { + //! The needle is rotating + RotateNeedle, + + //! The needle is fixed, the scales are rotating + RotateScale + }; + + explicit QwtDial( QWidget *parent = NULL ); + virtual ~QwtDial(); + + void setFrameShadow( Shadow ); + Shadow frameShadow() const; + + void setLineWidth( int ); + int lineWidth() const; + + void setMode( Mode ); + Mode mode() const; + + void setScaleArc( double min, double max ); + + void setMinScaleArc( double min ); + double minScaleArc() const; + + void setMaxScaleArc( double min ); + double maxScaleArc() const; + + virtual void setOrigin( double ); + double origin() const; + + void setNeedle( QwtDialNeedle * ); + const QwtDialNeedle *needle() const; + QwtDialNeedle *needle(); + + QRect boundingRect() const; + QRect innerRect() const; + + virtual QRect scaleInnerRect() const; + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + void setScaleDraw( QwtRoundScaleDraw * ); + + QwtRoundScaleDraw *scaleDraw(); + const QwtRoundScaleDraw *scaleDraw() const; + +protected: + virtual void wheelEvent( QWheelEvent * ); + virtual void paintEvent( QPaintEvent * ); + virtual void changeEvent( QEvent * ); + + virtual void drawFrame( QPainter *p ); + virtual void drawContents( QPainter * ) const; + virtual void drawFocusIndicator( QPainter * ) const; + + void invalidateCache(); + + virtual void drawScale( QPainter *, + const QPointF ¢er, double radius ) const; + + virtual void drawScaleContents( QPainter *painter, + const QPointF ¢er, double radius ) const; + + virtual void drawNeedle( QPainter *, const QPointF &, + double radius, double direction, QPalette::ColorGroup ) const; + + virtual double scrolledTo( const QPoint & ) const; + virtual bool isScrollPosition( const QPoint & ) const; + + virtual void sliderChange(); + virtual void scaleChange(); + +private: + void setAngleRange( double angle, double span ); + void drawNeedle( QPainter * ) const; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_dial_needle.cpp b/qwt/src/qwt_dial_needle.cpp new file mode 100644 index 000000000..1b53a3d5b --- /dev/null +++ b/qwt/src/qwt_dial_needle.cpp @@ -0,0 +1,440 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_dial_needle.h" +#include "qwt_global.h" +#include "qwt_math.h" +#include "qwt_painter.h" +#include +#include + +#if QT_VERSION < 0x040601 +#define qFastSin(x) qSin(x) +#define qFastCos(x) qCos(x) +#endif + +static void qwtDrawStyle1Needle( QPainter *painter, + const QPalette &palette, QPalette::ColorGroup colorGroup, + double length ) +{ + const double r[] = { 0.4, 0.3, 1, 0.8, 1, 0.3, 0.4 }; + const double a[] = { -45, -20, -15, 0, 15, 20, 45 }; + + QPainterPath path; + for ( int i = 0; i < 7; i++ ) + { + const double angle = a[i] / 180.0 * M_PI; + const double radius = r[i] * length; + + const double x = radius * qFastCos( angle ); + const double y = radius * qFastSin( angle ); + + path.lineTo( x, -y ); + } + + painter->setPen( Qt::NoPen ); + painter->setBrush( palette.brush( colorGroup, QPalette::Light ) ); + painter->drawPath( path ); +} + +static void qwtDrawStyle2Needle( QPainter *painter, + const QPalette &palette, QPalette::ColorGroup colorGroup, double length ) +{ + const double ratioX = 0.7; + const double ratioY = 0.3; + + QPainterPath path1; + path1.lineTo( ratioX * length, 0.0 ); + path1.lineTo( length, ratioY * length ); + + QPainterPath path2; + path2.lineTo( ratioX * length, 0.0 ); + path2.lineTo( length, -ratioY * length ); + + painter->setPen( Qt::NoPen ); + + painter->setBrush( palette.brush( colorGroup, QPalette::Light ) ); + painter->drawPath( path1 ); + + painter->setBrush( palette.brush( colorGroup, QPalette::Dark ) ); + painter->drawPath( path2 ); +} + +static void qwtDrawShadedPointer( QPainter *painter, + const QColor &lightColor, const QColor &darkColor, + double length, double width ) +{ + const double peak = qMax( length / 10.0, 5.0 ); + + const double knobWidth = width + 8; + QRectF knobRect( 0, 0, knobWidth, knobWidth ); + knobRect.moveCenter( QPointF(0, 0) ); + + QPainterPath path1; + path1.lineTo( 0.0, 0.5 * width ); + path1.lineTo( length - peak, 0.5 * width ); + path1.lineTo( length, 0.0 ); + path1.lineTo( 0.0, 0.0 ); + + QPainterPath arcPath1; + arcPath1.arcTo( knobRect, 0.0, -90.0 ); + + path1 = path1.united( arcPath1 ); + + QPainterPath path2; + path2.lineTo( 0.0, -0.5 * width ); + path2.lineTo( length - peak, -0.5 * width ); + path2.lineTo( length, 0.0 ); + path2.lineTo( 0.0, 0.0 ); + + QPainterPath arcPath2; + arcPath2.arcTo( knobRect, 0.0, 90.0 ); + + path2 = path2.united( arcPath2 ); + + painter->setPen( Qt::NoPen ); + + painter->setBrush( lightColor ); + painter->drawPath( path1 ); + + painter->setBrush( darkColor ); + painter->drawPath( path2 ); +} + +static void qwtDrawArrowNeedle( QPainter *painter, + const QPalette &palette, QPalette::ColorGroup colorGroup, + double length, double width ) +{ + if ( width <= 0 ) + width = qMax( length * 0.06, 9.0 ); + + const double peak = qMax( 2.0, 0.4 * width ); + + QPainterPath path; + path.moveTo( 0.0, 0.5 * width ); + path.lineTo( length - peak, 0.3 * width ); + path.lineTo( length, 0.0 ); + path.lineTo( length - peak, -0.3 * width ); + path.lineTo( 0.0, -0.5 * width ); + + QRectF br = path.boundingRect(); + + QPalette pal( palette.color( QPalette::Mid ) ); + QColor c1 = pal.color( QPalette::Light ); + QColor c2 = pal.color( QPalette::Dark ); + + QLinearGradient gradient( br.topLeft(), br.bottomLeft() ); + gradient.setColorAt( 0.0, c1 ); + gradient.setColorAt( 0.5, c1 ); + gradient.setColorAt( 0.5001, c2 ); + gradient.setColorAt( 1.0, c2 ); + + QPen pen( gradient, 1 ); + pen.setJoinStyle( Qt::MiterJoin ); + + painter->setPen( pen ); + painter->setBrush( palette.brush( colorGroup, QPalette::Mid ) ); + + painter->drawPath( path ); +} + +static void qwtDrawTriangleNeedle( QPainter *painter, + const QPalette &palette, QPalette::ColorGroup colorGroup, + double length ) +{ + const double width = qRound( length / 3.0 ); + + QPainterPath path[4]; + + path[0].lineTo( length, 0.0 ); + path[0].lineTo( 0.0, width / 2 ); + + path[1].lineTo( length, 0.0 ); + path[1].lineTo( 0.0, -width / 2 ); + + path[2].lineTo( -length, 0.0 ); + path[2].lineTo( 0.0, width / 2 ); + + path[3].lineTo( -length, 0.0 ); + path[3].lineTo( 0.0, -width / 2 ); + + + const int colorOffset = 10; + const QColor darkColor = palette.color( colorGroup, QPalette::Dark ); + const QColor lightColor = palette.color( colorGroup, QPalette::Light ); + + QColor color[4]; + color[0] = darkColor.light( 100 + colorOffset ); + color[1] = darkColor.dark( 100 + colorOffset ); + color[2] = lightColor.light( 100 + colorOffset ); + color[3] = lightColor.dark( 100 + colorOffset ); + + painter->setPen( Qt::NoPen ); + + for ( int i = 0; i < 4; i++ ) + { + painter->setBrush( color[i] ); + painter->drawPath( path[i] ); + } +} + +//! Constructor +QwtDialNeedle::QwtDialNeedle(): + d_palette( QApplication::palette() ) +{ +} + +//! Destructor +QwtDialNeedle::~QwtDialNeedle() +{ +} + +/*! + Sets the palette for the needle. + + \param palette New Palette +*/ +void QwtDialNeedle::setPalette( const QPalette &palette ) +{ + d_palette = palette; +} + +/*! + \return the palette of the needle. +*/ +const QPalette &QwtDialNeedle::palette() const +{ + return d_palette; +} + +/*! + Draw the needle + + \param painter Painter + \param center Center of the dial, start position for the needle + \param length Length of the needle + \param direction Direction of the needle, in degrees counter clockwise + \param colorGroup Color group, used for painting +*/ +void QwtDialNeedle::draw( QPainter *painter, + const QPointF ¢er, double length, double direction, + QPalette::ColorGroup colorGroup ) const +{ + painter->save(); + + painter->translate( center ); + painter->rotate( -direction ); + + drawNeedle( painter, length, colorGroup ); + + painter->restore(); +} + +//! Draw the knob +void QwtDialNeedle::drawKnob( QPainter *painter, + double width, const QBrush &brush, bool sunken ) const +{ + QPalette palette( brush.color() ); + + QColor c1 = palette.color( QPalette::Light ); + QColor c2 = palette.color( QPalette::Dark ); + + if ( sunken ) + qSwap( c1, c2 ); + + QRectF rect( 0.0, 0.0, width, width ); + rect.moveCenter( painter->combinedTransform().map( QPointF() ) ); + + QLinearGradient gradient( rect.topLeft(), rect.bottomRight() ); + gradient.setColorAt( 0.0, c1 ); + gradient.setColorAt( 0.3, c1 ); + gradient.setColorAt( 0.7, c2 ); + gradient.setColorAt( 1.0, c2 ); + + painter->save(); + + painter->resetTransform(); + + painter->setPen( QPen( gradient, 1 ) ); + painter->setBrush( brush ); + painter->drawEllipse( rect ); + + painter->restore(); +} + +/*! + Constructor + + \param style Style + \param hasKnob With/Without knob + \param mid Middle color + \param base Base color +*/ +QwtDialSimpleNeedle::QwtDialSimpleNeedle( Style style, bool hasKnob, + const QColor &mid, const QColor &base ): + d_style( style ), + d_hasKnob( hasKnob ), + d_width( -1 ) +{ + QPalette palette; + palette.setColor( QPalette::Mid, mid ); + palette.setColor( QPalette::Base, base ); + + setPalette( palette ); +} + +/*! + Set the width of the needle + \param width Width + \sa width() +*/ +void QwtDialSimpleNeedle::setWidth( double width ) +{ + d_width = width; +} + +/*! + \return the width of the needle + \sa setWidth() +*/ +double QwtDialSimpleNeedle::width() const +{ + return d_width; +} + +/*! + Draw the needle + + \param painter Painter + \param length Length of the needle + \param colorGroup Color group, used for painting +*/ +void QwtDialSimpleNeedle::drawNeedle( QPainter *painter, + double length, QPalette::ColorGroup colorGroup ) const +{ + double knobWidth = 0.0; + double width = d_width; + + if ( d_style == Arrow ) + { + if ( width <= 0.0 ) + width = qMax(length * 0.06, 6.0); + + qwtDrawArrowNeedle( painter, + palette(), colorGroup, length, width ); + + knobWidth = qMin( width * 2.0, 0.2 * length ); + } + else + { + if ( width <= 0.0 ) + width = 5.0; + + QPen pen ( palette().brush( colorGroup, QPalette::Mid ), width ); + pen.setCapStyle( Qt::FlatCap ); + + painter->setPen( pen ); + painter->drawLine( QPointF( 0.0, 0.0 ), QPointF( length, 0.0 ) ); + + knobWidth = qMax( width * 3.0, 5.0 ); + } + + if ( d_hasKnob && knobWidth > 0.0 ) + { + drawKnob( painter, knobWidth, + palette().brush( colorGroup, QPalette::Base ), false ); + } +} + +//! Constructor +QwtCompassMagnetNeedle::QwtCompassMagnetNeedle( Style style, + const QColor &light, const QColor &dark ): + d_style( style ) +{ + QPalette palette; + palette.setColor( QPalette::Light, light ); + palette.setColor( QPalette::Dark, dark ); + palette.setColor( QPalette::Base, Qt::gray ); + + setPalette( palette ); +} + +/*! + Draw the needle + + \param painter Painter + \param length Length of the needle + \param colorGroup Color group, used for painting +*/ +void QwtCompassMagnetNeedle::drawNeedle( QPainter *painter, + double length, QPalette::ColorGroup colorGroup ) const +{ + if ( d_style == ThinStyle ) + { + const double width = qMax( length / 6.0, 3.0 ); + + const int colorOffset = 10; + + const QColor light = palette().color( colorGroup, QPalette::Light ); + const QColor dark = palette().color( colorGroup, QPalette::Dark ); + + qwtDrawShadedPointer( painter, + dark.light( 100 + colorOffset ), + dark.dark( 100 + colorOffset ), + length, width ); + + painter->rotate( 180.0 ); + + qwtDrawShadedPointer( painter, + light.light( 100 + colorOffset ), + light.dark( 100 + colorOffset ), + length, width ); + + const QBrush baseBrush = palette().brush( colorGroup, QPalette::Base ); + drawKnob( painter, width, baseBrush, true ); + } + else + { + qwtDrawTriangleNeedle( painter, palette(), colorGroup, length ); + } +} + +/*! + Constructor + + \param style Arrow style + \param light Light color + \param dark Dark color +*/ +QwtCompassWindArrow::QwtCompassWindArrow( Style style, + const QColor &light, const QColor &dark ): + d_style( style ) +{ + QPalette palette; + palette.setColor( QPalette::Light, light ); + palette.setColor( QPalette::Dark, dark ); + + setPalette( palette ); +} + +/*! + Draw the needle + + \param painter Painter + \param length Length of the needle + \param colorGroup Color group, used for painting +*/ +void QwtCompassWindArrow::drawNeedle( QPainter *painter, + double length, QPalette::ColorGroup colorGroup ) const +{ + if ( d_style == Style1 ) + qwtDrawStyle1Needle( painter, palette(), colorGroup, length ); + else + qwtDrawStyle2Needle( painter, palette(), colorGroup, length ); +} diff --git a/qwt/src/qwt_dial_needle.h b/qwt/src/qwt_dial_needle.h new file mode 100644 index 000000000..d84384a0f --- /dev/null +++ b/qwt/src/qwt_dial_needle.h @@ -0,0 +1,187 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_DIAL_NEEDLE_H +#define QWT_DIAL_NEEDLE_H 1 + +#include "qwt_global.h" +#include + +class QPainter; +class QPoint; + +/*! + \brief Base class for needles that can be used in a QwtDial. + + QwtDialNeedle is a pointer that indicates a value by pointing + to a specific direction. + + \sa QwtDial, QwtCompass +*/ + +class QWT_EXPORT QwtDialNeedle +{ +public: + QwtDialNeedle(); + virtual ~QwtDialNeedle(); + + virtual void setPalette( const QPalette & ); + const QPalette &palette() const; + + virtual void draw( QPainter *painter, const QPointF ¢er, + double length, double direction, + QPalette::ColorGroup = QPalette::Active ) const; + +protected: + /*! + \brief Draw the needle + + The origin of the needle is at position (0.0, 0.0 ) + pointing in direction 0.0 ( = east ). + + The painter is already initialized with translation and + rotation. + + \param painter Painter + \param length Length of the needle + \param colorGroup Color group, used for painting + + \sa setPalette(), palette() + */ + virtual void drawNeedle( QPainter *painter, + double length, QPalette::ColorGroup colorGroup ) const = 0; + + virtual void drawKnob( QPainter *, double width, + const QBrush &, bool sunken ) const; + +private: + QPalette d_palette; +}; + +/*! + \brief A needle for dial widgets + + The following colors are used: + + - QPalette::Mid\n + Pointer + - QPalette::Base\n + Knob + + \sa QwtDial, QwtCompass +*/ + +class QWT_EXPORT QwtDialSimpleNeedle: public QwtDialNeedle +{ +public: + //! Style of the needle + enum Style + { + //! Arrow + Arrow, + + //! A straight line from the center + Ray + }; + + QwtDialSimpleNeedle( Style, bool hasKnob = true, + const QColor &mid = Qt::gray, const QColor &base = Qt::darkGray ); + + void setWidth( double width ); + double width() const; + +protected: + virtual void drawNeedle( QPainter *, double length, + QPalette::ColorGroup ) const; + +private: + Style d_style; + bool d_hasKnob; + double d_width; +}; + +/*! + \brief A magnet needle for compass widgets + + A magnet needle points to two opposite directions indicating + north and south. + + The following colors are used: + - QPalette::Light\n + Used for pointing south + - QPalette::Dark\n + Used for pointing north + - QPalette::Base\n + Knob (ThinStyle only) + + \sa QwtDial, QwtCompass +*/ + +class QWT_EXPORT QwtCompassMagnetNeedle: public QwtDialNeedle +{ +public: + //! Style of the needle + enum Style + { + //! A needle with a triangular shape + TriangleStyle, + + //! A thin needle + ThinStyle + }; + + QwtCompassMagnetNeedle( Style = TriangleStyle, + const QColor &light = Qt::white, const QColor &dark = Qt::red ); + +protected: + virtual void drawNeedle( QPainter *, + double length, QPalette::ColorGroup ) const; + +private: + Style d_style; +}; + +/*! + \brief An indicator for the wind direction + + QwtCompassWindArrow shows the direction where the wind comes from. + + - QPalette::Light\n + Used for Style1, or the light half of Style2 + - QPalette::Dark\n + Used for the dark half of Style2 + + \sa QwtDial, QwtCompass +*/ + +class QWT_EXPORT QwtCompassWindArrow: public QwtDialNeedle +{ +public: + //! Style of the arrow + enum Style + { + //! A needle pointing to the center + Style1, + + //! A needle pointing to the center + Style2 + }; + + QwtCompassWindArrow( Style, const QColor &light = Qt::white, + const QColor &dark = Qt::gray ); + +protected: + virtual void drawNeedle( QPainter *, + double length, QPalette::ColorGroup ) const; + +private: + Style d_style; +}; + +#endif diff --git a/qwt/src/qwt_dyngrid_layout.cpp b/qwt/src/qwt_dyngrid_layout.cpp new file mode 100644 index 000000000..9e0fa2851 --- /dev/null +++ b/qwt/src/qwt_dyngrid_layout.cpp @@ -0,0 +1,591 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_dyngrid_layout.h" +#include "qwt_math.h" +#include +#include + +class QwtDynGridLayout::PrivateData +{ +public: + PrivateData(): + isDirty( true ) + { + } + + void updateLayoutCache(); + + mutable QList itemList; + + uint maxColumns; + uint numRows; + uint numColumns; + + Qt::Orientations expanding; + + bool isDirty; + QVector itemSizeHints; +}; + +void QwtDynGridLayout::PrivateData::updateLayoutCache() +{ + itemSizeHints.resize( itemList.count() ); + + int index = 0; + + for ( QList::iterator it = itemList.begin(); + it != itemList.end(); ++it, index++ ) + { + itemSizeHints[ index ] = ( *it )->sizeHint(); + } + + isDirty = false; +} + +/*! + \param parent Parent widget + \param margin Margin + \param spacing Spacing +*/ + +QwtDynGridLayout::QwtDynGridLayout( QWidget *parent, + int margin, int spacing ): + QLayout( parent ) +{ + init(); + + setSpacing( spacing ); + setMargin( margin ); +} + +/*! + \param spacing Spacing +*/ + +QwtDynGridLayout::QwtDynGridLayout( int spacing ) +{ + init(); + setSpacing( spacing ); +} + +/*! + Initialize the layout with default values. +*/ +void QwtDynGridLayout::init() +{ + d_data = new QwtDynGridLayout::PrivateData; + d_data->maxColumns = d_data->numRows = d_data->numColumns = 0; + d_data->expanding = 0; +} + +//! Destructor + +QwtDynGridLayout::~QwtDynGridLayout() +{ + for ( int i = 0; i < d_data->itemList.size(); i++ ) + delete d_data->itemList[i]; + + delete d_data; +} + +//! Invalidate all internal caches +void QwtDynGridLayout::invalidate() +{ + d_data->isDirty = true; + QLayout::invalidate(); +} + +/*! + Limit the number of columns. + \param maxColumns upper limit, 0 means unlimited + \sa maxColumns() +*/ +void QwtDynGridLayout::setMaxColumns( uint maxColumns ) +{ + d_data->maxColumns = maxColumns; +} + +/*! + \brief Return the upper limit for the number of columns. + + 0 means unlimited, what is the default. + + \return Upper limit for the number of columns + \sa setMaxColumns() +*/ +uint QwtDynGridLayout::maxColumns() const +{ + return d_data->maxColumns; +} + +/*! + \brief Add an item to the next free position. + \param item Layout item + */ +void QwtDynGridLayout::addItem( QLayoutItem *item ) +{ + d_data->itemList.append( item ); + invalidate(); +} + +/*! + \return true if this layout is empty. +*/ +bool QwtDynGridLayout::isEmpty() const +{ + return d_data->itemList.isEmpty(); +} + +/*! + \return number of layout items +*/ +uint QwtDynGridLayout::itemCount() const +{ + return d_data->itemList.count(); +} + +/*! + Find the item at a specific index + + \param index Index + \return Item at a specific index + \sa takeAt() +*/ +QLayoutItem *QwtDynGridLayout::itemAt( int index ) const +{ + if ( index < 0 || index >= d_data->itemList.count() ) + return NULL; + + return d_data->itemList.at( index ); +} + +/*! + Find the item at a specific index and remove it from the layout + + \param index Index + \return Layout item, removed from the layout + \sa itemAt() +*/ +QLayoutItem *QwtDynGridLayout::takeAt( int index ) +{ + if ( index < 0 || index >= d_data->itemList.count() ) + return NULL; + + d_data->isDirty = true; + return d_data->itemList.takeAt( index ); +} + +//! \return Number of items in the layout +int QwtDynGridLayout::count() const +{ + return d_data->itemList.count(); +} + +/*! + Set whether this layout can make use of more space than sizeHint(). + A value of Qt::Vertical or Qt::Horizontal means that it wants to grow in only + one dimension, while Qt::Vertical | Qt::Horizontal means that it wants + to grow in both dimensions. The default value is 0. + + \param expanding Or'd orientations + \sa expandingDirections() +*/ +void QwtDynGridLayout::setExpandingDirections( Qt::Orientations expanding ) +{ + d_data->expanding = expanding; +} + +/*! + \brief Returns whether this layout can make use of more space than sizeHint(). + + A value of Qt::Vertical or Qt::Horizontal means that it wants to grow in only + one dimension, while Qt::Vertical | Qt::Horizontal means that it wants + to grow in both dimensions. + + \return Orientations, where the layout expands + \sa setExpandingDirections() +*/ +Qt::Orientations QwtDynGridLayout::expandingDirections() const +{ + return d_data->expanding; +} + +/*! + Reorganizes columns and rows and resizes managed items within + a rectangle. + + \param rect Layout geometry +*/ +void QwtDynGridLayout::setGeometry( const QRect &rect ) +{ + QLayout::setGeometry( rect ); + + if ( isEmpty() ) + return; + + d_data->numColumns = columnsForWidth( rect.width() ); + d_data->numRows = itemCount() / d_data->numColumns; + if ( itemCount() % d_data->numColumns ) + d_data->numRows++; + + QList itemGeometries = layoutItems( rect, d_data->numColumns ); + + int index = 0; + for ( QList::iterator it = d_data->itemList.begin(); + it != d_data->itemList.end(); ++it ) + { + ( *it )->setGeometry( itemGeometries[index] ); + index++; + } +} + +/*! + \brief Calculate the number of columns for a given width. + + The calculation tries to use as many columns as possible + ( limited by maxColumns() ) + + \param width Available width for all columns + \return Number of columns for a given width + + \sa maxColumns(), setMaxColumns() +*/ +uint QwtDynGridLayout::columnsForWidth( int width ) const +{ + if ( isEmpty() ) + return 0; + + uint maxColumns = itemCount(); + if ( d_data->maxColumns > 0 ) + maxColumns = qMin( d_data->maxColumns, maxColumns ); + + if ( maxRowWidth( maxColumns ) <= width ) + return maxColumns; + + for ( uint numColumns = 2; numColumns <= maxColumns; numColumns++ ) + { + const int rowWidth = maxRowWidth( numColumns ); + if ( rowWidth > width ) + return numColumns - 1; + } + + return 1; // At least 1 column +} + +/*! + Calculate the width of a layout for a given number of + columns. + + \param numColumns Given number of columns + \param itemWidth Array of the width hints for all items +*/ +int QwtDynGridLayout::maxRowWidth( int numColumns ) const +{ + int col; + + QVector colWidth( numColumns ); + for ( col = 0; col < numColumns; col++ ) + colWidth[col] = 0; + + if ( d_data->isDirty ) + d_data->updateLayoutCache(); + + for ( int index = 0; + index < d_data->itemSizeHints.count(); index++ ) + { + col = index % numColumns; + colWidth[col] = qMax( colWidth[col], + d_data->itemSizeHints[int( index )].width() ); + } + + int rowWidth = 2 * margin() + ( numColumns - 1 ) * spacing(); + for ( col = 0; col < numColumns; col++ ) + rowWidth += colWidth[col]; + + return rowWidth; +} + +/*! + \return the maximum width of all layout items +*/ +int QwtDynGridLayout::maxItemWidth() const +{ + if ( isEmpty() ) + return 0; + + if ( d_data->isDirty ) + d_data->updateLayoutCache(); + + int w = 0; + for ( int i = 0; i < d_data->itemSizeHints.count(); i++ ) + { + const int itemW = d_data->itemSizeHints[i].width(); + if ( itemW > w ) + w = itemW; + } + + return w; +} + +/*! + Calculate the geometries of the layout items for a layout + with numColumns columns and a given rectangle. + + \param rect Rect where to place the items + \param numColumns Number of columns + \return item geometries +*/ + +QList QwtDynGridLayout::layoutItems( const QRect &rect, + uint numColumns ) const +{ + QList itemGeometries; + if ( numColumns == 0 || isEmpty() ) + return itemGeometries; + + uint numRows = itemCount() / numColumns; + if ( numColumns % itemCount() ) + numRows++; + + if ( numRows == 0 ) + return itemGeometries; + + QVector rowHeight( numRows ); + QVector colWidth( numColumns ); + + layoutGrid( numColumns, rowHeight, colWidth ); + + bool expandH, expandV; + expandH = expandingDirections() & Qt::Horizontal; + expandV = expandingDirections() & Qt::Vertical; + + if ( expandH || expandV ) + stretchGrid( rect, numColumns, rowHeight, colWidth ); + + const int maxColumns = d_data->maxColumns; + d_data->maxColumns = numColumns; + const QRect alignedRect = alignmentRect( rect ); + d_data->maxColumns = maxColumns; + + const int xOffset = expandH ? 0 : alignedRect.x(); + const int yOffset = expandV ? 0 : alignedRect.y(); + + QVector colX( numColumns ); + QVector rowY( numRows ); + + const int xySpace = spacing(); + + rowY[0] = yOffset + margin(); + for ( uint r = 1; r < numRows; r++ ) + rowY[r] = rowY[r-1] + rowHeight[r-1] + xySpace; + + colX[0] = xOffset + margin(); + for ( uint c = 1; c < numColumns; c++ ) + colX[c] = colX[c-1] + colWidth[c-1] + xySpace; + + const int itemCount = d_data->itemList.size(); + for ( int i = 0; i < itemCount; i++ ) + { + const int row = i / numColumns; + const int col = i % numColumns; + + QRect itemGeometry( colX[col], rowY[row], + colWidth[col], rowHeight[row] ); + itemGeometries.append( itemGeometry ); + } + + return itemGeometries; +} + + +/*! + Calculate the dimensions for the columns and rows for a grid + of numColumns columns. + + \param numColumns Number of columns. + \param rowHeight Array where to fill in the calculated row heights. + \param colWidth Array where to fill in the calculated column widths. +*/ + +void QwtDynGridLayout::layoutGrid( uint numColumns, + QVector& rowHeight, QVector& colWidth ) const +{ + if ( numColumns <= 0 ) + return; + + if ( d_data->isDirty ) + d_data->updateLayoutCache(); + + for ( int index = 0; index < d_data->itemSizeHints.count(); index++ ) + { + const int row = index / numColumns; + const int col = index % numColumns; + + const QSize &size = d_data->itemSizeHints[int( index )]; + + rowHeight[row] = ( col == 0 ) + ? size.height() : qMax( rowHeight[row], size.height() ); + colWidth[col] = ( row == 0 ) + ? size.width() : qMax( colWidth[col], size.width() ); + } +} + +/*! + \return true: QwtDynGridLayout implements heightForWidth(). + \sa heightForWidth() +*/ +bool QwtDynGridLayout::hasHeightForWidth() const +{ + return true; +} + +/*! + \return The preferred height for this layout, given a width. + \sa hasHeightForWidth() +*/ +int QwtDynGridLayout::heightForWidth( int width ) const +{ + if ( isEmpty() ) + return 0; + + const uint numColumns = columnsForWidth( width ); + uint numRows = itemCount() / numColumns; + if ( itemCount() % numColumns ) + numRows++; + + QVector rowHeight( numRows ); + QVector colWidth( numColumns ); + + layoutGrid( numColumns, rowHeight, colWidth ); + + int h = 2 * margin() + ( numRows - 1 ) * spacing(); + for ( uint row = 0; row < numRows; row++ ) + h += rowHeight[row]; + + return h; +} + +/*! + Stretch columns in case of expanding() & QSizePolicy::Horizontal and + rows in case of expanding() & QSizePolicy::Vertical to fill the entire + rect. Rows and columns are stretched with the same factor. + + \param rect Bounding rectangle + \param numColumns Number of columns + \param rowHeight Array to be filled with the calculated row heights + \param colWidth Array to be filled with the calculated column widths + + \sa setExpanding(), expanding() +*/ +void QwtDynGridLayout::stretchGrid( const QRect &rect, + uint numColumns, QVector& rowHeight, QVector& colWidth ) const +{ + if ( numColumns == 0 || isEmpty() ) + return; + + bool expandH, expandV; + expandH = expandingDirections() & Qt::Horizontal; + expandV = expandingDirections() & Qt::Vertical; + + if ( expandH ) + { + int xDelta = rect.width() - 2 * margin() - ( numColumns - 1 ) * spacing(); + for ( uint col = 0; col < numColumns; col++ ) + xDelta -= colWidth[col]; + + if ( xDelta > 0 ) + { + for ( uint col = 0; col < numColumns; col++ ) + { + const int space = xDelta / ( numColumns - col ); + colWidth[col] += space; + xDelta -= space; + } + } + } + + if ( expandV ) + { + uint numRows = itemCount() / numColumns; + if ( itemCount() % numColumns ) + numRows++; + + int yDelta = rect.height() - 2 * margin() - ( numRows - 1 ) * spacing(); + for ( uint row = 0; row < numRows; row++ ) + yDelta -= rowHeight[row]; + + if ( yDelta > 0 ) + { + for ( uint row = 0; row < numRows; row++ ) + { + const int space = yDelta / ( numRows - row ); + rowHeight[row] += space; + yDelta -= space; + } + } + } +} + +/*! + Return the size hint. If maxColumns() > 0 it is the size for + a grid with maxColumns() columns, otherwise it is the size for + a grid with only one row. + + \return Size hint + \sa maxColumns(), setMaxColumns() +*/ +QSize QwtDynGridLayout::sizeHint() const +{ + if ( isEmpty() ) + return QSize(); + + uint numColumns = itemCount(); + if ( d_data->maxColumns > 0 ) + numColumns = qMin( d_data->maxColumns, numColumns ); + + uint numRows = itemCount() / numColumns; + if ( itemCount() % numColumns ) + numRows++; + + QVector rowHeight( numRows ); + QVector colWidth( numColumns ); + + layoutGrid( numColumns, rowHeight, colWidth ); + + int h = 2 * margin() + ( numRows - 1 ) * spacing(); + for ( uint row = 0; row < numRows; row++ ) + h += rowHeight[row]; + + int w = 2 * margin() + ( numColumns - 1 ) * spacing(); + for ( uint col = 0; col < numColumns; col++ ) + w += colWidth[col]; + + return QSize( w, h ); +} + +/*! + \return Number of rows of the current layout. + \sa numColumns() + \warning The number of rows might change whenever the geometry changes +*/ +uint QwtDynGridLayout::numRows() const +{ + return d_data->numRows; +} + +/*! + \return Number of columns of the current layout. + \sa numRows() + \warning The number of columns might change whenever the geometry changes +*/ +uint QwtDynGridLayout::numColumns() const +{ + return d_data->numColumns; +} diff --git a/qwt/src/qwt_dyngrid_layout.h b/qwt/src/qwt_dyngrid_layout.h new file mode 100644 index 000000000..dd2026612 --- /dev/null +++ b/qwt/src/qwt_dyngrid_layout.h @@ -0,0 +1,83 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_DYNGRID_LAYOUT_H +#define QWT_DYNGRID_LAYOUT_H + +#include "qwt_global.h" +#include +#include +#include + +/*! + \brief The QwtDynGridLayout class lays out widgets in a grid, + adjusting the number of columns and rows to the current size. + + QwtDynGridLayout takes the space it gets, divides it up into rows and + columns, and puts each of the widgets it manages into the correct cell(s). + It lays out as many number of columns as possible (limited by maxColumns()). +*/ + +class QWT_EXPORT QwtDynGridLayout : public QLayout +{ + Q_OBJECT +public: + explicit QwtDynGridLayout( QWidget *, int margin = 0, int space = -1 ); + explicit QwtDynGridLayout( int space = -1 ); + + virtual ~QwtDynGridLayout(); + + virtual void invalidate(); + + void setMaxColumns( uint maxCols ); + uint maxColumns() const; + + uint numRows () const; + uint numColumns () const; + + virtual void addItem( QLayoutItem * ); + + virtual QLayoutItem *itemAt( int index ) const; + virtual QLayoutItem *takeAt( int index ); + virtual int count() const; + + void setExpandingDirections( Qt::Orientations ); + virtual Qt::Orientations expandingDirections() const; + QList layoutItems( const QRect &, uint numCols ) const; + + virtual int maxItemWidth() const; + + virtual void setGeometry( const QRect &rect ); + + virtual bool hasHeightForWidth() const; + virtual int heightForWidth( int ) const; + + virtual QSize sizeHint() const; + + virtual bool isEmpty() const; + uint itemCount() const; + + virtual uint columnsForWidth( int width ) const; + +protected: + + void layoutGrid( uint numCols, + QVector& rowHeight, QVector& colWidth ) const; + void stretchGrid( const QRect &rect, uint numCols, + QVector& rowHeight, QVector& colWidth ) const; + +private: + void init(); + int maxRowWidth( int numCols ) const; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_event_pattern.cpp b/qwt/src/qwt_event_pattern.cpp new file mode 100644 index 000000000..463774362 --- /dev/null +++ b/qwt/src/qwt_event_pattern.cpp @@ -0,0 +1,265 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_event_pattern.h" +#include + +/*! + Constructor + + \sa MousePatternCode, KeyPatternCode +*/ + +QwtEventPattern::QwtEventPattern(): + d_mousePattern( MousePatternCount ), + d_keyPattern( KeyPatternCount ) +{ + initKeyPattern(); + initMousePattern( 3 ); +} + +//! Destructor +QwtEventPattern::~QwtEventPattern() +{ +} + +/*! + Set default mouse patterns, depending on the number of mouse buttons + + \param numButtons Number of mouse buttons ( <= 3 ) + \sa MousePatternCode +*/ +void QwtEventPattern::initMousePattern( int numButtons ) +{ + d_mousePattern.resize( MousePatternCount ); + + switch ( numButtons ) + { + case 1: + { + setMousePattern( MouseSelect1, Qt::LeftButton ); + setMousePattern( MouseSelect2, Qt::LeftButton, Qt::ControlModifier ); + setMousePattern( MouseSelect3, Qt::LeftButton, Qt::AltModifier ); + break; + } + case 2: + { + setMousePattern( MouseSelect1, Qt::LeftButton ); + setMousePattern( MouseSelect2, Qt::RightButton ); + setMousePattern( MouseSelect3, Qt::LeftButton, Qt::AltModifier ); + break; + } + default: + { + setMousePattern( MouseSelect1, Qt::LeftButton ); + setMousePattern( MouseSelect2, Qt::RightButton ); + setMousePattern( MouseSelect3, Qt::MidButton ); + } + } + + setMousePattern( MouseSelect4, d_mousePattern[MouseSelect1].button, + d_mousePattern[MouseSelect1].modifiers | Qt::ShiftModifier ); + + setMousePattern( MouseSelect5, d_mousePattern[MouseSelect2].button, + d_mousePattern[MouseSelect2].modifiers | Qt::ShiftModifier ); + + setMousePattern( MouseSelect6, d_mousePattern[MouseSelect3].button, + d_mousePattern[MouseSelect3].modifiers | Qt::ShiftModifier ); +} + +/*! + Set default mouse patterns. + + \sa KeyPatternCode +*/ +void QwtEventPattern::initKeyPattern() +{ + d_keyPattern.resize( KeyPatternCount ); + + setKeyPattern( KeySelect1, Qt::Key_Return ); + setKeyPattern( KeySelect2, Qt::Key_Space ); + setKeyPattern( KeyAbort, Qt::Key_Escape ); + + setKeyPattern( KeyLeft, Qt::Key_Left ); + setKeyPattern( KeyRight, Qt::Key_Right ); + setKeyPattern( KeyUp, Qt::Key_Up ); + setKeyPattern( KeyDown, Qt::Key_Down ); + + setKeyPattern( KeyRedo, Qt::Key_Plus ); + setKeyPattern( KeyUndo, Qt::Key_Minus ); + setKeyPattern( KeyHome, Qt::Key_Escape ); +} + +/*! + Change one mouse pattern + + \param pattern Index of the pattern + \param button Button + \param modifiers Keyboard modifiers + + \sa QMouseEvent +*/ +void QwtEventPattern::setMousePattern( MousePatternCode pattern, + Qt::MouseButton button, Qt::KeyboardModifiers modifiers ) +{ + if ( pattern >= 0 && pattern < MousePatternCount ) + { + d_mousePattern[ pattern ].button = button; + d_mousePattern[ pattern ].modifiers = modifiers; + } +} + +/*! + Change one key pattern + + \param pattern Index of the pattern + \param key Key + \param modifiers Keyboard modifiers + + \sa QKeyEvent +*/ +void QwtEventPattern::setKeyPattern( KeyPatternCode pattern, + int key, Qt::KeyboardModifiers modifiers ) +{ + if ( pattern >= 0 && pattern < KeyPatternCount ) + { + d_keyPattern[ pattern ].key = key; + d_keyPattern[ pattern ].modifiers = modifiers; + } +} + +//! Change the mouse event patterns +void QwtEventPattern::setMousePattern( const QVector &pattern ) +{ + d_mousePattern = pattern; +} + +//! Change the key event patterns +void QwtEventPattern::setKeyPattern( const QVector &pattern ) +{ + d_keyPattern = pattern; +} + +//! \return Mouse pattern +const QVector & +QwtEventPattern::mousePattern() const +{ + return d_mousePattern; +} + +//! \return Key pattern +const QVector & +QwtEventPattern::keyPattern() const +{ + return d_keyPattern; +} + +//! \return Mouse pattern +QVector &QwtEventPattern::mousePattern() +{ + return d_mousePattern; +} + +//! \return Key pattern +QVector &QwtEventPattern::keyPattern() +{ + return d_keyPattern; +} + +/*! + \brief Compare a mouse event with an event pattern. + + A mouse event matches the pattern when both have the same button + value and in the state value the same key flags(Qt::KeyButtonMask) + are set. + + \param code Index of the event pattern + \param event Mouse event + \return true if matches + + \sa keyMatch() +*/ +bool QwtEventPattern::mouseMatch( MousePatternCode code, + const QMouseEvent *event ) const +{ + if ( code >= 0 && code < MousePatternCount ) + return mouseMatch( d_mousePattern[ code ], event ); + + return false; +} + +/*! + \brief Compare a mouse event with an event pattern. + + A mouse event matches the pattern when both have the same button + value and in the state value the same key flags(Qt::KeyButtonMask) + are set. + + \param pattern Mouse event pattern + \param event Mouse event + \return true if matches + + \sa keyMatch() +*/ + +bool QwtEventPattern::mouseMatch( const MousePattern &pattern, + const QMouseEvent *event ) const +{ + if ( event == NULL ) + return false; + + const MousePattern mousePattern( event->button(), event->modifiers() ); + return mousePattern == pattern; +} + +/*! + \brief Compare a key event with an event pattern. + + A key event matches the pattern when both have the same key + value and in the state value the same key flags (Qt::KeyButtonMask) + are set. + + \param code Index of the event pattern + \param event Key event + \return true if matches + + \sa mouseMatch() +*/ +bool QwtEventPattern::keyMatch( KeyPatternCode code, + const QKeyEvent *event ) const +{ + if ( code >= 0 && code < KeyPatternCount ) + return keyMatch( d_keyPattern[ code ], event ); + + return false; +} + +/*! + \brief Compare a key event with an event pattern. + + A key event matches the pattern when both have the same key + value and in the state value the same key flags (Qt::KeyButtonMask) + are set. + + \param pattern Key event pattern + \param event Key event + \return true if matches + + \sa mouseMatch() +*/ + +bool QwtEventPattern::keyMatch( + const KeyPattern &pattern, const QKeyEvent *event ) const +{ + if ( event == NULL ) + return false; + + const KeyPattern keyPattern( event->key(), event->modifiers() ); + return keyPattern == pattern; +} diff --git a/qwt/src/qwt_event_pattern.h b/qwt/src/qwt_event_pattern.h new file mode 100644 index 000000000..7c5d1a37f --- /dev/null +++ b/qwt/src/qwt_event_pattern.h @@ -0,0 +1,240 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_EVENT_PATTERN +#define QWT_EVENT_PATTERN 1 + +#include "qwt_global.h" +#include +#include + +class QMouseEvent; +class QKeyEvent; + +/*! + \brief A collection of event patterns + + QwtEventPattern introduces an level of indirection for mouse and + keyboard inputs. Those are represented by symbolic names, so + the application code can be configured by individual mappings. + + \sa QwtPicker, QwtPickerMachine, QwtPlotZoomer +*/ +class QWT_EXPORT QwtEventPattern +{ +public: + /*! + \brief Symbolic mouse input codes + + QwtEventPattern implements 3 different settings for + mice with 1, 2, or 3 buttons that can be activated + using initMousePattern(). The default setting is for + 3 button mice. + + Individual settings can be configured using setMousePattern(). + + \sa initMousePattern(), setMousePattern(), setKeyPattern() + */ + enum MousePatternCode + { + /*! + The default setting for 1, 2 and 3 button mice is: + + - Qt::LeftButton + - Qt::LeftButton + - Qt::LeftButton + */ + MouseSelect1, + + /*! + The default setting for 1, 2 and 3 button mice is: + + - Qt::LeftButton + Qt::ControlModifier + - Qt::RightButton + - Qt::RightButton + */ + MouseSelect2, + + /*! + The default setting for 1, 2 and 3 button mice is: + + - Qt::LeftButton + Qt::AltModifier + - Qt::LeftButton + Qt::AltModifier + - Qt::MidButton + */ + MouseSelect3, + + /*! + The default setting for 1, 2 and 3 button mice is: + + - Qt::LeftButton + Qt::ShiftModifier + - Qt::LeftButton + Qt::ShiftModifier + - Qt::LeftButton + Qt::ShiftModifier + */ + MouseSelect4, + + /*! + The default setting for 1, 2 and 3 button mice is: + + - Qt::LeftButton + Qt::ControlButton | Qt::ShiftModifier + - Qt::RightButton + Qt::ShiftModifier + - Qt::RightButton + Qt::ShiftModifier + */ + MouseSelect5, + + /*! + The default setting for 1, 2 and 3 button mice is: + + - Qt::LeftButton + Qt::AltModifier + Qt::ShiftModifier + - Qt::LeftButton + Qt::AltModifier | Qt::ShiftModifier + - Qt::MidButton + Qt::ShiftModifier + */ + MouseSelect6, + + //! Number of mouse patterns + MousePatternCount + }; + + /*! + \brief Symbolic keyboard input codes + + Individual settings can be configured using setKeyPattern() + + \sa setKeyPattern(), setMousePattern() + */ + enum KeyPatternCode + { + //! Qt::Key_Return + KeySelect1, + + //! Qt::Key_Space + KeySelect2, + + //! Qt::Key_Escape + KeyAbort, + + //! Qt::Key_Left + KeyLeft, + + //! Qt::Key_Right + KeyRight, + + //! Qt::Key_Up + KeyUp, + + //! Qt::Key_Down + KeyDown, + + //! Qt::Key_Plus + KeyRedo, + + //! Qt::Key_Minus + KeyUndo, + + //! Qt::Key_Escape + KeyHome, + + //! Number of key patterns + KeyPatternCount + }; + + //! A pattern for mouse events + class MousePattern + { + public: + //! Constructor + MousePattern( Qt::MouseButton btn = Qt::NoButton, + Qt::KeyboardModifiers modifierCodes = Qt::NoModifier ): + button( btn ), + modifiers( modifierCodes ) + { + } + + //! Button + Qt::MouseButton button; + + //! Keyboard modifier + Qt::KeyboardModifiers modifiers; + }; + + //! A pattern for key events + class KeyPattern + { + public: + //! Constructor + KeyPattern( int keyCode = Qt::Key_unknown, + Qt::KeyboardModifiers modifierCodes = Qt::NoModifier ): + key( keyCode ), + modifiers( modifierCodes ) + { + } + + //! Key code + int key; + + //! Modifiers + Qt::KeyboardModifiers modifiers; + }; + + QwtEventPattern(); + virtual ~QwtEventPattern(); + + void initMousePattern( int numButtons ); + void initKeyPattern(); + + void setMousePattern( MousePatternCode, Qt::MouseButton button, + Qt::KeyboardModifiers = Qt::NoModifier ); + + void setKeyPattern( KeyPatternCode, int keyCode, + Qt::KeyboardModifiers modifierCodes = Qt::NoModifier ); + + void setMousePattern( const QVector & ); + void setKeyPattern( const QVector & ); + + const QVector &mousePattern() const; + const QVector &keyPattern() const; + + QVector &mousePattern(); + QVector &keyPattern(); + + bool mouseMatch( MousePatternCode, const QMouseEvent * ) const; + bool keyMatch( KeyPatternCode, const QKeyEvent * ) const; + +protected: + virtual bool mouseMatch( const MousePattern &, const QMouseEvent * ) const; + virtual bool keyMatch( const KeyPattern &, const QKeyEvent * ) const; + +private: + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable: 4251) +#endif + QVector d_mousePattern; + QVector d_keyPattern; +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +}; + +//! Compare operator +inline bool operator==( QwtEventPattern::MousePattern b1, + QwtEventPattern::MousePattern b2 ) +{ + return b1.button == b2.button && b1.modifiers == b2.modifiers; +} + +//! Compare operator +inline bool operator==( QwtEventPattern::KeyPattern b1, + QwtEventPattern::KeyPattern b2 ) +{ + return b1.key == b2.key && b1.modifiers == b2.modifiers; +} + +#endif diff --git a/qwt/src/qwt_global.h b/qwt/src/qwt_global.h new file mode 100644 index 000000000..3833077c5 --- /dev/null +++ b/qwt/src/qwt_global.h @@ -0,0 +1,41 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_GLOBAL_H +#define QWT_GLOBAL_H + +#include + +// QWT_VERSION is (major << 16) + (minor << 8) + patch. + +#define QWT_VERSION 0x060101 +#define QWT_VERSION_STR "6.1.1" + +#if defined(_MSC_VER) /* MSVC Compiler */ +/* template-class specialization 'identifier' is already instantiated */ +#pragma warning(disable: 4660) +/* inherits via dominance */ +#pragma warning(disable: 4250) +#endif // _MSC_VER + +#ifdef QWT_DLL + +#if defined(QWT_MAKEDLL) // create a Qwt DLL library +#define QWT_EXPORT Q_DECL_EXPORT +#else // use a Qwt DLL library +#define QWT_EXPORT Q_DECL_IMPORT +#endif + +#endif // QWT_DLL + +#ifndef QWT_EXPORT +#define QWT_EXPORT +#endif + +#endif diff --git a/qwt/src/qwt_graphic.cpp b/qwt/src/qwt_graphic.cpp new file mode 100644 index 000000000..d67bcea0e --- /dev/null +++ b/qwt/src/qwt_graphic.cpp @@ -0,0 +1,986 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_graphic.h" +#include "qwt_painter_command.h" +#include +#include +#include +#include +#include +#include +#include + +static bool qwtHasScalablePen( const QPainter *painter ) +{ + const QPen pen = painter->pen(); + + bool scalablePen = false; + + if ( pen.style() != Qt::NoPen && pen.brush().style() != Qt::NoBrush ) + { + scalablePen = !pen.isCosmetic(); + if ( !scalablePen && pen.widthF() == 0.0 ) + { + const QPainter::RenderHints hints = painter->renderHints(); + if ( hints.testFlag( QPainter::NonCosmeticDefaultPen ) ) + scalablePen = true; + } + } + + return scalablePen; +} + +static QRectF qwtStrokedPathRect( + const QPainter *painter, const QPainterPath &path ) +{ + QPainterPathStroker stroker; + stroker.setWidth( painter->pen().widthF() ); + stroker.setCapStyle( painter->pen().capStyle() ); + stroker.setJoinStyle( painter->pen().joinStyle() ); + stroker.setMiterLimit( painter->pen().miterLimit() ); + + QRectF rect; + if ( qwtHasScalablePen( painter ) ) + { + QPainterPath stroke = stroker.createStroke(path); + rect = painter->transform().map(stroke).boundingRect(); + } + else + { + QPainterPath mappedPath = painter->transform().map(path); + mappedPath = stroker.createStroke( mappedPath ); + + rect = mappedPath.boundingRect(); + } + + return rect; +} + +static inline void qwtExecCommand( + QPainter *painter, const QwtPainterCommand &cmd, + QwtGraphic::RenderHints renderHints, + const QTransform &transform ) +{ + switch( cmd.type() ) + { + case QwtPainterCommand::Path: + { + bool doMap = false; + + if ( renderHints.testFlag( QwtGraphic::RenderPensUnscaled ) + && painter->transform().isScaling() ) + { + bool isCosmetic = painter->pen().isCosmetic(); + if ( isCosmetic && painter->pen().widthF() == 0.0 ) + { + QPainter::RenderHints hints = painter->renderHints(); + if ( hints.testFlag( QPainter::NonCosmeticDefaultPen ) ) + isCosmetic = false; + } + + doMap = !isCosmetic; + } + + if ( doMap ) + { + const QTransform transform = painter->transform(); + + painter->resetTransform(); + painter->drawPath( transform.map( *cmd.path() ) ); + + painter->setTransform( transform ); + } + else + { + painter->drawPath( *cmd.path() ); + } + break; + } + case QwtPainterCommand::Pixmap: + { + const QwtPainterCommand::PixmapData *data = cmd.pixmapData(); + painter->drawPixmap( data->rect, data->pixmap, data->subRect ); + break; + } + case QwtPainterCommand::Image: + { + const QwtPainterCommand::ImageData *data = cmd.imageData(); + painter->drawImage( data->rect, data->image, + data->subRect, data->flags ); + break; + } + case QwtPainterCommand::State: + { + const QwtPainterCommand::StateData *data = cmd.stateData(); + + if ( data->flags & QPaintEngine::DirtyPen ) + painter->setPen( data->pen ); + + if ( data->flags & QPaintEngine::DirtyBrush ) + painter->setBrush( data->brush ); + + if ( data->flags & QPaintEngine::DirtyBrushOrigin ) + painter->setBrushOrigin( data->brushOrigin ); + + if ( data->flags & QPaintEngine::DirtyFont ) + painter->setFont( data->font ); + + if ( data->flags & QPaintEngine::DirtyBackground ) + { + painter->setBackgroundMode( data->backgroundMode ); + painter->setBackground( data->backgroundBrush ); + } + + if ( data->flags & QPaintEngine::DirtyTransform ) + { + painter->setTransform( data->transform * transform ); + } + + if ( data->flags & QPaintEngine::DirtyClipEnabled ) + painter->setClipping( data->isClipEnabled ); + + if ( data->flags & QPaintEngine::DirtyClipRegion) + { + painter->setClipRegion( data->clipRegion, + data->clipOperation ); + } + + if ( data->flags & QPaintEngine::DirtyClipPath ) + { + painter->setClipPath( data->clipPath, data->clipOperation ); + } + + if ( data->flags & QPaintEngine::DirtyHints) + { + const QPainter::RenderHints hints = data->renderHints; + + painter->setRenderHint( QPainter::Antialiasing, + hints.testFlag( QPainter::Antialiasing ) ); + + painter->setRenderHint( QPainter::TextAntialiasing, + hints.testFlag( QPainter::TextAntialiasing ) ); + + painter->setRenderHint( QPainter::SmoothPixmapTransform, + hints.testFlag( QPainter::SmoothPixmapTransform ) ); + + painter->setRenderHint( QPainter::HighQualityAntialiasing, + hints.testFlag( QPainter::HighQualityAntialiasing ) ); + + painter->setRenderHint( QPainter::NonCosmeticDefaultPen, + hints.testFlag( QPainter::NonCosmeticDefaultPen ) ); + } + + if ( data->flags & QPaintEngine::DirtyCompositionMode) + painter->setCompositionMode( data->compositionMode ); + + if ( data->flags & QPaintEngine::DirtyOpacity) + painter->setOpacity( data->opacity ); + + break; + } + default: + break; + } + +} + +class QwtGraphic::PathInfo +{ +public: + PathInfo(): + d_scalablePen( false ) + { + // QVector needs a default constructor + } + + PathInfo( const QRectF &pointRect, + const QRectF &boundingRect, bool scalablePen ): + d_pointRect( pointRect ), + d_boundingRect( boundingRect ), + d_scalablePen( scalablePen ) + { + } + + inline QRectF scaledBoundingRect( double sx, double sy, + bool scalePens ) const + { + if ( sx == 1.0 && sy == 1.0 ) + return d_boundingRect; + + QTransform transform; + transform.scale( sx, sy ); + + QRectF rect; + if ( scalePens && d_scalablePen ) + { + rect = transform.mapRect( d_boundingRect ); + } + else + { + rect = transform.mapRect( d_pointRect ); + + const double l = qAbs( d_pointRect.left() - d_boundingRect.left() ); + const double r = qAbs( d_pointRect.right() - d_boundingRect.right() ); + const double t = qAbs( d_pointRect.top() - d_boundingRect.top() ); + const double b = qAbs( d_pointRect.bottom() - d_boundingRect.bottom() ); + + rect.adjust( -l, -t, r, b ); + } + + return rect; + } + + inline double scaleFactorX( const QRectF& pathRect, + const QRectF &targetRect, bool scalePens ) const + { + if ( pathRect.width() <= 0.0 ) + return 0.0; + + const QPointF p0 = d_pointRect.center(); + + const double l = qAbs( pathRect.left() - p0.x() ); + const double r = qAbs( pathRect.right() - p0.x() ); + + const double w = 2.0 * qMin( l, r ) + * targetRect.width() / pathRect.width(); + + double sx; + if ( scalePens && d_scalablePen ) + { + sx = w / d_boundingRect.width(); + } + else + { + const double pw = qMax( + qAbs( d_boundingRect.left() - d_pointRect.left() ), + qAbs( d_boundingRect.right() - d_pointRect.right() ) ); + + sx = ( w - 2 * pw ) / d_pointRect.width(); + } + + return sx; + } + + inline double scaleFactorY( const QRectF& pathRect, + const QRectF &targetRect, bool scalePens ) const + { + if ( pathRect.height() <= 0.0 ) + return 0.0; + + const QPointF p0 = d_pointRect.center(); + + const double t = qAbs( pathRect.top() - p0.y() ); + const double b = qAbs( pathRect.bottom() - p0.y() ); + + const double h = 2.0 * qMin( t, b ) + * targetRect.height() / pathRect.height(); + + double sy; + if ( scalePens && d_scalablePen ) + { + sy = h / d_boundingRect.height(); + } + else + { + const double pw = + qMax( qAbs( d_boundingRect.top() - d_pointRect.top() ), + qAbs( d_boundingRect.bottom() - d_pointRect.bottom() ) ); + + sy = ( h - 2 * pw ) / d_pointRect.height(); + } + + return sy; + } + +private: + QRectF d_pointRect; + QRectF d_boundingRect; + bool d_scalablePen; +}; + +class QwtGraphic::PrivateData +{ +public: + PrivateData(): + boundingRect( 0.0, 0.0, -1.0, -1.0 ), + pointRect( 0.0, 0.0, -1.0, -1.0 ) + { + } + + QSizeF defaultSize; + QVector commands; + QVector pathInfos; + + QRectF boundingRect; + QRectF pointRect; + + QwtGraphic::RenderHints renderHints; +}; + +/*! + \brief Constructor + + Initializes a null graphic + \sa isNull() + */ +QwtGraphic::QwtGraphic(): + QwtNullPaintDevice() +{ + setMode( QwtNullPaintDevice::PathMode ); + d_data = new PrivateData; +} + +/*! + \brief Copy constructor + + \param other Source + \sa operator=() + */ +QwtGraphic::QwtGraphic( const QwtGraphic &other ): + QwtNullPaintDevice() +{ + setMode( other.mode() ); + d_data = new PrivateData( *other.d_data ); +} + +//! Destructor +QwtGraphic::~QwtGraphic() +{ + delete d_data; +} + +/*! + \brief Assignment operator + + \param other Source + \return A reference of this object + */ +QwtGraphic& QwtGraphic::operator=(const QwtGraphic &other) +{ + setMode( other.mode() ); + *d_data = *other.d_data; + + return *this; +} + +/*! + \brief Clear all stored commands + \sa isNull() + */ +void QwtGraphic::reset() +{ + d_data->commands.clear(); + d_data->pathInfos.clear(); + + d_data->boundingRect = QRectF( 0.0, 0.0, -1.0, -1.0 ); + d_data->pointRect = QRectF( 0.0, 0.0, -1.0, -1.0 ); + d_data->defaultSize = QSizeF(); + +} + +/*! + \return True, when no painter commands have been stored + \sa isEmpty(), commands() +*/ +bool QwtGraphic::isNull() const +{ + return d_data->commands.isEmpty(); +} + +/*! + \return True, when the bounding rectangle is empty + \sa boundingRect(), isNull() +*/ +bool QwtGraphic::isEmpty() const +{ + return d_data->boundingRect.isEmpty(); +} + +/*! + Toggle an render hint + + \param hint Render hint + \param on true/false + + \sa testRenderHint(), RenderHint +*/ +void QwtGraphic::setRenderHint( RenderHint hint, bool on ) +{ + if ( on ) + d_data->renderHints |= hint; + else + d_data->renderHints &= ~hint; +} + +/*! + Test a render hint + + \param hint Render hint + \return true/false + \sa setRenderHint(), RenderHint +*/ +bool QwtGraphic::testRenderHint( RenderHint hint ) const +{ + return d_data->renderHints.testFlag( hint ); +} + +/*! + The bounding rectangle is the controlPointRect() + extended by the areas needed for rendering the outlines + with unscaled pens. + + \return Bounding rectangle of the graphic + \sa controlPointRect(), scaledBoundingRect() + */ +QRectF QwtGraphic::boundingRect() const +{ + if ( d_data->boundingRect.width() < 0 ) + return QRectF(); + + return d_data->boundingRect; +} + +/*! + The control point rectangle is the bounding rectangle + of all control points of the paths and the target + rectangles of the images/pixmaps. + + \return Control point rectangle + \sa boundingRect(), scaledBoundingRect() + */ +QRectF QwtGraphic::controlPointRect() const +{ + if ( d_data->pointRect.width() < 0 ) + return QRectF(); + + return d_data->pointRect; +} + +/*! + \brief Calculate the target rectangle for scaling the graphic + + \param sx Horizontal scaling factor + \param sy Vertical scaling factor + + \note In case of paths that are painted with a cosmetic pen + ( see QPen::isCosmetic() ) the target rectangle is different to + multiplying the bounding rectangle. + + \return Scaled bounding rectangle + \sa boundingRect(), controlPointRect() + */ +QRectF QwtGraphic::scaledBoundingRect( double sx, double sy ) const +{ + if ( sx == 1.0 && sy == 1.0 ) + return d_data->boundingRect; + + QTransform transform; + transform.scale( sx, sy ); + + QRectF rect = transform.mapRect( d_data->pointRect ); + + for ( int i = 0; i < d_data->pathInfos.size(); i++ ) + { + rect |= d_data->pathInfos[i].scaledBoundingRect( sx, sy, + !d_data->renderHints.testFlag( RenderPensUnscaled ) ); + } + + return rect; +} + +//! \return Ceiled defaultSize() +QSize QwtGraphic::sizeMetrics() const +{ + const QSizeF sz = defaultSize(); + return QSize( qCeil( sz.width() ), qCeil( sz.height() ) ); +} + +/*! + \brief Set a default size + + The default size is used in all methods rendering the graphic, + where no size is explicitly specified. Assigning an empty size + means, that the default size will be calculated from the bounding + rectangle. + + The default setting is an empty size. + + \param size Default size + + \sa defaultSize(), boundingRect() + */ +void QwtGraphic::setDefaultSize( const QSizeF &size ) +{ + const double w = qMax( qreal( 0.0 ), size.width() ); + const double h = qMax( qreal( 0.0 ), size.height() ); + + d_data->defaultSize = QSizeF( w, h ); +} + +/*! + \brief Default size + + When a non empty size has been assigned by setDefaultSize() this + size will be returned. Otherwise the default size is the size + of the bounding rectangle. + + The default size is used in all methods rendering the graphic, + where no size is explicitly specified. + + \return Default size + \sa setDefaultSize(), boundingRect() + */ +QSizeF QwtGraphic::defaultSize() const +{ + if ( !d_data->defaultSize.isEmpty() ) + return d_data->defaultSize; + + return boundingRect().size(); +} + +/*! + \brief Replay all recorded painter commands + \param painter Qt painter + */ +void QwtGraphic::render( QPainter *painter ) const +{ + if ( isNull() ) + return; + + const int numCommands = d_data->commands.size(); + const QwtPainterCommand *commands = d_data->commands.constData(); + + const QTransform transform = painter->transform(); + + painter->save(); + + for ( int i = 0; i < numCommands; i++ ) + { + qwtExecCommand( painter, commands[i], + d_data->renderHints, transform ); + } + + painter->restore(); +} + +/*! + \brief Replay all recorded painter commands + + The graphic is scaled to fit into the rectangle + of the given size starting at ( 0, 0 ). + + \param painter Qt painter + \param size Size for the scaled graphic + \param aspectRatioMode Mode how to scale - See Qt::AspectRatioMode + */ +void QwtGraphic::render( QPainter *painter, const QSizeF &size, + Qt::AspectRatioMode aspectRatioMode ) const +{ + const QRectF r( 0.0, 0.0, size.width(), size.height() ); + render( painter, r, aspectRatioMode ); +} + +/*! + \brief Replay all recorded painter commands + + The graphic is scaled to fit into the given rectangle + + \param painter Qt painter + \param rect Rectangle for the scaled graphic + \param aspectRatioMode Mode how to scale - See Qt::AspectRatioMode + */ +void QwtGraphic::render( QPainter *painter, const QRectF &rect, + Qt::AspectRatioMode aspectRatioMode ) const +{ + if ( isEmpty() || rect.isEmpty() ) + return; + + double sx = 1.0; + double sy = 1.0; + + if ( d_data->pointRect.width() > 0.0 ) + sx = rect.width() / d_data->pointRect.width(); + + if ( d_data->pointRect.height() > 0.0 ) + sy = rect.height() / d_data->pointRect.height(); + + const bool scalePens = + !d_data->renderHints.testFlag( RenderPensUnscaled ); + + for ( int i = 0; i < d_data->pathInfos.size(); i++ ) + { + const PathInfo info = d_data->pathInfos[i]; + + const double ssx = info.scaleFactorX( + d_data->pointRect, rect, scalePens ); + + if ( ssx > 0.0 ) + sx = qMin( sx, ssx ); + + const double ssy = info.scaleFactorY( + d_data->pointRect, rect, scalePens ); + + if ( ssy > 0.0 ) + sy = qMin( sy, ssy ); + } + + if ( aspectRatioMode == Qt::KeepAspectRatio ) + { + const double s = qMin( sx, sy ); + sx = s; + sy = s; + } + else if ( aspectRatioMode == Qt::KeepAspectRatioByExpanding ) + { + const double s = qMax( sx, sy ); + sx = s; + sy = s; + } + + QTransform tr; + tr.translate( rect.center().x() - 0.5 * sx * d_data->pointRect.width(), + rect.center().y() - 0.5 * sy * d_data->pointRect.height() ); + tr.scale( sx, sy ); + tr.translate( -d_data->pointRect.x(), -d_data->pointRect.y() ); + + const QTransform transform = painter->transform(); + + painter->setTransform( tr, true ); + render( painter ); + + painter->setTransform( transform ); +} + +/*! + \brief Replay all recorded painter commands + + The graphic is scaled to the defaultSize() and aligned + to a position. + + \param painter Qt painter + \param pos Reference point, where to render + \param alignment Flags how to align the target rectangle + to pos. + */ +void QwtGraphic::render( QPainter *painter, + const QPointF &pos, Qt::Alignment alignment ) const +{ + QRectF r( pos, defaultSize() ); + + if ( alignment & Qt::AlignLeft ) + { + r.moveLeft( pos.x() ); + } + else if ( alignment & Qt::AlignHCenter ) + { + r.moveCenter( QPointF( pos.x(), r.center().y() ) ); + } + else if ( alignment & Qt::AlignRight ) + { + r.moveRight( pos.x() ); + } + + if ( alignment & Qt::AlignTop ) + { + r.moveTop( pos.y() ); + } + else if ( alignment & Qt::AlignVCenter ) + { + r.moveCenter( QPointF( r.center().x(), pos.y() ) ); + } + else if ( alignment & Qt::AlignBottom ) + { + r.moveBottom( pos.y() ); + } + + render( painter, r ); +} + +/*! + \brief Convert the graphic to a QPixmap + + All pixels of the pixmap get initialized by Qt::transparent + before the graphic is scaled and rendered on it. + + The size of the pixmap is the default size ( ceiled to integers ) + of the graphic. + + \return The graphic as pixmap in default size + \sa defaultSize(), toImage(), render() + */ +QPixmap QwtGraphic::toPixmap() const +{ + if ( isNull() ) + return QPixmap(); + + const QSizeF sz = defaultSize(); + + const int w = qCeil( sz.width() ); + const int h = qCeil( sz.height() ); + + QPixmap pixmap( w, h ); + pixmap.fill( Qt::transparent ); + + const QRectF r( 0.0, 0.0, sz.width(), sz.height() ); + + QPainter painter( &pixmap ); + render( &painter, r, Qt::KeepAspectRatio ); + painter.end(); + + return pixmap; +} + +/*! + \brief Convert the graphic to a QPixmap + + All pixels of the pixmap get initialized by Qt::transparent + before the graphic is scaled and rendered on it. + + \param size Size of the image + \param aspectRatioMode Aspect ratio how to scale the graphic + + \return The graphic as pixmap + \sa toImage(), render() + */ +QPixmap QwtGraphic::toPixmap( const QSize &size, + Qt::AspectRatioMode aspectRatioMode ) const +{ + QPixmap pixmap( size ); + pixmap.fill( Qt::transparent ); + + const QRect r( 0, 0, size.width(), size.height() ); + + QPainter painter( &pixmap ); + render( &painter, r, aspectRatioMode ); + painter.end(); + + return pixmap; +} + +/*! + \brief Convert the graphic to a QImage + + All pixels of the image get initialized by 0 ( transparent ) + before the graphic is scaled and rendered on it. + + The format of the image is QImage::Format_ARGB32_Premultiplied. + + \param size Size of the image + \param aspectRatioMode Aspect ratio how to scale the graphic + + \return The graphic as image + \sa toPixmap(), render() + */ +QImage QwtGraphic::toImage( const QSize &size, + Qt::AspectRatioMode aspectRatioMode ) const +{ + QImage image( size, QImage::Format_ARGB32_Premultiplied ); + image.fill( 0 ); + + const QRect r( 0, 0, size.width(), size.height() ); + + QPainter painter( &image ); + render( &painter, r, aspectRatioMode ); + painter.end(); + + return image; +} + +/*! + \brief Convert the graphic to a QImage + + All pixels of the image get initialized by 0 ( transparent ) + before the graphic is scaled and rendered on it. + + The format of the image is QImage::Format_ARGB32_Premultiplied. + + The size of the image is the default size ( ceiled to integers ) + of the graphic. + + \return The graphic as image in default size + \sa defaultSize(), toPixmap(), render() + */ +QImage QwtGraphic::toImage() const +{ + if ( isNull() ) + return QImage(); + + const QSizeF sz = defaultSize(); + + const int w = qCeil( sz.width() ); + const int h = qCeil( sz.height() ); + + QImage image( w, h, QImage::Format_ARGB32 ); + image.fill( 0 ); + + const QRect r( 0, 0, sz.width(), sz.height() ); + + QPainter painter( &image ); + render( &painter, r, Qt::KeepAspectRatio ); + painter.end(); + + return image; +} + +/*! + Store a path command in the command list + + \param path Painter path + \sa QPaintEngine::drawPath() +*/ +void QwtGraphic::drawPath( const QPainterPath &path ) +{ + const QPainter *painter = paintEngine()->painter(); + if ( painter == NULL ) + return; + + d_data->commands += QwtPainterCommand( path ); + + if ( !path.isEmpty() ) + { + const QPainterPath scaledPath = painter->transform().map( path ); + + QRectF pointRect = scaledPath.boundingRect(); + QRectF boundingRect = pointRect; + + if ( painter->pen().style() != Qt::NoPen + && painter->pen().brush().style() != Qt::NoBrush ) + { + boundingRect = qwtStrokedPathRect( painter, path ); + } + + updateControlPointRect( pointRect ); + updateBoundingRect( boundingRect ); + + d_data->pathInfos += PathInfo( pointRect, + boundingRect, qwtHasScalablePen( painter ) ); + } +} + +/*! + \brief Store a pixmap command in the command list + + \param rect target rectangle + \param pixmap Pixmap to be painted + \param subRect Reactangle of the pixmap to be painted + + \sa QPaintEngine::drawPixmap() +*/ +void QwtGraphic::drawPixmap( const QRectF &rect, + const QPixmap &pixmap, const QRectF &subRect ) +{ + const QPainter *painter = paintEngine()->painter(); + if ( painter == NULL ) + return; + + d_data->commands += QwtPainterCommand( rect, pixmap, subRect ); + + const QRectF r = painter->transform().mapRect( rect ); + updateControlPointRect( r ); + updateBoundingRect( r ); +} + +/*! + \brief Store a image command in the command list + + \param rect traget rectangle + \param image Image to be painted + \param subRect Reactangle of the pixmap to be painted + \param flags Image conversion flags + + \sa QPaintEngine::drawImage() + */ +void QwtGraphic::drawImage( const QRectF &rect, const QImage &image, + const QRectF &subRect, Qt::ImageConversionFlags flags) +{ + const QPainter *painter = paintEngine()->painter(); + if ( painter == NULL ) + return; + + d_data->commands += QwtPainterCommand( rect, image, subRect, flags ); + + const QRectF r = painter->transform().mapRect( rect ); + + updateControlPointRect( r ); + updateBoundingRect( r ); +} + +/*! + \brief Store a state command in the command list + + \param state State to be stored + \sa QPaintEngine::updateState() + */ +void QwtGraphic::updateState( const QPaintEngineState &state) +{ + d_data->commands += QwtPainterCommand( state ); +} + +void QwtGraphic::updateBoundingRect( const QRectF &rect ) +{ + QRectF br = rect; + + const QPainter *painter = paintEngine()->painter(); + if ( painter && painter->hasClipping() ) + { + QRectF cr = painter->clipRegion().boundingRect(); + cr = painter->transform().mapRect( br ); + + br &= cr; + } + + if ( d_data->boundingRect.width() < 0 ) + d_data->boundingRect = br; + else + d_data->boundingRect |= br; +} + +void QwtGraphic::updateControlPointRect( const QRectF &rect ) +{ + if ( d_data->pointRect.width() < 0.0 ) + d_data->pointRect = rect; + else + d_data->pointRect |= rect; +} + +/*! + \return List of recorded paint commands + \sa setCommands() + */ +const QVector< QwtPainterCommand > &QwtGraphic::commands() const +{ + return d_data->commands; +} + +/*! + \brief Append paint commands + + \param commands Paint commands + \sa commands() + */ +void QwtGraphic::setCommands( QVector< QwtPainterCommand > &commands ) +{ + reset(); + + const int numCommands = commands.size(); + if ( numCommands <= 0 ) + return; + + // to calculate a proper bounding rectangle we don't simply copy + // the commands. + + const QwtPainterCommand *cmds = commands.constData(); + + QPainter painter( this ); + for ( int i = 0; i < numCommands; i++ ) + qwtExecCommand( &painter, cmds[i], RenderHints(), QTransform() ); + + painter.end(); +} diff --git a/qwt/src/qwt_graphic.h b/qwt/src/qwt_graphic.h new file mode 100644 index 000000000..a1ce1e8a2 --- /dev/null +++ b/qwt/src/qwt_graphic.h @@ -0,0 +1,172 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_GRAPHIC_H +#define QWT_GRAPHIC_H + +#include "qwt_global.h" +#include "qwt_null_paintdevice.h" +#include +#include +#include + +class QwtPainterCommand; + +/*! + \brief A paint device for scalable graphics + + QwtGraphic is the representation of a graphic that is tailored for + scalability. Like QPicture it will be initialized by QPainter + operations and can be replayed later to any target paint device. + + While the usual image representations QImage and QPixmap are not + scalable Qt offers two paint devices, that might be candidates + for representing a vector graphic: + + - QPicture\n + Unfortunately QPicture had been forgotten, when Qt4 + introduced floating point based render engines. Its API + is still on integers, what make it unusable for proper scaling. + + - QSvgRenderer/QSvgGenerator\n + Unfortunately QSvgRenderer hides to much information about + its nodes in internal APIs, that are necessary for proper + layout calculations. Also it is derived from QObject and + can't be copied like QImage/QPixmap. + + QwtGraphic maps all scalable drawing primitives to a QPainterPath + and stores them together with the painter state changes + ( pen, brush, transformation ... ) in a list of QwtPaintCommands. + For being a complete QPaintDevice it also stores pixmaps or images, + what is somehow against the idea of the class, because these objects + can't be scaled without a loss in quality. + + The main issue about scaling a QwtGraphic object are the pens used for + drawing the outlines of the painter paths. While non cosmetic pens + ( QPen::isCosmetic() ) are scaled with the same ratio as the path, + cosmetic pens have a fixed width. A graphic might have paths with + different pens - cosmetic and non-cosmetic. + + QwtGraphic caches 2 different rectangles: + + - control point rectangle\n + The control point rectangle is the bounding rectangle of all + control point rectangles of the painter paths, or the target + rectangle of the pixmaps/images. + + - bounding rectangle\n + The bounding rectangle extends the control point rectangle by + what is needed for rendering the outline with an unscaled pen. + + Because the offset for drawing the outline depends on the shape + of the painter path ( the peak of a triangle is different than the flat side ) + scaling with a fixed aspect ratio always needs to be calculated from the + control point rectangle. + + \sa QwtPainterCommand + */ +class QWT_EXPORT QwtGraphic: public QwtNullPaintDevice +{ +public: + /*! + Hint how to render a graphic + \sa setRenderHint(), testRenderHint() + */ + enum RenderHint + { + /*! + When RenderPensUnscaled is set non cosmetic pens are + painted unscaled - like cosmetic pens. The difference to + using cosmetic pens is, when the graphic is rendered + to a document in a scalable vector format ( PDF, SVG ): + the width of non cosmetic pens will be scaled by the + document viewer. + */ + RenderPensUnscaled = 0x1 + }; + + /*! + \brief Render hints + + The default setting is to disable all hints + */ + typedef QFlags RenderHints; + + QwtGraphic(); + QwtGraphic( const QwtGraphic & ); + + virtual ~QwtGraphic(); + + QwtGraphic& operator=( const QwtGraphic & ); + + void reset(); + + bool isNull() const; + bool isEmpty() const; + + void render( QPainter * ) const; + + void render( QPainter *, const QSizeF &, + Qt::AspectRatioMode = Qt::IgnoreAspectRatio ) const; + + void render( QPainter *, const QRectF &, + Qt::AspectRatioMode = Qt::IgnoreAspectRatio ) const; + + void render( QPainter *, const QPointF &, + Qt::Alignment = Qt::AlignTop | Qt::AlignLeft ) const; + + QPixmap toPixmap() const; + QPixmap toPixmap( const QSize &, + Qt::AspectRatioMode = Qt::IgnoreAspectRatio ) const; + + QImage toImage() const; + QImage toImage( const QSize &, + Qt::AspectRatioMode = Qt::IgnoreAspectRatio ) const; + + QRectF scaledBoundingRect( double sx, double sy ) const; + + QRectF boundingRect() const; + QRectF controlPointRect() const; + + const QVector< QwtPainterCommand > &commands() const; + void setCommands( QVector< QwtPainterCommand > & ); + + void setDefaultSize( const QSizeF & ); + QSizeF defaultSize() const; + + void setRenderHint( RenderHint, bool on = true ); + bool testRenderHint( RenderHint ) const; + +protected: + virtual QSize sizeMetrics() const; + + virtual void drawPath( const QPainterPath & ); + + virtual void drawPixmap( const QRectF &, + const QPixmap &, const QRectF & ); + + virtual void drawImage( const QRectF &, + const QImage &, const QRectF &, Qt::ImageConversionFlags ); + + virtual void updateState( const QPaintEngineState &state ); + +private: + void updateBoundingRect( const QRectF & ); + void updateControlPointRect( const QRectF & ); + + class PathInfo; + + class PrivateData; + PrivateData *d_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtGraphic::RenderHints ) +Q_DECLARE_METATYPE( QwtGraphic ) + +#endif diff --git a/qwt/src/qwt_interval.cpp b/qwt/src/qwt_interval.cpp new file mode 100644 index 000000000..b7c6ee992 --- /dev/null +++ b/qwt/src/qwt_interval.cpp @@ -0,0 +1,354 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_interval.h" +#include "qwt_math.h" +#include + +/*! + \brief Normalize the limits of the interval + + If maxValue() < minValue() the limits will be inverted. + \return Normalized interval + + \sa isValid(), inverted() +*/ +QwtInterval QwtInterval::normalized() const +{ + if ( d_minValue > d_maxValue ) + { + return inverted(); + } + if ( d_minValue == d_maxValue && d_borderFlags == ExcludeMinimum ) + { + return inverted(); + } + + return *this; +} + +/*! + Invert the limits of the interval + \return Inverted interval + \sa normalized() +*/ +QwtInterval QwtInterval::inverted() const +{ + BorderFlags borderFlags = IncludeBorders; + if ( d_borderFlags & ExcludeMinimum ) + borderFlags |= ExcludeMaximum; + if ( d_borderFlags & ExcludeMaximum ) + borderFlags |= ExcludeMinimum; + + return QwtInterval( d_maxValue, d_minValue, borderFlags ); +} + +/*! + Test if a value is inside an interval + + \param value Value + \return true, if value >= minValue() && value <= maxValue() +*/ +bool QwtInterval::contains( double value ) const +{ + if ( !isValid() ) + return false; + + if ( value < d_minValue || value > d_maxValue ) + return false; + + if ( value == d_minValue && d_borderFlags & ExcludeMinimum ) + return false; + + if ( value == d_maxValue && d_borderFlags & ExcludeMaximum ) + return false; + + return true; +} + +//! Unite 2 intervals +QwtInterval QwtInterval::unite( const QwtInterval &other ) const +{ + /* + If one of the intervals is invalid return the other one. + If both are invalid return an invalid default interval + */ + if ( !isValid() ) + { + if ( !other.isValid() ) + return QwtInterval(); + else + return other; + } + if ( !other.isValid() ) + return *this; + + QwtInterval united; + BorderFlags flags = IncludeBorders; + + // minimum + if ( d_minValue < other.minValue() ) + { + united.setMinValue( d_minValue ); + flags &= d_borderFlags & ExcludeMinimum; + } + else if ( other.minValue() < d_minValue ) + { + united.setMinValue( other.minValue() ); + flags &= other.borderFlags() & ExcludeMinimum; + } + else // d_minValue == other.minValue() + { + united.setMinValue( d_minValue ); + flags &= ( d_borderFlags & other.borderFlags() ) & ExcludeMinimum; + } + + // maximum + if ( d_maxValue > other.maxValue() ) + { + united.setMaxValue( d_maxValue ); + flags &= d_borderFlags & ExcludeMaximum; + } + else if ( other.maxValue() > d_maxValue ) + { + united.setMaxValue( other.maxValue() ); + flags &= other.borderFlags() & ExcludeMaximum; + } + else // d_maxValue == other.maxValue() ) + { + united.setMaxValue( d_maxValue ); + flags &= d_borderFlags & other.borderFlags() & ExcludeMaximum; + } + + united.setBorderFlags( flags ); + return united; +} + +/*! + \brief Intersect 2 intervals + + \param other Interval to be intersect with + \return Intersection + */ +QwtInterval QwtInterval::intersect( const QwtInterval &other ) const +{ + if ( !other.isValid() || !isValid() ) + return QwtInterval(); + + QwtInterval i1 = *this; + QwtInterval i2 = other; + + // swap i1/i2, so that the minimum of i1 + // is smaller then the minimum of i2 + + if ( i1.minValue() > i2.minValue() ) + { + qSwap( i1, i2 ); + } + else if ( i1.minValue() == i2.minValue() ) + { + if ( i1.borderFlags() & ExcludeMinimum ) + qSwap( i1, i2 ); + } + + if ( i1.maxValue() < i2.minValue() ) + { + return QwtInterval(); + } + + if ( i1.maxValue() == i2.minValue() ) + { + if ( i1.borderFlags() & ExcludeMaximum || + i2.borderFlags() & ExcludeMinimum ) + { + return QwtInterval(); + } + } + + QwtInterval intersected; + BorderFlags flags = IncludeBorders; + + intersected.setMinValue( i2.minValue() ); + flags |= i2.borderFlags() & ExcludeMinimum; + + if ( i1.maxValue() < i2.maxValue() ) + { + intersected.setMaxValue( i1.maxValue() ); + flags |= i1.borderFlags() & ExcludeMaximum; + } + else if ( i2.maxValue() < i1.maxValue() ) + { + intersected.setMaxValue( i2.maxValue() ); + flags |= i2.borderFlags() & ExcludeMaximum; + } + else // i1.maxValue() == i2.maxValue() + { + intersected.setMaxValue( i1.maxValue() ); + flags |= i1.borderFlags() & i2.borderFlags() & ExcludeMaximum; + } + + intersected.setBorderFlags( flags ); + return intersected; +} + +/*! + \brief Unite this interval with the given interval. + + \param other Interval to be united with + \return This interval + */ +QwtInterval& QwtInterval::operator|=( const QwtInterval &other ) +{ + *this = *this | other; + return *this; +} + +/*! + \brief Intersect this interval with the given interval. + + \param other Interval to be intersected with + \return This interval + */ +QwtInterval& QwtInterval::operator&=( const QwtInterval &other ) +{ + *this = *this & other; + return *this; +} + +/*! + \brief Test if two intervals overlap + + \param other Interval + \return True, when the intervals are intersecting +*/ +bool QwtInterval::intersects( const QwtInterval &other ) const +{ + if ( !isValid() || !other.isValid() ) + return false; + + QwtInterval i1 = *this; + QwtInterval i2 = other; + + // swap i1/i2, so that the minimum of i1 + // is smaller then the minimum of i2 + + if ( i1.minValue() > i2.minValue() ) + { + qSwap( i1, i2 ); + } + else if ( i1.minValue() == i2.minValue() && + i1.borderFlags() & ExcludeMinimum ) + { + qSwap( i1, i2 ); + } + + if ( i1.maxValue() > i2.minValue() ) + { + return true; + } + if ( i1.maxValue() == i2.minValue() ) + { + return !( ( i1.borderFlags() & ExcludeMaximum ) || + ( i2.borderFlags() & ExcludeMinimum ) ); + } + return false; +} + +/*! + Adjust the limit that is closer to value, so that value becomes + the center of the interval. + + \param value Center + \return Interval with value as center +*/ +QwtInterval QwtInterval::symmetrize( double value ) const +{ + if ( !isValid() ) + return *this; + + const double delta = + qMax( qAbs( value - d_maxValue ), qAbs( value - d_minValue ) ); + + return QwtInterval( value - delta, value + delta ); +} + +/*! + Limit the interval, keeping the border modes + + \param lowerBound Lower limit + \param upperBound Upper limit + + \return Limited interval +*/ +QwtInterval QwtInterval::limited( double lowerBound, double upperBound ) const +{ + if ( !isValid() || lowerBound > upperBound ) + return QwtInterval(); + + double minValue = qMax( d_minValue, lowerBound ); + minValue = qMin( minValue, upperBound ); + + double maxValue = qMax( d_maxValue, lowerBound ); + maxValue = qMin( maxValue, upperBound ); + + return QwtInterval( minValue, maxValue, d_borderFlags ); +} + +/*! + \brief Extend the interval + + If value is below minValue(), value becomes the lower limit. + If value is above maxValue(), value becomes the upper limit. + + extend() has no effect for invalid intervals + + \param value Value + \return extended interval + + \sa isValid() +*/ +QwtInterval QwtInterval::extend( double value ) const +{ + if ( !isValid() ) + return *this; + + return QwtInterval( qMin( value, d_minValue ), + qMax( value, d_maxValue ), d_borderFlags ); +} + +/*! + Extend an interval + + \param value Value + \return Reference of the extended interval + + \sa extend() +*/ +QwtInterval& QwtInterval::operator|=( double value ) +{ + *this = *this | value; + return *this; +} + +#ifndef QT_NO_DEBUG_STREAM + +QDebug operator<<( QDebug debug, const QwtInterval &interval ) +{ + const int flags = interval.borderFlags(); + + debug.nospace() << "QwtInterval(" + << ( ( flags & QwtInterval::ExcludeMinimum ) ? "]" : "[" ) + << interval.minValue() << "," << interval.maxValue() + << ( ( flags & QwtInterval::ExcludeMaximum ) ? "[" : "]" ) + << ")"; + + return debug.space(); +} + +#endif diff --git a/qwt/src/qwt_interval.h b/qwt/src/qwt_interval.h new file mode 100644 index 000000000..68841e00b --- /dev/null +++ b/qwt/src/qwt_interval.h @@ -0,0 +1,320 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_INTERVAL_H +#define QWT_INTERVAL_H + +#include "qwt_global.h" +#include + +#ifndef QT_NO_DEBUG_STREAM +#include +#endif + +/*! + \brief A class representing an interval + + The interval is represented by 2 doubles, the lower and the upper limit. +*/ + +class QWT_EXPORT QwtInterval +{ +public: + /*! + Flag indicating if a border is included or excluded + \sa setBorderFlags(), borderFlags() + */ + enum BorderFlag + { + //! Min/Max values are inside the interval + IncludeBorders = 0x00, + + //! Min value is not included in the interval + ExcludeMinimum = 0x01, + + //! Max value is not included in the interval + ExcludeMaximum = 0x02, + + //! Min/Max values are not included in the interval + ExcludeBorders = ExcludeMinimum | ExcludeMaximum + }; + + //! Border flags + typedef QFlags BorderFlags; + + QwtInterval(); + QwtInterval( double minValue, double maxValue, + BorderFlags = IncludeBorders ); + + void setInterval( double minValue, double maxValue, + BorderFlags = IncludeBorders ); + + QwtInterval normalized() const; + QwtInterval inverted() const; + QwtInterval limited( double minValue, double maxValue ) const; + + bool operator==( const QwtInterval & ) const; + bool operator!=( const QwtInterval & ) const; + + void setBorderFlags( BorderFlags ); + BorderFlags borderFlags() const; + + double minValue() const; + double maxValue() const; + + double width() const; + + void setMinValue( double ); + void setMaxValue( double ); + + bool contains( double value ) const; + + bool intersects( const QwtInterval & ) const; + QwtInterval intersect( const QwtInterval & ) const; + QwtInterval unite( const QwtInterval & ) const; + + QwtInterval operator|( const QwtInterval & ) const; + QwtInterval operator&( const QwtInterval & ) const; + + QwtInterval &operator|=( const QwtInterval & ); + QwtInterval &operator&=( const QwtInterval & ); + + QwtInterval extend( double value ) const; + QwtInterval operator|( double ) const; + QwtInterval &operator|=( double ); + + bool isValid() const; + bool isNull() const; + void invalidate(); + + QwtInterval symmetrize( double value ) const; + +private: + double d_minValue; + double d_maxValue; + BorderFlags d_borderFlags; +}; + +Q_DECLARE_TYPEINFO(QwtInterval, Q_MOVABLE_TYPE); + +/*! + \brief Default Constructor + + Creates an invalid interval [0.0, -1.0] + \sa setInterval(), isValid() +*/ +inline QwtInterval::QwtInterval(): + d_minValue( 0.0 ), + d_maxValue( -1.0 ), + d_borderFlags( IncludeBorders ) +{ +} + +/*! + Constructor + + Build an interval with from min/max values + + \param minValue Minimum value + \param maxValue Maximum value + \param borderFlags Include/Exclude borders +*/ +inline QwtInterval::QwtInterval( + double minValue, double maxValue, BorderFlags borderFlags ): + d_minValue( minValue ), + d_maxValue( maxValue ), + d_borderFlags( borderFlags ) +{ +} + +/*! + Assign the limits of the interval + + \param minValue Minimum value + \param maxValue Maximum value + \param borderFlags Include/Exclude borders +*/ +inline void QwtInterval::setInterval( + double minValue, double maxValue, BorderFlags borderFlags ) +{ + d_minValue = minValue; + d_maxValue = maxValue; + d_borderFlags = borderFlags; +} + +/*! + Change the border flags + + \param borderFlags Or'd BorderMode flags + \sa borderFlags() +*/ +inline void QwtInterval::setBorderFlags( BorderFlags borderFlags ) +{ + d_borderFlags = borderFlags; +} + +/*! + \return Border flags + \sa setBorderFlags() +*/ +inline QwtInterval::BorderFlags QwtInterval::borderFlags() const +{ + return d_borderFlags; +} + +/*! + Assign the lower limit of the interval + + \param minValue Minimum value +*/ +inline void QwtInterval::setMinValue( double minValue ) +{ + d_minValue = minValue; +} + +/*! + Assign the upper limit of the interval + + \param maxValue Maximum value +*/ +inline void QwtInterval::setMaxValue( double maxValue ) +{ + d_maxValue = maxValue; +} + +//! \return Lower limit of the interval +inline double QwtInterval::minValue() const +{ + return d_minValue; +} + +//! \return Upper limit of the interval +inline double QwtInterval::maxValue() const +{ + return d_maxValue; +} + +/*! + A interval is valid when minValue() <= maxValue(). + In case of QwtInterval::ExcludeBorders it is true + when minValue() < maxValue() + + \return True, when the interval is valid +*/ +inline bool QwtInterval::isValid() const +{ + if ( ( d_borderFlags & ExcludeBorders ) == 0 ) + return d_minValue <= d_maxValue; + else + return d_minValue < d_maxValue; +} + +/*! + \brief Return the width of an interval + + The width of invalid intervals is 0.0, otherwise the result is + maxValue() - minValue(). + + \return Interval width + \sa isValid() +*/ +inline double QwtInterval::width() const +{ + return isValid() ? ( d_maxValue - d_minValue ) : 0.0; +} + +/*! + \brief Intersection of two intervals + + \param other Interval to intersect with + \return Intersection of this and other + + \sa intersect() +*/ +inline QwtInterval QwtInterval::operator&( + const QwtInterval &other ) const +{ + return intersect( other ); +} + +/*! + Union of two intervals + + \param other Interval to unite with + \return Union of this and other + + \sa unite() +*/ +inline QwtInterval QwtInterval::operator|( + const QwtInterval &other ) const +{ + return unite( other ); +} + +/*! + \brief Compare two intervals + + \param other Interval to compare with + \return True, when this and other are equal +*/ +inline bool QwtInterval::operator==( const QwtInterval &other ) const +{ + return ( d_minValue == other.d_minValue ) && + ( d_maxValue == other.d_maxValue ) && + ( d_borderFlags == other.d_borderFlags ); +} +/*! + \brief Compare two intervals + + \param other Interval to compare with + \return True, when this and other are not equal +*/ +inline bool QwtInterval::operator!=( const QwtInterval &other ) const +{ + return ( !( *this == other ) ); +} + +/*! + Extend an interval + + \param value Value + \return Extended interval + \sa extend() +*/ +inline QwtInterval QwtInterval::operator|( double value ) const +{ + return extend( value ); +} + +//! \return true, if isValid() && (minValue() >= maxValue()) +inline bool QwtInterval::isNull() const +{ + return isValid() && d_minValue >= d_maxValue; +} + +/*! + Invalidate the interval + + The limits are set to interval [0.0, -1.0] + \sa isValid() +*/ +inline void QwtInterval::invalidate() +{ + d_minValue = 0.0; + d_maxValue = -1.0; +} + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtInterval::BorderFlags ) +Q_DECLARE_METATYPE( QwtInterval ) + +#ifndef QT_NO_DEBUG_STREAM +QWT_EXPORT QDebug operator<<( QDebug, const QwtInterval & ); +#endif + +#endif diff --git a/qwt/src/qwt_interval_symbol.cpp b/qwt/src/qwt_interval_symbol.cpp new file mode 100644 index 000000000..83c842d98 --- /dev/null +++ b/qwt/src/qwt_interval_symbol.cpp @@ -0,0 +1,319 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_interval_symbol.h" +#include "qwt_painter.h" +#include "qwt_math.h" +#include + +#if QT_VERSION < 0x040601 +#define qAtan2(y, x) ::atan2(y, x) +#define qFastSin(x) qSin(x) +#define qFastCos(x) qCos(x) +#endif + +class QwtIntervalSymbol::PrivateData +{ +public: + PrivateData(): + style( QwtIntervalSymbol::NoSymbol ), + width( 6 ) + { + } + + bool operator==( const PrivateData &other ) const + { + return ( style == other.style ) + && ( width == other.width ) + && ( brush == other.brush ) + && ( pen == other.pen ); + } + + QwtIntervalSymbol::Style style; + int width; + + QPen pen; + QBrush brush; +}; + +/*! + Constructor + + \param style Style of the symbol + \sa setStyle(), style(), Style +*/ +QwtIntervalSymbol::QwtIntervalSymbol( Style style ) +{ + d_data = new PrivateData(); + d_data->style = style; +} + +//! Copy constructor +QwtIntervalSymbol::QwtIntervalSymbol( const QwtIntervalSymbol &other ) +{ + d_data = new PrivateData(); + *d_data = *other.d_data; +} + +//! Destructor +QwtIntervalSymbol::~QwtIntervalSymbol() +{ + delete d_data; +} + +//! \brief Assignment operator +QwtIntervalSymbol &QwtIntervalSymbol::operator=( + const QwtIntervalSymbol &other ) +{ + *d_data = *other.d_data; + return *this; +} + +//! \brief Compare two symbols +bool QwtIntervalSymbol::operator==( + const QwtIntervalSymbol &other ) const +{ + return *d_data == *other.d_data; +} + +//! \brief Compare two symbols +bool QwtIntervalSymbol::operator!=( + const QwtIntervalSymbol &other ) const +{ + return !( *d_data == *other.d_data ); +} + +/*! + Specify the symbol style + + \param style Style + \sa style(), Style +*/ +void QwtIntervalSymbol::setStyle( Style style ) +{ + d_data->style = style; +} + +/*! + \return Current symbol style + \sa setStyle() +*/ +QwtIntervalSymbol::Style QwtIntervalSymbol::style() const +{ + return d_data->style; +} + +/*! + Specify the width of the symbol + It is used depending on the style. + + \param width Width + \sa width(), setStyle() +*/ +void QwtIntervalSymbol::setWidth( int width ) +{ + d_data->width = width; +} + +/*! + \return Width of the symbol. + \sa setWidth(), setStyle() +*/ +int QwtIntervalSymbol::width() const +{ + return d_data->width; +} + +/*! + \brief Assign a brush + + The brush is used for the Box style. + + \param brush Brush + \sa brush() +*/ +void QwtIntervalSymbol::setBrush( const QBrush &brush ) +{ + d_data->brush = brush; +} + +/*! + \return Brush + \sa setBrush() +*/ +const QBrush& QwtIntervalSymbol::brush() const +{ + return d_data->brush; +} + +/*! + Build and assign a pen + + In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it + non cosmetic ( see QPen::isCosmetic() ). This method has been introduced + to hide this incompatibility. + + \param color Pen color + \param width Pen width + \param style Pen style + + \sa pen(), brush() + */ +void QwtIntervalSymbol::setPen( const QColor &color, + qreal width, Qt::PenStyle style ) +{ + setPen( QPen( color, width, style ) ); +} + +/*! + Assign a pen + + \param pen Pen + \sa pen(), setBrush() +*/ +void QwtIntervalSymbol::setPen( const QPen &pen ) +{ + d_data->pen = pen; +} + +/*! + \return Pen + \sa setPen(), brush() +*/ +const QPen& QwtIntervalSymbol::pen() const +{ + return d_data->pen; +} + +/*! + Draw a symbol depending on its style + + \param painter Painter + \param orientation Orientation + \param from Start point of the interval in target device coordinates + \param to End point of the interval in target device coordinates + + \sa setStyle() +*/ +void QwtIntervalSymbol::draw( QPainter *painter, Qt::Orientation orientation, + const QPointF &from, const QPointF &to ) const +{ + const qreal pw = qMax( painter->pen().widthF(), qreal( 1.0 ) ); + + QPointF p1 = from; + QPointF p2 = to; + if ( QwtPainter::roundingAlignment( painter ) ) + { + p1 = p1.toPoint(); + p2 = p2.toPoint(); + } + + switch ( d_data->style ) + { + case QwtIntervalSymbol::Bar: + { + QwtPainter::drawLine( painter, p1, p2 ); + if ( d_data->width > pw ) + { + if ( ( orientation == Qt::Horizontal ) + && ( p1.y() == p2.y() ) ) + { + const double sw = d_data->width; + + const double y = p1.y() - sw / 2; + QwtPainter::drawLine( painter, + p1.x(), y, p1.x(), y + sw ); + QwtPainter::drawLine( painter, + p2.x(), y, p2.x(), y + sw ); + } + else if ( ( orientation == Qt::Vertical ) + && ( p1.x() == p2.x() ) ) + { + const double sw = d_data->width; + + const double x = p1.x() - sw / 2; + QwtPainter::drawLine( painter, + x, p1.y(), x + sw, p1.y() ); + QwtPainter::drawLine( painter, + x, p2.y(), x + sw, p2.y() ); + } + else + { + const double sw = d_data->width; + + const double dx = p2.x() - p1.x(); + const double dy = p2.y() - p1.y(); + const double angle = qAtan2( dy, dx ) + M_PI_2; + double dw2 = sw / 2.0; + + const double cx = qFastCos( angle ) * dw2; + const double sy = qFastSin( angle ) * dw2; + + QwtPainter::drawLine( painter, + p1.x() - cx, p1.y() - sy, + p1.x() + cx, p1.y() + sy ); + QwtPainter::drawLine( painter, + p2.x() - cx, p2.y() - sy, + p2.x() + cx, p2.y() + sy ); + } + } + break; + } + case QwtIntervalSymbol::Box: + { + if ( d_data->width <= pw ) + { + QwtPainter::drawLine( painter, p1, p2 ); + } + else + { + if ( ( orientation == Qt::Horizontal ) + && ( p1.y() == p2.y() ) ) + { + const double sw = d_data->width; + + const double y = p1.y() - d_data->width / 2; + QwtPainter::drawRect( painter, + p1.x(), y, p2.x() - p1.x(), sw ); + } + else if ( ( orientation == Qt::Vertical ) + && ( p1.x() == p2.x() ) ) + { + const double sw = d_data->width; + + const double x = p1.x() - d_data->width / 2; + QwtPainter::drawRect( painter, + x, p1.y(), sw, p2.y() - p1.y() ); + } + else + { + const double sw = d_data->width; + + const double dx = p2.x() - p1.x(); + const double dy = p2.y() - p1.y(); + const double angle = qAtan2( dy, dx ) + M_PI_2; + double dw2 = sw / 2.0; + + const double cx = qFastCos( angle ) * dw2; + const double sy = qFastSin( angle ) * dw2; + + QPolygonF polygon; + polygon += QPointF( p1.x() - cx, p1.y() - sy ); + polygon += QPointF( p1.x() + cx, p1.y() + sy ); + polygon += QPointF( p2.x() + cx, p2.y() + sy ); + polygon += QPointF( p2.x() - cx, p2.y() - sy ); + + QwtPainter::drawPolygon( painter, polygon ); + } + } + break; + } + default:; + } +} diff --git a/qwt/src/qwt_interval_symbol.h b/qwt/src/qwt_interval_symbol.h new file mode 100644 index 000000000..f32e1c41d --- /dev/null +++ b/qwt/src/qwt_interval_symbol.h @@ -0,0 +1,87 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_INTERVAL_SYMBOL_H +#define QWT_INTERVAL_SYMBOL_H + +#include "qwt_global.h" +#include +#include + +class QPainter; +class QRect; +class QPointF; + +/*! + \brief A drawing primitive for displaying an interval like an error bar + + \sa QwtPlotIntervalCurve +*/ +class QWT_EXPORT QwtIntervalSymbol +{ +public: + //! Symbol style + enum Style + { + //! No Style. The symbol cannot be drawn. + NoSymbol = -1, + + /*! + The symbol displays a line with caps at the beginning/end. + The size of the caps depends on the symbol width(). + */ + Bar, + + /*! + The symbol displays a plain rectangle using pen() and brush(). + The size of the rectangle depends on the translated interval and + the width(), + */ + Box, + + /*! + Styles >= UserSymbol are reserved for derived + classes of QwtIntervalSymbol that overload draw() with + additional application specific symbol types. + */ + UserSymbol = 1000 + }; + +public: + QwtIntervalSymbol( Style = NoSymbol ); + QwtIntervalSymbol( const QwtIntervalSymbol & ); + virtual ~QwtIntervalSymbol(); + + QwtIntervalSymbol &operator=( const QwtIntervalSymbol & ); + bool operator==( const QwtIntervalSymbol & ) const; + bool operator!=( const QwtIntervalSymbol & ) const; + + void setWidth( int ); + int width() const; + + void setBrush( const QBrush& b ); + const QBrush& brush() const; + + void setPen( const QColor &, qreal width = 0.0, Qt::PenStyle = Qt::SolidLine ); + void setPen( const QPen & ); + const QPen& pen() const; + + void setStyle( Style ); + Style style() const; + + virtual void draw( QPainter *, Qt::Orientation, + const QPointF& from, const QPointF& to ) const; + +private: + + class PrivateData; + PrivateData* d_data; +}; + +#endif diff --git a/qwt/src/qwt_knob.cpp b/qwt/src/qwt_knob.cpp new file mode 100644 index 000000000..5860e42c4 --- /dev/null +++ b/qwt/src/qwt_knob.cpp @@ -0,0 +1,845 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_knob.h" +#include "qwt_round_scale_draw.h" +#include "qwt_math.h" +#include "qwt_painter.h" +#include "qwt_scale_map.h" +#include +#include +#include +#include +#include +#include +#include + +#if QT_VERSION < 0x040601 +#define qAtan2(y, x) ::atan2(y, x) +#define qFabs(x) ::fabs(x) +#define qFastCos(x) qCos(x) +#define qFastSin(x) qSin(x) +#endif + +static QSize qwtKnobSizeHint( const QwtKnob *knob, int min ) +{ + int knobWidth = knob->knobWidth(); + if ( knobWidth <= 0 ) + knobWidth = qMax( 3 * knob->markerSize(), min ); + + // Add the scale radial thickness to the knobWidth + const int extent = qCeil( knob->scaleDraw()->extent( knob->font() ) ); + const int d = 2 * ( extent + 4 ) + knobWidth; + + int left, right, top, bottom; + knob->getContentsMargins( &left, &top, &right, &bottom ); + + return QSize( d + left + right, d + top + bottom ); +} + +static inline double qwtToScaleAngle( double angle ) +{ + // the map is counter clockwise with the origin + // at 90° using angles from -180° -> 180° + + double a = 90.0 - angle; + if ( a <= -180.0 ) + a += 360.0; + else if ( a >= 180.0 ) + a -= 360.0; + + return a; +} + +static double qwtToDegrees( double value ) +{ + return qwtNormalizeDegrees( 90.0 - value ); +} + +class QwtKnob::PrivateData +{ +public: + PrivateData(): + knobStyle( QwtKnob::Raised ), + markerStyle( QwtKnob::Notch ), + borderWidth( 2 ), + borderDist( 4 ), + scaleDist( 4 ), + maxScaleTicks( 11 ), + knobWidth( 0 ), + alignment( Qt::AlignCenter ), + markerSize( 8 ), + totalAngle( 270.0 ), + mouseOffset( 0.0 ) + { + } + + QwtKnob::KnobStyle knobStyle; + QwtKnob::MarkerStyle markerStyle; + + int borderWidth; + int borderDist; + int scaleDist; + int maxScaleTicks; + int knobWidth; + Qt::Alignment alignment; + int markerSize; + + double totalAngle; + + double mouseOffset; +}; + +/*! + \brief Constructor + + Construct a knob with an angle of 270°. The style is + QwtKnob::Raised and the marker style is QwtKnob::Notch. + The width of the knob is set to 50 pixels. + + \param parent Parent widget + + \sa setTotalAngle() +*/ +QwtKnob::QwtKnob( QWidget* parent ): + QwtAbstractSlider( parent ) +{ + d_data = new PrivateData; + + setScaleDraw( new QwtRoundScaleDraw() ); + + setTotalAngle( 270.0 ); + + setScale( 0.0, 10.0 ); + setValue( 0.0 ); + + setSizePolicy( QSizePolicy::MinimumExpanding, + QSizePolicy::MinimumExpanding ); +} + +//! Destructor +QwtKnob::~QwtKnob() +{ + delete d_data; +} + +/*! + \brief Set the knob type + + \param knobStyle Knob type + \sa knobStyle(), setBorderWidth() +*/ +void QwtKnob::setKnobStyle( KnobStyle knobStyle ) +{ + if ( d_data->knobStyle != knobStyle ) + { + d_data->knobStyle = knobStyle; + update(); + } +} + +/*! + \return Marker type of the knob + \sa setKnobStyle(), setBorderWidth() +*/ +QwtKnob::KnobStyle QwtKnob::knobStyle() const +{ + return d_data->knobStyle; +} + +/*! + \brief Set the marker type of the knob + + \param markerStyle Marker type + \sa markerStyle(), setMarkerSize() +*/ +void QwtKnob::setMarkerStyle( MarkerStyle markerStyle ) +{ + if ( d_data->markerStyle != markerStyle ) + { + d_data->markerStyle = markerStyle; + update(); + } +} + +/*! + \return Marker type of the knob + \sa setMarkerStyle(), setMarkerSize() +*/ +QwtKnob::MarkerStyle QwtKnob::markerStyle() const +{ + return d_data->markerStyle; +} + +/*! + \brief Set the total angle by which the knob can be turned + \param angle Angle in degrees. + + The angle has to be between [10, 360] degrees. Angles above + 360 ( so that the knob can be turned several times around its axis ) + have to be set using setNumTurns(). + + The default angle is 270 degrees. + + \sa totalAngle(), setNumTurns() +*/ +void QwtKnob::setTotalAngle ( double angle ) +{ + angle = qBound( 10.0, angle, 360.0 ); + + if ( angle != d_data->totalAngle ) + { + d_data->totalAngle = angle; + + scaleDraw()->setAngleRange( -0.5 * d_data->totalAngle, + 0.5 * d_data->totalAngle ); + + updateGeometry(); + update(); + } +} + +/*! + \return the total angle + \sa setTotalAngle(), setNumTurns(), numTurns() + */ +double QwtKnob::totalAngle() const +{ + return d_data->totalAngle; +} + +/*! + \brief Set the number of turns + + When numTurns > 1 the knob can be turned several times around its axis + - otherwise the total angle is floored to 360°. + + \sa numTurns(), totalAngle(), setTotalAngle() +*/ + +void QwtKnob::setNumTurns( int numTurns ) +{ + numTurns = qMax( numTurns, 1 ); + + if ( numTurns == 1 && d_data->totalAngle <= 360.0 ) + return; + + const double angle = numTurns * 360.0; + if ( angle != d_data->totalAngle ) + { + d_data->totalAngle = angle; + + scaleDraw()->setAngleRange( -0.5 * d_data->totalAngle, + 0.5 * d_data->totalAngle ); + + updateGeometry(); + update(); + } +} + +/*! + \return Number of turns. + + When the total angle is below 360° numTurns() is ceiled to 1. + \sa setNumTurns(), setTotalAngle(), totalAngle() + */ +int QwtKnob::numTurns() const +{ + return qCeil( d_data->totalAngle / 360.0 ); +} + +/*! + Change the scale draw of the knob + + For changing the labels of the scales, it + is necessary to derive from QwtRoundScaleDraw and + overload QwtRoundScaleDraw::label(). + + \sa scaleDraw() +*/ +void QwtKnob::setScaleDraw( QwtRoundScaleDraw *scaleDraw ) +{ + setAbstractScaleDraw( scaleDraw ); + setTotalAngle( d_data->totalAngle ); +} + +/*! + \return the scale draw of the knob + \sa setScaleDraw() +*/ +const QwtRoundScaleDraw *QwtKnob::scaleDraw() const +{ + return static_cast( abstractScaleDraw() ); +} + +/*! + \return the scale draw of the knob + \sa setScaleDraw() +*/ +QwtRoundScaleDraw *QwtKnob::scaleDraw() +{ + return static_cast( abstractScaleDraw() ); +} + +/*! + Calculate the bounding rectangle of the knob without the scale + + \return Bounding rectangle of the knob + \sa knobWidth(), alignment(), QWidget::contentsRect() + */ +QRect QwtKnob::knobRect() const +{ + const QRect cr = contentsRect(); + + const int extent = qCeil( scaleDraw()->extent( font() ) ); + const int d = extent + d_data->scaleDist; + + int w = d_data->knobWidth; + if ( w <= 0 ) + { + const int dim = qMin( cr.width(), cr.height() ); + + w = dim - 2 * ( d ); + w = qMax( 0, w ); + } + + QRect r( 0, 0, w, w ); + + if ( d_data->alignment & Qt::AlignLeft ) + { + r.moveLeft( cr.left() + d ); + } + else if ( d_data->alignment & Qt::AlignRight ) + { + r.moveRight( cr.right() - d ); + } + else + { + r.moveCenter( QPoint( cr.center().x(), r.center().y() ) ); + } + + if ( d_data->alignment & Qt::AlignTop ) + { + r.moveTop( cr.top() + d ); + } + else if ( d_data->alignment & Qt::AlignBottom ) + { + r.moveBottom( cr.bottom() - d ); + } + else + { + r.moveCenter( QPoint( r.center().x(), cr.center().y() ) ); + } + + return r; +} + +/*! + \brief Determine what to do when the user presses a mouse button. + + \param pos Mouse position + + \retval True, when pos is inside the circle of the knob. + \sa scrolledTo() +*/ +bool QwtKnob::isScrollPosition( const QPoint &pos ) const +{ + const QRect kr = knobRect(); + + const QRegion region( kr, QRegion::Ellipse ); + if ( region.contains( pos ) && ( pos != kr.center() ) ) + { + const double angle = QLineF( kr.center(), pos ).angle(); + const double valueAngle = qwtToDegrees( transform( value() ) ); + + d_data->mouseOffset = qwtNormalizeDegrees( angle - valueAngle ); + + return true; + } + + return false; +} + +/*! + \brief Determine the value for a new position of the mouse + + \param pos Mouse position + + \return Value for the mouse position + \sa isScrollPosition() +*/ +double QwtKnob::scrolledTo( const QPoint &pos ) const +{ + double angle = QLineF( rect().center(), pos ).angle(); + angle = qwtNormalizeDegrees( angle - d_data->mouseOffset ); + + if ( scaleMap().pDist() > 360.0 ) + { + angle = qwtToDegrees( angle ); + + const double v = transform( value() ); + + int numTurns = qFloor( ( v - scaleMap().p1() ) / 360.0 ); + + double valueAngle = qwtNormalizeDegrees( v ); + if ( qAbs( valueAngle - angle ) > 180.0 ) + { + numTurns += ( angle > valueAngle ) ? -1 : 1; + } + + angle += scaleMap().p1() + numTurns * 360.0; + + if ( !wrapping() ) + { + const double boundedAngle = + qBound( scaleMap().p1(), angle, scaleMap().p2() ); + + d_data->mouseOffset += ( boundedAngle - angle ); + angle = boundedAngle; + } + } + else + { + angle = qwtToScaleAngle( angle ); + + const double boundedAngle = + qBound( scaleMap().p1(), angle, scaleMap().p2() ); + + if ( !wrapping() ) + d_data->mouseOffset += ( boundedAngle - angle ); + + angle = boundedAngle; + } + + return invTransform( angle ); +} + +/*! + Handle QEvent::StyleChange and QEvent::FontChange; + \param event Change event +*/ +void QwtKnob::changeEvent( QEvent *event ) +{ + switch( event->type() ) + { + case QEvent::StyleChange: + case QEvent::FontChange: + { + updateGeometry(); + update(); + break; + } + default: + break; + } +} + +/*! + Repaint the knob + \param event Paint event +*/ +void QwtKnob::paintEvent( QPaintEvent *event ) +{ + const QRectF knobRect = this->knobRect(); + + QPainter painter( this ); + painter.setClipRegion( event->region() ); + + QStyleOption opt; + opt.init(this); + style()->drawPrimitive(QStyle::PE_Widget, &opt, &painter, this); + + painter.setRenderHint( QPainter::Antialiasing, true ); + + if ( !knobRect.contains( event->region().boundingRect() ) ) + { + scaleDraw()->setRadius( 0.5 * knobRect.width() + d_data->scaleDist ); + scaleDraw()->moveCenter( knobRect.center() ); + + scaleDraw()->draw( &painter, palette() ); + } + + drawKnob( &painter, knobRect ); + + drawMarker( &painter, knobRect, + qwtNormalizeDegrees( transform( value() ) ) ); + + painter.setRenderHint( QPainter::Antialiasing, false ); + + if ( hasFocus() ) + drawFocusIndicator( &painter ); +} + +/*! + \brief Draw the knob + + \param painter painter + \param knobRect Bounding rectangle of the knob (without scale) +*/ +void QwtKnob::drawKnob( QPainter *painter, const QRectF &knobRect ) const +{ + double dim = qMin( knobRect.width(), knobRect.height() ); + dim -= d_data->borderWidth * 0.5; + + QRectF aRect( 0, 0, dim, dim ); + aRect.moveCenter( knobRect.center() ); + + QPen pen( Qt::NoPen ); + if ( d_data->borderWidth > 0 ) + { + QColor c1 = palette().color( QPalette::Light ); + QColor c2 = palette().color( QPalette::Dark ); + + QLinearGradient gradient( aRect.topLeft(), aRect.bottomRight() ); + gradient.setColorAt( 0.0, c1 ); + gradient.setColorAt( 0.3, c1 ); + gradient.setColorAt( 0.7, c2 ); + gradient.setColorAt( 1.0, c2 ); + + pen = QPen( gradient, d_data->borderWidth ); + } + + QBrush brush; + switch( d_data->knobStyle ) + { + case QwtKnob::Raised: + { + double off = 0.3 * knobRect.width(); + QRadialGradient gradient( knobRect.center(), + knobRect.width(), knobRect.topLeft() + QPointF( off, off ) ); + + gradient.setColorAt( 0.0, palette().color( QPalette::Midlight ) ); + gradient.setColorAt( 1.0, palette().color( QPalette::Button ) ); + + brush = QBrush( gradient ); + + break; + } + case QwtKnob::Styled: + { + QRadialGradient gradient(knobRect.center().x() - knobRect.width() / 3, + knobRect.center().y() - knobRect.height() / 2, + knobRect.width() * 1.3, + knobRect.center().x(), + knobRect.center().y() - knobRect.height() / 2); + + const QColor c = palette().color( QPalette::Button ); + gradient.setColorAt(0, c.lighter(110)); + gradient.setColorAt(qreal(0.5), c); + gradient.setColorAt(qreal(0.501), c.darker(102)); + gradient.setColorAt(1, c.darker(115)); + + brush = QBrush( gradient ); + + break; + } + case QwtKnob::Sunken: + { + QLinearGradient gradient( + knobRect.topLeft(), knobRect.bottomRight() ); + gradient.setColorAt( 0.0, palette().color( QPalette::Mid ) ); + gradient.setColorAt( 0.5, palette().color( QPalette::Button ) ); + gradient.setColorAt( 1.0, palette().color( QPalette::Midlight ) ); + brush = QBrush( gradient ); + + break; + } + case QwtKnob::Flat: + default: + brush = palette().brush( QPalette::Button ); + } + + painter->setPen( pen ); + painter->setBrush( brush ); + painter->drawEllipse( aRect ); +} + + +/*! + \brief Draw the marker at the knob's front + + \param painter Painter + \param rect Bounding rectangle of the knob without scale + \param angle Angle of the marker in degrees + ( clockwise, 0 at the 12 o'clock position ) +*/ +void QwtKnob::drawMarker( QPainter *painter, + const QRectF &rect, double angle ) const +{ + if ( d_data->markerStyle == NoMarker || !isValid() ) + return; + + const double radians = qwtRadians( angle ); + const double sinA = -qFastSin( radians ); + const double cosA = qFastCos( radians ); + + const double xm = rect.center().x(); + const double ym = rect.center().y(); + const double margin = 4.0; + + double radius = 0.5 * ( rect.width() - d_data->borderWidth ) - margin; + if ( radius < 1.0 ) + radius = 1.0; + + int markerSize = d_data->markerSize; + if ( markerSize <= 0 ) + markerSize = qRound( 0.4 * radius ); + + switch ( d_data->markerStyle ) + { + case Notch: + case Nub: + { + const double dotWidth = + qMin( double( markerSize ), radius); + + const double dotCenterDist = radius - 0.5 * dotWidth; + if ( dotCenterDist > 0.0 ) + { + const QPointF center( xm - sinA * dotCenterDist, + ym - cosA * dotCenterDist ); + + QRectF ellipse( 0.0, 0.0, dotWidth, dotWidth ); + ellipse.moveCenter( center ); + + QColor c1 = palette().color( QPalette::Light ); + QColor c2 = palette().color( QPalette::Mid ); + + if ( d_data->markerStyle == Notch ) + qSwap( c1, c2 ); + + QLinearGradient gradient( + ellipse.topLeft(), ellipse.bottomRight() ); + gradient.setColorAt( 0.0, c1 ); + gradient.setColorAt( 1.0, c2 ); + + painter->setPen( Qt::NoPen ); + painter->setBrush( gradient ); + + painter->drawEllipse( ellipse ); + } + break; + } + case Dot: + { + const double dotWidth = + qMin( double( markerSize ), radius); + + const double dotCenterDist = radius - 0.5 * dotWidth; + if ( dotCenterDist > 0.0 ) + { + const QPointF center( xm - sinA * dotCenterDist, + ym - cosA * dotCenterDist ); + + QRectF ellipse( 0.0, 0.0, dotWidth, dotWidth ); + ellipse.moveCenter( center ); + + painter->setPen( Qt::NoPen ); + painter->setBrush( palette().color( QPalette::ButtonText ) ); + painter->drawEllipse( ellipse ); + } + + break; + } + case Tick: + { + const double rb = qMax( radius - markerSize, 1.0 ); + const double re = radius; + + const QLineF line( xm - sinA * rb, ym - cosA * rb, + xm - sinA * re, ym - cosA * re ); + + QPen pen( palette().color( QPalette::ButtonText ), 0 ); + pen.setCapStyle( Qt::FlatCap ); + painter->setPen( pen ); + painter->drawLine ( line ); + + break; + } + case Triangle: + { + const double rb = qMax( radius - markerSize, 1.0 ); + const double re = radius; + + painter->translate( rect.center() ); + painter->rotate( angle - 90.0 ); + + QPolygonF polygon; + polygon += QPointF( re, 0.0 ); + polygon += QPointF( rb, 0.5 * ( re - rb ) ); + polygon += QPointF( rb, -0.5 * ( re - rb ) ); + + painter->setPen( Qt::NoPen ); + painter->setBrush( palette().color( QPalette::ButtonText ) ); + painter->drawPolygon( polygon ); + + painter->resetTransform(); + + break; + } + default: + break; + } +} + +/*! + Draw the focus indicator + \param painter Painter +*/ +void QwtKnob::drawFocusIndicator( QPainter *painter ) const +{ + const QRect cr = contentsRect(); + + int w = d_data->knobWidth; + if ( w <= 0 ) + { + w = qMin( cr.width(), cr.height() ); + } + else + { + const int extent = qCeil( scaleDraw()->extent( font() ) ); + w += 2 * ( extent + d_data->scaleDist ); + } + + QRect focusRect( 0, 0, w, w ); + focusRect.moveCenter( cr.center() ); + + QwtPainter::drawFocusRect( painter, this, focusRect ); +} + +/*! + \brief Set the alignment of the knob + + Similar to a QLabel::alignment() the flags decide how + to align the knob inside of contentsRect(). + + The default setting is Qt::AlignCenter + + \param alignment Or'd alignment flags + + \sa alignment(), setKnobWidth(), knobRect() + */ +void QwtKnob::setAlignment( Qt::Alignment alignment ) +{ + if ( d_data->alignment != alignment ) + { + d_data->alignment = alignment; + update(); + } +} + +/*! + \return Alignment of the knob inside of contentsRect() + \sa setAlignment(), knobWidth(), knobRect() + */ +Qt::Alignment QwtKnob::alignment() const +{ + return d_data->alignment; +} + +/*! + \brief Change the knob's width. + + Setting a fixed value for the diameter of the knob + is helpful for aligning several knobs in a row. + + \param width New width + + \sa knobWidth(), setAlignment() + \note Modifies the sizePolicy() +*/ +void QwtKnob::setKnobWidth( int width ) +{ + width = qMax( width, 0 ); + + if ( width != d_data->knobWidth ) + { + QSizePolicy::Policy policy; + if ( width > 0 ) + policy = QSizePolicy::Minimum; + else + policy = QSizePolicy::MinimumExpanding; + + setSizePolicy( policy, policy ); + + d_data->knobWidth = width; + + updateGeometry(); + update(); + } +} + +//! Return the width of the knob +int QwtKnob::knobWidth() const +{ + return d_data->knobWidth; +} + +/*! + \brief Set the knob's border width + \param borderWidth new border width +*/ +void QwtKnob::setBorderWidth( int borderWidth ) +{ + d_data->borderWidth = qMax( borderWidth, 0 ); + + updateGeometry(); + update(); + +} + +//! Return the border width +int QwtKnob::borderWidth() const +{ + return d_data->borderWidth; +} + +/*! + \brief Set the size of the marker + + When setting a size <= 0 the marker will + automatically scaled to 40% of the radius of the knob. + + \sa markerSize(), markerStyle() +*/ +void QwtKnob::setMarkerSize( int size ) +{ + if ( d_data->markerSize != size ) + { + d_data->markerSize = size; + update(); + } +} + +/*! + \return Marker size + \sa setMarkerSize() + */ +int QwtKnob::markerSize() const +{ + return d_data->markerSize; +} + +/*! + \return sizeHint() +*/ +QSize QwtKnob::sizeHint() const +{ + const QSize hint = qwtKnobSizeHint( this, 50 ); + return hint.expandedTo( QApplication::globalStrut() ); +} + +/*! + \return Minimum size hint + \sa sizeHint() +*/ +QSize QwtKnob::minimumSizeHint() const +{ + return qwtKnobSizeHint( this, 20 ); +} diff --git a/qwt/src/qwt_knob.h b/qwt/src/qwt_knob.h new file mode 100644 index 000000000..852374c02 --- /dev/null +++ b/qwt/src/qwt_knob.h @@ -0,0 +1,178 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_KNOB_H +#define QWT_KNOB_H + +#include "qwt_global.h" +#include "qwt_abstract_slider.h" + +class QwtRoundScaleDraw; + +/*! + \brief The Knob Widget + + The QwtKnob widget imitates look and behavior of a volume knob on a radio. + It looks similar to QDial - not to QwtDial. + + The value range of a knob might be divided into several turns. + + The layout of the knob depends on the knobWidth(). + + - width > 0 + The diameter of the knob is fixed and the knob is aligned + according to the alignment() flags inside of the contentsRect(). + + - width <= 0 + The knob is extended to the minimum of width/height of the contentsRect() + and aligned in the other direction according to alignment(). + + Setting a fixed knobWidth() is helpful to align several knobs with different + scale labels. + + \image html knob.png +*/ + +class QWT_EXPORT QwtKnob: public QwtAbstractSlider +{ + Q_OBJECT + + Q_ENUMS ( KnobStyle MarkerStyle ) + + Q_PROPERTY( KnobStyle knobStyle READ knobStyle WRITE setKnobStyle ) + Q_PROPERTY( int knobWidth READ knobWidth WRITE setKnobWidth ) + Q_PROPERTY( Qt::Alignment alignment READ alignment WRITE setAlignment ) + Q_PROPERTY( double totalAngle READ totalAngle WRITE setTotalAngle ) + Q_PROPERTY( int numTurns READ numTurns WRITE setNumTurns ) + Q_PROPERTY( MarkerStyle markerStyle READ markerStyle WRITE setMarkerStyle ) + Q_PROPERTY( int markerSize READ markerSize WRITE setMarkerSize ) + Q_PROPERTY( int borderWidth READ borderWidth WRITE setBorderWidth ) + +public: + /*! + \brief Style of the knob surface + + Depending on the KnobStyle the surface of the knob is + filled from the brushes of the widget palette(). + + \sa setKnobStyle(), knobStyle() + */ + enum KnobStyle + { + //! Fill the knob with a brush from QPalette::Button. + Flat, + + //! Build a gradient from QPalette::Midlight and QPalette::Button + Raised, + + /*! + Build a gradient from QPalette::Midlight, QPalette::Button + and QPalette::Midlight + */ + Sunken, + + /*! + Build a radial gradient from QPalette::Button + like it is used for QDial in various Qt styles. + */ + Styled + }; + + /*! + \brief Marker type + + The marker indicates the current value on the knob + The default setting is a Notch marker. + + \sa setMarkerStyle(), setMarkerSize() + */ + enum MarkerStyle + { + //! Don't paint any marker + NoMarker = -1, + + //! Paint a single tick in QPalette::ButtonText color + Tick, + + //! Paint a triangle in QPalette::ButtonText color + Triangle, + + //! Paint a circle in QPalette::ButtonText color + Dot, + + /*! + Draw a raised ellipse with a gradient build from + QPalette::Light and QPalette::Mid + */ + Nub, + + /*! + Draw a sunken ellipse with a gradient build from + QPalette::Light and QPalette::Mid + */ + Notch + }; + + explicit QwtKnob( QWidget* parent = NULL ); + virtual ~QwtKnob(); + + void setAlignment( Qt::Alignment ); + Qt::Alignment alignment() const; + + void setKnobWidth( int ); + int knobWidth() const; + + void setNumTurns( int ); + int numTurns() const; + + void setTotalAngle ( double angle ); + double totalAngle() const; + + void setKnobStyle( KnobStyle ); + KnobStyle knobStyle() const; + + void setBorderWidth( int bw ); + int borderWidth() const; + + void setMarkerStyle( MarkerStyle ); + MarkerStyle markerStyle() const; + + void setMarkerSize( int ); + int markerSize() const; + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + void setScaleDraw( QwtRoundScaleDraw * ); + + const QwtRoundScaleDraw *scaleDraw() const; + QwtRoundScaleDraw *scaleDraw(); + + QRect knobRect() const; + +protected: + virtual void paintEvent( QPaintEvent * ); + virtual void changeEvent( QEvent * ); + + virtual void drawKnob( QPainter *, const QRectF & ) const; + + virtual void drawFocusIndicator( QPainter * ) const; + + virtual void drawMarker( QPainter *, + const QRectF &, double arc ) const; + + virtual double scrolledTo( const QPoint & ) const; + virtual bool isScrollPosition( const QPoint & ) const; + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_legend.cpp b/qwt/src/qwt_legend.cpp new file mode 100644 index 000000000..01cef89f1 --- /dev/null +++ b/qwt/src/qwt_legend.cpp @@ -0,0 +1,801 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_legend.h" +#include "qwt_legend_label.h" +#include "qwt_dyngrid_layout.h" +#include "qwt_math.h" +#include "qwt_plot_item.h" +#include "qwt_painter.h" +#include +#include +#include +#include +#include +#include + +class QwtLegendMap +{ +public: + inline bool isEmpty() const { return d_entries.isEmpty(); } + + void insert( const QVariant &, const QList & ); + void remove( const QVariant & ); + + void removeWidget( const QWidget * ); + + QList legendWidgets( const QVariant & ) const; + QVariant itemInfo( const QWidget * ) const; + +private: + // we don't know anything about itemInfo and therefore don't have + // any key that can be used for a map or hashtab. + // But a simple linear list is o.k. here, as we will never have + // more than a few entries. + + class Entry + { + public: + QVariant itemInfo; + QList widgets; + }; + + QList< Entry > d_entries; +}; + +void QwtLegendMap::insert( const QVariant &itemInfo, + const QList &widgets ) +{ + for ( int i = 0; i < d_entries.size(); i++ ) + { + Entry &entry = d_entries[i]; + if ( entry.itemInfo == itemInfo ) + { + entry.widgets = widgets; + return; + } + } + + Entry newEntry; + newEntry.itemInfo = itemInfo; + newEntry.widgets = widgets; + + d_entries += newEntry; +} + +void QwtLegendMap::remove( const QVariant &itemInfo ) +{ + for ( int i = 0; i < d_entries.size(); i++ ) + { + Entry &entry = d_entries[i]; + if ( entry.itemInfo == itemInfo ) + { + d_entries.removeAt( i ); + return; + } + } +} + +void QwtLegendMap::removeWidget( const QWidget *widget ) +{ + QWidget *w = const_cast( widget ); + + for ( int i = 0; i < d_entries.size(); i++ ) + d_entries[ i ].widgets.removeAll( w ); +} + +QVariant QwtLegendMap::itemInfo( const QWidget *widget ) const +{ + if ( widget != NULL ) + { + QWidget *w = const_cast( widget ); + + for ( int i = 0; i < d_entries.size(); i++ ) + { + const Entry &entry = d_entries[i]; + if ( entry.widgets.indexOf( w ) >= 0 ) + return entry.itemInfo; + } + } + + return QVariant(); +} + +QList QwtLegendMap::legendWidgets( const QVariant &itemInfo ) const +{ + if ( itemInfo.isValid() ) + { + for ( int i = 0; i < d_entries.size(); i++ ) + { + const Entry &entry = d_entries[i]; + if ( entry.itemInfo == itemInfo ) + return entry.widgets; + } + } + + return QList(); +} + +class QwtLegend::PrivateData +{ +public: + PrivateData(): + itemMode( QwtLegendData::ReadOnly ), + view( NULL ) + { + } + + QwtLegendData::Mode itemMode; + QwtLegendMap itemMap; + + class LegendView; + LegendView *view; +}; + +class QwtLegend::PrivateData::LegendView: public QScrollArea +{ +public: + LegendView( QWidget *parent ): + QScrollArea( parent ) + { + contentsWidget = new QWidget( this ); + contentsWidget->setObjectName( "QwtLegendViewContents" ); + + setWidget( contentsWidget ); + setWidgetResizable( false ); + + viewport()->setObjectName( "QwtLegendViewport" ); + + // QScrollArea::setWidget internally sets autoFillBackground to true + // But we don't want a background. + contentsWidget->setAutoFillBackground( false ); + viewport()->setAutoFillBackground( false ); + } + + virtual bool event( QEvent *event ) + { + if ( event->type() == QEvent::PolishRequest ) + { + setFocusPolicy( Qt::NoFocus ); + } + + if ( event->type() == QEvent::Resize ) + { + // adjust the size to en/disable the scrollbars + // before QScrollArea adjusts the viewport size + + const QRect cr = contentsRect(); + + int w = cr.width(); + int h = contentsWidget->heightForWidth( cr.width() ); + if ( h > w ) + { + w -= verticalScrollBar()->sizeHint().width(); + h = contentsWidget->heightForWidth( w ); + } + + contentsWidget->resize( w, h ); + } + + return QScrollArea::event( event ); + } + + virtual bool viewportEvent( QEvent *event ) + { + bool ok = QScrollArea::viewportEvent( event ); + + if ( event->type() == QEvent::Resize ) + { + layoutContents(); + } + return ok; + } + + QSize viewportSize( int w, int h ) const + { + const int sbHeight = horizontalScrollBar()->sizeHint().height(); + const int sbWidth = verticalScrollBar()->sizeHint().width(); + + const int cw = contentsRect().width(); + const int ch = contentsRect().height(); + + int vw = cw; + int vh = ch; + + if ( w > vw ) + vh -= sbHeight; + + if ( h > vh ) + { + vw -= sbWidth; + if ( w > vw && vh == ch ) + vh -= sbHeight; + } + return QSize( vw, vh ); + } + + void layoutContents() + { + const QwtDynGridLayout *tl = qobject_cast( + contentsWidget->layout() ); + if ( tl == NULL ) + return; + + const QSize visibleSize = viewport()->contentsRect().size(); + + const int minW = int( tl->maxItemWidth() ) + 2 * tl->margin(); + + int w = qMax( visibleSize.width(), minW ); + int h = qMax( tl->heightForWidth( w ), visibleSize.height() ); + + const int vpWidth = viewportSize( w, h ).width(); + if ( w > vpWidth ) + { + w = qMax( vpWidth, minW ); + h = qMax( tl->heightForWidth( w ), visibleSize.height() ); + } + + contentsWidget->resize( w, h ); + } + + QWidget *contentsWidget; +}; + +/*! + Constructor + \param parent Parent widget +*/ +QwtLegend::QwtLegend( QWidget *parent ): + QwtAbstractLegend( parent ) +{ + setFrameStyle( NoFrame ); + + d_data = new QwtLegend::PrivateData; + + d_data->view = new QwtLegend::PrivateData::LegendView( this ); + d_data->view->setObjectName( "QwtLegendView" ); + d_data->view->setFrameStyle( NoFrame ); + + QwtDynGridLayout *gridLayout = new QwtDynGridLayout( + d_data->view->contentsWidget ); + gridLayout->setAlignment( Qt::AlignHCenter | Qt::AlignTop ); + + d_data->view->contentsWidget->installEventFilter( this ); + + QVBoxLayout *layout = new QVBoxLayout( this ); + layout->setContentsMargins( 0, 0, 0, 0 ); + layout->addWidget( d_data->view ); +} + +//! Destructor +QwtLegend::~QwtLegend() +{ + delete d_data; +} + +/*! + \brief Set the maximum number of entries in a row + + F.e when the maximum is set to 1 all items are aligned + vertically. 0 means unlimited + + \param numColums Maximum number of entries in a row + + \sa maxColumns(), QwtDynGridLayout::setMaxColumns() + */ +void QwtLegend::setMaxColumns( uint numColums ) +{ + QwtDynGridLayout *tl = qobject_cast( + d_data->view->contentsWidget->layout() ); + if ( tl ) + tl->setMaxColumns( numColums ); +} + +/*! + \return Maximum number of entries in a row + \sa setMaxColumns(), QwtDynGridLayout::maxColumns() + */ +uint QwtLegend::maxColumns() const +{ + uint maxCols = 0; + + const QwtDynGridLayout *tl = qobject_cast( + d_data->view->contentsWidget->layout() ); + if ( tl ) + maxCols = tl->maxColumns(); + + return maxCols; +} + +/*! + \brief Set the default mode for legend labels + + Legend labels will be constructed according to the + attributes in a QwtLegendData object. When it doesn't + contain a value for the QwtLegendData::ModeRole the + label will be initialized with the default mode of the legend. + + \param mode Default item mode + + \sa itemMode(), QwtLegendData::value(), QwtPlotItem::legendData() + \note Changing the mode doesn't have any effect on existing labels. + */ +void QwtLegend::setDefaultItemMode( QwtLegendData::Mode mode ) +{ + d_data->itemMode = mode; +} + +/*! + \return Default item mode + \sa setDefaultItemMode() +*/ +QwtLegendData::Mode QwtLegend::defaultItemMode() const +{ + return d_data->itemMode; +} + +/*! + The contents widget is the only child of the viewport of + the internal QScrollArea and the parent widget of all legend items. + + \return Container widget of the legend items +*/ +QWidget *QwtLegend::contentsWidget() +{ + return d_data->view->contentsWidget; +} + +/*! + \return Horizontal scrollbar + \sa verticalScrollBar() +*/ +QScrollBar *QwtLegend::horizontalScrollBar() const +{ + return d_data->view->horizontalScrollBar(); +} + +/*! + \return Vertical scrollbar + \sa horizontalScrollBar() +*/ +QScrollBar *QwtLegend::verticalScrollBar() const +{ + return d_data->view->verticalScrollBar(); +} + +/*! + The contents widget is the only child of the viewport of + the internal QScrollArea and the parent widget of all legend items. + + \return Container widget of the legend items + +*/ +const QWidget *QwtLegend::contentsWidget() const +{ + return d_data->view->contentsWidget; +} + +/*! + \brief Update the entries for an item + + \param itemInfo Info for an item + \param data List of legend entry attributes for the item + */ +void QwtLegend::updateLegend( const QVariant &itemInfo, + const QList &data ) +{ + QList widgetList = legendWidgets( itemInfo ); + + if ( widgetList.size() != data.size() ) + { + QLayout *contentsLayout = d_data->view->contentsWidget->layout(); + + while ( widgetList.size() > data.size() ) + { + QWidget *w = widgetList.takeLast(); + + contentsLayout->removeWidget( w ); + + // updates might be triggered by signals from the legend widget + // itself. So we better don't delete it here. + + w->hide(); + w->deleteLater(); + } + + for ( int i = widgetList.size(); i < data.size(); i++ ) + { + QWidget *widget = createWidget( data[i] ); + + if ( contentsLayout ) + contentsLayout->addWidget( widget ); + + widgetList += widget; + } + + if ( widgetList.isEmpty() ) + { + d_data->itemMap.remove( itemInfo ); + } + else + { + d_data->itemMap.insert( itemInfo, widgetList ); + } + + updateTabOrder(); + } + + for ( int i = 0; i < data.size(); i++ ) + updateWidget( widgetList[i], data[i] ); +} + +/*! + \brief Create a widget to be inserted into the legend + + The default implementation returns a QwtLegendLabel. + + \param data Attributes of the legend entry + \return Widget representing data on the legend + + \note updateWidget() will called soon after createWidget() + with the same attributes. + */ +QWidget *QwtLegend::createWidget( const QwtLegendData &data ) const +{ + Q_UNUSED( data ); + + QwtLegendLabel *label = new QwtLegendLabel(); + label->setItemMode( defaultItemMode() ); + + connect( label, SIGNAL( clicked() ), SLOT( itemClicked() ) ); + connect( label, SIGNAL( checked( bool ) ), SLOT( itemChecked( bool ) ) ); + + return label; +} + +/*! + \brief Update the widget + + \param widget Usually a QwtLegendLabel + \param data Attributes to be displayed + + \sa createWidget() + \note When widget is no QwtLegendLabel updateWidget() does nothing. + */ +void QwtLegend::updateWidget( QWidget *widget, const QwtLegendData &data ) +{ + QwtLegendLabel *label = qobject_cast( widget ); + if ( label ) + { + label->setData( data ); + if ( !data.value( QwtLegendData::ModeRole ).isValid() ) + { + // use the default mode, when there is no specific + // hint from the legend data + + label->setItemMode( defaultItemMode() ); + } + } +} + +void QwtLegend::updateTabOrder() +{ + QLayout *contentsLayout = d_data->view->contentsWidget->layout(); + if ( contentsLayout ) + { + // set tab focus chain + + QWidget *w = NULL; + + for ( int i = 0; i < contentsLayout->count(); i++ ) + { + QLayoutItem *item = contentsLayout->itemAt( i ); + if ( w && item->widget() ) + QWidget::setTabOrder( w, item->widget() ); + + w = item->widget(); + } + } +} + +//! Return a size hint. +QSize QwtLegend::sizeHint() const +{ + QSize hint = d_data->view->contentsWidget->sizeHint(); + hint += QSize( 2 * frameWidth(), 2 * frameWidth() ); + + return hint; +} + +/*! + \return The preferred height, for a width. + \param width Width +*/ +int QwtLegend::heightForWidth( int width ) const +{ + width -= 2 * frameWidth(); + + int h = d_data->view->contentsWidget->heightForWidth( width ); + if ( h >= 0 ) + h += 2 * frameWidth(); + + return h; +} + + +/*! + Handle QEvent::ChildRemoved andQEvent::LayoutRequest events + for the contentsWidget(). + + \param object Object to be filtered + \param event Event + + \return Forwarded to QwtAbstractLegend::eventFilter() +*/ +bool QwtLegend::eventFilter( QObject *object, QEvent *event ) +{ + if ( object == d_data->view->contentsWidget ) + { + switch ( event->type() ) + { + case QEvent::ChildRemoved: + { + const QChildEvent *ce = + static_cast(event); + if ( ce->child()->isWidgetType() ) + { + QWidget *w = static_cast< QWidget * >( ce->child() ); + d_data->itemMap.removeWidget( w ); + } + break; + } + case QEvent::LayoutRequest: + { + d_data->view->layoutContents(); + + if ( parentWidget() && parentWidget()->layout() == NULL ) + { + /* + We want the parent widget ( usually QwtPlot ) to recalculate + its layout, when the contentsWidget has changed. But + because of the scroll view we have to forward the LayoutRequest + event manually. + + We don't use updateGeometry() because it doesn't post LayoutRequest + events when the legend is hidden. But we want the + parent widget notified, so it can show/hide the legend + depending on its items. + */ + QApplication::postEvent( parentWidget(), + new QEvent( QEvent::LayoutRequest ) ); + } + break; + } + default: + break; + } + } + + return QwtAbstractLegend::eventFilter( object, event ); +} + +/*! + Called internally when the legend has been clicked on. + Emits a clicked() signal. +*/ +void QwtLegend::itemClicked() +{ + QWidget *w = qobject_cast( sender() ); + if ( w ) + { + const QVariant itemInfo = d_data->itemMap.itemInfo( w ); + if ( itemInfo.isValid() ) + { + const QList widgetList = + d_data->itemMap.legendWidgets( itemInfo ); + + const int index = widgetList.indexOf( w ); + if ( index >= 0 ) + Q_EMIT clicked( itemInfo, index ); + } + } +} + +/*! + Called internally when the legend has been checked + Emits a checked() signal. +*/ +void QwtLegend::itemChecked( bool on ) +{ + QWidget *w = qobject_cast( sender() ); + if ( w ) + { + const QVariant itemInfo = d_data->itemMap.itemInfo( w ); + if ( itemInfo.isValid() ) + { + const QList widgetList = + d_data->itemMap.legendWidgets( itemInfo ); + + const int index = widgetList.indexOf( w ); + if ( index >= 0 ) + Q_EMIT checked( itemInfo, on, index ); + } + } +} + +/*! + Render the legend into a given rectangle. + + \param painter Painter + \param rect Bounding rectangle + \param fillBackground When true, fill rect with the widget background + + \sa renderLegend() is used by QwtPlotRenderer - not by QwtLegend itself +*/ +void QwtLegend::renderLegend( QPainter *painter, + const QRectF &rect, bool fillBackground ) const +{ + if ( d_data->itemMap.isEmpty() ) + return; + + if ( fillBackground ) + { + if ( autoFillBackground() || + testAttribute( Qt::WA_StyledBackground ) ) + { + QwtPainter::drawBackgound( painter, rect, this ); + } + } + + const QwtDynGridLayout *legendLayout = + qobject_cast( contentsWidget()->layout() ); + if ( legendLayout == NULL ) + return; + + int left, right, top, bottom; + getContentsMargins( &left, &top, &right, &bottom ); + + QRect layoutRect; + layoutRect.setLeft( qCeil( rect.left() ) + left ); + layoutRect.setTop( qCeil( rect.top() ) + top ); + layoutRect.setRight( qFloor( rect.right() ) - right ); + layoutRect.setBottom( qFloor( rect.bottom() ) - bottom ); + + uint numCols = legendLayout->columnsForWidth( layoutRect.width() ); + QList itemRects = + legendLayout->layoutItems( layoutRect, numCols ); + + int index = 0; + + for ( int i = 0; i < legendLayout->count(); i++ ) + { + QLayoutItem *item = legendLayout->itemAt( i ); + QWidget *w = item->widget(); + if ( w ) + { + painter->save(); + + painter->setClipRect( itemRects[index] ); + renderItem( painter, w, itemRects[index], fillBackground ); + + index++; + painter->restore(); + } + } +} + +/*! + Render a legend entry into a given rectangle. + + \param painter Painter + \param widget Widget representing a legend entry + \param rect Bounding rectangle + \param fillBackground When true, fill rect with the widget background + + \note When widget is not derived from QwtLegendLabel renderItem + does nothing beside the background +*/ +void QwtLegend::renderItem( QPainter *painter, + const QWidget *widget, const QRectF &rect, bool fillBackground ) const +{ + if ( fillBackground ) + { + if ( widget->autoFillBackground() || + widget->testAttribute( Qt::WA_StyledBackground ) ) + { + QwtPainter::drawBackgound( painter, rect, widget ); + } + } + + const QwtLegendLabel *label = qobject_cast( widget ); + if ( label ) + { + // icon + + const QwtGraphic &icon = label->data().icon(); + const QSizeF sz = icon.defaultSize(); + + const QRectF iconRect( rect.x() + label->margin(), + rect.center().y() - 0.5 * sz.height(), + sz.width(), sz.height() ); + + icon.render( painter, iconRect, Qt::KeepAspectRatio ); + + // title + + QRectF titleRect = rect; + titleRect.setX( iconRect.right() + 2 * label->spacing() ); + + painter->setFont( label->font() ); + painter->setPen( label->palette().color( QPalette::Text ) ); + const_cast< QwtLegendLabel *>( label )->drawText( painter, titleRect ); + } +} + +/*! + \return List of widgets associated to a item + \param itemInfo Info about an item + \sa legendWidget(), itemInfo(), QwtPlot::itemToInfo() + */ +QList QwtLegend::legendWidgets( const QVariant &itemInfo ) const +{ + return d_data->itemMap.legendWidgets( itemInfo ); +} + +/*! + \return First widget in the list of widgets associated to an item + \param itemInfo Info about an item + \sa itemInfo(), QwtPlot::itemToInfo() + \note Almost all types of items have only one widget +*/ +QWidget *QwtLegend::legendWidget( const QVariant &itemInfo ) const +{ + const QList list = d_data->itemMap.legendWidgets( itemInfo ); + if ( list.isEmpty() ) + return NULL; + + return list[0]; +} + +/*! + Find the item that is associated to a widget + + \param widget Widget on the legend + \return Associated item info + \sa legendWidget() + */ +QVariant QwtLegend::itemInfo( const QWidget *widget ) const +{ + return d_data->itemMap.itemInfo( widget ); +} + +//! \return True, when no item is inserted +bool QwtLegend::isEmpty() const +{ + return d_data->itemMap.isEmpty(); +} + +/*! + Return the extent, that is needed for the scrollbars + + \param orientation Orientation ( + \return The width of the vertical scrollbar for Qt::Horizontal and v.v. + */ +int QwtLegend::scrollExtent( Qt::Orientation orientation ) const +{ + int extent = 0; + + if ( orientation == Qt::Horizontal ) + extent = verticalScrollBar()->sizeHint().width(); + else + extent = horizontalScrollBar()->sizeHint().height(); + + return extent; +} + diff --git a/qwt/src/qwt_legend.h b/qwt/src/qwt_legend.h new file mode 100644 index 000000000..3d8fca67a --- /dev/null +++ b/qwt/src/qwt_legend.h @@ -0,0 +1,117 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_LEGEND_H +#define QWT_LEGEND_H + +#include "qwt_global.h" +#include "qwt_abstract_legend.h" +#include + +class QScrollBar; + +/*! + \brief The legend widget + + The QwtLegend widget is a tabular arrangement of legend items. Legend + items might be any type of widget, but in general they will be + a QwtLegendLabel. + + \sa QwtLegendLabel, QwtPlotItem, QwtPlot +*/ + +class QWT_EXPORT QwtLegend : public QwtAbstractLegend +{ + Q_OBJECT + +public: + explicit QwtLegend( QWidget *parent = NULL ); + virtual ~QwtLegend(); + + void setMaxColumns( uint numColums ); + uint maxColumns() const; + + void setDefaultItemMode( QwtLegendData::Mode ); + QwtLegendData::Mode defaultItemMode() const; + + QWidget *contentsWidget(); + const QWidget *contentsWidget() const; + + QWidget *legendWidget( const QVariant & ) const; + QList legendWidgets( const QVariant & ) const; + + QVariant itemInfo( const QWidget * ) const; + + virtual bool eventFilter( QObject *, QEvent * ); + + virtual QSize sizeHint() const; + virtual int heightForWidth( int w ) const; + + QScrollBar *horizontalScrollBar() const; + QScrollBar *verticalScrollBar() const; + + virtual void renderLegend( QPainter *, + const QRectF &, bool fillBackground ) const; + + virtual void renderItem( QPainter *, + const QWidget *, const QRectF &, bool fillBackground ) const; + + virtual bool isEmpty() const; + virtual int scrollExtent( Qt::Orientation ) const; + +Q_SIGNALS: + /*! + A signal which is emitted when the user has clicked on + a legend label, which is in QwtLegendData::Clickable mode. + + \param itemInfo Info for the item item of the + selected legend item + \param index Index of the legend label in the list of widgets + that are associated with the plot item + + \note clicks are disabled as default + \sa setDefaultItemMode(), defaultItemMode(), QwtPlot::itemToInfo() + */ + void clicked( const QVariant &itemInfo, int index ); + + /*! + A signal which is emitted when the user has clicked on + a legend label, which is in QwtLegendData::Checkable mode + + \param itemInfo Info for the item of the + selected legend label + \param index Index of the legend label in the list of widgets + that are associated with the plot item + \param on True when the legend label is checked + + \note clicks are disabled as default + \sa setDefaultItemMode(), defaultItemMode(), QwtPlot::itemToInfo() + */ + void checked( const QVariant &itemInfo, bool on, int index ); + +public Q_SLOTS: + virtual void updateLegend( const QVariant &, + const QList & ); + +protected Q_SLOTS: + void itemClicked(); + void itemChecked( bool ); + +protected: + virtual QWidget *createWidget( const QwtLegendData & ) const; + virtual void updateWidget( QWidget *widget, const QwtLegendData &data ); + +private: + void updateTabOrder(); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_legend_data.cpp b/qwt/src/qwt_legend_data.cpp new file mode 100644 index 000000000..cf0cb2ce3 --- /dev/null +++ b/qwt/src/qwt_legend_data.cpp @@ -0,0 +1,129 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_legend_data.h" + +//! Constructor +QwtLegendData::QwtLegendData() +{ +} + +//! Destructor +QwtLegendData::~QwtLegendData() +{ +} + +/*! + Set the legend attributes + + QwtLegendData actually is a QMap with some + convenience interfaces + + \param map Values + \sa values() + */ +void QwtLegendData::setValues( const QMap &map ) +{ + d_map = map; +} + +/*! + \return Legend attributes + \sa setValues() + */ +const QMap &QwtLegendData::values() const +{ + return d_map; +} + +/*! + \param role Attribute role + \return True, when the internal map has an entry for role + */ +bool QwtLegendData::hasRole( int role ) const +{ + return d_map.contains( role ); +} + +/*! + Set an attribute value + + \param role Attribute role + \param data Attribute value + + \sa value() + */ +void QwtLegendData::setValue( int role, const QVariant &data ) +{ + d_map[role] = data; +} + +/*! + \param role Attribute role + \return Attribute value for a specific role + */ +QVariant QwtLegendData::value( int role ) const +{ + if ( !d_map.contains( role ) ) + return QVariant(); + + return d_map[role]; +} + +//! \return True, when the internal map is empty +bool QwtLegendData::isValid() const +{ + return !d_map.isEmpty(); +} + +//! \return Value of the TitleRole attribute +QwtText QwtLegendData::title() const +{ + QwtText text; + + const QVariant titleValue = value( QwtLegendData::TitleRole ); + if ( titleValue.canConvert() ) + { + text = qvariant_cast( titleValue ); + } + else if ( titleValue.canConvert() ) + { + text.setText( qvariant_cast( titleValue ) ); + } + + return text; +} + +//! \return Value of the IconRole attribute +QwtGraphic QwtLegendData::icon() const +{ + const QVariant iconValue = value( QwtLegendData::IconRole ); + + QwtGraphic graphic; + if ( iconValue.canConvert() ) + { + graphic = qvariant_cast( iconValue ); + } + + return graphic; +} + +//! \return Value of the ModeRole attribute +QwtLegendData::Mode QwtLegendData::mode() const +{ + const QVariant modeValue = value( QwtLegendData::ModeRole ); + if ( modeValue.canConvert() ) + { + const int mode = qvariant_cast( modeValue ); + return static_cast( mode ); + } + + return QwtLegendData::ReadOnly; +} + diff --git a/qwt/src/qwt_legend_data.h b/qwt/src/qwt_legend_data.h new file mode 100644 index 000000000..d83e13264 --- /dev/null +++ b/qwt/src/qwt_legend_data.h @@ -0,0 +1,87 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_LEGEND_DATA_H +#define QWT_LEGEND_DATA_H + +#include "qwt_global.h" +#include "qwt_text.h" +#include "qwt_graphic.h" +#include +#include +#include + +/*! + \brief Attributes of an entry on a legend + + QwtLegendData is an abstract container ( like QAbstractModel ) + to exchange attributes, that are only known between to + the plot item and the legend. + + By overloading QwtPlotItem::legendData() any other set of attributes + could be used, that can be handled by a modified ( or completely + different ) implementation of a legend. + + \sa QwtLegend, QwtPlotLegendItem + \note The stockchart example implements a legend as a tree + with checkable items + */ +class QWT_EXPORT QwtLegendData +{ +public: + //! Mode defining how a legend entry interacts + enum Mode + { + //! The legend item is not interactive, like a label + ReadOnly, + + //! The legend item is clickable, like a push button + Clickable, + + //! The legend item is checkable, like a checkable button + Checkable + }; + + //! Identifier how to interprete a QVariant + enum Role + { + // The value is a Mode + ModeRole, + + // The value is a title + TitleRole, + + // The value is an icon + IconRole, + + // Values < UserRole are reserved for internal use + UserRole = 32 + }; + + QwtLegendData(); + ~QwtLegendData(); + + void setValues( const QMap & ); + const QMap &values() const; + + void setValue( int role, const QVariant & ); + QVariant value( int role ) const; + + bool hasRole( int role ) const; + bool isValid() const; + + QwtGraphic icon() const; + QwtText title() const; + Mode mode() const; + +private: + QMap d_map; +}; + +#endif diff --git a/qwt/src/qwt_legend_label.cpp b/qwt/src/qwt_legend_label.cpp new file mode 100644 index 000000000..19a7eb957 --- /dev/null +++ b/qwt/src/qwt_legend_label.cpp @@ -0,0 +1,421 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_legend_label.h" +#include "qwt_legend_data.h" +#include "qwt_math.h" +#include "qwt_painter.h" +#include "qwt_symbol.h" +#include "qwt_graphic.h" +#include +#include +#include +#include +#include +#include +#include + +static const int ButtonFrame = 2; +static const int Margin = 2; + +static QSize buttonShift( const QwtLegendLabel *w ) +{ + QStyleOption option; + option.init( w ); + + const int ph = w->style()->pixelMetric( + QStyle::PM_ButtonShiftHorizontal, &option, w ); + const int pv = w->style()->pixelMetric( + QStyle::PM_ButtonShiftVertical, &option, w ); + return QSize( ph, pv ); +} + +class QwtLegendLabel::PrivateData +{ +public: + PrivateData(): + itemMode( QwtLegendData::ReadOnly ), + isDown( false ), + spacing( Margin ) + { + } + + QwtLegendData::Mode itemMode; + QwtLegendData legendData; + bool isDown; + + QPixmap icon; + + int spacing; +}; + +/*! + Set the attributes of the legend label + + \param legendData Attributes of the label + \sa data() + */ +void QwtLegendLabel::setData( const QwtLegendData &legendData ) +{ + d_data->legendData = legendData; + + const bool doUpdate = updatesEnabled(); + setUpdatesEnabled( false ); + + setText( legendData.title() ); + setIcon( legendData.icon().toPixmap() ); + + if ( legendData.hasRole( QwtLegendData::ModeRole ) ) + setItemMode( legendData.mode() ); + + if ( doUpdate ) + { + setUpdatesEnabled( true ); + update(); + } +} + +/*! + \return Attributes of the label + \sa setData(), QwtPlotItem::legendData() + */ +const QwtLegendData &QwtLegendLabel::data() const +{ + return d_data->legendData; +} + +/*! + \param parent Parent widget +*/ +QwtLegendLabel::QwtLegendLabel( QWidget *parent ): + QwtTextLabel( parent ) +{ + d_data = new PrivateData; + setMargin( Margin ); + setIndent( Margin ); +} + +//! Destructor +QwtLegendLabel::~QwtLegendLabel() +{ + delete d_data; + d_data = NULL; +} + +/*! + Set the text to the legend item + + \param text Text label + \sa QwtTextLabel::text() +*/ +void QwtLegendLabel::setText( const QwtText &text ) +{ + const int flags = Qt::AlignLeft | Qt::AlignVCenter + | Qt::TextExpandTabs | Qt::TextWordWrap; + + QwtText txt = text; + txt.setRenderFlags( flags ); + + QwtTextLabel::setText( txt ); +} + +/*! + Set the item mode + The default is QwtLegendData::ReadOnly + + \param mode Item mode + \sa itemMode() +*/ +void QwtLegendLabel::setItemMode( QwtLegendData::Mode mode ) +{ + if ( mode != d_data->itemMode ) + { + d_data->itemMode = mode; + d_data->isDown = false; + + setFocusPolicy( ( mode != QwtLegendData::ReadOnly ) + ? Qt::TabFocus : Qt::NoFocus ); + setMargin( ButtonFrame + Margin ); + + updateGeometry(); + } +} + +/*! + \return Item mode + \sa setItemMode() +*/ +QwtLegendData::Mode QwtLegendLabel::itemMode() const +{ + return d_data->itemMode; +} + +/*! + Assign the icon + + \param icon Pixmap representing a plot item + + \sa icon(), QwtPlotItem::legendIcon() +*/ +void QwtLegendLabel::setIcon( const QPixmap &icon ) +{ + d_data->icon = icon; + + int indent = margin() + d_data->spacing; + if ( icon.width() > 0 ) + indent += icon.width() + d_data->spacing; + + setIndent( indent ); +} + +/*! + \return Pixmap representing a plot item + \sa setIcon() +*/ +QPixmap QwtLegendLabel::icon() const +{ + return d_data->icon; +} + +/*! + \brief Change the spacing between icon and text + + \param spacing Spacing + \sa spacing(), QwtTextLabel::margin() +*/ +void QwtLegendLabel::setSpacing( int spacing ) +{ + spacing = qMax( spacing, 0 ); + if ( spacing != d_data->spacing ) + { + d_data->spacing = spacing; + + int indent = margin() + d_data->spacing; + if ( d_data->icon.width() > 0 ) + indent += d_data->icon.width() + d_data->spacing; + + setIndent( indent ); + } +} + +/*! + \return Spacing between icon and text + \sa setSpacing(), QwtTextLabel::margin() +*/ +int QwtLegendLabel::spacing() const +{ + return d_data->spacing; +} + +/*! + Check/Uncheck a the item + + \param on check/uncheck + \sa setItemMode() +*/ +void QwtLegendLabel::setChecked( bool on ) +{ + if ( d_data->itemMode == QwtLegendData::Checkable ) + { + const bool isBlocked = signalsBlocked(); + blockSignals( true ); + + setDown( on ); + + blockSignals( isBlocked ); + } +} + +//! Return true, if the item is checked +bool QwtLegendLabel::isChecked() const +{ + return d_data->itemMode == QwtLegendData::Checkable && isDown(); +} + +//! Set the item being down +void QwtLegendLabel::setDown( bool down ) +{ + if ( down == d_data->isDown ) + return; + + d_data->isDown = down; + update(); + + if ( d_data->itemMode == QwtLegendData::Clickable ) + { + if ( d_data->isDown ) + Q_EMIT pressed(); + else + { + Q_EMIT released(); + Q_EMIT clicked(); + } + } + + if ( d_data->itemMode == QwtLegendData::Checkable ) + Q_EMIT checked( d_data->isDown ); +} + +//! Return true, if the item is down +bool QwtLegendLabel::isDown() const +{ + return d_data->isDown; +} + +//! Return a size hint +QSize QwtLegendLabel::sizeHint() const +{ + QSize sz = QwtTextLabel::sizeHint(); + sz.setHeight( qMax( sz.height(), d_data->icon.height() + 4 ) ); + + if ( d_data->itemMode != QwtLegendData::ReadOnly ) + { + sz += buttonShift( this ); + sz = sz.expandedTo( QApplication::globalStrut() ); + } + + return sz; +} + +//! Paint event +void QwtLegendLabel::paintEvent( QPaintEvent *e ) +{ + const QRect cr = contentsRect(); + + QPainter painter( this ); + painter.setClipRegion( e->region() ); + + if ( d_data->isDown ) + { + qDrawWinButton( &painter, 0, 0, width(), height(), + palette(), true ); + } + + painter.save(); + + if ( d_data->isDown ) + { + const QSize shiftSize = buttonShift( this ); + painter.translate( shiftSize.width(), shiftSize.height() ); + } + + painter.setClipRect( cr ); + + drawContents( &painter ); + + if ( !d_data->icon.isNull() ) + { + QRect iconRect = cr; + iconRect.setX( iconRect.x() + margin() ); + if ( d_data->itemMode != QwtLegendData::ReadOnly ) + iconRect.setX( iconRect.x() + ButtonFrame ); + + iconRect.setSize( d_data->icon.size() ); + iconRect.moveCenter( QPoint( iconRect.center().x(), cr.center().y() ) ); + + painter.drawPixmap( iconRect, d_data->icon ); + } + + painter.restore(); +} + +//! Handle mouse press events +void QwtLegendLabel::mousePressEvent( QMouseEvent *e ) +{ + if ( e->button() == Qt::LeftButton ) + { + switch ( d_data->itemMode ) + { + case QwtLegendData::Clickable: + { + setDown( true ); + return; + } + case QwtLegendData::Checkable: + { + setDown( !isDown() ); + return; + } + default:; + } + } + QwtTextLabel::mousePressEvent( e ); +} + +//! Handle mouse release events +void QwtLegendLabel::mouseReleaseEvent( QMouseEvent *e ) +{ + if ( e->button() == Qt::LeftButton ) + { + switch ( d_data->itemMode ) + { + case QwtLegendData::Clickable: + { + setDown( false ); + return; + } + case QwtLegendData::Checkable: + { + return; // do nothing, but accept + } + default:; + } + } + QwtTextLabel::mouseReleaseEvent( e ); +} + +//! Handle key press events +void QwtLegendLabel::keyPressEvent( QKeyEvent *e ) +{ + if ( e->key() == Qt::Key_Space ) + { + switch ( d_data->itemMode ) + { + case QwtLegendData::Clickable: + { + if ( !e->isAutoRepeat() ) + setDown( true ); + return; + } + case QwtLegendData::Checkable: + { + if ( !e->isAutoRepeat() ) + setDown( !isDown() ); + return; + } + default:; + } + } + + QwtTextLabel::keyPressEvent( e ); +} + +//! Handle key release events +void QwtLegendLabel::keyReleaseEvent( QKeyEvent *e ) +{ + if ( e->key() == Qt::Key_Space ) + { + switch ( d_data->itemMode ) + { + case QwtLegendData::Clickable: + { + if ( !e->isAutoRepeat() ) + setDown( false ); + return; + } + case QwtLegendData::Checkable: + { + return; // do nothing, but accept + } + default:; + } + } + + QwtTextLabel::keyReleaseEvent( e ); +} diff --git a/qwt/src/qwt_legend_label.h b/qwt/src/qwt_legend_label.h new file mode 100644 index 000000000..f0a1584af --- /dev/null +++ b/qwt/src/qwt_legend_label.h @@ -0,0 +1,80 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_LEGEND_LABEL_H +#define QWT_LEGEND_LABEL_H + +#include "qwt_global.h" +#include "qwt_legend_data.h" +#include "qwt_text.h" +#include "qwt_text_label.h" +#include + +class QwtLegendData; + +/*! + \brief A widget representing something on a QwtLegend. +*/ +class QWT_EXPORT QwtLegendLabel: public QwtTextLabel +{ + Q_OBJECT +public: + explicit QwtLegendLabel( QWidget *parent = 0 ); + virtual ~QwtLegendLabel(); + + void setData( const QwtLegendData & ); + const QwtLegendData &data() const; + + void setItemMode( QwtLegendData::Mode ); + QwtLegendData::Mode itemMode() const; + + void setSpacing( int spacing ); + int spacing() const; + + virtual void setText( const QwtText & ); + + void setIcon( const QPixmap & ); + QPixmap icon() const; + + virtual QSize sizeHint() const; + + bool isChecked() const; + +public Q_SLOTS: + void setChecked( bool on ); + +Q_SIGNALS: + //! Signal, when the legend item has been clicked + void clicked(); + + //! Signal, when the legend item has been pressed + void pressed(); + + //! Signal, when the legend item has been released + void released(); + + //! Signal, when the legend item has been toggled + void checked( bool ); + +protected: + void setDown( bool ); + bool isDown() const; + + virtual void paintEvent( QPaintEvent * ); + virtual void mousePressEvent( QMouseEvent * ); + virtual void mouseReleaseEvent( QMouseEvent * ); + virtual void keyPressEvent( QKeyEvent * ); + virtual void keyReleaseEvent( QKeyEvent * ); + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_magnifier.cpp b/qwt/src/qwt_magnifier.cpp new file mode 100644 index 000000000..55e7bb5eb --- /dev/null +++ b/qwt/src/qwt_magnifier.cpp @@ -0,0 +1,492 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_magnifier.h" +#include "qwt_math.h" +#include +#include + +class QwtMagnifier::PrivateData +{ +public: + PrivateData(): + isEnabled( false ), + wheelFactor( 0.9 ), + wheelModifiers( Qt::NoModifier ), + mouseFactor( 0.95 ), + mouseButton( Qt::RightButton ), + mouseButtonModifiers( Qt::NoModifier ), + keyFactor( 0.9 ), + zoomInKey( Qt::Key_Plus ), + zoomInKeyModifiers( Qt::NoModifier ), + zoomOutKey( Qt::Key_Minus ), + zoomOutKeyModifiers( Qt::NoModifier ), + mousePressed( false ) + { + } + + bool isEnabled; + + double wheelFactor; + Qt::KeyboardModifiers wheelModifiers; + + double mouseFactor; + + Qt::MouseButton mouseButton; + Qt::KeyboardModifiers mouseButtonModifiers; + + double keyFactor; + + int zoomInKey; + Qt::KeyboardModifiers zoomInKeyModifiers; + + int zoomOutKey; + Qt::KeyboardModifiers zoomOutKeyModifiers; + + bool mousePressed; + bool hasMouseTracking; + QPoint mousePos; +}; + +/*! + Constructor + \param parent Widget to be magnified +*/ +QwtMagnifier::QwtMagnifier( QWidget *parent ): + QObject( parent ) +{ + d_data = new PrivateData(); + setEnabled( true ); +} + +//! Destructor +QwtMagnifier::~QwtMagnifier() +{ + delete d_data; +} + +/*! + \brief En/disable the magnifier + + When enabled is true an event filter is installed for + the observed widget, otherwise the event filter is removed. + + \param on true or false + \sa isEnabled(), eventFilter() +*/ +void QwtMagnifier::setEnabled( bool on ) +{ + if ( d_data->isEnabled != on ) + { + d_data->isEnabled = on; + + QObject *o = parent(); + if ( o ) + { + if ( d_data->isEnabled ) + o->installEventFilter( this ); + else + o->removeEventFilter( this ); + } + } +} + +/*! + \return true when enabled, false otherwise + \sa setEnabled(), eventFilter() +*/ +bool QwtMagnifier::isEnabled() const +{ + return d_data->isEnabled; +} + +/*! + \brief Change the wheel factor + + The wheel factor defines the ratio between the current range + on the parent widget and the zoomed range for each step of the wheel. + + Use values > 1 for magnification (i.e. 2.0) and values < 1 for + scaling down (i.e. 1/2.0 = 0.5). You can use this feature for + inverting the direction of the wheel. + + The default value is 0.9. + + \param factor Wheel factor + \sa wheelFactor(), setWheelButtonState(), + setMouseFactor(), setKeyFactor() +*/ +void QwtMagnifier::setWheelFactor( double factor ) +{ + d_data->wheelFactor = factor; +} + +/*! + \return Wheel factor + \sa setWheelFactor() +*/ +double QwtMagnifier::wheelFactor() const +{ + return d_data->wheelFactor; +} + +/*! + Assign keyboard modifiers for zooming in/out using the wheel. + The default modifiers are Qt::NoModifiers. + + \param modifiers Keyboard modifiers + \sa wheelModifiers() +*/ +void QwtMagnifier::setWheelModifiers( Qt::KeyboardModifiers modifiers ) +{ + d_data->wheelModifiers = modifiers; +} + +/*! + \return Wheel modifiers + \sa setWheelModifiers() +*/ +Qt::KeyboardModifiers QwtMagnifier::wheelModifiers() const +{ + return d_data->wheelModifiers; +} + +/*! + \brief Change the mouse factor + + The mouse factor defines the ratio between the current range + on the parent widget and the zoomed range for each vertical mouse movement. + The default value is 0.95. + + \param factor Wheel factor + \sa mouseFactor(), setMouseButton(), setWheelFactor(), setKeyFactor() +*/ +void QwtMagnifier::setMouseFactor( double factor ) +{ + d_data->mouseFactor = factor; +} + +/*! + \return Mouse factor + \sa setMouseFactor() +*/ +double QwtMagnifier::mouseFactor() const +{ + return d_data->mouseFactor; +} + +/*! + Assign the mouse button, that is used for zooming in/out. + The default value is Qt::RightButton. + + \param button Button + \param modifiers Keyboard modifiers + + \sa getMouseButton() +*/ +void QwtMagnifier::setMouseButton( + Qt::MouseButton button, Qt::KeyboardModifiers modifiers ) +{ + d_data->mouseButton = button; + d_data->mouseButtonModifiers = modifiers; +} + +//! \sa setMouseButton() +void QwtMagnifier::getMouseButton( + Qt::MouseButton &button, Qt::KeyboardModifiers &modifiers ) const +{ + button = d_data->mouseButton; + modifiers = d_data->mouseButtonModifiers; +} + +/*! + \brief Change the key factor + + The key factor defines the ratio between the current range + on the parent widget and the zoomed range for each key press of + the zoom in/out keys. The default value is 0.9. + + \param factor Key factor + \sa keyFactor(), setZoomInKey(), setZoomOutKey(), + setWheelFactor, setMouseFactor() +*/ +void QwtMagnifier::setKeyFactor( double factor ) +{ + d_data->keyFactor = factor; +} + +/*! + \return Key factor + \sa setKeyFactor() +*/ +double QwtMagnifier::keyFactor() const +{ + return d_data->keyFactor; +} + +/*! + Assign the key, that is used for zooming in. + The default combination is Qt::Key_Plus + Qt::NoModifier. + + \param key + \param modifiers + \sa getZoomInKey(), setZoomOutKey() +*/ +void QwtMagnifier::setZoomInKey( int key, + Qt::KeyboardModifiers modifiers ) +{ + d_data->zoomInKey = key; + d_data->zoomInKeyModifiers = modifiers; +} + +/*! + \brief Retrieve the settings of the zoom in key + + \param key Key code, see Qt::Key + \param modifiers Keyboard modifiers + + \sa setZoomInKey() +*/ +void QwtMagnifier::getZoomInKey( int &key, + Qt::KeyboardModifiers &modifiers ) const +{ + key = d_data->zoomInKey; + modifiers = d_data->zoomInKeyModifiers; +} + +/*! + Assign the key, that is used for zooming out. + The default combination is Qt::Key_Minus + Qt::NoModifier. + + \param key + \param modifiers + \sa getZoomOutKey(), setZoomOutKey() +*/ +void QwtMagnifier::setZoomOutKey( int key, + Qt::KeyboardModifiers modifiers ) +{ + d_data->zoomOutKey = key; + d_data->zoomOutKeyModifiers = modifiers; +} + +/*! + \brief Retrieve the settings of the zoom out key + + \param key Key code, see Qt::Key + \param modifiers Keyboard modifiers + + \sa setZoomOutKey() +*/ +void QwtMagnifier::getZoomOutKey( int &key, + Qt::KeyboardModifiers &modifiers ) const +{ + key = d_data->zoomOutKey; + modifiers = d_data->zoomOutKeyModifiers; +} + +/*! + \brief Event filter + + When isEnabled() is true, the mouse events of the + observed widget are filtered. + + \param object Object to be filtered + \param event Event + + \return Forwarded to QObject::eventFilter() + + \sa widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseMoveEvent(), widgetWheelEvent(), widgetKeyPressEvent() + widgetKeyReleaseEvent() +*/ +bool QwtMagnifier::eventFilter( QObject *object, QEvent *event ) +{ + if ( object && object == parent() ) + { + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + widgetMousePressEvent( static_cast( event ) ); + break; + } + case QEvent::MouseMove: + { + widgetMouseMoveEvent( static_cast( event ) ); + break; + } + case QEvent::MouseButtonRelease: + { + widgetMouseReleaseEvent( static_cast( event ) ); + break; + } + case QEvent::Wheel: + { + widgetWheelEvent( static_cast( event ) ); + break; + } + case QEvent::KeyPress: + { + widgetKeyPressEvent( static_cast( event ) ); + break; + } + case QEvent::KeyRelease: + { + widgetKeyReleaseEvent( static_cast( event ) ); + break; + } + default:; + } + } + return QObject::eventFilter( object, event ); +} + +/*! + Handle a mouse press event for the observed widget. + + \param mouseEvent Mouse event + \sa eventFilter(), widgetMouseReleaseEvent(), widgetMouseMoveEvent() +*/ +void QwtMagnifier::widgetMousePressEvent( QMouseEvent *mouseEvent ) +{ + if ( parentWidget() == NULL ) + return; + + if ( ( mouseEvent->button() != d_data->mouseButton ) || + ( mouseEvent->modifiers() != d_data->mouseButtonModifiers ) ) + { + return; + } + + d_data->hasMouseTracking = parentWidget()->hasMouseTracking(); + + parentWidget()->setMouseTracking( true ); + d_data->mousePos = mouseEvent->pos(); + d_data->mousePressed = true; +} + +/*! + Handle a mouse release event for the observed widget. + + \param mouseEvent Mouse event + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseMoveEvent(), +*/ +void QwtMagnifier::widgetMouseReleaseEvent( QMouseEvent *mouseEvent ) +{ + Q_UNUSED( mouseEvent ); + + if ( d_data->mousePressed && parentWidget() ) + { + d_data->mousePressed = false; + parentWidget()->setMouseTracking( d_data->hasMouseTracking ); + } +} + +/*! + Handle a mouse move event for the observed widget. + + \param mouseEvent Mouse event + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), +*/ +void QwtMagnifier::widgetMouseMoveEvent( QMouseEvent *mouseEvent ) +{ + if ( !d_data->mousePressed ) + return; + + const int dy = mouseEvent->pos().y() - d_data->mousePos.y(); + if ( dy != 0 ) + { + double f = d_data->mouseFactor; + if ( dy < 0 ) + f = 1 / f; + + rescale( f ); + } + + d_data->mousePos = mouseEvent->pos(); +} + +/*! + Handle a wheel event for the observed widget. + + \param wheelEvent Wheel event + \sa eventFilter() +*/ +void QwtMagnifier::widgetWheelEvent( QWheelEvent *wheelEvent ) +{ + if ( wheelEvent->modifiers() != d_data->wheelModifiers ) + { + return; + } + + if ( d_data->wheelFactor != 0.0 ) + { + /* + A positive delta indicates that the wheel was + rotated forwards away from the user; a negative + value indicates that the wheel was rotated + backwards toward the user. + Most mouse types work in steps of 15 degrees, + in which case the delta value is a multiple + of 120 (== 15 * 8). + */ + double f = qPow( d_data->wheelFactor, + qAbs( wheelEvent->delta() / 120.0 ) ); + + if ( wheelEvent->delta() > 0 ) + f = 1 / f; + + rescale( f ); + } +} + +/*! + Handle a key press event for the observed widget. + + \param keyEvent Key event + \sa eventFilter(), widgetKeyReleaseEvent() +*/ +void QwtMagnifier::widgetKeyPressEvent( QKeyEvent *keyEvent ) +{ + if ( keyEvent->key() == d_data->zoomInKey && + keyEvent->modifiers() == d_data->zoomInKeyModifiers ) + { + rescale( d_data->keyFactor ); + } + else if ( keyEvent->key() == d_data->zoomOutKey && + keyEvent->modifiers() == d_data->zoomOutKeyModifiers ) + { + rescale( 1.0 / d_data->keyFactor ); + } +} + +/*! + Handle a key release event for the observed widget. + + \param keyEvent Key event + \sa eventFilter(), widgetKeyReleaseEvent() +*/ +void QwtMagnifier::widgetKeyReleaseEvent( QKeyEvent *keyEvent ) +{ + Q_UNUSED( keyEvent ); +} + +//! \return Parent widget, where the rescaling happens +QWidget *QwtMagnifier::parentWidget() +{ + return qobject_cast( parent() ); +} + +//! \return Parent widget, where the rescaling happens +const QWidget *QwtMagnifier::parentWidget() const +{ + return qobject_cast( parent() ); +} + diff --git a/qwt/src/qwt_magnifier.h b/qwt/src/qwt_magnifier.h new file mode 100644 index 000000000..48e8ed8c4 --- /dev/null +++ b/qwt/src/qwt_magnifier.h @@ -0,0 +1,86 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_MAGNIFIER_H +#define QWT_MAGNIFIER_H 1 + +#include "qwt_global.h" +#include + +class QWidget; +class QMouseEvent; +class QWheelEvent; +class QKeyEvent; + +/*! + \brief QwtMagnifier provides zooming, by magnifying in steps. + + Using QwtMagnifier a plot can be zoomed in/out in steps using + keys, the mouse wheel or moving a mouse button in vertical direction. +*/ +class QWT_EXPORT QwtMagnifier: public QObject +{ + Q_OBJECT + +public: + explicit QwtMagnifier( QWidget * ); + virtual ~QwtMagnifier(); + + QWidget *parentWidget(); + const QWidget *parentWidget() const; + + void setEnabled( bool ); + bool isEnabled() const; + + // mouse + void setMouseFactor( double ); + double mouseFactor() const; + + void setMouseButton( Qt::MouseButton, Qt::KeyboardModifiers = Qt::NoModifier ); + void getMouseButton( Qt::MouseButton &, Qt::KeyboardModifiers & ) const; + + // mouse wheel + void setWheelFactor( double ); + double wheelFactor() const; + + void setWheelModifiers( Qt::KeyboardModifiers ); + Qt::KeyboardModifiers wheelModifiers() const; + + // keyboard + void setKeyFactor( double ); + double keyFactor() const; + + void setZoomInKey( int key, Qt::KeyboardModifiers = Qt::NoModifier ); + void getZoomInKey( int &key, Qt::KeyboardModifiers & ) const; + + void setZoomOutKey( int key, Qt::KeyboardModifiers = Qt::NoModifier ); + void getZoomOutKey( int &key, Qt::KeyboardModifiers & ) const; + + virtual bool eventFilter( QObject *, QEvent * ); + +protected: + /*! + Rescale the parent widget + \param factor Scale factor + */ + virtual void rescale( double factor ) = 0; + + virtual void widgetMousePressEvent( QMouseEvent * ); + virtual void widgetMouseReleaseEvent( QMouseEvent * ); + virtual void widgetMouseMoveEvent( QMouseEvent * ); + virtual void widgetWheelEvent( QWheelEvent * ); + virtual void widgetKeyPressEvent( QKeyEvent * ); + virtual void widgetKeyReleaseEvent( QKeyEvent * ); + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_math.cpp b/qwt/src/qwt_math.cpp new file mode 100644 index 000000000..9e898c105 --- /dev/null +++ b/qwt/src/qwt_math.cpp @@ -0,0 +1,74 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_math.h" + +/*! + \brief Find the smallest value in an array + \param array Pointer to an array + \param size Array size +*/ +double qwtGetMin( const double *array, int size ) +{ + if ( size <= 0 ) + return 0.0; + + double rv = array[0]; + for ( int i = 1; i < size; i++ ) + rv = qMin( rv, array[i] ); + + return rv; +} + + +/*! + \brief Find the largest value in an array + \param array Pointer to an array + \param size Array size +*/ +double qwtGetMax( const double *array, int size ) +{ + if ( size <= 0 ) + return 0.0; + + double rv = array[0]; + for ( int i = 1; i < size; i++ ) + rv = qMax( rv, array[i] ); + + return rv; +} + +/*! + \brief Normalize an angle to be int the range [0.0, 2 * PI[ + \param radians Angle in radians + \return Normalized angle in radians +*/ +double qwtNormalizeRadians( double radians ) +{ + double a = ::fmod( radians, 2.0 * M_PI ); + if ( a < 0.0 ) + a += 2.0 * M_PI; + + return a; + +} + +/*! + \brief Normalize an angle to be int the range [0.0, 360.0[ + \param radians Angle in degrees + \return Normalized angle in degrees +*/ +double qwtNormalizeDegrees( double degrees ) +{ + double a = ::fmod( degrees, 360.0 ); + if ( a < 0.0 ) + a += 360.0; + + return a; +} diff --git a/qwt/src/qwt_math.h b/qwt/src/qwt_math.h new file mode 100644 index 000000000..23ad20560 --- /dev/null +++ b/qwt/src/qwt_math.h @@ -0,0 +1,149 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_MATH_H +#define QWT_MATH_H + +#include "qwt_global.h" + +#if defined(_MSC_VER) +/* + Microsoft says: + + Define _USE_MATH_DEFINES before including math.h to expose these macro + definitions for common math constants. These are placed under an #ifdef + since these commonly-defined names are not part of the C/C++ standards. +*/ +#define _USE_MATH_DEFINES 1 +#endif + +#include +#include "qwt_global.h" + +#ifndef M_PI_2 +// For Qt <= 4.8.4 M_PI_2 is not known by MinGW-w64 +// when compiling with -std=c++11 +#define M_PI_2 (1.57079632679489661923) +#endif + +#ifndef LOG_MIN +//! Minimum value for logarithmic scales +#define LOG_MIN 1.0e-100 +#endif + +#ifndef LOG_MAX +//! Maximum value for logarithmic scales +#define LOG_MAX 1.0e100 +#endif + +QWT_EXPORT double qwtGetMin( const double *array, int size ); +QWT_EXPORT double qwtGetMax( const double *array, int size ); + +QWT_EXPORT double qwtNormalizeRadians( double radians ); +QWT_EXPORT double qwtNormalizeDegrees( double degrees ); + +/*! + \brief Compare 2 values, relative to an interval + + Values are "equal", when : + \f$\cdot value2 - value1 <= abs(intervalSize * 10e^{-6})\f$ + + \param value1 First value to compare + \param value2 Second value to compare + \param intervalSize interval size + + \return 0: if equal, -1: if value2 > value1, 1: if value1 > value2 +*/ +inline int qwtFuzzyCompare( double value1, double value2, double intervalSize ) +{ + const double eps = qAbs( 1.0e-6 * intervalSize ); + + if ( value2 - value1 > eps ) + return -1; + + if ( value1 - value2 > eps ) + return 1; + + return 0; +} + + +inline bool qwtFuzzyGreaterOrEqual( double d1, double d2 ) +{ + return ( d1 >= d2 ) || qFuzzyCompare( d1, d2 ); +} + +inline bool qwtFuzzyLessOrEqual( double d1, double d2 ) +{ + return ( d1 <= d2 ) || qFuzzyCompare( d1, d2 ); +} + +//! Return the sign +inline int qwtSign( double x ) +{ + if ( x > 0.0 ) + return 1; + else if ( x < 0.0 ) + return ( -1 ); + else + return 0; +} + +//! Return the square of a number +inline double qwtSqr( double x ) +{ + return x * x; +} + +//! Approximation of arc tangent ( error below 0,005 radians ) +inline double qwtFastAtan( double x ) +{ + if ( x < -1.0 ) + return -M_PI_2 - x / ( x * x + 0.28 ); + + if ( x > 1.0 ) + return M_PI_2 - x / ( x * x + 0.28 ); + + return x / ( 1.0 + x * x * 0.28 ); +} + +//! Approximation of arc tangent ( error below 0,005 radians ) +inline double qwtFastAtan2( double y, double x ) +{ + if ( x > 0 ) + return qwtFastAtan( y / x ); + + if ( x < 0 ) + { + const double d = qwtFastAtan( y / x ); + return ( y >= 0 ) ? d + M_PI : d - M_PI; + } + + if ( y < 0.0 ) + return -M_PI_2; + + if ( y > 0.0 ) + return M_PI_2; + + return 0.0; +} + +// Translate degrees into radians +inline double qwtRadians( double degrees ) +{ + return degrees * M_PI / 180.0; +} + +// Translate radians into degrees +inline double qwtDegrees( double degrees ) +{ + return degrees * 180.0 / M_PI; +} + +#endif diff --git a/qwt/src/qwt_matrix_raster_data.cpp b/qwt/src/qwt_matrix_raster_data.cpp new file mode 100644 index 000000000..69355adb3 --- /dev/null +++ b/qwt/src/qwt_matrix_raster_data.cpp @@ -0,0 +1,298 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_matrix_raster_data.h" +#include +#include + +class QwtMatrixRasterData::PrivateData +{ +public: + PrivateData(): + resampleMode(QwtMatrixRasterData::NearestNeighbour), + numColumns(0) + { + } + + inline double value(int row, int col) const + { + return values.data()[ row * numColumns + col ]; + } + + QwtMatrixRasterData::ResampleMode resampleMode; + + QVector values; + int numColumns; + int numRows; + + double dx; + double dy; +}; + +//! Constructor +QwtMatrixRasterData::QwtMatrixRasterData() +{ + d_data = new PrivateData(); + update(); +} + +//! Destructor +QwtMatrixRasterData::~QwtMatrixRasterData() +{ + delete d_data; +} + +/*! + \brief Set the resampling algorithm + + \param mode Resampling mode + \sa resampleMode(), value() +*/ +void QwtMatrixRasterData::setResampleMode( ResampleMode mode ) +{ + d_data->resampleMode = mode; +} + +/*! + \return resampling algorithm + \sa setResampleMode(), value() +*/ +QwtMatrixRasterData::ResampleMode QwtMatrixRasterData::resampleMode() const +{ + return d_data->resampleMode; +} + +/*! + \brief Assign the bounding interval for an axis + + Setting the bounding intervals for the X/Y axis is mandatory + to define the positions for the values of the value matrix. + The interval in Z direction defines the possible range for + the values in the matrix, what is f.e used by QwtPlotSpectrogram + to map values to colors. The Z-interval might be the bounding + interval of the values in the matrix, but usually it isn't. + ( f.e a interval of 0.0-100.0 for values in percentage ) + + \param axis X, Y or Z axis + \param interval Interval + + \sa QwtRasterData::interval(), setValueMatrix() +*/ +void QwtMatrixRasterData::setInterval( + Qt::Axis axis, const QwtInterval &interval ) +{ + QwtRasterData::setInterval( axis, interval ); + update(); +} + +/*! + \brief Assign a value matrix + + The positions of the values are calculated by dividing + the bounding rectangle of the X/Y intervals into equidistant + rectangles ( pixels ). Each value corresponds to the center of + a pixel. + + \param values Vector of values + \param numColumns Number of columns + + \sa valueMatrix(), numColumns(), numRows(), setInterval()() +*/ +void QwtMatrixRasterData::setValueMatrix( + const QVector &values, int numColumns ) +{ + d_data->values = values; + d_data->numColumns = qMax( numColumns, 0 ); + update(); +} + +/*! + \return Value matrix + \sa setValueMatrix(), numColumns(), numRows(), setInterval() +*/ +const QVector QwtMatrixRasterData::valueMatrix() const +{ + return d_data->values; +} + +/*! + \brief Change a single value in the matrix + + \param row Row index + \param col Column index + \param value New value + + \sa value(), setValueMatrix() +*/ +void QwtMatrixRasterData::setValue( int row, int col, double value ) +{ + if ( row >= 0 && row < d_data->numRows && + col >= 0 && col < d_data->numColumns ) + { + const int index = row * d_data->numColumns + col; + d_data->values.data()[ index ] = value; + } +} + +/*! + \return Number of columns of the value matrix + \sa valueMatrix(), numRows(), setValueMatrix() +*/ +int QwtMatrixRasterData::numColumns() const +{ + return d_data->numColumns; +} + +/*! + \return Number of rows of the value matrix + \sa valueMatrix(), numColumns(), setValueMatrix() +*/ +int QwtMatrixRasterData::numRows() const +{ + return d_data->numRows; +} + +/*! + \brief Calculate the pixel hint + + pixelHint() returns the geometry of a pixel, that can be used + to calculate the resolution and alignment of the plot item, that is + representing the data. + + - NearestNeighbour\n + pixelHint() returns the surrounding pixel of the top left value + in the matrix. + + - BilinearInterpolation\n + Returns an empty rectangle recommending + to render in target device ( f.e. screen ) resolution. + + \param area Requested area, ignored + \return Calculated hint + + \sa ResampleMode, setMatrix(), setInterval() +*/ +QRectF QwtMatrixRasterData::pixelHint( const QRectF &area ) const +{ + Q_UNUSED( area ) + + QRectF rect; + if ( d_data->resampleMode == NearestNeighbour ) + { + const QwtInterval intervalX = interval( Qt::XAxis ); + const QwtInterval intervalY = interval( Qt::YAxis ); + if ( intervalX.isValid() && intervalY.isValid() ) + { + rect = QRectF( intervalX.minValue(), intervalY.minValue(), + d_data->dx, d_data->dy ); + } + } + + return rect; +} + +/*! + \return the value at a raster position + + \param x X value in plot coordinates + \param y Y value in plot coordinates + + \sa ResampleMode +*/ +double QwtMatrixRasterData::value( double x, double y ) const +{ + const QwtInterval xInterval = interval( Qt::XAxis ); + const QwtInterval yInterval = interval( Qt::YAxis ); + + if ( !( xInterval.contains(x) && yInterval.contains(y) ) ) + return qQNaN(); + + double value; + + switch( d_data->resampleMode ) + { + case BilinearInterpolation: + { + int col1 = qRound( (x - xInterval.minValue() ) / d_data->dx ) - 1; + int row1 = qRound( (y - yInterval.minValue() ) / d_data->dy ) - 1; + int col2 = col1 + 1; + int row2 = row1 + 1; + + if ( col1 < 0 ) + col1 = col2; + else if ( col2 >= static_cast( d_data->numColumns ) ) + col2 = col1; + + if ( row1 < 0 ) + row1 = row2; + else if ( row2 >= static_cast( d_data->numRows ) ) + row2 = row1; + + const double v11 = d_data->value( row1, col1 ); + const double v21 = d_data->value( row1, col2 ); + const double v12 = d_data->value( row2, col1 ); + const double v22 = d_data->value( row2, col2 ); + + const double x2 = xInterval.minValue() + + ( col2 + 0.5 ) * d_data->dx; + const double y2 = yInterval.minValue() + + ( row2 + 0.5 ) * d_data->dy; + + const double rx = ( x2 - x ) / d_data->dx; + const double ry = ( y2 - y ) / d_data->dy; + + const double vr1 = rx * v11 + ( 1.0 - rx ) * v21; + const double vr2 = rx * v12 + ( 1.0 - rx ) * v22; + + value = ry * vr1 + ( 1.0 - ry ) * vr2; + + break; + } + case NearestNeighbour: + default: + { + int row = int( (y - yInterval.minValue() ) / d_data->dy ); + int col = int( (x - xInterval.minValue() ) / d_data->dx ); + + // In case of intervals, where the maximum is included + // we get out of bound for row/col, when the value for the + // maximum is requested. Instead we return the value + // from the last row/col + + if ( row >= d_data->numRows ) + row = d_data->numRows - 1; + + if ( col >= d_data->numColumns ) + col = d_data->numColumns - 1; + + value = d_data->value( row, col ); + } + } + + return value; +} + +void QwtMatrixRasterData::update() +{ + d_data->numRows = 0; + d_data->dx = 0.0; + d_data->dy = 0.0; + + if ( d_data->numColumns > 0 ) + { + d_data->numRows = d_data->values.size() / d_data->numColumns; + + const QwtInterval xInterval = interval( Qt::XAxis ); + const QwtInterval yInterval = interval( Qt::YAxis ); + if ( xInterval.isValid() ) + d_data->dx = xInterval.width() / d_data->numColumns; + if ( yInterval.isValid() ) + d_data->dy = yInterval.width() / d_data->numRows; + } +} diff --git a/qwt/src/qwt_matrix_raster_data.h b/qwt/src/qwt_matrix_raster_data.h new file mode 100644 index 000000000..0b107c9fd --- /dev/null +++ b/qwt/src/qwt_matrix_raster_data.h @@ -0,0 +1,74 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_MATRIX_RASTER_DATA_H +#define QWT_MATRIX_RASTER_DATA_H 1 + +#include "qwt_global.h" +#include "qwt_raster_data.h" +#include + +/*! + \brief A class representing a matrix of values as raster data + + QwtMatrixRasterData implements an interface for a matrix of + equidistant values, that can be used by a QwtPlotRasterItem. + It implements a couple of resampling algorithms, to provide + values for positions, that or not on the value matrix. +*/ +class QWT_EXPORT QwtMatrixRasterData: public QwtRasterData +{ +public: + /*! + \brief Resampling algorithm + The default setting is NearestNeighbour; + */ + enum ResampleMode + { + /*! + Return the value from the matrix, that is nearest to the + the requested position. + */ + NearestNeighbour, + + /*! + Interpolate the value from the distances and values of the + 4 surrounding values in the matrix, + */ + BilinearInterpolation + }; + + QwtMatrixRasterData(); + virtual ~QwtMatrixRasterData(); + + void setResampleMode(ResampleMode mode); + ResampleMode resampleMode() const; + + virtual void setInterval( Qt::Axis, const QwtInterval & ); + + void setValueMatrix( const QVector &values, int numColumns ); + const QVector valueMatrix() const; + + void setValue( int row, int col, double value ); + + int numColumns() const; + int numRows() const; + + virtual QRectF pixelHint( const QRectF & ) const; + + virtual double value( double x, double y ) const; + +private: + void update(); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_null_paintdevice.cpp b/qwt/src/qwt_null_paintdevice.cpp new file mode 100644 index 000000000..db1611da2 --- /dev/null +++ b/qwt/src/qwt_null_paintdevice.cpp @@ -0,0 +1,593 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_null_paintdevice.h" +#include +#include + +class QwtNullPaintDevice::PrivateData +{ +public: + PrivateData(): + mode( QwtNullPaintDevice::NormalMode ) + { + } + + QwtNullPaintDevice::Mode mode; +}; + +class QwtNullPaintDevice::PaintEngine: public QPaintEngine +{ +public: + PaintEngine(); + + virtual bool begin( QPaintDevice * ); + virtual bool end(); + + virtual Type type () const; + virtual void updateState(const QPaintEngineState &); + + virtual void drawRects(const QRect *, int ); + virtual void drawRects(const QRectF *, int ); + + virtual void drawLines(const QLine *, int ); + virtual void drawLines(const QLineF *, int ); + + virtual void drawEllipse(const QRectF &); + virtual void drawEllipse(const QRect &); + + virtual void drawPath(const QPainterPath &); + + virtual void drawPoints(const QPointF *, int ); + virtual void drawPoints(const QPoint *, int ); + + virtual void drawPolygon(const QPointF *, int , PolygonDrawMode ); + virtual void drawPolygon(const QPoint *, int , PolygonDrawMode ); + + virtual void drawPixmap(const QRectF &, + const QPixmap &, const QRectF &); + + virtual void drawTextItem(const QPointF &, const QTextItem &); + + virtual void drawTiledPixmap(const QRectF &, + const QPixmap &, const QPointF &s); + + virtual void drawImage(const QRectF &, + const QImage &, const QRectF &, Qt::ImageConversionFlags ); + +private: + QwtNullPaintDevice *nullDevice(); +}; + +QwtNullPaintDevice::PaintEngine::PaintEngine(): + QPaintEngine( QPaintEngine::AllFeatures ) +{ +} + +bool QwtNullPaintDevice::PaintEngine::begin( QPaintDevice * ) +{ + setActive( true ); + return true; +} + +bool QwtNullPaintDevice::PaintEngine::end() +{ + setActive( false ); + return true; +} + +QPaintEngine::Type QwtNullPaintDevice::PaintEngine::type() const +{ + return QPaintEngine::User; +} + +void QwtNullPaintDevice::PaintEngine::drawRects( + const QRect *rects, int rectCount) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawRects( rects, rectCount ); + return; + } + + device->drawRects( rects, rectCount ); +} + +void QwtNullPaintDevice::PaintEngine::drawRects( + const QRectF *rects, int rectCount) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawRects( rects, rectCount ); + return; + } + + device->drawRects( rects, rectCount ); +} + +void QwtNullPaintDevice::PaintEngine::drawLines( + const QLine *lines, int lineCount) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawLines( lines, lineCount ); + return; + } + + device->drawLines( lines, lineCount ); +} + +void QwtNullPaintDevice::PaintEngine::drawLines( + const QLineF *lines, int lineCount) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawLines( lines, lineCount ); + return; + } + + device->drawLines( lines, lineCount ); +} + +void QwtNullPaintDevice::PaintEngine::drawEllipse( + const QRectF &rect) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawEllipse( rect ); + return; + } + + device->drawEllipse( rect ); +} + +void QwtNullPaintDevice::PaintEngine::drawEllipse( + const QRect &rect) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawEllipse( rect ); + return; + } + + device->drawEllipse( rect ); +} + + +void QwtNullPaintDevice::PaintEngine::drawPath( + const QPainterPath &path) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + device->drawPath( path ); +} + +void QwtNullPaintDevice::PaintEngine::drawPoints( + const QPointF *points, int pointCount) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawPoints( points, pointCount ); + return; + } + + device->drawPoints( points, pointCount ); +} + +void QwtNullPaintDevice::PaintEngine::drawPoints( + const QPoint *points, int pointCount) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawPoints( points, pointCount ); + return; + } + + device->drawPoints( points, pointCount ); +} + +void QwtNullPaintDevice::PaintEngine::drawPolygon( + const QPointF *points, int pointCount, PolygonDrawMode mode) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() == QwtNullPaintDevice::PathMode ) + { + QPainterPath path; + + if ( pointCount > 0 ) + { + path.moveTo( points[0] ); + for ( int i = 1; i < pointCount; i++ ) + path.lineTo( points[i] ); + + if ( mode != PolylineMode ) + path.closeSubpath(); + } + + device->drawPath( path ); + return; + } + + device->drawPolygon( points, pointCount, mode ); +} + +void QwtNullPaintDevice::PaintEngine::drawPolygon( + const QPoint *points, int pointCount, PolygonDrawMode mode) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() == QwtNullPaintDevice::PathMode ) + { + QPainterPath path; + + if ( pointCount > 0 ) + { + path.moveTo( points[0] ); + for ( int i = 1; i < pointCount; i++ ) + path.lineTo( points[i] ); + + if ( mode != PolylineMode ) + path.closeSubpath(); + } + + device->drawPath( path ); + return; + } + + device->drawPolygon( points, pointCount, mode ); +} + +void QwtNullPaintDevice::PaintEngine::drawPixmap( + const QRectF &rect, const QPixmap &pm, const QRectF &subRect ) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + device->drawPixmap( rect, pm, subRect ); +} + +void QwtNullPaintDevice::PaintEngine::drawTextItem( + const QPointF &pos, const QTextItem &textItem) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawTextItem( pos, textItem ); + return; + } + + device->drawTextItem( pos, textItem ); +} + +void QwtNullPaintDevice::PaintEngine::drawTiledPixmap( + const QRectF &rect, const QPixmap &pixmap, + const QPointF &subRect) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + if ( device->mode() != QwtNullPaintDevice::NormalMode ) + { + QPaintEngine::drawTiledPixmap( rect, pixmap, subRect ); + return; + } + + device->drawTiledPixmap( rect, pixmap, subRect ); +} + +void QwtNullPaintDevice::PaintEngine::drawImage( + const QRectF &rect, const QImage &image, + const QRectF &subRect, Qt::ImageConversionFlags flags) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + device->drawImage( rect, image, subRect, flags ); +} + +void QwtNullPaintDevice::PaintEngine::updateState( + const QPaintEngineState &state) +{ + QwtNullPaintDevice *device = nullDevice(); + if ( device == NULL ) + return; + + device->updateState( state ); +} + +inline QwtNullPaintDevice *QwtNullPaintDevice::PaintEngine::nullDevice() +{ + if ( !isActive() ) + return NULL; + + return static_cast( paintDevice() ); +} + +//! Constructor +QwtNullPaintDevice::QwtNullPaintDevice(): + d_engine( NULL ) +{ + d_data = new PrivateData; +} + +//! Destructor +QwtNullPaintDevice::~QwtNullPaintDevice() +{ + delete d_engine; + delete d_data; +} + +/*! + Set the render mode + + \param mode New mode + \sa mode() + */ +void QwtNullPaintDevice::setMode( Mode mode ) +{ + d_data->mode = mode; +} + +/*! + \return Render mode + \sa setMode() +*/ +QwtNullPaintDevice::Mode QwtNullPaintDevice::mode() const +{ + return d_data->mode; +} + +//! See QPaintDevice::paintEngine() +QPaintEngine *QwtNullPaintDevice::paintEngine() const +{ + if ( d_engine == NULL ) + { + QwtNullPaintDevice *that = + const_cast< QwtNullPaintDevice * >( this ); + + that->d_engine = new PaintEngine(); + } + + return d_engine; +} + +/*! + See QPaintDevice::metric() + + \param deviceMetric Type of metric + \return Metric information for the given paint device metric. + + \sa sizeMetrics() +*/ +int QwtNullPaintDevice::metric( PaintDeviceMetric deviceMetric ) const +{ + int value; + + switch ( deviceMetric ) + { + case PdmWidth: + { + value = sizeMetrics().width(); + break; + } + case PdmHeight: + { + value = sizeMetrics().height(); + break; + } + case PdmNumColors: + { + value = 0xffffffff; + break; + } + case PdmDepth: + { + value = 32; + break; + } + case PdmPhysicalDpiX: + case PdmPhysicalDpiY: + case PdmDpiY: + case PdmDpiX: + { + value = 72; + break; + } + case PdmWidthMM: + { + value = qRound( metric( PdmWidth ) * 25.4 / metric( PdmDpiX ) ); + break; + } + case PdmHeightMM: + { + value = qRound( metric( PdmHeight ) * 25.4 / metric( PdmDpiY ) ); + break; + } + default: + value = 0; + } + return value; + +} + +//! See QPaintEngine::drawRects() +void QwtNullPaintDevice::drawRects( + const QRect *rects, int rectCount) +{ + Q_UNUSED(rects); + Q_UNUSED(rectCount); +} + +//! See QPaintEngine::drawRects() +void QwtNullPaintDevice::drawRects( + const QRectF *rects, int rectCount) +{ + Q_UNUSED(rects); + Q_UNUSED(rectCount); +} + +//! See QPaintEngine::drawLines() +void QwtNullPaintDevice::drawLines( + const QLine *lines, int lineCount) +{ + Q_UNUSED(lines); + Q_UNUSED(lineCount); +} + +//! See QPaintEngine::drawLines() +void QwtNullPaintDevice::drawLines( + const QLineF *lines, int lineCount) +{ + Q_UNUSED(lines); + Q_UNUSED(lineCount); +} + +//! See QPaintEngine::drawEllipse() +void QwtNullPaintDevice::drawEllipse( const QRectF &rect ) +{ + Q_UNUSED(rect); +} + +//! See QPaintEngine::drawEllipse() +void QwtNullPaintDevice::drawEllipse( const QRect &rect ) +{ + Q_UNUSED(rect); +} + +//! See QPaintEngine::drawPath() +void QwtNullPaintDevice::drawPath( const QPainterPath &path ) +{ + Q_UNUSED(path); +} + +//! See QPaintEngine::drawPoints() +void QwtNullPaintDevice::drawPoints( + const QPointF *points, int pointCount) +{ + Q_UNUSED(points); + Q_UNUSED(pointCount); +} + +//! See QPaintEngine::drawPoints() +void QwtNullPaintDevice::drawPoints( + const QPoint *points, int pointCount) +{ + Q_UNUSED(points); + Q_UNUSED(pointCount); +} + +//! See QPaintEngine::drawPolygon() +void QwtNullPaintDevice::drawPolygon( + const QPointF *points, int pointCount, + QPaintEngine::PolygonDrawMode mode) +{ + Q_UNUSED(points); + Q_UNUSED(pointCount); + Q_UNUSED(mode); +} + +//! See QPaintEngine::drawPolygon() +void QwtNullPaintDevice::drawPolygon( + const QPoint *points, int pointCount, + QPaintEngine::PolygonDrawMode mode) +{ + Q_UNUSED(points); + Q_UNUSED(pointCount); + Q_UNUSED(mode); +} + +//! See QPaintEngine::drawPixmap() +void QwtNullPaintDevice::drawPixmap( const QRectF &rect, + const QPixmap &pm, const QRectF &subRect ) +{ + Q_UNUSED(rect); + Q_UNUSED(pm); + Q_UNUSED(subRect); +} + +//! See QPaintEngine::drawTextItem() +void QwtNullPaintDevice::drawTextItem( + const QPointF &pos, const QTextItem &textItem) +{ + Q_UNUSED(pos); + Q_UNUSED(textItem); +} + +//! See QPaintEngine::drawTiledPixmap() +void QwtNullPaintDevice::drawTiledPixmap( + const QRectF &rect, const QPixmap &pixmap, + const QPointF &subRect) +{ + Q_UNUSED(rect); + Q_UNUSED(pixmap); + Q_UNUSED(subRect); +} + +//! See QPaintEngine::drawImage() +void QwtNullPaintDevice::drawImage( + const QRectF &rect, const QImage &image, + const QRectF &subRect, Qt::ImageConversionFlags flags) +{ + Q_UNUSED(rect); + Q_UNUSED(image); + Q_UNUSED(subRect); + Q_UNUSED(flags); +} + +//! See QPaintEngine::updateState() +void QwtNullPaintDevice::updateState( + const QPaintEngineState &state ) +{ + Q_UNUSED(state); +} diff --git a/qwt/src/qwt_null_paintdevice.h b/qwt/src/qwt_null_paintdevice.h new file mode 100644 index 000000000..d7f03beea --- /dev/null +++ b/qwt/src/qwt_null_paintdevice.h @@ -0,0 +1,126 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_NULL_PAINT_DEVICE_H +#define QWT_NULL_PAINT_DEVICE_H 1 + +#include "qwt_global.h" +#include +#include + +/*! + \brief A null paint device doing nothing + + Sometimes important layout/rendering geometries are not + available or changeable from the public Qt class interface. + ( f.e hidden in the style implementation ). + + QwtNullPaintDevice can be used to manipulate or filter out + this information by analyzing the stream of paint primitives. + + F.e. QwtNullPaintDevice is used by QwtPlotCanvas to identify + styled backgrounds with rounded corners. +*/ + +class QWT_EXPORT QwtNullPaintDevice: public QPaintDevice +{ +public: + /*! + \brief Render mode + + \sa setMode(), mode() + */ + enum Mode + { + /*! + All vector graphic primitives are painted by + the corresponding draw methods + */ + NormalMode, + + /*! + Vector graphic primitives ( beside polygons ) are mapped to a QPainterPath + and are painted by drawPath. In PathMode mode + only a few draw methods are called: + + - drawPath() + - drawPixmap() + - drawImage() + - drawPolygon() + */ + PolygonPathMode, + + /*! + Vector graphic primitives are mapped to a QPainterPath + and are painted by drawPath. In PathMode mode + only a few draw methods are called: + + - drawPath() + - drawPixmap() + - drawImage() + */ + PathMode + }; + + QwtNullPaintDevice(); + virtual ~QwtNullPaintDevice(); + + void setMode( Mode ); + Mode mode() const; + + virtual QPaintEngine *paintEngine() const; + + virtual int metric( PaintDeviceMetric metric ) const; + + virtual void drawRects(const QRect *, int ); + virtual void drawRects(const QRectF *, int ); + + virtual void drawLines(const QLine *, int ); + virtual void drawLines(const QLineF *, int ); + + virtual void drawEllipse(const QRectF &); + virtual void drawEllipse(const QRect &); + + virtual void drawPath(const QPainterPath &); + + virtual void drawPoints(const QPointF *, int ); + virtual void drawPoints(const QPoint *, int ); + + virtual void drawPolygon( + const QPointF *, int , QPaintEngine::PolygonDrawMode ); + + virtual void drawPolygon( + const QPoint *, int , QPaintEngine::PolygonDrawMode ); + + virtual void drawPixmap(const QRectF &, + const QPixmap &, const QRectF &); + + virtual void drawTextItem(const QPointF &, const QTextItem &); + + virtual void drawTiledPixmap(const QRectF &, + const QPixmap &, const QPointF &s); + + virtual void drawImage(const QRectF &, + const QImage &, const QRectF &, Qt::ImageConversionFlags ); + + virtual void updateState( const QPaintEngineState &state ); + +protected: + //! \return Size needed to implement metric() + virtual QSize sizeMetrics() const = 0; + +private: + class PaintEngine; + PaintEngine *d_engine; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_painter.cpp b/qwt/src/qwt_painter.cpp new file mode 100644 index 000000000..0bbf258c5 --- /dev/null +++ b/qwt/src/qwt_painter.cpp @@ -0,0 +1,1266 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_painter.h" +#include "qwt_math.h" +#include "qwt_clipper.h" +#include "qwt_color_map.h" +#include "qwt_scale_map.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if QT_VERSION >= 0x050000 +#include +#endif + +#if QT_VERSION < 0x050000 + +#ifdef Q_WS_X11 +#include +#endif + +#endif + +bool QwtPainter::d_polylineSplitting = true; +bool QwtPainter::d_roundingAlignment = true; + +static inline bool qwtIsClippingNeeded( + const QPainter *painter, QRectF &clipRect ) +{ + bool doClipping = false; + const QPaintEngine *pe = painter->paintEngine(); + if ( pe && pe->type() == QPaintEngine::SVG ) + { + // The SVG paint engine ignores any clipping, + + if ( painter->hasClipping() ) + { + doClipping = true; + clipRect = painter->clipRegion().boundingRect(); + } + } + + return doClipping; +} + +template +static inline void qwtDrawPolyline( QPainter *painter, + const T *points, int pointCount, bool polylineSplitting ) +{ + bool doSplit = false; + if ( polylineSplitting ) + { + const QPaintEngine *pe = painter->paintEngine(); + if ( pe && pe->type() == QPaintEngine::Raster ) + { + /* + The raster paint engine seems to use some algo with O(n*n). + ( Qt 4.3 is better than Qt 4.2, but remains unacceptable) + To work around this problem, we have to split the polygon into + smaller pieces. + */ + doSplit = true; + } + } + + if ( doSplit ) + { + const int splitSize = 20; + for ( int i = 0; i < pointCount; i += splitSize ) + { + const int n = qMin( splitSize + 1, pointCount - i ); + painter->drawPolyline( points + i, n ); + } + } + else + painter->drawPolyline( points, pointCount ); +} + +static inline void qwtUnscaleFont( QPainter *painter ) +{ + if ( painter->font().pixelSize() >= 0 ) + return; + + static QSize screenResolution; + if ( !screenResolution.isValid() ) + { + QDesktopWidget *desktop = QApplication::desktop(); + if ( desktop ) + { + screenResolution.setWidth( desktop->logicalDpiX() ); + screenResolution.setHeight( desktop->logicalDpiY() ); + } + } + + const QPaintDevice *pd = painter->device(); + if ( pd->logicalDpiX() != screenResolution.width() || + pd->logicalDpiY() != screenResolution.height() ) + { + QFont pixelFont( painter->font(), QApplication::desktop() ); + pixelFont.setPixelSize( QFontInfo( pixelFont ).pixelSize() ); + + painter->setFont( pixelFont ); + } +} + +/*! + Check is the application is running with the X11 graphics system + that has some special capabilities that can be used for incremental + painting to a widget. + + \return True, when the graphics system is X11 +*/ +bool QwtPainter::isX11GraphicsSystem() +{ + static int onX11 = -1; + if ( onX11 < 0 ) + { + QPixmap pm( 1, 1 ); + QPainter painter( &pm ); + + onX11 = ( painter.paintEngine()->type() == QPaintEngine::X11 ) ? 1 : 0; + } + + return onX11 == 1; +} + +/*! + Check if the painter is using a paint engine, that aligns + coordinates to integers. Today these are all paint engines + beside QPaintEngine::Pdf and QPaintEngine::SVG. + + If we have an integer based paint engine it is also + checked if the painter has a transformation matrix, + that rotates or scales. + + \param painter Painter + \return true, when the painter is aligning + + \sa setRoundingAlignment() +*/ +bool QwtPainter::isAligning( QPainter *painter ) +{ + if ( painter && painter->isActive() ) + { + switch ( painter->paintEngine()->type() ) + { + case QPaintEngine::Pdf: + case QPaintEngine::SVG: + return false; + + default:; + } + + const QTransform tr = painter->transform(); + if ( tr.isRotating() || tr.isScaling() ) + { + // we might have to check translations too + return false; + } + } + + return true; +} + +/*! + Enable whether coordinates should be rounded, before they are painted + to a paint engine that floors to integer values. For other paint engines + this ( PDF, SVG ), this flag has no effect. + QwtPainter stores this flag only, the rounding itself is done in + the painting code ( f.e the plot items ). + + The default setting is true. + + \sa roundingAlignment(), isAligning() +*/ +void QwtPainter::setRoundingAlignment( bool enable ) +{ + d_roundingAlignment = enable; +} + +/*! + \brief En/Disable line splitting for the raster paint engine + + In some Qt versions the raster paint engine paints polylines of many points + much faster when they are split in smaller chunks: f.e all supported Qt versions + >= Qt 5.0 when drawing an antialiased polyline with a pen width >=2. + + The default setting is true. + + \sa polylineSplitting() +*/ +void QwtPainter::setPolylineSplitting( bool enable ) +{ + d_polylineSplitting = enable; +} + +//! Wrapper for QPainter::drawPath() +void QwtPainter::drawPath( QPainter *painter, const QPainterPath &path ) +{ + painter->drawPath( path ); +} + +//! Wrapper for QPainter::drawRect() +void QwtPainter::drawRect( QPainter *painter, double x, double y, double w, double h ) +{ + drawRect( painter, QRectF( x, y, w, h ) ); +} + +//! Wrapper for QPainter::drawRect() +void QwtPainter::drawRect( QPainter *painter, const QRectF &rect ) +{ + const QRectF r = rect; + + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping ) + { + if ( !clipRect.intersects( r ) ) + return; + + if ( !clipRect.contains( r ) ) + { + fillRect( painter, r & clipRect, painter->brush() ); + + painter->save(); + painter->setBrush( Qt::NoBrush ); + drawPolyline( painter, QPolygonF( r ) ); + painter->restore(); + + return; + } + } + + painter->drawRect( r ); +} + +//! Wrapper for QPainter::fillRect() +void QwtPainter::fillRect( QPainter *painter, + const QRectF &rect, const QBrush &brush ) +{ + if ( !rect.isValid() ) + return; + + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + /* + Performance of Qt4 is horrible for a non trivial brush. Without + clipping expect minutes or hours for repainting large rectangles + (might result from zooming) + */ + + if ( deviceClipping ) + clipRect &= painter->window(); + else + clipRect = painter->window(); + + if ( painter->hasClipping() ) + clipRect &= painter->clipRegion().boundingRect(); + + QRectF r = rect; + if ( deviceClipping ) + r = r.intersected( clipRect ); + + if ( r.isValid() ) + painter->fillRect( r, brush ); +} + +//! Wrapper for QPainter::drawPie() +void QwtPainter::drawPie( QPainter *painter, const QRectF &rect, + int a, int alen ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + if ( deviceClipping && !clipRect.contains( rect ) ) + return; + + painter->drawPie( rect, a, alen ); +} + +//! Wrapper for QPainter::drawEllipse() +void QwtPainter::drawEllipse( QPainter *painter, const QRectF &rect ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping && !clipRect.contains( rect ) ) + return; + + painter->drawEllipse( rect ); +} + +//! Wrapper for QPainter::drawText() +void QwtPainter::drawText( QPainter *painter, double x, double y, + const QString &text ) +{ + drawText( painter, QPointF( x, y ), text ); +} + +//! Wrapper for QPainter::drawText() +void QwtPainter::drawText( QPainter *painter, const QPointF &pos, + const QString &text ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping && !clipRect.contains( pos ) ) + return; + + + painter->save(); + qwtUnscaleFont( painter ); + painter->drawText( pos, text ); + painter->restore(); +} + +//! Wrapper for QPainter::drawText() +void QwtPainter::drawText( QPainter *painter, + double x, double y, double w, double h, + int flags, const QString &text ) +{ + drawText( painter, QRectF( x, y, w, h ), flags, text ); +} + +//! Wrapper for QPainter::drawText() +void QwtPainter::drawText( QPainter *painter, const QRectF &rect, + int flags, const QString &text ) +{ + painter->save(); + qwtUnscaleFont( painter ); + painter->drawText( rect, flags, text ); + painter->restore(); +} + +#ifndef QT_NO_RICHTEXT + +/*! + Draw a text document into a rectangle + + \param painter Painter + \param rect Traget rectangle + \param flags Alignments/Text flags, see QPainter::drawText() + \param text Text document +*/ +void QwtPainter::drawSimpleRichText( QPainter *painter, const QRectF &rect, + int flags, const QTextDocument &text ) +{ + QTextDocument *txt = text.clone(); + + painter->save(); + + painter->setFont( txt->defaultFont() ); + qwtUnscaleFont( painter ); + + txt->setDefaultFont( painter->font() ); + txt->setPageSize( QSizeF( rect.width(), QWIDGETSIZE_MAX ) ); + + QAbstractTextDocumentLayout* layout = txt->documentLayout(); + + const double height = layout->documentSize().height(); + double y = rect.y(); + if ( flags & Qt::AlignBottom ) + y += ( rect.height() - height ); + else if ( flags & Qt::AlignVCenter ) + y += ( rect.height() - height ) / 2; + + QAbstractTextDocumentLayout::PaintContext context; + context.palette.setColor( QPalette::Text, painter->pen().color() ); + + painter->translate( rect.x(), y ); + layout->draw( painter, context ); + + painter->restore(); + delete txt; +} + +#endif // !QT_NO_RICHTEXT + + +//! Wrapper for QPainter::drawLine() +void QwtPainter::drawLine( QPainter *painter, + const QPointF &p1, const QPointF &p2 ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping && + !( clipRect.contains( p1 ) && clipRect.contains( p2 ) ) ) + { + QPolygonF polygon; + polygon += p1; + polygon += p2; + drawPolyline( painter, polygon ); + return; + } + + painter->drawLine( p1, p2 ); +} + +//! Wrapper for QPainter::drawPolygon() +void QwtPainter::drawPolygon( QPainter *painter, const QPolygonF &polygon ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + QPolygonF cpa = polygon; + if ( deviceClipping ) + cpa = QwtClipper::clipPolygonF( clipRect, polygon ); + + painter->drawPolygon( cpa ); +} + +//! Wrapper for QPainter::drawPolyline() +void QwtPainter::drawPolyline( QPainter *painter, const QPolygonF &polygon ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + QPolygonF cpa = polygon; + if ( deviceClipping ) + cpa = QwtClipper::clipPolygonF( clipRect, cpa ); + + qwtDrawPolyline( painter, + cpa.constData(), cpa.size(), d_polylineSplitting ); +} + +//! Wrapper for QPainter::drawPolyline() +void QwtPainter::drawPolyline( QPainter *painter, + const QPointF *points, int pointCount ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping ) + { + QPolygonF polygon( pointCount ); + ::memcpy( polygon.data(), points, pointCount * sizeof( QPointF ) ); + + polygon = QwtClipper::clipPolygonF( clipRect, polygon ); + qwtDrawPolyline( painter, + polygon.constData(), polygon.size(), d_polylineSplitting ); + } + else + { + qwtDrawPolyline( painter, points, pointCount, d_polylineSplitting ); + } +} + +//! Wrapper for QPainter::drawPolygon() +void QwtPainter::drawPolygon( QPainter *painter, const QPolygon &polygon ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + QPolygon cpa = polygon; + if ( deviceClipping ) + cpa = QwtClipper::clipPolygon( clipRect, polygon ); + + painter->drawPolygon( cpa ); +} + +//! Wrapper for QPainter::drawPolyline() +void QwtPainter::drawPolyline( QPainter *painter, const QPolygon &polygon ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + QPolygon cpa = polygon; + if ( deviceClipping ) + cpa = QwtClipper::clipPolygon( clipRect, cpa ); + + qwtDrawPolyline( painter, + cpa.constData(), cpa.size(), d_polylineSplitting ); +} + +//! Wrapper for QPainter::drawPolyline() +void QwtPainter::drawPolyline( QPainter *painter, + const QPoint *points, int pointCount ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping ) + { + QPolygon polygon( pointCount ); + ::memcpy( polygon.data(), points, pointCount * sizeof( QPoint ) ); + + polygon = QwtClipper::clipPolygon( clipRect, polygon ); + qwtDrawPolyline( painter, + polygon.constData(), polygon.size(), d_polylineSplitting ); + } + else + qwtDrawPolyline( painter, points, pointCount, d_polylineSplitting ); +} + +//! Wrapper for QPainter::drawPoint() +void QwtPainter::drawPoint( QPainter *painter, const QPointF &pos ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping && !clipRect.contains( pos ) ) + return; + + painter->drawPoint( pos ); +} + +//! Wrapper for QPainter::drawPoint() +void QwtPainter::drawPoint( QPainter *painter, const QPoint &pos ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping ) + { + const int minX = qCeil( clipRect.left() ); + const int maxX = qFloor( clipRect.right() ); + const int minY = qCeil( clipRect.top() ); + const int maxY = qFloor( clipRect.bottom() ); + + if ( pos.x() < minX || pos.x() > maxX + || pos.y() < minY || pos.y() > maxY ) + { + return; + } + } + + painter->drawPoint( pos ); +} + +//! Wrapper for QPainter::drawPoints() +void QwtPainter::drawPoints( QPainter *painter, + const QPoint *points, int pointCount ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping ) + { + const int minX = qCeil( clipRect.left() ); + const int maxX = qFloor( clipRect.right() ); + const int minY = qCeil( clipRect.top() ); + const int maxY = qFloor( clipRect.bottom() ); + + const QRect r( minX, minY, maxX - minX, maxY - minY ); + + QPolygon clippedPolygon( pointCount ); + QPoint *clippedData = clippedPolygon.data(); + + int numClippedPoints = 0; + for ( int i = 0; i < pointCount; i++ ) + { + if ( r.contains( points[i] ) ) + clippedData[ numClippedPoints++ ] = points[i]; + } + painter->drawPoints( clippedData, numClippedPoints ); + } + else + { + painter->drawPoints( points, pointCount ); + } +} + +//! Wrapper for QPainter::drawPoints() +void QwtPainter::drawPoints( QPainter *painter, + const QPointF *points, int pointCount ) +{ + QRectF clipRect; + const bool deviceClipping = qwtIsClippingNeeded( painter, clipRect ); + + if ( deviceClipping ) + { + QPolygonF clippedPolygon( pointCount ); + QPointF *clippedData = clippedPolygon.data(); + + int numClippedPoints = 0; + for ( int i = 0; i < pointCount; i++ ) + { + if ( clipRect.contains( points[i] ) ) + clippedData[ numClippedPoints++ ] = points[i]; + } + painter->drawPoints( clippedData, numClippedPoints ); + } + else + { + painter->drawPoints( points, pointCount ); + } +} + +//! Wrapper for QPainter::drawImage() +void QwtPainter::drawImage( QPainter *painter, + const QRectF &rect, const QImage &image ) +{ + const QRect alignedRect = rect.toAlignedRect(); + + if ( alignedRect != rect ) + { + const QRectF clipRect = rect.adjusted( 0.0, 0.0, -1.0, -1.0 ); + + painter->save(); + painter->setClipRect( clipRect, Qt::IntersectClip ); + painter->drawImage( alignedRect, image ); + painter->restore(); + } + else + { + painter->drawImage( alignedRect, image ); + } +} + +//! Wrapper for QPainter::drawPixmap() +void QwtPainter::drawPixmap( QPainter *painter, + const QRectF &rect, const QPixmap &pixmap ) +{ + const QRect alignedRect = rect.toAlignedRect(); + + if ( alignedRect != rect ) + { + const QRectF clipRect = rect.adjusted( 0.0, 0.0, -1.0, -1.0 ); + + painter->save(); + painter->setClipRect( clipRect, Qt::IntersectClip ); + painter->drawPixmap( alignedRect, pixmap ); + painter->restore(); + } + else + { + painter->drawPixmap( alignedRect, pixmap ); + } +} + +//! Draw a focus rectangle on a widget using its style. +void QwtPainter::drawFocusRect( QPainter *painter, const QWidget *widget ) +{ + drawFocusRect( painter, widget, widget->rect() ); +} + +//! Draw a focus rectangle on a widget using its style. +void QwtPainter::drawFocusRect( QPainter *painter, const QWidget *widget, + const QRect &rect ) +{ + QStyleOptionFocusRect opt; + opt.init( widget ); + opt.rect = rect; + opt.state |= QStyle::State_HasFocus; + + widget->style()->drawPrimitive( QStyle::PE_FrameFocusRect, + &opt, painter, widget ); +} + +/*! + Draw a round frame + + \param painter Painter + \param rect Frame rectangle + \param palette QPalette::WindowText is used for plain borders + QPalette::Dark and QPalette::Light for raised + or sunken borders + \param lineWidth Line width + \param frameStyle bitwise OR´ed value of QFrame::Shape and QFrame::Shadow +*/ +void QwtPainter::drawRoundFrame( QPainter *painter, + const QRectF &rect, const QPalette &palette, + int lineWidth, int frameStyle ) +{ + enum Style + { + Plain, + Sunken, + Raised + }; + + Style style = Plain; + if ( (frameStyle & QFrame::Sunken) == QFrame::Sunken ) + style = Sunken; + else if ( (frameStyle & QFrame::Raised) == QFrame::Raised ) + style = Raised; + + const double lw2 = 0.5 * lineWidth; + QRectF r = rect.adjusted( lw2, lw2, -lw2, -lw2 ); + + QBrush brush; + + if ( style != Plain ) + { + QColor c1 = palette.color( QPalette::Light ); + QColor c2 = palette.color( QPalette::Dark ); + + if ( style == Sunken ) + qSwap( c1, c2 ); + + QLinearGradient gradient( r.topLeft(), r.bottomRight() ); + gradient.setColorAt( 0.0, c1 ); +#if 0 + gradient.setColorAt( 0.3, c1 ); + gradient.setColorAt( 0.7, c2 ); +#endif + gradient.setColorAt( 1.0, c2 ); + + brush = QBrush( gradient ); + } + else // Plain + { + brush = palette.brush( QPalette::WindowText ); + } + + painter->save(); + + painter->setPen( QPen( brush, lineWidth ) ); + painter->setBrush( Qt::NoBrush ); + + painter->drawEllipse( r ); + + painter->restore(); +} + +/*! + Draw a rectangular frame + + \param painter Painter + \param rect Frame rectangle + \param palette Palette + \param foregroundRole Foreground role used for QFrame::Plain + \param frameWidth Frame width + \param midLineWidth Used for QFrame::Box + \param frameStyle bitwise OR´ed value of QFrame::Shape and QFrame::Shadow +*/ +void QwtPainter::drawFrame( QPainter *painter, const QRectF &rect, + const QPalette &palette, QPalette::ColorRole foregroundRole, + int frameWidth, int midLineWidth, int frameStyle ) +{ + if ( frameWidth <= 0 || rect.isEmpty() ) + return; + + const int shadow = frameStyle & QFrame::Shadow_Mask; + + painter->save(); + + if ( shadow == QFrame::Plain ) + { + const QRectF outerRect = rect.adjusted( 0.0, 0.0, -1.0, -1.0 ); + const QRectF innerRect = outerRect.adjusted( + frameWidth, frameWidth, -frameWidth, -frameWidth ); + + QPainterPath path; + path.addRect( outerRect ); + path.addRect( innerRect ); + + painter->setPen( Qt::NoPen ); + painter->setBrush( palette.color( foregroundRole ) ); + + painter->drawPath( path ); + } + else + { + const int shape = frameStyle & QFrame::Shape_Mask; + + if ( shape == QFrame::Box ) + { + const QRectF outerRect = rect.adjusted( 0.0, 0.0, -1.0, -1.0 ); + const QRectF midRect1 = outerRect.adjusted( + frameWidth, frameWidth, -frameWidth, -frameWidth ); + const QRectF midRect2 = midRect1.adjusted( + midLineWidth, midLineWidth, -midLineWidth, -midLineWidth ); + + const QRectF innerRect = midRect2.adjusted( + frameWidth, frameWidth, -frameWidth, -frameWidth ); + + QPainterPath path1; + path1.moveTo( outerRect.bottomLeft() ); + path1.lineTo( outerRect.topLeft() ); + path1.lineTo( outerRect.topRight() ); + path1.lineTo( midRect1.topRight() ); + path1.lineTo( midRect1.topLeft() ); + path1.lineTo( midRect1.bottomLeft() ); + + QPainterPath path2; + path2.moveTo( outerRect.bottomLeft() ); + path2.lineTo( outerRect.bottomRight() ); + path2.lineTo( outerRect.topRight() ); + path2.lineTo( midRect1.topRight() ); + path2.lineTo( midRect1.bottomRight() ); + path2.lineTo( midRect1.bottomLeft() ); + + QPainterPath path3; + path3.moveTo( midRect2.bottomLeft() ); + path3.lineTo( midRect2.topLeft() ); + path3.lineTo( midRect2.topRight() ); + path3.lineTo( innerRect.topRight() ); + path3.lineTo( innerRect.topLeft() ); + path3.lineTo( innerRect.bottomLeft() ); + + QPainterPath path4; + path4.moveTo( midRect2.bottomLeft() ); + path4.lineTo( midRect2.bottomRight() ); + path4.lineTo( midRect2.topRight() ); + path4.lineTo( innerRect.topRight() ); + path4.lineTo( innerRect.bottomRight() ); + path4.lineTo( innerRect.bottomLeft() ); + + QPainterPath path5; + path5.addRect( midRect1 ); + path5.addRect( midRect2 ); + + painter->setPen( Qt::NoPen ); + + QBrush brush1 = palette.dark().color(); + QBrush brush2 = palette.light().color(); + + if ( shadow == QFrame::Raised ) + qSwap( brush1, brush2 ); + + painter->setBrush( brush1 ); + painter->drawPath( path1 ); + painter->drawPath( path4 ); + + painter->setBrush( brush2 ); + painter->drawPath( path2 ); + painter->drawPath( path3 ); + + painter->setBrush( palette.mid() ); + painter->drawPath( path5 ); + } +#if 0 + // qDrawWinPanel doesn't result in something nice + // on a scalable document like PDF. Better draw a + // Panel. + + else if ( shape == QFrame::WinPanel ) + { + painter->setRenderHint( QPainter::NonCosmeticDefaultPen, true ); + qDrawWinPanel ( painter, rect.toRect(), palette, + frameStyle & QFrame::Sunken ); + } + else if ( shape == QFrame::StyledPanel ) + { + } +#endif + else + { + const QRectF outerRect = rect.adjusted( 0.0, 0.0, -1.0, -1.0 ); + const QRectF innerRect = outerRect.adjusted( + frameWidth - 1.0, frameWidth - 1.0, + -( frameWidth - 1.0 ), -( frameWidth - 1.0 ) ); + + QPainterPath path1; + path1.moveTo( outerRect.bottomLeft() ); + path1.lineTo( outerRect.topLeft() ); + path1.lineTo( outerRect.topRight() ); + path1.lineTo( innerRect.topRight() ); + path1.lineTo( innerRect.topLeft() ); + path1.lineTo( innerRect.bottomLeft() ); + + + QPainterPath path2; + path2.moveTo( outerRect.bottomLeft() ); + path2.lineTo( outerRect.bottomRight() ); + path2.lineTo( outerRect.topRight() ); + path2.lineTo( innerRect.topRight() ); + path2.lineTo( innerRect.bottomRight() ); + path2.lineTo( innerRect.bottomLeft() ); + + painter->setPen( Qt::NoPen ); + + QBrush brush1 = palette.dark().color(); + QBrush brush2 = palette.light().color(); + + if ( shadow == QFrame::Raised ) + qSwap( brush1, brush2 ); + + painter->setBrush( brush1 ); + painter->drawPath( path1 ); + + painter->setBrush( brush2 ); + painter->drawPath( path2 ); + } + + } + + painter->restore(); +} + +/*! + Draw a rectangular frame with rounded borders + + \param painter Painter + \param rect Frame rectangle + \param xRadius x-radius of the ellipses defining the corners + \param yRadius y-radius of the ellipses defining the corners + \param palette QPalette::WindowText is used for plain borders + QPalette::Dark and QPalette::Light for raised + or sunken borders + \param lineWidth Line width + \param frameStyle bitwise OR´ed value of QFrame::Shape and QFrame::Shadow +*/ + +void QwtPainter::drawRoundedFrame( QPainter *painter, + const QRectF &rect, double xRadius, double yRadius, + const QPalette &palette, int lineWidth, int frameStyle ) +{ + painter->save(); + painter->setRenderHint( QPainter::Antialiasing, true ); + painter->setBrush( Qt::NoBrush ); + + double lw2 = lineWidth * 0.5; + QRectF r = rect.adjusted( lw2, lw2, -lw2, -lw2 ); + + QPainterPath path; + path.addRoundedRect( r, xRadius, yRadius ); + + enum Style + { + Plain, + Sunken, + Raised + }; + + Style style = Plain; + if ( (frameStyle & QFrame::Sunken) == QFrame::Sunken ) + style = Sunken; + else if ( (frameStyle & QFrame::Raised) == QFrame::Raised ) + style = Raised; + + if ( style != Plain && path.elementCount() == 17 ) + { + // move + 4 * ( cubicTo + lineTo ) + QPainterPath pathList[8]; + + for ( int i = 0; i < 4; i++ ) + { + const int j = i * 4 + 1; + + pathList[ 2 * i ].moveTo( + path.elementAt(j - 1).x, path.elementAt( j - 1 ).y + ); + + pathList[ 2 * i ].cubicTo( + path.elementAt(j + 0).x, path.elementAt(j + 0).y, + path.elementAt(j + 1).x, path.elementAt(j + 1).y, + path.elementAt(j + 2).x, path.elementAt(j + 2).y ); + + pathList[ 2 * i + 1 ].moveTo( + path.elementAt(j + 2).x, path.elementAt(j + 2).y + ); + pathList[ 2 * i + 1 ].lineTo( + path.elementAt(j + 3).x, path.elementAt(j + 3).y + ); + } + + QColor c1( palette.color( QPalette::Dark ) ); + QColor c2( palette.color( QPalette::Light ) ); + + if ( style == Raised ) + qSwap( c1, c2 ); + + for ( int i = 0; i < 4; i++ ) + { + QRectF r = pathList[2 * i].controlPointRect(); + + QPen arcPen; + arcPen.setCapStyle( Qt::FlatCap ); + arcPen.setWidth( lineWidth ); + + QPen linePen; + linePen.setCapStyle( Qt::FlatCap ); + linePen.setWidth( lineWidth ); + + switch( i ) + { + case 0: + { + arcPen.setColor( c1 ); + linePen.setColor( c1 ); + break; + } + case 1: + { + QLinearGradient gradient; + gradient.setStart( r.topLeft() ); + gradient.setFinalStop( r.bottomRight() ); + gradient.setColorAt( 0.0, c1 ); + gradient.setColorAt( 1.0, c2 ); + + arcPen.setBrush( gradient ); + linePen.setColor( c2 ); + break; + } + case 2: + { + arcPen.setColor( c2 ); + linePen.setColor( c2 ); + break; + } + case 3: + { + QLinearGradient gradient; + + gradient.setStart( r.bottomRight() ); + gradient.setFinalStop( r.topLeft() ); + gradient.setColorAt( 0.0, c2 ); + gradient.setColorAt( 1.0, c1 ); + + arcPen.setBrush( gradient ); + linePen.setColor( c1 ); + break; + } + } + + + painter->setPen( arcPen ); + painter->drawPath( pathList[ 2 * i] ); + + painter->setPen( linePen ); + painter->drawPath( pathList[ 2 * i + 1] ); + } + } + else + { + QPen pen( palette.color( QPalette::WindowText ), lineWidth ); + painter->setPen( pen ); + painter->drawPath( path ); + } + + painter->restore(); +} + +/*! + Draw a color bar into a rectangle + + \param painter Painter + \param colorMap Color map + \param interval Value range + \param scaleMap Scale map + \param orientation Orientation + \param rect Traget rectangle +*/ +void QwtPainter::drawColorBar( QPainter *painter, + const QwtColorMap &colorMap, const QwtInterval &interval, + const QwtScaleMap &scaleMap, Qt::Orientation orientation, + const QRectF &rect ) +{ + QVector colorTable; + if ( colorMap.format() == QwtColorMap::Indexed ) + colorTable = colorMap.colorTable( interval ); + + QColor c; + + const QRect devRect = rect.toAlignedRect(); + + /* + We paint to a pixmap first to have something scalable for printing + ( f.e. in a Pdf document ) + */ + + QPixmap pixmap( devRect.size() ); + QPainter pmPainter( &pixmap ); + pmPainter.translate( -devRect.x(), -devRect.y() ); + + if ( orientation == Qt::Horizontal ) + { + QwtScaleMap sMap = scaleMap; + sMap.setPaintInterval( rect.left(), rect.right() ); + + for ( int x = devRect.left(); x <= devRect.right(); x++ ) + { + const double value = sMap.invTransform( x ); + + if ( colorMap.format() == QwtColorMap::RGB ) + c.setRgba( colorMap.rgb( interval, value ) ); + else + c = colorTable[colorMap.colorIndex( interval, value )]; + + pmPainter.setPen( c ); + pmPainter.drawLine( x, devRect.top(), x, devRect.bottom() ); + } + } + else // Vertical + { + QwtScaleMap sMap = scaleMap; + sMap.setPaintInterval( rect.bottom(), rect.top() ); + + for ( int y = devRect.top(); y <= devRect.bottom(); y++ ) + { + const double value = sMap.invTransform( y ); + + if ( colorMap.format() == QwtColorMap::RGB ) + c.setRgb( colorMap.rgb( interval, value ) ); + else + c = colorTable[colorMap.colorIndex( interval, value )]; + + pmPainter.setPen( c ); + pmPainter.drawLine( devRect.left(), y, devRect.right(), y ); + } + } + pmPainter.end(); + + drawPixmap( painter, rect, pixmap ); +} + +static inline void qwtFillRect( const QWidget *widget, QPainter *painter, + const QRect &rect, const QBrush &brush) +{ + if ( brush.style() == Qt::TexturePattern ) + { + painter->save(); + + painter->setClipRect( rect ); + painter->drawTiledPixmap(rect, brush.texture(), rect.topLeft()); + + painter->restore(); + } + else if ( brush.gradient() ) + { + painter->save(); + + painter->setClipRect( rect ); + painter->fillRect(0, 0, widget->width(), + widget->height(), brush); + + painter->restore(); + } + else + { + painter->fillRect(rect, brush); + } +} + +/*! + Fill a pixmap with the content of a widget + + In Qt >= 5.0 QPixmap::fill() is a nop, in Qt 4.x it is buggy + for backgrounds with gradients. Thus fillPixmap() offers + an alternative implementation. + + \param widget Widget + \param pixmap Pixmap to be filled + \param offset Offset + + \sa QPixmap::fill() + */ +void QwtPainter::fillPixmap( const QWidget *widget, + QPixmap &pixmap, const QPoint &offset ) +{ + const QRect rect( offset, pixmap.size() ); + + QPainter painter( &pixmap ); + painter.translate( -offset ); + + const QBrush autoFillBrush = + widget->palette().brush( widget->backgroundRole() ); + + if ( !( widget->autoFillBackground() && autoFillBrush.isOpaque() ) ) + { + const QBrush bg = widget->palette().brush( QPalette::Window ); + qwtFillRect( widget, &painter, rect, bg); + } + + if ( widget->autoFillBackground() ) + qwtFillRect( widget, &painter, rect, autoFillBrush); + + if ( widget->testAttribute(Qt::WA_StyledBackground) ) + { + painter.setClipRegion( rect ); + + QStyleOption opt; + opt.initFrom( widget ); + widget->style()->drawPrimitive( QStyle::PE_Widget, + &opt, &painter, widget ); + } +} + +/*! + Fill rect with the background of a widget + + \param painter Painter + \param rect Rectangle to be filled + \param widget Widget + + \sa QStyle::PE_Widget, QWidget::backgroundRole() + */ +void QwtPainter::drawBackgound( QPainter *painter, + const QRectF &rect, const QWidget *widget ) +{ + if ( widget->testAttribute( Qt::WA_StyledBackground ) ) + { + QStyleOption opt; + opt.initFrom( widget ); + opt.rect = rect.toAlignedRect(); + + widget->style()->drawPrimitive( + QStyle::PE_Widget, &opt, painter, widget); + } + else + { + const QBrush brush = + widget->palette().brush( widget->backgroundRole() ); + + painter->fillRect( rect, brush ); + } +} + +/*! + \return A pixmap that can be used as backing store + + \param widget Widget, for which the backinstore is intended + \param size Size of the pixmap + */ +QPixmap QwtPainter::backingStore( QWidget *widget, const QSize &size ) +{ + QPixmap pm; + +#define QWT_HIGH_DPI 1 + +#if QT_VERSION >= 0x050000 && QWT_HIGH_DPI + qreal pixelRatio = 1.0; + + if ( widget && widget->windowHandle() ) + { + pixelRatio = widget->windowHandle()->devicePixelRatio(); + } + else + { + if ( qApp ) + pixelRatio = qApp->devicePixelRatio(); + } + + pm = QPixmap( size * pixelRatio ); + pm.setDevicePixelRatio( pixelRatio ); +#else + Q_UNUSED( widget ) + pm = QPixmap( size ); +#endif + +#if QT_VERSION < 0x050000 +#ifdef Q_WS_X11 + if ( widget && isX11GraphicsSystem() ) + { + if ( pm.x11Info().screen() != widget->x11Info().screen() ) + pm.x11SetScreen( widget->x11Info().screen() ); + } +#endif +#endif + + return pm; +} + diff --git a/qwt/src/qwt_painter.h b/qwt/src/qwt_painter.h new file mode 100644 index 000000000..9609b6935 --- /dev/null +++ b/qwt/src/qwt_painter.h @@ -0,0 +1,188 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PAINTER_H +#define QWT_PAINTER_H + +#include "qwt_global.h" + +#include +#include +#include +#include +#include + +class QPainter; +class QBrush; +class QColor; +class QWidget; +class QPolygonF; +class QRectF; +class QImage; +class QPixmap; +class QwtScaleMap; +class QwtColorMap; +class QwtInterval; + +class QTextDocument; +class QPainterPath; + +/*! + \brief A collection of QPainter workarounds +*/ +class QWT_EXPORT QwtPainter +{ +public: + static void setPolylineSplitting( bool ); + static bool polylineSplitting(); + + static void setRoundingAlignment( bool ); + static bool roundingAlignment(); + static bool roundingAlignment(QPainter *); + + static void drawText( QPainter *, double x, double y, const QString & ); + static void drawText( QPainter *, const QPointF &, const QString & ); + static void drawText( QPainter *, double x, double y, double w, double h, + int flags, const QString & ); + static void drawText( QPainter *, const QRectF &, + int flags, const QString & ); + +#ifndef QT_NO_RICHTEXT + static void drawSimpleRichText( QPainter *, const QRectF &, + int flags, const QTextDocument & ); +#endif + + static void drawRect( QPainter *, double x, double y, double w, double h ); + static void drawRect( QPainter *, const QRectF &rect ); + static void fillRect( QPainter *, const QRectF &, const QBrush & ); + + static void drawEllipse( QPainter *, const QRectF & ); + static void drawPie( QPainter *, const QRectF & r, int a, int alen ); + + static void drawLine( QPainter *, double x1, double y1, double x2, double y2 ); + static void drawLine( QPainter *, const QPointF &p1, const QPointF &p2 ); + static void drawLine( QPainter *, const QLineF & ); + + static void drawPolygon( QPainter *, const QPolygonF & ); + static void drawPolyline( QPainter *, const QPolygonF & ); + static void drawPolyline( QPainter *, const QPointF *, int pointCount ); + + static void drawPolygon( QPainter *, const QPolygon & ); + static void drawPolyline( QPainter *, const QPolygon & ); + static void drawPolyline( QPainter *, const QPoint *, int pointCount ); + + static void drawPoint( QPainter *, const QPoint & ); + static void drawPoints( QPainter *, const QPolygon & ); + static void drawPoints( QPainter *, const QPoint *, int pointCount ); + + static void drawPoint( QPainter *, double x, double y ); + static void drawPoint( QPainter *, const QPointF & ); + static void drawPoints( QPainter *, const QPolygonF & ); + static void drawPoints( QPainter *, const QPointF *, int pointCount ); + + static void drawPath( QPainter *, const QPainterPath & ); + static void drawImage( QPainter *, const QRectF &, const QImage & ); + static void drawPixmap( QPainter *, const QRectF &, const QPixmap & ); + + static void drawRoundFrame( QPainter *, + const QRectF &, const QPalette &, int lineWidth, int frameStyle ); + + static void drawRoundedFrame( QPainter *, + const QRectF &, double xRadius, double yRadius, + const QPalette &, int lineWidth, int frameStyle ); + + static void drawFrame( QPainter *, const QRectF &rect, + const QPalette &palette, QPalette::ColorRole foregroundRole, + int lineWidth, int midLineWidth, int frameStyle ); + + static void drawFocusRect( QPainter *, const QWidget * ); + static void drawFocusRect( QPainter *, const QWidget *, const QRect & ); + + static void drawColorBar( QPainter *painter, + const QwtColorMap &, const QwtInterval &, + const QwtScaleMap &, Qt::Orientation, const QRectF & ); + + static bool isAligning( QPainter *painter ); + static bool isX11GraphicsSystem(); + + static void fillPixmap( const QWidget *, + QPixmap &, const QPoint &offset = QPoint() ); + + static void drawBackgound( QPainter *painter, + const QRectF &rect, const QWidget *widget ); + + static QPixmap backingStore( QWidget *, const QSize & ); + +private: + static bool d_polylineSplitting; + static bool d_roundingAlignment; +}; + +//! Wrapper for QPainter::drawPoint() +inline void QwtPainter::drawPoint( QPainter *painter, double x, double y ) +{ + QwtPainter::drawPoint( painter, QPointF( x, y ) ); +} + +//! Wrapper for QPainter::drawPoints() +inline void QwtPainter::drawPoints( QPainter *painter, const QPolygon &polygon ) +{ + drawPoints( painter, polygon.data(), polygon.size() ); +} + +//! Wrapper for QPainter::drawPoints() +inline void QwtPainter::drawPoints( QPainter *painter, const QPolygonF &polygon ) +{ + drawPoints( painter, polygon.data(), polygon.size() ); +} + +//! Wrapper for QPainter::drawLine() +inline void QwtPainter::drawLine( QPainter *painter, + double x1, double y1, double x2, double y2 ) +{ + QwtPainter::drawLine( painter, QPointF( x1, y1 ), QPointF( x2, y2 ) ); +} + +//! Wrapper for QPainter::drawLine() +inline void QwtPainter::drawLine( QPainter *painter, const QLineF &line ) +{ + QwtPainter::drawLine( painter, line.p1(), line.p2() ); +} + +/*! + \return True, when line splitting for the raster paint engine is enabled. + \sa setPolylineSplitting() +*/ +inline bool QwtPainter::polylineSplitting() +{ + return d_polylineSplitting; +} + +/*! + Check whether coordinates should be rounded, before they are painted + to a paint engine that rounds to integer values. For other paint engines + ( PDF, SVG ), this flag has no effect. + + \return True, when rounding is enabled + \sa setRoundingAlignment(), isAligning() +*/ +inline bool QwtPainter::roundingAlignment() +{ + return d_roundingAlignment; +} + +/*! + \return roundingAlignment() && isAligning(painter); + \param painter Painter +*/ +inline bool QwtPainter::roundingAlignment(QPainter *painter) +{ + return d_roundingAlignment && isAligning(painter); +} +#endif diff --git a/qwt/src/qwt_painter_command.cpp b/qwt/src/qwt_painter_command.cpp new file mode 100644 index 000000000..f6affae37 --- /dev/null +++ b/qwt/src/qwt_painter_command.cpp @@ -0,0 +1,237 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_painter_command.h" + +//! Construct an invalid command +QwtPainterCommand::QwtPainterCommand(): + d_type( Invalid ) +{ +} + +//! Copy constructor +QwtPainterCommand::QwtPainterCommand( const QPainterPath &path ): + d_type( Path ) +{ + d_path = new QPainterPath( path ); +} + +/*! + Constructor for Pixmap paint operation + + \param rect Target rectangle + \param pixmap Pixmap + \param subRect Rectangle inside the pixmap + + \sa QPainter::drawPixmap() + */ +QwtPainterCommand::QwtPainterCommand( const QRectF &rect, + const QPixmap &pixmap, const QRectF& subRect ): + d_type( Pixmap ) +{ + d_pixmapData = new PixmapData(); + d_pixmapData->rect = rect; + d_pixmapData->pixmap = pixmap; + d_pixmapData->subRect = subRect; +} + +/*! + Constructor for Image paint operation + + \param rect Target rectangle + \param image Image + \param subRect Rectangle inside the image + \param flags Conversion flags + + \sa QPainter::drawImage() + */ +QwtPainterCommand::QwtPainterCommand( const QRectF &rect, + const QImage &image, const QRectF& subRect, + Qt::ImageConversionFlags flags ): + d_type( Image ) +{ + d_imageData = new ImageData(); + d_imageData->rect = rect; + d_imageData->image = image; + d_imageData->subRect = subRect; + d_imageData->flags = flags; +} + +/*! + Constructor for State paint operation + \param state Paint engine state + */ +QwtPainterCommand::QwtPainterCommand( const QPaintEngineState &state ): + d_type( State ) +{ + d_stateData = new StateData(); + + d_stateData->flags = state.state(); + + if ( d_stateData->flags & QPaintEngine::DirtyPen ) + d_stateData->pen = state.pen(); + + if ( d_stateData->flags & QPaintEngine::DirtyBrush ) + d_stateData->brush = state.brush(); + + if ( d_stateData->flags & QPaintEngine::DirtyBrushOrigin ) + d_stateData->brushOrigin = state.brushOrigin(); + + if ( d_stateData->flags & QPaintEngine::DirtyFont ) + d_stateData->font = state.font(); + + if ( d_stateData->flags & QPaintEngine::DirtyBackground ) + { + d_stateData->backgroundMode = state.backgroundMode(); + d_stateData->backgroundBrush = state.backgroundBrush(); + } + + if ( d_stateData->flags & QPaintEngine::DirtyTransform ) + d_stateData->transform = state.transform(); + + if ( d_stateData->flags & QPaintEngine::DirtyClipEnabled ) + d_stateData->isClipEnabled = state.isClipEnabled(); + + if ( d_stateData->flags & QPaintEngine::DirtyClipRegion ) + { + d_stateData->clipRegion = state.clipRegion(); + d_stateData->clipOperation = state.clipOperation(); + } + + if ( d_stateData->flags & QPaintEngine::DirtyClipPath ) + { + d_stateData->clipPath = state.clipPath(); + d_stateData->clipOperation = state.clipOperation(); + } + + if ( d_stateData->flags & QPaintEngine::DirtyHints ) + d_stateData->renderHints = state.renderHints(); + + if ( d_stateData->flags & QPaintEngine::DirtyCompositionMode ) + d_stateData->compositionMode = state.compositionMode(); + + if ( d_stateData->flags & QPaintEngine::DirtyOpacity ) + d_stateData->opacity = state.opacity(); +} + +/*! + Copy constructor + \param other Command to be copied + + */ +QwtPainterCommand::QwtPainterCommand(const QwtPainterCommand &other) +{ + copy( other ); +} + +//! Destructor +QwtPainterCommand::~QwtPainterCommand() +{ + reset(); +} + +/*! + Assignment operator + + \param other Command to be copied + \return Modified command + */ +QwtPainterCommand &QwtPainterCommand::operator=(const QwtPainterCommand &other) +{ + reset(); + copy( other ); + + return *this; +} + +void QwtPainterCommand::copy( const QwtPainterCommand &other ) +{ + d_type = other.d_type; + + switch( other.d_type ) + { + case Path: + { + d_path = new QPainterPath( *other.d_path ); + break; + } + case Pixmap: + { + d_pixmapData = new PixmapData( *other.d_pixmapData ); + break; + } + case Image: + { + d_imageData = new ImageData( *other.d_imageData ); + break; + } + case State: + { + d_stateData = new StateData( *other.d_stateData ); + break; + } + default: + break; + } +} + +void QwtPainterCommand::reset() +{ + switch( d_type ) + { + case Path: + { + delete d_path; + break; + } + case Pixmap: + { + delete d_pixmapData; + break; + } + case Image: + { + delete d_imageData; + break; + } + case State: + { + delete d_stateData; + break; + } + default: + break; + } + + d_type = Invalid; +} + +//! \return Painter path to be painted +QPainterPath *QwtPainterCommand::path() +{ + return d_path; +} + +//! \return Attributes how to paint a QPixmap +QwtPainterCommand::PixmapData* QwtPainterCommand::pixmapData() +{ + return d_pixmapData; +} + +//! \return Attributes how to paint a QImage +QwtPainterCommand::ImageData* QwtPainterCommand::imageData() +{ + return d_imageData; +} + +//! \return Attributes of a state change +QwtPainterCommand::StateData* QwtPainterCommand::stateData() +{ + return d_stateData; +} diff --git a/qwt/src/qwt_painter_command.h b/qwt/src/qwt_painter_command.h new file mode 100644 index 000000000..2da597a7f --- /dev/null +++ b/qwt/src/qwt_painter_command.h @@ -0,0 +1,173 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PAINTER_COMMAND_H +#define QWT_PAINTER_COMMAND_H + +#include "qwt_global.h" +#include +#include +#include +#include + +class QPainterPath; + +/*! + QwtPainterCommand represents the attributes of a paint operation + how it is used between QPainter and QPaintDevice + + It is used by QwtGraphic to record and replay paint operations + + \sa QwtGraphic::commands() + */ + +class QWT_EXPORT QwtPainterCommand +{ +public: + //! Type of the paint command + enum Type + { + //! Invalid command + Invalid = -1, + + //! Draw a QPainterPath + Path, + + //! Draw a QPixmap + Pixmap, + + //! Draw a QImage + Image, + + //! QPainter state change + State + }; + + //! Attributes how to paint a QPixmap + struct PixmapData + { + QRectF rect; + QPixmap pixmap; + QRectF subRect; + }; + + //! Attributes how to paint a QImage + struct ImageData + { + QRectF rect; + QImage image; + QRectF subRect; + Qt::ImageConversionFlags flags; + }; + + //! Attributes of a state change + struct StateData + { + QPaintEngine::DirtyFlags flags; + + QPen pen; + QBrush brush; + QPointF brushOrigin; + QBrush backgroundBrush; + Qt::BGMode backgroundMode; + QFont font; + QMatrix matrix; + QTransform transform; + + Qt::ClipOperation clipOperation; + QRegion clipRegion; + QPainterPath clipPath; + bool isClipEnabled; + + QPainter::RenderHints renderHints; + QPainter::CompositionMode compositionMode; + qreal opacity; + }; + + QwtPainterCommand(); + QwtPainterCommand(const QwtPainterCommand &); + + QwtPainterCommand( const QPainterPath & ); + + QwtPainterCommand( const QRectF &rect, + const QPixmap &, const QRectF& subRect ); + + QwtPainterCommand( const QRectF &rect, + const QImage &, const QRectF& subRect, + Qt::ImageConversionFlags ); + + QwtPainterCommand( const QPaintEngineState & ); + + ~QwtPainterCommand(); + + QwtPainterCommand &operator=(const QwtPainterCommand & ); + + Type type() const; + + QPainterPath *path(); + const QPainterPath *path() const; + + PixmapData* pixmapData(); + const PixmapData* pixmapData() const; + + ImageData* imageData(); + const ImageData* imageData() const; + + StateData* stateData(); + const StateData* stateData() const; + +private: + void copy( const QwtPainterCommand & ); + void reset(); + + Type d_type; + + union + { + QPainterPath *d_path; + PixmapData *d_pixmapData; + ImageData *d_imageData; + StateData *d_stateData; + }; +}; + +//! \return Type of the command +inline QwtPainterCommand::Type QwtPainterCommand::type() const +{ + return d_type; +} + +//! \return Painter path to be painted +inline const QPainterPath *QwtPainterCommand::path() const +{ + return d_path; +} + +//! \return Attributes how to paint a QPixmap +inline const QwtPainterCommand::PixmapData* +QwtPainterCommand::pixmapData() const +{ + return d_pixmapData; +} + +//! \return Attributes how to paint a QImage +inline const QwtPainterCommand::ImageData * +QwtPainterCommand::imageData() const +{ + return d_imageData; +} + +//! \return Attributes of a state change +inline const QwtPainterCommand::StateData * +QwtPainterCommand::stateData() const +{ + return d_stateData; +} + +#endif diff --git a/qwt/src/qwt_panner.cpp b/qwt/src/qwt_panner.cpp new file mode 100644 index 000000000..18497a916 --- /dev/null +++ b/qwt/src/qwt_panner.cpp @@ -0,0 +1,538 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_panner.h" +#include "qwt_picker.h" +#include "qwt_painter.h" +#include +#include +#include +#include +#include + +static QVector qwtActivePickers( QWidget *w ) +{ + QVector pickers; + + QObjectList children = w->children(); + for ( int i = 0; i < children.size(); i++ ) + { + QwtPicker *picker = qobject_cast( children[i] ); + if ( picker && picker->isEnabled() ) + pickers += picker; + } + + return pickers; +} + +class QwtPanner::PrivateData +{ +public: + PrivateData(): + button( Qt::LeftButton ), + buttonModifiers( Qt::NoModifier ), + abortKey( Qt::Key_Escape ), + abortKeyModifiers( Qt::NoModifier ), +#ifndef QT_NO_CURSOR + cursor( NULL ), + restoreCursor( NULL ), + hasCursor( false ), +#endif + isEnabled( false ) + { + orientations = Qt::Vertical | Qt::Horizontal; + } + + ~PrivateData() + { +#ifndef QT_NO_CURSOR + delete cursor; + delete restoreCursor; +#endif + } + + Qt::MouseButton button; + Qt::KeyboardModifiers buttonModifiers; + + int abortKey; + Qt::KeyboardModifiers abortKeyModifiers; + + QPoint initialPos; + QPoint pos; + + QPixmap pixmap; + QBitmap contentsMask; + +#ifndef QT_NO_CURSOR + QCursor *cursor; + QCursor *restoreCursor; + bool hasCursor; +#endif + bool isEnabled; + Qt::Orientations orientations; +}; + +/*! + Creates an panner that is enabled for the left mouse button. + + \param parent Parent widget to be panned +*/ +QwtPanner::QwtPanner( QWidget *parent ): + QWidget( parent ) +{ + d_data = new PrivateData(); + + setAttribute( Qt::WA_TransparentForMouseEvents ); + setAttribute( Qt::WA_NoSystemBackground ); + setFocusPolicy( Qt::NoFocus ); + hide(); + + setEnabled( true ); +} + +//! Destructor +QwtPanner::~QwtPanner() +{ + delete d_data; +} + +/*! + Change the mouse button and modifiers used for panning + The defaults are Qt::LeftButton and Qt::NoModifier +*/ +void QwtPanner::setMouseButton( Qt::MouseButton button, + Qt::KeyboardModifiers modifiers ) +{ + d_data->button = button; + d_data->buttonModifiers = modifiers; +} + +//! Get mouse button and modifiers used for panning +void QwtPanner::getMouseButton( Qt::MouseButton &button, + Qt::KeyboardModifiers &modifiers ) const +{ + button = d_data->button; + modifiers = d_data->buttonModifiers; +} + +/*! + Change the abort key + The defaults are Qt::Key_Escape and Qt::NoModifiers + + \param key Key ( See Qt::Keycode ) + \param modifiers Keyboard modifiers +*/ +void QwtPanner::setAbortKey( int key, + Qt::KeyboardModifiers modifiers ) +{ + d_data->abortKey = key; + d_data->abortKeyModifiers = modifiers; +} + +//! Get the abort key and modifiers +void QwtPanner::getAbortKey( int &key, + Qt::KeyboardModifiers &modifiers ) const +{ + key = d_data->abortKey; + modifiers = d_data->abortKeyModifiers; +} + +/*! + Change the cursor, that is active while panning + The default is the cursor of the parent widget. + + \param cursor New cursor + + \sa setCursor() +*/ +#ifndef QT_NO_CURSOR +void QwtPanner::setCursor( const QCursor &cursor ) +{ + d_data->cursor = new QCursor( cursor ); +} +#endif + +/*! + \return Cursor that is active while panning + \sa setCursor() +*/ +#ifndef QT_NO_CURSOR +const QCursor QwtPanner::cursor() const +{ + if ( d_data->cursor ) + return *d_data->cursor; + + if ( parentWidget() ) + return parentWidget()->cursor(); + + return QCursor(); +} +#endif + +/*! + \brief En/disable the panner + + When enabled is true an event filter is installed for + the observed widget, otherwise the event filter is removed. + + \param on true or false + \sa isEnabled(), eventFilter() +*/ +void QwtPanner::setEnabled( bool on ) +{ + if ( d_data->isEnabled != on ) + { + d_data->isEnabled = on; + + QWidget *w = parentWidget(); + if ( w ) + { + if ( d_data->isEnabled ) + { + w->installEventFilter( this ); + } + else + { + w->removeEventFilter( this ); + hide(); + } + } + } +} + +/*! + Set the orientations, where panning is enabled + The default value is in both directions: Qt::Horizontal | Qt::Vertical + + /param o Orientation +*/ +void QwtPanner::setOrientations( Qt::Orientations o ) +{ + d_data->orientations = o; +} + +//! Return the orientation, where paning is enabled +Qt::Orientations QwtPanner::orientations() const +{ + return d_data->orientations; +} + +/*! + \return True if an orientation is enabled + \sa orientations(), setOrientations() +*/ +bool QwtPanner::isOrientationEnabled( Qt::Orientation o ) const +{ + return d_data->orientations & o; +} + +/*! + \return true when enabled, false otherwise + \sa setEnabled, eventFilter() +*/ +bool QwtPanner::isEnabled() const +{ + return d_data->isEnabled; +} + +/*! + \brief Paint event + + Repaint the grabbed pixmap on its current position and + fill the empty spaces by the background of the parent widget. + + \param pe Paint event +*/ +void QwtPanner::paintEvent( QPaintEvent *pe ) +{ + int dx = d_data->pos.x() - d_data->initialPos.x(); + int dy = d_data->pos.y() - d_data->initialPos.y(); + + QRect r( 0, 0, d_data->pixmap.width(), d_data->pixmap.height() ); + r.moveCenter( QPoint( r.center().x() + dx, r.center().y() + dy ) ); + + QPixmap pm( size() ); + QwtPainter::fillPixmap( parentWidget(), pm ); + + QPainter painter( &pm ); + + if ( !d_data->contentsMask.isNull() ) + { + QPixmap masked = d_data->pixmap; + masked.setMask( d_data->contentsMask ); + painter.drawPixmap( r, masked ); + } + else + { + painter.drawPixmap( r, d_data->pixmap ); + } + + painter.end(); + + if ( !d_data->contentsMask.isNull() ) + pm.setMask( d_data->contentsMask ); + + painter.begin( this ); + painter.setClipRegion( pe->region() ); + painter.drawPixmap( 0, 0, pm ); +} + +/*! + \brief Calculate a mask for the contents of the panned widget + + Sometimes only parts of the contents of a widget should be + panned. F.e. for a widget with a styled background with rounded borders + only the area inside of the border should be panned. + + \return An empty bitmap, indicating no mask +*/ +QBitmap QwtPanner::contentsMask() const +{ + return QBitmap(); +} + +/*! + Grab the widget into a pixmap. + \return Grabbed pixmap +*/ +QPixmap QwtPanner::grab() const +{ +#if QT_VERSION >= 0x050000 + return parentWidget()->grab( parentWidget()->rect() ); +#else + return QPixmap::grabWidget( parentWidget() ); +#endif +} + +/*! + \brief Event filter + + When isEnabled() is true mouse events of the + observed widget are filtered. + + \param object Object to be filtered + \param event Event + + \return Always false, beside for paint events for the + parent widget. + + \sa widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseMoveEvent() +*/ +bool QwtPanner::eventFilter( QObject *object, QEvent *event ) +{ + if ( object == NULL || object != parentWidget() ) + return false; + + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + widgetMousePressEvent( static_cast( event ) ); + break; + } + case QEvent::MouseMove: + { + widgetMouseMoveEvent( static_cast( event ) ); + break; + } + case QEvent::MouseButtonRelease: + { + widgetMouseReleaseEvent( static_cast( event ) ); + break; + } + case QEvent::KeyPress: + { + widgetKeyPressEvent( static_cast( event ) ); + break; + } + case QEvent::KeyRelease: + { + widgetKeyReleaseEvent( static_cast( event ) ); + break; + } + case QEvent::Paint: + { + if ( isVisible() ) + return true; + break; + } + default:; + } + + return false; +} + +/*! + Handle a mouse press event for the observed widget. + + \param mouseEvent Mouse event + \sa eventFilter(), widgetMouseReleaseEvent(), + widgetMouseMoveEvent(), +*/ +void QwtPanner::widgetMousePressEvent( QMouseEvent *mouseEvent ) +{ + if ( ( mouseEvent->button() != d_data->button ) + || ( mouseEvent->modifiers() != d_data->buttonModifiers ) ) + { + return; + } + + QWidget *w = parentWidget(); + if ( w == NULL ) + return; + +#ifndef QT_NO_CURSOR + showCursor( true ); +#endif + + d_data->initialPos = d_data->pos = mouseEvent->pos(); + + setGeometry( parentWidget()->rect() ); + + // We don't want to grab the picker ! + QVector pickers = qwtActivePickers( parentWidget() ); + for ( int i = 0; i < pickers.size(); i++ ) + pickers[i]->setEnabled( false ); + + d_data->pixmap = grab(); + d_data->contentsMask = contentsMask(); + + for ( int i = 0; i < pickers.size(); i++ ) + pickers[i]->setEnabled( true ); + + show(); +} + +/*! + Handle a mouse move event for the observed widget. + + \param mouseEvent Mouse event + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent() +*/ +void QwtPanner::widgetMouseMoveEvent( QMouseEvent *mouseEvent ) +{ + if ( !isVisible() ) + return; + + QPoint pos = mouseEvent->pos(); + if ( !isOrientationEnabled( Qt::Horizontal ) ) + pos.setX( d_data->initialPos.x() ); + if ( !isOrientationEnabled( Qt::Vertical ) ) + pos.setY( d_data->initialPos.y() ); + + if ( pos != d_data->pos && rect().contains( pos ) ) + { + d_data->pos = pos; + update(); + + Q_EMIT moved( d_data->pos.x() - d_data->initialPos.x(), + d_data->pos.y() - d_data->initialPos.y() ); + } +} + +/*! + Handle a mouse release event for the observed widget. + + \param mouseEvent Mouse event + \sa eventFilter(), widgetMousePressEvent(), + widgetMouseMoveEvent(), +*/ +void QwtPanner::widgetMouseReleaseEvent( QMouseEvent *mouseEvent ) +{ + if ( isVisible() ) + { + hide(); +#ifndef QT_NO_CURSOR + showCursor( false ); +#endif + + QPoint pos = mouseEvent->pos(); + if ( !isOrientationEnabled( Qt::Horizontal ) ) + pos.setX( d_data->initialPos.x() ); + if ( !isOrientationEnabled( Qt::Vertical ) ) + pos.setY( d_data->initialPos.y() ); + + d_data->pixmap = QPixmap(); + d_data->contentsMask = QBitmap(); + d_data->pos = pos; + + if ( d_data->pos != d_data->initialPos ) + { + Q_EMIT panned( d_data->pos.x() - d_data->initialPos.x(), + d_data->pos.y() - d_data->initialPos.y() ); + } + } +} + +/*! + Handle a key press event for the observed widget. + + \param keyEvent Key event + \sa eventFilter(), widgetKeyReleaseEvent() +*/ +void QwtPanner::widgetKeyPressEvent( QKeyEvent *keyEvent ) +{ + if ( ( keyEvent->key() == d_data->abortKey ) + && ( keyEvent->modifiers() == d_data->abortKeyModifiers ) ) + { + hide(); + +#ifndef QT_NO_CURSOR + showCursor( false ); +#endif + d_data->pixmap = QPixmap(); + } +} + +/*! + Handle a key release event for the observed widget. + + \param keyEvent Key event + \sa eventFilter(), widgetKeyReleaseEvent() +*/ +void QwtPanner::widgetKeyReleaseEvent( QKeyEvent *keyEvent ) +{ + Q_UNUSED( keyEvent ); +} + +#ifndef QT_NO_CURSOR +void QwtPanner::showCursor( bool on ) +{ + if ( on == d_data->hasCursor ) + return; + + QWidget *w = parentWidget(); + if ( w == NULL || d_data->cursor == NULL ) + return; + + d_data->hasCursor = on; + + if ( on ) + { + if ( w->testAttribute( Qt::WA_SetCursor ) ) + { + delete d_data->restoreCursor; + d_data->restoreCursor = new QCursor( w->cursor() ); + } + w->setCursor( *d_data->cursor ); + } + else + { + if ( d_data->restoreCursor ) + { + w->setCursor( *d_data->restoreCursor ); + delete d_data->restoreCursor; + d_data->restoreCursor = NULL; + } + else + w->unsetCursor(); + } +} +#endif diff --git a/qwt/src/qwt_panner.h b/qwt/src/qwt_panner.h new file mode 100644 index 000000000..a0c6873ab --- /dev/null +++ b/qwt/src/qwt_panner.h @@ -0,0 +1,103 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PANNER_H +#define QWT_PANNER_H 1 + +#include "qwt_global.h" +#include +#include + +class QCursor; + +/*! + \brief QwtPanner provides panning of a widget + + QwtPanner grabs the contents of a widget, that can be dragged + in all directions. The offset between the start and the end position + is emitted by the panned signal. + + QwtPanner grabs the content of the widget into a pixmap and moves + the pixmap around, without initiating any repaint events for the widget. + Areas, that are not part of content are not painted while panning. + This makes panning fast enough for widgets, where + repaints are too slow for mouse movements. + + For widgets, where repaints are very fast it might be better to + implement panning manually by mapping mouse events into paint events. +*/ +class QWT_EXPORT QwtPanner: public QWidget +{ + Q_OBJECT + +public: + QwtPanner( QWidget* parent ); + virtual ~QwtPanner(); + + void setEnabled( bool ); + bool isEnabled() const; + + void setMouseButton( Qt::MouseButton, + Qt::KeyboardModifiers = Qt::NoModifier ); + void getMouseButton( Qt::MouseButton &button, + Qt::KeyboardModifiers & ) const; + + void setAbortKey( int key, Qt::KeyboardModifiers = Qt::NoModifier ); + void getAbortKey( int &key, Qt::KeyboardModifiers & ) const; + + void setCursor( const QCursor & ); + const QCursor cursor() const; + + void setOrientations( Qt::Orientations ); + Qt::Orientations orientations() const; + + bool isOrientationEnabled( Qt::Orientation ) const; + + virtual bool eventFilter( QObject *, QEvent * ); + +Q_SIGNALS: + /*! + Signal emitted, when panning is done + + \param dx Offset in horizontal direction + \param dy Offset in vertical direction + */ + void panned( int dx, int dy ); + + /*! + Signal emitted, while the widget moved, but panning + is not finished. + + \param dx Offset in horizontal direction + \param dy Offset in vertical direction + */ + void moved( int dx, int dy ); + +protected: + virtual void widgetMousePressEvent( QMouseEvent * ); + virtual void widgetMouseReleaseEvent( QMouseEvent * ); + virtual void widgetMouseMoveEvent( QMouseEvent * ); + virtual void widgetKeyPressEvent( QKeyEvent * ); + virtual void widgetKeyReleaseEvent( QKeyEvent * ); + + virtual void paintEvent( QPaintEvent * ); + + virtual QBitmap contentsMask() const; + virtual QPixmap grab() const; + +private: +#ifndef QT_NO_CURSOR + void showCursor( bool ); +#endif + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_picker.cpp b/qwt/src/qwt_picker.cpp new file mode 100644 index 000000000..335ddf0cd --- /dev/null +++ b/qwt/src/qwt_picker.cpp @@ -0,0 +1,1577 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_picker.h" +#include "qwt_picker_machine.h" +#include "qwt_painter.h" +#include "qwt_math.h" +#include "qwt_widget_overlay.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static inline QRegion qwtMaskRegion( const QRect &r, int penWidth ) +{ + const int pw = qMax( penWidth, 1 ); + const int pw2 = penWidth / 2; + + int x1 = r.left() - pw2; + int x2 = r.right() + 1 + pw2 + ( pw % 2 ); + + int y1 = r.top() - pw2; + int y2 = r.bottom() + 1 + pw2 + ( pw % 2 ); + + QRegion region; + + region += QRect( x1, y1, x2 - x1, pw ); + region += QRect( x1, y1, pw, y2 - y1 ); + region += QRect( x1, y2 - pw, x2 - x1, pw ); + region += QRect( x2 - pw, y1, pw, y2 - y1 ); + + return region; +} + +static inline QRegion qwtMaskRegion( const QLine &l, int penWidth ) +{ + const int pw = qMax( penWidth, 1 ); + const int pw2 = penWidth / 2; + + QRegion region; + + if ( l.x1() == l.x2() ) + { + region += QRect( l.x1() - pw2, l.y1(), + pw, l.y2() ).normalized(); + } + else if ( l.y1() == l.y2() ) + { + region += QRect( l.x1(), l.y1() - pw2, + l.x2(), pw ).normalized(); + } + + return region; +} + +class QwtPickerRubberband: public QwtWidgetOverlay +{ +public: + QwtPickerRubberband( QwtPicker *, QWidget * ); + +protected: + virtual void drawOverlay( QPainter * ) const; + virtual QRegion maskHint() const; + + QwtPicker *d_picker; +}; + +class QwtPickerTracker: public QwtWidgetOverlay +{ +public: + QwtPickerTracker( QwtPicker *, QWidget * ); + +protected: + virtual void drawOverlay( QPainter * ) const; + virtual QRegion maskHint() const; + + QwtPicker *d_picker; +}; + + +class QwtPicker::PrivateData +{ +public: + PrivateData(): + enabled( false ), + stateMachine( NULL ), + resizeMode( QwtPicker::Stretch ), + rubberBand( QwtPicker::NoRubberBand ), + trackerMode( QwtPicker::AlwaysOff ), + isActive( false ), + trackerPosition( -1, -1 ), + mouseTracking( false ), + openGL( false ) + { + } + + bool enabled; + + QwtPickerMachine *stateMachine; + + QwtPicker::ResizeMode resizeMode; + + QwtPicker::RubberBand rubberBand; + QPen rubberBandPen; + + QwtPicker::DisplayMode trackerMode; + QPen trackerPen; + QFont trackerFont; + + QPolygon pickedPoints; + bool isActive; + QPoint trackerPosition; + + bool mouseTracking; // used to save previous value + + QPointer< QwtPickerRubberband > rubberBandOverlay; + QPointer< QwtPickerTracker> trackerOverlay; + + bool openGL; +}; + +QwtPickerRubberband::QwtPickerRubberband( + QwtPicker *picker, QWidget *parent ): + QwtWidgetOverlay( parent ), + d_picker( picker ) +{ + setMaskMode( QwtWidgetOverlay::MaskHint ); +} + +QRegion QwtPickerRubberband::maskHint() const +{ + return d_picker->rubberBandMask(); +} + +void QwtPickerRubberband::drawOverlay( QPainter *painter ) const +{ + painter->setPen( d_picker->rubberBandPen() ); + d_picker->drawRubberBand( painter ); +} + +QwtPickerTracker::QwtPickerTracker( + QwtPicker *picker, QWidget *parent ): + QwtWidgetOverlay( parent ), + d_picker( picker ) +{ + setMaskMode( QwtWidgetOverlay::MaskHint ); +} + +QRegion QwtPickerTracker::maskHint() const +{ + return d_picker->trackerRect( font() ); +} + +void QwtPickerTracker::drawOverlay( QPainter *painter ) const +{ + painter->setPen( d_picker->trackerPen() ); + d_picker->drawTracker( painter ); +} + +/*! + Constructor + + Creates an picker that is enabled, but without a state machine. + rubber band and tracker are disabled. + + \param parent Parent widget, that will be observed + */ + +QwtPicker::QwtPicker( QWidget *parent ): + QObject( parent ) +{ + init( parent, NoRubberBand, AlwaysOff ); +} + +/*! + Constructor + + \param rubberBand Rubber band style + \param trackerMode Tracker mode + \param parent Parent widget, that will be observed + */ +QwtPicker::QwtPicker( RubberBand rubberBand, + DisplayMode trackerMode, QWidget *parent ): + QObject( parent ) +{ + init( parent, rubberBand, trackerMode ); +} + +//! Destructor +QwtPicker::~QwtPicker() +{ + setMouseTracking( false ); + + delete d_data->stateMachine; + delete d_data->rubberBandOverlay; + delete d_data->trackerOverlay; + + delete d_data; +} + +//! Initialize the picker - used by the constructors +void QwtPicker::init( QWidget *parent, + RubberBand rubberBand, DisplayMode trackerMode ) +{ + d_data = new PrivateData; + + d_data->rubberBand = rubberBand; + + if ( parent ) + { + if ( parent->focusPolicy() == Qt::NoFocus ) + parent->setFocusPolicy( Qt::WheelFocus ); + + d_data->openGL = parent->inherits( "QGLWidget" ); + d_data->trackerFont = parent->font(); + d_data->mouseTracking = parent->hasMouseTracking(); + + setEnabled( true ); + } + + setTrackerMode( trackerMode ); +} + +/*! + Set a state machine and delete the previous one + + \param stateMachine State machine + \sa stateMachine() +*/ +void QwtPicker::setStateMachine( QwtPickerMachine *stateMachine ) +{ + if ( d_data->stateMachine != stateMachine ) + { + reset(); + + delete d_data->stateMachine; + d_data->stateMachine = stateMachine; + + if ( d_data->stateMachine ) + d_data->stateMachine->reset(); + } +} + +/*! + \return Assigned state machine + \sa setStateMachine() +*/ +QwtPickerMachine *QwtPicker::stateMachine() +{ + return d_data->stateMachine; +} + +/*! + \return Assigned state machine + \sa setStateMachine() +*/ +const QwtPickerMachine *QwtPicker::stateMachine() const +{ + return d_data->stateMachine; +} + +//! Return the parent widget, where the selection happens +QWidget *QwtPicker::parentWidget() +{ + QObject *obj = parent(); + if ( obj && obj->isWidgetType() ) + return static_cast( obj ); + + return NULL; +} + +//! Return the parent widget, where the selection happens +const QWidget *QwtPicker::parentWidget() const +{ + QObject *obj = parent(); + if ( obj && obj->isWidgetType() ) + return static_cast< const QWidget *>( obj ); + + return NULL; +} + +/*! + Set the rubber band style + + \param rubberBand Rubber band style + The default value is NoRubberBand. + + \sa rubberBand(), RubberBand, setRubberBandPen() +*/ +void QwtPicker::setRubberBand( RubberBand rubberBand ) +{ + d_data->rubberBand = rubberBand; +} + +/*! + \return Rubber band style + \sa setRubberBand(), RubberBand, rubberBandPen() +*/ +QwtPicker::RubberBand QwtPicker::rubberBand() const +{ + return d_data->rubberBand; +} + +/*! + \brief Set the display mode of the tracker. + + A tracker displays information about current position of + the cursor as a string. The display mode controls + if the tracker has to be displayed whenever the observed + widget has focus and cursor (AlwaysOn), never (AlwaysOff), or + only when the selection is active (ActiveOnly). + + \param mode Tracker display mode + + \warning In case of AlwaysOn, mouseTracking will be enabled + for the observed widget. + \sa trackerMode(), DisplayMode +*/ + +void QwtPicker::setTrackerMode( DisplayMode mode ) +{ + if ( d_data->trackerMode != mode ) + { + d_data->trackerMode = mode; + setMouseTracking( d_data->trackerMode == AlwaysOn ); + } +} + +/*! + \return Tracker display mode + \sa setTrackerMode(), DisplayMode +*/ +QwtPicker::DisplayMode QwtPicker::trackerMode() const +{ + return d_data->trackerMode; +} + +/*! + \brief Set the resize mode. + + The resize mode controls what to do with the selected points of an active + selection when the observed widget is resized. + + Stretch means the points are scaled according to the new + size, KeepSize means the points remain unchanged. + + The default mode is Stretch. + + \param mode Resize mode + \sa resizeMode(), ResizeMode +*/ +void QwtPicker::setResizeMode( ResizeMode mode ) +{ + d_data->resizeMode = mode; +} + +/*! + \return Resize mode + \sa setResizeMode(), ResizeMode +*/ + +QwtPicker::ResizeMode QwtPicker::resizeMode() const +{ + return d_data->resizeMode; +} + +/*! + \brief En/disable the picker + + When enabled is true an event filter is installed for + the observed widget, otherwise the event filter is removed. + + \param enabled true or false + \sa isEnabled(), eventFilter() +*/ +void QwtPicker::setEnabled( bool enabled ) +{ + if ( d_data->enabled != enabled ) + { + d_data->enabled = enabled; + + QWidget *w = parentWidget(); + if ( w ) + { + if ( enabled ) + w->installEventFilter( this ); + else + w->removeEventFilter( this ); + } + + updateDisplay(); + } +} + +/*! + \return true when enabled, false otherwise + \sa setEnabled(), eventFilter() +*/ + +bool QwtPicker::isEnabled() const +{ + return d_data->enabled; +} + +/*! + Set the font for the tracker + + \param font Tracker font + \sa trackerFont(), setTrackerMode(), setTrackerPen() +*/ +void QwtPicker::setTrackerFont( const QFont &font ) +{ + if ( font != d_data->trackerFont ) + { + d_data->trackerFont = font; + updateDisplay(); + } +} + +/*! + \return Tracker font + \sa setTrackerFont(), trackerMode(), trackerPen() +*/ + +QFont QwtPicker::trackerFont() const +{ + return d_data->trackerFont; +} + +/*! + Set the pen for the tracker + + \param pen Tracker pen + \sa trackerPen(), setTrackerMode(), setTrackerFont() +*/ +void QwtPicker::setTrackerPen( const QPen &pen ) +{ + if ( pen != d_data->trackerPen ) + { + d_data->trackerPen = pen; + updateDisplay(); + } +} + +/*! + \return Tracker pen + \sa setTrackerPen(), trackerMode(), trackerFont() +*/ +QPen QwtPicker::trackerPen() const +{ + return d_data->trackerPen; +} + +/*! + Set the pen for the rubberband + + \param pen Rubber band pen + \sa rubberBandPen(), setRubberBand() +*/ +void QwtPicker::setRubberBandPen( const QPen &pen ) +{ + if ( pen != d_data->rubberBandPen ) + { + d_data->rubberBandPen = pen; + updateDisplay(); + } +} + +/*! + \return Rubber band pen + \sa setRubberBandPen(), rubberBand() +*/ +QPen QwtPicker::rubberBandPen() const +{ + return d_data->rubberBandPen; +} + +/*! + \brief Return the label for a position + + In case of HLineRubberBand the label is the value of the + y position, in case of VLineRubberBand the value of the x position. + Otherwise the label contains x and y position separated by a ',' . + + The format for the string conversion is "%d". + + \param pos Position + \return Converted position as string +*/ + +QwtText QwtPicker::trackerText( const QPoint &pos ) const +{ + QString label; + + switch ( rubberBand() ) + { + case HLineRubberBand: + label.sprintf( "%d", pos.y() ); + break; + case VLineRubberBand: + label.sprintf( "%d", pos.x() ); + break; + default: + label.sprintf( "%d, %d", pos.x(), pos.y() ); + } + return label; +} + +/*! + Calculate the mask for the rubber band overlay + + \return Region for the mask + \sa QWidget::setMask() + */ +QRegion QwtPicker::rubberBandMask() const +{ + QRegion mask; + + if ( !isActive() || rubberBand() == NoRubberBand || + rubberBandPen().style() == Qt::NoPen ) + { + return mask; + } + + const QPolygon pa = adjustedPoints( d_data->pickedPoints ); + + QwtPickerMachine::SelectionType selectionType = + QwtPickerMachine::NoSelection; + + if ( d_data->stateMachine ) + selectionType = d_data->stateMachine->selectionType(); + + switch ( selectionType ) + { + case QwtPickerMachine::NoSelection: + case QwtPickerMachine::PointSelection: + { + if ( pa.count() < 1 ) + return mask; + + const QPoint pos = pa[0]; + const int pw = rubberBandPen().width(); + + const QRect pRect = pickArea().boundingRect().toRect(); + switch ( rubberBand() ) + { + case VLineRubberBand: + { + mask += qwtMaskRegion( QLine( pos.x(), pRect.top(), + pos.x(), pRect.bottom() ), pw ); + break; + } + case HLineRubberBand: + { + mask += qwtMaskRegion( QLine( pRect.left(), pos.y(), + pRect.right(), pos.y() ), pw ); + break; + } + case CrossRubberBand: + { + mask += qwtMaskRegion( QLine( pos.x(), pRect.top(), + pos.x(), pRect.bottom() ), pw ); + mask += qwtMaskRegion( QLine( pRect.left(), pos.y(), + pRect.right(), pos.y() ), pw ); + break; + } + default: + break; + } + break; + } + case QwtPickerMachine::RectSelection: + { + if ( pa.count() < 2 ) + return mask; + + const int pw = rubberBandPen().width(); + + switch ( rubberBand() ) + { + case RectRubberBand: + { + const QRect r = QRect( pa.first(), pa.last() ); + mask = qwtMaskRegion( r.normalized(), pw ); + break; + } + case EllipseRubberBand: + { + const QRect r = QRect( pa.first(), pa.last() ); + mask += r.adjusted( -pw, -pw, pw, pw ); + break; + } + default: + break; + } + break; + } + case QwtPickerMachine::PolygonSelection: + { + const int pw = rubberBandPen().width(); + if ( pw <= 1 ) + { + // because of the join style we better + // return a mask for a pen width <= 1 only + + const int off = 2 * pw; + const QRect r = pa.boundingRect(); + mask += r.adjusted( -off, -off, off, off ); + } + break; + } + default: + break; + } + + return mask; +} + +/*! + Draw a rubber band, depending on rubberBand() + + \param painter Painter, initialized with a clip region + + \sa rubberBand(), RubberBand +*/ + +void QwtPicker::drawRubberBand( QPainter *painter ) const +{ + if ( !isActive() || rubberBand() == NoRubberBand || + rubberBandPen().style() == Qt::NoPen ) + { + return; + } + + const QPolygon pa = adjustedPoints( d_data->pickedPoints ); + + QwtPickerMachine::SelectionType selectionType = + QwtPickerMachine::NoSelection; + + if ( d_data->stateMachine ) + selectionType = d_data->stateMachine->selectionType(); + + switch ( selectionType ) + { + case QwtPickerMachine::NoSelection: + case QwtPickerMachine::PointSelection: + { + if ( pa.count() < 1 ) + return; + + const QPoint pos = pa[0]; + + const QRect pRect = pickArea().boundingRect().toRect(); + switch ( rubberBand() ) + { + case VLineRubberBand: + { + QwtPainter::drawLine( painter, pos.x(), + pRect.top(), pos.x(), pRect.bottom() ); + break; + } + case HLineRubberBand: + { + QwtPainter::drawLine( painter, pRect.left(), + pos.y(), pRect.right(), pos.y() ); + break; + } + case CrossRubberBand: + { + QwtPainter::drawLine( painter, pos.x(), + pRect.top(), pos.x(), pRect.bottom() ); + QwtPainter::drawLine( painter, pRect.left(), + pos.y(), pRect.right(), pos.y() ); + break; + } + default: + break; + } + break; + } + case QwtPickerMachine::RectSelection: + { + if ( pa.count() < 2 ) + return; + + const QRect rect = QRect( pa.first(), pa.last() ).normalized(); + switch ( rubberBand() ) + { + case EllipseRubberBand: + { + QwtPainter::drawEllipse( painter, rect ); + break; + } + case RectRubberBand: + { + QwtPainter::drawRect( painter, rect ); + break; + } + default: + break; + } + break; + } + case QwtPickerMachine::PolygonSelection: + { + if ( rubberBand() == PolygonRubberBand ) + painter->drawPolyline( pa ); + break; + } + default: + break; + } +} + +/*! + Draw the tracker + + \param painter Painter + \sa trackerRect(), trackerText() +*/ + +void QwtPicker::drawTracker( QPainter *painter ) const +{ + const QRect textRect = trackerRect( painter->font() ); + if ( !textRect.isEmpty() ) + { + const QwtText label = trackerText( d_data->trackerPosition ); + if ( !label.isEmpty() ) + label.draw( painter, textRect ); + } +} + +/*! + \brief Map the pickedPoints() into a selection() + + adjustedPoints() maps the points, that have been collected on + the parentWidget() into a selection(). The default implementation + simply returns the points unmodified. + + The reason, why a selection() differs from the picked points + depends on the application requirements. F.e. : + + - A rectangular selection might need to have a specific aspect ratio only.\n + - A selection could accept non intersecting polygons only.\n + - ...\n + + The example below is for a rectangular selection, where the first + point is the center of the selected rectangle. + \par Example + \verbatim QPolygon MyPicker::adjustedPoints(const QPolygon &points) const +{ + QPolygon adjusted; + if ( points.size() == 2 ) + { + const int width = qAbs(points[1].x() - points[0].x()); + const int height = qAbs(points[1].y() - points[0].y()); + + QRect rect(0, 0, 2 * width, 2 * height); + rect.moveCenter(points[0]); + + adjusted += rect.topLeft(); + adjusted += rect.bottomRight(); + } + return adjusted; +}\endverbatim\n + + \param points Selected points + \return Selected points unmodified +*/ +QPolygon QwtPicker::adjustedPoints( const QPolygon &points ) const +{ + return points; +} + +/*! + \return Selected points + \sa pickedPoints(), adjustedPoints() +*/ +QPolygon QwtPicker::selection() const +{ + return adjustedPoints( d_data->pickedPoints ); +} + +//! \return Current position of the tracker +QPoint QwtPicker::trackerPosition() const +{ + return d_data->trackerPosition; +} + +/*! + Calculate the bounding rectangle for the tracker text + from the current position of the tracker + + \param font Font of the tracker text + \return Bounding rectangle of the tracker text + + \sa trackerPosition() +*/ +QRect QwtPicker::trackerRect( const QFont &font ) const +{ + if ( trackerMode() == AlwaysOff || + ( trackerMode() == ActiveOnly && !isActive() ) ) + { + return QRect(); + } + + if ( d_data->trackerPosition.x() < 0 || d_data->trackerPosition.y() < 0 ) + return QRect(); + + QwtText text = trackerText( d_data->trackerPosition ); + if ( text.isEmpty() ) + return QRect(); + + const QSizeF textSize = text.textSize( font ); + QRect textRect( 0, 0, qCeil( textSize.width() ), qCeil( textSize.height() ) ); + + const QPoint &pos = d_data->trackerPosition; + + int alignment = 0; + if ( isActive() && d_data->pickedPoints.count() > 1 + && rubberBand() != NoRubberBand ) + { + const QPoint last = + d_data->pickedPoints[int( d_data->pickedPoints.count() ) - 2]; + + alignment |= ( pos.x() >= last.x() ) ? Qt::AlignRight : Qt::AlignLeft; + alignment |= ( pos.y() > last.y() ) ? Qt::AlignBottom : Qt::AlignTop; + } + else + alignment = Qt::AlignTop | Qt::AlignRight; + + const int margin = 5; + + int x = pos.x(); + if ( alignment & Qt::AlignLeft ) + x -= textRect.width() + margin; + else if ( alignment & Qt::AlignRight ) + x += margin; + + int y = pos.y(); + if ( alignment & Qt::AlignBottom ) + y += margin; + else if ( alignment & Qt::AlignTop ) + y -= textRect.height() + margin; + + textRect.moveTopLeft( QPoint( x, y ) ); + + const QRect pickRect = pickArea().boundingRect().toRect(); + + int right = qMin( textRect.right(), pickRect.right() - margin ); + int bottom = qMin( textRect.bottom(), pickRect.bottom() - margin ); + textRect.moveBottomRight( QPoint( right, bottom ) ); + + int left = qMax( textRect.left(), pickRect.left() + margin ); + int top = qMax( textRect.top(), pickRect.top() + margin ); + textRect.moveTopLeft( QPoint( left, top ) ); + + return textRect; +} + +/*! + \brief Event filter + + When isEnabled() is true all events of the observed widget are filtered. + Mouse and keyboard events are translated into widgetMouse- and widgetKey- + and widgetWheel-events. Paint and Resize events are handled to keep + rubber band and tracker up to date. + + \param object Object to be filtered + \param event Event + + \return Always false. + + \sa widgetEnterEvent(), widgetLeaveEvent(), + widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent(), + QObject::installEventFilter(), QObject::event() +*/ +bool QwtPicker::eventFilter( QObject *object, QEvent *event ) +{ + if ( object && object == parentWidget() ) + { + switch ( event->type() ) + { + case QEvent::Resize: + { + const QResizeEvent *re = static_cast( event ); + if ( d_data->resizeMode == Stretch ) + stretchSelection( re->oldSize(), re->size() ); + + break; + } + case QEvent::Enter: + { + widgetEnterEvent( event ); + break; + } + case QEvent::Leave: + { + widgetLeaveEvent( event ); + break; + } + case QEvent::MouseButtonPress: + { + widgetMousePressEvent( static_cast( event ) ); + break; + } + case QEvent::MouseButtonRelease: + { + widgetMouseReleaseEvent( static_cast( event ) ); + break; + } + case QEvent::MouseButtonDblClick: + { + widgetMouseDoubleClickEvent( static_cast( event ) ); + break; + } + case QEvent::MouseMove: + { + widgetMouseMoveEvent( static_cast( event ) ); + break; + } + case QEvent::KeyPress: + { + widgetKeyPressEvent( static_cast( event ) ); + break; + } + case QEvent::KeyRelease: + { + widgetKeyReleaseEvent( static_cast( event ) ); + break; + } + case QEvent::Wheel: + { + widgetWheelEvent( static_cast( event ) ); + break; + } + default: + break; + } + } + return false; +} + +/*! + Handle a mouse press event for the observed widget. + + \param mouseEvent Mouse event + + \sa eventFilter(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetMousePressEvent( QMouseEvent *mouseEvent ) +{ + transition( mouseEvent ); +} + +/*! + Handle a mouse move event for the observed widget. + + \param mouseEvent Mouse event + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetMouseMoveEvent( QMouseEvent *mouseEvent ) +{ + if ( pickArea().contains( mouseEvent->pos() ) ) + d_data->trackerPosition = mouseEvent->pos(); + else + d_data->trackerPosition = QPoint( -1, -1 ); + + if ( !isActive() ) + updateDisplay(); + + transition( mouseEvent ); +} + +/*! + Handle a enter event for the observed widget. + + \param event Qt event + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetEnterEvent( QEvent *event ) +{ + transition( event ); +} + +/*! + Handle a leave event for the observed widget. + + \param event Qt event + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetLeaveEvent( QEvent *event ) +{ + transition( event ); + + d_data->trackerPosition = QPoint( -1, -1 ); + if ( !isActive() ) + updateDisplay(); +} + +/*! + Handle a mouse release event for the observed widget. + + \param mouseEvent Mouse event + + \sa eventFilter(), widgetMousePressEvent(), + widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetMouseReleaseEvent( QMouseEvent *mouseEvent ) +{ + transition( mouseEvent ); +} + +/*! + Handle mouse double click event for the observed widget. + + \param mouseEvent Mouse event + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseMoveEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetMouseDoubleClickEvent( QMouseEvent *mouseEvent ) +{ + transition( mouseEvent ); +} + + +/*! + Handle a wheel event for the observed widget. + + Move the last point of the selection in case of isActive() == true + + \param wheelEvent Wheel event + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), + widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetWheelEvent( QWheelEvent *wheelEvent ) +{ + if ( pickArea().contains( wheelEvent->pos() ) ) + d_data->trackerPosition = wheelEvent->pos(); + else + d_data->trackerPosition = QPoint( -1, -1 ); + + updateDisplay(); + + transition( wheelEvent ); +} + +/*! + Handle a key press event for the observed widget. + + Selections can be completely done by the keyboard. The arrow keys + move the cursor, the abort key aborts a selection. All other keys + are handled by the current state machine. + + \param keyEvent Key event + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), + widgetWheelEvent(), widgetKeyReleaseEvent(), stateMachine(), + QwtEventPattern::KeyPatternCode +*/ +void QwtPicker::widgetKeyPressEvent( QKeyEvent *keyEvent ) +{ + int dx = 0; + int dy = 0; + + int offset = 1; + if ( keyEvent->isAutoRepeat() ) + offset = 5; + + if ( keyMatch( KeyLeft, keyEvent ) ) + dx = -offset; + else if ( keyMatch( KeyRight, keyEvent ) ) + dx = offset; + else if ( keyMatch( KeyUp, keyEvent ) ) + dy = -offset; + else if ( keyMatch( KeyDown, keyEvent ) ) + dy = offset; + else if ( keyMatch( KeyAbort, keyEvent ) ) + { + reset(); + } + else + transition( keyEvent ); + + if ( dx != 0 || dy != 0 ) + { + const QRect rect = pickArea().boundingRect().toRect(); + const QPoint pos = parentWidget()->mapFromGlobal( QCursor::pos() ); + + int x = pos.x() + dx; + x = qMax( rect.left(), x ); + x = qMin( rect.right(), x ); + + int y = pos.y() + dy; + y = qMax( rect.top(), y ); + y = qMin( rect.bottom(), y ); + + QCursor::setPos( parentWidget()->mapToGlobal( QPoint( x, y ) ) ); + } +} + +/*! + Handle a key release event for the observed widget. + + Passes the event to the state machine. + + \param keyEvent Key event + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), stateMachine() +*/ +void QwtPicker::widgetKeyReleaseEvent( QKeyEvent *keyEvent ) +{ + transition( keyEvent ); +} + +/*! + Passes an event to the state machine and executes the resulting + commands. Append and Move commands use the current position + of the cursor ( QCursor::pos() ). + + \param event Event +*/ +void QwtPicker::transition( const QEvent *event ) +{ + if ( !d_data->stateMachine ) + return; + + const QList commandList = + d_data->stateMachine->transition( *this, event ); + + QPoint pos; + switch ( event->type() ) + { + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + { + const QMouseEvent *me = + static_cast< const QMouseEvent * >( event ); + pos = me->pos(); + break; + } + default: + pos = parentWidget()->mapFromGlobal( QCursor::pos() ); + } + + for ( int i = 0; i < commandList.count(); i++ ) + { + switch ( commandList[i] ) + { + case QwtPickerMachine::Begin: + { + begin(); + break; + } + case QwtPickerMachine::Append: + { + append( pos ); + break; + } + case QwtPickerMachine::Move: + { + move( pos ); + break; + } + case QwtPickerMachine::Remove: + { + remove(); + break; + } + case QwtPickerMachine::End: + { + end(); + break; + } + } + } +} + +/*! + Open a selection setting the state to active + + \sa isActive(), end(), append(), move() +*/ +void QwtPicker::begin() +{ + if ( d_data->isActive ) + return; + + d_data->pickedPoints.resize( 0 ); + d_data->isActive = true; + Q_EMIT activated( true ); + + if ( trackerMode() != AlwaysOff ) + { + if ( d_data->trackerPosition.x() < 0 || d_data->trackerPosition.y() < 0 ) + { + QWidget *w = parentWidget(); + if ( w ) + d_data->trackerPosition = w->mapFromGlobal( QCursor::pos() ); + } + } + + updateDisplay(); + setMouseTracking( true ); +} + +/*! + \brief Close a selection setting the state to inactive. + + The selection is validated and maybe fixed by accept(). + + \param ok If true, complete the selection and emit a selected signal + otherwise discard the selection. + \return true if the selection is accepted, false otherwise + \sa isActive(), begin(), append(), move(), selected(), accept() +*/ +bool QwtPicker::end( bool ok ) +{ + if ( d_data->isActive ) + { + setMouseTracking( false ); + + d_data->isActive = false; + Q_EMIT activated( false ); + + if ( trackerMode() == ActiveOnly ) + d_data->trackerPosition = QPoint( -1, -1 ); + + if ( ok ) + ok = accept( d_data->pickedPoints ); + + if ( ok ) + Q_EMIT selected( d_data->pickedPoints ); + else + d_data->pickedPoints.resize( 0 ); + + updateDisplay(); + } + else + ok = false; + + return ok; +} + +/*! + Reset the state machine and terminate ( end(false) ) the selection +*/ +void QwtPicker::reset() +{ + if ( d_data->stateMachine ) + d_data->stateMachine->reset(); + + if ( isActive() ) + end( false ); +} + +/*! + Append a point to the selection and update rubber band and tracker. + The appended() signal is emitted. + + \param pos Additional point + + \sa isActive(), begin(), end(), move(), appended() +*/ +void QwtPicker::append( const QPoint &pos ) +{ + if ( d_data->isActive ) + { + const int idx = d_data->pickedPoints.count(); + d_data->pickedPoints.resize( idx + 1 ); + d_data->pickedPoints[idx] = pos; + + updateDisplay(); + Q_EMIT appended( pos ); + } +} + +/*! + Move the last point of the selection + The moved() signal is emitted. + + \param pos New position + \sa isActive(), begin(), end(), append() +*/ +void QwtPicker::move( const QPoint &pos ) +{ + if ( d_data->isActive ) + { + const int idx = d_data->pickedPoints.count() - 1; + if ( idx >= 0 ) + { + if ( d_data->pickedPoints[idx] != pos ) + { + d_data->pickedPoints[idx] = pos; + + updateDisplay(); + Q_EMIT moved( pos ); + } + } + } +} + +/*! + Remove the last point of the selection + The removed() signal is emitted. + + \sa isActive(), begin(), end(), append(), move() +*/ +void QwtPicker::remove() +{ + if ( d_data->isActive ) + { + const int idx = d_data->pickedPoints.count() - 1; + if ( idx > 0 ) + { + const int idx = d_data->pickedPoints.count(); + + const QPoint pos = d_data->pickedPoints[idx - 1]; + d_data->pickedPoints.resize( idx - 1 ); + + updateDisplay(); + Q_EMIT removed( pos ); + } + } +} + +/*! + \brief Validate and fix up the selection + + Accepts all selections unmodified + + \param selection Selection to validate and fix up + \return true, when accepted, false otherwise +*/ +bool QwtPicker::accept( QPolygon &selection ) const +{ + Q_UNUSED( selection ); + return true; +} + +/*! + A picker is active between begin() and end(). + \return true if the selection is active. +*/ +bool QwtPicker::isActive() const +{ + return d_data->isActive; +} + +/*! + Return the points, that have been collected so far. The selection() + is calculated from the pickedPoints() in adjustedPoints(). + \return Picked points +*/ +const QPolygon &QwtPicker::pickedPoints() const +{ + return d_data->pickedPoints; +} + +/*! + Scale the selection by the ratios of oldSize and newSize + The changed() signal is emitted. + + \param oldSize Previous size + \param newSize Current size + + \sa ResizeMode, setResizeMode(), resizeMode() +*/ +void QwtPicker::stretchSelection( const QSize &oldSize, const QSize &newSize ) +{ + if ( oldSize.isEmpty() ) + { + // avoid division by zero. But scaling for small sizes also + // doesn't make much sense, because of rounding losses. TODO ... + return; + } + + const double xRatio = + double( newSize.width() ) / double( oldSize.width() ); + const double yRatio = + double( newSize.height() ) / double( oldSize.height() ); + + for ( int i = 0; i < int( d_data->pickedPoints.count() ); i++ ) + { + QPoint &p = d_data->pickedPoints[i]; + p.setX( qRound( p.x() * xRatio ) ); + p.setY( qRound( p.y() * yRatio ) ); + + Q_EMIT changed( d_data->pickedPoints ); + } +} + +/*! + Set mouse tracking for the observed widget. + + In case of enable is true, the previous value + is saved, that is restored when enable is false. + + \warning Even when enable is false, mouse tracking might be restored + to true. When mouseTracking for the observed widget + has been changed directly by QWidget::setMouseTracking + while mouse tracking has been set to true, this value can't + be restored. +*/ + +void QwtPicker::setMouseTracking( bool enable ) +{ + QWidget *widget = parentWidget(); + if ( !widget ) + return; + + if ( enable ) + { + d_data->mouseTracking = widget->hasMouseTracking(); + widget->setMouseTracking( true ); + } + else + { + widget->setMouseTracking( d_data->mouseTracking ); + } +} + +/*! + Find the area of the observed widget, where selection might happen. + + \return parentWidget()->contentsRect() +*/ +QPainterPath QwtPicker::pickArea() const +{ + QPainterPath path; + + const QWidget *widget = parentWidget(); + if ( widget ) + path.addRect( widget->contentsRect() ); + + return path; +} + +//! Update the state of rubber band and tracker label +void QwtPicker::updateDisplay() +{ + QWidget *w = parentWidget(); + + bool showRubberband = false; + bool showTracker = false; + + if ( w && w->isVisible() && d_data->enabled ) + { + if ( rubberBand() != NoRubberBand && isActive() && + rubberBandPen().style() != Qt::NoPen ) + { + showRubberband = true; + } + + if ( trackerMode() == AlwaysOn || + ( trackerMode() == ActiveOnly && isActive() ) ) + { + if ( trackerPen() != Qt::NoPen + && !trackerRect( QFont() ).isEmpty() ) + { + showTracker = true; + } + } + } + + QPointer< QwtPickerRubberband > &rw = d_data->rubberBandOverlay; + if ( showRubberband ) + { + if ( rw.isNull() ) + { + rw = new QwtPickerRubberband( this, w ); + rw->setObjectName( "PickerRubberBand" ); + rw->resize( w->size() ); + } + + if ( d_data->rubberBand <= RectRubberBand ) + rw->setMaskMode( QwtWidgetOverlay::MaskHint ); + else + rw->setMaskMode( QwtWidgetOverlay::AlphaMask ); + + rw->updateOverlay(); + } + else + { + if ( d_data->openGL ) + { + // Qt 4.8 crashes for a delete + if ( !rw.isNull() ) + { + rw->hide(); + rw->deleteLater(); + rw = NULL; + } + } + else + { + delete rw; + } + } + + QPointer< QwtPickerTracker > &tw = d_data->trackerOverlay; + if ( showTracker ) + { + if ( tw.isNull() ) + { + tw = new QwtPickerTracker( this, w ); + tw->setObjectName( "PickerTracker" ); + tw->resize( w->size() ); + } + tw->setFont( d_data->trackerFont ); + tw->updateOverlay(); + } + else + { + if ( d_data->openGL ) + { + // Qt 4.8 crashes for a delete + if ( !tw.isNull() ) + { + tw->hide(); + tw->deleteLater(); + tw = NULL; + } + } + else + { + delete tw; + } + } +} + +//! \return Overlay displaying the rubber band +const QwtWidgetOverlay *QwtPicker::rubberBandOverlay() const +{ + return d_data->rubberBandOverlay; +} + +//! \return Overlay displaying the tracker text +const QwtWidgetOverlay *QwtPicker::trackerOverlay() const +{ + return d_data->trackerOverlay; +} + diff --git a/qwt/src/qwt_picker.h b/qwt/src/qwt_picker.h new file mode 100644 index 000000000..87d6805e9 --- /dev/null +++ b/qwt/src/qwt_picker.h @@ -0,0 +1,329 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PICKER +#define QWT_PICKER 1 + +#include "qwt_global.h" +#include "qwt_text.h" +#include "qwt_event_pattern.h" +#include +#include +#include +#include +#include + +class QWidget; +class QMouseEvent; +class QWheelEvent; +class QKeyEvent; +class QwtPickerMachine; +class QwtWidgetOverlay; + +/*! + \brief QwtPicker provides selections on a widget + + QwtPicker filters all enter, leave, mouse and keyboard events of a widget + and translates them into an array of selected points. + + The way how the points are collected depends on type of state machine + that is connected to the picker. Qwt offers a couple of predefined + state machines for selecting: + + - Nothing\n + QwtPickerTrackerMachine + - Single points\n + QwtPickerClickPointMachine, QwtPickerDragPointMachine + - Rectangles\n + QwtPickerClickRectMachine, QwtPickerDragRectMachine + - Polygons\n + QwtPickerPolygonMachine + + While these state machines cover the most common ways to collect points + it is also possible to implement individual machines as well. + + QwtPicker translates the picked points into a selection using the + adjustedPoints() method. adjustedPoints() is intended to be reimplemented + to fix up the selection according to application specific requirements. + (F.e. when an application accepts rectangles of a fixed aspect ratio only.) + + Optionally QwtPicker support the process of collecting points by a + rubber band and tracker displaying a text for the current mouse + position. + + \par Example + \verbatim #include +#include + +QwtPicker *picker = new QwtPicker(widget); +picker->setStateMachine(new QwtPickerDragRectMachine); +picker->setTrackerMode(QwtPicker::ActiveOnly); +picker->setRubberBand(QwtPicker::RectRubberBand); \endverbatim\n + + The state machine triggers the following commands: + + - begin()\n + Activate/Initialize the selection. + - append()\n + Add a new point + - move() \n + Change the position of the last point. + - remove()\n + Remove the last point. + - end()\n + Terminate the selection and call accept to validate the picked points. + + The picker is active (isActive()), between begin() and end(). + In active state the rubber band is displayed, and the tracker is visible + in case of trackerMode is ActiveOnly or AlwaysOn. + + The cursor can be moved using the arrow keys. All selections can be aborted + using the abort key. (QwtEventPattern::KeyPatternCode) + + \warning In case of QWidget::NoFocus the focus policy of the observed + widget is set to QWidget::WheelFocus and mouse tracking + will be manipulated while the picker is active, + or if trackerMode() is AlwayOn. +*/ + +class QWT_EXPORT QwtPicker: public QObject, public QwtEventPattern +{ + Q_OBJECT + + Q_ENUMS( RubberBand DisplayMode ResizeMode ) + + Q_PROPERTY( bool isEnabled READ isEnabled WRITE setEnabled ) + Q_PROPERTY( ResizeMode resizeMode READ resizeMode WRITE setResizeMode ) + + Q_PROPERTY( DisplayMode trackerMode READ trackerMode WRITE setTrackerMode ) + Q_PROPERTY( QPen trackerPen READ trackerPen WRITE setTrackerPen ) + Q_PROPERTY( QFont trackerFont READ trackerFont WRITE setTrackerFont ) + + Q_PROPERTY( RubberBand rubberBand READ rubberBand WRITE setRubberBand ) + Q_PROPERTY( QPen rubberBandPen READ rubberBandPen WRITE setRubberBandPen ) + +public: + /*! + Rubber band style + + The default value is QwtPicker::NoRubberBand. + \sa setRubberBand(), rubberBand() + */ + + enum RubberBand + { + //! No rubberband. + NoRubberBand = 0, + + //! A horizontal line ( only for QwtPickerMachine::PointSelection ) + HLineRubberBand, + + //! A vertical line ( only for QwtPickerMachine::PointSelection ) + VLineRubberBand, + + //! A crosshair ( only for QwtPickerMachine::PointSelection ) + CrossRubberBand, + + //! A rectangle ( only for QwtPickerMachine::RectSelection ) + RectRubberBand, + + //! An ellipse ( only for QwtPickerMachine::RectSelection ) + EllipseRubberBand, + + //! A polygon ( only for QwtPickerMachine::PolygonSelection ) + PolygonRubberBand, + + /*! + Values >= UserRubberBand can be used to define additional + rubber bands. + */ + UserRubberBand = 100 + }; + + /*! + \brief Display mode + \sa setTrackerMode(), trackerMode(), isActive() + */ + enum DisplayMode + { + //! Display never + AlwaysOff, + + //! Display always + AlwaysOn, + + //! Display only when the selection is active + ActiveOnly + }; + + /*! + Controls what to do with the selected points of an active + selection when the observed widget is resized. + + The default value is QwtPicker::Stretch. + \sa setResizeMode() + */ + + enum ResizeMode + { + //! All points are scaled according to the new size, + Stretch, + + //! All points remain unchanged. + KeepSize + }; + + explicit QwtPicker( QWidget *parent ); + explicit QwtPicker( RubberBand rubberBand, + DisplayMode trackerMode, QWidget * ); + + virtual ~QwtPicker(); + + void setStateMachine( QwtPickerMachine * ); + const QwtPickerMachine *stateMachine() const; + QwtPickerMachine *stateMachine(); + + void setRubberBand( RubberBand ); + RubberBand rubberBand() const; + + void setTrackerMode( DisplayMode ); + DisplayMode trackerMode() const; + + void setResizeMode( ResizeMode ); + ResizeMode resizeMode() const; + + void setRubberBandPen( const QPen & ); + QPen rubberBandPen() const; + + void setTrackerPen( const QPen & ); + QPen trackerPen() const; + + void setTrackerFont( const QFont & ); + QFont trackerFont() const; + + bool isEnabled() const; + bool isActive() const; + + virtual bool eventFilter( QObject *, QEvent * ); + + QWidget *parentWidget(); + const QWidget *parentWidget() const; + + virtual QPainterPath pickArea() const; + + virtual void drawRubberBand( QPainter * ) const; + virtual void drawTracker( QPainter * ) const; + + virtual QRegion rubberBandMask() const; + + virtual QwtText trackerText( const QPoint &pos ) const; + QPoint trackerPosition() const; + virtual QRect trackerRect( const QFont & ) const; + + QPolygon selection() const; + +public Q_SLOTS: + void setEnabled( bool ); + +Q_SIGNALS: + /*! + A signal indicating, when the picker has been activated. + Together with setEnabled() it can be used to implement + selections with more than one picker. + + \param on True, when the picker has been activated + */ + void activated( bool on ); + + /*! + A signal emitting the selected points, + at the end of a selection. + + \param polygon Selected points + */ + void selected( const QPolygon &polygon ); + + /*! + A signal emitted when a point has been appended to the selection + + \param pos Position of the appended point. + \sa append(). moved() + */ + void appended( const QPoint &pos ); + + /*! + A signal emitted whenever the last appended point of the + selection has been moved. + + \param pos Position of the moved last point of the selection. + \sa move(), appended() + */ + void moved( const QPoint &pos ); + + /*! + A signal emitted whenever the last appended point of the + selection has been removed. + + \param pos Position of the point, that has been removed + \sa remove(), appended() + */ + void removed( const QPoint &pos ); + /*! + A signal emitted when the active selection has been changed. + This might happen when the observed widget is resized. + + \param selection Changed selection + \sa stretchSelection() + */ + void changed( const QPolygon &selection ); + +protected: + virtual QPolygon adjustedPoints( const QPolygon & ) const; + + virtual void transition( const QEvent * ); + + virtual void begin(); + virtual void append( const QPoint & ); + virtual void move( const QPoint & ); + virtual void remove(); + virtual bool end( bool ok = true ); + + virtual bool accept( QPolygon & ) const; + virtual void reset(); + + virtual void widgetMousePressEvent( QMouseEvent * ); + virtual void widgetMouseReleaseEvent( QMouseEvent * ); + virtual void widgetMouseDoubleClickEvent( QMouseEvent * ); + virtual void widgetMouseMoveEvent( QMouseEvent * ); + virtual void widgetWheelEvent( QWheelEvent * ); + virtual void widgetKeyPressEvent( QKeyEvent * ); + virtual void widgetKeyReleaseEvent( QKeyEvent * ); + virtual void widgetEnterEvent( QEvent * ); + virtual void widgetLeaveEvent( QEvent * ); + + virtual void stretchSelection( const QSize &oldSize, + const QSize &newSize ); + + virtual void updateDisplay(); + + const QwtWidgetOverlay *rubberBandOverlay() const; + const QwtWidgetOverlay *trackerOverlay() const; + + const QPolygon &pickedPoints() const; + +private: + void init( QWidget *, RubberBand rubberBand, DisplayMode trackerMode ); + + void setMouseTracking( bool ); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_picker_machine.cpp b/qwt/src/qwt_picker_machine.cpp new file mode 100644 index 000000000..299624ed8 --- /dev/null +++ b/qwt/src/qwt_picker_machine.cpp @@ -0,0 +1,526 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_picker_machine.h" +#include "qwt_event_pattern.h" +#include + +//! Constructor +QwtPickerMachine::QwtPickerMachine( SelectionType type ): + d_selectionType( type ), + d_state( 0 ) +{ +} + +//! Destructor +QwtPickerMachine::~QwtPickerMachine() +{ +} + +//! Return the selection type +QwtPickerMachine::SelectionType QwtPickerMachine::selectionType() const +{ + return d_selectionType; +} + +//! Return the current state +int QwtPickerMachine::state() const +{ + return d_state; +} + +//! Change the current state +void QwtPickerMachine::setState( int state ) +{ + d_state = state; +} + +//! Set the current state to 0. +void QwtPickerMachine::reset() +{ + setState( 0 ); +} + +//! Constructor +QwtPickerTrackerMachine::QwtPickerTrackerMachine(): + QwtPickerMachine( NoSelection ) +{ +} + +//! Transition +QList QwtPickerTrackerMachine::transition( + const QwtEventPattern &, const QEvent *e ) +{ + QList cmdList; + + switch ( e->type() ) + { + case QEvent::Enter: + case QEvent::MouseMove: + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + setState( 1 ); + } + else + { + cmdList += Move; + } + break; + } + case QEvent::Leave: + { + cmdList += Remove; + cmdList += End; + setState( 0 ); + } + default: + break; + } + + return cmdList; +} + +//! Constructor +QwtPickerClickPointMachine::QwtPickerClickPointMachine(): + QwtPickerMachine( PointSelection ) +{ +} + +//! Transition +QList QwtPickerClickPointMachine::transition( + const QwtEventPattern &eventPattern, const QEvent *event ) +{ + QList cmdList; + + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast( event ) ) ) + { + cmdList += Begin; + cmdList += Append; + cmdList += End; + } + break; + } + case QEvent::KeyPress: + { + if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, + static_cast ( event ) ) ) + { + cmdList += Begin; + cmdList += Append; + cmdList += End; + } + break; + } + default: + break; + } + + return cmdList; +} + +//! Constructor +QwtPickerDragPointMachine::QwtPickerDragPointMachine(): + QwtPickerMachine( PointSelection ) +{ +} + +//! Transition +QList QwtPickerDragPointMachine::transition( + const QwtEventPattern &eventPattern, const QEvent *event ) +{ + QList cmdList; + + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + setState( 1 ); + } + } + break; + } + case QEvent::MouseMove: + case QEvent::Wheel: + { + if ( state() != 0 ) + cmdList += Move; + break; + } + case QEvent::MouseButtonRelease: + { + if ( state() != 0 ) + { + cmdList += End; + setState( 0 ); + } + break; + } + case QEvent::KeyPress: + { + if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, + static_cast( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + setState( 1 ); + } + else + { + cmdList += End; + setState( 0 ); + } + } + break; + } + default: + break; + } + + return cmdList; +} + +//! Constructor +QwtPickerClickRectMachine::QwtPickerClickRectMachine(): + QwtPickerMachine( RectSelection ) +{ +} + +//! Transition +QList QwtPickerClickRectMachine::transition( + const QwtEventPattern &eventPattern, const QEvent *event ) +{ + QList cmdList; + + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast( event ) ) ) + { + switch ( state() ) + { + case 0: + { + cmdList += Begin; + cmdList += Append; + setState( 1 ); + break; + } + case 1: + { + // Uh, strange we missed the MouseButtonRelease + break; + } + default: + { + cmdList += End; + setState( 0 ); + } + } + } + } + case QEvent::MouseMove: + case QEvent::Wheel: + { + if ( state() != 0 ) + cmdList += Move; + break; + } + case QEvent::MouseButtonRelease: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast( event ) ) ) + { + if ( state() == 1 ) + { + cmdList += Append; + setState( 2 ); + } + } + break; + } + case QEvent::KeyPress: + { + if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, + static_cast ( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + setState( 1 ); + } + else + { + if ( state() == 1 ) + { + cmdList += Append; + setState( 2 ); + } + else if ( state() == 2 ) + { + cmdList += End; + setState( 0 ); + } + } + } + break; + } + default: + break; + } + + return cmdList; +} + +//! Constructor +QwtPickerDragRectMachine::QwtPickerDragRectMachine(): + QwtPickerMachine( RectSelection ) +{ +} + +//! Transition +QList QwtPickerDragRectMachine::transition( + const QwtEventPattern &eventPattern, const QEvent *event ) +{ + QList cmdList; + + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + cmdList += Append; + setState( 2 ); + } + } + break; + } + case QEvent::MouseMove: + case QEvent::Wheel: + { + if ( state() != 0 ) + cmdList += Move; + break; + } + case QEvent::MouseButtonRelease: + { + if ( state() == 2 ) + { + cmdList += End; + setState( 0 ); + } + break; + } + case QEvent::KeyPress: + { + if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, + static_cast ( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + cmdList += Append; + setState( 2 ); + } + else + { + cmdList += End; + setState( 0 ); + } + } + break; + } + default: + break; + } + + return cmdList; +} + +//! Constructor +QwtPickerPolygonMachine::QwtPickerPolygonMachine(): + QwtPickerMachine( PolygonSelection ) +{ +} + +//! Transition +QList QwtPickerPolygonMachine::transition( + const QwtEventPattern &eventPattern, const QEvent *event ) +{ + QList cmdList; + + switch ( event->type() ) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + cmdList += Append; + setState( 1 ); + } + else + { + cmdList += Append; + } + } + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect2, + static_cast( event ) ) ) + { + if ( state() == 1 ) + { + cmdList += End; + setState( 0 ); + } + } + break; + } + case QEvent::MouseMove: + case QEvent::Wheel: + { + if ( state() != 0 ) + cmdList += Move; + break; + } + case QEvent::KeyPress: + { + if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, + static_cast ( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + cmdList += Append; + setState( 1 ); + } + else + { + cmdList += Append; + } + } + else if ( eventPattern.keyMatch( QwtEventPattern::KeySelect2, + static_cast ( event ) ) ) + { + if ( state() == 1 ) + { + cmdList += End; + setState( 0 ); + } + } + break; + } + default: + break; + } + + return cmdList; +} + +//! Constructor +QwtPickerDragLineMachine::QwtPickerDragLineMachine(): + QwtPickerMachine( PolygonSelection ) +{ +} + +//! Transition +QList QwtPickerDragLineMachine::transition( + const QwtEventPattern &eventPattern, const QEvent *event ) +{ + QList cmdList; + + switch( event->type() ) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( QwtEventPattern::MouseSelect1, + static_cast( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + cmdList += Append; + setState( 1 ); + } + } + break; + } + case QEvent::KeyPress: + { + if ( eventPattern.keyMatch( QwtEventPattern::KeySelect1, + static_cast ( event ) ) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + cmdList += Append; + setState( 1 ); + } + else + { + cmdList += End; + setState( 0 ); + } + } + break; + } + case QEvent::MouseMove: + case QEvent::Wheel: + { + if ( state() != 0 ) + cmdList += Move; + + break; + } + case QEvent::MouseButtonRelease: + { + if ( state() != 0 ) + { + cmdList += End; + setState( 0 ); + } + } + default: + break; + } + + return cmdList; +} diff --git a/qwt/src/qwt_picker_machine.h b/qwt/src/qwt_picker_machine.h new file mode 100644 index 000000000..6164b93b5 --- /dev/null +++ b/qwt/src/qwt_picker_machine.h @@ -0,0 +1,214 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PICKER_MACHINE +#define QWT_PICKER_MACHINE 1 + +#include "qwt_global.h" +#include + +class QEvent; +class QwtEventPattern; + +/*! + \brief A state machine for QwtPicker selections + + QwtPickerMachine accepts key and mouse events and translates them + into selection commands. + + \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode +*/ + +class QWT_EXPORT QwtPickerMachine +{ +public: + /*! + Type of a selection. + \sa selectionType() + */ + enum SelectionType + { + //! The state machine not usable for any type of selection. + NoSelection = -1, + + //! The state machine is for selecting a single point. + PointSelection, + + //! The state machine is for selecting a rectangle (2 points). + RectSelection, + + //! The state machine is for selecting a polygon (many points). + PolygonSelection + }; + + //! Commands - the output of a state machine + enum Command + { + Begin, + Append, + Move, + Remove, + End + }; + + QwtPickerMachine( SelectionType ); + virtual ~QwtPickerMachine(); + + //! Transition + virtual QList transition( + const QwtEventPattern &, const QEvent * ) = 0; + void reset(); + + int state() const; + void setState( int ); + + SelectionType selectionType() const; + +private: + const SelectionType d_selectionType; + int d_state; +}; + +/*! + \brief A state machine for indicating mouse movements + + QwtPickerTrackerMachine supports displaying information + corresponding to mouse movements, but is not intended for + selecting anything. Begin/End are related to Enter/Leave events. +*/ +class QWT_EXPORT QwtPickerTrackerMachine: public QwtPickerMachine +{ +public: + QwtPickerTrackerMachine(); + + virtual QList transition( + const QwtEventPattern &, const QEvent * ); +}; + +/*! + \brief A state machine for point selections + + Pressing QwtEventPattern::MouseSelect1 or + QwtEventPattern::KeySelect1 selects a point. + + \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode +*/ +class QWT_EXPORT QwtPickerClickPointMachine: public QwtPickerMachine +{ +public: + QwtPickerClickPointMachine(); + + virtual QList transition( + const QwtEventPattern &, const QEvent * ); +}; + +/*! + \brief A state machine for point selections + + Pressing QwtEventPattern::MouseSelect1 or QwtEventPattern::KeySelect1 + starts the selection, releasing QwtEventPattern::MouseSelect1 or + a second press of QwtEventPattern::KeySelect1 terminates it. +*/ +class QWT_EXPORT QwtPickerDragPointMachine: public QwtPickerMachine +{ +public: + QwtPickerDragPointMachine(); + + virtual QList transition( + const QwtEventPattern &, const QEvent * ); +}; + +/*! + \brief A state machine for rectangle selections + + Pressing QwtEventPattern::MouseSelect1 starts + the selection, releasing it selects the first point. Pressing it + again selects the second point and terminates the selection. + Pressing QwtEventPattern::KeySelect1 also starts the + selection, a second press selects the first point. A third one selects + the second point and terminates the selection. + + \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode +*/ + +class QWT_EXPORT QwtPickerClickRectMachine: public QwtPickerMachine +{ +public: + QwtPickerClickRectMachine(); + + virtual QList transition( + const QwtEventPattern &, const QEvent * ); +}; + +/*! + \brief A state machine for rectangle selections + + Pressing QwtEventPattern::MouseSelect1 selects + the first point, releasing it the second point. + Pressing QwtEventPattern::KeySelect1 also selects the + first point, a second press selects the second point and terminates + the selection. + + \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode +*/ + +class QWT_EXPORT QwtPickerDragRectMachine: public QwtPickerMachine +{ +public: + QwtPickerDragRectMachine(); + + virtual QList transition( + const QwtEventPattern &, const QEvent * ); +}; + +/*! + \brief A state machine for line selections + + Pressing QwtEventPattern::MouseSelect1 selects + the first point, releasing it the second point. + Pressing QwtEventPattern::KeySelect1 also selects the + first point, a second press selects the second point and terminates + the selection. + + A common use case of QwtPickerDragLineMachine are pickers for + distance measurements. + + \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode +*/ + +class QWT_EXPORT QwtPickerDragLineMachine: public QwtPickerMachine +{ +public: + QwtPickerDragLineMachine(); + + virtual QList transition( + const QwtEventPattern &, const QEvent * ); +}; + +/*! + \brief A state machine for polygon selections + + Pressing QwtEventPattern::MouseSelect1 or QwtEventPattern::KeySelect1 + starts the selection and selects the first point, or appends a point. + Pressing QwtEventPattern::MouseSelect2 or QwtEventPattern::KeySelect2 + appends the last point and terminates the selection. + + \sa QwtEventPattern::MousePatternCode, QwtEventPattern::KeyPatternCode +*/ + +class QWT_EXPORT QwtPickerPolygonMachine: public QwtPickerMachine +{ +public: + QwtPickerPolygonMachine(); + + virtual QList transition( + const QwtEventPattern &, const QEvent * ); +}; + +#endif diff --git a/qwt/src/qwt_pixel_matrix.cpp b/qwt/src/qwt_pixel_matrix.cpp new file mode 100644 index 000000000..627992c7a --- /dev/null +++ b/qwt/src/qwt_pixel_matrix.cpp @@ -0,0 +1,51 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2003 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_pixel_matrix.h" + +/*! + \brief Constructor + + \param rect Bounding rectangle for the matrix +*/ +QwtPixelMatrix::QwtPixelMatrix( const QRect& rect ): + QBitArray( qMax( rect.width() * rect.height(), 0 ) ), + d_rect( rect ) +{ +} + +//! Destructor +QwtPixelMatrix::~QwtPixelMatrix() +{ +} + +/*! + Set the bounding rectangle of the matrix + + \param rect Bounding rectangle + + \note All bits are cleared + */ +void QwtPixelMatrix::setRect( const QRect& rect ) +{ + if ( rect != d_rect ) + { + d_rect = rect; + const int sz = qMax( rect.width() * rect.height(), 0 ); + resize( sz ); + } + + fill( false ); +} + +//! \return Bounding rectangle +QRect QwtPixelMatrix::rect() const +{ + return d_rect; +} diff --git a/qwt/src/qwt_pixel_matrix.h b/qwt/src/qwt_pixel_matrix.h new file mode 100644 index 000000000..0fe9daf1c --- /dev/null +++ b/qwt/src/qwt_pixel_matrix.h @@ -0,0 +1,98 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2003 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PIXEL_MATRIX_H +#define QWT_PIXEL_MATRIX_H + +#include "qwt_global.h" +#include +#include + +/*! + \brief A bit field corresponding to the pixels of a rectangle + + QwtPixelMatrix is intended to filter out duplicates in an + unsorted array of points. +*/ +class QWT_EXPORT QwtPixelMatrix: public QBitArray +{ +public: + QwtPixelMatrix( const QRect& rect ); + ~QwtPixelMatrix(); + + void setRect( const QRect& rect ); + QRect rect() const; + + bool testPixel( int x, int y ) const; + bool testAndSetPixel( int x, int y, bool on ); + + int index( int x, int y ) const; + +private: + QRect d_rect; +}; + +/*! + \brief Test if a pixel has been set + + \param x X-coordinate + \param y Y-coordinate + + \return true, when pos is outside of rect(), or when the pixel + has already been set. + */ +inline bool QwtPixelMatrix::testPixel( int x, int y ) const +{ + const int idx = index( x, y ); + return ( idx >= 0 ) ? testBit( idx ) : true; +} + +/*! + \brief Set a pixel and test if a pixel has been set before + + \param x X-coordinate + \param y Y-coordinate + \param on Set/Clear the pixel + + \return true, when pos is outside of rect(), or when the pixel + was set before. + */ +inline bool QwtPixelMatrix::testAndSetPixel( int x, int y, bool on ) +{ + const int idx = index( x, y ); + if ( idx < 0 ) + return true; + + const bool onBefore = testBit( idx ); + setBit( idx, on ); + + return onBefore; +} + +/*! + \brief Calculate the index in the bit field corresponding to a position + + \param x X-coordinate + \param y Y-coordinate + \return Index, when rect() contains pos - otherwise -1. + */ +inline int QwtPixelMatrix::index( int x, int y ) const +{ + const int dx = x - d_rect.x(); + if ( dx < 0 || dx >= d_rect.width() ) + return -1; + + const int dy = y - d_rect.y(); + if ( dy < 0 || dy >= d_rect.height() ) + return -1; + + return dy * d_rect.width() + dx; +} + +#endif diff --git a/qwt/src/qwt_plot.cpp b/qwt/src/qwt_plot.cpp new file mode 100644 index 000000000..eb4c7e6b4 --- /dev/null +++ b/qwt/src/qwt_plot.cpp @@ -0,0 +1,1180 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot.h" +#include "qwt_plot_dict.h" +#include "qwt_plot_layout.h" +#include "qwt_scale_widget.h" +#include "qwt_scale_engine.h" +#include "qwt_text_label.h" +#include "qwt_legend.h" +#include "qwt_legend_data.h" +#include "qwt_plot_canvas.h" +#include "qwt_scale_map_table.h" +#include +#include +#include +#include +#include +#include + +static inline void qwtEnableLegendItems( QwtPlot *plot, bool on ) +{ + if ( on ) + { + QObject::connect( + plot, SIGNAL( legendDataChanged( + const QVariant &, const QList & ) ), + plot, SLOT( updateLegendItems( + const QVariant &, const QList & ) ) ); + } + else + { + QObject::disconnect( + plot, SIGNAL( legendDataChanged( + const QVariant &, const QList & ) ), + plot, SLOT( updateLegendItems( + const QVariant &, const QList & ) ) ); + } +} + +static void qwtSetTabOrder( + QWidget *first, QWidget *second, bool withChildren ) +{ + QList tabChain; + tabChain += first; + tabChain += second; + + if ( withChildren ) + { + QList children = second->findChildren(); + + QWidget *w = second->nextInFocusChain(); + while ( children.contains( w ) ) + { + children.removeAll( w ); + + tabChain += w; + w = w->nextInFocusChain(); + } + } + + for ( int i = 0; i < tabChain.size() - 1; i++ ) + { + QWidget *from = tabChain[i]; + QWidget *to = tabChain[i+1]; + + const Qt::FocusPolicy policy1 = from->focusPolicy(); + const Qt::FocusPolicy policy2 = to->focusPolicy(); + + QWidget *proxy1 = from->focusProxy(); + QWidget *proxy2 = to->focusProxy(); + + from->setFocusPolicy( Qt::TabFocus ); + from->setFocusProxy( NULL); + + to->setFocusPolicy( Qt::TabFocus ); + to->setFocusProxy( NULL); + + QWidget::setTabOrder( from, to ); + + from->setFocusPolicy( policy1 ); + from->setFocusProxy( proxy1); + + to->setFocusPolicy( policy2 ); + to->setFocusProxy( proxy2 ); + } +} + +QwtScaleMapTable qwtScaleMapTable( const QwtPlot *plot ) +{ + QwtScaleMapTable table; + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + for ( int i = 0; i < plot->axesCount( axisPos ); i++ ) + table.maps[axisPos] += plot->canvasMap( QwtAxisId( axisPos, i ) ); + } + + return table; +} + +class QwtPlot::PrivateData +{ +public: + QPointer titleLabel; + QPointer footerLabel; + QPointer canvas; + QPointer legend; + QwtPlotLayout *layout; + + bool autoReplot; +}; + +/*! + \brief Constructor + \param parent Parent widget + */ +QwtPlot::QwtPlot( QWidget *parent ): + QFrame( parent ) +{ + initPlot( QwtText() ); +} + +/*! + \brief Constructor + \param title Title text + \param parent Parent widget + */ +QwtPlot::QwtPlot( const QwtText &title, QWidget *parent ): + QFrame( parent ) +{ + initPlot( title ); +} + +//! Destructor +QwtPlot::~QwtPlot() +{ + detachItems( QwtPlotItem::Rtti_PlotItem, autoDelete() ); + + delete d_data->layout; + deleteScaleData(); + delete d_data; +} + +/*! + \brief Initializes a QwtPlot instance + \param title Title text + */ +void QwtPlot::initPlot( const QwtText &title ) +{ + d_data = new PrivateData; + + d_data->layout = new QwtPlotLayout; + d_data->autoReplot = false; + + // title + d_data->titleLabel = new QwtTextLabel( this ); + d_data->titleLabel->setObjectName( "QwtPlotTitle" ); + d_data->titleLabel->setFont( QFont( fontInfo().family(), 14, QFont::Bold ) ); + + QwtText text( title ); + text.setRenderFlags( Qt::AlignCenter | Qt::TextWordWrap ); + d_data->titleLabel->setText( text ); + + // footer + d_data->footerLabel = new QwtTextLabel( this ); + d_data->footerLabel->setObjectName( "QwtPlotFooter" ); + + QwtText footer; + footer.setRenderFlags( Qt::AlignCenter | Qt::TextWordWrap ); + d_data->footerLabel->setText( footer ); + + // legend + d_data->legend = NULL; + + // scales + initScaleData(); + + // canvas + d_data->canvas = new QwtPlotCanvas( this ); + d_data->canvas->setObjectName( "QwtPlotCanvas" ); + d_data->canvas->installEventFilter( this ); + + setSizePolicy( QSizePolicy::MinimumExpanding, + QSizePolicy::MinimumExpanding ); + + resize( 200, 200 ); + + QList focusChain; + focusChain << this << d_data->titleLabel; + + for ( int i = 0; i < axesCount( QwtAxis::xTop ); i++ ) + focusChain << axisWidget( QwtAxisId( QwtAxis::xTop, i ) ); + + for ( int i = 0; i < axesCount( QwtAxis::yLeft ); i++ ) + focusChain << axisWidget( QwtAxisId( QwtAxis::yLeft, i ) ); + + focusChain << d_data->canvas; + + for ( int i = 0; i < axesCount( QwtAxis::yRight ); i++ ) + focusChain << axisWidget( QwtAxisId( QwtAxis::yRight, i ) ); + + for ( int i = 0; i < axesCount( QwtAxis::xBottom ); i++ ) + focusChain << axisWidget( QwtAxisId( QwtAxis::xBottom, i ) ); + + focusChain << d_data->footerLabel; + + for ( int i = 0; i < focusChain.size() - 1; i++ ) + qwtSetTabOrder( focusChain[i], focusChain[i+1], false ); + + qwtEnableLegendItems( this, true ); +} + +/*! + \brief Set the drawing canvas of the plot widget + + QwtPlot invokes methods of the canvas as meta methods ( see QMetaObject ). + In opposite to using conventional C++ techniques like virtual methods + they allow to use canvas implementations that are derived from + QWidget or QGLWidget. + + The following meta methods could be implemented: + + - replot() + When the canvas doesn't offer a replot method, QwtPlot calls + update() instead. + + - borderPath() + The border path is necessary to clip the content of the canvas + When the canvas doesn't have any special border ( f.e rounded corners ) + it is o.k. not to implement this method. + + The default canvas is a QwtPlotCanvas + + \param canvas Canvas Widget + \sa canvas() + */ +void QwtPlot::setCanvas( QWidget *canvas ) +{ + if ( canvas == d_data->canvas ) + return; + + delete d_data->canvas; + d_data->canvas = canvas; + + if ( canvas ) + { + canvas->setParent( this ); + canvas->installEventFilter( this ); + + if ( isVisible() ) + canvas->show(); + } +} + +/*! + \brief Adds handling of layout requests + \param event Event + + \return See QFrame::event() +*/ +bool QwtPlot::event( QEvent *event ) +{ + bool ok = QFrame::event( event ); + switch ( event->type() ) + { + case QEvent::LayoutRequest: + updateLayout(); + break; + case QEvent::PolishRequest: + replot(); + break; + default:; + } + return ok; +} + +/*! + \brief Event filter + + The plot handles the following events for the canvas: + + - QEvent::Resize + The canvas margins might depend on its size + + - QEvent::ContentsRectChange + The layout needs to be recalculated + + \param object Object to be filtered + \param event Event + + \return See QFrame::eventFilter() + + \sa updateCanvasMargins(), updateLayout() +*/ +bool QwtPlot::eventFilter( QObject *object, QEvent *event ) +{ + if ( object == d_data->canvas ) + { + if ( event->type() == QEvent::Resize ) + { + updateCanvasMargins(); + } + else if ( event->type() == QEvent::ContentsRectChange ) + { + updateLayout(); + } + } + + return QFrame::eventFilter( object, event ); +} + +//! Replots the plot if autoReplot() is \c true. +void QwtPlot::autoRefresh() +{ + if ( d_data->autoReplot ) + replot(); +} + +/*! + \brief Set or reset the autoReplot option + + If the autoReplot option is set, the plot will be + updated implicitly by manipulating member functions. + Since this may be time-consuming, it is recommended + to leave this option switched off and call replot() + explicitly if necessary. + + The autoReplot option is set to false by default, which + means that the user has to call replot() in order to make + changes visible. + \param tf \c true or \c false. Defaults to \c true. + \sa replot() +*/ +void QwtPlot::setAutoReplot( bool tf ) +{ + d_data->autoReplot = tf; +} + +/*! + \return true if the autoReplot option is set. + \sa setAutoReplot() +*/ +bool QwtPlot::autoReplot() const +{ + return d_data->autoReplot; +} + +/*! + Change the plot's title + \param title New title +*/ +void QwtPlot::setTitle( const QString &title ) +{ + if ( title != d_data->titleLabel->text().text() ) + { + d_data->titleLabel->setText( title ); + updateLayout(); + } +} + +/*! + Change the plot's title + \param title New title +*/ +void QwtPlot::setTitle( const QwtText &title ) +{ + if ( title != d_data->titleLabel->text() ) + { + d_data->titleLabel->setText( title ); + updateLayout(); + } +} + +//! \return Title of the plot +QwtText QwtPlot::title() const +{ + return d_data->titleLabel->text(); +} + +//! \return Title label widget. +QwtTextLabel *QwtPlot::titleLabel() +{ + return d_data->titleLabel; +} + +//! \return Title label widget. +const QwtTextLabel *QwtPlot::titleLabel() const +{ + return d_data->titleLabel; +} + +/*! + Change the text the footer + \param text New text of the footer +*/ +void QwtPlot::setFooter( const QString &text ) +{ + if ( text != d_data->footerLabel->text().text() ) + { + d_data->footerLabel->setText( text ); + updateLayout(); + } +} + +/*! + Change the text the footer + \param text New text of the footer +*/ +void QwtPlot::setFooter( const QwtText &text ) +{ + if ( text != d_data->footerLabel->text() ) + { + d_data->footerLabel->setText( text ); + updateLayout(); + } +} + +//! \return Text of the footer +QwtText QwtPlot::footer() const +{ + return d_data->footerLabel->text(); +} + +//! \return Footer label widget. +QwtTextLabel *QwtPlot::footerLabel() +{ + return d_data->footerLabel; +} + +//! \return Footer label widget. +const QwtTextLabel *QwtPlot::footerLabel() const +{ + return d_data->footerLabel; +} + +/*! + \brief Assign a new plot layout + + \param layout Layout() + \sa plotLayout() + */ +void QwtPlot::setPlotLayout( QwtPlotLayout *layout ) +{ + if ( layout != d_data->layout ) + { + delete d_data->layout; + layout = d_data->layout; + + updateLayout(); + } +} + +//! \return the plot's layout +QwtPlotLayout *QwtPlot::plotLayout() +{ + return d_data->layout; +} + +//! \return the plot's layout +const QwtPlotLayout *QwtPlot::plotLayout() const +{ + return d_data->layout; +} + +/*! + \return the plot's legend + \sa insertLegend() +*/ +QwtAbstractLegend *QwtPlot::legend() +{ + return d_data->legend; +} + +/*! + \return the plot's legend + \sa insertLegend() +*/ +const QwtAbstractLegend *QwtPlot::legend() const +{ + return d_data->legend; +} + + +/*! + \return the plot's canvas +*/ +QWidget *QwtPlot::canvas() +{ + return d_data->canvas; +} + +/*! + \return the plot's canvas +*/ +const QWidget *QwtPlot::canvas() const +{ + return d_data->canvas; +} + +/*! + \return Size hint for the plot widget + \sa minimumSizeHint() +*/ +QSize QwtPlot::sizeHint() const +{ + int dw = 0; + int dh = 0; + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + for ( int i = 0; i < axesCount( axisPos ); i++ ) + { + const QwtAxisId axisId( axisPos, i ); + + if ( isAxisVisible( axisId ) ) + { + const int niceDist = 40; + const QwtScaleWidget *scaleWidget = axisWidget( axisId ); + const QwtScaleDiv &scaleDiv = scaleWidget->scaleDraw()->scaleDiv(); + const int majCnt = scaleDiv.ticks( QwtScaleDiv::MajorTick ).count(); + + const QSize hint = scaleWidget->minimumSizeHint(); + + if ( QwtAxis::isYAxis( axisPos ) ) + { + const int hDiff = ( majCnt - 1 ) * niceDist - hint.height(); + dh = qMax( dh, hDiff ); + } + else + { + const int wDiff = ( majCnt - 1 ) * niceDist - hint.width(); + dw = qMax( dw, wDiff ); + } + } + } + } + + return minimumSizeHint() + QSize( dw, dh ); +} + +/*! + \brief Return a minimum size hint +*/ +QSize QwtPlot::minimumSizeHint() const +{ + QSize hint = d_data->layout->minimumSizeHint( this ); + hint += QSize( 2 * frameWidth(), 2 * frameWidth() ); + + return hint; +} + +/*! + Resize and update internal layout + \param e Resize event +*/ +void QwtPlot::resizeEvent( QResizeEvent *e ) +{ + QFrame::resizeEvent( e ); + updateLayout(); +} + +/*! + \brief Redraw the plot + + If the autoReplot option is not set (which is the default) + or if any curves are attached to raw data, the plot has to + be refreshed explicitly in order to make changes visible. + + \sa updateAxes(), setAutoReplot() +*/ +void QwtPlot::replot() +{ + bool doAutoReplot = autoReplot(); + setAutoReplot( false ); + + updateAxes(); + + /* + Maybe the layout needs to be updated, because of changed + axes labels. We need to process them here before painting + to avoid that scales and canvas get out of sync. + */ + QApplication::sendPostedEvents( this, QEvent::LayoutRequest ); + + if ( d_data->canvas ) + { + const bool ok = QMetaObject::invokeMethod( + d_data->canvas, "replot", Qt::DirectConnection ); + if ( !ok ) + { + // fallback, when canvas has no a replot method + d_data->canvas->update( d_data->canvas->contentsRect() ); + } + } + + setAutoReplot( doAutoReplot ); +} + +/*! + \brief Adjust plot content to its current size. + \sa resizeEvent() +*/ +void QwtPlot::updateLayout() +{ + d_data->layout->update( this, contentsRect() ); + + const QRect titleRect = d_data->layout->titleRect().toRect(); + const QRect footerRect = d_data->layout->footerRect().toRect(); + const QRect legendRect = d_data->layout->legendRect().toRect(); + const QRect canvasRect = d_data->layout->canvasRect().toRect(); + + // resize and show the visible widgets + + if ( !d_data->titleLabel->text().isEmpty() ) + { + d_data->titleLabel->setGeometry( titleRect ); + if ( !d_data->titleLabel->isVisibleTo( this ) ) + d_data->titleLabel->show(); + } + else + d_data->titleLabel->hide(); + + if ( !d_data->footerLabel->text().isEmpty() ) + { + d_data->footerLabel->setGeometry( footerRect ); + if ( !d_data->footerLabel->isVisibleTo( this ) ) + d_data->footerLabel->show(); + } + else + d_data->footerLabel->hide(); + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + for ( int i = 0; i < axesCount( axisPos ); i++ ) + { + const QwtAxisId axisId( axisPos, i ); + + QwtScaleWidget *scaleWidget = axisWidget( axisId ); + if ( isAxisVisible( axisId ) ) + { + const QRect scaleRect = d_data->layout->scaleRect( axisId ).toRect(); + scaleWidget->setGeometry( scaleRect ); + + if ( !scaleWidget->isVisibleTo( this ) ) + scaleWidget->show(); + } + else + { + scaleWidget->hide(); + } + } + } + + if ( d_data->legend ) + { + if ( d_data->legend->isEmpty() ) + { + d_data->legend->hide(); + } + else + { + d_data->legend->setGeometry( legendRect ); + d_data->legend->show(); + } + } + + d_data->canvas->setGeometry( canvasRect ); +} + +/*! + \brief Calculate the canvas margins + + \param maps QwtAxis::NumPositions maps, mapping between plot and paint device coordinates + \param canvasRect Bounding rectangle where to paint + \param left Return parameter for the left margin + \param top Return parameter for the top margin + \param right Return parameter for the right margin + \param bottom Return parameter for the bottom margin + + Plot items might indicate, that they need some extra space + at the borders of the canvas by the QwtPlotItem::Margins flag. + + updateCanvasMargins(), QwtPlotItem::getCanvasMarginHint() + */ +void QwtPlot::getCanvasMarginsHint( + const QwtScaleMapTable& mapsTable, const QRectF &canvasRect, + double &left, double &top, double &right, double &bottom) const +{ + left = top = right = bottom = -1.0; + + const QwtPlotItemList& itmList = itemList(); + for ( QwtPlotItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + const QwtPlotItem *item = *it; + if ( item->testItemAttribute( QwtPlotItem::Margins ) && + mapsTable.isValid( item->xAxis() ) && + mapsTable.isValid( item->yAxis() ) ) + { + double m[ QwtAxis::PosCount ]; + item->getCanvasMarginHint( + mapsTable.map( item->xAxis() ), + mapsTable.map( item->yAxis() ), + canvasRect, m[QwtAxis::yLeft], m[QwtAxis::xTop], + m[QwtAxis::yRight], m[QwtAxis::xBottom] ); + + left = qMax( left, m[QwtAxis::yLeft] ); + top = qMax( top, m[QwtAxis::xTop] ); + right = qMax( right, m[QwtAxis::yRight] ); + bottom = qMax( bottom, m[QwtAxis::xBottom] ); + } + } +} + +/*! + \brief Update the canvas margins + + Plot items might indicate, that they need some extra space + at the borders of the canvas by the QwtPlotItem::Margins flag. + + getCanvasMarginsHint(), QwtPlotItem::getCanvasMarginHint() + */ +void QwtPlot::updateCanvasMargins() +{ + double margins[QwtAxis::PosCount]; + getCanvasMarginsHint( qwtScaleMapTable( this ), canvas()->contentsRect(), + margins[QwtAxis::yLeft], margins[QwtAxis::xTop], + margins[QwtAxis::yRight], margins[QwtAxis::xBottom] ); + + bool doUpdate = false; + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + if ( margins[axisPos] >= 0.0 ) + { + const int m = qCeil( margins[axisPos] ); + plotLayout()->setCanvasMargin( m, axisPos); + doUpdate = true; + } + } + + if ( doUpdate ) + updateLayout(); +} + +/*! + Redraw the canvas. + \param painter Painter used for drawing + + \warning drawCanvas calls drawItems what is also used + for printing. Applications that like to add individual + plot items better overload drawItems() + \sa drawItems() +*/ +void QwtPlot::drawCanvas( QPainter *painter ) +{ + const QwtScaleMapTable table = qwtScaleMapTable( this ); + drawItems( painter, d_data->canvas->contentsRect(), table ); +} + +/*! + Redraw the canvas items. + + \param painter Painter used for drawing + \param canvasRect Bounding rectangle where to paint + \param maps QwtAxis::NumPositions maps, mapping between plot and paint device coordinates + + \note Usually canvasRect is contentsRect() of the plot canvas. + Due to a bug in Qt this rectangle might be wrong for certain + frame styles ( f.e QFrame::Box ) and it might be necessary to + fix the margins manually using QWidget::setContentsMargins() +*/ + +void QwtPlot::drawItems( QPainter *painter, + const QRectF &canvasRect, const QwtScaleMapTable &mapTable ) const +{ + const QwtPlotItemList& itmList = itemList(); + for ( QwtPlotItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + QwtPlotItem *item = *it; + if ( item && item->isVisible() ) + { + if ( mapTable.isValid( item->xAxis() ) && + mapTable.isValid( item->yAxis() ) ) + { + painter->save(); + + painter->setRenderHint( QPainter::Antialiasing, + item->testRenderHint( QwtPlotItem::RenderAntialiased ) ); + painter->setRenderHint( QPainter::HighQualityAntialiasing, + item->testRenderHint( QwtPlotItem::RenderAntialiased ) ); + + item->draw( painter, mapTable.map( item->xAxis() ), + mapTable.map( item->yAxis() ), canvasRect ); + + painter->restore(); + } + } + } +} + +/*! + \param axisPos Axis + \return Map for the axis on the canvas. With this map pixel coordinates can + translated to plot coordinates and vice versa. + \sa QwtScaleMap, transform(), invTransform() + +*/ +QwtScaleMap QwtPlot::canvasMap( QwtAxisId axisId ) const +{ + QwtScaleMap map; + if ( !d_data->canvas ) + return map; + + map.setTransformation( axisScaleEngine( axisId )->transformation() ); + + const QwtScaleDiv &sd = axisScaleDiv( axisId ); + map.setScaleInterval( sd.lowerBound(), sd.upperBound() ); + + if ( isAxisVisible( axisId ) ) + { + const QwtScaleWidget *s = axisWidget( axisId ); + if ( QwtAxis::isYAxis( axisId.pos ) ) + { + double y = s->y() + s->startBorderDist() - d_data->canvas->y(); + double h = s->height() - s->startBorderDist() - s->endBorderDist(); + map.setPaintInterval( y + h, y ); + } + else + { + double x = s->x() + s->startBorderDist() - d_data->canvas->x(); + double w = s->width() - s->startBorderDist() - s->endBorderDist(); + map.setPaintInterval( x, x + w ); + } + } + else + { + int margin = 0; + if ( !plotLayout()->alignCanvasToScale( axisId.pos ) ) + margin = plotLayout()->canvasMargin( axisId.pos ); + + const QRect &canvasRect = d_data->canvas->contentsRect(); + if ( QwtAxis::isYAxis( axisId.pos ) ) + { + map.setPaintInterval( canvasRect.bottom() - margin, + canvasRect.top() + margin ); + } + else + { + map.setPaintInterval( canvasRect.left() + margin, + canvasRect.right() - margin ); + } + } + return map; +} + +/*! + \brief Change the background of the plotting area + + Sets brush to QPalette::Window of all color groups of + the palette of the canvas. Using canvas()->setPalette() + is a more powerful way to set these colors. + + \param brush New background brush + \sa canvasBackground() +*/ +void QwtPlot::setCanvasBackground( const QBrush &brush ) +{ + QPalette pal = d_data->canvas->palette(); + pal.setBrush( QPalette::Window, brush ); + + canvas()->setPalette( pal ); +} + +/*! + Nothing else than: canvas()->palette().brush( + QPalette::Normal, QPalette::Window); + + \return Background brush of the plotting area. + \sa setCanvasBackground() +*/ +QBrush QwtPlot::canvasBackground() const +{ + return canvas()->palette().brush( + QPalette::Normal, QPalette::Window ); +} + +/*! + \brief Insert a legend + + If the position legend is \c QwtPlot::LeftLegend or \c QwtPlot::RightLegend + the legend will be organized in one column from top to down. + Otherwise the legend items will be placed in a table + with a best fit number of columns from left to right. + + insertLegend() will set the plot widget as parent for the legend. + The legend will be deleted in the destructor of the plot or when + another legend is inserted. + + Legends, that are not inserted into the layout of the plot widget + need to connect to the legendDataChanged() signal. Calling updateLegend() + initiates this signal for an initial update. When the application code + wants to implement its own layout this also needs to be done for + rendering plots to a document ( see QwtPlotRenderer ). + + \param legend Legend + \param pos The legend's position. For top/left position the number + of columns will be limited to 1, otherwise it will be set to + unlimited. + + \param ratio Ratio between legend and the bounding rectangle + of title, canvas and axes. The legend will be shrunk + if it would need more space than the given ratio. + The ratio is limited to ]0.0 .. 1.0]. In case of <= 0.0 + it will be reset to the default ratio. + The default vertical/horizontal ratio is 0.33/0.5. + + \sa legend(), QwtPlotLayout::legendPosition(), + QwtPlotLayout::setLegendPosition() +*/ +void QwtPlot::insertLegend( QwtAbstractLegend *legend, + QwtPlot::LegendPosition pos, double ratio ) +{ + d_data->layout->setLegendPosition( pos, ratio ); + + if ( legend != d_data->legend ) + { + if ( d_data->legend && d_data->legend->parent() == this ) + delete d_data->legend; + + d_data->legend = legend; + + if ( d_data->legend ) + { + connect( this, + SIGNAL( legendDataChanged( + const QVariant &, const QList & ) ), + d_data->legend, + SLOT( updateLegend( + const QVariant &, const QList & ) ) + ); + + if ( d_data->legend->parent() != this ) + d_data->legend->setParent( this ); + + qwtEnableLegendItems( this, false ); + updateLegend(); + qwtEnableLegendItems( this, true ); + + QwtLegend *lgd = qobject_cast( legend ); + if ( lgd ) + { + switch ( d_data->layout->legendPosition() ) + { + case LeftLegend: + case RightLegend: + { + if ( lgd->maxColumns() == 0 ) + lgd->setMaxColumns( 1 ); // 1 column: align vertical + break; + } + case TopLegend: + case BottomLegend: + { + lgd->setMaxColumns( 0 ); // unlimited + break; + } + default: + break; + } + } + + QWidget *previousInChain = NULL; + switch ( d_data->layout->legendPosition() ) + { + case LeftLegend: + { + previousInChain = axisWidget( + QwtAxisId( QwtAxis::xTop, axesCount( QwtAxis::xTop ) - 1 ) ); + break; + } + case TopLegend: + { + previousInChain = this; + break; + } + case RightLegend: + { + previousInChain = axisWidget( + QwtAxisId( QwtAxis::yRight, axesCount( QwtAxis::yRight ) - 1 ) ); + break; + } + case BottomLegend: + { + previousInChain = footerLabel(); + break; + } + } + + if ( previousInChain ) + qwtSetTabOrder( previousInChain, legend, true ); + } + } + + updateLayout(); +} + +/*! + Emit legendDataChanged() for all plot item + + \sa QwtPlotItem::legendData(), legendDataChanged() + */ +void QwtPlot::updateLegend() +{ + const QwtPlotItemList& itmList = itemList(); + for ( QwtPlotItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + updateLegend( *it ); + } +} + +/*! + Emit legendDataChanged() for a plot item + + \param plotItem Plot item + \sa QwtPlotItem::legendData(), legendDataChanged() + */ +void QwtPlot::updateLegend( const QwtPlotItem *plotItem ) +{ + if ( plotItem == NULL ) + return; + + QList legendData; + + if ( plotItem->testItemAttribute( QwtPlotItem::Legend ) ) + legendData = plotItem->legendData(); + + const QVariant itemInfo = itemToInfo( const_cast< QwtPlotItem *>( plotItem) ); + Q_EMIT legendDataChanged( itemInfo, legendData ); +} + +/*! + \brief Update all plot items interested in legend attributes + + Call QwtPlotItem::updateLegend(), when the QwtPlotItem::LegendInterest + flag is set. + + \param itemInfo Info about the plot item + \param legendData Entries to be displayed for the plot item ( usually 1 ) + + \sa QwtPlotItem::LegendInterest, + QwtPlotLegendItem, QwtPlotItem::updateLegend() + */ +void QwtPlot::updateLegendItems( const QVariant &itemInfo, + const QList &legendData ) +{ + QwtPlotItem *plotItem = infoToItem( itemInfo ); + if ( plotItem ) + { + const QwtPlotItemList& itmList = itemList(); + for ( QwtPlotItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + QwtPlotItem *item = *it; + if ( item->testItemInterest( QwtPlotItem::LegendInterest ) ) + item->updateLegend( plotItem, legendData ); + } + } +} + +/*! + \brief Attach/Detach a plot item + + \param plotItem Plot item + \param on When true attach the item, otherwise detach it + */ +void QwtPlot::attachItem( QwtPlotItem *plotItem, bool on ) +{ + if ( plotItem->testItemInterest( QwtPlotItem::LegendInterest ) ) + { + // plotItem is some sort of legend + + const QwtPlotItemList& itmList = itemList(); + for ( QwtPlotItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + QwtPlotItem *item = *it; + + QList legendData; + if ( on && item->testItemAttribute( QwtPlotItem::Legend ) ) + { + legendData = item->legendData(); + plotItem->updateLegend( item, legendData ); + } + } + } + + if ( on ) + insertItem( plotItem ); + else + removeItem( plotItem ); + + Q_EMIT itemAttached( plotItem, on ); + + if ( plotItem->testItemAttribute( QwtPlotItem::Legend ) ) + { + // the item wants to be represented on the legend + + if ( on ) + { + updateLegend( plotItem ); + } + else + { + const QVariant itemInfo = itemToInfo( plotItem ); + Q_EMIT legendDataChanged( itemInfo, QList() ); + } + } + + if ( autoReplot() ) + update(); +} + +/*! + \brief Build an information, that can be used to identify + a plot item on the legend. + + The default implementation simply wraps the plot item + into a QVariant object. When overloading itemToInfo() + usually infoToItem() needs to reimplemeted too. + +\code + QVariant itemInfo; + qVariantSetValue( itemInfo, plotItem ); +\endcode + + \param plotItem Plot item + \return Plot item embedded in a QVariant + \sa infoToItem() + */ +QVariant QwtPlot::itemToInfo( QwtPlotItem *plotItem ) const +{ + QVariant itemInfo; + qVariantSetValue( itemInfo, plotItem ); + + return itemInfo; +} + +/*! + \brief Identify the plot item according to an item info object, + that has bee generated from itemToInfo(). + + The default implementation simply tries to unwrap a QwtPlotItem + pointer: + +\code + if ( itemInfo.canConvert() ) + return qvariant_cast( itemInfo ); +\endcode + \param itemInfo Plot item + \return A plot item, when successful, otherwise a NULL pointer. + \sa itemToInfo() +*/ +QwtPlotItem *QwtPlot::infoToItem( const QVariant &itemInfo ) const +{ + if ( itemInfo.canConvert() ) + return qvariant_cast( itemInfo ); + + return NULL; +} + + diff --git a/qwt/src/qwt_plot.h b/qwt/src/qwt_plot.h new file mode 100644 index 000000000..b8ff76788 --- /dev/null +++ b/qwt/src/qwt_plot.h @@ -0,0 +1,325 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_H +#define QWT_PLOT_H + +#include "qwt_global.h" +#include "qwt_axis_id.h" +#include "qwt_text.h" +#include "qwt_plot_dict.h" +#include "qwt_scale_map.h" +#include "qwt_interval.h" +#include +#include +#include + +class QwtPlotLayout; +class QwtAbstractLegend; +class QwtScaleWidget; +class QwtScaleEngine; +class QwtScaleDiv; +class QwtScaleDraw; +class QwtTextLabel; +class QwtScaleMapTable; + +#define QWT_COMPAT 1 // flag to disable compatibilities - will be removed later +#define QWT_DUMMY_ID 0 // dummy id to help for migrating the code - will be removed later + +/*! + \brief A 2-D plotting widget + + QwtPlot is a widget for plotting two-dimensional graphs. + An unlimited number of plot items can be displayed on + its canvas. Plot items might be curves (QwtPlotCurve), markers + (QwtPlotMarker), the grid (QwtPlotGrid), or anything else derived + from QwtPlotItem. + A plot can have up to four axes, with each plot item attached to an x- and + a y axis. The scales at the axes can be explicitly set (QwtScaleDiv), or + are calculated from the plot items, using algorithms (QwtScaleEngine) which + can be configured separately for each axis. + + The simpleplot example is a good starting point to see how to set up a + plot widget. + + \image html plot.png + + \par Example + The following example shows (schematically) the most simple + way to use QwtPlot. By default, only the left and bottom axes are + visible and their scales are computed automatically. + \verbatim +#include +#include + +QwtPlot *myPlot = new QwtPlot("Two Curves", parent); + +// add curves +QwtPlotCurve *curve1 = new QwtPlotCurve("Curve 1"); +QwtPlotCurve *curve2 = new QwtPlotCurve("Curve 2"); + +// connect or copy the data to the curves +curve1->setData(...); +curve2->setData(...); + +curve1->attach(myPlot); +curve2->attach(myPlot); + +// finally, refresh the plot +myPlot->replot(); +\endverbatim +*/ + +class QWT_EXPORT QwtPlot: public QFrame, public QwtPlotDict +{ + Q_OBJECT + + Q_PROPERTY( QBrush canvasBackground + READ canvasBackground WRITE setCanvasBackground ) + Q_PROPERTY( bool autoReplot READ autoReplot WRITE setAutoReplot ) + +#if 0 + // This property is intended to configure the plot + // widget from a special dialog in the deigner plugin. + // Disabled until such a dialog has been implemented. + + Q_PROPERTY( QString propertiesDocument + READ grabProperties WRITE applyProperties ) +#endif + +public: + /*! + Position of the legend, relative to the canvas. + + \sa insertLegend() + */ + enum LegendPosition + { + //! The legend will be left from the QwtAxis::yLeft axis. + LeftLegend, + + //! The legend will be right from the QwtAxis::yRight axis. + RightLegend, + + //! The legend will be below the footer + BottomLegend, + + //! The legend will be above the title + TopLegend + }; + + explicit QwtPlot( QWidget * = NULL ); + explicit QwtPlot( const QwtText &title, QWidget * = NULL ); + + virtual ~QwtPlot(); + + void applyProperties( const QString & ); + QString grabProperties() const; + + void setAutoReplot( bool on = true ); + bool autoReplot() const; + + // Layout + + void setPlotLayout( QwtPlotLayout * ); + + QwtPlotLayout *plotLayout(); + const QwtPlotLayout *plotLayout() const; + + // Title + + void setTitle( const QString & ); + void setTitle( const QwtText &t ); + QwtText title() const; + + QwtTextLabel *titleLabel(); + const QwtTextLabel *titleLabel() const; + + // Footer + + void setFooter( const QString & ); + void setFooter( const QwtText &t ); + QwtText footer() const; + + QwtTextLabel *footerLabel(); + const QwtTextLabel *footerLabel() const; + + // Canvas + + void setCanvas( QWidget * ); + + QWidget *canvas(); + const QWidget *canvas() const; + + void setCanvasBackground( const QBrush & ); + QBrush canvasBackground() const; + + virtual QwtScaleMap canvasMap( QwtAxisId ) const; + + double invTransform( QwtAxisId, double value ) const; + double transform( QwtAxisId, double value ) const; + + // Axes + + void setAxesCount( int axisPos, int count ); + + int axesCount( int axisPos, bool onlyVisible = false ) const; + bool isAxisValid( QwtAxisId ) const; + + QwtScaleEngine *axisScaleEngine( QwtAxisId ); + const QwtScaleEngine *axisScaleEngine( QwtAxisId ) const; + + void setAxisScaleEngine( QwtAxisId, QwtScaleEngine * ); + + void setAxisAutoScale( QwtAxisId, bool on = true ); + bool axisAutoScale( QwtAxisId ) const; + + void setAxisVisible( QwtAxisId, bool on = true ); + bool isAxisVisible( QwtAxisId ) const; + + void setAxisFont( QwtAxisId, const QFont & ); + QFont axisFont( QwtAxisId ) const; + + void setAxisScale( QwtAxisId, double min, double max, double step = 0 ); + void setAxisScaleDiv( QwtAxisId, const QwtScaleDiv & ); + void setAxisScaleDraw( QwtAxisId, QwtScaleDraw * ); + + double axisStepSize( QwtAxisId ) const; + QwtInterval axisInterval( QwtAxisId ) const; + + const QwtScaleDiv &axisScaleDiv( QwtAxisId ) const; + + const QwtScaleDraw *axisScaleDraw( QwtAxisId ) const; + QwtScaleDraw *axisScaleDraw( QwtAxisId ); + + const QwtScaleWidget *axisWidget( QwtAxisId ) const; + QwtScaleWidget *axisWidget( QwtAxisId ); + + void setAxisLabelAlignment( QwtAxisId, Qt::Alignment ); + void setAxisLabelRotation( QwtAxisId, double rotation ); + + void setAxisTitle( QwtAxisId, const QString & ); + void setAxisTitle( QwtAxisId, const QwtText & ); + QwtText axisTitle( QwtAxisId ) const; + + void setAxisMaxMinor( QwtAxisId, int maxMinor ); + int axisMaxMinor( QwtAxisId ) const; + + void setAxisMaxMajor( QwtAxisId, int maxMajor ); + int axisMaxMajor( QwtAxisId ) const; + + // Legend + + void insertLegend( QwtAbstractLegend *, + LegendPosition = QwtPlot::RightLegend, double ratio = -1.0 ); + + QwtAbstractLegend *legend(); + const QwtAbstractLegend *legend() const; + + void updateLegend(); + void updateLegend( const QwtPlotItem * ); + + // Misc + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + virtual void updateLayout(); + virtual void drawCanvas( QPainter * ); + + void updateAxes(); + void updateCanvasMargins(); + + virtual void getCanvasMarginsHint( + const QwtScaleMapTable &, const QRectF &canvasRect, + double &left, double &top, double &right, double &bottom) const; + + virtual bool event( QEvent * ); + virtual bool eventFilter( QObject *, QEvent * ); + + virtual void drawItems( QPainter *, const QRectF &, + const QwtScaleMapTable & ) const; + + virtual QVariant itemToInfo( QwtPlotItem * ) const; + virtual QwtPlotItem *infoToItem( const QVariant & ) const; + +#if QWT_COMPAT + enum Axis + { + yLeft = QwtAxis::yLeft, + yRight = QwtAxis::yRight, + xBottom = QwtAxis::xBottom, + xTop = QwtAxis::xTop, + axisCnt = QwtAxis::PosCount + }; + + + void enableAxis( int axisId, bool on = true ) + { + setAxisVisible( axisId, on ); + } + + bool axisEnabled( int axisId ) const + { + return isAxisVisible( axisId ); + } +#endif + +Q_SIGNALS: + /*! + A signal indicating, that an item has been attached/detached + + \param plotItem Plot item + \param on Attached/Detached + */ + void itemAttached( QwtPlotItem *plotItem, bool on ); + + /*! + A signal with the attributes how to update + the legend entries for a plot item. + + \param itemInfo Info about a plot item, build from itemToInfo() + \param data Attributes of the entries ( usually <= 1 ) for + the plot item. + + \sa itemToInfo(), infoToItem(), QwtAbstractLegend::updateLegend() + */ + void legendDataChanged( const QVariant &itemInfo, + const QList &data ); + +public Q_SLOTS: + virtual void replot(); + void autoRefresh(); + +protected: + + virtual void resizeEvent( QResizeEvent *e ); + +private Q_SLOTS: + void updateLegendItems( const QVariant &itemInfo, + const QList &data ); + +private: + friend class QwtPlotItem; + void attachItem( QwtPlotItem *, bool ); + + void initScaleData(); + void deleteScaleData(); + void updateScaleDiv(); + + void initPlot( const QwtText &title ); + + class ScaleData; + ScaleData *d_scaleData; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_abstract_barchart.cpp b/qwt/src/qwt_plot_abstract_barchart.cpp new file mode 100644 index 000000000..9a72fdd5b --- /dev/null +++ b/qwt/src/qwt_plot_abstract_barchart.cpp @@ -0,0 +1,367 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_abstract_barchart.h" +#include "qwt_scale_map.h" + +static inline double qwtTransformWidth( + const QwtScaleMap &map, double value, double width ) +{ + const double w2 = 0.5 * width; + + const double v1 = map.transform( value - w2 ); + const double v2 = map.transform( value + w2 ); + + return qAbs( v2 - v1 ); +} + +class QwtPlotAbstractBarChart::PrivateData +{ +public: + PrivateData(): + layoutPolicy( QwtPlotAbstractBarChart::AutoAdjustSamples ), + layoutHint( 0.5 ), + spacing( 10 ), + margin( 5 ), + baseline( 0.0 ) + { + } + + QwtPlotAbstractBarChart::LayoutPolicy layoutPolicy; + double layoutHint; + int spacing; + int margin; + double baseline; +}; + +/*! + Constructor + \param title Title of the chart +*/ +QwtPlotAbstractBarChart::QwtPlotAbstractBarChart( const QwtText &title ): + QwtPlotSeriesItem( title ) +{ + d_data = new PrivateData; + + setItemAttribute( QwtPlotItem::Legend, true ); + setItemAttribute( QwtPlotItem::AutoScale, true ); + setItemAttribute( QwtPlotItem::Margins, true ); + setZ( 19.0 ); +} + +//! Destructor +QwtPlotAbstractBarChart::~QwtPlotAbstractBarChart() +{ + delete d_data; +} + +/*! + The combination of layoutPolicy() and layoutHint() define how the width + of the bars is calculated + + \param policy Layout policy + + \sa layoutPolicy(), layoutHint() + */ +void QwtPlotAbstractBarChart::setLayoutPolicy( LayoutPolicy policy ) +{ + if ( policy != d_data->layoutPolicy ) + { + d_data->layoutPolicy = policy; + itemChanged(); + } +} + +/*! + The combination of layoutPolicy() and layoutHint() define how the width + of the bars is calculated + + \return Layout policy of the chart item + \sa setLayoutPolicy(), layoutHint() + */ +QwtPlotAbstractBarChart::LayoutPolicy QwtPlotAbstractBarChart::layoutPolicy() const +{ + return d_data->layoutPolicy; +} + +/*! + The combination of layoutPolicy() and layoutHint() define how the width + of the bars is calculated + + \param hint Layout hint + + \sa LayoutPolicy, layoutPolicy(), layoutHint() + */ +void QwtPlotAbstractBarChart::setLayoutHint( double hint ) +{ + hint = qMax( 0.0, hint ); + if ( hint != d_data->layoutHint ) + { + d_data->layoutHint = hint; + itemChanged(); + } +} + +/*! + The combination of layoutPolicy() and layoutHint() define how the width + of the bars is calculated + + \return Layout policy of the chart item + \sa LayoutPolicy, setLayoutHint(), layoutPolicy() +*/ +double QwtPlotAbstractBarChart::layoutHint() const +{ + return d_data->layoutHint; +} + +/*! + \brief Set the spacing + + The spacing is the distance between 2 samples ( bars for QwtPlotBarChart or + a group of bars for QwtPlotMultiBarChart ) in paint device coordinates. + + \sa spacing() + */ +void QwtPlotAbstractBarChart::setSpacing( int spacing ) +{ + spacing = qMax( spacing, 0 ); + if ( spacing != d_data->spacing ) + { + d_data->spacing = spacing; + itemChanged(); + } +} + +/*! + \return Spacing between 2 samples ( bars or groups of bars ) + \sa setSpacing(), margin() + */ +int QwtPlotAbstractBarChart::spacing() const +{ + return d_data->spacing; +} +/*! + \brief Set the margin + + The margin is the distance between the outmost bars and the contentsRect() + of the canvas. The default setting is 5 pixels. + + \param margin Margin + + \sa spacing(), margin() + */ +void QwtPlotAbstractBarChart::setMargin( int margin ) +{ + margin = qMax( margin, 0 ); + if ( margin != d_data->margin ) + { + d_data->margin = margin; + itemChanged(); + } +} + +/*! + \return Margin between the outmost bars and the contentsRect() + of the canvas. + + \sa setMargin(), spacing() + */ +int QwtPlotAbstractBarChart::margin() const +{ + return d_data->margin; +} + +/*! + \brief Set the baseline + + The baseline is the origin for the chart. Each bar is + painted from the baseline in the direction of the sample + value. In case of a horizontal orientation() the baseline + is interpreted as x - otherwise as y - value. + + The default value for the baseline is 0. + + \param value Value for the baseline + + \sa baseline(), QwtPlotSeriesItem::orientation() +*/ +void QwtPlotAbstractBarChart::setBaseline( double value ) +{ + if ( value != d_data->baseline ) + { + d_data->baseline = value; + itemChanged(); + } +} + +/*! + \return Value for the origin of the bar chart + \sa setBaseline(), QwtPlotSeriesItem::orientation() + */ +double QwtPlotAbstractBarChart::baseline() const +{ + return d_data->baseline; +} + +/*! + Calculate the width for a sample in paint device coordinates + + \param map Scale map for the corresponding scale + \param canvasSize Size of the canvas in paint device coordinates + \param boundingSize Bounding size of the chart in plot coordinates + ( used in AutoAdjustSamples mode ) + \param value Value of the sample + + \return Sample width + \sa layoutPolicy(), layoutHint() +*/ +double QwtPlotAbstractBarChart::sampleWidth( const QwtScaleMap &map, + double canvasSize, double boundingSize, double value ) const +{ + double width; + + switch( d_data->layoutPolicy ) + { + case ScaleSamplesToAxes: + { + width = qwtTransformWidth( map, value, d_data->layoutHint ); + break; + } + case ScaleSampleToCanvas: + { + width = canvasSize * d_data->layoutHint; + break; + } + case FixedSampleSize: + { + width = d_data->layoutHint; + break; + } + case AutoAdjustSamples: + default: + { + const size_t numSamples = dataSize(); + + double w = 1.0; + if ( numSamples > 1 ) + { + w = qAbs( boundingSize / ( numSamples - 1 ) ); + } + + width = qwtTransformWidth( map, value, w ); + width -= d_data->spacing; + } + } + + return width; +} + +/*! + \brief Calculate a hint for the canvas margin + + Bar charts need to reserve some space for displaying the bars + for the first and the last sample. The hint is calculated + from the layoutHint() depending on the layoutPolicy(). + + The margins are in target device coordinates ( pixels on screen ) + + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param canvasRect Contents rectangle of the canvas in painter coordinates + \param left Returns the left margin + \param top Returns the top margin + \param right Returns the right margin + \param bottom Returns the bottom margin + + \return Margin + + \sa layoutPolicy(), layoutHint(), QwtPlotItem::Margins + QwtPlot::getCanvasMarginsHint(), QwtPlot::updateCanvasMargins() + */ +void QwtPlotAbstractBarChart::getCanvasMarginHint( const QwtScaleMap &xMap, + const QwtScaleMap &yMap, const QRectF &canvasRect, + double &left, double &top, double &right, double &bottom ) const +{ + double hint = -1.0; + + switch( layoutPolicy() ) + { + case ScaleSampleToCanvas: + { + if ( orientation() == Qt::Vertical ) + hint = 0.5 * canvasRect.width() * d_data->layoutHint; + else + hint = 0.5 * canvasRect.height() * d_data->layoutHint; + + break; + } + case FixedSampleSize: + { + hint = 0.5 * d_data->layoutHint; + break; + } + case AutoAdjustSamples: + case ScaleSamplesToAxes: + default: + { + const size_t numSamples = dataSize(); + if ( numSamples <= 0 ) + break; + + // doesn't work for nonlinear scales + + const QRectF br = dataRect(); + double spacing = 0.0; + double sampleWidthS = 1.0; + + if ( layoutPolicy() == ScaleSamplesToAxes ) + { + sampleWidthS = qMax( d_data->layoutHint, 0.0 ); + } + else + { + spacing = d_data->spacing; + + if ( numSamples > 1 ) + { + sampleWidthS = qAbs( br.width() / ( numSamples - 1 ) ); + } + } + + double ds, w; + if ( orientation() == Qt::Vertical ) + { + ds = qAbs( xMap.sDist() ); + w = canvasRect.width(); + } + else + { + ds = qAbs( yMap.sDist() ); + w = canvasRect.height(); + } + + const double sampleWidthP = ( w - spacing * ds ) + * sampleWidthS / ( ds + sampleWidthS ); + + hint = 0.5 * sampleWidthP; + hint += qMax( d_data->margin, 0 ); + } + } + + if ( orientation() == Qt::Vertical ) + { + left = right = hint; + top = bottom = -1.0; // no hint + } + else + { + left = right = -1.0; // no hint + top = bottom = hint; + } +} diff --git a/qwt/src/qwt_plot_abstract_barchart.h b/qwt/src/qwt_plot_abstract_barchart.h new file mode 100644 index 000000000..78b98a531 --- /dev/null +++ b/qwt/src/qwt_plot_abstract_barchart.h @@ -0,0 +1,97 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_ABSTRACT_BAR_CHART_H +#define QWT_PLOT_ABSTRACT_BAR_CHART_H + +#include "qwt_global.h" +#include "qwt_plot_seriesitem.h" +#include "qwt_series_data.h" + +/*! + \brief Abstract base class for bar chart items + + In opposite to almost all other plot items bar charts can't be + displayed inside of their bounding rectangle and need a special + API how to calculate the width of the bars and how they affect + the layout of the attached plot. + */ +class QWT_EXPORT QwtPlotAbstractBarChart: public QwtPlotSeriesItem +{ +public: + /*! + \brief Mode how to calculate the bar width + + setLayoutPolicy(), setLayoutHint(), barWidthHint() + */ + enum LayoutPolicy + { + /*! + The sample width is calculated by dividing the bounding rectangle + by the number of samples. + + \sa boundingRectangle() + \note The layoutHint() is ignored + */ + AutoAdjustSamples, + + /*! + layoutHint() defines an interval in axis coordinates + */ + ScaleSamplesToAxes, + + /*! + The bar width is calculated by multiplying layoutHint() + with the height or width of the canvas. + + \sa boundingRectangle() + */ + ScaleSampleToCanvas, + + /*! + layoutHint() defines a fixed width in paint device coordinates. + */ + FixedSampleSize + }; + + explicit QwtPlotAbstractBarChart( const QwtText &title ); + virtual ~QwtPlotAbstractBarChart(); + + void setLayoutPolicy( LayoutPolicy ); + LayoutPolicy layoutPolicy() const; + + void setLayoutHint( double ); + double layoutHint() const; + + void setSpacing( int ); + int spacing() const; + + void setMargin( int ); + int margin() const; + + void setBaseline( double ); + double baseline() const; + + virtual void getCanvasMarginHint( + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, + double &left, double &top, double &right, double &bottom) const; + + +protected: + double sampleWidth( const QwtScaleMap &map, + double canvasSize, double dataSize, + double value ) const; + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_axis.cpp b/qwt/src/qwt_plot_axis.cpp new file mode 100644 index 000000000..43c52f122 --- /dev/null +++ b/qwt/src/qwt_plot_axis.cpp @@ -0,0 +1,829 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot.h" +#include "qwt_math.h" +#include "qwt_scale_widget.h" +#include "qwt_scale_div.h" +#include "qwt_scale_engine.h" + +class QwtPlotAxisData +{ +public: + QwtPlotAxisData(): + isVisible( true ), + doAutoScale( true ), + minValue( 0.0 ), + maxValue( 1000.0 ), + stepSize( 0.0 ), + maxMajor( 8 ), + maxMinor( 5 ), + isValid( false ), + scaleEngine( new QwtLinearScaleEngine() ), + scaleWidget( NULL ) + { + } + + void initWidget( QwtScaleDraw::Alignment align, const QString& name, QwtPlot* plot ) + { + scaleWidget = new QwtScaleWidget( align, plot ); + scaleWidget->setObjectName( name ); + +#if 1 + // better find the font sizes from the application font + const QFont fscl( plot->fontInfo().family(), 10 ); + const QFont fttl( plot->fontInfo().family(), 12, QFont::Bold ); +#endif + + scaleWidget->setTransformation( scaleEngine->transformation() ); + + scaleWidget->setFont( fscl ); + scaleWidget->setMargin( 2 ); + + QwtText text = scaleWidget->title(); + text.setFont( fttl ); + scaleWidget->setTitle( text ); + } + + bool isVisible; + bool doAutoScale; + + double minValue; + double maxValue; + double stepSize; + + int maxMajor; + int maxMinor; + + bool isValid; + + QwtScaleDiv scaleDiv; + QwtScaleEngine *scaleEngine; + QwtScaleWidget *scaleWidget; +}; + +class QwtPlot::ScaleData +{ +public: + ScaleData( QwtPlot *plot ) + { + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + setAxesCount( plot, axisPos, 1 ); + } + + ~ScaleData() + { + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + for ( int i = 0; i < d[axisPos].axisData.size(); i++ ) + { + delete d[axisPos].axisData[i].scaleEngine; + } + } + } + + void setAxesCount( QwtPlot *plot, int axisPos, int count ) + { + QVector< QwtPlotAxisData > &axisData = d[axisPos].axisData; + + for ( int i = count; i < axisData.size(); i++ ) + { + delete axisData[i].scaleEngine; + delete axisData[i].scaleWidget; + } + + const int numAxis = axisData.size(); + axisData.resize( count ); + + for ( int i = numAxis; i < count; i++ ) + { + QString name; + QwtScaleDraw::Alignment align; + + switch( axisPos ) + { + case QwtAxis::yLeft: + align = QwtScaleDraw::LeftScale; + name = "QwtPlotAxisYLeft"; + break; + case QwtAxis::yRight: + align = QwtScaleDraw::RightScale; + name = "QwtPlotAxisYRight"; + break; + case QwtAxis::xBottom: + align = QwtScaleDraw::BottomScale; + name = "QwtPlotAxisXBottom"; + break; + case QwtAxis::xTop: + align = QwtScaleDraw::TopScale; + name = "QwtPlotAxisXTop"; + break; + } + + if ( i > 0 ) + name += QString().setNum( i ); + + + axisData[ i ].initWidget( align, name, plot ); + } + } + + inline int axesCount( int pos ) const + { + if ( !QwtAxis::isValid( pos ) ) + return -1; + + return d[pos].axisData.count(); + } + + inline QwtPlotAxisData &axisData( const QwtAxisId &axisId ) + { + return d[ axisId.pos ].axisData[ axisId.id ]; + } + + inline const QwtPlotAxisData &axisData( const QwtAxisId &axisId ) const + { + return d[ axisId.pos ].axisData[ axisId.id ]; + } + + struct + { + QVector< QwtPlotAxisData > axisData; + + } d[ QwtAxis::PosCount ]; +}; + +//! Initialize axes +void QwtPlot::initScaleData() +{ + d_scaleData = new ScaleData( this ); + + d_scaleData->axisData( QwtAxis::yRight ).isVisible = false; + d_scaleData->axisData( QwtAxis::xTop ).isVisible = false; +} + +void QwtPlot::deleteScaleData() +{ + delete d_scaleData; + d_scaleData = NULL; +} + +void QwtPlot::setAxesCount( int axisPos, int count ) +{ + count = qMax( count, 1 ); // we need at least one axis + + if ( count != axesCount( axisPos ) ) + { + d_scaleData->setAxesCount( this, axisPos, count ); + autoRefresh(); + } +} + +int QwtPlot::axesCount( int axisPos, bool onlyVisible ) const +{ + int count = 0; + + if ( onlyVisible ) + { + for ( int i = 0; i < d_scaleData->axesCount( axisPos ); i++ ) + { + const QwtAxisId axisId( axisPos, i ); + if ( d_scaleData->axisData( axisId ).isVisible ) + count++; + } + } + else + { + count = d_scaleData->axesCount( axisPos ); + } + + return count; +} + +/*! + \return \c true if the specified axis exists, otherwise \c false + \param axisPos axis index + */ +bool QwtPlot::isAxisValid( QwtAxisId axisId ) const +{ + return d_scaleData->axesCount( axisId.pos ) > axisId.id; +} + +/*! + \return Scale widget of the specified axis, or NULL if axisPos is invalid. + \param axisPos Axis position +*/ +const QwtScaleWidget *QwtPlot::axisWidget( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return d_scaleData->axisData( axisId ).scaleWidget; + + return NULL; +} + +/*! + \return Scale widget of the specified axis, or NULL if axisPos is invalid. + \param axisPos Axis position +*/ +QwtScaleWidget *QwtPlot::axisWidget( QwtAxisId axisId ) +{ + if ( isAxisValid( axisId ) ) + return d_scaleData->axisData( axisId ).scaleWidget; + + return NULL; +} + +/*! + Change the scale engine for an axis + + \param axisPos Axis position + \param scaleEngine Scale engine + + \sa axisScaleEngine() +*/ +void QwtPlot::setAxisScaleEngine( QwtAxisId axisId, QwtScaleEngine *scaleEngine ) +{ + if ( isAxisValid( axisId ) && scaleEngine != NULL ) + { + QwtPlotAxisData &d = d_scaleData->axisData( axisId ); + + delete d.scaleEngine; + d.scaleEngine = scaleEngine; + + d.scaleWidget->setTransformation( scaleEngine->transformation() ); + + d.isValid = false; + + autoRefresh(); + } +} + +/*! + \param axisPos Axis position + \return Scale engine for a specific axis +*/ +QwtScaleEngine *QwtPlot::axisScaleEngine( QwtAxisId axisId ) +{ + if ( isAxisValid( axisId ) ) + return d_scaleData->axisData( axisId ).scaleEngine; + else + return NULL; +} + +/*! + \param axisPos Axis position + \return Scale engine for a specific axis +*/ +const QwtScaleEngine *QwtPlot::axisScaleEngine( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return d_scaleData->axisData( axisId ).scaleEngine; + else + return NULL; +} +/*! + \return \c True, if autoscaling is enabled + \param axisPos Axis position +*/ +bool QwtPlot::axisAutoScale( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return d_scaleData->axisData( axisId ).doAutoScale; + else + return false; +} + +/*! + \return \c True, if a specified axis is visible + \param axisPos Axis position +*/ +bool QwtPlot::isAxisVisible( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return d_scaleData->axisData( axisId ).isVisible; + else + return false; +} + +/*! + \return The font of the scale labels for a specified axis + \param axisPos Axis position +*/ +QFont QwtPlot::axisFont( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return axisWidget( axisId )->font(); + else + return QFont(); + +} + +/*! + \return The maximum number of major ticks for a specified axis + \param axisPos Axis position + \sa setAxisMaxMajor(), QwtScaleEngine::divideScale() +*/ +int QwtPlot::axisMaxMajor( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return d_scaleData->axisData( axisId ).maxMajor; + else + return 0; +} + +/*! + \return the maximum number of minor ticks for a specified axis + \param axisPos Axis position + \sa setAxisMaxMinor(), QwtScaleEngine::divideScale() +*/ +int QwtPlot::axisMaxMinor( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return d_scaleData->axisData( axisId ).maxMinor; + else + return 0; +} + +/*! + \brief Return the scale division of a specified axis + + axisScaleDiv(axisPos).lowerBound(), axisScaleDiv(axisPos).upperBound() + are the current limits of the axis scale. + + \param axisPos Axis position + \return Scale division + + \sa QwtScaleDiv, setAxisScaleDiv(), QwtScaleEngine::divideScale() +*/ +const QwtScaleDiv &QwtPlot::axisScaleDiv( QwtAxisId axisId ) const +{ + return d_scaleData->axisData( axisId ).scaleDiv; +} + +/*! + \brief Return the scale draw of a specified axis + + \param axisPos Axis position + \return Specified scaleDraw for axis, or NULL if axis is invalid. +*/ +const QwtScaleDraw *QwtPlot::axisScaleDraw( QwtAxisId axisId ) const +{ + if ( !isAxisValid( axisId ) ) + return NULL; + + return axisWidget( axisId )->scaleDraw(); +} + +/*! + \brief Return the scale draw of a specified axis + + \param axisPos Axis position + \return Specified scaleDraw for axis, or NULL if axis is invalid. +*/ +QwtScaleDraw *QwtPlot::axisScaleDraw( QwtAxisId axisId ) +{ + if ( !isAxisValid( axisId ) ) + return NULL; + + return axisWidget( axisId )->scaleDraw(); +} + +/*! + \brief Return the step size parameter that has been set in setAxisScale. + + This doesn't need to be the step size of the current scale. + + \param axisPos Axis position + \return step size parameter value + + \sa setAxisScale(), QwtScaleEngine::divideScale() +*/ +double QwtPlot::axisStepSize( QwtAxisId axisId ) const +{ + if ( !isAxisValid( axisId ) ) + return 0; + + return d_scaleData->axisData( axisId ).stepSize; +} + +/*! + \brief Return the current interval of the specified axis + + This is only a convenience function for axisScaleDiv( axisPos )->interval(); + + \param axisPos Axis position + \return Scale interval + + \sa QwtScaleDiv, axisScaleDiv() +*/ +QwtInterval QwtPlot::axisInterval( QwtAxisId axisId ) const +{ + if ( !isAxisValid( axisId ) ) + return QwtInterval(); + + return d_scaleData->axisData( axisId ).scaleDiv.interval(); +} + +/*! + \return Title of a specified axis + \param axisPos Axis position +*/ +QwtText QwtPlot::axisTitle( QwtAxisId axisId ) const +{ + if ( isAxisValid( axisId ) ) + return axisWidget( axisId )->title(); + else + return QwtText(); +} + +/*! + \brief Enable or disable a specified axis + + Even when an axis is not visible curves, markers and can be attached + to it, and transformation of screen coordinates into values works as normal. + + Only QwtAxis::xBottom and QwtAxis::yLeft are visible by default. + + \param axisPos Axis position + \param on \c true (visisble) or \c false (hidden) +*/ +void QwtPlot::setAxisVisible( QwtAxisId axisId, bool on ) +{ + if ( isAxisValid( axisId ) && on != d_scaleData->axisData( axisId ).isVisible ) + { + d_scaleData->axisData( axisId ).isVisible = on; + updateLayout(); + } +} + +/*! + Transform the x or y coordinate of a position in the + drawing region into a value. + + \param axisPos Axis position + \param pos position + + \return Position as axis coordinate + + \warning The position can be an x or a y coordinate, + depending on the specified axis. +*/ +double QwtPlot::invTransform( QwtAxisId axisId, double pos ) const +{ + if ( isAxisValid( axisId ) ) + return( canvasMap( axisId ).invTransform( pos ) ); + else + return 0.0; +} + + +/*! + \brief Transform a value into a coordinate in the plotting region + + \param axisPos Axis position + \param value value + \return X or Y coordinate in the plotting region corresponding + to the value. +*/ +double QwtPlot::transform( QwtAxisId axisId, double value ) const +{ + if ( isAxisValid( axisId ) ) + return( canvasMap( axisId ).transform( value ) ); + else + return 0.0; +} + +/*! + \brief Change the font of an axis + + \param axisPos Axis position + \param font Font + \warning This function changes the font of the tick labels, + not of the axis title. +*/ +void QwtPlot::setAxisFont( QwtAxisId axisId, const QFont &font ) +{ + if ( isAxisValid( axisId ) ) + axisWidget( axisId )->setFont( font ); +} + +/*! + \brief Enable autoscaling for a specified axis + + This member function is used to switch back to autoscaling mode + after a fixed scale has been set. Autoscaling is enabled by default. + + \param axisPos Axis position + \param on On/Off + \sa setAxisScale(), setAxisScaleDiv(), updateAxes() + + \note The autoscaling flag has no effect until updateAxes() is executed + ( called by replot() ). +*/ +void QwtPlot::setAxisAutoScale( QwtAxisId axisId, bool on ) +{ + if ( isAxisValid( axisId ) && ( d_scaleData->axisData( axisId ).doAutoScale != on ) ) + { + d_scaleData->axisData( axisId ).doAutoScale = on; + autoRefresh(); + } +} + +/*! + \brief Disable autoscaling and specify a fixed scale for a selected axis. + + In updateAxes() the scale engine calculates a scale division from the + specified parameters, that will be assigned to the scale widget. So + updates of the scale widget usually happen delayed with the next replot. + + \param axisPos Axis position + \param min Minimum of the scale + \param max Maximum of the scale + \param stepSize Major step size. If step == 0, the step size is + calculated automatically using the maxMajor setting. + + \sa setAxisMaxMajor(), setAxisAutoScale(), axisStepSize(), QwtScaleEngine::divideScale() +*/ +void QwtPlot::setAxisScale( QwtAxisId axisId, double min, double max, double stepSize ) +{ + if ( isAxisValid( axisId ) ) + { + QwtPlotAxisData &d = d_scaleData->axisData( axisId ); + + d.doAutoScale = false; + d.isValid = false; + + d.minValue = min; + d.maxValue = max; + d.stepSize = stepSize; + + autoRefresh(); + } +} + +/*! + \brief Disable autoscaling and specify a fixed scale for a selected axis. + + The scale division will be stored locally only until the next call + of updateAxes(). So updates of the scale widget usually happen delayed with + the next replot. + + \param axisPos Axis position + \param scaleDiv Scale division + + \sa setAxisScale(), setAxisAutoScale() +*/ +void QwtPlot::setAxisScaleDiv( QwtAxisId axisId, const QwtScaleDiv &scaleDiv ) +{ + if ( isAxisValid( axisId ) ) + { + QwtPlotAxisData &d = d_scaleData->axisData( axisId ); + + d.doAutoScale = false; + d.scaleDiv = scaleDiv; + d.isValid = true; + + autoRefresh(); + } +} + +/*! + \brief Set a scale draw + + \param axisPos Axis position + \param scaleDraw Object responsible for drawing scales. + + By passing scaleDraw it is possible to extend QwtScaleDraw + functionality and let it take place in QwtPlot. Please note + that scaleDraw has to be created with new and will be deleted + by the corresponding QwtScale member ( like a child object ). + + \sa QwtScaleDraw, QwtScaleWidget + \warning The attributes of scaleDraw will be overwritten by those of the + previous QwtScaleDraw. +*/ + +void QwtPlot::setAxisScaleDraw( QwtAxisId axisId, QwtScaleDraw *scaleDraw ) +{ + if ( isAxisValid( axisId ) ) + { + axisWidget( axisId )->setScaleDraw( scaleDraw ); + autoRefresh(); + } +} + +/*! + Change the alignment of the tick labels + + \param axisPos Axis position + \param alignment Or'd Qt::AlignmentFlags see + + \sa QwtScaleDraw::setLabelAlignment() +*/ +void QwtPlot::setAxisLabelAlignment( QwtAxisId axisId, Qt::Alignment alignment ) +{ + if ( isAxisValid( axisId ) ) + axisWidget( axisId )->setLabelAlignment( alignment ); +} + +/*! + Rotate all tick labels + + \param axisPos Axis position + \param rotation Angle in degrees. When changing the label rotation, + the label alignment might be adjusted too. + + \sa QwtScaleDraw::setLabelRotation(), setAxisLabelAlignment() +*/ +void QwtPlot::setAxisLabelRotation( QwtAxisId axisId, double rotation ) +{ + if ( isAxisValid( axisId ) ) + axisWidget( axisId )->setLabelRotation( rotation ); +} + +/*! + Set the maximum number of minor scale intervals for a specified axis + + \param axisPos Axis position + \param maxMinor Maximum number of minor steps + + \sa axisMaxMinor() +*/ +void QwtPlot::setAxisMaxMinor( QwtAxisId axisId, int maxMinor ) +{ + if ( isAxisValid( axisId ) ) + { + maxMinor = qBound( 0, maxMinor, 100 ); + + QwtPlotAxisData &d = d_scaleData->axisData( axisId ); + if ( maxMinor != d.maxMinor ) + { + d.maxMinor = maxMinor; + d.isValid = false; + autoRefresh(); + } + } +} + +/*! + Set the maximum number of major scale intervals for a specified axis + + \param axisPos Axis position + \param maxMajor Maximum number of major steps + + \sa axisMaxMajor() +*/ +void QwtPlot::setAxisMaxMajor( QwtAxisId axisId, int maxMajor ) +{ + if ( isAxisValid( axisId ) ) + { + maxMajor = qBound( 1, maxMajor, 10000 ); + + QwtPlotAxisData &d = d_scaleData->axisData( axisId ); + if ( maxMajor != d.maxMajor ) + { + d.maxMajor = maxMajor; + d.isValid = false; + autoRefresh(); + } + } +} + +/*! + \brief Change the title of a specified axis + + \param axisPos Axis position + \param title axis title +*/ +void QwtPlot::setAxisTitle( QwtAxisId axisId, const QString &title ) +{ + if ( isAxisValid( axisId ) ) + axisWidget( axisId )->setTitle( title ); +} + +/*! + \brief Change the title of a specified axis + + \param axisPos Axis position + \param title Axis title +*/ +void QwtPlot::setAxisTitle( QwtAxisId axisId, const QwtText &title ) +{ + if ( isAxisValid( axisId ) ) + axisWidget( axisId )->setTitle( title ); +} + +/*! + \brief Rebuild the axes scales + + In case of autoscaling the boundaries of a scale are calculated + from the bounding rectangles of all plot items, having the + QwtPlotItem::AutoScale flag enabled ( QwtScaleEngine::autoScale() ). + Then a scale division is calculated ( QwtScaleEngine::didvideScale() ) + and assigned to scale widget. + + When the scale boundaries have been assigned with setAxisScale() a + scale division is calculated ( QwtScaleEngine::didvideScale() ) + for this interval and assigned to the scale widget. + + When the scale has been set explicitly by setAxisScaleDiv() the + locally stored scale division gets assigned to the scale widget. + + The scale widget indicates modifications by emitting a + QwtScaleWidget::scaleDivChanged() signal. + + updateAxes() is usually called by replot(). + + \sa setAxisAutoScale(), setAxisScale(), setAxisScaleDiv(), replot() + QwtPlotItem::boundingRect() + */ +void QwtPlot::updateAxes() +{ + // Find bounding interval of the item data + // for all axes, where autoscaling is enabled + + QwtInterval intv[QwtAxis::PosCount]; + + const QwtPlotItemList& itmList = itemList(); + + QwtPlotItemIterator it; + for ( it = itmList.begin(); it != itmList.end(); ++it ) + { + const QwtPlotItem *item = *it; + + if ( !item->testItemAttribute( QwtPlotItem::AutoScale ) ) + continue; + + if ( !item->isVisible() ) + continue; + + if ( axisAutoScale( item->xAxis() ) || axisAutoScale( item->yAxis() ) ) + { + const QRectF rect = item->boundingRect(); + + if ( rect.width() >= 0.0 ) + intv[ item->xAxis().pos ] |= QwtInterval( rect.left(), rect.right() ); + + if ( rect.height() >= 0.0 ) + intv[ item->yAxis().pos ] |= QwtInterval( rect.top(), rect.bottom() ); + } + } + + // Adjust scales + + for ( int axisPos = 0; axisPos < QwtAxis::PosCount; axisPos++ ) + { + for ( int i = 0; i < d_scaleData->axesCount( axisPos ); i++ ) + { + const QwtAxisId axisId( axisPos, i ); + + QwtPlotAxisData &d = d_scaleData->axisData( axisId ); + + double minValue = d.minValue; + double maxValue = d.maxValue; + double stepSize = d.stepSize; + + if ( d.doAutoScale && intv[axisPos].isValid() ) + { + d.isValid = false; + + minValue = intv[axisPos].minValue(); + maxValue = intv[axisPos].maxValue(); + + d.scaleEngine->autoScale( d.maxMajor, + minValue, maxValue, stepSize ); + } + if ( !d.isValid ) + { + d.scaleDiv = d.scaleEngine->divideScale( + minValue, maxValue, + d.maxMajor, d.maxMinor, stepSize ); + d.isValid = true; + } + + QwtScaleWidget *scaleWidget = axisWidget( axisId ); + scaleWidget->setScaleDiv( d.scaleDiv ); + + int startDist, endDist; + scaleWidget->getBorderDistHint( startDist, endDist ); + scaleWidget->setBorderDist( startDist, endDist ); + } + } + + for ( it = itmList.begin(); it != itmList.end(); ++it ) + { + QwtPlotItem *item = *it; + if ( item->testItemInterest( QwtPlotItem::ScaleInterest ) ) + { + item->updateScaleDiv( axisScaleDiv( item->xAxis() ), + axisScaleDiv( item->yAxis() ) ); + } + } +} + diff --git a/qwt/src/qwt_plot_barchart.cpp b/qwt/src/qwt_plot_barchart.cpp new file mode 100644 index 000000000..b3455af5e --- /dev/null +++ b/qwt/src/qwt_plot_barchart.cpp @@ -0,0 +1,455 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_barchart.h" +#include "qwt_scale_map.h" +#include "qwt_column_symbol.h" +#include "qwt_painter.h" +#include + +class QwtPlotBarChart::PrivateData +{ +public: + PrivateData(): + symbol( NULL ), + legendMode( QwtPlotBarChart::LegendChartTitle ) + { + } + + ~PrivateData() + { + delete symbol; + } + + QwtColumnSymbol *symbol; + QwtPlotBarChart::LegendMode legendMode; +}; + +/*! + Constructor + \param title Title of the curve +*/ +QwtPlotBarChart::QwtPlotBarChart( const QwtText &title ): + QwtPlotAbstractBarChart( title ) +{ + init(); +} + +/*! + Constructor + \param title Title of the curve +*/ +QwtPlotBarChart::QwtPlotBarChart( const QString &title ): + QwtPlotAbstractBarChart( QwtText( title ) ) +{ + init(); +} + +//! Destructor +QwtPlotBarChart::~QwtPlotBarChart() +{ + delete d_data; +} + +void QwtPlotBarChart::init() +{ + d_data = new PrivateData; + setData( new QwtPointSeriesData() ); +} + +//! \return QwtPlotItem::Rtti_PlotBarChart +int QwtPlotBarChart::rtti() const +{ + return QwtPlotItem::Rtti_PlotBarChart; +} + +/*! + Initialize data with an array of points + + \param samples Vector of points + \note QVector is implicitly shared + \note QPolygonF is derived from QVector +*/ +void QwtPlotBarChart::setSamples( + const QVector &samples ) +{ + setData( new QwtPointSeriesData( samples ) ); +} + +/*! + Initialize data with an array of doubles + + The indices in the array are taken as x coordinate, + while the doubles are interpreted as y values. + + \param samples Vector of y coordinates + \note QVector is implicitly shared +*/ +void QwtPlotBarChart::setSamples( + const QVector &samples ) +{ + QVector points; + for ( int i = 0; i < samples.size(); i++ ) + points += QPointF( i, samples[ i ] ); + + setData( new QwtPointSeriesData( points ) ); +} + +/*! + Assign a series of samples + + setSamples() is just a wrapper for setData() without any additional + value - beside that it is easier to find for the developer. + + \param data Data + \warning The item takes ownership of the data object, deleting + it when its not used anymore. +*/ +void QwtPlotBarChart::setSamples( QwtSeriesData *data ) +{ + setData( data ); +} + +/*! + \brief Assign a symbol + + The bar chart will take the ownership of the symbol, hence the previously + set symbol will be delete by setting a new one. If \p symbol is + \c NULL no symbol will be drawn. + + \param symbol Symbol + \sa symbol() +*/ +void QwtPlotBarChart::setSymbol( QwtColumnSymbol *symbol ) +{ + if ( symbol != d_data->symbol ) + { + delete d_data->symbol; + d_data->symbol = symbol; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Current symbol or NULL, when no symbol has been assigned + \sa setSymbol() +*/ +const QwtColumnSymbol *QwtPlotBarChart::symbol() const +{ + return d_data->symbol; +} + +/*! + Set the mode that decides what to display on the legend + + In case of LegendBarTitles barTitle() needs to be overloaded + to return individual titles for each bar. + + \param mode New mode + \sa legendMode(), legendData(), barTitle(), QwtPlotItem::ItemAttribute + */ +void QwtPlotBarChart::setLegendMode( LegendMode mode ) +{ + if ( mode != d_data->legendMode ) + { + d_data->legendMode = mode; + legendChanged(); + } +} + +/*! + \return Legend mode + \sa setLegendMode() + */ +QwtPlotBarChart::LegendMode QwtPlotBarChart::legendMode() const +{ + return d_data->legendMode; +} + +/*! + \return Bounding rectangle of all samples. + For an empty series the rectangle is invalid. +*/ +QRectF QwtPlotBarChart::boundingRect() const +{ + const size_t numSamples = dataSize(); + if ( numSamples == 0 ) + return QwtPlotSeriesItem::boundingRect(); + + const double baseLine = baseline(); + + QRectF rect = QwtPlotSeriesItem::boundingRect(); + if ( rect.bottom() < baseLine ) + rect.setBottom( baseLine ); + if ( rect.top() > baseLine ) + rect.setTop( baseLine ); + + if ( rect.isValid() && ( orientation() == Qt::Horizontal ) ) + rect.setRect( rect.y(), rect.x(), rect.height(), rect.width() ); + + return rect; +} + +/*! + Draw an interval of the bar chart + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param canvasRect Contents rect of the canvas + \param from Index of the first point to be painted + \param to Index of the last point to be painted. If to < 0 the + curve will be painted to its last point. + + \sa drawSymbols() +*/ +void QwtPlotBarChart::drawSeries( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + if ( to < 0 ) + to = dataSize() - 1; + + if ( from < 0 ) + from = 0; + + if ( from > to ) + return; + + + const QRectF br = data()->boundingRect(); + const QwtInterval interval( br.left(), br.right() ); + + painter->save(); + + for ( int i = from; i <= to; i++ ) + { + drawSample( painter, xMap, yMap, + canvasRect, interval, i, sample( i ) ); + } + + painter->restore(); +} + +/*! + Draw a sample + + \param painter Painter + \param xMap x map + \param yMap y map + \param canvasRect Contents rect of the canvas + \param boundingInterval Bounding interval of sample values + \param index Index of the sample + \param sample Value of the sample + + \sa drawSeries() +*/ +void QwtPlotBarChart::drawSample( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, const QwtInterval &boundingInterval, + int index, const QPointF &sample ) const +{ + QwtColumnRect barRect; + + if ( orientation() == Qt::Horizontal ) + { + const double barHeight = sampleWidth( yMap, canvasRect.height(), + boundingInterval.width(), sample.y() ); + + const double x1 = xMap.transform( baseline() ); + const double x2 = xMap.transform( sample.y() ); + + const double y = yMap.transform( sample.x() ); + const double y1 = y - 0.5 * barHeight; + const double y2 = y + 0.5 * barHeight; + + barRect.direction = ( x1 < x2 ) ? + QwtColumnRect::LeftToRight : QwtColumnRect::RightToLeft; + + barRect.hInterval = QwtInterval( x1, x2 ).normalized(); + barRect.vInterval = QwtInterval( y1, y2 ); + } + else + { + const double barWidth = sampleWidth( xMap, canvasRect.width(), + boundingInterval.width(), sample.y() ); + + const double x = xMap.transform( sample.x() ); + const double x1 = x - 0.5 * barWidth; + const double x2 = x + 0.5 * barWidth; + + const double y1 = yMap.transform( baseline() ); + const double y2 = yMap.transform( sample.y() ); + + barRect.direction = ( y1 < y2 ) ? + QwtColumnRect::TopToBottom : QwtColumnRect::BottomToTop; + + barRect.hInterval = QwtInterval( x1, x2 ); + barRect.vInterval = QwtInterval( y1, y2 ).normalized(); + } + + drawBar( painter, index, sample, barRect ); +} + +/*! + Draw a bar + + \param painter Painter + \param sampleIndex Index of the sample represented by the bar + \param sample Value of the sample + \param rect Bounding rectangle of the bar + */ +void QwtPlotBarChart::drawBar( QPainter *painter, + int sampleIndex, const QPointF &sample, + const QwtColumnRect &rect ) const +{ + const QwtColumnSymbol *specialSym = + specialSymbol( sampleIndex, sample ); + + const QwtColumnSymbol *sym = specialSym; + if ( sym == NULL ) + sym = d_data->symbol; + + if ( sym ) + { + sym->draw( painter, rect ); + } + else + { + // we build a temporary default symbol + QwtColumnSymbol sym( QwtColumnSymbol::Box ); + sym.setLineWidth( 1 ); + sym.setFrameStyle( QwtColumnSymbol::Plain ); + sym.draw( painter, rect ); + } + + delete specialSym; +} + +/*! + Needs to be overloaded to return a + non default symbol for a specific sample + + \param sampleIndex Index of the sample represented by the bar + \param sample Value of the sample + + \return NULL, indicating to use the default symbol + */ +QwtColumnSymbol *QwtPlotBarChart::specialSymbol( + int sampleIndex, const QPointF &sample ) const +{ + Q_UNUSED( sampleIndex ); + Q_UNUSED( sample ); + + return NULL; +} + +/*! + \brief Return the title of a bar + + In LegendBarTitles mode the title is displayed on + the legend entry corresponding to a bar. + + The default implementation is a dummy, that is intended + to be overloaded. + + \param sampleIndex Index of the bar + \return An empty text + \sa LegendBarTitles + */ +QwtText QwtPlotBarChart::barTitle( int sampleIndex ) const +{ + Q_UNUSED( sampleIndex ); + return QwtText(); +} + +/*! + \brief Return all information, that is needed to represent + the item on the legend + + In case of LegendBarTitles an entry for each bar is returned, + otherwise the chart is represented like any other plot item + from its title() and the legendIcon(). + + \return Information, that is needed to represent the item on the legend + \sa title(), setLegendMode(), barTitle(), QwtLegend, QwtPlotLegendItem + */ +QList QwtPlotBarChart::legendData() const +{ + QList list; + + if ( d_data->legendMode == LegendBarTitles ) + { + const size_t numSamples = dataSize(); + for ( size_t i = 0; i < numSamples; i++ ) + { + QwtLegendData data; + + QVariant titleValue; + qVariantSetValue( titleValue, barTitle( i ) ); + data.setValue( QwtLegendData::TitleRole, titleValue ); + + if ( !legendIconSize().isEmpty() ) + { + QVariant iconValue; + qVariantSetValue( iconValue, + legendIcon( i, legendIconSize() ) ); + + data.setValue( QwtLegendData::IconRole, iconValue ); + } + + list += data; + } + } + else + { + return QwtPlotAbstractBarChart::legendData(); + } + + return list; +} + +/*! + \return Icon representing a bar or the chart on the legend + + When the legendMode() is LegendBarTitles the icon shows + the bar corresponding to index - otherwise the bar + displays the default symbol. + + \param index Index of the legend entry + \param size Icon size + + \sa setLegendMode(), drawBar(), + QwtPlotItem::setLegendIconSize(), QwtPlotItem::legendData() + */ +QwtGraphic QwtPlotBarChart::legendIcon( + int index, const QSizeF &size ) const +{ + QwtColumnRect column; + column.hInterval = QwtInterval( 0.0, size.width() - 1.0 ); + column.vInterval = QwtInterval( 0.0, size.height() - 1.0 ); + + QwtGraphic icon; + icon.setDefaultSize( size ); + icon.setRenderHint( QwtGraphic::RenderPensUnscaled, true ); + + QPainter painter( &icon ); + painter.setRenderHint( QPainter::Antialiasing, + testRenderHint( QwtPlotItem::RenderAntialiased ) ); + + int barIndex = -1; + if ( d_data->legendMode == QwtPlotBarChart::LegendBarTitles ) + barIndex = index; + + drawBar( &painter, barIndex, QPointF(), column ); + + return icon; +} diff --git a/qwt/src/qwt_plot_barchart.h b/qwt/src/qwt_plot_barchart.h new file mode 100644 index 000000000..d47bfb972 --- /dev/null +++ b/qwt/src/qwt_plot_barchart.h @@ -0,0 +1,118 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_BAR_CHART_H +#define QWT_PLOT_BAR_CHART_H + +#include "qwt_global.h" +#include "qwt_plot_abstract_barchart.h" +#include "qwt_series_data.h" + +class QwtColumnRect; +class QwtColumnSymbol; + +/*! + \brief QwtPlotBarChart displays a series of a values as bars. + + Each bar might be customized individually by implementing + a specialSymbol(). Otherwise it is rendered using a default symbol. + + Depending on its orientation() the bars are displayed horizontally + or vertically. The bars cover the interval between the baseline() + and the value. + + By activating the LegendBarTitles mode each sample will have + its own entry on the legend. + + The most common use case of a bar chart is to display a + list of y coordinates, where the x coordinate is simply the index + in the list. But for other situations ( f.e. when values are related + to dates ) it is also possible to set x coordinates explicitly. + + \sa QwtPlotMultiBarChart, QwtPlotHistogram, QwtPlotCurve::Sticks, + QwtPlotSeriesItem::orientation(), QwtPlotAbstractBarChart::baseline() + */ +class QWT_EXPORT QwtPlotBarChart: + public QwtPlotAbstractBarChart, public QwtSeriesStore +{ +public: + /*! + \brief Legend modes. + + The default setting is QwtPlotBarChart::LegendChartTitle. + \sa setLegendMode(), legendMode() + */ + enum LegendMode + { + /*! + One entry on the legend showing the default symbol + and the title() of the chart + + \sa QwtPlotItem::title() + */ + LegendChartTitle, + + /*! + One entry for each value showing the individual symbol + of the corresponding bar and the bar title. + + \sa specialSymbol(), barTitle() + */ + LegendBarTitles + }; + + explicit QwtPlotBarChart( const QString &title = QString::null ); + explicit QwtPlotBarChart( const QwtText &title ); + + virtual ~QwtPlotBarChart(); + + virtual int rtti() const; + + void setSamples( const QVector & ); + void setSamples( const QVector & ); + void setSamples( QwtSeriesData *series ); + + void setSymbol( QwtColumnSymbol * ); + const QwtColumnSymbol *symbol() const; + + void setLegendMode( LegendMode ); + LegendMode legendMode() const; + + virtual void drawSeries( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const; + + virtual QRectF boundingRect() const; + + virtual QwtColumnSymbol *specialSymbol( + int sampleIndex, const QPointF& ) const; + + virtual QwtText barTitle( int sampleIndex ) const; + +protected: + virtual void drawSample( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, const QwtInterval &boundingInterval, + int index, const QPointF& sample ) const; + + virtual void drawBar( QPainter *, + int sampleIndex, const QPointF& point, + const QwtColumnRect & ) const; + + QList legendData() const; + QwtGraphic legendIcon( int index, const QSizeF & ) const; + +private: + void init(); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_canvas.cpp b/qwt/src/qwt_plot_canvas.cpp new file mode 100644 index 000000000..0271713a8 --- /dev/null +++ b/qwt/src/qwt_plot_canvas.cpp @@ -0,0 +1,1097 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_canvas.h" +#include "qwt_painter.h" +#include "qwt_null_paintdevice.h" +#include "qwt_math.h" +#include "qwt_plot.h" +#include +#include +#include +#include +#include + +class QwtStyleSheetRecorder: public QwtNullPaintDevice +{ +public: + QwtStyleSheetRecorder( const QSize &size ): + d_size( size ) + { + } + + virtual void updateState( const QPaintEngineState &state ) + { + if ( state.state() & QPaintEngine::DirtyPen ) + { + d_pen = state.pen(); + } + if ( state.state() & QPaintEngine::DirtyBrush ) + { + d_brush = state.brush(); + } + if ( state.state() & QPaintEngine::DirtyBrushOrigin ) + { + d_origin = state.brushOrigin(); + } + } + + virtual void drawRects(const QRectF *rects, int count ) + { + for ( int i = 0; i < count; i++ ) + border.rectList += rects[i]; + } + + virtual void drawPath( const QPainterPath &path ) + { + const QRectF rect( QPointF( 0.0, 0.0 ), d_size ); + if ( path.controlPointRect().contains( rect.center() ) ) + { + setCornerRects( path ); + alignCornerRects( rect ); + + background.path = path; + background.brush = d_brush; + background.origin = d_origin; + } + else + { + border.pathList += path; + } + } + + void setCornerRects( const QPainterPath &path ) + { + QPointF pos( 0.0, 0.0 ); + + for ( int i = 0; i < path.elementCount(); i++ ) + { + QPainterPath::Element el = path.elementAt(i); + switch( el.type ) + { + case QPainterPath::MoveToElement: + case QPainterPath::LineToElement: + { + pos.setX( el.x ); + pos.setY( el.y ); + break; + } + case QPainterPath::CurveToElement: + { + QRectF r( pos, QPointF( el.x, el.y ) ); + clipRects += r.normalized(); + + pos.setX( el.x ); + pos.setY( el.y ); + + break; + } + case QPainterPath::CurveToDataElement: + { + if ( clipRects.size() > 0 ) + { + QRectF r = clipRects.last(); + r.setCoords( + qMin( r.left(), el.x ), + qMin( r.top(), el.y ), + qMax( r.right(), el.x ), + qMax( r.bottom(), el.y ) + ); + clipRects.last() = r.normalized(); + } + break; + } + } + } + } + +protected: + virtual QSize sizeMetrics() const + { + return d_size; + } + +private: + void alignCornerRects( const QRectF &rect ) + { + for ( int i = 0; i < clipRects.size(); i++ ) + { + QRectF &r = clipRects[i]; + if ( r.center().x() < rect.center().x() ) + r.setLeft( rect.left() ); + else + r.setRight( rect.right() ); + + if ( r.center().y() < rect.center().y() ) + r.setTop( rect.top() ); + else + r.setBottom( rect.bottom() ); + } + } + + +public: + QVector clipRects; + + struct Border + { + QList pathList; + QList rectList; + QRegion clipRegion; + } border; + + struct Background + { + QPainterPath path; + QBrush brush; + QPointF origin; + } background; + +private: + const QSize d_size; + + QPen d_pen; + QBrush d_brush; + QPointF d_origin; +}; + +static void qwtDrawBackground( QPainter *painter, QwtPlotCanvas *canvas ) +{ + painter->save(); + + const QPainterPath borderClip = canvas->borderPath( canvas->rect() ); + if ( !borderClip.isEmpty() ) + painter->setClipPath( borderClip, Qt::IntersectClip ); + + const QBrush &brush = + canvas->palette().brush( canvas->backgroundRole() ); + + if ( brush.style() == Qt::TexturePattern ) + { + QPixmap pm( canvas->size() ); + QwtPainter::fillPixmap( canvas, pm ); + painter->drawPixmap( 0, 0, pm ); + } + else if ( brush.gradient() ) + { + QVector rects; + + if ( brush.gradient()->coordinateMode() == QGradient::ObjectBoundingMode ) + { + rects += canvas->rect(); + } + else + { + rects = painter->clipRegion().rects(); + } + +#if 1 + bool useRaster = false; + + if ( painter->paintEngine()->type() == QPaintEngine::X11 ) + { + // Qt 4.7.1: gradients on X11 are broken ( subrects + + // QGradient::StretchToDeviceMode ) and horrible slow. + // As workaround we have to use the raster paintengine. + // Even if the QImage -> QPixmap translation is slow + // it is three times faster, than using X11 directly + + useRaster = true; + } +#endif + if ( useRaster ) + { + QImage::Format format = QImage::Format_RGB32; + + const QGradientStops stops = brush.gradient()->stops(); + for ( int i = 0; i < stops.size(); i++ ) + { + if ( stops[i].second.alpha() != 255 ) + { + // don't use Format_ARGB32_Premultiplied. It's + // recommended by the Qt docs, but QPainter::drawImage() + // is horrible slow on X11. + + format = QImage::Format_ARGB32; + break; + } + } + + QImage image( canvas->size(), format ); + + QPainter p( &image ); + p.setPen( Qt::NoPen ); + p.setBrush( brush ); + + p.drawRects( rects ); + + p.end(); + + painter->drawImage( 0, 0, image ); + } + else + { + painter->setPen( Qt::NoPen ); + painter->setBrush( brush ); + + painter->drawRects( rects ); + } + } + else + { + painter->setPen( Qt::NoPen ); + painter->setBrush( brush ); + + painter->drawRects( painter->clipRegion().rects() ); + + } + + painter->restore(); +} + +static inline void qwtRevertPath( QPainterPath &path ) +{ + if ( path.elementCount() == 4 ) + { + QPainterPath::Element el0 = path.elementAt(0); + QPainterPath::Element el3 = path.elementAt(3); + + path.setElementPositionAt( 0, el3.x, el3.y ); + path.setElementPositionAt( 3, el0.x, el0.y ); + } +} + +static QPainterPath qwtCombinePathList( const QRectF &rect, + const QList &pathList ) +{ + if ( pathList.isEmpty() ) + return QPainterPath(); + + QPainterPath ordered[8]; // starting top left + + for ( int i = 0; i < pathList.size(); i++ ) + { + int index = -1; + QPainterPath subPath = pathList[i]; + + const QRectF br = pathList[i].controlPointRect(); + if ( br.center().x() < rect.center().x() ) + { + if ( br.center().y() < rect.center().y() ) + { + if ( qAbs( br.top() - rect.top() ) < + qAbs( br.left() - rect.left() ) ) + { + index = 1; + } + else + { + index = 0; + } + } + else + { + if ( qAbs( br.bottom() - rect.bottom() ) < + qAbs( br.left() - rect.left() ) ) + { + index = 6; + } + else + { + index = 7; + } + } + + if ( subPath.currentPosition().y() > br.center().y() ) + qwtRevertPath( subPath ); + } + else + { + if ( br.center().y() < rect.center().y() ) + { + if ( qAbs( br.top() - rect.top() ) < + qAbs( br.right() - rect.right() ) ) + { + index = 2; + } + else + { + index = 3; + } + } + else + { + if ( qAbs( br.bottom() - rect.bottom() ) < + qAbs( br.right() - rect.right() ) ) + { + index = 5; + } + else + { + index = 4; + } + } + if ( subPath.currentPosition().y() < br.center().y() ) + qwtRevertPath( subPath ); + } + ordered[index] = subPath; + } + + for ( int i = 0; i < 4; i++ ) + { + if ( ordered[ 2 * i].isEmpty() != ordered[2 * i + 1].isEmpty() ) + { + // we don't accept incomplete rounded borders + return QPainterPath(); + } + } + + + const QPolygonF corners( rect ); + + QPainterPath path; + //path.moveTo( rect.topLeft() ); + + for ( int i = 0; i < 4; i++ ) + { + if ( ordered[2 * i].isEmpty() ) + { + path.lineTo( corners[i] ); + } + else + { + path.connectPath( ordered[2 * i] ); + path.connectPath( ordered[2 * i + 1] ); + } + } + + path.closeSubpath(); + +#if 0 + return path.simplified(); +#else + return path; +#endif +} + +static inline void qwtDrawStyledBackground( + QWidget *w, QPainter *painter ) +{ + QStyleOption opt; + opt.initFrom(w); + w->style()->drawPrimitive( QStyle::PE_Widget, &opt, painter, w); +} + +static QWidget *qwtBackgroundWidget( QWidget *w ) +{ + if ( w->parentWidget() == NULL ) + return w; + + if ( w->autoFillBackground() ) + { + const QBrush brush = w->palette().brush( w->backgroundRole() ); + if ( brush.color().alpha() > 0 ) + return w; + } + + if ( w->testAttribute( Qt::WA_StyledBackground ) ) + { + QImage image( 1, 1, QImage::Format_ARGB32 ); + image.fill( Qt::transparent ); + + QPainter painter( &image ); + painter.translate( -w->rect().center() ); + qwtDrawStyledBackground( w, &painter ); + painter.end(); + + if ( qAlpha( image.pixel( 0, 0 ) ) != 0 ) + return w; + } + + return qwtBackgroundWidget( w->parentWidget() ); +} + +static void qwtFillBackground( QPainter *painter, + QWidget *widget, const QVector &fillRects ) +{ + if ( fillRects.isEmpty() ) + return; + + QRegion clipRegion; + if ( painter->hasClipping() ) + clipRegion = painter->transform().map( painter->clipRegion() ); + else + clipRegion = widget->contentsRect(); + + // Try to find out which widget fills + // the unfilled areas of the styled background + + QWidget *bgWidget = qwtBackgroundWidget( widget->parentWidget() ); + + for ( int i = 0; i < fillRects.size(); i++ ) + { + const QRect rect = fillRects[i].toAlignedRect(); + if ( clipRegion.intersects( rect ) ) + { + QPixmap pm( rect.size() ); + QwtPainter::fillPixmap( bgWidget, pm, widget->mapTo( bgWidget, rect.topLeft() ) ); + painter->drawPixmap( rect, pm ); + } + } +} + +static void qwtFillBackground( QPainter *painter, QwtPlotCanvas *canvas ) +{ + QVector rects; + + if ( canvas->testAttribute( Qt::WA_StyledBackground ) ) + { + QwtStyleSheetRecorder recorder( canvas->size() ); + + QPainter p( &recorder ); + qwtDrawStyledBackground( canvas, &p ); + p.end(); + + if ( recorder.background.brush.isOpaque() ) + rects = recorder.clipRects; + else + rects += canvas->rect(); + } + else + { + const QRectF r = canvas->rect(); + const double radius = canvas->borderRadius(); + if ( radius > 0.0 ) + { + QSizeF sz( radius, radius ); + + rects += QRectF( r.topLeft(), sz ); + rects += QRectF( r.topRight() - QPointF( radius, 0 ), sz ); + rects += QRectF( r.bottomRight() - QPointF( radius, radius ), sz ); + rects += QRectF( r.bottomLeft() - QPointF( 0, radius ), sz ); + } + } + + qwtFillBackground( painter, canvas, rects); +} + + +class QwtPlotCanvas::PrivateData +{ +public: + PrivateData(): + focusIndicator( NoFocusIndicator ), + borderRadius( 0 ), + paintAttributes( 0 ), + backingStore( NULL ) + { + styleSheet.hasBorder = false; + } + + ~PrivateData() + { + delete backingStore; + } + + FocusIndicator focusIndicator; + double borderRadius; + QwtPlotCanvas::PaintAttributes paintAttributes; + QPixmap *backingStore; + + struct StyleSheet + { + bool hasBorder; + QPainterPath borderPath; + QVector cornerRects; + + struct StyleSheetBackground + { + QBrush brush; + QPointF origin; + } background; + + } styleSheet; + +}; + +/*! + \brief Constructor + + \param plot Parent plot widget + \sa QwtPlot::setCanvas() +*/ +QwtPlotCanvas::QwtPlotCanvas( QwtPlot *plot ): + QFrame( plot ) +{ + setFrameStyle( QFrame::Panel | QFrame::Sunken ); + setLineWidth( 2 ); + + d_data = new PrivateData; + +#ifndef QT_NO_CURSOR + setCursor( Qt::CrossCursor ); +#endif + + setAutoFillBackground( true ); + setPaintAttribute( QwtPlotCanvas::BackingStore, true ); + setPaintAttribute( QwtPlotCanvas::Opaque, true ); + setPaintAttribute( QwtPlotCanvas::HackStyledBackground, true ); +} + +//! Destructor +QwtPlotCanvas::~QwtPlotCanvas() +{ + delete d_data; +} + +//! Return parent plot widget +QwtPlot *QwtPlotCanvas::plot() +{ + return qobject_cast( parent() ); +} + +//! Return parent plot widget +const QwtPlot *QwtPlotCanvas::plot() const +{ + return qobject_cast( parent() ); +} + +/*! + \brief Changing the paint attributes + + \param attribute Paint attribute + \param on On/Off + + \sa testPaintAttribute(), backingStore() +*/ +void QwtPlotCanvas::setPaintAttribute( PaintAttribute attribute, bool on ) +{ + if ( bool( d_data->paintAttributes & attribute ) == on ) + return; + + if ( on ) + d_data->paintAttributes |= attribute; + else + d_data->paintAttributes &= ~attribute; + + switch ( attribute ) + { + case BackingStore: + { + if ( on ) + { + if ( d_data->backingStore == NULL ) + d_data->backingStore = new QPixmap(); + + if ( isVisible() ) + { +#if QT_VERSION >= 0x050000 + *d_data->backingStore = grab( rect() ); +#else + *d_data->backingStore = + QPixmap::grabWidget( this, rect() ); +#endif + } + } + else + { + delete d_data->backingStore; + d_data->backingStore = NULL; + } + break; + } + case Opaque: + { + if ( on ) + setAttribute( Qt::WA_OpaquePaintEvent, true ); + + break; + } + case HackStyledBackground: + case ImmediatePaint: + { + break; + } + } +} + +/*! + Test whether a paint attribute is enabled + + \param attribute Paint attribute + \return true, when attribute is enabled + \sa setPaintAttribute() +*/ +bool QwtPlotCanvas::testPaintAttribute( PaintAttribute attribute ) const +{ + return d_data->paintAttributes & attribute; +} + +//! \return Backing store, might be null +const QPixmap *QwtPlotCanvas::backingStore() const +{ + return d_data->backingStore; +} + +//! Invalidate the internal backing store +void QwtPlotCanvas::invalidateBackingStore() +{ + if ( d_data->backingStore ) + *d_data->backingStore = QPixmap(); +} + +/*! + Set the focus indicator + + \sa FocusIndicator, focusIndicator() +*/ +void QwtPlotCanvas::setFocusIndicator( FocusIndicator focusIndicator ) +{ + d_data->focusIndicator = focusIndicator; +} + +/*! + \return Focus indicator + + \sa FocusIndicator, setFocusIndicator() +*/ +QwtPlotCanvas::FocusIndicator QwtPlotCanvas::focusIndicator() const +{ + return d_data->focusIndicator; +} + +/*! + Set the radius for the corners of the border frame + + \param radius Radius of a rounded corner + \sa borderRadius() +*/ +void QwtPlotCanvas::setBorderRadius( double radius ) +{ + d_data->borderRadius = qMax( 0.0, radius ); +} + +/*! + \return Radius for the corners of the border frame + \sa setBorderRadius() +*/ +double QwtPlotCanvas::borderRadius() const +{ + return d_data->borderRadius; +} + +/*! + Qt event handler for QEvent::PolishRequest and QEvent::StyleChange + + \param event Qt Event + \return See QFrame::event() +*/ +bool QwtPlotCanvas::event( QEvent *event ) +{ + if ( event->type() == QEvent::PolishRequest ) + { + if ( testPaintAttribute( QwtPlotCanvas::Opaque ) ) + { + // Setting a style sheet changes the + // Qt::WA_OpaquePaintEvent attribute, but we insist + // on painting the background. + + setAttribute( Qt::WA_OpaquePaintEvent, true ); + } + } + + if ( event->type() == QEvent::PolishRequest || + event->type() == QEvent::StyleChange ) + { + updateStyleSheetInfo(); + } + + return QFrame::event( event ); +} + +/*! + Paint event + \param event Paint event +*/ +void QwtPlotCanvas::paintEvent( QPaintEvent *event ) +{ + QPainter painter( this ); + painter.setClipRegion( event->region() ); + + if ( testPaintAttribute( QwtPlotCanvas::BackingStore ) && + d_data->backingStore != NULL ) + { + QPixmap &bs = *d_data->backingStore; + if ( bs.size() != size() ) + { + bs = QwtPainter::backingStore( this, size() ); + + if ( testAttribute(Qt::WA_StyledBackground) ) + { + QPainter p( &bs ); + qwtFillBackground( &p, this ); + drawCanvas( &p, true ); + } + else + { + QPainter p; + if ( d_data->borderRadius <= 0.0 ) + { + QwtPainter::fillPixmap( this, bs ); + p.begin( &bs ); + drawCanvas( &p, false ); + } + else + { + p.begin( &bs ); + qwtFillBackground( &p, this ); + drawCanvas( &p, true ); + } + + if ( frameWidth() > 0 ) + drawBorder( &p ); + } + } + + painter.drawPixmap( 0, 0, *d_data->backingStore ); + } + else + { + if ( testAttribute(Qt::WA_StyledBackground ) ) + { + if ( testAttribute( Qt::WA_OpaquePaintEvent ) ) + { + qwtFillBackground( &painter, this ); + drawCanvas( &painter, true ); + } + else + { + drawCanvas( &painter, false ); + } + } + else + { + if ( testAttribute( Qt::WA_OpaquePaintEvent ) ) + { + if ( autoFillBackground() ) + { + qwtFillBackground( &painter, this ); + qwtDrawBackground( &painter, this ); + } + } + else + { + if ( borderRadius() > 0.0 ) + { + QPainterPath clipPath; + clipPath.addRect( rect() ); + clipPath = clipPath.subtracted( borderPath( rect() ) ); + + painter.save(); + + painter.setClipPath( clipPath, Qt::IntersectClip ); + qwtFillBackground( &painter, this ); + qwtDrawBackground( &painter, this ); + + painter.restore(); + } + } + + drawCanvas( &painter, false ); + + if ( frameWidth() > 0 ) + drawBorder( &painter ); + } + } + + if ( hasFocus() && focusIndicator() == CanvasFocusIndicator ) + drawFocusIndicator( &painter ); +} + +void QwtPlotCanvas::drawCanvas( QPainter *painter, bool withBackground ) +{ + bool hackStyledBackground = false; + + if ( withBackground && testAttribute( Qt::WA_StyledBackground ) + && testPaintAttribute( HackStyledBackground ) ) + { + // Antialiasing rounded borders is done by + // inserting pixels with colors between the + // border color and the color on the canvas, + // When the border is painted before the plot items + // these colors are interpolated for the canvas + // and the plot items need to be clipped excluding + // the anialiased pixels. In situations, where + // the plot items fill the area at the rounded + // borders this is noticeable. + // The only way to avoid these annoying "artefacts" + // is to paint the border on top of the plot items. + + if ( d_data->styleSheet.hasBorder && + !d_data->styleSheet.borderPath.isEmpty() ) + { + // We have a border with at least one rounded corner + hackStyledBackground = true; + } + } + + if ( withBackground ) + { + painter->save(); + + if ( testAttribute( Qt::WA_StyledBackground ) ) + { + if ( hackStyledBackground ) + { + // paint background without border + + painter->setPen( Qt::NoPen ); + painter->setBrush( d_data->styleSheet.background.brush ); + painter->setBrushOrigin( d_data->styleSheet.background.origin ); + painter->setClipPath( d_data->styleSheet.borderPath ); + painter->drawRect( contentsRect() ); + } + else + { + qwtDrawStyledBackground( this, painter ); + } + } + else if ( autoFillBackground() ) + { + painter->setPen( Qt::NoPen ); + painter->setBrush( palette().brush( backgroundRole() ) ); + + if ( d_data->borderRadius > 0.0 && ( rect() == frameRect() ) ) + { + if ( frameWidth() > 0 ) + { + painter->setClipPath( borderPath( rect() ) ); + painter->drawRect( rect() ); + } + else + { + painter->setRenderHint( QPainter::Antialiasing, true ); + painter->drawPath( borderPath( rect() ) ); + } + } + else + { + painter->drawRect( rect() ); + } + } + + painter->restore(); + } + + painter->save(); + + if ( !d_data->styleSheet.borderPath.isEmpty() ) + { + painter->setClipPath( + d_data->styleSheet.borderPath, Qt::IntersectClip ); + } + else + { + if ( d_data->borderRadius > 0.0 ) + painter->setClipPath( borderPath( frameRect() ), Qt::IntersectClip ); + else + painter->setClipRect( contentsRect(), Qt::IntersectClip ); + } + + plot()->drawCanvas( painter ); + + painter->restore(); + + if ( withBackground && hackStyledBackground ) + { + // Now paint the border on top + QStyleOptionFrame opt; + opt.initFrom(this); + style()->drawPrimitive( QStyle::PE_Frame, &opt, painter, this); + } +} + +/*! + Draw the border of the plot canvas + + \param painter Painter + \sa setBorderRadius() +*/ +void QwtPlotCanvas::drawBorder( QPainter *painter ) +{ + if ( d_data->borderRadius > 0 ) + { + if ( frameWidth() > 0 ) + { + QwtPainter::drawRoundedFrame( painter, QRectF( frameRect() ), + d_data->borderRadius, d_data->borderRadius, + palette(), frameWidth(), frameStyle() ); + } + } + else + { +#if QT_VERSION >= 0x040500 + QStyleOptionFrameV3 opt; + opt.init(this); + + int frameShape = frameStyle() & QFrame::Shape_Mask; + int frameShadow = frameStyle() & QFrame::Shadow_Mask; + + opt.frameShape = QFrame::Shape( int( opt.frameShape ) | frameShape ); +#if 0 + opt.rect = frameRect(); +#endif + + switch (frameShape) + { + case QFrame::Box: + case QFrame::HLine: + case QFrame::VLine: + case QFrame::StyledPanel: + case QFrame::Panel: + { + opt.lineWidth = lineWidth(); + opt.midLineWidth = midLineWidth(); + break; + } + default: + { + opt.lineWidth = frameWidth(); + break; + } + } + + if ( frameShadow == Sunken ) + opt.state |= QStyle::State_Sunken; + else if ( frameShadow == Raised ) + opt.state |= QStyle::State_Raised; + + style()->drawControl(QStyle::CE_ShapedFrame, &opt, painter, this); +#else + drawFrame( painter ); +#endif + } +} + +/*! + Resize event + \param event Resize event +*/ +void QwtPlotCanvas::resizeEvent( QResizeEvent *event ) +{ + QFrame::resizeEvent( event ); + updateStyleSheetInfo(); +} + +/*! + Draw the focus indication + \param painter Painter +*/ +void QwtPlotCanvas::drawFocusIndicator( QPainter *painter ) +{ + const int margin = 1; + + QRect focusRect = contentsRect(); + focusRect.setRect( focusRect.x() + margin, focusRect.y() + margin, + focusRect.width() - 2 * margin, focusRect.height() - 2 * margin ); + + QwtPainter::drawFocusRect( painter, this, focusRect ); +} + +/*! + Invalidate the paint cache and repaint the canvas + \sa invalidatePaintCache() +*/ +void QwtPlotCanvas::replot() +{ + invalidateBackingStore(); + + if ( testPaintAttribute( QwtPlotCanvas::ImmediatePaint ) ) + repaint( contentsRect() ); + else + update( contentsRect() ); +} + +//! Update the cached information about the current style sheet +void QwtPlotCanvas::updateStyleSheetInfo() +{ + if ( !testAttribute(Qt::WA_StyledBackground ) ) + return; + + QwtStyleSheetRecorder recorder( size() ); + + QPainter painter( &recorder ); + + QStyleOption opt; + opt.initFrom(this); + style()->drawPrimitive( QStyle::PE_Widget, &opt, &painter, this); + + painter.end(); + + d_data->styleSheet.hasBorder = !recorder.border.rectList.isEmpty(); + d_data->styleSheet.cornerRects = recorder.clipRects; + + if ( recorder.background.path.isEmpty() ) + { + if ( !recorder.border.rectList.isEmpty() ) + { + d_data->styleSheet.borderPath = + qwtCombinePathList( rect(), recorder.border.pathList ); + } + } + else + { + d_data->styleSheet.borderPath = recorder.background.path; + d_data->styleSheet.background.brush = recorder.background.brush; + d_data->styleSheet.background.origin = recorder.background.origin; + } +} + +/*! + Calculate the painter path for a styled or rounded border + + When the canvas has no styled background or rounded borders + the painter path is empty. + + \param rect Bounding rectangle of the canvas + \return Painter path, that can be used for clipping +*/ +QPainterPath QwtPlotCanvas::borderPath( const QRect &rect ) const +{ + if ( testAttribute(Qt::WA_StyledBackground ) ) + { + QwtStyleSheetRecorder recorder( rect.size() ); + + QPainter painter( &recorder ); + + QStyleOption opt; + opt.initFrom(this); + opt.rect = rect; + style()->drawPrimitive( QStyle::PE_Widget, &opt, &painter, this); + + painter.end(); + + if ( !recorder.background.path.isEmpty() ) + return recorder.background.path; + + if ( !recorder.border.rectList.isEmpty() ) + return qwtCombinePathList( rect, recorder.border.pathList ); + } + else if ( d_data->borderRadius > 0.0 ) + { + double fw2 = frameWidth() * 0.5; + QRectF r = QRectF(rect).adjusted( fw2, fw2, -fw2, -fw2 ); + + QPainterPath path; + path.addRoundedRect( r, d_data->borderRadius, d_data->borderRadius ); + return path; + } + + return QPainterPath(); +} diff --git a/qwt/src/qwt_plot_canvas.h b/qwt/src/qwt_plot_canvas.h new file mode 100644 index 000000000..daea8a086 --- /dev/null +++ b/qwt/src/qwt_plot_canvas.h @@ -0,0 +1,171 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_CANVAS_H +#define QWT_PLOT_CANVAS_H + +#include "qwt_global.h" +#include +#include + +class QwtPlot; +class QPixmap; + +/*! + \brief Canvas of a QwtPlot. + + Canvas is the widget where all plot items are displayed + + \sa QwtPlot::setCanvas(), QwtPlotGLCanvas +*/ +class QWT_EXPORT QwtPlotCanvas : public QFrame +{ + Q_OBJECT + + Q_PROPERTY( double borderRadius READ borderRadius WRITE setBorderRadius ) + +public: + + /*! + \brief Paint attributes + + The default setting enables BackingStore and Opaque. + + \sa setPaintAttribute(), testPaintAttribute() + */ + enum PaintAttribute + { + /*! + \brief Paint double buffered reusing the content + of the pixmap buffer when possible. + + Using a backing store might improve the performance + significantly, when working with widget overlays ( like rubber bands ). + Disabling the cache might improve the performance for + incremental paints (using QwtPlotDirectPainter ). + + \sa backingStore(), invalidateBackingStore() + */ + BackingStore = 1, + + /*! + \brief Try to fill the complete contents rectangle + of the plot canvas + + When using styled backgrounds Qt assumes, that the + canvas doesn't fill its area completely + ( f.e because of rounded borders ) and fills the area + below the canvas. When this is done with gradients it might + result in a serious performance bottleneck - depending on the size. + + When the Opaque attribute is enabled the canvas tries to + identify the gaps with some heuristics and to fill those only. + + \warning Will not work for semitransparent backgrounds + */ + Opaque = 2, + + /*! + \brief Try to improve painting of styled backgrounds + + QwtPlotCanvas supports the box model attributes for + customizing the layout with style sheets. Unfortunately + the design of Qt style sheets has no concept how to + handle backgrounds with rounded corners - beside of padding. + + When HackStyledBackground is enabled the plot canvas tries + to separate the background from the background border + by reverse engineering to paint the background before and + the border after the plot items. In this order the border + gets perfectly antialiased and you can avoid some pixel + artifacts in the corners. + */ + HackStyledBackground = 4, + + /*! + When ImmediatePaint is set replot() calls repaint() + instead of update(). + + \sa replot(), QWidget::repaint(), QWidget::update() + */ + ImmediatePaint = 8 + }; + + //! Paint attributes + typedef QFlags PaintAttributes; + + /*! + \brief Focus indicator + The default setting is NoFocusIndicator + \sa setFocusIndicator(), focusIndicator(), paintFocus() + */ + + enum FocusIndicator + { + //! Don't paint a focus indicator + NoFocusIndicator, + + /*! + The focus is related to the complete canvas. + Paint the focus indicator using paintFocus() + */ + CanvasFocusIndicator, + + /*! + The focus is related to an item (curve, point, ...) on + the canvas. It is up to the application to display a + focus indication using f.e. highlighting. + */ + ItemFocusIndicator + }; + + explicit QwtPlotCanvas( QwtPlot * = NULL ); + virtual ~QwtPlotCanvas(); + + QwtPlot *plot(); + const QwtPlot *plot() const; + + void setFocusIndicator( FocusIndicator ); + FocusIndicator focusIndicator() const; + + void setBorderRadius( double ); + double borderRadius() const; + + void setPaintAttribute( PaintAttribute, bool on = true ); + bool testPaintAttribute( PaintAttribute ) const; + + const QPixmap *backingStore() const; + void invalidateBackingStore(); + + virtual bool event( QEvent * ); + + Q_INVOKABLE QPainterPath borderPath( const QRect & ) const; + +public Q_SLOTS: + void replot(); + +protected: + virtual void paintEvent( QPaintEvent * ); + virtual void resizeEvent( QResizeEvent * ); + + virtual void drawFocusIndicator( QPainter * ); + virtual void drawBorder( QPainter * ); + + void updateStyleSheetInfo(); + +private: + void drawCanvas( QPainter *, bool withBackground ); + + class PrivateData; + PrivateData *d_data; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS( QwtPlotCanvas::PaintAttributes ) + +#endif diff --git a/qwt/src/qwt_plot_curve.cpp b/qwt/src/qwt_plot_curve.cpp new file mode 100644 index 000000000..140cc5996 --- /dev/null +++ b/qwt/src/qwt_plot_curve.cpp @@ -0,0 +1,1191 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#include "qwt_plot_curve.h" +#include "qwt_point_data.h" +#include "qwt_math.h" +#include "qwt_clipper.h" +#include "qwt_painter.h" +#include "qwt_scale_map.h" +#include "qwt_plot.h" +#include "qwt_curve_fitter.h" +#include "qwt_symbol.h" +#include "qwt_point_mapper.h" +#include +#include +#include +#include + +static void qwtUpdateLegendIconSize( QwtPlotCurve *curve ) +{ + if ( curve->symbol() && + curve->testLegendAttribute( QwtPlotCurve::LegendShowSymbol ) ) + { + QSize sz = curve->symbol()->boundingRect().size(); + sz += QSize( 2, 2 ); // margin + + if ( curve->testLegendAttribute( QwtPlotCurve::LegendShowLine ) ) + { + // Avoid, that the line is completely covered by the symbol + + int w = qCeil( 1.5 * sz.width() ); + if ( w % 2 ) + w++; + + sz.setWidth( qMax( 8, w ) ); + } + + curve->setLegendIconSize( sz ); + } +} + +static int qwtVerifyRange( int size, int &i1, int &i2 ) +{ + if ( size < 1 ) + return 0; + + i1 = qBound( 0, i1, size - 1 ); + i2 = qBound( 0, i2, size - 1 ); + + if ( i1 > i2 ) + qSwap( i1, i2 ); + + return ( i2 - i1 + 1 ); +} + +class QwtPlotCurve::PrivateData +{ +public: + PrivateData(): + style( QwtPlotCurve::Lines ), + baseline( 0.0 ), + symbol( NULL ), + attributes( 0 ), + paintAttributes( + QwtPlotCurve::ClipPolygons | QwtPlotCurve::FilterPoints ), + legendAttributes( 0 ) + { + pen = QPen( Qt::black ); + curveFitter = new QwtSplineCurveFitter; + } + + ~PrivateData() + { + delete symbol; + delete curveFitter; + } + + QwtPlotCurve::CurveStyle style; + double baseline; + + const QwtSymbol *symbol; + QwtCurveFitter *curveFitter; + + QPen pen; + QBrush brush; + + QwtPlotCurve::CurveAttributes attributes; + QwtPlotCurve::PaintAttributes paintAttributes; + + QwtPlotCurve::LegendAttributes legendAttributes; +}; + +/*! + Constructor + \param title Title of the curve +*/ +QwtPlotCurve::QwtPlotCurve( const QwtText &title ): + QwtPlotSeriesItem( title ) +{ + init(); +} + +/*! + Constructor + \param title Title of the curve +*/ +QwtPlotCurve::QwtPlotCurve( const QString &title ): + QwtPlotSeriesItem( QwtText( title ) ) +{ + init(); +} + +//! Destructor +QwtPlotCurve::~QwtPlotCurve() +{ + delete d_data; +} + +//! Initialize internal members +void QwtPlotCurve::init() +{ + setItemAttribute( QwtPlotItem::Legend ); + setItemAttribute( QwtPlotItem::AutoScale ); + + d_data = new PrivateData; + setData( new QwtPointSeriesData() ); + + setZ( 20.0 ); +} + +//! \return QwtPlotItem::Rtti_PlotCurve +int QwtPlotCurve::rtti() const +{ + return QwtPlotItem::Rtti_PlotCurve; +} + +/*! + Specify an attribute how to draw the curve + + \param attribute Paint attribute + \param on On/Off + \sa testPaintAttribute() +*/ +void QwtPlotCurve::setPaintAttribute( PaintAttribute attribute, bool on ) +{ + if ( on ) + d_data->paintAttributes |= attribute; + else + d_data->paintAttributes &= ~attribute; +} + +/*! + \return True, when attribute is enabled + \sa setPaintAttribute() +*/ +bool QwtPlotCurve::testPaintAttribute( PaintAttribute attribute ) const +{ + return ( d_data->paintAttributes & attribute ); +} + +/*! + Specify an attribute how to draw the legend icon + + \param attribute Attribute + \param on On/Off + /sa testLegendAttribute(). legendIcon() +*/ +void QwtPlotCurve::setLegendAttribute( LegendAttribute attribute, bool on ) +{ + if ( on != testLegendAttribute( attribute ) ) + { + if ( on ) + d_data->legendAttributes |= attribute; + else + d_data->legendAttributes &= ~attribute; + + qwtUpdateLegendIconSize( this ); + legendChanged(); + } +} + +/*! + \return True, when attribute is enabled + \sa setLegendAttribute() +*/ +bool QwtPlotCurve::testLegendAttribute( LegendAttribute attribute ) const +{ + return ( d_data->legendAttributes & attribute ); +} + +/*! + Set the curve's drawing style + + \param style Curve style + \sa style() +*/ +void QwtPlotCurve::setStyle( CurveStyle style ) +{ + if ( style != d_data->style ) + { + d_data->style = style; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Style of the curve + \sa setStyle() +*/ +QwtPlotCurve::CurveStyle QwtPlotCurve::style() const +{ + return d_data->style; +} + +/*! + \brief Assign a symbol + + The curve will take the ownership of the symbol, hence the previously + set symbol will be delete by setting a new one. If \p symbol is + \c NULL no symbol will be drawn. + + \param symbol Symbol + \sa symbol() +*/ +void QwtPlotCurve::setSymbol( QwtSymbol *symbol ) +{ + if ( symbol != d_data->symbol ) + { + delete d_data->symbol; + d_data->symbol = symbol; + + qwtUpdateLegendIconSize( this ); + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Current symbol or NULL, when no symbol has been assigned + \sa setSymbol() +*/ +const QwtSymbol *QwtPlotCurve::symbol() const +{ + return d_data->symbol; +} + +/*! + Build and assign a pen + + In Qt5 the default pen width is 1.0 ( 0.0 in Qt4 ) what makes it + non cosmetic ( see QPen::isCosmetic() ). This method has been introduced + to hide this incompatibility. + + \param color Pen color + \param width Pen width + \param style Pen style + + \sa pen(), brush() + */ +void QwtPlotCurve::setPen( const QColor &color, qreal width, Qt::PenStyle style ) +{ + setPen( QPen( color, width, style ) ); +} + +/*! + Assign a pen + + \param pen New pen + \sa pen(), brush() +*/ +void QwtPlotCurve::setPen( const QPen &pen ) +{ + if ( pen != d_data->pen ) + { + d_data->pen = pen; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Pen used to draw the lines + \sa setPen(), brush() +*/ +const QPen& QwtPlotCurve::pen() const +{ + return d_data->pen; +} + +/*! + \brief Assign a brush. + + In case of brush.style() != QBrush::NoBrush + and style() != QwtPlotCurve::Sticks + the area between the curve and the baseline will be filled. + + In case !brush.color().isValid() the area will be filled by + pen.color(). The fill algorithm simply connects the first and the + last curve point to the baseline. So the curve data has to be sorted + (ascending or descending). + + \param brush New brush + \sa brush(), setBaseline(), baseline() +*/ +void QwtPlotCurve::setBrush( const QBrush &brush ) +{ + if ( brush != d_data->brush ) + { + d_data->brush = brush; + + legendChanged(); + itemChanged(); + } +} + +/*! + \return Brush used to fill the area between lines and the baseline + \sa setBrush(), setBaseline(), baseline() +*/ +const QBrush& QwtPlotCurve::brush() const +{ + return d_data->brush; +} + +/*! + Draw an interval of the curve + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param canvasRect Contents rectangle of the canvas + \param from Index of the first point to be painted + \param to Index of the last point to be painted. If to < 0 the + curve will be painted to its last point. + + \sa drawCurve(), drawSymbols(), +*/ +void QwtPlotCurve::drawSeries( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + const size_t numSamples = dataSize(); + + if ( !painter || numSamples <= 0 ) + return; + + if ( to < 0 ) + to = numSamples - 1; + + if ( qwtVerifyRange( numSamples, from, to ) > 0 ) + { + painter->save(); + painter->setPen( d_data->pen ); + + /* + Qt 4.0.0 is slow when drawing lines, but it's even + slower when the painter has a brush. So we don't + set the brush before we really need it. + */ + + drawCurve( painter, d_data->style, xMap, yMap, canvasRect, from, to ); + painter->restore(); + + if ( d_data->symbol && + ( d_data->symbol->style() != QwtSymbol::NoSymbol ) ) + { + painter->save(); + drawSymbols( painter, *d_data->symbol, + xMap, yMap, canvasRect, from, to ); + painter->restore(); + } + } +} + +/*! + \brief Draw the line part (without symbols) of a curve interval. + \param painter Painter + \param style curve style, see QwtPlotCurve::CurveStyle + \param xMap x map + \param yMap y map + \param canvasRect Contents rectangle of the canvas + \param from index of the first point to be painted + \param to index of the last point to be painted + \sa draw(), drawDots(), drawLines(), drawSteps(), drawSticks() +*/ +void QwtPlotCurve::drawCurve( QPainter *painter, int style, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + switch ( style ) + { + case Lines: + if ( testCurveAttribute( Fitted ) ) + { + // we always need the complete + // curve for fitting + from = 0; + to = dataSize() - 1; + } + drawLines( painter, xMap, yMap, canvasRect, from, to ); + break; + case Sticks: + drawSticks( painter, xMap, yMap, canvasRect, from, to ); + break; + case Steps: + drawSteps( painter, xMap, yMap, canvasRect, from, to ); + break; + case Dots: + drawDots( painter, xMap, yMap, canvasRect, from, to ); + break; + case NoCurve: + default: + break; + } +} + +/*! + \brief Draw lines + + If the CurveAttribute Fitted is enabled a QwtCurveFitter tries + to interpolate/smooth the curve, before it is painted. + + \param painter Painter + \param xMap x map + \param yMap y map + \param canvasRect Contents rectangle of the canvas + \param from index of the first point to be painted + \param to index of the last point to be painted + + \sa setCurveAttribute(), setCurveFitter(), draw(), + drawLines(), drawDots(), drawSteps(), drawSticks() +*/ +void QwtPlotCurve::drawLines( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + if ( from > to ) + return; + + const bool doAlign = QwtPainter::roundingAlignment( painter ); + const bool doFit = ( d_data->attributes & Fitted ) && d_data->curveFitter; + const bool doFill = ( d_data->brush.style() != Qt::NoBrush ) + && ( d_data->brush.color().alpha() > 0 ); + + QRectF clipRect; + if ( d_data->paintAttributes & ClipPolygons ) + { + qreal pw = qMax( qreal( 1.0 ), painter->pen().widthF()); + clipRect = canvasRect.adjusted(-pw, -pw, pw, pw); + } + + bool doIntegers = false; + +#if QT_VERSION < 0x040800 + + // For Qt <= 4.7 the raster paint engine is significantly faster + // for rendering QPolygon than for QPolygonF. So let's + // see if we can use it. + + if ( painter->paintEngine()->type() == QPaintEngine::Raster ) + { + // In case of filling or fitting performance doesn't count + // because both operations are much more expensive + // then drawing the polyline itself + + if ( !doFit && !doFill ) + doIntegers = true; + } +#endif + + const bool noDuplicates = d_data->paintAttributes & FilterPoints; + + QwtPointMapper mapper; + mapper.setFlag( QwtPointMapper::RoundPoints, doAlign ); + mapper.setFlag( QwtPointMapper::WeedOutPoints, noDuplicates ); + mapper.setBoundingRect( canvasRect ); + + if ( doIntegers ) + { + const QPolygon polyline = mapper.toPolygon( + xMap, yMap, data(), from, to ); + + if ( d_data->paintAttributes & ClipPolygons ) + { + const QPolygon clipped = QwtClipper::clipPolygon( + clipRect.toAlignedRect(), polyline, false ); + + QwtPainter::drawPolyline( painter, clipped ); + } + else + { + QwtPainter::drawPolyline( painter, polyline ); + } + } + else + { + QPolygonF polyline = mapper.toPolygonF( xMap, yMap, + data(), from, to ); + + if ( doFit ) + polyline = d_data->curveFitter->fitCurve( polyline ); + + if ( d_data->paintAttributes & ClipPolygons ) + { + const QPolygonF clipped = QwtClipper::clipPolygonF( + clipRect, polyline, false ); + + QwtPainter::drawPolyline( painter, clipped ); + } + else + { + QwtPainter::drawPolyline( painter, polyline ); + } + + if ( doFill ) + { + fillCurve( painter, xMap, yMap, canvasRect, polyline ); + } + } +} + +/*! + Draw sticks + + \param painter Painter + \param xMap x map + \param yMap y map + \param canvasRect Contents rectangle of the canvas + \param from index of the first point to be painted + \param to index of the last point to be painted + + \sa draw(), drawCurve(), drawDots(), drawLines(), drawSteps() +*/ +void QwtPlotCurve::drawSticks( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &, int from, int to ) const +{ + painter->save(); + painter->setRenderHint( QPainter::Antialiasing, false ); + + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + double x0 = xMap.transform( d_data->baseline ); + double y0 = yMap.transform( d_data->baseline ); + if ( doAlign ) + { + x0 = qRound( x0 ); + y0 = qRound( y0 ); + } + + const Qt::Orientation o = orientation(); + + const QwtSeriesData *series = data(); + + for ( int i = from; i <= to; i++ ) + { + const QPointF sample = series->sample( i ); + double xi = xMap.transform( sample.x() ); + double yi = yMap.transform( sample.y() ); + if ( doAlign ) + { + xi = qRound( xi ); + yi = qRound( yi ); + } + + if ( o == Qt::Horizontal ) + QwtPainter::drawLine( painter, x0, yi, xi, yi ); + else + QwtPainter::drawLine( painter, xi, y0, xi, yi ); + } + + painter->restore(); +} + +/*! + Draw dots + + \param painter Painter + \param xMap x map + \param yMap y map + \param canvasRect Contents rectangle of the canvas + \param from index of the first point to be painted + \param to index of the last point to be painted + + \sa draw(), drawCurve(), drawSticks(), drawLines(), drawSteps() +*/ +void QwtPlotCurve::drawDots( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + const QColor color = painter->pen().color(); + + if ( painter->pen().style() == Qt::NoPen || color.alpha() == 0 ) + { + return; + } + + const bool doFill = ( d_data->brush.style() != Qt::NoBrush ) + && ( d_data->brush.color().alpha() > 0 ); + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + QwtPointMapper mapper; + mapper.setBoundingRect( canvasRect ); + mapper.setFlag( QwtPointMapper::RoundPoints, doAlign ); + + if ( d_data->paintAttributes & FilterPoints ) + { + if ( ( color.alpha() == 255 ) + && !( painter->renderHints() & QPainter::Antialiasing ) ) + { + mapper.setFlag( QwtPointMapper::WeedOutPoints, true ); + } + } + + if ( doFill ) + { + mapper.setFlag( QwtPointMapper::WeedOutPoints, false ); + + QPolygonF points = mapper.toPointsF( + xMap, yMap, data(), from, to ); + + QwtPainter::drawPoints( painter, points ); + fillCurve( painter, xMap, yMap, canvasRect, points ); + } + else if ( d_data->paintAttributes & ImageBuffer ) + { + const QImage image = mapper.toImage( xMap, yMap, + data(), from, to, d_data->pen, + painter->testRenderHint( QPainter::Antialiasing ), + renderThreadCount() ); + + painter->drawImage( canvasRect.toAlignedRect(), image ); + } + else if ( d_data->paintAttributes & MinimizeMemory ) + { + const QwtSeriesData *series = data(); + + for ( int i = from; i <= to; i++ ) + { + const QPointF sample = series->sample( i ); + + double xi = xMap.transform( sample.x() ); + double yi = yMap.transform( sample.y() ); + + if ( doAlign ) + { + xi = qRound( xi ); + yi = qRound( yi ); + } + + QwtPainter::drawPoint( painter, QPointF( xi, yi ) ); + } + } + else + { + if ( doAlign ) + { + const QPolygon points = mapper.toPoints( + xMap, yMap, data(), from, to ); + + QwtPainter::drawPoints( painter, points ); + } + else + { + const QPolygonF points = mapper.toPointsF( + xMap, yMap, data(), from, to ); + + QwtPainter::drawPoints( painter, points ); + } + } +} + +/*! + Draw step function + + The direction of the steps depends on Inverted attribute. + + \param painter Painter + \param xMap x map + \param yMap y map + \param canvasRect Contents rectangle of the canvas + \param from index of the first point to be painted + \param to index of the last point to be painted + + \sa CurveAttribute, setCurveAttribute(), + draw(), drawCurve(), drawDots(), drawLines(), drawSticks() +*/ +void QwtPlotCurve::drawSteps( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + QPolygonF polygon( 2 * ( to - from ) + 1 ); + QPointF *points = polygon.data(); + + bool inverted = orientation() == Qt::Vertical; + if ( d_data->attributes & Inverted ) + inverted = !inverted; + + const QwtSeriesData *series = data(); + + int i, ip; + for ( i = from, ip = 0; i <= to; i++, ip += 2 ) + { + const QPointF sample = series->sample( i ); + double xi = xMap.transform( sample.x() ); + double yi = yMap.transform( sample.y() ); + if ( doAlign ) + { + xi = qRound( xi ); + yi = qRound( yi ); + } + + if ( ip > 0 ) + { + const QPointF &p0 = points[ip - 2]; + QPointF &p = points[ip - 1]; + + if ( inverted ) + { + p.rx() = p0.x(); + p.ry() = yi; + } + else + { + p.rx() = xi; + p.ry() = p0.y(); + } + } + + points[ip].rx() = xi; + points[ip].ry() = yi; + } + + if ( d_data->paintAttributes & ClipPolygons ) + { + const QPolygonF clipped = QwtClipper::clipPolygonF( + canvasRect, polygon, false ); + + QwtPainter::drawPolyline( painter, clipped ); + } + else + { + QwtPainter::drawPolyline( painter, polygon ); + } + + if ( d_data->brush.style() != Qt::NoBrush ) + fillCurve( painter, xMap, yMap, canvasRect, polygon ); +} + + +/*! + Specify an attribute for drawing the curve + + \param attribute Curve attribute + \param on On/Off + + /sa testCurveAttribute(), setCurveFitter() +*/ +void QwtPlotCurve::setCurveAttribute( CurveAttribute attribute, bool on ) +{ + if ( bool( d_data->attributes & attribute ) == on ) + return; + + if ( on ) + d_data->attributes |= attribute; + else + d_data->attributes &= ~attribute; + + itemChanged(); +} + +/*! + \return true, if attribute is enabled + \sa setCurveAttribute() +*/ +bool QwtPlotCurve::testCurveAttribute( CurveAttribute attribute ) const +{ + return d_data->attributes & attribute; +} + +/*! + Assign a curve fitter + + The curve fitter "smooths" the curve points, when the Fitted + CurveAttribute is set. setCurveFitter(NULL) also disables curve fitting. + + The curve fitter operates on the translated points ( = widget coordinates) + to be functional for logarithmic scales. Obviously this is less performant + for fitting algorithms, that reduce the number of points. + + For situations, where curve fitting is used to improve the performance + of painting huge series of points it might be better to execute the fitter + on the curve points once and to cache the result in the QwtSeriesData object. + + \param curveFitter() Curve fitter + \sa Fitted +*/ +void QwtPlotCurve::setCurveFitter( QwtCurveFitter *curveFitter ) +{ + delete d_data->curveFitter; + d_data->curveFitter = curveFitter; + + itemChanged(); +} + +/*! + Get the curve fitter. If curve fitting is disabled NULL is returned. + + \return Curve fitter + \sa setCurveFitter(), Fitted +*/ +QwtCurveFitter *QwtPlotCurve::curveFitter() const +{ + return d_data->curveFitter; +} + +/*! + Fill the area between the curve and the baseline with + the curve brush + + \param painter Painter + \param xMap x map + \param yMap y map + \param canvasRect Contents rectangle of the canvas + \param polygon Polygon - will be modified ! + + \sa setBrush(), setBaseline(), setStyle() +*/ +void QwtPlotCurve::fillCurve( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, QPolygonF &polygon ) const +{ + if ( d_data->brush.style() == Qt::NoBrush ) + return; + + closePolyline( painter, xMap, yMap, polygon ); + if ( polygon.count() <= 2 ) // a line can't be filled + return; + + QBrush brush = d_data->brush; + if ( !brush.color().isValid() ) + brush.setColor( d_data->pen.color() ); + + if ( d_data->paintAttributes & ClipPolygons ) + polygon = QwtClipper::clipPolygonF( canvasRect, polygon, true ); + + painter->save(); + + painter->setPen( Qt::NoPen ); + painter->setBrush( brush ); + + QwtPainter::drawPolygon( painter, polygon ); + + painter->restore(); +} + +/*! + \brief Complete a polygon to be a closed polygon including the + area between the original polygon and the baseline. + + \param painter Painter + \param xMap X map + \param yMap Y map + \param polygon Polygon to be completed +*/ +void QwtPlotCurve::closePolyline( QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + QPolygonF &polygon ) const +{ + if ( polygon.size() < 2 ) + return; + + const bool doAlign = QwtPainter::roundingAlignment( painter ); + + double baseline = d_data->baseline; + + if ( orientation() == Qt::Vertical ) + { + if ( yMap.transformation() ) + baseline = yMap.transformation()->bounded( baseline ); + + double refY = yMap.transform( baseline ); + if ( doAlign ) + refY = qRound( refY ); + + polygon += QPointF( polygon.last().x(), refY ); + polygon += QPointF( polygon.first().x(), refY ); + } + else + { + if ( xMap.transformation() ) + baseline = xMap.transformation()->bounded( baseline ); + + double refX = xMap.transform( baseline ); + if ( doAlign ) + refX = qRound( refX ); + + polygon += QPointF( refX, polygon.last().y() ); + polygon += QPointF( refX, polygon.first().y() ); + } +} + +/*! + Draw symbols + + \param painter Painter + \param symbol Curve symbol + \param xMap x map + \param yMap y map + \param canvasRect Contents rectangle of the canvas + \param from Index of the first point to be painted + \param to Index of the last point to be painted + + \sa setSymbol(), drawSeries(), drawCurve() +*/ +void QwtPlotCurve::drawSymbols( QPainter *painter, const QwtSymbol &symbol, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRectF &canvasRect, int from, int to ) const +{ + QwtPointMapper mapper; + mapper.setFlag( QwtPointMapper::RoundPoints, + QwtPainter::roundingAlignment( painter ) ); + mapper.setFlag( QwtPointMapper::WeedOutPoints, + testPaintAttribute( QwtPlotCurve::FilterPoints ) ); + mapper.setBoundingRect( canvasRect ); + + const int chunkSize = 500; + + for ( int i = from; i <= to; i += chunkSize ) + { + const int n = qMin( chunkSize, to - i + 1 ); + + const QPolygonF points = mapper.toPointsF( xMap, yMap, + data(), i, i + n - 1 ); + + if ( points.size() > 0 ) + symbol.drawSymbols( painter, points ); + } +} + +/*! + \brief Set the value of the baseline + + The baseline is needed for filling the curve with a brush or + the Sticks drawing style. + + The interpretation of the baseline depends on the orientation(). + With Qt::Horizontal, the baseline is interpreted as a horizontal line + at y = baseline(), with Qt::Vertical, it is interpreted as a vertical + line at x = baseline(). + + The default value is 0.0. + + \param value Value of the baseline + \sa baseline(), setBrush(), setStyle(), QwtPlotAbstractSeriesItem::orientation() +*/ +void QwtPlotCurve::setBaseline( double value ) +{ + if ( d_data->baseline != value ) + { + d_data->baseline = value; + itemChanged(); + } +} + +/*! + \return Value of the baseline + \sa setBaseline() +*/ +double QwtPlotCurve::baseline() const +{ + return d_data->baseline; +} + +/*! + Find the closest curve point for a specific position + + \param pos Position, where to look for the closest curve point + \param dist If dist != NULL, closestPoint() returns the distance between + the position and the closest curve point + \return Index of the closest curve point, or -1 if none can be found + ( f.e when the curve has no points ) + \note closestPoint() implements a dumb algorithm, that iterates + over all points +*/ +int QwtPlotCurve::closestPoint( const QPoint &pos, double *dist ) const +{ + const size_t numSamples = dataSize(); + + if ( plot() == NULL || numSamples <= 0 ) + return -1; + + const QwtSeriesData *series = data(); + + const QwtScaleMap xMap = plot()->canvasMap( xAxis() ); + const QwtScaleMap yMap = plot()->canvasMap( yAxis() ); + + int index = -1; + double dmin = 1.0e10; + + for ( uint i = 0; i < numSamples; i++ ) + { + const QPointF sample = series->sample( i ); + + const double cx = xMap.transform( sample.x() ) - pos.x(); + const double cy = yMap.transform( sample.y() ) - pos.y(); + + const double f = qwtSqr( cx ) + qwtSqr( cy ); + if ( f < dmin ) + { + index = i; + dmin = f; + } + } + if ( dist ) + *dist = qSqrt( dmin ); + + return index; +} + +/*! + \return Icon representing the curve on the legend + + \param index Index of the legend entry + ( ignored as there is only one ) + \param size Icon size + + \sa QwtPlotItem::setLegendIconSize(), QwtPlotItem::legendData() + */ +QwtGraphic QwtPlotCurve::legendIcon( int index, + const QSizeF &size ) const +{ + Q_UNUSED( index ); + + if ( size.isEmpty() ) + return QwtGraphic(); + + QwtGraphic graphic; + graphic.setDefaultSize( size ); + graphic.setRenderHint( QwtGraphic::RenderPensUnscaled, true ); + + QPainter painter( &graphic ); + painter.setRenderHint( QPainter::Antialiasing, + testRenderHint( QwtPlotItem::RenderAntialiased ) ); + + if ( d_data->legendAttributes == 0 || + d_data->legendAttributes & QwtPlotCurve::LegendShowBrush ) + { + QBrush brush = d_data->brush; + + if ( brush.style() == Qt::NoBrush && + d_data->legendAttributes == 0 ) + { + if ( style() != QwtPlotCurve::NoCurve ) + { + brush = QBrush( pen().color() ); + } + else if ( d_data->symbol && + ( d_data->symbol->style() != QwtSymbol::NoSymbol ) ) + { + brush = QBrush( d_data->symbol->pen().color() ); + } + } + + if ( brush.style() != Qt::NoBrush ) + { + QRectF r( 0, 0, size.width(), size.height() ); + painter.fillRect( r, brush ); + } + } + + if ( d_data->legendAttributes & QwtPlotCurve::LegendShowLine ) + { + if ( pen() != Qt::NoPen ) + { + QPen pn = pen(); + pn.setCapStyle( Qt::FlatCap ); + + painter.setPen( pn ); + + const double y = 0.5 * size.height(); + QwtPainter::drawLine( &painter, 0.0, y, size.width(), y ); + } + } + + if ( d_data->legendAttributes & QwtPlotCurve::LegendShowSymbol ) + { + if ( d_data->symbol ) + { + QRectF r( 0, 0, size.width(), size.height() ); + d_data->symbol->drawSymbol( &painter, r ); + } + } + + return graphic; +} + +/*! + Initialize data with an array of points. + + \param samples Vector of points + \note QVector is implicitly shared + \note QPolygonF is derived from QVector +*/ +void QwtPlotCurve::setSamples( const QVector &samples ) +{ + setData( new QwtPointSeriesData( samples ) ); +} + +/*! + Assign a series of points + + setSamples() is just a wrapper for setData() without any additional + value - beside that it is easier to find for the developer. + + \param data Data + \warning The item takes ownership of the data object, deleting + it when its not used anymore. +*/ +void QwtPlotCurve::setSamples( QwtSeriesData *data ) +{ + setData( data ); +} + +#ifndef QWT_NO_COMPAT + +/*! + \brief Initialize the data by pointing to memory blocks which + are not managed by QwtPlotCurve. + + setRawSamples is provided for efficiency. + It is important to keep the pointers + during the lifetime of the underlying QwtCPointerData class. + + \param xData pointer to x data + \param yData pointer to y data + \param size size of x and y + + \sa QwtCPointerData +*/ +void QwtPlotCurve::setRawSamples( + const double *xData, const double *yData, int size ) +{ + setData( new QwtCPointerData( xData, yData, size ) ); +} + +/*! + Set data by copying x- and y-values from specified memory blocks. + Contrary to setRawSamples(), this function makes a 'deep copy' of + the data. + + \param xData pointer to x values + \param yData pointer to y values + \param size size of xData and yData + + \sa QwtPointArrayData +*/ +void QwtPlotCurve::setSamples( + const double *xData, const double *yData, int size ) +{ + setData( new QwtPointArrayData( xData, yData, size ) ); +} + +/*! + \brief Initialize data with x- and y-arrays (explicitly shared) + + \param xData x data + \param yData y data + + \sa QwtPointArrayData +*/ +void QwtPlotCurve::setSamples( const QVector &xData, + const QVector &yData ) +{ + setData( new QwtPointArrayData( xData, yData ) ); +} + +#endif // !QWT_NO_COMPAT + diff --git a/qwt/src/qwt_plot_curve.h b/qwt/src/qwt_plot_curve.h new file mode 100644 index 000000000..3421abf81 --- /dev/null +++ b/qwt/src/qwt_plot_curve.h @@ -0,0 +1,337 @@ +/* -*- mode: C++ ; c-file-style: "stroustrup" -*- ***************************** + * Qwt Widget Library + * Copyright (C) 1997 Josef Wilgen + * Copyright (C) 2002 Uwe Rathmann + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the Qwt License, Version 1.0 + *****************************************************************************/ + +#ifndef QWT_PLOT_CURVE_H +#define QWT_PLOT_CURVE_H + +#include "qwt_global.h" +#include "qwt_plot_seriesitem.h" +#include "qwt_series_data.h" +#include "qwt_text.h" +#include +#include + +class QPainter; +class QPolygonF; +class QwtScaleMap; +class QwtSymbol; +class QwtCurveFitter; + +/*! + \brief A plot item, that represents a series of points + + A curve is the representation of a series of points in the x-y plane. + It supports different display styles, interpolation ( f.e. spline ) + and symbols. + + \par Usage +