| View previous topic :: View next topic |
| Author |
Message |
bitfarmer General User

Joined: 06 May 2004 Posts: 39 Location: Murcia, Spain
|
Posted: Tue Oct 20, 2009 1:33 am Post subject: Delphi: Interact with text docs from OpenOffice or MS-Word |
|
|
In the same way some time I posted a code to manage spread sheet in a dual way (you don't care if your user has excel or OpenOffice at all), I also build a similar unit to manage text docs from delphi usign OOo Writer or MS Word transparently.
This unit is not so complete as spread sheet is, many features are not implemented, just the ones I needed, like search&replace, load/save docs., print it and that's all.
One special note: If you throh 100 search&replace to Word and then ask it to save/print, it can take 30 seconds to make the changes and the it saves/print, BUT word is not yet finished, so the print will be done too early... discastig, eh?
The only work around I found is to wait until the winword.exe process was iddle, tricky but work. I added a WaitForIddle function that makes that, but uses a set of processes functions pasted at the end of this post.
Well, I hope it is of some use to someone!
| Code: |
// *********************************************
// ** Object for dual WordProcessing managing **
// ** using Word or OpenOffice automaticaly **
// ** By: Sergio Hernandez **
// ** oficina(at)hcsoft.net, CopyLeft 2004 **
// ** Version 0.3 20-10-2009 (DDMMYYYY) **
// ** Use it freely, change it, etc. **
// *********************************************
{EXAMPLE OF USE
//Create object: We have two flavours:
//(A) from an existing file...
TxtDoc:= TDocumentoTexto.create(OpenDialog.FileName, false);
//(B) from a blank document...
TxtDoc:= TDocumentoTexto.create(thcOpenOffice, true); //OpenOffice doc if possible, please
TxtDoc.FileName:= 'C:\MyNewDoc'; //Needs a file name before you SaveDoc!
//--end of creation.
//Change a text (search&replace)
TxtDoc.ChangeOneValue('<MyClient>', 'Cocoa company, ltd.');
//Preview print...
TxtDoc.ShowPrintPreview
TxtDoc.PrintDoc;
TxtDoc.SaveDoc;
TxtDoc.SaveToPDF(C:\MyPDF.pdf);
TxtDoc.Free;
}
{ABOUT CONVERTING DOC TO PDF:
//Force opening the .doc file with OOo like this:
TxtDoc:= TDocumentoTexto.create(thcOpenOffice, true);
TxtDoc.LoadDoc('C:\MyWord.doc');
//Now that OOo has imported the .doc, convert it:
TxtDoc.SaveToPDF('C:\MyPDF.pdf');
TxtDoc.Free;
}
{HISTORIY:
V0.3 - SaveToPDF for OpenOffice added from www.oooforum.org/forum/viewtopic.phtml?t=22344
V0.2 - WaitForIddle added to avoid Word behavior (saving before completing work)
V0.1 - Initial version, it works ok.
}
{TODO LIST:
-Can I know the OOo writer printing status like in Word?
-Can I know if Word/Writer is doing something in background?
}
unit UDocumentoTexto;
interface
uses Variants, SysUtils, ComObj, Classes, Launch;
//thcError: Tried to open but both failes
//thcNone: Haven't tried still to open
type TTipoDoc = (thcError, thcNone, thcWord, thcOpenOffice);
type TDocumentoTexto = class(TObject)
private
fVisible: boolean;
//Program loaded stuff...
procedure LoadProg;
procedure CloseProg;
function GetProgLoaded: boolean;
procedure NewDoc;
procedure LoadDoc;
procedure CloseDoc;
function GetDocLoaded: boolean;
function GetIsWord: boolean;
function GetIsOpenOffice: boolean;
procedure SetVisible(v: boolean);
//OpenOffice only stuff...
function FileName2URL(FileName: string): string;
procedure ooDispatch(ooCommand: string; ooParams: variant);
function ooCreateValue(ooName: string; ooData: variant): variant;
function isNullEmpty(thisVariant: Variant): Boolean;
public
Tipo: TTipoDoc; //Witch program was used to manage the doc?
FileName: string; //In windows FileName format C:\MyDoc.XXX
Programa: variant; //Word or OpenOfice instance created.
DeskTop: variant; //OpenOffice desktop reference (not used now).
Document: variant; //Document opened.
//Object internals...
constructor Create(Name: string; MakeVisible: boolean); overload;
constructor Create(MyTipo: TTipoDoc; MakeVisible: boolean); overload;
destructor Destroy; override;
//Program loaded stuff...
function SaveDoc: boolean;
function SaveToPDF(FileName: string): boolean;
function PrintDoc: boolean;
procedure ShowPrintPreview;
property ProgLoaded: boolean read GetProgLoaded;
property DocLoaded: boolean read GetDocLoaded;
property IsWord: boolean read GetIsWord;
property IsOpenOffice: boolean read GetIsOpenOffice;
property Visible: boolean read fVisible write SetVisible;
//Special function (Search & Replace)
procedure ChangeOneValue(SearchTxt, ReplaceTxt: String; Headers: boolean = true);
//Wait until word end doing its things
function WaitForIddle(MaxSec: double = 30): double;
end;
var
CoInitFlags: Integer = -1;
const
SearchAll: integer = 2; //Word search_for_all (0=None, 1=Once, 2=All)
implementation
// ************************
// ** Create and destroy **
// ************************
//Create with an empty doc of requested type (use thcWord, thcOpenOffice)
//Remember to define FileName before calling to SaveDoc
constructor TDocumentoTexto.Create(MyTipo: TTipoDoc; MakeVisible: boolean);
var
i: integer;
IsFirstTry: boolean;
begin
//Close all opened things first...
CloseDoc;
CloseProg;
//I will try to open twice, so if Word fails, OpenOffice is used instead
IsFirstTry:= true;
for i:= 1 to 2 do begin
//Try to open OpenOffice...
if (MyTipo = thcOpenOffice) or (MyTipo = thcNone)then begin
try
Programa:= CreateOleObject('com.sun.star.ServiceManager');
except
end;
if ProgLoaded then begin
Tipo:= thcOpenOffice;
break;
end else begin
if IsFirstTry then begin
//Try Excel as my second choice
MyTipo:= thcWord;
IsFirstTry:= false;
end else begin
//Both failed!
break;
end;
end;
end;
//Try to open Word...
if (MyTipo = thcWord) or (MyTipo = thcNone) then begin
try
Programa:= CreateOleObject('Word.Application');
except
end;
if ProgLoaded then begin
Tipo:= thcWord;
break;
end else begin
if IsFirstTry then begin
//Try OpenOffice as my second choice
MyTipo:= thcOpenOffice;
IsFirstTry:= false;
end else begin
//Both failed!
break;
end;
end;
end;
end;
//Was it able to open any of them?
if Tipo = thcNone then begin
Tipo:= thcError;
raise Exception.Create('TDocumentoTexto.create failed, may be no Office is installed?');
end;
//Add a blank document...
fVisible:= MakeVisible;
NewDoc;
end;
constructor TDocumentoTexto.Create(Name: string; MakeVisible: boolean);
begin
//Close all opened things first...
CloseDoc;
CloseProg;
Tipo:= thcNone;
//Store values...
FileName:= Name;
//Open program and document...
LoadProg;
LoadDoc;
//Visible?
Visible:= MakeVisible;
end;
destructor TDocumentoTexto.Destroy;
begin
CloseProg;
inherited;
end;
// *************************
// ** Loading the program **
// ** Word or OpenOffice **
// *************************
procedure TDocumentoTexto.LoadProg;
begin
if ProgLoaded then CloseProg;
if (UpperCase(ExtractFileExt(FileName))='.DOC') then begin
//Word is the primary choice...
try
Programa:= CreateOleObject('Word.Application');
except end;
if ProgLoaded then Tipo:= thcWord;
end;
//Not lucky with Word? Another filetype? Let's go with OpenOffice...
if Tipo = thcNone then begin
//Try with OpenOffice...
try
Programa:= CreateOleObject('com.sun.star.ServiceManager');
except end;
if ProgLoaded then Tipo:= thcOpenOffice;
end;
//Still no program loaded?
if not ProgLoaded then begin
Tipo:= thcError;
raise Exception.Create('TDocumentoTexto.LoadProg failed, may be no Office is installed?');
end;
end;
procedure TDocumentoTexto.CloseProg;
begin
if not Visible then CloseDoc;
if ProgLoaded then begin
try
if IsWord then begin
Programa.Quit;
Programa:= Null;
end;
if IsOpenOffice then begin
Desktop.Dispose;
Desktop:= unassigned;
Programa.Dispose;
end;
Programa:= Unassigned;
finally end;
end;
Tipo:= thcNone;
end;
//Is there any prog loaded? Witch one?
function TDocumentoTexto.GetProgLoaded: boolean;
begin
result:= not isNullEmpty(Programa);
end;
function TDocumentoTexto.GetIsWord: boolean;
begin
result:= (Tipo=thcWord);
end;
function TDocumentoTexto.GetIsOpenOffice: boolean;
begin
result:= (Tipo=thcOpenOffice);
end;
// ************************
// ** Loading a document **
// ************************
procedure TDocumentoTexto.NewDoc;
var ooParams: variant;
begin
//Is the program running? (Word or OpenOffice)
if not ProgLoaded then raise Exception.Create('No program loaded for the new document.');
//Is there a doc already loaded?
CloseDoc;
DeskTop:= Unassigned;
//OK, now try to create the doc...
if IsWord then begin
Programa.WorkBooks.Add;
Programa.Visible:= Visible;
Document:= Programa.ActiveWorkBook;
end;
if IsOpenOffice then begin
Desktop:= Programa.CreateInstance('com.sun.star.frame.Desktop');
//Optional parameters (visible)...
ooParams:= VarArrayCreate([0, 0], varVariant);
ooParams[0]:= ooCreateValue('Hidden', false); // çç not Visible);
//Create the document...
Document:= Desktop.LoadComponentFromURL('private:factory/swriter', '_blank', 0, ooParams);
end;
end;
procedure TDocumentoTexto.LoadDoc;
var ooParams: variant;
begin
if FileName='' then exit;
//Is the program running? (Word or OpenOffice)
if not ProgLoaded then LoadProg;
//Is there a doc already loaded?
CloseDoc;
DeskTop:= Unassigned;
//OK, now try to open the doc...
if IsWord then begin
Document:= Programa.Documents.Open(string(FileName),,,,,,,,,,,true);
end;
if IsOpenOffice then begin
Desktop:= Programa.CreateInstance('com.sun.star.frame.Desktop');
//Optional parameters (visible)...
ooParams:= VarArrayCreate([0, 0], varVariant);
ooParams[0]:= ooCreateValue('Hidden', false); //Should be "not Visible" but didn't work ok
//Open the document...
Document:= Desktop.LoadComponentFromURL(FileName2URL(FileName), '_blank', 0, ooParams);
if isNullEmpty(Document) then
raise Exception.Create('Could load the document "'+FileName+'".');
end;
if Tipo=thcNone then
raise Exception.Create('Could load the document "'+FileName+'". No Word processor installed?.');
end;
function TDocumentoTexto.SaveDoc: boolean;
begin
result:= false;
if DocLoaded then begin
if IsWord then begin
Document.Save;
result:= true;
end;
if IsOpenOffice then begin
//There is another method, more powerfull, see SaveToPDF function.
Document.Store;
result:= true;
end;
end;
end;
//If you are using OOo, you can export to PDF directly.
//In word, the only choice is to use a PDF printer, out of our scope here!
//NOTE: If you open a HTML in OOo, the param 0 need to be diferent as commented
//Code refactored from an example by Ryan at:
// http://www.oooforum.org/forum/viewtopic.phtml?t=22344
function TDocumentoTexto.SaveToPDF(FileName: string): boolean;
var ooParams: variant;
begin
result:= false;
if DocLoaded then begin
if IsOpenOffice then begin //Word can't export to PDF!
ooParams:= VarArrayCreate([0, 3], varVariant);
//If the doc loaded is a HTML, you should use this param[0] instead:
//ooParam[0]:= ooCreateValue('FilterName', 'writer_web_pdf_Export')
ooParams[0]:= ooCreateValue('FilterName', 'writer_pdf_Export');
ooParams[1]:= ooCreateValue('CompressionMode', '1');
ooParams[2]:= ooCreateValue('Pages', 'All');
ooParams[3]:= ooCreateValue('Overwrite', TRUE);
Document.StoreToURL(FileName2URL(FileName), ooParams);
result:= true;
end;
end;
end;
//Print the Doc...
function TDocumentoTexto.PrintDoc: boolean;
var ooParams: variant;
begin
result:= false;
if DocLoaded then begin
if IsWord then begin
Document.PrintOut;
while Programa.BackgroundPrintingStatus > 0 do
sleep(500);
result:= true;
end;
if IsOpenOffice then begin
ooParams:= VarArrayCreate([0, 0], varVariant);
ooParams[0]:= ooCreateValue('Wait', true);
Document.Print(ooParams);
//Can't know printing status (maybe it is possible)
//So wait for process to iddle (under 0.5% CPU)
WaitForIddle;
result:= true;
end;
end;
end;
procedure TDocumentoTexto.ShowPrintPreview;
begin
if DocLoaded then begin
//Force visibility of the doc...
Visible:= true;
if IsWord then
Document.PrintOut(,,,true);
if IsOpenOffice then
ooDispatch('.uno:PrintPreview', Unassigned);
end;
end;
procedure TDocumentoTexto.SetVisible(v: boolean);
begin
if DocLoaded and (v<>fVisible) then begin
if IsWord then
Programa.Visible:= v;
if IsOpenOffice then begin
Document.getCurrentController.getFrame.getContainerWindow.setVisible(v);
end;
fVisible:= v;
end;
end;
procedure TDocumentoTexto.CloseDoc;
begin
if DocLoaded then begin
//Close it...
try
if IsOpenOffice then Document.Dispose;
if IsWord then Document.close;
finally end;
//Clean up "pointer"...
Document:= Null;
end;
end;
function TDocumentoTexto.GetDocLoaded: boolean;
begin
result:= not isNullEmpty(Document);
end;
procedure TDocumentoTexto.ChangeOneValue(SearchTxt, ReplaceTxt: String; Headers: boolean = true);
var j, Paso: integer;
Txt, Busca, Reemp: string;
ooBuscador: variant;
begin
case Tipo of
thcWord: begin
//Word can't replace with a text longer than 250!
if length(ReplaceTxt) < 250 then begin
Programa.ActiveDocument.Content.Find.Execute(SearchTxt, true, false, false, false, false,,,, ReplaceTxt, SearchAll);
if Headers then begin
//Search in page header...
Programa.ActiveWindow.ActivePane.View.SeekView:= 9; //9 = wdSeekCurrentPageHeader
Programa.Selection.Find.Execute(SearchTxt, true, false, false, false, false,,,,ReplaceTxt,SearchAll);
//Search on footer...
Programa.ActiveWindow.ActivePane.View.SeekView:= 10; //10 = wdSeekCurrentPageFooter
Programa.Selection.Find.Execute(SearchTxt, true, false, false, false, false,,,,ReplaceTxt,SearchAll);
end;
//You can wait for Word to finish 0.1 secs. uncomenting next line...
//sleep(100);
//...but calling WaitForIddle after all your replaces is a better choice!
end else begin
//I have to "chop" the text into pieces of max. 240 chars!
//NOTE: Coping from array N[] to D[] can make a DLL run out of memory
//and raise rare errors, so I make it char by char!
Busca:= ''; for j:= 1 to Length(SearchTxt) do Busca:= Busca+SearchTxt[j];
Reemp:= ''; for j:= 1 to Length(ReplaceTxt) do Reemp:= Reemp+ReplaceTxt[j];
Paso:= 1;
repeat
Txt:= copy(Reemp,1,240);
Reemp:= copy(Reemp,241,9999);
if (Reemp<>'') then
Txt:= Txt+'[@#@'+IntToStr(Paso)+']';
Programa.ActiveDocument.Content.Find.Execute(Busca,true,false,false,false,false,,,,Txt,SearchAll);
Busca:= '[@#@'+IntToStr(Paso)+']';
inc(Paso);
//Need to wait for Word to finish before processing the next txt piece
sleep(100);
until (Reemp='');
end;
end;
thcOpenOffice: begin
if isNullEmpty(Document) then
raise Exception.Create('No text document loaded');
ooBuscador:= Document.createReplaceDescriptor;
ooBuscador.SearchString:= SearchTxt;
ooBuscador.ReplaceString:= ReplaceTxt;
Document.ReplaceAll(ooBuscador);
end;
end;
end;
//Wait for Word to end up with all instruction we have through at it.
//As Word diggest them asincronous, you never know if you are finish
//and you can end up saving BEFORE all previous commands are done!
//It detects it by measuring the proc %CPU usage until it drops below 0.5%
function TDocumentoTexto.WaitForIddle(MaxSec: double = 30): double;
var ProcName: string;
begin
//Process Name...
if Tipo=thcWord then ProcName:= 'WINWORD.EXE'
else ProcName:= 'swriter.exe';
//Wait for the %CPU to drop below 0.5%
result:= WaitExeIddle(ProcName, 1, MaxSec);
end;
// ***************************
// ** OpenOffice only stuff **
// ***************************
//Change 'C:\File.txt' into 'file:///c:/File.txt' (for OpenOffice OpenURL)
function TDocumentoTexto.FileName2URL(FileName: string): string;
begin
result:= '';
if LowerCase(copy(FileName,1,8))<>'file:///' then
result:= 'file:///';
result:= result + StringReplace(FileName, '\', '/', [rfReplaceAll, rfIgnoreCase]);
end;
function TDocumentoTexto.ooCreateValue(ooName: string; ooData: variant): variant;
var
ooReflection: variant;
begin
if IsOpenOffice then begin
ooReflection:= Programa.createInstance('com.sun.star.reflection.CoreReflection');
ooReflection.forName('com.sun.star.beans.PropertyValue').createObject(result);
result.Name := ooName;
result.Value:= ooData;
end else begin
raise Exception.Create('ooValue imposible to create, load OpenOffice first!');
end;
end;
procedure TDocumentoTexto.ooDispatch(ooCommand: string; ooParams: variant);
var
ooDispatcher, ooFrame: variant;
begin
if DocLoaded and IsOpenOffice then begin
if (VarIsEmpty(ooParams) or VarIsNull(ooParams)) then
ooParams:= VarArrayCreate([0, -1], varVariant);
ooFrame:= Document.getCurrentController.getFrame;
ooDispatcher:= Programa.createInstance('com.sun.star.frame.DispatchHelper');
ooDispatcher.executeDispatch(ooFrame, ooCommand, '', 0, ooParams);
end else begin
raise Exception.Create('Dispatch imposible, load a OpenOffice doc first!');
end;
end;
function TDocumentoTexto.isNullEmpty(thisVariant: Variant): Boolean;
begin
Result:= VarIsEmpty(thisVariant) or VarIsNull(thisVariant) or VarIsClear(thisVariant);
end;
end.
|
And here comes the snippet that wait for a process to iddle (THIS is the most tricky part of all), comments are in plain spanish, but function names are pretty self explanatory. You can copy-paste into prior unit or place in another unit file:
| Code: |
// **************************************************************************
// ** AÑADIDO SERGIO PARA MENEJAR PROCESOS POR SU HANDLE O SU EXE FILENAME **
// **************************************************************************
function FileTime2Milliseconds(FileTime: TFileTime): integer;
var ST: TSystemTime;
begin
FileTimeToSystemTime(FileTime, ST);
result:= ST.wMilliseconds + 1000 *
(ST.wSecond + 60 * (ST.wMinute + 60 * ST.wHour)) ;
end;
//Sabemos 'WINWORD.EXE', dame un THandle del proceso para hacerle putadillas
function GetProcHandle(ExeFileName: string): THandle;
var
ContinueLoop: BOOL;
FSnapshotHandle: THandle;
FProcessEntry32: TProcessEntry32;
begin
result:= 0;
FSnapshotHandle:= CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
FProcessEntry32.dwSize:= Sizeof(FProcessEntry32);
ContinueLoop:= Process32First(FSnapshotHandle, FProcessEntry32);
while integer(ContinueLoop) <> 0 do begin
if ((UpperCase(ExtractFileName(FProcessEntry32.szExeFile)) = UpperCase(ExeFileName))
or (UpperCase(FProcessEntry32.szExeFile) = UpperCase(ExeFileName))) then begin
result:= OpenProcess(PROCESS_ALL_ACCESS, FALSE, FProcessEntry32.th32ProcessID);
break;
end;
ContinueLoop:= Process32Next(FSnapshotHandle, FProcessEntry32);
end;
CloseHandle(FSnapshotHandle);
end;
//Mata este proceso!
function KillProc(H: THandle): boolean;
const PROCESS_TERMINATE=$0001;
begin
result:= (Integer(TerminateProcess(H, 0))<>0);
end;
//Espera a que un Proceso use menos del 0.5% de la CPU.
//Si no encuentra el proceso, esperara MinSeg, y si lo encuentra pero no baja
//del 1%, se esperara un maximo de MaxSeg.
function WaitProcIddle(H: THandle; MinSeg: double = 1; MaxSeg: double = 30): double;
var
LastProcTime, NewProcTime, UsedTime: integer; //Milisegundos todo
CreaTime, ExitTime, UserTime, KernelTime: TFileTime;
const
WAIT_FOR_MS: integer = 1000; //Cada 1 segundo (1000 ms) vuelves a mirar
IDDLE_LOWER: integer = 5; //Usados < 5 ms = (%CPU<0.5) salimos
begin
//Llevo esperando 0 segundos...
result:= 0;
//Igual no lo encuentro...
if (H=0) then begin
//Pues no esta, espero algo y me voy...
sleep(round(MinSeg*1000));
end else begin
//Proceso encontrado, a esperar...
if GetProcessTimes(H, CreaTime, ExitTime, KernelTime, UserTime)=BOOL(0) then exit;
LastProcTime:= FileTime2Milliseconds(KernelTime) + FileTime2Milliseconds(UserTime);
//Cada decima de segundo, recalculo...
repeat
sleep(WAIT_FOR_MS);
result:= result + (WAIT_FOR_MS/1000);
//Rescato tiempo dle proceso actualizado...
if GetProcessTimes(H, CreaTime, ExitTime, KernelTime, UserTime)=BOOL(0) then exit;
NewProcTime:= FileTime2Milliseconds(KernelTime) + FileTime2Milliseconds(UserTime);
UsedTime:= (NewProcTime - LastProcTime);
if (UsedTime) < IDDLE_LOWER then
break;
LastProcTime:= NewProcTime;
until (result >= MaxSeg);
end;
end;
// ***************************************************
// ** MANEJO PROCESOS SABIENDO SU NOMBRE DE FICHERO **
// ***************************************************
//Espera a que una tarea baje del 1%CPU usando su filename
function WaitExeIddle(ExeFileName: string; MinSeg: double = 0.1; MaxSeg: double = 10): double;
begin
result:= WaitProcIddle(GetProcHandle(ExeFileName), MinSeg, MaxSeg);
end;
//Mata programa por su nombre...
function KillTask(ExeFileName: string): boolean;
begin
result:= KillProc(GetProcHandle(ExeFileName));
end;
|
|
|
| Back to top |
|
 |
brace77 Newbie

Joined: 23 Apr 2010 Posts: 1
|
Posted: Fri Apr 23, 2010 5:33 am Post subject: |
|
|
Thanks a lot for the great class.
Please keep us informed as new features are added.
Just a note, i pasted the second part of the code just after the implementation keyword in the class but I had to add (in Delphi 2009) the following to compile it:
| Code: |
uses Windows, TlHelp32;
|
Anyway after that it works perfectly |
|
| Back to top |
|
 |
mike30 Newbie

Joined: 01 Jul 2010 Posts: 1
|
Posted: Thu Jul 01, 2010 8:00 pm Post subject: Good work |
|
|
thanks. I use Turbo Power components for MS Office integration in Delphi, but, I want to offer my customers ( I am a small ISV) OpenOffice integration.
------
Supplements
Last edited by mike30 on Wed Feb 09, 2011 6:58 pm; edited 2 times in total |
|
| Back to top |
|
 |
mkp Newbie

Joined: 14 Oct 2010 Posts: 2
|
Posted: Sat Oct 30, 2010 2:38 am Post subject: |
|
|
Oh my god. It is just the thing I was looking for. I don’t know how you guys do it. Being a newbie to open office it almost looks like magic to me. I hope I will master it one day. The code worked fabulously for me but with the little correction that Brace has suggested here. The only thing I found a little annoying was that it took sometime to get this code run. The snippet for wait is in Spanish couldn’t make out a thing.
__________
forex trading strategies |
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
Powered by phpBB © 2001, 2005 phpBB Group
|