2014-12-03

StreetView any address !!!

I've just received a request from a user to allow him to see the Google StreetView of our clients in out Client forms. This will be useful specially for delivery cases, allowing the team to know how the street looks like, and to better plan the deliveries of merchandises.

I found several samples, but all of them needed the GPS coordinates, the Latitude and Longitude to make it work.

After making some researches, I've found the solution in the JAYCODE DESIGN blog, from Jordan Clist.

"Everyone loves the streetview service that Google maps provides. Naturally the Google API provides the ability to embed streetview within a web application if you feed it the right data and co-ordinates.

What it doesn't do naturally and easily is automatically point the streetview camera an address.
This is something that I thought would be simple but was actually more difficult than it sounds.

Although it is fairly simple to create a streetview panorama at a given GPS point, what is more complicated is to ensure that the streetview camera is pointing in the correct direction (point of view, or yaw)."


Making it work was a little bit tricky, because the original sample provided worked only with some addresses already known by Google. That made it to fail in most cases, because of the spellings, or even a comma put in the wrong place.

So I used another Google Maps API, the "Google Maps Directions API", that is originally used to tell us the routes that we can take between 2 directions. This API works very well with not so well formatted directions, and returns Google's version of the direction.
I adapted a sample Mike Gagnon, from his article published at http://atoutfox.org "Calculer la distance entre 2 adresses, et montrer l'itineraire complet"
After getting this direction, I pass it to the Script from Jordan Clist and Voila!
It works!!!


Copy and paste the codes below, and save it to a PRG and call it "GOOGLESTREETVIEW.PRG"

And to test it, type in your command window"
=GoogleStreetView("Arche du Triumph")
or of course passing the full address:
=GoogleStreetView("Place Charles de Gaulle, 75008, Paris, France")

Cool isn't it? 





Now, just adapt the codes from the PRG to fit your needs. You may show the HTML in your forms using the WebBrowser control. Some other simple changes could be applied by just changing the Zoom values, and the Frame sizes.

Download VFPGoogleStreetView.prg from here


* Program: GOOGLESTREETVIEW.PRG
* Author: Cesar VFPIMAGING - www.vfpimaging.blogspot.com
* Description:
* Gets GoogleMaps full address, from the destination variable filled with the most complete info
* Creates a HTML / AJAX
* Thanks to Jordan Clist, code adapted from the one he osted in his blog:
* http://www.jaycodesign.co.nz/js/using-google-maps-to-show-a-streetview-of-a-house-based-on-an-address/
* Thanks to Mike Gagnon for his article published at http://atoutfox.org/articles.asp?ACTION=FCONSULTER&ID=0000000807
* "Calculer la distance entre 2 adresses, et montrer l'itineraire complet"

LPARAMETERS tcDestination

LOCAL loXML AS "MSXML2.ServerXMLHTTP.4.0"
LOCAL lcFullURL, lcResponse, lcRouteParameters
tcDestination          = EVL(tcDestination, "")

lcRouteParameters = "origin=" + STRTRAN(UPPER(ALLTRIM(tcDestination)), " ", "%20") + ;
    "&destination=" + STRTRAN(UPPER(ALLTRIM(tcDestination)), " ", "%20")

* Documentation at http://code.google.com/apis/maps/documentation/directions/)
* Build up the full URL with the required parameters
lcFullURL = "http://maps.googleapis.com/maps/api/directions/xml?" + lcRouteParameters + ;
    "&units=metrics&sensor=false"


* Test with all XML Versions
* Can also apply the info from http://support.microsoft.com/kb/278674/en-us
* to determine what version of MSXML is installed in the machine
TRY 
 loXML = CREATEOBJECT("MSXML2.ServerXMLHTTP.4.0") && Could use version 3.0, 4.0, 5.0, 6.0
CATCH
 TRY 
  loXML = CREATEOBJECT("MSXML2.ServerXMLHTTP.3.0") && Could use version 3.0, 4.0, 5.0, 6.0
 CATCH 
  TRY
   loXML = CREATEOBJECT("MSXML2.ServerXMLHTTP.5.0") && Could use version 3.0, 4.0, 5.0, 6.0
  CATCH
   TRY 
    loXML = CREATEOBJECT("MSXML2.ServerXMLHTTP.6.0") && Could use version 3.0, 4.0, 5.0, 6.0
   CATCH 
   ENDTRY 
  ENDTRY 
 ENDTRY 
ENDTRY 


loXML.OPEN("POST", lcFullURL, .F.)
loXML.SetRequestHeader("Content-Type", "application/xml")
loXML.SEND("")
lcResponse = loXML.ResponseText

LOCAL lcAddress, lcHTML
lcAddress = STREXTRACT(lcResponse, "<end_address>", "</end_address>")

TEXT TO lcHTML NOSHOW TEXTMERGE

<!DOCTYPE html>
<html>
<head>
<style>
 body {
  font-family: helvetica;
 }
 .map_container {
  width: 800px;
  height:800px;
 }
 #map_canvas_cont, #pano_cont {
  float: left;
  width: 750px;
  height: 400px;
  margin: 20px;
 }

 #pano, #map_canvas {
  width: 100%;
  height: 100%;
 }

</style>
<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false&region=us"></script>
</head>
<body>

  <div class="map_container">

  <div id="pano_cont">
    <div id="pano"></div>
   </div>

 <div id="map_canvas_cont">
    <div id="map_canvas"></div>
   </div>
  </div>
 <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js" type="text/javascript"></script>
 <script>

  var panorama;
  var addLatLng;
  var showPanoData;
  var panorama;

  function load_map_and_street_view_from_address(address) {
     // Check if GPS has been locally cached.
   var geocoder = new google.maps.Geocoder();
   console.log("new geocoder");
   geocoder.geocode( { 'address': address}, function(results, status) {
       if (status == google.maps.GeocoderStatus.OK) {

     var gps = results[0].geometry.location;
     create_map_and_streetview(gps.lat(), gps.lng(), 'map_canvas', 'pano');
       }
   });
  }


   function showPanoData(panoData, status) {
       if (status != google.maps.StreetViewStatus.OK) {
   $('#pano').html('No StreetView Picture Available').attr('style', 'text-align:center;font-weight:bold').show();
         return;
       }
       var angle = computeAngle(addLatLng, panoData.location.latLng);

       var panoOptions = {
      position: addLatLng,
      addressControl: false,
      linksControl: false,
      panControl: false,
      zoomControlOptions: {
   style: google.maps.ZoomControlStyle.SMALL
      },
      pov: {
   heading: angle,
   pitch: 10,
   zoom: 1
      },
      enableCloseButton: false,
      visible:true
  };

  panorama.setOptions(panoOptions);
 }


 function  create_map_and_streetview(lat, lng, map_id, street_view_id) {

  panorama = new google.maps.StreetViewPanorama(document.getElementById("pano"));
  addLatLng = new google.maps.LatLng(lat,lng);
  var service = new google.maps.StreetViewService();
  service.getPanoramaByLocation(addLatLng, 50, showPanoData);

     var myOptions = {
   zoom: 16,
   center: addLatLng,
   mapTypeId: google.maps.MapTypeId.ROADMAP,
   backgroundColor: 'transparent',
   streetViewControl: false,
   keyboardShortcuts: false
     }
     console.log("Create map marker")
     var map = new google.maps.Map(document.getElementById(map_id), myOptions);
  var marker = new google.maps.Marker({
    map:map,
    animation: google.maps.Animation.DROP,
    position: addLatLng
  });
  console.log("Create map marker 2")
   }



 function computeAngle(endLatLng, startLatLng) {
      var DEGREE_PER_RADIAN = 57.2957795;
      var RADIAN_PER_DEGREE = 0.017453;

      var dlat = endLatLng.lat() - startLatLng.lat();
      var dlng = endLatLng.lng() - startLatLng.lng();
      // We multiply dlng with cos(endLat), since the two points are very closeby,
      // so we assume their cos values are approximately equal.
      var yaw = Math.atan2(dlng * Math.cos(endLatLng.lat() * RADIAN_PER_DEGREE), dlat)
             * DEGREE_PER_RADIAN;
      return wrapAngle(yaw);
   }

   function wrapAngle(angle) {
  if (angle >= 360) {
      angle -= 360;
  } else if (angle < 0) {
      angle += 360;
  }
  return angle;
    }
  console.log("before $");
  $(document).ready(function() {
   console.log("doc ready");
   load_map_and_street_view_from_address("<<lcAddress>>");

   $('#change_street').click(function(){
    $('form').submit();
   });

  });
 </script>
