lmi-commits
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[lmi-commits] [6258] Implement new "group premium quote" report


From: Greg Chicares
Subject: [lmi-commits] [6258] Implement new "group premium quote" report
Date: Wed, 19 Aug 2015 17:02:28 +0000

Revision: 6258
          http://svn.sv.gnu.org/viewvc/?view=rev&root=lmi&revision=6258
Author:   chicares
Date:     2015-08-19 17:02:28 +0000 (Wed, 19 Aug 2015)
Log Message:
-----------
Implement new "group premium quote" report

Modified Paths:
--------------
    lmi/trunk/ChangeLog
    lmi/trunk/Makefile.am
    lmi/trunk/census_view.cpp
    lmi/trunk/census_view.hpp
    lmi/trunk/emit_ledger.cpp
    lmi/trunk/emit_ledger.hpp
    lmi/trunk/gpt_view.cpp
    lmi/trunk/ihs_acctval.cpp
    lmi/trunk/illustration_view.cpp
    lmi/trunk/main_wx.cpp
    lmi/trunk/main_wx_test.cpp
    lmi/trunk/mc_enum_type_enums.hpp
    lmi/trunk/mc_enum_types.cpp
    lmi/trunk/mc_enum_types_aux.cpp
    lmi/trunk/mec_view.cpp
    lmi/trunk/menus.xrc
    lmi/trunk/objects.make
    lmi/trunk/skeleton.cpp
    lmi/trunk/toolbar.xrc
    lmi/trunk/workhorse.make

Added Paths:
-----------
    lmi/trunk/group_quote_pdf_gen.cpp
    lmi/trunk/group_quote_pdf_gen.hpp
    lmi/trunk/group_quote_pdf_gen_wx.cpp
    lmi/trunk/wx_table_generator.cpp
    lmi/trunk/wx_table_generator.hpp

Modified: lmi/trunk/ChangeLog
===================================================================
--- lmi/trunk/ChangeLog 2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/ChangeLog 2015-08-19 17:02:28 UTC (rev 6258)
@@ -36630,3 +36630,37 @@
   ihs_basicval.cpp
 Remove an ugly disused data member.
 
+20150818T1840Z <address@hidden> [477]
+
+  workhorse.make
+  wx_pdfdoc_test.cpp [expunged]
+Expunge a temporary test that has served its purpose.
+
+20150819T1702Z <address@hidden> [477]
+
+  Makefile.am
+  census_view.cpp
+  census_view.hpp
+  emit_ledger.cpp
+  emit_ledger.hpp
+  gpt_view.cpp
+  group_quote_pdf_gen.cpp    [new file]
+  group_quote_pdf_gen.hpp    [new file]
+  group_quote_pdf_gen_wx.cpp [new file]
+  ihs_acctval.cpp
+  illustration_view.cpp
+  main_wx.cpp
+  main_wx_test.cpp
+  mc_enum_type_enums.hpp
+  mc_enum_types.cpp
+  mc_enum_types_aux.cpp
+  mec_view.cpp
+  menus.xrc
+  objects.make
+  skeleton.cpp
+  toolbar.xrc
+  workhorse.make
+  wx_table_generator.cpp     [new file]
+  wx_table_generator.hpp     [new file]
+Implement new "group premium quote" report.
+

Modified: lmi/trunk/Makefile.am
===================================================================
--- lmi/trunk/Makefile.am       2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/Makefile.am       2015-08-19 17:02:28 UTC (rev 6258)
@@ -167,6 +167,7 @@
     file_command_wx.cpp \
     gpt_document.cpp \
     gpt_view.cpp \
+    group_quote_pdf_generator_wx.cpp \
     icon_monger.cpp \
     illustration_document.cpp \
     illustration_view.cpp \
@@ -198,6 +199,7 @@
     tier_view_editor.cpp \
     transferor.cpp \
     view_ex.cpp \
+    wx_table_generator.cpp \
     wx_utility.cpp
 
 # main program executables
@@ -310,6 +312,7 @@
     getopt.cpp \
     global_settings.cpp \
     group_values.cpp \
+    group_quote_pdf_generator.cpp \
     illustrator.cpp \
     input.cpp \
     input_harmonization.cpp \
@@ -1103,6 +1106,7 @@
     gpt_state.hpp \
     gpt_view.hpp \
     gpt_xml_document.hpp \
+    group_quote_pdf_gen.hpp \
     group_values.hpp \
     handle_exceptions.hpp \
     icon_monger.hpp \
@@ -1228,6 +1232,7 @@
     view_ex.hpp \
     view_ex.tpp \
     wx_new.hpp \
+    wx_table_generator.hpp \
     wx_utility.hpp \
     wx_workarounds.hpp \
     xml_lmi.hpp \

Modified: lmi/trunk/census_view.cpp
===================================================================
--- lmi/trunk/census_view.cpp   2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/census_view.cpp   2015-08-19 17:02:28 UTC (rev 6258)
@@ -810,6 +810,7 @@
     EVT_MENU(XRCID("print_case_to_disk"        
),CensusView::UponPrintCaseToDisk        )
     EVT_MENU(XRCID("print_spreadsheet"         
),CensusView::UponRunCaseToSpreadsheet   )
     EVT_MENU(XRCID("print_group_roster"        
),CensusView::UponRunCaseToGroupRoster   )
+    EVT_MENU(XRCID("print_group_quote"         
),CensusView::UponRunCaseToGroupQuote    )
     EVT_MENU(XRCID("paste_census"              ),CensusView::UponPasteCensus   
         )
     EVT_MENU(XRCID("add_cell"                  ),CensusView::UponAddCell       
         )
     EVT_MENU(XRCID("delete_cells"              ),CensusView::UponDeleteCells   
         )
@@ -825,6 +826,7 @@
     EVT_UPDATE_UI(XRCID("print_case_to_disk"   
),CensusView::UponUpdateAlwaysEnabled    )
     EVT_UPDATE_UI(XRCID("print_spreadsheet"    
),CensusView::UponUpdateAlwaysEnabled    )
     EVT_UPDATE_UI(XRCID("print_group_roster"   
),CensusView::UponUpdateAlwaysEnabled    )
+    EVT_UPDATE_UI(XRCID("print_group_quote"    
),CensusView::UponUpdateAlwaysEnabled    )
     EVT_UPDATE_UI(XRCID("paste_census"         
),CensusView::UponUpdateAlwaysEnabled    )
     EVT_UPDATE_UI(XRCID("add_cell"             
),CensusView::UponUpdateAlwaysEnabled    )
     EVT_UPDATE_UI(XRCID("delete_cells"         
),CensusView::UponUpdateNonemptySelection)
@@ -1540,6 +1542,13 @@
     DoAllCells(mce_emit_group_roster);
 }
 
+/// Print group quote to PDF file.
+
+void CensusView::UponRunCaseToGroupQuote(wxCommandEvent&)
+{
+    DoAllCells(mce_emit_group_quote);
+}
+
 /// Paste a census from the clipboard.
 ///
 /// See unit tests in Skeleton::UponTestPasting().

Modified: lmi/trunk/census_view.hpp
===================================================================
--- lmi/trunk/census_view.hpp   2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/census_view.hpp   2015-08-19 17:02:28 UTC (rev 6258)
@@ -84,6 +84,7 @@
     void UponRunCase                (wxCommandEvent&);
     void UponRunCaseToSpreadsheet   (wxCommandEvent&);
     void UponRunCaseToGroupRoster   (wxCommandEvent&);
+    void UponRunCaseToGroupQuote    (wxCommandEvent&);
     void UponUpdateAlwaysDisabled   (wxUpdateUIEvent&);
     void UponUpdateAlwaysEnabled    (wxUpdateUIEvent&);
     void UponUpdateSingleSelection  (wxUpdateUIEvent&);

Modified: lmi/trunk/emit_ledger.cpp
===================================================================
--- lmi/trunk/emit_ledger.cpp   2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/emit_ledger.cpp   2015-08-19 17:02:28 UTC (rev 6258)
@@ -32,6 +32,7 @@
 #include "custom_io_0.hpp"
 #include "custom_io_1.hpp"
 #include "file_command.hpp"
+#include "group_quote_pdf_gen.hpp"
 #include "ledger.hpp"
 #include "ledger_text_formats.hpp"
 #include "ledger_xsl.hpp"
@@ -87,6 +88,13 @@
         std::remove(spreadsheet_filename.c_str());
         PrintRosterHeaders(spreadsheet_filename);
         }
+    if(emission_ & mce_emit_group_quote)
+        {
+        LMI_ASSERT(!case_filepath_.empty());
+        std::string const pdf_filename = case_filepath_.string() + 
".quote.pdf";
+        std::remove(pdf_filename.c_str());
+        group_quote_gen_ = group_quote_pdf_generator::create();
+        }
 
     return timer.stop().elapsed_seconds();
 }
