diff --git a/app/controllers/gantts_controller.rb b/app/controllers/gantts_controller.rb
--- a/app/controllers/gantts_controller.rb
+++ b/app/controllers/gantts_controller.rb
@@ -34,7 +34,6 @@ class GanttsController < ApplicationController
     @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/app/views/gantts/show.html.erb b/app/views/gantts/show.html.erb
--- a/app/views/gantts/show.html.erb
+++ b/app/views/gantts/show.html.erb
@@ -7,7 +7,9 @@
 
 <%= form_tag({:controller => 'gantts', :action => 'show',
              :project_id => @project, :month => params[:month],
-             :year => params[:year], :months => params[:months]},
+             :year => params[:year], :months => params[:months],
+             :not_detailed_groups => params[:not_detailed_groups],
+             :version_groups => params[:version_groups]},
              :method => :get, :id => 'query_form') do %>
 <%= hidden_field_tag 'set_filter', '1' %>
 <fieldset id="filters" class="collapsible <%= @query.new_record? ? "" : "collapsed" %>">
@@ -16,6 +18,40 @@
     <%= render :partial => 'queries/filters', :locals => {:query => @query} %>
   </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>
+<fieldset class="collapsible collapsed">
+  <legend onclick="toggleFieldset(this);"><%= l(:label_options) %></legend>
+  <div style="display: none;">
+    <table>
+      <tr>
+        <td><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>
+      <tr>
+        <td><%= l(:button_show) %></td>
+        <td><%= content_tag('label', check_box_tag('not_detailed_groups', true, !@gantt.detailed_groups) + l(:label_hide_group_details), :class => 'inline') %></td>
+      </tr>
+      <tr>
+        <td></td>
+        <td><%= content_tag('label', check_box_tag('version_groups', true, @gantt.version_groups) + l(:label_keep_version_groups), :class => 'inline') %></td>
+      </tr>
+    </table>
+  </div>
+</fieldset>
 
 <p class="contextual">
   <%= gantt_zoom_link(@gantt, :in) %>
@@ -33,6 +69,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/config/locales/de.yml b/config/locales/de.yml
--- a/config/locales/de.yml
+++ b/config/locales/de.yml
@@ -611,6 +611,8 @@ de:
   label_module_plural: Module
   label_month: Monat
   label_months_from: Monate ab
+  label_hide_group_details: Verstecke Gruppendetails
+  label_keep_version_groups: Behalte Versiongruppen
   label_more: Mehr
   label_more_than_ago: vor mehr als
   label_my_account: Mein Konto
diff --git a/config/locales/en-GB.yml b/config/locales/en-GB.yml
--- a/config/locales/en-GB.yml
+++ b/config/locales/en-GB.yml
@@ -587,6 +587,8 @@ en-GB:
   label_per_page: Per page
   label_calendar: Calendar
   label_months_from: months from
+  label_hide_group_details: Hide group details
+  label_keep_version_groups: Keep version groups
   label_gantt: Gantt
   label_internal: Internal
   label_last_changes: "last %{count} changes"
diff --git a/config/locales/en.yml b/config/locales/en.yml
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -627,6 +627,8 @@ en:
   label_per_page: Per page
   label_calendar: Calendar
   label_months_from: months from
+  label_hide_group_details: Hide group details
+  label_keep_version_groups: Keep version groups
   label_gantt: Gantt
   label_internal: Internal
   label_last_changes: "last %{count} changes"
diff --git a/lib/redmine/helpers/gantt.rb b/lib/redmine/helpers/gantt.rb
--- a/lib/redmine/helpers/gantt.rb
+++ b/lib/redmine/helpers/gantt.rb
@@ -36,6 +36,7 @@ module Redmine
       end
 
       attr_reader :year_from, :month_from, :date_from, :date_to, :zoom, :months, :truncated, :max_rows
+      attr_reader :detailed_groups, :version_groups
       attr_accessor :query
       attr_accessor :project
       attr_accessor :view
@@ -64,17 +65,35 @@ module Redmine
           @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
 
