Kylix - Delphi til Linux


Hvad er RAD

Rapid Application Development = Hurtig Program Udvikling. Ideen er dels, at man arbejder i den/de forme, som programmet er sat sammen af, dels at en ny komponent på en form automatisk integreres i kildeteksten.

Komponenter i Kylix er som regel udstyret med en række parametre, som direkte kan ændres under design, ligesom forskellige events (begivenheder) kan håndteres.


Installation af Kylix

Du kan hente den frie version af Kylix hos Borland.com mod at gennemføre en registrering. Denne openedition-version indeholder ikke de samme faciliteter som købe-versionerne, men giver dog adgang til at prøve at arbejde med Kylix. Specielt database og web-delen er begrænset.

Inden der installeres skal man sikre sig, at de korrekte pakker er installeret. Borland har på CD'en lagt scripts til forskellige distributioner, som det betaler sig at køre først. Når de er på plads installeres selve Kylix.

Det første du skal bestemme er placeringen af Kylix i dit filhieraki. Standard er $HOME/Kylix, hvilket sjældent er relevant. Installerer du som root, bliver dette til /root/Kylix. Der er dog i en af installetionsvejledningerne givet anvisning på, hvordan man kan gøre programmet tilgængeligt for andre brugere uanset placeringen.

Jeg valgte at placere den på /usr/Kylix af pladshensyn (det er en selvstændig harddisk), men /home/Kylix kunne også være et fint sted.

installationen


Som det ses i skærmbilledet herover fylder en fuld installation omkring 750MB. Hertil skal så lægges diverse databaser.

Efter endt installation startes X, og /usr/Kylix/bin/startkylix eksekveres (evt. via menuvalg og med din egen sti til startKylix).

Hvis man laver ting og sager, der anvender system-kommandoer, er det en stor fordel at starte Kylix fra en xterm. Denne vil nemlig vise alle systemfejl i klar tekst. Med start fra et menuvalg ses disse fejlmeldinger ikke, og opgaven med at finde fejl kan være ret frustrerende.


Skærmens elementer.

FirstUp


Kylix arbejder i en række løsrevne vinduer, som det ses herover. Dette er åbningsskærmen, men der er flyttet en smule om på vinduerne for at synliggøre dem.

Hovedvinduet.

I toppen af skærmen vises hovedvinduet i Kylix. Det er i dette vindue, man har adgang til dels programmets menu og dels de forskellige komponenter, der kan bruges. Komponenterne er opdelt i logiske grupper, så fx. databasekomponenterne er samlet. Det er muligt at flytte rundt på komponenternes placering.

Object inspector.

Det venstre vindue giver adgang til at ændre på det aktuelle objects parametre, samt til at koble handlinger på de forskellige events objectet er udstyret med.

Objectinspectoren kan, lige som en del andre vinduer, indlejres i vinduet der viser kildeteksten (docking windows). Klik med højremusen til højre for fanen [events] og fjern markeringen ud for Dockable, hvis du ikke ønsker at inspectoren pludseligt indlejrer sig i et andet vindue.

Opsætningen kan gemmes, så du kan opbygge forskelligt sammensatte skærme til forskellige arbejdsopgaver. Dog gemmes oplysninger om netop objectinspectoren desværre ikke!

Genvejstaster.

Kylix gør stor brug af genvejstaster, hvilket efterlader dig med et valg mellem pest eller kolera. Vil du bruge en tastekombination i KDE eller i Kylix?. Eftersom jeg er vant til den klassiske opsætning af tastatur/genvejstaster, og ikke har indarbejdet noget som helst i den retning i KDE, var mit valg ikke svært, jeg udryddede eller ændrede ganske enkelt stort set alle genvejene i KDE. Er du vant til at bruge CTRL F1..F8 til at skifte mellem de virtuelle skærme i KDE, er valget ikke helt så let. Efter nogen tids arbejde, er jeg gået over til at starte med icewm+gnome som grundlag ("startx icewm" skulle gøre dette, hvis den er installeret). Denne wm er dejligt fri for tant og fjas, som jeg alligevel ikke har brug for, når jeg arbejdes med Kylix. At den grafiske brugerflade (fx. KDE) håndterer tastetryk før det program, der har focus, er efter min bedste mening en kolossal misforståelse.

Formen.

Langt de fleste programmer har et eller flere vinduer. I Kylix kaldes et sådant vindue en form, og et nyt project starter normalt med at have en Form1 vist.

Det er direkte på denne form, at de enkelte komponenter placeres, og normalt vil man kunne placere dem i deres endelige position. Dette indebærer, at man direkte designer det færdige programs udseende.

Unit1.pas

Dette vindue viser i eksemplet herover to ting. Dels en liste med de benyttede komponenter (her endnu kun Form1), og dels kildeteksten til programmet.

Når der tilføjes flere komponenter vises de i listen til venstre. Den kan foldes ud og vise de enkelte komponenters egenskaber.

Det første program.

Hello World.

