← ST-Computer 06 / 1989

Lovely Helper: Ein Desk-Accessory, Teil 3

Grundlagen

Nachdem wir in der letzten Folge einige Vorarbeiten fĂŒr unseren Druckerspooler geleistet haben, ist es nun an der Zeit, das Werk zu vollenden. Bereits fertiggestellt hatten wir letztes Mal die komplette Parameterverwaltung und die Routinen zum Ausdrucken der unterschiedlichen Dokumente. Heute wird es unsere Aufgabe sein, eine Verwaltungsroutine zu erstellen, die die Dokumentauswahl ermöglicht. Dabei sollen soviele Dokumentparameter erfaßt werden, daß es auch ohne Pufferung der kompletten Dateien möglich ist, zu einem spĂ€teren Zeitpunkt mit dem Ausdruck zu beginnen. Auf diese Weise können wir sehr viel mehr Dokumente hintereinander wegdrucken, da der Speicherplatz fĂŒr ein paar Dokumentparameter sehr viel niedriger zu veranschlagen ist als der fĂŒr die zugehörigen Dateien. Die Arbeit der oben angesprochenen Verwaltungsroutine beschrĂ€nkt sich dadurch auf das Parametereinlesen, -verifizieren und -verwalten. Der Ausdruck erfolgt dann mit einer separaten Routine, die nichts weiter zu tun hat, als die Daten der entsprechenden Dokumente verfĂŒgbar zu machen und eine Einbettung unserer Druckoperationen in die Accessory-Umgebung zu bewerkstelligen.

Eine Warteschlange

Ein guter Anfang ist es, zunĂ€chst festzulegen, wie die interne Datenstruktur zur Speicherung der Dokumentparameter aussieht. Besonders sinnvoll erscheint mir hier eine Warteschlange (englisch: queue), da dort die Dokumente in der Reihenfolge gedruckt werden, in der sie zuvor in die Datenstruktur eingebracht wurden. Die Komponenten der Warteschlange haben dabei Variablen fĂŒr folgende Dokumentparameter zu enthalten:

  • den kompletten Dateinamen (inklusive Pfadprefix)
  • den Typ der Datei
  • die Anzahl der zu erstellenden Kopien (1-99)
  • FĂŒr Textdateien ist zusĂ€tzlich noch die Schriftart, in der der Ausdruck erfolgen soll, abzuspeichem. Mögliche Variationen sind hier: 2 Schriftarten, NLQ, Schmalschrift, Breitschrift und Fettdruck.

Eine entsprechende Typdefinition findet sich in den Zeilen 11 -20 des Listings 7 (queue_entry). Die komplette Warteschlange (queue_type, Zeilen 22-25) besteht nun aus einem Array 100 solcher Dokumentparameter zusammen mit einem Indexzeiger auf die letzte belegte Array komponente.

Anmerkung: An sich bin ich eher ein Freund der dynamischen Speicherverwaltung. Ich sehe diese Begrenzungen, die ich oben in die Dokumentanzahl einbaue, also Ă€ußerst ungern. Da wir aber bei Accessories meist nur einen sehr knapp bemessenen Programmstack zur VerfĂŒgung haben, werde ich mir heute - und bis zum (bitteren) Ende dieser Serie - irgendwelche grĂ¶ĂŸeren Zeigerexkursionen verkneifen. Auf einem Objekt obigen Datentyps benötigen wir folgende Operationen, um eine funktionierende Warteschlange zu erhalten:

  1. eine Initialisierung (init_queue, Zeilen 31-47)
  2. das AnhÀngen eines Eintrags am Ende der Warteschlange (enter_queue, Zeilen 49-65)
  3. das Löschen eines Eintrages an beliebiger Position der Warteschlange {remove queue, Zeilen 67-87), um:
    a. nach Ausdruck eines Dokumentes den entsprechenden Eintrag aus der Warteschlange zu entfernen,
    b. eventuell unabsichtlich eingehĂ€ngte EintrĂ€ge im Nachhinein rĂŒckgĂ€ngig machen zu können.

Die beiden letzten Punkte sind dabei mit einer FehlerĂŒberprĂŒfung auf eine zulĂ€ssige Warteschlangenposition zu sichern. Die Implementierungen sind recht einfach, weshalb ich darauf nicht weiter eingehen werde.

Objekt Objekttyp LĂ€nge Diverses
SID1-SID10 TEXT 2
TYP1-TYP10 TEXT 8
PARA1-PARA10 TEXT 5
PATH1-PATH10 TEXT 40
SPOOLAPP BUTTON Flags: Selectable & Exit
SPOOLDEL BUTTON "
SPOOLPAR BUTTON "
SPOOLEX BUTTON "
SPOOLZUR BUTTON "
SPOOLVOR BUTTON "

Tabelle 1: Objektdaten zum Dialog DSPOOLER

Die heutigen Dialoge

Was wir nun noch benötigen, ist zunĂ€chst ein Dialogsystem, mit dem die EintrĂ€ge in unserer Warteschlange verwaltet werden können. Als erstes wird hier ein Dialog zur Übersicht aller Dokumente benötigt. Er soll zudem noch das AnwĂ€hlen sĂ€mtlicher anderer Funktionen - inklusive der Parameterverwaltung aus der letzten Folge - ermöglichen. Dialog DSPOOLER - Abbildung 8 - erfĂŒllt diese Aufgaben. Im oberen Teil besitzt er Raum zur Ausgabe von zehn Dokumenten; im unteren Teil sind Feldtasten angebracht, die sowohl das BlĂ€ttern in den EintrĂ€gen der Warteschlange erlauben, als auch die Auswahl aller anderen Funktionen ermöglichen.

In Tabelle 1 finden Sie eine genaue Beschreibung aller enthaltenen GEM-Objekte dieses Dialoges. Eine recht umfangreiche Liste. Die Zuordnung der Dialogbestandteile zu den einzelnen Funktionen dĂŒrfte dabei einleuchtend sein.

Die weiteren Dialoge, die wir heute benötigen werden, sind alle direktes oder indirektes Resultat einer Auswahl im DSPOOLER. Zur Verdeutlichung dieser Situation betrachten Sie bitte die Abbildung 12. AufgefĂŒhrt ist der Aufrufbaum aller am Spooler beteiligten Dialoge. Durchwandern wir diese Abbildung einmal.

1. Selektion von “AnhĂ€ngen” in DSPOOLER:

Als Folge der Selektion von “AnhĂ€ngen” im Dialog DSPOOLER erscheint zunĂ€chst die Ihnen allen bekannte Fileselectorbox. Sie gestattet es Ihnen, einen Dateinamen einzugeben. Je nachdem welchen Dateityp unser Programm fĂŒr die ausgewĂ€hlte Datei ermittelt, erscheint nun entweder der Dialog DRUCTEXT (die ausgewĂ€hlte Datei ist eine Textdatei), oder der Dialog DRUCGRAP (die ausgewĂ€hlte Datei besitzt eines der drei zulĂ€ssigen Grafikformate). DRUCTEXT - Abbildung 9 und Tabelle 2 - erlaubt dabei die Angabe der Schriftarten fĂŒr den Ausdruck der selektierten Textdatei sowie die Eingabe der Dokumentanzahl.

Bei der Ausgabe von Grafiken - DRUCGRAP, Abbildung 10 und Tabelle 3 - sind noch weniger GEM-Objekte zu beachten. Informiert wird lediglich ĂŒber die Art der ausgewĂ€hlten Datei sowie ĂŒber ihre Startposition in der Warteschlange. Einzugeben ist nur die Anzahl der Kopien.

2. Selektion von “Löschen” in DSPOOLER:

Soll nun ein Dokument, etwa wegen falscher Eingabe, aus der Warteschlange entfernt werden, wird der Dialog DELSID, Abbildung 11 und Tabelle 4, ausgefĂŒhrt, um die zugehörige Nummer des Eintrags in der Warteschlange einzulesen. DELSID hat entsprechend nur ĂŒber eine Eingabemöglichkeit fĂŒr eine Warteschlangenposition und ĂŒber die obligatorische BestĂ€tigung, bzw. den Abbruch seiner Funktion zu verfĂŒgen.

3. Selektion von “Parameter” in DSPOOLER:

Ferner binden wir noch - bei Selektion von “Parameter” - die Parameterverwaltung (do_parameter) aus der letzten Folge ein.

Die Dialogverwaltung

Wenn wir uns nun wiederum dem Listing 7 zuwenden, findet sich in der Prozedur do_spooler (Zeilen 89-357) die Operation, die die obigen Dialoge alleine zu verwalten hat. Wie schon fĂŒr die Parameterverwaltung, so gilt auch fĂŒr die Warteschlangenverwaltung, daß der Quellcode zwar recht lang ist, aber nur wenige interessante Stellen besitzt. Große Teile der Prozedur do_spooler beschrĂ€nken sich auf die ĂŒblichen Dialogphasen, also Initialisierung, AusfĂŒhrung und Extraktion der eingegebenen Daten. Sehen wir uns das einmal im einzelnen an. Wir folgen dabei dem Aufrufbaum der Dialoge:

Beginnen mĂŒssen wir also im Anweisungsteil von do_spooler (Zeilen 337-357. In der zentralen REPEAT-Schleife des Anweisungsteils wird zunĂ€chst mit der Prozedur set_dialog (Zeilen 209-290) der Dialog DSPOOLER initialisiert. Daß set_dialog dabei recht lang ist, soll uns wenig stören, es hat ja immerhin einige GEM-Objekte zu verwalten. In der Substanz können wir es aber getrost vergessen. Wichtig ist vielleicht nur eine genaue Funktionsbeschreibung: In AbhĂ€ngigkeit von der Variablen seite zeigt setjdialog im Dialog DSPOOLER jeweils eine der zehn Dokumentseiten unserer Warteschlange.

Objekt Objekttyp LĂ€nge Diverses
SIDTEXT TEXT 2
ANZKOPTE ETEXT 2 Maske: ‘99’
ZEICH1 BUTTON Flags: Selectable, Exit & Default
ZEICH2 BUTTON Flags: Selectable & Exit
DRUCEXIT BUTTON "
DRUCNLQ BUTTON Flag: Selectable
DRUCSCHM BUTTON "
DRUCBREI BUTTON "
DRUCFETT BUTTON "

Tabelle 2: Objektdaten zum Dialog DRUCTEXT

Im Anweisungsteil von do_spooler erfolgt nun, direkt auf den Aufruf von set_dialog, die AusfĂŒhrung des Dialoges mit anschließender Analyse der eingelesenen Feldtaste und nachfolgender AusfĂŒhrung der selektierten Operation (CASE-Statement, Zeilen 344-353). Im einfachsten Fall beschrĂ€nkt sich dabei die AusfĂŒhrung der selektierten Operation auf das BlĂ€ttern in den Seiten der Warteschlange. “Etwas” komplexer sind die beiden Funktionen zum AnhĂ€ngen und Löschen in der Warteschlange, die wir nun besprechen werden.

ZunĂ€chst das AnhĂ€ngen (Zeilen 292-324). Die zugehörige Funktion - natĂŒrlich heißt sie auch anhaengen - stĂŒtzt sich ihrerseits auf die drei boolschen Funktionen get_name (Zeilen 191-207), do_text (Zeilen 94-132) und do_graphik (Zeilen 134-166) ab. Mit get_name wird dabei als erstes ĂŒber die Fileselectorbox ein Dateiname eingelesen. Sollte ein vollstĂ€ndiger Dateiname eingelesen worden sein - die Fileselectorbox wurde ĂŒber OK verlassen -, ist allerdings noch nicht sichergestellt, daß die Datei auch wirklich existiert. Dummerweise ist es in der Fileselectorbox bekanntlich nicht nur möglich, Dateinamen zu selektieren, sie können auch einfach nur eingegeben werden. FĂŒr diesen Fall prĂŒft get_name noch - mit Hilfe des Betriebssystemaufrufs fsfirst - auf das Vorhandensein der selektierten Datei. Der boolsche Funktionswert von get_name zeigt dann entsprechend an, ob eine zulĂ€ssige (!) Datei selektiert wurde oder nicht. Der Dateiname steht dann in der globalen Variablen datei_name.

Anmerkung: Die genaue Funktionsweise von fsfirst möchte ich heute nicht erlĂ€utern. Dies wird im Serienteil ĂŒber den Directorydruck geschehen, wo wesentlich ausgiebiger von fsfirst und benachbarten Operationen wie fgetdta und fsnext Gebrauch gemacht wird.

Im weiteren Verlauf von anhaengen wird die LĂ€nge 1 der selektierten Datei ermittelt. (ErklĂ€rungen hierzu möchte ich ebenfalls, mit gleicher BegrĂŒndung wie oben, vertagen.) Mit Hilfe dieses Wertes können wir nun feststellen, von welcher Art die selektierte Datei ist. Eine LĂ€nge von 32000 Bytes lĂ€ĂŸt auf das Format Neochrome oder Monostar schließen, 32002 Bytes entsprechen dem Doodle-Format, 32034 dem Degas-Format, alle ĂŒbrigen DateilĂ€ngen dem Format Text.

Diese Art der automatischen Dateierkennung birgt zwar einige Gefahren in sich, z.B. könnte eine Textdatei gerade die LĂ€nge eines der drei Grafikformate besitzen, wir wollen es aber trotzdem dabei bewenden lassen, weil diese Fehler nicht gerade wahrscheinlich sind und außerdem eine andere Methode der Dateierkennung - etwa auf statistischem Wege - sehr viel aufwendiger wĂ€re.

Entsprechend dem ermittelten Typ ist nun entweder in die Funktion do_text (fĂŒr Textdateien) oder in die Funktion do_graphik (fĂŒr die Grafikformate) zu verzweigen. Diese beiden boolschen Funktionen zeigen nichts, was ĂŒber das einfache Dialog-Handling (Daten rein; AusfĂŒhren; Daten raus) hinausgeht. Deswegen werden wir uns auch hier weitergehende Betrachtungen schenken.

Am Ende der Berechnungen beider Funktionen liegt auf jeden Fall entweder ein gĂŒltiger Eintrag zum EinhĂ€ngen in unsere Warteschlange vor (enter_queue), oder der entsprechende Funktionswert ist false, wir können dann den EinfĂŒgevorgang abbrechen.

Einfacher ist das Löschen eines Eintrags (loeschen, Zeilen 326-335). Eingegeben wird lediglich eine Warteschlangenposition (mit do_sidkill, Zeilen 168-189), die dann mit der entsprechenden Zugriffsoperation (remove_queue) gelöscht wird. Damit wĂ€re die reine Verwaltung der Warteschlange, also der Teil, der bei Selektion unseres Accessorys ausgefĂŒhrt werden soll, abgeschlossen.

Der Druckvorgang

Wenden wir uns dem Druckvorgang zu. Wie bereits in der letzten Folge angeklungen, wird er ĂŒber einen get_event-gesteuerten Timer-Interrupt durchgefĂŒhrt, um im Hintergrund arbeiten zu können. Dabei wird die Routine printer_interrupt (Zeilen 359-448) jeweils einmal (pro interrupt) aufgerufen. Hier ist zunĂ€chst eine Analyse des momentanen Zustandes notwendig, um im Druckvorgang korrekt fortfahren zu können. Benutzt wird dazu die Variable spoolstatus, der leicht entnommen werden kann, wie weiter zu verfahren ist. Die einfacheren FĂ€lle liegen vor, wenn sich gerade eine Datei im Druck befindet.

Im Falle einer Grafikdatei ist pic_next so oft aufzurufen, wie dies in den Parametern angegeben ist (gr_per_inter). Wird dabei das Bildende erreicht, ist der KopienzÀhler des aktuellen Warteschlangenkopfes runterzuzÀhlen. Ist dies nicht mehr möglich, wird der Warteschlangenkopf entfernt (remove_queue) und der spoolstatus wechselt auf unused.

Genauso ist bei einer Textdatei zu verfahren, mit dem einzigen Unterschied, daß die Funktion text_next selber eine Schleife ĂŒber die auszugebende Anzahl Charakter enthĂ€lt, also nur der entsprechende Parameter (parameters.t_per_inter) zu ĂŒbergeben ist.

Liegt zu Beginn der Prozedur printer_interrupt kein offenes Dokument an, ist ein neues zu besorgen (get_new, Zeilen 364-404). get_new ĂŒberprĂŒft dazu die Warteschlange auf das Vorhandensein von Elementen. Ist dies der Fall, so ist, in AbhĂ€ngigkeit vom Dateityp des nĂ€chsten Dokumentes, ein entsprechender Initialvorgang einzuleiten.

FĂŒr Grafikdateien ist mit der Pascal-Operation read_screen das Bild mit dem angegebenen Dateityp zu laden. Nach erfolgtem Ladevorgang ist pic_first aufzurufen. Bei Fehlern kann davon ausgegangen werden, daß das Bild auch bei spĂ€teren Zugriffen nicht auffindbar ist. Der Eintrag wird daher komplett aus der Warteschlange entfernt.

Bei Textdateien ist der Initialvorgang ganz Ă€hnlich. Nur daß hier, an Stelle des Ladens der kompletten Datei, lediglich ein reset zu erfolgen hat. (Das schrittweise Laden ĂŒbernimmt bei Texten die Funktion text_next selber.) Der weitere Ablauf ist vollstĂ€ndig analog zu den obigen AusfĂŒhrungen.

(* resource set indices for SPOOLER *)

CONST paramete = 0; (* form/dialog ) paraseit = 3; ( TEXT in tree PARAMETE ) chr1 = 6; ( TEXT in tree PARAMETE ) ord1 = 7; ( TEXT in tree PARAMETE ) makro1 = 8; ( FTEXT in tree PARAMETE ) chr2 = 9; ( TEXT in tree PARAMETE ) ord2 = 10; ( TEXT in tree PARAMETE ) makro2 = 11; ( FTEXT in tree PARAMETE ) chr3 = 12; ( TEXT in tree PARAMETE ) ord3 = 13; ( TEXT in tree PARAMETE ) makro3 = 14; ( FTEXT in tree PARAMETE ) chr4 = 15; ( TEXT in tree PARAMETE ) ord4 = 16; ( TEXT in tree PARAMETE ) makro4 = 17; ( FTEXT in tree PARAMETE ) chr5 = 18; ( TEXT in tree PARAMETE ) ord5 = 19; ( TEXT in tree PARAMETE ) makro5 = 20; ( FTEXT in tree PARAMETE ) chr6 = 21; ( TEXT in tree PARAMETE ) ord6 = 22; ( TEXT in tree PARAMETE ) makro6 = 23; ( FTEXT in tree PARAMETE ) chr7 = 24; ( TEXT in tree PARAMETE ) ord7 = 25; ( TEXT in tree PARAMETE ) makro7 = 26; ( FTEXT in tree PARAMETE ) chr8 = 27; ( TEXT in tree PARAMETE ) ord8 = 28; ( TEXT in tree PARAMETE ) makro8 = 29; ( FTEXT in tree PARAMETE ) chr9 = 30; ( TEXT in tree PARAMETE ) ord9 = 31; ( TEXT in tree PARAMETE ) makro9 = 32; ( FTEXT in tree PARAMETE ) chr10 = 33; ( TEXT in tree PARAMETE ) ord10 = 34; ( TEXT in tree PARAMETE ) makro10 = 35; ( FTEXT in tree PARAMETE ) paraspei = 36; ( BUTTON in tree PARAMETE ) parasync = 37; ( BUTTON in tree PARAMETE ) parazuru = 38; ( BUTTON in tree PARAMETE ) parachec = 39; ( BUTTON in tree PARAMETE ) paravor = 40; ( BUTTON in tree PARAMETE ) paratest = 41; ( BUTTON in tree PARAMETE ) paraexit = 42; ( BUTTON in tree PARAMETE ) dructext = 1; ( form/dialog ) sidtext = 4; ( TEXT in tree DRUCTEXT ) anzkopte = 6; ( FTEXT in tree DRUCTEXT ) zeich1 = 8; ( BUTTON in tree DRUCTEXT ) zeich2 = 9; ( BUTTON in tree DRUCTEXT ) drucexit = 10; ( BUTTON in tree DRUCTEXT ) drucnlq = 12; ( BUTTON in tree DRUCTEXT ) drucschm = 13; ( BUTTON in tree DRUCTEXT ) drucbrei = 14; ( BUTTON in tree DRUCTEXT ) drucfett = 15; ( BUTTON in tree DRUCTEXT ) dspooler = 2; ( form/dialog ) sid1 = 4; ( TEXT in tree DSPOOLER ) typ1 = 5; ( TEXT in tree DSPOOLER ) para1 = 6; ( TEXT in tree DSPOOLER ) anz1 = 7; ( TEXT in tree DSPOOLER ) path1 = 8; ( TEXT in tree DSPOOLER ) sid2 = 9; ( TEXT in tree DSPOOLER ) typ2 = 10; ( TEXT in tree DSPOOLER ) para2 = 11; ( TEXT in tree DSPOOLER ) anz2 = 12; ( TEXT in tree DSPOOLER ) path2 = 13; ( TEXT in tree DSPOOLER ) sid3 = 14; ( TEXT in tree DSPOOLER ) typ3 = 15; ( TEXT in tree DSPOOLER ) para3 = 16; < TEXT in tree DSPOOLER ) anz3 = 17; ( TEXT in tree DSPOOLER ) path3 = 18; ( TEXT in tree DSPOOLER ) sid4 = 19; ( TEXT in tree DSPOOLER ) typ4 = 20; ( TEXT in tree DSPOOLER ) para4 = 21; ( TEXT in tree DSPOOLER ) anz4 = 22; ( TEXT in tree DSPOOLER ) path4 = 23; ( TEXT in tree DSPOOLER ) sid5 = 24; ( TEXT in tree DSPOOLER ) typ5 = 25; ( TEXT in tree DSPOOLER ) para5 = 26; ( TEXT in tree DSPOOLER ) anz5 = 27; ( TEXT in tree DSPOOLER ) path5 = 28; ( TEXT in tree DSPOOLER ) sid6 = 29; ( TEXT in tree DSPOOLER ) typ6 = 30; ( TEXT in tree DSPOOLER ) para6 = 31; ( TEXT in tree DSPOOLER ) anz6 = 32; ( TEXT in tree DSPOOLER ) path6 = 33; ( TEXT in tree DSPOOLER ) sid7 = 34; ( TEXT in tree DSPOOLER ) typ7 = 35; ( TEXT in tree DSPOOLER ) para7 = 36; ( TEXT in tree DSPOOLER ) anz7 = 37; ( TEXT in tree DSPOOLER ) path7 = 38; ( TEXT in tree DSPOOLER ) sid8 = 39; ( TEXT in tree DSPOOLER ) type = 40; ( TEXT in tree DSPOOLER ) para8 = 41; ( TEXT in tree DSPOOLER ) anz8 = 42; ( TEXT in tree DSPOOLER ) path8 = 43; ( TEXT in tree DSPOOLER ) sid9 = 44; ( TEXT in tree DSPOOLER ) typ9 = 45; ( TEXT in tree DSPOOLER ) para9 = 46; ( TEXT in tree DSPOOLER ) anz9 = 47; ( TEXT in tree DSPOOLER ) path9 = 48; ( TEXT in tree DSPOOLER ) sid10 = 49; ( TEXT in tree DSPOOLER ) typ10 = 50; ( TEXT in tree DSPOOLER ) para10 = 51; ( TEXT in tree DSPOOLER ) anz10 = 52; ( TEXT in tree DSPOOLER ) path10 = 53; ( TEXT in tree DSPOOLER ) spoolapp = 54; ( BUTTON in tree DSPOOLER ) spooldel = 55; ( BUTTON in tree DSPOOLER ) spoolpar = 56; ( BUTTON in tree DSPOOLER ) spoolex = 57; ( BUTTON in tree DSPOOLER ) spoolzur = 58; ( BUTTON in tree DSPOOLER ) spoolvor = 59; ( BUTTON in tree DSPOOLER ) drucgrap = 3; ( form/dialog ) drucsid = 4; ( TEXT in tree DRUCGRAP ) drucanz = 6; ( FTEXT in tree DRUCGRAP ) dructyp = 8; ( TEXT in tree DRUCGRAP ) drucex = 9; ( BUTTON in tree DRUCGRAP ) drucdruc = 10; ( BUTTON in tree DRUCGRAP ) synchro = 4; ( form/dialog ) synclaen = 3; ( FTEXT in tree SYNCHRO ) syncgrap = 7; ( FTEXT in tree SYNCHRO ) synctext = 8; ( FTEXT in tree SYNCHRO ) syncexit = 9; ( BUTTON in tree SYNCHRO ) syncsetz = 10; ( BUTTON in tree SYNCHRO ) delsid = 5; ( form/dialog ) delsided = 4; ( FTEXT in tree DELSID ) delsiddo = 5; ( BUTTON in tree DELSID ) delsidun = 6; ( BUTTON in tree DELSID *)

Das Accessory Druckerspooler

Wie versprochen, erfolgt nun noch das Zusammenbinden der Sources dieser und der letzten Folge zum Accessory Druckerspooler (Listing 8). Im Variablendeklarationsteil sind dazu, ĂŒber die obligatorischen Variablen hinaus, die Dialoge der letzten beiden Folgen zu deklarieren. In der Initialisierung sind sie dann entsprechend zuzuweisen.

Ganz wichtig fĂŒr die Initialisierung: Sie enthĂ€lt die Umlenkung des spoolchannels, ohne die wir einen dieser bei Accessories fatalen Laufzeitfehler erzeugen wĂŒrden.

Im Hauptprogramm zeigt sich ein etwas differenzierterer get_event-Aufruf, als wir ihn bis jetzt kennengelemt hatten. Statt einem event (ac_open) haben wir nun gleich zwei anzufordern, nÀmlich gerade

  1. den Event, der uns bei der Auswahl unseres Accessorys Bescheid sagt (e_message).

bzw.

  1. den Event, der uns nach dem Verstreichen unserer Interruptzeit (parameter.interrupt) informiert, daß wir ein bißchen drucken sollen.
Objekt Objekttyp LĂ€nge Diverses
DRUCSID TEXT 2
DRUCTYP TEXT 9
DRUCANZ ETEXT 2
DRUCDRUC BUTTON Flags: Selectable, Exit & Default
DRUCEX BUTTON Flags: Selectable & Exit

Tabelle 3: Objektdaten zum Dialog DRUCGRAP

Entsprechendes gilt fĂŒr die Analyse nach erfolgtem get_event-Aufruf. Auch hier ist zwischen den beiden unterschiedlichen Gegebenheiten zu unterscheiden und entsprechend zu verfahren.

FĂŒr Knauser (etwa die Leute, die immer noch kein Megabyte in ihrem ST haben) möchte ich noch auf die Compileroption zu Beginn von Listing 8 hinweisen, nĂ€mlich {$sl30}. 130 kByte ist nĂ€mlich genau die StackgrĂ¶ĂŸe, die wir wegen der nicht gerade sparsamen Variablen vergĂ€be fĂŒr unser Accessory zu opfern haben. Das fertige Accessory braucht dann entsprechend noch einmal etwas mehr Hauptspeicher (auch GEM lĂ€ĂŸt - wegen der sechs Dialoge - freundlich grĂŒĂŸen). Dieses Speicherdilemma, in dem sich die Besitzereines Ă€lteren STs befinden könnten, kann man abschwĂ€chen, indem die Anzahl der möglichen Dokumente gesenkt wird. Auch ließe sich die StringlĂ€nge fĂŒr die Druckerparameter wohl noch um einige Zeichen senken. Denn bedenken Sie: Unser Array fĂŒr die Druckerparameter besitzt 256 EintrĂ€ge. Bereits die Reduzierung der formalen StringlĂ€nge um 4 bringt ein ganzes kByte.

Anmerkung: Bevor Sie nun - aufgeschreckt durch die letzten AusfĂŒhrungen -eventuell in den Glauben verfallen, daß der fertige Helper so seine 500 kByte besitzen wird, möchte ich Sie etwas beruhigen. Auch er wird “nur” 130 kByte Stack benötigen. Dieses scheinbare Paradoxon hĂ€ngt damit zusammen, daß die weiteren Helperbestandteile fast nur lokale Variablen besitzen werden. Diese teilen sich dann mit den lokalen Variablen des Druckerspoolers wechselseitig den Programmstack unseres Accessorys, mit dem Effekt, daß kein weiterer Speicher fĂŒr Variablen benötigt wird.

Vorausschau

Die nĂ€chsten beiden Bausteine zur Komplettierung des Lovely Helpers, heißen Kalender und Uhr. Das wesentliche Ziel wird es dabei sein, ein Programm zusammenzustellen, das den Kalender eines beliebigen Jahres berechnet und dabei alle lokalen und bundesweiten Feiertage berĂŒcksichtigt. Ganz nebenbei erhalten wir auch noch ein Utility zum Setzen der Systemuhr. (Ihr Kontrollfeld hat dann endgĂŒltig ausgedient.) In der nĂ€chsten Folgen werden wir dabei soweit kommen, folgende Probleme zu lösen:

  • eine Ordinalfunktion fĂŒr die Zeit
  • die Berechnung des Wochentages fĂŒr ein gegebenes Datum
  • die Berechnung des Osterdatums und damit auch (fast) aller anderen beweglichen Feiertage.

Zum letzten Punkt ist zu sagen, daß es keinesfalls der Papst ist, der Ostern jedes Jahr neu ausknobelt. Wenn Sie, wie ich, lange Zeit auch dieser irrigen Ansicht waren, interessiert Sie vielleicht die nĂ€chste Folge.

Bis dahin, Ihr

D. Brockhaus

Objekt Objekttyp LĂ€nge Diverses
DELSIDED ETEXT 2 Maske: ‘99’
DELSIDUN BUTTON Flags: Selectable & Exit
DELSIDDO BUTTON Flags: Selectable, Exit & Default

Tabelle 4: Objektdaten zum Dialog DELSID

Der Aufbau des Druckerspoolers
{******************************************************} {* Listing 08 : Resource-Handling des Druckerspoolers *} {* *} {* Datei : SPOOLER.PAS *} {* last update : 27.5.88 *} {******************************************************} {$s130} PROGRAM spooler (input,output); CONST {$i gemconst.pas} {$i trixcons.pas} {$i spooler.i} TYPE {$i gemtype.pas} {$i trixtype.pas} bundesland = (schleswig_holstein,hamburg,bremen,niedersachsen, nordrhein_westfalen,hessen,rheinland_pfalz, saarland,baden_wuertemberg,bayern); VAR msg : message_buffer; apl_name : str255; apl_nr , menu_nr , event , dummy : integer; parameter_dialog, synchro_dialog , spooler_dialog , text_dialog , graphik_dialog , sidkill_dialog : dialog_ptr; {$i gemsubs.pas} {$i trixsubs.pas} {$i hilf.pas) {$i spooler1.pas) {$i spooler2.pas} FUNCTION initialisieren : boolean; VAR ok : boolean; BEGIN ok:=load_resource('A:\SPOOLER.RSC'); IF Ok THEN BEGIN apl_name:=' Druckerspooler'; menu_nr:=menu_register(apl_nr,apl_name); io_check(false); load_parameter; init_queue; find_dialog(paramete,parameter_dialog); find_dialog(synchro,synchro_dialog); find_dialog(dspooler,spooler_dialog); find_dialog(dructext,text_dialog); find_dialog(drucgrap,graphik_dialog); find_dialog(delsid,sidkill_dialog); center_dialog(parameter_dialog); center_dialog(synchro_dialog); center_dialog(spooler_dialog); center_dialog(text_dialog); center_dialog(graphik_dialog); center_dialog(sidkill_dialog); datei_name:=''; pfad_name:='A:\*.*'; rewrite(spoolchannel,'PRN:''); END; initialisieren:=ok; END; BEGIN apl_nr: =init_gem; IF apl_nr>=0 THEN IF initialisieren THEN WHILE true DO BEGIN event:=get_event(e_message | e_timer,0,0,0, parameter.interrupt,false,0,0,0,0, false,0,0,0,0,msg,dummy,dummy,dummy, dummy,dummy,dummy); IF (event & e_message <> 0) AND (msg[0]=ac_open) THEN do_spooler; IF event & e_timer <> 0 THEN printer_interrupt; END; END. {************************************************} {* Listing 07 : 1. Verwaltung der SpooleintrĂ€ge*} {* unseres Druckerspoolers. *} {* 2. Eine Interruptroutine, die *} {* den portionsweisen *} {* Ausdruck sĂ€mtlicher Dateien *} {* ermöglicht. *} {* *} {* Datei : SPOOLER2.PAS *} {* last update : 17.5.1988 *} {************************************************} TYPE queue_entry = RECORD dateityp , anzahl : integer; zeichensatz , nlq , schmal , breit , fett : boolean; pfad : str40; END; queue_type = RECORD max : integer; q : ARRAY [0..99] OF queue_entry; END; VAR queue : queue_type; pfad_name , datei_name : path_name; PROCEDURE init_queue; VAR i : integer; BEGIN WITH queue DO BEGIN max:=-1; FOR i:=0 TO 99 DO WITH q[i] DO BEGIN dateityp:=unused; anzahl:=0; pfad:=''; END; END; END; FUNCTION enter_queue (entry : queue_entry): boolean; VAR ok : boolean; i : integer; BEGIN WITH queue DO BEGIN ok:=max<99; IF ok THEN BEGIN max:=succ(max); q[max]:=entry; END; END; enter_queue:=ok; END; FUNCTION remove_queue(nr : integer) : boolean; VAR ok : boolean; i : integer; BEGIN WITH queue DO BEGIN ok:=(max>-1) AND (nr<=max); IF ok THEN BEGIN FOR i:=nr+1 TO max DO {fĂŒr nr=0 und max=0 abweisend} q[i-1]:=q[i]; max:=pred(max); q[max+1].dateityp:=unused; q[max+1].pfad:=''; q[max+1].anzahl:=0; END; remove_queue:=ok; END; END; PROCEDURE do_spooler; VAR button , seite : integer; FUNCTION do_text( nr : integer; VAR entry : queue_entry) : boolean; VAR button : integer; str : str255; ok : boolean; BEGIN writev(str,nr); n_expand(2,str); set_dtext(text_dialog,sidtext,str,system_font,te_left); set_dedit(text_dialog,anzkopte,'__','99','01',system_font,te_left); obj_setstate(text_dialog,zeich1,normal,false); obj_setstate(text_dialog,zeich2,normal,false); obj_setstate(text_dialog,drucexit,normal,false); obj_setstate(text_dialog,drucnlq,normal,false); obj_setstate(text_dialog,drucschm,normal,false); obj_setstate(text_dialog,drucbrei,normal,false); obj_setstate(text_dialog,drucfett,normal,false); begin_update; button:=do_dialog(text_dialog,anzkopte); end_dialog(text_dialog); end_update; ok:=button<>drucexit; IF ok THEN WITH entry DO BEGIN get_dedit(text_dialog,anzkopte,str); readv(str,anzahl); ok:=anzahl>0; zeichensatz:=button=zeich1; nlq:=obj_state(text_dialog,drucnlq)&selected<>0; schmal:=obj_state(text_dialog,drucschm)&selected<>0; breit:=obj_state(text_dialog, drucbrei)&selected<>0; fett:=obj_state(text_dialog, drucfett)&selected<>0; END; do_text:=ok; END; FUNCTION do_graphik( nr : integer; VAR entry : queue_entry): boolean; VAR button : integer; str : str255; ok : boolean; BEGIN writev(str,nr); n_expand(2,str); set_dtext(graphik_dialog,drucsid,str,system_font,te_left); set_dedit(graphik_dialog,drucanz,'__','99','01',system_font,te_left); CASE entry.dateityp OF degas : str:='Degas'; doodle : str:='Doodle'; neochrome : str:='Neochrome'; END; set_dtext(graphik_dialog,dructyp,str,system_font,te_left); begin_update; button:=do_dialog(graphik_dialog,drucanz); obj_setstate(graphik_dialog,button,normal,false); end_dialog(graphik__dialog); end_update; ok:=button=drucdruc; IF ok THEN BEGIN get_dedit(graphik_dialog,drucanz,str); readv(str,entry.anzahl); ok:=entry.anzahl>0; END; do_graphik:=ok; END; FUNCTION do_sidkill(VAR sid: integer) : boolean; VAR button : integer; str : str255; ok : boolean; BEGIN set dedit(sidkill_dialog,delsided,'___','99','00',system_font,te_left); begin_update; button:=do_dialog(sidkill_dialog, delsided); obj_setstate(sidkill_dialog,button,normalfalse); end_dialog(sidkill_dialog); end_update; ok:=button=delsiddo; IF ok THEN BEGIN get_dedit(sidkill_dialog,delsided,str); readv(str,sid); END; do_sidkill:=ok; END; FUNCTION get_name : boolean; VAR cstr : cstring; ok : boolean; BEGIN begin_update; ok:=get_in_file(pfad_name,datei_name); show_dialog(spooler_dialog); end_update; IF ok THEN BEGIN ptocstr(datei_name,cstr); ok:=fsfirst(cstr,$00)=0; END; get_name:=ok; END; PROCEDURE set_dialog(seite : integer); PROCEDURE set_entry( siditem , typitem , paraitem , anzitem , pathitem , index : integer; VAR entry : queue_entry); VAR str : str255; BEGIN WITH entry DO BEGIN writev(str,index); n_expand(2,str); set_dtext(spooler_dialog,siditem,str,system_font,te_left); CASE dateityp OF unused : str:='Unbenutzt'; degas : str:='Degas'; doodle : str:='Doodle'; neochrome : str:='Neochrome'; textdatei : str:='Text'; END; set_dtext(spooler_dialog,typitem,str,system_font,te_left); IF dateityp=textdatei THEN BEGIN IF zeichensatz THEN str:='1' ELSE str:='2'; IF nlq THEN str:=concat(str,'N'); IF schmal THEN str:=concat(str,'S'); IF breit THEN str:=concat(str,'B'); IF fett THEN str:=concat(str,'F'); END ELSE str:=' '; set_dtext(spooler_dialog,paraitem,str,system_font,te_left); writev(str,anzahl); n_expand(2,str); set_dtext(spooler_dialog,anzitem,str,system_font,te_left); str:=pfad; set_dtext(spooler_dialog,pathitem,str,system_font,te_left); END; END; BEGIN WITH queue DO BEGIN set_entry(sid1,typ1,para1,anz1,path1,seite*10+0,q[seite*10+0]); set_entry(sid2,typ2,para2,anz2,path2,seite*10+1,q[seite*10+1]); set_entry(sid3,typ3,para3,anz3,path3,seite*10+2,q[seite*10+2]); set_entry(sid4,typ4,para4,anz4,path4,seite*10+3,q[seite*10+3]); set_entry(sid5,typ5,para5,anz5,path5,seite*10+4,q[seite*10+4]); set_entry(sid6,typ6,para6,anz6,path6,seite*10+5,q[seite*10+5]); set_entry(sid7,typ7,para7,anz7,path7,seite*10+6,q[seite*10+6]); set_entry(sid8,typ8,para8,anz8,path8,seite*10+7,q[seite*10+7]); set_entry(sid9,typ9,para9,anz9,path9,seite*10+8,q[seite*10+8]); set_entry(sid10,typ10,para10,anz10,path10,seite*10+9,q[seite*10+9]); END; END; PROCEDURE anhaengen; VAR entry : queue_entry; dta_ptr : dta_ptr_type; l : long_integer; BEGIN IF get_name THEN WITH entry DO BEGIN pfad:=datei_name; dta_ptr:=fgetdta; l:=dta_ptr^.groesse; CASE 1 OF 32000 : dateityp:=neochrome; 32002 : dateityp:=doodle; 32034 : dateityp:=degas; OTHERWISE : dateityp:=textdatei; END; IF dateityp=textdatei THEN BEGIN IF do_text(queue.max+1,entry) THEN IF NOT enter_queue(entry) THEN dummy:=do_alert('[1][Kein Platz mehr][O.K.]',1); END ELSE BEGIN IF do_graphik(queue.max+1,entry) THEN IF NOT enter_queue(entry) THEN dummy:=do_alert('[1][Kein Platz mehr][O.K.]',1); END; END; END; PROCEDURE loeschen; VAR sid : integer; BEGIN IF do_sidkill(sid) THEN IF NOT remove_queue(sid) THEN dummy:=do_alert('[1][Dieser Eintrag existiert nicht][O.K.],1); END; BEGIN begin_update; seite:=0; REPEAT set_dialog(seite); button:=do_dialog(spooler_dialog,0); obj_setstate (spooler_dialog,button,normal,false); CASE button OF spoolapp : anhaengen; spooldel : loeschen; spoolpar : do_parameter; spoolzur : IF seite>0 THEN seite:=pred(seite); spoolvor : IF seite<9 THEN seite:=succ(seite); spoolex :; END; UNTIL button=spoolex; end_dialog(spooler_dialog); end_update; END; PROCEDURE printer_interrupt; VAR ok : boolean; i : integer; PROCEDURE get_new; BEGIN IF queue.max>=0 THEN WITH queue.q[0] DO BEGIN spoolstatus:=dateityp; anzahl:=pred(anzahl); CASE spoolstatus OF degas , doodle , neochrome : BEGIN IF read_screen(spoolstatus,pfad, buffer)<>0 THEN ^ BEGIN spoolstatus:=unused; IF remove_queue(0) THEN; dummy:=do_alert('[1][SId 0 unauffindbar][O.K.],1); END ELSE pic_first; END; textdatei : BEGIN reset(textinput,pfad); IF io_result=0 THEN text_first(zeichensatz,nlq, schmal,breit,fett) ELSE BEGIN spoolstatus:=unused; IF remove_queue(0) THEN; dummy:=do_alert('[1][SId 0 unauffindbar][0.K.]',1); END; END; END; END; END; BEGIN CASE spoolstatus OF unused : get_new; degas , doodle , neochrome : BEGIN i:=0; REPEAT i:=succ(i); ok:=pic_next; UNTIL NOT ok OR (i>=parameter.gr_per_inter); IF NOT ok THEN WITH queue.q[0] DO BEGIN pic_last; IF anzahl=0 THEN BEGIN IF remove_queue(0) THEN; spoolstatus:=unused; END ELSE get_new; END; END; textdatei : BEGIN ok:=text_next(parameter.t_per_inter); IF NOT ok THEN WITH queue.q[0] DO BEGIN text_last(nlq,schmal,breit,fett); IF anzahl=0 THEN BEGIN IF remove_queue(0) THEN; spoolstatus:=unused; END ELSE get_new; END; END; END; END;
Dirk Brockhaus