Lovely Helper: Ein Desk-Accessory, Teil 3

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:

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:

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
Aus: ST-Computer 06 / 1989, Seite 116

Links

Copyright-Bestimmungen: siehe Über diese Seite