@@ -145,6 +153,10 @@
             +   configurable_settings::instance().spreadsheet_file_extension()
             );
         }
+    if(emission_ & mce_emit_group_quote)
+        {
+        group_quote_gen_->add_ledger(ledger);
+        }
     if(emission_ & mce_emit_text_stream)
         {
         PrintLedgerFlatText(ledger, std::cout);
@@ -180,7 +192,12 @@
 {
     Timer timer;
 
-    ; // Nothing to do for now.
+    if(emission_ & mce_emit_group_quote)
+        {
+        LMI_ASSERT(!case_filepath_.empty());
+        std::string const pdf_filename = case_filepath_.string() + 
".quote.pdf";
+        group_quote_gen_->save(pdf_filename);
+        }
 
     return timer.stop().elapsed_seconds();
 }

Modified: lmi/trunk/emit_ledger.hpp
===================================================================
--- lmi/trunk/emit_ledger.hpp   2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/emit_ledger.hpp   2015-08-19 17:02:28 UTC (rev 6258)
@@ -32,7 +32,9 @@
 #include "uncopyable_lmi.hpp"
 
 #include <boost/filesystem/path.hpp>
+#include <boost/shared_ptr.hpp>
 
+class group_quote_pdf_generator;
 class Ledger;
 
 /// Emit a group of ledgers in various guises.
@@ -55,6 +57,9 @@
   private:
     fs::path const& case_filepath_;
     mcenum_emission emission_;
+
+    // Used only if emission includes mce_emit_group_quote; empty otherwise.
+    boost::shared_ptr<group_quote_pdf_generator> group_quote_gen_;
 };
 
 double LMI_SO emit_ledger

Modified: lmi/trunk/gpt_view.cpp
===================================================================
--- lmi/trunk/gpt_view.cpp      2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/gpt_view.cpp      2015-08-19 17:02:28 UTC (rev 6258)
@@ -84,6 +84,7 @@
     EVT_UPDATE_UI(XRCID("print_case_to_disk"   
),gpt_view::UponUpdateInapplicable)
     EVT_UPDATE_UI(XRCID("print_spreadsheet"    
),gpt_view::UponUpdateInapplicable)
     EVT_UPDATE_UI(XRCID("print_group_roster"   
),gpt_view::UponUpdateInapplicable)
+    EVT_UPDATE_UI(XRCID("print_group_quote"    
),gpt_view::UponUpdateInapplicable)
     EVT_UPDATE_UI(XRCID("paste_census"         
),gpt_view::UponUpdateInapplicable)
     EVT_UPDATE_UI(XRCID("add_cell"             
),gpt_view::UponUpdateInapplicable)
     EVT_UPDATE_UI(XRCID("delete_cells"         
),gpt_view::UponUpdateInapplicable)

Added: lmi/trunk/group_quote_pdf_gen.cpp
===================================================================
--- lmi/trunk/group_quote_pdf_gen.cpp                           (rev 0)
+++ lmi/trunk/group_quote_pdf_gen.cpp   2015-08-19 17:02:28 UTC (rev 6258)
@@ -0,0 +1,55 @@
+// Generate group premium quote PDF file.
+//
+// Copyright (C) 2015 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// 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.
+//
+// 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 St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+// $Id$
+
+#ifdef __BORLANDC__
+#   include "pchfile.hpp"
+#   pragma hdrstop
+#endif // __BORLANDC__
+
+#include "group_quote_pdf_gen.hpp"
+
+#include "callback.hpp"
+
+namespace
+{
+callback<group_quote_pdf_generator::creator_type>
+    group_quote_pdf_generator_create_callback;
+} // Unnnamed namespace.
+
+typedef group_quote_pdf_generator::creator_type FunctionPointer;
+template<> FunctionPointer callback<FunctionPointer>::function_pointer_ = 0;
+
+bool group_quote_pdf_generator::set_creator(creator_type f)
+{
+    group_quote_pdf_generator_create_callback.initialize(f);
+    return true;
+}
+
+boost::shared_ptr<group_quote_pdf_generator> 
group_quote_pdf_generator::create()
+{
+    return group_quote_pdf_generator_create_callback()();
+}
+
+group_quote_pdf_generator::~group_quote_pdf_generator()
+{
+}


Property changes on: lmi/trunk/group_quote_pdf_gen.cpp
___________________________________________________________________
Added: svn:keywords
   + Id

Added: lmi/trunk/group_quote_pdf_gen.hpp
===================================================================
--- lmi/trunk/group_quote_pdf_gen.hpp                           (rev 0)
+++ lmi/trunk/group_quote_pdf_gen.hpp   2015-08-19 17:02:28 UTC (rev 6258)
@@ -0,0 +1,64 @@
+// Generate group premium quote PDF file.
+//
+// Copyright (C) 2015 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// 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.
+//
+// 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 St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+// $Id$
+
+#ifndef group_quote_pdf_gen_hpp
+#define group_quote_pdf_gen_hpp
+
+#include "config.hpp"
+
+#include "so_attributes.hpp"
+#include "uncopyable_lmi.hpp"
+
+#include <boost/shared_ptr.hpp>
+
+#include <string>
+
+class Ledger;
+
+/// Abstract base class for generating group premium quote PDFs.
+///
+/// Although there is currently only a single concrete implementation of this
+/// abstract base class and no other implementations are planned, splitting the
+/// PDF generation functionality into an abstract base and the concrete derived
+/// class is still needed because the former is part of liblmi while the latter
+/// uses wxPdfDocument and other wx facilities and is only part of libskeleton.
+
+class LMI_SO group_quote_pdf_generator
+    :private lmi::uncopyable<group_quote_pdf_generator>
+{
+  public:
+    typedef boost::shared_ptr<group_quote_pdf_generator> (*creator_type)();
+
+    static bool set_creator(creator_type);
+    static boost::shared_ptr<group_quote_pdf_generator> create();
+
+    virtual ~group_quote_pdf_generator();
+
+    virtual void add_ledger(Ledger const& ledger) = 0;
+    virtual void save(std::string const& output_filename) = 0;
+
+  protected:
+    group_quote_pdf_generator() {}
+};
+
+#endif // group_quote_pdf_gen_hpp


Property changes on: lmi/trunk/group_quote_pdf_gen.hpp
___________________________________________________________________
Added: svn:keywords
   + Id

