diff --git a/trunk/app/controllers/gantts_controller.rb b/trunk/app/controllers/gantts_controller.rb
--- a/trunk/app/controllers/gantts_controller.rb
+++ b/trunk/app/controllers/gantts_controller.rb
@@ -34,7 +34,6 @@
     @gantt = Redmine::Helpers::Gantt.new(params)
     @gantt.project = @project
     retrieve_query
-    @query.group_by = nil
     @gantt.query = @query if @query.valid?
 
     basename = (@project ? "#{@project.identifier}-" : '') + 'gantt'
diff --git a/trunk/app/views/gantts/show.html.erb b/trunk/app/views/gantts/show.html.erb
--- a/trunk/app/views/gantts/show.html.erb
+++ b/trunk/app/views/gantts/show.html.erb
@@ -1,17 +1,41 @@
 <% @gantt.view = self %>
 <h2><%= @query.new_record? ? l(:label_gantt) : h(@query.name) %></h2>
 
+<% if @query.valid? %>
+  <%- @gantt.setup_dates %>
+<% end %>
+
 <%= form_tag({:controller => 'gantts', :action => 'show',
              :project_id => @project, :month => params[:month],
-             :year => params[:year], :months => params[:months]},
+             :year => params[:year], :months => params[:months],
+             :group_filter => params[:group_filter]},
              :method => :get, :id => 'query_form') do %>
 <%= hidden_field_tag 'set_filter', '1' %>
 <fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
   <legend onclick="toggleFieldset(this);"><%= l(:label_filter_plural) %></legend>
   <div style="<%= @query.new_record? ? "" : "display: none;" %>">
     <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
+    <table>
+      <tr>
+        <td class="field"><label for='group_by'><%= l(:field_group_by) %></label></td>
+        <td><%= select_tag('group_by', options_for_select(
+                  [[]] + @query.groupable_columns.collect {|c| [c.caption, c.name.to_s]},
+                  @query.group_by)) %></td>
+      </tr>
+    </table>
   </div>
 </fieldset>
+<fieldset class="collapsible collapsed" style="display: none;">
+  <legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend>
+  <div style="display: none;">
+	<table>
+	  <tr>
+	    <td><%= l(:field_column_names) %></td>
+	    <td><%= render :partial => 'queries/columns', :locals => {:query => @query} %></td>
+	  </tr>
+	</table>
+  </div>
+</fieldset>
 
 <p class="contextual">
   <%= gantt_zoom_link(@gantt, :in) %>
@@ -29,6 +53,11 @@
                      :class => 'icon icon-checked' %>
 <%= link_to l(:button_clear), { :project_id => @project, :set_filter => 1 },
             :class => 'icon icon-reload' %>
+<% if @query.new_record? && User.current.allowed_to?(:save_queries, @project, :global => true) %>
+	<%= link_to_function l(:button_save),
+		             "$('query_form').action='#{ @project ? new_project_query_path(@project) : new_query_path }'; submit_query_form('query_form')",
+                     :class => 'icon icon-save' %>
+<% end %>
 </p>
 <% end %>
 
diff --git a/trunk/lib/redmine/helpers/gantt.rb b/trunk/lib/redmine/helpers/gantt.rb
--- a/trunk/lib/redmine/helpers/gantt.rb
+++ b/trunk/lib/redmine/helpers/gantt.rb
@@ -42,27 +42,42 @@
 
       def initialize(options={})
         options = options.dup
-        if options[:year] && options[:year].to_i >0
+
+        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
+          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
-          @month_from ||= Date.today.month
+          @month_from ||= Date.today.month - 1
           @year_from ||= Date.today.year
+          @calculate_date_range = true
         end
+
+        if @month_from <= 0
+          @month_from = 12
+          @year_from -= 1
+        end
+        if @month_from > 12
+          @month_from = 1
+          @year_from += 1
+        end
+        
+        months = (options[:months] || User.current.pref[:gantt_months]).to_i
+        @months = (months > 0 && months < 48) ? months : 48
+        
         zoom = (options[:zoom] || User.current.pref[:gantt_zoom]).to_i
         @zoom = (zoom > 0 && zoom < 5) ? zoom : 2
