OSDN Git Service

Implement preliminary GUI mode data sheet compiler.
authorKeith Marshall <keithmarshall@users.sourceforge.net>
Thu, 11 Oct 2012 10:02:17 +0000 (11:02 +0100)
committerKeith Marshall <keithmarshall@users.sourceforge.net>
Thu, 11 Oct 2012 10:02:17 +0000 (11:02 +0100)
ChangeLog
Makefile.in
src/guidata.rc
src/guimain.h
src/guixmld.cpp
src/pkgdata.cpp [new file with mode: 0644]
src/pkgdata.h [new file with mode: 0644]
src/pkgshow.cpp
src/pkgview.cpp

index c6bd290..f7b4b67 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,37 @@
+2012-10-11  Keith Marshall  <keithmarshall@users.sourceforge.net>
+
+       Implement preliminary GUI mode data sheet compiler.
+
+       * src/guimain.h (DataSheetMaker): New class; forward declare it.
+       (AppWindowMaker::InitPackageTabControl): New private method; declare it.
+       (AppWindowMaker::DataSheet, AppWindowMaker::PackageTabControl):
+       (AppWindowMaker::TabDataPane, AppWindowMaker::PackageTabPane): New
+       private data members; declare them.
+       (ID_SASH_WINDOW_PANE_CLASS): New string resource identifier; define it.
+       (ID_PACKAGE_DATASHEET, ID_PACKAGE_TABPANE): New window identifiers;
+       define them.
+
+       * src/guidata.rc (ID_SASH_WINDOW_PANE_CLASS): Specify string resource.
+
+       * src/pkgview.cpp (AppWindowMaker::OnSize): Add handler for...
+       (ID_PACKAGE_TABPANE, ID_PACKAGE_TABCONTROL, ID_PACKAGE_DATASHHET):
+       ...this group of sash pane child window entities.
+       (AppWindowMaker::OnNotify): Comment out disused stub implementation.
+
+       * src/pkgshow.cpp (pkgUTF8Parser): Factor out class declaration...
+       (UTF32_MAX, UTF32_OVERFLOW, UTF32_INVALID): ...these definitions...
+       (utf32_t): ...and this typedef; relocate to, and #include...
+       * src/pkgdata.h: ...this new header file.
+
+       * src/pkgdata.cpp (DataSheetMaker): New class; declare and implement it.
+       (AppWindowMaker::InitPackageTabControl): Implement it.
+       (AppWindowMaker::OnNotify): Reimplement it.
+
+       * Makefile.in (GUIMAIN_OBJECTS): Add pkgdata.OBJEXT
+
+       * src/guixmld.cpp (AppWindowMaker::Invoked): Add call to invoke
+       AppWindowMaker::InitPackageTabControl()
+
 2012-10-09  Keith Marshall  <keithmarshall@users.sourceforge.net>
 
        Remove a grossly heuristic list view hack.
index 146f475..9e84a12 100644 (file)
@@ -91,7 +91,7 @@ CLI_EXE_OBJECTS  =   \
 
 GUIMAIN_OBJECTS  =   \
    guimain.$(OBJEXT) guidata.$(OBJEXT) guixmld.$(OBJEXT) guidmh.$(OBJEXT) \
-   approot.$(OBJEXT) pkgview.$(OBJEXT) pkglist.$(OBJEXT)
+   approot.$(OBJEXT) pkgview.$(OBJEXT) pkglist.$(OBJEXT) pkgdata.$(OBJEXT)
 
 GUIMAIN_LIBS = -lwtklite -lcomctl32
 
index d5ebf5a..49b5f5b 100644 (file)
@@ -59,6 +59,7 @@ STRINGTABLE           DISCARDABLE
 BEGIN
   ID_MAIN_WINDOW_CLASS                 "mingw-get-gui"
   ID_MAIN_WINDOW_CAPTION               "MinGW Installation Manager"
+  ID_SASH_WINDOW_PANE_CLASS            "mingw-get-sash-pane"
   ID_FONT_PREF                         "Verdana"
 END
 
index ab9076b..275e7c3 100644 (file)
@@ -35,6 +35,7 @@
 #define ID_MAIN_WINDOW_CAPTION          102
 #define ID_FONT_PREF                    103
 
+#define ID_SASH_WINDOW_PANE_CLASS       104
 #define ID_HORIZONTAL_SASH              105
 #define ID_VERTICAL_SASH                106
 
@@ -43,6 +44,8 @@
 #define ID_PACKAGE_TREEVIEW             201
 #define ID_PACKAGE_LISTVIEW             202
 #define ID_PACKAGE_TABCONTROL           203
+#define ID_PACKAGE_DATASHEET            204
+#define ID_PACKAGE_TABPANE              205
 
 #define IDM_MAIN_MENU                   300
 #define IDM_REPO_UPDATE                 301
@@ -96,6 +99,7 @@
 #include <commctrl.h>
 
 class pkgXmlDocument;
+class DataSheetMaker;
 
 class AppWindowMaker;
 inline AppWindowMaker *GetAppWindow( HWND lookup )
