Revision 1297:0a574315af3e .svn/pristine/7d

View differences:

.svn/pristine/7d/7d269c022be2b708c54850fa0944c980f7e05aca.svn-base
1
<%= l(:text_issue_updated, :id => "##{@issue.id}", :author => h(@journal.user)) %>
2

  
3
<ul>
4
<% details_to_strings(@journal.details, false, :only_path => false).each do |string| %>
5
  <li><%= string %></li>
6
<% end %>
7
</ul>
8

  
9
<%= textilizable(@journal, :notes, :only_path => false) %>
10
<hr />
11
<%= render :partial => 'issue', :formats => [:html], :locals => { :issue => @issue, :issue_url => @issue_url } %>
.svn/pristine/7d/7d9750d88bd333d9b7606eb3694629d8e8fa3a5e.svn-base
1
require 'SVG/Graph/Graph'
2

  
3
module SVG
4
  module Graph
5
    # === Create presentation quality SVG pie graphs easily
6
    # 
7
    # == Synopsis
8
    # 
9
    #   require 'SVG/Graph/Pie'
10
    # 
11
    #   fields = %w(Jan Feb Mar)
12
    #   data_sales_02 = [12, 45, 21]
13
    #   
14
    #   graph = SVG::Graph::Pie.new({
15
    #   	:height => 500,
16
    # 	  :width  => 300,
17
    # 	  :fields => fields,
18
    #   })
19
    #   
20
    #   graph.add_data({
21
    #   	:data => data_sales_02,
22
    # 	  :title => 'Sales 2002',
23
    #   })
24
    #   
25
    #   print "Content-type: image/svg+xml\r\n\r\n"
26
    #   print graph.burn();
27
    # 
28
    # == Description
29
    # 
30
    # This object aims to allow you to easily create high quality
31
    # SVG pie graphs. You can either use the default style sheet
32
    # or supply your own. Either way there are many options which can
33
    # be configured to give you control over how the graph is
34
    # generated - with or without a key, display percent on pie chart,
35
    # title, subtitle etc.
36
    #
37
    # = Examples
38
    # 
39
    # http://www.germane-software/repositories/public/SVG/test/single.rb
40
    # 
41
    # == See also
42
    #
43
    # * SVG::Graph::Graph
44
    # * SVG::Graph::BarHorizontal
45
    # * SVG::Graph::Bar
46
    # * SVG::Graph::Line
47
    # * SVG::Graph::Plot
48
    # * SVG::Graph::TimeSeries
49
    #
50
    # == Author
51
    #
52
    # Sean E. Russell <serATgermaneHYPHENsoftwareDOTcom>
53
    #
54
    # Copyright 2004 Sean E. Russell
55
    # This software is available under the Ruby license[LICENSE.txt]
56
    #
57
    class Pie < Graph
58
      # Defaults are those set by Graph::initialize, and
59
      # [show_shadow] true
60
      # [shadow_offset] 10
61
      # [show_data_labels] false
62
      # [show_actual_values] false
63
      # [show_percent] true
64
      # [show_key_data_labels] true
65
      # [show_key_actual_values] true
66
      # [show_key_percent] false
67
      # [expanded] false
68
      # [expand_greatest] false
69
      # [expand_gap] 10
70
      # [show_x_labels] false
71
      # [show_y_labels] false
72
      # [datapoint_font_size] 12
73
      def set_defaults
74
        init_with(
75
          :show_shadow		        => true,
76
          :shadow_offset	        => 10, 
77
          
78
          :show_data_labels	      => false,
79
          :show_actual_values     => false,
80
          :show_percent		        => true,
81

  
82
          :show_key_data_labels	  => true,
83
          :show_key_actual_values => true,
84
          :show_key_percent		    => false,
85
          
86
          :expanded				        => false,
87
          :expand_greatest		    => false,
88
          :expand_gap             => 10,
89
          
90
          :show_x_labels          => false,
91
          :show_y_labels          => false,
92
          :datapoint_font_size    => 12
93
        )
94
        @data = []
95
      end
