Index: views.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/views.module,v
retrieving revision 1.280
diff -u -p -r1.280 views.module
--- views.module	27 May 2008 22:10:58 -0000	1.280
+++ views.module	28 May 2008 22:34:25 -0000
@@ -29,7 +29,7 @@ function views_theme() {
 
   $arguments = array(
     'display' => array('view' => NULL),
-    'style' => array('view' => NULL, 'options' => NULL, 'rows' => NULL),
+    'style' => array('view' => NULL, 'options' => NULL, 'rows' => NULL, 'title' => NULL),
     'row' => array('view' => NULL, 'options' => NULL, 'row' => NULL),
   );
 
Index: includes/field.handlers.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/field.handlers.inc,v
retrieving revision 1.10
diff -u -p -r1.10 field.handlers.inc
--- includes/field.handlers.inc	8 May 2008 21:20:31 -0000	1.10
+++ includes/field.handlers.inc	28 May 2008 22:34:25 -0000
@@ -25,6 +25,18 @@ class views_handler_field extends views_
     if (!empty($this->definition['additional fields'])) {
       $this->additional_fields = $this->definition['additional fields'];
     }
+    
+    if (!isset($this->options['exclude'])) {
+      $this->options['exclude'] = '';
+    }
+  }
+  
+  function init(&$view, $options) {
+    parent::init($view, $options);
+    
+    $this->options += array(
+      'exclude' => FALSE,
+    );
   }
 
   /**
@@ -117,6 +129,12 @@ class views_handler_field extends views_
       '#default_value' => isset($this->options['label']) ? $this->options['label'] : '',
       '#description' => t('The label for this field that will be displayed to end users if the style requires it.'),
     );
+    $form['exclude'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Exclude from display'),
+      '#default_value' => $this->options['exclude'],
+      '#description' => t('Check this box to not display this field, but still load it in the view.  Use this option to not show a grouping field in each record, or when doing advanced theming.'),
+    );
   }
 
   /**
Index: includes/plugins.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/includes/plugins.inc,v
retrieving revision 1.100
diff -u -p -r1.100 plugins.inc
--- includes/plugins.inc	27 May 2008 21:57:06 -0000	1.100
+++ includes/plugins.inc	28 May 2008 22:34:26 -0000
@@ -80,6 +80,8 @@ function views_views_plugins() {
         'handler' => 'views_plugin_style_default',
         'theme' => 'views_view_unformatted',
         'uses row plugin' => TRUE,
+        'uses options' => TRUE,
+        'uses grouping' => TRUE,
         'type' => 'normal',
       ),
       'list' => array(
@@ -2485,6 +2487,14 @@ class views_plugin_style extends views_p
     if ($this->uses_row_plugin() && $display->handler->get_option('row_plugin')) {
       $this->row_plugin = $display->handler->get_plugin('row');
     }
+    
+    $this->options += array(
+      'grouping' => '',
+    );
+    
+    $this->definition += array(
+      'uses grouping' => TRUE,
+    );
   }
 
   /**
@@ -2510,7 +2520,34 @@ class views_plugin_style extends views_p
   /**
    * Static member function to set default options.
    */