Beklager, det er en tvungen øvelse i alle programmeringssprog at lave et program, som kommer frem på skærmen med teksten "Hello World". (Det er dog tilladt at lave lokale udgaver "Go' daw do", men det falder udenfor denne korte gennemgang). (Uerfarne programmører har det med at springe denne del over. Gør det ikke! Traditioner er til for at blive holdt, se bare hvor mange generationer af sløjd-bordskånere, der ligger i skabe og skuffer rundt omkring). Efterhånden har programmering vel eksisteret så længe, så påstanden kunne være:

Din far og mor skrev "Hello World", din bedstefar skrev "Hello World", du skal også lave den.

Der er ingen steder i Form-komponenten, hvor man kan skrive sin tekst. Den har ganske vist en "Caption", hvor man kan skrive hvad man vil, men denne tekst kommer ind som overskrift i programmet. Det, der skal til, er en komponent, der er designet til at placere en tekst et sted på formen. Denne komponent kaldes en "Label" (etikette), og den findes i komponentgruppen "Standard"

Vælg den med venstremusen (den ligner et stort A) og placer den på komponentens form med venstremusen.

Komponenten får navnet Label1 og teksten Label1.

label1

Begynd umiddelbart at skrive teksten "Hello World", og læg mærke til, at komponenten beholder sit navn, men ændrer sin tekst.

Leg lidt med objectinspectorens forskellige parametre, og afprøv programmet med et tryk på [F9] eller et klik på menuevalget Run|Run.

label2

Ønsker du at se forløbet af kompileringen skal du ændre standard opsætningen under Tools|Environment til dette:

tools1


Kommentarer i kildeteksten.

Der kan i Kylix som i Delphi lægges tre forskellige typer kommentarer ind.

// (to skråstreger) gør resten af linien til en kommentar.

{ Tuborghat eller krølleparantes

   Dette starter en tekstblok med en kommentar

   som først afsluttes her --> }

(* starter og der afsluttes med *)

Personligt bruger jeg // og { tekst } til blivende kommentarer, mens (* og *) er til at udkommentere dele af kildeteksten under udviklingsarbejdet. Det gør det let at søge efter (* og finde det, der skal slettes helt eller lægges ind igen.

Det smarte ved kommentar-koderne er, at de kan omslutte hinanden.

Hvis kildeteksten er:

{ Lad os lige kommentere lidt generelt om den næste formel }

 A:= B+1; // dette er en kommentar

og man under udviklingsarbejdet vil forsøge sig med en helt anden formel, men gerne vil kunne vende tilbage, kan man gøre det på denne måde:

(*

{ Lad os lige kommentere lidt generelt om den næste formel }

 A:= B+1; // dette er en kommentar

*)

(* dette er et eksperiment af de større *)

 A:= B+2;

(* eksperimentet slutter her *)

Som det ses, vil det være rimeligt hurtigt at slette eksperimentet, og vende tilbage til det oprindelige.

Hermed er listerne savet og samlet til en bordskåner, nu er det tid til noget lidt mere anvendeligt. Til det bruger jeg projektet Hans og Ida, som bliver mit første Kylix-program.

Hans og Ida

I forbindelse med en tråd om Røde Orm lavede Hans Schou et link til min hjemmeside med en fejl i. Det gav et par hundrede linier i fejlloggen, alle sammen velmente, men alligevel. Som tak til Hans, vil jeg her bruge mit selvforsvars program til en lille gennemgang af forskellige teknikker.
Opgaven går i al sin enkelthed ud på at slippe af med både Hans og Ida fra logfilerne, men som det ofte sker, udviklede ideen sig lidt hen ad vejen. Fra blot at være rent selvforsvar, hvor det bare gjaldt om at slippe af med Hans og Ida, blev det til et program til håndtering af logfiler med mulighed for at slå gæsterne op med et kombineret nslookup og whois.

Helst ville jeg have haft det hele i en database, men det viste sig noget upraktisk af flere grunde.

Kylix og databaser.

Valg af database.

Der er i skrivende stund fem forskellige måder at løse databaseopgaver med Kylix.

Den tekstbaserede version har den fordel, at den ikke kræver en egentlig databaseserver installeret. Til gengæld får man mere eller mindre en flad database, som man selv i høj grad skal vedligeholde.

MySQL er angiveligt en hurtig database med et lille ressourceforbrug. Den er sikkert allerede installeret på maskinen, som en del af distributionen, og er derfor let at gå til. MySQL lider dog af den begrænsning, at den ikke kan håndtere kaskadevis sletning. For mere komplekse databaser betyder det en masse ekstra trafik over nettet og programmering at omgå denne mangel. En endnu større begrænsning er det dog, at den nye version af MySQL bruger libmysqlclient.10.0.0, mens Kylix kræver libmysqlclient.6.0.0. Forklaringen på Borland.public.kylix.database.sqlservers var: "The interface changed in the MySQL client library.
That's why the latest one doesn't currently work"
.
og svaret på, hvornår det så kommer til at virke er: "The only answer we ever give is "When it's ready." When it's ready, I'll let you know.". Det svar taler vist for sig selv.