-        # 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
+        detailed_groups = User.current.pref[:gantt_detailed_groups]
+        if options[:set_filter] == '1'
+          detailed_groups = (options[:not_detailed_groups].to_s == 'true') ? false : true
+        end
+        @detailed_groups = (detailed_groups.to_s == 'true') ? true : false
+
+        version_groups = User.current.pref[:gantt_version_groups].to_s
+        if options[:set_filter] == '1'
+          version_groups = options[:version_groups].to_s
+        end
+        @version_groups = (version_groups == 'true') ? true : false
+
+        # Save gantt parameters as user preference (zoom, months count and detailed grouping)
+        if (User.current.logged? &&
+             (@zoom != User.current.pref[:gantt_zoom] ||
+              @months != User.current.pref[:gantt_months] ||
+              @detailed_groups != User.current.pref[:gantt_detailed_groups] ||
+              @version_groups != User.current.pref[:gantt_version_groups]))
+          User.current.pref[:gantt_zoom] = @zoom
+          User.current.pref[:gantt_months] = @months
+          User.current.pref[:gantt_detailed_groups] = @detailed_groups
+          User.current.pref[:gantt_version_groups] = @version_groups
           User.current.preference.save
         end
 
@@ -120,19 +139,25 @@ module Redmine
 
       def params
         common_params.merge({:zoom => zoom, :year => year_from,
-                             :month => month_from, :months => months})
+                             :month => month_from, :months => months,
+                             :not_detailed_groups => !detailed_groups,
+                             :version_groups => version_groups})
       end
 
       def params_previous
         common_params.merge({:year => (date_from << months).year,
                              :month => (date_from << months).month,
-                             :zoom => zoom, :months => months})
+                             :zoom => zoom, :months => months,
+                             :not_detailed_groups => !detailed_groups,
+                             :version_groups => version_groups})
       end
 
       def params_next
         common_params.merge({:year => (date_from >> months).year,
                              :month => (date_from >> months).month,
-                             :zoom => zoom, :months => months})
+                             :zoom => zoom, :months => months,
+                             :not_detailed_groups => !detailed_groups,
+                             :version_groups => version_groups})
       end
 
       # Returns the number of rows that will be rendered on the Gantt chart
@@ -205,6 +230,13 @@ module Redmine
         project_issues(project).select {|issue| issue.fixed_version == version}
       end
 