-        months = (options[:months] || User.current.pref[:gantt_months]).to_i
-        @months = (months > 0 && months < 25) ? months : 6
+
         # Save gantt parameters as user preference (zoom and months count)
         if (User.current.logged? && (@zoom != User.current.pref[:gantt_zoom] ||
               @months != User.current.pref[:gantt_months]))
           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
         @subjects = ''
@@ -70,6 +85,7 @@
         @number_of_rows = nil
         @issue_ancestors = []
         @truncated = false
+
         if options.has_key?(:max_rows)
           @max_rows = options[:max_rows]
         else
@@ -77,6 +93,27 @@
         end
       end
 
+      def setup_dates
+        if @calculate_date_range
+          dates = issues_date_range
+
+          @date_from = dates[0]
+          if @date_from.day < 10
+            @date_from = dates[0] << 1
+          end
+          @date_to = dates[1]
+          if @date_to.day > 20
+            @date_to = dates[1] >> 1
+          end
+          @year_from = @date_from.year
+          @month_from = @date_from.month
+          months = (@date_to.year*12+@date_to.month) - (@date_from.year*12+@date_from.month) + 1
+          @months = months > 48 ? 48 : months
+          @date_from = Date.civil(@year_from, @month_from, 1)
+          @date_to = (@date_from >> @months) - 1
+        end
+      end
+
       def common_params
         { :controller => 'gantts', :action => 'show', :project_id => @project }
       end
@@ -209,14 +246,70 @@
 
       def render_issues(issues, options={})
         @issue_ancestors = []
-        issues.each do |i|
-          subject_for_issue(i, options) unless options[:only] == :lines
-          line_for_issue(i, options) unless options[:only] == :subjects
-          options[:top] += options[:top_increment]
-          @number_of_rows += 1
-          break if abort?
+        if @query.group_by == nil || @query.group_by == ""
+          issues.each do |i|
+            subject_for_issue(i, options) unless options[:only] == :lines
+            line_for_issue(i, options) unless options[:only] == :subjects
+            options[:top] += options[:top_increment]
+            @number_of_rows += 1
+            break if abort?
+          end
+          options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
+        else
+          new_group = true
+          group_start = options[:top] - options[:top_increment]
+          group_max_line = 0
+          group_write_line = group_max_line
+          dates_in_line = [[]]
+          last_shown_index = -1
+
+          issues.each_with_index do |i, index|
+            if i.start_date != nil
+              if i.due_before == nil
+                i.due_date = i.start_date
+              end
+
+              if ((i.start_date >= @date_from || i.due_before >= @date_from) && i.start_date <= @date_to)
+                new_group = last_shown_index < 0 || new_group?(i, issues[last_shown_index]) || !i.leaf? || !issues[last_shown_index].leaf?
+
+                if new_group
+                  options[:top] = group_start + ((group_max_line+1) * options[:top_increment])
+                  group_start = options[:top]
+                  group_max_line = 0
+                  group_write_line = group_max_line
+                  dates_in_line = [[]]
+                  @number_of_rows += 1
+                else
+                  group_write_line = -1
+                  dates_in_line.each_with_index do |dates, line|
+                    if dates.find {|e| (i.start_date >= e[0] && i.start_date <= e[1]) || (i.start_date <= e[0] && i.due_before >= e[1]) } == nil
+                      group_write_line = line
+                      break
+                    end
+                  end
+                  if group_write_line == -1
+                    group_max_line += 1
+                    group_write_line = group_max_line
+                    dates_in_line.push([])
+                    @number_of_rows += 1
+                  end
+                end
+
+                options[:top] = group_start + (group_write_line * options[:top_increment])
+                options[:no_title] = !new_group;
+
+                subject_for_issue(i, options) unless options[:only] == :lines
+                line_for_issue(i, options) unless options[:only] == :subjects
+
+                last_shown_index = index
+                dates_in_line[group_write_line].push([i.start_date, i.due_before])
+              end
+            end
+            break if abort?
+          end
+          options[:top] = group_start + ((group_max_line+1) * options[:top_increment])
+          options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
         end
-        options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
       end
 
       def render_version(project, version, options={})
@@ -330,42 +423,74 @@
           @issue_ancestors.pop
           options[:indent] -= options[:indent_increment]
         end