96

  
97
      # Adds a data set to the graph.
98
      #
99
      #   graph.add_data( { :data => [1,2,3,4] } )
100
      #
101
      # Note that the :title is not necessary.  If multiple
102
      # data sets are added to the graph, the pie chart will
103
      # display the +sums+ of the data.  EG:
104
      #
105
      #   graph.add_data( { :data => [1,2,3,4] } )
106
      #   graph.add_data( { :data => [2,3,5,9] } )
107
      #
108
      # is the same as:
109
      #
110
      #   graph.add_data( { :data => [3,5,8,13] } )
111
      def add_data arg
112
        arg[:data].each_index {|idx|
113
          @data[idx] = 0 unless @data[idx]
114
          @data[idx] += arg[:data][idx]
115
        }
116
      end
117

  
118
      # If true, displays a drop shadow for the chart
119
      attr_accessor :show_shadow 
120
      # Sets the offset of the shadow from the pie chart
121
      attr_accessor :shadow_offset
122
      # If true, display the data labels on the chart
123
      attr_accessor :show_data_labels 
124
      # If true, display the actual field values in the data labels
125
      attr_accessor :show_actual_values 
126
      # If true, display the percentage value of each pie wedge in the data
127
      # labels
128
      attr_accessor :show_percent
129
      # If true, display the labels in the key
130
      attr_accessor :show_key_data_labels 
131
      # If true, display the actual value of the field in the key
132
      attr_accessor :show_key_actual_values 
133
      # If true, display the percentage value of the wedges in the key
134
      attr_accessor :show_key_percent
135
      # If true, "explode" the pie (put space between the wedges)
136
      attr_accessor :expanded 
137
      # If true, expand the largest pie wedge
138
      attr_accessor :expand_greatest 
139
      # The amount of space between expanded wedges
140
      attr_accessor :expand_gap 
141
      # The font size of the data point labels
142
      attr_accessor :datapoint_font_size
143

  
144

  
145
      protected
146

  
147
      def add_defs defs
148
        gradient = defs.add_element( "filter", {
149
          "id"=>"dropshadow",
150
          "width" => "1.2",
151
          "height" => "1.2",
152
        } )
153
        gradient.add_element( "feGaussianBlur", {
154
          "stdDeviation" => "4",
155
          "result" => "blur"
156
        })
157
      end
158

  
159
      # We don't need the graph
160
      def draw_graph
161
      end
162

  
163
      def get_y_labels
164
        [""]
165
      end
166

  
167
      def get_x_labels
168
        [""]
169
      end
170

  
171
      def keys
172
        total = 0
173
        max_value = 0
174
        @data.each {|x| total += x }
175
        percent_scale = 100.0 / total
176
        count = -1
177
        a = @config[:fields].collect{ |x|
178
          count += 1
179
          v = @data[count]
180
          perc = show_key_percent ? " "+(v * percent_scale).round.to_s+"%" : ""
181
          x + " [" + v.to_s + "]" + perc
182
        }
183
      end
184

  
185
      RADIANS = Math::PI/180
186

  
187
      def draw_data
188
        @graph = @root.add_element( "g" )
189
        background = @graph.add_element("g")
190
        midground = @graph.add_element("g")
191

  
192
        diameter = @graph_height > @graph_width ? @graph_width : @graph_height
193
        diameter -= expand_gap if expanded or expand_greatest
194
        diameter -= datapoint_font_size if show_data_labels
195
        diameter -= 10 if show_shadow
196
        radius = diameter / 2.0
197

  
198
        xoff = (width - diameter) / 2
199
        yoff = (height - @border_bottom - diameter)
200
        yoff -= 10 if show_shadow
201
        @graph.attributes['transform'] = "translate( #{xoff} #{yoff} )"
202

  
203
        wedge_text_pad = 5
204
        wedge_text_pad = 20 if show_percent and show_data_labels
205

  
206
        total = 0
207
        max_value = 0
208
        @data.each {|x| 
209
          max_value = max_value < x ? x : max_value
210
          total += x 
211
        }
212
        percent_scale = 100.0 / total