Interbase er Borlands egen database. Den indeholder hvad der skal til, men de fleste skal først  hente og installere den, så det er lidt tungt. Såvel version 5.6 som 6.0 er med på installations CD'erne, version 6.0 kan også hentes hos Borland.

DB2 og Oracle  er begge købeprogrammer. Til udviklingsbrug kan man dog få begge databaser gratis, men igen, det er ikke sådan lige til at distrubuere.

Jeg havde i første omgang designet en database (og implementeret den), men valgte i stedet at gå en helt anden vej i håndteringen af log-filerne.

Først lidt oprydning efter "Hello World". Slet Label1 ved at klikke på den og derefter trykke på [Delete].


Start på projektet HansOgIda.

Rettigheder

Programmet her skal køres af root, eller af en bruger med root-rettigheder, da det skal kunne arbejde med logfiler. Jeg har valgt ikke at lade programmer bruge su-, så der er ingen vej uden om root-password.

Placering af filerne.

Lav en folder til projektet. Dette kan desværre ikke gøres i den dialog man bruger til at gemme med, så brug en xterm eller lignende til at oprette folderen med. Jeg har valgt at oprette /home/hansogida til projektet (kommando mkdir /home/hansogida), og har gemt projektet og unit1 som hhv. /home/hansogida/hansogida.dpr og /home/hansogida/main.pas. Den vigtigste grund til dette valg af placering er, at alt her i huset skal kunne smutte rundt på flere maskiner, så der skal være adgang til projektet via samba, og her synes /home at være et fornuftigt valgt sted.

Opbygningen af et datamodul.

Selv om der er valgt en simpel konstruktion, kan man jo godt forsøge at gøre tingene rigtigt. Derfor samles alt om data i et modul for sig, et såkaldt datamodul. Et datamodul giver en adgang til at lave globalt tilgængelige objecter, og der kan oprettes flere datamoduler i samme program, så man på den måde kan få styr på sin datatilgang. En anden fordel ved at samle flest mulige funktioner i et datamodul er, at man ekstremt let kan lave en console-applikation med det. Datamodulet indeholder ikke komponenter, der er afhængige af X, så alt hvad der sker i datamodulet kan lige så let ske i et console-program, der skal kunne køre fra en terminal, som et cron-job osv.

Det kan ikke understreges kraftigt nok. Læg alt hvad der direkte har med bruger interfacet i diverse formes kildetekst, og alt der ikke har i datamodul(er). Som Windows/Delphi programmør har denne opdeling ikke så stor betydning, som den har i Linux/Kylix, og hvorfor ikke få gode vaner fra start..

File,New giver dette vindue, hvor man kan vælge DataModule:

DMCreate

Giv det nyoprettede datamodul navnet dm i objectinspectoren, og gem under navnet /home/hansogida/dmodul. Du kommer til at referere til dette modul mange gange, så et helt kort navn på det giver god mening.

Da opgaven løses vha. StringLister, er der i første omgang ikke nogle komponenter, der skal placeres i den nye form. I stedet skal der laves lidt "håndarbejde":

Der skal laves forskellige filtre, som hver for sig bestemmer, hvordan en linie, der opfylder søgekriteriet, skal behandles. Oplysningerne om et sådant filter opbevares i en tFilterRec. Skriv disse linier:

type

  tFilterRec = class(tObject)

     Text: String;      // Den tekst der søges efter

     LogFile: String;   // Hvad hedder Apache-logfilen

     NewFile: String;   // Hvor skal vi hen du?

     Action: Integer;   // Hvad skal der ske med linier der matcher

                        // 0: Overlev i logfilen

                        // 1: Slet fra logfilen

                        // 2: Flyt til ny logfil

                        // 3: Kopi til ny logfil

     Level: Integer;    // Rækkefølgen en linie undersøges efter

     Constructor Create(s: String);

     Property FilTekst: String read GetFilTekst;

     Function ListeTekst(Orden: Integer): String;

  end;

Når linierne er skrevet, trykkes på [CTRL]+[SHIFT] mens cursor er i en af objektets linier, hvorefter de manglende dele af kildeteksten oprettes. Dette er kernen i begrebet Rapid. Man skriver det, der skal ses, og får leveret alt det kedelige stof automatisk! Tingene skal flyttes en smule (der plejer ikke at være properties i en record), så de private dele kommer op foran (tradition), og resultatet bliver derefter:


type

  tFilterRec = class(tObject)

    private

      fFilTekst: String;

    function GetFilTekst: String;

    public

     Text: String[32];  // Den tekst der søges efter

     LogFile: String;   // Hvad hedder Apache-logfilen

     NewFile: String;   // Hvor skal vi hen du?

     Action: Integer;   // Hvad skal der ske med linier der matcher

                        // 0: Overlev i logfilen

                        // 1: Slet fra logfilen

                        // 2: Flyt til ny logfil

                        // 3: Kopi til ny logfil

     Level: Integer;    // Rækkefølgen en linie undersøges efter

     Constructor Create(s: String);

     Property FilTekst: String read GetFilTekst;

     Function ListeTekst(Orden: Integer): String;

  end;

