commit 9f1d3c81d26607ab39e90f6ec09fa1fe5e1497ba Author: Sebastian Lohff Date: Thu Jun 30 01:04:57 2022 +0200 Import of original sourcecode for afutrainer-3.0 Original source: http://www.oliver-saal.de/software/afutrainer/download/afutrainer-3.0-src.zip Landing page: http://www.oliver-saal.de/software/afutrainer/download.php 092d114b47e40472238e27ee01a528a58ad2d311ec95280b404bb57c97c9fed6 afutrainer-3.0-src.zip diff --git a/APPNOTE.TXT b/APPNOTE.TXT new file mode 100644 index 0000000..c468fde --- /dev/null +++ b/APPNOTE.TXT @@ -0,0 +1,3066 @@ +File: APPNOTE.TXT - .ZIP File Format Specification +Version: 6.3.0 +Revised: September 29, 2006 +Copyright (c) 1989 - 2006 PKWARE Inc., All Rights Reserved. + +The use of certain technological aspects disclosed in the current +APPNOTE is available pursuant to the below section entitled +"Incorporating PKWARE Proprietary Technology into Your Product". + +I. Purpose +---------- + +This specification is intended to define a cross-platform, +interoperable file storage and transfer format. Since its +first publication in 1989, PKWARE has remained committed to +ensuring the interoperability of the .ZIP file format through +publication and maintenance of this specification. We trust that +all .ZIP compatible vendors and application developers that have +adopted and benefited from this format will share and support +this commitment to interoperability. + +II. Contacting PKWARE +--------------------- + + PKWARE, Inc. + 648 N. Plankinton Avenue, Suite 220 + Milwaukee, WI 53203 + +1-414-289-9788 + +1-414-289-9789 FAX + zipformat@pkware.com + +III. Disclaimer +--------------- + +Although PKWARE will attempt to supply current and accurate +information relating to its file formats, algorithms, and the +subject programs, the possibility of error or omission cannot +be eliminated. PKWARE therefore expressly disclaims any warranty +that the information contained in the associated materials relating +to the subject programs and/or the format of the files created or +accessed by the subject programs and/or the algorithms used by +the subject programs, or any other matter, is current, correct or +accurate as delivered. Any risk of damage due to any possible +inaccurate information is assumed by the user of the information. +Furthermore, the information relating to the subject programs +and/or the file formats created or accessed by the subject +programs and/or the algorithms used by the subject programs is +subject to change without notice. + +If the version of this file is marked as a NOTIFICATION OF CHANGE, +the content defines an Early Feature Specification (EFS) change +to the .ZIP file format that may be subject to modification prior +to publication of the Final Feature Specification (FFS). This +document may also contain information on Planned Feature +Specifications (PFS) defining recognized future extensions. + +IV. Change Log +-------------- + +Version Change Description Date +------- ------------------ ---------- +5.2 -Single Password Symmetric Encryption 06/02/2003 + storage + +6.1.0 -Smartcard compatibility 01/20/2004 + -Documentation on certificate storage + +6.2.0 -Introduction of Central Directory 04/26/2004 + Encryption for encrypting metadata + -Added OS/X to Version Made By values + +6.2.1 -Added Extra Field placeholder for 04/01/2005 + POSZIP using ID 0x4690 + + -Clarified size field on + "zip64 end of central directory record" + +6.2.2 -Documented Final Feature Specification 01/06/2006 + for Strong Encryption + + -Clarifications and typographical + corrections + +6.3.0 -Added tape positioning storage 09/29/2006 + parameters + + -Expanded list of supported hash algorithms + + -Expanded list of supported compression + algorithms + + -Expanded list of supported encryption + algorithms + + -Added option for Unicode filename + storage + + -Clarifications for consistent use + of Data Descriptor records + + -Added additional "Extra Field" + definitions + + +V. General Format of a .ZIP file +-------------------------------- + + Files stored in arbitrary order. Large .ZIP files can span multiple + volumes or be split into user-defined segment sizes. All values + are stored in little-endian byte order unless otherwise specified. + + Overall .ZIP file format: + + [local file header 1] + [file data 1] + [data descriptor 1] + . + . + . + [local file header n] + [file data n] + [data descriptor n] + [archive decryption header] + [archive extra data record] + [central directory] + [zip64 end of central directory record] + [zip64 end of central directory locator] + [end of central directory record] + + + A. Local file header: + + local file header signature 4 bytes (0x04034b50) + version needed to extract 2 bytes + general purpose bit flag 2 bytes + compression method 2 bytes + last mod file time 2 bytes + last mod file date 2 bytes + crc-32 4 bytes + compressed size 4 bytes + uncompressed size 4 bytes + file name length 2 bytes + extra field length 2 bytes + + file name (variable size) + extra field (variable size) + + B. File data + + Immediately following the local header for a file + is the compressed or stored data for the file. + The series of [local file header][file data][data + descriptor] repeats for each file in the .ZIP archive. + + C. Data descriptor: + + crc-32 4 bytes + compressed size 4 bytes + uncompressed size 4 bytes + + This descriptor exists only if bit 3 of the general + purpose bit flag is set (see below). It is byte aligned + and immediately follows the last byte of compressed data. + This descriptor is used only when it was not possible to + seek in the output .ZIP file, e.g., when the output .ZIP file + was standard output or a non-seekable device. For ZIP64(tm) format + archives, the compressed and uncompressed sizes are 8 bytes each. + + When compressing files, compressed and uncompressed sizes + should be stored in ZIP64 format (as 8 byte values) when a + files size exceeds 0xFFFFFFFF. However ZIP64 format may be + used regardless of the size of a file. When extracting, if + the zip64 extended information extra field is present for + the file the compressed and uncompressed sizes will be 8 + byte values. + + Although not originally assigned a signature, the value + 0x08074b50 has commonly been adopted as a signature value + for the data descriptor record. Implementers should be + aware that ZIP files may be encountered with or without this + signature marking data descriptors and should account for + either case when reading ZIP files to ensure compatibility. + When writing ZIP files, it is recommended to include the + signature value marking the data descriptor record. When + the signature is used, the fields currently defined for + the data descriptor record will immediately follow the + signature. + + An extensible data descriptor will be released in a future + version of this APPNOTE. This new record is intended to + resolve conflicts with the use of this record going forward, + and to provide better support for streamed file processing. + + When the Central Directory Encryption method is used, the data + descriptor record is not required, but may be used. If present, + and bit 3 of the general purpose bit field is set to indicate + its presence, the values in fields of the data descriptor + record should be set to binary zeros. + + D. Archive decryption header: + + The Archive Decryption Header is introduced in version 6.2 + of the ZIP format specification. This record exists in support + of the Central Directory Encryption Feature implemented as part of + the Strong Encryption Specification as described in this document. + When the Central Directory Structure is encrypted, this decryption + header will precede the encrypted data segment. The encrypted + data segment will consist of the Archive extra data record (if + present) and the encrypted Central Directory Structure data. + The format of this data record is identical to the Decryption + header record preceding compressed file data. If the central + directory structure is encrypted, the location of the start of + this data record is determined using the Start of Central Directory + field in the Zip64 End of Central Directory record. Refer to the + section on the Strong Encryption Specification for information + on the fields used in the Archive Decryption Header record. + + + E. Archive extra data record: + + archive extra data signature 4 bytes (0x08064b50) + extra field length 4 bytes + extra field data (variable size) + + The Archive Extra Data Record is introduced in version 6.2 + of the ZIP format specification. This record exists in support + of the Central Directory Encryption Feature implemented as part of + the Strong Encryption Specification as described in this document. + When present, this record immediately precedes the central + directory data structure. The size of this data record will be + included in the Size of the Central Directory field in the + End of Central Directory record. If the central directory structure + is compressed, but not encrypted, the location of the start of + this data record is determined using the Start of Central Directory + field in the Zip64 End of Central Directory record. + + + F. Central directory structure: + + [file header 1] + . + . + . + [file header n] + [digital signature] + + File header: + + central file header signature 4 bytes (0x02014b50) + version made by 2 bytes + version needed to extract 2 bytes + general purpose bit flag 2 bytes + compression method 2 bytes + last mod file time 2 bytes + last mod file date 2 bytes + crc-32 4 bytes + compressed size 4 bytes + uncompressed size 4 bytes + file name length 2 bytes + extra field length 2 bytes + file comment length 2 bytes + disk number start 2 bytes + internal file attributes 2 bytes + external file attributes 4 bytes + relative offset of local header 4 bytes + + file name (variable size) + extra field (variable size) + file comment (variable size) + + Digital signature: + + header signature 4 bytes (0x05054b50) + size of data 2 bytes + signature data (variable size) + + With the introduction of the Central Directory Encryption + feature in version 6.2 of this specification, the Central + Directory Structure may be stored both compressed and encrypted. + Although not required, it is assumed when encrypting the + Central Directory Structure, that it will be compressed + for greater storage efficiency. Information on the + Central Directory Encryption feature can be found in the section + describing the Strong Encryption Specification. The Digital + Signature record will be neither compressed nor encrypted. + + G. Zip64 end of central directory record + + zip64 end of central dir + signature 4 bytes (0x06064b50) + size of zip64 end of central + directory record 8 bytes + version made by 2 bytes + version needed to extract 2 bytes + number of this disk 4 bytes + number of the disk with the + start of the central directory 4 bytes + total number of entries in the + central directory on this disk 8 bytes + total number of entries in the + central directory 8 bytes + size of the central directory 8 bytes + offset of start of central + directory with respect to + the starting disk number 8 bytes + zip64 extensible data sector (variable size) + + The value stored into the "size of zip64 end of central + directory record" should be the size of the remaining + record and should not include the leading 12 bytes. + + Size = SizeOfFixedFields + SizeOfVariableData - 12. + + The above record structure defines Version 1 of the + zip64 end of central directory record. Version 1 was + implemented in versions of this specification preceding + 6.2 in support of the ZIP64 large file feature. The + introduction of the Central Directory Encryption feature + implemented in version 6.2 as part of the Strong Encryption + Specification defines Version 2 of this record structure. + Refer to the section describing the Strong Encryption + Specification for details on the version 2 format for + this record. + + Special purpose data may reside in the zip64 extensible data + sector field following either a V1 or V2 version of this + record. To ensure identification of this special purpose data + it must include an identifying header block consisting of the + following: + + Header ID - 2 bytes + Data Size - 4 bytes + + The Header ID field indicates the type of data that is in the + data block that follows. + + Data Size identifies the number of bytes that follow for this + data block type. + + Multiple special purpose data blocks may be present, but each + must be preceded by a Header ID and Data Size field. Current + mappings of Header ID values supported in this field are as + defined in APPENDIX C. + + H. Zip64 end of central directory locator + + zip64 end of central dir locator + signature 4 bytes (0x07064b50) + number of the disk with the + start of the zip64 end of + central directory 4 bytes + relative offset of the zip64 + end of central directory record 8 bytes + total number of disks 4 bytes + + I. End of central directory record: + + end of central dir signature 4 bytes (0x06054b50) + number of this disk 2 bytes + number of the disk with the + start of the central directory 2 bytes + total number of entries in the + central directory on this disk 2 bytes + total number of entries in + the central directory 2 bytes + size of the central directory 4 bytes + offset of start of central + directory with respect to + the starting disk number 4 bytes + .ZIP file comment length 2 bytes + .ZIP file comment (variable size) + + J. Explanation of fields: + + version made by (2 bytes) + + The upper byte indicates the compatibility of the file + attribute information. If the external file attributes + are compatible with MS-DOS and can be read by PKZIP for + DOS version 2.04g then this value will be zero. If these + attributes are not compatible, then this value will + identify the host system on which the attributes are + compatible. Software can use this information to determine + the line record format for text files etc. The current + mappings are: + + 0 - MS-DOS and OS/2 (FAT / VFAT / FAT32 file systems) + 1 - Amiga 2 - OpenVMS + 3 - UNIX 4 - VM/CMS + 5 - Atari ST 6 - OS/2 H.P.F.S. + 7 - Macintosh 8 - Z-System + 9 - CP/M 10 - Windows NTFS + 11 - MVS (OS/390 - Z/OS) 12 - VSE + 13 - Acorn Risc 14 - VFAT + 15 - alternate MVS 16 - BeOS + 17 - Tandem 18 - OS/400 + 19 - OS/X (Darwin) 20 thru 255 - unused + + The lower byte indicates the ZIP specification version + (the version of this document) supported by the software + used to encode the file. The value/10 indicates the major + version number, and the value mod 10 is the minor version + number. + + version needed to extract (2 bytes) + + The minimum supported ZIP specification version needed to + extract the file, mapped as above. This value is based on + the specific format features a ZIP program must support to + be able to extract the file. If multiple features are + applied to a file, the minimum version should be set to the + feature having the highest value. New features or feature + changes affecting the published format specification will be + implemented using higher version numbers than the last + published value to avoid conflict. + + Current minimum feature versions are as defined below: + + 1.0 - Default value + 1.1 - File is a volume label + 2.0 - File is a folder (directory) + 2.0 - File is compressed using Deflate compression + 2.0 - File is encrypted using traditional PKWARE encryption + 2.1 - File is compressed using Deflate64(tm) + 2.5 - File is compressed using PKWARE DCL Implode + 2.7 - File is a patch data set + 4.5 - File uses ZIP64 format extensions + 4.6 - File is compressed using BZIP2 compression* + 5.0 - File is encrypted using DES + 5.0 - File is encrypted using 3DES + 5.0 - File is encrypted using original RC2 encryption + 5.0 - File is encrypted using RC4 encryption + 5.1 - File is encrypted using AES encryption + 5.1 - File is encrypted using corrected RC2 encryption** + 5.2 - File is encrypted using corrected RC2-64 encryption** + 6.1 - File is encrypted using non-OAEP key wrapping*** + 6.2 - Central directory encryption + 6.3 - File is compressed using LZMA + 6.3 - File is compressed using PPMd+ + 6.3 - File is encrypted using Blowfish + 6.3 - File is encrypted using Twofish + + + * Early 7.x (pre-7.2) versions of PKZIP incorrectly set the + version needed to extract for BZIP2 compression to be 50 + when it should have been 46. + + ** Refer to the section on Strong Encryption Specification + for additional information regarding RC2 corrections. + + *** Certificate encryption using non-OAEP key wrapping is the + intended mode of operation for all versions beginning with 6.1. + Support for OAEP key wrapping should only be used for + backward compatibility when sending ZIP files to be opened by + versions of PKZIP older than 6.1 (5.0 or 6.0). + + + Files compressed using PPMd should set the version + needed to extract field to 6.3, however, not all ZIP + programs enforce this and may be unable to decompress + data files compressed using PPMd if this value is set. + + When using ZIP64 extensions, the corresponding value in the + zip64 end of central directory record should also be set. + This field should be set appropriately to indicate whether + Version 1 or Version 2 format is in use. + + general purpose bit flag: (2 bytes) + + Bit 0: If set, indicates that the file is encrypted. + + (For Method 6 - Imploding) + Bit 1: If the compression method used was type 6, + Imploding, then this bit, if set, indicates + an 8K sliding dictionary was used. If clear, + then a 4K sliding dictionary was used. + Bit 2: If the compression method used was type 6, + Imploding, then this bit, if set, indicates + 3 Shannon-Fano trees were used to encode the + sliding dictionary output. If clear, then 2 + Shannon-Fano trees were used. + + (For Methods 8 and 9 - Deflating) + Bit 2 Bit 1 + 0 0 Normal (-en) compression option was used. + 0 1 Maximum (-exx/-ex) compression option was used. + 1 0 Fast (-ef) compression option was used. + 1 1 Super Fast (-es) compression option was used. + + (For Method 14 - LZMA) + Bit 1: If the compression method used was type 14, + LZMA, then this bit, if set, indicates + an end-of-stream (EOS) marker is used to + mark the end of the compressed data stream. + If clear, then an EOS marker is not present + and the compressed data size must be known + to extract. + + Note: Bits 1 and 2 are undefined if the compression + method is any other. + + Bit 3: If this bit is set, the fields crc-32, compressed + size and uncompressed size are set to zero in the + local header. The correct values are put in the + data descriptor immediately following the compressed + data. (Note: PKZIP version 2.04g for DOS only + recognizes this bit for method 8 compression, newer + versions of PKZIP recognize this bit for any + compression method.) + + Bit 4: Reserved for use with method 8, for enhanced + deflating. + + Bit 5: If this bit is set, this indicates that the file is + compressed patched data. (Note: Requires PKZIP + version 2.70 or greater) + + Bit 6: Strong encryption. If this bit is set, you should + set the version needed to extract value to at least + 50 and you must also set bit 0. If AES encryption + is used, the version needed to extract value must + be at least 51. + + Bit 7: Currently unused. + + Bit 8: Currently unused. + + Bit 9: Currently unused. + + Bit 10: Currently unused. + + Bit 11: Language encoding flag (EFS). If this bit is set, + the filename and comment fields for this file + must be encoded using UTF-8. (see APPENDIX D) + + Bit 12: Reserved by PKWARE for enhanced compression. + + Bit 13: Used when encrypting the Central Directory to indicate + selected data values in the Local Header are masked to + hide their actual values. See the section describing + the Strong Encryption Specification for details. + + Bit 14: Reserved by PKWARE. + + Bit 15: Reserved by PKWARE. + + compression method: (2 bytes) + + (see accompanying documentation for algorithm + descriptions) + + 0 - The file is stored (no compression) + 1 - The file is Shrunk + 2 - The file is Reduced with compression factor 1 + 3 - The file is Reduced with compression factor 2 + 4 - The file is Reduced with compression factor 3 + 5 - The file is Reduced with compression factor 4 + 6 - The file is Imploded + 7 - Reserved for Tokenizing compression algorithm + 8 - The file is Deflated + 9 - Enhanced Deflating using Deflate64(tm) + 10 - PKWARE Data Compression Library Imploding (old IBM TERSE) + 11 - Reserved by PKWARE + 12 - File is compressed using BZIP2 algorithm + 13 - Reserved by PKWARE + 14 - LZMA (EFS) + 15 - Reserved by PKWARE + 16 - Reserved by PKWARE + 17 - Reserved by PKWARE + 18 - File is compressed using IBM TERSE (new) + 19 - IBM LZ77 z Architecture (PFS) + 98 - PPMd version I, Rev 1 + + date and time fields: (2 bytes each) + + The date and time are encoded in standard MS-DOS format. + If input came from standard input, the date and time are + those at which compression was started for this data. + If encrypting the central directory and general purpose bit + flag 13 is set indicating masking, the value stored in the + Local Header will be zero. + + CRC-32: (4 bytes) + + The CRC-32 algorithm was generously contributed by + David Schwaderer and can be found in his excellent + book "C Programmers Guide to NetBIOS" published by + Howard W. Sams & Co. Inc. The 'magic number' for + the CRC is 0xdebb20e3. The proper CRC pre and post + conditioning is used, meaning that the CRC register + is pre-conditioned with all ones (a starting value + of 0xffffffff) and the value is post-conditioned by + taking the one's complement of the CRC residual. + If bit 3 of the general purpose flag is set, this + field is set to zero in the local header and the correct + value is put in the data descriptor and in the central + directory. When encrypting the central directory, if the + local header is not in ZIP64 format and general purpose + bit flag 13 is set indicating masking, the value stored + in the Local Header will be zero. + + compressed size: (4 bytes) + uncompressed size: (4 bytes) + + The size of the file compressed and uncompressed, + respectively. When a decryption header is present it will + be placed in front of the file data and the value of the + compressed file size will include the bytes of the decryption + header. If bit 3 of the general purpose bit flag is set, + these fields are set to zero in the local header and the + correct values are put in the data descriptor and + in the central directory. If an archive is in ZIP64 format + and the value in this field is 0xFFFFFFFF, the size will be + in the corresponding 8 byte ZIP64 extended information + extra field. When encrypting the central directory, if the + local header is not in ZIP64 format and general purpose bit + flag 13 is set indicating masking, the value stored for the + uncompressed size in the Local Header will be zero. + + file name length: (2 bytes) + extra field length: (2 bytes) + file comment length: (2 bytes) + + The length of the file name, extra field, and comment + fields respectively. The combined length of any + directory record and these three fields should not + generally exceed 65,535 bytes. If input came from standard + input, the file name length is set to zero. + + disk number start: (2 bytes) + + The number of the disk on which this file begins. If an + archive is in ZIP64 format and the value in this field is + 0xFFFF, the size will be in the corresponding 4 byte zip64 + extended information extra field. + + internal file attributes: (2 bytes) + + Bits 1 and 2 are reserved for use by PKWARE. + + The lowest bit of this field indicates, if set, that + the file is apparently an ASCII or text file. If not + set, that the file apparently contains binary data. + The remaining bits are unused in version 1.0. + + The 0x0002 bit of this field indicates, if set, that a + 4 byte variable record length control field precedes each + logical record indicating the length of the record. The + record length control field is stored in little-endian byte + order. This flag is independent of text control characters, + and if used in conjunction with text data, includes any + control characters in the total length of the record. This + value is provided for mainframe data transfer support. + + external file attributes: (4 bytes) + + The mapping of the external attributes is + host-system dependent (see 'version made by'). For + MS-DOS, the low order byte is the MS-DOS directory + attribute byte. If input came from standard input, this + field is set to zero. + + relative offset of local header: (4 bytes) + + This is the offset from the start of the first disk on + which this file appears, to where the local header should + be found. If an archive is in ZIP64 format and the value + in this field is 0xFFFFFFFF, the size will be in the + corresponding 8 byte zip64 extended information extra field. + + file name: (Variable) + + The name of the file, with optional relative path. + The path stored should not contain a drive or + device letter, or a leading slash. All slashes + should be forward slashes '/' as opposed to + backwards slashes '\' for compatibility with Amiga + and UNIX file systems etc. If input came from standard + input, there is no file name field. If encrypting + the central directory and general purpose bit flag 13 is set + indicating masking, the file name stored in the Local Header + will not be the actual file name. A masking value consisting + of a unique hexadecimal value will be stored. This value will + be sequentially incremented for each file in the archive. See + the section on the Strong Encryption Specification for details + on retrieving the encrypted file name. + + extra field: (Variable) + + This is for expansion. If additional information + needs to be stored for special needs or for specific + platforms, it should be stored here. Earlier versions + of the software can then safely skip this file, and + find the next file or header. This field will be 0 + length in version 1.0. + + In order to allow different programs and different types + of information to be stored in the 'extra' field in .ZIP + files, the following structure should be used for all + programs storing data in this field: + + header1+data1 + header2+data2 . . . + + Each header should consist of: + + Header ID - 2 bytes + Data Size - 2 bytes + + Note: all fields stored in Intel low-byte/high-byte order. + + The Header ID field indicates the type of data that is in + the following data block. + + Header ID's of 0 thru 31 are reserved for use by PKWARE. + The remaining ID's can be used by third party vendors for + proprietary usage. + + The current Header ID mappings defined by PKWARE are: + + 0x0001 Zip64 extended information extra field + 0x0007 AV Info + 0x0008 Reserved for extended language encoding data (PFS) + (see APPENDIX D) + 0x0009 OS/2 + 0x000a NTFS + 0x000c OpenVMS + 0x000d UNIX + 0x000e Reserved for file stream and fork descriptors + 0x000f Patch Descriptor + 0x0014 PKCS#7 Store for X.509 Certificates + 0x0015 X.509 Certificate ID and Signature for + individual file + 0x0016 X.509 Certificate ID for Central Directory + 0x0017 Strong Encryption Header + 0x0018 Record Management Controls + 0x0019 PKCS#7 Encryption Recipient Certificate List + 0x0065 IBM S/390 (Z390), AS/400 (I400) attributes + - uncompressed + 0x0066 Reserved for IBM S/390 (Z390), AS/400 (I400) + attributes - compressed + 0x4690 POSZIP 4690 (reserved) + + Third party mappings commonly used are: + + + 0x07c8 Macintosh + 0x2605 ZipIt Macintosh + 0x2705 ZipIt Macintosh 1.3.5+ + 0x2805 ZipIt Macintosh 1.3.5+ + 0x334d Info-ZIP Macintosh + 0x4341 Acorn/SparkFS + 0x4453 Windows NT security descriptor (binary ACL) + 0x4704 VM/CMS + 0x470f MVS + 0x4b46 FWKCS MD5 (see below) + 0x4c41 OS/2 access control list (text ACL) + 0x4d49 Info-ZIP OpenVMS + 0x4f4c Xceed original location extra field + 0x5356 AOS/VS (ACL) + 0x5455 extended timestamp + 0x554e Xceed unicode extra field + 0x5855 Info-ZIP UNIX (original, also OS/2, NT, etc) + 0x6542 BeOS/BeBox + 0x756e ASi UNIX + 0x7855 Info-ZIP UNIX (new) + 0xa220 Microsoft Open Packaging Growth Hint + 0xfd4a SMS/QDOS + + Detailed descriptions of Extra Fields defined by third + party mappings will be documented as information on + these data structures is made available to PKWARE. + PKWARE does not guarantee the accuracy of any published + third party data. + + The Data Size field indicates the size of the following + data block. Programs can use this value to skip to the + next header block, passing over any data blocks that are + not of interest. + + Note: As stated above, the size of the entire .ZIP file + header, including the file name, comment, and extra + field should not exceed 64K in size. + + In case two different programs should appropriate the same + Header ID value, it is strongly recommended that each + program place a unique signature of at least two bytes in + size (and preferably 4 bytes or bigger) at the start of + each data area. Every program should verify that its + unique signature is present, in addition to the Header ID + value being correct, before assuming that it is a block of + known type. + + -Zip64 Extended Information Extra Field (0x0001): + + The following is the layout of the zip64 extended + information "extra" block. If one of the size or + offset fields in the Local or Central directory + record is too small to hold the required data, + a Zip64 extended information record is created. + The order of the fields in the zip64 extended + information record is fixed, but the fields will + only appear if the corresponding Local or Central + directory record field is set to 0xFFFF or 0xFFFFFFFF. + + Note: all fields stored in Intel low-byte/high-byte order. + + Value Size Description + ----- ---- ----------- + (ZIP64) 0x0001 2 bytes Tag for this "extra" block type + Size 2 bytes Size of this "extra" block + Original + Size 8 bytes Original uncompressed file size + Compressed + Size 8 bytes Size of compressed data + Relative Header + Offset 8 bytes Offset of local header record + Disk Start + Number 4 bytes Number of the disk on which + this file starts + + This entry in the Local header must include BOTH original + and compressed file size fields. If encrypting the + central directory and bit 13 of the general purpose bit + flag is set indicating masking, the value stored in the + Local Header for the original file size will be zero. + + + -OS/2 Extra Field (0x0009): + + The following is the layout of the OS/2 attributes "extra" + block. (Last Revision 09/05/95) + + Note: all fields stored in Intel low-byte/high-byte order. + + Value Size Description + ----- ---- ----------- + (OS/2) 0x0009 2 bytes Tag for this "extra" block type + TSize 2 bytes Size for the following data block + BSize 4 bytes Uncompressed Block Size + CType 2 bytes Compression type + EACRC 4 bytes CRC value for uncompress block + (var) variable Compressed block + + The OS/2 extended attribute structure (FEA2LIST) is + compressed and then stored in it's entirety within this + structure. There will only ever be one "block" of data in + VarFields[]. + + -NTFS Extra Field (0x000a): + + The following is the layout of the NTFS attributes + "extra" block. (Note: At this time the Mtime, Atime + and Ctime values may be used on any WIN32 system.) + + Note: all fields stored in Intel low-byte/high-byte order. + + Value Size Description + ----- ---- ----------- + (NTFS) 0x000a 2 bytes Tag for this "extra" block type + TSize 2 bytes Size of the total "extra" block + Reserved 4 bytes Reserved for future use + Tag1 2 bytes NTFS attribute tag value #1 + Size1 2 bytes Size of attribute #1, in bytes + (var.) Size1 Attribute #1 data + . + . + . + TagN 2 bytes NTFS attribute tag value #N + SizeN 2 bytes Size of attribute #N, in bytes + (var.) SizeN Attribute #N data + + For NTFS, values for Tag1 through TagN are as follows: + (currently only one set of attributes is defined for NTFS) + + Tag Size Description + ----- ---- ----------- + 0x0001 2 bytes Tag for attribute #1 + Size1 2 bytes Size of attribute #1, in bytes + Mtime 8 bytes File last modification time + Atime 8 bytes File last access time + Ctime 8 bytes File creation time + + -OpenVMS Extra Field (0x000c): + + The following is the layout of the OpenVMS attributes + "extra" block. + + Note: all fields stored in Intel low-byte/high-byte order. + + Value Size Description + ----- ---- ----------- + (VMS) 0x000c 2 bytes Tag for this "extra" block type + TSize 2 bytes Size of the total "extra" block + CRC 4 bytes 32-bit CRC for remainder of the block + Tag1 2 bytes OpenVMS attribute tag value #1 + Size1 2 bytes Size of attribute #1, in bytes + (var.) Size1 Attribute #1 data + . + . + . + TagN 2 bytes OpenVMS attribute tag value #N + SizeN 2 bytes Size of attribute #N, in bytes + (var.) SizeN Attribute #N data + + Rules: + + 1. There will be one or more of attributes present, which + will each be preceded by the above TagX & SizeX values. + These values are identical to the ATR$C_XXXX and + ATR$S_XXXX constants which are defined in ATR.H under + OpenVMS C. Neither of these values will ever be zero. + + 2. No word alignment or padding is performed. + + 3. A well-behaved PKZIP/OpenVMS program should never produce + more than one sub-block with the same TagX value. Also, + there will never be more than one "extra" block of type + 0x000c in a particular directory record. + + -UNIX Extra Field (0x000d): + + The following is the layout of the UNIX "extra" block. + Note: all fields are stored in Intel low-byte/high-byte + order. + + Value Size Description + ----- ---- ----------- + (UNIX) 0x000d 2 bytes Tag for this "extra" block type + TSize 2 bytes Size for the following data block + Atime 4 bytes File last access time + Mtime 4 bytes File last modification time + Uid 2 bytes File user ID + Gid 2 bytes File group ID + (var) variable Variable length data field + + The variable length data field will contain file type + specific data. Currently the only values allowed are + the original "linked to" file names for hard or symbolic + links, and the major and minor device node numbers for + character and block device nodes. Since device nodes + cannot be either symbolic or hard links, only one set of + variable length data is stored. Link files will have the + name of the original file stored. This name is NOT NULL + terminated. Its size can be determined by checking TSize - + 12. Device entries will have eight bytes stored as two 4 + byte entries (in little endian format). The first entry + will be the major device number, and the second the minor + device number. + + -PATCH Descriptor Extra Field (0x000f): + + The following is the layout of the Patch Descriptor "extra" + block. + + Note: all fields stored in Intel low-byte/high-byte order. + + Value Size Description + ----- ---- ----------- + (Patch) 0x000f 2 bytes Tag for this "extra" block type + TSize 2 bytes Size of the total "extra" block + Version 2 bytes Version of the descriptor + Flags 4 bytes Actions and reactions (see below) + OldSize 4 bytes Size of the file about to be patched + OldCRC 4 bytes 32-bit CRC of the file to be patched + NewSize 4 bytes Size of the resulting file + NewCRC 4 bytes 32-bit CRC of the resulting file + + Actions and reactions + + Bits Description + ---- ---------------- + 0 Use for auto detection + 1 Treat as a self-patch + 2-3 RESERVED + 4-5 Action (see below) + 6-7 RESERVED + 8-9 Reaction (see below) to absent file + 10-11 Reaction (see below) to newer file + 12-13 Reaction (see below) to unknown file + 14-15 RESERVED + 16-31 RESERVED + + Actions + + Action Value + ------ ----- + none 0 + add 1 + delete 2 + patch 3 + + Reactions + + Reaction Value + -------- ----- + ask 0 + skip 1 + ignore 2 + fail 3 + + Patch support is provided by PKPatchMaker(tm) technology and is + covered under U.S. Patents and Patents Pending. The use or + implementation in a product of certain technological aspects set + forth in the current APPNOTE, including those with regard to + strong encryption, patching, or extended tape operations requires + a license from PKWARE. Please contact PKWARE with regard to + acquiring a license. + + -PKCS#7 Store for X.509 Certificates (0x0014): + + This field contains information about each of the certificates + files may be signed with. When the Central Directory Encryption + feature is enabled for a ZIP file, this record will appear in + the Archive Extra Data Record, otherwise it will appear in the + first central directory record and will be ignored in any + other record. + + Note: all fields stored in Intel low-byte/high-byte order. + + Value Size Description + ----- ---- ----------- + (Store) 0x0014 2 bytes Tag for this "extra" block type + TSize 2 bytes Size of the store data + TData TSize Data about the store + + + -X.509 Certificate ID and Signature for individual file (0x0015): + + This field contains the information about which certificate in + the PKCS#7 store was used to sign a particular file. It also + contains the signature data. This field can appear multiple + times, but can only appear once per certificate. + + Note: all fields stored in Intel low-byte/high-byte order. + + Value Size Description + ----- ---- ----------- + (CID) 0x0015 2 bytes Tag for this "extra" block type + TSize 2 bytes Size of data that follows + TData TSize Signature Data + + -X.509 Certificate ID and Signature for central directory (0x0016): + + This field contains the information about which certificate in + the PKCS#7 store was used to sign the central directory structure. + When the Central Directory Encryption feature is enabled for a + ZIP file, this record will appear in the Archive Extra Data Record, + otherwise it will appear in the first central directory record. + + Note: all fields stored in Intel low-byte/high-byte order. + + Value Size Description + ----- ---- ----------- + (CDID) 0x0016 2 bytes Tag for this "extra" block type + TSize 2 bytes Size of data that follows + TData TSize Data + + -Strong Encryption Header (0x0017): + + Value Size Description + ----- ---- ----------- + 0x0017 2 bytes Tag for this "extra" block type + TSize 2 bytes Size of data that follows + Format 2 bytes Format definition for this record + AlgID 2 bytes Encryption algorithm identifier + Bitlen 2 bytes Bit length of encryption key + Flags 2 bytes Processing flags + CertData TSize-8 Certificate decryption extra field data + (refer to the explanation for CertData + in the section describing the + Certificate Processing Method under + the Strong Encryption Specification) + + + -Record Management Controls (0x0018): + + Value Size Description + ----- ---- ----------- +(Rec-CTL) 0x0018 2 bytes Tag for this "extra" block type + CSize 2 bytes Size of total extra block data + Tag1 2 bytes Record control attribute 1 + Size1 2 bytes Size of attribute 1, in bytes + Data1 Size1 Attribute 1 data + . + . + . + TagN 2 bytes Record control attribute N + SizeN 2 bytes Size of attribute N, in bytes + DataN SizeN Attribute N data + + + -PKCS#7 Encryption Recipient Certificate List (0x0019): + + This field contains information about each of the certificates + used in encryption processing and it can be used to identify who is + allowed to decrypt encrypted files. This field should only appear + in the archive extra data record. This field is not required and + serves only to aide archive modifications by preserving public + encryption key data. Individual security requirements may dictate + that this data be omitted to deter information exposure. + + Note: all fields stored in Intel low-byte/high-byte order. + + Value Size Description + ----- ---- ----------- + (CStore) 0x0019 2 bytes Tag for this "extra" block type + TSize 2 bytes Size of the store data + TData TSize Data about the store + + TData: + + Value Size Description + ----- ---- ----------- + Version 2 bytes Format version number - must 0x0001 at this time + CStore (var) PKCS#7 data blob + + + -MVS Extra Field (0x0065): + + The following is the layout of the MVS "extra" block. + Note: Some fields are stored in Big Endian format. + All text is in EBCDIC format unless otherwise specified. + + Value Size Description + ----- ---- ----------- + (MVS) 0x0065 2 bytes Tag for this "extra" block type + TSize 2 bytes Size for the following data block + ID 4 bytes EBCDIC "Z390" 0xE9F3F9F0 or + "T4MV" for TargetFour + (var) TSize-4 Attribute data (see APPENDIX B) + + + -OS/400 Extra Field (0x0065): + + The following is the layout of the OS/400 "extra" block. + Note: Some fields are stored in Big Endian format. + All text is in EBCDIC format unless otherwise specified. + + Value Size Description + ----- ---- ----------- + (OS400) 0x0065 2 bytes Tag for this "extra" block type + TSize 2 bytes Size for the following data block + ID 4 bytes EBCDIC "I400" 0xC9F4F0F0 or + "T4MV" for TargetFour + (var) TSize-4 Attribute data (see APPENDIX A) + + + Third-party Mappings: + + -ZipIt Macintosh Extra Field (long) (0x2605): + + The following is the layout of the ZipIt extra block + for Macintosh. The local-header and central-header versions + are identical. This block must be present if the file is + stored MacBinary-encoded and it should not be used if the file + is not stored MacBinary-encoded. + + Value Size Description + ----- ---- ----------- + (Mac2) 0x2605 Short tag for this extra block type + TSize Short total data size for this block + "ZPIT" beLong extra-field signature + FnLen Byte length of FileName + FileName variable full Macintosh filename + FileType Byte[4] four-byte Mac file type string + Creator Byte[4] four-byte Mac creator string + + + -ZipIt Macintosh Extra Field (short, for files) (0x2705): + + The following is the layout of a shortened variant of the + ZipIt extra block for Macintosh (without "full name" entry). + This variant is used by ZipIt 1.3.5 and newer for entries of + files (not directories) that do not have a MacBinary encoded + file. The local-header and central-header versions are identical. + + Value Size Description + ----- ---- ----------- + (Mac2b) 0x2705 Short tag for this extra block type + TSize Short total data size for this block (12) + "ZPIT" beLong extra-field signature + FileType Byte[4] four-byte Mac file type string + Creator Byte[4] four-byte Mac creator string + fdFlags beShort attributes from FInfo.frFlags, + may be omitted + 0x0000 beShort reserved, may be omitted + + + -ZipIt Macintosh Extra Field (short, for directories) (0x2805): + + The following is the layout of a shortened variant of the + ZipIt extra block for Macintosh used only for directory + entries. This variant is used by ZipIt 1.3.5 and newer to + save some optional Mac-specific information about directories. + The local-header and central-header versions are identical. + + Value Size Description + ----- ---- ----------- + (Mac2c) 0x2805 Short tag for this extra block type + TSize Short total data size for this block (12) + "ZPIT" beLong extra-field signature + frFlags beShort attributes from DInfo.frFlags, may + be omitted + View beShort ZipIt view flag, may be omitted + + + The View field specifies ZipIt-internal settings as follows: + + Bits of the Flags: + bit 0 if set, the folder is shown expanded (open) + when the archive contents are viewed in ZipIt. + bits 1-15 reserved, zero; + + + -FWKCS MD5 Extra Field (0x4b46): + + The FWKCS Contents_Signature System, used in + automatically identifying files independent of file name, + optionally adds and uses an extra field to support the + rapid creation of an enhanced contents_signature: + + Header ID = 0x4b46 + Data Size = 0x0013 + Preface = 'M','D','5' + followed by 16 bytes containing the uncompressed file's + 128_bit MD5 hash(1), low byte first. + + When FWKCS revises a .ZIP file central directory to add + this extra field for a file, it also replaces the + central directory entry for that file's uncompressed + file length with a measured value. + + FWKCS provides an option to strip this extra field, if + present, from a .ZIP file central directory. In adding + this extra field, FWKCS preserves .ZIP file Authenticity + Verification; if stripping this extra field, FWKCS + preserves all versions of AV through PKZIP version 2.04g. + + FWKCS, and FWKCS Contents_Signature System, are + trademarks of Frederick W. Kantor. + + (1) R. Rivest, RFC1321.TXT, MIT Laboratory for Computer + Science and RSA Data Security, Inc., April 1992. + ll.76-77: "The MD5 algorithm is being placed in the + public domain for review and possible adoption as a + standard." + + -Microsoft Open Packaging Growth Hint (0xa220): + + Value Size Description + ----- ---- ----------- + 0xa220 Short tag for this extra block type + TSize Short size of Sig + PadVal + Padding + Sig Short verification signature (A028) + PadVal Short Initial padding value + Padding variable filled with NULL characters + + + file comment: (Variable) + + The comment for this file. + + number of this disk: (2 bytes) + + The number of this disk, which contains central + directory end record. If an archive is in ZIP64 format + and the value in this field is 0xFFFF, the size will + be in the corresponding 4 byte zip64 end of central + directory field. + + + number of the disk with the start of the central + directory: (2 bytes) + + The number of the disk on which the central + directory starts. If an archive is in ZIP64 format + and the value in this field is 0xFFFF, the size will + be in the corresponding 4 byte zip64 end of central + directory field. + + total number of entries in the central dir on + this disk: (2 bytes) + + The number of central directory entries on this disk. + If an archive is in ZIP64 format and the value in + this field is 0xFFFF, the size will be in the + corresponding 8 byte zip64 end of central + directory field. + + total number of entries in the central dir: (2 bytes) + + The total number of files in the .ZIP file. If an + archive is in ZIP64 format and the value in this field + is 0xFFFF, the size will be in the corresponding 8 byte + zip64 end of central directory field. + + size of the central directory: (4 bytes) + + The size (in bytes) of the entire central directory. + If an archive is in ZIP64 format and the value in + this field is 0xFFFFFFFF, the size will be in the + corresponding 8 byte zip64 end of central + directory field. + + offset of start of central directory with respect to + the starting disk number: (4 bytes) + + Offset of the start of the central directory on the + disk on which the central directory starts. If an + archive is in ZIP64 format and the value in this + field is 0xFFFFFFFF, the size will be in the + corresponding 8 byte zip64 end of central + directory field. + + .ZIP file comment length: (2 bytes) + + The length of the comment for this .ZIP file. + + .ZIP file comment: (Variable) + + The comment for this .ZIP file. ZIP file comment data + is stored unsecured. No encryption or data authentication + is applied to this area at this time. Confidential information + should not be stored in this section. + + zip64 extensible data sector (variable size) + + (currently reserved for use by PKWARE) + + + K. Splitting and Spanning ZIP files + + Spanning is the process of segmenting a ZIP file across + multiple removable media. This support has typically only + been provided for DOS formatted floppy diskettes. + + File splitting is a newer derivative of spanning. + Splitting follows the same segmentation process as + spanning, however, it does not require writing each + segment to a unique removable medium and instead supports + placing all pieces onto local or non-removable locations + such as file systems, local drives, folders, etc... + + A key difference between spanned and split ZIP files is + that all pieces of a spanned ZIP file have the same name. + Since each piece is written to a separate volume, no name + collisions occur and each segment can reuse the original + .ZIP file name given to the archive. + + Sequence ordering for DOS spanned archives uses the DOS + volume label to determine segment numbers. Volume labels + for each segment are written using the form PKBACK#xxx, + where xxx is the segment number written as a decimal + value from 001 - nnn. + + Split ZIP files are typically written to the same location + and are subject to name collisions if the spanned name + format is used since each segment will reside on the same + drive. To avoid name collisions, split archives are named + as follows. + + Segment 1 = filename.z01 + Segment n-1 = filename.z(n-1) + Segment n = filename.zip + + The .ZIP extension is used on the last segment to support + quickly reading the central directory. The segment number + n should be a decimal value. + + Spanned ZIP files may be PKSFX Self-extracting ZIP files. + PKSFX files may also be split, however, in this case + the first segment must be named filename.exe. The first + segment of a split PKSFX archive must be large enough to + include the entire executable program. + + Capacities for split archives are as follows. + + Maximum number of segments = 4,294,967,295 - 1 + Maximum .ZIP segment size = 4,294,967,295 bytes + Minimum segment size = 64K + Maximum PKSFX segment size = 2,147,483,647 bytes + + Segment sizes may be different however by convention, all + segment sizes should be the same with the exception of the + last, which may be smaller. Local and central directory + header records must never be split across a segment boundary. + When writing a header record, if the number of bytes remaining + within a segment is less than the size of the header record, + end the current segment and write the header at the start + of the next segment. The central directory may span segment + boundaries, but no single record in the central directory + should be split across segments. + + Spanned/Split archives created using PKZIP for Windows + (V2.50 or greater), PKZIP Command Line (V2.50 or greater), + or PKZIP Explorer will include a special spanning + signature as the first 4 bytes of the first segment of + the archive. This signature (0x08074b50) will be + followed immediately by the local header signature for + the first file in the archive. + + A special spanning marker may also appear in spanned/split + archives if the spanning or splitting process starts but + only requires one segment. In this case the 0x08074b50 + signature will be replaced with the temporary spanning + marker signature of 0x30304b50. Split archives can + only be uncompressed by other versions of PKZIP that + know how to create a split archive. + + The signature value 0x08074b50 is also used by some + ZIP implementations as a marker for the Data Descriptor + record. Conflict in this alternate assignment can be + avoided by ensuring the position of the signature + within the ZIP file to determine the use for which it + is intended. + + L. General notes: + + 1) All fields unless otherwise noted are unsigned and stored + in Intel low-byte:high-byte, low-word:high-word order. + + 2) String fields are not null terminated, since the + length is given explicitly. + + 3) The entries in the central directory may not necessarily + be in the same order that files appear in the .ZIP file. + + 4) If one of the fields in the end of central directory + record is too small to hold required data, the field + should be set to -1 (0xFFFF or 0xFFFFFFFF) and the + ZIP64 format record should be created. + + 5) The end of central directory record and the + Zip64 end of central directory locator record must + reside on the same disk when splitting or spanning + an archive. + +VI. UnShrinking - Method 1 +-------------------------- + +Shrinking is a Dynamic Ziv-Lempel-Welch compression algorithm +with partial clearing. The initial code size is 9 bits, and +the maximum code size is 13 bits. Shrinking differs from +conventional Dynamic Ziv-Lempel-Welch implementations in several +respects: + +1) The code size is controlled by the compressor, and is not + automatically increased when codes larger than the current + code size are created (but not necessarily used). When + the decompressor encounters the code sequence 256 + (decimal) followed by 1, it should increase the code size + read from the input stream to the next bit size. No + blocking of the codes is performed, so the next code at + the increased size should be read from the input stream + immediately after where the previous code at the smaller + bit size was read. Again, the decompressor should not + increase the code size used until the sequence 256,1 is + encountered. + +2) When the table becomes full, total clearing is not + performed. Rather, when the compressor emits the code + sequence 256,2 (decimal), the decompressor should clear + all leaf nodes from the Ziv-Lempel tree, and continue to + use the current code size. The nodes that are cleared + from the Ziv-Lempel tree are then re-used, with the lowest + code value re-used first, and the highest code value + re-used last. The compressor can emit the sequence 256,2 + at any time. + +VII. Expanding - Methods 2-5 +---------------------------- + +The Reducing algorithm is actually a combination of two +distinct algorithms. The first algorithm compresses repeated +byte sequences, and the second algorithm takes the compressed +stream from the first algorithm and applies a probabilistic +compression method. + +The probabilistic compression stores an array of 'follower +sets' S(j), for j=0 to 255, corresponding to each possible +ASCII character. Each set contains between 0 and 32 +characters, to be denoted as S(j)[0],...,S(j)[m], where m<32. +The sets are stored at the beginning of the data area for a +Reduced file, in reverse order, with S(255) first, and S(0) +last. + +The sets are encoded as { N(j), S(j)[0],...,S(j)[N(j)-1] }, +where N(j) is the size of set S(j). N(j) can be 0, in which +case the follower set for S(j) is empty. Each N(j) value is +encoded in 6 bits, followed by N(j) eight bit character values +corresponding to S(j)[0] to S(j)[N(j)-1] respectively. If +N(j) is 0, then no values for S(j) are stored, and the value +for N(j-1) immediately follows. + +Immediately after the follower sets, is the compressed data +stream. The compressed data stream can be interpreted for the +probabilistic decompression as follows: + +let Last-Character <- 0. +loop until done + if the follower set S(Last-Character) is empty then + read 8 bits from the input stream, and copy this + value to the output stream. + otherwise if the follower set S(Last-Character) is non-empty then + read 1 bit from the input stream. + if this bit is not zero then + read 8 bits from the input stream, and copy this + value to the output stream. + otherwise if this bit is zero then + read B(N(Last-Character)) bits from the input + stream, and assign this value to I. + Copy the value of S(Last-Character)[I] to the + output stream. + + assign the last value placed on the output stream to + Last-Character. +end loop + +B(N(j)) is defined as the minimal number of bits required to +encode the value N(j)-1. + +The decompressed stream from above can then be expanded to +re-create the original file as follows: + +let State <- 0. + +loop until done + read 8 bits from the input stream into C. + case State of + 0: if C is not equal to DLE (144 decimal) then + copy C to the output stream. + otherwise if C is equal to DLE then + let State <- 1. + + 1: if C is non-zero then + let V <- C. + let Len <- L(V) + let State <- F(Len). + otherwise if C is zero then + copy the value 144 (decimal) to the output stream. + let State <- 0 + + 2: let Len <- Len + C + let State <- 3. + + 3: move backwards D(V,C) bytes in the output stream + (if this position is before the start of the output + stream, then assume that all the data before the + start of the output stream is filled with zeros). + copy Len+3 bytes from this position to the output stream. + let State <- 0. + end case +end loop + +The functions F,L, and D are dependent on the 'compression +factor', 1 through 4, and are defined as follows: + +For compression factor 1: + L(X) equals the lower 7 bits of X. + F(X) equals 2 if X equals 127 otherwise F(X) equals 3. + D(X,Y) equals the (upper 1 bit of X) * 256 + Y + 1. +For compression factor 2: + L(X) equals the lower 6 bits of X. + F(X) equals 2 if X equals 63 otherwise F(X) equals 3. + D(X,Y) equals the (upper 2 bits of X) * 256 + Y + 1. +For compression factor 3: + L(X) equals the lower 5 bits of X. + F(X) equals 2 if X equals 31 otherwise F(X) equals 3. + D(X,Y) equals the (upper 3 bits of X) * 256 + Y + 1. +For compression factor 4: + L(X) equals the lower 4 bits of X. + F(X) equals 2 if X equals 15 otherwise F(X) equals 3. + D(X,Y) equals the (upper 4 bits of X) * 256 + Y + 1. + +VIII. Imploding - Method 6 +-------------------------- + +The Imploding algorithm is actually a combination of two distinct +algorithms. The first algorithm compresses repeated byte +sequences using a sliding dictionary. The second algorithm is +used to compress the encoding of the sliding dictionary output, +using multiple Shannon-Fano trees. + +The Imploding algorithm can use a 4K or 8K sliding dictionary +size. The dictionary size used can be determined by bit 1 in the +general purpose flag word; a 0 bit indicates a 4K dictionary +while a 1 bit indicates an 8K dictionary. + +The Shannon-Fano trees are stored at the start of the compressed +file. The number of trees stored is defined by bit 2 in the +general purpose flag word; a 0 bit indicates two trees stored, a +1 bit indicates three trees are stored. If 3 trees are stored, +the first Shannon-Fano tree represents the encoding of the +Literal characters, the second tree represents the encoding of +the Length information, the third represents the encoding of the +Distance information. When 2 Shannon-Fano trees are stored, the +Length tree is stored first, followed by the Distance tree. + +The Literal Shannon-Fano tree, if present is used to represent +the entire ASCII character set, and contains 256 values. This +tree is used to compress any data not compressed by the sliding +dictionary algorithm. When this tree is present, the Minimum +Match Length for the sliding dictionary is 3. If this tree is +not present, the Minimum Match Length is 2. + +The Length Shannon-Fano tree is used to compress the Length part +of the (length,distance) pairs from the sliding dictionary +output. The Length tree contains 64 values, ranging from the +Minimum Match Length, to 63 plus the Minimum Match Length. + +The Distance Shannon-Fano tree is used to compress the Distance +part of the (length,distance) pairs from the sliding dictionary +output. The Distance tree contains 64 values, ranging from 0 to +63, representing the upper 6 bits of the distance value. The +distance values themselves will be between 0 and the sliding +dictionary size, either 4K or 8K. + +The Shannon-Fano trees themselves are stored in a compressed +format. The first byte of the tree data represents the number of +bytes of data representing the (compressed) Shannon-Fano tree +minus 1. The remaining bytes represent the Shannon-Fano tree +data encoded as: + + High 4 bits: Number of values at this bit length + 1. (1 - 16) + Low 4 bits: Bit Length needed to represent value + 1. (1 - 16) + +The Shannon-Fano codes can be constructed from the bit lengths +using the following algorithm: + +1) Sort the Bit Lengths in ascending order, while retaining the + order of the original lengths stored in the file. + +2) Generate the Shannon-Fano trees: + + Code <- 0 + CodeIncrement <- 0 + LastBitLength <- 0 + i <- number of Shannon-Fano codes - 1 (either 255 or 63) + + loop while i >= 0 + Code = Code + CodeIncrement + if BitLength(i) <> LastBitLength then + LastBitLength=BitLength(i) + CodeIncrement = 1 shifted left (16 - LastBitLength) + ShannonCode(i) = Code + i <- i - 1 + end loop + +3) Reverse the order of all the bits in the above ShannonCode() + vector, so that the most significant bit becomes the least + significant bit. For example, the value 0x1234 (hex) would + become 0x2C48 (hex). + +4) Restore the order of Shannon-Fano codes as originally stored + within the file. + +Example: + + This example will show the encoding of a Shannon-Fano tree + of size 8. Notice that the actual Shannon-Fano trees used + for Imploding are either 64 or 256 entries in size. + +Example: 0x02, 0x42, 0x01, 0x13 + + The first byte indicates 3 values in this table. Decoding the + bytes: + 0x42 = 5 codes of 3 bits long + 0x01 = 1 code of 2 bits long + 0x13 = 2 codes of 4 bits long + + This would generate the original bit length array of: + (3, 3, 3, 3, 3, 2, 4, 4) + + There are 8 codes in this table for the values 0 thru 7. Using + the algorithm to obtain the Shannon-Fano codes produces: + + Reversed Order Original +Val Sorted Constructed Code Value Restored Length +--- ------ ----------------- -------- -------- ------ +0: 2 1100000000000000 11 101 3 +1: 3 1010000000000000 101 001 3 +2: 3 1000000000000000 001 110 3 +3: 3 0110000000000000 110 010 3 +4: 3 0100000000000000 010 100 3 +5: 3 0010000000000000 100 11 2 +6: 4 0001000000000000 1000 1000 4 +7: 4 0000000000000000 0000 0000 4 + +The values in the Val, Order Restored and Original Length columns +now represent the Shannon-Fano encoding tree that can be used for +decoding the Shannon-Fano encoded data. How to parse the +variable length Shannon-Fano values from the data stream is beyond +the scope of this document. (See the references listed at the end of +this document for more information.) However, traditional decoding +schemes used for Huffman variable length decoding, such as the +Greenlaw algorithm, can be successfully applied. + +The compressed data stream begins immediately after the +compressed Shannon-Fano data. The compressed data stream can be +interpreted as follows: + +loop until done + read 1 bit from input stream. + + if this bit is non-zero then (encoded data is literal data) + if Literal Shannon-Fano tree is present + read and decode character using Literal Shannon-Fano tree. + otherwise + read 8 bits from input stream. + copy character to the output stream. + otherwise (encoded data is sliding dictionary match) + if 8K dictionary size + read 7 bits for offset Distance (lower 7 bits of offset). + otherwise + read 6 bits for offset Distance (lower 6 bits of offset). + + using the Distance Shannon-Fano tree, read and decode the + upper 6 bits of the Distance value. + + using the Length Shannon-Fano tree, read and decode + the Length value. + + Length <- Length + Minimum Match Length + + if Length = 63 + Minimum Match Length + read 8 bits from the input stream, + add this value to Length. + + move backwards Distance+1 bytes in the output stream, and + copy Length characters from this position to the output + stream. (if this position is before the start of the output + stream, then assume that all the data before the start of + the output stream is filled with zeros). +end loop + +IX. Tokenizing - Method 7 +------------------------- + +This method is not used by PKZIP. + +X. Deflating - Method 8 +----------------------- + +The Deflate algorithm is similar to the Implode algorithm using +a sliding dictionary of up to 32K with secondary compression +from Huffman/Shannon-Fano codes. + +The compressed data is stored in blocks with a header describing +the block and the Huffman codes used in the data block. The header +format is as follows: + + Bit 0: Last Block bit This bit is set to 1 if this is the last + compressed block in the data. + Bits 1-2: Block type + 00 (0) - Block is stored - All stored data is byte aligned. + Skip bits until next byte, then next word = block + length, followed by the ones compliment of the block + length word. Remaining data in block is the stored + data. + + 01 (1) - Use fixed Huffman codes for literal and distance codes. + Lit Code Bits Dist Code Bits + --------- ---- --------- ---- + 0 - 143 8 0 - 31 5 + 144 - 255 9 + 256 - 279 7 + 280 - 287 8 + + Literal codes 286-287 and distance codes 30-31 are + never used but participate in the huffman construction. + + 10 (2) - Dynamic Huffman codes. (See expanding Huffman codes) + + 11 (3) - Reserved - Flag a "Error in compressed data" if seen. + +Expanding Huffman Codes +----------------------- +If the data block is stored with dynamic Huffman codes, the Huffman +codes are sent in the following compressed format: + + 5 Bits: # of Literal codes sent - 256 (256 - 286) + All other codes are never sent. + 5 Bits: # of Dist codes - 1 (1 - 32) + 4 Bits: # of Bit Length codes - 3 (3 - 19) + +The Huffman codes are sent as bit lengths and the codes are built as +described in the implode algorithm. The bit lengths themselves are +compressed with Huffman codes. There are 19 bit length codes: + + 0 - 15: Represent bit lengths of 0 - 15 + 16: Copy the previous bit length 3 - 6 times. + The next 2 bits indicate repeat length (0 = 3, ... ,3 = 6) + Example: Codes 8, 16 (+2 bits 11), 16 (+2 bits 10) will + expand to 12 bit lengths of 8 (1 + 6 + 5) + 17: Repeat a bit length of 0 for 3 - 10 times. (3 bits of length) + 18: Repeat a bit length of 0 for 11 - 138 times (7 bits of length) + +The lengths of the bit length codes are sent packed 3 bits per value +(0 - 7) in the following order: + + 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 + +The Huffman codes should be built as described in the Implode algorithm +except codes are assigned starting at the shortest bit length, i.e. the +shortest code should be all 0's rather than all 1's. Also, codes with +a bit length of zero do not participate in the tree construction. The +codes are then used to decode the bit lengths for the literal and +distance tables. + +The bit lengths for the literal tables are sent first with the number +of entries sent described by the 5 bits sent earlier. There are up +to 286 literal characters; the first 256 represent the respective 8 +bit character, code 256 represents the End-Of-Block code, the remaining +29 codes represent copy lengths of 3 thru 258. There are up to 30 +distance codes representing distances from 1 thru 32k as described +below. + + Length Codes + ------------ + Extra Extra Extra Extra + Code Bits Length Code Bits Lengths Code Bits Lengths Code Bits Length(s) + ---- ---- ------ ---- ---- ------- ---- ---- ------- ---- ---- --------- + 257 0 3 265 1 11,12 273 3 35-42 281 5 131-162 + 258 0 4 266 1 13,14 274 3 43-50 282 5 163-194 + 259 0 5 267 1 15,16 275 3 51-58 283 5 195-226 + 260 0 6 268 1 17,18 276 3 59-66 284 5 227-257 + 261 0 7 269 2 19-22 277 4 67-82 285 0 258 + 262 0 8 270 2 23-26 278 4 83-98 + 263 0 9 271 2 27-30 279 4 99-114 + 264 0 10 272 2 31-34 280 4 115-130 + + Distance Codes + -------------- + Extra Extra Extra Extra + Code Bits Dist Code Bits Dist Code Bits Distance Code Bits Distance + ---- ---- ---- ---- ---- ------ ---- ---- -------- ---- ---- -------- + 0 0 1 8 3 17-24 16 7 257-384 24 11 4097-6144 + 1 0 2 9 3 25-32 17 7 385-512 25 11 6145-8192 + 2 0 3 10 4 33-48 18 8 513-768 26 12 8193-12288 + 3 0 4 11 4 49-64 19 8 769-1024 27 12 12289-16384 + 4 1 5,6 12 5 65-96 20 9 1025-1536 28 13 16385-24576 + 5 1 7,8 13 5 97-128 21 9 1537-2048 29 13 24577-32768 + 6 2 9-12 14 6 129-192 22 10 2049-3072 + 7 2 13-16 15 6 193-256 23 10 3073-4096 + +The compressed data stream begins immediately after the +compressed header data. The compressed data stream can be +interpreted as follows: + +do + read header from input stream. + + if stored block + skip bits until byte aligned + read count and 1's compliment of count + copy count bytes data block + otherwise + loop until end of block code sent + decode literal character from input stream + if literal < 256 + copy character to the output stream + otherwise + if literal = end of block + break from loop + otherwise + decode distance from input stream + + move backwards distance bytes in the output stream, and + copy length characters from this position to the output + stream. + end loop +while not last block + +if data descriptor exists + skip bits until byte aligned + read crc and sizes +endif + +XI. Enhanced Deflating - Method 9 +--------------------------------- + +The Enhanced Deflating algorithm is similar to Deflate but +uses a sliding dictionary of up to 64K. Deflate64(tm) is supported +by the Deflate extractor. + +XII. BZIP2 - Method 12 +---------------------- + +BZIP2 is an open-source data compression algorithm developed by +Julian Seward. Information and source code for this algorithm +can be found on the internet. + +XIII. LZMA - Method 14 (EFS) +---------------------------- + +LZMA is a block-oriented, general purpose data compression algorithm +developed and maintained by Igor Pavlov. It is a derivative of LZ77 +that utilizes Markov chains and a range coder. Information and +source code for this algorithm can be found on the internet. Consult +with the author of this algorithm for information on terms or +restrictions on use. + +Support for LZMA within the ZIP format is defined as follows: + +The Compression method field within the ZIP Local and Central +Header records will be set to the value 14 to indicate data was +compressed using LZMA. + +The Version needed to extract field within the ZIP Local and +Central Header records will be set to 6.3 to indicate the +minimum ZIP format version supporting this feature. + +File data compressed using the LZMA algorithm must be placed +immediately following the Local Header for the file. If a +standard ZIP encryption header is required, it will follow +the Local Header and will precede the LZMA compressed file +data segment. The location of LZMA compressed data segment +within the ZIP format will be as shown: + + [local header file 1] + [encryption header file 1] + [LZMA compressed data segment for file 1] + [data descriptor 1] + [local header file 2] + +The encryption header and data descriptor records may +be conditionally present. The LZMA Compressed Data Segment +will consist of an LZMA Properties Header followed by the +LZMA Compressed Data as shown: + + [LZMA properties header for file 1] + [LZMA compressed data for file 1] + +The LZMA Compressed Data will be stored as provided by the +LZMA compression library. Compressed size, uncompressed +size and other file characteristics about the file being +compressed must be stored in standard ZIP storage format. + +The LZMA Properties Header will store specific data required to +decompress the LZMA compressed Data. This data is set by the +LZMA compression engine using the function WriteCoderProperties() +as documented within the LZMA SDK. + +Storage fields for the property information within the LZMA +Properties Header are as follows: + + LZMA Version Information 2 bytes + LZMA Properties Size 2 bytes + LZMA Properties Data variable, defined by "LZMA Properties Size" + +LZMA Version Information - this field identifies which version of + the LZMA SDK was used to compress a file. The first byte will + store the major version number of the LZMA SDK and the second + byte will store the minor number. + +LZMA Properties Size - this field defines the size of the remaining + property data. Typically this size should be determined by the + version of the SDK. This size field is included as a convenience + and to help avoid any ambiguity should it arise in the future due + to changes in this compression algorithm. + +LZMA Property Data - this variable sized field records the required + values for the decompressor as defined by the LZMA SDK. The + data stored in this field should be obtained using the + WriteCoderProperties() in the version of the SDK defined by + the "LZMA Version Information" field. + +The layout of the "LZMA Properties Data" field is a function of the +LZMA compression algorithm. It is possible that this layout may be +changed by the author over time. The data layout in version 4.32 +of the LZMA SDK defines a 5 byte array that uses 4 bytes to store +the dictionary size in little-endian order. This is preceded by a +single packed byte as the first element of the array that contains +the following fields: + + PosStateBits + LiteralPosStateBits + LiteralContextBits + +Refer to the LZMA documentation for a more detailed explanation of +these fields. + +Data compressed with method 14, LZMA, may include an end-of-stream +(EOS) marker ending the compressed data stream. This marker is not +required, but its use is highly recommended to facilitate processing +and implementers should include the EOS marker whenever possible. +When the EOS marker is used, general purpose bit 1 must be set. If +general purpose bit 1 is not set, the EOS marker is not present. + +XIV. PPMd - Method 98 +--------------------- + +PPMd is a data compression algorithm developed by Dmitry Shkarin +which includes a carryless rangecoder developed by Dmitry Subbotin. +This algorithm is based on predictive phrase matching on multiple +order contexts. Information and source code for this algorithm +can be found on the internet. Consult with the author of this +algorithm for information on terms or restrictions on use. + +Support for PPMd within the ZIP format currently is provided only +for version I, revision 1 of the algorithm. Storage requirements +for using this algorithm are as follows: + +Parameters needed to control the algorithm are stored in the two +bytes immediately preceding the compressed data. These bytes are +used to store the following fields: + +Model order - sets the maximum model order, default is 8, possible + values are from 2 to 16 inclusive + +Sub-allocator size - sets the size of sub-allocator in MB, default is 50, + possible values are from 1MB to 256MB inclusive + +Model restoration method - sets the method used to restart context + model at memory insufficiency, values are: + + 0 - restarts model from scratch - default + 1 - cut off model - decreases performance by as much as 2x + 2 - freeze context tree - not recommended + +An example for packing these fields into the 2 byte storage field is +illustrated below. These values are stored in Intel low-byte/high-byte +order. + +wPPMd = (Model order - 1) + + ((Sub-allocator size - 1) << 4) + + (Model restoration method << 12) + + +XV. Traditional PKWARE Encryption +--------------------------------- + +The following information discusses the decryption steps +required to support traditional PKWARE encryption. This +form of encryption is considered weak by today's standards +and its use is recommended only for situations with +low security needs or for compatibility with older .ZIP +applications. + +Decryption +---------- + +PKWARE is grateful to Mr. Roger Schlafly for his expert contribution +towards the development of PKWARE's traditional encryption. + +PKZIP encrypts the compressed data stream. Encrypted files must +be decrypted before they can be extracted. + +Each encrypted file has an extra 12 bytes stored at the start of +the data area defining the encryption header for that file. The +encryption header is originally set to random values, and then +itself encrypted, using three, 32-bit keys. The key values are +initialized using the supplied encryption password. After each byte +is encrypted, the keys are then updated using pseudo-random number +generation techniques in combination with the same CRC-32 algorithm +used in PKZIP and described elsewhere in this document. + +The following is the basic steps required to decrypt a file: + +1) Initialize the three 32-bit keys with the password. +2) Read and decrypt the 12-byte encryption header, further + initializing the encryption keys. +3) Read and decrypt the compressed data stream using the + encryption keys. + +Step 1 - Initializing the encryption keys +----------------------------------------- + +Key(0) <- 305419896 +Key(1) <- 591751049 +Key(2) <- 878082192 + +loop for i <- 0 to length(password)-1 + update_keys(password(i)) +end loop + +Where update_keys() is defined as: + +update_keys(char): + Key(0) <- crc32(key(0),char) + Key(1) <- Key(1) + (Key(0) & 000000ffH) + Key(1) <- Key(1) * 134775813 + 1 + Key(2) <- crc32(key(2),key(1) >> 24) +end update_keys + +Where crc32(old_crc,char) is a routine that given a CRC value and a +character, returns an updated CRC value after applying the CRC-32 +algorithm described elsewhere in this document. + +Step 2 - Decrypting the encryption header +----------------------------------------- + +The purpose of this step is to further initialize the encryption +keys, based on random data, to render a plaintext attack on the +data ineffective. + +Read the 12-byte encryption header into Buffer, in locations +Buffer(0) thru Buffer(11). + +loop for i <- 0 to 11 + C <- buffer(i) ^ decrypt_byte() + update_keys(C) + buffer(i) <- C +end loop + +Where decrypt_byte() is defined as: + +unsigned char decrypt_byte() + local unsigned short temp + temp <- Key(2) | 2 + decrypt_byte <- (temp * (temp ^ 1)) >> 8 +end decrypt_byte + +After the header is decrypted, the last 1 or 2 bytes in Buffer +should be the high-order word/byte of the CRC for the file being +decrypted, stored in Intel low-byte/high-byte order. Versions of +PKZIP prior to 2.0 used a 2 byte CRC check; a 1 byte CRC check is +used on versions after 2.0. This can be used to test if the password +supplied is correct or not. + +Step 3 - Decrypting the compressed data stream +---------------------------------------------- + +The compressed data stream can be decrypted as follows: + +loop until done + read a character into C + Temp <- C ^ decrypt_byte() + update_keys(temp) + output Temp +end loop + + +XVI. Strong Encryption Specification +------------------------------------ + +The Strong Encryption technology defined in this specification is +covered under a pending patent application. The use or implementation +in a product of certain technological aspects set forth in the current +APPNOTE, including those with regard to strong encryption, patching, +or extended tape operations requires a license from PKWARE. Portions +of this Strong Encryption technology are available for use at no charge. +Contact PKWARE for licensing terms and conditions. Refer to section II +of this APPNOTE (Contacting PKWARE) for information on how to +contact PKWARE. + +Version 5.x of this specification introduced support for strong +encryption algorithms. These algorithms can be used with either +a password or an X.509v3 digital certificate to encrypt each file. +This format specification supports either password or certificate +based encryption to meet the security needs of today, to enable +interoperability between users within both PKI and non-PKI +environments, and to ensure interoperability between different +computing platforms that are running a ZIP program. + +Password based encryption is the most common form of encryption +people are familiar with. However, inherent weaknesses with +passwords (e.g. susceptibility to dictionary/brute force attack) +as well as password management and support issues make certificate +based encryption a more secure and scalable option. Industry +efforts and support are defining and moving towards more advanced +security solutions built around X.509v3 digital certificates and +Public Key Infrastructures(PKI) because of the greater scalability, +administrative options, and more robust security over traditional +password based encryption. + +Most standard encryption algorithms are supported with this +specification. Reference implementations for many of these +algorithms are available from either commercial or open source +distributors. Readily available cryptographic toolkits make +implementation of the encryption features straight-forward. +This document is not intended to provide a treatise on data +encryption principles or theory. Its purpose is to document the +data structures required for implementing interoperable data +encryption within the .ZIP format. It is strongly recommended that +you have a good understanding of data encryption before reading +further. + +The algorithms introduced in Version 5.0 of this specification +include: + + RC2 40 bit, 64 bit, and 128 bit + RC4 40 bit, 64 bit, and 128 bit + DES + 3DES 112 bit and 168 bit + +Version 5.1 adds support for the following: + + AES 128 bit, 192 bit, and 256 bit + + +Version 6.1 introduces encryption data changes to support +interoperability with Smartcard and USB Token certificate storage +methods which do not support the OAEP strengthening standard. + +Version 6.2 introduces support for encrypting metadata by compressing +and encrypting the central directory data structure to reduce information +leakage. Information leakage can occur in legacy ZIP applications +through exposure of information about a file even though that file is +stored encrypted. The information exposed consists of file +characteristics stored within the records and fields defined by this +specification. This includes data such as a files name, its original +size, timestamp and CRC32 value. + +Version 6.3 introduces support for encrypting data using the Blowfish +and Twofish algorithms. These are symmetric block ciphers developed +by Bruce Schneier. Blowfish supports using a variable length key from +32 to 448 bits. Block size is 64 bits. Implementations should use 16 +rounds and the only mode supported within ZIP files is CBC. Twofish +supports key sizes 128, 192 and 256 bits. Block size is 128 bits. +Implementations should use 16 rounds and the only mode supported within +ZIP files is CBC. Information and source code for both Blowfish and +Twofish algorithms can be found on the internet. Consult with the author +of these algorithms for information on terms or restrictions on use. + +Central Directory Encryption provides greater protection against +information leakage by encrypting the Central Directory structure and +by masking key values that are replicated in the unencrypted Local +Header. ZIP compatible programs that cannot interpret an encrypted +Central Directory structure cannot rely on the data in the corresponding +Local Header for decompression information. + +Extra Field records that may contain information about a file that should +not be exposed should not be stored in the Local Header and should only +be written to the Central Directory where they can be encrypted. This +design currently does not support streaming. Information in the End of +Central Directory record, the Zip64 End of Central Directory Locator, +and the Zip64 End of Central Directory records are not encrypted. Access +to view data on files within a ZIP file with an encrypted Central Directory +requires the appropriate password or private key for decryption prior to +viewing any files, or any information about the files, in the archive. + +Older ZIP compatible programs not familiar with the Central Directory +Encryption feature will no longer be able to recognize the Central +Directory and may assume the ZIP file is corrupt. Programs that +attempt streaming access using Local Headers will see invalid +information for each file. Central Directory Encryption need not be +used for every ZIP file. Its use is recommended for greater security. +ZIP files not using Central Directory Encryption should operate as +in the past. + +This strong encryption feature specification is intended to provide for +scalable, cross-platform encryption needs ranging from simple password +encryption to authenticated public/private key encryption. + +Encryption provides data confidentiality and privacy. It is +recommended that you combine X.509 digital signing with encryption +to add authentication and non-repudiation. + + +Single Password Symmetric Encryption Method: +------------------------------------------- + +The Single Password Symmetric Encryption Method using strong +encryption algorithms operates similarly to the traditional +PKWARE encryption defined in this format. Additional data +structures are added to support the processing needs of the +strong algorithms. + +The Strong Encryption data structures are: + +1. General Purpose Bits - Bits 0 and 6 of the General Purpose bit +flag in both local and central header records. Both bits set +indicates strong encryption. Bit 13, when set indicates the Central +Directory is encrypted and that selected fields in the Local Header +are masked to hide their actual value. + + +2. Extra Field 0x0017 in central header only. + + Fields to consider in this record are: + + Format - the data format identifier for this record. The only + value allowed at this time is the integer value 2. + + AlgId - integer identifier of the encryption algorithm from the + following range + + 0x6601 - DES + 0x6602 - RC2 (version needed to extract < 5.2) + 0x6603 - 3DES 168 + 0x6609 - 3DES 112 + 0x660E - AES 128 + 0x660F - AES 192 + 0x6610 - AES 256 + 0x6702 - RC2 (version needed to extract >= 5.2) + 0x6720 - Blowfish + 0x6721 - Twofish + 0x6801 - RC4 + 0xFFFF - Unknown algorithm + + Bitlen - Explicit bit length of key + + 32 - 448 bits + + Flags - Processing flags needed for decryption + + 0x0001 - Password is required to decrypt + 0x0002 - Certificates only + 0x0003 - Password or certificate required to decrypt + + Values > 0x0003 reserved for certificate processing + + +3. Decryption header record preceding compressed file data. + + -Decryption Header: + + Value Size Description + ----- ---- ----------- + IVSize 2 bytes Size of initialization vector (IV) + IVData IVSize Initialization vector for this file + Size 4 bytes Size of remaining decryption header data + Format 2 bytes Format definition for this record + AlgID 2 bytes Encryption algorithm identifier + Bitlen 2 bytes Bit length of encryption key + Flags 2 bytes Processing flags + ErdSize 2 bytes Size of Encrypted Random Data + ErdData ErdSize Encrypted Random Data + Reserved1 4 bytes Reserved certificate processing data + Reserved2 (var) Reserved for certificate processing data + VSize 2 bytes Size of password validation data + VData VSize-4 Password validation data + VCRC32 4 bytes Standard ZIP CRC32 of password validation data + + IVData - The size of the IV should match the algorithm block size. + The IVData can be completely random data. If the size of + the randomly generated data does not match the block size + it should be complemented with zero's or truncated as + necessary. If IVSize is 0,then IV = CRC32 + Uncompressed + File Size (as a 64 bit little-endian, unsigned integer value). + + Format - the data format identifier for this record. The only + value allowed at this time is the integer value 3. + + AlgId - integer identifier of the encryption algorithm from the + following range + + 0x6601 - DES + 0x6602 - RC2 (version needed to extract < 5.2) + 0x6603 - 3DES 168 + 0x6609 - 3DES 112 + 0x660E - AES 128 + 0x660F - AES 192 + 0x6610 - AES 256 + 0x6702 - RC2 (version needed to extract >= 5.2) + 0x6720 - Blowfish + 0x6721 - Twofish + 0x6801 - RC4 + 0xFFFF - Unknown algorithm + + Bitlen - Explicit bit length of key + + 32 - 448 bits + + Flags - Processing flags needed for decryption + + 0x0001 - Password is required to decrypt + 0x0002 - Certificates only + 0x0003 - Password or certificate required to decrypt + + Values > 0x0003 reserved for certificate processing + + ErdData - Encrypted random data is used to store random data that + is used to generate a file session key for encrypting + each file. SHA1 is used to calculate hash data used to + derive keys. File session keys are derived from a master + session key generated from the user-supplied password. + If the Flags field in the decryption header contains + the value 0x4000, then the ErdData field must be + decrypted using 3DES. If the value 0x4000 is not set, + then the ErdData field must be decrypted using AlgId. + + + Reserved1 - Reserved for certificate processing, if value is + zero, then Reserved2 data is absent. See the explanation + under the Certificate Processing Method for details on + this data structure. + + Reserved2 - If present, the size of the Reserved2 data structure + is located by skipping the first 4 bytes of this field + and using the next 2 bytes as the remaining size. See + the explanation under the Certificate Processing Method + for details on this data structure. + + VSize - This size value will always include the 4 bytes of the + VCRC32 data and will be greater than 4 bytes. + + VData - Random data for password validation. This data is VSize + in length and VSize must be a multiple of the encryption + block size. VCRC32 is a checksum value of VData. + VData and VCRC32 are stored encrypted and start the + stream of encrypted data for a file. + + +4. Useful Tips + +Strong Encryption is always applied to a file after compression. The +block oriented algorithms all operate in Cypher Block Chaining (CBC) +mode. The block size used for AES encryption is 16. All other block +algorithms use a block size of 8. Two ID's are defined for RC2 to +account for a discrepancy found in the implementation of the RC2 +algorithm in the cryptographic library on Windows XP SP1 and all +earlier versions of Windows. It is recommended that zero length files +not be encrypted, however programs should be prepared to extract them +if they are found within a ZIP file. + +A pseudo-code representation of the encryption process is as follows: + +Password = GetUserPassword() +MasterSessionKey = DeriveKey(SHA1(Password)) +RD = CryptographicStrengthRandomData() +For Each File + IV = CryptographicStrengthRandomData() + VData = CryptographicStrengthRandomData() + VCRC32 = CRC32(VData) + FileSessionKey = DeriveKey(SHA1(IV + RD) + ErdData = Encrypt(RD,MasterSessionKey,IV) + Encrypt(VData + VCRC32 + FileData, FileSessionKey,IV) +Done + +The function names and parameter requirements will depend on +the choice of the cryptographic toolkit selected. Almost any +toolkit supporting the reference implementations for each +algorithm can be used. The RSA BSAFE(r), OpenSSL, and Microsoft +CryptoAPI libraries are all known to work well. + + +Single Password - Central Directory Encryption: +----------------------------------------------- + +Central Directory Encryption is achieved within the .ZIP format by +encrypting the Central Directory structure. This encapsulates the metadata +most often used for processing .ZIP files. Additional metadata is stored for +redundancy in the Local Header for each file. The process of concealing +metadata by encrypting the Central Directory does not protect the data within +the Local Header. To avoid information leakage from the exposed metadata +in the Local Header, the fields containing information about a file are masked. + +Local Header: + +Masking replaces the true content of the fields for a file in the Local +Header with false information. When masked, the Local Header is not +suitable for streaming access and the options for data recovery of damaged +archives is reduced. Extra Data fields that may contain confidential +data should not be stored within the Local Header. The value set into +the Version needed to extract field should be the correct value needed to +extract the file without regard to Central Directory Encryption. The fields +within the Local Header targeted for masking when the Central Directory is +encrypted are: + + Field Name Mask Value + ------------------ --------------------------- + compression method 0 + last mod file time 0 + last mod file date 0 + crc-32 0 + compressed size 0 + uncompressed size 0 + file name (variable size) Base 16 value from the + range 1 - 0xFFFFFFFFFFFFFFFF + represented as a string whose + size will be set into the + file name length field + +The Base 16 value assigned as a masked file name is simply a sequentially +incremented value for each file starting with 1 for the first file. +Modifications to a ZIP file may cause different values to be stored for +each file. For compatibility, the file name field in the Local Header +should never be left blank. As of Version 6.2 of this specification, +the Compression Method and Compressed Size fields are not yet masked. +Fields having a value of 0xFFFF or 0xFFFFFFFF for the ZIP64 format +should not be masked. + +Encrypting the Central Directory: + +Encryption of the Central Directory does not include encryption of the +Central Directory Signature data, the Zip64 End of Central Directory +record, the Zip64 End of Central Directory Locator, or the End +of Central Directory record. The ZIP file comment data is never +encrypted. + +Before encrypting the Central Directory, it may optionally be compressed. +Compression is not required, but for storage efficiency it is assumed +this structure will be compressed before encrypting. Similarly, this +specification supports compressing the Central Directory without +requiring that it also be encrypted. Early implementations of this +feature will assume the encryption method applied to files matches the +encryption applied to the Central Directory. + +Encryption of the Central Directory is done in a manner similar to +that of file encryption. The encrypted data is preceded by a +decryption header. The decryption header is known as the Archive +Decryption Header. The fields of this record are identical to +the decryption header preceding each encrypted file. The location +of the Archive Decryption Header is determined by the value in the +Start of the Central Directory field in the Zip64 End of Central +Directory record. When the Central Directory is encrypted, the +Zip64 End of Central Directory record will always be present. + +The layout of the Zip64 End of Central Directory record for all +versions starting with 6.2 of this specification will follow the +Version 2 format. The Version 2 format is as follows: + +The leading fixed size fields within the Version 1 format for this +record remain unchanged. The record signature for both Version 1 +and Version 2 will be 0x06064b50. Immediately following the last +byte of the field known as the Offset of Start of Central +Directory With Respect to the Starting Disk Number will begin the +new fields defining Version 2 of this record. + +New fields for Version 2: + +Note: all fields stored in Intel low-byte/high-byte order. + + Value Size Description + ----- ---- ----------- + Compression Method 2 bytes Method used to compress the + Central Directory + Compressed Size 8 bytes Size of the compressed data + Original Size 8 bytes Original uncompressed size + AlgId 2 bytes Encryption algorithm ID + BitLen 2 bytes Encryption key length + Flags 2 bytes Encryption flags + HashID 2 bytes Hash algorithm identifier + Hash Length 2 bytes Length of hash data + Hash Data (variable) Hash data + +The Compression Method accepts the same range of values as the +corresponding field in the Central Header. + +The Compressed Size and Original Size values will not include the +data of the Central Directory Signature which is compressed or +encrypted. + +The AlgId, BitLen, and Flags fields accept the same range of values +the corresponding fields within the 0x0017 record. + +Hash ID identifies the algorithm used to hash the Central Directory +data. This data does not have to be hashed, in which case the +values for both the HashID and Hash Length will be 0. Possible +values for HashID are: + + Value Algorithm + ------ --------- + 0x0000 none + 0x0001 CRC32 + 0x8003 MD5 + 0x8004 SHA1 + 0x8007 RIPEMD160 + 0x8012 SHA256 + 0x8013 SHA384 + 0x8015 SHA512 + +When the Central Directory data is signed, the same hash algorithm +used to hash the Central Directory for signing should be used. +This is recommended for processing efficiency, however, it is +permissible for any of the above algorithms to be used independent +of the signing process. + +The Hash Data will contain the hash data for the Central Directory. +The length of this data will vary depending on the algorithm used. + +The Version Needed to Extract should be set to 62. + +The value for the Total Number of Entries on the Current Disk will +be 0. These records will no longer support random access when +encrypting the Central Directory. + +When the Central Directory is compressed and/or encrypted, the +End of Central Directory record will store the value 0xFFFFFFFF +as the value for the Total Number of Entries in the Central +Directory. The value stored in the Total Number of Entries in +the Central Directory on this Disk field will be 0. The actual +values will be stored in the equivalent fields of the Zip64 +End of Central Directory record. + +Decrypting and decompressing the Central Directory is accomplished +in the same manner as decrypting and decompressing a file. + +Certificate Processing Method: +----------------------------- + +The Certificate Processing Method of for ZIP file encryption +defines the following additional data fields: + +1. Certificate Flag Values + +Additional processing flags that can be present in the Flags field of both +the 0x0017 field of the central directory Extra Field and the Decryption +header record preceding compressed file data are: + + 0x0007 - reserved for future use + 0x000F - reserved for future use + 0x0100 - Indicates non-OAEP key wrapping was used. If this + this field is set, the version needed to extract must + be at least 61. This means OAEP key wrapping is not + used when generating a Master Session Key using + ErdData. + 0x4000 - ErdData must be decrypted using 3DES-168, otherwise use the + same algorithm used for encrypting the file contents. + 0x8000 - reserved for future use + + +2. CertData - Extra Field 0x0017 record certificate data structure + +The data structure used to store certificate data within the section +of the Extra Field defined by the CertData field of the 0x0017 +record are as shown: + + Value Size Description + ----- ---- ----------- + RCount 4 bytes Number of recipients. + HashAlg 2 bytes Hash algorithm identifier + HSize 2 bytes Hash size + SRList (var) Simple list of recipients hashed public keys + + + RCount This defines the number intended recipients whose + public keys were used for encryption. This identifies + the number of elements in the SRList. + + HashAlg This defines the hash algorithm used to calculate + the public key hash of each public key used + for encryption. This field currently supports + only the following value for SHA-1 + + 0x8004 - SHA1 + + HSize This defines the size of a hashed public key. + + SRList This is a variable length list of the hashed + public keys for each intended recipient. Each + element in this list is HSize. The total size of + SRList is determined using RCount * HSize. + + +3. Reserved1 - Certificate Decryption Header Reserved1 Data: + + Value Size Description + ----- ---- ----------- + RCount 4 bytes Number of recipients. + + RCount This defines the number intended recipients whose + public keys were used for encryption. This defines + the number of elements in the REList field defined below. + + +4. Reserved2 - Certificate Decryption Header Reserved2 Data Structures: + + + Value Size Description + ----- ---- ----------- + HashAlg 2 bytes Hash algorithm identifier + HSize 2 bytes Hash size + REList (var) List of recipient data elements + + + HashAlg This defines the hash algorithm used to calculate + the public key hash of each public key used + for encryption. This field currently supports + only the following value for SHA-1 + + 0x8004 - SHA1 + + HSize This defines the size of a hashed public key + defined in REHData. + + REList This is a variable length of list of recipient data. + Each element in this list consists of a Recipient + Element data structure as follows: + + + Recipient Element (REList) Data Structure: + + Value Size Description + ----- ---- ----------- + RESize 2 bytes Size of REHData + REKData + REHData HSize Hash of recipients public key + REKData (var) Simple key blob + + + RESize This defines the size of an individual REList + element. This value is the combined size of the + REHData field + REKData field. REHData is defined by + HSize. REKData is variable and can be calculated + for each REList element using RESize and HSize. + + REHData Hashed public key for this recipient. + + REKData Simple Key Blob. The format of this data structure + is identical to that defined in the Microsoft + CryptoAPI and generated using the CryptExportKey() + function. The version of the Simple Key Blob + supported at this time is 0x02 as defined by + Microsoft. + +Certificate Processing - Central Directory Encryption: +------------------------------------------------------ + +Central Directory Encryption using Digital Certificates will +operate in a manner similar to that of Single Password Central +Directory Encryption. This record will only be present when there +is data to place into it. Currently, data is placed into this +record when digital certificates are used for either encrypting +or signing the files within a ZIP file. When only password +encryption is used with no certificate encryption or digital +signing, this record is not currently needed. When present, this +record will appear before the start of the actual Central Directory +data structure and will be located immediately after the Archive +Decryption Header if the Central Directory is encrypted. + +The Archive Extra Data record will be used to store the following +information. Additional data may be added in future versions. + +Extra Data Fields: + +0x0014 - PKCS#7 Store for X.509 Certificates +0x0016 - X.509 Certificate ID and Signature for central directory +0x0019 - PKCS#7 Encryption Recipient Certificate List + +The 0x0014 and 0x0016 Extra Data records that otherwise would be +located in the first record of the Central Directory for digital +certificate processing. When encrypting or compressing the Central +Directory, the 0x0014 and 0x0016 records must be located in the +Archive Extra Data record and they should not remain in the first +Central Directory record. The Archive Extra Data record will also +be used to store the 0x0019 data. + +When present, the size of the Archive Extra Data record will be +included in the size of the Central Directory. The data of the +Archive Extra Data record will also be compressed and encrypted +along with the Central Directory data structure. + +Certificate Processing Differences: + +The Certificate Processing Method of encryption differs from the +Single Password Symmetric Encryption Method as follows. Instead +of using a user-defined password to generate a master session key, +cryptographically random data is used. The key material is then +wrapped using standard key-wrapping techniques. This key material +is wrapped using the public key of each recipient that will need +to decrypt the file using their corresponding private key. + +This specification currently assumes digital certificates will follow +the X.509 V3 format for 1024 bit and higher RSA format digital +certificates. Implementation of this Certificate Processing Method +requires supporting logic for key access and management. This logic +is outside the scope of this specification. + +OAEP Processing with Certificate-based Encryption: + +OAEP stands for Optimal Asymmetric Encryption Padding. It is a +strengthening technique used for small encoded items such as decryption +keys. This is commonly applied in cryptographic key-wrapping techniques +and is supported by PKCS #1. Versions 5.0 and 6.0 of this specification +were designed to support OAEP key-wrapping for certificate-based +decryption keys for additional security. + +Support for private keys stored on Smartcards or Tokens introduced +a conflict with this OAEP logic. Most card and token products do +not support the additional strengthening applied to OAEP key-wrapped +data. In order to resolve this conflict, versions 6.1 and above of this +specification will no longer support OAEP when encrypting using +digital certificates. + +Versions of PKZIP available during initial development of the +certificate processing method set a value of 61 into the +version needed to extract field for a file. This indicates that +non-OAEP key wrapping is used. This affects certificate encryption +only, and password encryption functions should not be affected by +this value. This means values of 61 may be found on files encrypted +with certificates only, or on files encrypted with both password +encryption and certificate encryption. Files encrypted with both +methods can safely be decrypted using the password methods documented. + +XVII. Change Process +-------------------- + +In order for the .ZIP file format to remain a viable definition, this +specification should be considered as open for periodic review and +revision. Although this format was originally designed with a +certain level of extensibility, not all changes in technology +(present or future) were or will be necessarily considered in its +design. If your application requires new definitions to the +extensible sections in this format, or if you would like to +submit new data structures, please forward your request to +zipformat@pkware.com. All submissions will be reviewed by the +ZIP File Specification Committee for possible inclusion into +future versions of this specification. Periodic revisions +to this specification will be published to ensure interoperability. +We encourage comments and feedback that may help improve clarity +or content. + +XVIII. Incorporating PKWARE Proprietary Technology into Your Product +-------------------------------------------------------------------- + +PKWARE is committed to the interoperability and advancement of the +.ZIP format. PKWARE offers a free license for certain technological +aspects described above under certain restrictions and conditions. +However, the use or implementation in a product of certain technological +aspects set forth in the current APPNOTE, including those with regard to +strong encryption, patching, or extended tape operations requires a +license from PKWARE. Please contact PKWARE with regard to acquiring +a license. + +XIX. Acknowledgements +---------------------- + +In addition to the above mentioned contributors to PKZIP and PKUNZIP, +I would like to extend special thanks to Robert Mahoney for suggesting +the extension .ZIP for this software. + +XX. References +-------------- + + Fiala, Edward R., and Greene, Daniel H., "Data compression with + finite windows", Communications of the ACM, Volume 32, Number 4, + April 1989, pages 490-505. + + Held, Gilbert, "Data Compression, Techniques and Applications, + Hardware and Software Considerations", John Wiley & Sons, 1987. + + Huffman, D.A., "A method for the construction of minimum-redundancy + codes", Proceedings of the IRE, Volume 40, Number 9, September 1952, + pages 1098-1101. + + Nelson, Mark, "LZW Data Compression", Dr. Dobbs Journal, Volume 14, + Number 10, October 1989, pages 29-37. + + Nelson, Mark, "The Data Compression Book", M&T Books, 1991. + + Storer, James A., "Data Compression, Methods and Theory", + Computer Science Press, 1988 + + Welch, Terry, "A Technique for High-Performance Data Compression", + IEEE Computer, Volume 17, Number 6, June 1984, pages 8-19. + + Ziv, J. and Lempel, A., "A universal algorithm for sequential data + compression", Communications of the ACM, Volume 30, Number 6, + June 1987, pages 520-540. + + Ziv, J. and Lempel, A., "Compression of individual sequences via + variable-rate coding", IEEE Transactions on Information Theory, + Volume 24, Number 5, September 1978, pages 530-536. + + +APPENDIX A - AS/400 Extra Field (0x0065) Attribute Definitions +-------------------------------------------------------------- + +Field Definition Structure: + + a. field length including length 2 bytes + b. field code 2 bytes + c. data x bytes + +Field Code Description + 4001 Source type i.e. CLP etc + 4002 The text description of the library + 4003 The text description of the file + 4004 The text description of the member + 4005 x'F0' or 0 is PF-DTA, x'F1' or 1 is PF_SRC + 4007 Database Type Code 1 byte + 4008 Database file and fields definition + 4009 GZIP file type 2 bytes + 400B IFS code page 2 bytes + 400C IFS Creation Time 4 bytes + 400D IFS Access Time 4 bytes + 400E IFS Modification time 4 bytes + 005C Length of the records in the file 2 bytes + 0068 GZIP two words 8 bytes + +APPENDIX B - z/OS Extra Field (0x0065) Attribute Definitions +------------------------------------------------------------ + +Field Definition Structure: + + a. field length including length 2 bytes + b. field code 2 bytes + c. data x bytes + +Field Code Description + 0001 File Type 2 bytes + 0002 NonVSAM Record Format 1 byte + 0003 Reserved + 0004 NonVSAM Block Size 2 bytes Big Endian + 0005 Primary Space Allocation 3 bytes Big Endian + 0006 Secondary Space Allocation 3 bytes Big Endian + 0007 Space Allocation Type1 byte flag + 0008 Modification Date Retired with PKZIP 5.0 + + 0009 Expiration Date Retired with PKZIP 5.0 + + 000A PDS Directory Block Allocation 3 bytes Big Endian binary value + 000B NonVSAM Volume List variable + 000C UNIT Reference Retired with PKZIP 5.0 + + 000D DF/SMS Management Class 8 bytes EBCDIC Text Value + 000E DF/SMS Storage Class 8 bytes EBCDIC Text Value + 000F DF/SMS Data Class 8 bytes EBCDIC Text Value + 0010 PDS/PDSE Member Info. 30 bytes + 0011 VSAM sub-filetype 2 bytes + 0012 VSAM LRECL 13 bytes EBCDIC "(num_avg num_max)" + 0013 VSAM Cluster Name Retired with PKZIP 5.0 + + 0014 VSAM KSDS Key Information 13 bytes EBCDIC "(num_length num_position)" + 0015 VSAM Average LRECL 5 bytes EBCDIC num_value padded with blanks + 0016 VSAM Maximum LRECL 5 bytes EBCDIC num_value padded with blanks + 0017 VSAM KSDS Key Length 5 bytes EBCDIC num_value padded with blanks + 0018 VSAM KSDS Key Position 5 bytes EBCDIC num_value padded with blanks + 0019 VSAM Data Name 1-44 bytes EBCDIC text string + 001A VSAM KSDS Index Name 1-44 bytes EBCDIC text string + 001B VSAM Catalog Name 1-44 bytes EBCDIC text string + 001C VSAM Data Space Type 9 bytes EBCDIC text string + 001D VSAM Data Space Primary 9 bytes EBCDIC num_value left-justified + 001E VSAM Data Space Secondary 9 bytes EBCDIC num_value left-justified + 001F VSAM Data Volume List variable EBCDIC text list of 6-character Volume IDs + 0020 VSAM Data Buffer Space 8 bytes EBCDIC num_value left-justified + 0021 VSAM Data CISIZE 5 bytes EBCDIC num_value left-justified + 0022 VSAM Erase Flag 1 byte flag + 0023 VSAM Free CI % 3 bytes EBCDIC num_value left-justified + 0024 VSAM Free CA % 3 bytes EBCDIC num_value left-justified + 0025 VSAM Index Volume List variable EBCDIC text list of 6-character Volume IDs + 0026 VSAM Ordered Flag 1 byte flag + 0027 VSAM REUSE Flag 1 byte flag + 0028 VSAM SPANNED Flag 1 byte flag + 0029 VSAM Recovery Flag 1 byte flag + 002A VSAM WRITECHK Flag 1 byte flag + 002B VSAM Cluster/Data SHROPTS 3 bytes EBCDIC "n,y" + 002C VSAM Index SHROPTS 3 bytes EBCDIC "n,y" + 002D VSAM Index Space Type 9 bytes EBCDIC text string + 002E VSAM Index Space Primary 9 bytes EBCDIC num_value left-justified + 002F VSAM Index Space Secondary 9 bytes EBCDIC num_value left-justified + 0030 VSAM Index CISIZE 5 bytes EBCDIC num_value left-justified + 0031 VSAM Index IMBED 1 byte flag + 0032 VSAM Index Ordered Flag 1 byte flag + 0033 VSAM REPLICATE Flag 1 byte flag + 0034 VSAM Index REUSE Flag 1 byte flag + 0035 VSAM Index WRITECHK Flag 1 byte flag Retired with PKZIP 5.0 + + 0036 VSAM Owner 8 bytes EBCDIC text string + 0037 VSAM Index Owner 8 bytes EBCDIC text string + 0038 Reserved + 0039 Reserved + 003A Reserved + 003B Reserved + 003C Reserved + 003D Reserved + 003E Reserved + 003F Reserved + 0040 Reserved + 0041 Reserved + 0042 Reserved + 0043 Reserved + 0044 Reserved + 0045 Reserved + 0046 Reserved + 0047 Reserved + 0048 Reserved + 0049 Reserved + 004A Reserved + 004B Reserved + 004C Reserved + 004D Reserved + 004E Reserved + 004F Reserved + 0050 Reserved + 0051 Reserved + 0052 Reserved + 0053 Reserved + 0054 Reserved + 0055 Reserved + 0056 Reserved + 0057 Reserved + 0058 PDS/PDSE Member TTR Info. 6 bytes Big Endian + 0059 PDS 1st LMOD Text TTR 3 bytes Big Endian + 005A PDS LMOD EP Rec # 4 bytes Big Endian + 005B Reserved + 005C Max Length of records 2 bytes Big Endian + 005D PDSE Flag 1 byte flag + 005E Reserved + 005F Reserved + 0060 Reserved + 0061 Reserved + 0062 Reserved + 0063 Reserved + 0064 Reserved + 0065 Last Date Referenced 4 bytes Packed Hex "yyyymmdd" + 0066 Date Created 4 bytes Packed Hex "yyyymmdd" + 0068 GZIP two words 8 bytes + 0071 Extended NOTE Location 12 bytes Big Endian + 0072 Archive device UNIT 6 bytes EBCDIC + 0073 Archive 1st Volume 6 bytes EBCDIC + 0074 Archive 1st VOL File Seq# 2 bytes Binary + +APPENDIX C - Zip64 Extensible Data Sector Mappings (EFS) +-------------------------------------------------------- + + -Z390 Extra Field: + + The following is the general layout of the attributes for the + ZIP 64 "extra" block for extended tape operations. Portions of + this extended tape processing technology is covered under a + pending patent application. The use or implementation in a + product of certain technological aspects set forth in the + current APPNOTE, including those with regard to strong encryption, + patching or extended tape operations, requires a license from + PKWARE. Please contact PKWARE with regard to acquiring a license. + + + Note: some fields stored in Big Endian format. All text is + in EBCDIC format unless otherwise specified. + + Value Size Description + ----- ---- ----------- + (Z390) 0x0065 2 bytes Tag for this "extra" block type + Size 4 bytes Size for the following data block + Tag 4 bytes EBCDIC "Z390" + Length71 2 bytes Big Endian + Subcode71 2 bytes Enote type code + FMEPos 1 byte + Length72 2 bytes Big Endian + Subcode72 2 bytes Unit type code + Unit 1 byte Unit + Length73 2 bytes Big Endian + Subcode73 2 bytes Volume1 type code + FirstVol 1 byte Volume + Length74 2 bytes Big Endian + Subcode74 2 bytes FirstVol file sequence + FileSeq 2 bytes Sequence + +APPENDIX D - Language Encoding (EFS) +------------------------------------ + +The ZIP format has historically supported only the original IBM PC character +encoding set, commonly referred to as IBM Code Page 437. This limits storing +file name characters to only those within the original MS-DOS range of values +and does not properly support file names in other character encodings, or +languages. To address this limitation, this specification will support the +following change. + +If general purpose bit 11 is unset, the file name and comment should conform +to the original ZIP character encoding. If general purpose bit 11 is set, the +filename and comment must support The Unicode Standard, Version 4.1.0 or +greater using the character encoding form defined by the UTF-8 storage +specification. The Unicode Standard is published by the The Unicode +Consortium (www.unicode.org). UTF-8 encoded data stored within ZIP files +is expected to not include a byte order mark (BOM). + +Applications may choose to supplement this file name storage through the use +of the 0x0008 Extra Field. Storage for this optional field is currently +undefined, however it will be used to allow storing extended information +on source or target encoding that may further assist applications with file +name, or file content encoding tasks. Please contact PKWARE with any +requirements on how this field should be used. + +The 0x0008 Extra Field storage may be used with either setting for general +purpose bit 11. Examples of the intended usage for this field is to store +whether "modified-UTF-8" (JAVA) is used, or UTF-8-MAC. Similarly, other +commonly used character encoding (code page) designations can be indicated +through this field. Formalized values for use of the 0x0008 record remain +undefined at this time. The definition for the layout of the 0x0008 field +will be published when available. Use of the 0x0008 Extra Field provides +for storing data within a ZIP file in an encoding other than IBM Code +Page 437 or UTF-8. + +General purpose bit 11 will not imply any encoding of file content or +password. Values defining character encoding for file content or +password must be stored within the 0x0008 Extended Language Encoding +Extra Field. + + diff --git a/AQDF-Format/afutrainer30.dtd b/AQDF-Format/afutrainer30.dtd new file mode 100644 index 0000000..001a652 --- /dev/null +++ b/AQDF-Format/afutrainer30.dtd @@ -0,0 +1,122 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AQDF-Format/aqdf.dtd b/AQDF-Format/aqdf.dtd new file mode 100644 index 0000000..6b56cdc --- /dev/null +++ b/AQDF-Format/aqdf.dtd @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/AQDF-Format/aqdf.xml b/AQDF-Format/aqdf.xml new file mode 100644 index 0000000..295ed63 --- /dev/null +++ b/AQDF-Format/aqdf.xml @@ -0,0 +1,75 @@ + + + + + + + + + DL-E-2006 + + + Prüfungsfragen im Prüfungsteil Technische Kenntnisse bei Prüfungen zum Erwerb von Amateurfunkzeugnissen der Klasse E + + + Bundesnetzagentur<br>Referat 225<br>Canisiusstraße 21<br>55122 Mainz<br>E-Mail: Poststelle@BNetzA.de<br>Fax: 06131 - 18 5644 + + + 1. Auflage, September 2006 + + + + + + Oliver Saal, DM1OLI<br>http://www.oliver-saal.de/software/afutrainer/<br>Mail:osaal@gmx.de + + + + + + + + + + TA\d\d\d + <...> + + + + Bronze und Messing sind Metalle und damit Leiter. Desweiteren leitet Konstantan und Graphitstaub ebenfalls den elektrischen Strom. + + + + + Zu übermittelnder
BuchstabeSchlüsselwortAussprache des
Schlüsselworts + AAlfaAL FAH + ZZoulouSUH LUH + + Die zu betonenden Silben sind unterstrichen. + ]]> +
+ + + + + + 0,042 A entspricht + 42*10<sup>-3</sup> A. + 42*10<sup>3</sup> A. + 42*10<sup>-2</sup> A. + 42*10<sup>-1</sup> A. + geändert 2006-09-22, 2006-09-23 DF1IAV + www.dj4uf.de/lehrg/a01/a01.html#Abg_Einheiten + + + + + +
\ No newline at end of file diff --git a/Abfragestrategie.odt b/Abfragestrategie.odt new file mode 100644 index 0000000..5570790 Binary files /dev/null and b/Abfragestrategie.odt differ diff --git a/Doxyfile b/Doxyfile new file mode 100644 index 0000000..3292c69 --- /dev/null +++ b/Doxyfile @@ -0,0 +1,261 @@ +# Doxyfile 1.5.1-p1 + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- +PROJECT_NAME = AFUTrainer +PROJECT_NUMBER = 2.2 +OUTPUT_DIRECTORY = D:/projekte/Software/afutrainer/afutrainer-2.2/doc/ +CREATE_SUBDIRS = NO +OUTPUT_LANGUAGE = English +USE_WINDOWS_ENCODING = YES +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +STRIP_FROM_PATH = D:/projekte/Software/afutrainer/afutrainer-2.2/ +STRIP_FROM_INC_PATH = +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +DETAILS_AT_TOP = NO +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 8 +ALIASES = +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +BUILTIN_STL_SUPPORT = NO +DISTRIBUTE_GROUP_DOC = NO +SUBGROUPING = YES +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- +EXTRACT_ALL = NO +EXTRACT_PRIVATE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +HIDE_UNDOC_MEMBERS = YES +HIDE_UNDOC_CLASSES = YES +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = NO +HIDE_SCOPE_NAMES = NO +SHOW_INCLUDE_FILES = YES +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_BY_SCOPE_NAME = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +ENABLED_SECTIONS = +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_DIRECTORIES = NO +FILE_VERSION_FILTER = +#--------------------------------------------------------------------------- +# configuration options related to warning and progress messages +#--------------------------------------------------------------------------- +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_NO_PARAMDOC = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LOGFILE = +#--------------------------------------------------------------------------- +# configuration options related to the input files +#--------------------------------------------------------------------------- +INPUT = D:/projekte/Software/afutrainer/afutrainer-2.2 +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.d \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.idl \ + *.odl \ + *.cs \ + *.php \ + *.php3 \ + *.inc \ + *.m \ + *.mm \ + *.dox \ + *.py +RECURSIVE = NO +EXCLUDE = +EXCLUDE_SYMLINKS = NO +EXCLUDE_PATTERNS = +EXAMPLE_PATH = +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +IMAGE_PATH = +INPUT_FILTER = +FILTER_PATTERNS = +FILTER_SOURCE_FILES = NO +#--------------------------------------------------------------------------- +# configuration options related to source browsing +#--------------------------------------------------------------------------- +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +USE_HTAGS = NO +VERBATIM_HEADERS = NO +#--------------------------------------------------------------------------- +# configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- +ALPHABETICAL_INDEX = NO +COLS_IN_ALPHA_INDEX = 5 +IGNORE_PREFIX = +#--------------------------------------------------------------------------- +# configuration options related to the HTML output +#--------------------------------------------------------------------------- +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_HEADER = +HTML_FOOTER = +HTML_STYLESHEET = +HTML_ALIGN_MEMBERS = YES +GENERATE_HTMLHELP = NO +CHM_FILE = +HHC_LOCATION = +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +DISABLE_INDEX = NO +ENUM_VALUES_PER_LINE = 4 +GENERATE_TREEVIEW = NO +TREEVIEW_WIDTH = 250 +#--------------------------------------------------------------------------- +# configuration options related to the LaTeX output +#--------------------------------------------------------------------------- +GENERATE_LATEX = YES +LATEX_OUTPUT = latex +LATEX_CMD_NAME = latex +MAKEINDEX_CMD_NAME = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4wide +EXTRA_PACKAGES = +LATEX_HEADER = +PDF_HYPERLINKS = NO +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +#--------------------------------------------------------------------------- +# configuration options related to the RTF output +#--------------------------------------------------------------------------- +GENERATE_RTF = YES +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +RTF_STYLESHEET_FILE = +RTF_EXTENSIONS_FILE = +#--------------------------------------------------------------------------- +# configuration options related to the man page output +#--------------------------------------------------------------------------- +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +#--------------------------------------------------------------------------- +# configuration options related to the XML output +#--------------------------------------------------------------------------- +GENERATE_XML = NO +XML_OUTPUT = xml +XML_SCHEMA = +XML_DTD = +XML_PROGRAMLISTING = YES +#--------------------------------------------------------------------------- +# configuration options for the AutoGen Definitions output +#--------------------------------------------------------------------------- +GENERATE_AUTOGEN_DEF = NO +#--------------------------------------------------------------------------- +# configuration options related to the Perl module output +#--------------------------------------------------------------------------- +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +PERLMOD_MAKEVAR_PREFIX = +#--------------------------------------------------------------------------- +# Configuration options related to the preprocessor +#--------------------------------------------------------------------------- +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +INCLUDE_PATH = +INCLUDE_FILE_PATTERNS = +PREDEFINED = +EXPAND_AS_DEFINED = +SKIP_FUNCTION_MACROS = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to external references +#--------------------------------------------------------------------------- +TAGFILES = +GENERATE_TAGFILE = +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +PERL_PATH = /usr/bin/perl +#--------------------------------------------------------------------------- +# Configuration options related to the dot tool +#--------------------------------------------------------------------------- +CLASS_DIAGRAMS = YES +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DOT_IMAGE_FORMAT = png +DOT_PATH = +DOTFILE_DIRS = +MAX_DOT_GRAPH_WIDTH = 1024 +MAX_DOT_GRAPH_HEIGHT = 1024 +MAX_DOT_GRAPH_DEPTH = 1000 +DOT_TRANSPARENT = NO +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES +#--------------------------------------------------------------------------- +# Configuration::additions related to the search engine +#--------------------------------------------------------------------------- +SEARCHENGINE = NO diff --git a/afutrainer.ico b/afutrainer.ico new file mode 100644 index 0000000..99e6a22 Binary files /dev/null and b/afutrainer.ico differ diff --git a/afutrainer.pro b/afutrainer.pro new file mode 100644 index 0000000..bfb55ae --- /dev/null +++ b/afutrainer.pro @@ -0,0 +1,61 @@ +win32 { + TEMPLATE = vcapp +} +else { + TEMPLATE = app +} +TARGET = afutrainer +QT += qt gui xml + +HEADERS = mainwindow.h \ + catalogmodel.h \ + questionmodel.h \ + dlglearn.h \ + dlgviewquestion.h \ + question.h \ + chapter.h \ + catalog.h \ + dlglearnassistant.h \ + answer.h \ + helper.h \ + chaptermodel.h \ + osziparchive.h \ + dlginformation.h \ + exam.h \ + dlgexam.h \ + dlgexamselect.h \ + error.h \ + tools.h \ + dlglearnstatistic.h \ + dlgexamstatistic.h \ + plotwidget.h \ + recentfiles.h + +SOURCES = main.cpp \ + mainwindow.cpp \ + catalogmodel.cpp \ + questionmodel.cpp \ + dlglearn.cpp \ + dlgviewquestion.cpp \ + question.cpp \ + chapter.cpp \ + catalog.cpp \ + dlglearnassistant.cpp \ + answer.cpp \ + helper.cpp \ + chaptermodel.cpp \ + osziparchive.cpp \ + dlginformation.cpp \ + exam.cpp \ + dlgexam.cpp \ + dlgexamselect.cpp \ + error.cpp \ + tools.cpp \ + dlglearnstatistic.cpp \ + dlgexamstatistic.cpp \ + plotwidget.cpp \ + recentfiles.cpp + +FORMS = mainwindow.ui dlglearn.ui dlgviewquestion.ui dlglearnassistant.ui dlginformation.ui dlgexamselect.ui dlgexam.ui dlglearnstatistic.ui dlgexamstatistic.ui +RC_FILE = afutrainer.rc +RESOURCES += afutrainer.qrc diff --git a/afutrainer.qrc b/afutrainer.qrc new file mode 100644 index 0000000..b451c62 --- /dev/null +++ b/afutrainer.qrc @@ -0,0 +1,36 @@ + + + icons/16x16/book1.png + icons/16x16/button_ok.png + icons/16x16/button_cancel.png + icons/16x16/cancel.png + icons/16x16/clock.png + icons/16x16/contexthelp.png + icons/16x16/exit.png + icons/16x16/filenew.png + icons/16x16/fileopen.png + icons/16x16/filesave.png + icons/16x16/filesaveas.png + icons/16x16/finish.png + icons/16x16/folder.png + icons/16x16/idea.png + icons/16x16/idea_gray.png + icons/16x16/idea_info.png + icons/16x16/info.png + icons/16x16/level0.png + icons/16x16/level1.png + icons/16x16/level2.png + icons/16x16/level3.png + icons/16x16/level4.png + icons/16x16/level5.png + icons/16x16/next.png + icons/16x16/previous.png + icons/16x16/start.png + icons/16x16/stats.png + icons/16x16/stop.png + icons/16x16/viewmag.png + icons/32x32/idea.png + icons/32x32/qt.png + translations/qt_de.qm + + diff --git a/afutrainer.rc b/afutrainer.rc new file mode 100644 index 0000000..af2ca0e --- /dev/null +++ b/afutrainer.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "afutrainer.ico" diff --git a/answer.cpp b/answer.cpp new file mode 100644 index 0000000..67602b4 --- /dev/null +++ b/answer.cpp @@ -0,0 +1,119 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "answer.h" + +#include + + +CAnswer::CAnswer(const QString& strText, const bool bCorrect) +{ + m_strText = strText; + m_bIsCorrect = bCorrect; +} + +void CAnswer::clear() +{ + m_strText.clear(); + m_bIsCorrect = false; +} + +QString CAnswerClicked::tr (const char *sourceText, const char *comment) +{ + return QCoreApplication::translate("CAnswerClicked", sourceText, comment); +} + +void CAnswerClicked::clear() +{ + m_dt = QDateTime::currentDateTime(); + m_uAnswer=0; + m_uNeededTime=0; +} + +bool CAnswerClicked::operator < (const CAnswerClicked& ac) const +{ + return m_dt < ac.m_dt; +} + +QString CAnswerClicked::answerText(const unsigned uAnswer) +{ +QString strRet; + for (int i=0; i<32; i++) + { + if (uAnswer & (1<& listAnswer) const +{ +unsigned uMask=0; + for (int i=0; i +#include +#include +#include + +//! Die Klasse CAnswer speichert eine einzelne Antwort auf eine Frage +/*! +Das Schreiben und Lesen von XML-Daten wird von der Klasse CQuestion übernommen +*/ + +class CAnswer +{ +public: + //! Standard-Konstruktor + /*! Initialisiert die Klasse, indem die Funktion clear() aufgerufen wird. */ + CAnswer() { clear(); } + + //! Konstruktor inkl. setzen eines Textes und ob die Antwort richtig ist + /*! + \param strText Antworttext + \param bCorrect true: Die Antwort ist korrekt + \sa m_strText, m_bIsCorrect + */ + CAnswer(const QString& strText, const bool bCorrect); + + //! Standard-Destruktor + /*! In der Standard-Implementierung tut der Destruktor nichts. */ + ~CAnswer() {} + + //! Zurücksetzen aller Werte + /*! Es werden alle Daten der Antwort gelöscht. */ + void clear(); + //! Überprüfen, ob die Klasse eine Antwort enthält + /*! \return true: Die Klasse ist leer und enthält keine Antwort */ + inline bool isEmpty() const { return m_strText.isEmpty(); } + + //! Auslesen, ob diese Antwort richtig ist + /*! \return true: Die Antwort ist richtig */ + inline bool isCorrect() const { return m_bIsCorrect; } + //! Auslesen des Antworttextes + /*! \return Antworttext */ + inline QString text() const { return m_strText; } + //! Setzen, ob diese Antwort richtig ist + /*! + \param bCorrect true: Die Antwort ist korrekt + \sa m_bIsCorrect + */ + inline void setCorrect (const bool bCorrect) { m_bIsCorrect = bCorrect; } + //! Setzen des Antworttextes + /*! + \param strText Antworttext + \sa m_strText + */ + inline void setText(const QString& strText) { m_strText = strText; } + + //! Anhängen eines Textes an den Antworttext + /*! + \param strText Anzuhängender Text + \sa m_strText + */ + inline void appendText(const QString& strText) { m_strText += strText; } + +protected: + //! Antworttext + /*! + Der Antworttext darf HTML-Code und HTML-Verweise auf Grafiken/Bilder enthalten. Bei Verweisen ist zu achten, dass kein Verzeichnis angegeben werden darf! + + Default: leer + */ + QString m_strText; + //! Richtige oder falsche Antwort + /*! Default: false */ + bool m_bIsCorrect; +}; + +//! Die Klasse CAnswerClicked speichert eine einzelne Beantwortung des Benutzers +/*! +*/ + +class CAnswerClicked +{ +public: + //! Standard-Konstruktor + /*! Initialisiert die Klasse, indem die Funktion clear() aufgerufen wird. */ + CAnswerClicked () { clear(); } + CAnswerClicked (const unsigned uAnswer, const unsigned uNeededTime) + { m_uAnswer = uAnswer; m_dt = QDateTime::currentDateTime(); m_uNeededTime = uNeededTime; } + //! Standard-Destruktor + /*! In der Standard-Implementierung tut der Destruktor nichts. */ + ~CAnswerClicked () {} + + //! Zurücksetzen aller Werte + /*! Es werden alle Daten auf Default-Werte zurückgesetzt. */ + void clear(); + + inline QDateTime dateTime() const { return m_dt; } + inline unsigned neededTime() const { return m_uNeededTime; } + inline unsigned answer() const { return m_uAnswer; } + static QString answerText(const unsigned uAnswer); + QString answerText() const; + QString neededTimeText() const; + + bool isCorrect(const QList& listAnswer) const; + + bool load (QDomElement elem); + void save (QDomElement& parent, QDomDocument& doc) const; + + static QString tr (const char *sourceText, const char *comment=0); + + bool operator < (const CAnswerClicked& ac) const; + +protected: + QDateTime m_dt; //!< Zeitstempel der Antwort + unsigned m_uAnswer; //!< Antwort-Bitmaske + unsigned m_uNeededTime; //!< Benötigte Zeit in ms +}; + +#endif diff --git a/catalog.cpp b/catalog.cpp new file mode 100644 index 0000000..4ff7771 --- /dev/null +++ b/catalog.cpp @@ -0,0 +1,433 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "catalog.h" +#include "osziparchive.h" +#include "tools.h" + +#include +#include +#include +#include +#include + +CCatalog::~CCatalog() +{ + qDeleteAll(m_listFiles); +} + +void CCatalog::clear() +{ + CChapter::clear(); + m_bMixAnswers = true; + m_bSort = false; + m_strFileName.clear(); + m_listHint.clear(); + m_strUniqueName.clear(); + qDeleteAll(m_listFiles); + m_strPublisher.clear(); + m_strContact.clear(); + m_dateValidFrom = QDate(); + m_dateValidUntil = QDate(); + m_dateCreated = QDate(); + m_datePublished = QDate(); + m_strVersion.clear(); + m_listExam.clear(); + m_listExamStat.clear(); +} + +bool CCatalog::isEmpty() +{ + return m_strText.isEmpty(); +} + +bool CCatalog::isValid() const +{ + if (m_dateValidFrom.isValid() && QDate::currentDate() < m_dateValidFrom) return false; + if (m_dateValidUntil.isValid() && QDate::currentDate() > m_dateValidUntil) return false; + return true; +} + +bool CCatalog::hasHints (const QString& strQuestionId) const +{ + for (int i=0; itr("Datei-Fehler"), pParent->tr("Konnte folgende Datei nicht zum Lesen öffnen:\n%1").arg(strFileName)); + return false; + } + + // Fragen entpacken + CZipFile *pzfQuestions = zip.findFile("questions.xml"); + if (pzfQuestions == 0) + { + QMessageBox::critical(pParent, pParent->tr("Datei-Fehler"), pParent->tr("Konnte in der Datei '%1' keine Fragen finden.").arg(strFileName)); + return false; + } + strXML = QString::fromUtf8(pzfQuestions->deflateToByteArray()); + + clear(); + + // Alle Grafiken in temp. Dateien entpacken +// pParent->setCursor(Qt::WaitCursor); + for (int i=0; ifileName().right(3); + if (str == "png" || str == "jpg" || str == "gif" || str == "bmp") + { // Datei entpacken und Pfade im XML anpassen + str = QDir::temp().absoluteFilePath(pzf->fileName()+".XXXXXX"); + QTemporaryFile *ptf = new QTemporaryFile(str); + ptf->open(); + str = ptf->fileName(); + pzf->deflateToFile(*ptf); + ptf->close(); + m_listFiles.append(ptf); + strXML.replace(pzf->fileName(), str); + //qDebug ("Deflating %s to %s", qPrintable(pzf->fileName()), qPrintable(str)); + } + } +// pParent->setCursor(Qt::ArrowCursor); + + if (!doc.setContent(strXML, true, &str, &iErrLine, &iErrCol)) + { + QMessageBox::critical(pParent, pParent->tr("XML-Fehler"), pParent->tr("Fragenkatalog: ") + strFileName + "\n" + str + "\n" + QString (pParent->tr("Zeile: %1 Spalte %2")).arg(iErrLine).arg(iErrCol)); + return false; + } + + elemRoot = doc.documentElement (); + if (elemRoot.tagName() != "aqdf") + { + QMessageBox::critical(pParent, pParent->tr("Datei-Fehler"), pParent->tr("Unbekanntes XML-Datenformat '%1'.").arg(elemRoot.tagName())); + return false; + } + //m_strText = elemRoot.attribute ("name"); + m_dateCreated = QDate::fromString(elemRoot.attribute("created"), Qt::ISODate); + dVersion = elemRoot.attribute("version").toDouble(); + if (dVersion != 1.0) + { + QMessageBox::information(pParent, pParent->tr("Information"), pParent->tr("Kann die Dateiversion %1 des Fragenkatalogs '%2' nicht lesen.").arg(dVersion).arg(strFileName)); + return false; + } + + + n = elemRoot.firstChild(); + while (!n.isNull()) + { + if (n.isElement ()) + { + e = n.toElement (); + if (e.tagName() == QString ("chapter")) + { + pChapter = new CChapter(); + if (pChapter->load (e)) + appendChapter(pChapter); + else + delete pChapter; + } + else if (e.tagName() == QString ("exam")) + { + CExam exam; + if (exam.load(e)) m_listExam.append(exam); + } + else if (e.tagName () == "hint") + { + CHint hint; + if (hint.load (e)) + m_listHint.append (hint); + } + else if (e.tagName () == "title") + { + m_strText = e.text(); + m_strUniqueName = e.attribute("unique"); + } + else if (e.tagName () == "comment") + m_strComment = e.text(); + else if (e.tagName () == "contact") + m_strContact = e.text(); + else if (e.tagName () == "publisher") + m_strPublisher = e.text(); + else if (e.tagName() == "valid") + { + m_dateValidFrom = QDate::fromString(e.attribute("from"), Qt::ISODate); + m_dateValidUntil = QDate::fromString(e.attribute("until"), Qt::ISODate); + } + else if (e.tagName() == "version") + { + m_datePublished = QDate::fromString(e.attribute("published"), Qt::ISODate); + m_strVersion = e.text(); + } + else if (e.tagName() == "options") + { + m_bMixAnswers = QVariant(e.attribute("mixanswers", "true")).toBool(); + m_bSort = QVariant(e.attribute("sort", "false")).toBool(); + } + } + n = n.nextSibling(); + } + + if (m_strUniqueName.isEmpty()) + m_strUniqueName = QDir(strFileName).dirName(); + + if (m_bSort) sortAll(); + + loadStatistic (pParent); + updateStatistic(); + + m_strFileName = strFileName; + return true; +} + +bool CCatalog::save (const QString& strFileName, QWidget *pParent) +{ +QFile file (strFileName); + + if (strFileName.isEmpty()) return false; + if (!file.open(QIODevice::WriteOnly)) + { + QMessageBox::critical(pParent, QString(pParent->tr("Datei-Fehler")), pParent->tr("Konnte folgende Datei nicht zum Schreiben öffnen:\n")+strFileName); + return false; + } + QTextStream out(&file); + + QDomDocument doc("afutrainer"); + QDomElement elemRoot = doc.createElement("afutrainer"); + //elemRoot.setAttribute("name", name()); + elemRoot.setAttribute("version", 3.0); + elemRoot.setAttribute("created", QDate::currentDate().toString(Qt::ISODate)); + doc.appendChild(elemRoot); + + + // save unique name + QDomElement elemTitle = createXmlTextElement("title", name(), doc); + elemTitle.setAttribute("unique", m_strUniqueName); + elemRoot.appendChild (elemTitle); + + // save comment + if (!m_strComment.isEmpty()) + elemRoot.appendChild (createXmlTextElement("comment", m_strComment, doc)); + + // save contact + if (!m_strContact.isEmpty()) + elemRoot.appendChild (createXmlTextElement("contact", m_strContact, doc)); + + // save publisher + if (!m_strPublisher.isEmpty()) + elemRoot.appendChild (createXmlTextElement("publisher", m_strPublisher, doc)); + + // save version + QDomElement elemVersion = createXmlTextElement("version", m_strVersion, doc); + elemVersion.setAttribute("published", m_datePublished.toString(Qt::ISODate)); + elemRoot.appendChild(elemVersion); + + // save dates + if (m_dateValidFrom.isValid() || m_dateValidUntil.isValid()) + { + QDomElement elemValid = doc.createElement("valid"); + elemValid.setAttribute("from", m_dateValidFrom.toString(Qt::ISODate)); + elemValid.setAttribute("until", m_dateValidUntil.toString(Qt::ISODate)); + elemRoot.appendChild(elemValid); + } + + // TODO: save tests + + + // save chapters + for (int i=0; isave(elemRoot, doc); + + // save helpers + for (int i=0; itr("Datei-Fehler")), pParent->tr("Konnte folgende Datei nicht zum Lesen öffnen:\n")+statisticFileName()); + return false; + } + QTextStream in (&file); + strXML = in.readAll(); + + if (!doc.setContent(strXML, true, &str, &iErrLine, &iErrCol)) + { + QMessageBox::critical(pParent, pParent->tr("XML-Fehler"), pParent->tr("Statistik zum Fragenkatalog: ") + statisticFileName() + "\n" + str + "\n" + QString (pParent->tr("Zeile: %1 Spalte %2")).arg(iErrLine).arg(iErrCol)); + return false; + } + file.close (); + + elemRoot = doc.documentElement (); + if (doc.doctype().name() != "AFUTrainerStatistics") return false; + if (elemRoot.tagName() != "statistic") return false; + + n = elemRoot.firstChild(); + while (!n.isNull()) + { + if (n.isElement ()) + { + e = n.toElement (); + if (e.tagName() == QString ("learning")) + { + loadLearnStatistic(e); + } + else if (e.tagName() == QString("exams")) + loadExamStatistic(e); + } + n = n.nextSibling(); + } + + return true; +} + +bool CCatalog::saveStatistic(QWidget *pParent) +{ +QDomDocument doc ("AFUTrainerStatistics"); +QString strFileName = statisticFileName(); +QFile file (strFileName); + + if (m_strUniqueName.isEmpty()) return false; + QDomElement elemRoot = doc.createElement ("statistic"); + elemRoot.setAttribute ("name", name()); + elemRoot.setAttribute("version", 2); + elemRoot.setAttribute("date", QDate::currentDate().toString(Qt::ISODate)); + doc.appendChild (elemRoot); + + QDomElement elemLearn = doc.createElement ("learning"); + elemRoot.appendChild (elemLearn); + + saveLearnStatistic(elemLearn, doc); + + QDomElement elemExams = doc.createElement ("exams"); + elemRoot.appendChild (elemExams); + saveExamStatistic(elemExams, doc); + + if (!file.open (QIODevice::WriteOnly)) + { + QMessageBox::critical (pParent, pParent->tr("Fehler"), pParent->tr("Konnte folgende Datei nicht zum Schreiben öffnen.\n")+strFileName); + return false; + } + QTextStream out(&file); + out << doc.toString (); + return true; +} + +bool CCatalog::loadExamStatistic (QDomElement& elemRoot) +{ +QDomNode n; +QDomElement e; + + n = elemRoot.firstChild(); + while (!n.isNull()) + { + if (n.isElement ()) + { + e = n.toElement (); + if (e.tagName() == QString ("exam")) + { + CExamStat es; + if (es.load(e)) m_listExamStat.append(es); + } + } + n = n.nextSibling(); + } + return true; +} + +void CCatalog::saveExamStatistic (QDomElement& parent, QDomDocument& doc) +{ + for (int i=0; i CCatalog::chapters() +{ +QList list; + list.append(this); + list << subChapters(); + return list; +} + +CExam CCatalog::examById(const QString& strId) const +{ + for (int i=0; i +#include + +class CCatalog : public CChapter +{ +public: + CCatalog() : CChapter() { clear(); } + ~CCatalog(); + + void clear(); + bool isEmpty(); + + inline QString name() const { return m_strText; } + inline void setName(const QString& strName) { m_strText = strName; } + + inline QString contact() const { return m_strContact; } + inline void setContact(const QString& strContact) { m_strContact = strContact; } + inline QString publisher() const { return m_strPublisher; } + inline void setPublisher(const QString& strPublisher) { m_strPublisher = strPublisher; } + + // Validation + inline QDate validFrom() const { return m_dateValidFrom; } + inline void setValidFrom(const QDate& date) { m_dateValidFrom = date; } + inline QDate validUntil() const { return m_dateValidUntil; } + inline void setValidUntil(const QDate& date) { m_dateValidUntil = date; } + bool isValid() const; + inline QDate created() const { return m_dateCreated; } + inline void setCreated(const QDate& date) { m_dateCreated = date; } + inline QDate published() const { return m_datePublished; } + inline void setPublished(const QDate& date) { m_datePublished = date; } + inline QString versionText() const { return m_strVersion; } + inline void setVersionText(const QString& str) { m_strVersion = str; } + + // file operations + inline QString fileName() const { return m_strFileName; } + bool load (const QString& strFileName, QWidget *pParent); + bool save (const QString& strFileName, QWidget *pParent); + + bool loadStatistic(QWidget *pParent); + bool saveStatistic(QWidget *pParent); + + // hints + inline int countHint() const { return m_listHint.size(); } + inline const CHint& hintAt(int i) const { return m_listHint.at(i); } + inline CHint& hintAt(int i) { return m_listHint[i]; } + inline void appendHint(const CHint& a) { m_listHint.append(a); } + bool hasHints (const QString& strQuestionId) const; + QString hintText (const QString& strQuestionId) const; + + // exams + inline int countExam() const { return m_listExam.size(); } + inline const CExam& examAt(int i) const { return m_listExam.at(i); } + inline CExam& examAt(int i) { return m_listExam[i]; } + inline void appendExam(const CExam& a) { m_listExam.append(a); } + CExam examById(const QString& strId) const; + + inline void appendExamStat(const CExamStat& a) { m_listExamStat.append(a); } + inline int countExamStat() const { return m_listExamStat.size(); } + inline const CExamStat& examStatAt(int i) const { return m_listExamStat.at(i); } + + // options + inline bool useMixedAnswers() const { return m_bMixAnswers; } + + // others + QList chapters(); + +protected: + QString statisticFileName() const; + bool loadExamStatistic (QDomElement& elemRoot); + void saveExamStatistic (QDomElement& parent, QDomDocument& doc); + +protected: + QString m_strFileName; + QString m_strUniqueName; + QList m_listFiles; + + QList m_listHint; + QString m_strContact; + QString m_strPublisher; + QDate m_dateValidFrom; + QDate m_dateValidUntil; + QDate m_dateCreated; + QDate m_datePublished; + QString m_strVersion; + QList m_listExam; + QList m_listExamStat; + + // Optionen + bool m_bMixAnswers; + bool m_bSort; +}; + +#endif diff --git a/catalogmodel.cpp b/catalogmodel.cpp new file mode 100644 index 0000000..c7e9185 --- /dev/null +++ b/catalogmodel.cpp @@ -0,0 +1,205 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "catalogmodel.h" + +#include "catalog.h" + +#define COL_CHAPTER 0 +#define COL_QUESTION 1 +#define COL_ASSIST 2 +#define COL_AVG 3 + +CCatalogModel::CCatalogModel (QObject *pParent) : QAbstractItemModel (pParent) +{ + m_pCatalog = 0; +} + +CCatalogModel::~CCatalogModel() +{ +} + + +void CCatalogModel::onLanguageChanged() +{ + headerDataChanged(Qt::Horizontal, 0, columnCount()-1); +} + +void CCatalogModel::setModelData (CCatalog *pCatalog) +{ + reset(); + m_pCatalog = pCatalog; + reset(); +} + +int CCatalogModel::columnCount (const QModelIndex& parent) const +{ + Q_UNUSED(parent); + return 4; +} + +QVariant CCatalogModel::headerData (int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Vertical) return QVariant(); + + if (role == Qt::DisplayRole) + { + if (section == COL_CHAPTER) + return tr("Kapitel"); + else if (section == COL_QUESTION) + return tr("Fragen"); + else if (section == COL_AVG) + return tr("Ø"); + else if (section == COL_ASSIST) + return tr("Lernassistent"); + } + else if (role == Qt::ToolTipRole) + { + if (section == COL_CHAPTER) + return tr("Kapitelname\nBitte markieren Sie das Kapitel, das Sie lernen möchten."); + else if (section == COL_QUESTION) + return tr("Anzahl d. Fragen inkl. Unterkapitel"); + else if (section == COL_AVG) + return tr("Durchschnittlicher Lernfortschritt"); + else if (section == COL_ASSIST) + return tr("Empfehlung des Lernassistentes"); + } + else if (role == Qt::WhatsThisRole) + { + + } + + return QVariant(); +} + +QVariant CCatalogModel::data (const QModelIndex& index, int role) const +{ +CChapter *p = (CChapter*) index.internalPointer(); + + if (m_pCatalog == 0 || !index.isValid()) return QVariant(); + + if (role == Qt::TextAlignmentRole && index.column() == COL_QUESTION) return Qt::AlignRight; + + if (p == 0) return QVariant(); + + if (role == Qt::DisplayRole) + { + if (index.column() == COL_CHAPTER) + { + if (p->id().isEmpty()) + return p->text(); + else + return p->id() + " - " + p->text(); + } + else if (index.column() == COL_QUESTION) + return QString("%1").arg(p->countSubQuestion()); + else if (index.column() == COL_ASSIST) + { + return p->recommendationText(); + } + } + else if (role == Qt::DecorationRole) + { + if (index.column() == COL_CHAPTER) + return QIcon(":/icons/16x16/folder.png"); + else if (index.column() == COL_AVG) + { + return p->levelAvgIcon(); + } + else if (index.column() == COL_ASSIST) + { + return p->recommendationIcon(m_pCatalog); + } + } + else if (role == Qt::ToolTipRole) + { + if (index.column() == COL_AVG) + { + return QString ("%1 - Kennzahl: %2").arg(p->/*statistic().*/levelAvgText()).arg(p->/*statistic().*/levelAvg(), 4, 'f', 2); + } + else if (index.column() == COL_ASSIST) + { + return p->recommendationToolTip(); + } + } + + return QVariant(); +} + +bool CCatalogModel::hasChildren (const QModelIndex& parent) const +{ +CChapter *p = (CChapter*) parent.internalPointer(); + + if (m_pCatalog == 0) return false; + + if (parent.isValid() && p) + return (p->countChapter() > 0); + else // root item + return true; +} + +QModelIndex CCatalogModel::index (int row, int column, const QModelIndex& parent) const +{ +CChapter *pParent = (CChapter*) parent.internalPointer(); + + if (!parent.isValid()) + { // root item + return createIndex (row, column, (void*) m_pCatalog); + } + else + { + // take entry from parent item + if (row > pParent->countChapter()) + return QModelIndex(); + return createIndex (row, column, (void*)pParent->chapterAt(row)); + } + return QModelIndex(); +} + +QModelIndex CCatalogModel::parent (const QModelIndex& index) const +{ +CChapter *pIndex = (CChapter*) index.internalPointer(); +int iRow=0; + + if (pIndex == 0 || pIndex->parentChapter() == 0) // root item + return QModelIndex(); // has no parent + + CChapter *pParent = pIndex->parentChapter(); + if (pParent->parentChapter()) + iRow = pParent->parentChapter()->indexOfChapter(pParent); + else // parent is the root item (parent of the parent doesn't exist) + iRow = 0; + + return createIndex (iRow, 0 /*index.column()*/, pParent); +} + +int CCatalogModel::rowCount (const QModelIndex& parent) const +{ +CChapter *pParent = (CChapter*) parent.internalPointer(); + + if (m_pCatalog == 0 || m_pCatalog->isEmpty()) return 0; + + if (!parent.isValid()) // root item + return 1; + + return pParent->countChapter(); +} + diff --git a/catalogmodel.h b/catalogmodel.h new file mode 100644 index 0000000..41ae5af --- /dev/null +++ b/catalogmodel.h @@ -0,0 +1,55 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef CATALOGMODEL_H +#define CATALOGMODEL_H + +#include + +class CCatalog; + +class CCatalogModel : public QAbstractItemModel +{ + Q_OBJECT +public: + CCatalogModel (QObject *pParent=0); + ~CCatalogModel(); + + void setModelData (CCatalog *pCatalog); + +public: + virtual int columnCount ( const QModelIndex & parent = QModelIndex() ) const; + virtual QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const; + virtual bool hasChildren (const QModelIndex & parent = QModelIndex()) const; + virtual QVariant headerData ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const; + virtual QModelIndex index (int row, int column, const QModelIndex & parent = QModelIndex()) const; + virtual QModelIndex parent ( const QModelIndex & index ) const; + virtual int rowCount ( const QModelIndex & parent = QModelIndex() ) const; + +public slots: + void onLanguageChanged(); + +protected: + CCatalog *m_pCatalog; +}; + +#endif + diff --git a/chapter.cpp b/chapter.cpp new file mode 100644 index 0000000..24343a0 --- /dev/null +++ b/chapter.cpp @@ -0,0 +1,808 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "catalog.h" +#include + + +QString CChapter::tr (const char *sourceText, const char *comment) +{ + return QCoreApplication::translate("CChapter", sourceText, comment); +} + +void CChapter::clear() +{ + m_pParentChapter=0; + m_strId.clear(); + m_strText.clear(); + m_strComment.clear(); + m_recom = RecommendationNone; + m_recom2 = RecommendationNone; + qDeleteAll(m_listChapter); + qDeleteAll(m_listQuestion); + m_bHasLearningNew = false; + m_bHasKnownQuestions = false; + m_bHasKnownQuestionsRepeatToday = false; + m_uNeverAskedCount = 0; + for (int i=0; i<=LEVEL_MAX; i++) m_uLevelCount[i] = 0; + for (int i=0; i<=RecommendationMax; i++) m_uRecomCount[i] = 0; +// m_mapExam.clear(); +} + +int CChapter::countSubQuestion() const +{ +int i=0, iRet=0; + for (i=0; icountSubQuestion(); + return (iRet + m_listQuestion.size()); +} + +bool CChapter::load (QDomElement elem) +{ +CChapter *pChapter=0; + + if (elem.tagName () != QString ("chapter")) return false; + if (!elem.hasAttribute ("name")) return false; +// if (pParent != NULL && !elem.hasAttribute ("id")) return false; + m_strId = elem.attribute ("id"); + m_strText = elem.attribute ("name"); + QDomNode n = elem.firstChild(); + while (!n.isNull()) + { + if (n.isElement ()) + { + QDomElement e = n.toElement (); + if (e.tagName() == "comment") + m_strComment = e.text(); + else if (e.tagName() == QString ("chapter")) + { + pChapter = new CChapter(); + if (pChapter->load (e)) + appendChapter(pChapter); + else + delete pChapter; + } + else if (e.tagName() == QString ("question")) + { + CQuestion *pQuestion = new CQuestion(); + if (pQuestion->load (e)) + appendQuestion(pQuestion); + else + delete pQuestion; + } + } + n = n.nextSibling(); + } + updateStatistic(); + return true; +} + +void CChapter::save (QDomElement& parent, QDomDocument& doc) +{ +QDomElement elem = doc.createElement("chapter"); + + elem.setAttribute("name", text()); + elem.setAttribute("id", id()); + + if (!m_strComment.isEmpty()) + { + QDomElement elemComment = doc.createElement("comment"); + QDomText textComment = doc.createTextNode(text()); + elemComment.appendChild(textComment); + elem.appendChild(elemComment); + } + + parent.appendChild(elem); + + // save exams +/* QStringList strl = m_mapExam.keys(); + for (int i=0; isave(elem, doc); + } + + // save questions + for (int i=0; isave(elem, doc); + } +} + +bool CChapter::loadLearnStatistic (QDomElement elem) +{ +QList listQuestionPool = questionPool(); + + if (elem.tagName () != QString ("learning")) return false; + + QDomNode n = elem.firstChild(); + while (!n.isNull()) + { + if (n.isElement ()) + { + QDomElement e = n.toElement (); + if (e.tagName() == QString ("question")) + { + QString strId = e.attribute("id"); + for (int i=0; iid() == strId) + listQuestionPool[i]->loadLearnStatistic(e); + } + } + } + n = n.nextSibling(); + } + updateRecommendation(); + return true; +} + +bool CChapter::saveLearnStatistic (QDomElement& parent, QDomDocument& doc) +{ + // questions from sub-chapters + for (int i=0; isaveLearnStatistic(parent, doc); + } + + // save questions + for (int i=0; isaveLearnStatistic(parent, doc); + } + return true; +} + +QString CChapter::checkForErrors() const +{ +QString str; + + // check chapters + for (int i=0; icheckForErrors(); + } + + // check questions + for (int i=0; icheckForErrors(); + } + return str; +} + +QList CChapter::subChapters() const +{ +QList list; + + for (int i=0; isubChapters(); + } + return list; +} + +QList CChapter::questionPool() const +{ +QList list; + for (int i=0; iquestionPool(); + } + list << m_listQuestion; + return list; +} + +QList CChapter::questionPoolLevel(const unsigned uLevel) const +{ +QList list; +int i; + for (i=0; iquestionPoolLevel(uLevel); + for (i=0; ilevel() == uLevel) + list.append (m_listQuestion.at(i)); + } + return list; +} + +QList CChapter::questionPoolDeepen() const +{ +QList list; +int i; + for (i=0; iquestionPoolDeepen(); + for (i=0; ilevel() == LEVEL_VERYOFTEN && !q->isNeverAsked()) + list.append (q); + } + return list; +} + +QList CChapter::questionPoolRepeat(const QDate d) const +{ +QList list; +int i; + for (i=0; iquestionPoolRepeat(d); + for (i=0; irepeatDate() <= d) + list.append (q); + } + return list; +} + + +void CChapter::updateStatisticCount() +{ +/* for (int i=0; iisLearningNew()) return true; + + for (int i=0; iisLearningNew()) return true; + + return false; +*/ + // Alle Statistiken zurücksetzen + m_uNeverAskedCount = 0; + m_bHasLearningNew = false; + m_bHasKnownQuestions = false; + m_bHasKnownQuestionsRepeatToday = false; + for (int i=0; i<=LEVEL_MAX; i++) m_uLevelCount[i] = 0; + + // Zuerst Statistiken für Unterkapitel berechnen und integrieren + for (int i=0; iupdateStatisticCount(); + for (int j=0; j<=LEVEL_MAX; j++) + m_uLevelCount[j] += p->m_uLevelCount[j]; + m_uNeverAskedCount += p->m_uNeverAskedCount; + if (p->hasLearningNewQuestions()) m_bHasLearningNew = true; + if (p->hasKnownQuestions()) m_bHasKnownQuestions = true; + if (p->hasKnownQuestionsRepeatToday()) m_bHasKnownQuestionsRepeatToday = true; + } + + // Anschließend Statistiken für eigene Fragen neu berechnen und hinzufügen + for (int i=0; iisNeverAsked()) m_uNeverAskedCount++; + m_uLevelCount[q->level()]++; + if (q->isLearningNew()) m_bHasLearningNew = true; + if (q->isKnownQuestion()) m_bHasKnownQuestions = true; + if (q->isKnownQuestion() && q->isRepeatToday()) m_bHasKnownQuestionsRepeatToday = true; + } +} + +void CChapter::updateRecommendationStatistic() +{ + for (int i=0; iupdateRecommendationStatistic(); + for (int j=0; jm_uRecomCount[j]; + } +} + +/* +Berechnet die Statistik für das Kapitel inkl. aller Unterkapitel neu. +*/ +void CChapter::updateStatistic() +{ + updateStatisticCount(); + updateRecommendation(); + updateRecommendationStatistic(); +} + +/* +CRecommendation CChapter::recommendation() const +{ +CRecommendation r; + r.create(this); + return r; +} +*/ +/* +CRecommendationStatistic CChapter::recommendationStatistic() const +{ +CRecommendationStatistic rs; + + for (int i=0; irecommendationStatistic()); + } + //TODO +// rs.append(recommendation()); + return rs; +} +*/ + +QString CChapter::idWithParents() const +{ +CChapter *pParent=m_pParentChapter; +QString str; + str = id(); + while (pParent != 0) + { + str = pParent->id() + str; + pParent = pParent->m_pParentChapter; + } + + return str; +} + +/*! +Das empfohlene Wiederholdatum eines Kapitels das frühestete Datum, +an dem eine Frage des Kapitels oder Unterkapitels wiederholt werden sollte. +\return Datum, wann das Kapitel wiederholt werden soll. +Gibt es kein solches Datum, so wird ein ungültiges Datum (QDate::isValid()) zurückgegeben. +Hinweis: Das Datum kann auch in der Vergangenheit liegen! +*/ +QDate CChapter::repeatDate() const +{ +QDate d; +QList list = questionPool(); + + for (int i=0; irepeatDate(); + if (!dq.isValid()) continue; + if (!d.isValid() || dq < d) d = dq; + } + return d; +} + +/*! +Die Variable m_recomRepeatDate muss up to date sein! + +\return Lernempfehlung dieses Kapitels ohne Berücksichtigung evt. vorhandener ünter- oder übergeordneter Kapitel. + +\sa m_recomRepeatDate +*/ +CChapter::Recommendation CChapter::recommendationIndividual() const +{ + if (!m_recomRepeatDate.isValid()) + return RecommendationLearnNew; + else if (m_recomRepeatDate <= QDate::currentDate()) + { + if (hasLearningNewQuestions() && !hasKnownQuestionsRepeatToday()) + return RecommendationLearnNew; + else + return RecommendationRepeatToday; + } + else + { + if (countNeverAsked() > 0) + return RecommendationLearnNew; + else + return RecommendationRepeatLater; + } +} + +/*! +Aktualisiert die Lernempfehlung für dieses Kapitel und alle Unterkapitel +\sa m_recom +*/ +void CChapter::updateRecommendation() +{ + m_recom = RecommendationNone; + m_recom2 = RecommendationNone; + + m_recomRepeatDate = repeatDate(); /*QDate();*/ + if (m_listChapter.isEmpty() && m_listQuestion.isEmpty()) return; + + if (m_pParentChapter && m_pParentChapter->recommendation() != RecommendationSubChapter) + m_recom = RecommendationParentChapter; + else + { + if (countSubQuestion() > 20) + { // Nur wenn in den Unterkapiteln zusammen mehr als x Fragen sind, zuerst die Unterkapitel einzeln lernen lassen + for (int i=0; ilevelAvg() < LEVEL_NORMAL || p->countNeverAsked() > 0) + m_recom = RecommendationSubChapter; + } + } + + if (m_recom == RecommendationNone) + { + //m_recomRepeatDate = repeatDate(); + m_recom = recommendationIndividual(); + } + } + + // Alternative Empfehlung + if (m_recom == RecommendationParentChapter || m_recom == RecommendationSubChapter) + { + m_recom2 = recommendationIndividual(); + if (m_recom2 != RecommendationLearnNew && m_recom2 != RecommendationRepeatToday) + m_recom2 = RecommendationNone; + } + + // find recommended questions + m_listQuestionRecommended.clear(); + QList list = questionPool(); + for (int i=0; iisNeverAsked() || q->isLearningNew()) + m_listQuestionRecommended.append(q); + } + else if (m_recom == RecommendationRepeatToday || m_recom2 == RecommendationRepeatToday) + { + if (q->isRepeatToday()) + m_listQuestionRecommended.append(q); + } + } + + // update child chapters + for (int i=0; iupdateRecommendation(); + //m_listQuestionRecommended << p->m_listQuestionRecommended; + } +} +/* +bool CChapter::hasRecommendedQuestions() const +{ + return (m_recom == RecommendationLearnNew || m_recom == RecommendationRepeatToday); +} + +QList CChapter::recommendedQuestions() const +{ +QList listRet, list = questionPool(); + + if (m_recom != RecommendationLearnNew && m_recom != RecommendationRepeatToday) return listRet; + + for (int i=0; iisNeverAsked() || q->isLearningNew()) + listRet.append(q); + break; + case RecommendationRepeatToday: + if (q->isRepeatToday()) + listRet.append(q); + break; + } + } + return listRet; +} +*/ + +QString CChapter::recommendationText(const Recommendation r, const QDate dRepeat) +{ +unsigned uDays=0; + switch (r) + { + default: + case RecommendationNone: + return tr("Keine Lernempfehlung"); + case RecommendationSubChapter: + return tr("Zuerst Unterkapitel lernen"); + case RecommendationParentChapter: + return tr("Übergeordnetes Kapitel lernen"); + case RecommendationLearnNew: + return tr("Neue Fragen lernen"); + case RecommendationRepeatToday: + return tr("Heute wiederholen"); + case RecommendationRepeatLater: + uDays = QDate::currentDate().daysTo(dRepeat); + if (uDays == 1) + return tr("Morgen wiederholen"); + else + return tr("In %1 Tagen wiederholen").arg(uDays); + } + return QString(); +} + +QString CChapter::recommendationText() const +{ + return recommendationText(m_recom, m_recomRepeatDate); +} + +QString CChapter::recommendationToolTip() const +{ +QString str = recommendationText(); +unsigned uCount = recommendedQuestionCount(); + if (hasRecommendedQuestions() && m_recom2 == RecommendationNone) + { + str += " "; + if (uCount == 1) + str += tr("(1 Frage)"); + else + str += tr("(%1 Fragen)").arg(uCount); + } + if (m_recom2 != RecommendationNone) + { + str += "\n" + tr("Alternativ: "); + if (m_recom2 == RecommendationLearnNew) + { + if (uCount == 1) + str += tr("1 neue Frage lernen"); + else + str += tr("%1 neue Fragen lernen").arg(uCount); + } + else if (m_recom2 == RecommendationRepeatToday) + { + if (uCount == 1) + str += tr("1 Frage wiederholen"); + else + str += tr("%1 Fragen wiederholen").arg(uCount); + } + else + str += recommendationText (m_recom2, m_recomRepeatDate); + } + return str; +} + +QString CChapter::recommendationTextExtended(const CCatalog *pCatalog) const +{ +unsigned uDays=0; +QString str; + + switch (m_recom) + { + default: + case RecommendationNone: + str = tr("Keine Lernempfehlung"); + break; + case RecommendationSubChapter: + str = tr("Dieses Kapitel enthält Unterkapitel, dessen Fragen Sie noch nicht ausreichend gelernt haben.\nEs wird empohlen in kleinen Etappen zu lernen und damit zuerst die Unterkapitel zu vertiefen."); + break; + case RecommendationParentChapter: + if (levelAvgRounded() >= LEVEL_NORMAL) + str = tr("Sie können die Fragen dieses Kapitels gut beantworten.\n"); + str += tr("Es wird empfohlen, alle Fragen des übergeordneten Kapitels gemischt zusammen zu lernen."); + break; + case RecommendationLearnNew: + if (!isRecommendedNow(pCatalog)) + str = tr("Es gibt andere Kapitel, deren Fragen heute wiederholt werden sollten. Bitte lernen Sie diese Kapitel zuerst."); + else + str = tr("Bitte beantworten Sie alle neuen Fragen mindestens einmal richtig."); + break; + case RecommendationRepeatToday: + str = tr("Bitte lernen Sie alle heute zu wiederholenden Fragen, bis sie eine Lernfortschritts-Stufe höher eingestuft sind."); + break; + case RecommendationRepeatLater: + uDays = QDate::currentDate().daysTo(m_recomRepeatDate); + if (uDays > 1) + str = tr("Die Wiederholung dieses Kapitels ist erst in %1 Tagen geplant.\n").arg(uDays); + else + str = tr("Die Wiederholung dieses Kapitels ist erst für morgen geplant.\n"); + if (pCatalog->m_uRecomCount[RecommendationRepeatToday] > 0) + str += tr("Es gibt andere Kapitel, deren Fragen heute wiederholt werden müssen. Bitte lernen Sie diese Kapitel zuerst."); + else if (pCatalog->m_uRecomCount[RecommendationLearnNew] > 0) + str += tr("Bitte lernen Sie zuerst Kapitel mit neuen Fragen."); + break; + } + + if (hasRecommendedQuestions() && isRecommendedNow(pCatalog)) + str += tr("

Dafür sind noch %1 Fragen zu lernen.").arg(recommendedQuestionCount()); + + return str; +} + +QString CChapter::recommendationTextExtended2(const CCatalog *pCatalog) const +{ +QString str; + + if (m_recom2 == RecommendationLearnNew || (m_recom == RecommendationLearnNew && !isRecommendedNow(pCatalog))) + { + //str = tr("Bitte beantworten Sie alle neuen Fragen mindestens einmal richtig."); + str = tr("Alternativ können Sie jetzt die neuen Fragen dieses Kapitels lernen (%1 Fragen).").arg(recommendedQuestionCount()); + } + else if (m_recom2 == RecommendationRepeatToday) + { + if (m_recom == RecommendationSubChapter) + str = tr("Bitte lernen Sie alle heute zu wiederholenden Fragen, bis sie eine Lernfortschritts-Stufe höher eingestuft sind (%1 Fragen).").arg(recommendedQuestionCount()); + else + str = tr("Alternativ können Sie jetzt die heute zu wiederholenden Fragen dieses Kapitels lernen (%1 Fragen).").arg(recommendedQuestionCount()); + } + + return str; +} + +QString CChapter::recommendationIconName(const Recommendation r, const CCatalog *pCatalog) +{ + switch (r) + { + case RecommendationSubChapter: + return QString(":/icons/16x16/button_cancel.png"); + case RecommendationParentChapter: + return QString(":/icons/16x16/button_ok.png"); + case RecommendationLearnNew: + if (pCatalog->m_uRecomCount[RecommendationRepeatToday] > 0) + return QString(":/icons/16x16/idea_gray.png"); + else + return QString(":/icons/16x16/idea.png"); + case RecommendationRepeatToday: + return QString(":/icons/16x16/idea.png"); + case RecommendationRepeatLater: + return QString(":/icons/16x16/idea_gray.png"); + default: + return QString(); + } +} + +QString CChapter::recommendationIconName(const CCatalog *pCatalog) const +{ + return recommendationIconName(m_recom, pCatalog); +} + +/*! +\return true: Kapitel kann jetzt gelernt werden, +false: Kapitel sollte überhaupt nicht oder erst später gelernt werden +*/ + +bool CChapter::isRecommendedNow(const CCatalog *pCatalog) const +{ + switch (m_recom) + { + case RecommendationSubChapter: + if (pCatalog->m_uRecomCount[RecommendationRepeatToday] > 0) + return true; + break; + case RecommendationRepeatToday: + return true; + case RecommendationLearnNew: + if (pCatalog->m_uRecomCount[RecommendationRepeatToday] == 0) + return true; + break; + default: + break; + } + return false; +} + +/* +\return true: Das Kapitel enthält noch neue Fragen, die gerade (="heute") gelernt werden +bool CChapter::isLearningNew() const +{ + for (int i=0; iisLearningNew()) return true; + + for (int i=0; iisLearningNew()) return true; + + return false; +} +*/ + +CDayStatistic CChapter::dayStatistic (const QDate& date) const +{ +CDayStatistic dsRet, ds; +QList listPool = questionPool(); + + for (int i=0; idayStatistic(date); + dsRet += ds; + } + if (listPool.size() != 0) dsRet.m_dLevel /= listPool.size(); + return dsRet; +} + +CDayStatistic CChapter::completeStatistic() const +{ + return dayStatistic(QDate()); +} + +QDateTime CChapter::firstAnswerClicked() const +{ +QList listPool = questionPool(); +QDateTime dtRet, dt; + + for (int i=0; ifirstClicked(); + if (dt.isNull()) continue; + if (dtRet.isNull() || dt < dtRet) dtRet = dt; + } + return dtRet; +} + +double CChapter::levelAvg() const +{ +double d=0.0; +double dCount=0.0; + + for (int i=0; i<=LEVEL_MAX; i++) + { + d += m_uLevelCount[i] * i; + dCount += m_uLevelCount[i]; + } + if (dCount != 0.0) d /= dCount; + return d; +} + +unsigned CChapter::levelAvgRounded() const +{ + return ((unsigned) (levelAvg()+0.5)); +} + +QString CChapter::levelAvgText() const +{ + return QString("%1").arg(CQuestion::levelText(levelAvgRounded())); +} + +QIcon CChapter::levelAvgIcon() const +{ + return QIcon(CQuestion::levelIconName(levelAvgRounded())); +} + +QPixmap CChapter::levelAvgPixmap() const +{ + return QPixmap(CQuestion::levelIconName(levelAvgRounded())); +} + +static bool chapterLessThan(const CChapter *c1, const CChapter *c2) +{ + return c1->id() < c2->id(); +} + +void CChapter::sortSubChapters(bool bSortQuestions) +{ + if (bSortQuestions) sortQuestions(); + qSort (m_listChapter.begin(), m_listChapter.end(), chapterLessThan); + + for (int i=0; isortSubChapters(bSortQuestions); + } +} + +static bool questionLessThan(const CQuestion *q1, const CQuestion *q2) +{ + return q1->id() < q2->id(); +} + +void CChapter::sortQuestions() +{ + qSort (m_listQuestion.begin(), m_listQuestion.end(), questionLessThan); +} + diff --git a/chapter.h b/chapter.h new file mode 100644 index 0000000..ca7e679 --- /dev/null +++ b/chapter.h @@ -0,0 +1,242 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef CHAPTER_H +#define CHAPTER_H + +#include "question.h" +//#include "recommendation.h" +//#include "chapterstatistic.h" + +#include + +//! Die Klasse CChapter speichert ein Kapitel mit allen Unterkapiteln und Fragen +/*! +*/ + +class CChapter +{ +public: + //! Empfehlung für Kapitel + enum Recommendation + { + RecommendationNone=0, //!< Keine Emfehlung + RecommendationSubChapter=1, //!< Untergeordnetes Kapitel lernen + RecommendationParentChapter=2, //!< Übergeordnetes Kapitel lernen + RecommendationRepeatToday=3, //!< Kapitel heute wiederholen + RecommendationLearnNew=4, //!< Neue Fragen lernen + RecommendationRepeatLater=5, //!< Kapitel später wiederholen + RecommendationMax=6 //!< Für for-Schleifen, etc. + }; + + //! Standard-Konstruktor + /*! Initialisiert die Klasse, indem die Funktion clear() aufgerufen wird. */ + CChapter() { clear(); } + + //! Standard-Destruktor + /*! Es werden alle Unterkapitel und Fragen dieses Kapitels aus dem Speicher gelöscht. */ + ~CChapter() { qDeleteAll(m_listChapter); qDeleteAll(m_listQuestion); } + + //! Zurücksetzen aller Werte + /*! Es werden alle Unterkapitel und Fragen dieses Kapitels aus dem Speicher gelöscht und alle andere Daten auf die Default-Werte zurückgesetzt. */ + void clear(); + + //! Elternkapitel auslesen + /*! + \return Elternkapitel m_pParentChapter + \sa setParentChapter(), m_pParentChapter + */ + inline CChapter* parentChapter() const { return m_pParentChapter; } + + //! Elternkapitel setzen + /*! + Durch diese Funktion wird lediglich die Variable m_pParentChapter gesetzt, jedoch nicht die Statistiken + des Eltern-Kapitels aktualisiert. + + \param pChapter Das zu setzende Elternkapitel dieses Kapitels + \sa parentChapter(), m_pParentChapter + */ + inline void setParentChapter(CChapter *pChapter) { m_pParentChapter = pChapter; } + + //! ID dieses Kapitels auslesen + inline QString id() const { return m_strId; } + //! ID dieses Kapitels zusammengesetzt mit allen IDs der Eltern-Kapitel auslesen + QString idWithParents() const; + //! Kapitelbeschreibung auslesen + inline QString text() const { return m_strText; } + //! Kommentar + inline QString comment() const { return m_strComment; } + + //! ID setzen + inline void setId (const QString& strId) { m_strId = strId; } + //! Kapitelbeschreibung setzen + inline void setText (const QString& strText) { m_strText = strText; } + //! Kommentar setzen + inline void setComment (const QString& strComment) { m_strComment = strComment; } + + //! Anhängen eines Textes an die Kapitelbeschreibung + /*! + \param strText Anzuhängender Text + \sa m_strText + */ + inline void appendText(const QString& strText) { m_strText += strText; } + + + //! Anzahl der Unterkapitel ermitteln + /*! \return Anzahl der Unterkapitel */ + inline int countChapter() const { return m_listChapter.size(); } + inline const CChapter* chapterAt(int i) const { return m_listChapter.at(i); } + inline int indexOfChapter(CChapter* c) const { return m_listChapter.indexOf(c); } + inline void appendChapter(CChapter* c) { m_listChapter.append(c); c->setParentChapter(this); } +// inline void removeChapter(int i) { delete m_listChapter.takeAt(i); } + QList subChapters() const; + inline void sortAll() { sortSubChapters(true); sortQuestions(); } + void sortSubChapters(bool bSortQuestions); + void sortQuestions(); + + //! Anzahl der Fragen dieses Kapitels und aller Unterkapitel ermitteln + /*! \return Anzahl der Fragen */ + int countSubQuestion() const; + //! Anzahl der Fragen dieses Kapitels (ohne Unterkapitel) ermitteln + /*! \return Anzahl der Fragen */ + inline int countQuestion() const { return m_listQuestion.size(); } + inline const CQuestion* questionAt(int i) const { return m_listQuestion.at(i); } + inline CQuestion* questionAt(int i) { return m_listQuestion.at(i); } + inline void appendQuestion(CQuestion* q) { m_listQuestion.append(q); q->setParentChapter(this); } + + QList questionPool() const; + QList questionPoolLevel(const unsigned uLevel) const; + QList questionPoolDeepen() const; + QList questionPoolRepeat(const QDate d=QDate::currentDate()) const; + + + + + bool load (QDomElement elem); + void save (QDomElement& parent, QDomDocument& doc); + bool loadLearnStatistic (QDomElement elem); + bool saveLearnStatistic (QDomElement& parent, QDomDocument& doc); + + QString checkForErrors() const; + + + // Kapitelstatistik auslesen + /* + \return Kapitelstatistik (Variable m_cs) + \sa m_cs + */ +// inline CChapterStatistic statistic() const { return m_cs; } + + + //! @name Funktionen zur Statistik + //@{ + void updateStatistic(); //!< Kapitelstatistik aktualisieren + //! Anzahl der Fragen einer bestimmten Abfragehäufigkeit + inline unsigned countQuestion(const unsigned uLevel) const { return m_uLevelCount[uLevel]; } + double levelAvg() const; //!< Durchschnittlicher Lernfortschritt des Kapitels + unsigned levelAvgRounded() const; //!< Durchschnittlicher, gerundeter Lernfortschritt des Kapitels + QString levelAvgText() const; //!< Durchschnittlicher Lernfortschritt als Text + QIcon levelAvgIcon() const; //!< Icon zum durchschnittlichen Lernfortschritt + QPixmap levelAvgPixmap() const; //!< Pixmap zum durchschnittlichen Lernfortschritt + //! Anzahl der noch nie abgefragen Fragen + inline unsigned countNeverAsked() const { return m_uNeverAskedCount; } + //!< Kapitel inkl. Unterkapitel enthalten Fragen, die gerade gelernt werden + inline bool hasLearningNewQuestions() const { return m_bHasLearningNew; } + inline bool hasKnownQuestions() const { return m_bHasKnownQuestions; } + inline bool hasKnownQuestionsRepeatToday() const { return m_bHasKnownQuestionsRepeatToday; } + + CDayStatistic dayStatistic (const QDate& date) const; // recommendedQuestions() const { return m_listQuestionRecommended; } + //! Kapitel hat Fragen, die beantwortet müssen, um die Lernempfehlung zu erfüllen + inline bool hasRecommendedQuestions() const { return m_listQuestionRecommended.size() > 0; } + //! Anzahl der Fragen, die beantwortet müssen, um die Lernempfehlung zu erfüllen + inline int recommendedQuestionCount() const { return m_listQuestionRecommended.size(); } + //! Alternative Lernempfehlung für das Kapitel + inline Recommendation recommendation2() const { return m_recom2; } + QString recommendationTextExtended2(const CCatalog *pCatalog) const; //!< Alternativer Lernempfehlungstext (ausführlich) + //@} + + //! @name Funktionen zu Prüfungen + //@{ +// inline void setExam(const QString& strId, const unsigned uQuestionCount) { m_mapExam.insert(strId, uQuestionCount); } +// inline unsigned examQuestionCount(const QString& strId) const { return m_mapExam[strId]; } + //@} + + // static + static QString tr (const char *sourceText, const char *comment=0); + static QString recommendationText(const Recommendation r, const QDate dRepeat); + static QString recommendationIconName(const Recommendation r, const CCatalog *pCatalog); + +protected: + void updateStatisticCount(); //!< Zählt alle Fragen des Kapitels (inkl. Unterkapitel) für die Statistik + void updateRecommendation(); //!< Lernempfehlung für das Kapitel (inkl. Unterkapitel) aktualisieren + void updateRecommendationStatistic(); //!< Statistik über Lernempfehlungen für das Kapitel (inkl. Unterkapitel) aktualisieren + Recommendation recommendationIndividual() const; //!< Empfehlung nur für dieses Kapitel, Unterkapitel und Elternkapitel werden ignoriert + +protected: + CChapter *m_pParentChapter; //!< Übergeordnetes Kapitel + QString m_strId; //!< ID des Kapitels + QString m_strText; //!< Name des Kapitels + QString m_strComment; //!< Kommentar + + QList m_listChapter; //!< Liste mit Unterkapiteln + QList m_listQuestion; //!< Liste mit Fragen + + // Statistik + unsigned m_uLevelCount[LEVEL_MAX+1]; //!< Anzahl der Fragen einer bestimmten Abfragehäufigkeit + unsigned m_uNeverAskedCount; //!< Anzahl der Fragen, die noch nie abgefragt wurden + unsigned m_uRecomCount[RecommendationMax]; //!< Anzahl der Unterkapitel (inkl. selbst) mit bestimmter Lernempfehlung + bool m_bHasLearningNew; //!< Kapitel enthält Fragen, die gerade neu gelernt werden + bool m_bHasKnownQuestions; //!< Kapitel enthält Fragen, die seit mind. gestern bekannt sind + bool m_bHasKnownQuestionsRepeatToday; //!< Kapitel enthält Fragen, die seit mind. gestern bekannt sind und heute wiederholt werden sollten + + Recommendation m_recom; //!< Lernempfehlung + Recommendation m_recom2; //!< Alternative Lernempfehlung + + //! Empfohlenes Datum zur Wiederholung der Fragen + /*! Diese Variable wird mit der Funktion updateRecommendation() aktualisiert */ + QDate m_recomRepeatDate; + //! Liste mit Fragen zum Erfüllen der Lernempfehlung + /*! Diese Liste wird mit der Funktion updateRecommendation() aktualisiert */ + QList m_listQuestionRecommended; +}; + +#endif diff --git a/chaptermodel.cpp b/chaptermodel.cpp new file mode 100644 index 0000000..7625aeb --- /dev/null +++ b/chaptermodel.cpp @@ -0,0 +1,184 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "chaptermodel.h" +#include "catalog.h" + +#include + +#define COL_CHAPTER 0 +#define COL_QUESTION 1 +#define COL_ASSIST 2 +#define COL_AVG 3 + +CChapterModel::CChapterModel(QObject *pParent) : QAbstractItemModel (pParent) +{ + m_pCatalog = 0; +} + +CChapterModel::~CChapterModel() +{ +} + +void CChapterModel::onLanguageChanged() +{ + headerDataChanged(Qt::Horizontal, 0, columnCount()-1); +} + +void CChapterModel::clear() +{ + m_listChapter.clear(); + m_pCatalog = 0; + reset(); +} + +void CChapterModel::setModelData (CCatalog *pCatalog, QList listChapter) +{ + m_listChapter = listChapter; + m_pCatalog = pCatalog; + reset(); +} + +int CChapterModel::columnCount (const QModelIndex & parent) const +{ + Q_UNUSED(parent) + return 4; +} + +QVariant CChapterModel::data (const QModelIndex & index, int role) const +{ +CChapter *p = (CChapter*) index.internalPointer(); + + if (m_pCatalog == 0 || !index.isValid()) return QVariant(); + + if (role == Qt::TextAlignmentRole && index.column() == COL_QUESTION) return Qt::AlignRight; + + if (p == 0) return QVariant(); + + if (role == Qt::DisplayRole) + { + if (index.column() == COL_CHAPTER) + { + if (p->id().isEmpty()) + return p->text(); + else + return p->idWithParents() + " - " + p->text(); + } + else if (index.column() == COL_QUESTION) + return QString("%1").arg(p->countSubQuestion()); + else if (index.column() == COL_ASSIST) + { + return p->recommendationText(); +/* CRecommendation r(p); + return r.textShort(); +*/ + } + } + else if (role == Qt::DecorationRole) + { + if (index.column() == COL_CHAPTER) + return QIcon(":/icons/16x16/folder.png"); + else if (index.column() == COL_AVG) + { + return p->levelAvgIcon(); + } + else if (index.column() == COL_ASSIST) + { + return p->recommendationIcon(m_pCatalog); +//TODO return p->recommendation().icon(m_pCatalog); + } + } + else if (role == Qt::ToolTipRole) + { + if (index.column() == COL_AVG) + { + return QString ("%1 - Kennzahl: %2").arg(p->/*statistic().*/levelAvgText()).arg(p->/*statistic().*/levelAvg(), 4, 'f', 2); + } + else if (index.column() == COL_ASSIST) + { + return p->recommendationToolTip(); +//TODO return p->recommendation().recommendationText(m_pCatalog); + } + } + + return QVariant(); +} + +bool CChapterModel::hasChildren (const QModelIndex & parent) const +{ + if (parent.isValid()) return false; + return (!m_listChapter.isEmpty()); +} + +QVariant CChapterModel::headerData (int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Vertical) return QVariant(); + + if (role == Qt::DisplayRole) + { + if (section == COL_CHAPTER) + return tr("Kapitel"); + else if (section == COL_QUESTION) + return tr("Fragen"); + else if (section == COL_AVG) + return tr("Ø"); + else if (section == COL_ASSIST) + return tr("Lernassistent"); + } + else if (role == Qt::ToolTipRole) + { + if (section == COL_CHAPTER) + return tr("Kapitelname\nBitte markieren Sie das Kapitel, das Sie lernen möchten."); + else if (section == COL_QUESTION) + return tr("Anzahl d. Fragen inkl. Unterkapitel"); + else if (section == COL_AVG) + return tr("Durchschnittlicher Lernfortschritt"); + else if (section == COL_ASSIST) + return tr("Empfehlung des Lernassistentes"); + } + else if (role == Qt::WhatsThisRole) + { + + } + return QVariant(); +} + +QModelIndex CChapterModel::index (int row, int column, const QModelIndex & parent) const +{ +CChapter *p=0; + + if (parent.isValid() || row >= m_listChapter.size()) return QModelIndex(); + p = m_listChapter.at (row); + return createIndex (row, column, (void*)p); +} + +QModelIndex CChapterModel::parent (const QModelIndex & index) const +{ + Q_UNUSED(index); + return QModelIndex(); +} + +int CChapterModel::rowCount (const QModelIndex & parent) const +{ + if (parent.isValid()) return 0; + return m_listChapter.size(); +} + diff --git a/chaptermodel.h b/chaptermodel.h new file mode 100644 index 0000000..e9ef482 --- /dev/null +++ b/chaptermodel.h @@ -0,0 +1,60 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef CHAPTERMODEL_H +#define CHAPTERMODEL_H + +#include + +class CChapter; +class CCatalog; + +class CChapterModel : public QAbstractItemModel +{ + Q_OBJECT +public: + CChapterModel(QObject *pParent=0); + ~CChapterModel(); + + void clear(); + void setModelData (CCatalog *pCatalog, QList listChapter); + +public: + virtual int columnCount ( const QModelIndex & parent = QModelIndex() ) const; + virtual QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const; + virtual bool hasChildren (const QModelIndex & parent = QModelIndex()) const; + virtual QVariant headerData ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const; + virtual QModelIndex index (int row, int column, const QModelIndex & parent = QModelIndex()) const; + virtual QModelIndex parent ( const QModelIndex & index ) const; + virtual int rowCount ( const QModelIndex & parent = QModelIndex() ) const; + +public slots: + void onLanguageChanged(); + +protected: + void recalcColumn(); + +protected: + CCatalog *m_pCatalog; + QList m_listChapter; +}; + +#endif // CHAPTERMODEL_H diff --git a/dlgexam.cpp b/dlgexam.cpp new file mode 100644 index 0000000..e8cb06b --- /dev/null +++ b/dlgexam.cpp @@ -0,0 +1,300 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "dlgexam.h" +#include "question.h" +#include "error.h" +#include "catalog.h" + +#include + +CDlgExam::CDlgExam (CCatalog *pCatalog, QWidget *pParent) : QDialog (pParent, Qt::WindowMaximizeButtonHint) +{ + m_pCatalog = pCatalog; + m_iCurrentQuestion = -1; + m_bIsFinished = false; + m_bTimeout = false; + setupUi(this); + connect (&m_timer, SIGNAL(timeout()), this, SLOT(onTimer())); +} + +CDlgExam::~CDlgExam () +{ +} + +bool CDlgExam::setup (const CExam& exam) +{ + m_exam = exam; + labHeader->setText(exam.name()); + + try + { + m_listQuestion = exam.createQuestionPool(m_pCatalog->questionPool()); + } + catch (CError e) + { + QMessageBox::critical(this, tr("Fehler"), e.toHtml()); + return false; + } + + m_listAnswerMask.clear(); + while (m_listAnswerMask.size() < m_listQuestion.size()) + m_listAnswerMask.append(0); + + // Antworten bei Bedarf durchmischen + if (m_pCatalog->useMixedAnswers()) + { + for (int i=0; imixAnswers(); + } + init(); + return true; +} + +void CDlgExam::init() +{ + // Zeitbalken & Zeitüberwachung + m_timer.setInterval(1000); + m_timer.start(); + m_dtStart = QDateTime::currentDateTime(); + pgTime->setMaximum (m_exam.duration() * 60); + + // Sonstiges + pgQuestions->setMaximum (m_listQuestion.size()); + m_bIsFinished = false; + m_bTimeout = false; + labMaxWrong->setText(QString("%1").arg(m_exam.maxErrorPoints())); + + // Navigation + spinBox->setRange (1, m_listQuestion.size()); + spinBox->setValue(1); + m_iCurrentQuestion = 1; + + // Alles updaten + updateNavi(); + showQuestion(); + updateProgressTimer(); + updateProgressQuestion(); +} + +void CDlgExam::onTimer() +{ + updateProgressTimer(); +} + +void CDlgExam::updateNavi() +{ + pbFirst->setEnabled (spinBox->value() > spinBox->minimum()); + pbPrev->setEnabled (spinBox->value() > spinBox->minimum()); + pbNext->setEnabled (spinBox->value() < spinBox->maximum()); + pbLast->setEnabled (spinBox->value() < spinBox->maximum()); +} + +unsigned CDlgExam::answeredQuestionCount() +{ +unsigned u=0; + for (int i=0; isetText(tr("%1 von %2").arg(uAnswered).arg(m_listAnswerMask.size())); + pgQuestions->setValue(uAnswered); +} + +void CDlgExam::updateProgressTimer() +{ +QDateTime dt = QDateTime::currentDateTime(); +unsigned uSecs = m_dtStart.secsTo(dt); + + labProgessTime->setText(tr("Abgelaufene Zeit: %1:%2 min von %3:00 min") + .arg(uSecs / 60) + .arg(uSecs % 60, 2, 10, QChar('0')) + .arg(m_exam.duration())); + + pgTime->setValue (uSecs); + + if (uSecs > m_exam.duration()*60) + { + m_bTimeout = true; + m_timer.stop(); + QMessageBox::information(this, tr("Information"), tr("Die Zeit ist abgelaufen.\nDie Prüfung wird deswegen beendet.")); + on_pbFinish_clicked(); + } +} + +void CDlgExam::on_pbFirst_clicked() +{ + spinBox->setValue(1); +} + +void CDlgExam::on_pbPrev_clicked() +{ +int iNew = spinBox->value() - 1; + if (iNew < 1) iNew = 1; + spinBox->setValue(iNew); +} + +void CDlgExam::on_pbNext_clicked() +{ +int iNew = spinBox->value() + 1; + if (iNew > m_listQuestion.size()) iNew = m_listQuestion.size(); + spinBox->setValue(iNew); +} + +void CDlgExam::on_pbLast_clicked() +{ + spinBox->setValue(m_listQuestion.size()); +} + +void CDlgExam::on_spinBox_valueChanged(int i) +{ + Q_UNUSED(i); + onQuestionChanged(); +} + +void CDlgExam::saveCurrentAnswer() +{ +unsigned uAnswerMask=0; + if (m_iCurrentQuestion >= 0) + { // Antwort zur aktuellen Frage abspeichern + if (rbA->isChecked()) uAnswerMask |= 1; + if (rbB->isChecked()) uAnswerMask |= 2; + if (rbC->isChecked()) uAnswerMask |= 4; + if (rbD->isChecked()) uAnswerMask |= 8; + m_listAnswerMask[m_iCurrentQuestion] = uAnswerMask; + } +} + +void CDlgExam::onQuestionChanged() +{ + saveCurrentAnswer(); + updateNavi(); + updateProgressQuestion(); + showQuestion(); // neue Frage anzeigen +} + +void CDlgExam::showQuestion() +{ +CQuestion *q=0; +unsigned uAnswerMask; +QString str; + + m_iCurrentQuestion = spinBox->value()-1; + q = m_listQuestion.at(m_iCurrentQuestion); + uAnswerMask = m_listAnswerMask.at(m_iCurrentQuestion); + str = q->learnText(m_pCatalog, m_bIsFinished, false); + if (m_bIsFinished) + { + str += "


"; + if (!q->isCorrectAnswer(uAnswerMask)) + str += ""; + else + str += ""; + + str += q->correctionText(uAnswerMask) + "

"; + } + textBrowser->setHtml(str); + + rbA->setChecked(uAnswerMask & 1); + rbB->setChecked(uAnswerMask & 2); + rbC->setChecked(uAnswerMask & 4); + rbD->setChecked(uAnswerMask & 8); + rbNoIdea->setChecked(uAnswerMask == 0); +} + +void CDlgExam::on_pbFinish_clicked() +{ +unsigned uCorrect=0, uWrong=0, uAnsweredCount = answeredQuestionCount(), uErrorPoints=0; +bool bSaveStat=true, bPassed=false; +QDateTime dt = QDateTime::currentDateTime(); +unsigned uSecs = m_dtStart.secsTo(dt); + + saveCurrentAnswer(); + if (uAnsweredCount < (unsigned)m_listQuestion.size() && !m_bTimeout) + { + if (QMessageBox::question (this, + tr("Sicherheitsabfrage"), + tr("Sie haben noch nicht alle Fragen beantwortet.\nWirklich abgeben?"), + QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::No) return; + } + + m_timer.stop(); + pbFinish->setEnabled(false); + rbA->setEnabled(false); + rbB->setEnabled(false); + rbC->setEnabled(false); + rbD->setEnabled(false); + rbNoIdea->setEnabled(false); + pbCancel->setText(tr("Beenden")); + m_bIsFinished = true; + showQuestion(); + + for (int i=0; iisCorrectAnswer(uAnswerMask)) + uCorrect++; + else + { + uWrong++; + uErrorPoints+=q->errorPoints(); + } + } + labCorrect->setText(QString("%1").arg(uCorrect)); + labWrong->setText(QString("%1").arg(uWrong)); + + if (uAnsweredCount < (unsigned)m_listQuestion.size()/2) + { + bPassed = false; + bSaveStat = false; + labResult->setText(tr("Ohne Wertung\n(Nicht bestanden)")); + QMessageBox::information(this, tr("Information"), tr("Sie haben weniger als die Hälfte aller Fragen beantwortet.\nDiese Prüfung wird deswegen nicht gewertet.")); + } + else if (uErrorPoints > m_exam.maxErrorPoints()) + { + bPassed = false; + labResult->setText(tr("Nicht bestanden")); + QMessageBox::information(this, tr("Ergebnis"), tr("Sie haben die Prüfung leider nicht bestanden.")); + } + else + { + bPassed = true; + labResult->setText(tr("Bestanden")); + QMessageBox::information(this, tr("Ergebnis"), tr("Herzlichen Glückwunsch!

Sie haben die Prüfung bestanden!")); + } + + if (bSaveStat) + { // Prüfungsstatistik speichern + CExamStat es(m_exam); + es.setSecs(uSecs); + es.setQuestions(m_listQuestion, m_listAnswerMask); + es.setResult(uCorrect, uWrong, uErrorPoints, bPassed); + m_pCatalog->appendExamStat(es); + m_pCatalog->saveStatistic(this); + } +} diff --git a/dlgexam.h b/dlgexam.h new file mode 100644 index 0000000..159783a --- /dev/null +++ b/dlgexam.h @@ -0,0 +1,81 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#pragma once + +#include "exam.h" + +#include "ui_dlgexam.h" +#include +#include +#include + +class CCatalog; +class CQuestion; + +class CDlgExam : public QDialog, Ui::DlgExam +{ +Q_OBJECT +public: + CDlgExam (CCatalog *pCatalog, QWidget *pParent=0); + ~CDlgExam (); + + bool setup (const CExam& exam); + +protected: + void init(); + void updateNavi(); + void updateProgressQuestion(); + void updateProgressTimer(); + void showQuestion(); + void onQuestionChanged(); + unsigned answeredQuestionCount(); + void saveCurrentAnswer(); + +protected slots: + void onTimer(); + void on_pbFirst_clicked(); + void on_pbPrev_clicked(); + void on_pbNext_clicked(); + void on_pbLast_clicked(); + void on_spinBox_valueChanged(int i); + void on_pbFinish_clicked(); + + inline void on_rbA_toggled(bool bChecked) { Q_UNUSED(bChecked); saveCurrentAnswer(); updateProgressQuestion(); } + inline void on_rbB_toggled(bool bChecked) { Q_UNUSED(bChecked); saveCurrentAnswer(); updateProgressQuestion(); } + inline void on_rbC_toggled(bool bChecked) { Q_UNUSED(bChecked); saveCurrentAnswer(); updateProgressQuestion(); } + inline void on_rbD_toggled(bool bChecked) { Q_UNUSED(bChecked); saveCurrentAnswer(); updateProgressQuestion(); } + inline void on_rbNoIdea_toggled(bool bChecked) { Q_UNUSED(bChecked); saveCurrentAnswer(); updateProgressQuestion(); } + +protected: + CCatalog *m_pCatalog; + CExam m_exam; + + QList m_listQuestion; + QList m_listAnswerMask; + QTimer m_timer; + QDateTime m_dtStart; + QDateTime m_dtStop; + int m_iCurrentQuestion; + bool m_bIsFinished; + bool m_bTimeout; +}; + diff --git a/dlgexam.ui b/dlgexam.ui new file mode 100644 index 0000000..0373422 --- /dev/null +++ b/dlgexam.ui @@ -0,0 +1,529 @@ + + DlgExam + + + + 0 + 0 + 607 + 507 + + + + Prüfungssimulation + + + + 9 + + + 6 + + + + + + 12 + 75 + true + + + + Bezeichnung + + + Qt::AlignCenter + + + + + + + 0 + + + 6 + + + + + 0 + + + 6 + + + + + Frage + + + + 9 + + + 6 + + + + + + 7 + 7 + 0 + 0 + + + + + 300 + 300 + + + + + 10 + + + + + + + + + + + Ihre Antwort + + + + 9 + + + 6 + + + + + &A + + + A + + + + + + + &B + + + B + + + + + + + &C + + + C + + + + + + + &D + + + D + + + + + + + &Keine Ahnung + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Navigation + + + + 9 + + + 6 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 40 + 16777215 + + + + << + + + + + + + + 40 + 16777215 + + + + < + + + + + + + + + + + 40 + 16777215 + + + + > + + + + + + + + 40 + 16777215 + + + + >> + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + 0 + + + 6 + + + + + + 300 + 16777215 + + + + Zeit + + + + 9 + + + 6 + + + + + TextLabel + + + + + + + 24 + + + Qt::Horizontal + + + + + + + + + + + 300 + 16777215 + + + + Beantwortete Fragen + + + + 9 + + + 6 + + + + + TextLabel + + + + + + + 24 + + + Qt::Horizontal + + + + + + + + + + + 300 + 16777215 + + + + Auswertung + + + + 9 + + + 6 + + + + + Richtige Antworten: + + + + + + + Falsche Antworten: + + + + + + + ? + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + ? + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + max. erlaubte falsche Antworten: + + + 10 + + + + + + + 10 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + 12 + 75 + true + + + + + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 20 + 31 + + + + + + + + 0 + + + 6 + + + + + + 150 + 0 + + + + Abgeben && auswerten + + + + + + + + + 0 + + + 6 + + + + + + 150 + 0 + + + + Abbrechen + + + + + + + + + + + + + + + pbCancel + clicked() + DlgExam + reject() + + + 468 + 482 + + + 514 + 501 + + + + + diff --git a/dlgexamselect.cpp b/dlgexamselect.cpp new file mode 100644 index 0000000..eebd1ae --- /dev/null +++ b/dlgexamselect.cpp @@ -0,0 +1,93 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "dlgexamselect.h" + +#include +#include +#include "catalog.h" + +CDlgExamSelect::CDlgExamSelect (QWidget *pParent) : QDialog (pParent) +{ + m_pCatalog = 0; + m_iSelectedExam = -1; + setupUi(this); + + twList->headerItem()->setText(0, tr("Name")); + twList->headerItem()->setText(1, tr("Dauer (min)")); + //twList->header()->setStretchLastSection(false); + twList->header()->resizeSection(0, 370); + //twList->header()->resizeSection(1, 70); + +} + +CDlgExamSelect::~CDlgExamSelect () +{ +} + +void CDlgExamSelect::setup (const CCatalog *pCatalog) +{ + m_pCatalog = pCatalog; + for (int i=0; icountExam(); i++) + { + CExam exam = m_pCatalog->examAt(i); + QTreeWidgetItem *pItem = new QTreeWidgetItem (twList); + pItem->setText(0, exam.name()); + pItem->setText(1, QString("%1 min").arg(exam.duration())); + pItem->setTextAlignment(1, Qt::AlignRight); + pItem->setData(0, Qt::UserRole, i); + } +} + +void CDlgExamSelect::on_twList_currentItemChanged (QTreeWidgetItem *current, QTreeWidgetItem *previous) +{ + Q_UNUSED(previous); + if (current) + { + m_iSelectedExam = current->data(0, Qt::UserRole).toInt(); + CExam exam = m_pCatalog->examAt(m_iSelectedExam); + labName->setText(exam.name()); + labComment->setText(exam.comment()); + labDuration->setText(QString("%1 min").arg(exam.duration())); + labQuestions->setText(QString("%1").arg(exam.questionCount())); + labError->setText(QString("%1").arg(exam.maxErrorPoints())); + } + else + { + m_iSelectedExam = -1; + labName->clear(); + labComment->clear(); + labDuration->clear(); + labQuestions->clear(); + labError->clear(); + } +} + +void CDlgExamSelect::on_buttonBox_accepted() +{ + if (m_iSelectedExam == -1) + { + QMessageBox::information(this, tr("Information"), tr("Bitte eine Prüfung auswählen!")); + return; + } + accept(); +} + diff --git a/dlgexamselect.h b/dlgexamselect.h new file mode 100644 index 0000000..5189cc8 --- /dev/null +++ b/dlgexamselect.h @@ -0,0 +1,46 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#pragma once + +#include "ui_dlgexamselect.h" +#include + +class CCatalog; + +class CDlgExamSelect : public QDialog, Ui::DlgExamSelect +{ +Q_OBJECT +public: + CDlgExamSelect (QWidget *pParent=0); + ~CDlgExamSelect (); + + void setup (const CCatalog *pCatalog); + inline int selectedExam() const { return m_iSelectedExam; } + +protected slots: + void on_buttonBox_accepted(); + void on_twList_currentItemChanged (QTreeWidgetItem *current, QTreeWidgetItem *previous); + +protected: + const CCatalog *m_pCatalog; + int m_iSelectedExam; +}; diff --git a/dlgexamselect.ui b/dlgexamselect.ui new file mode 100644 index 0000000..b5a0a8a --- /dev/null +++ b/dlgexamselect.ui @@ -0,0 +1,178 @@ + + DlgExamSelect + + + + 0 + 0 + 491 + 418 + + + + Prüfung auswählen + + + + 9 + + + 6 + + + + + Verfügbare Prüfungen + + + + 9 + + + 6 + + + + + false + + + + + + + + + + Detailinformation + + + + 9 + + + 6 + + + + + + 3 + 5 + 0 + 0 + + + + + + + + + + + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + Max. erlaubte Fehler: + + + + + + + Anzahl Fragen: + + + + + + + Dauer: + + + + + + + Hinweise: + + + + + + + Bezeichnung: + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + true + + + + + + + twList + buttonBox + + + + + buttonBox + rejected() + DlgExamSelect + reject() + + + 291 + 384 + + + 286 + 274 + + + + + diff --git a/dlgexamstatistic.cpp b/dlgexamstatistic.cpp new file mode 100644 index 0000000..aa9357e --- /dev/null +++ b/dlgexamstatistic.cpp @@ -0,0 +1,109 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "dlgexamstatistic.h" +#include "catalog.h" + +#include +#include + +CDlgExamStatistic::CDlgExamStatistic(QWidget *pParent) : QDialog(pParent) +{ + m_pCatalog = 0; + setupUi(this); + + twExamStat->headerItem()->setText(0, "Datum/Uhrzeit"); + twExamStat->headerItem()->setText(1, "Ergebnis"); + twExamStat->headerItem()->setText(2, "Fehler"); + twExamStat->headerItem()->setText(3, "Dauer"); + twExamStat->headerItem()->setText(4, "Ø pro Frage"); + + twExamStat->header()->resizeSection(0, 130); + twExamStat->header()->resizeSection(2, 50); + twExamStat->header()->resizeSection(3, 75); +} + +void CDlgExamStatistic::go (CCatalog *pCatalog) +{ + m_pCatalog = pCatalog; + + if (pCatalog->countExamStat() == 0) + { + QMessageBox::information(parentWidget(), tr("Information"), tr("Es wurden bisher noch keine Prüfungen durchgeführt.")); + return; + } + + cbExam->clear(); + for (int i=0; icountExam(); i++) + { + cbExam->addItem(pCatalog->examAt(i).name(), pCatalog->examAt(i).id()); + } + + exec(); +} + +void CDlgExamStatistic::on_cbExam_currentIndexChanged (int index) +{ +QString strExamId = cbExam->itemData(index).toString(); +CExam exam = m_pCatalog->examById(strExamId); +unsigned uExamCount=0, uPassedCount=0, uFailedCount=0, uErrorCount=0, uDuration=0, uQuestions=0; +QTreeWidgetItem *pItem=0; + + twExamStat->clear(); + + labExamQCount->setText(QString::number(exam.questionCount())); + labExamTime->setText(QString("%1 min").arg(exam.duration())); + labExamWrong->setText(QString::number(exam.maxErrorPoints())); + + for (int i=0; icountExamStat(); i++) + { + CExamStat es = m_pCatalog->examStatAt(i); + if (es.id() != strExamId) continue; + uExamCount++; + if (es.passed()) uPassedCount++; else uFailedCount++; + uErrorCount += es.errorPoints(); + uDuration += es.duration(); + uQuestions += es.correctAnswers() + es.wrongAnswers(); + pItem = new QTreeWidgetItem (twExamStat); + pItem->setText(0, es.datetime().toString(Qt::LocalDate)); + pItem->setText(1, es.passed() ? tr("Bestanden") : tr("Nicht bestanden")); + pItem->setText(2, QString::number(es.errorPoints())); + pItem->setTextAlignment(2, Qt::AlignRight); + pItem->setText(3, QString("%1 m %2 s").arg(es.duration() / 60).arg(es.duration() % 60, 2, 10, QChar('0'))); + pItem->setTextAlignment(3, Qt::AlignRight); + unsigned uQuestionCount = es.correctAnswers() + es.wrongAnswers(); + if (uQuestionCount != 0) + pItem->setText(4, QString("%1 m %2 s").arg(es.duration() / uQuestionCount / 60).arg(es.duration() / uQuestionCount % 60, 2, 10, QChar('0'))); + else + pItem->setText(4, "--"); + pItem->setTextAlignment(4, Qt::AlignRight); + } + + labExamCount->setText(QString::number(uExamCount)); + labExamPassed->setText(uExamCount != 0 ? QString::number(uPassedCount) : "--"); + labExamFailed->setText(uExamCount != 0 ? QString::number(uFailedCount) : "--"); + labExamPassedP->setText(uExamCount != 0 ? QString("%1 %").arg((double)uPassedCount / uExamCount * 100.0, 0, 'f', 1) : "--"); + labExamFailedP->setText(uExamCount != 0 ? QString("%1 %").arg((double)uFailedCount / uExamCount * 100.0, 0, 'f', 1) : "--"); + labAvgWrong->setText(uExamCount != 0 ? QString::number(uErrorCount / uExamCount) : "--"); + labTimeExam->setText(uExamCount != 0 ? QString("%1 m %2 s").arg(uDuration / uExamCount / 60).arg(uDuration / uExamCount % 60, 2, 10, QChar('0')) : "--"); + labTimeQuestion->setText(uQuestions != 0 ? QString("%1 m %2 s").arg(uDuration / uQuestions / 60).arg(uDuration / uQuestions % 60, 2, 10, QChar('0')) : "--"); +} + diff --git a/dlgexamstatistic.h b/dlgexamstatistic.h new file mode 100644 index 0000000..a647dfc --- /dev/null +++ b/dlgexamstatistic.h @@ -0,0 +1,43 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#pragma once + +#include +#include "ui_dlgexamstatistic.h" + +class CCatalog; + +class CDlgExamStatistic : public QDialog, Ui::DlgExamStatistic +{ +Q_OBJECT +public: + CDlgExamStatistic(QWidget *pParent=0); + ~CDlgExamStatistic() {} + + void go (CCatalog *pCatalog); + +protected slots: + void on_cbExam_currentIndexChanged (int index); + +protected: + CCatalog *m_pCatalog; +}; diff --git a/dlgexamstatistic.ui b/dlgexamstatistic.ui new file mode 100644 index 0000000..da96a51 --- /dev/null +++ b/dlgexamstatistic.ui @@ -0,0 +1,367 @@ + + DlgExamStatistic + + + + 0 + 0 + 452 + 433 + + + + Prüfungs-Statistik + + + + 9 + + + 6 + + + + + 0 + + + 6 + + + + + Prüfung: + + + + + + + + 3 + 0 + 0 + 0 + + + + + 200 + 0 + + + + + + + + + + + 10 + 75 + true + + + + Prüfungs-Rahmenbedinungen + + + + + + + 0 + + + 6 + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Verfügbare Zeit: + + + + + + + Max. erlaubte Anzahl falscher Fragen: + + + + + + + Anzahl der Fragen: + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + + + + + + + 10 + 75 + true + + + + Statistik + + + + + + + 0 + + + 6 + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + davon bestanden: + + + 10 + + + + + + + Durchschn. Anzahl falscher Fragen: + + + + + + + davon nicht bestanden: + + + 10 + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0.0 % + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0.0 % + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Anzahl Prüfungen durchgeführt: + + + + + + + Durchschn. benötigte Zeit pro Prüfung / pro Frage: + + + + + + + 0 m 0 s + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0 m 0 s + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + + + + + + + 10 + 75 + true + + + + Liste durchgeführter Prüfungen + + + + + + + false + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + true + + + + + + + cbExam + twExamStat + buttonBox + + + + + buttonBox + accepted() + DlgExamStatistic + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DlgExamStatistic + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/dlginformation.cpp b/dlginformation.cpp new file mode 100644 index 0000000..c89bfbf --- /dev/null +++ b/dlginformation.cpp @@ -0,0 +1,51 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "dlginformation.h" + +#include + +#include "catalog.h" + +CDlgInformation::CDlgInformation (QWidget *pParent) : QDialog(pParent) +{ + m_pCatalog=0; + setupUi(this); + +} + + +bool CDlgInformation::setup(CCatalog *pCatalog) +{ + labName->setText(pCatalog->name()); + labVersion->setText(pCatalog->versionText()); + labPublished->setText(pCatalog->published().toString(Qt::LocalDate)); + labStatQuestion->setText(QString("%1").arg(pCatalog->countSubQuestion())); + labStatChapter->setText(QString("%1").arg(pCatalog->subChapters().size())); + labValidFrom->setText(pCatalog->validFrom().toString(Qt::LocalDate)); + labValidUntil->setText(pCatalog->validUntil().toString(Qt::LocalDate)); + labDate->setText(pCatalog->created().toString(Qt::LocalDate)); + labComment->setText(pCatalog->comment()); + labPublisher->setText(pCatalog->publisher()); + labContact->setText(pCatalog->contact()); + return true; +} + diff --git a/dlginformation.h b/dlginformation.h new file mode 100644 index 0000000..3b07688 --- /dev/null +++ b/dlginformation.h @@ -0,0 +1,47 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef DLGINFORMATION_H +#define DLGINFORMATION_H + +#include +#include "ui_dlginformation.h" + +class CCatalog; + +class CDlgInformation : public QDialog, Ui::DlgInformation +{ + Q_OBJECT +public: + CDlgInformation (QWidget *pParent=0); + ~CDlgInformation () {} + + bool setup(CCatalog *pCatalog); + +protected slots: + + +protected: + CCatalog *m_pCatalog; + +}; + +#endif diff --git a/dlginformation.ui b/dlginformation.ui new file mode 100644 index 0000000..ed345dc --- /dev/null +++ b/dlginformation.ui @@ -0,0 +1,304 @@ + + DlgInformation + + + + 0 + 0 + 366 + 331 + + + + Datei-Information + + + :/icons/16x16/info.png + + + + 9 + + + 6 + + + + + Original-Fragenkatalog + + + + 9 + + + 6 + + + + + TextLabel + + + + + + + + 50 + 0 + + + + 0 + + + + + + + + 50 + 0 + + + + 0 + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + Herausgeber: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Anzahl der Kapitel: + + + + + + + Anzahl der Fragen: + + + + + + + Gültig bis: + + + + + + + Gültig ab: + + + + + + + Name: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + Version: + + + + + + + Datum: + + + + + + + + + + AFUTrainer-Fragenkatalog + + + + 9 + + + 6 + + + + + TextLabel + + + + + + + Erstellt von: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + TextLabel + + + true + + + + + + + + 3 + 5 + 0 + 0 + + + + TextLabel + + + + + + + Kommentar: + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + + + + 1 + 5 + 0 + 0 + + + + Erstellt am: + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + true + + + + + + + + + + + buttonBox + accepted() + DlgInformation + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DlgInformation + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/dlglearn.cpp b/dlglearn.cpp new file mode 100644 index 0000000..6a8831d --- /dev/null +++ b/dlglearn.cpp @@ -0,0 +1,435 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "catalog.h" +#include "dlglearn.h" +#include "dlgviewquestion.h" +#include "dlglearnassistant.h" +#include "tools.h" + +#include + +CDlgLearn::CDlgLearn (QWidget *pParent) : QDialog (pParent, Qt::WindowMaximizeButtonHint) +{ + m_pCatalog = 0; + m_pChapter = 0; + m_pQuestion=0; + m_pLastQuestion=0; + m_uLastAnswerMask=0; + m_bHintsUsed=false; + m_uElapsedBeforeBreak=0; +#ifdef _DEBUG + m_bCheatEnable=true; +#else + m_bCheatEnable=false; +#endif + m_bAssistantEnable=true; + setupUi(this); + + if (!m_bAssistantEnable) + gbAssistant->hide(); +} + +CDlgLearn::~CDlgLearn() +{ +} + +void CDlgLearn::go (CCatalog *pCatalog, CChapter *pChapter) +{ + Q_ASSERT (pChapter != 0); + m_pCatalog = pCatalog; + setNewChapter(pChapter); + + m_ds = pCatalog->dayStatistic(QDate::currentDate()); + onUpdateDS(); + + exec(); +} + +void CDlgLearn::setNewChapter(CChapter *pChapter) +{ + m_pQuestion=0; + m_bHintsUsed=false; + m_pChapter = pChapter; + m_listQuestion = m_pChapter->questionPool(); + nextQuestion(); +} + +void CDlgLearn::updateStatistic() +{ +const int w=60, h=16; + + // CHAPTER STATISTICS + labChapter->setText (m_pChapter->text()); + labChapterCount->setText(QString("%1").arg(m_listQuestion.size())); + labChapterVeryOften->setText(QString("%1").arg(m_pChapter->countQuestion(0))); + labChapterOften->setText(QString("%1").arg(m_pChapter->countQuestion(1))); + labChapterNormal->setText(QString("%1").arg(m_pChapter->countQuestion(2))); + labChapterRare->setText(QString("%1").arg(m_pChapter->countQuestion(3))); + labChapterVeryRare->setText(QString("%1").arg(m_pChapter->countQuestion(4))); + labChapterExtremeRare->setText(QString("%1").arg(m_pChapter->countQuestion(5))); + labChapterAvgText->setText(m_pChapter->levelAvgText()); + labChapterAvgIcon->setPixmap(m_pChapter->levelAvgPixmap()); + labChapterAvgIcon->setToolTip(QString("Kennzahl: %1").arg(m_pChapter->levelAvg(), 4, 'g', 2)); + + double dQuestionCount = m_listQuestion.size(), dPercent; +// dPercent = (double)m_listQuestion.size()/(double)m_pCatalog->countSubQuestion(); +// labChapterCountBar->setPixmap(createProgressBar(w, h, dPercent)); +// labChapterCountBar->setToolTip(QString("%1 %").arg(dPercent * 100, 0, 'f', 1)); + dPercent = (double)m_pChapter->countQuestion(0)/dQuestionCount; + labChapterVeryOftenBar->setPixmap(createProgressBar(w, h, dPercent)); + labChapterVeryOftenBar->setToolTip(QString("%1 %").arg(dPercent * 100, 0, 'f', 1)); + dPercent = (double)m_pChapter->countQuestion(1)/dQuestionCount; + labChapterOftenBar->setPixmap(createProgressBar(w, h, dPercent)); + labChapterOftenBar->setToolTip(QString("%1 %").arg(dPercent * 100, 0, 'f', 1)); + dPercent = (double)m_pChapter->countQuestion(2)/dQuestionCount; + labChapterNormalBar->setPixmap(createProgressBar(w, h, dPercent)); + labChapterNormalBar->setToolTip(QString("%1 %").arg(dPercent * 100, 0, 'f', 1)); + dPercent = (double)m_pChapter->countQuestion(3)/dQuestionCount; + labChapterRareBar->setPixmap(createProgressBar(w, h, dPercent)); + labChapterRareBar->setToolTip(QString("%1 %").arg(dPercent * 100, 0, 'f', 1)); + dPercent = (double)m_pChapter->countQuestion(4)/dQuestionCount; + labChapterVeryRareBar->setPixmap(createProgressBar(w, h, dPercent)); + labChapterVeryRareBar->setToolTip(QString("%1 %").arg(dPercent * 100, 0, 'f', 1)); + dPercent = (double)m_pChapter->countQuestion(5)/dQuestionCount; + labChapterExtremeRareBar->setPixmap(createProgressBar(w, h, dPercent)); + labChapterExtremeRareBar->setToolTip(QString("%1 %").arg(dPercent * 100, 0, 'f', 1)); + + // QUESTION STATISTICS + labQuestion->setText (tr("Frage %1").arg(m_pQuestion->id())); + labQuestionLevelIcon->setPixmap(m_pQuestion->levelPixmap()); + labQuestionLevelText->setText(m_pQuestion->levelText()); + if (m_pQuestion->clickedCorrectSuccessive() != 0) + labQuestionSuccessive->setText(tr("%1x richtig").arg(m_pQuestion->clickedCorrectSuccessive())); + else if (m_pQuestion->clickedWrongSuccessive() != 0) + labQuestionSuccessive->setText(tr("%1x falsch").arg(m_pQuestion->clickedWrongSuccessive())); + else + labQuestionSuccessive->setText(tr("--")); + + labQuestionCount->setText(QString("%1").arg(m_pQuestion->clickedCount())); + labQuestionCorrect->setText(QString("%1").arg(m_pQuestion->clickedCorrect())); + labQuestionWrong->setText(QString("%1").arg(m_pQuestion->clickedWrong())); + QDateTime dtLastClicked = m_pQuestion->lastClicked(); + if (!dtLastClicked.isValid()) + labQuestionDate->setText("--"); + else + { + labQuestionDate->setText(m_pQuestion->lastClickedText()); + } + + onUpdateDS(); +} + +void CDlgLearn::updateLearnAssistant() +{ +QString strRecom1, strRecom2, str; +QPixmap pixRecom1, pixRecom2, pix; + + // first recommendation + strRecom1 = m_pChapter->recommendationTextExtended(m_pCatalog); + if (m_pChapter->isRecommendedNow(m_pCatalog)) + strRecom1 = "" + strRecom1 + ""; + else + strRecom1 = "" + strRecom1 + ""; + pixRecom1 = QPixmap (m_pChapter->recommendationIconName (m_pCatalog)); + + // second recommendation + strRecom2 = m_pChapter->recommendationTextExtended2(m_pCatalog); + pixRecom2 = QPixmap (m_pChapter->recommendationIconName (m_pChapter->recommendation2(), m_pCatalog)); + + + if (m_pChapter->recommendation() == CChapter::RecommendationSubChapter && m_pChapter->recommendation2() == CChapter::RecommendationRepeatToday) + { + labRecommendationIcon->setPixmap (pixRecom2); + labRecommendation->setText("" + strRecom2 + ""); + } + else + { + str = strRecom1; + if (!strRecom2.isEmpty()) + str += "

" + strRecom2 + ""; + labRecommendationIcon->setPixmap (pixRecom1); + labRecommendation->setText(str); + } +} + +CQuestion *CDlgLearn::findNextQuestion() +{ +CQuestion *p = 0; + + if (m_bAssistantEnable && m_pChapter->hasRecommendedQuestions()) + { +// if (afu_random (0, 99) >= 0) // hier kann ggf. das Verhältnis zwischen RecommendedQuestion und Zufalls-Question ausgewählt werden +// { + p = findNextTargetQuestion(); + // Wichtig: p kann hier immernoch 0 sein! Dies bedeutet, dass keine passende Frage im TargetPool gefunden wurde. +// } + } + + if (p == 0) + p = findNextPoolQuestion(); + + return p; +} + +CQuestion *CDlgLearn::findNextTargetQuestion() +{ +CQuestion *pQuestion=0; +QList list = m_pChapter->recommendedQuestions(); +int iSize = list.size(); +unsigned uRnd=0; + + if (iSize == 0) return 0; + if (iSize == 1) + { + if (list.at(0) == m_pQuestion) + // Regel: Niemals die gleiche Frage mehrfach hintereinander + // -> kann hier nicht erfüllt werden -> 0 zurückgeben -> hole Frage aus allg. Pool (siehe findNextQuestion) + return 0; + else + return list.at(0); + } + + do + { + uRnd = afu_random (0, iSize-1); + pQuestion = list.at(uRnd); + } + while (m_pQuestion == pQuestion); + + return pQuestion; +} + +CQuestion *CDlgLearn::findNextPoolQuestion() +{ +CQuestion *pQuestion=0; +unsigned uCountQuestion[LEVEL_MAX+1]; +unsigned uRnd=0, uLevel=0; +int i=0; +unsigned uGewichtung[LEVEL_MAX+1], uEdge[LEVEL_MAX+1]; +unsigned uRndMax = 0; + + memset (uCountQuestion, 0, sizeof(unsigned)*(LEVEL_MAX+1)); + for (i=0;ilevel(); + Q_ASSERT(uLevel < LEVEL_MAX+1); + if (uLevel > LEVEL_MAX) continue; + uCountQuestion[uLevel]++; + } + + uGewichtung[LEVEL_MAX] = 1; + uRndMax = 1; + for (i=LEVEL_MAX-1; i>=0; i--) + { + uRndMax += uGewichtung[i+1]; + uGewichtung[i] = uRndMax; + } + + for (i=0; i<=LEVEL_MAX; i++) + uGewichtung[i] *= uCountQuestion[i]; + + uRndMax=0; + for (i=0; i<=LEVEL_MAX; i++) + { + uLevel = LEVEL_MAX-i; + uRndMax += uGewichtung[uLevel]; + uEdge[uLevel] = uRndMax; + } + + do /* diese Schleife verhindert, dass eine Frage zweimal hintereinander kommt */ + { + do /* diese Schleife wählt die Abfragehäufigkeit aus */ + { + uRnd = afu_random (1, uRndMax); + uLevel = LEVEL_MAX; + while (uLevel != LEVEL_VERYOFTEN && uRnd > uEdge[uLevel]) + uLevel--; + + Q_ASSERT(uCountQuestion[uLevel] != 0); // Wenn hier assertion failed => algorithmus falsch + } + while (uCountQuestion[uLevel] == 0); + + // Zufällige Frage bestimmen + uRnd = afu_random (1, uCountQuestion[uLevel]); + for (i=0; ilevel() == uLevel && --uRnd == 0) break; + } + while (m_pQuestion == m_listQuestion[i] && m_listQuestion.size() > 1); + pQuestion = m_listQuestion[i]; + return pQuestion; +} + +void CDlgLearn::nextQuestion() +{ +QString str, strCheat; + + if (m_listQuestion.size() == 0) + { + QMessageBox::critical (this, "Fehler", "Es gibt keine Fragen, die gelernt werden könnten!"); + accept(); + return; + } + + if (m_pQuestion) + { // Save current question as new last question + pbLastQuestion->setEnabled(true); + m_pLastQuestion = m_pQuestion; + } + + m_pQuestion = findNextQuestion(); + + // show answer + m_pQuestion->mixAnswers(); + + str = m_pQuestion->learnText(m_pCatalog, true, false); + if (m_bCheatEnable) + { + strCheat = "

Schummel-Modus
Richtige Antwort: " + CAnswerClicked::answerText (m_pQuestion->correctAnswerMask()) + "
"; + strCheat += "
Empfohlene Wiederholung: " + m_pQuestion->repeatDateText(); + strCheat += QString("
isLearningNew(): %1").arg(m_pQuestion->isLearningNew()); + + strCheat += "

"; + } + teQuestion->setHtml(strCheat + str); + + pbShowHelper->setEnabled (m_pCatalog->hasHints(m_pQuestion->id())); + m_bHintsUsed = false; + m_timeElapsed.restart(); + updateStatistic(); + updateLearnAssistant(); +} + +void CDlgLearn::on_pbAnswerA_clicked() +{ + handleAnswer(0); +} + +void CDlgLearn::on_pbAnswerB_clicked() +{ + handleAnswer(1); +} + +void CDlgLearn::on_pbAnswerC_clicked() +{ + handleAnswer(2); +} + +void CDlgLearn::on_pbAnswerD_clicked() +{ + handleAnswer(3); +} + +void CDlgLearn::handleAnswer(const int i) +{ +unsigned uAnswerMask = 1<isCorrectAnswer(uAnswerMask)) + { + QMessageBox::information(this, tr("Hinweis"), m_pQuestion->correctionText(uAnswerMask)); + } + m_uLastAnswerMask=uAnswerMask; + + if (!m_bHintsUsed) + { + m_pQuestion->registerAnswerClicked(uAnswerMask, m_timeElapsed.elapsed() + m_uElapsedBeforeBreak); + + bool bIsRecommendedOld = m_pChapter->isRecommendedNow(m_pCatalog) ; + + m_pCatalog->updateStatistic(); + m_ds = m_pCatalog->dayStatistic(QDate::currentDate()); + + if (bIsRecommendedOld && !m_pChapter->isRecommendedNow(m_pCatalog)) + { + QMessageBox msgBox(this); + QPushButton *pbAssistant = msgBox.addButton(tr("Assistent"), QMessageBox::AcceptRole); + QPushButton *pbIgnore = msgBox.addButton(QMessageBox::Ignore); + QPushButton *pbExit = msgBox.addButton(QMessageBox::Cancel); + pbAssistant->setIcon(QIcon(":/icons/16x16/idea_info.png")); + pbAssistant->setToolTip(tr("Lern-Assistent aufrufen")); + pbIgnore->setToolTip(tr("Meldung ignorieren, dieses Kapitel weiterlernen")); + pbExit->setToolTip(tr("Lernmodus beenden")); + msgBox.setText(tr("Herzlichen Glückwunsch!

Sie haben das heutige Lernziel für dieses Kapitel erreicht.
Bitte folgen Sie den weiteren Empfehlungen des Lernassistents.")); + msgBox.setWindowTitle(tr("Ziel erreicht")); + + msgBox.exec(); + if (msgBox.clickedButton() == pbExit) + { + reject(); + return; + } + else if (msgBox.clickedButton() == pbAssistant) + { + on_pbLearnAssistant_clicked(); + return; + } + } + } + nextQuestion(); +} + +void CDlgLearn::on_pbShowHelper_clicked() +{ + m_bHintsUsed = true; + QString str = m_pQuestion->learnText(m_pCatalog, true, true); + teQuestion->setHtml(str); +} + +void CDlgLearn::on_pbLastQuestion_clicked() +{ +CDlgViewQuestion dlg(this); + dlg.go(m_pCatalog, m_pLastQuestion, m_uLastAnswerMask); +} + +void CDlgLearn::on_pbSkip_clicked() +{ + m_uLastAnswerMask=0; + nextQuestion(); +} + +void CDlgLearn::on_pbQuit_clicked() +{ + m_pCatalog->updateStatistic(); + accept(); +} + +void CDlgLearn::on_pbLearnAssistant_clicked() +{ +CDlgLearnAssistant dlg(this); +CChapter *pChapter=0; + if (!dlg.setup(m_pCatalog)) + { + QMessageBox::information(this, tr("Information"), tr("Derzeit gibt es keine Empfehlung des Lernassistentes.")); + return; + } + if (dlg.exec() != QDialog::Accepted) return; + pChapter = dlg.selectedChapter(); + if (pChapter == 0) return; + setNewChapter(pChapter); +} + +void CDlgLearn::onUpdateDS() +{ + labDSTime->setText(QString("%1 h %2 m").arg(m_ds.timeExpediture()/1000/3600).arg(m_ds.timeExpediture()/1000/60 % 60, 2, 10, QChar('0'))); + labDSCount->setText(QString::number(m_ds.clickedCount())); + labDSCorrect->setText(QString::number(m_ds.clickedCorrect())); + labDSWrong->setText(QString::number(m_ds.clickedWrong())); +} diff --git a/dlglearn.h b/dlglearn.h new file mode 100644 index 0000000..906037b --- /dev/null +++ b/dlglearn.h @@ -0,0 +1,84 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef DLGLEARN_H +#define DLGLEARN_H + +#include +#include +#include +#include "question.h" + +#include "ui_dlglearn.h" + +class CChapter; +class CCatalog; + +class CDlgLearn : public QDialog, Ui::DlgLearn +{ + Q_OBJECT +public: + CDlgLearn (QWidget *pParent=0); + ~CDlgLearn(); + + void go (CCatalog *pCatalog, CChapter *pChapter); + +protected: + void setNewChapter(CChapter *pChapter); + void updateStatistic(); + void updateLearnAssistant(); + void nextQuestion(); + CQuestion *findNextQuestion(); + CQuestion *findNextPoolQuestion(); + CQuestion *findNextTargetQuestion(); + + void handleAnswer(const int i); + +protected slots: + void on_pbAnswerA_clicked(); + void on_pbAnswerB_clicked(); + void on_pbAnswerC_clicked(); + void on_pbAnswerD_clicked(); + void on_pbShowHelper_clicked(); + void on_pbSkip_clicked(); + void on_pbLastQuestion_clicked(); + void on_pbQuit_clicked(); + void on_pbLearnAssistant_clicked(); + void onUpdateDS(); + +protected: + CCatalog *m_pCatalog; + CChapter *m_pChapter; + QList m_listQuestion; + CQuestion *m_pQuestion; + CQuestion *m_pLastQuestion; + unsigned m_uLastAnswerMask; + bool m_bHintsUsed; + QTime m_timeElapsed; + unsigned m_uElapsedBeforeBreak; // if the user makes a break + bool m_bMsgBoxTargetReachedShowed; + CDayStatistic m_ds; + + bool m_bAssistantEnable; + bool m_bCheatEnable; +}; + +#endif diff --git a/dlglearn.ui b/dlglearn.ui new file mode 100644 index 0000000..033cd51 --- /dev/null +++ b/dlglearn.ui @@ -0,0 +1,1139 @@ + + DlgLearn + + + + 0 + 0 + 786 + 644 + + + + Fragen lernen + + + + 9 + + + 6 + + + + + 0 + + + 6 + + + + + Frage + + + + 9 + + + 6 + + + + + + MS Shell Dlg 2 + 10 + + + + true + + + + + + + + + + Antwort + + + + 9 + + + 6 + + + + + 0 + + + 6 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 50 + 16777215 + + + + Hotkey: A + + + &A + + + A + + + + + + + + 50 + 16777215 + + + + Hotkey: B + + + &B + + + B + + + + + + + + 50 + 16777215 + + + + Hotkey: C + + + &C + + + C + + + + + + + + 50 + 16777215 + + + + Hotkey: D + + + &D + + + D + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + 100 + 0 + + + + Hotkey: S + + + Ãœber&springen + + + S + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + 0 + + + 6 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 100 + 0 + + + + Hotkey: T + + + &Tipps anzeigen + + + T + + + + + + + false + + + + 100 + 0 + + + + Hotkey: L + + + &Letzte Frage... + + + L + + + + + + + + 100 + 0 + + + + &Beenden + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 0 + + + + + + + + + + + + + 0 + + + 6 + + + + + + 350 + 16 + + + + + 400 + 16777215 + + + + Statistik + + + + 9 + + + 6 + + + + + + MS Shell Dlg 2 + 10 + 75 + false + true + false + false + + + + Kapitel-Name + + + + + + + Lernfortschritts-Statistik zu diesem Kapitel: + + + + + + + 0 + + + 6 + + + + + Durchschnittlicher Lernfortschritt: + + + + + + + TextLabel + + + + + + + + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Anfänger: + + + 20 + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0.0 = Ahnungslos<br>1.0 = Anfänger<br>2.0 = Fortgeschritten<br>3.0 = Experte<br>4.0 = Freak<br>5.0 = Professor + + + 0.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Freak: + + + 20 + + + + + + + TextLabel + + + + + + + Gesamt-Anzahl der Fragen dieses Kapitels + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Fortgeschritten: + + + 20 + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 40 + 0 + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 1 + 5 + 0 + 0 + + + + + + + :/icons/16x16/level3.png + + + + + + + + 1 + 5 + 0 + 0 + + + + + + + :/icons/16x16/level0.png + + + + + + + Anzahl der Fragen: + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + TextLabel + + + + + + + Experte: + + + 20 + + + + + + + Professor: + + + 20 + + + + + + + + 1 + 5 + 0 + 0 + + + + + + + :/icons/16x16/level2.png + + + + + + + + + + :/icons/16x16/level0.png + + + + + + + Smiley mit Doktorhut + + + + + + :/icons/16x16/level5.png + + + + + + + + 1 + 5 + 0 + 0 + + + + + + + :/icons/16x16/level4.png + + + + + + + TextLabel + + + + + + + + 3 + 5 + 0 + 0 + + + + Ahnungslos: + + + 20 + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + 1 + 5 + 0 + 0 + + + + + + + :/icons/16x16/level1.png + + + + + + + + + Qt::Horizontal + + + + + + + + MS Shell Dlg 2 + 10 + 75 + false + true + false + false + + + + Frage + + + + + + + Statistik zu dieser Frage: + + + + + + + 0 + + + 6 + + + + + + 3 + 5 + 0 + 0 + + + + Lernfortschritt: + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Anzahl Abfragen: + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Zuletzt hintereinander: + + + + + + + davon falsch: + + + 10 + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + davon richtig: + + + 10 + + + + + + + + 40 + 0 + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + letze Abfrage: + + + + + + + noch nie + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0 + + + 6 + + + + + Ahnungslos + + + + + + + + 16 + 16777215 + + + + + + + :/icons/16x16/level0.png + + + + + + + + + + + Qt::Horizontal + + + + + + + + 10 + 75 + true + + + + Heutiger Lernaufwand + + + + + + + 0 + + + 6 + + + + + 0 h 00 m + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Anzahl Abfragen: + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + davon richtig: + + + 10 + + + + + + + Dauer: + + + + + + + davon falsch: + + + 10 + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Vertical + + + + 330 + 20 + + + + + + + + + + + + 400 + 16777215 + + + + Lernassistent + + + + 9 + + + 6 + + + + + 0 + + + 6 + + + + + + + + :/icons/16x16/idea.png + + + + + + + + 7 + 1 + 0 + 0 + + + + TextLabel + + + true + + + + + + + + + 0 + + + 6 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Kapitelübersicht ... + + + :/icons/16x16/idea_info.png + + + + + + + + + + + + + + + + + diff --git a/dlglearnassistant.cpp b/dlglearnassistant.cpp new file mode 100644 index 0000000..b7b412b --- /dev/null +++ b/dlglearnassistant.cpp @@ -0,0 +1,117 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "dlglearnassistant.h" + +#include +#include + +#include "catalog.h" + +CDlgLearnAssistant::CDlgLearnAssistant (QWidget *pParent) : QDialog(pParent) +{ + m_pCatalog=0; + setupUi(this); + + tvChapters->setModel (&m_modelChapter); + tvChapters->header()->setStretchLastSection(false); + tvChapters->header()->setResizeMode (0, QHeaderView::Stretch); + tvChapters->header()->setResizeMode (1, QHeaderView::Interactive); + tvChapters->header()->setResizeMode (3, QHeaderView::Interactive); + tvChapters->header()->resizeSection(1, 50); + tvChapters->header()->resizeSection(2, 140); + tvChapters->header()->resizeSection(3, 20); +} + + +bool CDlgLearnAssistant::setup(CCatalog *pCatalog) +{ +bool bEnableRepeat; +QList listAll, listNow, listToday, listSelected; +CChapter *c; + + m_pCatalog = pCatalog; + if (pCatalog->recommendationCount (CChapter::RecommendationRepeatToday) > 0) + { + bEnableRepeat = true; + labRepeat->setText(tr("Es sind %1 Fragen vorhanden").arg(pCatalog->recommendedQuestions().count())); + rbRepeat->setChecked(true); + } + else + { + bEnableRepeat = false; + labRepeat->setText(tr("Keine Fragen vorhanden.")); + rbList->setChecked(true); + } + rbRepeat->setEnabled(bEnableRepeat); + labRepeat->setEnabled(bEnableRepeat); + + // update list + listAll = pCatalog->chapters(); + for (int i=0; iisRecommendedNow(pCatalog)) + listNow << c; + else if (c->recommendation() == CChapter::RecommendationLearnNew) + listToday << c; + } + + listSelected << listNow << listToday; + m_modelChapter.setModelData (pCatalog, listSelected); + + if (!bEnableRepeat && listSelected.isEmpty()) return false; + + updateEnable(); + + return true; +} + +void CDlgLearnAssistant::updateEnable() +{ + labRepeat->setEnabled(rbRepeat->isChecked()); + tvChapters->setEnabled(rbList->isChecked()); +} + + +void CDlgLearnAssistant::on_buttonBox_accepted() +{ +QModelIndexList list = tvChapters->selectionModel()->selectedIndexes(); + if (rbList->isChecked() && list.isEmpty()) + { + QMessageBox::information(this, tr("Information"), tr("Bitte markieren Sie in der Liste ein Kapitel, das Sie lernen möchten!")); + return; + } + accept(); +} + +CChapter* CDlgLearnAssistant::selectedChapter() +{ +QModelIndexList list = tvChapters->selectionModel()->selectedIndexes(); + if (rbRepeat->isChecked()) + return m_pCatalog; + if (rbList->isChecked()) + { + if (list.isEmpty()) return 0; + return (CChapter*)list.first().internalPointer(); + } + return 0; +} diff --git a/dlglearnassistant.h b/dlglearnassistant.h new file mode 100644 index 0000000..28a4ba3 --- /dev/null +++ b/dlglearnassistant.h @@ -0,0 +1,56 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef DLGLEARNASSISTANT_H +#define DLGLEARNASSISTANT_H + +#include +#include "ui_dlglearnassistant.h" + +#include "chaptermodel.h" + +class CDlgLearnAssistant : public QDialog, Ui::DlgLearnAssistant +{ + Q_OBJECT +public: + CDlgLearnAssistant (QWidget *pParent=0); + ~CDlgLearnAssistant () {} + + bool setup(CCatalog *pCatalog); + CChapter* selectedChapter(); + +protected slots: + void on_buttonBox_accepted(); + inline void on_rbList_toggled() { updateEnable(); } + inline void on_rbRepeat_toggled() { updateEnable(); } + +protected: + void updateEnable(); + +protected: + CCatalog *m_pCatalog; + CChapterModel m_modelChapter; + +// CRecommendation m_recomDeepen; +// CRecommendation m_recomRepeat; +}; + +#endif diff --git a/dlglearnassistant.ui b/dlglearnassistant.ui new file mode 100644 index 0000000..a38797b --- /dev/null +++ b/dlglearnassistant.ui @@ -0,0 +1,112 @@ + + DlgLearnAssistant + + + + 0 + 0 + 493 + 422 + + + + Lern-Assistent + + + :/icons/16x16/idea_info.png + + + + 9 + + + 6 + + + + + Vorschläge des Lern-Assistents + + + + 9 + + + 6 + + + + + Alle heute zu wiederholenden Fragen lernen + + + + + + + TextLabel + + + 20 + + + + + + + Kapitel aus folgender Liste lernen + + + + + + + false + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::NoButton|QDialogButtonBox::Ok + + + true + + + + + + + rbRepeat + rbList + buttonBox + + + + + + + buttonBox + rejected() + DlgLearnAssistant + reject() + + + 293 + 397 + + + 416 + 418 + + + + + diff --git a/dlglearnstatistic.cpp b/dlglearnstatistic.cpp new file mode 100644 index 0000000..6a8cce4 --- /dev/null +++ b/dlglearnstatistic.cpp @@ -0,0 +1,417 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "dlglearnstatistic.h" +#include "chapter.h" +#include "tools.h" +#include "plotwidget.h" + +#include + +CDlgLearnStatistic::CDlgLearnStatistic(QWidget *pParent) : QDialog(pParent) +{ + + + m_pChapter = 0; + setupUi(this); + cbPeriod->addItem(tr("2 Wochen"), 14); + cbPeriod->addItem(tr("1 Monat"), 30); + cbPeriod->addItem(tr("3 Monate"), 90); + cbPeriod->addItem(tr("6 Monate"), 30*6); + cbPeriod->addItem(tr("1 Jahr"), 365); + cbPeriod->setCurrentIndex(0); + + m_pPlotLevel = new CPlotWidget(groupLevel); + m_pPlotLevel->setFrameShape(QFrame::Panel); + m_pPlotLevel->setFrameShadow(QFrame::Sunken); + groupLevel->layout()->addWidget(m_pPlotLevel); + + m_pPlotLevel->setBorderDistance(25, 5, 5, 25); + m_pPlotLevel->setLimitY(0.0, 5.5); + m_pPlotLevel->setBorderPen(QPen(Qt::black)); + m_pPlotLevel->setBorder(CPlotWidget::BorderLeft | CPlotWidget::BorderBottom); + m_pPlotLevel->setTicY(1.0); + + QList listTics; + for (int i=0; i<=LEVEL_MAX; i++) + { + CPlotWidgetTic tic (i, QPixmap(CQuestion::levelIconName(i))); + listTics.append(tic); + } + m_pPlotLevel->setTicListY(listTics); + + m_pPlotClicked = new CPlotWidget(groupClicked); + m_pPlotClicked->setFrameShape(QFrame::Panel); + m_pPlotClicked->setFrameShadow(QFrame::Sunken); + groupClicked->layout()->addWidget(m_pPlotClicked); + + m_pPlotClicked->setType(CPlotWidget::PlotBarsSum); + m_pPlotClicked->setBarOffset(-0.5); + m_pPlotClicked->setBorderDistance(25, 5, 5, 25); + + // Zeitaufwand + cbPeriod2->addItem(tr("2 Wochen"), 14); + cbPeriod2->addItem(tr("1 Monat"), 30); + cbPeriod2->addItem(tr("3 Monate"), 90); + cbPeriod2->addItem(tr("6 Monate"), 30*6); + cbPeriod2->addItem(tr("1 Jahr"), 365); + cbPeriod2->setCurrentIndex(0); + + m_pPlotTime = new CPlotWidget(groupTimeExpediture); + m_pPlotTime->setFrameShape(QFrame::Panel); + m_pPlotTime->setFrameShadow(QFrame::Sunken); + groupTimeExpediture->layout()->addWidget(m_pPlotTime); + + m_pPlotTime->setType(CPlotWidget::PlotBarsSum); + m_pPlotTime->setBarOffset(-0.5); + m_pPlotTime->setBorderDistance(25, 5, 5, 25); +/* + m_pPlotTimePerQuestion = new CPlotWidget(groupTimeExpediturePerQuestion); + m_pPlotTimePerQuestion->setFrameShape(QFrame::Panel); + m_pPlotTimePerQuestion->setFrameShadow(QFrame::Sunken); + groupTimeExpediturePerQuestion->layout()->addWidget(m_pPlotTimePerQuestion); + + m_pPlotTimePerQuestion->setType(CPlotWidget::PlotLines); + m_pPlotTimePerQuestion->setBorderDistance(25, 5, 5, 25); +*/ +} + +void CDlgLearnStatistic::go(CChapter *pChapter) +{ + m_pChapter = pChapter; + updateTable(); + updateHistory(); + updateTimeExpediture(); + exec(); +} + + +void CDlgLearnStatistic::updateTable() +{ +const int w=60, h=16; +QList listQuestion = m_pChapter->questionPool(); +CDayStatistic ds; + + // CHAPTER STATISTICS + labChapter->setText (m_pChapter->text()); + labChapterCount->setText(QString("%1").arg(listQuestion.size())); + labChapterVeryOften->setText(QString("%1").arg(m_pChapter->countQuestion(0))); + labChapterOften->setText(QString("%1").arg(m_pChapter->countQuestion(1))); + labChapterNormal->setText(QString("%1").arg(m_pChapter->countQuestion(2))); + labChapterRare->setText(QString("%1").arg(m_pChapter->countQuestion(3))); + labChapterVeryRare->setText(QString("%1").arg(m_pChapter->countQuestion(4))); + labChapterExtremeRare->setText(QString("%1").arg(m_pChapter->countQuestion(5))); + labChapterAvgText->setText(m_pChapter->levelAvgText()); + labChapterAvgIcon->setPixmap(m_pChapter->levelAvgPixmap()); + labChapterAvgIcon->setToolTip(QString("Kennzahl: %1").arg(m_pChapter->levelAvg(), 4, 'g', 2)); + + double dQuestionCount = listQuestion.size(), dPercent=0.0; + dPercent = (double)m_pChapter->countQuestion(0)/dQuestionCount; + labChapterVeryOftenBar->setPixmap(createProgressBar(w, h, dPercent)); + labChapterVeryOftenBar->setToolTip(QString("%1 %").arg(dPercent * 100, 0, 'f', 1)); + dPercent = (double)m_pChapter->countQuestion(1)/dQuestionCount; + labChapterOftenBar->setPixmap(createProgressBar(w, h, dPercent)); + labChapterOftenBar->setToolTip(QString("%1 %").arg(dPercent * 100, 0, 'f', 1)); + dPercent = (double)m_pChapter->countQuestion(2)/dQuestionCount; + labChapterNormalBar->setPixmap(createProgressBar(w, h, dPercent)); + labChapterNormalBar->setToolTip(QString("%1 %").arg(dPercent * 100, 0, 'f', 1)); + dPercent = (double)m_pChapter->countQuestion(3)/dQuestionCount; + labChapterRareBar->setPixmap(createProgressBar(w, h, dPercent)); + labChapterRareBar->setToolTip(QString("%1 %").arg(dPercent * 100, 0, 'f', 1)); + dPercent = (double)m_pChapter->countQuestion(4)/dQuestionCount; + labChapterVeryRareBar->setPixmap(createProgressBar(w, h, dPercent)); + labChapterVeryRareBar->setToolTip(QString("%1 %").arg(dPercent * 100, 0, 'f', 1)); + dPercent = (double)m_pChapter->countQuestion(5)/dQuestionCount; + labChapterExtremeRareBar->setPixmap(createProgressBar(w, h, dPercent)); + labChapterExtremeRareBar->setToolTip(QString("%1 %").arg(dPercent * 100, 0, 'f', 1)); + + ds = m_pChapter->dayStatistic(QDate()); + labClickCount->setText(QString::number(ds.clickedCount())); + labClickCorrect->setText(QString::number(ds.clickedCorrect())); + labClickWrong->setText(QString::number(ds.clickedWrong())); + labClickCorrectP->setText(ds.clickedCount() != 0 ? QString("%1 %").arg(100.0 * ds.clickedCorrect() / ds.clickedCount(), 0, 'f', 1) : "--"); + labClickWrongP->setText(ds.clickedCount() != 0 ? QString("%1 %").arg(100.0 * ds.clickedWrong() / ds.clickedCount(), 0, 'f', 1) : "--"); + labTimeExpediture->setText(QString("%1 h %2 m %3 s").arg(ds.timeExpediture() / 3600000).arg(ds.timeExpediture() / 60000 % 60, 2, 10, QChar('0')).arg(ds.timeExpediture()/1000 % 60, 2, 10, QChar('0'))); + labTimeExpeditureCorrect->setText(QString("%1 h %2 m %3 s").arg(ds.timeExpeditureCorrect() / 3600000).arg(ds.timeExpeditureCorrect() / 60000 % 60, 2, 10, QChar('0')).arg(ds.timeExpeditureCorrect()/1000 % 60, 2, 10, QChar('0'))); + labTimeExpeditureWrong->setText(QString("%1 h %2 m %3 s").arg(ds.timeExpeditureWrong() / 3600000).arg(ds.timeExpeditureWrong() / 60000 % 60, 2, 10, QChar('0')).arg(ds.timeExpeditureWrong()/1000 % 60, 2, 10, QChar('0'))); + labTimeExpeditureCorrectP->setText(ds.timeExpediture() != 0 ? QString("%1 %").arg(100.0 * ds.timeExpeditureCorrect() / ds.timeExpediture(), 0, 'f', 1) : "--"); + labTimeExpeditureWrongP->setText(ds.timeExpediture() != 0 ? QString("%1 %").arg(100.0 * ds.timeExpeditureWrong() / ds.timeExpediture(), 0, 'f', 1) : "--"); + labTimePerQuestion->setText(ds.clickedCount() != 0 ? QString("%2 m %3 s").arg(ds.timeExpediture() / ds.clickedCount() / 60000).arg(ds.timeExpediture() / 1000 / ds.clickedCount() % 60, 2, 10, QChar('0')) : "--"); + labTimePerQuestionCorrect->setText(ds.clickedCorrect() != 0 ? QString("%2 m %3 s").arg(ds.timeExpeditureCorrect() / ds.clickedCorrect() / 60000).arg(ds.timeExpeditureCorrect() / 1000 / ds.clickedCorrect() % 60, 2, 10, QChar('0')) : "--"); + labTimePerQuestionWrong->setText(ds.clickedWrong() != 0 ? QString("%2 m %3 s").arg(ds.timeExpeditureWrong() / ds.clickedWrong() / 60000).arg(ds.timeExpeditureWrong() / 1000 / ds.clickedWrong() % 60, 2, 10, QChar('0')) : "--"); +} + +void CDlgLearnStatistic::on_cbPeriod_currentIndexChanged(int index) +{ + Q_UNUSED(index); + updateHistory(); +} + +void CDlgLearnStatistic::updateHistory() +{ +int iDays = cbPeriod->itemData(cbPeriod->currentIndex()).toInt(), idx=0; +CPlotWidgetCurve curveLevel, curveClickedCorrect, curveClickedWrong; +QDate date = QDate::currentDate(); +CDayStatistic ds; +QList listTics; +CPlotWidgetTic tic; +bool bAddTic=false; +unsigned uMaxClicked=0; + + if (m_pChapter == 0) return; + setCursor(Qt::WaitCursor); + date = date.addDays(-iDays); + + tic.setLineType(CPlotWidgetTic::LineNone); + tic.setFillType(CPlotWidgetTic::FillAll); + tic.setPen(QPen(Qt::darkGray)); + + while (date <= QDate::currentDate()) + { + if (bAddTic) + { + bAddTic=false; + tic.setPos(idx-1); + if (listTics.size() % 2) + tic.setBrush(QBrush(Qt::white)); + else + tic.setBrush(QBrush(QColor(232,232,232))); + listTics.append(tic); + } + + ds = m_pChapter->dayStatistic(date); + curveLevel.append(CPlotWidgetPoint(idx, ds.level())); + curveClickedCorrect.append(CPlotWidgetPoint((double)idx, ds.clickedCorrect())); + curveClickedWrong.append(CPlotWidgetPoint((double)idx, ds.clickedWrong())); + if (ds.clickedCount() > uMaxClicked) uMaxClicked = ds.clickedCount(); + + date = date.addDays(1); + + // X-Tics + if (iDays <= 14) + { // daily tics + tic.setText(date.toString("ddd")); + tic.setWidth(1); + if (date.dayOfWeek() == Qt::Monday) + tic.setLineType(CPlotWidgetTic::LinePlot); + else + tic.setLineType(CPlotWidgetTic::LineNone); + + bAddTic=true; + } + else if (iDays <= 90 && date.dayOfWeek() == Qt::Monday) + { + tic.setText(QString("KW %1").arg(date.weekNumber())); + tic.setWidth(7); + bAddTic=true; + } + else if (iDays > 90 && date.day() == 1) + { + tic.setText(date.toString("MMM")); + tic.setWidth(date.daysInMonth()); + if (date.month() == 1) + tic.setLineType(CPlotWidgetTic::LinePlot); + else + tic.setLineType(CPlotWidgetTic::LineNone); + bAddTic=true; + } + + idx++; + } + + m_pPlotLevel->setTicListX(listTics); + m_pPlotClicked->setTicListX(listTics); + + + curveLevel.setPen(QPen(Qt::darkBlue)); + curveClickedCorrect.setPen(QPen(Qt::green)); + curveClickedCorrect.setBrush(QBrush(Qt::green)); + curveClickedWrong.setPen(QPen(Qt::red)); + curveClickedWrong.setBrush(QBrush(Qt::red)); + + + m_pPlotLevel->clearCurves(); + m_pPlotLevel->appendCurve(curveLevel); + m_pPlotLevel->update(); + + m_pPlotClicked->clearCurves(); + m_pPlotClicked->appendCurve(curveClickedCorrect); + m_pPlotClicked->appendCurve(curveClickedWrong); + m_pPlotClicked->update(); + + unsigned uDiffBase[] = {1, 2, 5, 10}, uDiffMulti=1, uDiff=0, uDiffCount=0; + idx = 0; + do + { + if (idx == 4) { idx = 0; uDiffMulti*=10; } + uDiff = uDiffBase[idx] * uDiffMulti; + uDiffCount = (uMaxClicked / uDiff) + 1; + idx++; + } + while (uDiffCount > 9); + + m_pPlotClicked->setLimitY(0, uDiffCount * uDiff + uDiff/2); + + listTics.clear(); + tic.clear(); + tic.setTextFlags(Qt::AlignRight | Qt::AlignVCenter); + for (unsigned u=0; u<=uDiffCount * uDiff; u+=uDiff) + { + tic.setPos(u); + tic.setText(QString("%1").arg(u)); + listTics.append(tic); + } + m_pPlotClicked->setTicListY(listTics); + + setCursor(Qt::ArrowCursor); +} + + +void CDlgLearnStatistic::on_cbPeriod2_currentIndexChanged(int index) +{ + Q_UNUSED(index); + updateTimeExpediture(); +} + +void CDlgLearnStatistic::updateTimeExpediture() +{ +int iDays = cbPeriod2->itemData(cbPeriod2->currentIndex()).toInt(), idx=0; +CPlotWidgetCurve curveTimeWrong, curveTimeCorrect,curveTimePQWrong, curveTimePQCorrect; // PQ=per question +QDate date = QDate::currentDate(); +CDayStatistic ds; +QList listTics; +CPlotWidgetTic tic; +bool bAddTic=false; +unsigned uMaxTime=0; + + if (m_pChapter == 0) return; + setCursor(Qt::WaitCursor); + date = date.addDays(-iDays); + + tic.setLineType(CPlotWidgetTic::LineNone); + tic.setFillType(CPlotWidgetTic::FillAll); + tic.setPen(QPen(Qt::darkGray)); + + while (date <= QDate::currentDate()) + { + if (bAddTic) + { + bAddTic=false; + tic.setPos(idx-1); + if (listTics.size() % 2) + tic.setBrush(QBrush(Qt::white)); + else + tic.setBrush(QBrush(QColor(232,232,232))); + listTics.append(tic); + } + + ds = m_pChapter->dayStatistic(date); + curveTimeWrong.append(CPlotWidgetPoint(idx, (double)ds.timeExpeditureWrong()/1000.0/60.0)); + curveTimeCorrect.append(CPlotWidgetPoint(idx, (double)ds.timeExpeditureCorrect()/1000.0/60.0)); +/* if (ds.clickedWrong() > 0) + curveTimePQWrong.append(CPlotWidgetPoint(idx, (double)ds.timeExpeditureWrong()/1000.0/60.0/(double)ds.clickedWrong())); + else + curveTimePQWrong.append(CPlotWidgetPoint(idx, 0)); + if (ds.clickedCorrect() > 0) + curveTimePQCorrect.append(CPlotWidgetPoint(idx, (double)ds.timeExpeditureCorrect()/1000.0/60.0/(double)ds.clickedCorrect())); + else + curveTimePQWrong.append(CPlotWidgetPoint(idx, 0));*/ + if (ds.timeExpediture()/1000/60 > uMaxTime) uMaxTime = ds.timeExpediture()/1000/60; + + date = date.addDays(1); + + // X-Tics + if (iDays <= 14) + { // daily tics + tic.setText(date.toString("ddd")); + tic.setWidth(1); + if (date.dayOfWeek() == Qt::Monday) + tic.setLineType(CPlotWidgetTic::LinePlot); + else + tic.setLineType(CPlotWidgetTic::LineNone); + + bAddTic=true; + } + else if (iDays <= 90 && date.dayOfWeek() == Qt::Monday) + { + tic.setText(QString("KW %1").arg(date.weekNumber())); + tic.setWidth(7); + bAddTic=true; + } + else if (iDays > 90 && date.day() == 1) + { + tic.setText(date.toString("MMM")); + tic.setWidth(date.daysInMonth()); + if (date.month() == 1) + tic.setLineType(CPlotWidgetTic::LinePlot); + else + tic.setLineType(CPlotWidgetTic::LineNone); + bAddTic=true; + } + + idx++; + } + + m_pPlotTime->setTicListX(listTics); + //m_pPlotTimePerQuestion->setTicListX(listTics); + + curveTimeCorrect.setPen(QPen(Qt::green)); + curveTimeCorrect.setBrush(QBrush(Qt::green)); + curveTimeWrong.setPen(QPen(Qt::red)); + curveTimeWrong.setBrush(QBrush(Qt::red)); + + m_pPlotTime->clearCurves(); + m_pPlotTime->appendCurve(curveTimeCorrect); + m_pPlotTime->appendCurve(curveTimeWrong); + m_pPlotTime->update(); +/* + m_pPlotTimePerQuestion->clearCurves(); + m_pPlotTimePerQuestion->appendCurve(curveTimePQCorrect); + m_pPlotTimePerQuestion->appendCurve(curveTimePQWrong); + m_pPlotTimePerQuestion->setAutoLimitRoundY(0.1); + m_pPlotTimePerQuestion->update(); +*/ + unsigned uDiffBase[] = {1, 2, 5, 10}, uDiffMulti=1, uDiff=0, uDiffCount=0; + idx = 0; + do + { + if (idx == 4) { idx = 0; uDiffMulti*=10; } + uDiff = uDiffBase[idx] * uDiffMulti; + uDiffCount = (uMaxTime / uDiff) + 1; + idx++; + } + while (uDiffCount > 9); + + m_pPlotTime->setLimitY(0, uDiffCount * uDiff + uDiff/2); + + listTics.clear(); + tic.clear(); + tic.setTextFlags(Qt::AlignRight | Qt::AlignVCenter); + for (unsigned u=0; u<=uDiffCount * uDiff; u+=uDiff) + { + tic.setPos(u); + tic.setText(QString("%1").arg(u)); + listTics.append(tic); + } + m_pPlotTime->setTicListY(listTics); + + setCursor(Qt::ArrowCursor); +} diff --git a/dlglearnstatistic.h b/dlglearnstatistic.h new file mode 100644 index 0000000..a54169e --- /dev/null +++ b/dlglearnstatistic.h @@ -0,0 +1,54 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#pragma once + +#include +#include "ui_dlglearnstatistic.h" + +class CChapter; +class CPlotWidget; + +class CDlgLearnStatistic : public QDialog, Ui::DlgLearnStatistic +{ +Q_OBJECT +public: + CDlgLearnStatistic(QWidget *pParent=0); + ~CDlgLearnStatistic() {} + + void go(CChapter *pChapter); + +protected slots: + void on_cbPeriod_currentIndexChanged(int index); + void on_cbPeriod2_currentIndexChanged(int index); + +protected: + void updateTable(); + void updateHistory(); + void updateTimeExpediture(); + +protected: + CChapter *m_pChapter; + CPlotWidget *m_pPlotLevel; + CPlotWidget *m_pPlotClicked; + CPlotWidget *m_pPlotTime; +// CPlotWidget *m_pPlotTimePerQuestion; +}; diff --git a/dlglearnstatistic.ui b/dlglearnstatistic.ui new file mode 100644 index 0000000..bd8efd1 --- /dev/null +++ b/dlglearnstatistic.ui @@ -0,0 +1,991 @@ + + DlgLearnStatistic + + + + 0 + 0 + 502 + 552 + + + + Lernstatistik + + + + 9 + + + 6 + + + + + + MS Shell Dlg 2 + 10 + 75 + false + true + false + false + + + + Kapitel-Name + + + Qt::AlignCenter + + + + + + + 0 + + + + Aktueller Status + + + + 9 + + + 6 + + + + + + 75 + true + + + + Lernfortschritt + + + + + + + 0 + + + 6 + + + + + Durchschnittlicher Lernfortschritt: + + + + + + + TextLabel + + + + + + + + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Anfänger: + + + 20 + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0.0 = Ahnungslos<br>1.0 = Anfänger<br>2.0 = Fortgeschritten<br>3.0 = Experte<br>4.0 = Freak<br>5.0 = Professor + + + 0.0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Freak: + + + 20 + + + + + + + TextLabel + + + + + + + Gesamt-Anzahl der Fragen dieses Kapitels + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Fortgeschritten: + + + 20 + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 40 + 0 + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 1 + 5 + 0 + 0 + + + + + + + :/icons/16x16/level3.png + + + + + + + + 1 + 5 + 0 + 0 + + + + + + + :/icons/16x16/level0.png + + + + + + + Anzahl der Fragen: + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + TextLabel + + + + + + + Experte: + + + 20 + + + + + + + Professor: + + + 20 + + + + + + + + 1 + 5 + 0 + 0 + + + + + + + :/icons/16x16/level2.png + + + + + + + + + + :/icons/16x16/level0.png + + + + + + + Smiley mit Doktorhut + + + + + + :/icons/16x16/level5.png + + + + + + + + 1 + 5 + 0 + 0 + + + + + + + :/icons/16x16/level4.png + + + + + + + TextLabel + + + + + + + + 3 + 5 + 0 + 0 + + + + Ahnungslos: + + + 20 + + + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + 1 + 5 + 0 + 0 + + + + + + + :/icons/16x16/level1.png + + + + + + + + + Qt::Horizontal + + + + + + + + 75 + true + + + + Abfragen + + + + + + + 0 + + + 6 + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0.0 % + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + davon falsche Antworten: + + + 20 + + + + + + + davon richtige Antworten: + + + 20 + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Abfragen gesamt: + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0.0 % + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + true + + + + Anzahl + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + true + + + + Anteil + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + + + + + + + 75 + true + + + + Zeitaufwand + + + + + + + 0 + + + 6 + + + + + 0.0 % + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0.0 % + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + davon für richtige Antworten: + + + 20 + + + + + + + 0 m 0 s + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0 m 0 s + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Zeitaufwand gesamt: + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + davon für falsche Antworten: + + + 20 + + + + + + + 0 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + true + + + + Dauer + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + true + + + + Anteil + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + true + + + + Ø pro Frage + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 0 m 0 s + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + Zeitlicher Verlauf + + + + 9 + + + 6 + + + + + 0 + + + 6 + + + + + Zeitraum: + + + + + + + + 200 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 200 + + + + Lernfortschritt + + + + 9 + + + 6 + + + + + + + + + 0 + 200 + + + + Abfragen + + + + 9 + + + 6 + + + + + + + + + Zeitaufwand + + + + 9 + + + 6 + + + + + 0 + + + 6 + + + + + Zeitraum: + + + + + + + + 200 + 0 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 200 + + + + Zeitaufwand [min] + + + + 9 + + + 6 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Ok + + + true + + + + + + + tabWidget + buttonBox + cbPeriod + + + + + buttonBox + accepted() + DlgLearnStatistic + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + DlgLearnStatistic + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/dlgviewquestion.cpp b/dlgviewquestion.cpp new file mode 100644 index 0000000..a1f27b2 --- /dev/null +++ b/dlgviewquestion.cpp @@ -0,0 +1,43 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + + +#include "dlgviewquestion.h" +#include "catalog.h" + +void CDlgViewQuestion::go (CCatalog *pCatalog, CQuestion *pQuestion, const unsigned uAnswer) +{ +QString str; + Q_ASSERT (pCatalog != 0); + if (!pQuestion) return; + str = pQuestion->learnText(pCatalog, true, true); + str += "

"; + if (uAnswer != 0 && pQuestion->isCorrectAnswer(uAnswer)) + str += ""; + else if (uAnswer != 0) + str += ""; + + str += pQuestion->correctionText(uAnswer); + str += "

"; + textBrowser->setHtml(str); + setWindowTitle (tr("Frage") + " " + pQuestion->id()); + exec(); +} diff --git a/dlgviewquestion.h b/dlgviewquestion.h new file mode 100644 index 0000000..86fafd2 --- /dev/null +++ b/dlgviewquestion.h @@ -0,0 +1,44 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef DLGVIEWQUESTION_H +#define DLGVIEWQUESTION_H + +#include +#include "ui_dlgviewquestion.h" + +class CQuestion; +class CCatalog; + +class CDlgViewQuestion : public QDialog, Ui::DlgViewQuestion +{ + Q_OBJECT +public: + CDlgViewQuestion(QWidget *pParent) : QDialog(pParent) { setupUi(this); } + ~CDlgViewQuestion() { } + + void go (CCatalog *pCatalog, CQuestion *pQuestion, const unsigned uAnswer=0); + +protected: +}; + + +#endif // DLGVIEWQUESTION_H diff --git a/dlgviewquestion.ui b/dlgviewquestion.ui new file mode 100644 index 0000000..af758bc --- /dev/null +++ b/dlgviewquestion.ui @@ -0,0 +1,99 @@ + + DlgViewQuestion + + + + 0 + 0 + 658 + 577 + + + + Frage ansehen + + + + 9 + + + 6 + + + + + + 10 + + + + + + + + 0 + + + 6 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + OK + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + textBrowser + pbOK + + + + + pbOK + clicked() + DlgViewQuestion + accept() + + + 367 + 566 + + + 358 + 293 + + + + + diff --git a/error.cpp b/error.cpp new file mode 100644 index 0000000..21478f9 --- /dev/null +++ b/error.cpp @@ -0,0 +1,98 @@ +/*************************************************************************** + * Copyright (C) 1999-2005 by Oliver Saal * + * http://www.oliver-saal.de/ * + * osaal@gmx.de * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "error.h" + +#include +#include + +#ifdef ERROR_USE_SQL +#include +#include +//#include +#include +#endif + +CError::CError () +{ + m_uLine = 0; +} + +CError::CError (const QString& strFunc, const QString& strFile, const unsigned int uLine) +{ + m_strFunction = strFunc; + m_strFile = strFile; + m_uLine = uLine; +} + +CError::CError (const QString& strText, const QString& strFunc, const QString& strFile, const unsigned int uLine) +{ + m_strText = strText; + m_strFunction = strFunc; + m_strFile = strFile; + m_uLine = uLine; +} + +#ifdef ERROR_USE_SQL + +CError::CError (const QString& strText, const QSqlDatabase* pDB, const QString& strFunc, const QString& strFile, const unsigned int uLine) +{ + m_strText = QString ("%1\n%3").arg (strText, pDB->lastError().text()); + m_strFunction = strFunc; + m_strFile = strFile; + m_uLine = uLine; +} + +CError::CError (const QString& strText, const QSqlQuery& query, const QString& strFunc, const QString& strFile, const unsigned int uLine) +{ +QMap map; + m_strText = QString ("%1\nSQL: %2\n").arg (strText, query.lastQuery()); + map = query.boundValues(); + if (!map.isEmpty()) + { + m_strText+="Bound values:\n"; + QMap::const_iterator i = map.constBegin(); + while (i != map.constEnd()) + { + m_strText += QString ("%1 / %2 ==> %4\n").arg (i.key(), i.value().typeName(), i.value().isNull() ? "NULL" : i.value().toString()); + ++i; + } + } + + m_strText += query.lastError().text(); + m_strFunction = strFunc; + m_strFile = strFile; + m_uLine = uLine; +} +#endif + +QString CError::toPlainText() const +{ + return QObject::tr("%1\n\nFunction: %2\nFile: %3 Line: %4\n").arg(m_strText, m_strFunction, m_strFile).arg(m_uLine); +} + +QString CError::toHtml() const +{ +QString str = m_strText; + str.replace ('\n', "
"); + return QObject::tr("

%1
Function: %2
File: %3 Line: %4


\n").arg(str, m_strFunction, m_strFile).arg(m_uLine); +} + diff --git a/error.h b/error.h new file mode 100644 index 0000000..77b8019 --- /dev/null +++ b/error.h @@ -0,0 +1,78 @@ +/*************************************************************************** + * Copyright (C) 1999-2007 by Oliver Saal * + * http://www.oliver-saal.de/ * + * osaal@gmx.de * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef ERROR_H +#define ERROR_H + +//#define ERROR_USE_SQL + +#ifndef __PRETTY_FUNCTION__ +# if defined __FUNCSIG__ +# define __PRETTY_FUNCTION__ __FUNCSIG__ +# elif defined __func__ +# define __PRETTY_FUNCTION__ __func__ +# else +# define __PRETTY_FUNCTION__ __FILE__ +# endif +#endif + + + +#include + +#ifdef ERROR_USE_SQL +#include + +#define THROW_TRANSACTION(x) throw CError (tr("Could not start database transaction."), x, __PRETTY_FUNCTION__, __FILE__, __LINE__); +#define THROW_COMMIT(x) throw CError (tr("Could not commit database transaction."), x, __PRETTY_FUNCTION__, __FILE__, __LINE__); +#endif + +class CError +{ +public: + CError (); + CError (const QString& strFunc, const QString& strFile, const unsigned int uLine); + CError (const QString& strText, const QString& strFunc, const QString& strFile, const unsigned int uLine); +#ifdef ERROR_USE_SQL + CError (const QString& strText, const QSqlDatabase* pDB, const QString& strFunc, const QString& strFile, const unsigned int uLine); + CError (const QString& strText, const QSqlQuery& query, const QString& strFunc, const QString& strFile, const unsigned int uLine); +#endif + + inline void preText (const QString& str) { m_strText = str + m_strText; } + inline void postText (const QString& str) { m_strText += str; } + + inline QString text() const { return m_strText; } + inline QString function() const { return m_strFunction; } + inline QString file() const { return m_strFile; } + inline unsigned line() const { return m_uLine; } + + QString toPlainText() const; + QString toHtml() const; + +protected: + QString m_strText; + QString m_strFunction; + QString m_strFile; + unsigned int m_uLine; +}; + + +#endif // ERROR_H diff --git a/exam.cpp b/exam.cpp new file mode 100644 index 0000000..d50bc1f --- /dev/null +++ b/exam.cpp @@ -0,0 +1,306 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "exam.h" +#include "question.h" +#include "error.h" +#include "tools.h" + +#include +#include + +//#define DEBUGMSG + +void CExamPart::clear() +{ + m_iQuestionCount = 0; + m_strRegExp.clear(); +} + +bool CExamPart::load (QDomElement elem) +{ + if (elem.tagName() != QString ("exam_part")) return false; + m_iQuestionCount = elem.attribute("count").toUInt(); + if (m_iQuestionCount == 0) return false; + m_strGroup = elem.attribute("group"); + m_strRegExp = elem.text (); + if (m_strRegExp.isEmpty()) return false; + return true; +} + +void CExamPart::save (QDomElement& parent, QDomDocument& doc) const +{ +QDomElement elemRoot = doc.createElement("exam_part"); + + elemRoot.setAttribute("count", QString("%1").arg(m_iQuestionCount)); + elemRoot.setAttribute("group", m_strGroup); + elemRoot.appendChild(doc.createTextNode(m_strRegExp)); + + parent.appendChild(elemRoot); +} + +QList CExamPart::createQuestionPool(const QList& listAllQuestions) const +{ +QList listRet, listSelected; +QRegExp regexp(m_strRegExp); +int i=0; +CQuestion *q=0; + + #ifdef DEBUGMSG + qDebug ("\tPart Group = '%s' RegExp '%s' mit %i Fragen", qPrintable (m_strGroup), qPrintable(m_strRegExp), m_iQuestionCount); + #endif + + // Alle Fragen raussuchen, die dem RegExp entsprechen + for (i=0; igroups().contains(m_strGroup, Qt::CaseInsensitive)) + continue; + + if (regexp.exactMatch(q->id())) + { + #ifdef DEBUGMSG + qDebug ("\t\tAdding Question '%s'", qPrintable (q->id())); + #endif + listSelected.append(q); + } + } + + if (listSelected.size() < questionCount()) + { + throw CError ( + QString("Für den regulären Ausdruck '%1' wurden nur %2 Fragen gefunden - zu wenig, um daraus %3 Fragen für die Prüfung auszuwählen.") + .arg(m_strRegExp).arg(listSelected.size()).arg(questionCount()), __PRETTY_FUNCTION__, __FILE__, __LINE__); + } + + #ifdef DEBUGMSG + qDebug ("\tStarte Auswahlverfahren..."); + #endif + for (i=0; i < questionCount(); i++) + { + int rnd = afu_random (0, listSelected.size()-1); + q = listSelected.at(rnd); + #ifdef DEBUGMSG + qDebug ("\t\tSelected question '%s'", qPrintable (q->id())); + #endif + listRet.append (q); + listSelected.removeAt(rnd); + } + + return listRet; +} + + + +void CExam::clear() +{ + m_strId.clear(); + m_strName.clear(); + m_strComment.clear(); + m_uDuration = 0; + m_uMaxErrorPoints = 0; + m_listParts.clear(); +} + +bool CExam::load (QDomElement elem) +{ + if (elem.tagName() != QString ("exam")) return false; + + m_strId = elem.attribute("id"); + m_strName = elem.attribute("name"); + m_uDuration = elem.attribute("duration").toUInt(); + m_uMaxErrorPoints = elem.attribute("maxerrorpoints").toUInt(); + m_strComment = elem.attribute("comment"); + + if (m_strName.isEmpty()) return false; + if (m_uDuration == 0) return false; + + QDomNode n = elem.firstChild(); + while (!n.isNull()) + { + if (n.isElement ()) + { + QDomElement e = n.toElement (); + if (e.tagName() == QString ("exam_part")) + { + CExamPart ep; + if (ep.load(e)) m_listParts.append(ep); + } + } + n = n.nextSibling(); + } + + return true; +} + +void CExam::save (QDomElement& parent, QDomDocument& doc) const +{ +QDomElement elemRoot = doc.createElement("exam"); + + elemRoot.setAttribute("id", m_strId); + elemRoot.setAttribute("name", m_strName); + elemRoot.setAttribute("duration", QString("%1").arg(m_uDuration)); + elemRoot.setAttribute("maxerrorpoints", QString("%1").arg(m_uMaxErrorPoints)); + elemRoot.setAttribute("comment", m_strComment); + for (int i=0; i CExam::createQuestionPool(const QList& listAllQuestions) const +{ +QList listRet; + + #ifdef DEBUGMSG + qDebug ("Erzeuge Prüfungsfragen für '%s'", qPrintable(name())); + #endif + + try + { + for (int i=0; iid())); + #endif + + return listRet; +} + +CExamStat::CExamStat(const CExam& exam) +{ + clear(); + m_strId = exam.id(); +} + +void CExamStat::clear() +{ + m_strId.clear(); + m_dt = QDateTime::currentDateTime(); + m_uSecs = 0; + + m_strlQuestionId.clear(); + m_listAnswer.clear(); + + m_uCorrect = 0; + m_uWrong = 0; + m_uErrorPoints = 0; + m_bPassed = false; +} + +void CExamStat::setQuestions(const QList& listQuestion, const QList& listAnswerMask) +{ + for (int i=0; iid()); + m_listAnswer.append(q->orderedAnswerMask(listAnswerMask.at(i))); + } +} + +void CExamStat::setResult (const unsigned uCorrect, const unsigned uWrong, const unsigned uErrorPoints, const bool bPassed) +{ + m_uCorrect = uCorrect; + m_uWrong = uWrong; + m_uErrorPoints = uErrorPoints; + m_bPassed = bPassed; +} + +bool CExamStat::load (QDomElement elem) +{ + if (elem.tagName() != QString ("exam")) return false; + + m_strId = elem.attribute("id"); + m_uSecs = elem.attribute("secs").toUInt(); + m_dt = QDateTime::fromString(elem.attribute("datetime"), Qt::ISODate); + m_uCorrect = elem.attribute("correct").toUInt(); + m_uWrong = elem.attribute("wrong").toUInt(); + m_uErrorPoints = elem.attribute("errorpoints").toUInt(); + m_bPassed = QVariant(elem.attribute("passed")).toBool(); + + if (m_strId.isEmpty()) return false; + if (m_uSecs == 0) return false; + + QDomNode n = elem.firstChild(); + while (!n.isNull()) + { + if (n.isElement ()) + { + QDomElement e = n.toElement (); + if (e.tagName() == QString ("question")) + { + QString strId = e.attribute("id"); + if (!strId.isEmpty()) + { + m_strlQuestionId.append (strId); + m_listAnswer.append(e.attribute("answer").toUInt()); + } + } + } + n = n.nextSibling(); + } + + return true; +} + +void CExamStat::save (QDomElement& parent, QDomDocument& doc) const +{ +QDomElement elemRoot = doc.createElement("exam"); + + elemRoot.setAttribute("id", m_strId); + elemRoot.setAttribute("secs", QString("%1").arg(m_uSecs)); + elemRoot.setAttribute("datetime", m_dt.toString(Qt::ISODate)); + elemRoot.setAttribute("correct", QString("%1").arg(m_uCorrect)); + elemRoot.setAttribute("wrong", QString("%1").arg(m_uWrong)); + elemRoot.setAttribute("errorpoints", QString("%1").arg(m_uErrorPoints)); + elemRoot.setAttribute("passed", QString("%1").arg(m_bPassed)); + for (int i=0; i +#include +#include +#include +#include + +class CQuestion; + +class CExamPart +{ +public: + CExamPart() { clear(); } + ~CExamPart() {} + + void clear(); + + inline int questionCount() const { return m_iQuestionCount; } + inline void setQuestionCount(int i) { m_iQuestionCount = i; } + + inline QString group() const { return m_strGroup; } + inline void setGroup(const QString& str) { m_strGroup = str; } + + inline QString regexp() const { return m_strRegExp; } + inline void setRegExp(const QString& str) { m_strRegExp = str; } + + bool load (QDomElement elem); + void save (QDomElement& parent, QDomDocument& doc) const; + + QList createQuestionPool(const QList& listAllQuestions) const; + +protected: + int m_iQuestionCount; + QString m_strGroup; + QString m_strRegExp; +}; + +class CExam +{ +public: + CExam() { clear(); } + ~CExam() {} + + //! Zurücksetzen aller Werte + /*! Es werden alle Daten der Prüfung gelöscht. */ + void clear(); + + //! ID abfragen + inline QString id() const { return m_strId; } + //! ID setzen + inline void setId(const QString& strId) { m_strId = strId; } + + inline QString name() const { return m_strName; } + inline QString comment() const { return m_strComment; } + unsigned duration() const { return m_uDuration; } + unsigned maxErrorPoints() const { return m_uMaxErrorPoints; } + + unsigned questionCount() const; + + bool load (QDomElement elem); + void save (QDomElement& parent, QDomDocument& doc) const; + + QList createQuestionPool(const QList& listAllQuestions) const; + +protected: + QString m_strId; //< ID der Prüfung + QString m_strName; //< Name / Bezeichnung + QString m_strComment; //< Kommentar / Hinweis + unsigned m_uDuration; //< Prüfungsdauer in min + unsigned m_uMaxErrorPoints; //< Max. Anzahl an erlaubten Fehlerpunkten, um die Prüfung zu bestehen + QList m_listParts; //< Abschnitte, aus denen sich die Prüfung zusammensetzt. +}; + +class CExamStat +{ +public: + CExamStat() { clear(); } + CExamStat(const CExam& exam); + ~CExamStat() {} + + //! Zurücksetzen aller Werte + /*! Es werden alle Daten dieser Prüfungsstatistik gelöscht. */ + void clear(); + + bool load (QDomElement elem); + void save (QDomElement& parent, QDomDocument& doc) const; + + //! ID abfragen + inline QString id() const { return m_strId; } + //! ID setzen + inline void setId(const QString& strId) { m_strId = strId; } + + inline void setSecs(const unsigned uSecs) { m_uSecs = uSecs; } + void setQuestions(const QList& listQuestion, const QList& listAnswerMask); + void setResult (const unsigned uCorrect, const unsigned uWrong, const unsigned uErrorPoints, const bool bPassed); + + inline unsigned duration() const { return m_uSecs; } + inline bool passed() const { return m_bPassed; } + inline unsigned correctAnswers() const { return m_uCorrect; } + inline unsigned wrongAnswers() const { return m_uWrong; } + inline unsigned errorPoints() const { return m_uErrorPoints; } + QDateTime datetime() const { return m_dt; } + +protected: + QString m_strId; //< ID der Prüfung + QDateTime m_dt; //< Datum/Uhrzeit + unsigned m_uSecs; //< Benötigte Zeit in sec + + QStringList m_strlQuestionId; //< IDs der Fragen + QList m_listAnswer; //< Antworten + + unsigned m_uCorrect; //< Anzahl d. richtigen Fragen + unsigned m_uWrong; //< Anzahl d. falschen Fragen + unsigned m_uErrorPoints; //< Fehlerpunkte + bool m_bPassed; //< Bestanden Ja/Nein +}; + +#endif + diff --git a/helper.cpp b/helper.cpp new file mode 100644 index 0000000..0885b02 --- /dev/null +++ b/helper.cpp @@ -0,0 +1,84 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "helper.h" +#include "catalog.h" + +void CHint::clear() +{ + m_strlQuestionId.clear(); + m_strAuthor.clear(); + m_date = QDate(); + m_strText.clear(); + m_strComment.clear(); +} + +bool CHint::load (QDomElement elem) +{ +QString str; + + if (elem.tagName() != QString ("hint")) return false; + m_strAuthor = elem.attribute ("author", "AFUTrainer-Hilfe"); + m_date = QDate::fromString(elem.attribute ("date"), Qt::ISODate); + str = elem.attribute("question"); + str = str.replace(' ', ';').replace(',', ';'); + m_strlQuestionId = str.split(";", QString::SkipEmptyParts); + m_strComment = elem.attribute ("comment"); + m_strText = elem.text (); + + if (m_strText.isEmpty()) return false; + return true; +} + +void CHint::save (QDomElement& parent, QDomDocument& doc) +{ +QDomElement elemRoot = doc.createElement("hint"); + if (!m_strAuthor.isEmpty()) elemRoot.setAttribute("author", m_strAuthor); + if (m_date.isValid()) elemRoot.setAttribute("date", m_date.toString(Qt::ISODate)); + elemRoot.setAttribute("question", m_strlQuestionId.join(";")); + if (!m_strComment.isEmpty()) elemRoot.setAttribute("comment", m_strComment); + parent.appendChild(elemRoot); + elemRoot.appendChild(doc.createTextNode(m_strText)); +} + +QString CHint::showText() const +{ +QString str; + +/* if (text().isEmpty()) + { + if (pCatalog) + return pCatalog->hintText(id()); + else + return QString(); + } +*/ +/* str = "

"; + str += ""; + str += ""; + str += ""; + str += ""; + str += "
"+author()+""+dateString()+"
"+text()+"
 

"; +*/ + + str += "

" + text() + "

"; //"

" + author() + " " + date() + "

"; + return (str); +} diff --git a/helper.h b/helper.h new file mode 100644 index 0000000..0e66872 --- /dev/null +++ b/helper.h @@ -0,0 +1,71 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef HELPER_H +#define HELPER_H + +#include +#include +#include +#include + +class CCatalog; + +class CHint +{ +public: + CHint() { clear(); } + ~CHint() {} + + void clear(); + + inline void appendQuestion(const QString& id) { if (!hasQuestion(id)) m_strlQuestionId.append(id); } + inline void removeQuestion(const QString& id) { m_strlQuestionId.removeAll(id); } + inline bool hasQuestion(const QString& id) const { return m_strlQuestionId.contains(id); } + + inline QString author() const { return m_strAuthor; } + inline void setAuthor(const QString& strAuthor) { m_strAuthor = strAuthor; } + + inline QDate date() const { return m_date; } + inline QString dateString() const { return m_date.toString(Qt::LocalDate); } + inline void setDate (const QDate& date) { m_date = date; } + + inline QString text() const { return m_strText; } + inline void setText(const QString& strText) { m_strText = strText; } + + inline QString comment() const { return m_strComment; } + inline void setComment (const QString& strComment) { m_strComment = strComment; } + + + QString showText() const; + + bool load (QDomElement elem); + void save (QDomElement& parent, QDomDocument& doc); + +protected: + QStringList m_strlQuestionId; + QString m_strAuthor; + QDate m_date; + QString m_strText; + QString m_strComment; +}; + +#endif diff --git a/icons/16x16/book1.png b/icons/16x16/book1.png new file mode 100644 index 0000000..56b98e0 Binary files /dev/null and b/icons/16x16/book1.png differ diff --git a/icons/16x16/button_cancel.png b/icons/16x16/button_cancel.png new file mode 100644 index 0000000..2415dfc Binary files /dev/null and b/icons/16x16/button_cancel.png differ diff --git a/icons/16x16/button_ok.png b/icons/16x16/button_ok.png new file mode 100644 index 0000000..543710f Binary files /dev/null and b/icons/16x16/button_ok.png differ diff --git a/icons/16x16/cancel.png b/icons/16x16/cancel.png new file mode 100644 index 0000000..6b990a2 Binary files /dev/null and b/icons/16x16/cancel.png differ diff --git a/icons/16x16/clock.png b/icons/16x16/clock.png new file mode 100644 index 0000000..dca2c7e Binary files /dev/null and b/icons/16x16/clock.png differ diff --git a/icons/16x16/contexthelp.png b/icons/16x16/contexthelp.png new file mode 100644 index 0000000..7b88a0c Binary files /dev/null and b/icons/16x16/contexthelp.png differ diff --git a/icons/16x16/exit.png b/icons/16x16/exit.png new file mode 100644 index 0000000..a77152b Binary files /dev/null and b/icons/16x16/exit.png differ diff --git a/icons/16x16/filenew.png b/icons/16x16/filenew.png new file mode 100644 index 0000000..83f3752 Binary files /dev/null and b/icons/16x16/filenew.png differ diff --git a/icons/16x16/fileopen.png b/icons/16x16/fileopen.png new file mode 100644 index 0000000..037c2da Binary files /dev/null and b/icons/16x16/fileopen.png differ diff --git a/icons/16x16/filesave.png b/icons/16x16/filesave.png new file mode 100644 index 0000000..41b3f43 Binary files /dev/null and b/icons/16x16/filesave.png differ diff --git a/icons/16x16/filesaveas.png b/icons/16x16/filesaveas.png new file mode 100644 index 0000000..3e28d5d Binary files /dev/null and b/icons/16x16/filesaveas.png differ diff --git a/icons/16x16/finish.png b/icons/16x16/finish.png new file mode 100644 index 0000000..64609dd Binary files /dev/null and b/icons/16x16/finish.png differ diff --git a/icons/16x16/folder.png b/icons/16x16/folder.png new file mode 100644 index 0000000..7b6f0a7 Binary files /dev/null and b/icons/16x16/folder.png differ diff --git a/icons/16x16/help.png b/icons/16x16/help.png new file mode 100644 index 0000000..b580f51 Binary files /dev/null and b/icons/16x16/help.png differ diff --git a/icons/16x16/idea.png b/icons/16x16/idea.png new file mode 100644 index 0000000..8d72794 Binary files /dev/null and b/icons/16x16/idea.png differ diff --git a/icons/16x16/idea_gray.png b/icons/16x16/idea_gray.png new file mode 100644 index 0000000..d83467d Binary files /dev/null and b/icons/16x16/idea_gray.png differ diff --git a/icons/16x16/idea_info.png b/icons/16x16/idea_info.png new file mode 100644 index 0000000..d05a927 Binary files /dev/null and b/icons/16x16/idea_info.png differ diff --git a/icons/16x16/info.png b/icons/16x16/info.png new file mode 100644 index 0000000..1903fab Binary files /dev/null and b/icons/16x16/info.png differ diff --git a/icons/16x16/level0.png b/icons/16x16/level0.png new file mode 100644 index 0000000..2190643 Binary files /dev/null and b/icons/16x16/level0.png differ diff --git a/icons/16x16/level1.png b/icons/16x16/level1.png new file mode 100644 index 0000000..611e8f8 Binary files /dev/null and b/icons/16x16/level1.png differ diff --git a/icons/16x16/level1a.png b/icons/16x16/level1a.png new file mode 100644 index 0000000..c808d88 Binary files /dev/null and b/icons/16x16/level1a.png differ diff --git a/icons/16x16/level2.png b/icons/16x16/level2.png new file mode 100644 index 0000000..1659635 Binary files /dev/null and b/icons/16x16/level2.png differ diff --git a/icons/16x16/level2a.png b/icons/16x16/level2a.png new file mode 100644 index 0000000..880003b Binary files /dev/null and b/icons/16x16/level2a.png differ diff --git a/icons/16x16/level3.png b/icons/16x16/level3.png new file mode 100644 index 0000000..8737a36 Binary files /dev/null and b/icons/16x16/level3.png differ diff --git a/icons/16x16/level3a.png b/icons/16x16/level3a.png new file mode 100644 index 0000000..2b8eb47 Binary files /dev/null and b/icons/16x16/level3a.png differ diff --git a/icons/16x16/level4.png b/icons/16x16/level4.png new file mode 100644 index 0000000..de202ca Binary files /dev/null and b/icons/16x16/level4.png differ diff --git a/icons/16x16/level5.png b/icons/16x16/level5.png new file mode 100644 index 0000000..245f0bd Binary files /dev/null and b/icons/16x16/level5.png differ diff --git a/icons/16x16/next.png b/icons/16x16/next.png new file mode 100644 index 0000000..a930053 Binary files /dev/null and b/icons/16x16/next.png differ diff --git a/icons/16x16/previous.png b/icons/16x16/previous.png new file mode 100644 index 0000000..f258c11 Binary files /dev/null and b/icons/16x16/previous.png differ diff --git a/icons/16x16/start.png b/icons/16x16/start.png new file mode 100644 index 0000000..56a6a5d Binary files /dev/null and b/icons/16x16/start.png differ diff --git a/icons/16x16/stats.png b/icons/16x16/stats.png new file mode 100644 index 0000000..acbfe1e Binary files /dev/null and b/icons/16x16/stats.png differ diff --git a/icons/16x16/stop.png b/icons/16x16/stop.png new file mode 100644 index 0000000..4d84554 Binary files /dev/null and b/icons/16x16/stop.png differ diff --git a/icons/16x16/viewmag.png b/icons/16x16/viewmag.png new file mode 100644 index 0000000..593a566 Binary files /dev/null and b/icons/16x16/viewmag.png differ diff --git a/icons/32x32/idea.png b/icons/32x32/idea.png new file mode 100644 index 0000000..0ad4e99 Binary files /dev/null and b/icons/32x32/idea.png differ diff --git a/icons/32x32/qt.png b/icons/32x32/qt.png new file mode 100644 index 0000000..a616ea0 Binary files /dev/null and b/icons/32x32/qt.png differ diff --git a/icons/64x16-old/veryrare.png b/icons/64x16-old/veryrare.png new file mode 100644 index 0000000..8cfbdad Binary files /dev/null and b/icons/64x16-old/veryrare.png differ diff --git a/icons/64x16/level0.png b/icons/64x16/level0.png new file mode 100644 index 0000000..273ce2b Binary files /dev/null and b/icons/64x16/level0.png differ diff --git a/icons/64x16/level1.png b/icons/64x16/level1.png new file mode 100644 index 0000000..2ac9295 Binary files /dev/null and b/icons/64x16/level1.png differ diff --git a/icons/64x16/level2.png b/icons/64x16/level2.png new file mode 100644 index 0000000..b2724b0 Binary files /dev/null and b/icons/64x16/level2.png differ diff --git a/icons/64x16/level3.png b/icons/64x16/level3.png new file mode 100644 index 0000000..bd85ba5 Binary files /dev/null and b/icons/64x16/level3.png differ diff --git a/icons/64x16/level4.png b/icons/64x16/level4.png new file mode 100644 index 0000000..4503d28 Binary files /dev/null and b/icons/64x16/level4.png differ diff --git a/icons/level.xcf b/icons/level.xcf new file mode 100644 index 0000000..d79401d Binary files /dev/null and b/icons/level.xcf differ diff --git a/icons/veryrare.xcf b/icons/veryrare.xcf new file mode 100644 index 0000000..a043d91 Binary files /dev/null and b/icons/veryrare.xcf differ diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..3627faa --- /dev/null +++ b/main.cpp @@ -0,0 +1,42 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "mainwindow.h" +#include +#include +#include + +int main (int argc, char *argv[]) +{ +QApplication app (argc, argv); +QTranslator translator; + app.setOrganizationName ("osaal"); + app.setApplicationName ("AFUTrainer"); + + translator.load(":/translations/qt_de"); + app.installTranslator(&translator); + + srand (time (NULL)); + + CMainWindow mw; + mw.show(); + return app.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..b8bfb95 --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,819 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "mainwindow.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dlglearn.h" +#include "dlglearnassistant.h" +#include "dlginformation.h" +#include "dlgexamselect.h" +#include "dlgexam.h" +#include "dlgexamstatistic.h" +#include "dlglearnstatistic.h" +#include "catalog.h" + + +CMainWindow::CMainWindow() : QMainWindow() +{ + setupUi(this); + m_pSplitter = new QSplitter (this); + setCentralWidget (m_pSplitter); + m_pViewChapter = new QTreeView (m_pSplitter); + m_pViewChapter->setModel(&m_modelCatalog); + m_pViewChapter->header()->setStretchLastSection(false); + m_pViewChapter->header()->setResizeMode (0, QHeaderView::Stretch); + m_pViewChapter->header()->setResizeMode (1, QHeaderView::Interactive); + m_pViewChapter->header()->setResizeMode (3, QHeaderView::Interactive); + m_pViewChapter->header()->resizeSection(1, 45); + m_pViewChapter->header()->resizeSection(2, 150); + m_pViewChapter->header()->resizeSection(3, 20); + //m_pViewChapter->setSelectionBehavior(QAbstractItemView::SelectRows); + connect (m_pViewChapter->selectionModel(), SIGNAL(selectionChanged (const QItemSelection& , const QItemSelection& )), this, SLOT(onCatalogSelectionChanged (const QItemSelection& , const QItemSelection& ))); + m_pSplitter2 = new QSplitter (m_pSplitter); + m_pSplitter2->setOrientation(Qt::Vertical); + + m_pViewQuestions = new QTreeView (m_pSplitter2); + m_pViewQuestions->setModel(&m_modelQuestion); + m_pViewQuestions->header()->setStretchLastSection(false); + m_pViewQuestions->header()->setResizeMode (0, QHeaderView::Interactive); + m_pViewQuestions->header()->setResizeMode (1, QHeaderView::Stretch); + m_pViewQuestions->header()->resizeSection(0, 60); + m_pViewQuestions->header()->resizeSection(2, 20); + m_pViewQuestions->header()->resizeSection(3, 20); + m_pViewQuestions->header()->resizeSection(4, 100); + m_pViewQuestions->header()->resizeSection(5, 80); + m_pViewQuestions->header()->resizeSection(6, 80); + m_pViewQuestions->setRootIsDecorated(false); + connect (m_pViewQuestions->selectionModel(), SIGNAL(selectionChanged (const QItemSelection& , const QItemSelection& )), this, SLOT(onQuestionSelectionChanged (const QItemSelection& , const QItemSelection& ))); + + m_pTextQuestion = new QTextBrowser (m_pSplitter2); + m_pTextQuestion->setOpenExternalLinks(true); + //m_pTextQuestion->setReadOnly(true); + //connect (m_pTextQuestion, SIGNAL(anchorClicked (const QUrl &)), this, SLOT(onAnchorClicked (const QUrl &))); + + m_pCatalog = 0; + + connect (&m_rf, SIGNAL(loadFile(const QString&)), this, SLOT(onOpenFile(const QString&))); + m_rf.create(QString(), 8); + m_rf.insertToMenu(menuFile, actFileExit); + m_rf.setShowNoEntry(false); + m_rf.setShowSeperator(CRecentFiles::SeperatorBottom); + + // open last file + onOpenFile(m_rf.recentFile(0)); + +#ifndef _DEBUG + actFileSave->setVisible(false); + actFileSaveAs->setVisible(false); + actFileImport->setVisible(false); + actFileImportUS->setVisible(false); +#endif + + updateWindowTitle(); +} + +CMainWindow::~CMainWindow() +{ + if (m_pCatalog) delete m_pCatalog; +} + +void CMainWindow::updateWindowTitle() +{ +QString str; + str = tr("AFUTrainer 3.0"); + if (m_pCatalog) + { + str += " - "; + if (m_pCatalog->name().isEmpty()) + str += tr("unbenannt"); + else + str += m_pCatalog->name(); + + if (!m_pCatalog->fileName().isEmpty()) + str += " [" + m_pCatalog->fileName() + "]"; + } + setWindowTitle(str); +} + +void CMainWindow::setCatalog (CCatalog *pCatalog) +{ + m_modelCatalog.setModelData(pCatalog); + m_pViewChapter->expandAll(); + if (m_pCatalog) delete m_pCatalog; + m_pCatalog = pCatalog; + m_modelQuestion.setModelData(0); + m_pTextQuestion->clear(); + updateWindowTitle(); + + if (m_pCatalog == 0) return; + + QDate d = m_pCatalog->validUntil(); + if (d.isValid() && d < QDate::currentDate()) + { + QString str = tr("Dieser Fragenkatalog ist seit dem %1 nicht mehr gültig!").arg(d.toString(Qt::LocalDate)); + if (!m_pCatalog->publisher().isEmpty()) + str += tr("

Weitere Informationen zur Gültigkeit erhalten Sie vom Herausgeber:
")+m_pCatalog->publisher(); + if (!m_pCatalog->contact().isEmpty()) + str += tr("

Einen aktuellen Fragenkatalog für den AFUTrainer erhalten Sie evt. von:
")+m_pCatalog->contact(); + + QMessageBox::warning(this, tr("Warnung"), str); + } +} + +bool CMainWindow::checkForErrors() +{ +QString str; + + if (m_pCatalog == 0) return false; + str = m_pCatalog->checkForErrors(); + if (str.isEmpty()) return false; + QMessageBox::critical(this, tr("Fehler im Fragenkatalog"), tr("Die gewünschte Funktion kann nicht ausgeführt werden, da der Fragenkatalog folgende Fehler enthält:

")+ str); + return true; +} + +bool CMainWindow::checkForHomeDir() +{ +QString strDir = QDir::homePath() + "/.afutrainer/"; +QDir dir; + if (dir.exists(strDir)) return true; + if (!dir.mkpath(strDir)) + { + QMessageBox::critical(this, tr("Fehler"), tr("Konnte Verzeichnis %1 nicht anlegen!").arg(strDir)); + return false; + } + return true; +} + +void CMainWindow::on_actFileNew_triggered() +{ + if (!checkForHomeDir()) return; + setCatalog(0); +} + +void CMainWindow::on_actFileOpen_triggered() +{ +QString strFileName; + if (!checkForHomeDir()) return; + strFileName = QFileDialog::getOpenFileName(this, tr("Öffne Fragenkatalog"), QString(), tr("Amateur Radio Questionary Data Format (*.aqz)\nAlle Dateien (*.*)")); + onOpenFile(strFileName); +} + +void CMainWindow::onOpenFile(const QString& strFileName) +{ +CCatalog *pCatalog=0; + if (strFileName.isEmpty()) return; + pCatalog = new CCatalog; + if (pCatalog->load(strFileName, this)) + { + setCatalog (pCatalog); + m_rf.setRecentFile(strFileName); + } + else + { + m_rf.removeFile(strFileName); + delete pCatalog; + } +} + +void CMainWindow::on_actFileInformation_triggered() +{ +CDlgInformation dlg(this); + if (!m_pCatalog) + { + QMessageBox::information(this, tr("Information"), tr("Kein Fragenkatalog geladen, zu dem Informationen angezeigt werden könnten.")); + return; + } + dlg.setup(m_pCatalog); + dlg.exec(); +} + +void CMainWindow::on_actFileSave_triggered() +{ + if (!m_pCatalog) return; + if (m_pCatalog->fileName().isEmpty()) + on_actFileSaveAs_triggered(); + else + { + m_pCatalog->save(m_pCatalog->fileName(), this); + updateWindowTitle(); + } +} + +void CMainWindow::on_actFileSaveAs_triggered() +{ +QString strFileName; + if (!m_pCatalog) return; + strFileName = QFileDialog::getSaveFileName(this, tr("Speichere Fragenkatalog"), QString(), tr("XML-Dateien (*.xml)\nAlle Dateien (*.*)")); + if (strFileName.isEmpty()) return; + m_pCatalog->save(strFileName, this); + updateWindowTitle(); +} + +bool ImportDE_isChapter(const QString& strLine) +{ + if (strLine.contains(QRegExp ("^[0-9](\\.[0-9]+)*\\s\\w"))) + return true; + else + return false; +} + +int ImportDE_chapterLevel(const QString& strLine) +{ +QString str = strLine.left(strLine.indexOf(' ')); + return (str.count('.')); +} + +bool ImportDE_isQuestion(const QString& strLine) +{ + return strLine.contains(QRegExp("^[A-Z]{2,2}[0-9]{3,3}")); +} + +bool ImportDE_isAnswer(const QString& strLine) +{ + return strLine.contains(QRegExp("^[A-D]\\s")) || strLine.contains(QRegExp("^[A-D]$")); +} + +void CMainWindow::on_actFileImport_triggered() +{ +QString strFileName, strLine, strChapter; +CChapter *pChapter=0, *pTempChapter=0; +CQuestion *pQuestion=0; +int uCurrentLevel=-1, uLevel=-1; +enum LastAdded {None, Chapter, Question, Answer}; +LastAdded la=None; +CCatalog *pCatalog=0; + +strFileName = QFileDialog::getOpenFileName(this, tr("Öffne Datei zum Fragenkatalog-Import"), QString("D:/projekte/afutrainer/fragenkataloge/2007-02/Klasse A Technik/"), tr("TXT-Dateien (*.txt)\nAlle Dateien (*.*)")); + if (strFileName.isEmpty()) return; + + QFile file(strFileName); + if(!file.open(QIODevice::ReadOnly)) + { + QMessageBox::information(this, tr("Fehler"), tr("Konnte Datei '%1' nicht zum Lesen öffnen!").arg(strFileName)); + return; + } + QTextStream in(&file); + pCatalog = new CCatalog(); + + while (!in.atEnd()) + { + strLine = in.readLine().trimmed(); + if (strLine.isEmpty() || strLine.contains(QRegExp("^Prüfungsfragen")) || strLine.contains("^Bundesnetzagentur")) + { + la = None; + } + else if (ImportDE_isChapter(strLine)) // && (uCurrentLevel+1 == ImportDE_chapterLevel(strLine) || uCurrentLevel-1 == ImportDE_chapterLevel(strLine))) + { + uLevel = ImportDE_chapterLevel(strLine); + while (uCurrentLevel >= uLevel && pChapter) + { + pChapter = pChapter->parentChapter(); + uCurrentLevel--; + } + pTempChapter = new CChapter(); + if (pChapter == 0) + pCatalog->appendChapter(pTempChapter); + else + pChapter->appendChapter(pTempChapter); + pTempChapter->setText(strLine.mid(strLine.indexOf(' ') + 1)); + + pChapter = pTempChapter; + uCurrentLevel++; + strChapter += QString("%1 -- %2
").arg(uLevel).arg(strLine); + qDebug ("%i -- %s", uLevel, qPrintable(strLine)); + la = Chapter; + } + else if (ImportDE_isQuestion(strLine)) + { + pQuestion = new CQuestion(); + pQuestion->setId (strLine.left(5)); + pQuestion->setText (strLine.mid(6).trimmed()); + if (pChapter) + { + pChapter->appendQuestion(pQuestion); + + // update id of chapters + pTempChapter = pChapter; + uLevel = uCurrentLevel; + while (pTempChapter && uLevel >= 0) + { + if (pTempChapter->id().isEmpty()) + pTempChapter->setId(pQuestion->id().at(uLevel)); + pTempChapter = pTempChapter->parentChapter(); + uLevel--; + } + } + else + delete pQuestion; + //qDebug (qPrintable(strLine)); + la = Question; + } + else if (ImportDE_isAnswer(strLine)) + { + CAnswer a(strLine.mid(2).trimmed(), strLine[0] == QChar('A')); + if (pQuestion) + pQuestion->appendAnswer(a); + //qDebug (qPrintable("\t"+strLine)); + la = Answer; + } + else + { + if (la == Chapter && pChapter) + { + if (pChapter->text().isEmpty()) + pChapter->appendText (strLine.trimmed()); + else + pChapter->appendText (" " + strLine.trimmed()); + } + else if (la == Question && pQuestion) + { + if (pQuestion->text().isEmpty() || pQuestion->text().right(1) == "-") + pQuestion->appendText (strLine.trimmed()); + else + pQuestion->appendText (" " + strLine.trimmed()); + } + else if (la == Answer && pQuestion && pQuestion->countAnswer() > 0) + { + QString strText = pQuestion->answerAt(pQuestion->countAnswer()-1).text(); + if (strText.isEmpty() || strText.right(1) == "-") + pQuestion->answerAt(pQuestion->countAnswer()-1).appendText(strLine.trimmed()); + else + pQuestion->answerAt(pQuestion->countAnswer()-1).appendText(" " + strLine.trimmed()); + } + } + } + + // Bilder hinzufügen + QList listPool = pCatalog->questionPool(); + QDir dirPath(strFileName.left(strFileName.lastIndexOf('/'))); + for (int i=0; iid().toLower()+"*"); + for (int j=0; j"; + + if (strImgFile.contains(QRegExp("f\\d*\\."))) + { // Frage + qDebug("Adding image %s to question %s", qPrintable(strImgFile), qPrintable(pQuestion->id())); + pQuestion->appendText("

" + strLink + "

"); + } + else if (strImgFile.contains("a.") && pQuestion->countAnswer() > 0) + { // Antwort A + //qDebug("Adding image %s to answer A of question %s", qPrintable(strImgFile), qPrintable(pQuestion->id())); + pQuestion->answerAt(0).appendText("

" + strLink + "

"); + } + else if (strImgFile.contains("b.") && pQuestion->countAnswer() > 1) + { // Antwort B + //qDebug("Adding image %s to answer B of question %s", qPrintable(strImgFile), qPrintable(pQuestion->id())); + pQuestion->answerAt(1).appendText("

" + strLink + "

"); + } + else if (strImgFile.contains("c.") && pQuestion->countAnswer() > 2) + { // Antwort C + //qDebug("Adding image %s to answer C of question %s", qPrintable(strImgFile), qPrintable(pQuestion->id())); + pQuestion->answerAt(2).appendText("

" + strLink + "

"); + } + else if (strImgFile.contains("d.") && pQuestion->countAnswer() > 3) + { // Antwort D + //qDebug("Adding image %s to answer D of question %s", qPrintable(strImgFile), qPrintable(pQuestion->id())); + pQuestion->answerAt(3).appendText("

" + strLink + "

"); + } + else + { + qDebug ("Konnte Grafik %s nicht zuordnen.\n\tMögliche Gründe: Frage oder Antwort existiert nicht. Dateiname der Grafik falsch geschrieben.", qPrintable(strImgFile)); + } + } + } + + pCatalog->setName("Importierter Katalog von " + strFileName); + QMessageBox::information(this, tr("Information"), + "Bitte Debug-Ausgabe überprüfen, ob alle Grafik-Dateien zugeordnet wurden!

" + "Bitte überprüfen, ob alle Kapitel korrekt erkannt wurden. " + "Wenn nicht, muss die TXT-Datei von Hand kontrolliert werden!


"+strChapter); + + qDebug("Weitere Vorgehensweise:\n\t1. Fragenkatalog als XML-Datei abspeichern.\n\t2. ggf. Korrekturen in der XML-Datei vornehmen\n\t3. XML-Datei und alle Grafik-Dateien in ein ZIP-Archiv packen und die Dateiänderung auf .atc ändern.\n\tFertig!"); + setCatalog (pCatalog); +} + +bool ImportUS_isChapter(const QString& strLine) +{ + if (strLine.contains(QRegExp("^SUBELEMENT")) || strLine.contains(QRegExp("^[A-Z][0-9][A-Z]\\s"))) + return true; + else + return false; +} + +bool ImportUS_isQuestion(const QString& strLine) +{ + return strLine.contains(QRegExp("^[A-Z][0-9][A-Z][0-9]{2,2}")); +} + +bool ImportUS_isAnswer(const QString& strLine) +{ + return strLine.contains(QRegExp("^[A-D]\\.\\s")); +} + +void CMainWindow::on_actFileImportUS_triggered() +{ +QString strFileName, strLine, strChapter, str; +CChapter *pChapter=0, *pTempChapter=0; +CQuestion *pQuestion=0; +int uCurrentLevel=-1, uLevel=-1; +enum LastAdded {None, Chapter, Question, Answer}; +LastAdded la=None; +CCatalog *pCatalog=0; + +strFileName = QFileDialog::getOpenFileName(this, tr("Öffne Datei zum Fragenkatalog-Import"), QString("D:/projekte/afutrainer/fragenkataloge/US/"), tr("TXT-Dateien (*.txt)\nAlle Dateien (*.*)")); + if (strFileName.isEmpty()) return; + + QFile file(strFileName); + if(!file.open(QIODevice::ReadOnly)) + { + QMessageBox::information(this, tr("Fehler"), tr("Konnte Datei '%1' nicht zum Lesen öffnen!").arg(strFileName)); + return; + } + QTextStream in(&file); + pCatalog = new CCatalog(); + + while (!in.atEnd()) + { + strLine = in.readLine().trimmed(); + if (strLine.isEmpty()) + { + //la = None; + } + else if (strLine == "~~") + { + la = None; + } + else if (ImportUS_isChapter(strLine)) + { + pTempChapter = new CChapter(); + if (strLine.contains(QRegExp("^SUBELEMENT"))) + { + uLevel = 0; + strLine = strLine.mid(strLine.indexOf(' ')+1); + pTempChapter->setId(strLine.left(2)); +// str = strLine.mid(strLine.indexOf(QRegExp("[A-Z]"), 2)); +// str.remove(QRegExp("\\[.*\\]")); + pTempChapter->setText(strLine.mid(strLine.indexOf(QRegExp("[A-Z]"), 2))); + } + else + { + uLevel = 1; + pTempChapter->setId(strLine.mid(2, 1)); +// str = strLine.mid(strLine.indexOf(QRegExp("[A-Z]"), 3)); +// str.remove(QRegExp("\\[.*\\]")); + pTempChapter->setText(strLine.mid(strLine.indexOf(QRegExp("[A-Z]"), 3))); + } + while (uCurrentLevel >= uLevel && pChapter) + { + pChapter = pChapter->parentChapter(); + uCurrentLevel--; + } + if (!pChapter) + pCatalog->appendChapter(pTempChapter); + else + { +// pTempChapter->setExam("main", 1); + pChapter->appendChapter(pTempChapter); + } + + strChapter += QString("%1 -- %2 - %3
").arg(uLevel).arg(pTempChapter->id(), pTempChapter->text()); + pChapter = pTempChapter; + uCurrentLevel++; + la = Chapter; + } + else if (ImportUS_isQuestion(strLine)) + { + pQuestion = new CQuestion(); + pQuestion->setId (strLine.left(5)); + pQuestion->setText (strLine.mid(6).trimmed()); + if (pChapter) + pChapter->appendQuestion(pQuestion); + else + delete pQuestion; + //qDebug (qPrintable(strLine)); + la = Question; + } + else if (ImportUS_isAnswer(strLine)) + { + CAnswer a(strLine.mid(3).trimmed(), false); + if (pQuestion) + pQuestion->appendAnswer(a); + //qDebug (qPrintable("\t"+strLine)); + la = Answer; + } + else + { + if (la == Chapter && pChapter) + { + if (pChapter->text().isEmpty()) + pChapter->appendText (strLine.trimmed()); + else + pChapter->appendText (" " + strLine.trimmed()); + } + else if (la == Question && pQuestion) + { + if (pQuestion->text().isEmpty() || pQuestion->text().right(1) == "-") + pQuestion->appendText (strLine.trimmed()); + else + pQuestion->appendText (" " + strLine.trimmed()); + } + else if (la == Answer && pQuestion && pQuestion->countAnswer() > 0) + { + QString strText = pQuestion->answerAt(pQuestion->countAnswer()-1).text(); + if (strText.isEmpty() || strText.right(1) == "-") + pQuestion->answerAt(pQuestion->countAnswer()-1).appendText(strLine.trimmed()); + else + pQuestion->answerAt(pQuestion->countAnswer()-1).appendText(" " + strLine.trimmed()); + } + } + } + + // Fragen durchgehen und richtige Antworten eintragen + QList listPool = pCatalog->questionPool(); + for (int i=0; itext(); + int idx = strText.indexOf(QRegExp("\\([A-Z]\\)")); + if (idx == -1) continue; + int iCorrect = pQuestion->text().at(idx+1).toAscii() - 'A'; + if (pQuestion->countAnswer() > iCorrect) + pQuestion->answerAt(iCorrect).setCorrect(true); + strText.remove(idx, 3); + idx = strText.indexOf(QRegExp("\\[.*\\]")); + if (idx >= 0) + { + int iLen = strText.indexOf(']', idx) - idx + 1; + CHint hint; + hint.appendQuestion(pQuestion->id()); + hint.setAuthor("FCC"); + hint.setText("See FCC rules part " + strText.mid(idx + 1, iLen - 2)); + pCatalog->appendHint(hint); + strText.remove(idx, iLen); + } + pQuestion->setText(strText.trimmed()); + } + + // Kapitelnamen korrigieren + QList listChapter = pCatalog->subChapters(); + for (int i=0; itext(); + //strText = strText.left(strText.indexOf(" - ")); + strText.remove(QRegExp("\\[.*\\]")); + pChapter->setText(strText.trimmed()); + } + + // Allgemeine Angaben: + //pCatalog->setVersionText(""); + pCatalog->setPublisher("NCVEC (National Conference of Volunteer Examiner Coordinators)
Question Pool Committee
http://www.ncvec.org/"); + pCatalog->setContact("Oliver Saal, DM1OLI
http://www.oliver-saal.de/software/afutrainer/
Mail: osaal@gmx.de"); + + pCatalog->setName("Importierter Katalog von " + strFileName); + QMessageBox::information(this, tr("Information"), + "Bitte überprüfen, ob alle Kapitel korrekt erkannt wurden. " + "Wenn nicht, muss die TXT-Datei von Hand kontrolliert werden!
"+strChapter); + + qDebug("Weitere Vorgehensweise:\n\t1. Fragenkatalog als XML-Datei abspeichern.\n\t2. ggf. Korrekturen in der XML-Datei vornehmen\n\t3. XML-Datei und alle Grafik-Dateien in ein ZIP-Archiv packen und die Dateiänderung auf .atc ändern.\n\tFertig!"); + setCatalog (pCatalog); +} + +void CMainWindow::on_actFileExit_triggered() +{ + qApp->quit(); +} + +void CMainWindow::on_actQuestionAssistant_triggered() +{ +CDlgLearnAssistant dlg(this); +CDlgLearn dlgLearn(this); +CChapter *pChapter=0; + + if (m_pCatalog == 0) return; + if (checkForErrors()) return; + if (!dlg.setup(m_pCatalog)) + { + QMessageBox::information(this, tr("Information"), tr("Derzeit gibt es keine Empfehlung des Lernassistentes.")); + return; + } + if (dlg.exec() != QDialog::Accepted) return; + pChapter = dlg.selectedChapter(); + if (pChapter == 0) return; + + dlgLearn.go(m_pCatalog, pChapter); + m_pCatalog->saveStatistic(this); +} + +void CMainWindow::on_actQuestionsLearn_triggered() +{ +CDlgLearn dlg(this); +QModelIndexList list = m_pViewChapter->selectionModel()->selectedIndexes(); + + if (checkForErrors()) return; + if (list.isEmpty()) + { + QMessageBox::information(this, tr("Information"), tr("Bitte ein Kapitel zum Lernen auswählen!")); + return; + } + CChapter *p = (CChapter*)list.first().internalPointer(); + Q_ASSERT(p != 0); + dlg.go(m_pCatalog, p); + m_pCatalog->saveStatistic(this); +} + +void CMainWindow::on_actQuestionsLearnStatistics_triggered() +{ +CDlgLearnStatistic dlg(this); +QModelIndexList list = m_pViewChapter->selectionModel()->selectedIndexes(); +CChapter *p=0; + + if (checkForErrors()) return; + if (list.isEmpty()) + p = m_pCatalog; + else + p = (CChapter*)list.first().internalPointer(); + + dlg.go(p); +} + +void CMainWindow::on_actQuestionsTest_triggered() +{ +int iCount=0; +CExam exam; + + if (checkForErrors()) return; + iCount = m_pCatalog->countExam(); + if (iCount == 0) + { + QMessageBox::information(this, tr("Information"), tr("Dieser Fragenkatalog enthält keine Prüfungen.")); + return; + } + else if (iCount == 1) + { + exam = m_pCatalog->examAt(0); + } + else if (iCount > 1) + { + CDlgExamSelect dlg1(this); + dlg1.setup(m_pCatalog); + dlg1.exec(); + if (dlg1.result() == QDialog::Rejected) return; + exam = m_pCatalog->examAt(dlg1.selectedExam()); + } + + CDlgExam dlg2(m_pCatalog, this); + if (dlg2.setup(exam)) + dlg2.exec(); +} + +void CMainWindow::on_actQuestionsTestStatistics_triggered() +{ +CDlgExamStatistic dlg(this); + if (checkForErrors()) return; + if (m_pCatalog->countExam() == 0) + { + QMessageBox::information(this, tr("Information"), tr("Dieser Fragenkatalog enthält keine Prüfungen.")); + return; + } + dlg.go(m_pCatalog); +} + +void CMainWindow::on_actViewToolbar_toggled(bool bChecked) +{ + if (bChecked) + toolBar->show(); + else + toolBar->hide(); +} + +void CMainWindow::on_actViewStatusbar_toggled(bool bChecked) +{ + if (bChecked) + statusbar->show(); + else + statusbar->hide(); +} + +void CMainWindow::on_actHelpWhatsThis_triggered() +{ + QWhatsThis::enterWhatsThisMode(); +} + +void CMainWindow::on_actHelpAbout_triggered() +{ +QString str = tr("AFUTrainer Version 3.0
" + "(c) 2003-2007 by Oliver Saal (DM1OLI)

" + "EMail: osaal@gmx.de
" + "http://www.oliver-saal.de/software/afutrainer/
" + "This program is free software; you can redistribute it and/or modify " + "it under the terms of the GNU General Public License as published by " + "the Free Software Foundation; either version 2 of the License, or " + "(at your option) any later version.

" + "This program is distributed in the hope that it will be useful, " + "but WITHOUT ANY WARRANTY; without even the implied warranty of " + "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the " + "GNU General Public License for more details.

" + "You should have received a copy of the GNU General Public License " + "along with this program; if not, write to the " + "Free Software Foundation, Inc., " + "59 Temple Place - Suite 330, Boston, MA 02111-1307, USA."); + QMessageBox::about(this,tr("Über..."), str); +} + +void CMainWindow::on_actHelpAboutQt_triggered() +{ + qApp->aboutQt(); +} + +void CMainWindow::onCatalogSelectionChanged (const QItemSelection& selected, const QItemSelection& deselected) +{ + Q_UNUSED(deselected); + m_pTextQuestion->clear(); + if (!selected.indexes().isEmpty()) + { + CChapter *pChapter = (CChapter*)selected.indexes().first().internalPointer(); + if (pChapter) + m_modelQuestion.setModelData(pChapter); + } + else + m_modelQuestion.clear(); +} + +void CMainWindow::onQuestionSelectionChanged (const QItemSelection& selected, const QItemSelection& deselected) +{ + Q_UNUSED(deselected); + m_pTextQuestion->clear(); + if (selected.indexes().isEmpty()) return; + CQuestion *pQuestion = (CQuestion*)selected.indexes().first().internalPointer(); + if (pQuestion == 0) return; + m_pTextQuestion->setHtml(pQuestion->showText(m_pCatalog)); +} +/* +void CMainWindow::onAnchorClicked (const QUrl &link) +{ + QMessageBox::information(this, tr("Click"), link.toString()); + m_pTextQuestion->home(); +} +*/ diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..6a062df --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,91 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include + +#include "catalogmodel.h" +#include "questionmodel.h" +#include "recentfiles.h" + +#include "ui_mainwindow.h" + +class CMainWindow : public QMainWindow, protected Ui::MainWindow +{ + Q_OBJECT +public: + CMainWindow(); + ~CMainWindow(); + +protected: + void setCatalog (CCatalog *pCatalog); + void updateWindowTitle(); + bool checkForErrors(); + bool checkForHomeDir(); + +protected slots: + void on_actFileNew_triggered(); + void on_actFileOpen_triggered(); + void on_actFileInformation_triggered(); + void on_actFileSave_triggered(); + void on_actFileSaveAs_triggered(); + void on_actFileImport_triggered(); + void on_actFileImportUS_triggered(); + void on_actFileExit_triggered(); + void on_actQuestionAssistant_triggered(); + void on_actQuestionsLearn_triggered(); + void on_actQuestionsLearnStatistics_triggered(); + void on_actQuestionsTest_triggered(); + void on_actQuestionsTestStatistics_triggered(); + void on_actViewToolbar_toggled(bool bChecked); + void on_actViewStatusbar_toggled(bool bChecked); + void on_actHelpWhatsThis_triggered(); + void on_actHelpAbout_triggered(); + void on_actHelpAboutQt_triggered(); + + void onCatalogSelectionChanged (const QItemSelection& selected, const QItemSelection& deselected); + void onQuestionSelectionChanged (const QItemSelection& selected, const QItemSelection& deselected); + + void onOpenFile(const QString& strFileName); + +// void onAnchorClicked (const QUrl &link ); + +protected: + CRecentFiles m_rf; + QSplitter *m_pSplitter; // splitter widget + QTreeView *m_pViewChapter; // listview chapters + QTreeView *m_pViewQuestions; // listview questions + QSplitter *m_pSplitter2; + QTextBrowser *m_pTextQuestion; + + CCatalog *m_pCatalog; + CCatalogModel m_modelCatalog; + CQuestionModel m_modelQuestion; +}; + + +#endif // MAINWINDOW_H + diff --git a/mainwindow.ui b/mainwindow.ui new file mode 100644 index 0000000..196b33a --- /dev/null +++ b/mainwindow.ui @@ -0,0 +1,397 @@ + + MainWindow + + + + 0 + 0 + 800 + 600 + + + + AFUTrainer 3.0 + + + :/icons/32x32/idea.png + + + + + + 0 + 0 + 800 + 21 + + + + + &Hilfe + + + + + + + + + &Ansicht + + + + + + + &Fragen + + + + + + + + + + + &Datei + + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + 4 + + + + + + + + + + + + + + + :/icons/16x16/fileopen.png + + + Ö&ffnen + + + Öffnet einen Fragenkatalog + + + Öffnet einen Fragenkatalog + + + <b>Datei öffnen</b><p>Öffnet einen existierenden Fragenkatalog + + + Ctrl+O + + + + + :/icons/16x16/exit.png + + + &Beenden + + + Beendet den AFUTrainer + + + Beendet den AFUTrainer + + + <b>Beenden</b><p>Beendet den AFUTrainer + + + Alt+F4 + + + + + true + + + true + + + &Werkzeugleiste + + + Aktiviert/deaktiviert die Werkzeugleiste + + + Aktiviert/deaktiviert die Werkzeugleiste + + + <b>Werkzeugleiste</b><p>Aktiviert/deaktiviert die Werkzeugleiste + + + + + true + + + true + + + &Statusleiste + + + Aktiviert/deaktiviert die Statusleiste + + + Aktiviert/deaktiviert die Statusleiste + + + <b>Statusleiste</b><p>Aktiviert/deaktiviert die Statusleiste + + + + + :/icons/16x16/idea.png + + + &Ãœber... + + + Ãœber + + + Ãœber den AFUTrainer + + + Ãœber den AFUTrainer + + + <b>Ãœber</b><p>Copyright-Hinweise und Autoren + + + + + :/icons/16x16/contexthelp.png + + + Was ist &das? + + + Was ist das? + + + Shift+F1 + + + + + :/icons/16x16/viewmag.png + + + &Ansehen... + + + Zeigt ausgewählte Frage an + + + Zeigt ausgewählte Frage an + + + <b>Frage ansehen</b><p>Zeigt die Frage mit Anworten und Statistik<br>in einem neuen Fenster an + + + F3 + + + + + :/icons/16x16/idea.png + + + &Lernen... + + + Fragen lernen + + + Fragen lernen + + + <b>Fragen lernen</b><p>Hier können Sie die Fragen lernen. Eine Frage wird so lange abgefragt, bis Sie sie richtig beantworten können. + + + F4 + + + + + :/icons/16x16/stats.png + + + &Lernstatistik... + + + Zeigt Lernstatistik des ausgewählten Kapitels an + + + Zeigt Lernstatistik des ausgewählten Kapitels an + + + <b>Lernstatistik</b><p>Zeigt Lernstatistik des ausgewählten Kapitels an + + + + + :/icons/16x16/clock.png + + + &Prüfungssimulation... + + + Simuliert die Prüfung + + + Simuliert die Prüfung + + + <b>Prüfungssimulation</b><p>Sie erhalten zufällig generierte Fragenbögen,<br>die Sie in vorgegebener Zeit lösen müssen! + + + F6 + + + + + &Prüfstatistik... + + + Zeigt Ergebnisse der Prüfungssimulationen an + + + Zeigt Ergebnisse der Prüfungssimulationen an + + + <b>Prüfstatistik</b><p>Sie erhalten eine detaillierte Auflistung<br>der Ergebnisse der Prüfungssimulationen. + + + + + :/icons/32x32/qt.png + + + Ãœber &Qt... + + + Ãœber die Qt-Bibliothek + + + Ãœber die Qt-Bibliothek + + + <b>Ãœber Qt</b><p>Zeigt Copyright-Hinweise zur verwendeten Qt-Bibliothek an + + + + + &Import DE... + + + Import DE + + + Import DE + + + + + :/icons/16x16/filenew.png + + + &Neu + + + + + :/icons/16x16/filesave.png + + + &Speichern + + + + + :/icons/16x16/filesaveas.png + + + Speichern &unter... + + + + + :/icons/16x16/idea_info.png + + + Lern-Assistent... + + + Ruft den Lern-Assistenten auf + + + Ruft den Lern-Assistenten auf + + + <b>Lern-Assistent</b><p>Der Lern-Assistent hilft Ihnen beim Auswahl der zu lerndenden Fragen. + + + F5 + + + + + Import US... + + + + + :/icons/16x16/info.png + + + &Information... + + + Informationen zum Fragenkatalog + + + Zeigt Informationen zum Fragenkatalog an. + + + <b>Information</b><p>Zeigt Informationen, wie z.B. Gültigkeitseitraum, Herausgeber, Version,... zum aktuell geladenen Fragenkatalog an. + + + + + + + + diff --git a/osziparchive.cpp b/osziparchive.cpp new file mode 100644 index 0000000..3ac3255 --- /dev/null +++ b/osziparchive.cpp @@ -0,0 +1,467 @@ + +#include "osziparchive.h" + +#include "zlib/zlib.h" + +#include +#include +#include + + +void CZipFileHeader::clear() +{ + m_uVersionMadeBy = 0; + m_uVersionNeeded = 0; + m_uFlags = 0; + m_uCompression = 0; + m_uTime = 0; + m_uDate = 0; + m_uCRC = 0; + m_uSizeCompressed = 0; + m_uSizeUncompressed = 0; + m_uDiskNumberStart = 0; + m_uInternalFileAttributes = 0; + m_uExternalFileAttributes = 0; + m_uRelativeOffsetLocalHeader = 0; + m_strFileName.clear(); + m_strComment.clear(); + m_baExtraField.clear(); + + // helpers + m_uDataPosition = 0; + m_uLocalHeaderPosition = 0; + m_uCentralHeaderPosition = 0; +} + +void CZipFileHeader::integrate(const CZipFileHeader& head) +{ + if (m_uVersionMadeBy == 0) m_uVersionMadeBy = head.m_uVersionMadeBy; + if (m_uVersionNeeded == 0) m_uVersionNeeded = head.m_uVersionNeeded; + if (m_uFlags == 0) m_uFlags = head.m_uFlags; + if (m_uCompression == 0) m_uCompression = head.m_uCompression; + if (m_uTime == 0) m_uTime = head.m_uTime; + if (m_uDate == 0) m_uDate = head.m_uDate; + if (m_uCRC == 0) m_uCRC = head.m_uCRC; + if (m_uSizeCompressed == 0) m_uSizeCompressed = head.m_uSizeCompressed; + if (m_uSizeUncompressed == 0) m_uSizeUncompressed = head.m_uSizeUncompressed; + if (m_uDiskNumberStart == 0) m_uDiskNumberStart = head.m_uDiskNumberStart; + if (m_uInternalFileAttributes == 0) m_uInternalFileAttributes = head.m_uInternalFileAttributes; + if (m_uExternalFileAttributes == 0) m_uExternalFileAttributes = head.m_uExternalFileAttributes; + if (m_uRelativeOffsetLocalHeader == 0) m_uRelativeOffsetLocalHeader = head.m_uRelativeOffsetLocalHeader; + + if (m_strFileName.isEmpty()) m_strFileName = head.m_strFileName; + if (m_baExtraField.isEmpty()) m_baExtraField = head.m_baExtraField; + if (m_strComment.isEmpty()) m_strComment = head.m_strComment; + + if (m_uDataPosition == 0) m_uDataPosition = head.m_uDataPosition; + if (m_uLocalHeaderPosition == 0) m_uLocalHeaderPosition = head.m_uLocalHeaderPosition; + if (m_uCentralHeaderPosition == 0) m_uCentralHeaderPosition = head.m_uCentralHeaderPosition; +} + +bool CZipFileHeader::readLocalFileHeader(QFile& file) +{ +QDataStream in(&file); +quint16 uFileNameLength=0, uExtraFieldLength=0; + + clear(); + in.setByteOrder(QDataStream::LittleEndian); + + m_uLocalHeaderPosition = file.pos(); + + in >> m_uVersionNeeded; + in >> m_uFlags; + in >> m_uCompression; + in >> m_uTime; + in >> m_uDate; + in >> m_uCRC; + in >> m_uSizeCompressed; + in >> m_uSizeUncompressed; + in >> uFileNameLength; + in >> uExtraFieldLength; + + if (uFileNameLength != 0) + { + QByteArray a (uFileNameLength, 0); + in.readRawData(a.data(), uFileNameLength); + m_strFileName = a; + } + if (uExtraFieldLength != 0) + { + m_baExtraField.resize(uExtraFieldLength); + in.readRawData(m_baExtraField.data(), uExtraFieldLength); + } + + m_uDataPosition = file.pos(); + in.skipRawData(m_uSizeCompressed); + + if (hasDataDescriptor()) + { + in >> m_uCRC; + in >> m_uSizeCompressed; + in >> m_uSizeUncompressed; + } + return true; +} + +void CZipFileHeader::writeLocalFileHeader(QFile& file) +{ + Q_UNUSED(file); +} + +bool CZipFileHeader::readCentralDirectoryHeader(QFile& file) +{ +QDataStream in(&file); +quint16 uFileNameLength=0, uExtraFieldLength=0, uCommentLength=0; + + clear(); + in.setByteOrder(QDataStream::LittleEndian); + + m_uCentralHeaderPosition = file.pos(); + + in >> m_uVersionMadeBy; + in >> m_uVersionNeeded; + in >> m_uFlags; + in >> m_uCompression; + in >> m_uTime; + in >> m_uDate; + in >> m_uCRC; + in >> m_uSizeCompressed; + in >> m_uSizeUncompressed; + in >> uFileNameLength; + in >> uExtraFieldLength; + in >> uCommentLength; + in >> m_uDiskNumberStart; + in >> m_uInternalFileAttributes; + in >> m_uExternalFileAttributes; + in >> m_uRelativeOffsetLocalHeader; + + if (uFileNameLength != 0) + { + QByteArray a (uFileNameLength, 0); + in.readRawData(a.data(), uFileNameLength); + m_strFileName = a; + } + if (uExtraFieldLength != 0) + { + m_baExtraField.resize(uExtraFieldLength); + in.readRawData(m_baExtraField.data(), uExtraFieldLength); + } + + if (uCommentLength != 0) + { + QByteArray a (uCommentLength, 0); + in.readRawData(a.data(), uCommentLength); + m_strComment = a; + } + return true; +} + +void CZipFileHeader::writeCentralDirectoryHeader(QFile& file) +{ + Q_UNUSED(file); +} + +void CZipEndRecord::clear() +{ + m_uDisk = 0; + m_uDiskCentralDir = 0; + m_uEntriesCentralDirDisk = 0; + m_uEntriesCentralDir = 0; + m_uCentralDirSize = 0; + m_uCentralDirOffset = 0; + m_strComment.clear(); +} + +bool CZipEndRecord::read (QFile& file) +{ +QDataStream in(&file); +quint16 uCommentLength=0; + + clear(); + in.setByteOrder(QDataStream::LittleEndian); + + in >> m_uDisk; + in >> m_uDiskCentralDir; + in >> m_uEntriesCentralDirDisk; + in >> m_uEntriesCentralDir; + in >> m_uCentralDirSize; + in >> m_uCentralDirOffset; + in >> uCommentLength; + + if (uCommentLength != 0) + { + QByteArray a (uCommentLength, 0); + in.readRawData(a.data(), uCommentLength); + m_strComment = a; + } + return true; +} + +void CZipEndRecord::write (QFile& file) +{ + Q_UNUSED(file); +} + + +CZipFile::CZipFile(CZipArchive *pArchive) +{ + m_pArchive = pArchive; + clear(); +} + +CZipFile::~CZipFile() +{ +} + +void CZipFile::clear() +{ +} + +bool CZipFile::readHeader () +{ +QFile& file = m_pArchive->m_file; + + return (m_head.readLocalFileHeader(file)); +} + +QByteArray CZipFile::deflateToByteArray() +{ +/*QByteArray a, b; + + if (m_head.m_uDataPosition == 0 || m_head.m_uSizeUncompressed == 0) return QByteArray(); + + m_pArchive->m_file.seek (m_head.m_uDataPosition); + a = m_pArchive->m_file.read (m_head.m_uSizeCompressed); + + b.resize(m_head.m_uSizeUncompressed); + quint32 u = m_head.m_uSizeUncompressed; + Bytef* src = (Bytef*)a.data(); + Bytef* dst = (Bytef*)b.data(); + if (uncompress(dst, (uLongf*) &u, src, m_head.m_uSizeCompressed) != Z_OK) + return QByteArray(); + + return b;*/ + +QByteArray a; +z_stream strm; +char *pIn=0, *pOut=0; +int ret; + + memset(&strm, 0, sizeof(z_stream)); + if (m_head.m_uDataPosition == 0 || m_head.m_uSizeUncompressed == 0) return QByteArray(); + + // prepare zip-stream + /* windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. + */ + if (inflateInit2(&strm, -MAX_WBITS) != Z_OK) return QByteArray(); + + // read data + pIn = new char[m_head.m_uSizeCompressed+1]; + memset (pIn, 0, m_head.m_uSizeCompressed+1); + m_pArchive->m_file.seek (m_head.m_uDataPosition); + m_pArchive->m_file.read (pIn, m_head.m_uSizeCompressed); + + // prepare output + pOut = new char[m_head.m_uSizeUncompressed]; + memset(pOut, 0, m_head.m_uSizeUncompressed); + +/* +// DEBUG START + FILE *fpt; + fpt = fopen ("questions.xml", "rt"); + fread (pOut, m_head.m_uSizeUncompressed, 1, fpt); + fclose (fpt); + + unsigned u = m_head.m_uSizeCompressed; + fpt = fopen ("test2.gz", "wb"); + compress2((Bytef*)pIn, (uLongf*) &u, (Bytef*)pOut, m_head.m_uSizeUncompressed, 9); + fwrite (pIn, u, 1, fpt); + fclose (fpt); +// DEBUG ENDE +*/ + do + { + strm.avail_in = m_head.m_uSizeCompressed+1; + strm.next_in = (Bytef*) pIn; + do + { + strm.avail_out = m_head.m_uSizeUncompressed; + strm.next_out = (Bytef*) pOut; + ret = inflate (&strm, Z_SYNC_FLUSH); + switch (ret) + { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; /* and fall through */ + case Z_DATA_ERROR: + case Z_MEM_ERROR: + inflateEnd(&strm); + delete [] pIn; + delete [] pOut; + return QByteArray(); + } + } + while (strm.avail_out == 0); + + /* done when inflate() says it's done */ + } + while (ret != Z_STREAM_END); + + inflateEnd(&strm); + + a = QByteArray (pOut, m_head.m_uSizeUncompressed); + delete [] pIn; + delete [] pOut; + + return a; +} +/* +QString CZipFile::deflateToString() +{ +QByteArray a = deflateToByteArray(); +QTextStream in (a, QIODevice::ReadOnly); +//QString str; +// in >> str; + return in.readAll(); +} +*/ + +#define CHUNK (1<<16) + +bool CZipFile::deflateToFile (QIODevice& dev) +{ +z_stream strm; +char cIn[CHUNK], cOut[CHUNK]; +int ret; + + if ((dev.openMode() & QIODevice::WriteOnly) == 0) + return false; + + memset(&strm, 0, sizeof(z_stream)); + if (m_head.m_uDataPosition == 0 || m_head.m_uSizeUncompressed == 0) return false; + + // prepare zip-stream + /* windowBits is passed < 0 to tell that there is no zlib header. + * Note that in this case inflate *requires* an extra "dummy" byte + * after the compressed stream in order to complete decompression and + * return Z_STREAM_END. + */ + if (inflateInit2(&strm, -MAX_WBITS) != Z_OK) return false; + + // prepare archive + m_pArchive->m_file.seek (m_head.m_uDataPosition); + + // unzip + unsigned uRead=0, uBytes=0; + do + { + uBytes = m_pArchive->m_file.read(cIn, CHUNK); + if (uRead + uBytes > m_head.m_uSizeCompressed) + uBytes = m_head.m_uSizeCompressed - uRead + 1; // one dummy byte extra + uRead += uBytes; + + strm.avail_in = uBytes; + if (strm.avail_in == 0) break; + strm.next_in = (Bytef*) cIn; + + do + { + strm.avail_out = CHUNK; + strm.next_out = (Bytef*) cOut; + ret = inflate (&strm, Z_NO_FLUSH); + switch (ret) + { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; /* and fall through */ + case Z_DATA_ERROR: + case Z_MEM_ERROR: + inflateEnd(&strm); + return false; + } + unsigned uHave = CHUNK - strm.avail_out; + dev.write(cOut, uHave); + } + while (strm.avail_out == 0); + + /* done when inflate() says it's done */ + } + while (ret != Z_STREAM_END); + + inflateEnd(&strm); + return true; +} + + +CZipArchive::CZipArchive() +{ +} + +CZipArchive::~CZipArchive() +{ + qDeleteAll(m_listFiles); + qDeleteAll(m_listEndRecords); +} + +bool CZipArchive::open (const QString& strFileName, const OpenMode om) +{ +unsigned uSignature=0; +CZipFile *pzf=0; + + if (strFileName.isEmpty()) return false; + if (om != OpenReadOnly) return false; + + m_strFileName = strFileName; + m_file.close(); + m_file.setFileName(strFileName); + if (!m_file.open(QIODevice::ReadOnly)) return false; + QDataStream in(&m_file); + in.setByteOrder(QDataStream::LittleEndian); + + while (!in.atEnd()) + { + uSignature = 0; + in >> uSignature; + if (uSignature == 0x04034b50) + { // local file header with data + pzf = new CZipFile(this); + if (pzf->readHeader()) + m_listFiles.append(pzf); + //qDebug("%7i %7i %s", pzf->m_head.m_uSizeUncompressed, pzf->m_head.m_uSizeCompressed, qPrintable(pzf->fileName())); +// file.deflateToFile("test.tmp"); + } + else if (uSignature == 0x02014b50) + { // central directory file header + CZipFileHeader head; + head.readCentralDirectoryHeader(m_file); + pzf = findFile(head.m_strFileName); + if (pzf) pzf->m_head.integrate(head); + } + else if (uSignature == 0x06054b50) + { // End of central directory record + CZipEndRecord *pzer = new CZipEndRecord(); + pzer->read(m_file); + m_listEndRecords.append(pzer); + } + else + { + qDebug("Unknown signature: %X", uSignature); + break; + } + } + + return true; +} + +CZipFile* CZipArchive::findFile (const QString& strFileName) +{ + for (int i=0; ifileName() == strFileName) + return m_listFiles.at(i); + } + + return 0; +} + diff --git a/osziparchive.h b/osziparchive.h new file mode 100644 index 0000000..da03109 --- /dev/null +++ b/osziparchive.h @@ -0,0 +1,155 @@ +/*************************************************************************** + * Copyright (C) 2003-2006 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef OSZIPARCHIVE_H +#define OSZIPARCHIVE_H + +#include +#include +#include + +class CZipFile; +class CZipArchive; + +class CZipFileHeader +{ +public: + CZipFileHeader() { clear(); } + ~CZipFileHeader() {} + + void clear(); + void integrate(const CZipFileHeader& head); + + bool readLocalFileHeader(QFile& file); + void writeLocalFileHeader(QFile& file); + + bool readCentralDirectoryHeader(QFile& file); + void writeCentralDirectoryHeader(QFile& file); + + inline bool hasDataDescriptor() const { return m_uFlags & (1<<3) ? true : false; } +public: + quint16 m_uVersionMadeBy; //!< version made by 2 bytes + quint16 m_uVersionNeeded; //!< version needed to extract 2 bytes + quint16 m_uFlags; //!< general purpose bit flag 2 bytes + quint16 m_uCompression; //!< compression method 2 bytes + quint16 m_uTime; //!< last mod file time 2 bytes + quint16 m_uDate; //!< last mod file date 2 bytes + quint32 m_uCRC; //!< crc-32 4 bytes + quint32 m_uSizeCompressed; //!< compressed size 4 bytes + quint32 m_uSizeUncompressed; //!< uncompressed size 4 bytes + quint16 m_uDiskNumberStart; //!< disk number start 2 bytes + quint16 m_uInternalFileAttributes; //!< internal file attributes 2 bytes + quint32 m_uExternalFileAttributes; //!< external file attributes 4 bytes + quint32 m_uRelativeOffsetLocalHeader; //!< relative offset of local header 4 bytes + + QString m_strFileName; //!< file name (variable size) + QByteArray m_baExtraField; //!< extra field (variable size) + QString m_strComment; //!< file comment (variable size) + + // helpers + quint64 m_uLocalHeaderPosition; //!< Position of the local file header, after the signature field + quint64 m_uDataPosition; //!< Position in the file of the data + quint64 m_uCentralHeaderPosition; //!< Position of the central directory file header, after the signature field +}; + +class CZipEndRecord +{ +public: + CZipEndRecord() { clear(); } + ~CZipEndRecord() { } + + void clear(); + + bool read (QFile& file); + void write (QFile& file); + +public: + quint16 m_uDisk; //!< number of this disk 2 bytes + quint16 m_uDiskCentralDir; //!< number of the disk with the start of the central directory 2 bytes + quint16 m_uEntriesCentralDirDisk; //!< total number of entries in the central directory on this disk 2 bytes + quint16 m_uEntriesCentralDir; //!< total number of entries in the central directory 2 bytes + quint32 m_uCentralDirSize; //!< size of the central directory 4 bytes + quint32 m_uCentralDirOffset; //!< offset of start of central directory with respect to the starting disk number 4 bytes + + QString m_strComment; +}; + +//! Represents a file in a zip archive +class CZipFile +{ +public: + CZipFile() { m_pArchive = 0; clear(); } + CZipFile(CZipArchive *pArchive); + ~CZipFile(); + + void clear(); + inline bool isValid() const { return m_pArchive != 0; } + + //! Reads local file header + //! Position has to be after the signature field + //! After the operation, the current position in the file is after the file data + bool readHeader(); + + bool deflateToFile (QIODevice& dev); + + QByteArray deflateToByteArray(); + //QString deflateToString(); + +// bool read (FILE *fpt, unsigned uPosition); + + //! this file has a data descriptor + + inline QString fileName() const { return m_head.m_strFileName; } + +public: + CZipArchive *m_pArchive; + CZipFileHeader m_head; + +}; + +class CZipArchive +{ +public: + CZipArchive(); + ~CZipArchive(); + + enum OpenMode { OpenReadOnly, OpenCreate, OpenModify }; + + bool open (const QString& strFileName, const OpenMode om); + + CZipFile* findFile (const QString& strFileName); + + inline int fileCount() const { return m_listFiles.size(); } + const CZipFile* fileAt(const int i) const { return m_listFiles.at(i); } + CZipFile* fileAt(const int i) { return m_listFiles.at(i); } + +protected: + friend class CZipFile; + + QFile m_file; + QString m_strFileName; + + QList m_listFiles; + QList m_listEndRecords; +}; + +#endif + diff --git a/plotwidget.cpp b/plotwidget.cpp new file mode 100644 index 0000000..03e51b7 --- /dev/null +++ b/plotwidget.cpp @@ -0,0 +1,426 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "plotwidget.h" +#include + +void CPlotWidgetPoint::clear() +{ +} + + +void CPlotWidgetCurve::clear() +{ + QList::clear(); +} + + +QRectF CPlotWidgetCurve::boundaries() const +{ +QRectF rect; + + for (int i=0; i rect.right()) + rect.setRight(p.x()); + + if (p.y() > rect.bottom()) + rect.setBottom(p.y()); + else if (p.y() < rect.top()) + rect.setTop(p.y()); + } + } + return rect; +} + +CPlotWidgetTic::CPlotWidgetTic(const double dPos, const QString& strText) +{ + clear(); + m_dPos = dPos; + m_strText = strText; +} + +CPlotWidgetTic::CPlotWidgetTic(const double dPos, const double dWidth, const QString& strText) +{ + clear(); + m_dPos = dPos; + m_dWidth = dWidth; + m_strText = strText; +} + +CPlotWidgetTic::CPlotWidgetTic(const double dPos, const QPixmap& pixmap) +{ + clear(); + m_dPos = dPos; + m_pixmap = pixmap; +} + +void CPlotWidgetTic::clear() +{ + m_dPos = 0.0; + m_dWidth = 0.0; + m_pen = QPen(Qt::DashLine); + m_pen.setColor(Qt::darkGray); + m_penFont = QPen(Qt::black); + m_pixmap = QPixmap(); + m_strText.clear(); + m_fillType = FillNone; + m_lineType = LinePlot; + m_iTextFlags = Qt::AlignHCenter | Qt::AlignTop; +} + +void CPlotWidgetTic::paintX (QPainter *pPainter, CPlotWidget *pWidget) const +{ +QPoint ptTL, ptBR; // TopLeft, BottomRight +QRect rectText; + + if (m_fillType != FillNone && m_dWidth > 0.0) + { + ptTL = pWidget->mapToPlot(QPointF(m_dPos, pWidget->m_rectData.top())); + ptBR = pWidget->mapToPlot(QPointF(m_dPos+m_dWidth, pWidget->m_rectData.bottom())); + + if (m_fillType == FillAll) + { + ptTL.setY(pWidget->m_rectPlot.top()); + ptBR.setY(pWidget->m_rectPlot.bottom()); + } + + if (ptTL.x() < pWidget->m_rectPlot.left()) + ptTL.setX(pWidget->m_rectPlot.left()); + + if (ptBR.x() > pWidget->m_rectPlot.right()) + ptBR.setX(pWidget->m_rectPlot.right()); + + pPainter->fillRect(QRect(ptTL, ptBR), m_brush); + + rectText = QRect( + QPoint(ptTL.x(), pWidget->m_rectPlot.bottom() + 5), + QPoint(ptBR.x(), pWidget->rect().bottom())); + } + else + { + rectText = QRect(); + } + + if (m_lineType != LineNone) + { + pPainter->setPen(m_pen); + pPainter->drawLine( + pWidget->mapToPlot(QPointF(m_dPos, pWidget->m_rectData.top())), + pWidget->mapToPlot(QPointF(m_dPos, pWidget->m_rectData.bottom()))); + } + + if (!m_strText.isEmpty()) + { + pPainter->setPen(m_penFont); + pPainter->drawText(rectText, m_iTextFlags, m_strText); + } +} + +void CPlotWidgetTic::paintY (QPainter *pPainter, CPlotWidget *pWidget) const +{ +QPoint ptTL, ptBR; // TopLeft, BottomRight +QRect rectText; + + if (m_fillType != FillNone && m_dWidth > 0.0) + { + ptTL = pWidget->mapToPlot(QPointF(pWidget->m_rectData.left(), m_dPos)); + ptBR = pWidget->mapToPlot(QPointF(pWidget->m_rectData.right(), m_dPos+m_dWidth)); + + if (m_fillType == FillAll) + { + ptTL.setX(pWidget->m_rectPlot.left()); + ptBR.setX(pWidget->m_rectPlot.right()); + } + + if (ptTL.y() < pWidget->m_rectPlot.top()) + ptTL.setY(pWidget->m_rectPlot.top()); + + if (ptBR.y() > pWidget->m_rectPlot.bottom()) + ptBR.setY(pWidget->m_rectPlot.bottom()); + + pPainter->fillRect(QRect(ptTL, ptBR), m_brush); + + rectText = QRect( + QPoint(3, ptTL.y()), + QPoint(pWidget->m_rectPlot.left()-3, ptBR.y())); + } + else + { + QFontMetrics fm(m_font); + QPoint pt = pWidget->mapToPlot(QPointF(pWidget->m_rectData.left(), m_dPos)); + rectText = QRect( + QPoint(3, pt.y()-fm.height()), + QPoint(pWidget->m_rectPlot.left()-3, pt.y()+fm.height())); + } + + if (m_lineType != LineNone) + { + pPainter->setPen(m_pen); + pPainter->drawLine( + pWidget->mapToPlot(QPointF(pWidget->m_rectData.left(), m_dPos)), + pWidget->mapToPlot(QPointF(pWidget->m_rectData.right(), m_dPos))); + } + + if (!m_pixmap.isNull()) + { + // Ziel-Mittelpunkt errechnen + QPoint pt = pWidget->mapToPlot(QPointF(0, m_dPos)); + pt.setX(pWidget->m_rectPlot.left() - m_pixmap.width()/2 - 5); + + // Linke obere Ecke errechnen und malen! + pt -= QPoint (m_pixmap.width()/2, m_pixmap.height()/2); + pPainter->drawPixmap(pt, m_pixmap); + } + + if (!m_strText.isEmpty()) + { + pPainter->setPen(m_penFont); + pPainter->drawText(rectText, m_iTextFlags, m_strText); + } +} + +CPlotWidget::CPlotWidget(QWidget *pParent) : QFrame(pParent) +{ + clear(); +} + +void CPlotWidget::clear() +{ + m_type = PlotLines; + m_bLimitAutoY = true; + m_bLimitAutoX = true; + m_dLimitXMin = 0.0; + m_dLimitXMax = 10.0; + m_dLimitYMin = 0.0; + m_dLimitYMax = 10.0; + m_dLimitXRound = 0.0; + m_dLimitYRound = 0.0; + m_dTicX = 0.0; + m_penTicX = QPen(Qt::DashLine); + m_penTicX.setColor(Qt::darkGray); + m_dTicY = 0.0; + m_penTicY = QPen(Qt::DashLine); + m_penTicY.setColor(Qt::darkGray); + m_iBorder = BorderLeft|BorderBottom; + m_iBorderDistTop = 5; + m_iBorderDistBottom = 5; + m_iBorderDistLeft = 5; + m_iBorderDistRight = 5; + m_brushPlotBkg = QBrush(Qt::white); + m_listTicX.clear(); + m_listTicY.clear(); + m_dBarWidth = 0.6; + m_dBarOffset = 0.0; + m_listCurves.clear(); +} + +void CPlotWidget::setBorderDistance(const int iLeft, const int iRight, const int iTop, const int iBottom) +{ + m_iBorderDistTop = iTop; + m_iBorderDistBottom = iBottom; + m_iBorderDistLeft = iLeft; + m_iBorderDistRight = iRight; +} + +void CPlotWidget::updateCache() +{ + m_rectPlot = plotArea(); + m_rectData = rectData(); +} + +QRect CPlotWidget::plotArea() const +{ +QRect rect = frameRect(); + rect.adjust(m_iBorderDistLeft, m_iBorderDistTop, -m_iBorderDistRight, -m_iBorderDistBottom); + return rect; +} + +QRectF CPlotWidget::rectData() const +{ +QRectF rect; + if (m_bLimitAutoX || m_bLimitAutoY) + { + for (int i=0; i 0.0) + { + rect.setLeft((unsigned)(rect.left() / m_dLimitXRound) * m_dLimitXRound); + rect.setRight((unsigned)(rect.right() / m_dLimitXRound + 1) * m_dLimitXRound); + } + + if (!m_bLimitAutoY) + { + rect.setTop(m_dLimitYMin); + rect.setBottom(m_dLimitYMax); + } + else if (m_dLimitYRound > 0.0) + { + rect.setTop((unsigned)(rect.top() / m_dLimitYRound) * m_dLimitYRound); + rect.setBottom((unsigned)(rect.bottom() / m_dLimitYRound + 1) * m_dLimitYRound); + } + return rect; +} + +QPoint CPlotWidget::mapToPlot (QPointF p) +{ +QPointF ret; +double m; + + // Umrechung X-Koordinate + m = ((double) (m_rectPlot.left() - m_rectPlot.right())) / (m_rectData.left() - m_rectData.right()); + ret.setX(m * (p.x() - m_rectData.left()) + m_rectPlot.left()); + + // Umrechnung Y-Koordinate (mit Spiegelung) + m = ((double) (m_rectPlot.bottom() - m_rectPlot.top())) / (m_rectData.top() - m_rectData.bottom()); + ret.setY(m * (p.y() - m_rectData.top()) + m_rectPlot.bottom()); + + return ret.toPoint(); +} + +void CPlotWidget::paintEvent (QPaintEvent *e) +{ +QList listPoints; +QList listTicsX, listTicsY; +double d=0.0; +int i=0; + + QFrame::paintEvent(e); + QPainter painter(this); + + updateCache(); + + // Draw Background + if (m_brushPlotBkg.style() != Qt::NoBrush) + { + painter.fillRect(m_rectPlot, m_brushPlotBkg); + } + + // Draw Tics + listTicsX = m_listTicX; + if (listTicsX.isEmpty() && m_dTicX > 0.0) + { + d = ((unsigned)(m_rectData.left() / m_dTicX)) * m_dTicX; + if (d < m_rectData.left()) d+=m_dTicX; + while (d <= m_rectData.right()) + { + CPlotWidgetTic tic(d, QString("%1").arg(d,0,'f',2)); + tic.setPen(m_penTicX); + listTicsX.append(tic); + d += m_dTicX; + } + } + listTicsY = m_listTicY; + if (listTicsY.isEmpty() && m_dTicY > 0.0) + { + d = ((unsigned)(m_rectData.top() / m_dTicY)) * m_dTicY; + if (d < m_rectData.top()) d+=m_dTicY; + while (d <= m_rectData.bottom()) + { + CPlotWidgetTic tic(d, QString("%1").arg(d,0,'f',2)); + tic.setPen(m_penTicY); + listTicsY.append(tic); + d += m_dTicY; + } + } + for (i=0; i +#include +#include +#include +#include +#include +#include + +class CPlotWidgetPoint; +class CPlotWidgetCurve; +class CPlotWidgetTic; +class CPlotWidget; + +class CPlotWidgetPoint : public QPointF +{ +public: + CPlotWidgetPoint() { clear(); } + CPlotWidgetPoint(const double x, const double y) : QPointF(x,y) {} + ~CPlotWidgetPoint() {} + + void clear(); + +protected: +}; + +class CPlotWidgetCurve : public QList +{ +public: + CPlotWidgetCurve() { clear(); } + ~CPlotWidgetCurve() {} + + void clear(); + QRectF boundaries() const; + + inline void setPen(const QPen& pen) { m_pen = pen; } + inline QPen pen() const { return m_pen; } + inline void setBrush(const QBrush& brush) { m_brush = brush; } + inline QBrush brush() const { return m_brush; } + +protected: + QPen m_pen; + QBrush m_brush; +}; + +class CPlotWidgetTic +{ +public: + enum LineType { LineNone, LineShort, LinePlot, LineFull }; + enum FillType { FillNone, FillPlot, FillAll }; + + CPlotWidgetTic() { clear(); } + CPlotWidgetTic(const double dPos, const QString& strText); + CPlotWidgetTic(const double dPos, const double dWidth, const QString& strText); + CPlotWidgetTic(const double dPos, const QPixmap& pixmap); + ~CPlotWidgetTic() {} + + void clear(); + + inline void setPen(const QPen& pen) { m_pen = pen; } + inline QPen pen() const { return m_pen; } + inline void setBrush(const QBrush& brush) { m_brush = brush; } + inline QBrush brush() const { return m_brush; } + inline void setFont(const QFont& font) { m_font = font; } + inline QFont font() const { return m_font; } + inline void setFontPen(const QPen& pen) { m_penFont = pen; } + inline QPen fontPen() const { return m_penFont; } + inline void setPos(const double dPos) { m_dPos = dPos; } + inline double pos() const { return m_dPos; } + inline void setWidth(const double dWidth) { m_dWidth = dWidth; } + inline double width() const { return m_dWidth; } + inline void setFillType(const FillType t) { m_fillType = t; } + inline FillType fillType () const { return m_fillType; } + inline void setLineType(const LineType t) { m_lineType = t; } + inline LineType lineType () const { return m_lineType; } + inline void setText(const QString& str) { m_strText = str; } + inline QString text() const { return m_strText; } + inline void setTextFlags (const int iFlags) { m_iTextFlags = iFlags; } + inline int textFlags() const { return m_iTextFlags; } + + void paintX (QPainter *pPainter, CPlotWidget *pWidget) const; + void paintY (QPainter *pPainter, CPlotWidget *pWidget) const; + +protected: + double m_dPos; + + // Beschriftung + QPixmap m_pixmap; + QString m_strText; + QFont m_font; + QPen m_penFont; + int m_iTextFlags; + + // Line + QPen m_pen; + LineType m_lineType; + + // Background + double m_dWidth; + QBrush m_brush; + FillType m_fillType; +}; + +class CPlotWidget : public QFrame +{ +Q_OBJECT +public: + enum PlotType { PlotPoints, PlotLines, PlotBarsSum, PlotBars }; + enum Border { BorderTop=0x01, BorderBottom=0x02, BorderLeft=0x04, BorderRight=0x08 }; + + CPlotWidget(QWidget *pParent=0); + ~CPlotWidget() {} + + void clear(); + + inline void clearCurves() { m_listCurves.clear(); } + inline void appendCurve (const CPlotWidgetCurve& c) { m_listCurves.append(c); } + + QRect plotArea() const; + QRectF rectData() const; + + inline void setType (const PlotType t) { m_type = t; } + inline PlotType type() const { return m_type; } + inline void setLimitX(const double min, const double max) { m_dLimitXMin = min; m_dLimitXMax = max; m_bLimitAutoX = false; } + inline void setLimitY(const double min, const double max) { m_dLimitYMin = min; m_dLimitYMax = max; m_bLimitAutoY = false; } + inline void setAutoLimitX(const bool bAutoLimit) { m_bLimitAutoX = bAutoLimit; } + inline void setAutoLimitY(const bool bAutoLimit) { m_bLimitAutoY = bAutoLimit; } + inline void setAutoLimitRoundX(const double dRound) { m_dLimitXRound = dRound; } + inline void setAutoLimitRoundY(const double dRound) { m_dLimitYRound = dRound; } + + inline void setLabelX(const QString& str) { m_strLabelX = str; } + inline void setLabelY(const QString& str) { m_strLabelY = str; } + + inline void setPlotBackground (const QBrush& brush) { m_brushPlotBkg = brush; } + inline void setBorderPen(const QPen& pen) { m_penBorder = pen; } + inline QPen borderPen() const { return m_penBorder; } + inline void setBorder(const int iBorder=BorderLeft|BorderBottom) { m_iBorder = iBorder; } + inline int border() const { return m_iBorder; } + void setBorderDistance(const int iLeft, const int iRight, const int iTop, const int iBottom); + + inline void setTicX(const double dTic) { m_dTicX = dTic; } + inline void setTicY(const double dTic) { m_dTicY = dTic; } + inline void setTicXPen(const QPen& pen) { m_penTicX = pen; } + inline void setTicYPen(const QPen& pen) { m_penTicY = pen; } + inline void setTicListX (const QList& list) { m_listTicX = list; } + inline void setTicListY (const QList& list) { m_listTicY = list; } + + inline void setBarWidth(const double d) { m_dBarWidth = d; } + inline void setBarOffset(const double d) { m_dBarOffset = d; } + +protected: + virtual void paintEvent (QPaintEvent *e); + void updateCache(); + QPoint mapToPlot (QPointF p); + + friend class CPlotWidgetTic; + +protected: + PlotType m_type; + QList m_listCurves; + + double m_dLimitXMin, m_dLimitXMax, m_dLimitYMin, m_dLimitYMax; + double m_dLimitXRound, m_dLimitYRound; + bool m_bLimitAutoX, m_bLimitAutoY; + + // Border + QBrush m_brushPlotBkg; + QPen m_penBorder; + int m_iBorder; //!< Logische Verknüpfung aus BorderTop, BorderBottom, BorderLeft, BorderRight + int m_iBorderDistTop; + int m_iBorderDistBottom; + int m_iBorderDistLeft; + int m_iBorderDistRight; + + // Tics & Labels + QList m_listTicX; + QList m_listTicY; + double m_dTicX; + QPen m_penTicX; + double m_dTicY; + QPen m_penTicY; + QString m_strLabelX; + QString m_strLabelY; + + // Bars + double m_dBarWidth; + double m_dBarOffset; + + // Cache + QRect m_rectPlot; + QRectF m_rectData; +}; + diff --git a/question.cpp b/question.cpp new file mode 100644 index 0000000..21f3b15 --- /dev/null +++ b/question.cpp @@ -0,0 +1,709 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "question.h" +#include "catalog.h" +#include "tools.h" + +#include +#include +#include +#include + + +void CDayStatistic::clear() +{ + m_date = QDate(); + m_uClickedWrong = 0; + m_uClickedCorrect = 0; + m_uTimeExpeditureCorrect = 0; + m_uTimeExpeditureWrong = 0; + m_dLevel = 0.0; +} + +CDayStatistic& CDayStatistic::operator += (const CDayStatistic& ds) +{ + m_date = ds.m_date; + m_uClickedWrong += ds.m_uClickedWrong; + m_uClickedCorrect += ds.m_uClickedCorrect; + m_uTimeExpeditureCorrect += ds.m_uTimeExpeditureCorrect; + m_uTimeExpeditureWrong += ds.m_uTimeExpeditureWrong; + m_dLevel += ds.m_dLevel; + return *this; +} + +void CDayStatistic::debug() const +{ + qDebug("-- Day statistic %s --", qPrintable(m_date.toString(Qt::LocalDate))); + if (m_uClickedCorrect + m_uClickedWrong == 0) + { + qDebug (" No clicks"); + return; + } + qDebug(" %i correct + %i wrong = %i", m_uClickedCorrect, m_uClickedWrong, m_uClickedCorrect + m_uClickedWrong); + qDebug(" %i ms + %i ms = %i ms", m_uTimeExpeditureCorrect, m_uTimeExpeditureWrong, m_uTimeExpeditureCorrect + m_uTimeExpeditureWrong); + qDebug(" Level = %lf", m_dLevel); +} + +QString CQuestion::tr (const char *sourceText, const char *comment) +{ + return QCoreApplication::translate("CQuestion", sourceText, comment); +} + +unsigned CQuestion::waitDaysForRepeat (const unsigned uLevel) +{ + switch (uLevel) + { + case LEVEL_VERYOFTEN: + return 0; + case LEVEL_OFTEN: + return 1; + case LEVEL_NORMAL: + return 2; + case LEVEL_RARE: + return 4; + case LEVEL_VERYRARE: + return 8; + default: + case LEVEL_EXTREMERARE: + return 16; + } +} + + +void CQuestion::clear() +{ + m_pParentChapter = 0; + m_strId.clear(); + m_strText.clear(); + m_listAnswer.clear(); + m_strlGroups.clear(); + m_uErrorPoints = 1; + + m_uCorrectAnswers=0; + m_uCorrectSuccessive=0; + m_uWrongAnswers=0; + m_uWrongSuccessive=0; + m_uLevel=0; +} + +QString CQuestion::plainText() const +{ +QTextDocument doc; + doc.setHtml(m_strText); + return doc.toPlainText(); +} + +QString CQuestion::firstTextLine() const +{ +QString str = plainText(); +int i = str.indexOf('\n'); + return str.left (i); +} + +QString CQuestion::showText(CCatalog *pCatalog) const +{ +QString str, strHints; + str +="

Frage " + id() + "

"; + str += "

" + text() + "


"; + + str += "

"; + for (int i=0; i %1 ").arg(QChar('A' + i)); + // Text + str += ""; + str += ""; + } + str += "
" + m_listAnswer[i].text() + "

"; + strHints = pCatalog->hintText(id()); + if (!strHints.isEmpty()) + { + str += "

Hilfestellung

" + strHints; + } + str += "

Abfrageverlauf

"; + str += lastClickedTextExtended(); + str += " " + repeatDateTextExtended(); + if (!m_listAnswerClicked.isEmpty()) + { + str += "

"; + for (int i=0; i"; + } + str += "
Datum/Uhrzeit Antwort Richtig? Benötigte Zeit
" + ac.answerText() + ""; + if (ac.isCorrect(m_listAnswer)) + str += tr("richtig"); + else + str += tr("falsch"); + str += "" + QString ("%1").arg(ac.neededTimeText()) + "
"; + } + //str +="

Statistik

"; + return str; +} + +QString CQuestion::learnText(CCatalog *pCatalog, const bool bShowId, const bool bShowHints) +{ +QString str; + + if (bShowId) str += "" + id() + " "; + str += text() + "
"; + + str += "

"; + for (int i=0; i %1 ").arg(QChar('A' + i)); + // Text + str += ""; + } + str += "
"; + //CHEAT: if (mixedAnswerAt(i).isCorrect()) str += "OK "; + str += learnAnswerAt(i).text(); + str += "

"; + + if (bShowHints) + { + QString strHints = pCatalog->hintText(id()); + if (!strHints.isEmpty()) str += "
" + strHints; + } + return str; +} + +bool CQuestion::load (QDomElement elem) +{ +QString str; + if (elem.tagName () != "question") return false; + if (!elem.hasAttribute ("id")) return false; + + clear (); + m_strId = elem.attribute ("id"); + m_uErrorPoints = elem.attribute("errorpoints", "1").toUInt(); + str = elem.attribute("groups"); + str = str.replace(' ', ';').replace(',', ';'); + m_strlGroups = str.split(";", QString::SkipEmptyParts); + + if (m_uErrorPoints == 0) return false; + + QDomNode n = elem.firstChild(); + while (!n.isNull()) + { + if (n.isElement ()) + { + QDomElement e = n.toElement (); + str = e.text(); + if (e.tagName () == "textquestion") + m_strText = str; + else if (e.tagName () == "textanswer") + m_listAnswer.append(CAnswer(str, QVariant(e.attribute("correct", "false")).toBool())); + } + n = n.nextSibling(); + } + return true; +} + + +QString CQuestion::removeTempPath(const QString& strText) +{ +QString str=strText, strFileName, strBaseName; +int idxStart=-1, idxEnd=0; + while ((idxStart = str.indexOf(QRegExp("src\\s*=\\s*\""), idxEnd)) != -1) + { + idxStart = str.indexOf('"', idxStart)+1; + idxEnd = str.indexOf('"', idxStart); + strFileName = str.mid(idxStart, idxEnd - idxStart); + if (strFileName.isEmpty()) continue; + QFileInfo fi(strFileName); + strBaseName = fi.fileName(); + strBaseName = strBaseName.left(strBaseName.lastIndexOf('.')); + str.replace(strFileName, strBaseName); + } + return str; +} + +void CQuestion::save (QDomElement& parent, QDomDocument& doc) +{ +QDomElement elemRoot = doc.createElement("question"); + elemRoot.setAttribute("id", id()); + if (m_uErrorPoints > 1) + elemRoot.setAttribute("errorpoints", QString("%1").arg(m_uErrorPoints)); + if (!m_strlGroups.isEmpty()) + elemRoot.setAttribute("groups", m_strlGroups.join("; ")); + + parent.appendChild(elemRoot); + + QDomElement elemQuestion = doc.createElement("textquestion"); + QDomText textQuestion = doc.createTextNode(removeTempPath(text())); + elemQuestion.appendChild(textQuestion); + elemRoot.appendChild(elemQuestion); + + // save answers + for (int i=0; i"; + iAnswerCount = countCorrectAnswer(); + if (iAnswerCount != 1) + str += QObject::tr("Die Frage %1 hat nicht exakt eine richtige Antwort, sondern %2 Stück.").arg(id()).arg(iAnswerCount) + "
"; + for (int j=0; j"; + } + if (text().isEmpty()) + str += QObject::tr("Die Frage %1 enthält keinen Text.").arg(id()) + "
"; + + return str; +} + +void CQuestion::mixAnswers(const bool bMix) +{ +unsigned u1=0, u2=0; + m_listMixedAnswer.clear(); + if (!bMix) return; + for (int i=0; i>i) & 0x0001) << m_listMixedAnswer.at(i); + } + return u; +} + +void CQuestion::registerAnswerClicked (const unsigned uAnswerMask, const unsigned uNeededTime) +{ + m_listAnswerClicked.append(CAnswerClicked(orderedAnswerMask(uAnswerMask), uNeededTime)); + if (correctAnswerMask() == uAnswerMask) + { + m_uCorrectAnswers++; + m_uCorrectSuccessive++; + m_uWrongSuccessive = 0; + //if (m_uLevel > 0 && m_uLevel < LEVEL_MAX || m_uLevel == 0 && m_uCorrectSuccessive == 2) + if (m_uLevel < LEVEL_MAX) + m_uLevel++; + } + else + { + m_uWrongAnswers++; + m_uCorrectSuccessive = 0; + m_uWrongSuccessive++; + if (m_uLevel > 0) + m_uLevel--; + } +/* if (m_pParentChapter) + m_pParentChapter->updateStatistic(true); +*/ +} + +QString CQuestion::levelIconName(const unsigned uLevel) +{ +// if (uLevel <= LEVEL_VERYRARE) + return QString(":/icons/16x16/level%1.png").arg(uLevel); +// else +// return ":/icons/16x16/button_ok.png"; +} + +QIcon CQuestion::levelIcon(const unsigned uLevel) +{ + return QIcon (levelIconName(uLevel)); +} + +QString CQuestion::levelText(const unsigned uLevel) +{ + switch (uLevel) + { + case LEVEL_VERYOFTEN: + return QObject::tr("Ahnungslos"); + case LEVEL_OFTEN: + return QObject::tr("Anfänger"); + case LEVEL_NORMAL: + return QObject::tr("Fortgeschritten"); + case LEVEL_RARE: + return QObject::tr("Experte"); + case LEVEL_VERYRARE: + return QObject::tr("Freak"); + case LEVEL_EXTREMERARE: + return QObject::tr("Professor"); + default: + return QObject::tr("unbekannt"); + } +} + +QDateTime CQuestion::firstClicked() const +{ + // Vorraussetzung: m_listAnswerClicked ist nach Datum aufsteigend sortiert + if (m_listAnswerClicked.size() > 0) + return m_listAnswerClicked.at(0).dateTime(); + return QDateTime(); +} + +QDateTime CQuestion::lastClicked() const +{ + // Vorraussetzung: m_listAnswerClicked ist nach Datum aufsteigend sortiert + if (m_listAnswerClicked.size() > 0) + return m_listAnswerClicked.at(m_listAnswerClicked.size()-1).dateTime(); + return QDateTime(); +} + +QString CQuestion::lastClickedText() const +{ +QDateTime dtLastClicked = lastClicked(); +unsigned uSecs = dtLastClicked.secsTo (QDateTime::currentDateTime()); +unsigned uDays = dtLastClicked.daysTo (QDateTime::currentDateTime()); + + if (!dtLastClicked.isValid()) + return QString(); + + if (uSecs < 60) + return tr("vor < 1 min"); + else if (uSecs < 3600) + return (tr("vor %1 min").arg(uSecs / 60)); + else if (uDays == 1) + return (tr("gestern")); + else if (uDays > 1) + return (tr("vor %1 Tagen").arg(uDays)); + else + return (tr("vor %1 h").arg(uSecs / 3600)); +} + +QString CQuestion::lastClickedTextExtended() const +{ +QDateTime dtLastClicked = lastClicked(); +QString strRet; +unsigned uLevelOld=0, uLevelNew=0; + if (!dtLastClicked.isValid()) + return tr("Die Frage wurde noch nie beantwortet."); + + strRet = tr("Die Frage wurde %1 (%2) zuletzt beantwortet. ", "%1=gestern, vor x Tagen, ... / %2 = exaktes Datum m. Uhrzeit").arg(lastClickedText(), dtLastClicked.toString(Qt::LocalDate)); + + uLevelOld = levelAtEndOfDay(dtLastClicked.date().addDays(-1)); + uLevelNew = levelAtEndOfDay(dtLastClicked.date()); + + if (uLevelOld == uLevelNew) + strRet += tr("Die Einstufung des Lernfortschritts änderte sich an diesem Tag nicht."); + else + strRet += tr("Der Lernfortschritt änderte sich an diesem Tag von '%1' auf '%2'.").arg(levelText(uLevelOld), levelText(uLevelNew)); + + return strRet; +} + +/*! +\return Datum, an dem die Frage wiederholt werden soll. +Gibt es kein solches Datum, so wird ein ungültiges Datum (QDate::isValid()) zurückgegeben. +Hinweis: Das Datum kann auch in der Vergangenheit liegen! +*/ + +QDate CQuestion::repeatDate() const +{ +QDate dLastClicked = lastClicked().date(); +unsigned uLevelOld=0, uLevelNew=0; + + if (!dLastClicked.isValid()) return QDate(); + + uLevelOld = levelAtEndOfDay(dLastClicked.addDays(-1)); + uLevelNew = levelAtEndOfDay(dLastClicked); + + if (uLevelNew <= uLevelOld && uLevelNew != LEVEL_MAX) + return dLastClicked; + + return dLastClicked.addDays(waitDaysForRepeat(m_uLevel)); +} + +bool CQuestion::isRepeatToday() const +{ +QDate d = repeatDate(); + + if (d.isValid() && d <= QDate::currentDate()) return true; + return false; +} + +QString CQuestion::repeatDateText() const +{ +QDate d = repeatDate(); + if (!d.isValid()) return QString(); + if (d <= QDate::currentDate()) + return tr("heute"); + else if (QDate::currentDate().addDays(1) == d) + return tr("morgen"); + else + return tr("in %1 Tagen").arg(QDate::currentDate().daysTo(d)); +} + +QString CQuestion::repeatDateTextExtended() const +{ +QString strRet; +unsigned uLevelOld=0, uLevelNew=0, uLevelTarget=0; +QDate dLastClicked = lastClicked().date(); + + if (!dLastClicked.isValid()) return QString(); + uLevelOld = levelAtEndOfDay(dLastClicked.addDays(-1)); + uLevelNew = levelAtEndOfDay(dLastClicked); + if (uLevelNew > uLevelOld) + uLevelTarget = uLevelNew + 1; + else + uLevelTarget = uLevelOld + 1; + if (uLevelTarget > LEVEL_MAX) uLevelTarget = LEVEL_MAX; + if (uLevelNew == LEVEL_MAX) + strRet = tr("Es wird empfohlen, die Frage %1 zu wiederholen.").arg(repeatDateText()); + else + strRet = tr("Es wird empfohlen, die Frage %1 zu wiederholen, um den Lernfortschritt '%2' zu erreichen.") + .arg(repeatDateText()).arg(levelText(uLevelTarget)); + return strRet; +} + +unsigned CQuestion::levelAtEndOfDay(const QDate& date) const +{ +unsigned uLevel = 0, uCorrectSuccessive=0; +CAnswerClicked ac; + // Vorraussetzung: m_listAnswerClicked ist nach Datum aufsteigend sortiert + for (int i=0; i date) return uLevel; + if (ac.isCorrect(m_listAnswer)) + { + uCorrectSuccessive++; + if (uLevel < LEVEL_MAX) + { + //if (uLevel == LEVEL_VERYOFTEN && uCorrectSuccessive == 2 || uLevel > LEVEL_VERYOFTEN) + uLevel++; + } + } + else + { + uCorrectSuccessive = 0; + if (uLevel > LEVEL_VERYOFTEN) uLevel--; + } + } + return uLevel; +} + +CDayStatistic CQuestion::dayStatistic(const QDate& date) const +{ +CAnswerClicked ac; +CDayStatistic ds; + ds.m_date = date; + if (date.isNull()) + ds.m_dLevel = levelAtEndOfDay(QDate::currentDate()); + else + ds.m_dLevel = levelAtEndOfDay(date); + + // Vorraussetzung: m_listAnswerClicked ist nach Datum aufsteigend sortiert + for (int i=0; i date) break; + if (ac.dateTime().date() < date) continue; + //if (ac.dateTime().date() != date) continue; + } + if (ac.isCorrect(m_listAnswer)) + { + ds.m_uClickedCorrect++; + ds.m_uTimeExpeditureCorrect += ac.neededTime(); + } + else + { + ds.m_uClickedWrong++; + ds.m_uTimeExpeditureWrong += ac.neededTime(); + } + } + + return ds; +} + + +/*! +\return true: Wenn die Frage heute zum ersten Mal beantwortet wurde und noch in der Kategorie LEVEL_VERYOFTEN ist +*/ +bool CQuestion::isLearningNew() const +{ + if (m_uLevel != LEVEL_VERYOFTEN) return false; + if (m_listAnswerClicked.size() > 0 + && m_listAnswerClicked.at(0).dateTime().date() != QDate::currentDate()) return false; + return true; +} + +/*! +\return true: Die Frage wurde gestern oder früher zum ersten Mal beantwortet +*/ +bool CQuestion::isKnownQuestion() const +{ + if (m_listAnswerClicked.size() > 0 + && m_listAnswerClicked.at(0).dateTime().date() < QDate::currentDate()) return true; + return false; +} diff --git a/question.h b/question.h new file mode 100644 index 0000000..c6005b8 --- /dev/null +++ b/question.h @@ -0,0 +1,247 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef QUESTION_H +#define QUESTION_H + +#include +#include +#include +#include +#include +#include +#include + +#include "answer.h" +#include "helper.h" + +unsigned afu_random (const unsigned uMin, const unsigned uMax); + +class CChapter; + +#define LEVEL_VERYOFTEN 0 +#define LEVEL_OFTEN 1 +#define LEVEL_NORMAL 2 +#define LEVEL_RARE 3 +#define LEVEL_VERYRARE 4 +#define LEVEL_EXTREMERARE 5 +#define LEVEL_MAX 5 + +class CDayStatistic +{ +public: + CDayStatistic() { clear(); } + ~CDayStatistic() {} + + void clear(); + + inline QDate date() const { return m_date; } + + inline unsigned clickedCount() const { return m_uClickedWrong + m_uClickedCorrect; } + inline unsigned clickedCorrect() const { return m_uClickedCorrect; } + inline unsigned clickedWrong() const { return m_uClickedWrong; } + inline unsigned timeExpediture() const { return m_uTimeExpeditureCorrect + m_uTimeExpeditureWrong; } + inline unsigned timeExpeditureCorrect() const { return m_uTimeExpeditureCorrect; } + inline unsigned timeExpeditureWrong() const { return m_uTimeExpeditureWrong; } + + inline unsigned levelRounded() const { return (unsigned) (m_dLevel + 0.5); } + inline double level() const { return m_dLevel; } + + CDayStatistic& operator += (const CDayStatistic& ds); + + void debug() const; + +protected: + QDate m_date; + unsigned m_uClickedWrong; + unsigned m_uClickedCorrect; + unsigned m_uTimeExpeditureCorrect; + unsigned m_uTimeExpeditureWrong; + double m_dLevel; + + friend class CQuestion; + friend class CChapter; +}; + +//! CQuestion speichert eine Frage mit allen Antworten und der Klick-Statistik. + +class CQuestion +{ +public: + //! Standard-Konstruktor + /*! Initialisiert die Klasse, indem die Funktion clear() aufgerufen wird. */ + CQuestion() { clear(); } + //! Standard-Destruktor + /*! In der Standard-Implementierung tut der Destruktor nichts. */ + ~CQuestion() {} + + /** @name Basisdaten + * Auslesen und setzen der Basisdaten einer Frage + */ + //@{ + //! Zurücksetzen aller Werte + /*! Es werden alle Daten der Frage gelöscht. */ + void clear(); + + //! Elternkapitel abfragen + /*! + \return Elternkapitel m_pParentChapter + \sa CChapter, setParentChapter(), m_pParentChapter + */ + inline CChapter* parentChapter() const { return m_pParentChapter; } + + //! Elternkapitel setzen + /*! + Durch diese Funktion wird lediglich die Variable m_pParentChapter gesetzt, jedoch nicht die Statistiken + des Eltern-Kapitels aktualisiert. + + \param pChapter Das zu setzende Elternkapitel der Frage + \sa CChapter, parentChapter(), m_pParentChapter + */ + inline void setParentChapter(CChapter *pChapter) { m_pParentChapter = pChapter; } + //! ID abfragen + inline QString id() const { return m_strId; } + //! Text abfragen + inline QString text() const { return m_strText; } + + //! ID setzen + inline void setId(const QString& strId) { m_strId = strId; } + //! Text setzen + inline void setText(const QString& strText) { m_strText = strText; } + + //! Anhängen eines Textes an den Fragentext + /*! + \param strText Anzuhängender Text + \sa m_strText + */ + inline void appendText(const QString& strText) { m_strText += strText; } + //@} + + QString plainText() const; + QString firstTextLine() const; + + QString showText(CCatalog *pCatalog) const; + QString learnText(CCatalog *pCatalog, const bool bShowId=true, const bool bShowHints=false); + + inline unsigned errorPoints() const { return m_uErrorPoints; } + inline void setErrorPoints (const unsigned u) { m_uErrorPoints = u; } + + QStringList groups() const { return m_strlGroups; } + + bool load (QDomElement elem); + void save (QDomElement& parent, QDomDocument& doc); + static QString removeTempPath(const QString& strText); + bool loadLearnStatistic (QDomElement elem); + bool saveLearnStatistic (QDomElement& parent, QDomDocument& doc); + QString checkForErrors() const; + + // answers + inline int countAnswer() const { return m_listAnswer.size(); } + inline const CAnswer& answerAt(int i) const { return m_listAnswer.at(i); } + inline CAnswer& answerAt(int i) { return m_listAnswer[i]; } + inline void appendAnswer(const CAnswer& a) { m_listAnswer.append(a); } + int countCorrectAnswer() const; + + // helpers +// inline bool hasHints() const { return !m_listHint.isEmpty(); } + + // learning + void mixAnswers(const bool bMix=true); + const CAnswer& learnAnswerAt(int i) const; + unsigned correctAnswerMask() const; + inline bool isCorrectAnswer (const unsigned uAnswerMask) const { return uAnswerMask == correctAnswerMask(); } + unsigned orderedAnswerMask(const unsigned uMixedAnswerMask); + void registerAnswerClicked (const unsigned uAnswerMask, const unsigned uNeededTime); + static QString answerMaskToString(const unsigned uAnswerMask); + QString correctionText(const unsigned uAnswerMask); + + // statistics + //! Abfragehäufigkeit der Frage + inline unsigned level() const { return m_uLevel; } + inline QString levelIconName() const { return levelIconName (m_uLevel); } + inline QIcon levelIcon() const { return levelIcon (m_uLevel); } + inline QPixmap levelPixmap() const { return QPixmap(levelIconName (m_uLevel)); } + inline QString levelText() const { return levelText (m_uLevel); } + inline int clickedCount() const { return m_listAnswerClicked.size(); } + inline unsigned clickedCorrect() const { return m_uCorrectAnswers; } + inline unsigned clickedWrong() const { return m_uWrongAnswers; } + inline unsigned clickedCorrectSuccessive() const { return m_uCorrectSuccessive; } + inline unsigned clickedWrongSuccessive() const { return m_uWrongSuccessive; } + inline bool isNeverAsked() const { return m_listAnswerClicked.isEmpty(); } + + unsigned levelAtEndOfDay(const QDate& date) const; //!< Lernfortschritt am Ende eines bestimmten Tages + CDayStatistic dayStatistic(const QDate& date) const; + + QDateTime firstClicked() const; + QDateTime lastClicked() const; + QString lastClickedText() const; + QString lastClickedTextExtended() const; + QDate repeatDate() const; //!< Wiederhol-Datum der Frage bestimmen + QString repeatDateText() const; + QString repeatDateTextExtended() const; + bool isRepeatToday() const; //!< Frage sollte heute wiederholt werden + + bool isLearningNew() const; //!< Ist dies eine neue Frage, die gerade gelernt wird? + bool isKnownQuestion() const; //!< Ist die Frage schon (länger) bekannt? + + //! Name des Icons einer bestimmten Abfragehäufigkeit ermitteln + static QString levelIconName(const unsigned uLevel); + //! Icon einer bestimmten Abfragehäufigkeit + static QIcon levelIcon(const unsigned uLevel); + //! Name einer bestimmten Abfragehäufigkeit + static QString levelText(const unsigned uLevel); + static QString tr (const char *sourceText, const char *comment=0); + //! Berechnung der Wartezeit für eine bestimmte Abfragehäufigkeit + static unsigned waitDaysForRepeat (const unsigned uLevel); + +protected: + + CChapter *m_pParentChapter; //!< Zeiger auf das (Eltern-) Kapitel, zu dem die Frage gehört. + // masterdata + QString m_strId; //!< ID der Frage + QString m_strText; //!< Fragentext + QList m_listAnswer; //!< Liste mit Antworten + unsigned m_uErrorPoints; //!< Fehlerpunkte bei Falschantwort in Prüfung + QStringList m_strlGroups; //!< Liste der Klassen/Gruppen, zu denen diese Frage gehört + + // learning + //! Durchmischte Antworten + /*! Die Liste enthält die Positionen der Liste m_listAnswer */ + QList m_listMixedAnswer; + + // Statistics + QList m_listAnswerClicked; //!< Liste mit sämtlichen Antwort-Klicks + unsigned m_uCorrectAnswers; //!< Anzahl der richtigen Antworten + unsigned m_uCorrectSuccessive; //!< Anzahl der richtigen Antworten zuletzt hintereinander + unsigned m_uWrongAnswers; //!< Anzahl der falschen Antworten + unsigned m_uWrongSuccessive; //!< Anzahl der falschen Antworten zuletzt hintereinander + + //! Aktuelle Stufe der Abfragehäufigkeit + /*! + Stufe 0: sehr häufige ... Stufe 5: sehr seltene Abfrage + + Die Abfragehäufigkeit wird nicht in der Statistik-Datei gespeichert, sondern bei jedem Laden der + Datei anhand der Beantwortungen in der Vergangenheit neu berechnet. + */ + unsigned m_uLevel; +}; + +#endif diff --git a/questionmodel.cpp b/questionmodel.cpp new file mode 100644 index 0000000..551488d --- /dev/null +++ b/questionmodel.cpp @@ -0,0 +1,175 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "chapter.h" +#include "questionmodel.h" + +#include + +CQuestionModel::CQuestionModel(QObject *pParent) : QAbstractItemModel (pParent) +{ + m_pChapter=0; +} + +CQuestionModel::~CQuestionModel() +{ +} + +void CQuestionModel::onLanguageChanged() +{ + headerDataChanged(Qt::Horizontal, 0, columnCount()-1); +} + +void CQuestionModel::clear() +{ + m_pChapter=0; + reset(); +} + +void CQuestionModel::setModelData (CChapter *pChapter) +{ + m_pChapter = pChapter; + reset(); +} + +int CQuestionModel::columnCount (const QModelIndex & parent) const +{ + Q_UNUSED(parent) + return 7; +} + +QVariant CQuestionModel::data (const QModelIndex & index, int role) const +{ +CQuestion *p; +QTextDocument t; + + if (!index.isValid()) return QVariant(); + p = (CQuestion*) index.internalPointer(); + if (p == 0) return QVariant(); + if (role == Qt::DisplayRole) + { + if (index.column() == 0) + return p->id(); + else if (index.column() == 1) + return p->firstTextLine(); + //return p->text(); + else if (index.column() == 2) + return p->clickedCorrect(); + else if (index.column() == 3) + return p->clickedWrong(); + else if (index.column() == 4) + return p->levelText(); + else if (index.column() == 5) + { + t.setHtml(p->lastClickedText()); + return t.toPlainText(); + } + else if (index.column() == 6) + { + t.setHtml(p->repeatDateText()); + return t.toPlainText(); + } + } + else if (role == Qt::DecorationRole) + { + if (index.column() == 4) + //return QIcon (QString(":/icons/16x16/level%1.png").arg(p->level())); + return p->levelIcon(); + } + else if (role == Qt::ToolTipRole) + { + if (index.column() == 5) + return p->lastClickedTextExtended(); + else if (index.column() == 6) + return p->repeatDateTextExtended(); +/* if (index.column() == 4) + return " " + p->levelText(); +*/ } +/* else if (role == Qt::WhatsThisRole) + { + if (index.column() == 4) + return QString("Was ist das?"); + } +*/ + return QVariant(); +} + +bool CQuestionModel::hasChildren (const QModelIndex & parent) const +{ + if (parent.isValid()) return false; + return (m_pChapter != 0); +} + +QVariant CQuestionModel::headerData (int section, Qt::Orientation orientation, int role) const +{ + if (orientation == Qt::Vertical) return QVariant(); + if (role == Qt::DisplayRole) + { + if (section == 0) + return tr("Kennung"); + else if (section == 1) + return tr("Frage"); + else if (section == 2) + return tr("R"); + else if (section == 3) + return tr("F"); + else if (section == 4) + return tr("Lernfortschritt"); + else if (section == 5) + return tr("letzte Abfrage"); + else if (section == 6) + return tr("Wiederholung"); + } +/* else if (role == Qt::DecorationRole) + { + if (section == m_iColBookmarkFlag) + return QIcon (":/icons/16x16/bookmark.png"); + }*/ + else if (role == Qt::ToolTipRole) + { + if (section == 2) + return tr("Anzahl der richtigen Antworten"); + else if (section == 3) + return tr("Anzahl der falschen Antworten"); + } + return QVariant(); +} + +QModelIndex CQuestionModel::index (int row, int column, const QModelIndex & parent) const +{ +CQuestion *p=0; + + if (m_pChapter == 0 || parent.isValid() || row >= m_pChapter->countQuestion()) return QModelIndex(); + p = m_pChapter->questionAt (row); + return createIndex (row, column, (void*)p); +} + +QModelIndex CQuestionModel::parent (const QModelIndex & index) const +{ + return QModelIndex(); +} + +int CQuestionModel::rowCount (const QModelIndex & parent) const +{ + if (m_pChapter == 0 || parent.isValid()) return 0; + return m_pChapter->countQuestion(); +} + diff --git a/questionmodel.h b/questionmodel.h new file mode 100644 index 0000000..45ba7ca --- /dev/null +++ b/questionmodel.h @@ -0,0 +1,58 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef QUESTIONMODEL_H +#define QUESTIONMODEL_H + +#include + +class CChapter; + +class CQuestionModel : public QAbstractItemModel +{ + Q_OBJECT +public: + CQuestionModel(QObject *pParent=0); + ~CQuestionModel(); + + void clear(); + void setModelData (CChapter *pChapter); + +public: + virtual int columnCount ( const QModelIndex & parent = QModelIndex() ) const; + virtual QVariant data ( const QModelIndex & index, int role = Qt::DisplayRole ) const; + virtual bool hasChildren (const QModelIndex & parent = QModelIndex()) const; + virtual QVariant headerData ( int section, Qt::Orientation orientation, int role = Qt::DisplayRole ) const; + virtual QModelIndex index (int row, int column, const QModelIndex & parent = QModelIndex()) const; + virtual QModelIndex parent ( const QModelIndex & index ) const; + virtual int rowCount ( const QModelIndex & parent = QModelIndex() ) const; + +public slots: + void onLanguageChanged(); + +protected: + void recalcColumn(); + +protected: + CChapter *m_pChapter; +}; + +#endif diff --git a/recentfiles.cpp b/recentfiles.cpp new file mode 100644 index 0000000..dba76d7 --- /dev/null +++ b/recentfiles.cpp @@ -0,0 +1,178 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "recentfiles.h" + +#include +#include +#include + +CRecentFiles::CRecentFiles(QObject *pParent) : QObject(pParent) +{ + m_actNoEntry = new QAction(this); + m_actSepTop = new QAction(this); + m_actSepTop->setSeparator(true); + m_actSepTop->setVisible(false); + m_actSepBottom = new QAction(this); + m_actSepBottom->setSeparator(true); + m_actSepBottom->setVisible(false); + m_showSeperator = SeperatorNone; + + m_actNoEntry->setEnabled(false); + m_bShowNoEntry = true; + onLanguageChanged(); +} + +CRecentFiles::~CRecentFiles() +{ + qDeleteAll(m_listActions); +} + +void CRecentFiles::clear() +{ + qDeleteAll(m_listActions); + m_listActions.clear(); +} + +void CRecentFiles::onLanguageChanged() +{ + m_actNoEntry->setText(tr("No recent files")); +} + +void CRecentFiles::create (const QString& strName, const unsigned uMaxCount) +{ +QAction *pAct=0; + qDeleteAll(m_listActions); + m_listActions.clear(); + m_strName = strName; + m_uMaxCount = uMaxCount; + + for (unsigned u=0; usetVisible(false); + connect(pAct, SIGNAL(triggered()), this, SLOT(onRecentFile())); + m_listActions.append(pAct); + } + updateActions(); +} + +void CRecentFiles::insertToMenu(QMenu *pMenu, QAction *pBefore) +{ + pMenu->insertAction(pBefore, m_actSepTop); + pMenu->insertAction(pBefore, m_actNoEntry); + pMenu->insertActions(pBefore, m_listActions); + pMenu->insertAction(pBefore, m_actSepBottom); +} + +QString CRecentFiles::settingName() const +{ +QString str = "RecentFileList"; + if (!m_strName.isEmpty()) str += "_" + m_strName; + return str; +} + +void CRecentFiles::onRecentFile() +{ + QAction *action = qobject_cast(sender()); + if (action) + loadFile(action->data().toString()); +} + +void CRecentFiles::setRecentFile(const QString& strFileName) +{ +QSettings set; +QStringList strl = set.value(settingName()).toStringList(); + + strl.removeAll(strFileName); + strl.prepend(strFileName); + while ((unsigned)strl.size() > m_uMaxCount) + strl.removeLast(); + + set.setValue(settingName(), strl); + + updateActions(); +} + +void CRecentFiles::removeFile(const QString& strFileName) +{ +QSettings set; +QStringList strl = set.value(settingName()).toStringList(); + strl.removeAll(strFileName); + set.setValue(settingName(), strl); + updateActions(); +} + +void CRecentFiles::removeAll() +{ +QSettings set; + set.setValue(settingName(), QStringList()); + updateActions(); +} + +void CRecentFiles::updateActions() +{ +QSettings set; +QStringList strl = set.value(settingName()).toStringList(); +unsigned uNumRecentFiles = qMin((unsigned)strl.size(), m_uMaxCount); +QString str; + + m_actNoEntry->setVisible(m_bShowNoEntry && uNumRecentFiles == 0); + m_actSepTop->setVisible((m_showSeperator == SeperatorTop || m_showSeperator == SeperatorBoth) && uNumRecentFiles != 0); + m_actSepBottom->setVisible((m_showSeperator == SeperatorBottom || m_showSeperator == SeperatorBoth) && uNumRecentFiles != 0); + + for (unsigned i=0; isetText(str); + m_listActions[i]->setData(strl[i]); + m_listActions[i]->setVisible(true); + } + for (unsigned j=uNumRecentFiles; jsetVisible(false); +} + +QString CRecentFiles::recentFile(const int idx) const +{ +QSettings set; +QStringList strl = set.value(settingName()).toStringList(); + if (idx >= strl.size()) return QString(); + return strl.at(idx); +} + +void CRecentFiles::setShowNoEntry (const bool bShow) +{ +QSettings set; +QStringList strl = set.value(settingName()).toStringList(); +unsigned uNumRecentFiles = qMin((unsigned)strl.size(), m_uMaxCount); + m_bShowNoEntry = bShow; + m_actNoEntry->setVisible(m_bShowNoEntry && uNumRecentFiles == 0); +} + +void CRecentFiles::setShowSeperator(const ShowSeperator show) +{ +QSettings set; +QStringList strl = set.value(settingName()).toStringList(); +unsigned uNumRecentFiles = qMin((unsigned)strl.size(), m_uMaxCount); + m_showSeperator = show; + m_actSepTop->setVisible((m_showSeperator == SeperatorTop || m_showSeperator == SeperatorBoth) && uNumRecentFiles != 0); + m_actSepBottom->setVisible((m_showSeperator == SeperatorBottom || m_showSeperator == SeperatorBoth) && uNumRecentFiles != 0); +} diff --git a/recentfiles.h b/recentfiles.h new file mode 100644 index 0000000..55c6868 --- /dev/null +++ b/recentfiles.h @@ -0,0 +1,82 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#pragma once + +#include +#include +#include + +class QMenu; + +class CRecentFiles : public QObject +{ +Q_OBJECT +public: + enum ShowSeperator { SeperatorNone, SeperatorTop, SeperatorBottom, SeperatorBoth }; + + CRecentFiles(QObject *pParent=0); + ~CRecentFiles(); + + void clear(); + + void create (const QString& strName, const unsigned uMaxCount=4); + void insertToMenu(QMenu *pMenu, QAction *pBefore=0); + + inline unsigned maxCount() const { return m_uMaxCount; } + inline QString name() const { return m_strName; } + + void setRecentFile(const QString& strFileName); + void removeFile(const QString& strFileName); + void removeAll(); + + QString recentFile(const int idx) const; + + void setShowNoEntry (const bool bShow); + inline bool showNoEntry() const { return m_bShowNoEntry; } + + void setShowSeperator(const ShowSeperator show); + inline ShowSeperator showSeperator() const { return m_showSeperator; } + +public slots: + void onLanguageChanged(); + +protected slots: + void onRecentFile(); + +signals: + void loadFile(QString strFile); + +protected: + QString settingName() const; + void updateActions(); + +protected: + QString m_strName; + unsigned m_uMaxCount; + + QList m_listActions; + QAction *m_actNoEntry; + QAction *m_actSepTop; + QAction *m_actSepBottom; + bool m_bShowNoEntry; + ShowSeperator m_showSeperator; +}; diff --git a/tools.cpp b/tools.cpp new file mode 100644 index 0000000..62f61f1 --- /dev/null +++ b/tools.cpp @@ -0,0 +1,53 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#include "tools.h" + +#include +#include + +unsigned afu_random (const unsigned uMin, const unsigned uMax) +{ +unsigned u=0; +float f = (float) (uMax-uMin+1) / (float) RAND_MAX; + u = (unsigned) ((float) rand () * f) + uMin;; + return (u); +} + +QDomElement createXmlTextElement(const QString& strName, const QString& strText, QDomDocument& doc) +{ +QDomElement elem = doc.createElement(strName); + elem.appendChild(doc.createTextNode(strText)); + return elem; +} + +QPixmap createProgressBar (int w, int h, double dPercent) +{ +QPixmap pix(w,h); +QPainter p(&pix); + + pix.fill(QPalette().color(QPalette::Dark)); + p.setBrush(Qt::blue); + p.setPen(Qt::blue); + if (dPercent != 0.0) + p.drawRect(0, 0, (int)(((double)w)*dPercent), h); + return pix; +} diff --git a/tools.h b/tools.h new file mode 100644 index 0000000..1f48732 --- /dev/null +++ b/tools.h @@ -0,0 +1,32 @@ +/*************************************************************************** + * Copyright (C) 2003-2007 by Oliver Saal * + * osaal@gmx.de * + * http://www.oliver-saal.de/software/afutrainer/ * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifndef TOOLS_H +#define TOOLS_H + +#include +#include + +unsigned afu_random (const unsigned uMin, const unsigned uMax); +QDomElement createXmlTextElement(const QString& strName, const QString& strText, QDomDocument& doc); +QPixmap createProgressBar (int w, int h, double dPercent); + +#endif diff --git a/translations/qt_de.qm b/translations/qt_de.qm new file mode 100644 index 0000000..da88adf Binary files /dev/null and b/translations/qt_de.qm differ diff --git a/zlib/zconf.h b/zlib/zconf.h new file mode 100644 index 0000000..e3b0c96 --- /dev/null +++ b/zlib/zconf.h @@ -0,0 +1,332 @@ +/* zconf.h -- configuration of the zlib compression library + * Copyright (C) 1995-2005 Jean-loup Gailly. + * For conditions of distribution and use, see copyright notice in zlib.h + */ + +/* @(#) $Id$ */ + +#ifndef ZCONF_H +#define ZCONF_H + +/* + * If you *really* need a unique prefix for all types and library functions, + * compile with -DZ_PREFIX. The "standard" zlib should be compiled without it. + */ +#ifdef Z_PREFIX +# define deflateInit_ z_deflateInit_ +# define deflate z_deflate +# define deflateEnd z_deflateEnd +# define inflateInit_ z_inflateInit_ +# define inflate z_inflate +# define inflateEnd z_inflateEnd +# define deflateInit2_ z_deflateInit2_ +# define deflateSetDictionary z_deflateSetDictionary +# define deflateCopy z_deflateCopy +# define deflateReset z_deflateReset +# define deflateParams z_deflateParams +# define deflateBound z_deflateBound +# define deflatePrime z_deflatePrime +# define inflateInit2_ z_inflateInit2_ +# define inflateSetDictionary z_inflateSetDictionary +# define inflateSync z_inflateSync +# define inflateSyncPoint z_inflateSyncPoint +# define inflateCopy z_inflateCopy +# define inflateReset z_inflateReset +# define inflateBack z_inflateBack +# define inflateBackEnd z_inflateBackEnd +# define compress z_compress +# define compress2 z_compress2 +# define compressBound z_compressBound +# define uncompress z_uncompress +# define adler32 z_adler32 +# define crc32 z_crc32 +# define get_crc_table z_get_crc_table +# define zError z_zError + +# define alloc_func z_alloc_func +# define free_func z_free_func +# define in_func z_in_func +# define out_func z_out_func +# define Byte z_Byte +# define uInt z_uInt +# define uLong z_uLong +# define Bytef z_Bytef +# define charf z_charf +# define intf z_intf +# define uIntf z_uIntf +# define uLongf z_uLongf +# define voidpf z_voidpf +# define voidp z_voidp +#endif + +#if defined(__MSDOS__) && !defined(MSDOS) +# define MSDOS +#endif +#if (defined(OS_2) || defined(__OS2__)) && !defined(OS2) +# define OS2 +#endif +#if defined(_WINDOWS) && !defined(WINDOWS) +# define WINDOWS +#endif +#if defined(_WIN32) || defined(_WIN32_WCE) || defined(__WIN32__) +# ifndef WIN32 +# define WIN32 +# endif +#endif +#if (defined(MSDOS) || defined(OS2) || defined(WINDOWS)) && !defined(WIN32) +# if !defined(__GNUC__) && !defined(__FLAT__) && !defined(__386__) +# ifndef SYS16BIT +# define SYS16BIT +# endif +# endif +#endif + +/* + * Compile with -DMAXSEG_64K if the alloc function cannot allocate more + * than 64k bytes at a time (needed on systems with 16-bit int). + */ +#ifdef SYS16BIT +# define MAXSEG_64K +#endif +#ifdef MSDOS +# define UNALIGNED_OK +#endif + +#ifdef __STDC_VERSION__ +# ifndef STDC +# define STDC +# endif +# if __STDC_VERSION__ >= 199901L +# ifndef STDC99 +# define STDC99 +# endif +# endif +#endif +#if !defined(STDC) && (defined(__STDC__) || defined(__cplusplus)) +# define STDC +#endif +#if !defined(STDC) && (defined(__GNUC__) || defined(__BORLANDC__)) +# define STDC +#endif +#if !defined(STDC) && (defined(MSDOS) || defined(WINDOWS) || defined(WIN32)) +# define STDC +#endif +#if !defined(STDC) && (defined(OS2) || defined(__HOS_AIX__)) +# define STDC +#endif + +#if defined(__OS400__) && !defined(STDC) /* iSeries (formerly AS/400). */ +# define STDC +#endif + +#ifndef STDC +# ifndef const /* cannot use !defined(STDC) && !defined(const) on Mac */ +# define const /* note: need a more gentle solution here */ +# endif +#endif + +/* Some Mac compilers merge all .h files incorrectly: */ +#if defined(__MWERKS__)||defined(applec)||defined(THINK_C)||defined(__SC__) +# define NO_DUMMY_DECL +#endif + +/* Maximum value for memLevel in deflateInit2 */ +#ifndef MAX_MEM_LEVEL +# ifdef MAXSEG_64K +# define MAX_MEM_LEVEL 8 +# else +# define MAX_MEM_LEVEL 9 +# endif +#endif + +/* Maximum value for windowBits in deflateInit2 and inflateInit2. + * WARNING: reducing MAX_WBITS makes minigzip unable to extract .gz files + * created by gzip. (Files created by minigzip can still be extracted by + * gzip.) + */ +#ifndef MAX_WBITS +# define MAX_WBITS 15 /* 32K LZ77 window */ +#endif + +/* The memory requirements for deflate are (in bytes): + (1 << (windowBits+2)) + (1 << (memLevel+9)) + that is: 128K for windowBits=15 + 128K for memLevel = 8 (default values) + plus a few kilobytes for small objects. For example, if you want to reduce + the default memory requirements from 256K to 128K, compile with + make CFLAGS="-O -DMAX_WBITS=14 -DMAX_MEM_LEVEL=7" + Of course this will generally degrade compression (there's no free lunch). + + The memory requirements for inflate are (in bytes) 1 << windowBits + that is, 32K for windowBits=15 (default value) plus a few kilobytes + for small objects. +*/ + + /* Type declarations */ + +#ifndef OF /* function prototypes */ +# ifdef STDC +# define OF(args) args +# else +# define OF(args) () +# endif +#endif + +/* The following definitions for FAR are needed only for MSDOS mixed + * model programming (small or medium model with some far allocations). + * This was tested only with MSC; for other MSDOS compilers you may have + * to define NO_MEMCPY in zutil.h. If you don't need the mixed model, + * just define FAR to be empty. + */ +#ifdef SYS16BIT +# if defined(M_I86SM) || defined(M_I86MM) + /* MSC small or medium model */ +# define SMALL_MEDIUM +# ifdef _MSC_VER +# define FAR _far +# else +# define FAR far +# endif +# endif +# if (defined(__SMALL__) || defined(__MEDIUM__)) + /* Turbo C small or medium model */ +# define SMALL_MEDIUM +# ifdef __BORLANDC__ +# define FAR _far +# else +# define FAR far +# endif +# endif +#endif + +#if defined(WINDOWS) || defined(WIN32) + /* If building or using zlib as a DLL, define ZLIB_DLL. + * This is not mandatory, but it offers a little performance increase. + */ +# ifdef ZLIB_DLL +# if defined(WIN32) && (!defined(__BORLANDC__) || (__BORLANDC__ >= 0x500)) +# ifdef ZLIB_INTERNAL +# define ZEXTERN extern __declspec(dllexport) +# else +# define ZEXTERN extern __declspec(dllimport) +# endif +# endif +# endif /* ZLIB_DLL */ + /* If building or using zlib with the WINAPI/WINAPIV calling convention, + * define ZLIB_WINAPI. + * Caution: the standard ZLIB1.DLL is NOT compiled using ZLIB_WINAPI. + */ +# ifdef ZLIB_WINAPI +# ifdef FAR +# undef FAR +# endif +# include + /* No need for _export, use ZLIB.DEF instead. */ + /* For complete Windows compatibility, use WINAPI, not __stdcall. */ +# define ZEXPORT WINAPI +# ifdef WIN32 +# define ZEXPORTVA WINAPIV +# else +# define ZEXPORTVA FAR CDECL +# endif +# endif +#endif + +#if defined (__BEOS__) +# ifdef ZLIB_DLL +# ifdef ZLIB_INTERNAL +# define ZEXPORT __declspec(dllexport) +# define ZEXPORTVA __declspec(dllexport) +# else +# define ZEXPORT __declspec(dllimport) +# define ZEXPORTVA __declspec(dllimport) +# endif +# endif +#endif + +#ifndef ZEXTERN +# define ZEXTERN extern +#endif +#ifndef ZEXPORT +# define ZEXPORT +#endif +#ifndef ZEXPORTVA +# define ZEXPORTVA +#endif + +#ifndef FAR +# define FAR +#endif + +#if !defined(__MACTYPES__) +typedef unsigned char Byte; /* 8 bits */ +#endif +typedef unsigned int uInt; /* 16 bits or more */ +typedef unsigned long uLong; /* 32 bits or more */ + +#ifdef SMALL_MEDIUM + /* Borland C/C++ and some old MSC versions ignore FAR inside typedef */ +# define Bytef Byte FAR +#else + typedef Byte FAR Bytef; +#endif +typedef char FAR charf; +typedef int FAR intf; +typedef uInt FAR uIntf; +typedef uLong FAR uLongf; + +#ifdef STDC + typedef void const *voidpc; + typedef void FAR *voidpf; + typedef void *voidp; +#else + typedef Byte const *voidpc; + typedef Byte FAR *voidpf; + typedef Byte *voidp; +#endif + +#if 0 /* HAVE_UNISTD_H -- this line is updated by ./configure */ +# include /* for off_t */ +# include /* for SEEK_* and off_t */ +# ifdef VMS +# include /* for off_t */ +# endif +# define z_off_t off_t +#endif +#ifndef SEEK_SET +# define SEEK_SET 0 /* Seek from beginning of file. */ +# define SEEK_CUR 1 /* Seek from current position. */ +# define SEEK_END 2 /* Set file pointer to EOF plus "offset" */ +#endif +#ifndef z_off_t +# define z_off_t long +#endif + +#if defined(__OS400__) +# define NO_vsnprintf +#endif + +#if defined(__MVS__) +# define NO_vsnprintf +# ifdef FAR +# undef FAR +# endif +#endif + +/* MVS linker does not support external names larger than 8 bytes */ +#if defined(__MVS__) +# pragma map(deflateInit_,"DEIN") +# pragma map(deflateInit2_,"DEIN2") +# pragma map(deflateEnd,"DEEND") +# pragma map(deflateBound,"DEBND") +# pragma map(inflateInit_,"ININ") +# pragma map(inflateInit2_,"ININ2") +# pragma map(inflateEnd,"INEND") +# pragma map(inflateSync,"INSY") +# pragma map(inflateSetDictionary,"INSEDI") +# pragma map(compressBound,"CMBND") +# pragma map(inflate_table,"INTABL") +# pragma map(inflate_fast,"INFA") +# pragma map(inflate_copyright,"INCOPY") +#endif + +#endif /* ZCONF_H */ diff --git a/zlib/zdll.exp b/zlib/zdll.exp new file mode 100644 index 0000000..2ca08c5 Binary files /dev/null and b/zlib/zdll.exp differ diff --git a/zlib/zdll.lib b/zlib/zdll.lib new file mode 100644 index 0000000..01f4e10 Binary files /dev/null and b/zlib/zdll.lib differ diff --git a/zlib/zlib.def b/zlib/zlib.def new file mode 100644 index 0000000..03bcf1c --- /dev/null +++ b/zlib/zlib.def @@ -0,0 +1,60 @@ +LIBRARY +; zlib data compression library + +EXPORTS +; basic functions + zlibVersion + deflate + deflateEnd + inflate + inflateEnd +; advanced functions + deflateSetDictionary + deflateCopy + deflateReset + deflateParams + deflateBound + deflatePrime + inflateSetDictionary + inflateSync + inflateCopy + inflateReset + inflateBack + inflateBackEnd + zlibCompileFlags +; utility functions + compress + compress2 + compressBound + uncompress + gzopen + gzdopen + gzsetparams + gzread + gzwrite + gzprintf + gzputs + gzgets + gzputc + gzgetc + gzungetc + gzflush + gzseek + gzrewind + gztell + gzeof + gzclose + gzerror + gzclearerr +; checksum functions + adler32 + crc32 +; various hacks, don't look :) + deflateInit_ + deflateInit2_ + inflateInit_ + inflateInit2_ + inflateBackInit_ + inflateSyncPoint + get_crc_table + zError diff --git a/zlib/zlib.h b/zlib/zlib.h new file mode 100644 index 0000000..62d0e46 --- /dev/null +++ b/zlib/zlib.h @@ -0,0 +1,1357 @@ +/* zlib.h -- interface of the 'zlib' general purpose compression library + version 1.2.3, July 18th, 2005 + + Copyright (C) 1995-2005 Jean-loup Gailly and Mark Adler + + This software is provided 'as-is', without any express or implied + warranty. In no event will the authors be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; you must not + claim that you wrote the original software. If you use this software + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original software. + 3. This notice may not be removed or altered from any source distribution. + + Jean-loup Gailly Mark Adler + jloup@gzip.org madler@alumni.caltech.edu + + + The data format used by the zlib library is described by RFCs (Request for + Comments) 1950 to 1952 in the files http://www.ietf.org/rfc/rfc1950.txt + (zlib format), rfc1951.txt (deflate format) and rfc1952.txt (gzip format). +*/ + +#ifndef ZLIB_H +#define ZLIB_H + +#include "zconf.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define ZLIB_VERSION "1.2.3" +#define ZLIB_VERNUM 0x1230 + +/* + The 'zlib' compression library provides in-memory compression and + decompression functions, including integrity checks of the uncompressed + data. This version of the library supports only one compression method + (deflation) but other algorithms will be added later and will have the same + stream interface. + + Compression can be done in a single step if the buffers are large + enough (for example if an input file is mmap'ed), or can be done by + repeated calls of the compression function. In the latter case, the + application must provide more input and/or consume the output + (providing more output space) before each call. + + The compressed data format used by default by the in-memory functions is + the zlib format, which is a zlib wrapper documented in RFC 1950, wrapped + around a deflate stream, which is itself documented in RFC 1951. + + The library also supports reading and writing files in gzip (.gz) format + with an interface similar to that of stdio using the functions that start + with "gz". The gzip format is different from the zlib format. gzip is a + gzip wrapper, documented in RFC 1952, wrapped around a deflate stream. + + This library can optionally read and write gzip streams in memory as well. + + The zlib format was designed to be compact and fast for use in memory + and on communications channels. The gzip format was designed for single- + file compression on file systems, has a larger header than zlib to maintain + directory information, and uses a different, slower check method than zlib. + + The library does not install any signal handler. The decoder checks + the consistency of the compressed data, so the library should never + crash even in case of corrupted input. +*/ + +typedef voidpf (*alloc_func) OF((voidpf opaque, uInt items, uInt size)); +typedef void (*free_func) OF((voidpf opaque, voidpf address)); + +struct internal_state; + +typedef struct z_stream_s { + Bytef *next_in; /* next input byte */ + uInt avail_in; /* number of bytes available at next_in */ + uLong total_in; /* total nb of input bytes read so far */ + + Bytef *next_out; /* next output byte should be put there */ + uInt avail_out; /* remaining free space at next_out */ + uLong total_out; /* total nb of bytes output so far */ + + char *msg; /* last error message, NULL if no error */ + struct internal_state FAR *state; /* not visible by applications */ + + alloc_func zalloc; /* used to allocate the internal state */ + free_func zfree; /* used to free the internal state */ + voidpf opaque; /* private data object passed to zalloc and zfree */ + + int data_type; /* best guess about the data type: binary or text */ + uLong adler; /* adler32 value of the uncompressed data */ + uLong reserved; /* reserved for future use */ +} z_stream; + +typedef z_stream FAR *z_streamp; + +/* + gzip header information passed to and from zlib routines. See RFC 1952 + for more details on the meanings of these fields. +*/ +typedef struct gz_header_s { + int text; /* true if compressed data believed to be text */ + uLong time; /* modification time */ + int xflags; /* extra flags (not used when writing a gzip file) */ + int os; /* operating system */ + Bytef *extra; /* pointer to extra field or Z_NULL if none */ + uInt extra_len; /* extra field length (valid if extra != Z_NULL) */ + uInt extra_max; /* space at extra (only when reading header) */ + Bytef *name; /* pointer to zero-terminated file name or Z_NULL */ + uInt name_max; /* space at name (only when reading header) */ + Bytef *comment; /* pointer to zero-terminated comment or Z_NULL */ + uInt comm_max; /* space at comment (only when reading header) */ + int hcrc; /* true if there was or will be a header crc */ + int done; /* true when done reading gzip header (not used + when writing a gzip file) */ +} gz_header; + +typedef gz_header FAR *gz_headerp; + +/* + The application must update next_in and avail_in when avail_in has + dropped to zero. It must update next_out and avail_out when avail_out + has dropped to zero. The application must initialize zalloc, zfree and + opaque before calling the init function. All other fields are set by the + compression library and must not be updated by the application. + + The opaque value provided by the application will be passed as the first + parameter for calls of zalloc and zfree. This can be useful for custom + memory management. The compression library attaches no meaning to the + opaque value. + + zalloc must return Z_NULL if there is not enough memory for the object. + If zlib is used in a multi-threaded application, zalloc and zfree must be + thread safe. + + On 16-bit systems, the functions zalloc and zfree must be able to allocate + exactly 65536 bytes, but will not be required to allocate more than this + if the symbol MAXSEG_64K is defined (see zconf.h). WARNING: On MSDOS, + pointers returned by zalloc for objects of exactly 65536 bytes *must* + have their offset normalized to zero. The default allocation function + provided by this library ensures this (see zutil.c). To reduce memory + requirements and avoid any allocation of 64K objects, at the expense of + compression ratio, compile the library with -DMAX_WBITS=14 (see zconf.h). + + The fields total_in and total_out can be used for statistics or + progress reports. After compression, total_in holds the total size of + the uncompressed data and may be saved for use in the decompressor + (particularly if the decompressor wants to decompress everything in + a single step). +*/ + + /* constants */ + +#define Z_NO_FLUSH 0 +#define Z_PARTIAL_FLUSH 1 /* will be removed, use Z_SYNC_FLUSH instead */ +#define Z_SYNC_FLUSH 2 +#define Z_FULL_FLUSH 3 +#define Z_FINISH 4 +#define Z_BLOCK 5 +/* Allowed flush values; see deflate() and inflate() below for details */ + +#define Z_OK 0 +#define Z_STREAM_END 1 +#define Z_NEED_DICT 2 +#define Z_ERRNO (-1) +#define Z_STREAM_ERROR (-2) +#define Z_DATA_ERROR (-3) +#define Z_MEM_ERROR (-4) +#define Z_BUF_ERROR (-5) +#define Z_VERSION_ERROR (-6) +/* Return codes for the compression/decompression functions. Negative + * values are errors, positive values are used for special but normal events. + */ + +#define Z_NO_COMPRESSION 0 +#define Z_BEST_SPEED 1 +#define Z_BEST_COMPRESSION 9 +#define Z_DEFAULT_COMPRESSION (-1) +/* compression levels */ + +#define Z_FILTERED 1 +#define Z_HUFFMAN_ONLY 2 +#define Z_RLE 3 +#define Z_FIXED 4 +#define Z_DEFAULT_STRATEGY 0 +/* compression strategy; see deflateInit2() below for details */ + +#define Z_BINARY 0 +#define Z_TEXT 1 +#define Z_ASCII Z_TEXT /* for compatibility with 1.2.2 and earlier */ +#define Z_UNKNOWN 2 +/* Possible values of the data_type field (though see inflate()) */ + +#define Z_DEFLATED 8 +/* The deflate compression method (the only one supported in this version) */ + +#define Z_NULL 0 /* for initializing zalloc, zfree, opaque */ + +#define zlib_version zlibVersion() +/* for compatibility with versions < 1.0.2 */ + + /* basic functions */ + +ZEXTERN const char * ZEXPORT zlibVersion OF((void)); +/* The application can compare zlibVersion and ZLIB_VERSION for consistency. + If the first character differs, the library code actually used is + not compatible with the zlib.h header file used by the application. + This check is automatically made by deflateInit and inflateInit. + */ + +/* +ZEXTERN int ZEXPORT deflateInit OF((z_streamp strm, int level)); + + Initializes the internal stream state for compression. The fields + zalloc, zfree and opaque must be initialized before by the caller. + If zalloc and zfree are set to Z_NULL, deflateInit updates them to + use default allocation functions. + + The compression level must be Z_DEFAULT_COMPRESSION, or between 0 and 9: + 1 gives best speed, 9 gives best compression, 0 gives no compression at + all (the input data is simply copied a block at a time). + Z_DEFAULT_COMPRESSION requests a default compromise between speed and + compression (currently equivalent to level 6). + + deflateInit returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if level is not a valid compression level, + Z_VERSION_ERROR if the zlib library version (zlib_version) is incompatible + with the version assumed by the caller (ZLIB_VERSION). + msg is set to null if there is no error message. deflateInit does not + perform any compression: this will be done by deflate(). +*/ + + +ZEXTERN int ZEXPORT deflate OF((z_streamp strm, int flush)); +/* + deflate compresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce some + output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. deflate performs one or both of the + following actions: + + - Compress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in and avail_in are updated and + processing will resume at this point for the next call of deflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. This action is forced if the parameter flush is non zero. + Forcing flush frequently degrades the compression ratio, so this parameter + should be set only when necessary (in interactive applications). + Some output may be provided even if flush is not set. + + Before the call of deflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating avail_in or avail_out accordingly; avail_out + should never be zero before the call. The application can consume the + compressed output when it wants, for example when the output buffer is full + (avail_out == 0), or after each call of deflate(). If deflate returns Z_OK + and with zero avail_out, it must be called again after making room in the + output buffer because there might be more output pending. + + Normally the parameter flush is set to Z_NO_FLUSH, which allows deflate to + decide how much data to accumualte before producing output, in order to + maximize compression. + + If the parameter flush is set to Z_SYNC_FLUSH, all pending output is + flushed to the output buffer and the output is aligned on a byte boundary, so + that the decompressor can get all input data available so far. (In particular + avail_in is zero after the call if enough output space has been provided + before the call.) Flushing may degrade compression for some compression + algorithms and so it should be used only when necessary. + + If flush is set to Z_FULL_FLUSH, all output is flushed as with + Z_SYNC_FLUSH, and the compression state is reset so that decompression can + restart from this point if previous compressed data has been damaged or if + random access is desired. Using Z_FULL_FLUSH too often can seriously degrade + compression. + + If deflate returns with avail_out == 0, this function must be called again + with the same value of the flush parameter and more output space (updated + avail_out), until the flush is complete (deflate returns with non-zero + avail_out). In the case of a Z_FULL_FLUSH or Z_SYNC_FLUSH, make sure that + avail_out is greater than six to avoid repeated flush markers due to + avail_out == 0 on return. + + If the parameter flush is set to Z_FINISH, pending input is processed, + pending output is flushed and deflate returns with Z_STREAM_END if there + was enough output space; if deflate returns with Z_OK, this function must be + called again with Z_FINISH and more output space (updated avail_out) but no + more input data, until it returns with Z_STREAM_END or an error. After + deflate has returned Z_STREAM_END, the only possible operations on the + stream are deflateReset or deflateEnd. + + Z_FINISH can be used immediately after deflateInit if all the compression + is to be done in a single step. In this case, avail_out must be at least + the value returned by deflateBound (see below). If deflate does not return + Z_STREAM_END, then it must be called again as described above. + + deflate() sets strm->adler to the adler32 checksum of all input read + so far (that is, total_in bytes). + + deflate() may update strm->data_type if it can make a good guess about + the input data type (Z_BINARY or Z_TEXT). In doubt, the data is considered + binary. This field is only for information purposes and does not affect + the compression algorithm in any manner. + + deflate() returns Z_OK if some progress has been made (more input + processed or more output produced), Z_STREAM_END if all input has been + consumed and all output has been produced (only when flush is set to + Z_FINISH), Z_STREAM_ERROR if the stream state was inconsistent (for example + if next_in or next_out was NULL), Z_BUF_ERROR if no progress is possible + (for example avail_in or avail_out was zero). Note that Z_BUF_ERROR is not + fatal, and deflate() can be called again with more input and more output + space to continue compressing. +*/ + + +ZEXTERN int ZEXPORT deflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + deflateEnd returns Z_OK if success, Z_STREAM_ERROR if the + stream state was inconsistent, Z_DATA_ERROR if the stream was freed + prematurely (some input or output was discarded). In the error case, + msg may be set but then points to a static string (which must not be + deallocated). +*/ + + +/* +ZEXTERN int ZEXPORT inflateInit OF((z_streamp strm)); + + Initializes the internal stream state for decompression. The fields + next_in, avail_in, zalloc, zfree and opaque must be initialized before by + the caller. If next_in is not Z_NULL and avail_in is large enough (the exact + value depends on the compression method), inflateInit determines the + compression method from the zlib header and allocates all data structures + accordingly; otherwise the allocation will be deferred to the first call of + inflate. If zalloc and zfree are set to Z_NULL, inflateInit updates them to + use default allocation functions. + + inflateInit returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_VERSION_ERROR if the zlib library version is incompatible with the + version assumed by the caller. msg is set to null if there is no error + message. inflateInit does not perform any decompression apart from reading + the zlib header if present: this will be done by inflate(). (So next_in and + avail_in may be modified, but next_out and avail_out are unchanged.) +*/ + + +ZEXTERN int ZEXPORT inflate OF((z_streamp strm, int flush)); +/* + inflate decompresses as much data as possible, and stops when the input + buffer becomes empty or the output buffer becomes full. It may introduce + some output latency (reading input without producing any output) except when + forced to flush. + + The detailed semantics are as follows. inflate performs one or both of the + following actions: + + - Decompress more input starting at next_in and update next_in and avail_in + accordingly. If not all input can be processed (because there is not + enough room in the output buffer), next_in is updated and processing + will resume at this point for the next call of inflate(). + + - Provide more output starting at next_out and update next_out and avail_out + accordingly. inflate() provides as much output as possible, until there + is no more input data or no more space in the output buffer (see below + about the flush parameter). + + Before the call of inflate(), the application should ensure that at least + one of the actions is possible, by providing more input and/or consuming + more output, and updating the next_* and avail_* values accordingly. + The application can consume the uncompressed output when it wants, for + example when the output buffer is full (avail_out == 0), or after each + call of inflate(). If inflate returns Z_OK and with zero avail_out, it + must be called again after making room in the output buffer because there + might be more output pending. + + The flush parameter of inflate() can be Z_NO_FLUSH, Z_SYNC_FLUSH, + Z_FINISH, or Z_BLOCK. Z_SYNC_FLUSH requests that inflate() flush as much + output as possible to the output buffer. Z_BLOCK requests that inflate() stop + if and when it gets to the next deflate block boundary. When decoding the + zlib or gzip format, this will cause inflate() to return immediately after + the header and before the first block. When doing a raw inflate, inflate() + will go ahead and process the first block, and will return when it gets to + the end of that block, or when it runs out of data. + + The Z_BLOCK option assists in appending to or combining deflate streams. + Also to assist in this, on return inflate() will set strm->data_type to the + number of unused bits in the last byte taken from strm->next_in, plus 64 + if inflate() is currently decoding the last block in the deflate stream, + plus 128 if inflate() returned immediately after decoding an end-of-block + code or decoding the complete header up to just before the first byte of the + deflate stream. The end-of-block will not be indicated until all of the + uncompressed data from that block has been written to strm->next_out. The + number of unused bits may in general be greater than seven, except when + bit 7 of data_type is set, in which case the number of unused bits will be + less than eight. + + inflate() should normally be called until it returns Z_STREAM_END or an + error. However if all decompression is to be performed in a single step + (a single call of inflate), the parameter flush should be set to + Z_FINISH. In this case all pending input is processed and all pending + output is flushed; avail_out must be large enough to hold all the + uncompressed data. (The size of the uncompressed data may have been saved + by the compressor for this purpose.) The next operation on this stream must + be inflateEnd to deallocate the decompression state. The use of Z_FINISH + is never required, but can be used to inform inflate that a faster approach + may be used for the single inflate() call. + + In this implementation, inflate() always flushes as much output as + possible to the output buffer, and always uses the faster approach on the + first call. So the only effect of the flush parameter in this implementation + is on the return value of inflate(), as noted below, or when it returns early + because Z_BLOCK is used. + + If a preset dictionary is needed after this call (see inflateSetDictionary + below), inflate sets strm->adler to the adler32 checksum of the dictionary + chosen by the compressor and returns Z_NEED_DICT; otherwise it sets + strm->adler to the adler32 checksum of all output produced so far (that is, + total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described + below. At the end of the stream, inflate() checks that its computed adler32 + checksum is equal to that saved by the compressor and returns Z_STREAM_END + only if the checksum is correct. + + inflate() will decompress and check either zlib-wrapped or gzip-wrapped + deflate data. The header type is detected automatically. Any information + contained in the gzip header is not retained, so applications that need that + information should instead use raw inflate, see inflateInit2() below, or + inflateBack() and perform their own processing of the gzip header and + trailer. + + inflate() returns Z_OK if some progress has been made (more input processed + or more output produced), Z_STREAM_END if the end of the compressed data has + been reached and all uncompressed output has been produced, Z_NEED_DICT if a + preset dictionary is needed at this point, Z_DATA_ERROR if the input data was + corrupted (input stream not conforming to the zlib format or incorrect check + value), Z_STREAM_ERROR if the stream structure was inconsistent (for example + if next_in or next_out was NULL), Z_MEM_ERROR if there was not enough memory, + Z_BUF_ERROR if no progress is possible or if there was not enough room in the + output buffer when Z_FINISH is used. Note that Z_BUF_ERROR is not fatal, and + inflate() can be called again with more input and more output space to + continue decompressing. If Z_DATA_ERROR is returned, the application may then + call inflateSync() to look for a good compression block if a partial recovery + of the data is desired. +*/ + + +ZEXTERN int ZEXPORT inflateEnd OF((z_streamp strm)); +/* + All dynamically allocated data structures for this stream are freed. + This function discards any unprocessed input and does not flush any + pending output. + + inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state + was inconsistent. In the error case, msg may be set but then points to a + static string (which must not be deallocated). +*/ + + /* Advanced functions */ + +/* + The following functions are needed only in some special applications. +*/ + +/* +ZEXTERN int ZEXPORT deflateInit2 OF((z_streamp strm, + int level, + int method, + int windowBits, + int memLevel, + int strategy)); + + This is another version of deflateInit with more compression options. The + fields next_in, zalloc, zfree and opaque must be initialized before by + the caller. + + The method parameter is the compression method. It must be Z_DEFLATED in + this version of the library. + + The windowBits parameter is the base two logarithm of the window size + (the size of the history buffer). It should be in the range 8..15 for this + version of the library. Larger values of this parameter result in better + compression at the expense of memory usage. The default value is 15 if + deflateInit is used instead. + + windowBits can also be -8..-15 for raw deflate. In this case, -windowBits + determines the window size. deflate() will then generate raw deflate data + with no zlib header or trailer, and will not compute an adler32 check value. + + windowBits can also be greater than 15 for optional gzip encoding. Add + 16 to windowBits to write a simple gzip header and trailer around the + compressed data instead of a zlib wrapper. The gzip header will have no + file name, no extra data, no comment, no modification time (set to zero), + no header crc, and the operating system will be set to 255 (unknown). If a + gzip stream is being written, strm->adler is a crc32 instead of an adler32. + + The memLevel parameter specifies how much memory should be allocated + for the internal compression state. memLevel=1 uses minimum memory but + is slow and reduces compression ratio; memLevel=9 uses maximum memory + for optimal speed. The default value is 8. See zconf.h for total memory + usage as a function of windowBits and memLevel. + + The strategy parameter is used to tune the compression algorithm. Use the + value Z_DEFAULT_STRATEGY for normal data, Z_FILTERED for data produced by a + filter (or predictor), Z_HUFFMAN_ONLY to force Huffman encoding only (no + string match), or Z_RLE to limit match distances to one (run-length + encoding). Filtered data consists mostly of small values with a somewhat + random distribution. In this case, the compression algorithm is tuned to + compress them better. The effect of Z_FILTERED is to force more Huffman + coding and less string matching; it is somewhat intermediate between + Z_DEFAULT and Z_HUFFMAN_ONLY. Z_RLE is designed to be almost as fast as + Z_HUFFMAN_ONLY, but give better compression for PNG image data. The strategy + parameter only affects the compression ratio but not the correctness of the + compressed output even if it is not set appropriately. Z_FIXED prevents the + use of dynamic Huffman codes, allowing for a simpler decoder for special + applications. + + deflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as an invalid + method). msg is set to null if there is no error message. deflateInit2 does + not perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the compression dictionary from the given byte sequence + without producing any compressed output. This function must be called + immediately after deflateInit, deflateInit2 or deflateReset, before any + call of deflate. The compressor and decompressor must use exactly the same + dictionary (see inflateSetDictionary). + + The dictionary should consist of strings (byte sequences) that are likely + to be encountered later in the data to be compressed, with the most commonly + used strings preferably put towards the end of the dictionary. Using a + dictionary is most useful when the data to be compressed is short and can be + predicted with good accuracy; the data can then be compressed better than + with the default empty dictionary. + + Depending on the size of the compression data structures selected by + deflateInit or deflateInit2, a part of the dictionary may in effect be + discarded, for example if the dictionary is larger than the window size in + deflate or deflate2. Thus the strings most likely to be useful should be + put at the end of the dictionary, not at the front. In addition, the + current implementation of deflate will use at most the window size minus + 262 bytes of the provided dictionary. + + Upon return of this function, strm->adler is set to the adler32 value + of the dictionary; the decompressor may later use this value to determine + which dictionary has been used by the compressor. (The adler32 value + applies to the whole dictionary even if only a subset of the dictionary is + actually used by the compressor.) If a raw deflate was requested, then the + adler32 value is not computed and strm->adler is not set. + + deflateSetDictionary returns Z_OK if success, or Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent (for example if deflate has already been called for this stream + or if the compression method is bsort). deflateSetDictionary does not + perform any compression: this will be done by deflate(). +*/ + +ZEXTERN int ZEXPORT deflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when several compression strategies will be + tried, for example when there are several ways of pre-processing the input + data with a filter. The streams that will be discarded should then be freed + by calling deflateEnd. Note that deflateCopy duplicates the internal + compression state which can be quite large, so this strategy is slow and + can consume lots of memory. + + deflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT deflateReset OF((z_streamp strm)); +/* + This function is equivalent to deflateEnd followed by deflateInit, + but does not free and reallocate all the internal compression state. + The stream will keep the same compression level and any other attributes + that may have been set by deflateInit2. + + deflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +ZEXTERN int ZEXPORT deflateParams OF((z_streamp strm, + int level, + int strategy)); +/* + Dynamically update the compression level and compression strategy. The + interpretation of level and strategy is as in deflateInit2. This can be + used to switch between compression and straight copy of the input data, or + to switch to a different kind of input data requiring a different + strategy. If the compression level is changed, the input available so far + is compressed with the old level (and may be flushed); the new level will + take effect only at the next call of deflate(). + + Before the call of deflateParams, the stream state must be set as for + a call of deflate(), since the currently available input may have to + be compressed and flushed. In particular, strm->avail_out must be non-zero. + + deflateParams returns Z_OK if success, Z_STREAM_ERROR if the source + stream state was inconsistent or if a parameter was invalid, Z_BUF_ERROR + if strm->avail_out was zero. +*/ + +ZEXTERN int ZEXPORT deflateTune OF((z_streamp strm, + int good_length, + int max_lazy, + int nice_length, + int max_chain)); +/* + Fine tune deflate's internal compression parameters. This should only be + used by someone who understands the algorithm used by zlib's deflate for + searching for the best matching string, and even then only by the most + fanatic optimizer trying to squeeze out the last compressed bit for their + specific input data. Read the deflate.c source code for the meaning of the + max_lazy, good_length, nice_length, and max_chain parameters. + + deflateTune() can be called after deflateInit() or deflateInit2(), and + returns Z_OK on success, or Z_STREAM_ERROR for an invalid deflate stream. + */ + +ZEXTERN uLong ZEXPORT deflateBound OF((z_streamp strm, + uLong sourceLen)); +/* + deflateBound() returns an upper bound on the compressed size after + deflation of sourceLen bytes. It must be called after deflateInit() + or deflateInit2(). This would be used to allocate an output buffer + for deflation in a single pass, and so would be called before deflate(). +*/ + +ZEXTERN int ZEXPORT deflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + deflatePrime() inserts bits in the deflate output stream. The intent + is that this function is used to start off the deflate output with the + bits leftover from a previous deflate stream when appending to it. As such, + this function can only be used for raw deflate, and must be used before the + first deflate() call after a deflateInit2() or deflateReset(). bits must be + less than or equal to 16, and that many of the least significant bits of + value will be inserted in the output. + + deflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT deflateSetHeader OF((z_streamp strm, + gz_headerp head)); +/* + deflateSetHeader() provides gzip header information for when a gzip + stream is requested by deflateInit2(). deflateSetHeader() may be called + after deflateInit2() or deflateReset() and before the first call of + deflate(). The text, time, os, extra field, name, and comment information + in the provided gz_header structure are written to the gzip header (xflag is + ignored -- the extra flags are set according to the compression level). The + caller must assure that, if not Z_NULL, name and comment are terminated with + a zero byte, and that if extra is not Z_NULL, that extra_len bytes are + available there. If hcrc is true, a gzip header crc is included. Note that + the current versions of the command-line version of gzip (up through version + 1.3.x) do not support header crc's, and will report that it is a "multi-part + gzip file" and give up. + + If deflateSetHeader is not used, the default gzip header has text false, + the time set to zero, and os set to 255, with no extra, name, or comment + fields. The gzip header is returned to the default state by deflateReset(). + + deflateSetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateInit2 OF((z_streamp strm, + int windowBits)); + + This is another version of inflateInit with an extra parameter. The + fields next_in, avail_in, zalloc, zfree and opaque must be initialized + before by the caller. + + The windowBits parameter is the base two logarithm of the maximum window + size (the size of the history buffer). It should be in the range 8..15 for + this version of the library. The default value is 15 if inflateInit is used + instead. windowBits must be greater than or equal to the windowBits value + provided to deflateInit2() while compressing, or it must be equal to 15 if + deflateInit2() was not used. If a compressed stream with a larger window + size is given as input, inflate() will return with the error code + Z_DATA_ERROR instead of trying to allocate a larger window. + + windowBits can also be -8..-15 for raw inflate. In this case, -windowBits + determines the window size. inflate() will then process raw deflate data, + not looking for a zlib or gzip header, not generating a check value, and not + looking for any check values for comparison at the end of the stream. This + is for use with other formats that use the deflate compressed data format + such as zip. Those formats provide their own check values. If a custom + format is developed using the raw deflate format for compressed data, it is + recommended that a check value such as an adler32 or a crc32 be applied to + the uncompressed data as is done in the zlib, gzip, and zip formats. For + most applications, the zlib format should be used as is. Note that comments + above on the use in deflateInit2() applies to the magnitude of windowBits. + + windowBits can also be greater than 15 for optional gzip decoding. Add + 32 to windowBits to enable zlib and gzip decoding with automatic header + detection, or add 16 to decode only the gzip format (the zlib format will + return a Z_DATA_ERROR). If a gzip stream is being decoded, strm->adler is + a crc32 instead of an adler32. + + inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_STREAM_ERROR if a parameter is invalid (such as a null strm). msg + is set to null if there is no error message. inflateInit2 does not perform + any decompression apart from reading the zlib header if present: this will + be done by inflate(). (So next_in and avail_in may be modified, but next_out + and avail_out are unchanged.) +*/ + +ZEXTERN int ZEXPORT inflateSetDictionary OF((z_streamp strm, + const Bytef *dictionary, + uInt dictLength)); +/* + Initializes the decompression dictionary from the given uncompressed byte + sequence. This function must be called immediately after a call of inflate, + if that call returned Z_NEED_DICT. The dictionary chosen by the compressor + can be determined from the adler32 value returned by that call of inflate. + The compressor and decompressor must use exactly the same dictionary (see + deflateSetDictionary). For raw inflate, this function can be called + immediately after inflateInit2() or inflateReset() and before any call of + inflate() to set the dictionary. The application must insure that the + dictionary that was used for compression is provided. + + inflateSetDictionary returns Z_OK if success, Z_STREAM_ERROR if a + parameter is invalid (such as NULL dictionary) or the stream state is + inconsistent, Z_DATA_ERROR if the given dictionary doesn't match the + expected one (incorrect adler32 value). inflateSetDictionary does not + perform any decompression: this will be done by subsequent calls of + inflate(). +*/ + +ZEXTERN int ZEXPORT inflateSync OF((z_streamp strm)); +/* + Skips invalid compressed data until a full flush point (see above the + description of deflate with Z_FULL_FLUSH) can be found, or until all + available input is skipped. No output is provided. + + inflateSync returns Z_OK if a full flush point has been found, Z_BUF_ERROR + if no more input was provided, Z_DATA_ERROR if no flush point has been found, + or Z_STREAM_ERROR if the stream structure was inconsistent. In the success + case, the application may save the current current value of total_in which + indicates where valid compressed data was found. In the error case, the + application may repeatedly call inflateSync, providing more input each time, + until success or end of the input data. +*/ + +ZEXTERN int ZEXPORT inflateCopy OF((z_streamp dest, + z_streamp source)); +/* + Sets the destination stream as a complete copy of the source stream. + + This function can be useful when randomly accessing a large stream. The + first pass through the stream can periodically record the inflate state, + allowing restarting inflate at those points when randomly accessing the + stream. + + inflateCopy returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_STREAM_ERROR if the source stream state was inconsistent + (such as zalloc being NULL). msg is left unchanged in both source and + destination. +*/ + +ZEXTERN int ZEXPORT inflateReset OF((z_streamp strm)); +/* + This function is equivalent to inflateEnd followed by inflateInit, + but does not free and reallocate all the internal decompression state. + The stream will keep attributes that may have been set by inflateInit2. + + inflateReset returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent (such as zalloc or state being NULL). +*/ + +ZEXTERN int ZEXPORT inflatePrime OF((z_streamp strm, + int bits, + int value)); +/* + This function inserts bits in the inflate input stream. The intent is + that this function is used to start inflating at a bit position in the + middle of a byte. The provided bits will be used before any bytes are used + from next_in. This function should only be used with raw inflate, and + should be used before the first inflate() call after inflateInit2() or + inflateReset(). bits must be less than or equal to 16, and that many of the + least significant bits of value will be inserted in the input. + + inflatePrime returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +ZEXTERN int ZEXPORT inflateGetHeader OF((z_streamp strm, + gz_headerp head)); +/* + inflateGetHeader() requests that gzip header information be stored in the + provided gz_header structure. inflateGetHeader() may be called after + inflateInit2() or inflateReset(), and before the first call of inflate(). + As inflate() processes the gzip stream, head->done is zero until the header + is completed, at which time head->done is set to one. If a zlib stream is + being decoded, then head->done is set to -1 to indicate that there will be + no gzip header information forthcoming. Note that Z_BLOCK can be used to + force inflate() to return immediately after header processing is complete + and before any actual data is decompressed. + + The text, time, xflags, and os fields are filled in with the gzip header + contents. hcrc is set to true if there is a header CRC. (The header CRC + was valid if done is set to one.) If extra is not Z_NULL, then extra_max + contains the maximum number of bytes to write to extra. Once done is true, + extra_len contains the actual extra field length, and extra contains the + extra field, or that field truncated if extra_max is less than extra_len. + If name is not Z_NULL, then up to name_max characters are written there, + terminated with a zero unless the length is greater than name_max. If + comment is not Z_NULL, then up to comm_max characters are written there, + terminated with a zero unless the length is greater than comm_max. When + any of extra, name, or comment are not Z_NULL and the respective field is + not present in the header, then that field is set to Z_NULL to signal its + absence. This allows the use of deflateSetHeader() with the returned + structure to duplicate the header. However if those fields are set to + allocated memory, then the application will need to save those pointers + elsewhere so that they can be eventually freed. + + If inflateGetHeader is not used, then the header information is simply + discarded. The header is always checked for validity, including the header + CRC if present. inflateReset() will reset the process to discard the header + information. The application would need to call inflateGetHeader() again to + retrieve the header from the next gzip stream. + + inflateGetHeader returns Z_OK if success, or Z_STREAM_ERROR if the source + stream state was inconsistent. +*/ + +/* +ZEXTERN int ZEXPORT inflateBackInit OF((z_streamp strm, int windowBits, + unsigned char FAR *window)); + + Initialize the internal stream state for decompression using inflateBack() + calls. The fields zalloc, zfree and opaque in strm must be initialized + before the call. If zalloc and zfree are Z_NULL, then the default library- + derived memory allocation routines are used. windowBits is the base two + logarithm of the window size, in the range 8..15. window is a caller + supplied buffer of that size. Except for special applications where it is + assured that deflate was used with small window sizes, windowBits must be 15 + and a 32K byte window must be supplied to be able to decompress general + deflate streams. + + See inflateBack() for the usage of these routines. + + inflateBackInit will return Z_OK on success, Z_STREAM_ERROR if any of + the paramaters are invalid, Z_MEM_ERROR if the internal state could not + be allocated, or Z_VERSION_ERROR if the version of the library does not + match the version of the header file. +*/ + +typedef unsigned (*in_func) OF((void FAR *, unsigned char FAR * FAR *)); +typedef int (*out_func) OF((void FAR *, unsigned char FAR *, unsigned)); + +ZEXTERN int ZEXPORT inflateBack OF((z_streamp strm, + in_func in, void FAR *in_desc, + out_func out, void FAR *out_desc)); +/* + inflateBack() does a raw inflate with a single call using a call-back + interface for input and output. This is more efficient than inflate() for + file i/o applications in that it avoids copying between the output and the + sliding window by simply making the window itself the output buffer. This + function trusts the application to not change the output buffer passed by + the output function, at least until inflateBack() returns. + + inflateBackInit() must be called first to allocate the internal state + and to initialize the state with the user-provided window buffer. + inflateBack() may then be used multiple times to inflate a complete, raw + deflate stream with each call. inflateBackEnd() is then called to free + the allocated state. + + A raw deflate stream is one with no zlib or gzip header or trailer. + This routine would normally be used in a utility that reads zip or gzip + files and writes out uncompressed files. The utility would decode the + header and process the trailer on its own, hence this routine expects + only the raw deflate stream to decompress. This is different from the + normal behavior of inflate(), which expects either a zlib or gzip header and + trailer around the deflate stream. + + inflateBack() uses two subroutines supplied by the caller that are then + called by inflateBack() for input and output. inflateBack() calls those + routines until it reads a complete deflate stream and writes out all of the + uncompressed data, or until it encounters an error. The function's + parameters and return types are defined above in the in_func and out_func + typedefs. inflateBack() will call in(in_desc, &buf) which should return the + number of bytes of provided input, and a pointer to that input in buf. If + there is no input available, in() must return zero--buf is ignored in that + case--and inflateBack() will return a buffer error. inflateBack() will call + out(out_desc, buf, len) to write the uncompressed data buf[0..len-1]. out() + should return zero on success, or non-zero on failure. If out() returns + non-zero, inflateBack() will return with an error. Neither in() nor out() + are permitted to change the contents of the window provided to + inflateBackInit(), which is also the buffer that out() uses to write from. + The length written by out() will be at most the window size. Any non-zero + amount of input may be provided by in(). + + For convenience, inflateBack() can be provided input on the first call by + setting strm->next_in and strm->avail_in. If that input is exhausted, then + in() will be called. Therefore strm->next_in must be initialized before + calling inflateBack(). If strm->next_in is Z_NULL, then in() will be called + immediately for input. If strm->next_in is not Z_NULL, then strm->avail_in + must also be initialized, and then if strm->avail_in is not zero, input will + initially be taken from strm->next_in[0 .. strm->avail_in - 1]. + + The in_desc and out_desc parameters of inflateBack() is passed as the + first parameter of in() and out() respectively when they are called. These + descriptors can be optionally used to pass any information that the caller- + supplied in() and out() functions need to do their job. + + On return, inflateBack() will set strm->next_in and strm->avail_in to + pass back any unused input that was provided by the last in() call. The + return values of inflateBack() can be Z_STREAM_END on success, Z_BUF_ERROR + if in() or out() returned an error, Z_DATA_ERROR if there was a format + error in the deflate stream (in which case strm->msg is set to indicate the + nature of the error), or Z_STREAM_ERROR if the stream was not properly + initialized. In the case of Z_BUF_ERROR, an input or output error can be + distinguished using strm->next_in which will be Z_NULL only if in() returned + an error. If strm->next is not Z_NULL, then the Z_BUF_ERROR was due to + out() returning non-zero. (in() will always be called before out(), so + strm->next_in is assured to be defined if out() returns non-zero.) Note + that inflateBack() cannot return Z_OK. +*/ + +ZEXTERN int ZEXPORT inflateBackEnd OF((z_streamp strm)); +/* + All memory allocated by inflateBackInit() is freed. + + inflateBackEnd() returns Z_OK on success, or Z_STREAM_ERROR if the stream + state was inconsistent. +*/ + +ZEXTERN uLong ZEXPORT zlibCompileFlags OF((void)); +/* Return flags indicating compile-time options. + + Type sizes, two bits each, 00 = 16 bits, 01 = 32, 10 = 64, 11 = other: + 1.0: size of uInt + 3.2: size of uLong + 5.4: size of voidpf (pointer) + 7.6: size of z_off_t + + Compiler, assembler, and debug options: + 8: DEBUG + 9: ASMV or ASMINF -- use ASM code + 10: ZLIB_WINAPI -- exported functions use the WINAPI calling convention + 11: 0 (reserved) + + One-time table building (smaller code, but not thread-safe if true): + 12: BUILDFIXED -- build static block decoding tables when needed + 13: DYNAMIC_CRC_TABLE -- build CRC calculation tables when needed + 14,15: 0 (reserved) + + Library content (indicates missing functionality): + 16: NO_GZCOMPRESS -- gz* functions cannot compress (to avoid linking + deflate code when not needed) + 17: NO_GZIP -- deflate can't write gzip streams, and inflate can't detect + and decode gzip streams (to avoid linking crc code) + 18-19: 0 (reserved) + + Operation variations (changes in library functionality): + 20: PKZIP_BUG_WORKAROUND -- slightly more permissive inflate + 21: FASTEST -- deflate algorithm with only one, lowest compression level + 22,23: 0 (reserved) + + The sprintf variant used by gzprintf (zero is best): + 24: 0 = vs*, 1 = s* -- 1 means limited to 20 arguments after the format + 25: 0 = *nprintf, 1 = *printf -- 1 means gzprintf() not secure! + 26: 0 = returns value, 1 = void -- 1 means inferred string length returned + + Remainder: + 27-31: 0 (reserved) + */ + + + /* utility functions */ + +/* + The following utility functions are implemented on top of the + basic stream-oriented functions. To simplify the interface, some + default options are assumed (compression level and memory usage, + standard memory allocation functions). The source code of these + utility functions can easily be modified if you need special options. +*/ + +ZEXTERN int ZEXPORT compress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Compresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be at least the value returned + by compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed buffer. + This function can be used to compress a whole file at once if the + input file is mmap'ed. + compress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer. +*/ + +ZEXTERN int ZEXPORT compress2 OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen, + int level)); +/* + Compresses the source buffer into the destination buffer. The level + parameter has the same meaning as in deflateInit. sourceLen is the byte + length of the source buffer. Upon entry, destLen is the total size of the + destination buffer, which must be at least the value returned by + compressBound(sourceLen). Upon exit, destLen is the actual size of the + compressed buffer. + + compress2 returns Z_OK if success, Z_MEM_ERROR if there was not enough + memory, Z_BUF_ERROR if there was not enough room in the output buffer, + Z_STREAM_ERROR if the level parameter is invalid. +*/ + +ZEXTERN uLong ZEXPORT compressBound OF((uLong sourceLen)); +/* + compressBound() returns an upper bound on the compressed size after + compress() or compress2() on sourceLen bytes. It would be used before + a compress() or compress2() call to allocate the destination buffer. +*/ + +ZEXTERN int ZEXPORT uncompress OF((Bytef *dest, uLongf *destLen, + const Bytef *source, uLong sourceLen)); +/* + Decompresses the source buffer into the destination buffer. sourceLen is + the byte length of the source buffer. Upon entry, destLen is the total + size of the destination buffer, which must be large enough to hold the + entire uncompressed data. (The size of the uncompressed data must have + been saved previously by the compressor and transmitted to the decompressor + by some mechanism outside the scope of this compression library.) + Upon exit, destLen is the actual size of the compressed buffer. + This function can be used to decompress a whole file at once if the + input file is mmap'ed. + + uncompress returns Z_OK if success, Z_MEM_ERROR if there was not + enough memory, Z_BUF_ERROR if there was not enough room in the output + buffer, or Z_DATA_ERROR if the input data was corrupted or incomplete. +*/ + + +typedef voidp gzFile; + +ZEXTERN gzFile ZEXPORT gzopen OF((const char *path, const char *mode)); +/* + Opens a gzip (.gz) file for reading or writing. The mode parameter + is as in fopen ("rb" or "wb") but can also include a compression level + ("wb9") or a strategy: 'f' for filtered data as in "wb6f", 'h' for + Huffman only compression as in "wb1h", or 'R' for run-length encoding + as in "wb1R". (See the description of deflateInit2 for more information + about the strategy parameter.) + + gzopen can be used to read a file which is not in gzip format; in this + case gzread will directly read from the file without decompression. + + gzopen returns NULL if the file could not be opened or if there was + insufficient memory to allocate the (de)compression state; errno + can be checked to distinguish the two cases (if errno is zero, the + zlib error is Z_MEM_ERROR). */ + +ZEXTERN gzFile ZEXPORT gzdopen OF((int fd, const char *mode)); +/* + gzdopen() associates a gzFile with the file descriptor fd. File + descriptors are obtained from calls like open, dup, creat, pipe or + fileno (in the file has been previously opened with fopen). + The mode parameter is as in gzopen. + The next call of gzclose on the returned gzFile will also close the + file descriptor fd, just like fclose(fdopen(fd), mode) closes the file + descriptor fd. If you want to keep fd open, use gzdopen(dup(fd), mode). + gzdopen returns NULL if there was insufficient memory to allocate + the (de)compression state. +*/ + +ZEXTERN int ZEXPORT gzsetparams OF((gzFile file, int level, int strategy)); +/* + Dynamically update the compression level or strategy. See the description + of deflateInit2 for the meaning of these parameters. + gzsetparams returns Z_OK if success, or Z_STREAM_ERROR if the file was not + opened for writing. +*/ + +ZEXTERN int ZEXPORT gzread OF((gzFile file, voidp buf, unsigned len)); +/* + Reads the given number of uncompressed bytes from the compressed file. + If the input file was not in gzip format, gzread copies the given number + of bytes into the buffer. + gzread returns the number of uncompressed bytes actually read (0 for + end of file, -1 for error). */ + +ZEXTERN int ZEXPORT gzwrite OF((gzFile file, + voidpc buf, unsigned len)); +/* + Writes the given number of uncompressed bytes into the compressed file. + gzwrite returns the number of uncompressed bytes actually written + (0 in case of error). +*/ + +ZEXTERN int ZEXPORTVA gzprintf OF((gzFile file, const char *format, ...)); +/* + Converts, formats, and writes the args to the compressed file under + control of the format string, as in fprintf. gzprintf returns the number of + uncompressed bytes actually written (0 in case of error). The number of + uncompressed bytes written is limited to 4095. The caller should assure that + this limit is not exceeded. If it is exceeded, then gzprintf() will return + return an error (0) with nothing written. In this case, there may also be a + buffer overflow with unpredictable consequences, which is possible only if + zlib was compiled with the insecure functions sprintf() or vsprintf() + because the secure snprintf() or vsnprintf() functions were not available. +*/ + +ZEXTERN int ZEXPORT gzputs OF((gzFile file, const char *s)); +/* + Writes the given null-terminated string to the compressed file, excluding + the terminating null character. + gzputs returns the number of characters written, or -1 in case of error. +*/ + +ZEXTERN char * ZEXPORT gzgets OF((gzFile file, char *buf, int len)); +/* + Reads bytes from the compressed file until len-1 characters are read, or + a newline character is read and transferred to buf, or an end-of-file + condition is encountered. The string is then terminated with a null + character. + gzgets returns buf, or Z_NULL in case of error. +*/ + +ZEXTERN int ZEXPORT gzputc OF((gzFile file, int c)); +/* + Writes c, converted to an unsigned char, into the compressed file. + gzputc returns the value that was written, or -1 in case of error. +*/ + +ZEXTERN int ZEXPORT gzgetc OF((gzFile file)); +/* + Reads one byte from the compressed file. gzgetc returns this byte + or -1 in case of end of file or error. +*/ + +ZEXTERN int ZEXPORT gzungetc OF((int c, gzFile file)); +/* + Push one character back onto the stream to be read again later. + Only one character of push-back is allowed. gzungetc() returns the + character pushed, or -1 on failure. gzungetc() will fail if a + character has been pushed but not read yet, or if c is -1. The pushed + character will be discarded if the stream is repositioned with gzseek() + or gzrewind(). +*/ + +ZEXTERN int ZEXPORT gzflush OF((gzFile file, int flush)); +/* + Flushes all pending output into the compressed file. The parameter + flush is as in the deflate() function. The return value is the zlib + error number (see function gzerror below). gzflush returns Z_OK if + the flush parameter is Z_FINISH and all output could be flushed. + gzflush should be called only when strictly necessary because it can + degrade compression. +*/ + +ZEXTERN z_off_t ZEXPORT gzseek OF((gzFile file, + z_off_t offset, int whence)); +/* + Sets the starting position for the next gzread or gzwrite on the + given compressed file. The offset represents a number of bytes in the + uncompressed data stream. The whence parameter is defined as in lseek(2); + the value SEEK_END is not supported. + If the file is opened for reading, this function is emulated but can be + extremely slow. If the file is opened for writing, only forward seeks are + supported; gzseek then compresses a sequence of zeroes up to the new + starting position. + + gzseek returns the resulting offset location as measured in bytes from + the beginning of the uncompressed stream, or -1 in case of error, in + particular if the file is opened for writing and the new starting position + would be before the current position. +*/ + +ZEXTERN int ZEXPORT gzrewind OF((gzFile file)); +/* + Rewinds the given file. This function is supported only for reading. + + gzrewind(file) is equivalent to (int)gzseek(file, 0L, SEEK_SET) +*/ + +ZEXTERN z_off_t ZEXPORT gztell OF((gzFile file)); +/* + Returns the starting position for the next gzread or gzwrite on the + given compressed file. This position represents a number of bytes in the + uncompressed data stream. + + gztell(file) is equivalent to gzseek(file, 0L, SEEK_CUR) +*/ + +ZEXTERN int ZEXPORT gzeof OF((gzFile file)); +/* + Returns 1 when EOF has previously been detected reading the given + input stream, otherwise zero. +*/ + +ZEXTERN int ZEXPORT gzdirect OF((gzFile file)); +/* + Returns 1 if file is being read directly without decompression, otherwise + zero. +*/ + +ZEXTERN int ZEXPORT gzclose OF((gzFile file)); +/* + Flushes all pending output if necessary, closes the compressed file + and deallocates all the (de)compression state. The return value is the zlib + error number (see function gzerror below). +*/ + +ZEXTERN const char * ZEXPORT gzerror OF((gzFile file, int *errnum)); +/* + Returns the error message for the last error which occurred on the + given compressed file. errnum is set to zlib error number. If an + error occurred in the file system and not in the compression library, + errnum is set to Z_ERRNO and the application may consult errno + to get the exact error code. +*/ + +ZEXTERN void ZEXPORT gzclearerr OF((gzFile file)); +/* + Clears the error and end-of-file flags for file. This is analogous to the + clearerr() function in stdio. This is useful for continuing to read a gzip + file that is being written concurrently. +*/ + + /* checksum functions */ + +/* + These functions are not related to compression but are exported + anyway because they might be useful in applications using the + compression library. +*/ + +ZEXTERN uLong ZEXPORT adler32 OF((uLong adler, const Bytef *buf, uInt len)); +/* + Update a running Adler-32 checksum with the bytes buf[0..len-1] and + return the updated checksum. If buf is NULL, this function returns + the required initial value for the checksum. + An Adler-32 checksum is almost as reliable as a CRC32 but can be computed + much faster. Usage example: + + uLong adler = adler32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + adler = adler32(adler, buffer, length); + } + if (adler != original_adler) error(); +*/ + +ZEXTERN uLong ZEXPORT adler32_combine OF((uLong adler1, uLong adler2, + z_off_t len2)); +/* + Combine two Adler-32 checksums into one. For two sequences of bytes, seq1 + and seq2 with lengths len1 and len2, Adler-32 checksums were calculated for + each, adler1 and adler2. adler32_combine() returns the Adler-32 checksum of + seq1 and seq2 concatenated, requiring only adler1, adler2, and len2. +*/ + +ZEXTERN uLong ZEXPORT crc32 OF((uLong crc, const Bytef *buf, uInt len)); +/* + Update a running CRC-32 with the bytes buf[0..len-1] and return the + updated CRC-32. If buf is NULL, this function returns the required initial + value for the for the crc. Pre- and post-conditioning (one's complement) is + performed within this function so it shouldn't be done by the application. + Usage example: + + uLong crc = crc32(0L, Z_NULL, 0); + + while (read_buffer(buffer, length) != EOF) { + crc = crc32(crc, buffer, length); + } + if (crc != original_crc) error(); +*/ + +ZEXTERN uLong ZEXPORT crc32_combine OF((uLong crc1, uLong crc2, z_off_t len2)); + +/* + Combine two CRC-32 check values into one. For two sequences of bytes, + seq1 and seq2 with lengths len1 and len2, CRC-32 check values were + calculated for each, crc1 and crc2. crc32_combine() returns the CRC-32 + check value of seq1 and seq2 concatenated, requiring only crc1, crc2, and + len2. +*/ + + + /* various hacks, don't look :) */ + +/* deflateInit and inflateInit are macros to allow checking the zlib version + * and the compiler's view of z_stream: + */ +ZEXTERN int ZEXPORT deflateInit_ OF((z_streamp strm, int level, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateInit_ OF((z_streamp strm, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT deflateInit2_ OF((z_streamp strm, int level, int method, + int windowBits, int memLevel, + int strategy, const char *version, + int stream_size)); +ZEXTERN int ZEXPORT inflateInit2_ OF((z_streamp strm, int windowBits, + const char *version, int stream_size)); +ZEXTERN int ZEXPORT inflateBackInit_ OF((z_streamp strm, int windowBits, + unsigned char FAR *window, + const char *version, + int stream_size)); +#define deflateInit(strm, level) \ + deflateInit_((strm), (level), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit(strm) \ + inflateInit_((strm), ZLIB_VERSION, sizeof(z_stream)) +#define deflateInit2(strm, level, method, windowBits, memLevel, strategy) \ + deflateInit2_((strm),(level),(method),(windowBits),(memLevel),\ + (strategy), ZLIB_VERSION, sizeof(z_stream)) +#define inflateInit2(strm, windowBits) \ + inflateInit2_((strm), (windowBits), ZLIB_VERSION, sizeof(z_stream)) +#define inflateBackInit(strm, windowBits, window) \ + inflateBackInit_((strm), (windowBits), (window), \ + ZLIB_VERSION, sizeof(z_stream)) + + +#if !defined(ZUTIL_H) && !defined(NO_DUMMY_DECL) + struct internal_state {int dummy;}; /* hack for buggy compilers */ +#endif + +ZEXTERN const char * ZEXPORT zError OF((int)); +ZEXTERN int ZEXPORT inflateSyncPoint OF((z_streamp z)); +ZEXTERN const uLongf * ZEXPORT get_crc_table OF((void)); + +#ifdef __cplusplus +} +#endif + +#endif /* ZLIB_H */