2012年9月2日日曜日

【Redmine】ガントチャートpdf出力拡張 vol.1.5 進捗度の描画ずれ修正

目的
前回、ガントチャートのpdf出力表示にて、
日付毎に縦線を引いたが、
バーのゲージや、端、マイルストンの位置がずれていた。
これを修正する。


作業手順
引き続き、
apps\redmine\backup\lib\redmine\helpers\gantt.rbを解析。

各項目毎の進捗は、
line_for_xxx(xxxはワイルドカード)メソッドにて行われているのは前回で分かった。
line_for_xxxメソッドの内部を見てみる。

coordinatesメソッドで座標を求め、
pdf_taskメソッドで描画を行っているようだ。

coordinatesメソッド内の最後の方にある、
以下の処理にて、floorメソッドを使用している。
coords.keys.each do |key|
  coords[key] = (coords[key] * zoom).floor
end
coords[key]もしくは、zoomが小数で、
かつpdf出力時は小数を許容する場合、
誤差が生じることになる。


まずは、
coords[key]もしくはzoomが小数かどうか調査する。

xの値を調べる場合、
ActiveRecord::Base.logger.info x
と記述することで、ログファイル(apps\redmine\htdocs\log内)に出力される。

coords[key]及び、zoomの値を出力してみたところ、
zoomの値は小数であった。(正確にはRational。)


次に、
pdf出力時は小数を許容するのか調査する。

描画する際に、gantt.rbでは、
pdf_taskメソッド内でRDMCellメソッドを呼び出している。

RDMCellメソッドは、
pdfオブジェクトを使用しているが、
これはto_pdfメソッド内の以下の処理にて生成されている。
pdf = ::Redmine::Export::PDF::ITCPDF.new(current_language)
ITCPDFクラスは、
apps\redmine\htdocs\lib\redmine\export\pdf.rbに記述されている。
RDMCellメソッドは、ITCPDFクラスのインスタンスメソッドであり、
Cellメソッドを呼び出しているだけである。
CellメソッドはITCPDFの親クラスである
TCPDFクラスのインスタンスメソッドである。

TCPDFクラスは、
apps\redmine\htdocs\lib\plugins\rfpdf\lib\tcpdf.rbに記述されている。
Cellメソッドのコメントを見ると、
第一、第二引数ともにfloatになっている。


これらからの調査結果から、
floorメソッドの呼び出しが原因である可能性が高い。
よって、pdf出力時にはfloorメソッドを呼ばないように変更をかける。


まずは、coordinatesメソッド内の最後にある、
floorメソッド呼び出し部分を以下のように変更する。
coords.keys.each do |key|
  coords[key] = coords[key] * zoom
  coords[key] = coords[key].floor unless format == :pdf
end
次に、coordinatesメソッドの引数にformatを追加する。
def coordinates(start_date, end_date, progress, zoom=nil, format=nil)
最後に、全てのcoordinatesメソッドの呼び出しに、
options[:format]を追加する。

以上で、作業完了ある。


変更後のソースコードは以下にアップされている。
『進捗度の描画ずれ修正 』のフォルダにある。
変更前のソースコードは『日付表示を見やすく 』のフォルダにある、
変更後のソースコードを参照してもらいたい。

ソースコード




結果
修正前:


修正後:


ずれはなくなっているようだ。

2012年8月19日日曜日

【Redmine】ガントチャートpdf出力拡張 vol.1 日付表示を見やすく

目的
ガントチャートpdf出力表示にて、
日付単位の表示が曜日になっているのを日付に変更、
日付毎に縦線を引き、
土日の文字色、背景色を変更する。
また、項目毎に横線を引く。


作業手順
app\controllers\gantts_controller.rbを見ると、
45行目に、format.pdf ...とあり、
ヘルパーであるganttのto_pdfメソッドを呼び出している。
これがpdfファイル作成処理のようだ。

apps\redmine\htdocs\lib\redmine\helper\gantt.rbを見ると、
to_pdfメソッドがあるので解析。
to_pdfメソッド内では、月単位、週単位、日付単位のヘッダを描画、
renderメソッドにて、
以降のチケットの進捗度部分を作成していることが分かった。

まずは、to_pdf内を変更し、
日付単位の曜日表示を日付表示に変更する。

587行目、
wday = self.date_from.cwday
にて、表示曜日の初期値設定を行っているが。
次の行に表示日付の初期値設定を追加する。
date = self.date_from
 
次に、(上記変更により)594行目、
pdf.RDMCell(width, height, day_name(wday).first, "LTR", 0, "C")
にて、曜日名を表示しているが、日付に変更するため、
pdf.RDMCell(width, height, date.day.to_s, "LTR", 0, "C")
に変更。


次に、(上記変更により)596、597行目、
wday = wday + 1
wday = 1 if wday > 7
にて、表示曜日の更新処理を行っているが、 
その次の行に、表示日付の更新処理を追加する。
date = date + 1
これで曜日から日付への変更が完了。


次に、土日の文字色の変更。
(上記変更により)591行目の下に、
以下の処理を追加。
case(wday)
when 6
  pdf.SetTextColor(0, 0, 255)
when 7
  pdf.SetTextColor(255, 0, 0)
else
  pdf.SetTextColor(0, 0, 0)
end

繰り返しを抜けた箇所に、
文字色を戻すため、以下を追加して完了。

pdf.SetTextColor(0, 0, 0)



次に、縦線を引く。
そのためには、
各プロジェクト、バージョン、イシュー毎の
描画描画処理を行っている箇所を解析する必要がある。
renderメソッドを見ていく。

renderメソッドでは、プロジェクトツリーに従い、
各プロジェクト毎にrender_projectメソッドを呼び出している。

