1 /*
2     SPDX-FileCopyrightText: 2010 Simon Andreas Eugster <simon.eu@gmail.com>
3     This file is part of kdenlive. See www.kdenlive.org.
4 
5 SPDX-License-Identifier: GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
6 */
7 
8 #ifndef ABSTRACTSCOPEWIDGET_H
9 #define ABSTRACTSCOPEWIDGET_H
10 
11 #include <QFuture>
12 #include <QMenu>
13 #include <QSemaphore>
14 #include <QWidget>
15 
16 /** @class AbstractScopeWidget
17     @brief Abstract class for audio/colour scopes (receive data and paint it).
18 
19   This abstract widget is a proof that abstract things sometimes \b *are* useful.
20 
21   The widget expects three layers which
22   \li Will be painted on top of each other on each update
23   \li Are rendered in a separate thread so that the UI is not blocked
24   \li Are rendered only if necessary (e.g., if a layer does not depend
25     on input images, it will not be re-rendered for incoming frames)
26 
27   The layer order is as follows:
28   \verbatim
29      _____________________
30     /                     \
31    /      HUD Layer        \
32   /                         \
33   ---------------------------
34      _____________________
35     /                     \
36    /     Scope Layer       \
37   /                         \
38   ---------------------------
39      _____________________
40     /                     \
41    /   Background Layer    \
42   /                         \
43   ---------------------------
44   \endverbatim
45 
46   Colors of Scope Widgets are defined in here (and thus don't need to be
47   re-defined in the implementation of the widget's .ui file).
48 
49   The custom context menu already contains entries, like for enabling auto-
50   refresh. It can certainly be extended in the implementation of the widget.
51 
52   If you intend to write an own widget inheriting from this one, please read
53   the comments on the unimplemented methods carefully. They are not only here
54   for optical amusement, but also contain important information.
55  */
56 class AbstractScopeWidget : public QWidget
57 {
58     Q_OBJECT
59 
60 public:
61     /**
62       \param trackMouse enables mouse tracking; The variables m_mousePos and m_mouseWithinWidget will be set
63             if mouse tracking is enabled.
64       \see signalMousePositionChanged(): Emitted when mouse tracking is enabled
65       */
66     explicit AbstractScopeWidget(bool trackMouse = false, QWidget *parent = nullptr);
67     ~AbstractScopeWidget() override; // Must be virtual because of inheritance, to avoid memory leaks
68 
69     enum RescaleDirection { North, Northeast, East, Southeast };
70 
71     QPalette m_scopePalette;
72 
73     /** Initializes widget settings (reads configuration).
74       Has to be called in the implementing object. */
75     virtual void init();
76 
77     /** Tell whether this scope has auto-refresh enabled. Required for determining whether
78         new data (e.g. an image frame) has to be delivered to this widget. */
79     bool autoRefreshEnabled() const;
80 
81     bool needsSingleFrame();
82 
83     ///// Unimplemented /////
84 
85     virtual QString widgetName() const = 0;
86 
87     ///// Variables /////
88     static const QColor colHighlightLight;
89     static const QColor colHighlightDark;
90     static const QColor colDarkWhite;
91 
92     static const QPen penThick;
93     static const QPen penThin;
94     static const QPen penLight;
95     static const QPen penLightDots;
96     static const QPen penLighter;
97     static const QPen penDark;
98     static const QPen penDarkDots;
99     static const QPen penBackground;
100 
101     static const QString directions[]; // Mainly for debug output
102 
103 protected:
104     ///// Variables /////
105 
106     /** The context menu. Feel free to add new entries in your implementation. */
107     QMenu *m_menu;
108 
109     /** Enables auto refreshing of the scope.
110         This is when fresh data is incoming.
111         Resize events always force a recalculation. */
112     QAction *m_aAutoRefresh;
113 
114     /** Realtime rendering. Should be disabled if it is not supported.
115         Use the accelerationFactor variable passed to the render functions as a hint of
116         how many times faster the scope should be calculated. */
117     QAction *m_aRealtime;
118 
119     /** The mouse position; Updated when the mouse enters the widget
120         AND mouse tracking has been enabled. */
121     QPoint m_mousePos;
122     /** Knows whether the mouse currently lies within the widget or not.
123         Can e.g. be used for drawing a HUD only when the mouse is in the widget. */
124     bool m_mouseWithinWidget{false};
125 
126     /** Offset from the widget's borders */
127     const uchar offset{5};
128 
129     /** The rect on the widget we're painting in.
130         Can be used by the implementing widget, e.g. in the render methods.
131         Is updated when necessary (size changes). */
132     QRect m_scopeRect;
133 
134     /** Images storing the calculated layers. Will be used on repaint events. */
135     QImage m_imgHUD;
136     QImage m_imgScope;
137     QImage m_imgBackground;
138 
139     /** The acceleration factors can be accessed also by other renderer tasks,
140         e.g. to display the scope's acceleration factor in the HUD renderer. */
141     int m_accelFactorHUD{1};
142     int m_accelFactorScope{1};
143     int m_accelFactorBackground{1};
144 
145     /** Reads the widget's configuration.
146         Can be extended in the implementing subclass (make sure to run readConfig as well). */
147     virtual void readConfig();
148     /** Writes the widget configuration.
149         Implementing widgets have to implement an own method and run it in their destructor. */
150     void writeConfig();
151     /** Identifier for the widget's configuration. */
152     QString configName();
153 
154     ///// Unimplemented Methods /////
155 
156     /** Where on the widget we can paint in.
157         May also update other variables, like m_scopeRect or custom ones,
158         that have to change together with the widget's size.  */
159     virtual QRect scopeRect() = 0;
160 
161     /** @brief HUD renderer. Must emit signalHUDRenderingFinished().
162         @see renderScope(uint). */
163     virtual QImage renderHUD(uint accelerationFactor) = 0;
164     /** @brief Rendering function for the scope layer.
165         This function \b must emit signalScopeRenderingFinished(), otherwise the scope
166         will not attempt to ever call this function again. This signal is required for multi-threading;
167         not emitting it on unused rendering function may increase performance.
168         @param accelerationFactor hints how much faster than usual the calculation should be accomplished, if possible.
169         @see renderHUD(uint) for the upper layer
170         @see renderBackground(uint) for the layer below
171          */
172     virtual QImage renderScope(uint accelerationFactor) = 0;
173     /** @brief Background renderer. Must emit signalBackgroundRenderingFinished().
174         @see renderScope(uint) */
175     virtual QImage renderBackground(uint accelerationFactor) = 0;
176 
177     /** Must return true if the HUD layer depends on the input data.
178         If it does not, then it does not need to be re-calculated when
179         fresh data is incoming. */
180     virtual bool isHUDDependingOnInput() const = 0;
181     /** @see isHUDDependingOnInput() */
182     virtual bool isScopeDependingOnInput() const = 0;
183     /** @see isHUDDependingOnInput() */
184     virtual bool isBackgroundDependingOnInput() const = 0;
185 
186     ///// Can be reimplemented /////
187     /** Calculates the acceleration factor to be used by the render thread.
188         This method can be refined in the subclass if required. */
189     virtual uint calculateAccelFactorHUD(uint oldMseconds, uint oldFactor);
190     virtual uint calculateAccelFactorScope(uint oldMseconds, uint oldFactor);
191     virtual uint calculateAccelFactorBackground(uint oldMseconds, uint oldFactor);
192 
193     /** The Abstract Scope will try to detect the movement direction when dragging on the widget with the mouse.
194         As soon as the direction is determined it will execute this method. Can be used e.g. for re-scaling content.
195         This is just a dummy function, re-implement to add functionality. */
196     virtual void handleMouseDrag(const QPoint &movement, const RescaleDirection rescaleDirection, const Qt::KeyboardModifiers rescaleModifiers);
197 
198     ///// Reimplemented /////
199 
200     void mouseMoveEvent(QMouseEvent *event) override;
201     void mousePressEvent(QMouseEvent *event) override;
202     void mouseReleaseEvent(QMouseEvent *event) override;
203     void leaveEvent(QEvent *) override;
204     void paintEvent(QPaintEvent *) override;
205     void resizeEvent(QResizeEvent *) override;
206     void showEvent(QShowEvent *) override; // Called when the widget is activated via the Menu entry
207     //    void raise(); // Called only when  manually calling the event -> useless
208 
209 public slots:
210     /** Forces an update of all layers. */
211     void forceUpdate(bool doUpdate = true);
212     void forceUpdateHUD();
213     void forceUpdateScope();
214     void forceUpdateBackground();
215 
216 protected slots:
217     void slotAutoRefreshToggled(bool);
218 
219 signals:
220     /**
221       \param mseconds represents the time taken for the calculation.
222       \param accelerationFactor is the acceleration factor that has been used for this calculation.
223       */
224     void signalHUDRenderingFinished(uint mseconds, uint accelerationFactor);
225     void signalScopeRenderingFinished(uint mseconds, uint accelerationFactor);
226     void signalBackgroundRenderingFinished(uint mseconds, uint accelerationFactor);
227 
228     /** For the mouse position itself see m_mousePos.
229         To check whether the mouse has leaved the widget, see m_mouseWithinWidget.
230         This signal is typically connected to forceUpdateHUD(). */
231     void signalMousePositionChanged();
232 
233     /** Do we need the renderer to send its frames to us?
234         Emitted when auto-refresh is toggled. */
235     void requestAutoRefresh(bool);
236 
237 private:
238     /** Counts the number of data frames that have been rendered in the active monitor.
239         The frame number will be reset when the calculation starts for the current data set. */
240     QAtomicInt m_newHUDFrames;
241     QAtomicInt m_newScopeFrames;
242     QAtomicInt m_newBackgroundFrames;
243 
244     /** Counts the number of updates that, unlike new frames, force a recalculation
245         of the scope, like for example a resize event. */
246     QAtomicInt m_newHUDUpdates;
247     QAtomicInt m_newScopeUpdates;
248     QAtomicInt m_newBackgroundUpdates;
249 
250     /** The semaphores ensure that the QFutures for the HUD/Scope/Background threads cannot
251         be assigned a new thread while it is still running. (Could cause deadlocks and other
252         nasty things known from parallelism.) */
253     QSemaphore m_semaphoreHUD;
254     QSemaphore m_semaphoreScope;
255     QSemaphore m_semaphoreBackground;
256 
257     QFuture<QImage> m_threadHUD;
258     QFuture<QImage> m_threadScope;
259     QFuture<QImage> m_threadBackground;
260 
261     bool m_initialDimensionUpdateDone{false};
262     bool m_requestForcedUpdate{false};
263 
264     QImage m_scopeImage;
265 
266     QString m_widgetName;
267 
268     void prodHUDThread();
269     void prodScopeThread();
270     void prodBackgroundThread();
271 
272     ///// Movement detection /////
273     const int m_rescaleMinDist{4};
274     const float m_rescaleVerticalThreshold{2.0f};
275 
276     bool m_rescaleActive{false};
277     bool m_rescalePropertiesLocked{false};
278     bool m_rescaleFirstRescaleDone{true};
279     Qt::KeyboardModifiers m_rescaleModifiers;
280     RescaleDirection m_rescaleDirection{North};
281     QPoint m_rescaleStartPoint;
282 
283     bool m_scopeWarningPrinted{false};
284 
285 protected slots:
286     void slotContextMenuRequested(const QPoint &pos);
287     /** To be called when a new frame has been received.
288         The scope then decides whether and when it wants to recalculate the scope, depending
289         on whether it is currently visible and whether a calculation thread is already running. */
290     void slotRenderZoneUpdated();
291     /** The following slots are called when rendering of a component has finished. They e.g. update
292         the widget and decide whether to immediately restart the calculation thread. */
293     void slotHUDRenderingFinished(uint mseconds, uint accelerationFactor);
294     void slotScopeRenderingFinished(uint mseconds, uint accelerationFactor);
295     void slotBackgroundRenderingFinished(uint mseconds, uint accelerationFactor);
296 
297     /** Resets the acceleration factors to 1 when realtime rendering is disabled. */
298     void slotResetRealtimeFactor(bool realtimeChecked);
299 };
300 
301 #endif // ABSTRACTSCOPEWIDGET_H
302