source: trunk/src/kernel/qtranslator.cpp@ 154

Last change on this file since 154 was 8, checked in by dmik, 20 years ago

Transferred Qt for OS/2 version 3.3.1-rc5 sources from the CVS

  • Property svn:keywords set to Id
File size: 37.8 KB
Line 
1/****************************************************************************
2** $Id: qtranslator.cpp 8 2005-11-16 19:36:46Z dmik $
3**
4** Localization database support.
5**
6** Created : 980906
7**
8** Copyright (C) 1998-2003 Trolltech AS. All rights reserved.
9**
10** This file is part of the kernel module of the Qt GUI Toolkit.
11**
12** This file may be distributed under the terms of the Q Public License
13** as defined by Trolltech AS of Norway and appearing in the file
14** LICENSE.QPL included in the packaging of this file.
15**
16** This file may be distributed and/or modified under the terms of the
17** GNU General Public License version 2 as published by the Free Software
18** Foundation and appearing in the file LICENSE.GPL included in the
19** packaging of this file.
20**
21** Licensees holding valid Qt Enterprise Edition or Qt Professional Edition
22** licenses may use this file in accordance with the Qt Commercial License
23** Agreement provided with the Software.
24**
25** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
26** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
27**
28** See http://www.trolltech.com/pricing.html or email sales@trolltech.com for
29** information about Qt Commercial License Agreements.
30** See http://www.trolltech.com/qpl/ for QPL licensing information.
31** See http://www.trolltech.com/gpl/ for GPL licensing information.
32**
33** Contact info@trolltech.com if any conditions of this licensing are
34** not clear to you.
35**
36**********************************************************************/
37
38#include "qplatformdefs.h"
39
40// POSIX Large File Support redefines open -> open64
41static inline int qt_open(const char *pathname, int flags, mode_t mode)
42{ return ::open(pathname, flags, mode); }
43#if defined(open)
44# undef open
45#endif
46
47// POSIX Large File Support redefines truncate -> truncate64
48#if defined(truncate)
49# undef truncate
50#endif
51
52#include "qtranslator.h"
53
54#ifndef QT_NO_TRANSLATION
55
56#include "qfileinfo.h"
57#include "qwidgetlist.h"
58#include "qintdict.h"
59#include "qstring.h"
60#include "qapplication.h"
61#include "qfile.h"
62#include "qbuffer.h"
63#include "qdatastream.h"
64#include "qmap.h"
65#include "qtl.h"
66
67#if defined(Q_OS_UNIX)
68#define QT_USE_MMAP
69#endif
70
71// most of the headers below are already included in qplatformdefs.h
72// also this lacks Large File support but that's probably irrelevant
73#if defined(QT_USE_MMAP)
74// for mmap
75#include <sys/types.h>
76#include <sys/stat.h>
77#include <sys/mman.h>
78#include <fcntl.h>
79#include <errno.h>
80// for htonl
81#include <netinet/in.h>
82#endif
83
84#include <stdlib.h>
85
86/*
87$ mcookie
883cb86418caef9c95cd211cbf60a1bddd
89$
90*/
91
92// magic number for the file
93static const int MagicLength = 16;
94static const uchar magic[MagicLength] = {
95 0x3c, 0xb8, 0x64, 0x18, 0xca, 0xef, 0x9c, 0x95,
96 0xcd, 0x21, 0x1c, 0xbf, 0x60, 0xa1, 0xbd, 0xdd
97};
98
99static bool match( const char* found, const char* target )
100{
101 // 0 means anything, "" means empty
102 return found == 0 || qstrcmp( found, target ) == 0;
103}
104
105#if defined(Q_C_CALLBACKS)
106extern "C" {
107#endif
108
109/*
110 Yes, unfortunately, we have code here that depends on endianness.
111 The candidate is big endian (it comes from a .qm file) whereas the
112 target endianness depends on the system Qt is running on.
113*/
114#ifdef Q_OS_TEMP
115static int __cdecl cmp_uint32_little( const void* target, const void* candidate )
116#else
117static int cmp_uint32_little( const void* target, const void* candidate )
118#endif
119{
120 const uchar* t = (const uchar*) target;
121 const uchar* c = (const uchar*) candidate;
122 return t[3] != c[0] ? (int) t[3] - (int) c[0]
123 : t[2] != c[1] ? (int) t[2] - (int) c[1]
124 : t[1] != c[2] ? (int) t[1] - (int) c[2]
125 : (int) t[0] - (int) c[3];
126}
127
128#ifdef Q_OS_TEMP
129static int __cdecl cmp_uint32_big( const void* target, const void* candidate )
130#else
131static int cmp_uint32_big( const void* target, const void* candidate )
132#endif
133{
134 const uchar* t = (const uchar*) target;
135 const uchar* c = (const uchar*) candidate;
136 return t[0] != c[0] ? (int) t[0] - (int) c[0]
137 : t[1] != c[1] ? (int) t[1] - (int) c[1]
138 : t[2] != c[2] ? (int) t[2] - (int) c[2]
139 : (int) t[3] - (int) c[3];
140}
141
142#if defined(Q_C_CALLBACKS)
143}
144#endif
145
146static int systemWordSize = 0;
147static bool systemBigEndian;
148
149static uint elfHash( const char * name )
150{
151 const uchar *k;
152 uint h = 0;
153 uint g;
154
155 if ( name ) {
156 k = (const uchar *) name;
157 while ( *k ) {
158 h = ( h << 4 ) + *k++;
159 if ( (g = (h & 0xf0000000)) != 0 )
160 h ^= g >> 24;
161 h &= ~g;
162 }
163 }
164 if ( !h )
165 h = 1;
166 return h;
167}
168
169extern bool qt_detectRTLLanguage();
170
171class QTranslatorPrivate {
172public:
173 struct Offset {
174 Offset()
175 : h( 0 ), o( 0 ) { }
176 Offset( const QTranslatorMessage& m, int offset )
177 : h( m.hash() ), o( offset ) { }
178
179 bool operator<( const Offset&k ) const {
180 return ( h != k.h ) ? h < k.h : o < k.o;
181 }
182 Q_DUMMY_COMPARISON_OPERATOR(QTranslatorPrivate::Offset)
183 uint h;
184 uint o;
185 };
186
187 enum { Contexts = 0x2f, Hashes = 0x42, Messages = 0x69 };
188
189 QTranslatorPrivate() :
190 unmapPointer( 0 ), unmapLength( 0 ),
191 messageArray( 0 ), offsetArray( 0 ), contextArray( 0 )
192#ifndef QT_NO_TRANSLATION_BUILDER
193 , messages( 0 )
194#endif
195 { }
196 // QTranslator must finalize this before deallocating it
197
198 // for mmap'ed files, this is what needs to be unmapped.
199 char * unmapPointer;
200 unsigned int unmapLength;
201
202 // for squeezed but non-file data, this is what needs to be deleted
203 QByteArray * messageArray;
204 QByteArray * offsetArray;
205 QByteArray * contextArray;
206
207#ifndef QT_NO_TRANSLATION_BUILDER
208 QMap<QTranslatorMessage, void *> * messages;
209#endif
210};
211
212
213/*!
214 \class QTranslator
215
216 \brief The QTranslator class provides internationalization support for text
217 output.
218
219 \ingroup i18n
220 \ingroup environment
221 \mainclass
222
223 An object of this class contains a set of QTranslatorMessage
224 objects, each of which specifies a translation from a source
225 language to a target language. QTranslator provides functions to
226 look up translations, add new ones, remove them, load and save
227 them, etc.
228
229 The most common use of QTranslator is to: load a translator file
230 created with \link linguist-manual.book Qt Linguist\endlink,
231 install it using QApplication::installTranslator(), and use it via
232 QObject::tr(). For example:
233
234 \code
235 int main( int argc, char ** argv )
236 {
237 QApplication app( argc, argv );
238
239 QTranslator translator( 0 );
240 translator.load( "french.qm", "." );
241 app.installTranslator( &translator );
242
243 MyWidget m;
244 app.setMainWidget( &m );
245 m.show();
246
247 return app.exec();
248 }
249 \endcode
250 Note that the translator must be created \e before the
251 application's main window.
252
253 Most applications will never need to do anything else with this
254 class. The other functions provided by this class are useful for
255 applications that work on translator files.
256
257 We call a translation a "messsage". For this reason, translation
258 files are sometimes referred to as "message files".
259
260 It is possible to lookup a translation using findMessage() (as
261 tr() and QApplication::translate() do) and contains(), to insert a
262 new translation messsage using insert(), and to remove one using
263 remove().
264
265 Translation tools often need more information than the bare source
266 text and translation, for example, context information to help
267 the translator. But end-user programs that are using translations
268 usually only need lookup. To cater for these different needs,
269 QTranslator can use stripped translator files that use the minimum
270 of memory and which support little more functionality than
271 findMessage().
272
273 Thus, load() may not load enough information to make anything more
274 than findMessage() work. save() has an argument indicating
275 whether to save just this minimum of information or to save
276 everything.
277
278 "Everything" means that for each translation item the following
279 information is kept:
280
281 \list
282 \i The \e {translated text} - the return value from tr().
283 \i The input key:
284 \list
285 \i The \e {source text} - usually the argument to tr().
286 \i The \e context - usually the class name for the tr() caller.
287 \i The \e comment - a comment that helps disambiguate different uses
288 of the same text in the same context.
289 \endlist
290 \endlist
291
292 The minimum for each item is just the information necessary for
293 findMessage() to return the right text. This may include the
294 source, context and comment, but usually it is just a hash value
295 and the translated text.
296
297 For example, the "Cancel" in a dialog might have "Anuluj" when the
298 program runs in Polish (in this case the source text would be
299 "Cancel"). The context would (normally) be the dialog's class
300 name; there would normally be no comment, and the translated text
301 would be "Anuluj".
302
303 But it's not always so simple. The Spanish version of a printer
304 dialog with settings for two-sided printing and binding would
305 probably require both "Activado" and "Activada" as translations
306 for "Enabled". In this case the source text would be "Enabled" in
307 both cases, and the context would be the dialog's class name, but
308 the two items would have disambiguating comments such as
309 "two-sided printing" for one and "binding" for the other. The
310 comment enables the translator to choose the appropriate gender
311 for the Spanish version, and enables Qt to distinguish between
312 translations.
313
314 Note that when QTranslator loads a stripped file, most functions
315 do not work. The functions that do work with stripped files are
316 explicitly documented as such.
317
318 \sa QTranslatorMessage QApplication::installTranslator()
319 QApplication::removeTranslator() QObject::tr() QApplication::translate()
320*/
321
322/*!
323 \enum QTranslator::SaveMode
324
325 This enum type defines how QTranslator writes translation
326 files. There are two modes:
327
328 \value Everything files are saved with all available information
329 \value Stripped files are saved with just enough information for
330 end-user applications
331
332 Note that when QTranslator loads a stripped file, most functions do
333 not work. The functions that do work with stripped files are
334 explicitly documented as such.
335*/
336
337/*!
338 Constructs an empty message file object that is not connected to
339 any file. The object is called \a name with parent \a parent.
340*/
341
342QTranslator::QTranslator( QObject * parent, const char * name )
343 : QObject( parent, name )
344{
345 d = new QTranslatorPrivate;
346}
347
348
349/*!
350 Destroys the object and frees any allocated resources.
351*/
352
353QTranslator::~QTranslator()
354{
355 if ( qApp )
356 qApp->removeTranslator( this );
357 clear();
358 delete d;
359}
360
361
362extern bool qt_detectRTLLanguage();
363
364/*!
365 Loads \a filename, which may be an absolute file name or relative
366 to \a directory. The previous contents of this translator object
367 is discarded.
368
369 If the full file name does not exist, other file names are tried
370 in the following order:
371
372 \list 1
373 \i File name with \a suffix appended (".qm" if the \a suffix is
374 QString::null).
375 \i File name with text after a character in \a search_delimiters
376 stripped ("_." is the default for \a search_delimiters if it is
377 QString::null).
378 \i File name stripped and \a suffix appended.
379 \i File name stripped further, etc.
380 \endlist
381
382 For example, an application running in the fr_CA locale
383 (French-speaking Canada) might call load("foo.fr_ca",
384 "/opt/foolib"). load() would then try to open the first existing
385 readable file from this list:
386
387 \list 1
388 \i /opt/foolib/foo.fr_ca
389 \i /opt/foolib/foo.fr_ca.qm
390 \i /opt/foolib/foo.fr
391 \i /opt/foolib/foo.fr.qm
392 \i /opt/foolib/foo
393 \i /opt/foolib/foo.qm
394 \endlist
395
396 \sa save()
397*/
398
399bool QTranslator::load( const QString & filename, const QString & directory,
400 const QString & search_delimiters,
401 const QString & suffix )
402{
403 clear();
404
405 QString prefix;
406
407 if ( filename[0] == '/'
408#if defined(Q_WS_WIN) || defined(Q_WS_PM)
409 || (filename[0] && filename[1] == ':') || filename[0] == '\\'
410#endif
411 )
412 prefix = QString::fromLatin1( "" );
413 else
414 prefix = directory;
415
416 if ( prefix.length() ) {
417 if ( prefix[int(prefix.length()-1)] != '/' )
418 prefix += QChar( '/' );
419 }
420
421 QString fname = filename;
422 QString realname;
423 QString delims;
424 delims = search_delimiters.isNull() ?
425 QString::fromLatin1( "_." ) : search_delimiters;
426
427 for ( ;; ) {
428 QFileInfo fi;
429
430 realname = prefix + fname;
431 fi.setFile( realname );
432 if ( fi.isReadable() )
433 break;
434
435 realname += suffix.isNull() ? QString::fromLatin1( ".qm" ) : suffix;
436 fi.setFile( realname );
437 if ( fi.isReadable() )
438 break;
439
440 int rightmost = 0;
441 for ( int i = 0; i < (int)delims.length(); i++ ) {
442 int k = fname.findRev( delims[i] );
443 if ( k > rightmost )
444 rightmost = k;
445 }
446
447 // no truncations? fail
448 if ( rightmost == 0 )
449 return FALSE;
450
451 fname.truncate( rightmost );
452 }
453
454 // realname is now the fully qualified name of a readable file.
455
456#if defined(QT_USE_MMAP)
457
458#ifndef MAP_FILE
459#define MAP_FILE 0
460#endif
461#ifndef MAP_FAILED
462#define MAP_FAILED -1
463#endif
464
465 int f;
466
467 f = qt_open( QFile::encodeName(realname), O_RDONLY, 0666 );
468 if ( f < 0 ) {
469 // qDebug( "can't open %s: %s", realname.ascii(), strerror( errno ) );
470 return FALSE;
471 }
472
473 struct stat st;
474 if ( fstat( f, &st ) ) {
475 // qDebug( "can't stat %s: %s", realname.ascii(), strerror( errno ) );
476 return FALSE;
477 }
478 char * tmp;
479 tmp = (char*)mmap( 0, st.st_size, // any address, whole file
480 PROT_READ, // read-only memory
481 MAP_FILE | MAP_PRIVATE, // swap-backed map from file
482 f, 0 ); // from offset 0 of f
483 if ( !tmp || tmp == (char*)MAP_FAILED ) {
484 // qDebug( "can't mmap %s: %s", filename.ascii(), strerror( errno ) );
485 return FALSE;
486 }
487
488 ::close( f );
489
490 d->unmapPointer = tmp;
491 d->unmapLength = st.st_size;
492#else
493 QFile f( realname );
494 if ( !f.exists() )
495 return FALSE;
496 d->unmapLength = f.size();
497 d->unmapPointer = new char[d->unmapLength];
498 bool ok = FALSE;
499 if ( f.open(IO_ReadOnly) ) {
500 ok = d->unmapLength ==
501 (uint)f.readBlock( d->unmapPointer, d->unmapLength );
502 f.close();
503 }
504 if ( !ok ) {
505 delete [] d->unmapPointer;
506 d->unmapPointer = 0;
507 return FALSE;
508 }
509#endif
510
511 return do_load( (const uchar *) d->unmapPointer, d->unmapLength );
512}
513
514/*!
515 \overload
516 \fn bool QTranslator::load( const uchar *data, int len )
517
518 Loads the .qm file data \a data of length \a len into the
519 translator.
520
521 The data is not copied. The caller must be able to guarantee that \a data
522 will not be deleted or modified.
523*/
524
525bool QTranslator::do_load( const uchar *data, int len )
526{
527 if ( len < MagicLength || memcmp( data, magic, MagicLength ) != 0 ) {
528 clear();
529 return FALSE;
530 }
531
532 QByteArray array;
533 array.setRawData( (const char *) data, len );
534 QDataStream s( array, IO_ReadOnly );
535 bool ok = TRUE;
536
537 s.device()->at( MagicLength );
538
539 Q_UINT8 tag = 0;
540 Q_UINT32 blockLen = 0;
541 s >> tag >> blockLen;
542 while ( tag && blockLen ) {
543 if ( (Q_UINT32) s.device()->at() + blockLen > (Q_UINT32) len ) {
544 ok = FALSE;
545 break;
546 }
547
548 if ( tag == QTranslatorPrivate::Contexts && !d->contextArray ) {
549 d->contextArray = new QByteArray;
550 d->contextArray->setRawData( array.data() + s.device()->at(),
551 blockLen );
552 } else if ( tag == QTranslatorPrivate::Hashes && !d->offsetArray ) {
553 d->offsetArray = new QByteArray;
554 d->offsetArray->setRawData( array.data() + s.device()->at(),
555 blockLen );
556 } else if ( tag == QTranslatorPrivate::Messages && !d->messageArray ) {
557 d->messageArray = new QByteArray;
558 d->messageArray->setRawData( array.data() + s.device()->at(),
559 blockLen );
560 }
561
562 if ( !s.device()->at(s.device()->at() + blockLen) ) {
563 ok = FALSE;
564 break;
565 }
566 tag = 0;
567 blockLen = 0;
568 if ( !s.atEnd() )
569 s >> tag >> blockLen;
570 }
571 array.resetRawData( (const char *) data, len );
572
573 if ( qApp && qApp->translators && qApp->translators->contains(this) )
574 qApp->setReverseLayout( qt_detectRTLLanguage() );
575 return ok;
576}
577
578#ifndef QT_NO_TRANSLATION_BUILDER
579
580/*!
581 Saves this message file to \a filename, overwriting the previous
582 contents of \a filename. If \a mode is \c Everything (the
583 default), all the information is preserved. If \a mode is \c
584 Stripped, any information that is not necessary for findMessage()
585 is stripped away.
586
587 \sa load()
588*/
589
590bool QTranslator::save( const QString & filename, SaveMode mode )
591{
592 QFile f( filename );
593 if ( f.open( IO_WriteOnly ) ) {
594 squeeze( mode );
595
596 QDataStream s( &f );
597 s.writeRawBytes( (const char *)magic, MagicLength );
598 Q_UINT8 tag;
599
600 if ( d->offsetArray != 0 ) {
601 tag = (Q_UINT8) QTranslatorPrivate::Hashes;
602 Q_UINT32 oas = (Q_UINT32) d->offsetArray->size();
603 s << tag << oas;
604 s.writeRawBytes( d->offsetArray->data(), oas );
605 }
606 if ( d->messageArray != 0 ) {
607 tag = (Q_UINT8) QTranslatorPrivate::Messages;
608 Q_UINT32 mas = (Q_UINT32) d->messageArray->size();
609 s << tag << mas;
610 s.writeRawBytes( d->messageArray->data(), mas );
611 }
612 if ( d->contextArray != 0 ) {
613 tag = (Q_UINT8) QTranslatorPrivate::Contexts;
614 Q_UINT32 cas = (Q_UINT32) d->contextArray->size();
615 s << tag << cas;
616 s.writeRawBytes( d->contextArray->data(), cas );
617 }
618 return TRUE;
619 }
620 return FALSE;
621}
622
623#endif
624
625/*!
626 Empties this translator of all contents.
627
628 This function works with stripped translator files.
629*/
630
631void QTranslator::clear()
632{
633 if ( d->unmapPointer && d->unmapLength ) {
634#if defined(QT_USE_MMAP)
635 munmap( d->unmapPointer, d->unmapLength );
636#else
637 delete [] d->unmapPointer;
638#endif
639 d->unmapPointer = 0;
640 d->unmapLength = 0;
641 }
642
643 if ( d->messageArray ) {
644 d->messageArray->resetRawData( d->messageArray->data(),
645 d->messageArray->size() );
646 delete d->messageArray;
647 d->messageArray = 0;
648 }
649 if ( d->offsetArray ) {
650 d->offsetArray->resetRawData( d->offsetArray->data(),
651 d->offsetArray->size() );
652 delete d->offsetArray;
653 d->offsetArray = 0;
654 }
655 if ( d->contextArray ) {
656 d->contextArray->resetRawData( d->contextArray->data(),
657 d->contextArray->size() );
658 delete d->contextArray;
659 d->contextArray = 0;
660 }
661#ifndef QT_NO_TRANSLATION_BUILDER
662 delete d->messages;
663 d->messages = 0;
664#endif
665
666 if ( qApp ) {
667 qApp->setReverseLayout( qt_detectRTLLanguage() );
668
669 QWidgetList *list = QApplication::topLevelWidgets();
670 QWidgetListIt it( *list );
671 QWidget *w;
672 while ( ( w=it.current() ) != 0 ) {
673 ++it;
674 if (!w->isDesktop())
675 qApp->postEvent( w, new QEvent( QEvent::LanguageChange ) );
676 }
677 delete list;
678 }
679}
680
681#ifndef QT_NO_TRANSLATION_BUILDER
682
683/*!
684 Converts this message file to the compact format used to store
685 message files on disk.
686
687 You should never need to call this directly; save() and other
688 functions call it as necessary. \a mode is for internal use.
689
690 \sa save() unsqueeze()
691*/
692
693void QTranslator::squeeze( SaveMode mode )
694{
695 if ( !d->messages ) {
696 if ( mode == Stripped )
697 unsqueeze();
698 else
699 return;
700 }
701
702 QMap<QTranslatorMessage, void *> * messages = d->messages;
703
704 d->messages = 0;
705 clear();
706
707 d->messageArray = new QByteArray;
708 d->offsetArray = new QByteArray;
709
710 QMap<QTranslatorPrivate::Offset, void *> offsets;
711
712 QDataStream ms( *d->messageArray, IO_WriteOnly );
713 QMap<QTranslatorMessage, void *>::Iterator it = messages->begin(), next;
714 int cpPrev = 0, cpNext = 0;
715 for ( it = messages->begin(); it != messages->end(); ++it ) {
716 cpPrev = cpNext;
717 next = it;
718 ++next;
719 if ( next == messages->end() )
720 cpNext = 0;
721 else
722 cpNext = (int) it.key().commonPrefix( next.key() );
723 offsets.replace( QTranslatorPrivate::Offset(it.key(),
724 ms.device()->at()), (void*)0 );
725 it.key().write( ms, mode == Stripped,
726 (QTranslatorMessage::Prefix) QMAX(cpPrev, cpNext + 1) );
727 }
728
729 d->offsetArray->resize( 0 );
730 QMap<QTranslatorPrivate::Offset, void *>::Iterator offset;
731 offset = offsets.begin();
732 QDataStream ds( *d->offsetArray, IO_WriteOnly );
733 while ( offset != offsets.end() ) {
734 QTranslatorPrivate::Offset k = offset.key();
735 ++offset;
736 ds << (Q_UINT32)k.h << (Q_UINT32)k.o;
737 }
738
739 if ( mode == Stripped ) {
740 QAsciiDict<int> contextSet( 1511 );
741 int baudelaire;
742
743 for ( it = messages->begin(); it != messages->end(); ++it )
744 contextSet.replace( it.key().context(), &baudelaire );
745
746 Q_UINT16 hTableSize;
747 if ( contextSet.count() < 200 )
748 hTableSize = ( contextSet.count() < 60 ) ? 151 : 503;
749 else if ( contextSet.count() < 2500 )
750 hTableSize = ( contextSet.count() < 750 ) ? 1511 : 5003;
751 else
752 hTableSize = 15013;
753
754 QIntDict<char> hDict( hTableSize );
755 QAsciiDictIterator<int> c = contextSet;
756 while ( c.current() != 0 ) {
757 hDict.insert( (long) (elfHash(c.currentKey()) % hTableSize),
758 c.currentKey() );
759 ++c;
760 }
761
762 /*
763 The contexts found in this translator are stored in a hash
764 table to provide fast lookup. The context array has the
765 following format:
766
767 Q_UINT16 hTableSize;
768 Q_UINT16 hTable[hTableSize];
769 Q_UINT8 contextPool[...];
770
771 The context pool stores the contexts as Pascal strings:
772
773 Q_UINT8 len;
774 Q_UINT8 data[len];
775
776 Let's consider the look-up of context "FunnyDialog". A
777 hash value between 0 and hTableSize - 1 is computed, say h.
778 If hTable[h] is 0, "FunnyDialog" is not covered by this
779 translator. Else, we check in the contextPool at offset
780 2 * hTable[h] to see if "FunnyDialog" is one of the
781 contexts stored there, until we find it or we meet the
782 empty string.
783 */
784 d->contextArray = new QByteArray;
785 d->contextArray->resize( 2 + (hTableSize << 1) );
786 QDataStream t( *d->contextArray, IO_WriteOnly );
787 Q_UINT16 *hTable = new Q_UINT16[hTableSize];
788 memset( hTable, 0, hTableSize * sizeof(Q_UINT16) );
789
790 t << hTableSize;
791 t.device()->at( 2 + (hTableSize << 1) );
792 t << (Q_UINT16) 0; // the entry at offset 0 cannot be used
793 uint upto = 2;
794
795 for ( int i = 0; i < hTableSize; i++ ) {
796 const char *con = hDict.find( i );
797 if ( con == 0 ) {
798 hTable[i] = 0;
799 } else {
800 hTable[i] = (Q_UINT16) ( upto >> 1 );
801 do {
802 uint len = (uint) qstrlen( con );
803 len = QMIN( len, 255 );
804 t << (Q_UINT8) len;
805 t.writeRawBytes( con, len );
806 upto += 1 + len;
807 hDict.remove( i );
808 } while ( (con = hDict.find(i)) != 0 );
809 do {
810 t << (Q_UINT8) 0; // empty string (at least one)
811 upto++;
812 } while ( (upto & 0x1) != 0 ); // offsets have to be even
813 }
814 }
815 t.device()->at( 2 );
816 for ( int j = 0; j < hTableSize; j++ )
817 t << hTable[j];
818 delete [] hTable;
819
820 if ( upto > 131072 ) {
821 qWarning( "QTranslator::squeeze: Too many contexts" );
822 delete d->contextArray;
823 d->contextArray = 0;
824 }
825 }
826 delete messages;
827}
828
829
830/*!
831 Converts this message file into an easily modifiable data
832 structure, less compact than the format used in the files.
833
834 You should never need to call this function; it is called by
835 insert() and friends as necessary.
836
837 \sa squeeze()
838*/
839
840void QTranslator::unsqueeze()
841{
842 if ( d->messages )
843 return;
844
845 d->messages = new QMap<QTranslatorMessage, void *>;
846 if ( !d->messageArray )
847 return;
848
849 QDataStream s( *d->messageArray, IO_ReadOnly );
850 for ( ;; ) {
851 QTranslatorMessage m( s );
852 if ( m.hash() == 0 )
853 break;
854 d->messages->insert( m, (void *) 0 );
855 }
856}
857
858
859/*!
860 Returns TRUE if this message file contains a message with the key
861 (\a context, \a sourceText, \a comment); otherwise returns FALSE.
862
863 This function works with stripped translator files.
864
865 (This is is a one-liner that calls findMessage().)
866*/
867
868bool QTranslator::contains( const char* context, const char* sourceText,
869 const char* comment ) const
870{
871 return !findMessage( context, sourceText, comment ).translation().isNull();
872}
873
874
875/*!
876 Inserts \a message into this message file.
877
878 This function does \e not work with stripped translator files. It
879 may appear to, but that is not dependable.
880
881 \sa remove()
882*/
883
884void QTranslator::insert( const QTranslatorMessage& message )
885{
886 unsqueeze();
887 d->messages->remove( message ); // safer
888 d->messages->insert( message, (void *) 0 );
889}
890
891
892/*!
893 \fn void QTranslator::insert( const char *, const char *, const QString & )
894 \overload
895 \obsolete
896*/
897
898/*!
899 Removes \a message from this translator.
900
901 This function works with stripped translator files.
902
903 \sa insert()
904*/
905
906void QTranslator::remove( const QTranslatorMessage& message )
907{
908 unsqueeze();
909 d->messages->remove( message );
910}
911
912
913/*!
914 \fn void QTranslator::remove( const char *, const char * )
915 \overload
916 \obsolete
917
918 Removes the translation associated to the key (\a context, \a sourceText,
919 "") from this translator.
920*/
921#endif
922
923/*!
924 \fn QString QTranslator::find( const char*, const char*, const char* ) const
925 \obsolete
926
927 Please use findMessage() instead.
928
929 Returns the translation for the key (\a context, \a sourceText,
930 \a comment) or QString::null if there is none in this translator.
931*/
932
933/*! Returns the QTranslatorMessage for the key
934 (\a context, \a sourceText, \a comment). If none is found,
935 also tries (\a context, \a sourceText, "").
936*/
937
938QTranslatorMessage QTranslator::findMessage( const char* context,
939 const char* sourceText,
940 const char* comment ) const
941{
942 if ( context == 0 )
943 context = "";
944 if ( sourceText == 0 )
945 sourceText = "";
946 if ( comment == 0 )
947 comment = "";
948
949#ifndef QT_NO_TRANSLATION_BUILDER
950 if ( d->messages ) {
951 QMap<QTranslatorMessage, void *>::ConstIterator it;
952
953 it = d->messages->find( QTranslatorMessage(context, sourceText,
954 comment) );
955 if ( it != d->messages->end() )
956 return it.key();
957
958 if ( comment[0] ) {
959 it = d->messages->find( QTranslatorMessage(context, sourceText,
960 "") );
961 if ( it != d->messages->end() )
962 return it.key();
963 }
964 return QTranslatorMessage();
965 }
966#endif
967
968 if ( !d->offsetArray )
969 return QTranslatorMessage();
970
971 /*
972 Check if the context belongs to this QTranslator. If many translators are
973 installed, this step is necessary.
974 */
975 if ( d->contextArray ) {
976 Q_UINT16 hTableSize = 0;
977 QDataStream t( *d->contextArray, IO_ReadOnly );
978 t >> hTableSize;
979 uint g = elfHash( context ) % hTableSize;
980 t.device()->at( 2 + (g << 1) );
981 Q_UINT16 off;
982 t >> off;
983 if ( off == 0 )
984 return QTranslatorMessage();
985 t.device()->at( 2 + (hTableSize << 1) + (off << 1) );
986
987 Q_UINT8 len;
988 char con[256];
989 for ( ;; ) {
990 t >> len;
991 if ( len == 0 )
992 return QTranslatorMessage();
993 t.readRawBytes( con, len );
994 con[len] = '\0';
995 if ( qstrcmp(con, context) == 0 )
996 break;
997 }
998 }
999
1000 size_t numItems = d->offsetArray->size() / ( 2 * sizeof(Q_UINT32) );
1001 if ( !numItems )
1002 return QTranslatorMessage();
1003
1004 if ( systemWordSize == 0 )
1005 qSysInfo( &systemWordSize, &systemBigEndian );
1006
1007 for ( ;; ) {
1008 Q_UINT32 h = elfHash( QCString(sourceText) + comment );
1009
1010 char *r = (char *) bsearch( &h, d->offsetArray->data(), numItems,
1011 2 * sizeof(Q_UINT32),
1012 systemBigEndian ? cmp_uint32_big
1013 : cmp_uint32_little );
1014 if ( r != 0 ) {
1015 // go back on equal key
1016 while ( r != d->offsetArray->data() &&
1017 cmp_uint32_big(r - 8, r) == 0 )
1018 r -= 8;
1019
1020 QDataStream s( *d->offsetArray, IO_ReadOnly );
1021 s.device()->at( r - d->offsetArray->data() );
1022
1023 Q_UINT32 rh, ro;
1024 s >> rh >> ro;
1025
1026 QDataStream ms( *d->messageArray, IO_ReadOnly );
1027 while ( rh == h ) {
1028 ms.device()->at( ro );
1029 QTranslatorMessage m( ms );
1030 if ( match(m.context(), context)
1031 && match(m.sourceText(), sourceText)
1032 && match(m.comment(), comment) )
1033 return m;
1034 if ( s.atEnd() )
1035 break;
1036 s >> rh >> ro;
1037 }
1038 }
1039 if ( !comment[0] )
1040 break;
1041 comment = "";
1042 }
1043 return QTranslatorMessage();
1044}
1045
1046/*!
1047 Returns TRUE if this translator is empty, otherwise returns FALSE.
1048 This function works with stripped and unstripped translation files.
1049*/
1050bool QTranslator::isEmpty() const
1051{
1052 return !( d->unmapPointer || d->unmapLength || d->messageArray ||
1053 d->offsetArray || d->contextArray ||
1054 (d->messages && d->messages->count())
1055 );
1056}
1057
1058
1059#ifndef QT_NO_TRANSLATION_BUILDER
1060
1061/*!
1062 Returns a list of the messages in the translator. This function is
1063 rather slow. Because it is seldom called, it's optimized for
1064 simplicity and small size, rather than speed.
1065
1066 If you want to iterate over the list, you should iterate over a
1067 copy, e.g.
1068 \code
1069 QValueList<QTranslatorMessage> list = myTranslator.messages();
1070 QValueList<QTranslatorMessage>::Iterator it = list.begin();
1071 while ( it != list.end() ) {
1072 process_message( *it );
1073 ++it;
1074 }
1075 \endcode
1076*/
1077
1078QValueList<QTranslatorMessage> QTranslator::messages() const
1079{
1080 ((QTranslator *) this)->unsqueeze();
1081 return d->messages->keys();
1082}
1083
1084#endif
1085
1086/*!
1087 \class QTranslatorMessage
1088
1089 \brief The QTranslatorMessage class contains a translator message and its
1090 properties.
1091
1092 \ingroup i18n
1093 \ingroup environment
1094
1095 This class is of no interest to most applications. It is useful
1096 for translation tools such as \link linguist-manual.book Qt
1097 Linguist\endlink. It is provided simply to make the API complete
1098 and regular.
1099
1100 For a QTranslator object, a lookup key is a triple (\e context, \e
1101 {source text}, \e comment) that uniquely identifies a message. An
1102 extended key is a quadruple (\e hash, \e context, \e {source
1103 text}, \e comment), where \e hash is computed from the source text
1104 and the comment. Unless you plan to read and write messages
1105 yourself, you need not worry about the hash value.
1106
1107 QTranslatorMessage stores this triple or quadruple and the relevant
1108 translation if there is any.
1109
1110 \sa QTranslator
1111*/
1112
1113/*!
1114 Constructs a translator message with the extended key (0, 0, 0, 0)
1115 and QString::null as translation.
1116*/
1117
1118QTranslatorMessage::QTranslatorMessage()
1119 : h( 0 ), cx( 0 ), st( 0 ), cm( 0 )
1120{
1121}
1122
1123
1124/*!
1125 Constructs an translator message with the extended key (\e h, \a
1126 context, \a sourceText, \a comment), where \e h is computed from
1127 \a sourceText and \a comment, and possibly with a \a translation.
1128*/
1129
1130QTranslatorMessage::QTranslatorMessage( const char * context,
1131 const char * sourceText,
1132 const char * comment,
1133 const QString& translation )
1134 : cx( context ), st( sourceText ), cm( comment ), tn( translation )
1135{
1136 // 0 means we don't know, "" means empty
1137 if ( cx == (const char*)0 )
1138 cx = "";
1139 if ( st == (const char*)0 )
1140 st = "";
1141 if ( cm == (const char*)0 )
1142 cm = "";
1143 h = elfHash( st + cm );
1144}
1145
1146
1147/*!
1148 Constructs a translator message read from the \a stream. The
1149 resulting message may have any combination of content.
1150
1151 \sa QTranslator::save()
1152*/
1153
1154QTranslatorMessage::QTranslatorMessage( QDataStream & stream )
1155 : cx( 0 ), st( 0 ), cm( 0 )
1156{
1157 QString str16;
1158 char tag;
1159 Q_UINT8 obs1;
1160
1161 for ( ;; ) {
1162 tag = 0;
1163 if ( !stream.atEnd() )
1164 stream.readRawBytes( &tag, 1 );
1165 switch( (Tag)tag ) {
1166 case Tag_End:
1167 if ( h == 0 )
1168 h = elfHash( st + cm );
1169 return;
1170 case Tag_SourceText16: // obsolete
1171 stream >> str16;
1172 st = str16.latin1();
1173 break;
1174 case Tag_Translation:
1175 stream >> tn;
1176 break;
1177 case Tag_Context16: // obsolete
1178 stream >> str16;
1179 cx = str16.latin1();
1180 break;
1181 case Tag_Hash:
1182 stream >> h;
1183 break;
1184 case Tag_SourceText:
1185 stream >> st;
1186 break;
1187 case Tag_Context:
1188 stream >> cx;
1189 if ( cx == "" ) // for compatibility
1190 cx = 0;
1191 break;
1192 case Tag_Comment:
1193 stream >> cm;
1194 break;
1195 case Tag_Obsolete1: // obsolete
1196 stream >> obs1;
1197 break;
1198 default:
1199 h = 0;
1200 st = 0;
1201 cx = 0;
1202 cm = 0;
1203 tn = QString::null;
1204 return;
1205 }
1206 }
1207}
1208
1209
1210/*!
1211 Constructs a copy of translator message \a m.
1212*/
1213
1214QTranslatorMessage::QTranslatorMessage( const QTranslatorMessage & m )
1215 : cx( m.cx ), st( m.st ), cm( m.cm ), tn( m.tn )
1216{
1217 h = m.h;
1218}
1219
1220
1221/*!
1222 Assigns message \a m to this translator message and returns a
1223 reference to this translator message.
1224*/
1225
1226QTranslatorMessage & QTranslatorMessage::operator=(
1227 const QTranslatorMessage & m )
1228{
1229 h = m.h;
1230 cx = m.cx;
1231 st = m.st;
1232 cm = m.cm;
1233 tn = m.tn;
1234 return *this;
1235}
1236
1237
1238/*!
1239 \fn uint QTranslatorMessage::hash() const
1240
1241 Returns the hash value used internally to represent the lookup
1242 key. This value is zero only if this translator message was
1243 constructed from a stream containing invalid data.
1244
1245 The hashing function is unspecified, but it will remain unchanged
1246 in future versions of Qt.
1247*/
1248
1249/*!
1250 \fn const char *QTranslatorMessage::context() const
1251
1252 Returns the context for this message (e.g. "MyDialog").
1253*/
1254
1255/*!
1256 \fn const char *QTranslatorMessage::sourceText() const
1257
1258 Returns the source text of this message (e.g. "&Save").
1259*/
1260
1261/*!
1262 \fn const char *QTranslatorMessage::comment() const
1263
1264 Returns the comment for this message (e.g. "File|Save").
1265*/
1266
1267/*!
1268 \fn void QTranslatorMessage::setTranslation( const QString & translation )
1269
1270 Sets the translation of the source text to \a translation.
1271
1272 \sa translation()
1273*/
1274
1275/*!
1276 \fn QString QTranslatorMessage::translation() const
1277
1278 Returns the translation of the source text (e.g., "&Sauvegarder").
1279
1280 \sa setTranslation()
1281*/
1282
1283/*!
1284 \enum QTranslatorMessage::Prefix
1285
1286 Let (\e h, \e c, \e s, \e m) be the extended key. The possible
1287 prefixes are
1288
1289 \value NoPrefix no prefix
1290 \value Hash only (\e h)
1291 \value HashContext only (\e h, \e c)
1292 \value HashContextSourceText only (\e h, \e c, \e s)
1293 \value HashContextSourceTextComment the whole extended key, (\e
1294 h, \e c, \e s, \e m)
1295
1296 \sa write() commonPrefix()
1297*/
1298
1299/*!
1300 Writes this translator message to the \a stream. If \a strip is
1301 FALSE (the default), all the information in the message is
1302 written. If \a strip is TRUE, only the part of the extended key
1303 specified by \a prefix is written with the translation (\c
1304 HashContextSourceTextComment by default).
1305
1306 \sa commonPrefix()
1307*/
1308
1309void QTranslatorMessage::write( QDataStream & stream, bool strip,
1310 Prefix prefix ) const
1311{
1312 char tag;
1313
1314 tag = (char)Tag_Translation;
1315 stream.writeRawBytes( &tag, 1 );
1316 stream << tn;
1317
1318 bool mustWriteHash = TRUE;
1319 if ( !strip )
1320 prefix = HashContextSourceTextComment;
1321
1322 switch ( prefix ) {
1323 case HashContextSourceTextComment:
1324 tag = (char)Tag_Comment;
1325 stream.writeRawBytes( &tag, 1 );
1326 stream << cm;
1327 // fall through
1328 case HashContextSourceText:
1329 tag = (char)Tag_SourceText;
1330 stream.writeRawBytes( &tag, 1 );
1331 stream << st;
1332 // fall through
1333 case HashContext:
1334 tag = (char)Tag_Context;
1335 stream.writeRawBytes( &tag, 1 );
1336 stream << cx;
1337 // fall through
1338 default:
1339 if ( mustWriteHash ) {
1340 tag = (char)Tag_Hash;
1341 stream.writeRawBytes( &tag, 1 );
1342 stream << h;
1343 }
1344 }
1345
1346 tag = (char)Tag_End;
1347 stream.writeRawBytes( &tag, 1 );
1348}
1349
1350
1351/*!
1352 Returns the widest lookup prefix that is common to this translator
1353 message and to message \a m.
1354
1355 For example, if the extended key is for this message is (71,
1356 "PrintDialog", "Yes", "Print?") and that for \a m is (71,
1357 "PrintDialog", "No", "Print?"), this function returns \c
1358 HashContext.
1359
1360 \sa write()
1361*/
1362
1363QTranslatorMessage::Prefix QTranslatorMessage::commonPrefix(
1364 const QTranslatorMessage& m ) const
1365{
1366 if ( h != m.h )
1367 return NoPrefix;
1368 if ( cx != m.cx )
1369 return Hash;
1370 if ( st != m.st )
1371 return HashContext;
1372 if ( cm != m.cm )
1373 return HashContextSourceText;
1374 return HashContextSourceTextComment;
1375}
1376
1377
1378/*!
1379 Returns TRUE if the extended key of this object is equal to that of
1380 \a m; otherwise returns FALSE.
1381*/
1382
1383bool QTranslatorMessage::operator==( const QTranslatorMessage& m ) const
1384{
1385 return h == m.h && cx == m.cx && st == m.st && cm == m.cm;
1386}
1387
1388
1389/*!
1390 \fn bool QTranslatorMessage::operator!=( const QTranslatorMessage& m ) const
1391
1392 Returns TRUE if the extended key of this object is different from
1393 that of \a m; otherwise returns FALSE.
1394*/
1395
1396
1397/*!
1398 Returns TRUE if the extended key of this object is
1399 lexicographically before than that of \a m; otherwise returns
1400 FALSE.
1401*/
1402
1403bool QTranslatorMessage::operator<( const QTranslatorMessage& m ) const
1404{
1405 return h != m.h ? h < m.h
1406 : ( cx != m.cx ? cx < m.cx
1407 : (st != m.st ? st < m.st : cm < m.cm) );
1408}
1409
1410
1411/*!
1412 \fn bool QTranslatorMessage::operator<=( const QTranslatorMessage& m ) const
1413
1414 Returns TRUE if the extended key of this object is
1415 lexicographically before that of \a m or if they are equal;
1416 otherwise returns FALSE.
1417*/
1418
1419/*!
1420 \fn bool QTranslatorMessage::operator>( const QTranslatorMessage& m ) const
1421
1422 Returns TRUE if the extended key of this object is
1423 lexicographically after that of \a m; otherwise returns FALSE.
1424*/
1425
1426/*!
1427 \fn bool QTranslatorMessage::operator>=( const QTranslatorMessage& m ) const
1428
1429 Returns TRUE if the extended key of this object is
1430 lexicographically after that of \a m or if they are equal;
1431 otherwise returns FALSE.
1432*/
1433
1434#endif // QT_NO_TRANSLATION
Note: See TracBrowser for help on using the repository browser.