@@ -137,6 +141,11 @@ class AppWindowMaker: public WTK::MainWindowMaker
     void InitPackageListView( void );
     void ClearPackageList( void ){ ListView_DeleteAllItems( PackageListView ); }
     void UpdatePackageList( void );
+
+    DataSheetMaker *DataSheet;
+    WTK::ChildWindowMaker *TabDataPane;
+    HWND PackageTabControl, PackageTabPane;
+    void InitPackageTabControl();
 };
 
 #endif /* ! RC_INVOKED */
index 1fa4a81..08bcf0b 100644 (file)
@@ -50,6 +50,11 @@ int AppWindowMaker::Invoked( void )
   LoadPackageData();
   InitPackageListView();
 
+  /* Initialise the data-sheet tab control, displaying the default
+   * "no package selected" message.
+   */
+  InitPackageTabControl();
+
   /* Force a layout adjustment, to ensure that the displayed
    * data controls are correctly populated.
    */
diff --git a/src/pkgdata.cpp b/src/pkgdata.cpp
new file mode 100644 (file)
index 0000000..05e70e8
--- /dev/null
@@ -0,0 +1,747 @@
+/*
+ * pkgdata.cpp
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2012, MinGW Project
+ *
+ *
+ * Implementation of the classes and methods required to support the
+ * GUI tabbed view of package information "data sheets".
+ *
+ *
+ * This is free software.  Permission is granted to copy, modify and
+ * redistribute this software, under the provisions of the GNU General
+ * Public License, Version 3, (or, at your option, any later version),
+ * as published by the Free Software Foundation; see the file COPYING
+ * for licensing details.
+ *
+ * Note, in particular, that this software is provided "as is", in the
+ * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
+ * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
+ * PARTICULAR PURPOSE.  Under no circumstances will the author, or the
+ * MinGW Project, accept liability for any damages, however caused,
+ * arising from the use of this software.
+ *
+ */
+#include <stdlib.h>
+#include <string.h>
+
+#include "dmh.h"
+
+#include "guimain.h"
+#include "pkgbase.h"
+#include "pkgdata.h"
+#include "pkgkeys.h"
+#include "pkglist.h"
+
+using WTK::StringResource;
+using WTK::WindowClassMaker;
+using WTK::ChildWindowMaker;
+
+/* Margin settings, controlling the positioning of the
+ * active viewport within the data sheet display pane.
+ */
+#define TOP_MARGIN             5
+#define PARAGRAPH_MARGIN       5
+#define LEFT_MARGIN            8
+#define RIGHT_MARGIN           8
+#define BOTTOM_MARGIN          5
+
+class pkgTroffLayoutEngine: public pkgUTF8Parser
+{
+  /* A privately implemented class, supporting a simplified troff
+   * style layout for a UTF-8 text stream, within a scrolling GUI
+   * display pane.
+   */
+  public:
+    pkgTroffLayoutEngine( const char *input, long displacement ):
+      pkgUTF8Parser( input ), curr( this ), offset( displacement ){}
+    bool WriteLn( HDC, RECT * );
+
+  private:
+    inline bool IsReady();
+    pkgTroffLayoutEngine *curr;
+    long offset;
+};
+
+inline bool pkgTroffLayoutEngine::IsReady()
+{
+  /* Private helper method, used to position the input stream to
+   * the next parseable token, if any, for processing by WriteLn.
+   */
+  while( (curr != NULL) && ((curr->length == 0) || (curr->text == NULL)) )
+    curr = (pkgTroffLayoutEngine *)(curr->next);
+  return (curr != NULL);
+}
+
+bool pkgTroffLayoutEngine::WriteLn( HDC canvas, RECT *bounds )
+{
+  /* Method to extract a single line of text from the UTF-8 stream,
+   * (if any is available for processing), format it as appropriate
+   * for display, and write it into the display pane.
+   */
+  if( IsReady() )
+  {
+    /* Initialise a buffer, in which to compile the formatted text
+     * record for display; establish and initialise the counters for
+     * controlling the formatting process.
+     */
+    wchar_t linebuf[1 + strlen( curr->text )];
+    long curr_width, new_width = 0, max_width = bounds->right - bounds->left;
+    int filled, extent = 0, fold = 0;
+
+    /* Establish default tracking and justification settings.
+     */
+    SetTextCharacterExtra( canvas, 0 );
+    SetTextJustification( canvas, 0, 0 );
+
+    /* Copy text from the input stream, to fill the transfer buffer
+     * up to the maximum permitted output line length, or until the
+     * input stream has been exhausted.
+     */
+    SIZE span;
+    do { if( curr->length > 0 )
+        { /* There is at least one more word of input text to copy,
+           * and there may be sufficient output space to accommodate
+           * it; record the space filled so far, up to the end of the
+           * preceding word, (if any)...
+           */
+          filled = extent;
+          curr_width = new_width;
+          if( extent > 0 )
+          {
+            /* ...and, when there was a preceding word, add white
+             * space and record a potential line folding point.
+             */
+            linebuf[extent++] = L'\x20';
+            ++fold;
+          }
+
+          /* Append one word, copied from the input stream to the
+           * output line buffer.
+           */
+          const char *mark = curr->text;
+          for( int i = 0; i < curr->length; ++i )
+            linebuf[extent++] = GetCodePoint( mark = ScanBuffer( mark ) );
+
+          /* Check the effective output line length which would be
+           * required to accommodate the extended output record...
+           */
+          if( GetTextExtentPoint32W( canvas, linebuf, extent, &span ) )
+          {
+            /* ...and while it still fits within the maximum width
+             * of the display pane...
+             */
+            if( max_width >= (new_width = span.cx) )
+              /*
+               * ...accept the current input word, and move on to
+               * see if we can accommodate another.
+               */
+              curr = (pkgTroffLayoutEngine *)(curr->next);
+          }
+          else
+            /* In the event of any error in evaluating the output
+             * line length, reject any remaining input.
+             */
+            curr = NULL;
+        }
+        else
+          /* We found a zero-length entity in the input stream;
+           * ignore it, and move on to the next, if any.
+           */
+          curr = (pkgTroffLayoutEngine *)(curr->next);
+
+        /* Continue the cycle, unless we have exhausted the input
+         * stream, or we have run out of available output space.
+         */
+       } while( (curr != NULL) && (max_width > new_width) );
+
+    /* When we've collected a complete line of output text...
+     */
+    if(  (bounds->top >= (TOP_MARGIN + offset))
+    &&  ((bounds->bottom + offset) >= (bounds->top + span.cy))  )
+    {
+      /* ...and when it is to be positioned vertically within the
+       * bounds of the active viewport...
+       */
+      if( bounds->top < (TOP_MARGIN + offset + span.cy) )
+       /*
+        * ...when it is the topmost visible line, ensure that it
+        * is vertically aligned flush with the top margin.
+        */
+       bounds->top = TOP_MARGIN + offset;
+
+      /* Check if the output line collection loop, above, ended
+       * on an attempt to over-fill the buffer...
+       */
+      if( max_width >= new_width )
+       /*
+        * ...but when it did not, handle it as a partially filled
+        * line, which is thus exempt from right justification.
+        */
+       filled = extent;
+
+      /* When the output line is over-filled, then we will attempt
+       * to fold it at the last counted fold point, and then insert
+       * padding space at each remaining internal fold point, so as
+       * to achieve flush left/right justification; (note that we
+       * decrement the fold count here, because the point at which
+       * we fold the line has been included in the count, but we
+       * don't want to add padding space at the right margin).
+       */
+      else if( --fold > 0 )
+      {
+       /* To adjust the output line, we first compute the number
+        * of padding PIXELS required, then...
+        */
+       long padding;
+       if( (padding = max_width - curr_width) >= filled )
+       {
+         /* ...in the event that this is no fewer than the number
+          * of physical GLYPHS to be output, we adjust the tracking
+          * to accommodate as many padding pixels as possible, with
+          * ONE additional inter-glyph tracking pixel per glyph...
+          */
+         SetTextCharacterExtra( canvas, 1 );
+         if( GetTextExtentPoint32W( canvas, linebuf, filled, &span ) )
+           /*
+            * ...and then, we recompute the number of additional
+            * inter-word padding pixels, if any, which are still
+            * required.
+            */
+           padding = max_width - span.cx;
+
+         /* In the event that adjustment of tracking fails, we
+          * must reset it, because the padding count remains as
+          * computed for default tracking.
+          */
+         else
+           SetTextCharacterExtra( canvas, 0 );
+       }
+       /* Now, distribute the padding pixels among the remaining
+        * inter-word (fold) spaces within the output line...
+        */
+       SetTextJustification( canvas, padding, fold );
+      }
+      /* ...and write the output line at the designated position
+       * within the display viewport.
+       */
+      TextOutW( canvas, bounds->left, bounds->top - offset, linebuf, filled );
+    }
+    /* Finally, adjust the top boundary of the viewport, to indicate
+     * where the NEXT output line, if any, is to be positioned, and
+     * return TRUE, to indicate that an output line was processed.
+     */
+    bounds->top += span.cy;
+    return true;
+  }
+  /* If we get to here, then there was nothing in the input stream to
+   * be processed; return FALSE, to indicate this.
+   */
+  return false;
+}
+
+class DataSheetMaker: public ChildWindowMaker
+{
+  /* Specialised variant of the standard child window class, augmented
+   * to provide the custom methods for formatting and displaying package
+   * data sheet content within the tabbed data display pane.
+   *
+   * FIXME: we may eventually need to make this class externally visible,
+   * but for now we implement it as a locally declared class.
+   */
+  public:
+    DataSheetMaker( HINSTANCE inst ): ChildWindowMaker( inst ),
+      PackageRef( NULL ), DataClass( NULL ){}
+    virtual void DisplayData( HWND, HWND );
+
+  private:
+    virtual long OnCreate();
+    virtual long OnVerticalScroll( int, int, HWND );
+    virtual long OnPaint();
+
+    HWND PackageRef, DataClass;
+    HDC canvas; RECT bounding_box; 
+    HFONT NormalFont, BoldFont;
+
+    long offset; char *desc;
+    inline void DisplayDescription( pkgXmlNode * );
+    void ComposeDescription( pkgXmlNode *, pkgXmlNode * );
+    inline void FormatText( HDC, const char * );
+    long LineSpacing;
+};
+
+enum
+{ /* Tab identifiers for the available data sheet collection.
+   */
+  PKG_DATASHEET_GENERAL = 0,
+  PKG_DATASHEET_DESCRIPTION,
+  PKG_DATASHEET_DEPENDENCIES,
+  PKG_DATASHEET_INSTALLED_FILES,
+  PKG_DATASHEET_VERSIONS
+};
+
+long DataSheetMaker::OnCreate()
+{
+  /* Method called when creating a data sheet window; initialise font
+   * preferences and line spacing for any instance of a DataSheetMaker
+   * object.
+   *
+   * Initially, we match the font properties to the default GUI font...
+   */
+  LOGFONT font_info;
+  HFONT font = (HFONT)(GetStockObject( DEFAULT_GUI_FONT ));
+  GetObject( BoldFont = NormalFont = font, sizeof( LOGFONT ), &font_info );
+
+  /* ...then, we substitute the preferred type face.
+   */
+  strcpy( (char *)(&(font_info.lfFaceName)), "Verdana" );
+  if( (font = CreateFontIndirect( &font_info )) != NULL )
+  {
+    /* On successfully creating the preferred font, we may discard
+     * the original default font object, and assign our preference
+     * as both the normal and bold working font...
+     */
+    DeleteObject( NormalFont );
+    BoldFont = NormalFont = font;
+
+    /* ...before adjusting the weight for the bold variant...
+     */
+    font_info.lfWeight = FW_BOLD;
+    if( (font = CreateFontIndirect( &font_info )) != NULL )
+    {
+      /* ...and reassigning when successful.
+       */
+      BoldFont = font;
+    }
+  }
+
+  /* Finally, we determine the line spacing (in pixels) for a line
+   * of text, in the preferred normal font, within the device context
+   * for the data sheet window.
+   */
+  SIZE span;
+  HDC canvas = GetDC( AppWindow );
+  SelectObject( canvas, NormalFont );
+  LineSpacing = GetTextExtentPoint32A( canvas, "Height", 6, &span ) ? span.cy : 13;
+  ReleaseDC( AppWindow, canvas );
+  
+  return offset = 0;
+}
+
+void DataSheetMaker::DisplayData( HWND tab, HWND package )
+{
+  /* Method to force a refresh of the data sheet display pane.
+   */
+  PackageRef = package; DataClass = tab;
+  InvalidateRect( AppWindow, NULL, TRUE );
+  UpdateWindow( AppWindow );
+}
+
+inline void DataSheetMaker::FormatText( HDC canvas, const char *text )
+{
+  /* Helper method to transfer text to the display device, formatting
+   * it to fill as many lines of the viewing window as may be required,
+   * justifying for flush margins at both left and right.
+   */
+  pkgTroffLayoutEngine page( text, offset );
+  while( page.WriteLn( canvas, &bounding_box ) )
+    ;
+}
+
+inline void DataSheetMaker::DisplayDescription( pkgXmlNode *ref )
+{
+  /* A convenience method to invoke the recursive retrieval of any
+   * package description, without requiring a look-up of the address
+   * of the XML document root at every level of recursion.
+   */
+  ComposeDescription( ref, ref->GetDocumentRoot() );
+}
+
+void DataSheetMaker::ComposeDescription( pkgXmlNode *ref, pkgXmlNode *root )
+{
+  /* Recursive method to compile a package description, from text
+   * fragments retrieved from the XML specification document, and
+   * present it in a data sheet window.
+   */
+  if( ref != root )
+  {
+    /* Recursively walk the XML hierarchy, until we reach the
+     * document root...
+     */
+    ComposeDescription( ref->GetParent(), root );
+
+    /* ...then unwind the recursion, selecting "description"
+     * elements, (if any), at each level...
+     */
+    if( (root = ref->FindFirstAssociate( description_key )) != NULL )
+    {
+      /* ...formatting each, paragraph by paragraph, for display
+       * within the viewport bounding box of the data sheet...
+       */
+      do { if( (ref = root->FindFirstAssociate( paragraph_key )) != NULL )
+            do { if( bounding_box.top > (TOP_MARGIN + offset) )
+                   /*
+                    * When this is not the top-most visible
+                    * paragraph, within the viewport, displace
+                    * it downwards by one paragraph margin from
+                    * its predecessor...
+                    */
+                   bounding_box.top += PARAGRAPH_MARGIN;
+
+                 /* ...before laying out the visible text of
+                  * this paragraph, (if any).
+                  */
+                 FormatText( canvas, ref->GetText() );
+
+                 /* Cycle, to process any further paragraphs
+                  * which are included within the current
+                  * description block...
+                  */
+               } while( (ref = ref->FindNextAssociate( paragraph_key )) != NULL );
+
+          /* ...and ultimately, for any additional description blocks
+           * which may have been specified within the current XML element,
+           * at the current hierarchical nesting level.
+           */
+        } while( (root = root->FindNextAssociate( description_key )) != NULL );
+    }
+  }
+}
+
+long DataSheetMaker::OnPaint()
+{
+  /* Handler for WM_PAINT message messages, sent to any window
+   * ascribed to the DataSheetMaker class.
+   */
+  PAINTSTRUCT content;
+  canvas = BeginPaint( AppWindow, &content );
+  HFONT original_font = (HFONT)(SelectObject( canvas, NormalFont ));
+
+  /* Establish a viewport, with a suitable margin, within the
+   * bounding rectangle of the window.
+   */
+  GetClientRect( AppWindow, &bounding_box );
+  bounding_box.left += LEFT_MARGIN; bounding_box.right -= RIGHT_MARGIN;
+  bounding_box.top += TOP_MARGIN; bounding_box.bottom -= BOTTOM_MARGIN;
+
+  /* Provide bindings for a vertical scrollbar...
+   */
+  SCROLLINFO scrollbar;
+  if( offset == 0 )
+  {
+    /* ...and prepare to initialise it, when we redraw
+     * the data sheet from the top.
+     */
+    scrollbar.cbSize = sizeof( scrollbar );
+    scrollbar.fMask = SIF_POS | SIF_RANGE | SIF_PAGE;
+    scrollbar.nPos = scrollbar.nMin = bounding_box.top;
+    scrollbar.nPage = 1 + bounding_box.bottom - bounding_box.top;
+    scrollbar.nMax = bounding_box.bottom;
+  }
+
+  /* Identify the package, as selected in the package list window,
+   * for which the data sheet is to be compiled.
+   */
+  LVITEM lookup;
+  lookup.iItem = (PackageRef != NULL)
+    ? ListView_GetNextItem( PackageRef, (WPARAM)(-1), LVIS_SELECTED )
+    : -1;
+
+  if( lookup.iItem >= 0 )
+  {
+    /* There is an active package selection; identify the selected
+     * data sheet tab, if any...
+     */
+    int tab = ( DataClass != NULL ) ? TabCtrl_GetCurSel( DataClass )
+      /*
+       * ...otherwise default to the package description.
+       */
+      : PKG_DATASHEET_DESCRIPTION;
+
+    /* Retrieve the package title from the list view; assign it as
+     * a bold face heading in the data sheet view...
+     */
+    char desc[256];
+    SelectObject( canvas, BoldFont );
+    ListView_GetItemText( PackageRef, lookup.iItem, 5, desc, sizeof( desc ) );
+    FormatText( canvas, desc );
+    if( offset > 0 )
+    {
+      /* ...adjusting as appropriate, when the heading is scrolled
+       * out of the viewport.
+       */
+      if( (offset -= (bounding_box.top - TOP_MARGIN)) < 0 )
+       offset = 0;
+      bounding_box.top = TOP_MARGIN;
+    }
+
+    /* Revert to normal typeface, in preparation for compilation
+     * of the selected data sheet.
+     */
+    SelectObject( canvas, NormalFont );
+    switch( tab )
+    {
+      case PKG_DATASHEET_DESCRIPTION:
+       /* This represents the package description, provided by
+        * the package maintainer, within the XML specification.
+        */
+       lookup.iSubItem = 0;
+       lookup.mask = LVIF_PARAM;
+       ListView_GetItem( PackageRef, &lookup );
+       DisplayDescription( (pkgXmlNode *)(lookup.lParam) );
+       break;
+
+      default:
+       /* Handle requests for data sheets for which we have yet
+        * to provide a compiling routine.
+        */
+       bounding_box.top += TOP_MARGIN;
+       FormatText( canvas,
+           "FIXME:data sheet unavailable; a compiler for this "
+           "data category has yet to be implemented."
+         );
+    }
+  }
+  else
+  { /* There is no active package selection; advise accordingly.
+     */
+    bounding_box.top += TOP_MARGIN << 1;
+    FormatText( canvas,
+       "No package selected."
+      );
+    bounding_box.top += PARAGRAPH_MARGIN << 1;
+    FormatText( canvas,
+       "Please select a package from the list above, "
+       "to view related data."
+      );
+  }
+
+  /* When redrawing the data sheet window from the top...
+   */
+  if( offset == 0 )
+  {
+    /* ...adjust the scrolling range to accommodate the full extent
+     * of the data sheet text, and initialise the scrollbar control.
+     */
+    if( bounding_box.top > bounding_box.bottom )
+      scrollbar.nMax = bounding_box.top;
+    SetScrollInfo( AppWindow, SB_VERT, &scrollbar, TRUE );
+  }
+
+  /* Finally, restore the original (default) font assignment
+   * for the data sheet window, complete the redraw action, and
+   * we are done.
+   */
+  SelectObject( canvas, original_font );
+  EndPaint( AppWindow, &content );
+  return EXIT_SUCCESS;
+}
+
+long DataSheetMaker::OnVerticalScroll( int req, int pos, HWND ctrl )
+{
+  /* Handler for events signalled by the vertical scrollbar control,
+   * (if any), in any window ascribed to the DataSheetMaker class.
+   */
+  SCROLLINFO scrollbar;
+  scrollbar.fMask = SIF_ALL;
+  scrollbar.cbSize = sizeof( scrollbar );
+  GetScrollInfo( AppWindow, SB_VERT, &scrollbar );
+
+  /* Save the original "thumb" position.
+   */
+  long origin = scrollbar.nPos;
+  switch( req )
+  {
+    /* Identify, and process the event message.
+     */
+    case SB_LINEUP:
+      /* User clicked the "scroll-up" button; move the
+       * "thumb" up by a distance equivalent to the height
+       * of a single line of text.
+       */
+      scrollbar.nPos -= LineSpacing;
+      break;
+
+    case SB_LINEDOWN:
+      /* Similarly, for a click on the "scroll-down" button,
+       * move the "thumb" down by one line height.
+       */
+      scrollbar.nPos += LineSpacing;
+      break;
+
+    case SB_PAGEUP:
+      /* User clicked the scrollbar region above the "thumb";
+       * move the "thumb" up by half of the viewport height.
+       */
+      scrollbar.nPos -= scrollbar.nPage >> 1;
+      break;
+
+    case SB_PAGEDOWN:
+      /* Similarly, for a click below the "thumb", move it
+       * down by half of the viewport height.
+       */
+      scrollbar.nPos += scrollbar.nPage >> 1;
+      break;
+
+    case SB_THUMBTRACK:
+      /* User is dragging...
+       */
+    case SB_THUMBPOSITION:
+      /* ...or has just finished dragging the "thumb"; move it
+       * by the distance it has been dragged.
+       */
+      scrollbar.nPos = scrollbar.nTrackPos;
+
+    case SB_ENDSCROLL:
+      /* Preceding scrollbar event has completed; we do not need
+       * to take any specific action here.
+       */
+      break;
+
+    default:
+      /* We received an unexpected scrollbar event message...
+       */
+      dmh_notify( DMH_WARNING,
+         "Unhandled scrollbar message: request = %d\n", req
+               );
+  }
+  /* Update the scrollbar control, to capture any change in
+   * "thumb" position...
+   */
+  scrollbar.fMask = SIF_POS;
+  SetScrollInfo( AppWindow, SB_VERT, &scrollbar, TRUE );
+
+  /* ...then read it back, since the control hay have adjusted
+   * the actual recorded position.
+   */
+  GetScrollInfo( AppWindow, SB_VERT, &scrollbar );
+  if( scrollbar.nPos != origin )
+  {
+    /* When the "thumb" has moved, force a redraw of the data
+     * sheet window, to capture any change in the visible text.
+     */
+    offset = scrollbar.nPos - scrollbar.nMin;
+    InvalidateRect( AppWindow, NULL, TRUE );
+    UpdateWindow( AppWindow );
+
+    /* Reset the default starting point, so that any subsequent
+     * redraw will favour a "redraw-from-top"...
+     */
+    offset = 0;
+  }
+  /* ...and we are done.
+   */
+  return EXIT_SUCCESS;
+}
+
+void AppWindowMaker::InitPackageTabControl()
+{
+  /* Create and initialise a TabControl window, in which to present
+   * miscellaneous package information...
+   */
+  WindowClassMaker AppWindowRegistry( AppInstance );
+  StringResource ClassName( AppInstance, ID_SASH_WINDOW_PANE_CLASS );
+  AppWindowRegistry.Register( ClassName );
+
+  /* Package data sheets will be displayed in a derived child window
+   * which we create as a member of the SASH_WINDOW_PANE_CLASS; it will
+   * ultimately be displayed below the tab bar, within the tab control
+   * region, with content dynamically painted on the basis of package
+   * selection, (in the package list pane), and tab selection.
+   */
+  DataSheet = new DataSheetMaker( AppInstance );
+  PackageTabPane = DataSheet->Create( ID_PACKAGE_DATASHEET,
+      AppWindow, ClassName, WS_VSCROLL | WS_BORDER
+    );
+
+  /* The tab control itself is the standard control, selected from
+   * the common controls library.
+   */
+  PackageTabControl = CreateWindow( WC_TABCONTROL, NULL,
+      WS_VISIBLE | WS_CHILD | WS_CLIPSIBLINGS, 0, 0, 0, 0,
+      AppWindow, (HMENU)(ID_PACKAGE_TABCONTROL),
+      AppInstance, NULL
+    );
+
+  /* Keep the font for tab labels consistent with our preference,
+   * as assigned to the main application window.
+   */
+  SendMessage( PackageTabControl, WM_SETFONT, (WPARAM)(DefaultFont), TRUE );
+
+  /* Create the designated set of tabs, with appropriate labels...
+   */
+  TCITEM tab;
+  tab.mask = TCIF_TEXT;
+  char *TabLegend[] =
+  { "General", "Description", "Dependencies", "Installed Files", "Versions",
+
+    /* ...with a NULL sentinel marking the preceding label as
+     * the last in the list.
+     */
+    NULL
+  };
+  for( int i = 0; TabLegend[i] != NULL; ++i )
+  {
+    /* This loop assumes responsibility for actual tab creation...
+     */
+    tab.pszText = TabLegend[i];
+    if( TabCtrl_InsertItem( PackageTabControl, i, &tab ) == -1 )
+    {
+      /* ...bailing out, and deleting the container window,
+       * in the event of a creation error.
+       */
+      TabLegend[i + 1] = NULL;
+      DestroyWindow( PackageTabControl );
+      PackageTabControl = NULL;
+    }
+  }
+  if( PackageTabControl != NULL )
+  {
+    /* When the tab control has been successfully created, we
+     * create one additional basic SASH_WINDOW_PANE_CLASS window;
+     * this serves to draw a border around the tab pane.
+     */
+    TabDataPane = new ChildWindowMaker( AppInstance );
+    TabDataPane->Create( ID_PACKAGE_TABPANE, AppWindow, ClassName, WS_BORDER );
+
+    /* We also assign the package description data sheet as the
+     * initial default tab selection.
+     */
+    TabCtrl_SetCurSel( PackageTabControl, PKG_DATASHEET_DESCRIPTION );
+  }
+}
+
+long AppWindowMaker::OnNotify( WPARAM client_id, LPARAM data )
+{
+  /* Handler for notifiable events to be processed in the context
+   * of the main application window.
+   *
+   * FIXME: this supersedes the stub handler, originally provided
+   * by pkgview.cpp; it may not yet be substantially complete, and
+   * may eventually migrate elsewhere.
+   */
+  switch( client_id )
+  {
+    /* At present, we handle only mouse click events within the
+     * package list view and data sheet tab control panes...
+     */
+    case ID_PACKAGE_LISTVIEW:
+    case ID_PACKAGE_TABCONTROL:
+      if( ((NMHDR *)(data))->code == NM_CLICK )
+       /*
+        * ...each of which may require the data sheet content
+        * to be updated, (to reflect a changed selection).
+        */
+       DataSheet->DisplayData( PackageTabControl, PackageListView );
+      break;
+  }
+  /* Otherwise, this return causes any other notifiable events
+   * to be simply ignored, (as they were by the original stub).
+   */
+  return EXIT_SUCCESS;
+}
+
+/* $RCSfile$: end of file */
diff --git a/src/pkgdata.h b/src/pkgdata.h
new file mode 100644 (file)
index 0000000..87975d6
--- /dev/null
@@ -0,0 +1,60 @@
+#ifndef PKGDATA_H
+/*
+ * pkgdata.h
+ *
+ * $Id$
+ *
+ * Written by Keith Marshall <keithmarshall@users.sourceforge.net>
+ * Copyright (C) 2010, 2012, MinGW Project
+ *
+ *
+ * Declarations of the classes and properties used to implement the
+ * package data sheet compilers, for both CLI and GUI clients.
+ *
+ *
+ * This is free software.  Permission is granted to copy, modify and
+ * redistribute this software, under the provisions of the GNU General
+ * Public License, Version 3, (or, at your option, any later version),
+ * as published by the Free Software Foundation; see the file COPYING
+ * for licensing details.
+ *
+ * Note, in particular, that this software is provided "as is", in the
+ * hope that it may prove useful, but WITHOUT WARRANTY OF ANY KIND; not
+ * even an implied WARRANTY OF MERCHANTABILITY, nor of FITNESS FOR ANY
+ * PARTICULAR PURPOSE.  Under no circumstances will the author, or the
+ * MinGW Project, accept liability for any damages, however caused,
+ * arising from the use of this software.
+ *
+ */
+#define PKGDATA_H  1
+
+/* Utility classes and definitions for interpreting and formatting
+ * package descriptions, encoded as UTF-8.
+ */
+typedef unsigned long utf32_t;
+
+#define UTF32_MAX                      (utf32_t)(0x10FFFFUL)
+#define UTF32_OVERFLOW                 (utf32_t)(UTF32_MAX + 1UL)
+#define UTF32_INVALID                  (utf32_t)(0x0FFFDUL)
+
+class pkgUTF8Parser
+{
+  /* A class for parsing a UTF-8 string, decomposing it into
+   * non-whitespace substrings, separated by whitespace, which
+   * is interpreted as potential word-wrap points.
+   */
+  public:
+    pkgUTF8Parser( const char* );
+    ~pkgUTF8Parser(){ delete next; }
+
+  protected:
+    class pkgUTF8Parser *next;
+    union { char utf8; wchar_t utf16; utf32_t utf32; } codepoint;
+    const char *text;
+    int length;
+
+    const char *ScanBuffer( const char* );
+    wchar_t GetCodePoint( ... );
+};
+
+#endif /* PKGDATA_H: $RCSfile$: end of file */ 
index 9c5c28f..a3eefdd 100644 (file)
@@ -30,6 +30,7 @@
 #include <stdarg.h>
 
 #include "pkgbase.h"