213

  
214
        prev_percent = 0
215
        rad_mult = 3.6 * RADIANS
216
        @config[:fields].each_index { |count|
217
          value = @data[count]
218
          percent = percent_scale * value
219

  
220
          radians = prev_percent * rad_mult
221
          x_start = radius+(Math.sin(radians) * radius)
222
          y_start = radius-(Math.cos(radians) * radius)
223
          radians = (prev_percent+percent) * rad_mult
224
          x_end = radius+(Math.sin(radians) * radius)
225
          x_end -= 0.00001 if @data.length == 1
226
          y_end = radius-(Math.cos(radians) * radius)
227
          path = "M#{radius},#{radius} L#{x_start},#{y_start} "+
228
            "A#{radius},#{radius} "+
229
            "0, #{percent >= 50 ? '1' : '0'},1, "+
230
            "#{x_end} #{y_end} Z"
231

  
232

  
233
          wedge = @foreground.add_element( "path", {
234
            "d" => path,
235
            "class" => "fill#{count+1}"
236
          })
237

  
238
          translate = nil
239
          tx = 0
240
          ty = 0
241
          half_percent = prev_percent + percent / 2
242
          radians = half_percent * rad_mult
243

  
244
          if show_shadow
245
            shadow = background.add_element( "path", {
246
              "d" => path,
247
              "filter" => "url(#dropshadow)",
248
              "style" => "fill: #ccc; stroke: none;"
249
            })
250
            clear = midground.add_element( "path", {
251
              "d" => path,
252
              "style" => "fill: #fff; stroke: none;"
253
            })
254
          end
255

  
256
          if expanded or (expand_greatest && value == max_value)
257
            tx = (Math.sin(radians) * expand_gap)
258
            ty = -(Math.cos(radians) * expand_gap)
259
            translate = "translate( #{tx} #{ty} )"
260
            wedge.attributes["transform"] = translate
261
            clear.attributes["transform"] = translate if clear
262
          end
263

  
264
          if show_shadow
265
            shadow.attributes["transform"] = 
266
              "translate( #{tx+shadow_offset} #{ty+shadow_offset} )"
267
          end
268
          
269
          if show_data_labels and value != 0
270
            label = ""
271
            label += @config[:fields][count] if show_key_data_labels
272
            label += " ["+value.to_s+"]" if show_actual_values
273
            label += " "+percent.round.to_s+"%" if show_percent
274

  
275
            msr = Math.sin(radians)
276
            mcr = Math.cos(radians)
277
            tx = radius + (msr * radius)
278
            ty = radius -(mcr * radius)
279

  
280
            if expanded or (expand_greatest && value == max_value)
281
              tx += (msr * expand_gap)
282
              ty -= (mcr * expand_gap)
283
            end
284
            @foreground.add_element( "text", {
285
              "x" => tx.to_s,
286
              "y" => ty.to_s,
287
              "class" => "dataPointLabel",
288
              "style" => "stroke: #fff; stroke-width: 2;"
289
            }).text = label.to_s
290
            @foreground.add_element( "text", {
291
              "x" => tx.to_s,
292
              "y" => ty.to_s,
293
              "class" => "dataPointLabel",
294
            }).text = label.to_s
295
          end
296

  
297
          prev_percent += percent
298
        }
299
      end
300
      
301

  
302
      def round val, to
303
        up = 10**to.to_f
304
        (val * up).to_i / up
305
      end
306

  
307

  
308
      def get_css
309
        return <<EOL
