manage bookmarks, bugfixes
[smr.git] / gui / app / helpers / application_helper.rb
blob64914792545e5b30965cc65f0bfb6616da1323cb
2 # This file is part of SMR.
4 # SMR is free software: you can redistribute it and/or modify it under the
5 # terms of the GNU General Public License as published by the Free Software
6 # Foundation, either version 3 of the License, or (at your option) any later
7 # version.
9 # SMR is distributed in the hope that it will be useful, but WITHOUT ANY
10 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
11 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License along with
14 # SMR.  If not, see <http://www.gnu.org/licenses/>.
17 require 'percentage'
20 # Collection of helper methods for use in all views.
21 module ApplicationHelper
23     ##
24     # Date the user is currently browsing, as Time, positioned at
25     # end of day (23:59:59)
26     #
27     # use :keep_current_time=>true to take the time part of Time.now and apply
28     # it to the browse date
29     def smr_browse_date(options={:keep_current_time=>false})
30         bd = if session[:smr_browse_date].nil? then Time.new
31              else Time.at(session[:smr_browse_date]) end
33         if options[:keep_current_time]
34             bd.beginning_of_day + (Time.now - Time.now.beginning_of_day)
35         else bd end
36     end
38     ##
39     # Place the users us browsing as +String+.
40     def smr_browse_position
41         controller.class.to_s.sub('Controller', '')
42     end
44     ##
45     # Returns change from +val1+ to +val2+ in percent, as smr_humanize()d String.
46     def percentage_change(val1, val2)
47         return nil if val1.zero? or val2.blank?
48         smr_humanize(Percentage.change(val1, val2))
49     end
51     ##
52     # Returns percentage of +val+ from +base+, as smr_humanize()d String.
53     def percentage_of(base, val)
54         return nil if base.zero? or not val
55         smr_humanize(BigDecimal(val.to_s).as_percentage_of(base))
56     end
58     ##
59     # Humanize a number in ways useful in SMR.
60     #
61     # This method is written to return as quick as possible because it is used
62     # many times in almost every view.
63     #
64     # Options:
65     # - :scientific notation, ie 1000 => 1.0e3
66     # - :shortword as spoken but short form, ie 1000 = 1 Kilo => 1k
67     # - :scale to force it to N digits, no matter what
68     # - :date_with_time adds time information, default for showing a Time
69     #   is YYYY-MM-DD
70     # - :date_time_only shows only the time information from given Time
71     # - :time_for_humans is fuzzy wording relative to #smr_browse_date
72     # - :seconds_for_humans converts a number into a String of hours, minutes,
73     #    seconds. For example: 12345 becomes '3 hours 25 minutes 45 seconds'.
74     #
75     def smr_humanize(n, options={
76             :scientific=>false, :shortword=>false, :scale=>false,
77             :date_with_time=>false, :date_time_only=>false,
78             :time_for_humans=>false, :seconds_for_humans=>false,
79         })
80         unit = String.new
81         format = '%s'
82         skip_rounding=false
84         # handle 'inappropriate' data quickly
85         return '-' if n == 0.0 or n == false
86         return '' if n == ''
87         return n if n.is_a?(String)
89         # decide whether to round
90         skip_rounding = true if n.is_a?(Integer) and n.abs < 1000
91         skip_rounding = true if (n.nil? or n == false)
93         if n.is_a?(Percentage)
94             unit = '%'
95             if options[:scale]
96                 format = "%.#{options[:scale]}f"
97             else
98                 format = if n.to_f < 10 then '%.1f' else '%i' end
99             end
100             skip_rounding = true           
101         end
103         if n.is_a?(Time)
104             return '-' if n.to_i == 0
106             dateformat = ''
107             if options[:time_for_humans]
108                 return 'Today' if n.between? smr_browse_date.beginning_of_day, smr_browse_date.end_of_day
109                 return 'Yesterday' if n.between? (smr_browse_date - 1.day).beginning_of_day, (smr_browse_date - 1.day).end_of_day
110                 return n.strftime('%Y') if n.between?(n.end_of_year-1.day, n.end_of_year)
111                 return n.strftime('%%Y Q%i' % ((n.month - 1) / 3 + 1) ) if n.between?(n.end_of_quarter-1.day, n.end_of_quarter)
112                 return n.strftime('%B') if n.between?(n.end_of_month-1.day, n.end_of_month)
113                 dateformat = '%%A, %s' % n.day.ordinalize
114                 dateformat += ' %B' if n.month!=smr_browse_date.month or n.year!=smr_browse_date.year
115                 dateformat += ' %Y' if n.year != smr_browse_date.year
116             else
117                 dateformat = if options[:date_with_time] then '%Y-%m-%d %H:%M'
118                     elsif options[:date_time_only] then '%H:%M:%S'
119                     else '%Y-%m-%d'
120                 end
121             end
122             return n.strftime(dateformat)
123         end
125         if options[:shortword]
126             skip_rounding = true
127             n = number_to_human(
128                 n,
129                 :units=>{
130                     :unit=>'', :thousand=>'k', :million=>'m', :billion=>'b',
131                     :trillion=>'t', :quadrillion=>'q'
132                 }
133             )
134         end
136         if options[:seconds_for_humans]
137             return [[60, :seconds], [60, :minutes], [24, :hours], [1000, :days]].map{ |count, name|
138                 if n > 0
139                   n, v = n.divmod(count)
140                   v > 0 ? "#{v.to_i} #{name}" : nil
141                 end
142             }.compact.reverse.join(' ')
143         end
145         # do generic rounding if necessary
146         if not skip_rounding and not options[:scale]
147             case n.abs
148                 when 0..1 then format = '%.4f'
149                 when 1..9 then format = '%.3f'
150                 when 10..99 then format = '%.2f'
151                 when 100..1000 then format = '%.1f'
152             else
153                 format = if options[:scientific] then '%.3e' else '%i' end
154             end
155         elsif options[:scale]
156             format = "%.#{options[:scale]}f"
157         end
159         # return a String
160         (format + '%s') % [n, unit]
161     end
163     ##
164     # Returns menu to browse SMR functionality in HTML format, see
165     # @smr_menu_items and smr_menu_addsubitem().
166     # TODO: supports one sublevel only, do we ever need more?
167     def smr_menu
168         content_tag(:ul, nil, :id=>'smr_menu') {
169           @smr_menu_items.reduce('') { |ctag, top|
170             if top.second.is_a?(Hash) then
171                 # sub level
172                 ctag << content_tag(:li) do
173                     link_to(top.first, top.second.delete('_toplink')) + 
174                     content_tag(:ul, nil) do
175                         top.second.reduce('') { |c, sub|
176                             c << content_tag(:li, link_to(sub.first, sub.second))
177                         }.html_safe
178                     end
179                 end
180             else
181                 # top level
182                 ctag << content_tag(:li, link_to(top.first, top.second))
183             end
184           }.html_safe
185         }
186     end
188     ##
189     # Create HTML container with page links.
190     #
191     # It will be positioned at +current_page+ with a maximum of +num_pages+
192     # and each link will direct to +basepath/?page=N+.
193     def smr_paginate(current_page, num_pages, basepath)
194             JustPaginate.page_navigation(current_page, num_pages) do |p|
195             '%s/?page=%i' % [basepath, p]
196         end.html_safe
197     end
199     ##
200     # Profit/Loss status number :n as text for humans to read or CSS class
201     # name.
202     def smr_profitloss_status(n, css=false)
203         if n.is_a?(FalseClass) or n == 0
204             s='Unvalued'
205         elsif n.between?(-150, 200)
206             s='Even'
207         elsif n > 0.0
208             s='Profit'
209         elsif n < 0.0
210             s='Loss'
211         end
213         if css then s.downcase else s end
214     end
216     ##
217     # Highlight +pattern+ in +string+.
218     #
219     # All occurences of +pattern+ will be surrounded with by a
220     # <tt><em class="pattern_match">...</em></tt> HTML tag. The matching is
221     # case insensitive.
222     #
223     # How the highlighting actually looks if defined in the +application.css+
224     # stylesheet.
225     #
226     # If no +pattern+ is given (or +pattern+ is false), then +string+
227     # is returned without modification.
228     def smr_highlight(string, pattern=false)
229         return string if pattern.blank?
230         if matched = /.*(#{pattern}).*/i.match(string)
231             string.gsub(matched[1], '<em class="pattern_match">%s</em>' % matched[1]).html_safe
232         else string end
233     end