-  function options(&$options) { }
+  function options(&$options) {
+    $options['grouping'] = '';
+  }
+
+  function options_form(&$form, &$form_state) {
+    // Only fields-based views can handle grouping.  Style plugins can also exclude 
+    // themselves from being groupable by setting their "use grouping" definiton
+    // key to FALSE.
+    // @TODO: Document "uses grouping" in docs.php when docs.php is written.
+    if ($this->uses_fields() && $this->definition['uses grouping']) {
+      $options = array('' => t('<None>'));
+      foreach ($this->display->handler->get_option('fields') as $field) {
+        $label = (empty($field['label']) ? '' : $field['label'] . ' ' ) ;
+        $options[$field['id']] = $label . '(' . $field['id'] .')';
+      }
+
+      // If there are no fields, we can't group on them.
+      if (count($options) > 1) {
+        $form['grouping'] = array(
+          '#type' => 'select',
+          '#title' => t('Grouping field'),
+          '#options' => $options,
+          '#default_value' => $this->options['grouping'],
+          '#description' => t('You may optionally specify a field by which to group the records.  Leave blank to not group.'),
+        );
+      }
+    }
+  }
 
   /**
    * Called by the view builder to see if this style handler wants to
@@ -2542,11 +2579,50 @@ class views_plugin_style extends views_p
       vpr('views_plugin_style_default: Missing row plugin');
       return;
     }
-    $rows = array();
-    foreach ($this->view->result as $row) {
-      $rows[] = $this->row_plugin->render($row);
+
+    // Group the rows according to the grouping field, if specified.
+    $sets = $this->render_grouping($this->view->result, $this->options['grouping']);
+
+    // Render each group separately and concatenate.  Plugins may override this
+    // method if they wish some other way of handling grouping.
+    $output = '';
+    foreach ($sets as $title => $records) {
+      $rows = array();
+      foreach ($records as $label => $row) {
+        $rows[] = $this->row_plugin->render($row);
+      }
+      $output .= theme($this->theme_functions(), $this->view, $this->options, $rows, $title);
+    }
+    return $output;
+  }
+  
+  /**
+   * Group records as needed for rendering.
+   *
+   * @param $records
+   *   An array of records from the view to group.
+   * @param $grouping_field
+   *   The field id on which to group.  If empty, the result set will be given
+   *   a single group with an empty string as a label.
+   * @return 
+   *   The grouped record set.
+   */
+  function render_grouping($records, $grouping_field = '') {
+    $sets = array();
+    if ($grouping_field) {
+      foreach ($records as $row) {
+        // Group on the rendered version of the field, not the raw.  That way,
+        // we can control any special formatting of the grouping field through
+        // the admin or theme layer or anywhere else we'd like.
+        $grouping = $this->view->field[$grouping_field]['handler']->theme($row);
+        $sets[$grouping][] = $row;
+      }
     }
-    return theme($this->theme_functions(), $this->view, $this->options, $rows);
+    else {
+      // Create a single group with an empty grouping field.
+      $sets[''] = $records;
+    }
+    return $sets;
   }
 
   function validate() {
@@ -2567,19 +2643,28 @@ class views_plugin_style extends views_p
  * decorations.
  */
 class views_plugin_style_default extends views_plugin_style {
+  /**
+   * Set default options
+   */
+  function options(&$options) {
+    parent::options($options);
+  }
+
   function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
     // @todo -- separator option
   }
 }
 
 /**
- * Style plugin to render each item in an ordered or unordered list
+ * Style plugin to render each item in an ordered or unordered list.
  */
 class views_plugin_style_list extends views_plugin_style {
   /**
    * Set default options
    */
   function options(&$options) {
+    parent::options($options);
     $options['type'] = 'ul';
   }
 
@@ -2587,6 +2672,7 @@ class views_plugin_style_list extends vi
    * Render the given style.
    */
   function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
     $form['type'] = array(
       '#type' => 'radios',
       '#title' => t('List type'),
@@ -2604,6 +2690,7 @@ class views_plugin_style_grid extends vi
    * Set default options
    */
   function options(&$options) {
+    parent::options($options);
     $options['columns'] = 4;
     $options['alignment'] = 'horizontal';
   }
@@ -2612,6 +2699,7 @@ class views_plugin_style_grid extends vi
    * Render the given style.
    */
   function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
     $form['columns'] = array(
       '#type' => 'textfield',
       '#title' => t('Number of columns'),
@@ -2635,6 +2723,7 @@ class views_plugin_style_table extends v
    * Set default options
    */
   function options(&$options) {
+    parent::options($options);
     $options['columns'] = array();
     $options['default'] = '';
     $options['info'] = array();
@@ -2735,6 +2824,7 @@ class views_plugin_style_table extends v
    * Render the given style.
    */
   function options_form(&$form, &$form_state) {
+    parent::options_form($form, $form_state);
     $fields = $this->display->handler->get_option('fields');
     if (empty($fields)) {
       $form['error_markup'] = array(
@@ -2862,7 +2952,13 @@ class views_plugin_style_table extends v
    * Render the table style.
    */
   function render() {
-    return theme($this->theme_functions(), $this->view, $this->options, array());
+    $sets = $this->render_grouping($this->view->result, $this->options['grouping']);
+
+    $output = '';
+    foreach ($sets as $title => $records) {
+      $output .= theme($this->theme_functions(), $this->view, $this->options, $records, $title);
+    }
+    return $output;
   }
 }
 
Index: theme/theme.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/theme/theme.inc,v
retrieving revision 1.44
diff -u -p -r1.44 theme.inc
--- theme/theme.inc	21 May 2008 16:36:19 -0000	1.44
+++ theme/theme.inc	28 May 2008 22:34:26 -0000
@@ -141,7 +141,7 @@ function template_preprocess_views_view_
   // Loop through the fields for this view.
   $inline = FALSE;
   foreach ($view->field as $id => $field) {
-    if (!empty($field['handler']) && is_object($field['handler'])) {
+    if (!empty($field['handler']) && is_object($field['handler']) && empty($field['exclude'])) {
       $object = new stdClass();
 
       $object->content = $field['handler']->theme($vars['row']);
@@ -217,7 +217,14 @@ function template_preprocess_views_view_
  */
 function template_preprocess_views_view_table(&$vars) {
   $view     = $vars['view'];
-  $result   = $view->result;
+
+  // We need the raw data for this grouping, which is passed in as $vars['rows'].
+  // However, the template also needs to use for the rendered fields.  We
+  // therefore swap the raw data out to a new variable and reset $vars['rows']
+  // so that it can get rebuilt.
+  $result   = $vars['rows'];
+  $vars['rows'] = array();
+
   $options  = $view->style_plugin->options;
   $handler  = $view->style_plugin;
 
@@ -231,10 +238,10 @@ function template_preprocess_views_view_
   if ($query) {
     $query = '&' . $query;
   }
-
+  
   foreach ($columns as $field => $column) {
     // render the header labels
-    if ($field == $column) {
+    if ($field == $column && empty($fields[$field]['exclude'])) {
       $label = check_plain(!empty($fields[$field]['handler']) ? $fields[$field]['handler']->label() : '');
       if (empty($options['info'][$field]['sortable'])) {
         $vars['header'][$field] = $label;
@@ -267,7 +274,7 @@ function template_preprocess_views_view_
 
     // Render each field into its appropriate column.
     foreach ($result as $num => $row) {
-      if (!empty($fields[$field]['handler']) && is_object($fields[$field]['handler'])) {
+      if (!empty($fields[$field]['handler']) && is_object($fields[$field]['handler']) && empty($fields[$field]['exclude'])) {
         $handler = &$fields[$field]['handler'];
         $field_output = $handler->theme($row);
 
Index: theme/views-view-grid.tpl.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/theme/views-view-grid.tpl.php,v
retrieving revision 1.1
diff -u -p -r1.1 views-view-grid.tpl.php
--- theme/views-view-grid.tpl.php	20 Apr 2008 23:41:06 -0000	1.1
+++ theme/views-view-grid.tpl.php	28 May 2008 22:34:26 -0000
@@ -10,6 +10,9 @@
  * @ingroup views_templates
  */
 ?>
+<?php if (!empty($title)) : ?>
+  <h3><?php print $title; ?></h3>
+<?php endif; ?>
 <table class="views-view-grid">
   <tbody>
     <?php foreach ($rows as $row_number => $columns): ?>
Index: theme/views-view-list.tpl.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/theme/views-view-list.tpl.php,v
retrieving revision 1.1
diff -u -p -r1.1 views-view-list.tpl.php
--- theme/views-view-list.tpl.php	15 Feb 2008 04:11:48 -0000	1.1
+++ theme/views-view-list.tpl.php	28 May 2008 22:34:26 -0000
@@ -4,11 +4,15 @@
  * @file views-view-list.tpl.php
  * Default simple view template to display a list of rows.
  *
+ * - $title : The title of this group of rows.  May be empty.
  * - $options['type'] will either be ul or ol.
  * @ingroup views_templates
  */
 ?>
 <div class="item-list">
+  <?php if (!empty($title)) : ?>
+    <h3><?php print $title; ?></h3>
+  <?php endif; ?>
   <<?php print $options['type']; ?>>
     <?php foreach ($rows as $row): ?>
       <li><?php print $row ?></li>
Index: theme/views-view-table.tpl.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/theme/views-view-table.tpl.php,v
retrieving revision 1.4
diff -u -p -r1.4 views-view-table.tpl.php
--- theme/views-view-table.tpl.php	14 May 2008 01:32:55 -0000	1.4
+++ theme/views-view-table.tpl.php	28 May 2008 22:34:26 -0000
@@ -4,6 +4,7 @@
  * @file views-view-table.tpl.php
  * Template to display a view as a table.
  *
+ * - $title : The title of this group of rows.  May be empty.
  * - $header: An array of header labels keyed by field id.
  * - $fields: An array of CSS IDs to use for each field id.
  * - $class: A class or classes to apply to the table, based on settings.
@@ -13,6 +14,9 @@
  */
 ?>
 <table class="<?php print $class; ?>">
+  <?php if (!empty($title)) : ?>
+    <caption><?php print $title; ?></caption>
+  <?php endif; ?>
   <thead>
     <tr>
       <?php foreach ($header as $field => $label): ?>
Index: theme/views-view-unformatted.tpl.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/views/theme/views-view-unformatted.tpl.php,v
retrieving revision 1.2
diff -u -p -r1.2 views-view-unformatted.tpl.php
--- theme/views-view-unformatted.tpl.php	15 Feb 2008 04:11:48 -0000	1.2
+++ theme/views-view-unformatted.tpl.php	28 May 2008 22:34:26 -0000
@@ -7,6 +7,9 @@
  * @ingroup views_templates
  */
 ?>
+<?php if (!empty($title)) : ?>
+  <h3><?php print $title; ?></h3>
+<?php endif; ?>
 <?php foreach ($rows as $row): ?>
   <?php print $row ?>
 <?php endforeach; ?>
\ No newline at end of file