310
.dataPointLabel{
311
	fill: #000000;
312
	text-anchor:middle;
313
	font-size: #{datapoint_font_size}px;
314
	font-family: "Arial", sans-serif;
315
	font-weight: normal;
316
}
317

  
318
/* key - MUST match fill styles */
319
.key1,.fill1{
320
	fill: #ff0000;
321
	fill-opacity: 0.7;
322
	stroke: none;
323
	stroke-width: 1px;	
324
}
325
.key2,.fill2{
326
	fill: #0000ff;
327
	fill-opacity: 0.7;
328
	stroke: none;
329
	stroke-width: 1px;	
330
}
331
.key3,.fill3{
332
	fill-opacity: 0.7;
333
	fill: #00ff00;
334
	stroke: none;
335
	stroke-width: 1px;	
336
}
337
.key4,.fill4{
338
	fill-opacity: 0.7;
339
	fill: #ffcc00;
340
	stroke: none;
341
	stroke-width: 1px;	
342
}
343
.key5,.fill5{
344
	fill-opacity: 0.7;
345
	fill: #00ccff;
346
	stroke: none;
347
	stroke-width: 1px;	
348
}
349
.key6,.fill6{
350
	fill-opacity: 0.7;
351
	fill: #ff00ff;
352
	stroke: none;
353
	stroke-width: 1px;	
354
}
355
.key7,.fill7{
356
	fill-opacity: 0.7;
357
	fill: #00ff99;
358
	stroke: none;
359
	stroke-width: 1px;	
360
}
361
.key8,.fill8{
362
	fill-opacity: 0.7;
363
	fill: #ffff00;
364
	stroke: none;
365
	stroke-width: 1px;	
366
}
367
.key9,.fill9{
368
	fill-opacity: 0.7;
369
	fill: #cc6666;
370
	stroke: none;
371
	stroke-width: 1px;	
372
}
373
.key10,.fill10{
374
	fill-opacity: 0.7;
375
	fill: #663399;
376
	stroke: none;
377
	stroke-width: 1px;	
378
}
379
.key11,.fill11{
380
	fill-opacity: 0.7;
381
	fill: #339900;
382
	stroke: none;
383
	stroke-width: 1px;	
384
}
385
.key12,.fill12{
386
	fill-opacity: 0.7;
387
	fill: #9966FF;
388
	stroke: none;
389
	stroke-width: 1px;	
390
}
391
EOL
392
      end
393
    end
394
  end
395
end
.svn/pristine/7d/7dca38a5b7c6d3fe11bbdf716206c78cae18789c.svn-base
1
# Redmine - project management software
2
# Copyright (C) 2006-2012  Jean-Philippe Lang
3
#
4
# This program is free software; you can redistribute it and/or
5
# modify it under the terms of the GNU General Public License
6
# as published by the Free Software Foundation; either version 2
7
# of the License, or (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17

  
18
class TimeEntry < ActiveRecord::Base
19
  include Redmine::SafeAttributes
20
  # could have used polymorphic association
21
  # project association here allows easy loading of time entries at project level with one database trip
22
  belongs_to :project
23
  belongs_to :issue
24
  belongs_to :user
25
  belongs_to :activity, :class_name => 'TimeEntryActivity', :foreign_key => 'activity_id'
26

  
27
  attr_protected :project_id, :user_id, :tyear, :tmonth, :tweek
28

  
29
  acts_as_customizable
30
  acts_as_event :title => Proc.new {|o| "#{l_hours(o.hours)} (#{(o.issue || o.project).event_title})"},
31
                :url => Proc.new {|o| {:controller => 'timelog', :action => 'index', :project_id => o.project, :issue_id => o.issue}},
32
                :author => :user,
33
                :description => :comments
34

  
35
  acts_as_activity_provider :timestamp => "#{table_name}.created_on",
36
                            :author_key => :user_id,
37
                            :find_options => {:include => :project}
38

  
39
  validates_presence_of :user_id, :activity_id, :project_id, :hours, :spent_on
40
  validates_numericality_of :hours, :allow_nil => true, :message => :invalid
41
  validates_length_of :comments, :maximum => 255, :allow_nil => true
42
  before_validation :set_project_if_nil
43
  validate :validate_time_entry
44

  
45
  scope :visible, lambda {|*args| {
46
    :include => :project,
47
    :conditions => Project.allowed_to_condition(args.shift || User.current, :view_time_entries, *args)
48
  }}
49
  scope :on_issue, lambda {|issue| {
50
    :include => :issue,
51
    :conditions => "#{Issue.table_name}.root_id = #{issue.root_id} AND #{Issue.table_name}.lft >= #{issue.lft} AND #{Issue.table_name}.rgt <= #{issue.rgt}"
52
  }}
