Procedure wird nicht abgearbeitet
Verfasst: 28.06.2023 12:14
Hallo,
ich hoffe irgendwer aus der PureBasic-Gemeinde kann mir helfen und findet meinen Fehler.
Kurze Beschreibung was das Programm machen soll:
Nach meinen Reisen mache ich meine Urlaubsfilme, in denen immer chronolgisch die Reise gezeigt wird.
Da ich die Bilder und Videos mit mehreren Kameras mache, (Fotocamera, mein Pixel-Handy, IPhone meiner Frau) haben leider alle Bilder verschiedene Dateinamen-Strukturen. Mal ist das Aufnahmedatum im Filename mal nicht. Aber alle haben Exif-Daten! Das Sortieren unter Win10 nach Aufnahmedatum ist nicht wirklich zu gebrauchen. leider!
Um die Bilder im Projektfolder für das Urlaubsvideo in der chronologischen Reihenfolge zu haben möchte ich aller Bilder wie folgt in einen "Date-Renamed"-Folder unter dem Original-Folder kopieren:
Klicke ich einen Dateinamen in der Explorerliste an, funktioniert die Anzeige der Exif-Daten einwandfrei. Gleiche Prozedur wird in der While-Wend-Schleife aufgerufen und funktioniert nicht!
IM Source-Code nach "MISTAKE" suchen dann ist man in der Prozedure die nicht so funktioniert wie sie soll!
Für Eure Hilfe bedanke ich mich schon mal im voraus.
Rainer
Hier mein Code, der auf einem Code aus dem englische Forum basiert.
ich hoffe irgendwer aus der PureBasic-Gemeinde kann mir helfen und findet meinen Fehler.
Kurze Beschreibung was das Programm machen soll:
Nach meinen Reisen mache ich meine Urlaubsfilme, in denen immer chronolgisch die Reise gezeigt wird.
Da ich die Bilder und Videos mit mehreren Kameras mache, (Fotocamera, mein Pixel-Handy, IPhone meiner Frau) haben leider alle Bilder verschiedene Dateinamen-Strukturen. Mal ist das Aufnahmedatum im Filename mal nicht. Aber alle haben Exif-Daten! Das Sortieren unter Win10 nach Aufnahmedatum ist nicht wirklich zu gebrauchen. leider!
Um die Bilder im Projektfolder für das Urlaubsvideo in der chronologischen Reihenfolge zu haben möchte ich aller Bilder wie folgt in einen "Date-Renamed"-Folder unter dem Original-Folder kopieren:
- Falls unter dem Original-Folder noch kein Date-Renamed-Folder besteht, wird er erstellt >>> funktioniert!
- Dann wird der Selectbalken auf den ersten Eintrag in der Folder Liste gesetzt >>> Funktioniert leider nicht wie gewünscht.
- Danach wird der Folder Datei für Datei in einer While-Wend-Schleife abgearbeitet und wenn der Eintrag ein Bild ist wird es gezeigt, die Exif-Daten ausgelesen >>> wobei die Anzeige funktioniert, das Auslesen vielleicht, aber das füllen der Listbox mit den Exifdaten funktioniert nicht!
- Der Folder wird komplett durchgearbeitet >> Funktioniert, denn jedes Bild wird im Preview-Bereich gezeigt.
- Aber die Exif-Daten, wenn sie denn richtig gelesen werden (kann ich nicht nachvollziehen!) >>> werden nicht gezeigt!
- Die Schleife soll eigentlich über den Button "Bilder umbenennen abbrechen" abgebrochen werden >>> funktioniert auch nicht.
Klicke ich einen Dateinamen in der Explorerliste an, funktioniert die Anzeige der Exif-Daten einwandfrei. Gleiche Prozedur wird in der While-Wend-Schleife aufgerufen und funktioniert nicht!
IM Source-Code nach "MISTAKE" suchen dann ist man in der Prozedure die nicht so funktioniert wie sie soll!
Für Eure Hilfe bedanke ich mich schon mal im voraus.
Rainer
Hier mein Code, der auf einem Code aus dem englische Forum basiert.
Code: Alles auswählen
;/=====================================================================================================================
;| File : ExifDataTestApp.pb
;| Purpose : Show the DateTime Information stored in the Exif data field of Jpeg Files.
;| Read information is available only.
;|
;| Specification has so much more information to share...
;|
;| Version : 0.02
;|
;| State : Experimental, tested on only a few jpg images
;|
;| OS : Tested on Windows x64 with ASM Backend only
;|
;| License : MIT
;|
;| Copyright (c) 2022 by A.H. (Axolotl)
;|
;| ChangeLog :
;| 0.01 .. first attempt (published on forum)
;| Link:
;|
;| 0.02 .. added new TAG ImageDescription
;| adapted main window with improved Preview for long values
;|
;|
;\=====================================================================================================================
EnableExplicit
; DebugLevel 9 ; show all debug messages
Global Rename_Path.s, Only_filename.s, Rename_Abort.i = 0, file.s, index.i, Original_Path.s
; ---== MainWindow ==--------------------------------------------------------------------------------------------------
#ProgramName$ = "ExifDataTestApp"
#ProgramVersion$ = "0.02" ; internally used + #PB_Editor_BuildCount + "." + #PB_Editor_CompileCount
#MainCaption$ = "Image Exif Data and more... V" + #ProgramVersion$ + " ~ EXPERIMENTAL "
Enumeration EWindow 1 ; -----------------------------------------------------------------------------
#WINDOW_Main
EndEnumeration
Enumeration EGadget 1 ; -----------------------------------------------------------------------------
#GADGET_ExpImageFiles
#GADGET_LstImageInfo
#GADGET_CnvPreView
#GADGET_EdtPreView ; show selected items (especially very long values)
#Frame3D_0
#Frame3D_1
#Frame3D_2
#GADGET_Source_path
#GADGET_Source_path_show
#GADGET_Target_path
#GADGET_Target_path_show
#GADGET_Button_exit
#GADGET_Button_rename
#GADGET_Original_file
#GADGET_Original_file_show
#GADGET_Renamed_file
#GADGET_Renamed_file_show
#GADGET_Button_rename_abort
#GADGET_ExplorerTree
EndEnumeration
Enumeration EImage 1 ; -----------------------------------------------------------------------------
#IMAGE_PreView
EndEnumeration
; -----------------------------------------------------------------------------
; The example app shows the image, too.
; -----------------------------------------------------------------------------
UseJPEGImageDecoder()
UsePNGImageEncoder()
UseJPEG2000ImageDecoder()
; ---== Exif Imaage Data ==--------------------------------------------------------------------------------------------
DeclareModule ExifData ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
; ---------------------------------------------------------------------------
; Constants
;
Enumeration EExifTAG ; Number of the TAG (Exif, TIFF, etc.)
#ExifTAG_ImageWidth = $0100 ; recommended not to use, better use jpeg
#ExifTAG_ImageHeight = $0101 ; recommended not to use, better use jpeg
#ExifTAG_BitsPerSample = $0102 ;
#ExifTAG_Compression = $0103 ; thumbnail stuff ..
;
#ExifTAG_Orientation = $0112 ;
#ExifTAG_XResolution = $011A ;
#ExifTAG_YResolution = $011B ;
#ExifTAG_ResolutionUnit = $0128 ;
; ;
; new
#ExifTAG_ImageDescription = $010E ; char string giving the title of the image (no two-char codes)
; ;
#ExifTAG_Make = $010F ; "Make", #Exif_Type_ASCII, -1, 0)
#ExifTAG_Model = $0110 ; "Model", #Exif_Type_ASCII, -1, 0)
#ExifTAG_Software = $0131 ; "Software", #Exif_Type_ASCII, -1, 0)
#ExifTAG_DateTime = $0132 ; "DateTime", #Exif_Type_ASCII, 20, 0)
#ExifTAG_Artist = $013B ; name of the camera owner, photographer or image creator
#ExifTAG_Copyright = $8298 ; indicate both the photographer and editor copyrights
;
#ExifTAG_ExifVersion = $9000 ; 36864 (9000.H) | UNDEFINED | 4 | "0232" ; no NULL termination
#ExifTAG_ExifFlashpixVersion = $A000 ; 40960 (A000.H) | UNDEFINED | 4 | "0100" ; Flashpix Format Version 1.0
;
#ExifTAG_DateTimeOriginal = $9003 ; 36867 (9003.H) | ASCII | 20 | None
#ExifTAG_DateTimeDigitized = $9004 ; 36868 (9004.H) | ASCII | 20 | None
#ExifTAG_OffsetTime = $9010 ; 36880 (9010.H) | ASCII | 7 | None ; including NULL
#ExifTAG_OffsetTimeOriginal = $9011 ; 36881 (9011.H) | ASCII | 7 | None ; including NULL
#ExifTAG_OffsetTimeDigitized = $9012 ; 36882 (9012.H) | ASCII | 7 | None ; including NULL
;
#ExifTAG_SubsecTime = $9290 ; Fractions of seconds for DateTime
#ExifTAG_SubsecTimeOriginal = $9291 ; Fractions of seconds for DateTimeOriginal
#ExifTAG_SubsecTimeDigitized = $9292 ; Fractions of seconds for DateTimeDigitized
;
#ExifTAG_ColorSpace = $A001 ; Color space information tag
#ExifTAG_PixelXDimension = $A002 ; Valid Image Width | PixelXDimension | 40962 ~ A002.H ~ SHORT or LONG ~ 1
#ExifTAG_PixelYDimension = $A003 ; Valid Image Height | PixelYDimension | 40963 ~ A003.H ~ SHORT or LONG ~ 1
EndEnumeration ; EExifTAG
; ---------------------------------------------------------------------------
Declare.i ReadExifDataFromFile(FileName$) ;
Declare FreeExifData() ;
Declare ShowResultsOnGadget(Gadget)
Declare.i GetExifTagAsInteger(ExifTag, DefaultValue = -1)
Declare.s GetExifTagAsString(ExifTag, DefaultValue$ = "")
EndDeclareModule
Module ExifData ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
EnableExplicit
; ---------------------------------------------------------------------------
; Constants
;
Enumeration EExifTAG ; Number of the TAG (Exif, TIFF, etc.)
#ExifTAG_Unsupported = $0000 ; internal define ..
#ExifTAG_ExifIFD = $8769 ; | LONG | 1 |
EndEnumeration ; EExifTAG
;
; HINT: This constant is representing the number of defined äExifTAG_Xxxx in Enumeration EExifTAG
; If you add new constants to the enumeration (local or global) you must increase the #ExifTagTableSize as well
;
#ExifTagTableSize = 29 :Debug "HINT: #ExifTagTableSize: " + #ExifTagTableSize
; ---------------------------------------------------------------------------
Enumeration EJpegMarkers
#JM_Start = $FF
; ...
#JM_SOI = $D8 ;
; ...
#JM_APP1 = $E1 ; EXIF and XMP (XMP not supported yet)
; ...
#JM_APP13 = $ED ; IPTC (not supported yet)
#JM_APP14 = $EE ; (not supported yet)
#JM_APP15 = $EF ; (not supported yet)
; ...
#JM_JPG0 = $F0 ; JPG0 == 0xF0 to JPG13 == 0xFD
#JM_COM = $FE
EndEnumeration ; EJpegMarkers
Enumeration EExifByteOrderMark
#ExifByteOderMark_Intel = $4949 ; Little-endian | 0x4D - 0x49 == 0x04 | II
#ExifByteOderMark_Motorola = $4D4D ; Big-endian | | MM
EndEnumeration ; EExifByteOrder
Enumeration EExifByteOrder
#ExifByteOrder_Motorola
#ExifByteOrder_Intel
EndEnumeration ; EExifByteOrder
; ---------------------------------------------------------------------------
#TIFF_TAG_Mark = $002A ; constant in correct byte order
#TIFF_FirstIFDOffset = $00000008 ; default offset to the first IDD
; ---------------------------------------------------------------------------
; Structure User Defined Types
;
Structure TByteArray ; Access to the Image Memory byte by byte
Byte.a[0]
EndStructure
; ---------------------------------------------------------------------------
Structure TExifTagEntry ; TAG structure for ExifTags, etc.
Number.i ;
Name$ ;
;Descr$ ; .. Description, <sorry, to much typing or formatting)
Private.i ; .. #False or #True (usage makes sense only inside module)
EndStructure
; ---------------------------------------------------------------------------
Structure TExifTagValue ; TAG structure for ExifTags, etc.
Number.i ;
Name$ ;
Caption$ ; .. display text (different language, maybe in future)
Format.i ; .. data format (type) of TAG
Private.i ; .. IsPrivate = #False or #True ??
;
AddressOffset.i ; .. address offset of the tag in the memory
AddressSize.i ; .. address size/length of the tag in the memory
;
Value.i ; .. Value, different types are supported ??
Value$ ; \ __ quick solution, needs some improvement
Array Vals.i(0) ; /
EndStructure
; ---------------------------------------------------------------------------
; Module Global Variables
;
Global *ExifData.TByteArray ; entire file is stored in this memory
Global NewList ResultValues.TExifTagValue() ; found tags need a place to wait
Global Dim ExifTagTable.TExifTagEntry(0) ; the table of TAGs we can use
; ---== Simple Helpers ==----------------------------------------------------------------------------------------------
Macro DQ
"
EndMacro
Macro ByteToHex(_Value_)
"0x" + RSet(Hex(_Value_, #PB_Byte), 2, "0") + ", (" + Str(_Value_) + ")"
EndMacro
Macro WordToHex(_Value_)
"0x" + RSet(Hex(_Value_, #PB_Word), 4, "0") + ", (" + Str(_Value_) + ")"
EndMacro
Macro LongToHex(_Value_)
"0x" + RSet(Hex(_Value_, #PB_Long), 8, "0") + ", (" + Str(_Value_) + ")"
EndMacro
Macro IntToHex(_Value_)
"0x" + RSet(Hex(_Value_, #PB_Quad), 16, "0") + ", (" + Str(_Value_) + ")"
EndMacro
; ---== Fetch BYTE, WORD, LONG, ASCII from *Memory ==------------------------------------------------------------------
; Little Endian (LE) == 8, 0; Big Endian (BE) == 0, 8
Global Dim ByteOrderWord(1, 1) ; (ByteOrderLE, Offset)
ByteOrderWord(0, 0) = 8 : ByteOrderWord(0, 1) = 0 ; <-- BE
ByteOrderWord(1, 0) = 0 : ByteOrderWord(1, 1) = 8 ; <-- LE
; Little Endian (LE) == 24, 16, 8, 0; Big Endian (BE) == 0, 8, 16, 24
Global Dim ByteOrderLong(1, 3) ; (ByteOrderLE, Offset)
ByteOrderLong(0, 0) = 24 : ByteOrderLong(0, 1) = 16 : ByteOrderLong(0, 2) = 8 : ByteOrderLong(0, 3) = 0 ; <-- BE
ByteOrderLong(1, 0) = 0 : ByteOrderLong(1, 1) = 8 : ByteOrderLong(1, 2) = 16 : ByteOrderLong(1, 3) = 24 ; <-- LE
; ---------------------------------------------------------------------------
Procedure.i FetchByte(Offset) ; return $00
ProcedureReturn *ExifData\Byte[Offset]
EndProcedure
; ---------------------------------------------------------------------------
Procedure.i FetchWord(Offset, ByteOrderLE=0) ; returns $00 00
ProcedureReturn *ExifData\Byte[Offset + 0] << ByteOrderWord(ByteOrderLE, 0) +
*ExifData\Byte[Offset + 1] << ByteOrderWord(ByteOrderLE, 1)
EndProcedure
; ---------------------------------------------------------------------------
Procedure.i FetchLong(Offset, ByteOrderLE=0) ; returns $00 00 00 00
ProcedureReturn *ExifData\Byte[Offset + 0] << ByteOrderLong(ByteOrderLE, 0) +
*ExifData\Byte[Offset + 1] << ByteOrderLong(ByteOrderLE, 1) +
*ExifData\Byte[Offset + 2] << ByteOrderLong(ByteOrderLE, 2) +
*ExifData\Byte[Offset + 3] << ByteOrderLong(ByteOrderLE, 3)
EndProcedure
; ---------------------------------------------------------------------------
Procedure.s FetchAscii(Offset, Length) ; returns value$[Length]
ProcedureReturn PeekS(*ExifData + Offset, Length, #PB_Ascii)
EndProcedure
; ---------------------------------------------------------------------------
Procedure.s GetByteOrderName(ByteOrder)
Select ByteOrder
Case #ExifByteOrder_Motorola : ProcedureReturn "Motorola"
Case #ExifByteOrder_Intel : ProcedureReturn "Intel"
EndSelect
ProcedureReturn ""
EndProcedure
; ---------------------------------------------------------------------------
Procedure LogOut(Message$) ; @ Todo: further improvements
Debug "LOG -> " + Message$
EndProcedure
; ---------------------------------------------------------------------------
; temp marco for use in the next procedure only
Macro _setTagEntry(_ConstantName_, _Private_) ; _Private_ == #False or #True
ExifTagTable(Index)\Number = _ConstantName_
ExifTagTable(Index)\Name$ = Mid(DQ#_ConstantName_#DQ, 11) ; cut off #ExifTAG_ constant prefix
ExifTagTable(Index)\Private = _Private_
Index + 1
EndMacro
;
Procedure InitializeExifTagTable() ; fill the table with supported TAGs
Protected Index = 0
Dim ExifTagTable(#ExifTagTableSize) ; constant avoid redim at the end
; internal used constants
_setTagEntry(#ExifTAG_Unsupported , #False) ; internal stuff ??
_setTagEntry(#ExifTAG_ImageWidth , #False)
_setTagEntry(#ExifTAG_ImageHeight , #False)
_setTagEntry(#ExifTAG_BitsPerSample , #False)
_setTagEntry(#ExifTAG_Compression , #False)
_setTagEntry(#ExifTAG_Orientation , #False)
_setTagEntry(#ExifTAG_XResolution , #False)
_setTagEntry(#ExifTAG_YResolution , #False)
_setTagEntry(#ExifTAG_ResolutionUnit , #False)
_setTagEntry(#ExifTAG_ImageDescription , #False) ; new
_setTagEntry(#ExifTAG_Make , #False)
_setTagEntry(#ExifTAG_Model , #False)
_setTagEntry(#ExifTAG_Software , #False)
_setTagEntry(#ExifTAG_DateTime , #False)
_setTagEntry(#ExifTAG_Artist , #False)
_setTagEntry(#ExifTAG_Copyright , #False)
_setTagEntry(#ExifTAG_ExifIFD , #True) ; offset to IFD (Image File Directory)
_setTagEntry(#ExifTAG_ExifVersion , #False)
_setTagEntry(#ExifTAG_ExifFlashpixVersion, #False)
_setTagEntry(#ExifTAG_DateTimeOriginal , #False)
_setTagEntry(#ExifTAG_DateTimeDigitized , #False)
_setTagEntry(#ExifTAG_OffsetTime , #False)
_setTagEntry(#ExifTAG_OffsetTimeOriginal , #False)
_setTagEntry(#ExifTAG_OffsetTimeDigitized, #False)
_setTagEntry(#ExifTAG_SubsecTime , #False)
_setTagEntry(#ExifTAG_SubsecTimeOriginal , #False)
_setTagEntry(#ExifTAG_SubsecTimeDigitized, #False)
_setTagEntry(#ExifTAG_ColorSpace , #False)
_setTagEntry(#ExifTAG_PixelXDimension , #False)
_setTagEntry(#ExifTAG_PixelYDimension , #False)
If Index - 1 <> #ExifTagTableSize
Debug "INTERNAL INFO: ARRAY SIZE is redimmed to " + Str(Index-1) + " constant = " + #ExifTagTableSize
; optimize the memory usage
ReDim ExifTagTable(Index-1) ; the number of Tags we can use
EndIf
; for binary search the numbers must be sorted
SortStructuredArray(ExifTagTable(), 0, OffsetOf(TExifTagEntry\Number), TypeOf(TExifTagEntry\Number))
; Debug #LF$+"Show ExifTagTable "
; For Index = 0 To #ExifTagTableSize
; Debug " " + index + ". " + ExifTagTable(Index)\Name$ + ", 0x" + Hex(ExifTagTable(Index)\Number) + ", " + ExifTagTable(Index)\Private
; Next Index
; Debug ""
EndProcedure
;
UndefineMacro _setTagEntry
;
InitializeExifTagTable() ; call it directly to fill the arrays
; ---------------------------------------------------------------------------
Procedure.i GetExifTagIndex(Number) ; returns index or -1
; >> Iterative Binary Search -- faster than Linear or Sequential search on sorted arrays
Protected retIdx, firstIdx, lastIdx, midIdx ;:Debug #LF$+#PB_Compiler_Procedure+"(0x"+Hex(Number)+")", 9
retIdx = -1 ; default return value == -1
firstIdx = 0 ; start search with entire array
lastIdx = ArraySize(ExifTagTable()) ; -"-
While lastIdx - firstIdx > 1
midIdx = (lastIdx + firstIdx) / 2 ;:Debug " iterate: " + firstIdx + ", " + lastIdx + ", " + midIdx, 9
If ExifTagTable(midIdx)\Number < Number
firstIdx = midIdx + 1
Else
lastIdx = midIdx
EndIf
Wend
If ExifTagTable(firstIdx)\Number = Number
retIdx = firstIdx
ElseIf ExifTagTable(lastIdx)\Number = Number
retIdx = lastIdx
EndIf ;:Debug "Found 0x" + Hex(Number) + " at Index " + retIdx, 9
ProcedureReturn retIdx ; return -1 (not found) or index (0..ArraySize())
EndProcedure
; ---------------------------------------------------------------------------
Procedure.i ParseTagValue(entryOffset, tiffStart, byteOrderLE)
Protected fmt, numValues, valueOffset, offset, n, numerator, denominator :Debug #LF$+#PB_Compiler_Procedure+"()", 9
Protected ret_val, tmp$, v.f
fmt = FetchWord(entryOffset + 2, byteOrderLE) ;.. acc. to spec.
numValues = FetchLong(entryOffset + 4, byteOrderLE) ;..
valueOffset = FetchLong(entryOffset + 8, byteOrderLE) + tiffStart ;..
ResultValues()\Format = fmt ;
Select fmt
Case 1, 7 ;// 1 -> byte, 8-bit unsigned int .. 7 -> undefined, 8-bit byte, value depending on field
If numValues = 1
ResultValues()\Value = FetchByte(entryOffset + 8)
Else
If numValues > 4 : offset = valueOffset : Else : offset = entryOffset + 8 : EndIf
ResultValues()\Value$ = FetchAscii(offset, numValues) ; numValues == 4
EndIf
; keep the position (address) of the value to overwrite with new data ????
ResultValues()\AddressOffset = offset
ResultValues()\AddressSize = numValues
ret_val = #True
Case 2 ;// 2 -> ascii, 8-bit byte
If numValues > 4 : offset = valueOffset : Else : offset = entryOffset + 8 : EndIf
ResultValues()\Value$ = FetchAscii(offset, numValues - 1)
; keep the position (address) of the value to overwrite with new data ????
ResultValues()\AddressOffset = offset
ResultValues()\AddressSize = numValues - 1 ; different (because no trailing ZERO)
ret_val = #True
Case 3 ;// 3 -> short, 16 bit int
If numValues = 1
ResultValues()\Value = FetchWord(entryOffset + 8, byteOrderLE)
Else
If numValues > 2 : offset = valueOffset : Else : offset = entryOffset + 8 : EndIf
ReDim ResultValues()\Vals(numValues)
For n = 0 To numValues - 1
ResultValues()\Vals(n) = FetchWord(offset + 2 * n, byteOrderLE)
Next n
EndIf
; keep the position (address) of the value to overwrite with new data ????
ResultValues()\AddressOffset = offset
ResultValues()\AddressSize = numValues - 1
ret_val = #True
Case 4 ;// 4 -> long, 32 bit int
If numValues = 1
ResultValues()\Value = FetchLong(entryOffset + 8, byteOrderLE)
Else
ReDim ResultValues()\Vals(numValues)
For n = 0 To numValues - 1
ResultValues()\Vals(n) = FetchLong(offset + 4 * n, byteOrderLE)
Next n
EndIf
; keep the position (address) of the value to overwrite with new data ????
ResultValues()\AddressOffset = offset
ResultValues()\AddressSize = numValues - 1
ret_val = #True
Case 5 ;// 5 -> rational = two long values, first is numerator, second is denominator
If numValues = 1
numerator = FetchLong(valueOffset, byteOrderLE)
denominator = FetchLong(valueOffset + 4, byteOrderLE)
v = numerator / denominator
ResultValues()\Value$ = StrF(v)
Else
For n = 0 To numValues - 1
numerator = FetchLong(valueOffset + 8*n, byteOrderLE)
denominator = FetchLong(valueOffset + 4 + 8*n, byteOrderLE)
v = numerator / denominator
ResultValues()\Value$ + StrF(v) + ";" ; ??
Next n
EndIf
;ResultValues()\Private = #True ; don't share, not verified by now
ret_val = #False ; not supported yet.
Case 9 ;// 9 ; slong, 32 bit signed int
If numValues = 1
ResultValues()\Value = FetchLong(entryOffset + 8, byteOrderLE) & $FFFF ; ??
Else
ReDim ResultValues()\Vals(numValues)
For n = 0 To numValues - 1
ResultValues()\Vals(n) = FetchLong(offset + 4 * n, byteOrderLE) & $FFFF ; ??
Next n
EndIf
; keep the position (address) of the value to overwrite with new data ????
ResultValues()\AddressOffset = offset
ResultValues()\AddressSize = numValues - 1
ret_val = #True
Case 10 ;// 10 -> signed rational, two slongs, first is numerator, second is denominator
If numValues = 1
ResultValues()\Value = FetchLong(valueOffset, byteOrderLE) / FetchLong(valueOffset+4, byteOrderLE) ;??
Else
ReDim ResultValues()\Vals(numValues)
For n = 0 To numValues - 1
ResultValues()\Vals(n) = FetchLong(valueOffset + 8*n, byteOrderLE) / FetchLong(valueOffset+4 + 8*n, byteOrderLE) ;??
Next n
EndIf
;ResultValues()\Private = #True ; don't share, not verified by now
ret_val = #False ; not supported yet.
EndSelect
ProcedureReturn ret_val
EndProcedure
; ---------------------------------------------------------------------------------------------------------------------
Procedure.i ParseTags(tiffStart, dirStart, byteOrderLE) ;
Protected entries, entryOffset, tag, ii, idx, name$ :Debug #LF$+#PB_Compiler_Procedure+"()", 9
Protected ret_val
entries = FetchWord(dirStart, byteOrderLE) :Debug " entries = " + entries, 9
For ii = 0 To entries - 1
entryOffset = dirStart + ii * 12 + 2 ;.. calulation acc. to specification -- jump over unsupported tags
tag = FetchWord(entryOffset, byteOrderLE)
idx = GetExifTagIndex(tag) ; look for TAG in the ExifTagTable()
If idx = -1
; ##_TAG_##
; ; Log all available but not supported Tags
; LogOut("TAG: " + WordToHex(tag) + " not supported!")
Continue ; with the next tag
EndIf
; work on supported TAGs
AddElement(ResultValues())
ResultValues()\Number = tag ; copy TAG Number ...
ResultValues()\Name$ = ExifTagTable(idx)\Name$ ; ... and name$
ResultValues()\Private = ExifTagTable(idx)\Private ; ... and private flag
;;LogOut("TAG: " + WordToHex(tag) + " " + ExifTagTable(idx)\Name$ + " supported!")
ret_val | ParseTagValue(entryOffset, tiffStart, byteOrderLE) ; one valid tag is enough :)
Next ii
ProcedureReturn ret_val
EndProcedure
; -----------------------------------------------------------------------------
Procedure.i ParseEXIFData(Start)
Protected byteOrderLE, tags, tag, exifData, gpsData, tiffOffset :Debug #LF$+#PB_Compiler_Procedure+"()", 9
Protected firstIFD_offset, ExifIFD_offset
Protected ret_val, rc
If FetchAscii(Start, 4) <> "Exif"
LogOut("Not valid EXIF data! " + FetchAscii(Start, 4))
ProcedureReturn #False
EndIf
tiffOffset = Start + 6 ; kept for further investigation
; test for TIFF validity and byte order
If FetchWord(tiffOffset) = #ExifByteOderMark_Intel ; 0x4949 == Intel Byte Order
byteOrderLE = #True ; .. \-> little endian
ElseIf FetchWord(tiffOffset) = #ExifByteOderMark_Motorola ; 0x4D4D == Motorola Byte Order
byteOrderLE = #False ; .. \-> big endian
Else
LogOut("Not valid TIFF data! (no 0x4949 or 0x4D4D)")
ProcedureReturn #False ;.. failure because of unknown byteorder!
EndIf
If FetchWord(tiffOffset + 2, byteOrderLE) <> #TIFF_TAG_Mark ; 0x002A == TIFF_TAG_Mark
LogOut("Not valid TIFF data! (no 0x002A)")
ProcedureReturn #False
EndIf
firstIFD_offset = FetchLong(tiffOffset + 4, byteOrderLE)
If firstIFD_offset < #TIFF_FirstIFDOffset ; == 0x00000008 (Default offset)
LogOut("Not valid TIFF data! (First offset less than 8) " + FetchLong(tiffOffset + 4, byteOrderLE))
ProcedureReturn #False ; failure
EndIf
ret_val = ParseTags(tiffOffset, tiffOffset + firstIFD_offset, byteOrderLE) ;; ### TiffTags --> ARRAY or MAP ????
If ret_val
If ResultValues()\Number = #ExifTAG_ExifIFD ; <--> 0x8769 == ExifIFD pointer
ExifIFD_offset = ResultValues()\Value
; Debug "HINT: ExifIFD-Offset = " + ExifIFD_offset + " // parse tags "
rc = ParseTags(tiffOffset, tiffOffset + ExifIFD_offset, byteOrderLE) ;
; .. needs some further investigation ..
EndIf
EndIf
ProcedureReturn ret_val
EndProcedure
; ---== Read, Write, Free ImageFile and *Memory ==---------------------------------------------------------------------
Procedure.i ReadExifDataFromFile(FileName$) ;
Protected FILE, memsize, bytes :Debug #LF$+#PB_Compiler_Procedure+"("+FileName$+")", 9
Protected offset, marker
FreeExifData()
ClearList(ResultValues())
FILE = ReadFile(#PB_Any, FileName$) ; read with no flags
If FILE
memsize = Lof(FILE) ; Lof .. Length of (opened) file
*ExifData = AllocateMemory(memsize) ; returns the address, or zero if the memory cannot be allocated
If *ExifData
bytes = ReadData(FILE, *ExifData, memsize) ; read all data into memory block
LogOut("Read file with length of " + Str(bytes) + " bytes. ")
EndIf
CloseFile(FILE)
Else
*ExifData = 0 ;
LogOut("ERROR: Couldn't open the file '" + FileName$ + "'")
ProcedureReturn #False ; not a valid image (jpeg) file
EndIf
; analyze file from the beginning
If FetchByte(0) <> #JM_Start Or FetchByte(1) <> #JM_SOI ;.. == FFD8
LogOut("Not a valid JPEG")
ProcedureReturn #False ; not a valid jpeg
EndIf
offset = 2 ; jump over the first two bytes :)
While offset < memsize ; scan the file memory byte by byte for find the marker 0xFFE1
If FetchByte(offset) <> #JM_Start
LogOut("Not a valid marker at offset " + offset + ", found: " + FetchByte(offset))
ProcedureReturn #False ; not a valid marker, something is wrong
EndIf
marker = FetchByte(offset + 1)
; we could implement handling for other markers here, but we're only looking for 0xFFE1 for EXIF data
If marker = #JM_APP1 ; == 225 = $E1
LogOut("Found 0xFFE1 marker")
ProcedureReturn ParseEXIFData(offset + 4)
Else
offset + 2 + FetchWord(offset + 2)
EndIf
Wend
ProcedureReturn #False ; return failure, could not found a valid marker
EndProcedure
; ---------------------------------------------------------------------------
Procedure FreeExifData() ;
If *ExifData <> 0 :Debug #LF$+#PB_Compiler_Procedure+"() // in use, clear it first.", 9
FreeMemory(*ExifData) ; in use, free memory first
*ExifData = 0
EndIf
EndProcedure
; ---------------------------------------------------------------------------
Procedure ShowResultsOnGadget(Gadget) ; Gadget is a #ListIcon and has two (2) columns
Protected txt$, ii
If IsGadget(Gadget) And GadgetType(Gadget) = #PB_GadgetType_ListIcon And GetGadgetAttribute(Gadget, #PB_ListIcon_ColumnCount) = 2
; Debug #LF$+"ResultValues(): "
ForEach ResultValues()
; Debug " " + ResultValues()\Name$ + " " + ResultValues()\Value + " | '" + ResultValues()\Value$ + "' | ... "
If ResultValues()\Private = #True ; <--> for internal use only :)
Continue
EndIf
; Debug " " + ResultValues()\Name$ + " " + ResultValues()\Value + " | '" + ResultValues()\Value$ + "' | ... "
If ResultValues()\Value$ <> ""
txt$ = ResultValues()\Value$
ElseIf ResultValues()\Value <> 0
txt$ = Str(ResultValues()\Value)
Else
For ii = 0 To ArraySize(ResultValues()\Vals()) - 1
txt$ + Str(ResultValues()\Vals(ii)) + ";"
Next ii
EndIf
AddGadgetItem(Gadget, -1, ResultValues()\Name$ + #LF$ + txt$)
Next
Else ; not the correct gadget
Debug "INTERNAL: Gadget " + Gadget + " is not the correct gadget type. "
EndIf
EndProcedure
; ---------------------------------------------------------------------------
Procedure.i GetExifTagAsInteger(ExifTag, DefaultValue = -1)
ForEach ResultValues()
If ResultValues()\Number = ExifTag
ProcedureReturn ResultValues()\Value
EndIf
Next
ProcedureReturn DefaultValue
EndProcedure
; ---------------------------------------------------------------------------
Procedure.s GetExifTagAsString(ExifTag, DefaultValue$ = "")
ForEach ResultValues()
If ResultValues()\Number = ExifTag
ProcedureReturn ResultValues()\Value$
EndIf
Next
ProcedureReturn DefaultValue$
EndProcedure
; ---------------------------------------------------------------------------
; Procedure GetExifTagValue(ExifTag)
; EndProcedure
; ---------------------------------------------------------------------------
EndModule ; <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
; ---== MainWindow ==--------------------------------------------------------------------------------------------------
Procedure ShowImagePreview(FileName$) ; update all UI gadgets
Protected IMAGE
Protected ix, iy, iw, ih, gw, gh, txt$
gw = GadgetWidth(#GADGET_CnvPreView) ; <-- get the size of the image gadget
gh = GadgetHeight(#GADGET_CnvPreView)
If FileSize(FileName$) > 0 ; FileName$ = "" returns -1 as well
IMAGE = LoadImage(#PB_Any, FileName$) ; <-- load image
EndIf
If IMAGE
iw = ImageWidth(IMAGE)
ih = ImageHeight(IMAGE)
txt$ = "Image Size = " + Str(iw) + " x " + Str(ih)
; calc factor to reduce to the available gadget size
ix = 1 : While iw/ix > gw : ix + 1 : Wend
iy = 1 : While ih/iy > gh : iy + 1 : Wend
If ix < iy : ix = iy : EndIf ; the bigger the better :)
iw / ix : ih / ix ; shrink the size
; center in hori and verti orientation
ix = (gw - iw) / 2 : If ix < 0 : ix = 0 : EndIf
iy = (gh - ih) / 2 : If iy < 0 : iy = 0 : EndIf
Else ; <-- default ??
txt$ = "Image: Nothing selected or found!"
EndIf
If StartDrawing(CanvasOutput(#GADGET_CnvPreView))
Box(0, 0, gw, gh, #White)
If IMAGE ; <-- valid image
DrawImage(ImageID(IMAGE), ix, iy, iw, ih)
EndIf
DrawingMode(#PB_2DDrawing_Transparent)
DrawText(4, 4, txt$, #Blue)
StopDrawing()
EndIf
If IMAGE
FreeImage(IMAGE)
EndIf
EndProcedure
;-----------------------------------------------------------------------------
Procedure UpdateImage(FileName$)
If FileName$ And ExifData::ReadExifDataFromFile(FileName$)
ExifData::ShowResultsOnGadget(#GADGET_LstImageInfo)
ExifData::FreeExifData()
Else ; do some info
AddGadgetItem(#GADGET_LstImageInfo, -1, "No Exif-Info!")
EndIf
ShowImagePreview(FileName$)
EndProcedure
; -----------------------------------------------------------------------------
Procedure ResizeGadgetsWindow_0()
Protected FormWindowWidth, FormWindowHeight
FormWindowWidth = WindowWidth(#WINDOW_Main)
FormWindowHeight = WindowHeight(#WINDOW_Main)
ResizeGadget(#GADGET_ExpImageFiles, 20, 20, 240, FormWindowHeight - 180)
ResizeGadget(#GADGET_LstImageInfo, 270, 20, 320, FormWindowHeight - 180)
ResizeGadget(#GADGET_CnvPreView, 600, 20, FormWindowWidth - 620, FormWindowHeight - 250)
ResizeGadget(#GADGET_EdtPreView, 600, FormWindowHeight - 220, FormWindowWidth - 620, 60)
ResizeGadget(#GADGET_Source_path, 20, FormWindowHeight - 130, 70, 25)
ResizeGadget(#GADGET_Source_path_show, 100, FormWindowHeight - 130, FormWindowWidth - 570, 25)
ResizeGadget(#GADGET_Target_path, 20, FormWindowHeight - 100, 70, 25)
ResizeGadget(#GADGET_Target_path_show, 100, FormWindowHeight - 100, FormWindowWidth - 570, 25)
ResizeGadget(#Frame3D_0, 10, 0, FormWindowWidth - 20, FormWindowHeight - 150)
ResizeGadget(#Frame3D_1, 10, FormWindowHeight - 150, FormWindowWidth - 20, 80)
ResizeGadget(#Frame3D_2, 10, FormWindowHeight - 70, FormWindowWidth - 20, 60)
ResizeGadget(#GADGET_Original_file, FormWindowWidth - 460, FormWindowHeight - 130, 120, 25)
ResizeGadget(#GADGET_Original_file_show, FormWindowWidth - 330, FormWindowHeight - 130, 310, 25)
ResizeGadget(#GADGET_Renamed_file, FormWindowWidth - 460, FormWindowHeight - 100, 120, 25)
ResizeGadget(#GADGET_Renamed_file_show, FormWindowWidth - 330, FormWindowHeight - 100, 310, 25)
ResizeGadget(#GADGET_Button_exit, 20, FormWindowHeight - 50, 180, 25)
ResizeGadget(#GADGET_Button_rename, FormWindowWidth - 200, FormWindowHeight - 50, 180, 25)
ResizeGadget(#GADGET_Button_rename_abort, FormWindowWidth - 400, FormWindowHeight - 50, 180, 25)
EndProcedure
; -----------------------------------------------------------------------------
Procedure OpenMainWindow(WndW, WndH) ;
If OpenWindow(#WINDOW_Main, 0, 0, 1120, 750, "", #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget | #PB_Window_SizeGadget | #PB_Window_ScreenCentered)
StickyWindow(#WINDOW_Main, 1) ; always on top, my sreen is a mess :)
FrameGadget(#Frame3D_0, 10, 0, 1100, 600, "")
FrameGadget(#Frame3D_1, 10, 600, 1100, 80, "")
FrameGadget(#Frame3D_2, 10, 680, 1100, 60, "")
ExplorerListGadget(#GADGET_ExpImageFiles, 20, 20, 240, 570, "C:\Users\Rainer\Pictures\*.jpg", #PB_Explorer_AlwaysShowSelection | #PB_Explorer_FullRowSelect)
RemoveGadgetColumn(#GADGET_ExpImageFiles, 1) ; we don't need the other columns
RemoveGadgetColumn(#GADGET_ExpImageFiles, 1) ; -"-
RemoveGadgetColumn(#GADGET_ExpImageFiles, 1) ; -"-
SetGadgetItemAttribute(#GADGET_ExpImageFiles, 0, #PB_Explorer_ColumnWidth, 216, 0)
SetGadgetItemState(#GADGET_ExpImageFiles, 0, #PB_Explorer_Selected | #PB_Explorer_AlwaysShowSelection | #PB_Explorer_FullRowSelect )
ListIconGadget(#GADGET_LstImageInfo, 270, 20, 320, 570, "Name", 120, #PB_ListIcon_FullRowSelect | #PB_ListIcon_AlwaysShowSelection | #PB_ListIcon_GridLines)
AddGadgetColumn(#GADGET_LstImageInfo, 1, "Value", 200-24)
CanvasGadget(#GADGET_CnvPreView, 600, 20, 500, 500, #PB_Canvas_Border)
EditorGadget(#GADGET_EdtPreView , 600, 530, 500, 60, #PB_Editor_WordWrap|#PB_Editor_ReadOnly)
SetGadgetText(#GADGET_EdtPreView, "No Selected Value!")
TextGadget(#GADGET_Source_path, 20, 620, 70, 25, "Quell-Pfad:", #PB_Text_Right)
TextGadget(#GADGET_Source_path_show, 100, 620, 550, 25, "", #PB_Text_Border)
TextGadget(#GADGET_Target_path, 20, 650, 70, 25, "Ziel-Pfad:", #PB_Text_Right)
TextGadget(#GADGET_Target_path_show, 100, 650, 550, 25, "", #PB_Text_Border)
TextGadget(#GADGET_Original_file, 660, 620, 120, 25, "Original-Dateiname:", #PB_Text_Right)
TextGadget(#GADGET_Original_file_show, 790, 620, 310, 25, "", #PB_Text_Border)
TextGadget(#GADGET_Renamed_file, 660, 650, 120, 25, "Renamed-Dateiname:", #PB_Text_Right)
TextGadget(#GADGET_Renamed_file_show, 790, 650, 310, 25, "", #PB_Text_Border)
ButtonGadget(#GADGET_Button_exit, 20, 700, 180, 25, "Programm beenden")
ButtonGadget(#GADGET_Button_rename, 920, 700, 180, 25, "Bilder umbennen starten")
ButtonGadget(#GADGET_Button_rename_abort, 720, 700, 180, 25, "Bilder umbennen abbrechen")
DisableGadget(#GADGET_Button_rename_abort, #True)
ProcedureReturn 1 ; success
EndIf
ProcedureReturn 0 ; failure
EndProcedure
; ---------------------------------------------------------------------------
; MISTAKE !!!!
; In this Procedure I want to work down the exlorerlist-entries 1 by 1 from top to bottom.
; If it is a jpg-file I want to show the picture and want to show the exif-Data in the listbox.
; In the next step I want to read the DateTimeDigitized or the DateTimeOriginal-date to
; copy the picture into the created folder "Date-Renamed" under a new name like this: "Digitized_date-Filename.ext"
; But it does not work the wanted!
; The list will be worked down 1 by 1 >> OK
; The picture will be shown in the Canvas >> OK
; The selected filename should be highlighted >> will not be done!!
; The Exif-date should be shown in the listbox >> will not be done!!
; The Rename-abort-button will not be recognized >> no abort action in the While-wend loop happens, the loop will not be ended!
Procedure Rename_Pictures()
Protected File_Loop.i = 0, Rename_FileName.s, Exif_Date.s, File_Extension.s, Items_in_Folder.i = 0, List_File.s
SetGadgetItemState(#GADGET_ExpImageFiles, 0, #PB_Explorer_Selected )
Items_in_Folder = CountGadgetItems(#GADGET_ExpImageFiles)
ClearGadgetItems(#GADGET_LstImageInfo)
While Items_in_Folder > File_Loop
Select WaitWindowEvent()
Case #PB_Event_Gadget
Select EventGadget()
Case #GADGET_Button_rename_abort
Select EventType()
Case #PB_EventType_LeftClick
Break
EndSelect
EndSelect
EndSelect
SetGadgetItemState(#GADGET_ExpImageFiles, File_Loop, #PB_Explorer_Selected )
List_File = GetGadgetText(#GADGET_ExpImageFiles) + GetGadgetItemText(#GADGET_ExpImageFiles, File_Loop, 0)
If Not FileSize(List_File) = -2 ;Nur Dateinamen werden bearbeitet, keine Verzeichnisse
Only_Filename = GetFilePart(List_File)
File_Extension = UCase(GetExtensionPart(Only_filename))
If (File_Extension = "JPG") Or (File_Extension = "PNG") Or (File_Extension = "BMP")
ClearGadgetItems(#GADGET_LstImageInfo)
UpdateImage(List_File)
Exif_Date = "Exif_Date-" ;Get Exif_Date
Rename_FileName = Exif_Date + Only_filename
SetGadgetText(#GADGET_Original_file_show, Only_Filename)
SetGadgetText(#GADGET_Renamed_file_show, Rename_FileName)
Delay(1000)
EndIf
EndIf
File_Loop + 1
Wend
DisableGadget(#GADGET_Button_rename_abort, #True)
DisableGadget(#GADGET_Button_exit, #False)
DisableGadget(#GADGET_Button_rename, #False)
EndProcedure
; ---== main program ==------------------------------------------------------------------------------------------------
Procedure main()
Protected WndW = 1120, WndH = 750
InitKeyboard()
ExamineDesktops()
If DesktopWidth(0) < WndW Or DesktopHeight(0) < WndH
MessageRequester("Display Information", "Your current resolution is to small for this application!")
WndW = 800 : WndH = 600
EndIf
If OpenMainWindow(WndW, WndH)
If FileSize("C:\Temp\camera") = -2 ; existing directory ... some test pictures exists on my computer
SetGadgetText(#GADGET_ExpImageFiles, "C:\Temp\camera\*.jpg")
EndIf
Repeat ; <--- main loop ---
Select WaitWindowEvent()
Case #PB_Event_CloseWindow
Break ; say good bye
Case #PB_Event_SizeWindow
ResizeGadgetsWindow_0()
Case #PB_Event_Gadget
Select EventGadget()
Case #GADGET_ExpImageFiles ; Explorer with Images
Select EventType()
Case #PB_EventType_Change
ClearGadgetItems(#GADGET_LstImageInfo)
index = GetGadgetState(#GADGET_ExpImageFiles)
SetGadgetText(#GADGET_Source_path_show, GetGadgetText(#GADGET_ExpImageFiles))
If index > -1
file = GetGadgetText(#GADGET_ExpImageFiles) + GetGadgetItemText(#GADGET_ExpImageFiles, index, 0)
If Not FileSize(file) = -2 ;Nur Dateinamen werden gezeigt, keine Verzeichnisse
Only_Filename = GetFilePart(file)
SetGadgetText(#GADGET_Original_file_show, Only_Filename)
Else
SetGadgetText(#GADGET_Original_file_show, "")
EndIf
Else ; update image section anyway
file = ""
EndIf
UpdateImage(file)
SetGadgetText(#GADGET_EdtPreView, "No Selected Value!")
EndSelect
Case #GADGET_LstImageInfo ; Info of selected Image
Select EventType()
Case #PB_EventType_Change
index = GetGadgetState(#GADGET_LstImageInfo)
If index > -1
SetGadgetText(#GADGET_EdtPreView, GetGadgetItemText(#GADGET_LstImageInfo, index, 1))
Else
SetGadgetText(#GADGET_EdtPreView, "No Selected Value!")
EndIf
EndSelect
Case #GADGET_Button_rename
Select EventType()
Case #PB_EventType_LeftClick
DisableGadget(#GADGET_Button_rename_abort, #False)
DisableGadget(#GADGET_Button_exit, #True)
DisableGadget(#GADGET_Button_rename, #True)
Rename_Path = GetGadgetText(#GADGET_ExpImageFiles) + "\Date_Renamed" ; Rename_Path_Namen erstellen
If FileSize(Rename_Path) = -1 ;Prüfen ob der RenamePfad schon existiert
If CreateDirectory(Rename_Path) ; Pfad wird erstellt
MessageRequester("Information", "Rename-Pfad zum Quell-Verzeichnis wurde erstellt", #PB_MessageRequester_Ok | #PB_MessageRequester_Info)
SetGadgetText(#GADGET_Target_path_show, Rename_Path)
Rename_Pictures()
EndIf
ElseIf FileSize(Rename_Path) = -2
MessageRequester("Information", "Rename-Pfad zum Quell-Verzeichnis ist bereits vorhanden", #PB_MessageRequester_Ok | #PB_MessageRequester_Info)
SetGadgetText(#GADGET_Target_path_show, Rename_Path)
Rename_Pictures()
EndIf
EndSelect
Case #GADGET_Button_exit
Select EventType()
Case #PB_EventType_LeftClick
Break ; say good bye
EndSelect
Case #GADGET_Button_rename_abort
Select EventType()
Case #PB_EventType_LeftClick
Rename_Abort = 1
EndSelect
EndSelect ; EventGadget()
EndSelect ; WaitWindowEvent()
ForEver ; <-- main loop end
EndIf ; OpenMainWindow()
ProcedureReturn 0
EndProcedure
; ---------------------------------------------------------------------------
End main()
;----== Bottom of File ==----------------------------------------------------------------------------------------------