I implementationsdelen er der blevet oprettet følgende:

{ tFilterRec }

constructor tFilterRec.Create(s: String);

begin

end;

function tFilterRec.GetFilTekst: String;

begin

end;

og herefter er det bare at fylde på, så det ønskede sker.

constructor tFilterRec.Create(s: String);

var TempStr: String;

begin

 TempStr:=s;

 Text:=StripFirstText(TempStr,';');

 LogFile:=StripFirstText(TempStr,';');

 NewFile:=StripFirstText(TempStr,';');

 Action:=StripFirstInt(TempStr,';');

 level:=StripFirstInt(TempStr,';');

end;


function tFilterRec.GetFilTekst: String;

begin

 Result:=format('%s;%s;%s;%d;%d;',

                [Text,LogFile,NewFile,

                 Action,level]);

end;

Object-purister vil med fuld ret hævde, at de resterende variabler i denne record også skulle have været oprettet som en property, det har de helt ret i.

StripFirstText er en funktion, der er oprettet i implementationsdelen, men ikke er med i interfacedelen. Denne funktion kan derfor kun "ses" af den del af programmet, der arbejder i implementationsdelen. StripFirstText returnerer den første del ud af en string (indtil tegnet Delim mødes), og sletter samme del i strengen. Da S er en var parameter, er det selve den variabel, der kaldes med, som får skåret første del fra, og StripFirstText kan ikke kaldes med fx. ('Dette er den '+IntToStr(x)+'. linie').

Function StripFirstText(var s: String; Delim: char): String;

var i: Integer;

begin

 if pos(delim,s)=0 then

  begin

  result:=s;s:='';

  end

  else

  begin

  Result:='';

  i:=pos(delim,s);

  Result:=copy(s,1,pred(i));

  s:=copy(s,i+1,length(s)-i);

  end;

end;

Hvis "S" er lig med 'Dette er;en prøve", vil StripFirstText(s,';') resultere i teksten "Dette er", og "S" ville derefter være lig med "en prøve". StripFirstText(s,' ') ville returnere "Dette", og "S" ville være "er;en prøve". Bemærk, at selve skilletegnet (Delim) smides bort.

De enkelte filterrecords skal vises frem for alverden i en listbox. Det er praktisk, hvis de kan vises sorteret i forskellige rækkefølger. For at løse den opgave let, lader jeg listboxens egne tekster indeholde en tekst, der giver den rigtige sorteringsorden, mens det, der skrives på skærmen, bestemmes ved et kald til den enkelte Filterrecord. Listens rækkefølge afgøres udenfor objectet (den oplysning tilhører listen som sådan, ikke de enkelte records), så denne orden må sendes med i funktionskaldet.

function tFilterRec.ListeTekst(Orden: Integer): String;

var s: String;