render_projectメソッドでは、
まず、subject_for_projectメソッド、line_for_projectメソッドを呼び出し、
プロジェクトの項目名、 進捗度の描画を行っている。

次にバージョンに含まれないイシューの描画のため、
render_issuesメソッド内で各イシュー毎に
subject_for_issueメソッド、line_for_issueメソッドを呼び出し、
イシューの項目名、進捗度の描画を行っている。

次にバージョンのため、
render_versionメソッド内で各バージョン毎に
subject_for_versionメソッド、line_for_versionメソッドを呼び出し、
バージョンの項目名、ラインの描画を行ってい、
バージョンに含まれるイシュー毎に、
render_issuesメソッドを呼び出している。

line_for_xxxメソッド(xxxはワイルドカード)にて、
ヘッダー描画時の日付毎の縦線描画処理を入れることで
容易に対処できそうなので、
line_for_xxxメソッド内に処理を追加する。

しかし、subject_for_xxxメソッドでは、
各項目の外枠を描画しているようなので、
後に回すことで日付毎の線を色を変えて引く際に
端かどうか判定してする必要も無くなる。

ということで、
まずは、line_for_xxxメソッド内に当該処理を追加する。
対象となるメソッドはline_for_projects、line_for_version、line_for_issueである。
各メソッドの先頭に処理を追加する。
追加処理はpdf出力時しか使用しないので、
条件式で括っておく。 必要な値は全てoptions内に含まれるため、
引数はoptionsとし、以下の処理を追加する。(※line_for_projectの場合。)
if project.is_a?(Project) && options[:format] == :pdf
  render_day_separator(options)
end

project.is_a?(Project)は以降の処理に含まれているものであるため、
とりあえず追加している。
この処理はline_for_xxxメソッドによって変更する必要がある。

次に、render_day_separator内で、
日付表示だった場合に描画を行うため、
optionsに日付表示フラグを追加する必要がある。
そのため、to_pdfメソッドでoptionsにshow_daysを追加する。
617行目、
options = {
  :top => top,
  :zoom => zoom,
  :subject_width => subject_width,
  :g_width => g_width,
  :indent => 0,
  :indent_increment => 5,
  :top_increment => 5,
  :format => :pdf,
  :pdf => pdf
}
 に、:show_days => show_daysを追加。
options = {
  :top => top,
  :zoom => zoom,
  :subject_width => subject_width,
  :g_width => g_width,
  :show_days => show_days,
  :indent => 0,
  :indent_increment => 5,
  :top_increment => 5,
  :format => :pdf,
  :pdf => pdf
}

render_day_separatorメソッドに関しては、
少し長いのでソースコードを参照してほしい。
こちらに背景色の変更処理も含まれている。
コメントで土日の背景色設定と書かれている処理がそれにあたる。



次に、subject_for_xxxメソッドとline_for_xxxメソッドの処理順を入れ替える。
その際に、subject_for_xxxメソッド内にある、
pdf_new_page?メソッドをline_for_xxxメソッド内に移動する必要がある。
作業としては、上記追加箇所に追加するだけで良い。

if project.is_a?(Project) && options[:format] == :pdf
  pdf_new_page?(options)
  render_day_separator(options)
end
長かったので、ソースコードを見て、
各自確認をしてもらえればと思う。


変更前、変更後のソースコードは以下にアップされている。
『日付表示を見やすく 』のフォルダにある。

ソースコード



結果
大分見やすくなったはず。
ただ、バーのゲージや端、マイルストンの位置がずれているように見える。
次回、原因を調査する。









2012年8月12日日曜日

【Redmine】チケットの題名を正規表現でフィルタリング

目的
チケットの題名にて、
プレフィックスとして、□や、■など、意味を持たせた場合に、
□及び、■を含むチケットのフィルタリングを行いたい。
現状題名の操作にある、『含む』では、
正規表現での□.*というフィルタリングしか行えないため、
汎用性を持たせるため、
題名の操作に正規表現を追加し、
正規表現による題名のフィルタリングを行えるようにする。


作業手順
Ruby on Rails自体、詳細に把握しているわけではないので、
出力されたhtmlからボトムアップ的に調査する。

チケット一覧ページのhtmlには、
<legend onclick="toggleFieldset(this);">フィルタ</legend>
とある。 




まずは、issuesによるページに含まれるので、
app/models/index.html.erbを見る。
<legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
という行があり、config\locales\ja.ymlにて、
label_filter_pluralをみるとフィルタと関連付けられているため、

ここが当該箇所だと断定。
 
その下の行を見てみると、
div内でrenderで'queries/filters'を呼んでいる箇所がある。

 
app\views\queries\_filters.html.erbを見ると、
query.available_filtersに含まれるフィルタ分、GUIを追加している。
題名のフィルタ操作に正規表現を追加したいので、
題名のタイプを見るとテキストになっているため、
テキストの操作に正規表現を追加してやればよい。
そのため、@@operatorsに、
"r"   => :label_regexp
を追加、
@@operators_by_filter_typeの:textを
:text => [  "~", "!~", "!*", "*", "r" ]
に変更。

:label_regexpのため、config\locales\ja.ymlに、
label_regexp: 正規表現
を追加。

最終的にSQL文が発行されるので、
sql_for_field関数に以下を追加。
when "r"
  sql = "LOWER(#{db_table}.#{db_field}) REGEXP '#{connection.quote_string(value.first.to_s.downcase)}'"

変更前、変更後のソースコードは以下にアップされている。
『チケットの題名を正規表現でフィルタリング』のフォルダにある。

ソースコード


結果
無事追加されている模様。


動作に関しても、それっぽく動作している。






と思いきや、REGEXPがマルチバイト対応されていないために、
日本語が含まれている場合に不具合が発生する。
後ほど、マルチバイト対応も記す。