+      # Returns the issues that belong to +project+ and are grouped by +group+
+      def group_issues!(group, issues)
+        result = issues.take_while {|issue| group_name(issue) == group}
+        issues.reject! {|issue| group_name(issue) == group}
+        result
+      end
+
       def render(options={})
         options = {:top => 0, :top_increment => 20,
                    :indent_increment => 20, :render => :subject,
@@ -230,9 +262,12 @@ module Redmine
         options[:indent] += options[:indent_increment]
         @number_of_rows += 1
         return if abort?
-        issues = project_issues(project).select {|i| i.fixed_version.nil?}
-        sort_issues!(issues)
-        if issues
+        issues = project_issues(project).select {|i| i.fixed_version.nil? || (grouped? && !@version_groups) }
+        if issues && grouped?
+          render_groups(issues, options)
+          return if abort?
+        else
+          sort_issues!(issues)
           render_issues(issues, options)
           return if abort?
         end
@@ -245,33 +280,94 @@ module Redmine
         options[:indent] -= options[:indent_increment]
       end
 
-      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?
-        end
-        options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
-      end
-
       def render_version(project, version, options={})
         # Version header
         subject_for_version(version, options) unless options[:only] == :lines
         line_for_version(version, options) unless options[:only] == :subjects
         options[:top] += options[:top_increment]
+        options[:indent] += options[:indent_increment]
         @number_of_rows += 1
-        return if abort?
+        return if abort? || (grouped? && !@version_groups)
         issues = version_issues(project, version)
-        if issues
+        if issues && grouped?
+          render_groups(issues, options)
+          return if abort?
+        else
           sort_issues!(issues)
-          # Indent issues
-          options[:indent] += options[:indent_increment]
           render_issues(issues, options)
-          options[:indent] -= options[:indent_increment]
+          return if abort?
         end
+        # Remove indent to hit the next sibling
+        options[:indent] -= options[:indent_increment]
+      end
+
+      def render_groups(issues, options={})
+        while !issues.empty?
+          # Group header
+          group = group_name(issues[0])
+          subject_for_group(group, options) unless options[:only] == :lines
+          options[:top] += options[:top_increment]
+          @number_of_rows += 1
+          break if abort?
+          gr_issues = group_issues!(group, issues)
+          if gr_issues
+            sort_issues!(gr_issues)
+            # Indent issues
+            options[:indent] += options[:indent_increment]
+            render_issues(gr_issues, options)
+            options[:indent] -= options[:indent_increment]
+          end
+          break if abort?
+        end
+      end
+
+      def render_issues(issues, options={})
+        @issue_ancestors = []
+        if !grouped? || @detailed_groups
+          issues.each do |i|
+            if i.due_before == nil
+              i.due_date = i.start_date
+            end
+            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
+        else
+          group_start = options[:top] - options[:top_increment]
+          group_max_line = 0
+          group_write_line = group_max_line
+          dates_in_line = [[]]
+          @number_of_rows += 1
+          issues.each do |i|
+            if i.leaf?
+              if i.due_before == nil
+                i.due_date = i.start_date
+              end
+              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
+              options[:top] = group_start + (group_write_line * options[:top_increment])
+              subject_for_issue(i, options) unless options[:only] == :lines
+              line_for_issue(i, options) unless options[:only] == :subjects
+              dates_in_line[group_write_line].push([i.start_date, i.due_before])
+            end
+            break if abort?
+          end
+          options[:top] = group_start + ((group_max_line+1) * options[:top_increment])
+        end
+        options[:indent] -= (options[:indent_increment] * @issue_ancestors.size)
       end
 
       def render_end(options={})
@@ -300,7 +396,7 @@ module Redmine
       end
 
       def line_for_project(project, options)
-        # Skip versions that don't have a start_date or due date
+        # Skip projects that don't have a start_date or due date
         if project.is_a?(Project) && project.start_date && project.due_date
           options[:zoom] ||= 1
           options[:g_width] ||= (self.date_to - self.date_from + 1) * options[:zoom]
@@ -363,6 +459,23 @@ module Redmine
         end
       end
 
+      def subject_for_group(group, options)
+        case options[:format]
+        when :html
+          html_class = ""
+          html_class << 'icon icon-package '
+          s = group.html_safe
+          subject = view.content_tag(:span, s,
+                                     :class => html_class).html_safe
+          html_subject(options, subject, :css => "version-name")
+        when :image
+          image_subject(options, group)
+        when :pdf
+          pdf_new_page?(options)
+          pdf_subject(options, group)
+        end
+      end
+
       def subject_for_issue(issue, options)
         while @issue_ancestors.any? && !issue.is_descendant_of?(@issue_ancestors.last)
           @issue_ancestors.pop
@@ -370,27 +483,31 @@ module Redmine
         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
-          s = "".html_safe
-          if 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
-          subject = view.content_tag(:span, s, :class => css_classes).html_safe
-          html_subject(options, subject, :css => "issue-subject",
-                       :title => issue.subject) + "\n"
+          if !grouped? || @detailed_groups
+            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
+            s = "".html_safe
+            if 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
+            subject = view.content_tag(:span, s, :class => css_classes).html_safe
+            html_subject(options, subject, :css => "issue-subject",
+                         :title => issue.subject) + "\n"
+          end
         when :image
-          image_subject(options, issue.subject)
+          if !grouped? || @detailed_groups
+            image_subject(options, issue.subject)
+          end
         when :pdf
           pdf_new_page?(options)
-          pdf_subject(options, issue.subject)
+          pdf_subject(options, (grouped? || @detailed_groups) ? ' ' : issue.subject)
         end
         unless issue.leaf?
           @issue_ancestors << issue
@@ -403,7 +520,10 @@ module Redmine
         # 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}%"
+          label = " "
+          if !grouped? || @detailed_groups
+            label = "#{issue.status.name} #{issue.done_ratio}%"
+          end
           case options[:format]
           when :html
             html_task(options, coords,
@@ -742,6 +862,34 @@ module Redmine
         [min, max]
       end
 
+      def grouped?
+        @query.grouped?
+      end
+
+      def new_group?(x, y)
+        if grouped?
+          value_x = @query.group_by_column.value(x)
+          value_y = @query.group_by_column.value(y)
+
+          value_x != value_y
+        else
+          true
+        end
+      end
+
+      def group_name(issue)
+        if grouped?
+          result = @query.group_by_column.value(issue)
+          if result == nil || result == ''
+            'None'
+          else
+            result.to_s
+          end
+        else
+          issue.subject
+        end
+      end
+
       def pdf_new_page?(options)
         if options[:top] > 180
           options[:pdf].Line(15, options[:top], PDF::TotalWidth, options[:top])