Added: lmi/trunk/group_quote_pdf_gen_wx.cpp
===================================================================
--- lmi/trunk/group_quote_pdf_gen_wx.cpp                                (rev 0)
+++ lmi/trunk/group_quote_pdf_gen_wx.cpp        2015-08-19 17:02:28 UTC (rev 
6258)
@@ -0,0 +1,903 @@
+// Generate group premium quote PDF file.
+//
+// Copyright (C) 2015 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// 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.
+//
+// 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 St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+// $Id$
+
+#ifdef __BORLANDC__
+#   include "pchfile.hpp"
+#   pragma hdrstop
+#endif // __BORLANDC__
+
+#include "group_quote_pdf_gen.hpp"
+
+#include "alert.hpp"
+#include "assert_lmi.hpp"
+#include "calendar_date.hpp"            // jdn_t()
+#include "force_linking.hpp"
+#include "ledger.hpp"
+#include "ledger_invariant.hpp"
+#include "ledger_text_formats.hpp"      // ledger_format()
+#include "oecumenic_enumerations.hpp"   // oenum_format_style
+#include "wx_table_generator.hpp"
+#include "wx_utility.hpp"               // ConvertDateToWx()
+
+#include <boost/scoped_ptr.hpp>
+#include <boost/static_assert.hpp>
+
+#include <wx/datetime.h>
+#include <wx/html/htmlcell.h>
+#include <wx/html/winpars.h>
+#include <wx/image.h>
+#include <wx/pdfdc.h>
+
+#include <limits>
+#include <utility>                      // std::pair
+#include <vector>
+
+LMI_FORCE_LINKING_IN_SITU(group_quote_pdf_generator_wx)
+
+namespace
+{
+
+enum enum_output_mode
+    {e_output_normal
+    ,e_output_measure_only
+    };
+
+/// Load the image from the given file. Throw on failure.
+
+wxImage load_image(char const* file)
+{
+    wxImage image(file);
+    if(!image.IsOk())
+        {
+        fatal_error()
+            << "File '"
+            << file
+            << "' is required but could not be found. Try reinstalling."
+            << LMI_FLUSH
+            ;
+        }
+
+    return image;
+}
+
+/// Render, or just pretend rendering in order to measure it, the given HTML
+/// contents at the specified position wrapping it at the given width.
+/// Return the height of the output (using this width).
+
+int output_html
+    (wxHtmlWinParser& html_parser
+    ,int x
+    ,int y
+    ,int width
+    ,wxString const& html
+    ,enum_output_mode output_mode = e_output_normal
+    )
+{
+    boost::scoped_ptr<wxHtmlContainerCell> const cell
+        (static_cast<wxHtmlContainerCell*>(html_parser.Parse(html))
+        );
+    LMI_ASSERT(cell);
+
+    cell->Layout(width);
+    switch(output_mode)
+        {
+        case e_output_normal:
+            {
+            wxHtmlRenderingInfo rendering_info;
+            cell->Draw
+                (*html_parser.GetDC()
+                ,x
+                ,y
+                ,0
+                ,std::numeric_limits<int>::max()
+                ,rendering_info
+                );
+            }
+            break;
+        case e_output_measure_only:
+            // Nothing else to do.
+            break;
+        }
+
+    return cell->GetHeight();
+}
+
+enum enum_group_quote_columns
+    {e_col_number
+    ,e_col_name
+    ,e_col_age
+    ,e_col_dob
+    ,e_col_salary
+    ,e_col_face_amount
+    ,e_col_premium
+    ,e_col_premium_with_waiver
+    ,e_col_premium_with_adb
+    ,e_col_premium_with_waiver_and_adb
+    ,e_col_max
+    };
+
+struct column_definition
+{
+    char const* const header_;
+    char const* const widest_text_; // Empty string means variable width.
+};
+
+column_definition const column_definitions[] =
+    {{"Part#"                            ,             "9999"   }
+    ,{"Participant"                      ,                ""    }
+    ,{"Issue Age"                        ,              "999"   }
+    ,{"Date of Birth"                    ,       "9999-99-99"   }
+    ,{"Income"                           ,      "$99,999,999"   }
+    ,{"Face Amount"                      ,   "$99,999,999.00"   }
+    // All the subsequent columns use dynamically determined "premium mode" in
+    // their title, so their labels are actually format strings.
+    ,{"%s\nPremium"                      ,      "$999,999.00"   }
+    ,{"%s\nPremium with\nWaiver"         ,      "$999,999.00"   }
+    ,{"%s\nPremium with\nADB"            ,      "$999,999.00"   }
+    ,{"%s\nPremium with\nWaiver &\nADB"  ,      "$999,999.00"   }
+    };
+
+BOOST_STATIC_ASSERT(sizeof column_definitions / sizeof(column_definitions[0]) 
== e_col_max);
+
+class group_quote_pdf_generator_wx
+    :public group_quote_pdf_generator
+{
+  public:
+    static boost::shared_ptr<group_quote_pdf_generator> do_create()
+        {
+        return boost::shared_ptr<group_quote_pdf_generator>
+                (new group_quote_pdf_generator_wx()
+                );
+        }
+
+    virtual void add_ledger(Ledger const& ledger);
+    virtual void save(std::string const& output_filename);
+
+  private:
+    // These margins are arbitrary and can be changed to conform to subjective
+    // preferences.
+    static int const horz_margin = 24;
+    static int const vert_margin = 36;
+    static int const vert_skip   = 12;
+
+    // Ctor is private as it is only used by do_create().
+    group_quote_pdf_generator_wx();
+
+    // Generate the PDF once we have all the data.
+    void do_generate_pdf(wxPdfDC& pdf_dc);
+
+    // Compute the number of pages needed by the table rows in the output given
+    // the space remaining on the first page, the heights of the header, one
+    // table row and the footer and the last row position.
+    // Remaining space contains the space on the first page on input and is
+    // updated with the space remaining on the last page on output.
+    int compute_pages_for_table_rows
+        (int* remaining_space
+        ,int  header_height
+        ,int  row_height
+        ,int  last_row_y
+        );
+
+    void output_page_number
+        (wxPdfDC& pdf_dc
+        ,int      total_pages
+        ,int      current_page
+        );
+    void output_image_header
+        (wxPdfDC& pdf_dc
+        ,int*     pos_y
+        );
+    void output_document_header
+        (wxPdfDC&         pdf_dc
+        ,wxHtmlWinParser& html_parser
+        ,int*             pos_y
+        );
+    void output_table_totals
+        (wxPdfDC&            pdf_dc
+        ,wx_table_generator& table_gen
+        ,int*                pos_y
+        );
+    void output_footer
+        (wxPdfDC&         pdf_dc
+        ,wxHtmlWinParser& html_parser
+        ,int*             pos_y
+        ,enum_output_mode output_mode = e_output_normal
+        );
+
+    struct header_data
+        {
+        // Extract header fields from a ledger.
+        void fill_header_data(LedgerInvariant const& ledger);
+
+        std::string company_;
+        std::string prepared_by_;
+        std::string guarantee_issue_max_;
+        std::string product_;
+        std::string available_riders_;
+        std::string plan_type_;
+        std::string premium_mode_;
+        std::string contract_state_;
+        };
+    header_data header_;
+
+    struct row_data
+        {
+        std::string values[e_col_max];
+        };
+    std::vector<row_data> rows_;
+
+    class totals_data
+    {
+      public:
+        totals_data()
+            {
+            for(int col = e_col_face_amount; col < e_col_max; ++col)
+                {
+                value(col) = 0.0;
+                }
+            }
+
+        void total(int col, double d)
+            {
+            value(col) = d;
+            }
+
+        double total(int col) const
+            {
+            return const_cast<totals_data*>(this)->value(col);
+            }
+
+      private:
+        double& value(int col) { return values_[col - e_col_face_amount]; }
+
+        double values_[e_col_max - e_col_face_amount];
+    };
+    totals_data totals_;
+
+    struct page_metrics
+        {
+        page_metrics()
+            :width_(0)
+            {
+            }
+
+        void initialize(wxDC const& dc)
+            {
+            total_size_ = dc.GetSize();
+            width_ = total_size_.x - 2 * horz_margin;
+            }
+
+        wxSize total_size_;
+        int width_;
+        };
+    page_metrics page_;
+
+    int row_num_;
+};
+
+group_quote_pdf_generator_wx::group_quote_pdf_generator_wx()
+    :row_num_(0)
+{
+}
+
+void group_quote_pdf_generator_wx::header_data::fill_header_data
+    (LedgerInvariant const& ledger
+    )
+{
+    company_ = ledger.CorpName;
+
+    prepared_by_ = ledger.ProducerName;
+    guarantee_issue_max_ = "$500,000"; // FIXME
+    product_ = ledger.ProductName;
+    available_riders_ = "Waiver, ADB, ABR, Spouse or Child"; // FIXME
+    plan_type_ = "Mandatory"; // FIXME
+    premium_mode_ = ledger.ErMode.at(0).str();
+    contract_state_ = ledger.GetStatePostalAbbrev();
+}
+
+void group_quote_pdf_generator_wx::add_ledger(Ledger const& ledger)
+{
+    LedgerInvariant const& Invar = ledger.GetLedgerInvariant();
+
+    // We suppose that header data is the same for all ledgers, so only fill it
+    // once.
+    if(header_.company_.empty())
+        {
+        header_.fill_header_data(Invar);
+        }
+
+    int const year = 0;
+
+    std::pair<int, oenum_format_style> const f0(0, oe_format_normal);
+    std::pair<int, oenum_format_style> const f2(2, oe_format_normal);
+
+    bool const is_composite = ledger.GetIsComposite();
+
+    row_data rd;
+    for(int col = 0; col < e_col_max; ++col)
+        {
+        // The cast is only used to ensure that if any new elements are added
+        // to the enum, the compiler would warn about their values not being
+        // present in this switch.
+        switch(static_cast<enum_group_quote_columns>(col))
+            {
+            case e_col_number:
+                rd.values[col] = wxString::Format("%d", 
++row_num_).ToStdString();
+                continue;
+            case e_col_name:
+                rd.values[col] = Invar.Insured1;
+                continue;
+            case e_col_age:
+                rd.values[col] = wxString::Format("%.0f", 
Invar.Age).ToStdString();
+                continue;
+            case e_col_dob:
+                rd.values[col] = ConvertDateToWx
+                    (jdn_t(static_cast<int>(Invar.DateOfBirthJdn))
+                    ).FormatDate();
+                continue;
+            case e_col_salary:
+                rd.values[col] = '$' + ledger_format(Invar.Salary.at(year), 
f0);
+                continue;
+            case e_col_face_amount:
+                {
+                double const z = Invar.SpecAmt.at(year);
+                rd.values[col] = '$' + ledger_format(z, f0);
+                if(is_composite)
+                    {
+                    totals_.total(col, z);
+                    }
+                }
+                continue;
+            case e_col_premium:
+                {
+                double const z = Invar.InitModalPrem00;
+                rd.values[col] = '$' + ledger_format(z, f2);
+                if(is_composite)
+                    {
+                    totals_.total(col, z);
+                    }
+                }
+                continue;
+            case e_col_premium_with_waiver:
+                {
+                double const z = Invar.InitModalPrem01;
+                rd.values[col] = '$' + ledger_format(z, f2);
+                if(is_composite)
+                    {
+                    totals_.total(col, z);
+                    }
+                }
+                continue;
+            case e_col_premium_with_adb:
+                {
+                double const z = Invar.InitModalPrem10;
+                rd.values[col] = '$' + ledger_format(z, f2);
+                if(is_composite)
+                    {
+                    totals_.total(col, z);
+                    }
+                }
+                continue;
+            case e_col_premium_with_waiver_and_adb:
+                {
+                double const z = Invar.InitModalPrem11;
+                rd.values[col] = '$' + ledger_format(z, f2);
+                if(is_composite)
+                    {
+                    totals_.total(col, z);
+                    }
+                }
+                continue;
+            case e_col_max:
+                break;
+            }
+
+        LMI_ASSERT(!"Unknown group premium quote column.");
+        }
+
+    // The last, composite, ledger is only used for the totals, it shouldn't be
+    // shown in the main table.
+    if(!is_composite)
+        {
+        rows_.push_back(rd);
+        }
+}
+
+void group_quote_pdf_generator_wx::save(std::string const& output_filename)
+{
+    // Create a wxPrintData object just to describe the paper to use.
+    wxPrintData print_data;
+    print_data.SetOrientation(wxLANDSCAPE);
+    print_data.SetPaperId(wxPAPER_LETTER);
+
+    wxPdfDC pdf_dc(print_data);
+
+    page_.initialize(pdf_dc);
+
+    do_generate_pdf(pdf_dc);
+
+    // This is pretty baroque: to specify the name of the file after wxPdfDC
+    // creation, we need to poke print data stored inside it.
+    wxPdfDCImpl* const impl = dynamic_cast<wxPdfDCImpl*>(pdf_dc.GetImpl());
+    LMI_ASSERT(impl);
+
+    impl->GetPrintData().SetFilename(output_filename);
+
+    // After the above, EndDoc() will produce output in the specified file.
+    pdf_dc.EndDoc();
+}
+
+void group_quote_pdf_generator_wx::do_generate_pdf(wxPdfDC& pdf_dc)
+{
+    // Ensure that the output is independent of the current display resolution:
+    // it seems that this is only the case with the PDF map mode and wxDC mode
+    // different from wxMM_TEXT.
+    pdf_dc.SetMapModeStyle(wxPDF_MAPMODESTYLE_PDF);
+
+    // For simplicity, use points for everything: font sizers are expressed in
+    // them anyhow, so it's convenient to use them for everything else too.
+    pdf_dc.SetMapMode(wxMM_POINTS);
+
+    pdf_dc.StartDoc(wxString()); // Argument is not used.
+    pdf_dc.StartPage();
+
+    // Use a standard PDF Helvetica font (without embedding any custom fonts in
+    // the generated file, the only other realistic choice is Times New Roman).
+    pdf_dc.SetFont
+        (wxFontInfo(8).Family(wxFONTFAMILY_SWISS).FaceName("Helvetica")
+        );
+
+    // Create an HTML parser to allow easily adding HTML contents to the 
output.
+    wxHtmlWinParser html_parser(NULL);
+    html_parser.SetDC(&pdf_dc);
+    html_parser.SetStandardFonts
+        (pdf_dc.GetFont().GetPointSize()
+        ,"Helvetica"
+        ,"Courier"
+        );
+
+    int pos_y = 0;
+
+    output_image_header(pdf_dc, &pos_y);
+    pos_y += 2 * vert_skip;
+
+    output_document_header(pdf_dc, html_parser, &pos_y);
+    pos_y += 2 * vert_skip;
+
+    wx_table_generator table_gen
+        (pdf_dc
+        ,horz_margin
+        ,page_.width_
+        );
+
+    for(int col = 0; col < e_col_max; ++col)
+        {
+        column_definition const& cd = column_definitions[col];
+        std::string header(cd.header_);
+
+        // The cast is only used to ensure that if any new elements are added
+        // to the enum, the compiler would warn about their values not being
+        // present in this switch.
+        switch(static_cast<enum_group_quote_columns>(col))
+            {
+            case e_col_number:
+            case e_col_name:
+            case e_col_age:
+            case e_col_dob:
+            case e_col_salary:
+            case e_col_face_amount:
+                // Nothing to do for these columns, their labels are literal.
+                break;
+
+            case e_col_premium:
+            case e_col_premium_with_waiver:
+            case e_col_premium_with_adb:
+            case e_col_premium_with_waiver_and_adb:
+                {
+                // Labels of these columns are format strings as they need to
+                // be constructed dynamically.
+                LMI_ASSERT(header.find("%s") != std::string::npos);
+
+                header = wxString::Format
+                            (wxString(header), header_.premium_mode_
+                            ).ToStdString();
+                }
+                break;
+
+            case e_col_max:
+                LMI_ASSERT(!"unreachable");
+            }
+
+        table_gen.add_column(header.c_str(), cd.widest_text_);
+        }
+
+    output_table_totals(pdf_dc, table_gen, &pos_y);
+
+    int const y_before_header = pos_y;
+    table_gen.output_header(&pos_y);
+    int const header_height = pos_y - y_before_header;
+
+    int y_after_footer = pos_y;
+    output_footer(pdf_dc, html_parser, &y_after_footer, e_output_measure_only);
+    int const footer_height = y_after_footer - pos_y;
+
+    int const last_row_y = page_.total_size_.y - vert_margin;
+    int remaining_space = last_row_y - pos_y;
+
+    int total_pages = compute_pages_for_table_rows
+        (&remaining_space
+        ,header_height
+        ,table_gen.row_height()
+        ,last_row_y
+        );
+
+    // Check if the footer fits into the same page or if it needs a new one (we
+    // never want to have a page break in the footer).
+    bool const footer_on_its_own_page
+        = remaining_space < (footer_height + 2 * vert_skip);
+    if(footer_on_its_own_page)
+        {
+        total_pages++;
+        }
+
+    int current_page = 1;
+
+    typedef std::vector<row_data>::const_iterator rdci;
+    for(rdci i = rows_.begin(); i != rows_.end(); ++i)
+        {
+        table_gen.output_row(&pos_y, i->values);
+
+        if(pos_y >= last_row_y)
+            {
+            output_page_number(pdf_dc, total_pages, current_page);
+
+            current_page++;
+            pdf_dc.StartPage();
+
+            pos_y = vert_margin;
+            table_gen.output_header(&pos_y);
+            }
+        }
+
+    if(footer_on_its_own_page)
+        {
+        output_page_number(pdf_dc, total_pages, current_page);
+
+        current_page++;
+        pdf_dc.StartPage();
+
+        pos_y = vert_margin;
+        }
+    else
+        {
+        pos_y += 2 * vert_skip;
+        }
+
+    output_footer(pdf_dc, html_parser, &pos_y);
+
+    LMI_ASSERT(current_page == total_pages);
+    output_page_number(pdf_dc, total_pages, current_page);
+}
+
+int group_quote_pdf_generator_wx::compute_pages_for_table_rows
+    (int* remaining_space
+    ,int header_height
+    ,int row_height
+    ,int last_row_y
+    )
+{
+    int total_pages = 1;
+
+    int const max_rows_on_first_page = (*remaining_space) / row_height;
+    int remaining_rows = static_cast<int>(rows_.size());
+    if(max_rows_on_first_page < remaining_rows)
+        {
+        // All rows don't fit on the first page, so add enough pages for the
+        // rest of them.
+        remaining_rows -= max_rows_on_first_page;
+
+        int const page_area_y = last_row_y - vert_margin - header_height;
+        int const rows_per_page = page_area_y / row_height;
+        total_pages += (remaining_rows + rows_per_page - 1) / rows_per_page;
+        *remaining_space = page_area_y;
+        remaining_rows %= rows_per_page;
+        }
+
+    *remaining_space -= remaining_rows * row_height;
+
+    return total_pages;
+}
+
+void group_quote_pdf_generator_wx::output_page_number
+    (wxPdfDC& pdf_dc
+    ,int total_pages
+    ,int current_page
+    )
+{
+    pdf_dc.DrawLabel
+        (wxString::Format("Page %d of %d", current_page, total_pages)
+        ,wxRect
+            (horz_margin
+            ,page_.total_size_.y - vert_margin
+            ,page_.width_
+            ,vert_margin
+            )
+        ,wxALIGN_RIGHT | wxALIGN_BOTTOM
+        );
+}
+
+void group_quote_pdf_generator_wx::output_image_header
+    (wxPdfDC& pdf_dc
+    ,int* pos_y
+    )
+{
+    wxImage background_image(load_image("background.png"));
+    if(!background_image.IsOk())
+        {
+        return;
+        }
+
+    // Use wxPdfDocument API directly as wxDC doesn't provide a way to set the
+    // image scale at PDF level and also because passing via wxDC wastefully
+    // converts wxImage to wxBitmap only to convert it back to wxImage when
+    // embedding it into the PDF.
+    wxPdfDocument* const pdf_doc = pdf_dc.GetPdfDocument();
+    LMI_ASSERT(pdf_doc);
+
+    wxSize const image_size = background_image.GetSize();
+
+    // Set the scale to fit the image to the document width.
+    pdf_doc->SetImageScale
+        (static_cast<double>(image_size.x) / page_.total_size_.x
+        );
+    pdf_doc->Image("background", background_image, 0, *pos_y);
+
+    int const y = wxRound(image_size.y / pdf_doc->GetImageScale());
+
+    pdf_doc->SetImageScale(1);
+
+    wxDCFontChanger set_bigger_font(pdf_dc, pdf_dc.GetFont().Scaled(1.5));
+    wxDCTextColourChanger set_white_text(pdf_dc, *wxWHITE);
+
+    wxString const image_text
+        (header_.company_
+         + "\nPremium & Benefit Summary"
+        );
+
+    pdf_dc.DrawLabel
+        (image_text
+        ,wxRect
+            (wxPoint(horz_margin, *pos_y + y / 2),
+             pdf_dc.GetMultiLineTextExtent(image_text)
+            )
+        ,wxALIGN_CENTER_HORIZONTAL
+        );
+
+    *pos_y += y;
+}
+
+void group_quote_pdf_generator_wx::output_document_header
+    (wxPdfDC& pdf_dc
+    ,wxHtmlWinParser& html_parser
+    ,int* pos_y
+    )
+{
+    wxString const title_html = wxString::Format
+        ("<table width=\"100%%\">"
+         "<tr>"
+         "<td align=\"center\"><i><font size=\"+1\">%s</font></i></td>"
+         "</tr>"
+         "<tr>"
+         "<td align=\"center\"><i>Prepared Date: %s</i></td>"
+         "</tr>"
+         "<tr>"
+         "<td align=\"center\"><i>Prepared By: %s</i></td>"
+         "</tr>"
+         "</table>"
+        ,header_.company_
+        ,wxDateTime::Today().FormatDate()
+        ,header_.prepared_by_
+        );
+
+    output_html(html_parser, horz_margin, *pos_y, page_.width_ / 2, 
title_html);
+
+    wxString const summary_html = wxString::Format
+        ("<table width=\"100%%\" cellspacing=\"0\" cellpadding=\"0\">"
+         // This extra top empty row works around a bug in wxHTML
+         // table positioning code: it uses the provided ordinate
+         // coordinate as a base line of the first table line and
+         // not as its top, as it ought to, so without this line
+         // the rectangle drawn below wouldn't contain the header.
+         "<tr>"
+         "<td align=\"center\" colspan=\"4\">&nbsp;</td>"
+         "</tr>"
+         "<tr>"
+         "<td align=\"center\" colspan=\"4\"><font size=\"+1\">Plan Details 
Summary</font></td>"
+         "</tr>"
+         "<tr>"
+         "<td align=\"right\"><b>Effective 
Data:&nbsp;&nbsp;</b></td><td>%s</td>"
+         "<td align=\"right\"><b>Plan Type(s):&nbsp;&nbsp;</b></td><td>%s</td>"
+         "</tr>"
+         "<tr>"
+         "<td align=\"right\"><b>Guarantee Issue 
Max:&nbsp;&nbsp;</b></td><td>%s</td>"
+         "<td align=\"right\"><b>Premium Mode:&nbsp;&nbsp;</b></td><td>%s</td>"
+         "</tr>"
+         "<tr>"
+         "<td align=\"right\"><b>Product(s):&nbsp;&nbsp;</b></td><td>%s</td>"
+         "<td align=\"right\"><b>Contract 
State:&nbsp;&nbsp;</b></td><td>%s</td>"
+         "</tr>"
+         "<tr>"
+         "<td align=\"right\"><b>Available 
Riders:&nbsp;&nbsp;</b></td><td>%s</td>"
+         "</tr>"
+         "<tr>"
+         "<td align=\"right\"><b>Number of 
eligibles:&nbsp;&nbsp;</b></td><td>%d</td>"
+         "</tr>"
+         "</table>"
+        ,wxDateTime::Today().FormatDate()
+        ,header_.plan_type_
+        ,header_.guarantee_issue_max_
+        ,header_.premium_mode_
+        ,header_.product_
+        ,header_.contract_state_
+        ,header_.available_riders_
+        ,row_num_
+        );
+
+    int const summary_height = output_html
+        (html_parser
+        ,horz_margin + page_.width_ / 2
+        ,*pos_y
+        ,page_.width_ / 2
+        ,summary_html
+        );
+
+    // wxHTML tables don't support "frame" attribute, so draw the border around
+    // the table manually.
+    pdf_dc.SetBrush(*wxTRANSPARENT_BRUSH);
+    pdf_dc.DrawRectangle
+        (horz_margin + page_.width_ / 2
+        ,*pos_y
+        ,page_.width_ / 2
+        ,summary_height
+        );
+
+    *pos_y += summary_height;
+}
+
+void group_quote_pdf_generator_wx::output_table_totals
+    (wxPdfDC& pdf_dc
+    ,wx_table_generator& table_gen
+    ,int* pos_y
+    )
+{
+    int& y = *pos_y;
+
+    table_gen.output_horz_separator(e_col_face_amount, e_col_max, y);
+    table_gen.output_vert_separator(e_col_face_amount, y);
+    table_gen.output_vert_separator(e_col_max, y);
+
+    y += table_gen.row_height();
+
+    table_gen.output_vert_separator(e_col_number, y);
+
+    int const cell_margin_x = pdf_dc.GetCharWidth();
+    int const y_text = y + pdf_dc.GetCharHeight();
+
+    // Render "Census" in bold.
+    wxDCFontChanger set_bold_font(pdf_dc, pdf_dc.GetFont().Bold());
+    pdf_dc.DrawLabel
+        ("Census"
+        ,table_gen.cell_rect(e_col_name, y_text).Deflate(cell_margin_x, 0)
+        ,wxALIGN_LEFT
+        );
+
+    // And the totals in bold italic: notice that there is no need to create
+    // another wxDCFontChanger here, the original font will be restored by the
+    // one just above anyhow.
+    pdf_dc.SetFont(pdf_dc.GetFont().Italic());
+
+    pdf_dc.DrawLabel
+        ("Totals:"
+        ,table_gen.cell_rect(e_col_salary, y_text).Deflate(cell_margin_x, 0)
+        ,wxALIGN_RIGHT
+        );
+
+    std::pair<int, oenum_format_style> const f2(2, oe_format_normal);
+    for(int col = e_col_face_amount; col < e_col_max; ++col)
+        {
+        wxRect const cell_rect = table_gen.cell_rect(col, y);
+            {
+            wxDCPenChanger set_transparent_pen(pdf_dc, *wxTRANSPARENT_PEN);
+            wxDCBrushChanger set_grey_brush(pdf_dc, *wxLIGHT_GREY_BRUSH);
+            pdf_dc.DrawRectangle(cell_rect);
+            }
+
+        wxRect const text_rect
+            (cell_rect.x + cell_margin_x
+            ,y_text
+            ,cell_rect.width - 2 * cell_margin_x
+            ,cell_rect.height
+            );
+
+        pdf_dc.DrawLabel
+            ("$"
+            ,text_rect
+            ,wxALIGN_LEFT
+            );
+        pdf_dc.DrawLabel
+            (ledger_format(totals_.total(col), f2)
+            ,text_rect
+            ,wxALIGN_RIGHT
+            );
+
+        table_gen.output_vert_separator(col, y);
+        }
+
+    table_gen.output_vert_separator(e_col_max, y);
+    table_gen.output_horz_separator(e_col_number, e_col_max, y);
+
+    y += table_gen.row_height();
+}
+
+void group_quote_pdf_generator_wx::output_footer
+    (wxPdfDC& pdf_dc
+    ,wxHtmlWinParser& html_parser
+    ,int* pos_y
+    ,enum_output_mode output_mode
+    )
+{
+    wxImage logo_image(load_image("logo.png"));
+    if(logo_image.IsOk())
+        {
+        switch(output_mode)
+            {
+            case e_output_normal:
+                pdf_dc.DrawBitmap(logo_image, horz_margin, *pos_y);
+                break;
+            case e_output_measure_only:
+                break;
+            }
+        *pos_y += logo_image.GetSize().y + vert_skip;
+        }
+
+    wxString const footer_html =
+        "<p>"
+        "...footnotes such as LedgerInvariant::MarketingNameFootnote..."
+        "</p>"
+        ;
+
+    *pos_y += output_html
+        (html_parser
+        ,horz_margin
+        ,*pos_y
+        ,page_.width_
+        ,footer_html
+        ,output_mode
+        );
+}
+
+volatile bool ensure_setup = group_quote_pdf_generator_wx::set_creator
+    (group_quote_pdf_generator_wx::do_create
+    );
+
+} // Unnamed namespace.