53
  scope :on_project, lambda {|project, include_subprojects| {
54
    :include => :project,
55
    :conditions => project.project_condition(include_subprojects)
56
  }}
57
  scope :spent_between, lambda {|from, to|
58
    if from && to
59
     {:conditions => ["#{TimeEntry.table_name}.spent_on BETWEEN ? AND ?", from, to]}
60
    elsif from
61
     {:conditions => ["#{TimeEntry.table_name}.spent_on >= ?", from]}
62
    elsif to
63
     {:conditions => ["#{TimeEntry.table_name}.spent_on <= ?", to]}
64
    else
65
     {}
66
    end
67
  }
68

  
69
  safe_attributes 'hours', 'comments', 'issue_id', 'activity_id', 'spent_on', 'custom_field_values', 'custom_fields'
70

  
71
  def initialize(attributes=nil, *args)
72
    super
73
    if new_record? && self.activity.nil?
74
      if default_activity = TimeEntryActivity.default
75
        self.activity_id = default_activity.id
76
      end
77
      self.hours = nil if hours == 0
78
    end
79
  end
80

  
81
  def set_project_if_nil
82
    self.project = issue.project if issue && project.nil?
83
  end
84

  
85
  def validate_time_entry
86
    errors.add :hours, :invalid if hours && (hours < 0 || hours >= 1000)
87
    errors.add :project_id, :invalid if project.nil?
88
    errors.add :issue_id, :invalid if (issue_id && !issue) || (issue && project!=issue.project)
89
  end
90

  
91
  def hours=(h)
92
    write_attribute :hours, (h.is_a?(String) ? (h.to_hours || h) : h)
93
  end
94

  
95
  def hours
96
    h = read_attribute(:hours)
97
    if h.is_a?(Float)
98
      h.round(2)
99
    else
100
      h
101
    end
102
  end
103

  
104
  # tyear, tmonth, tweek assigned where setting spent_on attributes
105
  # these attributes make time aggregations easier
106
  def spent_on=(date)
107
    super
108
    if spent_on.is_a?(Time)
109
      self.spent_on = spent_on.to_date
110
    end
111
    self.tyear = spent_on ? spent_on.year : nil
112
    self.tmonth = spent_on ? spent_on.month : nil
113
    self.tweek = spent_on ? Date.civil(spent_on.year, spent_on.month, spent_on.day).cweek : nil
114
  end
115

  
116
  # Returns true if the time entry can be edited by usr, otherwise false
117
  def editable_by?(usr)
118
    (usr == user && usr.allowed_to?(:edit_own_time_entries, project)) || usr.allowed_to?(:edit_time_entries, project)
119
  end
120
end
.svn/pristine/7d/7dcff04daa3640525c3742cf4b96f170992ba0a8.svn-base
1
/* Chinese initialisation for the jQuery UI date picker plugin. */
2
/* Written by Ressol (ressol@gmail.com). */
3
jQuery(function($){
4
	$.datepicker.regional['zh-TW'] = {
5
		closeText: '關閉',
6
		prevText: '&#x3c;上月',
7
		nextText: '下月&#x3e;',
8
		currentText: '今天',
9
		monthNames: ['一月','二月','三月','四月','五月','六月',
10
		'七月','八月','九月','十月','十一月','十二月'],
11
		monthNamesShort: ['一','二','三','四','五','六',
12
		'七','八','九','十','十一','十二'],
13
		dayNames: ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'],
14
		dayNamesShort: ['周日','周一','周二','周三','周四','周五','周六'],
15
		dayNamesMin: ['日','一','二','三','四','五','六'],
16
		weekHeader: '周',
17
		dateFormat: 'yy/mm/dd',
18
		firstDay: 1,
19
		isRTL: false,
20
		showMonthAfterYear: true,
21
		yearSuffix: '年'};
22
	$.datepicker.setDefaults($.datepicker.regional['zh-TW']);
23
});

Also available in: Unified diff