2019-03-17 16:36:34 +00:00
# frozen_string_literal: true
2019-03-15 01:32:57 +00:00
2008-09-10 18:26:13 +00:00
# Redmine - project management software
2024-02-26 22:55:54 +00:00
# Copyright (C) 2006- Jean-Philippe Lang
2008-09-10 18:26:13 +00:00
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
2011-05-06 10:39:45 +00:00
#
2008-09-10 18:26:13 +00:00
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
2011-05-06 10:39:45 +00:00
#
2008-09-10 18:26:13 +00:00
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
module Redmine
module Helpers
# Simple class to handle gantt chart data
class Gantt
2019-05-21 23:53:10 +00:00
class MaxLinesLimitReached < StandardError
2014-11-26 21:06:47 +00:00
end
2010-09-10 03:09:02 +00:00
include ERB :: Util
include Redmine :: I18n
2012-10-29 10:21:00 +00:00
include Redmine :: Utils :: DateCalculation
2010-09-10 03:09:02 +00:00
2013-01-05 12:28:34 +00:00
# Relation types that are rendered
DRAW_TYPES = {
2020-11-20 11:36:44 +00:00
IssueRelation :: TYPE_BLOCKS = > { :landscape_margin = > 16 , :color = > '#F34F4F' } ,
IssueRelation :: TYPE_PRECEDES = > { :landscape_margin = > 20 , :color = > '#628FEA' }
2013-01-05 12:28:34 +00:00
} . freeze
2019-05-16 21:54:33 +00:00
UNAVAILABLE_COLUMNS = [ :tracker , :id , :subject ]
2010-09-10 03:09:02 +00:00
# Some utility methods for the PDF export
2018-06-08 00:55:41 +00:00
# @private
2010-09-10 03:09:02 +00:00
class PDF
MaxCharactorsForSubject = 45
TotalWidth = 280
LeftPaneWidth = 100
def self . right_pane_width
TotalWidth - LeftPaneWidth
end
end
2010-12-15 21:18:06 +00:00
attr_reader :year_from , :month_from , :date_from , :date_to , :zoom , :months , :truncated , :max_rows
2010-09-10 03:09:02 +00:00
attr_accessor :query
attr_accessor :project
attr_accessor :view
2011-05-06 10:39:45 +00:00
2008-09-10 18:26:13 +00:00
def initialize ( options = { } )
options = options . dup
if options [ :year ] && options [ :year ] . to_i > 0
@year_from = options [ :year ] . to_i
if options [ :month ] && options [ :month ] . to_i > = 1 && options [ :month ] . to_i < = 12
@month_from = options [ :month ] . to_i
else
@month_from = 1
end
else
2016-05-07 10:42:22 +00:00
@month_from || = User . current . today . month
@year_from || = User . current . today . year
2008-09-10 18:26:13 +00:00
end
zoom = ( options [ :zoom ] || User . current . pref [ :gantt_zoom ] ) . to_i
2011-05-06 10:39:45 +00:00
@zoom = ( zoom > 0 && zoom < 5 ) ? zoom : 2
2008-09-10 18:26:13 +00:00
months = ( options [ :months ] || User . current . pref [ :gantt_months ] ) . to_i
2019-05-08 08:57:31 +00:00
@months = ( months > 0 && months < Setting . gantt_months_limit . to_i + 1 ) ? months : 6
2008-09-10 18:26:13 +00:00
# Save gantt parameters as user preference (zoom and months count)
2019-10-22 15:55:11 +00:00
if User . current . logged? &&
( @zoom != User . current . pref [ :gantt_zoom ] ||
@months != User . current . pref [ :gantt_months ] )
2008-09-10 18:26:13 +00:00
User . current . pref [ :gantt_zoom ] , User . current . pref [ :gantt_months ] = @zoom , @months
User . current . preference . save
end
@date_from = Date . civil ( @year_from , @month_from , 1 )
@date_to = ( @date_from >> @months ) - 1
2019-03-17 16:36:34 +00:00
@subjects = + ''
@lines = + ''
2019-05-16 21:54:33 +00:00
@columns || = { }
2010-12-07 19:29:47 +00:00
@number_of_rows = nil
2010-12-15 21:18:06 +00:00
@truncated = false
if options . has_key? ( :max_rows )
@max_rows = options [ :max_rows ]
else
@max_rows = Setting . gantt_items_limit . blank? ? nil : Setting . gantt_items_limit . to_i
end
2008-09-10 18:26:13 +00:00
end
2010-09-10 03:09:18 +00:00
def common_params
2020-11-20 11:36:44 +00:00
{ :controller = > 'gantts' , :action = > 'show' , :project_id = > @project }
2010-09-10 03:09:18 +00:00
end
2011-05-06 10:39:45 +00:00
2008-09-10 18:26:13 +00:00
def params
2012-09-10 11:55:13 +00:00
common_params . merge ( { :zoom = > zoom , :year = > year_from ,
:month = > month_from , :months = > months } )
2008-09-10 18:26:13 +00:00
end
2011-05-06 10:39:45 +00:00
2008-09-10 18:26:13 +00:00
def params_previous
2012-09-10 11:55:13 +00:00
common_params . merge ( { :year = > ( date_from << months ) . year ,
:month = > ( date_from << months ) . month ,
:zoom = > zoom , :months = > months } )
2008-09-10 18:26:13 +00:00
end
2011-05-06 10:39:45 +00:00
2008-09-10 18:26:13 +00:00
def params_next
2012-09-10 11:55:13 +00:00
common_params . merge ( { :year = > ( date_from >> months ) . year ,
:month = > ( date_from >> months ) . month ,
:zoom = > zoom , :months = > months } )
2008-09-10 18:26:13 +00:00
end
2010-09-10 03:09:02 +00:00
# Returns the number of rows that will be rendered on the Gantt chart
def number_of_rows
2010-12-07 19:29:47 +00:00
return @number_of_rows if @number_of_rows
2020-12-04 13:59:29 +00:00
2011-03-10 18:07:09 +00:00
rows = projects . inject ( 0 ) { | total , p | total += number_of_rows_on_project ( p ) }
2023-01-02 06:10:53 +00:00
[ rows , @max_rows ] . min
2010-09-10 03:09:02 +00:00
end
2011-05-06 10:39:45 +00:00
2010-09-10 03:09:02 +00:00
# Returns the number of rows that will be used to list a project on
# the Gantt chart. This will recurse for each subproject.
def number_of_rows_on_project ( project )
2011-03-10 18:07:09 +00:00
return 0 unless projects . include? ( project )
2020-12-04 13:59:29 +00:00
2010-09-10 03:09:02 +00:00
count = 1
2011-03-10 18:07:09 +00:00
count += project_issues ( project ) . size
count += project_versions ( project ) . size
2010-09-10 03:09:02 +00:00
count
end
2011-05-06 10:39:45 +00:00
2010-09-10 03:09:02 +00:00
# Renders the subjects of the Gantt chart, the left side.
def subjects ( options = { } )
2010-12-07 18:40:34 +00:00
render ( options . merge ( :only = > :subjects ) ) unless @subjects_rendered
@subjects
2010-09-10 03:09:02 +00:00
end
# Renders the lines of the Gantt chart, the right side
def lines ( options = { } )
2010-12-07 18:40:34 +00:00
render ( options . merge ( :only = > :lines ) ) unless @lines_rendered
@lines
end
2011-05-06 10:39:45 +00:00
2019-05-16 21:54:33 +00:00
# Renders the selected column of the Gantt chart, the right side of subjects.
def selected_column_content ( options = { } )
render ( options . merge ( :only = > :selected_columns ) ) unless @columns . has_key? ( options [ :column ] . name )
@columns [ options [ :column ] . name ]
end
2011-03-10 18:07:09 +00:00
# Returns issues that will be rendered
def issues
@issues || = @query . issues (
2020-04-02 07:52:04 +00:00
:order = > [ " #{ Project . table_name } .lft ASC " , " #{ Issue . table_name } .id ASC " ] ,
2011-03-10 18:07:09 +00:00
:limit = > @max_rows
)
end
2011-05-06 10:39:45 +00:00
2013-01-05 12:28:34 +00:00
# Returns a hash of the relations between the issues that are present on the gantt
# and that should be displayed, grouped by issue ids.
def relations
return @relations if @relations
2020-12-04 13:59:29 +00:00
2013-01-05 12:28:34 +00:00
if issues . any?
issue_ids = issues . map ( & :id )
@relations = IssueRelation .
where ( :issue_from_id = > issue_ids , :issue_to_id = > issue_ids , :relation_type = > DRAW_TYPES . keys ) .
group_by ( & :issue_from_id )
else
@relations = { }
end
end
2011-03-10 18:07:09 +00:00
# Return all the project nodes that will be displayed
def projects
return @projects if @projects
2020-12-04 13:59:29 +00:00
2011-03-10 18:07:09 +00:00
ids = issues . collect ( & :project ) . uniq . collect ( & :id )
if ids . any?
# All issues projects and their visible ancestors
2013-06-12 19:13:25 +00:00
@projects = Project . visible .
joins ( " LEFT JOIN #{ Project . table_name } child ON #{ Project . table_name } .lft <= child.lft AND #{ Project . table_name } .rgt >= child.rgt " ) .
where ( " child.id IN (?) " , ids ) .
order ( " #{ Project . table_name } .lft ASC " ) .
2016-07-14 07:15:13 +00:00
distinct .
2014-10-22 17:37:16 +00:00
to_a
2011-03-10 18:07:09 +00:00
else
@projects = [ ]
end
end
2011-05-06 10:39:45 +00:00
2011-03-10 18:07:09 +00:00
# Returns the issues that belong to +project+
def project_issues ( project )
@issues_by_project || = issues . group_by ( & :project )
@issues_by_project [ project ] || [ ]
end
2011-05-06 10:39:45 +00:00
2011-03-10 18:07:09 +00:00
# Returns the distinct versions of the issues that belong to +project+
def project_versions ( project )
2023-01-17 01:38:27 +00:00
project_issues ( project ) . filter_map ( & :fixed_version ) . uniq
2011-03-10 18:07:09 +00:00
end
2011-05-06 10:39:45 +00:00
2011-03-10 18:07:09 +00:00
# Returns the issues that belong to +project+ and are assigned to +version+
def version_issues ( project , version )
project_issues ( project ) . select { | issue | issue . fixed_version == version }
end
2011-05-06 10:39:45 +00:00
2010-12-07 18:40:34 +00:00
def render ( options = { } )
2012-09-10 22:55:13 +00:00
options = { :top = > 0 , :top_increment = > 20 ,
:indent_increment = > 20 , :render = > :subject ,
:format = > :html } . merge ( options )
2011-03-10 18:07:09 +00:00
indent = options [ :indent ] || 4
2019-05-16 21:54:33 +00:00
@subjects = + '' unless options [ :only ] == :lines || options [ :only ] == :selected_columns
@lines = + '' unless options [ :only ] == :subjects || options [ :only ] == :selected_columns
@columns [ options [ :column ] . name ] = + '' if options [ :only ] == :selected_columns && @columns . has_key? ( options [ :column ] ) == false
2010-12-07 19:29:47 +00:00
@number_of_rows = 0
2014-11-26 21:06:47 +00:00
begin
Project . project_tree ( projects ) do | project , level |
options [ :indent ] = indent + level * options [ :indent_increment ]
render_project ( project , options )
end
rescue MaxLinesLimitReached
@truncated = true
2010-09-10 03:09:02 +00:00
end
2019-05-16 21:54:33 +00:00
@subjects_rendered = true unless options [ :only ] == :lines || options [ :only ] == :selected_columns
@lines_rendered = true unless options [ :only ] == :subjects || options [ :only ] == :selected_columns
2010-12-07 18:57:46 +00:00
render_end ( options )
2010-09-10 03:09:02 +00:00
end
def render_project ( project , options = { } )
2014-11-26 21:06:47 +00:00
render_object_row ( project , options )
increment_indent ( options ) do
# render issue that are not assigned to a version
issues = project_issues ( project ) . select { | i | i . fixed_version . nil? }
2010-12-07 18:40:34 +00:00
render_issues ( issues , options )
2014-11-26 21:06:47 +00:00
# then render project versions and their issues
versions = project_versions ( project )
self . class . sort_versions! ( versions )
versions . each do | version |
render_version ( project , version , options )
end
2010-09-10 03:09:02 +00:00
end
2014-11-26 21:06:47 +00:00
end
def render_version ( project , version , options = { } )
render_object_row ( version , options )
increment_indent ( options ) do
issues = version_issues ( project , version )
render_issues ( issues , options )
2010-09-10 03:09:02 +00:00
end
end
def render_issues ( issues , options = { } )
2014-11-26 21:06:47 +00:00
self . class . sort_issues! ( issues )
ancestors = [ ]
issues . each do | issue |
while ancestors . any? && ! issue . is_descendant_of? ( ancestors . last )
ancestors . pop
decrement_indent ( options )
end
render_object_row ( issue , options )
unless issue . leaf?
ancestors << issue
increment_indent ( options )
end
2010-09-10 03:09:02 +00:00
end
2014-11-26 21:06:47 +00:00
decrement_indent ( options , ancestors . size )
2010-09-10 03:09:02 +00:00
end
2014-11-26 21:06:47 +00:00
def render_object_row ( object , options )
class_name = object . class . name . downcase
2023-12-20 09:23:05 +00:00
send ( :" subject_for_ #{ class_name } " , object , options ) unless options [ :only ] == :lines || options [ :only ] == :selected_columns
send ( :" line_for_ #{ class_name } " , object , options ) unless options [ :only ] == :subjects || options [ :only ] == :selected_columns
2019-05-16 21:54:33 +00:00
column_content_for_issue ( object , options ) if options [ :only ] == :selected_columns && options [ :column ] . present? && object . is_a? ( Issue )
2010-09-10 03:09:02 +00:00
options [ :top ] += options [ :top_increment ]
2010-12-07 19:29:47 +00:00
@number_of_rows += 1
2014-11-26 21:06:47 +00:00
if @max_rows && @number_of_rows > = @max_rows
raise MaxLinesLimitReached
2010-09-10 03:09:02 +00:00
end
end
2011-05-06 10:39:45 +00:00
2010-12-07 18:57:46 +00:00
def render_end ( options = { } )
case options [ :format ]
2011-05-06 10:39:45 +00:00
when :pdf
2010-12-07 18:57:46 +00:00
options [ :pdf ] . Line ( 15 , options [ :top ] , PDF :: TotalWidth , options [ :top ] )
end
end
2010-09-10 03:09:02 +00:00
2014-11-26 21:06:47 +00:00
def increment_indent ( options , factor = 1 )
options [ :indent ] += options [ :indent_increment ] * factor
if block_given?
yield
decrement_indent ( options , factor )
end
end
def decrement_indent ( options , factor = 1 )
increment_indent ( options , - factor )
end
2010-09-10 03:09:02 +00:00
def subject_for_project ( project , options )
2014-11-28 13:42:47 +00:00
subject ( project . name , options , project )
2010-09-10 03:09:02 +00:00
end
def line_for_project ( project , options )
2014-11-30 13:18:22 +00:00
# Skip projects that don't have a start_date or due date
2010-10-22 22:13:39 +00:00
if project . is_a? ( Project ) && project . start_date && project . due_date
2014-11-28 13:42:47 +00:00
label = project . name
line ( project . start_date , project . due_date , nil , true , label , options , project )
2010-09-10 03:09:02 +00:00
end
end
def subject_for_version ( version , options )
2014-11-28 13:42:47 +00:00
subject ( version . to_s_with_project , options , version )
2010-09-10 03:09:02 +00:00
end
def line_for_version ( version , options )
# Skip versions that don't have a start_date
2013-01-06 14:05:55 +00:00
if version . is_a? ( Version ) && version . due_date && version . start_date
2017-11-29 19:38:44 +00:00
label = " #{ h ( version ) } #{ h ( version . visible_fixed_issues . completed_percent . to_f . round ) } % "
2010-12-17 14:59:32 +00:00
label = h ( " #{ version . project } - " ) + label unless @project && @project == version . project
2020-12-09 14:12:15 +00:00
line ( version . start_date , version . due_date ,
version . visible_fixed_issues . completed_percent ,
true , label , options , version )
2010-09-10 03:09:02 +00:00
end
end
def subject_for_issue ( issue , options )
2014-11-28 13:42:47 +00:00
subject ( issue . subject , options , issue )
2010-09-10 03:09:02 +00:00
end
def line_for_issue ( issue , options )
# Skip issues that don't have a due_before (due_date or version's due_date)
if issue . is_a? ( Issue ) && issue . due_before
2017-06-15 04:14:58 +00:00
label = issue . status . name . dup
unless issue . disabled_core_fields . include? ( 'done_ratio' )
label << " #{ issue . done_ratio } % "
end
2014-11-28 13:42:47 +00:00
markers = ! issue . leaf?
line ( issue . start_date , issue . due_before , issue . done_ratio , markers , label , options , issue )
2010-09-10 03:09:02 +00:00
end
end
2019-05-16 21:54:33 +00:00
def column_content_for_issue ( issue , options )
if options [ :format ] == :html
data_options = { }
data_options [ :collapse_expand ] = " issue- #{ issue . id } "
2021-07-31 06:21:35 +00:00
data_options [ :number_of_rows ] = number_of_rows
2019-05-16 21:54:33 +00:00
style = " position: absolute;top: #{ options [ :top ] } px; font-size: 0.8em; "
2020-12-09 14:12:15 +00:00
content =
view . content_tag (
:div , view . column_content ( options [ :column ] , issue ) ,
:style = > style , :class = > " issue_ #{ options [ :column ] . name } " ,
:id = > " #{ options [ :column ] . name } _issue_ #{ issue . id } " ,
:data = > data_options
)
2019-05-16 21:54:33 +00:00
@columns [ options [ :column ] . name ] << content if @columns . has_key? ( options [ :column ] . name )
content
end
end
2014-11-30 13:18:22 +00:00
def subject ( label , options , object = nil )
2023-12-20 09:23:05 +00:00
send :" #{ options [ :format ] } _subject " , options , label , object
2014-11-28 13:42:47 +00:00
end
def line ( start_date , end_date , done_ratio , markers , label , options , object = nil )
options [ :zoom ] || = 1
options [ :g_width ] || = ( self . date_to - self . date_from + 1 ) * options [ :zoom ]
coords = coordinates ( start_date , end_date , done_ratio , options [ :zoom ] )
2023-12-20 09:23:05 +00:00
send :" #{ options [ :format ] } _task " , options , coords , markers , label , object
2014-11-28 13:42:47 +00:00
end
2008-09-10 18:26:13 +00:00
# Generates a gantt image
2019-08-14 02:40:56 +00:00
# Only defined if MiniMagick is avalaible
2010-09-10 03:09:02 +00:00
def to_image ( format = 'PNG' )
2012-09-11 00:56:41 +00:00
date_to = ( @date_from >> @months ) - 1
2008-09-10 18:26:13 +00:00
show_weeks = @zoom > 1
show_days = @zoom > 2
2010-07-25 11:43:19 +00:00
subject_width = 400
2011-05-07 09:50:39 +00:00
header_height = 18
2008-09-10 18:26:13 +00:00
# width of one day in pixels
2012-09-11 00:56:41 +00:00
zoom = @zoom * 2
g_width = ( @date_to - @date_from + 1 ) * zoom
2010-09-10 03:09:02 +00:00
g_height = 20 * number_of_rows + 30
2012-09-11 00:56:41 +00:00
headers_height = ( show_weeks ? 2 * header_height : header_height )
2011-05-07 09:50:39 +00:00
height = g_height + headers_height
2019-08-14 02:40:56 +00:00
# TODO: Remove rmagick_font_path in a later version
2020-12-09 14:12:15 +00:00
unless Redmine :: Configuration [ 'rmagick_font_path' ] . nil?
Rails . logger . warn (
'rmagick_font_path option is deprecated. Use minimagick_font_path instead.'
)
end
font_path =
Redmine :: Configuration [ 'minimagick_font_path' ] . presence ||
Redmine :: Configuration [ 'rmagick_font_path' ] . presence
2024-10-02 23:46:17 +00:00
img = MiniMagick :: Image . create ( " . #{ format } " )
2022-02-20 19:05:49 +00:00
if Redmine :: Configuration [ 'imagemagick_convert_command' ] . present?
2025-04-03 20:32:10 +00:00
if MiniMagick . respond_to? ( :cli_path )
MiniMagick . cli_path = File . dirname ( Redmine :: Configuration [ 'imagemagick_convert_command' ] )
else
Rails . logger . warn (
'imagemagick_convert_command option is ignored because MiniMagick has removed the option to define a custom path for the binary. Please ensure the convert binary is available in your PATH.'
)
end
2022-02-20 19:05:49 +00:00
end
2024-10-02 23:46:17 +00:00
MiniMagick . convert do | gc |
2019-08-14 02:40:56 +00:00
gc . size ( '%dx%d' % [ subject_width + g_width + 1 , height ] )
gc . xc ( 'white' )
gc . font ( font_path ) if font_path . present?
# Subjects
2008-09-10 18:26:13 +00:00
gc . stroke ( 'transparent' )
2019-08-14 02:40:56 +00:00
subjects ( :image = > gc , :top = > ( headers_height + 20 ) , :indent = > 4 , :format = > :image )
# Months headers
month_f = @date_from
2012-07-21 12:53:06 +00:00
left = subject_width
2019-08-14 02:40:56 +00:00
@months . times do
width = ( ( month_f >> 1 ) - month_f ) * zoom
2012-07-21 12:53:06 +00:00
gc . fill ( 'white' )
gc . stroke ( 'grey' )
2019-08-14 02:40:56 +00:00
gc . strokewidth ( 1 )
gc . draw ( 'rectangle %d,%d %d,%d' % [
left , 0 , left + width , height
] )
2012-07-21 12:53:06 +00:00
gc . fill ( 'black' )
gc . stroke ( 'transparent' )
2019-08-14 02:40:56 +00:00
gc . strokewidth ( 1 )
gc . draw ( 'text %d,%d %s' % [
2023-09-21 00:35:13 +00:00
left . round + 8 , 14 , magick_text ( " #{ month_f . year } - #{ month_f . month } " )
2019-08-14 02:40:56 +00:00
] )
2012-07-21 12:53:06 +00:00
left = left + width
2019-08-14 02:40:56 +00:00
month_f = month_f >> 1
2012-07-21 12:53:06 +00:00
end
2019-08-14 02:40:56 +00:00
# Weeks headers
if show_weeks
left = subject_width
height = header_height
if @date_from . cwday == 1
# date_from is monday
week_f = date_from
else
# find next monday after date_from
week_f = @date_from + ( 7 - @date_from . cwday + 1 )
width = ( 7 - @date_from . cwday + 1 ) * zoom
gc . fill ( 'white' )
gc . stroke ( 'grey' )
gc . strokewidth ( 1 )
gc . draw ( 'rectangle %d,%d %d,%d' % [
left , header_height , left + width , 2 * header_height + g_height - 1
] )
left = left + width
end
while week_f < = date_to
width = ( week_f + 6 < = date_to ) ? 7 * zoom : ( date_to - week_f + 1 ) * zoom
gc . fill ( 'white' )
gc . stroke ( 'grey' )
gc . strokewidth ( 1 )
gc . draw ( 'rectangle %d,%d %d,%d' % [
left . round , header_height , left . round + width , 2 * header_height + g_height - 1
] )
gc . fill ( 'black' )
gc . stroke ( 'transparent' )
gc . strokewidth ( 1 )
gc . draw ( 'text %d,%d %s' % [
2023-09-21 00:35:13 +00:00
left . round + 2 , header_height + 14 , magick_text ( week_f . cweek . to_s )
2019-08-14 02:40:56 +00:00
] )
left = left + width
week_f = week_f + 7
end
end
# Days details (week-end in grey)
if show_days
left = subject_width
height = g_height + header_height - 1
2020-05-27 02:24:56 +00:00
( @date_from .. date_to ) . each do | g_date |
2019-08-14 02:40:56 +00:00
width = zoom
2020-05-27 02:24:56 +00:00
gc . fill ( non_working_week_days . include? ( g_date . cwday ) ? '#eee' : 'white' )
2019-08-14 02:40:56 +00:00
gc . stroke ( '#ddd' )
gc . strokewidth ( 1 )
gc . draw ( 'rectangle %d,%d %d,%d' % [
left , 2 * header_height , left + width , 2 * header_height + g_height - 1
] )
left = left + width
end
end
# border
gc . fill ( 'transparent' )
gc . stroke ( 'grey' )
gc . strokewidth ( 1 )
gc . draw ( 'rectangle %d,%d %d,%d' % [
0 , 0 , subject_width + g_width , headers_height
] )
gc . stroke ( 'black' )
gc . draw ( 'rectangle %d,%d %d,%d' % [
0 , 0 , subject_width + g_width , g_height + headers_height - 1
] )
# content
top = headers_height + 20
gc . stroke ( 'transparent' )
lines ( :image = > gc , :top = > top , :zoom = > zoom ,
:subject_width = > subject_width , :format = > :image )
# today red line
if User . current . today > = @date_from and User . current . today < = date_to
gc . stroke ( 'red' )
x = ( User . current . today - @date_from + 1 ) * zoom + subject_width
gc . draw ( 'line %g,%g %g,%g' % [
x , headers_height , x , headers_height + g_height - 1
] )
2012-07-21 12:53:06 +00:00
end
2019-08-14 02:40:56 +00:00
gc << img . path
2008-09-10 18:26:13 +00:00
end
2019-08-14 02:40:56 +00:00
img . to_blob
ensure
img . destroy! if img
end if Object . const_defined? ( :MiniMagick )
2010-09-10 03:09:02 +00:00
def to_pdf
2011-05-09 11:12:27 +00:00
pdf = :: Redmine :: Export :: PDF :: ITCPDF . new ( current_language )
2010-09-10 03:09:02 +00:00
pdf . SetTitle ( " #{ l ( :label_gantt ) } #{ project } " )
2011-03-30 07:32:08 +00:00
pdf . alias_nb_pages
2016-05-07 10:42:22 +00:00
pdf . footer_date = format_date ( User . current . today )
2010-09-10 03:09:02 +00:00
pdf . AddPage ( " L " )
2012-09-11 00:08:16 +00:00
pdf . SetFontStyle ( 'B' , 12 )
2010-09-10 03:09:02 +00:00
pdf . SetX ( 15 )
2011-03-30 07:32:08 +00:00
pdf . RDMCell ( PDF :: LeftPaneWidth , 20 , project . to_s )
2010-09-10 03:09:02 +00:00
pdf . Ln
2012-09-11 00:08:16 +00:00
pdf . SetFontStyle ( 'B' , 9 )
2010-09-10 03:09:02 +00:00
subject_width = PDF :: LeftPaneWidth
2011-05-07 09:50:39 +00:00
header_height = 5
headers_height = header_height
2010-09-10 03:09:02 +00:00
show_weeks = false
show_days = false
if self . months < 7
show_weeks = true
2012-09-11 00:08:16 +00:00
headers_height = 2 * header_height
2010-09-10 03:09:02 +00:00
if self . months < 3
show_days = true
2012-09-11 00:08:16 +00:00
headers_height = 3 * header_height
2015-10-03 09:10:27 +00:00
if self . months < 2
show_day_num = true
headers_height = 4 * header_height
end
2010-09-10 03:09:02 +00:00
end
end
g_width = PDF . right_pane_width
2020-05-27 01:56:27 +00:00
zoom = g_width / ( self . date_to - self . date_from + 1 )
2010-09-10 03:09:02 +00:00
g_height = 120
2011-05-07 09:50:39 +00:00
t_height = g_height + headers_height
2010-09-10 03:09:02 +00:00
y_start = pdf . GetY
# Months headers
month_f = self . date_from
left = subject_width
2011-05-07 09:50:39 +00:00
height = header_height
2011-05-06 10:39:45 +00:00
self . months . times do
width = ( ( month_f >> 1 ) - month_f ) * zoom
2010-09-10 03:09:02 +00:00
pdf . SetY ( y_start )
pdf . SetX ( left )
2011-03-30 07:32:08 +00:00
pdf . RDMCell ( width , height , " #{ month_f . year } - #{ month_f . month } " , " LTR " , 0 , " C " )
2010-09-10 03:09:02 +00:00
left = left + width
month_f = month_f >> 1
2011-05-06 10:39:45 +00:00
end
2010-09-10 03:09:02 +00:00
# Weeks headers
if show_weeks
left = subject_width
2011-05-07 09:50:39 +00:00
height = header_height
2010-09-10 03:09:02 +00:00
if self . date_from . cwday == 1
# self.date_from is monday
week_f = self . date_from
2010-03-13 14:56:49 +00:00
else
2010-09-10 03:09:02 +00:00
# find next monday after self.date_from
week_f = self . date_from + ( 7 - self . date_from . cwday + 1 )
width = ( 7 - self . date_from . cwday + 1 ) * zoom - 1
2011-05-07 09:50:39 +00:00
pdf . SetY ( y_start + header_height )
2010-09-10 03:09:02 +00:00
pdf . SetX ( left )
2011-03-30 07:32:08 +00:00
pdf . RDMCell ( width + 1 , height , " " , " LTR " )
2012-09-11 02:16:03 +00:00
left = left + width + 1
2010-09-10 03:09:02 +00:00
end
while week_f < = self . date_to
width = ( week_f + 6 < = self . date_to ) ? 7 * zoom : ( self . date_to - week_f + 1 ) * zoom
2011-05-07 09:50:39 +00:00
pdf . SetY ( y_start + header_height )
2010-09-10 03:09:02 +00:00
pdf . SetX ( left )
2011-03-30 07:32:08 +00:00
pdf . RDMCell ( width , height , ( width > = 5 ? week_f . cweek . to_s : " " ) , " LTR " , 0 , " C " )
2010-09-10 03:09:02 +00:00
left = left + width
2012-09-11 02:16:03 +00:00
week_f = week_f + 7
2010-09-10 03:09:02 +00:00
end
end
2015-10-03 09:10:27 +00:00
# Day numbers headers
if show_day_num
left = subject_width
height = header_height
day_num = self . date_from
pdf . SetFontStyle ( 'B' , 7 )
2020-05-27 02:25:06 +00:00
( self . date_from .. self . date_to ) . each do | g_date |
2015-10-03 09:10:27 +00:00
width = zoom
pdf . SetY ( y_start + header_height * 2 )
pdf . SetX ( left )
2020-05-27 02:25:06 +00:00
pdf . SetTextColor ( non_working_week_days . include? ( g_date . cwday ) ? 150 : 0 )
2015-10-03 09:10:27 +00:00
pdf . RDMCell ( width , height , day_num . day . to_s , " LTR " , 0 , " C " )
left = left + width
day_num = day_num + 1
end
end
2010-09-10 03:09:02 +00:00
# Days headers
if show_days
left = subject_width
2011-05-07 09:50:39 +00:00
height = header_height
2012-09-11 00:08:16 +00:00
pdf . SetFontStyle ( 'B' , 7 )
2020-05-27 02:25:06 +00:00
( self . date_from .. self . date_to ) . each do | g_date |
2010-09-10 03:09:02 +00:00
width = zoom
2015-10-03 09:10:27 +00:00
pdf . SetY ( y_start + header_height * ( show_day_num ? 3 : 2 ) )
2010-09-10 03:09:02 +00:00
pdf . SetX ( left )
2020-05-27 02:25:06 +00:00
pdf . SetTextColor ( non_working_week_days . include? ( g_date . cwday ) ? 150 : 0 )
pdf . RDMCell ( width , height , day_name ( g_date . cwday ) . first , " LTR " , 0 , " C " )
2010-09-10 03:09:02 +00:00
left = left + width
2010-03-13 14:56:49 +00:00
end
end
2010-09-10 03:09:02 +00:00
pdf . SetY ( y_start )
pdf . SetX ( 15 )
2015-10-03 09:10:27 +00:00
pdf . SetTextColor ( 0 )
2012-09-11 00:08:16 +00:00
pdf . RDMCell ( subject_width + g_width - 15 , headers_height , " " , 1 )
2010-09-10 03:09:02 +00:00
# Tasks
2011-05-07 09:50:39 +00:00
top = headers_height + y_start
2010-12-07 18:40:34 +00:00
options = {
:top = > top ,
:zoom = > zoom ,
:subject_width = > subject_width ,
:g_width = > g_width ,
:indent = > 0 ,
:indent_increment = > 5 ,
2010-12-07 18:53:15 +00:00
:top_increment = > 5 ,
2010-12-07 18:40:34 +00:00
:format = > :pdf ,
:pdf = > pdf
}
render ( options )
2010-09-10 03:09:02 +00:00
pdf . Output
2010-03-13 14:56:49 +00:00
end
2011-05-06 10:39:45 +00:00
2010-09-10 03:09:02 +00:00
private
2011-05-06 10:39:45 +00:00
2010-12-17 12:24:11 +00:00
def coordinates ( start_date , end_date , progress , zoom = nil )
zoom || = @zoom
coords = { }
2020-07-25 08:17:49 +00:00
if start_date && end_date && start_date < = self . date_to && end_date > = self . date_from
2020-08-12 04:21:32 +00:00
if start_date > = self . date_from
2010-12-17 12:24:11 +00:00
coords [ :start ] = start_date - self . date_from
coords [ :bar_start ] = start_date - self . date_from
else
coords [ :bar_start ] = 0
end
2020-08-12 04:21:32 +00:00
if end_date < = self . date_to
2020-03-20 06:19:19 +00:00
coords [ :end ] = end_date - self . date_from + 1
2010-12-17 12:24:11 +00:00
coords [ :bar_end ] = end_date - self . date_from + 1
else
coords [ :bar_end ] = self . date_to - self . date_from + 1
end
if progress
2013-01-20 12:30:40 +00:00
progress_date = calc_progress_date ( start_date , end_date , progress )
2010-12-17 12:24:11 +00:00
if progress_date > self . date_from && progress_date > start_date
if progress_date < self . date_to
2011-03-28 17:35:20 +00:00
coords [ :bar_progress_end ] = progress_date - self . date_from
2010-12-17 12:24:11 +00:00
else
coords [ :bar_progress_end ] = self . date_to - self . date_from + 1
end
end
2018-04-09 13:00:41 +00:00
if progress_date < = User . current . today
late_date = [ User . current . today , end_date ] . min + 1
2010-12-17 12:24:11 +00:00
if late_date > self . date_from && late_date > start_date
if late_date < self . date_to
2018-04-09 13:00:41 +00:00
coords [ :bar_late_end ] = late_date - self . date_from
2010-12-17 12:24:11 +00:00
else
coords [ :bar_late_end ] = self . date_to - self . date_from + 1
end
end
end
end
end
# Transforms dates into pixels witdh
2018-08-01 14:28:39 +00:00
coords . each_key do | key |
2010-12-17 12:24:11 +00:00
coords [ key ] = ( coords [ key ] * zoom ) . floor
end
coords
end
2010-09-10 03:09:02 +00:00
2013-01-20 12:30:40 +00:00
def calc_progress_date ( start_date , end_date , progress )
start_date + ( end_date - start_date + 1 ) * ( progress / 100 . 0 )
end
2019-10-19 13:32:59 +00:00
# Singleton class method is public
class << self
def sort_issues! ( issues )
issues . sort_by! { | issue | sort_issue_logic ( issue ) }
end
2013-05-21 13:42:19 +00:00
2019-10-19 13:32:59 +00:00
def sort_issue_logic ( issue )
2019-11-24 13:18:43 +00:00
julian_date = Date . new
2019-10-19 13:32:59 +00:00
ancesters_start_date = [ ]
current_issue = issue
begin
ancesters_start_date . unshift ( [ current_issue . start_date || julian_date , current_issue . id ] )
current_issue = current_issue . parent
end while ( current_issue )
ancesters_start_date
end
2011-05-06 10:39:45 +00:00
2019-10-19 13:32:59 +00:00
def sort_versions! ( versions )
versions . sort!
end
2013-05-21 13:42:34 +00:00
end
2010-12-07 18:53:15 +00:00
def pdf_new_page? ( options )
if options [ :top ] > 180
options [ :pdf ] . Line ( 15 , options [ :top ] , PDF :: TotalWidth , options [ :top ] )
options [ :pdf ] . AddPage ( " L " )
options [ :top ] = 15
options [ :pdf ] . Line ( 15 , options [ :top ] - 0 . 1 , PDF :: TotalWidth , options [ :top ] - 0 . 1 )
end
end
2011-05-06 10:39:45 +00:00
2014-11-28 13:42:47 +00:00
def html_subject_content ( object )
case object
when Issue
issue = object
2019-03-17 16:36:34 +00:00
css_classes = + ''
2014-11-28 13:42:47 +00:00
css_classes << ' issue-overdue' if issue . overdue?
css_classes << ' issue-behind-schedule' if issue . behind_schedule?
css_classes << ' icon icon-issue' unless Setting . gravatar_enabled? && issue . assigned_to
css_classes << ' issue-closed' if issue . closed?
if issue . start_date && issue . due_before && issue . done_ratio
progress_date = calc_progress_date ( issue . start_date ,
issue . due_before , issue . done_ratio )
css_classes << ' behind-start-date' if progress_date < self . date_from
2024-09-23 01:33:40 +00:00
css_classes << ' over-end-date' if progress_date > self . date_to && issue . done_ratio > 0
2014-11-28 13:42:47 +00:00
end
2019-03-17 16:36:34 +00:00
s = ( + " " ) . html_safe
2024-10-08 21:19:29 +00:00
s << view . sprite_icon ( 'issue' ) . html_safe unless Setting . gravatar_enabled? && issue . assigned_to
2019-05-19 22:27:41 +00:00
s << view . assignee_avatar ( issue . assigned_to , :size = > 13 , :class = > 'icon-gravatar' )
2014-11-28 13:42:47 +00:00
s << view . link_to_issue ( issue ) . html_safe
2020-12-09 14:12:15 +00:00
s << view . content_tag ( :input , nil , :type = > 'checkbox' , :name = > 'ids[]' ,
:value = > issue . id , :style = > 'display:none;' ,
:class = > 'toggle-selection' )
2014-11-28 13:42:47 +00:00
view . content_tag ( :span , s , :class = > css_classes ) . html_safe
when Version
version = object
2019-03-17 16:36:34 +00:00
html_class = + " "
2014-11-28 13:42:47 +00:00
html_class << 'icon icon-package '
html_class << ( version . behind_schedule? ? 'version-behind-schedule' : '' ) << " "
html_class << ( version . overdue? ? 'version-overdue' : '' )
html_class << ' version-closed' unless version . open?
2017-11-29 19:38:44 +00:00
if version . start_date && version . due_date && version . visible_fixed_issues . completed_percent
2014-11-28 13:42:47 +00:00
progress_date = calc_progress_date ( version . start_date ,
2017-11-29 19:38:44 +00:00
version . due_date , version . visible_fixed_issues . completed_percent )
2014-11-28 13:42:47 +00:00
html_class << ' behind-start-date' if progress_date < self . date_from
2024-09-23 01:33:40 +00:00
html_class << ' over-end-date' if progress_date > self . date_to && version . visible_fixed_issues . completed_percent > 0
2014-11-28 13:42:47 +00:00
end
2024-09-06 20:48:16 +00:00
s = ( + " " ) . html_safe
s << view . sprite_icon ( 'package' ) . html_safe
s << view . link_to_version ( version ) . html_safe
2014-11-28 13:42:47 +00:00
view . content_tag ( :span , s , :class = > html_class ) . html_safe
when Project
project = object
2019-03-17 16:36:34 +00:00
html_class = + " "
2014-11-28 13:42:47 +00:00
html_class << 'icon icon-projects '
html_class << ( project . overdue? ? 'project-overdue' : '' )
2024-09-06 20:48:16 +00:00
s = ( + " " ) . html_safe
s << view . sprite_icon ( 'projects' ) . html_safe
s << view . link_to_project ( project ) . html_safe
2014-11-28 13:42:47 +00:00
view . content_tag ( :span , s , :class = > html_class ) . html_safe
end
end
def html_subject ( params , subject , object )
content = html_subject_content ( object ) || subject
2019-03-05 14:52:09 +00:00
tag_options = { }
2014-11-28 13:42:47 +00:00
case object
when Issue
tag_options [ :id ] = " issue- #{ object . id } "
2017-11-25 16:30:23 +00:00
tag_options [ :class ] = " issue-subject hascontextmenu "
2014-11-28 13:42:47 +00:00
tag_options [ :title ] = object . subject
2024-01-03 12:14:04 +00:00
children = object . leaf? ? [ ] : object . children & project_issues ( object . project )
2020-12-09 14:12:15 +00:00
has_children =
children . present? &&
2024-08-20 23:40:58 +00:00
children . collect ( & :fixed_version ) . uniq . intersect? ( [ object . fixed_version ] )
2014-11-28 13:42:47 +00:00
when Version
tag_options [ :id ] = " version- #{ object . id } "
tag_options [ :class ] = " version-name "
2019-03-05 14:52:09 +00:00
has_children = object . fixed_issues . exists?
2014-11-28 13:42:47 +00:00
when Project
tag_options [ :class ] = " project-name "
2019-03-05 14:52:09 +00:00
has_children = object . issues . exists? || object . versions . exists?
end
if object
tag_options [ :data ] = {
:collapse_expand = > {
:top_increment = > params [ :top_increment ] ,
:obj_id = > " #{ object . class } - #{ object . id } " . downcase ,
} ,
2021-07-31 06:21:35 +00:00
:number_of_rows = > number_of_rows ,
2019-03-05 14:52:09 +00:00
}
end
if has_children
2024-10-08 21:19:29 +00:00
content = view . content_tag ( :span , view . sprite_icon ( 'angle-down' ) . html_safe , :class = > 'icon icon-expanded expander' ) + content
2019-03-17 16:36:34 +00:00
tag_options [ :class ] += ' open'
2019-03-05 14:52:09 +00:00
else
if params [ :indent ]
params = params . dup
2024-10-08 21:19:29 +00:00
params [ :indent ] += 18
2019-03-05 14:52:09 +00:00
end
2014-11-28 13:42:47 +00:00
end
2019-03-05 14:52:09 +00:00
style = " position: absolute;top: #{ params [ :top ] } px;left: #{ params [ :indent ] } px; "
2019-03-17 16:36:34 +00:00
style += " width: #{ params [ :subject_width ] - params [ :indent ] } px; " if params [ :subject_width ]
2019-03-05 14:52:09 +00:00
tag_options [ :style ] = style
2014-11-28 13:42:47 +00:00
output = view . content_tag ( :div , content , tag_options )
2010-12-17 15:21:38 +00:00
@subjects << output
output
end
2011-05-06 10:39:45 +00:00
2010-12-17 15:21:38 +00:00
def pdf_subject ( params , subject , options = { } )
2014-11-28 13:42:47 +00:00
pdf_new_page? ( params )
2010-12-17 15:21:38 +00:00
params [ :pdf ] . SetY ( params [ :top ] )
params [ :pdf ] . SetX ( 15 )
char_limit = PDF :: MaxCharactorsForSubject - params [ :indent ]
2012-09-11 00:08:32 +00:00
params [ :pdf ] . RDMCell ( params [ :subject_width ] - 15 , 5 ,
( " " * params [ :indent ] ) +
subject . to_s . sub ( / ^(.{ #{ char_limit } }[^ \ s]* \ s).*$ / , '\1 (...)' ) ,
2019-11-09 17:10:17 +00:00
" LR " )
2010-12-17 15:21:38 +00:00
params [ :pdf ] . SetY ( params [ :top ] )
params [ :pdf ] . SetX ( params [ :subject_width ] )
2011-03-30 07:32:08 +00:00
params [ :pdf ] . RDMCell ( params [ :g_width ] , 5 , " " , " LR " )
2010-12-17 15:21:38 +00:00
end
2011-05-06 10:39:45 +00:00
2010-12-17 15:21:38 +00:00
def image_subject ( params , subject , options = { } )
params [ :image ] . fill ( 'black' )
params [ :image ] . stroke ( 'transparent' )
2019-08-14 02:40:56 +00:00
params [ :image ] . strokewidth ( 1 )
params [ :image ] . draw ( 'text %d,%d %s' % [
2023-09-21 00:35:13 +00:00
params [ :indent ] , params [ :top ] + 2 , magick_text ( subject )
2019-08-14 02:40:56 +00:00
] )
2010-12-17 15:21:38 +00:00
end
2011-05-06 10:39:45 +00:00
2013-01-05 12:28:34 +00:00
def issue_relations ( issue )
rels = { }
if relations [ issue . id ]
relations [ issue . id ] . each do | relation |
( rels [ relation . relation_type ] || = [ ] ) << relation . issue_to_id
end
end
rels
end
2014-11-28 13:42:47 +00:00
def html_task ( params , coords , markers , label , object )
2019-03-17 16:36:34 +00:00
output = + ''
2019-03-05 14:52:09 +00:00
data_options = { }
2021-07-31 06:21:35 +00:00
if object
data_options [ :collapse_expand ] = " #{ object . class } - #{ object . id } " . downcase
data_options [ :number_of_rows ] = number_of_rows
end
2019-10-17 16:45:54 +00:00
css = " task " +
case object
2014-11-28 13:42:47 +00:00
when Project
" project "
when Version
" version "
when Issue
object . leaf? ? 'leaf' : 'parent'
else
" "
end
2010-12-17 12:41:54 +00:00
# Renders the task bar, with progress and late
if coords [ :bar_start ] && coords [ :bar_end ]
2012-09-11 00:57:10 +00:00
width = coords [ :bar_end ] - coords [ :bar_start ] - 2
2019-03-17 16:36:34 +00:00
style = + " "
2012-09-11 01:53:34 +00:00
style << " top: #{ params [ :top ] } px; "
style << " left: #{ coords [ :bar_start ] } px; "
style << " width: #{ width } px; "
2014-11-28 13:42:47 +00:00
html_id = " task-todo-issue- #{ object . id } " if object . is_a? ( Issue )
html_id = " task-todo-version- #{ object . id } " if object . is_a? ( Version )
2013-01-05 12:28:34 +00:00
content_opt = { :style = > style ,
2014-11-28 13:42:47 +00:00
:class = > " #{ css } task_todo " ,
2019-03-05 14:52:09 +00:00
:id = > html_id ,
:data = > { } }
2014-11-28 13:42:47 +00:00
if object . is_a? ( Issue )
rels = issue_relations ( object )
2013-01-05 12:41:24 +00:00
if rels . present?
content_opt [ :data ] = { " rels " = > rels . to_json }
end
2013-01-05 12:28:34 +00:00
end
2019-03-05 14:52:09 +00:00
content_opt [ :data ] . merge! ( data_options )
2013-01-05 12:28:34 +00:00
output << view . content_tag ( :div , ' ' . html_safe , content_opt )
2010-12-17 12:41:54 +00:00
if coords [ :bar_late_end ]
2012-09-11 00:57:10 +00:00
width = coords [ :bar_late_end ] - coords [ :bar_start ] - 2
2019-03-17 16:36:34 +00:00
style = + " "
2012-09-11 01:53:34 +00:00
style << " top: #{ params [ :top ] } px; "
style << " left: #{ coords [ :bar_start ] } px; "
style << " width: #{ width } px; "
2012-09-11 00:57:10 +00:00
output << view . content_tag ( :div , ' ' . html_safe ,
:style = > style ,
2019-03-05 14:52:09 +00:00
:class = > " #{ css } task_late " ,
:data = > data_options )
2010-12-17 12:41:54 +00:00
end
if coords [ :bar_progress_end ]
2012-09-11 00:57:10 +00:00
width = coords [ :bar_progress_end ] - coords [ :bar_start ] - 2
2019-03-17 16:36:34 +00:00
style = + " "
2012-09-11 01:53:34 +00:00
style << " top: #{ params [ :top ] } px; "
style << " left: #{ coords [ :bar_start ] } px; "
style << " width: #{ width } px; "
2014-11-28 13:42:47 +00:00
html_id = " task-done-issue- #{ object . id } " if object . is_a? ( Issue )
html_id = " task-done-version- #{ object . id } " if object . is_a? ( Version )
2012-09-11 00:57:10 +00:00
output << view . content_tag ( :div , ' ' . html_safe ,
:style = > style ,
2014-11-28 13:42:47 +00:00
:class = > " #{ css } task_done " ,
2019-03-05 14:52:09 +00:00
:id = > html_id ,
:data = > data_options )
2010-12-17 12:41:54 +00:00
end
end
2010-12-17 13:40:25 +00:00
# Renders the markers
2014-11-28 14:02:57 +00:00
if markers
2010-12-17 13:40:25 +00:00
if coords [ :start ]
2019-03-17 16:36:34 +00:00
style = + " "
2012-09-11 02:16:18 +00:00
style << " top: #{ params [ :top ] } px; "
style << " left: #{ coords [ :start ] } px; "
style << " width:15px; "
output << view . content_tag ( :div , ' ' . html_safe ,
:style = > style ,
2019-03-05 14:52:09 +00:00
:class = > " #{ css } marker starting " ,
:data = > data_options )
2010-12-17 13:40:25 +00:00
end
if coords [ :end ]
2019-03-17 16:36:34 +00:00
style = + " "
2012-09-11 02:16:18 +00:00
style << " top: #{ params [ :top ] } px; "
2020-03-20 06:19:19 +00:00
style << " left: #{ coords [ :end ] } px; "
2012-09-11 02:16:18 +00:00
style << " width:15px; "
output << view . content_tag ( :div , ' ' . html_safe ,
:style = > style ,
2019-03-05 14:52:09 +00:00
:class = > " #{ css } marker ending " ,
:data = > data_options )
2010-12-17 13:40:25 +00:00
end
end
2010-12-17 12:41:54 +00:00
# Renders the label on the right
2014-11-28 13:42:47 +00:00
if label
2019-03-17 16:36:34 +00:00
style = + " "
2012-09-11 03:44:52 +00:00
style << " top: #{ params [ :top ] } px; "
style << " left: #{ ( coords [ :bar_end ] || 0 ) + 8 } px; "
style << " width:15px; "
2014-11-28 13:42:47 +00:00
output << view . content_tag ( :div , label ,
2012-09-11 03:44:52 +00:00
:style = > style ,
2019-03-05 14:52:09 +00:00
:class = > " #{ css } label " ,
:data = > data_options )
2010-12-17 12:24:11 +00:00
end
2010-12-17 12:41:54 +00:00
# Renders the tooltip
2014-11-28 13:42:47 +00:00
if object . is_a? ( Issue ) && coords [ :bar_start ] && coords [ :bar_end ]
2012-09-11 03:45:06 +00:00
s = view . content_tag ( :span ,
2014-11-28 13:42:47 +00:00
view . render_issue_tooltip ( object ) . html_safe ,
2012-09-11 03:45:06 +00:00
:class = > " tip " )
2020-12-09 14:12:15 +00:00
s += view . content_tag ( :input , nil , :type = > 'checkbox' , :name = > 'ids[]' ,
:value = > object . id , :style = > 'display:none;' ,
:class = > 'toggle-selection' )
2019-03-17 16:36:34 +00:00
style = + " "
2012-09-11 03:45:06 +00:00
style << " position: absolute; "
style << " top: #{ params [ :top ] } px; "
style << " left: #{ coords [ :bar_start ] } px; "
style << " width: #{ coords [ :bar_end ] - coords [ :bar_start ] } px; "
style << " height:12px; "
output << view . content_tag ( :div , s . html_safe ,
:style = > style ,
2019-03-05 14:52:09 +00:00
:class = > " tooltip hascontextmenu " ,
:data = > data_options )
2010-12-17 12:24:11 +00:00
end
2010-12-17 14:59:32 +00:00
@lines << output
2010-12-17 12:24:11 +00:00
output
end
2011-05-06 10:39:45 +00:00
2014-11-28 13:42:47 +00:00
def pdf_task ( params , coords , markers , label , object )
2019-11-24 13:18:43 +00:00
cell_height_ratio = params [ :pdf ] . get_cell_height_ratio
2014-08-28 10:23:46 +00:00
params [ :pdf ] . set_cell_height_ratio ( 0 . 1 )
2014-11-28 13:42:47 +00:00
height = 2
height /= 2 if markers
2010-12-17 14:37:51 +00:00
# Renders the task bar, with progress and late
if coords [ :bar_start ] && coords [ :bar_end ]
2020-03-20 04:48:15 +00:00
width = [ 1 , coords [ :bar_end ] - coords [ :bar_start ] ] . max
2012-09-11 00:08:46 +00:00
params [ :pdf ] . SetY ( params [ :top ] + 1 . 5 )
2010-12-17 14:37:51 +00:00
params [ :pdf ] . SetX ( params [ :subject_width ] + coords [ :bar_start ] )
2012-09-11 00:08:46 +00:00
params [ :pdf ] . SetFillColor ( 200 , 200 , 200 )
2020-03-20 04:48:15 +00:00
params [ :pdf ] . RDMCell ( width , height , " " , 0 , 0 , " " , 1 )
2010-12-17 14:37:51 +00:00
if coords [ :bar_late_end ]
2020-03-20 04:48:15 +00:00
width = [ 1 , coords [ :bar_late_end ] - coords [ :bar_start ] ] . max
2012-09-11 00:08:46 +00:00
params [ :pdf ] . SetY ( params [ :top ] + 1 . 5 )
2010-12-17 14:37:51 +00:00
params [ :pdf ] . SetX ( params [ :subject_width ] + coords [ :bar_start ] )
2012-09-11 00:08:46 +00:00
params [ :pdf ] . SetFillColor ( 255 , 100 , 100 )
2020-03-20 04:48:15 +00:00
params [ :pdf ] . RDMCell ( width , height , " " , 0 , 0 , " " , 1 )
2010-12-17 14:37:51 +00:00
end
if coords [ :bar_progress_end ]
2020-03-20 04:48:15 +00:00
width = [ 1 , coords [ :bar_progress_end ] - coords [ :bar_start ] ] . max
2012-09-11 00:08:46 +00:00
params [ :pdf ] . SetY ( params [ :top ] + 1 . 5 )
2010-12-17 14:37:51 +00:00
params [ :pdf ] . SetX ( params [ :subject_width ] + coords [ :bar_start ] )
2012-09-11 00:08:46 +00:00
params [ :pdf ] . SetFillColor ( 90 , 200 , 90 )
2020-03-20 04:48:15 +00:00
params [ :pdf ] . RDMCell ( width , height , " " , 0 , 0 , " " , 1 )
2010-12-17 14:37:51 +00:00
end
end
# Renders the markers
2014-11-28 13:42:47 +00:00
if markers
2010-12-17 14:37:51 +00:00
if coords [ :start ]
params [ :pdf ] . SetY ( params [ :top ] + 1 )
params [ :pdf ] . SetX ( params [ :subject_width ] + coords [ :start ] - 1 )
2012-09-11 00:08:46 +00:00
params [ :pdf ] . SetFillColor ( 50 , 50 , 200 )
2011-05-06 10:39:45 +00:00
params [ :pdf ] . RDMCell ( 2 , 2 , " " , 0 , 0 , " " , 1 )
2010-12-17 14:37:51 +00:00
end
if coords [ :end ]
params [ :pdf ] . SetY ( params [ :top ] + 1 )
params [ :pdf ] . SetX ( params [ :subject_width ] + coords [ :end ] - 1 )
2012-09-11 00:08:46 +00:00
params [ :pdf ] . SetFillColor ( 50 , 50 , 200 )
2011-05-06 10:39:45 +00:00
params [ :pdf ] . RDMCell ( 2 , 2 , " " , 0 , 0 , " " , 1 )
2010-12-17 14:37:51 +00:00
end
end
# Renders the label on the right
2014-11-28 13:42:47 +00:00
if label
2010-12-17 14:37:51 +00:00
params [ :pdf ] . SetX ( params [ :subject_width ] + ( coords [ :bar_end ] || 0 ) + 5 )
2014-11-28 13:42:47 +00:00
params [ :pdf ] . RDMCell ( 30 , 2 , label )
2010-12-17 14:37:51 +00:00
end
2014-08-28 10:23:46 +00:00
params [ :pdf ] . set_cell_height_ratio ( cell_height_ratio )
2010-12-17 14:37:51 +00:00
end
2010-12-17 14:53:30 +00:00
2014-11-28 13:42:47 +00:00
def image_task ( params , coords , markers , label , object )
height = 6
height /= 2 if markers
2010-12-17 14:53:30 +00:00
# Renders the task bar, with progress and late
if coords [ :bar_start ] && coords [ :bar_end ]
2010-12-30 15:04:08 +00:00
params [ :image ] . fill ( '#aaa' )
2019-08-14 02:40:56 +00:00
params [ :image ] . draw ( 'rectangle %d,%d %d,%d' % [
params [ :subject_width ] + coords [ :bar_start ] ,
params [ :top ] ,
params [ :subject_width ] + coords [ :bar_end ] ,
params [ :top ] - height
] )
2010-12-17 14:53:30 +00:00
if coords [ :bar_late_end ]
2010-12-30 15:04:08 +00:00
params [ :image ] . fill ( '#f66' )
2019-08-14 02:40:56 +00:00
params [ :image ] . draw ( 'rectangle %d,%d %d,%d' % [
params [ :subject_width ] + coords [ :bar_start ] ,
params [ :top ] ,
params [ :subject_width ] + coords [ :bar_late_end ] ,
params [ :top ] - height
] )
2010-12-17 14:53:30 +00:00
end
if coords [ :bar_progress_end ]
2010-12-30 15:04:08 +00:00
params [ :image ] . fill ( '#00c600' )
2019-08-14 02:40:56 +00:00
params [ :image ] . draw ( 'rectangle %d,%d %d,%d' % [
params [ :subject_width ] + coords [ :bar_start ] ,
params [ :top ] ,
params [ :subject_width ] + coords [ :bar_progress_end ] ,
params [ :top ] - height
] )
2010-12-17 14:53:30 +00:00
end
end
# Renders the markers
2014-11-28 13:42:47 +00:00
if markers
2010-12-17 14:53:30 +00:00
if coords [ :start ]
2010-12-30 15:04:08 +00:00
x = params [ :subject_width ] + coords [ :start ]
y = params [ :top ] - height / 2
2010-12-17 14:53:30 +00:00
params [ :image ] . fill ( 'blue' )
2019-08-14 02:40:56 +00:00
params [ :image ] . draw ( 'polygon %d,%d %d,%d %d,%d %d,%d' % [
x - 4 , y ,
x , y - 4 ,
x + 4 , y ,
x , y + 4
] )
2010-12-17 14:53:30 +00:00
end
if coords [ :end ]
2020-03-20 06:19:19 +00:00
x = params [ :subject_width ] + coords [ :end ]
2010-12-30 15:04:08 +00:00
y = params [ :top ] - height / 2
2010-12-17 14:53:30 +00:00
params [ :image ] . fill ( 'blue' )
2019-08-14 02:40:56 +00:00
params [ :image ] . draw ( 'polygon %d,%d %d,%d %d,%d %d,%d' % [
x - 4 , y ,
x , y - 4 ,
x + 4 , y ,
x , y + 4
] )
2010-12-17 14:53:30 +00:00
end
end
# Renders the label on the right
2014-11-28 13:42:47 +00:00
if label
2010-12-17 14:53:30 +00:00
params [ :image ] . fill ( 'black' )
2019-08-14 02:40:56 +00:00
params [ :image ] . draw ( 'text %d,%d %s' % [
2020-12-09 14:12:15 +00:00
params [ :subject_width ] + ( coords [ :bar_end ] || 0 ) + 5 ,
params [ :top ] + 1 ,
2023-09-21 00:35:13 +00:00
magick_text ( label )
2019-08-14 02:40:56 +00:00
] )
2010-12-17 14:53:30 +00:00
end
end
2023-09-21 00:35:13 +00:00
# Escape the passed string as a text argument in a draw rule for
# mini_magick. Note that the returned string is not shell-safe on its own.
def magick_text ( str )
" ' #{ str . to_s . gsub ( / [' \\ ] / , '\\\\\0' ) } ' "
end
2008-09-10 18:26:13 +00:00
end
end
end