+#include "pkgdata.h"
 #include "pkgkeys.h"
 #include "pkglist.h"
 
 
 EXTERN_C const char *pkgMsgUnknownPackage( void );
 
-/* Utility classes and definitions for interpreting and formatting
- * package descriptions, encoded as UTF-8.
- */
-typedef unsigned long utf32_t;
-
-#define UTF32_MAX                      (utf32_t)(0x10FFFFUL)
-#define UTF32_OVERFLOW                 (utf32_t)(UTF32_MAX + 1UL)
-
-#define UTF32_INVALID                  (utf32_t)(0x0FFFDUL)
-
-class pkgUTF8Parser
-{
-  /* A class for parsing a UTF-8 string, decomposing it into
-   * non-whitespace substrings, separated by whitespace, which
-   * is interpreted as potential word-wrap points.
-   */
-  public:
-    pkgUTF8Parser( const char* );
-    ~pkgUTF8Parser(){ delete next; }
-
-  protected:
-    class pkgUTF8Parser *next;
-    union { char utf8; wchar_t utf16; utf32_t utf32; } codepoint;
-    const char *text;
-    int length;
-
-    const char *ScanBuffer( const char* );
-    wchar_t GetCodePoint( ... );
-};
-
-/* Constructor...
+/* pkgUTF8Parser Class Implementation
+ * ==================================
+ *
+ * Constructor...
  */
 pkgUTF8Parser::pkgUTF8Parser( const char *input ):
 text( NULL ), length( 0 ), next( NULL )
@@ -308,6 +282,9 @@ wchar_t pkgUTF8Parser::GetCodePoint( ... )
   return codepoint.utf16;
 }
 
+/* pkgNroffLayoutEngine Class Implementation (local)
+ * =================================================
+ */
 class pkgNroffLayoutEngine : public pkgUTF8Parser
 {
   /* A class for laying out a UTF-8 encoded string as a rudimentary
@@ -454,7 +431,10 @@ pkgNroffLayoutEngine::WriteLn( int style, int offset, int maxlen )
   return curr;
 }
 
-/* Constructor...
+/* pkgDirectory Class Implementation
+ * =================================
+ *
+ * Constructor...
  */
 pkgDirectory::pkgDirectory( pkgXmlNode *item ):
 entry( item ), prev( NULL ), next( NULL ){}
@@ -583,6 +563,9 @@ pkgDirectory::~pkgDirectory()
   delete next;
 }
 
+/* pkgDirectoryViewer Class Implementation
+ * =======================================
+ */
 class pkgDirectoryViewer : public pkgDirectoryViewerEngine
 {
   /* A concrete class, providing the directory traversal hooks
index 711e259..25cb7de 100644 (file)
@@ -132,6 +132,12 @@ long AppWindowMaker::OnCommand( WPARAM cmd )
   return EXIT_SUCCESS;
 }
 
+#if 0
+/* FIXME: this stub implementation has been superseded by an
+ * alternative implementation in pkgdata.cpp; eventually, this
+ * may itself be superseded, and may migrate elsewhere, perhaps
+ * even back to here.
+ */
 long AppWindowMaker::OnNotify( WPARAM client_id, LPARAM data )
 {
   /* Handler for notifications received from user controls; for now
@@ -139,6 +145,7 @@ long AppWindowMaker::OnNotify( WPARAM client_id, LPARAM data )
    */
   return EXIT_SUCCESS;
 }
+#endif
 
 long AppWindowMaker::OnSize( WPARAM mode, int width, int height )
 {
@@ -216,6 +223,44 @@ int AppWindowMaker::LayoutEngine( HWND pane, LPARAM region )
       pane_width -= pane_left;
       break;
 
+    case ID_PACKAGE_TABPANE:
+    case ID_PACKAGE_TABCONTROL:
+    case ID_PACKAGE_DATASHEET:
+      /* Lower right hand pane; occupies the remaining area of the parent,
+       * to the right of the tree view pane, and below the list view, again
+       * with allowance for small gaps to the left and above, to accommodate
+       * the sash bars separating it from the adjacent panes.
+       */
+      pane_top = VerticalSash->Displacement( pane_height ) + SASH_BAR_THICKNESS;
+      pane_left = HorizontalSash->Displacement( pane_width ) + SASH_BAR_THICKNESS;
+      if( pane_id == ID_PACKAGE_TABCONTROL )
+      {
+       /* Shift the tab control window, and shrink it, so that it doesn't
+        * occlude the border surrounding the underlying tab pane window.
+        */
+       ++pane_left; ++pane_top; --pane_width; --pane_height;
+      }
+      else if( pane_id == ID_PACKAGE_DATASHEET )
+      {
+       /* Leave the data sheet window at the full width of the tab pane,
+        * but adjust its height and top edge position so that it appears
+        * below to tabs; (its own border will appear in place of the tab
+        * pane border, where they overlap.
+        */
+       RECT frame;
+       frame.top = pane_top;
+       frame.left = pane_left;
+       frame.right = pane_left + pane_width;
+       frame.bottom = pane_top + pane_height;
+       TabCtrl_AdjustRect( PackageTabControl, FALSE, &frame );
+       pane_top = frame.top;
+      }
+      /* Adjust height and width to fill the space below and to the right
+       * of the two sash bars.
+       */
+      pane_height -= pane_top; pane_width -= pane_left;
+      break;
+
     case ID_HORIZONTAL_SASH:
       /* A slim window, placed to the right of the tree view pane, and
        * occupying the full height of the parent window, representing the