-- StrawSil.mesa -- Written by Joe Maleson -- Last changed by Doug Wyatt, October 27, 1980 4:40 PM DIRECTORY DisplayListDefs: FROM "DisplayListDefs", GraphicsDefs: FROM "GraphicsDefs", InlineDefs, IODefs: FROM "IODefs" USING [GetInputStream], MagicDefs: FROM "MagicDefs" USING [Menu], PressDefs: FROM "PressDefs" USING [PressObject,SignedMulDiv,MulDiv, ELShowRectangle,ELShowObject,ELShowCharacters,ELShowDots, ELShowDotsOpaque,PressDotsData], ProcessDefs: FROM "ProcessDefs" USING [Yield], StrawDefs: FROM "StrawDefs", StreamDefs: FROM "StreamDefs" USING [StreamHandle], SystemDefs: FROM "SystemDefs" USING [AllocateHeapNode,FreeHeapNode]; StrawSil: PROGRAM IMPORTS DisplayListDefs,GraphicsDefs,InlineDefs,IODefs,MagicDefs, PressDefs,ProcessDefs,StrawDefs,SystemDefs EXPORTS StrawDefs = BEGIN Buttons: TYPE = MACHINE DEPENDENT RECORD [ KeyPadAndGarbage: [0..10000B), Mark: BOOLEAN, --TRUE means button up Select: BOOLEAN, Draw: BOOLEAN ]; KeyWord2: TYPE = MACHINE DEPENDENT RECORD [ One,Esc,Tab,F,Ctrl,C,J,B,Z,LShift,Period,Semi,Return,LArrow,Del,xxx: BOOLEAN ]; MouseButtons: POINTER TO Buttons = LOOPHOLE[177030B]; KeyWord: POINTER TO KeyWord2 = LOOPHOLE[177036B]; MouseX: POINTER TO INTEGER = LOOPHOLE[424B]; MouseY: POINTER TO INTEGER = LOOPHOLE[425B]; CursorX: POINTER TO INTEGER = LOOPHOLE[426B]; CursorY: POINTER TO INTEGER = LOOPHOLE[427B]; bitsPerInch: CARDINAL _ 72; micasPerInch: CARDINAL = 2540; keys: StreamDefs.StreamHandle _ IODefs.GetInputStream[]; displayList: POINTER TO ARRAY [0..0) OF POINTER TO GraphicsDefs.Box; selectionIndex: CARDINAL; selectGray: CARDINAL _ 15; waitCount: CARDINAL _ 40; UnDeleteRecord: TYPE = RECORD [ count: CARDINAL, item: ARRAY [0..0) OF POINTER TO GraphicsDefs.Box ]; RealUnDeleteMax: CARDINAL = 6; UnDeleteStack: ARRAY [1..RealUnDeleteMax] OF POINTER TO UnDeleteRecord; UnDeleteStackPointer: CARDINAL _ 0; UnDeleteStackMax: CARDINAL _ RealUnDeleteMax-1; Ctrl: ARRAY CHARACTER ['A..'Z] OF CHARACTER = [1C,2C,3C,4C,5C,6C,7C,10C,11C,12C,13C, 14C,15C,16C,17C,20C,21C,22C,23C,24C,25C,26C,27C,30C,31C,32C]; ScreenHeight: CARDINAL _ GraphicsDefs.GetDefaultBitmapHandle[].nLines; Mica: TYPE = INTEGER; OriginX: Mica _ 0; OriginY: Mica _ 0; MarkX: Mica _ 0; MarkY: Mica _ 0; ButtonX: Mica _ 0; ButtonY: Mica _ 0; dc: PROCEDURE [m: Mica] RETURNS [INTEGER] = INLINE BEGIN RETURN[PressDefs.SignedMulDiv[m,bitsPerInch,micasPerInch]]; END; mc: PROCEDURE [c: CARDINAL] RETURNS [Mica] = INLINE BEGIN RETURN[PressDefs.MulDiv[c,micasPerInch,bitsPerInch]]; END; EditDisplayList: PUBLIC PROCEDURE = BEGIN OPEN DisplayListDefs; i: CARDINAL; ButtonState: Buttons; MagnifyOn: BOOLEAN _ FALSE; FlashMarks: PROCEDURE = BEGIN GraphicsDefs.XorArea[dc[OriginX],dc[OriginY],dc[OriginX]+3,dc[OriginY]+1]; GraphicsDefs.XorArea[dc[MarkX],dc[MarkY],dc[MarkX]+1,dc[MarkY]+6]; FOR i IN [0..waitCount] DO ProcessDefs.Yield[];ENDLOOP; GraphicsDefs.XorArea[dc[OriginX],dc[OriginY],dc[OriginX]+3,dc[OriginY]+1]; GraphicsDefs.XorArea[dc[MarkX],dc[MarkY],dc[MarkX]+1,dc[MarkY]+6]; END; displayList _ DisplayListDefs.GetDisplayListHandle[]; selectionIndex _ DisplayListDefs.GetDisplayCount[]; OriginX _ 0; OriginY _ 0; DO --edit loop DO --wait for all buttons to be released ButtonState _ MouseButtons^; IF (ButtonState.Mark AND ButtonState.Select AND ButtonState.Draw) OR (NOT keys.endof[keys]) THEN EXIT; FlashMarks[]; ENDLOOP; DO --wait for a button to go down count,i: CARDINAL; ButtonState _ MouseButtons^; IF (NOT (ButtonState.Mark AND ButtonState.Select AND ButtonState.Draw)) OR (NOT keys.endof[keys]) THEN EXIT; --check for cursor inside top selected image count _ DisplayListDefs.GetDisplayCount[]; IF count>0 AND (NOT MagnifyOn) AND (i_count-1)>=selectionIndex AND DisplayListDefs.Inside[CursorX^,CursorY^,displayList[i]] THEN { OPEN PressDefs; d: POINTER TO StrawDefs.DotObject _ LOOPHOLE[displayList[i]]; IF d.Object.command IN [ELShowDots..ELShowDotsOpaque] AND d.DotsData.nBitsPerPixel = 8 THEN StrawDefs.EditGrayImage[d]; }; FlashMarks[]; ENDLOOP; IF NOT keys.endof[keys] THEN BEGIN ch: CHARACTER _ keys.get[keys]; IF MagnifyOn THEN StrawDefs.StopMagnifier[]; SELECT ch FROM Ctrl['C] => BEGIN Copy[MarkX-OriginX,MarkY-OriginY]; OriginX _ MarkX;OriginY _ MarkY; END; Ctrl['D] => Delete[]; Ctrl['I] => BEGIN haveSelection: BOOLEAN _ selectionIndex StrawDefs.SelectFile[]; IF haveSelection THEN BEGIN UnDeleteStackMax _ UnDeleteStackMax+1;Delete[]; END; StrawDefs.AddFile[]; selectionIndex _ DisplayListDefs.GetDisplayCount[]; IF haveSelection THEN BEGIN UnDelete[];UnDeleteStackMax _ UnDeleteStackMax-1; END; END; Ctrl['K] => BEGIN ClearUnDeleteStack[]; StrawDefs.NewPage[]; StrawDefs.Refresh[]; selectionIndex _ 0; END; Ctrl['M] => BEGIN IF KeyWord.Ctrl THEN LOOP; --was MagnifyOn _ NOT MagnifyOn; IF MagnifyOn THEN BEGIN StrawDefs.SetMagnifierPosition[MarkX,MarkY]; END; END; Ctrl['O] => StrawDefs.WriteFile[]; Ctrl['Q] => EXIT; Ctrl['T] => BEGIN nItems: CARDINAL _ DisplayListDefs.GetDisplayCount[]; RemoveSelections[];ResetDisplayCount[selectionIndex]; StrawDefs.SetGrid[]; ResetDisplayCount[nItems];ShowSelections[]; END; Ctrl['U] => BEGIN NewSelection[];UnDelete[];END; Ctrl['X] => BEGIN temp: INTEGER; Move[MarkX-OriginX,MarkY-OriginY]; temp _ MarkX;MarkX _ OriginX;OriginX _ temp; temp _ MarkY;MarkY _ OriginY;OriginY _ temp; END; ENDCASE; IF MagnifyOn THEN StrawDefs.StartMagnifier[selectionIndex,selectGray]; LOOP; END; IF MagnifyOn THEN [ButtonX,ButtonY] _ StrawDefs.MagToMicaCoords[CursorX^,CursorY^] ELSE BEGIN ButtonX _ mc[CursorX^];ButtonY _ mc[CursorY^];END; IF NOT ButtonState.Select THEN BEGIN IF MagnifyOn THEN StrawDefs.StopMagnifier[]; IF NOT (KeyWord.Ctrl OR KeyWord.LShift) THEN --"DeSelect" BEGIN DeSelect[]; END ELSE IF NOT KeyWord.Ctrl THEN --"AddSelect" BEGIN Select[]; END ELSE IF NOT KeyWord.LShift THEN --"AreaSelect" BEGIN OriginX _ ButtonX; OriginY _ ButtonY; NewSelection[]; SelectArea[]; END ELSE BEGIN --"Select" NewSelection[]; Select[]; END; IF MagnifyOn THEN StrawDefs.StartMagnifier[selectionIndex,selectGray]; LOOP; END; IF NOT ButtonState.Draw THEN BEGIN IF MagnifyOn THEN StrawDefs.StopMagnifier[]; IF NOT (KeyWord.Ctrl OR KeyWord.LShift) THEN --"UnDelete" BEGIN NewSelection[]; UnDelete[]; END ELSE IF NOT KeyWord.Ctrl THEN --"Copy" BEGIN dx: INTEGER _ ButtonX - OriginX; dy: INTEGER _ ButtonY - OriginY; Copy[dx,dy]; MarkX _ OriginX _ OriginX + dx; MarkY _ OriginY _ OriginY + dy; END ELSE IF NOT KeyWord.LShift THEN --"Delete" BEGIN NewSelection[]; Select[]; Delete[]; END ELSE BEGIN --"Help" Help[]; END; IF MagnifyOn THEN StrawDefs.StartMagnifier[selectionIndex,selectGray]; LOOP; END; IF NOT ButtonState.Mark THEN BEGIN IF NOT (KeyWord.Ctrl OR KeyWord.LShift) THEN --"Move Magnifier" BEGIN IF NOT MagnifyOn THEN BEGIN MagnifyOn _ TRUE; StrawDefs.SetMagnifierPosition[ButtonX,ButtonY]; StrawDefs.StartMagnifier[selectionIndex,selectGray]; END; WHILE NOT (KeyWord.Ctrl OR KeyWord.LShift OR MouseButtons.Mark) DO [ButtonX,ButtonY] _ StrawDefs.MagToMicaCoords[CursorX^,CursorY^]; StrawDefs.SetMagnifierPosition[ButtonX,ButtonY]; StrawDefs.ShowMagnifier[]; ENDLOOP; END ELSE BEGIN IF MagnifyOn THEN IF StrawDefs.ModifyMagnifier[dc[ButtonX],dc[ButtonY]] THEN BEGIN WHILE (NOT MouseButtons.Mark) AND StrawDefs.ModifyMagnifier[CursorX^,CursorY^] DO ENDLOOP; LOOP; END ELSE StrawDefs.StopMagnifier[]; IF NOT KeyWord.Ctrl THEN --Move BEGIN dx: INTEGER _ ButtonX - OriginX; dy: INTEGER _ ButtonY - OriginY; Move[dx,dy]; MarkX _ OriginX; MarkY _ OriginY; OriginX _ OriginX + dx; OriginY _ OriginY + dy; END ELSE IF NOT KeyWord.LShift THEN --"MoveOrigin" BEGIN OriginX _ ButtonX; OriginY _ ButtonY; END ELSE BEGIN --"Mark" MarkX _ ButtonX; MarkY _ ButtonY; END; IF MagnifyOn THEN StrawDefs.StartMagnifier[selectionIndex,selectGray]; END; --non-magnify stuff LOOP; END; ENDLOOP; --main loop RemoveSelections[]; FOR i IN [selectionIndex..DisplayListDefs.GetDisplayCount[]) DO UnSelect[i]; ENDLOOP; --and clear out the UnDeleteStack ClearUnDeleteStack[]; END; Help: PROCEDURE = BEGIN HelpMenu: ARRAY [0..7) OF STRING _ [ " | Shift Ctrl CtlShft", "-----------------------------------", "Mark | MARK ORIGIN MOVE MAGNIFY", "Draw | HELP DELETE COPY UNDELETE", "Select| SEL AREA ADD DESELECT", "-----------------------------------", " keys| ^C,^D,^I,^K,^M,^O,^Q,^T,^U,^X" ]; font: POINTER TO GraphicsDefs.StrikeFont _ GraphicsDefs.GetStrikeHandle["Gacha10"]; [] _ MagicDefs.Menu[7,LOOPHOLE[@HelpMenu],font]; END; Copy: PROCEDURE [dx,dy: Mica] = BEGIN i: CARDINAL; newEBox,eBox: POINTER TO StrawDefs.EntityBox; obj: POINTER TO PressDefs.PressObject; nItems: CARDINAL _ DisplayListDefs.GetDisplayCount[]; objSize: CARDINAL; RemoveSelections[]; FOR i IN [selectionIndex..nItems) DO eBox _ LOOPHOLE[displayList[i]]; IF eBox.Object.command IN [PressDefs.ELShowDots..PressDefs.ELShowDotsOpaque] THEN BEGIN do: POINTER TO StrawDefs.DotObject _ SystemDefs.AllocateHeapNode[SIZE[StrawDefs.DotObject]]; newEBox _ LOOPHOLE[do]; objSize _ SIZE[PressDefs.PressObject]+SIZE[PressDefs.PressDotsData]; obj _ SystemDefs.AllocateHeapNode[objSize]; do.DotsData _ LOOPHOLE[obj+SIZE[PressDefs.PressObject]]; END ELSE BEGIN newEBox _ SystemDefs.AllocateHeapNode[SIZE[StrawDefs.EntityBox]]; objSize _ SIZE[PressDefs.PressObject]; obj _ SystemDefs.AllocateHeapNode[objSize]; END; GraphicsDefs.CreateBox[@newEBox.Box,eBox.Box.boxBitmap.nBits, eBox.Box.boxBitmap.nLines]; InlineDefs.COPY[from:eBox.Object,nwords:objSize,to:obj]; newEBox.Object _ obj; newEBox.Box.function _ eBox.Box.function; newEBox.Box.type _ eBox.Box.type; newEBox.Box.gray _ eBox.Box.gray; newEBox.Object.micaX _ eBox.Object.micaX + dx; newEBox.Object.micaY _ eBox.Object.micaY - dy; GraphicsDefs.PutBitmap[@eBox.Box.boxBitmap,0,0, @newEBox.Box.boxBitmap]; DisplayListDefs.AddDisplayItem[@newEBox.Box]; [newEBox.Box.displayX,newEBox.Box.displayY] _ ScreenCoords[newEBox]; ENDLOOP; FOR i IN [selectionIndex..nItems) DO UnSelect[i];ENDLOOP; ShowSelections[]; END; RemoveSelections: PROCEDURE = BEGIN i: CARDINAL; FOR i DECREASING IN [selectionIndex..DisplayListDefs.GetDisplayCount[]) DO GraphicsDefs.RemoveBox[displayList[i]]; ENDLOOP; END; ShowSelections: PROCEDURE = BEGIN i: CARDINAL; d: POINTER TO GraphicsDefs.Box; FOR i IN [selectionIndex..DisplayListDefs.GetDisplayCount[]) DO d _ displayList[i]; GraphicsDefs.DisplayBox[d,d.displayX,d.displayY]; ENDLOOP; END; ScreenCoords: PROCEDURE [e: POINTER TO StrawDefs.EntityBox] RETURNS [x,y: INTEGER] = BEGIN OPEN PressDefs; obj: POINTER TO PressObject _ e.Object; --displayX,displayY refer to top left corner. --different objects use different reference points, so be careful SELECT e.Object.command FROM ELShowObject => BEGIN x _ dc[obj.micaX+obj.spaceX]; y _ ScreenHeight-dc[obj.micaY+obj.spaceY]; END; ELShowRectangle,ELShowDots,ELShowDotsOpaque => BEGIN x _ dc[obj.micaX]; y _ ScreenHeight-dc[obj.micaY+obj.spaceY]; END; ELShowCharacters => BEGIN strike: POINTER TO GraphicsDefs.StrikeFont; [strike,,,] _ StrawDefs.GetFont[obj.font]; x _ dc[obj.micaX]; y _ ScreenHeight-(dc[obj.micaY]+strike.ascent); END; ENDCASE => ERROR; --can't move guys with unknown reference points END; Move: PROCEDURE [dx,dy: Mica] = BEGIN i: CARDINAL; eBox: POINTER TO StrawDefs.EntityBox; x,y: INTEGER; IF dx = 0 AND dy = 0 THEN RETURN; IF (DisplayListDefs.GetDisplayCount[]-selectionIndex) > 1 THEN FOR i DECREASING IN [selectionIndex..DisplayListDefs.GetDisplayCount[]) DO GraphicsDefs.RemoveBox[displayList[i]]; ENDLOOP; FOR i IN [selectionIndex..DisplayListDefs.GetDisplayCount[]) DO eBox _ LOOPHOLE[displayList[i]]; eBox.Object.micaX _ eBox.Object.micaX + dx; eBox.Object.micaY _ eBox.Object.micaY - dy; [x,y] _ ScreenCoords[eBox]; GraphicsDefs.MoveBox[@eBox.Box,x,y]; ENDLOOP; END; Delete: PROCEDURE = BEGIN i: CARDINAL; nItems: CARDINAL _ DisplayListDefs.GetDisplayCount[]; d: POINTER TO GraphicsDefs.Box; eBox: POINTER TO StrawDefs.EntityBox; u: POINTER TO UnDeleteRecord; IF selectionIndex = nItems THEN RETURN; --no action IF UnDeleteStackPointer = UnDeleteStackMax THEN --real delete BEGIN FOR i IN [0..UnDeleteStack[1].count) DO d _ UnDeleteStack[1].item[i]; eBox _ LOOPHOLE[d]; GraphicsDefs.DestroyBox[d,FALSE]; SystemDefs.FreeHeapNode[eBox.Object]; SystemDefs.FreeHeapNode[d]; ENDLOOP; SystemDefs.FreeHeapNode[UnDeleteStack[1]]; FOR i IN [1..UnDeleteStackMax) DO UnDeleteStack[i] _ UnDeleteStack[i+1]; ENDLOOP; END ELSE UnDeleteStackPointer _ UnDeleteStackPointer+1; u _ SystemDefs.AllocateHeapNode[(nItems-selectionIndex)+1]; u.count _ nItems-selectionIndex; UnDeleteStack[UnDeleteStackPointer] _ u; FOR i DECREASING IN [selectionIndex..nItems) DO d _ displayList[i]; u.item[i-selectionIndex] _ d; DisplayListDefs.DeleteDisplayItem[d]; StrawDefs.RestoreGrid[d]; ENDLOOP; END; UnDelete: PROCEDURE = BEGIN i: CARDINAL; u: POINTER TO UnDeleteRecord; IF UnDeleteStackPointer = 0 THEN RETURN; --nothing left u _ UnDeleteStack[UnDeleteStackPointer]; UnDeleteStackPointer _ UnDeleteStackPointer-1; FOR i IN [0..u.count) DO DisplayListDefs.AddDisplayItem[u.item[i]]; ENDLOOP; SystemDefs.FreeHeapNode[u]; ShowSelections[]; END; ClearUnDeleteStack: PROCEDURE = BEGIN d: POINTER TO GraphicsDefs.Box; eBox: POINTER TO StrawDefs.EntityBox; i: CARDINAL; UNTIL UnDeleteStackPointer = 0 DO FOR i IN [0..UnDeleteStack[UnDeleteStackPointer].count) DO d _ UnDeleteStack[UnDeleteStackPointer].item[i]; eBox _ LOOPHOLE[d]; GraphicsDefs.DestroyBox[d,FALSE]; SystemDefs.FreeHeapNode[eBox.Object]; SystemDefs.FreeHeapNode[d]; ENDLOOP; SystemDefs.FreeHeapNode[UnDeleteStack[UnDeleteStackPointer]]; UnDeleteStackPointer _ UnDeleteStackPointer-1; ENDLOOP; END; NewSelection: PROCEDURE = BEGIN OPEN GraphicsDefs; d: POINTER TO Box; i: CARDINAL; RemoveSelections[]; FOR i IN [selectionIndex..DisplayListDefs.GetDisplayCount[]) DO d _ displayList[i]; SetGrayLevel[selectGray]; XorGray[0,0,d.boxBitmap.nBits,d.boxBitmap.nLines,@d.boxBitmap]; DisplayBox[d,d.displayX,d.displayY]; ReleaseBox[d]; ENDLOOP; selectionIndex _ DisplayListDefs.GetDisplayCount[]; END; Select: PROCEDURE = BEGIN i: CARDINAL; x: INTEGER _ dc[ButtonX]; y: INTEGER _ dc[ButtonY]; FOR i DECREASING IN [0..selectionIndex) DO IF DisplayListDefs.Inside[x,y,displayList[i]] THEN BEGIN d: POINTER TO GraphicsDefs.Box _ displayList[i]; RemoveSelections[]; MakeSelect[i]; ShowSelections[]; EXIT; END; ENDLOOP; END; SelectArea: PROCEDURE = BEGIN i: CARDINAL; x1: INTEGER _ dc[MIN[OriginX,MarkX]]; y1: INTEGER _ dc[MIN[OriginY,MarkY]]; x2: INTEGER _ dc[MAX[OriginX,MarkX]]; y2: INTEGER _ dc[MAX[OriginY,MarkY]]; d: POINTER TO GraphicsDefs.Box; nItems: CARDINAL _ DisplayListDefs.GetDisplayCount[]; lastItem: CARDINAL _ nItems-1; FOR i DECREASING IN [0..selectionIndex) DO d _ displayList[i]; IF INTEGER[d.displayX] >= x1 AND INTEGER[d.displayY] >= y1 AND INTEGER[d.displayX+d.boxBitmap.nBits] <= x2 AND INTEGER[d.displayY+d.boxBitmap.nLines] <= y2 THEN BEGIN MakeSelect[i]; END; ENDLOOP; --first, need to unscramble them FOR i IN [0..(nItems-selectionIndex)/2) DO d _ displayList[lastItem-i]; displayList[lastItem-i] _ displayList[selectionIndex+i]; displayList[selectionIndex+i] _ d; ENDLOOP; FOR i IN [selectionIndex..nItems) DO d _ displayList[i]; GraphicsDefs.DisplayBox[d,d.displayX,d.displayY]; ENDLOOP; OriginX _ MIN[OriginX,MarkX]; OriginY _ MIN[OriginY,MarkY]; END; MakeSelect: PROCEDURE [i: CARDINAL] = BEGIN d: POINTER TO GraphicsDefs.Box _ displayList[i]; e: POINTER TO StrawDefs.EntityBox _ LOOPHOLE[d]; DisplayListDefs.DeleteDisplayItem[d]; StrawDefs.RestoreGrid[d]; DisplayListDefs.AddDisplayItem[d]; GraphicsDefs.SetGrayLevel[selectGray]; GraphicsDefs.XorGray[0,0,d.boxBitmap.nBits,d.boxBitmap.nLines,@d.boxBitmap]; OriginX _ mc[d.displayX]; OriginY _ mc[d.displayY]; selectionIndex _ selectionIndex-1; END; DeSelect: PROCEDURE = BEGIN i: CARDINAL; x: INTEGER _ dc[ButtonX]; y: INTEGER _ dc[ButtonY]; FOR i DECREASING IN [selectionIndex..DisplayListDefs.GetDisplayCount[]) DO IF DisplayListDefs.Inside[x,y,displayList[i]] THEN BEGIN RemoveSelections[]; UnSelect[i]; ShowSelections[]; EXIT; END; ENDLOOP; END; UnSelect: PROCEDURE [i: CARDINAL] = BEGIN j: CARDINAL; d: POINTER TO GraphicsDefs.Box _ displayList[i]; GraphicsDefs.SetGrayLevel[selectGray]; GraphicsDefs.XorGray[0,0,d.boxBitmap.nBits,d.boxBitmap.nLines,@d.boxBitmap]; GraphicsDefs.DisplayBox[d,d.displayX,d.displayY]; GraphicsDefs.ReleaseBox[d]; FOR j DECREASING IN [selectionIndex..i) DO displayList[j+1] _ displayList[j]; ENDLOOP; displayList[selectionIndex] _ d; selectionIndex _ selectionIndex+1; END; END.(635)\f1 661b8B146b7B150b8B118b12B42b7B43b6B39b6B39b7B39b7B39b11B18b12B225b14B91b15B16b13B60b20B16b16B182b12B60b4B129b2B121b2B116b15B1313b14B4799b4B479b4B1541b16B165b14B216b12B790b4B594b6B1026b8B338b18B510b12B389b6B328b10B973b10B440b8B310b8B