begin

  case Action of

   0: s:=format(‘ "%s" i %s overlever.',[Text,Logfile]);

   1: s:=format(‘ "%s" slettes fra %s',[Text,LogFile]);

   2: s:=format(‘ "%s" flyttes til %s',[Text,NewFile]);

   3: s:=format(‘ "%s" kopieres til %s ‘,[Text,NewFile]);

  end;

  case Orden of

   0,3,4: Result:=s;

   1: Result:= format(‘%d: %s',[Level,s]);

   2: Result:= format(‘%d: %s',[Action,s]);

  end;

end;

Altså: Action er, hvad netop dette filter gør ved en log-linie, Orden er den rækkefølge de forskellige filtre skal vises i.

function tFilterRec.GetFilTekst: String;

begin

 Result:=format('%s;%s;%s;%d;%d;',

                [Text,LogFile,NewFile,

                 Action,level]);

end;

Nu er der altså to forskellige måder at få en tekst ud af en filterrecord.

Property Filtekst og

Function ListeTekst(Orden: Integer): String;


Filtekst kan behandles som enhver anden streng, mens resultatet af Listetekst skal opsamles i en lokal variabel (eller evt. sendes videre i systemet). Da der ikke er defineret nogen write procedure til Filtekst, kan man ikke udefra komme til at ændre på dens indhold, den er ReadOnly.

Format er en funktion i Kylix der fungere på helt samme måde som i C. Der hvor der optræder en kode, indsættes en variabel. %s og %d er koderne, variablerne følger i []. Fordelene ved at benytte format er flere. For det første gør det kildeteksten lettere at læse og vedligeholde, for det andet gør det den i nogen grad oversættelig til fremmede sprog. I starten virker det tungt og besværligt i forhold til blot at addere strenge, men i dette eksempel skulle man jo i givet fald have skrevet:

 Result:=Text+';'+LogFile+';'+NewFile+';'+IntToStr(Action)+';'+IntToStr(level);

og hvor er så besparelsen blevet af.

De enkelte FilterRecords skal ind i en filterliste:

  tFilterList = class(tStringList)

  private

    FOrden: Integer;

    FFilnavn: String;

    procedure SetOrden(const Value: Integer);

    procedure SetFilnavn(const Value: String);

  published

   Property Orden: Integer read FOrden write SetOrden;

   Property Filnavn: String write SetFilnavn;

   Procedure GemIFil;

   Procedure HentFraFil;

   Function RecordNr(ix: Integer): tFilterRec;

  end;

Som det ses, er filterlisten et barn af tStringlist, og den arver derfor alle dennes egenskaber. Jeg kunne have valgt at lave en create(Filnavn: String), for på den måde at få filnavnet ind med det samme. I stedet har jeg valgt at sætte listen op i

procedure Tdm.DataModuleCreate(Sender: TObject);

begin

 fFiltre:=tFilterlist.Create;

 fFiltre.Sorted:=True;

 fFiltre.Duplicates:=dupAccept;

 fFiltre.Filnavn:='/etc/hansogida.liste';

 fFiltre.HentFraFil;

end;


Listen skal acceptere dubletter, da dens tekst ikke nødvendigvis er forskellig post for post. Husk, at det ikke er teksten der vises på skærmen, den bruges alene til at bestemme listens orden.

For en god ordens skyld skal det nævnes, at filterlisten gemmes når programmet lukker.

procedure Tdm.DataModuleDestroy(Sender: TObject);

begin

 if dm=NIL then exit;

 fFiltre.GemIFil;

end;

Det at gemme de forskellige filtrerecords i en fil er enkelt nok. De kan jo alle sammen selv fortælle, hvad der skal stå i filen, så det er bare at gennemløbe dem alle fra en ende af.

Det lille "f" foran FilNavn indikerer, at der er tale om en privat var (tradition, men bestemt en af de gode). Udefra kaldes parameteren Filnavn, men internt arbejdes der med den ægte vare, som er fFilNavn. "Filnavn" er jo i dette tilfælde en read only property, så fFilNavn:='‘ er den eneste måde, man kan sætte en ny værdi ind i den.

procedure tFilterList.GemIFil;

var f: TextFile;

    i: Integer;

begin

 AssignFile(f,fFilNavn);Rewrite(f);

  for i:=0 to pred(count) do WriteLn(f,tFilterRec(Objects[i]).FilTekst);

 WriteLn(f,IntToStr(fOrden));             // gem orden i sidste linie

 closeFile(f);

end;

Den sidste oplysning, der gemmes i filen er den sorteringsorden, listen befinder sig i. Her ville man kunne bruge IntToStr(Orden), altså læse property Orden, men det ville være en omvej, da værdien så først skulle hentes af objectet i fOrden og returneres.

Den procedure der henter oplysningerne ind fra filen ses herunder. Den første del sker kun, hvis filen "fFilnavn" eksisterer.

procedure tFilterList.HentFraFil;

var f: TextFile;

    s: String;

begin

 Clear;

  if FileExists(fFilNavn) then

  begin

  AssignFile(f,fFilnavn);Reset(f);

  While NOT EOF(f) do

  begin

   ReadLn(f,s);

   if pos(';',s)=0

   then               // sidste linie udløser ny sortering

    Orden:=StrToInt(s)

   else               // dette er ikke sidste record, opret den

    AddObject('.',tFilterRec.Create(s));

  end;

  CloseFile(f);

  end; // Der skal mindst være et filter fra start.

  if count=0 then AddObject('.',

                            tFilterRec.Create(format('%s;%s;%s;%d;%d;%d;%d;',

                                              ['default.ida',

                                               '/var/log/httpd/access_log',

                                               '/var/log/httpd/ny_log',

                                                30,0,1,0])));

end;

Det er værd at bemærke, at hvis en linie ikke indeholder ‘;' er det fordi, det er den sidste linie =  den med sorteringsordenen. Når den linie læses sættes værdien ikke direkte i fOrden, men property Orden bruges. Forklaringen er den, at det at skrive en værdi til property'en ikke alene sætter værdien ind i den private fOrden, det udløser også en sortering af listen ved at lægge nye tekster ind i den:

procedure tFilterList.SetOrden(const Value: Integer);

var i: Integer;

begin

 FOrden := Value;

 Sorted:=False;

  for i:=0 to pred(Count) do

  with tFilterRec(Objects[i]) do

  case Orden of

  -1,0: Strings[i]:=Text;

  1: Strings[i]:=IntToStr(1000+Level); // Sørg for at "10" kommer efter "2"

  2: Strings[i]:=IntToStr(Action);

  3: Strings[i]:=NewFile;

  4: Strings[i]:=NewFile;

  end;

 Sorted:=True;

end;

(Det er nødvendigt at indlede med at sætte listen til at være usorteret, ellers kan man ikke udskifte teksterne i den).

For sjov skyld er her en lille funktion, der returnerer den filterrecord, der findes i position ix.

function tFilterList.RecordNr(ix: Integer): tFilterRec;

begin

 Result:=tFilterRec(Objects[ix]);

end;

Det er altså muligt at skrive:

with dm.FilterListe do


begin

  with RecordNr(ix) do { gør noget med den }

{eller}

  with tFilterRec(Objects[ix]) do { gør et eller andet }

end;

Der er et par andre typer i datamodulet, dem må du selv finde i kildeteksten.

I gang med formen.

Nu kommer jeg endelig til det, de fleste nok opfatter som RAD, nemlig det grafiske design. På hovedformen placeres en tPageControl. Der klikkes på den med højremusen, og oprettes to nye sider, tTabPages. Og så er jeg nået til det første svære valg. Skal der bruges std. komponentnavne eller ej.

Personligt foretrækker jeg at ændre på navnene, så de får lidt mere mening, men samtidigt, så det af navnet fremgår, hvilken type komponent, der er tale om. De to sider omdøbes derfor til hhv. tsFiltre og tsLogFiler. Konsekvensen bliver, at de to komponenter stadig optræder samlet i komponentlisten, da de starter med ts, men at de samtidig er lette at genkende.

hiMainform

Justering.

En komponent kan have forskellige former for justering. Det er ret vigtigt, at huske, at de fleste forme vil kunne trækkes til andre størrelser, så et eller andet på formen, skal kunne fylde pladsen ud. Da højre halvdel ikke sådan kan ændre størrelse, uden at det går ud over funktionaliteten, er denne sat til alignment alRight. Dette gør, at den beholder sin bredde uanset formes bredde, og at den holder sig i højre side.

Filterlisten i venstre sider er derefter sat til alignment alClient, så den fylder formen ud. De enkelte linier i filterlisten vil så blive mere eller mindre komplette, brugeren om det.

Selve formen skal jo vises på skærmen et eller andet sted. Også dette kan bestemmes vha. parameteren Position, som jeg her har sat til poDesktopCenter.

Andre komponenter, som fx. labels og editorer kan også have justeringsmuligheder for deres indhold. Således vil en editor brugt til tal normalt være højrejusteret, mens en der anvendes til tekster vil være venstrejusteret.

Filterlisten.

Denne komponent har fået sat sin style til lbOwnerDrawFixed, hvilket betyder, at det er min opgave at skrive, hvad der nu skal stå i listen, og at jeg skal holde mig indenfor det område, som Kylix selv ville have brugt.

Det er her muligt at vælge lbOwnerDrawVariable. I det tilfælde, skulle jeg have skrevet en procedure, der for hvert enkelt element i listen, ville oplyse dets højde.

Med lbOwnerDrawFixed bliver det nu nødvendigt at reagere på det event i lbFiltre, der kaldes onDrawItem. Fanen events vælges, og der dobbeltklikkes på eventet.

Resultatet bliver (med lidt skriveri mellem begin og end) dette:

procedure TForm1.lbFiltreDrawItem(Sender: TObject; Index: Integer;

  Rect: TRect; State: TOwnerDrawState; var Handled: Boolean);

var s: String;

begin

  if (Index>=0) and (Index<dm.Filtre.Count) then

  with (Sender as TListBox).Canvas, tFilterRec(dm.Filtre.Objects[Index]) do

  begin

  FillRect(Rect);

  s:=ListeTekst(dm.Filtre.Orden);

  TextRect(Rect,Rect.Left+2,Rect.Top+2,s);

  Handled:=True;

  end;

end;

Det første der sker er, at det tildelte område renses med FillRect, det næste, at den listetekst objectet i denne linie kan give fra sig, udskrives. Nu begynder forarbejdet at give pote. Jeg har et og kun et sted at rette, hvis det der udskrives ikke er korrekt, og det er i objectets funktion Listetekst. At jeg lægger den ind i en variabel s først skyldes alene, at jeg gerne vil kunne stoppr programmet på dette sted, og se resultatet af kaldet til Listetekst i debuggeren.

Linien

TextRect(Rect,Rect.Left+2,Rect.Top+2,ListeTekst(dm.Filtre.Orden));

ville give samme resultat på en mere kompakt måde, og den ville spare pladsen i RAM, som "S" optager.

I kaldet til denne procedure oplyser Kylix hvilken tilstand teksten er i, og man kan så beslutte, om den std. farve der er sat op på listboxen skal anvendes eller erstattes af en anden. Alle de oplysninger, der er til rådighed om udskrifter (Brush, Pen osv.) kan ændres på dette tidspunkt, og ændringerne vil vise sig i listen. Med Listeteksten lagt ind i en variabel, kunne det måske være interessant at udskrive det første element i fed skrift (fsBold), og resten i normalskrift.

Hvad enten man klikker på listen, eller bruger tastaturet til at flytte op/ned i listen, skal højre side sættes op, så den afspejler oplysningerne i den valgte linie i filterlisten.

Dette sker igen via events, her onKeyUp og onClick:

procedure TForm1.lbFiltreClick(Sender: TObject);

begin

 SetupRight; // sæt værdier i højre del af skærmen

end;


procedure TForm1.lbFiltreKeyUp(Sender: TObject; var Key: Word;

  Shift: TShiftState);

begin

 SetupRight; // sæt værdier i højre del af skærmen

end;


procedure TForm1.SetupRight;

begin

 if (lbFiltre.ItemIndex>=0) and (lbFiltre.ItemIndex<dm.Filtre.Count) then

   with tFilterRec(dm.Filtre.Objects[lbFiltre.ItemIndex]) do

 begin

  seNiveau.Value:=Level;

  cbHandling.ItemIndex:=Action;

  edSoegeTekst.Text:=Text;

  panLogFile.Caption:=LogFile;

  panNewFile.Caption:=NewFile;

  cbLogFelter.Items:=dm.LogFilFelter(LogFile,StatusBar1.Panels[0]);

  end;

end;

Da der er to forskellige events der skal udløse denne opsætning, og de to events ikke kaldes med samme parametre, har jeg skrevet en lille procedure til at udføre arbejdet. Denne kaldes fra begge events.

Her kunne jeg have valgt at undersøge, om der var trykket på en bestemt tast, fx. [DEL] eller [CTRL+DEL], og så have udført en bestemt handling, hvis det var tilfældet. Teknikken demonstreres i loglisterne senere.

Filterlistens knapper.

[Ny] og [Slet] giver sig selv.

[Opret kopi] giver en kopi af det filter man har valgt. Har man intet valgt, kommer der blot et nyt filter ind.

For at komme til at skrive den ønskede kode, klikkes på knappen i formen, og der oprettes et onClick event:

procedure TForm1.btnKopiFilterClick(Sender: TObject);

begin

  if lbFiltre.Itemindex=-1 then btnNytFilterClick(Sender)

  else

  with dm.Filtre do

   AddObject(‘.',

              tFilterRec.Create(

                     tFilterRec(dm.Filtre.Objects[lbFiltre.ItemIndex]).FilTekst

                               )

            );

 dm.Filtre.Orden:=cbFilterOrden.ItemIndex;

 lbFiltre.Items:=dm.Filtre;

 lbFiltre.Invalidate;

end;

Hvis filterlisten er tom, sendes klikket videre til btnNytFilterClick. Det er det event, der er knyttet til knappen [Ny]. Der er således intet i vejen for at kalde "events" direkte fra komponenten selv. Det er ganske almindelige procedurer, de er ikke en gang private, så de kan for den sags skyld kaldes direkte udefra. Her skulle det ske med Form1.btnNytFilterClick(NIL).

Logfilerne.

Alle de logfiler, der er brugt i filtrene, hvad enten det nu er som indgang eller udgangsfil, opsamles i en logliste (venstre side af skærmen). Ved et klik på en linie ske der to ting.

Man kan nu:

Sletning af log-linier bør kun ske i de filer man har filtreret ud i, ellers kan der gå nye loglinier tabt. Den del af programmet fungerer altså ikke optimalt, når det gælder de "rigtige" logfiler, men man kan da få ryddet op i sine resultat-filer.

Filterdialogen.

Dialogen som sådan er ganske enkel. En listbox i toppen til fremvisning og et par knapper i bunden til dialogens funktioner. Listen skal kunne skifte mellem visning af den oprindelige logfil, eller rettere de linier, der vil overleve i den, og de udvalgte loglinier, enten de nu skal slettes eller tilføjes i en ny logfil. Da loglinier er lange og mange, vil det ikke være hensigtsmæssigt at vise mere end en af de tre lister af gangen, og hvorfor så ikke nøjes med en enkelt listbox at fremvise dem i.

Man kan oprette denne dialog på flere måder. Under File,New kan man vælge en af de dialoger Borland har strikket sammen, og bruge den som udgangspunkt, eller man kan vælge en ny, tom form, og selv ændre den, så den bliver til en dialog. Personligt foretrækker jeg det sidste, da det er meget sjældent, at Borlands forslag passer mig helt, og har man først valgt et af dem, kan man ikke fjerne de komponenter, de er født med.

Fordelingen af loglinierne skal følge et og kun et filter, når resultatet skal vises i filterdialogen. Når programmet skal udføre alle filtreringer, skal dette ske uden at der først vises noget i filterdialogen, men stadig filter for filter i ordnet rækkefølge. Sorteringen er noget, der ikke kræver en brugerfladeadgang, så det hører til i datamodulet. Dette er utroligt vigtigt. Jeg havde i første omgang lagt disse rutiner ind i filterdialogens kildetekst, med det resultat, at de skulle skrives om, da programmet skulle anvendes som er service kaldt fra cron.

tDM skal udstyres med:

De to lister oprettes som properties Loglinier og NyeLinier af typen tStringList. Under DataModuleCreate oprettes de to lister, og under DataModuleDestroy nedlægges de igen. Listerne skal ikke være sorterede, da det normalt er hensigtsmæssigt at få sine loglinier i den oprindelige rækkefølge.

Procedure OpbygFilterResult skal have to parametre med. Den første er indeksnummeret i filterlisten på det filter, der skal følges, den anden er en besked om, hvorvidt filtreringen reelt skal gennemføres, eller om dette måske skal vente på, at brugeren godkender eller fortryder.

Filtreringen sker ud fra et øjebliksbillede af den oprindelige log-fil. Den tager så lang tid, så der kan nå et komme nye linier ind i den originale log. Det vil derfor være nødvendigt i den "rigtige" filtrering at følge denne fremgangsmåde:

For at kunne fremvise dialogen modalt skal man sno sig lidt.

På trods af, at dialogen er sat til StayOnTop og at den åbnes med ShowModal så kan man alligevel luske sig ned i hovedprogrammet og arbejde med den. Dette er et problem, der skjuler sig dybt i windowsmakeren, QT eller Linux, jeg må blot konstatere, at tingene ikke altid virker som de burde.

At håndtere det problem, der består i, at man kan åbne en filterdialog mens den i forvejen er åben, kan naturligvis løses på mange måder. Man kan teste om dialogen er assigned, og ignorere hvis det er tilfældet, eller som jeg har valgt, man kan reparere på det, der lige mangler i at Kylix/QT opfører sig ordentligt.

I dialogen tilføjes et event:

    procedure FormDeactivate(Sender: Tobject); // standard event aktiveres.


      published

    property onFilterClose: tNotifyevent read FonFilterClose write SetonFilterClose;

I implementationen holdes der øje med, om man forsøger at forlade dialogen:

procedure TFilterDialog.FormDeactivate(Sender: TObject);

begin

  if (csDestroying in componentstate  ) then exit; // Det her er i orden

  if Visible then if CanFocus then SetFocus;       // Det her er ikke!

end;

Med andre ord, fokus tvinges tilbage hvor jeg vil have det, med mindre programmet er ved helt at lukke dialogen. Skift til et helt andet program er stadig muligt, "verden" er begrænset til dette program selv.

Automatisk eksekvering.

For at kunne eksekvere et program automatisk fra fx. crontab, skal det kunne køre uden brug af X.

Dette krav opfylder cmhansogida, der i al sin enkelthed er følgende stump program:

program cmhansogida;


{$APPTYPE CONSOLE}

uses

  dmodul in ‘dmodul.pas' {dm: TDataModule};


var i, OldOrden: Integer;

begin

 dm:=tDM.Create(NIL);

 OldOrden:=dm.Filtre.Orden;

 dm.Filtre.Orden:=1;

  for i:=0 to pred(dm.Filtre.Count) do  dm.OpbygFilterResult(i,true);

 dm.Filtre.Orden:=OldOrden;

 dm.Free;

end.

I /etc/crontab eller en af dens underfiler (time, daglig, ugentlig efter smag) lægges nu denne linie ind:

28 * * * *    root /home/hansogida/cmhansogida

Den gennemfører en oprydning hver gang klokken er 28 minutter over hel, (hvilket er et meget omhyggeligt udvalgt tidspunkt;).

Downloads:

Der tages alle former for forbehold overfor disse pakker. Jeg har ikke evnet at lave en .rpm udgave, så du må tage til takke med .zip versioner af de tre pakker. Min placering er i /home/hansogida/, men herfra kan de blot flyttes.

Kildeteksten gemmer sig i HansOgIda-src.zip.

De kompilerede programmer (HansOgIda + cmhansogida)  i HansOgIda.zip

Nødvendige .so filer ligger her i HansOgIda-so.zip

.so filerne kan du fx. placere i /usr/lib (eller et andet ¨/lib dir), hvorefter du skal køre programmet ldconfig for at få aktiveret dem korrekt. ldconfig skal kun køres, når der lægges nye .so filer ind i et af ¨/lib bibliotekerne.

Der ydes ingen form for support på programmet, men kommer der indlæg om det på en sslug-liste er det da tænkeligt, at jeg vil søge at besvare dem! Det er også i det forum, du evt. kan komme med ideer til programmet.

Ophavsret og patenter.

Kylix og QT er formodentlig registrerede varemærker ejet af hhv Borland og Troll Tech. Der er i dette program ikke ændret så meget som et komma i nogen del af de to programmer/pakker (men det bør gøres snart af dem selv).

Hvis du bruger dette  program som grundlag for et nyt program til håndtering af logfiler, skal du angive i din kildetekst, at du har gjort dette, og at det oprindelige program er skrevet af E. Sjørlund i 2001.

Snupper du blot stumper fra det, og bruger dem i andre sammenhænge er det helt fint. Jeg har ikke udtaget patent på programmet;)

Skulle du have lært noget nyt og nyttigt, så brug det til at gavne andre - det gavner alle.

Tak til

En tak til Hans Schou (chlor@schou.dk), som med sit link i et indlæg på [sslug-misc] gav inspirationen til programmet.

Det link, der udløste det hele giver stadig fejl. Med jævne mellemrum får jeg besøg i min fejl-log af Hans' venner, men de lever kun i fejlloggen til 28 minutter over, så er det ud i en venne-log, der er oprettet til formålet. Her kan jeg så bruge ledige stunder til at se efter, hvem det egentlig er Hans sådan omgås.