| Win32 API and Printers: Changing Printer Attributes |
|
|
|
| Contributed by Rick Kelly | |
| 27 August 2002 | |
|
Rick shows us how to change or update a Windows printer!Win32 API and Printers Changing Printer Attributes © 2002 Rick Kelly www.crooit.com Preface Libraries and forms (Paradox 9) presented are available as a download here. Paradox 8 is not supported although Paradox 7-32 surprisingly works. After downloading into the folder of your choice, make that folder :WORK: and run the included form for a demonstration. You'll need to run the form before opening it in design mode as it references table :PRIV:__PJobs that is created in the form's INIT event. (If running Paradox 10, or if the table doesn't get created properly, you should first run the BldSpool.ssl script prior to running the form.) Introduction Previous articles in this series:
As printers have differing capabilities, each driver communicates which portion of the Windows common feature set it supports through the use of bit level flags. These bit level flags and printer settings are stored in a structure called the DEVMODE. Each printer driver typically references a large amount of device-independent and device-dependent information in DEVMODE that we don’t know or care about. When we make modifications to fields in the device-independent part of DEVMODE, it may also effect changes in the device-dependent portion and we will need to take additional steps to ensure we have a consistent DEVMODE structure before the printer update. In the first article of this series, we covered retrieval of printer attributes which were returned in a Paradox Record Type structure of custom type W32PrinterInfo. We are adding an additional field of LongInt Type called FieldsCanChange to W32PrinterInfo which contains bit level flags for which DEVMODE fields the print driver supports changes. These bit level flags are mapped into the following custom Paradox Record Type by the W32CanSetPrinterFields method: Type
;
; Printer attributes that can be changed - driver specific
;
W32SetPrinterFields =
record
Orientation Logical
PaperSize Logical
PaperLength Logical
PaperWidth Logical
Scale Logical
Copies Logical
DefaultPaperSource Logical
PrintQuality Logical
Color Logical
Duplex Logical
YResolution Logical
TTOption Logical
Collate Logical
FormName Logical
endRecord
endType
Const
;
; Printer attributes that can be changed
;
cnOrientation = 1
cnPaperSize = 2
cnPaperLength = 4
cnPaperWidth = 8
cnScale = 16
cnCopies = 256
cnDefaultPaperSource = 512
cnPrintQuality = 1024
cnColor = 2048
cnDuplex = 4096
cnYResolution = 8192
cnTTOption = 16384
cnCollate = 32768
cnFormName = 65536
endConst
method W32CanSetPrinterFields(liFields LongInt) W32SetPrinterFields
var
spf W32SetPrinterFields
endVar
;
; Return Available Fields for printer
;
switch
case liFields.bitAnd(cnOrientation) = cnOrientation :
spf.Orientation = True
otherwise :
spf.Orientation = False
endSwitch
switch
case liFields.bitAnd(cnPaperSize) = cnPaperSize :
spf.PaperSize = True
otherwise :
spf.PaperSize = False
endSwitch
switch
case liFields.bitAnd(cnPaperLength) = cnPaperLength :
spf.PaperLength = True
otherwise :
spf.PaperLength = False
endSwitch
switch
case liFields.bitAnd(cnPaperWidth) = cnPaperWidth :
spf.PaperWidth = True
otherwise :
spf.PaperWidth = False
endSwitch
switch
case liFields.bitAnd(cnScale) = cnScale :
spf.Scale = True
otherwise :
spf.Scale = False
endSwitch
switch
case liFields.bitAnd(cnCopies) = cnCopies :
spf.Copies = True
otherwise :
spf.Copies = False
endSwitch
switch
case liFields.bitAnd(cnOrientation) = cnOrientation :
spf.Orientation = True
otherwise :
spf.Orientation = False
endSwitch
switch
case liFields.bitAnd(cnDefaultPaperSource) = cnDefaultPaperSource :
spf.DefaultPaperSource = True
otherwise :
spf.DefaultPaperSource = False
endSwitch
switch
case liFields.bitAnd(cnPrintQuality) = cnPrintQuality :
spf.PrintQuality = True
otherwise :
spf.PrintQuality = False
endSwitch
switch
case liFields.bitAnd(cnColor) = cnColor :
spf.Color = True
otherwise :
spf.Color = False
endSwitch
switch
case liFields.bitAnd(cnDuplex) = cnDuplex :
spf.Duplex = True
otherwise :
spf.Duplex = False
endSwitch
switch
case liFields.bitAnd(cnYResolution) = cnYResolution :
spf.YResolution = True
otherwise :
spf.YResolution = False
endSwitch
switch
case liFields.bitAnd(cnTTOption) = cnTTOption :
spf.TTOption = True
otherwise :
spf.TTOption = False
endSwitch
switch
case liFields.bitAnd(cnCollate) = cnCollate :
spf.Collate = True
otherwise :
spf.Collate = False
endSwitch
switch
case liFields.bitAnd(cnFormName) = cnFormName :
spf.FormName = True
otherwise :
spf.FormName = False
endSwitch
return spf
endMethod
Referenced Win32 APIs Other referenced Win32 API's have been covered in earlier articles. Newly introduced methods are: DocumentProperties( hWnd cLong, hPrn cLong, pDeviceName cPtr, pDevModeOutput cLong, pDevModeInput cLong, fMode cLong) cLong [stdcall "DocumentPropertiesA"]hWnd - Window Handle - always 0 for our use hPrn - Handle to the printer pDeviceName - Printer Name pDevModeOutput - Pointer to update/merged/retrieved DEVMODE pDevModeInput - Pointer to input DEVMODE fMode - Flags indicating operation to perform Updating Printer We use the following Paradox DynArray of AnyType to hold what fields to change and their respective new values. Type W32SetPrinter = DynArray[] AnyType ; ; Index Name Type ; --------------------------- ; ; Comment String ; Location String ; SeparatorPage String ; Orientation SmallInt ; PaperSize SmallInt ; PaperLength SmallInt ; PaperWidth SmallInt ; Scale SmallInt ; Copies SmallInt ; DefaultPaperSource SmallInt ; PrintQuality SmallInt ; Color SmallInt ; Duplex SmallInt ; YResolution SmallInt ; TTOption SmallInt ; Collate SmallInt ; FormName String ; DefaultPriority SmallInt ; Priority SmallInt ; StartTime LongInt ; UntilTime LongInt ; endTypeRefer to the first article of this series for an explanation of each field and valid values. Constants we will be referring to: Const ; ; Administrative Access ; cnPrinterAccessAdminister = 4 cnPrinterAccessUse = 8 cnPrinterStandardRights = 983040 ;0x000F0000 ; ; Printer attributes that can be changed ; cnOrientation = 1 cnPaperSize = 2 cnPaperLength = 4 cnPaperWidth = 8 cnScale = 16 cnCopies = 256 cnDefaultPaperSource = 512 cnPrintQuality = 1024 cnColor = 2048 cnDuplex = 4096 cnYResolution = 8192 cnTTOption = 16384 cnCollate = 32768 cnFormName = 65536 ; ; DocumentProperties Options ; cnDMOutBuffer = 2 cnDMInBuffer = 8 endConstCommon procedures referenced are: Proc cmGetPrinterInfo(var stPrinter String,
var liPrinterHandle LongInt,
var liMemoryStructure LongInt) Logical
var
liReturn LongInt
liSizeNeeded LongInt
liSizeUsed LongInt
liDevMode LongInt
loReturn Logical
endVar
;
; Get admin access handle to the printer
;
liPrinterHandle = 0
liMemoryStructure = 0
liDevMode = 0
loReturn = False
switch
case cmOpenPrinterAdmin(stPrinter,liPrinterHandle) = False :
otherwise :
;
; First call is to get buffer size needed
;
liSizeNeeded = 0
liSizeUsed = 0
liReturn = GetPrinter(
liPrinterHandle,
2,
0,
0,
liSizeNeeded)
;
; Allocate memory for Printer Info Structure Level 2
;
liMemoryStructure = GlobalAlloc(fromHex("0x40"),liSizeNeeded)
;
; Get Printer Info Structure
;
liReturn = GetPrinter(
liPrinterHandle,
2,
liMemoryStructure,
liSizeNeeded,
liSizeUsed)
;
; Check if GetPrinter returned a DEVMODE structure pointer
;
switch
case liReturn = 1 :
MoveFromMemory(liDevMode,liMemoryStructure + 28,4)
switch
;
; DEVMODE structure missing - attempt to
; get one
;
case liDevMode = 0 :
loReturn = cmGetDeviceMode(
stPrinter,
liPrinterHandle,
liDevMode)
otherwise :
loReturn = True
endSwitch
endSwitch
switch
case loReturn = False :
liReturn = GlobalFree(liMemoryStructure)
liMemoryStructure = 0
liReturn = ClosePrinter(liPrinterHandle)
liPrinterHandle = 0
switch
case liDevMode <> 0 :
liReturn = GlobalFree(liDevMode)
endSwitch
endSwitch
endSwitch
return loReturn
endProc
Proc cmGetDeviceMode(var stPrinter String,
var liPrinterHandle LongInt,
var liDevMode LongInt) Logical
var
liSizeNeeded LongInt
liReturn LongInt
loReturn Logical
endVar
liDevMode = 0
loReturn = False
liSizeNeeded = DocumentProperties(
0,
liPrinterHandle,
stPrinter,
0,
0,
0)
switch
case liSizeNeeded > 0 :
;
; Allocate memory for DEVMODE structure
;
liDevMode = GlobalAlloc(fromHex("0x40"),liSizeNeeded)
;
; Retrieve DEVMODE structure from driver
;
liReturn = DocumentProperties(
0,
liPrinterHandle,
stPrinter,
liDevMode,
0,
cnDMOutBuffer)
loReturn = (liReturn = 1)
endSwitch
return loReturn
endProc
Proc cmOpenPrinterAdmin(var stPrinter String,
var liPrinterHandle LongInt) Logical
;
; Open printer for administrative access
;
var
liReturn LongInt
liAny LongInt
liDefaultStructure LongInt
endVar
;
; Build a printer defaults structure
;
; liDataType LongInt (address pointer)
; liDeviceMode LongInt (address pointer)
; liDesiredAccess LongInt
;
liDefaultStructure = GlobalAlloc(fromHex("0x40"),12)
liAny = 0
MoveToMemory(liDefaultStructure,liAny,4)
MoveToMemory(liDefaultStructure + 4,liAny,4)
liAny = cnPrinterAccessAdminister
+ cnPrinterAccessUse
+ cnPrinterStandardRights
MoveToMemory(liDefaultStructure + 8,liAny,4)
;
; Get a handle to the printer
;
liPrinterHandle = 0
liReturn = OpenPrinter(stPrinter,liPrinterHandle,liDefaultStructure)
GlobalFree(liDefaultStructure)
return liPrinterHandle <> 0
endProc
Proc cmCopyStringToMemory(stAny String) LongInt
var
liPointer LongInt
liSize LongInt
endVar
;
; If string is not blank, allocate memory and copy it
;
liPointer = 0
switch
case stAny.size() > 0 :
liSize = stAny.size() + 1
liPointer = GlobalAlloc(fromHex("0x40"),liSize)
MoveToMemory(liPointer,stAny,liSize)
endSwitch
return liPointer
endProc
Our basic outline to updating printer attributes is:
method SetPrinterInfo(stPrinter String,
var dyPrinterAttributes W32SetPrinter) Logical
var
loReturn Logical
liPrinterHandle LongInt
liReturn LongInt
liMemoryStructure LongInt
liDevMode LongInt
liAny LongInt
liFields LongInt
arMemory Array[] LongInt
siAny SmallInt
stAny String
liSupportedFields LongInt
endVar
liFields = 0
loReturn = False
arMemory.empty()
;
; Open printer
;
switch
case cmGetPrinterInfo(
stPrinter,
liPrinterHandle,
liMemoryStructure) = False :
otherwise :
;
; Update Printer Info Structure Level 2 and/or associated DEVMODE
;
liAny = 0
liSupportedFields = 0
;
; Do not attempt to set security descriptor
;
MoveToMemory(liMemoryStructure + 48,liAny,4)
;
; Get pointer to DevMode structure
;
MoveFromMemory(liDevMode,liMemoryStructure + 28,4)
;
; Get flags for driver field change support
;
MoveFromMemory(liSupportedFields,liDevMode + 40,4)
;
; Update Comment
;
switch
case dyPrinterAttributes.contains("Comment") = False :
otherwise :
liAny = cmCopyStringToMemory(
dyPrinterAttributes["Comment"])
MoveToMemory(liMemoryStructure + 20,liAny,4)
arMemory.grow(1)
arMemory[arMemory.size()] = liAny
endSwitch
;
; Update Location
;
switch
case dyPrinterAttributes.contains("Location") = False :
otherwise :
liAny = cmCopyStringToMemory(
dyPrinterAttributes["Location"])
MoveToMemory(liMemoryStructure + 24,liAny,4)
arMemory.grow(1)
arMemory[arMemory.size()] = liAny
endSwitch
;
; Update Separator Page
;
switch
case dyPrinterAttributes.contains("SeparatorPage") = False :
otherwise :
liAny = cmCopyStringToMemory(
dyPrinterAttributes["SeparatorPage"])
MoveToMemory(liMemoryStructure + 32,liAny,4)
arMemory.grow(1)
arMemory[arMemory.size()] = liAny
endSwitch
;
; Update Default Priority
;
switch
case dyPrinterAttributes.contains("DefaultPriority") = False :
otherwise :
try
liAny = dyPrinterAttributes["DefaultPriority"]
MoveToMemory(liMemoryStructure + 60,liAny,4)
onFail
errorClear()
msgStop(stPrinter,"Invalid Default Priority=" +
strval(dyPrinterAttributes["DefaultPriority"]))
endTry
endSwitch
;
; Update Priority
;
switch
case dyPrinterAttributes.contains("Priority") = False :
otherwise :
try
liAny = dyPrinterAttributes["Priority"]
MoveToMemory(liMemoryStructure + 56,liAny,4)
onFail
errorClear()
msgStop(stPrinter,"Invalid Priority=" +
strval(dyPrinterAttributes["Priority"]))
endTry
endSwitch
;
; Update Start Time
;
switch
case dyPrinterAttributes.contains("StartTime") = False :
otherwise :
try
liAny = dyPrinterAttributes["StartTime"]
MoveToMemory(liMemoryStructure + 64,liAny,4)
onFail
errorClear()
msgStop(stPrinter,"Invalid Start Time=" +
strval(dyPrinterAttributes["StartTime"]))
endTry
endSwitch
;
; Update Until Time
;
switch
case dyPrinterAttributes.contains("UntilTime") = False :
otherwise :
try
liAny = dyPrinterAttributes["UntilTime"]
MoveToMemory(liMemoryStructure + 68,liAny,4)
onFail
errorClear()
msgStop(stPrinter,"Invalid Until Time=" +
strval(dyPrinterAttributes["UntilTime"]))
endTry
endSwitch
;
; Update orientation
;
switch
case dyPrinterAttributes.contains("Orientation") = False :
;
; Does driver support this change?
;
case liSupportedFields.bitAnd(cnOrientation) = cnOrientation :
try
siAny = dyPrinterAttributes["Orientation"]
liFields = liFields
+ cnOrientation
MoveToMemory(liDevMode + 44,siAny,2)
onFail
errorClear()
msgStop(stPrinter,"Invalid Orientation=" +
strval(dyPrinterAttributes["Orientation"]))
endTry
otherwise :
msgStop(stPrinter,"Driver does not support Orientation changes")
endSwitch
;
; Update Paper Size
;
switch
case dyPrinterAttributes.contains("PaperSize") = False :
;
; Does driver support this change?
;
case liSupportedFields.bitAnd(cnPaperSize) = cnPaperSize :
try
siAny = dyPrinterAttributes["PaperSize"]
liFields = liFields
+ cnPaperSize
MoveToMemory(liDevMode + 46,siAny,2)
onFail
errorClear()
msgStop(stPrinter,"Invalid Paper Size=" +
strval(dyPrinterAttributes["PaperSize"]))
endTry
otherwise :
msgStop(stPrinter,"Driver does not support Paper Size changes")
endSwitch
;
; Update Paper Length
;
switch
case dyPrinterAttributes.contains("PaperLength") = False :
;
; Does driver support this change?
;
case liSupportedFields.bitAnd(cnPaperLength) = cnPaperLength :
try
siAny = dyPrinterAttributes["PaperLength"]
liFields = liFields
+ cnPaperLength
MoveToMemory(liDevMode + 48,siAny,2)
onFail
errorClear()
msgStop(stPrinter,"Invalid Paper Length=" +
strval(dyPrinterAttributes["PaperLength"]))
endTry
otherwise :
msgStop(stPrinter,"Driver does not support Paper Length changes")
endSwitch
;
; Update Paper Width
;
switch
case dyPrinterAttributes.contains("PaperWidth") = False :
;
; Does driver support this change?
;
case liSupportedFields.bitAnd(cnPaperWidth) = cnPaperWidth :
try
siAny = dyPrinterAttributes["PaperWidth"]
liFields = liFields
+ cnPaperWidth
MoveToMemory(liDevMode + 50,siAny,2)
onFail
errorClear()
msgStop(stPrinter,"Invalid Paper Width=" +
strval(dyPrinterAttributes["PaperWidth"]))
endTry
otherwise :
msgStop(stPrinter,"Driver does not support Paper Width changes")
endSwitch
;
; Update Scale
;
switch
case dyPrinterAttributes.contains("Scale") = False :
;
; Does driver support this change?
;
case liSupportedFields.bitAnd(cnScale) = cnScale :
try
siAny = dyPrinterAttributes["Scale"]
liFields = liFields
+ cnScale
MoveToMemory(liDevMode + 52,siAny,2)
onFail
errorClear()
msgStop(stPrinter,"Invalid Scale=" +
strval(dyPrinterAttributes["Scale"]))
endTry
otherwise :
msgStop(stPrinter,"Driver does not support Scale changes")
endSwitch
;
; Update copies
;
switch
case dyPrinterAttributes.contains("Copies") = False :
;
; Does driver support this change?
;
case liSupportedFields.bitAnd(cnCopies) = cnCopies :
try
siAny = dyPrinterAttributes["Copies"]
liFields = liFields
+ cnCopies
MoveToMemory(liDevMode + 54,siAny,2)
onFail
errorClear()
msgStop(stPrinter,"Invalid Copies=" +
strval(dyPrinterAttributes["Copies"]))
endTry
otherwise :
msgStop(stPrinter,"Driver does not support Copies changes")
endSwitch
;
; Update Default Paper Source
;
switch
case dyPrinterAttributes.contains("DefaultPaperSource") = False :
;
; Does driver support this change?
;
case liSupportedFields.bitAnd(cnDefaultPaperSource) = cnDefaultPaperSource :
try
siAny = dyPrinterAttributes["DefaultPaperSource"]
liFields = liFields
+ cnDefaultPaperSource
MoveToMemory(liDevMode + 56,siAny,2)
onFail
errorClear()
msgStop(stPrinter,"Invalid Default Paper Source=" +
strval(dyPrinterAttributes["DefaultPaperSource"]))
endTry
otherwise :
msgStop(stPrinter,"Driver does not support Default Paper Source changes")
endSwitch
;
; Update Print Quality
;
switch
case dyPrinterAttributes.contains("PrintQuality") = False :
;
; Does driver support this change?
;
case liSupportedFields.bitAnd(cnPrintQuality) = cnPrintQuality :
try
siAny = dyPrinterAttributes["PrintQuality"]
liFields = liFields
+ cnPrintQuality
MoveToMemory(liDevMode + 58,siAny,2)
onFail
errorClear()
msgStop(stPrinter,"Invalid Print Quality=" +
strval(dyPrinterAttributes["PrintQuality"]))
endTry
otherwise :
msgStop(stPrinter,"Driver does not support Print Quality changes")
endSwitch
;
; Update Color
;
switch
case dyPrinterAttributes.contains("Color") = False :
;
; Does driver support this change?
;
case liSupportedFields.bitAnd(cnColor) = cnColor :
try
siAny = dyPrinterAttributes["Color"]
liFields = liFields
+ cnColor
MoveToMemory(liDevMode + 60,siAny,2)
onFail
errorClear()
msgStop(stPrinter,"Invalid Color=" +
strval(dyPrinterAttributes["Color"]))
endTry
otherwise :
msgStop(stPrinter,"Driver does not support Color changes")
endSwitch
;
; Update Duplex
;
switch
case dyPrinterAttributes.contains("Duplex") = False :
;
; Does driver support this change?
;
case liSupportedFields.bitAnd(cnDuplex) = cnDuplex :
try
siAny = dyPrinterAttributes["Duplex"]
liFields = liFields
+ cnDuplex
MoveToMemory(liDevMode + 62,siAny,2)
onFail
errorClear()
msgStop(stPrinter,"Invalid Duplex=" +
strval(dyPrinterAttributes["Duplex"]))
endTry
otherwise :
msgStop(stPrinter,"Driver does not support Duplex changes")
endSwitch
;
; Update YResolution
;
switch
case dyPrinterAttributes.contains("YResolution") = False :
;
; Does driver support this change?
;
case liSupportedFields.bitAnd(cnYResolution) = cnYResolution :
try
siAny = dyPrinterAttributes["YResolution"]
liFields = liFields
+ cnYResolution
MoveToMemory(liDevMode + 64,siAny,2)
onFail
errorClear()
msgStop(stPrinter,"Invalid YResolution=" +
strval(dyPrinterAttributes["YResolution"]))
endTry
otherwise :
msgStop(stPrinter,"Driver does not support YResolution changes")
endSwitch
;
; Update TTOption
;
switch
case dyPrinterAttributes.contains("TTOption") = False :
;
; Does driver support this change?
;
case liSupportedFields.bitAnd(cnTTOption) = cnTTOption :
try
siAny = dyPrinterAttributes["TTOption"]
liFields = liFields
+ cnTTOption
MoveToMemory(liDevMode + 66,siAny,2)
onFail
errorClear()
msgStop(stPrinter,"Invalid TTOption=" +
strval(dyPrinterAttributes["TTOption"]))
endTry
otherwise :
msgStop(stPrinter,"Driver does not support TTOption changes")
endSwitch
;
; Update Collate
;
switch
case dyPrinterAttributes.contains("Collate") = False :
;
; Does driver support this change?
;
case liSupportedFields.bitAnd(cnCollate) = cnCollate :
try
siAny = dyPrinterAttributes["Collate"]
liFields = liFields
+ cnCollate
MoveToMemory(liDevMode + 68,siAny,2)
onFail
errorClear()
msgStop(stPrinter,"Invalid Collate=" +
strval(dyPrinterAttributes["Collate"]))
endTry
otherwise :
msgStop(stPrinter,"Driver does not support Collate changes")
endSwitch
;
; Update Form Name
;
switch
case dyPrinterAttributes.contains("FormName") = False :
;
; Does driver support this change?
;
case liSupportedFields.bitAnd(cnFormName) = cnFormName :
;
; Form Name size limit is 32 bytes
;
switch
case dyPrinterAttributes["FormName"].size() > 31 :
stAny = dyPrinterAttributes["FormName"].substr(1,31)
otherwise :
stAny = dyPrinterAttributes["FormName"]
endSwitch
liAny = stAny.size() + 1
MoveToMemory(liDevMode + 70,stAny,liAny)
liFields = liFields
+ cnFormName
otherwise :
msgStop(stPrinter,"Driver does not support Form Name changes")
endSwitch
;
; Ensure that driver dependent portion of DEVMODE is updated
;
liReturn = DocumentProperties(
0,
liPrinterHandle,
stPrinter,
liDevMode,
liDevMode,
cnDMInBuffer + cnDMOutBuffer)
switch
case liReturn = 1 :
;
; Update printer attributes - if liReturn <> 1
; the driver doesn't support or is unable to
; make the changes
;
liReturn = SetPrinter(
liPrinterHandle,
2,
liMemoryStructure,
0)
switch
case liReturn = 1 :
;
; Send out alert to all running applications
; that a system value has been updated
;
liReturn = SendDevModeMessage(
cnBroadcast,
cnSettingChange,
0,
stPrinter,
cnTimeOutNormal,
1000,
0)
loReturn = True
endSwitch
endSwitch
;
; Release Memory
;
liReturn = GlobalFree(liMemoryStructure)
liReturn = GlobalFree(liDevMode)
;
; Release printer handle
;
liReturn = ClosePrinter(liPrinterHandle)
endSwitch
;
; Release any miscellaneous memory
;
switch
case arMemory.size() > 0 :
for siAny from 1 to arMemory.size()
liAny = arMemory[siAny]
liReturn = GlobalFree(liAny)
endFor
endSwitch
return loReturn
endMethod
Conclusion We now have methods that support updating Windows printer attributes. (Hint: Look at the underlying OPAL in the provided libraries and the form and see what else you can discover and learn about<g>) Next: I tink T-T-T-T-T-T-T-T-That's all folks! |
| < Prev | Next > |
|---|