Property changes on: lmi/trunk/group_quote_pdf_gen_wx.cpp
___________________________________________________________________
Added: svn:keywords
   + Id

Modified: lmi/trunk/ihs_acctval.cpp
===================================================================
--- lmi/trunk/ihs_acctval.cpp   2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/ihs_acctval.cpp   2015-08-19 17:02:28 UTC (rev 6258)
@@ -1081,7 +1081,7 @@
 /// not inhibited here: all input is taken as deliberate, as an end
 /// user might reasonably wish to show the effect of other riders; if
 /// assertions as to input are to be made at all, then they should be
-/// made in the function that creates the group premium report.
+/// made in the function that creates the group premium quote report.
 
 double AccountValue::SuppositiveModalPremium
     (int         year

Modified: lmi/trunk/illustration_view.cpp
===================================================================
--- lmi/trunk/illustration_view.cpp     2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/illustration_view.cpp     2015-08-19 17:02:28 UTC (rev 6258)
@@ -91,6 +91,7 @@
     EVT_UPDATE_UI(XRCID("print_case_to_disk"   
),IllustrationView::UponUpdateInapplicable )
     EVT_UPDATE_UI(XRCID("print_spreadsheet"    
),IllustrationView::UponUpdateInapplicable )
     EVT_UPDATE_UI(XRCID("print_group_roster"   
),IllustrationView::UponUpdateInapplicable )
+    EVT_UPDATE_UI(XRCID("print_group_quote"    
),IllustrationView::UponUpdateInapplicable )
     EVT_UPDATE_UI(XRCID("paste_census"         
),IllustrationView::UponUpdateInapplicable )
     EVT_UPDATE_UI(XRCID("add_cell"             
),IllustrationView::UponUpdateInapplicable )
     EVT_UPDATE_UI(XRCID("delete_cells"         
),IllustrationView::UponUpdateInapplicable )

Modified: lmi/trunk/main_wx.cpp
===================================================================
--- lmi/trunk/main_wx.cpp       2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/main_wx.cpp       2015-08-19 17:02:28 UTC (rev 6258)
@@ -50,6 +50,7 @@
 #include <string>
 
 LMI_FORCE_LINKING_EX_SITU(file_command_wx)
+LMI_FORCE_LINKING_EX_SITU(group_quote_pdf_generator_wx)
 LMI_FORCE_LINKING_EX_SITU(progress_meter_wx)
 LMI_FORCE_LINKING_EX_SITU(system_command_wx)
 

Modified: lmi/trunk/main_wx_test.cpp
===================================================================
--- lmi/trunk/main_wx_test.cpp  2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/main_wx_test.cpp  2015-08-19 17:02:28 UTC (rev 6258)
@@ -65,6 +65,7 @@
 #include <vector>
 
 LMI_FORCE_LINKING_EX_SITU(file_command_wx)
+LMI_FORCE_LINKING_EX_SITU(group_quote_pdf_generator_wx)
 LMI_FORCE_LINKING_EX_SITU(progress_meter_wx)
 LMI_FORCE_LINKING_EX_SITU(system_command_wx)
 

Modified: lmi/trunk/mc_enum_type_enums.hpp
===================================================================
--- lmi/trunk/mc_enum_type_enums.hpp    2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/mc_enum_type_enums.hpp    2015-08-19 17:02:28 UTC (rev 6258)
@@ -54,6 +54,7 @@
     ,mce_emit_text_stream    =  512
     ,mce_emit_custom_0       = 1024
     ,mce_emit_custom_1       = 2048
+    ,mce_emit_group_quote    = 4096
     };
 
 /// Rounding styles.

