/*
Copyright (C) 2012, University of Cambridge, Department of Psychiatry.
Created by Rudolf Cardinal (rnc1001@cam.ac.uk).
This file is part of CamCOPS.
CamCOPS is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
CamCOPS 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with CamCOPS. If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include <QString>
#include <QStringList>
#include <QVector>
#include <QWidget>
class Thermometer : public QWidget
{
/*
Represents clickable images/text in a vertical stack, e.g.:
image0 text0
image1 text1
image2 text2
and has two images (active + inactive) for each slot.
(It also applies a "being touched" colour.)
- The images may be pre-scaled.
- The widget scales up to the maximum size of the images/text.
- The aspect ratio of images is preserved.
- No use yet for adding images on the fly.
- Fonts currently via stylesheets.
- No current support for vertical gaps between images (generally the point
is to have no gap).
The layout of each row is as follows:
left_text IMAGE_IMAGE right_text
| | | | | |
1 2 3 4 5 6
aaaaaaaaa bbbbbbbbbbb cccccccccc
g g
dddddddddddddddddddddddddddddddd
The widget draws to a pixmap, then draws that pixmap to the screen.
In internal (pixmap) coordinates:
Positions:
[1] m_lstring_left = 0
[2] m_lstring_right
[3] m_image_left
[4] m_image_right
[5] m_rstring_left
[6]
left_text and right_text are vertically aligned with the centre of each
image. The images may well be shorter vertically than the text label. To
prevent the labels at the top and bottom from being clipped, the images may
be padded with image_padding_px (m_image_padding_px). The padding is
included when calculating the total height of the widget.
Widths:
[a] m_lstring_width;
left_string_span / (left_string_span + image_span + right_string_span)
[b] m_image_width;
image_span / (left_string_span + image_span + right_string_span)
[c] m_rstring_width;
right_string_span / (left_string_span + image_span + right_string_span)
[d] m_target_total_size.width()
[g] text_gap_px, m_text_gap_px
*/
Q_OBJECT
public:
// Constructor.
explicit Thermometer(
const QVector<QPixmap>& active_images, // top to bottom
const QVector<QPixmap>& inactive_images, // top to bottom
const QStringList* left_strings = nullptr, // top to bottom
const QStringList* right_strings = nullptr, // top to bottom
int left_string_span = 1, // arbitrary int representing "left text column proportion"
int image_span = 1, // arbitrary int representing "image column proportion"
int right_string_span = 1, // arbitrary int representing "right text column proportion"
bool allow_deselection = true, // allow images to be re-clicked to deselect them?
bool read_only = false, // read-only mode?
bool rescale_images = false, // rescale from images' intrinsic size?
double rescale_image_factor = 1.0, // if rescale: scale factor
int text_gap_px = 4, // gap between images and adjacent text
int image_padding_px = 0,
QWidget* parent = nullptr);
// ------------------------------------------------------------------------
// Standard Qt widget overrides.
// ------------------------------------------------------------------------
virtual bool hasHeightForWidth() const override;
virtual int heightForWidth(int width) const override;
virtual QSize sizeHint() const override;
virtual QSize minimumSizeHint() const override;
// ------------------------------------------------------------------------
// Picking an image
// ------------------------------------------------------------------------
// Set the selected image (negative means "none selected") and update
// the display accordingly.
void setSelectedIndex(int selected_index);
signals:
// "The user has changed the selection."
void selectionIndexChanged(int index);
protected:
// ------------------------------------------------------------------------
// Event handling
// ------------------------------------------------------------------------
// Standard Qt widget overrides.
virtual void mousePressEvent(QMouseEvent* event) override;
virtual void mouseReleaseEvent(QMouseEvent* event) override;
virtual void mouseMoveEvent(QMouseEvent* event) override;
virtual void paintEvent(QPaintEvent* event) override;
// Update the display to indicate which image is being *touched*. The user
// can touch lots (e.g. moving finger up/down on the stack) but until they
// release their finger, the selection won't change. This handles the
// finger-moving stuff.
void setTouchedIndex(int touched_index);
// ------------------------------------------------------------------------
// Coordinate calculations
// ------------------------------------------------------------------------
// Return the part of the contentsRect() that fits our aspect ratio, in
// case we are sized oddly by our owner.
QRect activeContentsRect() const;
// Returns the image rectangle for a given row, in external (screen) space.
// Used to calculate regions for redrawing.
QRect imageRect(int row) const;
// Returns the row number containing the screen coordinates specified, or
// -1 if none do.
int rowForPoint(const QPoint& pt) const;
// Scale factor, as ratio: external/internal.
qreal widgetScaleFactor(const QRect& activecontentsrect) const;
// Convert internal (pixmap) coordinates to external (screen) coordinates:
QPoint externalPt(const QPointF& internal_pt,
const QRect& activecontentsrect) const;
// Convert external (screen) coordinates to internal (pixmap) coordinates.
QPointF internalPt(const QPoint& external_pt,
const QRect& activecontentsrect) const;
QRectF internalRect(const QRect& external_rect,
const QRect& activecontentsrect) const;
// ------------------------------------------------------------------------
// Data
// ------------------------------------------------------------------------
protected:
// Config:
QVector<QPixmap> m_active_images; // all active (selected) images, top to bottom
QVector<QPixmap> m_inactive_images; // all inactive (unselected) images, top to bottom
int m_n_rows; // number of rows (each with image + text)
bool m_use_left_strings; // show text on the left of the images?
bool m_use_right_strings; // show text on the right of the images?
QStringList m_left_strings; // list of "left" strings
QStringList m_right_strings; // list of "right" strings
int m_left_string_span; // relative width of "left text" column
int m_image_span; // relative width of "image" column
int m_right_string_span; // relative width of "right text" column
bool m_allow_deselection; // allow returning to "none selected" state?
bool m_read_only; // read-only mode?
bool m_rescale_images; // rescale images?
double m_rescale_image_factor; // if rescale: by what factor?
int m_text_gap_px; // gap between images and adjacent text
int m_image_padding_px; // gap above and below the stack of images
// QColor m_unused_space_colour; // colour for any "unpainted" area
// Details of the current selection:
int m_selected_index; // -1 for none selected, or zero-based index of selected row
int m_touching_index; // similarly, for row being touched now
int m_start_touch_index; // row in which the current touch began
// Calculated layout, in raw image coordinates:
QVector<int> m_raw_image_tops; // top coordinate of each image
// Calculated layout, in internal (pixmap) coordinates:
qreal m_lstring_width; // width of "left string" column
qreal m_image_width; // width of "image" column
qreal m_rstring_width; // width of "right string" column
qreal m_lstring_left; // left edge of left string; always 0
qreal m_lstring_right; // right edge of left string
qreal m_image_left; // left edge of image
qreal m_image_right; // right edge of image
qreal m_rstring_left; // left edge of right string
QVector<QPair<qreal, qreal>> m_image_top_bottom;
QSize m_target_total_size; // final target size
qreal m_aspect_ratio; // widget aspect ratio; width / height
// Modified images (modified to show "currently being touched" shading):
QVector<QPixmap> m_active_touched_images; // "selected and being touched" images
QVector<QPixmap> m_inactive_touched_images; // "unselected and being touched" images
};