+        
+        is_not_leaf_or_grouped = !issue.leaf? || @query.group_by == nil || @query.group_by == ""
+        group_name = group_name(issue)
+
+        if is_not_leaf_or_grouped
+          subject_title = issue.subject
+        else
+          subject_title = group_name
+        end
+
+        if options[:no_title]
+          subject_title = " "
+        end
+        
         output = case options[:format]
         when :html
           css_classes = ''
           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 << ' icon icon-issue' unless Setting.gravatar_enabled? && issue.assigned_to || options[:no_title]
+          
           s = "".html_safe
-          if issue.assigned_to.present?
+          if !options[:no_title] && issue.assigned_to.present?
             assigned_string = l(:field_assigned_to) + ": " + issue.assigned_to.name
             s << view.avatar(issue.assigned_to,
                              :class => 'gravatar icon-gravatar',
                              :size => 10,
                              :title => assigned_string).to_s.html_safe
           end
-          s << view.link_to_issue(issue).html_safe
+          
+          if is_not_leaf_or_grouped
+            s << view.link_to_issue(issue).html_safe
+          else
+            s << subject_title.html_safe
+          end
+          
           subject = view.content_tag(:span, s, :class => css_classes).html_safe
           html_subject(options, subject, :css => "issue-subject",
-                       :title => issue.subject) + "\n"
+                       :title => subject_title) + "\n"
         when :image
-          image_subject(options, issue.subject)
+          image_subject(options, subject_title)
         when :pdf
           pdf_new_page?(options)
-          pdf_subject(options, issue.subject)
+          pdf_subject(options, subject_title)
         end
+        
         unless issue.leaf?
           @issue_ancestors << issue
           options[:indent] += options[:indent_increment]
         end
+        
         output
       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