Modified: lmi/trunk/mc_enum_types.cpp
===================================================================
--- lmi/trunk/mc_enum_types.cpp 2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/mc_enum_types.cpp 2015-08-19 17:02:28 UTC (rev 6258)
@@ -71,6 +71,7 @@
     ,mce_emit_text_stream
     ,mce_emit_custom_0
     ,mce_emit_custom_1
+    ,mce_emit_group_quote
     };
 extern char const*const emission_strings[] =
     {"emit_nothing"
@@ -86,9 +87,10 @@
     ,"emit_text_stream"
     ,"emit_custom_0"
     ,"emit_custom_1"
+    ,"emit_group_quote"
     };
 template<> struct mc_enum_key<mcenum_emission>
-  :public mc_enum_data<mcenum_emission, 13, emission_enums, emission_strings> 
{};
+  :public mc_enum_data<mcenum_emission, 14, emission_enums, emission_strings> 
{};
 template class mc_enum<mcenum_emission>;
 
 extern rounding_style const rounding_style_enums[] =

Modified: lmi/trunk/mc_enum_types_aux.cpp
===================================================================
--- lmi/trunk/mc_enum_types_aux.cpp     2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/mc_enum_types_aux.cpp     2015-08-19 17:02:28 UTC (rev 6258)
@@ -44,6 +44,7 @@
 {
     z.allow(z.ordinal("emit_pdf_to_printer"), false);
     z.allow(z.ordinal("emit_pdf_to_viewer" ), false);
+    z.allow(z.ordinal("emit_group_quote"   ), false);
 }
 
 /// Validate mc_n_gen_bases, mc_n_sep_bases, and mc_n_rate_periods.

