File indexing completed on 2024-04-14 05:39:52

0001 /*****************************************************************************
0002  *   Copyright 2003 - 2010 Craig Drummond <craig.p.drummond@gmail.com>       *
0003  *   Copyright 2013 - 2015 Yichao Yu <yyc1992@gmail.com>                     *
0004  *                                                                           *
0005  *   This program is free software; you can redistribute it and/or modify    *
0006  *   it under the terms of the GNU Lesser General Public License as          *
0007  *   published by the Free Software Foundation; either version 2.1 of the    *
0008  *   License, or (at your option) version 3, or any later version accepted   *
0009  *   by the membership of KDE e.V. (or its successor approved by the         *
0010  *   membership of KDE e.V.), which shall act as a proxy defined in          *
0011  *   Section 6 of version 3 of the license.                                  *
0012  *                                                                           *
0013  *   This program is distributed in the hope that it will be useful,         *
0014  *   but WITHOUT ANY WARRANTY; without even the implied warranty of          *
0015  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU       *
0016  *   Lesser General Public License for more details.                         *
0017  *                                                                           *
0018  *   You should have received a copy of the GNU Lesser General Public        *
0019  *   License along with this library. If not,                                *
0020  *   see <http://www.gnu.org/licenses/>.                                     *
0021  *****************************************************************************/
0022 
0023 /*
0024  * Stolen from Clearlooks...
0025  */
0026 
0027 /* Yes, this is evil code. But many people seem to like hazardous things, so
0028  * it exists. Most of this was written by Kulyk Nazar.
0029  *
0030  * heavily modified by Benjamin Berg <benjamin@sipsolutions.net>.
0031  */
0032 
0033 #define CHECK_ANIMATION_TIME 0.5
0034 
0035 #include "animation.h"
0036 #include <common/common.h>
0037 
0038 namespace QtCurve {
0039 namespace Animation {
0040 
0041 class Info {
0042 public:
0043     GtkWidget *const widget;
0044 
0045     Info(const GtkWidget *w, double stop_time);
0046     ~Info();
0047     double timer_elapsed() const;
0048     void timer_reset() const;
0049     bool need_stop() const;
0050 private:
0051     GTimer *const m_timer;
0052     const double m_stop_time;
0053 };
0054 
0055 struct SignalInfo {
0056     GtkWidget *widget;
0057     unsigned long handler_id;
0058 };
0059 
0060 static GSList *connected_widgets = nullptr;
0061 static GHashTable *animated_widgets = nullptr;
0062 static int timer_id = 0;
0063 
0064 static gboolean timeoutHandler(void *data);
0065 
0066 inline
0067 Info::Info(const GtkWidget *w, double stop_time)
0068     : widget(const_cast<GtkWidget*>(w)),
0069       m_timer(g_timer_new()),
0070       m_stop_time(stop_time)
0071 {
0072 }
0073 
0074 inline
0075 Info::~Info()
0076 {
0077     g_timer_destroy(m_timer);
0078 }
0079 
0080 inline double
0081 Info::timer_elapsed() const
0082 {
0083     return g_timer_elapsed(m_timer, nullptr);
0084 }
0085 
0086 inline bool
0087 Info::need_stop() const
0088 {
0089     return m_stop_time != 0 && timer_elapsed() > m_stop_time;
0090 }
0091 
0092 inline void
0093 Info::timer_reset() const
0094 {
0095     g_timer_start(m_timer);
0096 }
0097 
0098 /* This forces a redraw on a widget */
0099 static void
0100 force_widget_redraw(GtkWidget *widget)
0101 {
0102     if (GTK_IS_PROGRESS_BAR(widget)) {
0103         gtk_widget_queue_resize(widget);
0104     } else {
0105         gtk_widget_queue_draw(widget);
0106     }
0107 }
0108 
0109 /* ensures that the timer is running */
0110 static void
0111 startTimer()
0112 {
0113     if (timer_id == 0) {
0114         timer_id = g_timeout_add(PROGRESS_ANIMATION,
0115                                  timeoutHandler, nullptr);
0116     }
0117 }
0118 
0119 /* ensures that the timer is stopped */
0120 static void
0121 stopTimer()
0122 {
0123     if (timer_id != 0) {
0124         g_source_remove(timer_id);
0125         timer_id = 0;
0126     }
0127 }
0128 
0129 /* This function does not unref the weak reference, because the object
0130  * is being destroyed currently. */
0131 static void
0132 onWidgetDestruction(void *data, GObject *object)
0133 {
0134     /* steal the animation info from the hash table(destroying it would
0135      * result in the weak reference to be unrefed, which does not work
0136      * as the widget is already destroyed. */
0137     g_hash_table_steal(animated_widgets, object);
0138     delete (Info*)data;
0139 }
0140 
0141 /* This function also needs to unref the weak reference. */
0142 static void
0143 destroyInfoAndWeakUnref(void *data)
0144 {
0145     Info *info = (Info*)data;
0146 
0147     /* force a last redraw. This is so that if the animation is removed,
0148      * the widget is left in a sane state. */
0149     force_widget_redraw(info->widget);
0150 
0151     g_object_weak_unref(G_OBJECT(info->widget),
0152                         onWidgetDestruction, data);
0153     delete info;
0154 }
0155 
0156 /* Find and return a pointer to the data linked to this widget, if it exists */
0157 static Info*
0158 lookupInfo(const GtkWidget *widget)
0159 {
0160     if (animated_widgets) {
0161         return (Info*)g_hash_table_lookup(animated_widgets, widget);
0162     }
0163     return nullptr;
0164 }
0165 
0166 /* Create all the relevant information for the animation,
0167  * and insert it into the hash table. */
0168 static void
0169 addWidget(const GtkWidget *widget, double stop_time)
0170 {
0171     /* object already in the list, do not add it twice */
0172     if (lookupInfo(widget)) {
0173         return;
0174     }
0175 
0176     if (animated_widgets == nullptr) {
0177         animated_widgets =
0178             g_hash_table_new_full(g_direct_hash, g_direct_equal, nullptr,
0179                                   destroyInfoAndWeakUnref);
0180     }
0181 
0182     auto *value = new Info(widget, stop_time);
0183 
0184     g_object_weak_ref(G_OBJECT(widget), onWidgetDestruction, value);
0185     g_hash_table_insert(animated_widgets, (GtkWidget*)widget, value);
0186 
0187     startTimer();
0188 }
0189 
0190 /* update the animation information for each widget. This will also queue a redraw
0191  * and stop the animation if it is done. */
0192 static gboolean
0193 updateInfo(void *key, void *value, void*)
0194 {
0195     Info *info = (Info*)value;
0196     GtkWidget *widget = (GtkWidget*)key;
0197 
0198     if ((widget == nullptr) || (info == nullptr)) {
0199         g_assert_not_reached();
0200     }
0201 
0202     /* remove the widget from the hash table if it is not drawable */
0203     if (!gtk_widget_is_drawable(widget)) {
0204         return true;
0205     }
0206 
0207     if (GTK_IS_PROGRESS_BAR(widget)) {
0208         float fraction =
0209             gtk_progress_bar_get_fraction(GTK_PROGRESS_BAR(widget));
0210         /* stop animation for filled/not filled progress bars */
0211         if (fraction <= 0.0 || fraction >= 1.0) {
0212             return true;
0213         }
0214     } else if (GTK_IS_ENTRY(widget)) {
0215         float fraction = gtk_entry_get_progress_fraction(GTK_ENTRY(widget));
0216 
0217         /* stop animation for filled/not filled progress bars */
0218         if (fraction <= 0.0 || fraction >= 1.0) {
0219             return true;
0220         }
0221     }
0222 
0223     force_widget_redraw(widget);
0224 
0225     /* stop at stop_time */
0226     if (info->need_stop()) {
0227         return true;
0228     }
0229     return false;
0230 }
0231 
0232 /* This gets called by the glib main loop every once in a while. */
0233 static gboolean
0234 timeoutHandler(void*)
0235 {
0236     /* enter threads as updateInfo will use gtk/gdk. */
0237     gdk_threads_enter();
0238     g_hash_table_foreach_remove(animated_widgets, updateInfo, nullptr);
0239     /* leave threads again */
0240     gdk_threads_leave();
0241 
0242     if (g_hash_table_size(animated_widgets) == 0) {
0243         stopTimer();
0244         return false;
0245     }
0246     return true;
0247 }
0248 
0249 static void
0250 onConnectedWidgetDestruction(void *data, GObject*)
0251 {
0252     connected_widgets = g_slist_remove(connected_widgets, data);
0253     free(data);
0254 }
0255 
0256 static void
0257 disconnect()
0258 {
0259     GSList *item = connected_widgets;
0260     while (item != nullptr) {
0261         SignalInfo *signal_info = (SignalInfo*)item->data;
0262 
0263         g_signal_handler_disconnect(signal_info->widget,
0264                                     signal_info->handler_id);
0265         g_object_weak_unref(G_OBJECT(signal_info->widget),
0266                             onConnectedWidgetDestruction,
0267                             signal_info);
0268         free(signal_info);
0269 
0270         item = g_slist_next(item);
0271     }
0272 
0273     g_slist_free(connected_widgets);
0274     connected_widgets = nullptr;
0275 }
0276 
0277 #if 0
0278 static void
0279 on_checkbox_toggle(GtkWidget *widget, void*)
0280 {
0281     if (Info *info = lookupInfo(widget)) {
0282         info->timer_reset();
0283     } else {
0284         addWidget(widget, CHECK_ANIMATION_TIME);
0285     }
0286 }
0287 
0288 /* helper function for animationConnectCheckbox */
0289 static int
0290 findSignalInfo(const void *signal_info, const void *widget)
0291 {
0292     return ((SignalInfo*)signal_info)->widget != widget;
0293 }
0294 
0295 /* hooks up the signals for check and radio buttons */
0296 static void
0297 connectCheckbox(GtkWidget *widget)
0298 {
0299     if (GTK_IS_CHECK_BUTTON(widget)) {
0300         if (!g_slist_find_custom(connected_widgets, widget,
0301                                  findSignalInfo)) {
0302             SignalInfo *signal_info = qtcNew(SignalInfo);
0303 
0304             signal_info->widget = widget;
0305             signal_info->handler_id =
0306                 g_signal_connect((GObject*)widget, "toggled",
0307                                  G_CALLBACK(on_checkbox_toggle), nullptr);
0308             connected_widgets = g_slist_append(connected_widgets, signal_info);
0309             g_object_weak_ref(
0310                 G_OBJECT(widget), onConnectedWidgetDestruction,
0311                 signal_info);
0312         }
0313     }
0314 }
0315 #endif
0316 
0317 /* external interface */
0318 
0319 /* adds a progress bar */
0320 void
0321 addProgressBar(GtkWidget *progressbar, bool isEntry)
0322 {
0323     double fraction =
0324         (isEntry ? gtk_entry_get_progress_fraction(GTK_ENTRY(progressbar)) :
0325          gtk_progress_bar_get_fraction(GTK_PROGRESS_BAR(progressbar)));
0326 
0327     if (fraction < 1.0 && fraction > 0.0) {
0328         addWidget((GtkWidget*)progressbar, 0.0);
0329     }
0330 }
0331 
0332 /* cleans up all resources of the animation system */
0333 void
0334 cleanup()
0335 {
0336     disconnect();
0337 
0338     if (animated_widgets != nullptr) {
0339         g_hash_table_destroy(animated_widgets);
0340         animated_widgets = nullptr;
0341     }
0342     stopTimer();
0343 }
0344 
0345 /* returns the elapsed time for the animation */
0346 double
0347 elapsed(void *data)
0348 {
0349     Info *info = lookupInfo((GtkWidget*)data);
0350 
0351     if (info) {
0352         return info->timer_elapsed();
0353     }
0354     return 0.0;
0355 }
0356 
0357 }
0358 
0359 }