-          coords = coordinates(issue.start_date, issue.due_before, issue.done_ratio, options[:zoom])
-          label = "#{issue.status.name} #{issue.done_ratio}%"
+        if issue.is_a?(Issue)
+          due_date = issue.due_before
+          if !due_date
+            due_date = issue.start_date
+          end
+
+          coords = coordinates(issue.start_date, due_date, issue.done_ratio, options[:zoom])
+          
+          label = " "
+          if @query.group_by == nil || @query.group_by == ""
+            label = "#{issue.status.name} #{issue.done_ratio}%"
+          end
+
           case options[:format]
           when :html
             html_task(options, coords,
@@ -376,9 +501,9 @@
             image_task(options, coords, :label => label)
           when :pdf
             pdf_task(options, coords, :label => label)
-        end
+          end
         else
-          ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue with a due_before"
+          ActiveRecord::Base.logger.debug "GanttHelper#line_for_issue was not given an issue"
           ''
         end
       end
@@ -640,18 +765,93 @@
 
       # Sorts a collection of issues by start_date, due_date, id for gantt rendering
       def sort_issues!(issues)
-        issues.sort! { |a, b| gantt_issue_compare(a, b) }
+        issues.sort! { |a, b| gantt_issue_compare(a, b, issues) }
       end
 
-      # TODO: top level issues should be sorted by start date
-      def gantt_issue_compare(x, y)
-        if x.root_id == y.root_id
-          x.lft <=> y.lft
+      def gantt_issue_compare(x, y, issues)
+        if @query.group_by == nil || @query.group_by == ""
+          [(x.root.start_date or x.start_date or Date.new()), x.root_id, (x.start_date or Date.new()), x.lft] <=> [(y.root.start_date or y.start_date or Date.new()), y.root_id, (y.start_date or Date.new()), y.lft]
+        elsif x.leaf? && !y.leaf?
+          1
+        elsif !x.leaf? && y.leaf?
+          -1
         else
-          x.root_id <=> y.root_id
+          sort_group(x, y, issues)
         end
       end
+      
+      def sort_group(x, y, issues)
+        value_x = @query.group_by_column.value(x)
+        value_y = @query.group_by_column.value(y)
 
+        if value_x == nil && value_y == nil
+          [(x.root.start_date or x.start_date or Date.new()), x.root_id, (x.start_date or Date.new()), x.lft] <=> [(y.root.start_date or y.start_date or Date.new()), y.root_id, (y.start_date or Date.new()), y.lft]
+        elsif value_x == nil
+          -1
+        elsif value_y == nil
+          1
+        elsif value_x == value_y
+          [(x.root.start_date or x.start_date or Date.new()), x.root_id, (x.start_date or Date.new()), x.lft] <=> [(y.root.start_date or y.start_date or Date.new()), y.root_id, (y.start_date or Date.new()), y.lft]
+        else
+          value_x <=> value_y
+        end
+      end
+      
+      def issues_date_range
+        min = nil
+        max = nil
+        projects.each do |p|
+          all_issues = project_issues(p)
+
+          all_issues.each do |i|
+            if min == nil || (i.start_date != nil && min > i.start_date)
+              min = i.start_date
+            end
+            if min == nil || (i.due_before != nil && min > i.due_before)
+              min = i.due_before
+            end
+            if max == nil || (i.start_date != nil && max < i.start_date)
+              max = i.start_date
+            end
+            if max == nil || (i.due_before != nil && max < i.due_before)
+              max = i.due_before
+            end
+          end
+        end
+
+        if min == nil && max == nil
+          min = Date.today
+          max = Date.today
+        end
+
+        [min, max]
+      end
+      
+      def new_group?(x, y)
+        if @query.group_by == nil || @query.group_by == ""
+          true
+        else
+          value_x = @query.group_by_column.value(x)
+          value_y = @query.group_by_column.value(y)
+
+          value_x != value_y
+        end
+      end
+
+      def group_name(issue)
+        if @query.group_by == nil || @query.group_by == ""
+          issue.subject
+        else
+          result = @query.group_by_column.value(issue)
+
+          if result == nil || result == ''
+            'None'
+          else
+            result.to_s
+          end
+        end
+      end
+
       def current_limit
         if @max_rows
           @max_rows - @number_of_rows
@@ -790,25 +990,44 @@
 
       def pdf_task(params, coords, options={})
         height = options[:height] || 2
+        
         # Renders the task bar, with progress and late
         if coords[:bar_start] && coords[:bar_end]
-          params[:pdf].SetY(params[:top] + 1.5)
-          params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
+          top = params[:top] + 1.5
+          width = params[:subject_width] + coords[:bar_start]
+          
+          length = coords[:bar_end] - coords[:bar_start]
+          if length <= 0
+            length = 1
+          end
+          params[:pdf].SetY(top)
+          params[:pdf].SetX(width)
           params[:pdf].SetFillColor(200, 200, 200)
-          params[:pdf].RDMCell(coords[:bar_end] - coords[:bar_start], height, "", 0, 0, "", 1)
+          params[:pdf].RDMCell(length, height, "", 0, 0, "", 1)
+          
           if coords[:bar_late_end]
-            params[:pdf].SetY(params[:top] + 1.5)
-            params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
+            length = coords[:bar_late_end] - coords[:bar_start]
+            if length <= 0
+              length = 1
+            end
+            params[:pdf].SetY(top)
+            params[:pdf].SetX(width)
             params[:pdf].SetFillColor(255, 100, 100)
-            params[:pdf].RDMCell(coords[:bar_late_end] - coords[:bar_start], height, "", 0, 0, "", 1)
+            params[:pdf].RDMCell(length, height, "", 0, 0, "", 1)
           end
+          
           if coords[:bar_progress_end]
-            params[:pdf].SetY(params[:top] + 1.5)
-            params[:pdf].SetX(params[:subject_width] + coords[:bar_start])
+            length = coords[:bar_progress_end] - coords[:bar_start]
+            if length <= 0
+              length = 1
+            end
+            params[:pdf].SetY(top)
+            params[:pdf].SetX(width)
             params[:pdf].SetFillColor(90, 200, 90)
-            params[:pdf].RDMCell(coords[:bar_progress_end] - coords[:bar_start], height, "", 0, 0, "", 1)
+            params[:pdf].RDMCell(length, height, "", 0, 0, "", 1)
           end
         end
+        
         # Renders the markers
         if options[:markers]
           if coords[:start]
@@ -824,6 +1043,7 @@
             params[:pdf].RDMCell(2, 2, "", 0, 0, "", 1)
           end
         end
+        
         # Renders the label on the right
         if options[:label]
           params[:pdf].SetX(params[:subject_width] + (coords[:bar_end] || 0) + 5)