Modified: lmi/trunk/mec_view.cpp
===================================================================
--- lmi/trunk/mec_view.cpp      2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/mec_view.cpp      2015-08-19 17:02:28 UTC (rev 6258)
@@ -84,6 +84,7 @@
     EVT_UPDATE_UI(XRCID("print_case_to_disk"   
),mec_view::UponUpdateInapplicable)
     EVT_UPDATE_UI(XRCID("print_spreadsheet"    
),mec_view::UponUpdateInapplicable)
     EVT_UPDATE_UI(XRCID("print_group_roster"   
),mec_view::UponUpdateInapplicable)
+    EVT_UPDATE_UI(XRCID("print_group_quote"    
),mec_view::UponUpdateInapplicable)
     EVT_UPDATE_UI(XRCID("paste_census"         
),mec_view::UponUpdateInapplicable)
     EVT_UPDATE_UI(XRCID("add_cell"             
),mec_view::UponUpdateInapplicable)
     EVT_UPDATE_UI(XRCID("delete_cells"         
),mec_view::UponUpdateInapplicable)

Modified: lmi/trunk/menus.xrc
===================================================================
--- lmi/trunk/menus.xrc 2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/menus.xrc 2015-08-19 17:02:28 UTC (rev 6258)
@@ -350,10 +350,17 @@
         <bitmap platform="win" stock_id="write-spreadsheet"/>
         <help>Run and print all cells to a spreadsheet file</help>
     </object>
-    <object class="wxMenuItem" name="print_group_roster">
-        <label>Print r_oster to spreadsheet\tCtrl-Shift-O</label>
-        <bitmap platform="win" stock_id="roster"/>
-        <help>Run and print group roster to a spreadsheet file</help>
+    <object class="wxMenu">
+        <label>Print r_oster</label>
+        <object class="wxMenuItem" name="print_group_roster">
+            <label>Print r_oster to spreadsheet\tCtrl-Shift-O</label>
+            <bitmap platform="win" stock_id="roster"/>
+            <help>Run and print group roster to a spreadsheet file</help>
+        </object>
+        <object class="wxMenuItem" name="print_group_quote">
+            <label>Print group premium _quote\tCtrl-Shift-Q</label>
+            <help>Run and print group premium quote to a PDF file</help>
+        </object>
     </object>
     <object class="separator"/>
     <object class="wxMenuItem" name="paste_census">

Modified: lmi/trunk/objects.make
===================================================================
--- lmi/trunk/objects.make      2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/objects.make      2015-08-19 17:02:28 UTC (rev 6258)
@@ -205,6 +205,7 @@
   file_command.o \
   getopt.o \
   global_settings.o \
+  group_quote_pdf_gen.o \
   group_values.o \
   illustrator.o \
   input.o \
@@ -317,6 +318,7 @@
   file_command_wx.o \
   gpt_document.o \
   gpt_view.o \
+  group_quote_pdf_gen_wx.o \
   icon_monger.o \
   illustration_document.o \
   illustration_view.o \
@@ -349,6 +351,7 @@
   transferor.o \
   view_ex.o \
   wx_checks.o \
+  wx_table_generator.o \
   wx_utility.o \
 
 lmi_wx_objects := \

