Apply http://patches.synfig.org/r/4/: Fix bad time entry when format is set to FFf...
[synfig.git] / synfig-core / trunk / src / synfig / time.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file time.cpp
3 **      \brief Template File
4 **
5 **      $Id$
6 **
7 **      \legal
8 **      Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 **      Copyright (c) 2007, 2008 Chris Moore
10 **
11 **      This package is free software; you can redistribute it and/or
12 **      modify it under the terms of the GNU General Public License as
13 **      published by the Free Software Foundation; either version 2 of
14 **      the License, or (at your option) any later version.
15 **
16 **      This package is distributed in the hope that it will be useful,
17 **      but WITHOUT ANY WARRANTY; without even the implied warranty of
18 **      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 **      General Public License for more details.
20 **      \endlegal
21 */
22 /* ========================================================================= */
23
24 /* === H E A D E R S ======================================================= */
25
26 #ifdef USING_PCH
27 #       include "pch.h"
28 #else
29 #ifdef HAVE_CONFIG_H
30 #       include <config.h>
31 #endif
32
33 #include "time.h"
34 #include <ETL/stringf>
35 #include <ETL/misc>
36 #include "general.h"
37 #include <cmath>
38 #include <cassert>
39 #include <algorithm>
40 #include <ctype.h>
41 #include <math.h>
42
43
44 #ifdef WIN32
45 #include <float.h>
46 #ifndef isnan
47 extern "C" { int _isnan(double x); }
48 #define isnan _isnan
49 #endif
50 #endif
51
52 // For some reason isnan() isn't working on macosx any more.
53 // This is a quick fix.
54 #if defined(__APPLE__) && !defined(SYNFIG_ISNAN_FIX)
55 #ifdef isnan
56 #undef isnan
57 #endif
58 inline bool isnan(double x) { return x != x; }
59 inline bool isnan(float x) { return x != x; }
60 #define SYNFIG_ISNAN_FIX 1
61 #endif
62
63
64 #endif
65
66 /* === U S I N G =========================================================== */
67
68 using namespace std;
69 using namespace etl;
70 using namespace synfig;
71
72 #define tolower ::tolower
73
74 /* === M A C R O S ========================================================= */
75
76 /* === G L O B A L S ======================================================= */
77
78 /* === M E T H O D S ======================================================= */
79
80 Time::Time(const String &str_, float fps):
81         value_(0)
82 {
83         String str(str_);
84         std::transform(str.begin(),str.end(),str.begin(),&tolower);
85
86         // Start/Begin Of Time
87         if(str=="sot" || str=="bot")
88         {
89                 operator=(begin());
90                 return;
91         }
92         // End Of Time
93         if(str=="eot")
94         {
95                 operator=(end());
96                 return;
97         }
98
99
100         unsigned int pos=0;
101         int read;
102         float amount;
103
104         // Now try to read it in the letter-abbreviated format
105         while(pos<str.size() && sscanf(String(str,pos).c_str(),"%f%n",&amount,&read))
106         {
107                 pos+=read;
108                 if(pos>=str.size() || read==0)
109                 {
110                         // Throw up a warning if there are no units
111                         // and the amount isn't zero. There is no need
112                         // to warn about units if the value is zero
113                         // it is the only case where units are irrelevant.
114                         if(amount!=0)
115                                 synfig::warning("Time(): No unit provided in time code, assuming SECONDS (\"%s\")",str.c_str());
116                         value_+=amount;
117                         return;
118                 }
119                 switch(str[pos])
120                 {
121                         case 'h':
122                         case 'H':
123                                 value_+=amount*3600;
124                                 break;
125                         case 'm':
126                         case 'M':
127                                 value_+=amount*60;
128                                 break;
129                         case 's':
130                         case 'S':
131                                 value_+=amount;
132                                 break;
133                         case 'f':
134                         case 'F':
135                                 if(fps)
136                                         value_+=amount/fps;
137                                 else
138                                         synfig::warning("Time(): Individual frames referenced, but frame rate is unknown");
139                                 break;
140                         case ':':
141                                 // try to read it in as a traditional time format
142                                 {
143                                         int hour,minute,second;
144                                         float frame;
145                                         if(fps && sscanf(str.c_str(),"%d:%d:%d.%f",&hour,&minute,&second,&frame)==4)
146                                         {
147                                                         value_=frame/fps+(hour*3600+minute*60+second);
148                                                         return;
149                                         }
150
151                                         if(sscanf(str.c_str(),"%d:%d:%d",&hour,&minute,&second)==3)
152                                         {
153                                                 value_=hour*3600+minute*60+second;
154                                                 return;
155                                         }
156                                 }
157                                 synfig::warning("Time(): Bad time format");
158                                 break;
159
160                         default:
161                                 value_+=amount;
162                                 synfig::warning("Time(): Unexpected character '%c' when parsing time string \"%s\"",str[pos],str.c_str());
163                                 break;
164                 }
165                 pos++;
166                 amount=0;
167         }
168 }
169
170 String
171 Time::get_string(float fps, Time::Format format)const
172 {
173         Time time(*this);
174
175         if(time<=begin())
176                 return "SOT";   // Start Of Time
177         if(time>=end())
178                 return "EOT";   // End Of Time
179
180         if(fps<0)fps=0;
181
182         if(ceil(time.value_)-time.value_<epsilon_())
183                 time.value_=ceil(time.value_);
184
185         int hour,minute;
186         if(!(format<=FORMAT_FRAMES))
187         {
188                 hour=time/3600;time-=hour*3600;
189                 minute=time/60;time-=minute*60;
190         }
191         // <= is redefined, so this means "is the FORMAT_VIDEO bit set in the format?"
192         if(format<=FORMAT_VIDEO)
193         {
194                 int second;
195                 second=time;time-=second;
196
197                 if(fps && fps>1)
198                 {
199                         int frame;
200                         frame=round_to_int(time*fps);
201
202                         return strprintf("%02d:%02d:%02d.%02d",hour,minute,second,frame);
203                 }
204                 else
205                         return strprintf("%02d:%02d:%02d",hour,minute,second);
206         }
207
208         if (format <= FORMAT_FRAMES)
209         {
210                 if (fps && fps>0)
211                         return strprintf("%df", round_to_int(time * fps));
212                 else
213                         return strprintf("%ds", round_to_int(time * 1));
214         }
215
216         String ret;
217         bool started = false;
218
219         if(format<=FORMAT_FULL || hour)
220         {
221                 ret+=strprintf("%dh",hour);
222                 started = true;
223         }
224
225         if(format<=FORMAT_FULL || minute)
226         {
227                 if (!(format<=FORMAT_NOSPACES) && started)
228                         ret += " ";
229
230                 ret += strprintf("%dm", minute);
231                 started = true;
232         }
233
234         if(fps && fps>1)
235         {
236                 int second;
237                 float frame;
238                 second=time;time-=second;
239                 frame=time*fps;
240
241                 if(format<=FORMAT_FULL || second)
242                 {
243                         if (!(format<=FORMAT_NOSPACES) && started)
244                                 ret += " ";
245
246                         ret += strprintf("%ds", (int)second);
247                         started = true;
248                 }
249
250                 if(format<=FORMAT_FULL || frame || !started)
251                 {
252                         if (!(format<=FORMAT_NOSPACES) && started)
253                                 ret += " ";
254
255                         if(abs(frame-floor(frame) >= epsilon_()))
256                                 ret += strprintf("%0.3ff", frame);
257                         else
258                                 ret += strprintf("%0.0ff", frame);
259                 }
260         }
261         else
262         {
263                 float second;
264                 second=time;
265                 if(format<=FORMAT_FULL || second || !started)
266                 {
267                         if (!(format<=FORMAT_NOSPACES) && started)
268                                 ret += " ";
269
270                         if(abs(second-floor(second))>=epsilon_())
271                         {
272                                 String seconds(strprintf("%0.8f",second));
273
274                                 // skip trailing zeros
275                                 int count = 0;
276                                 String::reverse_iterator i = seconds.rbegin();
277                                 for ( ; (*i) == '0'; i++)
278                                         count++;
279
280                                 // if we removed too many, go back one place, leaving one zero
281                                 if (*i < '0' || *i > '9') count--;
282
283                                 ret += seconds.substr(0, seconds.size()-count) + "s";
284                         }
285                         else
286                                 ret+=strprintf("%0.0fs",second);
287                 }
288         }
289
290         return ret;
291 }
292
293 Time
294 Time::round(float fps)const
295 {
296         assert(fps>0);
297
298         value_type time(*this);
299
300         time*=fps;
301
302         if(abs(time-floor(time))<0.5)
303                 return floor(time)/fps;
304         else
305                 return ceil(time)/fps;
306 }
307
308 #ifdef _DEBUG
309 const char *
310 Time::c_str()const
311 {
312         return get_string().c_str();
313 }
314 #endif
315
316 //! \writeme
317 bool
318 Time::is_valid()const
319 {
320         return !isnan(value_);
321 }