File indexing completed on 2024-04-28 09:42:12
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 }