</body>
</html>

ENDTEXT

* Save the HTML to a local file on the disk
LOCAL lcFile
lcFile = ADDBS(GETENV("TEMP")) + SYS(2015) + ".htm"
STRTOFILE(lcHTML, lcFile)

* Show the StreetView
RUN /N6 Explorer.EXE &lcFile.
Download VFPGoogleStreetView.PRG directly

2014-11-25

MSKB 968409 - Latest Hotfix for VFP9 SP2 - Updates VFP to v9.0.0.7423

Recently MS removed several files from their Archive gallery, I've uploaded the latest VFP9 SP2 Hotfix – MSKB 968409 at the FoxyPreviewer downloads page.
This hotfix is essential for Foxers who use FoxyPreviewer and Top Level Forms. It brings a fix for a "Disabled report toolbar".
You can get the files directly in the FoxyPreviewer releases page, at: http://foxypreviewer.codeplex.com/releases
Below is the Information provided by Microsoft regarding this HotFix at http://support.microsoft.com/kb/968409/en-us . Hopefully they'll not retire that page as well !!!

FIX: The group header of a data grouping is not printed at the top of a page as expected after you install Visual FoxPro 9.0 Service Pack 2



Article ID: 968409

SYMPTOMS

Consider the following scenario:
  • You have installed Microsoft Visual FoxPro 9.0 Service Pack 2 (SP2) on a computer.
  • You have a report that contains a data grouping.
  • You select the Reprint group header on each page option for the data grouping.
  • You run the report.
  • The details for the data grouping end near the bottom of a page. This causes the data group footer to be printed at the top of the next page.
In this scenario, the header of the next data group and the footer of the current data group are interleaved at the top of the next page as follows:
  • The data group header of the next data group
  • The data group footer of the current data group
  • The details for the next data group
However, you expect that the data group headers and footers should print at the top of the next page as follows:
  • The data group header of the current data group
  • The data group footer of the current data group
  • The data group header of the next data group
  • The details for the next data group

RESOLUTION

Hotfix information

A supported hotfix is available from Microsoft. However, this hotfix is intended to correct only the problem that is described in this article. Apply this hotfix only to systems that are experiencing this specific problem. This hotfix might receive additional testing. Therefore, if you are not severely affected by this problem, we recommend that you wait for the next software update that contains this hotfix.

To download this hotfix from the MSDN Code Gallery, visit the following Microsoft Web site:

Note The MSDN Code Gallery displays the languages for which the hotfix is available. If you do not see your language listed, it is because the Code Gallery resource page is not available for that language.

Note If additional issues occur or if any troubleshooting is required, you might have to create a separate service request. The usual support costs will apply to additional support questions and issues that do not qualify for this specific hotfix. For a complete list of Microsoft Customer Service and Support telephone numbers or to create a separate service request, visit the following Microsoft Web site:

Prerequisites

You must have Visual FoxPro 9.0 SP2 installed to apply this hotfix.

Restart requirement

You do not have to restart the computer after you apply this hotfix.

Hotfix replacement information

This hotfix does not replace a previously released hotfix.

File information

The English version of this hotfix has the file attributes (or later file attributes) that are listed in the following table. The dates and times for these files are listed in Coordinated Universal Time (UTC). When you view the file information, it is converted to local time. To find the difference between UTC and local time, use the Time Zone tab in the Date and Time item in Control Panel.
File nameFile versionFile sizeDateTime
Vfp9.exe9.0.0.74235,783,55223-Feb-200921:58
Vfp9r.dll9.0.0.74234,734,97623-Feb-200921:59
Vfp9runtime.msmNot Applicable4,584,44824-Feb-200900:37
Vfp9t.dll9.0.0.74233,907,58423-Feb-200921:59

STATUS

Microsoft has confirmed that this is a problem in the Microsoft products that are listed in the "Applies to" section.

MORE INFORMATION

For more information about software update terminology, click the following article number to view the article in the Microsoft Knowledge Base:
824684 Description of the standard terminology that is used to describe Microsoft software updates

Properties

Article ID: 968409 - Last Review: October 7, 2011 - Revision: 3.0

APPLIES TO
  • Microsoft Visual FoxPro 9.0 Service Pack 2
Keywords: 
kbexpertiseinter kbsurveynew kbqfe KB968409

DOWNLOAD

Get the alternative file from the FoxyPreviewer's release page:

GdiPlusX v1.21

Our last OFFICIAL published version is v1.20, at CodePlex.

Recently I noticed that some very well known Foxers have published in CodePlex some fixes and patches for GdiPlusX. In most cases, the bugs are very simple, some variables that have not been defined, some API calls with a missing parameter, and small typos, like using "tnWidth" instead of "lnWidth". None of these fixes change the way the library was coded, or its structure. An API was declaring an INTEGER parameter, when the correct would be to use a STRING.

No new properties were introduced, nothing. I've been applying and testing them all, and all sounds good. In fact, these bugs appeared in methods that I did not test before wide smile

The fixes come from: Christian Ehlscheidt, Doug Hennig, Rick Schummer, Martina Jindrova, MReigler, Daveaswj and me.

I've also included one single method in "xfcBitmap", that uses the API "ExtFloodFill", that resides in GDI32 (not Gdi+), and allows us to make fills in images. You tell the API the coordinates and the color to be used, and it fills the image till it finds a different color from the original. Very fast and cool! Me and Yosfi discussed about this API usage last month here at Foxite.



Another thing...

In the last version, we forgot to release the "samples" project, so people only had the SYSTEM.APP, and the samples project was somehow "hidden" at the old archives. New users could have difficulties with the library because of that.


So, I am preparing the release 2 distinct files:

1 - GdiPlusX v1.21 with the fixes above and the new function for "FloodFill".
2 - An updated "Samples" project, adding some few samples (4 or 5) using these new fixes and functions

VERY IMPORTANT:

This is not an official release !!!

This version is for testing purposes only.
I've submitted these files to Bo Durban, that is the project owner and manager. He is the person to decide to move it to production and to give the final words regarding it.

So, this is just for testing, ok? Or if you want to play with the samples, there are 2 that I think are realy very cool, the last in the list, one that uses the new "FloodFill" function and another that uses the "MeasureCharacterRanges" functions. I like specially the last sample, "MeasureCharacterRanges.scx". 
See at the image below why:



In summary, the "MeasureCharacterRanges" functions allow us to obtain VERY PRECISE measuring of strings, and getting the precise coordinates where each part of the text was rendered in the bitmap. Special thanks to Christian Ehlscheidt, the same author of VFP2C32 library from VFPX for the fixes in the functions related.
I've already updated VFPPaint to benefit from the new "FloodFill" function, and hope to publish it very soon.

http://vfpx.codeplex.com/releases/view/103462

FILEINDISK() VFP Function

