From 13bd8a1f52386408733f9da82260a16edbaa1d4e Mon Sep 17 00:00:00 2001 From: Sean Rhea Date: Sun, 20 Sep 2009 10:09:15 -0700 Subject: [PATCH] check qwt-5.2, r588 into repository --- qwt/CHANGES | 409 +++++ qwt/COPYING | 543 ++++++ qwt/INSTALL | 176 ++ qwt/README | 33 + qwt/admin/msvc-qmake.bat | 58 + qwt/admin/no-qt-keywords.sh | 9 + qwt/admin/svn2package.sh | 299 ++++ qwt/designer/designer.pro | 135 ++ 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 | 39 + qwt/designer/qwt_designer_plugin.cpp | 516 ++++++ qwt/designer/qwt_designer_plugin.h | 244 +++ qwt/designer/qwt_designer_plugin.qrc | 15 + qwt/designer/qwtplugin.cpp | 202 +++ qwt/designer/qwtplugin.h | 42 + qwt/doc/Doxyfile | 1521 +++++++++++++++++ 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/qwt.dox | 161 ++ qwt/examples/bode/bode.cpp | 350 ++++ qwt/examples/bode/bode.h | 32 + qwt/examples/bode/bode.pro | 23 + qwt/examples/bode/bode_plot.cpp | 181 ++ qwt/examples/bode/bode_plot.h | 25 + qwt/examples/bode/cplx.h | 62 + qwt/examples/bode/pixmaps.h | 95 + qwt/examples/cpuplot/cpupiemarker.cpp | 57 + qwt/examples/cpuplot/cpupiemarker.h | 18 + qwt/examples/cpuplot/cpuplot.cpp | 244 +++ qwt/examples/cpuplot/cpuplot.h | 42 + qwt/examples/cpuplot/cpuplot.pro | 22 + qwt/examples/cpuplot/cpustat.cpp | 235 +++ qwt/examples/cpuplot/cpustat.h | 23 + qwt/examples/curvdemo1/curvdemo1.cpp | 230 +++ qwt/examples/curvdemo1/curvdemo1.pro | 15 + qwt/examples/curvdemo2/curvdemo2.cpp | 223 +++ qwt/examples/curvdemo2/curvdemo2.h | 26 + qwt/examples/curvdemo2/curvdemo2.pro | 18 + qwt/examples/data_plot/data_plot.cpp | 147 ++ qwt/examples/data_plot/data_plot.h | 32 + qwt/examples/data_plot/data_plot.pro | 20 + qwt/examples/data_plot/main.cpp | 62 + qwt/examples/dials/attitude_indicator.cpp | 149 ++ qwt/examples/dials/attitude_indicator.h | 38 + qwt/examples/dials/cockpit_grid.cpp | 206 +++ qwt/examples/dials/cockpit_grid.h | 28 + qwt/examples/dials/compass_grid.cpp | 225 +++ qwt/examples/dials/compass_grid.h | 11 + qwt/examples/dials/dials.cpp | 30 + qwt/examples/dials/dials.pro | 26 + qwt/examples/dials/speedo_meter.cpp | 52 + qwt/examples/dials/speedo_meter.h | 18 + qwt/examples/event_filter/README | 27 + qwt/examples/event_filter/canvaspicker.cpp | 329 ++++ qwt/examples/event_filter/canvaspicker.h | 33 + qwt/examples/event_filter/colorbar.cpp | 125 ++ qwt/examples/event_filter/colorbar.h | 34 + qwt/examples/event_filter/event_filter.cpp | 62 + qwt/examples/event_filter/event_filter.pro | 25 + qwt/examples/event_filter/plot.cpp | 196 +++ qwt/examples/event_filter/plot.h | 25 + qwt/examples/event_filter/scalepicker.cpp | 114 ++ qwt/examples/event_filter/scalepicker.h | 20 + qwt/examples/examples.pri | 61 + qwt/examples/examples.pro | 49 + qwt/examples/histogram/histogram.pro | 19 + qwt/examples/histogram/histogram_item.cpp | 283 +++ qwt/examples/histogram/histogram_item.h | 64 + qwt/examples/histogram/main.cpp | 60 + qwt/examples/radio/ampfrm.cpp | 182 ++ qwt/examples/radio/ampfrm.h | 29 + qwt/examples/radio/radio.cpp | 40 + qwt/examples/radio/radio.h | 9 + qwt/examples/radio/radio.pro | 22 + qwt/examples/radio/tunerfrm.cpp | 96 ++ qwt/examples/radio/tunerfrm.h | 30 + qwt/examples/realtime_plot/README | 25 + qwt/examples/realtime_plot/clear.xpm | 51 + .../realtime_plot/incrementalplot.cpp | 122 ++ qwt/examples/realtime_plot/incrementalplot.h | 46 + qwt/examples/realtime_plot/mainwindow.cpp | 230 +++ qwt/examples/realtime_plot/mainwindow.h | 44 + qwt/examples/realtime_plot/randomplot.cpp | 124 ++ qwt/examples/realtime_plot/randomplot.h | 35 + qwt/examples/realtime_plot/realtime.cpp | 15 + qwt/examples/realtime_plot/realtime_plot.pro | 28 + qwt/examples/realtime_plot/scrollbar.cpp | 184 ++ qwt/examples/realtime_plot/scrollbar.h | 53 + qwt/examples/realtime_plot/scrollzoomer.cpp | 518 ++++++ qwt/examples/realtime_plot/scrollzoomer.h | 77 + qwt/examples/realtime_plot/start.xpm | 266 +++ qwt/examples/simple_plot/simple.cpp | 125 ++ qwt/examples/simple_plot/simple_plot.pro | 16 + qwt/examples/sliders/sliders.cpp | 201 +++ qwt/examples/sliders/sliders.h | 27 + qwt/examples/sliders/sliders.pro | 19 + qwt/examples/spectrogram/main.cpp | 91 + qwt/examples/spectrogram/plot.cpp | 166 ++ qwt/examples/spectrogram/plot.h | 18 + qwt/examples/spectrogram/spectrogram.pro | 19 + qwt/examples/svgmap/main.cpp | 48 + qwt/examples/svgmap/plot.cpp | 83 + qwt/examples/svgmap/plot.h | 21 + qwt/examples/svgmap/svgmap.pro | 20 + qwt/examples/sysinfo/sysinfo.cpp | 112 ++ qwt/examples/sysinfo/sysinfo.pro | 15 + qwt/qwt.prf | 34 + qwt/qwt.pro | 24 + qwt/qwtconfig.pri | 124 ++ qwt/src/qwt.h | 22 + qwt/src/qwt_abstract_scale.cpp | 311 ++++ qwt/src/qwt_abstract_scale.h | 70 + qwt/src/qwt_abstract_scale_draw.cpp | 406 +++++ qwt/src/qwt_abstract_scale_draw.h | 145 ++ qwt/src/qwt_abstract_slider.cpp | 588 +++++++ qwt/src/qwt_abstract_slider.h | 193 +++ qwt/src/qwt_analog_clock.cpp | 241 +++ qwt/src/qwt_analog_clock.h | 94 + qwt/src/qwt_array.h | 29 + qwt/src/qwt_arrow_button.cpp | 367 ++++ qwt/src/qwt_arrow_button.h | 54 + qwt/src/qwt_clipper.cpp | 522 ++++++ qwt/src/qwt_clipper.h | 37 + qwt/src/qwt_color_map.cpp | 515 ++++++ qwt/src/qwt_color_map.h | 221 +++ qwt/src/qwt_compass.cpp | 318 ++++ qwt/src/qwt_compass.h | 85 + qwt/src/qwt_compass_rose.cpp | 281 +++ qwt/src/qwt_compass_rose.h | 90 + qwt/src/qwt_counter.cpp | 656 +++++++ qwt/src/qwt_counter.h | 157 ++ qwt/src/qwt_curve_fitter.cpp | 240 +++ qwt/src/qwt_curve_fitter.h | 116 ++ qwt/src/qwt_data.cpp | 384 +++++ qwt/src/qwt_data.h | 166 ++ qwt/src/qwt_dial.cpp | 1290 ++++++++++++++ qwt/src/qwt_dial.h | 228 +++ qwt/src/qwt_dial_needle.cpp | 612 +++++++ qwt/src/qwt_dial_needle.h | 198 +++ qwt/src/qwt_double_interval.cpp | 320 ++++ qwt/src/qwt_double_interval.h | 284 +++ qwt/src/qwt_double_range.cpp | 392 +++++ qwt/src/qwt_double_range.h | 88 + qwt/src/qwt_double_rect.cpp | 605 +++++++ qwt/src/qwt_double_rect.h | 501 ++++++ qwt/src/qwt_dyngrid_layout.cpp | 705 ++++++++ qwt/src/qwt_dyngrid_layout.h | 107 ++ qwt/src/qwt_event_pattern.cpp | 288 ++++ qwt/src/qwt_event_pattern.h | 221 +++ qwt/src/qwt_global.h | 51 + qwt/src/qwt_interval_data.cpp | 90 + qwt/src/qwt_interval_data.h | 90 + qwt/src/qwt_knob.cpp | 548 ++++++ qwt/src/qwt_knob.h | 100 ++ qwt/src/qwt_layout_metrics.cpp | 339 ++++ qwt/src/qwt_layout_metrics.h | 169 ++ qwt/src/qwt_legend.cpp | 668 ++++++++ qwt/src/qwt_legend.h | 142 ++ qwt/src/qwt_legend_item.cpp | 590 +++++++ qwt/src/qwt_legend_item.h | 122 ++ qwt/src/qwt_legend_itemmanager.h | 54 + qwt/src/qwt_magnifier.cpp | 482 ++++++ qwt/src/qwt_magnifier.h | 86 + qwt/src/qwt_math.cpp | 47 + qwt/src/qwt_math.h | 192 +++ qwt/src/qwt_paint_buffer.cpp | 201 +++ qwt/src/qwt_paint_buffer.h | 65 + qwt/src/qwt_painter.cpp | 768 +++++++++ qwt/src/qwt_painter.h | 159 ++ qwt/src/qwt_panner.cpp | 577 +++++++ qwt/src/qwt_panner.h | 101 ++ qwt/src/qwt_picker.cpp | 1441 ++++++++++++++++ qwt/src/qwt_picker.h | 376 ++++ qwt/src/qwt_picker_machine.cpp | 371 ++++ qwt/src/qwt_picker_machine.h | 153 ++ qwt/src/qwt_plot.cpp | 861 ++++++++++ qwt/src/qwt_plot.h | 324 ++++ qwt/src/qwt_plot_axis.cpp | 653 +++++++ qwt/src/qwt_plot_canvas.cpp | 422 +++++ qwt/src/qwt_plot_canvas.h | 119 ++ qwt/src/qwt_plot_curve.cpp | 1353 +++++++++++++++ qwt/src/qwt_plot_curve.h | 329 ++++ qwt/src/qwt_plot_dict.cpp | 190 ++ qwt/src/qwt_plot_dict.h | 65 + qwt/src/qwt_plot_grid.cpp | 358 ++++ qwt/src/qwt_plot_grid.h | 84 + qwt/src/qwt_plot_item.cpp | 564 ++++++ qwt/src/qwt_plot_item.h | 199 +++ qwt/src/qwt_plot_layout.cpp | 1228 +++++++++++++ qwt/src/qwt_plot_layout.h | 108 ++ qwt/src/qwt_plot_magnifier.cpp | 150 ++ qwt/src/qwt_plot_magnifier.h | 55 + qwt/src/qwt_plot_marker.cpp | 506 ++++++ qwt/src/qwt_plot_marker.h | 117 ++ qwt/src/qwt_plot_panner.cpp | 169 ++ qwt/src/qwt_plot_panner.h | 57 + qwt/src/qwt_plot_picker.cpp | 399 +++++ qwt/src/qwt_plot_picker.h | 115 ++ qwt/src/qwt_plot_print.cpp | 530 ++++++ qwt/src/qwt_plot_printfilter.cpp | 590 +++++++ qwt/src/qwt_plot_printfilter.h | 83 + qwt/src/qwt_plot_rasteritem.cpp | 306 ++++ qwt/src/qwt_plot_rasteritem.h | 107 ++ qwt/src/qwt_plot_rescaler.cpp | 619 +++++++ qwt/src/qwt_plot_rescaler.h | 135 ++ qwt/src/qwt_plot_scaleitem.cpp | 482 ++++++ qwt/src/qwt_plot_scaleitem.h | 105 ++ qwt/src/qwt_plot_spectrogram.cpp | 658 +++++++ qwt/src/qwt_plot_spectrogram.h | 109 ++ qwt/src/qwt_plot_svgitem.cpp | 286 ++++ qwt/src/qwt_plot_svgitem.h | 66 + qwt/src/qwt_plot_xml.cpp | 27 + qwt/src/qwt_plot_zoomer.cpp | 668 ++++++++ qwt/src/qwt_plot_zoomer.h | 122 ++ qwt/src/qwt_polygon.h | 35 + qwt/src/qwt_raster_data.cpp | 435 +++++ qwt/src/qwt_raster_data.h | 119 ++ qwt/src/qwt_round_scale_draw.cpp | 325 ++++ qwt/src/qwt_round_scale_draw.h | 69 + qwt/src/qwt_scale_div.cpp | 173 ++ qwt/src/qwt_scale_div.h | 124 ++ qwt/src/qwt_scale_draw.cpp | 949 ++++++++++ qwt/src/qwt_scale_draw.h | 113 ++ qwt/src/qwt_scale_engine.cpp | 892 ++++++++++ qwt/src/qwt_scale_engine.h | 214 +++ qwt/src/qwt_scale_map.cpp | 228 +++ qwt/src/qwt_scale_map.h | 198 +++ qwt/src/qwt_scale_widget.cpp | 928 ++++++++++ qwt/src/qwt_scale_widget.h | 132 ++ qwt/src/qwt_slider.cpp | 897 ++++++++++ qwt/src/qwt_slider.h | 138 ++ qwt/src/qwt_spline.cpp | 421 +++++ qwt/src/qwt_spline.h | 130 ++ qwt/src/qwt_symbol.cpp | 364 ++++ qwt/src/qwt_symbol.h | 88 + qwt/src/qwt_text.cpp | 714 ++++++++ qwt/src/qwt_text.h | 207 +++ qwt/src/qwt_text_engine.cpp | 436 +++++ qwt/src/qwt_text_engine.h | 174 ++ qwt/src/qwt_text_label.cpp | 329 ++++ qwt/src/qwt_text_label.h | 75 + qwt/src/qwt_thermo.cpp | 919 ++++++++++ qwt/src/qwt_thermo.h | 182 ++ qwt/src/qwt_valuelist.h | 57 + qwt/src/qwt_wheel.cpp | 694 ++++++++ qwt/src/qwt_wheel.h | 84 + qwt/src/src.pro | 224 +++ qwt/textengines/mathml/mathml.pro | 49 + qwt/textengines/mathml/qtmmlwidget.cpp.diff | 23 + .../mathml/qwt_mathml_text_engine.cpp | 136 ++ .../mathml/qwt_mathml_text_engine.h | 62 + qwt/textengines/textengines.pri | 75 + qwt/textengines/textengines.pro | 16 + 279 files changed, 57199 insertions(+) create mode 100644 qwt/CHANGES create mode 100644 qwt/COPYING create mode 100644 qwt/INSTALL create mode 100644 qwt/README create mode 100644 qwt/admin/msvc-qmake.bat create mode 100755 qwt/admin/no-qt-keywords.sh 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/designer/qwtplugin.cpp create mode 100644 qwt/designer/qwtplugin.h create mode 100644 qwt/doc/Doxyfile 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/qwt.dox create mode 100644 qwt/examples/bode/bode.cpp create mode 100644 qwt/examples/bode/bode.h create mode 100644 qwt/examples/bode/bode.pro create mode 100644 qwt/examples/bode/bode_plot.cpp create mode 100644 qwt/examples/bode/bode_plot.h create mode 100644 qwt/examples/bode/cplx.h create mode 100644 qwt/examples/bode/pixmaps.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/curvdemo2/curvdemo2.cpp create mode 100644 qwt/examples/curvdemo2/curvdemo2.h create mode 100644 qwt/examples/curvdemo2/curvdemo2.pro create mode 100644 qwt/examples/data_plot/data_plot.cpp create mode 100644 qwt/examples/data_plot/data_plot.h create mode 100644 qwt/examples/data_plot/data_plot.pro create mode 100644 qwt/examples/data_plot/main.cpp 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/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/histogram/histogram.pro create mode 100644 qwt/examples/histogram/histogram_item.cpp create mode 100644 qwt/examples/histogram/histogram_item.h create mode 100644 qwt/examples/histogram/main.cpp create mode 100644 qwt/examples/radio/ampfrm.cpp create mode 100644 qwt/examples/radio/ampfrm.h create mode 100644 qwt/examples/radio/radio.cpp create mode 100644 qwt/examples/radio/radio.h 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/realtime_plot/README create mode 100644 qwt/examples/realtime_plot/clear.xpm create mode 100644 qwt/examples/realtime_plot/incrementalplot.cpp create mode 100644 qwt/examples/realtime_plot/incrementalplot.h create mode 100644 qwt/examples/realtime_plot/mainwindow.cpp create mode 100644 qwt/examples/realtime_plot/mainwindow.h create mode 100644 qwt/examples/realtime_plot/randomplot.cpp create mode 100644 qwt/examples/realtime_plot/randomplot.h create mode 100644 qwt/examples/realtime_plot/realtime.cpp create mode 100644 qwt/examples/realtime_plot/realtime_plot.pro create mode 100644 qwt/examples/realtime_plot/scrollbar.cpp create mode 100644 qwt/examples/realtime_plot/scrollbar.h create mode 100644 qwt/examples/realtime_plot/scrollzoomer.cpp create mode 100644 qwt/examples/realtime_plot/scrollzoomer.h create mode 100644 qwt/examples/realtime_plot/start.xpm create mode 100644 qwt/examples/simple_plot/simple.cpp create mode 100644 qwt/examples/simple_plot/simple_plot.pro create mode 100644 qwt/examples/sliders/sliders.cpp create mode 100644 qwt/examples/sliders/sliders.h create mode 100644 qwt/examples/sliders/sliders.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/svgmap/main.cpp create mode 100644 qwt/examples/svgmap/plot.cpp create mode 100644 qwt/examples/svgmap/plot.h create mode 100644 qwt/examples/svgmap/svgmap.pro create mode 100644 qwt/examples/sysinfo/sysinfo.cpp create mode 100644 qwt/examples/sysinfo/sysinfo.pro create mode 100644 qwt/qwt.prf create mode 100644 qwt/qwt.pro create mode 100644 qwt/qwtconfig.pri create mode 100644 qwt/src/qwt.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_array.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_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_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_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_data.cpp create mode 100644 qwt/src/qwt_data.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_double_interval.cpp create mode 100644 qwt/src/qwt_double_interval.h create mode 100644 qwt/src/qwt_double_range.cpp create mode 100644 qwt/src/qwt_double_range.h create mode 100644 qwt/src/qwt_double_rect.cpp create mode 100644 qwt/src/qwt_double_rect.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_interval_data.cpp create mode 100644 qwt/src/qwt_interval_data.h create mode 100644 qwt/src/qwt_knob.cpp create mode 100644 qwt/src/qwt_knob.h create mode 100644 qwt/src/qwt_layout_metrics.cpp create mode 100644 qwt/src/qwt_layout_metrics.h create mode 100644 qwt/src/qwt_legend.cpp create mode 100644 qwt/src/qwt_legend.h create mode 100644 qwt/src/qwt_legend_item.cpp create mode 100644 qwt/src/qwt_legend_item.h create mode 100644 qwt/src/qwt_legend_itemmanager.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_paint_buffer.cpp create mode 100644 qwt/src/qwt_paint_buffer.h create mode 100644 qwt/src/qwt_painter.cpp create mode 100644 qwt/src/qwt_painter.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_plot.cpp create mode 100644 qwt/src/qwt_plot.h create mode 100644 qwt/src/qwt_plot_axis.cpp 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_grid.cpp create mode 100644 qwt/src/qwt_plot_grid.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_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_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_print.cpp create mode 100644 qwt/src/qwt_plot_printfilter.cpp create mode 100644 qwt/src/qwt_plot_printfilter.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_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_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_xml.cpp create mode 100644 qwt/src/qwt_plot_zoomer.cpp create mode 100644 qwt/src/qwt_plot_zoomer.h create mode 100644 qwt/src/qwt_polygon.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_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_widget.cpp create mode 100644 qwt/src/qwt_scale_widget.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_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_valuelist.h create mode 100644 qwt/src/qwt_wheel.cpp create mode 100644 qwt/src/qwt_wheel.h create mode 100644 qwt/src/src.pro create mode 100644 qwt/textengines/mathml/mathml.pro create mode 100644 qwt/textengines/mathml/qtmmlwidget.cpp.diff 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/textengines.pri create mode 100644 qwt/textengines/textengines.pro diff --git a/qwt/CHANGES b/qwt/CHANGES new file mode 100644 index 000000000..adc90fcb0 --- /dev/null +++ b/qwt/CHANGES @@ -0,0 +1,409 @@ +Release 5.2.1 +=================== + +Bug Fixes +--------- +1) QwtScaleDraw + Wrong border dist hints for unregular scale divisions fixed + +Release 5.2.0 +=================== + +Changes +------- +1) Ported to Qt 4.5.x +2) Scaling of non cosmetic pens (for printing to devices in high resolution) +3) Clipping of polygons for SVG rendering +4) QwtRect removed + use QwtClipper instead +5) QwtPlotRescaler + Introduced +6) QwtDoubleInterval + BorderMode introduced +7) QwtPlotCurve + Performance of incremental curve painting ( = draw(from, to) ) improved. +8) QwtLegendItem + setIdentfierMode renamed to setIdentifierMode +9) QwtPlotCanvas::replot() introduced + code from QwtPlot::replot shifted +10)QwtPlot + drawCanvas(), updateAxes() changed from protected to public +11)QwtScaleEngine + loMargin/hiMargin renamed to lowerMargin/upperMargin +12)QwtScaleDiv + lBound/hBound renamed to lowerBound/upperBound +13)QwtSpline + cofficientA/B/C introduced +14)QwtDial + counter clockwise scales introduced +15)QwtPlotMarker + Vertical text labels +16)doc/qwt-5.2.0.qch added foe browsing the Qwt docs in the Qt assistant + +Bug Fixes +--------- +1) QwtLinearScaleEngine + Rounding problems fixed +2) Again some print layout problems fixed +3) QwtPlotScaleItem: 1 pixel offset fixed +4) QwtPlotSpectrogram, clipping of contour lines + against the bounding rect +5) QwtPlotZoomer::setZoomStack for stacks with unlimited depth +6) Printing of rotated tick labels + + +Release 5.1.1 +=================== + +Bug Fixes +--------- +1) Several compiler incompatibilities fixed +2) DBL_EPSILON removed + Using DBL_EPSILON in the calculations of the dials/sliders and the + scale engines leads to problems with the inaccuracy of floating points. + The behaviour has been reverted to 5.0.x. +3) QwtSlider/QwtKnob + setScaleDraw() fixed. +4) QwtRect + Pointless private declaration removed + +Release 5.1.0 +=================== + +Changes +------- +1) QwtSymbol::copy introduced + Now it is possible to use derived symbol classes for curves +2) QwtPlotScaleItem introduced + A new type of plot item for displaying axes on the canvas +3) QwtClipper added + A collection of clipping algos +4) Using DBL_EPSILON + This change allows smaller intervals for sliders/dials +5) QwtPanner + setOrientation() added. +6) QwtPlot + axisStepSize() added + clear is virtual now +7) QwtPlotPrintFilter + PrintCanvasBackground splitted into PrintBackground, PrintFrameWithScales +8) QwtPlotZoomer + setZoomStack() added +9) Changes for the QwtPolar package + QwtLegendItemManager introduced + QwtMagnifier introduced +10)Suffix rules added in qwtconfig.pri for different targets for + debug/release builds. + +Bug Fixes +--------- +1. QwtAbstractScaleDraw::setAbstractScaleDraw + Reinitialization problem fixed +2. QwtLegendItem + key event handlers fixed +3. QwtPicker + solaris-cc compiler problem fixed +4. Inaccurate mapping of scale to widget coordinates fixed +5. QwtPlotCurve::draw + Updates for Qt 4.3 added +6. QwtPlotLayout + AlignToCanvas layout calculation fixed +7. QwtPlot::print + Workaround for a QPen initialization problem, + when printing to Pdf, added +8. QwtText + Layout of rich text documents fixed +9. Designer + Handling of QwtScaleWidget fixed +10. realtime example + Qt::WA_PaintOutsidePaintEvent added, ScrollZoomer fixed +11. Several others I have forgotten + +Release 5.0.2 +=================== + +Bug Fixes +--------- +1. QwtPlotCurve::Xfy curve type fixed +2. Memory leak in QwtLegend fixed +3. Vertical alignment of rich texts fixed +4. Workaround for a Qt4 bug added, that produces horrible performance + when painting curves with a pen width > 1. +5. Background for the tracker text of QwtPickers fixed. + Improved (faster + better rendered texts) implementation of + painting tracker texts, using capabilities of Qt >= 4.3. +6. QwtArrowButton/QwtCounter: workaround for layout bug ( Qt < 4.3 ) + of the Cleanlook style added. +7. A couple of minor fixes + +Changes +------- +1. QSvgGenerator added to the bode example + +Release 5.0.1 +=================== + +Changes +------- +1. A couple of problems, when building Qwt fixed. +2. Displaying Rich Text with Qt 4.x fixed + +Release 5.0.0 +=================== + +Platforms +--------- +Support of Qt3 and Qt4. Qt2 is not supported any longer. + +Key features +------------ +1. Redesign of plot items. Makes it much easier to develop + individual items. +2. Redesign of the scale classes. All calculations are + collected in scale engines, where the application can + implement it´s own (f.e log2, or date scales). Now it´s + also possible to have individual and completely irregular scales +3. Redesign of the QwtText classes. The MathML renderer of + the Qt4 solutions package is embedded. + work for all expressions/situations. +4. New classes for navigating: QwtPanner, QwtMaginfier +5. Spectrogram/Contour plots and other classes for displaying + raster data added. + +Changes +------- +5.0.0 is by far the release with the most changes in the history of Qwt +- too many to make list. + + +Release 4.2.0/0.4.2 +=================== + +License +-------- +A couple of exceptions to the LGPL with the intention to allow static +linking with commercial applications. See COPYING. + +Key features: +------------- +1. Designer plugin +2. Rich Text support ( f.e. E = m * c2 ) added. +3. QwtDial class family added (QwtDial, QwtCompass, QwtAnalogClock, ...) +4. QwtPicker class family added. Includes QwtPlotZoomer, a complete + implementation of recursive zooming. +5. Device metrics independent printing of QwtPlot. (QPrinter::HighResolution) +6. QwtPlot::setCurveBrush(), QwtCurve::setBrush() added. The area + between curve and baseline will be filled with this brush. +7. Rotation of axis tick labels added. Very useful for axis with long + labels like time scales ... +8. Added a new abstract QwtData class to plot data from almost any type of + container class. +9. QwtDoublePoint, QwtDoubleSize, QwtDoubleRect double counterparts + for QPoint, QSize, QRect. +10. First steps to support Qtopia. All examples can be compiled and started + in the qvfb emulator. + + +Changes: +--------- +1. Rewrite of QwtLegend/QwtLegendItem (no QTable anymore) +2. Each plot item will be painted, even if one of the axis it is + attached to is disabled. (like in all other releases beside 0.4.1) +3. Code for double buffering moved to a new class QwtPaintBuffer. + Double buffering can be enabled/disabled now. +4. QwtPainter, QwtMetricsMap, QwtLayoutMetrics added + Hide paint device metrics dependencies. +5. Layout code rewritten and moved to a new class QwtPlotLayout + New layout options canvasMargin(), alignCanvasToScales() +6. QwtPlot: sizeHint() != minimumSizeHint() +9. Internal plot data are private again. A couple of get methods + added instead. +10. canvas repaints triggered by paint events. Enables event filtering +11. QwtPlot::drawCanvasItems added. In opposite to QwtPlot::drawCanvas + it is used by the printing code too. +12. qwtMax, qwtMin, qwtInt mapped to QMAX, QMIN, qRound from qglobal.h +13. operator= for plot item classes changed. +14. readOnly property added for sliders. +15. valid flag added for QwtDblRange +16. QwtCounter wrap around policy: a counter under- or overflow sets + focus to the smallest up/down button and disables counting. + A space bar keypress release event re-enables counting. +17. QwtPushButton added. A class that adds rich text and alignments + features to QPushButton, like they are used in QLabel +18. Clipped painting code moved from QwtCurve to QwtPainter/QwtRect +19. Canvas cache added to optimize trivial repaints. +20. QwtPlot::drawCurve added for incremental curve data +21. QwtSliderBase, readOnly, isValid added +22. Added filtering of the colors of the title and scales to QwtPrintFilter. +23. Support of QT_NO_CAST_ASII and QT_NO_COMPAT added +24. Batch file added for generating Visual Studio project files +25. QwtPlotCurve, QwtPlotMarker, QwtPlotGrid: more methods public +26. QwtPlot::setLegendPosition added +27. A lot of changes I don't remember, ... sorry. + +Bugfixes: +--------- +1. Autodetection of painter redirection. QPixmap::grabWidget() works + with Qwt Widgets again. +2. QwtSlider: Rounding double->int conversions instead of simple casts. +3. Bad additional line, connected to the first curve point, when zooming + deep, fixed. +4. QwtMarker: Painting of symbols with width != height fixed +5. QwtPlot::plotMouseXXX/canvasMap pixel coordinates synced. + Now both include the canvas frame. +6. Layout fixed for QwtScaleDraws without tick labels +8. Tab focus chains fixed, focus indications added. +9. Support QwtAutoScale::Inverted when autoScale is off also. +10. Keyboard control, focus indications added. +11. Improved QStyle awareness. +12. Printing of plots with disabled axes + +Examples +-------- +1. New example linux/cpustat added. Runs also on non linux boxes + with dummy values. Beside showing a couple of features that + are new with 0.4.1 and 0.4.2, it shows how to extend and customize + a QwtPlots. +2. Added new example event_filter to demonstrate event filtering. + This example shows how to add additional controls to the scales, + how to translate mouse clicks on the scales into signals and + how to move points on the canvas. +3. realtime example shows how to use scrollbars when zooming + +Release 0.4.1 +============ + +Changes: +--------- +1. Platform independent project files. makefiles directory removed. +2. RPM spec file template added. +3. __declspec formalism added for Win32 DLLs. Requires + 'DEFINES += QWT_DLL' in the .pro file. +4. QString used for visible texts. +5. Code for error curves removed. These type of features should be + implemented in derived curve classes. +6. A lot of Qt 1.2 related code removed/replaced. +7. QwtColorFilter, QwtPixFrame removed. QwtPlotPixFrame renamed + to QwtPlotCanvas. +8. qmodules.h aware. Skips QwtLegend in case of !QT_MODULE_TABLE +9. All Widgets including QwtPlot optimized to reduce flicker + during resize/repaint. +10. QwtPlot curves/markers can be disabled/enabled to hide/show individual + curves without removing the curves from the plot. +11. Internal maps removed from QwtCurve. QwtCurve::setMap, QwtCurve::setRect, + QwtCurve::setRange removed. + +Feature additions: +------------------ +1. Printing + QwtPlot::print prints to any type of QPaintDevice now. + Hardcoded printer attributes margin, creator and document title have + been removed and must/can be set by the applications now. + Printing of background and legends added. QwtColorFilter replaced + by QwtPlotPrintFilter. +2. Layout + Many layout fixes and additions. Now all Widgets behave well in + QLayouts and provide sensible sizeHints. QwtPlot::setMargin(int) added. + Fieldwidth added for QwtPlot::setAxisFormat for application that need + range independent width. Title and axis title are Qt:Alignment aware. + Qt::WordBreak or multiline titles are possible. +3. Legend + En/Disabling of single curves in the legend added. + QwtPlot::setAutoLegend added. +4. Extensibility + QwtPlot::insertCurve + QwtPlot::insertMarker added. Now derived + classes of QwtPlotCurve and QwtPlotMarker can be added. Virtual + methods provided by QwtPlotCurve for sub-classing. + QwtScale::setScaleDraw + QwtPlot::setAxisScaleDraw + some virtual + methods for QwtScaleDraw added. Application can implement individual + axis labels now. +5. Sliders + QWheelEvent added. The MouseWheel stepsize is controlled by the + Scroll Page Size. QwtWheel::setWheelWidth added. QwtKnob::setSymbol, + QwtKnob::symbol added. + +Bugfixes: +--------- +1. Workaround for spontanous curves resulting from overruns + when zooming too deep. +2. Wrong QColorGroup::ColorRole for background colors fixed. + Necessary for several non default QStyles. +3. QwtWheel fixed for vertical wheels. Better color support. +4. QwtSlider fixed. +5. Many forgotten others + +Release 0.4.0 +============ + +Bugfixes: +--------- +1. A few occurences of the boolean literal \c false were changed into macro + \c FALSE for cross compiler compatibility. +2. A few local variables in member functions were renamed to suppress + warnings issued by really picky compilers about global/class variables + being hidden. +3. In qwt_legend.h, a fully qualified name was used in a class declaration. + The HPUX compiler chokes on this (and it's ugly), so it was fixed. +4. Macro M_2PI is now only defined is this hasn't already been done by the + system's clib. + +Feature additions: +------------------ +1. Qwt now works with Qt3.0. In order to achieve this, QwtLegend now no + longer derives from QTableView, but from QTable. This seems to have had + quite a few consequences. Kudo's to Uwe Rathmann for uploading this nice + fix to the CVS tree. +2. Getters for a plot's title and title font have been added. + +Release 0.3.0 +============ + +License: +-------- +1. The license has changed from GPL to LGPL. + +Bugfixes: +--------- +1. The makefiles for win32 caused object files to have extension .o instead of + .obj. The 'propagate' file was changed to fix this, using tmake's target + platform flag. +2. There were problems with rint() on win32 platforms. rint() is a BSD call, + not even available on all unices. All calls to rint(x) have been replaced + by floor(x+.5). +3. Some static class data members were initialized with the value of other + static class data members (from Qt). This caused programs depend on the + initialization order of class members. This is now fixed by replacing the + static properties by static signleton factories. +4. When a plot was zoomed and then printed, curves and markers laying outside + the plot's scale were still printed. The print() function now uses clipping. + +Feature additions: +------------------ +1. Multi-line plot titles are now supported: the PostScript document name is + not the plot title, with "\n" characters replaced by "--". Geometry + management has been changed to support multi-line titles. +2. In the mailinglist, there were often feature requests for features that + were in fact implemented, but not available through QwtPlot's API. Many + private members have been made protected or even public, to give users + more control. This is poor design, but Qwt will be refactored anyway. +3. Qwt always displayed floats with 5 digits. This was insufficient for many + applications. QwtPlot, QwtScale, QwtAutoScale got some methods to set the + label format. This is a printf like format for the numbers at the scales, + consisting of 'f' and a precision, or 'g' and the significance. + +Build system: +------------- +1. The 'makefiles' directory was removed from the cvs tree, and is now only + generated for releases. CVS users should have tmake installed, to generate + the makefiles themselves. +2. The 'examples' directory now uses tmake's 'subdirs' template, to iterate + over all subdirectories and build all examples with one command. There was + allready a makefile for this, but now the process is automated by tmake. +3. Under unix, the library now gets a proper version number. Current version + is 0.3.0. + +Documentation: +-------------- +1. All documentation is converted to the Doxygen documentation system. The + release contains two settings files, 'Doxygen' and 'Doxygen.users', + generating a developer's and user's manual, respectively. 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..f65118559 --- /dev/null +++ b/qwt/INSTALL @@ -0,0 +1,176 @@ +Introduction +============ + +Qwt uses qmake to build all its components and examples. +qmake is part of a Qt distribution. + +qmake reads project files, that contain the options and rules how to +build a certain project. A project file ends with the suffix "*.pro". +Files that end with the suffix "*.pri" are included by the project +files and contain definitions, that are common for several project files. + +qwtconfig.pri is read by all project files of the Qwt package. +So the first step is to edit qwtconfig.pri to adjust it to your +needs. + +MathML Extension +================ + +Qwt/Qt4 supports the MathML render engine from the Qt solutions package, +that is only available with a commercial Qt license. + +You need a release of qtmmlwidget >= 2.1. +Copy the files qtmmlwidget.[cpp|h] to textengines/mathml. + +Documentation +========================== + +Qwt includes a class documentation, that is available in various formats: + +- Html files +- PDF document +- Qt Compressed Help (*.qch ) for the Qt assistant. +- Man pages ( UNIX only ) + +A) Unix Qt3/Qt4 +========================== + +qmake +make +make install + +If you have installed a shared library it's path has to be known to +the run-time linker of your operating system. On Linux systems read +"man ldconfig" ( or google for it ). Another option is to use +the LD_LIBRARY_PATH (on some systems LIBPATH is used instead, on MacOSX +it is called DYLD_LIBRARY_PATH) environment variable. + +If you only want to check the Qwt examples without installing something, +you can set the LD_LIBRARY_PATH to the lib directory +of your local build. + +If you didn't enable autobuilding of the examples in qwtconfig.pri +you have to build the examples this way: + +cd examples +qmake +make + + +B) Win32/MSVC Qt3/Qt4 +===================== + +Please read the qmake documentation how to convert +your *.pro files into your development environment. + +F.e MSVC with nmake: +qmake qwt.pro +nmake + +If you didn't enable autobuilding of the examples in qwtconfig.pri +you have to build the examples this way: + +cd examples +qmake examples.pro +nmake + +admin/msvc-qmake.bat helps users of Visual Studio users to +generate makefiles or project files (.dsp for MSVC-6.0 or vcproj for +MSVC.NET) for Qwt. + +To generate makefiles, type: "admin\msvc-qmake" +To generate project files, type: "admin\msvc-qmake vc" + +When you have built a Qwt DLL you need to add the following +define to your compiler flags: QWT_DLL. + +Windows doesn't like mixing of debug and release binaries. Most +of the problems with using the Qwt designer plugin are because +of trying to load a Qwt debug library into a designer release +executable. + + +C) Win32/MinGW Qt4 +================== + +C1) Windows Shell + +Start a Windows Shell, where Qt4 is initialized. ( F.e. with +"Programs->Qt by Trolltech ...->Qt 4.x.x Command Prompt" ). + +qmake qwt.pro +make + +If you didn't enable autobuilding of the examples in qwtconfig.pri +you have to build the examples this way: + +cd examples +qmake examples.pro +make +make install + +C2) MSYS Shell Qt >= 4.3.0 + +Support for the MSYS Shell has been improved in Qt 4.3.0. +Now building Qwt from the MSYS Shell works exactly like in UNIX or in the +Windows Shell - or at least it should: +because of a bug in Qt 4.3.0 you always have to do a "qmake -r". + +C3) MSYS Shell Qt < 4.3.0 + +For Qt < 4.3.0 you have to set the MINGW_IN_SHELL variable. +make will run into errors with the subdirs target, that can be +ignored (make -i). + +export MINGW_IN_SHELL=1; + +qmake +make -i +make -i install + +If you didn't enable autobuilding of the examples in qwtconfig.pri +you have to build the examples this way: + +cd examples +qmake examples.pro +make -i +make -i install + +C1-C3) + +When you have built a Qwt DLL you need to add QWT_DLL to your compiler +flags. If you are using qmake for your own builds this done by adding +the following line to your profile: "DEFINES += QWT_DLL". + +Windows doesn't like mixing of debug and release binaries. Most +of the problems with using the Qwt designer plugin are because +of trying to load a Qwt debug library into a designer release +executable. + +D) MacOSX + +Well, the Mac is only another Unix system. So read the instructions in A). + +In the recent Qt4 releases the default target of qmake is to generate +XCode project files instead of makefiles. So you might need to do the +following: + +qmake -spec macx-g++ +... + +D) Qtopia Core + +I only tested Qwt with Qtopia Core in qvfb (Virtual Framebuffer Devivce) +Emulator on my Linux box. To build Qwt for the emulator was as simple as +for a regular Unix build. + +qmake +make + +E) Qtopia (!= Qtopia Core) + +I once compiled the Qwt library against Qtopia 4.2.0 successfully - but +not more. It should be possible to build and install Qwt, but it's +not done yet. + +Good luck ! diff --git a/qwt/README b/qwt/README new file mode 100644 index 000000000..e45bbb95c --- /dev/null +++ b/qwt/README @@ -0,0 +1,33 @@ + +The Qwt Widget Library +---------------------- + + Qwt is an extension to the Qt GUI library from Troll Tech AS. + 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/admin/msvc-qmake.bat b/qwt/admin/msvc-qmake.bat new file mode 100644 index 000000000..6cc2b7b0a --- /dev/null +++ b/qwt/admin/msvc-qmake.bat @@ -0,0 +1,58 @@ +REM Batch file to make all Makefiles or all Visual Studio project files +REM (*.dsp for MSVC-6.0 or *.vcproj for MSVC-7.0) for Qwt with qmake. +REM +REM BUG: the designer plugin *.dsp file may not work; the Makefile does. +REM +REM To make Makefiles, type: msvc-qmake +REM To make project files type: msvc-qmake vc + +REM For the Qwt library: +cd src +qmake -t %1lib% src.pro +cd .. + +REM For the designer plugin: +cd textengines\mathml +qmake -t %1lib mathml.pro +cd ..\.. + +REM For the designer plugin: +cd designer +qmake -t %1lib designer.pro +cd .. + +REM For the examples: +cd examples +cd bode +qmake -t %1app bode.pro +cd ..\cpuplot +qmake -t %1app cpuplot.pro +cd ..\curvdemo1 +qmake -t %1app curvdemo1.pro +cd ..\curvdemo2 +qmake -t %1app curvdemo2.pro +cd ..\data_plot +qmake -t %1app data_plot.pro +cd ..\dials +qmake -t %1app dials.pro +cd ..\event_filter +qmake -t %1app event_filter.pro +cd ..\histogram +qmake -t %1app histogram.pro +cd ..\radio +qmake -t %1app radio.pro +cd ..\realtime_plot +qmake -t %1app realtime_plot.pro +cd ..\simple_plot +qmake -t %1app simple_plot.pro +cd ..\sliders +qmake -t %1app sliders.pro +cd ..\spectrogram +qmake -t %1app spectrogram.pro +cd ..\svgmap +qmake -t %1app svgmap.pro +cd ..\sysinfo +qmake -t %1app sysinfo.pro +cd ..\.. + +REM EOF diff --git a/qwt/admin/no-qt-keywords.sh b/qwt/admin/no-qt-keywords.sh new file mode 100755 index 000000000..6611bdb0b --- /dev/null +++ b/qwt/admin/no-qt-keywords.sh @@ -0,0 +1,9 @@ +#! /bin/sh + +find src -name "qwt_*.h" | xargs grep -l 'signals:' | xargs sed -i "s/signals:/Q_SIGNALS:/" +find src -name "qwt_*.h" | xargs grep -l 'slots:' | xargs sed -i "s/signals:/Q_SLOTS:/" +find src -name "qwt_*.cpp" | xargs grep -l 'emit ' | xargs sed -i "s/emit /Q_EMIT /" + +echo "CONFIG += no_keywords" >> src/src.pro + + diff --git a/qwt/admin/svn2package.sh b/qwt/admin/svn2package.sh new file mode 100755 index 000000000..e58a7984e --- /dev/null +++ b/qwt/admin/svn2package.sh @@ -0,0 +1,299 @@ +#! /bin/sh +# +# Generates a Qwt package from sourceforge svn +# +# Usage: svn2package.sh [-b|--branch ] [packagename] +# + +########################## +# usage +########################## + +function usage() { + echo "Usage: $0 [-b|--branch ] [-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://qwt.svn.sourceforge.net/svnroot/qwt/$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 TODO + rm admin/svn2package.sh + + PROFILES="qwtconfig.pri" + for PROFILE in $PROFILES + do + sed -i -e 's/= debug /= 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 + + sed -i -e "s/\$\$VERSION-svn/$VERSION/" qwtconfig.pri + sed -i -e "s/\$\${QwtVersion}-svn/\$\${QwtVersion}/" qwt.prf + + cd - > /dev/null +} + +########################## +# createDocs dirname +########################## + +function createDocs { + + ODIR=`pwd` + + cd $1 + if [ $? -ne 0 ] + then + exit $? + fi + + cp Doxyfile Doxyfile.doc + + sed -i '/PROJECT_NUMBER/d' Doxyfile.doc + echo "PROJECT_NUMBER = $VERSION" >> Doxyfile.doc + + if [ $GENERATE_MAN -ne 0 ] + then + sed -i -e '/GENERATE_MAN/d' -e '/PROJECT_NUMBER/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' -e '/PROJECT_NUMBER/d' Doxyfile.doc + echo 'GENERATE_LATEX = YES' >> Doxyfile.doc + echo 'GENERATE_MAN = YES' >> Doxyfile.doc + echo "PROJECT_NUMBER = $VERSION" >> Doxyfile.doc + fi + + if [ $GENERATE_QCH -ne 0 ] + then + sed -i -e '/GENERATE_HTMLHELP/d' Doxyfile.doc + echo "GENERATE_HTMLHELP = YES" >> Doxyfile.doc + fi + + cp ../INSTALL ../COPYING ./ + + doxygen Doxyfile.doc > /dev/null + if [ $? -ne 0 ] + then + exit $? + fi + + if [ $GENERATE_QCH -ne 0 ] + then + doxygen2qthelp --namespace=net.sourceforge.qwt-$VERSION --folder=qwt-$VERSION html/index.hhp qwt-$VERSION.qch + rm html/index.hh* + 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.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 -r doc/man + + # 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` + + for FILE in $BATCHES $HEADERS $SOURCES $PROFILES $PRIFILES + do + posix2dos $FILE + done + + cd - > /dev/null +} + +########################## +# prepare4Unix dirname +########################## + +function prepare4Unix { + + cd $1 + if [ $? -ne 0 ] + then + exit $? + fi + + rm -rf admin + + cd - > /dev/null +} + +########################## +# main +########################## + +QWTDIR= +SVNDIR=trunk +BRANCH=qwt +VERSION= +GENERATE_PDF=0 +GENERATE_QCH=0 +GENERATE_MAN=1 + +while [ $# -gt 0 ] ; do + case "$1" in + -h|--help) + usage; exit 1 ;; + -b|--branch) + shift; SVNDIR=branches; BRANCH=$1; shift;; + -pdf) + GENERATE_PDF=1; shift;; + -qch) + GENERATE_QCH=1; shift;; + *) + QWTDIR=qwt-$1 ; VERSION=$1; shift;; + esac +done + +if [ "$QWTDIR" == "" ] +then + usage + exit 2 +fi + +TMPDIR=/tmp/$QWTDIR-tmp + +echo -n "checkout to $TMPDIR ... " +checkoutQwt $SVNDIR $BRANCH $TMPDIR +cleanQwt $TMPDIR +echo done + +echo -n "generate documentation ... " +createDocs $TMPDIR/doc + +if [ $GENERATE_PDF -ne 0 ] +then + mv $TMPDIR/doc/pdf/qwtdoc.pdf $QWTDIR.pdf + rmdir $TMPDIR/doc/pdf +fi + +echo done + + +DIR=`pwd` +echo -n "create packages in $DIR ... " + +cd /tmp + +rm -rf $QWTDIR +cp -a $TMPDIR $QWTDIR +prepare4Unix $QWTDIR +tar cfz $QWTDIR.tgz $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.tgz $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..2df4894a1 --- /dev/null +++ b/qwt/designer/designer.pro @@ -0,0 +1,135 @@ +# -*- mode: sh -*- ########################### +# 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 = .. + +include ( $${QWT_ROOT}/qwtconfig.pri ) + +contains(CONFIG, QwtDesigner) { + + CONFIG += warn_on + + SUFFIX_STR = + + VVERSION = $$[QT_VERSION] + isEmpty(VVERSION) { + + # Qt 3 + debug { + SUFFIX_STR = $${DEBUG_SUFFIX} + } + else { + SUFFIX_STR = $${RELEASE_SUFFIX} + } + } + else { + + CONFIG(debug, debug|release) { + SUFFIX_STR = $${DEBUG_SUFFIX} + } + else { + SUFFIX_STR = $${RELEASE_SUFFIX} + } + } + + TEMPLATE = lib + MOC_DIR = moc + OBJECTS_DIR = obj$${SUFFIX_STR} + DESTDIR = plugins/designer + INCLUDEPATH += $${QWT_ROOT}/src + DEPENDPATH += $${QWT_ROOT}/src + + LIBNAME = qwt$${SUFFIX_STR} + contains(CONFIG, QwtDll) { + win32 { + DEFINES += QT_DLL QWT_DLL + LIBNAME = $${LIBNAME}$${VER_MAJ} + } + } + + !contains(CONFIG, QwtPlot) { + DEFINES += NO_QWT_PLOT + } + + !contains(CONFIG, QwtWidgets) { + DEFINES += NO_QWT_WIDGETS + } + + unix:LIBS += -L$${QWT_ROOT}/lib -l$${LIBNAME} + win32-msvc:LIBS += $${QWT_ROOT}/lib/$${LIBNAME}.lib + win32-msvc.net:LIBS += $${QWT_ROOT}/lib/$${LIBNAME}.lib + win32-msvc2002:LIBS += $${QWT_ROOT}/lib/$${LIBNAME}.lib + win32-msvc2003:LIBS += $${QWT_ROOT}/lib/$${LIBNAME}.lib + win32-msvc2005:LIBS += $${QWT_ROOT}/lib/$${LIBNAME}.lib + win32-msvc2008:LIBS += $${QWT_ROOT}/lib/$${LIBNAME}.lib + win32-g++:LIBS += -L$${QWT_ROOT}/lib -l$${LIBNAME} + + # isEmpty(QT_VERSION) does not work with Qt-4.1.0/MinGW + + VVERSION = $$[QT_VERSION] + isEmpty(VVERSION) { + # Qt 3 + TARGET = qwtplugin$${SUFFIX_STR} + CONFIG += qt plugin + + UI_DIR = ui + + HEADERS += qwtplugin.h + SOURCES += qwtplugin.cpp + + target.path = $(QTDIR)/plugins/designer + INSTALLS += target + + IMAGES += \ + 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 + + } else { + + # Qt 4 + + TARGET = qwt_designer_plugin$${SUFFIX_STR} + CONFIG += qt designer plugin + + RCC_DIR = resources + + HEADERS += \ + qwt_designer_plugin.h + + SOURCES += \ + qwt_designer_plugin.cpp + + contains(CONFIG, QwtPlot) { + + HEADERS += \ + qwt_designer_plotdialog.h + + SOURCES += \ + qwt_designer_plotdialog.cpp + } + + RESOURCES += \ + qwt_designer_plugin.qrc + + target.path = $$[QT_INSTALL_PLUGINS]/designer + 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..65793a794 --- /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..0de9260a9 --- /dev/null +++ b/qwt/designer/qwt_designer_plotdialog.h @@ -0,0 +1,39 @@ +/* -*- 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 + +#if QT_VERSION < 0x040000 +#ifdef __GNUC__ +#error This code is Qt4 only +#endif +#endif + +#include + +namespace QwtDesignerPlugin +{ + +class PlotDialog: public QDialog +{ + Q_OBJECT + +public: + PlotDialog(const QString &properties, QWidget *parent = NULL); + +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..ea9a2e944 --- /dev/null +++ b/qwt/designer/qwt_designer_plugin.cpp @@ -0,0 +1,516 @@ +/* -*- 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_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_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); +} + +#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) +{ + return new QwtCompass(parent); +} + +#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) +{ + return new QwtDial(parent); +} + +#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" + " 100\n" + " 100\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"; +} + +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" + " 200\n" + " 60\n" + " \n" + " \n" + "\n"; +} + +QWidget *SliderInterface::createWidget(QWidget *parent) +{ + QwtSlider *slider = new QwtSlider(parent); +#if 0 + slider->setScalePosition(QwtSlider::Bottom); + slider->setRange(0.0, 10.0, 1.0, 0); + slider->setValue(3.0); +#endif + return slider; +} + +#endif + +TextLabelInterface::TextLabelInterface(QObject *parent): + CustomWidgetInterface(parent) +{ + d_name = "QwtTextLabel"; + d_include = "qwt_text_label.h"; + +#ifdef __GNUC__ +#warning QwtTextLabel icon is missing +#endif + + 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(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"; +} + +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)); + 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); +} + +Q_EXPORT_PLUGIN2(QwtDesignerPlugin, CustomWidgetCollectionInterface) diff --git a/qwt/designer/qwt_designer_plugin.h b/qwt/designer/qwt_designer_plugin.h new file mode 100644 index 000000000..d631255ad --- /dev/null +++ b/qwt/designer/qwt_designer_plugin.h @@ -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 + *****************************************************************************/ + +#ifndef QWT_DESIGNER_PLUGIN_H +#define QWT_DESIGNER_PLUGIN_H + +#include + +#if QT_VERSION < 0x040000 + +#ifdef __GNUC__ +#error This code is Qt4 only +#endif + +#endif + +#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) + +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); +}; +#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 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/designer/qwtplugin.cpp b/qwt/designer/qwtplugin.cpp new file mode 100644 index 000000000..2ee6f6903 --- /dev/null +++ b/qwt/designer/qwtplugin.cpp @@ -0,0 +1,202 @@ +/* -*- 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 "qwtplugin.h" +#include "qwt_text_label.h" + +#ifndef NO_QWT_PLOT +#include "qwt_plot.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_analog_clock.h" +#include "qwt_compass.h" +#endif + +namespace +{ + struct Entry + { + Entry() {} + Entry( QString _classname, QString _header, QString _pixmap, + QString _tooltip, QString _whatshis): + classname(_classname), + header(_header), + pixmap(_pixmap), + tooltip(_tooltip), + whatshis(_whatshis) + {} + + QString classname; + QString header; + QString pixmap; + QString tooltip; + QString whatshis; + }; + + QValueList vec; + + const Entry *entry(const QString& str) + { + for ( uint i = 0; i < vec.count(); i++ ) + { + if (str == vec[i].classname) + return &vec[i]; + } + return NULL; + } +} + +QwtPlugin::QwtPlugin() +{ +#ifndef NO_QWT_PLOT + vec.append(Entry("QwtPlot", "qwt_plot.h", + "qwtplot.png", "QwtPlot", "whatsthis")); + vec.append(Entry("QwtScaleWidget", "qwt_scale_widget.h", + "qwtscale.png", "QwtScaleWidget", "whatsthis")); +#endif + +#ifndef NO_QWT_WIDGETS + vec.append(Entry("QwtAnalogClock", "qwt_analog_clock.h", + "qwtanalogclock.png", "QwtAnalogClock", "whatsthis")); + vec.append(Entry("QwtCompass", "qwt_compass.h", + "qwtcompass.png", "QwtCompass", "whatsthis")); + vec.append(Entry("QwtCounter", "qwt_counter.h", + "qwtcounter.png", "QwtCounter", "whatsthis")); + vec.append(Entry("QwtDial", "qwt_dial.h", + "qwtdial.png", "QwtDial", "whatsthis")); + vec.append(Entry("QwtKnob", "qwt_knob.h", + "qwtknob.png", "QwtKnob", "whatsthis")); + vec.append(Entry("QwtSlider", "qwt_slider.h", + "qwtslider.png", "QwtSlider", "whatsthis")); + vec.append(Entry("QwtThermo", "qwt_thermo.h", + "qwtthermo.png", "QwtThermo", "whatsthis")); + vec.append(Entry("QwtWheel", "qwt_wheel.h", + "qwtwheel.png", "QwtWheel", "whatsthis")); +#endif + + vec.append(Entry("QwtTextLabel", "qwt_text_label.h", + "qwtwidget.png", "QwtTextLabel", "whatsthis")); + +} + +QWidget* QwtPlugin::create(const QString &key, + QWidget* parent, const char* name) +{ + QWidget *w = NULL; + +#ifndef NO_QWT_PLOT + if ( key == "QwtPlot" ) + w = new QwtPlot( parent ); + else if ( key == "QwtScaleWidget" ) + w = new QwtScaleWidget( QwtScaleDraw::LeftScale, parent); +#endif + +#ifndef NO_QWT_WIDGETS + if ( key == "QwtAnalogClock" ) + w = new QwtAnalogClock( parent); + else if ( key == "QwtCounter" ) + w = new QwtCounter( parent); + else if ( key == "QwtCompass" ) + w = new QwtCompass( parent); + else if ( key == "QwtDial" ) + w = new QwtDial( parent); + else if ( key == "QwtWheel" ) + w = new QwtWheel( parent); + else if ( key == "QwtThermo" ) + w = new QwtThermo( parent); + else if ( key == "QwtKnob" ) + w = new QwtKnob( parent); + else if ( key == "QwtSlider" ) + w = new QwtSlider( parent); +#endif + + if ( key == "QwtTextLabel" ) + w = new QwtTextLabel( parent); + + if ( w ) + w->setName(name); + + return w; +} + +QStringList QwtPlugin::keys() const +{ + QStringList list; + + for (unsigned i = 0; i < vec.count(); i++) + list += vec[i].classname; + + return list; +} + +QString QwtPlugin::group( const QString& feature ) const +{ + if (entry(feature) != NULL ) + return QString("Qwt"); + return QString::null; +} + +QIconSet QwtPlugin::iconSet( const QString& pmap) const +{ + QString pixmapKey("qwtwidget.png"); + if (entry(pmap) != NULL ) + pixmapKey = entry(pmap)->pixmap; + + const QMimeSource *ms = + QMimeSourceFactory::defaultFactory()->data(pixmapKey); + + QPixmap pixmap; + QImageDrag::decode(ms, pixmap); + + return QIconSet(pixmap); +} + +QString QwtPlugin::includeFile( const QString& feature ) const +{ + if (entry(feature) != NULL) + return entry(feature)->header; + return QString::null; +} + +QString QwtPlugin::toolTip( const QString& feature ) const +{ + if (entry(feature) != NULL ) + return entry(feature)->tooltip; + return QString::null; +} + +QString QwtPlugin::whatsThis( const QString& feature ) const +{ + if (entry(feature) != NULL) + return entry(feature)->whatshis; + return QString::null; +} + +bool QwtPlugin::isContainer( const QString& ) const +{ + return false; +} + +Q_EXPORT_PLUGIN( QwtPlugin ) diff --git a/qwt/designer/qwtplugin.h b/qwt/designer/qwtplugin.h new file mode 100644 index 000000000..3b78520a1 --- /dev/null +++ b/qwt/designer/qwtplugin.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_PLUGIN_H +#define QWT_PLUGIN_H + +#include + +#if QT_VERSION >= 0x040000 + +#ifdef __GNUC__ +#error This code is Qt3 only +#endif + +This code is Qt3 only + +#endif + +#include + +class QT_WIDGET_PLUGIN_EXPORT QwtPlugin: public QWidgetPlugin +{ +public: + QwtPlugin(); + + QStringList keys() const; + QWidget* create( const QString &classname, QWidget* parent = 0, const char* name = 0 ); + QString group( const QString& ) const; + QIconSet iconSet( const QString& ) const; + QString includeFile( const QString& ) const; + QString toolTip( const QString& ) const; + QString whatsThis( const QString& ) const; + bool isContainer( const QString& ) const; +}; + +#endif diff --git a/qwt/doc/Doxyfile b/qwt/doc/Doxyfile new file mode 100644 index 000000000..35edacf1b --- /dev/null +++ b/qwt/doc/Doxyfile @@ -0,0 +1,1521 @@ +# Doxyfile 1.5.8 + +# 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 a sequence of words surrounded +# by quotes) that should identify the project. + +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 = 5.2 + +# 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, 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-Cyrilic, Slovak, Slovene, +# Spanish, Swedish, and Ukrainian. + +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 = NO + +# 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 = YES + +# 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 is your file systems +# 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 = 2 + +# 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 = + +# 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, C#, 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 + +EXTENSION_MAPPING = + +# 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 make 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 to 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 = 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 penality. +# 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 rougly 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 + +#--------------------------------------------------------------------------- +# 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_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 namespace 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 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_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 + +# 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 define 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 defines 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 + +# If the sources in your project are distributed over multiple directories +# then setting the SHOW_DIRECTORIES tag to YES will show the directory hierarchy +# in the documentation. The default is NO. + +SHOW_DIRECTORIES = 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 = + +#--------------------------------------------------------------------------- +# 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 + +# This WARN_NO_PARAMDOC option can be abled 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 = NO + +# 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++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx +# *.hpp *.h++ *.idl *.odl *.cs *.php *.php3 *.inc *.m *.mm *.py *.f90 + +FILE_PATTERNS = *.cpp \ + *.h \ + *.dox + +# 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 +# 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. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used select whether or not files or +# directories that are symbolic links (a Unix filesystem 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 = + +# 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, INPUT_FILTER +# is applied to all files. + +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 + +#--------------------------------------------------------------------------- +# 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 = NO + +# 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 = 4 + +# 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. + +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 +# stylesheet in the HTML output directory as well, or it will be erased! + +HTML_STYLESHEET = + +# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, +# files or namespaces will be aligned in HTML using tables. If set to +# NO a bullet list will be used. + +HTML_ALIGN_MEMBERS = YES + +# 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. For this to work a browser that supports +# JavaScript and DHTML is required (for instance Mozilla 1.0+, Firefox +# Netscape 6.0+, Internet explorer 5.0+, Konqueror, or Safari). + +HTML_DYNAMIC_SECTIONS = NO + +# 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 + +# 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 = + +# 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 = + +# 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 = doc + +# 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 = + +# The DISABLE_INDEX tag can be used to turn on/off the condensed index at +# top of each HTML page. The value NO (the default) enables the index and +# the value YES disables it. + +DISABLE_INDEX = NO + +# This tag can be used to set the number of enum values (range [1..20]) +# that doxygen will group on one line in the generated HTML documentation. + +ENUM_VALUES_PER_LINE = 1 + +# 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 FRAME, 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 (for instance Mozilla 1.0+, +# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are +# probably better off using the HTML help feature. Other possible values +# for this tag are: HIERARCHIES, which will generate the Groups, Directories, +# and Class Hierarchy pages using a tree view instead of an ordered list; +# ALL, which combines the behavior of FRAME and HIERARCHIES; and NONE, which +# disables this behavior completely. For backwards compatibility with previous +# releases of Doxygen, the values YES and NO are equivalent to FRAME and NONE +# respectively. + +GENERATE_TREEVIEW = NO + +# 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 + +# 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 + +#--------------------------------------------------------------------------- +# 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. + +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, a4wide, 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 = + +# 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 + +#--------------------------------------------------------------------------- +# 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 stylesheet 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 +# in the INCLUDE_PATH (see below) will be search if 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 = $(QTDIR)/include + +# 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 = *.h + +# 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 = QwtArray=QwtArray \ + QwtMatrix=QMatrix \ + Q_ENUMS(x)= \ + Q_EXPORT= \ + Q_OBJECT= \ + Q_PROPERTY(x)= \ + QT_VERSION=0x040000 + +# 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. + +EXPAND_AS_DEFINED = + +# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then +# doxygen's preprocessor will remove all function-like macros that are alone +# on a line, have an all uppercase name, and do not end with a semicolon. Such +# function macros are typically used for boiler-plate code, and 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. +# Optionally an initial location of the external documentation +# can be added for each tagfile. 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. If a location is present for each tag, the installdox tool +# does not have to be run to correct the links. +# 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 = NO + +# 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 is superseded by the HAVE_DOT option below. This is only a +# fallback. 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 + +# By default doxygen will write a font called FreeSans.ttf to the output +# directory and reference it in all dot files that doxygen generates. This +# font does not include all possible unicode characters however, so when you need +# these (or just want a differently looking font) you can specify the font name +# using DOT_FONTNAME. You need 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 output directory to look for the +# FreeSans.ttf font (which doxygen will put there itself). If you specify a +# different font using DOT_FONTNAME you can set the path where dot +# can find it using this tag. + +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 +# 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 set to YES, the inheritance and collaboration graphs will show the +# relations between templates and their instances. + +TEMPLATE_RELATIONS = YES + +# 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 graphical hierarchy of all classes instead of a textual one. + +GRAPHICAL_HIERARCHY = YES + +# If the DIRECTORY_GRAPH, SHOW_DIRECTORIES 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 png, jpg, or gif +# If left blank png will be used. + +DOT_IMAGE_FORMAT = png + +# 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 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 + +#--------------------------------------------------------------------------- +# Options related to the search engine +#--------------------------------------------------------------------------- + +# The SEARCHENGINE tag specifies whether or not a search engine should be +# used. If set to NO the values of all tags below this one will be ignored. + +SEARCHENGINE = NO 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 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 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 zcmXtgWmH>T*X_Zb;_lGmQd|qAxVuw=ySux)yBBwND_R_iySo&3`0~7W-27mKBxmHT zy_d{2=h_KZkP}BiBtQfJ07X(lL&4r;IjgBbe+07&^OA@&xb zif~qGwxIKfKm(c44Xb_o7&aXf_IqJ#^PknX1dP3iHrKaTsoDe@riQXmQnF zNGpda3nqqmh}L3=?>C=_ZIu%>0y@fm!vC&f0^b%I^v@LH05S!sbY8P;@7ZPuAl}gO zJ~>e(#T*Hp+eVV9nmbC2B7S+9M^$yy{UwtIskDFF2>OT2iEo@iJ>e?n*wb?Yv%$tVZE063$HPdF7i+ZLhXNP6 z9X8(9*9(n>7{X{}RDU=#&G29i6RJ&47mR9!%Bdr7@MFK~5 zc5tZhq_24`byq3#l(w|qGc3rV{p9eS#h=GvxPju7Luq=O)1B-?o|qpoyDh+#-Uh)U^eAF@A?_H;p!&pL-IH?NQJ^y~HRG>|O_bT7#Tva5 z97aRT`6xS$w~dElJ@?OfGvN&bNgx!(j+L=g*`uu@~Jgz=xV?!%MU&5h%g8-L=JH6}I$ z2SQ4Mz=3T`6ez*ykXtU{EX<3iDwQa-fM&&tVpm3r$b?dZ0cX`nokM8x?%4d!AFf(n zJ`@6kZAiO%psX~VGK)F}`z7!R+pxi=w#Dyk?N8Us`o(&OamAHkzkcDUsP3ZZj2La& zsh-GKRvboLfmxz)=sMGiRdP$!}1b|azBXB>L=wu*kDpwS(zPoYg>B_ zS(<9rAcl6;jybd=cUFPfkG*p86`1%$V@WL0+Ex;EN(~yU(mjpMrD^B4wnLomhc0_v zE~OW{qF)eOfKjAIIqVW6>?*r8hk3bk$Dh@WFFohhKNSa}xdRa(|6O`+!qEvtISVa1 zAFTd$&R!;EU`j7qE-x-_E*_#ELzPUp{@EED8{75qcIm=R+kxwJVPqND`hXx%9YrX+V9burWo4u$l9hP5?NS@hlsM&|%hTLC^C^{m- zk~+p1K{0iBtn(G2+4b@pke4JU6Ll!d0)U_+#R|*ISroIIwV;7S5C=z75MBhv)Pq*$ z2wZ!$@42Nqsn@@gHpg$mBZ!!#=9JDrD+Cpw-_~G9ywp^oZ*$SGU3)((op_K#$o1{5 z=Y5eGsTB%}3?feAi9_9@rPO?_NP4JYDg^8E%&Mg+JHuAQKJ2h{mYYh7cUV|Re&%eP z(kO)i;CJ}6vZ5uK5V6k;4TyN{#oKN;FKTOPuSPh_0YZB_S8nVC``nY;ai1k&HB4ND zL`we7{QX-&GkSJbIn`o@H#D@fFce_kDp%E#u;A7_LDDJ7B9*U^qLc_?WULb9A^&x4 zwhMMW5;(At=JYILwF?W)%gf7ygTWI^SxS`g68x|K9;KNliZL=KMsO=IXAnTl1wC;} z&+5MO50AU4)q2eB?m|$YzV8%5uz^{{2=aneAFqdO-rln>8|u4G4uoc~tH5`W_G(H< zauL;etReqFV4-Sr5M$R8+>296DMhNx$OGE+-b+Aocsxq3~CNy4oX54 zr2s;f?V{&nF7Q)#M-qi3R2U!vq2hPjAx#yONPQ>xzMC#iq6rUu8_gQ1+N3k*oLzK1 zU|`?RVulFxE}S(rIqT}`g8y=Azu(WEJsydFjE#@qHcWj}j!4YR!@?51dwKI0B8#7% zeW#P0O<~mSa5*olsuCZF{#}IvAskjvde!KqED8j^ z4yn)Oazk5LS-oAq6C(fgxnW9f-SaYXO29R6wvj_XmNqOJu%SsmzKGQG>h9<`C?plo zD7YyRg1`V2{b+}8 zSwFDjmJ&$df^=G!pmGvp8S}6V2WdAuEzHkfd6|2o>{Heg_Y3?{p#N~rLDX4 z=G()E;d1rJ7v>(pyz=rpd7@Ab4h}xo3oA=Ywo@rvgDw{2AB)ba(VsE?3|l_qy64zh zTb~~-DQRn~H#H_C5m6@_o*$c=A8(keEtd?d*yuAXQ3^BzH=X_4alby> zc9JcvtgHy+WE>xtf$Z2;e278%9N|GjJ85bdGAYE^W!Y*z0T6!rc-wZ|1kons8Z?dR z%#Gh(Z%epHy>GQfM~xY2`oS*0M9*42<@>%j7@6yJ-gJEZGXjqd4QGeUI(~v5 zH_H#5!*|uL?$*ua+B@p;I#eQD$ON&&<0B5>Jc$^fpsfv#E0w;wHl1p4cnH32e!SR@ zdFhH!1qJnQyYXLKwN`Noq>i;YpDqaX`raJKiYjuNR-cgt*%2vZP^GnPQfYu=rpN&e zedgRnJqQE{!H0`bXu@;y@RXU)cQn?Jm}%`We_WrPF=&@-9eSYl!fcg|*McEFn!>lc zvXYPU%?-WqNVelNiT!V1@=temW9g>^NniG;Pdz=(R5=1wgYy=g1>&a*X21nA*yvuA z^<6n--+I+$ZgElFB`EAMrR8?h=OvTQ*}0#^t6PYhs4k|cG#+|B(K zF3+Ef5dpHNi2xXru_KUU1ze6z1v|9bP6>gpevKi*YdZ!1T6{bHJ??%x5ULoTR?DM1 zSvSidh!)d#x8zmC;^Xtw*l;R>(-0LBNgXq9VMdCbbN2S}@v%5DxkIb)$UEgF50PY% zFgc<|_%hbwvzfK_opztwh9f{$Uf%j(#$-Uf$K(Ch{I7`lH&Q|&!=bfqE>5)G?zKup zz-X28`Hwv3!omuxZ&_5SS68NpaIA>%_)}~dEF}Nb`~s(wi_7x)y>^+5XI-9oM)7AW z&des!+_C)xDsg@iX)26NHe4w3AT)yL5LH!K0nCUrDh=C9wT3U7U+|Wcl~g(6r`gbD zVulSv^o<0D36c8puEDNx!(|Jru43;9#jdXW)?6Nd3i>s=&yqJa{C1-5zwxs!|9j~f z@{oOUVq|+YVF*cd$OJac%ce<#^?G|%cejr+g^9mDUPOT%QGLW-TtvfzTW5PUt<>6C z%!^n^H8n;2v;#IAejgZeb0Rboq^a{!_I3WWrN}ROGTTf8qmeb9&CS<|v6SEd&D{>{ zJXZ7oLO6!a_y#6o4211LT*{FAhKu3k9gWZX1?6cN16651ccFt`YS)^y6$tEQ>4b4M%9C}h@Sj|7m68p-0*^?U^)|G;C*c3R4YtBT_n3kkk z2zVjr@X$I;sZ^g0KXap=o}9Sni=p$Wr3&6U7n4`!h(4KRZJNJRGR^77v8tVC|zh%}b; z5>$l+pZ~vqs(e{pGlq@$<=wwS6aX%kQA#va-dB+gzW1gmt1aqzo$8vt68$WY{t6WN zGjBSRBWf3RqaB2u_w`t62Z z)!0(DVK4!DTFrz5UCDvz+|bvzh2}cLB#grMWKQ-T9l#PbT|JOT5#F!UtZxjn@0K`1^EEW?ckv3eY}#;uP_}`Q*}c_<$eB29&(b@ zX>ygiEm%NkKtvb`8uDk&qEn7L|Hh=;7lOiM;-C0&5@tRckZ7f5VTIF{#^mN1HMAFuF5LHJU@>yIC}w4d{*Dadcv+GwTFdj;8@(%V94_n%)n zL}y%1=AboB_i&8cHuKtKSzKk(46Ef%%m z`glnMK0UeU<7X=C>+8CDn;J9u`$2X~P2_tN70h)BAn>dIeL6S*ye5)g`3PS~KXy~; zc@emh+4Xx0`yDU4#09V8#nZ`>jQcLtC$lrpFE4RKz22V`r7**Sl%W2zDqM+|yOqJw z7X-osVBy45E<25ED9dRPJp7eJuxR#QUOYwqvv;Dqo2f_7p4&=B=KW7D(Z@o|WU>;I zaCz-Xf*IAod$es(!1rr#p&jE4Ox9sKX}7^c$hDneN$Uwk`yF0j{cs^rscUgZNs`*9 zm|h-o3PXV*oW5X&V;67SR#B07=-#vGDm{dD(tdEmSdWU9gaOE5S%cxF3$Jc6$qGSU z($H>wH4+GlP7*Cv{iDVZkBYMI3$^XDSLD3l`B2Hqg-s9z--_!Gw<4MB27vlg05eCW4Kufgq$rleLIO>yctN$QMk_YtXslU^A z1HXR<=%Y29&`#}kdxvJp9f~RH4Gp(A-7aqrO+;T0DNCuUHZ4OtDl)?d{5_t#8(=)J zWCxREw@*(n{SreBYj#O}g*k^Rot>pKEeET(mvW(|nEC@UZkz zGzsBNbS$%rt7p&0o4THly9Sz_)wu7=c{bp7p3ze^;eBJ$>ZC4D4$-Skmwa7VsKsA> zzDk|?OuXHybP^ea*3W{2gQL`i1o%lMbozeyM=TbM6DI`}EJ2_fAs7(uW}9{*zy{j1 ztTG%Kx2$Swtzd+UaO=Rzr6ROfgIkzi?KJ?!()sNB8{FA_bp9rk5LLn%SC9>Lc4o~P z$N>(?UQ3cicQLRJbSM3wBLttG9sNG_@znWQ+YfRHf>+wq+^~4e@zCVur9%Y0`#Sj} zE%!mMHI9KbwU{ym>!2xZy{X4{*!v;6>+RfARFPe_@^f?95CoO9OX2yl=`Km^$ND9c zd)pbtNo2T)fs0Ec~!?JGPD5{3S4{|7}>vQJZw|rCJmAa zx{5pH<<__9kyY(V&`^Mo42BF^5Q<1`csQzRa{CKF6P(@_ece&Zi;z)Vj6QDDLNCQ1 zUpo7%*qIzl>Dxowp%P;T_?rXeQ6FXmm_n-C#O?4_WWhl9y}CVSebh$GybV7VC-|kD}#eBMm)pwq0eMoWTA% zDi0?V`8~PEiFRWtOuIR($WvJ=c~PDghRljwUb%L)s8BpM!?=hOapx)tsZY_LJwv$H zq}q(#;9TI;p>1`AmnbHBdwBwpRZ!3rZ$M_l6dF_1bN+zV1X7HGmb!EmhERkiBM(ar zqq568vY*`cC<7x&Nq153p$$SfD(Oc&97sl#jIKpN-7&EB>mDEh^CdM z3AW?5nz(py<0mH0E}*eFO1HGI&{CafY@_Pqw&`Cw#4k`>W>LCfnCchX#nbkzqSEku zNhxRi8;^wC|2S8Y(=_6V*bCu=yXf`2m)L*lvFH*zj5 z`o6k}dv}fhw(xoW+fX8>yU%_78>uic(Z8ujhZv9aK~@g7y~DEm<886?=kcfat>#a@ zkD=l*jg3w2QfO&~+WmI$=yKVJMEpnpJR*Crn7WBo7w?364C*cya<28A6aEM!QY>&h+UWxM{uSZ`p1flAzjqcv&vhMEn2; z`~-$EA<}lOwJw`_FP2{l#d9M#-4b6e zJtx=j`yS7#`nlbmhE?d>*%_G~WfTuyv>k$*!MV-Nw-{F|`aWkGp*1ZrRmHPU{4BWe zM@4CU5tYnzQeGw@iV=e=Ta(!xmX?wafvMy0L^*^)IjwGlCF1`UVf$2QcH)mrO zcv%#jOOnPqM?&@EmtWxZ4U;G;}4&&wRPm@>-U>SAn)#GXM71L zW2UeX$%KabOTdO<1?4Qj=W26NH~dp?L#D2)RkkpO^pe}&pr^o$xyQ@c?%&SqYZV&l zH_m2mK0-(dct~ENYuh)q8Zx`xT~X${Jg6n59#k<3Zcd!GT~zgQH5q#a8Si-aa#Ir% zAWkVS2~F7bd}V%NL9LHF?5Od(77`ss&o5?3tey9CO*6*@^(8aOFm&V)8d=mIXhF}< z9yKk^^@XFvL2>En_#g|u&q=?lbvrULGwB7Yy@Y5#LsP~Z_vy?X0|O`}eQdSm#DuA! zq**Y#eWI>slB@Kw0ujOKgQG_&DJ+ZPdoVt-wY9IJFvSSP%VKvtTXf37!4GikhDW&h z_@(XV+ov)oQs*-G(3#HcfuRkTO;p1-*zxZDJCBS^dYyv_+R(ys-0O&#!X%3f5Z>#o z`VFFFQIc3;{n~D@L@;hhWsEv^=dXs*`of~3;Hp!4?ph-In*)@FZS6~ z%qmidD%@)PDQCC0sqc5rUod|`&<8e%D=2xDB}(w6Ay=_n`<@30>%aUO`l+S&I!rm& z_8v*ktr5{sHZs`XpI-0rcOjrC-DtrE+&;KoZf>glJ4WzplIwchB^77>8HMc%OP`^G z4E>ssBJtVSZ+KV0ZLc!Y~8`DfRPa))$fLL8#1Y849F znvJ@nqvfD)+1a$Sx4!;{Fw4o68!G8HwPSniAO)I4=gBT~x}s{-1x)s6FimMqq<)@r z21mkOWsVS8OapO?<0hxLN9w-!X43*MB%jkceEDhJ`+*CbmfI7wsiQEdyJ;nRe;Fz^ za69=GJ(>!iefq`?c680|+MihZoO8|sScr@aXcv=K5g}S!(-?cR*2^GaM^vJ;!Oyp0*ygJL|*r*M& z(bz{Dars>48j`ie#Ruh0_$Te}v(lI~T_#`fYVGfFHh52#O~v)Ru7RJ|NJ7o1RwKWn`TBky-LQ;e5CexgMbe4ai&HkXQ? zOZ(UB-#@Q>JaLL5+a+ah`3e5jBUj7cAzmp%;@@Jmv+EixT6xNSv{fAjBI6Ad2m737 z7Y8=6x(T>-no-9Xx7pNqdcj9SHB~@4hFt{L>>|S8|7e%DmBpWuW#NFID6K_*{kq`G zvgzjgeU-QOU0~p0Xvh#E$r)__*4BJ}{+O+&_L%Lf*s!VKiJK@tuX7*Y}xKz0RsV_L*-7t=^6DF(e&X#?JbYIpkSibi9(xO&P zY-w&9@XInmR>~VAhpzwqJJIDz2M7huf5uHG9a(wnJ$K>a;P}o;TdReJ2=f=d*>E_9 z?jhtLKw=!ooH-nNmenmQuse(7CC9&iSoGkT@s3F2(W?B4T>v`SB{oO{eGO z;*ytWrMGwg=CA}&@Nj6bw>P=Llo}|YmU{)zZF{=%h#-)hEW^2l&o&>2I;(N}GPI6f z!RTX(u1itMJ*Vfqd6&Vn42@>1SoC@pETx)o6J1#{j|kCTEJ-=$JkS;$yQlzkhg7(l z|7McuyNKlZ`H0aX3*$|9c1PJxc}uWp^XUkp3B+b)rtGtZckkRrS8(}|(p=KwLD&!4 zxG{Ea&fDtV4)FpXBhOIf73m;k!N)##X&4n$kodNZs6_Gk12Sq3xa_MvF593WqyEGv z;N9HpDJl*Bi1@smo0&6YnnNqxmlt5P^!mzApEdO}^ZuT*W>L8nMs>RIdwwKuBu?#5 zWz3joQDas}ChEq+c08&e<@wuD`L@ts8=cDp;oP2^SfU*$0iq2BLH#jdg=3T0)Que0 z^^V*`cXxLI3fM0E!XsyARs?-IgbftqQh47ukpBNHfDv-LWC6hsTN>@WN+heN1hLwX z5Lo@UoOg_E(s%tZG#t`_A!T*9ifu6K@$KV@Z`U4NfcSy36|+JqKgc++)8E|mfvM8T z?rvwkX0^Wh#%StXWCkXjmC4WYZDx34{3MyVxooAA)<>&`p)=AmdPT6n{xpHmeFI zDXXTX_=3Sp-z!TClXr)5hY}rph=&c4QCx&>wb>mwzuUruMkuQHD~TZFD;ry3xs*>N zhOD#(edi+A+#(mdS|>}5J{~6Kdn7J33dtCNkaUAPiyPsLs%quE2S0KVp#S1!~4)tt0X} zxSFYJg9VWJX_sI}1WnR%zjs}QJShq|9Z=zli+dsm+8!wd!BPW4`jetKiyqs*<%V;f zwOU9E>x3tpG2$hXlt%4|9Ed15IXM3~?7u)yjQ_%{tly=@S*pR0{8RA_q&Z3H(k=on z3??Sv-Y<15xg|0P9T{CVfQN3qDO%~dh`FnmJuOKpwZy&#jC#0y8#1kP`Q2luaG!qT z`fzh|*O$>&LxL;sK9b%eIiMfS4QvU19B%Mkt||5MXdYs!X%J0ik(*A5A-M-$Q+cox zu&#D1OX)J%Dl(LQL96TXJ~%)MAIx;&cL~F34oKN=ok#)vf*lC}A!VpJk3}yF`s_#m zNI*`T_#sJq_tfd=h>|2cMN9yIC?Ku^2ngT;tJU=c<~&MNR;&BxWM;Z7gV1FE%G5+q zNrNRfY@L}?uoUt<3-2t)5iDHp)h@{|co%Y6mTY;DFhrT}YR0UOU zMOLPN8DsRXwnM!m5`2q&a~$j~EJAjk31lAW<4Tm2ag3uW61 z{C)OiR`a+IfA3_cJO>Ml%h7aTy%nG10cQU6&h+$AvCcSzlbxMLLL-~$H}r6`P*ef8 zdnu2pMrb(gYGbt)%PC=f< z*K+uLSKUQQ7X4y|QQ5~5)ep*ymMXHRrlta{%*DPRQRhAm zxEepx3J z-nAhiKQ%Bo9Q1U5qk*S#3MX3kaa+>I`ezWtB1D@O z`M{b~c6OFkS#_u9mXs@46KK{S_)_ON=5aZ{AMua>dr+{wq+S>#U`J#%rh}U%YEoUy zY$7?2hkwk09HVr!M7Y1k3IOEno!vQ+LEy$e#qsc-8l$<3l%3!KVe4i$`cG4jCd+Re z^ccS?YewVdp9CS>H;l#TXKM9%kbcGCm4^nLOQPe97VS%UH#ax=`y5E?HtKa2#3ksJ zU4@W$1EfBW&I^;gO@$qC4R?3WPj92^uD+|=EDPT}Fw^t1xUU2xF(Rs2--mrm^A*6| ziu2gu;GofyNzASlr>CJIe*5NjwJv2rDRq+=^hy)Vr}M&sv|YmBFn)R6>R(Ao+cjHx$0{P{mZT;ffR3CibMMVj3eA50+$)W&FN94@-aM)l<(U1dC zs_Vdnlt`hXA5}+Oe+1guAnru{Iy@PGb#lP(eqHGE@q1dKj?SQGn}wU(qQ!Dh4IdqF z)_opizCMobMEx(`jzdkCT5RP>QgxWo6tV7fJ3=7Llq{iv8aP`%=)2R}{#q*(p!L2! zvHZ_~{MrgsdZ$?hfg*XJjuD5Mn7HOMob$Z7lK3oX;HfkA*h*D9_tvbNyP?M$}ghJRiGUhbBOaM1eN&eKRu`H-`^+ zA%M_>kAm$QH2}!S2$SXB$!&y*8iKybgJ(33HBA+lh2y}fB1M>3P&JI`Zm_-$@9*_j zwWxwLd`kYQiimc0(d2AUiJWY)fi)lmTS~uY1#ZOFlxuZ?%&Du^!)q# zy+Kl#GUEcroHNg-WP4%W>2>e^Qxx)mG7cqRWhO-Y{x1>l zWP=Jyh$XWGh-7mEk=|jq{x~wOnpKQqGLtKDf1f*N;iCD~xrz!!%ADa#_O~uyhk6ts zg?+jKGS1{DcGi=-yS5lBWi9;>Qg}t(HEhJAH}HT*Wh!H&)1=iTJVZuUpjZ` z(4}jy`b~ugxzBB7Yir08GuSmMg^VmyT;2@z@V@5I@~x;wLS*H)$B_{=$KY9Kst5QAF%)#UugPZ}-44eTW>R#jCv^GbgcSC;w}}8V1b5E4+;y9~ zbgozk~2VVvWZ8mi^xa zW33s2=Ypb&p<5?Mnmx{vn&!e32&0MVX=fsm4UgklxqgYs_u}F(z|I}INf=QGD@cMJ z-^(B9wMU7*Q`kA7N=mRG(eI)1QVWM zo>Lj!&-0=UkNosVFu9@|uhy;2E$8z1xJu%dpJHM0g^yRmXVsw!9nj9aS|UHuXlkaW zC!4g}t&9{&EkLdv(v0o|`Y-tIJROdhZTVyAgjOT`}qNybk#e zdHH%?J$oKBAVlrbc;e9UOilLa)fcWj_o;AcVNU7i=H>=&gker&Z*Qa0C2_|Z#Q?hz zw_CDC6PIvJohS*~=Sl6HBw4==n`TM)T!|$zBqf)*nHd{hy$HsOE@ka6aq9L=a`Id~ z_bcBm_m4j3QUD?pcGe85^U|M$OUp}57BY-lI@T{Rjw36%-3a;+FLyMvOIy2JwZ08W zB~3Q}u_ppp1L_14)-SI1lxrx(6{Jc^a2feJ-6Or!)(^Bd2|SLACOmA50Af76_l*ZR z>_W&GeX$})3FdA*ruyzu8bpeH_n2Kb5wMJ$aVZoD6;tSWk9^bV4^Q7Dw#B{HhT?LW zM^Dw#kKVHEq>Mu7-3Kw0a0jcMQM#B|M{G}^P>(6nvnjyWF_-Yig!O!NAnpQ1CrJ zH)By|+e;!r_NM-%XcOPuqrz1VK12rx5?v>aiocMErkd-S23eWq2qsz2{vc+Zhh=ka_)IS!bn>6W~t;NFY{cKmB3Fok$N>B0%KP z^S=A{`pOE?AfpVO*RMWPA5)Fxmb2&# zSbr=%iNK$0*fXUQCqPPxGAKJW07~oEi99TM!T{3mQ`+Z6V|U7Zy{n! zG>u99UbThD-OfSB5pS2pmukuP!0|sx7+E#CBt^?~wKw zZhL@@xuHF*Rqi}ES?vB8OQn*7J2%r)W`gWB=w!svz9--!)iUw;+*iaY3EUn`H2*F6 zL=&-IPxiB#7P9viIXgSF{5wN&T2B)hJc+F&gKm)+m=*yp#tiJ-v*a2+dO zmy6729by!N+)nKoZ=yO}arjCSTj#|Zvy0Yte{X?+B{E>WIz$xHN?Xik79CdG1ov>6 zU&h3;oHac)^qkO&F51sn2r$v{X3stt2CvHoHo;f{2Y(iRVQo$4 zwiHHq#I?sWSeJ@peV-Qh<+=U!{c+v(qoQAb^_C$8uTg?-msR;>FtVA5&#H3bw}zFZ zRjZE8_fn&QXzZ|&8>6Wl>9B*<97#*)Jm;ph%ox8#S=u`{imQ!7fb^Syg?rF{ zz=>faiZNcfZ6e2IcxW9cvBSpZuAxM=OtkT#E-N`C(a%F1r#VQOb$x~ z_HXaGcU$#9QsS*1z;qu`XfB2q*O5_zb53k3J`oX^F7y3w(J`szyjJTg|9tu?twQJf z?8K0YZ-oMesRWjv#xU5xLJ1O~!IghlRaHZDG*SvKPs0Ly*z|gM=)+OeA=em!WDt%bSp`s$H8F83mEKdFAb!ihp~_+8pNN= zVeLdox#+M>zwPqF%$t#)eEH4fOX{>TtdzO5^i7?ixP?V$c{yD~ISt(^=)*EYErQ** zUjhT!&p{^v)ZTsZqKm@Z#cupWRqYCUdU-zCHtWG6aT2==UCp%?;|NV1#4fwg= zu%Yjzr|Zv-1K+E`S1GYf=NO&-{;S>L#aj4ka$Lcj@N#3UM4l87Vq@O@T_`Lz6UKS5g^=dv|I;@{2@*KUO~7>2&;Nr0SfGwl z1QtSI30m>sZD-kl}A*Lusmr2<-YczBC+Gr&iFE4br z?t0nv@&0k>xyko2{iDa@X}`kKS08h*zqc3O@BDA>dlh)PCiiu36s#1)Et+HbQRUJm zhlJwBkm~hY)t2i}1=SwFN*4Lg?lhA%&*0URE1PQXZ(OYOQxG7)2b-}V=d&E0+Hi%1 zk;pf;JUlm9b}hVBSVY5T+YV*ewEp{14hsR;yc4u%N9 znH5j|4G_y=(+{;;RmpI8HR}<|qY(WrlnBGRrJWsNCOIsrvcJdNUXKd4whtM7PDI0h z>KVrHuffaMV3A@)Nf2Gd&q_H9f8f-9aA1NY5BeN5J~2oG6PeL*kqe%rwE}_0ck6l{ zy7;(lyan(%6{(IkpC;QpFWmXDvy;Vpc5%-iQI9jvM+1JJygw+R#qh*howaZBNg_VFRphbvZcKl|b3N zP!jnhOzH((EoNo+l;tzf_31DlS_#=E(lxi!6d|QJ9$;zIb>UC6# zP}H%{zz2{;v{;D*>i?TD>&AaZ#djldNOa6spaFZAR2fYK+^37pHpdP!tTYN(mkzU* z$jWFi$R&zlV0eTlzj|D8fr;70&t$B6k1ky%oZUTT*2_Y7d0Da_ObM64k zPR3TxhY|W3A~~v*V88#?6CUI24Q+!Trckh?JF@P%&|oe1bc+~e+EILMdeD;E{v=t| zvc(uHB2FSsn$nWj3ck;7Y0yZY<>>3>m}23%uWPAIEOHj~;tTQkjP@S}8NH$X1oU#HEglbNy7}LC z)6RK?;SyxcnFNM-Se3#7RM5ZC;e*MsLO}3p16X7Q6UCRyj!$6v)ZF(^0T^?>oNt}) z4axDIe8L>et$?I%FYV)-^1|%@TS1(lsSFKxytAHoh)JeM!1v{aE=%<5;}(qt;PKsy z@!C);g(6Ku{cW)GCUPni@%|)J8bR?3W86I_kxE=~&j24mO~0~6=TWNiGcLr@>#HzR zeo0qXmv{^jgBnzsh%oqw1$ZTLnL%1MHyWFjR2th3=m|v!(1v!(ke&Xy1+1@Q8X6w&57e7Q16vmU z>!cV>gBn(flcM7aQn;aimhvVv-iEbQX<}rjh0v6JIDjazeT9+!TQI;X>Wf|>9$Mfl zK!Fp937!!zxE-az`N6#(kSDn3#6zxWh;#13$H*WT0VXtQ+4|_hIs zPc=}e<=@}6H=pMn4~rMRIbjaMou97WgQwnJz*BGZTU32XuJ{oDeh0g`Y?y?1=qD8< zK~CKmS72YbgnlCZ#>X+w%em0T!o(ak<`UX{7X#{th<9Yh#Y4p>Vr^t62ab3{ezW6& zokVzha+1);SC(c^(`HrYhvlB=79sK7>hamBWh@&jN}=GPI@f7+Ijt~@$gmCCZ@564 zFG%40+@z_x;!e<>G`G`>1mqDqSwY&!QKPIC!A;#{q|`c;cIUfTSd3E4FQ5pA#h)1o zYofPa&uNrlMdUG82k2aXwXn3b={PsDw9FUf`Mo`|Q(&v#ha1lDc^7x^nK+i-96B>S zedbPtoZ9zx24Rev$(HYAYwOV@|6j!JK)7w!tHUHyHL^n0iX7RNj>dFmhT5B~dNO#J zyGVG>j3<+@wm1I%zHDeHcxEQ&=?RwOiw^&BaH7+npjK8FIkdcLoKp+`&lVyDo~x8K z{0CB{$KpTTk~TxbL%J#s{y39RH`LU4_x!Cfc{wf5FbL53kQ^8EWFc_INI1{N1;^gk zVEjY~C8U5h7`!gj@(>L=+Z_uDDSZ)WJ8t3vGi~MXe`2K2D_HjMyyU_r`3@|a;>qM7+Jx3&Qh0WYm>8!(JwBN zRNBEmNY#Ik<`ff`T-3o-JMt`@YcRY`>csxq8{fa@76>v%3fQTS>E={$)*O}1k}VGB zt~8M?N>U;|V5;W4pXU%oJnRmApE)>l(ULFBK>k**P2$OqO1W6d3{o?uXT!)aNfmK1 zR%~3h3LYH<^Ke--Dw;-i9lfND7b7{bmQpq|ek`KoPW7}NY3tnLe!h2; zWeI>#5HW(L{};4fo17aAy}kqyv9@oBc^XW0#lZm+5^i1`@V(62!DdAzxtDv!L zJ4)p*X5bkaYS4r2YyR2_{`B!OkGPFicrU}m_9!T`R@M^6(DykLj>9K@IVBwvstLzPYCWD>3HGNV~ z9~n7{CSrQA1q!%U#ov_<;X*SpZ4kXON?$J}WBI_JNE_D%GlS>HZ8K~9zIPTiQ^O}% zWS`yMupjQ!^Qr|l977K_E*kHlIcNUL2kzoKo;N%Z5W2>J?Zp3C{PRz%r?V_N89Tya z?$|5ia+R&OW2H|GarQAL%`?>AD2Gafnk%F2z>d9b6&#u$#jyW`T5Cs<7QC8;NTDeF zyGRS>!su{b(#2BU`BvW>eZ_nw$8Wrbq>z4nsWEbR^@1bl!Xyo3wx=w{ zIBS=}2e;a%IL0aj3cK3Vx7((g!fK-tW*u2eECDZT<~zMH{)E;~!pitLVmSGw^mbP> z$@X-O#-5$0txT?MetPH(B^E3m*^|s_l5%?NL`6W_>^~wV1jM5}tMFquV6tm6fr$`F zbOx8a&1obn_B-wQk(nSk30qrNE2bvRD$sP+ZfR?1GzN943{m-q)k#F5N5K@W><8@a ztm&*!YHj|GY{P~CAOGQuE>(pTRfL4t2k#_Uw!d~@7gJh(!vad`VR37UnVGtX0(v3r zR1+N5ms)whT7kLJ|M$B93hIZXgr`{<7EMD{R4P7h(|r6A+RejC2_aB`NRp-gq32OB zsZpoDYNJ?CHDCCLmdWt=Tv^fNCauw5EQqaZOcewMlwK1qk63Y5iL1RoG;3KQbqM8z z^NA;N@cV%5Pq($7G5spj*X@1JgeB$n4ne()*zT17=FVYTkk*;(I$Yd$O>=2fUfSLr ztuIF3LqZPSeU_u-X|1g)!7E`VEv?LS_!YVT@)P&~BMz>&36|xc`4DVv7KkM0@cPTr zbe!}2Pz_ffR}n3%)#zZkP~UR_0<<4!nk1I6KFfVWQ&n$|n=QLjtJnk(68TQ1-SX4c zCWjDznE+X9WccL}z^JyVx^S1z(#47xZ&tKpS?>qQamW}7c0EqnF}A;Ii^g)}s#^Y!rmqUCql=PloCKHP4ncyuLvVL@m*DQfLU4B{!QC~uI|O%k z5AHDi&%MLL`FJ=lr+e=uwW?~VdQ0-bch@O+T(zo5A3Y@eE{V`}zINs#Zah!fOBV@= zw%u4CbF|Lnj+11PDcK-qi~OQ(ucag!`1-Vf@RRM`$V>?W=mMzO5}YvlL3Fa2g{c}) zO#H+K>0xe;&d+}m7s1E)b$S3MUe&dOSk$G}Bm|{XfPc;B)hneE;oCGbQDfG737j9S z4stNB9h6Q)-F*dWX4j$3fR!z7y|{i)W$V!G!E{l~Pnloh<-7}jG09mSZnYpKy`%e; zm4wrBsE&DBIHG6j=$bsoLlk{cfBX|xM!@EpFdy|>E_Dj5KoWJ}R>u2{PvCTyQ+^kF zPXawD5T{`ExQNl6HX1fRf56RfWL^j=<=VbKXP^+#p*N%eSqy%KrWDUl)iHJn@m4NMLm=a(N~} z3u2t_H--#c4`1%`2$ZO$b%w2tE&<_Lc9w16r$?N`o<7|BzBJSQ-t&7d`S&t8gU>C7 z1@nvb5xRa1-`Sb3@fs?h>JY?4eoR?3e7`W&`fR6J;EE6VH;voN7SZ`US;Lpam@E#} zMhfo(NA_^z3kYPCW|iPlM~t}*9XhhM$MO);g>ksE8c4HMiddVbZ@)+u7wml&%{tri z)Ui0YT(LWG;Zh$Z4=kbK;V6_-2tg!2bPFMj=DPlPz_&Q^D-@JgS15h*9O+k$AD}FS zj5noyIQR=O#=7X!p>{%PKEhw5I)h$L|9r(jKL?*7x6yE7B{RA@Bx%o<&W*>*W-s{K zoV9a1u>Np5WVQT3-$QkA@H5^W#Rgf4kVBKQ^_dDibijuhuDaktctD)mcQYuI*2Jnj zbl`BPp`p}39j3T{$ppg|6zal89aG^HV4C8tGPv+2a42Vo^rTN$YFHC^lO1*ntj)Tl zr~N}~X>80Jj6N$a8#N?lLu~dXWH2bysQk)>2)Ox+`-=mp(7k)%&1+~Ww8w4|rk;fS zRwH3-foVy73*U*PF~XqB5ur3ul%?=eIO}T{`)J;!v`?EDU#OnjZmuFsb~LI-Lf*7KZPg9ZeTt2 ztAbwdRtXaaH(<-i>-I~r6|6Lrf8}+6KPT!xNPrAJ5G;!Mv*9t4-MkxI!mH5{0R&Rb zkEf=OhY=-zGmr>eoU5EqY1t3V*Z_aX2h$ZjciYyJO54O~MxU)SzqbQHuidy51iOO< z4$(Mwp8jR+keP4pEvF^6Q+u_%S$FAp+&jLFuMq~ik7E`@tsaiSS<>+jJ;L_r$=J}J z%;qu-#-soA!G`4F%<)LWV%-9@eZ$IwSkvoGI3e2W-x91)S&vxlaMx^o4l<04QlA)3 zSg}1dlLKqV77mwsNR>fbm9PFkw@lY+`H6=pFlj7FO?*@F@%=^lJkR>%;MZicaRF2a z&)7B+ZVdEW8IEm-S?PE(sgIKy-`Jp`$gI-s20lA$#_GzFYl_)rDu;7)<8gg-n<9`n zDvZ#r9|)s@x;#Q4nuG?pvIxPb_U&ZeY(|I)zPFc&Pj+xX(_&!Nwk}o@USLBxL%CJY zxs{wjqi-k7gU=|NQNlGN5`3Q2^g)KBw800g317mA;He8JggF5-Dt&L2Z8Gl1g~tbCBKC;d-#pao1z;eyf6j zpd>a086X_r^64Zam%5C>OPyY0ob1R*OCw4;P&pi46;mm>n8LAE^z#SP4`v^f3z!$J z6C-dAZ2s1xWZZqw6VN1>x0oBx6fI5Db^Wpt zA|013sHFprQFVeAbCC>bs)^8S8iVL)n@|wvU>TQN{!Ba_@t*dw-&r5*d=KZ?f?tZ#*!0h*)mX^;aCo(M6|)q>yaY=I1d zzC9^}IBnV^->z}(76Pznv#rr_b-z#<1A^0CRZ^V4{>p8FD00z&#D$o2aBFSab|oT4=^5-5dcnWzXN4AkOUPu#A*)+H4}l z&x44^Khcr8E%)n!)eJRgm(~W}md|&D^Sx|x60E(EJ?SkFS~Zb|2Cv70x~&{fb5-k; zcRoileuMA>;Q_A?g9^J6Owu$?nYdh_6_741`1Cd9tTR1~weQ=HUE&FO*WC^k^TuI8 zf({NM%FLD#JVl+Mj4MK-FdgNf@ztpfIT@fBLVkW3w00i4Lg>h268gCKA|YP+vPJAm zXM3>F`8KYM9w{S-4&Sxr_uhJUVedPt(=q&Z2WrFDH(uA;k&q0O)t~@@%dkAZdv|+V zo0oVax^~DC)hJ;vE=*!FbgJV9-JY;ugxg(?!Nw>&hOMtV)$|T2Yj6PO*PTbIyEAXw zQ7j?Up_8Hs{fCBJYWG#gLEeo=d3Dp{hs$iRQ)rb|U)TD(c=+f2ZKK`6Xn%*;&&tx0 z<*X_MxuNZDnW~&qh=zdsYSLn2j85?HatwwVJ8?^Dekdqt@w!=5QFzr<`=nG4^;e}R zn7ZI#B8%JY)D7@>y>$kWfpG#luuh6iS!F8|wk=SQ3pW{6 zMh<}{+9thr>*s}G)DM-;-hzf~=hy%V8fkzd%OxanHbV!($VcI3%UPo+Dq(2pZo35v znsbNZG1$@Ff^l_4gX{zPm*g74gtmR4A|G?~a zY9oMQf|6ja>YA!0m9wS@iWC%p*ka{%Uw7M;%plpED|2{cA;b-MI95BAfx|-YKjh?J zLj(vUbSrTZ;^@ZM-T#noPx=YOwwK!}eK3FRwxv?gf3AAJ4W&c4Y>tKp0}6+zBH z-?wXP;NL!{``b45LWM9IT^1WGTnGZr!29I7x449p-a)Q+j!G7})5JG_sZ`NGA!tb@ zOgU-Qd`KEvs!@%}>D#&7mQ!oNB@LN-ZF_z#?diG#Yo+-l0t%T#nx%NzXu8NTCf40u zTv>7XwL`$A6j{x6J>~}kCZ1)GyrusG7PdxSQR|;SUJFYW7q4gbRe`SGSGKQ38M>RF zd_G8NDo*dDt9Udg%3Oi-%eMn46bb_5yzf$*>B>*b)>V3cT#k;4a^{a*+7x)GMA)2> z#gtahH^;`>Cb7}`xe6>BjzJ2&2rQIdl6&6FD~ttweu!HTFGj!IlUQE?CKB1r9g#Yw znWbfM)BWEHohD#&D}G#T6EJv`Ru2j%c>u_(@Z?^H*Ax zKYZed)z@zH?30EKod<*WNw{{A<6+0;!P5=hz$nf(zeoJN0Ef-20kdTBM^Wn+$?Zi_Le3ptc z(^5*@{RjNQgb6rwQLQS1g=)}7qD+u4B5!CISk@*tp(BWXzct8VpdB^$Z)qgN5_3#w z!#`sQ4^=N~Xl*^%?!Rtt50TII*^A~qZ#$`M?R>lKd;;AoPFGhmSI&5xcBA(s3h0g= zLx;N}DcNwlz~-u{IE5Zshp=WYF4%ji2VN%VbGQE`iRWIGCSUzGxso1#=2!~6k6Oy& zz2q!;%;szZvXoPXp^jDG8iH9DBWS~Ozr>riWG&#~6wHE-1`}OueMT$X^9mwQjX8p#nlWq$jph!7T|Z? z_$~)N+!(&;%QAztrv<cYOnUdm9%AxZxUI0(RHmxxakoJoh>^Ln5xR zl_$UaTF9xW;^a{;a)0#ch>ME@b%D51;+hJ_q~06Bp;!o-bUo<_U*>zpWdHpje)&CT zb$;P)P8#=dx%6fKs+eXW^q!5F+v#+>9|3SUo}~qopSPDcif4OvSXdZ7XQhUk+QQtN z)kZuj^C@W@{3kI|^eG9lV9VJq{IjM{sS{&(PpREOswjmlZ70{ET627j(C}lRK}{$k zTACQOD7+|itg^Q|kx#YX5kKv5MCcGs>@HR|V8s2z&0S-u0*i$p?0_ds3J*h@MKEDb{SjLq z$F~5omxw;P4F*3uu0}>@W>h3% zh-8-5!JIjR>%xgnJA4k*N!ScAnAgSY}Le6d_ zRn_c2@7BtfHhrpbPc#b+r4Ep=*;!VwWDE)avX~=WL10{c>%l&+tZXFW*M9Cn=*^`j6cl(# zZgOdCNb304Q+-F6>6j3Smoo7?J6w=faIQ>8)AL`CKSUpH7!#gi5H&fo`1VIp+&D~1 zRz)1@@M(Jl6=*FZ70L)>-7=xiSP`D>`gKc)Ku}kWNrQEp2g2pcH7Do>loQfWa`SlGf8&E1< z`vHGc^9J$qc}cOKPXrO-NO7Sw=*(r=EJO?TXW{16H3QyB;dZkt@IYEC-P%Ke!#(l# z9&ugkJ1W{P?W8h=;#%1(A*?n1(v=JLzz!W~sfI?MwKXW{E*}Q~L%?ULkr{;z@24Bgi-2d)O6GC>{=A~R z{w3!9awX|PK|CsA@Mwhn?~* ziY%Ap5WM;Crxx#5zftI~DxROtpOzQi48)W`Az%6wP3{y9C*Dp$fsD$Knbsz9y!VL1 zB0&^hQkzBzE{W>t&jH@lhG-Hkiunn>s7RHS?#5tSyiJ+=yF!x*PI9y0hdwO1;X-(h-h?J zQ^sgzWowbl1Cm@e`vNSe)na6Yxx)vn72q%pX#1C$*Q`r!f{MnBW7>@h9_esuDceFH z94+Z}Mtmq8WLW>OpsS3HNKh0k(fQDi@v`Li+=LG*ugHTotNi$DulSb*qMGn5-a1ap zKYhM+@D&RIJg;oIg^~uzN3WCz?72~usuotx?tH*WSuSM`dKkt2LBAvg5%M5;Twjkc z8@g0b@1-O~M>VS6?c<{-GrAN7`lyYe!@s*tLyjFe7j1#k?rN#dA^NbC#O=$VVG5vY zDsNEV_pzV%q74B+t;x-;ZD{xeWXfA$QT>4y#e`n6Ls>fe-Jf8H2#8JLgrzoo(3Bcc z3(UZk`E_Jhd?{PY||M2W;N1Hp6}Oe;vLI%ySC z^QYI6Zv04_R4O8ykt~qNj?WAs%M@)U8dls zPDvWI1VDWC^n~GLlE6Ff`m>4Yl0k1%_Y*=FAd#ZLtSyz&P-3%9iqYM|#ICtj%A*!$ zg6?LDK~OQbF;7^LEnyURI@G!TW~e>XR(jKe&_wKAev;DJHk^}{{!#zWE+h~`1wQp* zfhcRjs<*37O^J!HVVv>u?;NSf5{Pq%g#!D;vB_xKH?QV+b0&q1!_LurBt>AI)RnucD4<&3A9x z(Yx>%Y^`T}zH92tIB({LcrWotE^qTWdi2o6MlLfd;6`WeH-?qWDyqRMj!tqH+0ipB z=}FQtAfka}vPdv4lM9X!K}Dd3B3p8R~hB6)at!0@D+{Ui+vc|}b#e-G9!PgJoniOMoFiH@}ddT2moNE~c#%A9o2 zg}^rYJZx_}c07)2fW?_c10z`EtQ@UZ?JLwmH6Rr+A-7$PRFF$ZM-F&Kp;MEo ztEs7}tGl_nURsDUU%p=CxVDP?ytG{+(IdKjdDIf_Xbym)i@;_D$L{H?RQZRLB`3EZ z+69!9YuSDX$16r^lMUdZjgkqVUkdj@$?Es3EL&-vp%t&fsdhf;Rf&fK<&7x;=3)PI zHIAbp0M%2BJIMPi89QrVoC%b?sqW)*|8#v!^_(+^aOhlYG9uLP;ka!D1GuQY{ajF9 zFvVZd&=TYhaVM2Swwai+bExUF80oVPCi{$eCRRT{mv{fN?bn@LHIyrA4s8>UqL!jV z)kTE+LD~VOQz~w+=jWb{NXJTXt8~fv_a^7>KZxcl7>@?TMkiPpC`lm7-;M|o4YlW2 zgufjGBHG`dLf8;S;6JEd-LFK=$Uv~iiwbDH5H(1gbABSzXKW-I(ffPh1pMRW;qh>p z!0|pVF?czYz;62IZA&m!oS7x?WzHrwU+zHZ18F?PHCZfmIHgKDdxs@S;*t$v1;^;L zC(BQ<1Jdm+B6xBl_!g}-qk_He2K6n|e0bn1v_m6(tH92?lvNCX0+&$9d4mu5ikNL}v^XwQAk>58)ab=D_c~Vf3l;DQE21FJBvpc`9}%qii$4@GjXsfm`KW?4 zX2^u~V>LK=e*_0ZEFpRSi`3HdMFY7Qy%Qcg164GO(wIRM6aMbsuKGY};KTTAdlVTi zBRUZ4|3Y~Bj$7{sq5lf#PMI;An*8Xn-MhXv4E1NIsrh!xqqvUD`N3ZRYvuU|u?ye5 z?ynlhm%nRrem8I&8RLeUfAFZ}BVVos?NN$AOg2w|cRi}H2|sE;ZUH+kX7ES<7NZzq z7Em#+sh3vEEgD#;%#!@NM#YtDtg36R} zg+iueE!F?cFtm7D*E5UvC-^XyY`)eSfoEkbZ{Sm(k|AQsbYA~HDt4R*luoup(oDw} ze$d`0PpPD{^8?9<1k+R34cqFy-r~Jq?`TLj4B&ZdyR?bGqzJVokO~VxGeQ5@)2Q?i zdAfSHC?V*_c|KC8;=7$?4F3__DDd=@e$iHUq|x~&>FA%Bq+JYFf=|YEL}>BMhniF# zpM1#+GQf~sEx#e`Lr-ZZ9b}*0?;}>5U3S*rjlIO=)~FHsaK*curUto|-$)7bR2;2uw^Y3S@9An!NJ0sU|sVH^_+O!p(^OW7out0To~9zJ5l zl;s-W`nu;SIq;~cm~sBP`QRaA4o8P0{L2rn?Umo*OkvX$hORdJ%MXS(e`c(RVZSS? z&Zlma3EMSpJ3R3`yS>R8Da-3(r98)(HvAr3>BsVgosQ z$r(?+tN0vaTKmCQ%zZ;iMF+#Gb-}ico|z7(`=@+(j4>H(mx|&zAxo^_7?bDTUsVka z9Z1?lkzQ4OcAb`3KZL{GWhsI(I9JRqXnA?;bSk!thiI+3advi8xVCFN$l``MGDXe| zc43ogk1_)TVn3(Tn#d-XB=6&eZWBU5th6}8SzBAAXADNzD%X(E>3#l51I`BLHIy)=rO@y+VZVyfgUuyHnANvR zZz8NZT8!e0&?$woVy7$ai-WG?(id2~8KL(7yG=h9dkKTD*BrOukrn8XR7A9`Yc z{Kl|f8b=1Go+Zg0HKFIC2-l5iCp>8#<%$oGkoGj>7+-{FN+bJojk7~3I>%tPwJ=@P zu5&hE?hhJ2{r+7EIvm{HZJkm*n4+A42_6wzc!^^1GtC5y4}cX`!i(EYf-_Rf7raKK z`!2SErF$(IVaSvj70|wg^qH}ZDegr^OCZJJJ6($8+HHhbfTgOI2!X4KCZ04RlYyRI z%DHP<)mtPfsiNYH?nO}UBoclm*B*54Ef{jn2=I_GfSug4*J{L0XP ze{H3!@*+mjujjKezE1I3oYbH=A)D_zRv|MV8i@wGv&EAC9^xXXVD-{;Zg}p?+Kcq6 ziu6(x=%fux#YLj`OLClupO-ZDXEv!HBSc?8@v=06fi(S z1shyU_>-ZyeYT!{Dsr%h5Caq09D-i>+VGEQ;bw`4%=z03|xQMZtivl0bC6 zp<(22p%`0w#g#Kh>5p)&(#w0;nbX)PIilg3NG(MA;qtQTq9TyUxZ~37Z zXL$axOrZWrunLd;iudL?W~s*g&n>%pwe~dn=g6n&-X~t~q(f7OE4H*YVwzlM6-`oDJPg${x)S3vz=hJD8gbpD^zT-xS*&h9TZ{iR9eQQuhWYb7MkZ%bF zN?2GRltfCI49(z~{zSM4V>`k)9FZhDE%95enh9sppMEC1TP@CS|L_P5xJ}sEaUaD1 z(Dl`U=Bej4gOvd=FhCfO>U#VZPk{RYsWbmRGHqs021dw23UwVB0ZNmJ5KI3p$oHF{MMm2$9 zY*XLFDSVP4HrfAhmO@|iA4&Du=kxgZPD2B0Xc|iKC_p(A&mcsHH7Uj_K!)BYjK7HW zHC_mlbS6z8)Q}~NXYJ1XtTG}KKeazMVsI`Zd24Rx=fK-rI24=}qkiVZRiaxQ?$h(i z_l}1dg?FCTUd{OPAEX1NrSsvw(@$cefz(t>oG_SC4J*}kNaa|7VqmFEb=6I%d7Whw zF&bP0~9HECZinAO&4B*d|T4W_*ZWSi_7nst*1 z37h7+$zgz;s6h;sZo_0#ReyT6YI-52#2SO3EFwB5_`qL<{LaRPwzhW*`YIo+(-wN3 zpEI&O_iL!KJt#-7m&7=@d|zfz1Rh=^u=TVEL{i4`$o>m~(FMfz2;tkB;Wx$wH8~K#fn6+P%iK&;5 zb&wYLA7)p#78hy~Wd*$53~Vb}8(d{kukfXYYHjKqJ%jq|vNEpCaJMdZ+e&p2flH4} z<#c!al`qh}idr>qqtjhhYs2~Yz$R?rzTp18&oHLtzQ@hf$b!DJb+)9bqVcZB)i7}m z4}1FF<0+ZSW*9~jvENIGxpS}}F#r^=eSCSZYiRfep9WM18$=H7nAhQAx3Vf?l0We| z+2g3H30t#LkGV5AoCwOrdGZ1zf$}L1T#$SW^-ID*JnhoRL{-|&hp|WKNNk1Q2x`4N}Cv$Z#e@j=hjtXUBt3Oz*aSn{t{EVF*+qDA~ zgW>^iQ^(R8#bvWu&6*`o-89P&-(bJFn-{NAB)zgcP+GR;X=_tfs;xbPtAZXDL|2s= z()IyW7*lM^*-{OvPTO53vdm;~FoFgaD@weMjR~-{{L9!$!}2>H@+%$X{e2T2;w1)E zrTxtL+gWr+M`K6FKmrw29ak`iva+;{l;@*1+fKuqT+vL4Hif71v2kQTe66`Wz7chZ~0>X`{dE z2N64iGCEDK^Zwp09!JH~=7E`!XhE-ixyJ83so@ou1vY>LHqZtg$HbC@xfSV*!)1~1 z`7GpK#D|jY(dzc``V%1`xw$ii!Y7F&5VyGWAj2D%6EuTBESKyGB$Z|5{!V_;sO{ct zP_I3HY+9U&m-& zKQh*ZBO;x2yev5;a91w>aey#-v!fjYSt3^XOx=-HM(R5~ExnYdU6kaMKFZpKsW=tn zR~xeG&Bp(JpCv+dN#bv*4Hqu1t5VzZFVKZc>+UEP)g@n0)+U(`OYt9!nEJE4Eu`k5Y%E9_KGG>F5BlkA1`z2m2)OVZ8utVS;18_)*px;LMX$0rISsl5qpMXq-2ftn)a)brO0MN?cCmO z`!$0+7ags8dnqAxf;Dlx>xO(Eo|$D8zTbnLc-L5 zbbR8PXuX-_FYeH}b7w~mmgO0zx6N)^dU^~qv+Kav;o1ot6Lpa0pAQxU`HLQmH@=~` z@r1-G7TAKStJTugEd;=?J4xkNLx_p*DN&t6>f;SyBk#2yr=5ULWcLA@4)*~{R8&j+ zoi9?p>*uJ)xzC5_d7?zJ;8N)Q=DYzgV)~(_Sj%HN*JZ_s<^o@7wqTPJOt9!#&W-Dr z+?)<_bEzp4gsctiny55j?zPVN!(WdEZ+qW&c5?h4wm{Y1!TCx!Ue8X< z1wQfnOH;>fGVg_KnPMGf`6&O|O*a1tUHrFKGSFkiR5(-{IcCtgM7$7gic(=gwdL#% zo>WWWLlNfvnbfe#XR!TIAPfAZaVOZVO=KvEU8W<=#o=far;VN!f)w~H_C2W~UwxO2 zs&MmP)mG02YqF*pl#=JyrhC13%8zgZ|3;GdwUFEPHUfgk`}S(3Sf6xbFsmvnRs9IX zjJgdqlbtc*2X<;+a5ZZJU|4r2PCcgOTwM#EG?g?d(8q`0GL-*VD2xQAen>eWe!o;U z+0p2{jG93aY-{T5Cb@H8nilWY}h`WVT7*JRHL`#b? zuirg?G&ghP(v`;yo2%KD#xOmH9BlT(Z$>PfLuYfbb+zDZbXWg-N?s&=skj)nzpbH; z6f_eL1c3APgnyaH^k!oqj=05;wE9X9%*O2(+HN-1@fEg*v#l*ea76^ z`k}=#SU*|<_Kt`8LuGgRvnpJAacJpXjX{?m3cT^XfeF*;S{P2bTp|L3*DaJPXh`P& zddG_423PE#5O`5uMKd3PfUl=;g3S*jv}sEAr4Lg8yz#Fhi`U3y9$%$+3Lro6Bcxd* zDOQ$&X{3>UN#^Ey{?T=q%~dDaiIG)$o7Jcy%|* zld@W&^LDC@sg!CIB*XB)uED~*G~8kGHz;SGxs&(c^Hmz#tL;aMMD0P6RdPxzV`K;- zs~)Zjh7Y2~lBq0h>4z60@no;gCX#m&2OzsxC=-#9qXk}XsvvIQkZmF>IwH!aHa5<$ zhZO{0Pw4750Dp=>8OJdf*9wI#twF}%UZHeZ4l-dnb(cK4v?b`X(UEXkaxu}rCm8MR zDH-XMioa4%2X;^gg^e9>nI%3}yt9vMOO#_QpX(3cM2eP&3oji)>oz0O@+4}AM{(LW z2KA=he?LHJXkZ@>K?L^q`MAg2$c(uNzBoaRen3UT>HSy8pE-v|n*Xau97f#MR_C>`#HE{>*fk=e zI>1Bex9Ye{jb?c$i(Vu%NzmyD?09CB8hL*_`ukL#1z|J^S?*f8axejJ%PuBbfbvr0 z$-PyoOv>47nODjheWVeIp_r&9{FkDMT*g~}+FWrKwpseo{9>>%(sXqdW9b(&t^t>p zjG9M^f21w|z%rD10I9sV@~d4pWX443xy>{ZpsJzKWt^PdXM8CsI%XaT3fU9kgDl;L zwQSq6eXg|frrPjXuz=UsGtdy|VMlPJGK&~UPaui|MDI?Sxd~2)9lp~mR!;p4O;jGh zj6RiT<1~yE@~%^7w{4J&YiNog;9|-T->Kfaj!%3XIvqEwEt8W=ps0E4STCl_z>&?| zKaLLNkg2P#eh{LTI-N#cC6XF0vHCB8p);@jnbTF(7b?Plj&e9z&E!NM=7$Ty#~}^E zUqp_MBB?(rv#2}MoQ?vQY8ZmKsv>GZ?qNIiaoKgdFSYw?j_3X>rAoMQ7lRWrbl1+H z;NJaUt@QyFpuIla*Zl)udbDB(8=Oq&{+%!$`)TN6=dboju`a(W8J&hKa0&;sM>W=4 zbBV9}0DL7F6;;zcNcm1!$36X*2?qaHn2zyGH6Q)_Y!jUr2I6wOMIbOlS-7+mfvSh) zP$U{vS2ySEoL2U8j{phD^`+>*z{qR))5pDzZa!3MWvEeZmQd`9`78oP4z}465Z}m5 za}4R_zKaNb*g{!gbT?@wHL1z#>|DD@A@Y04sYAx%W!G}acPX#37s=H=c@UE>$@Lc> zjPEy|qL2B8V=`vhRrwk3U_`Pex5s&Rh`W`)oT2%pK26VA4}JQdl-eT(>hC-g9Dh($ zkN=WHX%e(XFu!hFNLe=AxQhn`fo~$i!Vntzo5}l@pqYPk!r8Opav;i0iOuHm)BfhM zuFq1qxXEutMI|TV$Zli+^Pc zH0dU^N)kT;)wkuUqN^(~!~JZZl_^AzPTS^YoJERc#?zSUqGc9izckI@ zeihYJ2p%yE{QyZGK$0;BkD)T{Hfan!j6;DgOc9S)mzUc$hkyD+PtUFtg@+5JRVX5& z7hc=1g{PG)M+sFRQ^d^Ak1^Iy(5=?}|Kk&dC+nNZihqsVhXU=2--L8S-x|^p%s~Rr zzue#v>G4wZo85%TY@VcX!w|i4(7O{^_@2-M2-WbRpkSR+D0X>S{LzGRm{uxZJ9T_m z@8uu0C8bQ_IV%~1WA_}o$rp3o(=nJ(x}T8C#mtzs(UELbmq$N^WMnYN-luR)#`c^J zXyVC(M9H?cS*l-*SY_TPk`WHTzH+t8+*~&|wJJIh9KmMRC@W;*;Nzt~cfmcKceH*7 zUkDrt@HpYV4o+X(QIN*X+>2iUOn)a%3pV-NyZu?lk=o39LqkLgDRoRwnMKY>W@=he zq=@0yM9G>z%dQXqJj>WCX1T25iybwF1Z|Z!R;F1f;bl5d7wDMN$A_DlvJJ^)+S&Vz zExDTOlS@7i%+7r63mwwuoitB=X=AiAlpCkfhUb* zwlmZf5{}=^!lSa4zBMoUvPK;a+0`82NLjkkiHaQZNH^`{liU5(hQszpXS~)>aoo=n z#1PJUk8|TX9D>ZFv)AO|d|tn|nGocHw5=uq)L+Wii>{}s{f3K&3XM|>KZPTDA!$B` z#!5=+)LOB&6WzJ|Lzx#3PaIiWTWI}bhtQL5Q9!yTo1;YQXV?U%A}re&ZJ_({)?ok! zPeAOorXYo$uDpR?XyD&~Er*L<1@~ofoWVkw25;`v3(}o7-1xZMFhn*0%kF7& zz}p!Hq1qY7*JE54SK~860!#zx>39e>h8LbF_CH2-X_Lrl5ZsTqLRvxpa3@!<)=hAR z2F^B~Q3y-lZi+xcXwH)-mg{Q!Xx^g?zCvyfELdc*VMQoO&#h(BS+FN_Krs#A081vQXi?Z{?w0U3Fj6TqQ_|%lMpG;(b4U;y0W8( z*;E98V^Mube4L|5c%Cd%4heUwg^!%o`3!bsm>;E-iToU;kNEGo=JK%tQ!GX+(=8YI zqLQMb%V^$ox4IH;$h%wi`@2S-tcbE31V+Tpmn+di3G11gD+zqO?X0YfRMiI3&LMkw z0U;f|LWCTN{(V?|lHEA6&XVJkA{4?u^}j!fs_5PreEQ^hdF$%RN{gY6AA=168q%kx zOJ#poZIUJVGZiGkkQJSnxK~9ufnKXG(k1sFLWGxdWG%UY#2?=b&ZM^0L){|3%c$ZfxfVPJoosp-zl7(`0SRU;OuSwLIET>F zGcci6`5;IUO~h}Y-q>NdGVK`e%glrO@e%72Eg0RPjgqq4*th#<4ddXHYkTZs=-$5l zZCGzPl$>W^F6=q6sLDS0!4;3F*5+10ON9L~S+1h8a{9<+Sll}^BLh?^QGc}q&~Qz( zp+?WzCbzp`OE|Gcktx=OYIT*ACXf&TRj5(o9dU$qB7FR)^m>cNo67EPCBSDtjY^iX z*N%f7fdt_8ePIuD*WVwMK z{DiVq8Lf0k{In#-Dl=fUkPZ*Lr(#LmC%dL31-xr}ObJvIa6idH$cusyA4<6_~3 zh!ij>gN=}G6}5ZF#j+S2H@gb<_dnE1MYAoJ>+0)%2v5+U3;kL$J;mJi6oCf8l!cc$ zoTwkTJI{1qDBM!eMl*sl#PZ>1`8R{k<07c)sSj>j=zSP`RyWTw1A`}=uCa(DqfTIZ zr5()z0--Q3}C95Q7Ln`>0d zHEnvd6VX@DR<#KY;M8eJSv8ZeE@x4I03)^-Vb3|T-?A<7;F8ylE|97J1n&n z8KuR;LrbHNf?wcCeLEG-%%B0S_4MdpFUIY$jzzE$XJ_=)LxH5>J;5Oo9!f2-532`O z?4;zQYC|5!i5GJA%ifdxwoAlkw?9C-+VjN)w_{Hdf-8P>FCJd0F%u&HgP|0x#8SOM zB88N#K1C3!uqn5Fsa~Kzzn*i-wuY~;j`qNVX5^T0ir91%-G4N?#S`YhG+bTNi`c2-d;PeT?R z6$STW3sM8LGzib7Ku4$0P!JxPuvXJ->TA9ZkEo>sZKSOvP-DQaL3%2!>fv+X+kR7m z!SmcqO^S1d$?Sa0X!1GHxj?WrbU9&QBLVFpo;#oBIKkyI84-q;{^Acv5DaU6oxSFH z*6rkL-)!%Y5|wi%Mc~!0Nh9|nx?}Xt)=#F-421133I+yB%D=22^8!KkA=|lt|Jhk@ zP`NW}c_cO(L|odq^~i6UkFDH@z}Iij{6>pk%LdZFW~M4n=$9Xw651zM8cdGbC05Br zMY$fc)|N*`M#4c}KRz_OGIJgXg9I?z>?3pT?mwRgvalIH2L4^tEDC-DzZRqLvsdRI zJ57P#KR8#Z3RU)`^cGhnM7A8k_ajB>^_6G(qPmx^fM7l330VKvS3LpZec|QcACNZ` zek#zhnG+fIzRtm>Z*tFK`t%3NX;QQn+(omj1HT%V1wW6qQ4?`Ht#1ugrhxK19zr0p ztz05#L|Wn7QO{F{PlLyl4VKC$AQ=j+yR$3AJdyY>)K{|K82v~{uhQOQhN10l9pwz# z?lE>&r920oVgrj?y>JgH+fuU`oc1t~0ju(%Yg`mk`j!17I4W+c9IowH16d1gLr8So z${(8pY{N>?$au1P-d<&DiI#}3$%!9R-tRWD1zn1t(k#}ESdvbk`jG$#y1^K~{FbG( zhI*vIjQI0N0JU%7>Dd-PGrI;ISNf%8%??x}adUrd`6S0ampjR^^vBunT~>8v3~1b6jg zZUEgVM~0fU;crC`V`D2LBkNi;f}BTLcG8!R33O|Vo14YUdjG&uoNT%%+Y8*uV6wnl z2yu7Y01p5208x5cb1GXXpa4J~#~M`8;(k5M+ruSh=xqpQ-oN#tnM=nUor}&!Lr*L_ zoVCD zhW)?qlUPr_S;!mjZ?$`sv^r3{j>f0E!&3htHT3-a?MV%Z)t{t{~3bbn)11%Fv4DgUi`NYvEf(a~%)Om=qmtylYF z@`@gIAW7SioEdV6+we$KH`V9uauh)y>Sse23kZmzuCBKcX$gkDbyWf=CW;u8P|j!~ zIRZY*VRM+#FQ*PI<|H($F#bV+iS!+9N*s*F+^=CEk{R5VD;IErCPtZ+)oZN**PyBD zsUM9rp>Uvv>9)f`(zs48PF5%yxK9DOLysvwBo{wp)u5`HSeA$-6>qoNJPh;RNDzAH zHrcZs8zlxMv)|Dus1J9UOE*fHP}9~1l9U&!HvT=f>3AD|d3%Uy@y*~dCAXM&z1rOk z!@zibZHGhNZNK(t{ngod-4#%oKxN@XaW`NDX9=qqzvbIw$o0H_Z=Nl*H#EXHV0`Jo zl{+4*C>>6}@e>&z01LSC62~Q*3}-4@l@k2%JSXTtrkyWG9BAnos#eI|tO@Py&kvJD z)ZuiaO?FWelW?Xj)Tud%%2M%a;B0fQ%ME}Fff1>GYBE$mLB5Pw?P}h|U$AI)wVpl5 zNXa)Y$xcrG2!~>&!blgQL=HIMO>}i-zdoPLdk@MFbbUSeEh3j`xpN)X5&_Dm19qY0 zzX*W)uaXl}EO>u2(soMH+RmoEgboGByZtaf?C?5O8x%*UFAme&r} zYKls`OI9|wwyMzLA67+RmXUUneJYz@w1% zkpfGJaL6vl=aaUM1nbrV{bZgUgQhGC$jOXbCQsGFHaS<)(o#}Vg6(Sln378kzU(`X zeM6Zg)G}i$tB+{lbN1I&t8Kz6b7QvSC{W6bHESs^uLp~7G*u{YkJtQaW^1wWcaZ1# zvU*U;wu8;()Mfqu?rBMt&)#pp_xqL3*NUo6zl{@zQ*l4P=Kv_%NkPHqtI-65#}N^b zdUX(L2?g8&NPx8RyB^u@jf9{!4SYr_RT47y!(-l5@fZ4W9CBz^9%&9*!*pUM;OB9m ziIZys?~f)EH(y6IhNcM;jEG9fKmx5~7@@;stDmM#xpXobRqHp;-@`tC== z-*@7D|Fr%zFPpEZA%^@yrjQ9%N5y$K4@ji&e=4e=v~0JB0{i2o7P@nVODL(eBye4$ zc0@%Zj7aIQ#KJB&yT5z2M`M^j`X^3JIaVzsm`-7l!~af5*jd&h27WEzH~vd>@>dk) z9YsWgSE5UXMA93YY3I+aKNk2b{WH?6coBvI52a0Rcg~4|hV_+Q%EHf9)^_ z%c6wQ_&E=vNDJ>n{g=*L*dUQZMI|E4q*7j(X6~eJ55_F4Iz=@_!NL_@ZYb4==o6S- z#u5uC;duYrfF-=1UR1yFJYTQA_gN2k6^5MSLyM_Lc6XsE-1Szws0+k=_mBKTyKoqY z`7Im7;c5DU)?a?t)abJ_G0eE^kBNbcCOp3X8_&%by?^ekx_VY(UF5KkR|!eT5)2v(nXkt|pzZZ{0b-!o zIgq*_RHwxWH{VmZ_Q^$FN(@@sERXrx+>~-uygz3=%{1otxHLFeJ1UzVAMekWTWj7_ z!@lh&6ye{olLs?aJxbCrMiKY=OaAmZk8KrFoV9css)?Lzvt)tuU3^3eY~z;cxsas0 zhf1LE`SL3WFON!?_|{%-y!`8J2j;=5o$zlzGt*zsLq&3b&}H4gtbo7B_F0<^aDk|% zSb#-UXy2nR%qDWS)2Jq>GtsSF^6*h2hee|<0Ox6_jpfcFD8`M5zf9XKIU<%!@jj4x z4XN3EpNTbFGG=b7XQMDf0$1mSg{8H$v?x;Nc_!;XNS6$<4kAS^)HDZ>#@pht3tQrj z4w$bD`XCdGcfrSaNSD7!RCB*?P32;0()m}_j3Eh@8q>mMpi)OgKr{&NvC@oeb=S9PYSXa8_SKTS;unBxex`zgo3u(<4;*?bzeEzmwBN-xufA{vaDruH&S?ojuy3O0X`K^na+Wtwme?!Yd+@ zoTE62=_Cv(Cg{dhc=M(*VE-NTmZyU9UQQy^*3(nur5ZA+c|@OWz5R63o6D%Qc#THV z8V(HjqoX6G*c)lNZ+Vk9M@xCs6U*8v@x#c`-ShLLw#51}#WS`+M@`p}5ihEvG`&8* z_}q{DS$YBslbAv7C3(zRlk?J6`bxZ#=0c#_kGoCDiYc){IVTsjGtE}ubMe0dynLzu zm95yu^VT)P&fMP?g4Y;SU4H4A4VbvRX%)Ut=a*z^@$V_T_V^O>`^xo&y&Co6UxU#& zA`=SL6vN>>hc~(3jVfPxI#mj>M)z-6j~k?BWqlcJ)NOsq4}>A`I=#-3g?VPy^vkuC z8`I)Y2DE-?l?rOvlSdxN0D|F{*)Ci_{>%~gnLKi5!tb9iZ^9E0BG2}J|0!m_Sl`@1 zQtoI&5p0OrS0FK7byje7kmU6N2gtIs0!E<50bX;kbj2%PEvKU5tYQLQ(x*+F=c;^_@Zpmk7)W)6B^5z^3D-J|TJ%wS_YXBArG3r#pYvK&$7!l2K z+WjS=OcDSOv3T3;WNPE??tTA=J3^7K;`{~N_7e{fXhddbpfQSIwsEBX6734?*?9cH&p5{i`>4!O z{VwFs8*Y`is$6)4@(MdE$IG)nkphD(s59>6(xwPNUE!ssrcJ;<3!%F`jiQ{i;rJHu zl2TRo5H~l%hKJOwx|qW;Cn*YURx7q$7H}dPN7!6<-Fmt|i??B9zmCtxAPF<5&?_gs z7DA$9{HEAvmOHK2<6#I3V!}T?k`0*KtID-MMEpHF{+?rL+he_zYo;q+O&Ls}W~HS? z$~O3`>RlC=&yZpA2oS0bL^1A>pR;20w=+Oc;i;&wKpb7GsPrGXzxi11XFI$GTA@y) z7@N%Z_e)gR2_r=N2X>X^<@O&+?*E0Kcpf)rayR1Y>gwIz0fVKJ1hE2_9ML!MK_+6<$9 zW-Mx|d;^f532ILlzuBH?9w*zq#qrHF5)lFP^0P=S?($NQ4gHm$x2tMtWu>0R{ND7Y zpWj$yaL6uUC?=-c{$&erh9`)Ve44PT{TAR&i%UUC>9_l%f#%thRc%cNfxWAGYg1(d zVomM+^I|v8^7i@S;U4q`fqS^6_PclGWM2Z^Tg6E+?J-)?=?5C3j1w)JYiimG3IPA$ z-EZywk(A0716K?Tj92z!8>!jZs^p|fyj7lJ)mVI2-CxUD8R3NyL7-kzTSOan_|9ye zj!ih$-tEduu|1Aj!#d8XuA{gMwTM+9)s)E;I%Y0>|B@=(5Qjd*zG%uLC*7So07{)p zM`6=N2nYx$7v7A0`K)|&-&vKX^oB#e=kmy?Yy7#``vM$GeVDivox|BCTq~wQ_8^Wf?&T83E5!3_EUUmsEn(bc(!(Xk*yXWD28|aZ%4Z_s2>7w;yu`c54qcAP z$;raYVB)AyVE#`0X^EI1&&pN9#{r^F*j!XJZ7$pxz zNx2XBhv4khvV{5-G9TPda;@pHGJ_kVT(zvnyw@G&AA3J;!RLMo7Z5XxB%h}#Ew)B3 zg4yK}!3R;}l4te1?4@zbCb@?@s-F>6fbfOU6r4Qx z0jI0yH5>2ErfJq*OJ6}$uy62UlNClSfA#1l&DMXr2 z->F{aXn*o&8)Or4gM z>kPtGlc}}zCHHO7-*V&`=h0lM3D=TjeM78 zYVzGH=FkT3JE2J>Gy0BCtX|aC_Kt+k%B04wb5d{>k^eVdFd7P+oH&R}wKX$&i7}BI zr3H&^Xh$^P=qF5@77dNkos@&E-P^lqbYkEftH2GqJmuw_#8F98OlhnRCY3pVnBO>Y z&(9keZ@A%(5t(ByJ50>aCoYL+DE_TZs0760jP%HW+mQ+(L__c8)xyN=?93iUKqI=0#QoiLQ0Y22AtApGu!3oeCnzwHWq7Cq#8>Szf;mRy z^ZZX%xs22;qQCu>-PLcDFDkNLb|4{{TixehA!m)`lkDsQ+&fLIXifY$mPnzZ_hLMx zZYwK8k)Niz2<4hT*}0jSO&z%_#b4aDg+JLF7^khW!m9#gcu%Y?WPWN&*6)%<-fGVoDn?N58hNnQtE_aVd{HxP~y zY=r7TOqxfVq)Ekw;V#sLNHG|3G;hL?mZ9OMo9K+6xB>?;R%lNv(I8>!XjoyPca{;# z(9mU~I>f12Y|}hJB1ebNBN$N!+mwgY*4B2KY~GU#XA|UDwK!>?7^EN323WzoU@%y zRe1?_1>%3RhDj+pq%k9e2V{WLAwnJlZ!dk(OG-gT_5_fP*@8DDz3Wvg4r`syU0}|B zXnQ;PaxF9NT-wIbk&he;0>MO8nI*Io5*6A@J8xP!ULmz%A~jE(0%8(^nD(U(z3b~s za%Ltq90Wlw2IF6M5I(1ZeuNNMkfx$*3g^BFOC5|$NNyrdZn1jw*8Wdn8$ne z&Wi%!RUgN^UekZx{UpYe&k2&ZaX)@EbBa{{HlWyR`23N}O;Fx_kzX$4N;^cBHy=n{?$tAJ^4=T6-I&x*$>- zi6v+$|D7?eCpCS-@B{NP6q6@@6a3;>kUOI1@ug>sMqmm$KR6%(l+)LqFDt_v>kQQ4 ze-(eppRvsi22u{K|4!p{goP<0t&x_a*)nCl?L^nW}se%#^?;1v>j5jj~?!}s}zRA&yy2zF6|?d==WeOUpaKi;bM%arsj0Yi0l#MV~JlYeXX_vH?%M9Ug6r+kQ5A_p!L3vNVGa(c&U zjDp13x7Xt0V2IP-ikUSs^zX1JoG6^K;NNsPTJM;FA0BPJ58!vEB<)2GcQs^tBw#1T z*VLTF$7@FQD=Q8&nV))!;o~ut;7b0RpWg-TNZ;e-Gz^k){()@&yG|U;<(xS+kagw4 z%IbbAv1#PJMwAVP;KmFv4-^q^BS_?U=oeRz(2<&pW~sk_540kH|E=#@MRfJ)X*FP0 zKoXN*y?K)rLi-~`A-oF^5jcoxXfiImfTs5Om%a*xQCw=Zhn%OO>qWb2#%@L=e5o>? zYI6^d+q^h;iGNZfe%FF{!88wl)G+vP^WeY3ODXcHzU&Nhe9Zu9jH8}ue|`j`+w3FN z`f(e>c@-h)+JfXd)#bAE)aaoY?>sD``^~DKA3LA7w^EEB1_K_PS0&_1iaA`9=j%(8 zhATA)Exm`2u5a6oDp`r^a}OIJ=A-sPDJdzHA5D;=l5;0*i~!%0P^PLaS4*&p&>zd( z9=t#8d~{->Q?(A^JO+bAM)D@e!y&d}U@%C=TLHq8@d>b~Dc0_0D`saax6WN*5M?;T zF{}bBOVTbIDD>tF%P{OK1~)klC2JiBWDVKN z{-i&Em_?3-j8Ltc$s3B>!y;E)`6F@v5;je{jPp&OmcF3LI&QDy{xAYa)l_lxqVI{o(?WI=CU!V{F_F!2B$xf4A32NE z)R5q-TSV_uIxYtIt*wAg6-DzMDg?0@QjEZ=rWy0|EFcMPS$^|p#%lik&0wjw4`*2Xo7FY~FO-0cHUK@(&!0f6`uQYD#|Rr4 z0fhPWitxWLYQEk`NxTW>ud=Xxz8L;OL+v8{hd-B_NdXV}S%d!%WyL{_78ezf*nLP4 z2}0NB;2}aejzz9}<>TAAmg0(#V40>_0}FzaG)Ejr&8-TQ6ye`Q5AO}?>T%ey>*yxM z2v6F{$&vIIBmIK?B*dh(1zR!VY8_g@DB}BW4OstC<()+`N=R0d_Fh?i?kl=yF z(&Pj@-9G2EpJT`6iC#bZST&3t!#tK;9ODiChIOyZn;_89HGjBX#l`2!UqQr24f+a? zhK8s}`BuhL#<}~e)K8{c4H^FKd1P~Y4m~cH{sxPo7wkvM`T)S(ZtIkct$SY@tGy(c zGx$%jbRmsl%wtDHgWm;M{ zd&N(nk;fiHv_j8tc}V}CJ*lfm=EA~)d+Ul-tsckcpBMrv@1Wr3c@c>}Q50DCA=u=T zmF0pxarTZbyN=M!F?WjrdW4k7#=%ANd%%-8JiNH`HOfOaf~N}+$s*I!)9WSD&D;SH z+XvbpILMnsD?@D=`15-||A4NYb8^9d_(=4~pPS4T2K603HPq7udKGIo&3~k=Z(33$ zkYWxbqOBX~cSv!(*~f*TO0h`2`M}m_ueS|@rV+oXX?=Uo1U;f>AjIBu118eg87jDl z`jzCVei~5dw1Bc8(1d{IX+_QU25AnTXO3Cb>y{Ij_M6qagwS4$F5G4p5$#b+I#H4& z20N%Jw2n|=BKt{HGAhY$rT^`=ss-Rx(KK0gSFUa^)wjhXnba-_oHrWBG8a7UpmPnY zkErM?@l`3>E=1y_ZqjI6ZKj>SN*}HrPM#k9l{Rq4{M>2ob3ZB6ggnVjYT`pM0+G5F zvp%9d>UIB{p~IoY-@pqQN5bGs;T%a78cSwOvZH6HKQa>u!3FMUP9l7Kd>@6tcir@A z|Iv`BIqPcxg%)bb4lk=jpa!PP!_8x3xlm3xQ?7euZ+a#S*>Ajz-Si@ua1Fs_6n_`Qn!^QY|0Dj(}cQ1=y0r{R4H`gG5e`W(0^ z63c%|ImyJ+_LD+Q@!IzrZZ2zN=k(g@aWdo3&Uv+qM#+7hvSYCH!;Se`IE^BEI5-h|B0Uos%zXOQjEtI=wCYkRFa!y!lpB z>cacczml6&>%l)~Vu#<%RueULGgGywcO4gz?gc!?ITq$?6I^Y!lugv7?^eyXxsrd* zk>DjdS(S51|K0k^zg2++Mo>{LuC{mJNc@|fVS_NsKAG7O4(YY7srCfRy*Bc5wcJ8Fv4i2>qRX-Bt;NTF6~|3_kHL1lFJyG%MoQyX8SdgHg~8`zdV>0 zHQ0pWOIMHI_G|RA>;12|`GgP|cmjkc6LnMgcTn?=q#ieSSbe?b$B%#JtEJu%An}Bj z?}s6UwHKri9bOY1ZzNf=CJtcWvT|)(jn}0lp;=lsjE?qQcSIoV*@N1{%Lvzg^zEX( zQ1*@rGx#SqajmRPk;#>TBF@Fp!LnM(G6jdHr81+Xg3pvQSNS2GQ7N7fSB~w;P#V(= zi$47=vvk#hy?g15s6o(WeUu*(vZ>D$r2?*WUOKG!tqI3!INE5jWqNX5eaT=fQYMUoA_4h-YVS`K{5nwi?p-kG#D0K4A2cak}$TW(!w) zwJ+H7W~f-zf`{))>GRb>)c#Mp!Le*U7wPBe~kkb#yKH(OohUGNJ!TBRCNR2y<= z;`iL*NPI0qxn#@UC_!&2*@PiVE*=p6)w;-dq|>AqEIj!?(?nn&V7YML8HeIokv~Tk zmaN0`Km5y!oYmgyK`N-$aB$GhNDn6AVx^8FwqjOFB?@u4pR;TKshuu$Pv~_S&{CkjQ{VIF62=s$)ef{M$ zru?a`{`)LbDYh+<_?#SL^+fB2)R#TgWWoi8L{Bnmv{P%nkx6KX5KKQJ`8cK4T?NAl z&X6YzmTIrgp+4t6tLg>RU>K~1yMxTr6CduVLMx1b-cnb2Is zSmu$s9rDO+(}__L8I{OuT=S*jCk4jrdE+RA!_Y8WLi;$#?z5@LxOfWFq!E!(SCTl= zBy(yAQTrZuV<7rV*N zS0fS|k;|xL=1GI|Ipk|b2ETugNNM?jgY8O%XRH&sE#wjPE%bw$xeo2*r753y>R7T9 zQ1;+q-6Kq$P8&jGjtolFPWkUEaRqNSvAtv2{=4;$5@r6pj$W#Y{RQ0DTo5ezin*h+ zUWVa)R90*Y=5MD3fL9wirpn=K1eyGmYymJxtXlMLTA;F#pA@sO`%ohEm%x}=_1kPd zx>v$WedljYMnk@4tH?kCzvm({$>Ccnzh>`YrI5+e0v{RKQ1|2P%Os){PD3qS-7bDRY42z%1rfx=0+aB;1KZQ4CEuF-FtUDA*W=}d znbH&>MmtgKh{UIywq4 zp9~eA_dojKRHp5NKduJ9OySZiRxmM&`-wmvEABQ9MmBQ;`wxJ+bmWf~k-+I(*# zH#XvnGkFjvGAOYV3O`VyV z+g;4eEopt0s1!Vg4nZycu=2y-UQJ`hSE6%`MI%ydBF^H|JU4T~UTx0fau^nlSS0A9k%5PkK-qgPOgM;7G?_j)hci?Da+wyN#-*I5~flefr1hKVu z{paW?KH`tL1~7q~{26G_78lR2JCXOp$w$Co*x`PAQ*Tg6=0oL42y_qS@o9pbDF+dg zzkTCv>O#ke${fc;RV#ce5q$~PrPqRQ%$R463w6|P*g3PXWM%T7fkIJZEdy?2w=T!* zlEeLE`}NaB{2SuIbr(R}j+n8neg6@d?bT4i6hVUupkc&O&Bet{wY9Ga`x$fQSm+Wp zGsfJ9Hjvz~WiXMJs<|5gDRFLm(f^QuxfZwM}*Sh2>7^r?Yb(`YBeCQ9LJ7h(?FDd&n^mgoMu(} z9d#C|WZ+Fig`LfC5WnFm28i7YA!JBoE=0=TUz!&8pLcr+KJPavbPSBtu9n)f*5Q~i z#KI4WJGUVZLguGZh@!7vB`6z)844)6vKDBUemdQ_RW0IgYek|1 zW8O!5_KvOuJuHm(U!_rpe*SE_oLvF{Z%2p$739BE9Q0Pd19D~6S2`sy8_CAOap5Tj zFv+LhH)x*O#!XHR^76=lY@W#*)Vw5~2tU~Gl;W)jq#bh+kpbt>tU7hf3JSd=(gplM z1%+@%B_%T|EJ>JjI=}xz6SOqh%F4>U^H)9V#qERjc&10AhrIZ(0ADhGLY~63WWo4tw%Wa6UoG&$?pO%|=Q;a)&X@lyU#W-Ft1%0+Ke_w3|HA9j| z<;~zkK;z)&XW-_R6pLdG`pT)Ft#Uz%i3uKe3g_@or<%&w3kNV|Ft|s^#Ki902PZ3= zpUZmnp0v=XZ!QY=UWTqe!>`#rnr;_~_CoBD0c_P18f%X1|^KR?5j=JTBcfFWiJ zzxa=B`tm0nSjPXp4vplGx;nT0-yevGYC*4SGJMlBy!S&j;ypUYt5*uia&birLHm5O zQFy@->4ohVQ)?ba-fGEPdbL(-o|`Jz;RFy1z14<~BTtX_qgLlzDV5E~EzgQgB$jo& z)~a$!N_4K)bnpe_bh61?EiKHT>aH<2h?bI7!nf4JzII4^3#tzTuDRXA@gJ@bl)7{j8xWgkO2UAG z=h%MP+PHGni$=g4RvMyt)VlU~b=3H5bM}`F^6$9yQM_XIs#uOL;=+piQIINab*s`wJ4psF7UoZ?0TB zgCmYU$JWSbyeITrrnb~6Xs8CiTu;Z&LgtSr{+ z5EBt8oQ@`LNWn+ds?E$BM`y;yVplev|LNXed%BW_LU$&NogOcR=k0@+j_~B1A8ybN zt4=pr9?o;PncR!>LQ3ATOr%4JBmE$d>>YCQ^jD(3U&u8-eS*^diA!dRI$j6^XAv$g zVz=CuBxVWmhnaKfb60?<*uB_k^kfkpH*$pCbo;Crf`EY-jGgN*FsAv$* zmKqYuxd%#fn7ru)c^GKOhHlSM4x+$ME!19?p~;&m6c7F}bQvCM7@2DT)@E@r7qVYbh`R38VH^aSY|RGc-QV%Un?XLyz{tWf2%ztD zbQ2_qGMhewm8IpvQEW?#t9?LqMTG+}-9)3dyOTjJ)2r=ScaplMOvas>TraPbRAc*M zNa_Tgfdg#_4PLu{y~88y7rtSg|3bOK8qH@^z?(yAPr3ivGWEb00;Z~XuNXvgm|aFete#gxF7BCqhTuQbZb&q z_BdK>_PMT*i{b*Ezz$KLt7W|R3au-vIwrTbNPx=V{L|rYW$UqPb%*?W8C2)y_oXoG z(M-MV^V^1xOQPWFii}(c!HpX{pH%SK883HhU73(*{dBa*&ipntb@#N8B2lw;q0ayD zed)}AL}1x~{_EGSfKWDg%IhbPBjM+=)WCT#XDoOFRaREcop;imSil97z7KV|X6|Ly z<{`KVUxd@%!eAlPhcFnpGhr}tYTeY9e_|dpP`h$%3P_FeK|~$s?KU@aZiu!-+?)>H zQ+w|Q0fMBkKxW)=U_ww>SeWptXFF`atI4?_G&Gdqmh?=_nlJ4hJ^JNevun5=kzTIY zKr;SeSSgPEO3N(>(JVo`@g2!$IiY||Uh{3>blqUTQ0H?!ZaJb|rZ3z#JxvP_a)3q$ zp!&8h@|{pOXge&-s=CR-?*NX+;nh0R0V#fY6UzZIE&wY5p#b`qH_(=+8uYoRa;EG%JB z%f>?NyRm1bHv{L*+m)nm-IQ&j;uVuJ(*m7qBu1EvR<%g*{*}U}-?|t8-?hT9HU21C zDz(evB|Z){Jq&iY5yfr4)DW6vK_QMB430VD`yP@G!n-8ZW~;0r3v?t`hKCuu*jQQZ^GJwtNxxb1K8!uJ zYd%(xK)3Hl5fn<}xZJdUxKKMuZ9mN8ywRuc*#2>~4GAwNC~d$9-kj=JE^k-V{}Kmm z)htmk60)JEHTT^ZchY{lvSnX6s{bu2t_*8IdLfm#w{r4U@hByTQ9u!1US9s~osN?N zgCPV3N(u{$##{L~5=Y5R*HvvC!e~(=Qs18wW$*ML4GfC|f&WNL49shL!1JXL5_>3; ze4N}o?K7(@2_D?^Iv<;(ASZvp&R(vqZ@~gIRsa$R0m)!Xj&>}?rC*_~U0n7n5BGmx ztIP*&aVT#*zarpPH(c3(jHFJ%+6gb@kKkNct?uah^}&4gyYxc25kDAg7y;)YlBuW= zcpKfX?H?TE`M(eE#miQrA92yvj$Pr(m;K39kciuKwRlM!_447;d{9*m7xEd(yrbNL{pFdzzi=^ndK|^I`afkl}Mlq-$l>utNRVQ=YfN)v|I_+4;2ibK)G- zHieHy-teY0`IiTkJQNyPLUkQS2|Q#Gh`=HN>oI*01kjrKQB2 z?+I6?@nulg58YAT93eq~;-xRhtA@V1zhsPPa#$f}=1FdtKrDp0>DF>J^exwH?Y%mA zFa>NUh`W3GooJM=E_tSL|Pki~6T`n#zoWyV3p2zc6;?i?+o=5rh=gRab zVCvJ8>mO`vnw)-A|5*3+^IHLzr=49~Lw&P_o`bEeOY?h6J#=J1fH0|lpJpc)-v{!W ztGk0v^2K7|2rd{51TaYWqx?pkWvA;F7vEL0R%z<#-QH}*sbqgRhmEh5We_9GYQ5q!aApV-NG>kxYY+5LNqt#Zz){F&B*kSL~l zK_F-1ai-G0ys4?r>R`>sNZ$vr%94{~r}r{NKfOOJBP{s6a~>wlT(=OiyeqE@9|!;<@xDej5^Mg5G5QpJ}b-bdPP#d>0kW%{?~pl5K(X5xN0#E@6p8F zb)&R`T-E&QpBPegSNB7QmCM?(Z zSZ3CHSw44rdgSo_!lBoUlrXmbiCe=0Osj#zZQA_$rP==PAwMMM$llaXbo3xE7)=I; zK1t3Pb(@ww>i9R~^L3_IVJ^CTYggKJD81n-pW4z9{9ha`&(*7w*^uL%4 z%6f%US0~`QoGE1@xDkiT4ma9c9qh*j$0}`5&ib7d^}At{`s04-N!$6jyWH(&2-@@u z_M%FI=FH8Xz|sO33<&34Ma7USD@U@k?}T(}8n5w`Stm-D@1Ea+OHR0(c5RZbbRP=yX4sH{NL=N-MN~&yu;UY- zaOMUx9U@tOD_Z@{?4|a@HEnD4Bp>%my|S%2C(-Dzut-2MN+2$PU`U$_`T}HD93|DY zwfsSO=z|t~h8`<|bO56>H-87XW>wme-QOQZj92f$qsLnNV1X->%%OL6p0j3Gts8DP zp#|Mz;!%nhqn=PaG!xz7&Xx>#YThxrJ1{oB^4{?l6E5B-R<1-KMd|9dY*B`I2A(Dw zm49fwd(@=;^|WZ0p|8Jmc2)zT*IY6%QDM5Sw6Y`qXs*VhCN?(qUD0?9k6JZsU&_Zv zUs_ta#_K#DrJ;9ma(#S)n{>zvm5y@oT#Q87R0}$@Hwt`*3NJY?T|s8xgLXKYcHWDh zIO2Am{cWTAK>p2yp;o=GJEn)5cw;|HYM75tcJ4=m) z_^_}bp1l2U{Wb7&>j5+z9i8helYhL>cIv=#JOU(~Dud~YxjM(0nMrY{+Q}0;G`AAJ6xE@Ej67Fhnll_{Zl4=9HiAp%;HWX+`R3G|mtvb0BoUGETve+SuDM*(I|HfJHuyq<) zphYCGsQLf>R&;%Qw7WacG2#KI)i+`+YJdts;duk?JUWk&^!%3wgT>X;^XKuE%-uV z1M12u%n3sls)|MO+HjY{?A$vd75Dpp=c^Oq^M#>!c;MFRe11G>s3X;1(HJq+S+Lbsw0{;{uQPE$GVK2oev?#PD)}Tsk}38WK>kq+cZi{`PvFL zI!50-jcI}KUTVx(8yZ$V0hQLf{Z9a`x=?=I8(LVX{Rp@MkCG4TFk#&O7tO4l!MOgh})>hpYbdN3L6Zy2)AZ*j6obUMs5^AKQVMg z<{Qvr4`C|rAb;)#Pv6z5(0sYc+1Z9B6`;SOes{l1-W5q*i!$J%{+`XI91BSB42}?+ zP)k)`1SWO@>BrqqS3$oI5A8eGXig>6EkXd+2p`n-g&=3wm~FVmcg|4K!YA^28R2>r zqf|R+5W(kq!0mS)1`uz&aT?p7vGepN#TvLjv?vAK3W>FA=?zNp?vmHbr{Fs7xVyX4Xf-!&Tq5Dz{~-Sw z6}a+g!4L8;R_TuNTfM!!ZJRXY2&-B^OekTuCdd2SZ6dbxTV461&{D1(hD=W7I(oFG z_nP1J>~Bg7YgBO{YwFXqlWl|JLHIdBP!Q#$^<;IOSScd83%|-Jk@3U%c2TzH#;Nd{ zuVH*2C=HD6vXMycAO+|&nI=lkWE7k5URQa3j^Rw?ul@p!U!ztI~fYl9eFoaOu z`}0J~`P2Qig~dFJYG&##9aA8Cr2)aQw7Bzzn|0BIF2?|zteYqm5fQ(igt56ff0rVE z9t!>cqM>z#YsAI3tt7n=n9U`7sW0$v{2fwTKE@V@Hz)e&f881mqV?zpiN_&C#$-#G z7E!w#9@6`RTm`^%9?b>-bb<4;JzO2y?nN3{zU)0KWFCmSdpMtHHmIO^z9~vEY`6gQ zSX@NY?{@{!Aaj+gW)JT^xRkh>e|OUE!A1H&vSDlW@bnL*#(`=?D)_;7_b^1;G$)55zBgFc0BFfIK6YvGyGm83|3IE`QpX!cb~?tgUc+jlDTe69vK(cTI;bF z=bmCQF=LVVrSNNq zgN3~FmNKMQKt2f^5vgO_00_focSZP~(#6-dI&Qkq?QOLRa2sSIQ^ zV(!jY4^;FXf4WfC9*;Qzps02tb}_=GZ=m)2-R_?RwQ+B;tZ$sepkB&+>FNy*g1tu) zikG8$;+DgGLkTQ(4GkKwEXlR}53gmrX{W&HKQlS0p+)FLk1}y?4+{s4kVSi$w?5K# z>41w>ASV}ksPnpy-+H{hzWI~4`qH8Spe!H7Gh(|%SKCIj-Cv*Rw_c%3|4M4^ZY!Z2 zMEWe0kqrL`l!=+*I#Ak^p=@_!gU>fWNd8anK0(s zTLU^Hym~VyIA<^-yD&A?7Vz8wWc{@|QEH7hZ+D)b?haT1&hJ zerA6}QpGdu7ZFREM@YvFRPN7)5;~I^g_p>cB*A5HwOHCZqJ8Mbv8$*IE45+Ph1$ z+B&aaPooB?{11c0%o73PeC;(Z$-Sm2TP6zO;kZ-t^Z1y^;dM&j=L4WcgV!(wSLI-@ zC00>y>HF@f@y7K3a{*o$Kxe*o&5JE<*_M`0Ln$foB%eIj!mv7LI>&kFwagLe{(=M# zWYwUn>E`{;D_aN3WMBfC1%MpZR;1g>*u{k}BSw9#Ky#G!%EQ5@^?t#+vBJr4p~neI|2#$DyU@_Z-9!Cnls#5lI4 z)Gof}4%}Vp=$4Cza)SP0LFI(VDXp169FhKbt<&-Gj%zKR z+H;k0gai$+r*U3Bzj>QI5KYD(McOSoV7AiYFG@kXQK&Kj)yc2~9ss4P zz3xw9Z?oUNV26XC293U@4QV^a&hIc}glH>wE-&82U0wBp=8rNCpcd@+ec3EhIas`b z3oI<$PA1m5EB}%Z9v`23c1HHY5cA3!P*_Xdsx23Hlin#lH z;`34#wl-o{{)gwusk6EX5BIA>7sbamdF8#u`qD8g{%J_T#h-2l+}h|f4II^U0q5bw zFNYIL%ga6K*ewPQm2)}8oy`mO*qmf~Ng}liAW!?4a(XtR6E=LxUwBwVoJF<8N$gk}Z-c)3#&%nu<1Ru>t*5C5k@;rMbM=dzT?B$%%Tk zIjGigD*uV+23u;CC&jz(fGMKMN@b2FX2yolx`#ne>BYh}I!Y}TEq;H+7J@{}%sE*f$fBJB#QJPxTH1K^f{EbzZy5#jmZ zc~n2=pDu_>LD{gyID$_=y2RoxN);q!^3`zQZ%q_H>fJ>sCnTSjOWqyTJgj~V5<;kU zq@JP1ga>!u99mm2ECD3-wlR zI*8Eax3%@yk7FZuBB_&~GH52R$G+lreJaqIpu$4=$5&W+Uo$f+e!aUY zhrs}%PpN=P7vcHz5K33Nv>R?ISXW;!r;ajc-@r3RTuYN^Th`XAIM84_b7llT?bQ%Y z?F|NNZQPxCwPQ3CpfkzI+cTweyUj%rHuZ}#@CZ`cH&)+uYyZCImbm>ZRC$fw$9=6o zY1QQWd{0UZL?YVrzth&U{|Wi5h+CPM|DT;!gKQiVK4N!rLITwtfCpo?D6p=@aKtaV z%;Us?aN<-GwYmkZ8E6k5E?BOA^mwmtD7H3}Z>08#;&JZm-tRwjwslRMP>(9p$isMA zpopKrOb1r@0bryoEU)q-XJ>`7&;jcA2o!jt4_iK)l#%C+R~Wu3EiJvE9P;SxU<$EG zPfkh_|EV9}!=)(AXMNz z+vvqvAE5ZMvbv>`^@_yE3n@K)YDn}9*3!~&HU5S>>%>WfKs?p73NT@g|BtJ$jH;@O z+C9=QUBaP5Qt3t->6C7ykp`vfP|_ixG)Tjtr5mM0O1fLRyWuX!cklP(I@F@eeUi;VZ zKib(OIOOc0(%W!qbFh;LvmfPBZctEUerNgg_)|1nta;J_OqU{~6hlQFnFL}J>6i)m z!RnQAsq(xHWMZY@H4*?}W$MV>5n>MX1u0}A@3b#&Y8=-8BC|*Gp%M69`lxG$LaUH4F}gZ=RTD~9LTihk)*0l=NmU<58(!Py=<{{+Te#H)pc!!o=Jq?u(bzA?5Txg>&BO}uh zM&BE!|MqPaAf=_H1<%)u6)G%(xgTYlkN29s8 za03r9E)5JhtNa{%V z>|04cvb;T^U@Uj)_3^E7pm?XF$F>+EZ75(4WoBkVniI^PE*rMq97U4#!d?DW^(+st zKWS{8gov{TUXWF%aI}Pnr}I5%snydMQ|{SKPW}kSac9l~G=l3Pr-buST+Z-4!i_lg z)+!Mj?i;28qNvn@an6?Q57Mk!CWJo`)UAdyL|x2mmb2??8@-3_6LOkkD9f~85rh{4 zs6jBZ3$2z2$w1B|Nj(#Rpr;cIO=6uJUv$mbMWX}sja_YG9-GAw5MO~2CjSVl=}EDc z7V72U^XD7w*CC<)=Xm$iu=xFbHYZyz!Tp8*tVaqH-RL0C{htWTd!5aDqbdFhNS%8N z!OrvMrz<LdK7u$h_EEl5oBv_E_!r84>V% zKx^)Qzb@3(QcgQ2OL=2tvvVCoM&+{-7-pe|ZRzsxaK!E@oE1j3bUdEDbhaAivjl_b zbeq5F1|#NBx|!M8ez>`e?@u)h+By5Fo3kwo>#K3I!L2jJgqc@1jFFQLW>^KCXCfQX z^fu6niO-!W$;Q?jWev;z+zEj)IOdZAA6%EDV%O1Deg0Fz^$s&*SSbk!2kGRFPFs+v z?z3k0cC@4R`3qhe4j;Y+-;ctTR8(IZ8s>Gel{&KMB-04H%Mk<ZI$i}KfCV<&6&JL1J%J{RyM9UPZt2IQcc+0X?FlV`{l5L+#3JvM- zy5q^`qi#zynCqwWi4g4K_-?;dmhXpx2n~^3CP4cNq~P&#GKgG8^NYC z?>zEW%WVa`HCG?Jqk%clH)`17Lgd@ZMQ0w6Z`$BOK0Uz3$J?}aPi%hi+Wlw=_;o2G zIgR}Oov)df1muVDmR`TUuobwDBbt`v3OMAmj=lel_TyW;ez@g2F z7f>~)O5CJw&OQ9&8X)1sY_7db@vyhQR=fW8Gy1fT3;Vgci}_o4By7Kc8ZCZu!%QjVEvb4IlLDMnUU{0;fN#<7Dicr) z;yj_uLm~eaY-~J)WO3;m7~Ia`WRDzOU1{vUECgRvzq54ye-vfew=x-9fadctgSkvR z&f`~-p_B!JIVpkke`-4Pw;@7mU7*f;VJgr7*b0<7@R6;$SvG}x9R|{+=FRfR9d2uI-{@KJeFolpG5X;P633l1THE~1^}s#@`(%tlJ_e0M6-}bSs7xyM z=h@H@s~^TQhN|WvFyJ2Vje%uwS=w4SS!120XlSY>wF`;+nIf1Eb7mq4`L|v!G<}SZ z;gI?F3*|f! zskhKfO20g+yu5n|40rAspsQ26WcqI z(rQTMcX|YbOq%=#SsgKeIV7n|{m#gEqfOT^YmbL!Kazz*OJ=MD9t2*EUGkFd5&$yb zofcf+^t>IFrCRjA!=dy$zl!8Srj8$?%JRj>rr4deG;AYnKmLS-%}68={KE!E)bk0j z=vMyZq|p$2Mdr3XT&9ERL1x<^C5;7`xU#pMdvq*MFD%?|ra$R7yXqn3I@9XmchW%|7H$QbEJ4E$+@jCeTr#v>9F#?PrpaO`-MflX-MD5=9l)0Ht~`+NBQ zQ^&P5LRcc{#1!Ro=~1hiop?X?z*&23`Q0>?o_;ni3&EMuRn$iMEaHP?kL6Fq??Tx9 z@c_V%-f!^iC}xw&gY>35ZFc6Itr?KcMe z%lGAYu`;-2IsNU+l?_}yOhwo&Zw9w5B*SN(<|G$1_y+*Js*t)@j9soKqkVH z{E<5oFbBT397?L~pA;^6F_MUxD82bD0U1p42VjnKt}MbIt*h_WD4Xs@@Q>_{)Mhq2 zDUp#R!BA-_dk41@Uojug{skok#*okZ`z%r>Bj^exleT1kuR*KGO(Pdt+e5>7Ng5XQ zI8TZXl6BNcDZL4-TRi2oItWKzVPgt@3>Ch29=UQC5eUugv zzNVgLFE!Im2rSwP|*-|{cTwH~g$~zwQ$eBqK*9<4@p0PyGAr_XXp5ckIGK7FU)( z%+|@f2V1IEpB40%!$TAt=fDzlw1f#U^lNuVv-1ARO%_yBPy1(P!Rp_?=iNlN56#}8 z6BC+OEq7V9;J?p&s}M&ilDj1*Dc9H;B*9BpbzM#jm?AWhF0A43u&wN{Od%h4l2@-l zyGZc*TQNRbSy{Qrm*b-RA%>71sqhF2rLlz6oeVKpY!{~#f)D6P)&ef<@YNR?+(S^; zUMf^A?|Ov)Lh9uy)G)Cg_Qfxk=*2RhEKW>hyqN0RrLTIMW`92wg^H7v@9L0CHQGiU z{&&itT1b}|vZB*LJ3G6xq6~aDlL3(iu#9)oE^1ix^`^e)^1;Qs@9)7bZjF`NR>2h6 zw|;0a&ET4)iPNz2sRuu@4oH@6>!SX!a%d-M6?Fo}6ri8pfZfLX59}i-ty=h?~}YZQ4l6p4gmDrM&YUHNW!r>^sA+)IAQ#> zuRmYJeZJTu5Q1-04-^2b60|IUMCCQ9HQfz^(9?fPqNN$EEG^yh766MAu+#mcd%ue7 zPL2ccx2Z|cJJ!j@r8-Lhk>Yd4q=StA-A^)zSDR=S|Hb#%Hl)I>3;;hFJK3t%fD03+ z&Yj+Ku(KN=gGW%`Rh>VF29O%G=mHw|N2^r22-yAldL)-rl9AcXc=n3Ha?p1!fGnCU zzHU5xeZM6}BS_qMt2Q|n9lh?c=+-dAK=(JthR*;4j8a(R_XJXBykeGI6eFL>Xo%# z`5RE>SqN@lxMM$-sRr-z1>>TBf&`enwQ@N786j>G6N5EMZYBvfNVG9F^gQc}_+2ktKWGi#BN zsjq$Pk%JWg|Hf0?>0JW%0t4LD+T`s7Pn}czIU3UDiD$0JFDbJ!(z~&-v9h}ve@Ui5Pm@NTTwXdHUPS%F51%%TnBtfGyRZT)iguj2DVGk$@zb+x{Z+wbKC$hh28H*HVD zEfW$#Phbujw(wGM-8tPEBn6iqML&Gy1;vIGrxvAbSC=ktC|C-BKq$NqEC#~$-cTj; z*w3R-`OknBt?E7Ag7X`2D_l&^@#kT@6LLmclc1?wWNq+eJimQ|BLbKucELX?Ue2(0 z+>h8*8fE)$YB<-$!Y-bwEB`a=J%eHSKgf6m#8rR-E-5D}wR)Ftl-$x6&XiJQ|M3{O|Enc39scLUkh1k9xDltpGJfD!yphe8mEpn^KX1C(f-Ip*%xNwUC^&p~pEnz9erAEQ3uiNuA?}qYMdJZWT5ie848f?bO%DfQY>(wk!=gbd<*Y69w(C>%F7WSlBiGpVVhce^Vu+oR37<}Q&p|H3G@b% zdj`Pr4WG?$igqbZH5fq)gC*NLpnymMRU^B&n=!0-58!%Qq z?H^i2K->m?+^}1-_rwFge}nJOOr|TYR5GgQc}`zQQu+}(wv!a9qC#e>5&0ZQ%bY)s znQ?85WOuBb4ooxr-Cf1ul0xrfHEi7eMEp3mwB&z(U5$)Eq{lJ^em$@m?!JPxvTP>w1%II>ce17< z(S>S6^-(4S$cP8pPh6uFbJU*Wd}0I$@Fo%-0S|v3Sm*kU}5k>@0>zXR3qqi$Qx5dloXStwJrV zs!PB9`}fVieF-emLF10|u>o6K?0+12Fn5ZeFeU6St@vH~nxk7H1SvAIJB?J5S)x|V zxdZUWTvvF$oL4xGv;X)e*<$Jy8BDH(2JSN*g*c@vzRJc-(!?hw5B;G*SE~*z#(8f} z=2IjY#t>JupA%AX2%!JU5}71s3uYDtr5-gzUP?mK@EkB#iP_shd@>C(@+ScPp#<#j zYyRFML6agEUSJ4WbM;P3zo{|~n6l3LkB{e6Rm@>mLsyu!bpCwqUF+?x>Y_`Z(JE?4 zgC_ZRE&&?hbpC&|x6?DALH+*yJEYTbda}Qp+Re0~@#t|4%&ALdO-`z!h@vJf#wJBN zsrg*FJS-)}r7o#(Ia0%OsUg0XHBTqABagDzrmDi6wa?{gnn8DiK$Em(6tYgrwtFCs zTRy+6E$XIxT#(E{A~H!ZUfDbZaLHvqkE_pN5xL|`pPP=ntCK-_PZx3yS}i(56Zth! z&Txt#YNd2OzrNV3FyZh2j>x#Qf z?V=BUqZ15J;gJQ|2%7!+vxny=3Szg~4Lgbhf0>U*mjeA2aGOF=2FB>uO2#=}f_kXf zcH~{c`nCThaTt3QQfLlIH|qbKH0>sF{ZLnYOW&=`5U%B z9d~d$|I+@&D23L0ebq+lbGv^R%4yy!SN zw&R|xtC6HRKNny73$g^N!W5SXEy4IfJUk#hSW=$SnXChIYsHWSI!?tQOe53#+UB*T0H|R7jtC# z=HU=1#UIytqLSI)30f+&67X3tLx0nMFRY}}0@KkUGaDR~rp?V9RoU>gAREsWYGvZm zbt996$l1AL3m#WzX~-uQO^6*=Y3ru!%DDV(P}R~>h{uIcGbAz#D+J*s13*IZwpou0Za#$c=Z>6q}yV5F3^jd`gQg7ZD)F13u-l!37+H8%VQD) zGmIT~AEiRio;-xk{nvoX|r;N zilAicy26*k_Qv1<^6C4`Bi@IVux~AHhI4@bObVh7ZXGHYpQ{4rlSy`tOhxoyO{u4$ z1JI#koe0cIOUmiZYPLMIT^kMu(4ZY@YAUIEE|E<#xn5zcDeqkVp%Pw@85}sv6i^BJ z@#8xeT29a(z+bF`ttxVonEhV6ib~2Yv`H%jXt~G7c)Y+)2IVyJ9v4!&=i`M#f4t}j zpar$boofxo0JVMDsTT0e2N^0w_IyREk|E&#wSY+nG&SUwmb}EqFea_M z&CMwX&xr?s}-t&IHB~P5H%!X zLzf7}pl9nrjTU$WyaKP;MpNWE?|Y%K@xej*d`E*pKXbYs7Fg`&%tD*@$` z2=LQsFeRV1tv~LIyJ*wER88?*>(R+SjeJJF{h6Nd^%)ng8)}Hqp#J@mnUl%p(bFm4p!|tD%4fu0L-|r9c8+2!El9a+EMDY^BynN~tMT zh14%>UO**56V)`bWU(G7u zX{i&VyZOk~u=4P^RaYDYGR+oXjQ_l^4e{GHZ#OCwh5F+{I*{WiUH}^keTrqKXNf<} z%KHnZclpAHBdmWM8u=X&iX{8*{UuPvFI~c@LS-soOC~sR6*~?Jl9cqWiUM+5gG?jG zoff}pnb%uCcj=V&-<8fUqCoHhH@1kG6kc9}KVoUA_biKQkeH(6UYL74jp4$QP`aSj zTu_ynS8vQr^C6Ja%GT|i$Z_0QAHIKIJk9t5p-<59PG!9JP8|0<(=3jomy}G^gLK6! z7FR|NX9^py?!D%LURtNw$)sxLYh^;|hchZZYS3f-qg|Yw7$Frhg{TiL|M7XGH80Cq zortCA0332A+E_Av|BKzy+Lval&Ypd-4nCK@FC*6y=!we=y*{~@A2!IU)W1Mp1!RJ` z`6nXmWp2aalrcOh+@2@C^wlLF{(Sho0r6kw856GejtHi@A^DevizuABZ>cE+0@{vr zQbuQvJFTA9&Ms~mfqz_tI^2N-3x)HRsaWU)T`9`5xVTu@^H}^lN?GEEmoHzMJAuW~ zd3*X^(-oUt0jt{2S>pS>gyEO9_xe{5GCrg~PGm9@1p8J#`cR{OFfTbM>mWh!QkWHg8xY7Su$>-gr>l9JL11i9Pp zE&_NucoA#}7-6b9XGdD%4i0QZ<6bu#rAiD01yZ;nKq>$+i9TFv+XK|O*#nYKpQH!A zvMx8B<%v2@gB#8e^2h>CgtPXEjLFaLIRmfC2wtFc?EzgGm?#oOx%~**tr=Euu@$4` z5>A`wdw7td(A zcU+|~fB;>95MB0hw$x~FXvpVc(g8cne6gAAu>CI9W9er7VtN@3;@BXYkUusS%SKsH zV4j{vde_({1X%UmCZ3vc?IchrL}H=Y(}lEW&V`(D0G&RG69p5Bn8+OeqSx=v)~Vy+ zD-adDaM$S<|6|}rCTog!b(522?tqKPZJl}Dko-4y?FZD1w8~+=lHZ~EX@EVB?(!Va zautL=TnNUFCsf|)s>&ru)s6RgB57-D=Uiow@J& z*Uj{zdviN?pUZ!myFGI_RACZ?vZ$2z8yt|p zLB(t9O4V?6O$pv(Zmzj!q7f$!+uegLpFWvcTE5OQ;pOM$1_X->zj{1_gugX3#K#W1^G65oLfw0WyiPMh zC@qjU?LOb?Q?n>KRDQ2lyzd{Y)V?8Hb!1l^Mw=soE8s>bM4bEA}c)$ zXW&l7gsHgUJ2T?IPhG3S3i1@^w#0ZRhb_~vlb$V)C9nA|94As<+wi*NRDcqm^)-LA??8mhdU&C%%qrk%BF`xS3DMJff{EtY$mB{uc>8eS_h_2| zFMLI#1fCopH!?Gh+J3I-u`$1*;%JhOYoC$}m^Dz{!n=?_>;VrBoJgDs6J_L{4gxur zc4OU?4qP`&GXNUrh54Mg;m@7#dD!%81-{DKGEV&f=`FPi{J;Ytes>kmdA@!+QK%bg z8l5TN;ydtlOY;J&mrbr88cGljBMR9An?fP8Mq#0K9z-%6uvs--h#(MJ4z%lFg_L8P zWyjB)R_^t~?XVX`OcW}bQV@GKrhv4zMZ~wk1E!zQsdGkF;G`cmR64CIfc^dcMmnR*Ku7IxuQqgs%Z9B`H0+3 z%=C1HiNXHHhbb|Po5wx8#a%)WDDcs^dxj{$5()F#ucmTbysb=R_)^f?sZ8ZLr!pOc zhed1l+R>rqPg`@0-96G&0m4gKXT!YP4w5+NwRrOe*%K`89uZ|<#$PisMF7o2+qef%`dKaDa%Jkk5x%S3VTzF^;ScZ zpFV;7ns+8O4|3Tu_$YYMy+RdDo4B+ST zrE`)HI3Ns#ZLjJS-N=~vr=K?Pk~YS{Mv)M&_w-l0l5%Rs$*_WNsH+E>GQ=Pq!Oj)6 zE}#XRCcskx7W1<;TXa0OnMs^Jml@!1EZGJaOO& z=arLQsdXAQui4XYrUDh~QaHRCjfK@$6ch-h+D7lWj6ThjyTqv+Q7g!-e<{_0wKO); zO`1ml!v>>r3i_P7mm?^NpO1Y>(XPSpoAKWd)M7JRS2wa=mvSo%*uCz#idx$1gKiA= zFdt@9&JWjpVvp_K7tcYNbaS&6hTkrCz9vK16BP7whMOnXg-1x25Jf}jIUTOc?!*pI zN>NqsrRN~issK1;cS5&=y;qkw@IsB9mUSO+AAmvD%U3nwYnz&JmP|Bg%jv>`cR?6m z^R3Nf;@;C@qtKVnBA7>SHFYxpl46>HW7DWTU&rlkeZG#;f$?H5r!5cV0@VZbC;?+P z%RAJ{GUhn77H-?S9`|%U#Q(%$`J1N~fA&^GfnFZ{lVp;6?aHtI{z+O#{A+s>^B>

gL#*-_QQ-|L^OU>;0PGIjU$*1sgrR)R8S^PmcdE>^BGXwlb%6 zB=9qNR$I@AdbK*)*#ZmVjcRA@_oiWe?+r1IsY)mnZ)+Cq+hGs4=}HV5aM=X39|8Hl z;q@6sNfAMEXb}xB%(W(?cM#p-#72vSZMB*a7M9dpYWA&15`9Ark_xXfsdH!T+xIGtdagaobEJs_ z)qdYH5yi{6<3R|eCG7c0kDdvX^sSsDAPO*}U2$!?z_=k6??*@Fh{W$YOyrVn!v8fM zt|9Z(C>R(8h^STL7_tYKiDG_pKKl=oX=0+{7q{GEx71sKHNtO4EF#_a`JMdO-pq78|e4_r1Wmb#ua zhWK%yg9T&)w?F;F7_k}Z2qgzeVt%5zW53q=OgOq{%>8h~H5=6I`BW7#Zhe12sbA95 zGd>;^bVl>+-Uv_RB;n`s@$o&QHWz2+CXLBu3YE<9uMie?^dJu8ET5IZEYth=WvNoY zfkeoCKaE$X=L~a7+62Hc@DWX%{CM!1&pUPGwYHk=jA0P5rde?Lkx`c&k>;5|D1%o4 zNqktZ-B(Xqdc(-W4-B$A#!|m-YKKe7LQK3T%e`bB1pk zeX!WV_*&n3uGWT96@RKby!byQpXbUZoIrPFtlQxqY)rzo_y^uz{;nf%YkgaN+Q`JQ zp(w8Jjs9%U>1m`~&R1SLwrt_lR3)7k3^PYQ*MU$js5YCi@Z;}SLXpp8U}kUdJPgv| z_}e@&xf0QS(qg!-C64P%ENh}-`3Ho4Q6rVhw`FNxGU3%&I!p!FVZonc*%!^KEi5cT zyF|_pB8g+yTMrK43bg-`fxkaGNCV_OgY=4Q`%fIM34k6?tgST^?b0U6tff^K)mf%( zkj&P-xT;gp+S>7g0t(o)U>xAsa7UrlM%el70*R!ABcN*3^JXx0;bOXSY8T1P56N6K z`>Oqp56G`txm~^-%66Zkz>4TY@zkuDdjdGXE)>&h^hcU2805sXpuEJYlK#NxsZ!tT3K$Jwc@ozdfYc znV8tWxbq9Au*>`XyB`kko)O7&s(nQ*S=$MLR_Rvy4hw1kp6%1&QivR*76`o%e!Lzr z!{N(Mnwpvdhhy$9xWX+iw69>?pOds4dw9{KGF`-gHV$!?&0?&Y?17acgJau!Q>NhGsG>8&z+F@JA#ih_W zyhuI_7at8NEj^vK0X#V(Lc(q>ru*pH`dXRSHFgMjnlYTGfI{sCGEzxo@A%{sn$gM{8wDmbh)>w^FDf&)Zrt{!*!0vbI5LFPUucqa{ugZe4%7beA-=R@Pe(h`H<4p%L(4{Ovfx#KNLqZ+|nK zSyCMZ{OHSBRSGTVxq6{*X#PvHhz8Vk)7GBV+W)QXIj!oleAnbhA_s28vP|DNI1C*w z3K1wS9jPb}6(zZQ4&#~bPLxo_$&TBqSKvUR0~t8Rm^uHNKZnu|r}1vh*@am|9v`oR zac4TOor>XHI6gEAjpgf$&D#1X(o*em!=?`vOv94oE?zO=m62;1lclAV&9+)`hvT9g zsY;;P+&dc)^O<+x2cj2;qc-5T(6Id9|8}v?Ex=+~Svdxg>vqPup|0fbx$vv-Acgf| z%jiOIpkK~A0V5=SGj{B0wZ+1EhK$sW(#o#4a?DJ^Cks(OW2C#&AWfAwpj&lJOUzEA&ig@>C z@7GvPM2=ph>W;$g!gNQk%h@e+9ECCXJZ^A~@>jvI7ly_z0H*u7&UpS2+7$}g4svPn$n&L`IsCxEBS4~{8L)N{9g z>2|ermwB5P_PLsJKLv6KZpreY8?XZ91Acf1$g6QdN@h! zx?}<&c0War6BJP@G{R>Wz%zr{9n=-EeFaDD=C*G2R+j5J?`}s7bQ>-N6VgjZf<=OU zY3Z;FZ)G6uwE+*^zcnM+xRSIarGTWbrq%Q$7}W+`pt@*CAhZY?CHQ%&2zUUSr}A~K zsR&C72*(j^6a+@26(dhTNjr)FpiiCx+kLVCaC6UAF6WP!UcI8?E<8W!qsrp4>=+dw zJ}K42vHd1~KW33;VBEe3K9LD<1}`sZGq1={r>}=nQfh?HsQ@)N;DTC zij9~5RIJ0{sQLDxk@A(jebc(*pPD9Apx73TxV#!n;T+gHqhAar#Tq$FQdc&iFpjuJ zc3+R-uhex(t%#98#M3Ej+)uNc>b>heM6h#mI;wthcIsB+rS=ney6>^hZA z#zvt+WTLvAjlul(!=jm+aVzR#kLuS5yWDSSU~1Gd-{91;mDM7xmH`xANF;b|J}63KNDL3O8^ zDSXkn`6ndeR@>y;iDzqz2Ml+XA^ zPuJCA!|KWpM8xJF6sy#0Nz5AAz8^|K1o4B8Jle?(f)P@^^Qu|pG}-iM9W^Ghn61;X zFlG23%3%>C{z*OPf|SzRPoO5FDiZpF~=+wrfgaV$p7u5@J55D-H`!#ttK@f`BZ z`CBZM#60iuz9Ouez#t1#RalWfGFV)Bh;cM(`uFdTXgPj5&MLG(3IM{$34?~mf=|#f z@st_BKc}ja@~!VB4mAOT(#-b_c|b}$s{Gyp!Vs~-x=xI((dMPG68oXps7LL0p{=d+ zLz}bVFw}2vX!W0;yooAkF5BDBFn1&He=D4T;r3dk@V z9UUwIfA-2j{p>W+=OIT0d^qP#HvLsv$Y+3M){-ck$ph!3=fuP=^s(xt-wYC*p&0&y5!|oXhK3HbJEh zD0tOt$z%dQ^)*sxKvJ(-&5~mSyRq-%Qk#!(mXtf)Ya{V)V9^UO8hhZx-3s4UGl|Sq zQ0I6S=(6w@_^qV_o55Y+V(#+y*EHN)MW7X7rD8%b=6Qg1bT1C_YG;`qH(Q&li+}rJ zSV2jx_jhD!*Ed zuR{3F%$7Yp($?7`C3SULce!^@`2UHckr?UY<3S-DD6}IldWmz7&b+9A`zMHdX7mYo z5evMGfS;l2!R7TON|Ji-uP`eA3275BupS!v4@D5;=W$&WX#(BDc~P7qetiQfQ=7Wh zG#4L%IP{t#f0O+mMzzxD@!wowoV&l&N0pdfiHkUgY2 z>n^q3WmCa?(+4(I8krMZk0cJ65#Hk3;dUUn-2YZv~X z{o#Ta=&;88GYxP)w&)rda68QLMD^HK>w?3|q9;=E=j!qFJDA(?${i{;kcjY+VgmnL zLoJc0YI?{9q=)le`ZJCInSi|TCwjE<+-qK%Fbs5st-(}7V^O>FZCJ$lM-C_^c!b%0 zmwJZCF){-Bnts%{weFW+Ce8LN1@n(c!;swE`bfz@v~vK>Da3WI7Jhko8CV)j$_}KK zfB6maNQpHR0+~T+S|_d` zgwH|$3*h%tbLL@mEyQ_WA8!#wS?p^B)V*-ScyTa z!BAcC@c7T1aPP=<$?%(?mA?WcqjAu0wJK`*4)qK7_Xstq*S-$G`Z}|<^?u1;Eer>i z6(1ju{Y5T}D5RL4KF{l!#H(9O2t=3>+c3Atz4>3bfXkX5;yHdv0eD-iYBJrXW)ZD3 z&}bYz<{uyK>JX_RkWQeudQM0P3=NB9$;rk06Ebz1!hP^HEvIj5Bk4jdh_KIKYRp4cI{p>+q6-#uH`s= zwNafFFrazK%IfwyGo8HcQ`PHWboR3owX;B9yNg` zrl;FA%mb65%N<2^=dMs6uzlTL(_Dz)XD+P>wAW3lz5N_#D69U`T&aMI>2v4c^A|wu zvpH5kjHA;sGq>vrEFSor{Ownae;&9~3422DK)L})iA_jI0Fv^syic5Rac#8X`50Os zHRRH_@LHRqXP+5v-n=R-N30`^1qm3fj`tjggb(TIsrt&pAM{xG2?L}%4^^>-rXOdV`a zcMTAiQn1m`_;^yhCf9@_Bf(&r_XF*-ZwnL?x6g*?Y^2>1Y{(+*QXrMuN!LLl! zhGK?}h>2NnrS`>;WuHfdNvbOkQT7&J)=>v7W(oS;o_{5hMIf094%SwdL=Uy+vm*}t zuN8W99urR9r<`rf>vj4!Els4v;WktIpR>&&4(vJ+==Im}clF;ef5|sd9?JTKPl5-Y zKRA;{#}aByDpo&FFRPxOoz;B5lErPKx zPPJ7W#sQsa@rbL(ke%@UO$socb?4e&-!oVOv9?w|50QemlVOJH)ogkSI|Pc;TFR;i^;-=78vJASDHoXmErLpLa_Z3U za<)7(^MfocU|Sh<_`d}C1JG?=zd@7I)cV57 z5gxkjEGGRGB;MWp5Mr4&)1IGS^z+11mjsL4ApMn?7>wfpm!^CDgj~2WZVX!^2rYK56L3Bqt)b&PL=ce7Av*^1%)&tm z!V9!{kDxJ$lrBXJh#DY>;Go{2lHIWJJz6>bEg~%&_!XGK0rz!%H9`Xa=@49?!lC8B zlE4LdCOc<0sKwof9Lwwg|7->9i23@5Tj1j@;`O~9*uK2ykvRkguIylne)N5y9Sf54 zcKiQF**GQt3jJLXP?_Qj4}wt8uYvL3m+KXts%SzgRsMLohQC)NG$Fu0luFE>$9ot| zra|?Djua%C&A%v`4UMPkH^Bwws?pLI(VV}}e9fHD@qpjpT94u|m^LdaMs$or5aG9dv7QPK^XfW2ZZDH2ajk#kUGTiB0wOi`K%& z5t;JShd{);t>H}6IfS$q(OCKT4lvLd0%lWpAdh<3p6^N8N#1!SeGa~FE;kK%4@OkiG#^sHKfbvp+1@TRFdMD&9A;Hzm3p4rtV#|TABPpEY z8y|T2_!!JvcMxcYQ@O7m@2FY6mUyR_IKq0P zX=VpNHZoMU;98~9YPGs7uXKzbYqA|87Fs|J4%o|aVoO6%^MAw#~-Z4(l0lXWsPAE{J>L&d^a_gye|{%>~nSeN@H4CwLH5z>cqMRc4gcT>_V}@(Z(XnD@)q1C z*8cKU3e z$uhDtQ)DnS;*l^iwoDIMN=AcFiL!-wY#IAb%Gf5#NJvsC@)ncEHnz%|Y?UTS(qeg! z_n-Ik@%Omy`@ZgTU+4VJ`Tk0@bU0pLdxeY{?7dsI?@12$!P7$Ue0;^mm`2}cx;3Url$^bwkBiG->G;eQpKa$lRz$0^y-+dAe z!d(0Bv0da;By=GO6bFU1>CG1OjTCfbCyz;V1!+ceCINXiHdZ&=2kJaMS&!MZfg;@^ zJAxGrUa9fW>sSm|!VCj)j2x9}1d}fD&)Mv$nHhYdxOfZB;~us=8*F)A^u}i~T4?6O zmC9GS(_k3#aIjiVECPriBpA`3nNcBA{8`C26}k7i=Q_K(62u)zclc6OtKFi#zZW19 znLs`EC#)SD#OLRS9i%BH7DtKIGv7bjTjrg5;>G&~R;5m^2jxw7O3;bm1q8*}z3MW< z{ryVZm<*(T2Va5yStIiDIx~2zQ18-@w8$sQ`}RpAfccmQejnC0Ha7qKll22!6s?fp zo0>F~u7p2ma749}YXAP?BRC4}0vXnO)YsmA=~uXT(p#GOTL;OosaIk6oyMs$ ze;!wL?sCb?*VOeL`3Ya8wl*_+8DFy<2sD-254Cl$@3N-#fXNhBVGNy)oGm27E;*F<VK;NrsrR7~4+SX)R@W9|8)^Oz! zn_Y1)1JY+>GT8$}y-%Ow1p5{EOGQa}Os|8&}qzX?Gh0ek>dBo%FpbKBTLus0fUY z%`q(>D5hwk%7Mgdr;9%Il-1S(Lw@Sxws08t^dbWPeE@R6a6|s{qkOq7@f{sjl>gwzii8Ia^09uF+d4Lq^4cNCkivpci;(gDE>#-| z=0|i+YB0&U{-Unptm44$y|dHPODZ){KXKqg1jWYtSnZ;20DLw95{PL4-@B$S{S{e; zt`|R9g8d7R#mUZ1m4WtcD6={_#lfV>tDLYHG_(;BqYmGCZP&O@U;oVe7j^Z7yPIT8 z(onOjrCp4;NrHxkEW$$%`}E$-<*suN7^gq69C~Gq%}t);^D1-FMYmLl|Mee#lJ_xS zLxURIXogAFJpAs`rNz*l*W{E=jVW&$`SJjoSm4$Hf4X;W$>Lbg{IVtU=c*wk^ZsaI zr{{s-(|`~1w6wADrBZnxc=WbZnHx-|wikWyc6BwV8od*iJ7J^v2H#+dX3i6@yL~w_ zAX#hr%Z}edD^BWbrx5@K5VaU85{Q&HEULcyCFG{rrKxpD%zAHXRln9R4Zrm?BR}7} zehAj|rD0w$jlj{y;YZTYaLDy=GISX^mg`|}pJK-1 zd3`<_6toWAp&tND9u5KANG=`nFppRLfpfmnj0X%&kOeW1n$}DSIL-3FMZLf4fVSA; z+n~?1Q8%F$eTPS_OnKGaJ!e95K4xNZl_Q}avEt|qEyVwsu)VUHU?eBuVs8G20qmHf zoSbPtl7eJFh4Hw?C)6cUDz~dkQ%WQgQ~F&xuJwQJJTae_5&?^O_p#3aDE|8EE6Olu z!uJiEWgd$7+A<#eIfVXoiwV9gTbEeyJ!D89Fdka>gA zinGhe{@{ftAVHvn{f#U+ft(31IO!&W4Uy+eM?QYU9&L;+X=>`4U)FBJ@vpIo^Wq8) z(D7dP_T+wtR}Kr_f6)QRJq^?A>NOvpbRU*;rd2E@*;+h?sT%O{c-G-r$C;#LSx`dP z$;^ysHantOb_f3ls?9Oro($cN>Q1Kah?*4poSTj~UfDUjXt+G04Gkb+fq|+@_-sfw zwep2$2TJH^=1>KIKPWkT97|w6N`S>f;K@~r16EH>$J?Xg40GhdpH(bpNuH2u!nX_a zGo&Q7ep>wX7zwYq=*+S?W2nbZ%bA8Ke%ho`1L{KT^9@y@xJD4shi|xU9Snt zjaxFR-n1~kh z&kY(&EA^6qQ04v6k+Mzqz=5)Mw{A2U#!u(xPSB!^iY+rG4&U0|CI)1C~*Qd(r?3^48M_cBdJh@moEwHomBMl7|Ow^2A zW+16$`C$11G*uNotd0O2{2 z1=Z?eJ1CY^RdvDR%Xg1yA%uRvwz9H9BfPt)_uNg7xfl-1m0^lPEcEnLQHXu+rat;m zAIp(UF{2Wd&aPc-9NuWv1AJ{m_iSfpr^`dX1<=w}@o_jfiG+-_(2Q~i%)c4APfF{3 z)67?cvjPH;QIumf>(!Ng%r5?QfLiPeidu>U32iZCL{Oek_0B|AAf`*|*7av0g}~*` z#9+nh<^H#fK0+ID4u^cA#!XaMo*93CIdyh(;cipvyI%qL)WgWFaRR5RT_H9qJD)ZT z(22LI`*6Hc`B1a%*<2-x;PC26)o7j(9{}l>(tWM8{w_5fvT&j0$&q0`mLrWUvDnUwyHa(0M zI{YCrZzyD=dM#hnU5VRU#vY6iLzE^Zm>NyN3j;Ui@q=JX}- zK`b6R5ZS*Q%kkcK1$bJd!rA(NlazwARbzq00O<+~f8N~XVwt?pv?x=@>GKb8xjaY7 zT#YCiRG}P*$HR{}R_SK7zmbhno(t=rD#ck4B<$?t(wiX8Ci58H{bC0e7cZ?u;3ZH( zF(z+RtLJwI285d3I2#Q+Ok(PC*Xi0JevqjR6=RHx2v)w55r za%;CRrH`(C@HZbi7l)0<#(UQ3XluuyrMAVv4Lxx~RV-B70RDBF3ZYGcvvB>Hl}er5 z*N<|9!r2bbv&WB@tL?j|1}!83q|c&_tc^>4=$7nb?F#7>3H&0U$|)|*S;%H*iy7nO z?JlLLq}R*T8*sj8=gmk?g2#LamFTP{Zb89eGL^j`d-_`uImirm95O|>~xlI_DuNBsLTp;Rsr zpuf;|;zisha1_tVHf^A#VBZtk9tWs=D!7Ht*d*7jNdFgUkj8k@?k*+-+)#F6Dem5# zSmQAWOfQ>k&s>XQKuP7#ORp$NlrR;OsX#dV4zY1L#OoMx0un+5z*ouhf5lg-Cz~}S z9Xv>h(cH@g)7hwJAEn`<$W-W&d#5K_Z*H8rb4PtM)zWsZpaWT!tjS(-FV5C7!+Rux zgVLMX#htx6<*sL9i}{MI&E?F^DlW89dd`t(_2ij#K@qb&j~_MG`$3gQP{=l>ca3S$ 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 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 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 zcma&Oby(Ex);0_XN{E1hAe~ZzG=ek&5>gHz0-}NvN=m07p(EYhF@Thmw15Iir$~x) zch|edd++<+`+45)JKp(2aE{?OSFCHTbDis4*E@Ap1wwpEd<+Z>LdCmxG%+x+Zed_x zhTvksJ2#2~>oG9sF%<7eBb*YJ6CEF1`9{@Za_%yjG%0p5`3KgaZdptXjb&U8{6&UE zFKhY-YyBm^7?9bl4~j-}UZ=5^hc{t8mBl6~#<+?=K9>C~ufVc(WYl&1R7R-nIz#-r z2Q3{P-|e;K_;1y*d)550EUOds!SKZxuk)=VR?xp-sAHZZgCC#GAE%&)^nmap0{wQL zmlt}>j|F+uG2!Qj^XEC>$Lj%8ju9`>ul@a`M*35!vi_H1V`E9MHCnifF9a0B?@njd zm7@~whc^uCYF^EC?o@hyVZV9v>|icAJS?ov?I0{EiQDLfnVDH=XsF}bI2Q*;BgN9v z(%$mVSG^gELPA2=(nTe6t~-mq#Bst_Nx{Lv%tXA+T3T8PLDcU813SKd&&|!{7qPKn7YOKu6ca_}d-|*HsZT$ZI_5Fcc zT}qj-)p!kja&xBb(W6I_C)?reog9JQ-rn%uv**w8qZ4jBa&vQAHhAN_nr+uevtl-$ z)6vl(P-^Y0x1B*|XJ=PbR0OQd&CWimbgRpV#RY;GlpyqXQ5lYFG6c9V(8Xr$bN zh)!bk<1^)>-4%Z#x&r+~X$OyJZUggWx1Sd{q#PE=WmZ*FRm)zhP5@ZsfVXa8Alp{K8}54$^2 z=dM$rFJkli4zt}0nd9BJ)_5{+tK2u`{i~~QN4JUFPPbM&uBoV~T;+Nw;I#huqhP=W z`sr)q?hCz{u;c<3!*;Ml9v&Xe&COsa_V)I2xcPb&mcPHeGj_jl!T-aD4_@aktd3Pz zG~dJxJ>A&c6t(_^Av6E8#ALdXt>!C5%av+u>rk3Vjph>nb08L7C*&Akd65lAVxIFR>awDQ#j zLdrWO!uxI>ftLiV#;F;$PfuKJ=BR`%Z!En3SXzo0y+%Mluvv@S-?cv3^tFP~6^7rL zc>DhS`|sbsx3{xvZ*Lb96Fb~qxIa~yB8+54r+_@^GBbn;mo!a50E^r470>-T&-QC@koFNS857yL(`GolUx4`&J+G9c^ zBUw4!q=~7Y2|6-riO)GCMS(?)-Y~kIt2|8-(8*aCQ=-)c5 z4AY3akQ#{@H3uv&zno0Z%F3FYoYd#mnGT?sLL%|uuN8u6-<9r;Sk;T%y7jhw4$n}5 zVv79cBb6kv%Ct0@PnX~zzzOlcMDwLdb#`*X<)%Y+A&!w(lYhqXJ^KzjKq5m(DL}mH##hPE zcXM%x?Zyoi&3@en4{)Qw?e%R}K56m;&+R=fBp|>{CWhzVB;vf8@$Ma7E?s}MmM6uO zkB`qInV7gZKVM(GAI=Ax&!nV2!{{Gvyxc54S-ys%vaU!Ck&R@7-SZVvUt?fPiTbR5-rLC_R>D8-I@$t&U zKK1pUGBSk1k)6>SASKTx;nh=@W$!H3`Yad=Ha1{qv4E`^iS%w{PDHSqu|n7u!rVgHemt+N}SQ_6aFE zM=>=zIyyA;px2p;i_2>ZTY9wK(=a2?S27N)@M(Z=qBL%3Co>BR*g&XZ-M4Sw7|Y?D z=AcmBeSNp#oIic~8$5)fB+D4{MO<9^iG^&nWGAt4uyQwdcZ-)V#l*#5+1j=yrLGx<5q~Iwzjso-oLDG_5UD|>wnjRcQ2Vr=p_X$t;gzUC9Sy2k{)+($;in~OiYB$`%QP3e}W6rEqQ(qM)3_FTY7uG#|R7t zJT`On`+n{Wqv_u$nCXp2H4oG=84%jq*WGkAxBWhyJN@&5vrm3Y61*wwh|;1tCg2x% z1^^_8yYlk#Cx<&swO}FlR8NAn>a8cSq={!|XYXgJ^!)ficiZ8E zHHCUT`D73c?^B<;x;m$purL%vUdglLl63{9lzqL{hG3POtyJ}lAue+3p`ihkg5Mh& zylS>_y>Gc63do>d{3t+wxVX3&Y_(O<%puLV}p?>pd)nON&2#WK^E*O166ZSgP{|JzjA}L4UPKE+zT>x1$FP zR*az`!i>Lv|88AV$ArT^vvnXMB7&QWdG0)J-S&lx6zZIws1foTfn@pvgj7PLW?nVf z8xS?|l)&Ay?N@uAop!_tx}Kf7YifqY#{LYX5+;^$aBwKI{a90zQ1gvxU}R&eC0WdQ z6T%$CK-T(e9UUEj%2+8--9L^g0q^abW9Jp@4RGa)i{={C#vCMV}evC#z@tCN!xI4Mhui^DnhSpzn}mH~MJAY(aczq_c# zijsOo_Nl)9%+T{>20l~y%0wa=_U{C|f3Fv1Vhe@m!KUwXoj$wsM%1*lr&Ga_w2~eP zZ8NG#V$Mx|grw=D&HJz~Ex~l;D$UlF`pU{};Gn~9Zf$L4?uyw{@Oa#8WW8*b+$nrr z#Cp8SUfF3z{98x7@Hfd;_E^f12p7$`WcIQ=@6HyAE7%P)wik$KZ;T$?9kyp?Uu+eU zQ)P9f9+X{7Ov~kh$9io1_DxTp>djOpk)fDkRZDstASfU}DQr0k$r3=ohzL@?FC;Qz zw1v$M*bEO4h&%P}qO|4a1B+kZlQ#+~5TdhKQi>!F*>Kqlg%z$!Zq-pQa8h8CtSl^{owsk@ z>RvYY;{QFaWPkm}jR1OFfZeZOWB3qP*7;#AuhmoEKiD$aBoY@F&#_^= ze4b(o@F;$xRMZ|LDk?mjY1I2J>ix~OMhf@~2@#P47~k3PRu}ki>=}4X-?jjk1$ZYj z3DLWwHPTfokDXZm;s>{Ms`XsBXTKATVxXw>P}`5WLwR=uqU`BV(G%QMh)e(!1kHM< z0h~khN#ZvNTG#Xq2nc{VpfffIhF)G?j9ej(#a)TFqaz~h<~kF}IJLX2lI#4%Xm0}K zL7~{LU%xIYD$37)e;r4_`q$^Id-0dCZI*|45l2TJ00`!n?3Gt6JUr^anMOzd02|cO z)<%1GczSpsWGNBH=Zuc$d(z=cNl8gzC??>bv8Ctd=R?|36er*;ky2Bu?9TZSQW47x z4-XFx4r&^7bam}*qhB9-Fm2}M+GF_v=KvN(PV6xyHc&{q?epBa6|)^hDi8Q8bn&8y z@D|``2)5v7Al?R5)n#U~H1ET{aq1MPsH)b%d4NUz=G#ww3H*t?dBR01p^HWdfG=oi zA0ZHSkal>Uod|PszQ1@*cbwoGgw1bPv6B;>Q1AI2mLJ$2y_@hj;sRhPEW8KF&-9`9 zhYvRbAmF@bpYDVYlgJoJ-=?UC%JV98S_fKEDvk1YNq;~lsR-t?l5t77(x zi0z%zqoOBGY3b>Cd3jUkoG@MASotxpHb(;M>4gIE#&s*Sdn2;WTaN?zV1^ z;hmnC?sb-S3}t~_fXSqCY;?3hzj6~c65!5cW(=SYa>F8e)e6h8_KpsK#$3x7&houp z@5yi0x$KxX2V9Gbi<|Bqfl#Yxa8EQmG?ZcaqBJr5Su}?t*NqzmknO|QX{5_D_)rR% zN=4~_**uu&>RJQC0Y*W@ez8C2ezp%0mqAr203^*RpNnsP6dDpVVlWs#eJbPUbz{xf zc(y$@TQ$+;>{R^V=erMrg(Tke0)m1trp3w0(Ad~Y*FDQ8wXKuZkVW4qhzqwn3Kd&` z%mEvdi7z*FOy&%B>m#_iEjiI-`nQ?N(P3>&Br;(IOgS1R5LT}~)YIE?606+TpTLk~ z!4Vg)ZEbB;48JZ2L0r=VaJ91x_;sJy(96zMl>^=GGD zl4qo5PkPeiAdBrC7+|T_^*B3mJY4D9C;|Wk91nmBvz{~@|0aMI$)fh=4<5vXk>&=%t_t)44YvZ z^028XBY-)nsbC|)AtA)BuCANv0Nt7z8)rKc(fe`9KRrKR+_XEz{b(1@`G&C@<1M{(jnFQvWy?Ej8>)R2_uP_t~ zMuT6INl#Dz7;FYm!mRZz@E8mX4AT_@2U~M5#B}ns^Mhz^3%TyT^qPabl}RD!A`wxt z$gAtp#G0;k;D=xp!dca>`ZSc9^))s&QVN0;C{8|W`tU&w)0v}l8E$U0oir>G2|2#Qtu%dZQR%Dj791$H2K4A|x zH6?}qnZ=tgok^G&heea3xyi57a>@1U+^#WXfSdU=c?m_NfHy3nj7k50 zVHZD3{q<=I6#Nl}_PKvill@OjkdkM^g)hnB-H7(V5J?}plw8% zZ!eP$B;IzM?TC*K3-cxhhJoPX#em@8IonDIFI&62Sx%#@f+OXpX9rdkg$!@YHf?kQ z)~&AySEziwrl}KdSK4g5A(O*$V_CFvn@SGnxdCgJN7L!s_3T@h7+PWuuh{CzW*q9B zvpqhq&1PTqgRc2gX{p`!2zEI+IY|HHadFO{pPin5Ir^zMBjZb3TN|L5o}S|Jjrq>MR+A2n>3%fFJZe=B`U>+!LSer3{FYmIm{glEwUZ3knK=I4vzKEUc^Z08#+* zMKT&1uIQ4G!aF)t!dO9k&BR)k5pJ0R#+zfqBVW?EECv{sHm$4 zn!qg7)aZ{EMH~C%g(hEVexWYg@l2X23XBOl{y35Lj4P__n+PF6zsdJ8^`&iv#cNXlGFv&px)}FWB}V z-?!^w$lv4ZnxM)$flSbCSWQwrV z5f}uIvrq^ep;KI4hX*m~J;FZQQL$uf9L;}>pkGQO2R{XcAthAPu5nX#+Fkpn2gVMi zIwJQ1!7G77zmRdh63r)az9%`svVZ1ri8Rt21Bw3_2ma_P22yF~@!#V}K~Yf2wDOi< z$!VFOV$v@E#Db}$xEbppiWZ4NuBjt#{H`M=9r$|?k@%b(W>Wt=^%Vkxb6r5;x%_-K z|G)O)ZQ5mtj4H!TEQE?ikB_eirMv81t5{uju3D}FnJWFEB{fY3ALo#Px@UL+cqq8_CaF`#$@zHWS7GoOHv<*OMndzGY6ZaquivojvltU>yBlTLayV>>pFa8&@9jG74cTqa7q8WAJ@3V4eSZHN2Chgm%CHp=1FYyXHCc*Xm4kaV`|kLc zFeB97>i6&V_u{$pay649OJC!ICH==)f~V?#_p z;QJ6uKh~*!VIpi@4gjaOTZAHO&~F0QaF(=NZi z6TOuBVxq>%WjYK;`qw3{_sbV1oFhieJJ(jr%qJU41|=;IHOxEcgW2N8ubo}rYl?|6 zbw1i0OXA!W4Lp#rFI&nPA}who66II8ZcsV$>gyv`FU6ughWAOaEtk)m+wKPq)<^~g zi6rraWbu1f_z_#a*tZN5Tyyq1?Jtw?{{H72f5$rXGY=o{-B1Y9R?76a2G~ zhPAzBV^uLhd1;Vv(8q?0uD{x^%DA<=IfWY~6YPHNlPFKLg{hROF9LI;N!C zFzA*j97VfEuihIiU$t#Lw5YqFM^!^EaQW(>bHdkJn6KvvLmWL1d&wQuZTGGZUDW9* zO;c|!1$S|F(~U0S$(X|Uc5c^xezV(BaIth#*WzWR%Fve@J%`;FOem&zgQp6JzX+SWG=FS89riU;`Hc|Gwdj-KBKNjE)HpaU zjF_z!I-4A~l5-9(G}eG6Nv?CPm`M_!EwjYYKKb?z2A5oCdeD~KhwC#^apU2d$A|Sd zs>@vbdp0^@bas`|KRe4Onr5pq`;Of?B+TUUfA(~)uS~vwa|_#n)?;e3mX`B0P6-*a z!n)$HFSkzX_^@i^K)K9sYn{sKef*cLI$c|_nzg3ioANa+G$Pm%*NVzg2eW%5&)m$~ z{DPka@-PO(dphlCTP;{dFYEYWg?nPjY{y0192Ycr({whrh3mE6jZ=?bAu=Z9e44Sp zyw=!Zp*Zv-eNxyZ;!LCAwo zPmYMFZ&?Flx;AkZO%M?ong5{pDG<-v+S-6Y=C%%OMQ8|~VY~!O^5lmviTBM*;Qok zeI%jAa=qP5C3v(Cc7~hUOqdt2BbiYB({=4yy!Ij8Zhj^6%a11+iCOaYD8nz>^{DT; z=N}T*;nEQjEQy)n7Jdj@sxW%)v>Xz7l!eR5;r-dWqI_&mD)(zn&A+t(O!;cE)Z`l5 z=Q?nDb7c(Ja)E6F28)PFsJ*Q%4@ztF^iucl^D{GJGmuhH#Ds^J{8dc2dGjU_5fKzi z;yS$3up&Bvg=)KWYvSQY;+9xG z6CcX3nHe&S!lk=6yfM;8&Nb#OMl2IYIq+^|5Ra3iL9g%E`UUrBk*0-*u7?J6$=*81T+l zwV07$%g1c&_gHMl(UHTqoe^Gs#!gBjEQalmsMzcNYQ!+9`aLd+*m65mTKmYQu8knCNNK`Ac@GrmG_JrrV^7|yH~ub`&sn0!1p zDLmc%ELY0lcs3mh6;NpRj#gAu1a=<^5Tv9~1any0HI(m$)EF|M!O=Th8l$w)xD3xn z-t-D*6kK004V?MytEv28$li8A7>VCr73B6a>z<9Yw2=?7|JKN7ud4iqk=-_0q-IEb znIK+E%h)ZNj;!Fj3^s{nyfr4ZLdS#a z=L_PG5-sX4q@T}9to~WuERy?-J6rHTj{TbW?Fx?h(OeAlX_5nnrFNvu`TTT5Dt#FB zv&AU-0;3Q1?9`1_d#kft&mYvMeL8ojsKZBfJ~jL-DRE=vK-#wKFKmIym4drZ35^1yzdpA3jt*=#ZHSN{&@Kw_^~^w zc1$v@cFP61U4*muR9vJAh}6RWspG?6?G2|Mzu`YRO9Yrh1C1mqT)`E*#>s00Il<#Ln>)+Zfz+m zDfwNZQR?K1jf;aif3n#0zZ0~%-@nlu%)2YB-)92;hV;3sWRb---6C^ zNb_H*iq;;|ygOwsnD)d`I39tHYcU_x{cBH2{X~7K**GH68Q7gT;lkF=@!^_-n{J7I zSaLgkYU~D8H3pxoOF!2BuLrg)SRhExW;+JmPCX^Bu#jmzjO&5D%a~dbhWv+Ok<=Cj za2ay`%Nu+3-o%@GbjUbc9AvIp-}kw$H#^%=c;|T@e$JJYdVYsAa-Ct|B#aybnL8-u z(nbLRcx-H9yyoD{ggFekIMo7`{J`7xVOwv!(coK^K_}1S%~)TgVqh#58%Ih|Pxr5S z*9zdgaMs2JWrhjWPcLVbc!_OyNh(~T;k)foW5N}84pR1u?Ha|nab{S5XIZ&z+vPfi zSOGf|wJ&^#Wu7I6mS4?55zFM{aJlYqq)e@!d5!gv`3?Id>?_BlYZ5*NBfYvcyP={o z&ShY}H}T?>^2>o0!8-eW+DlYXQ!C;HS3A7Wxg=@iVnZW!U>!q7Mf-=}Pu=xFW%!j&PH(Ijc*J{YfP%(T5FAc9FzZunzmL zv~-b{D}S-S-(Yu_TP=CX=J)v&S=-4ywtZ{89psA z0EJtg2_tS1@lUb=P+>v6jhcdjO_TP2W06BO!U*z3XNKZ`C#!j)cdb@dAY1)Cb^D!; z)7dF`!$hIsQjO;XFt+3ZHS)L(k35NO1<7H^E~m%N;FapcasEHJv44-)JK{bjrfgbz z^?TYQZCJ6v{AwTKhn2QY1}?b$CcszF($cc9uz+%-iDG1UnA@;UUr+D0q~t_rXVBjs zZt?hF3-n1Gj%PU<*W?D2FCxGHecF|J7FIHq*ZBG4&yK7rC^-SQ`KZ7ApcpUowg2>( zP91?Sn<=Y$RgUG5ky{sJWfkPEiT+`D%V%Jv_F%F7*`okirB z@UdXA@3u70{)7YJM;O2J%j*fD|5wsTzw?qzM*d|x3CG9Rd-c#x49&&?<3?eG=-XWs zqqPFDA(U7_A_WqbM&$RF7FJ$fjZ>@Fmm@V0W#0er3EZ`1#m?o9YxNaYH;HL8QDj>7 zf^(fTMpW@8y){`lKGa4Cfoby_p|16r+1cSoSx*Dd#rNmWwV^Ia+uPkuuN46$G$8(= zdire>s*P8!`1P=%8Hm^ToOw0jtm(}`i49X_VGnYw*%#~1)#ej($BXdit0f<^C@(W# zJAsvX-QLQl;LF=gp%%*rB?xZd%RyaYP-dr!s%#(; z{BRgf^v~jxEaV5N4m7-BrJ+u6uF*y;Sp1wb@l}?4xW6}H$s}*Qw5ZLH^ZAh{zU+)l zP}&`v6M156|E8aybkxuYEh`fgdD@XInJl*WfAQ!`HD&9>=zVy)x7vrx0O(kAQC#n} zpHFH{Fyq&3RT>d+O2!$bTpWL5#qIm3e^?!*@db-H-4e)ywLaP{v{L51t8v?HSE~t~ zYH{6NO;lSUlSx6*Pn&apn3~Wi2D%J`{(FjaSA~_Nj(+>jCgt9Kv6tOBJfxCN1qYW! zbWVbE`Gm8KnyiY+57RLV^SLd!4s7q3J<#E~mK7)dsFL7>e@9|7@sE3CzF4hlg z3ew0%R0R?_+rg^krKADC!o|r6%GShpIviJco1v_~zttrP;tsOe)H`5anj~*oeTZ|C ziDmM%3Eu|_KH(#0!2=-f?w&BPIa9u|kti43fR4k9Yt#c!2C8ycQL>mQd+`I3zi$I9 zBA@>bWY`0^SZXor`_YC4T!u+(#A+@sk=)#YI{B1Bk6#~EzB2GpV6V)m=tts%kcEVp z=S$0+c1IfWGO%0ydUBc1QA4?sPhdn?cd`<{w^1>(l*!>5)DA!D{GB83MBB=Ae$1I` z;2T)URc`9Yn4#17b7-T@sYjyqqTn&koxd0Z+8OYCPzIe@SojI`5h#Vyh&hFH&9!+P ze*E|m3h@^ycs_ssZVqZ>TeB<0e1EZ-zL8Q~cxOzJ_a6)q+76pFc=reiS++$7Kd5u_ zDRR<$?4oyiz|m!}NW~x*8BMN+KtQ;_KSvtrB9(~(sf^>wurDzqZex4x?w1qz`o{fdD{kk1hk=C1WYZvN-m$5fm=sX_>d}B4Otg2Ft7vf@InB1H2 z3`QcuY*KRn6mqsfCb+$WewqBMkfUJxq{fea@cX!GpwItgL6OluzV=D{toM2Dz8rd7 z?RFrSqsrIxP4R4~m~!x~W`GoKs?AhfqR3X#ar{#npZ?WIs~_)bYFq{^aOZ@PCE>Ww zbGh~Bx6d4we zq}d7n3)&pDd-oz|Mxp9cUQ#l(;iS(^&JYn7C+c~64Eo<*f#?j>jT?_zmq>o?U^ZC2 z)4U%TG4B_~5*pC0jhN3vZSK;#Zat4bL!upJ`ZyA49)xc+TJ%JAb4@;wGW0CG%Nt!9 zTi#Lxo(GEOARU=jwy;=%D#?a3gzaRA7MQ$Qws^|JZkPv)uCV&Z86U=tsP_qie(>`h&b{G>tNjgK{B_1{I|=F4T5cp(q9^ z^|V!Pa}dqZ@xiNWo)TK>>W^Q%;QWAcb91|V<%(Hc6E)fAtLEvbyLHYEJZR$Bx>gGu zD|97*Y)1@4_@Lxx*#6aUZZo@cTKO99-*u$}P_CluN{iP?NaGAYU%t_dTDkl|ZGgO> zs%L%U?1q7;Z9hsSZV$x(zPy}4hX@hDqe)6u*{pW?Bhm2xJ-JyVneukn>Wm z@>)W|s6$_t6L{9D_bN~*NnaI4F1S5oFe}4+my^Tp%eAb6iNV)|2Bda&xxKv(m6amQ z%yuF5I#TZ5CrG4AD>JV5)1Eg-@nGcje`5CxDVYo^!{@fuxQ`F#q^Kw&yzZ|;%>7D{ zTqYS}>?g*?%uZ&;%rh0dN=fNq=!%ct<6P9~xudmlqtgj|DU8S1M`V#a(_x#S@?rGh zX`artRYz=M_v$sHY+Tw@H1JX$VdDN(E{nX6V01e@)?MENJpqdIK6~!(-%mG#!4Vbc zmNX9x4E+2V+cbGcPHqLHXdrK~w6xr`j$r`rRc`&11F0f=X3Qhda_W%o@tW-Q>l>)3 zw2wtaY#>xCw}>w*vw!+Dt+3Dvvq?a$tM1ekAV@T;|Y#L}cK@0-@d);*ZON$;M~R$WGsdKwo+76>uPIrJv|no(Hjz{!7!G` zL?W^6$fT}lC8R@)?C+20949IIVVLi=9mQ*&F#g>@BQ&InLuePf!vym^219l;L$>WJ zL?bbF!=!cH(P}kdjo#d@Mz84W=(*6RryV*BB;U#?h`ZHuaJW-`Q4JGydszqZF@+DQ zu3PASrk5{Yy15<0?f@Ok zHV_q+)<{tgA}u(y07ul|EAQTIZEH*S$V#*Qk2O@W;;jEL@l>JZTU@A+Qs^AxT%LI* zdR#)&2z3o~cZ5N;!(3Ogu&^*3kErTuS0I+d!owGadNmPhNn){rFSHdEp&B$+Wsep% zf}#ubQIQ{S=A$9F-UF-m$UOCKLBH8;QZl@GjuiQ!LKHO(jb52~nNdp+NP0jlwl-4H z_2Y*y1A|xR{adE&xu7D1dPheL@0(HO98jHs^?>p^a=^y-AC>~Vc7dU!K4=kAvQc;c z8uFi|2V>aoeb+rmTaPq^uG94IUe@;cP9jf7{N9Mf$b0bO?fE7Ylt@?M*4)LkvyQ4C%q>4 zSL4?m^r$wsw?V*|1bP(^$ik3XXKW!*nFA>S0#HBhwsYvUM7@%e!y+og!Y(q@NECMk z4W+iM>>D>lVxM1DIo+BF<)|BG!=+{rNt*XTR;Qt%0s38?@9E;EjsFC@*l~~CM>ZG?$@Ah zR0%CSsrix2Y=Zq(vId#OaPnu|z_9W_^90y7DDKBVb_Kc?f8NfH4ug6RaT-1LuJp9Q zXZDh&KlT&I0KkT6B&9KW-5bQ*<;;a5o1U(&ua6JpBl@*2JD?MTR<;`GI)cJM#&B-? zBTr@lx{v=X-(q$eBZ-HTR{q6>e{Uu(orGI!SC^WS(jtf}p@}XpH@CF3be=-<{~_$! zZX{g;qPPg>8W~ximM7zDpvz%*I}&g2K^&bn5H&QM0KqG0xO)c&g_xP|)sia$LRoAjRQcPueZNSzAp6b4uPKE859Py$H$$K0{Ym zx1pf{P;+Nj*F=^53vKNvTgQyd|JXwQ+Wf=KJ{ba0)j<^tDEK3b3h)V*R9b-i`;$Gt ziAqyaQQ6+zo!vS(6J~k~P9T61V)CA zpk?HNrsi)@szYzr{QO-FZaj#r85zv1tbj5l5Gaygnm-Kk9= zOa~z}OXU5EuBi7(S=LY`#V1jNo4-Y z$h_&?K>7*<+g(Tv@9j5T;SQZTgDPPAOGsiFT#v$n1|M*H$mg(N7i&wO$k$&W z?Xe3_gI9(VU$k(TNF5Olom9}j$36z-i1ru*UQ613LMz{kxU)JpiDg6-)Fvly?293WBjnm ziBS4O=1)}%JR)J6S8a%d|GS2Aq9$R?r1SLM(Y3w!Qaw*wJQ1YH&>u^E-uiBbN<Aagp4^!J#}n{EG7uk-xiiztx63zCbgSLI9Yfs}Tr(eA!LIYFGW=1-5im{C}{yo~3_5^p+?h)6T? zsSmKRg8uxw)N|wK@XaE9phATZ5B%62+=cmh0KIlA!&X^<}Qcn}cvWFYjo7!)WuF zy!`nCf`^JTq;}A-$HDO(l+hr}ht3Y5>Oo_kvK2sd=f1`}0!43n2(}*3k&#nQF$I(j z9D2MFbGSf&$pI(~DJdz)%-@_ROh`<0cXRvYhb=W7Gviq65j!8 z%GXg`O-+q_TP9sT5S(!`9OHz9<)tNM&;Jb5UTJA&PA4uSnCC``=!62_zh_RqbdjN@ zU1@-f?B4&#!nq#A(Zg2(Y=I<2KuBn?uaC*l+|(4b-O^6q{58=sPl$?{jlU)(&Ne~D6oo%=spQjynv2z)Ya*+&rLTq{AQK@#JK)9hj}Qr z?%v+G+l~cp2BA!Up6;FY^m%6c_U`?ieMRy4Te|%CLrcMRPh@2LM=HqgD9{xbJ8S33 z(@#rj^fdJMBYLw48)M=$$HG$t6`q-!Q~qhBrk9+Xfoj@ou7D+Pv5p>}`fG8V5F%a| zXxcx>L#f=m2apDppMOsy8?wTWANinzSrdmtP764UhYyjw0Olz~?M#a)+TBg?I}*?9 zq9w4{a$nn(Di>DvH+nrw*+?=uI6lTbu2a(20j+Mqc_i`+==Sg{;VS`=wy zPL3HkX9z7!iV^IN-RIG(KA?jR?+Efij$DY2Xmvbjj>5`!nkWpR4h3l*0L{?;yNQ-c zdM1A*ib!PaAJuEWZtDOF&<2IhIpBdGel&gubq8r;im8l@47i0MCLaAd+n)*sCbSv) z$evhO+~ZYB6?LMG{QYOc?KS>|zd5dd5j$xYnogmGj zVFw@;bRXL|H~=O>U;Xj+?fTvx9k3+2(mFc79dEJaJ_Ic;I%&;KPP<%WG2(c%w`Xr_ zD{Aw59%?K(`SjEC2vjoC93URW6m)_FLV$)m^U0GZZW5k5ivs`{fUe`&2}8o-ZIZm5 z?#nQhn17V|hcF00BA?}*GGh|qKIaFh*Iz9FDX_H(J|@IlcmleYkmvDhV3%uaPXJ-S zr4aez`J8{0#D6Ris0x7xmu_m$1plp_~9SRmIWD8yjKZER~> z%1-tOdvmmnvJpTVptnc!d>3Bpv6|m3M;hv`luq%LT~3ZaS7SVV`V`8n$Dn@#Y$kf^ z7TiIR(0L;A5V;6FOMqsfi=Y}@BhZ+T+TEg7H8##@Px^tiK@#&Xv2qG3Mwgwx_C()e z(9qAMssv=hhYC1Vi|w(L9@SV33fVFz2n29TfDECr$JNem8*T;wf{2Ei8gS@5I_ZU* zAV6wFN*kM{mYftF4HS*w?4vd))?>bG=qHv_m8o^)7tn!z2&^ik1%P>H7nkcSEYO$> ztQNbd=Lz{Y8u*}#EJihJdoA^ zxM=C};`wuCz>=tlG)9Kxrz|mrX{d_~3MKk0WMsiD&}pn&W*!NIq>Smo<_vU0&TTl^ zy)85+VA!2$jR|Rl?|@*5ce!N z8nF^`T>D;x!VW~nO5^E${GurX%QMo%7518 zIaEY`W<28H*bH9!F>;CGm&bmQnZEw?&yoSn4u!nklA$gb$@V(0ti}xPU$YGSyl0i!b}V2G0@nQ#gf0UVc)N22~At zTr*Qs`mEd3J(>srGjQ`oaZwS)F;~Eew!l9wX>_~X{V1!~AOgD@(7=S%w5C)J5w2fI zo+Kn9ZZIW){SqLk0d5A+GSnKF9BoHoOxe-s2ujTxCife*pbdyY9WjAX zrNmxzUn4RqDhO`;`0!yFYA`|X-if&F7qwnOqphT*B+U`v5g}bn%g%NL?I_#w1^{2^1J45t1||TPSqwp04GhL|^b-jM1u8Yw z=GSLT(G>{QAS)E);{!G>?5Bk@7CMvQmn^*NZ5xLmtO8_?u zOm8@W%LbwXx+x{?=0kqXL;VS^eeu{?)XG6W5Mnk!LdbAW0S*-tw~y)Y{Ii6OK%(SQ z*y7>QP}5}l`X?)?sr3xxr}6lJ76o-4nG3CXxT)wC`wt&pehXuPYkiLAl0Cn-wJ9qr z!%hQ7p;T}`8lOwf9+n$;0S}>d$DcOrmF| z_;*J|t^`D$QQx-rO++HQ=AZ%C*u><1f9ezmms}a}oHq^Yy6ppT{)v2ieIqyB#NRaA zC49I>HVVBPm3N=m@K!u&!dYFt;&HUH2{$f4^1}7#f{dzWDkU3-H+{L)galmp14i9^ zIc4BdV$U*M(ewjuuz>4t3JSQXl2(-eEbqO_`JY(*FPUUt?Pwvepqv@I$s@46oT1oP zU~vDJ?DZwCWdzh+U?rfxza6^KdWXDtC7@OX9`cR1H?yFOvc|#TAt)K(4iIFay2)Qm z@*e;*x~;8NmO%YZmD`T1?eW6Fqr4SBne*`QNJu0>w|Tr!$H+*0Q);yuH~NsjPD>+v`Kf^Zk7jBn zmlL=jQG9=TTx537kA>9*iYWk+FjNAMKQ4Q^APM~r8lk(bAL@DbhsHU|#FbQ^Rrs~0}aAo@eF{40=f9gSj;M*1{TD*MnGf1N8# zn_}iiQ>P~I$o|7YLnyNX9R(!qGNjBCKtTZhg2Pa#SE0&7p!K&Zv#$SNs>~G@%;NhW zq$fO_8d+?QCpx&oV^`qv8)GZO-X6-fuU%|qKJmzZVxKXrX~AeL?W|E(yAvbRblE2Zp8WM}V@5+Q}GXxLeatRh9iZ6_mTuZ&7V z$<9bTvRASqd_Q;3`@GNd{@(X@|5ed-U)On^=W!nA@fin5)9`u%`3LKf61rkaKNYMZ zaYpqNkYYZ@iM@OI(i#>X*v2qNAl(1o#7K<}euvfH_-?a(M$v!ayZ=a8fa&0@ian!4$F6*QE`Sj^iq$#4^0f1W+XF7I;6E|He+$?@N zmTP`TW~Rfc)X}4G+Sn&4xdat0u7r&pO4t*WqvPWrQF~tF3#>XIy*D=L7uF^`n53l> z>iX613tJ#z22T93u`w(-IKQ|!cAkbifN_67n`5o)63^ChO`d(!R?A!!?vszqy6I=d zlV*a(T@)adn{-^Df3zpm&+s) zcSCV|{CL#~|4ko0d^mpm_{>oOS)M3GowjNNA2G~a=CM4YEeX0V0o4kr5B8FX;9$sR z11B;jTC*6WRHHJCtG&HobmHPtke7FGm9%G)WF)Fuj%no$jAELOOl8lErME~ajtF4X zwe{CAZc&kuPB7zuDjhZLtwWixpz{Wi3!#tyYM%@$(V=`M;invwtchBzNfdkn+_FZ8 zQp^M&V-y{G%fuiH-nHvRbF;3zJWK&l;6iS5^ytx!2eE~Lfd#%*+-l9Hib=0VbIcfo zQs2yZ%Sz+)L{!*2IGCLcG(=XXjr!FRG#{jQ9u~$k-Amd4zsh`B_d61h6k*V87Xq>> zwXV_6rQ2a)V*Ce&1iHDHlGX$S=qUXV3a&akBTo*Fh&Wqu?dd?z9lGBS{6y99TPS-* zalS`cs@!?**nel}6%}8lJH4t|B zMrsG&=rJ&K3wCoYYzPTaY*J@1CQ<7PgezlMkb>7vjUq5nmkykpT3hvk5>oS_q(G-r z!g0)QVdX#db`V)nY~;w*!xPM#C+|;kY#5p|RBDy1yp+;GEB5Km6wjVrgH0W6zO~y6 zi2hLXK^aF9((QgEBr9L5{YBY&C3(m<_YSTD#T*7RSrwE`?LYoXHWVuRNXYeBI`2rP z6Xsd-ZKU!1tp~)TJl@78I`VU2a`=%<$Lgd+eEHR|n8|H|P;t5EYcn@V=qJw=6#-1% z8ltM3yjyMxSk}f(B|DGXv$L>Vw2o2M(F>FROD9QJzz6BxYsOAiZQlyUHq;3b>xoi8>dO-RP`R!XDbovqR&@>sSc!#JiUB9sxd>rV~fsryCVZmt%Ct9pH z?!=GY|K1c#zHET{^RIhqpD1Tm@gLQ5#0lYZQj-B*qMNvKOnkftk_oIx7<~O&9?wJe zM45~lFbY{H4@bEnql0Bvj^s{#C7res8+y_vnEEIEi;Te(fPK=VaRJb*vf^3~dO9nT ztXv={hSne1%M;_LL{yl_$jCs^j7P4F6K_KXJ+SX1M~}jy;AaKfAzZeXg`~VBDSr&K z9QpC?+uy%e0W<>lg6+1gq#JEr-3JEdTH!@oTU8s6`Hh>m?LS?j4UsbNZNv!P4wDOZ zcBq^I6@7{4HY1}uWZ4|JH9^>LXivj9tKxedi`#Hf zP!3~v8*S!6Q8=Ips}$rW&+h|@x1Sak&f`qhAoVyiGZVWk3?xb`6CJ@JH4yy0Odcz{ zo9-dOtEnVeuX18Qz%~ZnBiNX}55D2E8}EU{kD6WQR%CX(lmL8P7#Qv%G$Pj>8Xleo zYBbULl>CH~EUo z^2}}XmpRL%;0jkYjOX*hLQJLAU%yIDANb!P_HkE@oQZ6oc0#jhmQ8F5O8BN~B70I( zG9Be9OS}9Ieg7yUy*&5b)wQ+ks^is5J87TLb7v%}73yVZ)?#jEB1c9ssvI^O!0x=^ z$K?_&M=1tJEI6W|iVMdwxJiX@q|t&(9l71eCWVRRli1@)PePjT`S&fs!Fn)@fJbJ+ zPFiY@Rj6m9DduR}4py$7^|-o&`DK(i;n!r8d~H94L&a`^kPctL0XkVaMy4CVRB>)h zS2gq_zj9GTP=?&e+_L*X<_ejTYbE`+NO?cL-FJMGl5|q;m><(oD5ZO_ymmq4!TZmm zBI+M!)^;QwQ`azXb4wc;D=yEv_soswp6>K$j(^?eka)hWx$_x^ot>GpvNl#$RA?sH z)@=0?JJ-;E$|sI&k3c;)tB!MgdvycV9)ZTXntb2uCnk?q8l2(3ooUQszg<38@nAu7 zF!xQmnDZ}!shm~pb_;lp<>g%X9{NO>I1t&td(i3+bK@5k<{pxj28#SN;hBsz+^^$0 zi&(1~RQCjND(V0EEjH_9Q8FjSq{Xcd5p6Gb6$|gePj32Kkp1wR9G){QO>6aVA0IkG zd#%L1>=m`t&)?t5TiwTM=Gn~1s>7X1eR8L~E7`-!cj^0;^lgb6&P;z_U}nycKdqHh zr<6vwN5MpxsLS@Ve4k)0>An{;MK>zx2g?f+n~l4kem(N{e_EI;ib)fdBb0a#sy1Fhdv%m(iA>3E~su`q}7} z?#KCk&+k7zu=@R{ryM0AIyxFuPb(yJcqu_aK_EZq&waK@JX7X6iu@J%8^ESH!95tA zDd@5gK!f5VFONn<9>hL6dQ>qq^qFxo0=tdWo<1tD~tJ#R&MrH5>z8?ba6bb zx~N3Ct*7T0&hb0Fmmv7jBxq}E&%l|@=KOhjIZjSa7}$<@=%bFOg_j%_W^F9;B}I<2 zInG0WsdeW~siU#pKfi6awoWLz#PRVPMMt3rQTJz>ObF4{)fHA0DB_x#nEXUp6#g)% z%Ih-1sI8~BchBzKupN2#_AL^C!T$c_=&;A4w68ceJ?WPgxZ>3Cw0)U7_QB`A_JtNO z{?!hYgfC`xsdKDyAp*InsHgxsMp*!^Nhl|bzViZg6sIrJnrR~$o8(qk%dBxRHLI*Y z*!J?{t}Iz~ zW6G9hv|5gzW17DPtI~10JjuGy@W~m@IlQS=yNoTQ@w<#l`jq^LG?Uvi%&Y?Gm~IMt zMhE&2UwFTDU{t7FcPuP(!>WC{9bR5at~h;9#lAMCFamGtv*&0{5VaRD5t_EOtf8WUD+*&@!Y=o zz?~OqD%(H4?pRtbeV5w(&`4aD`2K1GV2(fPV1$M2`}SF zqUZFiqxs=C`|QoKp>3HJv6gY$l?2^o4b4qg&hRcA5^nN-J=&fBLDS-B+FjG8ciG)< z?lSGSwKmrF7B}fr)reKszh7W^^Rl^ldaY7HUGDg)7DtvEhxd!QKkldrFbF}Nc5T9# zsm_n;sOp5}mu|m_iLo3#u@pk zLocopl*8vd6Rr<+=kGk)YiOL6x{{ zcgT}GR7}WwEYDyyC_4iSG>Ef~#iU=)$K`^$0*_nc-rJdx;xB?9b7{Ms#>?jIiQL62 z^7oMcwX5{7xe%QV>?a8O3M~|PQV-F^?5$7^d`^yO5;Vqq`gO4C52qQ-R?e?`eS@mo)MF zhayA6xcEv*JDiKTLLjZ&SaN+J;`=|RZ*6dh+X+sW3DrXHhFe&-bTOR2pMI(J>`Hp{ z|GPT=+RK%wrH6f7i%=7pwOK#UID78^vpjZ1*1Bl&BRnctV}y$4ZBG>&9cpnYFez1~6SWpJBdc zKOD31z-{=>VkTw;E)PeV>_A0yNJg^SL& zGbIE1FK+mc0l!l*b#>6={uP!r%dKa-$krb?k1YyihG}cJvl(gz8t6ToI#TnMC4c1E;u89^4NZX=(u6qN%gOEOSN={nrZPg z{%7%nv=j0U-_I$Vc56lK_?D6Jy1QE@@_i8U3wkAe6*Uzl{l(p1)7hTHZD2mwKXZ|9 zf=qAyXDs!x4K*fTO$kRfQ109se{hXW=a!NYL7S68KGFZfdH(J~FLJsp(0pg^Z7%gP z&*JFm`!QTh)SwS*KIMM&(lseY<;u{oS$DNtnL|u<>pXI?ZcU|RkJ!nQQ&XSw&Ix6I zepdPH*^0o2)y=;becgs3ibKS6MylDn1wsi+BbFuQEV>c0B8BB$XOGpr70mCa?-377 z*jej0KF~KN;r~-`#Hdufl5E+iR!DEOdv!td_H}X(Wv!o$Z2MwrW7+au#uCXs?>uL@ zxk7Ed^6rJ@@*ToHJj;&5Z#2(w+@j(n6B+g$KHcxRr`_$ucmL!Eew+JVq~!Ixi&9_P zBA*zFLAxc7#Ne{lM~2=KIs$?9E2&3-l(Vyqs_LwUI=gjDZLq2crt9+nVd7z*`2*jI zKM)Add^W|d%$(#ty-e8mYy6<%l#;Ir*PI!Jn7i;Y-QcRxva~=~*_)Y|=SIB;1p?g9 zNJmqb1w5n*yqUeWY!7w zdZ&j^=IMDnxG(BGZx9svZq?*j#e<##+|3Us;r-@jw0i`ukdj0CWid`~nw!lsxz!2m zRBAm*&($`s+#HZ9UW(@>>t>7HZhZ%Fy_p*8REYa_=J_7)So#F&h_IUThh`{$m2IZ(lPz}h z;h+7=A$-x3MJLRiT81n7dc@lq8vSYCtwUc2_f+-HCTM4GHLSw&49PQA(y!&y4&Jtz z=gs4v-7Pb%l*bdPxyZH4Hh;16YrQeRe?Czy?-`eQ$v;wMR-LDUX*6_g4{D~@tQQ06 zwE8>wRhD1!sqb);AzwAq=W|m?S!g zxaI{iU9WR^NI^)7xY5XXzuGUbBcPOVj)RLLWbJO8#$jv49q%j22p@iESZExme{^Fo zr743dd^Xvp@EVorDdE8pijJ!j4ZkLftv*q793tvg0ak}SC9s#Vkr87Plbln9wj)i+ z1RRq&*xBQvqLTCSWKi!$Y5n^(^?_hogB~hmPtITdmGLxqzi0QLArd~*eDG!0EW?q zxu;M1aKHxB%8y>h=2+6$jTI5;BEqeVqGt1mS7s8Tp?v5;)n((i8pH(j^lN%sI)W+tDk- znan?o_^h>D`kNQGxLTzei_#67O?3}yoJD%-#b<4lS3fn#2y!lNAw>3Ft(&`Uo=>MP zbMsqE(uvJG;z;pQOg@~}c6lyBf#tN(K%M}1YH8``zP`TZ=2?JC@OwaF2z-35xFEb) z;E20g+H7zLEu7fKr>1r=GKzZtT7p&-yiO2)>R-OxLcRgV6O1140J0a5nxbF>Q2=ZE zZ8|73Dx(U8&2cnE|BRd4K_z>Xm6 zNMI{^ae#1*4~!QHN#NzAr@v}$zVkr}#?`2RrrAgTKBE3!0S0t2#8<)r@|r6_51ne< zZkTVnt91B;jy_yFLPA4FO{fS^p6L6 z+lzHuc#1QH1BreHt&Z-n~U3ybcAGn&DKw=6bGy%-ofBP!*=1$RfW=4- zpBb8Id?By@C@n8nR!{&n^$-;Pxq3b4mj237;>H;&(R-g z=||h#*?A2WNK8z){X{-WBgnSvoJv!6%<3AM&NM>>8wJ6K4>>`xMkfUlA&blsF74n3 ziyS_TaR#P6K0W<$;G`$w)A_n!s=`yhr!m+CU?&_7k_>E+`@wXlJqTEN^bHlI*2}Ko6mL8>YO|` zd-W22{OS4EEgOTLUnXuaSUYI7n#&SI@y+J$d`_$D8-+lA0k69>}9`1~D+nQF3e6-GV}sHsj*3H0fVYZTw$9>?h@{!7ZZp2!H_7Hc?P~ z1{t!u8}3m?yJ%_U6n?0jr(G=~5^KQ&)Zl15IqPu?D*Gv2N{_5GIMTnlV4rw@G3Lr* z@=(qru*(kf0#pv$XlN?0Or8S!KCbHncY!;u1f{{2 zE3tBtnWq#CufEwCHmRq!qFTP9UAJ4{hHNBWAuVka3WA{3k%*al_fl5#1i)vwg$;Jp z=N%jl#3jSL8HK~6K=Vl#b-_h-^ic>#E%a&mV4ayQ-J8A>KdYZjvi+#C-R_lX!C-(= zgN8;J+(}oR5f@O37JBPT@Yv$KC0uF9YCfiYF*!&>9|b4yC;*f)Dw6sBD^_g6d|FPi z+C<)|7OdtZdYCsm?|IdBF?%Aqv3NrHYiYS>m7oag7gWuyfxG%zX}^>x zDHE@>dD8cTJeGCq)*BqkM@WROqWt^|I{$r1BH!EIUg_Pt2QYg(82{fR`;;F`tNe9(|K)<^$I^48+a~10CmGNwNX4GNzQ4x$S{+G=AOMVQg|)Nh zroBo9u=H`22w6SDF84U7`q}$8V}g@Y@~5S_e$N#%(tOA~Q}gH4`Pb}lf+YlZ{Y6RA zydDs3G?nJM$b39;I{~mCVh9;QP6g7Nn>)QJ31jX9uF!)LBID!j?CsShBGw+5#b&2L z;5accK}aeoF@C;DE*Qj#!Dnlr!+l>|v?&$}=;D2tscjm3`?jl#%NROelyU5a4uOW? z>Z(5F@d%d2?(RoIk4N_~LH0U1={4RiR8|D? zJSe)C>x1dgegMXy?${zMEmJBhr4v8OxQ#I;48wjKd}m?d$d?E9)FXyu3W2^#_5v2k zf&v2dRb`$(3lbC0Lc)UXH~8w%kPr|~v7zpdLq97k_@<%!&X7LwrLw!Zxd0a0(0HuQ zwP=B8b;-^SyedOuV^&y{4}XS>HcZ6Pst8O=47>B%ub*Oe?G3j55D&s18y^^ODeAO( zOjn{uQXxMvf#KQ3mVI&!nCV_#UJx!x1D%C35z6NW22<>3hilN`22Nbq!VESwPMJIb+oi1 zszl=+JUGb$pg4Ucq<3w1{Wh~@`Aaq6D|UCU)Zb#jh0rdGd6h&T44^&x`$Jgz=(EPa z#pMMa51gVc9)aA0m=Izn4xe~10taCLkgonfjuLhn;4+n59u9}gJHVaMWA5(mcq4$M zP{T#*_lC&=Li1_UiX0djZ~o^`+1$&VAkVciLtlgKAjdH313{19r1Pz}wynTzk@T!v&tp@rW?IXa++J8+F>@D2X5uFQ|dgls1IUW)| ze3&S+ja8UHKrN9>mbDM~BCm+Z54?R)NtaPPRjj@P7MKH4a5#j2hleHXP2fSl1y=4M za-bt!27+$oZ{5bfARLGRZCxeiRiAXoPE8H?SrlN^w(nu)h*1rCd?lIxz{o-U`grV-^a&LKPN&Q;RyMXvAc?;WVFC*$ z`fk{^`1(hsIKB9{+7}#eMu)UzbooGR)qtlixHO;!-0iEyds|21hEg;F7GfQxq7FE* zsN2>a17#x3=4d}N)A1d)e;>5c=zcsG{bjT`6}Jfjc0FUbewlV}1dX zY#t^~BXu&6f?;QHUbECdoH*$Y5~ zFf-q~>|3IbvB8I#nK?N*d5-Skwk`k}aPUS)ASH-a^z>S=S2p*n0NBVkACDKXGX=61 z^Gv8L0UPipU#NHZpJ-TY95K}f9W^{W9IVfjoh|ppFQNU88E7{kBBFsF=#*{CtBRS5 zsBm7gMGU~6S-ZLIYlMt%FMwBX@8jHiWn>bGnV2y}-ljM*;OYxMeuzj&uy`Di*M%29 z0g@s6-m(}?lYH|Na88hw^$iVGLx(!j0vQ$qP7QnnyEL#-vajHzlW%g4@hm73!@&y_ zj{YK8G%KpXn_~{|GtqOFWLPuuQrD#L%|+jch>uw2^78VqP6p0`<{t&K3jD_){`&1tKLu?IZPtbsmW*lC^@6hIgYqvah~$KY~~#Pj4lN74vs zTVMHA5={q=fOALgr(&Hf->QPn5g&wGXltd>x~IN=5J3a`7;s_y{y{|@(Qqp))saL|lamjknS|Qn#PhXJojP(l06m`a@}}{V01ewe z7IwA1LO%2GHyu?)LxW4&-4)7w-LX`@$3E+8*y7Uos0eZK@iDqM(2b9y^#?wTEG!wR zsjg$K7x8At92n&;k@E`FyO9LGVq(1&*DpIbOkpaFDjc~J93Ea&ScuEcfoKKmki%$K zh-Q=UeqpH9{sYP@#4XZK(V&T)gJZOS`1DgWzux2QY&47%R-7}HQ&CiWX_M%6-qKP8 zVd436Q41O@Riu;%x@-~-r!QQ{%y)?)l8#Mqg*jlHFYDTvj{`V-Z`!|nSu3BeO3_0* zio=$#s6p$3$cP7NbIJ?M*^4ddCZIEvmVO%=YF^2_=XD}j3ZvcuZQh4R(kuSkkLMQ^ zX`VbumyS@M4&R2OM?Jt0#oh=!t`v!7_4?{Gsi}cTPnnw_bZB@c$Y`Jhi+q9xVZEK5 z(3cpuw@X~=+Dl7`<37}LK1=4!6Lb+@U*Ei{<&XoYMwFsCf{q;Ss6`~ltcMq4t_&jh$^(z8#i3)n4jY5+j}^7U)Mt7mC-=_XB(Y~RDyA^hfE zbaZQL>mcbYg`Ld{K^aGgP1|=+6R;WKph9@@$9DxE=6m11U&HiD@)1WYHWSxZxt0te z%$?VN%un^kYfiLeY)>#lz6XHw{CidlIuKHVVnR!6?Av@0GX4BEET_k@!eJ2^;zr+* zV#EscqvDcuGDpZ7Wpv+w4Dkr^J3Il%=D$`~Ni99_2N+n<`; z)kUne7A={W_btd{t5+u-kxzoSzJ?+m=HuAlU@J;q7C~47gctD9fc9Gqy?96~jZ};S zjShJ@E{u2a+Fc?hl*lI{@(d%1Tk$~$A{2elSf&YU0A?f(_VyJn0Dc9H!r8?HK(-MT zjXqHO8PP$hx1?sokSvx|m{?bT$_jkfu4mltb&@b5Cizk>5j zmP~43K zch|Ru``-Kg_VesNzTfxGA2Kt}Rp)tLYaQ!2j&=DwRFWk+Pj&vxnKMN9<)l>3oWY5J z_ge%w@b711XQDG_uAjLtbx+MHdTGR2S8eN9e6`J#0?($Of1qQSL$mtTnFnj7#xnY0 z&DCtG)}w0jTD*NaBT~Gq7tYpp=SnRlX<#0fj=u_4WdFm)|5n{IxCDPzssI0cT+8JYN9}J$3);#A&`@5!yw>J%yitU4 z=MfoMpP!$tcUSVPPQ=^Q?c2A9Q$I2; z_^Pq!6$tgLYHE8GYTcj-r3mLh{3=qk$jR*3<(KYTU*=S-j0op&B@6T zKRM*lk#>lv6U;5WPLGK z&lNw;r(Tt)u{=;vP-O`rKzJb$SITuCA`G7<3i!Eh)+5*E^z!h=`N@zN*KZD#G`Xa?hSUb8&Hb^@_Vo`ehmt z{ZW@A*`7|=flXMrEIxjG#1S*4uyiAqGCSJxu7h!OSdvu$VW~8YAp}C_t`3f4nuNlB&iMlBHiU2#ce>-gIw$3W5#S zosspkIQQh`f44;OGcq!gbLdjg(Jjx;z9+fjTgwvi;aCYtP>`SBKQNG^f(fw z4b$_em8}P+(h?HYqZO`mC)i_t`Eniov8=2tAz`AxybmcUc^RphtxqfBbVui!p!gW;&V{Y%l*x{=H_OE}|Yipy^+Pk{C z;82Mw9dj=VBKMAd1f3Tws5CRu2>$qyLV;^VoG>_`we@s>IgR)%5_**I{K; z)NxtYFGu2fxERVsTU*=6$jI4w4{kD(=I|d7NJ)|!7W%Tg7%n4W8RHYv4HuY``wmeK zmMZ2J7Z-oSz9wfo5+H1_IZW=eTs`llnyfTZ?raM?f=zeDMn>l58N;yg27>BpHm59s^Oh-YeKJoc7PFfrLEd6wnqHr3 zS`5YJ>etJI6{eh=9$~oH4}YNq;ZL=7`{~C+ynjBTk~vLx3NO};xqH1QmVQ-*tnp?j zF+lv03>wZRS&Nrj)_gBuV2qh-`V02qO}aVd^3sj<;#xfNtaK#74y%;gtxpHn7{snN z9-Y6e{DBuSi~Zpl_X!sRt946OTZ@dBRdUOm?KDkp1mtD`&&1Fdcnq=C)H__G2tJ54W{VBhm;;JNVEEsMH(S@f-*iEhCr^)Z6}`u|Ao zlx2aao!APKOzgFuiLq2T8uMh%x1Vs@`xr0js!1#yT2WfF&Pf&b{R$$VeG9j#ibEO0 z1V0t6+p@(d{^?zkZ>pLt>?VHftOwr`Qw!+-$Nv9su*Cn`){1P6koC!vYjB{@j-C4Z z3BLld^Mwk@L6uB-|G|Uzuetn|nm7$AEqaCE)0uW2oASR0=6{USKOcFvhl@sBw<8Mz zya(VS#=4QPktkkOlNKKN0Zhnj;_?V96Z@7VPtT9bH^UVAIk2-dOSh|cdX+kuDsNW! z;%u_$S}50y+K-i2@ac7epGfj~cbK5+UpPvIOX*{d*d=6?!@Yik{9PHZBTlZoeZh~v1~}j+XJ(mm3T*SWTr#vX5!CS`; zaxr1Rf8E8u?uY+zd;Is0b#rS?Z{NPPPkQ3VsaJ~Tav!rLL_pT+=;%0i?p$3nF&t?1 zYVyJQr%zYV?U0ebCM0m`m-lDR82ZsWPX;ZEU4UPV(Z((rF{6h|?G+e;NRas_$|u1Q z5db#1tcHs-GT43vb|&)i@!e!%lE3lkelQbiy^;Bf4|=2*J9nbhd6mOw5>=dcmPdw% zhxaySJXSrIQ>^sFB0qnYA`rBn?}oIb94qpC?GyI*q_e^(LBwQ;rhZOdUX;K~Lj?uj zaalsdtCB;Ajpn@>fSlI0w)Aq130a?E$GD-p`jsS8iDpauP1&n(b08*^e3aPD|~nF$|rV;x^9OTD#2U_{~^^7yTSQloXRE7 zE%u0`p2F4Dl~cFaX09`_x3`yzi_5evc4@8iuGR4Fbd`U0h0K5}p>x(Yj?sAlz2ju&2}UO=T|WLDYv2A0WYR zZ+3TgM@B|QN7F`cWTQa2j%}KLZ3Ww8rRbXpqs;wkUR+@j``A z=CJh29{bY!{^Xv9h6eF|EllLRtC`L`z9?OJR3M-ETht;PuQ}`A*!!6B>Q%diUUpIr z5BL?6E>&$p~o$@YCNaKZ1W`}5vWe{^YfFEl3+Yg2iq;-TyK}Y(!G}Dlf;(2)mQ#Z zOo)n#CM!m8J1!5Wq@+afnC9wryj@E)IZbWVHFxR)Mb;Z?eaQ=w0nb{F!ZM$3VIwm+ zLvL=fnTnYbKU|fRlasSKeaXo+Y|eEJWTkLUbkGLv-)M-_`zSv4S!}DUyu92EmUPrz zOT)5{-scRK-*;=?J6?Qq_?IJgFI6YUt`ZVwhSWvaW@&@|{Q0A+r-zG+J4YMT&oPg{ z*~`1Ijvw9G=N=aJ`Lo;hfGPSTbF7o$NU1%PXcpu>ruLPLDl#&7Wj96Du6sD#J2t8P z-7{HHW7UF6~*4orrLZEP+sPhk&y8$85 z-`{_PBeDpp9CCK;8$ogm3=A5X+Ru!KB7_+_cH}U$L8Vdza|6EH_dkl4UH53mx5?)W zYnp<3aiP~a91x}Y~xC-~E+QUgBD`-z>ufB)Xyb;8H@h7?g> zS9g54(N-st!|y0%sNW662B804@+H^P@%~~xrfZ-?&772ma;p(r_ z(o*0nM0bX5CEs1ts&p#}YRk;dwzjYk0jLYNFu-9bd8S@D@0icf`{h(sog^xR%{~=J zx0l$?;9a6Io9uFRlf5x5%~dyo>Zm-sZc09Bec!E|Ax@Uhc6pdTRV^|$_ISWFuArd6 zey0R#`rXN3WtY{juS%jxRJ6357yG#qRrczrt;-LV%c%LS7Jq!*O;1W zfZDf%L%=;grzC#iiN0OKM?|Qz^z!S&fpdF#Zvr(ZH&RSmx2E!En9!d?LZoG7$srs$ z5>_4Wv(^dl@^^NmVHz5mQI8XpZv~C)6F-Ou>^4^=WuH9x((2yoO?tZV zC)ijLiG0YP-C%kl@Wu3SiFF-NKV`3`7m-U0xujt!Or5-O)K;s_QSG|Aa4 z;?rYhxVTA+`R=M+^E7e#CBU7i;LKWyiKuu}^7FfQr9(nPFTwkbO~3?uySsKfp*g6Y;+GR_q)t#` zwxusZoPj|2I=Oi?3{(8lX#A(w6`vbyY!(1#q@|@t^nSv`RRZ3rtCNXgf94{H&lCRP zsomWeRtW+G#533JMdzcfzA~L_ouw0Jdq#L>S~5+ITH?iVtY5t7j8NhnOwG>bH$m7H zw)X8orMx9z^Wknb!7Y0!5{3EvTM@RtE9;Ewb9B#@ncYrq(4Wb31#Ft%bhZvS%c z(W6Ij`*pzWFD!1iG5tVOvu~)#x)1m$c=7RFyLz=<=eD4rptGy%(7*r@#htO~mMA?d zMvu)7S=d%jY*V5+A_e_1M%b~isA%U2V!G>X&NK;%(U?0(ZgOsJ;O!Kf zUg>6pX{=8`K+?h)(P_K!pXa=gYpud7-MzgK*IZm(g@uHKPAl^#e!jlGQBhGVBV~?& zwcowNgKHos4+8)Pw4CCttDR-SFlEz|lWrR={1p~(CHw%p_iD)vHeU%QN!LdnY!6wx zuIe(a-g}GJkpcT%Uthm^z7~F1c5N3JGr$ZT^hS4gxK~GcfB!t4-16Q%kzKz28^S!KduPaB zKq@jZGn?+Nj=#kxzC=hE0cinh7SGz>#<{?4s%dB>B_;w#!o$Kc4?&ie*Q^~jHgKN0 z1~D*Zj}Wo9x2L3})SP*+i9nMeN1bP4uCZ#|kz~&aAX)!6)!4fFLKzpB$y$Yv?YOyDYVp?sg8+>5f*3V(QjIGAUU8kqQNlkFllIT6z# z$%F#+u9NKU?gH_sU*$ehZIK}3{~;hiNlwnp+}x=3F#-+c0uKjAYh4`^&{4v|!c0tc zy8*i1zy*Dk_RY=Bjdhv7?YH*f8Y@KW)FW+6OG`*WC%g6HbgqkBMr6Bl$#Jbw{B4Gk z+_RExRTl^e5oikWUR^1vvqZY_56zT0+1UZC2$DM4+2wnC%MuU1=i1)fbORQz0Jl$& z@^Spb_G-BK9^v{LK06~i@y}Ra>PqJ{G&C%=pZgZX!pa)wzCU+(=vI1C^5x5ut@$2| z$4P}}G6}Lb+aQqSYXVNXS{i`M=;&xP8l4>HUfN`SP-B=QcSD6Ggu*EPVSl!PIQpTv zdAfWe1G>Mz|JKzHp`oFz%7Cyja2Ej2YT6v01M?dH@I8s4{z$m`4t&=JSb> z$1AOrF~pq|l?pdS>t|vdNAD5b4v}A~=BgW9Vn9FfJJ?z6Q(zr3`x zw6anK@e|Y&xaT>62Zme3v(058vxtoC@pq4tV}R4ZmpVH;(~7#N9RzHnN#Y;Qj*UqW zymWLd%E{@_H~lPs6DfD_874M7~LYP5m=DZ>k?N_5vfB*0v=#<78aJyf$^ZoNNrVB z*pe~0(a^}~=&2x|TewKdwyD}tLlFW%GJ%gcV$RAeL-DXAo`$676U!OUfX zvp|(YT-Y>4tE7I`P`}{C5Tc!_Vcz`$PURkfmKGtL2)xi5I8?>i*_``~HW7+4oZo&> zK)kXZ$ejWJ#=EL4ex!EL6m*ZE#Af`B(%2y&Fu;ryemQ}GZQDyjbA?xVAPO0qnyw5M zH1%L#_K`z?)kPZN0HxBqCu4-uS2=X2 z;!Sl=PY9m=?aaa}+C>EgjOd51QQH>p33Z)MtjZj}6*xHDl}j+e$xcu2I`&5@bAQ3c zM{{z?Y{5lt8}vsn{PqQ|)ASFaw!2~K)wJ#rhz=Yc9x5Vf5e2Tr@4UU~1z&y3&$j~% z3YluqG!C--A;^eI3JP#1GUfgDsr^%3En#hKZEn6WSo#jbYA*CS@~$-gzL6gH>(=(9 zSu=CZksttFd{F!x>|FZP50Tzh$-$=16PITE~`B>8ee%LZ4zBrxUJ!p zn>+75CMG5Z5X~?rym8x$T)!W*h)6zjyUHjlOl8a2schL*Yl?#|ARqn({G-BFXlF%q}RZthCvOhXVO@2cfcp?&3MT;D11 zm`*^^dR96^u$NDKVQrnf0cpU_nKw_0IA!2Y-ii_bMI#y@CdkMNO3Q3U5o+2F$xX6cb@GX66&1LM#p(IQl@$XGjo$8I3&tx5w2U7`hv4d8xOm|i zKWK2Vw=+M!(LOumd|BCgSXEwV^mQ~=3CwdHh&)eke)l!$#5XJ^t@gw{K0XHSkd#UB zNiVD4%ah|Hpj4*TL+2ykx#nubC`h^Zam>VK6eJbK=j5!d24%y{$(k%!bf-Lmx*ynJ zsEh?|#?Gwtvi`bLhl_t{xXMGEboc#b#yfSJSEx8SvkyEmnPH7XiUQSf64yUA4yCR1 zLRmp0w%!AL4IdQ|s-q)8mdA4iQ{@5YQ$Bj~l;RZQvQtdVbtNY!VhLi9vKEJm0tHik zXu-`$iW01QeB;WwvuAhr_C$|1yPECbQUQS{B~90vlps5-*4y~&iVHF;_N`E|uJubp zQ>N}BO(MLOgS;Ld9xjD$ak|-343D4`a~MOD0NAB+>Z%C6>!tPX!*fejwLaeVY7c>p znQc;{;jXT{4WEywE2pP)4TN!+Y}F*i2oR;!)IM)Vyo;`dLc1f36bbodL`b%MMF&~~ORF3{JPXWWW7c+9BfV%FyZsFBI_oeE)LFgTU^@!D~XE_9vuaR z+ztoCD=H^dTVIuDb?nT)YiqYwJmQpaPmXqk?Uaq4Jb6NYNf#eGR`i=(Y@1VnA3#H& zot=H+zA_48=TEN-c9?Ubn3kp{h3h2Uy?llF+Gf{1Jp)C!_t&^6CR9%jVg=3Dz_TH4 zJAKji+=U7cU9f9#Rvj8fq$$23j6S}fooj?6>k%C$fopB4`gklHr*1ds-dnRVX62*6 zl8!4xx(8I!Vt84&f5TZkI0=dgV6@v}MP2IyXs4#8&>sWE_Gc5r^dVi@Q4fGP-rKuv z^}tdg{bny~Qri3XkD&U{OzoDg(*LUS+c0Mre!4%K5J-T4jp4?H+1~?5URJJ5YRctl zjj60CFQ-9B;U@0{$qX zI`7gV#+%E1+)HrVkPAz#KKuRoHr#UMK)Y|WiJxqCDdXeeX&0xBCYausJ*!mJghr0T zE$)Sk&o9kNkQ~VKXa3bUNs*EUh|*))lwtz-(7}O01~h<5dmLf{g0X`+?^8@QI=3#v zI!&jO7)JLvPpe*oYhY!)v$GTMBDis>*tIsufVm>_ls-eI`W$DN0YtOM&2t^b%lqzv?n6-)eFb=)_>VQL_ zq->{~DFt6)c{TBqy*7;dL5m_E>wo%&etC8!yo7oJ*3}nJ9@l$7vY3rc3uEdf)B$er zu3jKtk3b=KS4o+b*tBxdbRFGA=;i4rEFZUf!=n1B=&dKU@Ptl#s{q;SR=wo{TIsJlrfP*KuhOA{k(( zcrZx#N__>Si7)1*e^7rmfW#2Tda1SL_5<@n4AULYuG@-Zb#8brAK%`WIKDcGQ4ndN zxAA|ze83bU4?OF{#Dt}V1zb8nTwraDn@O_`u!{PuJ6@^3g{7sQmR6?5&SZeki2cfF zMdpg-UYo}y66CRvNO64p=+IDoW8=%Q*RsE@uwf@AUJ}p94+>|nL2WJc(K}fi1BtM> zn1Pa{=H%qGRa08oDz=?b0$k&_1_iXg8yV4Iw#N}`%pU+|xNxgj=$Vg?%}U@_*(R@Z z{(8rNen(6XXOpE@20-!QFZ#E!Q|+#&6F^75flp%`8JPVyR~r7bybUU;$p^~7Cp20x z@+4bn6J|b83Hb~CF*^E0MwD-EqiyGc*Uzb!ga+;*Q&aXH`CrG{AEF=LZ+w>YjXBP3 z-811jBN_}pK>ccDJVpe`$pq3JL4|2GkXuW$g(YPEHV6$DzXT>7l3_^GRGr8pVVlkR z5t~dQBI;-NDJI4)#}mXig`J4dJvAN^ZH*7R?s0y7MO18>SfHUM>MLX*>#8FMU@)tW zw>V7J%T2>9P@4UG66P<1bbj41i|`3*{5O#!hBULh>=8Iz)s4wZ~) zOQiksa0x8vv**r%(=H<6+W+B)a~2mD-@SVW>Rp(#8nexk>xdbC0LfQRNApc)+tr;5 z5(I$X(~pbuHJNRYa$%m-tv}qZdkE34^AnCvSyLdsZOp0KO@>2l3sdh6)wYaGQgSlS zTkV{HK)!`L1N|&i{?iacgdLZoMV!}x9-f+;OFc3J-v8dczecVFlB{oSZLO_Q3fPQ= zuxf_qH3G2m_kW855+pBGi-(x_>W^N>XWS8Q-~RgD*Vl}C8VfeH)@Trx zMt&t^6&9wY30<$INASYU2so<}=x+Fbq0H5-EkI4ObEJt_H0NMR0HzI6#1M zR8$uiy?@21c@ame%z<6h^v>P84q$T{?r9EbY>N2@&Zl+WI*3W*n9lcWLjXLUu zzN=R!8PQ21*S1!Q__>99gfGz1MMt@oW>v;+Pk|of;NSpJ3?%TZM^Hb-#P#fPKb$U> zTzMXMZEkMvxw(0hMQwV|izU^Kd|c4g%N&)3ZSe8&_eoG6LPGeEp_SX&JXB7WdNEa1 z^mY9|e%xcW0SMS?Q2fsJ4m6d4$MW#1z2gw;*Ten4vC3cwQ=H)?Pnu7c?KVSOf z#~aY)12%o#GPJy(@2|r-0l5a&wC0hKBTpV-pfm*q!2zJ9qXUc8D?&*;=taTY&J#O3 zyTxX?6SJ91i7Eq_riDxoW@+gMe0Rj_`Uh&SA?$Xnh@IlSEv=hDBc^`4s~jiz3}~QXO^l7Bh3r3!HY&wGB=AC(rhgj6 zt-=;xq-Et3AD*q|n7o4-sIIQ2(n+C3T=Mbb z;voFGnxlOLdN9BBXI@TDYGx*Cj1=r$P*b_;W(Ljb7egAV(rL?8924!zt9A{QT)78n z#hT^tcNNc49wiA9O|sOLyuY&&__qxZ>WmmBOlPNIUr!qgO3$b9KtZh6;&o#{*v=V* z)WMlN;A<3_y37>a{zUovx5NgDZ>+E1J(jyt*Wa2&kCaN}GyIg9$&SGnU*fhY(EjE1 z=+MG)Xz>@%G(5^)(B-j)3BBIQp-0Pj$@m4h}VCr5ysg~AY)oJoAIIE(;p{~<|H ze*SERw4T-aroM#4lQt#74*f7#CE%h{)Vz9ujm#-LAZ5D=3)1c#)Qp(qCka0gG-wiwZuhiXTYO{8H1?%WTGfLU2zydYUN9bQY(1zZZ!1 z%*<;D3(KP!J#(PeXCL~2zL}xfuZM?Ilxuh*ONf+R@w2if0`O~XmYmlUc*(@|in$}dr)?Ck7q6Q&AXsf4E|vVzEz$ybEPhLINk2oOu>W@hU3rJ25v3XIe@ zq*jaOFY|Td}<^U1}bai@rA_&J($TsI;j}RU8xZz&xF@KFRe9W<~8f8e9^yR?6Mp|6It7@4uz~ zj3uLF$0kVvAXnROH#_8Lx2)@De^Lk|LH_vhE8-KRP+`B9mX_=*Su$NoqymP^QRynV z{TxWS#%%b_FYl9(h=#MR8tT)hA}Ky9T-qb7>yfPQhYzh#D*@pQ=>U=%P)@+6bsTVk zwv?zc*MmvOO#|Dkj(avaVscbeYhX!-VEl}SxDX7QG)I`GhK3jzk%1KmlDm;08|V(% z`y9?+Wizvsl9?Kgmw@1JFEfV3)dmuLdR0B`b9SBNhTq2o!adP3ij)Eh~trv5@vWts*0Fw`8Mt|-z-;kRkG}<>V$R66}q8cM*&uB=JCp>&V&%LI{LwdbibkR#waA*%e7DsbJmbe)<#L zldfrQ@JVjPGdMh)0!+V@lo6jk87}LHk|LGj#6-dW9KR#w zSS7B5x=QLKF5LT0{%~B8gH#~qE9WXyNOQWlFTU)No!OH-uBgWxZh><0zr{>6$$ps` zltbg=<3T||kmQF;YEGfoGXstg`7m~!yGC^vBNTh5E<)bKR*Hc%g1Js>hCWhAe$X|e zc0h%@GolaUAS>$)oRggK$P}^}D!g%xxd-TnPQ_>?=o>I_TdM{43ked4S&&l2#ExMc zz%jiAsMUV7kI%dpO#0v`Mgi*$mT&>5)h}A`|x+iR&a<7iLX@2w8bw zFQ16puM5;U)x7`c?qR`*CH>*YkYsuXVwT6p*P40SfY5srp<-YV7Zz3oi3oZCJUmWn zYHA=g^k(q+N->z4n)-36MumluZ`>j+R+@r%V2G1^fHWKHMJixKH4{3bMS|XPT_Pe{ z-l^9op%SolxZ=gNOjA5Qt#jsISDg&XD~NVrgE7o`oT&l31ZSC!e zE?(4yz7EHIX~+|Ar#AXi!}MKz0(fQlqd}nsf^^^2WjHf6tSDb=I-D1AgLCC4C^%lo z@dQR>gykgMxETgjqv1E(o75w8l)*Id;lp_z4Zn|%*_7nvKluAg6FkLgPdhFA7>W%k zCL5Pg3nL>&BATA=qC&0Z^KT9hMW}Y)x047Z>R?4q=9@PwzG*2-x@6H4th^UGywvX zmPF5;yu2DYU-UeNHx;qAI1)5@@3kJkH$3HcMj8WFekK&UU z9u%X;FL3beg3*9Z++Bo?jZILH@`x*(Xk&?m%ol7f!s1+y1H~TNuC6|b@nB+$ovMr` zVF;1e%y|4rQ`2#MvT=R=m5?3;M0NG2ewkh=nVCHq$jOjvAxF)-g!`9)_K5EQa}z`=5br_4 z2mACP_;!eO-#|BtoZJ^rOeHFcj_=zLJ&vb@neLke$Le)hw=mx8uo}QjD3Zw9`%0cq_N$j}w@xECVr``Fw$Ja@mTwTFI z1zIpI;s%+oI*U432V#JiO>E*aRoHwTNo2IKzYbL|ti$@68mLi0GPHvwj@6}Q5)f#D zvu~!Sry-Ac95yCvVGiSTN4Fa7m>wAzcuck^z|v z#&mQnFE8KaSZ||dhk*qau{l;$=f)LiwU}HT|D~Cw8v!92rk#k59VQ-d1IVI%*#?*j z*B!tZ4|Kj?0t|IpPid6s>g(xMfL}5tC;$w@O6I#o zE0(Nz3)|{<3<(>2oS|%5>x+49+qtdyg0{pe)3XGwZHH+*8_OK!&P-+Yw&g*T16b0I3wyEm|xsed53a^5RAE}Vj7tW9JPJU`elaPy|< zjRYb?tZ^=)I--k@-qix_?d{sS2CY_|xCc8#*j?QZuKnXaa+HNYHyH1{vOT$Y*w`2x zo_B!|p*TkTD^%6=Z_lc!O%`XB)7qZ^T48Y3i^jxAA0NAK7`ViL|DIAj`3i1AWvdV| zZnK9F&Bae7;@f|(3ds;WO6_)Z=j5!kOI>uJjM?osVnj;5nbW`e4k})7X#*9yGww|f zx^Psvy{;s|?OV6Lc$&w_zvbHA*>Qb7It=p?_CewJqKH6E8|Uj~YF@=uwUH9lM~4g9 zu%gZq_c>pKM8dTC{rh+CB}c-)WfMx8!Fr{>bTA@O%tG_Rd9Ob+7xwzHF;B#Hmg(pi z>8S^lEjSCweGj(s@)o>FUYMI(KDV?CYK)4Ef&{;iFGE9h*G^7KDjX{GNe4gXMx2TgaMYH!p(xI)C;!?5X6hpE96VWH(JwZ&dE6 zpAH|LCu$MTX2GdsO%!V52l2P6^wx$#Si#Yx*P=+$88bUQz2Eoc%hO-)L{=9|yq_-3 zTT$x_4Z6@2b0vQe5gAgxda9mHl$Hx#tUq`~W^XsBs-;}KUKBt*z9PwDl`!mFW2 z*)Jjv?>MaLBKUz-o`r6^zOW?SaLF*JsUN*yU6237rAx(>1v;sonX)36h(i8zQxs5 z8<+H!|H05k*|%rVO&GN_{6q;FAUnKAd0zcQ6E??;m+UEZF%%@*e3xTf%V7r=k6x_M z3#i;*JB1BX>%g~hNdwo}+uaTA8X!H7Rh=AJL-#=0l)XMt!pho{Jc*g(s1 zqn7>Ezb~w^$cy9?xDGTm`8|%^gc47i8F-Rs@z0$D+p}*4PFZz*BxUG<-l*VbNK?KQ zr4g%NAC=$gl>7U7w{MMxd39l&ELT@F8OrXBa8E@JJ))QiKc=4f1X`Hk(a}BiV1}<4(N{JKp=iJ zHKk`P9@Ea%{>yo=1;iHgCD`lB%?M}N6|!%=wVZwHT{z95mBpDH4~%Mitg^waL&Hg7 zOEK_RZ%T>a(kE4Z7U>(rfs!(`X06by>Vntl3!3h~db~rUbxZ9UJ37{&Foq2X;?p&= zFVTq<<7elx_k0#*pf_Ls1pEv@1*-EE(vpFa<5!a&Rk$U)W)v6idTwkcPL}kN0ZUwT zgRa19I|%&|uoH;4U@}Ix15pIpHu#y3fDHw+J7; ztgl}QswgFo2~2FuStCX2p6+h7M~{B|kb%-eas5BWe$%tCroP_Q#YHKC`x)d=u*O24 zsd3p?{0W5?vWm)R&Um{EoK&^G4E{Csg}?`*cKfkK1##8j8!q?BAXyImayi-L{Jgv& z=!N~a7uk>UUhDeqW1c_VyQdFIn9Txb+$%fM)7D9uH~(~C{`W@12}1)Yg5!XV=8|l^ zjD!FyDJe-bO#TPr{S}`Ym2=y)z&M--EPm#gn+IskfF`MM`UdLO$jT8z=XjIgvi0J z0`bQEQF3Ghq$?m4{pK#n%gg8YWkWq=aeTN3_|`~Y4nw+?shTe+WD6C2=hV#f^n7#n z2#t_3P^eurLV;^7_l6gnuqH^rXkfDOig*QtHCRM0(u+mcrtPx?$*HMeU!TIl_@8%v z10V^#GrzLHdIM|W?@nWAl-?Y9T9)E{HG`H3>kRfK=R5|HnE+9sJ>rZ9722OahB1$Z z?Ctg{pZFQLF7Zv^2mGQSzjm!->JWZsROjam-S{NP%|GkQg%{?YD?yvRneCmr{aCra zO}H`U+?w@KTj~>0ziKOoYiplD1REF_X#NbYOt5|!`4J+Z-3|sC2ph02puk26oL$5c z?Gigv%#whQ)Ov?3y%%U!Qc^S`POE_M=mcL)hI1KRq8C$)V-0SE8RG}91eRRrN_qg0 znJqtB9XzWZCkL1i`I|gbVElBMjxYuP3>O%^p=O9#l1IL8y=W)&K$wSzhk?Nm7@Ouz z04hM|V2^aFj+ygPAqi@%zJbXZ2(m!&V`y#u92Vx->hJ7$d`b>kdt4wUHmGtJR9rB&Fy^GY{-{vm!e!ZGWrJ z;N*Cxs>^%SZQ}vDy)l>>dIi-UKW(7lcc1V8X!L97}dXd^M(Xz7d6+TX%Hjo3Mv1duI{kw zN<}h{hmlc2CFN)NDJUL+!UD_6$q)P;{A;j31~iBx^KbwWAR|F{^t!&i{P5OQ&|;x& z>upt4Rpl_)F<}k@pj-qCCAigB`g0^*2|qdnQsz03AgQRSF<3_{#$8xk`~+yS;qOmz zvCMMnMveEqu(%>1)Bw9*B`4=O;rfo|$GE9tNgU_(E@g31`+2n)%6v@NsM+)9jlX|W zaT!X@FtuA}f;S{i#98IQer33111PZ*V94bjJm|3s^Tc4@MM)B1jWsWA1qQ zwF41n2^0meW}LRL!rVcSe+6vC#s)m+WO3cLi`gDhL;@e&9iY&`^9Go}SFER}cj3YX zEZv7y^GdhyuJNm0&g=>_RNj%`Ok#^9WW4ro+xoN>-L$z(xX7 zBbL1r#~-1%4c6K$ouXCAfU1R%O%M-}5n5bkfYAn_ zZ|+bE`M#1e19~81VmeOZ?qJ)56dr>Z5VxI{2fltz)O5<1k>)jO=?z@{IQ#YF5z=5*bt_YzVLdbqVS!C=dXrV)2{19s7_?{jO7 zcRc6H(M(HAi+}8SrJ_){4&gSSY3zzP`%H|CcwjYfR_-EQzd~1(OhSq44u@X+HN)SEeFYez3kn`#1$BvAQy{PAI~G31J|vbGinKfunY zYj#PA7E1`IwTdx9>IXic(aR_1LeW-HG17q2qA-FkLueC(r+?@%1%rv>WWnG>yUM*{ zPN&uq%oCl73SbfH7SrzP?zRIr4lpMvYH5&CTFX%Rre-f*7-rq*e#hltXGg>cevB?hTcA^*g+1QC0>8A`-`RXtq6s(_5DJ>LnD%39RW9t-Hxx)FbYwiv z`I?dfZtK3@-cHUe4(4r9?uf-vdI%sy9`d&#Wl!%5>0K#GLs65u9{{6s}W$T;;6I~6!v0B8YS3Y-$G zxWnwKAueF678QpFavW; 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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

    +
  • the user releases the mouse + button and the value has changed or +
  • at the end of automatic scrolling.
+ Tracking is enabled by default. + \param enable \c true (enable) or \c false (disable) tracking. +*/ +void QwtAbstractSlider::setTracking(bool enable) +{ + d_data->tracking = enable; +} + +/*! + Mouse Move Event handler + \param e Mouse event +*/ +void QwtAbstractSlider::mouseMoveEvent(QMouseEvent *e) +{ + if ( isReadOnly() ) + { + e->ignore(); + return; + } + + if ( !isValid() ) + return; + + if (d_data->scrollMode == ScrMouse ) + { + setPosition(e->pos()); + if (d_data->mass > 0.0) + { + double ms = double(d_data->time.elapsed()); + if (ms < 1.0) + ms = 1.0; + d_data->speed = (exactValue() - exactPrevValue()) / ms; + d_data->time.start(); + } + if (value() != prevValue()) + emit sliderMoved(value()); + } +} + +/*! + Wheel Event handler + \param e Whell event +*/ +void QwtAbstractSlider::wheelEvent(QWheelEvent *e) +{ + if ( isReadOnly() ) + { + e->ignore(); + return; + } + + if ( !isValid() ) + return; + + int mode = ScrNone, direction = 0; + + // Give derived classes a chance to say ScrNone + getScrollMode(e->pos(), mode, direction); + if ( mode != ScrNone ) + { + const int inc = e->delta() / WHEEL_DELTA; + QwtDoubleRange::incPages(inc); + if (value() != prevValue()) + emit sliderMoved(value()); + } +} + +/*! + Handles key events + + - Key_Down, KeyLeft\n + Decrement by 1 + - Key_Up, Key_Right\n + Increment by 1 + + \param e Key event + \sa isReadOnly() +*/ +void QwtAbstractSlider::keyPressEvent(QKeyEvent *e) +{ + if ( isReadOnly() ) + { + e->ignore(); + return; + } + + if ( !isValid() ) + return; + + int increment = 0; + switch ( e->key() ) + { + case Qt::Key_Down: + if ( orientation() == Qt::Vertical ) + increment = -1; + break; + case Qt::Key_Up: + if ( orientation() == Qt::Vertical ) + increment = 1; + break; + case Qt::Key_Left: + if ( orientation() == Qt::Horizontal ) + increment = -1; + break; + case Qt::Key_Right: + if ( orientation() == Qt::Horizontal ) + increment = 1; + break; + default:; + e->ignore(); + } + + if ( increment != 0 ) + { + QwtDoubleRange::incValue(increment); + if (value() != prevValue()) + emit sliderMoved(value()); + } +} + +/*! + Qt timer event + \param e Timer event +*/ +void QwtAbstractSlider::timerEvent(QTimerEvent *) +{ + const double inc = step(); + + switch (d_data->scrollMode) + { + case ScrMouse: + { + if (d_data->mass > 0.0) + { + d_data->speed *= exp( - double(d_data->updTime) * 0.001 / d_data->mass ); + const double newval = + exactValue() + d_data->speed * double(d_data->updTime); + QwtDoubleRange::fitValue(newval); + // stop if d_data->speed < one step per second + if (fabs(d_data->speed) < 0.001 * fabs(step())) + { + d_data->speed = 0; + stopMoving(); + buttonReleased(); + } + + } + else + stopMoving(); + break; + } + + case ScrPage: + { + QwtDoubleRange::incPages(d_data->direction); + if (!d_data->timerTick) + { + killTimer(d_data->tmrID); + d_data->tmrID = startTimer(d_data->updTime); + } + break; + } + case ScrTimer: + { + QwtDoubleRange::fitValue(value() + double(d_data->direction) * inc); + if (!d_data->timerTick) + { + killTimer(d_data->tmrID); + d_data->tmrID = startTimer(d_data->updTime); + } + break; + } + default: + { + stopMoving(); + break; + } + } + + d_data->timerTick = 1; +} + + +/*! + Notify change of value + + This function can be reimplemented by derived classes + in order to keep track of changes, i.e. repaint the widget. + The default implementation emits a valueChanged() signal + if tracking is enabled. +*/ +void QwtAbstractSlider::valueChange() +{ + if (d_data->tracking) + emit valueChanged(value()); +} + +/*! + \brief Set the slider's mass for flywheel effect. + + If the slider's mass is greater then 0, it will continue + to move after the mouse button has been released. Its speed + decreases with time at a rate depending on the slider's mass. + A large mass means that it will continue to move for a + long time. + + Derived widgets may overload this function to make it public. + + \param val New mass in kg + + \bug If the mass is smaller than 1g, it is set to zero. + The maximal mass is limited to 100kg. + \sa mass() +*/ +void QwtAbstractSlider::setMass(double val) +{ + if (val < 0.001) + d_data->mass = 0.0; + else if (val > 100.0) + d_data->mass = 100.0; + else + d_data->mass = val; +} + +/*! + \return mass + \sa setMass() +*/ +double QwtAbstractSlider::mass() const +{ + return d_data->mass; +} + + +/*! + \brief Move the slider to a specified value + + This function can be used to move the slider to a value + which is not an integer multiple of the step size. + \param val new value + \sa fitValue() +*/ +void QwtAbstractSlider::setValue(double val) +{ + if (d_data->scrollMode == ScrMouse) + stopMoving(); + QwtDoubleRange::setValue(val); +} + + +/*! + \brief Set the slider's value to the nearest integer multiple + of the step size. + + \param value Value + \sa setValue(), incValue() +*/ +void QwtAbstractSlider::fitValue(double value) +{ + if (d_data->scrollMode == ScrMouse) + stopMoving(); + QwtDoubleRange::fitValue(value); +} + +/*! + \brief Increment the value by a specified number of steps + \param steps number of steps + \sa setValue() +*/ +void QwtAbstractSlider::incValue(int steps) +{ + if (d_data->scrollMode == ScrMouse) + stopMoving(); + QwtDoubleRange::incValue(steps); +} + +void QwtAbstractSlider::setMouseOffset(double offset) +{ + d_data->mouseOffset = offset; +} + +double QwtAbstractSlider::mouseOffset() const +{ + return d_data->mouseOffset; +} + +int QwtAbstractSlider::scrollMode() const +{ + return d_data->scrollMode; +} diff --git a/qwt/src/qwt_abstract_slider.h b/qwt/src/qwt_abstract_slider.h new file mode 100644 index 000000000..c0fe7d462 --- /dev/null +++ b/qwt/src/qwt_abstract_slider.h @@ -0,0 +1,193 @@ +/* -*- 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 +#include "qwt_global.h" +#include "qwt_double_range.h" + +/*! + \brief An abstract base class for slider widgets + + QwtAbstractSlider is a base class for + slider widgets. It handles mouse events + and updates the slider's value accordingly. Derived classes + only have to implement the getValue() and + getScrollMode() members, and should react to a + valueChange(), which normally requires repainting. +*/ + +class QWT_EXPORT QwtAbstractSlider : public QWidget, public QwtDoubleRange +{ + Q_OBJECT + Q_PROPERTY( bool readOnly READ isReadOnly WRITE setReadOnly ) + Q_PROPERTY( bool valid READ isValid WRITE setValid ) + Q_PROPERTY( double mass READ mass WRITE setMass ) +#ifndef Q_MOC_RUN // Qt3 moc +#define QWT_PROPERTY Q_PROPERTY + Q_PROPERTY( Orientation orientation + READ orientation WRITE setOrientation ) +#else // Qt4 moc +// MOC_SKIP_BEGIN + Q_PROPERTY( Qt::Orientation orientation + READ orientation WRITE setOrientation ) +// MOC_SKIP_END +#endif + +public: + /*! + Scroll mode + \sa getScrollMode() + */ + enum ScrollMode + { + ScrNone, + ScrMouse, + ScrTimer, + ScrDirect, + ScrPage + }; + + explicit QwtAbstractSlider(Qt::Orientation, QWidget *parent = NULL); + virtual ~QwtAbstractSlider(); + + void setUpdateTime(int t); + void stopMoving(); + void setTracking(bool enable); + + virtual void setMass(double val); + virtual double mass() const; + +#if QT_VERSION >= 0x040000 + virtual void setOrientation(Qt::Orientation o); + Qt::Orientation orientation() const; +#else + virtual void setOrientation(Orientation o); + Orientation orientation() const; +#endif + + bool isReadOnly() const; + + /* + Wrappers for QwtDblRange::isValid/QwtDblRange::setValid made + to be available as Q_PROPERTY in the designer. + */ + + /*! + \sa QwtDblRange::isValid() + */ + bool isValid() const { return QwtDoubleRange::isValid(); } + + /*! + \param valid true/false + \sa QwtDblRange::isValid() + */ + void setValid(bool valid) { QwtDoubleRange::setValid(valid); } + +public slots: + virtual void setValue(double val); + virtual void fitValue(double val); + virtual void incValue(int steps); + + virtual void setReadOnly(bool); + +signals: + + /*! + \brief Notify a change of value. + + In the default setting + (tracking enabled), this signal will be emitted every + time the value changes ( see setTracking() ). + \param value new value + */ + void valueChanged(double value); + + /*! + This signal is emitted when the user presses the + movable part of the slider (start ScrMouse Mode). + */ + 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 + */ + void sliderMoved(double value); + +protected: + virtual void setPosition(const QPoint &); + virtual void valueChange(); + + virtual void timerEvent(QTimerEvent *e); + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void keyPressEvent(QKeyEvent *e); + virtual void wheelEvent(QWheelEvent *e); + + /*! + \brief Determine the value corresponding to a specified poind + + This is an abstract virtual function which is called when + the user presses or releases a mouse button or moves the + mouse. It has to be implemented by the derived class. + \param p point + */ + virtual double getValue(const QPoint & p) = 0; + /*! + \brief Determine what to do when the user presses a mouse button. + + This function is abstract and has to be implemented by derived classes. + It is called on a mousePress event. The derived class can determine + what should happen next in dependence of the position where the mouse + was pressed by returning scrolling mode and direction. QwtAbstractSlider + knows the following modes:
+
QwtAbstractSlider::ScrNone +
Scrolling switched off. Don't change the value. +
QwtAbstractSlider::ScrMouse +
Change the value while the user keeps the + button pressed and moves the mouse. +
QwtAbstractSlider::ScrTimer +
Automatic scrolling. Increment the value + in the specified direction as long as + the user keeps the button pressed. +
QwtAbstractSlider::ScrPage +
Automatic scrolling. Same as ScrTimer, but + increment by page size.
+ + \param p point where the mouse was pressed + \retval scrollMode The scrolling mode + \retval direction direction: 1, 0, or -1. + */ + virtual void getScrollMode( const QPoint &p, + int &scrollMode, int &direction) = 0; + + void setMouseOffset(double); + double mouseOffset() const; + + int scrollMode() const; + +private: + void buttonReleased(); + + 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..35f6fd058 --- /dev/null +++ b/qwt/src/qwt_analog_clock.cpp @@ -0,0 +1,241 @@ +/* -*- 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" + +/*! + Constructor + \param parent Parent widget +*/ +QwtAnalogClock::QwtAnalogClock(QWidget *parent): + QwtDial(parent) +{ + initClock(); +} + +#if QT_VERSION < 0x040000 +/*! + Constructor + \param parent Parent widget + \param name Object name +*/ +QwtAnalogClock::QwtAnalogClock(QWidget* parent, const char *name): + QwtDial(parent, name) +{ + initClock(); +} +#endif + +void QwtAnalogClock::initClock() +{ + setWrapping(true); + setReadOnly(true); + + setOrigin(270.0); + setRange(0.0, 60.0 * 60.0 * 12.0); // seconds + setScale(-1, 5, 60.0 * 60.0); + + setScaleOptions(ScaleTicks | ScaleLabel); + setScaleTicks(1, 0, 8); + scaleDraw()->setSpacing(8); + + QColor knobColor = +#if QT_VERSION < 0x040000 + palette().color(QPalette::Active, QColorGroup::Text); +#else + palette().color(QPalette::Active, QPalette::Text); +#endif + 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((Hand)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 clockhand + \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 ((QwtAnalogClock *)this)->hand(hd); +} + +/*! + \brief Set the current time + + This is the same as QwtAnalogClock::setTime(), but Qt < 3.0 + can't handle default parameters for slots. +*/ +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); +} + +/*! + Find the scale label for a given value + + \param value Value + \return Label +*/ +QwtText QwtAnalogClock::scaleLabel(double value) const +{ + if ( value == 0.0 ) + value = 60.0 * 60.0 * 12.0; + + return QString::number(int(value / (60.0 * 60.0))); +} + +/*! + \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 direction Dummy, not used. + \param cg ColorGroup + + \sa drawHand() +*/ +void QwtAnalogClock::drawNeedle(QPainter *painter, const QPoint ¢er, + int radius, double, QPalette::ColorGroup cg) const +{ + if ( isValid() ) + { + const double hours = value() / (60.0 * 60.0); + const double minutes = (value() - (int)hours * 60.0 * 60.0) / 60.0; + const double seconds = value() - (int)hours * 60.0 * 60.0 + - (int)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++ ) + { + double d = angle[hand]; + if ( direction() == Clockwise ) + d = 360.0 - d; + + d -= origin(); + + drawHand(painter, (Hand)hand, center, radius, d, cg); + } + } +} + +/*! + 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 QPoint ¢er, int 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..519bc7d74 --- /dev/null +++ b/qwt/src/qwt_analog_clock.h @@ -0,0 +1,94 @@ +/* -*- 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 +#include "qwt_global.h" +#include "qwt_dial.h" +#include "qwt_dial_needle.h" + +/*! + \brief An analog clock + + \image html analogclock.png + + \par Example + \verbatim #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); + + \endverbatim + + Qwt is missing a set of good looking hands. + Contributions are very welcome. + + \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 + { + SecondHand, + MinuteHand, + HourHand, + + NHands + }; + + explicit QwtAnalogClock(QWidget* parent = NULL); +#if QT_VERSION < 0x040000 + explicit QwtAnalogClock(QWidget* parent, const char *name); +#endif + virtual ~QwtAnalogClock(); + + virtual void setHand(Hand, QwtDialNeedle *); + const QwtDialNeedle *hand(Hand) const; + QwtDialNeedle *hand(Hand); + +public slots: + void setCurrentTime(); + void setTime(const QTime & = QTime::currentTime()); + +protected: + virtual QwtText scaleLabel(double) const; + + virtual void drawNeedle(QPainter *, const QPoint &, + int radius, double direction, QPalette::ColorGroup) const; + + virtual void drawHand(QPainter *, Hand, const QPoint &, + int radius, double direction, QPalette::ColorGroup) const; + +private: + virtual void setNeedle(QwtDialNeedle *); + void initClock(); + + QwtDialNeedle *d_hand[NHands]; +}; + +#endif diff --git a/qwt/src/qwt_array.h b/qwt/src/qwt_array.h new file mode 100644 index 000000000..c5209cfb9 --- /dev/null +++ b/qwt/src/qwt_array.h @@ -0,0 +1,29 @@ +/* -*- 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 + +#ifndef QWT_ARRAY_H +#define QWT_ARRAY_H + +#include "qwt_global.h" + +/*! + \def QwtArray + */ + +#if QT_VERSION < 0x040000 +#include +#define QwtArray QMemArray +#else +#include +#define QwtArray QVector +#endif + +#endif diff --git a/qwt/src/qwt_arrow_button.cpp b/qwt/src/qwt_arrow_button.cpp new file mode 100644 index 000000000..b4914ceb7 --- /dev/null +++ b/qwt/src/qwt_arrow_button.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 +#include +#include +#include "qwt_math.h" +#include "qwt_polygon.h" +#include "qwt_arrow_button.h" + +static const int MaxNum = 3; +static const int Margin = 2; +static const int Spacing = 1; + +class QwtArrowButton::PrivateData +{ +public: + int num; + Qt::ArrowType arrowType; +}; + + +#if QT_VERSION >= 0x040000 +#include +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; +} +#endif + +/*! + \param num Number of arrows + \param arrowType see Qt::ArowType 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 = qwtLim(num, 1, 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 rect 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() ) + { + int ph, pv; +#if QT_VERSION < 0x040000 + ph = style().pixelMetric( + QStyle::PM_ButtonShiftHorizontal, this); + pv = style().pixelMetric( + QStyle::PM_ButtonShiftVertical, this); + r.moveBy(ph, pv); +#else + QStyleOptionButton option = styleOpt(this); + ph = style()->pixelMetric( + QStyle::PM_ButtonShiftHorizontal, &option, this); + pv = style()->pixelMetric( + QStyle::PM_ButtonShiftVertical, &option, this); + r.translate(ph, pv); +#endif + } + + return r; +} + +#if QT_VERSION >= 0x040000 +/*! + Paint event handler + \param event Paint event +*/ +void QwtArrowButton::paintEvent(QPaintEvent *event) +{ + QPushButton::paintEvent(event); + QPainter painter(this); + drawButtonLabel(&painter); +} +#endif + +/*! + \brief Draw the button label + + \param painter Painter + \sa The Qt Manual on 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; + +#if QT_VERSION >= 0x040000 + arrowRect.translate(dx, dy); +#else + arrowRect.moveBy(dx, dy); +#endif + } + painter->restore(); + + if ( hasFocus() ) + { +#if QT_VERSION >= 0x040000 + QStyleOptionFocusRect option; + option.init(this); + option.backgroundColor = palette().color(QPalette::Background); + + style()->drawPrimitive(QStyle::PE_FrameFocusRect, + &option, painter, this); +#else + const QRect focusRect = + style().subRect(QStyle::SR_PushButtonFocusRect, this); + style().drawPrimitive(QStyle::PE_FocusRect, painter, + focusRect, colorGroup()); +#endif + } +} + +/*! + Draw an arrow int a bounding rect + + \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 +{ + QwtPolygon 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(); +#if QT_VERSION < 0x040000 + painter->setPen(colorGroup().buttonText()); + painter->setBrush(colorGroup().brush(QColorGroup::ButtonText)); +#else + painter->setPen(palette().color(QPalette::ButtonText)); + painter->setBrush(palette().brush(QPalette::ButtonText)); +#endif + painter->drawPolygon(pa); + painter->restore(); +} + +/*! + \return a size hint +*/ +QSize QwtArrowButton::sizeHint() const +{ + return minimumSizeHint(); +} + +/*! + \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(); + +#if QT_VERSION >= 0x040000 + QStyleOption styleOption; + styleOption.init(this); + + const QSize hsz = style()->sizeFromContents(QStyle::CT_PushButton, + &styleOption, sz, this); +#if QT_VERSION < 0x040300 + if ( hsz.width() != 80 ) // avoid a bug in the Cleanlooks style +#endif + sz = hsz; + +#else + sz = style().sizeFromContents(QStyle::CT_PushButton, this, sz); +#endif + + return sz; +} + +/*! + Calculate the size for a arrow that fits into a rect 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 *e) +{ + if ( e->isAutoRepeat() && e->key() == Qt::Key_Space ) + emit clicked(); + + QPushButton::keyPressEvent(e); +} diff --git a/qwt/src/qwt_arrow_button.h b/qwt/src/qwt_arrow_button.h new file mode 100644 index 000000000..9d1a7829b --- /dev/null +++ b/qwt/src/qwt_arrow_button.h @@ -0,0 +1,54 @@ +/* -*- 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 +#include "qwt_global.h" + +/*! + \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: +#if QT_VERSION >= 0x040000 + virtual void paintEvent(QPaintEvent *event); +#endif + + 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_clipper.cpp b/qwt/src/qwt_clipper.cpp new file mode 100644 index 000000000..ccbf0eb06 --- /dev/null +++ b/qwt/src/qwt_clipper.cpp @@ -0,0 +1,522 @@ +/* -*- 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 "qwt_math.h" +#include "qwt_clipper.h" + +static inline QwtDoubleRect boundingRect(const QwtPolygonF &polygon) +{ +#if QT_VERSION < 0x040000 + if (polygon.isEmpty()) + return QwtDoubleRect(0, 0, 0, 0); + + register const QwtDoublePoint *pd = polygon.data(); + + double minx, maxx, miny, maxy; + minx = maxx = pd->x(); + miny = maxy = pd->y(); + pd++; + + for (uint i = 1; i < polygon.size(); i++, pd++) + { + if (pd->x() < minx) + minx = pd->x(); + else if (pd->x() > maxx) + maxx = pd->x(); + if (pd->y() < miny) + miny = pd->y(); + else if (pd->y() > maxy) + maxy = pd->y(); + } + return QwtDoubleRect(minx, miny, maxx - minx, maxy - miny); +#else + return polygon.boundingRect(); +#endif +} + +enum Edge +{ + Left, + Top, + Right, + Bottom, + NEdges +}; + +class QwtPolygonClipper: public QRect +{ +public: + QwtPolygonClipper(const QRect &r); + + QwtPolygon clipPolygon(const QwtPolygon &) const; + +private: + void clipEdge(Edge, const QwtPolygon &, QwtPolygon &) const; + bool insideEdge(const QPoint &, Edge edge) const; + QPoint intersectEdge(const QPoint &p1, + const QPoint &p2, Edge edge) const; + + void addPoint(QwtPolygon &, uint pos, const QPoint &point) const; +}; + +class QwtPolygonClipperF: public QwtDoubleRect +{ +public: + QwtPolygonClipperF(const QwtDoubleRect &r); + QwtPolygonF clipPolygon(const QwtPolygonF &) const; + +private: + void clipEdge(Edge, const QwtPolygonF &, QwtPolygonF &) const; + bool insideEdge(const QwtDoublePoint &, Edge edge) const; + QwtDoublePoint intersectEdge(const QwtDoublePoint &p1, + const QwtDoublePoint &p2, Edge edge) const; + + void addPoint(QwtPolygonF &, uint pos, const QwtDoublePoint &point) const; +}; + +#if QT_VERSION >= 0x040000 +class QwtCircleClipper: public QwtDoubleRect +{ +public: + QwtCircleClipper(const QwtDoubleRect &r); + QwtArray clipCircle( + const QwtDoublePoint &, double radius) const; + +private: + QList cuttingPoints( + Edge, const QwtDoublePoint &pos, double radius) const; + double toAngle(const QwtDoublePoint &, const QwtDoublePoint &) const; +}; +#endif + +QwtPolygonClipper::QwtPolygonClipper(const QRect &r): + QRect(r) +{ +} + +inline void QwtPolygonClipper::addPoint( + QwtPolygon &pa, uint pos, const QPoint &point) const +{ + if ( uint(pa.size()) <= pos ) + pa.resize(pos + 5); + + pa.setPoint(pos, point); +} + +//! Sutherland-Hodgman polygon clipping +QwtPolygon QwtPolygonClipper::clipPolygon(const QwtPolygon &pa) const +{ + if ( contains( pa.boundingRect() ) ) + return pa; + + QwtPolygon cpa(pa.size()); + + clipEdge((Edge)0, pa, cpa); + + for ( uint edge = 1; edge < NEdges; edge++ ) + { + const QwtPolygon rpa = cpa; +#if QT_VERSION < 0x040000 + cpa.detach(); +#endif + clipEdge((Edge)edge, rpa, cpa); + } + + return cpa; +} + +bool QwtPolygonClipper::insideEdge(const QPoint &p, Edge edge) const +{ + switch(edge) + { + case Left: + return p.x() > left(); + case Top: + return p.y() > top(); + case Right: + return p.x() < right(); + case Bottom: + return p.y() < bottom(); + default: + break; + } + + return false; +} + +QPoint QwtPolygonClipper::intersectEdge(const QPoint &p1, + const QPoint &p2, Edge edge ) const +{ + int x=0, y=0; + double m = 0; + + const double dy = p2.y() - p1.y(); + const double dx = p2.x() - p1.x(); + + switch ( edge ) + { + case Left: + x = left(); + m = double(qwtAbs(p1.x() - x)) / qwtAbs(dx); + y = p1.y() + int(dy * m); + break; + case Top: + y = top(); + m = double(qwtAbs(p1.y() - y)) / qwtAbs(dy); + x = p1.x() + int(dx * m); + break; + case Right: + x = right(); + m = double(qwtAbs(p1.x() - x)) / qwtAbs(dx); + y = p1.y() + int(dy * m); + break; + case Bottom: + y = bottom(); + m = double(qwtAbs(p1.y() - y)) / qwtAbs(dy); + x = p1.x() + int(dx * m); + break; + default: + break; + } + + return QPoint(x,y); +} + +void QwtPolygonClipper::clipEdge(Edge edge, + const QwtPolygon &pa, QwtPolygon &cpa) const +{ + if ( pa.count() == 0 ) + { + cpa.resize(0); + return; + } + + unsigned int count = 0; + + QPoint p1 = pa.point(0); + if ( insideEdge(p1, edge) ) + addPoint(cpa, count++, p1); + + const uint nPoints = pa.size(); + for ( uint i = 1; i < nPoints; i++ ) + { + const QPoint p2 = pa.point(i); + if ( insideEdge(p2, edge) ) + { + if ( insideEdge(p1, edge) ) + addPoint(cpa, count++, p2); + else + { + addPoint(cpa, count++, intersectEdge(p1, p2, edge)); + addPoint(cpa, count++, p2); + } + } + else + { + if ( insideEdge(p1, edge) ) + addPoint(cpa, count++, intersectEdge(p1, p2, edge)); + } + p1 = p2; + } + cpa.resize(count); +} + +QwtPolygonClipperF::QwtPolygonClipperF(const QwtDoubleRect &r): + QwtDoubleRect(r) +{ +} + +inline void QwtPolygonClipperF::addPoint(QwtPolygonF &pa, uint pos, const QwtDoublePoint &point) const +{ + if ( uint(pa.size()) <= pos ) + pa.resize(pos + 5); + + pa[(int)pos] = point; +} + +//! Sutherland-Hodgman polygon clipping +QwtPolygonF QwtPolygonClipperF::clipPolygon(const QwtPolygonF &pa) const +{ + if ( contains( ::boundingRect(pa) ) ) + return pa; + + QwtPolygonF cpa(pa.size()); + + clipEdge((Edge)0, pa, cpa); + + for ( uint edge = 1; edge < NEdges; edge++ ) + { + const QwtPolygonF rpa = cpa; +#if QT_VERSION < 0x040000 + cpa.detach(); +#endif + clipEdge((Edge)edge, rpa, cpa); + } + + return cpa; +} + +bool QwtPolygonClipperF::insideEdge(const QwtDoublePoint &p, Edge edge) const +{ + switch(edge) + { + case Left: + return p.x() > left(); + case Top: + return p.y() > top(); + case Right: + return p.x() < right(); + case Bottom: + return p.y() < bottom(); + default: + break; + } + + return false; +} + +QwtDoublePoint QwtPolygonClipperF::intersectEdge(const QwtDoublePoint &p1, + const QwtDoublePoint &p2, Edge edge ) const +{ + double x=0.0, y=0.0; + double m = 0; + + const double dy = p2.y() - p1.y(); + const double dx = p2.x() - p1.x(); + + switch ( edge ) + { + case Left: + x = left(); + m = double(qwtAbs(p1.x() - x)) / qwtAbs(dx); + y = p1.y() + int(dy * m); + break; + case Top: + y = top(); + m = double(qwtAbs(p1.y() - y)) / qwtAbs(dy); + x = p1.x() + int(dx * m); + break; + case Right: + x = right(); + m = double(qwtAbs(p1.x() - x)) / qwtAbs(dx); + y = p1.y() + int(dy * m); + break; + case Bottom: + y = bottom(); + m = double(qwtAbs(p1.y() - y)) / qwtAbs(dy); + x = p1.x() + int(dx * m); + break; + default: + break; + } + + return QwtDoublePoint(x,y); +} + +void QwtPolygonClipperF::clipEdge(Edge edge, + const QwtPolygonF &pa, QwtPolygonF &cpa) const +{ + if ( pa.count() == 0 ) + { + cpa.resize(0); + return; + } + + unsigned int count = 0; + + QwtDoublePoint p1 = pa[0]; + if ( insideEdge(p1, edge) ) + addPoint(cpa, count++, p1); + + const uint nPoints = pa.size(); + for ( uint i = 1; i < nPoints; i++ ) + { + const QwtDoublePoint p2 = pa[(int)i]; + if ( insideEdge(p2, edge) ) + { + if ( insideEdge(p1, edge) ) + addPoint(cpa, count++, p2); + else + { + addPoint(cpa, count++, intersectEdge(p1, p2, edge)); + addPoint(cpa, count++, p2); + } + } + else + { + if ( insideEdge(p1, edge) ) + addPoint(cpa, count++, intersectEdge(p1, p2, edge)); + } + p1 = p2; + } + cpa.resize(count); +} + +#if QT_VERSION >= 0x040000 + +QwtCircleClipper::QwtCircleClipper(const QwtDoubleRect &r): + QwtDoubleRect(r) +{ +} + +QwtArray QwtCircleClipper::clipCircle( + const QwtDoublePoint &pos, double radius) const +{ + QList points; + for ( int edge = 0; edge < NEdges; edge++ ) + points += cuttingPoints((Edge)edge, pos, radius); + + QwtArray intv; + if ( points.size() <= 0 ) + { + QwtDoubleRect cRect(0, 0, 2 * radius, 2* radius); + cRect.moveCenter(pos); + if ( contains(cRect) ) + intv += QwtDoubleInterval(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 = contains(qwtPolar2Pos(pos, radius, + angles[0] + (angles[1] - angles[0]) / 2)); + if ( in ) + { + for ( int i = 0; i < angles.size() - 1; i += 2) + intv += QwtDoubleInterval(angles[i], angles[i+1]); + } + else + { + for ( int i = 1; i < angles.size() - 1; i += 2) + intv += QwtDoubleInterval(angles[i], angles[i+1]); + intv += QwtDoubleInterval(angles.last(), angles.first()); + } + } + + return intv; +} + +double QwtCircleClipper::toAngle( + const QwtDoublePoint &from, const QwtDoublePoint &to) const +{ + if ( from.x() == to.x() ) + return from.y() <= to.y() ? M_PI / 2.0 : 3 * M_PI / 2.0; + + const double m = qwtAbs((to.y() - from.y()) / (to.x() - from.x()) ); + + double angle = ::atan(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 QwtDoublePoint &pos, double radius) const +{ + QList points; + + if ( edge == Left || edge == Right ) + { + const double x = (edge == Left) ? left() : right(); + if ( qwtAbs(pos.x() - x) < radius ) + { + const double off = ::sqrt(qwtSqr(radius) - qwtSqr(pos.x() - x)); + const double y1 = pos.y() + off; + if ( y1 >= top() && y1 <= bottom() ) + points += QwtDoublePoint(x, y1); + const double y2 = pos.y() - off; + if ( y2 >= top() && y2 <= bottom() ) + points += QwtDoublePoint(x, y2); + } + } + else + { + const double y = (edge == Top) ? top() : bottom(); + if ( qwtAbs(pos.y() - y) < radius ) + { + const double off = ::sqrt(qwtSqr(radius) - qwtSqr(pos.y() - y)); + const double x1 = pos.x() + off; + if ( x1 >= left() && x1 <= right() ) + points += QwtDoublePoint(x1, y); + const double x2 = pos.x() - off; + if ( x2 >= left() && x2 <= right() ) + points += QwtDoublePoint(x2, y); + } + } + return points; +} +#endif + +/*! + Sutherland-Hodgman polygon clipping + + \param clipRect Clip rectangle + \param polygon Polygon + + \return Clipped polygon +*/ +QwtPolygon QwtClipper::clipPolygon( + const QRect &clipRect, const QwtPolygon &polygon) +{ + QwtPolygonClipper clipper(clipRect); + return clipper.clipPolygon(polygon); +} + +/*! + Sutherland-Hodgman polygon clipping + + \param clipRect Clip rectangle + \param polygon Polygon + + \return Clipped polygon +*/ +QwtPolygonF QwtClipper::clipPolygonF( + const QwtDoubleRect &clipRect, const QwtPolygonF &polygon) +{ + QwtPolygonClipperF clipper(clipRect); + return clipper.clipPolygon(polygon); +} + +#if QT_VERSION >= 0x040000 +/*! + Circle clipping + + clipCircle() devides 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 +*/ +QwtArray QwtClipper::clipCircle( + const QwtDoubleRect &clipRect, + const QwtDoublePoint ¢er, double radius) +{ + QwtCircleClipper clipper(clipRect); + return clipper.clipCircle(center, radius); +} +#endif diff --git a/qwt/src/qwt_clipper.h b/qwt/src/qwt_clipper.h new file mode 100644 index 000000000..5f17834f7 --- /dev/null +++ b/qwt/src/qwt_clipper.h @@ -0,0 +1,37 @@ +/* -*- 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_array.h" +#include "qwt_polygon.h" +#include "qwt_double_rect.h" +#include "qwt_double_interval.h" + +class QRect; + +/*! + \brief Some clipping algos +*/ + +class QWT_EXPORT QwtClipper +{ +public: + static QwtPolygon clipPolygon(const QRect &, const QwtPolygon &); + static QwtPolygonF clipPolygonF(const QwtDoubleRect &, const QwtPolygonF &); + +#if QT_VERSION >= 0x040000 + static QwtArray clipCircle( + const QwtDoubleRect &, const QwtDoublePoint &, double radius); +#endif +}; + +#endif diff --git a/qwt/src/qwt_color_map.cpp b/qwt/src/qwt_color_map.cpp new file mode 100644 index 000000000..bf04d6cd9 --- /dev/null +++ b/qwt/src/qwt_color_map.cpp @@ -0,0 +1,515 @@ +/* -*- 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_array.h" +#include "qwt_math.h" +#include "qwt_double_interval.h" +#include "qwt_color_map.h" + +#if QT_VERSION < 0x040000 +#include +typedef QValueVector QwtColorTable; +#else +typedef QVector QwtColorTable; +#endif + +class QwtLinearColorMap::ColorStops +{ +public: + ColorStops() + { +#if QT_VERSION >= 0x040000 + _stops.reserve(256); +#endif + } + + void insert(double pos, const QColor &color); + QRgb rgb(QwtLinearColorMap::Mode, double pos) const; + + QwtArray 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; + QwtArray _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; +#if QT_VERSION < 0x040000 + _stops.resize(1, QGArray::SpeedOptim); +#else + _stops.resize(1); +#endif + } + else + { + index = findUpper(pos); + if ( index == (int)_stops.size() || + qwtAbs(_stops[index].pos - pos) >= 0.001 ) + { +#if QT_VERSION < 0x040000 + _stops.resize(_stops.size() + 1, QGArray::SpeedOptim); +#else + _stops.resize(_stops.size() + 1); +#endif + for ( int i = _stops.size() - 1; i > index; i-- ) + _stops[i] = _stops[i-1]; + } + } + + _stops[index] = ColorStop(pos, color); +} + +inline QwtArray QwtLinearColorMap::ColorStops::stops() const +{ + QwtArray positions(_stops.size()); + for ( int i = 0; i < (int)_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[(int)(_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 +*/ +QwtColorTable QwtColorMap::colorTable( + const QwtDoubleInterval &interval) const +{ + QwtColorTable table(256); + + if ( interval.isValid() ) + { + const double step = interval.width() / (table.size() - 1); + for ( int i = 0; i < (int) 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); +} + +//! Copy constructor +QwtLinearColorMap::QwtLinearColorMap(const QwtLinearColorMap &other): + QwtColorMap(other) +{ + d_data = new PrivateData; + *this = other; +} + +/*! + 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 of the coor 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; +} + +//! Assignment operator +QwtLinearColorMap &QwtLinearColorMap::operator=( + const QwtLinearColorMap &other) +{ + QwtColorMap::operator=(other); + *d_data = *other.d_data; + return *this; +} + +//! Clone the color map +QwtColorMap *QwtLinearColorMap::copy() const +{ + QwtLinearColorMap* map = new QwtLinearColorMap(); + *map = *this; + + return map; +} + +/*! + \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 all positions of color stops in increasing order +*/ +QwtArray 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 +*/ +QRgb QwtLinearColorMap::rgb( + const QwtDoubleInterval &interval, double value) const +{ + 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); +} + +/*! + Map a value of a given interval into a color index, between 0 and 255 + + \param interval Range for all values + \param value Value to map into a color index +*/ +unsigned char QwtLinearColorMap::colorIndex( + const QwtDoubleInterval &interval, double value) const +{ + const double width = interval.width(); + + if ( width <= 0.0 || value <= interval.minValue() ) + return 0; + + if ( value >= interval.maxValue() ) + return (unsigned char)255; + + const double ratio = (value - interval.minValue()) / width; + + unsigned char index; + if ( d_data->mode == FixedColors ) + index = (unsigned char)(ratio * 255); // always floor + else + index = (unsigned char)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); +} + +/*! + Copy constructor + \param other Other color map +*/ +QwtAlphaColorMap::QwtAlphaColorMap(const QwtAlphaColorMap &other): + QwtColorMap(other) +{ + d_data = new PrivateData; + *this = other; +} + +//! Destructor +QwtAlphaColorMap::~QwtAlphaColorMap() +{ + delete d_data; +} + +/*! + Assignment operator + \param other Other color map + \return *this +*/ +QwtAlphaColorMap &QwtAlphaColorMap::operator=( + const QwtAlphaColorMap &other) +{ + QwtColorMap::operator=(other); + *d_data = *other.d_data; + return *this; +} + +//! Clone the color map +QwtColorMap *QwtAlphaColorMap::copy() const +{ + QwtAlphaColorMap* map = new QwtAlphaColorMap(); + *map = *this; + + return map; +} + +/*! + 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 QwtDoubleInterval &interval, + double value) const +{ + const double width = interval.width(); + if ( 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 QwtDoubleInterval &, 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..3d54aa395 --- /dev/null +++ b/qwt/src/qwt_color_map.h @@ -0,0 +1,221 @@ +/* -*- 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 +#include +#if QT_VERSION < 0x040000 +#include +#else +#include +#endif +#include "qwt_array.h" +#include "qwt_double_interval.h" + +#if defined(QWT_TEMPLATEDLL) +// MOC_SKIP_BEGIN +template class QWT_EXPORT QwtArray; +// MOC_SKIP_END +#endif + +/*! + \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: + /*! + - RGB\n + The map is intended to map into QRgb values. + - Indexed\n + The map is intended to map into 8 bit values, that + are indices into the color table. + + \sa rgb(), colorIndex(), colorTable() + */ + + enum Format + { + RGB, + Indexed + }; + + QwtColorMap(Format = QwtColorMap::RGB ); + virtual ~QwtColorMap(); + + Format format() const; + + //! Clone the color map + virtual QwtColorMap *copy() const = 0; + + /*! + 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 QwtDoubleInterval &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 QwtDoubleInterval &interval, double value) const = 0; + + QColor color(const QwtDoubleInterval &, double value) const; +#if QT_VERSION < 0x040000 + virtual QValueVector colorTable(const QwtDoubleInterval &) const; +#else + virtual QVector colorTable(const QwtDoubleInterval &) const; +#endif + +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. If + mode() == FixedColors the color is calculated from the next lower + color stop. If mode() == ScaledColors the color is calculated + by interpolating the colors of the adjacent stops. +*/ +class QWT_EXPORT QwtLinearColorMap: public QwtColorMap +{ +public: + /*! + Mode of color map + \sa setMode(), mode() + */ + enum Mode + { + FixedColors, + ScaledColors + }; + + QwtLinearColorMap(QwtColorMap::Format = QwtColorMap::RGB); + QwtLinearColorMap( const QColor &from, const QColor &to, + QwtColorMap::Format = QwtColorMap::RGB); + + QwtLinearColorMap(const QwtLinearColorMap &); + + virtual ~QwtLinearColorMap(); + + QwtLinearColorMap &operator=(const QwtLinearColorMap &); + + virtual QwtColorMap *copy() const; + + void setMode(Mode); + Mode mode() const; + + void setColorInterval(const QColor &color1, const QColor &color2); + void addColorStop(double value, const QColor&); + QwtArray colorStops() const; + + QColor color1() const; + QColor color2() const; + + virtual QRgb rgb(const QwtDoubleInterval &, double value) const; + virtual unsigned char colorIndex( + const QwtDoubleInterval &, double value) const; + + class ColorStops; + +private: + class PrivateData; + PrivateData *d_data; +}; + +/*! + \brief QwtAlphaColorMap variies the alpha value of a color +*/ +class QWT_EXPORT QwtAlphaColorMap: public QwtColorMap +{ +public: + QwtAlphaColorMap(const QColor & = QColor(Qt::gray)); + QwtAlphaColorMap(const QwtAlphaColorMap &); + + virtual ~QwtAlphaColorMap(); + + QwtAlphaColorMap &operator=(const QwtAlphaColorMap &); + + virtual QwtColorMap *copy() const; + + void setColor(const QColor &); + QColor color() const; + + virtual QRgb rgb(const QwtDoubleInterval &, double value) const; + +private: + virtual unsigned char colorIndex( + const QwtDoubleInterval &, 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 QwtDoubleInterval &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_compass.cpp b/qwt/src/qwt_compass.cpp new file mode 100644 index 000000000..9b484e996 --- /dev/null +++ b/qwt/src/qwt_compass.cpp @@ -0,0 +1,318 @@ +/* -*- 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 "qwt_math.h" +#include "qwt_scale_draw.h" +#include "qwt_paint_buffer.h" +#include "qwt_painter.h" +#include "qwt_dial_needle.h" +#include "qwt_compass_rose.h" +#include "qwt_compass.h" + +class QwtCompass::PrivateData +{ +public: + PrivateData(): + rose(NULL) + { + } + + ~PrivateData() + { + delete rose; + } + + QwtCompassRose *rose; + QMap labelMap; +}; + +/*! + \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) +{ + initCompass(); +} + +#if QT_VERSION < 0x040000 + +/*! + \brief Constructor + \param parent Parent widget + \param name Object name + + 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, const char *name): + QwtDial(parent, name) +{ + initCompass(); +} + +#endif + +//! Destructor +QwtCompass::~QwtCompass() +{ + delete d_data; +} + +void QwtCompass::initCompass() +{ + d_data = new PrivateData; + + setScaleOptions(ScaleLabel); // Only labels, no backbone, no ticks + + setOrigin(270.0); + setWrapping(true); + + + d_data->labelMap.insert(0.0, QString::fromLatin1("N")); + d_data->labelMap.insert(45.0, QString::fromLatin1("NE")); + d_data->labelMap.insert(90.0, QString::fromLatin1("E")); + d_data->labelMap.insert(135.0, QString::fromLatin1("SE")); + d_data->labelMap.insert(180.0, QString::fromLatin1("S")); + d_data->labelMap.insert(225.0, QString::fromLatin1("SW")); + d_data->labelMap.insert(270.0, QString::fromLatin1("W")); + d_data->labelMap.insert(315.0, QString::fromLatin1("NW")); + +#if 0 + d_data->labelMap.insert(22.5, QString::fromLatin1("NNE")); + d_data->labelMap.insert(67.5, QString::fromLatin1("NEE")); + d_data->labelMap.insert(112.5, QString::fromLatin1("SEE")); + d_data->labelMap.insert(157.5, QString::fromLatin1("SSE")); + d_data->labelMap.insert(202.5, QString::fromLatin1("SSW")); + d_data->labelMap.insert(247.5, QString::fromLatin1("SWW")); + d_data->labelMap.insert(292.5, QString::fromLatin1("NWW")); + d_data->labelMap.insert(337.5, QString::fromLatin1("NNW")); +#endif +} + +/*! + 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 QPoint ¢er, int 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 QPoint ¢er, + int 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); + } +} + +/*! + \return map, mapping values to labels + \sa setLabelMap() +*/ +const QMap &QwtCompass::labelMap() const +{ + return d_data->labelMap; +} + +/*! + \return map, mapping values to labels + \sa setLabelMap() +*/ +QMap &QwtCompass::labelMap() +{ + return d_data->labelMap; +} + +/*! + \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 QwtCompass::setLabelMap(const QMap &map) +{ + d_data->labelMap = map; +} + +/*! + Map a value to a corresponding label + \param value Value that will be mapped + \return Label, or QString::null + + label() looks in a map for a corresponding label for value + or return an null text. + \sa labelMap(), setLabelMap() +*/ + +QwtText QwtCompass::scaleLabel(double value) const +{ +#if 0 + // better solution ??? + if ( value == -0 ) + value = 0.0; +#endif + + if ( value < 0.0 ) + value += 360.0; + + if ( d_data->labelMap.contains(value) ) + return d_data->labelMap[value]; + + return QwtText(); +} diff --git a/qwt/src/qwt_compass.h b/qwt/src/qwt_compass.h new file mode 100644 index 000000000..68b942964 --- /dev/null +++ b/qwt/src/qwt_compass.h @@ -0,0 +1,85 @@ +/* -*- 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 +#include +#include "qwt_dial.h" + +#if defined(QWT_TEMPLATEDLL) + +#if defined(QT_NO_STL) || QT_VERSION < 0x040000 || QT_VERSION > 0x040001 +/* + Unfortunately Qt 4.0.0/Qt 4.0.1 contains uncompilable + code in the STL adaptors of qmap.h. The declaration below + instantiates this code resulting in compiler errors. + If you really need the map to be exported, remove the condition above + and fix the qmap.h +*/ +// MOC_SKIP_BEGIN +template class QWT_EXPORT QMap; +// MOC_SKIP_END +#endif + +#endif + + +class QwtCompassRose; + +/*! + \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); +#if QT_VERSION < 0x040000 + explicit QwtCompass(QWidget* parent, const char *name); +#endif + virtual ~QwtCompass(); + + void setRose(QwtCompassRose *rose); + const QwtCompassRose *rose() const; + QwtCompassRose *rose(); + + const QMap &labelMap() const; + QMap &labelMap(); + void setLabelMap(const QMap &map); + +protected: + virtual QwtText scaleLabel(double value) const; + + virtual void drawRose(QPainter *, const QPoint ¢er, + int radius, double north, QPalette::ColorGroup) const; + + virtual void drawScaleContents(QPainter *, + const QPoint ¢er, int radius) const; + + virtual void keyPressEvent(QKeyEvent *); + +private: + void initCompass(); + + 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..e85644021 --- /dev/null +++ b/qwt/src/qwt_compass_rose.cpp @@ -0,0 +1,281 @@ +/* -*- 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 "qwt_math.h" +#include "qwt_painter.h" +#include "qwt_compass_rose.h" + +static QPoint cutPoint(QPoint p11, QPoint p12, QPoint p21, QPoint p22) +{ + double dx1 = p12.x() - p11.x(); + double dy1 = p12.y() - p11.y(); + double dx2 = p22.x() - p21.x(); + double dy2 = p22.y() - p21.y(); + + if ( dx1 == 0.0 && dx2 == 0.0 ) + return QPoint(); + + if ( dx1 == 0.0 ) + { + const double m = dy2 / dx2; + const double t = p21.y() - m * p21.x(); + return QPoint(p11.x(), qRound(m * p11.x() + t)); + } + + if ( dx2 == 0 ) + { + const double m = dy1 / dx1; + const double t = p11.y() - m * p11.x(); + return QPoint(p21.x(), qRound(m * p21.x() + t)); + } + + const double m1 = dy1 / dx1; + const double t1 = p11.y() - m1 * p11.x(); + + const double m2 = dy2 / dx2; + const double t2 = p21.y() - m2 * p21.x(); + + if ( m1 == m2 ) + return QPoint(); + + const double x = ( t2 - t1 ) / ( m1 - m2 ); + const double y = t1 + m1 * x; + + return QPoint(qRound(x), qRound(y)); +} + +/*! + Constructor + + \param numThorns Number of thorns + \param numThornLevels Number of thorn levels +*/ +QwtSimpleCompassRose::QwtSimpleCompassRose(int numThorns, int numThornLevels): + d_width(0.2), + d_numThorns(numThorns), + d_numThornLevels(numThornLevels), + d_shrinkFactor(0.9) +{ + const QColor dark(128,128,255); + const QColor light(192,255,255); + + QPalette palette; + for ( int i = 0; i < QPalette::NColorGroups; i++ ) + { +#if QT_VERSION < 0x040000 + palette.setColor((QPalette::ColorGroup)i, + QColorGroup::Dark, dark); + palette.setColor((QPalette::ColorGroup)i, + QColorGroup::Light, light); +#else + palette.setColor((QPalette::ColorGroup)i, + QPalette::Dark, dark); + palette.setColor((QPalette::ColorGroup)i, + QPalette::Light, light); +#endif + } + + setPalette(palette); +} + +/*! + 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 QPoint ¢er, + int radius, double north, QPalette::ColorGroup cg) const +{ +#if QT_VERSION < 0x040000 + QColorGroup colorGroup; + switch(cg) + { + case QPalette::Disabled: + colorGroup = palette().disabled(); + case QPalette::Inactive: + colorGroup = palette().inactive(); + default: + colorGroup = palette().active(); + } + + drawRose(painter, colorGroup, center, radius, north, d_width, + d_numThorns, d_numThornLevels, d_shrinkFactor); +#else + QPalette pal = palette(); + pal.setCurrentColorGroup(cg); + drawRose(painter, pal, center, radius, north, d_width, + d_numThorns, d_numThornLevels, d_shrinkFactor); +#endif +} + +/*! + 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, +#if QT_VERSION < 0x040000 + const QColorGroup &cg, +#else + const QPalette &palette, +#endif + const QPoint ¢er, int 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 = pow(2.0, j) * M_PI / (double)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 = north / 180.0 * M_PI; + for ( double angle = origin; + angle < 2.0 * M_PI + origin; angle += step) + { + const QPoint p = qwtPolar2Pos(center, r, angle); + QPoint p1 = qwtPolar2Pos(center, leafWidth, angle + M_PI_2); + QPoint p2 = qwtPolar2Pos(center, leafWidth, angle - M_PI_2); + + QwtPolygon pa(3); + pa.setPoint(0, center); + pa.setPoint(1, p); + + QPoint p3 = qwtPolar2Pos(center, r, angle + step / 2.0); + p1 = cutPoint(center, p3, p1, p); + pa.setPoint(2, p1); +#if QT_VERSION < 0x040000 + painter->setBrush(cg.brush(QColorGroup::Dark)); +#else + painter->setBrush(palette.brush(QPalette::Dark)); +#endif + painter->drawPolygon(pa); + + QPoint p4 = qwtPolar2Pos(center, r, angle - step / 2.0); + p2 = cutPoint(center, p4, p2, p); + + pa.setPoint(2, p2); +#if QT_VERSION < 0x040000 + painter->setBrush(cg.brush(QColorGroup::Light)); +#else + painter->setBrush(palette.brush(QPalette::Light)); +#endif + painter->drawPolygon(pa); + } + } + 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_width = width; + if (d_width < 0.03) + d_width = 0.03; + + if (d_width > 0.4) + d_width = 0.4; +} + +/*! + 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_numThorns = numThorns; +} + +/*! + \return Number of thorns + \sa setNumThorns(), setNumThornLevels() +*/ +int QwtSimpleCompassRose::numThorns() const +{ + return d_numThorns; +} + +/*! + Set the of thorns levels + + \param numThornLevels Number of thorns levels + \sa setNumThorns(), numThornLevels() +*/ +void QwtSimpleCompassRose::setNumThornLevels(int numThornLevels) +{ + d_numThornLevels = numThornLevels; +} + +/*! + \return Number of thorn levels + \sa setNumThorns(), setNumThornLevels() +*/ +int QwtSimpleCompassRose::numThornLevels() const +{ + return d_numThornLevels; +} diff --git a/qwt/src/qwt_compass_rose.h b/qwt/src/qwt_compass_rose.h new file mode 100644 index 000000000..d5649ee9f --- /dev/null +++ b/qwt/src/qwt_compass_rose.h @@ -0,0 +1,90 @@ +/* -*- 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 +#include "qwt_global.h" + +class QPainter; + +/*! + \brief Abstract base class for a compass rose +*/ +class QWT_EXPORT QwtCompassRose +{ +public: + 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 QPoint ¢er, + int 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); + + void setWidth(double w); + + //! \sa setWidth() + double width() const { return d_width; } + + void setNumThorns(int count); + int numThorns() const; + + void setNumThornLevels(int count); + int numThornLevels() const; + + void setShrinkFactor(double factor) { d_shrinkFactor = factor; } + double shrinkFactor() const { return d_shrinkFactor; } + + virtual void draw(QPainter *, const QPoint ¢er, int radius, + double north, QPalette::ColorGroup = QPalette::Active) const; + + static void drawRose(QPainter *, +#if QT_VERSION < 0x040000 + const QColorGroup &, +#else + const QPalette &, +#endif + const QPoint ¢er, int radius, double origin, double width, + int numThorns, int numThornLevels, double shrinkFactor); + +private: + double d_width; + int d_numThorns; + int d_numThornLevels; + double d_shrinkFactor; +}; + +#endif // QWT_COMPASS_ROSE_H diff --git a/qwt/src/qwt_counter.cpp b/qwt/src/qwt_counter.cpp new file mode 100644 index 000000000..31e87d935 --- /dev/null +++ b/qwt/src/qwt_counter.cpp @@ -0,0 +1,656 @@ +/* -*- 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 "qwt_math.h" +#include "qwt_counter.h" +#include "qwt_arrow_button.h" + +class QwtCounter::PrivateData +{ +public: + PrivateData(): + editable(true) + { + increment[Button1] = 1; + increment[Button2] = 10; + increment[Button3] = 100; + } + + QwtArrowButton *buttonDown[ButtonCnt]; + QwtArrowButton *buttonUp[ButtonCnt]; + QLineEdit *valueEdit; + + int increment[ButtonCnt]; + int nButtons; + + bool editable; +}; + +/*! + 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(); +} + +#if QT_VERSION < 0x040000 +/*! + 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, const char *name): + QWidget(parent, name) +{ + initCounter(); +} +#endif + +void QwtCounter::initCounter() +{ + d_data = new PrivateData; + +#if QT_VERSION >= 0x040000 + using namespace Qt; +#endif + + QHBoxLayout *layout = new QHBoxLayout(this); + layout->setSpacing(0); + layout->setMargin(0); + + int i; + for(i = ButtonCnt - 1; i >= 0; i--) + { + QwtArrowButton *btn = + new QwtArrowButton(i+1, Qt::DownArrow,this); + btn->setFocusPolicy(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); + +#if QT_VERSION >= 0x040000 + connect( d_data->valueEdit, SIGNAL(editingFinished()), + SLOT(textChanged()) ); +#else + connect( d_data->valueEdit, SIGNAL(returnPressed()), SLOT(textChanged()) ); + connect( d_data->valueEdit, SIGNAL(lostFocus()), SLOT(textChanged()) ); +#endif + + layout->setStretchFactor(d_data->valueEdit, 10); + + for(i = 0; i < ButtonCnt; i++) + { +#if QT_VERSION >= 0x040000 + using namespace Qt; +#endif + QwtArrowButton *btn = + new QwtArrowButton(i+1, Qt::UpArrow, this); + btn->setFocusPolicy(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,0.001); + setValue(0.0); + + setSizePolicy( + QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed)); + + setFocusProxy(d_data->valueEdit); + setFocusPolicy(StrongFocus); +} + +//! Destructor +QwtCounter::~QwtCounter() +{ + delete d_data; +} + +/*! + Sets the minimum width for the buttons +*/ +void QwtCounter::polish() +{ + 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); + } + +#if QT_VERSION < 0x040000 + QWidget::polish(); +#endif +} + +//! Set from lineedit +void QwtCounter::textChanged() +{ + if ( !d_data->editable ) + return; + + bool converted = false; + + const double value = d_data->valueEdit->text().toDouble(&converted); + if ( converted ) + setValue( value ); +} + +/** + \brief Allow/disallow the user to manually edit the value + + \param editable true enables editing + \sa editable() +*/ +void QwtCounter::setEditable(bool editable) +{ +#if QT_VERSION >= 0x040000 + using namespace Qt; +#endif + if ( editable == d_data->editable ) + return; + + d_data->editable = editable; + d_data->valueEdit->setReadOnly(!editable); +} + +//! returns whether the line edit is edatble. (default is yes) +bool QwtCounter::editable() const +{ + return d_data->editable; +} + +/*! + Handle PolishRequest events +*/ +bool QwtCounter::event ( QEvent * e ) +{ +#if QT_VERSION >= 0x040000 + if ( e->type() == QEvent::PolishRequest ) + polish(); +#endif + return QWidget::event(e); +} + +/*! + Handle key events + + - Ctrl + Qt::Key_Home + Step to minValue() + - Ctrl + Qt::Key_End + Step to maxValue() + - Qt::Key_Up + Increment by incSteps(QwtCounter::Button1) + - Qt::Key_Down + Decrement by incSteps(QwtCounter::Button1) + - Qt::Key_PageUp + Increment by incSteps(QwtCounter::Button2) + - Qt::Key_PageDown + Decrement by incSteps(QwtCounter::Button2) + - Shift + Qt::Key_PageUp + Increment by incSteps(QwtCounter::Button3) + - Shift + Qt::Key_PageDown + Decrement by incSteps(QwtCounter::Button3) +*/ +void QwtCounter::keyPressEvent (QKeyEvent *e) +{ + bool accepted = true; + + switch ( e->key() ) + { + case Qt::Key_Home: +#if QT_VERSION >= 0x040000 + if ( e->modifiers() & Qt::ControlModifier ) +#else + if ( e->state() & Qt::ControlButton ) +#endif + setValue(minValue()); + else + accepted = false; + break; + case Qt::Key_End: +#if QT_VERSION >= 0x040000 + if ( e->modifiers() & Qt::ControlModifier ) +#else + if ( e->state() & Qt::ControlButton ) +#endif + setValue(maxValue()); + else + accepted = false; + break; + case Qt::Key_Up: + incValue(d_data->increment[0]); + break; + case Qt::Key_Down: + incValue(-d_data->increment[0]); + break; + case Qt::Key_PageUp: + case Qt::Key_PageDown: + { + int increment = d_data->increment[0]; + if ( d_data->nButtons >= 2 ) + increment = d_data->increment[1]; + if ( d_data->nButtons >= 3 ) + { +#if QT_VERSION >= 0x040000 + if ( e->modifiers() & Qt::ShiftModifier ) +#else + if ( e->state() & Qt::ShiftButton ) +#endif + increment = d_data->increment[2]; + } + if ( e->key() == Qt::Key_PageDown ) + increment = -increment; + incValue(increment); + break; + } + default: + accepted = false; + } + + if ( accepted ) + { + e->accept(); + return; + } + + QWidget::keyPressEvent (e); +} + +/*! + Handle wheel events + \param e Wheel event +*/ +void QwtCounter::wheelEvent(QWheelEvent *e) +{ + e->accept(); + + if ( d_data->nButtons <= 0 ) + return; + + int increment = d_data->increment[0]; + if ( d_data->nButtons >= 2 ) + { +#if QT_VERSION >= 0x040000 + if ( e->modifiers() & Qt::ControlModifier ) +#else + if ( e->state() & Qt::ControlButton ) +#endif + increment = d_data->increment[1]; + } + if ( d_data->nButtons >= 3 ) + { +#if QT_VERSION >= 0x040000 + if ( e->modifiers() & Qt::ShiftModifier ) +#else + if ( e->state() & Qt::ShiftButton ) +#endif + increment = d_data->increment[2]; + } + + for ( int i = 0; i < d_data->nButtons; i++ ) + { + if ( d_data->buttonDown[i]->geometry().contains(e->pos()) || + d_data->buttonUp[i]->geometry().contains(e->pos()) ) + { + increment = d_data->increment[i]; + } + } + + const int wheel_delta = 120; + + int delta = e->delta(); + if ( delta >= 2 * wheel_delta ) + delta /= 2; // Never saw an abs(delta) < 240 + + incValue(delta / wheel_delta * increment); +} + +/*! + Specify the number of steps by which the value + is incremented or decremented when a specified button + is pushed. + + \param btn One of \c QwtCounter::Button1, \c QwtCounter::Button2, + \c QwtCounter::Button3 + \param nSteps Number of steps +*/ +void QwtCounter::setIncSteps(QwtCounter::Button btn, int nSteps) +{ + if (( btn >= 0) && (btn < ButtonCnt)) + d_data->increment[btn] = nSteps; +} + +/*! + \return the number of steps by which a specified button increments the value + or 0 if the button is invalid. + \param btn One of \c QwtCounter::Button1, \c QwtCounter::Button2, + \c QwtCounter::Button3 +*/ +int QwtCounter::incSteps(QwtCounter::Button btn) const +{ + if (( btn >= 0) && (btn < ButtonCnt)) + return d_data->increment[btn]; + + return 0; +} + +/*! + \brief Set a new value + \param v new value + Calls QwtDoubleRange::setValue and does all visual updates. + \sa QwtDoubleRange::setValue() +*/ + +void QwtCounter::setValue(double v) +{ + QwtDoubleRange::setValue(v); + + showNum(value()); + updateButtons(); +} + +/*! + \brief Notify a change of value +*/ +void QwtCounter::valueChange() +{ + if ( isValid() ) + showNum(value()); + else + d_data->valueEdit->setText(QString::null); + + updateButtons(); + + if ( isValid() ) + emit valueChanged(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 ( 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 < ButtonCnt; i++ ) + { + d_data->buttonDown[i]->setEnabled(value() > minValue()); + d_data->buttonUp[i]->setEnabled(value() < maxValue()); + } + } + else + { + for ( int i = 0; i < ButtonCnt; i++ ) + { + d_data->buttonDown[i]->setEnabled(false); + d_data->buttonUp[i]->setEnabled(false); + } + } +} + +/*! + \brief Specify the number of buttons on each side of the label + \param n Number of buttons +*/ +void QwtCounter::setNumButtons(int n) +{ + if ( n<0 || n>ButtonCnt ) + return; + + for ( int i = 0; i < ButtonCnt; i++ ) + { + if ( i < n ) + { + d_data->buttonDown[i]->show(); + d_data->buttonUp[i]->show(); + } + else + { + d_data->buttonDown[i]->hide(); + d_data->buttonUp[i]->hide(); + } + } + + d_data->nButtons = n; +} + +/*! + \return The number of buttons on each side of the widget. +*/ +int QwtCounter::numButtons() const +{ + return d_data->nButtons; +} + +/*! + Display number string + + \param number Number +*/ +void QwtCounter::showNum(double number) +{ + QString v; + v.setNum(number); + + const int cursorPos = d_data->valueEdit->cursorPosition(); + d_data->valueEdit->setText(v); + d_data->valueEdit->setCursorPosition(cursorPos); +} + +//! Button clicked +void QwtCounter::btnClicked() +{ + for ( int i = 0; i < ButtonCnt; i++ ) + { + if ( d_data->buttonUp[i] == sender() ) + incValue(d_data->increment[i]); + + if ( d_data->buttonDown[i] == sender() ) + incValue(-d_data->increment[i]); + } +} + +//! Button released +void QwtCounter::btnReleased() +{ + emit buttonReleased(value()); +} + +/*! + \brief Notify change of range + + This function updates the enabled property of + all buttons contained in QwtCounter. +*/ +void QwtCounter::rangeChange() +{ + updateButtons(); +} + +//! A size hint +QSize QwtCounter::sizeHint() const +{ + QString tmp; + + int w = tmp.setNum(minValue()).length(); + int w1 = tmp.setNum(maxValue()).length(); + if ( w1 > w ) + w = w1; + w1 = tmp.setNum(minValue() + step()).length(); + if ( w1 > w ) + w = w1; + w1 = tmp.setNum(maxValue() - step()).length(); + if ( w1 > w ) + w = w1; + + tmp.fill('9', w); + + QFontMetrics fm(d_data->valueEdit->font()); + w = fm.width(tmp) + 2; +#if QT_VERSION >= 0x040000 + if ( d_data->valueEdit->hasFrame() ) + w += 2 * style()->pixelMetric(QStyle::PM_DefaultFrameWidth); +#else + w += 2 * d_data->valueEdit->frameWidth(); +#endif + + // 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 = qwtMin(QWidget::sizeHint().height(), + d_data->valueEdit->minimumSizeHint().height()); + return QSize(w, h); +} + +//! returns the step size +double QwtCounter::step() const +{ + return QwtDoubleRange::step(); +} + +/*! + Set the step size + \param stepSize Step size + \sa QwtDoubleRange::setStep() +*/ +void QwtCounter::setStep(double stepSize) +{ + QwtDoubleRange::setStep(stepSize); +} + +//! returns the minimum value of the range +double QwtCounter::minVal() const +{ + return minValue(); +} + +/*! + Set the minimum value of the range + + \param value Minimum value + \sa setMaxValue(), minVal() +*/ +void QwtCounter::setMinValue(double value) +{ + setRange(value, maxValue(), step()); +} + +//! returns the maximum value of the range +double QwtCounter::maxVal() const +{ + return QwtDoubleRange::maxValue(); +} + +/*! + Set the maximum value of the range + + \param value Maximum value + \sa setMinValue(), maxVal() +*/ +void QwtCounter::setMaxValue(double value) +{ + setRange(minValue(), value, step()); +} + +/*! + Set the number of increment steps for button 1 + \param nSteps Number of steps +*/ +void QwtCounter::setStepButton1(int nSteps) +{ + setIncSteps(Button1, nSteps); +} + +//! returns the number of increment steps for button 1 +int QwtCounter::stepButton1() const +{ + return incSteps(Button1); +} + +/*! + Set the number of increment steps for button 2 + \param nSteps Number of steps +*/ +void QwtCounter::setStepButton2(int nSteps) +{ + setIncSteps(Button2, nSteps); +} + +//! returns the number of increment steps for button 2 +int QwtCounter::stepButton2() const +{ + return incSteps(Button2); +} + +/*! + Set the number of increment steps for button 3 + \param nSteps Number of steps +*/ +void QwtCounter::setStepButton3(int nSteps) +{ + setIncSteps(Button3, nSteps); +} + +//! returns the number of increment steps for button 3 +int QwtCounter::stepButton3() const +{ + return incSteps(Button3); +} + +//! \return Current value +double QwtCounter::value() const +{ + return QwtDoubleRange::value(); +} + diff --git a/qwt/src/qwt_counter.h b/qwt/src/qwt_counter.h new file mode 100644 index 000000000..e92e2fc91 --- /dev/null +++ b/qwt/src/qwt_counter.h @@ -0,0 +1,157 @@ +/* -*- 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 + +#ifndef QWT_COUNTER_H +#define QWT_COUNTER_H + +#include +#include "qwt_global.h" +#include "qwt_double_range.h" + +/*! + \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. The range can be specified using + QwtDblRange::setRange(). + The counter's value is an integer multiple of the step size. + The number of steps by which a button increments or decrements + the value can be specified using QwtCounter::setIncSteps(). + The number of buttons can be changed with + QwtCounter::setNumButtons(). + + Holding the space bar down with focus on a button is the + fastest method to step through the counter values. + When the counter underflows/overflows, the focus is set + to the smallest up/down button and counting is disabled. + Counting is re-enabled on a button release event (mouse or + space bar). + + Example: +\code +#include "../include/qwt_counter.h> + +QwtCounter *cnt; + +cnt = new QwtCounter(parent, name); + +cnt->setRange(0.0, 100.0, 1.0); // From 0.0 to 100, step 1.0 +cnt->setNumButtons(2); // Two buttons each side +cnt->setIncSteps(QwtCounter::Button1, 1); // Button 1 increments 1 step +cnt->setIncSteps(QwtCounter::Button2, 20); // Button 2 increments 20 steps + +connect(cnt, SIGNAL(valueChanged(double)), my_class, SLOT(newValue(double))); +\endcode + */ + +class QWT_EXPORT QwtCounter : public QWidget, public QwtDoubleRange +{ + Q_OBJECT + + Q_PROPERTY( int numButtons READ numButtons WRITE setNumButtons ) + Q_PROPERTY( double basicstep READ step WRITE setStep ) + Q_PROPERTY( double minValue READ minVal WRITE setMinValue ) + Q_PROPERTY( double maxValue READ maxVal WRITE setMaxValue ) + 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( double value READ value WRITE setValue ) + Q_PROPERTY( bool editable READ editable WRITE setEditable ) + +public: + /*! + Button index + */ + + enum Button + { + Button1, + Button2, + Button3, + ButtonCnt + }; + + explicit QwtCounter(QWidget *parent = NULL); +#if QT_VERSION < 0x040000 + explicit QwtCounter(QWidget *parent, const char *name); +#endif + virtual ~QwtCounter(); + + bool editable() const; + void setEditable(bool); + + void setNumButtons(int n); + int numButtons() const; + + void setIncSteps(QwtCounter::Button btn, int nSteps); + int incSteps(QwtCounter::Button btn) const; + + virtual void setValue(double); + virtual QSize sizeHint() const; + + virtual void polish(); + + // a set of dummies to help the designer + + double step() const; + void setStep(double s); + double minVal() const; + void setMinValue(double m); + double maxVal() const; + void setMaxValue(double m); + void setStepButton1(int nSteps); + int stepButton1() const; + void setStepButton2(int nSteps); + int stepButton2() const; + void setStepButton3(int nSteps); + int stepButton3() const; + virtual double value() const; + +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 *); + virtual void rangeChange(); + +private slots: + void btnReleased(); + void btnClicked(); + void textChanged(); + +private: + void initCounter(); + void updateButtons(); + void showNum(double); + virtual void valueChange(); + + 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..0e352cb74 --- /dev/null +++ b/qwt/src/qwt_curve_fitter.cpp @@ -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 + *****************************************************************************/ + +#include "qwt_math.h" +#include "qwt_spline.h" +#include "qwt_curve_fitter.h" + +//! 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; +} + +void QwtSplineCurveFitter::setSpline(const QwtSpline &spline) +{ + d_data->spline = spline; + d_data->spline.reset(); +} + +const QwtSpline &QwtSplineCurveFitter::spline() const +{ + return d_data->spline; +} + +QwtSpline &QwtSplineCurveFitter::spline() +{ + return d_data->spline; +} + +void QwtSplineCurveFitter::setSplineSize(int splineSize) +{ + d_data->splineSize = qwtMax(splineSize, 10); +} + +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 +*/ +#if QT_VERSION < 0x040000 +QwtArray QwtSplineCurveFitter::fitCurve( + const QwtArray & points) const +#else +QPolygonF QwtSplineCurveFitter::fitCurve(const QPolygonF &points) const +#endif +{ + const int size = (int)points.size(); + if ( size <= 2 ) + return points; + + FitMode fitMode = d_data->fitMode; + if ( fitMode == Auto ) + { + fitMode = Spline; + + const QwtDoublePoint *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); +} + +#if QT_VERSION < 0x040000 +QwtArray QwtSplineCurveFitter::fitSpline( + const QwtArray &points) const +#else +QPolygonF QwtSplineCurveFitter::fitSpline( + const QPolygonF &points) const +#endif +{ + d_data->spline.setPoints(points); + if ( !d_data->spline.isValid() ) + return points; + +#if QT_VERSION < 0x040000 + QwtArray fittedPoints(d_data->splineSize); +#else + QPolygonF fittedPoints(d_data->splineSize); +#endif + + 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++) + { + QwtDoublePoint &p = fittedPoints[i]; + + const double v = x1 + i * delta; + const double sv = d_data->spline.value(v); + + p.setX(qRound(v)); + p.setY(qRound(sv)); + } + d_data->spline.reset(); + + return fittedPoints; +} + +#if QT_VERSION < 0x040000 +QwtArray QwtSplineCurveFitter::fitParametric( + const QwtArray &points) const +#else +QPolygonF QwtSplineCurveFitter::fitParametric( + const QPolygonF &points) const +#endif +{ + int i; + const int size = points.size(); + +#if QT_VERSION < 0x040000 + QwtArray fittedPoints(d_data->splineSize); + QwtArray splinePointsX(size); + QwtArray splinePointsY(size); +#else + QPolygonF fittedPoints(d_data->splineSize); + QPolygonF splinePointsX(size); + QPolygonF splinePointsY(size); +#endif + + const QwtDoublePoint *p = points.data(); + QwtDoublePoint *spX = splinePointsX.data(); + QwtDoublePoint *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 = sqrt( qwtSqr(x - spX[i-1].y()) + + qwtSqr( y - spY[i-1].y() ) ); + param += qwtMax(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(qRound(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(qRound(d_data->spline.value(dtmp))); + } + + return fittedPoints; +} diff --git a/qwt/src/qwt_curve_fitter.h b/qwt/src/qwt_curve_fitter.h new file mode 100644 index 000000000..e17e3f8c3 --- /dev/null +++ b/qwt/src/qwt_curve_fitter.h @@ -0,0 +1,116 @@ +/* -*- 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 "qwt_double_rect.h" + +class QwtSpline; + +#if QT_VERSION >= 0x040000 +#include +#else +#include "qwt_array.h" +#endif + +// MOC_SKIP_BEGIN + +#if defined(QWT_TEMPLATEDLL) + +#if QT_VERSION < 0x040000 +#ifndef QWTARRAY_TEMPLATE_QWTDOUBLEPOINT // by mjo3 +#define QWTARRAY_TEMPLATE_QWTDOUBLEPOINT +template class QWT_EXPORT QwtArray; +#endif //end of QWTARRAY_TEMPLATE_QWTDOUBLEPOINT +#endif + +#endif + +// MOC_SKIP_END + +/*! + \brief Abstract base class for a curve fitter +*/ +class QWT_EXPORT QwtCurveFitter +{ +public: + virtual ~QwtCurveFitter(); + +#if QT_VERSION < 0x040000 + virtual QwtArray fitCurve( + const QwtArray&) const = 0; +#else + /*! + 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; +#endif + +protected: + QwtCurveFitter(); + +private: + QwtCurveFitter( const QwtCurveFitter & ); + QwtCurveFitter &operator=( const QwtCurveFitter & ); +}; + +/*! + \brief A curve fitter using cubic splines +*/ +class QWT_EXPORT QwtSplineCurveFitter: public QwtCurveFitter +{ +public: + enum FitMode + { + Auto, + Spline, + 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; + +#if QT_VERSION < 0x040000 + virtual QwtArray fitCurve( + const QwtArray &) const; +#else + virtual QPolygonF fitCurve(const QPolygonF &) const; +#endif + +private: +#if QT_VERSION < 0x040000 + QwtArray fitSpline( + const QwtArray &) const; + QwtArray fitParametric( + const QwtArray &) const; +#else + QPolygonF fitSpline(const QPolygonF &) const; + QPolygonF fitParametric(const QPolygonF &) const; +#endif + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_data.cpp b/qwt/src/qwt_data.cpp new file mode 100644 index 000000000..caf73d4cc --- /dev/null +++ b/qwt/src/qwt_data.cpp @@ -0,0 +1,384 @@ +/* -*- 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" +#include "qwt_data.h" + +//! Constructor +QwtData::QwtData() +{ +} + +//! Destructor +QwtData::~QwtData() +{ +} + +/*! + Returns the bounding rectangle of the data. If there is + no bounding rect, like for empty data the rectangle is invalid: + QwtDoubleRect::isValid() == false + + \warning This is an slow implementation iterating over all points. + It is intended to be overloaded by derived classes. In case of + auto scaling boundingRect() is called for every replot, so it + might be worth to implement a cache, or use x(0), x(size() - 1) + for ordered data ... +*/ +QwtDoubleRect QwtData::boundingRect() const +{ + const size_t sz = size(); + + if ( sz <= 0 ) + return QwtDoubleRect(1.0, 1.0, -2.0, -2.0); // invalid + + double minX, maxX, minY, maxY; + minX = maxX = x(0); + minY = maxY = y(0); + + for ( size_t i = 1; i < sz; i++ ) + { + const double xv = x(i); + if ( xv < minX ) + minX = xv; + if ( xv > maxX ) + maxX = xv; + + const double yv = y(i); + if ( yv < minY ) + minY = yv; + if ( yv > maxY ) + maxY = yv; + } + return QwtDoubleRect(minX, minY, maxX - minX, maxY - minY); +} + +/*! + Constructor + + \param polygon Polygon data + \sa QwtPlotCurve::setData() +*/ +#if QT_VERSION >= 0x040000 +QwtPolygonFData::QwtPolygonFData(const QPolygonF &polygon): +#else +QwtPolygonFData::QwtPolygonFData(const QwtArray &polygon): +#endif + d_data(polygon) +{ +} + +//! Assignment +QwtPolygonFData& QwtPolygonFData::operator=( + const QwtPolygonFData &data) +{ + if (this != &data) + { + d_data = data.d_data; + } + return *this; +} + +//! \return Size of the data set +size_t QwtPolygonFData::size() const +{ + return d_data.size(); +} + +/*! + Return the x value of data point i + + \param i Index + \return x X value of data point i +*/ +double QwtPolygonFData::x(size_t i) const +{ + return d_data[int(i)].x(); +} + +/*! + Return the y value of data point i + + \param i Index + \return y Y value of data point i +*/ +double QwtPolygonFData::y(size_t i) const +{ + return d_data[int(i)].y(); +} + +//! \return Point array +#if QT_VERSION >= 0x040000 +const QPolygonF &QwtPolygonFData::data() const +#else +const QwtArray &QwtPolygonFData::data() const +#endif +{ + return d_data; +} + +/*! + \return Pointer to a copy (virtual copy constructor) +*/ +QwtData *QwtPolygonFData::copy() const +{ + return new QwtPolygonFData(d_data); +} + +/*! + Constructor + + \param x Array of x values + \param y Array of y values + + \sa QwtPlotCurve::setData() +*/ +QwtArrayData::QwtArrayData( + const QwtArray &x, const QwtArray &y): + d_x(x), + d_y(y) +{ +} + +/*! + Constructor + + \param x Array of x values + \param y Array of y values + \param size Size of the x and y arrays + \sa QwtPlotCurve::setData() +*/ +QwtArrayData::QwtArrayData(const double *x, const double *y, size_t size) +{ +#if QT_VERSION >= 0x040000 + d_x.resize(size); + qMemCopy(d_x.data(), x, size * sizeof(double)); + + d_y.resize(size); + qMemCopy(d_y.data(), y, size * sizeof(double)); +#else + d_x.detach(); + d_x.duplicate(x, size); + + d_y.detach(); + d_y.duplicate(y, size); +#endif +} + +//! Assignment +QwtArrayData& QwtArrayData::operator=(const QwtArrayData &data) +{ + if (this != &data) + { + d_x = data.d_x; + d_y = data.d_y; + } + return *this; +} + +//! \return Size of the data set +size_t QwtArrayData::size() const +{ + return qwtMin(d_x.size(), d_y.size()); +} + +/*! + Return the x value of data point i + + \param i Index + \return x X value of data point i +*/ +double QwtArrayData::x(size_t i) const +{ + return d_x[int(i)]; +} + +/*! + Return the y value of data point i + + \param i Index + \return y Y value of data point i +*/ +double QwtArrayData::y(size_t i) const +{ + return d_y[int(i)]; +} + +//! \return Array of the x-values +const QwtArray &QwtArrayData::xData() const +{ + return d_x; +} + +//! \return Array of the y-values +const QwtArray &QwtArrayData::yData() const +{ + return d_y; +} + +/*! + \return Pointer to a copy (virtual copy constructor) +*/ +QwtData *QwtArrayData::copy() const +{ + return new QwtArrayData(d_x, d_y); +} + +/*! + Returns the bounding rectangle of the data. If there is + no bounding rect, like for empty data the rectangle is invalid: + QwtDoubleRect::isValid() == false +*/ +QwtDoubleRect QwtArrayData::boundingRect() const +{ + const size_t sz = size(); + + if ( sz <= 0 ) + return QwtDoubleRect(1.0, 1.0, -2.0, -2.0); // invalid + + double minX, maxX, minY, maxY; + QwtArray::ConstIterator xIt = d_x.begin(); + QwtArray::ConstIterator yIt = d_y.begin(); + QwtArray::ConstIterator end = d_x.begin() + sz; + minX = maxX = *xIt++; + minY = maxY = *yIt++; + + while ( xIt < end ) + { + const double xv = *xIt++; + if ( xv < minX ) + minX = xv; + if ( xv > maxX ) + maxX = xv; + + const double yv = *yIt++; + if ( yv < minY ) + minY = yv; + if ( yv > maxY ) + maxY = yv; + } + return QwtDoubleRect(minX, minY, maxX - minX, maxY - minY); +} + +/*! + Constructor + + \param x Array of x values + \param y Array of y values + \param size Size of the x and y arrays + + \warning The programmer must assure that the memory blocks referenced + by the pointers remain valid during the lifetime of the + QwtPlotCPointer object. + + \sa QwtPlotCurve::setData(), QwtPlotCurve::setRawData() +*/ +QwtCPointerData::QwtCPointerData( + const double *x, const double *y, size_t size): + d_x(x), + d_y(y), + d_size(size) +{ +} + +//! Assignment +QwtCPointerData& QwtCPointerData::operator=(const QwtCPointerData &data) +{ + if (this != &data) + { + d_x = data.d_x; + d_y = data.d_y; + d_size = data.d_size; + } + return *this; +} + +//! \return Size of the data set +size_t QwtCPointerData::size() const +{ + return d_size; +} + +/*! + Return the x value of data point i + + \param i Index + \return x X value of data point i +*/ +double QwtCPointerData::x(size_t i) const +{ + return d_x[int(i)]; +} + +/*! + Return the y value of data point i + + \param i Index + \return y Y value of data point i +*/ +double QwtCPointerData::y(size_t i) const +{ + return d_y[int(i)]; +} + +//! \return Array of the x-values +const double *QwtCPointerData::xData() const +{ + return d_x; +} + +//! \return Array of the y-values +const double *QwtCPointerData::yData() const +{ + return d_y; +} + +/*! + \return Pointer to a copy (virtual copy constructor) +*/ +QwtData *QwtCPointerData::copy() const +{ + return new QwtCPointerData(d_x, d_y, d_size); +} + +/*! + Returns the bounding rectangle of the data. If there is + no bounding rect, like for empty data the rectangle is invalid: + QwtDoubleRect::isValid() == false +*/ +QwtDoubleRect QwtCPointerData::boundingRect() const +{ + const size_t sz = size(); + + if ( sz <= 0 ) + return QwtDoubleRect(1.0, 1.0, -2.0, -2.0); // invalid + + double minX, maxX, minY, maxY; + const double *xIt = d_x; + const double *yIt = d_y; + const double *end = d_x + sz; + minX = maxX = *xIt++; + minY = maxY = *yIt++; + + while ( xIt < end ) + { + const double xv = *xIt++; + if ( xv < minX ) + minX = xv; + if ( xv > maxX ) + maxX = xv; + + const double yv = *yIt++; + if ( yv < minY ) + minY = yv; + if ( yv > maxY ) + maxY = yv; + } + return QwtDoubleRect(minX, minY, maxX - minX, maxY - minY); +} diff --git a/qwt/src/qwt_data.h b/qwt/src/qwt_data.h new file mode 100644 index 000000000..03b90bf70 --- /dev/null +++ b/qwt/src/qwt_data.h @@ -0,0 +1,166 @@ +/* -*- 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 + +#ifndef QWT_DATA_H +#define QWT_DATA_H 1 + +#include "qwt_global.h" +#include "qwt_array.h" +#include "qwt_double_rect.h" +#if QT_VERSION >= 0x040000 +#include +#endif + +// MOC_SKIP_BEGIN + +#if defined(QWT_TEMPLATEDLL) + +template class QWT_EXPORT QwtArray; + +#if QT_VERSION < 0x040000 +#ifndef QWTARRAY_TEMPLATE_QWTDOUBLEPOINT // by mjo3 +#define QWTARRAY_TEMPLATE_QWTDOUBLEPOINT +template class QWT_EXPORT QwtArray; +#endif //end of QWTARRAY_TEMPLATE_QWTDOUBLEPOINT +#endif + +#endif + +// MOC_SKIP_END + +/*! + \brief QwtData defines an interface to any type of curve data. + + Classes, derived from QwtData may: + - store the data in almost any type of container + - calculate the data on the fly instead of storing it + */ + +class QWT_EXPORT QwtData +{ +public: + QwtData(); + virtual ~QwtData(); + + //! \return Pointer to a copy (virtual copy constructor) + virtual QwtData *copy() const = 0; + + //! \return Size of the data set + virtual size_t size() const = 0; + + /*! + Return the x value of data point i + \param i Index + \return x X value of data point i + */ + virtual double x(size_t i) const = 0; + /*! + Return the y value of data point i + \param i Index + \return y Y value of data point i + */ + virtual double y(size_t i) const = 0; + + virtual QwtDoubleRect boundingRect() const; + +protected: + /*! + Assignment operator (virtualized) + */ + QwtData &operator=(const QwtData &); +}; + + +/*! + \brief Data class containing a single QwtArray object. + */ +class QWT_EXPORT QwtPolygonFData: public QwtData +{ +public: +#if QT_VERSION < 0x040000 + QwtPolygonFData(const QwtArray &); +#else + QwtPolygonFData(const QPolygonF &); +#endif + + QwtPolygonFData &operator=(const QwtPolygonFData &); + virtual QwtData *copy() const; + + virtual size_t size() const; + virtual double x(size_t i) const; + virtual double y(size_t i) const; + +#if QT_VERSION < 0x040000 + const QwtArray &data() const; +#else + const QPolygonF &data() const; +#endif + +private: +#if QT_VERSION < 0x040000 + QwtArray d_data; +#else + QPolygonF d_data; +#endif +}; + +/*! + \brief Data class containing two QwtArray objects. + */ + +class QWT_EXPORT QwtArrayData: public QwtData +{ +public: + QwtArrayData(const QwtArray &x, const QwtArray &y); + QwtArrayData(const double *x, const double *y, size_t size); + QwtArrayData &operator=(const QwtArrayData &); + virtual QwtData *copy() const; + + virtual size_t size() const; + virtual double x(size_t i) const; + virtual double y(size_t i) const; + + const QwtArray &xData() const; + const QwtArray &yData() const; + + virtual QwtDoubleRect boundingRect() const; + +private: + QwtArray d_x; + QwtArray d_y; +}; + +/*! + \brief Data class containing two pointers to memory blocks of doubles. + */ +class QWT_EXPORT QwtCPointerData: public QwtData +{ +public: + QwtCPointerData(const double *x, const double *y, size_t size); + QwtCPointerData &operator=(const QwtCPointerData &); + virtual QwtData *copy() const; + + virtual size_t size() const; + virtual double x(size_t i) const; + virtual double y(size_t i) const; + + const double *xData() const; + const double *yData() const; + + virtual QwtDoubleRect boundingRect() const; + +private: + const double *d_x; + const double *d_y; + size_t d_size; +}; + +#endif // !QWT_DATA diff --git a/qwt/src/qwt_dial.cpp b/qwt/src/qwt_dial.cpp new file mode 100644 index 000000000..c79e82712 --- /dev/null +++ b/qwt/src/qwt_dial.cpp @@ -0,0 +1,1290 @@ +/* -*- 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 +#if QT_VERSION >= 0x040000 +#include +#include +#endif +#include +#include +#include "qwt_math.h" +#include "qwt_scale_engine.h" +#include "qwt_scale_map.h" +#include "qwt_paint_buffer.h" +#include "qwt_painter.h" +#include "qwt_dial_needle.h" +#include "qwt_dial.h" + +class QwtDial::PrivateData +{ +public: + PrivateData(): + visibleBackground(true), + frameShadow(Sunken), + lineWidth(0), + mode(RotateNeedle), + direction(Clockwise), + origin(90.0), + minScaleArc(0.0), + maxScaleArc(0.0), + scaleDraw(0), + maxMajIntv(36), + maxMinIntv(10), + scaleStep(0.0), + needle(0) + { + } + + ~PrivateData() + { + delete scaleDraw; + delete needle; + } + bool visibleBackground; + Shadow frameShadow; + int lineWidth; + + QwtDial::Mode mode; + QwtDial::Direction direction; + + double origin; + double minScaleArc; + double maxScaleArc; + + QwtDialScaleDraw *scaleDraw; + int maxMajIntv; + int maxMinIntv; + double scaleStep; + + QwtDialNeedle *needle; + + static double previousDir; +}; + +double QwtDial::PrivateData::previousDir = -1.0; + +/*! + Constructor + + \param parent Parent dial widget +*/ +QwtDialScaleDraw::QwtDialScaleDraw(QwtDial *parent): + d_parent(parent), + d_penWidth(1) +{ +} + +/*! + Set the pen width used for painting the scale + + \param penWidth Pen width + \sa penWidth(), QwtDial::drawScale() +*/ + +void QwtDialScaleDraw::setPenWidth(uint penWidth) +{ + d_penWidth = penWidth; +} + +/*! + \return Pen width used for painting the scale + \sa setPenWidth, QwtDial::drawScale() +*/ +uint QwtDialScaleDraw::penWidth() const +{ + return d_penWidth; +} + +/*! + Call QwtDial::scaleLabel of the parent dial widget. + + \param value Value to display + + \sa QwtDial::scaleLabel() +*/ +QwtText QwtDialScaleDraw::label(double value) const +{ + if ( d_parent == NULL ) + return QwtRoundScaleDraw::label(value); + + return d_parent->scaleLabel(value); +} + +/*! + \brief Constructor + \param parent Parent widget + + Create a dial widget with no scale and no needle. + The default origin is 90.0 with no valid value. It accepts + mouse and keyboard inputs and has no step size. The default mode + is QwtDial::RotateNeedle. +*/ + +QwtDial::QwtDial(QWidget* parent): + QwtAbstractSlider(Qt::Horizontal, parent) +{ + initDial(); +} + +#if QT_VERSION < 0x040000 +/*! + \brief Constructor + \param parent Parent widget + \param name Object name + + Create a dial widget with no scale and no needle. + The default origin is 90.0 with no valid value. It accepts + mouse and keyboard inputs and has no step size. The default mode + is QwtDial::RotateNeedle. +*/ +QwtDial::QwtDial(QWidget* parent, const char *name): + QwtAbstractSlider(Qt::Horizontal, parent) +{ + setName(name); + initDial(); +} +#endif + +void QwtDial::initDial() +{ + d_data = new PrivateData; + +#if QT_VERSION < 0x040000 + setWFlags(Qt::WNoAutoErase); +#endif + +#if QT_VERSION >= 0x040000 + using namespace Qt; +#endif + setFocusPolicy(TabFocus); + + QPalette p = palette(); + for ( int i = 0; i < QPalette::NColorGroups; i++ ) + { + const QPalette::ColorGroup cg = (QPalette::ColorGroup)i; + + // Base: background color of the circle inside the frame. + // Foreground: background color of the circle inside the scale + +#if QT_VERSION < 0x040000 + p.setColor(cg, QColorGroup::Foreground, + p.color(cg, QColorGroup::Base)); +#else + p.setColor(cg, QPalette::Foreground, + p.color(cg, QPalette::Base)); +#endif + } + setPalette(p); + + d_data->scaleDraw = new QwtDialScaleDraw(this); + d_data->scaleDraw->setRadius(0); + + setScaleArc(0.0, 360.0); // scale as a full circle + setRange(0.0, 360.0, 1.0, 10); // degrees as deafult +} + +//! Destructor +QwtDial::~QwtDial() +{ + delete d_data; +} + +/*! + Show/Hide the area outside of the frame + \param show Show if true, hide if false + + \sa hasVisibleBackground(), setMask() + \warning When QwtDial is a toplevel widget the window + border might disappear too. +*/ +void QwtDial::showBackground(bool show) +{ + if ( d_data->visibleBackground != show ) + { + d_data->visibleBackground = show; + updateMask(); + } +} + +/*! + true when the area outside of the frame is visible + + \sa showBackground(), setMask() +*/ +bool QwtDial::hasVisibleBackground() const +{ + return d_data->visibleBackground; +} + +/*! + 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 ) + { + 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 + + \param lineWidth Line width + \sa setFrameShadow() +*/ +void QwtDial::setLineWidth(int lineWidth) +{ + if ( lineWidth < 0 ) + lineWidth = 0; + + if ( d_data->lineWidth != lineWidth ) + { + 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 rect of the circle inside the frame + \sa setLineWidth(), scaleContentsRect(), boundingRect() +*/ +QRect QwtDial::contentsRect() const +{ + const int lw = lineWidth(); + + QRect r = boundingRect(); + if ( lw > 0 ) + { + r.setRect(r.x() + lw, r.y() + lw, + r.width() - 2 * lw, r.height() - 2 * lw); + } + return r; +} + +/*! + \return bounding rect of the dial including the frame + \sa setLineWidth(), scaleContentsRect(), contentsRect() +*/ +QRect QwtDial::boundingRect() const +{ + const int radius = qwtMin(width(), height()) / 2; + + QRect r(0, 0, 2 * radius, 2 * radius); + r.moveCenter(rect().center()); + return r; +} + +/*! + \return rect inside the scale + \sa setLineWidth(), boundingRect(), contentsRect() +*/ +QRect QwtDial::scaleContentsRect() const +{ +#if QT_VERSION < 0x040000 + const QPen scalePen(colorGroup().text(), 0, Qt::NoPen); +#else + const QPen scalePen(palette().text(), 0, Qt::NoPen); +#endif + + int scaleDist = 0; + if ( d_data->scaleDraw ) + { + scaleDist = d_data->scaleDraw->extent(scalePen, font()); + scaleDist++; // margin + } + + const QRect rect = contentsRect(); + return QRect(rect.x() + scaleDist, rect.y() + scaleDist, + rect.width() - 2 * scaleDist, rect.height() - 2 * scaleDist); +} + +/*! + \brief Change the mode of the meter. + \param mode New mode + + The value of the meter is indicated by the difference + between north of the scale and the direction of the needle. + In case of QwtDial::RotateNeedle north is pointing + to the origin() and 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 ) + { + d_data->mode = mode; + update(); + } +} + +/*! + \return mode of the dial. + + The value of the dial is indicated by the difference + between the origin and the direction of the needle. + In case of QwtDial::RotateNeedle the scale arc is fixed + to the origin() and 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 setMode(), origin(), setScaleArc(), value() +*/ +QwtDial::Mode QwtDial::mode() const +{ + return d_data->mode; +} + +/*! + Sets whether it is possible to step the value from the highest value to + the lowest value and vice versa to on. + + \param wrapping en/disables wrapping + + \sa wrapping(), QwtDoubleRange::periodic() + \note The meaning of wrapping is like the wrapping property of QSpinBox, + but not like it is used in QDial. +*/ +void QwtDial::setWrapping(bool wrapping) +{ + setPeriodic(wrapping); +} + +/*! + wrapping() holds whether it is possible to step the value from the + highest value to the lowest value and vice versa. + + \sa setWrapping(), QwtDoubleRange::setPeriodic() + \note The meaning of wrapping is like the wrapping property of QSpinBox, + but not like it is used in QDial. +*/ +bool QwtDial::wrapping() const +{ + return periodic(); +} + +/*! + Set the direction of the dial (clockwise/counterclockwise) + + Direction direction + \sa direction() +*/ +void QwtDial::setDirection(Direction direction) +{ + if ( direction != d_data->direction ) + { + d_data->direction = direction; + update(); + } +} + +/*! + \return Direction of the dial + + The default direction of a dial is QwtDial::Clockwise + + \sa setDirection() +*/ +QwtDial::Direction QwtDial::direction() const +{ + return d_data->direction; +} + +/*! + Resize the dial widget + \param e Resize event +*/ +void QwtDial::resizeEvent(QResizeEvent *e) +{ + QWidget::resizeEvent(e); + + if ( !hasVisibleBackground() ) + updateMask(); +} + +/*! + Paint the dial + \param e Paint event +*/ +void QwtDial::paintEvent(QPaintEvent *e) +{ + const QRect &ur = e->rect(); + if ( ur.isValid() ) + { +#if QT_VERSION < 0x040000 + QwtPaintBuffer paintBuffer(this, ur); + QPainter &painter = *paintBuffer.painter(); +#else + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing, true); +#endif + + painter.save(); + drawContents(&painter); + painter.restore(); + + painter.save(); + drawFrame(&painter); + painter.restore(); + + if ( hasFocus() ) + drawFocusIndicator(&painter); + } +} + +/*! + Draw a dotted round circle, if !isReadOnly() + + \param painter Painter +*/ +void QwtDial::drawFocusIndicator(QPainter *painter) const +{ + if ( !isReadOnly() ) + { + QRect focusRect = contentsRect(); + + const int margin = 2; + focusRect.setRect( + focusRect.x() + margin, + focusRect.y() + margin, + focusRect.width() - 2 * margin, + focusRect.height() - 2 * margin); + +#if QT_VERSION < 0x040000 + QColor color = colorGroup().color(QColorGroup::Base); +#else + QColor color = palette().color(QPalette::Base); +#endif + if (color.isValid()) + { + const QColor gray(Qt::gray); + + int h, s, v; +#if QT_VERSION < 0x040000 + color.hsv(&h, &s, &v); +#else + color.getHsv(&h, &s, &v); +#endif + color = (v > 128) ? gray.dark(120) : gray.light(120); + } + else + color = Qt::darkGray; + + painter->save(); + painter->setBrush(Qt::NoBrush); + painter->setPen(QPen(color, 0, Qt::DotLine)); + painter->drawEllipse(focusRect); + painter->restore(); + } +} + +/*! + Draw the frame around the dial + + \param painter Painter + \sa lineWidth(), frameShadow() +*/ +void QwtDial::drawFrame(QPainter *painter) +{ + const int lw = lineWidth(); + const int off = (lw + 1) % 2; + + QRect r = boundingRect(); + r.setRect(r.x() + lw / 2 - off, r.y() + lw / 2 - off, + r.width() - lw + off + 1, r.height() - lw + off + 1); +#if QT_VERSION >= 0x040000 + r.setX(r.x() + 1); + r.setY(r.y() + 1); + r.setWidth(r.width() - 2); + r.setHeight(r.height() - 2); +#endif + + if ( lw > 0 ) + { + switch(d_data->frameShadow) + { + case QwtDial::Raised: +#if QT_VERSION < 0x040000 + QwtPainter::drawRoundFrame(painter, r, + lw, colorGroup(), false); +#else + QwtPainter::drawRoundFrame(painter, r, + lw, palette(), false); +#endif + break; + case QwtDial::Sunken: +#if QT_VERSION < 0x040000 + QwtPainter::drawRoundFrame(painter, r, + lw, colorGroup(), true); +#else + QwtPainter::drawRoundFrame(painter, r, + lw, palette(), true); +#endif + break; + default: // Plain + { + painter->save(); + painter->setPen(QPen(Qt::black, lw)); + painter->setBrush(Qt::NoBrush); + painter->drawEllipse(r); + painter->restore(); + } + } + } +} + +/*! + \brief Draw the contents inside the frame + + QColorGroup::Background is the background color outside of the frame. + QColorGroup::Base is the background color inside the frame. + QColorGroup::Foreground is the background color inside the scale. + + \param painter Painter + \sa boundingRect(), contentsRect(), + scaleContentsRect(), QWidget::setPalette() +*/ +void QwtDial::drawContents(QPainter *painter) const +{ +#if QT_VERSION < 0x040000 + if ( backgroundMode() == Qt::NoBackground || + colorGroup().brush(QColorGroup::Base) != + colorGroup().brush(QColorGroup::Background) ) +#else + if ( testAttribute(Qt::WA_NoSystemBackground) || + palette().brush(QPalette::Base) != + palette().brush(QPalette::Background) ) +#endif + { + + const QRect br = boundingRect(); + + painter->save(); + painter->setPen(Qt::NoPen); + +#if QT_VERSION < 0x040000 + painter->setBrush(colorGroup().brush(QColorGroup::Base)); +#else + painter->setBrush(palette().brush(QPalette::Base)); +#endif + + painter->drawEllipse(br); + painter->restore(); + } + + + const QRect insideScaleRect = scaleContentsRect(); +#if QT_VERSION < 0x040000 + if ( colorGroup().brush(QColorGroup::Foreground) != + colorGroup().brush(QColorGroup::Base) ) +#else + if ( palette().brush(QPalette::Foreground) != + palette().brush(QPalette::Base) ) +#endif + { + painter->save(); + painter->setPen(Qt::NoPen); + +#if QT_VERSION < 0x040000 + painter->setBrush(colorGroup().brush(QColorGroup::Foreground)); +#else + painter->setBrush(palette().brush(QPalette::Foreground)); +#endif + + painter->drawEllipse(insideScaleRect.x() - 1, insideScaleRect.y() - 1, + insideScaleRect.width(), insideScaleRect.height() ); + + painter->restore(); + } + + const QPoint center = insideScaleRect.center(); + const int radius = insideScaleRect.width() / 2; + + painter->save(); + drawScaleContents(painter, center, radius); + painter->restore(); + + double direction = d_data->origin; + + if (isValid()) + { + direction = d_data->minScaleArc; + if ( maxValue() > minValue() && d_data->maxScaleArc > d_data->minScaleArc ) + { + const double ratio = + (value() - minValue()) / (maxValue() - minValue()); + direction += ratio * (d_data->maxScaleArc - d_data->minScaleArc); + } + + if ( d_data->direction == QwtDial::CounterClockwise ) + direction = d_data->maxScaleArc - (direction - d_data->minScaleArc); + + direction += d_data->origin; + if ( direction >= 360.0 ) + direction -= 360.0; + else if ( direction < 0.0 ) + direction += 360.0; + } + + double origin = d_data->origin; + if ( mode() == RotateScale ) + { + origin -= direction - d_data->origin; + direction = d_data->origin; + } + + painter->save(); + drawScale(painter, center, radius, origin, + d_data->minScaleArc, d_data->maxScaleArc); + painter->restore(); + + if ( isValid() ) + { + QPalette::ColorGroup cg; + if ( isEnabled() ) + cg = hasFocus() ? QPalette::Active : QPalette::Inactive; + else + cg = QPalette::Disabled; + + painter->save(); + drawNeedle(painter, center, radius, direction, cg); + 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 cg ColorGroup +*/ +void QwtDial::drawNeedle(QPainter *painter, const QPoint ¢er, + int radius, double direction, QPalette::ColorGroup cg) const +{ + if ( d_data->needle ) + { + direction = 360.0 - direction; // counter clockwise + d_data->needle->draw(painter, center, radius, direction, cg); + } +} + +/*! + Draw the scale + + \param painter Painter + \param center Center of the dial + \param radius Radius of the scale + \param origin Origin of the scale + \param minArc Minimum of the arc + \param maxArc Minimum of the arc + + \sa QwtAbstractScaleDraw::setAngleRange() +*/ +void QwtDial::drawScale(QPainter *painter, const QPoint ¢er, + int radius, double origin, double minArc, double maxArc) const +{ + if ( d_data->scaleDraw == NULL ) + return; + + origin -= 270.0; // hardcoded origin of QwtScaleDraw + + double angle = maxArc - minArc; + if ( angle > 360.0 ) + angle = ::fmod(angle, 360.0); + + minArc += origin; + if ( minArc < -360.0 ) + minArc = ::fmod(minArc, 360.0); + + maxArc = minArc + angle; + if ( maxArc > 360.0 ) + { + // QwtAbstractScaleDraw::setAngleRange accepts only values + // in the range [-360.0..360.0] + minArc -= 360.0; + maxArc -= 360.0; + } + + if ( d_data->direction == QwtDial::CounterClockwise ) + qSwap(minArc, maxArc); + + painter->setFont(font()); + + d_data->scaleDraw->setAngleRange(minArc, maxArc); + d_data->scaleDraw->setRadius(radius); + d_data->scaleDraw->moveCenter(center); + +#if QT_VERSION < 0x040000 + QColorGroup cg = colorGroup(); + + const QColor textColor = cg.color(QColorGroup::Text); + cg.setColor(QColorGroup::Foreground, textColor); + painter->setPen(QPen(textColor, d_data->scaleDraw->penWidth())); + + d_data->scaleDraw->draw(painter, cg); +#else + QPalette pal = palette(); + + const QColor textColor = pal.color(QPalette::Text); + pal.setColor(QPalette::Foreground, textColor); //ticks, backbone + + painter->setPen(QPen(textColor, d_data->scaleDraw->penWidth())); + + d_data->scaleDraw->draw(painter, pal); +#endif +} + +void QwtDial::drawScaleContents(QPainter *, + const QPoint &, int) const +{ + // empty default implementation +} + +/*! + Set a needle for the dial + + Qwt is missing a set of good looking needles. + Contributions are very welcome. + + \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; +} + +//! QwtDoubleRange update hook +void QwtDial::rangeChange() +{ + updateScale(); +} + +/*! + Update the scale with the current attributes + \sa setScale() +*/ +void QwtDial::updateScale() +{ + if ( d_data->scaleDraw ) + { + QwtLinearScaleEngine scaleEngine; + + const QwtScaleDiv scaleDiv = scaleEngine.divideScale( + minValue(), maxValue(), + d_data->maxMajIntv, d_data->maxMinIntv, d_data->scaleStep); + + d_data->scaleDraw->setTransformation(scaleEngine.transformation()); + d_data->scaleDraw->setScaleDiv(scaleDiv); + } +} + +//! Return the scale draw +QwtDialScaleDraw *QwtDial::scaleDraw() +{ + return d_data->scaleDraw; +} + +//! Return the scale draw +const QwtDialScaleDraw *QwtDial::scaleDraw() const +{ + return d_data->scaleDraw; +} + +/*! + Set an individual scale draw + + \param scaleDraw Scale draw + \warning The previous scale draw is deleted +*/ +void QwtDial::setScaleDraw(QwtDialScaleDraw *scaleDraw) +{ + if ( scaleDraw != d_data->scaleDraw ) + { + if ( d_data->scaleDraw ) + delete d_data->scaleDraw; + + d_data->scaleDraw = scaleDraw; + updateScale(); + update(); + } +} + +/*! + Change the intervals of the scale + \sa QwtAbstractScaleDraw::setScale() +*/ +void QwtDial::setScale(int maxMajIntv, int maxMinIntv, double step) +{ + d_data->maxMajIntv = maxMajIntv; + d_data->maxMinIntv = maxMinIntv; + d_data->scaleStep = step; + + updateScale(); +} + +/*! + A wrapper method for accessing the scale draw. + + - options == 0\n + No visible scale: setScaleDraw(NULL) + - options & ScaleBackbone\n + En/disable the backbone of the scale. + - options & ScaleTicks\n + En/disable the ticks of the scale. + - options & ScaleLabel\n + En/disable scale labels + + \sa QwtAbstractScaleDraw::enableComponent() +*/ +void QwtDial::setScaleOptions(int options) +{ + if ( options == 0 ) + setScaleDraw(NULL); + + QwtDialScaleDraw *sd = d_data->scaleDraw; + if ( sd == NULL ) + return; + + sd->enableComponent(QwtAbstractScaleDraw::Backbone, + options & ScaleBackbone); + + sd->enableComponent(QwtAbstractScaleDraw::Ticks, + options & ScaleTicks); + + sd->enableComponent(QwtAbstractScaleDraw::Labels, + options & ScaleLabel); +} + +/*! + Assign length and width of the ticks + + \param minLen Length of the minor ticks + \param medLen Length of the medium ticks + \param majLen Length of the major ticks + \param penWidth Width of the pen for all ticks + + \sa QwtAbstractScaleDraw::setTickLength(), QwtDialScaleDraw::setPenWidth() +*/ +void QwtDial::setScaleTicks(int minLen, int medLen, + int majLen, int penWidth) +{ + QwtDialScaleDraw *sd = d_data->scaleDraw; + if ( sd ) + { + sd->setTickLength(QwtScaleDiv::MinorTick, minLen); + sd->setTickLength(QwtScaleDiv::MediumTick, medLen); + sd->setTickLength(QwtScaleDiv::MajorTick, majLen); + sd->setPenWidth(penWidth); + } +} + +/*! + Find the label for a value + + \param value Value + \return label +*/ +QwtText QwtDial::scaleLabel(double value) const +{ +#if 1 + if ( value == -0 ) + value = 0; +#endif + + return QString::number(value); +} + +//! \return Lower limit of the scale arc +double QwtDial::minScaleArc() const +{ + return d_data->minScaleArc; +} + +//! \return Upper limit of the scale arc +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) +{ + d_data->origin = origin; + update(); +} + +/*! + 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; +} + +/*! + Change the arc of the scale + + \param minArc Lower limit + \param maxArc Upper limit +*/ +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); + + d_data->minScaleArc = qwtMin(minArc, maxArc); + d_data->maxScaleArc = qwtMax(minArc, maxArc); + if ( d_data->maxScaleArc - d_data->minScaleArc > 360.0 ) + d_data->maxScaleArc = d_data->minScaleArc + 360.0; + + update(); +} + +//! QwtDoubleRange update hook +void QwtDial::valueChange() +{ + update(); + QwtAbstractSlider::valueChange(); +} + +/*! + \return Size hint +*/ +QSize QwtDial::sizeHint() const +{ + int sh = 0; + if ( d_data->scaleDraw ) + sh = d_data->scaleDraw->extent( QPen(), font() ); + + const int d = 6 * sh + 2 * lineWidth(); + + return QSize( d, d ); +} + +/*! + \brief Return a minimum size hint + \warning The return value of QwtDial::minimumSizeHint() depends on the + font and the scale. +*/ +QSize QwtDial::minimumSizeHint() const +{ + int sh = 0; + if ( d_data->scaleDraw ) + sh = d_data->scaleDraw->extent(QPen(), font() ); + + const int d = 3 * sh + 2 * lineWidth(); + + return QSize( d, d ); +} + +static double line2Radians(const QPoint &p1, const QPoint &p2) +{ + const QPoint p = p2 - p1; + + double angle; + if ( p.x() == 0 ) + angle = ( p.y() <= 0 ) ? M_PI_2 : 3 * M_PI_2; + else + { + angle = atan(double(-p.y()) / double(p.x())); + if ( p.x() < 0 ) + angle += M_PI; + if ( angle < 0.0 ) + angle += 2 * M_PI; + } + return 360.0 - angle * 180.0 / M_PI; +} + +/*! + Find the value for a given position + + \param pos Position + \return Value +*/ +double QwtDial::getValue(const QPoint &pos) +{ + if ( d_data->maxScaleArc == d_data->minScaleArc || maxValue() == minValue() ) + return minValue(); + + double dir = line2Radians(rect().center(), pos) - d_data->origin; + if ( dir < 0.0 ) + dir += 360.0; + + if ( mode() == RotateScale ) + dir = 360.0 - dir; + + // The position might be in the area that is outside the scale arc. + // We need the range of the scale if it was a complete circle. + + const double completeCircle = 360.0 / (d_data->maxScaleArc - d_data->minScaleArc) + * (maxValue() - minValue()); + + double posValue = minValue() + completeCircle * dir / 360.0; + + if ( scrollMode() == ScrMouse ) + { + if ( d_data->previousDir >= 0.0 ) // valid direction + { + // We have to find out whether the mouse is moving + // clock or counter clockwise + + bool clockWise = false; + + const double angle = dir - d_data->previousDir; + if ( (angle >= 0.0 && angle <= 180.0) || angle < -180.0 ) + clockWise = true; + + if ( clockWise ) + { + if ( dir < d_data->previousDir && mouseOffset() > 0.0 ) + { + // We passed 360 -> 0 + setMouseOffset(mouseOffset() - completeCircle); + } + + if ( wrapping() ) + { + if ( posValue - mouseOffset() > maxValue() ) + { + // We passed maxValue and the value will be set + // to minValue. We have to adjust the mouseOffset. + + setMouseOffset(posValue - minValue()); + } + } + else + { + if ( posValue - mouseOffset() > maxValue() || + value() == maxValue() ) + { + // We fix the value at maxValue by adjusting + // the mouse offset. + + setMouseOffset(posValue - maxValue()); + } + } + } + else + { + if ( dir > d_data->previousDir && mouseOffset() < 0.0 ) + { + // We passed 0 -> 360 + setMouseOffset(mouseOffset() + completeCircle); + } + + if ( wrapping() ) + { + if ( posValue - mouseOffset() < minValue() ) + { + // We passed minValue and the value will be set + // to maxValue. We have to adjust the mouseOffset. + + setMouseOffset(posValue - maxValue()); + } + } + else + { + if ( posValue - mouseOffset() < minValue() || + value() == minValue() ) + { + // We fix the value at minValue by adjusting + // the mouse offset. + + setMouseOffset(posValue - minValue()); + } + } + } + } + d_data->previousDir = dir; + } + + return posValue; +} + +/*! + See QwtAbstractSlider::getScrollMode() + + \param pos point where the mouse was pressed + \retval scrollMode The scrolling mode + \retval direction direction: 1, 0, or -1. + + \sa QwtAbstractSlider::getScrollMode() +*/ +void QwtDial::getScrollMode(const QPoint &pos, int &scrollMode, int &direction) +{ + direction = 0; + scrollMode = ScrNone; + + const QRegion region(contentsRect(), QRegion::Ellipse); + if ( region.contains(pos) && pos != rect().center() ) + { + scrollMode = ScrMouse; + d_data->previousDir = -1.0; + } +} + +/*! + Handles key events + + - Key_Down, KeyLeft\n + Decrement by 1 + - Key_Prior\n + Decrement by pageSize() + - Key_Home\n + Set the value to minValue() + + - Key_Up, KeyRight\n + Increment by 1 + - Key_Next\n + Increment by pageSize() + - Key_End\n + Set the value to maxValue() + + \param event Key event + \sa isReadOnly() +*/ +void QwtDial::keyPressEvent(QKeyEvent *event) +{ + if ( isReadOnly() ) + { + event->ignore(); + return; + } + + if ( !isValid() ) + return; + + double previous = prevValue(); + switch ( event->key() ) + { + case Qt::Key_Down: + case Qt::Key_Left: + QwtDoubleRange::incValue(-1); + break; +#if QT_VERSION < 0x040000 + case Qt::Key_Prior: +#else + case Qt::Key_PageUp: +#endif + QwtDoubleRange::incValue(-pageSize()); + break; + case Qt::Key_Home: + setValue(minValue()); + break; + + case Qt::Key_Up: + case Qt::Key_Right: + QwtDoubleRange::incValue(1); + break; +#if QT_VERSION < 0x040000 + case Qt::Key_Next: +#else + case Qt::Key_PageDown: +#endif + QwtDoubleRange::incValue(pageSize()); + break; + case Qt::Key_End: + setValue(maxValue()); + break; + default:; + event->ignore(); + } + + if (value() != previous) + emit sliderMoved(value()); +} + +/*! + \brief Update the mask of the dial + + In case of "hasVisibleBackground() == false", the backgound is + transparent by a mask. + + \sa showBackground(), hasVisibleBackground() +*/ +void QwtDial::updateMask() +{ + if ( d_data->visibleBackground ) + clearMask(); + else + setMask(QRegion(boundingRect(), QRegion::Ellipse)); +} diff --git a/qwt/src/qwt_dial.h b/qwt/src/qwt_dial.h new file mode 100644 index 000000000..00d410474 --- /dev/null +++ b/qwt/src/qwt_dial.h @@ -0,0 +1,228 @@ +/* -*- 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 + +#ifndef QWT_DIAL_H +#define QWT_DIAL_H 1 + +#include +#include +#include "qwt_global.h" +#include "qwt_abstract_slider.h" +#include "qwt_round_scale_draw.h" + +class QwtDialNeedle; +class QwtDial; + +/*! + \brief A special scale draw made for QwtDial + + \sa QwtDial, QwtCompass +*/ +class QWT_EXPORT QwtDialScaleDraw: public QwtRoundScaleDraw +{ +public: + explicit QwtDialScaleDraw(QwtDial *); + virtual QwtText label(double value) const; + + void setPenWidth(uint); + uint penWidth() const; + +private: + QwtDial *d_parent; + int d_penWidth; +}; + +/*! + \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 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. + + Qwt is missing a set of good looking needles (QwtDialNeedle). + Contributions are very welcome. + + \sa QwtCompass, QwtAnalogClock, QwtDialNeedle + \note The examples/dials example shows different types of dials. +*/ + +class QWT_EXPORT QwtDial: public QwtAbstractSlider +{ + Q_OBJECT + + Q_ENUMS(Shadow) + Q_ENUMS(Mode) + Q_ENUMS(Direction) + + Q_PROPERTY(bool visibleBackground READ hasVisibleBackground WRITE showBackground) + 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(bool wrapping READ wrapping WRITE setWrapping) + Q_PROPERTY(Direction direction READ direction WRITE setDirection) + + friend class QwtDialScaleDraw; +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 + { + Plain = QFrame::Plain, + Raised = QFrame::Raised, + Sunken = QFrame::Sunken + }; + + //! see QwtDial::setScaleOptions + enum ScaleOptions + { + ScaleBackbone = 1, + ScaleTicks = 2, + ScaleLabel = 4 + }; + + /*! + In case of RotateNeedle the needle is rotating, in case of + RotateScale, the needle points to origin() + and the scale is rotating. + */ + enum Mode + { + RotateNeedle, + RotateScale + }; + + /*! + Direction of the dial + */ + enum Direction + { + Clockwise, + CounterClockwise + }; + + explicit QwtDial( QWidget *parent = NULL); +#if QT_VERSION < 0x040000 + explicit QwtDial( QWidget *parent, const char *name); +#endif + + virtual ~QwtDial(); + + void setFrameShadow(Shadow); + Shadow frameShadow() const; + + bool hasVisibleBackground() const; + void showBackground(bool); + + void setLineWidth(int); + int lineWidth() const; + + void setMode(Mode); + Mode mode() const; + + virtual void setWrapping(bool); + bool wrapping() const; + + virtual void setScale(int maxMajIntv, int maxMinIntv, double step = 0.0); + + void setScaleArc(double min, double max); + void setScaleOptions(int); + void setScaleTicks(int minLen, int medLen, int majLen, int penWidth = 1); + + double minScaleArc() const; + double maxScaleArc() const; + + virtual void setOrigin(double); + double origin() const; + + void setDirection(Direction); + Direction direction() const; + + virtual void setNeedle(QwtDialNeedle *); + const QwtDialNeedle *needle() const; + QwtDialNeedle *needle(); + + QRect boundingRect() const; + QRect contentsRect() const; + virtual QRect scaleContentsRect() const; + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + virtual void setScaleDraw(QwtDialScaleDraw *); + + QwtDialScaleDraw *scaleDraw(); + const QwtDialScaleDraw *scaleDraw() const; + +protected: + virtual void paintEvent(QPaintEvent *); + virtual void resizeEvent(QResizeEvent *); + virtual void keyPressEvent(QKeyEvent *); + + virtual void updateMask(); + + virtual void drawFrame(QPainter *p); + virtual void drawContents(QPainter *) const; + virtual void drawFocusIndicator(QPainter *) const; + + virtual void drawScale(QPainter *, const QPoint ¢er, + int radius, double origin, double arcMin, double arcMax) const; + + /*! + 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 + */ + virtual void drawScaleContents(QPainter *painter, const QPoint ¢er, + int radius) const; + + virtual void drawNeedle(QPainter *, const QPoint &, + int radius, double direction, QPalette::ColorGroup) const; + + virtual QwtText scaleLabel(double) const; + void updateScale(); + + virtual void rangeChange(); + virtual void valueChange(); + + virtual double getValue(const QPoint &); + virtual void getScrollMode(const QPoint &, + int &scrollMode, int &direction); + +private: + void initDial(); + + 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..7f4772474 --- /dev/null +++ b/qwt/src/qwt_dial_needle.cpp @@ -0,0 +1,612 @@ +/* -*- 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 "qwt_math.h" +#include "qwt_painter.h" +#include "qwt_polygon.h" +#include "qwt_dial_needle.h" + +#if QT_VERSION < 0x040000 +typedef QColorGroup QwtPalette; +#else +typedef QPalette QwtPalette; +#endif + +//! 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 knob +void QwtDialNeedle::drawKnob(QPainter *painter, + const QPoint &pos, int width, const QBrush &brush, bool sunken) +{ + painter->save(); + + QRect rect(0, 0, width, width); + rect.moveCenter(pos); + + painter->setPen(Qt::NoPen); + painter->setBrush(brush); + painter->drawEllipse(rect); + + painter->setBrush(Qt::NoBrush); + + const int colorOffset = 20; + + int startAngle = 45; + if ( sunken ) + startAngle += 180; + + QPen pen; + pen.setWidth(1); + + pen.setColor(brush.color().dark(100 - colorOffset)); + painter->setPen(pen); + painter->drawArc(rect, startAngle * 16, 180 * 16); + + pen.setColor(brush.color().dark(100 + colorOffset)); + painter->setPen(pen); + painter->drawArc(rect, (startAngle + 180) * 16, 180 * 16); + + 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; + for ( int i = 0; i < QPalette::NColorGroups; i++ ) + { + palette.setColor((QPalette::ColorGroup)i, + QwtPalette::Mid, mid); + palette.setColor((QPalette::ColorGroup)i, + QwtPalette::Base, base); + } + + setPalette(palette); +} + +/*! + Set the width of the needle + \param width Width + \sa width() +*/ +void QwtDialSimpleNeedle::setWidth(int width) +{ + d_width = width; +} + +/*! + \return the width of the needle + \sa setWidth() +*/ +int QwtDialSimpleNeedle::width() const +{ + return d_width; +} + +/*! + 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 QwtDialSimpleNeedle::draw(QPainter *painter, const QPoint ¢er, + int length, double direction, QPalette::ColorGroup colorGroup) const +{ + if ( d_style == Arrow ) + { + drawArrowNeedle(painter, palette(), colorGroup, + center, length, d_width, direction, d_hasKnob); + } + else + { + drawRayNeedle(painter, palette(), colorGroup, + center, length, d_width, direction, d_hasKnob); + } +} + +/*! + Draw a needle looking like a ray + + \param painter Painter + \param palette Palette + \param colorGroup Color group + \param center center of the needle + \param length Length of the needle + \param width Width of the needle + \param direction Current Direction + \param hasKnob With/Without knob +*/ +void QwtDialSimpleNeedle::drawRayNeedle(QPainter *painter, + const QPalette &palette, QPalette::ColorGroup colorGroup, + const QPoint ¢er, int length, int width, double direction, + bool hasKnob) +{ + if ( width <= 0 ) + width = 5; + + direction *= M_PI / 180.0; + + painter->save(); + + const QPoint p1(center.x() + 1, center.y() + 2); + const QPoint p2 = qwtPolar2Pos(p1, length, direction); + + if ( width == 1 ) + { + const QColor midColor = + palette.color(colorGroup, QwtPalette::Mid); + + painter->setPen(QPen(midColor, 1)); + painter->drawLine(p1, p2); + } + else + { + QwtPolygon pa(4); + pa.setPoint(0, qwtPolar2Pos(p1, width / 2, direction + M_PI_2)); + pa.setPoint(1, qwtPolar2Pos(p2, width / 2, direction + M_PI_2)); + pa.setPoint(2, qwtPolar2Pos(p2, width / 2, direction - M_PI_2)); + pa.setPoint(3, qwtPolar2Pos(p1, width / 2, direction - M_PI_2)); + + painter->setPen(Qt::NoPen); + painter->setBrush(palette.brush(colorGroup, QwtPalette::Mid)); + painter->drawPolygon(pa); + } + if ( hasKnob ) + { + int knobWidth = qwtMax(qRound(width * 0.7), 5); + if ( knobWidth % 2 == 0 ) + knobWidth++; + + drawKnob(painter, center, knobWidth, + palette.brush(colorGroup, QwtPalette::Base), + false); + } + + painter->restore(); +} + +/*! + Draw a needle looking like an arrow + + \param painter Painter + \param palette Palette + \param colorGroup Color group + \param center center of the needle + \param length Length of the needle + \param width Width of the needle + \param direction Current Direction + \param hasKnob With/Without knob +*/ +void QwtDialSimpleNeedle::drawArrowNeedle(QPainter *painter, + const QPalette &palette, QPalette::ColorGroup colorGroup, + const QPoint ¢er, int length, int width, + double direction, bool hasKnob) +{ + direction *= M_PI / 180.0; + + painter->save(); + + if ( width <= 0 ) + { + width = (int)qwtMax(length * 0.06, 9.0); + if ( width % 2 == 0 ) + width++; + } + + const int peak = 3; + const QPoint p1(center.x() + 1, center.y() + 1); + const QPoint p2 = qwtPolar2Pos(p1, length - peak, direction); + const QPoint p3 = qwtPolar2Pos(p1, length, direction); + + QwtPolygon pa(5); + pa.setPoint(0, qwtPolar2Pos(p1, width / 2, direction - M_PI_2)); + pa.setPoint(1, qwtPolar2Pos(p2, 1, direction - M_PI_2)); + pa.setPoint(2, p3); + pa.setPoint(3, qwtPolar2Pos(p2, 1, direction + M_PI_2)); + pa.setPoint(4, qwtPolar2Pos(p1, width / 2, direction + M_PI_2)); + + painter->setPen(Qt::NoPen); + painter->setBrush(palette.brush(colorGroup, QwtPalette::Mid)); + painter->drawPolygon(pa); + + QwtPolygon shadowPa(3); + + const int colorOffset = 10; + + int i; + for ( i = 0; i < 3; i++ ) + shadowPa.setPoint(i, pa[i]); + + const QColor midColor = palette.color(colorGroup, QwtPalette::Mid); + + painter->setPen(midColor.dark(100 + colorOffset)); + painter->drawPolyline(shadowPa); + + for ( i = 0; i < 3; i++ ) + shadowPa.setPoint(i, pa[i + 2]); + + painter->setPen(midColor.dark(100 - colorOffset)); + painter->drawPolyline(shadowPa); + + if ( hasKnob ) + { + drawKnob(painter, center, qRound(width * 1.3), + palette.brush(colorGroup, QwtPalette::Base), + false); + } + + painter->restore(); +} + +//! Constructor + +QwtCompassMagnetNeedle::QwtCompassMagnetNeedle(Style style, + const QColor &light, const QColor &dark): + d_style(style) +{ + QPalette palette; + for ( int i = 0; i < QPalette::NColorGroups; i++ ) + { + palette.setColor((QPalette::ColorGroup)i, + QwtPalette::Light, light); + palette.setColor((QPalette::ColorGroup)i, + QwtPalette::Dark, dark); + palette.setColor((QPalette::ColorGroup)i, + QwtPalette::Base, Qt::darkGray); + } + + setPalette(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 QwtCompassMagnetNeedle::draw(QPainter *painter, const QPoint ¢er, + int length, double direction, QPalette::ColorGroup colorGroup) const +{ + if ( d_style == ThinStyle ) + { + drawThinNeedle(painter, palette(), colorGroup, + center, length, direction); + } + else + { + drawTriangleNeedle(painter, palette(), colorGroup, + center, length, direction); + } +} + +/*! + Draw a compass needle + + \param painter Painter + \param palette Palette + \param colorGroup Color group + \param center Center, where the needle starts + \param length Length of the needle + \param direction Direction +*/ +void QwtCompassMagnetNeedle::drawTriangleNeedle(QPainter *painter, + const QPalette &palette, QPalette::ColorGroup colorGroup, + const QPoint ¢er, int length, double direction) +{ + const QBrush darkBrush = palette.brush(colorGroup, QwtPalette::Dark); + const QBrush lightBrush = palette.brush(colorGroup, QwtPalette::Light); + + QBrush brush; + + const int width = qRound(length / 3.0); + const int colorOffset = 10; + + painter->save(); + painter->setPen(Qt::NoPen); + + const QPoint arrowCenter(center.x() + 1, center.y() + 1); + + QwtPolygon pa(3); + pa.setPoint(0, arrowCenter); + pa.setPoint(1, qwtDegree2Pos(arrowCenter, length, direction)); + + pa.setPoint(2, qwtDegree2Pos(arrowCenter, width / 2, direction + 90.0)); + + brush = darkBrush; + brush.setColor(brush.color().dark(100 + colorOffset)); + painter->setBrush(brush); + painter->drawPolygon(pa); + + pa.setPoint(2, qwtDegree2Pos(arrowCenter, width / 2, direction - 90.0)); + + brush = darkBrush; + brush.setColor(brush.color().dark(100 - colorOffset)); + painter->setBrush(brush); + painter->drawPolygon(pa); + + // -- + + pa.setPoint(1, qwtDegree2Pos(arrowCenter, length, direction + 180.0)); + + pa.setPoint(2, qwtDegree2Pos(arrowCenter, width / 2, direction + 90.0)); + + brush = lightBrush; + brush.setColor(brush.color().dark(100 + colorOffset)); + painter->setBrush(brush); + painter->drawPolygon(pa); + + pa.setPoint(2, qwtDegree2Pos(arrowCenter, width / 2, direction - 90.0)); + + brush = lightBrush; + brush.setColor(brush.color().dark(100 - colorOffset)); + painter->setBrush(brush); + painter->drawPolygon(pa); + + painter->restore(); +} + +/*! + Draw a compass needle + + \param painter Painter + \param palette Palette + \param colorGroup Color group + \param center Center, where the needle starts + \param length Length of the needle + \param direction Direction +*/ +void QwtCompassMagnetNeedle::drawThinNeedle(QPainter *painter, + const QPalette &palette, QPalette::ColorGroup colorGroup, + const QPoint ¢er, int length, double direction) +{ + const QBrush darkBrush = palette.brush(colorGroup, QwtPalette::Dark); + const QBrush lightBrush = palette.brush(colorGroup, QwtPalette::Light); + const QBrush baseBrush = palette.brush(colorGroup, QwtPalette::Base); + + const int colorOffset = 10; + const int width = qwtMax(qRound(length / 6.0), 3); + + painter->save(); + + const QPoint arrowCenter(center.x() + 1, center.y() + 1); + + drawPointer(painter, darkBrush, colorOffset, + arrowCenter, length, width, direction); + drawPointer(painter, lightBrush, -colorOffset, + arrowCenter, length, width, direction + 180.0); + + drawKnob(painter, arrowCenter, width, baseBrush, true); + + painter->restore(); +} + +/*! + Draw a compass needle + + \param painter Painter + \param brush Brush + \param colorOffset Color offset + \param center Center, where the needle starts + \param length Length of the needle + \param width Width of the needle + \param direction Direction +*/ +void QwtCompassMagnetNeedle::drawPointer( + QPainter *painter, const QBrush &brush, + int colorOffset, const QPoint ¢er, int length, + int width, double direction) +{ + painter->save(); + + const int peak = qwtMax(qRound(length / 10.0), 5); + + const int knobWidth = width + 8; + QRect knobRect(0, 0, knobWidth, knobWidth); + knobRect.moveCenter(center); + + QwtPolygon pa(5); + + pa.setPoint(0, qwtDegree2Pos(center, width / 2, direction + 90.0)); + pa.setPoint(1, center); + pa.setPoint(2, qwtDegree2Pos(pa.point(1), length - peak, direction)); + pa.setPoint(3, qwtDegree2Pos(center, length, direction)); + pa.setPoint(4, qwtDegree2Pos(pa.point(0), length - peak, direction)); + + painter->setPen(Qt::NoPen); + + QBrush darkBrush = brush; + darkBrush.setColor(darkBrush.color().dark(100 + colorOffset)); + painter->setBrush(darkBrush); + painter->drawPolygon(pa); + painter->drawPie(knobRect, qRound(direction * 16), 90 * 16); + + pa.setPoint(0, qwtDegree2Pos(center, width / 2, direction - 90.0)); + pa.setPoint(4, qwtDegree2Pos(pa.point(0), length - peak, direction)); + + QBrush lightBrush = brush; + lightBrush.setColor(lightBrush.color().dark(100 - colorOffset)); + painter->setBrush(lightBrush); + painter->drawPolygon(pa); + painter->drawPie(knobRect, qRound(direction * 16), -90 * 16); + + painter->restore(); +} + +/*! + 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; + for ( int i = 0; i < QPalette::NColorGroups; i++ ) + { + palette.setColor((QPalette::ColorGroup)i, + QwtPalette::Light, light); + palette.setColor((QPalette::ColorGroup)i, + QwtPalette::Dark, dark); + } + + setPalette(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 QwtCompassWindArrow::draw(QPainter *painter, const QPoint ¢er, + int length, double direction, QPalette::ColorGroup colorGroup) const +{ + if ( d_style == Style1 ) + { + drawStyle1Needle(painter, palette(), colorGroup, + center, length, direction); + } + else + { + drawStyle2Needle(painter, palette(), colorGroup, + center, length, direction); + } +} + +/*! + Draw a compass needle + + \param painter Painter + \param palette Palette + \param colorGroup colorGroup + \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 +*/ +void QwtCompassWindArrow::drawStyle1Needle(QPainter *painter, + const QPalette &palette, QPalette::ColorGroup colorGroup, + const QPoint ¢er, int length, double direction) +{ + const QBrush lightBrush = palette.brush(colorGroup, QwtPalette::Light); + + const double AR1[] = {0, 0.4, 0.3, 1, 0.8, 1, 0.3, 0.4}; + const double AW1[] = {0, -45, -20, -15, 0, 15, 20, 45}; + + const QPoint arrowCenter(center.x() + 1, center.y() + 1); + + QwtPolygon pa(8); + pa.setPoint(0, arrowCenter); + for (int i=1; i<8; i++) + { + const QPoint p = qwtDegree2Pos(center, + AR1[i] * length, direction + AW1[i]); + pa.setPoint(i, p); + } + + painter->save(); + painter->setPen(Qt::NoPen); + painter->setBrush(lightBrush); + painter->drawPolygon(pa); + painter->restore(); +} + +/*! + Draw a compass needle + + \param painter Painter + \param palette Palette + \param colorGroup colorGroup + \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 +*/ +void QwtCompassWindArrow::drawStyle2Needle(QPainter *painter, + const QPalette &palette, QPalette::ColorGroup colorGroup, + const QPoint ¢er, int length, double direction) +{ + const QBrush lightBrush = palette.brush(colorGroup, QwtPalette::Light); + const QBrush darkBrush = palette.brush(colorGroup, QwtPalette::Dark); + + painter->save(); + painter->setPen(Qt::NoPen); + + const double angle = 12.0; + const double ratio = 0.7; + + const QPoint arrowCenter(center.x() + 1, center.y() + 1); + + QwtPolygon pa(3); + + pa.setPoint(0, center); + pa.setPoint(2, qwtDegree2Pos(arrowCenter, ratio * length, direction)); + + pa.setPoint(1, qwtDegree2Pos(arrowCenter, length, direction + angle)); + painter->setBrush(darkBrush); + painter->drawPolygon(pa); + + pa.setPoint(1, qwtDegree2Pos(arrowCenter, length, direction - angle)); + painter->setBrush(lightBrush); + painter->drawPolygon(pa); + + painter->restore(); +} + diff --git a/qwt/src/qwt_dial_needle.h b/qwt/src/qwt_dial_needle.h new file mode 100644 index 000000000..b8dba4e41 --- /dev/null +++ b/qwt/src/qwt_dial_needle.h @@ -0,0 +1,198 @@ +/* -*- 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 +#include "qwt_global.h" + +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. + + Qwt is missing a set of good looking needles. + Contributions are very welcome. + + \sa QwtDial, QwtCompass +*/ + +class QWT_EXPORT QwtDialNeedle +{ +public: + QwtDialNeedle(); + virtual ~QwtDialNeedle(); + + /*! + 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 cg Color group, used for painting + */ + virtual void draw(QPainter *painter, const QPoint ¢er, + int length, double direction, + QPalette::ColorGroup cg = QPalette::Active) const = 0; + + virtual void setPalette(const QPalette &); + const QPalette &palette() const; + +protected: + static void drawKnob(QPainter *, const QPoint &pos, + int width, const QBrush &, bool sunken); + +private: + QPalette d_palette; +}; + +/*! + \brief A needle for dial widgets + + The following colors are used: + - QColorGroup::Mid\n + Pointer + - QColorGroup::base\n + Knob + + \sa QwtDial, QwtCompass +*/ + +class QWT_EXPORT QwtDialSimpleNeedle: public QwtDialNeedle +{ +public: + //! Style of the needle + enum Style + { + Arrow, + Ray + }; + + QwtDialSimpleNeedle(Style, bool hasKnob = true, + const QColor &mid = Qt::gray, const QColor &base = Qt::darkGray); + + virtual void draw(QPainter *, const QPoint &, int length, + double direction, QPalette::ColorGroup = QPalette::Active) const; + + static void drawArrowNeedle(QPainter *, + const QPalette&, QPalette::ColorGroup, + const QPoint &, int length, int width, double direction, + bool hasKnob); + + static void drawRayNeedle(QPainter *, + const QPalette&, QPalette::ColorGroup, + const QPoint &, int length, int width, double direction, + bool hasKnob); + + void setWidth(int width); + int width() const; + +private: + Style d_style; + bool d_hasKnob; + int 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: + - QColorGroup::Light\n + Used for pointing south + - QColorGroup::Dark\n + Used for pointing north + - QColorGroup::Base\n + Knob (ThinStyle only) + + \sa QwtDial, QwtCompass +*/ + +class QWT_EXPORT QwtCompassMagnetNeedle: public QwtDialNeedle +{ +public: + //! Style of the needle + enum Style + { + TriangleStyle, + ThinStyle + }; + QwtCompassMagnetNeedle(Style = TriangleStyle, + const QColor &light = Qt::white, const QColor &dark = Qt::red); + + virtual void draw(QPainter *, const QPoint &, int length, + double direction, QPalette::ColorGroup = QPalette::Active) const; + + static void drawTriangleNeedle(QPainter *, + const QPalette &, QPalette::ColorGroup, + const QPoint &, int length, double direction); + + static void drawThinNeedle(QPainter *, + const QPalette &, QPalette::ColorGroup, + const QPoint &, int length, double direction); + +protected: + static void drawPointer(QPainter *painter, const QBrush &brush, + int colorOffset, const QPoint ¢er, + int length, int width, double direction); + +private: + Style d_style; +}; + +/*! + \brief An indicator for the wind direction + + QwtCompassWindArrow shows the direction where the wind comes from. + + - QColorGroup::Light\n + Used for Style1, or the light half of Style2 + - QColorGroup::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 + { + Style1, + Style2 + }; + + QwtCompassWindArrow(Style, const QColor &light = Qt::white, + const QColor &dark = Qt::gray); + + virtual void draw(QPainter *, const QPoint &, int length, + double direction, QPalette::ColorGroup = QPalette::Active) const; + + static void drawStyle1Needle(QPainter *, + const QPalette &, QPalette::ColorGroup, + const QPoint &, int length, double direction); + + static void drawStyle2Needle(QPainter *, + const QPalette &, QPalette::ColorGroup, + const QPoint &, int length, double direction); + +private: + Style d_style; +}; + +#endif // QWT_DIAL_NEEDLE_H diff --git a/qwt/src/qwt_double_interval.cpp b/qwt/src/qwt_double_interval.cpp new file mode 100644 index 000000000..b8e2f412b --- /dev/null +++ b/qwt/src/qwt_double_interval.cpp @@ -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 + *****************************************************************************/ + +#include +#if QT_VERSION >= 0x040000 +#include +#else +#include +#endif + +#include "qwt_math.h" +#include "qwt_double_interval.h" + +/*! + \brief Normalize the limits of the interval + + If maxValue() < minValue() the limits will be inverted. + \return Normalized interval + + \sa isValid(), inverted() +*/ +QwtDoubleInterval QwtDoubleInterval::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() +*/ +QwtDoubleInterval QwtDoubleInterval::inverted() const +{ + int borderFlags = 0; + if ( d_borderFlags & ExcludeMinimum ) + borderFlags |= ExcludeMaximum; + if ( d_borderFlags & ExcludeMaximum ) + borderFlags |= ExcludeMinimum; + + return QwtDoubleInterval(d_maxValue, d_minValue, borderFlags); +} + +/*! + Test if a value is inside an interval + + \param value Value + \return true, if value >= minValue() && value <= maxValue() +*/ +bool QwtDoubleInterval::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 +QwtDoubleInterval QwtDoubleInterval::unite( + const QwtDoubleInterval &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 QwtDoubleInterval(); + else + return other; + } + if ( !other.isValid() ) + return *this; + + QwtDoubleInterval united; + int flags = 0; + + // 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; +} + +//! Intersect 2 intervals +QwtDoubleInterval QwtDoubleInterval::intersect( + const QwtDoubleInterval &other) const +{ + if ( !other.isValid() || !isValid() ) + return QwtDoubleInterval(); + + QwtDoubleInterval i1 = *this; + QwtDoubleInterval 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 QwtDoubleInterval(); + } + + if ( i1.maxValue() == i2.minValue() ) + { + if ( i1.borderFlags() & ExcludeMaximum || + i2.borderFlags() & ExcludeMinimum ) + { + return QwtDoubleInterval(); + } + } + + QwtDoubleInterval intersected; + int flags = 0; + + 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; +} + +//! Unites this interval with the given interval. +QwtDoubleInterval& QwtDoubleInterval::operator|=( + const QwtDoubleInterval &interval) +{ + *this = *this | interval; + return *this; +} + +//! Intersects this interval with the given interval. +QwtDoubleInterval& QwtDoubleInterval::operator&=( + const QwtDoubleInterval &interval) +{ + *this = *this & interval; + return *this; +} + +/*! + Test if two intervals overlap +*/ +bool QwtDoubleInterval::intersects(const QwtDoubleInterval &other) const +{ + if ( !isValid() || !other.isValid() ) + return false; + + QwtDoubleInterval i1 = *this; + QwtDoubleInterval 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 +*/ +QwtDoubleInterval QwtDoubleInterval::symmetrize(double value) const +{ + if ( !isValid() ) + return *this; + + const double delta = + qwtMax(qwtAbs(value - d_maxValue), qwtAbs(value - d_minValue)); + + return QwtDoubleInterval(value - delta, value + delta); +} + +/*! + Limit the interval, keeping the border modes + + \param lowerBound Lower limit + \param upperBound Upper limit + + \return Limited interval +*/ +QwtDoubleInterval QwtDoubleInterval::limited( + double lowerBound, double upperBound) const +{ + if ( !isValid() || lowerBound > upperBound ) + return QwtDoubleInterval(); + + double minValue = qwtMax(d_minValue, lowerBound); + minValue = qwtMin(minValue, upperBound); + + double maxValue = qwtMax(d_maxValue, lowerBound); + maxValue = qwtMin(maxValue, upperBound); + + return QwtDoubleInterval(minValue, maxValue, d_borderFlags); +} + +/*! + 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 + \sa isValid() +*/ +QwtDoubleInterval QwtDoubleInterval::extend(double value) const +{ + if ( !isValid() ) + return *this; + + return QwtDoubleInterval( qwtMin(value, d_minValue), + qwtMax(value, d_maxValue), d_borderFlags ); +} + +QwtDoubleInterval& QwtDoubleInterval::operator|=(double value) +{ + *this = *this | value; + return *this; +} diff --git a/qwt/src/qwt_double_interval.h b/qwt/src/qwt_double_interval.h new file mode 100644 index 000000000..8d0b06f45 --- /dev/null +++ b/qwt/src/qwt_double_interval.h @@ -0,0 +1,284 @@ +/* -*- 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_DOUBLE_INTERVAL_H +#define QWT_DOUBLE_INTERVAL_H + +#include "qwt_global.h" + +/*! + \brief A class representing an interval + + The interval is represented by 2 doubles, the lower and the upper limit. +*/ + +class QWT_EXPORT QwtDoubleInterval +{ +public: + /*! + Flag indicating if a border is included/excluded from an interval + + - IncludeBorders\n + min/max values are inside the interval + - ExcludeMinimum\n + min value is not included in the interval + - ExcludeMaximum\n + max value is not included in the interval + - ExcludeBorders\n + min/max values are not included in the interval + + \sa setBorderMode(), testBorderMode() + */ + enum BorderMode + { + IncludeBorders = 0, + + ExcludeMinimum = 1, + ExcludeMaximum = 2, + + ExcludeBorders = ExcludeMinimum | ExcludeMaximum + }; + + QwtDoubleInterval(); + QwtDoubleInterval(double minValue, double maxValue, + int borderFlags = IncludeBorders); + + void setInterval(double minValue, double maxValue, + int borderFlags = IncludeBorders); + + QwtDoubleInterval normalized() const; + QwtDoubleInterval inverted() const; + QwtDoubleInterval limited(double minValue, double maxValue) const; + + int operator==(const QwtDoubleInterval &) const; + int operator!=(const QwtDoubleInterval &) const; + + void setBorderFlags(int); + int 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 QwtDoubleInterval &) const; + QwtDoubleInterval intersect(const QwtDoubleInterval &) const; + QwtDoubleInterval unite(const QwtDoubleInterval &) const; + + QwtDoubleInterval operator|(const QwtDoubleInterval &) const; + QwtDoubleInterval operator&(const QwtDoubleInterval &) const; + + QwtDoubleInterval &operator|=(const QwtDoubleInterval &); + QwtDoubleInterval &operator&=(const QwtDoubleInterval &); + + QwtDoubleInterval extend(double value) const; + QwtDoubleInterval operator|(double) const; + QwtDoubleInterval &operator|=(double); + + bool isValid() const; + bool isNull() const; + void invalidate(); + + QwtDoubleInterval symmetrize(double value) const; + +private: + double d_minValue; + double d_maxValue; + int d_borderFlags; +}; + +/*! + \brief Default Constructor + + Creates an invalid interval [0.0, -1.0] + \sa setInterval(), isValid() +*/ +inline QwtDoubleInterval::QwtDoubleInterval(): + 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 QwtDoubleInterval::QwtDoubleInterval( + double minValue, double maxValue, int 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 QwtDoubleInterval::setInterval( + double minValue, double maxValue, int borderFlags) +{ + d_minValue = minValue; + d_maxValue = maxValue; + d_borderFlags = borderFlags; +} + +/*! + Change the border flags + + \param borderFlags Or'd BorderMode flags + \sa borderFlags() +*/ +inline void QwtDoubleInterval::setBorderFlags(int borderFlags) +{ + d_borderFlags = borderFlags; +} + +/*! + \return Border flags + \sa setBorderFlags() +*/ +inline int QwtDoubleInterval::borderFlags() const +{ + return d_borderFlags; +} + +/*! + Assign the lower limit of the interval + + \param minValue Minimum value +*/ +inline void QwtDoubleInterval::setMinValue(double minValue) +{ + d_minValue = minValue; +} + +/*! + Assign the upper limit of the interval + + \param maxValue Maximum value +*/ +inline void QwtDoubleInterval::setMaxValue(double maxValue) +{ + d_maxValue = maxValue; +} + +//! \return Lower limit of the interval +inline double QwtDoubleInterval::minValue() const +{ + return d_minValue; +} + +//! \return Upper limit of the interval +inline double QwtDoubleInterval::maxValue() const +{ + return d_maxValue; +} + +/*! + Return the width of an interval + The width of invalid intervals is 0.0, otherwise the result is + maxValue() - minValue(). + + \sa isValid() +*/ +inline double QwtDoubleInterval::width() const +{ + return isValid() ? (d_maxValue - d_minValue) : 0.0; +} + +/*! + Intersection of two intervals + \sa intersect() +*/ +inline QwtDoubleInterval QwtDoubleInterval::operator&( + const QwtDoubleInterval &interval ) const +{ + return intersect(interval); +} + +/*! + Union of two intervals + \sa unite() +*/ +inline QwtDoubleInterval QwtDoubleInterval::operator|( + const QwtDoubleInterval &interval) const +{ + return unite(interval); +} + +//! Compare two intervals +inline int QwtDoubleInterval::operator==(const QwtDoubleInterval &other) const +{ + return (d_minValue == other.d_minValue) && + (d_maxValue == other.d_maxValue) && + (d_borderFlags == other.d_borderFlags); +} + +//! Compare two intervals +inline int QwtDoubleInterval::operator!=(const QwtDoubleInterval &other) const +{ + return (!(*this == other)); +} + +/*! + Extend an interval + \sa extend() +*/ +inline QwtDoubleInterval QwtDoubleInterval::operator|(double value) const +{ + return extend(value); +} + +//! \return true, if isValid() && (minValue() >= maxValue()) +inline bool QwtDoubleInterval::isNull() const +{ + return isValid() && d_minValue >= d_maxValue; +} + +/*! + A interval is valid when minValue() <= maxValue(). + In case of QwtDoubleInterval::ExcludeBorders it is true + when minValue() < maxValue() +*/ +inline bool QwtDoubleInterval::isValid() const +{ + if ( (d_borderFlags & ExcludeBorders) == 0 ) + return d_minValue <= d_maxValue; + else + return d_minValue < d_maxValue; +} + +/*! + Invalidate the interval + + The limits are set to interval [0.0, -1.0] + \sa isValid() +*/ +inline void QwtDoubleInterval::invalidate() +{ + d_minValue = 0.0; + d_maxValue = -1.0; +} + +#endif diff --git a/qwt/src/qwt_double_range.cpp b/qwt/src/qwt_double_range.cpp new file mode 100644 index 000000000..c19428b88 --- /dev/null +++ b/qwt/src/qwt_double_range.cpp @@ -0,0 +1,392 @@ +/* -*- 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 "qwt_double_range.h" +#include "qwt_math.h" + +static double MinRelStep = 1.0e-10; +static double DefaultRelStep = 1.0e-2; +static double MinEps = 1.0e-10; + +/*! + The range is initialized to [0.0, 100.0], the + step size to 1.0, and the value to 0.0. +*/ +QwtDoubleRange::QwtDoubleRange(): + d_minValue(0.0), + d_maxValue(0.0), + d_step(1.0), + d_pageSize(1), + d_isValid(false), + d_value(0.0), + d_exactValue(0.0), + d_exactPrevValue(0.0), + d_prevValue(0.0), + d_periodic(false) +{ +} + +//! Destroys the QwtDoubleRange +QwtDoubleRange::~QwtDoubleRange() +{ +} + +//! Set the value to be valid/invalid +void QwtDoubleRange::setValid(bool isValid) +{ + if ( isValid != d_isValid ) + { + d_isValid = isValid; + valueChange(); + } +} + +//! Indicates if the value is valid +bool QwtDoubleRange::isValid() const +{ + return d_isValid; +} + +/*! + \brief No docs + + Description + \param x ??? + \param align + \todo Documentation +*/ +void QwtDoubleRange::setNewValue(double x, bool align) +{ + double vmin,vmax; + + d_prevValue = d_value; + + vmin = qwtMin(d_minValue, d_maxValue); + vmax = qwtMax(d_minValue, d_maxValue); + + // + // Range check + // + if (x < vmin) + { + if ((d_periodic) && (vmin != vmax)) + d_value = x + ::ceil( (vmin - x) / (vmax - vmin ) ) + * (vmax - vmin); + else + d_value = vmin; + } + else if (x > vmax) + { + if ((d_periodic) && (vmin != vmax)) + d_value = x - ::ceil( ( x - vmax) / (vmax - vmin )) + * (vmax - vmin); + else + d_value = vmax; + } + else + d_value = x; + + d_exactPrevValue = d_exactValue; + d_exactValue = d_value; + + // align to grid + if (align) + { + if (d_step != 0.0) + { + d_value = d_minValue + + qwtRound((d_value - d_minValue) / d_step) * d_step; + } + else + d_value = d_minValue; + + // correct rounding error at the border + if (fabs(d_value - d_maxValue) < MinEps * qwtAbs(d_step)) + d_value = d_maxValue; + + // correct rounding error if value = 0 + if (::fabs(d_value) < MinEps * qwtAbs(d_step)) + d_value = 0.0; + } + + if (!d_isValid || d_prevValue != d_value) + { + d_isValid = true; + valueChange(); + } +} + +/*! + \brief Adjust the value to the closest point in the step raster. + \param x value + \warning The value is clipped when it lies outside the range. + When the range is QwtDoubleRange::periodic, it will + be mapped to a point in the interval such that + \verbatim new value := x + n * (max. value - min. value)\endverbatim + with an integer number n. +*/ +void QwtDoubleRange::fitValue(double x) +{ + setNewValue(x, true); +} + + +/*! + \brief Set a new value without adjusting to the step raster + \param x new value + \warning The value is clipped when it lies outside the range. + When the range is QwtDoubleRange::periodic, it will + be mapped to a point in the interval such that + \verbatim new value := x + n * (max. value - min. value)\endverbatim + with an integer number n. +*/ +void QwtDoubleRange::setValue(double x) +{ + setNewValue(x, false); +} + +/*! + \brief Specify range and step size + + \param vmin lower boundary of the interval + \param vmax higher boundary of the interval + \param vstep step width + \param pageSize page size in steps + \warning + \li A change of the range changes the value if it lies outside the + new range. The current value + will *not* be adjusted to the new step raster. + \li vmax < vmin is allowed. + \li If the step size is left out or set to zero, it will be + set to 1/100 of the interval length. + \li If the step size has an absurd value, it will be corrected + to a better one. +*/ +void QwtDoubleRange::setRange(double vmin, double vmax, double vstep, int pageSize) +{ + bool rchg = ((d_maxValue != vmax) || (d_minValue != vmin)); + + if (rchg) + { + d_minValue = vmin; + d_maxValue = vmax; + } + + // + // look if the step width has an acceptable + // value or otherwise change it. + // + setStep(vstep); + + // + // limit page size + // + d_pageSize = qwtLim(pageSize,0, + int(qwtAbs((d_maxValue - d_minValue) / d_step))); + + // + // If the value lies out of the range, it + // will be changed. Note that it will not be adjusted to + // the new step width. + setNewValue(d_value, false); + + // call notifier after the step width has been + // adjusted. + if (rchg) + rangeChange(); +} + +/*! + \brief Change the step raster + \param vstep new step width + \warning The value will \e not be adjusted to the new step raster. +*/ +void QwtDoubleRange::setStep(double vstep) +{ + double intv = d_maxValue - d_minValue; + + double newStep; + if (vstep == 0.0) + newStep = intv * DefaultRelStep; + else + { + if ( (intv > 0 && vstep < 0) || (intv < 0 && vstep > 0) ) + newStep = -vstep; + else + newStep = vstep; + + if ( fabs(newStep) < fabs(MinRelStep * intv) ) + newStep = MinRelStep * intv; + } + + if (newStep != d_step) + { + d_step = newStep; + stepChange(); + } +} + + +/*! + \brief Make the range periodic + + When the range is periodic, the value will be set to a point + inside the interval such that + + \verbatim point = value + n * width \endverbatim + + if the user tries to set a new value which is outside the range. + If the range is nonperiodic (the default), values outside the + range will be clipped. + + \param tf true for a periodic range +*/ +void QwtDoubleRange::setPeriodic(bool tf) +{ + d_periodic = tf; +} + +/*! + \brief Increment the value by a specified number of steps + \param nSteps Number of steps to increment + \warning As a result of this operation, the new value will always be + adjusted to the step raster. +*/ +void QwtDoubleRange::incValue(int nSteps) +{ + if ( isValid() ) + setNewValue(d_value + double(nSteps) * d_step, true); +} + +/*! + \brief Increment the value by a specified number of pages + \param nPages Number of pages to increment. + A negative number decrements the value. + \warning The Page size is specified in the constructor. +*/ +void QwtDoubleRange::incPages(int nPages) +{ + if ( isValid() ) + setNewValue(d_value + double(nPages) * double(d_pageSize) * d_step, true); +} + +/*! + \brief Notify a change of value + + This virtual function is called whenever the value changes. + The default implementation does nothing. +*/ +void QwtDoubleRange::valueChange() +{ +} + + +/*! + \brief Notify a change of the range + + This virtual function is called whenever the range changes. + The default implementation does nothing. +*/ +void QwtDoubleRange::rangeChange() +{ +} + + +/*! + \brief Notify a change of the step size + + This virtual function is called whenever the step size changes. + The default implementation does nothing. +*/ +void QwtDoubleRange::stepChange() +{ +} + +/*! + \return the step size + \sa setStep(), setRange() +*/ +double QwtDoubleRange::step() const +{ + return qwtAbs(d_step); +} + +/*! + \brief Returns the value of the second border of the range + + maxValue returns the value which has been specified + as the second parameter in QwtDoubleRange::setRange. + + \sa setRange() +*/ +double QwtDoubleRange::maxValue() const +{ + return d_maxValue; +} + +/*! + \brief Returns the value at the first border of the range + + minValue returns the value which has been specified + as the first parameter in setRange(). + + \sa setRange() +*/ +double QwtDoubleRange::minValue() const +{ + return d_minValue; +} + +/*! + \brief Returns true if the range is periodic + \sa setPeriodic() +*/ +bool QwtDoubleRange::periodic() const +{ + return d_periodic; +} + +//! Returns the page size in steps. +int QwtDoubleRange::pageSize() const +{ + return d_pageSize; +} + +//! Returns the current value. +double QwtDoubleRange::value() const +{ + return d_value; +} + +/*! + \brief Returns the exact value + + The exact value is the value which QwtDoubleRange::value would return + if the value were not adjusted to the step raster. It differs from + the current value only if QwtDoubleRange::fitValue or + QwtDoubleRange::incValue have been used before. This function + is intended for internal use in derived classes. +*/ +double QwtDoubleRange::exactValue() const +{ + return d_exactValue; +} + +//! Returns the exact previous value +double QwtDoubleRange::exactPrevValue() const +{ + return d_exactPrevValue; +} + +//! Returns the previous value +double QwtDoubleRange::prevValue() const +{ + return d_prevValue; +} diff --git a/qwt/src/qwt_double_range.h b/qwt/src/qwt_double_range.h new file mode 100644 index 000000000..2f9d7da2a --- /dev/null +++ b/qwt/src/qwt_double_range.h @@ -0,0 +1,88 @@ +/* -*- 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_DOUBLE_RANGE_H +#define QWT_DOUBLE_RANGE_H + +#include "qwt_global.h" + +/*! + \brief A class which controls a value within an interval + + This class is useful as a base class or a member for sliders. + It represents an interval of type double within which a value can + be moved. The value can be either an arbitrary point inside + the interval (see QwtDoubleRange::setValue), or it can be fitted + into a step raster (see QwtDoubleRange::fitValue and + QwtDoubleRange::incValue). + + As a special case, a QwtDoubleRange can be periodic, which means that + a value outside the interval will be mapped to a value inside the + interval when QwtDoubleRange::setValue(), QwtDoubleRange::fitValue(), + QwtDoubleRange::incValue() or QwtDoubleRange::incPages() are called. +*/ + +class QWT_EXPORT QwtDoubleRange +{ +public: + QwtDoubleRange(); + virtual ~QwtDoubleRange(); + + void setRange(double vmin, double vmax, double vstep = 0.0, + int pagesize = 1); + + void setValid(bool); + bool isValid() const; + + virtual void setValue(double); + double value() const; + + void setPeriodic(bool tf); + bool periodic() const; + + void setStep(double); + double step() const; + + double maxValue() const; + double minValue() const; + + int pageSize() const; + + virtual void incValue(int); + virtual void incPages(int); + virtual void fitValue(double); + +protected: + + double exactValue() const; + double exactPrevValue() const; + double prevValue() const; + + virtual void valueChange(); + virtual void stepChange(); + virtual void rangeChange(); + +private: + void setNewValue(double x, bool align = false); + + double d_minValue; + double d_maxValue; + double d_step; + int d_pageSize; + + bool d_isValid; + double d_value; + double d_exactValue; + double d_exactPrevValue; + double d_prevValue; + + bool d_periodic; +}; + +#endif diff --git a/qwt/src/qwt_double_rect.cpp b/qwt/src/qwt_double_rect.cpp new file mode 100644 index 000000000..2b2a18ab4 --- /dev/null +++ b/qwt/src/qwt_double_rect.cpp @@ -0,0 +1,605 @@ +/* -*- 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 + +#if QT_VERSION < 0x040000 + +#include "qwt_math.h" +#include "qwt_double_rect.h" + +/*! + Constructs a null point. + + \sa QwtDoublePoint::isNull() +*/ +QwtDoublePoint::QwtDoublePoint(): + d_x(0.0), + d_y(0.0) +{ +} + +//! Constructs a point with coordinates specified by x and y. +QwtDoublePoint::QwtDoublePoint(double x, double y ): + d_x(x), + d_y(y) +{ +} + +/*! + Copy constructor. + + Constructs a point using the values of the point specified. +*/ +QwtDoublePoint::QwtDoublePoint(const QPoint &p): + d_x(double(p.x())), + d_y(double(p.y())) +{ +} + +/*! + Returns true if point1 is equal to point2; otherwise returns false. + + Two points are equal to each other if both x-coordinates and + both y-coordinates are the same. +*/ +bool QwtDoublePoint::operator==(const QwtDoublePoint &other) const +{ + return (d_x == other.d_x) && (d_y == other.d_y); +} + +//! Returns true if point1 is not equal to point2; otherwise returns false. +bool QwtDoublePoint::operator!=(const QwtDoublePoint &other) const +{ + return !operator==(other); +} + +/*! + Negates the coordinates of the point, and returns a point with the + new coordinates. (Inversion). +*/ +const QwtDoublePoint QwtDoublePoint::operator-() const +{ + return QwtDoublePoint(-d_x, -d_y); +} + +/*! + Adds the coordinates of the point to the corresponding coordinates of + the other point, and returns a point with the new coordinates. + (Vector addition.) +*/ +const QwtDoublePoint QwtDoublePoint::operator+( + const QwtDoublePoint &other) const +{ + return QwtDoublePoint(d_x + other.d_x, d_y + other.d_y); +} + +/*! + Subtracts the coordinates of the other point from the corresponding + coordinates of the given point, and returns a point with the new + coordinates. (Vector subtraction.) +*/ +const QwtDoublePoint QwtDoublePoint::operator-( + const QwtDoublePoint &other) const +{ + return QwtDoublePoint(d_x - other.d_x, d_y - other.d_y); +} + +/*! + Multiplies the coordinates of the point by the given scale factor, + and returns a point with the new coordinates. + (Scalar multiplication of a vector.) +*/ +const QwtDoublePoint QwtDoublePoint::operator*(double factor) const +{ + return QwtDoublePoint(d_x * factor, d_y * factor); +} + +/*! + Divides the coordinates of the point by the given scale factor, + and returns a point with the new coordinates. + (Scalar division of a vector.) +*/ +const QwtDoublePoint QwtDoublePoint::operator/(double factor) const +{ + return QwtDoublePoint(d_x / factor, d_y / factor); +} + +/*! + Adds the coordinates of this point to the corresponding coordinates + of the other point, and returns a reference to this point with the + new coordinates. This is equivalent to vector addition. +*/ +QwtDoublePoint &QwtDoublePoint::operator+=(const QwtDoublePoint &other) +{ + d_x += other.d_x; + d_y += other.d_y; + return *this; +} + +/*! + Subtracts the coordinates of the other point from the corresponding + coordinates of this point, and returns a reference to this point with + the new coordinates. This is equivalent to vector subtraction. +*/ +QwtDoublePoint &QwtDoublePoint::operator-=(const QwtDoublePoint &other) +{ + d_x -= other.d_x; + d_y -= other.d_y; + return *this; +} + +/*! + Multiplies the coordinates of this point by the given scale factor, + and returns a reference to this point with the new coordinates. + This is equivalent to scalar multiplication of a vector. +*/ +QwtDoublePoint &QwtDoublePoint::operator*=(double factor) +{ + d_x *= factor; + d_y *= factor; + return *this; +} + +/*! + Divides the coordinates of this point by the given scale factor, + and returns a references to this point with the new coordinates. + This is equivalent to scalar division of a vector. +*/ +QwtDoublePoint &QwtDoublePoint::operator/=(double factor) +{ + d_x /= factor; + d_y /= factor; + return *this; +} + +//! Constructs an invalid size. +QwtDoubleSize::QwtDoubleSize(): + d_width(-1.0), + d_height(-1.0) +{ +} + +//! Constructs a size with width width and height height. +QwtDoubleSize::QwtDoubleSize( double width, double height ): + d_width(width), + d_height(height) +{ +} + +//! Constructs a size with floating point accuracy from the given size. +QwtDoubleSize::QwtDoubleSize(const QSize &sz): + d_width(double(sz.width())), + d_height(double(sz.height())) +{ +} + +//! Swaps the width and height values. +void QwtDoubleSize::transpose() +{ + double tmp = d_width; + d_width = d_height; + d_height = tmp; +} + +/*! + Returns a size with the maximum width and height of this + size and other. +*/ +QwtDoubleSize QwtDoubleSize::expandedTo( + const QwtDoubleSize &other) const +{ + return QwtDoubleSize( + qwtMax(d_width, other.d_width), + qwtMax(d_height, other.d_height) + ); +} + +/*! + Returns a size with the minimum width and height of this size and other. +*/ +QwtDoubleSize QwtDoubleSize::boundedTo( + const QwtDoubleSize &other) const +{ + return QwtDoubleSize( + qwtMin(d_width, other.d_width), + qwtMin(d_height, other.d_height) + ); +} + +//! Returns true if s1 and s2 are equal; otherwise returns false. +bool QwtDoubleSize::operator==(const QwtDoubleSize &other) const +{ + return d_width == other.d_width && d_height == other.d_height; +} + +//! Returns true if s1 and s2 are different; otherwise returns false. +bool QwtDoubleSize::operator!=(const QwtDoubleSize &other) const +{ + return !operator==(other); +} + +/*! + Returns the size formed by adding both components by + the components of other. Each component is added separately. +*/ +const QwtDoubleSize QwtDoubleSize::operator+( + const QwtDoubleSize &other) const +{ + return QwtDoubleSize(d_width + other.d_width, + d_height + other.d_height); +} + +/*! + Returns the size formed by subtracting both components by + the components of other. Each component is subtracted separately. +*/ +const QwtDoubleSize QwtDoubleSize::operator-( + const QwtDoubleSize &other) const +{ + return QwtDoubleSize(d_width - other.d_width, + d_height - other.d_height); +} + +//! Returns the size formed by multiplying both components by c. +const QwtDoubleSize QwtDoubleSize::operator*(double c) const +{ + return QwtDoubleSize(d_width * c, d_height * c); +} + +//! Returns the size formed by dividing both components by c. +const QwtDoubleSize QwtDoubleSize::operator/(double c) const +{ + return QwtDoubleSize(d_width / c, d_height / c); +} + +//! Adds size other to this size and returns a reference to this size. +QwtDoubleSize &QwtDoubleSize::operator+=(const QwtDoubleSize &other) +{ + d_width += other.d_width; + d_height += other.d_height; + return *this; +} + +//! Subtracts size other from this size and returns a reference to this size. +QwtDoubleSize &QwtDoubleSize::operator-=(const QwtDoubleSize &other) +{ + d_width -= other.d_width; + d_height -= other.d_height; + return *this; +} + +/* + Multiplies this size's width and height by c, + and returns a reference to this size. +*/ +QwtDoubleSize &QwtDoubleSize::operator*=(double c) +{ + d_width *= c; + d_height *= c; + return *this; +} + +/* + Devides this size's width and height by c, + and returns a reference to this size. +*/ +QwtDoubleSize &QwtDoubleSize::operator/=(double c) +{ + d_width /= c; + d_height /= c; + return *this; +} + +//! Constructs an rectangle with all components set to 0.0 +QwtDoubleRect::QwtDoubleRect(): + d_left(0.0), + d_right(0.0), + d_top(0.0), + d_bottom(0.0) +{ +} + +/*! + Constructs an rectangle with x1 to x2 as x-range and, + y1 to y2 as y-range. +*/ +QwtDoubleRect::QwtDoubleRect(double left, double top, + double width, double height): + d_left(left), + d_right(left + width), + d_top(top), + d_bottom(top + height) +{ +} + +/*! + Constructs a rectangle with topLeft as the top-left corner and + size as the rectangle size. +*/ +QwtDoubleRect::QwtDoubleRect( + const QwtDoublePoint &p, const QwtDoubleSize &size): + d_left(p.x()), + d_right(p.x() + size.width()), + d_top(p.y()), + d_bottom(p.y() + size.height()) +{ +} + +QwtDoubleRect::QwtDoubleRect(const QRect &rect): + d_left(rect.left()), + d_right(rect.right()), + d_top(rect.top()), + d_bottom(rect.bottom()) +{ +} + +QRect QwtDoubleRect::toRect() const +{ + return QRect(qRound(x()), qRound(y()), qRound(width()), qRound(height())); +} + +/*! + Set the x-range from x1 to x2 and the y-range from y1 to y2. +*/ +void QwtDoubleRect::setRect(double left, double top, + double width, double height) +{ + d_left = left; + d_right = left + width; + d_top = top; + d_bottom = top + height; +} + +/*! + Sets the size of the rectangle to size. + Changes x2 and y2 only. +*/ +void QwtDoubleRect::setSize(const QwtDoubleSize &size) +{ + setWidth(size.width()); + setHeight(size.height()); +} + +/*! + Returns a normalized rectangle, i.e. a rectangle that has a non-negative + width and height. + + It swaps x1 and x2 if x1() > x2(), and swaps y1 and y2 if y1() > y2(). +*/ +QwtDoubleRect QwtDoubleRect::normalized() const +{ + QwtDoubleRect r; + if ( d_right < d_left ) + { + r.d_left = d_right; + r.d_right = d_left; + } + else + { + r.d_left = d_left; + r.d_right = d_right; + } + if ( d_bottom < d_top ) + { + r.d_top = d_bottom; + r.d_bottom = d_top; + } + else + { + r.d_top = d_top; + r.d_bottom = d_bottom; + } + return r; +} + +/*! + Returns the bounding rectangle of this rectangle and rectangle other. + r.unite(s) is equivalent to r|s. +*/ +QwtDoubleRect QwtDoubleRect::unite(const QwtDoubleRect &other) const +{ + return *this | other; +} + +/*! + Returns the intersection of this rectangle and rectangle other. + r.intersect(s) is equivalent to r&s. +*/ +QwtDoubleRect QwtDoubleRect::intersect(const QwtDoubleRect &other) const +{ + return *this & other; +} + +/*! + Returns true if this rectangle intersects with rectangle other; + otherwise returns false. +*/ +bool QwtDoubleRect::intersects(const QwtDoubleRect &other) const +{ + return ( qwtMax(d_left, other.d_left) <= qwtMin(d_right, other.d_right) ) && + ( qwtMax(d_top, other.d_top ) <= qwtMin(d_bottom, other.d_bottom) ); +} + +//! Returns true if this rect and other are equal; otherwise returns false. +bool QwtDoubleRect::operator==(const QwtDoubleRect &other) const +{ + return d_left == other.d_left && d_right == other.d_right && + d_top == other.d_top && d_bottom == other.d_bottom; +} + +//! Returns true if this rect and other are different; otherwise returns false. +bool QwtDoubleRect::operator!=(const QwtDoubleRect &other) const +{ + return !operator==(other); +} + +/*! + Returns the bounding rectangle of this rectangle and rectangle other. + The bounding rectangle of a nonempty rectangle and an empty or + invalid rectangle is defined to be the nonempty rectangle. +*/ +QwtDoubleRect QwtDoubleRect::operator|(const QwtDoubleRect &other) const +{ + if ( isEmpty() ) + return other; + + if ( other.isEmpty() ) + return *this; + + const double minX = qwtMin(d_left, other.d_left); + const double maxX = qwtMax(d_right, other.d_right); + const double minY = qwtMin(d_top, other.d_top); + const double maxY = qwtMax(d_bottom, other.d_bottom); + + return QwtDoubleRect(minX, minY, maxX - minX, maxY - minY); +} + +/*! + Returns the intersection of this rectangle and rectangle other. + Returns an empty rectangle if there is no intersection. +*/ +QwtDoubleRect QwtDoubleRect::operator&(const QwtDoubleRect &other) const +{ + if (isNull() || other.isNull()) + return QwtDoubleRect(); + + const QwtDoubleRect r1 = normalized(); + const QwtDoubleRect r2 = other.normalized(); + + const double minX = qwtMax(r1.left(), r2.left()); + const double maxX = qwtMin(r1.right(), r2.right()); + const double minY = qwtMax(r1.top(), r2.top()); + const double maxY = qwtMin(r1.bottom(), r2.bottom()); + + return QwtDoubleRect(minX, minY, maxX - minX, maxY - minY); +} + +//! Unites this rectangle with rectangle other. +QwtDoubleRect &QwtDoubleRect::operator|=(const QwtDoubleRect &other) +{ + *this = *this | other; + return *this; +} + +//! Intersects this rectangle with rectangle other. +QwtDoubleRect &QwtDoubleRect::operator&=(const QwtDoubleRect &other) +{ + *this = *this & other; + return *this; +} + +//! Returns the center point of the rectangle. +QwtDoublePoint QwtDoubleRect::center() const +{ + return QwtDoublePoint(d_left + (d_right - d_left) / 2.0, + d_top + (d_bottom - d_top) / 2.0); +} + +/*! + Returns true if the point (x, y) is inside or on the edge of the rectangle; + otherwise returns false. + + If proper is true, this function returns true only if p is inside + (not on the edge). +*/ +bool QwtDoubleRect::contains(double x, double y, bool proper) const +{ + if ( proper ) + return x > d_left && x < d_right && y > d_top && y < d_bottom; + else + return x >= d_left && x <= d_right && y >= d_top && y <= d_bottom; +} + +/*! + Returns true if the point p is inside or on the edge of the rectangle; + otherwise returns false. + + If proper is true, this function returns true only if p is inside + (not on the edge). +*/ +bool QwtDoubleRect::contains(const QwtDoublePoint &p, bool proper) const +{ + return contains(p.x(), p.y(), proper); +} + +/*! + Returns true if the rectangle other is inside this rectangle; + otherwise returns false. + + If proper is true, this function returns true only if other is entirely + inside (not on the edge). +*/ +bool QwtDoubleRect::contains(const QwtDoubleRect &other, bool proper) const +{ + return contains(other.d_left, other.d_top, proper) && + contains(other.d_right, other.d_bottom, proper); +} + +//! moves x1() to x, leaving the size unchanged +void QwtDoubleRect::moveLeft(double x) +{ + const double w = width(); + d_left = x; + d_right = d_left + w; +} + +//! moves x1() to x, leaving the size unchanged +void QwtDoubleRect::moveRight(double x) +{ + const double w = width(); + d_right = x; + d_left = d_right - w; +} + +//! moves y1() to y, leaving the size unchanged +void QwtDoubleRect::moveTop(double y) +{ + const double h = height(); + d_top = y; + d_bottom = d_top + h; +} + +//! moves y1() to y, leaving the size unchanged +void QwtDoubleRect::moveBottom(double y) +{ + const double h = height(); + d_bottom = y; + d_top = d_bottom - h; +} + +//! moves left() to x and top() to y, leaving the size unchanged +void QwtDoubleRect::moveTo(double x, double y) +{ + moveLeft(x); + moveTop(y); +} + +//! moves x1() by dx and y1() by dy. leaving the size unchanged +void QwtDoubleRect::moveBy(double dx, double dy) +{ + d_left += dx; + d_right += dx; + d_top += dy; + d_bottom += dy; +} + +//! moves the center to pos, leaving the size unchanged +void QwtDoubleRect::moveCenter(const QwtDoublePoint &pos) +{ + moveCenter(pos.x(), pos.y()); +} + +//! moves the center to (x, y), leaving the size unchanged +void QwtDoubleRect::moveCenter(double x, double y) +{ + moveTo(x - width() / 2.0, y - height() / 2.0); +} + +#endif // QT_VERSION < 0x040000 diff --git a/qwt/src/qwt_double_rect.h b/qwt/src/qwt_double_rect.h new file mode 100644 index 000000000..341e17c08 --- /dev/null +++ b/qwt/src/qwt_double_rect.h @@ -0,0 +1,501 @@ +/* -*- 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_DOUBLE_RECT_H +#define QWT_DOUBLE_RECT_H 1 + +#include "qwt_global.h" +#include "qwt_array.h" + +#if QT_VERSION >= 0x040000 + +#include +#include +#include + +/*! + \typedef QPointF QwtDoublePoint + \brief This is a typedef, see Trolltech Documentation for QPointF + in QT assistant 4.x. As soon as Qt3 compatibility is dropped + this typedef will disappear. +*/ +typedef QPointF QwtDoublePoint; + +/*! + \typedef QSizeF QwtDoubleSize + \brief This is a typedef, see Trolltech Documentation for QSizeF + in QT assistant 4.x. As soon as Qt3 compatibility is dropped + this typedef will disappear. +*/ +typedef QSizeF QwtDoubleSize; + +/*! + \typedef QRectF QwtDoubleRect + \brief This is a typedef, see Trolltech Documentation for QRectF + in QT assistant 4.x. As soon as Qt3 compatibility is dropped + this typedef will disappear. +*/ +typedef QRectF QwtDoubleRect; + +#else + +#include +#include +#include + +/*! + \brief The QwtDoublePoint class defines a point in double coordinates +*/ + +class QWT_EXPORT QwtDoublePoint +{ +public: + QwtDoublePoint(); + QwtDoublePoint(double x, double y); + QwtDoublePoint(const QPoint &); + + QPoint toPoint() const; + + bool isNull() const; + + double x() const; + double y() const; + + double &rx(); + double &ry(); + + void setX(double x); + void setY(double y); + + bool operator==(const QwtDoublePoint &) const; + bool operator!=(const QwtDoublePoint &) const; + + const QwtDoublePoint operator-() const; + const QwtDoublePoint operator+(const QwtDoublePoint &) const; + const QwtDoublePoint operator-(const QwtDoublePoint &) const; + const QwtDoublePoint operator*(double) const; + const QwtDoublePoint operator/(double) const; + + QwtDoublePoint &operator+=(const QwtDoublePoint &); + QwtDoublePoint &operator-=(const QwtDoublePoint &); + QwtDoublePoint &operator*=(double); + QwtDoublePoint &operator/=(double); + +private: + double d_x; + double d_y; +}; + +/*! + The QwtDoubleSize class defines a size in double coordinates +*/ + +class QWT_EXPORT QwtDoubleSize +{ +public: + QwtDoubleSize(); + QwtDoubleSize(double width, double height); + QwtDoubleSize(const QSize &); + + bool isNull() const; + bool isEmpty() const; + bool isValid() const; + + double width() const; + double height() const; + void setWidth( double w ); + void setHeight( double h ); + void transpose(); + + QwtDoubleSize expandedTo(const QwtDoubleSize &) const; + QwtDoubleSize boundedTo(const QwtDoubleSize &) const; + + bool operator==(const QwtDoubleSize &) const; + bool operator!=(const QwtDoubleSize &) const; + + const QwtDoubleSize operator+(const QwtDoubleSize &) const; + const QwtDoubleSize operator-(const QwtDoubleSize &) const; + const QwtDoubleSize operator*(double) const; + const QwtDoubleSize operator/(double) const; + + QwtDoubleSize &operator+=(const QwtDoubleSize &); + QwtDoubleSize &operator-=(const QwtDoubleSize &); + QwtDoubleSize &operator*=(double c); + QwtDoubleSize &operator/=(double c); + +private: + double d_width; + double d_height; +}; + +/*! + The QwtDoubleRect class defines a size in double coordinates. +*/ + +class QWT_EXPORT QwtDoubleRect +{ +public: + QwtDoubleRect(); + QwtDoubleRect(double left, double top, double width, double height); + QwtDoubleRect(const QwtDoublePoint&, const QwtDoubleSize &); + + QwtDoubleRect(const QRect &); + QRect toRect() const; + + bool isNull() const; + bool isEmpty() const; + bool isValid() const; + + QwtDoubleRect normalized() const; + + double x() const; + double y() const; + + double left() const; + double right() const; + double top() const; + double bottom() const; + + void setX(double); + void setY(double); + + void setLeft(double); + void setRight(double); + void setTop(double); + void setBottom(double); + + QwtDoublePoint center() const; + + void moveLeft(double x); + void moveRight(double x); + void moveTop(double y ); + void moveBottom(double y ); + void moveTo(double x, double y); + void moveTo(const QwtDoublePoint &); + void moveBy(double dx, double dy); + void moveCenter(const QwtDoublePoint &); + void moveCenter(double dx, double dy); + + void setRect(double x1, double x2, double width, double height); + + double width() const; + double height() const; + QwtDoubleSize size() const; + + void setWidth(double w ); + void setHeight(double h ); + void setSize(const QwtDoubleSize &); + + QwtDoubleRect operator|(const QwtDoubleRect &r) const; + QwtDoubleRect operator&(const QwtDoubleRect &r) const; + QwtDoubleRect &operator|=(const QwtDoubleRect &r); + QwtDoubleRect &operator&=(const QwtDoubleRect &r); + bool operator==( const QwtDoubleRect &) const; + bool operator!=( const QwtDoubleRect &) const; + + bool contains(const QwtDoublePoint &p, bool proper = false) const; + bool contains(double x, double y, bool proper = false) const; + bool contains(const QwtDoubleRect &r, bool proper=false) const; + + QwtDoubleRect unite(const QwtDoubleRect &) const; + QwtDoubleRect intersect(const QwtDoubleRect &) const; + bool intersects(const QwtDoubleRect &) const; + + QwtDoublePoint bottomRight() const; + QwtDoublePoint topRight() const; + QwtDoublePoint topLeft() const; + QwtDoublePoint bottomLeft() const; + +private: + double d_left; + double d_right; + double d_top; + double d_bottom; +}; + +/*! + Returns true if the point is null; otherwise returns false. + + A point is considered to be null if both the x- and y-coordinates + are equal to zero. +*/ +inline bool QwtDoublePoint::isNull() const +{ + return d_x == 0.0 && d_y == 0.0; +} + +//! Returns the x-coordinate of the point. +inline double QwtDoublePoint::x() const +{ + return d_x; +} + +//! Returns the y-coordinate of the point. +inline double QwtDoublePoint::y() const +{ + return d_y; +} + +//! Returns a reference to the x-coordinate of the point. +inline double &QwtDoublePoint::rx() +{ + return d_x; +} + +//! Returns a reference to the y-coordinate of the point. +inline double &QwtDoublePoint::ry() +{ + return d_y; +} + +//! Sets the x-coordinate of the point to the value specified by x. +inline void QwtDoublePoint::setX(double x) +{ + d_x = x; +} + +//! Sets the y-coordinate of the point to the value specified by y. +inline void QwtDoublePoint::setY(double y) +{ + d_y = y; +} + +/*! + Rounds the coordinates of this point to the nearest integer and + returns a QPoint with these rounded coordinates. +*/ +inline QPoint QwtDoublePoint::toPoint() const +{ + return QPoint(qRound(d_x), qRound(d_y)); +} + +/*! + Returns true if the width is 0 and the height is 0; + otherwise returns false. +*/ +inline bool QwtDoubleSize::isNull() const +{ + return d_width == 0.0 && d_height == 0.0; +} + +/*! + Returns true if the width is <= 0.0 or the height is <= 0.0, + otherwise false. +*/ +inline bool QwtDoubleSize::isEmpty() const +{ + return d_width <= 0.0 || d_height <= 0.0; +} + +/*! + Returns true if the width is equal to or greater than 0.0 and the height + is equal to or greater than 0.0; otherwise returns false. +*/ +inline bool QwtDoubleSize::isValid() const +{ + return d_width >= 0.0 && d_height >= 0.0; +} + +//! Returns the width. +inline double QwtDoubleSize::width() const +{ + return d_width; +} + +//! Returns the height. +inline double QwtDoubleSize::height() const +{ + return d_height; +} + +//! Sets the width to width. +inline void QwtDoubleSize::setWidth(double width) +{ + d_width = width; +} + +//! Sets the height to height. +inline void QwtDoubleSize::setHeight(double height) +{ + d_height = height; +} + +/*! + Returns true if the rectangle is a null rectangle; otherwise returns false. + A null rectangle has both the width and the height set to 0. + A null rectangle is also empty and invalid. + + \sa isEmpty(), isValid() +*/ +inline bool QwtDoubleRect::isNull() const +{ + return d_right == d_left && d_bottom == d_top; +} + +/*! + Returns true if the rectangle is empty; otherwise returns false. + An empty rectangle has a width() <= 0 or height() <= 0. + An empty rectangle is not valid. isEmpty() == !isValid() + + \sa isNull(), isValid() +*/ +inline bool QwtDoubleRect::isEmpty() const +{ + return d_left >= d_right || d_top >= d_bottom; +} + +/*! + Returns true if the rectangle is valid; otherwise returns false. + A valid rectangle has a width() > 0 and height() > 0. + Note that non-trivial operations like intersections are not defined + for invalid rectangles. isValid() == !isEmpty() + + \sa isNull(), isEmpty(), and normalized(). +*/ +inline bool QwtDoubleRect::isValid() const +{ + return d_left < d_right && d_top < d_bottom; +} + +//! Returns x +inline double QwtDoubleRect::x() const +{ + return d_left; +} + +//! Returns y +inline double QwtDoubleRect::y() const +{ + return d_top; +} + +//! Returns left +inline double QwtDoubleRect::left() const +{ + return d_left; +} + +//! Returns right +inline double QwtDoubleRect::right() const +{ + return d_right; +} + +//! Returns top +inline double QwtDoubleRect::top() const +{ + return d_top; +} + +//! Returns bottom +inline double QwtDoubleRect::bottom() const +{ + return d_bottom; +} + +//! Set left +inline void QwtDoubleRect::setX(double x) +{ + d_left = x; +} + +//! Set left +inline void QwtDoubleRect::setY(double y) +{ + d_top = y; +} + +//! Set left +inline void QwtDoubleRect::setLeft(double x) +{ + d_left = x; +} + +//! Set right +inline void QwtDoubleRect::setRight(double x) +{ + d_right = x; +} + +//! Set top +inline void QwtDoubleRect::setTop(double y) +{ + d_top = y; +} + +//! Set bottom +inline void QwtDoubleRect::setBottom(double y) +{ + d_bottom = y; +} + +//! Returns the width +inline double QwtDoubleRect::width() const +{ + return d_right - d_left; +} + +//! Returns the height +inline double QwtDoubleRect::height() const +{ + return d_bottom - d_top; +} + +//! Returns the size +inline QwtDoubleSize QwtDoubleRect::size() const +{ + return QwtDoubleSize(width(), height()); +} + +//! Set the width, by right = left + w; +inline void QwtDoubleRect::setWidth(double w) +{ + d_right = d_left + w; +} + +//! Set the height, by bottom = top + h; +inline void QwtDoubleRect::setHeight(double h) +{ + d_bottom = d_top + h; +} + +/*! + Moves the top left corner of the rectangle to p, + without changing the rectangles size. +*/ +inline void QwtDoubleRect::moveTo(const QwtDoublePoint &p) +{ + moveTo(p.x(), p.y()); +} + +inline QwtDoublePoint QwtDoubleRect::bottomRight() const +{ + return QwtDoublePoint(bottom(), right()); +} + +inline QwtDoublePoint QwtDoubleRect::topRight() const +{ + return QwtDoublePoint(top(), right()); +} + +inline QwtDoublePoint QwtDoubleRect::topLeft() const +{ + return QwtDoublePoint(top(), left()); +} + +inline QwtDoublePoint QwtDoubleRect::bottomLeft() const +{ + return QwtDoublePoint(bottom(), left()); +} + + +#endif // QT_VERSION < 0x040000 + +#endif // QWT_DOUBLE_RECT_H diff --git a/qwt/src/qwt_dyngrid_layout.cpp b/qwt/src/qwt_dyngrid_layout.cpp new file mode 100644 index 000000000..78fba6fc9 --- /dev/null +++ b/qwt/src/qwt_dyngrid_layout.cpp @@ -0,0 +1,705 @@ +/* -*- 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 "qwt_dyngrid_layout.h" +#include "qwt_math.h" + +#if QT_VERSION < 0x040000 +#include +#else +#include +#endif + +class QwtDynGridLayout::PrivateData +{ +public: + +#if QT_VERSION < 0x040000 + class LayoutIterator: public QGLayoutIterator + { + public: + LayoutIterator(PrivateData *data): + d_data(data) + { + d_iterator = d_data->itemList.begin(); + } + + virtual QLayoutItem *current() + { + if (d_iterator == d_data->itemList.end()) + return NULL; + + return *d_iterator; + } + + virtual QLayoutItem *next() + { + if (d_iterator == d_data->itemList.end()) + return NULL; + + d_iterator++; + if (d_iterator == d_data->itemList.end()) + return NULL; + + return *d_iterator; + } + + virtual QLayoutItem *takeCurrent() + { + if ( d_iterator == d_data->itemList.end() ) + return NULL; + + QLayoutItem *item = *d_iterator; + + d_data->isDirty = true; + d_iterator = d_data->itemList.remove(d_iterator); + return item; + } + + private: + + QValueListIterator d_iterator; + QwtDynGridLayout::PrivateData *d_data; + }; +#endif + + PrivateData(): + isDirty(true) + { + } + +#if QT_VERSION < 0x040000 + typedef QValueList LayoutItemList; +#else + typedef QList LayoutItemList; +#endif + + mutable LayoutItemList itemList; + + uint maxCols; + uint numRows; + uint numCols; + +#if QT_VERSION < 0x040000 + QSizePolicy::ExpandData expanding; +#else + Qt::Orientations expanding; +#endif + + bool isDirty; + QwtArray itemSizeHints; +}; + + +/*! + \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); +} + +#if QT_VERSION < 0x040000 +/*! + \param parent Parent widget + \param spacing Spacing +*/ +QwtDynGridLayout::QwtDynGridLayout(QLayout *parent, int spacing): + QLayout(parent, spacing) +{ + init(); +} +#endif + +/*! + \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->maxCols = d_data->numRows + = d_data->numCols = 0; + +#if QT_VERSION < 0x040000 + d_data->expanding = QSizePolicy::NoDirection; + setSupportsMargin(true); +#else + d_data->expanding = 0; +#endif +} + +//! Destructor + +QwtDynGridLayout::~QwtDynGridLayout() +{ +#if QT_VERSION < 0x040000 + deleteAllItems(); +#endif + + delete d_data; +} + +//! Invalidate all internal caches +void QwtDynGridLayout::invalidate() +{ + d_data->isDirty = true; + QLayout::invalidate(); +} + +void QwtDynGridLayout::updateLayoutCache() +{ + d_data->itemSizeHints.resize(itemCount()); + + int index = 0; + + for (PrivateData::LayoutItemList::iterator it = d_data->itemList.begin(); + it != d_data->itemList.end(); ++it, index++) + { + d_data->itemSizeHints[int(index)] = (*it)->sizeHint(); + } + + d_data->isDirty = false; +} + +/*! + Limit the number of columns. + \param maxCols upper limit, 0 means unlimited + \sa maxCols() +*/ + +void QwtDynGridLayout::setMaxCols(uint maxCols) +{ + d_data->maxCols = maxCols; +} + +/*! + Return the upper limit for the number of columns. + 0 means unlimited, what is the default. + \sa setMaxCols() +*/ + +uint QwtDynGridLayout::maxCols() const +{ + return d_data->maxCols; +} + +//! Adds item to the next free position. + +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(); +} + +#if QT_VERSION < 0x040000 +/*! + \return An iterator over the children of this layout. +*/ + +QLayoutIterator QwtDynGridLayout::iterator() +{ + return QLayoutIterator( + new QwtDynGridLayout::PrivateData::LayoutIterator(d_data) ); +} + +void QwtDynGridLayout::setExpanding(QSizePolicy::ExpandData expanding) +{ + d_data->expanding = expanding; +} + +QSizePolicy::ExpandData QwtDynGridLayout::expanding() const +{ + return d_data->expanding; +} + +#else // QT_VERSION >= 0x040000 + +/*! + Find the item at a spcific index + + \param index 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 spcific index and remove it from the layout + + \param index Index + \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; +} + +/*! + 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. + \sa setExpandingDirections() +*/ +Qt::Orientations QwtDynGridLayout::expandingDirections() const +{ + return d_data->expanding; +} + +#endif + +/*! + Reorganizes columns and rows and resizes managed widgets within + the rectangle rect. + + \param rect Layout geometry +*/ +void QwtDynGridLayout::setGeometry(const QRect &rect) +{ + QLayout::setGeometry(rect); + + if ( isEmpty() ) + return; + + d_data->numCols = columnsForWidth(rect.width()); + d_data->numRows = itemCount() / d_data->numCols; + if ( itemCount() % d_data->numCols ) + d_data->numRows++; + +#if QT_VERSION < 0x040000 + QValueList itemGeometries = layoutItems(rect, d_data->numCols); +#else + QList itemGeometries = layoutItems(rect, d_data->numCols); +#endif + + int index = 0; + for (PrivateData::LayoutItemList::iterator it = d_data->itemList.begin(); + it != d_data->itemList.end(); ++it) + { + QWidget *w = (*it)->widget(); + if ( w ) + { + w->setGeometry(itemGeometries[index]); + index++; + } + } +} + +/*! + Calculate the number of columns for a given width. It tries to + use as many columns as possible (limited by maxCols()) + + \param width Available width for all columns + \sa maxCols(), setMaxCols() +*/ + +uint QwtDynGridLayout::columnsForWidth(int width) const +{ + if ( isEmpty() ) + return 0; + + const int maxCols = (d_data->maxCols > 0) ? d_data->maxCols : itemCount(); + if ( maxRowWidth(maxCols) <= width ) + return maxCols; + + for (int numCols = 2; numCols <= maxCols; numCols++ ) + { + const int rowWidth = maxRowWidth(numCols); + if ( rowWidth > width ) + return numCols - 1; + } + + return 1; // At least 1 column +} + +/*! + Calculate the width of a layout for a given number of + columns. + + \param numCols Given number of columns + \param itemWidth Array of the width hints for all items +*/ +int QwtDynGridLayout::maxRowWidth(int numCols) const +{ + int col; + + QwtArray colWidth(numCols); + for ( col = 0; col < (int)numCols; col++ ) + colWidth[col] = 0; + + if ( d_data->isDirty ) + ((QwtDynGridLayout*)this)->updateLayoutCache(); + + for ( uint index = 0; + index < (uint)d_data->itemSizeHints.count(); index++ ) + { + col = index % numCols; + colWidth[col] = qwtMax(colWidth[col], + d_data->itemSizeHints[int(index)].width()); + } + + int rowWidth = 2 * margin() + (numCols - 1) * spacing(); + for ( col = 0; col < (int)numCols; 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 ) + ((QwtDynGridLayout*)this)->updateLayoutCache(); + + int w = 0; + for ( uint i = 0; i < (uint)d_data->itemSizeHints.count(); i++ ) + { + const int itemW = d_data->itemSizeHints[int(i)].width(); + if ( itemW > w ) + w = itemW; + } + + return w; +} + +/*! + Calculate the geometries of the layout items for a layout + with numCols columns and a given rect. + + \param rect Rect where to place the items + \param numCols Number of columns + \return item geometries +*/ + +#if QT_VERSION < 0x040000 +QValueList QwtDynGridLayout::layoutItems(const QRect &rect, + uint numCols) const +#else +QList QwtDynGridLayout::layoutItems(const QRect &rect, + uint numCols) const +#endif +{ +#if QT_VERSION < 0x040000 + QValueList itemGeometries; +#else + QList itemGeometries; +#endif + if ( numCols == 0 || isEmpty() ) + return itemGeometries; + + uint numRows = itemCount() / numCols; + if ( numRows % itemCount() ) + numRows++; + + QwtArray rowHeight(numRows); + QwtArray colWidth(numCols); + + layoutGrid(numCols, rowHeight, colWidth); + + bool expandH, expandV; +#if QT_VERSION >= 0x040000 + expandH = expandingDirections() & Qt::Horizontal; + expandV = expandingDirections() & Qt::Vertical; +#else + expandH = expanding() & QSizePolicy::Horizontally; + expandV = expanding() & QSizePolicy::Vertically; +#endif + + if ( expandH || expandV ) + stretchGrid(rect, numCols, rowHeight, colWidth); + + QwtDynGridLayout *that = (QwtDynGridLayout *)this; + const int maxCols = d_data->maxCols; + that->d_data->maxCols = numCols; + const QRect alignedRect = alignmentRect(rect); + that->d_data->maxCols = maxCols; + + const int xOffset = expandH ? 0 : alignedRect.x(); + const int yOffset = expandV ? 0 : alignedRect.y(); + + QwtArray colX(numCols); + QwtArray rowY(numRows); + + const int xySpace = spacing(); + + rowY[0] = yOffset + margin(); + for ( int r = 1; r < (int)numRows; r++ ) + rowY[r] = rowY[r-1] + rowHeight[r-1] + xySpace; + + colX[0] = xOffset + margin(); + for ( int c = 1; c < (int)numCols; 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 / numCols; + const int col = i % numCols; + + 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 numCols columns. + + \param numCols 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 numCols, + QwtArray& rowHeight, QwtArray& colWidth) const +{ + if ( numCols <= 0 ) + return; + + if ( d_data->isDirty ) + ((QwtDynGridLayout*)this)->updateLayoutCache(); + + for ( uint index = 0; + index < (uint)d_data->itemSizeHints.count(); index++ ) + { + const int row = index / numCols; + const int col = index % numCols; + + const QSize &size = d_data->itemSizeHints[int(index)]; + + rowHeight[row] = (col == 0) + ? size.height() : qwtMax(rowHeight[row], size.height()); + colWidth[col] = (row == 0) + ? size.width() : qwtMax(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 the width w. + \sa hasHeightForWidth() +*/ +int QwtDynGridLayout::heightForWidth(int width) const +{ + if ( isEmpty() ) + return 0; + + const uint numCols = columnsForWidth(width); + uint numRows = itemCount() / numCols; + if ( itemCount() % numCols ) + numRows++; + + QwtArray rowHeight(numRows); + QwtArray colWidth(numCols); + + layoutGrid(numCols, rowHeight, colWidth); + + int h = 2 * margin() + (numRows - 1) * spacing(); + for ( int row = 0; row < (int)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. + + \sa setExpanding(), expanding() +*/ +void QwtDynGridLayout::stretchGrid(const QRect &rect, + uint numCols, QwtArray& rowHeight, QwtArray& colWidth) const +{ + if ( numCols == 0 || isEmpty() ) + return; + + bool expandH, expandV; +#if QT_VERSION >= 0x040000 + expandH = expandingDirections() & Qt::Horizontal; + expandV = expandingDirections() & Qt::Vertical; +#else + expandH = expanding() & QSizePolicy::Horizontally; + expandV = expanding() & QSizePolicy::Vertically; +#endif + + if ( expandH ) + { + int xDelta = rect.width() - 2 * margin() - (numCols - 1) * spacing(); + for ( int col = 0; col < (int)numCols; col++ ) + xDelta -= colWidth[col]; + + if ( xDelta > 0 ) + { + for ( int col = 0; col < (int)numCols; col++ ) + { + const int space = xDelta / (numCols - col); + colWidth[col] += space; + xDelta -= space; + } + } + } + + if ( expandV ) + { + uint numRows = itemCount() / numCols; + if ( itemCount() % numCols ) + numRows++; + + int yDelta = rect.height() - 2 * margin() - (numRows - 1) * spacing(); + for ( int row = 0; row < (int)numRows; row++ ) + yDelta -= rowHeight[row]; + + if ( yDelta > 0 ) + { + for ( int row = 0; row < (int)numRows; row++ ) + { + const int space = yDelta / (numRows - row); + rowHeight[row] += space; + yDelta -= space; + } + } + } +} + +/*! + Return the size hint. If maxCols() > 0 it is the size for + a grid with maxCols() columns, otherwise it is the size for + a grid with only one row. + + \sa maxCols(), setMaxCols() +*/ +QSize QwtDynGridLayout::sizeHint() const +{ + if ( isEmpty() ) + return QSize(); + + const uint numCols = (d_data->maxCols > 0 ) ? d_data->maxCols : itemCount(); + uint numRows = itemCount() / numCols; + if ( itemCount() % numCols ) + numRows++; + + QwtArray rowHeight(numRows); + QwtArray colWidth(numCols); + + layoutGrid(numCols, rowHeight, colWidth); + + int h = 2 * margin() + (numRows - 1) * spacing(); + for ( int row = 0; row < (int)numRows; row++ ) + h += rowHeight[row]; + + int w = 2 * margin() + (numCols - 1) * spacing(); + for ( int col = 0; col < (int)numCols; col++ ) + w += colWidth[col]; + + return QSize(w, h); +} + +/*! + \return Number of rows of the current layout. + \sa numCols() + \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::numCols() const +{ + return d_data->numCols; +} diff --git a/qwt/src/qwt_dyngrid_layout.h b/qwt/src/qwt_dyngrid_layout.h new file mode 100644 index 000000000..0fa1cc22d --- /dev/null +++ b/qwt/src/qwt_dyngrid_layout.h @@ -0,0 +1,107 @@ +/* -*- 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 +#include +#if QT_VERSION >= 0x040000 +#include +#else +#include +#endif +#include "qwt_global.h" +#include "qwt_array.h" + +/*! + \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 maxCols()). +*/ + +class QWT_EXPORT QwtDynGridLayout : public QLayout +{ + Q_OBJECT +public: + explicit QwtDynGridLayout(QWidget *, int margin = 0, int space = -1); +#if QT_VERSION < 0x040000 + explicit QwtDynGridLayout(QLayout *, int space = -1); +#endif + explicit QwtDynGridLayout(int space = -1); + + virtual ~QwtDynGridLayout(); + + virtual void invalidate(); + + void setMaxCols(uint maxCols); + uint maxCols() const; + + uint numRows () const; + uint numCols () const; + + virtual void addItem(QLayoutItem *); + +#if QT_VERSION >= 0x040000 + 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; +#else + virtual QLayoutIterator iterator(); + + void setExpanding(QSizePolicy::ExpandData); + virtual QSizePolicy::ExpandData expanding() const; + QValueList layoutItems(const QRect &, uint numCols) const; +#endif + + 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, + QwtArray& rowHeight, QwtArray& colWidth) const; + void stretchGrid(const QRect &rect, uint numCols, + QwtArray& rowHeight, QwtArray& colWidth) const; + + +private: + void init(); + int maxRowWidth(int numCols) const; + void updateLayoutCache(); + +#if QT_VERSION < 0x040000 +// xlC 5.1, the IBM/AIX C++ compiler, needs it to be public +public: +#endif + class PrivateData; + +private: + 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..73a51d228 --- /dev/null +++ b/qwt/src/qwt_event_pattern.cpp @@ -0,0 +1,288 @@ +/* -*- 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 "qwt_event_pattern.h" + +/*! + 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) +{ +#if QT_VERSION < 0x040000 + const int altButton = Qt::AltButton; + const int controlButton = Qt::ControlButton; + const int shiftButton = Qt::ShiftButton; +#else + const int altButton = Qt::AltModifier; + const int controlButton = Qt::ControlModifier; + const int shiftButton = Qt::ShiftModifier; +#endif + + d_mousePattern.resize(MousePatternCount); + + switch(numButtons) + { + case 1: + { + setMousePattern(MouseSelect1, Qt::LeftButton); + setMousePattern(MouseSelect2, Qt::LeftButton, controlButton); + setMousePattern(MouseSelect3, Qt::LeftButton, altButton); + break; + } + case 2: + { + setMousePattern(MouseSelect1, Qt::LeftButton); + setMousePattern(MouseSelect2, Qt::RightButton); + setMousePattern(MouseSelect3, Qt::LeftButton, altButton); + break; + } + default: + { + setMousePattern(MouseSelect1, Qt::LeftButton); + setMousePattern(MouseSelect2, Qt::RightButton); + setMousePattern(MouseSelect3, Qt::MidButton); + } + } + for ( int i = 0; i < 3; i++ ) + { + setMousePattern(MouseSelect4 + i, + d_mousePattern[MouseSelect1 + i].button, + d_mousePattern[MouseSelect1 + i].state | shiftButton); + } +} + +/*! + 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 state State + + \sa QMouseEvent +*/ +void QwtEventPattern::setMousePattern(uint pattern, int button, int state) +{ + if ( pattern < (uint)d_mousePattern.count() ) + { + d_mousePattern[int(pattern)].button = button; + d_mousePattern[int(pattern)].state = state; + } +} + +/*! + Change one key pattern + + \param pattern Index of the pattern + \param key Key + \param state State + + \sa QKeyEvent +*/ +void QwtEventPattern::setKeyPattern(uint pattern, int key, int state) +{ + if ( pattern < (uint)d_keyPattern.count() ) + { + d_keyPattern[int(pattern)].key = key; + d_keyPattern[int(pattern)].state = state; + } +} + +//! Change the mouse event patterns +void QwtEventPattern::setMousePattern(const QwtArray &pattern) +{ + d_mousePattern = pattern; +} + +//! Change the key event patterns +void QwtEventPattern::setKeyPattern(const QwtArray &pattern) +{ + d_keyPattern = pattern; +} + +//! Return mouse patterns +const QwtArray & +QwtEventPattern::mousePattern() const +{ + return d_mousePattern; +} + +//! Return key patterns +const QwtArray & +QwtEventPattern::keyPattern() const +{ + return d_keyPattern; +} + +//! Return ,ouse patterns +QwtArray &QwtEventPattern::mousePattern() +{ + return d_mousePattern; +} + +//! Return Key patterns +QwtArray &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 pattern Index of the event pattern + \param e Mouse event + \return true if matches + + \sa keyMatch() +*/ +bool QwtEventPattern::mouseMatch(uint pattern, const QMouseEvent *e) const +{ + bool ok = false; + + if ( e && pattern < (uint)d_mousePattern.count() ) + ok = mouseMatch(d_mousePattern[int(pattern)], e); + + return ok; +} + +/*! + \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 e Mouse event + \return true if matches + + \sa keyMatch() +*/ + +bool QwtEventPattern::mouseMatch(const MousePattern &pattern, + const QMouseEvent *e) const +{ + if ( e->button() != pattern.button ) + return false; + + const bool matched = +#if QT_VERSION < 0x040000 + (e->state() & Qt::KeyButtonMask) == + (pattern.state & Qt::KeyButtonMask); +#else + (e->modifiers() & Qt::KeyboardModifierMask) == + (int)(pattern.state & Qt::KeyboardModifierMask); +#endif + + return matched; +} + +/*! + \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 Index of the event pattern + \param e Key event + \return true if matches + + \sa mouseMatch() +*/ +bool QwtEventPattern::keyMatch(uint pattern, const QKeyEvent *e) const +{ + bool ok = false; + + if ( e && pattern < (uint)d_keyPattern.count() ) + ok = keyMatch(d_keyPattern[int(pattern)], e); + + return ok; +} + +/*! + \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 e Key event + \return true if matches + + \sa mouseMatch() +*/ + +bool QwtEventPattern::keyMatch( + const KeyPattern &pattern, const QKeyEvent *e) const +{ + if ( e->key() != pattern.key) + return false; + + const bool matched = +#if QT_VERSION < 0x040000 + (e->state() & Qt::KeyButtonMask) == + (pattern.state & Qt::KeyButtonMask); +#else + (e->modifiers() & Qt::KeyboardModifierMask) == + (int)(pattern.state & Qt::KeyboardModifierMask); +#endif + + return matched; +} diff --git a/qwt/src/qwt_event_pattern.h b/qwt/src/qwt_event_pattern.h new file mode 100644 index 000000000..546f3da67 --- /dev/null +++ b/qwt/src/qwt_event_pattern.h @@ -0,0 +1,221 @@ +/* -*- 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 +#include "qwt_array.h" + +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 + + The default initialization for 3 button mice is: + - MouseSelect1\n + Qt::LeftButton + - MouseSelect2\n + Qt::RightButton + - MouseSelect3\n + Qt::MidButton + - MouseSelect4\n + Qt::LeftButton + Qt::ShiftButton + - MouseSelect5\n + Qt::RightButton + Qt::ShiftButton + - MouseSelect6\n + Qt::MidButton + Qt::ShiftButton + + The default initialization for 2 button mice is: + - MouseSelect1\n + Qt::LeftButton + - MouseSelect2\n + Qt::RightButton + - MouseSelect3\n + Qt::LeftButton + Qt::AltButton + - MouseSelect4\n + Qt::LeftButton + Qt::ShiftButton + - MouseSelect5\n + Qt::RightButton + Qt::ShiftButton + - MouseSelect6\n + Qt::LeftButton + Qt::AltButton + Qt::ShiftButton + + The default initialization for 1 button mice is: + - MouseSelect1\n + Qt::LeftButton + - MouseSelect2\n + Qt::LeftButton + Qt::ControlButton + - MouseSelect3\n + Qt::LeftButton + Qt::AltButton + - MouseSelect4\n + Qt::LeftButton + Qt::ShiftButton + - MouseSelect5\n + Qt::LeftButton + Qt::ControlButton + Qt::ShiftButton + - MouseSelect6\n + Qt::LeftButton + Qt::AltButton + Qt::ShiftButton + + \sa initMousePattern() + */ + + enum MousePatternCode + { + MouseSelect1, + MouseSelect2, + MouseSelect3, + MouseSelect4, + MouseSelect5, + MouseSelect6, + + MousePatternCount + }; + + /*! + \brief Symbolic keyboard input codes + + Default initialization: + - KeySelect1\n + Qt::Key_Return + - KeySelect2\n + Qt::Key_Space + - KeyAbort\n + Qt::Key_Escape + + - KeyLeft\n + Qt::Key_Left + - KeyRight\n + Qt::Key_Right + - KeyUp\n + Qt::Key_Up + - KeyDown\n + Qt::Key_Down + + - KeyUndo\n + Qt::Key_Minus + - KeyRedo\n + Qt::Key_Plus + - KeyHome\n + Qt::Key_Escape + */ + enum KeyPatternCode + { + KeySelect1, + KeySelect2, + KeyAbort, + + KeyLeft, + KeyRight, + KeyUp, + KeyDown, + + KeyRedo, + KeyUndo, + KeyHome, + + KeyPatternCount + }; + + //! A pattern for mouse events + class MousePattern + { + public: + MousePattern(int btn = Qt::NoButton, int st = Qt::NoButton) + { + button = btn; + state = st; + } + + int button; + int state; + }; + + //! A pattern for key events + class KeyPattern + { + public: + KeyPattern(int k = 0, int st = Qt::NoButton) + { + key = k; + state = st; + } + + int key; + int state; + }; + + QwtEventPattern(); + virtual ~QwtEventPattern(); + + void initMousePattern(int numButtons); + void initKeyPattern(); + + void setMousePattern(uint pattern, int button, int state = Qt::NoButton); + void setKeyPattern(uint pattern, int key, int state = Qt::NoButton); + + void setMousePattern(const QwtArray &); + void setKeyPattern(const QwtArray &); + + const QwtArray &mousePattern() const; + const QwtArray &keyPattern() const; + + QwtArray &mousePattern(); + QwtArray &keyPattern(); + + bool mouseMatch(uint pattern, const QMouseEvent *) const; + bool keyMatch(uint pattern, 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 + QwtArray d_mousePattern; + QwtArray d_keyPattern; +#if defined(_MSC_VER) +#pragma warning(pop) +#endif +}; + +inline bool operator==(QwtEventPattern::MousePattern b1, + QwtEventPattern::MousePattern b2) +{ + return b1.button == b2.button && b1.state == b2.state; +} + +inline bool operator==(QwtEventPattern::KeyPattern b1, + QwtEventPattern::KeyPattern b2) +{ + return b1.key == b2.key && b1.state == b2.state; +} + +#if defined(QWT_TEMPLATEDLL) +// MOC_SKIP_BEGIN +template class QWT_EXPORT QwtArray; +template class QWT_EXPORT QwtArray; +// MOC_SKIP_END +#endif + +#endif diff --git a/qwt/src/qwt_global.h b/qwt/src/qwt_global.h new file mode 100644 index 000000000..217513502 --- /dev/null +++ b/qwt/src/qwt_global.h @@ -0,0 +1,51 @@ +/* -*- 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 + +#ifndef QWT_GLOBAL_H +#define QWT_GLOBAL_H + +#include +#if QT_VERSION < 0x040000 +#include +#endif + +// QWT_VERSION is (major << 16) + (minor << 8) + patch. + +#define QWT_VERSION 0x050201 +#define QWT_VERSION_STR "5.2.1" + +#if defined(Q_WS_WIN) + +#if defined(_MSC_VER) /* MSVC Compiler */ +/* template-class specialization 'identifier' is already instantiated */ +#pragma warning(disable: 4660) +#endif // _MSC_VER + +#ifdef QWT_DLL + +#if defined(QWT_MAKEDLL) // create a Qwt DLL library +#define QWT_EXPORT __declspec(dllexport) +#define QWT_TEMPLATEDLL +#else // use a Qwt DLL library +#define QWT_EXPORT __declspec(dllimport) +#endif + +#endif // QWT_DLL + +#endif // Q_WS_WIN + +#ifndef QWT_EXPORT +#define QWT_EXPORT +#endif + +// #define QWT_NO_COMPAT 1 // disable withdrawn functionality + +#endif // QWT_GLOBAL_H diff --git a/qwt/src/qwt_interval_data.cpp b/qwt/src/qwt_interval_data.cpp new file mode 100644 index 000000000..5283f95a2 --- /dev/null +++ b/qwt/src/qwt_interval_data.cpp @@ -0,0 +1,90 @@ +/* -*- 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" +#include "qwt_interval_data.h" + +//! Constructor +QwtIntervalData::QwtIntervalData() +{ +} + +//! Constructor +QwtIntervalData::QwtIntervalData( + const QwtArray &intervals, + const QwtArray &values): + d_intervals(intervals), + d_values(values) +{ +} + +//! Destructor +QwtIntervalData::~QwtIntervalData() +{ +} + +//! Assign samples +void QwtIntervalData::setData( + const QwtArray &intervals, + const QwtArray &values) +{ + d_intervals = intervals; + d_values = values; +} + +/*! + Calculate the bounding rectangle of the samples + + The x coordinates of the rectangle are built from the intervals, + the y coordinates from the values. + + \return Bounding rectangle +*/ +QwtDoubleRect QwtIntervalData::boundingRect() const +{ + double minX, maxX, minY, maxY; + minX = maxX = minY = maxY = 0.0; + + bool isValid = false; + + const size_t sz = size(); + for ( size_t i = 0; i < sz; i++ ) + { + const QwtDoubleInterval intv = interval(i); + if ( !intv.isValid() ) + continue; + + const double v = value(i); + + if ( !isValid ) + { + minX = intv.minValue(); + maxX = intv.maxValue(); + minY = maxY = v; + + isValid = true; + } + else + { + if ( intv.minValue() < minX ) + minX = intv.minValue(); + if ( intv.maxValue() > maxX ) + maxX = intv.maxValue(); + + if ( v < minY ) + minY = v; + if ( v > maxY ) + maxY = v; + } + } + if ( !isValid ) + return QwtDoubleRect(1.0, 1.0, -2.0, -2.0); // invalid + + return QwtDoubleRect(minX, minY, maxX - minX, maxY - minY); +} diff --git a/qwt/src/qwt_interval_data.h b/qwt/src/qwt_interval_data.h new file mode 100644 index 000000000..5b042c01b --- /dev/null +++ b/qwt/src/qwt_interval_data.h @@ -0,0 +1,90 @@ +/* -*- 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_DATA_H +#define QWT_INTERVAL_DATA_H 1 + +#include "qwt_global.h" +#include "qwt_math.h" +#include "qwt_array.h" +#include "qwt_double_interval.h" +#include "qwt_double_rect.h" + +#if defined(_MSC_VER) && (_MSC_VER > 1310) +#include +#endif + +#if defined(QWT_TEMPLATEDLL) +// MOC_SKIP_BEGIN +template class QWT_EXPORT QwtArray; +template class QWT_EXPORT QwtArray; +// MOC_SKIP_END +#endif + +/*! + \brief Series of samples of a value and an interval + + QwtIntervalData is a series of samples of a value and an interval. + F.e. error bars are built from samples [x, y1-y2], while a + histogram might consist of [x1-x2, y] samples. +*/ +class QWT_EXPORT QwtIntervalData +{ +public: + QwtIntervalData(); + QwtIntervalData(const QwtArray &, + const QwtArray &); + + ~QwtIntervalData(); + + void setData(const QwtArray &, + const QwtArray &); + + size_t size() const; + const QwtDoubleInterval &interval(size_t i) const; + double value(size_t i) const; + + QwtDoubleRect boundingRect() const; + +private: + QwtArray d_intervals; + QwtArray d_values; +}; + +//! \return Number of samples +inline size_t QwtIntervalData::size() const +{ + return qwtMin(d_intervals.size(), d_values.size()); +} + +/*! + Interval of a sample + + \param i Sample index + \return Interval + \sa value(), size() +*/ +inline const QwtDoubleInterval &QwtIntervalData::interval(size_t i) const +{ + return d_intervals[int(i)]; +} + +/*! + Value of a sample + + \param i Sample index + \return Value + \sa interval(), size() +*/ +inline double QwtIntervalData::value(size_t i) const +{ + return d_values[int(i)]; +} + +#endif diff --git a/qwt/src/qwt_knob.cpp b/qwt/src/qwt_knob.cpp new file mode 100644 index 000000000..d489bd652 --- /dev/null +++ b/qwt/src/qwt_knob.cpp @@ -0,0 +1,548 @@ +/* -*- 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_round_scale_draw.h" +#include "qwt_knob.h" +#include "qwt_math.h" +#include "qwt_painter.h" +#include "qwt_paint_buffer.h" + +class QwtKnob::PrivateData +{ +public: + PrivateData() + { + angle = 0.0; + nTurns = 0.0; + borderWidth = 2; + borderDist = 4; + totalAngle = 270.0; + scaleDist = 4; + symbol = Line; + maxScaleTicks = 11; + knobWidth = 50; + dotWidth = 8; + } + + int borderWidth; + int borderDist; + int scaleDist; + int maxScaleTicks; + int knobWidth; + int dotWidth; + + Symbol symbol; + double angle; + double totalAngle; + double nTurns; + + QRect knobRect; // bounding rect of the knob without scale +}; + +/*! + Constructor + \param parent Parent widget +*/ +QwtKnob::QwtKnob(QWidget* parent): + QwtAbstractSlider(Qt::Horizontal, parent) +{ + initKnob(); +} + +#if QT_VERSION < 0x040000 +/*! + Constructor + \param parent Parent widget + \param name Object name +*/ +QwtKnob::QwtKnob(QWidget* parent, const char *name): + QwtAbstractSlider(Qt::Horizontal, parent) +{ + setName(name); + initKnob(); +} +#endif + +void QwtKnob::initKnob() +{ +#if QT_VERSION < 0x040000 + setWFlags(Qt::WNoAutoErase); +#endif + + d_data = new PrivateData; + + setScaleDraw(new QwtRoundScaleDraw()); + + setUpdateTime(50); + setTotalAngle( 270.0 ); + recalcAngle(); + setSizePolicy(QSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum)); + + setRange(0.0, 10.0, 1.0); + setValue(0.0); +} + +//! Destructor +QwtKnob::~QwtKnob() +{ + delete d_data; +} + +/*! + \brief Set the symbol of the knob + \sa symbol() +*/ +void QwtKnob::setSymbol(QwtKnob::Symbol s) +{ + if ( d_data->symbol != s ) + { + d_data->symbol = s; + update(); + } +} + +/*! + \return symbol of the knob + \sa setSymbol() +*/ +QwtKnob::Symbol QwtKnob::symbol() const +{ + return d_data->symbol; +} + +/*! + \brief Set the total angle by which the knob can be turned + \param angle Angle in degrees. + + The default angle is 270 degrees. It is possible to specify + an angle of more than 360 degrees so that the knob can be + turned several times around its axis. +*/ +void QwtKnob::setTotalAngle (double angle) +{ + if (angle < 10.0) + d_data->totalAngle = 10.0; + else + d_data->totalAngle = angle; + + scaleDraw()->setAngleRange( -0.5 * d_data->totalAngle, + 0.5 * d_data->totalAngle); + layoutKnob(); +} + +//! Return the total angle +double QwtKnob::totalAngle() const +{ + return d_data->totalAngle; +} + +/*! + 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 (QwtRoundScaleDraw *)abstractScaleDraw(); +} + +/*! + \return the scale draw of the knob + \sa setScaleDraw() +*/ +QwtRoundScaleDraw *QwtKnob::scaleDraw() +{ + return (QwtRoundScaleDraw *)abstractScaleDraw(); +} + +/*! + \brief Draw the knob + \param painter painter + \param r Bounding rectangle of the knob (without scale) +*/ +void QwtKnob::drawKnob(QPainter *painter, const QRect &r) +{ +#if QT_VERSION < 0x040000 + const QBrush buttonBrush = colorGroup().brush(QColorGroup::Button); + const QColor buttonTextColor = colorGroup().buttonText(); + const QColor lightColor = colorGroup().light(); + const QColor darkColor = colorGroup().dark(); +#else + const QBrush buttonBrush = palette().brush(QPalette::Button); + const QColor buttonTextColor = palette().color(QPalette::ButtonText); + const QColor lightColor = palette().color(QPalette::Light); + const QColor darkColor = palette().color(QPalette::Dark); +#endif + + const int bw2 = d_data->borderWidth / 2; + + const int radius = (qwtMin(r.width(), r.height()) - bw2) / 2; + + const QRect aRect( + r.center().x() - radius, r.center().y() - radius, + 2 * radius, 2 * radius); + + // + // draw button face + // + painter->setBrush(buttonBrush); + painter->drawEllipse(aRect); + + // + // draw button shades + // + QPen pn; + pn.setWidth(d_data->borderWidth); + + pn.setColor(lightColor); + painter->setPen(pn); + painter->drawArc(aRect, 45*16, 180*16); + + pn.setColor(darkColor); + painter->setPen(pn); + painter->drawArc(aRect, 225*16, 180*16); + + // + // draw marker + // + if ( isValid() ) + drawMarker(painter, d_data->angle, buttonTextColor); +} + +/*! + \brief Notify change of value + + Sets the knob's value to the nearest multiple + of the step size. +*/ +void QwtKnob::valueChange() +{ + recalcAngle(); + update(); + QwtAbstractSlider::valueChange(); +} + +/*! + \brief Determine the value corresponding to a specified position + + Called by QwtAbstractSlider + \param p point +*/ +double QwtKnob::getValue(const QPoint &p) +{ + const double dx = double((rect().x() + rect().width() / 2) - p.x() ); + const double dy = double((rect().y() + rect().height() / 2) - p.y() ); + + const double arc = atan2(-dx,dy) * 180.0 / M_PI; + + double newValue = 0.5 * (minValue() + maxValue()) + + (arc + d_data->nTurns * 360.0) * (maxValue() - minValue()) + / d_data->totalAngle; + + const double oneTurn = fabs(maxValue() - minValue()) * 360.0 / d_data->totalAngle; + const double eqValue = value() + mouseOffset(); + + if (fabs(newValue - eqValue) > 0.5 * oneTurn) + { + if (newValue < eqValue) + newValue += oneTurn; + else + newValue -= oneTurn; + } + + return newValue; +} + +/*! + \brief Set the scrolling mode and direction + + Called by QwtAbstractSlider + \param p Point in question +*/ +void QwtKnob::getScrollMode(const QPoint &p, int &scrollMode, int &direction) +{ + const int r = d_data->knobRect.width() / 2; + + const int dx = d_data->knobRect.x() + r - p.x(); + const int dy = d_data->knobRect.y() + r - p.y(); + + if ( (dx * dx) + (dy * dy) <= (r * r)) // point is inside the knob + { + scrollMode = ScrMouse; + direction = 0; + } + else // point lies outside + { + scrollMode = ScrTimer; + double arc = atan2(double(-dx),double(dy)) * 180.0 / M_PI; + if ( arc < d_data->angle) + direction = -1; + else if (arc > d_data->angle) + direction = 1; + else + direction = 0; + } +} + + +/*! + \brief Notify a change of the range + + Called by QwtAbstractSlider +*/ +void QwtKnob::rangeChange() +{ + if (autoScale()) + rescale(minValue(), maxValue()); + + layoutKnob(); + recalcAngle(); +} + +/*! + Qt Resize Event +*/ +void QwtKnob::resizeEvent(QResizeEvent *) +{ + layoutKnob( false ); +} + +/*! + Recalculate the knob's geometry and layout based on + the current rect and fonts. + + \param update_geometry notify the layout system and call update + to redraw the scale +*/ +void QwtKnob::layoutKnob( bool update_geometry ) +{ + const QRect r = rect(); + const int radius = d_data->knobWidth / 2; + + d_data->knobRect.setWidth(2 * radius); + d_data->knobRect.setHeight(2 * radius); + d_data->knobRect.moveCenter(r.center()); + + scaleDraw()->setRadius(radius + d_data->scaleDist); + scaleDraw()->moveCenter(r.center()); + + if ( update_geometry ) + { + updateGeometry(); + update(); + } +} + +/*! + Repaint the knob + + \param e Paint event +*/ +void QwtKnob::paintEvent(QPaintEvent *e) +{ + const QRect &ur = e->rect(); + if ( ur.isValid() ) + { +#if QT_VERSION < 0x040000 + QwtPaintBuffer paintBuffer(this, ur); + draw(paintBuffer.painter(), ur); +#else + QPainter painter(this); + painter.setRenderHint(QPainter::Antialiasing); + draw(&painter, ur); +#endif + } +} + +/*! + Repaint the knob + + \param painter Painter + \param rect Update rectangle +*/ +void QwtKnob::draw(QPainter *painter, const QRect& rect) +{ + if ( !d_data->knobRect.contains( rect ) ) // event from valueChange() + { +#if QT_VERSION < 0x040000 + scaleDraw()->draw( painter, colorGroup() ); +#else + scaleDraw()->draw( painter, palette() ); +#endif + } + + drawKnob( painter, d_data->knobRect ); + + if ( hasFocus() ) + QwtPainter::drawFocusRect(painter, this); +} + +/*! + \brief Draw the marker at the knob's front + \param p Painter + \param arc Angle of the marker + \param c Marker color +*/ +void QwtKnob::drawMarker(QPainter *p, double arc, const QColor &c) +{ + const double rarc = arc * M_PI / 180.0; + const double ca = cos(rarc); + const double sa = - sin(rarc); + + int radius = d_data->knobRect.width() / 2 - d_data->borderWidth; + if (radius < 3) + radius = 3; + + const int ym = d_data->knobRect.y() + radius + d_data->borderWidth; + const int xm = d_data->knobRect.x() + radius + d_data->borderWidth; + + switch (d_data->symbol) + { + case Dot: + { + p->setBrush(c); + p->setPen(Qt::NoPen); + + const double rb = double(qwtMax(radius - 4 - d_data->dotWidth / 2, 0)); + p->drawEllipse(xm - qRound(sa * rb) - d_data->dotWidth / 2, + ym - qRound(ca * rb) - d_data->dotWidth / 2, + d_data->dotWidth, d_data->dotWidth); + break; + } + case Line: + { + p->setPen(QPen(c, 2)); + + const double rb = qwtMax(double((radius - 4) / 3.0), 0.0); + const double re = qwtMax(double(radius - 4), 0.0); + + p->drawLine ( xm - qRound(sa * rb), ym - qRound(ca * rb), + xm - qRound(sa * re), ym - qRound(ca * re)); + + break; + } + } +} + +/*! + \brief Change the knob's width. + + The specified width must be >= 5, or it will be clipped. + \param w New width +*/ +void QwtKnob::setKnobWidth(int w) +{ + d_data->knobWidth = qwtMax(w,5); + layoutKnob(); +} + +//! Return the width of the knob +int QwtKnob::knobWidth() const +{ + return d_data->knobWidth; +} + +/*! + \brief Set the knob's border width + \param bw new border width +*/ +void QwtKnob::setBorderWidth(int bw) +{ + d_data->borderWidth = qwtMax(bw, 0); + layoutKnob(); +} + +//! Return the border width +int QwtKnob::borderWidth() const +{ + return d_data->borderWidth; +} + +/*! + \brief Recalculate the marker angle corresponding to the + current value +*/ +void QwtKnob::recalcAngle() +{ + // + // calculate the angle corresponding to the value + // + if (maxValue() == minValue()) + { + d_data->angle = 0; + d_data->nTurns = 0; + } + else + { + d_data->angle = (value() - 0.5 * (minValue() + maxValue())) + / (maxValue() - minValue()) * d_data->totalAngle; + d_data->nTurns = floor((d_data->angle + 180.0) / 360.0); + d_data->angle = d_data->angle - d_data->nTurns * 360.0; + } +} + + +/*! + Recalculates the layout + \sa layoutKnob() +*/ +void QwtKnob::scaleChange() +{ + layoutKnob(); +} + +/*! + Recalculates the layout + \sa layoutKnob() +*/ +void QwtKnob::fontChange(const QFont &f) +{ + QwtAbstractSlider::fontChange( f ); + layoutKnob(); +} + +/*! + \return minimumSizeHint() +*/ +QSize QwtKnob::sizeHint() const +{ + return minimumSizeHint(); +} + +/*! + \brief Return a minimum size hint + \warning The return value of QwtKnob::minimumSizeHint() depends on the + font and the scale. +*/ +QSize QwtKnob::minimumSizeHint() const +{ + // Add the scale radial thickness to the knobWidth + const int sh = scaleDraw()->extent( QPen(), font() ); + const int d = 2 * sh + 2 * d_data->scaleDist + d_data->knobWidth; + + return QSize( d, d ); +} diff --git a/qwt/src/qwt_knob.h b/qwt/src/qwt_knob.h new file mode 100644 index 000000000..41ec1bdd9 --- /dev/null +++ b/qwt/src/qwt_knob.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_KNOB_H +#define QWT_KNOB_H + +#include "qwt_global.h" +#include "qwt_abstract_slider.h" +#include "qwt_abstract_scale.h" + +class QwtRoundScaleDraw; + +/*! + \brief The Knob Widget + + The QwtKnob widget imitates look and behaviour of a volume knob on a radio. + It contains a scale around the knob which is set up automatically or can + be configured manually (see QwtAbstractScale). + Automatic scrolling is enabled when the user presses a mouse + button on the scale. For a description of signals, slots and other + members, see QwtAbstractSlider. + + \image html knob.png + \sa QwtAbstractSlider and QwtAbstractScale for the descriptions + of the inherited members. +*/ + +class QWT_EXPORT QwtKnob : public QwtAbstractSlider, public QwtAbstractScale +{ + Q_OBJECT + Q_ENUMS (Symbol) + Q_PROPERTY( int knobWidth READ knobWidth WRITE setKnobWidth ) + Q_PROPERTY( int borderWidth READ borderWidth WRITE setBorderWidth ) + Q_PROPERTY( double totalAngle READ totalAngle WRITE setTotalAngle ) + Q_PROPERTY( Symbol symbol READ symbol WRITE setSymbol ) + +public: + /*! + Symbol + \sa QwtKnob::QwtKnob() + */ + + enum Symbol { Line, Dot }; + + explicit QwtKnob(QWidget* parent = NULL); +#if QT_VERSION < 0x040000 + explicit QwtKnob(QWidget* parent, const char *name); +#endif + virtual ~QwtKnob(); + + void setKnobWidth(int w); + int knobWidth() const; + + void setTotalAngle (double angle); + double totalAngle() const; + + void setBorderWidth(int bw); + int borderWidth() const; + + void setSymbol(Symbol); + Symbol symbol() const; + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + void setScaleDraw(QwtRoundScaleDraw *); + const QwtRoundScaleDraw *scaleDraw() const; + QwtRoundScaleDraw *scaleDraw(); + +protected: + virtual void paintEvent(QPaintEvent *e); + virtual void resizeEvent(QResizeEvent *e); + + void draw(QPainter *p, const QRect& ur); + void drawKnob(QPainter *p, const QRect &r); + void drawMarker(QPainter *p, double arc, const QColor &c); + +private: + void initKnob(); + void layoutKnob( bool update = true ); + double getValue(const QPoint &p); + void getScrollMode( const QPoint &p, int &scrollMode, int &direction ); + void recalcAngle(); + + virtual void valueChange(); + virtual void rangeChange(); + virtual void scaleChange(); + virtual void fontChange(const QFont &oldFont); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_layout_metrics.cpp b/qwt/src/qwt_layout_metrics.cpp new file mode 100644 index 000000000..2a87652e5 --- /dev/null +++ b/qwt/src/qwt_layout_metrics.cpp @@ -0,0 +1,339 @@ +/* -*- 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 +#if QT_VERSION < 0x040000 +#include +#include +#define QwtMatrix QWMatrix +#else +#include +#define QwtMatrix QMatrix +#endif +#include +#include +#include "qwt_math.h" +#include "qwt_polygon.h" +#include "qwt_layout_metrics.h" + +static QSize deviceDpi(const QPaintDevice *device) +{ + QSize dpi; +#if QT_VERSION < 0x040000 + const QPaintDeviceMetrics metrics(device); + dpi.setWidth(metrics.logicalDpiX()); + dpi.setHeight(metrics.logicalDpiY()); +#else + dpi.setWidth(device->logicalDpiX()); + dpi.setHeight(device->logicalDpiY()); +#endif + + return dpi; +} + +#if QT_VERSION < 0x040000 + +inline static const QWMatrix &matrix(const QPainter *painter) +{ + return painter->worldMatrix(); +} +inline static QWMatrix invMatrix(const QPainter *painter) +{ + return painter->worldMatrix().invert(); +} + +#else // QT_VERSION >= 0x040000 + +inline static const QMatrix &matrix(const QPainter *painter) +{ + return painter->matrix(); +} +inline static QMatrix invMatrix(const QPainter *painter) +{ + return painter->matrix().inverted(); +} + +#endif + +QwtMetricsMap::QwtMetricsMap() +{ + d_screenToLayoutX = d_screenToLayoutY = + d_deviceToLayoutX = d_deviceToLayoutY = 1.0; +} + +void QwtMetricsMap::setMetrics(const QPaintDevice *layoutDevice, + const QPaintDevice *paintDevice) +{ + const QSize screenDpi = deviceDpi(QApplication::desktop()); + const QSize layoutDpi = deviceDpi(layoutDevice); + const QSize paintDpi = deviceDpi(paintDevice); + + d_screenToLayoutX = double(layoutDpi.width()) / + double(screenDpi.width()); + d_screenToLayoutY = double(layoutDpi.height()) / + double(screenDpi.height()); + + d_deviceToLayoutX = double(layoutDpi.width()) / + double(paintDpi.width()); + d_deviceToLayoutY = double(layoutDpi.height()) / + double(paintDpi.height()); +} + +#ifndef QT_NO_TRANSFORMATIONS +QPoint QwtMetricsMap::layoutToDevice(const QPoint &point, + const QPainter *painter) const +#else +QPoint QwtMetricsMap::layoutToDevice(const QPoint &point, + const QPainter *) const +#endif +{ + if ( isIdentity() ) + return point; + + QPoint mappedPoint(point); + +#ifndef QT_NO_TRANSFORMATIONS + if ( painter ) + mappedPoint = matrix(painter).map(mappedPoint); +#endif + + mappedPoint.setX(layoutToDeviceX(mappedPoint.x())); + mappedPoint.setY(layoutToDeviceY(mappedPoint.y())); + +#ifndef QT_NO_TRANSFORMATIONS + if ( painter ) + mappedPoint = invMatrix(painter).map(mappedPoint); +#endif + + return mappedPoint; +} + +#ifndef QT_NO_TRANSFORMATIONS +QPoint QwtMetricsMap::deviceToLayout(const QPoint &point, + const QPainter *painter) const +#else +QPoint QwtMetricsMap::deviceToLayout(const QPoint &point, + const QPainter *) const +#endif +{ + if ( isIdentity() ) + return point; + + QPoint mappedPoint(point); + +#ifndef QT_NO_TRANSFORMATIONS + if ( painter ) + mappedPoint = matrix(painter).map(mappedPoint); +#endif + + mappedPoint.setX(deviceToLayoutX(mappedPoint.x())); + mappedPoint.setY(deviceToLayoutY(mappedPoint.y())); + +#ifndef QT_NO_TRANSFORMATIONS + if ( painter ) + mappedPoint = invMatrix(painter).map(mappedPoint); +#endif + + return mappedPoint; +} + +QPoint QwtMetricsMap::screenToLayout(const QPoint &point) const +{ + if ( d_screenToLayoutX == 1.0 && d_screenToLayoutY == 1.0 ) + return point; + + return QPoint(screenToLayoutX(point.x()), screenToLayoutY(point.y())); +} + +QPoint QwtMetricsMap::layoutToScreen(const QPoint &point) const +{ + if ( d_screenToLayoutX == 1.0 && d_screenToLayoutY == 1.0 ) + return point; + + return QPoint(layoutToScreenX(point.x()), layoutToScreenY(point.y())); +} + +#ifndef QT_NO_TRANSFORMATIONS +QRect QwtMetricsMap::layoutToDevice(const QRect &rect, + const QPainter *painter) const +#else +QRect QwtMetricsMap::layoutToDevice(const QRect &rect, + const QPainter *) const +#endif +{ + if ( isIdentity() ) + return rect; + + int dx = 0; + int dy = 0; + + QRect mappedRect(rect); +#ifndef QT_NO_TRANSFORMATIONS + if ( painter ) + { + // only translations, but this code doesn't need to be perfect + // as it used for printing of stuff only, that is not on the canvas. + // Here we know we have translations only. + // As soon as Qt3 support is dropped, Qwt will use a floating point + // based layout and this class can die completely. + + dx = qRound(matrix(painter).dx()); + dy = qRound(matrix(painter).dy()); + + mappedRect = QRect(mappedRect.x() + dx, mappedRect.y() + dy, + mappedRect.width(), mappedRect.height() ); + } +#endif + + mappedRect = QRect( + layoutToDevice(mappedRect.topLeft()), + layoutToDevice(mappedRect.bottomRight()) + ); + + mappedRect = QRect(mappedRect.x() - dx, mappedRect.y() - dy, + mappedRect.width(), mappedRect.height() ); + + return mappedRect; +} + +#ifndef QT_NO_TRANSFORMATIONS +QRect QwtMetricsMap::deviceToLayout(const QRect &rect, + const QPainter *painter) const +#else +QRect QwtMetricsMap::deviceToLayout(const QRect &rect, + const QPainter *) const +#endif +{ + if ( isIdentity() ) + return rect; + + QRect mappedRect(rect); +#ifndef QT_NO_TRANSFORMATIONS + if ( painter ) + mappedRect = translate(matrix(painter), mappedRect); +#endif + + mappedRect = QRect( + deviceToLayout(mappedRect.topLeft()), + deviceToLayout(mappedRect.bottomRight()) + ); + +#ifndef QT_NO_TRANSFORMATIONS + if ( painter ) + mappedRect = translate(invMatrix(painter), mappedRect); +#endif + + return mappedRect; +} + +QRect QwtMetricsMap::screenToLayout(const QRect &rect) const +{ + if ( d_screenToLayoutX == 1.0 && d_screenToLayoutY == 1.0 ) + return rect; + + return QRect(screenToLayoutX(rect.x()), screenToLayoutY(rect.y()), + screenToLayoutX(rect.width()), screenToLayoutY(rect.height())); +} + +QRect QwtMetricsMap::layoutToScreen(const QRect &rect) const +{ + if ( d_screenToLayoutX == 1.0 && d_screenToLayoutY == 1.0 ) + return rect; + + return QRect(layoutToScreenX(rect.x()), layoutToScreenY(rect.y()), + layoutToScreenX(rect.width()), layoutToScreenY(rect.height())); +} + +#ifndef QT_NO_TRANSFORMATIONS +QwtPolygon QwtMetricsMap::layoutToDevice(const QwtPolygon &pa, + const QPainter *painter) const +#else +QwtPolygon QwtMetricsMap::layoutToDevice(const QwtPolygon &pa, + const QPainter *) const +#endif +{ + if ( isIdentity() ) + return pa; + + QwtPolygon mappedPa(pa); + +#ifndef QT_NO_TRANSFORMATIONS + if ( painter ) + mappedPa = translate(matrix(painter), mappedPa); +#endif + + QwtMatrix m; + m.scale(1.0 / d_deviceToLayoutX, 1.0 / d_deviceToLayoutY); + mappedPa = translate(m, mappedPa); + +#ifndef QT_NO_TRANSFORMATIONS + if ( painter ) + mappedPa = translate(invMatrix(painter), mappedPa); +#endif + + return mappedPa; +} + +#ifndef QT_NO_TRANSFORMATIONS +QwtPolygon QwtMetricsMap::deviceToLayout(const QwtPolygon &pa, + const QPainter *painter) const +#else +QwtPolygon QwtMetricsMap::deviceToLayout(const QwtPolygon &pa, + const QPainter *) const +#endif +{ + if ( isIdentity() ) + return pa; + + QwtPolygon mappedPa(pa); + +#ifndef QT_NO_TRANSFORMATIONS + if ( painter ) + mappedPa = translate(matrix(painter), mappedPa); +#endif + + QwtMatrix m; + m.scale(d_deviceToLayoutX, d_deviceToLayoutY); + mappedPa = translate(m, mappedPa); + +#ifndef QT_NO_TRANSFORMATIONS + if ( painter ) + mappedPa = translate(invMatrix(painter), mappedPa); +#endif + + return mappedPa; +} + +/*! + Wrapper for QMatrix::mapRect. + + \param m Matrix + \param rect Rectangle to translate + \return Translated rectangle +*/ + +QRect QwtMetricsMap::translate( + const QwtMatrix &m, const QRect &rect) +{ + return m.mapRect(rect); +} + +/*! + Wrapper for QMatrix::map. + + \param m Matrix + \param pa Polygon to translate + \return Translated polygon +*/ +QwtPolygon QwtMetricsMap::translate( + const QwtMatrix &m, const QwtPolygon &pa) +{ + return m.map(pa); +} diff --git a/qwt/src/qwt_layout_metrics.h b/qwt/src/qwt_layout_metrics.h new file mode 100644 index 000000000..b1cc29d5f --- /dev/null +++ b/qwt/src/qwt_layout_metrics.h @@ -0,0 +1,169 @@ +/* -*- 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_LAYOUT_METRICS_H +#define QWT_LAYOUT_METRICS_H + +#include +#include +#include "qwt_polygon.h" +#include "qwt_global.h" + +class QPainter; +class QString; +class QFontMetrics; +#if QT_VERSION < 0x040000 +class QWMatrix; +#else +class QMatrix; +#endif +class QPaintDevice; + +/*! + \brief A Map to translate between layout, screen and paint device metrics + + Qt3 supports painting in integer coordinates only. Therefore it is not + possible to scale the layout in screen coordinates to layouts in higher + resolutions ( f.e printing ) without losing the higher precision. + QwtMetricsMap is used to incorporate the various widget attributes + ( always in screen resolution ) into the layout/printing code of QwtPlot. + + Qt4 is able to paint floating point based coordinates, what makes it + possible always to render in screen coordinates + ( with a common scale factor ). + QwtMetricsMap will be obsolete as soon as Qt3 support has been + dropped ( Qwt 6.x ). +*/ +class QWT_EXPORT QwtMetricsMap +{ +public: + QwtMetricsMap(); + + bool isIdentity() const; + + void setMetrics(const QPaintDevice *layoutMetrics, + const QPaintDevice *deviceMetrics); + + int layoutToDeviceX(int x) const; + int deviceToLayoutX(int x) const; + int screenToLayoutX(int x) const; + int layoutToScreenX(int x) const; + + int layoutToDeviceY(int y) const; + int deviceToLayoutY(int y) const; + int screenToLayoutY(int y) const; + int layoutToScreenY(int y) const; + + QPoint layoutToDevice(const QPoint &, const QPainter * = NULL) const; + QPoint deviceToLayout(const QPoint &, const QPainter * = NULL) const; + QPoint screenToLayout(const QPoint &) const; + QPoint layoutToScreen(const QPoint &point) const; + + + QSize layoutToDevice(const QSize &) const; + QSize deviceToLayout(const QSize &) const; + QSize screenToLayout(const QSize &) const; + QSize layoutToScreen(const QSize &) const; + + QRect layoutToDevice(const QRect &, const QPainter * = NULL) const; + QRect deviceToLayout(const QRect &, const QPainter * = NULL) const; + QRect screenToLayout(const QRect &) const; + QRect layoutToScreen(const QRect &) const; + + QwtPolygon layoutToDevice(const QwtPolygon &, + const QPainter * = NULL) const; + QwtPolygon deviceToLayout(const QwtPolygon &, + const QPainter * = NULL) const; + +#if QT_VERSION < 0x040000 + static QwtPolygon translate(const QWMatrix &, const QwtPolygon &); + static QRect translate(const QWMatrix &, const QRect &); +#else + static QwtPolygon translate(const QMatrix &, const QwtPolygon &); + static QRect translate(const QMatrix &, const QRect &); +#endif + +private: + double d_screenToLayoutX; + double d_screenToLayoutY; + + double d_deviceToLayoutX; + double d_deviceToLayoutY; +}; + +inline bool QwtMetricsMap::isIdentity() const +{ + return d_deviceToLayoutX == 1.0 && d_deviceToLayoutY == 1.0; +} + +inline int QwtMetricsMap::layoutToDeviceX(int x) const +{ + return qRound(x / d_deviceToLayoutX); +} + +inline int QwtMetricsMap::deviceToLayoutX(int x) const +{ + return qRound(x * d_deviceToLayoutX); +} + +inline int QwtMetricsMap::screenToLayoutX(int x) const +{ + return qRound(x * d_screenToLayoutX); +} + +inline int QwtMetricsMap::layoutToScreenX(int x) const +{ + return qRound(x / d_screenToLayoutX); +} + +inline int QwtMetricsMap::layoutToDeviceY(int y) const +{ + return qRound(y / d_deviceToLayoutY); +} + +inline int QwtMetricsMap::deviceToLayoutY(int y) const +{ + return qRound(y * d_deviceToLayoutY); +} + +inline int QwtMetricsMap::screenToLayoutY(int y) const +{ + return qRound(y * d_screenToLayoutY); +} + +inline int QwtMetricsMap::layoutToScreenY(int y) const +{ + return qRound(y / d_screenToLayoutY); +} + +inline QSize QwtMetricsMap::layoutToDevice(const QSize &size) const +{ + return QSize(layoutToDeviceX(size.width()), + layoutToDeviceY(size.height())); +} + +inline QSize QwtMetricsMap::deviceToLayout(const QSize &size) const +{ + return QSize(deviceToLayoutX(size.width()), + deviceToLayoutY(size.height())); +} + +inline QSize QwtMetricsMap::screenToLayout(const QSize &size) const +{ + return QSize(screenToLayoutX(size.width()), + screenToLayoutY(size.height())); +} + +inline QSize QwtMetricsMap::layoutToScreen(const QSize &size) const +{ + return QSize(layoutToScreenX(size.width()), + layoutToScreenY(size.height())); +} + +#endif diff --git a/qwt/src/qwt_legend.cpp b/qwt/src/qwt_legend.cpp new file mode 100644 index 000000000..b910f6126 --- /dev/null +++ b/qwt/src/qwt_legend.cpp @@ -0,0 +1,668 @@ +/* -*- 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 +#if QT_VERSION >= 0x040000 +#include +#endif +#include "qwt_math.h" +#include "qwt_dyngrid_layout.h" +#include "qwt_legend_itemmanager.h" +#include "qwt_legend_item.h" +#include "qwt_legend.h" + +class QwtLegend::PrivateData +{ +public: + class LegendMap + { + public: + void insert(const QwtLegendItemManager *, QWidget *); + + void remove(const QwtLegendItemManager *); + void remove(QWidget *); + + void clear(); + + uint count() const; + + inline const QWidget *find(const QwtLegendItemManager *) const; + inline QWidget *find(const QwtLegendItemManager *); + + inline const QwtLegendItemManager *find(const QWidget *) const; + inline QwtLegendItemManager *find(const QWidget *); + + const QMap &widgetMap() const; + QMap &widgetMap(); + + private: + QMap d_widgetMap; + QMap d_itemMap; + }; + + QwtLegend::LegendItemMode itemMode; + QwtLegend::LegendDisplayPolicy displayPolicy; + int identifierMode; + + LegendMap map; + + class LegendView; + LegendView *view; +}; + +#if QT_VERSION < 0x040000 +#include + +class QwtLegend::PrivateData::LegendView: public QScrollView +{ +public: + LegendView(QWidget *parent): + QScrollView(parent) + { + setResizePolicy(Manual); + + viewport()->setBackgroundMode(Qt::NoBackground); // Avoid flicker + + contentsWidget = new QWidget(viewport()); + + addChild(contentsWidget); + } + + void viewportResizeEvent(QResizeEvent *e) + { + QScrollView::viewportResizeEvent(e); + + // It's not safe to update the layout now, because + // we are in an internal update of the scrollview framework. + // So we delay the update by posting a LayoutHint. + + QApplication::postEvent(contentsWidget, + new QEvent(QEvent::LayoutHint)); + } + + QWidget *contentsWidget; +}; + +#else // QT_VERSION >= 0x040000 + +#include + +class QwtLegend::PrivateData::LegendView: public QScrollArea +{ +public: + LegendView(QWidget *parent): + QScrollArea(parent) + { + contentsWidget = new QWidget(this); + + setWidget(contentsWidget); + setWidgetResizable(false); + setFocusPolicy(Qt::NoFocus); + } + + virtual bool viewportEvent(QEvent *e) + { + bool ok = QScrollArea::viewportEvent(e); + + if ( e->type() == QEvent::Resize ) + { + QEvent event(QEvent::LayoutRequest); + QApplication::sendEvent(contentsWidget, &event); + } + 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); + } + + QWidget *contentsWidget; +}; + +#endif + + +void QwtLegend::PrivateData::LegendMap::insert( + const QwtLegendItemManager *item, QWidget *widget) +{ + d_itemMap.insert(item, widget); + d_widgetMap.insert(widget, item); +} + +void QwtLegend::PrivateData::LegendMap::remove(const QwtLegendItemManager *item) +{ + QWidget *widget = d_itemMap[item]; + d_itemMap.remove(item); + d_widgetMap.remove(widget); +} + +void QwtLegend::PrivateData::LegendMap::remove(QWidget *widget) +{ + const QwtLegendItemManager *item = d_widgetMap[widget]; + d_itemMap.remove(item); + d_widgetMap.remove(widget); +} + +void QwtLegend::PrivateData::LegendMap::clear() +{ + + /* + We can't delete the widgets in the following loop, because + we would get ChildRemoved events, changing d_itemMap, while + we are iterating. + */ + +#if QT_VERSION < 0x040000 + QValueList widgets; + + QMap::const_iterator it; + for ( it = d_itemMap.begin(); it != d_itemMap.end(); ++it ) + widgets.append(it.data()); +#else + QList widgets; + + QMap::const_iterator it; + for ( it = d_itemMap.begin(); it != d_itemMap.end(); ++it ) + widgets.append(it.value()); +#endif + + d_itemMap.clear(); + d_widgetMap.clear(); + + for ( int i = 0; i < (int)widgets.size(); i++ ) + delete widgets[i]; +} + +uint QwtLegend::PrivateData::LegendMap::count() const +{ + return d_itemMap.count(); +} + +inline const QWidget *QwtLegend::PrivateData::LegendMap::find(const QwtLegendItemManager *item) const +{ + if ( !d_itemMap.contains((QwtLegendItemManager *)item) ) + return NULL; + + return d_itemMap[(QwtLegendItemManager *)item]; +} + +inline QWidget *QwtLegend::PrivateData::LegendMap::find(const QwtLegendItemManager *item) +{ + if ( !d_itemMap.contains((QwtLegendItemManager *)item) ) + return NULL; + + return d_itemMap[(QwtLegendItemManager *)item]; +} + +inline const QwtLegendItemManager *QwtLegend::PrivateData::LegendMap::find( + const QWidget *widget) const +{ + if ( !d_widgetMap.contains((QWidget *)widget) ) + return NULL; + + return d_widgetMap[(QWidget *)widget]; +} + +inline QwtLegendItemManager *QwtLegend::PrivateData::LegendMap::find( + const QWidget *widget) +{ + if ( !d_widgetMap.contains((QWidget *)widget) ) + return NULL; + + return (QwtLegendItemManager *)d_widgetMap[(QWidget *)widget]; +} + +inline const QMap & + QwtLegend::PrivateData::LegendMap::widgetMap() const +{ + return d_widgetMap; +} + +inline QMap & + QwtLegend::PrivateData::LegendMap::widgetMap() +{ + return d_widgetMap; +} + +/*! + Constructor + + \param parent Parent widget +*/ +QwtLegend::QwtLegend(QWidget *parent): + QFrame(parent) +{ + setFrameStyle(NoFrame); + + d_data = new QwtLegend::PrivateData; + d_data->itemMode = QwtLegend::ReadOnlyItem; + d_data->displayPolicy = QwtLegend::AutoIdentifier; + d_data->identifierMode = QwtLegendItem::ShowLine | + QwtLegendItem::ShowSymbol | QwtLegendItem::ShowText; + + d_data->view = new QwtLegend::PrivateData::LegendView(this); + d_data->view->setFrameStyle(NoFrame); + + QwtDynGridLayout *layout = new QwtDynGridLayout( + d_data->view->contentsWidget); +#if QT_VERSION < 0x040000 + layout->setAutoAdd(true); +#endif + layout->setAlignment(Qt::AlignHCenter | Qt::AlignTop); + + d_data->view->contentsWidget->installEventFilter(this); +} + +//! Destructor +QwtLegend::~QwtLegend() +{ + delete d_data; +} + +/*! + Set the legend display policy to: + + \param policy Legend display policy + \param mode Identifier mode (or'd ShowLine, ShowSymbol, ShowText) + + \sa displayPolicy(), LegendDisplayPolicy +*/ +void QwtLegend::setDisplayPolicy(LegendDisplayPolicy policy, int mode) +{ + d_data->displayPolicy = policy; + if (-1 != mode) + d_data->identifierMode = mode; + + QMap &map = + d_data->map.widgetMap(); + + QMap::iterator it; + for ( it = map.begin(); it != map.end(); ++it ) + { +#if QT_VERSION < 0x040000 + QwtLegendItemManager *item = (QwtLegendItemManager *)it.data(); +#else + QwtLegendItemManager *item = (QwtLegendItemManager *)it.value(); +#endif + if ( item ) + item->updateLegend(this); + } +} + +/*! + \return the legend display policy. + Default is LegendDisplayPolicy::Auto. + \sa setDisplayPolicy(), LegendDisplayPolicy +*/ + +QwtLegend::LegendDisplayPolicy QwtLegend::displayPolicy() const +{ + return d_data->displayPolicy; +} + +//! \sa LegendItemMode +void QwtLegend::setItemMode(LegendItemMode mode) +{ + d_data->itemMode = mode; +} + +//! \sa LegendItemMode +QwtLegend::LegendItemMode QwtLegend::itemMode() const +{ + return d_data->itemMode; +} + +/*! + \return the IdentifierMode to be used in combination with + LegendDisplayPolicy::Fixed. + + Default is ShowLine | ShowSymbol | ShowText. +*/ +int QwtLegend::identifierMode() const +{ + return d_data->identifierMode; +} + +/*! + The contents widget is the only child of the viewport() and + the parent widget of all 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() and + the parent widget of all legend items. +*/ +const QWidget *QwtLegend::contentsWidget() const +{ + return d_data->view->contentsWidget; +} + +/*! + Insert a new item for a plot item + \param plotItem Plot item + \param legendItem New legend item + \note The parent of item will be changed to QwtLegend::contentsWidget() +*/ +void QwtLegend::insert(const QwtLegendItemManager *plotItem, QWidget *legendItem) +{ + if ( legendItem == NULL || plotItem == NULL ) + return; + + QWidget *contentsWidget = d_data->view->contentsWidget; + + if ( legendItem->parent() != contentsWidget ) + { +#if QT_VERSION >= 0x040000 + legendItem->setParent(contentsWidget); +#else + legendItem->reparent(contentsWidget, QPoint(0, 0)); +#endif + } + + legendItem->show(); + + d_data->map.insert(plotItem, legendItem); + + layoutContents(); + + if ( contentsWidget->layout() ) + { +#if QT_VERSION >= 0x040000 + contentsWidget->layout()->addWidget(legendItem); +#endif + + // set tab focus chain + + QWidget *w = NULL; + +#if QT_VERSION < 0x040000 + QLayoutIterator layoutIterator = + contentsWidget->layout()->iterator(); + for ( QLayoutItem *item = layoutIterator.current(); + item != 0; item = ++layoutIterator) + { +#else + for (int i = 0; i < contentsWidget->layout()->count(); i++) + { + QLayoutItem *item = contentsWidget->layout()->itemAt(i); +#endif + if ( w && item->widget() ) + { + QWidget::setTabOrder(w, item->widget()); + w = item->widget(); + } + } + } + if ( parentWidget() && parentWidget()->layout() == NULL ) + { + /* + updateGeometry() doesn't post LayoutRequest in certain + situations, like when we are hidden. But we want the + parent widget notified, so it can show/hide the legend + depending on its items. + */ +#if QT_VERSION < 0x040000 + QApplication::postEvent(parentWidget(), + new QEvent(QEvent::LayoutHint)); +#else + QApplication::postEvent(parentWidget(), + new QEvent(QEvent::LayoutRequest)); +#endif + } +} + +/*! + Find the widget that represents a plot item + + \param plotItem Plot item + \return Widget on the legend, or NULL +*/ +QWidget *QwtLegend::find(const QwtLegendItemManager *plotItem) const +{ + return d_data->map.find(plotItem); +} + +/*! + Find the widget that represents a plot item + + \param legendItem Legend item + \return Widget on the legend, or NULL +*/ +QwtLegendItemManager *QwtLegend::find(const QWidget *legendItem) const +{ + return d_data->map.find(legendItem); +} + +/*! + Find the corresponding item for a plotItem and remove it + from the item list. + + \param plotItem Plot item +*/ +void QwtLegend::remove(const QwtLegendItemManager *plotItem) +{ + QWidget *legendItem = d_data->map.find(plotItem); + d_data->map.remove(legendItem); + delete legendItem; +} + +//! Remove all items. +void QwtLegend::clear() +{ +#if QT_VERSION < 0x040000 + bool doUpdate = isUpdatesEnabled(); +#else + bool doUpdate = updatesEnabled(); +#endif + setUpdatesEnabled(false); + + d_data->map.clear(); + + setUpdatesEnabled(doUpdate); + update(); +} + +//! 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 the width w. + \param width Width +*/ +int QwtLegend::heightForWidth(int width) const +{ + width -= 2 * frameWidth(); + + int h = d_data->view->contentsWidget->heightForWidth(width); +#if QT_VERSION < 0x040000 + + // Asking the layout is the default implementation in Qt4 + + if ( h <= 0 ) + { + QLayout *l = d_data->view->contentsWidget->layout(); + if ( l && l->hasHeightForWidth() ) + h = l->heightForWidth(width); + } +#endif + if ( h >= 0 ) + h += 2 * frameWidth(); + + return h; +} + +/*! + Adjust contents widget and item layout to the size of the viewport(). +*/ +void QwtLegend::layoutContents() +{ + const QSize visibleSize = d_data->view->viewport()->size(); + + const QLayout *l = d_data->view->contentsWidget->layout(); + if ( l && l->inherits("QwtDynGridLayout") ) + { + const QwtDynGridLayout *tl = (const QwtDynGridLayout *)l; + + const int minW = int(tl->maxItemWidth()) + 2 * tl->margin(); + + int w = qwtMax(visibleSize.width(), minW); + int h = qwtMax(tl->heightForWidth(w), visibleSize.height()); + + const int vpWidth = d_data->view->viewportSize(w, h).width(); + if ( w > vpWidth ) + { + w = qwtMax(vpWidth, minW); + h = qwtMax(tl->heightForWidth(w), visibleSize.height()); + } + + d_data->view->contentsWidget->resize(w, h); +#if QT_VERSION < 0x040000 + d_data->view->resizeContents(w, h); +#endif + } +} + +/*! + Filter layout related events of QwtLegend::contentsWidget(). + + \param o Object to be filtered + \param e Event +*/ +bool QwtLegend::eventFilter(QObject *o, QEvent *e) +{ + if ( o == d_data->view->contentsWidget ) + { + switch(e->type()) + { + case QEvent::ChildRemoved: + { + const QChildEvent *ce = (const QChildEvent *)e; + if ( ce->child()->isWidgetType() ) + d_data->map.remove((QWidget *)ce->child()); + break; + } +#if QT_VERSION < 0x040000 + case QEvent::LayoutHint: +#else + case QEvent::LayoutRequest: +#endif + { + layoutContents(); + break; + } +#if QT_VERSION < 0x040000 + case QEvent::Resize: + { + updateGeometry(); + break; + } +#endif + default: + break; + } + } + + return QFrame::eventFilter(o, e); +} + + +//! Return true, if there are no legend items. +bool QwtLegend::isEmpty() const +{ + return d_data->map.count() == 0; +} + +//! Return the number of legend items. +uint QwtLegend::itemCount() const +{ + return d_data->map.count(); +} + +//! Return a list of all legend items +#if QT_VERSION < 0x040000 +QValueList QwtLegend::legendItems() const +#else +QList QwtLegend::legendItems() const +#endif +{ + const QMap &map = + d_data->map.widgetMap(); + +#if QT_VERSION < 0x040000 + QValueList list; +#else + QList list; +#endif + + QMap::const_iterator it; + for ( it = map.begin(); it != map.end(); ++it ) + list += it.key(); + + return list; +} + +/*! + Resize event + \param e Resize event +*/ +void QwtLegend::resizeEvent(QResizeEvent *e) +{ + QFrame::resizeEvent(e); + d_data->view->setGeometry(contentsRect()); +} diff --git a/qwt/src/qwt_legend.h b/qwt/src/qwt_legend.h new file mode 100644 index 000000000..ffb16370b --- /dev/null +++ b/qwt/src/qwt_legend.h @@ -0,0 +1,142 @@ +/* -*- 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 + +#ifndef QWT_LEGEND_H +#define QWT_LEGEND_H + +#include +#include "qwt_global.h" +#if QT_VERSION < 0x040000 +#include +#else +#include +#endif + +class QScrollBar; +class QwtLegendItemManager; + +/*! + \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 QwtLegendItem. + + \sa QwtLegendItem, QwtLegendItemManager QwtPlot +*/ + +class QWT_EXPORT QwtLegend : public QFrame +{ + Q_OBJECT + +public: + /*! + \brief Display policy + + - NoIdentifier\n + The client code is responsible how to display of each legend item. + The Qwt library will not interfere. + + - FixedIdentifier\n + All legend items are displayed with the QwtLegendItem::IdentifierMode + to be passed in 'mode'. + + - AutoIdentifier\n + Each legend item is displayed with a mode that is a bitwise or of + - QwtLegendItem::ShowLine (if its curve is drawn with a line) and + - QwtLegendItem::ShowSymbol (if its curve is drawn with symbols) and + - QwtLegendItem::ShowText (if the has a title). + + Default is AutoIdentifier. + \sa setDisplayPolicy(), displayPolicy(), QwtLegendItem::IdentifierMode + */ + + enum LegendDisplayPolicy + { + NoIdentifier = 0, + FixedIdentifier = 1, + AutoIdentifier = 2 + }; + + /*! + \brief Interaction mode for the legend items + + - ReadOnlyItem\n + The legend item is not interactive, like a label + + - ClickableItem\n + The legend item is clickable, like a push button + + - CheckableItem\n + The legend item is checkable, like a checkable button + + Default is ReadOnlyItem. + \sa setItemMode(), itemMode(), QwtLegendItem::IdentifierMode + QwtLegendItem::clicked(), QwtLegendItem::checked(), + QwtPlot::legendClicked(), QwtPlot::legendChecked() + */ + + enum LegendItemMode + { + ReadOnlyItem, + ClickableItem, + CheckableItem + }; + + explicit QwtLegend(QWidget *parent = NULL); + virtual ~QwtLegend(); + + void setDisplayPolicy(LegendDisplayPolicy policy, int mode); + LegendDisplayPolicy displayPolicy() const; + + void setItemMode(LegendItemMode); + LegendItemMode itemMode() const; + + int identifierMode() const; + + QWidget *contentsWidget(); + const QWidget *contentsWidget() const; + + void insert(const QwtLegendItemManager *, QWidget *); + void remove(const QwtLegendItemManager *); + + QWidget *find(const QwtLegendItemManager *) const; + QwtLegendItemManager *find(const QWidget *) const; + +#if QT_VERSION < 0x040000 + virtual QValueList legendItems() const; +#else + virtual QList legendItems() const; +#endif + + void clear(); + + bool isEmpty() const; + uint itemCount() const; + + virtual bool eventFilter(QObject *, QEvent *); + + virtual QSize sizeHint() const; + virtual int heightForWidth(int w) const; + + QScrollBar *horizontalScrollBar() const; + QScrollBar *verticalScrollBar() const; + +protected: + virtual void resizeEvent(QResizeEvent *); + virtual void layoutContents(); + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif // QWT_LEGEND_H diff --git a/qwt/src/qwt_legend_item.cpp b/qwt/src/qwt_legend_item.cpp new file mode 100644 index 000000000..8098bfcf6 --- /dev/null +++ b/qwt/src/qwt_legend_item.cpp @@ -0,0 +1,590 @@ +/* -*- 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 +#if QT_VERSION >= 0x040000 +#include +#include +#endif +#include "qwt_math.h" +#include "qwt_painter.h" +#include "qwt_symbol.h" +#include "qwt_legend_item.h" + +static const int ButtonFrame = 2; +static const int Margin = 2; + +static QSize buttonShift(const QwtLegendItem *w) +{ +#if QT_VERSION < 0x040000 + const int ph = w->style().pixelMetric( + QStyle::PM_ButtonShiftHorizontal, w); + const int pv = w->style().pixelMetric( + QStyle::PM_ButtonShiftVertical, w); +#else + 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); +#endif + return QSize(ph, pv); +} + +class QwtLegendItem::PrivateData +{ +public: + PrivateData(): + itemMode(QwtLegend::ReadOnlyItem), + isDown(false), + identifierWidth(8), + identifierMode(QwtLegendItem::ShowLine | QwtLegendItem::ShowText), + curvePen(Qt::NoPen), + spacing(Margin) + { + symbol = new QwtSymbol(); + } + + ~PrivateData() + { + delete symbol; + } + + QwtLegend::LegendItemMode itemMode; + bool isDown; + + int identifierWidth; + int identifierMode; + QwtSymbol *symbol; + QPen curvePen; + + int spacing; +}; + +/*! + \param parent Parent widget +*/ +QwtLegendItem::QwtLegendItem(QWidget *parent): + QwtTextLabel(parent) +{ + d_data = new PrivateData; + init(QwtText()); +} + +/*! + \param symbol Curve symbol + \param curvePen Curve pen + \param text Label text + \param parent Parent widget +*/ +QwtLegendItem::QwtLegendItem(const QwtSymbol &symbol, + const QPen &curvePen, const QwtText &text, + QWidget *parent): + QwtTextLabel(parent) +{ + d_data = new PrivateData; + + delete d_data->symbol; + d_data->symbol = symbol.clone(); + + d_data->curvePen = curvePen; + + init(text); +} + +void QwtLegendItem::init(const QwtText &text) +{ + setMargin(Margin); + setIndent(margin() + d_data->identifierWidth + 2 * d_data->spacing); + setText(text); +} + +//! Destructor +QwtLegendItem::~QwtLegendItem() +{ + delete d_data; + d_data = NULL; +} + +/*! + Set the text to the legend item + + \param text Text label + \sa QwtTextLabel::text() +*/ +void QwtLegendItem::setText(const QwtText &text) +{ + const int flags = Qt::AlignLeft | Qt::AlignVCenter +#if QT_VERSION < 0x040000 + | Qt::WordBreak | Qt::ExpandTabs; +#else + | Qt::TextExpandTabs | Qt::TextWordWrap; +#endif + + QwtText txt = text; + txt.setRenderFlags(flags); + + QwtTextLabel::setText(txt); +} + +/*! + Set the item mode + The default is QwtLegend::ReadOnlyItem + + \param mode Item mode + \sa itemMode() +*/ +void QwtLegendItem::setItemMode(QwtLegend::LegendItemMode mode) +{ + d_data->itemMode = mode; + d_data->isDown = false; + +#if QT_VERSION >= 0x040000 + using namespace Qt; +#endif + setFocusPolicy(mode != QwtLegend::ReadOnlyItem ? TabFocus : NoFocus); + setMargin(ButtonFrame + Margin); + + updateGeometry(); +} + +/*! + Return the item mode + + \sa setItemMode() +*/ +QwtLegend::LegendItemMode QwtLegendItem::itemMode() const +{ + return d_data->itemMode; +} + +/*! + Set identifier mode. + Default is ShowLine | ShowText. + \param mode Or'd values of IdentifierMode + + \sa identifierMode() +*/ +void QwtLegendItem::setIdentifierMode(int mode) +{ + if ( mode != d_data->identifierMode ) + { + d_data->identifierMode = mode; + update(); + } +} + +/*! + Or'd values of IdentifierMode. + \sa setIdentifierMode(), IdentifierMode +*/ +int QwtLegendItem::identifierMode() const +{ + return d_data->identifierMode; +} + +/*! + Set the width for the identifier + Default is 8 pixels + + \param width New width + + \sa identifierMode(), identifierWidth() +*/ +void QwtLegendItem::setIdentifierWidth(int width) +{ + width = qwtMax(width, 0); + if ( width != d_data->identifierWidth ) + { + d_data->identifierWidth = width; + setIndent(margin() + d_data->identifierWidth + + 2 * d_data->spacing); + } +} +/*! + Return the width of the identifier + + \sa setIdentifierWidth() +*/ +int QwtLegendItem::identifierWidth() const +{ + return d_data->identifierWidth; +} + +/*! + Change the spacing + \param spacing Spacing + \sa spacing(), identifierWidth(), QwtTextLabel::margin() +*/ +void QwtLegendItem::setSpacing(int spacing) +{ + spacing = qwtMax(spacing, 0); + if ( spacing != d_data->spacing ) + { + d_data->spacing = spacing; + setIndent(margin() + d_data->identifierWidth + + 2 * d_data->spacing); + } +} + +/*! + Return the spacing + \sa setSpacing(), identifierWidth(), QwtTextLabel::margin() +*/ +int QwtLegendItem::spacing() const +{ + return d_data->spacing; +} + +/*! + Set curve symbol. + \param symbol Symbol + + \sa symbol() +*/ +void QwtLegendItem::setSymbol(const QwtSymbol &symbol) +{ + delete d_data->symbol; + d_data->symbol = symbol.clone(); + update(); +} + +/*! + \return The curve symbol. + \sa setSymbol() +*/ +const QwtSymbol& QwtLegendItem::symbol() const +{ + return *d_data->symbol; +} + + +/*! + Set curve pen. + \param pen Curve pen + + \sa curvePen() +*/ +void QwtLegendItem::setCurvePen(const QPen &pen) +{ + if ( pen != d_data->curvePen ) + { + d_data->curvePen = pen; + update(); + } +} + +/*! + \return The curve pen. + \sa setCurvePen() +*/ +const QPen& QwtLegendItem::curvePen() const +{ + return d_data->curvePen; +} + +/*! + Paint the identifier to a given rect. + \param painter Painter + \param rect Rect where to paint +*/ +void QwtLegendItem::drawIdentifier( + QPainter *painter, const QRect &rect) const +{ + if ( rect.isEmpty() ) + return; + + if ( (d_data->identifierMode & ShowLine ) && (d_data->curvePen.style() != Qt::NoPen) ) + { + painter->save(); + painter->setPen(QwtPainter::scaledPen(d_data->curvePen)); + QwtPainter::drawLine(painter, rect.left(), rect.center().y(), + rect.right(), rect.center().y()); + painter->restore(); + } + + if ( (d_data->identifierMode & ShowSymbol) + && (d_data->symbol->style() != QwtSymbol::NoSymbol) ) + { + QSize symbolSize = + QwtPainter::metricsMap().screenToLayout(d_data->symbol->size()); + + // scale the symbol size down if it doesn't fit into rect. + + if ( rect.width() < symbolSize.width() ) + { + const double ratio = + double(symbolSize.width()) / double(rect.width()); + symbolSize.setWidth(rect.width()); + symbolSize.setHeight(qRound(symbolSize.height() / ratio)); + } + if ( rect.height() < symbolSize.height() ) + { + const double ratio = + double(symbolSize.width()) / double(rect.width()); + symbolSize.setHeight(rect.height()); + symbolSize.setWidth(qRound(symbolSize.width() / ratio)); + } + + QRect symbolRect; + symbolRect.setSize(symbolSize); + symbolRect.moveCenter(rect.center()); + + painter->save(); + painter->setBrush(d_data->symbol->brush()); + painter->setPen(QwtPainter::scaledPen(d_data->symbol->pen())); + d_data->symbol->draw(painter, symbolRect); + painter->restore(); + } +} + +/*! + Draw the legend item to a given rect. + \param painter Painter + \param rect Rect where to paint the button +*/ + +void QwtLegendItem::drawItem(QPainter *painter, const QRect &rect) const +{ + painter->save(); + + const QwtMetricsMap &map = QwtPainter::metricsMap(); + + const int m = map.screenToLayoutX(margin()); + const int spacing = map.screenToLayoutX(d_data->spacing); + const int identifierWidth = map.screenToLayoutX(d_data->identifierWidth); + + const QRect identifierRect(rect.x() + m, rect.y(), + identifierWidth, rect.height()); + drawIdentifier(painter, identifierRect); + + // Label + + QRect titleRect = rect; + titleRect.setX(identifierRect.right() + 2 * spacing); + + text().draw(painter, titleRect); + + painter->restore(); +} + +//! Paint event +void QwtLegendItem::paintEvent(QPaintEvent *e) +{ + const QRect cr = contentsRect(); + + QPainter painter(this); + painter.setClipRegion(e->region()); + + if ( d_data->isDown ) + { + qDrawWinButton(&painter, 0, 0, width(), height(), +#if QT_VERSION < 0x040000 + colorGroup(), +#else + palette(), +#endif + true); + } + + painter.save(); + + if ( d_data->isDown ) + { + const QSize shiftSize = buttonShift(this); + painter.translate(shiftSize.width(), shiftSize.height()); + } + + painter.setClipRect(cr); + + drawContents(&painter); + + QRect rect = cr; + rect.setX(rect.x() + margin()); + if ( d_data->itemMode != QwtLegend::ReadOnlyItem ) + rect.setX(rect.x() + ButtonFrame); + + rect.setWidth(d_data->identifierWidth); + + drawIdentifier(&painter, rect); + + painter.restore(); +} + +//! Handle mouse press events +void QwtLegendItem::mousePressEvent(QMouseEvent *e) +{ + if ( e->button() == Qt::LeftButton ) + { + switch(d_data->itemMode) + { + case QwtLegend::ClickableItem: + { + setDown(true); + return; + } + case QwtLegend::CheckableItem: + { + setDown(!isDown()); + return; + } + default:; + } + } + QwtTextLabel::mousePressEvent(e); +} + +//! Handle mouse release events +void QwtLegendItem::mouseReleaseEvent(QMouseEvent *e) +{ + if ( e->button() == Qt::LeftButton ) + { + switch(d_data->itemMode) + { + case QwtLegend::ClickableItem: + { + setDown(false); + return; + } + case QwtLegend::CheckableItem: + { + return; // do nothing, but accept + } + default:; + } + } + QwtTextLabel::mouseReleaseEvent(e); +} + +//! Handle key press events +void QwtLegendItem::keyPressEvent(QKeyEvent *e) +{ + if ( e->key() == Qt::Key_Space ) + { + switch(d_data->itemMode) + { + case QwtLegend::ClickableItem: + { + if ( !e->isAutoRepeat() ) + setDown(true); + return; + } + case QwtLegend::CheckableItem: + { + if ( !e->isAutoRepeat() ) + setDown(!isDown()); + return; + } + default:; + } + } + + QwtTextLabel::keyPressEvent(e); +} + +//! Handle key release events +void QwtLegendItem::keyReleaseEvent(QKeyEvent *e) +{ + if ( e->key() == Qt::Key_Space ) + { + switch(d_data->itemMode) + { + case QwtLegend::ClickableItem: + { + if ( !e->isAutoRepeat() ) + setDown(false); + return; + } + case QwtLegend::CheckableItem: + { + return; // do nothing, but accept + } + default:; + } + } + + QwtTextLabel::keyReleaseEvent(e); +} + +/*! + Check/Uncheck a the item + + \param on check/uncheck + \sa setItemMode() +*/ +void QwtLegendItem::setChecked(bool on) +{ + if ( d_data->itemMode == QwtLegend::CheckableItem ) + { + const bool isBlocked = signalsBlocked(); + blockSignals(true); + + setDown(on); + + blockSignals(isBlocked); + } +} + +//! Return true, if the item is checked +bool QwtLegendItem::isChecked() const +{ + return d_data->itemMode == QwtLegend::CheckableItem && isDown(); +} + +//! Set the item being down +void QwtLegendItem::setDown(bool down) +{ + if ( down == d_data->isDown ) + return; + + d_data->isDown = down; + update(); + + if ( d_data->itemMode == QwtLegend::ClickableItem ) + { + if ( d_data->isDown ) + emit pressed(); + else + { + emit released(); + emit clicked(); + } + } + + if ( d_data->itemMode == QwtLegend::CheckableItem ) + emit checked(d_data->isDown); +} + +//! Return true, if the item is down +bool QwtLegendItem::isDown() const +{ + return d_data->isDown; +} + +//! Return a size hint +QSize QwtLegendItem::sizeHint() const +{ + QSize sz = QwtTextLabel::sizeHint(); + if ( d_data->itemMode != QwtLegend::ReadOnlyItem ) + sz += buttonShift(this); + + return sz; +} + +void QwtLegendItem::drawText(QPainter *painter, const QRect &rect) +{ + QwtTextLabel::drawText(painter, rect); +} diff --git a/qwt/src/qwt_legend_item.h b/qwt/src/qwt_legend_item.h new file mode 100644 index 000000000..2e6a461b0 --- /dev/null +++ b/qwt/src/qwt_legend_item.h @@ -0,0 +1,122 @@ +/* -*- 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 + +#ifndef QWT_LEGEND_ITEM_H +#define QWT_LEGEND_ITEM_H + +#include "qwt_global.h" +#include "qwt_legend.h" +#include "qwt_text.h" +#include "qwt_text_label.h" + +class QPainter; +class QPen; +class QwtSymbol; + +/*! + \brief A legend label + + QwtLegendItem represents a curve on a legend. + It displays an curve identifier with an explaining text. + The identifier might be a combination of curve symbol and line. + In readonly mode it behaves like a label, otherwise like + an unstylish push button. + + \sa QwtLegend, QwtPlotCurve +*/ +class QWT_EXPORT QwtLegendItem: public QwtTextLabel +{ + Q_OBJECT +public: + + /*! + \brief Identifier mode + + Default is ShowLine | ShowText + \sa identifierMode(), setIdentifierMode() + */ + + enum IdentifierMode + { + NoIdentifier = 0, + ShowLine = 1, + ShowSymbol = 2, + ShowText = 4 + }; + + explicit QwtLegendItem(QWidget *parent = 0); + explicit QwtLegendItem(const QwtSymbol &, const QPen &, + const QwtText &, QWidget *parent = 0); + virtual ~QwtLegendItem(); + + virtual void setText(const QwtText &); + + void setItemMode(QwtLegend::LegendItemMode); + QwtLegend::LegendItemMode itemMode() const; + + void setIdentifierMode(int); + int identifierMode() const; + + void setIdentifierWidth(int width); + int identifierWidth() const; + + void setSpacing(int spacing); + int spacing() const; + + void setSymbol(const QwtSymbol &); + const QwtSymbol& symbol() const; + + void setCurvePen(const QPen &); + const QPen& curvePen() const; + + virtual void drawIdentifier(QPainter *, const QRect &) const; + virtual void drawItem(QPainter *p, const QRect &) const; + + virtual QSize sizeHint() const; + + bool isChecked() const; + +public slots: + void setChecked(bool on); + +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 relased + 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 *); + + virtual void drawText(QPainter *, const QRect &); + +private: + void init(const QwtText &); + + class PrivateData; + PrivateData *d_data; +}; + +#endif // QWT_LEGEND_ITEM_H diff --git a/qwt/src/qwt_legend_itemmanager.h b/qwt/src/qwt_legend_itemmanager.h new file mode 100644 index 000000000..5d26e2b94 --- /dev/null +++ b/qwt/src/qwt_legend_itemmanager.h @@ -0,0 +1,54 @@ +/* -*- 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 + +#ifndef QWT_LEGEND_ITEM_MANAGER_H +#define QWT_LEGEND_ITEM_MANAGER_H + +#include "qwt_global.h" + +class QwtLegend; +class QWidget; + +/*! + \brief Abstract API to bind plot items to the legend +*/ + +class QWT_EXPORT QwtLegendItemManager +{ +public: + //! Constructor + QwtLegendItemManager() + { + } + + //! Destructor + virtual ~QwtLegendItemManager() + { + } + + /*! + Update the widget that represents the item on the legend + \param legend Legend + \sa legendItem() + */ + virtual void updateLegend(QwtLegend *legend) const = 0; + + /*! + Allocate the widget that represents the item on the legend + \return Allocated widget + \sa updateLegend() QwtLegend() + */ + + virtual QWidget *legendItem() const = 0; +}; + +#endif + diff --git a/qwt/src/qwt_magnifier.cpp b/qwt/src/qwt_magnifier.cpp new file mode 100644 index 000000000..44a4a2e7e --- /dev/null +++ b/qwt/src/qwt_magnifier.cpp @@ -0,0 +1,482 @@ +/* -*- 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 "qwt_math.h" +#include "qwt_magnifier.h" + +class QwtMagnifier::PrivateData +{ +public: + PrivateData(): + isEnabled(false), + wheelFactor(0.9), + wheelButtonState(Qt::NoButton), + mouseFactor(0.95), + mouseButton(Qt::RightButton), + mouseButtonState(Qt::NoButton), + keyFactor(0.9), + zoomInKey(Qt::Key_Plus), + zoomOutKey(Qt::Key_Minus), +#if QT_VERSION < 0x040000 + zoomInKeyModifiers(Qt::NoButton), + zoomOutKeyModifiers(Qt::NoButton), +#else + zoomInKeyModifiers(Qt::NoModifier), + zoomOutKeyModifiers(Qt::NoModifier), +#endif + mousePressed(false) + { + } + + bool isEnabled; + + double wheelFactor; + int wheelButtonState; + + double mouseFactor; + int mouseButton; + int mouseButtonState; + + double keyFactor; + int zoomInKey; + int zoomOutKey; + int zoomInKeyModifiers; + int 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. + 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 a mandatory button state for zooming in/out using the wheel. + The default button state is Qt::NoButton. + + \param buttonState Button state + \sa wheelButtonState() +*/ +void QwtMagnifier::setWheelButtonState(int buttonState) +{ + d_data->wheelButtonState = buttonState; +} + +/*! + \return Wheel button state + \sa setWheelButtonState() +*/ +int QwtMagnifier::wheelButtonState() const +{ + return d_data->wheelButtonState; +} + +/*! + \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 buttonState Button state + \sa getMouseButton() +*/ +void QwtMagnifier::setMouseButton(int button, int buttonState) +{ + d_data->mouseButton = button; + d_data->mouseButtonState = buttonState; +} + +//! \sa setMouseButton() +void QwtMagnifier::getMouseButton( + int &button, int &buttonState) const +{ + button = d_data->mouseButton; + buttonState = d_data->mouseButtonState; +} + +/*! + \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, int modifiers) +{ + d_data->zoomInKey = key; + d_data->zoomInKeyModifiers = modifiers; +} + +//! \sa setZoomInKey() +void QwtMagnifier::getZoomInKey(int &key, int &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, int modifiers) +{ + d_data->zoomOutKey = key; + d_data->zoomOutKeyModifiers = modifiers; +} + +//! \sa setZoomOutKey() +void QwtMagnifier::getZoomOutKey(int &key, int &modifiers) const +{ + key = d_data->zoomOutKey; + modifiers = d_data->zoomOutKeyModifiers; +} + +/*! + \brief Event filter + + When isEnabled() the mouse events of the observed widget are filtered. + + \sa widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseMoveEvent(), widgetWheelEvent(), widgetKeyPressEvent() + widgetKeyReleaseEvent() +*/ +bool QwtMagnifier::eventFilter(QObject *o, QEvent *e) +{ + if ( o && o == parent() ) + { + switch(e->type() ) + { + case QEvent::MouseButtonPress: + { + widgetMousePressEvent((QMouseEvent *)e); + break; + } + case QEvent::MouseMove: + { + widgetMouseMoveEvent((QMouseEvent *)e); + break; + } + case QEvent::MouseButtonRelease: + { + widgetMouseReleaseEvent((QMouseEvent *)e); + break; + } + case QEvent::Wheel: + { + widgetWheelEvent((QWheelEvent *)e); + break; + } + case QEvent::KeyPress: + { + widgetKeyPressEvent((QKeyEvent *)e); + break; + } + case QEvent::KeyRelease: + { + widgetKeyReleaseEvent((QKeyEvent *)e); + break; + } + default:; + } + } + return QObject::eventFilter(o, e); +} + +/*! + Handle a mouse press event for the observed widget. + + \param me Mouse event + \sa eventFilter(), widgetMouseReleaseEvent(), widgetMouseMoveEvent() +*/ +void QwtMagnifier::widgetMousePressEvent(QMouseEvent *me) +{ + if ( me->button() != d_data->mouseButton || parentWidget() == NULL ) + return; + +#if QT_VERSION < 0x040000 + if ( (me->state() & Qt::KeyButtonMask) != + (d_data->mouseButtonState & Qt::KeyButtonMask) ) +#else + if ( (me->modifiers() & Qt::KeyboardModifierMask) != + (int)(d_data->mouseButtonState & Qt::KeyboardModifierMask) ) +#endif + { + return; + } + + d_data->hasMouseTracking = parentWidget()->hasMouseTracking(); + parentWidget()->setMouseTracking(true); + d_data->mousePos = me->pos(); + d_data->mousePressed = true; +} + +/*! + Handle a mouse release event for the observed widget. + \sa eventFilter(), widgetMousePressEvent(), widgetMouseMoveEvent(), +*/ +void QwtMagnifier::widgetMouseReleaseEvent(QMouseEvent *) +{ + if ( d_data->mousePressed && parentWidget() ) + { + d_data->mousePressed = false; + parentWidget()->setMouseTracking(d_data->hasMouseTracking); + } +} + +/*! + Handle a mouse move event for the observed widget. + + \param me Mouse event + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), +*/ +void QwtMagnifier::widgetMouseMoveEvent(QMouseEvent *me) +{ + if ( !d_data->mousePressed ) + return; + + const int dy = me->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 = me->pos(); +} + +/*! + Handle a wheel event for the observed widget. + + \param we Wheel event + \sa eventFilter() +*/ +void QwtMagnifier::widgetWheelEvent(QWheelEvent *we) +{ +#if QT_VERSION < 0x040000 + if ( (we->state() & Qt::KeyButtonMask) != + (d_data->wheelButtonState & Qt::KeyButtonMask) ) +#else + if ( (we->modifiers() & Qt::KeyboardModifierMask) != + (int)(d_data->wheelButtonState & Qt::KeyboardModifierMask) ) +#endif + { + 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 = ::pow(d_data->wheelFactor, + qwtAbs(we->delta() / 120)); + if ( we->delta() > 0 ) + f = 1 / f; + + rescale(f); + } +} + +/*! + Handle a key press event for the observed widget. + + \param ke Key event + \sa eventFilter(), widgetKeyReleaseEvent() +*/ +void QwtMagnifier::widgetKeyPressEvent(QKeyEvent *ke) +{ + const int key = ke->key(); +#if QT_VERSION < 0x040000 + const int state = ke->state(); +#else + const int state = ke->modifiers(); +#endif + + if ( key == d_data->zoomInKey && + state == d_data->zoomInKeyModifiers ) + { + rescale(d_data->keyFactor); + } + else if ( key == d_data->zoomOutKey && + state == d_data->zoomOutKeyModifiers ) + { + rescale(1.0 / d_data->keyFactor); + } +} + +/*! + Handle a key release event for the observed widget. + + \param ke Key event + \sa eventFilter(), widgetKeyReleaseEvent() +*/ +void QwtMagnifier::widgetKeyReleaseEvent(QKeyEvent *) +{ +} + +//! \return Parent widget, where the rescaling happens +QWidget *QwtMagnifier::parentWidget() +{ + if ( parent()->inherits("QWidget") ) + return (QWidget *)parent(); + + return NULL; +} + +//! \return Parent widget, where the rescaling happens +const QWidget *QwtMagnifier::parentWidget() const +{ + if ( parent()->inherits("QWidget") ) + return (const QWidget *)parent(); + + return NULL; +} + diff --git a/qwt/src/qwt_magnifier.h b/qwt/src/qwt_magnifier.h new file mode 100644 index 000000000..c50e34145 --- /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(int button, int buttonState = Qt::NoButton); + void getMouseButton(int &button, int &buttonState) const; + + // mouse wheel + void setWheelFactor(double); + double wheelFactor() const; + + void setWheelButtonState(int buttonState); + int wheelButtonState() const; + + // keyboard + void setKeyFactor(double); + double keyFactor() const; + + void setZoomInKey(int key, int modifiers); + void getZoomInKey(int &key, int &modifiers) const; + + void setZoomOutKey(int key, int modifiers); + void getZoomOutKey(int &key, int &modifiers) 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..45cda171a --- /dev/null +++ b/qwt/src/qwt_math.cpp @@ -0,0 +1,47 @@ +/* -*- 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 "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 = qwtMin(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 = qwtMax(rv, array[i]); + + return rv; +} diff --git a/qwt/src/qwt_math.h b/qwt/src/qwt_math.h new file mode 100644 index 000000000..80c2f36af --- /dev/null +++ b/qwt/src/qwt_math.h @@ -0,0 +1,192 @@ +/* -*- 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 +#include +#include "qwt_global.h" +#include "qwt_double_rect.h" + +#if QT_VERSION < 0x040000 + +#define qwtMax QMAX +#define qwtMin QMIN +#define qwtAbs QABS + +#else // QT_VERSION >= 0x040000 + +#define qwtMax qMax +#define qwtMin qMin +#define qwtAbs qAbs + +#endif + +#ifndef LOG10_2 +#define LOG10_2 0.30102999566398119802 /* log10(2) */ +#endif + +#ifndef LOG10_3 +#define LOG10_3 0.47712125471966243540 /* log10(3) */ +#endif + +#ifndef LOG10_5 +#define LOG10_5 0.69897000433601885749 /* log10(5) */ +#endif + +#ifndef M_2PI +#define M_2PI 6.28318530717958623200 /* 2 pi */ +#endif + +#ifndef LOG_MIN +//! Mininum 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 + +#ifndef M_E +#define M_E 2.7182818284590452354 /* e */ +#endif + +#ifndef M_LOG2E +#define M_LOG2E 1.4426950408889634074 /* log_2 e */ +#endif + +#ifndef M_LOG10E +#define M_LOG10E 0.43429448190325182765 /* log_10 e */ +#endif + +#ifndef M_LN2 +#define M_LN2 0.69314718055994530942 /* log_e 2 */ +#endif + +#ifndef M_LN10 +#define M_LN10 2.30258509299404568402 /* log_e 10 */ +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 /* pi */ +#endif + +#ifndef M_PI_2 +#define M_PI_2 1.57079632679489661923 /* pi/2 */ +#endif + +#ifndef M_PI_4 +#define M_PI_4 0.78539816339744830962 /* pi/4 */ +#endif + +#ifndef M_1_PI +#define M_1_PI 0.31830988618379067154 /* 1/pi */ +#endif + +#ifndef M_2_PI +#define M_2_PI 0.63661977236758134308 /* 2/pi */ +#endif + +#ifndef M_2_SQRTPI +#define M_2_SQRTPI 1.12837916709551257390 /* 2/sqrt(pi) */ +#endif + +#ifndef M_SQRT2 +#define M_SQRT2 1.41421356237309504880 /* sqrt(2) */ +#endif + +#ifndef M_SQRT1_2 +#define M_SQRT1_2 0.70710678118654752440 /* 1/sqrt(2) */ +#endif + +QWT_EXPORT double qwtGetMin(const double *array, int size); +QWT_EXPORT double qwtGetMax(const double *array, int size); + + +//! 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(const double x) +{ + return x*x; +} + +/*! + \brief Limit a value to fit into a specified interval + \param x Input value + \param x1 First interval boundary + \param x2 Second interval boundary +*/ +template +T qwtLim(const T& x, const T& x1, const T& x2) +{ + T rv; + T xmin, xmax; + + xmin = qwtMin(x1, x2); + xmax = qwtMax(x1, x2); + + if ( x < xmin ) + rv = xmin; + else if ( x > xmax ) + rv = xmax; + else + rv = x; + + return rv; +} + +inline QPoint qwtPolar2Pos(const QPoint &pole, + double radius, double angle) +{ + const double x = pole.x() + radius * ::cos(angle); + const double y = pole.y() - radius * ::sin(angle); + + return QPoint(qRound(x), qRound(y)); +} + +inline QPoint qwtDegree2Pos(const QPoint &pole, + double radius, double angle) +{ + return qwtPolar2Pos(pole, radius, angle / 180.0 * M_PI); +} + +inline QwtDoublePoint qwtPolar2Pos(const QwtDoublePoint &pole, + double radius, double angle) +{ + const double x = pole.x() + radius * ::cos(angle); + const double y = pole.y() - radius * ::sin(angle); + + return QPoint(qRound(x), qRound(y)); +} + +inline QwtDoublePoint qwtDegree2Pos(const QwtDoublePoint &pole, + double radius, double angle) +{ + return qwtPolar2Pos(pole, radius, angle / 180.0 * M_PI); +} + +//! Rounding of doubles, like qRound for integers +inline double qwtRound(double value) +{ + return ::floor(value + 0.5); // MSVC has no ::round(). +} + +#endif diff --git a/qwt/src/qwt_paint_buffer.cpp b/qwt/src/qwt_paint_buffer.cpp new file mode 100644 index 000000000..3d8c6a1e5 --- /dev/null +++ b/qwt/src/qwt_paint_buffer.cpp @@ -0,0 +1,201 @@ +/* -*- 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 +#if QT_VERSION < 0x040000 + +#include +#include +#include "qwt_paint_buffer.h" + +bool QwtPaintBuffer::d_enabled = true; + +//! Default constructor +QwtPaintBuffer::QwtPaintBuffer(): + d_device(0), + d_painter(0), + d_devicePainter(0) +{ +} + +/*! + Create an open paint buffer + \param device Device to paint on + \param rect Rect to paint on + \param painter Painter to paint on device. In case of 0 + QwtPaintBuffer uses an internal painter + + \sa open() +*/ + +QwtPaintBuffer::QwtPaintBuffer(QPaintDevice *device, + const QRect &rect, QPainter *painter): + d_device(0), + d_painter(0), + d_devicePainter(0) +{ + open(device, rect, painter); +} + +/*! + Closes the buffer + \sa close() +*/ +QwtPaintBuffer::~QwtPaintBuffer() +{ + close(); +} + +/*! + \return Depending on isEnabled() the painter + connected to an internal pixmap buffer + otherwise the painter connected to the device. +*/ + +QPainter *QwtPaintBuffer::painter() +{ + return d_painter; +} + +/*! + \return Device to paint on +*/ +const QPaintDevice *QwtPaintBuffer::device() +{ + return d_device; +} + +/*! + Enable/Disable double buffering. Please note that + this is a global switch for all QwtPaintBuffers, but + won't change opened buffers. +*/ +void QwtPaintBuffer::setEnabled(bool enable) +{ + d_enabled = enable; +} + +/*! + \return true if double buffering is enabled, false otherwise. +*/ +bool QwtPaintBuffer::isEnabled() +{ + return d_enabled; +} + +/*! + Open the buffer + \param device Device to paint on + \param rect Rect to paint on + \param painter Painter to paint on device. In case of 0 + QwtPaintBuffer uses an internal painter +*/ + +void QwtPaintBuffer::open(QPaintDevice *device, + const QRect &rect, QPainter *painter) +{ + close(); + + if ( device == 0 || !rect.isValid() ) + return; + + d_device = device; + d_devicePainter = painter; + d_rect = rect; + + if ( isEnabled() ) + { +#ifdef Q_WS_X11 + if ( d_pixBuffer.x11Screen() != d_device->x11Screen() ) + d_pixBuffer.x11SetScreen(d_device->x11Screen()); +#endif + d_pixBuffer.resize(d_rect.size()); + + d_painter = new QPainter(); + if ( d_device->devType() == QInternal::Widget ) + { + QWidget *w = (QWidget *)d_device; + d_pixBuffer.fill(w, d_rect.topLeft()); + d_painter->begin(&d_pixBuffer, w); + d_painter->translate(-d_rect.x(), -d_rect.y()); + } + else + { + d_painter->begin(&d_pixBuffer); + } + } + else + { + if ( d_devicePainter ) + d_painter = d_devicePainter; + else + d_painter = new QPainter(d_device); + + if ( d_device->devType() == QInternal::Widget ) + { + QWidget *w = (QWidget *)d_device; + if ( w->testWFlags( Qt::WNoAutoErase ) ) + d_painter->eraseRect(d_rect); + } + } +} + +/*! + Flush the internal pixmap buffer to the device. +*/ +void QwtPaintBuffer::flush() +{ + if ( d_enabled && d_device != 0 && d_rect.isValid()) + { + // We need a painter to find out if + // there is a painter redirection for d_device. + + QPainter *p; + if ( d_devicePainter == 0 ) + p = new QPainter(d_device); + else + p = d_devicePainter; + + QPaintDevice *device = p->device(); + if ( device->isExtDev() ) + d_devicePainter->drawPixmap(d_rect.topLeft(), d_pixBuffer); + else + bitBlt(device, d_rect.topLeft(), &d_pixBuffer ); + + if ( d_devicePainter == 0 ) + delete p; + } +} + +/*! + Flush the internal pixmap buffer to the device and close the buffer. +*/ +void QwtPaintBuffer::close() +{ + flush(); + + if ( d_painter ) + { + if ( d_painter->isActive() ) + d_painter->end(); + + if ( d_painter != d_devicePainter ) + delete d_painter; + } + + if ( !d_pixBuffer.isNull() ) + d_pixBuffer = QPixmap(); + + d_device = 0; + d_painter = 0; + d_devicePainter = 0; +} + +#endif // QT_VERSION < 0x040000 diff --git a/qwt/src/qwt_paint_buffer.h b/qwt/src/qwt_paint_buffer.h new file mode 100644 index 000000000..2fc196903 --- /dev/null +++ b/qwt/src/qwt_paint_buffer.h @@ -0,0 +1,65 @@ +/* -*- 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_PAINT_BUFFER_H +#define QWT_PAINT_BUFFER_H 1 + +#include +#if QT_VERSION < 0x040000 + +#include +#include "qwt_global.h" + +class QPainter; + +/*! + \brief Paint buffer for Qwt widgets + + QwtPaintBuffer offers a simple way to en/disable double buffering. + Double buffering is enabled as default and in general there will be + no reason to change this. +*/ + +class QWT_EXPORT QwtPaintBuffer +{ +public: + explicit QwtPaintBuffer(); + explicit QwtPaintBuffer(QPaintDevice *, const QRect &, QPainter *p = NULL); + + virtual ~QwtPaintBuffer(); + + void open(QPaintDevice *, const QRect &, QPainter *p = NULL); + void close(); + + QPainter *painter(); + const QPaintDevice *device(); + + static void setEnabled(bool enable); + static bool isEnabled(); + + //! Return Buffer used for double buffering + const QPixmap &buffer() const { return d_pixBuffer; } + +protected: + void flush(); + +private: + QPixmap d_pixBuffer; + QRect d_rect; + + QPaintDevice *d_device; // use QGuardedPtr + QPainter *d_painter; // use QGuardedPtr + QPainter *d_devicePainter; // use QGuardedPtr + + static bool d_enabled; +}; + +#endif // QT_VERSION < 0x040000 + +#endif diff --git a/qwt/src/qwt_painter.cpp b/qwt/src/qwt_painter.cpp new file mode 100644 index 000000000..c8b066919 --- /dev/null +++ b/qwt/src/qwt_painter.cpp @@ -0,0 +1,768 @@ +/* -*- 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 +#include +#if QT_VERSION < 0x040000 +#include +#else +#include +#include +#include +#include +#endif + +#include "qwt_math.h" +#include "qwt_clipper.h" +#include "qwt_color_map.h" +#include "qwt_scale_map.h" +#include "qwt_painter.h" + +QwtMetricsMap QwtPainter::d_metricsMap; + +#if defined(Q_WS_X11) +bool QwtPainter::d_deviceClipping = true; +#else +bool QwtPainter::d_deviceClipping = false; +#endif + +#if QT_VERSION < 0x040000 +bool QwtPainter::d_SVGMode = false; +#endif + +static inline bool isClippingNeeded(const QPainter *painter, QRect &clipRect) +{ + bool doClipping = false; +#if QT_VERSION >= 0x040000 + const QPaintEngine *pe = painter->paintEngine(); + if ( pe && pe->type() == QPaintEngine::SVG ) +#else + if ( painter->device()->devType() == QInternal::Picture ) +#endif + { + // The SVG paint engine ignores any clipping, + + if ( painter->hasClipping() ) + { + doClipping = true; + clipRect = painter->clipRegion().boundingRect(); + } + } + + if ( QwtPainter::deviceClipping() ) + { + if (painter->device()->devType() == QInternal::Widget || + painter->device()->devType() == QInternal::Pixmap ) + { + if ( doClipping ) + { + clipRect &= QwtPainter::deviceClipRect(); + } + else + { + doClipping = true; + clipRect = QwtPainter::deviceClipRect(); + } + } + } + + return doClipping; +} + +/*! + \brief En/Disable device clipping. + + On X11 the default for device clipping is enabled, + otherwise it is disabled. + \sa QwtPainter::deviceClipping() +*/ +void QwtPainter::setDeviceClipping(bool enable) +{ + d_deviceClipping = enable; +} + +/*! + Returns rect for device clipping + \sa QwtPainter::setDeviceClipping() +*/ +const QRect &QwtPainter::deviceClipRect() +{ + static QRect clip; + + if ( !clip.isValid() ) + { + clip.setCoords(QWT_COORD_MIN, QWT_COORD_MIN, + QWT_COORD_MAX, QWT_COORD_MAX); + } + return clip; +} + +#if QT_VERSION < 0x040000 + +/*! + \brief En/Disable SVG mode. + + When saving a QPicture to a SVG some texts are misaligned. + In SVGMode QwtPainter tries to fix them. + + \sa QwtPainter::isSVGMode() + \note A QPicture that is created in SVG mode and saved to the + native format, will be misaligned. Also it is not possible to + reload and play a SVG document, that was created in SVG mode. +*/ +void QwtPainter::setSVGMode(bool on) +{ + d_SVGMode = on; +} + +bool QwtPainter::isSVGMode() +{ + return d_SVGMode; +} + +#endif // QT_VERSION < 0x040000 + +/*! + Scale all QwtPainter drawing operations using the ratio + QwtPaintMetrics(from).logicalDpiX() / QwtPaintMetrics(to).logicalDpiX() + and QwtPaintMetrics(from).logicalDpiY() / QwtPaintMetrics(to).logicalDpiY() + + \sa QwtPainter::resetScaleMetrics(), QwtPainter::scaleMetricsX(), + QwtPainter::scaleMetricsY() +*/ +void QwtPainter::setMetricsMap(const QPaintDevice *layout, + const QPaintDevice *device) +{ + d_metricsMap.setMetrics(layout, device); +} + +/*! + Change the metrics map + \sa QwtPainter::resetMetricsMap(), QwtPainter::metricsMap() +*/ +void QwtPainter::setMetricsMap(const QwtMetricsMap &map) +{ + d_metricsMap = map; +} + +/*! + Reset the metrics map to the ratio 1:1 + \sa QwtPainter::setMetricsMap(), QwtPainter::resetMetricsMap() +*/ +void QwtPainter::resetMetricsMap() +{ + d_metricsMap = QwtMetricsMap(); +} + +/*! + \return Metrics map +*/ +const QwtMetricsMap &QwtPainter::metricsMap() +{ + return d_metricsMap; +} + +/*! + Wrapper for QPainter::setClipRect() +*/ +void QwtPainter::setClipRect(QPainter *painter, const QRect &rect) +{ + painter->setClipRect(d_metricsMap.layoutToDevice(rect, painter)); +} + +/*! + Wrapper for QPainter::drawRect() +*/ +void QwtPainter::drawRect(QPainter *painter, int x, int y, int w, int h) +{ + drawRect(painter, QRect(x, y, w, h)); +} + +/*! + Wrapper for QPainter::drawRect() +*/ +void QwtPainter::drawRect(QPainter *painter, const QRect &rect) +{ + const QRect r = d_metricsMap.layoutToDevice(rect, painter); + + QRect clipRect; + const bool deviceClipping = isClippingNeeded(painter, clipRect); + + if ( deviceClipping ) + { + if ( !clipRect.intersects(r) ) + return; + + if ( !clipRect.contains(r) ) + { + fillRect(painter, r & clipRect, painter->brush()); + + int pw = painter->pen().width(); + pw = pw % 2 + pw / 2; + + QwtPolygon pa(5); + pa.setPoint(0, r.left(), r.top()); + pa.setPoint(1, r.right() - pw, r.top()); + pa.setPoint(2, r.right() - pw, r.bottom() - pw); + pa.setPoint(3, r.left(), r.bottom() - pw); + pa.setPoint(4, r.left(), r.top()); + + painter->save(); + painter->setBrush(Qt::NoBrush); + drawPolyline(painter, pa); + painter->restore(); + + return; + } + } + + painter->drawRect(r); +} + +/*! + Wrapper for QPainter::fillRect() +*/ +void QwtPainter::fillRect(QPainter *painter, + const QRect &rect, const QBrush &brush) +{ + if ( !rect.isValid() ) + return; + + QRect clipRect; + const bool deviceClipping = isClippingNeeded(painter, clipRect); + +#if QT_VERSION >= 0x040000 + /* + Performance of Qt4 is horrible for non trivial brushs. Without + clipping expect minutes or hours for repainting large rects + (might result from zooming) + */ + + if ( deviceClipping ) + clipRect &= painter->window(); + else + clipRect = painter->window(); + + if ( painter->hasClipping() ) + clipRect &= painter->clipRegion().boundingRect(); +#endif + + QRect r = d_metricsMap.layoutToDevice(rect, painter); + if ( deviceClipping ) + r = r.intersect(clipRect); + + if ( r.isValid() ) + painter->fillRect(r, brush); +} + +/*! + Wrapper for QPainter::drawPie() +*/ +void QwtPainter::drawPie(QPainter *painter, const QRect &rect, + int a, int alen) +{ + const QRect r = d_metricsMap.layoutToDevice(rect, painter); + + QRect clipRect; + const bool deviceClipping = isClippingNeeded(painter, clipRect); + if ( deviceClipping && !clipRect.contains(r) ) + return; + + painter->drawPie(r, a, alen); +} + +/*! + Wrapper for QPainter::drawEllipse() +*/ +void QwtPainter::drawEllipse(QPainter *painter, const QRect &rect) +{ + QRect r = d_metricsMap.layoutToDevice(rect, painter); + + QRect clipRect; + const bool deviceClipping = isClippingNeeded(painter, clipRect); + + if ( deviceClipping && !clipRect.contains(r) ) + return; + +#if QT_VERSION >= 0x040000 + if ( painter->pen().style() != Qt::NoPen && + painter->pen().color().isValid() ) + { + // Qt4 adds the pen to the rect, Qt3 not. + int pw = painter->pen().width(); + if ( pw == 0 ) + pw = 1; + + r.setWidth(r.width() - pw); + r.setHeight(r.height() - pw); + } +#endif + + painter->drawEllipse(r); +} + +/*! + Wrapper for QPainter::drawText() +*/ +void QwtPainter::drawText(QPainter *painter, int x, int y, + const QString &text) +{ + drawText(painter, QPoint(x, y), text); +} + +/*! + Wrapper for QPainter::drawText() +*/ +void QwtPainter::drawText(QPainter *painter, const QPoint &pos, + const QString &text) +{ + const QPoint p = d_metricsMap.layoutToDevice(pos, painter); + + QRect clipRect; + const bool deviceClipping = isClippingNeeded(painter, clipRect); + + if ( deviceClipping && !clipRect.contains(p) ) + return; + + painter->drawText(p, text); +} + +/*! + Wrapper for QPainter::drawText() +*/ +void QwtPainter::drawText(QPainter *painter, int x, int y, int w, int h, + int flags, const QString &text) +{ + drawText(painter, QRect(x, y, w, h), flags, text); +} + +/*! + Wrapper for QPainter::drawText() +*/ +void QwtPainter::drawText(QPainter *painter, const QRect &rect, + int flags, const QString &text) +{ + QRect textRect = d_metricsMap.layoutToDevice(rect, painter); +#if QT_VERSION < 0x040000 + if ( d_SVGMode && + ( flags == 0 || flags & Qt::AlignVCenter ) + && painter->device()->devType() == QInternal::Picture ) + { + /* + Qt3 misalignes texts, when saving a text + to a SVG image. + */ + textRect.setY(textRect.y() - painter->fontMetrics().height() / 4); + } +#endif + painter->drawText(textRect, flags, text); +} + +#ifndef QT_NO_RICHTEXT + +/*! + Wrapper for QSimpleRichText::draw() +*/ +#if QT_VERSION < 0x040000 + +void QwtPainter::drawSimpleRichText(QPainter *painter, const QRect &rect, + int flags, QSimpleRichText &text) +{ + QColorGroup cg; + cg.setColor(QColorGroup::Text, painter->pen().color()); + + const QRect scaledRect = d_metricsMap.layoutToDevice(rect, painter); + + text.setWidth(painter, scaledRect.width()); + + // QSimpleRichText is Qt::AlignTop by default + + int y = scaledRect.y(); + if (flags & Qt::AlignBottom) + y += (scaledRect.height() - text.height()); + else if (flags & Qt::AlignVCenter) + y += (scaledRect.height() - text.height())/2; + + text.draw(painter, scaledRect.x(), y, scaledRect, cg); +} +#else +void QwtPainter::drawSimpleRichText(QPainter *painter, const QRect &rect, + int flags, QTextDocument &text) +{ + const QRect scaledRect = d_metricsMap.layoutToDevice(rect, painter); + text.setPageSize(QSize(scaledRect.width(), QWIDGETSIZE_MAX)); + + QAbstractTextDocumentLayout* layout = text.documentLayout(); + + const int height = qRound(layout->documentSize().height()); + int y = scaledRect.y(); + if (flags & Qt::AlignBottom) + y += (scaledRect.height() - height); + else if (flags & Qt::AlignVCenter) + y += (scaledRect.height() - height)/2; + + QAbstractTextDocumentLayout::PaintContext context; + context.palette.setColor(QPalette::Text, painter->pen().color()); + + painter->save(); + + painter->translate(scaledRect.x(), y); + layout->draw(painter, context); + + painter->restore(); +} +#endif + +#endif // !QT_NO_RICHTEXT + + +/*! + Wrapper for QPainter::drawLine() +*/ +void QwtPainter::drawLine(QPainter *painter, int x1, int y1, int x2, int y2) +{ + QRect clipRect; + const bool deviceClipping = isClippingNeeded(painter, clipRect); + + if ( deviceClipping && + !(clipRect.contains(x1, y1) && clipRect.contains(x2, y2)) ) + { + QwtPolygon pa(2); + pa.setPoint(0, x1, y1); + pa.setPoint(1, x2, y2); + drawPolyline(painter, pa); + return; + } + + if ( d_metricsMap.isIdentity() ) + { +#if QT_VERSION >= 0x030200 && QT_VERSION < 0x040000 + if ( !painter->device()->isExtDev() ) +#endif + { + painter->drawLine(x1, y1, x2, y2); + return; + } + } + + const QPoint p1 = d_metricsMap.layoutToDevice(QPoint(x1, y1)); + const QPoint p2 = d_metricsMap.layoutToDevice(QPoint(x2, y2)); + +#if QT_VERSION >= 0x030200 && QT_VERSION < 0x040000 + if ( painter->device()->isExtDev() ) + { + // Strange: the postscript driver of QPrinter adds an offset + // of 0.5 to the start/endpoint when using drawLine, but not + // for lines painted with drawLineSegments. + + QwtPolygon pa(2); + pa.setPoint(0, p1); + pa.setPoint(1, p2); + painter->drawLineSegments(pa); + } + else + painter->drawLine(p1, p2); +#else + painter->drawLine(p1, p2); +#endif +} + +/*! + Wrapper for QPainter::drawPolygon() +*/ +void QwtPainter::drawPolygon(QPainter *painter, const QwtPolygon &pa) +{ + QRect clipRect; + const bool deviceClipping = isClippingNeeded(painter, clipRect); + + QwtPolygon cpa = d_metricsMap.layoutToDevice(pa); + if ( deviceClipping ) + { +#ifdef __GNUC__ +#warning clipping ignores painter transformations +#endif + cpa = QwtClipper::clipPolygon(clipRect, cpa); + } + painter->drawPolygon(cpa); +} + +/*! + Wrapper for QPainter::drawPolyline() +*/ +void QwtPainter::drawPolyline(QPainter *painter, const QwtPolygon &pa) +{ + QRect clipRect; + const bool deviceClipping = isClippingNeeded(painter, clipRect); + + QwtPolygon cpa = d_metricsMap.layoutToDevice(pa); + if ( deviceClipping ) + cpa = QwtClipper::clipPolygon(clipRect, cpa); + +#if QT_VERSION >= 0x040000 + bool doSplit = false; + + const QPaintEngine *pe = painter->paintEngine(); + if ( pe && pe->type() == QPaintEngine::Raster && + painter->pen().width() >= 2 ) + { + /* + 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 numPoints = cpa.size(); + const QPoint *points = cpa.data(); + + const int splitSize = 20; + for ( int i = 0; i < numPoints; i += splitSize ) + { + const int n = qwtMin(splitSize + 1, cpa.size() - i); + painter->drawPolyline(points + i, n); + } + } + else +#endif + painter->drawPolyline(cpa); +} + +/*! + Wrapper for QPainter::drawPoint() +*/ + +void QwtPainter::drawPoint(QPainter *painter, int x, int y) +{ + QRect clipRect; + const bool deviceClipping = isClippingNeeded(painter, clipRect); + + const QPoint pos = d_metricsMap.layoutToDevice(QPoint(x, y)); + + if ( deviceClipping && !clipRect.contains(pos) ) + return; + + painter->drawPoint(pos); +} + +void QwtPainter::drawColoredArc(QPainter *painter, const QRect &rect, + int peak, int arc, int interval, const QColor &c1, const QColor &c2) +{ + int h1, s1, v1; + int h2, s2, v2; + +#if QT_VERSION < 0x040000 + c1.hsv(&h1, &s1, &v1); + c2.hsv(&h2, &s2, &v2); +#else + c1.getHsv(&h1, &s1, &v1); + c2.getHsv(&h2, &s2, &v2); +#endif + + arc /= 2; + for ( int angle = -arc; angle < arc; angle += interval) + { + double ratio; + if ( angle >= 0 ) + ratio = 1.0 - angle / double(arc); + else + ratio = 1.0 + angle / double(arc); + + + QColor c; + c.setHsv( h1 + qRound(ratio * (h2 - h1)), + s1 + qRound(ratio * (s2 - s1)), + v1 + qRound(ratio * (v2 - v1)) ); + + painter->setPen(QPen(c, painter->pen().width())); + painter->drawArc(rect, (peak + angle) * 16, interval * 16); + } +} + +void QwtPainter::drawFocusRect(QPainter *painter, QWidget *widget) +{ + drawFocusRect(painter, widget, widget->rect()); +} + +void QwtPainter::drawFocusRect(QPainter *painter, QWidget *widget, + const QRect &rect) +{ +#if QT_VERSION < 0x040000 + widget->style().drawPrimitive(QStyle::PE_FocusRect, painter, + rect, widget->colorGroup()); +#else + QStyleOptionFocusRect opt; + opt.init(widget); + opt.rect = rect; + opt.state |= QStyle::State_HasFocus; + + widget->style()->drawPrimitive(QStyle::PE_FrameFocusRect, + &opt, painter, widget); +#endif + +} + +//! Draw a round frame +#if QT_VERSION < 0x040000 +void QwtPainter::drawRoundFrame(QPainter *painter, const QRect &rect, + int width, const QColorGroup &cg, bool sunken) +#else +void QwtPainter::drawRoundFrame(QPainter *painter, const QRect &rect, + int width, const QPalette &palette, bool sunken) +#endif +{ + +#if QT_VERSION < 0x040000 + QColor c0 = cg.mid(); + QColor c1, c2; + if ( sunken ) + { + c1 = cg.dark(); + c2 = cg.light(); + } + else + { + c1 = cg.light(); + c2 = cg.dark(); + } +#else + QColor c0 = palette.color(QPalette::Mid); + QColor c1, c2; + if ( sunken ) + { + c1 = palette.color(QPalette::Dark); + c2 = palette.color(QPalette::Light); + } + else + { + c1 = palette.color(QPalette::Light); + c2 = palette.color(QPalette::Dark); + } +#endif + + painter->setPen(QPen(c0, width)); + painter->drawArc(rect, 0, 360 * 16); // full + + const int peak = 150; + const int interval = 2; + + if ( c0 != c1 ) + drawColoredArc(painter, rect, peak, 160, interval, c0, c1); + if ( c0 != c2 ) + drawColoredArc(painter, rect, peak + 180, 120, interval, c0, c2); +} + +void QwtPainter::drawColorBar(QPainter *painter, + const QwtColorMap &colorMap, const QwtDoubleInterval &interval, + const QwtScaleMap &scaleMap, Qt::Orientation orientation, + const QRect &rect) +{ +#if QT_VERSION < 0x040000 + QValueVector colorTable; +#else + QVector colorTable; +#endif + if ( colorMap.format() == QwtColorMap::Indexed ) + colorTable = colorMap.colorTable(interval); + + QColor c; + + const QRect devRect = d_metricsMap.layoutToDevice(rect); + + /* + 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(devRect.left(), devRect.right()); + + for ( int x = devRect.left(); x <= devRect.right(); x++ ) + { + const double value = sMap.invTransform(x); + + if ( colorMap.format() == QwtColorMap::RGB ) + c.setRgb(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(devRect.bottom(), devRect.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(); + painter->drawPixmap(devRect, pixmap); +} + +/*! + \brief Scale a pen according to the layout metrics + + The width of non cosmetic pens is scaled from screen to layout metrics, + so that they look similar on paint devices with different resolutions. + + \param pen Unscaled pen + \return Scaled pen +*/ + +QPen QwtPainter::scaledPen(const QPen &pen) +{ +#if QT_VERSION < 0x040300 + return pen; +#else + QPen sPen = pen; + + if ( !pen.isCosmetic() ) + { + int pw = pen.width(); + if ( pw == 0 ) + pw = 1; + + sPen.setWidth(QwtPainter::metricsMap().screenToLayoutX(pw)); + sPen.setCosmetic(true); + } + + return sPen; +#endif +} + diff --git a/qwt/src/qwt_painter.h b/qwt/src/qwt_painter.h new file mode 100644 index 000000000..ce9c74e86 --- /dev/null +++ b/qwt/src/qwt_painter.h @@ -0,0 +1,159 @@ +/* -*- 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 +#include +#include +#include "qwt_global.h" +#include "qwt_layout_metrics.h" +#include "qwt_polygon.h" + +class QPainter; +class QBrush; +class QColor; +class QWidget; +class QwtScaleMap; +class QwtColorMap; +class QwtDoubleInterval; + +#if QT_VERSION < 0x040000 +class QColorGroup; +class QSimpleRichText; +#else +class QPalette; +class QTextDocument; +#endif + +#if defined(Q_WS_X11) +// Warning: QCOORD_MIN, QCOORD_MAX are wrong on X11. +#define QWT_COORD_MAX 16384 +#define QWT_COORD_MIN (-QWT_COORD_MAX - 1) +#else +#define QWT_COORD_MAX 2147483647 +#define QWT_COORD_MIN -QWT_COORD_MAX - 1 +#endif + +/*! + \brief A collection of QPainter workarounds + + 1) Clipping to coordinate system limits (Qt3 only) + + On X11 pixel coordinates are stored in shorts. Qt + produces overruns when mapping QCOORDS to shorts. + + 2) Scaling to device metrics + + QPainter scales fonts, line and fill patterns to the metrics + of the paint device. Other values like the geometries of rects, points + remain device independend. To enable a device independent widget + implementation, QwtPainter adds scaling of these geometries. + (Unfortunately QPainter::scale scales both types of paintings, + so the objects of the first type would be scaled twice). +*/ + +class QWT_EXPORT QwtPainter +{ +public: + static void setMetricsMap(const QPaintDevice *layout, + const QPaintDevice *device); + static void setMetricsMap(const QwtMetricsMap &); + static void resetMetricsMap(); + static const QwtMetricsMap &metricsMap(); + + static void setDeviceClipping(bool); + static bool deviceClipping(); + static const QRect &deviceClipRect(); + + static void setClipRect(QPainter *, const QRect &); + + static void drawText(QPainter *, int x, int y, + const QString &); + static void drawText(QPainter *, const QPoint &, + const QString &); + static void drawText(QPainter *, int x, int y, int w, int h, + int flags, const QString &); + static void drawText(QPainter *, const QRect &, + int flags, const QString &); + +#ifndef QT_NO_RICHTEXT +#if QT_VERSION < 0x040000 + static void drawSimpleRichText(QPainter *, const QRect &, + int flags, QSimpleRichText &); +#else + static void drawSimpleRichText(QPainter *, const QRect &, + int flags, QTextDocument &); +#endif +#endif + + static void drawRect(QPainter *, int x, int y, int w, int h); + static void drawRect(QPainter *, const QRect &rect); + static void fillRect(QPainter *, const QRect &, const QBrush &); + + static void drawEllipse(QPainter *, const QRect &); + static void drawPie(QPainter *, const QRect & r, int a, int alen); + + static void drawLine(QPainter *, int x1, int y1, int x2, int y2); + static void drawLine(QPainter *, const QPoint &p1, const QPoint &p2); + static void drawPolygon(QPainter *, const QwtPolygon &pa); + static void drawPolyline(QPainter *, const QwtPolygon &pa); + static void drawPoint(QPainter *, int x, int y); + +#if QT_VERSION < 0x040000 + static void drawRoundFrame(QPainter *, const QRect &, + int width, const QColorGroup &cg, bool sunken); +#else + static void drawRoundFrame(QPainter *, const QRect &, + int width, const QPalette &, bool sunken); +#endif + static void drawFocusRect(QPainter *, QWidget *); + static void drawFocusRect(QPainter *, QWidget *, const QRect &); + + static void drawColorBar(QPainter *painter, + const QwtColorMap &, const QwtDoubleInterval &, + const QwtScaleMap &, Qt::Orientation, const QRect &); + +#if QT_VERSION < 0x040000 + static void setSVGMode(bool on); + static bool isSVGMode(); +#endif + + static QPen scaledPen(const QPen &); + +private: + static void drawColoredArc(QPainter *, const QRect &, + int peak, int arc, int intervall, const QColor &c1, const QColor &c2); + + static bool d_deviceClipping; + static QwtMetricsMap d_metricsMap; +#if QT_VERSION < 0x040000 + static bool d_SVGMode; +#endif +}; + +//! Wrapper for QPainter::drawLine() +inline void QwtPainter::drawLine(QPainter *painter, + const QPoint &p1, const QPoint &p2) +{ + drawLine(painter, p1.x(), p1.y(), p2.x(), p2.y()); +} + +/*! + Returns whether device clipping is enabled. On X11 the default + is enabled, otherwise it is disabled. + \sa QwtPainter::setDeviceClipping() +*/ +inline bool QwtPainter::deviceClipping() +{ + return d_deviceClipping; +} + +#endif diff --git a/qwt/src/qwt_panner.cpp b/qwt/src/qwt_panner.cpp new file mode 100644 index 000000000..4ed819fde --- /dev/null +++ b/qwt/src/qwt_panner.cpp @@ -0,0 +1,577 @@ +/* -*- 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 +#if QT_VERSION < 0x040000 +#include +#endif +#include "qwt_picker.h" +#include "qwt_array.h" +#include "qwt_panner.h" + +static QwtArray activePickers(QWidget *w) +{ + QwtArray pickers; + +#if QT_VERSION >= 0x040000 + QObjectList children = w->children(); + for ( int i = 0; i < children.size(); i++ ) + { + QObject *obj = children[i]; + if ( obj->inherits("QwtPicker") ) + { + QwtPicker *picker = (QwtPicker *)obj; + if ( picker->isEnabled() ) + pickers += picker; + } + } +#else + QObjectList *children = (QObjectList *)w->children(); + if ( children ) + { + for ( QObjectListIterator it(*children); it.current(); ++it ) + { + QObject *obj = (QObject *)it.current(); + if ( obj->inherits("QwtPicker") ) + { + QwtPicker *picker = (QwtPicker *)obj; + if ( picker->isEnabled() ) + { + pickers.resize(pickers.size() + 1); + pickers[int(pickers.size()) - 1] = picker; + } + } + } + } +#endif + + return pickers; +} + +class QwtPanner::PrivateData +{ +public: + PrivateData(): + button(Qt::LeftButton), + buttonState(Qt::NoButton), + abortKey(Qt::Key_Escape), + abortKeyState(Qt::NoButton), +#ifndef QT_NO_CURSOR + cursor(NULL), + restoreCursor(NULL), + hasCursor(false), +#endif + isEnabled(false) + { +#if QT_VERSION >= 0x040000 + orientations = Qt::Vertical | Qt::Horizontal; +#else + orientations[Qt::Vertical] = true; + orientations[Qt::Horizontal] = true; +#endif + } + + ~PrivateData() + { +#ifndef QT_NO_CURSOR + delete cursor; + delete restoreCursor; +#endif + } + + int button; + int buttonState; + int abortKey; + int abortKeyState; + + QPoint initialPos; + QPoint pos; + + QPixmap pixmap; +#ifndef QT_NO_CURSOR + QCursor *cursor; + QCursor *restoreCursor; + bool hasCursor; +#endif + bool isEnabled; +#if QT_VERSION >= 0x040000 + Qt::Orientations orientations; +#else + bool orientations[2]; +#endif +}; + +/*! + 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(); + +#if QT_VERSION >= 0x040000 + setAttribute(Qt::WA_TransparentForMouseEvents); + setAttribute(Qt::WA_NoSystemBackground); + setFocusPolicy(Qt::NoFocus); +#else + setBackgroundMode(Qt::NoBackground); + setFocusPolicy(QWidget::NoFocus); +#endif + hide(); + + setEnabled(true); +} + +//! Destructor +QwtPanner::~QwtPanner() +{ + delete d_data; +} + +/*! + Change the mouse button + The defaults are Qt::LeftButton and Qt::NoButton +*/ +void QwtPanner::setMouseButton(int button, int buttonState) +{ + d_data->button = button; + d_data->buttonState = buttonState; +} + +//! Get the mouse button +void QwtPanner::getMouseButton(int &button, int &buttonState) const +{ + button = d_data->button; + buttonState = d_data->buttonState; +} + +/*! + Change the abort key + The defaults are Qt::Key_Escape and Qt::NoButton + + \param key Key ( See Qt::Keycode ) + \param state State +*/ +void QwtPanner::setAbortKey(int key, int state) +{ + d_data->abortKey = key; + d_data->abortKeyState = state; +} + +//! Get the abort key +void QwtPanner::getAbortKey(int &key, int &state) const +{ + key = d_data->abortKey; + state = d_data->abortKeyState; +} + +/*! + 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(); + } + } + } +} + +#if QT_VERSION >= 0x040000 +/*! + 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; +} + +#else +void QwtPanner::enableOrientation(Qt::Orientation o, bool enable) +{ + if ( o == Qt::Vertical || o == Qt::Horizontal ) + d_data->orientations[o] = enable; +} +#endif + +/*! + Return true if a orientatio is enabled + \sa orientations(), setOrientations() +*/ +bool QwtPanner::isOrientationEnabled(Qt::Orientation o) const +{ +#if QT_VERSION >= 0x040000 + return d_data->orientations & o; +#else + if ( o == Qt::Vertical || o == Qt::Horizontal ) + return d_data->orientations[o]; + return false; +#endif +} + +/*! + \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) +{ + QPixmap pm(size()); + + QPainter painter(&pm); + + const QColor bg = +#if QT_VERSION < 0x040000 + parentWidget()->palette().color( + QPalette::Normal, QColorGroup::Background); +#else + parentWidget()->palette().color( + QPalette::Normal, QPalette::Background); +#endif + + painter.setPen(Qt::NoPen); + painter.setBrush(QBrush(bg)); + painter.drawRect(0, 0, pm.width(), pm.height()); + + 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)); + + painter.drawPixmap(r, d_data->pixmap); + painter.end(); + + painter.begin(this); + painter.setClipRegion(pe->region()); + painter.drawPixmap(0, 0, pm); +} + +/*! + \brief Event filter + + When isEnabled() the mouse events of the observed widget are filtered. + + \sa widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseMoveEvent() +*/ +bool QwtPanner::eventFilter(QObject *o, QEvent *e) +{ + if ( o == NULL || o != parentWidget() ) + return false; + + switch(e->type()) + { + case QEvent::MouseButtonPress: + { + widgetMousePressEvent((QMouseEvent *)e); + break; + } + case QEvent::MouseMove: + { + widgetMouseMoveEvent((QMouseEvent *)e); + break; + } + case QEvent::MouseButtonRelease: + { + widgetMouseReleaseEvent((QMouseEvent *)e); + break; + } + case QEvent::KeyPress: + { + widgetKeyPressEvent((QKeyEvent *)e); + break; + } + case QEvent::KeyRelease: + { + widgetKeyReleaseEvent((QKeyEvent *)e); + break; + } + case QEvent::Paint: + { + if ( isVisible() ) + return true; + break; + } + default:; + } + + return false; +} + +/*! + Handle a mouse press event for the observed widget. + + \param me Mouse event + \sa eventFilter(), widgetMouseReleaseEvent(), + widgetMouseMoveEvent(), +*/ +void QwtPanner::widgetMousePressEvent(QMouseEvent *me) +{ + if ( me->button() != d_data->button ) + return; + + QWidget *w = parentWidget(); + if ( w == NULL ) + return; + +#if QT_VERSION < 0x040000 + if ( (me->state() & Qt::KeyButtonMask) != + (d_data->buttonState & Qt::KeyButtonMask) ) +#else + if ( (me->modifiers() & Qt::KeyboardModifierMask) != + (int)(d_data->buttonState & Qt::KeyboardModifierMask) ) +#endif + { + return; + } + +#ifndef QT_NO_CURSOR + showCursor(true); +#endif + + d_data->initialPos = d_data->pos = me->pos(); + + QRect cr = parentWidget()->rect(); + if ( parentWidget()->inherits("QFrame") ) + { + const QFrame* frame = (QFrame*)parentWidget(); + cr = frame->contentsRect(); + } + setGeometry(cr); + + // We don't want to grab the picker ! + QwtArray pickers = activePickers(parentWidget()); + for ( int i = 0; i < (int)pickers.size(); i++ ) + pickers[i]->setEnabled(false); + + d_data->pixmap = QPixmap::grabWidget(parentWidget(), + cr.x(), cr.y(), cr.width(), cr.height()); + + for ( int i = 0; i < (int)pickers.size(); i++ ) + pickers[i]->setEnabled(true); + + show(); +} + +/*! + Handle a mouse move event for the observed widget. + + \param me Mouse event + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent() +*/ +void QwtPanner::widgetMouseMoveEvent(QMouseEvent *me) +{ + if ( !isVisible() ) + return; + + QPoint pos = me->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(); + + 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 me Mouse event + \sa eventFilter(), widgetMousePressEvent(), + widgetMouseMoveEvent(), +*/ +void QwtPanner::widgetMouseReleaseEvent(QMouseEvent *me) +{ + if ( isVisible() ) + { + hide(); +#ifndef QT_NO_CURSOR + showCursor(false); +#endif + + QPoint pos = me->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->pos = pos; + + if ( d_data->pos != d_data->initialPos ) + { + 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 ke Key event + \sa eventFilter(), widgetKeyReleaseEvent() +*/ +void QwtPanner::widgetKeyPressEvent(QKeyEvent *ke) +{ + if ( ke->key() == d_data->abortKey ) + { + const bool matched = +#if QT_VERSION < 0x040000 + (ke->state() & Qt::KeyButtonMask) == + (d_data->abortKeyState & Qt::KeyButtonMask); +#else + (ke->modifiers() & Qt::KeyboardModifierMask) == + (int)(d_data->abortKeyState & Qt::KeyboardModifierMask); +#endif + if ( matched ) + { + hide(); +#ifndef QT_NO_CURSOR + showCursor(false); +#endif + d_data->pixmap = QPixmap(); + } + } +} + +/*! + Handle a key release event for the observed widget. + \sa eventFilter(), widgetKeyReleaseEvent() +*/ +void QwtPanner::widgetKeyReleaseEvent(QKeyEvent *) +{ +} + +#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 QT_VERSION < 0x040000 + if ( w->testWState(WState_OwnCursor) ) +#else + if ( w->testAttribute(Qt::WA_SetCursor) ) +#endif + { + 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..13590a5cb --- /dev/null +++ b/qwt/src/qwt_panner.h @@ -0,0 +1,101 @@ +/* -*- 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 +#include +#include "qwt_global.h" + +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 + in in process. 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(int button, int buttonState = Qt::NoButton); + void getMouseButton(int &button, int &buttonState) const; + void setAbortKey(int key, int state = Qt::NoButton); + void getAbortKey(int &key, int &state) const; + + void setCursor(const QCursor &); + const QCursor cursor() const; + +#if QT_VERSION >= 0x040000 + void setOrientations(Qt::Orientations); + Qt::Orientations orientations() const; +#else + void enableOrientation(Qt::Orientation, bool enable); +#endif + + bool isOrientationEnabled(Qt::Orientation) const; + + virtual bool eventFilter(QObject *, QEvent *); + +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 *); + +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..71717e272 --- /dev/null +++ b/qwt/src/qwt_picker.cpp @@ -0,0 +1,1441 @@ +/* -*- 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 +#include +#include "qwt_math.h" +#include "qwt_painter.h" +#include "qwt_picker_machine.h" +#include "qwt_picker.h" +#if QT_VERSION < 0x040000 +#include +#else +#include +#include +#endif + +class QwtPicker::PickerWidget: public QWidget +{ +public: + enum Type + { + RubberBand, + Text + }; + + PickerWidget(QwtPicker *, QWidget *, Type); + virtual void updateMask(); + + /* + For a tracker text with a background we can use the background + rect as mask. Also for "regular" Qt widgets >= 4.3.0 we + don't need to mask the text anymore. + */ + bool d_hasTextMask; + +protected: + virtual void paintEvent(QPaintEvent *); + + QwtPicker *d_picker; + Type d_type; +}; + +class QwtPicker::PrivateData +{ +public: + bool enabled; + + QwtPickerMachine *stateMachine; + + int selectionFlags; + QwtPicker::ResizeMode resizeMode; + + QwtPicker::RubberBand rubberBand; + QPen rubberBandPen; + + QwtPicker::DisplayMode trackerMode; + QPen trackerPen; + QFont trackerFont; + + QwtPolygon selection; + bool isActive; + QPoint trackerPosition; + + bool mouseTracking; // used to save previous value + + /* + On X11 the widget below the picker widgets gets paint events + with a region that is the bounding rect of the mask, if it is complex. + In case of (f.e) a CrossRubberBand and a text this creates complete + repaints of the widget. So we better use two different widgets. + */ + +#if QT_VERSION < 0x040000 + QGuardedPtr rubberBandWidget; + QGuardedPtr trackerWidget; +#else + QPointer rubberBandWidget; + QPointer trackerWidget; +#endif +}; + +QwtPicker::PickerWidget::PickerWidget( + QwtPicker *picker, QWidget *parent, Type type): + QWidget(parent), + d_hasTextMask(false), + d_picker(picker), + d_type(type) +{ +#if QT_VERSION >= 0x040000 + setAttribute(Qt::WA_TransparentForMouseEvents); + setAttribute(Qt::WA_NoSystemBackground); + setFocusPolicy(Qt::NoFocus); +#else + setBackgroundMode(Qt::NoBackground); + setFocusPolicy(QWidget::NoFocus); + setMouseTracking(true); +#endif + hide(); +} + +void QwtPicker::PickerWidget::updateMask() +{ + QRegion mask; + + if ( d_type == RubberBand ) + { + QBitmap bm(width(), height()); + bm.fill(Qt::color0); + + QPainter painter(&bm); + QPen pen = d_picker->rubberBandPen(); + pen.setColor(Qt::color1); + painter.setPen(pen); + + d_picker->drawRubberBand(&painter); + + mask = QRegion(bm); + } + if ( d_type == Text ) + { + d_hasTextMask = true; +#if QT_VERSION >= 0x040300 + if ( !parentWidget()->testAttribute(Qt::WA_PaintOnScreen) ) + { +#if 0 + if ( parentWidget()->paintEngine()->type() != QPaintEngine::OpenGL ) +#endif + { + // With Qt >= 4.3 drawing of the tracker can be implemented in an + // easier way, using the textRect as mask. + + d_hasTextMask = false; + } + } +#endif + + if ( d_hasTextMask ) + { + const QwtText label = d_picker->trackerText( + d_picker->trackerPosition()); + if ( label.testPaintAttribute(QwtText::PaintBackground) + && label.backgroundBrush().style() != Qt::NoBrush ) + { +#if QT_VERSION >= 0x040300 + if ( label.backgroundBrush().color().alpha() > 0 ) +#endif + // We don't need a text mask, when we have a background + d_hasTextMask = false; + } + } + + if ( d_hasTextMask ) + { + QBitmap bm(width(), height()); + bm.fill(Qt::color0); + + QPainter painter(&bm); + painter.setFont(font()); + + QPen pen = d_picker->trackerPen(); + pen.setColor(Qt::color1); + painter.setPen(pen); + + d_picker->drawTracker(&painter); + + mask = QRegion(bm); + } + else + { + mask = d_picker->trackerRect(font()); + } + } + +#if QT_VERSION < 0x040000 + QWidget *w = parentWidget(); + const bool doUpdate = w->isUpdatesEnabled(); + const Qt::BackgroundMode bgMode = w->backgroundMode(); + w->setUpdatesEnabled(false); + if ( bgMode != Qt::NoBackground ) + w->setBackgroundMode(Qt::NoBackground); +#endif + + setMask(mask); + +#if QT_VERSION < 0x040000 + if ( bgMode != Qt::NoBackground ) + w->setBackgroundMode(bgMode); + + w->setUpdatesEnabled(doUpdate); +#endif + + setShown(!mask.isEmpty()); +} + +void QwtPicker::PickerWidget::paintEvent(QPaintEvent *e) +{ + QPainter painter(this); + painter.setClipRegion(e->region()); + + if ( d_type == RubberBand ) + { + painter.setPen(d_picker->rubberBandPen()); + d_picker->drawRubberBand(&painter); + } + + if ( d_type == Text ) + { + /* + If we have a text mask we simply fill the region of + the mask. This gives better results for antialiased fonts. + */ + bool doDrawTracker = !d_hasTextMask; +#if QT_VERSION < 0x040000 + if ( !doDrawTracker && QPainter::redirect(this) ) + { + // setMask + painter redirection doesn't work + doDrawTracker = true; + } +#endif + if ( doDrawTracker ) + { + painter.setPen(d_picker->trackerPen()); + d_picker->drawTracker(&painter); + } + else + painter.fillRect(e->rect(), QBrush(d_picker->trackerPen().color())); + } +} + +/*! + Constructor + + Creates an picker that is enabled, but where selection flag + is set to NoSelection, rubberband and tracker are disabled. + + \param parent Parent widget, that will be observed + */ + +QwtPicker::QwtPicker(QWidget *parent): + QObject(parent) +{ + init(parent, NoSelection, NoRubberBand, AlwaysOff); +} + +/*! + Constructor + + \param selectionFlags Or'd value of SelectionType, RectSelectionType and + SelectionMode + \param rubberBand Rubberband style + \param trackerMode Tracker mode + \param parent Parent widget, that will be observed + */ +QwtPicker::QwtPicker(int selectionFlags, RubberBand rubberBand, + DisplayMode trackerMode, QWidget *parent): + QObject(parent) +{ + init(parent, selectionFlags, rubberBand, trackerMode); +} + +//! Destructor +QwtPicker::~QwtPicker() +{ + setMouseTracking(false); + delete d_data->stateMachine; + delete d_data->rubberBandWidget; + delete d_data->trackerWidget; + delete d_data; +} + +//! Init the picker, used by the constructors +void QwtPicker::init(QWidget *parent, int selectionFlags, + RubberBand rubberBand, DisplayMode trackerMode) +{ + d_data = new PrivateData; + + d_data->rubberBandWidget = NULL; + d_data->trackerWidget = NULL; + + d_data->rubberBand = rubberBand; + d_data->enabled = false; + d_data->resizeMode = Stretch; + d_data->trackerMode = AlwaysOff; + d_data->isActive = false; + d_data->trackerPosition = QPoint(-1, -1); + d_data->mouseTracking = false; + + d_data->stateMachine = NULL; + setSelectionFlags(selectionFlags); + + if ( parent ) + { +#if QT_VERSION >= 0x040000 + if ( parent->focusPolicy() == Qt::NoFocus ) + parent->setFocusPolicy(Qt::WheelFocus); +#else + if ( parent->focusPolicy() == QWidget::NoFocus ) + parent->setFocusPolicy(QWidget::WheelFocus); +#endif + + d_data->trackerFont = parent->font(); + d_data->mouseTracking = parent->hasMouseTracking(); + setEnabled(true); + } + setTrackerMode(trackerMode); +} + +/*! + Set a state machine and delete the previous one +*/ +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(); + } +} + +/*! + Create a state machine depending on the selection flags. + + - PointSelection | ClickSelection\n + QwtPickerClickPointMachine() + - PointSelection | DragSelection\n + QwtPickerDragPointMachine() + - RectSelection | ClickSelection\n + QwtPickerClickRectMachine() + - RectSelection | DragSelection\n + QwtPickerDragRectMachine() + - PolygonSelection\n + QwtPickerPolygonMachine() + + \sa setSelectionFlags() +*/ +QwtPickerMachine *QwtPicker::stateMachine(int flags) const +{ + if ( flags & PointSelection ) + { + if ( flags & ClickSelection ) + return new QwtPickerClickPointMachine; + else + return new QwtPickerDragPointMachine; + } + if ( flags & RectSelection ) + { + if ( flags & ClickSelection ) + return new QwtPickerClickRectMachine; + else + return new QwtPickerDragRectMachine; + } + if ( flags & PolygonSelection ) + { + return new QwtPickerPolygonMachine(); + } + return NULL; +} + +//! Return the parent widget, where the selection happens +QWidget *QwtPicker::parentWidget() +{ + QObject *obj = parent(); + if ( obj && obj->isWidgetType() ) + return (QWidget *)obj; + + return NULL; +} + +//! Return the parent widget, where the selection happens +const QWidget *QwtPicker::parentWidget() const +{ + QObject *obj = parent(); + if ( obj && obj->isWidgetType() ) + return (QWidget *)obj; + + return NULL; +} + +/*! + Set the selection flags + + \param flags Or'd value of SelectionType, RectSelectionType and + SelectionMode. The default value is NoSelection. + + \sa selectionFlags(), SelectionType, RectSelectionType, SelectionMode +*/ + +void QwtPicker::setSelectionFlags(int flags) +{ + d_data->selectionFlags = flags; + setStateMachine(stateMachine(flags)); +} + +/*! + \return Selection flags, an Or'd value of SelectionType, RectSelectionType and + SelectionMode. + \sa setSelectionFlags(), SelectionType, RectSelectionType, SelectionMode +*/ +int QwtPicker::selectionFlags() const +{ + return d_data->selectionFlags; +} + +/*! + Set the rubberband style + + \param rubberBand Rubberband style + The default value is NoRubberBand. + + \sa rubberBand(), RubberBand, setRubberBandPen() +*/ +void QwtPicker::setRubberBand(RubberBand rubberBand) +{ + d_data->rubberBand = rubberBand; +} + +/*! + \return Rubberband 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 Rubberband pen + \sa rubberBandPen(), setRubberBand() +*/ +void QwtPicker::setRubberBandPen(const QPen &pen) +{ + if ( pen != d_data->rubberBandPen ) + { + d_data->rubberBandPen = pen; + updateDisplay(); + } +} + +/*! + \return Rubberband 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; +} + +/*! + Draw a rubberband , depending on rubberBand() and selectionFlags() + + \param painter Painter, initialized with clip rect + + \sa rubberBand(), RubberBand, selectionFlags() +*/ + +void QwtPicker::drawRubberBand(QPainter *painter) const +{ + if ( !isActive() || rubberBand() == NoRubberBand || + rubberBandPen().style() == Qt::NoPen ) + { + return; + } + + const QRect &pRect = pickRect(); + const QwtPolygon &pa = d_data->selection; + + if ( selectionFlags() & PointSelection ) + { + if ( pa.count() < 1 ) + return; + + const QPoint pos = pa[0]; + + 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; + } + } + + else if ( selectionFlags() & RectSelection ) + { + if ( pa.count() < 2 ) + return; + + QPoint p1 = pa[0]; + QPoint p2 = pa[int(pa.count() - 1)]; + + if ( selectionFlags() & CenterToCorner ) + { + p1.setX(p1.x() - (p2.x() - p1.x())); + p1.setY(p1.y() - (p2.y() - p1.y())); + } + else if ( selectionFlags() & CenterToRadius ) + { + const int radius = qwtMax(qwtAbs(p2.x() - p1.x()), + qwtAbs(p2.y() - p1.y())); + p2.setX(p1.x() + radius); + p2.setY(p1.y() + radius); + p1.setX(p1.x() - radius); + p1.setY(p1.y() - radius); + } + +#if QT_VERSION < 0x040000 + const QRect rect = QRect(p1, p2).normalize(); +#else + const QRect rect = QRect(p1, p2).normalized(); +#endif + switch(rubberBand()) + { + case EllipseRubberBand: + QwtPainter::drawEllipse(painter, rect); + break; + case RectRubberBand: + QwtPainter::drawRect(painter, rect); + break; + default: + break; + } + } + else if ( selectionFlags() & PolygonSelection ) + { + if ( rubberBand() == PolygonRubberBand ) + painter->drawPolyline(pa); + } +} + +/*! + Draw the tracker + + \param painter Painter + \sa trackerRect(), trackerText() +*/ + +void QwtPicker::drawTracker(QPainter *painter) const +{ + const QRect textRect = trackerRect(painter->font()); + if ( !textRect.isEmpty() ) + { + QwtText label = trackerText(d_data->trackerPosition); + if ( !label.isEmpty() ) + { + painter->save(); + +#if defined(Q_WS_MAC) + // Antialiased fonts are broken on the Mac. +#if QT_VERSION >= 0x040000 + painter->setRenderHint(QPainter::TextAntialiasing, false); +#else + QFont fnt = label.usedFont(painter->font()); + fnt.setStyleStrategy(QFont::NoAntialias); + label.setFont(fnt); +#endif +#endif + label.draw(painter, textRect); + + painter->restore(); + } + } +} + +//! \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(); + + QRect textRect(QPoint(0, 0), text.textSize(font)); + + const QPoint &pos = d_data->trackerPosition; + + int alignment = 0; + if ( isActive() && d_data->selection.count() > 1 + && rubberBand() != NoRubberBand ) + { + const QPoint last = + d_data->selection[int(d_data->selection.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)); + + int right = qwtMin(textRect.right(), pickRect().right() - margin); + int bottom = qwtMin(textRect.bottom(), pickRect().bottom() - margin); + textRect.moveBottomRight(QPoint(right, bottom)); + + int left = qwtMax(textRect.left(), pickRect().left() + margin); + int top = qwtMax(textRect.top(), pickRect().top() + margin); + textRect.moveTopLeft(QPoint(left, top)); + + return textRect; +} + +/*! + \brief Event filter + + When isEnabled() == 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 + rubberband and tracker up to date. + + \sa event(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +bool QwtPicker::eventFilter(QObject *o, QEvent *e) +{ + if ( o && o == parentWidget() ) + { + switch(e->type()) + { + case QEvent::Resize: + { + const QResizeEvent *re = (QResizeEvent *)e; + if ( d_data->resizeMode == Stretch ) + stretchSelection(re->oldSize(), re->size()); + + if ( d_data->rubberBandWidget ) + d_data->rubberBandWidget->resize(re->size()); + + if ( d_data->trackerWidget ) + d_data->trackerWidget->resize(re->size()); + break; + } + case QEvent::Leave: + widgetLeaveEvent(e); + break; + case QEvent::MouseButtonPress: + widgetMousePressEvent((QMouseEvent *)e); + break; + case QEvent::MouseButtonRelease: + widgetMouseReleaseEvent((QMouseEvent *)e); + break; + case QEvent::MouseButtonDblClick: + widgetMouseDoubleClickEvent((QMouseEvent *)e); + break; + case QEvent::MouseMove: + widgetMouseMoveEvent((QMouseEvent *)e); + break; + case QEvent::KeyPress: + widgetKeyPressEvent((QKeyEvent *)e); + break; + case QEvent::KeyRelease: + widgetKeyReleaseEvent((QKeyEvent *)e); + break; + case QEvent::Wheel: + widgetWheelEvent((QWheelEvent *)e); + break; + default: + break; + } + } + return false; +} + +/*! + Handle a mouse press event for the observed widget. + + Begin and/or end a selection depending on the selection flags. + + \sa QwtPicker, selectionFlags() + \sa eventFilter(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetMousePressEvent(QMouseEvent *e) +{ + transition(e); +} + +/*! + Handle a mouse move event for the observed widget. + + Move the last point of the selection in case of isActive() == true + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetMouseMoveEvent(QMouseEvent *e) +{ + if ( pickRect().contains(e->pos()) ) + d_data->trackerPosition = e->pos(); + else + d_data->trackerPosition = QPoint(-1, -1); + + if ( !isActive() ) + updateDisplay(); + + transition(e); +} + +/*! + Handle a leave event for the observed widget. + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetLeaveEvent(QEvent *) +{ + d_data->trackerPosition = QPoint(-1, -1); + if ( !isActive() ) + updateDisplay(); +} + +/*! + Handle a mouse relase event for the observed widget. + + End a selection depending on the selection flags. + + \sa QwtPicker, selectionFlags() + \sa eventFilter(), widgetMousePressEvent(), + widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetMouseReleaseEvent(QMouseEvent *e) +{ + transition(e); +} + +/*! + Handle mouse double click event for the observed widget. + + Empty implementation, does nothing. + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseMoveEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetMouseDoubleClickEvent(QMouseEvent *me) +{ + transition(me); +} + + +/*! + Handle a wheel event for the observed widget. + + Move the last point of the selection in case of isActive() == true + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), + widgetKeyPressEvent(), widgetKeyReleaseEvent() +*/ +void QwtPicker::widgetWheelEvent(QWheelEvent *e) +{ + if ( pickRect().contains(e->pos()) ) + d_data->trackerPosition = e->pos(); + else + d_data->trackerPosition = QPoint(-1, -1); + + updateDisplay(); + + transition(e); +} + +/*! + 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. + + \sa QwtPicker, selectionFlags() + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), + widgetWheelEvent(), widgetKeyReleaseEvent(), stateMachine(), + QwtEventPattern::KeyPatternCode +*/ +void QwtPicker::widgetKeyPressEvent(QKeyEvent *ke) +{ + int dx = 0; + int dy = 0; + + int offset = 1; + if ( ke->isAutoRepeat() ) + offset = 5; + + if ( keyMatch(KeyLeft, ke) ) + dx = -offset; + else if ( keyMatch(KeyRight, ke) ) + dx = offset; + else if ( keyMatch(KeyUp, ke) ) + dy = -offset; + else if ( keyMatch(KeyDown, ke) ) + dy = offset; + else if ( keyMatch(KeyAbort, ke) ) + { + reset(); + } + else + transition(ke); + + if ( dx != 0 || dy != 0 ) + { + const QRect rect = pickRect(); + const QPoint pos = parentWidget()->mapFromGlobal(QCursor::pos()); + + int x = pos.x() + dx; + x = qwtMax(rect.left(), x); + x = qwtMin(rect.right(), x); + + int y = pos.y() + dy; + y = qwtMax(rect.top(), y); + y = qwtMin(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. + + \sa eventFilter(), widgetMousePressEvent(), widgetMouseReleaseEvent(), + widgetMouseDoubleClickEvent(), widgetMouseMoveEvent(), + widgetWheelEvent(), widgetKeyPressEvent(), stateMachine() +*/ +void QwtPicker::widgetKeyReleaseEvent(QKeyEvent *ke) +{ + transition(ke); +} + +/*! + 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 e Event +*/ +void QwtPicker::transition(const QEvent *e) +{ + if ( !d_data->stateMachine ) + return; + + QwtPickerMachine::CommandList commandList = + d_data->stateMachine->transition(*this, e); + + QPoint pos; + switch(e->type()) + { + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: + { + const QMouseEvent *me = (QMouseEvent *)e; + pos = me->pos(); + break; + } + default: + pos = parentWidget()->mapFromGlobal(QCursor::pos()); + } + + for ( uint i = 0; i < (uint)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::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->selection.resize(0); + d_data->isActive = 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 QwtPicker::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; + + if ( trackerMode() == ActiveOnly ) + d_data->trackerPosition = QPoint(-1, -1); + + if ( ok ) + ok = accept(d_data->selection); + + if ( ok ) + emit selected(d_data->selection); + else + d_data->selection.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 rubberband 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->selection.count(); + d_data->selection.resize(idx + 1); + d_data->selection[idx] = pos; + + updateDisplay(); + + 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->selection.count() - 1; + if ( idx >= 0 ) + { + if ( d_data->selection[idx] != pos ) + { + d_data->selection[idx] = pos; + + updateDisplay(); + + emit moved(pos); + } + } + } +} + +bool QwtPicker::accept(QwtPolygon &) const +{ + 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 Selected points +const QwtPolygon &QwtPicker::selection() const +{ + return d_data->selection; +} + +/*! + 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->selection.count()); i++ ) + { + QPoint &p = d_data->selection[i]; + p.setX(qRound(p.x() * xRatio)); + p.setY(qRound(p.y() * yRatio)); + + emit changed(d_data->selection); + } +} + +/*! + 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 QFrame::contentsRect() if it is a QFrame, QWidget::rect() otherwise. +*/ +QRect QwtPicker::pickRect() const +{ + QRect rect; + + const QWidget *widget = parentWidget(); + if ( !widget ) + return rect; + + if ( widget->inherits("QFrame") ) + rect = ((QFrame *)widget)->contentsRect(); + else + rect = widget->rect(); + + return rect; +} + +//! Update the state of rubberband 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 ) + showTracker = true; + } + } + +#if QT_VERSION < 0x040000 + QGuardedPtr &rw = d_data->rubberBandWidget; +#else + QPointer &rw = d_data->rubberBandWidget; +#endif + if ( showRubberband ) + { + if ( rw.isNull() ) + { + rw = new PickerWidget( this, w, PickerWidget::RubberBand); + rw->resize(w->size()); + } + rw->updateMask(); + rw->update(); // Needed, when the mask doesn't change + } + else + delete rw; + +#if QT_VERSION < 0x040000 + QGuardedPtr &tw = d_data->trackerWidget; +#else + QPointer &tw = d_data->trackerWidget; +#endif + if ( showTracker ) + { + if ( tw.isNull() ) + { + tw = new PickerWidget( this, w, PickerWidget::Text); + tw->resize(w->size()); + } + tw->updateMask(); + tw->update(); // Needed, when the mask doesn't change + } + else + delete tw; +} + +//! \return Widget displaying the rubberband +const QWidget *QwtPicker::rubberBandWidget() const +{ + return d_data->rubberBandWidget; +} + +//! \return Widget displaying the tracker text +const QWidget *QwtPicker::trackerWidget() const +{ + return d_data->trackerWidget; +} + diff --git a/qwt/src/qwt_picker.h b/qwt/src/qwt_picker.h new file mode 100644 index 000000000..ef1edc867 --- /dev/null +++ b/qwt/src/qwt_picker.h @@ -0,0 +1,376 @@ +/* -*- 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 +#include +#include +#include +#include "qwt_global.h" +#include "qwt_text.h" +#include "qwt_polygon.h" +#include "qwt_event_pattern.h" + +class QWidget; +class QMouseEvent; +class QWheelEvent; +class QKeyEvent; +class QwtPickerMachine; + +/*! + \brief QwtPicker provides selections on a widget + + QwtPicker filters all mouse and keyboard events of a widget + and translates them into an array of selected points. Depending + on the QwtPicker::SelectionType the selection might be a single point, + a rectangle or a polygon. The selection process is supported by + optional rubberbands (rubberband selection) and position trackers. + + QwtPicker is useful for widgets where the event handlers + can't be overloaded, like for components of composite widgets. + It offers alternative handlers for mouse and key events. + + \par Example + \verbatim #include + +QwtPicker *picker = new QwtPicker(widget); +picker->setTrackerMode(QwtPicker::ActiveOnly); +connect(picker, SIGNAL(selected(const QwtPolygon &)), ...); + +// emit the position of clicks on widget +picker->setSelectionFlags(QwtPicker::PointSelection | QwtPicker::ClickSelection); + + ... + +// now select rectangles +picker->setSelectionFlags(QwtPicker::RectSelection | QwtPicker::DragSelection); +picker->setRubberBand(QwtPicker::RectRubberBand); \endverbatim\n + + The selection process uses the commands begin(), append(), move() and end(). + append() adds a new point to the selection, move() changes the position of + the latest point. + + The commands are initiated from a small state machine (QwtPickerMachine) + that translates mouse and key events. There are a couple of predefined + state machines for point, rect and polygon selections. The selectionFlags() + control which one should be used. It is possible to use other machines + by overloading stateMachine(). + + The picker is active (isActive()), between begin() and end(). + In active state the rubberband 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 for ClickSelection while the picker is active, + or if trackerMode() is AlwayOn. +*/ + +class QWT_EXPORT QwtPicker: public QObject, public QwtEventPattern +{ + Q_OBJECT + + Q_ENUMS(RubberBand) + Q_ENUMS(DisplayMode) + Q_ENUMS(ResizeMode) + + Q_PROPERTY(int selectionFlags READ selectionFlags WRITE setSelectionFlags) + Q_PROPERTY(DisplayMode trackerMode READ trackerMode WRITE setTrackerMode) + Q_PROPERTY(QFont trackerFont READ trackerFont WRITE setTrackerFont) + Q_PROPERTY(RubberBand rubberBand READ rubberBand WRITE setRubberBand) + Q_PROPERTY(ResizeMode resizeMode READ resizeMode WRITE setResizeMode) + Q_PROPERTY(bool isEnabled READ isEnabled WRITE setEnabled) + + Q_PROPERTY(QPen trackerPen READ trackerPen WRITE setTrackerPen) + Q_PROPERTY(QPen rubberBandPen READ rubberBandPen WRITE setRubberBandPen) + +public: + /*! + This enum type describes the type of a selection. It can be or'd + with QwtPicker::RectSelectionType and QwtPicker::SelectionMode + and passed to QwtPicker::setSelectionFlags() + - NoSelection\n + Selection is disabled. Note this is different to the disabled + state, as you might have a tracker. + - PointSelection\n + Select a single point. + - RectSelection\n + Select a rectangle. + - PolygonSelection\n + Select a polygon. + + The default value is NoSelection. + \sa QwtPicker::setSelectionFlags(), QwtPicker::selectionFlags() + */ + + enum SelectionType + { + NoSelection = 0, + PointSelection = 1, + RectSelection = 2, + PolygonSelection = 4 + }; + + /*! + \brief Selection subtype for RectSelection + This enum type describes the type of rectangle selections. + It can be or'd with QwtPicker::RectSelectionType and + QwtPicker::SelectionMode and passed to QwtPicker::setSelectionFlags(). + - CornerToCorner\n + The first and the second selected point are the corners + of the rectangle. + - CenterToCorner\n + The first point is the center, the second a corner of the + rectangle. + - CenterToRadius\n + The first point is the center of a quadrat, calculated by the maximum + of the x- and y-distance. + + The default value is CornerToCorner. + \sa QwtPicker::setSelectionFlags(), QwtPicker::selectionFlags() + */ + enum RectSelectionType + { + CornerToCorner = 64, + CenterToCorner = 128, + CenterToRadius = 256 + }; + + /*! + Values of this enum type or'd together with a SelectionType value + identifies which state machine should be used for the selection. + + The default value is ClickSelection. + \sa stateMachine() + */ + enum SelectionMode + { + ClickSelection = 1024, + DragSelection = 2048 + }; + + /*! + Rubberband style + - NoRubberBand\n + No rubberband. + - HLineRubberBand & PointSelection\n + A horizontal line. + - VLineRubberBand & PointSelection\n + A vertical line. + - CrossRubberBand & PointSelection\n + A horizontal and a vertical line. + - RectRubberBand & RectSelection\n + A rectangle. + - EllipseRubberBand & RectSelection\n + An ellipse. + - PolygonRubberBand &PolygonSelection\n + A polygon. + - UserRubberBand\n + Values >= UserRubberBand can be used to define additional + rubber bands. + + The default value is NoRubberBand. + \sa QwtPicker::setRubberBand(), QwtPicker::rubberBand() + */ + + enum RubberBand + { + NoRubberBand = 0, + + // Point + HLineRubberBand, + VLineRubberBand, + CrossRubberBand, + + // Rect + RectRubberBand, + EllipseRubberBand, + + // Polygon + PolygonRubberBand, + + UserRubberBand = 100 + }; + + /*! + - AlwaysOff\n + Display never. + - AlwaysOn\n + Display always. + - ActiveOnly\n + Display only when the selection is active. + + \sa QwtPicker::setTrackerMode(), QwtPicker::trackerMode(), + QwtPicker::isActive() + */ + enum DisplayMode + { + AlwaysOff, + AlwaysOn, + ActiveOnly + }; + + /*! + Controls what to do with the selected points of an active + selection when the observed widget is resized. + - Stretch\n + All points are scaled according to the new size, + - KeepSize\n + All points remain unchanged. + + The default value is Stretch. + \sa QwtPicker::setResizeMode(), QwtPicker::resize() + */ + + enum ResizeMode + { + Stretch, + KeepSize + }; + + explicit QwtPicker(QWidget *parent); + explicit QwtPicker(int selectionFlags, RubberBand rubberBand, + DisplayMode trackerMode, QWidget *); + + virtual ~QwtPicker(); + + virtual void setSelectionFlags(int); + int selectionFlags() const; + + virtual void setRubberBand(RubberBand); + RubberBand rubberBand() const; + + virtual void setTrackerMode(DisplayMode); + DisplayMode trackerMode() const; + + virtual void setResizeMode(ResizeMode); + ResizeMode resizeMode() const; + + virtual void setRubberBandPen(const QPen &); + QPen rubberBandPen() const; + + virtual void setTrackerPen(const QPen &); + QPen trackerPen() const; + + virtual void setTrackerFont(const QFont &); + QFont trackerFont() const; + + bool isEnabled() const; + virtual void setEnabled(bool); + + bool isActive() const; + + virtual bool eventFilter(QObject *, QEvent *); + + QWidget *parentWidget(); + const QWidget *parentWidget() const; + + virtual QRect pickRect() const; + const QwtPolygon &selection() const; + + virtual void drawRubberBand(QPainter *) const; + virtual void drawTracker(QPainter *) const; + + virtual QwtText trackerText(const QPoint &pos) const; + QPoint trackerPosition() const; + QRect trackerRect(const QFont &) const; + + +signals: + /*! + A signal emitting the selected points, + at the end of a selection. + + \param pa Selected points + */ + void selected(const QwtPolygon &pa); + + /*! + 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 when the active selection has been changed. + This might happen when the observed widget is resized. + + \param pa Changed selection + \sa stretchSelection() + */ + void changed(const QwtPolygon &pa); + +protected: + /*! + \brief Validate and fixup the selection + + Accepts all selections unmodified + + \param selection Selection to validate and fixup + \return true, when accepted, false otherwise + */ + virtual bool accept(QwtPolygon &selection) const; + + virtual void transition(const QEvent *); + + virtual void begin(); + virtual void append(const QPoint &); + virtual void move(const QPoint &); + virtual bool end(bool ok = true); + + 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 widgetLeaveEvent(QEvent *); + + virtual void stretchSelection(const QSize &oldSize, + const QSize &newSize); + + virtual QwtPickerMachine *stateMachine(int) const; + + virtual void updateDisplay(); + + const QWidget *rubberBandWidget() const; + const QWidget *trackerWidget() const; + +private: + void init(QWidget *, int selectionFlags, RubberBand rubberBand, + DisplayMode trackerMode); + + void setStateMachine(QwtPickerMachine *); + void setMouseTracking(bool); + + class PickerWidget; + 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..ac4edc3b2 --- /dev/null +++ b/qwt/src/qwt_picker_machine.cpp @@ -0,0 +1,371 @@ +/* -*- 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 "qwt_event_pattern.h" +#include "qwt_picker_machine.h" + +//! Constructor +QwtPickerMachine::QwtPickerMachine(): + d_state(0) +{ +} + +//! Destructor +QwtPickerMachine::~QwtPickerMachine() +{ +} + +//! 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); +} + +//! Transition +QwtPickerMachine::CommandList QwtPickerClickPointMachine::transition( + const QwtEventPattern &eventPattern, const QEvent *e) +{ + QwtPickerMachine::CommandList cmdList; + + switch(e->type()) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( + QwtEventPattern::MouseSelect1, (const QMouseEvent *)e) ) + { + cmdList += Begin; + cmdList += Append; + cmdList += End; + } + break; + } + case QEvent::KeyPress: + { + if ( eventPattern.keyMatch( + QwtEventPattern::KeySelect1, (const QKeyEvent *)e) ) + { + cmdList += Begin; + cmdList += Append; + cmdList += End; + } + break; + } + default: + break; + } + + return cmdList; +} + +//! Transition +QwtPickerMachine::CommandList QwtPickerDragPointMachine::transition( + const QwtEventPattern &eventPattern, const QEvent *e) +{ + QwtPickerMachine::CommandList cmdList; + + switch(e->type()) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( + QwtEventPattern::MouseSelect1, (const QMouseEvent *)e) ) + { + 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, (const QKeyEvent *)e) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + setState(1); + } + else + { + cmdList += End; + setState(0); + } + } + break; + } + default: + break; + } + + return cmdList; +} + +//! Transition +QwtPickerMachine::CommandList QwtPickerClickRectMachine::transition( + const QwtEventPattern &eventPattern, const QEvent *e) +{ + QwtPickerMachine::CommandList cmdList; + + switch(e->type()) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( + QwtEventPattern::MouseSelect1, (const QMouseEvent *)e) ) + { + 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, (const QMouseEvent *)e) ) + { + if ( state() == 1 ) + { + cmdList += Append; + setState(2); + } + } + break; + } + case QEvent::KeyPress: + { + if ( eventPattern.keyMatch( + QwtEventPattern::KeySelect1, (const QKeyEvent *)e) ) + { + 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; +} + +//! Transition +QwtPickerMachine::CommandList QwtPickerDragRectMachine::transition( + const QwtEventPattern &eventPattern, const QEvent *e) +{ + QwtPickerMachine::CommandList cmdList; + + switch(e->type()) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( + QwtEventPattern::MouseSelect1, (const QMouseEvent *)e) ) + { + 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, (const QKeyEvent *)e) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + cmdList += Append; + setState(2); + } + else + { + cmdList += End; + setState(0); + } + } + break; + } + default: + break; + } + + return cmdList; +} + +//! Transition +QwtPickerMachine::CommandList QwtPickerPolygonMachine::transition( + const QwtEventPattern &eventPattern, const QEvent *e) +{ + QwtPickerMachine::CommandList cmdList; + + switch(e->type()) + { + case QEvent::MouseButtonPress: + { + if ( eventPattern.mouseMatch( + QwtEventPattern::MouseSelect1, (const QMouseEvent *)e) ) + { + if (state() == 0) + { + cmdList += Begin; + cmdList += Append; + cmdList += Append; + setState(1); + } + else + { + cmdList += End; + setState(0); + } + } + if ( eventPattern.mouseMatch( + QwtEventPattern::MouseSelect2, (const QMouseEvent *)e) ) + { + if (state() == 1) + cmdList += Append; + } + break; + } + case QEvent::MouseMove: + case QEvent::Wheel: + { + if ( state() != 0 ) + cmdList += Move; + break; + } + case QEvent::KeyPress: + { + if ( eventPattern.keyMatch( + QwtEventPattern::KeySelect1, (const QKeyEvent *)e) ) + { + if ( state() == 0 ) + { + cmdList += Begin; + cmdList += Append; + cmdList += Append; + setState(1); + } + else + { + cmdList += End; + setState(0); + } + } + else if ( eventPattern.keyMatch( + QwtEventPattern::KeySelect2, (const QKeyEvent *)e) ) + { + if ( state() == 1 ) + cmdList += Append; + } + break; + } + 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..aadda004d --- /dev/null +++ b/qwt/src/qwt_picker_machine.h @@ -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 + *****************************************************************************/ + +#ifndef QWT_PICKER_MACHINE +#define QWT_PICKER_MACHINE 1 + +#include "qwt_global.h" +#if QT_VERSION < 0x040000 +#include +#else +#include +#endif + +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: + //! Commands - the output of the state machine + enum Command + { + Begin, + Append, + Move, + End + }; + +#if QT_VERSION < 0x040000 + typedef QValueList CommandList; +#else + typedef QList CommandList; +#endif + + virtual ~QwtPickerMachine(); + + //! Transition + virtual CommandList transition( + const QwtEventPattern &, const QEvent *) = 0; + void reset(); + + int state() const; + void setState(int); + +protected: + QwtPickerMachine(); + +private: + int d_state; +}; + +/*! + \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: + virtual CommandList 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: + virtual CommandList 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: + virtual CommandList 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: + virtual CommandList 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: + virtual CommandList transition( + const QwtEventPattern &, const QEvent *); +}; + +#endif diff --git a/qwt/src/qwt_plot.cpp b/qwt/src/qwt_plot.cpp new file mode 100644 index 000000000..8ca6ef8dd --- /dev/null +++ b/qwt/src/qwt_plot.cpp @@ -0,0 +1,861 @@ +/* -*- 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 +#if QT_VERSION < 0x040000 +#include +#include +#else +#include +#include +#endif +#include +#include +#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_dyngrid_layout.h" +#include "qwt_plot_canvas.h" +#include "qwt_paint_buffer.h" + +class QwtPlot::PrivateData +{ +public: +#if QT_VERSION < 0x040000 + QGuardedPtr lblTitle; + QGuardedPtr canvas; + QGuardedPtr legend; +#else + QPointer lblTitle; + QPointer canvas; + QPointer legend; +#endif + 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); +} + +#if QT_VERSION < 0x040000 +/*! + \brief Constructor + \param parent Parent widget + \param name Object name + */ +QwtPlot::QwtPlot(QWidget *parent, const char *name): + QFrame(parent, name) +{ + initPlot(QwtText()); +} +#endif + + +//! Destructor +QwtPlot::~QwtPlot() +{ + detachItems(QwtPlotItem::Rtti_PlotItem, autoDelete()); + + delete d_data->layout; + deleteAxesData(); + delete d_data; +} + +/*! + \brief Initializes a QwtPlot instance + \param title Title text + */ +void QwtPlot::initPlot(const QwtText &title) +{ + d_data = new PrivateData; + +#if QT_VERSION < 0x040000 + setWFlags(Qt::WNoAutoErase); +#endif + + d_data->layout = new QwtPlotLayout; + + d_data->autoReplot = false; + + d_data->lblTitle = new QwtTextLabel(title, this); + d_data->lblTitle->setFont(QFont(fontInfo().family(), 14, QFont::Bold)); + + QwtText text(title); + int flags = Qt::AlignCenter; +#if QT_VERSION < 0x040000 + flags |= Qt::WordBreak | Qt::ExpandTabs; +#else + flags |= Qt::TextWordWrap; +#endif + text.setRenderFlags(flags); + d_data->lblTitle->setText(text); + + d_data->legend = NULL; + + initAxesData(); + + d_data->canvas = new QwtPlotCanvas(this); + d_data->canvas->setFrameStyle(QFrame::Panel|QFrame::Sunken); + d_data->canvas->setLineWidth(2); + d_data->canvas->setMidLineWidth(0); + + updateTabOrder(); + + setSizePolicy(QSizePolicy::MinimumExpanding, + QSizePolicy::MinimumExpanding); +} + +/*! + \brief Adds handling of layout requests +*/ +bool QwtPlot::event(QEvent *e) +{ + bool ok = QFrame::event(e); + switch(e->type()) + { +#if QT_VERSION < 0x040000 + case QEvent::LayoutHint: +#else + case QEvent::LayoutRequest: +#endif + updateLayout(); + break; +#if QT_VERSION >= 0x040000 + case QEvent::PolishRequest: + polish(); + break; +#endif + default:; + } + return ok; +} + +//! Replots the plot if QwtPlot::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. +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->lblTitle->text().text() ) + { + d_data->lblTitle->setText(title); + updateLayout(); + } +} + +/*! + Change the plot's title + \param title New title +*/ +void QwtPlot::setTitle(const QwtText &title) +{ + if ( title != d_data->lblTitle->text() ) + { + d_data->lblTitle->setText(title); + updateLayout(); + } +} + +//! \return the plot's title +QwtText QwtPlot::title() const +{ + return d_data->lblTitle->text(); +} + +//! \return the plot's title +QwtPlotLayout *QwtPlot::plotLayout() +{ + return d_data->layout; +} + +//! \return the plot's titel label. +const QwtPlotLayout *QwtPlot::plotLayout() const +{ + return d_data->layout; +} + +//! \return the plot's titel label. +QwtTextLabel *QwtPlot::titleLabel() +{ + return d_data->lblTitle; +} + +/*! + \return the plot's titel label. +*/ +const QwtTextLabel *QwtPlot::titleLabel() const +{ + return d_data->lblTitle; +} + +/*! + \return the plot's legend + \sa insertLegend() +*/ +QwtLegend *QwtPlot::legend() +{ + return d_data->legend; +} + +/*! + \return the plot's legend + \sa insertLegend() +*/ +const QwtLegend *QwtPlot::legend() const +{ + return d_data->legend; +} + + +/*! + \return the plot's canvas +*/ +QwtPlotCanvas *QwtPlot::canvas() +{ + return d_data->canvas; +} + +/*! + \return the plot's canvas +*/ +const QwtPlotCanvas *QwtPlot::canvas() const +{ + return d_data->canvas; +} + +//! Polish +void QwtPlot::polish() +{ + replot(); + +#if QT_VERSION < 0x040000 + QFrame::polish(); +#endif +} + +/*! + Return sizeHint + \sa minimumSizeHint() +*/ + +QSize QwtPlot::sizeHint() const +{ + int dw = 0; + int dh = 0; + for ( int axisId = 0; axisId < axisCnt; axisId++ ) + { + if ( axisEnabled(axisId) ) + { + const int niceDist = 40; + const QwtScaleWidget *scaleWidget = axisWidget(axisId); + const QwtScaleDiv &scaleDiv = scaleWidget->scaleDraw()->scaleDiv(); + const int majCnt = scaleDiv.ticks(QwtScaleDiv::MajorTick).count(); + + if ( axisId == yLeft || axisId == yRight ) + { + int hDiff = (majCnt - 1) * niceDist + - scaleWidget->minimumSizeHint().height(); + if ( hDiff > dh ) + dh = hDiff; + } + else + { + int wDiff = (majCnt - 1) * niceDist + - scaleWidget->minimumSizeHint().width(); + if ( wDiff > dw ) + 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 setAutoReplot() + \warning Calls canvas()->repaint, take care of infinite recursions +*/ +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. + */ +#if QT_VERSION >= 0x040000 + QApplication::sendPostedEvents(this, QEvent::LayoutRequest); +#else + QApplication::sendPostedEvents(this, QEvent::LayoutHint); +#endif + + d_data->canvas->replot(); + + setAutoReplot(doAutoReplot); +} + +/*! + \brief Adjust plot content to its current size. + \sa resizeEvent() +*/ +void QwtPlot::updateLayout() +{ + d_data->layout->activate(this, contentsRect()); + + // + // resize and show the visible widgets + // + if (!d_data->lblTitle->text().isEmpty()) + { + d_data->lblTitle->setGeometry(d_data->layout->titleRect()); + if (!d_data->lblTitle->isVisible()) + d_data->lblTitle->show(); + } + else + d_data->lblTitle->hide(); + + for (int axisId = 0; axisId < axisCnt; axisId++ ) + { + if (axisEnabled(axisId) ) + { + axisWidget(axisId)->setGeometry(d_data->layout->scaleRect(axisId)); + + if ( axisId == xBottom || axisId == xTop ) + { + QRegion r(d_data->layout->scaleRect(axisId)); + if ( axisEnabled(yLeft) ) + r = r.subtract(QRegion(d_data->layout->scaleRect(yLeft))); + if ( axisEnabled(yRight) ) + r = r.subtract(QRegion(d_data->layout->scaleRect(yRight))); + r.translate(-d_data->layout->scaleRect(axisId).x(), + -d_data->layout->scaleRect(axisId).y()); + + axisWidget(axisId)->setMask(r); + } + if (!axisWidget(axisId)->isVisible()) + axisWidget(axisId)->show(); + } + else + axisWidget(axisId)->hide(); + } + + if ( d_data->legend && + d_data->layout->legendPosition() != ExternalLegend ) + { + if (d_data->legend->itemCount() > 0) + { + d_data->legend->setGeometry(d_data->layout->legendRect()); + d_data->legend->show(); + } + else + d_data->legend->hide(); + } + + d_data->canvas->setGeometry(d_data->layout->canvasRect()); +} + +/*! + Update the focus tab order + + The order is changed so that the canvas will be in front of the + first legend item, or behind the last legend item - depending + on the position of the legend. +*/ + +void QwtPlot::updateTabOrder() +{ +#if QT_VERSION >= 0x040000 + using namespace Qt; // QWidget::NoFocus/Qt::NoFocus +#else + if ( d_data->canvas->focusPolicy() == NoFocus ) + return; +#endif + if ( d_data->legend.isNull() + || d_data->layout->legendPosition() == ExternalLegend + || d_data->legend->legendItems().count() == 0 ) + { + return; + } + + // Depending on the position of the legend the + // tab order will be changed that the canvas is + // next to the last legend item, or before + // the first one. + + const bool canvasFirst = + d_data->layout->legendPosition() == QwtPlot::BottomLegend || + d_data->layout->legendPosition() == QwtPlot::RightLegend; + + QWidget *previous = NULL; + + QWidget *w; +#if QT_VERSION >= 0x040000 + w = d_data->canvas; + while ( ( w = w->nextInFocusChain() ) != d_data->canvas ) +#else + if ( focusData() == NULL ) + return; + + while ( focusData()->next() != d_data->canvas ); + while ( (w = focusData()->next()) != d_data->canvas ) +#endif + { + bool isLegendItem = false; + if ( w->focusPolicy() != NoFocus + && w->parent() && w->parent() == d_data->legend->contentsWidget() ) + { + isLegendItem = true; + } + + if ( canvasFirst ) + { + if ( isLegendItem ) + break; + + previous = w; + } + else + { + if ( isLegendItem ) + previous = w; + else + { + if ( previous ) + break; + } + } + } + + if ( previous && previous != d_data->canvas) + setTabOrder(previous, d_data->canvas); +} + +/*! + 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) +{ + QwtScaleMap maps[axisCnt]; + for ( int axisId = 0; axisId < axisCnt; axisId++ ) + maps[axisId] = canvasMap(axisId); + + drawItems(painter, + d_data->canvas->contentsRect(), maps, QwtPlotPrintFilter()); +} + +/*! + Redraw the canvas items. + \param painter Painter used for drawing + \param rect Bounding rectangle where to paint + \param map QwtPlot::axisCnt maps, mapping between plot and paint device coordinates + \param pfilter Plot print filter +*/ + +void QwtPlot::drawItems(QPainter *painter, const QRect &rect, + const QwtScaleMap map[axisCnt], + const QwtPlotPrintFilter &pfilter) const +{ + const QwtPlotItemList& itmList = itemList(); + for ( QwtPlotItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + QwtPlotItem *item = *it; + if ( item && item->isVisible() ) + { + if ( !(pfilter.options() & QwtPlotPrintFilter::PrintGrid) + && item->rtti() == QwtPlotItem::Rtti_PlotGrid ) + { + continue; + } + + painter->save(); + +#if QT_VERSION >= 0x040000 + painter->setRenderHint(QPainter::Antialiasing, + item->testRenderHint(QwtPlotItem::RenderAntialiased) ); +#endif + + item->draw(painter, + map[item->xAxis()], map[item->yAxis()], + rect); + + painter->restore(); + } + } +} + +/*! + \param axisId 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(int 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 ( axisEnabled(axisId) ) + { + const QwtScaleWidget *s = axisWidget(axisId); + if ( axisId == yLeft || axisId == yRight ) + { + int y = s->y() + s->startBorderDist() - d_data->canvas->y(); + int h = s->height() - s->startBorderDist() - s->endBorderDist(); + map.setPaintInterval(y + h, y); + } + else + { + int x = s->x() + s->startBorderDist() - d_data->canvas->x(); + int w = s->width() - s->startBorderDist() - s->endBorderDist(); + map.setPaintInterval(x, x + w); + } + } + else + { + const int margin = plotLayout()->canvasMargin(axisId); + + const QRect &canvasRect = d_data->canvas->contentsRect(); + if ( axisId == yLeft || axisId == yRight ) + { + map.setPaintInterval(canvasRect.bottom() - margin, + canvasRect.top() + margin); + } + else + { + map.setPaintInterval(canvasRect.left() + margin, + canvasRect.right() - margin); + } + } + return map; +} + +/*! + Change the margin of the plot. The margin is the space + around all components. + + \param margin new margin + \sa QwtPlotLayout::setMargin(), margin(), plotLayout() +*/ +void QwtPlot::setMargin(int margin) +{ + if ( margin < 0 ) + margin = 0; + + if ( margin != d_data->layout->margin() ) + { + d_data->layout->setMargin(margin); + updateLayout(); + } +} + +/*! + \return margin + \sa setMargin(), QwtPlotLayout::margin(), plotLayout() +*/ +int QwtPlot::margin() const +{ + return d_data->layout->margin(); +} + +/*! + \brief Change the background of the plotting area + + Sets c to QColorGroup::Background of all colorgroups of + the palette of the canvas. Using canvas()->setPalette() + is a more powerful way to set these colors. + \param c new background color +*/ +void QwtPlot::setCanvasBackground(const QColor &c) +{ + QPalette p = d_data->canvas->palette(); + + for ( int i = 0; i < QPalette::NColorGroups; i++ ) + { +#if QT_VERSION < 0x040000 + p.setColor((QPalette::ColorGroup)i, QColorGroup::Background, c); +#else + p.setColor((QPalette::ColorGroup)i, QPalette::Background, c); +#endif + } + + canvas()->setPalette(p); +} + +/*! + Nothing else than: canvas()->palette().color( + QPalette::Normal, QColorGroup::Background); + + \return the background color of the plotting area. +*/ +const QColor & QwtPlot::canvasBackground() const +{ +#if QT_VERSION < 0x040000 + return canvas()->palette().color( + QPalette::Normal, QColorGroup::Background); +#else + return canvas()->palette().color( + QPalette::Normal, QPalette::Background); +#endif +} + +/*! + \brief Change the border width of the plotting area + Nothing else than canvas()->setLineWidth(w), + left for compatibility only. + \param w new border width +*/ +void QwtPlot::setCanvasLineWidth(int w) +{ + canvas()->setLineWidth(w); + updateLayout(); +} + +/*! + Nothing else than: canvas()->lineWidth(), + left for compatibility only. + \return the border width of the plotting area +*/ +int QwtPlot::canvasLineWidth() const +{ + return canvas()->lineWidth(); +} + +/*! + \return \c true if the specified axis exists, otherwise \c false + \param axisId axis index + */ +bool QwtPlot::axisValid(int axisId) +{ + return ((axisId >= QwtPlot::yLeft) && (axisId < QwtPlot::axisCnt)); +} + +/*! + Called internally when the legend has been clicked on. + Emits a legendClicked() signal. +*/ +void QwtPlot::legendItemClicked() +{ + if ( d_data->legend && sender()->isWidgetType() ) + { + QwtPlotItem *plotItem = + (QwtPlotItem*)d_data->legend->find((QWidget *)sender()); + if ( plotItem ) + emit legendClicked(plotItem); + } +} + +/*! + Called internally when the legend has been checked + Emits a legendClicked() signal. +*/ +void QwtPlot::legendItemChecked(bool on) +{ + if ( d_data->legend && sender()->isWidgetType() ) + { + QwtPlotItem *plotItem = + (QwtPlotItem*)d_data->legend->find((QWidget *)sender()); + if ( plotItem ) + emit legendChecked(plotItem, on); + } +} + +/*! + Remove all curves and markers + \deprecated Use QwtPlotDeict::detachItems instead +*/ +void QwtPlot::clear() +{ + detachItems(QwtPlotItem::Rtti_PlotCurve); + detachItems(QwtPlotItem::Rtti_PlotMarker); +} + +/*! + \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. + + If pos != QwtPlot::ExternalLegend the plot widget will become + parent of the legend. It will be deleted when the plot is deleted, + or another legend is set with insertLegend(). + + \param legend Legend + \param pos The legend's position. For top/left position the number + of colums will be limited to 1, otherwise it will be set to + unlimited. + + \param ratio Ratio between legend and the bounding rect + of title, canvas and axes. The legend will be shrinked + 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(QwtLegend *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 ) + { + if ( pos != ExternalLegend ) + { + if ( d_data->legend->parent() != this ) + { +#if QT_VERSION < 0x040000 + d_data->legend->reparent(this, QPoint(0, 0)); +#else + d_data->legend->setParent(this); +#endif + } + } + + const QwtPlotItemList& itmList = itemList(); + for ( QwtPlotItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + (*it)->updateLegend(d_data->legend); + } + + QLayout *l = d_data->legend->contentsWidget()->layout(); + if ( l && l->inherits("QwtDynGridLayout") ) + { + QwtDynGridLayout *tl = (QwtDynGridLayout *)l; + switch(d_data->layout->legendPosition()) + { + case LeftLegend: + case RightLegend: + tl->setMaxCols(1); // 1 column: align vertical + break; + case TopLegend: + case BottomLegend: + tl->setMaxCols(0); // unlimited + break; + case ExternalLegend: + break; + } + } + } + updateTabOrder(); + } + + updateLayout(); +} diff --git a/qwt/src/qwt_plot.h b/qwt/src/qwt_plot.h new file mode 100644 index 000000000..42a641158 --- /dev/null +++ b/qwt/src/qwt_plot.h @@ -0,0 +1,324 @@ +/* -*- 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 +#include "qwt_global.h" +#include "qwt_array.h" +#include "qwt_text.h" +#include "qwt_plot_dict.h" +#include "qwt_scale_map.h" +#include "qwt_plot_printfilter.h" + +class QwtPlotLayout; +class QwtLegend; +class QwtScaleWidget; +class QwtScaleEngine; +class QwtScaleDiv; +class QwtScaleDraw; +class QwtTextLabel; +class QwtPlotCanvas; +class QwtPlotPrintFilter; + +/*! + \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 explicitely set (QwtScaleDiv), or + are calculated from the plot items, using algorithms (QwtScaleEngine) which + can be configured separately for each axis. + + \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"); + +// copy the data into 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( QString propertiesDocument + READ grabProperties WRITE applyProperties ) + +public: + /*! + Axis index + + - yLeft\n + - yRight\n + - xBottom\n + - xTop\n + */ + enum Axis + { + yLeft, + yRight, + xBottom, + xTop, + + axisCnt + }; + + /*! + Position of the legend, relative to the canvas. + + - LeftLegend\n + The legend will be left from the yLeft axis. + - RightLegend\n + The legend will be right from the yLeft axis. + - BottomLegend\n + The legend will be right below the xBottom axis. + - TopLegend\n + The legend will be between xTop axis and the title. + - ExternalLegend\n + External means that only the content of the legend + will be handled by QwtPlot, but not its geometry. + This might be interesting if an application wants to + have a legend in an external window ( or on the canvas ). + + \note In case of ExternalLegend, the legend is not + printed by print(). + + \sa insertLegend() + */ + enum LegendPosition + { + LeftLegend, + RightLegend, + BottomLegend, + TopLegend, + + ExternalLegend + }; + + explicit QwtPlot(QWidget * = NULL); + explicit QwtPlot(const QwtText &title, QWidget *p = NULL); +#if QT_VERSION < 0x040000 + explicit QwtPlot(QWidget *, const char* name); +#endif + + virtual ~QwtPlot(); + + void applyProperties(const QString &); + QString grabProperties() const; + + void setAutoReplot(bool tf = true); + bool autoReplot() const; + + void print(QPaintDevice &p, + const QwtPlotPrintFilter & = QwtPlotPrintFilter()) const; + virtual void print(QPainter *, const QRect &rect, + const QwtPlotPrintFilter & = QwtPlotPrintFilter()) const; + + // Layout + + QwtPlotLayout *plotLayout(); + const QwtPlotLayout *plotLayout() const; + + void setMargin(int margin); + int margin() const; + + // Title + + void setTitle(const QString &); + void setTitle(const QwtText &t); + QwtText title() const; + + QwtTextLabel *titleLabel(); + const QwtTextLabel *titleLabel() const; + + // Canvas + + QwtPlotCanvas *canvas(); + const QwtPlotCanvas *canvas() const; + + void setCanvasBackground (const QColor &c); + const QColor& canvasBackground() const; + + void setCanvasLineWidth(int w); + int canvasLineWidth() const; + + virtual QwtScaleMap canvasMap(int axisId) const; + + double invTransform(int axisId, int pos) const; + int transform(int axisId, double value) const; + + // Axes + + QwtScaleEngine *axisScaleEngine(int axisId); + const QwtScaleEngine *axisScaleEngine(int axisId) const; + void setAxisScaleEngine(int axisId, QwtScaleEngine *); + + void setAxisAutoScale(int axisId); + bool axisAutoScale(int axisId) const; + + void enableAxis(int axisId, bool tf = true); + bool axisEnabled(int axisId) const; + + void setAxisFont(int axisId, const QFont &f); + QFont axisFont(int axisId) const; + + void setAxisScale(int axisId, double min, double max, double step = 0); + void setAxisScaleDiv(int axisId, const QwtScaleDiv &); + void setAxisScaleDraw(int axisId, QwtScaleDraw *); + + double axisStepSize(int axisId) const; + + const QwtScaleDiv *axisScaleDiv(int axisId) const; + QwtScaleDiv *axisScaleDiv(int axisId); + + const QwtScaleDraw *axisScaleDraw(int axisId) const; + QwtScaleDraw *axisScaleDraw(int axisId); + + const QwtScaleWidget *axisWidget(int axisId) const; + QwtScaleWidget *axisWidget(int axisId); + +#if QT_VERSION < 0x040000 + void setAxisLabelAlignment(int axisId, int); +#else + void setAxisLabelAlignment(int axisId, Qt::Alignment); +#endif + void setAxisLabelRotation(int axisId, double rotation); + + void setAxisTitle(int axisId, const QString &); + void setAxisTitle(int axisId, const QwtText &); + QwtText axisTitle(int axisId) const; + + void setAxisMaxMinor(int axisId, int maxMinor); + int axisMaxMajor(int axisId) const; + void setAxisMaxMajor(int axisId, int maxMajor); + int axisMaxMinor(int axisId) const; + + // Legend + + void insertLegend(QwtLegend *, LegendPosition = QwtPlot::RightLegend, + double ratio = -1.0); + + QwtLegend *legend(); + const QwtLegend *legend() const; + + // Misc + + virtual void polish(); + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + virtual void updateLayout(); + virtual void drawCanvas(QPainter *); + + void updateAxes(); + + virtual bool event(QEvent *); + +signals: + /*! + A signal which is emitted when the user has clicked on + a legend item, which is in QwtLegend::ClickableItem mode. + + \param plotItem Corresponding plot item of the + selected legend item + + \note clicks are disabled as default + \sa QwtLegend::setItemMode(), QwtLegend::itemMode() + */ + void legendClicked(QwtPlotItem *plotItem); + + /*! + A signal which is emitted when the user has clicked on + a legend item, which is in QwtLegend::CheckableItem mode + + \param plotItem Corresponding plot item of the + selected legend item + \param on True when the legen item is checked + + \note clicks are disabled as default + \sa QwtLegend::setItemMode(), QwtLegend::itemMode() + */ + + void legendChecked(QwtPlotItem *plotItem, bool on); + +public slots: + virtual void clear(); + + virtual void replot(); + void autoRefresh(); + +protected slots: + virtual void legendItemClicked(); + virtual void legendItemChecked(bool); + +protected: + static bool axisValid(int axisId); + + virtual void drawItems(QPainter *, const QRect &, + const QwtScaleMap maps[axisCnt], + const QwtPlotPrintFilter &) const; + + virtual void updateTabOrder(); + + virtual void resizeEvent(QResizeEvent *e); + + virtual void printLegendItem(QPainter *, + const QWidget *, const QRect &) const; + + virtual void printTitle(QPainter *, const QRect &) const; + + virtual void printScale(QPainter *, int axisId, int startDist, int endDist, + int baseDist, const QRect &) const; + + virtual void printCanvas(QPainter *, + const QRect &boundingRect, const QRect &canvasRect, + const QwtScaleMap maps[axisCnt], const QwtPlotPrintFilter &) const; + + virtual void printLegend(QPainter *, const QRect &) const; + +private: + void initAxesData(); + void deleteAxesData(); + void updateScaleDiv(); + + void initPlot(const QwtText &title); + + class AxisData; + AxisData *d_axisData[axisCnt]; + + 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..e85c5abe0 --- /dev/null +++ b/qwt/src/qwt_plot_axis.cpp @@ -0,0 +1,653 @@ +/* -*- 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 QwtPlot::AxisData +{ +public: + bool isEnabled; + bool doAutoScale; + + double minValue; + double maxValue; + double stepSize; + + int maxMajor; + int maxMinor; + + QwtScaleDiv scaleDiv; + QwtScaleEngine *scaleEngine; + QwtScaleWidget *scaleWidget; +}; + +//! Initialize axes +void QwtPlot::initAxesData() +{ + int axisId; + + for( axisId = 0; axisId < axisCnt; axisId++) + d_axisData[axisId] = new AxisData; + + d_axisData[yLeft]->scaleWidget = + new QwtScaleWidget(QwtScaleDraw::LeftScale, this); + d_axisData[yRight]->scaleWidget = + new QwtScaleWidget(QwtScaleDraw::RightScale, this); + d_axisData[xTop]->scaleWidget = + new QwtScaleWidget(QwtScaleDraw::TopScale, this); + d_axisData[xBottom]->scaleWidget = + new QwtScaleWidget(QwtScaleDraw::BottomScale, this); + + + QFont fscl(fontInfo().family(), 10); + QFont fttl(fontInfo().family(), 12, QFont::Bold); + + for(axisId = 0; axisId < axisCnt; axisId++) + { + AxisData &d = *d_axisData[axisId]; + + d.scaleWidget->setFont(fscl); + d.scaleWidget->setMargin(2); + + QwtText text = d.scaleWidget->title(); + text.setFont(fttl); + d.scaleWidget->setTitle(text); + + d.doAutoScale = true; + + d.minValue = 0.0; + d.maxValue = 1000.0; + d.stepSize = 0.0; + + d.maxMinor = 5; + d.maxMajor = 8; + + d.scaleEngine = new QwtLinearScaleEngine; + + d.scaleDiv.invalidate(); + } + + d_axisData[yLeft]->isEnabled = true; + d_axisData[yRight]->isEnabled = false; + d_axisData[xBottom]->isEnabled = true; + d_axisData[xTop]->isEnabled = false; +} + +void QwtPlot::deleteAxesData() +{ + for( int axisId = 0; axisId < axisCnt; axisId++) + { + delete d_axisData[axisId]->scaleEngine; + delete d_axisData[axisId]; + d_axisData[axisId] = NULL; + } +} + +/*! + \return specified axis, or NULL if axisId is invalid. + \param axisId axis index +*/ +const QwtScaleWidget *QwtPlot::axisWidget(int axisId) const +{ + if (axisValid(axisId)) + return d_axisData[axisId]->scaleWidget; + + return NULL; +} + +/*! + \return specified axis, or NULL if axisId is invalid. + \param axisId axis index +*/ +QwtScaleWidget *QwtPlot::axisWidget(int axisId) +{ + if (axisValid(axisId)) + return d_axisData[axisId]->scaleWidget; + + return NULL; +} + +/*! + Change the scale engine for an axis + + \param axisId axis index + \param scaleEngine Scale engine + + \sa axisScaleEngine() +*/ +void QwtPlot::setAxisScaleEngine(int axisId, QwtScaleEngine *scaleEngine) +{ + if (axisValid(axisId) && scaleEngine != NULL ) + { + AxisData &d = *d_axisData[axisId]; + + delete d.scaleEngine; + d.scaleEngine = scaleEngine; + + d.scaleDiv.invalidate(); + + autoRefresh(); + } +} + +/*! + \param axisId axis index + \return Scale engine for a specific axis +*/ +QwtScaleEngine *QwtPlot::axisScaleEngine(int axisId) +{ + if (axisValid(axisId)) + return d_axisData[axisId]->scaleEngine; + else + return NULL; +} + +/*! + \param axisId axis index + \return Scale engine for a specific axis +*/ +const QwtScaleEngine *QwtPlot::axisScaleEngine(int axisId) const +{ + if (axisValid(axisId)) + return d_axisData[axisId]->scaleEngine; + else + return NULL; +} +/*! + \return \c true if autoscaling is enabled + \param axisId axis index +*/ +bool QwtPlot::axisAutoScale(int axisId) const +{ + if (axisValid(axisId)) + return d_axisData[axisId]->doAutoScale; + else + return false; + +} + +/*! + \return \c true if a specified axis is enabled + \param axisId axis index +*/ +bool QwtPlot::axisEnabled(int axisId) const +{ + if (axisValid(axisId)) + return d_axisData[axisId]->isEnabled; + else + return false; +} + +/*! + \return the font of the scale labels for a specified axis + \param axisId axis index +*/ +QFont QwtPlot::axisFont(int axisId) const +{ + if (axisValid(axisId)) + return axisWidget(axisId)->font(); + else + return QFont(); + +} + +/*! + \return the maximum number of major ticks for a specified axis + \param axisId axis index + sa setAxisMaxMajor() +*/ +int QwtPlot::axisMaxMajor(int axisId) const +{ + if (axisValid(axisId)) + return d_axisData[axisId]->maxMajor; + else + return 0; +} + +/*! + \return the maximum number of minor ticks for a specified axis + \param axisId axis index + sa setAxisMaxMinor() +*/ +int QwtPlot::axisMaxMinor(int axisId) const +{ + if (axisValid(axisId)) + return d_axisData[axisId]->maxMinor; + else + return 0; +} + +/*! + \brief Return the scale division of a specified axis + + axisScaleDiv(axisId)->lowerBound(), axisScaleDiv(axisId)->upperBound() + are the current limits of the axis scale. + + \param axisId axis index + \return Scale division + + \sa QwtScaleDiv, setAxisScaleDiv() +*/ +const QwtScaleDiv *QwtPlot::axisScaleDiv(int axisId) const +{ + if (!axisValid(axisId)) + return NULL; + + return &d_axisData[axisId]->scaleDiv; +} + +/*! + \brief Return the scale division of a specified axis + + axisScaleDiv(axisId)->lowerBound(), axisScaleDiv(axisId)->upperBound() + are the current limits of the axis scale. + + \param axisId axis index + \return Scale division + + \sa QwtScaleDiv, setAxisScaleDiv() +*/ +QwtScaleDiv *QwtPlot::axisScaleDiv(int axisId) +{ + if (!axisValid(axisId)) + return NULL; + + return &d_axisData[axisId]->scaleDiv; +} + +/*! + \returns the scale draw of a specified axis + \param axisId axis index + \return specified scaleDraw for axis, or NULL if axis is invalid. + \sa QwtScaleDraw +*/ +const QwtScaleDraw *QwtPlot::axisScaleDraw(int axisId) const +{ + if (!axisValid(axisId)) + return NULL; + + return axisWidget(axisId)->scaleDraw(); +} + +/*! + \returns the scale draw of a specified axis + \param axisId axis index + \return specified scaleDraw for axis, or NULL if axis is invalid. + \sa QwtScaleDraw +*/ +QwtScaleDraw *QwtPlot::axisScaleDraw(int axisId) +{ + if (!axisValid(axisId)) + return NULL; + + return axisWidget(axisId)->scaleDraw(); +} + +/*! + 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 axisId axis index + \return step size parameter value + + \sa setAxisScale() +*/ +double QwtPlot::axisStepSize(int axisId) const +{ + if (!axisValid(axisId)) + return 0; + + return d_axisData[axisId]->stepSize; +} + +/*! + \return the title of a specified axis + \param axisId axis index +*/ +QwtText QwtPlot::axisTitle(int axisId) const +{ + if (axisValid(axisId)) + return axisWidget(axisId)->title(); + else + return QwtText(); +} + +/*! + \brief Enable or disable a specified axis + + When an axis is disabled, this only means that it is not + visible on the screen. Curves, markers and can be attached + to disabled axes, and transformation of screen coordinates + into values works as normal. + + Only xBottom and yLeft are enabled by default. + \param axisId axis index + \param tf \c true (enabled) or \c false (disabled) +*/ +void QwtPlot::enableAxis(int axisId, bool tf) +{ + if (axisValid(axisId) && tf != d_axisData[axisId]->isEnabled) + { + d_axisData[axisId]->isEnabled = tf; + updateLayout(); + } +} + +/*! + Transform the x or y coordinate of a position in the + drawing region into a value. + \param axisId axis index + \param pos position + \warning The position can be an x or a y coordinate, + depending on the specified axis. +*/ +double QwtPlot::invTransform(int axisId, int pos) const +{ + if (axisValid(axisId)) + return(canvasMap(axisId).invTransform(pos)); + else + return 0.0; +} + + +/*! + \brief Transform a value into a coordinate in the plotting region + \param axisId axis index + \param value value + \return X or y coordinate in the plotting region corresponding + to the value. +*/ +int QwtPlot::transform(int axisId, double value) const +{ + if (axisValid(axisId)) + return(canvasMap(axisId).transform(value)); + else + return 0; + +} + +/*! + \brief Change the font of an axis + \param axisId axis index + \param f font + \warning This function changes the font of the tick labels, + not of the axis title. +*/ +void QwtPlot::setAxisFont(int axisId, const QFont &f) +{ + if (axisValid(axisId)) + axisWidget(axisId)->setFont(f); +} + +/*! + \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 axisId axis index + \sa QwtPlot::setAxisScale(), QwtPlot::setAxisScaleDiv() +*/ +void QwtPlot::setAxisAutoScale(int axisId) +{ + if (axisValid(axisId) && !d_axisData[axisId]->doAutoScale ) + { + d_axisData[axisId]->doAutoScale = true; + autoRefresh(); + } +} + +/*! + \brief Disable autoscaling and specify a fixed scale for a selected axis. + \param axisId axis index + \param min + \param max minimum and 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() +*/ +void QwtPlot::setAxisScale(int axisId, double min, double max, double stepSize) +{ + if (axisValid(axisId)) + { + AxisData &d = *d_axisData[axisId]; + + d.doAutoScale = false; + d.scaleDiv.invalidate(); + + d.minValue = min; + d.maxValue = max; + d.stepSize = stepSize; + + autoRefresh(); + } +} + +/*! + \brief Disable autoscaling and specify a fixed scale for a selected axis. + \param axisId axis index + \param scaleDiv Scale division + \sa setAxisScale(), setAxisAutoScale() +*/ +void QwtPlot::setAxisScaleDiv(int axisId, const QwtScaleDiv &scaleDiv) +{ + if (axisValid(axisId)) + { + AxisData &d = *d_axisData[axisId]; + + d.doAutoScale = false; + d.scaleDiv = scaleDiv; + + autoRefresh(); + } +} + +/*! + \brief Set a scale draw + \param axisId axis index + \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(int axisId, QwtScaleDraw *scaleDraw) +{ + if (axisValid(axisId)) + { + axisWidget(axisId)->setScaleDraw(scaleDraw); + autoRefresh(); + } +} + +/*! + Change the alignment of the tick labels + \param axisId axis index + \param alignment Or'd Qt::AlignmentFlags + \sa QwtScaleDraw::setLabelAlignment() +*/ +#if QT_VERSION < 0x040000 +void QwtPlot::setAxisLabelAlignment(int axisId, int alignment) +#else +void QwtPlot::setAxisLabelAlignment(int axisId, Qt::Alignment alignment) +#endif +{ + if (axisValid(axisId)) + axisWidget(axisId)->setLabelAlignment(alignment); +} + +/*! + Rotate all tick labels + \param axisId axis index + \param rotation Angle in degrees. When changing the label rotation, + the label alignment might be adjusted too. + \sa QwtScaleDraw::setLabelRotation(), setAxisLabelAlignment() +*/ +void QwtPlot::setAxisLabelRotation(int axisId, double rotation) +{ + if (axisValid(axisId)) + axisWidget(axisId)->setLabelRotation(rotation); +} + +/*! + Set the maximum number of minor scale intervals for a specified axis + + \param axisId axis index + \param maxMinor maximum number of minor steps + \sa axisMaxMinor() +*/ +void QwtPlot::setAxisMaxMinor(int axisId, int maxMinor) +{ + if (axisValid(axisId)) + { + if ( maxMinor < 0 ) + maxMinor = 0; + if ( maxMinor > 100 ) + maxMinor = 100; + + AxisData &d = *d_axisData[axisId]; + + if ( maxMinor != d.maxMinor ) + { + d.maxMinor = maxMinor; + d.scaleDiv.invalidate(); + autoRefresh(); + } + } +} + +/*! + Set the maximum number of major scale intervals for a specified axis + + \param axisId axis index + \param maxMajor maximum number of major steps + \sa axisMaxMajor() +*/ +void QwtPlot::setAxisMaxMajor(int axisId, int maxMajor) +{ + if (axisValid(axisId)) + { + if ( maxMajor < 1 ) + maxMajor = 1; + if ( maxMajor > 1000 ) + maxMajor = 10000; + + AxisData &d = *d_axisData[axisId]; + if ( maxMajor != d.maxMajor ) + { + d.maxMajor = maxMajor; + d.scaleDiv.invalidate(); + autoRefresh(); + } + } +} + +/*! + \brief Change the title of a specified axis + \param axisId axis index + \param title axis title +*/ +void QwtPlot::setAxisTitle(int axisId, const QString &title) +{ + if (axisValid(axisId)) + axisWidget(axisId)->setTitle(title); +} + +/*! + \brief Change the title of a specified axis + \param axisId axis index + \param title axis title +*/ +void QwtPlot::setAxisTitle(int axisId, const QwtText &title) +{ + if (axisValid(axisId)) + axisWidget(axisId)->setTitle(title); +} + +//! Rebuild the scales +void QwtPlot::updateAxes() +{ + // Find bounding interval of the item data + // for all axes, where autoscaling is enabled + + QwtDoubleInterval intv[axisCnt]; + + 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 ( axisAutoScale(item->xAxis()) || axisAutoScale(item->yAxis()) ) + { + const QwtDoubleRect rect = item->boundingRect(); + intv[item->xAxis()] |= QwtDoubleInterval(rect.left(), rect.right()); + intv[item->yAxis()] |= QwtDoubleInterval(rect.top(), rect.bottom()); + } + } + + // Adjust scales + + for (int axisId = 0; axisId < axisCnt; axisId++) + { + AxisData &d = *d_axisData[axisId]; + + double minValue = d.minValue; + double maxValue = d.maxValue; + double stepSize = d.stepSize; + + if ( d.doAutoScale && intv[axisId].isValid() ) + { + d.scaleDiv.invalidate(); + + minValue = intv[axisId].minValue(); + maxValue = intv[axisId].maxValue(); + + d.scaleEngine->autoScale(d.maxMajor, + minValue, maxValue, stepSize); + } + if ( !d.scaleDiv.isValid() ) + { + d.scaleDiv = d.scaleEngine->divideScale( + minValue, maxValue, + d.maxMajor, d.maxMinor, stepSize); + } + + QwtScaleWidget *scaleWidget = axisWidget(axisId); + scaleWidget->setScaleDiv( + d.scaleEngine->transformation(), d.scaleDiv); + + int startDist, endDist; + scaleWidget->getBorderDistHint(startDist, endDist); + scaleWidget->setBorderDist(startDist, endDist); + } + + for ( it = itmList.begin(); it != itmList.end(); ++it ) + { + QwtPlotItem *item = *it; + item->updateScaleDiv( *axisScaleDiv(item->xAxis()), + *axisScaleDiv(item->yAxis())); + } +} + diff --git a/qwt/src/qwt_plot_canvas.cpp b/qwt/src/qwt_plot_canvas.cpp new file mode 100644 index 000000000..e56106c14 --- /dev/null +++ b/qwt/src/qwt_plot_canvas.cpp @@ -0,0 +1,422 @@ +/* -*- 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 +#if QT_VERSION >= 0x040000 +#include +#include +#ifdef Q_WS_X11 +#include +#endif +#endif +#include +#include "qwt_painter.h" +#include "qwt_math.h" +#include "qwt_plot.h" +#include "qwt_paint_buffer.h" +#include "qwt_plot_canvas.h" + +class QwtPlotCanvas::PrivateData +{ +public: + PrivateData(): + focusIndicator(NoFocusIndicator), + paintAttributes(0), + cache(NULL) + { + } + + ~PrivateData() + { + delete cache; + } + + FocusIndicator focusIndicator; + int paintAttributes; + QPixmap *cache; +}; + +//! Sets a cross cursor, enables QwtPlotCanvas::PaintCached + +QwtPlotCanvas::QwtPlotCanvas(QwtPlot *plot): + QFrame(plot) +{ + d_data = new PrivateData; + +#if QT_VERSION >= 0x040100 + setAutoFillBackground(true); +#endif + +#if QT_VERSION < 0x040000 + setWFlags(Qt::WNoAutoErase); +#ifndef QT_NO_CURSOR + setCursor(Qt::crossCursor); +#endif +#else +#ifndef QT_NO_CURSOR + setCursor(Qt::CrossCursor); +#endif +#endif // >= 0x040000 + + setPaintAttribute(PaintCached, true); + setPaintAttribute(PaintPacked, true); +} + +//! Destructor +QwtPlotCanvas::~QwtPlotCanvas() +{ + delete d_data; +} + +//! Return parent plot widget +QwtPlot *QwtPlotCanvas::plot() +{ + QWidget *w = parentWidget(); + if ( w && w->inherits("QwtPlot") ) + return (QwtPlot *)w; + + return NULL; +} + +//! Return parent plot widget +const QwtPlot *QwtPlotCanvas::plot() const +{ + const QWidget *w = parentWidget(); + if ( w && w->inherits("QwtPlot") ) + return (QwtPlot *)w; + + return NULL; +} + +/*! + \brief Changing the paint attributes + + \param attribute Paint attribute + \param on On/Off + + The default setting enables PaintCached and PaintPacked + + \sa testPaintAttribute(), drawCanvas(), drawContents(), paintCache() +*/ +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 PaintCached: + { + if ( on ) + { + if ( d_data->cache == NULL ) + d_data->cache = new QPixmap(); + + if ( isVisible() ) + { + const QRect cr = contentsRect(); + *d_data->cache = QPixmap::grabWidget(this, + cr.x(), cr.y(), cr.width(), cr.height() ); + } + } + else + { + delete d_data->cache; + d_data->cache = NULL; + } + break; + } + case PaintPacked: + { + /* + If not visible, changing of the background mode + is delayed until it becomes visible. This tries to avoid + looking through the canvas when the canvas is shown the first + time. + */ + + if ( on == false || isVisible() ) + QwtPlotCanvas::setSystemBackground(!on); + + break; + } + } +} + +/*! + Test wether a paint attribute is enabled + + \param attribute Paint attribute + \return true if the attribute is enabled + \sa setPaintAttribute() +*/ +bool QwtPlotCanvas::testPaintAttribute(PaintAttribute attribute) const +{ + return (d_data->paintAttributes & attribute) != 0; +} + +//! Return the paint cache, might be null +QPixmap *QwtPlotCanvas::paintCache() +{ + return d_data->cache; +} + +//! Return the paint cache, might be null +const QPixmap *QwtPlotCanvas::paintCache() const +{ + return d_data->cache; +} + +//! Invalidate the internal paint cache +void QwtPlotCanvas::invalidatePaintCache() +{ + if ( d_data->cache ) + *d_data->cache = 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; +} + +/*! + Hide event + \param event Hide event +*/ +void QwtPlotCanvas::hideEvent(QHideEvent *event) +{ + QFrame::hideEvent(event); + + if ( d_data->paintAttributes & PaintPacked ) + { + // enable system background to avoid the "looking through + // the canvas" effect, for the next show + + setSystemBackground(true); + } +} + +/*! + Paint event + \param event Paint event +*/ +void QwtPlotCanvas::paintEvent(QPaintEvent *event) +{ +#if QT_VERSION >= 0x040000 + QPainter painter(this); + + if ( !contentsRect().contains( event->rect() ) ) + { + painter.save(); + painter.setClipRegion( event->region() & frameRect() ); + drawFrame( &painter ); + painter.restore(); + } + + painter.setClipRegion(event->region() & contentsRect()); + + drawContents( &painter ); +#else // QT_VERSION < 0x040000 + QFrame::paintEvent(event); +#endif + + if ( d_data->paintAttributes & PaintPacked ) + setSystemBackground(false); +} + +/*! + Redraw the canvas, and focus rect + \param painter Painter +*/ +void QwtPlotCanvas::drawContents(QPainter *painter) +{ + if ( d_data->paintAttributes & PaintCached && d_data->cache + && d_data->cache->size() == contentsRect().size() ) + { + painter->drawPixmap(contentsRect().topLeft(), *d_data->cache); + } + else + { + QwtPlot *plot = ((QwtPlot *)parent()); + const bool doAutoReplot = plot->autoReplot(); + plot->setAutoReplot(false); + + drawCanvas(painter); + + plot->setAutoReplot(doAutoReplot); + } + + if ( hasFocus() && focusIndicator() == CanvasFocusIndicator ) + drawFocusIndicator(painter); +} + +/*! + Draw the the canvas + + Paints all plot items to the contentsRect(), using QwtPlot::drawCanvas + and updates the paint cache. + + \param painter Painter + + \sa QwtPlot::drawCanvas(), setPaintAttributes(), testPaintAttributes() +*/ +void QwtPlotCanvas::drawCanvas(QPainter *painter) +{ + if ( !contentsRect().isValid() ) + return; + + QBrush bgBrush; +#if QT_VERSION >= 0x040000 + bgBrush = palette().brush(backgroundRole()); +#else + QColorGroup::ColorRole role = + QPalette::backgroundRoleFromMode( backgroundMode() ); + bgBrush = colorGroup().brush( role ); +#endif + + if ( d_data->paintAttributes & PaintCached && d_data->cache ) + { + *d_data->cache = QPixmap(contentsRect().size()); + +#ifdef Q_WS_X11 +#if QT_VERSION >= 0x040000 + if ( d_data->cache->x11Info().screen() != x11Info().screen() ) + d_data->cache->x11SetScreen(x11Info().screen()); +#else + if ( d_data->cache->x11Screen() != x11Screen() ) + d_data->cache->x11SetScreen(x11Screen()); +#endif +#endif + + if ( d_data->paintAttributes & PaintPacked ) + { + QPainter bgPainter(d_data->cache); + bgPainter.setPen(Qt::NoPen); + + bgPainter.setBrush(bgBrush); + bgPainter.drawRect(d_data->cache->rect()); + } + else + d_data->cache->fill(this, d_data->cache->rect().topLeft()); + + QPainter cachePainter(d_data->cache); + cachePainter.translate(-contentsRect().x(), + -contentsRect().y()); + + ((QwtPlot *)parent())->drawCanvas(&cachePainter); + + cachePainter.end(); + + painter->drawPixmap(contentsRect(), *d_data->cache); + } + else + { +#if QT_VERSION >= 0x040000 + if ( d_data->paintAttributes & PaintPacked ) +#endif + { + painter->save(); + + painter->setPen(Qt::NoPen); + painter->setBrush(bgBrush); + painter->drawRect(contentsRect()); + + painter->restore(); + } + + ((QwtPlot *)parent())->drawCanvas(painter); + } +} + +/*! + 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); +} + +void QwtPlotCanvas::setSystemBackground(bool on) +{ +#if QT_VERSION < 0x040000 + if ( backgroundMode() == Qt::NoBackground ) + { + if ( on ) + setBackgroundMode(Qt::PaletteBackground); + } + else + { + if ( !on ) + setBackgroundMode(Qt::NoBackground); + } +#else + if ( testAttribute(Qt::WA_NoSystemBackground) == on ) + setAttribute(Qt::WA_NoSystemBackground, !on); +#endif +} + +/*! + Invalidate the paint cache and repaint the canvas + \sa invalidatePaintCache() +*/ +void QwtPlotCanvas::replot() +{ + invalidatePaintCache(); + + /* + In case of cached or packed painting the canvas + is repainted completely and doesn't need to be erased. + */ + const bool erase = + !testPaintAttribute(QwtPlotCanvas::PaintPacked) + && !testPaintAttribute(QwtPlotCanvas::PaintCached); + +#if QT_VERSION >= 0x040000 + const bool noBackgroundMode = testAttribute(Qt::WA_NoBackground); + if ( !erase && !noBackgroundMode ) + setAttribute(Qt::WA_NoBackground, true); + + repaint(contentsRect()); + + if ( !erase && !noBackgroundMode ) + setAttribute(Qt::WA_NoBackground, false); +#else + repaint(contentsRect(), erase); +#endif +} diff --git a/qwt/src/qwt_plot_canvas.h b/qwt/src/qwt_plot_canvas.h new file mode 100644 index 000000000..71aa7b6db --- /dev/null +++ b/qwt/src/qwt_plot_canvas.h @@ -0,0 +1,119 @@ +/* -*- 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 + +#ifndef QWT_PLOT_CANVAS_H +#define QWT_PLOT_CANVAS_H + +#include +#include +#include "qwt_global.h" + +class QwtPlot; +class QPixmap; + +/*! + \brief Canvas of a QwtPlot. + \sa QwtPlot +*/ +class QWT_EXPORT QwtPlotCanvas : public QFrame +{ + Q_OBJECT + +public: + + /*! + \brief Paint attributes + + - PaintCached\n + Paint double buffered and reuse the content of the pixmap buffer + for some spontaneous repaints that happen when a plot gets unhidden, + deiconified or changes the focus. + Disabling the cache will improve the performance for + incremental paints (using QwtPlotCurve::draw). + + - PaintPacked\n + Suppress system background repaints and paint it together with + the canvas contents. + Painting packed might avoid flickering for expensive repaints, + when there is a notable gap between painting the background + and the plot contents. + + The default setting enables PaintCached and PaintPacked + + \sa setPaintAttribute(), testPaintAttribute(), paintCache() + */ + enum PaintAttribute + { + PaintCached = 1, + PaintPacked = 2 + }; + + /*! + \brief Focus indicator + + - NoFocusIndicator\n + Don't paint a focus indicator + + - CanvasFocusIndicator\n + The focus is related to the complete canvas. + Paint the focus indicator using paintFocus() + + - ItemFocusIndicator\n + 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. + + \sa setFocusIndicator(), focusIndicator(), paintFocus() + */ + + enum FocusIndicator + { + NoFocusIndicator, + CanvasFocusIndicator, + ItemFocusIndicator + }; + + explicit QwtPlotCanvas(QwtPlot *); + virtual ~QwtPlotCanvas(); + + QwtPlot *plot(); + const QwtPlot *plot() const; + + void setFocusIndicator(FocusIndicator); + FocusIndicator focusIndicator() const; + + void setPaintAttribute(PaintAttribute, bool on = true); + bool testPaintAttribute(PaintAttribute) const; + + QPixmap *paintCache(); + const QPixmap *paintCache() const; + void invalidatePaintCache(); + + void replot(); + +protected: + virtual void hideEvent(QHideEvent *); + + virtual void paintEvent(QPaintEvent *); + + virtual void drawContents(QPainter *); + virtual void drawFocusIndicator(QPainter *); + + void drawCanvas(QPainter *painter = NULL); + +private: + void setSystemBackground(bool); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_curve.cpp b/qwt/src/qwt_plot_curve.cpp new file mode 100644 index 000000000..35f06a21e --- /dev/null +++ b/qwt/src/qwt_plot_curve.cpp @@ -0,0 +1,1353 @@ +/* -*- 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 "qwt_global.h" +#include "qwt_legend.h" +#include "qwt_legend_item.h" +#include "qwt_data.h" +#include "qwt_scale_map.h" +#include "qwt_double_rect.h" +#include "qwt_math.h" +#include "qwt_clipper.h" +#include "qwt_painter.h" +#include "qwt_plot.h" +#include "qwt_plot_canvas.h" +#include "qwt_curve_fitter.h" +#include "qwt_symbol.h" +#include "qwt_plot_curve.h" + +#if QT_VERSION < 0x040000 +#include +#else +#include +#endif + +#if QT_VERSION >= 0x040000 + +#include +#include + +class QwtPlotCurvePaintHelper: public QObject +{ +public: + QwtPlotCurvePaintHelper(const QwtPlotCurve *curve, int from, int to): + _curve(curve), + _from(from), + _to(to) + { + } + + virtual bool eventFilter(QObject *, QEvent *event) + { + if ( event->type() == QEvent::Paint ) + { + _curve->draw(_from, _to); + return true; + } + return false; + } +private: + const QwtPlotCurve *_curve; + int _from; + int _to; +}; + +#endif // QT_VERSION >= 0x040000 + +// Creating and initializing a QPainter is an +// expensive operation. So we keep an painter +// open for situations, where we paint outside +// of paint events. This improves the performance +// of incremental painting like in the realtime +// example a lot. +// But it is not possible to have more than +// one QPainter open at the same time. So we +// need to close it before regular paint events +// are processed. + +class QwtGuardedPainter: public QObject +{ +public: + ~QwtGuardedPainter() + { + end(); + } + + QPainter *begin(QwtPlotCanvas *canvas) + { + _canvas = canvas; + + QMap::iterator it = _map.find(_canvas); + if ( it == _map.end() ) + { + QPainter *painter = new QPainter(_canvas); + painter->setClipping(true); + painter->setClipRect(_canvas->contentsRect()); + + it = _map.insert(_canvas, painter); + _canvas->installEventFilter(this); + } +#if QT_VERSION < 0x040000 + return it.data(); +#else + return it.value(); +#endif + } + + void end() + { + if ( _canvas ) + { + QMap::iterator it = _map.find(_canvas); + if ( it != _map.end() ) + { + _canvas->removeEventFilter(this); + +#if QT_VERSION < 0x040000 + delete it.data(); +#else + delete it.value(); +#endif + _map.erase(it); + } + } + } + + virtual bool eventFilter(QObject *, QEvent *event) + { + if ( event->type() == QEvent::Paint ) + end(); + + return false; + } + +private: +#if QT_VERSION < 0x040000 + QGuardedPtr _canvas; +#else + QPointer _canvas; +#endif + static QMap _map; +}; + +QMap QwtGuardedPainter::_map; + +static int verifyRange(int size, int &i1, int &i2) +{ + if (size < 1) + return 0; + + i1 = qwtLim(i1, 0, size-1); + i2 = qwtLim(i2, 0, size-1); + + if ( i1 > i2 ) + qSwap(i1, i2); + + return (i2 - i1 + 1); +} + +class QwtPlotCurve::PrivateData +{ +public: + class PixelMatrix: private QBitArray + { + public: + PixelMatrix(const QRect& rect): + QBitArray(rect.width() * rect.height()), + _rect(rect) + { + fill(false); + } + + inline bool testPixel(const QPoint& pos) + { + if ( !_rect.contains(pos) ) + return false; + + const int idx = _rect.width() * (pos.y() - _rect.y()) + + (pos.x() - _rect.x()); + + const bool marked = testBit(idx); + if ( !marked ) + setBit(idx, true); + + return !marked; + } + + private: + QRect _rect; + }; + + PrivateData(): + curveType(Yfx), + style(QwtPlotCurve::Lines), + reference(0.0), + attributes(0), + paintAttributes(0) + { + symbol = new QwtSymbol(); + pen = QPen(Qt::black); + curveFitter = new QwtSplineCurveFitter; + } + + ~PrivateData() + { + delete symbol; + delete curveFitter; + } + + QwtPlotCurve::CurveType curveType; + QwtPlotCurve::CurveStyle style; + double reference; + + QwtSymbol *symbol; + QwtCurveFitter *curveFitter; + + QPen pen; + QBrush brush; + + int attributes; + int paintAttributes; + + QwtGuardedPainter guardedPainter; +}; + +//! Constructor +QwtPlotCurve::QwtPlotCurve(): + QwtPlotItem(QwtText()) +{ + init(); +} + +/*! + Constructor + \param title Title of the curve +*/ +QwtPlotCurve::QwtPlotCurve(const QwtText &title): + QwtPlotItem(title) +{ + init(); +} + +/*! + Constructor + \param title Title of the curve +*/ +QwtPlotCurve::QwtPlotCurve(const QString &title): + QwtPlotItem(QwtText(title)) +{ + init(); +} + +//! Destructor +QwtPlotCurve::~QwtPlotCurve() +{ + delete d_xy; + delete d_data; +} + +/*! + \brief Initialize data members +*/ +void QwtPlotCurve::init() +{ + setItemAttribute(QwtPlotItem::Legend); + setItemAttribute(QwtPlotItem::AutoScale); + + d_data = new PrivateData; + d_xy = new QwtPolygonFData(QwtArray()); + + 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 PaintAttribute, testPaintAttribute() +*/ +void QwtPlotCurve::setPaintAttribute(PaintAttribute attribute, bool on) +{ + if ( on ) + d_data->paintAttributes |= attribute; + else + d_data->paintAttributes &= ~attribute; +} + +/*! + \brief Return the current paint attributes + \sa PaintAttribute, setPaintAttribute() +*/ +bool QwtPlotCurve::testPaintAttribute(PaintAttribute attribute) const +{ + return (d_data->paintAttributes & attribute); +} + +/*! + Set the curve's drawing style + + \param style Curve style + \sa CurveStyle, style() +*/ +void QwtPlotCurve::setStyle(CurveStyle style) +{ + if ( style != d_data->style ) + { + d_data->style = style; + itemChanged(); + } +} + +/*! + Return the current style + \sa CurveStyle, setStyle() +*/ +QwtPlotCurve::CurveStyle QwtPlotCurve::style() const +{ + return d_data->style; +} + +/*! + \brief Assign a symbol + \param symbol Symbol + \sa symbol() +*/ +void QwtPlotCurve::setSymbol(const QwtSymbol &symbol ) +{ + delete d_data->symbol; + d_data->symbol = symbol.clone(); + itemChanged(); +} + +/*! + \brief Return the current symbol + \sa setSymbol() +*/ +const QwtSymbol &QwtPlotCurve::symbol() const +{ + return *d_data->symbol; +} + +/*! + Assign a pen + + The width of non cosmetic pens is scaled according to the resolution + of the paint device. + + \param pen New pen + \sa pen(), brush(), QwtPainter::scaledPen() +*/ +void QwtPlotCurve::setPen(const QPen &pen) +{ + if ( pen != d_data->pen ) + { + d_data->pen = pen; + itemChanged(); + } +} + +/*! + \brief Return the 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; + itemChanged(); + } +} + +/*! + \brief Return the brush used to fill the area between lines and the baseline + \sa setBrush(), setBaseline(), baseline() +*/ +const QBrush& QwtPlotCurve::brush() const +{ + return d_data->brush; +} + + +/*! + Set data by copying x- and y-values from specified memory blocks. + Contrary to setCurveRawData(), 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 + + \note Internally the data is stored in a QwtArrayData object +*/ +void QwtPlotCurve::setData(const double *xData, const double *yData, int size) +{ + delete d_xy; + d_xy = new QwtArrayData(xData, yData, size); + itemChanged(); +} + +/*! + Initialize data with x- and y-arrays (explicitly shared) + ( Builds an QwtArrayData object internally ) + + \param xData x data + \param yData y data + + \note Internally the data is stored in a QwtArrayData object +*/ +void QwtPlotCurve::setData(const QwtArray &xData, + const QwtArray &yData) +{ + delete d_xy; + d_xy = new QwtArrayData(xData, yData); + itemChanged(); +} + +/*! + Initialize data with an array of points (explicitly shared). + + \param data Data + \note Internally the data is stored in a QwtPolygonFData object +*/ +#if QT_VERSION < 0x040000 +void QwtPlotCurve::setData(const QwtArray &data) +#else +void QwtPlotCurve::setData(const QPolygonF &data) +#endif +{ + delete d_xy; + d_xy = new QwtPolygonFData(data); + itemChanged(); +} + +/*! + Initialize data with a pointer to QwtData. + + \param data Data + \sa QwtData::copy() +*/ +void QwtPlotCurve::setData(const QwtData &data) +{ + delete d_xy; + d_xy = data.copy(); + itemChanged(); +} + +/*! + \brief Initialize the data by pointing to memory blocks which are not managed + by QwtPlotCurve. + + setRawData 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 + + \note Internally the data is stored in a QwtCPointerData object +*/ +void QwtPlotCurve::setRawData(const double *xData, const double *yData, int size) +{ + delete d_xy; + d_xy = new QwtCPointerData(xData, yData, size); + itemChanged(); +} + +/*! + Returns the bounding rectangle of the curve data. If there is + no bounding rect, like for empty data the rectangle is invalid. + \sa QwtData::boundingRect(), QwtDoubleRect::isValid() +*/ + +QwtDoubleRect QwtPlotCurve::boundingRect() const +{ + if ( d_xy == NULL ) + return QwtDoubleRect(1.0, 1.0, -2.0, -2.0); // invalid + + return d_xy->boundingRect(); +} + +/*! + \brief Draw the complete curve + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + + \sa drawCurve(), drawSymbols() +*/ +void QwtPlotCurve::draw(QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRect &) const +{ + draw(painter, xMap, yMap, 0, -1); +} + +/*! + \brief Draw a set of points of a curve. + + When observing an measurement while it is running, new points have to be + added to an existing curve. drawCurve 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 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::draw(int from, int to) const +{ + if ( !plot() ) + return; + + QwtPlotCanvas *canvas = plot()->canvas(); + +#if QT_VERSION >= 0x040000 +#if 0 + if ( canvas->paintEngine()->type() == QPaintEngine::OpenGL ) + { + /* + OpenGL alway repaint the complete widget. + So for this operation OpenGL is one of the slowest + environments. + */ + canvas->repaint(); + return; + } +#endif + + if ( !canvas->testAttribute(Qt::WA_WState_InPaintEvent) && + !canvas->testAttribute(Qt::WA_PaintOutsidePaintEvent) ) + { + /* + We save curve and range in helper and call repaint. + The helper filters the Paint event, to repeat + the QwtPlotCurve::draw, but now from inside the paint + event. + */ + + QwtPlotCurvePaintHelper helper(this, from, to); + canvas->installEventFilter(&helper); + + const bool noSystemBackground = + canvas->testAttribute(Qt::WA_NoSystemBackground); + canvas->setAttribute(Qt::WA_NoSystemBackground, true); + canvas->repaint(); + canvas->setAttribute(Qt::WA_NoSystemBackground, noSystemBackground); + + return; + } +#endif + + const QwtScaleMap xMap = plot()->canvasMap(xAxis()); + const QwtScaleMap yMap = plot()->canvasMap(yAxis()); + + if ( canvas->testPaintAttribute(QwtPlotCanvas::PaintCached) && + canvas->paintCache() && !canvas->paintCache()->isNull() ) + { + QPainter cachePainter((QPixmap *)canvas->paintCache()); + cachePainter.translate(-canvas->contentsRect().x(), + -canvas->contentsRect().y()); + + draw(&cachePainter, xMap, yMap, from, to); + } + +#if QT_VERSION >= 0x040000 + if ( canvas->testAttribute(Qt::WA_WState_InPaintEvent) ) + { + QPainter painter(canvas); + + painter.setClipping(true); + painter.setClipRect(canvas->contentsRect()); + + draw(&painter, xMap, yMap, from, to); + } + else +#endif + { + QPainter *painter = d_data->guardedPainter.begin(canvas); + draw(painter, xMap, yMap, from, to); + } +} + +/*! + \brief 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 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::draw(QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to) const +{ + if ( !painter || dataSize() <= 0 ) + return; + + if (to < 0) + to = dataSize() - 1; + + if ( verifyRange(dataSize(), from, to) > 0 ) + { + painter->save(); + painter->setPen(QwtPainter::scaledPen(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, from, to); + painter->restore(); + + if (d_data->symbol->style() != QwtSymbol::NoSymbol) + { + painter->save(); + drawSymbols(painter, *d_data->symbol, xMap, yMap, 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 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, + 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, from, to); + break; + case Sticks: + drawSticks(painter, xMap, yMap, from, to); + break; + case Steps: + drawSteps(painter, xMap, yMap, from, to); + break; + case Dots: + drawDots(painter, xMap, yMap, 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 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, + int from, int to) const +{ + int size = to - from + 1; + if ( size <= 0 ) + return; + + QwtPolygon polyline; + if ( ( d_data->attributes & Fitted ) && d_data->curveFitter ) + { + // Transform x and y values to window coordinates + // to avoid a distinction between linear and + // logarithmic scales. + +#if QT_VERSION < 0x040000 + QwtArray points(size); +#else + QPolygonF points(size); +#endif + for (int i = from; i <= to; i++) + { + QwtDoublePoint &p = points[i]; + p.setX( xMap.xTransform(x(i)) ); + p.setY( yMap.xTransform(y(i)) ); + } + + points = d_data->curveFitter->fitCurve(points); + size = points.size(); + + if ( size == 0 ) + return; + + // Round QwtDoublePoints to QPoints + // When Qwt support for Qt3 has been dropped (Qwt 6.x) + // we will use a doubles for painting and the following + // step will be obsolete. + + polyline.resize(size); + + const QwtDoublePoint *p = points.data(); + QPoint *pl = polyline.data(); + if ( d_data->paintAttributes & PaintFiltered ) + { + + QPoint pp(qRound(p[0].x()), qRound(p[0].y())); + pl[0] = pp; + + int count = 1; + for (int i = 1; i < size; i++) + { + const QPoint pi(qRound(p[i].x()), qRound(p[i].y())); + if ( pi != pp ) + { + pl[count++] = pi; + pp = pi; + } + } + if ( count != size ) + polyline.resize(count); + } + else + { + for ( int i = 0; i < size; i++ ) + { + pl[i].setX( qRound(p[i].x()) ); + pl[i].setY( qRound(p[i].y()) ); + } + } + } + else + { + polyline.resize(size); + + if ( d_data->paintAttributes & PaintFiltered ) + { + QPoint pp( xMap.transform(x(from)), yMap.transform(y(from)) ); + polyline.setPoint(0, pp); + + int count = 1; + for (int i = from + 1; i <= to; i++) + { + const QPoint pi(xMap.transform(x(i)), yMap.transform(y(i))); + if ( pi != pp ) + { + polyline.setPoint(count, pi); + count++; + + pp = pi; + } + } + if ( count != size ) + polyline.resize(count); + } + else + { + for (int i = from; i <= to; i++) + { + int xi = xMap.transform(x(i)); + int yi = yMap.transform(y(i)); + + polyline.setPoint(i - from, xi, yi); + } + } + } + + if ( d_data->paintAttributes & ClipPolygons ) + polyline = QwtClipper::clipPolygon(painter->window(), polyline); + + QwtPainter::drawPolyline(painter, polyline); + + if ( d_data->brush.style() != Qt::NoBrush ) + fillCurve(painter, xMap, yMap, polyline); +} + +/*! + Draw sticks + + \param painter Painter + \param xMap x map + \param yMap y map + \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, + int from, int to) const +{ + int x0 = xMap.transform(d_data->reference); + int y0 = yMap.transform(d_data->reference); + + for (int i = from; i <= to; i++) + { + const int xi = xMap.transform(x(i)); + const int yi = yMap.transform(y(i)); + + if (d_data->curveType == Xfy) + QwtPainter::drawLine(painter, x0, yi, xi, yi); + else + QwtPainter::drawLine(painter, xi, y0, xi, yi); + } +} + +/*! + Draw dots + + \param painter Painter + \param xMap x map + \param yMap y map + \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, + int from, int to) const +{ + const QRect window = painter->window(); + if ( window.isEmpty() ) + return; + + const bool doFill = d_data->brush.style() != Qt::NoBrush; + + QwtPolygon polyline; + if ( doFill ) + polyline.resize(to - from + 1); + + if ( to > from && d_data->paintAttributes & PaintFiltered ) + { + if ( doFill ) + { + QPoint pp( xMap.transform(x(from)), yMap.transform(y(from)) ); + + QwtPainter::drawPoint(painter, pp.x(), pp.y()); + polyline.setPoint(0, pp); + + int count = 1; + for (int i = from + 1; i <= to; i++) + { + const QPoint pi(xMap.transform(x(i)), yMap.transform(y(i))); + if ( pi != pp ) + { + QwtPainter::drawPoint(painter, pi.x(), pi.y()); + + polyline.setPoint(count, pi); + count++; + + pp = pi; + } + } + if ( int(polyline.size()) != count ) + polyline.resize(count); + } + else + { + // if we don't need to fill, we can sort out + // duplicates independent from the order + + PrivateData::PixelMatrix pixelMatrix(window); + + for (int i = from; i <= to; i++) + { + const QPoint p( xMap.transform(x(i)), + yMap.transform(y(i)) ); + + if ( pixelMatrix.testPixel(p) ) + QwtPainter::drawPoint(painter, p.x(), p.y()); + } + } + } + else + { + for (int i = from; i <= to; i++) + { + const int xi = xMap.transform(x(i)); + const int yi = yMap.transform(y(i)); + QwtPainter::drawPoint(painter, xi, yi); + + if ( doFill ) + polyline.setPoint(i - from, xi, yi); + } + } + + if ( doFill ) + { + if ( d_data->paintAttributes & ClipPolygons ) + polyline = QwtClipper::clipPolygon(painter->window(), polyline); + + fillCurve(painter, xMap, yMap, polyline); + } +} + +/*! + Draw step function + + The direction of the steps depends on Inverted attribute. + + \param painter Painter + \param xMap x map + \param yMap y map + \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, + int from, int to) const +{ + QwtPolygon polyline(2 * (to - from) + 1); + + bool inverted = d_data->curveType == Yfx; + if ( d_data->attributes & Inverted ) + inverted = !inverted; + + int i,ip; + for (i = from, ip = 0; i <= to; i++, ip += 2) + { + const int xi = xMap.transform(x(i)); + const int yi = yMap.transform(y(i)); + + if ( ip > 0 ) + { + if (inverted) + polyline.setPoint(ip - 1, polyline[ip-2].x(), yi); + else + polyline.setPoint(ip - 1, xi, polyline[ip-2].y()); + } + + polyline.setPoint(ip, xi, yi); + } + + if ( d_data->paintAttributes & ClipPolygons ) + polyline = QwtClipper::clipPolygon(painter->window(), polyline); + + QwtPainter::drawPolyline(painter, polyline); + + if ( d_data->brush.style() != Qt::NoBrush ) + fillCurve(painter, xMap, yMap, polyline); +} + + +/*! + Specify an attribute for drawing the curve + + \param attribute Curve attribute + \param on On/Off + + /sa CurveAttribute, 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 CurveAttribute, setCurveAttribute() +*/ +bool QwtPlotCurve::testCurveAttribute(CurveAttribute attribute) const +{ + return d_data->attributes & attribute; +} + +/*! + Assign the curve type + + \param curveType Yfx or Xfy + \sa CurveType, curveType() +*/ +void QwtPlotCurve::setCurveType(CurveType curveType) +{ + if ( d_data->curveType != curveType ) + { + d_data->curveType = curveType; + itemChanged(); + } +} + +/*! + Return the curve type + \sa CurveType, setCurveType() +*/ +QwtPlotCurve::CurveType QwtPlotCurve::curveType() const +{ + return d_data->curveType; +} + +/*! + Assign a curve fitter + setCurveFitter(NULL) disables curve fitting. + + \param curveFitter Curve fitter +*/ +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 +*/ +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 pa Polygon + + \sa setBrush(), setBaseline(), setCurveType() +*/ +void QwtPlotCurve::fillCurve(QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + QwtPolygon &pa) const +{ + if ( d_data->brush.style() == Qt::NoBrush ) + return; + + closePolyline(xMap, yMap, pa); + if ( pa.count() <= 2 ) // a line can't be filled + return; + + QBrush b = d_data->brush; + if ( !b.color().isValid() ) + b.setColor(d_data->pen.color()); + + painter->save(); + + painter->setPen(QPen(Qt::NoPen)); + painter->setBrush(b); + + QwtPainter::drawPolygon(painter, pa); + + painter->restore(); +} + +/*! + \brief Complete a polygon to be a closed polygon + including the area between the original polygon + and the baseline. + \param xMap X map + \param yMap Y map + \param pa Polygon to be completed +*/ +void QwtPlotCurve::closePolyline( + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + QwtPolygon &pa) const +{ + const int sz = pa.size(); + if ( sz < 2 ) + return; + + pa.resize(sz + 2); + + if ( d_data->curveType == QwtPlotCurve::Xfy ) + { + pa.setPoint(sz, + xMap.transform(d_data->reference), pa.point(sz - 1).y()); + pa.setPoint(sz + 1, + xMap.transform(d_data->reference), pa.point(0).y()); + } + else + { + pa.setPoint(sz, + pa.point(sz - 1).x(), yMap.transform(d_data->reference)); + pa.setPoint(pa.size() - 1, + pa.point(0).x(), yMap.transform(d_data->reference)); + } +} + +/*! + \brief Draw symbols + \param painter Painter + \param symbol Curve symbol + \param xMap x map + \param yMap y map + \param from index of the first point to be painted + \param to index of the last point to be painted + + \sa setSymbol(), draw(), drawCurve() +*/ +void QwtPlotCurve::drawSymbols(QPainter *painter, const QwtSymbol &symbol, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to) const +{ + painter->setBrush(symbol.brush()); + painter->setPen(QwtPainter::scaledPen(symbol.pen())); + + const QwtMetricsMap &metricsMap = QwtPainter::metricsMap(); + + QRect rect; + rect.setSize(metricsMap.screenToLayout(symbol.size())); + + if ( to > from && d_data->paintAttributes & PaintFiltered ) + { + const QRect window = painter->window(); + if ( window.isEmpty() ) + return; + + PrivateData::PixelMatrix pixelMatrix(window); + + for (int i = from; i <= to; i++) + { + const QPoint pi( xMap.transform(x(i)), + yMap.transform(y(i)) ); + + if ( pixelMatrix.testPixel(pi) ) + { + rect.moveCenter(pi); + symbol.draw(painter, rect); + } + } + } + else + { + for (int i = from; i <= to; i++) + { + const int xi = xMap.transform(x(i)); + const int yi = yMap.transform(y(i)); + + rect.moveCenter(QPoint(xi, yi)); + symbol.draw(painter, rect); + } + } +} + +/*! + \brief Set the value of the baseline + + The baseline is needed for filling the curve with a brush or + the Sticks drawing style. + The default value is 0.0. The interpretation + of the baseline depends on the CurveType. With QwtPlotCurve::Yfx, + the baseline is interpreted as a horizontal line at y = baseline(), + with QwtPlotCurve::Yfy, it is interpreted as a vertical line at + x = baseline(). + \param reference baseline + \sa baseline(), setBrush(), setStyle(), setCurveType() +*/ +void QwtPlotCurve::setBaseline(double reference) +{ + if ( d_data->reference != reference ) + { + d_data->reference = reference; + itemChanged(); + } +} + +/*! + Return the value of the baseline + \sa setBaseline() +*/ +double QwtPlotCurve::baseline() const +{ + return d_data->reference; +} + +/*! + Return the size of the data arrays + \sa setData() +*/ +int QwtPlotCurve::dataSize() const +{ + return d_xy->size(); +} + +/*! + 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 clostest 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 +{ + if ( plot() == NULL || dataSize() <= 0 ) + return -1; + + const QwtScaleMap xMap = plot()->canvasMap(xAxis()); + const QwtScaleMap yMap = plot()->canvasMap(yAxis()); + + int index = -1; + double dmin = 1.0e10; + + for (int i=0; i < dataSize(); i++) + { + const double cx = xMap.xTransform(x(i)) - pos.x(); + const double cy = yMap.xTransform(y(i)) - pos.y(); + + const double f = qwtSqr(cx) + qwtSqr(cy); + if (f < dmin) + { + index = i; + dmin = f; + } + } + if ( dist ) + *dist = sqrt(dmin); + + return index; +} + +//! Update the widget that represents the curve on the legend +void QwtPlotCurve::updateLegend(QwtLegend *legend) const +{ + if ( !legend ) + return; + + QwtPlotItem::updateLegend(legend); + + QWidget *widget = legend->find(this); + if ( !widget || !widget->inherits("QwtLegendItem") ) + return; + + QwtLegendItem *legendItem = (QwtLegendItem *)widget; + +#if QT_VERSION < 0x040000 + const bool doUpdate = legendItem->isUpdatesEnabled(); +#else + const bool doUpdate = legendItem->updatesEnabled(); +#endif + legendItem->setUpdatesEnabled(false); + + const int policy = legend->displayPolicy(); + + if (policy == QwtLegend::FixedIdentifier) + { + int mode = legend->identifierMode(); + + if (mode & QwtLegendItem::ShowLine) + legendItem->setCurvePen(pen()); + + if (mode & QwtLegendItem::ShowSymbol) + legendItem->setSymbol(symbol()); + + if (mode & QwtLegendItem::ShowText) + legendItem->setText(title()); + else + legendItem->setText(QwtText()); + + legendItem->setIdentifierMode(mode); + } + else if (policy == QwtLegend::AutoIdentifier) + { + int mode = 0; + + if (QwtPlotCurve::NoCurve != style()) + { + legendItem->setCurvePen(pen()); + mode |= QwtLegendItem::ShowLine; + } + if (QwtSymbol::NoSymbol != symbol().style()) + { + legendItem->setSymbol(symbol()); + mode |= QwtLegendItem::ShowSymbol; + } + if ( !title().isEmpty() ) + { + legendItem->setText(title()); + mode |= QwtLegendItem::ShowText; + } + else + { + legendItem->setText(QwtText()); + } + legendItem->setIdentifierMode(mode); + } + + legendItem->setUpdatesEnabled(doUpdate); + legendItem->update(); +} diff --git a/qwt/src/qwt_plot_curve.h b/qwt/src/qwt_plot_curve.h new file mode 100644 index 000000000..08d3a85d6 --- /dev/null +++ b/qwt/src/qwt_plot_curve.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_PLOT_CURVE_H +#define QWT_PLOT_CURVE_H + +#include +#include +#include "qwt_global.h" +#include "qwt_plot_item.h" +#include "qwt_text.h" +#include "qwt_polygon.h" +#include "qwt_data.h" + +class QPainter; +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 +
a) Assign curve properties
+
When a curve is created, it is configured to draw black solid lines + with in 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 QwtData object offering + a bridge to the real storage of the points ( like QAbstractItemModel ). + There are several convenience classes derived from QwtData, that also store + the points inside ( like QStandardItemModel ). QwtPlotCurve also offers + a couple of variations of setData(), that build QwtData objects from + arrays internally.
+
c) Attach the curve to a plot
+
See QwtPlotItem::attach() +
+ + \par Example: + see examples/bode + + \sa QwtPlot, QwtData, QwtSymbol, QwtScaleMap +*/ +class QWT_EXPORT QwtPlotCurve: public QwtPlotItem +{ +public: + /*! + Curve type. + + - Yfx\n + Draws y as a function of x (the default). The + baseline is interpreted as a horizontal line + with y = baseline(). + - Xfy\n + Draws x as a function of y. The baseline is + interpreted as a vertical line with x = baseline(). + + The baseline is used for aligning the sticks, or + filling the curve with a brush. + + \sa setCurveType(), curveType(), baseline() brush() + */ + enum CurveType + { + Yfx, + Xfy + }; + + /*! + Curve styles. + + - NoCurve\n + Don't draw a curve. Note: This doesn't affect the symbols. + - Lines\n + Connect the points with straight lines. The lines might + be interpolated depending on the 'Fitted' attribute. Curve + fitting can be configured using setCurveFitter(). + - Sticks\n + Draw vertical(Yfx) or horizontal(Xfy) sticks from a baseline + which is defined by setBaseline(). + - Steps\n + Connect the points with a step function. The step function + is drawn from the left to the right or vice versa, + depending on the 'Inverted' attribute. + - Dots\n + 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 NoStyle style and a symbol painting a point. + - UserCurve\n + Styles >= UserCurve are reserved for derived + classes of QwtPlotCurve that overload drawCurve() with + additional application specific curve types. + + \sa setStyle(), style() + */ + enum CurveStyle + { + NoCurve, + + Lines, + Sticks, + Steps, + Dots, + + UserCurve = 100 + }; + + /*! + Attribute for drawing the curve + + - Fitted ( in combination with the Lines QwtPlotCurve::CurveStyle only )\n + A QwtCurveFitter tries to + interpolate/smooth the curve, before it is painted. + Note that curve fitting requires temorary memory + for calculating coefficients and additional points. + If painting in Fitted mode is slow it might be better + to fit the points, before they are passed to QwtPlotCurve. + - Inverted\n + For Steps only. Draws a step function + from the right to the left. + + \sa setCurveAttribute(), testCurveAttribute(), curveFitter() + */ + enum CurveAttribute + { + Inverted = 1, + Fitted = 2 + }; + + /*! + Attributes to modify the drawing algorithm. + + - PaintFiltered\n + 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 algos are implemented. + - ClipPolygons\n + 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 + ( especially on Windows ). + + The default is, that no paint attributes are enabled. + + \sa setPaintAttribute(), testPaintAttribute() + */ + enum PaintAttribute + { + PaintFiltered = 1, + ClipPolygons = 2 + }; + + explicit QwtPlotCurve(); + explicit QwtPlotCurve(const QwtText &title); + explicit QwtPlotCurve(const QString &title); + + virtual ~QwtPlotCurve(); + + virtual int rtti() const; + + void setCurveType(CurveType); + CurveType curveType() const; + + void setPaintAttribute(PaintAttribute, bool on = true); + bool testPaintAttribute(PaintAttribute) const; + + void setRawData(const double *x, const double *y, int size); + void setData(const double *xData, const double *yData, int size); + void setData(const QwtArray &xData, const QwtArray &yData); +#if QT_VERSION < 0x040000 + void setData(const QwtArray &data); +#else + void setData(const QPolygonF &data); +#endif + void setData(const QwtData &data); + + int closestPoint(const QPoint &pos, double *dist = NULL) const; + + QwtData &data(); + const QwtData &data() const; + + int dataSize() const; + double x(int i) const; + double y(int i) const; + + virtual QwtDoubleRect boundingRect() 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 QPen &); + const QPen &pen() const; + + void setBrush(const QBrush &); + const QBrush &brush() const; + + void setBaseline(double ref); + double baseline() const; + + void setStyle(CurveStyle style); + CurveStyle style() const; + + void setSymbol(const QwtSymbol &s); + const QwtSymbol& symbol() const; + + void setCurveFitter(QwtCurveFitter *); + QwtCurveFitter *curveFitter() const; + + virtual void draw(QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRect &) const; + + virtual void draw(QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to) const; + + void draw(int from, int to) const; + + virtual void updateLegend(QwtLegend *) const; + +protected: + + void init(); + + virtual void drawCurve(QPainter *p, int style, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to) const; + + virtual void drawSymbols(QPainter *p, const QwtSymbol &, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to) const; + + void drawLines(QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to) const; + void drawSticks(QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to) const; + void drawDots(QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to) const; + void drawSteps(QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + int from, int to) const; + + void fillCurve(QPainter *, + const QwtScaleMap &, const QwtScaleMap &, + QwtPolygon &) const; + void closePolyline(const QwtScaleMap &, const QwtScaleMap &, + QwtPolygon &) const; + +private: + QwtData *d_xy; + + class PrivateData; + PrivateData *d_data; +}; + +//! \return the the curve data +inline QwtData &QwtPlotCurve::data() +{ + return *d_xy; +} + +//! \return the the curve data +inline const QwtData &QwtPlotCurve::data() const +{ + return *d_xy; +} + +/*! + \param i index + \return x-value at position i +*/ +inline double QwtPlotCurve::x(int i) const +{ + return d_xy->x(i); +} + +/*! + \param i index + \return y-value at position i +*/ +inline double QwtPlotCurve::y(int i) const +{ + return d_xy->y(i); +} + +//! 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(); +} + +#endif diff --git a/qwt/src/qwt_plot_dict.cpp b/qwt/src/qwt_plot_dict.cpp new file mode 100644 index 000000000..808880e0d --- /dev/null +++ b/qwt/src/qwt_plot_dict.cpp @@ -0,0 +1,190 @@ +/* -*- 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 "qwt_plot_dict.h" + +class QwtPlotDict::PrivateData +{ +public: + +#if QT_VERSION < 0x040000 + class ItemList: public QValueList +#else + class ItemList: public QList +#endif + { + public: + void insertItem(QwtPlotItem *item) + { + if ( item == NULL ) + return; + + // Unfortunately there is no inSort operation + // for lists in Qt4. The implementation below + // is slow, but there shouldn't be many plot items. + +#ifdef __GNUC__ +#warning binary search missing +#endif + +#if QT_VERSION < 0x040000 + QValueListIterator it; +#else + QList::Iterator it; +#endif + for ( it = begin(); it != end(); ++it ) + { + if ( *it == item ) + return; + + if ( (*it)->z() > item->z() ) + { + insert(it, item); + return; + } + } + append(item); + } + + void removeItem(QwtPlotItem *item) + { + if ( item == NULL ) + return; + + int i = 0; + +#if QT_VERSION < 0x040000 + QValueListIterator it; +#else + QList::Iterator it; +#endif + for ( it = begin(); it != end(); ++it ) + { + if ( item == *it ) + { +#if QT_VERSION < 0x040000 + remove(it); +#else + removeAt(i); +#endif + return; + } + i++; + } + } + }; + + ItemList itemList; + bool autoDelete; +}; + +/*! + Constructor + + Auto deletion is enabled. + \sa setAutoDelete(), attachItem() +*/ +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(), attachItem() +*/ +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(), attachItem() +*/ +void QwtPlotDict::setAutoDelete(bool autoDelete) +{ + d_data->autoDelete = autoDelete; +} + +/*! + \return true if auto deletion is enabled + \sa setAutoDelete(), attachItem() +*/ +bool QwtPlotDict::autoDelete() const +{ + return d_data->autoDelete; +} + +/*! + Attach/Detach a plot item + + Attached items will be deleted in the destructor, + if auto deletion is enabled (default). Manually detached + items are not deleted. + + \param item Plot item to attach/detach + \ on If true attach, else detach the item + + \sa setAutoDelete(), ~QwtPlotDict() +*/ +void QwtPlotDict::attachItem(QwtPlotItem *item, bool on) +{ + if ( on ) + d_data->itemList.insertItem(item); + else + 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; +} diff --git a/qwt/src/qwt_plot_dict.h b/qwt/src/qwt_plot_dict.h new file mode 100644 index 000000000..bc369ae50 --- /dev/null +++ b/qwt/src/qwt_plot_dict.h @@ -0,0 +1,65 @@ +/* -*- 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 + +/*! \file !*/ +#ifndef QWT_PLOT_DICT +#define QWT_PLOT_DICT + +#include "qwt_global.h" +#include "qwt_plot_item.h" + +#if QT_VERSION < 0x040000 +#include +typedef QValueListConstIterator QwtPlotItemIterator; +/// \var typedef QValueList< QwtPlotItem *> QwtPlotItemList +/// \brief See QT 3.x assistant documentation for QValueList +typedef QValueList QwtPlotItemList; +#else +#include +typedef QList::ConstIterator QwtPlotItemIterator; +/// \var typedef QList< QwtPlotItem *> QwtPlotItemList +/// \brief See QT 4.x assistant documentation for QList +typedef QList QwtPlotItemList; +#endif + +/*! + \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. + + \sa QwtPlotItem::attach(), QwtPlotItem::detach(), QwtPlotItem::z() +*/ +class QWT_EXPORT QwtPlotDict +{ +public: + explicit QwtPlotDict(); + ~QwtPlotDict(); + + void setAutoDelete(bool); + bool autoDelete() const; + + const QwtPlotItemList& itemList() const; + + void detachItems(int rtti = QwtPlotItem::Rtti_PlotItem, + bool autoDelete = true); + +private: + friend class QwtPlotItem; + + void attachItem(QwtPlotItem *, bool); + + 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..2e6e0051b --- /dev/null +++ b/qwt/src/qwt_plot_grid.cpp @@ -0,0 +1,358 @@ +/* -*- 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 "qwt_painter.h" +#include "qwt_text.h" +#include "qwt_scale_map.h" +#include "qwt_scale_div.h" +#include "qwt_plot_grid.h" + +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 majPen; + QPen minPen; +}; + +//! Enables major grid, disables minor grid +QwtPlotGrid::QwtPlotGrid(): + QwtPlotItem(QwtText("Grid")) +{ + d_data = new PrivateData; + 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 gridlines + \param tf Enable (true) or disable + + \sa Minor gridlines can be enabled or disabled with + enableXMin() +*/ +void QwtPlotGrid::enableX(bool tf) +{ + if ( d_data->xEnabled != tf ) + { + d_data->xEnabled = tf; + itemChanged(); + } +} + +/*! + \brief Enable or disable horizontal gridlines + \param tf Enable (true) or disable + \sa Minor gridlines can be enabled or disabled with enableYMin() +*/ +void QwtPlotGrid::enableY(bool tf) +{ + if ( d_data->yEnabled != tf ) + { + d_data->yEnabled = tf; + itemChanged(); + } +} + +/*! + \brief Enable or disable minor vertical gridlines. + \param tf Enable (true) or disable + \sa enableX() +*/ +void QwtPlotGrid::enableXMin(bool tf) +{ + if ( d_data->xMinEnabled != tf ) + { + d_data->xMinEnabled = tf; + itemChanged(); + } +} + +/*! + \brief Enable or disable minor horizontal gridlines + \param tf Enable (true) or disable + \sa enableY() +*/ +void QwtPlotGrid::enableYMin(bool tf) +{ + if ( d_data->yMinEnabled != tf ) + { + d_data->yMinEnabled = tf; + 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(); + } +} + +/*! + Assign a pen for both major and minor gridlines + + The width of non cosmetic pens is scaled according to the resolution + of the paint device. + + \param pen Pen + \sa setMajPen(), setMinPen(), QwtPainter::scaledPen() +*/ +void QwtPlotGrid::setPen(const QPen &pen) +{ + if ( d_data->majPen != pen || d_data->minPen != pen ) + { + d_data->majPen = pen; + d_data->minPen = pen; + itemChanged(); + } +} + +/*! + Assign a pen for the major gridlines + + The width of non cosmetic pens is scaled according to the resolution + of the paint device. + + \param pen Pen + \sa majPen(), setMinPen(), setPen(), QwtPainter::scaledPen() +*/ +void QwtPlotGrid::setMajPen(const QPen &pen) +{ + if ( d_data->majPen != pen ) + { + d_data->majPen = pen; + itemChanged(); + } +} + +/*! + Assign a pen for the minor gridlines + + The width of non cosmetic pens is scaled according to the resolution + of the paint device. + + \param pen Pen + \sa minPen(), setMajPen(), setPen(), QwtPainter::scaledPen() +*/ +void QwtPlotGrid::setMinPen(const QPen &pen) +{ + if ( d_data->minPen != pen ) + { + d_data->minPen = pen; + itemChanged(); + } +} + +/*! + \brief Draw the grid + + The grid is drawn into the bounding rectangle such that + gridlines 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 rect of the plot canvas +*/ +void QwtPlotGrid::draw(QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRect &canvasRect) const +{ + // draw minor gridlines + painter->setPen(QwtPainter::scaledPen(d_data->minPen)); + + 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 gridlines + painter->setPen(QwtPainter::scaledPen(d_data->majPen)); + + 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 QRect &canvasRect, + Qt::Orientation orientation, const QwtScaleMap &scaleMap, + const QwtValueList &values) const +{ + const int x1 = canvasRect.left(); + const int x2 = canvasRect.right(); + const int y1 = canvasRect.top(); + const int y2 = canvasRect.bottom(); + + for (uint i = 0; i < (uint)values.count(); i++) + { + const int value = scaleMap.transform(values[i]); + if ( orientation == Qt::Horizontal ) + { + if ((value >= y1) && (value <= y2)) + QwtPainter::drawLine(painter, x1, value, x2, value); + } + else + { + if ((value >= x1) && (value <= x2)) + QwtPainter::drawLine(painter, value, y1, value, y2); + } + } +} + +/*! + \return the pen for the major gridlines + \sa setMajPen(), setMinPen(), setPen() +*/ +const QPen &QwtPlotGrid::majPen() const +{ + return d_data->majPen; +} + +/*! + \return the pen for the minor gridlines + \sa setMinPen(), setMajPen(), setPen() +*/ +const QPen &QwtPlotGrid::minPen() const +{ + return d_data->minPen; +} + +/*! + \return true if vertical gridlines are enabled + \sa enableX() +*/ +bool QwtPlotGrid::xEnabled() const +{ + return d_data->xEnabled; +} + +/*! + \return true if minor vertical gridlines are enabled + \sa enableXMin() +*/ +bool QwtPlotGrid::xMinEnabled() const +{ + return d_data->xMinEnabled; +} + +/*! + \return true if horizontal gridlines are enabled + \sa enableY() +*/ +bool QwtPlotGrid::yEnabled() const +{ + return d_data->yEnabled; +} + +/*! + \return true if minor horizontal gridlines 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..ef64ddd0f --- /dev/null +++ b/qwt/src/qwt_plot_grid.h @@ -0,0 +1,84 @@ +/* -*- 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 gridlines. The locations of the gridlines + 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 QPen &p); + + void setMajPen(const QPen &p); + const QPen& majPen() const; + + void setMinPen(const QPen &p); + const QPen& minPen() const; + + virtual void draw(QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRect &rect) const; + + virtual void updateScaleDiv(const QwtScaleDiv &xMap, + const QwtScaleDiv &yMap); + +private: + void drawLines(QPainter *painter, const QRect &, + Qt::Orientation orientation, const QwtScaleMap &, + const QwtValueList &) const; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_item.cpp b/qwt/src/qwt_plot_item.cpp new file mode 100644 index 000000000..d32cda639 --- /dev/null +++ b/qwt/src/qwt_plot_item.cpp @@ -0,0 +1,564 @@ +/* -*- 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_text.h" +#include "qwt_plot.h" +#include "qwt_legend.h" +#include "qwt_legend_item.h" +#include "qwt_plot_item.h" + +class QwtPlotItem::PrivateData +{ +public: + PrivateData(): + plot(NULL), + isVisible(true), + attributes(0), +#if QT_VERSION >= 0x040000 + renderHints(0), +#endif + z(0.0), + xAxis(QwtPlot::xBottom), + yAxis(QwtPlot::yLeft) + { + } + + mutable QwtPlot *plot; + + bool isVisible; + int attributes; +#if QT_VERSION >= 0x040000 + int renderHints; +#endif + double z; + + int xAxis; + int yAxis; + + QwtText title; +}; + +/*! + 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 QwtPlotItem::detach() +*/ +void QwtPlotItem::attach(QwtPlot *plot) +{ + if ( plot == d_data->plot ) + return; + + // remove the item from the previous plot + + if ( d_data->plot ) + { + if ( d_data->plot->legend() ) + { + QWidget *legendItem = d_data->plot->legend()->find(this); + if ( legendItem ) + delete legendItem; + } + + d_data->plot->attachItem(this, false); + + if ( d_data->plot->autoReplot() ) + d_data->plot->update(); + } + + d_data->plot = plot; + + if ( d_data->plot ) + { + // insert the item into the current plot + + d_data->plot->attachItem(this, true); + itemChanged(); + } +} + +/*! + 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 ) + { + d_data->z = z; + if ( d_data->plot ) + { + // update the z order + d_data->plot->attachItem(this, false); + 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; + itemChanged(); + } +} + +/*! + \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(), ItemAttribute +*/ +void QwtPlotItem::setItemAttribute(ItemAttribute attribute, bool on) +{ + if ( bool(d_data->attributes & attribute) != on ) + { + if ( on ) + d_data->attributes |= attribute; + else + d_data->attributes &= ~attribute; + + itemChanged(); + } +} + +/*! + Test an item attribute + + \param attribute Attribute type + \return true/false + \sa setItemAttribute(), ItemAttribute +*/ +bool QwtPlotItem::testItemAttribute(ItemAttribute attribute) const +{ + return d_data->attributes & attribute; +} + +#if QT_VERSION >= 0x040000 + +/*! + 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 & hint) != 0) != 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 & hint); +} + +#endif + +//! 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 updateLegend() +*/ +void QwtPlotItem::itemChanged() +{ + if ( d_data->plot ) + { + if ( d_data->plot->legend() ) + updateLegend(d_data->plot->legend()); + + d_data->plot->autoRefresh(); + } +} + +/*! + Set X and Y axis + + The item will painted according to the coordinates its Axes. + + \param xAxis X Axis + \param yAxis Y Axis + + \sa setXAxis(), setYAxis(), xAxis(), yAxis() +*/ +void QwtPlotItem::setAxis(int xAxis, int yAxis) +{ + if (xAxis == QwtPlot::xBottom || xAxis == QwtPlot::xTop ) + d_data->xAxis = xAxis; + + if (yAxis == QwtPlot::yLeft || yAxis == QwtPlot::yRight ) + d_data->yAxis = yAxis; + + itemChanged(); +} + +/*! + Set the X axis + + The item will painted according to the coordinates its Axes. + + \param axis X Axis + \sa setAxis(), setYAxis(), xAxis() +*/ +void QwtPlotItem::setXAxis(int axis) +{ + if (axis == QwtPlot::xBottom || axis == QwtPlot::xTop ) + { + d_data->xAxis = axis; + itemChanged(); + } +} + +/*! + Set the Y axis + + The item will painted according to the coordinates its Axes. + + \param axis Y Axis + \sa setAxis(), setXAxis(), yAxis() +*/ +void QwtPlotItem::setYAxis(int axis) +{ + if (axis == QwtPlot::yLeft || axis == QwtPlot::yRight ) + { + d_data->yAxis = axis; + itemChanged(); + } +} + +//! Return xAxis +int QwtPlotItem::xAxis() const +{ + return d_data->xAxis; +} + +//! Return yAxis +int QwtPlotItem::yAxis() const +{ + return d_data->yAxis; +} + +/*! + \return An invalid bounding rect: QwtDoubleRect(1.0, 1.0, -2.0, -2.0) +*/ +QwtDoubleRect QwtPlotItem::boundingRect() const +{ + return QwtDoubleRect(1.0, 1.0, -2.0, -2.0); // invalid +} + +/*! + \brief Allocate the widget that represents the item on the legend + + The default implementation is made for QwtPlotCurve and returns a + QwtLegendItem(), but an item could be represented by any type of widget, + by overloading legendItem() and updateLegend(). + + \return QwtLegendItem() + \sa updateLegend() QwtLegend() +*/ +QWidget *QwtPlotItem::legendItem() const +{ + return new QwtLegendItem; +} + +/*! + \brief Update the widget that represents the item on the legend + + updateLegend() is called from itemChanged() to adopt the widget + representing the item on the legend to its new configuration. + + The default implementation is made for QwtPlotCurve and updates a + QwtLegendItem(), but an item could be represented by any type of widget, + by overloading legendItem() and updateLegend(). + + \param legend Legend + + \sa legendItem(), itemChanged(), QwtLegend() +*/ +void QwtPlotItem::updateLegend(QwtLegend *legend) const +{ + if ( !legend ) + return; + + QWidget *lgdItem = legend->find(this); + if ( testItemAttribute(QwtPlotItem::Legend) ) + { + if ( lgdItem == NULL ) + { + lgdItem = legendItem(); + if ( lgdItem ) + { + if ( lgdItem->inherits("QwtLegendItem") ) + { + QwtLegendItem *label = (QwtLegendItem *)lgdItem; + label->setItemMode(legend->itemMode()); + + if ( d_data->plot ) + { + QObject::connect(label, SIGNAL(clicked()), + d_data->plot, SLOT(legendItemClicked())); + QObject::connect(label, SIGNAL(checked(bool)), + d_data->plot, SLOT(legendItemChecked(bool))); + } + } + legend->insert(this, lgdItem); + } + } + if ( lgdItem && lgdItem->inherits("QwtLegendItem") ) + { + QwtLegendItem* label = (QwtLegendItem*)lgdItem; + if ( label ) + label->setText(d_data->title); + } + } + else + { + delete lgdItem; + } +} + +/*! + \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() + + \param xScaleDiv Scale division of the x-axis + \param yScaleDiv Scale division of the y-axis + + \sa QwtPlot::updateAxes() +*/ +void QwtPlotItem::updateScaleDiv(const QwtScaleDiv &, + const QwtScaleDiv &) +{ +} + +/*! + \brief Calculate the bounding scale rect of 2 maps + + \param xMap X map + \param yMap X map + + \return Bounding rect of the scale maps +*/ +QwtDoubleRect QwtPlotItem::scaleRect(const QwtScaleMap &xMap, + const QwtScaleMap &yMap) const +{ + return QwtDoubleRect(xMap.s1(), yMap.s1(), + xMap.sDist(), yMap.sDist() ); +} + +/*! + \brief Calculate the bounding paint rect of 2 maps + + \param xMap X map + \param yMap X map + + \return Bounding rect of the scale maps +*/ +QRect QwtPlotItem::paintRect(const QwtScaleMap &xMap, + const QwtScaleMap &yMap) const +{ + const QRect rect( qRound(xMap.p1()), qRound(yMap.p1()), + qRound(xMap.pDist()), qRound(yMap.pDist()) ); + + return rect; +} + +/*! + Transform a rectangle + + \param xMap X map + \param yMap Y map + \param rect Rectangle in scale coordinates + \return Rectangle in paint coordinates + + \sa invTransform() +*/ +QRect QwtPlotItem::transform(const QwtScaleMap &xMap, + const QwtScaleMap &yMap, const QwtDoubleRect& rect) const +{ + int x1 = qRound(xMap.transform(rect.left())); + int x2 = qRound(xMap.transform(rect.right())); + int y1 = qRound(yMap.transform(rect.top())); + int y2 = qRound(yMap.transform(rect.bottom())); + + if ( x2 < x1 ) + qSwap(x1, x2); + if ( y2 < y1 ) + qSwap(y1, y2); + + return QRect(x1, y1, x2 - x1 + 1, y2 - y1 + 1); +} + +/*! + Transform a rectangle from paint to scale coordinates + + \param xMap X map + \param yMap Y map + \param rect Rectangle in paint coordinates + \return Rectangle in scale coordinates + \sa transform() +*/ +QwtDoubleRect QwtPlotItem::invTransform(const QwtScaleMap &xMap, + const QwtScaleMap &yMap, const QRect& rect) const +{ + const double x1 = xMap.invTransform(rect.left()); + const double x2 = xMap.invTransform(rect.right()); + const double y1 = yMap.invTransform(rect.top()); + const double y2 = yMap.invTransform(rect.bottom()); + + const QwtDoubleRect r(x1, y1, x2 - x1, y2 - y1); + + return r.normalized(); +} diff --git a/qwt/src/qwt_plot_item.h b/qwt/src/qwt_plot_item.h new file mode 100644 index 000000000..a7ef37403 --- /dev/null +++ b/qwt/src/qwt_plot_item.h @@ -0,0 +1,199 @@ +/* -*- 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_legend_itemmanager.h" +#include "qwt_text.h" +#include "qwt_double_rect.h" + +class QString; +class QRect; +class QPainter; +class QWidget; +class QwtPlot; +class QwtLegend; +class QwtScaleMap; +class QwtScaleDiv; + +/*! + \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 legnd. + + 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 QwtLegendItemManager +{ +public: + /*! + \brief Runtime type information + + RttiValues is used to cast plot items, without + having to enable runtime type information of the compiler. + */ + enum RttiValues + { + Rtti_PlotItem = 0, + + Rtti_PlotGrid, + Rtti_PlotScale, + Rtti_PlotMarker, + Rtti_PlotCurve, + Rtti_PlotHistogram, + Rtti_PlotSpectrogram, + Rtti_PlotSVG, + + Rtti_PlotUserItem = 1000 + }; + + /*! + Plot Item Attributes + + - Legend\n + The item is represented on the legend. + - AutoScale \n + The boundingRect() of the item is included in the + autoscaling calculation. + + \sa setItemAttribute(), testItemAttribute() + */ + enum ItemAttribute + { + Legend = 1, + AutoScale = 2 + }; + +#if QT_VERSION >= 0x040000 + //! Render hints + enum RenderHint + { + RenderAntialiased = 1 + }; +#endif + + explicit QwtPlotItem(const QwtText &title = QwtText()); + virtual ~QwtPlotItem(); + + void attach(QwtPlot *plot); + + /*! + \brief This method detaches a QwtPlotItem from any QwtPlot it has been + associated with. + + detach() is equivalent to calling attach( NULL ) + \sa attach( QwtPlot* plot ) + */ + void detach() { attach(NULL); } + + 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; + +#if QT_VERSION >= 0x040000 + void setRenderHint(RenderHint, bool on = true); + bool testRenderHint(RenderHint) const; +#endif + + double z() const; + void setZ(double z); + + void show(); + void hide(); + virtual void setVisible(bool); + bool isVisible () const; + + void setAxis(int xAxis, int yAxis); + + void setXAxis(int axis); + int xAxis() const; + + void setYAxis(int axis); + int yAxis() const; + + virtual void itemChanged(); + + /*! + \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 QRect &canvasRect) const = 0; + + virtual QwtDoubleRect boundingRect() const; + + virtual void updateLegend(QwtLegend *) const; + virtual void updateScaleDiv(const QwtScaleDiv&, + const QwtScaleDiv&); + + virtual QWidget *legendItem() const; + + QwtDoubleRect scaleRect(const QwtScaleMap &, const QwtScaleMap &) const; + QRect paintRect(const QwtScaleMap &, const QwtScaleMap &) const; + + QRect transform(const QwtScaleMap &, const QwtScaleMap &, + const QwtDoubleRect&) const; + QwtDoubleRect invTransform(const QwtScaleMap &, const QwtScaleMap &, + const QRect&) const; + +private: + // Disabled copy constructor and operator= + QwtPlotItem( const QwtPlotItem & ); + QwtPlotItem &operator=( const QwtPlotItem & ); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_layout.cpp b/qwt/src/qwt_plot_layout.cpp new file mode 100644 index 000000000..e368e2abf --- /dev/null +++ b/qwt/src/qwt_plot_layout.cpp @@ -0,0 +1,1228 @@ +/* -*- 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 "qwt_text.h" +#include "qwt_text_label.h" +#include "qwt_plot_canvas.h" +#include "qwt_scale_widget.h" +#include "qwt_legend.h" +#include "qwt_plot_layout.h" + +class QwtPlotLayout::LayoutData +{ +public: + void init(const QwtPlot *, const QRect &rect); + + struct t_legendData + { + int frameWidth; + int vScrollBarWidth; + int hScrollBarHeight; + QSize hint; + } legend; + + struct t_titleData + { + QwtText text; + int frameWidth; + } title; + + struct t_scaleData + { + bool isEnabled; + const QwtScaleWidget *scaleWidget; + QFont scaleFont; + int start; + int end; + int baseLineOffset; + int tickOffset; + int dimWithoutTitle; + } scale[QwtPlot::axisCnt]; + + struct t_canvasData + { + int frameWidth; + } canvas; +}; + +/* + Extract all layout relevant data from the plot components +*/ + +void QwtPlotLayout::LayoutData::init(const QwtPlot *plot, const QRect &rect) +{ + // legend + + if ( plot->plotLayout()->legendPosition() != QwtPlot::ExternalLegend + && plot->legend() ) + { + legend.frameWidth = plot->legend()->frameWidth(); + legend.vScrollBarWidth = + plot->legend()->verticalScrollBar()->sizeHint().width(); + legend.hScrollBarHeight = + plot->legend()->horizontalScrollBar()->sizeHint().height(); + + const QSize hint = plot->legend()->sizeHint(); + + int w = qwtMin(hint.width(), rect.width()); + int h = plot->legend()->heightForWidth(w); + if ( h == 0 ) + h = hint.height(); + + if ( h > rect.height() ) + w += legend.vScrollBarWidth; + + legend.hint = QSize(w, h); + } + + // title + + title.frameWidth = 0; + title.text = QwtText(); + + if (plot->titleLabel() ) + { + const QwtTextLabel *label = plot->titleLabel(); + title.text = label->text(); + if ( !(title.text.testPaintAttribute(QwtText::PaintUsingTextFont)) ) + title.text.setFont(label->font()); + + title.frameWidth = plot->titleLabel()->frameWidth(); + } + + // scales + + for (int axis = 0; axis < QwtPlot::axisCnt; axis++ ) + { + if ( plot->axisEnabled(axis) ) + { + const QwtScaleWidget *scaleWidget = plot->axisWidget(axis); + + scale[axis].isEnabled = true; + + scale[axis].scaleWidget = scaleWidget; + + scale[axis].scaleFont = scaleWidget->font(); + + scale[axis].start = scaleWidget->startBorderDist(); + scale[axis].end = scaleWidget->endBorderDist(); + + scale[axis].baseLineOffset = scaleWidget->margin(); + scale[axis].tickOffset = scaleWidget->margin(); + if ( scaleWidget->scaleDraw()->hasComponent( + QwtAbstractScaleDraw::Ticks) ) + { + scale[axis].tickOffset += + (int)scaleWidget->scaleDraw()->majTickLength(); + } + + scale[axis].dimWithoutTitle = scaleWidget->dimForLength( + QWIDGETSIZE_MAX, scale[axis].scaleFont); + + if ( !scaleWidget->title().isEmpty() ) + { + scale[axis].dimWithoutTitle -= + scaleWidget->titleHeightForWidth(QWIDGETSIZE_MAX); + } + } + else + { + scale[axis].isEnabled = false; + scale[axis].start = 0; + scale[axis].end = 0; + scale[axis].baseLineOffset = 0; + scale[axis].tickOffset = 0; + scale[axis].dimWithoutTitle = 0; + } + } + + // canvas + + canvas.frameWidth = plot->canvas()->frameWidth(); +} + +class QwtPlotLayout::PrivateData +{ +public: + PrivateData(): + margin(0), + spacing(5), + alignCanvasToScales(false) + { + } + + QRect titleRect; + QRect legendRect; + QRect scaleRect[QwtPlot::axisCnt]; + QRect canvasRect; + + QwtPlotLayout::LayoutData layoutData; + + QwtPlot::LegendPosition legendPos; + double legendRatio; + unsigned int margin; + unsigned int spacing; + unsigned int canvasMargin[QwtPlot::axisCnt]; + bool alignCanvasToScales; +}; + +/*! + \brief Constructor + */ + +QwtPlotLayout::QwtPlotLayout() +{ + d_data = new PrivateData; + + setLegendPosition(QwtPlot::BottomLegend); + setCanvasMargin(4); + + invalidate(); +} + +//! Destructor +QwtPlotLayout::~QwtPlotLayout() +{ + delete d_data; +} + +/*! + Change the margin of the plot. The margin is the space + around all components. + + \param margin new margin + \sa margin(), setSpacing(), + QwtPlot::setMargin() +*/ +void QwtPlotLayout::setMargin(int margin) +{ + if ( margin < 0 ) + margin = 0; + d_data->margin = margin; +} + +/*! + \return margin + \sa setMargin(), spacing(), QwtPlot::margin() +*/ +int QwtPlotLayout::margin() const +{ + return d_data->margin; +} + +/*! + 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 axis One of QwtPlot::Axis. Specifies where the position of the margin. + -1 means margin at all borders. + \sa canvasMargin() + + \warning The margin will have no effect when alignCanvasToScales is true +*/ + +void QwtPlotLayout::setCanvasMargin(int margin, int axis) +{ + if ( margin < -1 ) + margin = -1; + + if ( axis == -1 ) + { + for (axis = 0; axis < QwtPlot::axisCnt; axis++) + d_data->canvasMargin[axis] = margin; + } + else if ( axis >= 0 && axis < QwtPlot::axisCnt ) + d_data->canvasMargin[axis] = margin; +} + +/*! + \return Margin around the scale tick borders + \sa setCanvasMargin() +*/ +int QwtPlotLayout::canvasMargin(int axis) const +{ + if ( axis < 0 || axis >= QwtPlot::axisCnt ) + return 0; + + return d_data->canvasMargin[axis]; +} + +/*! + 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. + + \param alignCanvasToScales New align-canvas-to-axis-scales setting + + \sa setCanvasMargin() + \note In this context the term 'scale' means the backbone of a scale. + \warning In case of alignCanvasToScales == true canvasMargin will have + no effect +*/ +void QwtPlotLayout::setAlignCanvasToScales(bool alignCanvasToScales) +{ + d_data->alignCanvasToScales = alignCanvasToScales; +} + +/*! + 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. + + \return align-canvas-to-axis-scales setting + \sa setAlignCanvasToScales, setCanvasMargin() + \note In this context the term 'scale' means the backbone of a scale. +*/ +bool QwtPlotLayout::alignCanvasToScales() const +{ + return d_data->alignCanvasToScales; +} + +/*! + Change the spacing of the plot. The spacing is the distance + between the plot components. + + \param spacing new spacing + \sa setMargin(), spacing() +*/ +void QwtPlotLayout::setSpacing(int spacing) +{ + d_data->spacing = qwtMax(0, spacing); +} + +/*! + \return spacing + \sa margin(), setSpacing() +*/ +int QwtPlotLayout::spacing() const +{ + return d_data->spacing; +} + +/*! + \brief Specify the position of the legend + \param pos The legend's position. + \param ratio Ratio between legend and the bounding rect + of title, canvas and axes. The legend will be shrinked + 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->legendRatio = ratio; + d_data->legendPos = pos; + break; + case QwtPlot::LeftLegend: + case QwtPlot::RightLegend: + if ( ratio <= 0.0 ) + ratio = 0.5; + d_data->legendRatio = ratio; + d_data->legendPos = pos; + break; + case QwtPlot::ExternalLegend: + d_data->legendRatio = ratio; // meaningless + d_data->legendPos = pos; + 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->legendPos; +} + +/*! + Specify the relative size of the legend in the plot + \param ratio Ratio between legend and the bounding rect + of title, canvas and axes. The legend will be shrinked + 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->legendRatio; +} + +/*! + \return Geometry for the title + \sa activate(), invalidate() +*/ + +const QRect &QwtPlotLayout::titleRect() const +{ + return d_data->titleRect; +} + +/*! + \return Geometry for the legend + \sa activate(), invalidate() +*/ + +const QRect &QwtPlotLayout::legendRect() const +{ + return d_data->legendRect; +} + +/*! + \param axis Axis index + \return Geometry for the scale + \sa activate(), invalidate() +*/ + +const QRect &QwtPlotLayout::scaleRect(int axis) const +{ + if ( axis < 0 || axis >= QwtPlot::axisCnt ) + { + static QRect dummyRect; + return dummyRect; + } + return d_data->scaleRect[axis]; +} + +/*! + \return Geometry for the canvas + \sa activate(), invalidate() +*/ + +const QRect &QwtPlotLayout::canvasRect() const +{ + return d_data->canvasRect; +} + +/*! + Invalidate the geometry of all components. + \sa activate() +*/ +void QwtPlotLayout::invalidate() +{ + d_data->titleRect = d_data->legendRect = d_data->canvasRect = QRect(); + for (int axis = 0; axis < QwtPlot::axisCnt; axis++ ) + d_data->scaleRect[axis] = QRect(); +} + +/*! + \brief Return a minimum size hint + \sa QwtPlot::minimumSizeHint() +*/ + +QSize QwtPlotLayout::minimumSizeHint(const QwtPlot *plot) const +{ + class ScaleData + { + public: + ScaleData() + { + w = h = minLeft = minRight = tickOffset = 0; + } + + int w; + int h; + int minLeft; + int minRight; + int tickOffset; + } scaleData[QwtPlot::axisCnt]; + + int canvasBorder[QwtPlot::axisCnt]; + + int axis; + for ( axis = 0; axis < QwtPlot::axisCnt; axis++ ) + { + if ( plot->axisEnabled(axis) ) + { + const QwtScaleWidget *scl = plot->axisWidget(axis); + ScaleData &sd = scaleData[axis]; + + const QSize hint = scl->minimumSizeHint(); + sd.w = hint.width(); + sd.h = hint.height(); + scl->getBorderDistHint(sd.minLeft, sd.minRight); + sd.tickOffset = scl->margin(); + if ( scl->scaleDraw()->hasComponent(QwtAbstractScaleDraw::Ticks) ) + sd.tickOffset += scl->scaleDraw()->majTickLength(); + } + + canvasBorder[axis] = plot->canvas()->frameWidth() + + d_data->canvasMargin[axis] + 1; + + } + + + for ( axis = 0; axis < QwtPlot::axisCnt; axis++ ) + { + ScaleData &sd = scaleData[axis]; + if ( sd.w && (axis == QwtPlot::xBottom || axis == QwtPlot::xTop) ) + { + if ( (sd.minLeft > canvasBorder[QwtPlot::yLeft]) + && scaleData[QwtPlot::yLeft].w ) + { + int shiftLeft = sd.minLeft - canvasBorder[QwtPlot::yLeft]; + if ( shiftLeft > scaleData[QwtPlot::yLeft].w ) + shiftLeft = scaleData[QwtPlot::yLeft].w; + + sd.w -= shiftLeft; + } + if ( (sd.minRight > canvasBorder[QwtPlot::yRight]) + && scaleData[QwtPlot::yRight].w ) + { + int shiftRight = sd.minRight - canvasBorder[QwtPlot::yRight]; + if ( shiftRight > scaleData[QwtPlot::yRight].w ) + shiftRight = scaleData[QwtPlot::yRight].w; + + sd.w -= shiftRight; + } + } + + if ( sd.h && (axis == QwtPlot::yLeft || axis == QwtPlot::yRight) ) + { + if ( (sd.minLeft > canvasBorder[QwtPlot::xBottom]) && + scaleData[QwtPlot::xBottom].h ) + { + int shiftBottom = sd.minLeft - canvasBorder[QwtPlot::xBottom]; + if ( shiftBottom > scaleData[QwtPlot::xBottom].tickOffset ) + shiftBottom = scaleData[QwtPlot::xBottom].tickOffset; + + sd.h -= shiftBottom; + } + if ( (sd.minLeft > canvasBorder[QwtPlot::xTop]) && + scaleData[QwtPlot::xTop].h ) + { + int shiftTop = sd.minRight - canvasBorder[QwtPlot::xTop]; + if ( shiftTop > scaleData[QwtPlot::xTop].tickOffset ) + shiftTop = scaleData[QwtPlot::xTop].tickOffset; + + sd.h -= shiftTop; + } + } + } + + const QwtPlotCanvas *canvas = plot->canvas(); + const QSize minCanvasSize = canvas->minimumSize(); + + int w = scaleData[QwtPlot::yLeft].w + scaleData[QwtPlot::yRight].w; + int cw = qwtMax(scaleData[QwtPlot::xBottom].w, scaleData[QwtPlot::xTop].w) + + 2 * (canvas->frameWidth() + 1); + w += qwtMax(cw, minCanvasSize.width()); + + int h = scaleData[QwtPlot::xBottom].h + scaleData[QwtPlot::xTop].h; + int ch = qwtMax(scaleData[QwtPlot::yLeft].h, scaleData[QwtPlot::yRight].h) + + 2 * (canvas->frameWidth() + 1); + h += qwtMax(ch, minCanvasSize.height()); + + const QwtTextLabel *title = plot->titleLabel(); + if (title && !title->text().isEmpty()) + { + // If only QwtPlot::yLeft or QwtPlot::yRight is showing, + // we center on the plot canvas. + const bool centerOnCanvas = !(plot->axisEnabled(QwtPlot::yLeft) + && plot->axisEnabled(QwtPlot::yRight)); + + int titleW = w; + if ( centerOnCanvas ) + { + titleW -= scaleData[QwtPlot::yLeft].w + + scaleData[QwtPlot::yRight].w; + } + + int titleH = title->heightForWidth(titleW); + if ( titleH > titleW ) // Compensate for a long title + { + w = titleW = titleH; + if ( centerOnCanvas ) + { + w += scaleData[QwtPlot::yLeft].w + + scaleData[QwtPlot::yRight].w; + } + + titleH = title->heightForWidth(titleW); + } + h += titleH + d_data->spacing; + } + + // Compute the legend contribution + + const QwtLegend *legend = plot->legend(); + if ( d_data->legendPos != QwtPlot::ExternalLegend + && legend && !legend->isEmpty() ) + { + if ( d_data->legendPos == QwtPlot::LeftLegend + || d_data->legendPos == QwtPlot::RightLegend ) + { + int legendW = legend->sizeHint().width(); + int legendH = legend->heightForWidth(legendW); + + if ( legend->frameWidth() > 0 ) + w += d_data->spacing; + + if ( legendH > h ) + legendW += legend->verticalScrollBar()->sizeHint().height(); + + if ( d_data->legendRatio < 1.0 ) + legendW = qwtMin(legendW, int(w / (1.0 - d_data->legendRatio))); + + w += legendW; + } + else // QwtPlot::Top, QwtPlot::Bottom + { + int legendW = qwtMin(legend->sizeHint().width(), w); + int legendH = legend->heightForWidth(legendW); + + if ( legend->frameWidth() > 0 ) + h += d_data->spacing; + + if ( d_data->legendRatio < 1.0 ) + legendH = qwtMin(legendH, int(h / (1.0 - d_data->legendRatio))); + + h += legendH; + } + } + + w += 2 * d_data->margin; + h += 2 * d_data->margin; + + return QSize( w, h ); +} + +/*! + Find the geometry for the legend + \param options Options how to layout the legend + \param rect Rectangle where to place the legend + \return Geometry for the legend + \sa Options +*/ + +QRect QwtPlotLayout::layoutLegend(int options, + const QRect &rect) const +{ + const QSize hint(d_data->layoutData.legend.hint); + + int dim; + if ( d_data->legendPos == QwtPlot::LeftLegend + || d_data->legendPos == QwtPlot::RightLegend ) + { + // We don't allow vertical legends to take more than + // half of the available space. + + dim = qwtMin(hint.width(), int(rect.width() * d_data->legendRatio)); + + if ( !(options & IgnoreScrollbars) ) + { + if ( hint.height() > rect.height() ) + { + // The legend will need additional + // space for the vertical scrollbar. + + dim += d_data->layoutData.legend.vScrollBarWidth; + } + } + } + else + { + dim = qwtMin(hint.height(), int(rect.height() * d_data->legendRatio)); + dim = qwtMax(dim, d_data->layoutData.legend.hScrollBarHeight); + } + + QRect legendRect = rect; + switch(d_data->legendPos) + { + case QwtPlot::LeftLegend: + legendRect.setWidth(dim); + break; + case QwtPlot::RightLegend: + legendRect.setX(rect.right() - dim + 1); + legendRect.setWidth(dim); + break; + case QwtPlot::TopLegend: + legendRect.setHeight(dim); + break; + case QwtPlot::BottomLegend: + legendRect.setY(rect.bottom() - dim + 1); + legendRect.setHeight(dim); + break; + case QwtPlot::ExternalLegend: + break; + } + + return legendRect; +} + +/*! + Align the legend to the canvas + \param canvasRect Geometry of the canvas + \param legendRect Maximum geometry for the legend + \return Geometry for the aligned legend +*/ +QRect QwtPlotLayout::alignLegend(const QRect &canvasRect, + const QRect &legendRect) const +{ + QRect alignedRect = legendRect; + + if ( d_data->legendPos == QwtPlot::BottomLegend + || d_data->legendPos == QwtPlot::TopLegend ) + { + if ( d_data->layoutData.legend.hint.width() < canvasRect.width() ) + { + alignedRect.setX(canvasRect.x()); + alignedRect.setWidth(canvasRect.width()); + } + } + else + { + if ( d_data->layoutData.legend.hint.height() < canvasRect.height() ) + { + alignedRect.setY(canvasRect.y()); + alignedRect.setHeight(canvasRect.height()); + } + } + + return alignedRect; +} + +/*! + Expand all line breaks in text labels, and calculate the height + of their widgets in orientation of the text. + + \param options Options how to layout the legend + \param rect Bounding rect for title, axes and canvas. + \param dimTitle Expanded height of the title widget + \param dimAxis Expanded heights of the axis in axis orientation. + + \sa Options +*/ +void QwtPlotLayout::expandLineBreaks(int options, const QRect &rect, + int &dimTitle, int dimAxis[QwtPlot::axisCnt]) const +{ + dimTitle = 0; + for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) + dimAxis[axis] = 0; + + int backboneOffset[QwtPlot::axisCnt]; + for (int axis = 0; axis < QwtPlot::axisCnt; axis++ ) + { + backboneOffset[axis] = 0; + if ( !d_data->alignCanvasToScales ) + backboneOffset[axis] += d_data->canvasMargin[axis]; + if ( !(options & IgnoreFrames) ) + backboneOffset[axis] += d_data->layoutData.canvas.frameWidth; + } + + 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 ( !d_data->layoutData.title.text.isEmpty() ) + { + int w = rect.width(); + + if ( d_data->layoutData.scale[QwtPlot::yLeft].isEnabled + != d_data->layoutData.scale[QwtPlot::yRight].isEnabled ) + { + // center to the canvas + w -= dimAxis[QwtPlot::yLeft] + dimAxis[QwtPlot::yRight]; + } + + int d = d_data->layoutData.title.text.heightForWidth(w); + if ( !(options & IgnoreFrames) ) + d += 2 * d_data->layoutData.title.frameWidth; + + if ( d > dimTitle ) + { + dimTitle = d; + done = false; + } + } + + for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) + { + const struct LayoutData::t_scaleData &scaleData = + d_data->layoutData.scale[axis]; + + if (scaleData.isEnabled) + { + int length; + if ( axis == QwtPlot::xTop || axis == QwtPlot::xBottom ) + { + length = rect.width() - dimAxis[QwtPlot::yLeft] + - dimAxis[QwtPlot::yRight]; + length -= scaleData.start + scaleData.end; + + if ( dimAxis[QwtPlot::yRight] > 0 ) + length -= 1; + + length += qwtMin(dimAxis[QwtPlot::yLeft], + scaleData.start - backboneOffset[QwtPlot::yLeft]); + length += qwtMin(dimAxis[QwtPlot::yRight], + scaleData.end - backboneOffset[QwtPlot::yRight]); + } + else // QwtPlot::yLeft, QwtPlot::yRight + { + length = rect.height() - dimAxis[QwtPlot::xTop] + - dimAxis[QwtPlot::xBottom]; + length -= scaleData.start + scaleData.end; + length -= 1; + + if ( dimAxis[QwtPlot::xBottom] <= 0 ) + length -= 1; + if ( dimAxis[QwtPlot::xTop] <= 0 ) + length -= 1; + + if ( dimAxis[QwtPlot::xBottom] > 0 ) + { + length += qwtMin( + d_data->layoutData.scale[QwtPlot::xBottom].tickOffset, + scaleData.start - backboneOffset[QwtPlot::xBottom]); + } + if ( dimAxis[QwtPlot::xTop] > 0 ) + { + length += qwtMin( + d_data->layoutData.scale[QwtPlot::xTop].tickOffset, + scaleData.end - backboneOffset[QwtPlot::xTop]); + } + + if ( dimTitle > 0 ) + length -= dimTitle + d_data->spacing; + } + + int d = scaleData.dimWithoutTitle; + if ( !scaleData.scaleWidget->title().isEmpty() ) + { + d += scaleData.scaleWidget->titleHeightForWidth(length); + } + + + if ( d > dimAxis[axis] ) + { + dimAxis[axis] = d; + done = false; + } + } + } + } +} + +/*! + Align the ticks of the axis to the canvas borders using + the empty corners. + + \sa Options +*/ + +void QwtPlotLayout::alignScales(int options, + QRect &canvasRect, QRect scaleRect[QwtPlot::axisCnt]) const +{ + int axis; + + int backboneOffset[QwtPlot::axisCnt]; + for (axis = 0; axis < QwtPlot::axisCnt; axis++ ) + { + backboneOffset[axis] = 0; + if ( !d_data->alignCanvasToScales ) + backboneOffset[axis] += d_data->canvasMargin[axis]; + if ( !(options & IgnoreFrames) ) + backboneOffset[axis] += d_data->layoutData.canvas.frameWidth; + } + + for (axis = 0; axis < QwtPlot::axisCnt; axis++ ) + { + if ( !scaleRect[axis].isValid() ) + continue; + + const int startDist = d_data->layoutData.scale[axis].start; + const int endDist = d_data->layoutData.scale[axis].end; + + QRect &axisRect = scaleRect[axis]; + + if ( axis == QwtPlot::xTop || axis == QwtPlot::xBottom ) + { + const int leftOffset = + backboneOffset[QwtPlot::yLeft] - startDist; + + if ( scaleRect[QwtPlot::yLeft].isValid() ) + { + int minLeft = scaleRect[QwtPlot::yLeft].left(); + int left = axisRect.left() + leftOffset; + axisRect.setLeft(qwtMax(left, minLeft)); + } + else + { + if ( d_data->alignCanvasToScales && leftOffset < 0 ) + { + canvasRect.setLeft(qwtMax(canvasRect.left(), + axisRect.left() - leftOffset)); + } + else + { + if ( leftOffset > 0 ) + axisRect.setLeft(axisRect.left() + leftOffset); + } + } + + const int rightOffset = + backboneOffset[QwtPlot::yRight] - endDist + 1; + + if ( scaleRect[QwtPlot::yRight].isValid() ) + { + int maxRight = scaleRect[QwtPlot::yRight].right(); + int right = axisRect.right() - rightOffset; + axisRect.setRight(qwtMin(right, maxRight)); + } + else + { + if ( d_data->alignCanvasToScales && rightOffset < 0 ) + { + canvasRect.setRight( qwtMin(canvasRect.right(), + axisRect.right() + rightOffset) ); + } + else + { + if ( rightOffset > 0 ) + axisRect.setRight(axisRect.right() - rightOffset); + } + } + } + else // QwtPlot::yLeft, QwtPlot::yRight + { + const int bottomOffset = + backboneOffset[QwtPlot::xBottom] - endDist + 1; + + if ( scaleRect[QwtPlot::xBottom].isValid() ) + { + int maxBottom = scaleRect[QwtPlot::xBottom].top() + + d_data->layoutData.scale[QwtPlot::xBottom].tickOffset; + + int bottom = axisRect.bottom() - bottomOffset; + axisRect.setBottom(qwtMin(bottom, maxBottom)); + } + else + { + if ( d_data->alignCanvasToScales && bottomOffset < 0 ) + { + canvasRect.setBottom(qwtMin(canvasRect.bottom(), + axisRect.bottom() + bottomOffset)); + } + else + { + if ( bottomOffset > 0 ) + axisRect.setBottom(axisRect.bottom() - bottomOffset); + } + } + + const int topOffset = backboneOffset[QwtPlot::xTop] - startDist; + + if ( scaleRect[QwtPlot::xTop].isValid() ) + { + int minTop = scaleRect[QwtPlot::xTop].bottom() - + d_data->layoutData.scale[QwtPlot::xTop].tickOffset; + + int top = axisRect.top() + topOffset; + axisRect.setTop(qwtMax(top, minTop)); + } + else + { + if ( d_data->alignCanvasToScales && topOffset < 0 ) + { + canvasRect.setTop(qwtMax(canvasRect.top(), + axisRect.top() - topOffset)); + } + else + { + if ( topOffset > 0 ) + axisRect.setTop(axisRect.top() + topOffset); + } + } + } + } + + if ( d_data->alignCanvasToScales ) + { + /* + The canvas has been aligned to the scale with largest + border distances. Now we have to realign the other scale. + */ + + int fw = 0; + if ( !(options & IgnoreFrames) ) + fw = d_data->layoutData.canvas.frameWidth; + + if ( scaleRect[QwtPlot::xBottom].isValid() && + scaleRect[QwtPlot::xTop].isValid() ) + { + for ( int axis = QwtPlot::xBottom; axis <= QwtPlot::xTop; axis++ ) + { + scaleRect[axis].setLeft(canvasRect.left() + fw + - d_data->layoutData.scale[axis].start); + scaleRect[axis].setRight(canvasRect.right() - fw - 1 + + d_data->layoutData.scale[axis].end); + } + } + + if ( scaleRect[QwtPlot::yLeft].isValid() && + scaleRect[QwtPlot::yRight].isValid() ) + { + for ( int axis = QwtPlot::yLeft; axis <= QwtPlot::yRight; axis++ ) + { + scaleRect[axis].setTop(canvasRect.top() + fw + - d_data->layoutData.scale[axis].start); + scaleRect[axis].setBottom(canvasRect.bottom() - fw - 1 + + d_data->layoutData.scale[axis].end); + } + } + } +} + +/*! + \brief Recalculate the geometry of all components. + + \param plot Plot to be layout + \param plotRect Rect where to place the components + \param options Options + + \sa invalidate(), Options, titleRect(), + legendRect(), scaleRect(), canvasRect() +*/ +void QwtPlotLayout::activate(const QwtPlot *plot, + const QRect &plotRect, int options) +{ + invalidate(); + + QRect rect(plotRect); // undistributed rest of the plot rect + + if ( !(options & IgnoreMargin) ) + { + // subtract the margin + + rect.setRect( + rect.x() + d_data->margin, + rect.y() + d_data->margin, + rect.width() - 2 * d_data->margin, + rect.height() - 2 * d_data->margin + ); + } + + // We extract all layout relevant data from the widgets, + // filter them through pfilter and save them to d_data->layoutData. + + d_data->layoutData.init(plot, rect); + + if (!(options & IgnoreLegend) + && d_data->legendPos != QwtPlot::ExternalLegend + && plot->legend() && !plot->legend()->isEmpty() ) + { + d_data->legendRect = layoutLegend(options, rect); + + // subtract d_data->legendRect from rect + + const QRegion region(rect); + rect = region.subtract(d_data->legendRect).boundingRect(); + + if ( d_data->layoutData.legend.frameWidth && + !(options & IgnoreFrames ) ) + { + // In case of a frame we have to insert a spacing. + // Otherwise the leading of the font separates + // legend and scale/canvas + + switch(d_data->legendPos) + { + case QwtPlot::LeftLegend: + rect.setLeft(rect.left() + d_data->spacing); + break; + case QwtPlot::RightLegend: + rect.setRight(rect.right() - d_data->spacing); + break; + case QwtPlot::TopLegend: + rect.setTop(rect.top() + d_data->spacing); + break; + case QwtPlot::BottomLegend: + rect.setBottom(rect.bottom() - d_data->spacing); + break; + case QwtPlot::ExternalLegend: + break; // suppress compiler warning + } + } + } + +#ifdef __GNUC__ +#warning Layout code needs to be reorganized +#endif + /* + +---+-----------+---+ + | Title | + +---+-----------+---+ + | | Axis | | + +---+-----------+---+ + | A | | A | + | x | Canvas | x | + | i | | i | + | s | | s | + +---+-----------+---+ + | | Axis | | + +---+-----------+---+ + */ + + // axes and title 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. + // expandLineBreaks finds the height/width for title and axes + // including all line breaks. + + int dimTitle, dimAxes[QwtPlot::axisCnt]; + expandLineBreaks(options, rect, dimTitle, dimAxes); + + if (dimTitle > 0 ) + { + d_data->titleRect = QRect(rect.x(), rect.y(), + rect.width(), dimTitle); + + if ( d_data->layoutData.scale[QwtPlot::yLeft].isEnabled != + d_data->layoutData.scale[QwtPlot::yRight].isEnabled ) + { + // if only one of the y axes is missing we align + // the title centered to the canvas + + d_data->titleRect.setX(rect.x() + dimAxes[QwtPlot::yLeft]); + d_data->titleRect.setWidth(rect.width() + - dimAxes[QwtPlot::yLeft] - dimAxes[QwtPlot::yRight]); + } + + // subtract title + rect.setTop(rect.top() + dimTitle + d_data->spacing); + } + + d_data->canvasRect.setRect( + rect.x() + dimAxes[QwtPlot::yLeft], + rect.y() + dimAxes[QwtPlot::xTop], + rect.width() - dimAxes[QwtPlot::yRight] - dimAxes[QwtPlot::yLeft], + rect.height() - dimAxes[QwtPlot::xBottom] - dimAxes[QwtPlot::xTop]); + + for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) + { + // set the rects for the axes + + if ( dimAxes[axis] ) + { + int dim = dimAxes[axis]; + QRect &scaleRect = d_data->scaleRect[axis]; + + scaleRect = d_data->canvasRect; + switch(axis) + { + case QwtPlot::yLeft: + scaleRect.setX(d_data->canvasRect.left() - dim); + scaleRect.setWidth(dim); + break; + case QwtPlot::yRight: + scaleRect.setX(d_data->canvasRect.right() + 1); + scaleRect.setWidth(dim); + break; + case QwtPlot::xBottom: + scaleRect.setY(d_data->canvasRect.bottom() + 1); + scaleRect.setHeight(dim); + break; + case QwtPlot::xTop: + scaleRect.setY(d_data->canvasRect.top() - dim); + scaleRect.setHeight(dim); + break; + } +#if QT_VERSION < 0x040000 + scaleRect = scaleRect.normalize(); +#else + scaleRect = scaleRect.normalized(); +#endif + } + } + + // +---+-----------+---+ + // | <- 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. + + alignScales(options, d_data->canvasRect, d_data->scaleRect); + + if (!d_data->legendRect.isEmpty() ) + { + // We prefer to align the legend to the canvas - not to + // the complete plot - if possible. + + d_data->legendRect = alignLegend(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..22fa50752 --- /dev/null +++ b/qwt/src/qwt_plot_layout.h @@ -0,0 +1,108 @@ +/* -*- 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" + +/*! + \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. +*/ + +class QWT_EXPORT QwtPlotLayout +{ +public: + /*! + Options to configure the plot layout engine + + - AlignScales\n + Unused + - IgnoreScrollbars\n + Ignore the dimension of the scrollbars. There are no + scrollbars, when the plot is rendered to a paint device + (QwtPlot::print() ). + - IgnoreFrames\n + Ignore all frames. QwtPlot::print() doesn't paint them. + - IgnoreMargin\n + Ignore the margin(). + - IgnoreLegend\n + Ignore the legend. + + \sa activate() + */ + enum Options + { + AlignScales = 1, + IgnoreScrollbars = 2, + IgnoreFrames = 4, + IgnoreMargin = 8, + IgnoreLegend = 16 + }; + + explicit QwtPlotLayout(); + virtual ~QwtPlotLayout(); + + void setMargin(int); + int margin() const; + + void setCanvasMargin(int margin, int axis = -1); + int canvasMargin(int axis) const; + + void setAlignCanvasToScales(bool); + bool alignCanvasToScales() const; + + void setSpacing(int); + int spacing() const; + + void setLegendPosition(QwtPlot::LegendPosition pos, double ratio); + void setLegendPosition(QwtPlot::LegendPosition pos); + QwtPlot::LegendPosition legendPosition() const; + + void setLegendRatio(double ratio); + double legendRatio() const; + + virtual QSize minimumSizeHint(const QwtPlot *) const; + + virtual void activate(const QwtPlot *, + const QRect &rect, int options = 0); + + virtual void invalidate(); + + const QRect &titleRect() const; + const QRect &legendRect() const; + const QRect &scaleRect(int axis) const; + const QRect &canvasRect() const; + + class LayoutData; + +protected: + + QRect layoutLegend(int options, const QRect &) const; + QRect alignLegend(const QRect &canvasRect, + const QRect &legendRect) const; + + void expandLineBreaks(int options, const QRect &rect, + int &dimTitle, int dimAxes[QwtPlot::axisCnt]) const; + + void alignScales(int options, QRect &canvasRect, + QRect scaleRect[QwtPlot::axisCnt]) const; + +private: + class PrivateData; + + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_magnifier.cpp b/qwt/src/qwt_plot_magnifier.cpp new file mode 100644 index 000000000..8c71bf75d --- /dev/null +++ b/qwt/src/qwt_plot_magnifier.cpp @@ -0,0 +1,150 @@ +/* -*- 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 "qwt_plot.h" +#include "qwt_plot_canvas.h" +#include "qwt_scale_div.h" +#include "qwt_plot_magnifier.h" + +class QwtPlotMagnifier::PrivateData +{ +public: + PrivateData() + { + for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) + isAxisEnabled[axis] = true; + } + + bool isAxisEnabled[QwtPlot::axisCnt]; +}; + +/*! + Constructor + \param canvas Plot canvas to be magnified +*/ +QwtPlotMagnifier::QwtPlotMagnifier(QwtPlotCanvas *canvas): + QwtMagnifier(canvas) +{ + d_data = new PrivateData(); +} + +//! Destructor +QwtPlotMagnifier::~QwtPlotMagnifier() +{ + delete d_data; +} + +/*! + \brief En/Disable an axis + + Axes that are enabled will be synchronized to the + result of panning. All other axes will remain unchanged. + + \param axis Axis, see QwtPlot::Axis + \param on On/Off + + \sa isAxisEnabled() +*/ +void QwtPlotMagnifier::setAxisEnabled(int axis, bool on) +{ + if ( axis >= 0 && axis < QwtPlot::axisCnt ) + d_data->isAxisEnabled[axis] = on; +} + +/*! + Test if an axis is enabled + + \param axis Axis, see QwtPlot::Axis + \return True, if the axis is enabled + + \sa setAxisEnabled() +*/ +bool QwtPlotMagnifier::isAxisEnabled(int axis) const +{ + if ( axis >= 0 && axis < QwtPlot::axisCnt ) + return d_data->isAxisEnabled[axis]; + + return true; +} + +//! Return observed plot canvas +QwtPlotCanvas *QwtPlotMagnifier::canvas() +{ + QObject *w = parent(); + if ( w && w->inherits("QwtPlotCanvas") ) + return (QwtPlotCanvas *)w; + + return NULL; +} + +//! Return Observed plot canvas +const QwtPlotCanvas *QwtPlotMagnifier::canvas() const +{ + return ((QwtPlotMagnifier *)this)->canvas(); +} + +//! Return plot widget, containing the observed plot canvas +QwtPlot *QwtPlotMagnifier::plot() +{ + QObject *w = canvas(); + if ( w ) + { + w = w->parent(); + if ( w && w->inherits("QwtPlot") ) + return (QwtPlot *)w; + } + + return NULL; +} + +//! Return plot widget, containing the observed plot canvas +const QwtPlot *QwtPlotMagnifier::plot() const +{ + return ((QwtPlotMagnifier *)this)->plot(); +} + +/*! + Zoom in/out the axes scales + \param factor A value < 1.0 zooms in, a value > 1.0 zooms out. +*/ +void QwtPlotMagnifier::rescale(double factor) +{ + factor = qwtAbs(factor); + if ( factor == 1.0 || factor == 0.0 ) + return; + + bool doReplot = false; + QwtPlot* plt = plot(); + + const bool autoReplot = plt->autoReplot(); + plt->setAutoReplot(false); + + for ( int axisId = 0; axisId < QwtPlot::axisCnt; axisId++ ) + { + const QwtScaleDiv *scaleDiv = plt->axisScaleDiv(axisId); + if ( isAxisEnabled(axisId) && scaleDiv->isValid() ) + { + const double center = + scaleDiv->lowerBound() + scaleDiv->range() / 2; + const double width_2 = scaleDiv->range() / 2 * factor; + + plt->setAxisScale(axisId, center - width_2, center + width_2); + doReplot = true; + } + } + + plt->setAutoReplot(autoReplot); + + if ( doReplot ) + plt->replot(); +} diff --git a/qwt/src/qwt_plot_magnifier.h b/qwt/src/qwt_plot_magnifier.h new file mode 100644 index 000000000..2e3239560 --- /dev/null +++ b/qwt/src/qwt_plot_magnifier.h @@ -0,0 +1,55 @@ +/* -*- 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_MAGNIFIER_H +#define QWT_PLOT_MAGNIFIER_H 1 + +#include "qwt_global.h" +#include "qwt_magnifier.h" + +class QwtPlotCanvas; +class QwtPlot; + +/*! + \brief QwtPlotMagnifier provides zooming, by magnifying in steps. + + Using QwtPlotMagnifier a plot can be zoomed in/out in steps using + keys, the mouse wheel or moving a mouse button in vertical direction. + + Together with QwtPlotZoomer and QwtPlotPanner it is possible to implement + individual and powerful navigation of the plot canvas. + + \sa QwtPlotZoomer, QwtPlotPanner, QwtPlot +*/ +class QWT_EXPORT QwtPlotMagnifier: public QwtMagnifier +{ + Q_OBJECT + +public: + explicit QwtPlotMagnifier(QwtPlotCanvas *); + virtual ~QwtPlotMagnifier(); + + void setAxisEnabled(int axis, bool on); + bool isAxisEnabled(int axis) const; + + QwtPlotCanvas *canvas(); + const QwtPlotCanvas *canvas() const; + + QwtPlot *plot(); + const QwtPlot *plot() const; + +protected: + virtual void rescale(double factor); + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_marker.cpp b/qwt/src/qwt_plot_marker.cpp new file mode 100644 index 000000000..b1deb44e6 --- /dev/null +++ b/qwt/src/qwt_plot_marker.cpp @@ -0,0 +1,506 @@ +/* -*- 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 "qwt_painter.h" +#include "qwt_scale_map.h" +#include "qwt_plot_marker.h" +#include "qwt_symbol.h" +#include "qwt_text.h" +#include "qwt_math.h" + +class QwtPlotMarker::PrivateData +{ +public: + PrivateData(): + labelAlignment(Qt::AlignCenter), + labelOrientation(Qt::Horizontal), + spacing(2), + style(NoLine), + xValue(0.0), + yValue(0.0) + { + symbol = new QwtSymbol(); + } + + ~PrivateData() + { + delete symbol; + } + + QwtText label; +#if QT_VERSION < 0x040000 + int labelAlignment; +#else + Qt::Alignment labelAlignment; +#endif + Qt::Orientation labelOrientation; + int spacing; + + QPen pen; + QwtSymbol *symbol; + LineStyle style; + + double xValue; + double yValue; +}; + +//! Sets alignment to Qt::AlignCenter, and style to NoLine +QwtPlotMarker::QwtPlotMarker(): + QwtPlotItem(QwtText("Marker")) +{ + d_data = new PrivateData; + setZ(30.0); +} + +//! Destructor +QwtPlotMarker::~QwtPlotMarker() +{ + delete d_data; +} + +//! \return QwtPlotItem::Rtti_PlotMarker +int QwtPlotMarker::rtti() const +{ + return QwtPlotItem::Rtti_PlotMarker; +} + +//! Return Value +QwtDoublePoint QwtPlotMarker::value() const +{ + return QwtDoublePoint(d_data->xValue, d_data->yValue); +} + +//! Return x Value +double QwtPlotMarker::xValue() const +{ + return d_data->xValue; +} + +//! Return y Value +double QwtPlotMarker::yValue() const +{ + return d_data->yValue; +} + +//! Set Value +void QwtPlotMarker::setValue(const QwtDoublePoint& pos) +{ + setValue(pos.x(), pos.y()); +} + +//! Set Value +void QwtPlotMarker::setValue(double x, double y) +{ + if ( x != d_data->xValue || y != d_data->yValue ) + { + d_data->xValue = x; + d_data->yValue = y; + itemChanged(); + } +} + +//! Set X Value +void QwtPlotMarker::setXValue(double x) +{ + setValue(x, d_data->yValue); +} + +//! Set Y Value +void QwtPlotMarker::setYValue(double y) +{ + setValue(d_data->xValue, y); +} + +/*! + Draw the marker + + \param painter Painter + \param xMap x Scale Map + \param yMap y Scale Map + \param canvasRect Contents rect of the canvas in painter coordinates +*/ +void QwtPlotMarker::draw(QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRect &canvasRect) const +{ + const int x = xMap.transform(d_data->xValue); + const int y = yMap.transform(d_data->yValue); + + drawAt(painter, canvasRect, QPoint(x, y)); +} + +/*! + Draw the marker at a specific position + + \param painter Painter + \param canvasRect Contents rect of the canvas in painter coordinates + \param pos Position of the marker in painter coordinates +*/ +void QwtPlotMarker::drawAt(QPainter *painter, + const QRect &canvasRect, const QPoint &pos) const +{ + // draw lines + if (d_data->style != NoLine) + { + painter->setPen(QwtPainter::scaledPen(d_data->pen)); + if ( d_data->style == QwtPlotMarker::HLine || + d_data->style == QwtPlotMarker::Cross ) + { + QwtPainter::drawLine(painter, canvasRect.left(), + pos.y(), canvasRect.right(), pos.y() ); + } + if (d_data->style == QwtPlotMarker::VLine || + d_data->style == QwtPlotMarker::Cross ) + { + QwtPainter::drawLine(painter, pos.x(), + canvasRect.top(), pos.x(), canvasRect.bottom()); + } + } + + // draw symbol + if (d_data->symbol->style() != QwtSymbol::NoSymbol) + d_data->symbol->draw(painter, pos.x(), pos.y()); + + drawLabel(painter, canvasRect, pos); +} + +void QwtPlotMarker::drawLabel(QPainter *painter, + const QRect &canvasRect, const QPoint &pos) const +{ + if (d_data->label.isEmpty()) + return; + + int align = d_data->labelAlignment; + QPoint alignPos = pos; + + QSize symbolOff(0, 0); + + switch(d_data->style) + { + case QwtPlotMarker::VLine: + { + // In VLine-style the y-position is pointless and + // the alignment flags are relative to the canvas + + if (d_data->labelAlignment & (int) Qt::AlignTop) + { + alignPos.setY(canvasRect.top()); + align &= ~Qt::AlignTop; + align |= Qt::AlignBottom; + } + else if (d_data->labelAlignment & (int) Qt::AlignBottom) + { + // In HLine-style the x-position is pointless and + // the alignment flags are relative to the canvas + + alignPos.setY(canvasRect.bottom() - 1); + align &= ~Qt::AlignBottom; + align |= Qt::AlignTop; + } + else + { + alignPos.setY(canvasRect.center().y()); + } + break; + } + case QwtPlotMarker::HLine: + { + if (d_data->labelAlignment & (int) Qt::AlignLeft) + { + alignPos.setX(canvasRect.left()); + align &= ~Qt::AlignLeft; + align |= Qt::AlignRight; + } + else if (d_data->labelAlignment & (int) Qt::AlignRight) + { + alignPos.setX(canvasRect.right() - 1); + align &= ~Qt::AlignRight; + align |= Qt::AlignLeft; + } + else + { + alignPos.setX(canvasRect.center().x()); + } + break; + } + default: + { + if ( d_data->symbol->style() != QwtSymbol::NoSymbol ) + { + symbolOff = d_data->symbol->size() + QSize(1, 1); + symbolOff /= 2; + } + } + } + + int pw = d_data->pen.width(); + if ( pw == 0 ) + pw = 1; + + const int xSpacing = + QwtPainter::metricsMap().screenToLayoutX(d_data->spacing); + const int ySpacing = + QwtPainter::metricsMap().screenToLayoutY(d_data->spacing); + + + int xOff = qwtMax( (pw + 1) / 2, symbolOff.width() ); + int yOff = qwtMax( (pw + 1) / 2, symbolOff.height() ); + + const QSize textSize = d_data->label.textSize(painter->font()); + + if ( align & Qt::AlignLeft ) + { + alignPos.rx() -= xOff + xSpacing; + if ( d_data->labelOrientation == Qt::Vertical ) + alignPos.rx() -= textSize.height(); + else + alignPos.rx() -= textSize.width(); + } + else if ( align & Qt::AlignRight ) + { + alignPos.rx() += xOff + xSpacing; + } + else + { + if ( d_data->labelOrientation == Qt::Vertical ) + alignPos.rx() -= textSize.height() / 2; + else + alignPos.rx() -= textSize.width() / 2; + } + + if (align & (int) Qt::AlignTop) + { + alignPos.ry() -= yOff + ySpacing; + if ( d_data->labelOrientation != Qt::Vertical ) + alignPos.ry() -= textSize.height(); + } + else if (align & (int) Qt::AlignBottom) + { + alignPos.ry() += yOff + ySpacing; + if ( d_data->labelOrientation == Qt::Vertical ) + alignPos.ry() += textSize.width(); + } + else + { + if ( d_data->labelOrientation == Qt::Vertical ) + alignPos.ry() += textSize.width() / 2; + else + alignPos.ry() -= textSize.height() / 2; + } + + painter->translate(alignPos.x(), alignPos.y()); + if ( d_data->labelOrientation == Qt::Vertical ) + painter->rotate(-90.0); + + const QRect textRect(0, 0, textSize.width(), textSize.height()); + d_data->label.draw(painter, textRect); +} + +/*! + \brief Set the line style + \param st Line style. Can be one of QwtPlotMarker::NoLine, + HLine, VLine or Cross + \sa lineStyle() +*/ +void QwtPlotMarker::setLineStyle(QwtPlotMarker::LineStyle st) +{ + if ( st != d_data->style ) + { + d_data->style = st; + itemChanged(); + } +} + +/*! + \return the line style + \sa For a description of line styles, see QwtPlotMarker::setLineStyle() +*/ +QwtPlotMarker::LineStyle QwtPlotMarker::lineStyle() const +{ + return d_data->style; +} + +/*! + \brief Assign a symbol + \param s New symbol + \sa symbol() +*/ +void QwtPlotMarker::setSymbol(const QwtSymbol &s) +{ + delete d_data->symbol; + d_data->symbol = s.clone(); + itemChanged(); +} + +/*! + \return the symbol + \sa setSymbol(), QwtSymbol +*/ +const QwtSymbol &QwtPlotMarker::symbol() const +{ + return *d_data->symbol; +} + +/*! + \brief Set the label + \param label label text + \sa label() +*/ +void QwtPlotMarker::setLabel(const QwtText& label) +{ + if ( label != d_data->label ) + { + d_data->label = label; + itemChanged(); + } +} + +/*! + \return the label + \sa setLabel() +*/ +QwtText QwtPlotMarker::label() const +{ + return d_data->label; +} + +/*! + \brief Set the alignment of the label + + In case of QwtPlotMarker::HLine the alignment is relative to the + y position of the marker, but the horizontal flags correspond to the + canvas rectangle. In case of QwtPlotMarker::VLine the alignment is + relative to the x position of the marker, but the vertical flags + correspond to the canvas rectangle. + + In all other styles the alignment is relative to the marker's position. + + \param align Alignment. A combination of AlignTop, AlignBottom, + AlignLeft, AlignRight, AlignCenter, AlgnHCenter, + AlignVCenter. + \sa labelAlignment(), labelOrientation() +*/ +#if QT_VERSION < 0x040000 +void QwtPlotMarker::setLabelAlignment(int align) +#else +void QwtPlotMarker::setLabelAlignment(Qt::Alignment align) +#endif +{ + if ( align != d_data->labelAlignment ) + { + d_data->labelAlignment = align; + itemChanged(); + } +} + +/*! + \return the label alignment + \sa setLabelAlignment(), setLabelOrientation() +*/ +#if QT_VERSION < 0x040000 +int QwtPlotMarker::labelAlignment() const +#else +Qt::Alignment QwtPlotMarker::labelAlignment() const +#endif +{ + return d_data->labelAlignment; +} + +/*! + \brief Set the orientation of the label + + When orientation is Qt::Vertical the label is rotated by 90.0 degrees + ( from bottom to top ). + + \param orientation Orientation of the label + + \sa labelOrientation(), setLabelAlignment() +*/ +void QwtPlotMarker::setLabelOrientation(Qt::Orientation orientation) +{ + if ( orientation != d_data->labelOrientation ) + { + d_data->labelOrientation = orientation; + itemChanged(); + } +} + +/*! + \return the label orientation + \sa setLabelOrientation(), labelAlignment() +*/ +Qt::Orientation QwtPlotMarker::labelOrientation() const +{ + return d_data->labelOrientation; +} + +/*! + \brief Set the spacing + + When the label is not centered on the marker position, the spacing + is the distance between the position and the label. + + \param spacing Spacing + \sa spacing(), setLabelAlignment() +*/ +void QwtPlotMarker::setSpacing(int spacing) +{ + if ( spacing < 0 ) + spacing = 0; + + if ( spacing == d_data->spacing ) + return; + + d_data->spacing = spacing; + itemChanged(); +} + +/*! + \return the spacing + \sa setSpacing() +*/ +int QwtPlotMarker::spacing() const +{ + return d_data->spacing; +} + +/*! + Specify a pen for the line. + + The width of non cosmetic pens is scaled according to the resolution + of the paint device. + + \param pen New pen + \sa linePen(), QwtPainter::scaledPen() +*/ +void QwtPlotMarker::setLinePen(const QPen &pen) +{ + if ( pen != d_data->pen ) + { + d_data->pen = pen; + itemChanged(); + } +} + +/*! + \return the line pen + \sa setLinePen() +*/ +const QPen &QwtPlotMarker::linePen() const +{ + return d_data->pen; +} + +QwtDoubleRect QwtPlotMarker::boundingRect() const +{ + return QwtDoubleRect(d_data->xValue, d_data->yValue, 0.0, 0.0); +} diff --git a/qwt/src/qwt_plot_marker.h b/qwt/src/qwt_plot_marker.h new file mode 100644 index 000000000..e4ce77010 --- /dev/null +++ b/qwt/src/qwt_plot_marker.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 + *****************************************************************************/ + +// vim: expandtab + +#ifndef QWT_PLOT_MARKER_H +#define QWT_PLOT_MARKER_H + +#include +#include +#include +#include +#include "qwt_global.h" +#include "qwt_plot_item.h" + +class QRect; +class QwtText; +class QwtSymbol; + +/*! + \brief A class for drawing markers + + A marker can be a horizontal line, a vertical line, + a symbol, a label or any combination of them, which can + be drawn around a center point inside a bounding rectangle. + + The QwtPlotMarker::setSymbol() member assigns a symbol to the marker. + The symbol is drawn at the specified point. + + With QwtPlotMarker::setLabel(), a label can be assigned to the marker. + The QwtPlotMarker::setLabelAlignment() member specifies where the label is + drawn. All the Align*-constants in Qt::AlignmentFlags (see Qt documentation) + are valid. The interpretation of the alignment depends on the marker's + line style. The alignment refers to the center point of + the marker, which means, for example, that the label would be printed + left above the center point if the alignment was set to AlignLeft|AlignTop. +*/ + +class QWT_EXPORT QwtPlotMarker: public QwtPlotItem +{ +public: + + /*! + Line styles. + \sa setLineStyle(), lineStyle() + */ + enum LineStyle + { + NoLine, + HLine, + VLine, + Cross + }; + + explicit QwtPlotMarker(); + virtual ~QwtPlotMarker(); + + virtual int rtti() const; + + double xValue() const; + double yValue() const; + QwtDoublePoint value() const; + + void setXValue(double); + void setYValue(double); + void setValue(double, double); + void setValue(const QwtDoublePoint &); + + void setLineStyle(LineStyle st); + LineStyle lineStyle() const; + + void setLinePen(const QPen &p); + const QPen &linePen() const; + + void setSymbol(const QwtSymbol &s); + const QwtSymbol &symbol() const; + + void setLabel(const QwtText&); + QwtText label() const; + +#if QT_VERSION < 0x040000 + void setLabelAlignment(int align); + int labelAlignment() const; +#else + void setLabelAlignment(Qt::Alignment); + Qt::Alignment labelAlignment() const; +#endif + + void setLabelOrientation(Qt::Orientation); + Qt::Orientation labelOrientation() const; + + void setSpacing(int); + int spacing() const; + + virtual void draw(QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRect &) const; + + virtual QwtDoubleRect boundingRect() const; + +protected: + void drawAt(QPainter *,const QRect &, const QPoint &) const; + +private: + void drawLabel(QPainter *, const QRect &, const QPoint &) const; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_panner.cpp b/qwt/src/qwt_plot_panner.cpp new file mode 100644 index 000000000..0c9c8b042 --- /dev/null +++ b/qwt/src/qwt_plot_panner.cpp @@ -0,0 +1,169 @@ +/* -*- 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 "qwt_scale_div.h" +#include "qwt_plot.h" +#include "qwt_plot_canvas.h" +#include "qwt_plot_panner.h" + +class QwtPlotPanner::PrivateData +{ +public: + PrivateData() + { + for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) + isAxisEnabled[axis] = true; + } + + bool isAxisEnabled[QwtPlot::axisCnt]; +}; + +/*! + \brief Create a plot panner + + The panner is enabled for all axes + + \param canvas Plot canvas to pan, also the parent object + + \sa setAxisEnabled() +*/ +QwtPlotPanner::QwtPlotPanner(QwtPlotCanvas *canvas): + QwtPanner(canvas) +{ + d_data = new PrivateData(); + + connect(this, SIGNAL(panned(int, int)), + SLOT(moveCanvas(int, int))); +} + +//! Destructor +QwtPlotPanner::~QwtPlotPanner() +{ + delete d_data; +} + +/*! + \brief En/Disable an axis + + Axes that are enabled will be synchronized to the + result of panning. All other axes will remain unchanged. + + \param axis Axis, see QwtPlot::Axis + \param on On/Off + + \sa isAxisEnabled(), moveCanvas() +*/ +void QwtPlotPanner::setAxisEnabled(int axis, bool on) +{ + if ( axis >= 0 && axis < QwtPlot::axisCnt ) + d_data->isAxisEnabled[axis] = on; +} + +/*! + Test if an axis is enabled + + \param axis Axis, see QwtPlot::Axis + \return True, if the axis is enabled + + \sa setAxisEnabled(), moveCanvas() +*/ +bool QwtPlotPanner::isAxisEnabled(int axis) const +{ + if ( axis >= 0 && axis < QwtPlot::axisCnt ) + return d_data->isAxisEnabled[axis]; + + return true; +} + +//! Return observed plot canvas +QwtPlotCanvas *QwtPlotPanner::canvas() +{ + QWidget *w = parentWidget(); + if ( w && w->inherits("QwtPlotCanvas") ) + return (QwtPlotCanvas *)w; + + return NULL; +} + +//! Return Observed plot canvas +const QwtPlotCanvas *QwtPlotPanner::canvas() const +{ + return ((QwtPlotPanner *)this)->canvas(); +} + +//! Return plot widget, containing the observed plot canvas +QwtPlot *QwtPlotPanner::plot() +{ + QObject *w = canvas(); + if ( w ) + { + w = w->parent(); + if ( w && w->inherits("QwtPlot") ) + return (QwtPlot *)w; + } + + return NULL; +} + +//! Return plot widget, containing the observed plot canvas +const QwtPlot *QwtPlotPanner::plot() const +{ + return ((QwtPlotPanner *)this)->plot(); +} + +/*! + Adjust the enabled axes according to dx/dy + + \param dx Pixel offset in x direction + \param dy Pixel offset in y direction + + \sa QwtPanner::panned() +*/ +void QwtPlotPanner::moveCanvas(int dx, int dy) +{ + if ( dx == 0 && dy == 0 ) + return; + + QwtPlot *plot = QwtPlotPanner::plot(); + if ( plot == NULL ) + return; + + const bool doAutoReplot = plot->autoReplot(); + plot->setAutoReplot(false); + + for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) + { + if ( !d_data->isAxisEnabled[axis] ) + continue; + + const QwtScaleMap map = plot->canvasMap(axis); + + const int i1 = map.transform(plot->axisScaleDiv(axis)->lowerBound()); + const int i2 = map.transform(plot->axisScaleDiv(axis)->upperBound()); + + double d1, d2; + if ( axis == QwtPlot::xBottom || axis == QwtPlot::xTop ) + { + d1 = map.invTransform(i1 - dx); + d2 = map.invTransform(i2 - dx); + } + else + { + d1 = map.invTransform(i1 - dy); + d2 = map.invTransform(i2 - dy); + } + + plot->setAxisScale(axis, d1, d2); + } + + plot->setAutoReplot(doAutoReplot); + plot->replot(); +} diff --git a/qwt/src/qwt_plot_panner.h b/qwt/src/qwt_plot_panner.h new file mode 100644 index 000000000..0448a8fa2 --- /dev/null +++ b/qwt/src/qwt_plot_panner.h @@ -0,0 +1,57 @@ +/* -*- 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_PANNER_H +#define QWT_PLOT_PANNER_H 1 + +#include "qwt_global.h" +#include "qwt_panner.h" + +class QwtPlotCanvas; +class QwtPlot; + +/*! + \brief QwtPlotPanner provides panning of a plot canvas + + QwtPlotPanner is a panner for a QwtPlotCanvas, that + adjusts the scales of the axes after dropping + the canvas on its new position. + + Together with QwtPlotZoomer and QwtPlotMagnifier powerful ways + of navigating on a QwtPlot widget can be implemented easily. + + \note The axes are not updated, while dragging the canvas + \sa QwtPlotZoomer, QwtPlotMagnifier +*/ +class QWT_EXPORT QwtPlotPanner: public QwtPanner +{ + Q_OBJECT + +public: + explicit QwtPlotPanner(QwtPlotCanvas *); + virtual ~QwtPlotPanner(); + + QwtPlotCanvas *canvas(); + const QwtPlotCanvas *canvas() const; + + QwtPlot *plot(); + const QwtPlot *plot() const; + + void setAxisEnabled(int axis, bool on); + bool isAxisEnabled(int axis) const; + +protected slots: + virtual void moveCanvas(int dx, int dy); + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_picker.cpp b/qwt/src/qwt_plot_picker.cpp new file mode 100644 index 000000000..8f180017a --- /dev/null +++ b/qwt/src/qwt_plot_picker.cpp @@ -0,0 +1,399 @@ +/* -*- 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 "qwt_plot.h" +#include "qwt_double_rect.h" +#include "qwt_scale_div.h" +#include "qwt_painter.h" +#include "qwt_scale_map.h" +#include "qwt_plot_picker.h" + +/*! + \brief Create a plot picker + + The picker is set to those x- and y-axis of the plot + that are enabled. If both or no x-axis are enabled, the picker + is set to QwtPlot::xBottom. If both or no y-axis are + enabled, it is set to QwtPlot::yLeft. + + \param canvas Plot canvas to observe, also the parent object + + \sa QwtPlot::autoReplot(), QwtPlot::replot(), QwtPlotPicker::scaleRect() +*/ + +QwtPlotPicker::QwtPlotPicker(QwtPlotCanvas *canvas): + QwtPicker(canvas), + d_xAxis(-1), + d_yAxis(-1) +{ + if ( !canvas ) + return; + + // attach axes + + int xAxis = QwtPlot::xBottom; + + const QwtPlot *plot = QwtPlotPicker::plot(); + if ( !plot->axisEnabled(QwtPlot::xBottom) && + plot->axisEnabled(QwtPlot::xTop) ) + { + xAxis = QwtPlot::xTop; + } + + int yAxis = QwtPlot::yLeft; + if ( !plot->axisEnabled(QwtPlot::yLeft) && + plot->axisEnabled(QwtPlot::yRight) ) + { + yAxis = QwtPlot::yRight; + } + + setAxis(xAxis, yAxis); +} + +/*! + Create a plot picker + + \param xAxis Set the x axis of the picker + \param yAxis Set the y axis of the picker + \param canvas Plot canvas to observe, also the parent object + + \sa QwtPlot::autoReplot(), QwtPlot::replot(), QwtPlotPicker::scaleRect() +*/ +QwtPlotPicker::QwtPlotPicker(int xAxis, int yAxis, QwtPlotCanvas *canvas): + QwtPicker(canvas), + d_xAxis(xAxis), + d_yAxis(yAxis) +{ +} + +/*! + Create a plot picker + + \param xAxis X axis of the picker + \param yAxis Y axis of the picker + \param selectionFlags Or'd value of SelectionType, RectSelectionType and + SelectionMode + \param rubberBand Rubberband style + \param trackerMode Tracker mode + \param canvas Plot canvas to observe, also the parent object + + \sa QwtPicker, QwtPicker::setSelectionFlags(), QwtPicker::setRubberBand(), + QwtPicker::setTrackerMode + + \sa QwtPlot::autoReplot(), QwtPlot::replot(), QwtPlotPicker::scaleRect() +*/ +QwtPlotPicker::QwtPlotPicker(int xAxis, int yAxis, int selectionFlags, + RubberBand rubberBand, DisplayMode trackerMode, + QwtPlotCanvas *canvas): + QwtPicker(selectionFlags, rubberBand, trackerMode, canvas), + d_xAxis(xAxis), + d_yAxis(yAxis) +{ +} + +//! Destructor +QwtPlotPicker::~QwtPlotPicker() +{ +} + +//! Return observed plot canvas +QwtPlotCanvas *QwtPlotPicker::canvas() +{ + QWidget *w = parentWidget(); + if ( w && w->inherits("QwtPlotCanvas") ) + return (QwtPlotCanvas *)w; + + return NULL; +} + +//! Return Observed plot canvas +const QwtPlotCanvas *QwtPlotPicker::canvas() const +{ + return ((QwtPlotPicker *)this)->canvas(); +} + +//! Return plot widget, containing the observed plot canvas +QwtPlot *QwtPlotPicker::plot() +{ + QObject *w = canvas(); + if ( w ) + { + w = w->parent(); + if ( w && w->inherits("QwtPlot") ) + return (QwtPlot *)w; + } + + return NULL; +} + +//! Return plot widget, containing the observed plot canvas +const QwtPlot *QwtPlotPicker::plot() const +{ + return ((QwtPlotPicker *)this)->plot(); +} + +/*! + Return normalized bounding rect of the axes + + \sa QwtPlot::autoReplot(), QwtPlot::replot(). +*/ +QwtDoubleRect QwtPlotPicker::scaleRect() const +{ + QwtDoubleRect rect; + + if ( plot() ) + { + const QwtScaleDiv *xs = plot()->axisScaleDiv(xAxis()); + const QwtScaleDiv *ys = plot()->axisScaleDiv(yAxis()); + + if ( xs && ys ) + { + rect = QwtDoubleRect( xs->lowerBound(), ys->lowerBound(), + xs->range(), ys->range() ); + rect = rect.normalized(); + } + } + + return rect; +} + +/*! + Set the x and y axes of the picker + + \param xAxis X axis + \param yAxis Y axis +*/ +void QwtPlotPicker::setAxis(int xAxis, int yAxis) +{ + const QwtPlot *plt = plot(); + if ( !plt ) + return; + + if ( xAxis != d_xAxis || yAxis != d_yAxis ) + { + d_xAxis = xAxis; + d_yAxis = yAxis; + } +} + +//! Return x axis +int QwtPlotPicker::xAxis() const +{ + return d_xAxis; +} + +//! Return y axis +int QwtPlotPicker::yAxis() const +{ + return d_yAxis; +} + +/*! + Translate a pixel position into a position string + + \param pos Position in pixel coordinates + \return Position string +*/ +QwtText QwtPlotPicker::trackerText(const QPoint &pos) const +{ + return trackerText(invTransform(pos)); +} + +/*! + \brief Translate a position into a position string + + 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 double to string conversion is "%.4f". + + \param pos Position + \return Position string +*/ +QwtText QwtPlotPicker::trackerText(const QwtDoublePoint &pos) const +{ + QString text; + + switch(rubberBand()) + { + case HLineRubberBand: + text.sprintf("%.4f", pos.y()); + break; + case VLineRubberBand: + text.sprintf("%.4f", pos.x()); + break; + default: + text.sprintf("%.4f, %.4f", pos.x(), pos.y()); + } + return QwtText(text); +} + +/*! + Append a point to the selection and update rubberband and tracker. + + \param pos Additional point + \sa isActive, begin(), end(), move(), appended() + + \note The appended(const QPoint &), appended(const QDoublePoint &) + signals are emitted. +*/ +void QwtPlotPicker::append(const QPoint &pos) +{ + QwtPicker::append(pos); + emit appended(invTransform(pos)); +} + +/*! + Move the last point of the selection + + \param pos New position + \sa isActive, begin(), end(), append() + + \note The moved(const QPoint &), moved(const QDoublePoint &) + signals are emitted. +*/ +void QwtPlotPicker::move(const QPoint &pos) +{ + QwtPicker::move(pos); + emit moved(invTransform(pos)); +} + +/*! + Close a selection setting the state to inactive. + + \param ok If true, complete the selection and emit selected signals + otherwise discard the selection. + \return true if the selection is accepted, false otherwise +*/ + +bool QwtPlotPicker::end(bool ok) +{ + ok = QwtPicker::end(ok); + if ( !ok ) + return false; + + QwtPlot *plot = QwtPlotPicker::plot(); + if ( !plot ) + return false; + + const QwtPolygon &pa = selection(); + if ( pa.count() == 0 ) + return false; + + if ( selectionFlags() & PointSelection ) + { + const QwtDoublePoint pos = invTransform(pa[0]); + emit selected(pos); + } + else if ( (selectionFlags() & RectSelection) && pa.count() >= 2 ) + { + QPoint p1 = pa[0]; + QPoint p2 = pa[int(pa.count() - 1)]; + + if ( selectionFlags() & CenterToCorner ) + { + p1.setX(p1.x() - (p2.x() - p1.x())); + p1.setY(p1.y() - (p2.y() - p1.y())); + } + else if ( selectionFlags() & CenterToRadius ) + { + const int radius = qwtMax(qwtAbs(p2.x() - p1.x()), + qwtAbs(p2.y() - p1.y())); + p2.setX(p1.x() + radius); + p2.setY(p1.y() + radius); + p1.setX(p1.x() - radius); + p1.setY(p1.y() - radius); + } + + emit selected(invTransform(QRect(p1, p2)).normalized()); + } + else + { + QwtArray dpa(pa.count()); + for ( int i = 0; i < int(pa.count()); i++ ) + dpa[i] = invTransform(pa[i]); + + emit selected(dpa); + } + + return true; +} + +/*! + Translate a rectangle from pixel into plot coordinates + + \return Rectangle in plot coordinates + \sa QwtPlotPicker::transform() +*/ +QwtDoubleRect QwtPlotPicker::invTransform(const QRect &rect) const +{ + QwtScaleMap xMap = plot()->canvasMap(d_xAxis); + QwtScaleMap yMap = plot()->canvasMap(d_yAxis); + + const double left = xMap.invTransform(rect.left()); + const double right = xMap.invTransform(rect.right()); + const double top = yMap.invTransform(rect.top()); + const double bottom = yMap.invTransform(rect.bottom()); + + return QwtDoubleRect(left, top, + right - left, bottom - top); +} + +/*! + Translate a rectangle from plot into pixel coordinates + \return Rectangle in pixel coordinates + \sa QwtPlotPicker::invTransform() +*/ +QRect QwtPlotPicker::transform(const QwtDoubleRect &rect) const +{ + QwtScaleMap xMap = plot()->canvasMap(d_xAxis); + QwtScaleMap yMap = plot()->canvasMap(d_yAxis); + + const int left = xMap.transform(rect.left()); + const int right = xMap.transform(rect.right()); + const int top = yMap.transform(rect.top()); + const int bottom = yMap.transform(rect.bottom()); + + return QRect(left, top, right - left, bottom - top); +} + +/*! + Translate a point from pixel into plot coordinates + \return Point in plot coordinates + \sa QwtPlotPicker::transform() +*/ +QwtDoublePoint QwtPlotPicker::invTransform(const QPoint &pos) const +{ + QwtScaleMap xMap = plot()->canvasMap(d_xAxis); + QwtScaleMap yMap = plot()->canvasMap(d_yAxis); + + return QwtDoublePoint( + xMap.invTransform(pos.x()), + yMap.invTransform(pos.y()) + ); +} + +/*! + Translate a point from plot into pixel coordinates + \return Point in pixel coordinates + \sa QwtPlotPicker::invTransform() +*/ +QPoint QwtPlotPicker::transform(const QwtDoublePoint &pos) const +{ + QwtScaleMap xMap = plot()->canvasMap(d_xAxis); + QwtScaleMap yMap = plot()->canvasMap(d_yAxis); + + return QPoint( + xMap.transform(pos.x()), + yMap.transform(pos.y()) + ); +} diff --git a/qwt/src/qwt_plot_picker.h b/qwt/src/qwt_plot_picker.h new file mode 100644 index 000000000..c81890c4f --- /dev/null +++ b/qwt/src/qwt_plot_picker.h @@ -0,0 +1,115 @@ +/* -*- 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 + +#ifndef QWT_PLOT_PICKER_H +#define QWT_PLOT_PICKER_H + +#include "qwt_double_rect.h" +#include "qwt_plot_canvas.h" +#include "qwt_picker.h" + +class QwtPlot; + +/*! + \brief QwtPlotPicker provides selections on a plot canvas + + QwtPlotPicker is a QwtPicker tailored for selections on + a plot canvas. It is set to a x-Axis and y-Axis and + translates all pixel coordinates into this coodinate system. +*/ + +class QWT_EXPORT QwtPlotPicker: public QwtPicker +{ + Q_OBJECT + +public: + explicit QwtPlotPicker(QwtPlotCanvas *); + virtual ~QwtPlotPicker(); + + explicit QwtPlotPicker(int xAxis, int yAxis, + QwtPlotCanvas *); + + explicit QwtPlotPicker(int xAxis, int yAxis, int selectionFlags, + RubberBand rubberBand, DisplayMode trackerMode, + QwtPlotCanvas *); + + virtual void setAxis(int xAxis, int yAxis); + + int xAxis() const; + int yAxis() const; + + QwtPlot *plot(); + const QwtPlot *plot() const; + + QwtPlotCanvas *canvas(); + const QwtPlotCanvas *canvas() const; + +signals: + + /*! + A signal emitted in case of selectionFlags() & PointSelection. + \param pos Selected point + */ + void selected(const QwtDoublePoint &pos); + + /*! + A signal emitted in case of selectionFlags() & RectSelection. + \param rect Selected rectangle + */ + void selected(const QwtDoubleRect &rect); + + /*! + A signal emitting the selected points, + at the end of a selection. + + \param pa Selected points + */ + void selected(const QwtArray &pa); + + /*! + 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 QwtDoublePoint &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 QwtDoublePoint &pos); + +protected: + QwtDoubleRect scaleRect() const; + + QwtDoubleRect invTransform(const QRect &) const; + QRect transform(const QwtDoubleRect &) const; + + QwtDoublePoint invTransform(const QPoint &) const; + QPoint transform(const QwtDoublePoint &) const; + + virtual QwtText trackerText(const QPoint &) const; + virtual QwtText trackerText(const QwtDoublePoint &) const; + + virtual void move(const QPoint &); + virtual void append(const QPoint &); + virtual bool end(bool ok = true); + +private: + int d_xAxis; + int d_yAxis; +}; + +#endif diff --git a/qwt/src/qwt_plot_print.cpp b/qwt/src/qwt_plot_print.cpp new file mode 100644 index 000000000..2ae7d8691 --- /dev/null +++ b/qwt/src/qwt_plot_print.cpp @@ -0,0 +1,530 @@ +/* -*- 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 +#if QT_VERSION < 0x040000 +#include +#else +#include +#endif +#include "qwt_painter.h" +#include "qwt_legend_item.h" +#include "qwt_plot.h" +#include "qwt_plot_canvas.h" +#include "qwt_plot_layout.h" +#include "qwt_legend.h" +#include "qwt_dyngrid_layout.h" +#include "qwt_scale_widget.h" +#include "qwt_scale_engine.h" +#include "qwt_text.h" +#include "qwt_text_label.h" +#include "qwt_math.h" + +/*! + \brief Print the plot to a \c QPaintDevice (\c QPrinter) + This function prints the contents of a QwtPlot instance to + \c QPaintDevice object. The size is derived from its device + metrics. + + \param paintDev device to paint on, often a printer + \param pfilter print filter + + \sa QwtPlotPrintFilter +*/ + +void QwtPlot::print(QPaintDevice &paintDev, + const QwtPlotPrintFilter &pfilter) const +{ +#if QT_VERSION < 0x040000 + QPaintDeviceMetrics mpr(&paintDev); + int w = mpr.width(); + int h = mpr.height(); +#else + int w = paintDev.width(); + int h = paintDev.height(); +#endif + + QRect rect(0, 0, w, h); + double aspect = double(rect.width())/double(rect.height()); + if ((aspect < 1.0)) + rect.setHeight(int(aspect*rect.width())); + + QPainter p(&paintDev); + print(&p, rect, pfilter); +} + +/*! + \brief Paint the plot into a given rectangle. + Paint the contents of a QwtPlot instance into a given rectangle. + + \param painter Painter + \param plotRect Bounding rectangle + \param pfilter Print filter + + \sa QwtPlotPrintFilter +*/ +void QwtPlot::print(QPainter *painter, const QRect &plotRect, + const QwtPlotPrintFilter &pfilter) const +{ + int axisId; + + if ( painter == 0 || !painter->isActive() || + !plotRect.isValid() || size().isNull() ) + return; + + painter->save(); +#if 1 + /* + PDF: In Qt4 ( <= 4.3.2 ) the scales are painted in gray instead of + black. See http://trolltech.com/developer/task-tracker/index_html?id=184671&method=entry + The dummy lines below work around the problem. + */ + const QPen pen = painter->pen(); + painter->setPen(QPen(Qt::black, 1)); + painter->setPen(pen); +#endif + + // All paint operations need to be scaled according to + // the paint device metrics. + + QwtPainter::setMetricsMap(this, painter->device()); + const QwtMetricsMap &metricsMap = QwtPainter::metricsMap(); + + // It is almost impossible to integrate into the Qt layout + // framework, when using different fonts for printing + // and screen. To avoid writing different and Qt unconform + // layout engines we change the widget attributes, print and + // reset the widget attributes again. This way we produce a lot of + // useless layout events ... + + pfilter.apply((QwtPlot *)this); + + int baseLineDists[QwtPlot::axisCnt]; + if ( pfilter.options() & QwtPlotPrintFilter::PrintFrameWithScales ) + { + for (axisId = 0; axisId < QwtPlot::axisCnt; axisId++ ) + { + QwtScaleWidget *scaleWidget = (QwtScaleWidget *)axisWidget(axisId); + if ( scaleWidget ) + { + baseLineDists[axisId] = scaleWidget->margin(); + scaleWidget->setMargin(0); + } + } + } + // Calculate the layout for the print. + + int layoutOptions = QwtPlotLayout::IgnoreScrollbars + | QwtPlotLayout::IgnoreFrames; + if ( !(pfilter.options() & QwtPlotPrintFilter::PrintMargin) ) + layoutOptions |= QwtPlotLayout::IgnoreMargin; + if ( !(pfilter.options() & QwtPlotPrintFilter::PrintLegend) ) + layoutOptions |= QwtPlotLayout::IgnoreLegend; + + ((QwtPlot *)this)->plotLayout()->activate(this, + QwtPainter::metricsMap().deviceToLayout(plotRect), + layoutOptions); + + if ((pfilter.options() & QwtPlotPrintFilter::PrintTitle) + && (!titleLabel()->text().isEmpty())) + { + printTitle(painter, plotLayout()->titleRect()); + } + + if ( (pfilter.options() & QwtPlotPrintFilter::PrintLegend) + && legend() && !legend()->isEmpty() ) + { + printLegend(painter, plotLayout()->legendRect()); + } + + for ( axisId = 0; axisId < QwtPlot::axisCnt; axisId++ ) + { + QwtScaleWidget *scaleWidget = (QwtScaleWidget *)axisWidget(axisId); + if (scaleWidget) + { + int baseDist = scaleWidget->margin(); + + int startDist, endDist; + scaleWidget->getBorderDistHint(startDist, endDist); + + printScale(painter, axisId, startDist, endDist, + baseDist, plotLayout()->scaleRect(axisId)); + } + } + + QRect canvasRect = plotLayout()->canvasRect(); + + /* + The border of the bounding rect needs to ba scaled to + layout coordinates, so that it is aligned to the axes + */ + QRect boundingRect( canvasRect.left() - 1, canvasRect.top() - 1, + canvasRect.width() + 2, canvasRect.height() + 2); + boundingRect = metricsMap.layoutToDevice(boundingRect); + boundingRect.setWidth(boundingRect.width() - 1); + boundingRect.setHeight(boundingRect.height() - 1); + + canvasRect = metricsMap.layoutToDevice(canvasRect); + + // When using QwtPainter all sizes where computed in pixel + // coordinates and scaled by QwtPainter later. This limits + // the precision to screen resolution. A better solution + // is to scale the maps and print in unlimited resolution. + + QwtScaleMap map[axisCnt]; + for (axisId = 0; axisId < axisCnt; axisId++) + { + map[axisId].setTransformation(axisScaleEngine(axisId)->transformation()); + + const QwtScaleDiv &scaleDiv = *axisScaleDiv(axisId); + map[axisId].setScaleInterval( + scaleDiv.lowerBound(), scaleDiv.upperBound()); + + double from, to; + if ( axisEnabled(axisId) ) + { + const int sDist = axisWidget(axisId)->startBorderDist(); + const int eDist = axisWidget(axisId)->endBorderDist(); + const QRect &scaleRect = plotLayout()->scaleRect(axisId); + + if ( axisId == xTop || axisId == xBottom ) + { + from = metricsMap.layoutToDeviceX(scaleRect.left() + sDist); + to = metricsMap.layoutToDeviceX(scaleRect.right() + 1 - eDist); + } + else + { + from = metricsMap.layoutToDeviceY(scaleRect.bottom() + 1 - eDist ); + to = metricsMap.layoutToDeviceY(scaleRect.top() + sDist); + } + } + else + { + int margin = plotLayout()->canvasMargin(axisId); + if ( axisId == yLeft || axisId == yRight ) + { + margin = metricsMap.layoutToDeviceY(margin); + from = canvasRect.bottom() - margin; + to = canvasRect.top() + margin; + } + else + { + margin = metricsMap.layoutToDeviceX(margin); + from = canvasRect.left() + margin; + to = canvasRect.right() - margin; + } + } + map[axisId].setPaintXInterval(from, to); + } + + // The canvas maps are already scaled. + QwtPainter::setMetricsMap(painter->device(), painter->device()); + printCanvas(painter, boundingRect, canvasRect, map, pfilter); + QwtPainter::resetMetricsMap(); + + ((QwtPlot *)this)->plotLayout()->invalidate(); + + // reset all widgets with their original attributes. + if ( pfilter.options() & QwtPlotPrintFilter::PrintFrameWithScales ) + { + // restore the previous base line dists + + for (axisId = 0; axisId < QwtPlot::axisCnt; axisId++ ) + { + QwtScaleWidget *scaleWidget = (QwtScaleWidget *)axisWidget(axisId); + if ( scaleWidget ) + scaleWidget->setMargin(baseLineDists[axisId]); + } + } + + pfilter.reset((QwtPlot *)this); + + painter->restore(); +} + +/*! + Print the title into a given rectangle. + + \param painter Painter + \param rect Bounding rectangle +*/ + +void QwtPlot::printTitle(QPainter *painter, const QRect &rect) const +{ + painter->setFont(titleLabel()->font()); + + const QColor color = +#if QT_VERSION < 0x040000 + titleLabel()->palette().color( + QPalette::Active, QColorGroup::Text); +#else + titleLabel()->palette().color( + QPalette::Active, QPalette::Text); +#endif + + painter->setPen(color); + titleLabel()->text().draw(painter, rect); +} + +/*! + Print the legend into a given rectangle. + + \param painter Painter + \param rect Bounding rectangle +*/ + +void QwtPlot::printLegend(QPainter *painter, const QRect &rect) const +{ + if ( !legend() || legend()->isEmpty() ) + return; + + QLayout *l = legend()->contentsWidget()->layout(); + if ( l == 0 || !l->inherits("QwtDynGridLayout") ) + return; + + QwtDynGridLayout *legendLayout = (QwtDynGridLayout *)l; + + uint numCols = legendLayout->columnsForWidth(rect.width()); +#if QT_VERSION < 0x040000 + QValueList itemRects = + legendLayout->layoutItems(rect, numCols); +#else + QList itemRects = + legendLayout->layoutItems(rect, numCols); +#endif + + int index = 0; + +#if QT_VERSION < 0x040000 + QLayoutIterator layoutIterator = legendLayout->iterator(); + for ( QLayoutItem *item = layoutIterator.current(); + item != 0; item = ++layoutIterator) + { +#else + for ( int i = 0; i < legendLayout->count(); i++ ) + { + QLayoutItem *item = legendLayout->itemAt(i); +#endif + QWidget *w = item->widget(); + if ( w ) + { + painter->save(); + painter->setClipping(true); + QwtPainter::setClipRect(painter, itemRects[index]); + + printLegendItem(painter, w, itemRects[index]); + + index++; + painter->restore(); + } + } +} + +/*! + Print the legend item into a given rectangle. + + \param painter Painter + \param w Widget representing a legend item + \param rect Bounding rectangle +*/ + +void QwtPlot::printLegendItem(QPainter *painter, + const QWidget *w, const QRect &rect) const +{ + if ( w->inherits("QwtLegendItem") ) + { + QwtLegendItem *item = (QwtLegendItem *)w; + + painter->setFont(item->font()); + item->drawItem(painter, rect); + } +} + +/*! + \brief Paint a scale into a given rectangle. + Paint the scale into a given rectangle. + + \param painter Painter + \param axisId Axis + \param startDist Start border distance + \param endDist End border distance + \param baseDist Base distance + \param rect Bounding rectangle +*/ + +void QwtPlot::printScale(QPainter *painter, + int axisId, int startDist, int endDist, int baseDist, + const QRect &rect) const +{ + if (!axisEnabled(axisId)) + return; + + const QwtScaleWidget *scaleWidget = axisWidget(axisId); + if ( scaleWidget->isColorBarEnabled() + && scaleWidget->colorBarWidth() > 0) + { + const QwtMetricsMap map = QwtPainter::metricsMap(); + + QRect r = map.layoutToScreen(rect); + r.setWidth(r.width() - 1); + r.setHeight(r.height() - 1); + + scaleWidget->drawColorBar(painter, scaleWidget->colorBarRect(r)); + + const int off = scaleWidget->colorBarWidth() + scaleWidget->spacing(); + if ( scaleWidget->scaleDraw()->orientation() == Qt::Horizontal ) + baseDist += map.screenToLayoutY(off); + else + baseDist += map.screenToLayoutX(off); + } + + QwtScaleDraw::Alignment align; + int x, y, w; + + switch(axisId) + { + case yLeft: + { + x = rect.right() - baseDist; + y = rect.y() + startDist; + w = rect.height() - startDist - endDist; + align = QwtScaleDraw::LeftScale; + break; + } + case yRight: + { + x = rect.left() + baseDist; + y = rect.y() + startDist; + w = rect.height() - startDist - endDist; + align = QwtScaleDraw::RightScale; + break; + } + case xTop: + { + x = rect.left() + startDist; + y = rect.bottom() - baseDist; + w = rect.width() - startDist - endDist; + align = QwtScaleDraw::TopScale; + break; + } + case xBottom: + { + x = rect.left() + startDist; + y = rect.top() + baseDist; + w = rect.width() - startDist - endDist; + align = QwtScaleDraw::BottomScale; + break; + } + default: + return; + } + + scaleWidget->drawTitle(painter, align, rect); + + painter->save(); + painter->setFont(scaleWidget->font()); + + QPen pen = painter->pen(); + pen.setWidth(scaleWidget->penWidth()); + painter->setPen(pen); + + QwtScaleDraw *sd = (QwtScaleDraw *)scaleWidget->scaleDraw(); + const QPoint sdPos = sd->pos(); + const int sdLength = sd->length(); + + sd->move(x, y); + sd->setLength(w); + +#if QT_VERSION < 0x040000 + sd->draw(painter, scaleWidget->palette().active()); +#else + QPalette palette = scaleWidget->palette(); + palette.setCurrentColorGroup(QPalette::Active); + sd->draw(painter, palette); +#endif + // reset previous values + sd->move(sdPos); + sd->setLength(sdLength); + + painter->restore(); +} + +/*! + Print the canvas into a given rectangle. + + \param painter Painter + \param map Maps mapping between plot and paint device coordinates + \param boundingRect Bounding rectangle + \param canvasRect Canvas rectangle + \param pfilter Print filter + \sa QwtPlotPrintFilter +*/ + +void QwtPlot::printCanvas(QPainter *painter, + const QRect &boundingRect, const QRect &canvasRect, + const QwtScaleMap map[axisCnt], const QwtPlotPrintFilter &pfilter) const +{ + if ( pfilter.options() & QwtPlotPrintFilter::PrintBackground ) + { + QBrush bgBrush; +#if QT_VERSION >= 0x040000 + bgBrush = canvas()->palette().brush(backgroundRole()); +#else + QColorGroup::ColorRole role = + QPalette::backgroundRoleFromMode( backgroundMode() ); + bgBrush = canvas()->colorGroup().brush( role ); +#endif + QRect r = boundingRect; + if ( !(pfilter.options() & QwtPlotPrintFilter::PrintFrameWithScales) ) + { + r = canvasRect; +#if QT_VERSION >= 0x040000 + // Unfortunately the paint engines do no always the same + const QPaintEngine *pe = painter->paintEngine(); + if ( pe ) + { + switch(painter->paintEngine()->type() ) + { + case QPaintEngine::Raster: + case QPaintEngine::X11: + break; + default: + r.setWidth(r.width() - 1); + r.setHeight(r.height() - 1); + break; + } + } +#else + if ( painter->device()->isExtDev() ) + { + r.setWidth(r.width() - 1); + r.setHeight(r.height() - 1); + } +#endif + } + + QwtPainter::fillRect(painter, r, bgBrush); + } + + if ( pfilter.options() & QwtPlotPrintFilter::PrintFrameWithScales ) + { + painter->save(); + painter->setPen(QPen(Qt::black)); + painter->setBrush(QBrush(Qt::NoBrush)); + QwtPainter::drawRect(painter, boundingRect); + painter->restore(); + } + + painter->setClipping(true); + QwtPainter::setClipRect(painter, canvasRect); + + drawItems(painter, canvasRect, map, pfilter); +} diff --git a/qwt/src/qwt_plot_printfilter.cpp b/qwt/src/qwt_plot_printfilter.cpp new file mode 100644 index 000000000..cad7e8058 --- /dev/null +++ b/qwt/src/qwt_plot_printfilter.cpp @@ -0,0 +1,590 @@ +/* -*- 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 "qwt_plot.h" +#include "qwt_plot_grid.h" +#include "qwt_plot_curve.h" +#include "qwt_plot_marker.h" +#include "qwt_symbol.h" +#include "qwt_legend.h" +#include "qwt_legend_item.h" +#include "qwt_scale_widget.h" +#include "qwt_text_label.h" +#include "qwt_plot_printfilter.h" + +#if QT_VERSION < 0x040000 +typedef QColorGroup Palette; +#else +typedef QPalette Palette; +#endif + +class QwtPlotPrintFilter::PrivateData +{ +public: + PrivateData(): + options(QwtPlotPrintFilter::PrintAll), + cache(NULL) + { + } + + ~PrivateData() + { + delete cache; + } + + class Cache + { + public: + QColor titleColor; + QFont titleFont; + + QwtText scaleTitle[QwtPlot::axisCnt]; + QColor scaleColor[QwtPlot::axisCnt]; + QFont scaleFont[QwtPlot::axisCnt]; + QColor scaleTitleColor[QwtPlot::axisCnt]; + QFont scaleTitleFont[QwtPlot::axisCnt]; + + QMap legendFonts; + + QColor widgetBackground; + QColor canvasBackground; + QColor gridColors[2]; + + QMap curveColors; + QMap curveSymbolBrushColors; + QMap curveSymbolPenColors; + + QMap markerFonts; + QMap markerLabelColors; + QMap markerLineColors; + QMap markerSymbolBrushColors; + QMap markerSymbolPenColors; + }; + + int options; + mutable Cache *cache; +}; + + +/*! + Sets filter options to PrintAll +*/ + +QwtPlotPrintFilter::QwtPlotPrintFilter() +{ + d_data = new PrivateData; +} + +//! Destructor +QwtPlotPrintFilter::~QwtPlotPrintFilter() +{ + delete d_data; +} + +/*! + \brief Set plot print options + \param options Or'd QwtPlotPrintFilter::Options values + + \sa options() +*/ +void QwtPlotPrintFilter::setOptions(int options) +{ + d_data->options = options; +} + +/*! + \brief Get plot print options + \sa setOptions() +*/ +int QwtPlotPrintFilter::options() const +{ + return d_data->options; +} + +/*! + \brief Modifies a color for printing + \param c Color to be modified + \param item Type of item where the color belongs + \return Modified color. + + In case of !(QwtPlotPrintFilter::options() & PrintBackground) + MajorGrid is modified to Qt::darkGray, MinorGrid to Qt::gray. + All other colors are returned unmodified. +*/ + +QColor QwtPlotPrintFilter::color(const QColor &c, Item item) const +{ + if ( !(options() & PrintBackground)) + { + switch(item) + { + case MajorGrid: + return Qt::darkGray; + case MinorGrid: + return Qt::gray; + default:; + } + } + return c; +} + +/*! + \brief Modifies a font for printing + \param f Font to be modified + \param item Type of item where the font belongs + + All fonts are returned unmodified +*/ + +QFont QwtPlotPrintFilter::font(const QFont &f, Item) const +{ + return f; +} + +/*! + Change color and fonts of a plot + \sa apply() +*/ +void QwtPlotPrintFilter::apply(QwtPlot *plot) const +{ + const bool doAutoReplot = plot->autoReplot(); + plot->setAutoReplot(false); + + delete d_data->cache; + d_data->cache = new PrivateData::Cache; + + PrivateData::Cache &cache = *d_data->cache; + + if ( plot->titleLabel() ) + { + QPalette palette = plot->titleLabel()->palette(); + cache.titleColor = palette.color( + QPalette::Active, Palette::Text); + palette.setColor(QPalette::Active, Palette::Text, + color(cache.titleColor, Title)); + plot->titleLabel()->setPalette(palette); + + cache.titleFont = plot->titleLabel()->font(); + plot->titleLabel()->setFont(font(cache.titleFont, Title)); + } + if ( plot->legend() ) + { +#if QT_VERSION < 0x040000 + QValueList list = plot->legend()->legendItems(); + for ( QValueListIterator it = list.begin(); + it != list.end(); ++it ) +#else + QList list = plot->legend()->legendItems(); + for ( QList::iterator it = list.begin(); + it != list.end(); ++it ) +#endif + { + QWidget *w = *it; + + cache.legendFonts.insert(w, w->font()); + w->setFont(font(w->font(), Legend)); + + if ( w->inherits("QwtLegendItem") ) + { + QwtLegendItem *label = (QwtLegendItem *)w; + + QwtSymbol symbol = label->symbol(); + QPen pen = symbol.pen(); + QBrush brush = symbol.brush(); + + pen.setColor(color(pen.color(), CurveSymbol)); + brush.setColor(color(brush.color(), CurveSymbol)); + + symbol.setPen(pen); + symbol.setBrush(brush); + label->setSymbol(symbol); + + pen = label->curvePen(); + pen.setColor(color(pen.color(), Curve)); + label->setCurvePen(pen); + } + } + } + for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) + { + QwtScaleWidget *scaleWidget = plot->axisWidget(axis); + if ( scaleWidget ) + { + cache.scaleColor[axis] = scaleWidget->palette().color( + QPalette::Active, Palette::Foreground); + QPalette palette = scaleWidget->palette(); + palette.setColor(QPalette::Active, Palette::Foreground, + color(cache.scaleColor[axis], AxisScale)); + scaleWidget->setPalette(palette); + + cache.scaleFont[axis] = scaleWidget->font(); + scaleWidget->setFont(font(cache.scaleFont[axis], AxisScale)); + + cache.scaleTitle[axis] = scaleWidget->title(); + + QwtText scaleTitle = scaleWidget->title(); + if ( scaleTitle.testPaintAttribute(QwtText::PaintUsingTextColor) ) + { + cache.scaleTitleColor[axis] = scaleTitle.color(); + scaleTitle.setColor( + color(cache.scaleTitleColor[axis], AxisTitle)); + } + + if ( scaleTitle.testPaintAttribute(QwtText::PaintUsingTextFont) ) + { + cache.scaleTitleFont[axis] = scaleTitle.font(); + scaleTitle.setFont( + font(cache.scaleTitleFont[axis], AxisTitle)); + } + + scaleWidget->setTitle(scaleTitle); + + int startDist, endDist; + scaleWidget->getBorderDistHint(startDist, endDist); + scaleWidget->setBorderDist(startDist, endDist); + } + } + + + QPalette p = plot->palette(); + cache.widgetBackground = plot->palette().color( + QPalette::Active, Palette::Background); + p.setColor(QPalette::Active, Palette::Background, + color(cache.widgetBackground, WidgetBackground)); + plot->setPalette(p); + + cache.canvasBackground = plot->canvasBackground(); + plot->setCanvasBackground(color(cache.canvasBackground, CanvasBackground)); + + const QwtPlotItemList& itmList = plot->itemList(); + for ( QwtPlotItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + apply(*it); + } + + plot->setAutoReplot(doAutoReplot); +} + +void QwtPlotPrintFilter::apply(QwtPlotItem *item) const +{ + PrivateData::Cache &cache = *d_data->cache; + + switch(item->rtti()) + { + case QwtPlotItem::Rtti_PlotGrid: + { + QwtPlotGrid *grid = (QwtPlotGrid *)item; + + QPen pen = grid->majPen(); + cache.gridColors[0] = pen.color(); + pen.setColor(color(pen.color(), MajorGrid)); + grid->setMajPen(pen); + + pen = grid->minPen(); + cache.gridColors[1] = pen.color(); + pen.setColor(color(pen.color(), MinorGrid)); + grid->setMinPen(pen); + + break; + } + case QwtPlotItem::Rtti_PlotCurve: + { + QwtPlotCurve *c = (QwtPlotCurve *)item; + + QwtSymbol symbol = c->symbol(); + + QPen pen = symbol.pen(); + cache.curveSymbolPenColors.insert(c, pen.color()); + pen.setColor(color(pen.color(), CurveSymbol)); + symbol.setPen(pen); + + QBrush brush = symbol.brush(); + cache.curveSymbolBrushColors.insert(c, brush.color()); + brush.setColor(color(brush.color(), CurveSymbol)); + symbol.setBrush(brush); + + c->setSymbol(symbol); + + pen = c->pen(); + cache.curveColors.insert(c, pen.color()); + pen.setColor(color(pen.color(), Curve)); + c->setPen(pen); + + break; + } + case QwtPlotItem::Rtti_PlotMarker: + { + QwtPlotMarker *m = (QwtPlotMarker *)item; + + QwtText label = m->label(); + cache.markerFonts.insert(m, label.font()); + label.setFont(font(label.font(), Marker)); + cache.markerLabelColors.insert(m, label.color()); + label.setColor(color(label.color(), Marker)); + m->setLabel(label); + + QPen pen = m->linePen(); + cache.markerLineColors.insert(m, pen.color()); + pen.setColor(color(pen.color(), Marker)); + m->setLinePen(pen); + + QwtSymbol symbol = m->symbol(); + + pen = symbol.pen(); + cache.markerSymbolPenColors.insert(m, pen.color()); + pen.setColor(color(pen.color(), MarkerSymbol)); + symbol.setPen(pen); + + QBrush brush = symbol.brush(); + cache.markerSymbolBrushColors.insert(m, brush.color()); + brush.setColor(color(brush.color(), MarkerSymbol)); + symbol.setBrush(brush); + + m->setSymbol(symbol); + + break; + } + default: + break; + } +} + +/*! + Reset color and fonts of a plot + \sa apply() +*/ +void QwtPlotPrintFilter::reset(QwtPlot *plot) const +{ + if ( d_data->cache == 0 ) + return; + + const bool doAutoReplot = plot->autoReplot(); + plot->setAutoReplot(false); + + const PrivateData::Cache &cache = *d_data->cache; + + if ( plot->titleLabel() ) + { + QwtTextLabel* title = plot->titleLabel(); + if ( title->text().testPaintAttribute(QwtText::PaintUsingTextFont) ) + { + QwtText text = title->text(); + text.setColor(cache.titleColor); + title->setText(text); + } + else + { + QPalette palette = title->palette(); + palette.setColor( + QPalette::Active, Palette::Text, cache.titleColor); + title->setPalette(palette); + } + + if ( title->text().testPaintAttribute(QwtText::PaintUsingTextFont) ) + { + QwtText text = title->text(); + text.setFont(cache.titleFont); + title->setText(text); + } + else + { + title->setFont(cache.titleFont); + } + } + + if ( plot->legend() ) + { +#if QT_VERSION < 0x040000 + QValueList list = plot->legend()->legendItems(); + for ( QValueListIterator it = list.begin(); + it != list.end(); ++it ) +#else + QList list = plot->legend()->legendItems(); + for ( QList::iterator it = list.begin(); + it != list.end(); ++it ) +#endif + { + QWidget *w = *it; + + if ( cache.legendFonts.contains(w) ) + w->setFont(cache.legendFonts[w]); + + if ( w->inherits("QwtLegendItem") ) + { + QwtLegendItem *label = (QwtLegendItem *)w; + const QwtPlotItem *plotItem = + (const QwtPlotItem*)plot->legend()->find(label); + + QwtSymbol symbol = label->symbol(); + if ( cache.curveSymbolPenColors.contains(plotItem) ) + { + QPen pen = symbol.pen(); + pen.setColor(cache.curveSymbolPenColors[plotItem]); + symbol.setPen(pen); + } + + if ( cache.curveSymbolBrushColors.contains(plotItem) ) + { + QBrush brush = symbol.brush(); + brush.setColor(cache.curveSymbolBrushColors[plotItem]); + symbol.setBrush(brush); + } + label->setSymbol(symbol); + + if ( cache.curveColors.contains(plotItem) ) + { + QPen pen = label->curvePen(); + pen.setColor(cache.curveColors[plotItem]); + label->setCurvePen(pen); + } + } + } + } + for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) + { + QwtScaleWidget *scaleWidget = plot->axisWidget(axis); + if ( scaleWidget ) + { + QPalette palette = scaleWidget->palette(); + palette.setColor(QPalette::Active, Palette::Foreground, + cache.scaleColor[axis]); + scaleWidget->setPalette(palette); + + scaleWidget->setFont(cache.scaleFont[axis]); + scaleWidget->setTitle(cache.scaleTitle[axis]); + + int startDist, endDist; + scaleWidget->getBorderDistHint(startDist, endDist); + scaleWidget->setBorderDist(startDist, endDist); + } + } + + QPalette p = plot->palette(); + p.setColor(QPalette::Active, Palette::Background, cache.widgetBackground); + plot->setPalette(p); + + plot->setCanvasBackground(cache.canvasBackground); + + const QwtPlotItemList& itmList = plot->itemList(); + for ( QwtPlotItemIterator it = itmList.begin(); + it != itmList.end(); ++it ) + { + reset(*it); + } + + delete d_data->cache; + d_data->cache = 0; + + plot->setAutoReplot(doAutoReplot); +} + +void QwtPlotPrintFilter::reset(QwtPlotItem *item) const +{ + if ( d_data->cache == 0 ) + return; + + const PrivateData::Cache &cache = *d_data->cache; + + switch(item->rtti()) + { + case QwtPlotItem::Rtti_PlotGrid: + { + QwtPlotGrid *grid = (QwtPlotGrid *)item; + + QPen pen = grid->majPen(); + pen.setColor(cache.gridColors[0]); + grid->setMajPen(pen); + + pen = grid->minPen(); + pen.setColor(cache.gridColors[1]); + grid->setMinPen(pen); + + break; + } + case QwtPlotItem::Rtti_PlotCurve: + { + QwtPlotCurve *c = (QwtPlotCurve *)item; + + QwtSymbol symbol = c->symbol(); + + if ( cache.curveSymbolPenColors.contains(c) ) + { + symbol.setPen(cache.curveSymbolPenColors[c]); + } + + if ( cache.curveSymbolBrushColors.contains(c) ) + { + QBrush brush = symbol.brush(); + brush.setColor(cache.curveSymbolBrushColors[c]); + symbol.setBrush(brush); + } + c->setSymbol(symbol); + + if ( cache.curveColors.contains(c) ) + { + QPen pen = c->pen(); + pen.setColor(cache.curveColors[c]); + c->setPen(pen); + } + + break; + } + case QwtPlotItem::Rtti_PlotMarker: + { + QwtPlotMarker *m = (QwtPlotMarker *)item; + + if ( cache.markerFonts.contains(m) ) + { + QwtText label = m->label(); + label.setFont(cache.markerFonts[m]); + m->setLabel(label); + } + + if ( cache.markerLabelColors.contains(m) ) + { + QwtText label = m->label(); + label.setColor(cache.markerLabelColors[m]); + m->setLabel(label); + } + + if ( cache.markerLineColors.contains(m) ) + { + QPen pen = m->linePen(); + pen.setColor(cache.markerLineColors[m]); + m->setLinePen(pen); + } + + QwtSymbol symbol = m->symbol(); + + if ( cache.markerSymbolPenColors.contains(m) ) + { + QPen pen = symbol.pen(); + pen.setColor(cache.markerSymbolPenColors[m]); + symbol.setPen(pen); + } + + if ( cache.markerSymbolBrushColors.contains(m) ) + { + QBrush brush = symbol.brush(); + brush.setColor(cache.markerSymbolBrushColors[m]); + symbol.setBrush(brush); + } + + m->setSymbol(symbol); + + break; + } + default: + break; + } +} diff --git a/qwt/src/qwt_plot_printfilter.h b/qwt/src/qwt_plot_printfilter.h new file mode 100644 index 000000000..dbabf7db1 --- /dev/null +++ b/qwt/src/qwt_plot_printfilter.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_PLOT_PRINTFILTER_H +#define QWT_PLOT_PRINTFILTER_H + +#include +#include +#include "qwt_global.h" + +class QwtPlot; +class QwtPlotItem; + +/*! + \brief A base class for plot print filters. + + A print filter can be used to customize QwtPlot::print(). + + \deprecated In Qwt 5.0 the design of QwtPlot allows/recommends writing + individual QwtPlotItems, that are not known to QwtPlotPrintFilter. + So this concept is outdated and QwtPlotPrintFilter will be + removed/replaced in Qwt 6.x. +*/ +class QWT_EXPORT QwtPlotPrintFilter +{ +public: + //! Print options + enum Options + { + PrintMargin = 1, + PrintTitle = 2, + PrintLegend = 4, + PrintGrid = 8, + PrintBackground = 16, + PrintFrameWithScales = 32, + + PrintAll = ~PrintFrameWithScales + }; + + //! Print items + enum Item + { + Title, + Legend, + Curve, + CurveSymbol, + Marker, + MarkerSymbol, + MajorGrid, + MinorGrid, + CanvasBackground, + AxisScale, + AxisTitle, + WidgetBackground + }; + + explicit QwtPlotPrintFilter(); + virtual ~QwtPlotPrintFilter(); + + virtual QColor color(const QColor &, Item item) const; + virtual QFont font(const QFont &, Item item) const; + + void setOptions(int options); + int options() const; + + virtual void apply(QwtPlot *) const; + virtual void reset(QwtPlot *) const; + + virtual void apply(QwtPlotItem *) const; + virtual void reset(QwtPlotItem *) const; + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_rasteritem.cpp b/qwt/src/qwt_plot_rasteritem.cpp new file mode 100644 index 000000000..dc19825a2 --- /dev/null +++ b/qwt/src/qwt_plot_rasteritem.cpp @@ -0,0 +1,306 @@ +/* -*- 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_legend.h" +#include "qwt_legend_item.h" +#include "qwt_scale_map.h" +#include "qwt_plot_rasteritem.h" + +class QwtPlotRasterItem::PrivateData +{ +public: + PrivateData(): + alpha(-1) + { + cache.policy = QwtPlotRasterItem::NoCache; + } + + int alpha; + + struct ImageCache + { + QwtPlotRasterItem::CachePolicy policy; + QwtDoubleRect rect; + QSize size; + QImage image; + } cache; +}; + +static QImage toRgba(const QImage& image, int alpha) +{ + if ( alpha < 0 || alpha >= 255 ) + return image; + +#if QT_VERSION < 0x040000 + QImage alphaImage(image.size(), 32); + alphaImage.setAlphaBuffer(true); +#else + QImage alphaImage(image.size(), QImage::Format_ARGB32); +#endif + + const QRgb mask1 = qRgba(0, 0, 0, alpha); + const QRgb mask2 = qRgba(255, 255, 255, 0); + const QRgb mask3 = qRgba(0, 0, 0, 255); + + const int w = image.size().width(); + const int h = image.size().height(); + + if ( image.depth() == 8 ) + { + for ( int y = 0; y < h; y++ ) + { + QRgb* alphaLine = (QRgb*)alphaImage.scanLine(y); + const unsigned char *line = image.scanLine(y); + + for ( int x = 0; x < w; x++ ) + *alphaLine++ = (image.color(*line++) & mask2) | mask1; + } + } + else if ( image.depth() == 32 ) + { + for ( int y = 0; y < h; y++ ) + { + QRgb* alphaLine = (QRgb*)alphaImage.scanLine(y); + const QRgb* line = (const QRgb*) image.scanLine(y); + + for ( int x = 0; x < w; x++ ) + { + const QRgb rgb = *line++; + if ( rgb & mask3 ) // alpha != 0 + *alphaLine++ = (rgb & mask2) | mask1; + else + *alphaLine++ = rgb; + } + } + } + + return alphaImage; +} + +//! Constructor +QwtPlotRasterItem::QwtPlotRasterItem(const QString& title): + QwtPlotItem(QwtText(title)) +{ + init(); +} + +//! Constructor +QwtPlotRasterItem::QwtPlotRasterItem(const QwtText& title): + QwtPlotItem(title) +{ + init(); +} + +//! Destructor +QwtPlotRasterItem::~QwtPlotRasterItem() +{ + delete d_data; +} + +void QwtPlotRasterItem::init() +{ + d_data = new PrivateData(); + + setItemAttribute(QwtPlotItem::AutoScale, true); + setItemAttribute(QwtPlotItem::Legend, false); + + setZ(8.0); +} + +/*! + \brief Set an alpha value for the raster data + + Often a plot has several types of raster data organized in layers. + ( f.e a geographical map, with weather statistics ). + Using setAlpha() raster items can be stacked easily. + + The alpha value is a value [0, 255] to + control the transparency of the image. 0 represents a fully + transparent color, while 255 represents a fully opaque color. + + \param alpha Alpha value + + - alpha >= 0\n + All alpha values of the pixels returned by renderImage() will be set to + alpha, beside those with an alpha value of 0 (invalid pixels). + - alpha < 0 + The alpha values returned by renderImage() are not changed. + + The default alpha value is -1. + + \sa alpha() +*/ +void QwtPlotRasterItem::setAlpha(int alpha) +{ + if ( alpha < 0 ) + alpha = -1; + + if ( alpha > 255 ) + alpha = 255; + + if ( alpha != d_data->alpha ) + { + d_data->alpha = alpha; + + itemChanged(); + } +} + +/*! + \return Alpha value of the raster item + \sa setAlpha() +*/ +int QwtPlotRasterItem::alpha() const +{ + return d_data->alpha; +} + +/*! + Change the cache policy + + The default policy is NoCache + + \param policy Cache policy + \sa CachePolicy, cachePolicy() +*/ +void QwtPlotRasterItem::setCachePolicy( + QwtPlotRasterItem::CachePolicy policy) +{ + if ( d_data->cache.policy != policy ) + { + d_data->cache.policy = policy; + + invalidateCache(); + itemChanged(); + } +} + +/*! + \return Cache policy + \sa CachePolicy, setCachePolicy() +*/ +QwtPlotRasterItem::CachePolicy QwtPlotRasterItem::cachePolicy() const +{ + return d_data->cache.policy; +} + +/*! + Invalidate the paint cache + \sa setCachePolicy() +*/ +void QwtPlotRasterItem::invalidateCache() +{ + d_data->cache.image = QImage(); + d_data->cache.rect = QwtDoubleRect(); + d_data->cache.size = QSize(); +} + +/*! + \brief Returns the recommended raster for a given rect. + + F.e the raster hint can be used to limit the resolution of + the image that is rendered. + + The default implementation returns an invalid size (QSize()), + what means: no hint. +*/ +QSize QwtPlotRasterItem::rasterHint(const QwtDoubleRect &) const +{ + return QSize(); +} + +/*! + \brief Draw the raster data + \param painter Painter + \param xMap X-Scale Map + \param yMap Y-Scale Map + \param canvasRect Contents rect of the plot canvas +*/ +void QwtPlotRasterItem::draw(QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRect &canvasRect) const +{ + if ( canvasRect.isEmpty() || d_data->alpha == 0 ) + return; + + QwtDoubleRect area = invTransform(xMap, yMap, canvasRect); + if ( boundingRect().isValid() ) + area &= boundingRect(); + + const QRect paintRect = transform(xMap, yMap, area); + if ( !paintRect.isValid() ) + return; + + QImage image; + + bool doCache = true; + if ( painter->device()->devType() == QInternal::Printer + || painter->device()->devType() == QInternal::Picture ) + { + doCache = false; + } + + if ( !doCache || d_data->cache.policy == NoCache ) + { + image = renderImage(xMap, yMap, area); + if ( d_data->alpha >= 0 && d_data->alpha < 255 ) + image = toRgba(image, d_data->alpha); + + } + else if ( d_data->cache.policy == PaintCache ) + { + if ( d_data->cache.image.isNull() || d_data->cache.rect != area + || d_data->cache.size != paintRect.size() ) + { + d_data->cache.image = renderImage(xMap, yMap, area); + d_data->cache.rect = area; + d_data->cache.size = paintRect.size(); + } + + image = d_data->cache.image; + if ( d_data->alpha >= 0 && d_data->alpha < 255 ) + image = toRgba(image, d_data->alpha); + } + else if ( d_data->cache.policy == ScreenCache ) + { + const QSize screenSize = + QApplication::desktop()->screenGeometry().size(); + + if ( paintRect.width() > screenSize.width() || + paintRect.height() > screenSize.height() ) + { + image = renderImage(xMap, yMap, area); + } + else + { + if ( d_data->cache.image.isNull() || d_data->cache.rect != area ) + { + QwtScaleMap cacheXMap = xMap; + cacheXMap.setPaintInterval( 0, screenSize.width()); + + QwtScaleMap cacheYMap = yMap; + cacheYMap.setPaintInterval(screenSize.height(), 0); + + d_data->cache.image = renderImage( + cacheXMap, cacheYMap, area); + d_data->cache.rect = area; + d_data->cache.size = paintRect.size(); + } + + image = d_data->cache.image; + } + image = toRgba(image, d_data->alpha); + } + + painter->drawImage(paintRect, image); +} diff --git a/qwt/src/qwt_plot_rasteritem.h b/qwt/src/qwt_plot_rasteritem.h new file mode 100644 index 000000000..db79f86dc --- /dev/null +++ b/qwt/src/qwt_plot_rasteritem.h @@ -0,0 +1,107 @@ +/* -*- 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_RASTERITEM_H +#define QWT_PLOT_RASTERITEM_H + +#include +#include +#include + +#include "qwt_plot_item.h" + +/*! + \brief A class, which displays raster data + + Raster data is a grid of pixel values, that can be represented + as a QImage. It is used for many types of information like + spectrograms, cartograms, geographical maps ... + + Often a plot has several types of raster data organized in layers. + ( f.e a geographical map, with weather statistics ). + Using setAlpha() raster items can be stacked easily. + + QwtPlotRasterItem is only implemented for images of the following formats: + QImage::Format_Indexed8, QImage::Format_ARGB32. + + \sa QwtPlotSpectrogram +*/ + +class QWT_EXPORT QwtPlotRasterItem: public QwtPlotItem +{ +public: + /*! + - NoCache\n + renderImage() is called, whenever the item has to be repainted + - PaintCache\n + renderImage() is called, whenever the image cache is not valid, + or the scales, or the size of the canvas has changed. This type + of cache is only useful for improving the performance of hide/show + operations. All other situations are already handled by the + plot canvas cache. + - ScreenCache\n + The screen cache is an image in size of the screen. As long as + the scales don't change the target image is scaled from the cache. + This might improve the performance + when resizing the plot widget, but suffers from scaling effects. + + The default policy is NoCache + */ + enum CachePolicy + { + NoCache, + PaintCache, + ScreenCache + }; + + explicit QwtPlotRasterItem(const QString& title = QString::null); + explicit QwtPlotRasterItem(const QwtText& title); + virtual ~QwtPlotRasterItem(); + + void setAlpha(int alpha); + int alpha() const; + + void setCachePolicy(CachePolicy); + CachePolicy cachePolicy() const; + + void invalidateCache(); + + virtual void draw(QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRect &rect) const; + + virtual QSize rasterHint(const QwtDoubleRect &) const; + +protected: + + /*! + Renders an image for an area + + The format of the image must be QImage::Format_Indexed8, + QImage::Format_RGB32 or QImage::Format_ARGB32 + + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param area Requested area for the image in scale coordinates + */ + virtual QImage renderImage(const QwtScaleMap &xMap, + const QwtScaleMap &yMap, const QwtDoubleRect &area + ) const = 0; + +private: + QwtPlotRasterItem( const QwtPlotRasterItem & ); + QwtPlotRasterItem &operator=( const QwtPlotRasterItem & ); + + void init(); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_rescaler.cpp b/qwt/src/qwt_plot_rescaler.cpp new file mode 100644 index 000000000..1a34c49ff --- /dev/null +++ b/qwt/src/qwt_plot_rescaler.cpp @@ -0,0 +1,619 @@ +/* -*- 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 "qwt_plot.h" +#include "qwt_plot_canvas.h" +#include "qwt_scale_div.h" +#include "qwt_double_interval.h" +#include "qwt_plot_rescaler.h" + +class QwtPlotRescaler::AxisData +{ +public: + AxisData(): + aspectRatio(1.0), + expandingDirection(QwtPlotRescaler::ExpandUp) + { + } + + double aspectRatio; + QwtDoubleInterval intervalHint; + QwtPlotRescaler::ExpandingDirection expandingDirection; + mutable QwtScaleDiv scaleDiv; +}; + +class QwtPlotRescaler::PrivateData +{ +public: + PrivateData(): + referenceAxis(QwtPlot::xBottom), + rescalePolicy(QwtPlotRescaler::Expanding), + isEnabled(false), + inReplot(0) + { + } + + int referenceAxis; + RescalePolicy rescalePolicy; + QwtPlotRescaler::AxisData axisData[QwtPlot::axisCnt]; + bool isEnabled; + + mutable int inReplot; +}; + +/*! + Constructor + + \param canvas Canvas + \param referenceAxis Reference axis, see RescalePolicy + \param policy Rescale policy + + \sa setRescalePolicy(), setReferenceAxis() +*/ +QwtPlotRescaler::QwtPlotRescaler(QwtPlotCanvas *canvas, + int referenceAxis, RescalePolicy policy): + QObject(canvas) +{ + d_data = new PrivateData; + d_data->referenceAxis = referenceAxis; + d_data->rescalePolicy = policy; + + setEnabled(true); +} + +//! Destructor +QwtPlotRescaler::~QwtPlotRescaler() +{ + delete d_data; +} + +/*! + \brief En/disable the rescaler + + When enabled is true an event filter is installed for + the canvas, otherwise the event filter is removed. + + \param on true or false + \sa isEnabled(), eventFilter() +*/ +void QwtPlotRescaler::setEnabled(bool on) +{ + if ( d_data->isEnabled != on ) + { + d_data->isEnabled = on; + + QWidget *w = canvas(); + if ( w ) + { + if ( d_data->isEnabled ) + w->installEventFilter(this); + else + w->removeEventFilter(this); + } + } +} + +/*! + \return true when enabled, false otherwise + \sa setEnabled, eventFilter() +*/ +bool QwtPlotRescaler::isEnabled() const +{ + return d_data->isEnabled; +} + +/*! + Change the rescale policy + + \param policy Rescale policy + \sa rescalePolicy() +*/ +void QwtPlotRescaler::setRescalePolicy(RescalePolicy policy) +{ + d_data->rescalePolicy = policy; +} + +/*! + \return Rescale policy + \sa setRescalePolicy() +*/ +QwtPlotRescaler::RescalePolicy QwtPlotRescaler::rescalePolicy() const +{ + return d_data->rescalePolicy; +} + +/*! + Set the reference axis ( see RescalePolicy ) + + \param axis Axis index ( QwtPlot::Axis ) + \sa referenceAxis() +*/ +void QwtPlotRescaler::setReferenceAxis(int axis) +{ + d_data->referenceAxis = axis; +} + +/*! + \return Reference axis ( see RescalePolicy ) + \sa setReferenceAxis() +*/ +int QwtPlotRescaler::referenceAxis() const +{ + return d_data->referenceAxis; +} + +/*! + Set the direction in which all axis should be expanded + + \param direction Direction + \sa expandingDirection() +*/ +void QwtPlotRescaler::setExpandingDirection( + ExpandingDirection direction) +{ + for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) + setExpandingDirection(axis, direction); +} + +/*! + Set the direction in which an axis should be expanded + + \param axis Axis index ( see QwtPlot::AxisId ) + \param direction Direction + \sa expandingDirection() +*/ +void QwtPlotRescaler::setExpandingDirection( + int axis, ExpandingDirection direction) +{ + if ( axis >= 0 && axis < QwtPlot::axisCnt ) + d_data->axisData[axis].expandingDirection = direction; +} + +/*! + Return direction in which an axis should be expanded + + \param axis Axis index ( see QwtPlot::AxisId ) + \sa setExpandingDirection() +*/ +QwtPlotRescaler::ExpandingDirection +QwtPlotRescaler::expandingDirection(int axis) const +{ + if ( axis >= 0 && axis < QwtPlot::axisCnt ) + return d_data->axisData[axis].expandingDirection; + + return ExpandBoth; +} + +/*! + Set the aspect ratio between the scale of the reference axis + and the other scales. The default ratio is 1.0 + + \param ratio Aspect ratio + \sa aspectRatio() +*/ +void QwtPlotRescaler::setAspectRatio(double ratio) +{ + for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) + setAspectRatio(axis, ratio); +} + +/*! + Set the aspect ratio between the scale of the reference axis + and another scale. The default ratio is 1.0 + + \param axis Axis index ( see QwtPlot::AxisId ) + \param ratio Aspect ratio + \sa aspectRatio() +*/ +void QwtPlotRescaler::setAspectRatio(int axis, double ratio) +{ + if ( ratio < 0.0 ) + ratio = 0.0; + + if ( axis >= 0 && axis < QwtPlot::axisCnt ) + d_data->axisData[axis].aspectRatio = ratio; +} + +/*! + Return aspect ratio between an axis and the reference axis. + + \param axis Axis index ( see QwtPlot::AxisId ) + \sa setAspectRatio() +*/ +double QwtPlotRescaler::aspectRatio(int axis) const +{ + if ( axis >= 0 && axis < QwtPlot::axisCnt ) + return d_data->axisData[axis].aspectRatio; + + return 0.0; +} + +void QwtPlotRescaler::setIntervalHint(int axis, + const QwtDoubleInterval &interval) +{ + if ( axis >= 0 && axis < QwtPlot::axisCnt ) + d_data->axisData[axis].intervalHint = interval; +} + +QwtDoubleInterval QwtPlotRescaler::intervalHint(int axis) const +{ + if ( axis >= 0 && axis < QwtPlot::axisCnt ) + return d_data->axisData[axis].intervalHint; + + return QwtDoubleInterval(); +} + +//! \return plot canvas +QwtPlotCanvas *QwtPlotRescaler::canvas() +{ + QObject *o = parent(); + if ( o && o->inherits("QwtPlotCanvas") ) + return (QwtPlotCanvas *)o; + + return NULL; +} + +//! \return plot canvas +const QwtPlotCanvas *QwtPlotRescaler::canvas() const +{ + return ((QwtPlotRescaler *)this)->canvas(); +} + +//! \return plot widget +QwtPlot *QwtPlotRescaler::plot() +{ + QObject *w = canvas(); + if ( w ) + { + w = w->parent(); + if ( w && w->inherits("QwtPlot") ) + return (QwtPlot *)w; + } + + return NULL; +} + +//! \return plot widget +const QwtPlot *QwtPlotRescaler::plot() const +{ + return ((QwtPlotRescaler *)this)->plot(); +} + +//! Event filter for the plot canvas +bool QwtPlotRescaler::eventFilter(QObject *o, QEvent *e) +{ + if ( o && o == canvas() ) + { + switch(e->type()) + { + case QEvent::Resize: + canvasResizeEvent((QResizeEvent *)e); + break; +#if QT_VERSION >= 0x040000 + case QEvent::PolishRequest: + rescale(); + break; +#endif + default:; + } + } + + return false; +} + +void QwtPlotRescaler::canvasResizeEvent(QResizeEvent* e) +{ + const int fw = 2 * canvas()->frameWidth(); + const QSize newSize = e->size() - QSize(fw, fw); + const QSize oldSize = e->oldSize() - QSize(fw, fw); + + rescale(oldSize, newSize); +} + +//! Adjust the plot axes scales +void QwtPlotRescaler::rescale() const +{ +#if 0 + const int axis = referenceAxis(); + if ( axis < 0 || axis >= QwtPlot::axisCnt ) + return; + + const QwtDoubleInterval hint = intervalHint(axis); + if ( !hint.isNull() ) + { + QwtPlot *plt = (QwtPlot *)plot(); + + const bool doReplot = plt->autoReplot(); + plt->setAutoReplot(false); + plt->setAxisScale(axis, hint.minValue(), hint.maxValue()); + plt->setAutoReplot(doReplot); + plt->updateAxes(); + } +#endif + + const QSize size = canvas()->contentsRect().size(); + rescale(size, size); +} + +/*! + Adjust the plot axes scales + + \param oldSize Previous size of the canvas + \param newSize New size of the canvas +*/ +void QwtPlotRescaler::rescale( + const QSize &oldSize, const QSize &newSize) const +{ + if ( newSize.isEmpty() ) + return; + + QwtDoubleInterval intervals[QwtPlot::axisCnt]; + for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) + intervals[axis] = interval(axis); + + const int refAxis = referenceAxis(); + intervals[refAxis] = expandScale(refAxis, oldSize, newSize); + + for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) + { + if ( aspectRatio(axis) > 0.0 && axis != refAxis ) + intervals[axis] = syncScale(axis, intervals[refAxis], newSize); + } + + updateScales(intervals); +} + +/*! + Calculate the new scale interval of a plot axis + + \param axis Axis index ( see QwtPlot::AxisId ) + \param oldSize Previous size of the canvas + \param newSize New size of the canvas + + \return Calculated new interval for the axis +*/ +QwtDoubleInterval QwtPlotRescaler::expandScale( int axis, + const QSize &oldSize, const QSize &newSize) const +{ + const QwtDoubleInterval oldInterval = interval(axis); + + QwtDoubleInterval expanded = oldInterval; + switch(rescalePolicy()) + { + case Fixed: + { + break; // do nothing + } + case Expanding: + { + if ( !oldSize.isEmpty() ) + { + double width = oldInterval.width(); + if ( orientation(axis) == Qt::Horizontal ) + width *= double(newSize.width()) / oldSize.width(); + else + width *= double(newSize.height()) / oldSize.height(); + + expanded = expandInterval(oldInterval, + width, expandingDirection(axis)); + } + break; + } + case Fitting: + { + double dist = 0.0; + for ( int ax = 0; ax < QwtPlot::axisCnt; ax++ ) + { + const double d = pixelDist(ax, newSize); + if ( d > dist ) + dist = d; + } + if ( dist > 0.0 ) + { + double width; + if ( orientation(axis) == Qt::Horizontal ) + width = newSize.width() * dist; + else + width = newSize.height() * dist; + + expanded = expandInterval(intervalHint(axis), + width, expandingDirection(axis)); + } + break; + } + } + + return expanded; +} + +/*! + Synchronize an axis scale according to the scale of the reference axis + + \param axis Axis index ( see QwtPlot::AxisId ) + \param reference Interval of the reference axis + \param size Size of the canvas +*/ +QwtDoubleInterval QwtPlotRescaler::syncScale(int axis, + const QwtDoubleInterval& reference, const QSize &size) const +{ + double dist; + if ( orientation(referenceAxis()) == Qt::Horizontal ) + dist = reference.width() / size.width(); + else + dist = reference.width() / size.height(); + + if ( orientation(axis) == Qt::Horizontal ) + dist *= size.width(); + else + dist *= size.height(); + + dist /= aspectRatio(axis); + + QwtDoubleInterval intv; + if ( rescalePolicy() == Fitting ) + intv = intervalHint(axis); + else + intv = interval(axis); + + intv = expandInterval(intv, dist, expandingDirection(axis)); + + return intv; +} + +/*! + Return orientation of an axis + \param axis Axis index ( see QwtPlot::AxisId ) +*/ +Qt::Orientation QwtPlotRescaler::orientation(int axis) const +{ + if ( axis == QwtPlot::yLeft || axis == QwtPlot::yRight ) + return Qt::Vertical; + + return Qt::Horizontal; +} + +/*! + Return interval of an axis + \param axis Axis index ( see QwtPlot::AxisId ) +*/ +QwtDoubleInterval QwtPlotRescaler::interval(int axis) const +{ + if ( axis < 0 || axis >= QwtPlot::axisCnt ) + return QwtDoubleInterval(); + + const QwtPlot *plt = plot(); + + const double v1 = plt->axisScaleDiv(axis)->lowerBound(); + const double v2 = plt->axisScaleDiv(axis)->upperBound(); + + return QwtDoubleInterval(v1, v2).normalized(); +} + +/*! + Expand the interval + + \param interval Interval to be expanded + \param width Distance to be added to the interval + \param direction Direction of the expand operation + + \return Expanded interval +*/ +QwtDoubleInterval QwtPlotRescaler::expandInterval( + const QwtDoubleInterval &interval, double width, + ExpandingDirection direction) const +{ + QwtDoubleInterval expanded = interval; + + switch(direction) + { + case ExpandUp: + expanded.setMinValue(interval.minValue()); + expanded.setMaxValue(interval.minValue() + width); + break; + case ExpandDown: + expanded.setMaxValue(interval.maxValue()); + expanded.setMinValue(interval.maxValue() - width); + break; + case ExpandBoth: + default: + expanded.setMinValue(interval.minValue() + + interval.width() / 2.0 - width / 2.0); + expanded.setMaxValue(expanded.minValue() + width); + } + return expanded; +} + +double QwtPlotRescaler::pixelDist(int axis, const QSize &size) const +{ + const QwtDoubleInterval intv = intervalHint(axis); + + double dist = 0.0; + if ( !intv.isNull() ) + { + if ( axis == referenceAxis() ) + dist = intv.width(); + else + { + const double r = aspectRatio(axis); + if ( r > 0.0 ) + dist = intv.width() * r; + } + } + + if ( dist > 0.0 ) + { + if ( orientation(axis) == Qt::Horizontal ) + dist /= size.width(); + else + dist /= size.height(); + } + + return dist; +} + +/*! + Update the axes scales + + \param intervals Scale intervals +*/ +void QwtPlotRescaler::updateScales( + QwtDoubleInterval intervals[QwtPlot::axisCnt]) const +{ + if ( d_data->inReplot >= 5 ) + { + return; + } + + QwtPlot *plt = (QwtPlot *)plot(); + + const bool doReplot = plt->autoReplot(); + plt->setAutoReplot(false); + + for ( int axis = 0; axis < QwtPlot::axisCnt; axis++ ) + { + if ( axis == referenceAxis() || aspectRatio(axis) > 0.0 ) + { + double v1 = intervals[axis].minValue(); + double v2 = intervals[axis].maxValue(); + + if ( plt->axisScaleDiv(axis)->lowerBound() > + plt->axisScaleDiv(axis)->upperBound() ) + { + qSwap(v1, v2); + } + + if ( d_data->inReplot >= 1 ) + { + d_data->axisData[axis].scaleDiv = *plt->axisScaleDiv(axis); + } + + if ( d_data->inReplot >= 2 ) + { + QwtValueList ticks[QwtScaleDiv::NTickTypes]; + for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ ) + ticks[i] = d_data->axisData[axis].scaleDiv.ticks(i); + + plt->setAxisScaleDiv(axis, QwtScaleDiv(v1, v2, ticks)); + } + else + { + plt->setAxisScale(axis, v1, v2); + } + } + } + + plt->setAutoReplot(doReplot); + + d_data->inReplot++; + plt->replot(); + d_data->inReplot--; +} diff --git a/qwt/src/qwt_plot_rescaler.h b/qwt/src/qwt_plot_rescaler.h new file mode 100644 index 000000000..c7710c7eb --- /dev/null +++ b/qwt/src/qwt_plot_rescaler.h @@ -0,0 +1,135 @@ +/* -*- 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_RESCALER_H +#define QWT_PLOT_RESCALER_H 1 + +#include "qwt_global.h" +#include "qwt_double_rect.h" +#include "qwt_double_interval.h" +#include "qwt_plot.h" +#include + +class QwtPlotCanvas; +class QResizeEvent; + +/*! + \brief QwtPlotRescaler takes care of fixed aspect ratios for plot scales + + QwtPlotRescaler autoadjusts the axes of a QwtPlot according + to fixed aspect ratios. +*/ + +class QWT_EXPORT QwtPlotRescaler: public QObject +{ +public: + /*! + \brief Rescale Policy + + The rescale policy defines how to rescale the reference axis and + their depending axes. + + - Fixed + + The interval of the reference axis remains unchanged, when the + geometry of the canvas changes. All other axes + will be adjusted according to their aspect ratio. + + - Expanding + + The interval of the reference axis will be shrinked/expanded, + when the geometry of the canvas changes. All other axes + will be adjusted according to their aspect ratio. + + The interval, that is represented by one pixel is fixed. + + - Fitting + + The intervals of the axes are calculated, so that all axes include + their minimal interval. + */ + + enum RescalePolicy + { + Fixed, + Expanding, + Fitting + }; + + enum ExpandingDirection + { + ExpandUp, + ExpandDown, + ExpandBoth + }; + + explicit QwtPlotRescaler(QwtPlotCanvas *, + int referenceAxis = QwtPlot::xBottom, + RescalePolicy = Expanding ); + + virtual ~QwtPlotRescaler(); + + void setEnabled(bool); + bool isEnabled() const; + + void setRescalePolicy(RescalePolicy); + RescalePolicy rescalePolicy() const; + + void setExpandingDirection(ExpandingDirection); + void setExpandingDirection(int axis, ExpandingDirection); + ExpandingDirection expandingDirection(int axis) const; + + void setReferenceAxis(int axis); + int referenceAxis() const; + + void setAspectRatio(double ratio); + void setAspectRatio(int axis, double ratio); + double aspectRatio(int axis) const; + + void setIntervalHint(int axis, const QwtDoubleInterval&); + QwtDoubleInterval intervalHint(int axis) const; + + QwtPlotCanvas *canvas(); + const QwtPlotCanvas *canvas() const; + + QwtPlot *plot(); + const QwtPlot *plot() const; + + virtual bool eventFilter(QObject *, QEvent *); + + void rescale() const; + +protected: + virtual void canvasResizeEvent(QResizeEvent *); + + virtual void rescale(const QSize &oldSize, const QSize &newSize) const; + virtual QwtDoubleInterval expandScale( int axis, + const QSize &oldSize, const QSize &newSize) const; + + virtual QwtDoubleInterval syncScale( + int axis, const QwtDoubleInterval& reference, + const QSize &size) const; + + virtual void updateScales( + QwtDoubleInterval intervals[QwtPlot::axisCnt]) const; + + Qt::Orientation orientation(int axis) const; + QwtDoubleInterval interval(int axis) const; + QwtDoubleInterval expandInterval(const QwtDoubleInterval &, + double width, ExpandingDirection) const; + +private: + double pixelDist(int axis, const QSize &) const; + + class AxisData; + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_scaleitem.cpp b/qwt/src/qwt_plot_scaleitem.cpp new file mode 100644 index 000000000..cb5c41c76 --- /dev/null +++ b/qwt/src/qwt_plot_scaleitem.cpp @@ -0,0 +1,482 @@ +/* -*- 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 "qwt_plot.h" +#include "qwt_plot_canvas.h" +#include "qwt_scale_map.h" +#include "qwt_plot_scaleitem.h" +#include "qwt_double_interval.h" + +class QwtPlotScaleItem::PrivateData +{ +public: + PrivateData(): + position(0.0), + borderDistance(-1), + scaleDivFromAxis(true), + scaleDraw(new QwtScaleDraw()) + { + } + + ~PrivateData() + { + delete scaleDraw; + } + +#if QT_VERSION < 0x040000 + QColorGroup colorGroup; +#else + QPalette palette; +#endif + QFont font; + double position; + int borderDistance; + bool scaleDivFromAxis; + QwtScaleDraw *scaleDraw; + QRect canvasRectCache; +}; + +/*! + \brief Constructor for scale item at the position pos. + + \param alignment In case of QwtScaleDraw::BottomScale/QwtScaleDraw::TopScale + the scale item is corresponding to the xAxis(), + otherwise it corresponds to the yAxis(). + + \param pos x or y position, depending on the corresponding axis. + + \sa setPosition(), setAlignment() +*/ +QwtPlotScaleItem::QwtPlotScaleItem( + QwtScaleDraw::Alignment alignment, const double pos): + QwtPlotItem(QwtText("Scale")) +{ + d_data = new PrivateData; + d_data->position = pos; + d_data->scaleDraw->setAlignment(alignment); + + setZ(11.0); +} + +//! Destructor +QwtPlotScaleItem::~QwtPlotScaleItem() +{ + delete d_data; +} + +//! \return QwtPlotItem::Rtti_PlotScale +int QwtPlotScaleItem::rtti() const +{ + return QwtPlotItem::Rtti_PlotScale; +} + +/*! + \brief Assign a scale division + + When assigning a scaleDiv the scale division won't be synchronized + with the corresponding axis anymore. + + \param scaleDiv Scale division + \sa scaleDiv(), setScaleDivFromAxis(), isScaleDivFromAxis() +*/ +void QwtPlotScaleItem::setScaleDiv(const QwtScaleDiv& scaleDiv) +{ + d_data->scaleDivFromAxis = false; + d_data->scaleDraw->setScaleDiv(scaleDiv); +} + +//! \return Scale division +const QwtScaleDiv& QwtPlotScaleItem::scaleDiv() const +{ + return d_data->scaleDraw->scaleDiv(); +} + +/*! + Enable/Disable the synchronization of the scale division with + the corresponding axis. + + \param on true/false + \sa isScaleDivFromAxis() +*/ +void QwtPlotScaleItem::setScaleDivFromAxis(bool on) +{ + if ( on != d_data->scaleDivFromAxis ) + { + d_data->scaleDivFromAxis = on; + if ( on ) + { + const QwtPlot *plt = plot(); + if ( plt ) + { + updateScaleDiv( *plt->axisScaleDiv(xAxis()), + *plt->axisScaleDiv(yAxis()) ); + itemChanged(); + } + } + } +} + +/*! + \return True, if the synchronization of the scale division with + the corresponding axis is enabled. + \sa setScaleDiv(), setScaleDivFromAxis() +*/ +bool QwtPlotScaleItem::isScaleDivFromAxis() const +{ + return d_data->scaleDivFromAxis; +} + +#if QT_VERSION < 0x040000 + +/*! + Set the color group + \sa QwtAbstractScaleDraw::draw(), colorGroup() +*/ +void QwtPlotScaleItem::setColorGroup(const QColorGroup &colorGroup) +{ + if ( colorGroup != d_data->colorGroup ) + { + d_data->colorGroup = colorGroup; + itemChanged(); + } +} + +/*! + \return color group + \sa setColorGroup() +*/ +QColorGroup QwtPlotScaleItem::colorGroup() const +{ + return d_data->colorGroup; +} + +#else + +/*! + Set the palette + \sa QwtAbstractScaleDraw::draw(), palette() +*/ +void QwtPlotScaleItem::setPalette(const QPalette &palette) +{ + if ( palette != d_data->palette ) + { + d_data->palette = palette; + itemChanged(); + } +} + +/*! + \return palette + \sa setPalette() +*/ +QPalette QwtPlotScaleItem::palette() const +{ + return d_data->palette; +} + +#endif + +/*! + Change the tick label font + \sa font() +*/ +void QwtPlotScaleItem::setFont(const QFont &font) +{ + if ( font != d_data->font ) + { + d_data->font = font; + itemChanged(); + } +} + +/*! + \return tick label font + \sa setFont() +*/ +QFont QwtPlotScaleItem::font() const +{ + return d_data->font; +} + +/*! + \brief Set a scale draw + + \param scaleDraw object responsible for drawing scales. + + The main use case for replacing the default QwtScaleDraw is + to overload QwtAbstractScaleDraw::label, to replace or swallow + tick labels. + + \sa scaleDraw() +*/ +void QwtPlotScaleItem::setScaleDraw(QwtScaleDraw *scaleDraw) +{ + if ( scaleDraw == NULL ) + return; + + if ( scaleDraw != d_data->scaleDraw ) + delete d_data->scaleDraw; + + d_data->scaleDraw = scaleDraw; + + const QwtPlot *plt = plot(); + if ( plt ) + { + updateScaleDiv( *plt->axisScaleDiv(xAxis()), + *plt->axisScaleDiv(yAxis()) ); + } + + itemChanged(); +} + +/*! + \return Scale draw + \sa setScaleDraw() +*/ +const QwtScaleDraw *QwtPlotScaleItem::scaleDraw() const +{ + return d_data->scaleDraw; +} + +/*! + \return Scale draw + \sa setScaleDraw() +*/ +QwtScaleDraw *QwtPlotScaleItem::scaleDraw() +{ + return d_data->scaleDraw; +} + +/*! + Change the position of the scale + + The position is interpreted as y value for horizontal axes + and as x value for vertical axes. + + The border distance is set to -1. + + \param pos New position + \sa position(), setAlignment() +*/ +void QwtPlotScaleItem::setPosition(double pos) +{ + if ( d_data->position != pos ) + { + d_data->position = pos; + d_data->borderDistance = -1; + itemChanged(); + } +} + +/*! + \return Position of the scale + \sa setPosition(), setAlignment() +*/ +double QwtPlotScaleItem::position() const +{ + return d_data->position; +} + +/*! + \brief Align the scale to the canvas + + If distance is >= 0 the scale will be aligned to a + border of the contents rect of the canvas. If + alignment() is QwtScaleDraw::LeftScale, the scale will + be aligned to the right border, if it is QwtScaleDraw::TopScale + it will be aligned to the bottom (and vice versa), + + If distance is < 0 the scale will be at the position(). + + \param distance Number of pixels between the canvas border and the + backbone of the scale. + + \sa setPosition(), borderDistance() +*/ +void QwtPlotScaleItem::setBorderDistance(int distance) +{ + if ( distance < 0 ) + distance = -1; + + if ( distance != d_data->borderDistance ) + { + d_data->borderDistance = distance; + itemChanged(); + } +} + +/*! + \return Distance from a canvas border + \sa setBorderDistance(), setPosition() +*/ +int QwtPlotScaleItem::borderDistance() const +{ + return d_data->borderDistance; +} + +/*! + Change the alignment of the scale + + The alignment sets the orientation of the scale and the position of + the ticks: + + - QwtScaleDraw::BottomScale: horizontal, ticks below + - QwtScaleDraw::TopScale: horizontal, ticks above + - QwtScaleDraw::LeftScale: vertical, ticks left + - QwtScaleDraw::RightScale: vertical, ticks right + + For horizontal scales the position corresponds to QwtPlotItem::yAxis(), + otherwise to QwtPlotItem::xAxis(). + + \sa scaleDraw(), QwtScaleDraw::alignment(), setPosition() +*/ +void QwtPlotScaleItem::setAlignment(QwtScaleDraw::Alignment alignment) +{ + QwtScaleDraw *sd = d_data->scaleDraw; + if ( sd->alignment() != alignment ) + { + sd->setAlignment(alignment); + itemChanged(); + } +} + +/*! + \brief Draw the scale +*/ +void QwtPlotScaleItem::draw(QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRect &canvasRect) const +{ + if ( canvasRect != d_data->canvasRectCache ) + { + QwtPlotScaleItem* that = (QwtPlotScaleItem*)this; + that->updateBorders(); + } + + QPen pen = painter->pen(); + pen.setStyle(Qt::SolidLine); + painter->setPen(pen); + + int pw = painter->pen().width(); + if ( pw == 0 ) + pw = 1; + + QwtScaleDraw *sd = d_data->scaleDraw; + if ( sd->orientation() == Qt::Horizontal ) + { + int y; + if ( d_data->borderDistance >= 0 ) + { + if ( sd->alignment() == QwtScaleDraw::BottomScale ) + y = canvasRect.top() + d_data->borderDistance; + else + { + y = canvasRect.bottom() - d_data->borderDistance - pw + 1; + } + + } + else + { + y = yMap.transform(d_data->position); + } + + if ( y < canvasRect.top() || y > canvasRect.bottom() ) + return; + + sd->move(canvasRect.left(), y); + sd->setLength(canvasRect.width() - 1); + sd->setTransformation(xMap.transformation()->copy()); + } + else // == Qt::Vertical + { + int x; + if ( d_data->borderDistance >= 0 ) + { + if ( sd->alignment() == QwtScaleDraw::RightScale ) + x = canvasRect.left() + d_data->borderDistance; + else + { + x = canvasRect.right() - d_data->borderDistance - pw + 1; + } + } + else + { + x = xMap.transform(d_data->position); + } + if ( x < canvasRect.left() || x > canvasRect.right() ) + return; + + sd->move(x, canvasRect.top()); + sd->setLength(canvasRect.height() - 1); + sd->setTransformation(yMap.transformation()->copy()); + } + + painter->setFont(d_data->font); + +#if QT_VERSION < 0x040000 + sd->draw(painter, d_data->colorGroup); +#else + sd->draw(painter, d_data->palette); +#endif + +} + +/*! + \brief Update the item to changes of the axes scale division + + In case of isScaleDivFromAxis(), the scale draw is synchronized + to the correspond axis. + + \param xScaleDiv Scale division of the x-axis + \param yScaleDiv Scale division of the y-axis + + \sa QwtPlot::updateAxes() +*/ + +void QwtPlotScaleItem::updateScaleDiv(const QwtScaleDiv& xScaleDiv, + const QwtScaleDiv& yScaleDiv) +{ + QwtScaleDraw *sd = d_data->scaleDraw; + if ( d_data->scaleDivFromAxis && sd ) + { + sd->setScaleDiv( + sd->orientation() == Qt::Horizontal ? xScaleDiv : yScaleDiv); + updateBorders(); + } +} + +void QwtPlotScaleItem::updateBorders() +{ + const QwtPlot *plt = plot(); + if ( plt == NULL || !d_data->scaleDivFromAxis ) + return; + + const QRect r = plt->canvas()->contentsRect(); + d_data->canvasRectCache = r; + + QwtDoubleInterval interval; + if ( d_data->scaleDraw->orientation() == Qt::Horizontal ) + { + const QwtScaleMap map = plt->canvasMap(xAxis()); + interval.setMinValue(map.invTransform(r.left())); + interval.setMaxValue(map.invTransform(r.right())); + } + else + { + const QwtScaleMap map = plt->canvasMap(yAxis()); + interval.setMinValue(map.invTransform(r.bottom())); + interval.setMaxValue(map.invTransform(r.top())); + } + + QwtScaleDiv scaleDiv = d_data->scaleDraw->scaleDiv(); + scaleDiv.setInterval(interval); + d_data->scaleDraw->setScaleDiv(scaleDiv); +} diff --git a/qwt/src/qwt_plot_scaleitem.h b/qwt/src/qwt_plot_scaleitem.h new file mode 100644 index 000000000..9107cb80b --- /dev/null +++ b/qwt/src/qwt_plot_scaleitem.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_PLOT_SCALE_ITEM_H +#define QWT_PLOT_SCALE_ITEM_H + +#include "qwt_global.h" +#include "qwt_plot_item.h" +#include "qwt_scale_draw.h" + +#if QT_VERSION < 0x040000 +class QColorGroup; +#else +class QPalette; +#endif + +/*! + \brief A class which draws a scale inside the plot canvas + + QwtPlotScaleItem can be used to draw an axis inside the plot canvas. + It might by synchronized to one of the axis of the plot, but can + also display its own ticks and labels. + + It is allowed to synchronize the scale item with a disabled axis. + In plots with vertical and horizontal scale items, it might be + necessary to remove ticks at the intersections, by overloading + updateScaleDiv(). + + The scale might be at a specific position (f.e 0.0) or it might be + aligned to a canvas border. + + \par Example + The following example shows how to replace the left axis, by a scale item + at the x position 0.0. + \verbatim +QwtPlotScaleItem *scaleItem = + new QwtPlotScaleItem(QwtScaleDraw::RightScale, 0.0); +scaleItem->setFont(plot->axisWidget(QwtPlot::yLeft)->font()); +scaleItem->attach(plot); + +plot->enableAxis(QwtPlot::yLeft, false); +\endverbatim +*/ + +class QWT_EXPORT QwtPlotScaleItem: public QwtPlotItem +{ +public: + explicit QwtPlotScaleItem( + QwtScaleDraw::Alignment = QwtScaleDraw::BottomScale, + const double pos = 0.0); + virtual ~QwtPlotScaleItem(); + + virtual int rtti() const; + + void setScaleDiv(const QwtScaleDiv& ); + const QwtScaleDiv& scaleDiv() const; + + void setScaleDivFromAxis(bool on); + bool isScaleDivFromAxis() const; + +#if QT_VERSION < 0x040000 + void setColorGroup(const QColorGroup &); + QColorGroup colorGroup() const; +#else + void setPalette(const QPalette &); + QPalette palette() const; +#endif + + void setFont(const QFont&); + QFont font() const; + + void setScaleDraw(QwtScaleDraw *); + + const QwtScaleDraw *scaleDraw() const; + QwtScaleDraw *scaleDraw(); + + void setPosition(double pos); + double position() const; + + void setBorderDistance(int numPixels); + int borderDistance() const; + + void setAlignment(QwtScaleDraw::Alignment); + + virtual void draw(QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRect &rect) const; + + virtual void updateScaleDiv(const QwtScaleDiv&, + const QwtScaleDiv&); + +private: + void updateBorders(); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_spectrogram.cpp b/qwt/src/qwt_plot_spectrogram.cpp new file mode 100644 index 000000000..56b7304f8 --- /dev/null +++ b/qwt/src/qwt_plot_spectrogram.cpp @@ -0,0 +1,658 @@ +/* -*- 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 "qwt_painter.h" +#include "qwt_double_interval.h" +#include "qwt_scale_map.h" +#include "qwt_color_map.h" +#include "qwt_plot_spectrogram.h" + +#if QT_VERSION < 0x040000 +typedef QValueVector QwtColorTable; +#else +typedef QVector QwtColorTable; +#endif + +class QwtPlotSpectrogramImage: public QImage +{ + // This class hides some Qt3/Qt4 API differences +public: + QwtPlotSpectrogramImage(const QSize &size, QwtColorMap::Format format): +#if QT_VERSION < 0x040000 + QImage(size, format == QwtColorMap::RGB ? 32 : 8) +#else + QImage(size, format == QwtColorMap::RGB + ? QImage::Format_ARGB32 : QImage::Format_Indexed8 ) +#endif + { + } + + QwtPlotSpectrogramImage(const QImage &other): + QImage(other) + { + } + + void initColorTable(const QImage& other) + { +#if QT_VERSION < 0x040000 + const unsigned int numColors = other.numColors(); + + setNumColors(numColors); + for ( unsigned int i = 0; i < numColors; i++ ) + setColor(i, other.color(i)); +#else + setColorTable(other.colorTable()); +#endif + } + +#if QT_VERSION < 0x040000 + + void setColorTable(const QwtColorTable &colorTable) + { + setNumColors(colorTable.size()); + for ( unsigned int i = 0; i < colorTable.size(); i++ ) + setColor(i, colorTable[i]); + } + + QwtColorTable colorTable() const + { + QwtColorTable table(numColors()); + for ( int i = 0; i < numColors(); i++ ) + table[i] = color(i); + + return table; + } +#endif +}; + +class QwtPlotSpectrogram::PrivateData +{ +public: + class DummyData: public QwtRasterData + { + public: + virtual QwtRasterData *copy() const + { + return new DummyData(); + } + + virtual double value(double, double) const + { + return 0.0; + } + + virtual QwtDoubleInterval range() const + { + return QwtDoubleInterval(0.0, 1.0); + } + }; + + PrivateData() + { + data = new DummyData(); + colorMap = new QwtLinearColorMap(); + displayMode = ImageMode; + + conrecAttributes = QwtRasterData::IgnoreAllVerticesOnLevel; + conrecAttributes |= QwtRasterData::IgnoreOutOfRange; + } + ~PrivateData() + { + delete data; + delete colorMap; + } + + QwtRasterData *data; + QwtColorMap *colorMap; + int displayMode; + + QwtValueList contourLevels; + QPen defaultContourPen; + int conrecAttributes; +}; + +/*! + Sets the following item attributes: + - QwtPlotItem::AutoScale: true + - QwtPlotItem::Legend: false + + The z value is initialized by 8.0. + + \param title Title + + \sa QwtPlotItem::setItemAttribute(), QwtPlotItem::setZ() +*/ +QwtPlotSpectrogram::QwtPlotSpectrogram(const QString &title): + QwtPlotRasterItem(title) +{ + d_data = new PrivateData(); + + setItemAttribute(QwtPlotItem::AutoScale, true); + setItemAttribute(QwtPlotItem::Legend, false); + + setZ(8.0); +} + +//! Destructor +QwtPlotSpectrogram::~QwtPlotSpectrogram() +{ + delete d_data; +} + +//! \return QwtPlotItem::Rtti_PlotSpectrogram +int QwtPlotSpectrogram::rtti() const +{ + return QwtPlotItem::Rtti_PlotSpectrogram; +} + +/*! + The display mode controls how the raster data will be represented. + + \param mode Display mode + \param on On/Off + + The default setting enables ImageMode. + + \sa DisplayMode, displayMode() +*/ +void QwtPlotSpectrogram::setDisplayMode(DisplayMode mode, bool on) +{ + if ( on != bool(mode & d_data->displayMode) ) + { + if ( on ) + d_data->displayMode |= mode; + else + d_data->displayMode &= ~mode; + } + + itemChanged(); +} + +/*! + The display mode controls how the raster data will be represented. + + \param mode Display mode + \return true if mode is enabled +*/ +bool QwtPlotSpectrogram::testDisplayMode(DisplayMode mode) const +{ + return (d_data->displayMode & mode); +} + +/*! + Change the color map + + Often it is useful to display the mapping between intensities and + colors as an additional plot axis, showing a color bar. + + \param colorMap Color Map + + \sa colorMap(), QwtScaleWidget::setColorBarEnabled(), + QwtScaleWidget::setColorMap() +*/ +void QwtPlotSpectrogram::setColorMap(const QwtColorMap &colorMap) +{ + delete d_data->colorMap; + d_data->colorMap = colorMap.copy(); + + invalidateCache(); + itemChanged(); +} + +/*! + \return Color Map used for mapping the intensity values to colors + \sa setColorMap() +*/ +const QwtColorMap &QwtPlotSpectrogram::colorMap() const +{ + return *d_data->colorMap; +} + +/*! + \brief Set the default pen for the contour lines + + If the spectrogram has a valid default contour pen + a contour line is painted using the default contour pen. + Otherwise (pen.style() == Qt::NoPen) the pen is calculated + for each contour level using contourPen(). + + \sa defaultContourPen(), contourPen() +*/ +void QwtPlotSpectrogram::setDefaultContourPen(const QPen &pen) +{ + if ( pen != d_data->defaultContourPen ) + { + d_data->defaultContourPen = pen; + itemChanged(); + } +} + +/*! + \return Default contour pen + \sa setDefaultContourPen() +*/ +QPen QwtPlotSpectrogram::defaultContourPen() const +{ + return d_data->defaultContourPen; +} + +/*! + \brief Calculate the pen for a contour line + + The color of the pen is the color for level calculated by the color map + + \param level Contour level + \return Pen for the contour line + \note contourPen is only used if defaultContourPen().style() == Qt::NoPen + + \sa setDefaultContourPen(), setColorMap(), setContourLevels() +*/ +QPen QwtPlotSpectrogram::contourPen(double level) const +{ + const QwtDoubleInterval intensityRange = d_data->data->range(); + const QColor c(d_data->colorMap->rgb(intensityRange, level)); + + return QPen(c); +} + +/*! + Modify an attribute of the CONREC algorithm, used to calculate + the contour lines. + + \param attribute CONREC attribute + \param on On/Off + + \sa testConrecAttribute(), renderContourLines(), + QwtRasterData::contourLines() +*/ +void QwtPlotSpectrogram::setConrecAttribute( + QwtRasterData::ConrecAttribute attribute, bool on) +{ + if ( bool(d_data->conrecAttributes & attribute) == on ) + return; + + if ( on ) + d_data->conrecAttributes |= attribute; + else + d_data->conrecAttributes &= ~attribute; + + itemChanged(); +} + +/*! + Test an attribute of the CONREC algorithm, used to calculate + the contour lines. + + \param attribute CONREC attribute + \return true, is enabled + + \sa setConrecAttribute(), renderContourLines(), + QwtRasterData::contourLines() +*/ +bool QwtPlotSpectrogram::testConrecAttribute( + QwtRasterData::ConrecAttribute attribute) const +{ + return d_data->conrecAttributes & attribute; +} + +/*! + Set the levels of the contour lines + + \param levels Values of the contour levels + \sa contourLevels(), renderContourLines(), + QwtRasterData::contourLines() + + \note contourLevels returns the same levels but sorted. +*/ +void QwtPlotSpectrogram::setContourLevels(const QwtValueList &levels) +{ + d_data->contourLevels = levels; +#if QT_VERSION >= 0x040000 + qSort(d_data->contourLevels); +#else + qHeapSort(d_data->contourLevels); +#endif + itemChanged(); +} + +/*! + \brief Return the levels of the contour lines. + + The levels are sorted in increasing order. + + \sa contourLevels(), renderContourLines(), + QwtRasterData::contourLines() +*/ +QwtValueList QwtPlotSpectrogram::contourLevels() const +{ + return d_data->contourLevels; +} + +/*! + Set the data to be displayed + + \param data Spectrogram Data + \sa data() +*/ +void QwtPlotSpectrogram::setData(const QwtRasterData &data) +{ + delete d_data->data; + d_data->data = data.copy(); + + invalidateCache(); + itemChanged(); +} + +/*! + \return Spectrogram data + \sa setData() +*/ +const QwtRasterData &QwtPlotSpectrogram::data() const +{ + return *d_data->data; +} + +/*! + \return Bounding rect of the data + \sa QwtRasterData::boundingRect() +*/ +QwtDoubleRect QwtPlotSpectrogram::boundingRect() const +{ + return d_data->data->boundingRect(); +} + +/*! + \brief Returns the recommended raster for a given rect. + + F.e the raster hint is used to limit the resolution of + the image that is rendered. + + \param rect Rect for the raster hint + \return data().rasterHint(rect) +*/ +QSize QwtPlotSpectrogram::rasterHint(const QwtDoubleRect &rect) const +{ + return d_data->data->rasterHint(rect); +} + +/*! + \brief Render an image from the data and color map. + + The area is translated into a rect of the paint device. + For each pixel of this rect the intensity is mapped + into a color. + + \param xMap X-Scale Map + \param yMap Y-Scale Map + \param area Area that should be rendered in scale coordinates. + + \return A QImage::Format_Indexed8 or QImage::Format_ARGB32 depending + on the color map. + + \sa QwtRasterData::intensity(), QwtColorMap::rgb(), + QwtColorMap::colorIndex() +*/ +QImage QwtPlotSpectrogram::renderImage( + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QwtDoubleRect &area) const +{ + if ( area.isEmpty() ) + return QImage(); + + QRect rect = transform(xMap, yMap, area); + + QwtScaleMap xxMap = xMap; + QwtScaleMap yyMap = yMap; + + const QSize res = d_data->data->rasterHint(area); + if ( res.isValid() ) + { + /* + It is useless to render an image with a higher resolution + than the data offers. Of course someone will have to + scale this image later into the size of the given rect, but f.e. + in case of postscript this will done on the printer. + */ + rect.setSize(rect.size().boundedTo(res)); + + int px1 = rect.x(); + int px2 = rect.x() + rect.width(); + if ( xMap.p1() > xMap.p2() ) + qSwap(px1, px2); + + double sx1 = area.x(); + double sx2 = area.x() + area.width(); + if ( xMap.s1() > xMap.s2() ) + qSwap(sx1, sx2); + + int py1 = rect.y(); + int py2 = rect.y() + rect.height(); + if ( yMap.p1() > yMap.p2() ) + qSwap(py1, py2); + + double sy1 = area.y(); + double sy2 = area.y() + area.height(); + if ( yMap.s1() > yMap.s2() ) + qSwap(sy1, sy2); + + xxMap.setPaintInterval(px1, px2); + xxMap.setScaleInterval(sx1, sx2); + yyMap.setPaintInterval(py1, py2); + yyMap.setScaleInterval(sy1, sy2); + } + + QwtPlotSpectrogramImage image(rect.size(), d_data->colorMap->format()); + + const QwtDoubleInterval intensityRange = d_data->data->range(); + if ( !intensityRange.isValid() ) + return image; + + d_data->data->initRaster(area, rect.size()); + + if ( d_data->colorMap->format() == QwtColorMap::RGB ) + { + for ( int y = rect.top(); y <= rect.bottom(); y++ ) + { + const double ty = yyMap.invTransform(y); + + QRgb *line = (QRgb *)image.scanLine(y - rect.top()); + for ( int x = rect.left(); x <= rect.right(); x++ ) + { + const double tx = xxMap.invTransform(x); + + *line++ = d_data->colorMap->rgb(intensityRange, + d_data->data->value(tx, ty)); + } + } + } + else if ( d_data->colorMap->format() == QwtColorMap::Indexed ) + { + image.setColorTable(d_data->colorMap->colorTable(intensityRange)); + + for ( int y = rect.top(); y <= rect.bottom(); y++ ) + { + const double ty = yyMap.invTransform(y); + + unsigned char *line = image.scanLine(y - rect.top()); + for ( int x = rect.left(); x <= rect.right(); x++ ) + { + const double tx = xxMap.invTransform(x); + + *line++ = d_data->colorMap->colorIndex(intensityRange, + d_data->data->value(tx, ty)); + } + } + } + + d_data->data->discardRaster(); + + // Mirror the image in case of inverted maps + + const bool hInvert = xxMap.p1() > xxMap.p2(); + const bool vInvert = yyMap.p1() < yyMap.p2(); + if ( hInvert || vInvert ) + { +#ifdef __GNUC__ +#warning Better invert the for loops above +#endif +#if QT_VERSION < 0x040000 + image = image.mirror(hInvert, vInvert); +#else + image = image.mirrored(hInvert, vInvert); +#endif + } + + return image; +} + +/*! + \brief Return the raster to be used by the CONREC contour algorithm. + + A larger size will improve the precisision of the CONREC algorithm, + but will slow down the time that is needed to calculate the lines. + + The default implementation returns rect.size() / 2 bounded to + data().rasterHint(). + + \param area Rect, where to calculate the contour lines + \param rect Rect in pixel coordinates, where to paint the contour lines + \return Raster to be used by the CONREC contour algorithm. + + \note The size will be bounded to rect.size(). + + \sa drawContourLines(), QwtRasterData::contourLines() +*/ +QSize QwtPlotSpectrogram::contourRasterSize(const QwtDoubleRect &area, + const QRect &rect) const +{ + QSize raster = rect.size() / 2; + + const QSize rasterHint = d_data->data->rasterHint(area); + if ( rasterHint.isValid() ) + raster = raster.boundedTo(rasterHint); + + return raster; +} + +/*! + Calculate contour lines + + \param rect Rectangle, where to calculate the contour lines + \param raster Raster, used by the CONREC algorithm + + \sa contourLevels(), setConrecAttribute(), + QwtRasterData::contourLines() +*/ +QwtRasterData::ContourLines QwtPlotSpectrogram::renderContourLines( + const QwtDoubleRect &rect, const QSize &raster) const +{ + return d_data->data->contourLines(rect, raster, + d_data->contourLevels, d_data->conrecAttributes ); +} + +/*! + Paint the contour lines + + \param painter Painter + \param xMap Maps x-values into pixel coordinates. + \param yMap Maps y-values into pixel coordinates. + \param contourLines Contour lines + + \sa renderContourLines(), defaultContourPen(), contourPen() +*/ +void QwtPlotSpectrogram::drawContourLines(QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QwtRasterData::ContourLines &contourLines) const +{ + const QwtDoubleInterval intensityRange = d_data->data->range(); + + const int numLevels = (int)d_data->contourLevels.size(); + for (int l = 0; l < numLevels; l++) + { + const double level = d_data->contourLevels[l]; + + QPen pen = defaultContourPen(); + if ( pen.style() == Qt::NoPen ) + pen = contourPen(level); + + if ( pen.style() == Qt::NoPen ) + continue; + + painter->setPen(QwtPainter::scaledPen(pen)); + +#if QT_VERSION >= 0x040000 + const QPolygonF &lines = contourLines[level]; +#else + const QwtArray &lines = contourLines[level]; +#endif + for ( int i = 0; i < (int)lines.size(); i += 2 ) + { + const QPoint p1( xMap.transform(lines[i].x()), + yMap.transform(lines[i].y()) ); + const QPoint p2( xMap.transform(lines[i+1].x()), + yMap.transform(lines[i+1].y()) ); + + QwtPainter::drawLine(painter, p1, p2); + } + } +} + +/*! + \brief Draw the spectrogram + + \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 + + \sa setDisplayMode(), renderImage(), + QwtPlotRasterItem::draw(), drawContourLines() +*/ + +void QwtPlotSpectrogram::draw(QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRect &canvasRect) const +{ + if ( d_data->displayMode & ImageMode ) + QwtPlotRasterItem::draw(painter, xMap, yMap, canvasRect); + + if ( d_data->displayMode & ContourMode ) + { + // Add some pixels at the borders, so that + const int margin = 2; + QRect rasterRect(canvasRect.x() - margin, canvasRect.y() - margin, + canvasRect.width() + 2 * margin, canvasRect.height() + 2 * margin); + + QwtDoubleRect area = invTransform(xMap, yMap, rasterRect); + + const QwtDoubleRect br = boundingRect(); + if ( br.isValid() ) + { + area &= br; + if ( area.isEmpty() ) + return; + + rasterRect = transform(xMap, yMap, area); + } + + QSize raster = contourRasterSize(area, rasterRect); + raster = raster.boundedTo(rasterRect.size()); + if ( raster.isValid() ) + { + const QwtRasterData::ContourLines lines = + renderContourLines(area, raster); + + drawContourLines(painter, xMap, yMap, lines); + } + } +} + diff --git a/qwt/src/qwt_plot_spectrogram.h b/qwt/src/qwt_plot_spectrogram.h new file mode 100644 index 000000000..6f3389d4c --- /dev/null +++ b/qwt/src/qwt_plot_spectrogram.h @@ -0,0 +1,109 @@ +/* -*- 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_SPECTROGRAM_H +#define QWT_PLOT_SPECTROGRAM_H + +#include + +#include "qwt_valuelist.h" +#include "qwt_raster_data.h" +#include "qwt_plot_rasteritem.h" + +class QwtColorMap; + +/*! + \brief A plot item, which displays a spectrogram + + A spectrogram displays threedimenional data, where the 3rd dimension + ( the intensity ) is displayed using colors. The colors are calculated + from the values using a color map. + + In ContourMode contour lines are painted for the contour levels. + + \image html spectrogram3.png + + \sa QwtRasterData, QwtColorMap +*/ + +class QWT_EXPORT QwtPlotSpectrogram: public QwtPlotRasterItem +{ +public: + /*! + The display mode controls how the raster data will be represented. + - ImageMode\n + The values are mapped to colors using a color map. + - ContourMode\n + The data is displayed using contour lines + + When both modes are enabled the contour lines are painted on + top of the spectrogram. The default setting enables ImageMode. + + \sa setDisplayMode(), testDisplayMode() + */ + + enum DisplayMode + { + ImageMode = 1, + ContourMode = 2 + }; + + explicit QwtPlotSpectrogram(const QString &title = QString::null); + virtual ~QwtPlotSpectrogram(); + + void setDisplayMode(DisplayMode, bool on = true); + bool testDisplayMode(DisplayMode) const; + + void setData(const QwtRasterData &data); + const QwtRasterData &data() const; + + void setColorMap(const QwtColorMap &); + const QwtColorMap &colorMap() const; + + virtual QwtDoubleRect boundingRect() const; + virtual QSize rasterHint(const QwtDoubleRect &) const; + + void setDefaultContourPen(const QPen &); + QPen defaultContourPen() const; + + virtual QPen contourPen(double level) const; + + void setConrecAttribute(QwtRasterData::ConrecAttribute, bool on); + bool testConrecAttribute(QwtRasterData::ConrecAttribute) const; + + void setContourLevels(const QwtValueList &); + QwtValueList contourLevels() const; + + virtual int rtti() const; + + virtual void draw(QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRect &rect) const; + +protected: + virtual QImage renderImage( + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QwtDoubleRect &rect) const; + + virtual QSize contourRasterSize( + const QwtDoubleRect &, const QRect &) const; + + virtual QwtRasterData::ContourLines renderContourLines( + const QwtDoubleRect &rect, const QSize &raster) const; + + virtual void drawContourLines(QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QwtRasterData::ContourLines& lines) const; + +private: + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_svgitem.cpp b/qwt/src/qwt_plot_svgitem.cpp new file mode 100644 index 000000000..6b0e4ad6b --- /dev/null +++ b/qwt/src/qwt_plot_svgitem.cpp @@ -0,0 +1,286 @@ +/* -*- 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 +#if QT_VERSION >= 0x040100 +#include +#else +#include +#include +#endif +#if QT_VERSION < 0x040000 +#include +#endif +#include "qwt_scale_map.h" +#include "qwt_legend.h" +#include "qwt_legend_item.h" +#include "qwt_plot_svgitem.h" + +class QwtPlotSvgItem::PrivateData +{ +public: + PrivateData() + { + } + + QwtDoubleRect boundingRect; +#if QT_VERSION >= 0x040100 + QSvgRenderer renderer; +#else + QPicture picture; +#endif +}; + +/*! + \brief Constructor + + Sets the following item attributes: + - QwtPlotItem::AutoScale: true + - QwtPlotItem::Legend: false + + \param title Title +*/ +QwtPlotSvgItem::QwtPlotSvgItem(const QString& title): + QwtPlotItem(QwtText(title)) +{ + init(); +} + +/*! + \brief Constructor + + Sets the following item attributes: + - QwtPlotItem::AutoScale: true + - QwtPlotItem::Legend: false + + \param title Title +*/ +QwtPlotSvgItem::QwtPlotSvgItem(const QwtText& title): + QwtPlotItem(title) +{ + init(); +} + +//! Destructor +QwtPlotSvgItem::~QwtPlotSvgItem() +{ + delete d_data; +} + +void QwtPlotSvgItem::init() +{ + d_data = new PrivateData(); + + setItemAttribute(QwtPlotItem::AutoScale, true); + setItemAttribute(QwtPlotItem::Legend, false); + + setZ(8.0); +} + +//! \return QwtPlotItem::Rtti_PlotSVG +int QwtPlotSvgItem::rtti() const +{ + return QwtPlotItem::Rtti_PlotSVG; +} + +/*! + Load a SVG file + + \param rect Bounding rectangle + \param fileName SVG file name + + \return true, if the SVG file could be loaded +*/ +bool QwtPlotSvgItem::loadFile(const QwtDoubleRect &rect, + const QString &fileName) +{ + d_data->boundingRect = rect; +#if QT_VERSION >= 0x040100 + const bool ok = d_data->renderer.load(fileName); +#else + const bool ok = d_data->picture.load(fileName, "svg"); +#endif + itemChanged(); + return ok; +} + +/*! + Load SVG data + + \param rect Bounding rectangle + \param data in SVG format + + \return true, if the SVG data could be loaded +*/ +bool QwtPlotSvgItem::loadData(const QwtDoubleRect &rect, + const QByteArray &data) +{ + d_data->boundingRect = rect; +#if QT_VERSION >= 0x040100 + const bool ok = d_data->renderer.load(data); +#else +#if QT_VERSION >= 0x040000 + QBuffer buffer(&(QByteArray&)data); +#else + QBuffer buffer(data); +#endif + const bool ok = d_data->picture.load(&buffer, "svg"); +#endif + itemChanged(); + return ok; +} + +//! Bounding rect of the item +QwtDoubleRect QwtPlotSvgItem::boundingRect() const +{ + return d_data->boundingRect; +} + +#if QT_VERSION >= 0x040100 + +//! \return Renderer used to render the SVG data +const QSvgRenderer &QwtPlotSvgItem::renderer() const +{ + return d_data->renderer; +} + +//! \return Renderer used to render the SVG data +QSvgRenderer &QwtPlotSvgItem::renderer() +{ + return d_data->renderer; +} +#endif + +/*! + Draw the SVG item + + \param painter Painter + \param xMap X-Scale Map + \param yMap Y-Scale Map + \param canvasRect Contents rect of the plot canvas +*/ +void QwtPlotSvgItem::draw(QPainter *painter, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRect &canvasRect) const +{ + const QwtDoubleRect cRect = invTransform(xMap, yMap, canvasRect); + const QwtDoubleRect bRect = boundingRect(); + if ( bRect.isValid() && cRect.isValid() ) + { + QwtDoubleRect rect = bRect; + if ( bRect.contains(cRect) ) + rect = cRect; + + const QRect r = transform(xMap, yMap, rect); + render(painter, viewBox(rect), r); + } +} + +/*! + Render the SVG data + + \param painter Painter + \param viewBox View Box, see QSvgRenderer::viewBox + \param rect Traget rectangle on the paint device +*/ +void QwtPlotSvgItem::render(QPainter *painter, + const QwtDoubleRect &viewBox, const QRect &rect) const +{ + if ( !viewBox.isValid() ) + return; + +#if QT_VERSION >= 0x040200 + d_data->renderer.setViewBox(viewBox); + d_data->renderer.render(painter, rect); + return; +#else + +#if QT_VERSION >= 0x040100 + const QSize paintSize(painter->window().width(), + painter->window().height()); + if ( !paintSize.isValid() ) + return; + + const double xRatio = paintSize.width() / viewBox.width(); + const double yRatio = paintSize.height() / viewBox.height(); + + const double dx = rect.left() / xRatio + 1.0; + const double dy = rect.top() / yRatio + 1.0; + + const double mx = double(rect.width()) / paintSize.width(); + const double my = double(rect.height()) / paintSize.height(); + + painter->save(); + + painter->translate(dx, dy); + painter->scale(mx, my); + + d_data->renderer.setViewBox(viewBox.toRect()); + d_data->renderer.render(painter); + + painter->restore(); +#else + const double mx = rect.width() / viewBox.width(); + const double my = rect.height() / viewBox.height(); + const double dx = rect.x() - mx * viewBox.x(); + const double dy = rect.y() - my * viewBox.y(); + + painter->save(); + + painter->translate(dx, dy); + painter->scale(mx, my); + + d_data->picture.play(painter); + + painter->restore(); +#endif // < 0x040100 +#endif // < 0x040200 +} + +/*! + Calculate the viewBox from an rect and boundingRect(). + + \param rect Rectangle in scale coordinates + \return viewBox View Box, see QSvgRenderer::viewBox +*/ +QwtDoubleRect QwtPlotSvgItem::viewBox(const QwtDoubleRect &rect) const +{ +#if QT_VERSION >= 0x040100 + const QSize sz = d_data->renderer.defaultSize(); +#else +#if QT_VERSION > 0x040000 + const QSize sz(d_data->picture.width(), + d_data->picture.height()); +#else + QPaintDeviceMetrics metrics(&d_data->picture); + const QSize sz(metrics.width(), metrics.height()); +#endif +#endif + const QwtDoubleRect br = boundingRect(); + + if ( !rect.isValid() || !br.isValid() || sz.isNull() ) + return QwtDoubleRect(); + + QwtScaleMap xMap; + xMap.setScaleInterval(br.left(), br.right()); + xMap.setPaintInterval(0, sz.width()); + + QwtScaleMap yMap; + yMap.setScaleInterval(br.top(), br.bottom()); + yMap.setPaintInterval(sz.height(), 0); + + const double x1 = xMap.xTransform(rect.left()); + const double x2 = xMap.xTransform(rect.right()); + const double y1 = yMap.xTransform(rect.bottom()); + const double y2 = yMap.xTransform(rect.top()); + + return QwtDoubleRect(x1, y1, x2 - x1, y2 - y1); +} diff --git a/qwt/src/qwt_plot_svgitem.h b/qwt/src/qwt_plot_svgitem.h new file mode 100644 index 000000000..296d31f00 --- /dev/null +++ b/qwt/src/qwt_plot_svgitem.h @@ -0,0 +1,66 @@ +/* -*- 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_SVGITEM_H +#define QWT_PLOT_SVGITEM_H + +#include + +#include +#include "qwt_double_rect.h" +#include "qwt_plot_item.h" + +#if QT_VERSION >= 0x040100 +class QSvgRenderer; +class QByteArray; +#endif + +/*! + \brief A plot item, which displays + data in Scalable Vector Graphics (SVG) format. + + SVG images are often used to display maps +*/ + +class QWT_EXPORT QwtPlotSvgItem: public QwtPlotItem +{ +public: + explicit QwtPlotSvgItem(const QString& title = QString::null ); + explicit QwtPlotSvgItem(const QwtText& title ); + virtual ~QwtPlotSvgItem(); + + bool loadFile(const QwtDoubleRect&, const QString &fileName); + bool loadData(const QwtDoubleRect&, const QByteArray &); + + virtual QwtDoubleRect boundingRect() const; + + virtual void draw(QPainter *p, + const QwtScaleMap &xMap, const QwtScaleMap &yMap, + const QRect &rect) const; + + virtual int rtti() const; + +protected: +#if QT_VERSION >= 0x040100 + const QSvgRenderer &renderer() const; + QSvgRenderer &renderer(); +#endif + + void render(QPainter *painter, + const QwtDoubleRect &viewBox, const QRect &rect) const; + QwtDoubleRect viewBox(const QwtDoubleRect &area) const; + +private: + void init(); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_plot_xml.cpp b/qwt/src/qwt_plot_xml.cpp new file mode 100644 index 000000000..4b5b544a8 --- /dev/null +++ b/qwt/src/qwt_plot_xml.cpp @@ -0,0 +1,27 @@ +/* -*- 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" + +void QwtPlot::applyProperties(const QString &xmlDocument) +{ +#if 1 + // Temporary dummy code, for designer tests + setTitle(xmlDocument); + replot(); +#endif +} + +QString QwtPlot::grabProperties() const +{ +#if 1 + // Temporary dummy code, for designer tests + return title().text(); +#endif +} diff --git a/qwt/src/qwt_plot_zoomer.cpp b/qwt/src/qwt_plot_zoomer.cpp new file mode 100644 index 000000000..5375d64e0 --- /dev/null +++ b/qwt/src/qwt_plot_zoomer.cpp @@ -0,0 +1,668 @@ +/* -*- 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 "qwt_plot.h" +#include "qwt_plot_canvas.h" +#include "qwt_plot_zoomer.h" +#include "qwt_scale_div.h" +#if QT_VERSION < 0x040000 +typedef QValueStack QwtZoomStack; +#else +typedef QStack QwtZoomStack; +#endif + +class QwtPlotZoomer::PrivateData +{ +public: + uint zoomRectIndex; + QwtZoomStack zoomStack; + + int maxStackDepth; +}; + +/*! + \brief Create a zoomer for a plot canvas. + + The zoomer is set to those x- and y-axis of the parent plot of the + canvas that are enabled. If both or no x-axis are enabled, the picker + is set to QwtPlot::xBottom. If both or no y-axis are + enabled, it is set to QwtPlot::yLeft. + + The selectionFlags() are set to + QwtPicker::RectSelection | QwtPicker::ClickSelection, the + tracker mode to QwtPicker::ActiveOnly. + + \param canvas Plot canvas to observe, also the parent object + \param doReplot Call replot for the attached plot before initializing + the zoomer with its scales. This might be necessary, + when the plot is in a state with pending scale changes. + + \sa QwtPlot::autoReplot(), QwtPlot::replot(), setZoomBase() +*/ +QwtPlotZoomer::QwtPlotZoomer(QwtPlotCanvas *canvas, bool doReplot): + QwtPlotPicker(canvas) +{ + if ( canvas ) + init(RectSelection | ClickSelection, ActiveOnly, doReplot); +} + +/*! + \brief Create a zoomer for a plot canvas. + + The selectionFlags() are set to + QwtPicker::RectSelection | QwtPicker::ClickSelection, the + tracker mode to QwtPicker::ActiveOnly. + + \param xAxis X axis of the zoomer + \param yAxis Y axis of the zoomer + \param canvas Plot canvas to observe, also the parent object + \param doReplot Call replot for the attached plot before initializing + the zoomer with its scales. This might be necessary, + when the plot is in a state with pending scale changes. + + \sa QwtPlot::autoReplot(), QwtPlot::replot(), setZoomBase() +*/ + +QwtPlotZoomer::QwtPlotZoomer(int xAxis, int yAxis, + QwtPlotCanvas *canvas, bool doReplot): + QwtPlotPicker(xAxis, yAxis, canvas) +{ + if ( canvas ) + init(RectSelection | ClickSelection, ActiveOnly, doReplot); +} + +/*! + Create a zoomer for a plot canvas. + + \param xAxis X axis of the zoomer + \param yAxis Y axis of the zoomer + \param selectionFlags Or'd value of QwtPicker::RectSelectionType and + QwtPicker::SelectionMode. + QwtPicker::RectSelection will be auto added. + \param trackerMode Tracker mode + \param canvas Plot canvas to observe, also the parent object + \param doReplot Call replot for the attached plot before initializing + the zoomer with its scales. This might be necessary, + when the plot is in a state with pending scale changes. + + \sa QwtPicker, QwtPicker::setSelectionFlags(), QwtPicker::setRubberBand(), + QwtPicker::setTrackerMode() + + \sa QwtPlot::autoReplot(), QwtPlot::replot(), setZoomBase() +*/ + +QwtPlotZoomer::QwtPlotZoomer(int xAxis, int yAxis, int selectionFlags, + DisplayMode trackerMode, QwtPlotCanvas *canvas, bool doReplot): + QwtPlotPicker(xAxis, yAxis, canvas) +{ + if ( canvas ) + init(selectionFlags, trackerMode, doReplot); +} + +//! Init the zoomer, used by the constructors +void QwtPlotZoomer::init(int selectionFlags, + DisplayMode trackerMode, bool doReplot) +{ + d_data = new PrivateData; + + d_data->maxStackDepth = -1; + + setSelectionFlags(selectionFlags); + setTrackerMode(trackerMode); + setRubberBand(RectRubberBand); + + if ( doReplot && plot() ) + plot()->replot(); + + setZoomBase(scaleRect()); +} + +QwtPlotZoomer::~QwtPlotZoomer() +{ + delete d_data; +} + +/*! + \brief Limit the number of recursive zoom operations to depth. + + A value of -1 set the depth to unlimited, 0 disables zooming. + If the current zoom rectangle is below depth, the plot is unzoomed. + + \param depth Maximum for the stack depth + \sa maxStackDepth() + \note depth doesn't include the zoom base, so zoomStack().count() might be + maxStackDepth() + 1. +*/ +void QwtPlotZoomer::setMaxStackDepth(int depth) +{ + d_data->maxStackDepth = depth; + + if ( depth >= 0 ) + { + // unzoom if the current depth is below d_data->maxStackDepth + + const int zoomOut = + int(d_data->zoomStack.count()) - 1 - depth; // -1 for the zoom base + + if ( zoomOut > 0 ) + { + zoom(-zoomOut); + for ( int i = int(d_data->zoomStack.count()) - 1; + i > int(d_data->zoomRectIndex); i-- ) + { + (void)d_data->zoomStack.pop(); // remove trailing rects + } + } + } +} + +/*! + \return Maximal depth of the zoom stack. + \sa setMaxStackDepth() +*/ +int QwtPlotZoomer::maxStackDepth() const +{ + return d_data->maxStackDepth; +} + +/*! + Return the zoom stack. zoomStack()[0] is the zoom base, + zoomStack()[1] the first zoomed rectangle. + + \sa setZoomStack(), zoomRectIndex() +*/ +const QwtZoomStack &QwtPlotZoomer::zoomStack() const +{ + return d_data->zoomStack; +} + +/*! + \return Initial rectangle of the zoomer + \sa setZoomBase(), zoomRect() +*/ +QwtDoubleRect QwtPlotZoomer::zoomBase() const +{ + return d_data->zoomStack[0]; +} + +/*! + Reinitialized the zoom stack with scaleRect() as base. + + \param doReplot Call replot for the attached plot before initializing + the zoomer with its scales. This might be necessary, + when the plot is in a state with pending scale changes. + + \sa zoomBase(), scaleRect() QwtPlot::autoReplot(), QwtPlot::replot(). +*/ +void QwtPlotZoomer::setZoomBase(bool doReplot) +{ + QwtPlot *plt = plot(); + if ( plt == NULL ) + return; + + if ( doReplot ) + plt->replot(); + + d_data->zoomStack.clear(); + d_data->zoomStack.push(scaleRect()); + d_data->zoomRectIndex = 0; + + rescale(); +} + +/*! + \brief Set the initial size of the zoomer. + + base is united with the current scaleRect() and the zoom stack is + reinitalized with it as zoom base. plot is zoomed to scaleRect(). + + \param base Zoom base + + \sa zoomBase(), scaleRect() +*/ +void QwtPlotZoomer::setZoomBase(const QwtDoubleRect &base) +{ + const QwtPlot *plt = plot(); + if ( !plt ) + return; + + const QwtDoubleRect sRect = scaleRect(); + const QwtDoubleRect bRect = base | sRect; + + d_data->zoomStack.clear(); + d_data->zoomStack.push(bRect); + d_data->zoomRectIndex = 0; + + if ( base != sRect ) + { + d_data->zoomStack.push(sRect); + d_data->zoomRectIndex++; + } + + rescale(); +} + +/*! + Rectangle at the current position on the zoom stack. + + \sa zoomRectIndex(), scaleRect(). +*/ +QwtDoubleRect QwtPlotZoomer::zoomRect() const +{ + return d_data->zoomStack[d_data->zoomRectIndex]; +} + +/*! + \return Index of current position of zoom stack. +*/ +uint QwtPlotZoomer::zoomRectIndex() const +{ + return d_data->zoomRectIndex; +} + +/*! + \brief Zoom in + + Clears all rectangles above the current position of the + zoom stack and pushs the intersection of zoomRect() and + the normalized rect on it. + + \note If the maximal stack depth is reached, zoom is ignored. + \note The zoomed signal is emitted. +*/ + +void QwtPlotZoomer::zoom(const QwtDoubleRect &rect) +{ + if ( d_data->maxStackDepth >= 0 && + int(d_data->zoomRectIndex) >= d_data->maxStackDepth ) + { + return; + } + + const QwtDoubleRect zoomRect = d_data->zoomStack[0] & rect.normalized(); + if ( zoomRect != d_data->zoomStack[d_data->zoomRectIndex] ) + { + for ( uint i = int(d_data->zoomStack.count()) - 1; + i > d_data->zoomRectIndex; i-- ) + { + (void)d_data->zoomStack.pop(); + } + + d_data->zoomStack.push(zoomRect); + d_data->zoomRectIndex++; + + rescale(); + + emit zoomed(zoomRect); + } +} + +/*! + \brief Zoom in or out + + Activate a rectangle on the zoom stack with an offset relative + to the current position. Negative values of offest will zoom out, + positive zoom in. A value of 0 zooms out to the zoom base. + + \param offset Offset relative to the current position of the zoom stack. + \note The zoomed signal is emitted. + \sa zoomRectIndex() +*/ +void QwtPlotZoomer::zoom(int offset) +{ + if ( offset == 0 ) + d_data->zoomRectIndex = 0; + else + { + int newIndex = d_data->zoomRectIndex + offset; + newIndex = qwtMax(0, newIndex); + newIndex = qwtMin(int(d_data->zoomStack.count()) - 1, newIndex); + + d_data->zoomRectIndex = uint(newIndex); + } + + rescale(); + + emit zoomed(zoomRect()); +} + +/*! + \brief Assign a zoom stack + + In combination with other types of navigation it might be useful to + modify to manipulate the complete zoom stack. + + \param zoomStack New zoom stack + \param zoomRectIndex Index of the current position of zoom stack. + In case of -1 the current position is at the top + of the stack. + + \note The zoomed signal might be emitted. + \sa zoomStack(), zoomRectIndex() +*/ +void QwtPlotZoomer::setZoomStack( + const QwtZoomStack &zoomStack, int zoomRectIndex) +{ + if ( zoomStack.isEmpty() ) + return; + + if ( d_data->maxStackDepth >= 0 && + int(zoomStack.count()) > d_data->maxStackDepth ) + { + return; + } + + if ( zoomRectIndex < 0 || zoomRectIndex > int(zoomStack.count()) ) + zoomRectIndex = zoomStack.count() - 1; + + const bool doRescale = zoomStack[zoomRectIndex] != zoomRect(); + + d_data->zoomStack = zoomStack; + d_data->zoomRectIndex = uint(zoomRectIndex); + + if ( doRescale ) + { + rescale(); + emit zoomed(zoomRect()); + } +} + +/*! + Adjust the observed plot to zoomRect() + + \note Initiates QwtPlot::replot +*/ + +void QwtPlotZoomer::rescale() +{ + QwtPlot *plt = plot(); + if ( !plt ) + return; + + const QwtDoubleRect &rect = d_data->zoomStack[d_data->zoomRectIndex]; + if ( rect != scaleRect() ) + { + const bool doReplot = plt->autoReplot(); + plt->setAutoReplot(false); + + double x1 = rect.left(); + double x2 = rect.right(); + if ( plt->axisScaleDiv(xAxis())->lowerBound() > + plt->axisScaleDiv(xAxis())->upperBound() ) + { + qSwap(x1, x2); + } + + plt->setAxisScale(xAxis(), x1, x2); + + double y1 = rect.top(); + double y2 = rect.bottom(); + if ( plt->axisScaleDiv(yAxis())->lowerBound() > + plt->axisScaleDiv(yAxis())->upperBound() ) + { + qSwap(y1, y2); + } + plt->setAxisScale(yAxis(), y1, y2); + + plt->setAutoReplot(doReplot); + + plt->replot(); + } +} + +/*! + Reinitialize the axes, and set the zoom base to their scales. + + \param xAxis X axis + \param yAxis Y axis +*/ + +void QwtPlotZoomer::setAxis(int xAxis, int yAxis) +{ + if ( xAxis != QwtPlotPicker::xAxis() || yAxis != QwtPlotPicker::yAxis() ) + { + QwtPlotPicker::setAxis(xAxis, yAxis); + setZoomBase(scaleRect()); + } +} + +/*! + Qt::MidButton zooms out one position on the zoom stack, + Qt::RightButton to the zoom base. + + Changes the current position on the stack, but doesn't pop + any rectangle. + + \note The mouse events can be changed, using + QwtEventPattern::setMousePattern: 2, 1 +*/ +void QwtPlotZoomer::widgetMouseReleaseEvent(QMouseEvent *me) +{ + if ( mouseMatch(MouseSelect2, me) ) + zoom(0); + else if ( mouseMatch(MouseSelect3, me) ) + zoom(-1); + else if ( mouseMatch(MouseSelect6, me) ) + zoom(+1); + else + QwtPlotPicker::widgetMouseReleaseEvent(me); +} + +/*! + Qt::Key_Plus zooms in, Qt::Key_Minus zooms out one position on the + zoom stack, Qt::Key_Escape zooms out to the zoom base. + + Changes the current position on the stack, but doesn't pop + any rectangle. + + \note The keys codes can be changed, using + QwtEventPattern::setKeyPattern: 3, 4, 5 +*/ + +void QwtPlotZoomer::widgetKeyPressEvent(QKeyEvent *ke) +{ + if ( !isActive() ) + { + if ( keyMatch(KeyUndo, ke) ) + zoom(-1); + else if ( keyMatch(KeyRedo, ke) ) + zoom(+1); + else if ( keyMatch(KeyHome, ke) ) + zoom(0); + } + + QwtPlotPicker::widgetKeyPressEvent(ke); +} + +/*! + Move the current zoom rectangle. + + \param dx X offset + \param dy Y offset + + \note The changed rectangle is limited by the zoom base +*/ +void QwtPlotZoomer::moveBy(double dx, double dy) +{ + const QwtDoubleRect &rect = d_data->zoomStack[d_data->zoomRectIndex]; + move(rect.left() + dx, rect.top() + dy); +} + +/*! + Move the the current zoom rectangle. + + \param x X value + \param y Y value + + \sa QwtDoubleRect::move() + \note The changed rectangle is limited by the zoom base +*/ +void QwtPlotZoomer::move(double x, double y) +{ + if ( x < zoomBase().left() ) + x = zoomBase().left(); + if ( x > zoomBase().right() - zoomRect().width() ) + x = zoomBase().right() - zoomRect().width(); + + if ( y < zoomBase().top() ) + y = zoomBase().top(); + if ( y > zoomBase().bottom() - zoomRect().height() ) + y = zoomBase().bottom() - zoomRect().height(); + + if ( x != zoomRect().left() || y != zoomRect().top() ) + { + d_data->zoomStack[d_data->zoomRectIndex].moveTo(x, y); + rescale(); + } +} + +/*! + \brief Check and correct a selected rectangle + + Reject rectangles with a hight or width < 2, otherwise + expand the selected rectangle to a minimum size of 11x11 + and accept it. + + \return true If rect is accepted, or has been changed + to a accepted rectangle. +*/ + +bool QwtPlotZoomer::accept(QwtPolygon &pa) const +{ + if ( pa.count() < 2 ) + return false; + + QRect rect = QRect(pa[0], pa[int(pa.count()) - 1]); +#if QT_VERSION < 0x040000 + rect = rect.normalize(); +#else + rect = rect.normalized(); +#endif + + const int minSize = 2; + if (rect.width() < minSize && rect.height() < minSize ) + return false; + + const int minZoomSize = 11; + + const QPoint center = rect.center(); + rect.setSize(rect.size().expandedTo(QSize(minZoomSize, minZoomSize))); + rect.moveCenter(center); + + pa.resize(2); + pa[0] = rect.topLeft(); + pa[1] = rect.bottomRight(); + + return true; +} + +/*! + \brief Limit zooming by a minimum rectangle + + \return zoomBase().width() / 10e4, zoomBase().height() / 10e4 +*/ +QwtDoubleSize QwtPlotZoomer::minZoomSize() const +{ + return QwtDoubleSize( + d_data->zoomStack[0].width() / 10e4, + d_data->zoomStack[0].height() / 10e4 + ); +} + +/*! + Rejects selections, when the stack depth is too deep, or + the zoomed rectangle is minZoomSize(). + + \sa minZoomSize(), maxStackDepth() +*/ +void QwtPlotZoomer::begin() +{ + if ( d_data->maxStackDepth >= 0 ) + { + if ( d_data->zoomRectIndex >= uint(d_data->maxStackDepth) ) + return; + } + + const QwtDoubleSize minSize = minZoomSize(); + if ( minSize.isValid() ) + { + const QwtDoubleSize sz = + d_data->zoomStack[d_data->zoomRectIndex].size() * 0.9999; + + if ( minSize.width() >= sz.width() && + minSize.height() >= sz.height() ) + { + return; + } + } + + QwtPlotPicker::begin(); +} + +/*! + Expand the selected rectangle to minZoomSize() and zoom in + if accepted. + + \sa accept(), minZoomSize() +*/ +bool QwtPlotZoomer::end(bool ok) +{ + ok = QwtPlotPicker::end(ok); + if (!ok) + return false; + + QwtPlot *plot = QwtPlotZoomer::plot(); + if ( !plot ) + return false; + + const QwtPolygon &pa = selection(); + if ( pa.count() < 2 ) + return false; + + QRect rect = QRect(pa[0], pa[int(pa.count() - 1)]); +#if QT_VERSION < 0x040000 + rect = rect.normalize(); +#else + rect = rect.normalized(); +#endif + + + QwtDoubleRect zoomRect = invTransform(rect).normalized(); + + const QwtDoublePoint center = zoomRect.center(); + zoomRect.setSize(zoomRect.size().expandedTo(minZoomSize())); + zoomRect.moveCenter(center); + + zoom(zoomRect); + + return true; +} + +/*! + Set the selection flags + + \param flags Or'd value of QwtPicker::RectSelectionType and + QwtPicker::SelectionMode. The default value is + QwtPicker::RectSelection & QwtPicker::ClickSelection. + + \sa selectionFlags(), SelectionType, RectSelectionType, SelectionMode + \note QwtPicker::RectSelection will be auto added. +*/ + +void QwtPlotZoomer::setSelectionFlags(int flags) +{ + // we accept only rects + flags &= ~(PointSelection | PolygonSelection); + flags |= RectSelection; + + QwtPlotPicker::setSelectionFlags(flags); +} diff --git a/qwt/src/qwt_plot_zoomer.h b/qwt/src/qwt_plot_zoomer.h new file mode 100644 index 000000000..fffc841b1 --- /dev/null +++ b/qwt/src/qwt_plot_zoomer.h @@ -0,0 +1,122 @@ +/* -*- 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 + +#ifndef QWT_PLOT_ZOOMER_H +#define QWT_PLOT_ZOOMER_H + +#include +#if QT_VERSION < 0x040000 +#include +#else +#include +#endif + +#include "qwt_double_rect.h" +#include "qwt_plot_picker.h" + +/*! + \brief QwtPlotZoomer provides stacked zooming for a plot widget + + QwtPlotZoomer offers rubberband selections on the plot canvas, + translating the selected rectangles into plot coordinates and + adjusting the axes to them. Zooming can repeated as often as + possible, limited only by maxStackDepth() or minZoomSize(). + Each rectangle is pushed on a stack. + + Zoom rectangles can be selected depending on selectionFlags() using the + mouse or keyboard (QwtEventPattern, QwtPickerMachine). + QwtEventPattern::MouseSelect3/QwtEventPattern::KeyUndo, + or QwtEventPattern::MouseSelect6/QwtEventPattern::KeyRedo + walk up and down the zoom stack. + QwtEventPattern::MouseSelect2 or QwtEventPattern::KeyHome unzoom to + the initial size. + + QwtPlotZoomer is tailored for plots with one x and y axis, but it is + allowed to attach a second QwtPlotZoomer for the other axes. + + \note The realtime example includes an derived zoomer class that adds + scrollbars to the plot canvas. +*/ + +class QWT_EXPORT QwtPlotZoomer: public QwtPlotPicker +{ + Q_OBJECT +public: + explicit QwtPlotZoomer(QwtPlotCanvas *, bool doReplot = true); + explicit QwtPlotZoomer(int xAxis, int yAxis, + QwtPlotCanvas *, bool doReplot = true); + explicit QwtPlotZoomer(int xAxis, int yAxis, int selectionFlags, + DisplayMode trackerMode, QwtPlotCanvas *, + bool doReplot = true); + + virtual ~QwtPlotZoomer(); + + virtual void setZoomBase(bool doReplot = true); + virtual void setZoomBase(const QwtDoubleRect &); + + QwtDoubleRect zoomBase() const; + QwtDoubleRect zoomRect() const; + + virtual void setAxis(int xAxis, int yAxis); + + void setMaxStackDepth(int); + int maxStackDepth() const; + +#if QT_VERSION < 0x040000 + const QValueStack &zoomStack() const; + void setZoomStack(const QValueStack &, + int zoomRectIndex = -1); +#else + const QStack &zoomStack() const; + void setZoomStack(const QStack &, + int zoomRectIndex = -1); +#endif + uint zoomRectIndex() const; + + virtual void setSelectionFlags(int); + +public slots: + void moveBy(double x, double y); + virtual void move(double x, double y); + + virtual void zoom(const QwtDoubleRect &); + virtual void zoom(int up); + +signals: + /*! + A signal emitting the zoomRect(), when the plot has been + zoomed in or out. + + \param rect Current zoom rectangle. + */ + + void zoomed(const QwtDoubleRect &rect); + +protected: + virtual void rescale(); + + virtual QwtDoubleSize minZoomSize() const; + + virtual void widgetMouseReleaseEvent(QMouseEvent *); + virtual void widgetKeyPressEvent(QKeyEvent *); + + virtual void begin(); + virtual bool end(bool ok = true); + virtual bool accept(QwtPolygon &) const; + +private: + void init(int selectionFlags, DisplayMode trackerMode, bool doReplot); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_polygon.h b/qwt/src/qwt_polygon.h new file mode 100644 index 000000000..eda9329dd --- /dev/null +++ b/qwt/src/qwt_polygon.h @@ -0,0 +1,35 @@ +/* -*- 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 + +#ifndef QWT_POLYGON_H +#define QWT_POLYGON_H + +#include "qwt_global.h" + +/*! + \def QwtPolygon + */ + +#if QT_VERSION < 0x040000 +#include +#include "qwt_double_rect.h" + +typedef QPointArray QwtPolygon; +typedef QMemArray QwtPolygonF; + +#else + +#include +typedef QPolygon QwtPolygon; +typedef QPolygonF QwtPolygonF; +#endif + +#endif diff --git a/qwt/src/qwt_raster_data.cpp b/qwt/src/qwt_raster_data.cpp new file mode 100644 index 000000000..ff31e3f29 --- /dev/null +++ b/qwt/src/qwt_raster_data.cpp @@ -0,0 +1,435 @@ +/* -*- 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_raster_data.h" + +class QwtRasterData::Contour3DPoint +{ +public: + inline void setPos(double x, double y) + { + d_x = x; + d_y = y; + } + + inline QwtDoublePoint pos() const + { + return QwtDoublePoint(d_x, d_y); + } + + inline void setX(double x) { d_x = x; } + inline void setY(double y) { d_y = y; } + inline void setZ(double z) { d_z = z; } + + inline double x() const { return d_x; } + inline double y() const { return d_y; } + inline double z() const { return d_z; } + +private: + double d_x; + double d_y; + double d_z; +}; + +class QwtRasterData::ContourPlane +{ +public: + inline ContourPlane(double z): + d_z(z) + { + } + + inline bool intersect(const Contour3DPoint vertex[3], + QwtDoublePoint line[2], bool ignoreOnPlane) const; + + inline double z() const { return d_z; } + +private: + inline int compare(double z) const; + inline QwtDoublePoint intersection( + const Contour3DPoint& p1, const Contour3DPoint &p2) const; + + double d_z; +}; + +inline bool QwtRasterData::ContourPlane::intersect( + const Contour3DPoint vertex[3], QwtDoublePoint line[2], + bool ignoreOnPlane) const +{ + bool found = true; + + // Are the vertices below (-1), on (0) or above (1) the plan ? + const int eq1 = compare(vertex[0].z()); + const int eq2 = compare(vertex[1].z()); + const int eq3 = compare(vertex[2].z()); + + /* + (a) All the vertices lie below the contour level. + (b) Two vertices lie below and one on the contour level. + (c) Two vertices lie below and one above the contour level. + (d) One vertex lies below and two on the contour level. + (e) One vertex lies below, one on and one above the contour level. + (f) One vertex lies below and two above the contour level. + (g) Three vertices lie on the contour level. + (h) Two vertices lie on and one above the contour level. + (i) One vertex lies on and two above the contour level. + (j) All the vertices lie above the contour level. + */ + + static const int tab[3][3][3] = + { + // jump table to avoid nested case statements + { { 0, 0, 8 }, { 0, 2, 5 }, { 7, 6, 9 } }, + { { 0, 3, 4 }, { 1, 10, 1 }, { 4, 3, 0 } }, + { { 9, 6, 7 }, { 5, 2, 0 }, { 8, 0, 0 } } + }; + + const int edgeType = tab[eq1+1][eq2+1][eq3+1]; + switch (edgeType) + { + case 1: + // d(0,0,-1), h(0,0,1) + line[0] = vertex[0].pos(); + line[1] = vertex[1].pos(); + break; + case 2: + // d(-1,0,0), h(1,0,0) + line[0] = vertex[1].pos(); + line[1] = vertex[2].pos(); + break; + case 3: + // d(0,-1,0), h(0,1,0) + line[0] = vertex[2].pos(); + line[1] = vertex[0].pos(); + break; + case 4: + // e(0,-1,1), e(0,1,-1) + line[0] = vertex[0].pos(); + line[1] = intersection(vertex[1], vertex[2]); + break; + case 5: + // e(-1,0,1), e(1,0,-1) + line[0] = vertex[1].pos(); + line[1] = intersection(vertex[2], vertex[0]); + break; + case 6: + // e(-1,1,0), e(1,0,-1) + line[0] = vertex[1].pos(); + line[1] = intersection(vertex[0], vertex[1]); + break; + case 7: + // c(-1,1,-1), f(1,1,-1) + line[0] = intersection(vertex[0], vertex[1]); + line[1] = intersection(vertex[1], vertex[2]); + break; + case 8: + // c(-1,-1,1), f(1,1,-1) + line[0] = intersection(vertex[1], vertex[2]); + line[1] = intersection(vertex[2], vertex[0]); + break; + case 9: + // f(-1,1,1), c(1,-1,-1) + line[0] = intersection(vertex[2], vertex[0]); + line[1] = intersection(vertex[0], vertex[1]); + break; + case 10: + // g(0,0,0) + // The CONREC algorithm has no satisfying solution for + // what to do, when all vertices are on the plane. + + if ( ignoreOnPlane ) + found = false; + else + { + line[0] = vertex[2].pos(); + line[1] = vertex[0].pos(); + } + break; + default: + found = false; + } + + return found; +} + +inline int QwtRasterData::ContourPlane::compare(double z) const +{ + if (z > d_z) + return 1; + + if (z < d_z) + return -1; + + return 0; +} + +inline QwtDoublePoint QwtRasterData::ContourPlane::intersection( + const Contour3DPoint& p1, const Contour3DPoint &p2) const +{ + const double h1 = p1.z() - d_z; + const double h2 = p2.z() - d_z; + + const double x = (h2 * p1.x() - h1 * p2.x()) / (h2 - h1); + const double y = (h2 * p1.y() - h1 * p2.y()) / (h2 - h1); + + return QwtDoublePoint(x, y); +} + +//! Constructor +QwtRasterData::QwtRasterData() +{ +} + +/*! + Constructor + + \param boundingRect Bounding rectangle + \sa setBoundingRect() +*/ +QwtRasterData::QwtRasterData(const QwtDoubleRect &boundingRect): + d_boundingRect(boundingRect) +{ +} + +//! Destructor +QwtRasterData::~QwtRasterData() +{ +} + +/*! + Set the bounding rect ( == area, un plot coordinates ) + + \param boundingRect Bounding rectangle + \sa boundingRect() +*/ +void QwtRasterData::setBoundingRect(const QwtDoubleRect &boundingRect) +{ + d_boundingRect = boundingRect; +} + +/*! + \return Bounding rectangle + \sa boundingRect() +*/ +QwtDoubleRect QwtRasterData::boundingRect() const +{ + return d_boundingRect; +} + +/*! + \brief Initialize a raster + + Before the composition of an image QwtPlotSpectrogram calls initRaster, + announcing the area and its resolution that will be requested. + + The default implementation does nothing, but for data sets that + are stored in files, it might be good idea to reimplement initRaster, + where the data is resampled and loaded into memory. + + \param rect Area of the raster + \param raster Number of horizontal and vertical pixels + + \sa initRaster(), value() +*/ +void QwtRasterData::initRaster(const QwtDoubleRect &, const QSize&) +{ +} + +/*! + \brief Discard a raster + + After the composition of an image QwtPlotSpectrogram calls discardRaster(). + + The default implementation does nothing, but if data has been loaded + in initRaster(), it could deleted now. + + \sa initRaster(), value() +*/ +void QwtRasterData::discardRaster() +{ +} + +/*! + \brief Find the raster of the data for an area + + The resolution is the number of horizontal and vertical pixels + that the data can return for an area. An invalid resolution + indicates that the data can return values for any detail level. + + The resolution will limit the size of the image that is rendered + from the data. F.e. this might be important when printing a spectrogram + to a A0 printer with 600 dpi. + + The default implementation returns an invalid resolution (size) + + \param rect In most implementations the resolution of the data doesn't + depend on the requested rectangle. + + \return Resolution, as number of horizontal and vertical pixels +*/ +QSize QwtRasterData::rasterHint(const QwtDoubleRect &) const +{ + return QSize(); // use screen resolution +} + +/*! + Calculate contour lines + + An adaption of CONREC, a simple contouring algorithm. + http://local.wasp.uwa.edu.au/~pbourke/papers/conrec/ +*/ +#if QT_VERSION >= 0x040000 +QwtRasterData::ContourLines QwtRasterData::contourLines( + const QwtDoubleRect &rect, const QSize &raster, + const QList &levels, int flags) const +#else +QwtRasterData::ContourLines QwtRasterData::contourLines( + const QwtDoubleRect &rect, const QSize &raster, + const QValueList &levels, int flags) const +#endif +{ + ContourLines contourLines; + + if ( levels.size() == 0 || !rect.isValid() || !raster.isValid() ) + return contourLines; + + const double dx = rect.width() / raster.width(); + const double dy = rect.height() / raster.height(); + + const bool ignoreOnPlane = + flags & QwtRasterData::IgnoreAllVerticesOnLevel; + + const QwtDoubleInterval range = this->range(); + bool ignoreOutOfRange = false; + if ( range.isValid() ) + ignoreOutOfRange = flags & IgnoreOutOfRange; + + ((QwtRasterData*)this)->initRaster(rect, raster); + + for ( int y = 0; y < raster.height() - 1; y++ ) + { + enum Position + { + Center, + + TopLeft, + TopRight, + BottomRight, + BottomLeft, + + NumPositions + }; + + Contour3DPoint xy[NumPositions]; + + for ( int x = 0; x < raster.width() - 1; x++ ) + { + const QwtDoublePoint pos(rect.x() + x * dx, rect.y() + y * dy); + + if ( x == 0 ) + { + xy[TopRight].setPos(pos.x(), pos.y()); + xy[TopRight].setZ( + value( xy[TopRight].x(), xy[TopRight].y()) + ); + + xy[BottomRight].setPos(pos.x(), pos.y() + dy); + xy[BottomRight].setZ( + value(xy[BottomRight].x(), xy[BottomRight].y()) + ); + } + + xy[TopLeft] = xy[TopRight]; + xy[BottomLeft] = xy[BottomRight]; + + xy[TopRight].setPos(pos.x() + dx, pos.y()); + xy[BottomRight].setPos(pos.x() + dx, pos.y() + dy); + + xy[TopRight].setZ( + value(xy[TopRight].x(), xy[TopRight].y()) + ); + xy[BottomRight].setZ( + value(xy[BottomRight].x(), xy[BottomRight].y()) + ); + + double zMin = xy[TopLeft].z(); + double zMax = zMin; + double zSum = zMin; + + for ( int i = TopRight; i <= BottomLeft; i++ ) + { + const double z = xy[i].z(); + + zSum += z; + if ( z < zMin ) + zMin = z; + if ( z > zMax ) + zMax = z; + } + + if ( ignoreOutOfRange ) + { + if ( !range.contains(zMin) || !range.contains(zMax) ) + continue; + } + + if ( zMax < levels[0] || + zMin > levels[levels.size() - 1] ) + { + continue; + } + + xy[Center].setPos(pos.x() + 0.5 * dx, pos.y() + 0.5 * dy); + xy[Center].setZ(0.25 * zSum); + const int numLevels = (int)levels.size(); + for (int l = 0; l < numLevels; l++) + { + const double level = levels[l]; + if ( level < zMin || level > zMax ) + continue; +#if QT_VERSION >= 0x040000 + QPolygonF &lines = contourLines[level]; +#else + QwtArray &lines = contourLines[level]; +#endif + const ContourPlane plane(level); + + QwtDoublePoint line[2]; + Contour3DPoint vertex[3]; + + for (int m = TopLeft; m < NumPositions; m++) + { + vertex[0] = xy[m]; + vertex[1] = xy[0]; + vertex[2] = xy[m != BottomLeft ? m + 1 : TopLeft]; + + const bool intersects = + plane.intersect(vertex, line, ignoreOnPlane); + if ( intersects ) + { +#if QT_VERSION >= 0x040000 + lines += line[0]; + lines += line[1]; +#else + const int index = lines.size(); + lines.resize(lines.size() + 2, QGArray::SpeedOptim); + + lines[index] = line[0]; + lines[index+1] = line[1]; +#endif + } + } + } + } + } + + ((QwtRasterData*)this)->discardRaster(); + + return contourLines; +} diff --git a/qwt/src/qwt_raster_data.h b/qwt/src/qwt_raster_data.h new file mode 100644 index 000000000..6147a2225 --- /dev/null +++ b/qwt/src/qwt_raster_data.h @@ -0,0 +1,119 @@ +/* -*- 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 + +#ifndef QWT_RASTER_DATA_H +#define QWT_RASTER_DATA_H 1 + +#include +#include "qwt_global.h" +#include "qwt_double_rect.h" +#include "qwt_double_interval.h" + +#if QT_VERSION >= 0x040000 +#include +#include + +#if defined(QWT_TEMPLATEDLL) +// MOC_SKIP_BEGIN +template class QWT_EXPORT QMap; +// MOC_SKIP_END +#endif + +#else +#include +#include "qwt_array.h" +#include "qwt_double_rect.h" +#if defined(QWT_TEMPLATEDLL) +// MOC_SKIP_BEGIN +#ifndef QWTARRAY_TEMPLATE_QWTDOUBLEPOINT // by mjo3 +#define QWTARRAY_TEMPLATE_QWTDOUBLEPOINT +template class QWT_EXPORT QwtArray; +#endif //end of QWTARRAY_TEMPLATE_QWTDOUBLEPOINT +#ifndef QMAP_TEMPLATE_DOUBLE_QWTDOUBLEPOINT // by mjo3 +#define QMAP_TEMPLATE_DOUBLE_QWTDOUBLEPOINT +template class QWT_EXPORT QMap >; +#endif //end of QMAP_TEMPLATE_QWTDOUBLEPOINT +// MOC_SKIP_END +#endif +#endif + +class QwtScaleMap; + +/*! + \brief QwtRasterData defines an interface to any type of raster data. + + QwtRasterData is an abstract interface, that is used by + QwtPlotRasterItem to find the values at the pixels of its raster. + + Often a raster item is used to display values from a matrix. Then the + derived raster data class needs to implement some sort of resampling, + that maps the raster of the matrix into the requested raster of + the raster item ( depending on resolution and scales of the canvas ). +*/ +class QWT_EXPORT QwtRasterData +{ +public: +#if QT_VERSION >= 0x040000 + typedef QMap ContourLines; +#else + typedef QMap > ContourLines; +#endif + + //! Attribute to modify the contour algorithm + enum ConrecAttribute + { + IgnoreAllVerticesOnLevel = 1, + IgnoreOutOfRange = 2 + }; + + QwtRasterData(); + QwtRasterData(const QwtDoubleRect &); + virtual ~QwtRasterData(); + + //! Clone the data + virtual QwtRasterData *copy() const = 0; + + virtual void setBoundingRect(const QwtDoubleRect &); + QwtDoubleRect boundingRect() const; + + virtual QSize rasterHint(const QwtDoubleRect &) const; + + virtual void initRaster(const QwtDoubleRect &, const QSize& raster); + virtual void discardRaster(); + + /*! + \return the value at a raster position + \param x X value in plot coordinates + \param y Y value in plot coordinates + */ + virtual double value(double x, double y) const = 0; + + //! \return the range of the values + virtual QwtDoubleInterval range() const = 0; + +#if QT_VERSION >= 0x040000 + virtual ContourLines contourLines(const QwtDoubleRect &rect, + const QSize &raster, const QList &levels, + int flags) const; +#else + virtual ContourLines contourLines(const QwtDoubleRect &rect, + const QSize &raster, const QValueList &levels, + int flags) const; +#endif + + class Contour3DPoint; + class ContourPlane; + +private: + QwtDoubleRect d_boundingRect; +}; + +#endif diff --git a/qwt/src/qwt_round_scale_draw.cpp b/qwt/src/qwt_round_scale_draw.cpp new file mode 100644 index 000000000..b3c1593dc --- /dev/null +++ b/qwt/src/qwt_round_scale_draw.cpp @@ -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 + *****************************************************************************/ + +// vim: expandtab + +#include +#include +#include +#include +#include "qwt_painter.h" +#include "qwt_scale_div.h" +#include "qwt_scale_map.h" +#include "qwt_round_scale_draw.h" + +class QwtRoundScaleDraw::PrivateData +{ +public: + PrivateData(): + center(50, 50), + radius(50), + startAngle(-135 * 16), + endAngle(135 * 16) + { + } + + QPoint center; + int radius; + + int startAngle; + int endAngle; +}; + +/*! + \brief Constructor + + The range of the scale is initialized to [0, 100], + The center is set to (50, 50) with a radius of 50. + The angle range is set to [-135, 135]. +*/ +QwtRoundScaleDraw::QwtRoundScaleDraw() +{ + d_data = new QwtRoundScaleDraw::PrivateData; + + setRadius(50); + scaleMap().setPaintInterval(d_data->startAngle, d_data->endAngle); +} + +//! Copy constructor +QwtRoundScaleDraw::QwtRoundScaleDraw(const QwtRoundScaleDraw &other): + QwtAbstractScaleDraw(other) +{ + d_data = new QwtRoundScaleDraw::PrivateData(*other.d_data); +} + + +//! Destructor +QwtRoundScaleDraw::~QwtRoundScaleDraw() +{ + delete d_data; +} + +//! Assignment operator +QwtRoundScaleDraw &QwtRoundScaleDraw::operator=(const QwtRoundScaleDraw &other) +{ + *(QwtAbstractScaleDraw*)this = (const QwtAbstractScaleDraw &)other; + *d_data = *other.d_data; + return *this; +} + +/*! + Change of radius the scale + + Radius is the radius of the backbone without ticks and labels. + + \param radius New Radius + \sa moveCenter() +*/ +void QwtRoundScaleDraw::setRadius(int radius) +{ + d_data->radius = radius; +} + +/*! + Get the radius + + Radius is the radius of the backbone without ticks and labels. + + \sa setRadius(), extent() +*/ +int QwtRoundScaleDraw::radius() const +{ + return d_data->radius; +} + +/*! + Move the center of the scale draw, leaving the radius unchanged + + \param center New center + \sa setRadius() +*/ +void QwtRoundScaleDraw::moveCenter(const QPoint ¢er) +{ + d_data->center = center; +} + +//! Get the center of the scale +QPoint QwtRoundScaleDraw::center() const +{ + return d_data->center; +} + +/*! + \brief Adjust the baseline circle segment for round scales. + + The baseline will be drawn from min(angle1,angle2) to max(angle1, angle2). + The default setting is [ -135, 135 ]. + An angle of 0 degrees corresponds to the 12 o'clock position, + and positive angles count in a clockwise direction. + \param angle1 + \param angle2 boundaries of the angle interval in degrees. + \warning
    +
  • The angle range is limited to [-360, 360] degrees. Angles exceeding + this range will be clipped. +
  • For angles more than 359 degrees above or below min(angle1, angle2), + scale marks will not be drawn. +
  • If you need a counterclockwise scale, use QwtScaleDiv::setRange +
+*/ +void QwtRoundScaleDraw::setAngleRange(double angle1, double angle2) +{ + angle1 = qwtLim(angle1, -360.0, 360.0); + angle2 = qwtLim(angle2, -360.0, 360.0); + + d_data->startAngle = qRound(angle1 * 16.0); + d_data->endAngle = qRound(angle2 * 16.0); + + if (d_data->startAngle == d_data->endAngle) + { + d_data->startAngle -= 1; + d_data->endAngle += 1; + } + + scaleMap().setPaintInterval(d_data->startAngle, d_data->endAngle); +} + +/*! + Draws the label for a major scale tick + + \param painter Painter + \param value Value + + \sa drawTick(), drawBackbone() +*/ +void QwtRoundScaleDraw::drawLabel(QPainter *painter, double value) const +{ + const QwtText label = tickLabel(painter->font(), value); + if ( label.isEmpty() ) + return; + + const int tval = map().transform(value); + if ((tval > d_data->startAngle + 359 * 16) + || (tval < d_data->startAngle - 359 * 16)) + { + return; + } + + double radius = d_data->radius; + if ( hasComponent(QwtAbstractScaleDraw::Ticks) || + hasComponent(QwtAbstractScaleDraw::Backbone) ) + { + radius += spacing(); + } + + if ( hasComponent(QwtAbstractScaleDraw::Ticks) ) + radius += majTickLength(); + + const QSize sz = label.textSize(painter->font()); + const double arc = tval / 16.0 / 360.0 * 2 * M_PI; + + const int x = d_data->center.x() + + qRound((radius + sz.width() / 2.0) * sin(arc)); + const int y = d_data->center.y() - + qRound( (radius + sz.height() / 2.0) * cos(arc)); + + const QRect r(x - sz.width() / 2, y - sz.height() / 2, + sz.width(), sz.height() ); + label.draw(painter, r); +} + +/*! + Draw a tick + + \param painter Painter + \param value Value of the tick + \param len Lenght of the tick + + \sa drawBackbone(), drawLabel() +*/ +void QwtRoundScaleDraw::drawTick(QPainter *painter, double value, int len) const +{ + if ( len <= 0 ) + return; + + const int tval = map().transform(value); + + const int cx = d_data->center.x(); + const int cy = d_data->center.y(); + const int radius = d_data->radius; + + if ((tval <= d_data->startAngle + 359 * 16) + || (tval >= d_data->startAngle - 359 * 16)) + { + const double arc = double(tval) / 16.0 * M_PI / 180.0; + + const double sinArc = sin(arc); + const double cosArc = cos(arc); + + const int x1 = qRound( cx + radius * sinArc ); + const int x2 = qRound( cx + (radius + len) * sinArc ); + const int y1 = qRound( cy - radius * cosArc ); + const int y2 = qRound( cy - (radius + len) * cosArc ); + + QwtPainter::drawLine(painter, x1, y1, x2, y2); + } +} + +/*! + Draws the baseline of the scale + \param painter Painter + + \sa drawTick(), drawLabel() +*/ +void QwtRoundScaleDraw::drawBackbone(QPainter *painter) const +{ + const int a1 = qRound(qwtMin(map().p1(), map().p2()) - 90 * 16); + const int a2 = qRound(qwtMax(map().p1(), map().p2()) - 90 * 16); + + const int radius = d_data->radius; + const int x = d_data->center.x() - radius; + const int y = d_data->center.y() - radius; + + painter->drawArc(x, y, 2 * radius, 2 * radius, + -a2, a2 - a1 + 1); // counterclockwise +} + +/*! + Calculate the extent of the scale + + The extent is the distcance between the baseline to the outermost + pixel of the scale draw. radius() + extent() is an upper limit + for the radius of the bounding circle. + + \param pen Pen that is used for painting backbone and ticks + \param font Font used for painting the labels + + \sa setMinimumExtent(), minimumExtent() + \warning The implemented algo is not too smart and + calculates only an upper limit, that might be a + few pixels too large +*/ +int QwtRoundScaleDraw::extent(const QPen &pen, const QFont &font) const +{ + int d = 0; + + if ( hasComponent(QwtAbstractScaleDraw::Labels) ) + { + const QwtScaleDiv &sd = scaleDiv(); + const QwtValueList &ticks = sd.ticks(QwtScaleDiv::MajorTick); + for (uint i = 0; i < (uint)ticks.count(); i++) + { + const double value = ticks[i]; + if ( !sd.contains(value) ) + continue; + + const QwtText label = tickLabel(font, value); + if ( label.isEmpty() ) + continue; + + const int tval = map().transform(value); + if ((tval < d_data->startAngle + 360 * 16) + && (tval > d_data->startAngle - 360 * 16)) + { + const double arc = tval / 16.0 / 360.0 * 2 * M_PI; + + const QSize sz = label.textSize(font); + const double off = qwtMax(sz.width(), sz.height()); + + double x = off * sin(arc); + double y = off * cos(arc); + + const int dist = (int)ceil(sqrt(x * x + y * y) + 1 ); + if ( dist > d ) + d = dist; + } + } + } + + if ( hasComponent(QwtAbstractScaleDraw::Ticks) ) + { + d += majTickLength(); + } + + if ( hasComponent(QwtAbstractScaleDraw::Backbone) ) + { + const int pw = qwtMax( 1, pen.width() ); // penwidth can be zero + d += pw; + } + + if ( hasComponent(QwtAbstractScaleDraw::Labels) && + ( hasComponent(QwtAbstractScaleDraw::Ticks) || + hasComponent(QwtAbstractScaleDraw::Backbone) ) ) + { + d += spacing(); + } + + d = qwtMax(d, minimumExtent()); + + return d; +} diff --git a/qwt/src/qwt_round_scale_draw.h b/qwt/src/qwt_round_scale_draw.h new file mode 100644 index 000000000..bf4d7311b --- /dev/null +++ b/qwt/src/qwt_round_scale_draw.h @@ -0,0 +1,69 @@ +/* -*- 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_ROUND_SCALE_DRAW_H +#define QWT_ROUND_SCALE_DRAW_H + +#include +#include "qwt_global.h" +#include "qwt_abstract_scale_draw.h" + +class QPen; + +/*! + \brief A class for drawing round scales + + QwtRoundScaleDraw can be used to draw round scales. + The circle segment can be adjusted by QwtRoundScaleDraw::setAngleRange(). + The geometry of the scale can be specified with + QwtRoundScaleDraw::moveCenter() and QwtRoundScaleDraw::setRadius(). + + After a scale division has been specified as a QwtScaleDiv object + using QwtAbstractScaleDraw::setScaleDiv(const QwtScaleDiv &s), + the scale can be drawn with the QwtAbstractScaleDraw::draw() member. +*/ + +class QWT_EXPORT QwtRoundScaleDraw: public QwtAbstractScaleDraw +{ +public: + QwtRoundScaleDraw(); + QwtRoundScaleDraw(const QwtRoundScaleDraw &); + + virtual ~QwtRoundScaleDraw(); + + QwtRoundScaleDraw &operator=(const QwtRoundScaleDraw &other); + + void setRadius(int radius); + int radius() const; + + void moveCenter(int x, int y); + void moveCenter(const QPoint &); + QPoint center() const; + + void setAngleRange(double angle1, double angle2); + + virtual int extent(const QPen &, const QFont &) const; + +protected: + virtual void drawTick(QPainter *p, double val, int len) const; + virtual void drawBackbone(QPainter *p) const; + virtual void drawLabel(QPainter *p, double val) const; + +private: + class PrivateData; + PrivateData *d_data; +}; + +//! Move the center of the scale draw, leaving the radius unchanged +inline void QwtRoundScaleDraw::moveCenter(int x, int y) +{ + moveCenter(QPoint(x, y)); +} + +#endif diff --git a/qwt/src/qwt_scale_div.cpp b/qwt/src/qwt_scale_div.cpp new file mode 100644 index 000000000..78f5819f7 --- /dev/null +++ b/qwt/src/qwt_scale_div.cpp @@ -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 + *****************************************************************************/ + +#include "qwt_scale_div.h" +#include "qwt_math.h" +#include "qwt_double_interval.h" + +//! Construct an invalid QwtScaleDiv instance. +QwtScaleDiv::QwtScaleDiv(): + d_lowerBound(0.0), + d_upperBound(0.0), + d_isValid(false) +{ +} + +/*! + Construct QwtScaleDiv instance. + + \param interval Interval + \param ticks List of major, medium and minor ticks +*/ +QwtScaleDiv::QwtScaleDiv( + const QwtDoubleInterval &interval, + QwtValueList ticks[NTickTypes]): + d_lowerBound(interval.minValue()), + d_upperBound(interval.maxValue()), + d_isValid(true) +{ + for ( int i = 0; i < NTickTypes; i++ ) + d_ticks[i] = ticks[i]; +} + +/*! + Construct QwtScaleDiv instance. + + \param lowerBound First interval limit + \param upperBound Second interval limit + \param ticks List of major, medium and minor ticks +*/ +QwtScaleDiv::QwtScaleDiv( + double lowerBound, double upperBound, + QwtValueList ticks[NTickTypes]): + d_lowerBound(lowerBound), + d_upperBound(upperBound), + d_isValid(true) +{ + for ( int i = 0; i < NTickTypes; i++ ) + d_ticks[i] = ticks[i]; +} + +/*! + Change the interval + \param interval Interval +*/ +void QwtScaleDiv::setInterval(const QwtDoubleInterval &interval) +{ + setInterval(interval.minValue(), interval.maxValue()); +} + +/*! + \brief Equality operator + \return true if this instance is equal to other +*/ +int QwtScaleDiv::operator==(const QwtScaleDiv &other) const +{ + if ( d_lowerBound != other.d_lowerBound || + d_upperBound != other.d_upperBound || + d_isValid != other.d_isValid ) + { + return false; + } + + for ( int i = 0; i < NTickTypes; i++ ) + { + if ( d_ticks[i] != other.d_ticks[i] ) + return false; + } + + return true; +} + +/*! + \brief Inequality + \return true if this instance is not equal to s +*/ +int QwtScaleDiv::operator!=(const QwtScaleDiv &s) const +{ + return (!(*this == s)); +} + +//! Invalidate the scale division +void QwtScaleDiv::invalidate() +{ + d_isValid = false; + + // detach arrays + for ( int i = 0; i < NTickTypes; i++ ) + d_ticks[i].clear(); + + d_lowerBound = d_upperBound = 0; +} + +//! Check if the scale division is valid +bool QwtScaleDiv::isValid() const +{ + return d_isValid; +} + +/*! + Return if a value is between lowerBound() and upperBound() + + \param value Value + \return true/false +*/ +bool QwtScaleDiv::contains(double value) const +{ + if ( !d_isValid ) + return false; + + const double min = qwtMin(d_lowerBound, d_upperBound); + const double max = qwtMax(d_lowerBound, d_upperBound); + + return value >= min && value <= max; +} + +//! Invert the scale divison +void QwtScaleDiv::invert() +{ + qSwap(d_lowerBound, d_upperBound); + + for ( int i = 0; i < NTickTypes; i++ ) + { + QwtValueList& ticks = d_ticks[i]; + + const int size = ticks.count(); + const int size2 = size / 2; + + for (int i=0; i < size2; i++) + qSwap(ticks[i], ticks[size - 1 - i]); + } +} + +/*! + Assign ticks + + \param type MinorTick, MediumTick or MajorTick + \param ticks Values of the tick positions +*/ +void QwtScaleDiv::setTicks(int type, const QwtValueList &ticks) +{ + if ( type >= 0 || type < NTickTypes ) + d_ticks[type] = ticks; +} + +/*! + Return a list of ticks + + \param type MinorTick, MediumTick or MajorTick +*/ +const QwtValueList &QwtScaleDiv::ticks(int type) const +{ + if ( type >= 0 || type < NTickTypes ) + return d_ticks[type]; + + static QwtValueList noTicks; + return noTicks; +} diff --git a/qwt/src/qwt_scale_div.h b/qwt/src/qwt_scale_div.h new file mode 100644 index 000000000..cfa76e46b --- /dev/null +++ b/qwt/src/qwt_scale_div.h @@ -0,0 +1,124 @@ +/* -*- 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_SCALE_DIV_H +#define QWT_SCALE_DIV_H + +#include "qwt_global.h" +#include "qwt_valuelist.h" +#include "qwt_double_interval.h" + +class QwtDoubleInterval; + +/*! + \brief A class representing a scale division + + A scale division consists of its limits and 3 list + of tick values qualified as major, medium and minor ticks. + + In most cases scale divisions are calculated by a QwtScaleEngine. + + \sa subDivideInto(), subDivide() +*/ + +class QWT_EXPORT QwtScaleDiv +{ +public: + //! Scale tick types + enum TickType + { + NoTick = -1, + + MinorTick, + MediumTick, + MajorTick, + + NTickTypes + }; + + explicit QwtScaleDiv(); + explicit QwtScaleDiv(const QwtDoubleInterval &, + QwtValueList[NTickTypes]); + explicit QwtScaleDiv(double lowerBound, double upperBound, + QwtValueList[NTickTypes]); + + int operator==(const QwtScaleDiv &s) const; + int operator!=(const QwtScaleDiv &s) const; + + void setInterval(double lowerBound, double upperBound); + void setInterval(const QwtDoubleInterval &); + QwtDoubleInterval interval() const; + + double lowerBound() const; + double upperBound() const; + double range() const; + + bool contains(double v) const; + + void setTicks(int type, const QwtValueList &); + const QwtValueList &ticks(int type) const; + + void invalidate(); + bool isValid() const; + + void invert(); + +private: + double d_lowerBound; + double d_upperBound; + QwtValueList d_ticks[NTickTypes]; + + bool d_isValid; +}; + +/*! + Change the interval + \param lowerBound lower bound + \param upperBound upper bound +*/ +inline void QwtScaleDiv::setInterval(double lowerBound, double upperBound) +{ + d_lowerBound = lowerBound; + d_upperBound = upperBound; +} + +/*! + \return lowerBound -> upperBound +*/ +inline QwtDoubleInterval QwtScaleDiv::interval() const +{ + return QwtDoubleInterval(d_lowerBound, d_upperBound); +} + +/*! + \return lower bound + \sa upperBound() +*/ +inline double QwtScaleDiv::lowerBound() const +{ + return d_lowerBound; +} + +/*! + \return upper bound + \sa lowerBound() +*/ +inline double QwtScaleDiv::upperBound() const +{ + return d_upperBound; +} + +/*! + \return upperBound() - lowerBound() +*/ +inline double QwtScaleDiv::range() const +{ + return d_upperBound - d_lowerBound; +} +#endif diff --git a/qwt/src/qwt_scale_draw.cpp b/qwt/src/qwt_scale_draw.cpp new file mode 100644 index 000000000..4624c15a8 --- /dev/null +++ b/qwt/src/qwt_scale_draw.cpp @@ -0,0 +1,949 @@ +/* -*- 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 "qwt_math.h" +#include "qwt_painter.h" +#include "qwt_polygon.h" +#include "qwt_scale_div.h" +#include "qwt_scale_map.h" +#include "qwt_scale_draw.h" + +#if QT_VERSION < 0x040000 +#include +#define QwtMatrix QWMatrix +#else +#include +#define QwtMatrix QMatrix +#endif + +class QwtScaleDraw::PrivateData +{ +public: + PrivateData(): + len(0), + alignment(QwtScaleDraw::BottomScale), + labelAlignment(0), + labelRotation(0.0) + { + } + + QPoint pos; + int len; + + Alignment alignment; + +#if QT_VERSION < 0x040000 + int labelAlignment; +#else + Qt::Alignment labelAlignment; +#endif + double labelRotation; +}; + +/*! + \brief Constructor + + The range of the scale is initialized to [0, 100], + The position is at (0, 0) with a length of 100. + The orientation is QwtAbstractScaleDraw::Bottom. +*/ +QwtScaleDraw::QwtScaleDraw() +{ + d_data = new QwtScaleDraw::PrivateData; + setLength(100); +} + +//! Copy constructor +QwtScaleDraw::QwtScaleDraw(const QwtScaleDraw &other): + QwtAbstractScaleDraw(other) +{ + d_data = new QwtScaleDraw::PrivateData(*other.d_data); +} + +//! Destructor +QwtScaleDraw::~QwtScaleDraw() +{ + delete d_data; +} + +//! Assignment operator +QwtScaleDraw &QwtScaleDraw::operator=(const QwtScaleDraw &other) +{ + *(QwtAbstractScaleDraw*)this = (const QwtAbstractScaleDraw &)other; + *d_data = *other.d_data; + return *this; +} + +/*! + Return alignment of the scale + \sa setAlignment() +*/ +QwtScaleDraw::Alignment QwtScaleDraw::alignment() const +{ + return d_data->alignment; +} + +/*! + Set the alignment of the scale + + The default alignment is QwtScaleDraw::BottomScale + \sa alignment() +*/ +void QwtScaleDraw::setAlignment(Alignment align) +{ + d_data->alignment = align; +} + +/*! + Return the orientation + + TopScale, BottomScale are horizontal (Qt::Horizontal) scales, + LeftScale, RightScale are vertical (Qt::Vertical) scales. + + \sa alignment() +*/ +Qt::Orientation QwtScaleDraw::orientation() const +{ + switch(d_data->alignment) + { + case TopScale: + case BottomScale: + return Qt::Horizontal; + case LeftScale: + case RightScale: + default: + return Qt::Vertical; + } +} + +/*! + \brief Determine the minimum border distance + + This member function returns the minimum space + needed to draw the mark labels at the scale's endpoints. + + \param font Font + \param start Start border distance + \param end End border distance +*/ +void QwtScaleDraw::getBorderDistHint(const QFont &font, + int &start, int &end ) const +{ + start = 0; + end = 0; + + if ( !hasComponent(QwtAbstractScaleDraw::Labels) ) + return; + + const QwtValueList &ticks = scaleDiv().ticks(QwtScaleDiv::MajorTick); + if ( ticks.count() == 0 ) + return; + + // Find the ticks, that are mapped to the borders. + // minTick is the tick, that is mapped to the top/left-most position + // in widget coordinates. + + double minTick = ticks[0]; + int minPos = map().transform(minTick); + double maxTick = minTick; + int maxPos = minPos; + + for (uint i = 1; i < (uint)ticks.count(); i++) + { + const int tickPos = map().transform(ticks[i]); + if ( tickPos < minPos ) + { + minTick = ticks[i]; + minPos = tickPos; + } + if ( tickPos > map().transform(maxTick) ) + { + maxTick = ticks[i]; + maxPos = tickPos; + } + } + + if ( orientation() == Qt::Vertical ) + { + start = -labelRect(font, minTick).top(); + start -= qwtAbs(minPos - qRound(map().p2())); + + end = labelRect(font, maxTick).bottom() + 1; + end -= qwtAbs(maxPos - qRound(map().p1())); + } + else + { + start = -labelRect(font, minTick).left(); + start -= qwtAbs(minPos - qRound(map().p1())); + + end = labelRect(font, maxTick).right() + 1; + end -= qwtAbs(maxPos - qRound(map().p2())); + } + + if ( start < 0 ) + start = 0; + if ( end < 0 ) + end = 0; +} + +/*! + Determine the minimum distance between two labels, that is necessary + that the texts don't overlap. + + \param font Font + \return The maximum width of a label + + \sa getBorderDistHint() +*/ + +int QwtScaleDraw::minLabelDist(const QFont &font) const +{ + if ( !hasComponent(QwtAbstractScaleDraw::Labels) ) + return 0; + + const QwtValueList &ticks = scaleDiv().ticks(QwtScaleDiv::MajorTick); + if (ticks.count() == 0) + return 0; + + const QFontMetrics fm(font); + + const bool vertical = (orientation() == Qt::Vertical); + + QRect bRect1; + QRect bRect2 = labelRect(font, ticks[0]); + if ( vertical ) + { + bRect2.setRect(-bRect2.bottom(), 0, bRect2.height(), bRect2.width()); + } + int maxDist = 0; + + for (uint i = 1; i < (uint)ticks.count(); i++ ) + { + bRect1 = bRect2; + bRect2 = labelRect(font, ticks[i]); + if ( vertical ) + { + bRect2.setRect(-bRect2.bottom(), 0, + bRect2.height(), bRect2.width()); + } + + int dist = fm.leading(); // space between the labels + if ( bRect1.right() > 0 ) + dist += bRect1.right(); + if ( bRect2.left() < 0 ) + dist += -bRect2.left(); + + if ( dist > maxDist ) + maxDist = dist; + } + + double angle = labelRotation() / 180.0 * M_PI; + if ( vertical ) + angle += M_PI / 2; + + if ( sin(angle) == 0.0 ) + return maxDist; + + const int fmHeight = fm.ascent() - 2; + + // The distance we need until there is + // the height of the label font. This height is needed + // for the neighbour labal. + + int labelDist = (int)(fmHeight / sin(angle) * cos(angle)); + if ( labelDist < 0 ) + labelDist = -labelDist; + + // The cast above floored labelDist. We want to ceil. + labelDist++; + + // For text orientations close to the scale orientation + + if ( labelDist > maxDist ) + labelDist = maxDist; + + // For text orientations close to the opposite of the + // scale orientation + + if ( labelDist < fmHeight ) + labelDist = fmHeight; + + return labelDist; +} + +/*! + Calculate the width/height that is needed for a + vertical/horizontal scale. + + The extent is calculated from the pen width of the backbone, + the major tick length, the spacing and the maximum width/height + of the labels. + + \param pen Pen that is used for painting backbone and ticks + \param font Font used for painting the labels + + \sa minLength() +*/ +int QwtScaleDraw::extent(const QPen &pen, const QFont &font) const +{ + int d = 0; + + if ( hasComponent(QwtAbstractScaleDraw::Labels) ) + { + if ( orientation() == Qt::Vertical ) + d = maxLabelWidth(font); + else + d = maxLabelHeight(font); + + if ( d > 0 ) + d += spacing(); + } + + if ( hasComponent(QwtAbstractScaleDraw::Ticks) ) + { + d += majTickLength(); + } + + if ( hasComponent(QwtAbstractScaleDraw::Backbone) ) + { + const int pw = qwtMax( 1, pen.width() ); // penwidth can be zero + d += pw; + } + + d = qwtMax(d, minimumExtent()); + return d; +} + +/*! + Calculate the minimum length that is needed to draw the scale + + \param pen Pen that is used for painting backbone and ticks + \param font Font used for painting the labels + + \sa extent() +*/ +int QwtScaleDraw::minLength(const QPen &pen, const QFont &font) const +{ + int startDist, endDist; + getBorderDistHint(font, startDist, endDist); + + const QwtScaleDiv &sd = scaleDiv(); + + const uint minorCount = + sd.ticks(QwtScaleDiv::MinorTick).count() + + sd.ticks(QwtScaleDiv::MediumTick).count(); + const uint majorCount = + sd.ticks(QwtScaleDiv::MajorTick).count(); + + int lengthForLabels = 0; + if ( hasComponent(QwtAbstractScaleDraw::Labels) ) + { + if ( majorCount >= 2 ) + lengthForLabels = minLabelDist(font) * (majorCount - 1); + } + + int lengthForTicks = 0; + if ( hasComponent(QwtAbstractScaleDraw::Ticks) ) + { + const int pw = qwtMax( 1, pen.width() ); // penwidth can be zero + lengthForTicks = 2 * (majorCount + minorCount) * pw; + } + + return startDist + endDist + qwtMax(lengthForLabels, lengthForTicks); +} + +/*! + Find the position, where to paint a label + + The position has a distance of majTickLength() + spacing() + 1 + from the backbone. The direction depends on the alignment() + + \param value Value +*/ +QPoint QwtScaleDraw::labelPosition( double value) const +{ + const int tval = map().transform(value); + int dist = spacing() + 1; + if ( hasComponent(QwtAbstractScaleDraw::Ticks) ) + dist += majTickLength(); + + int px = 0; + int py = 0; + + switch(alignment()) + { + case RightScale: + { + px = d_data->pos.x() + dist; + py = tval; + break; + } + case LeftScale: + { + px = d_data->pos.x() - dist; + py = tval; + break; + } + case BottomScale: + { + px = tval; + py = d_data->pos.y() + dist; + break; + } + case TopScale: + { + px = tval; + py = d_data->pos.y() - dist; + break; + } + } + + return QPoint(px, py); +} + +/*! + Draw a tick + + \param painter Painter + \param value Value of the tick + \param len Lenght of the tick + + \sa drawBackbone(), drawLabel() +*/ +void QwtScaleDraw::drawTick(QPainter *painter, double value, int len) const +{ + if ( len <= 0 ) + return; + + int pw2 = qwtMin((int)painter->pen().width(), len) / 2; + + QwtScaleMap scaleMap = map(); + const QwtMetricsMap metricsMap = QwtPainter::metricsMap(); + QPoint pos = d_data->pos; + + if ( !metricsMap.isIdentity() ) + { + /* + The perfect position of the ticks is important. + To avoid rounding errors we have to use + device coordinates. + */ + QwtPainter::resetMetricsMap(); + + pos = metricsMap.layoutToDevice(pos); + + if ( orientation() == Qt::Vertical ) + { + scaleMap.setPaintInterval( + metricsMap.layoutToDeviceY((int)scaleMap.p1()), + metricsMap.layoutToDeviceY((int)scaleMap.p2()) + ); + len = metricsMap.layoutToDeviceX(len); + } + else + { + scaleMap.setPaintInterval( + metricsMap.layoutToDeviceX((int)scaleMap.p1()), + metricsMap.layoutToDeviceX((int)scaleMap.p2()) + ); + len = metricsMap.layoutToDeviceY(len); + } + } + + const int tval = scaleMap.transform(value); + + switch(alignment()) + { + case LeftScale: + { +#if QT_VERSION < 0x040000 + QwtPainter::drawLine(painter, pos.x() + pw2, tval, + pos.x() - len - 2 * pw2, tval); +#else + QwtPainter::drawLine(painter, pos.x() - pw2, tval, + pos.x() - len, tval); +#endif + break; + } + + case RightScale: + { +#if QT_VERSION < 0x040000 + QwtPainter::drawLine(painter, pos.x(), tval, + pos.x() + len + pw2, tval); +#else + QwtPainter::drawLine(painter, pos.x() + pw2, tval, + pos.x() + len, tval); +#endif + break; + } + + case BottomScale: + { +#if QT_VERSION < 0x040000 + QwtPainter::drawLine(painter, tval, pos.y(), + tval, pos.y() + len + 2 * pw2); +#else + QwtPainter::drawLine(painter, tval, pos.y() + pw2, + tval, pos.y() + len); +#endif + break; + } + + case TopScale: + { +#if QT_VERSION < 0x040000 + QwtPainter::drawLine(painter, tval, pos.y() + pw2, + tval, pos.y() - len - 2 * pw2); +#else + QwtPainter::drawLine(painter, tval, pos.y() - pw2, + tval, pos.y() - len); +#endif + break; + } + } + QwtPainter::setMetricsMap(metricsMap); // restore metrics map +} + +/*! + Draws the baseline of the scale + \param painter Painter + + \sa drawTick(), drawLabel() +*/ +void QwtScaleDraw::drawBackbone(QPainter *painter) const +{ + const int bw2 = painter->pen().width() / 2; + + const QPoint &pos = d_data->pos; + const int len = d_data->len - 1; + + switch(alignment()) + { + case LeftScale: + QwtPainter::drawLine(painter, pos.x() - bw2, + pos.y(), pos.x() - bw2, pos.y() + len ); + break; + case RightScale: + QwtPainter::drawLine(painter, pos.x() + bw2, + pos.y(), pos.x() + bw2, pos.y() + len); + break; + case TopScale: + QwtPainter::drawLine(painter, pos.x(), pos.y() - bw2, + pos.x() + len, pos.y() - bw2); + break; + case BottomScale: + QwtPainter::drawLine(painter, pos.x(), pos.y() + bw2, + pos.x() + len, pos.y() + bw2); + break; + } +} + +/*! + \brief Move the position of the scale + + The meaning of the parameter pos depends on the alignment: +
+
QwtScaleDraw::LeftScale +
The origin is the topmost point of the + backbone. The backbone is a vertical line. + Scale marks and labels are drawn + at the left of the backbone. +
QwtScaleDraw::RightScale +
The origin is the topmost point of the + backbone. The backbone is a vertical line. + Scale marks and labels are drawn + at the right of the backbone. +
QwtScaleDraw::TopScale +
The origin is the leftmost point of the + backbone. The backbone is a horizontal line. + Scale marks and labels are drawn + above the backbone. +
QwtScaleDraw::BottomScale +
The origin is the leftmost point of the + backbone. The backbone is a horizontal line + Scale marks and labels are drawn + below the backbone. +
+ + \param pos Origin of the scale + + \sa pos(), setLength() +*/ +void QwtScaleDraw::move(const QPoint &pos) +{ + d_data->pos = pos; + updateMap(); +} + +/*! + \return Origin of the scale + \sa move(), length() +*/ +QPoint QwtScaleDraw::pos() const +{ + return d_data->pos; +} + +/*! + Set the length of the backbone. + + The length doesn't include the space needed for + overlapping labels. + + \sa move(), minLabelDist() +*/ +void QwtScaleDraw::setLength(int length) +{ + if ( length >= 0 && length < 10 ) + length = 10; + if ( length < 0 && length > -10 ) + length = -10; + + d_data->len = length; + updateMap(); +} + +/*! + \return the length of the backbone + \sa setLength(), pos() +*/ +int QwtScaleDraw::length() const +{ + return d_data->len; +} + +/*! + Draws the label for a major scale tick + + \param painter Painter + \param value Value + + \sa drawTick(), drawBackbone(), boundingLabelRect() +*/ +void QwtScaleDraw::drawLabel(QPainter *painter, double value) const +{ + QwtText lbl = tickLabel(painter->font(), value); + if ( lbl.isEmpty() ) + return; + + QPoint pos = labelPosition(value); + + QSize labelSize = lbl.textSize(painter->font()); + if ( labelSize.height() % 2 ) + labelSize.setHeight(labelSize.height() + 1); + + const QwtMetricsMap metricsMap = QwtPainter::metricsMap(); + QwtPainter::resetMetricsMap(); + + labelSize = metricsMap.layoutToDevice(labelSize); + pos = metricsMap.layoutToDevice(pos); + + const QwtMatrix m = labelMatrix( pos, labelSize); + + painter->save(); +#if QT_VERSION < 0x040000 + painter->setWorldMatrix(m, true); +#else + painter->setMatrix(m, true); +#endif + + lbl.draw (painter, QRect(QPoint(0, 0), labelSize) ); + + QwtPainter::setMetricsMap(metricsMap); // restore metrics map + + painter->restore(); +} + +/*! + Find the bounding rect for the label. The coordinates of + the rect are absolute coordinates ( calculated from pos() ). + in direction of the tick. + + \param font Font used for painting + \param value Value + + \sa labelRect() +*/ +QRect QwtScaleDraw::boundingLabelRect(const QFont &font, double value) const +{ + QwtText lbl = tickLabel(font, value); + if ( lbl.isEmpty() ) + return QRect(); + + const QPoint pos = labelPosition(value); + QSize labelSize = lbl.textSize(font); + if ( labelSize.height() % 2 ) + labelSize.setHeight(labelSize.height() + 1); + + const QwtMatrix m = labelMatrix( pos, labelSize); + return m.mapRect(QRect(QPoint(0, 0), labelSize)); +} + +/*! + Calculate the matrix that is needed to paint a label + depending on its alignment and rotation. + + \param pos Position where to paint the label + \param size Size of the label + + \sa setLabelAlignment(), setLabelRotation() +*/ +QwtMatrix QwtScaleDraw::labelMatrix( + const QPoint &pos, const QSize &size) const +{ + QwtMatrix m; + m.translate(pos.x(), pos.y()); + m.rotate(labelRotation()); + + int flags = labelAlignment(); + if ( flags == 0 ) + { + switch(alignment()) + { + case RightScale: + { + if ( flags == 0 ) + flags = Qt::AlignRight | Qt::AlignVCenter; + break; + } + case LeftScale: + { + if ( flags == 0 ) + flags = Qt::AlignLeft | Qt::AlignVCenter; + break; + } + case BottomScale: + { + if ( flags == 0 ) + flags = Qt::AlignHCenter | Qt::AlignBottom; + break; + } + case TopScale: + { + if ( flags == 0 ) + flags = Qt::AlignHCenter | Qt::AlignTop; + break; + } + } + } + + const int w = size.width(); + const int h = size.height(); + + int x, y; + + if ( flags & Qt::AlignLeft ) + x = -w; + else if ( flags & Qt::AlignRight ) + x = -(w % 2); + else // Qt::AlignHCenter + x = -(w / 2); + + if ( flags & Qt::AlignTop ) + y = -h ; + else if ( flags & Qt::AlignBottom ) + y = -(h % 2); + else // Qt::AlignVCenter + y = -(h/2); + + m.translate(x, y); + + return m; +} + +/*! + Find the bounding rect for the label. The coordinates of + the rect are relative to spacing + ticklength from the backbone + in direction of the tick. + + \param font Font used for painting + \param value Value +*/ +QRect QwtScaleDraw::labelRect(const QFont &font, double value) const +{ + QwtText lbl = tickLabel(font, value); + if ( lbl.isEmpty() ) + return QRect(0, 0, 0, 0); + + const QPoint pos = labelPosition(value); + + QSize labelSize = lbl.textSize(font); + if ( labelSize.height() % 2 ) + { + labelSize.setHeight(labelSize.height() + 1); + } + + const QwtMatrix m = labelMatrix(pos, labelSize); + +#if 0 + QRect br = QwtMetricsMap::translate(m, QRect(QPoint(0, 0), labelSize)); +#else + QwtPolygon pol(4); + pol.setPoint(0, 0, 0); + pol.setPoint(1, 0, labelSize.height() - 1 ); + pol.setPoint(2, labelSize.width() - 1, 0); + pol.setPoint(3, labelSize.width() - 1, labelSize.height() - 1 ); + + pol = QwtMetricsMap::translate(m, pol); + QRect br = pol.boundingRect(); +#endif + +#if QT_VERSION < 0x040000 + br.moveBy(-pos.x(), -pos.y()); +#else + br.translate(-pos.x(), -pos.y()); +#endif + + return br; +} + +/*! + Calculate the size that is needed to draw a label + + \param font Label font + \param value Value +*/ +QSize QwtScaleDraw::labelSize(const QFont &font, double value) const +{ + return labelRect(font, value).size(); +} + +/*! + Rotate all labels. + + When changing the rotation, it might be necessary to + adjust the label flags too. Finding a useful combination is + often the result of try and error. + + \param rotation Angle in degrees. When changing the label rotation, + the label flags often needs to be adjusted too. + + \sa setLabelAlignment(), labelRotation(), labelAlignment(). + +*/ +void QwtScaleDraw::setLabelRotation(double rotation) +{ + d_data->labelRotation = rotation; +} + +/*! + \return the label rotation + \sa setLabelRotation(), labelAlignment() +*/ +double QwtScaleDraw::labelRotation() const +{ + return d_data->labelRotation; +} + +/*! + \brief Change the label flags + + Labels are aligned to the point ticklength + spacing away from the backbone. + + The alignment is relative to the orientation of the label text. + In case of an flags of 0 the label will be aligned + depending on the orientation of the scale: + + QwtScaleDraw::TopScale: Qt::AlignHCenter | Qt::AlignTop\n + QwtScaleDraw::BottomScale: Qt::AlignHCenter | Qt::AlignBottom\n + QwtScaleDraw::LeftScale: Qt::AlignLeft | Qt::AlignVCenter\n + QwtScaleDraw::RightScale: Qt::AlignRight | Qt::AlignVCenter\n + + Changing the alignment is often necessary for rotated labels. + + \param alignment Or'd Qt::AlignmentFlags + + \sa setLabelRotation(), labelRotation(), labelAlignment() + \warning The various alignments might be confusing. + The alignment of the label is not the alignment + of the scale and is not the alignment of the flags + (QwtText::flags()) returned from QwtAbstractScaleDraw::label(). +*/ + +#if QT_VERSION < 0x040000 +void QwtScaleDraw::setLabelAlignment(int alignment) +#else +void QwtScaleDraw::setLabelAlignment(Qt::Alignment alignment) +#endif +{ + d_data->labelAlignment = alignment; +} + +/*! + \return the label flags + \sa setLabelAlignment(), labelRotation() +*/ +#if QT_VERSION < 0x040000 +int QwtScaleDraw::labelAlignment() const +#else +Qt::Alignment QwtScaleDraw::labelAlignment() const +#endif +{ + return d_data->labelAlignment; +} + +/*! + \param font Font + \return the maximum width of a label +*/ +int QwtScaleDraw::maxLabelWidth(const QFont &font) const +{ + int maxWidth = 0; + + const QwtValueList &ticks = scaleDiv().ticks(QwtScaleDiv::MajorTick); + for (uint i = 0; i < (uint)ticks.count(); i++) + { + const double v = ticks[i]; + if ( scaleDiv().contains(v) ) + { + const int w = labelSize(font, ticks[i]).width(); + if ( w > maxWidth ) + maxWidth = w; + } + } + + return maxWidth; +} + +/*! + \param font Font + \return the maximum height of a label +*/ +int QwtScaleDraw::maxLabelHeight(const QFont &font) const +{ + int maxHeight = 0; + + const QwtValueList &ticks = scaleDiv().ticks(QwtScaleDiv::MajorTick); + for (uint i = 0; i < (uint)ticks.count(); i++) + { + const double v = ticks[i]; + if ( scaleDiv().contains(v) ) + { + const int h = labelSize(font, ticks[i]).height(); + if ( h > maxHeight ) + maxHeight = h; + } + } + + return maxHeight; +} + +void QwtScaleDraw::updateMap() +{ + QwtScaleMap &sm = scaleMap(); + if ( orientation() == Qt::Vertical ) + sm.setPaintInterval(d_data->pos.y() + d_data->len, d_data->pos.y()); + else + sm.setPaintInterval(d_data->pos.x(), d_data->pos.x() + d_data->len); +} diff --git a/qwt/src/qwt_scale_draw.h b/qwt/src/qwt_scale_draw.h new file mode 100644 index 000000000..40e23b632 --- /dev/null +++ b/qwt/src/qwt_scale_draw.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_SCALE_DRAW_H +#define QWT_SCALE_DRAW_H + +#include +#include "qwt_global.h" +#include "qwt_abstract_scale_draw.h" + +/*! + \brief A class for drawing scales + + QwtScaleDraw can be used to draw linear or logarithmic scales. + A scale has a position, an alignment and a length, which can be specified . + The labels can be rotated and aligned + to the ticks using setLabelRotation() and setLabelAlignment(). + + After a scale division has been specified as a QwtScaleDiv object + using QwtAbstractScaleDraw::setScaleDiv(const QwtScaleDiv &s), + the scale can be drawn with the QwtAbstractScaleDraw::draw() member. +*/ + +class QWT_EXPORT QwtScaleDraw: public QwtAbstractScaleDraw +{ +public: + /*! + Alignment of the scale draw + \sa setAlignment(), alignment() + */ + enum Alignment { BottomScale, TopScale, LeftScale, RightScale }; + + QwtScaleDraw(); + QwtScaleDraw(const QwtScaleDraw &); + + virtual ~QwtScaleDraw(); + + QwtScaleDraw &operator=(const QwtScaleDraw &other); + + void getBorderDistHint(const QFont &, int &start, int &end) const; + int minLabelDist(const QFont &) const; + + int minLength(const QPen &, const QFont &) const; + virtual int extent(const QPen &, const QFont &) const; + + void move(int x, int y); + void move(const QPoint &); + void setLength(int length); + + Alignment alignment() const; + void setAlignment(Alignment); + + Qt::Orientation orientation() const; + + QPoint pos() const; + int length() const; + +#if QT_VERSION < 0x040000 + void setLabelAlignment(int); + int labelAlignment() const; +#else + void setLabelAlignment(Qt::Alignment); + Qt::Alignment labelAlignment() const; +#endif + + void setLabelRotation(double rotation); + double labelRotation() const; + + int maxLabelHeight(const QFont &) const; + int maxLabelWidth(const QFont &) const; + + QPoint labelPosition(double val) const; + + QRect labelRect(const QFont &, double val) const; + QSize labelSize(const QFont &, double val) const; + + QRect boundingLabelRect(const QFont &, double val) const; + +protected: + +#if QT_VERSION < 0x040000 + QWMatrix labelMatrix(const QPoint &, const QSize &) const; +#else + QMatrix labelMatrix(const QPoint &, const QSize &) const; +#endif + + virtual void drawTick(QPainter *p, double val, int len) const; + virtual void drawBackbone(QPainter *p) const; + virtual void drawLabel(QPainter *p, double val) const; + +private: + void updateMap(); + + class PrivateData; + PrivateData *d_data; +}; + +/*! + Move the position of the scale + \sa move(const QPoint &) +*/ +inline void QwtScaleDraw::move(int x, int y) +{ + move(QPoint(x, y)); +} + +#endif diff --git a/qwt/src/qwt_scale_engine.cpp b/qwt/src/qwt_scale_engine.cpp new file mode 100644 index 000000000..e40ddeeec --- /dev/null +++ b/qwt/src/qwt_scale_engine.cpp @@ -0,0 +1,892 @@ +/* -*- 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" +#include "qwt_scale_map.h" +#include "qwt_scale_engine.h" + +static const double _eps = 1.0e-6; + +/*! + \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 +*/ +int QwtScaleArithmetic::compareEps(double value1, double value2, + double intervalSize) +{ + const double eps = qwtAbs(_eps * intervalSize); + + if ( value2 - value1 > eps ) + return -1; + + if ( value1 - value2 > eps ) + return 1; + + return 0; +} + +/*! + Ceil a value, relative to an interval + + \param value Value to ceil + \param intervalSize Interval size + + \sa floorEps() +*/ +double QwtScaleArithmetic::ceilEps(double value, + double intervalSize) +{ + const double eps = _eps * intervalSize; + + value = (value - eps) / intervalSize; + return ceil(value) * intervalSize; +} + +/*! + Floor a value, relative to an interval + + \param value Value to floor + \param intervalSize Interval size + + \sa floorEps() +*/ +double QwtScaleArithmetic::floorEps(double value, double intervalSize) +{ + const double eps = _eps * intervalSize; + + value = (value + eps) / intervalSize; + return floor(value) * intervalSize; +} + +/*! + \brief Divide an interval into steps + + \f$stepSize = (intervalSize - intervalSize * 10e^{-6}) / numSteps\f$ + + \param intervalSize Interval size + \param numSteps Number of steps + \return Step size +*/ +double QwtScaleArithmetic::divideEps(double intervalSize, double numSteps) +{ + if ( numSteps == 0.0 || intervalSize == 0.0 ) + return 0.0; + + return (intervalSize - (_eps * intervalSize)) / numSteps; +} + +/*! + Find the smallest value out of {1,2,5}*10^n with an integer number n + which is greater than or equal to x + + \param x Input value +*/ +double QwtScaleArithmetic::ceil125(double x) +{ + if (x == 0.0) + return 0.0; + + const double sign = (x > 0) ? 1.0 : -1.0; + const double lx = log10(fabs(x)); + const double p10 = floor(lx); + + double fr = pow(10.0, lx - p10); + if (fr <=1.0) + fr = 1.0; + else if (fr <= 2.0) + fr = 2.0; + else if (fr <= 5.0) + fr = 5.0; + else + fr = 10.0; + + return sign * fr * pow(10.0, p10); +} + +/*! + \brief Find the largest value out of {1,2,5}*10^n with an integer number n + which is smaller than or equal to x + + \param x Input value +*/ +double QwtScaleArithmetic::floor125(double x) +{ + if (x == 0.0) + return 0.0; + + double sign = (x > 0) ? 1.0 : -1.0; + const double lx = log10(fabs(x)); + const double p10 = floor(lx); + + double fr = pow(10.0, lx - p10); + if (fr >= 10.0) + fr = 10.0; + else if (fr >= 5.0) + fr = 5.0; + else if (fr >= 2.0) + fr = 2.0; + else + fr = 1.0; + + return sign * fr * pow(10.0, p10); +} + +class QwtScaleEngine::PrivateData +{ +public: + PrivateData(): + attributes(QwtScaleEngine::NoAttribute), + lowerMargin(0.0), + upperMargin(0.0), + referenceValue(0.0) + { + } + + int attributes; // scale attributes + + double lowerMargin; // margins + double upperMargin; + + double referenceValue; // reference value + +}; + +//! Constructor +QwtScaleEngine::QwtScaleEngine() +{ + d_data = new PrivateData; +} + + +//! Destructor +QwtScaleEngine::~QwtScaleEngine () +{ + delete d_data; +} + +/*! + \return the margin at the lower end of the scale + The default margin is 0. + + \sa setMargins() +*/ +double QwtScaleEngine::lowerMargin() const +{ + return d_data->lowerMargin; +} + +/*! + \return the margin at the upper end of the scale + The default margin is 0. + + \sa setMargins() +*/ +double QwtScaleEngine::upperMargin() const +{ + return d_data->upperMargin; +} + +/*! + \brief Specify margins at the scale's endpoints + \param lower minimum distance between the scale's lower boundary and the + smallest enclosed value + \param upper minimum distance between the scale's upper boundary and the + greatest enclosed value + + Margins can be used to leave a minimum amount of space between + the enclosed intervals and the boundaries of the scale. + + \warning + \li QwtLog10ScaleEngine measures the margins in decades. + + \sa upperMargin(), lowerMargin() +*/ + +void QwtScaleEngine::setMargins(double lower, double upper) +{ + d_data->lowerMargin = qwtMax(lower, 0.0); + d_data->upperMargin = qwtMax(upper, 0.0); +} + +/*! + Calculate a step size for an interval size + + \param intervalSize Interval size + \param numSteps Number of steps + + \return Step size +*/ +double QwtScaleEngine::divideInterval( + double intervalSize, int numSteps) const +{ + if ( numSteps <= 0 ) + return 0.0; + + double v = QwtScaleArithmetic::divideEps(intervalSize, numSteps); + return QwtScaleArithmetic::ceil125(v); +} + +/*! + Check if an interval "contains" a value + + \param interval Interval + \param value Value + + \sa QwtScaleArithmetic::compareEps() +*/ +bool QwtScaleEngine::contains( + const QwtDoubleInterval &interval, double value) const +{ + if (!interval.isValid() ) + return false; + + if ( QwtScaleArithmetic::compareEps(value, + interval.minValue(), interval.width()) < 0 ) + { + return false; + } + + if ( QwtScaleArithmetic::compareEps(value, + interval.maxValue(), interval.width()) > 0 ) + { + return false; + } + + return true; +} + +/*! + Remove ticks from a list, that are not inside an interval + + \param ticks Tick list + \param interval Interval + + \return Stripped tick list +*/ +QwtValueList QwtScaleEngine::strip( + const QwtValueList& ticks, + const QwtDoubleInterval &interval) const +{ + if ( !interval.isValid() || ticks.count() == 0 ) + return QwtValueList(); + + if ( contains(interval, ticks.first()) + && contains(interval, ticks.last()) ) + { + return ticks; + } + + QwtValueList strippedTicks; + for ( int i = 0; i < (int)ticks.count(); i++ ) + { + if ( contains(interval, ticks[i]) ) + strippedTicks += ticks[i]; + } + return strippedTicks; +} + +/*! + \brief Build an interval for a value + + In case of v == 0.0 the interval is [-0.5, 0.5], + otherwide it is [0.5 * v, 1.5 * v] +*/ + +QwtDoubleInterval QwtScaleEngine::buildInterval(double v) const +{ + const double delta = (v == 0.0) ? 0.5 : qwtAbs(0.5 * v); + return QwtDoubleInterval(v - delta, v + delta); +} + +/*! + Change a scale attribute + + \param attribute Attribute to change + \param on On/Off + + \sa Attribute, testAttribute() +*/ +void QwtScaleEngine::setAttribute(Attribute attribute, bool on) +{ + if (on) + d_data->attributes |= attribute; + else + d_data->attributes &= (~attribute); +} + +/*! + Check if a attribute is set. + + \param attribute Attribute to be tested + \sa Attribute, setAttribute() +*/ +bool QwtScaleEngine::testAttribute(Attribute attribute) const +{ + return bool(d_data->attributes & attribute); +} + +/*! + Change the scale attribute + + \param attributes Set scale attributes + \sa Attribute, attributes() +*/ +void QwtScaleEngine::setAttributes(int attributes) +{ + d_data->attributes = attributes; +} + +/*! + Return the scale attributes + \sa Attribute, setAttributes(), testAttribute() +*/ +int QwtScaleEngine::attributes() const +{ + return d_data->attributes; +} + +/*! + \brief Specify a reference point + \param r new reference value + + The reference point is needed if options IncludeReference or + Symmetric are active. Its default value is 0.0. + + \sa Attribute +*/ +void QwtScaleEngine::setReference(double r) +{ + d_data->referenceValue = r; +} + +/*! + \return the reference value + \sa setReference(), setAttribute() +*/ +double QwtScaleEngine::reference() const +{ + return d_data->referenceValue; +} + +/*! + Return a transformation, for linear scales +*/ +QwtScaleTransformation *QwtLinearScaleEngine::transformation() const +{ + return new QwtScaleTransformation(QwtScaleTransformation::Linear); +} + +/*! + Align and divide an interval + + \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 setAttribute() +*/ +void QwtLinearScaleEngine::autoScale(int maxNumSteps, + double &x1, double &x2, double &stepSize) const +{ + QwtDoubleInterval 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()); + + stepSize = divideInterval(interval.width(), qwtMax(maxNumSteps, 1)); + + if ( !testAttribute(QwtScaleEngine::Floating) ) + interval = align(interval, stepSize); + + x1 = interval.minValue(); + x2 = interval.maxValue(); + + if (testAttribute(QwtScaleEngine::Inverted)) + { + qSwap(x1, x2); + stepSize = -stepSize; + } +} + +/*! + \brief Calculate a scale division + + \param x1 First interval limit + \param x2 Second interval limit + \param maxMajSteps Maximum for the number of major steps + \param maxMinSteps Maximum number of minor steps + \param stepSize Step size. If stepSize == 0, the scaleEngine + calculates one. + + \sa QwtScaleEngine::stepSize(), QwtScaleEngine::subDivide() +*/ +QwtScaleDiv QwtLinearScaleEngine::divideScale(double x1, double x2, + int maxMajSteps, int maxMinSteps, double stepSize) const +{ + QwtDoubleInterval interval = QwtDoubleInterval(x1, x2).normalized(); + if (interval.width() <= 0 ) + return QwtScaleDiv(); + + stepSize = qwtAbs(stepSize); + if ( stepSize == 0.0 ) + { + if ( maxMajSteps < 1 ) + maxMajSteps = 1; + + stepSize = divideInterval(interval.width(), maxMajSteps); + } + + QwtScaleDiv scaleDiv; + + if ( stepSize != 0.0 ) + { + QwtValueList ticks[QwtScaleDiv::NTickTypes]; + buildTicks(interval, stepSize, maxMinSteps, ticks); + + scaleDiv = QwtScaleDiv(interval, ticks); + } + + if ( x1 > x2 ) + scaleDiv.invert(); + + return scaleDiv; +} + +void QwtLinearScaleEngine::buildTicks( + const QwtDoubleInterval& interval, double stepSize, int maxMinSteps, + QwtValueList ticks[QwtScaleDiv::NTickTypes]) const +{ + const QwtDoubleInterval boundingInterval = + align(interval, stepSize); + + ticks[QwtScaleDiv::MajorTick] = + buildMajorTicks(boundingInterval, stepSize); + + if ( maxMinSteps > 0 ) + { + buildMinorTicks(ticks[QwtScaleDiv::MajorTick], maxMinSteps, stepSize, + ticks[QwtScaleDiv::MinorTick], ticks[QwtScaleDiv::MediumTick]); + } + + for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ ) + { + ticks[i] = strip(ticks[i], interval); + + // ticks very close to 0.0 are + // explicitely set to 0.0 + + for ( int j = 0; j < (int)ticks[i].count(); j++ ) + { + if ( QwtScaleArithmetic::compareEps(ticks[i][j], 0.0, stepSize) == 0 ) + ticks[i][j] = 0.0; + } + } +} + +QwtValueList QwtLinearScaleEngine::buildMajorTicks( + const QwtDoubleInterval &interval, double stepSize) const +{ + int numTicks = qRound(interval.width() / stepSize) + 1; + if ( numTicks > 10000 ) + numTicks = 10000; + + QwtValueList ticks; + + ticks += interval.minValue(); + for (int i = 1; i < numTicks - 1; i++) + ticks += interval.minValue() + i * stepSize; + ticks += interval.maxValue(); + + return ticks; +} + +void QwtLinearScaleEngine::buildMinorTicks( + const QwtValueList& majorTicks, + int maxMinSteps, double stepSize, + QwtValueList &minorTicks, + QwtValueList &mediumTicks) const +{ + double minStep = divideInterval(stepSize, maxMinSteps); + if (minStep == 0.0) + return; + + // # ticks per interval + int numTicks = (int)::ceil(qwtAbs(stepSize / minStep)) - 1; + + // Do the minor steps fit into the interval? + if ( QwtScaleArithmetic::compareEps((numTicks + 1) * qwtAbs(minStep), + qwtAbs(stepSize), stepSize) > 0) + { + numTicks = 1; + minStep = stepSize * 0.5; + } + + int medIndex = -1; + if ( numTicks % 2 ) + medIndex = numTicks / 2; + + // calculate minor ticks + + for (int i = 0; i < (int)majorTicks.count(); i++) + { + double val = majorTicks[i]; + for (int k = 0; k < numTicks; k++) + { + val += minStep; + + double alignedValue = val; + if (QwtScaleArithmetic::compareEps(val, 0.0, stepSize) == 0) + alignedValue = 0.0; + + if ( k == medIndex ) + mediumTicks += alignedValue; + else + minorTicks += alignedValue; + } + } +} + +/*! + \brief Align an interval to a step size + + The limits of an interval are aligned that both are integer + multiples of the step size. + + \param interval Interval + \param stepSize Step size + + \return Aligned interval +*/ +QwtDoubleInterval QwtLinearScaleEngine::align( + const QwtDoubleInterval &interval, double stepSize) const +{ + const double x1 = + QwtScaleArithmetic::floorEps(interval.minValue(), stepSize); + const double x2 = + QwtScaleArithmetic::ceilEps(interval.maxValue(), stepSize); + + return QwtDoubleInterval(x1, x2); +} + +/*! + Return a transformation, for logarithmic (base 10) scales +*/ +QwtScaleTransformation *QwtLog10ScaleEngine::transformation() const +{ + return new QwtScaleTransformation(QwtScaleTransformation::Log10); +} + +/*! + Align and divide an interval + + \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 QwtLog10ScaleEngine::autoScale(int maxNumSteps, + double &x1, double &x2, double &stepSize) const +{ + if ( x1 > x2 ) + qSwap(x1, x2); + + QwtDoubleInterval interval(x1 / pow(10.0, lowerMargin()), + x2 * pow(10.0, upperMargin()) ); + + double logRef = 1.0; + if (reference() > LOG_MIN / 2) + logRef = qwtMin(reference(), LOG_MAX / 2); + + if (testAttribute(QwtScaleEngine::Symmetric)) + { + const double delta = qwtMax(interval.maxValue() / logRef, + logRef / interval.minValue()); + interval.setInterval(logRef / delta, logRef * delta); + } + + if (testAttribute(QwtScaleEngine::IncludeReference)) + interval = interval.extend(logRef); + + interval = interval.limited(LOG_MIN, LOG_MAX); + + if (interval.width() == 0.0) + interval = buildInterval(interval.minValue()); + + stepSize = divideInterval(log10(interval).width(), qwtMax(maxNumSteps, 1)); + if ( stepSize < 1.0 ) + stepSize = 1.0; + + if (!testAttribute(QwtScaleEngine::Floating)) + interval = align(interval, stepSize); + + x1 = interval.minValue(); + x2 = interval.maxValue(); + + if (testAttribute(QwtScaleEngine::Inverted)) + { + qSwap(x1, x2); + stepSize = -stepSize; + } +} + +/*! + \brief Calculate a scale division + + \param x1 First interval limit + \param x2 Second interval limit + \param maxMajSteps Maximum for the number of major steps + \param maxMinSteps Maximum number of minor steps + \param stepSize Step size. If stepSize == 0, the scaleEngine + calculates one. + + \sa QwtScaleEngine::stepSize(), QwtLog10ScaleEngine::subDivide() +*/ +QwtScaleDiv QwtLog10ScaleEngine::divideScale(double x1, double x2, + int maxMajSteps, int maxMinSteps, double stepSize) const +{ + QwtDoubleInterval interval = QwtDoubleInterval(x1, x2).normalized(); + interval = interval.limited(LOG_MIN, LOG_MAX); + + if (interval.width() <= 0 ) + return QwtScaleDiv(); + + if (interval.maxValue() / interval.minValue() < 10.0) + { + // scale width is less than one decade -> build linear scale + + QwtLinearScaleEngine linearScaler; + linearScaler.setAttributes(attributes()); + linearScaler.setReference(reference()); + linearScaler.setMargins(lowerMargin(), upperMargin()); + + return linearScaler.divideScale(x1, x2, + maxMajSteps, maxMinSteps, stepSize); + } + + stepSize = qwtAbs(stepSize); + if ( stepSize == 0.0 ) + { + if ( maxMajSteps < 1 ) + maxMajSteps = 1; + + stepSize = divideInterval(log10(interval).width(), maxMajSteps); + if ( stepSize < 1.0 ) + stepSize = 1.0; // major step must be >= 1 decade + } + + QwtScaleDiv scaleDiv; + if ( stepSize != 0.0 ) + { + QwtValueList ticks[QwtScaleDiv::NTickTypes]; + buildTicks(interval, stepSize, maxMinSteps, ticks); + + scaleDiv = QwtScaleDiv(interval, ticks); + } + + if ( x1 > x2 ) + scaleDiv.invert(); + + return scaleDiv; +} + +void QwtLog10ScaleEngine::buildTicks( + const QwtDoubleInterval& interval, double stepSize, int maxMinSteps, + QwtValueList ticks[QwtScaleDiv::NTickTypes]) const +{ + const QwtDoubleInterval boundingInterval = + align(interval, stepSize); + + ticks[QwtScaleDiv::MajorTick] = + buildMajorTicks(boundingInterval, stepSize); + + if ( maxMinSteps > 0 ) + { + ticks[QwtScaleDiv::MinorTick] = buildMinorTicks( + ticks[QwtScaleDiv::MajorTick], maxMinSteps, stepSize); + } + + for ( int i = 0; i < QwtScaleDiv::NTickTypes; i++ ) + ticks[i] = strip(ticks[i], interval); +} + +QwtValueList QwtLog10ScaleEngine::buildMajorTicks( + const QwtDoubleInterval &interval, double stepSize) const +{ + double width = log10(interval).width(); + + int numTicks = qRound(width / stepSize) + 1; + if ( numTicks > 10000 ) + numTicks = 10000; + + const double lxmin = log(interval.minValue()); + const double lxmax = log(interval.maxValue()); + const double lstep = (lxmax - lxmin) / double(numTicks - 1); + + QwtValueList ticks; + + ticks += interval.minValue(); + + for (int i = 1; i < numTicks; i++) + ticks += exp(lxmin + double(i) * lstep); + + ticks += interval.maxValue(); + + return ticks; +} + +QwtValueList QwtLog10ScaleEngine::buildMinorTicks( + const QwtValueList &majorTicks, + int maxMinSteps, double stepSize) const +{ + if (stepSize < 1.1) // major step width is one decade + { + if ( maxMinSteps < 1 ) + return QwtValueList(); + + int k0, kstep, kmax; + + if (maxMinSteps >= 8) + { + k0 = 2; + kmax = 9; + kstep = 1; + } + else if (maxMinSteps >= 4) + { + k0 = 2; + kmax = 8; + kstep = 2; + } + else if (maxMinSteps >= 2) + { + k0 = 2; + kmax = 5; + kstep = 3; + } + else + { + k0 = 5; + kmax = 5; + kstep = 1; + } + + QwtValueList minorTicks; + + for (int i = 0; i < (int)majorTicks.count(); i++) + { + const double v = majorTicks[i]; + for (int k = k0; k<= kmax; k+=kstep) + minorTicks += v * double(k); + } + + return minorTicks; + } + else // major step > one decade + { + double minStep = divideInterval(stepSize, maxMinSteps); + if ( minStep == 0.0 ) + return QwtValueList(); + + if ( minStep < 1.0 ) + minStep = 1.0; + + // # subticks per interval + int nMin = qRound(stepSize / minStep) - 1; + + // Do the minor steps fit into the interval? + + if ( QwtScaleArithmetic::compareEps((nMin + 1) * minStep, + qwtAbs(stepSize), stepSize) > 0) + { + nMin = 0; + } + + if (nMin < 1) + return QwtValueList(); // no subticks + + // substep factor = 10^substeps + const double minFactor = qwtMax(pow(10.0, minStep), 10.0); + + QwtValueList minorTicks; + for (int i = 0; i < (int)majorTicks.count(); i++) + { + double val = majorTicks[i]; + for (int k=0; k< nMin; k++) + { + val *= minFactor; + minorTicks += val; + } + } + return minorTicks; + } +} + +/*! + \brief Align an interval to a step size + + The limits of an interval are aligned that both are integer + multiples of the step size. + + \param interval Interval + \param stepSize Step size + + \return Aligned interval +*/ +QwtDoubleInterval QwtLog10ScaleEngine::align( + const QwtDoubleInterval &interval, double stepSize) const +{ + const QwtDoubleInterval intv = log10(interval); + + const double x1 = QwtScaleArithmetic::floorEps(intv.minValue(), stepSize); + const double x2 = QwtScaleArithmetic::ceilEps(intv.maxValue(), stepSize); + + return pow10(QwtDoubleInterval(x1, x2)); +} + +/*! + Return the interval [log10(interval.minValue(), log10(interval.maxValue] +*/ + +QwtDoubleInterval QwtLog10ScaleEngine::log10( + const QwtDoubleInterval &interval) const +{ + return QwtDoubleInterval(::log10(interval.minValue()), + ::log10(interval.maxValue())); +} + +/*! + Return the interval [pow10(interval.minValue(), pow10(interval.maxValue] +*/ +QwtDoubleInterval QwtLog10ScaleEngine::pow10( + const QwtDoubleInterval &interval) const +{ + return QwtDoubleInterval(pow(10.0, interval.minValue()), + pow(10.0, interval.maxValue())); +} diff --git a/qwt/src/qwt_scale_engine.h b/qwt/src/qwt_scale_engine.h new file mode 100644 index 000000000..cf0da5dc3 --- /dev/null +++ b/qwt/src/qwt_scale_engine.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_SCALE_ENGINE_H +#define QWT_SCALE_ENGINE_H + +#include "qwt_global.h" +#include "qwt_scale_div.h" +#include "qwt_double_interval.h" + +class QwtScaleTransformation; + +/*! + \brief Arithmetic including a tolerance +*/ +class QWT_EXPORT QwtScaleArithmetic +{ +public: + static int compareEps( + double value1, double value2, double intervalSize); + + static double ceilEps(double value, double intervalSize); + static double floorEps(double value, double intervalSize); + + static double divideEps(double interval, double steps); + + static double ceil125(double x); + static double floor125(double x); +}; + +/*! + \brief Base class for scale engines. + + A scale engine trys to find "reasonable" ranges and step sizes + for scales. + + The layout of the scale can be varied with setAttribute(). + + Qwt offers implementations for logarithmic (log10) + and linear scales. Contributions for other types of scale engines + (date/time, log2 ... ) are welcome. +*/ + +class QWT_EXPORT QwtScaleEngine +{ +public: + /*! + - IncludeReference\n + Build a scale which includes the reference() value. + - Symmetric\n + Build a scale which is symmetric to the reference() value. + - Floating\n + The endpoints of the scale are supposed to be equal the + outmost included values plus the specified margins (see setMargins()). If this attribute is *not* set, the endpoints of the scale will + be integer multiples of the step size. + - Inverted\n + Turn the scale upside down. + + \sa setAttribute(), testAttribute(), reference(), + lowerMargin(), upperMargin() + */ + + enum Attribute + { + NoAttribute = 0, + IncludeReference = 1, + Symmetric = 2, + Floating = 4, + Inverted = 8 + }; + + explicit QwtScaleEngine(); + virtual ~QwtScaleEngine(); + + void setAttribute(Attribute, bool on = true); + bool testAttribute(Attribute) const; + + void setAttributes(int); + int attributes() const; + + void setReference(double reference); + double reference() const; + + void setMargins(double lower, double upper); + double lowerMargin() const; + double upperMargin() const; + + /*! + Align and divide an interval + + \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 (Return value) + */ + virtual void autoScale(int maxNumSteps, + double &x1, double &x2, double &stepSize) const = 0; + + /*! + \brief Calculate a scale division + + \param x1 First interval limit + \param x2 Second interval limit + \param maxMajSteps Maximum for the number of major steps + \param maxMinSteps Maximum number of minor steps + \param stepSize Step size. If stepSize == 0.0, the scaleEngine + calculates one. + */ + virtual QwtScaleDiv divideScale(double x1, double x2, + int maxMajSteps, int maxMinSteps, + double stepSize = 0.0) const = 0; + + //! \return a transformation + virtual QwtScaleTransformation *transformation() const = 0; + +protected: + bool contains(const QwtDoubleInterval &, double val) const; + QwtValueList strip(const QwtValueList&, const QwtDoubleInterval &) const; + double divideInterval(double interval, int numSteps) const; + + QwtDoubleInterval buildInterval(double v) const; + +private: + class PrivateData; + PrivateData *d_data; +}; + +/*! + \brief A scale engine for linear scales + + The step size will fit into the pattern + \f$\left\{ 1,2,5\right\} \cdot 10^{n}\f$, where n is an integer. +*/ + +class QWT_EXPORT QwtLinearScaleEngine: public QwtScaleEngine +{ +public: + virtual void autoScale(int maxSteps, + double &x1, double &x2, double &stepSize) const; + + virtual QwtScaleDiv divideScale(double x1, double x2, + int numMajorSteps, int numMinorSteps, + double stepSize = 0.0) const; + + virtual QwtScaleTransformation *transformation() const; + +protected: + QwtDoubleInterval align(const QwtDoubleInterval&, + double stepSize) const; + +private: + void buildTicks( + const QwtDoubleInterval &, double stepSize, int maxMinSteps, + QwtValueList ticks[QwtScaleDiv::NTickTypes]) const; + + void buildMinorTicks( + const QwtValueList& majorTicks, + int maxMinMark, double step, + QwtValueList &, QwtValueList &) const; + + QwtValueList buildMajorTicks( + const QwtDoubleInterval &interval, double stepSize) const; +}; + +/*! + \brief A scale engine for logarithmic (base 10) scales + + The step size is measured in *decades* + and the major step size will be adjusted to fit the pattern + \f$\left\{ 1,2,3,5\right\} \cdot 10^{n}\f$, where n is a natural number + including zero. + + \warning the step size as well as the margins are measured in *decades*. +*/ + +class QWT_EXPORT QwtLog10ScaleEngine: public QwtScaleEngine +{ +public: + virtual void autoScale(int maxSteps, + double &x1, double &x2, double &stepSize) const; + + virtual QwtScaleDiv divideScale(double x1, double x2, + int numMajorSteps, int numMinorSteps, + double stepSize = 0.0) const; + + virtual QwtScaleTransformation *transformation() const; + +protected: + QwtDoubleInterval log10(const QwtDoubleInterval&) const; + QwtDoubleInterval pow10(const QwtDoubleInterval&) const; + +private: + QwtDoubleInterval align(const QwtDoubleInterval&, + double stepSize) const; + + void buildTicks( + const QwtDoubleInterval &, double stepSize, int maxMinSteps, + QwtValueList ticks[QwtScaleDiv::NTickTypes]) const; + + QwtValueList buildMinorTicks( + const QwtValueList& majorTicks, + int maxMinMark, double step) const; + + QwtValueList buildMajorTicks( + const QwtDoubleInterval &interval, double stepSize) const; +}; + +#endif diff --git a/qwt/src/qwt_scale_map.cpp b/qwt/src/qwt_scale_map.cpp new file mode 100644 index 000000000..17d26d732 --- /dev/null +++ b/qwt/src/qwt_scale_map.cpp @@ -0,0 +1,228 @@ +/* -*- 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_scale_map.h" + +QT_STATIC_CONST_IMPL double QwtScaleMap::LogMin = 1.0e-150; +QT_STATIC_CONST_IMPL double QwtScaleMap::LogMax = 1.0e150; + +//! Constructor for a linear transformation +QwtScaleTransformation::QwtScaleTransformation(Type type): + d_type(type) +{ +} + +//! Destructor +QwtScaleTransformation::~QwtScaleTransformation() +{ +} + +//! Create a clone of the transformation +QwtScaleTransformation *QwtScaleTransformation::copy() const +{ + return new QwtScaleTransformation(d_type); +} + +/*! + \brief Transform a value between 2 linear intervals + + \param x value related to the interval [x1, x2] + \param x1 first border of source interval + \param x2 first border of source interval + \param y1 first border of target interval + \param y2 first border of target interval + \return +
+
linear mapping:
y1 + (y2 - y1) / (x2 - x1) * (x - x1)
+
+
+
log10 mapping:
p1 + (p2 - p1) / log(s2 / s1) * log(x / s1)
+
+*/ + +double QwtScaleTransformation::xForm( + double s, double s1, double s2, double p1, double p2) const +{ + if ( d_type == Log10 ) + return p1 + (p2 - p1) / log(s2 / s1) * log(s / s1); + else + return p1 + (p2 - p1) / (s2 - s1) * (s - s1); +} + +/*! + \brief Transform a value from a linear to a logarithmic interval + + \param x value related to the linear interval [p1, p2] + \param p1 first border of linear interval + \param p2 first border of linear interval + \param s1 first border of logarithmic interval + \param s2 first border of logarithmic interval + \return +
+
exp((x - p1) / (p2 - p1) * log(s2 / s1)) * s1; +
+*/ + +double QwtScaleTransformation::invXForm(double p, double p1, double p2, + double s1, double s2) const +{ + if ( d_type == Log10 ) + return exp((p - p1) / (p2 - p1) * log(s2 / s1)) * s1; + else + return s1 + (s2 - s1) / (p2 - p1) * (p - p1); +} + +/*! + \brief Constructor + + The scale and paint device intervals are both set to [0,1]. +*/ +QwtScaleMap::QwtScaleMap(): + d_s1(0.0), + d_s2(1.0), + d_p1(0.0), + d_p2(1.0), + d_cnv(1.0) +{ + d_transformation = new QwtScaleTransformation( + QwtScaleTransformation::Linear); +} + +//! Copy constructor +QwtScaleMap::QwtScaleMap(const QwtScaleMap& other): + d_s1(other.d_s1), + d_s2(other.d_s2), + d_p1(other.d_p1), + d_p2(other.d_p2), + d_cnv(other.d_cnv) +{ + d_transformation = other.d_transformation->copy(); +} + +/*! + Destructor +*/ +QwtScaleMap::~QwtScaleMap() +{ + delete d_transformation; +} + +//! Assignment operator +QwtScaleMap &QwtScaleMap::operator=(const QwtScaleMap &other) +{ + d_s1 = other.d_s1; + d_s2 = other.d_s2; + d_p1 = other.d_p1; + d_p2 = other.d_p2; + d_cnv = other.d_cnv; + + delete d_transformation; + d_transformation = other.d_transformation->copy(); + + return *this; +} + +/*! + Initialize the map with a transformation +*/ +void QwtScaleMap::setTransformation( + QwtScaleTransformation *transformation) +{ + if ( transformation == NULL ) + return; + + delete d_transformation; + d_transformation = transformation; + setScaleInterval(d_s1, d_s2); +} + +//! Get the transformation +const QwtScaleTransformation *QwtScaleMap::transformation() const +{ + return d_transformation; +} + +/*! + \brief Specify the borders of the scale interval + \param s1 first border + \param s2 second border + \warning logarithmic scales might be aligned to [LogMin, LogMax] +*/ +void QwtScaleMap::setScaleInterval(double s1, double s2) +{ + if (d_transformation->type() == QwtScaleTransformation::Log10 ) + { + if (s1 < LogMin) + s1 = LogMin; + else if (s1 > LogMax) + s1 = LogMax; + + if (s2 < LogMin) + s2 = LogMin; + else if (s2 > LogMax) + s2 = LogMax; + } + + d_s1 = s1; + d_s2 = s2; + + if ( d_transformation->type() != QwtScaleTransformation::Other ) + newFactor(); +} + +/*! + \brief Specify the borders of the paint device interval + \param p1 first border + \param p2 second border +*/ +void QwtScaleMap::setPaintInterval(int p1, int p2) +{ + d_p1 = p1; + d_p2 = p2; + + if ( d_transformation->type() != QwtScaleTransformation::Other ) + newFactor(); +} + +/*! + \brief Specify the borders of the paint device interval + \param p1 first border + \param p2 second border +*/ +void QwtScaleMap::setPaintXInterval(double p1, double p2) +{ + d_p1 = p1; + d_p2 = p2; + + if ( d_transformation->type() != QwtScaleTransformation::Other ) + newFactor(); +} + +/*! + \brief Re-calculate the conversion factor. +*/ +void QwtScaleMap::newFactor() +{ + d_cnv = 0.0; +#if 1 + if (d_s2 == d_s1) + return; +#endif + + switch( d_transformation->type() ) + { + case QwtScaleTransformation::Linear: + d_cnv = (d_p2 - d_p1) / (d_s2 - d_s1); + break; + case QwtScaleTransformation::Log10: + d_cnv = (d_p2 - d_p1) / log(d_s2 / d_s1); + break; + default:; + } +} diff --git a/qwt/src/qwt_scale_map.h b/qwt/src/qwt_scale_map.h new file mode 100644 index 000000000..830c49a73 --- /dev/null +++ b/qwt/src/qwt_scale_map.h @@ -0,0 +1,198 @@ +/* -*- 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_SCALE_MAP_H +#define QWT_SCALE_MAP_H + +#include "qwt_global.h" +#include "qwt_math.h" + +/*! + \brief Operations for linear or logarithmic (base 10) transformations +*/ +class QWT_EXPORT QwtScaleTransformation +{ +public: + enum Type + { + Linear, + Log10, + + Other + }; + + QwtScaleTransformation(Type type); + virtual ~QwtScaleTransformation(); + + virtual double xForm(double x, double s1, double s2, + double p1, double p2) const; + virtual double invXForm(double x, double s1, double s2, + double p1, double p2) const; + + Type type() const; + + virtual QwtScaleTransformation *copy() const; + +private: + QwtScaleTransformation(); + QwtScaleTransformation &operator=( const QwtScaleTransformation); + + const Type d_type; +}; + +//! \return Transformation type +inline QwtScaleTransformation::Type QwtScaleTransformation::type() const +{ + return d_type; +} + +/*! + \brief A scale map + + QwtScaleMap offers transformations from a scale + into a paint interval and vice versa. +*/ +class QWT_EXPORT QwtScaleMap +{ +public: + QwtScaleMap(); + QwtScaleMap(const QwtScaleMap&); + + ~QwtScaleMap(); + + QwtScaleMap &operator=(const QwtScaleMap &); + + void setTransformation(QwtScaleTransformation * ); + const QwtScaleTransformation *transformation() const; + + void setPaintInterval(int p1, int p2); + void setPaintXInterval(double p1, double p2); + void setScaleInterval(double s1, double s2); + + int transform(double x) const; + double invTransform(double i) const; + + double xTransform(double x) const; + + double p1() const; + double p2() const; + + double s1() const; + double s2() const; + + double pDist() const; + double sDist() const; + + QT_STATIC_CONST double LogMin; + QT_STATIC_CONST double LogMax; + +private: + void newFactor(); + + double d_s1, d_s2; // scale interval boundaries + double d_p1, d_p2; // paint device interval boundaries + + double d_cnv; // conversion factor + + QwtScaleTransformation *d_transformation; +}; + +/*! + \return First border of the scale interval +*/ +inline double QwtScaleMap::s1() const +{ + return d_s1; +} + +/*! + \return Second border of the scale interval +*/ +inline double QwtScaleMap::s2() const +{ + return d_s2; +} + +/*! + \return First border of the paint interval +*/ +inline double QwtScaleMap::p1() const +{ + return d_p1; +} + +/*! + \return Second border of the paint interval +*/ +inline double QwtScaleMap::p2() const +{ + return d_p2; +} + +/*! + \return qwtAbs(p2() - p1()) +*/ +inline double QwtScaleMap::pDist() const +{ + return qwtAbs(d_p2 - d_p1); +} + +/*! + \return qwtAbs(s2() - s1()) +*/ +inline double QwtScaleMap::sDist() const +{ + return qwtAbs(d_s2 - d_s1); +} + +/*! + Transform a point related to the scale interval into an point + related to the interval of the paint device + + \param s Value relative to the coordinates of the scale +*/ +inline double QwtScaleMap::xTransform(double s) const +{ + // try to inline code from QwtScaleTransformation + + if ( d_transformation->type() == QwtScaleTransformation::Linear ) + return d_p1 + (s - d_s1) * d_cnv; + + if ( d_transformation->type() == QwtScaleTransformation::Log10 ) + return d_p1 + log(s / d_s1) * d_cnv; + + return d_transformation->xForm(s, d_s1, d_s2, d_p1, d_p2 ); +} + +/*! + Transform an paint device value into a value in the + interval of the scale. + + \param p Value relative to the coordinates of the paint device + \sa transform() +*/ +inline double QwtScaleMap::invTransform(double p) const +{ + return d_transformation->invXForm(p, d_p1, d_p2, d_s1, d_s2 ); +} + +/*! + Transform a point related to the scale interval into an point + related to the interval of the paint device and round it to + an integer. (In Qt <= 3.x paint devices are integer based. ) + + \param s Value relative to the coordinates of the scale + \sa xTransform() +*/ +inline int QwtScaleMap::transform(double s) const +{ + return qRound(xTransform(s)); +} + +#endif diff --git a/qwt/src/qwt_scale_widget.cpp b/qwt/src/qwt_scale_widget.cpp new file mode 100644 index 000000000..264e9efbb --- /dev/null +++ b/qwt/src/qwt_scale_widget.cpp @@ -0,0 +1,928 @@ +/* -*- 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 "qwt_painter.h" +#include "qwt_color_map.h" +#include "qwt_scale_widget.h" +#include "qwt_scale_map.h" +#include "qwt_math.h" +#include "qwt_paint_buffer.h" +#include "qwt_scale_div.h" +#include "qwt_text.h" + +class QwtScaleWidget::PrivateData +{ +public: + PrivateData(): + scaleDraw(NULL) + { + colorBar.colorMap = NULL; + } + + ~PrivateData() + { + delete scaleDraw; + delete colorBar.colorMap; + } + + QwtScaleDraw *scaleDraw; + + int borderDist[2]; + int minBorderDist[2]; + int scaleLength; + int margin; + int penWidth; + + int titleOffset; + int spacing; + QwtText title; + + struct t_colorBar + { + bool isEnabled; + int width; + QwtDoubleInterval interval; + QwtColorMap *colorMap; + } colorBar; +}; + +/*! + \brief Create a scale with the position QwtScaleWidget::Left + \param parent Parent widget +*/ +QwtScaleWidget::QwtScaleWidget(QWidget *parent): + QWidget(parent) +{ + initScale(QwtScaleDraw::LeftScale); +} + +#if QT_VERSION < 0x040000 +/*! + \brief Create a scale with the position QwtScaleWidget::Left + \param parent Parent widget + \param name Object name +*/ +QwtScaleWidget::QwtScaleWidget(QWidget *parent, const char *name): + QWidget(parent, name) +{ + initScale(QwtScaleDraw::LeftScale); +} +#endif + +/*! + \brief Constructor + \param align Alignment. + \param parent Parent widget +*/ +QwtScaleWidget::QwtScaleWidget( + QwtScaleDraw::Alignment align, QWidget *parent): + QWidget(parent) +{ + initScale(align); +} + +//! Destructor +QwtScaleWidget::~QwtScaleWidget() +{ + delete d_data; +} + +//! Initialize the scale +void QwtScaleWidget::initScale(QwtScaleDraw::Alignment align) +{ + d_data = new PrivateData; + +#if QT_VERSION < 0x040000 + setWFlags(Qt::WNoAutoErase); +#endif + + d_data->borderDist[0] = 0; + d_data->borderDist[1] = 0; + d_data->minBorderDist[0] = 0; + d_data->minBorderDist[1] = 0; + d_data->margin = 4; + d_data->penWidth = 0; + d_data->titleOffset = 0; + d_data->spacing = 2; + + d_data->scaleDraw = new QwtScaleDraw; + d_data->scaleDraw->setAlignment(align); + d_data->scaleDraw->setLength(10); + + d_data->colorBar.colorMap = new QwtLinearColorMap(); + d_data->colorBar.isEnabled = false; + d_data->colorBar.width = 10; + + const int flags = Qt::AlignHCenter +#if QT_VERSION < 0x040000 + | Qt::WordBreak | Qt::ExpandTabs; +#else + | Qt::TextExpandTabs | Qt::TextWordWrap; +#endif + d_data->title.setRenderFlags(flags); + d_data->title.setFont(font()); + + QSizePolicy policy(QSizePolicy::MinimumExpanding, + QSizePolicy::Fixed); + if ( d_data->scaleDraw->orientation() == Qt::Vertical ) + policy.transpose(); + + setSizePolicy(policy); + +#if QT_VERSION >= 0x040000 + setAttribute(Qt::WA_WState_OwnSizePolicy, false); +#else + clearWState( WState_OwnSizePolicy ); +#endif + +} + +/*! + Give title new text contents + + \param title New title + \sa title(), setTitle(const QwtText &); +*/ +void QwtScaleWidget::setTitle(const QString &title) +{ + if ( d_data->title.text() != title ) + { + d_data->title.setText(title); + layoutScale(); + } +} + +/*! + Give title new text contents + + \param title New title + \sa title() + \warning The title flags are interpreted in + direction of the label, AlignTop, AlignBottom can't be set + as the title will always be aligned to the scale. +*/ +void QwtScaleWidget::setTitle(const QwtText &title) +{ + QwtText t = title; + const int flags = title.renderFlags() & ~(Qt::AlignTop | Qt::AlignBottom); + t.setRenderFlags(flags); + + if (t != d_data->title) + { + d_data->title = t; + layoutScale(); + } +} + +/*! + Change the alignment + + \param alignment New alignment + \sa alignment() +*/ +void QwtScaleWidget::setAlignment(QwtScaleDraw::Alignment alignment) +{ +#if QT_VERSION >= 0x040000 + if ( !testAttribute(Qt::WA_WState_OwnSizePolicy) ) +#else + if ( !testWState( WState_OwnSizePolicy ) ) +#endif + { + QSizePolicy policy(QSizePolicy::MinimumExpanding, + QSizePolicy::Fixed); + if ( d_data->scaleDraw->orientation() == Qt::Vertical ) + policy.transpose(); + setSizePolicy(policy); + +#if QT_VERSION >= 0x040000 + setAttribute(Qt::WA_WState_OwnSizePolicy, false); +#else + clearWState( WState_OwnSizePolicy ); +#endif + } + + if (d_data->scaleDraw) + d_data->scaleDraw->setAlignment(alignment); + layoutScale(); +} + + +/*! + \return position + \sa setPosition() +*/ +QwtScaleDraw::Alignment QwtScaleWidget::alignment() const +{ + if (!scaleDraw()) + return QwtScaleDraw::LeftScale; + + return scaleDraw()->alignment(); +} + +/*! + Specify distances of the scale's endpoints from the + widget's borders. The actual borders will never be less + than minimum border distance. + \param dist1 Left or top Distance + \param dist2 Right or bottom distance + \sa borderDist() +*/ +void QwtScaleWidget::setBorderDist(int dist1, int dist2) +{ + if ( dist1 != d_data->borderDist[0] || dist2 != d_data->borderDist[1] ) + { + d_data->borderDist[0] = dist1; + d_data->borderDist[1] = dist2; + layoutScale(); + } +} + +/*! + \brief Specify the margin to the colorBar/base line. + \param margin Margin + \sa margin() +*/ +void QwtScaleWidget::setMargin(int margin) +{ + margin = qwtMax( 0, margin ); + if ( margin != d_data->margin ) + { + d_data->margin = margin; + layoutScale(); + } +} + +/*! + \brief Specify the distance between color bar, scale and title + \param spacing Spacing + \sa spacing() +*/ +void QwtScaleWidget::setSpacing(int spacing) +{ + spacing = qwtMax( 0, spacing ); + if ( spacing != d_data->spacing ) + { + d_data->spacing = spacing; + layoutScale(); + } +} + +/*! + \brief Specify the width of the scale pen + \param width Pen width + \sa penWidth() +*/ +void QwtScaleWidget::setPenWidth(int width) +{ + if ( width < 0 ) + width = 0; + + if ( width != d_data->penWidth ) + { + d_data->penWidth = width; + layoutScale(); + } +} + +/*! + \brief Change the alignment for the labels. + + \sa QwtScaleDraw::setLabelAlignment(), setLabelRotation() +*/ +#if QT_VERSION < 0x040000 +void QwtScaleWidget::setLabelAlignment(int alignment) +#else +void QwtScaleWidget::setLabelAlignment(Qt::Alignment alignment) +#endif +{ + d_data->scaleDraw->setLabelAlignment(alignment); + layoutScale(); +} + +/*! + \brief Change the rotation for the labels. + See QwtScaleDraw::setLabelRotation(). + + \para, rotation Rotation + \sa QwtScaleDraw::setLabelRotation(), setLabelFlags() +*/ +void QwtScaleWidget::setLabelRotation(double rotation) +{ + d_data->scaleDraw->setLabelRotation(rotation); + layoutScale(); +} + +/*! + Set a scale draw + sd has to be created with new and will be deleted in + ~QwtScaleWidget() or the next call of setScaleDraw(). + + \param sd ScaleDraw object + \sa scaleDraw() +*/ +void QwtScaleWidget::setScaleDraw(QwtScaleDraw *sd) +{ + if ( sd == NULL || sd == d_data->scaleDraw ) + return; + + if ( d_data->scaleDraw ) + sd->setAlignment(d_data->scaleDraw->alignment()); + + delete d_data->scaleDraw; + d_data->scaleDraw = sd; + + layoutScale(); +} + +/*! + scaleDraw of this scale + \sa setScaleDraw(), QwtScaleDraw::setScaleDraw() +*/ +const QwtScaleDraw *QwtScaleWidget::scaleDraw() const +{ + return d_data->scaleDraw; +} + +/*! + scaleDraw of this scale + \sa QwtScaleDraw::setScaleDraw() +*/ +QwtScaleDraw *QwtScaleWidget::scaleDraw() +{ + return d_data->scaleDraw; +} + +/*! + \return title + \sa setTitle() +*/ +QwtText QwtScaleWidget::title() const +{ + return d_data->title; +} + +/*! + \return start border distance + \sa setBorderDist() +*/ +int QwtScaleWidget::startBorderDist() const +{ + return d_data->borderDist[0]; +} + +/*! + \return end border distance + \sa setBorderDist() +*/ +int QwtScaleWidget::endBorderDist() const +{ + return d_data->borderDist[1]; +} + +/*! + \return margin + \sa setMargin() +*/ +int QwtScaleWidget::margin() const +{ + return d_data->margin; +} + +/*! + \return distance between scale and title + \sa setMargin() +*/ +int QwtScaleWidget::spacing() const +{ + return d_data->spacing; +} + +/*! + \return Scale pen width + \sa setPenWidth() +*/ +int QwtScaleWidget::penWidth() const +{ + return d_data->penWidth; +} +/*! + \brief paintEvent +*/ +void QwtScaleWidget::paintEvent(QPaintEvent *e) +{ + const QRect &ur = e->rect(); + if ( ur.isValid() ) + { +#if QT_VERSION < 0x040000 + QwtPaintBuffer paintBuffer(this, ur); + draw(paintBuffer.painter()); +#else + QPainter painter(this); + draw(&painter); +#endif + } +} + +/*! + \brief draw the scale +*/ +void QwtScaleWidget::draw(QPainter *painter) const +{ + painter->save(); + + QPen scalePen = painter->pen(); + scalePen.setWidth(d_data->penWidth); + painter->setPen(scalePen); + +#if QT_VERSION < 0x040000 + d_data->scaleDraw->draw(painter, colorGroup()); +#else + d_data->scaleDraw->draw(painter, palette()); +#endif + painter->restore(); + + if ( d_data->colorBar.isEnabled && d_data->colorBar.width > 0 && + d_data->colorBar.interval.isValid() ) + { + drawColorBar(painter, colorBarRect(rect())); + } + + QRect r = rect(); + if ( d_data->scaleDraw->orientation() == Qt::Horizontal ) + { + r.setLeft(r.left() + d_data->borderDist[0]); + r.setWidth(r.width() - d_data->borderDist[1]); + } + else + { + r.setTop(r.top() + d_data->borderDist[0]); + r.setHeight(r.height() - d_data->borderDist[1]); + } + + if ( !d_data->title.isEmpty() ) + { + QRect tr = r; + switch(d_data->scaleDraw->alignment()) + { + case QwtScaleDraw::LeftScale: + tr.setRight( r.right() - d_data->titleOffset ); + break; + + case QwtScaleDraw::RightScale: + tr.setLeft( r.left() + d_data->titleOffset ); + break; + + case QwtScaleDraw::BottomScale: + tr.setTop( r.top() + d_data->titleOffset ); + break; + + case QwtScaleDraw::TopScale: + default: + tr.setBottom( r.bottom() - d_data->titleOffset ); + break; + } + + drawTitle(painter, d_data->scaleDraw->alignment(), tr); + } +} + +QRect QwtScaleWidget::colorBarRect(const QRect& rect) const +{ + QRect cr = rect; + + if ( d_data->scaleDraw->orientation() == Qt::Horizontal ) + { + cr.setLeft(cr.left() + d_data->borderDist[0]); + cr.setWidth(cr.width() - d_data->borderDist[1] + 1); + } + else + { + cr.setTop(cr.top() + d_data->borderDist[0]); + cr.setHeight(cr.height() - d_data->borderDist[1] + 1); + } + + switch(d_data->scaleDraw->alignment()) + { + case QwtScaleDraw::LeftScale: + { + cr.setLeft( cr.right() - d_data->spacing + - d_data->colorBar.width + 1 ); + cr.setWidth(d_data->colorBar.width); + break; + } + + case QwtScaleDraw::RightScale: + { + cr.setLeft( cr.left() + d_data->spacing ); + cr.setWidth(d_data->colorBar.width); + break; + } + + case QwtScaleDraw::BottomScale: + { + cr.setTop( cr.top() + d_data->spacing ); + cr.setHeight(d_data->colorBar.width); + break; + } + + case QwtScaleDraw::TopScale: + { + cr.setTop( cr.bottom() - d_data->spacing + - d_data->colorBar.width + 1 ); + cr.setHeight(d_data->colorBar.width); + break; + } + } + + return cr; +} + +/*! + \brief resizeEvent +*/ +void QwtScaleWidget::resizeEvent(QResizeEvent *) +{ + layoutScale(false); +} + +//! Recalculate the scale's geometry and layout based on +// the current rect and fonts. +// \param update_geometry notify the layout system and call update +// to redraw the scale + +void QwtScaleWidget::layoutScale( bool update_geometry ) +{ + int bd0, bd1; + getBorderDistHint(bd0, bd1); + if ( d_data->borderDist[0] > bd0 ) + bd0 = d_data->borderDist[0]; + if ( d_data->borderDist[1] > bd1 ) + bd1 = d_data->borderDist[1]; + + int colorBarWidth = 0; + if ( d_data->colorBar.isEnabled && d_data->colorBar.interval.isValid() ) + colorBarWidth = d_data->colorBar.width + d_data->spacing; + + const QRect r = rect(); + int x, y, length; + + if ( d_data->scaleDraw->orientation() == Qt::Vertical ) + { + y = r.top() + bd0; + length = r.height() - (bd0 + bd1); + + if ( d_data->scaleDraw->alignment() == QwtScaleDraw::LeftScale ) + x = r.right() - d_data->margin - colorBarWidth; + else + x = r.left() + d_data->margin + colorBarWidth; + } + else + { + x = r.left() + bd0; + length = r.width() - (bd0 + bd1); + + if ( d_data->scaleDraw->alignment() == QwtScaleDraw::BottomScale ) + y = r.top() + d_data->margin + colorBarWidth; + else + y = r.bottom() - d_data->margin - colorBarWidth; + } + + d_data->scaleDraw->move(x, y); + d_data->scaleDraw->setLength(length); + + d_data->titleOffset = d_data->margin + d_data->spacing + + colorBarWidth + + d_data->scaleDraw->extent(QPen(Qt::black, d_data->penWidth), font()); + + if ( update_geometry ) + { + updateGeometry(); + update(); + } +} + +void QwtScaleWidget::drawColorBar(QPainter *painter, const QRect& rect) const +{ + if ( !d_data->colorBar.interval.isValid() ) + return; + + const QwtScaleDraw* sd = d_data->scaleDraw; + + QwtPainter::drawColorBar(painter, *d_data->colorBar.colorMap, + d_data->colorBar.interval.normalized(), sd->map(), + sd->orientation(), rect); +} + +/*! + Rotate and paint a title according to its position into a given rectangle. + \param painter Painter + \param align Alignment + \param rect Bounding rectangle +*/ + +void QwtScaleWidget::drawTitle(QPainter *painter, + QwtScaleDraw::Alignment align, const QRect &rect) const +{ + QRect r; + double angle; + int flags = d_data->title.renderFlags() & + ~(Qt::AlignTop | Qt::AlignBottom | Qt::AlignVCenter); + + switch(align) + { + case QwtScaleDraw::LeftScale: + flags |= Qt::AlignTop; + angle = -90.0; + r.setRect(rect.left(), rect.bottom(), rect.height(), rect.width()); + break; + case QwtScaleDraw::RightScale: + flags |= Qt::AlignTop; + angle = 90.0; + r.setRect(rect.right(), rect.top(), rect.height(), rect.width()); + break; + case QwtScaleDraw::TopScale: + flags |= Qt::AlignTop; + angle = 0.0; + r = rect; + break; + case QwtScaleDraw::BottomScale: + default: + flags |= Qt::AlignBottom; + angle = 0.0; + r = rect; + break; + } + + painter->save(); + painter->setFont(font()); +#if QT_VERSION < 0x040000 + painter->setPen(colorGroup().color(QColorGroup::Text)); +#else + painter->setPen(palette().color(QPalette::Text)); +#endif + + const QwtMetricsMap metricsMap = QwtPainter::metricsMap(); + QwtPainter::resetMetricsMap(); + + r = metricsMap.layoutToDevice(r); + + painter->translate(r.x(), r.y()); + if (angle != 0.0) + painter->rotate(angle); + + QwtText title = d_data->title; + title.setRenderFlags(flags); + title.draw(painter, QRect(0, 0, r.width(), r.height())); + + QwtPainter::setMetricsMap(metricsMap); // restore metrics map + + painter->restore(); +} + +/*! + \brief Notify a change of the scale + + This virtual function can be overloaded by derived + classes. The default implementation updates the geometry + and repaints the widget. +*/ + +void QwtScaleWidget::scaleChange() +{ + layoutScale(); +} + +/*! + \return a size hint +*/ +QSize QwtScaleWidget::sizeHint() const +{ + return minimumSizeHint(); +} + +/*! + \return a minimum size hint +*/ +QSize QwtScaleWidget::minimumSizeHint() const +{ + const Qt::Orientation o = d_data->scaleDraw->orientation(); + + // Border Distance cannot be less than the scale borderDistHint + // Note, the borderDistHint is already included in minHeight/minWidth + int length = 0; + int mbd1, mbd2; + getBorderDistHint(mbd1, mbd2); + length += qwtMax( 0, d_data->borderDist[0] - mbd1 ); + length += qwtMax( 0, d_data->borderDist[1] - mbd2 ); + length += d_data->scaleDraw->minLength( + QPen(Qt::black, d_data->penWidth), font()); + + int dim = dimForLength(length, font()); + if ( length < dim ) + { + // compensate for long titles + length = dim; + dim = dimForLength(length, font()); + } + + QSize size(length + 2, dim); + if ( o == Qt::Vertical ) + size.transpose(); + + return size; +} + +/*! + \brief Find the height of the title for a given width. + \param width Width + \return height Height + */ + +int QwtScaleWidget::titleHeightForWidth(int width) const +{ + return d_data->title.heightForWidth(width, font()); +} + +/*! + \brief Find the minimum dimension for a given length. + dim is the height, length the width seen in + direction of the title. + \param length width for horizontal, height for vertical scales + \param scaleFont Font of the scale + \return height for horizontal, width for vertical scales +*/ + +int QwtScaleWidget::dimForLength(int length, const QFont &scaleFont) const +{ + int dim = d_data->margin; + dim += d_data->scaleDraw->extent( + QPen(Qt::black, d_data->penWidth), scaleFont); + + if ( !d_data->title.isEmpty() ) + dim += titleHeightForWidth(length) + d_data->spacing; + + if ( d_data->colorBar.isEnabled && d_data->colorBar.interval.isValid() ) + dim += d_data->colorBar.width + d_data->spacing; + + return dim; +} + +/*! + \brief Calculate a hint for the border distances. + + This member function calculates the distance + of the scale's endpoints from the widget borders which + is required for the mark labels to fit into the widget. + The maximum of this distance an the minimum border distance + is returned. + + \warning +
  • The minimum border distance depends on the font.
+ \sa setMinBorderDist(), getMinBorderDist(), setBorderDist() +*/ +void QwtScaleWidget::getBorderDistHint(int &start, int &end) const +{ + d_data->scaleDraw->getBorderDistHint(font(), start, end); + + if ( start < d_data->minBorderDist[0] ) + start = d_data->minBorderDist[0]; + + if ( end < d_data->minBorderDist[1] ) + end = d_data->minBorderDist[1]; +} + +/*! + Set a minimum value for the distances of the scale's endpoints from + the widget borders. This is useful to avoid that the scales + are "jumping", when the tick labels or their positions change + often. + + \param start Minimum for the start border + \param end Minimum for the end border + \sa getMinBorderDist(), getBorderDistHint() +*/ +void QwtScaleWidget::setMinBorderDist(int start, int end) +{ + d_data->minBorderDist[0] = start; + d_data->minBorderDist[1] = end; +} + +/*! + Get the minimum value for the distances of the scale's endpoints from + the widget borders. + + \sa setMinBorderDist(), getBorderDistHint() +*/ +void QwtScaleWidget::getMinBorderDist(int &start, int &end) const +{ + start = d_data->minBorderDist[0]; + end = d_data->minBorderDist[1]; +} + +#if QT_VERSION < 0x040000 + +/*! + \brief Notify a change of the font + + This virtual function may be overloaded by derived widgets. + The default implementation resizes the scale and repaints + the widget. + \param oldFont Previous font +*/ +void QwtScaleWidget::fontChange(const QFont &oldFont) +{ + QWidget::fontChange( oldFont ); + layoutScale(); +} + +#endif + +/*! + \brief Assign a scale division + + The scale division determines where to set the tick marks. + + \param transformation Transformation, needed to translate between + scale and pixal values + \param scaleDiv Scale Division + \sa For more information about scale divisions, see QwtScaleDiv. +*/ +void QwtScaleWidget::setScaleDiv( + QwtScaleTransformation *transformation, + const QwtScaleDiv &scaleDiv) +{ + QwtScaleDraw *sd = d_data->scaleDraw; + if (sd->scaleDiv() != scaleDiv || + sd->map().transformation()->type() != transformation->type() ) + { + sd->setTransformation(transformation); + sd->setScaleDiv(scaleDiv); + layoutScale(); + + emit scaleDivChanged(); + } + else + delete transformation; +} + +void QwtScaleWidget::setColorBarEnabled(bool on) +{ + if ( on != d_data->colorBar.isEnabled ) + { + d_data->colorBar.isEnabled = on; + layoutScale(); + } +} + +bool QwtScaleWidget::isColorBarEnabled() const +{ + return d_data->colorBar.isEnabled; +} + + +void QwtScaleWidget::setColorBarWidth(int width) +{ + if ( width != d_data->colorBar.width ) + { + d_data->colorBar.width = width; + if ( isColorBarEnabled() ) + layoutScale(); + } +} + +int QwtScaleWidget::colorBarWidth() const +{ + return d_data->colorBar.width; +} + +QwtDoubleInterval QwtScaleWidget::colorBarInterval() const +{ + return d_data->colorBar.interval; +} + +void QwtScaleWidget::setColorMap(const QwtDoubleInterval &interval, + const QwtColorMap &colorMap) +{ + d_data->colorBar.interval = interval; + + delete d_data->colorBar.colorMap; + d_data->colorBar.colorMap = colorMap.copy(); + + if ( isColorBarEnabled() ) + layoutScale(); +} + +const QwtColorMap &QwtScaleWidget::colorMap() const +{ + return *d_data->colorBar.colorMap; +} diff --git a/qwt/src/qwt_scale_widget.h b/qwt/src/qwt_scale_widget.h new file mode 100644 index 000000000..25647726d --- /dev/null +++ b/qwt/src/qwt_scale_widget.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_SCALE_WIDGET_H +#define QWT_SCALE_WIDGET_H + +#include +#include +#include +#include + +#include "qwt_global.h" +#include "qwt_text.h" +#include "qwt_scale_draw.h" + +class QPainter; +class QwtScaleTransformation; +class QwtScaleDiv; +class QwtColorMap; + +/*! + \brief A Widget which contains a scale + + This Widget can be used to decorate composite widgets with + a scale. +*/ + +class QWT_EXPORT QwtScaleWidget : public QWidget +{ + Q_OBJECT + +public: + explicit QwtScaleWidget(QWidget *parent = NULL); +#if QT_VERSION < 0x040000 + explicit QwtScaleWidget(QWidget *parent, const char *name); +#endif + explicit QwtScaleWidget(QwtScaleDraw::Alignment, QWidget *parent = NULL); + virtual ~QwtScaleWidget(); + +signals: + //! Signal emitted, whenever the scale divison changes + void scaleDivChanged(); + +public: + void setTitle(const QString &title); + void setTitle(const QwtText &title); + QwtText title() const; + + void setBorderDist(int start, int end); + int startBorderDist() const; + int endBorderDist() const; + + void getBorderDistHint(int &start, int &end) const; + + void getMinBorderDist(int &start, int &end) const; + void setMinBorderDist(int start, int end); + + void setMargin(int); + int margin() const; + + void setSpacing(int td); + int spacing() const; + + void setPenWidth(int); + int penWidth() const; + + void setScaleDiv(QwtScaleTransformation *, const QwtScaleDiv &sd); + + void setScaleDraw(QwtScaleDraw *); + const QwtScaleDraw *scaleDraw() const; + QwtScaleDraw *scaleDraw(); + +#if QT_VERSION < 0x040000 + void setLabelAlignment(int); +#else + void setLabelAlignment(Qt::Alignment); +#endif + void setLabelRotation(double rotation); + + void setColorBarEnabled(bool); + bool isColorBarEnabled() const; + + void setColorBarWidth(int); + int colorBarWidth() const; + + void setColorMap(const QwtDoubleInterval &, const QwtColorMap &); + + QwtDoubleInterval colorBarInterval() const; + const QwtColorMap &colorMap() const; + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + int titleHeightForWidth(int width) const; + int dimForLength(int length, const QFont &scaleFont) const; + + void drawColorBar(QPainter *painter, const QRect &rect) const; + void drawTitle(QPainter *painter, QwtScaleDraw::Alignment, + const QRect &rect) const; + + void setAlignment(QwtScaleDraw::Alignment); + QwtScaleDraw::Alignment alignment() const; + + QRect colorBarRect(const QRect&) const; + +protected: + virtual void paintEvent(QPaintEvent *e); + virtual void resizeEvent(QResizeEvent *e); + +#if QT_VERSION < 0x040000 + virtual void fontChange(const QFont &oldfont); +#endif + + void draw(QPainter *p) const; + + void scaleChange(); + void layoutScale( bool update = true ); + +private: + void initScale(QwtScaleDraw::Alignment); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_slider.cpp b/qwt/src/qwt_slider.cpp new file mode 100644 index 000000000..27cc98d1a --- /dev/null +++ b/qwt/src/qwt_slider.cpp @@ -0,0 +1,897 @@ +/* -*- 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 "qwt_painter.h" +#include "qwt_paint_buffer.h" +#include "qwt_scale_draw.h" +#include "qwt_scale_map.h" +#include "qwt_slider.h" + +class QwtSlider::PrivateData +{ +public: + QRect sliderRect; + + int thumbLength; + int thumbWidth; + int borderWidth; + int scaleDist; + int xMargin; + int yMargin; + + QwtSlider::ScalePos scalePos; + QwtSlider::BGSTYLE bgStyle; + + /* + Scale and values might have different maps. This is + confusing and I can't see strong arguments for such + a feature. TODO ... + */ + QwtScaleMap map; // linear map + mutable QSize sizeHintCache; +}; + +/*! + \brief Constructor + \param parent parent widget + \param orientation Orientation of the slider. Can be Qt::Horizontal + or Qt::Vertical. Defaults to Qt::Horizontal. + \param scalePos Position of the scale. + Defaults to QwtSlider::NoScale. + \param bgStyle Background style. QwtSlider::BgTrough draws the + slider button in a trough, QwtSlider::BgSlot draws + a slot underneath the button. An or-combination of both + may also be used. The default is QwtSlider::BgTrough. + + QwtSlider enforces valid combinations of its orientation and scale position. + If the combination is invalid, the scale position will be set to NoScale. + Valid combinations are: + - Qt::Horizonal with NoScale, TopScale, or BottomScale; + - Qt::Vertical with NoScale, LeftScale, or RightScale. +*/ +QwtSlider::QwtSlider(QWidget *parent, + Qt::Orientation orientation, ScalePos scalePos, BGSTYLE bgStyle): + QwtAbstractSlider(orientation, parent) +{ + initSlider(orientation, scalePos, bgStyle); +} + +#if QT_VERSION < 0x040000 +/*! + \brief Constructor + + Build a horizontal slider with no scale and BgTrough as + background style + + \param parent parent widget + \param name Object name +*/ +QwtSlider::QwtSlider(QWidget *parent, const char* name): + QwtAbstractSlider(Qt::Horizontal, parent) +{ + setName(name); + initSlider(Qt::Horizontal, NoScale, BgTrough); +} +#endif + +void QwtSlider::initSlider(Qt::Orientation orientation, + ScalePos scalePos, BGSTYLE bgStyle) +{ + if (orientation == Qt::Vertical) + setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Expanding); + else + setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); + +#if QT_VERSION >= 0x040000 + setAttribute(Qt::WA_WState_OwnSizePolicy, false); +#else + clearWState( WState_OwnSizePolicy ); +#endif + + +#if QT_VERSION < 0x040000 + setWFlags(Qt::WNoAutoErase); +#endif + + d_data = new QwtSlider::PrivateData; + + d_data->borderWidth = 2; + d_data->scaleDist = 4; + d_data->scalePos = scalePos; + d_data->xMargin = 0; + d_data->yMargin = 0; + d_data->bgStyle = bgStyle; + + if (bgStyle == BgSlot) + { + d_data->thumbLength = 16; + d_data->thumbWidth = 30; + } + else + { + d_data->thumbLength = 31; + d_data->thumbWidth = 16; + } + + d_data->sliderRect.setRect(0,0,8,8); + + QwtScaleDraw::Alignment align; + if ( orientation == Qt::Vertical ) + { + // enforce a valid combination of scale position and orientation + if ((d_data->scalePos == BottomScale) || (d_data->scalePos == TopScale)) + d_data->scalePos = NoScale; + // adopt the policy of layoutSlider (NoScale lays out like Left) + if (d_data->scalePos == RightScale) + align = QwtScaleDraw::RightScale; + else + align = QwtScaleDraw::LeftScale; + } + else + { + // enforce a valid combination of scale position and orientation + if ((d_data->scalePos == LeftScale) || (d_data->scalePos == RightScale)) + d_data->scalePos = NoScale; + // adopt the policy of layoutSlider (NoScale lays out like Bottom) + if (d_data->scalePos == TopScale) + align = QwtScaleDraw::TopScale; + else + align = QwtScaleDraw::BottomScale; + } + + scaleDraw()->setAlignment(align); + scaleDraw()->setLength(100); + + setRange(0.0, 100.0, 1.0); + setValue(0.0); +} + +QwtSlider::~QwtSlider() +{ + delete d_data; +} + +/*! + \brief Set the orientation. + \param o Orientation. Allowed values are Qt::Horizontal and Qt::Vertical. + + If the new orientation and the old scale position are an invalid combination, + the scale position will be set to QwtSlider::NoScale. + \sa QwtAbstractSlider::orientation() +*/ +void QwtSlider::setOrientation(Qt::Orientation o) +{ + if ( o == orientation() ) + return; + + if (o == Qt::Horizontal) + { + if ((d_data->scalePos == LeftScale) || (d_data->scalePos == RightScale)) + d_data->scalePos = NoScale; + } + else // if (o == Qt::Vertical) + { + if ((d_data->scalePos == BottomScale) || (d_data->scalePos == TopScale)) + d_data->scalePos = NoScale; + } + +#if QT_VERSION >= 0x040000 + if ( !testAttribute(Qt::WA_WState_OwnSizePolicy) ) +#else + if ( !testWState( WState_OwnSizePolicy ) ) +#endif + { + QSizePolicy sp = sizePolicy(); + sp.transpose(); + setSizePolicy(sp); + +#if QT_VERSION >= 0x040000 + setAttribute(Qt::WA_WState_OwnSizePolicy, false); +#else + clearWState( WState_OwnSizePolicy ); +#endif + } + + QwtAbstractSlider::setOrientation(o); + layoutSlider(); +} + +/*! + \brief Change the scale position (and slider orientation). + + \param s Position of the scale. + + A valid combination of scale position and orientation is enforced: + - if the new scale position is Left or Right, the scale orientation will + become Qt::Vertical; + - if the new scale position is Bottom or Top the scale orientation will + become Qt::Horizontal; + - if the new scale position is QwtSlider::NoScale, the scale + orientation will not change. +*/ +void QwtSlider::setScalePosition(ScalePos s) +{ + if ( d_data->scalePos == s ) + return; + + d_data->scalePos = s; + + switch(d_data->scalePos) + { + case BottomScale: + { + setOrientation(Qt::Horizontal); + scaleDraw()->setAlignment(QwtScaleDraw::BottomScale); + break; + } + case TopScale: + { + setOrientation(Qt::Horizontal); + scaleDraw()->setAlignment(QwtScaleDraw::TopScale); + break; + } + case LeftScale: + { + setOrientation(Qt::Vertical); + scaleDraw()->setAlignment(QwtScaleDraw::LeftScale); + break; + } + case RightScale: + { + setOrientation(Qt::Vertical); + scaleDraw()->setAlignment(QwtScaleDraw::RightScale); + break; + } + default: + { + // nothing + } + } + + layoutSlider(); +} + +//! Return the scale position. +QwtSlider::ScalePos QwtSlider::scalePosition() const +{ + return d_data->scalePos; +} + +/*! + \brief Change the slider's border width + \param bd border width +*/ +void QwtSlider::setBorderWidth(int bd) +{ + if ( bd < 0 ) + bd = 0; + + if ( bd != d_data->borderWidth ) + { + d_data->borderWidth = bd; + layoutSlider(); + } +} + +/*! + \brief Set the slider's thumb length + \param thumbLength new length +*/ +void QwtSlider::setThumbLength(int thumbLength) +{ + if ( thumbLength < 8 ) + thumbLength = 8; + + if ( thumbLength != d_data->thumbLength ) + { + d_data->thumbLength = thumbLength; + layoutSlider(); + } +} + +/*! + \brief Change the width of the thumb + \param w new width +*/ +void QwtSlider::setThumbWidth(int w) +{ + if ( w < 4 ) + w = 4; + + if ( d_data->thumbWidth != w ) + { + d_data->thumbWidth = w; + layoutSlider(); + } +} + +/*! + \brief Set a scale draw + + For changing the labels of the scales, it + is necessary to derive from QwtScaleDraw and + overload QwtScaleDraw::label(). + + \param scaleDraw ScaleDraw object, that has to be created with + new and will be deleted in ~QwtSlider or the next + call of setScaleDraw(). +*/ +void QwtSlider::setScaleDraw(QwtScaleDraw *scaleDraw) +{ + const QwtScaleDraw *previousScaleDraw = this->scaleDraw(); + if ( scaleDraw == NULL || scaleDraw == previousScaleDraw ) + return; + + if ( previousScaleDraw ) + scaleDraw->setAlignment(previousScaleDraw->alignment()); + + setAbstractScaleDraw(scaleDraw); + layoutSlider(); +} + +/*! + \return the scale draw of the slider + \sa setScaleDraw() +*/ +const QwtScaleDraw *QwtSlider::scaleDraw() const +{ + return (QwtScaleDraw *)abstractScaleDraw(); +} + +/*! + \return the scale draw of the slider + \sa setScaleDraw() +*/ +QwtScaleDraw *QwtSlider::scaleDraw() +{ + return (QwtScaleDraw *)abstractScaleDraw(); +} + +//! Notify changed scale +void QwtSlider::scaleChange() +{ + layoutSlider(); +} + + +//! Notify change in font +void QwtSlider::fontChange(const QFont &f) +{ + QwtAbstractSlider::fontChange( f ); + layoutSlider(); +} + +/*! + Draw the slider into the specified rectangle. + + \param painter Painter + \param r Rectangle +*/ +void QwtSlider::drawSlider(QPainter *painter, const QRect &r) +{ + QRect cr(r); + + if (d_data->bgStyle & BgTrough) + { + qDrawShadePanel(painter, r.x(), r.y(), + r.width(), r.height(), +#if QT_VERSION < 0x040000 + colorGroup(), +#else + palette(), +#endif + true, d_data->borderWidth,0); + + cr.setRect(r.x() + d_data->borderWidth, + r.y() + d_data->borderWidth, + r.width() - 2 * d_data->borderWidth, + r.height() - 2 * d_data->borderWidth); + + painter->fillRect(cr.x(), cr.y(), cr.width(), cr.height(), +#if QT_VERSION < 0x040000 + colorGroup().brush(QColorGroup::Mid) +#else + palette().brush(QPalette::Mid) +#endif + ); + } + + if ( d_data->bgStyle & BgSlot) + { + int ws = 4; + int ds = d_data->thumbLength / 2 - 4; + if ( ds < 1 ) + ds = 1; + + QRect rSlot; + if (orientation() == Qt::Horizontal) + { + if ( cr.height() & 1 ) + ws++; + rSlot = QRect(cr.x() + ds, + cr.y() + (cr.height() - ws) / 2, + cr.width() - 2 * ds, ws); + } + else + { + if ( cr.width() & 1 ) + ws++; + rSlot = QRect(cr.x() + (cr.width() - ws) / 2, + cr.y() + ds, + ws, cr.height() - 2 * ds); + } + painter->fillRect(rSlot.x(), rSlot.y(), rSlot.width(), rSlot.height(), +#if QT_VERSION < 0x040000 + colorGroup().brush(QColorGroup::Dark) +#else + palette().brush(QPalette::Dark) +#endif + ); + qDrawShadePanel(painter, rSlot.x(), rSlot.y(), + rSlot.width(), rSlot.height(), +#if QT_VERSION < 0x040000 + colorGroup(), +#else + palette(), +#endif + true, 1 ,0); + + } + + if ( isValid() ) + drawThumb(painter, cr, xyPosition(value())); +} + +/*! + Draw the thumb at a position + + \param painter Painter + \param sliderRect Bounding rectangle of the slider + \param pos Position of the slider thumb +*/ +void QwtSlider::drawThumb(QPainter *painter, const QRect &sliderRect, int pos) +{ + pos++; // shade line points one pixel below + if (orientation() == Qt::Horizontal) + { + qDrawShadePanel(painter, pos - d_data->thumbLength / 2, + sliderRect.y(), d_data->thumbLength, sliderRect.height(), +#if QT_VERSION < 0x040000 + colorGroup(), +#else + palette(), +#endif + false, d_data->borderWidth, +#if QT_VERSION < 0x040000 + &colorGroup().brush(QColorGroup::Button) +#else + &palette().brush(QPalette::Button) +#endif + ); + + qDrawShadeLine(painter, pos, sliderRect.y(), + pos, sliderRect.y() + sliderRect.height() - 2, +#if QT_VERSION < 0x040000 + colorGroup(), +#else + palette(), +#endif + true, 1); + } + else // Vertical + { + qDrawShadePanel(painter, sliderRect.x(), pos - d_data->thumbLength / 2, + sliderRect.width(), d_data->thumbLength, +#if QT_VERSION < 0x040000 + colorGroup(), +#else + palette(), +#endif + false, d_data->borderWidth, +#if QT_VERSION < 0x040000 + &colorGroup().brush(QColorGroup::Button) +#else + &palette().brush(QPalette::Button) +#endif + ); + + qDrawShadeLine(painter, sliderRect.x(), pos, + sliderRect.x() + sliderRect.width() - 2, pos, +#if QT_VERSION < 0x040000 + colorGroup(), +#else + palette(), +#endif + true, 1); + } +} + +/*! + Find the x/y position for a given value v + \param value Value +*/ +int QwtSlider::xyPosition(double value) const +{ + return d_data->map.transform(value); +} + +/*! + Determine the value corresponding to a specified mouse location. + \param pos Mouse position +*/ +double QwtSlider::getValue(const QPoint &pos) +{ + return d_data->map.invTransform( + orientation() == Qt::Horizontal ? pos.x() : pos.y()); +} + +/*! + \brief Determine scrolling mode and direction + \param p point + \param scrollMode Scrolling mode + \param direction Direction +*/ +void QwtSlider::getScrollMode(const QPoint &p, + int &scrollMode, int &direction ) +{ + if (!d_data->sliderRect.contains(p)) + { + scrollMode = ScrNone; + direction = 0; + return; + } + + const int pos = ( orientation() == Qt::Horizontal ) ? p.x() : p.y(); + const int markerPos = xyPosition(value()); + + if ((pos > markerPos - d_data->thumbLength / 2) + && (pos < markerPos + d_data->thumbLength / 2)) + { + scrollMode = ScrMouse; + direction = 0; + return; + } + + scrollMode = ScrPage; + direction = (pos > markerPos) ? 1 : -1; + + if ( scaleDraw()->map().p1() > scaleDraw()->map().p2() ) + direction = -direction; +} + +/*! + Qt paint event + \param event Paint event +*/ +void QwtSlider::paintEvent(QPaintEvent *event) +{ + const QRect &ur = event->rect(); + if ( ur.isValid() ) + { +#if QT_VERSION < 0x040000 + QwtPaintBuffer paintBuffer(this, ur); + draw(paintBuffer.painter(), ur); +#else + QPainter painter(this); + draw(&painter, ur); +#endif + } +} + +//! Draw the QwtSlider +void QwtSlider::draw(QPainter *painter, const QRect&) +{ + if (d_data->scalePos != NoScale) + { +#if QT_VERSION < 0x040000 + scaleDraw()->draw(painter, colorGroup()); +#else + scaleDraw()->draw(painter, palette()); +#endif + } + + drawSlider(painter, d_data->sliderRect); + + if ( hasFocus() ) + QwtPainter::drawFocusRect(painter, this, d_data->sliderRect); +} + +//! Qt resize event +void QwtSlider::resizeEvent(QResizeEvent *) +{ + layoutSlider( false ); +} + +/*! + Recalculate the slider's geometry and layout based on + the current rect and fonts. + \param update_geometry notify the layout system and call update + to redraw the scale +*/ +void QwtSlider::layoutSlider( bool update_geometry ) +{ + int sliderWidth = d_data->thumbWidth; + int sld1 = d_data->thumbLength / 2 - 1; + int sld2 = d_data->thumbLength / 2 + d_data->thumbLength % 2; + if ( d_data->bgStyle & BgTrough ) + { + sliderWidth += 2 * d_data->borderWidth; + sld1 += d_data->borderWidth; + sld2 += d_data->borderWidth; + } + + int scd = 0; + if ( d_data->scalePos != NoScale ) + { + int d1, d2; + scaleDraw()->getBorderDistHint(font(), d1, d2); + scd = qwtMax(d1, d2); + } + + int slo = scd - sld1; + if ( slo < 0 ) + slo = 0; + + int x, y, length; + + const QRect r = rect(); + if (orientation() == Qt::Horizontal) + { + switch (d_data->scalePos) + { + case TopScale: + { + d_data->sliderRect.setRect( + r.x() + d_data->xMargin + slo, + r.y() + r.height() - + d_data->yMargin - sliderWidth, + r.width() - 2 * d_data->xMargin - 2 * slo, + sliderWidth); + + x = d_data->sliderRect.x() + sld1; + y = d_data->sliderRect.y() - d_data->scaleDist; + + break; + } + + case BottomScale: + { + d_data->sliderRect.setRect( + r.x() + d_data->xMargin + slo, + r.y() + d_data->yMargin, + r.width() - 2 * d_data->xMargin - 2 * slo, + sliderWidth); + + x = d_data->sliderRect.x() + sld1; + y = d_data->sliderRect.y() + d_data->sliderRect.height() + + d_data->scaleDist; + + break; + } + + case NoScale: // like Bottom, but no scale. See QwtSlider(). + default: // inconsistent orientation and scale position + { + d_data->sliderRect.setRect( + r.x() + d_data->xMargin + slo, + r.y() + d_data->yMargin, + r.width() - 2 * d_data->xMargin - 2 * slo, + sliderWidth); + + x = d_data->sliderRect.x() + sld1; + y = 0; + + break; + } + } + length = d_data->sliderRect.width() - (sld1 + sld2); + } + else // if (orientation() == Qt::Vertical + { + switch (d_data->scalePos) + { + case RightScale: + d_data->sliderRect.setRect( + r.x() + d_data->xMargin, + r.y() + d_data->yMargin + slo, + sliderWidth, + r.height() - 2 * d_data->yMargin - 2 * slo); + + x = d_data->sliderRect.x() + d_data->sliderRect.width() + + d_data->scaleDist; + y = d_data->sliderRect.y() + sld1; + + break; + + case LeftScale: + d_data->sliderRect.setRect( + r.x() + r.width() - sliderWidth - d_data->xMargin, + r.y() + d_data->yMargin + slo, + sliderWidth, + r.height() - 2 * d_data->yMargin - 2 * slo); + + x = d_data->sliderRect.x() - d_data->scaleDist; + y = d_data->sliderRect.y() + sld1; + + break; + + case NoScale: // like Left, but no scale. See QwtSlider(). + default: // inconsistent orientation and scale position + d_data->sliderRect.setRect( + r.x() + r.width() - sliderWidth - d_data->xMargin, + r.y() + d_data->yMargin + slo, + sliderWidth, + r.height() - 2 * d_data->yMargin - 2 * slo); + + x = 0; + y = d_data->sliderRect.y() + sld1; + + break; + } + length = d_data->sliderRect.height() - (sld1 + sld2); + } + + scaleDraw()->move(x, y); + scaleDraw()->setLength(length); + + d_data->map.setPaintXInterval(scaleDraw()->map().p1(), + scaleDraw()->map().p2()); + + if ( update_geometry ) + { + d_data->sizeHintCache = QSize(); // invalidate + updateGeometry(); + update(); + } +} + +//! Notify change of value +void QwtSlider::valueChange() +{ + QwtAbstractSlider::valueChange(); + update(); +} + + +//! Notify change of range +void QwtSlider::rangeChange() +{ + d_data->map.setScaleInterval(minValue(), maxValue()); + + if (autoScale()) + rescale(minValue(), maxValue()); + + QwtAbstractSlider::rangeChange(); + layoutSlider(); +} + +/*! + \brief Set distances between the widget's border and internals. + \param xMargin Horizontal margin + \param yMargin Vertical margin +*/ +void QwtSlider::setMargins(int xMargin, int yMargin) +{ + if ( xMargin < 0 ) + xMargin = 0; + if ( yMargin < 0 ) + yMargin = 0; + + if ( xMargin != d_data->xMargin || yMargin != d_data->yMargin ) + { + d_data->xMargin = xMargin; + d_data->yMargin = yMargin; + layoutSlider(); + } +} + +/*! + Set the background style. +*/ +void QwtSlider::setBgStyle(BGSTYLE st) +{ + d_data->bgStyle = st; + layoutSlider(); +} + +/*! + \return the background style. +*/ +QwtSlider::BGSTYLE QwtSlider::bgStyle() const +{ + return d_data->bgStyle; +} + +/*! + \return the thumb length. +*/ +int QwtSlider::thumbLength() const +{ + return d_data->thumbLength; +} + +/*! + \return the thumb width. +*/ +int QwtSlider::thumbWidth() const +{ + return d_data->thumbWidth; +} + +/*! + \return the border width. +*/ +int QwtSlider::borderWidth() const +{ + return d_data->borderWidth; +} + +/*! + \return QwtSlider::minimumSizeHint() +*/ +QSize QwtSlider::sizeHint() const +{ + return minimumSizeHint(); +} + +/*! + \brief Return a minimum size hint + \warning The return value of QwtSlider::minimumSizeHint() depends on + the font and the scale. +*/ +QSize QwtSlider::minimumSizeHint() const +{ + if (!d_data->sizeHintCache.isEmpty()) + return d_data->sizeHintCache; + + int sliderWidth = d_data->thumbWidth; + if (d_data->bgStyle & BgTrough) + sliderWidth += 2 * d_data->borderWidth; + + int w = 0, h = 0; + if (d_data->scalePos != NoScale) + { + int d1, d2; + scaleDraw()->getBorderDistHint(font(), d1, d2); + int msMbd = qwtMax(d1, d2); + + int mbd = d_data->thumbLength / 2; + if (d_data->bgStyle & BgTrough) + mbd += d_data->borderWidth; + + if ( mbd < msMbd ) + mbd = msMbd; + + const int sdExtent = scaleDraw()->extent( QPen(), font() ); + const int sdLength = scaleDraw()->minLength( QPen(), font() ); + + h = sliderWidth + sdExtent + d_data->scaleDist; + w = sdLength - 2 * msMbd + 2 * mbd; + } + else // no scale + { + w = 200; + h = sliderWidth; + } + + if ( orientation() == Qt::Vertical ) + qSwap(w, h); + + w += 2 * d_data->xMargin; + h += 2 * d_data->yMargin; + + d_data->sizeHintCache = QSize(w, h); + return d_data->sizeHintCache; +} diff --git a/qwt/src/qwt_slider.h b/qwt/src/qwt_slider.h new file mode 100644 index 000000000..ade69020e --- /dev/null +++ b/qwt/src/qwt_slider.h @@ -0,0 +1,138 @@ +/* -*- 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 + +#ifndef QWT_SLIDER_H +#define QWT_SLIDER_H + +#include "qwt_global.h" +#include "qwt_abstract_scale.h" +#include "qwt_abstract_slider.h" + +class QwtScaleDraw; + +/*! + \brief The Slider Widget + + QwtSlider is a slider widget which operates on an interval + of type double. QwtSlider supports different layouts as + well as a scale. + + \image html sliders.png + + \sa QwtAbstractSlider and QwtAbstractScale for the descriptions + of the inherited members. +*/ + +class QWT_EXPORT QwtSlider : public QwtAbstractSlider, public QwtAbstractScale +{ + Q_OBJECT + Q_ENUMS( ScalePos ) + Q_ENUMS( BGSTYLE ) + Q_PROPERTY( ScalePos scalePosition READ scalePosition + WRITE setScalePosition ) + Q_PROPERTY( BGSTYLE bgStyle READ bgStyle WRITE setBgStyle ) + Q_PROPERTY( int thumbLength READ thumbLength WRITE setThumbLength ) + Q_PROPERTY( int thumbWidth READ thumbWidth WRITE setThumbWidth ) + Q_PROPERTY( int borderWidth READ borderWidth WRITE setBorderWidth ) + +public: + + /*! + Scale position. QwtSlider tries to enforce valid combinations of its + orientation and scale position: + - Qt::Horizonal combines with NoScale, TopScale and BottomScale + - Qt::Vertical combines with NoScale, LeftScale and RightScale + + \sa QwtSlider() + */ + enum ScalePos + { + NoScale, + + LeftScale, + RightScale, + TopScale, + BottomScale + }; + + /*! + Background style. + \sa QwtSlider() + */ + enum BGSTYLE + { + BgTrough = 0x1, + BgSlot = 0x2, + BgBoth = BgTrough | BgSlot + }; + + explicit QwtSlider(QWidget *parent, + Qt::Orientation = Qt::Horizontal, + ScalePos = NoScale, BGSTYLE bgStyle = BgTrough); +#if QT_VERSION < 0x040000 + explicit QwtSlider(QWidget *parent, const char *name); +#endif + + virtual ~QwtSlider(); + + virtual void setOrientation(Qt::Orientation); + + void setBgStyle(BGSTYLE); + BGSTYLE bgStyle() const; + + void setScalePosition(ScalePos s); + ScalePos scalePosition() const; + + int thumbLength() const; + int thumbWidth() const; + int borderWidth() const; + + void setThumbLength(int l); + void setThumbWidth(int w); + void setBorderWidth(int bw); + void setMargins(int x, int y); + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + void setScaleDraw(QwtScaleDraw *); + const QwtScaleDraw *scaleDraw() const; + +protected: + virtual double getValue(const QPoint &p); + virtual void getScrollMode(const QPoint &p, + int &scrollMode, int &direction); + + void draw(QPainter *p, const QRect& update_rect); + virtual void drawSlider (QPainter *p, const QRect &r); + virtual void drawThumb(QPainter *p, const QRect &, int pos); + + virtual void resizeEvent(QResizeEvent *e); + virtual void paintEvent (QPaintEvent *e); + + virtual void valueChange(); + virtual void rangeChange(); + virtual void scaleChange(); + virtual void fontChange(const QFont &oldFont); + + void layoutSlider( bool update = true ); + int xyPosition(double v) const; + + QwtScaleDraw *scaleDraw(); + +private: + void initSlider(Qt::Orientation, ScalePos scalePos, BGSTYLE bgStyle); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_spline.cpp b/qwt/src/qwt_spline.cpp new file mode 100644 index 000000000..5f5c489c7 --- /dev/null +++ b/qwt/src/qwt_spline.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_spline.h" +#include "qwt_math.h" +#include "qwt_array.h" + +class QwtSpline::PrivateData +{ +public: + PrivateData(): + splineType(QwtSpline::Natural) + { + } + + QwtSpline::SplineType splineType; + + // coefficient vectors + QwtArray a; + QwtArray b; + QwtArray c; + + // control points +#if QT_VERSION < 0x040000 + QwtArray points; +#else + QPolygonF points; +#endif +}; + +#if QT_VERSION < 0x040000 +static int lookup(double x, const QwtArray &values) +#else +static int lookup(double x, const QPolygonF &values) +#endif +{ +#if 0 +//qLowerBiund/qHigherBound ??? +#endif + int i1; + const int size = (int)values.size(); + + if (x <= values[0].x()) + i1 = 0; + else if (x >= values[size - 2].x()) + i1 = size - 2; + else + { + i1 = 0; + int i2 = size - 2; + int i3 = 0; + + while ( i2 - i1 > 1 ) + { + i3 = i1 + ((i2 - i1) >> 1); + + if (values[i3].x() > x) + i2 = i3; + else + i1 = i3; + } + } + return i1; +} + +//! Constructor +QwtSpline::QwtSpline() +{ + d_data = new PrivateData; +} + +/*! + Copy constructor + \param other Spline used for initilization +*/ +QwtSpline::QwtSpline(const QwtSpline& other) +{ + d_data = new PrivateData(*other.d_data); +} + +/*! + Assignment operator + \param other Spline used for initilization +*/ +QwtSpline &QwtSpline::operator=( const QwtSpline &other) +{ + *d_data = *other.d_data; + return *this; +} + +//! Destructor +QwtSpline::~QwtSpline() +{ + delete d_data; +} + +/*! + Select the algorithm used for calculating the spline + + \param splineType Spline type + \sa splineType() +*/ +void QwtSpline::setSplineType(SplineType splineType) +{ + d_data->splineType = splineType; +} + +/*! + \return the spline type + \sa setSplineType() +*/ +QwtSpline::SplineType QwtSpline::splineType() const +{ + return d_data->splineType; +} + +/*! + \brief Calculate the spline coefficients + + Depending on the value of \a periodic, this function + will determine the coefficients for a natural or a periodic + spline and store them internally. + + \param x + \param y points + \param size number of points + \param periodic if true, calculate periodic spline + \return true if successful + \warning The sequence of x (but not y) values has to be strictly monotone + increasing, which means x[0] < x[1] < .... < x[n-1]. + If this is not the case, the function will return false +*/ +#if QT_VERSION < 0x040000 +bool QwtSpline::setPoints(const QwtArray& points) +#else +bool QwtSpline::setPoints(const QPolygonF& points) +#endif +{ + const int size = points.size(); + if (size <= 2) + { + reset(); + return false; + } + +#if QT_VERSION < 0x040000 + d_data->points = points.copy(); // Qt3: deep copy +#else + d_data->points = points; +#endif + + d_data->a.resize(size-1); + d_data->b.resize(size-1); + d_data->c.resize(size-1); + + bool ok; + if ( d_data->splineType == Periodic ) + ok = buildPeriodicSpline(points); + else + ok = buildNaturalSpline(points); + + if (!ok) + reset(); + + return ok; +} + +/*! + Return points passed by setPoints +*/ +#if QT_VERSION < 0x040000 +QwtArray QwtSpline::points() const +#else +QPolygonF QwtSpline::points() const +#endif +{ + return d_data->points; +} + +//! \return A coefficients +const QwtArray &QwtSpline::coefficientsA() const +{ + return d_data->a; +} + +//! \return B coefficients +const QwtArray &QwtSpline::coefficientsB() const +{ + return d_data->b; +} + +//! \return C coefficients +const QwtArray &QwtSpline::coefficientsC() const +{ + return d_data->c; +} + + +//! Free allocated memory and set size to 0 +void QwtSpline::reset() +{ + d_data->a.resize(0); + d_data->b.resize(0); + d_data->c.resize(0); + d_data->points.resize(0); +} + +//! True if valid +bool QwtSpline::isValid() const +{ + return d_data->a.size() > 0; +} + +/*! + Calculate the interpolated function value corresponding + to a given argument x. +*/ +double QwtSpline::value(double x) const +{ + if (d_data->a.size() == 0) + return 0.0; + + const int i = lookup(x, d_data->points); + + const double delta = x - d_data->points[i].x(); + return( ( ( ( d_data->a[i] * delta) + d_data->b[i] ) + * delta + d_data->c[i] ) * delta + d_data->points[i].y() ); +} + +/*! + \brief Determines the coefficients for a natural spline + \return true if successful +*/ +#if QT_VERSION < 0x040000 +bool QwtSpline::buildNaturalSpline(const QwtArray &points) +#else +bool QwtSpline::buildNaturalSpline(const QPolygonF &points) +#endif +{ + int i; + +#if QT_VERSION < 0x040000 + const QwtDoublePoint *p = points.data(); +#else + const QPointF *p = points.data(); +#endif + const int size = points.size(); + + double *a = d_data->a.data(); + double *b = d_data->b.data(); + double *c = d_data->c.data(); + + // set up tridiagonal equation system; use coefficient + // vectors as temporary buffers + QwtArray h(size-1); + for (i = 0; i < size - 1; i++) + { + h[i] = p[i+1].x() - p[i].x(); + if (h[i] <= 0) + return false; + } + + QwtArray d(size-1); + double dy1 = (p[1].y() - p[0].y()) / h[0]; + for (i = 1; i < size - 1; i++) + { + b[i] = c[i] = h[i]; + a[i] = 2.0 * (h[i-1] + h[i]); + + const double dy2 = (p[i+1].y() - p[i].y()) / h[i]; + d[i] = 6.0 * ( dy1 - dy2); + dy1 = dy2; + } + + // + // solve it + // + + // L-U Factorization + for(i = 1; i < size - 2;i++) + { + c[i] /= a[i]; + a[i+1] -= b[i] * c[i]; + } + + // forward elimination + QwtArray s(size); + s[1] = d[1]; + for ( i = 2; i < size - 1; i++) + s[i] = d[i] - c[i-1] * s[i-1]; + + // backward elimination + s[size - 2] = - s[size - 2] / a[size - 2]; + for (i = size -3; i > 0; i--) + s[i] = - (s[i] + b[i] * s[i+1]) / a[i]; + s[size - 1] = s[0] = 0.0; + + // + // Finally, determine the spline coefficients + // + for (i = 0; i < size - 1; i++) + { + a[i] = ( s[i+1] - s[i] ) / ( 6.0 * h[i]); + b[i] = 0.5 * s[i]; + c[i] = ( p[i+1].y() - p[i].y() ) / h[i] + - (s[i+1] + 2.0 * s[i] ) * h[i] / 6.0; + } + + return true; +} + +/*! + \brief Determines the coefficients for a periodic spline + \return true if successful +*/ +#if QT_VERSION < 0x040000 +bool QwtSpline::buildPeriodicSpline( + const QwtArray &points) +#else +bool QwtSpline::buildPeriodicSpline(const QPolygonF &points) +#endif +{ + int i; + +#if QT_VERSION < 0x040000 + const QwtDoublePoint *p = points.data(); +#else + const QPointF *p = points.data(); +#endif + const int size = points.size(); + + double *a = d_data->a.data(); + double *b = d_data->b.data(); + double *c = d_data->c.data(); + + QwtArray d(size-1); + QwtArray h(size-1); + QwtArray s(size); + + // + // setup equation system; use coefficient + // vectors as temporary buffers + // + for (i = 0; i < size - 1; i++) + { + h[i] = p[i+1].x() - p[i].x(); + if (h[i] <= 0.0) + return false; + } + + const int imax = size - 2; + double htmp = h[imax]; + double dy1 = (p[0].y() - p[imax].y()) / htmp; + for (i = 0; i <= imax; i++) + { + b[i] = c[i] = h[i]; + a[i] = 2.0 * (htmp + h[i]); + const double dy2 = (p[i+1].y() - p[i].y()) / h[i]; + d[i] = 6.0 * ( dy1 - dy2); + dy1 = dy2; + htmp = h[i]; + } + + // + // solve it + // + + // L-U Factorization + a[0] = sqrt(a[0]); + c[0] = h[imax] / a[0]; + double sum = 0; + + for( i = 0; i < imax - 1; i++) + { + b[i] /= a[i]; + if (i > 0) + c[i] = - c[i-1] * b[i-1] / a[i]; + a[i+1] = sqrt( a[i+1] - qwtSqr(b[i])); + sum += qwtSqr(c[i]); + } + b[imax-1] = (b[imax-1] - c[imax-2] * b[imax-2]) / a[imax-1]; + a[imax] = sqrt(a[imax] - qwtSqr(b[imax-1]) - sum); + + + // forward elimination + s[0] = d[0] / a[0]; + sum = 0; + for( i = 1; i < imax; i++) + { + s[i] = (d[i] - b[i-1] * s[i-1]) / a[i]; + sum += c[i-1] * s[i-1]; + } + s[imax] = (d[imax] - b[imax-1] * s[imax-1] - sum) / a[imax]; + + + // backward elimination + s[imax] = - s[imax] / a[imax]; + s[imax-1] = -(s[imax-1] + b[imax-1] * s[imax]) / a[imax-1]; + for (i= imax - 2; i >= 0; i--) + s[i] = - (s[i] + b[i] * s[i+1] + c[i] * s[imax]) / a[i]; + + // + // Finally, determine the spline coefficients + // + s[size-1] = s[0]; + for ( i=0; i < size-1; i++) + { + a[i] = ( s[i+1] - s[i] ) / ( 6.0 * h[i]); + b[i] = 0.5 * s[i]; + c[i] = ( p[i+1].y() - p[i].y() ) + / h[i] - (s[i+1] + 2.0 * s[i] ) * h[i] / 6.0; + } + + return true; +} diff --git a/qwt/src/qwt_spline.h b/qwt/src/qwt_spline.h new file mode 100644 index 000000000..f0cd466cc --- /dev/null +++ b/qwt/src/qwt_spline.h @@ -0,0 +1,130 @@ +/* -*- 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_SPLINE_H +#define QWT_SPLINE_H + +#include "qwt_global.h" +#include "qwt_double_rect.h" + +#if QT_VERSION >= 0x040000 +#include +#else +#include "qwt_array.h" +#endif + +// MOC_SKIP_BEGIN + +#if defined(QWT_TEMPLATEDLL) + +#if QT_VERSION < 0x040000 +#ifndef QWTARRAY_TEMPLATE_QWTDOUBLEPOINT // by mjo3 +#define QWTARRAY_TEMPLATE_QWTDOUBLEPOINT +template class QWT_EXPORT QwtArray; +#endif //end of QWTARRAY_TEMPLATE_QWTDOUBLEPOINT +#endif + +#endif + +// MOC_SKIP_END + +/*! + \brief A class for spline interpolation + + The QwtSpline class is used for cubical spline interpolation. + Two types of splines, natural and periodic, are supported. + + \par Usage: +
    +
  1. First call setPoints() to determine the spline coefficients + for a tabulated function y(x). +
  2. After the coefficients have been set up, the interpolated + function value for an argument x can be determined by calling + QwtSpline::value(). +
+ + \par Example: + \code +#include + +QPolygonF interpolate(const QPolygonF& points, int numValues) +{ + QwtSpline spline; + if ( !spline.setPoints(points) ) + return points; + + QPolygonF interpolatedPoints(numValues); + + const double delta = + (points[numPoints - 1].x() - points[0].x()) / (points.size() - 1); + for(i = 0; i < points.size(); i++) / interpolate + { + const double x = points[0].x() + i * delta; + interpolatedPoints[i].setX(x); + interpolatedPoints[i].setY(spline.value(x)); + } + return interpolatedPoints; +} + \endcode +*/ + +class QWT_EXPORT QwtSpline +{ +public: + //! Spline type + enum SplineType + { + Natural, + Periodic + }; + + QwtSpline(); + QwtSpline( const QwtSpline & ); + + ~QwtSpline(); + + QwtSpline &operator=( const QwtSpline & ); + + void setSplineType(SplineType); + SplineType splineType() const; + +#if QT_VERSION < 0x040000 + bool setPoints(const QwtArray& points); + QwtArray points() const; +#else + bool setPoints(const QPolygonF& points); + QPolygonF points() const; +#endif + + void reset(); + + bool isValid() const; + double value(double x) const; + + const QwtArray &coefficientsA() const; + const QwtArray &coefficientsB() const; + const QwtArray &coefficientsC() const; + +protected: + +#if QT_VERSION < 0x040000 + bool buildNaturalSpline( + const QwtArray &); + bool buildPeriodicSpline( + const QwtArray &); +#else + bool buildNaturalSpline(const QPolygonF &); + bool buildPeriodicSpline(const QPolygonF &); +#endif + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_symbol.cpp b/qwt/src/qwt_symbol.cpp new file mode 100644 index 000000000..5f8c73ab1 --- /dev/null +++ b/qwt/src/qwt_symbol.cpp @@ -0,0 +1,364 @@ +/* -*- 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 "qwt_painter.h" +#include "qwt_polygon.h" +#include "qwt_symbol.h" + +/*! + Default Constructor + + The symbol is constructed with gray interior, + black outline with zero width, no size and style 'NoSymbol'. +*/ +QwtSymbol::QwtSymbol(): + d_brush(Qt::gray), + d_pen(Qt::black), + d_size(0,0), + d_style(QwtSymbol::NoSymbol) +{ +} + +/*! + \brief Constructor + \param style Symbol Style + \param brush brush to fill the interior + \param pen outline pen + \param size size +*/ +QwtSymbol::QwtSymbol(QwtSymbol::Style style, const QBrush &brush, + const QPen &pen, const QSize &size): + d_brush(brush), + d_pen(pen), + d_size(size), + d_style(style) +{ +} + +//! Destructor +QwtSymbol::~QwtSymbol() +{ +} + +/*! + Allocate and return a symbol with the same attributes + \return Cloned symbol +*/ +QwtSymbol *QwtSymbol::clone() const +{ + QwtSymbol *other = new QwtSymbol; + *other = *this; + + return other; +} + +/*! + \brief Specify the symbol's size + + If the 'h' parameter is left out or less than 0, + and the 'w' parameter is greater than or equal to 0, + the symbol size will be set to (w,w). + \param width Width + \param height Height (defaults to -1) +*/ +void QwtSymbol::setSize(int width, int height) +{ + if ((width >= 0) && (height < 0)) + height = width; + d_size = QSize(width, height); +} + +/*! + Set the symbol's size + \param size Size +*/ +void QwtSymbol::setSize(const QSize &size) +{ + if (size.isValid()) + d_size = size; +} + +/*! + \brief Assign a brush + + The brush is used to draw the interior of the symbol. + \param brush Brush +*/ +void QwtSymbol::setBrush(const QBrush &brush) +{ + d_brush = brush; +} + +/*! + Assign a pen + + The pen is used to draw the symbol's outline. + + The width of non cosmetic pens is scaled according to the resolution + of the paint device. + + \param pen Pen + \sa pen(), setBrush(), QwtPainter::scaledPen() +*/ +void QwtSymbol::setPen(const QPen &pen) +{ + d_pen = pen; +} + +/*! + \brief Draw the symbol at a point (x,y). +*/ +void QwtSymbol::draw(QPainter *painter, int x, int y) const +{ + draw(painter, QPoint(x, y)); +} + + +/*! + \brief Draw the symbol into a bounding rectangle. + + This function assumes that the painter has been initialized with + brush and pen before. This allows a much more performant implementation + when painting many symbols with the same brush and pen like in curves. + + \param painter Painter + \param r Bounding rectangle +*/ +void QwtSymbol::draw(QPainter *painter, const QRect& r) const +{ + switch(d_style) + { + case QwtSymbol::Ellipse: + QwtPainter::drawEllipse(painter, r); + break; + case QwtSymbol::Rect: + QwtPainter::drawRect(painter, r); + break; + case QwtSymbol::Diamond: + { + const int w2 = r.width() / 2; + const int h2 = r.height() / 2; + + QwtPolygon pa(4); + pa.setPoint(0, r.x() + w2, r.y()); + pa.setPoint(1, r.right(), r.y() + h2); + pa.setPoint(2, r.x() + w2, r.bottom()); + pa.setPoint(3, r.x(), r.y() + h2); + QwtPainter::drawPolygon(painter, pa); + break; + } + case QwtSymbol::Cross: + { + const int w2 = r.width() / 2; + const int h2 = r.height() / 2; + + QwtPainter::drawLine(painter, r.x() + w2, r.y(), + r.x() + w2, r.bottom()); + QwtPainter::drawLine(painter, r.x(), r.y() + h2, + r.right(), r.y() + h2); + break; + } + case QwtSymbol::XCross: + { + QwtPainter::drawLine(painter, r.left(), r.top(), + r.right(), r.bottom()); + QwtPainter::drawLine(painter, r.left(), r.bottom(), + r.right(), r.top()); + break; + } + case QwtSymbol::Triangle: + case QwtSymbol::UTriangle: + { + const int w2 = r.width() / 2; + + QwtPolygon pa(3); + pa.setPoint(0, r.x() + w2, r.y()); + pa.setPoint(1, r.right(), r.bottom()); + pa.setPoint(2, r.x(), r.bottom()); + QwtPainter::drawPolygon(painter, pa); + break; + } + case QwtSymbol::DTriangle: + { + const int w2 = r.width() / 2; + + QwtPolygon pa(3); + pa.setPoint(0, r.x(), r.y()); + pa.setPoint(1, r.right(), r.y()); + pa.setPoint(2, r.x() + w2, r.bottom()); + QwtPainter::drawPolygon(painter, pa); + break; + } + case QwtSymbol::RTriangle: + { + const int h2 = r.height() / 2; + + QwtPolygon pa(3); + pa.setPoint(0, r.x(), r.y()); + pa.setPoint(1, r.right(), r.y() + h2); + pa.setPoint(2, r.x(), r.bottom()); + QwtPainter::drawPolygon(painter, pa); + break; + } + case QwtSymbol::LTriangle: + { + const int h2 = r.height() / 2; + + QwtPolygon pa(3); + pa.setPoint(0, r.right(), r.y()); + pa.setPoint(1, r.x(), r.y() + h2); + pa.setPoint(2, r.right(), r.bottom()); + QwtPainter::drawPolygon(painter, pa); + break; + } + case QwtSymbol::HLine: + { + const int h2 = r.height() / 2; + QwtPainter::drawLine(painter, r.left(), r.top() + h2, + r.right(), r.top() + h2); + break; + } + case QwtSymbol::VLine: + { + const int w2 = r.width() / 2; + QwtPainter::drawLine(painter, r.left() + w2, r.top(), + r.left() + w2, r.bottom()); + break; + } + case QwtSymbol::Star1: + { + const double sqrt1_2 = 0.70710678118654752440; /* 1/sqrt(2) */ + + const int w2 = r.width() / 2; + const int h2 = r.height() / 2; + const int d1 = (int)( (double)w2 * (1.0 - sqrt1_2) ); + + QwtPainter::drawLine(painter, r.left() + d1, r.top() + d1, + r.right() - d1, r.bottom() - d1); + QwtPainter::drawLine(painter, r.left() + d1, r.bottom() - d1, + r.right() - d1, r.top() + d1); + QwtPainter::drawLine(painter, r.left() + w2, r.top(), + r.left() + w2, r.bottom()); + QwtPainter::drawLine(painter, r.left(), r.top() + h2, + r.right(), r.top() + h2); + break; + } + case QwtSymbol::Star2: + { + const int w = r.width(); + const int side = (int)(((double)r.width() * (1.0 - 0.866025)) / + 2.0); // 0.866025 = cos(30°) + const int h4 = r.height() / 4; + const int h2 = r.height() / 2; + const int h34 = (r.height() * 3) / 4; + + QwtPolygon pa(12); + pa.setPoint(0, r.left() + (w / 2), r.top()); + pa.setPoint(1, r.right() - (side + (w - 2 * side) / 3), + r.top() + h4 ); + pa.setPoint(2, r.right() - side, r.top() + h4); + pa.setPoint(3, r.right() - (side + (w / 2 - side) / 3), + r.top() + h2 ); + pa.setPoint(4, r.right() - side, r.top() + h34); + pa.setPoint(5, r.right() - (side + (w - 2 * side) / 3), + r.top() + h34 ); + pa.setPoint(6, r.left() + (w / 2), r.bottom()); + pa.setPoint(7, r.left() + (side + (w - 2 * side) / 3), + r.top() + h34 ); + pa.setPoint(8, r.left() + side, r.top() + h34); + pa.setPoint(9, r.left() + (side + (w / 2 - side) / 3), + r.top() + h2 ); + pa.setPoint(10, r.left() + side, r.top() + h4); + pa.setPoint(11, r.left() + (side + (w - 2 * side) / 3), + r.top() + h4 ); + QwtPainter::drawPolygon(painter, pa); + break; + } + case QwtSymbol::Hexagon: + { + const int w2 = r.width() / 2; + const int side = (int)(((double)r.width() * (1.0 - 0.866025)) / + 2.0); // 0.866025 = cos(30°) + const int h4 = r.height() / 4; + const int h34 = (r.height() * 3) / 4; + + QwtPolygon pa(6); + pa.setPoint(0, r.left() + w2, r.top()); + pa.setPoint(1, r.right() - side, r.top() + h4); + pa.setPoint(2, r.right() - side, r.top() + h34); + pa.setPoint(3, r.left() + w2, r.bottom()); + pa.setPoint(4, r.left() + side, r.top() + h34); + pa.setPoint(5, r.left() + side, r.top() + h4); + QwtPainter::drawPolygon(painter, pa); + break; + } + default:; + } +} + +/*! + \brief Draw the symbol at a specified point + + \param painter Painter + \param pos Center of the symbol +*/ +void QwtSymbol::draw(QPainter *painter, const QPoint &pos) const +{ + QRect rect; + rect.setSize(QwtPainter::metricsMap().screenToLayout(d_size)); + rect.moveCenter(pos); + + painter->setBrush(d_brush); + painter->setPen(QwtPainter::scaledPen(d_pen)); + + draw(painter, rect); +} + +/*! + \brief Specify the symbol style + + The following styles are defined:
+
NoSymbol
No Style. The symbol cannot be drawn. +
Ellipse
Ellipse or circle +
Rect
Rectangle +
Diamond
Diamond +
Triangle
Triangle pointing upwards +
DTriangle
Triangle pointing downwards +
UTriangle
Triangle pointing upwards +
LTriangle
Triangle pointing left +
RTriangle
Triangle pointing right +
Cross
Cross (+) +
XCross
Diagonal cross (X) +
HLine
Horizontal line +
VLine
Vertical line +
Star1
X combined with + +
Star2
Six-pointed star +
Hexagon
Hexagon
+ + \param s style +*/ +void QwtSymbol::setStyle(QwtSymbol::Style s) +{ + d_style = s; +} + +//! == operator +bool QwtSymbol::operator==(const QwtSymbol &other) const +{ + return brush() == other.brush() && pen() == other.pen() + && style() == other.style() && size() == other.size(); +} + +//! != operator +bool QwtSymbol::operator!=(const QwtSymbol &other) const +{ + return !(*this == other); +} diff --git a/qwt/src/qwt_symbol.h b/qwt/src/qwt_symbol.h new file mode 100644 index 000000000..b8d3d3097 --- /dev/null +++ b/qwt/src/qwt_symbol.h @@ -0,0 +1,88 @@ +/* -*- 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_SYMBOL_H +#define QWT_SYMBOL_H + +#include +#include +#include +#include "qwt_global.h" + +class QPainter; +class QRect; + +//! A class for drawing symbols +class QWT_EXPORT QwtSymbol +{ +public: + /*! + Style + \sa setStyle(), style() + */ + enum Style + { + NoSymbol = -1, + + Ellipse, + Rect, + Diamond, + Triangle, + DTriangle, + UTriangle, + LTriangle, + RTriangle, + Cross, + XCross, + HLine, + VLine, + Star1, + Star2, + Hexagon, + + StyleCnt + }; + +public: + QwtSymbol(); + QwtSymbol(Style st, const QBrush &bd, const QPen &pn, const QSize &s); + virtual ~QwtSymbol(); + + bool operator!=(const QwtSymbol &) const; + virtual bool operator==(const QwtSymbol &) const; + + virtual QwtSymbol *clone() const; + + void setSize(const QSize &s); + void setSize(int a, int b = -1); + void setBrush(const QBrush& b); + void setPen(const QPen &p); + void setStyle (Style s); + + //! Return Brush + const QBrush& brush() const { return d_brush; } + //! Return Pen + const QPen& pen() const { return d_pen; } + //! Return Size + const QSize& size() const { return d_size; } + //! Return Style + Style style() const { return d_style; } + + void draw(QPainter *p, const QPoint &pt) const; + void draw(QPainter *p, int x, int y) const; + virtual void draw(QPainter *p, const QRect &r) const; + +private: + QBrush d_brush; + QPen d_pen; + QSize d_size; + Style d_style; +}; + +#endif diff --git a/qwt/src/qwt_text.cpp b/qwt/src/qwt_text.cpp new file mode 100644 index 000000000..b6cdef1a2 --- /dev/null +++ b/qwt/src/qwt_text.cpp @@ -0,0 +1,714 @@ +/* -*- 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 + *****************************************************************************/ + +// vim: expandtab + +#include +#include +#include +#include +#include +#include +#include "qwt_painter.h" +#include "qwt_text_engine.h" +#include "qwt_text.h" +#if QT_VERSION >= 0x040000 +#include +#include +#endif + +class QwtTextEngineDict +{ +public: + QwtTextEngineDict(); + ~QwtTextEngineDict(); + + void setTextEngine(QwtText::TextFormat, QwtTextEngine *); + const QwtTextEngine *textEngine(QwtText::TextFormat) const; + const QwtTextEngine *textEngine(const QString &, + QwtText::TextFormat) const; + +private: + typedef QMap EngineMap; + + inline const QwtTextEngine *engine(EngineMap::const_iterator &it) const + { +#if QT_VERSION < 0x040000 + return it.data(); +#else + return it.value(); +#endif + } + + EngineMap d_map; +}; + +QwtTextEngineDict::QwtTextEngineDict() +{ + d_map.insert(QwtText::PlainText, new QwtPlainTextEngine()); +#ifndef QT_NO_RICHTEXT + d_map.insert(QwtText::RichText, new QwtRichTextEngine()); +#endif +} + +QwtTextEngineDict::~QwtTextEngineDict() +{ + for ( EngineMap::const_iterator it = d_map.begin(); + it != d_map.end(); ++it ) + { + QwtTextEngine *textEngine = (QwtTextEngine *)engine(it); + delete textEngine; + } +} + +const QwtTextEngine *QwtTextEngineDict::textEngine(const QString& text, + QwtText::TextFormat format) const +{ + if ( format == QwtText::AutoText ) + { + for ( EngineMap::const_iterator it = d_map.begin(); + it != d_map.end(); ++it ) + { + if ( it.key() != QwtText::PlainText ) + { + const QwtTextEngine *e = engine(it); + if ( e && e->mightRender(text) ) + return (QwtTextEngine *)e; + } + } + } + + EngineMap::const_iterator it = d_map.find(format); + if ( it != d_map.end() ) + { + const QwtTextEngine *e = engine(it); + if ( e ) + return e; + } + + it = d_map.find(QwtText::PlainText); + return engine(it); +} + +void QwtTextEngineDict::setTextEngine(QwtText::TextFormat format, + QwtTextEngine *engine) +{ + if ( format == QwtText::AutoText ) + return; + + if ( format == QwtText::PlainText && engine == NULL ) + return; + + EngineMap::const_iterator it = d_map.find(format); + if ( it != d_map.end() ) + { + const QwtTextEngine *e = this->engine(it); + if ( e ) + delete e; + + d_map.remove(format); + } + + if ( engine != NULL ) + d_map.insert(format, engine); +} + +const QwtTextEngine *QwtTextEngineDict::textEngine( + QwtText::TextFormat format) const +{ + const QwtTextEngine *e = NULL; + + EngineMap::const_iterator it = d_map.find(format); + if ( it != d_map.end() ) + e = engine(it); + + return e; +} + +static QwtTextEngineDict *engineDict = NULL; + +class QwtText::PrivateData +{ +public: + PrivateData(): + renderFlags(Qt::AlignCenter), + backgroundPen(Qt::NoPen), + backgroundBrush(Qt::NoBrush), + paintAttributes(0), + layoutAttributes(0), + textEngine(NULL) + { + } + + int renderFlags; + QString text; + QFont font; + QColor color; + QPen backgroundPen; + QBrush backgroundBrush; + + int paintAttributes; + int layoutAttributes; + + const QwtTextEngine *textEngine; +}; + +class QwtText::LayoutCache +{ +public: + void invalidate() + { + textSize = QSize(); + } + + QFont font; + QSize textSize; +}; + +/*! + Constructor + + \param text Text content + \param textFormat Text format +*/ +QwtText::QwtText(const QString &text, QwtText::TextFormat textFormat) +{ + d_data = new PrivateData; + d_data->text = text; + d_data->textEngine = textEngine(text, textFormat); + + d_layoutCache = new LayoutCache; +} + +//! Copy constructor +QwtText::QwtText(const QwtText &other) +{ + d_data = new PrivateData; + *d_data = *other.d_data; + + d_layoutCache = new LayoutCache; + *d_layoutCache = *other.d_layoutCache; +} + +//! Destructor +QwtText::~QwtText() +{ + delete d_data; + delete d_layoutCache; +} + +//! Assignment operator +QwtText &QwtText::operator=(const QwtText &other) +{ + *d_data = *other.d_data; + *d_layoutCache = *other.d_layoutCache; + return *this; +} + +//! Relational operator +int QwtText::operator==(const QwtText &other) const +{ + return d_data->renderFlags == other.d_data->renderFlags && + d_data->text == other.d_data->text && + d_data->font == other.d_data->font && + d_data->color == other.d_data->color && + d_data->backgroundPen == other.d_data->backgroundPen && + d_data->backgroundBrush == other.d_data->backgroundBrush && + d_data->paintAttributes == other.d_data->paintAttributes && + d_data->textEngine == other.d_data->textEngine; +} + +//! Relational operator +int QwtText::operator!=(const QwtText &other) const // invalidate +{ + return !(other == *this); +} + +/*! + Assign a new text content + + \param text Text content + \param textFormat Text format + + \sa text() +*/ +void QwtText::setText(const QString &text, + QwtText::TextFormat textFormat) +{ + d_data->text = text; + d_data->textEngine = textEngine(text, textFormat); + d_layoutCache->invalidate(); +} + +/*! + Return the text. + \sa setText() +*/ +QString QwtText::text() const +{ + return d_data->text; +} + +/*! + \brief Change the render flags + + The default setting is Qt::AlignCenter + + \param renderFlags Bitwise OR of the flags used like in QPainter::drawText + + \sa renderFlags(), QwtTextEngine::draw() + \note Some renderFlags might have no effect, depending on the text format. +*/ +void QwtText::setRenderFlags(int renderFlags) +{ + if ( renderFlags != d_data->renderFlags ) + { + d_data->renderFlags = renderFlags; + d_layoutCache->invalidate(); + } +} + +/*! + \return Render flags + \sa setRenderFlags() +*/ +int QwtText::renderFlags() const +{ + return d_data->renderFlags; +} + +/*! + Set the font. + + \param font Font + \note Setting the font might have no effect, when + the text contains control sequences for setting fonts. +*/ +void QwtText::setFont(const QFont &font) +{ + d_data->font = font; + setPaintAttribute(PaintUsingTextFont); +} + +//! Return the font. +QFont QwtText::font() const +{ + return d_data->font; +} + +/*! + Return the font of the text, if it has one. + Otherwise return defaultFont. + + \param defaultFont Default font + \sa setFont(), font(), PaintAttributes +*/ +QFont QwtText::usedFont(const QFont &defaultFont) const +{ + if ( d_data->paintAttributes & PaintUsingTextFont ) + return d_data->font; + + return defaultFont; +} + +/*! + Set the pen color used for painting the text. + + \param color Color + \note Setting the color might have no effect, when + the text contains control sequences for setting colors. +*/ +void QwtText::setColor(const QColor &color) +{ + d_data->color = color; + setPaintAttribute(PaintUsingTextColor); +} + +//! Return the pen color, used for painting the text +QColor QwtText::color() const +{ + return d_data->color; +} + +/*! + Return the color of the text, if it has one. + Otherwise return defaultColor. + + \param defaultColor Default color + \sa setColor(), color(), PaintAttributes +*/ +QColor QwtText::usedColor(const QColor &defaultColor) const +{ + if ( d_data->paintAttributes & PaintUsingTextColor ) + return d_data->color; + + return defaultColor; +} + +/*! + Set the background pen + + \param pen Background pen + \sa backgroundPen(), setBackgroundBrush() +*/ +void QwtText::setBackgroundPen(const QPen &pen) +{ + d_data->backgroundPen = pen; + setPaintAttribute(PaintBackground); +} + +/*! + \return Background pen + \sa setBackgroundPen(), backgroundBrush() +*/ +QPen QwtText::backgroundPen() const +{ + return d_data->backgroundPen; +} + +/*! + Set the background brush + + \param brush Background brush + \sa backgroundBrush(), setBackgroundPen() +*/ +void QwtText::setBackgroundBrush(const QBrush &brush) +{ + d_data->backgroundBrush = brush; + setPaintAttribute(PaintBackground); +} + +/*! + \return Background brush + \sa setBackgroundBrush(), backgroundPen() +*/ +QBrush QwtText::backgroundBrush() const +{ + return d_data->backgroundBrush; +} + +/*! + Change a paint attribute + + \param attribute Paint attribute + \param on On/Off + + \note Used by setFont(), setColor(), + setBackgroundPen() and setBackgroundBrush() + \sa testPaintAttribute() +*/ +void QwtText::setPaintAttribute(PaintAttribute attribute, bool on) +{ + if ( on ) + d_data->paintAttributes |= attribute; + else + d_data->paintAttributes &= ~attribute; +} + +/*! + Test a paint attribute + + \param attribute Paint attribute + \return true, if attribute is enabled + + \sa setPaintAttribute() +*/ +bool QwtText::testPaintAttribute(PaintAttribute attribute) const +{ + return d_data->paintAttributes & attribute; +} + +/*! + Change a layout attribute + + \param attribute Layout attribute + \param on On/Off + \sa testLayoutAttribute() +*/ +void QwtText::setLayoutAttribute(LayoutAttribute attribute, bool on) +{ + if ( on ) + d_data->layoutAttributes |= attribute; + else + d_data->layoutAttributes &= ~attribute; +} + +/*! + Test a layout attribute + + \param attribute Layout attribute + \return true, if attribute is enabled + + \sa setLayoutAttribute() +*/ +bool QwtText::testLayoutAttribute(LayoutAttribute attribute) const +{ + return d_data->layoutAttributes | attribute; +} + +/*! + Find the height for a given width + + \param defaultFont Font, used for the calculation if the text has no font + \param width Width + + \return Calculated height +*/ +int QwtText::heightForWidth(int width, const QFont &defaultFont) const +{ + const QwtMetricsMap map = QwtPainter::metricsMap(); + width = map.layoutToScreenX(width); + +#if QT_VERSION < 0x040000 + const QFont font = usedFont(defaultFont); +#else + // We want to calculate in screen metrics. So + // we need a font that uses screen metrics + + const QFont font(usedFont(defaultFont), QApplication::desktop()); +#endif + + int h = 0; + + if ( d_data->layoutAttributes & MinimumLayout ) + { + int left, right, top, bottom; + d_data->textEngine->textMargins(font, d_data->text, + left, right, top, bottom); + + h = d_data->textEngine->heightForWidth( + font, d_data->renderFlags, d_data->text, + width + left + right); + + h -= top + bottom; + } + else + { + h = d_data->textEngine->heightForWidth( + font, d_data->renderFlags, d_data->text, width); + } + + h = map.screenToLayoutY(h); + return h; +} + +/*! + Find the height for a given width + + \param defaultFont Font, used for the calculation if the text has no font + + \return Calculated height +*/ + +/*! + Returns the size, that is needed to render text + + \param defaultFont Font of the text + \return Caluclated size +*/ +QSize QwtText::textSize(const QFont &defaultFont) const +{ +#if QT_VERSION < 0x040000 + const QFont font(usedFont(defaultFont)); +#else + // We want to calculate in screen metrics. So + // we need a font that uses screen metrics + + const QFont font(usedFont(defaultFont), QApplication::desktop()); +#endif + + if ( !d_layoutCache->textSize.isValid() + || d_layoutCache->font != font ) + { + d_layoutCache->textSize = d_data->textEngine->textSize( + font, d_data->renderFlags, d_data->text); + d_layoutCache->font = font; + } + + QSize sz = d_layoutCache->textSize; + + const QwtMetricsMap map = QwtPainter::metricsMap(); + + if ( d_data->layoutAttributes & MinimumLayout ) + { + int left, right, top, bottom; + d_data->textEngine->textMargins(font, d_data->text, + left, right, top, bottom); + sz -= QSize(left + right, top + bottom); + +#if QT_VERSION >= 0x040000 + if ( !map.isIdentity() ) + { +#ifdef __GNUC__ +#warning Too small text size, when printing in high resolution +#endif + /* + When printing in high resolution, the tick labels + of are cut of. We need to find out why, but for + the moment we add a couple of pixels instead. + */ + sz += QSize(3, 2); + } +#endif + } + + sz = map.screenToLayout(sz); + return sz; +} + +/*! + Draw a text into a rectangle + + \param painter Painter + \param rect Rectangle +*/ +void QwtText::draw(QPainter *painter, const QRect &rect) const +{ + if ( d_data->paintAttributes & PaintBackground ) + { + if ( d_data->backgroundPen != Qt::NoPen || + d_data->backgroundBrush != Qt::NoBrush ) + { + painter->save(); + painter->setPen(QwtPainter::scaledPen(d_data->backgroundPen)); + painter->setBrush(d_data->backgroundBrush); +#if QT_VERSION < 0x040000 + QwtPainter::drawRect(painter, rect); +#else + const QRect r(rect.x(), rect.y(), + rect.width() - 1, rect.height() - 1); + QwtPainter::drawRect(painter, r); +#endif + painter->restore(); + } + } + + painter->save(); + + if ( d_data->paintAttributes & PaintUsingTextFont ) + { + painter->setFont(d_data->font); + } + + if ( d_data->paintAttributes & PaintUsingTextColor ) + { + if ( d_data->color.isValid() ) + painter->setPen(d_data->color); + } + + QRect expandedRect = rect; + if ( d_data->layoutAttributes & MinimumLayout ) + { +#if QT_VERSION < 0x040000 + const QFont font(painter->font()); +#else + // We want to calculate in screen metrics. So + // we need a font that uses screen metrics + + const QFont font(painter->font(), QApplication::desktop()); +#endif + + int left, right, top, bottom; + d_data->textEngine->textMargins( + font, d_data->text, + left, right, top, bottom); + + const QwtMetricsMap map = QwtPainter::metricsMap(); + left = map.screenToLayoutX(left); + right = map.screenToLayoutX(right); + top = map.screenToLayoutY(top); + bottom = map.screenToLayoutY(bottom); + + expandedRect.setTop(rect.top() - top); + expandedRect.setBottom(rect.bottom() + bottom); + expandedRect.setLeft(rect.left() - left); + expandedRect.setRight(rect.right() + right); + } + + d_data->textEngine->draw(painter, expandedRect, + d_data->renderFlags, d_data->text); + + painter->restore(); +} + +/*! + Find the text engine for a text format + + In case of QwtText::AutoText the first text engine + (beside QwtPlainTextEngine) is returned, where QwtTextEngine::mightRender + returns true. If there is none QwtPlainTextEngine is returnd. + + If no text engine is registered for the format QwtPlainTextEngine + is returnd. + + \param text Text, needed in case of AutoText + \param format Text format +*/ +const QwtTextEngine *QwtText::textEngine(const QString &text, + QwtText::TextFormat format) +{ + if ( engineDict == NULL ) + { + /* + Note: engineDict is allocated, the first time it is used, + but never deleted, because there is no known last access time. + So don't be irritated, if it is reported as a memory leak + from your memory profiler. + */ + engineDict = new QwtTextEngineDict(); + } + + return engineDict->textEngine(text, format); +} + +/*! + Assign/Replace a text engine for a text format + + With setTextEngine it is possible to extend Qwt with + other types of text formats. + + Owner of a commercial Qt license can build the qwtmathml library, + that is based on the MathML renderer, that is included in MML Widget + component of the Qt solutions package. + + For QwtText::PlainText it is not allowed to assign a engine == NULL. + + \param format Text format + \param engine Text engine + + \sa QwtMathMLTextEngine + \warning Using QwtText::AutoText does nothing. +*/ +void QwtText::setTextEngine(QwtText::TextFormat format, + QwtTextEngine *engine) +{ + if ( engineDict == NULL ) + engineDict = new QwtTextEngineDict(); + + engineDict->setTextEngine(format, engine); +} + +/*! + \brief Find the text engine for a text format + + textEngine can be used to find out if a text format is supported. + F.e, if one wants to use MathML labels, the MathML renderer from the + commercial Qt solutions package might be required, that is not + available in Qt Open Source Edition environments. + + \param format Text format + \return The text engine, or NULL if no engine is available. +*/ +const QwtTextEngine *QwtText::textEngine(QwtText::TextFormat format) +{ + if ( engineDict == NULL ) + engineDict = new QwtTextEngineDict(); + + return engineDict->textEngine(format); +} diff --git a/qwt/src/qwt_text.h b/qwt/src/qwt_text.h new file mode 100644 index 000000000..af93ff462 --- /dev/null +++ b/qwt/src/qwt_text.h @@ -0,0 +1,207 @@ +/* -*- 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 + *****************************************************************************/ + +// vim: expandtab + +#ifndef QWT_TEXT_H +#define QWT_TEXT_H + +#include +#include +#include +#include "qwt_global.h" + +class QColor; +class QPen; +class QBrush; +class QRect; +class QPainter; +class QwtTextEngine; + +/*! + \brief A class representing a text + + A QwtText is a text including a set of attributes how to render it. + + - Format\n + A text might include control sequences (f.e tags) describing + how to render it. Each format (f.e MathML, TeX, Qt Rich Text) + has its own set of control sequences, that can be handles by + a QwtTextEngine for this format. + - Background\n + A text might have a background, defined by a QPen and QBrush + to improve its visibility. + - Font\n + A text might have an individual font. + - Color\n + A text might have an individual color. + - Render Flags\n + Flags from Qt::AlignmentFlag and Qt::TextFlag used like in + QPainter::drawText. + + \sa QwtTextEngine, QwtTextLabel +*/ + +class QWT_EXPORT QwtText +{ +public: + + /*! + \brief Text format + + The text format defines the QwtTextEngine, that is used to render + the text. + + - AutoText\n + The text format is determined using QwtTextEngine::mightRender for + all available text engines in increasing order > PlainText. + If none of the text engines can render the text is rendered + like PlainText. + - PlainText\n + Draw the text as it is, using a QwtPlainTextEngine. + - RichText\n + Use the Scribe framework (Qt Rich Text) to render the text. + - MathMLText\n + Use a MathML (http://en.wikipedia.org/wiki/MathML) render engine + to display the text. The Qwt MathML extension offers such an engine + based on the MathML renderer of the Qt solutions package. Unfortunately + it is only available for owners of a commercial Qt license. + - TeXText\n + Use a TeX (http://en.wikipedia.org/wiki/TeX) render engine + to display the text. + - OtherFormat\n + The number of text formats can be extended using setTextEngine. + Formats >= OtherFormat are not used by Qwt. + + \sa QwtTextEngine, setTextEngine() + */ + + enum TextFormat + { + AutoText = 0, + + PlainText, + RichText, + + MathMLText, + TeXText, + + OtherFormat = 100 + }; + + /*! + \brief Paint Attributes + + Font and color and background are optional attributes of a QwtText. + The paint attributes hold the information, if they are set. + + - PaintUsingTextFont\n + The text has an individual font. + - PaintUsingTextColor\n + The text has an individual color. + - PaintBackground\n + The text has an individual background. + */ + enum PaintAttribute + { + PaintUsingTextFont = 1, + PaintUsingTextColor = 2, + PaintBackground = 4 + }; + + /*! + \brief Layout Attributes + + The layout attributes affects some aspects of the layout of the text. + + - MinimumLayout\n + Layout the text without its margins. This mode is useful if a + text needs to be aligned accurately, like the tick labels of a scale. + If QwtTextEngine::textMargins is not implemented for the format + of the text, MinimumLayout has no effect. + */ + enum LayoutAttribute + { + MinimumLayout = 1 + }; + + QwtText(const QString & = QString::null, + TextFormat textFormat = AutoText); + QwtText(const QwtText &); + ~QwtText(); + + QwtText &operator=(const QwtText &); + + int operator==(const QwtText &) const; + int operator!=(const QwtText &) const; + + void setText(const QString &, + QwtText::TextFormat textFormat = AutoText); + QString text() const; + + bool isNull() const; + bool isEmpty() const; + + void setFont(const QFont &); + QFont font() const; + + QFont usedFont(const QFont &) const; + + void setRenderFlags(int flags); + int renderFlags() const; + + void setColor(const QColor &); + QColor color() const; + + QColor usedColor(const QColor &) const; + + void setBackgroundPen(const QPen &); + QPen backgroundPen() const; + + void setBackgroundBrush(const QBrush &); + QBrush backgroundBrush() const; + + void setPaintAttribute(PaintAttribute, bool on = true); + bool testPaintAttribute(PaintAttribute) const; + + void setLayoutAttribute(LayoutAttribute, bool on = true); + bool testLayoutAttribute(LayoutAttribute) const; + + int heightForWidth(int width, const QFont & = QFont()) const; + QSize textSize(const QFont & = QFont()) const; + + void draw(QPainter *painter, const QRect &rect) const; + + static const QwtTextEngine *textEngine(const QString &text, + QwtText::TextFormat = AutoText); + + static const QwtTextEngine *textEngine(QwtText::TextFormat); + static void setTextEngine(QwtText::TextFormat, QwtTextEngine *); + +private: + class PrivateData; + PrivateData *d_data; + + class LayoutCache; + LayoutCache *d_layoutCache; +}; + +//! \return text().isNull() +inline bool QwtText::isNull() const +{ + return text().isNull(); +} + +//! \return text().isEmpty() +inline bool QwtText::isEmpty() const +{ + return text().isEmpty(); +} + +#endif diff --git a/qwt/src/qwt_text_engine.cpp b/qwt/src/qwt_text_engine.cpp new file mode 100644 index 000000000..4ad56e2ec --- /dev/null +++ b/qwt/src/qwt_text_engine.cpp @@ -0,0 +1,436 @@ +/* -*- 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 + *****************************************************************************/ + +// vim: expandtab + +#include +#include +#include +#include +#include +#include "qwt_math.h" +#include "qwt_painter.h" +#include "qwt_text_engine.h" + +static QString taggedRichText(const QString &text, int flags) +{ + QString richText = text; + + // By default QSimpleRichText is Qt::AlignLeft + if (flags & Qt::AlignJustify) + { + richText.prepend(QString::fromLatin1("
")); + richText.append(QString::fromLatin1("
")); + } + else if (flags & Qt::AlignRight) + { + richText.prepend(QString::fromLatin1("
")); + richText.append(QString::fromLatin1("
")); + } + else if (flags & Qt::AlignHCenter) + { + richText.prepend(QString::fromLatin1("
")); + richText.append(QString::fromLatin1("
")); + } + + return richText; +} + +#if QT_VERSION < 0x040000 + +#include +#include + +class QwtRichTextDocument: public QSimpleRichText +{ +public: + QwtRichTextDocument(const QString &text, int flags, const QFont &font): + QSimpleRichText(taggedRichText(text, flags), font) + { + } +}; + +#else // QT_VERSION >= 0x040000 + +#include +#include +#include + +#if QT_VERSION < 0x040200 +#include +#endif + +class QwtRichTextDocument: public QTextDocument +{ +public: + QwtRichTextDocument(const QString &text, int flags, const QFont &font) + { + setUndoRedoEnabled(false); + setDefaultFont(font); +#if QT_VERSION >= 0x040300 + setHtml(text); +#else + setHtml(taggedRichText(text, flags)); +#endif + + // make sure we have a document layout + (void)documentLayout(); + +#if QT_VERSION >= 0x040300 + QTextOption option = defaultTextOption(); + if ( flags & Qt::TextWordWrap ) + option.setWrapMode(QTextOption::WordWrap); + else + option.setWrapMode(QTextOption::NoWrap); + + option.setAlignment((Qt::Alignment) flags); + setDefaultTextOption(option); + + QTextFrame *root = rootFrame(); + QTextFrameFormat fm = root->frameFormat(); + fm.setBorder(0); + fm.setMargin(0); + fm.setPadding(0); + fm.setBottomMargin(0); + fm.setLeftMargin(0); + root->setFrameFormat(fm); + + adjustSize(); +#endif + } +}; + +#endif + +class QwtPlainTextEngine::PrivateData +{ +public: + int effectiveAscent(const QFont &font) const + { + const QString fontKey = font.key(); + + QMap::const_iterator it = + d_ascentCache.find(fontKey); + if ( it == d_ascentCache.end() ) + { + int ascent = findAscent(font); + it = d_ascentCache.insert(fontKey, ascent); + } + + return (*it); + } + +private: + int findAscent(const QFont &font) const + { + static const QString dummy("E"); + static const QColor white(Qt::white); + + const QFontMetrics fm(font); + QPixmap pm(fm.width(dummy), fm.height()); + pm.fill(white); + + QPainter p(&pm); + p.setFont(font); + p.drawText(0, 0, pm.width(), pm.height(), 0, dummy); + p.end(); + +#if QT_VERSION < 0x040000 + const QImage img = pm.convertToImage(); +#else + const QImage img = pm.toImage(); +#endif + + int row = 0; + for ( row = 0; row < img.height(); row++ ) + { + const QRgb *line = (const QRgb *)img.scanLine(row); + + const int w = pm.width(); + for ( int col = 0; col < w; col++ ) + { + if ( line[col] != white.rgb() ) + return fm.ascent() - row + 1; + } + } + + return fm.ascent(); + } + + mutable QMap d_ascentCache; +}; + +//! Constructor +QwtTextEngine::QwtTextEngine() +{ +} + +//! Destructor +QwtTextEngine::~QwtTextEngine() +{ +} + +//! Constructor +QwtPlainTextEngine::QwtPlainTextEngine() +{ + d_data = new PrivateData; +} + +//! Destructor +QwtPlainTextEngine::~QwtPlainTextEngine() +{ + delete d_data; +} + +/*! + Find the height for a given width + + \param font Font of the text + \param flags Bitwise OR of the flags used like in QPainter::drawText + \param text Text to be rendered + \param width Width + + \return Calculated height +*/ +int QwtPlainTextEngine::heightForWidth(const QFont& font, int flags, + const QString& text, int width) const +{ + const QFontMetrics fm(font); + const QRect rect = fm.boundingRect( + 0, 0, width, QWIDGETSIZE_MAX, flags, text); + + return rect.height(); +} + +/*! + Returns the size, that is needed to render text + + \param font Font of the text + \param flags Bitwise OR of the flags used like in QPainter::drawText + \param text Text to be rendered + + \return Caluclated size +*/ +QSize QwtPlainTextEngine::textSize(const QFont &font, + int flags, const QString& text) const +{ + const QFontMetrics fm(font); + const QRect rect = fm.boundingRect( + 0, 0, QWIDGETSIZE_MAX, QWIDGETSIZE_MAX, flags, text); + + return rect.size(); +} + +/*! + Return margins around the texts + + \param font Font of the text + \param left Return 0 + \param right Return 0 + \param top Return value for the top margin + \param bottom Return value for the bottom margin +*/ +void QwtPlainTextEngine::textMargins(const QFont &font, const QString &, + int &left, int &right, int &top, int &bottom) const +{ + left = right = top = 0; + + const QFontMetrics fm(font); + top = fm.ascent() - d_data->effectiveAscent(font); + bottom = fm.descent() + 1; +} + +/*! + \brief Draw the text in a clipping rectangle + + A wrapper for QPainter::drawText. + + \param painter Painter + \param rect Clipping rectangle + \param flags Bitwise OR of the flags used like in QPainter::drawText + \param text Text to be rendered +*/ +void QwtPlainTextEngine::draw(QPainter *painter, const QRect &rect, + int flags, const QString& text) const +{ + QwtPainter::drawText(painter, rect, flags, text); +} + +/*! + Test if a string can be rendered by this text engine. + \return Always true. All texts can be rendered by QwtPlainTextEngine +*/ +bool QwtPlainTextEngine::mightRender(const QString &) const +{ + return true; +} + +#ifndef QT_NO_RICHTEXT + +//! Constructor +QwtRichTextEngine::QwtRichTextEngine() +{ +} + +/*! + Find the height for a given width + + \param font Font of the text + \param flags Bitwise OR of the flags used like in QPainter::drawText + \param text Text to be rendered + \param width Width + + \return Calculated height +*/ +int QwtRichTextEngine::heightForWidth(const QFont& font, int flags, + const QString& text, int width) const +{ + QwtRichTextDocument doc(text, flags, font); + +#if QT_VERSION < 0x040000 + doc.setWidth(width); + const int h = doc.height(); +#else + doc.setPageSize(QSize(width, QWIDGETSIZE_MAX)); + const int h = qRound(doc.documentLayout()->documentSize().height()); +#endif + return h; +} + +/*! + Returns the size, that is needed to render text + + \param font Font of the text + \param flags Bitwise OR of the flags used like in QPainter::drawText + \param text Text to be rendered + + \return Caluclated size +*/ + +QSize QwtRichTextEngine::textSize(const QFont &font, + int flags, const QString& text) const +{ + QwtRichTextDocument doc(text, flags, font); + +#if QT_VERSION < 0x040000 + doc.setWidth(QWIDGETSIZE_MAX); + + const int w = doc.widthUsed(); + const int h = doc.height(); + return QSize(w, h); + +#else // QT_VERSION >= 0x040000 + +#if QT_VERSION < 0x040200 + /* + Unfortunately offering the bounding rect calculation in the + API of QTextDocument has been forgotten in Qt <= 4.1.x. It + is planned to come with Qt 4.2.x. + In the meantime we need a hack with a temporary QLabel, + to reengineer the internal calculations. + */ + + static int off = 0; + static QLabel *label = NULL; + if ( label == NULL ) + { + label = new QLabel; + label->hide(); + + const char *s = "XXXXX"; + label->setText(s); + int w1 = label->sizeHint().width(); + const QFontMetrics fm(label->font()); + int w2 = fm.width(s); + off = w1 - w2; + } + label->setFont(doc.defaultFont()); + label->setText(text); + + int w = qwtMax(label->sizeHint().width() - off, 0); + doc.setPageSize(QSize(w, QWIDGETSIZE_MAX)); + + int h = qRound(doc.documentLayout()->documentSize().height()); + return QSize(w, h); + +#else // QT_VERSION >= 0x040200 + +#if QT_VERSION >= 0x040300 + QTextOption option = doc.defaultTextOption(); + if ( option.wrapMode() != QTextOption::NoWrap ) + { + option.setWrapMode(QTextOption::NoWrap); + doc.setDefaultTextOption(option); + doc.adjustSize(); + } +#endif + + return doc.size().toSize(); +#endif +#endif +} + +/*! + Draw the text in a clipping rectangle + + \param painter Painter + \param rect Clipping rectangle + \param flags Bitwise OR of the flags like in for QPainter::drawText + \param text Text to be rendered +*/ +void QwtRichTextEngine::draw(QPainter *painter, const QRect &rect, + int flags, const QString& text) const +{ + QwtRichTextDocument doc(text, flags, painter->font()); + QwtPainter::drawSimpleRichText(painter, rect, flags, doc); +} + +/*! + Wrap text into
tags according flags + + \param text Text + \param flags Bitwise OR of the flags like in for QPainter::drawText + + \return Tagged text +*/ +QString QwtRichTextEngine::taggedText(const QString &text, int flags) const +{ + return taggedRichText(text, flags); +} + +/*! + Test if a string can be rendered by this text engine + + \param text Text to be tested + \return QStyleSheet::mightBeRichText(text); +*/ +bool QwtRichTextEngine::mightRender(const QString &text) const +{ +#if QT_VERSION < 0x040000 + return QStyleSheet::mightBeRichText(text); +#else + return Qt::mightBeRichText(text); +#endif +} + +/*! + Return margins around the texts + + \param left Return 0 + \param right Return 0 + \param top Return 0 + \param bottom Return 0 +*/ +void QwtRichTextEngine::textMargins(const QFont &, const QString &, + int &left, int &right, int &top, int &bottom) const +{ + left = right = top = bottom = 0; +} + +#endif // !QT_NO_RICHTEXT diff --git a/qwt/src/qwt_text_engine.h b/qwt/src/qwt_text_engine.h new file mode 100644 index 000000000..9f7d00da6 --- /dev/null +++ b/qwt/src/qwt_text_engine.h @@ -0,0 +1,174 @@ +/* -*- 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 + *****************************************************************************/ + +// vim: expandtab + +#ifndef QWT_TEXT_ENGINE_H +#define QWT_TEXT_ENGINE_H 1 + +#include +#include "qwt_global.h" + +class QFont; +class QRect; +class QString; +class QPainter; + +/*! + \brief Abstract base class for rendering text strings + + A text engine is responsible for rendering texts for a + specific text format. They are used by QwtText to render a text. + + QwtPlainTextEngine and QwtRichTextEngine are part of the Qwt library. + + QwtMathMLTextEngine can be found in Qwt MathML extension, that + needs the MathML renderer of the Qt solutions package. Unfortunately + it is only available with a commercial Qt license. + + \sa QwtText::setTextEngine() +*/ + +class QWT_EXPORT QwtTextEngine +{ +public: + virtual ~QwtTextEngine(); + + /*! + Find the height for a given width + + \param font Font of the text + \param flags Bitwise OR of the flags used like in QPainter::drawText + \param text Text to be rendered + \param width Width + + \return Calculated height + */ + virtual int heightForWidth(const QFont &font, int flags, + const QString &text, int width) const = 0; + + /*! + Returns the size, that is needed to render text + + \param font Font of the text + \param flags Bitwise OR of the flags like in for QPainter::drawText + \param text Text to be rendered + + \return Caluclated size + */ + virtual QSize textSize(const QFont &font, int flags, + const QString &text) const = 0; + + /*! + Test if a string can be rendered by this text engine + + \param text Text to be tested + \return true, if it can be rendered + */ + virtual bool mightRender(const QString &text) const = 0; + + /*! + Return margins around the texts + + The textSize might include margins around the + text, like QFontMetrics::descent. In situations + where texts need to be aligend in detail, knowing + these margins might improve the layout calculations. + + \param font Font of the text + \param text Text to be rendered + \param left Return value for the left margin + \param right Return value for the right margin + \param top Return value for the top margin + \param bottom Return value for the bottom margin + */ + virtual void textMargins(const QFont &font, const QString &text, + int &left, int &right, int &top, int &bottom) const = 0; + + /*! + Draw the text in a clipping rectangle + + \param painter Painter + \param rect Clipping rectangle + \param flags Bitwise OR of the flags like in for QPainter::drawText + \param text Text to be rendered + */ + virtual void draw(QPainter *painter, const QRect &rect, + int flags, const QString &text) const = 0; + +protected: + QwtTextEngine(); +}; + + +/*! + \brief A text engine for plain texts + + QwtPlainTextEngine renders texts using the basic Qt classes + QPainter and QFontMetrics. +*/ +class QWT_EXPORT QwtPlainTextEngine: public QwtTextEngine +{ +public: + QwtPlainTextEngine(); + virtual ~QwtPlainTextEngine(); + + virtual int heightForWidth(const QFont &font, int flags, + const QString &text, int width) const; + + virtual QSize textSize(const QFont &font, int flags, + const QString &text) const; + + virtual void draw(QPainter *painter, const QRect &rect, + int flags, const QString &text) const; + + virtual bool mightRender(const QString &) const; + + virtual void textMargins(const QFont &, const QString &, + int &left, int &right, int &top, int &bottom) const; + +private: + class PrivateData; + PrivateData *d_data; +}; + + +#ifndef QT_NO_RICHTEXT + +/*! + \brief A text engine for Qt rich texts + + QwtRichTextEngine renders Qt rich texts using the classes + of the Scribe framework of Qt. +*/ +class QWT_EXPORT QwtRichTextEngine: public QwtTextEngine +{ +public: + QwtRichTextEngine(); + + virtual int heightForWidth(const QFont &font, int flags, + const QString &text, int width) const; + + virtual QSize textSize(const QFont &font, int flags, + const QString &text) const; + + virtual void draw(QPainter *painter, const QRect &rect, + int flags, const QString &text) const; + + virtual bool mightRender(const QString &) const; + + virtual void textMargins(const QFont &, const QString &, + int &left, int &right, int &top, int &bottom) const; +private: + QString taggedText(const QString &, int flags) const; +}; + +#endif // !QT_NO_RICHTEXT + +#endif diff --git a/qwt/src/qwt_text_label.cpp b/qwt/src/qwt_text_label.cpp new file mode 100644 index 000000000..278604a46 --- /dev/null +++ b/qwt/src/qwt_text_label.cpp @@ -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 + *****************************************************************************/ + +// vim: expandtab + +#include +#include +#include "qwt_text.h" +#include "qwt_painter.h" +#include "qwt_text_label.h" + +class QwtTextLabel::PrivateData +{ +public: + PrivateData(): + indent(4), + margin(0) + { + } + + int indent; + int margin; + QwtText text; +}; + +/*! + Constructs an empty label. + \param parent Parent widget +*/ +QwtTextLabel::QwtTextLabel(QWidget *parent): + QFrame(parent) +{ + init(); +} + +#if QT_VERSION < 0x040000 +/*! + Constructs an empty label. + \param parent Parent widget + \param name Object name +*/ +QwtTextLabel::QwtTextLabel(QWidget *parent, const char *name): + QFrame(parent, name) +{ + init(); +} +#endif + +/*! + Constructs a label that displays the text, text + \param parent Parent widget + \param text Text +*/ +QwtTextLabel::QwtTextLabel(const QwtText &text, QWidget *parent): + QFrame(parent) +{ + init(); + d_data->text = text; +} + +//! Destructor +QwtTextLabel::~QwtTextLabel() +{ + delete d_data; +} + +void QwtTextLabel::init() +{ + d_data = new PrivateData(); + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); +} + +/*! + Change the label's text, keeping all other QwtText attributes + \param text New text + \param textFormat Format of text + + \sa QwtText +*/ +void QwtTextLabel::setText(const QString &text, QwtText::TextFormat textFormat) +{ + d_data->text.setText(text, textFormat); + + update(); + updateGeometry(); +} + +/*! + Change the label's text + \param text New text +*/ +void QwtTextLabel::setText(const QwtText &text) +{ + d_data->text = text; + + update(); + updateGeometry(); +} + +//! Return the text +const QwtText &QwtTextLabel::text() const +{ + return d_data->text; +} + +//! Clear the text and all QwtText attributes +void QwtTextLabel::clear() +{ + d_data->text = QwtText(); + + update(); + updateGeometry(); +} + +//! Return label's text indent in pixels +int QwtTextLabel::indent() const +{ + return d_data->indent; +} + +/*! + Set label's text indent in pixels + \param indent Indentation in pixels +*/ +void QwtTextLabel::setIndent(int indent) +{ + if ( indent < 0 ) + indent = 0; + + d_data->indent = indent; + + update(); + updateGeometry(); +} + +//! Return label's text indent in pixels +int QwtTextLabel::margin() const +{ + return d_data->margin; +} + +/*! + Set label's margin in pixels + \param margin Margin in pixels +*/ +void QwtTextLabel::setMargin(int margin) +{ + d_data->margin = margin; + + update(); + updateGeometry(); +} + +//! Return label's margin in pixels +QSize QwtTextLabel::sizeHint() const +{ + return minimumSizeHint(); +} + +//! Return a minimum size hint +QSize QwtTextLabel::minimumSizeHint() const +{ + QSize sz = d_data->text.textSize(font()); + + int mw = 2 * (frameWidth() + d_data->margin); + int mh = mw; + + int indent = d_data->indent; + if ( indent <= 0 ) + indent = defaultIndent(); + + if ( indent > 0 ) + { + const int align = d_data->text.renderFlags(); + if ( align & Qt::AlignLeft || align & Qt::AlignRight ) + mw += d_data->indent; + else if ( align & Qt::AlignTop || align & Qt::AlignBottom ) + mh += d_data->indent; + } + + sz += QSize(mw, mh); + + return sz; +} + +/*! + Returns the preferred height for this widget, given the width. + \param width Width +*/ +int QwtTextLabel::heightForWidth(int width) const +{ + const int renderFlags = d_data->text.renderFlags(); + + int indent = d_data->indent; + if ( indent <= 0 ) + indent = defaultIndent(); + + width -= 2 * frameWidth(); + if ( renderFlags & Qt::AlignLeft || renderFlags & Qt::AlignRight ) + width -= indent; + + int height = d_data->text.heightForWidth(width, font()); + if ( renderFlags & Qt::AlignTop || renderFlags & Qt::AlignBottom ) + height += indent; + + height += 2 * frameWidth(); + + return height; +} + +/*! + Qt paint event + \param event Paint event +*/ +void QwtTextLabel::paintEvent(QPaintEvent *event) +{ +#if QT_VERSION >= 0x040000 + QPainter painter(this); + + if ( !contentsRect().contains( event->rect() ) ) + { + painter.save(); + painter.setClipRegion( event->region() & frameRect() ); + drawFrame( &painter ); + painter.restore(); + } + + painter.setClipRegion(event->region() & contentsRect()); + + drawContents( &painter ); +#else // QT_VERSION < 0x040000 + QFrame::paintEvent(event); +#endif + +} + +//! Redraw the text and focus indicator +void QwtTextLabel::drawContents(QPainter *painter) +{ + const QRect r = textRect(); + if ( r.isEmpty() ) + return; + + painter->setFont(font()); +#if QT_VERSION < 0x040000 + painter->setPen(palette().color(QPalette::Active, QColorGroup::Text)); +#else + painter->setPen(palette().color(QPalette::Active, QPalette::Text)); +#endif + + drawText(painter, r); + + if ( hasFocus() ) + { + const int margin = 2; + + QRect focusRect = contentsRect(); + focusRect.setRect(focusRect.x() + margin, focusRect.y() + margin, + focusRect.width() - 2 * margin - 2, + focusRect.height() - 2 * margin - 2); + + QwtPainter::drawFocusRect(painter, this, focusRect); + } +} + +//! Redraw the text +void QwtTextLabel::drawText(QPainter *painter, const QRect &textRect) +{ + d_data->text.draw(painter, textRect); +} + +/*! + Calculate the rect for the text in widget coordinates + \return Text rect +*/ +QRect QwtTextLabel::textRect() const +{ + QRect r = contentsRect(); + + if ( !r.isEmpty() && d_data->margin > 0 ) + { + r.setRect(r.x() + d_data->margin, r.y() + d_data->margin, + r.width() - 2 * d_data->margin, r.height() - 2 * d_data->margin ); + } + + if ( !r.isEmpty() ) + { + int indent = d_data->indent; + if ( indent <= 0 ) + indent = defaultIndent(); + + if ( indent > 0 ) + { + const int renderFlags = d_data->text.renderFlags(); + + if ( renderFlags & Qt::AlignLeft ) + r.setX(r.x() + indent); + else if ( renderFlags & Qt::AlignRight ) + r.setWidth(r.width() - indent); + else if ( renderFlags & Qt::AlignTop ) + r.setY(r.y() + indent); + else if ( renderFlags & Qt::AlignBottom ) + r.setHeight(r.height() - indent); + } + } + + return r; +} + +int QwtTextLabel::defaultIndent() const +{ + if ( frameWidth() <= 0 ) + return 0; + + QFont fnt; + if ( d_data->text.testPaintAttribute(QwtText::PaintUsingTextFont) ) + fnt = d_data->text.font(); + else + fnt = font(); + + return QFontMetrics(fnt).width('x') / 2; +} + diff --git a/qwt/src/qwt_text_label.h b/qwt/src/qwt_text_label.h new file mode 100644 index 000000000..acaaedd45 --- /dev/null +++ b/qwt/src/qwt_text_label.h @@ -0,0 +1,75 @@ +/* -*- 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_TEXT_LABEL_H +#define QWT_TEXT_LABEL_H + +#include +#include "qwt_global.h" +#include "qwt_text.h" + +class QString; +class QPaintEvent; +class QPainter; + +/*! + \brief A Widget which displays a QwtText +*/ + +class QWT_EXPORT QwtTextLabel : public QFrame +{ + Q_OBJECT + + Q_PROPERTY( int indent READ indent WRITE setIndent ) + Q_PROPERTY( int margin READ margin WRITE setMargin ) + +public: + explicit QwtTextLabel(QWidget *parent = NULL); +#if QT_VERSION < 0x040000 + explicit QwtTextLabel(QWidget *parent, const char *name); +#endif + explicit QwtTextLabel(const QwtText &, QWidget *parent = NULL); + virtual ~QwtTextLabel(); + +public slots: + void setText(const QString &, + QwtText::TextFormat textFormat = QwtText::AutoText); + virtual void setText(const QwtText &); + + void clear(); + +public: + const QwtText &text() const; + + int indent() const; + void setIndent(int); + + int margin() const; + void setMargin(int); + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + virtual int heightForWidth(int) const; + + QRect textRect() const; + +protected: + virtual void paintEvent(QPaintEvent *e); + virtual void drawContents(QPainter *); + virtual void drawText(QPainter *, const QRect &); + +private: + void init(); + int defaultIndent() const; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_thermo.cpp b/qwt/src/qwt_thermo.cpp new file mode 100644 index 000000000..ddc0d72f0 --- /dev/null +++ b/qwt/src/qwt_thermo.cpp @@ -0,0 +1,919 @@ +/* -*- 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 +#include "qwt_math.h" +#include "qwt_scale_engine.h" +#include "qwt_scale_draw.h" +#include "qwt_scale_map.h" +#include "qwt_paint_buffer.h" +#include "qwt_thermo.h" + +class QwtThermo::PrivateData +{ +public: + PrivateData(): + fillBrush(Qt::black), + alarmBrush(Qt::white), + orientation(Qt::Vertical), + scalePos(QwtThermo::LeftScale), + borderWidth(2), + scaleDist(3), + thermoWidth(10), + minValue(0.0), + maxValue(1.0), + value(0.0), + alarmLevel(0.0), + alarmEnabled(false) + { + map.setScaleInterval(minValue, maxValue); + } + + QwtScaleMap map; + QRect thermoRect; + QBrush fillBrush; + QBrush alarmBrush; + + Qt::Orientation orientation; + ScalePos scalePos; + int borderWidth; + int scaleDist; + int thermoWidth; + + double minValue; + double maxValue; + double value; + double alarmLevel; + bool alarmEnabled; +}; + +/*! + Constructor + \param parent Parent widget +*/ +QwtThermo::QwtThermo(QWidget *parent): + QWidget(parent) +{ + initThermo(); +} + +#if QT_VERSION < 0x040000 +/*! + Constructor + \param parent Parent widget + \param name Object name +*/ +QwtThermo::QwtThermo(QWidget *parent, const char *name): + QWidget(parent, name) +{ + initThermo(); +} +#endif + +void QwtThermo::initThermo() +{ +#if QT_VERSION < 0x040000 + setWFlags(Qt::WNoAutoErase); +#endif + d_data = new PrivateData; + setRange(d_data->minValue, d_data->maxValue, false); + + QSizePolicy policy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); + if (d_data->orientation == Qt::Vertical) + policy.transpose(); + + setSizePolicy(policy); + +#if QT_VERSION >= 0x040000 + setAttribute(Qt::WA_WState_OwnSizePolicy, false); +#else + clearWState( WState_OwnSizePolicy ); +#endif +} + +//! Destructor +QwtThermo::~QwtThermo() +{ + delete d_data; +} + +/*! + Set the maximum value. + + \param max Maximum value + \sa maxValue(), setMinValue() +*/ +void QwtThermo::setMaxValue(double max) +{ + setRange(d_data->minValue, max); +} + +//! Return the maximum value. +double QwtThermo::maxValue() const +{ + return d_data->maxValue; +} + +/*! + Set the minimum value. + + \param min Minimum value + \sa minValue(), setMaxValue() +*/ +void QwtThermo::setMinValue(double min) +{ + setRange(min, d_data->maxValue); +} + +//! Return the minimum value. +double QwtThermo::minValue() const +{ + return d_data->minValue; +} + +/*! + Set the current value. + + \param value New Value + \sa value() +*/ +void QwtThermo::setValue(double value) +{ + if (d_data->value != value) + { + d_data->value = value; + update(); + } +} + +//! Return the value. +double QwtThermo::value() const +{ + return d_data->value; +} + +/*! + \brief Set a scale draw + + For changing the labels of the scales, it + is necessary to derive from QwtScaleDraw and + overload QwtScaleDraw::label(). + + \param scaleDraw ScaleDraw object, that has to be created with + new and will be deleted in ~QwtThermo or the next + call of setScaleDraw(). +*/ +void QwtThermo::setScaleDraw(QwtScaleDraw *scaleDraw) +{ + setAbstractScaleDraw(scaleDraw); +} + +/*! + \return the scale draw of the thermo + \sa setScaleDraw() +*/ +const QwtScaleDraw *QwtThermo::scaleDraw() const +{ + return (QwtScaleDraw *)abstractScaleDraw(); +} + +/*! + \return the scale draw of the thermo + \sa setScaleDraw() +*/ +QwtScaleDraw *QwtThermo::scaleDraw() +{ + return (QwtScaleDraw *)abstractScaleDraw(); +} + +/*! + Qt paint event. + event Paint event +*/ +void QwtThermo::paintEvent(QPaintEvent *event) +{ + // Use double-buffering + const QRect &ur = event->rect(); + if ( ur.isValid() ) + { +#if QT_VERSION < 0x040000 + QwtPaintBuffer paintBuffer(this, ur); + draw(paintBuffer.painter(), ur); +#else + QPainter painter(this); + draw(&painter, ur); +#endif + } +} + +/*! + Draw the whole QwtThermo. + + \param painter Painter + \param rect Update rectangle +*/ +void QwtThermo::draw(QPainter *painter, const QRect& rect) +{ + if ( !d_data->thermoRect.contains(rect) ) + { + if (d_data->scalePos != NoScale) + { +#if QT_VERSION < 0x040000 + scaleDraw()->draw(painter, colorGroup()); +#else + scaleDraw()->draw(painter, palette()); +#endif + } + + qDrawShadePanel(painter, + d_data->thermoRect.x() - d_data->borderWidth, + d_data->thermoRect.y() - d_data->borderWidth, + d_data->thermoRect.width() + 2 * d_data->borderWidth, + d_data->thermoRect.height() + 2 * d_data->borderWidth, +#if QT_VERSION < 0x040000 + colorGroup(), +#else + palette(), +#endif + true, d_data->borderWidth, 0); + } + drawThermo(painter); +} + +//! Qt resize event handler +void QwtThermo::resizeEvent(QResizeEvent *) +{ + layoutThermo( false ); +} + +/*! + Recalculate the QwtThermo geometry and layout based on + the QwtThermo::rect() and the fonts. + + \param update_geometry notify the layout system and call update + to redraw the scale +*/ +void QwtThermo::layoutThermo( bool update_geometry ) +{ + QRect r = rect(); + int mbd = 0; + if ( d_data->scalePos != NoScale ) + { + int d1, d2; + scaleDraw()->getBorderDistHint(font(), d1, d2); + mbd = qwtMax(d1, d2); + } + + if ( d_data->orientation == Qt::Horizontal ) + { + switch ( d_data->scalePos ) + { + case TopScale: + { + d_data->thermoRect.setRect( + r.x() + mbd + d_data->borderWidth, + r.y() + r.height() + - d_data->thermoWidth - 2*d_data->borderWidth, + r.width() - 2*(d_data->borderWidth + mbd), + d_data->thermoWidth); + scaleDraw()->setAlignment(QwtScaleDraw::TopScale); + scaleDraw()->move( d_data->thermoRect.x(), + d_data->thermoRect.y() - d_data->borderWidth + - d_data->scaleDist); + scaleDraw()->setLength(d_data->thermoRect.width()); + break; + } + + case BottomScale: + case NoScale: // like Bottom but without scale + default: // inconsistent orientation and scale position + // Mapping between values and pixels requires + // initialization of the scale geometry + { + d_data->thermoRect.setRect( + r.x() + mbd + d_data->borderWidth, + r.y() + d_data->borderWidth, + r.width() - 2*(d_data->borderWidth + mbd), + d_data->thermoWidth); + scaleDraw()->setAlignment(QwtScaleDraw::BottomScale); + scaleDraw()->move( + d_data->thermoRect.x(), + d_data->thermoRect.y() + d_data->thermoRect.height() + + d_data->borderWidth + d_data->scaleDist ); + scaleDraw()->setLength(d_data->thermoRect.width()); + break; + } + } + d_data->map.setPaintInterval(d_data->thermoRect.x(), + d_data->thermoRect.x() + d_data->thermoRect.width() - 1); + } + else // Qt::Vertical + { + switch ( d_data->scalePos ) + { + case RightScale: + { + d_data->thermoRect.setRect( + r.x() + d_data->borderWidth, + r.y() + mbd + d_data->borderWidth, + d_data->thermoWidth, + r.height() - 2*(d_data->borderWidth + mbd)); + scaleDraw()->setAlignment(QwtScaleDraw::RightScale); + scaleDraw()->move( + d_data->thermoRect.x() + d_data->thermoRect.width() + + d_data->borderWidth + d_data->scaleDist, + d_data->thermoRect.y()); + scaleDraw()->setLength(d_data->thermoRect.height()); + break; + } + + case LeftScale: + case NoScale: // like Left but without scale + default: // inconsistent orientation and scale position + // Mapping between values and pixels requires + // initialization of the scale geometry + { + d_data->thermoRect.setRect( + r.x() + r.width() - 2*d_data->borderWidth - d_data->thermoWidth, + r.y() + mbd + d_data->borderWidth, + d_data->thermoWidth, + r.height() - 2*(d_data->borderWidth + mbd)); + scaleDraw()->setAlignment(QwtScaleDraw::LeftScale); + scaleDraw()->move( + d_data->thermoRect.x() - d_data->scaleDist + - d_data->borderWidth, + d_data->thermoRect.y() ); + scaleDraw()->setLength(d_data->thermoRect.height()); + break; + } + } + d_data->map.setPaintInterval( + d_data->thermoRect.y() + d_data->thermoRect.height() - 1, + d_data->thermoRect.y()); + } + if ( update_geometry ) + { + updateGeometry(); + update(); + } +} + +/*! + \brief Set the thermometer orientation and the scale position. + + The scale position NoScale disables the scale. + \param o orientation. Possible values are Qt::Horizontal and Qt::Vertical. + The default value is Qt::Vertical. + \param s Position of the scale. + The default value is NoScale. + + A valid combination of scale position and orientation is enforced: + - a horizontal thermometer can have the scale positions TopScale, + BottomScale or NoScale; + - a vertical thermometer can have the scale positions LeftScale, + RightScale or NoScale; + - an invalid scale position will default to NoScale. + + \sa setScalePosition() +*/ +void QwtThermo::setOrientation(Qt::Orientation o, ScalePos s) +{ + if ( o == d_data->orientation && s == d_data->scalePos ) + return; + + switch(o) + { + case Qt::Horizontal: + { + if ((s == NoScale) || (s == BottomScale) || (s == TopScale)) + d_data->scalePos = s; + else + d_data->scalePos = NoScale; + break; + } + case Qt::Vertical: + { + if ((s == NoScale) || (s == LeftScale) || (s == RightScale)) + d_data->scalePos = s; + else + d_data->scalePos = NoScale; + break; + } + } + + if ( o != d_data->orientation ) + { +#if QT_VERSION >= 0x040000 + if ( !testAttribute(Qt::WA_WState_OwnSizePolicy) ) +#else + if ( !testWState( WState_OwnSizePolicy ) ) +#endif + { + QSizePolicy sp = sizePolicy(); + sp.transpose(); + setSizePolicy(sp); + +#if QT_VERSION >= 0x040000 + setAttribute(Qt::WA_WState_OwnSizePolicy, false); +#else + clearWState( WState_OwnSizePolicy ); +#endif + } + } + + d_data->orientation = o; + layoutThermo(); +} + +/*! + \brief Change the scale position (and thermometer orientation). + + \param scalePos Position of the scale. + + A valid combination of scale position and orientation is enforced: + - if the new scale position is LeftScale or RightScale, the + scale orientation will become Qt::Vertical; + - if the new scale position is BottomScale or TopScale, the scale + orientation will become Qt::Horizontal; + - if the new scale position is NoScale, the scale orientation will not change. + + \sa setOrientation(), scalePosition() +*/ +void QwtThermo::setScalePosition(ScalePos scalePos) +{ + if ((scalePos == BottomScale) || (scalePos == TopScale)) + setOrientation(Qt::Horizontal, scalePos); + else if ((scalePos == LeftScale) || (scalePos == RightScale)) + setOrientation(Qt::Vertical, scalePos); + else + setOrientation(d_data->orientation, NoScale); +} + +/*! + Return the scale position. + \sa setScalePosition() +*/ +QwtThermo::ScalePos QwtThermo::scalePosition() const +{ + return d_data->scalePos; +} + +//! Notify a font change. +void QwtThermo::fontChange(const QFont &f) +{ + QWidget::fontChange( f ); + layoutThermo(); +} + +//! Notify a scale change. +void QwtThermo::scaleChange() +{ + update(); + layoutThermo(); +} + +/*! + Redraw the liquid in thermometer pipe. + \param painter Painter +*/ +void QwtThermo::drawThermo(QPainter *painter) +{ + int alarm = 0, taval = 0; + + QRect fRect; + QRect aRect; + QRect bRect; + + int inverted = ( d_data->maxValue < d_data->minValue ); + + // + // Determine if value exceeds alarm threshold. + // Note: The alarm value is allowed to lie + // outside the interval (minValue, maxValue). + // + if (d_data->alarmEnabled) + { + if (inverted) + { + alarm = ((d_data->alarmLevel >= d_data->maxValue) + && (d_data->alarmLevel <= d_data->minValue) + && (d_data->value >= d_data->alarmLevel)); + + } + else + { + alarm = (( d_data->alarmLevel >= d_data->minValue) + && (d_data->alarmLevel <= d_data->maxValue) + && (d_data->value >= d_data->alarmLevel)); + } + } + + // + // transform values + // + int tval = transform(d_data->value); + + if (alarm) + taval = transform(d_data->alarmLevel); + + // + // calculate recangles + // + if ( d_data->orientation == Qt::Horizontal ) + { + if (inverted) + { + bRect.setRect(d_data->thermoRect.x(), d_data->thermoRect.y(), + tval - d_data->thermoRect.x(), + d_data->thermoRect.height()); + + if (alarm) + { + aRect.setRect(tval, d_data->thermoRect.y(), + taval - tval + 1, + d_data->thermoRect.height()); + fRect.setRect(taval + 1, d_data->thermoRect.y(), + d_data->thermoRect.x() + d_data->thermoRect.width() - (taval + 1), + d_data->thermoRect.height()); + } + else + { + fRect.setRect(tval, d_data->thermoRect.y(), + d_data->thermoRect.x() + d_data->thermoRect.width() - tval, + d_data->thermoRect.height()); + } + } + else + { + bRect.setRect(tval + 1, d_data->thermoRect.y(), + d_data->thermoRect.width() - (tval + 1 - d_data->thermoRect.x()), + d_data->thermoRect.height()); + + if (alarm) + { + aRect.setRect(taval, d_data->thermoRect.y(), + tval - taval + 1, + d_data->thermoRect.height()); + fRect.setRect(d_data->thermoRect.x(), d_data->thermoRect.y(), + taval - d_data->thermoRect.x(), + d_data->thermoRect.height()); + } + else + { + fRect.setRect(d_data->thermoRect.x(), d_data->thermoRect.y(), + tval - d_data->thermoRect.x() + 1, + d_data->thermoRect.height()); + } + + } + } + else // Qt::Vertical + { + if (tval < d_data->thermoRect.y()) + tval = d_data->thermoRect.y(); + else + { + if (tval > d_data->thermoRect.y() + d_data->thermoRect.height()) + tval = d_data->thermoRect.y() + d_data->thermoRect.height(); + } + + if (inverted) + { + bRect.setRect(d_data->thermoRect.x(), tval + 1, + d_data->thermoRect.width(), + d_data->thermoRect.height() - (tval + 1 - d_data->thermoRect.y())); + + if (alarm) + { + aRect.setRect(d_data->thermoRect.x(), taval, + d_data->thermoRect.width(), + tval - taval + 1); + fRect.setRect(d_data->thermoRect.x(), d_data->thermoRect.y(), + d_data->thermoRect.width(), + taval - d_data->thermoRect.y()); + } + else + { + fRect.setRect(d_data->thermoRect.x(), d_data->thermoRect.y(), + d_data->thermoRect.width(), + tval - d_data->thermoRect.y() + 1); + } + } + else + { + bRect.setRect(d_data->thermoRect.x(), d_data->thermoRect.y(), + d_data->thermoRect.width(), + tval - d_data->thermoRect.y()); + if (alarm) + { + aRect.setRect(d_data->thermoRect.x(),tval, + d_data->thermoRect.width(), + taval - tval + 1); + fRect.setRect(d_data->thermoRect.x(),taval + 1, + d_data->thermoRect.width(), + d_data->thermoRect.y() + d_data->thermoRect.height() - (taval + 1)); + } + else + { + fRect.setRect(d_data->thermoRect.x(),tval, + d_data->thermoRect.width(), + d_data->thermoRect.y() + d_data->thermoRect.height() - tval); + } + } + } + + // + // paint thermometer + // + const QColor bgColor = +#if QT_VERSION < 0x040000 + colorGroup().color(QColorGroup::Background); +#else + palette().color(QPalette::Background); +#endif + painter->fillRect(bRect, bgColor); + + if (alarm) + painter->fillRect(aRect, d_data->alarmBrush); + + painter->fillRect(fRect, d_data->fillBrush); +} + +/*! + Set the border width of the pipe. + \param width Border width + \sa borderWidth() +*/ +void QwtThermo::setBorderWidth(int width) +{ + if ((width >= 0) && (width < (qwtMin(d_data->thermoRect.width(), + d_data->thermoRect.height()) + d_data->borderWidth) / 2 - 1)) + { + d_data->borderWidth = width; + layoutThermo(); + } +} + +/*! + Return the border width of the thermometer pipe. + \sa setBorderWidth() +*/ +int QwtThermo::borderWidth() const +{ + return d_data->borderWidth; +} + +/*! + \brief Set the range + \param vmin value corresponding lower or left end of the thermometer + \param vmax value corresponding to the upper or right end of the thermometer + \param logarithmic logarithmic mapping, true or false +*/ +void QwtThermo::setRange(double vmin, double vmax, bool logarithmic) +{ + d_data->minValue = vmin; + d_data->maxValue = vmax; + + if ( logarithmic ) + setScaleEngine(new QwtLog10ScaleEngine); + else + setScaleEngine(new QwtLinearScaleEngine); + + /* + There are two different maps, one for the scale, the other + for the values. This is confusing and will be changed + in the future. TODO ... + */ + + d_data->map.setTransformation(scaleEngine()->transformation()); + d_data->map.setScaleInterval(d_data->minValue, d_data->maxValue); + + if (autoScale()) + rescale(d_data->minValue, d_data->maxValue); + + layoutThermo(); +} + +/*! + \brief Change the brush of the liquid. + \param brush New brush. The default brush is solid black. + \sa fillBrush() +*/ +void QwtThermo::setFillBrush(const QBrush& brush) +{ + d_data->fillBrush = brush; + update(); +} + +/*! + Return the liquid brush. + \sa setFillBrush() +*/ +const QBrush& QwtThermo::fillBrush() const +{ + return d_data->fillBrush; +} + +/*! + \brief Change the color of the liquid. + \param c New color. The default color is black. + \sa fillColor() +*/ +void QwtThermo::setFillColor(const QColor &c) +{ + d_data->fillBrush.setColor(c); + update(); +} + +/*! + Return the liquid color. + \sa setFillColor() +*/ +const QColor &QwtThermo::fillColor() const +{ + return d_data->fillBrush.color(); +} + +/*! + \brief Specify the liquid brush above the alarm threshold + \param brush New brush. The default is solid white. + \sa alarmBrush() +*/ +void QwtThermo::setAlarmBrush(const QBrush& brush) +{ + d_data->alarmBrush = brush; + update(); +} + +/*! + Return the liquid brush above the alarm threshold. + \sa setAlarmBrush() +*/ +const QBrush& QwtThermo::alarmBrush() const +{ + return d_data->alarmBrush; +} + +/*! + \brief Specify the liquid color above the alarm threshold + \param c New color. The default is white. +*/ +void QwtThermo::setAlarmColor(const QColor &c) +{ + d_data->alarmBrush.setColor(c); + update(); +} + +//! Return the liquid color above the alarm threshold. +const QColor &QwtThermo::alarmColor() const +{ + return d_data->alarmBrush.color(); +} + +/*! + Specify the alarm threshold. + + \param level Alarm threshold + \sa alarmLevel() +*/ +void QwtThermo::setAlarmLevel(double level) +{ + d_data->alarmLevel = level; + d_data->alarmEnabled = 1; + update(); +} + +/*! + Return the alarm threshold. + \sa setAlarmLevel() +*/ +double QwtThermo::alarmLevel() const +{ + return d_data->alarmLevel; +} + +/*! + Change the width of the pipe. + + \param width Width of the pipe + \sa pipeWidth() +*/ +void QwtThermo::setPipeWidth(int width) +{ + if (width > 0) + { + d_data->thermoWidth = width; + layoutThermo(); + } +} + +/*! + Return the width of the pipe. + \sa setPipeWidth() +*/ +int QwtThermo::pipeWidth() const +{ + return d_data->thermoWidth; +} + + +/*! + \brief Specify the distance between the pipe's endpoints + and the widget's border + + The margin is used to leave some space for the scale + labels. If a large font is used, it is advisable to + adjust the margins. + \param m New Margin. The default values are 10 for + horizontal orientation and 20 for vertical + orientation. + \warning The margin has no effect if the scale is disabled. + \warning This function is a NOOP because margins are determined + automatically. +*/ +void QwtThermo::setMargin(int) +{ +} + + +/*! + \brief Enable or disable the alarm threshold + \param tf true (disabled) or false (enabled) +*/ +void QwtThermo::setAlarmEnabled(bool tf) +{ + d_data->alarmEnabled = tf; + update(); +} + +//! Return if the alarm threshold is enabled or disabled. +bool QwtThermo::alarmEnabled() const +{ + return d_data->alarmEnabled; +} + +/*! + \return the minimum size hint + \sa minimumSizeHint() +*/ +QSize QwtThermo::sizeHint() const +{ + return minimumSizeHint(); +} + +/*! + \brief Return a minimum size hint + \warning The return value depends on the font and the scale. + \sa sizeHint() +*/ +QSize QwtThermo::minimumSizeHint() const +{ + int w = 0, h = 0; + + if ( d_data->scalePos != NoScale ) + { + const int sdExtent = scaleDraw()->extent( QPen(), font() ); + const int sdLength = scaleDraw()->minLength( QPen(), font() ); + + w = sdLength; + h = d_data->thermoWidth + sdExtent + + d_data->borderWidth + d_data->scaleDist; + + } + else // no scale + { + w = 200; + h = d_data->thermoWidth; + } + + if ( d_data->orientation == Qt::Vertical ) + qSwap(w, h); + + w += 2 * d_data->borderWidth; + h += 2 * d_data->borderWidth; + + return QSize( w, h ); +} + +int QwtThermo::transform(double value) const +{ + const double min = qwtMin(d_data->map.s1(), d_data->map.s2()); + const double max = qwtMax(d_data->map.s1(), d_data->map.s2()); + + if ( value > max ) + value = max; + if ( value < min ) + value = min; + + return d_data->map.transform(value); +} diff --git a/qwt/src/qwt_thermo.h b/qwt/src/qwt_thermo.h new file mode 100644 index 000000000..767b4a974 --- /dev/null +++ b/qwt/src/qwt_thermo.h @@ -0,0 +1,182 @@ +/* -*- 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_THERMO_H +#define QWT_THERMO_H + +#include +#include +#include +#include +#include "qwt_global.h" +#include "qwt_abstract_scale.h" + +class QwtScaleDraw; + +/*! + \brief The Thermometer Widget + + QwtThermo is a widget which displays a value in an interval. It supports: + - a horizontal or vertical layout; + - a range; + - a scale; + - an alarm level. + + \image html sysinfo.png + + By default, the scale and range run over the same interval of values. + QwtAbstractScale::setScale() changes the interval of the scale and allows + easy conversion between physical units. + + The example shows how to make the scale indicate in degrees Fahrenheit and + to set the value in degrees Kelvin: +\code +#include +#include + +double Kelvin2Fahrenheit(double kelvin) +{ + // see http://en.wikipedia.org/wiki/Kelvin + return 1.8*kelvin - 459.67; +} + +int main(int argc, char **argv) +{ + const double minKelvin = 0.0; + const double maxKelvin = 500.0; + + QApplication a(argc, argv); + QwtThermo t; + t.setRange(minKelvin, maxKelvin); + t.setScale(Kelvin2Fahrenheit(minKelvin), Kelvin2Fahrenheit(maxKelvin)); + // set the value in Kelvin but the scale displays in Fahrenheit + // 273.15 Kelvin = 0 Celsius = 32 Fahrenheit + t.setValue(273.15); + a.setMainWidget(&t); + t.show(); + return a.exec(); +} +\endcode + + \todo Improve the support for a logarithmic range and/or scale. +*/ +class QWT_EXPORT QwtThermo: public QWidget, public QwtAbstractScale +{ + Q_OBJECT + + Q_ENUMS( ScalePos ) + + Q_PROPERTY( QBrush alarmBrush READ alarmBrush WRITE setAlarmBrush ) + Q_PROPERTY( QColor alarmColor READ alarmColor WRITE setAlarmColor ) + Q_PROPERTY( bool alarmEnabled READ alarmEnabled WRITE setAlarmEnabled ) + Q_PROPERTY( double alarmLevel READ alarmLevel WRITE setAlarmLevel ) + Q_PROPERTY( ScalePos scalePosition READ scalePosition + WRITE setScalePosition ) + Q_PROPERTY( int borderWidth READ borderWidth WRITE setBorderWidth ) + Q_PROPERTY( QBrush fillBrush READ fillBrush WRITE setFillBrush ) + Q_PROPERTY( QColor fillColor READ fillColor WRITE setFillColor ) + Q_PROPERTY( double maxValue READ maxValue WRITE setMaxValue ) + Q_PROPERTY( double minValue READ minValue WRITE setMinValue ) + Q_PROPERTY( int pipeWidth READ pipeWidth WRITE setPipeWidth ) + Q_PROPERTY( double value READ value WRITE setValue ) + +public: + /* + Scale position. QwtThermo tries to enforce valid combinations of its + orientation and scale position: + - Qt::Horizonal combines with NoScale, TopScale and BottomScale + - Qt::Vertical combines with NoScale, LeftScale and RightScale + + \sa setOrientation(), setScalePosition() + */ + enum ScalePos + { + NoScale, + LeftScale, + RightScale, + TopScale, + BottomScale + }; + + explicit QwtThermo(QWidget *parent = NULL); +#if QT_VERSION < 0x040000 + explicit QwtThermo(QWidget *parent, const char *name); +#endif + virtual ~QwtThermo(); + + void setOrientation(Qt::Orientation o, ScalePos s); + + void setScalePosition(ScalePos s); + ScalePos scalePosition() const; + + void setBorderWidth(int w); + int borderWidth() const; + + void setFillBrush(const QBrush &b); + const QBrush &fillBrush() const; + + void setFillColor(const QColor &c); + const QColor &fillColor() const; + + void setAlarmBrush(const QBrush &b); + const QBrush &alarmBrush() const; + + void setAlarmColor(const QColor &c); + const QColor &alarmColor() const; + + void setAlarmLevel(double v); + double alarmLevel() const; + + void setAlarmEnabled(bool tf); + bool alarmEnabled() const; + + void setPipeWidth(int w); + int pipeWidth() const; + + void setMaxValue(double v); + double maxValue() const; + + void setMinValue(double v); + double minValue() const; + + double value() const; + + void setRange(double vmin, double vmax, bool lg = false); + void setMargin(int m); + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + + void setScaleDraw(QwtScaleDraw *); + const QwtScaleDraw *scaleDraw() const; + +public slots: + void setValue(double val); + +protected: + void draw(QPainter *p, const QRect& update_rect); + void drawThermo(QPainter *p); + void layoutThermo( bool update = true ); + virtual void scaleChange(); + virtual void fontChange(const QFont &oldFont); + + virtual void paintEvent(QPaintEvent *e); + virtual void resizeEvent(QResizeEvent *e); + + QwtScaleDraw *scaleDraw(); + +private: + void initThermo(); + int transform(double v) const; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_valuelist.h b/qwt/src/qwt_valuelist.h new file mode 100644 index 000000000..99a55272f --- /dev/null +++ b/qwt/src/qwt_valuelist.h @@ -0,0 +1,57 @@ +/* -*- 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 + +#ifndef QWT_VALUELIST_H +#define QWT_VALUELIST_H + +#include "qwt_global.h" + +/*! + \def QwtValueList + */ + +#if QT_VERSION < 0x040000 + +#include + +#if defined(QWT_TEMPLATEDLL) +// MOC_SKIP_BEGIN +template class QWT_EXPORT QValueList; +// MOC_SKIP_END +#endif + +typedef QValueList QwtValueList; + +#else // QT_VERSION >= 0x040000 + +#include + +#if defined(QWT_TEMPLATEDLL) + +#if QT_VERSION < 0x040300 +// Some compilers have problems, +// without a qHash(double) implementation +#include +#include +inline uint qHash(double key) { return uint(key); } +#endif + +// MOC_SKIP_BEGIN +template class QWT_EXPORT QList; +// MOC_SKIP_END + +#endif // QWT_TEMPLATEDLL + +typedef QList QwtValueList; + +#endif + +#endif diff --git a/qwt/src/qwt_wheel.cpp b/qwt/src/qwt_wheel.cpp new file mode 100644 index 000000000..66b30c4b7 --- /dev/null +++ b/qwt/src/qwt_wheel.cpp @@ -0,0 +1,694 @@ +/* -*- 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_math.h" +#include "qwt_painter.h" +#include "qwt_paint_buffer.h" +#include "qwt_wheel.h" + +#define NUM_COLORS 30 + +class QwtWheel::PrivateData +{ +public: + PrivateData() + { + viewAngle = 175.0; + totalAngle = 360.0; + tickCnt = 10; + intBorder = 2; + borderWidth = 2; + wheelWidth = 20; +#if QT_VERSION < 0x040000 + allocContext = 0; +#endif + }; + + QRect sliderRect; + double viewAngle; + double totalAngle; + int tickCnt; + int intBorder; + int borderWidth; + int wheelWidth; +#if QT_VERSION < 0x040000 + int allocContext; +#endif + QColor colors[NUM_COLORS]; +}; + +//! Constructor +QwtWheel::QwtWheel(QWidget *parent): + QwtAbstractSlider(Qt::Horizontal, parent) +{ + initWheel(); +} + +#if QT_VERSION < 0x040000 +QwtWheel::QwtWheel(QWidget *parent, const char *name): + QwtAbstractSlider(Qt::Horizontal, parent) +{ + setName(name); + initWheel(); +} +#endif + +void QwtWheel::initWheel() +{ + d_data = new PrivateData; + +#if QT_VERSION < 0x040000 + setWFlags(Qt::WNoAutoErase); +#endif + + setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); + +#if QT_VERSION >= 0x040000 + setAttribute(Qt::WA_WState_OwnSizePolicy, false); +#else + clearWState( WState_OwnSizePolicy ); +#endif + + setUpdateTime(50); +} + +//! Destructor +QwtWheel::~QwtWheel() +{ +#if QT_VERSION < 0x040000 + if ( d_data->allocContext ) + QColor::destroyAllocContext( d_data->allocContext ); +#endif + delete d_data; +} + +//! Set up the color array for the background pixmap. +void QwtWheel::setColorArray() +{ + if ( !d_data->colors ) + return; + +#if QT_VERSION < 0x040000 + const QColor light = colorGroup().light(); + const QColor dark = colorGroup().dark(); +#else + const QColor light = palette().color(QPalette::Light); + const QColor dark = palette().color(QPalette::Dark); +#endif + + if ( !d_data->colors[0].isValid() || + d_data->colors[0] != light || + d_data->colors[NUM_COLORS - 1] != dark ) + { +#if QT_VERSION < 0x040000 + if ( d_data->allocContext ) + QColor::destroyAllocContext( d_data->allocContext ); + + d_data->allocContext = QColor::enterAllocContext(); +#endif + + d_data->colors[0] = light; + d_data->colors[NUM_COLORS - 1] = dark; + + int dh, ds, dv, lh, ls, lv; +#if QT_VERSION < 0x040000 + d_data->colors[0].rgb(&lh, &ls, &lv); + d_data->colors[NUM_COLORS - 1].rgb(&dh, &ds, &dv); +#else + d_data->colors[0].getRgb(&lh, &ls, &lv); + d_data->colors[NUM_COLORS - 1].getRgb(&dh, &ds, &dv); +#endif + + for ( int i = 1; i < NUM_COLORS - 1; ++i ) + { + const double factor = double(i) / double(NUM_COLORS); + + d_data->colors[i].setRgb( lh + int( double(dh - lh) * factor ), + ls + int( double(ds - ls) * factor ), + lv + int( double(dv - lv) * factor )); + } +#if QT_VERSION < 0x040000 + QColor::leaveAllocContext(); +#endif + } +} + +/*! + \brief Adjust the number of grooves in the wheel's surface. + + The number of grooves is limited to 6 <= cnt <= 50. + Values outside this range will be clipped. + The default value is 10. + + \param cnt Number of grooves per 360 degrees + \sa tickCnt() +*/ +void QwtWheel::setTickCnt(int cnt) +{ + d_data->tickCnt = qwtLim( cnt, 6, 50 ); + update(); +} + +/*! + \return Number of grooves in the wheel's surface. + \sa setTickCnt() +*/ +int QwtWheel::tickCnt() const +{ + return d_data->tickCnt; +} + +/*! + \return mass +*/ +double QwtWheel::mass() const +{ + return QwtAbstractSlider::mass(); +} + +/*! + \brief Set the internal border width of the wheel. + + The internal border must not be smaller than 1 + and is limited in dependence on the wheel's size. + Values outside the allowed range will be clipped. + + The internal border defaults to 2. + + \param w border width + \sa internalBorder() +*/ +void QwtWheel::setInternalBorder(int w) +{ + const int d = qwtMin( width(), height() ) / 3; + w = qwtMin( w, d ); + d_data->intBorder = qwtMax( w, 1 ); + layoutWheel(); +} + +/*! + \return Internal border width of the wheel. + \sa setInternalBorder() +*/ +int QwtWheel::internalBorder() const +{ + return d_data->intBorder; +} + +/*! + Draw the Wheel's background gradient + + \param painter Painter + \param r Bounding rectangle +*/ +void QwtWheel::drawWheelBackground(QPainter *painter, const QRect &r ) +{ + painter->save(); + + // + // initialize pens + // +#if QT_VERSION < 0x040000 + const QColor light = colorGroup().light(); + const QColor dark = colorGroup().dark(); +#else + const QColor light = palette().color(QPalette::Light); + const QColor dark = palette().color(QPalette::Dark); +#endif + + QPen lightPen; + lightPen.setColor(light); + lightPen.setWidth(d_data->intBorder); + + QPen darkPen; + darkPen.setColor(dark); + darkPen.setWidth(d_data->intBorder); + + setColorArray(); + + // + // initialize auxiliary variables + // + + const int nFields = NUM_COLORS * 13 / 10; + const int hiPos = nFields - NUM_COLORS + 1; + + if ( orientation() == Qt::Horizontal ) + { + const int rx = r.x(); + int ry = r.y() + d_data->intBorder; + const int rh = r.height() - 2* d_data->intBorder; + const int rw = r.width(); + // + // draw shaded background + // + int x1 = rx; + for (int i = 1; i < nFields; i++ ) + { + const int x2 = rx + (rw * i) / nFields; + painter->fillRect(x1, ry, x2-x1 + 1 ,rh, + d_data->colors[qwtAbs(i-hiPos)]); + x1 = x2 + 1; + } + painter->fillRect(x1, ry, rw - (x1 - rx), rh, + d_data->colors[NUM_COLORS - 1]); + + // + // draw internal border + // + painter->setPen(lightPen); + ry = r.y() + d_data->intBorder / 2; + painter->drawLine(r.x(), ry, r.x() + r.width() , ry); + + painter->setPen(darkPen); + ry = r.y() + r.height() - (d_data->intBorder - d_data->intBorder / 2); + painter->drawLine(r.x(), ry , r.x() + r.width(), ry); + } + else // Qt::Vertical + { + int rx = r.x() + d_data->intBorder; + const int ry = r.y(); + const int rh = r.height(); + const int rw = r.width() - 2 * d_data->intBorder; + + // + // draw shaded background + // + int y1 = ry; + for ( int i = 1; i < nFields; i++ ) + { + const int y2 = ry + (rh * i) / nFields; + painter->fillRect(rx, y1, rw, y2-y1 + 1, + d_data->colors[qwtAbs(i-hiPos)]); + y1 = y2 + 1; + } + painter->fillRect(rx, y1, rw, rh - (y1 - ry), + d_data->colors[NUM_COLORS - 1]); + + // + // draw internal borders + // + painter->setPen(lightPen); + rx = r.x() + d_data->intBorder / 2; + painter->drawLine(rx, r.y(), rx, r.y() + r.height()); + + painter->setPen(darkPen); + rx = r.x() + r.width() - (d_data->intBorder - d_data->intBorder / 2); + painter->drawLine(rx, r.y(), rx , r.y() + r.height()); + } + + painter->restore(); +} + + +/*! + \brief Set the total angle which the wheel can be turned. + + One full turn of the wheel corresponds to an angle of + 360 degrees. A total angle of n*360 degrees means + that the wheel has to be turned n times around its axis + to get from the minimum value to the maximum value. + + The default setting of the total angle is 360 degrees. + + \param angle total angle in degrees + \sa totalAngle() +*/ +void QwtWheel::setTotalAngle(double angle) +{ + if ( angle < 0.0 ) + angle = 0.0; + + d_data->totalAngle = angle; + update(); +} + +/*! + \return Total angle which the wheel can be turned. + \sa setTotalAngle() +*/ +double QwtWheel::totalAngle() const +{ + return d_data->totalAngle; +} + +/*! + \brief Set the wheel's orientation. + \param o Orientation. Allowed values are + Qt::Horizontal and Qt::Vertical. + Defaults to Qt::Horizontal. + \sa QwtAbstractSlider::orientation() +*/ +void QwtWheel::setOrientation(Qt::Orientation o) +{ + if ( orientation() == o ) + return; + +#if QT_VERSION >= 0x040000 + if ( !testAttribute(Qt::WA_WState_OwnSizePolicy) ) +#else + if ( !testWState( WState_OwnSizePolicy ) ) +#endif + { + QSizePolicy sp = sizePolicy(); + sp.transpose(); + setSizePolicy(sp); + +#if QT_VERSION >= 0x040000 + setAttribute(Qt::WA_WState_OwnSizePolicy, false); +#else + clearWState( WState_OwnSizePolicy ); +#endif + } + + QwtAbstractSlider::setOrientation(o); + layoutWheel(); +} + +/*! + \brief Specify the visible portion of the wheel. + + You may use this function for fine-tuning the appearance of + the wheel. The default value is 175 degrees. The value is + limited from 10 to 175 degrees. + + \param angle Visible angle in degrees + \sa viewAngle(), setTotalAngle() +*/ +void QwtWheel::setViewAngle(double angle) +{ + d_data->viewAngle = qwtLim( angle, 10.0, 175.0 ); + update(); +} + +/*! + \return Visible portion of the wheel + \sa setViewAngle(), totalAngle() +*/ +double QwtWheel::viewAngle() const +{ + return d_data->viewAngle; +} + +/*! + \brief Redraw the wheel + \param painter painter + \param r contents rectangle +*/ +void QwtWheel::drawWheel( QPainter *painter, const QRect &r ) +{ + // + // draw background gradient + // + drawWheelBackground( painter, r ); + + if ( maxValue() == minValue() || d_data->totalAngle == 0.0 ) + return; + +#if QT_VERSION < 0x040000 + const QColor light = colorGroup().light(); + const QColor dark = colorGroup().dark(); +#else + const QColor light = palette().color(QPalette::Light); + const QColor dark = palette().color(QPalette::Dark); +#endif + + const double sign = (minValue() < maxValue()) ? 1.0 : -1.0; + double cnvFactor = qwtAbs(d_data->totalAngle / (maxValue() - minValue())); + const double halfIntv = 0.5 * d_data->viewAngle / cnvFactor; + const double loValue = value() - halfIntv; + const double hiValue = value() + halfIntv; + const double tickWidth = 360.0 / double(d_data->tickCnt) / cnvFactor; + const double sinArc = sin(d_data->viewAngle * M_PI / 360.0); + cnvFactor *= M_PI / 180.0; + + + // + // draw grooves + // + if ( orientation() == Qt::Horizontal ) + { + const double halfSize = double(r.width()) * 0.5; + + int l1 = r.y() + d_data->intBorder; + int l2 = r.y() + r.height() - d_data->intBorder - 1; + + // draw one point over the border if border > 1 + if ( d_data->intBorder > 1 ) + { + l1 --; + l2 ++; + } + + const int maxpos = r.x() + r.width() - 2; + const int minpos = r.x() + 2; + + // + // draw tick marks + // + for ( double tickValue = ceil(loValue / tickWidth) * tickWidth; + tickValue < hiValue; tickValue += tickWidth ) + { + // + // calculate position + // + const int tickPos = r.x() + r.width() + - int( halfSize + * (sinArc + sign * sin((tickValue - value()) * cnvFactor)) + / sinArc); + // + // draw vertical line + // + if ( (tickPos <= maxpos) && (tickPos > minpos) ) + { + painter->setPen(dark); + painter->drawLine(tickPos -1 , l1, tickPos - 1, l2 ); + painter->setPen(light); + painter->drawLine(tickPos, l1, tickPos, l2); + } + } + } + else if ( orientation() == Qt::Vertical ) + { + const double halfSize = double(r.height()) * 0.5; + + int l1 = r.x() + d_data->intBorder; + int l2 = r.x() + r.width() - d_data->intBorder - 1; + + if ( d_data->intBorder > 1 ) + { + l1--; + l2++; + } + + const int maxpos = r.y() + r.height() - 2; + const int minpos = r.y() + 2; + + // + // draw tick marks + // + for ( double tickValue = ceil(loValue / tickWidth) * tickWidth; + tickValue < hiValue; tickValue += tickWidth ) + { + + // + // calculate position + // + const int tickPos = r.y() + int( halfSize * + (sinArc + sign * sin((tickValue - value()) * cnvFactor)) + / sinArc); + + // + // draw horizontal line + // + if ( (tickPos <= maxpos) && (tickPos > minpos) ) + { + painter->setPen(dark); + painter->drawLine(l1, tickPos - 1 ,l2, tickPos - 1); + painter->setPen(light); + painter->drawLine(l1, tickPos, l2, tickPos); + } + } + } +} + + +//! Determine the value corresponding to a specified point +double QwtWheel::getValue( const QPoint &p ) +{ + // The reference position is arbitrary, but the + // sign of the offset is important + int w, dx; + if ( orientation() == Qt::Vertical ) + { + w = d_data->sliderRect.height(); + dx = d_data->sliderRect.y() - p.y(); + } + else + { + w = d_data->sliderRect.width(); + dx = p.x() - d_data->sliderRect.x(); + } + + // w pixels is an arc of viewAngle degrees, + // so we convert change in pixels to change in angle + const double ang = dx * d_data->viewAngle / w; + + // value range maps to totalAngle degrees, + // so convert the change in angle to a change in value + const double val = ang * ( maxValue() - minValue() ) / d_data->totalAngle; + + // Note, range clamping and rasterizing to step is automatically + // handled by QwtAbstractSlider, so we simply return the change in value + return val; +} + +//! Qt Resize Event +void QwtWheel::resizeEvent(QResizeEvent *) +{ + layoutWheel( false ); +} + +//! Recalculate the slider's geometry and layout based on +// the current rect and fonts. +// \param update_geometry notify the layout system and call update +// to redraw the scale +void QwtWheel::layoutWheel( bool update_geometry ) +{ + const QRect r = this->rect(); + d_data->sliderRect.setRect(r.x() + d_data->borderWidth, r.y() + d_data->borderWidth, + r.width() - 2*d_data->borderWidth, r.height() - 2*d_data->borderWidth); + + if ( update_geometry ) + { + updateGeometry(); + update(); + } +} + +//! Qt Paint Event +void QwtWheel::paintEvent(QPaintEvent *e) +{ + // Use double-buffering + const QRect &ur = e->rect(); + if ( ur.isValid() ) + { +#if QT_VERSION < 0x040000 + QwtPaintBuffer paintBuffer(this, ur); + draw(paintBuffer.painter(), ur); +#else + QPainter painter(this); + draw(&painter, ur); +#endif + } +} + +/*! + Redraw panel and wheel + \param painter Painter +*/ +void QwtWheel::draw(QPainter *painter, const QRect&) +{ + qDrawShadePanel( painter, rect().x(), rect().y(), + rect().width(), rect().height(), +#if QT_VERSION < 0x040000 + colorGroup(), +#else + palette(), +#endif + true, d_data->borderWidth ); + + drawWheel( painter, d_data->sliderRect ); + + if ( hasFocus() ) + QwtPainter::drawFocusRect(painter, this); +} + +//! Notify value change +void QwtWheel::valueChange() +{ + QwtAbstractSlider::valueChange(); + update(); +} + + +/*! + \brief Determine the scrolling mode and direction corresponding + to a specified point + \param p point + \param scrollMode scrolling mode + \param direction direction +*/ +void QwtWheel::getScrollMode( const QPoint &p, int &scrollMode, int &direction) +{ + if ( d_data->sliderRect.contains(p) ) + scrollMode = ScrMouse; + else + scrollMode = ScrNone; + + direction = 0; +} + +/*! + \brief Set the mass of the wheel + + Assigning a mass turns the wheel into a flywheel. + \param val the wheel's mass +*/ +void QwtWheel::setMass(double val) +{ + QwtAbstractSlider::setMass(val); +} + +/*! + \brief Set the width of the wheel + + Corresponds to the wheel height for horizontal orientation, + and the wheel width for vertical orientation. + \param w the wheel's width +*/ +void QwtWheel::setWheelWidth(int w) +{ + d_data->wheelWidth = w; + layoutWheel(); +} + +/*! + \return a size hint +*/ +QSize QwtWheel::sizeHint() const +{ + return minimumSizeHint(); +} + +/*! + \brief Return a minimum size hint + \warning The return value is based on the wheel width. +*/ +QSize QwtWheel::minimumSizeHint() const +{ + QSize sz( 3*d_data->wheelWidth + 2*d_data->borderWidth, + d_data->wheelWidth + 2*d_data->borderWidth ); + if ( orientation() != Qt::Horizontal ) + sz.transpose(); + return sz; +} + +/*! + \brief Call update() when the palette changes +*/ +void QwtWheel::paletteChange( const QPalette& ) +{ + update(); +} + diff --git a/qwt/src/qwt_wheel.h b/qwt/src/qwt_wheel.h new file mode 100644 index 000000000..a4ae37a33 --- /dev/null +++ b/qwt/src/qwt_wheel.h @@ -0,0 +1,84 @@ +/* -*- 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_WHEEL_H +#define QWT_WHEEL_H + +#include "qwt_global.h" +#include "qwt_abstract_slider.h" + +/*! + \brief The Wheel Widget + + The wheel widget can be used to change values over a very large range + in very small steps. Using the setMass member, it can be configured + as a flywheel. + + \sa The radio example. +*/ +class QWT_EXPORT QwtWheel : public QwtAbstractSlider +{ + Q_OBJECT + Q_PROPERTY( double totalAngle READ totalAngle WRITE setTotalAngle ) + Q_PROPERTY( double viewAngle READ viewAngle WRITE setViewAngle ) + Q_PROPERTY( int tickCnt READ tickCnt WRITE setTickCnt ) + Q_PROPERTY( int internalBorder READ internalBorder WRITE setInternalBorder ) + Q_PROPERTY( double mass READ mass WRITE setMass ) + +public: + explicit QwtWheel(QWidget *parent = NULL); +#if QT_VERSION < 0x040000 + explicit QwtWheel(QWidget *parent, const char *name); +#endif + virtual ~QwtWheel(); + + virtual void setOrientation(Qt::Orientation); + + double totalAngle() const; + double viewAngle() const; + int tickCnt() const; + int internalBorder() const; + + double mass() const; + + void setTotalAngle (double angle); + void setTickCnt(int cnt); + void setViewAngle(double angle); + void setInternalBorder(int width); + void setMass(double val); + void setWheelWidth( int w ); + + virtual QSize sizeHint() const; + virtual QSize minimumSizeHint() const; + +protected: + virtual void resizeEvent(QResizeEvent *e); + virtual void paintEvent(QPaintEvent *e); + + void layoutWheel( bool update = true ); + void draw(QPainter *p, const QRect& update_rect); + void drawWheel(QPainter *p, const QRect &r); + void drawWheelBackground(QPainter *p, const QRect &r); + void setColorArray(); + + virtual void valueChange(); + virtual void paletteChange( const QPalette &); + + virtual double getValue(const QPoint &p); + virtual void getScrollMode(const QPoint &p, + int &scrollMode, int &direction); + +private: + void initWheel(); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/src.pro b/qwt/src/src.pro new file mode 100644 index 000000000..c62740a2b --- /dev/null +++ b/qwt/src/src.pro @@ -0,0 +1,224 @@ +# -*- mode: sh -*- ########################### +# 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 building the qwt libraries + +QWT_ROOT = .. + +include( $${QWT_ROOT}/qwtconfig.pri ) + +SUFFIX_STR = +VVERSION = $$[QT_VERSION] +isEmpty(VVERSION) { + + # Qt 3 + debug { + SUFFIX_STR = $${DEBUG_SUFFIX} + } + else { + SUFFIX_STR = $${RELEASE_SUFFIX} + } +} +else { + CONFIG(debug, debug|release) { + SUFFIX_STR = $${DEBUG_SUFFIX} + } + else { + SUFFIX_STR = $${RELEASE_SUFFIX} + } +} + +TARGET = qwt$${SUFFIX_STR} +TEMPLATE = lib + +MOC_DIR = moc +OBJECTS_DIR = obj$${SUFFIX_STR} +DESTDIR = $${QWT_ROOT}/lib + +contains(CONFIG, QwtDll ) { + CONFIG += dll +} +else { + CONFIG += staticlib +} + +win32:QwtDll { + DEFINES += QT_DLL QWT_DLL QWT_MAKEDLL +} + +HEADERS += \ + qwt.h \ + qwt_abstract_scale_draw.h \ + qwt_array.h \ + qwt_color_map.h \ + qwt_clipper.h \ + qwt_double_interval.h \ + qwt_double_rect.h \ + qwt_dyngrid_layout.h \ + qwt_global.h \ + qwt_layout_metrics.h \ + qwt_math.h \ + qwt_magnifier.h \ + qwt_paint_buffer.h \ + qwt_painter.h \ + qwt_panner.h \ + qwt_picker.h \ + qwt_picker_machine.h \ + qwt_polygon.h \ + qwt_round_scale_draw.h \ + qwt_scale_div.h \ + qwt_scale_draw.h \ + qwt_scale_engine.h \ + qwt_scale_map.h \ + qwt_spline.h \ + qwt_symbol.h \ + qwt_text_engine.h \ + qwt_text_label.h \ + qwt_text.h \ + qwt_valuelist.h + +SOURCES += \ + qwt_abstract_scale_draw.cpp \ + qwt_color_map.cpp \ + qwt_clipper.cpp \ + qwt_double_interval.cpp \ + qwt_double_rect.cpp \ + qwt_dyngrid_layout.cpp \ + qwt_layout_metrics.cpp \ + qwt_math.cpp \ + qwt_magnifier.cpp \ + qwt_paint_buffer.cpp \ + qwt_panner.cpp \ + qwt_painter.cpp \ + qwt_picker.cpp \ + qwt_round_scale_draw.cpp \ + qwt_scale_div.cpp \ + qwt_scale_draw.cpp \ + qwt_scale_map.cpp \ + qwt_spline.cpp \ + qwt_text_engine.cpp \ + qwt_text_label.cpp \ + qwt_text.cpp \ + qwt_event_pattern.cpp \ + qwt_picker_machine.cpp \ + qwt_scale_engine.cpp \ + qwt_symbol.cpp + + +contains(CONFIG, QwtPlot) { + + HEADERS += \ + qwt_curve_fitter.h \ + qwt_data.h \ + qwt_event_pattern.h \ + qwt_interval_data.h \ + qwt_legend.h \ + qwt_legend_item.h \ + qwt_legend_itemmanager.h \ + qwt_plot.h \ + qwt_plot_curve.h \ + qwt_plot_dict.h \ + qwt_plot_grid.h \ + qwt_plot_item.h \ + qwt_plot_layout.h \ + qwt_plot_marker.h \ + qwt_plot_printfilter.h \ + qwt_plot_rasteritem.h \ + qwt_plot_spectrogram.h \ + qwt_plot_scaleitem.h \ + qwt_plot_canvas.h \ + qwt_plot_rescaler.h \ + qwt_plot_panner.h \ + qwt_plot_picker.h \ + qwt_plot_zoomer.h \ + qwt_plot_magnifier.h \ + qwt_raster_data.h \ + qwt_scale_widget.h + + SOURCES += \ + qwt_curve_fitter.cpp \ + qwt_data.cpp \ + qwt_interval_data.cpp \ + qwt_legend.cpp \ + qwt_legend_item.cpp \ + qwt_plot.cpp \ + qwt_plot_print.cpp \ + qwt_plot_xml.cpp \ + qwt_plot_axis.cpp \ + qwt_plot_curve.cpp \ + qwt_plot_dict.cpp \ + qwt_plot_grid.cpp \ + qwt_plot_item.cpp \ + qwt_plot_spectrogram.cpp \ + qwt_plot_scaleitem.cpp \ + qwt_plot_marker.cpp \ + qwt_plot_layout.cpp \ + qwt_plot_printfilter.cpp \ + qwt_plot_rasteritem.cpp \ + qwt_plot_canvas.cpp \ + qwt_plot_rescaler.cpp \ + qwt_plot_panner.cpp \ + qwt_plot_picker.cpp \ + qwt_plot_zoomer.cpp \ + qwt_plot_magnifier.cpp \ + qwt_raster_data.cpp \ + qwt_scale_widget.cpp +} + +contains(CONFIG, QwtSVGItem) { + + QT += svg + HEADERS += qwt_plot_svgitem.h + SOURCES += qwt_plot_svgitem.cpp +} + +contains(CONFIG, QwtWidgets) { + + HEADERS += \ + qwt_abstract_slider.h \ + qwt_abstract_scale.h \ + qwt_arrow_button.h \ + qwt_analog_clock.h \ + qwt_compass.h \ + qwt_compass_rose.h \ + qwt_counter.h \ + qwt_dial.h \ + qwt_dial_needle.h \ + qwt_double_range.h \ + qwt_knob.h \ + qwt_slider.h \ + qwt_thermo.h \ + qwt_wheel.h + + SOURCES += \ + qwt_abstract_slider.cpp \ + qwt_abstract_scale.cpp \ + qwt_arrow_button.cpp \ + qwt_analog_clock.cpp \ + qwt_compass.cpp \ + qwt_compass_rose.cpp \ + qwt_counter.cpp \ + qwt_dial.cpp \ + qwt_dial_needle.cpp \ + qwt_double_range.cpp \ + qwt_knob.cpp \ + qwt_slider.cpp \ + qwt_thermo.cpp \ + qwt_wheel.cpp +} + +# Install directives + +headers.files = $$HEADERS +doc.files = $${QWT_ROOT}/doc/html $${QWT_ROOT}/doc/qwt-5.2.0.qch +unix { + doc.files += $${QWT_ROOT}/doc/man +} + +INSTALLS = target headers doc diff --git a/qwt/textengines/mathml/mathml.pro b/qwt/textengines/mathml/mathml.pro new file mode 100644 index 000000000..6ee31da38 --- /dev/null +++ b/qwt/textengines/mathml/mathml.pro @@ -0,0 +1,49 @@ +# -*- mode: sh -*- ########################### +# 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 +############################################## + +VVERSION = $$[QT_VERSION] +isEmpty(VVERSION) { + + # Qt3 + + message(qwtmathml is not supported for Qt3 !) + TEMPLATE = subdirs + +} else { + + # Qt4 + + include( ../textengines.pri ) + + exists( qtmmlwidget.cpp ) { + + TARGET = qwtmathml$${SUFFIX_STR} + VERSION = 1.0.0 + QT += xml + + HEADERS = \ + qwt_mathml_text_engine.h + + SOURCES = \ + qwt_mathml_text_engine.cpp + + # The files below can be found in the MathML tarball of the Qt Solution + # package http://www.trolltech.com/products/qt/addon/solutions/catalog/4/Widgets/qtmmlwidget + # that is available for owners of a commercial Qt license. + # + # Copy them here, or modify the pro file to your installation. + + HEADERS += qtmmlwidget.h + SOURCES += qtmmlwidget.cpp + } + + else { + error( "qtmmlwidget.cpp is missing, see http://www.trolltech.com/products/qt/addon/solutions/catalog/4/Widgets/qtmmlwidget" ) + } +} diff --git a/qwt/textengines/mathml/qtmmlwidget.cpp.diff b/qwt/textengines/mathml/qtmmlwidget.cpp.diff new file mode 100644 index 000000000..a055e6f69 --- /dev/null +++ b/qwt/textengines/mathml/qtmmlwidget.cpp.diff @@ -0,0 +1,23 @@ +--- qtmmlwidget.cpp.org 2007-01-15 22:34:37.000000000 +0100 ++++ qtmmlwidget.cpp 2007-01-15 22:34:37.000000000 +0100 +@@ -3970,9 +3970,6 @@ + void MmlNode::paint(QPainter *p) + { + p->save(); +- p->setViewport(deviceRect()); +- p->setWindow(myRect()); +- + + QColor fg = color(); + QColor bg = background(); +@@ -4219,7 +4216,9 @@ + p->save(); + p->setFont(fn); + +- p->drawText(0, fm.strikeOutPos(), m_text); ++ QPoint dPos = devicePoint(relOrigin()); ++ p->drawText(dPos.x(), dPos.y() + fm.strikeOutPos(), m_text); ++ + p->restore(); + } + diff --git a/qwt/textengines/mathml/qwt_mathml_text_engine.cpp b/qwt/textengines/mathml/qwt_mathml_text_engine.cpp new file mode 100644 index 000000000..c8dec7de3 --- /dev/null +++ b/qwt/textengines/mathml/qwt_mathml_text_engine.cpp @@ -0,0 +1,136 @@ +/* -*- 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 + *****************************************************************************/ + +// vim: expandtab + +#include +#if QT_VERSION >= 0x040000 + +#include +#include +#include "qwt_mathml_text_engine.h" + +#include + +//! Constructor +QwtMathMLTextEngine::QwtMathMLTextEngine() +{ +} + +//! Destructor +QwtMathMLTextEngine::~QwtMathMLTextEngine() +{ +} + +/*! + Find the height for a given width + + \param font Font of the text + \param flags Bitwise OR of the flags used like in QPainter::drawText + \param text Text to be rendered + \param width Width + + \return Calculated height +*/ +int QwtMathMLTextEngine::heightForWidth(const QFont& font, int flags, + const QString& text, int) const +{ + return textSize(font, flags, text).height(); +} + +/*! + Returns the size, that is needed to render text + + \param font Font of the text + \param flags Bitwise OR of the flags used like in QPainter::drawText + \param text Text to be rendered + + \return Caluclated size +*/ +QSize QwtMathMLTextEngine::textSize(const QFont &font, + int, const QString& text) const +{ + static QString t; + static QSize sz; + + if ( text != t ) + { + QtMmlDocument doc; + doc.setContent(text); + doc.setBaseFontPointSize(font.pointSize()); + + sz = doc.size(); + t = text; + } + + return sz; +} + +/*! + Return margins around the texts + + \param left Return 0 + \param right Return 0 + \param top Return 0 + \param bottom Return 0 +*/ +void QwtMathMLTextEngine::textMargins(const QFont &, const QString &, + int &left, int &right, int &top, int &bottom) const +{ + left = right = top = bottom = 0; +} + +/*! + Draw the text in a clipping rectangle + + \param painter Painter + \param rect Clipping rectangle + \param flags Bitwise OR of the flags like in for QPainter::drawText + \param text Text to be rendered +*/ +void QwtMathMLTextEngine::draw(QPainter *painter, const QRect &rect, + int flags, const QString& text) const +{ + QtMmlDocument doc; + doc.setContent(text); + doc.setBaseFontPointSize(painter->font().pointSize()); + + const QSize docSize = doc.size(); + + QPoint pos = rect.topLeft(); + if ( rect.width() > docSize.width() ) + { + if ( flags & Qt::AlignRight ) + pos.setX(rect.right() - docSize.width()); + if ( flags & Qt::AlignHCenter ) + pos.setX(rect.center().x() - docSize.width() / 2); + } + if ( rect.height() > docSize.height() ) + { + if ( flags & Qt::AlignBottom ) + pos.setY(rect.bottom() - docSize.height()); + if ( flags & Qt::AlignVCenter ) + pos.setY(rect.center().y() - docSize.height() / 2); + } + + doc.paint(painter, pos); +} + +/*! + Test if a string can be rendered by QwtMathMLTextEngine + + \param text Text to be tested + \return true, if text begins with "". +*/ +bool QwtMathMLTextEngine::mightRender(const QString &text) const +{ + return text.trimmed().startsWith("= 0x040000 + +#include "qwt_text_engine.h" + +/*! + \brief Text Engine for the MathML renderer of the Qt solutions package. + + The Qt Solution package includes a renderer for MathML + http://www.trolltech.com/products/qt/addon/solutions/catalog/4/Widgets/qtmmlwidget + that is available for owners of a commercial Qt license. + You need a version >= 2.1, that is only available for Qt4. + + To enable MathML support the following code needs to be added to the + application: + \verbatim +#include + +QwtText::setTextEngine(QwtText::MathMLText, new QwtMathMLTextEngine()); + \endverbatim + + \sa QwtTextEngine, QwtText::setTextEngine + \warning Unfortunately the MathML renderer doesn't support rotating of texts. +*/ + +class QWT_EXPORT QwtMathMLTextEngine: public QwtTextEngine +{ +public: + QwtMathMLTextEngine(); + virtual ~QwtMathMLTextEngine(); + + virtual int heightForWidth(const QFont &font, int flags, + const QString &text, int width) const; + + virtual QSize textSize(const QFont &font, int flags, + const QString &text) const; + + virtual void draw(QPainter *painter, const QRect &rect, + int flags, const QString &text) const; + + virtual bool mightRender(const QString &) const; + + virtual void textMargins(const QFont &, const QString &, + int &left, int &right, int &top, int &bottom) const; +}; + +#endif // QT_VERSION >= 0x040000 + +#endif diff --git a/qwt/textengines/textengines.pri b/qwt/textengines/textengines.pri new file mode 100644 index 000000000..6d7ee140e --- /dev/null +++ b/qwt/textengines/textengines.pri @@ -0,0 +1,75 @@ +# -*- mode: sh -*- ########################### +# 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 = ../.. + +include( $${QWT_ROOT}/qwtconfig.pri ) + +SUFFIX_STR = +VVERSION = $$[QT_VERSION] +isEmpty(VVERSION) { + + # Qt 3 + debug { + SUFFIX_STR = $${DEBUG_SUFFIX} + } + else { + SUFFIX_STR = $${RELEASE_SUFFIX} + } +} +else { + CONFIG(debug, debug|release) { + SUFFIX_STR = $${DEBUG_SUFFIX} + } + else { + SUFFIX_STR = $${RELEASE_SUFFIX} + } +} + +TEMPLATE = lib + +contains(CONFIG, QwtDll ) { + CONFIG += dll +} +else { + CONFIG += staticlib +} + +MOC_DIR = moc +OBJECTS_DIR = obj$${SUFFIX_STR} +DESTDIR = $${QWT_ROOT}/lib +INCLUDEPATH += $${QWT_ROOT}/src +DEPENDPATH += $${QWT_ROOT}/src + +QWTLIB = qwt$${SUFFIX_STR} + +win32 { + QwtDll { + DEFINES += QT_DLL QWT_DLL QWT_MAKEDLL + QWTLIB = $${QWTLIB}$${VER_MAJ} + } + + win32-msvc:LIBS += $${QWT_ROOT}/lib/$${QWTLIB}.lib + win32-msvc.net:LIBS += $${QWT_ROOT}/lib/$${QWTLIB}.lib + win32-msvc2002:LIBS += $${QWT_ROOT}/lib/$${QWTLIB}.lib + win32-msvc2003:LIBS += $${QWT_ROOT}/lib/$${QWTLIB}.lib + win32-msvc2005:LIBS += $${QWT_ROOT}/lib/$${QWTLIB}.lib + win32-msvc2008:LIBS += $${QWT_ROOT}/lib/$${QWTLIB}.lib + win32-g++:LIBS += -L$${QWT_ROOT}/lib -l$${QWTLIB} +} +else { + LIBS += -L$${QWT_ROOT}/lib -l$${QWTLIB} +} + +target.path = $$INSTALLBASE/lib +headers.path = $$INSTALLBASE/include +doc.path = $$INSTALLBASE/doc + +headers.files = $$HEADERS +INSTALLS = target headers diff --git a/qwt/textengines/textengines.pro b/qwt/textengines/textengines.pro new file mode 100644 index 000000000..7b947a65c --- /dev/null +++ b/qwt/textengines/textengines.pro @@ -0,0 +1,16 @@ +# -*- mode: sh -*- ################################################ +# 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 + +contains(CONFIG, QwtMathML) { + SUBDIRS += mathml +}

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 literal 0 HcmV?d00001 diff --git a/qwt/doc/qwt.dox b/qwt/doc/qwt.dox new file mode 100644 index 000000000..f379ce630 --- /dev/null +++ b/qwt/doc/qwt.dox @@ -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 + *****************************************************************************/ + +/* + 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 2D plot widget +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 5.x might be usable in all environments where you find + Qt. + It is compatible with Qt 3.3.x and Qt 4.x, but the documentation + is generated for Qt 4.x.\n + + \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, prereleases and snapshots are available at the Qwt project page. + + For getting a snapshot with all bugfixes for the latest 5.2 release: + \code svn co https://qwt.svn.sourceforge.net/svnroot/qwt/branches/qwt-5.2 \endcode + + For getting a development snapshot from the SVN repository: + \code svn co https://qwt.svn.sourceforge.net/svnroot/qwt/trunk/qwt \endcode + + Qwt doesn't distribute binary packages, but today all major Linux distributors + offer one. Note, that these packages often don't include the examples. + + \section installonmainpage Installation + + Have a look at the qwt.pro project file. It is prepared for building + dynamic libraries in Win32 and Unix/X11 environments. + If you don't know what to do with it, read the file \ref qwtinstall and/or + Trolltechs + qmake documentation. Once you have build the library you have to install + all files from the lib, include and doc directories. + + \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 qwt-support@tigertal.de. + + \section relatedprojects Related Projects + + QwtPolar, a polar plot widget.\n + QwtPlot3D, an OpenGL 3D plot widget.\n + QtiPlot, + data analysis and scientific plotting tool, using QwtPlot. + + \section languagebindings Language Bindings + + PyQwt, a set of Qwt Python bindings.\n + Korundum/QtRuby, including a set of Qwt Ruby bindings.\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 qwtinstall INSTALL + \include "INSTALL" +*/ + +/*! + \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/examples/bode/bode.cpp b/qwt/examples/bode/bode.cpp new file mode 100644 index 000000000..fcfe4b1ba --- /dev/null +++ b/qwt/examples/bode/bode.cpp @@ -0,0 +1,350 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_VERSION >= 0x040300 +#ifdef QT_SVG_LIB +#include +#endif +#endif +#if QT_VERSION >= 0x040000 +#include +#include +#else +#include +#endif +#include +#include +#include +#include +#include +#include "pixmaps.h" +#include "bode_plot.h" +#include "bode.h" + +class Zoomer: public QwtPlotZoomer +{ +public: + Zoomer(int xAxis, int yAxis, QwtPlotCanvas *canvas): + QwtPlotZoomer(xAxis, yAxis, canvas) + { + setSelectionFlags(QwtPicker::DragSelection | QwtPicker::CornerToCorner); + setTrackerMode(QwtPicker::AlwaysOff); + setRubberBand(QwtPicker::NoRubberBand); + + // RightButton: zoom out by 1 + // Ctrl+RightButton: zoom out to full size + +#if QT_VERSION < 0x040000 + setMousePattern(QwtEventPattern::MouseSelect2, + Qt::RightButton, Qt::ControlButton); +#else + setMousePattern(QwtEventPattern::MouseSelect2, + Qt::RightButton, Qt::ControlModifier); +#endif + 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. +// +//----------------------------------------------------------------- + +MainWin::MainWin(QWidget *parent): + QMainWindow(parent) +{ + d_plot = new BodePlot(this); + d_plot->setMargin(5); + +#if QT_VERSION >= 0x040000 + setContextMenuPolicy(Qt::NoContextMenu); +#endif + + d_zoomer[0] = new Zoomer( QwtPlot::xBottom, QwtPlot::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(QwtPlot::xTop, QwtPlot::yRight, + d_plot->canvas()); + + d_panner = new QwtPlotPanner(d_plot->canvas()); + d_panner->setMouseButton(Qt::MidButton); + + d_picker = new QwtPlotPicker(QwtPlot::xBottom, QwtPlot::yLeft, + QwtPicker::PointSelection | QwtPicker::DragSelection, + QwtPlotPicker::CrossRubberBand, QwtPicker::AlwaysOn, + d_plot->canvas()); + 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); +#if QT_VERSION >= 0x040000 + btnZoom->setText("Zoom"); + btnZoom->setIcon(QIcon(zoom_xpm)); + btnZoom->setCheckable(true); + btnZoom->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); +#else + btnZoom->setTextLabel("Zoom"); + btnZoom->setPixmap(zoom_xpm); + btnZoom->setToggleButton(true); + btnZoom->setUsesTextLabel(true); +#endif + + QToolButton *btnPrint = new QToolButton(toolBar); +#if QT_VERSION >= 0x040000 + btnPrint->setText("Print"); + btnPrint->setIcon(QIcon(print_xpm)); + btnPrint->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); +#else + btnPrint->setTextLabel("Print"); + btnPrint->setPixmap(print_xpm); + btnPrint->setUsesTextLabel(true); +#endif + +#if QT_VERSION < 0x040000 + QToolButton *btnSVG = new QToolButton(toolBar); + btnSVG->setTextLabel("SVG"); + btnSVG->setPixmap(print_xpm); + btnSVG->setUsesTextLabel(true); +#elif QT_VERSION >= 0x040300 +#ifdef QT_SVG_LIB + QToolButton *btnSVG = new QToolButton(toolBar); + btnSVG->setText("SVG"); + btnSVG->setIcon(QIcon(print_xpm)); + btnSVG->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); +#endif +#endif + +#if QT_VERSION >= 0x040000 + toolBar->addWidget(btnZoom); + toolBar->addWidget(btnPrint); +#if QT_VERSION >= 0x040300 +#ifdef QT_SVG_LIB + toolBar->addWidget(btnSVG); +#endif +#endif +#endif + 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, 0.01); + cntDamp->setValue(0.0); + + layout->addWidget(cntDamp, 0); + +#if QT_VERSION >= 0x040000 + (void)toolBar->addWidget(hBox); +#else + toolBar->setStretchableWidget(hBox); +#endif + + addToolBar(toolBar); +#ifndef QT_NO_STATUSBAR + (void)statusBar(); +#endif + + enableZoomMode(false); + showInfo(); + + connect(cntDamp, SIGNAL(valueChanged(double)), + d_plot, SLOT(setDamp(double))); + + connect(btnPrint, SIGNAL(clicked()), SLOT(print())); +#if QT_VERSION < 0x040000 + connect(btnSVG, SIGNAL(clicked()), SLOT(exportSVG())); +#elif QT_VERSION >= 0x040300 +#ifdef QT_SVG_LIB + connect(btnSVG, SIGNAL(clicked()), SLOT(exportSVG())); +#endif +#endif + connect(btnZoom, SIGNAL(toggled(bool)), SLOT(enableZoomMode(bool))); + + connect(d_picker, SIGNAL(moved(const QPoint &)), + SLOT(moved(const QPoint &))); + connect(d_picker, SIGNAL(selected(const QwtPolygon &)), + SLOT(selected(const QwtPolygon &))); +} + +void MainWin::print() +{ +#if 1 + QPrinter printer; +#else + QPrinter printer(QPrinter::HighResolution); +#if QT_VERSION < 0x040000 + printer.setOutputToFile(true); + printer.setOutputFileName("/tmp/bode.ps"); + printer.setColorMode(QPrinter::Color); +#else + printer.setOutputFileName("/tmp/bode.pdf"); +#endif +#endif + + 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); + +#if QT_VERSION >= 0x040000 + QPrintDialog dialog(&printer); + if ( dialog.exec() ) + { +#else + if (printer.setup()) + { +#endif + QwtPlotPrintFilter filter; + if ( printer.colorMode() == QPrinter::GrayScale ) + { + int options = QwtPlotPrintFilter::PrintAll; + options &= ~QwtPlotPrintFilter::PrintBackground; + options |= QwtPlotPrintFilter::PrintFrameWithScales; + filter.setOptions(options); + } + d_plot->print(printer, filter); + } +} + +void MainWin::exportSVG() +{ + QString fileName = "bode.svg"; + +#if QT_VERSION < 0x040000 + +#ifndef QT_NO_FILEDIALOG + fileName = QFileDialog::getSaveFileName( + "bode.svg", "SVG Documents (*.svg)", this); +#endif + if ( !fileName.isEmpty() ) + { + // enable workaround for Qt3 misalignments + QwtPainter::setSVGMode(true); + + QPicture picture; + + QPainter p(&picture); + d_plot->print(&p, QRect(0, 0, 800, 600)); + p.end(); + + picture.save(fileName, "svg"); + } + +#elif QT_VERSION >= 0x040300 + +#ifdef QT_SVG_LIB +#ifndef QT_NO_FILEDIALOG + fileName = QFileDialog::getSaveFileName( + this, "Export File Name", QString(), + "SVG Documents (*.svg)"); +#endif + if ( !fileName.isEmpty() ) + { + QSvgGenerator generator; + generator.setFileName(fileName); + generator.setSize(QSize(800, 600)); + + d_plot->print(generator); + } +#endif +#endif +} + +void MainWin::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 MainWin::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 +#if QT_VERSION >= 0x040000 + statusBar()->showMessage(text); +#else + statusBar()->message(text); +#endif +#endif +} + +void MainWin::moved(const QPoint &pos) +{ + QString info; + info.sprintf("Freq=%g, Ampl=%g, Phase=%g", + d_plot->invTransform(QwtPlot::xBottom, pos.x()), + d_plot->invTransform(QwtPlot::yLeft, pos.y()), + d_plot->invTransform(QwtPlot::yRight, pos.y()) + ); + showInfo(info); +} + +void MainWin::selected(const QwtPolygon &) +{ + showInfo(); +} + +int main (int argc, char **argv) +{ + QApplication a(argc, argv); + + MainWin w; +#if QT_VERSION < 0x040000 + a.setMainWidget(&w); +#endif + w.resize(540,400); + w.show(); + + int rv = a.exec(); + return rv; +} diff --git a/qwt/examples/bode/bode.h b/qwt/examples/bode/bode.h new file mode 100644 index 000000000..81c9de5e5 --- /dev/null +++ b/qwt/examples/bode/bode.h @@ -0,0 +1,32 @@ +#include +#include + +class QwtPlotZoomer; +class QwtPlotPicker; +class QwtPlotPanner; +class BodePlot; + +class MainWin : public QMainWindow +{ + Q_OBJECT + +public: + MainWin(QWidget *parent = 0); + +private slots: + void moved(const QPoint &); + void selected(const QwtPolygon &); + + void print(); + void exportSVG(); + void enableZoomMode(bool); + +private: + void showInfo(QString text = QString::null); + + BodePlot *d_plot; + + QwtPlotZoomer *d_zoomer[2]; + QwtPlotPicker *d_picker; + QwtPlotPanner *d_panner; +}; diff --git a/qwt/examples/bode/bode.pro b/qwt/examples/bode/bode.pro new file mode 100644 index 000000000..b2a925087 --- /dev/null +++ b/qwt/examples/bode/bode.pro @@ -0,0 +1,23 @@ +# -*- mode: sh -*- ################################################ +# 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( ../examples.pri ) + +TARGET = bode +QT += svg + +HEADERS = \ + bode.h \ + bode_plot.h \ + cplx.h \ + pixmaps.h + +SOURCES = \ + bode.cpp \ + bode_plot.cpp diff --git a/qwt/examples/bode/bode_plot.cpp b/qwt/examples/bode/bode_plot.cpp new file mode 100644 index 000000000..040dce84c --- /dev/null +++ b/qwt/examples/bode/bode_plot.cpp @@ -0,0 +1,181 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "cplx.h" +#include "bode_plot.h" + +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] = exp(lxmin + double(i) * lstep); +} + +BodePlot::BodePlot(QWidget *parent): + QwtPlot(parent) +{ + setAutoReplot(false); + + setTitle("Frequency Response of a Second-Order System"); + + setCanvasBackground(QColor(Qt::darkBlue)); + + // legend + QwtLegend *legend = new QwtLegend; + legend->setFrameStyle(QFrame::Box|QFrame::Sunken); + insertLegend(legend, QwtPlot::BottomLegend); + + // grid + QwtPlotGrid *grid = new QwtPlotGrid; + grid->enableXMin(true); + grid->setMajPen(QPen(Qt::white, 0, Qt::DotLine)); + grid->setMinPen(QPen(Qt::gray, 0 , Qt::DotLine)); + grid->attach(this); + + // axes + enableAxis(QwtPlot::yRight); + setAxisTitle(QwtPlot::xBottom, "Normalized Frequency"); + setAxisTitle(QwtPlot::yLeft, "Amplitude [dB]"); + setAxisTitle(QwtPlot::yRight, "Phase [deg]"); + + setAxisMaxMajor(QwtPlot::xBottom, 6); + setAxisMaxMinor(QwtPlot::xBottom, 10); + setAxisScaleEngine(QwtPlot::xBottom, new QwtLog10ScaleEngine); + + // curves + d_crv1 = new QwtPlotCurve("Amplitude"); +#if QT_VERSION >= 0x040000 + d_crv1->setRenderHint(QwtPlotItem::RenderAntialiased); +#endif + d_crv1->setPen(QPen(Qt::yellow)); + d_crv1->setYAxis(QwtPlot::yLeft); + d_crv1->attach(this); + + d_crv2 = new QwtPlotCurve("Phase"); +#if QT_VERSION >= 0x040000 + d_crv2->setRenderHint(QwtPlotItem::RenderAntialiased); +#endif + d_crv2->setPen(QPen(Qt::cyan)); + d_crv2->setYAxis(QwtPlot::yRight); + d_crv2->attach(this); + + // marker + d_mrk1 = new QwtPlotMarker(); + d_mrk1->setValue(0.0, 0.0); + d_mrk1->setLineStyle(QwtPlotMarker::VLine); + d_mrk1->setLabelAlignment(Qt::AlignRight | Qt::AlignBottom); + d_mrk1->setLinePen(QPen(Qt::green, 0, Qt::DashDotLine)); + d_mrk1->attach(this); + + d_mrk2 = new QwtPlotMarker(); + d_mrk2->setLineStyle(QwtPlotMarker::HLine); + d_mrk2->setLabelAlignment(Qt::AlignRight | Qt::AlignBottom); + d_mrk2->setLinePen(QPen(QColor(200,150,0), 0, Qt::DashDotLine)); + d_mrk2->setSymbol( QwtSymbol(QwtSymbol::Diamond, + QColor(Qt::yellow), QColor(Qt::green), QSize(7,7))); + d_mrk2->attach(this); + + setDamp(0.0); + + setAutoReplot(true); +} + +void BodePlot::showData(double *frequency, double *amplitude, + double *phase, int count) +{ + d_crv1->setData(frequency, amplitude, count); + d_crv2->setData(frequency, phase, count); +} + +void BodePlot::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_mrk2->setValue(freq, amplitude); + d_mrk2->setLabel(text); +} + +void BodePlot::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_mrk1->setValue(freq, 0.0); + d_mrk1->setLabel(text); +} + +// +// re-calculate frequency response +// +void BodePlot::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]; + cplx g = cplx(1.0) / cplx(1.0 - f * f, 2.0 * damping * f); + amplitude[i] = 20.0 * log10(sqrt( g.real()*g.real() + g.imag()*g.imag())); + phase[i] = atan2(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/bode_plot.h b/qwt/examples/bode/bode_plot.h new file mode 100644 index 000000000..cd04f042f --- /dev/null +++ b/qwt/examples/bode/bode_plot.h @@ -0,0 +1,25 @@ +#include + +class QwtPlotCurve; +class QwtPlotMarker; + +class BodePlot: public QwtPlot +{ + Q_OBJECT +public: + BodePlot(QWidget *parent); + +public slots: + void setDamp(double damping); + +private: + void showData(double *frequency, double *amplitude, + double *phase, int count); + void showPeak(double freq, double amplitude); + void show3dB(double freq); + + QwtPlotCurve *d_crv1; + QwtPlotCurve *d_crv2; + QwtPlotMarker *d_mrk1; + QwtPlotMarker *d_mrk2; +}; diff --git a/qwt/examples/bode/cplx.h b/qwt/examples/bode/cplx.h new file mode 100644 index 000000000..d95344cc3 --- /dev/null +++ b/qwt/examples/bode/cplx.h @@ -0,0 +1,62 @@ +#ifndef BODE_CPLX_H +#define BODE_CPLX_H + +#include + + +class cplx { +private: + double re,im; + +public: + + double real() {return re;} + double imag() {return im;} + + cplx() { + re = 0.0; + im = -0.0; + } + + cplx& operator= (cplx a) { + re = a.re; + im = a.im; + return *this; + } + + cplx(double r, double i = 0.0) { + re = r; + im = i; + } + + friend cplx operator * (cplx x1, cplx x2); + friend cplx operator + (cplx x1, cplx x2); + friend cplx operator - (cplx x1, cplx x2); + friend cplx operator / (cplx x1, cplx x2); + +}; + +inline cplx operator+(cplx x1, cplx x2) +{ + return cplx(x1.re + x2.re, x1.im + x2.im); +} + +inline cplx operator-(cplx x1, cplx x2) +{ + return cplx(x1.re - x2.re, x1.im - x2.im); +} + +inline cplx operator*(cplx x1, cplx x2) +{ + return cplx(x1.re * x2.re - x1.im * x2.im, + x1.re * x2.im + x2.re * x1.im); +} + +inline cplx operator/(cplx x1, cplx x2) +{ + double denom = x2.re * x2.re + x2.im * x2.im; + return cplx( (x1.re * x2.re + x1.im * x2.im) /denom, + (x1.im * x2.re - x2.im * x1.re) / denom); +} + +#endif diff --git a/qwt/examples/bode/pixmaps.h b/qwt/examples/bode/pixmaps.h new file mode 100644 index 000000000..048a6aea2 --- /dev/null +++ b/qwt/examples/bode/pixmaps.h @@ -0,0 +1,95 @@ +#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/cpuplot/cpupiemarker.cpp b/qwt/examples/cpuplot/cpupiemarker.cpp new file mode 100644 index 000000000..be563633e --- /dev/null +++ b/qwt/examples/cpuplot/cpupiemarker.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include "cpuplot.h" +#include "cpupiemarker.h" + +CpuPieMarker::CpuPieMarker() +{ + setZ(1000); +#if QT_VERSION >= 0x040000 + setRenderHint(QwtPlotItem::RenderAntialiased, true); +#endif +} + +int CpuPieMarker::rtti() const +{ + return QwtPlotItem::Rtti_PlotUserItem; +} + +void CpuPieMarker::draw(QPainter *p, + const QwtScaleMap &, const QwtScaleMap &, + const QRect &rect) const +{ + const CpuPlot *cpuPlot = (CpuPlot *)plot(); + + const QwtScaleMap yMap = cpuPlot->canvasMap(QwtPlot::yLeft); + + const int margin = 5; + + QRect 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 = (int)(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 = (int)(5760 * curve->y(0) / 100.0); + + p->save(); + p->setBrush(QBrush(curve->pen().color(), Qt::SolidPattern)); + if ( value != 0 ) + p->drawPie(pieRect, -angle, -value); + p->restore(); + + angle += value; + } + } +} + diff --git a/qwt/examples/cpuplot/cpupiemarker.h b/qwt/examples/cpuplot/cpupiemarker.h new file mode 100644 index 000000000..165e9a49d --- /dev/null +++ b/qwt/examples/cpuplot/cpupiemarker.h @@ -0,0 +1,18 @@ +//----------------------------------------------------------------- +// 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 *p, + const QwtScaleMap &, const QwtScaleMap &, + const QRect &rect) const; +}; diff --git a/qwt/examples/cpuplot/cpuplot.cpp b/qwt/examples/cpuplot/cpuplot.cpp new file mode 100644 index 000000000..fc91df668 --- /dev/null +++ b/qwt/examples/cpuplot/cpuplot.cpp @@ -0,0 +1,244 @@ +#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((int)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 QRect &rect) const + { + QColor c(Qt::white); + QRect r = rect; + + 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) + { +#if QT_VERSION >= 0x040000 + setRenderHint(QwtPlotItem::RenderAntialiased); +#endif + } + + void setColor(const QColor &color) + { +#if QT_VERSION >= 0x040000 + QColor c = color; + c.setAlpha(150); + + setPen(c); + setBrush(c); +#else + setPen(color); + setBrush(QBrush(color, Qt::Dense4Pattern)); +#endif + } +}; + +CpuPlot::CpuPlot(QWidget *parent): + QwtPlot(parent), + dataCount(0) +{ + setAutoReplot(false); + + plotLayout()->setAlignCanvasToScales(true); + + QwtLegend *legend = new QwtLegend; + legend->setItemMode(QwtLegend::CheckableItem); + insertLegend(legend, QwtPlot::RightLegend); + + setAxisTitle(QwtPlot::xBottom, " System Uptime [h:m:s]"); + setAxisScaleDraw(QwtPlot::xBottom, + new TimeScaleDraw(cpuStat.upTime())); + setAxisScale(QwtPlot::xBottom, 0, HISTORY); + setAxisLabelRotation(QwtPlot::xBottom, -50.0); + setAxisLabelAlignment(QwtPlot::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(QwtPlot::xBottom); + const int fmh = QFontMetrics(scaleWidget->font()).height(); + scaleWidget->setMinBorderDist(0, fmh / 2); + + setAxisTitle(QwtPlot::yLeft, "Cpu Usage [%]"); + setAxisScale(QwtPlot::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(this, SIGNAL(legendChecked(QwtPlotItem *, bool)), + SLOT(showCurve(QwtPlotItem *, 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(QwtPlot::xBottom, + timeData[HISTORY - 1], timeData[0]); + + for ( int c = 0; c < NCpuData; c++ ) + { + data[c].curve->setRawData( + timeData, data[c].data, dataCount); + } + + replot(); +} + +void CpuPlot::showCurve(QwtPlotItem *item, bool on) +{ + item->setVisible(on); + QWidget *w = legend()->find(item); + if ( w && w->inherits("QwtLegendItem") ) + ((QwtLegendItem *)w)->setChecked(on); + + replot(); +} + +int main(int argc, char **argv) +{ + QApplication a(argc, argv); + + QWidget vBox; +#if QT_VERSION >= 0x040000 + vBox.setWindowTitle("Cpu Plot"); +#else + vBox.setCaption("Cpu Plot"); +#endif + + CpuPlot *plot = new CpuPlot(&vBox); + plot->setTitle("History"); + plot->setMargin(5); + + 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); + +#if QT_VERSION < 0x040000 + a.setMainWidget(&vBox); +#endif + + 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..873c9528a --- /dev/null +++ b/qwt/examples/cpuplot/cpuplot.h @@ -0,0 +1,42 @@ +#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 slots: + void showCurve(QwtPlotItem *, bool on); + +private: + 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..043a8031b --- /dev/null +++ b/qwt/examples/cpuplot/cpuplot.pro @@ -0,0 +1,22 @@ +# -*- mode: sh -*- ################################################ +# 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( ../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..73bf987be --- /dev/null +++ b/qwt/examples/cpuplot/cpustat.cpp @@ -0,0 +1,235 @@ +#include +#include +#include +#include "cpustat.h" + +CpuStat::CpuStat() +{ + lookUp(procValues); +} + +QTime CpuStat::upTime() const +{ + QTime t; + 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 QT_VERSION >= 0x040000 + if ( !file.open(QIODevice::ReadOnly) ) +#else + if ( !file.open(IO_ReadOnly) ) +#endif + { + 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(); +#if QT_VERSION < 0x040000 + line = line.stripWhiteSpace(); +#else + line = line.trimmed(); +#endif + if ( line.startsWith("cpu ") ) + { + const QStringList valueList = +#if QT_VERSION < 0x040000 + QStringList::split(" ", line); +#else + line.split(" ", QString::SkipEmptyParts); +#endif + if ( valueList.count() >= 5 ) + { + for ( int i = 0; i < NValues; i++ ) + values[i] = valueList[i+1].toDouble(); + } + break; + } + } +#if QT_VERSION < 0x040000 + while(!textStream.eof()); +#else + while(!textStream.atEnd()); +#endif + } +} diff --git a/qwt/examples/cpuplot/cpustat.h b/qwt/examples/cpuplot/cpustat.h new file mode 100644 index 000000000..828ac0dac --- /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..d69ee8c98 --- /dev/null +++ b/qwt/examples/curvdemo1/curvdemo1.cpp @@ -0,0 +1,230 @@ +#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; +#if QT_VERSION >= 0x040000 +const int CurvCnt = 6; +#else +const int CurvCnt = 5; +#endif + +// +// Arrays holding the values +// +double xval[Size]; +double yval[Size]; +QwtScaleMap xMap; +QwtScaleMap yMap; + +class MainWin : public QFrame +{ +public: + MainWin(); + +protected: +#if QT_VERSION >= 0x040000 + virtual void paintEvent(QPaintEvent *); +#endif + void drawContents(QPainter *p); + +private: + void shiftDown(QRect &rect, int offset) const; + + QwtPlotCurve crv[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= 0x040000 + crv[i].setPen(QColor(Qt::darkBlue)); + crv[i].setStyle(QwtPlotCurve::Lines); + crv[i].setRenderHint(QwtPlotItem::RenderAntialiased); + i++; +#endif + + + crv[i].setPen(QColor(Qt::darkCyan)); + crv[i].setStyle(QwtPlotCurve::Steps); + i++; + + sym.setStyle(QwtSymbol::XCross); + sym.setPen(QColor(Qt::darkMagenta)); + crv[i].setSymbol(sym); + crv[i].setStyle(QwtPlotCurve::NoCurve); + i++; + + + // + // attach data + // + for(i=0;i= 0x040000 +void MainWin::paintEvent(QPaintEvent *event) +{ + QFrame::paintEvent(event); + + QPainter painter(this); + painter.setClipRect(contentsRect()); + drawContents(&painter); +} +#endif + + +// +// 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= 0x040000 + painter->setRenderHint(QPainter::Antialiasing, + crv[i].testRenderHint(QwtPlotItem::RenderAntialiased) ); +#endif + crv[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); + +#if QT_VERSION >= 0x040000 + painter->drawText(0 ,r.top(),r.width(), painter->fontMetrics().height(), + alignment, "Style: Lines, Symbol: None, Antialiased"); + shiftDown(r, deltay); +#endif + + + 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; + +#if QT_VERSION < 0x040000 + a.setMainWidget(&w); +#endif + 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..047c735e4 --- /dev/null +++ b/qwt/examples/curvdemo1/curvdemo1.pro @@ -0,0 +1,15 @@ +# -*- mode: sh -*- ################################################ +# 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( ../examples.pri ) + +TARGET = curvdemo1 + +SOURCES = \ + curvdemo1.cpp diff --git a/qwt/examples/curvdemo2/curvdemo2.cpp b/qwt/examples/curvdemo2/curvdemo2.cpp new file mode 100644 index 000000000..80b9d41ff --- /dev/null +++ b/qwt/examples/curvdemo2/curvdemo2.cpp @@ -0,0 +1,223 @@ +#include +#include +#include +#include +#include +#include "curvdemo2.h" + + +//------------------------------------------------------------ +// curvdemo2 +// +// This example shows a simple animation featuring +// with several QwtPlotCurves +// +//------------------------------------------------------------ + +// +// Array Sizes +// +const int Size = 15; +const int USize = 13; + +// +// Arrays holding the values +// +double xval[Size]; +double yval[Size]; +double zval[Size]; +double uval[USize]; +double vval[USize]; + + +// +// CONSTRUCT MAIN WINDOW +// +MainWin::MainWin(): + QFrame() +{ + setFrameStyle(QFrame::Box|QFrame::Raised); + setLineWidth(2); + setMidLineWidth(3); + + const QColor bgColor(30,30,50); +#if QT_VERSION < 0x040000 + setPaletteBackgroundColor(bgColor); +#else + QPalette p = palette(); + p.setColor(backgroundRole(), bgColor); + setPalette(p); +#endif + + QwtSplineCurveFitter* curveFitter; + + // + // curve 1 + // + int i = 0; + xMap[i].setScaleInterval(-1.5, 1.5); + yMap[i].setScaleInterval(0.0, 6.28); + + curve[i].setPen(QPen(QColor(150,150,200),2)); + curve[i].setStyle(QwtPlotCurve::Lines); + curve[i].setCurveAttribute(QwtPlotCurve::Fitted, true); + curveFitter = new QwtSplineCurveFitter(); + curveFitter->setSplineSize(150); + curve[i].setCurveFitter(curveFitter); + + QwtSymbol sym; + sym.setStyle(QwtSymbol::XCross); + sym.setPen(QPen(Qt::yellow,2)); + sym.setSize(7); + + curve[i].setSymbol(sym); + + curve[i].setRawData(yval,xval,Size); + + // + // curve 2 + // + i++; + xMap[i].setScaleInterval(0.0, 6.28); + yMap[i].setScaleInterval(-3.0, 1.1); + curve[i].setPen(QPen(QColor(200,150,50))); + curve[i].setStyle(QwtPlotCurve::Sticks); + curve[i].setSymbol(QwtSymbol(QwtSymbol::Ellipse, + QColor(Qt::blue), QColor(Qt::yellow), QSize(5,5))); + + curve[i].setRawData(xval,zval,Size); + + + // + // curve 3 + // + i++; + xMap[i].setScaleInterval(-1.1, 3.0); + yMap[i].setScaleInterval(-1.1, 3.0); + curve[i].setStyle(QwtPlotCurve::Lines); + curve[i].setCurveAttribute(QwtPlotCurve::Fitted, true); + curve[i].setPen(QColor(100,200,150)); + curveFitter = new QwtSplineCurveFitter(); + curveFitter->setFitMode(QwtSplineCurveFitter::ParametricSpline); + curveFitter->setSplineSize(200); + curve[i].setCurveFitter(curveFitter); + + curve[i].setRawData(yval,zval,Size); + + + // + // curve 4 + // + i++; + xMap[i].setScaleInterval(-5, 1.1); + yMap[i].setScaleInterval(-1.1, 5.0); + curve[i].setStyle(QwtPlotCurve::Lines); + curve[i].setCurveAttribute(QwtPlotCurve::Fitted, true); + curve[i].setPen(QColor(Qt::red)); + curveFitter = new QwtSplineCurveFitter(); + curveFitter->setSplineSize(200); + curve[i].setCurveFitter(curveFitter); + + curve[i].setRawData(uval,vval,USize); + + // + // initialize values + // + double base = 2.0 * M_PI / double(USize - 1); + double toggle = 1.0; + for (i = 0; i < USize; i++) + { + uval[i] = toggle * cos( double(i) * base); + vval[i] = toggle * sin( double(i) * base); + + if (toggle == 1.0) + toggle = 0.5; + else + toggle = 1.0; + } + + newValues(); + + // + // start timer + // + (void)startTimer(250); +} + +#if QT_VERSION >= 0x040000 +void MainWin::paintEvent(QPaintEvent *event) +{ + QFrame::paintEvent(event); + + QPainter painter(this); + painter.setClipRect(contentsRect()); + drawContents(&painter); +} +#endif + +void MainWin::drawContents(QPainter *painter) +{ + const QRect &r = contentsRect(); + + for ( int i = 0; i < curveCount; i++ ) + { + xMap[i].setPaintInterval(r.left(), r.right()); + yMap[i].setPaintInterval(r.top(), r.bottom()); + curve[i].draw(painter, xMap[i], yMap[i], r); + } +} + +// +// TIMER EVENT +// +void MainWin::timerEvent(QTimerEvent *) +{ + newValues(); + repaint(); +} + +// +// RE-CALCULATE VALUES +// +void MainWin::newValues() +{ + int i; + static double phs = 0.0; + double s,c,u; + + for (i=0;i 6.28) + phs = 0.0; + +} + +int main (int argc, char **argv) +{ + QApplication a(argc, argv); + + MainWin w; + +#if QT_VERSION < 0x040000 + a.setMainWidget(&w); +#endif + w.resize(300,300); + w.show(); + + return a.exec(); +} diff --git a/qwt/examples/curvdemo2/curvdemo2.h b/qwt/examples/curvdemo2/curvdemo2.h new file mode 100644 index 000000000..9a08301f1 --- /dev/null +++ b/qwt/examples/curvdemo2/curvdemo2.h @@ -0,0 +1,26 @@ +#include +#include +#include + +class MainWin : public QFrame +{ +public: + enum { curveCount = 4 }; + + QwtScaleMap xMap[curveCount]; + QwtScaleMap yMap[curveCount]; + QwtPlotCurve curve[curveCount]; + +public: + MainWin(); + +protected: + virtual void timerEvent(QTimerEvent *t); +#if QT_VERSION >= 0x040000 + virtual void paintEvent(QPaintEvent *); +#endif + virtual void drawContents(QPainter *); + +private: + void newValues(); +}; diff --git a/qwt/examples/curvdemo2/curvdemo2.pro b/qwt/examples/curvdemo2/curvdemo2.pro new file mode 100644 index 000000000..da6a2caa2 --- /dev/null +++ b/qwt/examples/curvdemo2/curvdemo2.pro @@ -0,0 +1,18 @@ +# -*- mode: sh -*- ################################################ +# 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( ../examples.pri ) + +TARGET = curvdemo2 + +HEADERS = \ + curvdemo2.h + +SOURCES = \ + curvdemo2.cpp diff --git a/qwt/examples/data_plot/data_plot.cpp b/qwt/examples/data_plot/data_plot.cpp new file mode 100644 index 000000000..adc9d5983 --- /dev/null +++ b/qwt/examples/data_plot/data_plot.cpp @@ -0,0 +1,147 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "data_plot.h" + +// +// Initialize main window +// +DataPlot::DataPlot(QWidget *parent): + QwtPlot(parent), + d_interval(0), + d_timerId(-1) +{ + // Disable polygon clipping + QwtPainter::setDeviceClipping(false); + + // We don't need the cache here + canvas()->setPaintAttribute(QwtPlotCanvas::PaintCached, false); + canvas()->setPaintAttribute(QwtPlotCanvas::PaintPacked, false); + +#if QT_VERSION >= 0x040000 +#ifdef Q_WS_X11 + /* + Qt::WA_PaintOnScreen is only supported for X11, but leads + to substantial bugs with Qt 4.2.x/Windows + */ + canvas()->setAttribute(Qt::WA_PaintOnScreen, true); +#endif +#endif + + alignScales(); + + // Initialize data + for (int i = 0; i< PLOT_SIZE; i++) + { + d_x[i] = 0.5 * i; // time axis + d_y[i] = 0; + d_z[i] = 0; + } + + // Assign a title + setTitle("A Test for High Refresh Rates"); + insertLegend(new QwtLegend(), QwtPlot::BottomLegend); + + // Insert new curves + QwtPlotCurve *cRight = new QwtPlotCurve("Data Moving Right"); + cRight->attach(this); + + QwtPlotCurve *cLeft = new QwtPlotCurve("Data Moving Left"); + cLeft->attach(this); + + // Set curve styles + cRight->setPen(QPen(Qt::red)); + cLeft->setPen(QPen(Qt::blue)); + + // Attach (don't copy) data. Both curves use the same x array. + cRight->setRawData(d_x, d_y, PLOT_SIZE); + cLeft->setRawData(d_x, d_z, PLOT_SIZE); + +#if 0 + // Insert zero line at y = 0 + QwtPlotMarker *mY = new QwtPlotMarker(); + mY->setLabelAlignment(Qt::AlignRight|Qt::AlignTop); + mY->setLineStyle(QwtPlotMarker::HLine); + mY->setYValue(0.0); + mY->attach(this); +#endif + + // Axis + setAxisTitle(QwtPlot::xBottom, "Time/seconds"); + setAxisScale(QwtPlot::xBottom, 0, 100); + + setAxisTitle(QwtPlot::yLeft, "Values"); + setAxisScale(QwtPlot::yLeft, -1.5, 1.5); + + setTimerInterval(0.0); +} + +// +// Set a plain canvas frame and align the scales to it +// +void DataPlot::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. + + canvas()->setFrameStyle(QFrame::Box | QFrame::Plain ); + canvas()->setLineWidth(1); + + for ( int i = 0; i < QwtPlot::axisCnt; i++ ) + { + QwtScaleWidget *scaleWidget = (QwtScaleWidget *)axisWidget(i); + if ( scaleWidget ) + scaleWidget->setMargin(0); + + QwtScaleDraw *scaleDraw = (QwtScaleDraw *)axisScaleDraw(i); + if ( scaleDraw ) + scaleDraw->enableComponent(QwtAbstractScaleDraw::Backbone, false); + } +} + +void DataPlot::setTimerInterval(double ms) +{ + d_interval = qRound(ms); + + if ( d_timerId >= 0 ) + { + killTimer(d_timerId); + d_timerId = -1; + } + if (d_interval >= 0 ) + d_timerId = startTimer(d_interval); +} + +// Generate new values +void DataPlot::timerEvent(QTimerEvent *) +{ + static double phase = 0.0; + + if (phase > (M_PI - 0.0001)) + phase = 0.0; + + // y moves from left to right: + // Shift y array right and assign new value to y[0]. + + for ( int i = PLOT_SIZE - 1; i > 0; i-- ) + d_y[i] = d_y[i-1]; + d_y[0] = sin(phase) * (-1.0 + 2.0 * double(rand()) / double(RAND_MAX)); + + for ( int j = 0; j < PLOT_SIZE - 1; j++ ) + d_z[j] = d_z[j+1]; + + d_z[PLOT_SIZE - 1] = 0.8 - (2.0 * phase/M_PI) + 0.4 * + double(rand()) / double(RAND_MAX); + + // update the display + replot(); + + phase += M_PI * 0.02; +} diff --git a/qwt/examples/data_plot/data_plot.h b/qwt/examples/data_plot/data_plot.h new file mode 100644 index 000000000..058966ac0 --- /dev/null +++ b/qwt/examples/data_plot/data_plot.h @@ -0,0 +1,32 @@ +#ifndef _DATA_PLOT_H +#define _DATA_PLOT_H 1 + +#include + +const int PLOT_SIZE = 201; // 0 to 200 + +class DataPlot : public QwtPlot +{ + Q_OBJECT + +public: + DataPlot(QWidget* = NULL); + +public slots: + void setTimerInterval(double interval); + +protected: + virtual void timerEvent(QTimerEvent *e); + +private: + void alignScales(); + + double d_x[PLOT_SIZE]; + double d_y[PLOT_SIZE]; + double d_z[PLOT_SIZE]; + + int d_interval; // timer in ms + int d_timerId; +}; + +#endif diff --git a/qwt/examples/data_plot/data_plot.pro b/qwt/examples/data_plot/data_plot.pro new file mode 100644 index 000000000..81ce4104e --- /dev/null +++ b/qwt/examples/data_plot/data_plot.pro @@ -0,0 +1,20 @@ +# -*- mode: sh -*- ################################################ +# 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( ../examples.pri ) + +TARGET = data_plot + +HEADERS = \ + data_plot.h + +SOURCES = \ + data_plot.cpp \ + main.cpp + diff --git a/qwt/examples/data_plot/main.cpp b/qwt/examples/data_plot/main.cpp new file mode 100644 index 000000000..04e56f45a --- /dev/null +++ b/qwt/examples/data_plot/main.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include +#include +#include "data_plot.h" + +class MainWindow: public QMainWindow +{ +public: + MainWindow() + { + QToolBar *toolBar = new QToolBar(this); + toolBar->setFixedHeight(80); + +#if QT_VERSION < 0x040000 + setDockEnabled(TornOff, true); + setRightJustification(true); +#else + toolBar->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea); +#endif + QWidget *hBox = new QWidget(toolBar); + QLabel *label = new QLabel("Timer Interval", hBox); + QwtCounter *counter = new QwtCounter(hBox); + counter->setRange(-1.0, 100.0, 1.0); + + QHBoxLayout *layout = new QHBoxLayout(hBox); + layout->addWidget(label); + layout->addWidget(counter); + layout->addWidget(new QWidget(hBox), 10); // spacer); + +#if QT_VERSION >= 0x040000 + toolBar->addWidget(hBox); +#endif + addToolBar(toolBar); + + + DataPlot *plot = new DataPlot(this); + setCentralWidget(plot); + + connect(counter, SIGNAL(valueChanged(double)), + plot, SLOT(setTimerInterval(double)) ); + + counter->setValue(20.0); + } +}; + +int main(int argc, char **argv) +{ + QApplication a(argc, argv); + + MainWindow mainWindow; +#if QT_VERSION < 0x040000 + a.setMainWidget(&mainWindow); +#endif + + mainWindow.resize(600,400); + mainWindow.show(); + + return a.exec(); +} diff --git a/qwt/examples/dials/attitude_indicator.cpp b/qwt/examples/dials/attitude_indicator.cpp new file mode 100644 index 000000000..910ed09de --- /dev/null +++ b/qwt/examples/dials/attitude_indicator.cpp @@ -0,0 +1,149 @@ +#include +#include +#include +#include +#include "attitude_indicator.h" + +AttitudeIndicatorNeedle::AttitudeIndicatorNeedle(const QColor &c) +{ + QPalette palette; + for ( int i = 0; i < QPalette::NColorGroups; i++ ) + { +#if QT_VERSION < 0x040000 + palette.setColor((QPalette::ColorGroup)i, + QColorGroup::Text, c); +#else + palette.setColor((QPalette::ColorGroup)i, + QPalette::Text, c); +#endif + } + setPalette(palette); +} + +void AttitudeIndicatorNeedle::draw(QPainter *painter, const QPoint ¢er, + int length, double direction, QPalette::ColorGroup cg) const +{ + direction *= M_PI / 180.0; + int triangleSize = qRound(length * 0.1); + + painter->save(); + + const QPoint p0(QPoint(center.x() + 1, center.y() + 1)); + + const QPoint p1 = qwtPolar2Pos(p0, + length - 2 * triangleSize - 2, direction); + + QwtPolygon pa(3); + pa.setPoint(0, qwtPolar2Pos(p1, 2 * triangleSize, direction)); + pa.setPoint(1, qwtPolar2Pos(p1, triangleSize, direction + M_PI_2)); + pa.setPoint(2, qwtPolar2Pos(p1, triangleSize, direction - M_PI_2)); + + const QColor color = +#if QT_VERSION < 0x040000 + palette().color(cg, QColorGroup::Text); +#else + palette().color(cg, QPalette::Text); +#endif + painter->setBrush(color); + painter->drawPolygon(pa); + + painter->setPen(QPen(color, 3)); + painter->drawLine(qwtPolar2Pos(p0, length - 2, direction + M_PI_2), + qwtPolar2Pos(p0, length - 2, direction - M_PI_2)); + + painter->restore(); +} + +AttitudeIndicator::AttitudeIndicator( + QWidget *parent): + QwtDial(parent), + d_gradient(0.0) +{ + setMode(RotateScale); + setWrapping(true); + + setOrigin(270.0); + setScaleOptions(ScaleTicks); + setScale(0, 0, 30.0); + + const QColor color = +#if QT_VERSION < 0x040000 + colorGroup().text(); +#else + palette().color(QPalette::Text); +#endif + + 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 QPoint ¢er, + int radius, double origin, double minArc, double maxArc) const +{ + double dir = (360.0 - origin) * M_PI / 180.0; // counter clockwise, radian + + int offset = 4; + + const QPoint p0 = qwtPolar2Pos(center, offset, dir + M_PI); + + const int w = contentsRect().width(); + + QwtPolygon pa(4); + pa.setPoint(0, qwtPolar2Pos(p0, w, dir - M_PI_2)); + pa.setPoint(1, qwtPolar2Pos(pa.point(0), 2 * w, dir + M_PI_2)); + pa.setPoint(2, qwtPolar2Pos(pa.point(1), w, dir)); + pa.setPoint(3, qwtPolar2Pos(pa.point(2), 2 * w, dir - M_PI_2)); + + painter->save(); + painter->setClipRegion(pa); // swallow 180 - 360 degrees + + QwtDial::drawScale(painter, center, radius, origin, + minArc, maxArc); + + painter->restore(); +} + +void AttitudeIndicator::drawScaleContents(QPainter *painter, + const QPoint &, int) 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(scaleContentsRect(), + (dir - arc) * 16, 2 * arc * 16 ); + painter->restore(); +} + +void AttitudeIndicator::keyPressEvent(QKeyEvent *e) +{ + switch(e->key()) + { + case Qt::Key_Plus: + setGradient(gradient() + 0.05); + break; + + case Qt::Key_Minus: + setGradient(gradient() - 0.05); + break; + + default: + QwtDial::keyPressEvent(e); + } +} diff --git a/qwt/examples/dials/attitude_indicator.h b/qwt/examples/dials/attitude_indicator.h new file mode 100644 index 000000000..00c4b433e --- /dev/null +++ b/qwt/examples/dials/attitude_indicator.h @@ -0,0 +1,38 @@ +#include +#include + +class AttitudeIndicatorNeedle: public QwtDialNeedle +{ +public: + AttitudeIndicatorNeedle(const QColor &); + + virtual void draw(QPainter *, const QPoint &, int length, + double direction, 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 slots: + void setGradient(double); + void setAngle(double angle) { setValue(angle); } + +protected: + virtual void keyPressEvent(QKeyEvent *); + + virtual void drawScale(QPainter *, const QPoint ¢er, + int radius, double origin, double arcMin, double arcMax) const; + + virtual void drawScaleContents(QPainter *painter, + const QPoint ¢er, int 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..eb4dc224d --- /dev/null +++ b/qwt/examples/dials/cockpit_grid.cpp @@ -0,0 +1,206 @@ +#include +#include +#include +#include "attitude_indicator.h" +#include "speedo_meter.h" +#include "cockpit_grid.h" + +#if QT_VERSION < 0x040000 +typedef QColorGroup Palette; +#else +typedef QPalette Palette; +#endif + +CockpitGrid::CockpitGrid(QWidget *parent): + QFrame(parent) +{ +#if QT_VERSION >= 0x040100 + setAutoFillBackground(true); +#endif + + 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); + } + +#if QT_VERSION < 0x040000 + for ( i = 0; i < layout->numCols(); i++ ) + layout->setColStretch(i, 1); +#else + for ( i = 0; i < layout->columnCount(); i++ ) + layout->setColumnStretch(i, 1); +#endif +} + +QwtDial *CockpitGrid::createDial(int pos) +{ + QwtDial *dial = NULL; + switch(pos) + { + case 0: + { + d_clock = new QwtAnalogClock(this); + + 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((QwtAnalogClock::Hand)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->setRange(0.0, 240.0); + d_speedo->setScale(-1, 2, 20); + + 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); + + 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->scaleDraw()->setPenWidth(3); + dial->setLineWidth(4); + dial->setFrameShadow(QwtDial::Sunken); + } + return dial; +} + +QPalette CockpitGrid::colorTheme(const QColor &base) const +{ + const QColor background = base.dark(150); + const QColor foreground = base.dark(200); + + const QColor mid = base.dark(110); + const QColor dark = base.dark(170); + const QColor light = base.light(170); + const QColor text = foreground.light(800); + + QPalette palette; + for ( int i = 0; i < QPalette::NColorGroups; i++ ) + { + QPalette::ColorGroup cg = (QPalette::ColorGroup)i; + + palette.setColor(cg, Palette::Base, base); + palette.setColor(cg, Palette::Background, background); + palette.setColor(cg, Palette::Mid, mid); + palette.setColor(cg, Palette::Light, light); + palette.setColor(cg, Palette::Dark, dark); + palette.setColor(cg, Palette::Text, text); + palette.setColor(cg, Palette::Foreground, foreground); + } + + return palette; +} + +void CockpitGrid::changeSpeed() +{ + static double offset = 0.8; + + double speed = d_speedo->value(); + + if ( (speed < 40.0 && offset < 0.0 ) || + (speed > 160.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..58101c306 --- /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 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..0d924580e --- /dev/null +++ b/qwt/examples/dials/compass_grid.cpp @@ -0,0 +1,225 @@ +#include +#include +#include +#include +#include "compass_grid.h" + +#if QT_VERSION < 0x040000 +typedef QColorGroup Palette; +#else +typedef QPalette Palette; +#endif + +CompassGrid::CompassGrid(QWidget *parent): + QFrame(parent) +{ +#if QT_VERSION < 0x040000 + setBackgroundColor(Qt::gray); +#else + QPalette p = palette(); + p.setColor(backgroundRole(), Qt::gray); + setPalette(p); +#endif + +#if QT_VERSION >= 0x040100 + setAutoFillBackground(true); +#endif + + 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); + } + +#if QT_VERSION < 0x040000 + for ( i = 0; i < layout->numCols(); i++ ) + layout->setColStretch(i, 1); +#else + for ( i = 0; i < layout->columnCount(); i++ ) + layout->setColumnStretch(i, 1); +#endif +} + +QwtCompass *CompassGrid::createCompass(int pos) +{ + int c; + + Palette colorGroup; + for ( c = 0; c < Palette::NColorRoles; c++ ) + colorGroup.setColor((Palette::ColorRole)c, QColor()); + +#if QT_VERSION < 0x040000 + colorGroup.setColor(Palette::Base, backgroundColor().light(120)); +#else + colorGroup.setColor(Palette::Base, + palette().color(backgroundRole()).light(120)); +#endif + colorGroup.setColor(Palette::Foreground, + colorGroup.color(Palette::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->setLabelMap(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. + */ + + colorGroup.setColor(Palette::Base, Qt::darkBlue); + colorGroup.setColor(Palette::Foreground, + QColor(Qt::darkBlue).dark(120)); + colorGroup.setColor(Palette::Text, Qt::white); + + compass->setScaleOptions(QwtDial::ScaleTicks | QwtDial::ScaleLabel); + compass->setScaleTicks(1, 1, 3); + compass->setScale(36, 5, 0); + + 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 + */ +#if QT_VERSION < 0x040000 + colorGroup.setColor(Palette::Base, backgroundColor()); +#else + colorGroup.setColor(Palette::Base, + palette().color(backgroundRole())); +#endif + colorGroup.setColor(Palette::Foreground, Qt::blue); + + compass->setLineWidth(0); + + compass->setScaleOptions(QwtDial::ScaleBackbone | + QwtDial::ScaleTicks | QwtDial::ScaleLabel); + compass->setScaleTicks(0, 0, 3); + + QMap map; + for ( double d = 0.0; d < 360.0; d += 60.0 ) + { + QString label; + label.sprintf("%.0f", d); + map.insert(d, label); + } + compass->setLabelMap(map); + compass->setScale(36, 5, 0); + + compass->setNeedle(new QwtDialSimpleNeedle(QwtDialSimpleNeedle::Ray, + false, Qt::white)); + compass->setOrigin(220.0); + compass->setValue(20.0); + break; + } + case 4: + { + /* + A compass showing another needle + */ + compass->setScaleOptions(QwtDial::ScaleTicks | QwtDial::ScaleLabel); + compass->setScaleTicks(0, 0, 3); + + 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 + */ + colorGroup.setColor(Palette::Foreground, Qt::black); + + compass->setNeedle(new QwtDialSimpleNeedle(QwtDialSimpleNeedle::Ray, + false, Qt::yellow)); + compass->setValue(315.0); + break; + } + } + + QPalette newPalette = compass->palette(); + for ( c = 0; c < Palette::NColorRoles; c++ ) + { + if ( colorGroup.color((Palette::ColorRole)c).isValid() ) + { + for ( int cg = 0; cg < QPalette::NColorGroups; cg++ ) + { + newPalette.setColor( + (QPalette::ColorGroup)cg, + (Palette::ColorRole)c, + colorGroup.color((Palette::ColorRole)c)); + } + } + } + + for ( int i = 0; i < QPalette::NColorGroups; i++ ) + { + QPalette::ColorGroup cg = (QPalette::ColorGroup)i; + + const QColor light = + newPalette.color(cg, Palette::Base).light(170); + const QColor dark = newPalette.color(cg, Palette::Base).dark(170); + const QColor mid = compass->frameShadow() == QwtDial::Raised + ? newPalette.color(cg, Palette::Base).dark(110) + : newPalette.color(cg, Palette::Base).light(110); + + newPalette.setColor(cg, Palette::Dark, dark); + newPalette.setColor(cg, Palette::Mid, mid); + newPalette.setColor(cg, Palette::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..912cac107 --- /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..ce0c4db61 --- /dev/null +++ b/qwt/examples/dials/dials.cpp @@ -0,0 +1,30 @@ +#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; +#if QT_VERSION < 0x040000 + tabWidget.addTab(new CompassGrid(&tabWidget), "Compass"); + tabWidget.addTab(new CockpitGrid(&tabWidget), "Cockpit"); + a.setMainWidget(&tabWidget); +#else + tabWidget.addTab(new CompassGrid, "Compass"); + tabWidget.addTab(new CockpitGrid, "Cockpit"); +#endif + + 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..02c8a7374 --- /dev/null +++ b/qwt/examples/dials/dials.pro @@ -0,0 +1,26 @@ +# -*- mode: sh -*- ################################################ +# 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( ../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..358f65f79 --- /dev/null +++ b/qwt/examples/dials/speedo_meter.cpp @@ -0,0 +1,52 @@ +#include +#include +#include "speedo_meter.h" + +SpeedoMeter::SpeedoMeter(QWidget *parent): + QwtDial(parent), + d_label("km/h") +{ + setWrapping(false); + setReadOnly(true); + + setOrigin(135.0); + setScaleArc(0.0, 270.0); + scaleDraw()->setSpacing(8); + + QwtDialSimpleNeedle *needle = new QwtDialSimpleNeedle( + QwtDialSimpleNeedle::Arrow, true, Qt::red, + QColor(Qt::gray).light(130)); + setNeedle(needle); + + setScaleOptions(ScaleTicks | ScaleLabel); + setScaleTicks(0, 4, 8); +} + +void SpeedoMeter::setLabel(const QString &label) +{ + d_label = label; + update(); +} + +QString SpeedoMeter::label() const +{ + return d_label; +} + +void SpeedoMeter::drawScaleContents(QPainter *painter, + const QPoint ¢er, int radius) const +{ + QRect rect(0, 0, 2 * radius, 2 * radius - 10); + rect.moveCenter(center); + + const QColor color = +#if QT_VERSION < 0x040000 + colorGroup().text(); +#else + palette().color(QPalette::Text); +#endif + 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..d779cd151 --- /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 QPoint ¢er, int radius) const; + +private: + QString d_label; +}; 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..1fd3a8c87 --- /dev/null +++ b/qwt/examples/event_filter/canvaspicker.cpp @@ -0,0 +1,329 @@ +#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 = plot->canvas(); + + canvas->installEventFilter(this); + + // We want the focus, but no focus rect. The + // selected point will be highlighted instead. + +#if QT_VERSION >= 0x040000 + canvas->setFocusPolicy(Qt::StrongFocus); +#ifndef QT_NO_CURSOR + canvas->setCursor(Qt::PointingHandCursor); +#endif +#else + canvas->setFocusPolicy(QWidget::StrongFocus); +#ifndef QT_NO_CURSOR + canvas->setCursor(Qt::pointingHandCursor); +#endif +#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"; +#if QT_VERSION >= 0x040000 + canvas->setWhatsThis(text); +#else + QWhatsThis::add(canvas, text); +#endif + + shiftCurveCursor(true); +} + +bool CanvasPicker::event(QEvent *e) +{ + if ( e->type() == QEvent::User ) + { + showCursor(true); + return true; + } + return QObject::event(e); +} + +bool CanvasPicker::eventFilter(QObject *object, QEvent *e) +{ + if ( object != (QObject *)plot()->canvas() ) + return false; + + switch(e->type()) + { + case QEvent::FocusIn: + showCursor(true); + case QEvent::FocusOut: + showCursor(false); + + case QEvent::Paint: + { + QApplication::postEvent(this, new QEvent(QEvent::User)); + break; + } + case QEvent::MouseButtonPress: + { + select(((QMouseEvent *)e)->pos()); + return true; + } + case QEvent::MouseMove: + { + move(((QMouseEvent *)e)->pos()); + return true; + } + case QEvent::KeyPress: + { + const int delta = 5; + switch(((const QKeyEvent *)e)->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, e); +} + +// 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 = (QwtPlotCurve*)(*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 int x = plot()->transform(d_selectedCurve->xAxis(), + d_selectedCurve->x(d_selectedPoint)) + dx; + const int y = plot()->transform(d_selectedCurve->yAxis(), + d_selectedCurve->y(d_selectedPoint)) + dy; + + move(QPoint(x, y)); +} + +// Move the selected point +void CanvasPicker::move(const QPoint &pos) +{ + if ( !d_selectedCurve ) + return; + + QwtArray xData(d_selectedCurve->dataSize()); + QwtArray yData(d_selectedCurve->dataSize()); + + for ( int i = 0; i < 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 + { + xData[i] = d_selectedCurve->x(i); + yData[i] = d_selectedCurve->y(i); + } + } + d_selectedCurve->setData(xData, yData); + + plot()->replot(); + showCursor(true); +} + +// Hightlight the selected point +void CanvasPicker::showCursor(bool showIt) +{ + if ( !d_selectedCurve ) + return; + + const QwtSymbol symbol = d_selectedCurve->symbol(); + + QwtSymbol newSymbol = symbol; + if ( showIt ) + newSymbol.setBrush(symbol.brush().color().dark(150)); + + const bool doReplot = plot()->autoReplot(); + + plot()->setAutoReplot(false); + d_selectedCurve->setSymbol(newSymbol); + + d_selectedCurve->draw(d_selectedPoint, d_selectedPoint); + + d_selectedCurve->setSymbol(symbol); + plot()->setAutoReplot(doReplot); +} + +// 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 = (QwtPlotCurve *)*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..89113829d --- /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() { return (QwtPlot *)parent(); } + const QwtPlot *plot() const { return (QwtPlot *)parent(); } + + 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..ab4fbecce --- /dev/null +++ b/qwt/examples/event_filter/colorbar.cpp @@ -0,0 +1,125 @@ +#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 +#if QT_VERSION < 0x040000 + setCursor(Qt::pointingHandCursor); +#else + setCursor(Qt::PointingHandCursor); +#endif +#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); +#if QT_VERSION < 0x040000 + const QRgb rgb = pm.convertToImage().pixel(e->x(), e->y()); +#else + const QRgb rgb = pm.toImage().pixel(e->x(), e->y()); +#endif + + 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; + +#if QT_VERSION < 0x040000 + d_light.hsv(&h1, &s1, &v1); + d_dark.hsv(&h2, &s2, &v2); +#else + d_light.getHsv(&h1, &s1, &v1); + d_dark.getHsv(&h2, &s2, &v2); +#endif + + painter->save(); + painter->setClipRect(rect); + painter->setClipping(true); + + painter->fillRect(rect, d_dark); + + const int sectionSize = 2; + + int numIntervalls; + if ( d_orientation == Qt::Horizontal ) + numIntervalls = rect.width() / sectionSize; + else + numIntervalls = rect.height() / sectionSize; + + for ( int i = 0; i < numIntervalls; 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 / (double)numIntervalls; + + 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..30907874c --- /dev/null +++ b/qwt/examples/event_filter/colorbar.h @@ -0,0 +1,34 @@ +#include + +class ColorBar: public QWidget +{ + Q_OBJECT + +public: + ColorBar(Qt::Orientation = Qt::Horizontal, + QWidget * = NULL); + + virtual void setOrientation(Qt::Orientation o); + 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; } + +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..291a555f3 --- /dev/null +++ b/qwt/examples/event_filter/event_filter.cpp @@ -0,0 +1,62 @@ +//----------------------------------------------------------------- +// 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); +#if QT_VERSION >= 0x040000 + QAction *action = QWhatsThis::createAction(toolBar); + toolBar->addAction(action); + mainWindow.addToolBar(toolBar); +#else + (void)QWhatsThis::whatsThisButton(toolBar); +#endif + + 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); +#if QT_VERSION < 0x040000 + a.setMainWidget(&mainWindow); +#endif + + 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."; +#if QT_VERSION < 0x040000 + QWhatsThis::add(plot, text); +#else + plot->setWhatsThis(text); +#endif + + 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..43139d61b --- /dev/null +++ b/qwt/examples/event_filter/event_filter.pro @@ -0,0 +1,25 @@ +# -*- mode: sh -*- ################################################ +# 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( ../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..278600420 --- /dev/null +++ b/qwt/examples/event_filter/plot.cpp @@ -0,0 +1,196 @@ +#include "plot.h" +#include "colorbar.h" +#include +#if QT_VERSION < 0x040000 +#include +#endif +#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->setMajPen(QPen(Qt::white, 0, Qt::DotLine)); + grid->attach(this); + + // axes + + setAxisScale(QwtPlot::xBottom, 0.0, 100.0); + setAxisScale(QwtPlot::yLeft, 0.0, 100.0); + + // Avoid jumping when label with 3 digits + // appear/disappear when scrolling vertically + + QwtScaleDraw *sd = axisScaleDraw(QwtPlot::yLeft); + sd->setMinimumExtent( sd->extent(QPen(), + axisWidget(QwtPlot::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 = (QwtScaleWidget *)axisWidget(yLeft); + scaleWidget->setMargin(10); // area for the color bar + d_colorBar = new ColorBar(Qt::Vertical, scaleWidget); + d_colorBar->setRange(Qt::red, Qt::darkBlue); +#if QT_VERSION >= 0x040000 + d_colorBar->setFocusPolicy( Qt::TabFocus ); +#else + d_colorBar->setFocusPolicy( QWidget::TabFocus ); +#endif + + 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); + +#if QT_VERSION < 0x040000 + QWhatsThis::add(d_colorBar, + "Selecting a color will change the background of the plot."); + QWhatsThis::add(scaleWidget, + "Selecting a value at the scale will insert a new curve."); + QWhatsThis::add(d_wheel, + "With the wheel you can move the visible area."); + QWhatsThis::add(axisWidget(xBottom), + "Selecting a value at the scale will insert a new curve."); +#else + 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(xBottom)->setWhatsThis( + "Selecting a value at the scale will insert a new curve."); +#endif + +} + +void Plot::setCanvasColor(const QColor &c) +{ + setCanvasBackground(c); + replot(); +} + +void Plot::scrollLeftAxis(double value) +{ + setAxisScale(yLeft, value, value + 100.0); + replot(); +} + +bool Plot::eventFilter(QObject *object, QEvent *e) +{ + if ( e->type() == QEvent::Resize ) + { + const QSize &size = ((QResizeEvent *)e)->size(); + if ( object == (QObject *)axisWidget(yLeft) ) + { + const QwtScaleWidget *scaleWidget = axisWidget(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) +{ + Qt::Orientation o; + if ( axis == yLeft || axis == yRight ) + o = Qt::Horizontal; + else + o = Qt::Vertical; + + QRgb rgb = (uint)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(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->setData(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..9523acd88 --- /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 slots: + void setCanvasColor(const QColor &); + void insertCurve(int axis, double base); + +private 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..57ffeae17 --- /dev/null +++ b/qwt/examples/event_filter/scalepicker.cpp @@ -0,0 +1,114 @@ +#include +#include +#include +#include "scalepicker.h" + +ScalePicker::ScalePicker(QwtPlot *plot): + QObject(plot) +{ + for ( uint i = 0; i < QwtPlot::axisCnt; i++ ) + { + QwtScaleWidget *scaleWidget = (QwtScaleWidget *)plot->axisWidget(i); + if ( scaleWidget ) + scaleWidget->installEventFilter(this); + } +} + +bool ScalePicker::eventFilter(QObject *object, QEvent *e) +{ + if ( object->inherits("QwtScaleWidget") && + e->type() == QEvent::MouseButtonPress ) + { + mouseClicked((const QwtScaleWidget *)object, + ((QMouseEvent *)e)->pos()); + return true; + } + + return QObject::eventFilter(object, e); +} + +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->map().invTransform(pos.y()); + axis = QwtPlot::yLeft; + break; + } + case QwtScaleDraw::RightScale: + { + value = sd->map().invTransform(pos.y()); + axis = QwtPlot::yRight; + break; + } + case QwtScaleDraw::BottomScale: + { + value = sd->map().invTransform(pos.x()); + axis = QwtPlot::xBottom; + break; + } + case QwtScaleDraw::TopScale: + { + value = sd->map().invTransform(pos.x()); + axis = QwtPlot::xTop; + break; + } + } + 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 = scale->scaleDraw()->majTickLength(); + 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..f40399eee --- /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 *); + +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..617af51b6 --- /dev/null +++ b/qwt/examples/examples.pri @@ -0,0 +1,61 @@ +# -*- mode: sh -*- ################################################ +# 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 = ../.. + +include( $${QWT_ROOT}/qwtconfig.pri ) + +SUFFIX_STR = +VVERSION = $$[QT_VERSION] +isEmpty(VVERSION) { + + # Qt 3 + debug { + SUFFIX_STR = $${DEBUG_SUFFIX} + } + else { + SUFFIX_STR = $${RELEASE_SUFFIX} + } +} +else { + CONFIG(debug, debug|release) { + SUFFIX_STR = $${DEBUG_SUFFIX} + } + else { + SUFFIX_STR = $${RELEASE_SUFFIX} + } +} + +TEMPLATE = app + +MOC_DIR = moc +INCLUDEPATH += $${QWT_ROOT}/src +DEPENDPATH += $${QWT_ROOT}/src +OBJECTS_DIR = obj$${SUFFIX_STR} +DESTDIR = $${QWT_ROOT}/examples/bin$${SUFFIX_STR} + +QWTLIB = qwt$${SUFFIX_STR} + +win32 { + contains(CONFIG, QwtDll) { + DEFINES += QT_DLL QWT_DLL + QWTLIB = $${QWTLIB}$${VER_MAJ} + } + + win32-msvc:LIBS += $${QWT_ROOT}/lib/$${QWTLIB}.lib + win32-msvc.net:LIBS += $${QWT_ROOT}/lib/$${QWTLIB}.lib + win32-msvc2002:LIBS += $${QWT_ROOT}/lib/$${QWTLIB}.lib + win32-msvc2003:LIBS += $${QWT_ROOT}/lib/$${QWTLIB}.lib + win32-msvc2005:LIBS += $${QWT_ROOT}/lib/$${QWTLIB}.lib + win32-msvc2008:LIBS += $${QWT_ROOT}/lib/$${QWTLIB}.lib + win32-g++:LIBS += -L$${QWT_ROOT}/lib -l$${QWTLIB} +} +else { + LIBS += -L$${QWT_ROOT}/lib -l$${QWTLIB} +} diff --git a/qwt/examples/examples.pro b/qwt/examples/examples.pro new file mode 100644 index 000000000..0912d1260 --- /dev/null +++ b/qwt/examples/examples.pro @@ -0,0 +1,49 @@ +# -*- mode: sh -*- ################################################ +# 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 + +contains(CONFIG, QwtPlot) { + + SUBDIRS += \ + cpuplot \ + curvdemo1 \ + curvdemo2 \ + simple_plot \ + realtime_plot \ + spectrogram \ + histogram + + contains(CONFIG, QwtWidgets) { + + SUBDIRS += \ + bode \ + data_plot \ + event_filter + } + + contains(CONFIG, QwtSVGItem) { + + SUBDIRS += \ + svgmap + } +} + +contains(CONFIG, QwtWidgets) { + + SUBDIRS += \ + sysinfo \ + radio \ + dials \ + sliders +} + + diff --git a/qwt/examples/histogram/histogram.pro b/qwt/examples/histogram/histogram.pro new file mode 100644 index 000000000..55a4751c9 --- /dev/null +++ b/qwt/examples/histogram/histogram.pro @@ -0,0 +1,19 @@ +# -*- mode: sh -*- ################################################ +# 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( ../examples.pri ) + +TARGET = histogram + +SOURCES = \ + main.cpp \ + histogram_item.cpp + +INCLUDES = \ + histogram_item.h diff --git a/qwt/examples/histogram/histogram_item.cpp b/qwt/examples/histogram/histogram_item.cpp new file mode 100644 index 000000000..12b145343 --- /dev/null +++ b/qwt/examples/histogram/histogram_item.cpp @@ -0,0 +1,283 @@ +#include +#include +#include +#include +#include +#include +#include "histogram_item.h" + +class HistogramItem::PrivateData +{ +public: + int attributes; + QwtIntervalData data; + QColor color; + double reference; +}; + +HistogramItem::HistogramItem(const QwtText &title): + QwtPlotItem(title) +{ + init(); +} + +HistogramItem::HistogramItem(const QString &title): + QwtPlotItem(QwtText(title)) +{ + init(); +} + +HistogramItem::~HistogramItem() +{ + delete d_data; +} + +void HistogramItem::init() +{ + d_data = new PrivateData(); + d_data->reference = 0.0; + d_data->attributes = HistogramItem::Auto; + + setItemAttribute(QwtPlotItem::AutoScale, true); + setItemAttribute(QwtPlotItem::Legend, true); + + setZ(20.0); +} + +void HistogramItem::setBaseline(double reference) +{ + if ( d_data->reference != reference ) + { + d_data->reference = reference; + itemChanged(); + } +} + +double HistogramItem::baseline() const +{ + return d_data->reference; +} + +void HistogramItem::setData(const QwtIntervalData &data) +{ + d_data->data = data; + itemChanged(); +} + +const QwtIntervalData &HistogramItem::data() const +{ + return d_data->data; +} + +void HistogramItem::setColor(const QColor &color) +{ + if ( d_data->color != color ) + { + d_data->color = color; + itemChanged(); + } +} + +QColor HistogramItem::color() const +{ + return d_data->color; +} + +QwtDoubleRect HistogramItem::boundingRect() const +{ + QwtDoubleRect rect = d_data->data.boundingRect(); + if ( !rect.isValid() ) + return rect; + + if ( d_data->attributes & Xfy ) + { + rect = QwtDoubleRect( rect.y(), rect.x(), + rect.height(), rect.width() ); + + if ( rect.left() > d_data->reference ) + rect.setLeft( d_data->reference ); + else if ( rect.right() < d_data->reference ) + rect.setRight( d_data->reference ); + } + else + { + if ( rect.bottom() < d_data->reference ) + rect.setBottom( d_data->reference ); + else if ( rect.top() > d_data->reference ) + rect.setTop( d_data->reference ); + } + + return rect; +} + + +int HistogramItem::rtti() const +{ + return QwtPlotItem::Rtti_PlotHistogram; +} + +void HistogramItem::setHistogramAttribute(HistogramAttribute attribute, bool on) +{ + if ( bool(d_data->attributes & attribute) == on ) + return; + + if ( on ) + d_data->attributes |= attribute; + else + d_data->attributes &= ~attribute; + + itemChanged(); +} + +bool HistogramItem::testHistogramAttribute(HistogramAttribute attribute) const +{ + return d_data->attributes & attribute; +} + +void HistogramItem::draw(QPainter *painter, const QwtScaleMap &xMap, + const QwtScaleMap &yMap, const QRect &) const +{ + const QwtIntervalData &iData = d_data->data; + + painter->setPen(QPen(d_data->color)); + + const int x0 = xMap.transform(baseline()); + const int y0 = yMap.transform(baseline()); + + for ( int i = 0; i < (int)iData.size(); i++ ) + { + if ( d_data->attributes & HistogramItem::Xfy ) + { + const int x2 = xMap.transform(iData.value(i)); + if ( x2 == x0 ) + continue; + + int y1 = yMap.transform( iData.interval(i).minValue()); + int y2 = yMap.transform( iData.interval(i).maxValue()); + if ( y1 > y2 ) + qSwap(y1, y2); + + if ( i < (int)iData.size() - 2 ) + { + const int yy1 = yMap.transform(iData.interval(i+1).minValue()); + const int yy2 = yMap.transform(iData.interval(i+1).maxValue()); + + if ( y2 == qwtMin(yy1, yy2) ) + { + const int xx2 = xMap.transform( + iData.interval(i+1).minValue()); + if ( xx2 != x0 && ( (xx2 < x0 && x2 < x0) || + (xx2 > x0 && x2 > x0) ) ) + { + // One pixel distance between neighboured bars + y2++; + } + } + } + + drawBar(painter, Qt::Horizontal, + QRect(x0, y1, x2 - x0, y2 - y1)); + } + else + { + const int y2 = yMap.transform(iData.value(i)); + if ( y2 == y0 ) + continue; + + int x1 = xMap.transform(iData.interval(i).minValue()); + int x2 = xMap.transform(iData.interval(i).maxValue()); + if ( x1 > x2 ) + qSwap(x1, x2); + + if ( i < (int)iData.size() - 2 ) + { + const int xx1 = xMap.transform(iData.interval(i+1).minValue()); + const int xx2 = xMap.transform(iData.interval(i+1).maxValue()); + + if ( x2 == qwtMin(xx1, xx2) ) + { + const int yy2 = yMap.transform(iData.value(i+1)); + if ( yy2 != y0 && ( (yy2 < y0 && y2 < y0) || + (yy2 > y0 && y2 > y0) ) ) + { + // One pixel distance between neighboured bars + x2--; + } + } + } + drawBar(painter, Qt::Vertical, + QRect(x1, y0, x2 - x1, y2 - y0) ); + } + } +} + +void HistogramItem::drawBar(QPainter *painter, + Qt::Orientation, const QRect& rect) const +{ + painter->save(); + + const QColor color(painter->pen().color()); +#if QT_VERSION >= 0x040000 + const QRect r = rect.normalized(); +#else + const QRect r = rect.normalize(); +#endif + + const int factor = 125; + const QColor light(color.light(factor)); + const QColor dark(color.dark(factor)); + + painter->setBrush(color); + painter->setPen(Qt::NoPen); + QwtPainter::drawRect(painter, r.x() + 1, r.y() + 1, + r.width() - 2, r.height() - 2); + painter->setBrush(Qt::NoBrush); + + painter->setPen(QPen(light, 2)); +#if QT_VERSION >= 0x040000 + QwtPainter::drawLine(painter, + r.left() + 1, r.top() + 2, r.right() + 1, r.top() + 2); +#else + QwtPainter::drawLine(painter, + r.left(), r.top() + 2, r.right() + 1, r.top() + 2); +#endif + + painter->setPen(QPen(dark, 2)); +#if QT_VERSION >= 0x040000 + QwtPainter::drawLine(painter, + r.left() + 1, r.bottom(), r.right() + 1, r.bottom()); +#else + QwtPainter::drawLine(painter, + r.left(), r.bottom(), r.right() + 1, r.bottom()); +#endif + + painter->setPen(QPen(light, 1)); + +#if QT_VERSION >= 0x040000 + QwtPainter::drawLine(painter, + r.left(), r.top() + 1, r.left(), r.bottom()); + QwtPainter::drawLine(painter, + r.left() + 1, r.top() + 2, r.left() + 1, r.bottom() - 1); +#else + QwtPainter::drawLine(painter, + r.left(), r.top() + 1, r.left(), r.bottom() + 1); + QwtPainter::drawLine(painter, + r.left() + 1, r.top() + 2, r.left() + 1, r.bottom()); +#endif + + painter->setPen(QPen(dark, 1)); + +#if QT_VERSION >= 0x040000 + QwtPainter::drawLine(painter, + r.right() + 1, r.top() + 1, r.right() + 1, r.bottom()); + QwtPainter::drawLine(painter, + r.right(), r.top() + 2, r.right(), r.bottom() - 1); +#else + QwtPainter::drawLine(painter, + r.right() + 1, r.top() + 1, r.right() + 1, r.bottom() + 1); + QwtPainter::drawLine(painter, + r.right(), r.top() + 2, r.right(), r.bottom()); +#endif + + painter->restore(); +} diff --git a/qwt/examples/histogram/histogram_item.h b/qwt/examples/histogram/histogram_item.h new file mode 100644 index 000000000..b5edf3275 --- /dev/null +++ b/qwt/examples/histogram/histogram_item.h @@ -0,0 +1,64 @@ +/* -*- 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 HISTOGRAM_ITEM_H +#define HISTOGRAM_ITEM_H + +#include +#include + +#include "qwt_plot_item.h" + +class QwtIntervalData; +class QString; + +class HistogramItem: public QwtPlotItem +{ +public: + explicit HistogramItem(const QString &title = QString::null); + explicit HistogramItem(const QwtText &title); + virtual ~HistogramItem(); + + void setData(const QwtIntervalData &data); + const QwtIntervalData &data() const; + + void setColor(const QColor &); + QColor color() const; + + virtual QwtDoubleRect boundingRect() const; + + virtual int rtti() const; + + virtual void draw(QPainter *, const QwtScaleMap &xMap, + const QwtScaleMap &yMap, const QRect &) const; + + void setBaseline(double reference); + double baseline() const; + + enum HistogramAttribute + { + Auto = 0, + Xfy = 1 + }; + + void setHistogramAttribute(HistogramAttribute, bool on = true); + bool testHistogramAttribute(HistogramAttribute) const; + +protected: + virtual void drawBar(QPainter *, + Qt::Orientation o, const QRect &) const; + +private: + void init(); + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/examples/histogram/main.cpp b/qwt/examples/histogram/main.cpp new file mode 100644 index 000000000..932c38953 --- /dev/null +++ b/qwt/examples/histogram/main.cpp @@ -0,0 +1,60 @@ +#include +#include +#include +#include +#include +#include +#include +#include "histogram_item.h" + +int main(int argc, char **argv) +{ + QApplication a(argc, argv); + + QwtPlot plot; + plot.setCanvasBackground(QColor(Qt::white)); + plot.setTitle("Histogram"); + + QwtPlotGrid *grid = new QwtPlotGrid; + grid->enableXMin(true); + grid->enableYMin(true); + grid->setMajPen(QPen(Qt::black, 0, Qt::DotLine)); + grid->setMinPen(QPen(Qt::gray, 0 , Qt::DotLine)); + grid->attach(&plot); + + HistogramItem *histogram = new HistogramItem(); + histogram->setColor(Qt::darkCyan); + + const int numValues = 20; + + QwtArray intervals(numValues); + QwtArray values(numValues); + + double pos = 0.0; + for ( int i = 0; i < (int)intervals.size(); i++ ) + { + const int width = 5 + rand() % 15; + const int value = rand() % 100; + + intervals[i] = QwtDoubleInterval(pos, pos + double(width)); + values[i] = value; + + pos += width; + } + + histogram->setData(QwtIntervalData(intervals, values)); + histogram->attach(&plot); + + plot.setAxisScale(QwtPlot::yLeft, 0.0, 100.0); + plot.setAxisScale(QwtPlot::xBottom, 0.0, pos); + plot.replot(); + +#if QT_VERSION < 0x040000 + a.setMainWidget(&plot); +#endif + + plot.resize(600,400); + plot.show(); + + return a.exec(); +} diff --git a/qwt/examples/radio/ampfrm.cpp b/qwt/examples/radio/ampfrm.cpp new file mode 100644 index 000000000..a40580280 --- /dev/null +++ b/qwt/examples/radio/ampfrm.cpp @@ -0,0 +1,182 @@ +#include "ampfrm.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class Knob: public QWidget +{ +public: + Knob(const QString &title, double min, double max, QWidget *parent): + QWidget(parent) + { + d_knob = new QwtKnob(this); + d_knob->setRange(min, max, 0,1); + d_knob->setScaleMaxMajor(10); + + d_knob->setKnobWidth(50); + + 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 = qwtMax(sz1.width(), sz2.width()); + const int h = sz1.height() + sz2.height(); + + int off = d_knob->scaleDraw()->extent(QPen(), d_knob->font()); + off -= 10; // spacing + + return QSize(w, h - off); + } + + 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 = d_knob->scaleDraw()->extent(QPen(), 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->setRange(-40,10); + d_thermo->setFillColor(Qt::green); + d_thermo->setAlarmColor(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); + + (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()) + * sin(13.0 * phs); + const double sig_mid_l = sin(17.0 * phs); + const double sig_mid_r = cos(17.5 * phs); + const double sig_trbl_l = 0.5 * (1.0 + 0.1 * d_knbTreble->value()) + * sin(35.0 * phs); + const double sig_trbl_r = 0.5 * (1.0 + 0.1 * d_knbTreble->value()) + * sin(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..c642a57cc --- /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 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/radio.cpp b/qwt/examples/radio/radio.cpp new file mode 100644 index 000000000..377f871c2 --- /dev/null +++ b/qwt/examples/radio/radio.cpp @@ -0,0 +1,40 @@ +#include +#include +#include "tunerfrm.h" +#include "ampfrm.h" +#include "radio.h" + +MainWin::MainWin(): + 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); +} + +int main (int argc, char **argv) +{ + QApplication a(argc, argv); + + MainWin w; + +#if QT_VERSION < 0x040000 + a.setMainWidget(&w); +#endif + w.show(); + + return a.exec(); +} diff --git a/qwt/examples/radio/radio.h b/qwt/examples/radio/radio.h new file mode 100644 index 000000000..79fe1f6ca --- /dev/null +++ b/qwt/examples/radio/radio.h @@ -0,0 +1,9 @@ +#include + +class MainWin : public QWidget +{ +public: + MainWin(); +}; + + diff --git a/qwt/examples/radio/radio.pro b/qwt/examples/radio/radio.pro new file mode 100644 index 000000000..0fc4730ce --- /dev/null +++ b/qwt/examples/radio/radio.pro @@ -0,0 +1,22 @@ +# -*- mode: sh -*- ################################################ +# 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( ../examples.pri ) + +TARGET = radio + +HEADERS = \ + radio.h \ + ampfrm.h \ + tunerfrm.h + +SOURCES = \ + radio.cpp \ + ampfrm.cpp \ + tunerfrm.cpp diff --git a/qwt/examples/radio/tunerfrm.cpp b/qwt/examples/radio/tunerfrm.cpp new file mode 100644 index 000000000..c26c581c5 --- /dev/null +++ b/qwt/examples/radio/tunerfrm.cpp @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include +#include +#include "tunerfrm.h" + +class TuningThermo: public QWidget +{ +public: + TuningThermo(QWidget *parent): + QWidget(parent) + { + d_thermo = new QwtThermo(this); + d_thermo->setOrientation(Qt::Horizontal, QwtThermo::NoScale); + d_thermo->setRange(0.0, 1.0); + d_thermo->setFillColor(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) +{ + d_sldFreq = new QwtSlider(this, Qt::Horizontal, QwtSlider::TopScale); + d_sldFreq->setRange(87.5, 108, 0.01, 10); + d_sldFreq->setScaleMaxMinor(5); + d_sldFreq->setScaleMaxMajor(12); + d_sldFreq->setThumbLength(80); + d_sldFreq->setBorderWidth(1); + + d_thmTune = new TuningThermo(this); + + d_whlFreq = new QwtWheel(this); + d_whlFreq->setMass(0.5); + d_whlFreq->setRange(87.5, 108, 0.01); + d_whlFreq->setTotalAngle(3600.0); + d_whlFreq->setFixedHeight(30); + + + connect(d_whlFreq, SIGNAL(valueChanged(double)), SLOT(adjustFreq(double))); + connect(d_sldFreq, SIGNAL(valueChanged(double)), SLOT(adjustFreq(double))); + + QVBoxLayout *mainLayout = new QVBoxLayout(this); + mainLayout->setMargin(10); + mainLayout->setSpacing(5); + mainLayout->addWidget(d_sldFreq); + + QHBoxLayout *hLayout = new QHBoxLayout; + hLayout->setMargin(0); + hLayout->addWidget(d_thmTune, 0); + hLayout->addStretch(5); + hLayout->addWidget(d_whlFreq, 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(sin(x) * cos(4.0 * x)); + + d_thmTune->setValue(field); + + if (d_sldFreq->value() != frq) + d_sldFreq->setValue(frq); + if (d_whlFreq->value() != frq) + d_whlFreq->setValue(frq); + + emit fieldChanged(field); +} + +void TunerFrame::setFreq(double frq) +{ + d_whlFreq->setValue(frq); +} diff --git a/qwt/examples/radio/tunerfrm.h b/qwt/examples/radio/tunerfrm.h new file mode 100644 index 000000000..017e0e30d --- /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); + +signals: + void fieldChanged(double f); + +public slots: + void setFreq(double frq); + +private slots: + void adjustFreq(double frq); + +private: + QwtWheel *d_whlFreq; + TuningThermo *d_thmTune; + QwtSlider *d_sldFreq; +}; + + + + diff --git a/qwt/examples/realtime_plot/README b/qwt/examples/realtime_plot/README new file mode 100644 index 000000000..32c7f1393 --- /dev/null +++ b/qwt/examples/realtime_plot/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_plot/clear.xpm b/qwt/examples/realtime_plot/clear.xpm new file mode 100644 index 000000000..86c72b849 --- /dev/null +++ b/qwt/examples/realtime_plot/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_plot/incrementalplot.cpp b/qwt/examples/realtime_plot/incrementalplot.cpp new file mode 100644 index 000000000..edba1cbb7 --- /dev/null +++ b/qwt/examples/realtime_plot/incrementalplot.cpp @@ -0,0 +1,122 @@ +#include +#include +#include +#include +#include "incrementalplot.h" +#if QT_VERSION >= 0x040000 +#include +#endif + +CurveData::CurveData(): + d_count(0) +{ +} + +void CurveData::append(double *x, double *y, int count) +{ + int newSize = ( (d_count + count) / 1000 + 1 ) * 1000; + if ( newSize > size() ) + { + d_x.resize(newSize); + d_y.resize(newSize); + } + + for ( register int i = 0; i < count; i++ ) + { + d_x[d_count + i] = x[i]; + d_y[d_count + i] = y[i]; + } + d_count += count; +} + +int CurveData::count() const +{ + return d_count; +} + +int CurveData::size() const +{ + return d_x.size(); +} + +const double *CurveData::x() const +{ + return d_x.data(); +} + +const double *CurveData::y() const +{ + return d_y.data(); +} + +IncrementalPlot::IncrementalPlot(QWidget *parent): + QwtPlot(parent), + d_data(NULL), + d_curve(NULL) +{ + setAutoReplot(false); +} + +IncrementalPlot::~IncrementalPlot() +{ + delete d_data; +} + +void IncrementalPlot::appendData(double x, double y) +{ + appendData(&x, &y, 1); +} + +void IncrementalPlot::appendData(double *x, double *y, int size) +{ + if ( d_data == NULL ) + d_data = new CurveData; + + if ( d_curve == NULL ) + { + d_curve = new QwtPlotCurve("Test Curve"); + d_curve->setStyle(QwtPlotCurve::NoCurve); + d_curve->setPaintAttribute(QwtPlotCurve::PaintFiltered); + + const QColor &c = Qt::white; + d_curve->setSymbol(QwtSymbol(QwtSymbol::XCross, + QBrush(c), QPen(c), QSize(5, 5)) ); + + d_curve->attach(this); + } + + d_data->append(x, y, size); + d_curve->setRawData(d_data->x(), d_data->y(), d_data->count()); +#ifdef __GNUC__ +#warning better use QwtData +#endif + + const bool cacheMode = + canvas()->testPaintAttribute(QwtPlotCanvas::PaintCached); + +#if QT_VERSION >= 0x040000 && defined(Q_WS_X11) + // Even if not recommended by TrollTech, Qt::WA_PaintOutsidePaintEvent + // works on X11. This has an tremendous effect on the performance.. + + canvas()->setAttribute(Qt::WA_PaintOutsidePaintEvent, true); +#endif + + canvas()->setPaintAttribute(QwtPlotCanvas::PaintCached, false); + d_curve->draw(d_curve->dataSize() - size, d_curve->dataSize() - 1); + canvas()->setPaintAttribute(QwtPlotCanvas::PaintCached, cacheMode); + +#if QT_VERSION >= 0x040000 && defined(Q_WS_X11) + canvas()->setAttribute(Qt::WA_PaintOutsidePaintEvent, false); +#endif +} + +void IncrementalPlot::removeData() +{ + delete d_curve; + d_curve = NULL; + + delete d_data; + d_data = NULL; + + replot(); +} diff --git a/qwt/examples/realtime_plot/incrementalplot.h b/qwt/examples/realtime_plot/incrementalplot.h new file mode 100644 index 000000000..263169d06 --- /dev/null +++ b/qwt/examples/realtime_plot/incrementalplot.h @@ -0,0 +1,46 @@ +#ifndef _INCREMENTALPLOT_H_ +#define _INCREMENTALPLOT_H_ 1 + +#include +#include + +class QwtPlotCurve; + +class CurveData +{ + // A container class for growing data +public: + + CurveData(); + + void append(double *x, double *y, int count); + + int count() const; + int size() const; + const double *x() const; + const double *y() const; + +private: + int d_count; + QwtArray d_x; + QwtArray d_y; +}; + +class IncrementalPlot : public QwtPlot +{ + Q_OBJECT +public: + IncrementalPlot(QWidget *parent = NULL); + virtual ~IncrementalPlot(); + + void appendData(double x, double y); + void appendData(double *x, double *y, int size); + + void removeData(); + +private: + CurveData *d_data; + QwtPlotCurve *d_curve; +}; + +#endif // _INCREMENTALPLOT_H_ diff --git a/qwt/examples/realtime_plot/mainwindow.cpp b/qwt/examples/realtime_plot/mainwindow.cpp new file mode 100644 index 000000000..24befe594 --- /dev/null +++ b/qwt/examples/realtime_plot/mainwindow.cpp @@ -0,0 +1,230 @@ +#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); +#if QT_VERSION >= 0x040000 + addWidget(label); +#endif + 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)); + +#if QT_VERSION < 0x040000 + d_counter = new QSpinBox(min, max, step, this); +#else + d_counter = new QSpinBox(this); + d_counter->setRange(min, max); + d_counter->setSingleStep(step); +#endif + 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() +{ +#if QT_VERSION < 0x040000 + setDockEnabled(TornOff, true); + setRightJustification(true); +#endif + + addToolBar(toolBar()); +#ifndef QT_NO_STATUSBAR + (void)statusBar(); +#endif + + d_plot = new RandomPlot(this); + d_plot->setMargin(4); + + setCentralWidget(d_plot); + +#if QT_VERSION >= 0x040000 + connect(d_startAction, SIGNAL(toggled(bool)), this, SLOT(appendPoints(bool))); + connect(d_clearAction, SIGNAL(triggered()), d_plot, SLOT(clear())); +#else + connect(d_startBtn, SIGNAL(toggled(bool)), this, SLOT(appendPoints(bool))); + connect(d_clearBtn, SIGNAL(clicked()), d_plot, SLOT(clear())); +#endif + connect(d_plot, SIGNAL(running(bool)), this, SLOT(showRunning(bool))); + + initWhatsThis(); + +#if QT_VERSION >= 0x040000 + setContextMenuPolicy(Qt::NoContextMenu); +#endif +} + +QToolBar *MainWindow::toolBar() +{ + MyToolBar *toolBar = new MyToolBar(this); + +#if QT_VERSION >= 0x040000 + toolBar->setAllowedAreas(Qt::TopToolBarArea | Qt::BottomToolBarArea); + setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + + d_startAction = new QAction(QIcon(start_xpm), "Clear", toolBar); + d_startAction->setCheckable(true); + d_clearAction = new QAction(QIcon(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)); +#else + d_startBtn = new QToolButton(toolBar); + d_startBtn->setUsesTextLabel(true); + d_startBtn->setPixmap(QPixmap(start_xpm)); + d_startBtn->setToggleButton(true); + + d_clearBtn = new QToolButton(toolBar); + d_clearBtn->setUsesTextLabel(true); + d_clearBtn->setPixmap(QPixmap(clear_xpm)); + d_clearBtn->setTextLabel("Clear", false); + + QToolButton *helpBtn = QWhatsThis::whatsThisButton(toolBar); + helpBtn->setUsesTextLabel(true); + helpBtn->setTextLabel("Help", false); +#endif + + QWidget *hBox = new QWidget(toolBar); + + 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_randomCount); + layout->addSpacing(5); + layout->addWidget(d_timerCount); + + showRunning(false); + +#if QT_VERSION < 0x040000 + toolBar->setStretchableWidget(hBox); + + d_startBtn->setMinimumWidth(helpBtn->sizeHint().width() + 20); + d_clearBtn->setMinimumWidth(helpBtn->sizeHint().width() + 20); + helpBtn->setMinimumWidth(helpBtn->sizeHint().width() + 20); +#else + toolBar->addWidget(hBox); +#endif + + 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); +#if QT_VERSION < 0x040000 + d_startBtn->setOn(running); + d_startBtn->setTextLabel(running ? "Stop" : "Start", false); +#else + d_startAction->setChecked(running); + d_startAction->setText(running ? "Stop" : "Start"); +#endif +} + +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."; + +#if QT_VERSION < 0x040000 + QWhatsThis::add(d_plot, text1); + QWhatsThis::add(d_randomCount, text2); + QWhatsThis::add(d_timerCount, text3); + QWhatsThis::add(d_startBtn, text4); + QWhatsThis::add(d_clearBtn, text5); +#else + d_plot->setWhatsThis(text1); + d_randomCount->setWhatsThis(text2); + d_timerCount->setWhatsThis(text3); + d_startAction->setWhatsThis(text4); + d_clearAction->setWhatsThis(text5); +#endif +} + diff --git a/qwt/examples/realtime_plot/mainwindow.h b/qwt/examples/realtime_plot/mainwindow.h new file mode 100644 index 000000000..0418c3c4b --- /dev/null +++ b/qwt/examples/realtime_plot/mainwindow.h @@ -0,0 +1,44 @@ +#ifndef _MAINWINDOW_H_ +#define _MAINWINDOW_H_ 1 + +#include +#if QT_VERSION < 0x040000 +#include +#else +#include +#endif + +class QSpinBox; +class QPushButton; +class RandomPlot; +class Counter; + +class MainWindow: public QMainWindow +{ + Q_OBJECT +public: + MainWindow(); + +private slots: + void showRunning(bool); + void appendPoints(bool); + +private: + QToolBar *toolBar(); + void initWhatsThis(); + +private: + Counter *d_randomCount; + Counter *d_timerCount; +#if QT_VERSION < 0x040000 + QToolButton *d_startBtn; + QToolButton *d_clearBtn; +#else + QAction *d_startAction; + QAction *d_clearAction; +#endif + + RandomPlot *d_plot; +}; + +#endif diff --git a/qwt/examples/realtime_plot/randomplot.cpp b/qwt/examples/realtime_plot/randomplot.cpp new file mode 100644 index 000000000..df7335c53 --- /dev/null +++ b/qwt/examples/realtime_plot/randomplot.cpp @@ -0,0 +1,124 @@ +#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(QwtPlotCanvas *canvas): + ScrollZoomer(canvas) + { + } + + virtual void rescale() + { + QwtScaleWidget *scaleWidget = plot()->axisWidget(yAxis()); + QwtScaleDraw *sd = scaleWidget->scaleDraw(); + + int minExtent = 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->majTickLength() + 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); + setCanvasLineWidth(2); + + plotLayout()->setAlignCanvasToScales(true); + + QwtPlotGrid *grid = new QwtPlotGrid; + grid->setMajPen(QPen(Qt::gray, 0, Qt::DotLine)); + grid->attach(this); + + setCanvasBackground(QColor(29, 100, 141)); // nice blue + + setAxisScale(xBottom, 0, c_rangeMax); + setAxisScale(yLeft, 0, c_rangeMax); + + replot(); + + // enable zooming + + Zoomer *zoomer = new Zoomer(canvas()); + zoomer->setRubberBandPen(QPen(Qt::red, 2, Qt::DotLine)); + zoomer->setTrackerPen(QPen(Qt::red)); +} + +QSize RandomPlot::sizeHint() const +{ + return QSize(540,400); +} + +void RandomPlot::appendPoint() +{ + double x = rand() % c_rangeMax; + x += ( rand() % 100 ) / 100; + + double y = rand() % c_rangeMax; + y += ( rand() % 100 ) / 100; + + appendData(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; + + emit running(true); + + canvas()->setPaintAttribute(QwtPlotCanvas::PaintCached, false); + d_timer->start(timeout); +} + +void RandomPlot::stop() +{ + if ( d_timer ) + { + d_timer->stop(); + emit running(false); + } + + canvas()->setPaintAttribute(QwtPlotCanvas::PaintCached, true); +} + +void RandomPlot::clear() +{ + removeData(); + replot(); +} diff --git a/qwt/examples/realtime_plot/randomplot.h b/qwt/examples/realtime_plot/randomplot.h new file mode 100644 index 000000000..554cd468a --- /dev/null +++ b/qwt/examples/realtime_plot/randomplot.h @@ -0,0 +1,35 @@ +#ifndef _RANDOMPLOT_H_ +#define _RANDOMPLOT_H_ 1 + +#include "incrementalplot.h" + +class QTimer; + +class RandomPlot: public IncrementalPlot +{ + Q_OBJECT + +public: + RandomPlot(QWidget *parent); + + virtual QSize sizeHint() const; + +signals: + void running(bool); + +public slots: + void clear(); + void stop(); + void append(int timeout, int count); + +private slots: + void appendPoint(); + +private: + void initCurve(); + + QTimer *d_timer; + int d_timerCount; +}; + +#endif // _RANDOMPLOT_H_ diff --git a/qwt/examples/realtime_plot/realtime.cpp b/qwt/examples/realtime_plot/realtime.cpp new file mode 100644 index 000000000..3c3f03a7f --- /dev/null +++ b/qwt/examples/realtime_plot/realtime.cpp @@ -0,0 +1,15 @@ +#include +#include "mainwindow.h" + +int main(int argc, char **argv) +{ + QApplication a(argc, argv); + + MainWindow w; + w.show(); +#if QT_VERSION < 0x040000 + a.setMainWidget(&w); +#endif + + return a.exec(); +} diff --git a/qwt/examples/realtime_plot/realtime_plot.pro b/qwt/examples/realtime_plot/realtime_plot.pro new file mode 100644 index 000000000..0f0f68a0d --- /dev/null +++ b/qwt/examples/realtime_plot/realtime_plot.pro @@ -0,0 +1,28 @@ +# -*- mode: sh -*- ################################################ +# 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( ../examples.pri ) + +TARGET = realtime + +HEADERS = \ + mainwindow.h \ + scrollzoomer.h \ + scrollbar.h \ + incrementalplot.h \ + randomplot.h + +SOURCES = \ + realtime.cpp \ + mainwindow.cpp \ + scrollzoomer.cpp \ + scrollbar.cpp \ + incrementalplot.cpp \ + randomplot.cpp + diff --git a/qwt/examples/realtime_plot/scrollbar.cpp b/qwt/examples/realtime_plot/scrollbar.cpp new file mode 100644 index 000000000..1027b5fa6 --- /dev/null +++ b/qwt/examples/realtime_plot/scrollbar.cpp @@ -0,0 +1,184 @@ +#include +#if QT_VERSION >= 0x040000 +#include +#endif +#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; + +#if QT_VERSION < 0x040000 + setSteps(steps, sliderTicks); +#else + setSingleStep(steps); + setPageStep(sliderTicks); +#endif + + int tick = mapToTick(min + (max - min) / 2); + if ( isInverted() ) + tick = d_baseTicks - tick; + +#if QT_VERSION < 0x040000 + directSetValue(tick); + rangeChange(); +#else + setSliderPosition(tick); +#endif + 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 +{ + return (int) ( ( v - d_minBase) / (d_maxBase - d_minBase ) * d_baseTicks ); +} + +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); + emit valueChanged(orientation(), min, max); +} + +void ScrollBar::catchSliderMoved(int value) +{ + double min, max; + sliderRange(value, min, max); + emit sliderMoved(orientation(), min, max); +} + +int ScrollBar::extent() const +{ +#if QT_VERSION < 0x040000 + return style().pixelMetric(QStyle::PM_ScrollBarExtent, this); +#else + 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); +#endif +} diff --git a/qwt/examples/realtime_plot/scrollbar.h b/qwt/examples/realtime_plot/scrollbar.h new file mode 100644 index 000000000..2bd148d3b --- /dev/null +++ b/qwt/examples/realtime_plot/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; + +signals: + void sliderMoved(Qt::Orientation, double, double); + void valueChanged(Qt::Orientation, double, double); + +public 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 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_plot/scrollzoomer.cpp b/qwt/examples/realtime_plot/scrollzoomer.cpp new file mode 100644 index 000000000..9a0a39cb1 --- /dev/null +++ b/qwt/examples/realtime_plot/scrollzoomer.cpp @@ -0,0 +1,518 @@ +#include +#include +#include +#include +#include +#include "scrollbar.h" +#include "scrollzoomer.h" + +class ScrollData +{ +public: + ScrollData(): + scrollBar(NULL), + position(ScrollZoomer::OppositeToScale), +#if QT_VERSION < 0x040000 + mode(QScrollView::Auto) +#else + mode(Qt::ScrollBarAsNeeded) +#endif + { + } + + ~ScrollData() + { + delete scrollBar; + } + + ScrollBar *scrollBar; + ScrollZoomer::ScrollBarPosition position; +#if QT_VERSION < 0x040000 + QScrollView::ScrollBarMode mode; +#else + Qt::ScrollBarPolicy mode; +#endif +}; + +ScrollZoomer::ScrollZoomer(QwtPlotCanvas *canvas): + QwtPlotZoomer(canvas), + d_cornerWidget(NULL), + d_hScrollData(NULL), + d_vScrollData(NULL), + d_inZoom(false), + d_alignCanvasToScales(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(); + layout->setAlignCanvasToScales(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(); + d_alignCanvasToScales = layout->alignCanvasToScales(); + layout->setAlignCanvasToScales(false); + + d_inZoom = true; + } + } + + QwtPlotZoomer::rescale(); + updateScrollBars(); +} + +ScrollBar *ScrollZoomer::scrollBar(Qt::Orientation o) +{ + ScrollBar *&sb = (o == Qt::Vertical) + ? d_vScrollData->scrollBar : d_hScrollData->scrollBar; + + if ( sb == NULL ) + { + sb = new ScrollBar(o, 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; +} + +#if QT_VERSION < 0x040000 +void ScrollZoomer::setHScrollBarMode(QScrollView::ScrollBarMode mode) +#else +void ScrollZoomer::setHScrollBarMode(Qt::ScrollBarPolicy mode) +#endif +{ + if ( hScrollBarMode() != mode ) + { + d_hScrollData->mode = mode; + updateScrollBars(); + } +} + +#if QT_VERSION < 0x040000 +void ScrollZoomer::setVScrollBarMode(QScrollView::ScrollBarMode mode) +#else +void ScrollZoomer::setVScrollBarMode(Qt::ScrollBarPolicy mode) +#endif +{ + if ( vScrollBarMode() != mode ) + { + d_vScrollData->mode = mode; + updateScrollBars(); + } +} + +#if QT_VERSION < 0x040000 +QScrollView::ScrollBarMode ScrollZoomer::hScrollBarMode() const +#else +Qt::ScrollBarPolicy ScrollZoomer::hScrollBarMode() const +#endif +{ + return d_hScrollData->mode; +} + +#if QT_VERSION < 0x040000 +QScrollView::ScrollBarMode ScrollZoomer::vScrollBarMode() const +#else +Qt::ScrollBarPolicy ScrollZoomer::vScrollBarMode() const +#endif +{ + 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() ) + { +#if QT_VERSION < 0x040000 + d_cornerWidget->reparent(canvas(), QPoint(0, 0)); +#else + d_cornerWidget->setParent(canvas()); +#endif + } + + updateScrollBars(); + } + } +} + +QWidget *ScrollZoomer::cornerWidget() const +{ + return d_cornerWidget; +} + +bool ScrollZoomer::eventFilter(QObject *o, QEvent *e) +{ + if ( o == canvas() ) + { + switch(e->type()) + { + case QEvent::Resize: + { + const int fw = ((QwtPlotCanvas *)canvas())->frameWidth(); + + QRect rect; + rect.setSize(((QResizeEvent *)e)->size()); + rect.setRect(rect.x() + fw, rect.y() + fw, + rect.width() - 2 * fw, rect.height() - 2 * fw); + + layoutScrollBars(rect); + break; + } + case QEvent::ChildRemoved: + { + const QObject *child = ((QChildEvent *)e)->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(o, e); +} + +bool ScrollZoomer::needScrollBar(Qt::Orientation o) const +{ +#if QT_VERSION < 0x040000 + QScrollView::ScrollBarMode mode; +#else + Qt::ScrollBarPolicy mode; +#endif + double zoomMin, zoomMax, baseMin, baseMax; + + if ( o == 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) + { +#if QT_VERSION < 0x040000 + case QScrollView::AlwaysOn: +#else + case Qt::ScrollBarAlwaysOn: +#endif + needed = true; + break; +#if QT_VERSION < 0x040000 + case QScrollView::AlwaysOff: +#else + case Qt::ScrollBarAlwaysOff: +#endif + 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(); + const int yAxis = QwtPlotZoomer::yAxis(); + + 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()); + + const QwtScaleDiv *sd = plot()->axisScaleDiv(xAxis); + sb->setInverted(sd->lowerBound() > sd->upperBound() ); + + 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()); + + const QwtScaleDiv *sd = plot()->axisScaleDiv(yAxis); + sb->setInverted(sd->lowerBound() < sd->upperBound() ); + + 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()); +#if QT_VERSION >= 0x040100 + d_cornerWidget->setAutoFillBackground(true); +#endif + d_cornerWidget->setPalette(plot()->palette()); + } + d_cornerWidget->show(); + } + else + { + if ( d_cornerWidget ) + d_cornerWidget->hide(); + } + + layoutScrollBars(((QwtPlotCanvas *)canvas())->contentsRect()); + plot()->updateLayout(); +} + +void ScrollZoomer::layoutScrollBars(const QRect &rect) +{ + int hPos = xAxis(); + if ( hScrollBarPosition() == OppositeToScale ) + hPos = oppositeAxis(hPos); + + int vPos = yAxis(); + 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 == QwtPlot::xTop) + ? rect.top() : rect.bottom() - hdim + 1; + int w = rect.width(); + + if ( vScrollBar && vScrollBar->isVisible() ) + { + if ( vPos == QwtPlot::yLeft ) + x += vdim; + w -= vdim; + } + + hScrollBar->setGeometry(x, y, w, hdim); + } + if ( vScrollBar && vScrollBar->isVisible() ) + { + int pos = yAxis(); + if ( vScrollBarPosition() == OppositeToScale ) + pos = oppositeAxis(pos); + + int x = (vPos == QwtPlot::yLeft) + ? rect.left() : rect.right() - vdim + 1; + int y = rect.y(); + + int h = rect.height(); + + if ( hScrollBar && hScrollBar->isVisible() ) + { + if ( hPos == QwtPlot::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) +{ + if ( o == Qt::Horizontal ) + move(min, zoomRect().top()); + else + move(zoomRect().left(), min); + + emit zoomed(zoomRect()); +} + +int ScrollZoomer::oppositeAxis(int axis) const +{ + switch(axis) + { + case QwtPlot::xBottom: + return QwtPlot::xTop; + case QwtPlot::xTop: + return QwtPlot::xBottom; + case QwtPlot::yLeft: + return QwtPlot::yRight; + case QwtPlot::yRight: + return QwtPlot::yLeft; + default: + break; + } + + return axis; +} diff --git a/qwt/examples/realtime_plot/scrollzoomer.h b/qwt/examples/realtime_plot/scrollzoomer.h new file mode 100644 index 000000000..7f0c37f93 --- /dev/null +++ b/qwt/examples/realtime_plot/scrollzoomer.h @@ -0,0 +1,77 @@ +#ifndef _SCROLLZOOMER_H +#define _SCROLLZOOMER_H + +#include +#if QT_VERSION < 0x040000 +#include +#endif +#include + +class ScrollData; +class ScrollBar; + +class ScrollZoomer: public QwtPlotZoomer +{ + Q_OBJECT +public: + enum ScrollBarPosition + { + AttachedToScale, + OppositeToScale + }; + + ScrollZoomer(QwtPlotCanvas *); + virtual ~ScrollZoomer(); + + ScrollBar *horizontalScrollBar() const; + ScrollBar *verticalScrollBar() const; + +#if QT_VERSION < 0x040000 + void setHScrollBarMode(QScrollView::ScrollBarMode); + void setVScrollBarMode(QScrollView::ScrollBarMode); + + QScrollView::ScrollBarMode vScrollBarMode () const; + QScrollView::ScrollBarMode hScrollBarMode () const; +#else + void setHScrollBarMode(Qt::ScrollBarPolicy); + void setVScrollBarMode(Qt::ScrollBarPolicy); + + Qt::ScrollBarPolicy vScrollBarMode () const; + Qt::ScrollBarPolicy hScrollBarMode () const; +#endif + + 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 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; +}; + +#endif diff --git a/qwt/examples/realtime_plot/start.xpm b/qwt/examples/realtime_plot/start.xpm new file mode 100644 index 000000000..62146842a --- /dev/null +++ b/qwt/examples/realtime_plot/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/simple_plot/simple.cpp b/qwt/examples/simple_plot/simple.cpp new file mode 100644 index 000000000..5d6612474 --- /dev/null +++ b/qwt/examples/simple_plot/simple.cpp @@ -0,0 +1,125 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +//----------------------------------------------------------------- +// simple.cpp +// +// A simple example which shows how to use QwtPlot and QwtData +//----------------------------------------------------------------- + +class SimpleData: public QwtData +{ + // The x values depend on its index and the y values + // can be calculated from the corresponding x value. + // So we don´t need to store the values. + // Such an implementation is slower because every point + // has to be recalculated for every replot, but it demonstrates how + // QwtData can be used. + +public: + SimpleData(double(*y)(double), size_t size): + d_size(size), + d_y(y) + { + } + + virtual QwtData *copy() const + { + return new SimpleData(d_y, d_size); + } + + virtual size_t size() const + { + return d_size; + } + + virtual double x(size_t i) const + { + return 0.1 * i; + } + + virtual double y(size_t i) const + { + return d_y(x(i)); + } +private: + size_t d_size; + double(*d_y)(double); +}; + +class Plot : public QwtPlot +{ +public: + Plot(); +}; + + +Plot::Plot() +{ + setTitle("A Simple QwtPlot Demonstration"); + insertLegend(new QwtLegend(), QwtPlot::RightLegend); + + // Set axis titles + setAxisTitle(xBottom, "x -->"); + setAxisTitle(yLeft, "y -->"); + + // Insert new curves + QwtPlotCurve *cSin = new QwtPlotCurve("y = sin(x)"); +#if QT_VERSION >= 0x040000 + cSin->setRenderHint(QwtPlotItem::RenderAntialiased); +#endif + cSin->setPen(QPen(Qt::red)); + cSin->attach(this); + + QwtPlotCurve *cCos = new QwtPlotCurve("y = cos(x)"); +#if QT_VERSION >= 0x040000 + cCos->setRenderHint(QwtPlotItem::RenderAntialiased); +#endif + cCos->setPen(QPen(Qt::blue)); + cCos->attach(this); + + // Create sin and cos data + const int nPoints = 100; + cSin->setData(SimpleData(::sin, nPoints)); + cCos->setData(SimpleData(::cos, nPoints)); + + // 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(QPen(Qt::black, 0, Qt::DashDotLine)); + mX->setXValue(2.0 * M_PI); + mX->attach(this); +} + +int main(int argc, char **argv) +{ + QApplication a(argc, argv); + + Plot plot; +#if QT_VERSION < 0x040000 + a.setMainWidget(&plot); +#endif + plot.resize(600,400); + plot.show(); + return a.exec(); +} diff --git a/qwt/examples/simple_plot/simple_plot.pro b/qwt/examples/simple_plot/simple_plot.pro new file mode 100644 index 000000000..387a9c525 --- /dev/null +++ b/qwt/examples/simple_plot/simple_plot.pro @@ -0,0 +1,16 @@ +# -*- mode: sh -*- ################################################ +# 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( ../examples.pri ) + +TARGET = simple + +SOURCES = \ + simple.cpp + diff --git a/qwt/examples/sliders/sliders.cpp b/qwt/examples/sliders/sliders.cpp new file mode 100644 index 000000000..0d6f62f5c --- /dev/null +++ b/qwt/examples/sliders/sliders.cpp @@ -0,0 +1,201 @@ +#include +#include +#include +#include +#include +#include +#include +#include "sliders.h" + +class Layout: public QBoxLayout +{ +public: + Layout(Qt::Orientation o, QWidget *parent = NULL): +#if QT_VERSION < 0x040000 + QBoxLayout(parent, QBoxLayout::LeftToRight) +#else + QBoxLayout(QBoxLayout::LeftToRight, parent) +#endif + { + if ( o == Qt::Vertical ) + setDirection(QBoxLayout::TopToBottom); + + setSpacing(20); + setMargin(0); + } +}; + +Slider::Slider(QWidget *parent, int sliderType): + QWidget(parent) +{ + d_slider = createSlider(this, sliderType); + +#if QT_VERSION < 0x040000 + int alignment = Qt::AlignCenter; +#else + QFlags alignment; +#endif + switch(d_slider->scalePosition()) + { + case QwtSlider::NoScale: + if ( d_slider->orientation() == Qt::Horizontal ) + alignment = Qt::AlignHCenter | Qt::AlignTop; + else + alignment = Qt::AlignVCenter | Qt::AlignLeft; + break; + case QwtSlider::LeftScale: + alignment = Qt::AlignVCenter | Qt::AlignRight; + break; + case QwtSlider::RightScale: + alignment = Qt::AlignVCenter | Qt::AlignLeft; + break; + case QwtSlider::TopScale: + alignment = Qt::AlignHCenter | Qt::AlignBottom; + break; + case QwtSlider::BottomScale: + alignment = Qt::AlignHCenter | Qt::AlignTop; + break; + } + + d_label = new QLabel("0", 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); +} + +QwtSlider *Slider::createSlider(QWidget *parent, int sliderType) const +{ + QwtSlider *slider = NULL; + + switch(sliderType) + { + case 0: + { + slider = new QwtSlider(parent, + Qt::Horizontal, QwtSlider::TopScale, QwtSlider::BgTrough); + slider->setThumbWidth(10); + slider->setRange(-10.0, 10.0, 1.0, 0); // paging disabled + break; + } + case 1: + { + slider = new QwtSlider(parent, + Qt::Horizontal, QwtSlider::NoScale, QwtSlider::BgBoth); + slider->setRange(0.0, 1.0, 0.01, 5); + break; + } + case 2: + { + slider = new QwtSlider(parent, + Qt::Horizontal, QwtSlider::BottomScale, QwtSlider::BgSlot); + slider->setThumbWidth(25); + slider->setThumbLength(12); + slider->setRange(1000.0, 3000.0, 10.0, 10); + break; + } + case 3: + { + slider = new QwtSlider(parent, + Qt::Vertical, QwtSlider::LeftScale, QwtSlider::BgSlot); + slider->setRange(0.0, 100.0, 1.0, 5); + slider->setScaleMaxMinor(5); + break; + } + case 4: + { + slider = new QwtSlider(parent, + Qt::Vertical, QwtSlider::NoScale, QwtSlider::BgTrough); + slider->setRange(0.0,100.0,1.0, 10); + break; + } + case 5: + { + slider = new QwtSlider(parent, + Qt::Vertical, QwtSlider::RightScale, QwtSlider::BgBoth); + slider->setScaleEngine(new QwtLog10ScaleEngine); + slider->setThumbWidth(20); + slider->setBorderWidth(1); + slider->setRange(0.0, 4.0, 0.01); + slider->setScale(1.0, 1.0e4); + slider->setScaleMaxMinor(10); + break; + } + } + + return slider; +} + +void Slider::setNum(double v) +{ + if ( d_slider->scaleMap().transformation()->type() == + QwtScaleTransformation::Log10 ) + { + v = pow(10.0, v); + } + + QString text; + text.setNum(v, 'f', 1); + + d_label->setText(text); +} + +SliderDemo::SliderDemo(QWidget *p): + QWidget(p) +{ + int i; + + Layout *hSliderLayout = new Layout(Qt::Vertical); + for ( i = 0; i < 3; i++ ) + hSliderLayout->addWidget(new Slider(this, i)); + hSliderLayout->addStretch(); + + Layout *vSliderLayout = new Layout(Qt::Horizontal); + for ( ; i < 6; i++ ) + vSliderLayout->addWidget(new Slider(this, i)); + + QLabel *vTitle = new QLabel("Vertical Sliders", this); + vTitle->setFont(QFont("Helvetica", 14, QFont::Bold)); + vTitle->setAlignment(Qt::AlignHCenter); + + Layout *layout1 = new Layout(Qt::Vertical); + layout1->addWidget(vTitle, 0); + layout1->addLayout(vSliderLayout, 10); + + QLabel *hTitle = new QLabel("Horizontal Sliders", this); + hTitle->setFont(vTitle->font()); + hTitle->setAlignment(Qt::AlignHCenter); + + Layout *layout2 = new Layout(Qt::Vertical); + layout2->addWidget(hTitle, 0); + layout2->addLayout(hSliderLayout, 10); + + Layout *mainLayout = new Layout(Qt::Horizontal, this); + mainLayout->addLayout(layout1); + mainLayout->addLayout(layout2, 10); +} + +int main (int argc, char **argv) +{ + QApplication a(argc, argv); + + QApplication::setFont(QFont("Helvetica",10)); + + SliderDemo w; + +#if QT_VERSION < 0x040000 + a.setMainWidget(&w); +#endif + w.show(); + + return a.exec(); +} diff --git a/qwt/examples/sliders/sliders.h b/qwt/examples/sliders/sliders.h new file mode 100644 index 000000000..e9e58ace2 --- /dev/null +++ b/qwt/examples/sliders/sliders.h @@ -0,0 +1,27 @@ +#include +#include + +class QLabel; +class QLayout; + +class Slider: public QWidget +{ + Q_OBJECT +public: + Slider(QWidget *parent, int sliderType); + +private slots: + void setNum(double v); + +private: + QwtSlider *createSlider(QWidget *, int sliderType) const; + + QwtSlider *d_slider; + QLabel *d_label; +}; + +class SliderDemo : public QWidget +{ +public: + SliderDemo(QWidget *p = NULL); +}; diff --git a/qwt/examples/sliders/sliders.pro b/qwt/examples/sliders/sliders.pro new file mode 100644 index 000000000..f7501703c --- /dev/null +++ b/qwt/examples/sliders/sliders.pro @@ -0,0 +1,19 @@ +# -*- mode: sh -*- ################################################ +# 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( ../examples.pri ) + +TARGET = sliders + +HEADERS = \ + sliders.h + +SOURCES = \ + sliders.cpp + diff --git a/qwt/examples/spectrogram/main.cpp b/qwt/examples/spectrogram/main.cpp new file mode 100644 index 000000000..f13c49e1e --- /dev/null +++ b/qwt/examples/spectrogram/main.cpp @@ -0,0 +1,91 @@ +#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); + QToolButton *btnContour = new QToolButton(toolBar); + QToolButton *btnPrint = new QToolButton(toolBar); + +#if QT_VERSION >= 0x040000 + btnSpectrogram->setText("Spectrogram"); + //btnSpectrogram->setIcon(QIcon()); + btnSpectrogram->setCheckable(true); + btnSpectrogram->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + toolBar->addWidget(btnSpectrogram); + + btnContour->setText("Contour"); + //btnContour->setIcon(QIcon()); + btnContour->setCheckable(true); + btnContour->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + toolBar->addWidget(btnContour); + + btnPrint->setText("Print"); + btnPrint->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + toolBar->addWidget(btnPrint); +#else + btnSpectrogram->setTextLabel("Spectrogram"); + //btnSpectrogram->setPixmap(zoom_xpm); + btnSpectrogram->setToggleButton(true); + btnSpectrogram->setUsesTextLabel(true); + + btnContour->setTextLabel("Contour"); + //btnContour->setPixmap(zoom_xpm); + btnContour->setToggleButton(true); + btnContour->setUsesTextLabel(true); + + btnPrint->setTextLabel("Print"); + btnPrint->setUsesTextLabel(true); +#endif + + addToolBar(toolBar); + + connect(btnSpectrogram, SIGNAL(toggled(bool)), + d_plot, SLOT(showSpectrogram(bool))); + connect(btnContour, SIGNAL(toggled(bool)), + d_plot, SLOT(showContour(bool))); + connect(btnPrint, SIGNAL(clicked()), + d_plot, SLOT(printPlot()) ); + +#if QT_VERSION >= 0x040000 + btnSpectrogram->setChecked(true); + btnContour->setChecked(false); +#else + btnSpectrogram->setOn(true); + btnContour->setOn(false); +#endif +} + +int main(int argc, char **argv) +{ + QApplication a(argc, argv); + + MainWindow mainWindow; +#if QT_VERSION < 0x040000 + a.setMainWidget(&mainWindow); +#endif + + 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..0a617f965 --- /dev/null +++ b/qwt/examples/spectrogram/plot.cpp @@ -0,0 +1,166 @@ +#include +#if QT_VERSION >= 0x040000 +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include "plot.h" + +class MyZoomer: public QwtPlotZoomer +{ +public: + MyZoomer(QwtPlotCanvas *canvas): + QwtPlotZoomer(canvas) + { + setTrackerMode(AlwaysOn); + } + + virtual QwtText trackerText(const QwtDoublePoint &pos) const + { + QColor bg(Qt::white); +#if QT_VERSION >= 0x040300 + bg.setAlpha(200); +#endif + + QwtText text = QwtPlotZoomer::trackerText(pos); + text.setBackgroundBrush( QBrush( bg )); + return text; + } +}; + +class SpectrogramData: public QwtRasterData +{ +public: + SpectrogramData(): + QwtRasterData(QwtDoubleRect(-1.5, -1.5, 3.0, 3.0)) + { + } + + virtual QwtRasterData *copy() const + { + return new SpectrogramData(); + } + + virtual QwtDoubleInterval range() const + { + return QwtDoubleInterval(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); + } +}; + +Plot::Plot(QWidget *parent): + QwtPlot(parent) +{ + d_spectrogram = new QwtPlotSpectrogram(); + + QwtLinearColorMap colorMap(Qt::darkCyan, Qt::red); + colorMap.addColorStop(0.1, Qt::cyan); + colorMap.addColorStop(0.6, Qt::green); + colorMap.addColorStop(0.95, Qt::yellow); + + d_spectrogram->setColorMap(colorMap); + + d_spectrogram->setData(SpectrogramData()); + d_spectrogram->attach(this); + + QwtValueList contourLevels; + for ( double level = 0.5; level < 10.0; level += 1.0 ) + contourLevels += level; + d_spectrogram->setContourLevels(contourLevels); + + // A color bar on the right axis + QwtScaleWidget *rightAxis = axisWidget(QwtPlot::yRight); + rightAxis->setTitle("Intensity"); + rightAxis->setColorBarEnabled(true); + rightAxis->setColorMap(d_spectrogram->data().range(), + d_spectrogram->colorMap()); + + setAxisScale(QwtPlot::yRight, + d_spectrogram->data().range().minValue(), + d_spectrogram->data().range().maxValue() ); + enableAxis(QwtPlot::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()); +#if QT_VERSION < 0x040000 + zoomer->setMousePattern(QwtEventPattern::MouseSelect2, + Qt::RightButton, Qt::ControlButton); +#else + zoomer->setMousePattern(QwtEventPattern::MouseSelect2, + Qt::RightButton, Qt::ControlModifier); +#endif + zoomer->setMousePattern(QwtEventPattern::MouseSelect3, + Qt::RightButton); + + QwtPlotPanner *panner = new QwtPlotPanner(canvas()); + panner->setAxisEnabled(QwtPlot::yRight, false); + panner->setMouseButton(Qt::MidButton); + + // Avoid jumping when labels with more/less digits + // appear/disappear when scrolling vertically + + const QFontMetrics fm(axisWidget(QwtPlot::yLeft)->font()); + QwtScaleDraw *sd = axisScaleDraw(QwtPlot::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() : QPen(Qt::NoPen)); + replot(); +} + +void Plot::printPlot() +{ + QPrinter printer; + printer.setOrientation(QPrinter::Landscape); +#if QT_VERSION < 0x040000 + printer.setColorMode(QPrinter::Color); +#if 0 + printer.setOutputFileName("/tmp/spectrogram.ps"); +#endif + if (printer.setup()) +#else +#if 0 + printer.setOutputFileName("/tmp/spectrogram.pdf"); +#endif + QPrintDialog dialog(&printer); + if ( dialog.exec() ) +#endif + { + print(printer); + } +} + diff --git a/qwt/examples/spectrogram/plot.h b/qwt/examples/spectrogram/plot.h new file mode 100644 index 000000000..58aa90fc7 --- /dev/null +++ b/qwt/examples/spectrogram/plot.h @@ -0,0 +1,18 @@ +#include +#include + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot(QWidget * = NULL); + +public slots: + void showContour(bool on); + void showSpectrogram(bool on); + void printPlot(); + +private: + QwtPlotSpectrogram *d_spectrogram; +}; diff --git a/qwt/examples/spectrogram/spectrogram.pro b/qwt/examples/spectrogram/spectrogram.pro new file mode 100644 index 000000000..04b62873c --- /dev/null +++ b/qwt/examples/spectrogram/spectrogram.pro @@ -0,0 +1,19 @@ +# -*- mode: sh -*- ################################################ +# 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( ../examples.pri ) + +TARGET = spectrogram + +HEADERS = \ + plot.h + +SOURCES = \ + plot.cpp \ + main.cpp diff --git a/qwt/examples/svgmap/main.cpp b/qwt/examples/svgmap/main.cpp new file mode 100644 index 000000000..2bea8c524 --- /dev/null +++ b/qwt/examples/svgmap/main.cpp @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include "plot.h" + +class MainWindow: public QMainWindow +{ +public: + MainWindow(QWidget *parent = NULL): + QMainWindow(parent) + { + Plot *plot = new Plot(this); + setCentralWidget(plot); + + QToolBar *toolBar = new QToolBar(this); + + QToolButton *btnLoad = new QToolButton(toolBar); + +#if QT_VERSION >= 0x040000 + btnLoad->setText("Load SVG"); + btnLoad->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + toolBar->addWidget(btnLoad); +#else + btnLoad->setTextLabel("Load SVG"); + btnLoad->setUsesTextLabel(true); +#endif + + addToolBar(toolBar); + + connect(btnLoad, SIGNAL(clicked()), plot, SLOT(loadSVG())); + } +}; + +int main(int argc, char **argv) +{ + QApplication a(argc, argv); + + MainWindow w; +#if QT_VERSION < 0x040000 + a.setMainWidget(&w); +#endif + w.resize(600,400); + w.show(); + + int rv = a.exec(); + return rv; +} diff --git a/qwt/examples/svgmap/plot.cpp b/qwt/examples/svgmap/plot.cpp new file mode 100644 index 000000000..1b13777da --- /dev/null +++ b/qwt/examples/svgmap/plot.cpp @@ -0,0 +1,83 @@ +#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 < QwtPlot::axisCnt; axis++ ) + enableAxis(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()); + +#if QT_VERSION >= 0x040000 + using namespace Qt; +#endif + canvas()->setFocusPolicy(WheelFocus); + rescale(); +} + +void Plot::loadSVG() +{ + QString dir; +#if 0 + dir = "/dw/svg"; +#endif +#if QT_VERSION >= 0x040000 + const QString fileName = QFileDialog::getOpenFileName( NULL, + "Load a Scaleable Vector Graphic (SVG) Map", + dir, "SVG Files (*.svg)"); +#else + const QString fileName = QFileDialog::getOpenFileName( + dir, "SVG Files (*.svg)", NULL, NULL, + "Load a Scaleable Vector Graphic (SVG) Map" ); +#endif + if ( !fileName.isEmpty() ) + { + if ( d_mapItem == NULL ) + { + d_mapItem = new QwtPlotSvgItem(); + d_mapItem->attach(this); + } + + d_mapItem->loadFile(d_mapRect, fileName); + rescale(); + + replot(); + } +} + +void Plot::rescale() +{ + setAxisScale(QwtPlot::xBottom, + d_mapRect.left(), d_mapRect.right()); + setAxisScale(QwtPlot::yLeft, + d_mapRect.top(), d_mapRect.bottom()); +} diff --git a/qwt/examples/svgmap/plot.h b/qwt/examples/svgmap/plot.h new file mode 100644 index 000000000..300c04fc5 --- /dev/null +++ b/qwt/examples/svgmap/plot.h @@ -0,0 +1,21 @@ +#include +#include + +class QwtPlotSvgItem; + +class Plot: public QwtPlot +{ + Q_OBJECT + +public: + Plot(QWidget * = NULL); + +public slots: + void loadSVG(); + +private: + void rescale(); + + QwtPlotSvgItem *d_mapItem; + const QwtDoubleRect d_mapRect; +}; diff --git a/qwt/examples/svgmap/svgmap.pro b/qwt/examples/svgmap/svgmap.pro new file mode 100644 index 000000000..4b9782405 --- /dev/null +++ b/qwt/examples/svgmap/svgmap.pro @@ -0,0 +1,20 @@ +# -*- mode: sh -*- ################################################ +# 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( ../examples.pri ) + +TARGET = svgmap +QT += svg + +HEADERS = \ + plot.h + +SOURCES = \ + plot.cpp \ + main.cpp diff --git a/qwt/examples/sysinfo/sysinfo.cpp b/qwt/examples/sysinfo/sysinfo.cpp new file mode 100644 index 000000000..ec883464c --- /dev/null +++ b/qwt/examples/sysinfo/sysinfo.cpp @@ -0,0 +1,112 @@ +#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->setRange(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->setMargin(10); + d_thermo->setFillColor(QColor("DarkMagenta")); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->setMargin(0); + layout->setSpacing(0); + + if ( orientation == Qt::Horizontal ) + { + d_label->setAlignment(Qt::AlignCenter); + d_thermo->setOrientation(orientation, QwtThermo::BottomScale); + layout->addWidget(d_label); + layout->addWidget(d_thermo); + } + else + { + d_label->setAlignment(Qt::AlignRight); + d_thermo->setOrientation(orientation, QwtThermo::LeftScale); + 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))); +#if QT_VERSION < 0x040000 + a.setMainWidget(&info); +#endif + 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..f12d27354 --- /dev/null +++ b/qwt/examples/sysinfo/sysinfo.pro @@ -0,0 +1,15 @@ +# -*- mode: sh -*- ################################################ +# 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( ../examples.pri ) + +TARGET = sysinfo + +SOURCES = \ + sysinfo.cpp diff --git a/qwt/qwt.prf b/qwt/qwt.prf new file mode 100644 index 000000000..f91e65d98 --- /dev/null +++ b/qwt/qwt.prf @@ -0,0 +1,34 @@ +# -*- mode: sh -*- ################################################ +# 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 +################################################################### + +QwtBuild = dll +QwtVersion = 5.2.0 + +unix { + QwtBase = /usr/local/qwt-$${QwtVersion}-svn + LIBS += -L$${QwtBase}/lib -lqwt +} +win32 { + QwtBase = C:/Qwt-$${QwtVersion}-svn + + contains(QwtBuild, dll) { + DEFINES += QWT_DLL + + msvc:LIBS += $${QwtBase}/lib/qwt5.lib + msvc.net:LIBS += $${QwtBase}/lib/qwt5.lib + msvc2005:LIBS += $${QwtBase}/lib/qwt5.lib + } else { + win32-msvc:LIBS += $${QwtBase}/lib/qwt.lib + win32-msvc.net:LIBS += $${QwtBase}/lib/qwt.lib + win32-msvc2005:LIBS += $${QwtBase}/lib/qwt.lib + } + g++:LIBS += -L$${QwtBase}/lib -lqwt +} + +INCLUDEPATH += $${QwtBase}/include diff --git a/qwt/qwt.pro b/qwt/qwt.pro new file mode 100644 index 000000000..20543ab37 --- /dev/null +++ b/qwt/qwt.pro @@ -0,0 +1,24 @@ +# -*- mode: sh -*- ########################### +# 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 + +SUBDIRS = \ + src \ + textengines + +contains(CONFIG, QwtDesigner ) { + SUBDIRS += designer +} + +contains(CONFIG, QwtExamples ) { + SUBDIRS += examples +} diff --git a/qwt/qwtconfig.pri b/qwt/qwtconfig.pri new file mode 100644 index 000000000..94af04b5b --- /dev/null +++ b/qwt/qwtconfig.pri @@ -0,0 +1,124 @@ +###################################################################### +# Install paths +###################################################################### + +VER_MAJ = 5 +VER_MIN = 2 +VER_PAT = 1 +VERSION = $${VER_MAJ}.$${VER_MIN}.$${VER_PAT} + +unix { + INSTALLBASE = /usr/local/qwt-$$VERSION-svn +} + +win32 { + INSTALLBASE = C:/Qwt-$$VERSION-svn +} + +target.path = $$INSTALLBASE/lib +headers.path = $$INSTALLBASE/include +doc.path = $$INSTALLBASE/doc + +###################################################################### +# qmake internal options +###################################################################### + +CONFIG += qt # Also for Qtopia Core! +CONFIG += warn_on +CONFIG += thread + +###################################################################### +# release/debug mode +# If you want to build both DEBUG_SUFFIX and RELEASE_SUFFIX +# have to differ to avoid, that they overwrite each other. +###################################################################### + +VVERSION = $$[QT_VERSION] +isEmpty(VVERSION) { + + # Qt 3 + CONFIG += debug # release/debug +} +else { + # Qt 4 + 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 # release/debug/debug_and_release + #CONFIG += debug_and_release + #CONFIG += build_all + } + else { + CONFIG += debug # release/debug + } +} + +###################################################################### +# If you want to have different names for the debug and release +# versions you can add a suffix rule below. +###################################################################### + +DEBUG_SUFFIX = +RELEASE_SUFFIX = + +win32 { + DEBUG_SUFFIX = d +} + +###################################################################### +# Build the static/shared libraries. +# If QwtDll is enabled, a shared library is built, otherwise +# it will be a static library. +###################################################################### + +CONFIG += QwtDll + +###################################################################### +# QwtPlot enables all classes, that are needed to use the QwtPlot +# widget. +###################################################################### + +CONFIG += QwtPlot + +###################################################################### +# QwtWidgets enables all classes, that are needed to use the all other +# widgets (sliders, dials, ...), beside QwtPlot. +###################################################################### + +CONFIG += QwtWidgets + +###################################################################### +# If you want to display svg imageson the plot canvas, enable the +# line below. Note that Qwt needs the svg+xml, when enabling +# QwtSVGItem. +###################################################################### + +#CONFIG += QwtSVGItem + +###################################################################### +# If you have a commercial license you can use the MathML renderer +# of the Qt solutions package to enable MathML support in Qwt. +# So if you want this, copy qtmmlwidget.h + qtmmlwidget.cpp to +# textengines/mathml and enable the line below. +###################################################################### + +#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. +###################################################################### + +CONFIG += QwtDesigner + +###################################################################### +# If you want to auto build the examples, enable the line below +# Otherwise you have to build them from the examples directory. +###################################################################### + +#CONFIG += QwtExamples diff --git a/qwt/src/qwt.h b/qwt/src/qwt.h new file mode 100644 index 000000000..7d7634d1c --- /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_scale.cpp b/qwt/src/qwt_abstract_scale.cpp new file mode 100644 index 000000000..316a8fb3a --- /dev/null +++ b/qwt/src/qwt_abstract_scale.cpp @@ -0,0 +1,311 @@ +/* -*- 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_scale_engine.h" +#include "qwt_scale_draw.h" +#include "qwt_scale_div.h" +#include "qwt_scale_map.h" +#include "qwt_double_interval.h" +#include "qwt_abstract_scale.h" + +class QwtAbstractScale::PrivateData +{ +public: + PrivateData(): + maxMajor(5), + maxMinor(3), + stepSize(0.0), + autoScale(true) + { + scaleEngine = new QwtLinearScaleEngine; + scaleDraw = new QwtScaleDraw(); + } + + ~PrivateData() + { + delete scaleEngine; + delete scaleDraw; + } + + QwtScaleEngine *scaleEngine; + QwtAbstractScaleDraw *scaleDraw; + + int maxMajor; + int maxMinor; + double stepSize; + + bool autoScale; +}; + +/*! + Constructor + + Creates a default QwtScaleDraw and a QwtLinearScaleEngine. + Autoscaling is enabled, and the stepSize is initialized by 0.0. +*/ + +QwtAbstractScale::QwtAbstractScale() +{ + d_data = new PrivateData; + rescale(0.0, 100.0); +} + +//! Destructor +QwtAbstractScale::~QwtAbstractScale() +{ + delete d_data; +} + +/*! + \brief Specify a scale. + + Disable autoscaling and define a scale by an interval and a step size + + \param vmin lower limit of the scale interval + \param vmax upper limit of the scale interval + \param stepSize major step size + \sa setAutoScale() +*/ +void QwtAbstractScale::setScale(double vmin, double vmax, double stepSize) +{ + d_data->autoScale = false; + d_data->stepSize = stepSize; + + rescale(vmin, vmax, stepSize); +} + +/*! + \brief Specify a scale. + + Disable autoscaling and define a scale by an interval and a step size + + \param interval Interval + \param stepSize major step size + \sa setAutoScale() +*/ +void QwtAbstractScale::setScale(const QwtDoubleInterval &interval, + double stepSize) +{ + setScale(interval.minValue(), interval.maxValue(), stepSize); +} + + +/*! + \brief Specify a scale. + + Disable autoscaling and define a scale by a scale division + + \param scaleDiv Scale division + \sa setAutoScale() +*/ +void QwtAbstractScale::setScale(const QwtScaleDiv &scaleDiv) +{ + d_data->autoScale = false; + + if (scaleDiv != d_data->scaleDraw->scaleDiv()) + { + d_data->scaleDraw->setScaleDiv(scaleDiv); + scaleChange(); + } +} + +/*! + Recalculate the scale division and update the scale draw. + + \param vmin Lower limit of the scale interval + \param vmax Upper limit of the scale interval + \param stepSize Major step size + + \sa scaleChange() +*/ +void QwtAbstractScale::rescale(double vmin, double vmax, double stepSize) +{ + const QwtScaleDiv scaleDiv = d_data->scaleEngine->divideScale( + vmin, vmax, d_data->maxMajor, d_data->maxMinor, stepSize); + + if ( scaleDiv != d_data->scaleDraw->scaleDiv() ) + { + d_data->scaleDraw->setTransformation( + d_data->scaleEngine->transformation()); + d_data->scaleDraw->setScaleDiv(scaleDiv); + scaleChange(); + } +} + +/*! + \brief Advise the widget to control the scale range internally. + + Autoscaling is on by default. + \sa setScale(), autoScale() +*/ +void QwtAbstractScale::setAutoScale() +{ + if (!d_data->autoScale) + { + d_data->autoScale = true; + scaleChange(); + } +} + +/*! + \return \c true if autoscaling is enabled +*/ +bool QwtAbstractScale::autoScale() const +{ + return d_data->autoScale; +} + +/*! + \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 QwtAbstractScaleDraw +*/ +void QwtAbstractScale::setScaleMaxMajor(int ticks) +{ + if (ticks != d_data->maxMajor) + { + d_data->maxMajor = ticks; + updateScaleDraw(); + } +} + +/*! + \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 + \sa QwtAbstractScaleDraw +*/ +void QwtAbstractScale::setScaleMaxMinor(int ticks) +{ + if ( ticks != d_data->maxMinor) + { + d_data->maxMinor = ticks; + updateScaleDraw(); + } +} + +/*! + \return Max. number of minor tick intervals + The default value is 3. +*/ +int QwtAbstractScale::scaleMaxMinor() const +{ + return d_data->maxMinor; +} + +/*! + \return Max. number of major tick intervals + The default value is 5. +*/ +int QwtAbstractScale::scaleMaxMajor() const +{ + return d_data->maxMajor; +} + +/*! + \brief Set a scale draw + + scaleDraw has to be created with new and will be deleted in + ~QwtAbstractScale or the next call of setAbstractScaleDraw. +*/ +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; +} + +void QwtAbstractScale::updateScaleDraw() +{ + rescale( d_data->scaleDraw->scaleDiv().lowerBound(), + d_data->scaleDraw->scaleDiv().upperBound(), d_data->stepSize); +} + +/*! + \brief Set a scale engine + + The scale engine is responsible for calculating the scale division, + and in case of auto scaling how to align the scale. + + scaleEngine has to be created with new and will be deleted in + ~QwtAbstractScale 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; +} + +/*! + \brief Notify changed scale + + Dummy empty implementation, intended to be overloaded by derived classes +*/ +void QwtAbstractScale::scaleChange() +{ +} + +/*! + \return abstractScaleDraw()->scaleMap() +*/ +const QwtScaleMap &QwtAbstractScale::scaleMap() const +{ + return d_data->scaleDraw->scaleMap(); +} diff --git a/qwt/src/qwt_abstract_scale.h b/qwt/src/qwt_abstract_scale.h new file mode 100644 index 000000000..cc1cc538f --- /dev/null +++ b/qwt/src/qwt_abstract_scale.h @@ -0,0 +1,70 @@ +/* -*- 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" + +class QwtScaleEngine; +class QwtAbstractScaleDraw; +class QwtScaleDiv; +class QwtScaleMap; +class QwtDoubleInterval; + +/*! + \brief An abstract base class for classes containing a scale + + QwtAbstractScale is used to provide classes with a QwtScaleDraw, + and a QwtScaleDiv. The QwtScaleDiv might be set explicitely + or calculated by a QwtScaleEngine. +*/ + +class QWT_EXPORT QwtAbstractScale +{ +public: + QwtAbstractScale(); + virtual ~QwtAbstractScale(); + + void setScale(double vmin, double vmax, double step = 0.0); + void setScale(const QwtDoubleInterval &, double step = 0.0); + void setScale(const QwtScaleDiv &s); + + void setAutoScale(); + bool autoScale() const; + + void setScaleMaxMajor( int ticks); + int scaleMaxMinor() const; + + void setScaleMaxMinor( int ticks); + int scaleMaxMajor() const; + + void setScaleEngine(QwtScaleEngine *); + const QwtScaleEngine *scaleEngine() const; + QwtScaleEngine *scaleEngine(); + + const QwtScaleMap &scaleMap() const; + +protected: + void rescale(double vmin, double vmax, double step = 0.0); + + 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..2c38b2505 --- /dev/null +++ b/qwt/src/qwt_abstract_scale_draw.cpp @@ -0,0 +1,406 @@ +/* -*- 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 "qwt_math.h" +#include "qwt_text.h" +#include "qwt_painter.h" +#include "qwt_scale_map.h" +#include "qwt_scale_draw.h" + +class QwtAbstractScaleDraw::PrivateData +{ +public: + PrivateData(): + components(Backbone | Ticks | Labels), + spacing(4), + minExtent(0) + { + tickLength[QwtScaleDiv::MinorTick] = 4; + tickLength[QwtScaleDiv::MediumTick] = 6; + tickLength[QwtScaleDiv::MajorTick] = 8; + } + + int components; + + QwtScaleMap map; + QwtScaleDiv scldiv; + + int spacing; + int tickLength[QwtScaleDiv::NTickTypes]; + + int 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; +} + +//! Copy constructor +QwtAbstractScaleDraw::QwtAbstractScaleDraw(const QwtAbstractScaleDraw &other) +{ + d_data = new QwtAbstractScaleDraw::PrivateData(*other.d_data); +} + +//! Destructor +QwtAbstractScaleDraw::~QwtAbstractScaleDraw() +{ + delete d_data; +} +//! Assignment operator +QwtAbstractScaleDraw &QwtAbstractScaleDraw::operator=(const QwtAbstractScaleDraw &other) +{ + *d_data = *other.d_data; + return *this; +} + +/*! + 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 + \sa enableComponent() +*/ +bool QwtAbstractScaleDraw::hasComponent(ScaleComponent component) const +{ + return (d_data->components & component); +} + +/*! + Change the scale division + \param sd New scale division +*/ +void QwtAbstractScaleDraw::setScaleDiv(const QwtScaleDiv &sd) +{ + d_data->scldiv = sd; + d_data->map.setScaleInterval(sd.lowerBound(), sd.upperBound()); + d_data->labelCache.clear(); +} + +/*! + Change the transformation of the scale + \param transformation New scale transformation +*/ +void QwtAbstractScaleDraw::setTransformation( + QwtScaleTransformation *transformation) +{ + d_data->map.setTransformation(transformation); +} + +//! \return Map how to translate between scale and pixel values +const QwtScaleMap &QwtAbstractScaleDraw::map() 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->scldiv; +} + +#if QT_VERSION < 0x040000 +/*! + \brief Draw the scale + + \param painter The painter + + \param colorGroup Color group, text color is used for the labels, + foreground color for ticks and backbone +*/ +void QwtAbstractScaleDraw::draw(QPainter *painter, + const QColorGroup& colorGroup) const + +#else + +/*! + \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 +#endif +{ + if ( hasComponent(QwtAbstractScaleDraw::Labels) ) + { + painter->save(); + +#if QT_VERSION < 0x040000 + painter->setPen(colorGroup.text()); // ignore pen style +#else + painter->setPen(palette.color(QPalette::Text)); // ignore pen style +#endif + + const QwtValueList &majorTicks = + d_data->scldiv.ticks(QwtScaleDiv::MajorTick); + + for (int i = 0; i < (int)majorTicks.count(); i++) + { + const double v = majorTicks[i]; + if ( d_data->scldiv.contains(v) ) + drawLabel(painter, majorTicks[i]); + } + + painter->restore(); + } + + if ( hasComponent(QwtAbstractScaleDraw::Ticks) ) + { + painter->save(); + + QPen pen = painter->pen(); +#if QT_VERSION < 0x040000 + pen.setColor(colorGroup.foreground()); +#else + pen.setColor(palette.color(QPalette::Foreground)); +#endif + painter->setPen(pen); + + for ( int tickType = QwtScaleDiv::MinorTick; + tickType < QwtScaleDiv::NTickTypes; tickType++ ) + { + const QwtValueList &ticks = d_data->scldiv.ticks(tickType); + for (int i = 0; i < (int)ticks.count(); i++) + { + const double v = ticks[i]; + if ( d_data->scldiv.contains(v) ) + drawTick(painter, v, d_data->tickLength[tickType]); + } + } + + painter->restore(); + } + + if ( hasComponent(QwtAbstractScaleDraw::Backbone) ) + { + painter->save(); + + QPen pen = painter->pen(); +#if QT_VERSION < 0x040000 + pen.setColor(colorGroup.foreground()); +#else + pen.setColor(palette.color(QPalette::Foreground)); +#endif + painter->setPen(pen); + + drawBackbone(painter); + + 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(int 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. + + \sa setSpacing() +*/ +int QwtAbstractScaleDraw::spacing() const +{ + return d_data->spacing; +} + +/*! + \brief Set a minimum for the extent + + The extent is calculated from the coomponents 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(int minExtent) +{ + if ( minExtent < 0 ) + minExtent = 0; + + d_data->minExtent = minExtent; +} + +/*! + Get the minimum extent + \sa extent(), setMinimumExtent() +*/ +int 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, int length) +{ + if ( tickType < QwtScaleDiv::MinorTick || + tickType > QwtScaleDiv::MajorTick ) + { + return; + } + + if ( length < 0 ) + length = 0; + + const int maxTickLen = 1000; + if ( length > maxTickLen ) + length = 1000; + + d_data->tickLength[tickType] = length; +} + +/*! + Return the length of the ticks + + \sa setTickLength(), majTickLength() +*/ +int QwtAbstractScaleDraw::tickLength(QwtScaleDiv::TickType tickType) const +{ + if ( tickType < QwtScaleDiv::MinorTick || + tickType > QwtScaleDiv::MajorTick ) + { + return 0; + } + + return d_data->tickLength[tickType]; +} + +/*! + The same as QwtAbstractScaleDraw::tickLength(QwtScaleDiv::MajorTick). +*/ +int QwtAbstractScaleDraw::majTickLength() const +{ + return d_data->tickLength[QwtScaleDiv::MajorTick]; +} + +/*! + \brief Convert a value into its representing label + + The value is converted to a plain text using + QLocale::system().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::system().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 QwtAbstractScaleDraw::tickLabel + + The cache is invalidated, when a new QwtScaleDiv is set. If + the labels need to be changed. while the same QwtScaleDiv is set, + QwtAbstractScaleDraw::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..bc3381990 --- /dev/null +++ b/qwt/src/qwt_abstract_scale_draw.h @@ -0,0 +1,145 @@ +/* -*- 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" + + +#if QT_VERSION < 0x040000 +class QColorGroup; +#else +class QPalette; +#endif +class QPainter; +class QFont; +class QwtScaleTransformation; +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 QwtAbstractScaleDraw::setScaleDiv(const QwtScaleDiv &s), + the scale can be drawn with the QwtAbstractScaleDraw::draw() member. +*/ +class QWT_EXPORT QwtAbstractScaleDraw +{ +public: + + /*! + Components of a scale + + - Backbone + - Ticks + - Labels + + \sa enableComponent(), hasComponent + */ + + enum ScaleComponent + { + Backbone = 1, + Ticks = 2, + Labels = 4 + }; + + QwtAbstractScaleDraw(); + QwtAbstractScaleDraw( const QwtAbstractScaleDraw & ); + virtual ~QwtAbstractScaleDraw(); + + QwtAbstractScaleDraw &operator=(const QwtAbstractScaleDraw &); + + void setScaleDiv(const QwtScaleDiv &s); + const QwtScaleDiv& scaleDiv() const; + + void setTransformation(QwtScaleTransformation *); + const QwtScaleMap &map() const; + + void enableComponent(ScaleComponent, bool enable = true); + bool hasComponent(ScaleComponent) const; + + void setTickLength(QwtScaleDiv::TickType, int length); + int tickLength(QwtScaleDiv::TickType) const; + int majTickLength() const; + + void setSpacing(int margin); + int spacing() const; + +#if QT_VERSION < 0x040000 + virtual void draw(QPainter *, const QColorGroup &) const; +#else + virtual void draw(QPainter *, const QPalette &) const; +#endif + + virtual QwtText label(double) const; + + /*! + Calculate the extent + + The extent is the distcance from the baseline to the outermost + pixel of the scale draw in opposite to its orientation. + It is at least minimumExtent() pixels. + + \sa setMinimumExtent(), minimumExtent() + */ + virtual int extent(const QPen &, const QFont &) const = 0; + + void setMinimumExtent(int); + int minimumExtent() const; + + QwtScaleMap &scaleMap(); + +protected: + /*! + Draw a tick + + \param painter Painter + \param value Value of the tick + \param len Lenght of the tick + + \sa drawBackbone(), drawLabel() + */ + virtual void drawTick(QPainter *painter, double value, int 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: + int operator==(const QwtAbstractScaleDraw &) const; + int operator!=(const QwtAbstractScaleDraw &) const; + + class PrivateData; + PrivateData *d_data; +}; + +#endif diff --git a/qwt/src/qwt_abstract_slider.cpp b/qwt/src/qwt_abstract_slider.cpp new file mode 100644 index 000000000..f5ed74a4a --- /dev/null +++ b/qwt/src/qwt_abstract_slider.cpp @@ -0,0 +1,588 @@ +/* -*- 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 "qwt_abstract_slider.h" +#include "qwt_math.h" + +#ifndef WHEEL_DELTA +#define WHEEL_DELTA 120 +#endif + +class QwtAbstractSlider::PrivateData +{ +public: + PrivateData(): + scrollMode(ScrNone), + mouseOffset(0.0), + tracking(true), + tmrID(0), + updTime(150), + mass(0.0), + readOnly(false) + { + } + + int scrollMode; + double mouseOffset; + int direction; + int tracking; + + int tmrID; + int updTime; + int timerTick; + QTime time; + double speed; + double mass; + Qt::Orientation orientation; + bool readOnly; +}; + +/*! + \brief Constructor + + \param orientation Orientation + \param parent Parent widget +*/ +QwtAbstractSlider::QwtAbstractSlider( + Qt::Orientation orientation, QWidget *parent): + QWidget(parent, NULL) +{ + d_data = new QwtAbstractSlider::PrivateData; + d_data->orientation = orientation; + +#if QT_VERSION >= 0x040000 + using namespace Qt; +#endif + setFocusPolicy(TabFocus); +} + +//! Destructor +QwtAbstractSlider::~QwtAbstractSlider() +{ + if(d_data->tmrID) + killTimer(d_data->tmrID); + + delete d_data; +} + +/*! + En/Disable read only mode + + In read only mode the slider can't be controlled by mouse + or keyboard. + + \param readOnly Enables in case of true + \sa isReadOnly() +*/ +void QwtAbstractSlider::setReadOnly(bool readOnly) +{ + d_data->readOnly = readOnly; + 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 Set the orientation. + \param o Orientation. Allowed values are + Qt::Horizontal and Qt::Vertical. +*/ +void QwtAbstractSlider::setOrientation(Qt::Orientation o) +{ + d_data->orientation = o; +} + +/*! + \return Orientation + \sa setOrientation() +*/ +Qt::Orientation QwtAbstractSlider::orientation() const +{ + return d_data->orientation; +} + +//! Stop updating if automatic scrolling is active + +void QwtAbstractSlider::stopMoving() +{ + if(d_data->tmrID) + { + killTimer(d_data->tmrID); + d_data->tmrID = 0; + } +} + +/*! + \brief Specify the update interval for automatic scrolling + \param t update interval in milliseconds + \sa getScrollMode() +*/ +void QwtAbstractSlider::setUpdateTime(int t) +{ + if (t < 50) + t = 50; + d_data->updTime = t; +} + + +/*! + Mouse press event handler + \param e Mouse event +*/ +void QwtAbstractSlider::mousePressEvent(QMouseEvent *e) +{ + if ( isReadOnly() ) + { + e->ignore(); + return; + } + if ( !isValid() ) + return; + + const QPoint &p = e->pos(); + + d_data->timerTick = 0; + + getScrollMode(p, d_data->scrollMode, d_data->direction); + stopMoving(); + + switch(d_data->scrollMode) + { + case ScrPage: + case ScrTimer: + d_data->mouseOffset = 0; + d_data->tmrID = startTimer(qwtMax(250, 2 * d_data->updTime)); + break; + + case ScrMouse: + d_data->time.start(); + d_data->speed = 0; + d_data->mouseOffset = getValue(p) - value(); + emit sliderPressed(); + break; + + default: + d_data->mouseOffset = 0; + d_data->direction = 0; + break; + } +} + + +//! Emits a valueChanged() signal if necessary +void QwtAbstractSlider::buttonReleased() +{ + if ((!d_data->tracking) || (value() != prevValue())) + emit valueChanged(value()); +} + + +/*! + Mouse Release Event handler + \param e Mouse event +*/ +void QwtAbstractSlider::mouseReleaseEvent(QMouseEvent *e) +{ + if ( isReadOnly() ) + { + e->ignore(); + return; + } + if ( !isValid() ) + return; + + const double inc = step(); + + switch(d_data->scrollMode) + { + case ScrMouse: + { + setPosition(e->pos()); + d_data->direction = 0; + d_data->mouseOffset = 0; + if (d_data->mass > 0.0) + { + const int ms = d_data->time.elapsed(); + if ((fabs(d_data->speed) > 0.0) && (ms < 50)) + d_data->tmrID = startTimer(d_data->updTime); + } + else + { + d_data->scrollMode = ScrNone; + buttonReleased(); + } + emit sliderReleased(); + + break; + } + + case ScrDirect: + { + setPosition(e->pos()); + d_data->direction = 0; + d_data->mouseOffset = 0; + d_data->scrollMode = ScrNone; + buttonReleased(); + break; + } + + case ScrPage: + { + stopMoving(); + if (!d_data->timerTick) + QwtDoubleRange::incPages(d_data->direction); + d_data->timerTick = 0; + buttonReleased(); + d_data->scrollMode = ScrNone; + break; + } + + case ScrTimer: + { + stopMoving(); + if (!d_data->timerTick) + QwtDoubleRange::fitValue(value() + double(d_data->direction) * inc); + d_data->timerTick = 0; + buttonReleased(); + d_data->scrollMode = ScrNone; + break; + } + + default: + { + d_data->scrollMode = ScrNone; + buttonReleased(); + } + } +} + + +/*! + Move the slider to a specified point, adjust the value + and emit signals if necessary. +*/ +void QwtAbstractSlider::setPosition(const QPoint &p) +{ + QwtDoubleRange::fitValue(getValue(p) - d_data->mouseOffset); +} + + +/*! + \brief Enables or disables tracking. + + If tracking is enabled, the slider emits a + valueChanged() signal whenever its value + changes (the default behaviour). If tracking + is disabled, the value changed() signal will only + be emitted if: