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:
- eine Initialisierung (init_queue, Zeilen 31-47)
- das AnhÀngen eines Eintrags am Ende der Warteschlange (enter_queue, Zeilen 49-65)
- 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
- den Event, der uns bei der Auswahl unseres Accessorys Bescheid sagt (e_message).
bzw.
- 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;