FUNCTION FileInDisk
LPARAMETERS zcFileName
IF TYPE("zcFileName") <> "C"
    RETURN .F.
ENDIF
LOCAL ARRAY laJunk[1] 
RETURN (ADIR(laJunk, zcFileName, "ARS") > 0)

2014-11-23

VFP and OPEN OFFICE AUTOMATION

Here are some samples of OO Automation
Author: UNKNOWN

* Library of routines to manipulate OpenOffice.org.
*
*
* http://kosh.datateamsys.com/~danny/OOo/VisualFoxPro-OOo/LibOOo-2003-11-17-01.prg

*############################################################
*    Public API
*    High level routines.
*############################################################


* Return .T. if OpenOffice.org is installed.
* Return .F. if not installed.
FUNCTION OOoIsInstalled()
    LOCAL oServiceManager
    oServiceManager = .NULL.

    LOCAL cOldErrHandler
    cOldErrHandler = ON( "ERROR" )
    ON ERROR = DoNothing__ErrorHandler( ERROR(), MESSAGE(), LINENO(), SYS(16), PROGRAM(), SYS(2018) )
        oServiceManager = OOoGetServiceManager()
    ON ERROR &cOldErrHandler
    
    * If we could create a Service Manager,
    *  then OpenOffice.org must be installed.
    RETURN NOT ISNULL( oServiceManager )
ENDFUNC




* Easy way to create a new Draw document.
FUNCTION OOoCreateNewDrawDocument()
    LOCAL oDrawDoc
    oDrawDoc = OOoOpenURL( "private:factory/sdraw" )
    RETURN oDrawDoc
ENDFUNC


* Easy way to create a new Writer document.
FUNCTION OOoCreateNewWriterDocument()
    LOCAL oWriterDoc
    oWriterDoc = OOoOpenURL( "private:factory/swriter" )
    RETURN oWriterDoc
ENDFUNC


* Easy way to create a new Calc document.
FUNCTION OOoCreateNewCalcDocument()
    LOCAL oCalcDoc
    oCalcDoc = OOoOpenURL( "private:factory/scalc" )
    RETURN oCalcDoc
ENDFUNC


* Easy way to create a new Impress document.
FUNCTION OOoCreateNewImpressDocument()
    LOCAL oImpressDoc
    oImpressDoc = OOoOpenURL( "private:factory/simpress" )
    RETURN oImpressDoc
ENDFUNC



FUNCTION OOoOpenFile( cFilename )
    LOCAL cURL
    cURL = OOoConvertToURL( cFilename )
    
    LOCAL oDoc
    oDoc = OOoOpenURL( cURL )
    RETURN oDoc
ENDFUNC


PROCEDURE OOoTerminateProgram()
    LOCAL oDesktop
    oDesktop = OOoGetDesktop()
    oDesktop.Terminate()
    
    =__OOoReleaseCachedVars()
ENDPROC