Modified: lmi/trunk/skeleton.cpp
===================================================================
--- lmi/trunk/skeleton.cpp      2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/skeleton.cpp      2015-08-19 17:02:28 UTC (rev 6258)
@@ -159,6 +159,7 @@
     EVT_UPDATE_UI(XRCID("print_case_to_disk"         
),Skeleton::UponUpdateInapplicable           )
     EVT_UPDATE_UI(XRCID("print_spreadsheet"          
),Skeleton::UponUpdateInapplicable           )
     EVT_UPDATE_UI(XRCID("print_group_roster"         
),Skeleton::UponUpdateInapplicable           )
+    EVT_UPDATE_UI(XRCID("print_group_quote"          
),Skeleton::UponUpdateInapplicable           )
     EVT_UPDATE_UI(XRCID("paste_census"               
),Skeleton::UponUpdateInapplicable           )
     EVT_UPDATE_UI(XRCID("add_cell"                   
),Skeleton::UponUpdateInapplicable           )
     EVT_UPDATE_UI(XRCID("delete_cells"               
),Skeleton::UponUpdateInapplicable           )

Modified: lmi/trunk/toolbar.xrc
===================================================================
--- lmi/trunk/toolbar.xrc       2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/toolbar.xrc       2015-08-19 17:02:28 UTC (rev 6258)
@@ -115,10 +115,21 @@
         <bitmap stock_id="write-spreadsheet"/>
         <longhelp>Run and print all cells to a spreadsheet file</longhelp>
     </object>
-    <object class="tool" name="print_group_roster">
-        <tooltip>Print roster to spreadsheet</tooltip>
+    <object class="tool" name="print_group">
+        <tooltip>Print roster</tooltip>
         <bitmap stock_id="roster"/>
-        <longhelp>Run and print group roster to a spreadsheet file</longhelp>
+        <dropdown>
+            <object class="wxMenu">
+                <object class="wxMenuItem" name="print_group_roster">
+                    <label>Print r_oster to spreadsheet</label>
+                    <help>Run and print group roster to a spreadsheet 
file</help>
+                </object>
+                <object class="wxMenuItem" name="print_group_quote">
+                    <label>Print group premium _quote</label>
+                    <help>Run and print group premium quote to a PDF 
file</help>
+                </object>
+            </object>
+        </dropdown>
     </object>
     <object class="separator"/>
     <object class="tool" name="paste_census">

Modified: lmi/trunk/workhorse.make
===================================================================
--- lmi/trunk/workhorse.make    2015-08-18 18:40:15 UTC (rev 6257)
+++ lmi/trunk/workhorse.make    2015-08-19 17:02:28 UTC (rev 6258)
@@ -835,7 +835,7 @@
 # source files that are unrelated to wx, and that are therefore not
 # part of $(skeleton_objects).
 skeleton$(SHREXT): lmi_so_attributes := -DLMI_USE_SO
-skeleton$(SHREXT): EXTRA_LDFLAGS := $(wx_ldflags)
+skeleton$(SHREXT): EXTRA_LDFLAGS := $(wx_pdfdoc_ldflags) $(wx_ldflags)
 skeleton$(SHREXT): $(skeleton_objects) liblmi$(SHREXT) wx_new$(SHREXT)
 
 lmi_wx_shared$(EXEEXT): lmi_so_attributes := -DLMI_USE_SO

Added: lmi/trunk/wx_table_generator.cpp
===================================================================
--- lmi/trunk/wx_table_generator.cpp                            (rev 0)
+++ lmi/trunk/wx_table_generator.cpp    2015-08-19 17:02:28 UTC (rev 6258)
@@ -0,0 +1,352 @@
+// Generate a table using wxDC.
+//
+// Copyright (C) 2015 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// 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.
+//
+// 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 St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+// $Id$
+
+#ifdef __BORLANDC__
+#   include "pchfile.hpp"
+#   pragma hdrstop
+#endif // __BORLANDC__
+
+#include "wx_table_generator.hpp"
+
+#include "alert.hpp"
+#include "assert_lmi.hpp"
+
+#include <algorithm>                    // std::count()
+
+namespace
+{
+
+// Return the number of lines in a possibly multiline string.
+std::size_t count_lines(std::string const& s)
+{
+    return 1u + std::count(s.begin(), s.end(), '\n');
+}
+
+// Split a string into lines separated by new line characters.
+std::vector<std::string> split_in_lines(std::string const& s)
+{
+    // BOOST !! Unfortunately boost::split() can't be easily used with the
+    // current ancient version of the library (1.33), so we reimplement it
+    // here.
+    std::vector<std::string> lines;
+    std::string line;
+    for(std::string::const_iterator i = s.begin(); i != s.end(); ++i)
+        {
+        if('\n' == *i)
+            {
+            lines.push_back(line);
+            line.clear();
+            }
+        else
+            {
+            line += *i;
+            }
+        }
+    return lines;
+}
+
+// Increase the first argument to the second one if it's smaller.
+template<typename T>
+void increase_to_if_smaller(T& first, T second)
+{
+    if(first < second)
+        {
+        first = second;
+        }
+}
+
+} // Unnamed namespace.
+
+wx_table_generator::wx_table_generator
+    (wxDC& dc_
+    ,int left_margin
+    ,int total_width
+    )
+    :dc_(dc_)
+    ,left_margin_(left_margin)
+    ,total_width_(total_width)
+    ,char_height_(dc_.GetCharHeight())
+    ,row_height_((4 * char_height_ + 2) / 3) // Arbitrarily use 1.333 line 
spacing.
+    ,has_column_widths_(false)
+    ,max_header_lines_(1)
+{
+    // Set a pen with 0 width to get the thin lines and butt cap style for the
+    // different segments drawn in do_output_values() to seamlessly combine
+    // into a single line.
+    wxPen pen(*wxBLACK, 0);
+    pen.SetCap(wxCAP_ROUND);
+    dc_.SetPen(pen);
+}
+
+void wx_table_generator::add_column
+    (char const* header
+    ,char const* widest_text
+    )
+{
+    wxDCFontChanger set_header_font(dc_, get_header_font());
+
+    // Set width to the special value of 0 for the variable width columns.
+    int width = widest_text[0] ? dc_.GetTextExtent(widest_text).x : 0;
+
+    // Keep track of the maximal number of lines in a header as this determines
+    // the number of lines used for all of them.
+    increase_to_if_smaller(max_header_lines_, count_lines(header));
+
+    // Also increase the column width to be sufficiently wide to fit
+    // this header line (with roughly 1 em margins on either side) if it has
+    // fixed width.
+    if(0 != width)
+        {
+        increase_to_if_smaller
+            (width
+            ,dc_.GetMultiLineTextExtent(header).x + dc_.GetTextExtent("MM").x
+            );
+        }
+
+    columns_.push_back(column_info(header, width));
+}
+
+wxFont wx_table_generator::get_header_font() const
+{
+    return dc_.GetFont().Bold();
+}
+
+void wx_table_generator::do_output_horz_separator(int x1, int x2, int y)
+{
+    dc_.DrawLine(x1, y, x2, y);
+}
+
+void wx_table_generator::do_output_vert_separator(int x, int y1, int y2)
+{
+    // TODO: add a possibility to have a thick border between the columns.
+    dc_.DrawLine(x, y1, x, y2);
+}
+
+int wx_table_generator::do_get_cell_x(std::size_t column)
+{
+    do_compute_column_widths_if_necessary();
+
+    int x = left_margin_;
+    for(std::size_t col = 0; col < column; ++col)
+        {
+        x += columns_.at(col).width_;
+        }
+
+    return x;
+}
+
+wxRect wx_table_generator::cell_rect(std::size_t column, int y)
+{
+    LMI_ASSERT(column < columns_.size());
+
+    // Note: call do_get_cell_x() here and not from the wxRect ctor arguments
+    // list to ensure that the column width is initialized before it is used
+    // below.
+    int const x = do_get_cell_x(column);
+
+    return wxRect(x, y, columns_.at(column).width_, row_height_);
+}
+
+void wx_table_generator::do_compute_column_widths_if_necessary()
+{
+    if(has_column_widths_)
+        {
+        return;
+        }
+
+    int num_expand = 0;
+    int total_fixed = 0;
+
+    typedef std::vector<column_info>::const_iterator cici;
+    for(cici i = columns_.begin(); i != columns_.end(); ++i)
+        {
+        if(0 == i->width_)
+            {
+            num_expand++;
+            }
+        else
+            {
+            total_fixed += i->width_;
+            }
+        }
+
+    if(total_width_ < total_fixed)
+        {
+        warning()
+            << "Not enough space for all fixed columns "
+               "in group premium quote."
+            << LMI_FLUSH
+            ;
+        return;
+        }
+
+    if(num_expand)
+        {
+        int const per_expand
+            = (total_width_ - total_fixed + num_expand - 1)/num_expand;
+
+        typedef std::vector<column_info>::iterator cii;
+        for(cii i = columns_.begin(); i != columns_.end(); ++i)
+            {
+            if(0 == i->width_)
+                {
+                i->width_ = per_expand;
+                }
+            }
+        }
+
+    has_column_widths_ = true;
+}
+
+void wx_table_generator::do_output_values
+    (int& x
+    ,int& y
+    ,std::string const* values
+    )
+{
+    int const y_top = y;
+
+    int const y_text = y + char_height_;
+    y += row_height_;
+
+    do_output_vert_separator(x, y_top, y);
+
+    std::size_t const num_columns = columns_.size();
+    for(std::size_t col = 0; col < num_columns; ++col)
+        {
+        int const width = columns_.at(col).width_;
+
+        std::string const& s = values[col];
+        if(!s.empty())
+            {
+            int x_text = x;
+            if(columns_.at(col).is_centered_)
+                {
+                // Centre the text for the columns configured to do it.
+                x_text += (width - dc_.GetTextExtent(s).x) / 2;
+                }
+            else
+                {
+                // Otherwise just offset it by ~1 em.
+                x_text += dc_.GetTextExtent("M").x;
+                }
+
+            dc_.DrawText(s, x_text, y_text);
+            }
+        x += width;
+        do_output_vert_separator(x, y_top, y);
+        }
+}
+
+void wx_table_generator::output_vert_separator
+    (std::size_t before_column
+    ,int y
+    )
+{
+    LMI_ASSERT(before_column <= columns_.size());
+
+    do_output_vert_separator
+        (do_get_cell_x(before_column)
+        ,y
+        ,y + row_height_
+        );
+}
+
+void wx_table_generator::output_horz_separator
+    (std::size_t begin_column
+    ,std::size_t end_column
+    ,int y
+    )
+{
+    LMI_ASSERT(begin_column < end_column);
+    LMI_ASSERT(end_column <= columns_.size());
+
+    do_compute_column_widths_if_necessary();
+
+    int const x1 = do_get_cell_x(begin_column);
+
+    int x2 = x1;
+    for(std::size_t col = begin_column; col < end_column; ++col)
+        {
+        x2 += columns_.at(col).width_;
+        }
+
+    do_output_horz_separator(x1, x2, y);
+}
+
+void wx_table_generator::output_header(int* pos_y)
+{
+    do_compute_column_widths_if_necessary();
+
+    wxDCFontChanger set_header_font(dc_, get_header_font());
+
+    // Split headers in single lines and fill up the entire columns*lines 2D
+    // matrix, using empty strings for the headers with less than the maximal
+    // number of lines.
+    std::size_t const num_columns = columns_.size();
+    std::vector<std::string> headers_by_line(max_header_lines_ * num_columns);
+    for(std::size_t col = 0; col < num_columns; ++col)
+        {
+        column_info const& ci = columns_.at(col);
+        std::vector<std::string> const lines(split_in_lines(ci.header_));
+
+        // Fill the elements from the bottom line to the top one, so that a
+        // single line header is shown on the last line, as per the spec.
+        std::size_t const first_line = max_header_lines_ - lines.size();
+        for(std::size_t line = 0; line < lines.size(); ++line)
+            {
+            headers_by_line.at
+                ((first_line + line) * num_columns + col
+                ) = lines.at(line);
+            }
+        }
+
+    // And output all lines of all column headers.
+    int y_top = *pos_y;
+    int x = 0;
+    for(std::size_t line = 0; line < max_header_lines_; ++line)
+        {
+        x = left_margin_;
+        do_output_values
+            (x
+            ,*pos_y
+            ,&headers_by_line.at(line * num_columns)
+            );
+        }
+
+    // Finally draw the separators above and (a double one) below them.
+    do_output_horz_separator(left_margin_, x,  y_top    );
+    do_output_horz_separator(left_margin_, x, *pos_y - 1);
+    do_output_horz_separator(left_margin_, x, *pos_y    );
+}
+
+void wx_table_generator::output_row
+    (int* pos_y
+    ,std::string const* values
+    )
+{
+    int x = left_margin_;
+    do_output_values(x, *pos_y, values);
+
+    do_output_horz_separator(left_margin_, x, *pos_y);
+}


