DIRECTORY AIS USING [Error, OpenFile, ReadRaster], Ascii USING [Upper], BasicTime USING [GMT, nullGMT], BiScrollers USING [Align, BiScroller, BiScrollerClass, BiScrollerStyle, bsMenu, GetStyle, QuaBiScroller, QuaViewer, Scale], Buttons USING [Button, ButtonProc, Create], Commander USING [CommandProc, Handle, Register], CommandTool USING [Failed, ParseToList], Containers USING [ChildXBound, ChildYBound, Create], FileNames USING [CurrentWorkingDirectory, Directory, GetShortName, ResolveRelativePath, Tail, StripVersionNumber], FS USING [ComponentPositions, Error, ExpandName, FileInfo, OpenFile, OpenFileFromStream, StreamOpen], GriffinImageUtils USING [ReadGriffinImage], Icons USING [IconFlavor, NewIconFromFile], Imager USING [Color, Error], ImagerBackdoor USING [MakeStipple], ImagerColorOperator USING [BlackColorModel, GrayLinearColorModel, RGBLinearColorModel], ImagerMemory USING [NewMemoryContext], ImagerPixelArray USING [Error, FromAIS, Join3AIS, MaxSampleValue], Interpress USING [LogProc, Open, OpenMaster], IO USING [EndOfStream, Error, GetInt, int, PutFR, real, RIS, SetIndex, STREAM], IPMaster USING [Error, GetHeader], List USING [Append, Remove], Menus USING [AppendMenuEntry, CopyMenu, CreateEntry, Menu], MessageWindow USING [Append, Blink], PDFileReader USING [Error, FromStream, Handle, Rep, Warning], PDImageReader USING [GetPageStructure, Handle, Rep], PressReader USING [FromOpenFile, PressReaderError], PreView USING [AISData, AISState, AISStateRep, BBoxStateRep, Data, DoFileOps, FileInfo, FileInfoRep, FileType, GData, IPData, NameList, PDData, PressData, PVBasicTransformProc, PVDestroy, PVExtremaProc, PVFeedback, PVGetName, PVNotify, PVPaint, Rep, Switches, SwitchRange], PreViewClient USING [], Process USING [Abort], Real USING [FDiv, RoundI], Rope USING [Cat, Concat, Equal, Fetch, Length, ROPE, SkipTo, Substr], Rules USING [Create, Rule], ShowPress USING [Handle, Open, ShowPressError], Sliders USING [Create, FilterProc, SetContents, SliderProc], TiogaMenuOps USING [Open], TIPUser USING [InstantiateNewTIPTable, TIPTable], UserProfile USING [Boolean, CallWhenProfileChanges, ListOfTokens, ProfileChangedProc], ViewerClasses USING [Viewer], ViewerOps USING [AddProp, FetchProp, PaintViewer], ViewerSpecs USING [captionHeight, menuBarHeight, scrollBarW, windowBorderSize], ViewerTools USING [GetContents, GetSelectionContents, MakeNewTextViewer, SetContents]; PreViewTool: CEDAR MONITOR IMPORTS AIS, Ascii, BiScrollers, --ChoiceButtons, --Buttons, Commander, CommandTool, Containers, FileNames, FS, GriffinImageUtils, Icons, Imager, ImagerBackdoor, ImagerColorOperator, ImagerMemory, ImagerPixelArray, Interpress, IO, IPMaster, List, Menus, MessageWindow, PDFileReader, PDImageReader, PressReader, PreView, Process, Real, Rope, Rules, ShowPress, Sliders, TiogaMenuOps, TIPUser, UserProfile, ViewerOps, ViewerSpecs, ViewerTools EXPORTS PreView, PreViewClient = BEGIN FileType: TYPE = PreView.FileType; Data: TYPE = PreView.Data; Rep: TYPE = PreView.Rep; IPData: TYPE = PreView.IPData; PressData: TYPE = PreView.PressData; PDData: TYPE = PreView.PDData; AISData: TYPE = PreView.AISData; GData: TYPE = PreView.GData; AISState: TYPE = PreView.AISState; pvIcon: Icons.IconFlavor = Icons.NewIconFromFile["PreView.icons", 0]; ipPrefix: Rope.ROPE = "Interpress/"; defaultPageHeight: REAL = 11.0*72.0; -- points defaultPageWidth: REAL = 8.5*72.0; -- points versatecPageHeight: REAL = 40.0*72.0; -- points versatecPageWidth: REAL = 40.0*72.0; -- points screenResolution: REAL = 72.0; -- points per inch pointsPerMica: REAL = 72.0/2540.0; micasPerPoint: REAL = 2540.0/72.0; pointsPerMeter: REAL = 72.0/.0254; metersPerPoint: REAL = .0254/72.0; visibleGrey: Imager.Color = ImagerBackdoor.MakeStipple[122645B]; invisibleGrey: Imager.Color = ImagerBackdoor.MakeStipple[100040B]; LProc: Interpress.LogProc = { MessageWindow.Append[message: Rope.Concat["InterpressMaster Error: ", explanation], clearFirst: TRUE]; MessageWindow.Blink[]; }; WhichFileType: PROC [fileName: Rope.ROPE] RETURNS [PreView.FileInfo] = { fname: Rope.ROPE _ NIL; created: BasicTime.GMT _ BasicTime.nullGMT; [fullFName: fname, created: created] _ FS.FileInfo[name: fileName ! FS.Error => CONTINUE]; IF fname#NIL THEN RETURN[DiscoverFileType[fname, created]];-- named file exists IF extensionList = NIL THEN extensionList _ defaultExtensionList; FOR l: LIST OF Rope.ROPE _ extensionList, l.rest UNTIL l=NIL DO fname _ Rope.Cat[fileName, ".", l.first]; [fullFName: fname, created: created] _ FS.FileInfo[name: fname ! FS.Error => LOOP]; RETURN[DiscoverFileType[fname, created]]; -- file with extension exists ENDLOOP; RETURN[NEW[PreView.FileInfoRep _ [] ]]; -- file does not exist }; DiscoverFileType: PROC [fileName: Rope.ROPE, created: BasicTime.GMT] RETURNS [fileInfo: PreView.FileInfo] = { fileInfo _ NEW[PreView.FileInfoRep _ [] ]; --initialize to fileType none, etc. { ref: REF ANY _ NIL; s: IO.STREAM _ FS.StreamOpen[fileName: fileName ! FS.Error => GOTO None]; cp: FS.ComponentPositions _ FS.ExpandName[fileName].cp; ext: Rope.ROPE _ Rope.Substr[base: fileName, start: cp.ext.start, len: cp.ext.length]; SELECT TRUE FROM Rope.Equal[s1: ext, s2: "ip", case: FALSE], Rope.Equal[s1: ext, s2: "interpress", case: FALSE] => IF (ref _ IPMaster.GetHeader[stream: s, prefix: ipPrefix ! IPMaster.Error => CONTINUE;])#NIL THEN {fileInfo^ _ [ip, fileName, created, ref]; RETURN;}; Rope.Equal[s1: ext, s2: "press", case: FALSE] => IF (ref _ PressReader.FromOpenFile[openFile: FS.OpenFileFromStream[self: s] ! PressReader.PressReaderError => CONTINUE;])#NIL THEN {fileInfo^ _ [press, fileName, created, ref]; RETURN;}; Rope.Equal[s1: ext, s2: "pd", case: FALSE] => IF (ref _ PDFileReader.FromStream[stream: s ! PDFileReader.Error, PDFileReader.Warning => CONTINUE;])#NIL THEN {fileInfo^ _ [pd, fileName, created, ref]; RETURN;}; Rope.Equal[s1: ext, s2: "ais", case: FALSE] => IF (ref _ AIS.OpenFile[name: fileName ! AIS.Error, FS.Error => CONTINUE;])#NIL THEN {fileInfo^ _ [ais, fileName, created, ref]; RETURN;}; Rope.Equal[s1: ext, s2: "griffin", case: FALSE] => IF (ref _ GriffinImageUtils.ReadGriffinImage[name: fileName ! FS.Error => CONTINUE;])#NIL THEN {fileInfo^ _ [griffin, fileName, created, ref]; RETURN;}; ENDCASE => NULL; IO.SetIndex[s,0]; --reset stream for subsequent calls IF (ref _ IPMaster.GetHeader[stream: s, prefix: ipPrefix ! IPMaster.Error => CONTINUE;])#NIL THEN {fileInfo^ _ [ip, fileName, created, ref]; RETURN;}; IO.SetIndex[s,0]; --reset stream for subsequent calls IF (ref _ PressReader.FromOpenFile[openFile: FS.OpenFileFromStream[self: s] ! PressReader.PressReaderError => CONTINUE;])#NIL THEN {fileInfo^ _ [press, fileName, created, ref]; RETURN;}; IO.SetIndex[s,0]; --reset stream for subsequent calls IF (ref _ PDFileReader.FromStream[stream: s ! PDFileReader.Error, PDFileReader.Warning => CONTINUE;])#NIL THEN {fileInfo^ _ [pd, fileName, created, ref]; RETURN;}; IF (ref _ AIS.OpenFile[name: fileName ! AIS.Error, FS.Error => CONTINUE;])#NIL THEN {fileInfo^ _ [ais, fileName, created, ref]; RETURN;}; IF (ref _ GriffinImageUtils.ReadGriffinImage[name: fileName ! FS.Error => CONTINUE;])#NIL THEN {fileInfo^ _ [griffin, fileName, created, ref]; RETURN;}; fileInfo^ _ [none, NIL, BasicTime.nullGMT, NIL]; EXITS None => fileInfo^ _ [none, NIL, BasicTime.nullGMT, NIL]; }; }; CreatePreViewer: PUBLIC PROC [fileNames: PreView.NameList, switches: PreView.Switches] RETURNS [preViewer: ViewerClasses.Viewer] = { bs: BiScrollers.BiScroller _ NIL; data: Data _ NIL; curIndent, topLine: INTEGER _ 0; button: Buttons.Button; menu: Menus.Menu; rule: Rules.Rule; fileInfo: PreView.FileInfo _ NIL; versionSpecified: BOOL _ fileNames.rest#NIL OR Rope.SkipTo[s: fileNames.first, skip: "!"]#Rope.Length[fileNames.first]; -- don't do versions with multiple AIS files fileInfo _ WhichFileType[fileNames.first]; -- adds file extension to the named file if needed SELECT fileInfo.filetype FROM ip => { ipmaster: Interpress.OpenMaster _ Interpress.Open[fileName: fileInfo.fullFName, logProc: LProc, logData: NIL ! FS.Error => { MessageWindow.Append[message: error.explanation, clearFirst: TRUE]; GOTO Quit; }; IPMaster.Error => { --ErrorDesc: TYPE = RECORD[code: ATOM, explanation: ROPE, index: INT _ 0] MessageWindow.Append[message: Rope.Cat[error.explanation, " for ", fileInfo.fullFName], clearFirst: TRUE]; GOTO Quit; }; Imager.Error => { --ErrorDesc: TYPE = RECORD [code: ATOM, explanation: ROPE] MessageWindow.Append[message: Rope.Cat[error.explanation, " for ", fileInfo.fullFName], clearFirst: TRUE]; GOTO Quit; }; IO.Error, IO.EndOfStream => { MessageWindow.Append[message: Rope.Cat["IO Stream Error for ", fileInfo.fullFName], clearFirst: TRUE]; GOTO Quit; }; ]; IF ipmaster.pages=0 THEN { MessageWindow.Append[message: Rope.Concat["Zero pages in ", fileInfo.fullFName], clearFirst: TRUE]; GOTO Quit; }; data _ NEW[ip Rep _ [fileInfo: fileInfo, pageNumber: 1, lastPageNumber: ipmaster.pages, pageHeight: IF switches['V] THEN versatecPageHeight ELSE defaultPageHeight, pageWidth: IF switches['V] THEN versatecPageWidth ELSE defaultPageWidth, switches: switches, kind: ip[ipMaster: ipmaster]]]; -- pages IN [1..end] }; press => { pressfile: ShowPress.Handle _ ShowPress.Open[fileInfo.fullFName ! FS.Error => { MessageWindow.Append[message: error.explanation, clearFirst: TRUE]; GOTO Quit; }; ShowPress.ShowPressError => { SELECT code FROM $CantReadFile => MessageWindow.Append[message: Rope.Concat["Can't open PreViewer on ", fileInfo.fullFName], clearFirst: TRUE]; $CantFindFonts => MessageWindow.Append[message: Rope.Concat["Can't Find Fonts for ", fileInfo.fullFName], clearFirst: TRUE]; ENDCASE => MessageWindow.Append[message: Rope.Concat["ShowPressError on ", fileInfo.fullFName], clearFirst: TRUE]; GOTO Quit; }; Imager.Error => { MessageWindow.Append[message: Rope.Cat[error.explanation, " for ", fileInfo.fullFName], clearFirst: TRUE]; GOTO Quit; }; ]; IF pressfile.lastPart-1=0 THEN { MessageWindow.Append[message: Rope.Concat["Zero pages in ", fileInfo.fullFName], clearFirst: TRUE]; GOTO Quit; }; data _ NEW[press Rep _ [fileInfo: fileInfo, pageNumber: 1, lastPageNumber: pressfile.lastPart-1, pageHeight: defaultPageHeight, pageWidth: defaultPageWidth, switches: switches, kind: press[presshandle: pressfile]]]; }; pd => { pdfile: PDFileReader.Handle _ NARROW[fileInfo.ref]; -- DiscoverFileType did a PDFileReader.FromStream already pdData: PDData _ NEW[pd Rep _ [fileInfo: fileInfo, pageNumber: 1, pageHeight: (pdfile.herald.imageSSize/pdfile.herald.sResolution)*screenResolution, pageWidth: (pdfile.herald.imageFSize/pdfile.herald.fResolution)*screenResolution, switches: switches, kind: pd[pdhandle: pdfile, scalePD: NOT switches['F]]]]; [pdData.pageStructure, pdData.lastPageNumber] _ PDImageReader.GetPageStructure[pdfile]; pdData.scaleFactors _ [x: screenResolution/pdfile.herald.fResolution, y: screenResolution/pdfile.herald.sResolution]; IF switches['F] THEN { -- "full" scale, so ExtremaProc has to know about pixels pdData.pageWidth _ pdData.pageWidth*(pdfile.herald.fResolution/screenResolution); pdData.pageHeight _ pdData.pageHeight*(pdfile.herald.sResolution/screenResolution); }; IF pdData.lastPageNumber=0 THEN { MessageWindow.Append[message: Rope.Concat["Zero pages in ", fileInfo.fullFName], clearFirst: TRUE]; GOTO Quit; }; data _ pdData; }; ais => { aisData: AISData _ NEW[ais Rep _ [fileInfo: fileInfo, pageNumber: 1, lastPageNumber: 1, switches: switches, kind: ais[state: NEW[PreView.AISStateRep _ []]]]]; aisData.state.openFile _ NARROW[fileInfo.ref]; -- DiscoverFileType did an AIS.OpenFile already IF (fileInfo.fullFName _ InitAIS[data: aisData, fileNames: fileNames, singleName: fileInfo.fullFName]) = NIL THEN GOTO Quit; aisData.pageHeight _ aisData.state.scans; aisData.pageWidth _ aisData.state.pixels; data _ aisData; }; griffin => { data _ NEW[griffin Rep _ [fileInfo: fileInfo, pageNumber: 1, lastPageNumber: 1, pageHeight: defaultPageHeight, pageWidth: defaultPageWidth, switches: switches, kind: griffin[image: NARROW[fileInfo.ref]]]]; -- single page }; ENDCASE => { IF tryTioga THEN { [] _ TiogaMenuOps.Open[fileNames.first]; RETURN; } ELSE MessageWindow.Append[message: Rope.Concat["Unknown file or filetype: ", fileNames.first], clearFirst: TRUE]; GOTO Quit; }; menu _ Menus.CopyMenu[BiScrollers.bsMenu]; Menus.AppendMenuEntry[menu: menu, entry: Menus.CreateEntry[name: "Selection", proc: SelectionOps, clientData: data, documentation: "Left: center selection, Middle: center and fit selection, Right: select entire page "]]; data.bBox _ NEW[PreView.BBoxStateRep _ []]; data.iMemContext _ ImagerMemory.NewMemoryContext[]; data.container _ preViewer _ Containers.Create[ info: [ name: IF versionSpecified THEN Rope.Concat["PreView: ", data.fileInfo.fullFName] ELSE Rope.Cat["PreView: ", FileNames.StripVersionNumber[data.fileInfo.fullFName], " (!", FileNames.Tail[data.fileInfo.fullFName, '!], ")"], label: FileNames.GetShortName[path: data.fileInfo.fullFName, stripOffVersionNumber: TRUE], iconic: TRUE, menu: menu, icon: pvIcon, data: data, scrollable: FALSE], paint: FALSE ]; button _ Buttons.Create[ info: [ name: "Stuff", wx: curIndent, wy: topLine, border: FALSE, parent: data.container], proc: Stuff, clientData: data, documentation: "Click to stuff an IP node at the current selection", paint: FALSE]; curIndent _ button.wx + button.ww; button _ Buttons.Create[ info: [ name: "ToIP", wx: curIndent, wy: topLine, border: FALSE, parent: data.container], proc: ToIP, clientData: data, documentation: "Click to create an IP Master", paint: FALSE]; curIndent _ button.wx + button.ww; button _ Buttons.Create[ info: [ name: "FirstPage", wx: curIndent, wy: topLine, border: FALSE, parent: data.container], proc: FirstPage, clientData: data, documentation: "Left-click for the first page of the file", paint: FALSE]; curIndent _ button.wx + button.ww; button _ Buttons.Create[ info: [ name: "TurnPage", wx: curIndent, wy: topLine, border: FALSE, parent: data.container], proc: TurnPage, clientData: data, documentation: "Left-click for next page; Right-click for previous page", paint: FALSE]; curIndent _ button.wx + button.ww; button _ Buttons.Create[ info: [ name: "LastPage", wx: curIndent, wy: topLine, border: FALSE, parent: data.container], proc: LastPage, clientData: data, documentation: "Left-click for last page of the file", paint: FALSE]; curIndent _ button.wx + button.ww; button _ Buttons.Create[ info: [ name: "Page", wx: curIndent, wy: topLine, border: FALSE, parent: data.container], proc: PageNumber, clientData: data, documentation: "Left-click for selected page number", paint: FALSE]; curIndent _ button.wx + button.ww; data.pageNumberViewer _ ViewerTools.MakeNewTextViewer[ info: [ wx: curIndent, wy: topLine+ViewerSpecs.windowBorderSize, ww: 40, wh: ViewerSpecs.scrollBarW+2*ViewerSpecs.windowBorderSize, parent: data.container, border: FALSE, scrollable: FALSE], paint: FALSE]; curIndent _ data.pageNumberViewer.wx + data.pageNumberViewer.ww; data.pageNumberSlider _ Sliders.Create[ info: [ wx: curIndent, wy: topLine, ww: data.container.ww-curIndent, wh: ViewerSpecs.scrollBarW+2*ViewerSpecs.windowBorderSize, border: FALSE, parent: data.container, scrollable: FALSE], filterProc: NormalizePageNumber, sliderProc: PageNumberSlider, orientation: horizontal, foreground: visibleGrey, background: invisibleGrey, clientData: data, paint: FALSE]; Containers.ChildXBound[data.container, data.pageNumberSlider]; rule _ Rules.Create[ info: [ parent: data.container, wx: 0, wy: topLine+ViewerSpecs.captionHeight+4*ViewerSpecs.windowBorderSize, wh: ViewerSpecs.menuBarHeight]]; Containers.ChildXBound[data.container, rule]; bs _ bsStyle.CreateBiScroller[ class: pvBSClass, info: [ parent: data.container, wx: 0, wy: rule.wy+rule.wh+2*ViewerSpecs.windowBorderSize, border: FALSE, scrollable: FALSE, data: data], paint: FALSE ]; data.preViewer _ bs.QuaViewer[inner: FALSE]; ViewerOps.AddProp[viewer: bs.QuaViewer[inner: TRUE], prop: $PVWDir, val: FileNames.CurrentWorkingDirectory[]]; Containers.ChildXBound[data.container, data.preViewer]; Containers.ChildYBound[data.container, data.preViewer]; ViewerOps.PaintViewer[viewer: data.container, hint: all, clearClient: TRUE]; DeltaPage[data, 1]; PVListAdd[LIST[data]]; EXITS Quit => { MessageWindow.Blink[]; RETURN; }; }; InitAIS: PROC [data: AISData, fileNames: PreView.NameList, singleName: Rope.ROPE] RETURNS [newName: Rope.ROPE _ NIL] = { ENABLE { FS.Error => { MessageWindow.Append[message: error.explanation, clearFirst: TRUE]; newName _ NIL; CONTINUE; }; ImagerPixelArray.Error => { MessageWindow.Append[message: error.explanation, clearFirst: TRUE]; newName _ NIL; CONTINUE; }; }; state: AISState _ data.state; IF fileNames.rest#NIL THEN { --multi-file color AIS image redName: Rope.ROPE _ Rope.Concat[fileNames.rest.rest.first, ".ais"]; grnName: Rope.ROPE _ Rope.Concat[fileNames.rest.first, ".ais"]; bluName: Rope.ROPE _ Rope.Concat[fileNames.first, ".ais"]; IF data.abort THEN {data.abort _ FALSE; RETURN[NIL]}; -- set asynchronously by StopIfYouCan state.pa_ ImagerPixelArray.Join3AIS[name1: redName, name2: grnName, name3: bluName]; -- in red, green, blue order !! [scanCount: state.scans, scanLength: state.pixels] _ AIS.ReadRaster[state.openFile]^; -- must be identical raster info in all separations IF data.abort THEN {data.abort _ FALSE; RETURN[NIL]}; -- set asynchronously by StopIfYouCan state.op _ ImagerColorOperator.RGBLinearColorModel[maxSampleValue: ImagerPixelArray.MaxSampleValue[pa: state.pa, i: 0]]; newName _ Rope.Concat[Rope.Substr[base: fileNames.first, start: 0, len: Rope.SkipTo[s: fileNames.first, pos: 0, skip: "-"]], "-*.ais"]; } ELSE { -- single file AIS image bps: [0..16] _ 0; IF data.abort THEN {data.abort _ FALSE; RETURN[NIL]}; -- set asynchronously by StopIfYouCan state.pa_ ImagerPixelArray.FromAIS[singleName]; [scanCount: state.scans, scanLength: state.pixels, scanMode: , bitsPerPixel: bps] _ AIS.ReadRaster[state.openFile]^; IF data.abort THEN {data.abort _ FALSE; RETURN[NIL]}; -- set asynchronously by StopIfYouCan state.op _ IF bps#0 THEN ImagerColorOperator.GrayLinearColorModel[sWhite: ImagerPixelArray.MaxSampleValue[pa: state.pa, i: 0], sBlack: 0.0] ELSE ImagerColorOperator.BlackColorModel[clear: FALSE]; newName _ singleName; }; state.active _ TRUE; }; StopIfYouCan: ENTRY Buttons.ButtonProc = { --pvList is a global list of all PreView Datas ENABLE UNWIND => NULL; KillIt: PROC [data: Data] = TRUSTED { IF data.process#NIL THEN {Process.Abort[data.process]; data.process _ NIL}; }; FOR l: LIST OF REF ANY _ pvList, l.rest UNTIL l=NIL DO data: PreView.Data _ NARROW[l.first]; data.abort _ TRUE; -- this aborts PD file painting, which can't properly use Process.Abort WITH data SELECT FROM ipData: IPData => KillIt[ipData]; aisData: AISData => KillIt[aisData]; gData: GData => KillIt[gData]; pressData: PressData => NULL; -- punt on Press files pdData: PDData => NULL; ENDCASE => ERROR; ENDLOOP; }; ResetProcess: PUBLIC ENTRY PROC [data: Data] = { ENABLE UNWIND => NULL; data.process _ NIL; }; SelectionOps: Buttons.ButtonProc = { data: Data _ NARROW[clientData]; viewer: ViewerClasses.Viewer _ BiScrollers.QuaBiScroller[data.preViewer].QuaViewer[inner: TRUE]; SELECT mouseButton FROM red => { -- center selection in viewer IF NOT data.bBox.active THEN GOTO NoSel; BiScrollers.Align[bs: BiScrollers.QuaBiScroller[data.preViewer], client: [variant: coord[x: data.bBox.rect.x+(data.bBox.rect.w/2.0), y: data.bBox.rect.y+(data.bBox.rect.h/2.0)]], viewer: [variant: fraction[fx: 0.5, fy: 0.5]], paint: TRUE]; }; yellow => { -- center selection in viewer then scale to fit inside viewer vH, vW, sH, sW: REAL _ 1.0; -- viewer and selection dimensions IF NOT data.bBox.active THEN GOTO NoSel; sH _ MAX[sH, data.bBox.rect.h]; sW _ MAX[sW, data.bBox.rect.w]; vH _ MAX[vH, viewer.ch]; vW _ MAX[vW, viewer.cw]; BiScrollers.Align[bs: BiScrollers.QuaBiScroller[data.preViewer], client: [variant: coord[x: data.bBox.rect.x+(data.bBox.rect.w/2.0), y: data.bBox.rect.y+(data.bBox.rect.h/2.0)]], viewer: [variant: fraction[fx: 0.5, fy: 0.5]], paint: FALSE]; IF NOT shift THEN BiScrollers.Scale[bs: BiScrollers.QuaBiScroller[data.preViewer], op: [variant: reset[] ], paint: FALSE ]; BiScrollers.Scale[bs: BiScrollers.QuaBiScroller[data.preViewer], op: [variant: byArg[arg: MIN[vW/sW, vH/sH]]], paint: TRUE ]; }; blue => { -- select entire page IF data.bBox.active THEN PreView.PVFeedback[data: data, v: BiScrollers.QuaBiScroller[data.preViewer].QuaViewer[inner: TRUE], op: remove]; -- remove any old selection data.bBox^ _ [active: TRUE, rect: [x: 0.0, y: 0.0, w: data.pageWidth, h: data.pageHeight], mode: waitingForSecondPoint, x0: 0.0, y0: 0.0, lastX: data.pageWidth, lastY: data.pageHeight]; -- fake the box into the right state to select the whole page PreView.PVFeedback[data: data, v: BiScrollers.QuaBiScroller[data.preViewer].QuaViewer[inner: TRUE], op: paint]; -- paint new selection }; ENDCASE => ERROR; EXITS NoSel => MessageWindow.Append["No PreView Selection", TRUE]; }; ToIP: Buttons.ButtonProc = { --parent: REF ANY, clientData: REF ANY, mouseButton: MouseButton _ red, shift, control: BOOL _ FALSE data: Data _ NARROW[clientData]; iv: ViewerClasses.Viewer _ BiScrollers.QuaBiScroller[data.preViewer].QuaViewer[inner: TRUE]; name: Rope.ROPE _ ViewerTools.GetSelectionContents[]; IF mouseButton#blue AND (data.bBox.rect.w=0 OR data.bBox.rect.h=0) THEN { MessageWindow.Append["Specify clipping box before left clicking ToIP", TRUE]; RETURN; }; IF name=NIL OR Rope.Length[name]=0 THEN { MessageWindow.Append["Select a file name before buttoning ToIP", TRUE]; RETURN; }; IF Rope.Length[FileNames.Directory[name]]=0 THEN name _ Rope.Concat[NARROW[ViewerOps.FetchProp[viewer: iv, prop: $PVWDir]], name]; PreView.DoFileOps[op: ipMaster, viewer: iv, data: data, fileName: name, clip: mouseButton#blue]; }; Stuff: Buttons.ButtonProc = { data: Data _ NARROW[clientData]; IF data.bBox.rect.w=0 OR data.bBox.rect.h=0 THEN { MessageWindow.Append["Specify clipping box before clicking Stuff", TRUE]; RETURN; }; data.stuffWithBorders _ mouseButton=blue; PreView.DoFileOps[op: stuff, viewer: BiScrollers.QuaBiScroller[data.preViewer].QuaViewer[inner: TRUE], data: data, fileName: NIL, clip: TRUE]; }; FirstPage: Buttons.ButtonProc = { data: Data _ NARROW[clientData]; DeltaPage[data, 1]; }; TurnPage: Buttons.ButtonProc = { data: Data _ NARROW[clientData]; where: INT _ IF mouseButton = red THEN +1 ELSE -1; DeltaPage[data, MIN[MAX[data.pageNumber+where, 1], (data.lastPageNumber)]]; }; LastPage: Buttons.ButtonProc = { data: Data _ NARROW[clientData]; DeltaPage[data, (data.lastPageNumber)]; }; PageNumber: Buttons.ButtonProc = { data: Data _ NARROW[clientData]; stream: IO.STREAM _ IO.RIS[ViewerTools.GetContents[data.pageNumberViewer]]; where: INT _ stream.GetInt[]; DeltaPage[data, MIN[MAX[where, 1], (data.lastPageNumber)]]; }; NormalizePageNumber: Sliders.FilterProc = { data: Data _ NARROW[clientData]; RETURN [Real.FDiv[Real.RoundI[value*(data.lastPageNumber)], (data.lastPageNumber)]] }; PageNumberSlider: Sliders.SliderProc = { data: Data _ NARROW[clientData]; SELECT reason FROM abort => { ViewerTools.SetContents[data.pageNumberViewer, IO.PutFR["%-g", IO.real[data.pageNumber]]]; }; move => { ViewerTools.SetContents[data.pageNumberViewer, IO.PutFR["%-g", IO.real[Real.RoundI[(data.lastPageNumber)*value]]]]; }; set => { DeltaPage[data, MIN[MAX[Real.RoundI[(data.lastPageNumber)*value], 1], (data.lastPageNumber)]]; }; ENDCASE; }; DeltaPage: PROCEDURE [data: Data, newvalue: INT] = { ViewerTools.SetContents[data.pageNumberViewer, IO.PutFR["%-g", IO.int[newvalue]]]; Sliders.SetContents[data.pageNumberSlider, Real.FDiv[newvalue, (data.lastPageNumber)]]; IF data.pageNumber=newvalue THEN RETURN; -- just did the initialization needed data.pageNumber _ newvalue; ViewerOps.PaintViewer[viewer: data.preViewer, hint: all, clearClient: TRUE]; }; MakePreViewer: Commander.CommandProc = { args: PreView.NameList _ LIST[]; --LIST OF Rope.ROPE nameList: PreView.NameList _ LIST[]; --LIST OF Rope.ROPE switches: PreView.Switches _ ALL[FALSE]; argLength: NAT _ 0; switchChar: CHAR = '-; [list: args, length: argLength] _ CommandTool.ParseToList[cmd: cmd, starExpand: TRUE, switchChar: switchChar ! CommandTool.Failed => CONTINUE; ]; IF args = NIL OR argLength < 1 THEN RETURN[$Failure, "Unable to parse command line"]; IF Rope.Fetch[base: args.first, index: 0] = switchChar THEN { tChar: CHAR; FOR iChar: INT IN [1..Rope.Length[args.first]) DO IF (tChar _ Ascii.Upper[Rope.Fetch[base: args.first, index: iChar]]) IN PreView.SwitchRange THEN switches[tChar] _ TRUE; ENDLOOP; args _ args.rest; }; IF switches['C] THEN { -- command arguments are RGB components of a single color image FOR a: PreView.NameList _ args, a.rest UNTIL a=NIL DO nameList _ CONS[FileNames.ResolveRelativePath[a.first], nameList]; ENDLOOP; [] _ CreatePreViewer[fileNames: nameList, switches: switches]; } ELSE FOR rl: PreView.NameList _ args, rl.rest UNTIL rl = NIL DO --open a PreViewer on each file [] _ CreatePreViewer[fileNames: LIST[FileNames.ResolveRelativePath[rl.first]], switches: switches]; ENDLOOP; }; PVChangedProc: UserProfile.ProfileChangedProc = { extensionList _ UserProfile.ListOfTokens[key: "PreView.Extensions", default: defaultExtensionList]; tryTioga _ UserProfile.Boolean[key: "PreView.TryTiogaOpen", default: FALSE]; }; PVListRemove: PUBLIC ENTRY PROC [ref: REF ANY] = { ENABLE UNWIND => NULL; pvList _ List.Remove[ref: ref, list: pvList]; }; PVListAdd: PUBLIC ENTRY PROC [list: LIST OF REF ANY] = { ENABLE UNWIND => NULL; pvList _ List.Append[l1: pvList, l2: list]; }; pvList: LIST OF REF ANY _ LIST[]; --shared defaultExtensionList: LIST OF Rope.ROPE = LIST["ip", "interpress", "press", "pd", "ais", "griffin"]; extensionList: LIST OF Rope.ROPE _ defaultExtensionList; tryTioga: BOOL _ FALSE; bsStyle: PUBLIC BiScrollers.BiScrollerStyle; pvBSClass: BiScrollers.BiScrollerClass; PVStart: PROC = { pvTIP: TIPUser.TIPTable _ TIPUser.InstantiateNewTIPTable["PreView.tip"]; bsStyle _ BiScrollers.GetStyle[]; -- default gets BiScrollersButtonned pvBSClass _ bsStyle.NewBiScrollerClass[[ flavor: $PreViewer, extrema: PreView.PVExtremaProc, notify: PreView.PVNotify, paint: PreView.PVPaint, destroy: PreView.PVDestroy, get: PreView.PVGetName, tipTable: pvTIP, cursor: crossHairsCircle, mayStretch: FALSE, -- NOT OK to scale X and Y differently vanilla: PreView.PVBasicTransformProc, --proc which provides the vanilla transform for BiScrollers preserve: [X: 0.0, Y: 1.0] --this specifies point that stays fixed when viewer size changes ]]; UserProfile.CallWhenProfileChanges[proc: PVChangedProc]; -- PVChangedProc called immediately after this registration call [] _ Buttons.Create[info: [name: "PVSTOP!"], proc: StopIfYouCan]; Commander.Register[key: "Preview", proc: MakePreViewer, doc: "Create a PreViewer for a Press, PD, Interpress, or AIS file" ]; }; PVStart[]; END. >PreViewTool.mesa Last Edited by: Ken Pier, May 15, 1986 11:24:20 am PDT ChoiceButtons USING [BuildTextPrompt, PromptDataRef], [master: OpenMaster, class: ErrorClass, code: ATOM, explanation: ROPE] expects a file name which may not have an extension; will try extensions .ip, .interpress, .press, .pd, .ais .griffin in that order did not find full name, so try a few extensions given a filename, figure out if it is an interpress, press, PD, AIS, or Griffin file (or none) uses the extension as a hint as to the filetype. Extension as hint did not help; so try all other possibilities promptData: ChoiceButtons.PromptDataRef _ NIL; --read the AIS file(s) and get ready for display. aisData.state will be further filled in by this call. data.fileInfo.fullFName _ FS.ExpandName[name: data.fileInfo.fullFName].fullFName; promptData _ ChoiceButtons.BuildTextPrompt[ viewer: data.container, x: 0, y: rule.wy+rule.wh+2*ViewerSpecs.windowBorderSize, title: "ScratchPad: ", textViewerWidth: 500]; rule _ Rules.Create[ info: [ parent: data.container, wx: 0, wy: promptData.newy+4*ViewerSpecs.windowBorderSize, wh: ViewerSpecs.menuBarHeight]]; Containers.ChildXBound[data.container, rule]; this PROC is here solely to synchronize the Paint Proc in PreViewImpl with StopIfYouCan [cmd: Handle] RETURNS [result: REF _ NIL, msg: Rope.ROPE _ NIL]; ʘIcodešœ™K™6K˜šÏk ˜ Kšœœ˜(Kšœœ ˜Kšœ œœ ˜Kšœ œj˜{Kšœœ˜+Kšœœ"™5Kšœ œ!˜0Kšœ œ˜(Kšœ œ$˜4Kšœ œc˜rKšœœ]˜eKšœœ˜+Kšœœ˜*Kšœœ˜Kšœœ˜#Kšœœ>˜WKšœ œ˜&Kšœœ,˜BKšœ œ˜-Kšœœ0œ œ˜OKšœ œ˜"Kšœœ˜Kšœœ0˜;Kšœœ˜$Kšœ œ+˜=Kšœœ!˜4Kšœ œ"˜3Kšœœ„˜‘Kšœœ˜Kšœœ ˜Kšœœ˜Kšœœ%œ˜EKšœœ˜Kšœ œ ˜/Kšœœ/˜˜OKšœ œE˜V—K˜šÏn œœ˜KšœœaœuœÒ˜·šœ˜&K˜——Kšœ œ˜"Kšœœ˜Kšœœ˜Kšœœ˜Kšœ œ˜$Kšœœ˜Kšœ œ˜ Kšœœ˜Kšœ œ˜"K˜KšœE˜EKšœœ˜$KšœœÏc ˜.Kšœœ Ÿ ˜,KšœœŸ ˜/KšœœŸ ˜.Kšœœ Ÿ˜2Kšœœ˜"Kšœœ˜"Kšœœ˜"Kšœœ˜"Kšœ@˜@KšœB˜BK˜šžœ˜Kšœ.œœ™FKšœ`œ˜fKšœ˜K˜K˜—šž œœœœ˜HKšœƒ™ƒKšœ œœ˜Kšœœ˜+Kšœ'œœ œ˜ZKš œœœœ#Ÿ˜OK™/Kšœœœ&˜Aš œœœœœœ˜?Kšœ)˜)Kšœ'œœ œ˜SKšœ$Ÿ˜GKšœ˜—KšœœŸ˜>Kšœ˜K˜—š žœœœœœ!˜mK™^K™0Kšœ œŸ#˜N˜Kšœœœœ˜Kš œœœœ!œ œ˜IK•StartOfExpansion[s: ROPE, char: CHAR]šœœœ˜7K–9[base: ROPE, start: INT _ 0, len: INT _ 2147483647]šœ œH˜Všœœ˜K–-[s1: ROPE, s2: ROPE, case: BOOL _ TRUE]šœ$œ/œœKœœœ,œ˜øK–-[s1: ROPE, s2: ROPE, case: BOOL _ TRUE]šœ'œœ+œ?œœœ/œ˜ëK–-[s1: ROPE, s2: ROPE, case: BOOL _ TRUE]š œ$œœXœœœ,œ˜ÑK–-[s1: ROPE, s2: ROPE, case: BOOL _ TRUE]šœ%œœœœœ œœœ-œ˜¸K–-[s1: ROPE, s2: ROPE, case: BOOL _ TRUE]šœ)œœ<œ œœœ1œ˜ÌKšœœ˜—K™>KšœŸ#˜5Kš œKœœœ,œ˜–KšœŸ#˜5K–[openFile: FS.OpenFile]š œ+œ?œœœ/œ˜ºKšœŸ#˜5K–[stream: STREAM]š œXœœœ,œ˜£K–[stream: STREAM]šœœœœ œœœ-œ˜‰K–[stream: STREAM]š œ<œ œœœ1œ˜™Kšœœœ˜0š˜Kšœœœ˜8—K˜—Kšœ˜K˜—šžœœœ;œ&˜„Kšœœ˜!Kšœ*œ™.Kšœ œ˜Kšœœ˜ K˜K˜K˜Kšœœ˜!KšœœœœJŸ,˜¤K˜Kšœ+Ÿ2˜]šœ˜šœ˜šœiœ˜nšœ ˜ Kšœ=œ˜CKšœ˜ Kšœ˜—šœŸI˜]Kšœdœ˜jKšœ˜ Kšœ˜—šœŸ:˜LKšœdœ˜jKšœ˜ Kšœ˜—šœœ˜Kšœ`œ˜fKšœ˜ Kšœ˜—Kšœ˜—šœœ˜Kšœ]œ˜cKšœ˜ Kšœ˜—KšœœZœœœœœœGŸ˜µKšœ˜—˜ šœA˜Ašœ ˜ Kšœ=œ˜CKšœ˜ Kšœ˜—šœ˜šœ˜Kšœxœ˜~Kšœvœ˜|Kšœeœ˜r—Kšœ˜ Kšœ˜—šœ˜Kšœdœ˜jKšœ˜ Kšœ˜—K˜—šœœ˜ Kšœ]œ˜cKšœ˜ Kšœ˜—KšœœÍ˜×K˜—˜KšœœŸ9˜mKšœœ‹œ˜³KšœW˜WKšœu˜ušœœŸ8˜OKšœQ˜QKšœS˜SK˜—šœœ˜!Kšœ]œ˜cKšœ˜ Kšœ˜—Kšœ˜K˜—˜Kšœœgœ˜žKšœœŸ/˜^KšœŸg™hKšœgœœœ˜|Kšœ)˜)Kšœ)˜)Kšœ˜K˜—˜ Kšœœ«œŸ˜ÜK˜—šœ˜ šœ œ˜Kšœ(˜(Kšœ˜K˜—Kšœgœ˜qKšœ˜ Kšœ˜——K˜K–’[name: ROPE, proc: Menus.ClickProc, clientData: REF ANY _ NIL, documentation: REF ANY _ NIL, fork: BOOL _ TRUE, guarded: BOOL _ FALSE]šœ*˜*KšœÜ˜ÜKšœ œ˜+Kšœ3˜3KšœQ™Qšœ/˜/šœ˜K–"[name: ROPE, wDir: ROPE _ NIL]šœœœ3œ‡˜ÜK–4[path: ROPE, stripOffVersionNumber: BOOL _ TRUE]šœTœ˜ZKšœœ˜ Kšœ ˜ Kšœ ˜ Kšœ ˜ Kšœ œ˜—Kšœœ˜—K˜šœ˜šœ˜Kšœ˜Kšœ˜Kšœ ˜ Kšœœ˜Kšœ˜—Kšœ ˜ Kšœ˜KšœD˜DKšœœ˜—K˜"K˜šœ˜šœ˜Kšœ ˜ Kšœ˜Kšœ ˜ Kšœœ˜Kšœ˜—Kšœ ˜ Kšœ˜Kšœ.˜.Kšœœ˜—K˜"K˜šœ˜šœ˜Kšœ˜Kšœ˜Kšœ ˜ Kšœœ˜Kšœ˜—Kšœ˜Kšœ˜Kšœ;˜;Kšœœ˜—K˜"K˜šœ˜šœ˜Kšœ˜Kšœ˜Kšœ ˜ Kšœœ˜Kšœ˜—Kšœ˜Kšœ˜KšœI˜IKšœœ˜—Kšœ"˜"K˜šœ˜šœ˜Kšœ˜Kšœ˜Kšœ ˜ Kšœœ˜Kšœ˜—Kšœ˜Kšœ˜Kšœ6˜6Kšœœ˜—Kšœ"˜"K˜šœ˜šœ˜Kšœ ˜ Kšœ˜Kšœ ˜ Kšœœ˜Kšœ˜—Kšœ˜Kšœ˜Kšœ5˜5Kšœœ˜—Kšœ"˜"K˜šœ6˜6šœ˜Kšœ˜Kšœ)˜)Kšœ˜Kšœ:˜:Kšœ˜Kšœœ˜Kšœ œ˜—Kšœœ˜—Kšœ@˜@K˜šœ'˜'šœ˜Kšœ˜Kšœ ˜ Kšœ ˜ Kšœ:˜:Kšœœ˜Kšœ˜Kšœ œ˜—K˜ Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœœ˜—Kšœ>˜>K˜˜˜Kšœ˜K˜KšœE˜EKšœ ˜ ——Kšœ-˜-K™šœ+™+Kšœ~™~—K™™™Kšœ™K™Kšœ3™3Kšœ ™ ——Kšœ-™-K™šœ˜Kšœ˜šœ˜Kšœ˜Kšœ˜Kšœ3˜3Kšœœ˜Kšœ œ˜Kšœ ˜ —Kšœœ˜K˜—Kšœ%œ˜,K–>[viewer: ViewerClasses.Viewer, prop: ATOM, val: REF ANY]šœ.œ<˜nKšœ7˜7Kšœ7˜7KšœFœ˜LKšœ˜K–4[l1: LIST OF REF ANY, l2: LIST OF REF ANY _ NIL]šœ œ˜š˜šœ ˜ Kšœ˜Kšœ˜K˜——Kšœ˜K˜—š žœœ?œœœœ˜xšœ˜šœ ˜ Kšœ=œ˜CKšœ œœ˜Kšœ˜—šœ˜Kšœ=œ˜CKšœ œœ˜Kšœ˜—Kšœ˜—Kšœ˜šœœœŸ˜9Kšœœ2˜DKšœœ-˜?Kšœœ(˜:Kš œ œœœœŸ%˜[KšœUŸ˜tKšœ5œŸ3˜‰Kš œ œœœœŸ%˜[K–'[maxSampleValue: ImagerSample.Sample]šœx˜xK–)[s: ROPE, pos: INT _ 0, skip: ROPE]šœ‡˜‡K˜—šœŸ˜Kšœ˜Kš œ œœœœŸ%˜[Kšœ/˜/KšœTœ˜tKš œ œœœœŸ%˜[K–[clear: BOOL]š œ œœtœ,œ˜ÃKšœ˜K˜—Kšœœ˜K˜K˜—šÐan œœŸ.˜YKšœœœ˜– [PROCESS]šžœœœ˜%Kšœœœ.œ˜KKšœ˜—šœœœœœœœ˜6Kšœœ ˜%Kšœ œŸG˜Zšœœ˜Kšœ!˜!Kšœ$˜$Kšœ˜KšœœŸ˜4Kšœœ˜Kšœœ˜—Kšœ˜—K˜K˜—šž œœœœ˜0KšœœN™WKšœœœ˜Kšœœ˜K˜K˜—š  œ˜$Kšœ œ ˜ KšœZœ˜`šœ ˜šœ Ÿ˜&Kšœœœœ˜(K–œ[bs: BiScrollers.BiScroller, client: BiScrollers.Location, viewer: BiScrollers.Location, doX: BOOL _ TRUE, doY: BOOL _ TRUE, paint: BOOL _ TRUE]šœéœ˜ïK˜—šœ Ÿ=˜IKšœœŸ"˜>Kšœœœœ˜(K–œ[bs: BiScrollers.BiScroller, client: BiScrollers.Location, viewer: BiScrollers.Location, doX: BOOL _ TRUE, doY: BOOL _ TRUE, paint: BOOL _ TRUE]šœœœ˜@Kšœœœ˜2Kšœéœ˜ðK–O[bs: BiScrollers.BiScroller, op: BiScrollers.ScaleOp, paint: BOOL _ TRUE]šœœœbœ˜{KšœZœœ˜}K˜—šœ Ÿ˜Kšœœ^œŸ˜¥Kšœœ Ÿ=˜÷Kšœ]œŸ˜†K˜—Kšœœ˜—š˜Kšœ6œ˜<—K˜K˜—š œŸd˜Kšœ œ ˜ KšœVœ˜\K–.[viewer: ViewerClasses.Viewer, prop: ATOM]šœ œ&˜5šœœœœ˜IKšœGœ˜MKšœ˜K˜—šœœœœ˜)KšœAœ˜GKšœ˜K˜—Kšœ*œœ8˜‚Kšœ`˜`K˜K˜—š œ˜Kšœ œ ˜ šœœœ˜2KšœCœ˜IKšœ˜Kšœ˜—K˜)Kšœ`œœœ˜ŽK˜K˜—š  œ˜!Kšœ œ ˜ Kšœ˜K˜K˜—š œ˜ Kšœ œ ˜ Kš œœœœœ˜2Kšœœœ4˜KK˜K˜—š œ˜ Kšœ œ ˜ Kšœ'˜'K˜K˜—š  œ˜"Kšœ œ ˜ Kš œœœœœ1˜KKšœœ˜Kšœœœ$˜;K˜K˜—š œ˜+Kšœ œ ˜ KšœM˜SK˜K˜—š œ˜(Kšœ œ ˜ šœ˜šœ ˜ Kšœ/œœ˜ZKšœ˜—šœ ˜ Kšœ/œœ2˜sKšœ˜—šœ˜KšœœœG˜^Kšœ˜—Kšœ˜—K˜K˜—šž œ œœ˜4Kšœ/œœ˜RK˜WKšœœœŸ%˜NKšœ˜KšœFœ˜LK˜K˜—š  œ˜(Kš œœ œœ œœ™@KšœœŸ˜4KšœœŸ˜8Kšœœœ˜(Kšœ œ˜Kšœ œ˜KšœPœ1œ˜‘Kš œœœœœ+˜U– [base: ROPE, index: INT _ 0]šœ5œ˜=Kšœœ˜ šœœœ˜1KšœCœœœ˜xKšœ˜—K˜K˜—šœœŸ?˜VKš œ$œœœ œ3˜xKšœ˜Kšœ>˜>Kšœ˜—š œœ&œœœŸ˜_Kšœ œ?˜cKšœ˜—Kšœ˜K˜—šž œ$˜1Kšœc˜cKšœEœ˜LK˜K˜—š ž œœœœœœ˜2Kšœœœ˜Kšœ-˜-K˜K˜—šž œœœœœœœœ˜8Kšœœœ˜Kšœ+˜+K˜—K˜Kš œœœœœœŸ˜*Kš œœœœœ6˜dKšœœœœ˜8Kšœ œœ˜Kšœ œ˜,Kšœ'˜'K˜šžœœ˜˜Kšœ.˜.—Kšœ"Ÿ$˜Fšœ(˜(Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ˜Kšœ œŸ&˜9Kšœ'Ÿ;˜bKšœŸ@˜[K˜—Kšœ9Ÿ@˜yKšœA˜AKšœ}˜}Kšœ˜—K˜K˜ K˜Kšœ˜K˜K˜—…—hþŒÌ