* Convert a local filename to an OOo URL.
FUNCTION OOoConvertToURL( cFilename )
    * Ensure leading slash.
    IF LEFT( cFilename, 1 ) != "/"
        cFileName = "/" + cFileName
    ENDIF
    
    LOCAL cURL
    cURL = CHRTRAN( cFilename, "\", "/" )    && change backslashes to forward slashes.
    cURL = "file://" + cURL
    RETURN cURL
ENDFUNC




*############################################################
*    Draw specific tools
*############################################################


* Page zero is the first page.
* Once you have a drawing document, obtain one of its pages.
FUNCTION OOoGetDrawPage( oDrawDoc, nPageNum )
    LOCAL oPages
    oPages = oDrawDoc.GetDrawPages()
    LOCAL oPage
    oPage = oPages.GetByIndex( nPageNum )
    RETURN oPage
ENDFUNC


* Given a draw document, create a shape.
* The shape must be added to the draw page, by calling
*  the draw page's Add() method.
* The oPosition and oSize arguments are optional, but you may
*  pass a Point and Size struct for these arguments.

FUNCTION OOoMakeRectangleShape( oDrawDoc, oPosition, oSize )
    LOCAL oShape
    oShape = OOoMakeShape( oDrawDoc, "com.sun.star.drawing.RectangleShape", oPosition, oSize )
    RETURN oShape
ENDFUNC

FUNCTION OOoMakeEllipseShape( oDrawDoc, oPosition, oSize )
    LOCAL oShape
    oShape = OOoMakeShape( oDrawDoc, "com.sun.star.drawing.EllipseShape", oPosition, oSize )
    RETURN oShape
ENDFUNC

FUNCTION OOoMakeLineShape( oDrawDoc, oPosition, oSize )
    LOCAL oShape
    oShape = OOoMakeShape( oDrawDoc, "com.sun.star.drawing.LineShape", oPosition, oSize )
    RETURN oShape
ENDFUNC

FUNCTION OOoMakeTextShape( oDrawDoc, oPosition, oSize )
    LOCAL oShape
    oShape = OOoMakeShape( oDrawDoc, "com.sun.star.drawing.TextShape", oPosition, oSize )
    RETURN oShape
ENDFUNC


* Create and return a Point struct.
FUNCTION OOoPosition( nX, nY )
    LOCAL oPoint
    oPoint = OOoCreateStruct( "com.sun.star.awt.Point" )
    oPoint.X = nX
    oPoint.Y = nY
    RETURN oPoint
ENDFUNC

* Create and return a Size struct.
FUNCTION OOoSize( nWidth, nHeight )
    LOCAL oSize
    oSize = OOoCreateStruct( "com.sun.star.awt.Size" )
    oSize.Width = nWidth
    oSize.Height = nHeight
    RETURN oSize
ENDFUNC


* Given a shape, alter its position.
PROCEDURE OOoSetPosition( oShape, nX, nY )
    LOCAL oPosition
    oPosition = oShape.Position
    oPosition.X = nX
    oPosition.Y = nY
    oShape.Position = oPosition
ENDPROC

* Given a shape, alter its size.
PROCEDURE OOoSetSize( oShape, nWidth, nHeight )
    LOCAL oSize
    oSize = oShape.Size
    oSize.Width = nWidth
    oSize.Height = nHeight
    oShape.Size = oSize
ENDPROC


* Given a draw document, create a shape.
* Optional:  oPosition, oSize can receive a Point or Size struct.
FUNCTION OOoMakeShape( oDrawDoc, cShapeClassName, oPosition, oSize )
    LOCAL oShape
    oShape = oDrawDoc.CreateInstance( cShapeClassName )
    
    IF (TYPE([oPosition])="O")  AND  (NOT ISNULL( oPosition ))
        oShape.Position = oPosition
    ENDIF
    IF (TYPE([oSize])="O")  AND  (NOT ISNULL( oSize ))
        oShape.Size = oSize
    ENDIF
    
    RETURN oShape
ENDFUNC



*############################################################
*    Color conversion utilities
*    --------------------------
*    Visual FoxPro and OpenOffice.org do not represent
*     color values the same way.
*    Conversion routines in both directions are provided
*     to keep things easy.
*    In addition, extremely convenient HSV routines are provided.
*    See LibGraphics for discussion of HSV color model.
*############################################################


* Similar to VFP's built in RGB() function.
* Pass in R,G,B values and out comes an OpenOffice.org color value.
* Note that this is DIFFERENT from how VFP constructs color values.
FUNCTION OOoRGB( nRed, nGreen, nBlue )
    RETURN BITOR( BITOR( BITLSHIFT( nRed, 16 ), BITLSHIFT( nGreen, 8 ) ), nBlue )
ENDFUNC

* Translate between a Visual FoxPro color and an OpenOffice.org color
*  by using a simple formula.
* Pass in a VFP color, out comes an OOo color.  And vice versa.
FUNCTION OOoColor( nColor )
    LOCAL nTranslatedColor
    nTranslatedColor = (INT( nColor / 65536 )) + (INT( INT( nColor / 256 ) % 256 ) * 256) + (INT( nColor % 256 ) * 65536)
    RETURN nTranslatedColor
ENDFUNC

* Extract the Red component from an OpenOffice.org color.
* SImilar to the RGBRed() function in LibGraphics.
FUNCTION OOoRed( nOOoColor )
*    RETURN INT( nOOoColor / 65536 )
    RETURN BITRSHIFT( BITAND( nOOoColor, 0x00FF0000 ), 16 )
ENDFUNC

* Extract the Green component from an OpenOffice.org color.
* SImilar to the RGBGreen() function in LibGraphics.
FUNCTION OOoGreen( nOOoColor )
*    RETURN INT( INT( nOOoColor / 256 ) % 256 )
    RETURN BITRSHIFT( BITAND( nOOoColor, 0x0000FF00 ), 8 )
ENDFUNC

* Extract the Blue component from an OpenOffice.org color.
* SImilar to the RGBBlue() function in LibGraphics.
FUNCTION OOoBlue( nOOoColor )
*    RETURN INT( nOOoColor % 256 )
    RETURN BITAND( nOOoColor, 0x000000FF )
ENDFUNC

* Convenient way to construct an OOo color from HSV components.
* See LibGraphics for information about HSV.
* Note nHue is a number from 0.0 to 1.0.
FUNCTION OOoHSV( nHue, nSaturation, nBrightness )
    LOCAL nColor
    nColor = MakeHSVColor( nHue * 6.0, nSaturation, nBrightness )
    nColor = OOoColor( nColor )
    RETURN nColor
ENDFUNC

* Convenient way to extract the Hue component from an OOo color.
* See LibGraphics for information about HSV.
* Note nHue is a number from 0.0 to 1.0.
FUNCTION OOoHue( nOOoColor )
    RETURN HSVHue( OOoColor( nOOoColor ) ) / 6.0
ENDFUNC

* Convenient way to extract the Saturation component from an OOo color.
* See LibGraphics for information about HSV.
FUNCTION OOoSaturation( nOOoColor )
    RETURN HSVSaturation( OOoColor( nOOoColor ) )
ENDFUNC

* Convenient way to extract the Brightness component from an OOo color.
* See LibGraphics for information about HSV.
FUNCTION OOoBrightness( nOOoColor )
    RETURN HSVValue( OOoColor( nOOoColor ) )
ENDFUNC



*############################################################



PROCEDURE DoNothing__ErrorHandler( pnError, pcErrMessage, pnLineNo, pcProgramFileSys16, pcProgram, pcErrorParamSys2018 )
ENDPROC


* Return the OpenOffice.org service manager object.
* Cache it in a global variable.
* Create it if not already cached.
FUNCTION OOoGetServiceManager()
    IF (TYPE([goOOoServiceManager])!="O")  OR  ISNULL( goOOoServiceManager )
        PUBLIC goOOoServiceManager
        goOOoServiceManager = CREATEOBJECT( "com.sun.star.ServiceManager" )
    ENDIF
    RETURN goOOoServiceManager
ENDFUNC


* Sugar coated routine to ask the service manager to
*  create you an instance of some other OpenOffice.org UNO object.
FUNCTION OOoServiceManager_CreateInstance( cServiceName )
    LOCAL oServiceManager
    oServiceManager = OOoGetServiceManager()
    
    LOCAL oInstance
    oInstance = .NULL.

    LOCAL cOldErrHandler
    cOldErrHandler = ON( "ERROR" )
    ON ERROR = DoNothing__ErrorHandler( ERROR(), MESSAGE(), LINENO(), SYS(16), PROGRAM(), SYS(2018) )
        oInstance = oServiceManager.createInstance( cServiceName )
    ON ERROR &cOldErrHandler
    
    IF ISNULL( oInstance )
        =__OOoReleaseCachedVars()
        oServiceManager = OOoGetServiceManager()
        oInstance = oServiceManager.createInstance( cServiceName )
    ENDIF

    RETURN oInstance
ENDFUNC


* Sugar coated routine to create any UNO struct.
* Use the Bridge_GetStruct() feature of the OLE-UNO bridge.
FUNCTION OOoCreateStruct( cTypeName )
    LOCAL oServiceManager
    oServiceManager = OOoGetServiceManager()
    
    LOCAL oStruct
    oStruct = .NULL.

    LOCAL cOldErrHandler
    cOldErrHandler = ON( "ERROR" )
    ON ERROR = DoNothing__ErrorHandler( ERROR(), MESSAGE(), LINENO(), SYS(16), PROGRAM(), SYS(2018) )
        oStruct = oServiceManager.Bridge_GetStruct( cTypeName )
    ON ERROR &cOldErrHandler
    
    IF ISNULL( oStruct )
        =__OOoReleaseCachedVars()
        oServiceManager = OOoGetServiceManager()
        oStruct = oServiceManager.Bridge_GetStruct( cTypeName )
    ENDIF

    RETURN oStruct
ENDFUNC


* Return the OpenOffice.org desktop object.
* Cache it in a global variable.
* Create it if not already cached.
FUNCTION OOoGetDesktop()
    IF (TYPE([goOOoDesktop])!="O")  OR  ISNULL( goOOoDesktop )
        PUBLIC goOOoDesktop
        goOOoDesktop = OOoServiceManager_CreateInstance( "com.sun.star.frame.Desktop" )
        COMARRAY( goOOoDesktop, 10 )
    ENDIF
    RETURN goOOoDesktop
ENDFUNC


*!*    * Return an instance of com.sun.star.reflection.CoreReflection.
*!*    * Cache it in a global variable.
*!*    * Create it if not already cached.
*!*    FUNCTION OOoGetCoreReflection()
*!*        IF (TYPE([goOOoCoreReflection])!="O")  OR  ISNULL( goOOoCoreReflection )
*!*            PUBLIC goOOoCoreReflection
*!*            goOOoCoreReflection = OOoServiceManager_CreateInstance( "com.sun.star.reflection.CoreReflection" )
*!*            COMARRAY( goOOoCoreReflection, 10 )
*!*        ENDIF
*!*        RETURN goOOoCoreReflection
*!*    ENDFUNC


*!*    * Create a UNO struct object and return it.
*!*    * This routine is now obsolete.
*!*    * See a superior implementation of this routine elsewhere
*!*    *  which uses the Bridge_GetStruct() feature of the OLE-UNO bridge.
*!*    FUNCTION OOoCreateStruct( cTypeName )
*!*        * Ask service manager for a CoreReflection object.
*!*        LOCAL oCoreReflection
*!*        oCoreReflection = OOoGetCoreReflection()
*!*        
*!*        * Get the IDL Class for the type name.
*!*        LOCAL oXIdlClass
*!*        oXIdlClass = .NULL.

*!*        LOCAL cOldErrHandler
*!*        cOldErrHandler = ON( "ERROR" )
*!*        ON ERROR = DoNothing__ErrorHandler( ERROR(), MESSAGE(), LINENO(), SYS(16), PROGRAM(), SYS(2018) )
*!*            oXIdlClass = oCoreReflection.forName( cTypeName )
*!*        ON ERROR &cOldErrHandler
*!*        
*!*        IF ISNULL( oXIdlClass )
*!*            =__OOoReleaseCachedVars()
*!*            oCoreReflection = OOoGetCoreReflection()
*!*            oXIdlClass = oCoreReflection.forName( cTypeName )
*!*        ENDIF
*!*        
*!*        * Create a variable to hold the created Struct.
*!*        * Assign it some initial value.
*!*        LOCAL oStruct
*!*        oStruct = CREATEOBJECT( "relation" ) && assign some kind of object initially

*!*        * Ask the class definition to create an instance.
*!*        oXIdlClass.CreateObject( @oStruct )
*!*        
*!*        RETURN oStruct
*!*    ENDFUNC


* Create a com.sun.star.beans.PropertyValue struct and return it.
FUNCTION OOoPropertyValue( cName, uValue, nHandle, nState )
    LOCAL oPropertyValue
    oPropertyValue = OOoCreateStruct( "com.sun.star.beans.PropertyValue" )
    
    oPropertyValue.Name = cName
    oPropertyValue.Value = uValue
    
    IF TYPE([nHandle])="N"
        oPropertyValue.Handle = nHandle
    ENDIF
    IF TYPE([nState])="N"
        oPropertyValue.State = nState
    ENDIF
    
    RETURN oPropertyValue
ENDFUNC



* Open or Create a document from it's URL.
* New documents are created by URL's such as:
*    private:factory/sdraw
*    private:factory/swriter
*    private:factory/scalc
*    private:factory/simpress
FUNCTION OOoOpenURL( cURL )
*    LOCAL oPropertyValue
*    oPropertyValue = OOoCreateStruct( "com.sun.star.beans.PropertyValue" )

*    LOCAL ARRAY aNoArgs[1]
*    aNoArgs[1] = oPropertyValue
*    aNoArgs[1].Name = "ReadOnly"
*    aNoArgs[1].Value = .F.

    * These two lines replace the alternate version above,
    *  which are left commented for the insight they provide.
    LOCAL ARRAY aNoArgs[1]
    aNoArgs[1] = OOoPropertyValue( "Hidden", .F. )
    
    LOCAL oDesktop
    oDesktop = OOoGetDesktop()

    LOCAL oDoc
    oDoc = oDesktop.LoadComponentFromUrl( cURL, "_blank", 0, @ aNoargs )
    
    * Make sure that arrays passed to this document are passed zero based.
    COMARRAY( oDoc, 10 )
    
    RETURN oDoc
ENDFUNC


PROCEDURE __OOoReleaseCachedVars()
    RELEASE goOOoServiceManager, goOOoDesktop, goOOoCoreReflection
ENDPROC



*############################################################


* Experimental stuff



*SET PROCEDURE TO LibOOo ADDITIVE
*oDoc = OOoOpenFile( GetDesktopFolderPathname() + "test.sxw" )


* This is an attempt to print a document.
PROCEDURE PrintExperiment( oDoc )
    LOCAL ARRAY aArgs[1]
    aArgs[1] = OOoPropertyValue( "CopyCount", 1, -1, 0 )
*    aArgs[1] = OOoPropertyValue( "Collate", .F. )
    
    oDoc.Print( @ aArgs )
ENDPROC


*############################################################


PROCEDURE TestOOoDraw()
    * Create a drawing.
    LOCAL oDoc
    oDoc = OOoCreateNewDrawDocument()
    
    * Get the first page of the drawing.
    LOCAL oPage
    oPage = OOoGetDrawPage( oDoc, 0 )
    
    LOCAL nHue, nSaturation, nBrightness
    LOCAL nRow, nCol
    LOCAL oShape

    * Now let's draw some rectangles.

    *-----
    *    Notes about HSV color notation.
    * Imagine hue is a "rainbow" of colors, from 0.0 to 1.0.
    * 0.0 is red, then you progress to orange, yellow, green, blue, purple, and
    *  then back to red again.  (Not like a real rainbow.)  1.0 is red also.
    * HSV colors are easy for HUMANS, while RGB colors are easy for COMPUTERS.
    * Quick, what is the RGB for Pink?  Brown?
    * In HSV, you would think Pink is a low-saturation red at full brightness,
    *  thus you could say H=0.0, S=0.5, B=1.0; and that's off the top of my head.
    * Brown is a low brightness Orange, so H=0.0888, S=1.0, B=0.6; right out of thin air.
    * Need a highly contrasting color?  Easy in HSV coordinates.
    * Just pick the opposite hue, maybe maximize saturation (opposite of other color),
    *  and maybe also a min or max brightness.  Can't easily come up with highly
    *  contrasting colors in RGB coordinates in RGB space.
    * Need to "soften" colors?  Take each HSV coordinate, and lower the saturation
    *  by 10 %.
    * Need to "darken" colors?  Lower the brightness of each pixel by 10 %.
    *-----
    
    * Let's vary the hue and saturation, while keeping the brightness constant.
    * But you could vary any two parameters, while keeping the third constant.
    
    nBrightness = 1.0 && full brightness
    FOR nRow = 0 TO 9
        FOR nCol = 0 TO 9
            * Create a shape
            oShape = OOoMakeRectangleShape( oDoc )
            * Calculate color
            nHue = nCol / 9 && hue varies *across* by column
            nSaturation = nRow / 9 && saturation varies *down* by row
            * set color of shape
            * oShape.FillColor = OOoRGB( 255, 255, 255 )
            oShape.FillColor = OOoHSV( nHue, nSaturation, nBrightness )
            * Position and size the shape
            OOoSetSize( oShape, 500, 500 ) && 1/2 cm by 1/2 cm
            OOoSetPosition( oShape, 1000 + nCol * 600, 1000 + nRow * 600 )
            * Add shape to page
            oPage.Add( oShape )
        ENDFOR
    ENDFOR


    * Let's vary the hue and saturation, while keeping the brightness constant.
    * This time, we'll use less-brightness
    * But you could vary any two parameters, while keeping the third constant.
    
    nBrightness = 0.7 && less brightness
    FOR nRow = 0 TO 9
        FOR nCol = 0 TO 9
            * Create a shape
            oShape = OOoMakeRectangleShape( oDoc )
            * Calculate color
            nHue = nCol / 9 && hue varies *across* by column
            nSaturation = nRow / 9 && saturation varies *down* by row
            * set color of shape
            oShape.FillColor = OOoHSV( nHue, nSaturation, nBrightness )
            * Position and size the shape
            OOoSetSize( oShape, 500, 500 ) && 1/2 cm by 1/2 cm
            OOoSetPosition( oShape, 9000 + nCol * 600, 1000 + nRow * 600 )
            * Add shape to page
            oPage.Add( oShape )
        ENDFOR
    ENDFOR


    * Let's vary the hue and brightness, while keeping the saturation constant.
    * But you could vary any two parameters, while keeping the third constant.
    
    nSaturation = 1.0 && full saturation
    FOR nRow = 0 TO 9
        FOR nCol = 0 TO 9
            * Create a shape
            oShape = OOoMakeRectangleShape( oDoc )
            * Calculate color
            nHue = nRow / 9 && hue varies *down* by row
            nBrightness = nCol / 9 && brightness varies *across* by column
            * set color of shape
            oShape.FillColor = OOoHSV( nHue, nSaturation, nBrightness )
            * Position and size the shape
            OOoSetSize( oShape, 500, 500 ) && 1/2 cm by 1/2 cm
            OOoSetPosition( oShape, 1000 + nCol * 600, 9000 + nRow * 600 )
            * Add shape to page
            oPage.Add( oShape )
        ENDFOR
    ENDFOR


    * Let's vary the saturation and brightness, while keeping the hue constant.
    * The hue will be yellow.
    * But you could vary any two parameters, while keeping the third constant.

    nHue = 0.1666 && yellow
    FOR nRow = 0 TO 9
        FOR nCol = 0 TO 9
            * Create a shape
            oShape = OOoMakeRectangleShape( oDoc )
            * Calculate color
            nSaturation = nRow / 9 && saturation varies *down* by row
            nBrightness = nCol / 9 && brightness varies *across* by column
            * set color of shape
            oShape.FillColor = OOoHSV( nHue, nSaturation, nBrightness )
            * Position and size the shape
            OOoSetSize( oShape, 500, 500 ) && 1/2 cm by 1/2 cm
            OOoSetPosition( oShape, 9000 + nCol * 600, 9000 + nRow * 600 )
            * Add shape to page
            oPage.Add( oShape )
        ENDFOR
    ENDFOR
    
    
    * Gee, you don't suppose someone out there could take the knowledge represented
    *  by this code and figure out how to make OpenOffice.org draw bar charts?
    
ENDPROC


PROCEDURE TestOOoCalc()
    * Create a spreadsheet.
    LOCAL oDoc
    oDoc = OOoCreateNewCalcDocument()
    
    * Get first sheet
    LOCAL oSheet
    oSheet = oDoc.getSheets().getByIndex( 0 )
    
    * Plug in some stuff...
    oSheet.getCellByPosition( 0, 0 ).setFormula( "Month" )
    oSheet.getCellByPosition( 1, 0 ).setFormula( "Sales" )
    oSheet.getCellByPosition( 0, 1 ).setFormula( "Jan" )
    oSheet.getCellByPosition( 0, 2 ).setFormula( "Feb" )
    oSheet.getCellByPosition( 0, 3 ).setFormula( "Mar" )
    oSheet.getCellByPosition( 0, 4 ).setFormula( "Apr" )
    oSheet.getCellByPosition( 0, 5 ).setFormula( "May" )
    oSheet.getCellByPosition( 0, 6 ).setFormula( "Jun" )
    oSheet.getCellByPosition( 1, 1 ).setValue( 3826 )
    oSheet.getCellByPosition( 1, 2 ).setValue( 3504 )
    oSheet.getCellByPosition( 1, 3 ).setValue( 2961 )
    oSheet.getCellByPosition( 1, 4 ).setValue( 2504 )
    oSheet.getCellByPosition( 1, 5 ).setValue( 2102 )
    oSheet.getCellByPosition( 1, 6 ).setValue( 1756 )
    
    LOCAL ARRAY aOneArg[1]
    LOCAL cFile, cURL
    
*    cFile = GetDesktopFolderPathname()+"example"
    cFile = "c:\example"

    * Now save the spreadsheet.
    cURL = OOoConvertToURL( cFile + ".sxw" )
    aOneArg[1] = OOoPropertyValue( "Overwrite", .T. )
    oDoc.storeToUrl( cURL, @ aOneArg )
    
    * Now save it as Excel
    cURL = OOoConvertToURL( cFile + ".xls" )
    aOneArg[1] = OOoPropertyValue( "FilterName", "MS Excel 97" )
    oDoc.storeToUrl( cURL, @ aOneArg )
ENDPROC




PROCEDURE CreateHueRainbow()
    * Create a drawing.
    LOCAL oDoc
    oDoc = OOoCreateNewDrawDocument()
    
    * Get the first page of the drawing.
    LOCAL oPage
    oPage = OOoGetDrawPage( oDoc, 0 )
    
    LOCAL nHue, nSaturation, nBrightness
    LOCAL nCol
    LOCAL oShape

    * Now let's draw some rectangles.

    * Let's vary the hue and saturation, while keeping the brightness constant.
    * But you could vary any two parameters, while keeping the third constant.
    
    nBrightness = 1.0 && full brightness
    nSaturation = 1.0 && full saturation
    FOR nCol = 0 TO 200
        * Create a shape
        oShape = OOoMakeRectangleShape( oDoc )
        * Calculate color
        nHue = nCol / 200 && hue varies *across* by column
        * set color of shape
        * oShape.FillColor = OOoRGB( 255, 255, 255 )
        oShape.FillColor = OOoHSV( nHue, nSaturation, nBrightness )
        * Position and size the shape
        OOoSetSize( oShape, 52, 2000 ) && 0.05 cm by 2 cm
        OOoSetPosition( oShape, 1000 + nCol * 50, 1000 )
        oShape.LineStyle = 0
        * Add shape to page
        oPage.Add( oShape )
    ENDFOR
    
    LOCAL cFile, cURL
*    cFile = GetDesktopFolderPathname()+"hue rainbow"
    cFile = "c:\hue rainbow"
    cURL = OOoConvertToURL( cFile + ".sxd" )
    LOCAL ARRAY aOneArg[1]
    aOneArg[1] = OOoPropertyValue( "Overwrite", .T. )
    oDoc.storeAsUrl( cURL, @ aOneArg )
*    oDoc.dispose()
ENDPROC





***    Here is a very similar Visual FoxPro - Open Office example that..... 
***    1. Creates an OOo Calc spreadsheet 
***    2. Puts some strings into it. 
***    3. Puts some numbers into it. 
***    4. Puts some dates into it (as formulas) 
***    5. Saves it in Calc format (as C:\Example.sxw) 
***    6. Saves it in Excel format (as C:\Example.xls) 
***    7. Closes the document -- but this line is commented to leave the spreadsheet open in OOo 

***    Code: 
VfpOOoCalcExample() 

PROCEDURE VfpOOoCalcExample() 
   * Create a spreadsheet. 
   LOCAL oDoc 
   
   oDoc = OOoOpenURL( "private:factory/scalc" ) 
    
   * Get first sheet 
   LOCAL oSheet 
   oSheet = oDoc.getSheets().getByIndex( 0 ) 
    
   *----- 
   * Put some sales figures onto the sheet. 
   oSheet.getCellByPosition( 0, 0 ).setString( "Month" ) 
   oSheet.getCellByPosition( 1, 0 ).setString( "Sales" ) 
   oSheet.getCellByPosition( 2, 0 ).setString( "End Date" ) 
    
   oSheet.getCellByPosition( 0, 1 ).setString( "Jan" ) 
   oSheet.getCellByPosition( 0, 2 ).setString( "Feb" ) 
   oSheet.getCellByPosition( 0, 3 ).setString( "Mar" ) 
   oSheet.getCellByPosition( 0, 4 ).setString( "Apr" ) 
   oSheet.getCellByPosition( 0, 5 ).setString( "May" ) 
   oSheet.getCellByPosition( 0, 6 ).setString( "Jun" ) 
   oSheet.getCellByPosition( 0, 7 ).setString( "Jul" ) 
   oSheet.getCellByPosition( 0, 8 ).setString( "Aug" ) 
   oSheet.getCellByPosition( 0, 9 ).setString( "Sep" ) 
   oSheet.getCellByPosition( 0, 10 ).setString( "Oct" ) 
   oSheet.getCellByPosition( 0, 11 ).setString( "Nov" ) 
   oSheet.getCellByPosition( 0, 12 ).setString( "Dec" ) 
    
   oSheet.getCellByPosition( 1, 1 ).setValue( 3826.37 ) 
   oSheet.getCellByPosition( 1, 2 ).setValue( 3504.21 ) 
   oSheet.getCellByPosition( 1, 3 ).setValue( 2961.45 ) 
   oSheet.getCellByPosition( 1, 4 ).setValue( 2504.12 ) 
   oSheet.getCellByPosition( 1, 5 ).setValue( 2713.98 ) 
   oSheet.getCellByPosition( 1, 6 ).setValue( 2448.17 ) 
   oSheet.getCellByPosition( 1, 7 ).setValue( 1802.13 ) 
   oSheet.getCellByPosition( 1, 8 ).setValue( 2203.22 ) 
   oSheet.getCellByPosition( 1, 9 ).setValue( 1502.54 ) 
   oSheet.getCellByPosition( 1, 10 ).setValue( 1207.68 ) 
   oSheet.getCellByPosition( 1, 11 ).setValue( 1819.71 ) 
   oSheet.getCellByPosition( 1, 12 ).setValue( 986.03 ) 
    
   oSheet.getCellByPosition( 2, 1 ).setFormula( "=DATE(2004;01;31)" ) 
   oSheet.getCellByPosition( 2, 2 ).setFormula( "=DATE(2004;02;29)" ) 
   oSheet.getCellByPosition( 2, 3 ).setFormula( "=DATE(2004;03;31)" ) 
   oSheet.getCellByPosition( 2, 4 ).setFormula( "=DATE(2004;04;30)" ) 
   oSheet.getCellByPosition( 2, 5 ).setFormula( "=DATE(2004;05;31)" ) 
   oSheet.getCellByPosition( 2, 6 ).setFormula( "=DATE(2004;06;30)" ) 
   oSheet.getCellByPosition( 2, 7 ).setFormula( "=DATE(2004;07;31)" ) 
   oSheet.getCellByPosition( 2, 8 ).setFormula( "=DATE(2004;08;31)" ) 
   oSheet.getCellByPosition( 2, 9 ).setFormula( "=DATE(2004;09;30)" ) 
   * Note that these last three dates are not set as DATE() function calls. 
   oSheet.getCellByPosition( 2, 10 ).setFormula( "10/31/2004" ) 
   oSheet.getCellByPosition( 2, 11 ).setFormula( "11/30/2004" ) 
   oSheet.getCellRangeByName( "C13" ).setFormula( "12/31/2004" ) 
   *----- 
    
   * Format the date cells as dates.    
   oFormats = oDoc.getNumberFormats() 
   oLocale = OOoCreateStruct( "com.sun.star.lang.Locale" ) 
   * com.sun.star.util.NumberFormat.DATE = 2 
   nDateKey = oFormats.getStandardFormat( 2, oLocale ) 
   oCell = oSheet.getCellRangeByName( "C2:C13" ) 
   oCell.NumberFormat = nDateKey 
    
   LOCAL ARRAY aOneArg[1] 
   LOCAL cFile, cURL 
    
*   cFile = GetDesktopFolderPathname()+"example" 
   cFile = "c:\example" 

   * Now save the spreadsheet. 
   cURL = OOoConvertToURL( cFile + ".sxw" ) 
   aOneArg[1] = OOoMakePropertyValue( "Overwrite", .T. ) 
   oDoc.storeToUrl( cURL, @ aOneArg ) 
    
   * Now save it as Excel 
   cURL = OOoConvertToURL( cFile + ".xls" ) 
   aOneArg[1] = OOoMakePropertyValue( "FilterName", "MS Excel 97" ) 
   oDoc.storeToUrl( cURL, @ aOneArg ) 
    
   * Close the document. 
*   oDoc.close( 1 ) && TRUE 
ENDPROC 





* Open or Create a document from it's URL. 
* New documents are created by URL's such as: 
*   private:factory/sdraw 
*   private:factory/swriter 
*   private:factory/scalc 
*   private:factory/simpress 
FUNCTION OOoOpenURL( cURL ) 
*   LOCAL oPropertyValue 
*   oPropertyValue = OOoCreateStruct( "com.sun.star.beans.PropertyValue" ) 

*   LOCAL ARRAY aNoArgs[1] 
*   aNoArgs[1] = oPropertyValue 
*   aNoArgs[1].Name = "ReadOnly" 
*   aNoArgs[1].Value = .F. 

   * These two lines replace the alternate version above, 
   *  which are left commented for the insight they provide. 
   LOCAL ARRAY aNoArgs[1] 
   aNoArgs[1] = OOoMakePropertyValue( "Hidden", .F. ) 
    
   LOCAL oDesktop 
   oDesktop = OOoGetDesktop() 

   LOCAL oDoc 
   oDoc = oDesktop.LoadComponentFromUrl( cURL, "_blank", 0, @ aNoargs ) 
    
   * Make sure that arrays passed to this document are passed zero based. 
   COMARRAY( oDoc, 10 ) 
    
   RETURN oDoc 
ENDFUNC 

* Create a com.sun.star.beans.PropertyValue struct and return it. 
FUNCTION OOoMakePropertyValue( cName, uValue, nHandle, nState ) 
   LOCAL oPropertyValue 
   oPropertyValue = OOoCreateStruct( "com.sun.star.beans.PropertyValue" ) 
    
   oPropertyValue.Name = cName 
   oPropertyValue.Value = uValue 
    
   IF TYPE([nHandle])="N" 
      oPropertyValue.Handle = nHandle 
   ENDIF 
   IF TYPE([nState])="N" 
      oPropertyValue.State = nState 
   ENDIF 
    
   RETURN oPropertyValue 
ENDFUNC 



* Sugar coated routine to create any UNO struct. 
* Use the Bridge_GetStruct() feature of the OLE-UNO bridge. 
FUNCTION OOoCreateStruct( cTypeName ) 
   LOCAL oServiceManager 
   oServiceManager = OOoGetServiceManager() 
    
   LOCAL oStruct 
   oStruct = .NULL. 

   LOCAL cOldErrHandler 
   cOldErrHandler = ON( "ERROR" ) 
   ON ERROR = DoNothing__ErrorHandler( ERROR(), MESSAGE(), LINENO(), SYS(16), PROGRAM(), SYS(2018) ) 
      oStruct = oServiceManager.Bridge_GetStruct( cTypeName ) 
   ON ERROR &cOldErrHandler 
    
   IF ISNULL( oStruct ) 
      =__OOoReleaseCachedVars() 
      oServiceManager = OOoGetServiceManager() 
      oStruct = oServiceManager.Bridge_GetStruct( cTypeName ) 
   ENDIF 

   RETURN oStruct 
ENDFUNC 


* Return the OpenOffice.org desktop object. 
* Cache it in a global variable. 
* Create it if not already cached. 
FUNCTION OOoGetDesktop() 
   IF (TYPE([goOOoDesktop])!="O")  OR  ISNULL( goOOoDesktop ) 
      PUBLIC goOOoDesktop 
      goOOoDesktop = OOoServiceManager_CreateInstance( "com.sun.star.frame.Desktop" ) 
      COMARRAY( goOOoDesktop, 10 ) 
   ENDIF 
   RETURN goOOoDesktop 
ENDFUNC 



* Return the OpenOffice.org service manager object. 
* Cache it in a global variable. 
* Create it if not already cached. 
FUNCTION OOoGetServiceManager() 
   IF (TYPE([goOOoServiceManager])!="O")  OR  ISNULL( goOOoServiceManager ) 
      PUBLIC goOOoServiceManager 
      goOOoServiceManager = CREATEOBJECT( "com.sun.star.ServiceManager" ) 
   ENDIF 
   RETURN goOOoServiceManager 
ENDFUNC 


* Sugar coated routine to ask the service manager to 
*  create you an instance of some other OpenOffice.org UNO object. 
FUNCTION OOoServiceManager_CreateInstance( cServiceName ) 
   LOCAL oServiceManager 
   oServiceManager = OOoGetServiceManager() 
    
   LOCAL oInstance 
   oInstance = .NULL. 

   LOCAL cOldErrHandler 
   cOldErrHandler = ON( "ERROR" ) 
   ON ERROR = DoNothing__ErrorHandler( ERROR(), MESSAGE(), LINENO(), SYS(16), PROGRAM(), SYS(2018) ) 
      oInstance = oServiceManager.createInstance( cServiceName ) 
   ON ERROR &cOldErrHandler 
    
   IF ISNULL( oInstance ) 
      =__OOoReleaseCachedVars() 
      oServiceManager = OOoGetServiceManager() 
      oInstance = oServiceManager.createInstance( cServiceName ) 
   ENDIF 

   RETURN oInstance 
ENDFUNC 


PROCEDURE DoNothing__ErrorHandler( pnError, pcErrMessage, pnLineNo, pcProgramFileSys16, pcProgram, pcErrorParamSys2018 ) 
ENDPROC 


PROCEDURE __OOoReleaseCachedVars() 
   RELEASE goOOoServiceManager, goOOoDesktop, goOOoCoreReflection 
ENDPROC 


* Convert a local filename to an OOo URL. 
FUNCTION OOoConvertToURL( cFilename ) 
   * Ensure leading slash. 
   IF LEFT( cFilename, 1 ) != "/" 
      cFileName = "/" + cFileName 
   ENDIF 
    
   LOCAL cURL 
   cURL = CHRTRAN( cFilename, "\", "/" )   && change backslashes to forward slashes. 
   cURL = "file://" + cURL 
   RETURN cURL 
ENDFUNC 





LOCAL loOfcMgr, loDesktop, loDocument, args(1), loCoreReflection,;
loPropertyValue,loText,loCursor, oDisp, oct1, oSourceframe
* Create the Service Manger and Desktop
loOfcMgr = CreateObject("com.sun.star.ServiceManager")
loDesktop = loOfcMgr.createInstance("com.sun.star.frame.Desktop")

** The args array is an array of "PropertyValue - objects - create by invoking OO.o reflection
loCoreReflection = loOfcMgr.createInstance("com.sun.star.reflection.CoreReflection" )
loPropertyValue = CREATEOBJECT("Empty")
loCoreReflection.forName("com.sun.star.beans.PropertyValue"). createobject(@loPropertyValue)
args[1] = loPropertyValue
args[1].name = "ReadOnly"
args[1].value = .F.
* Tell VFP to pass arrays to the loDesktop object as zero-based, by reference
COMARRAY(loDesktop,10)
* Open a new empty writer document
loDocument = loDesktop.loadComponentFromURL("private:factory/swriter","_blank", 0, @args)

** Insert sample Text
loText=loDocument.getText() && Create text object
* Create a cursor object (position pointer)
loCursor= loText.createTextCursor()
**
loCursor.CharHeight = 16
loCursor.CharWeight = 100
loText.insertString(loCursor, "my text", .F.) 




Document: OpenOffice Automation by Andreas Ahammer
Document: Programming OpenOffice with Visual Basic
Document: StarOffice Programmer's Tutorial

2014-03-12

Convert BMP to ICO

The code below is a courtesy of Sergey Karimov, and converts BMP files to the ICO format, keeping the same appearance, and quality.

FUNCTION Bmp2ICO(sFile, aFilesName)
    EXTERNAL ARRAY aFilesName

    * sFile - file name for resulting .ico file
    * aFilesName - array with the list of .bmp files

    LOCAL nFiles, i, nf, fn
    m.nFiles = ALEN(m.aFilesName, 1)

    LOCAL ARRAY aFiles(nFiles)
    LOCAL sLine, nOffset, nWidth, nHeight, nBit0, nShift

    FOR m.i = 1 TO m.nFiles
        m.nf = FOPEN(m.aFilesName(m.i))
        IF m.nf < 1
            = WMsg("!", "Cannot open file " + m.aFilesName(m.i) + " !")
            RETURN .F.
        ENDIF
        m.aFiles(m.i) = FREAD(m.nf, 999999)
        = FCLOSE(m.nf)
    ENDFOR

    m.fn = FULLPATH(m.sFile) + IIF(EMPTY(JUSTEXT(m.sFile)), ".ICO", "")
    m.nf = FCREATE(m.fn)
    IF m.nf < 1
        = WMsg("!", "Cannot create file " + m.fn + " !")
        RETURN .F.
    ENDIF

    m.sLine    = ""
    m.sLine    = m.sLine + CHRN( 0, 2) && 0 reserved
    m.sLine    = m.sLine + CHRN( 1, 2) && 2 type
    m.sLine    = m.sLine + CHRN(m.nFiles, 2) && 4 Number of Icons in this file

    m.nOffset = LEN(m.sLine) + 16 * m.nFiles
    FOR m.i = 1 TO m.nFiles
        m.nWidth  = ASC(SUBSTR(m.aFiles(m.i), 19, 1)) &&width  of the image, in pixels
        m.nHeight = ASC(SUBSTR(m.aFiles(m.i), 23, 1)) &&height of the image, in pixels
        m.nBit0      = ASC(SUBSTR(m.aFiles(m.i), 29, 1)) &&Bits per pixel

        m.sLine    = m.sLine + CHR(m.nWidth)  && 0 width  of the image, in pixels
        m.sLine    = m.sLine + CHR(m.nHeight) && 1 height of the image, in pixels (OR & AND bitmaps)
        m.sLine    = m.sLine + SUBSTR(m.aFiles(m.i), 47, 1) && 2 Number of Colors
        m.sLine    = m.sLine + CHR(0)                 && 3 reserved
        m.sLine    = m.sLine + SUBSTR(m.aFiles(m.i), 27, 2) && 4 Number of Planes

        m.sLine = m.sLine + SUBSTR(m.aFiles(m.i), 29, 2) && 6 Bits per pixel

        m.nShift = ASCN(SUBSTR(m.aFiles(m.i), 11, 4)) &&offset from the beginning of the file to the bitmap data

        m.aFiles(m.i) = SUBSTR(m.aFiles(m.i), 15, 40 + IIF(m.nBit0 > 8, 0, 4 * 2^m.nBit0)) + SUBSTR(m.aFiles(m.i), m.nShift+1) &&image
        m.aFiles(m.i) = STUFF(m.aFiles(m.i), 9, 1, CHR(m.nHeight * 2)) &&height of the image, in pixels (OR & AND bitmaps)

        m.nWidth = CEILING(m.nWidth / 8) && meaning bytes in a row for AND bitmap
        m.nWidth = 4 * CEILING(m.nWidth / 4) &&bytes in a row for AND bitmap

        m.aFiles(m.i) = m.aFiles[m.i] + REPLICATE(CHR(0), m.nWidth * m.nHeight) &&AND bitmap

        m.sLine    = m.sLine + CHRN(LEN(m.aFiles(m.i)), 4) && 8 Size of image area
        m.sLine    = m.sLine + CHRN(m.nOffset, 4)       &&12 offset to image area

        m.nOffset = m.nOffset + LEN(m.aFiles(m.i))
    ENDFOR

    = FWRITE(m.nf, m.sLine)

    FOR m.i = 1 TO m.nFiles
        = FWRITE(m.nf, m.aFiles(m.i))
    ENDFOR

    = FCLOSE(m.nf)

    RETURN .T.

*-------------------------------
FUNCTION ASCN(s) &&converts binary string to numeric
    * s - string, bytes go from tail to head

    LOCAL i, n

    m.n = 0
    FOR m.i = LEN(m.s) TO 1 STEP - 1
        m.n = m.n * 256 + ASC(SUBSTR(m.s, m.i, 1))
    ENDFOR

    RETURN m.n

*-------------------------------
FUNCTION CHRN(n, ln) &&converts numeric value to binary string,
    &&bytes go from tail to head
    * n, ln - numeric value and output string length
    LOCAL i, s, sc
    m.s     = ""
    m.sc = m.n
    FOR m.i = 1 TO m.ln
        m.s     = m.s + CHR(m.sc % 256)
        m.sc = INT(m.sc / 256)
    ENDFOR

    RETURN m.s