Property changes on: lmi/trunk/wx_table_generator.cpp
___________________________________________________________________
Added: svn:keywords
   + Id

Added: lmi/trunk/wx_table_generator.hpp
===================================================================
--- lmi/trunk/wx_table_generator.hpp                            (rev 0)
+++ lmi/trunk/wx_table_generator.hpp    2015-08-19 17:02:28 UTC (rev 6258)
@@ -0,0 +1,142 @@
+// Generate a table using wxDC.
+//
+// Copyright (C) 2015 Gregory W. Chicares.
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License version 2 as
+// published by the Free Software Foundation.
+//
+// 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.
+//
+// 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 St, Fifth Floor, Boston, MA 02110-1301, USA
+//
+// http://savannah.nongnu.org/projects/lmi
+// email: <address@hidden>
+// snail: Chicares, 186 Belle Woods Drive, Glastonbury CT 06033, USA
+
+// $Id$
+
+#ifndef wx_table_generator_hpp
+#define wx_table_generator_hpp
+
+#include "config.hpp"
+
+#include <wx/dc.h>
+#include <wx/font.h>
+
+#include <cstddef>                      // std::size_t
+#include <string>
+#include <vector>
+
+/// Simplifies outputting tabular data on wxDC.
+///
+/// To create a table, columns must be initialized first by calling
+/// set_column() for each of them once. After this, output_header() and
+/// output_row() can be called reusing the same pos_y argument which contains
+/// the coordinate of the top of the header or row to output and is updated to
+/// correspond to the value for the next row by these functions.
+
+class wx_table_generator
+{
+  public:
+    // The life time of the specified wxDC must be greater than the life time
+    // of this object itself and nothing should be using it while this object
+    // does (as it changes its attributes).
+    //
+    // The table has the given total width and starts at the left margin.
+    wx_table_generator(wxDC& dc, int left_margin, int total_width);
+
+    // Each column must either have a fixed width, specified as the width of
+    // the longest text that may appear in this column, or be expandable
+    // meaning that the rest of the page width is allocated to it which will be
+    // the case if widest_text is empty (but it shouldn't be null).
+    // Notice that column headers may be multiline strings.
+    void add_column(char const* header, char const* widest_text);
+
+    // Render the headers at the given position and update it.
+    void output_header(int* pos_y);
+
+    // Render a row with the given values at the given position and update it.
+    // The values here can be single-line only and there must be exactly the
+    // same number of them as the number of columns.
+    void output_row(int* pos_y, std::string const* values);
+
+    // Return the height of a single table row.
+    int row_height() const {return row_height_;}
+
+    // Return the rectangle containing the cell area.
+    wxRect cell_rect(std::size_t column, int y);
+
+    // Output a horizontal separator line across the specified columns,
+    // using the usual C++ close/open interval convention.
+    void output_horz_separator
+        (std::size_t begin_column
+        ,std::size_t end_column
+        ,int         y
+        );
+
+    // Output a vertical separator line before the given column. Notice that
+    // the column index here may be equal to the number of columns in order to
+    // output a separator after the last column.
+    void output_vert_separator(std::size_t before_column, int y);
+
+  private:
+    // Return the font used for the headers.
+    wxFont get_header_font() const;
+
+    int do_get_cell_x(std::size_t column);
+
+    void do_output_horz_separator(int x1, int x2, int y );
+    void do_output_vert_separator(int x , int y1, int y2);
+
+    void do_compute_column_widths_if_necessary();
+
+    void do_output_values
+        (int&               pos_x
+        ,int&               pos_y
+        ,std::string const* values
+        );
+
+    wxDC& dc_;
+
+    int left_margin_;
+    int total_width_;
+
+    // These values could be recomputed, but cache them for performance.
+    int const char_height_;
+    int const row_height_;
+
+    struct column_info
+    {
+        column_info(char const* header, int width)
+            :header_(header)
+            ,width_(width)
+            // Fixed width columns are centered by default, variable width ones
+            // are not as long strings look better with the default left
+            // alignment.
+            ,is_centered_(width != 0)
+            {
+            }
+
+        std::string header_;
+        int width_;
+        bool is_centered_;
+    };
+
+    std::vector<column_info> columns_;
+
+    // Initially false, set to true after do_compute_column_widths() call
+    // meaning that all column_info::width_ values are now valid.
+    bool has_column_widths_;
+
+    // Maximal number of lines in any column header, initially 1 but can be
+    // higher if multiline headers are used.
+    std::size_t max_header_lines_;
+};
+
+#endif // wx_table_generator_hpp


Property changes on: lmi/trunk/wx_table_generator.hpp
___________________________________________________________________
Added: svn:keywords
   + Id




reply via email to

[Prev in Thread] Current Thread [Next in Thread]