Page MenuHomePhorge

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 05123dd071..b8a00a63fc 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,362 +1,362 @@
project(kdelibs)
# where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake/modules")
# Make CPack available to easy generate binary packages
include(CPack)
################# set KDE specific information #################
set (KDE_VERSION_MAJOR 4)
set (KDE_VERSION_MINOR 8)
-set (KDE_VERSION_RELEASE 00)
+set (KDE_VERSION_RELEASE 1)
set (KDE_VERSION "${KDE_VERSION_MAJOR}.${KDE_VERSION_MINOR}.${KDE_VERSION_RELEASE}" )
-set (KDE_VERSION_STRING "${KDE_VERSION} (4.8.0)")
+set (KDE_VERSION_STRING "${KDE_VERSION} (4.8.1)")
set (KDE_DISTRIBUTION_TEXT "compiled sources" CACHE STRING "Indicate the distribution in bug reports" )
# win32: give kde home in debug mode a different name as the release home dir because the settings and caches are different
if (WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug")
set (_KDE_DEFAULT_HOME_POSTFIX "-debug" CACHE STRING "default KDE home directory postfix" )
endif (WIN32 AND CMAKE_BUILD_TYPE STREQUAL "Debug")
set (KDE_DEFAULT_HOME ".kde${_KDE_DEFAULT_HOME_POSTFIX}" CACHE STRING "The default KDE home directory" )
# this must be before FindKDE4Internal in order to preset the result of the visibility test, so that it will be skipped
option(KHTML_BUILD_TESTREGRESSION "Build KHTML's testregression. Note: this disables hidden visibility")
# Disable visibility if testregression is built, because the symbols are needed then
if (KHTML_BUILD_TESTREGRESSION)
set (__KDE_HAVE_GCC_VISIBILITY 0)
endif (KHTML_BUILD_TESTREGRESSION)
option(STATIC_LIBRARY "Build kdelibs as static libraries." FALSE)
################# write platform profile file which will be installed #################
include(CreateKDEPlatformProfile.cmake)
if(KDE_PLATFORM_FEATURE_DISABLE_DEPRECATED)
set(KDE_NO_DEPRECATED TRUE)
endif(KDE_PLATFORM_FEATURE_DISABLE_DEPRECATED)
############### Load the CTest options ###############
# CTestCustom.cmake has to be in the CTEST_BINARY_DIR.
# in the KDE build system, this is the same as CMAKE_BINARY_DIR.
configure_file(${CMAKE_SOURCE_DIR}/CTestCustom.cmake ${CMAKE_BINARY_DIR}/CTestCustom.cmake COPYONLY)
################# now find all used packages #################
set (QT_MIN_VERSION "4.7.0")
find_package(KDE4Internal REQUIRED)
include(KDE4Defaults)
include (MacroLibrary)
if (APPLE)
find_package(Carbon REQUIRED)
endif (APPLE)
if(UNIX AND Q_WS_X11)
#X11 Session Management (SM) is required
#X11_SM_FOUND is set in FindX11, which is required by KDE4Internal
if(NOT X11_SM_FOUND)
message(FATAL_ERROR "\nThe X11 Session Management (SM) development package could not be found.\nPlease install libSM.\n")
endif(NOT X11_SM_FOUND)
endif(UNIX AND Q_WS_X11)
#required features:
# Perl is used e.g. in khtml, kjs, kjsembed and others
find_package(Perl)
macro_log_feature(PERL_FOUND "Perl" "Needed for building kdelibs" "http://www.perl.org" TRUE "" "")
find_package(ZLIB)
macro_log_feature(ZLIB_FOUND "ZLib" "Support for gzip compressed files and data streams" "http://www.zlib.net" TRUE "" "Required by the core KDE libraries and some critical kioslaves")
set(STRIGI_MIN_VERSION 0.6.3)
find_package(Strigi)
if (WIN32)
set (STRIGI_REQUIRED FALSE)
set (STRIGI_EXTRA_TEXT "")
else (WIN32)
set (STRIGI_REQUIRED TRUE)
set (STRIGI_EXTRA_TEXT "Required by some critical kioslaves")
endif (WIN32)
macro_log_feature(STRIGI_FOUND "Strigi" "Desktop indexing and search support" "http://strigi.sourceforge.net" ${STRIGI_REQUIRED} "${STRIGI_MIN_VERSION}" ${STRIGI_EXTRA_TEXT})
set(LIBATTICA_MIN_VERSION "0.1.90")
find_package(LibAttica)
macro_log_feature(LIBATTICA_FOUND "libattica" "Support for Get Hot New Stuff" "git clone git://anongit.kde.org/attica" TRUE "${LIBATTICA_MIN_VERSION}" "")
#optional features
if(X11_FOUND)
#X11_Xrender discovery is done by FindX11
macro_log_feature(X11_Xrender_FOUND "X Rendering Extension (libXrender)" "Support for compositing, rendering operations, and alpha-blending" "http://www.x.org" FALSE "" "STRONGLY RECOMMENDED")
macro_bool_to_01(X11_Xscreensaver_FOUND HAVE_XSCREENSAVER)
macro_bool_to_01(X11_XSync_FOUND HAVE_XSYNC)
macro_log_feature(HAVE_XSCREENSAVER "X Screensaver Extension (libXss)" "Support for KIdleTime (fallback mode)" "http://www.x.org/" FALSE "" "")
macro_log_feature(HAVE_XSYNC "X Sync Extension (libXext)" "Efficient operation of KIdleTime" "http://www.x.org/" FALSE "" "STRONGLY RECOMMENDED")
if(NOT HAVE_XSYNC AND NOT HAVE_XSCREENSAVER)
message(FATAL_ERROR "\nNeither the XSync (libXext) nor XScreensaver (libXss) development package was found.\nPlease install one of them (XSync is recommended)\n")
endif(NOT HAVE_XSYNC AND NOT HAVE_XSCREENSAVER)
endif(X11_FOUND)
macro_optional_find_package(OpenSSL)
macro_log_feature(OPENSSL_FOUND "OpenSSL" "Support for secure network communications (SSL and TLS)" "http://openssl.org" FALSE "" "STRONGLY RECOMMENDED: KDE uses OpenSSL for the bulk of secure communications, including secure web browsing via HTTPS")
macro_optional_find_package(Libintl)
macro_log_feature(LIBINTL_FOUND "Libintl" "Support for multiple languages" "http://www.gnu.org/software/gettext" FALSE "" "STRONGLY RECOMMENDED: Enables KDE to be available in many different languages")
set(SOPRANO_MIN_VERSION "2.5.60")
macro_optional_find_package(Soprano ${SOPRANO_MIN_VERSION} COMPONENTS PLUGIN_RAPTORPARSER PLUGIN_REDLANDBACKEND)
macro_log_feature(SOPRANO_FOUND "Soprano" "Support for the Nepomuk semantic desktop system" "http://soprano.sourceforge.net" FALSE "${SOPRANO_MIN_VERSION}" "")
macro_log_feature(SOPRANO_PLUGIN_RAPTORPARSER_FOUND "Soprano Raptor Parser" "Support for the Nepomuk semantic desktop system" "http://soprano.sourceforge.net" FALSE "" "")
macro_log_feature(SOPRANO_PLUGIN_REDLANDBACKEND_FOUND "Soprano Redland Backend" "Support for the Nepomuk semantic desktop system" "http://soprano.sourceforge.net" FALSE "" "")
set(SHAREDDESKTOPONTOLOGIES_MIN_VERSION 0.6.50)
macro_optional_find_package(SharedDesktopOntologies ${SHAREDDESKTOPONTOLOGIES_MIN_VERSION})
macro_log_feature(SHAREDDESKTOPONTOLOGIES_FOUND "Shared desktop ontologies" "Support for the Nepomuk semantic desktop system" "http://oscaf.sourceforge.net" FALSE "${SHAREDDESKTOPONTOLOGIES_MIN_VERSION}" "")
macro_optional_find_package(QCA2)
macro_log_feature(QCA2_FOUND "QCA2" "Support for remote plasma widgets" "http://delta.affinix.com/qca" FALSE "2.0.0" "")
find_package(DBusMenuQt)
macro_log_feature(DBUSMENUQT_FOUND "DBusMenuQt" "Support for notification area menus via the DBusMenu protocol" "https://launchpad.net/libdbusmenu-qt" TRUE "" "")
################# Disallow in-source build #################
macro_ensure_out_of_source_build("kdelibs requires an out of source build. Please create a separate build directory and run 'cmake path_to_kdelibs [options]' there.")
# ... and warn in case of an earlier in-source build
set(generatedFileInSourceDir EXISTS ${kdelibs_SOURCE_DIR}/kdemacros.h OR EXISTS ${kdelibs_SOURCE_DIR}/config.h)
if(${generatedFileInSourceDir})
message(STATUS "kdemacros.h or config.h exists in your source directory.")
message(FATAL_ERROR "Please run svn-clean, it would seem that your source directory has generated files in it.")
endif(${generatedFileInSourceDir})
#########################################################################
add_definitions(${QT_DEFINITIONS} ${KDE4_DEFINITIONS})
remove_definitions(-DQT3_SUPPORT_WARNINGS -DQT3_SUPPORT)
add_definitions(-DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS)
################# setup the include directories #################
# for including config.h and for includes like <kparts/foo.h>
include_directories( ${CMAKE_SOURCE_DIR} ${CMAKE_BINARY_DIR} ${CMAKE_SOURCE_DIR}/interfaces)
if(QCA2_FOUND)
include_directories(
${QCA2_INCLUDE_DIR}
)
endif(QCA2_FOUND)
# Those variables for are only valid inside of kdelibs, of course.
# Use the one variable for the lib you depend upon.
# E.g. kdeui uses ${KDE4_KDECORE_INCLUDES}. Something that depends on kparts uses ${KDE4_KPARTS_INCLUDES}.
set(KDE4_KJS_INCLUDES ${CMAKE_SOURCE_DIR}/kjs
${CMAKE_BINARY_DIR}/kjs)
if(NOT WINCE)
set(KDE4_KDECORE_INCLUDES ${KDE4_KJS_INCLUDES} )
endif(NOT WINCE)
# kdecore depends on Qt (need only headers from kjs)
set(KDE4_KDECORE_INCLUDES ${KDE4_KDECORE_INCLUDES}
${CMAKE_SOURCE_DIR}/kdecore
${CMAKE_BINARY_DIR}/kdecore
${CMAKE_SOURCE_DIR}/kdecore/compression
${CMAKE_SOURCE_DIR}/kdecore/config
${CMAKE_SOURCE_DIR}/kdecore/date
${CMAKE_SOURCE_DIR}/kdecore/io
${CMAKE_SOURCE_DIR}/kdecore/jobs
${CMAKE_SOURCE_DIR}/kdecore/kernel
${CMAKE_SOURCE_DIR}/kdecore/auth
${CMAKE_SOURCE_DIR}/kdecore/network
${CMAKE_SOURCE_DIR}/kdecore/services
${CMAKE_SOURCE_DIR}/kdecore/localization
${CMAKE_SOURCE_DIR}/kdecore/sycoca
${CMAKE_SOURCE_DIR}/kdecore/text
${CMAKE_SOURCE_DIR}/kdecore/util
${CMAKE_SOURCE_DIR}/kdecore/sonnet
${QT_INCLUDES}
${_KDE4_PLATFORM_INCLUDE_DIRS})
# kdeui depends on kdecore
set(KDE4_KDEUI_INCLUDES ${CMAKE_SOURCE_DIR}/kdeui
${CMAKE_SOURCE_DIR}/kdeui/actions
${CMAKE_SOURCE_DIR}/kdeui/colors
${CMAKE_SOURCE_DIR}/kdeui/config
${CMAKE_SOURCE_DIR}/kdeui/dialogs
${CMAKE_SOURCE_DIR}/kdeui/findreplace
${CMAKE_SOURCE_DIR}/kdeui/fonts
${CMAKE_SOURCE_DIR}/kdeui/icons
${CMAKE_SOURCE_DIR}/kdeui/itemviews
${CMAKE_SOURCE_DIR}/kdeui/jobs
${CMAKE_SOURCE_DIR}/kdeui/kernel
${CMAKE_SOURCE_DIR}/kdeui/notifications
${CMAKE_SOURCE_DIR}/kdeui/paged
${CMAKE_SOURCE_DIR}/kdeui/plotting
${CMAKE_SOURCE_DIR}/kdeui/shortcuts
${CMAKE_SOURCE_DIR}/kdeui/sonnet
${CMAKE_SOURCE_DIR}/kdeui/util
${CMAKE_SOURCE_DIR}/kdeui/widgets
${CMAKE_SOURCE_DIR}/kdeui/windowmanagement
${CMAKE_SOURCE_DIR}/kdeui/xmlgui
${KDE4_KDECORE_INCLUDES})
# kio depends on kdeui
set(KDE4_KIO_INCLUDES ${CMAKE_SOURCE_DIR}/kio
${CMAKE_SOURCE_DIR}/kio/bookmarks
${CMAKE_SOURCE_DIR}/kio/kio
${CMAKE_SOURCE_DIR}/kio/kfile
${KDE4_KDEUI_INCLUDES})
# kpty
set(KDE4_KPTY_INCLUDES ${CMAKE_SOURCE_DIR}/kpty ${KDE4_KIO_INCLUDES} )
# kparts depends on kio
set(KDE4_KPARTS_INCLUDES ${CMAKE_SOURCE_DIR}/kparts
${KDE4_KIO_INCLUDES})
# kde3support depends on kparts
set(KDE4_KDE3SUPPORT_INCLUDES ${CMAKE_SOURCE_DIR}/kde3support
${CMAKE_SOURCE_DIR}/kde3support/kdecore
${CMAKE_SOURCE_DIR}/kde3support/kdeui
${CMAKE_SOURCE_DIR}/kde3support/kio
${KDE4_KPARTS_INCLUDES})
if(NOT WINCE)
set(KDE4_KHTML_INCLUDES ${CMAKE_SOURCE_DIR}/khtml)
endif(NOT WINCE)
################# configure checks and create the configured files #################
if(WINCE)
set(STATIC_LIBRARY ON)
add_definitions(-DSTATIC_INSTALL_PATH=L\\\"/programme/kde\\\")
endif(WINCE)
if(STATIC_LIBRARY)
set(LIBRARY_TYPE STATIC)
add_definitions(-DKDELIBS_STATIC_LIBS)
message(STATUS "Building kdelibs as static libraries")
else(STATIC_LIBRARY)
set(LIBRARY_TYPE SHARED)
endif(STATIC_LIBRARY)
# ACL stuff (used in kio/ and kioslaves/)
macro_optional_find_package(ACL)
macro_bool_to_01(ACL_FOUND HAVE_LIBACL HAVE_POSIX_ACL)
macro_log_feature(ACL_FOUND "LibACL" "Support for manipulating access control lists" "ftp://oss.sgi.com/projects/xfs/cmd_tars" FALSE "" "STRONGLY RECOMMENDED")
configure_file(config-acl.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-acl.h )
include(ConfigureChecks.cmake)
# Actually nepomuk is not optional, without it other KDE modules don't build,
# so this must be fixed. Alex
if(Soprano_FOUND AND SHAREDDESKTOPONTOLOGIES_FOUND)
set(HAVE_NEPOMUK true)
include(SopranoAddOntology)
add_subdirectory(nepomuk)
endif(Soprano_FOUND AND SHAREDDESKTOPONTOLOGIES_FOUND)
# now create config headers
configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h )
configure_file(config-prefix.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-prefix.h )
configure_file(config-compiler.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-compiler.h )
configure_file(config-pty.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-pty.h )
configure_file(config-nepomuk.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-nepomuk.h )
configure_file(kdemacros.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/kdemacros.h )
# these two calls here should go somewhere else, Alex
check_library_exists(nsl gethostbyname "" HAVE_NSL_LIBRARY)
check_library_exists(socket connect "" HAVE_SOCKET_LIBRARY)
################# list the subdirectories #################
add_subdirectory( cmake )
add_subdirectory( kdecore )
add_subdirectory( kdeui )
if (UNIX)
add_subdirectory( kpty )
add_subdirectory( kdesu )
endif (UNIX)
if(NOT WINCE)
add_subdirectory( kjs )
add_subdirectory( kjsembed )
endif(NOT WINCE)
add_subdirectory( kio )
add_subdirectory( solid )
add_subdirectory( kded )
if (QT_QT3SUPPORT_FOUND)
add_subdirectory( kde3support )
endif (QT_QT3SUPPORT_FOUND)
add_subdirectory( kfile )
add_subdirectory( kconf_update )
if(NOT WINCE)
add_subdirectory( kdoctools )
endif(NOT WINCE)
add_subdirectory( kioslave )
add_subdirectory( knewstuff )
add_subdirectory( kparts )
add_subdirectory( kutils )
add_subdirectory( licenses )
add_subdirectory( mimetypes )
add_subdirectory( kinit )
add_subdirectory( threadweaver )
add_subdirectory( sonnet )
if(NOT WINCE)
add_subdirectory( khtml )
endif(NOT WINCE)
add_subdirectory( interfaces )
#if ( NOT CMAKE_CROSSCOMPILING AND QT_QTDESIGNER_FOUND )
add_subdirectory( kdewidgets )
#endif ( NOT CMAKE_CROSSCOMPILING AND QT_QTDESIGNER_FOUND )
add_subdirectory( knotify )
if(NOT WINCE)
add_subdirectory( kimgio )
endif(NOT WINCE)
add_subdirectory( dnssd )
add_subdirectory( kross )
add_subdirectory( security )
if(NOT WINCE)
add_subdirectory( plasma )
endif(NOT WINCE)
add_subdirectory( kunitconversion )
add_subdirectory( kdewebkit )
add_subdirectory( includes )
add_subdirectory( experimental )
macro_optional_add_subdirectory( doc )
################# write dependency file which will be installed #################
# Used in configure_file() and install(EXPORT)
set(KDE4_TARGET_PREFIX KDE4__)
include(CreateKDELibsDependenciesFile.cmake)
################# install files #################
install( FILES ${CMAKE_CURRENT_BINARY_DIR}/kdemacros.h DESTINATION ${INCLUDE_INSTALL_DIR} )
install( FILES ${CMAKE_CURRENT_BINARY_DIR}/config-nepomuk.h DESTINATION ${INCLUDE_INSTALL_DIR} )
install( FILES ${CMAKE_CURRENT_BINARY_DIR}/KDELibsDependencies.cmake DESTINATION ${DATA_INSTALL_DIR}/cmake/modules)
install( FILES ${CMAKE_CURRENT_BINARY_DIR}/KDEPlatformProfile.cmake DESTINATION ${DATA_INSTALL_DIR}/cmake/modules)
# run a script before installing the exports files which deletes previously installed
# configuration specific export files KDELibs4(Library|Tools)Targets-<config>.cmake
# if the main exports file KDELibs4(Library|Tools)Targets.cmake has changed. This makes sure
# that this main file doesn't include older and different configuration specific exports files,
# which might have a different set of targets or targets with different names.
# The code for installing the exports files will soon go into a macro. Alex
install(CODE "set(EXPORT_FILES KDELibs4LibraryTargets.cmake KDELibs4ToolsTargets.cmake)"
CODE "set(EXPORT_INSTALL_DIR \"${DATA_INSTALL_DIR}/cmake/modules\")"
SCRIPT "${CMAKE_SOURCE_DIR}/cmake/modules/check_installed_exports_file.cmake" )
install( EXPORT kdelibsLibraryTargets DESTINATION ${DATA_INSTALL_DIR}/cmake/modules NAMESPACE ${KDE4_TARGET_PREFIX} FILE KDELibs4LibraryTargets.cmake )
install( EXPORT kdelibsToolsTargets DESTINATION ${DATA_INSTALL_DIR}/cmake/modules NAMESPACE ${KDE4_TARGET_PREFIX} FILE KDELibs4ToolsTargets.cmake )
# the following will be the correct locations once cmake has the improved FIND_PACKAGE()
# install( FILES ${CMAKE_CURRENT_BINARY_DIR}/KDELibsDependencies.cmake DESTINATION ${PLUGIN_INSTALL_DIR}/cmake RENAME KDE4Config.cmake)
macro_display_feature_log()
diff --git a/kdecore/io/ktar.cpp b/kdecore/io/ktar.cpp
index 9c3a010057..139dcfa3f1 100644
--- a/kdecore/io/ktar.cpp
+++ b/kdecore/io/ktar.cpp
@@ -1,847 +1,862 @@
/* This file is part of the KDE libraries
Copyright (C) 2000 David Faure <faure@kde.org>
Copyright (C) 2003 Leo Savernik <l.savernik@aon.at>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "ktar.h"
#include <stdlib.h> // strtol
#include <time.h> // time()
#include <assert.h>
#include <QtCore/QDir>
#include <QtCore/QFile>
#include <kdebug.h>
#include <kmimetype.h>
#include <ktemporaryfile.h>
#include <kfilterdev.h>
#include <kfilterbase.h>
////////////////////////////////////////////////////////////////////////
/////////////////////////// KTar ///////////////////////////////////
////////////////////////////////////////////////////////////////////////
// Mime types of known filters
static const char application_gzip[] = "application/x-gzip";
static const char application_bzip[] = "application/x-bzip";
static const char application_lzma[] = "application/x-lzma";
static const char application_xz[] = "application/x-xz";
static const char application_zip[] = "application/zip";
class KTar::KTarPrivate
{
public:
KTarPrivate(KTar *parent)
: q(parent),
tarEnd( 0 ),
tmpFile( 0 )
{
}
KTar *q;
QStringList dirList;
qint64 tarEnd;
KTemporaryFile* tmpFile;
QString mimetype;
QByteArray origFileName;
bool fillTempFile(const QString & fileName);
bool writeBackTempFile( const QString & fileName );
void fillBuffer( char * buffer, const char * mode, qint64 size, time_t mtime,
char typeflag, const char * uname, const char * gname );
void writeLonglink(char *buffer, const QByteArray &name, char typeflag,
const char *uname, const char *gname);
qint64 readRawHeader(char *buffer);
bool readLonglink(char *buffer, QByteArray &longlink);
qint64 readHeader(char *buffer, QString &name, QString &symlink);
};
KTar::KTar( const QString& fileName, const QString & _mimetype )
: KArchive( fileName ), d(new KTarPrivate(this))
{
d->mimetype = _mimetype;
}
KTar::KTar( QIODevice * dev )
: KArchive( dev ), d(new KTarPrivate(this))
{
Q_ASSERT( dev );
}
// Only called when a filename was given
bool KTar::createDevice(QIODevice::OpenMode mode)
{
if (d->mimetype.isEmpty()) {
// Find out mimetype manually
KMimeType::Ptr mime;
if (mode != QIODevice::WriteOnly && QFile::exists(fileName())) {
// Give priority to file contents: if someone renames a .tar.bz2 to .tar.gz,
// we can still do the right thing here.
mime = KMimeType::findByFileContent(fileName());
if (mime == KMimeType::defaultMimeTypePtr()) {
// Unable to determine mimetype from contents, get it from file name
mime = KMimeType::findByPath(fileName(), 0, true);
}
} else {
mime = KMimeType::findByPath(fileName(), 0, true);
}
//kDebug(7041) << mode << mime->name();
if (mime->is(QString::fromLatin1("application/x-compressed-tar")) || mime->is(QString::fromLatin1(application_gzip))) {
// gzipped tar file (with possibly invalid file name), ask for gzip filter
d->mimetype = QString::fromLatin1(application_gzip);
} else if (mime->is(QString::fromLatin1("application/x-bzip-compressed-tar")) || mime->is(QString::fromLatin1(application_bzip))) {
// bzipped2 tar file (with possibly invalid file name), ask for bz2 filter
d->mimetype = QString::fromLatin1(application_bzip);
} else if (mime->is(QString::fromLatin1("application/x-lzma-compressed-tar")) || mime->is(QString::fromLatin1(application_lzma))) {
// lzma compressed tar file (with possibly invalid file name), ask for xz filter
d->mimetype = QString::fromLatin1(application_lzma);
} else if (mime->is(QString::fromLatin1("application/x-xz-compressed-tar")) || mime->is(QString::fromLatin1(application_xz))) {
// xz compressed tar file (with possibly invalid name), ask for xz filter
d->mimetype = QString::fromLatin1(application_xz);
}
}
if (d->mimetype == QLatin1String("application/x-tar")) {
return KArchive::createDevice(mode);
} else if (mode == QIODevice::WriteOnly) {
if (!KArchive::createDevice(mode))
return false;
if (!d->mimetype.isEmpty()) {
// Create a compression filter on top of the KSaveFile device that KArchive created.
//kDebug(7041) << "creating KFilterDev for" << d->mimetype;
QIODevice *filterDev = KFilterDev::device(device(), d->mimetype);
Q_ASSERT(filterDev);
setDevice(filterDev);
}
return true;
} else {
// The compression filters are very slow with random access.
// So instead of applying the filter to the device,
// the file is completely extracted instead,
// and we work on the extracted tar file.
// This improves the extraction speed by the tar ioslave dramatically,
// if the archive file contains many files.
// This is because the tar ioslave extracts one file after the other and normally
// has to walk through the decompression filter each time.
// Which is in fact nearly as slow as a complete decompression for each file.
Q_ASSERT(!d->tmpFile);
d->tmpFile = new KTemporaryFile();
d->tmpFile->setPrefix(QLatin1String("ktar-"));
d->tmpFile->setSuffix(QLatin1String(".tar"));
d->tmpFile->open();
//kDebug(7041) << "creating tempfile:" << d->tmpFile->fileName();
setDevice(d->tmpFile);
return true;
}
}
KTar::~KTar()
{
// mjarrett: Closes to prevent ~KArchive from aborting w/o device
if( isOpen() )
close();
delete d->tmpFile;
delete d;
}
void KTar::setOrigFileName( const QByteArray & fileName ) {
if ( !isOpen() || !(mode() & QIODevice::WriteOnly) )
{
kWarning(7041) << "KTar::setOrigFileName: File must be opened for writing first.\n";
return;
}
d->origFileName = fileName;
}
qint64 KTar::KTarPrivate::readRawHeader( char *buffer ) {
// Read header
qint64 n = q->device()->read( buffer, 0x200 );
- if ( n == 0x200 && buffer[0] != 0 ) {
+ // we need to test if there is a prefix value because the file name can be null
+ // and the prefix can have a value and in this case we don't reset n.
+ if ( n == 0x200 && (buffer[0] != 0 || buffer[0x159] != 0) ) {
// Make sure this is actually a tar header
if (strncmp(buffer + 257, "ustar", 5)) {
// The magic isn't there (broken/old tars), but maybe a correct checksum?
int check = 0;
for( uint j = 0; j < 0x200; ++j )
check += buffer[j];
// adjust checksum to count the checksum fields as blanks
for( uint j = 0; j < 8 /*size of the checksum field including the \0 and the space*/; j++ )
check -= buffer[148 + j];
check += 8 * ' ';
QByteArray s = QByteArray::number( check, 8 ); // octal
// only compare those of the 6 checksum digits that mean something,
// because the other digits are filled with all sorts of different chars by different tars ...
// Some tars right-justify the checksum so it could start in one of three places - we have to check each.
if( strncmp( buffer + 148 + 6 - s.length(), s.data(), s.length() )
&& strncmp( buffer + 148 + 7 - s.length(), s.data(), s.length() )
&& strncmp( buffer + 148 + 8 - s.length(), s.data(), s.length() ) ) {
kWarning(7041) << "KTar: invalid TAR file. Header is:" << QByteArray( buffer+257, 5 )
<< "instead of ustar. Reading from wrong pos in file?"
<< "checksum=" << QByteArray( buffer + 148 + 6 - s.length(), s.length() );
return -1;
}
}/*end if*/
} else {
// reset to 0 if 0x200 because logical end of archive has been reached
if (n == 0x200) n = 0;
}/*end if*/
return n;
}
bool KTar::KTarPrivate::readLonglink(char *buffer,QByteArray &longlink) {
qint64 n = 0;
//kDebug() << "reading longlink from pos " << device()->pos();
QIODevice *dev = q->device();
// read size of longlink from size field in header
// size is in bytes including the trailing null (which we ignore)
qint64 size = QByteArray( buffer + 0x7c, 12 ).trimmed().toLongLong( 0, 8 /*octal*/ );
size--; // ignore trailing null
longlink.resize(size);
qint64 offset = 0;
while (size > 0) {
int chunksize = qMin(size, 0x200LL);
n = dev->read( longlink.data() + offset, chunksize );
if (n == -1) return false;
size -= chunksize;
offset += 0x200;
}/*wend*/
// jump over the rest
const int skip = 0x200 - (n % 0x200);
- if (skip < 0x200) {
+ if (skip <= 0x200) {
if (dev->read(buffer,skip) != skip)
return false;
}
return true;
}
qint64 KTar::KTarPrivate::readHeader( char *buffer, QString &name, QString &symlink ) {
name.truncate(0);
symlink.truncate(0);
while (true) {
qint64 n = readRawHeader(buffer);
if (n != 0x200) return n;
// is it a longlink?
if (strcmp(buffer,"././@LongLink") == 0) {
char typeflag = buffer[0x9c];
QByteArray longlink;
readLonglink(buffer,longlink);
switch (typeflag) {
case 'L': name = QFile::decodeName(longlink); break;
case 'K': symlink = QFile::decodeName(longlink); break;
}/*end switch*/
} else {
break;
}/*end if*/
}/*wend*/
// if not result of longlink, read names directly from the header
if (name.isEmpty())
// there are names that are exactly 100 bytes long
// and neither longlink nor \0 terminated (bug:101472)
name = QFile::decodeName(QByteArray(buffer, 100));
if (symlink.isEmpty())
symlink = QFile::decodeName(QByteArray(buffer + 0x9d /*?*/, 100));
return 0x200;
}
/*
* If we have created a temporary file, we have
* to decompress the original file now and write
* the contents to the temporary file.
*/
bool KTar::KTarPrivate::fillTempFile( const QString & fileName) {
if ( ! tmpFile )
return true;
//kDebug(7041) << "filling tmpFile of mimetype" << mimetype;
bool forced = false;
if ( QLatin1String(application_gzip) == mimetype || QLatin1String(application_bzip) == mimetype )
forced = true;
QIODevice *filterDev = KFilterDev::deviceForFile( fileName, mimetype, forced );
if( filterDev ) {
QFile* file = tmpFile;
Q_ASSERT(file->isOpen());
Q_ASSERT(file->openMode() & QIODevice::WriteOnly);
file->seek(0);
QByteArray buffer;
buffer.resize(8*1024);
if ( ! filterDev->open( QIODevice::ReadOnly ) )
{
delete filterDev;
return false;
}
qint64 len = -1;
while ( !filterDev->atEnd() && len != 0 ) {
len = filterDev->read(buffer.data(),buffer.size());
if ( len < 0 ) { // corrupted archive
delete filterDev;
return false;
}
if ( file->write(buffer.data(), len) != len ) { // disk full
delete filterDev;
return false;
}
}
filterDev->close();
delete filterDev;
file->flush();
file->seek(0);
Q_ASSERT(file->isOpen());
Q_ASSERT(file->openMode() & QIODevice::ReadOnly);
} else {
kDebug(7041) << "no filterdevice found!";
}
//kDebug( 7041 ) << "filling tmpFile finished.";
return true;
}
bool KTar::openArchive( QIODevice::OpenMode mode ) {
if ( !(mode & QIODevice::ReadOnly) )
return true;
if ( !d->fillTempFile( fileName() ) )
return false;
// We'll use the permission and user/group of d->rootDir
// for any directory we emulate (see findOrCreate)
//struct stat buf;
//stat( fileName(), &buf );
d->dirList.clear();
QIODevice* dev = device();
if ( !dev )
return false;
// read dir information
char buffer[ 0x200 ];
bool ende = false;
do
{
QString name;
QString symlink;
// Read header
qint64 n = d->readHeader( buffer, name, symlink );
if (n < 0) return false;
if (n == 0x200)
{
bool isdir = false;
+ bool isGlobalHeader = false;
if ( name.endsWith( QLatin1Char( '/' ) ) )
{
isdir = true;
name.truncate( name.length() - 1 );
}
+ QByteArray prefix = QByteArray(buffer + 0x159, 155);
+ if (prefix[0] != '\0') {
+ name = (QString::fromLatin1(prefix.constData()) + QLatin1Char('/') + name);
+ }
+
int pos = name.lastIndexOf( QLatin1Char('/') );
QString nm = ( pos == -1 ) ? name : name.mid( pos + 1 );
// read access
buffer[ 0x6b ] = 0;
char *dummy;
const char* p = buffer + 0x64;
while( *p == ' ' ) ++p;
int access = (int)strtol( p, &dummy, 8 );
// read user and group
QString user = QString::fromLocal8Bit( buffer + 0x109 );
QString group = QString::fromLocal8Bit( buffer + 0x129 );
// read time
buffer[ 0x93 ] = 0;
p = buffer + 0x88;
while( *p == ' ' ) ++p;
int time = (int)strtol( p, &dummy, 8 );
// read type flag
char typeflag = buffer[ 0x9c ];
// '0' for files, '1' hard link, '2' symlink, '5' for directory
// (and 'L' for longlink fileNames, 'K' for longlink symlink targets)
- // and 'D' for GNU tar extension DUMPDIR
+ // 'D' for GNU tar extension DUMPDIR, 'x' for Extended header referring
+ // to the next file in the archive and 'g' for Global extended header
+ if ( typeflag == 'g' )
+ isGlobalHeader = true;
+
if ( typeflag == '5' )
isdir = true;
bool isDumpDir = false;
if ( typeflag == 'D' )
{
isdir = false;
isDumpDir = true;
}
//kDebug(7041) << "typeflag=" << typeflag << " islink=" << ( typeflag == '1' || typeflag == '2' );
if (isdir)
access |= S_IFDIR; // f*cking broken tar files
KArchiveEntry* e;
if ( isdir )
{
//kDebug(7041) << "directory" << nm;
e = new KArchiveDirectory( this, nm, access, time, user, group, symlink );
}
else
{
// read size
QByteArray sizeBuffer( buffer + 0x7c, 12 );
qint64 size = sizeBuffer.trimmed().toLongLong( 0, 8 /*octal*/ );
//kDebug(7041) << "sizeBuffer='" << sizeBuffer << "' -> size=" << size;
// for isDumpDir we will skip the additional info about that dirs contents
if ( isDumpDir )
{
//kDebug(7041) << nm << "isDumpDir";
e = new KArchiveDirectory( this, nm, access, time, user, group, symlink );
}
else
{
// Let's hack around hard links. Our classes don't support that, so make them symlinks
if ( typeflag == '1' )
{
kDebug(7041) << "Hard link, setting size to 0 instead of" << size;
size = 0; // no contents
}
//kDebug(7041) << "file" << nm << "size=" << size;
e = new KArchiveFile( this, nm, access, time, user, group, symlink,
dev->pos(), size );
}
// Skip contents + align bytes
qint64 rest = size % 0x200;
qint64 skip = size + (rest ? 0x200 - rest : 0);
//kDebug(7041) << "pos()=" << dev->pos() << "rest=" << rest << "skipping" << skip;
if (! dev->seek( dev->pos() + skip ) )
kWarning(7041) << "skipping" << skip << "failed";
}
+ if (isGlobalHeader)
+ continue;
+
if ( pos == -1 )
{
if (nm == QLatin1String(".")) { // special case
Q_ASSERT( isdir );
if ( isdir )
setRootDir( static_cast<KArchiveDirectory *>( e ) );
}
else
rootDir()->addEntry( e );
}
else
{
// In some tar files we can find dir/./file => call cleanPath
QString path = QDir::cleanPath( name.left( pos ) );
// Ensure container directory exists, create otherwise
KArchiveDirectory * d = findOrCreate( path );
d->addEntry( e );
}
}
else
{
//qDebug("Terminating. Read %d bytes, first one is %d", n, buffer[0]);
d->tarEnd = dev->pos() - n; // Remember end of archive
ende = true;
}
} while( !ende );
return true;
}
/*
* Writes back the changes of the temporary file
* to the original file.
* Must only be called if in write mode, not in read mode
*/
bool KTar::KTarPrivate::writeBackTempFile( const QString & fileName )
{
if ( !tmpFile )
return true;
//kDebug(7041) << "Write temporary file to compressed file" << fileName << mimetype;
bool forced = false;
if (QLatin1String(application_gzip) == mimetype || QLatin1String(application_bzip) == mimetype ||
QLatin1String(application_lzma) == mimetype || QLatin1String(application_xz) == mimetype)
forced = true;
// #### TODO this should use KSaveFile to avoid problems on disk full
// (KArchive uses KSaveFile by default, but the temp-uncompressed-file trick
// circumvents that).
QIODevice *dev = KFilterDev::deviceForFile( fileName, mimetype, forced );
if( dev ) {
QFile* file = tmpFile;
if ( !dev->open(QIODevice::WriteOnly) )
{
file->close();
delete dev;
return false;
}
if ( forced )
static_cast<KFilterDev *>(dev)->setOrigFileName( origFileName );
file->seek(0);
QByteArray buffer;
buffer.resize(8*1024);
qint64 len;
while ( !file->atEnd()) {
len = file->read(buffer.data(), buffer.size());
dev->write(buffer.data(),len); // TODO error checking
}
file->close();
dev->close();
delete dev;
}
//kDebug(7041) << "Write temporary file to compressed file done.";
return true;
}
bool KTar::closeArchive() {
d->dirList.clear();
bool ok = true;
// If we are in readwrite mode and had created
// a temporary tar file, we have to write
// back the changes to the original file
if (d->tmpFile && (mode() & QIODevice::WriteOnly)) {
ok = d->writeBackTempFile( fileName() );
delete d->tmpFile;
d->tmpFile = 0;
setDevice(0);
}
return ok;
}
bool KTar::doFinishWriting( qint64 size ) {
// Write alignment
int rest = size % 0x200;
if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
d->tarEnd = device()->pos() + (rest ? 0x200 - rest : 0); // Record our new end of archive
if ( rest )
{
char buffer[ 0x201 ];
for( uint i = 0; i < 0x200; ++i )
buffer[i] = 0;
qint64 nwritten = device()->write( buffer, 0x200 - rest );
return nwritten == 0x200 - rest;
}
return true;
}
/*** Some help from the tar sources
struct posix_header
{ byte offset
char name[100]; * 0 * 0x0
char mode[8]; * 100 * 0x64
char uid[8]; * 108 * 0x6c
char gid[8]; * 116 * 0x74
char size[12]; * 124 * 0x7c
char mtime[12]; * 136 * 0x88
char chksum[8]; * 148 * 0x94
char typeflag; * 156 * 0x9c
char linkname[100]; * 157 * 0x9d
char magic[6]; * 257 * 0x101
char version[2]; * 263 * 0x107
char uname[32]; * 265 * 0x109
char gname[32]; * 297 * 0x129
char devmajor[8]; * 329 * 0x149
char devminor[8]; * 337 * ...
char prefix[155]; * 345 *
* 500 *
};
*/
void KTar::KTarPrivate::fillBuffer( char * buffer,
const char * mode, qint64 size, time_t mtime, char typeflag,
const char * uname, const char * gname ) {
// mode (as in stpos())
assert( strlen(mode) == 6 );
memcpy( buffer+0x64, mode, 6 );
buffer[ 0x6a ] = ' ';
buffer[ 0x6b ] = '\0';
// dummy uid
strcpy( buffer + 0x6c, " 765 ");
// dummy gid
strcpy( buffer + 0x74, " 144 ");
// size
QByteArray s = QByteArray::number( size, 8 ); // octal
s = s.rightJustified( 11, '0' );
memcpy( buffer + 0x7c, s.data(), 11 );
buffer[ 0x87 ] = ' '; // space-terminate (no null after)
// modification time
s = QByteArray::number( static_cast<qulonglong>(mtime), 8 ); // octal
s = s.rightJustified( 11, '0' );
memcpy( buffer + 0x88, s.data(), 11 );
buffer[ 0x93 ] = ' '; // space-terminate (no null after) -- well current tar writes a null byte
// spaces, replaced by the check sum later
buffer[ 0x94 ] = 0x20;
buffer[ 0x95 ] = 0x20;
buffer[ 0x96 ] = 0x20;
buffer[ 0x97 ] = 0x20;
buffer[ 0x98 ] = 0x20;
buffer[ 0x99 ] = 0x20;
/* From the tar sources :
Fill in the checksum field. It's formatted differently from the
other fields: it has [6] digits, a null, then a space -- rather than
digits, a space, then a null. */
buffer[ 0x9a ] = '\0';
buffer[ 0x9b ] = ' ';
// type flag (dir, file, link)
buffer[ 0x9c ] = typeflag;
// magic + version
strcpy( buffer + 0x101, "ustar");
strcpy( buffer + 0x107, "00" );
// user
strcpy( buffer + 0x109, uname );
// group
strcpy( buffer + 0x129, gname );
// Header check sum
int check = 32;
for( uint j = 0; j < 0x200; ++j )
check += buffer[j];
s = QByteArray::number( check, 8 ); // octal
s = s.rightJustified( 6, '0' );
memcpy( buffer + 0x94, s.constData(), 6 );
}
void KTar::KTarPrivate::writeLonglink(char *buffer, const QByteArray &name, char typeflag,
const char *uname, const char *gname) {
strcpy( buffer, "././@LongLink" );
qint64 namelen = name.length() + 1;
fillBuffer( buffer, " 0", namelen, 0, typeflag, uname, gname );
q->device()->write( buffer, 0x200 ); // TODO error checking
qint64 offset = 0;
while (namelen > 0) {
int chunksize = qMin(namelen, 0x200LL);
memcpy(buffer, name.data()+offset, chunksize);
// write long name
q->device()->write( buffer, 0x200 ); // TODO error checking
// not even needed to reclear the buffer, tar doesn't do it
namelen -= chunksize;
offset += 0x200;
}/*wend*/
}
bool KTar::doPrepareWriting(const QString &name, const QString &user,
const QString &group, qint64 size, mode_t perm,
time_t /*atime*/, time_t mtime, time_t /*ctime*/) {
if ( !isOpen() )
{
kWarning(7041) << "You must open the tar file before writing to it\n";
return false;
}
if ( !(mode() & QIODevice::WriteOnly) )
{
kWarning(7041) << "You must open the tar file for writing\n";
return false;
}
// In some tar files we can find dir/./file => call cleanPath
QString fileName ( QDir::cleanPath( name ) );
/*
// Create toplevel dirs
// Commented out by David since it's not necessary, and if anybody thinks it is,
// he needs to implement a findOrCreate equivalent in writeDir.
// But as KTar and the "tar" program both handle tar files without
// dir entries, there's really no need for that
QString tmp ( fileName );
int i = tmp.lastIndexOf( '/' );
if ( i != -1 )
{
QString d = tmp.left( i + 1 ); // contains trailing slash
if ( !m_dirList.contains( d ) )
{
tmp = tmp.mid( i + 1 );
writeDir( d, user, group ); // WARNING : this one doesn't create its toplevel dirs
}
}
*/
char buffer[ 0x201 ];
memset( buffer, 0, 0x200 );
if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
// provide converted stuff we need later on
const QByteArray encodedFileName = QFile::encodeName(fileName);
const QByteArray uname = user.toLocal8Bit();
const QByteArray gname = group.toLocal8Bit();
// If more than 100 chars, we need to use the LongLink trick
if ( fileName.length() > 99 )
d->writeLonglink(buffer,encodedFileName,'L',uname,gname);
// Write (potentially truncated) name
strncpy( buffer, encodedFileName, 99 );
buffer[99] = 0;
// zero out the rest (except for what gets filled anyways)
memset(buffer+0x9d, 0, 0x200 - 0x9d);
QByteArray permstr = QByteArray::number( (unsigned int)perm, 8 );
permstr = permstr.rightJustified(6, '0');
d->fillBuffer(buffer, permstr, size, mtime, 0x30, uname, gname);
// Write header
return device()->write( buffer, 0x200 ) == 0x200;
}
bool KTar::doWriteDir(const QString &name, const QString &user,
const QString &group, mode_t perm,
time_t /*atime*/, time_t mtime, time_t /*ctime*/) {
if ( !isOpen() )
{
kWarning(7041) << "You must open the tar file before writing to it\n";
return false;
}
if ( !(mode() & QIODevice::WriteOnly) )
{
kWarning(7041) << "You must open the tar file for writing\n";
return false;
}
// In some tar files we can find dir/./ => call cleanPath
QString dirName ( QDir::cleanPath( name ) );
// Need trailing '/'
if ( !dirName.endsWith( QLatin1Char( '/' ) ) )
dirName += QLatin1Char( '/' );
if ( d->dirList.contains( dirName ) )
return true; // already there
char buffer[ 0x201 ];
memset( buffer, 0, 0x200 );
if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
// provide converted stuff we need lateron
QByteArray encodedDirname = QFile::encodeName(dirName);
QByteArray uname = user.toLocal8Bit();
QByteArray gname = group.toLocal8Bit();
// If more than 100 chars, we need to use the LongLink trick
if ( dirName.length() > 99 )
d->writeLonglink(buffer,encodedDirname,'L',uname,gname);
// Write (potentially truncated) name
strncpy( buffer, encodedDirname, 99 );
buffer[99] = 0;
// zero out the rest (except for what gets filled anyways)
memset(buffer+0x9d, 0, 0x200 - 0x9d);
QByteArray permstr = QByteArray::number( (unsigned int)perm, 8 );
permstr = permstr.rightJustified(6, ' ');
d->fillBuffer( buffer, permstr, 0, mtime, 0x35, uname, gname);
// Write header
device()->write( buffer, 0x200 );
if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
d->tarEnd = device()->pos();
d->dirList.append( dirName ); // contains trailing slash
return true; // TODO if wanted, better error control
}
bool KTar::doWriteSymLink(const QString &name, const QString &target,
const QString &user, const QString &group,
mode_t perm, time_t /*atime*/, time_t mtime, time_t /*ctime*/) {
if ( !isOpen() )
{
kWarning(7041) << "You must open the tar file before writing to it\n";
return false;
}
if ( !(mode() & QIODevice::WriteOnly) )
{
kWarning(7041) << "You must open the tar file for writing\n";
return false;
}
// In some tar files we can find dir/./file => call cleanPath
QString fileName ( QDir::cleanPath( name ) );
char buffer[ 0x201 ];
memset( buffer, 0, 0x200 );
if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
device()->seek(d->tarEnd); // Go to end of archive as might have moved with a read
// provide converted stuff we need lateron
QByteArray encodedFileName = QFile::encodeName(fileName);
QByteArray encodedTarget = QFile::encodeName(target);
QByteArray uname = user.toLocal8Bit();
QByteArray gname = group.toLocal8Bit();
// If more than 100 chars, we need to use the LongLink trick
if (target.length() > 99)
d->writeLonglink(buffer,encodedTarget,'K',uname,gname);
if ( fileName.length() > 99 )
d->writeLonglink(buffer,encodedFileName,'L',uname,gname);
// Write (potentially truncated) name
strncpy( buffer, encodedFileName, 99 );
buffer[99] = 0;
// Write (potentially truncated) symlink target
strncpy(buffer+0x9d, encodedTarget, 99);
buffer[0x9d+99] = 0;
// zero out the rest
memset(buffer+0x9d+100, 0, 0x200 - 100 - 0x9d);
QByteArray permstr = QByteArray::number( (unsigned int)perm, 8 );
permstr = permstr.rightJustified(6, ' ');
d->fillBuffer(buffer, permstr, 0, mtime, 0x32, uname, gname);
// Write header
bool retval = device()->write( buffer, 0x200 ) == 0x200;
if ( ( mode() & QIODevice::ReadWrite ) == QIODevice::ReadWrite )
d->tarEnd = device()->pos();
return retval;
}
void KTar::virtual_hook( int id, void* data ) {
KArchive::virtual_hook( id, data );
}
diff --git a/kdecore/localization/all_languages.desktop b/kdecore/localization/all_languages.desktop
index b48eda31f9..d80055bb3f 100644
--- a/kdecore/localization/all_languages.desktop
+++ b/kdecore/localization/all_languages.desktop
@@ -1,18094 +1,18096 @@
[aa]
Name=Afar
Name[af]=Afar
Name[ar]=عفرية
Name[as]=আফাৰ
Name[ast]=Afar
Name[be]=Афарская
Name[be@latin]=Afarskaja
Name[bg]=Афар
Name[bn]=আফার
Name[bn_IN]=আফার
Name[br]=Afar
Name[bs]=Afarski
Name[ca]=Àfar
Name[ca@valencia]=Àfar
Name[cs]=Afar
Name[csb]=Afarsczi
Name[cy]=Afareg
Name[da]=Afar
Name[de]=Afar
Name[el]=Afar
Name[en_GB]=Afar
Name[eo]=Afara
Name[es]=Afar
Name[et]=Afari
Name[eu]=Afar
Name[fa]=افار
Name[fi]=Afar
Name[fr]=Afar
Name[fy]=Afar
Name[ga]=Afárais
Name[gl]=Afar
Name[gu]=અફર
Name[he]=אפאר
Name[hi]=अफ्र
Name[hne]=अफ्र
Name[hr]=Afarski
Name[hsb]=Afar
Name[hu]=Afar
Name[ia]=Afar
Name[id]=Afar
Name[is]=Afar
Name[it]=Afar
Name[ja]=アファル語
Name[kk]=Афарша
Name[km]=អាហ្វារ
Name[kn]=ಅಫಾರ್
Name[ko]=아파르어
Name[ku]=Afar
Name[lb]=Afar
Name[lt]=Afarų
Name[lv]=Afāru
Name[mai]=अफ्र
Name[mk]=Афар
Name[ml]=അഭര്‍
Name[mr]=अफार
Name[ms]=Afar
Name[nb]=Afar
Name[nds]=Afaarsch
Name[ne]=अफार
Name[nl]=Afar
Name[nn]=Afar
Name[oc]=Afar
Name[or]=Afar
Name[pa]=ਅਫਾਰ
Name[pl]=Afarski
Name[ps]=افار
Name[pt]=Afar
Name[pt_BR]=Afar
Name[ro]=Afară
Name[ru]=Афарский
Name[se]=Afárgiella
Name[si]=අෆාර්
Name[sk]=Afarčina
Name[sl]=afarsko
Name[sq]=Afar
Name[sr]=афарски
Name[sr@ijekavian]=афарски
Name[sr@ijekavianlatin]=afarski
Name[sr@latin]=afarski
Name[sv]=Afar
Name[ta]=அபார்
Name[te]=ఎఫార్
Name[tg]=Афарӣ
Name[th]=ภาษาอะฟาร์
Name[tr]=Afar
Name[tt]=Афарча
Name[ug]=ئافارچە
Name[uk]=Афар
Name[uz]=Afar
Name[uz@cyrillic]=Афар
Name[vi]=Afar
Name[wa]=Afar
Name[xh]=Afar
Name[x-test]=xxAfarxx
Name[zh_CN]=阿法尔语
Name[zh_HK]=阿發爾
Name[zh_TW]=阿發爾
[ab]
Name=Abkhazian
Name[af]=Abkhazian
Name[ar]=أبخازية
Name[as]=আব্‌খাজিয়ান
Name[ast]=Abxazianu
Name[be]=Абхазская
Name[be@latin]=Abchazkaja
Name[bg]=Абхазки
Name[bn]=আবখাজিয়ান
Name[bn_IN]=আভকাজিয়ান
Name[br]=Abkhazieg
Name[bs]=Abkhazijski
Name[ca]=Abkhaz
Name[ca@valencia]=Abkhaz
Name[cs]=Abcházský
Name[csb]=Abchasczi
Name[cy]=Abkhazeg
Name[da]=Abkhaziansk
Name[de]=Abhasisch
Name[el]=Αμπχαζιανά
Name[en_GB]=Abkhazian
Name[eo]=Abĥaza
Name[es]=Abjaso
Name[et]=Abhaasia
Name[eu]=Abkhazian
Name[fa]=ابخازیان
Name[fi]=Abhaasi
Name[fr]=Abkhaze
Name[fy]=Abkhasysk
Name[ga]=Abcáisis
Name[gl]=Abxasio
Name[gu]=અબ્ખાઝીઅન
Name[he]=אבחזית
Name[hi]=अबकाजियन
Name[hne]=अबकाजियन
Name[hr]=Abhaški
Name[hsb]=Abchazisce
Name[hu]=Abház
Name[ia]=Abkhaziano
Name[id]=Abkhazian
Name[is]=Abkhazian
Name[it]=Abcaso
Name[ja]=アブハズ語
Name[kk]=Ахбазша
Name[km]=អាប់ខាហ្ស៊ាន
Name[kn]=ಅಬ್ಕಾಜಿಯನ್
Name[ko]=아브하지아어
Name[ku]=Abxazî
Name[lb]=Abhasesch
Name[lt]=Abhazų
Name[lv]=Abhāzu
Name[mai]=अबकाजियन
Name[mk]=Абхаски
Name[ml]=അബ്ഖാസിയന്‍
Name[mr]=अब्काजीयन
Name[ms]=Abkhazia
Name[nb]=Abkhasisk
Name[nds]=Abchaassch
Name[ne]=अब्खाजियन
Name[nl]=Abkhazian
Name[nn]=Abkhasisk
Name[oc]=Turc
Name[or]=Abkhazian
Name[pa]=ਅਬਖਾਜ਼ੀਨ
Name[pl]=Abchaski
Name[ps]=ابکازين
Name[pt]=Abkhazian
Name[pt_BR]=Abcázio
Name[ro]=Abhaziană
Name[ru]=Абхазский
Name[se]=Abhásiagiella
Name[si]=අබ්ඛාසියන්
Name[sk]=Abcházština
Name[sl]=abkazijansko
Name[sq]=Abkhaziane
Name[sr]=абхазијски
Name[sr@ijekavian]=абхазијски
Name[sr@ijekavianlatin]=abhazijski
Name[sr@latin]=abhazijski
Name[sv]=Abkhasiska
Name[ta]=அப்காசியன்
Name[te]=అబ్ఖజియన్
Name[tg]=Абхозӣ
Name[th]=ภาษาแอบคาเซียน
Name[tr]=Abkhazian
Name[tt]=Абхазча
Name[ug]=ئابخازچە
Name[uk]=Абхазька
Name[uz]=Abxazcha
Name[uz@cyrillic]=Абхазча
Name[vi]=Abkhazian
Name[wa]=Abxhaze
Name[xh]=Abkhazian
Name[x-test]=xxAbkhazianxx
Name[zh_CN]=阿布哈西亚语
Name[zh_HK]=阿布哈西亞語
Name[zh_TW]=阿布哈西亞語
[ae]
Name=Avestan
Name[af]=Avestan
Name[ar]=أفستانية
Name[as]=আভেষ্টান
Name[ast]=Avestanu
Name[be]=Авестанская
Name[be@latin]=Avestanskaja
Name[bg]=Авестийски
Name[bn]=আভেস্তান
Name[bn_IN]=আভেস্তান
Name[br]=Avestan
Name[bs]=Avesta
Name[ca]=Avèstic
Name[ca@valencia]=Avèstic
Name[cs]=Avestánský
Name[csb]=Awestańsczi (irańsczi)
Name[cy]=Afestaneg
Name[da]=Avestansk
Name[de]=Avestisch
Name[el]=Avestan
Name[en_GB]=Avestan
Name[eo]=Avesta
Name[es]=Avéstico
Name[et]=Vanapärsia
Name[eu]=Avestan
Name[fa]=اوستایی
Name[fi]=Avesta
Name[fr]=Avestan
Name[fy]=Avestaansk
Name[ga]=Aivéistis
Name[gl]=Avestaní
Name[gu]=અવેસ્ટાન
Name[he]=אווסטית
Name[hi]=अवेस्तन
Name[hne]=अवेस्तन
Name[hr]=Avestanski
Name[hsb]=Awestisce
Name[hu]=Avesztáni
Name[ia]=Avestan
Name[id]=Avestan
Name[is]=Avestan
Name[it]=Avestano
Name[ja]=アベスタ語
Name[kk]=Авестша
Name[km]=អាវែស្តង់
Name[kn]=ಅವೆಸ್ತನ್
Name[ko]=아베스탄어
Name[ku]=Avestayî
Name[lb]=Avestan
Name[lt]=Avestan
Name[lv]=Avesta
Name[mai]=अवेस्तन
Name[mk]=Авестан
Name[ml]=അവെസ്തന്‍
Name[mr]=अवेस्तन
Name[ms]=Avestan
Name[nb]=Avestisk
Name[nds]=Avesta
Name[ne]=अभेस्टन
Name[nl]=Avestan
Name[nn]=Avestisk
Name[or]=Avestan
Name[pa]=ਅਵਸਟਅਨ
Name[pl]=Awestański (Irański)
Name[ps]=اوستايي
Name[pt]=Avestan
Name[pt_BR]=Avéstico
Name[ro]=Avestană
Name[ru]=Авестийский
Name[se]=Avestánagiella
Name[si]=අවෙස්ටන්
Name[sk]=Avestčina
Name[sl]=avestansko
Name[sq]=Avestane
Name[sr]=авестански
Name[sr@ijekavian]=авестански
Name[sr@ijekavianlatin]=avestanski
Name[sr@latin]=avestanski
Name[sv]=Avestiska
Name[ta]=அவெஸ்டன்
Name[te]=అవెస్థన్
Name[tg]=Авастоӣ
Name[th]=ภาษาอะเวสแทน
Name[tr]=Avestan
Name[tt]=Авестанча
Name[ug]=ئاۋېستاچە
Name[uk]=Авестан
Name[uz]=Avesta
Name[uz@cyrillic]=Авеста
Name[vi]=Avestan
Name[wa]=Avestan
Name[xh]=Avestan
Name[x-test]=xxAvestanxx
Name[zh_CN]=阿维斯陀语
Name[zh_HK]=阿維斯陀語
Name[zh_TW]=阿維斯陀語
[af]
Name=Afrikaans
Name[af]=Afrikaans
Name[ar]=أفريكانس
Name[as]=আফ্ৰিকান্স
Name[ast]=Afrikaans
Name[be]=Афрыканская
Name[be@latin]=Afrykaans
Name[bg]=Африкаанс
Name[bn]=আফ্রিকান্‌স
Name[bn_IN]=আফ্রিকান্স
Name[br]=Afrikaans
Name[bs]=Afrikaans
Name[ca]=Afrikaans
Name[ca@valencia]=Afrikaans
Name[cs]=Afrikánský
Name[csb]=Afrikanersczi
Name[cy]=Affricaneg
Name[da]=Afrikaans
Name[de]=Afrikaans
Name[el]=Αφρικανικά
Name[en_GB]=Afrikaans
Name[eo]=Afrikansa
Name[es]=Afrikaans
Name[et]=Afrikaani
Name[eu]=Afrikaansa
Name[fa]=آفریکانس
Name[fi]=Afrikaans
Name[fr]=Afrikaans
Name[fy]=Afrikaansk
Name[ga]=Afracáinis
Name[gl]=Africáner
Name[gu]=આફ્રિકાન્સ
Name[he]=אפריקנס
Name[hi]=अफ्रीकी
Name[hne]=अफ्रीकी
Name[hr]=Afrikaans
Name[hsb]=Afrikaans
Name[hu]=Afrikaans
Name[ia]=Afrikaans
Name[id]=Afrikaans
Name[is]=Afrikaans
Name[it]=Afrikaans
Name[ja]=アフリカーンス語
Name[kk]=Африкаанс
Name[km]=អាហ្វ្រីកាអាន
Name[kn]=ಆಫ್ರಿಕಾನಾಸ್
Name[ko]=아프리칸스어
Name[ku]=Afrîkans
Name[lb]=Afrikaans
Name[lt]=Afrikanso
Name[lv]=Afrikandu
Name[mai]=अफ्रीकी
Name[mk]=Африкански
Name[ml]=ആഫ്രികാന്‍സ്
Name[mr]=अफ्रीकी
Name[ms]=Afrika
Name[nb]=Afrikaans
Name[nds]=Afrikaansch
Name[ne]=अफ्रिकी
Name[nl]=Afrikaans
Name[nn]=Afrikaans
Name[oc]=Afrikaans
Name[or]=ଆଫ୍ରିକାନ
Name[pa]=ਅਫਰੀਕੀ
Name[pl]=Afrykanerski
Name[ps]=افريکانس
Name[pt]=Afrikaans
Name[pt_BR]=Africâner
Name[ro]=Africană
Name[ru]=Африкаанс
Name[se]=Afrikánsgiella
Name[si]=ඇෆ්‍රිකාන්ස්
Name[sk]=Afrikánčina
Name[sl]=afrikansko
Name[sq]=Afrikaans
Name[sr]=африканерски
Name[sr@ijekavian]=африканерски
Name[sr@ijekavianlatin]=afrikanerski
Name[sr@latin]=afrikanerski
Name[sv]=Afrikaans
Name[ta]=ஆப்பிரிகான்ஸ்
Name[te]=ఆఫ్రికాన్స్
Name[tg]=Африкоӣ
Name[th]=ภาษาแอฟริคานส์
Name[tr]=Afrika Dili
Name[tt]=Африкаанс
Name[ug]=ئافرىكانچە
Name[uk]=Африкаанс
Name[uz]=Afrikancha
Name[uz@cyrillic]=Африканча
Name[vi]=Afrikaans
Name[wa]=Afrikaans
Name[xh]=Isibhulu
Name[x-test]=xxAfrikaansxx
Name[zh_CN]=南非荷兰语
Name[zh_HK]=南非荷蘭語
Name[zh_TW]=南非荷蘭語
[am]
Name=Amharic
Name[af]=Amharic
Name[ar]=أمهرية
Name[as]=আম্‌হাৰিক
Name[ast]=Amháricu
Name[be]=Амхарская
Name[be@latin]=Amharskaja
Name[bg]=Амхарски
Name[bn]=আমহারিক
Name[bn_IN]=আমহারিক
Name[br]=Amhareg
Name[bs]=Amharski
Name[ca]=Amhàric
Name[ca@valencia]=Amhàric
Name[cs]=Amharský
Name[csb]=Etiopsczi
Name[cy]=Amhareg
Name[da]=Amharisk
Name[de]=Amharisch
Name[el]=Amharic
Name[en_GB]=Amharic
Name[eo]=Amhara
Name[es]=Amárico
Name[et]=Amhaara
Name[eu]=Amharic
Name[fa]=امهری
Name[fi]=Amhara
Name[fr]=Amharique
Name[fy]=Amhaarsk
Name[ga]=Amáiris
Name[gl]=Amhárico
Name[gu]=અમ્હારિક
Name[he]=אמהרית
Name[hi]=अम्हारिक
Name[hne]=अम्हारिक
Name[hr]=Amarski
Name[hsb]=Amharisce
Name[hu]=Amhár
Name[ia]=Amharico
Name[id]=Amharic
Name[is]=Amharic
Name[it]=Amarico
Name[ja]=アムハラ語
Name[kk]=Ахмарша
Name[km]=អាមហារិក
Name[kn]=ಅಮ್ಹಾರಿಕ್
Name[ko]=암하라어
Name[ku]=Amharî
Name[lb]=Amharesch
Name[lt]=Amhari
Name[lv]=Amharu
Name[mai]=अम्हारिक
Name[mk]=Амхарски
Name[ml]=അമ്ഹാരിക്
Name[mr]=अम्हारिक
Name[ms]=Amharic
Name[nb]=Amharisk
Name[nds]=Amhaarsch
Name[ne]=अम्हारिक
Name[nl]=Amharisch
Name[nn]=Amharisk
Name[oc]=Amaric
Name[or]=ଆମ୍ହାରିକ
Name[pa]=ਅਮਹਾਰਿਕ
Name[pl]=Etiopski
Name[ps]=امهارېک
Name[pt]=Amárico
Name[pt_BR]=Amárico
Name[ro]=Amarică
Name[ru]=Амхарский
Name[se]=Ambháriagiella
Name[si]=අමාරික්
Name[sk]=Amharčina
Name[sl]=amharik
Name[sq]=Amharike
Name[sr]=амарски
Name[sr@ijekavian]=амарски
Name[sr@ijekavianlatin]=amarski
Name[sr@latin]=amarski
Name[sv]=Amarinja
Name[ta]=அம்ஹாரிக்
Name[te]=అమ్హరిక్
Name[tg]=Амхарикӣ
Name[th]=ภาษาอัมฮาริค
Name[tr]=Amharic
Name[tt]=Амхара
Name[ug]=ئامخاراچە
Name[uk]=Амхарська
Name[uz]=Amxarik
Name[uz@cyrillic]=Амхарик
Name[vi]=Amharic
Name[wa]=Amarike
Name[xh]=Amharic
Name[x-test]=xxAmharicxx
Name[zh_CN]=阿比西尼亚语
Name[zh_HK]=衣索比亞官方語
Name[zh_TW]=衣索比亞官方語
[ar]
Name=Arabic
Name[af]=Arabies
Name[ar]=عربية
Name[as]=আৰবীয়
Name[ast]=Árabe
Name[be]=Арабская
Name[be@latin]=Arabskaja
Name[bg]=Арабски
Name[bn]=আরবী
Name[bn_IN]=আরবি
Name[br]=Arabeg
Name[bs]=Arapski
Name[ca]=Àrab
Name[ca@valencia]=Àrab
Name[cs]=Arabský
Name[csb]=Arabsczi
Name[cy]=Arabeg
Name[da]=Arabisk
Name[de]=Arabisch
Name[el]=Αραβικά
Name[en_GB]=Arabic
Name[eo]=Araba
Name[es]=Árabe
Name[et]=Araabia
Name[eu]=Arabiera
Name[fa]=عربی
Name[fi]=Arabia
Name[fr]=Arabe
Name[fy]=Arabysk
Name[ga]=Araibis
Name[gl]=Árabe
Name[gu]=અરેબિક
Name[he]=ערבית
Name[hi]=अरबी
Name[hne]=अरबी
Name[hr]=Arapski
Name[hsb]=Arabsce
Name[hu]=Arab
Name[hy]=Արաբերեն
Name[ia]=Arabe
Name[id]=Arab
Name[is]=Arabíska
Name[it]=Arabo
Name[ja]=アラビア語
Name[kk]=Арабша
Name[km]=អារ៉ាប់
Name[kn]=ಅರೇಬಿಕ್
Name[ko]=아랍어
Name[ku]=Erebî
Name[lb]=Arabesch
Name[lt]=Arabų
Name[lv]=Arābu
Name[mai]=अरबी
Name[mk]=Арапски
Name[ml]=അറബി
Name[mr]=अरेबिक
Name[ms]=Arab
Name[nb]=Arabisk
Name[nds]=Araabsch
Name[ne]=अरबी
Name[nl]=Arabisch
Name[nn]=Arabisk
Name[oc]=Arab
Name[or]=Arabic
Name[pa]=ਅਰਬੀ
Name[pl]=Arabski
Name[ps]=عربي
Name[pt]=Árabe
Name[pt_BR]=Árabe
Name[ro]=Arabă
Name[ru]=Арабский
Name[se]=Arábiagiella
Name[si]=අරාබි
Name[sk]=Arabčina
Name[sl]=arabsko
Name[sq]=Arabisht
Name[sr]=арапски
Name[sr@ijekavian]=арапски
Name[sr@ijekavianlatin]=arapski
Name[sr@latin]=arapski
Name[sv]=Arabiska
Name[ta]=அராபிக்
Name[te]=అరబిక్
Name[tg]=Арабӣ
Name[th]=ภาษาอารบิก
Name[tr]=Arapça
Name[tt]=Гарәп
Name[ug]=ئەرەبچە
Name[uk]=Арабська
Name[uz]=Arabcha
Name[uz@cyrillic]=Арабча
Name[vi]=Ả Rập
Name[wa]=Arabe
Name[xh]=Arabic
Name[x-test]=xxArabicxx
Name[zh_CN]=阿拉伯语
Name[zh_HK]=阿拉伯語
Name[zh_TW]=阿拉伯語
[as]
Name=Assamese
Name[af]=Assamese
Name[ar]=أسامية
Name[as]=অসমীয়া
Name[ast]=Asamés
Name[be]=Асамская
Name[be@latin]=Asameskaja
Name[bg]=Асами
Name[bn]=আসামী
Name[bn_IN]=অসমীয়া
Name[br]=Assamese
Name[bs]=Asamski
Name[ca]=Assamès
Name[ca@valencia]=Assamés
Name[cs]=Asamský
Name[csb]=Assamijsczi
Name[cy]=Assameg
Name[da]=Assamese
Name[de]=Assamesisch
Name[el]=Assamese
Name[en_GB]=Assamese
Name[eo]=Asama
Name[es]=Asamés
Name[et]=Assami
Name[eu]=Assamese
Name[fa]=آسامی
Name[fi]=Assami
Name[fr]=Assamais
Name[fy]=Assameesk
Name[ga]=Asaimis
Name[gl]=Asamixa
Name[gu]=આસામીઝ
Name[he]=אסאמית
Name[hi]=असमी
Name[hne]=असमी
Name[hr]=Asamski
Name[hsb]=Asamezisce
Name[hu]=Asszámi
Name[ia]=Assamese
Name[id]=Assamese
Name[is]=Assamese
Name[it]=Assamese
Name[ja]=アッサム語
Name[kk]=Ассамша
Name[km]=អាសាមីស
Name[kn]=ಅಸ್ಸಾಮಿ
Name[ko]=아삼어
Name[ku]=Asamî
Name[lb]=Assamesesch
Name[lt]=Asamesų
Name[lv]=Asamiešu
Name[mai]=असामी
Name[mk]=Асамески
Name[ml]=അസ്സാമീസ്
Name[mr]=असमी
Name[ms]=Assamese
Name[nb]=Assamesisk
Name[nds]=Assameesch
Name[ne]=आसामी
Name[nl]=Assamees
Name[nn]=Assami
Name[oc]=Assamés
Name[or]=Assamese
Name[pa]=ਆਸਾਮੀ
Name[pl]=Assamijski
Name[ps]=اسامي
Name[pt]=Assamese
Name[pt_BR]=Assamês
Name[ro]=Asameză
Name[ru]=Ассамский
Name[se]=Assamesegiella
Name[si]=ඇසමීස්
Name[sk]=Asámčina
Name[sl]=asamese
Name[sq]=Asameze
Name[sr]=асамејски
Name[sr@ijekavian]=асамејски
Name[sr@ijekavianlatin]=asamejski
Name[sr@latin]=asamejski
Name[sv]=Assamesiska
Name[ta]=அஸ்ஸாமிய
Name[te]=అస్సామీ
Name[tg]=Ассамӣ
Name[th]=ภาษาอัสสัม
Name[tr]=Assamese
Name[tt]=Ассам
Name[ug]=ئاسسامچە
Name[uk]=Асамійська
Name[uz]=Assamese
Name[uz@cyrillic]=Ассамесе
Name[vi]=Assam
Name[wa]=Assamès
Name[xh]=Assamese
Name[x-test]=xxAssamesexx
Name[zh_CN]=阿萨姆语
Name[zh_HK]=阿薩姆語
Name[zh_TW]=阿薩姆語
[ast]
Name=Asturian
Name[ar]=أستريان
Name[ast]=Asturianu
Name[bg]=Астурийски
Name[bs]=Asturski
Name[ca]=Asturià
Name[ca@valencia]=Asturià
Name[cs]=Asturijský
Name[da]=Asturiansk
Name[de]=Asturisch
Name[el]=Asturian
Name[en_GB]=Asturian
Name[es]=Asturiano
Name[et]=Astuuria
Name[eu]=Asturiera
Name[fa]=اتریشی
Name[fi]=Asturia
Name[fr]=Asturien
Name[ga]=Astúiris
Name[gl]=Asturiano
Name[he]=אוסטרית
Name[hr]=Asturijanski
Name[hu]=Asztúriai
Name[ia]=Asturian
Name[id]=Asturia
Name[is]=Astúríska
Name[it]=Asturiano
Name[ja]=アストゥリアス語
Name[kk]=Астурианша
Name[km]=អូស្តូរៀន
Name[kn]=ಆಸ್ಚೂರಿಯನ್
Name[ko]=아스투리아스어
Name[ku]=Astûrî
Name[lt]=Asturų
Name[lv]=Astūriešu
Name[ms]=Asturian
Name[nb]=Asturisk
Name[nds]=Astuursch
Name[nl]=Asturisch
Name[nn]=Asturleonesisk
Name[pa]=ਅਸਟੁਰੀਆਈ
Name[pl]=Asturyjski
Name[pt]=Asturiano
Name[pt_BR]=Asturiano
Name[ro]=Asturiană
Name[ru]=Астурийский
Name[se]=Asturialagiella
Name[si]=ඇස්ටරියන්
Name[sk]=Astúrčina
Name[sl]=asturijsko
Name[sq]=Asturiançe
Name[sr]=астуријски
Name[sr@ijekavian]=астуријски
Name[sr@ijekavianlatin]=asturijski
Name[sr@latin]=asturijski
Name[sv]=Asturiska
Name[ta]=அஸ்டூரியன்
Name[tg]=Асториан
Name[th]=ภาษาอัสตูเรียส
Name[tr]=Avusturya Dili
Name[tt]=Астурий
Name[ug]=ئاستۇرىيەچە
Name[uk]=Астурійська
Name[vi]=Asturian
Name[wa]=Asturyin
Name[x-test]=xxAsturianxx
Name[zh_CN]=阿斯图里亚斯语
Name[zh_TW]=Asturian
[ay]
Name=Aymara
Name[af]=Aymara
Name[ar]=أيمرا
Name[as]=আইমাৰা
Name[ast]=Aymara
Name[be]=Аймарская
Name[be@latin]=Aymara
Name[bg]=Аймара
Name[bn]=আইমারা
Name[bn_IN]=আয়মারা
Name[br]=Aymara
Name[bs]=ajmarski
Name[ca]=Aymara
Name[ca@valencia]=Aymara
Name[cs]=Aymara
Name[csb]=Aymara
Name[cy]=Aimareg
Name[da]=Aymara
Name[de]=Aimara
Name[el]=Aymara
Name[en_GB]=Aymara
Name[eo]=Ajmara
Name[es]=Aymara
Name[et]=Aimaraa
Name[eu]=Aymara
Name[fa]=آیمارایی
Name[fi]=Aimara
Name[fr]=Aymara
Name[fy]=Aymara
Name[ga]=Adhmarais
Name[gl]=Aimará
Name[gu]=અયમારા
Name[he]=איימרה
Name[hi]=अयमारा
Name[hne]=अयमारा
Name[hr]=Ajmarski
Name[hsb]=Aymara
Name[hu]=Ajmara
Name[ia]=Aymara
Name[id]=Aymara
Name[is]=Aymara
Name[it]=Aymara
Name[ja]=アイマラ語
Name[kk]=Аймарша
Name[km]=អេយ៍ម៉ារ៉ា
Name[kn]=ಅಯಮಾರಾ
Name[ko]=아이마라어
Name[ku]=Aymara
Name[lb]=Aimara
Name[lt]=Aymara
Name[lv]=Aimaru
Name[mai]=अयमारा
Name[mk]=Ајмара
Name[ml]=അരാമ്യ
Name[mr]=अयमारा
Name[ms]=Aymara
Name[nb]=Aymara
Name[nds]=Aymara
Name[ne]=एमरा
Name[nl]=Aymara
Name[nn]=Aymara
Name[oc]=Aymarà
Name[or]=Aymara
Name[pa]=ਅਯਮਾਰਾ
Name[pl]=Aymara
Name[ps]=اېمارا
Name[pt]=Aymara
Name[pt_BR]=Aimará
Name[ro]=Aymară
Name[ru]=Аймарский
Name[se]=Aimáragiella
Name[si]=අයිමාරා
Name[sk]=Aymarčina
Name[sl]=ajmarsko
Name[sq]=Aymara
Name[sr]=ајмарски
Name[sr@ijekavian]=ајмарски
Name[sr@ijekavianlatin]=ajmarski
Name[sr@latin]=ajmarski
Name[sv]=Aymara
Name[ta]=அய்மாரா
Name[te]=అయ్మరా
Name[tg]=Аймарагӣ
Name[th]=ภาษาอัยมารา
Name[tr]=Aymara
Name[tt]=Аймарча
Name[ug]=ئايماراچە
Name[uk]=Аймарська
Name[uz]=Aymara
Name[uz@cyrillic]=Аймара
Name[vi]=Aymara
Name[wa]=Aymara
Name[xh]=Aymara
Name[x-test]=xxAymaraxx
Name[zh_CN]=艾马拉语
Name[zh_HK]=愛瑪拉語
Name[zh_TW]=愛瑪拉語
[az]
Name=Azerbaijani
Name[af]=Azerbaijani
Name[ar]=أذربيجانية
Name[as]=আজাৰ্বাইজানী
Name[ast]=Azerbaiyanu
Name[be]=Азербайджанская
Name[be@latin]=Azerbajdžanskaja
Name[bg]=Азербайджански
Name[bn]=আজেরবাইজানি
Name[bn_IN]=আজারবাইজানি
Name[br]=Azerbaidjanek
Name[bs]=Azerbejdžanski
Name[ca]=Àzeri
Name[ca@valencia]=Àzeri
Name[cs]=Ázerbajdžánský
Name[csb]=Azerbejdżańsczi
Name[cy]=Azerbaijaneg
Name[da]=Azerbaijansk
Name[de]=Aserbaidschanisch
Name[el]=Αζερμπαϊτζανικά
Name[en_GB]=Azerbaijani
Name[eo]=Azerbajĝana
Name[es]=Azerbayano
Name[et]=Aserbaidžaani
Name[eu]=Azerbaijanera
Name[fa]=آذربایجانی
Name[fi]=Azeri
Name[fr]=Azerbaïdjanais
Name[fy]=Azerbeidzjaansk
Name[ga]=Asarbaiseáinis
Name[gl]=Azerí
Name[gu]=અઝરબાયજાની
Name[he]=אזרית
Name[hi]=अजरबेजानी
Name[hne]=अजरबेजानी
Name[hr]=Azerbejdžanski
Name[hsb]=Azerbajdźansce
Name[hu]=Azerbajdzsán
Name[hy]=Ադրբեջաներեն
Name[ia]=Azerbaijano
Name[id]=Azerbaijani
Name[is]=Adzerbadjanska
Name[it]=Azero
Name[ja]=アゼルバイジャン語
Name[kk]=Азербайжанша
Name[km]=អាហ្ស៊ែរបែហ្សង់
Name[kn]=ಅಜರ್ಬೈಜಾನಿ
Name[ko]=아제르바이잔어
Name[ku]=Azerî
Name[lb]=Asserbaidschanesch
Name[lt]=Azerbaidžaniečių
Name[lv]=Azerbaidžāņu
Name[mai]=अज़रबैजानी
Name[mk]=Азербејџански
Name[ml]=അസര്‍ബെയ്ജാനി
Name[mr]=अजरबेजानी
Name[ms]=Azerbaijan
Name[nb]=Aserbajdsjansk
Name[nds]=Aserbaidschaansch
Name[ne]=अजरबैजानी
Name[nl]=Azerbeidjaans
Name[nn]=Aserbajdsjansk
Name[oc]=Aseri
Name[or]=Azerbaijani
Name[pa]=ਅਜ਼ਰਬਾਈਜਾਨੀ
Name[pl]=Azerbejdżański
Name[ps]=ازربايجاني
Name[pt]=Azerbaijano
Name[pt_BR]=Azeri
Name[ro]=Azerbaijană
Name[ru]=Азербайджанский
Name[se]=Azerbaižánagiella
Name[si]=අසර්බයිජානි
Name[sk]=Azerbajdžančina
Name[sl]=azerbajdžansko
Name[sq]=Azerbaixhani
Name[sr]=азербејџански
Name[sr@ijekavian]=азербејџански
Name[sr@ijekavianlatin]=azerbejdžanski
Name[sr@latin]=azerbejdžanski
Name[sv]=Azerbajdzjanska
Name[ta]=அசர்பைசானி
Name[te]=అజెర్ బైజాని
Name[tg]=Озарбойҷонӣ
Name[th]=ภาษาอาเซอร์ไบจัน
Name[tr]=Azerbeycanca
Name[tt]=Әзербәйҗанча
Name[ug]=ئەزەربەيجانچە
Name[uk]=Азербайджанська
Name[uz]=Ozarbayjoncha
Name[uz@cyrillic]=Озарбайжонча
Name[vi]=A-zéc-bai-zan
Name[wa]=Azeri
Name[xh]=Azerbaijani
Name[x-test]=xxAzerbaijanixx
Name[zh_CN]=阿塞拜疆语
Name[zh_HK]=亞塞拜彊語
Name[zh_TW]=亞塞拜然語
[ba]
Name=Bashkir
Name[af]=Bashkir
Name[ar]=بشكيرية
Name[as]=বাস্কিৰ
Name[ast]=Bashkir
Name[be]=Башкірская
Name[be@latin]=Baškirskaja
Name[bg]=Башкирски
Name[bn]=বাশকীর
Name[bn_IN]=বাশকির
Name[br]=Bashkir
Name[bs]=Baškirski
Name[ca]=Baixkir
Name[ca@valencia]=Baixkir
Name[cs]=Baškirský
Name[csb]=Baszkirsczi
Name[cy]=Bashkireg
Name[da]=Bashkir
Name[de]=Baschkirisch
Name[el]=Bashkir
Name[en_GB]=Bashkir
Name[eo]=Baŝkira
Name[es]=Baskir
Name[et]=Baškiiri
Name[eu]=Bashkir
Name[fa]=بشکیری
Name[fi]=Baškiiri
Name[fr]=Bachkir
Name[fy]=Bashkir
Name[ga]=Baiscíris
Name[gl]=Bashquir
Name[gu]=બાસ્કિર
Name[he]=בשקירית
Name[hi]=बाशकिर
Name[hne]=बासकिर
Name[hr]=Baškirski
Name[hsb]=Baškirsce
Name[hu]=Baskír
Name[ia]=Bashkir
Name[id]=Bashkir
Name[is]=Bashkir
Name[it]=Baškiro
Name[ja]=バシュキール語
Name[kk]=Башкұртша
Name[km]=បាសគៀរ
Name[kn]=ಬಾಶ್ಕಿರ್
Name[ko]=바슈키르어
Name[ku]=Başkir
Name[lb]=Baschkiresch
Name[lt]=Baškirų
Name[lv]=Baškīru
Name[mai]=बाशकिर
Name[mk]=Башкир
Name[ml]=ബാഷ്കിര്‍
Name[mr]=बाशकिर
Name[ms]=Bashkir
Name[nb]=Bashkir
Name[nds]=Baschkiirsch
Name[ne]=बास्खिर
Name[nl]=Bashkir
Name[nn]=Basjkirsk
Name[oc]=Bachkir
Name[or]=Bashkir
Name[pa]=ਬਸਕੀਰ
Name[pl]=Baszkirski
Name[ps]=بشکير
Name[pt]=Bashkir
Name[pt_BR]=Basquir
Name[ro]=Bașchiră
Name[ru]=Башкирский
Name[se]=Baškiriagiella
Name[si]=බෂ්කීර්
Name[sk]=Baškirčina
Name[sl]=baškirsko
Name[sq]=Bashkire
Name[sr]=башкирски
Name[sr@ijekavian]=башкирски
Name[sr@ijekavianlatin]=baškirski
Name[sr@latin]=baškirski
Name[sv]=Bashkir
Name[ta]=பஷ்கீர்
Name[te]=బాష్కిర్
Name[tg]=Бошқирдӣ
Name[th]=ภาษาแบชเคียร์
Name[tr]=Bashkir
Name[tt]=Башкортча
Name[ug]=باشقىرتچە
Name[uk]=Башкирська
Name[uz]=Boshqircha
Name[uz@cyrillic]=Бошқирча
Name[vi]=Bashkir
Name[wa]=Bashkir
Name[xh]=Bashkir
Name[x-test]=xxBashkirxx
Name[zh_CN]=巴什基尔语
Name[zh_HK]=Bashkir語
Name[zh_TW]=Bashkir語
[be]
Name=Belarusian
Name[af]=Belarusian
Name[ar]=بلاروسية
Name[as]=বেলাৰুচীয়
Name[ast]=Bielorrusu
Name[be]=Беларуская
Name[be@latin]=Biełaruskaja
Name[bg]=Белоруски
Name[bn]=বেলারুশিয়
Name[bn_IN]=বেলারুশিয়ান
Name[br]=Belarusieg
Name[bs]=Bjeloruski
Name[ca]=Bielorús
Name[ca@valencia]=Bielorús
Name[cs]=Běloruský
Name[csb]=Białorusczi
Name[cy]=Belarwsieg
Name[da]=Hviderussisk
Name[de]=Weißrussisch
Name[el]=Λευκορωσικά
Name[en_GB]=Belarusian
Name[eo]=Belorusa
Name[es]=Bielorruso
Name[et]=Valgevene
Name[eu]=Bielorrusiera
Name[fa]=بلاروسی
Name[fi]=Valkovenäjä
Name[fr]=Biélorusse
Name[fy]=Wyt-Russysk
Name[ga]=Bealarúisis
Name[gl]=Bielorruso
Name[gu]=બેલારશિયન
Name[he]=בלרוסית
Name[hi]=बेलारूसी
Name[hne]=बेलारूसी
Name[hr]=Bjeloruski
Name[hsb]=Běłorusce
Name[hu]=Belorusz
Name[ia]=Bielorusso
Name[id]=Belarusia
Name[is]=Hvít-Rússneska
Name[it]=Bielorusso
Name[ja]=ベラルーシ語
Name[kk]=Белорусша
Name[km]=បេឡារុស្ស
Name[kn]=ಬೆಲರೂಸಿಯನ್
Name[ko]=벨라루스어
Name[ku]=Belarusî
Name[lb]=Wéisrussesch
Name[lt]=Baltarusių
Name[lv]=Baltkrievu
Name[mai]=बेलारूसी
Name[mk]=Белоруски
Name[ml]=ബെലാറൂഷ്യന്‍
Name[mr]=बेलारूसी
Name[ms]=Belarusia
Name[nb]=Hviterussisk
Name[nds]=Wittruss'sch
Name[ne]=बेलारूसी
Name[nl]=Wit-Rusland
Name[nn]=Kviterussisk
Name[oc]=Bielorus
Name[or]=Belarusian
Name[pa]=ਬੇਲਾਰੂਸੀ
Name[pl]=Białoruski
Name[ps]=بېلاروسي
Name[pt]=Bielorrusso
Name[pt_BR]=Bielorrusso
Name[ro]=Belarusă
Name[ru]=Белорусский
Name[se]=Vilgesruoššagiella
Name[si]=බෙලරුසියානු
Name[sk]=Bieloruština
Name[sl]=belorusko
Name[sq]=Bjelloruse
Name[sr]=белоруски
Name[sr@ijekavian]=бјелоруски
Name[sr@ijekavianlatin]=bjeloruski
Name[sr@latin]=beloruski
Name[sv]=Vitryska
Name[ta]=பெலாரூசியன்
Name[te]=బెలరషియన్
Name[tg]=Белорусӣ
Name[th]=ภาษาเบลารุสเซีย
Name[tr]=Belarus Dili
Name[tt]=Белорус
Name[ug]=بېلارۇسچە
Name[uk]=Білоруська
Name[uz]=Beloruscha
Name[uz@cyrillic]=Белорусча
Name[vi]=Bê-la-rút
Name[wa]=Bielorûsse
Name[xh]=Belarusian
Name[x-test]=xxBelarusianxx
Name[zh_CN]=白俄罗斯语
Name[zh_HK]=白俄羅斯語
Name[zh_TW]=白俄羅斯語
[be@latin]
Name=Belarusian (Latin)
Name[ar]=بلاروسية (لاتينية)
Name[as]=বেলাৰুচীয় (লেটিন)
Name[ast]=Bielorrusu (Llatinu)
Name[be@latin]=Biełaruskaja łacinka
Name[bg]=Белоруски (латиница)
Name[bn]=বেলারুশিয় (লাতিন)
Name[bn_IN]=বেলারুশিয়ান (লাতিন)
Name[bs]=Bjeloruski(Latinica)
Name[ca]=Bielorús (llatí)
Name[ca@valencia]=Bielorús (llatí)
Name[cs]=Běloruský (latinka)
Name[csb]=Białorusczi (łacëzniany)
Name[da]=Hviderussisk (latin)
Name[de]=Weißrussisch (lat. Alphabet)
Name[el]=Λευκορωσικά (Λατινικά)
Name[en_GB]=Belarusian (Latin)
Name[eo]=Belorusa (Latina)
Name[es]=Bielorruso (Latino)
Name[et]=Valgevene (ladina)
Name[eu]=Bielorrusiera (Latinoa)
Name[fa]=بلاروسی (لاتین)
Name[fi]=Valkovenäjä (Latin)
Name[fr]=Biélorusse (Latin)
Name[fy]=Wyt-Russysk (latynsk)
Name[ga]=Bealarúisis (aibítir Laidineach)
Name[gl]=Bielorruso (latino)
Name[gu]=બેલારશિયન (લેટિન)
Name[he]=בלרוסית (לטינית)
Name[hi]=बेलारूसी (लातिन)
Name[hne]=बेलारूसी (लेटिन)
Name[hr]=Bjeloruski (latinica)
Name[hsb]=Běłorusce (z łaćonskim pismom)
Name[hu]=Belorusz (latin betűs)
Name[ia]=Bielorusso (Latin)
Name[id]=Belarusia (Latin)
Name[is]=Hvít-Rússneska latnesk
Name[it]=Bielorusso (Latino)
Name[ja]=ベラルーシ語 (ラテン文字)
Name[kk]=Латындағы белорусша
Name[km]=បេឡារុស្ស៊ី (ឡាតាំង)
Name[kn]=ಬೆಲರೂಸಿಯನ್ (ಲಾಟಿನ್)
Name[ko]=벨라루스어 (라틴 문자)
Name[ku]=Belarusî (Latin)
Name[lt]=Baltarusių (lotynų)
Name[lv]=Baltkrievu (Latīņu)
Name[mai]=बेलारूसी (लातिन)
Name[ml]=ബെലാറൂഷ്യന്‍ (ലാറ്റിന്‍)
Name[ms]=Belarus (Latin)
Name[nb]=Hviterussisk (latinsk)
Name[nds]=Wittruss'sch (Latiensch)
Name[nl]=Wit-Rusland (Latijn)
Name[nn]=Kviterussisk (romanisert)
Name[pa]=ਬੇਲਾਰੂਸੀ (ਲੈਟਿਨ)
Name[pl]=Białoruski (alfabet łaciński)
Name[pt]=Bielorrusso (Latino)
Name[pt_BR]=Bielorrusso (latino)
Name[ro]=Bielorusă (latin)
Name[ru]=Белорусский (латиница)
Name[se]=Vilgesruoššagiella (Latiidna)
Name[si]=බෙලරුසියානු (ලතින්)
Name[sk]=Bieloruština (latinka)
Name[sl]=belorusko (latinica)
Name[sq]=Bjelloruse (Latine)
Name[sr]=белоруски (латиница)
Name[sr@ijekavian]=бјелоруски (латиница)
Name[sr@ijekavianlatin]=bjeloruski (latinica)
Name[sr@latin]=beloruski (latinica)
Name[sv]=Vitryska (latinsk)
Name[ta]=பெலாரூசியன் (இலத்தீன்)
Name[tg]=Белорусӣ (Лотинӣ)
Name[th]=ภาษาเบลารุสเซีย (ละติน)
Name[tr]=Belarus Dili (Latin)
Name[tt]=Беларусча (Латин алф.)
Name[ug]=بېلارۇسىيەچە(لاتىن)
Name[uk]=Білоруська (латиниця)
Name[vi]=Belarusian (Latin)
Name[wa]=Bielorûsse (Latén)
Name[x-test]=xxBelarusian (Latin)xx
Name[zh_CN]=白俄罗斯语(拉丁语系)
Name[zh_TW]=白俄羅斯語(拉丁)
[bg]
Name=Bulgarian
Name[af]=Bulgaars
Name[ar]=بلغارية
Name[as]=বুলগেৰীয়
Name[ast]=Búlgaru
Name[be]=Балгарская
Name[be@latin]=Baŭharskaja
Name[bg]=Български
Name[bn]=বুলগেরিয়
Name[bn_IN]=বুলগেরিয়ান
Name[br]=Bulgareg
Name[bs]=Bugarski
Name[ca]=Búlgar
Name[ca@valencia]=Búlgar
Name[cs]=Bulharský
Name[csb]=Bùlgarsczi
Name[cy]=Bwlgareg
Name[da]=Bulgarsk
Name[de]=Bulgarisch
Name[el]=Βουλγαρικά
Name[en_GB]=Bulgarian
Name[eo]=Bulgara
Name[es]=Búlgaro
Name[et]=Bulgaaria
Name[eu]=Bulgariera
Name[fa]=بلغاری
Name[fi]=Bulgaria
Name[fr]=Bulgare
Name[fy]=Bulgaarsk
Name[ga]=Bulgáiris
Name[gl]=Búlgaro
Name[gu]=બલ્ગેરીયન
Name[he]=בולגרית
Name[hi]=बुल्गारियाई
Name[hne]=बुल्गारियाई
Name[hr]=Bugarski
Name[hsb]=Bołharsce
Name[hu]=Bolgár
Name[ia]=Bulgaro
Name[id]=Bulgaria
Name[is]=Búlgarska
Name[it]=Bulgaro
Name[ja]=ブルガリア語
Name[kk]=Болғарша
Name[km]=ប៊ុលហ្គារី
Name[kn]=ಬಲ್ಗೇರಿಯನ್
Name[ko]=불가리아어
Name[ku]=Bulgarî
Name[lb]=Bulgaresch
Name[lt]=Bulgarų
Name[lv]=Bulgāru
Name[mai]=बुल्गारियाइ
Name[mk]=Бугарски
Name[ml]=ബള്‍ഗേറിയന്‍
Name[mr]=बुल्गारियाई
Name[ms]=Bulgaria
Name[nb]=Bulgarsk
Name[nds]=Bulgaarsch
Name[ne]=बुल्गेरियाली
Name[nl]=Bulgaars
Name[nn]=Bulgarsk
Name[oc]=Bulgar
Name[or]=Bulgarian
Name[pa]=ਬੁਲਗਾਰੀਆਈ
Name[pl]=Bułgarski
Name[ps]=بلګريايي
Name[pt]=Búlgaro
Name[pt_BR]=Búlgaro
Name[ro]=Bulgară
Name[ru]=Болгарский
Name[se]=Bulgáriagiella
Name[si]=බල්ගේරියානු
Name[sk]=Bulharčina
Name[sl]=bolgarsko
Name[sq]=Bullgarisht
Name[sr]=бугарски
Name[sr@ijekavian]=бугарски
Name[sr@ijekavianlatin]=bugarski
Name[sr@latin]=bugarski
Name[sv]=Bulgariska
Name[ta]=பல்கேரியன்
Name[te]=బల్గెరియన్
Name[tg]=Булғорӣ
Name[th]=ภาษาบัลแกเรีย
Name[tr]=Bulgarca
Name[tt]=Болгар
Name[ug]=بۇلغارچە
Name[uk]=Болгарська
Name[uz]=Bolgarcha
Name[uz@cyrillic]=Болгарча
Name[vi]=Bun-ga-ry
Name[wa]=Bulgåre
Name[xh]=Bulgarian
Name[x-test]=xxBulgarianxx
Name[zh_CN]=保加利亚语
Name[zh_HK]=保加利亞語
Name[zh_TW]=保加利亞語
[bh]
Name=Bihari
Name[af]=Bihari
Name[ar]=بهارية
Name[as]=বিহাৰী
Name[ast]=Biharí
Name[be]=Біхарская
Name[be@latin]=Biharskaja
Name[bg]=Бихари
Name[bn]=বিহারী
Name[bn_IN]=বিহারি
Name[br]=Bihari
Name[bs]=Biharski
Name[ca]=Bihari
Name[ca@valencia]=Bihari
Name[cs]=Bihari
Name[csb]=Bihari
Name[cy]=Bihareg
Name[da]=Bihari
Name[de]=Biharisch
Name[el]=Bihari
Name[en_GB]=Bihari
Name[eo]=Bihari
Name[es]=Bihari
Name[et]=Bihari
Name[eu]=Bihari
Name[fa]=بیهاری
Name[fi]=Bihari
Name[fr]=Bihari
Name[fy]=Bihary
Name[ga]=Bihairis
Name[gl]=Bihari
Name[gu]=બિહારી
Name[he]=ביהרי
Name[hi]=बिहारी
Name[hne]=बिहारी
Name[hr]=Biharski
Name[hsb]=Biharisce
Name[hu]=Bihari
Name[ia]=Bihari
Name[id]=Bihari
Name[is]=Bihari
Name[it]=Bihari
Name[ja]=ビハール語
Name[kk]=Бихарша
Name[km]=ប៊ិហារី
Name[kn]=ಬಿಹಾರಿ
Name[ko]=비하르어
Name[ku]=Biharî
Name[lb]=Biharesch
Name[lt]=Bihari
Name[lv]=Biharu
Name[mai]=बिहारी
Name[mk]=Бихари
Name[ml]=ബീഹാരി
Name[mr]=बिहारी
Name[ms]=Bihari
Name[nb]=Bihari
Name[nds]=Bihari
Name[ne]=बिहारी
Name[nl]=Bihari
Name[nn]=Bihari
Name[oc]=Bihari
Name[or]=Bihari
Name[pa]=ਬਿਹਾਰੀ
Name[pl]=Bihari
Name[ps]=بېهاري
Name[pt]=Bihari
Name[pt_BR]=Bihari
Name[ro]=Bihari
Name[ru]=Бихарский
Name[se]=Biháragiella
Name[si]=බිහාරි
Name[sk]=Bihárčina
Name[sl]=bihari
Name[sq]=Bihari
Name[sr]=бихарски
Name[sr@ijekavian]=бихарски
Name[sr@ijekavianlatin]=biharski
Name[sr@latin]=biharski
Name[sv]=Bihari
Name[ta]=பிகாரி
Name[te]=బిహారి
Name[tg]=Бихарӣ
Name[th]=ภาษาบิฮาริ
Name[tr]=Bihari
Name[tt]=Бихарча
Name[ug]=بىخارىچە
Name[uk]=Біхарі
Name[uz]=Bixari
Name[uz@cyrillic]=Бихари
Name[vi]=Bihari
Name[wa]=Bihari
Name[xh]=Bihari
Name[x-test]=xxBiharixx
Name[zh_CN]=比哈尔语
Name[zh_HK]=比哈爾語
Name[zh_TW]=比哈爾語
[bi]
Name=Bislama
Name[af]=Bislama
Name[ar]=بيسلاما
Name[as]=বিস্লামা
Name[ast]=Bislama
Name[be]=Бісламская
Name[be@latin]=Biślama
Name[bg]=Бислама
Name[bn]=বিসলামা
Name[bn_IN]=বিসলামা
Name[br]=Bislama
Name[bs]=Bislama
Name[ca]=Bislama
Name[ca@valencia]=Bislama
Name[cs]=Bislama
Name[csb]=Bislama
Name[cy]=Bislameg
Name[da]=Bislama
Name[de]=Bislama
Name[el]=Bislama
Name[en_GB]=Bislama
Name[eo]=Bislama
Name[es]=Bislama
Name[et]=Bislama
Name[eu]=Bislama
Name[fa]=بیسلاما
Name[fi]=Bislama
Name[fr]=Bichlamar
Name[fy]=Bislama
Name[ga]=Bioslamais
Name[gl]=Bislama
Name[gu]=બિસલામા
Name[he]=ביסלמה
Name[hi]=बिस्लामा
Name[hne]=बिस्लामा
Name[hr]=Bislamski
Name[hsb]=Bislamisce
Name[hu]=Biszlama
Name[ia]=Bislama
Name[id]=Bislama
Name[is]=Bislama
Name[it]=Bislama
Name[ja]=ビスラマ語
Name[kk]=Бисамша
Name[km]=ប៊ីសឡាម៉ា
Name[kn]=ಬಿಸ್ಲಾಮ
Name[ko]=비슬라마어
Name[ku]=Bislama
Name[lb]=Bislama
Name[lt]=Bislama
Name[lv]=Bišlamā
Name[mai]=बिस्लामा
Name[mk]=Бислама
Name[ml]=ബിസ്ലാമാ
Name[mr]=बिस्लामा
Name[ms]=Bislama
Name[nb]=Bislama
Name[nds]=Bislama
Name[ne]=बिस्लामा
Name[nl]=Bislama
Name[nn]=Bislama
Name[oc]=Bislamà
Name[or]=Bislama
Name[pa]=ਬਿਸਲਾਮਾ
Name[pl]=Bislama
Name[ps]=بېسلاما
Name[pt]=Bislama
Name[pt_BR]=Bislama
Name[ro]=Bislama
Name[ru]=Бислама
Name[se]=Bislamagiella
Name[si]=බිස්ලාමා
Name[sk]=Bislama
Name[sl]=bislama
Name[sq]=Bislama
Name[sr]=бисламски
Name[sr@ijekavian]=бисламски
Name[sr@ijekavianlatin]=bislamski
Name[sr@latin]=bislamski
Name[sv]=Bislama
Name[ta]=பிஸ்லாமா
Name[te]=బిస్లమా
Name[tg]=Бисламавӣ
Name[th]=ภาษาบิสลามา
Name[tr]=Bislama
Name[tt]=Бисламча
Name[ug]=بىسلاماچە
Name[uk]=Біслама
Name[uz]=Bislama
Name[uz@cyrillic]=Бислама
Name[vi]=Bislama
Name[wa]=Bislama
Name[xh]=Bislama
Name[x-test]=xxBislamaxx
Name[zh_CN]=比斯拉马语
Name[zh_HK]=Bislama語
Name[zh_TW]=Bislama語
[bn]
Name=Bengali
Name[af]=Bengali
Name[ar]=بنغالية
Name[as]=বঙালী
Name[ast]=Bengalí
Name[be]=Бенгальская
Name[be@latin]=Bengalskaja
Name[bg]=Бенгали
Name[bn]=বাংলা
Name[bn_IN]=বাংলা
Name[br]=Bangali
Name[bs]=Bengalski
Name[ca]=Bengalí
Name[ca@valencia]=Bengalí
Name[cs]=Bengálský
Name[csb]=Bengalsczi
Name[cy]=Bengaleg
Name[da]=Bengali
Name[de]=Bengalisch
Name[el]=Bengali
Name[en_GB]=Bengali
Name[eo]=Bengala
Name[es]=Bengalí
Name[et]=Bengali
Name[eu]=Bengalera
Name[fa]=بنگلادشی
Name[fi]=Bengali
Name[fr]=Bengalî
Name[fy]=Bengaalsk
Name[ga]=Beangáilis
Name[gl]=Bengalí
Name[gu]=બંગાળી
Name[he]=בנגלית
Name[hi]=बंगाली
Name[hne]=बंगाली
Name[hr]=Bengalski
Name[hsb]=Bengalsce
Name[hu]=Bengáli
Name[ia]=Bengali
Name[id]=Bengali
Name[is]=Bengalst
Name[it]=Bengalese
Name[ja]=ベンガル語
Name[kk]=Бенгали
Name[km]=​បេន្កាលី
Name[kn]=ಬಂಗಾಳಿ
Name[ko]=벵갈어
Name[ku]=Bengalî
Name[lb]=Bengalesch
Name[lt]=Bengalų
Name[lv]=Bengāļu
Name[mai]=बंगाली
Name[mk]=Бенгалски
Name[ml]=ബംഗാളി
Name[mr]=बंगाली
Name[ms]=Bengali
Name[nb]=Bengali
Name[nds]=Bengaalsch
Name[ne]=बङ्गाली
Name[nl]=Bengali
Name[nn]=Bengali
Name[oc]=Bengalí
Name[or]=Bengali
Name[pa]=ਬੰਗਾਲੀ
Name[pl]=Bengalski
Name[ps]=بنګالي
Name[pt]=Bengali
Name[pt_BR]=Bengali
Name[ro]=Bengali
Name[ru]=Бенгальский
Name[se]=Bengalagiella
Name[si]=බෙංගාලි
Name[sk]=Bengálčina
Name[sl]=bengalsko
Name[sq]=Bengali
Name[sr]=бенгалски
Name[sr@ijekavian]=бенгалски
Name[sr@ijekavianlatin]=bengalski
Name[sr@latin]=bengalski
Name[sv]=Bengali
Name[ta]=பெங்காலி
Name[te]=బెంగాలి
Name[tg]=Банголӣ
Name[th]=ภาษาเบงกาลี
Name[tr]=Bengalce
Name[tt]=Бенгаль
Name[ug]=بېنگالچە
Name[uk]=Бенгальська
Name[uz]=Bengalcha
Name[uz@cyrillic]=Бенгалча
Name[vi]=Băng-gan
Name[wa]=Bengali
Name[xh]=Bengali
Name[x-test]=xxBengalixx
Name[zh_CN]=孟加拉语
Name[zh_HK]=孟加拉語
Name[zh_TW]=孟加拉語
[bn_IN]
Name=Bengali (India)
Name[ar]=بنغالية (الهند)
Name[as]=বঙালী (ভাৰত)
Name[ast]=Bengalí (India)
Name[be@latin]=Bengalskaja (Indyja)
Name[bg]=Бенгали (Индия)
Name[bn]=বাংলা (ভারত)
Name[bn_IN]=বাংলা (ভারত)
Name[bs]=Bengalski(Indija)
Name[ca]=Bengalí (Índia)
Name[ca@valencia]=Bengalí (Índia)
Name[cs]=Bengálský (Indie)
Name[csb]=Bengalsczi (Indie)
Name[da]=Bengali (Indien)
Name[de]=Bengalisch (Indien)
Name[el]=Bengali (Ινδία)
Name[en_GB]=Bengali (India)
Name[eo]=Bengala (Hindio)
Name[es]=Bengalí (India)
Name[et]=Bengali (India)
Name[eu]=Bengalera (India)
Name[fa]=بنگلادشی (هند)
Name[fi]=Bengali (Intia)
Name[fr]=Bengali (Inde)
Name[fy]=Bengaalsk (india)
Name[ga]=Beangáilis (An India)
Name[gl]=Bengalí (India)
Name[gu]=બંગાળી (ભારત)
Name[he]=בנגלית (הודו)
Name[hi]=बंगाली (भारत)
Name[hne]=बंगाली (भारत)
Name[hr]=Bengalski (Indija)
Name[hsb]=Bengalsce (Indiska)
Name[hu]=Bengáli (India)
Name[ia]=Bengali (India)
Name[id]=Bengali (India)
Name[is]=Bengalst (Indland)
Name[it]=Bengalese (India)
Name[ja]=ベンガル語 (インド)
Name[kk]=Бенгали (Үндістан)
Name[km]=​បេន្កាលី (ឥណ្ឌា)
Name[kn]=ಬಂಗಾಳಿ (ಭಾರತ)
Name[ko]=벵갈어 (인도)
Name[ku]=Bengalî (Hindistan)
Name[lt]=Bengalų (Indija)
Name[lv]=Bengāļu (Indijas)
Name[mai]=बंगाली (भारत)
Name[mk]=Бенгалски (Индија)
Name[ml]=ബംഗാളി (ഇന്ത്യ)
Name[mr]=बंगाली (भारत)
Name[ms]=Bengali (India)
Name[nb]=Bengali (India)
Name[nds]=Bengaalsch (Indien)
Name[nl]=Bengali (India)
Name[nn]=Bengali (India)
Name[oc]=Bengalin (Índia)
Name[or]=Bengali (India)
Name[pa]=ਬੰਗਾਲੀ (ਭਾਰਤ)
Name[pl]=Bengalski (Indie)
Name[ps]=بنګالي (انډيا)
Name[pt]=Bengali (Índia)
Name[pt_BR]=Bengali (Índia)
Name[ro]=Bengali (India)
Name[ru]=Бенгальский (Индия)
Name[se]=Bengalagiella (India)
Name[si]=බෙංගාලි (ඉන්දියා)
Name[sk]=Bengálčina (India)
Name[sl]=bengalsko (Indija)
Name[sq]=Bengali (India)
Name[sr]=бенгалски (Индија)
Name[sr@ijekavian]=бенгалски (Индија)
Name[sr@ijekavianlatin]=bengalski (Indija)
Name[sr@latin]=bengalski (Indija)
Name[sv]=Bengali (Indien)
Name[ta]=பெங்காலி (இந்தியா)
Name[te]=బెంగాలి (భారతీయ)
Name[tg]=Банголӣ (Ҳиндӣ)
Name[th]=ภาษาเบงกาลี (อินเดีย)
Name[tr]=Bengalce (Hindistan)
Name[tt]=Бенгаль (Һинд.)
Name[ug]=بېنگالچە (ھىندىستان)
Name[uk]=Бенгальська (Індія)
Name[vi]=Bengali (Ấn độ)
Name[wa]=Bengali (Inde)
Name[x-test]=xxBengali (India)xx
Name[zh_CN]=孟加拉语(印度)
Name[zh_TW]=孟加拉語(印度)
[bo]
Name=Tibetan
Name[af]=Tibetan
Name[ar]=تبتية
Name[as]=টিব্বটীয়
Name[ast]=Tibetanu
Name[be]=Тыбецкая
Name[be@latin]=Tybeckaja
Name[bg]=Тибетски
Name[bn]=তিব্বতী
Name[bn_IN]=তিব্বতি
Name[br]=Yezh an Tibet
Name[bs]=Tibetski
Name[ca]=Tibetà
Name[ca@valencia]=Tibetà
Name[cs]=Tibetský
Name[csb]=Tibetańsczi
Name[cy]=Tibeteg
Name[da]=Tibetansk
Name[de]=Tibetisch
Name[el]=Θιβετιανά
Name[en_GB]=Tibetan
Name[eo]=Tibeta
Name[es]=Tibetano
Name[et]=Tiibeti
Name[eu]=Tibetera
Name[fa]=تیبوتیایی
Name[fi]=Tiibet
Name[fr]=Tibétain
Name[fy]=Tibetaansk
Name[ga]=Tibéidis
Name[gl]=Tibetano
Name[gu]=તિબેટીયન
Name[he]=טיבטית
Name[hi]=तिब्बती
Name[hne]=तिब्बती
Name[hr]=Tibetanski
Name[hsb]=Tibetisce
Name[hu]=Tibeti
Name[ia]=Tibetano
Name[id]=Tibet
Name[is]=Tíbeska
Name[it]=Tibetano
Name[ja]=チベット語
Name[kk]=Тибетше
Name[km]=ទីបេ
Name[kn]=ಟಿಬೆಟನ್
Name[ko]=티베트어
Name[ku]=Tîbetî
Name[lb]=Tibetanesch
Name[lt]=Tibetiečių
Name[lv]=Tibetiešu
Name[mai]=तिब्बती
Name[mk]=Тибетански
Name[ml]=ടിബറ്റന്‍
Name[mr]=टिबेटियन
Name[ms]=Tibet
Name[nb]=Tibetansk
Name[nds]=Tibeetsch
Name[ne]=तिब्बती
Name[nl]=Tibetaans
Name[nn]=Tibetansk
Name[oc]=Tibetan
Name[or]=Tibetan
Name[pa]=ਤਿੱਬਤੀ
Name[pl]=Tybetański
Name[ps]=ټېبېټي
Name[pt]=Tibetano
Name[pt_BR]=Tibetano
Name[ro]=Tibetană
Name[ru]=Тибетский
Name[se]=Tibehtagiella
Name[si]=ටිබෙටියානු
Name[sk]=Tibetčina
Name[sl]=tibetansko
Name[sq]=Tibetiane
Name[sr]=тибетански
Name[sr@ijekavian]=тибетански
Name[sr@ijekavianlatin]=tibetanski
Name[sr@latin]=tibetanski
Name[sv]=Tibetanska
Name[ta]=திபெத்தியன்
Name[te]=టిబెటియన్
Name[tg]=Тибетӣ
Name[th]=ภาษาฑิเบต
Name[tr]=Tibetan
Name[tt]=Тибет
Name[ug]=تىبەتچە
Name[uk]=Тибетська
Name[uz]=Tibetcha
Name[uz@cyrillic]=Тибетча
Name[vi]=Tây Tạng
Name[wa]=Tibetyin
Name[xh]=Tibetan
Name[x-test]=xxTibetanxx
Name[zh_CN]=藏语
Name[zh_HK]=藏語
Name[zh_TW]=藏語
[br]
Name=Breton
Name[af]=Breton
Name[ar]=بريتونية
Name[as]=ব্ৰেট'ন
Name[ast]=Bretón
Name[be]=Брэтонская
Name[be@latin]=Bretonskaja
Name[bg]=Бретонски
Name[bn]=ব্রেটন
Name[bn_IN]=ব্রেটন
Name[br]=Brezhoneg
Name[bs]=Bretonski
Name[ca]=Bretó
Name[ca@valencia]=Bretó
Name[cs]=Bretonský
Name[csb]=Bretońsczi
Name[cy]=Llydaweg
Name[da]=Bretonsk
Name[de]=Bretonisch
Name[el]=Βρετονικά
Name[en_GB]=Breton
Name[eo]=Bretona
Name[es]=Bretón
Name[et]=Bretooni
Name[eu]=Bretoiera
Name[fa]=برتونیایی
Name[fi]=Bretoni
Name[fr]=Breton
Name[fy]=Bretonsk
Name[ga]=Briotáinis
Name[gl]=Bretón
Name[gu]=બ્રેટોન
Name[he]=ברטונית
Name[hi]=ब्रेटन
Name[hne]=ब्रेटन
Name[hr]=Bretonski
Name[hsb]=Bretonisce
Name[hu]=Breton
Name[ia]=Bretone
Name[id]=Breton
Name[is]=Bretánska
Name[it]=Bretone
Name[ja]=ブルトン語
Name[kk]=Бретонша
Name[km]=ប្រេតុង
Name[kn]=ಬ್ರೆಟನ್
Name[ko]=브르타뉴어
Name[ku]=Bretonî
Name[lb]=Britesch
Name[lt]=Bretonų
Name[lv]=Bretoņu
Name[mai]=ब्रेटन
Name[mk]=Бретонски
Name[ml]=ബ്രെട്ടോണ്‍
Name[mr]=ब्रेटन
Name[ms]=Breton
Name[nb]=Bretonsk
Name[nds]=Bretoonsch
Name[ne]=बेलायती
Name[nl]=Bretons
Name[nn]=Bretonsk
Name[oc]=Breton
Name[or]=Breton
Name[pa]=ਬਰੀਟੋਨ
Name[pl]=Bretoński
Name[ps]=برېټون
Name[pt]=Bretão
Name[pt_BR]=Bretão
Name[ro]=Bretonă
Name[ru]=Бретонский
Name[se]=Bretonagiella
Name[si]=බ්‍රෙටන්
Name[sk]=Bretónčina
Name[sl]=bretonsko
Name[sq]=Bretone
Name[sr]=бретонски
Name[sr@ijekavian]=бретонски
Name[sr@ijekavianlatin]=bretonski
Name[sr@latin]=bretonski
Name[sv]=Bretonska
Name[ta]=பிரெடான்
Name[te]=బ్రెటన్
Name[tg]=Бритонӣ
Name[th]=ภาษาเบรทตัน
Name[tr]=Britanya Dili
Name[tt]=Бретон
Name[ug]=بىرېتونچە
Name[uk]=Бретонська
Name[uz]=Bretoncha
Name[uz@cyrillic]=Бретонча
Name[vi]=Breton
Name[wa]=Burton
Name[xh]=Breton
Name[x-test]=xxBretonxx
Name[zh_CN]=布里多尼语
Name[zh_HK]=不列塔尼語
Name[zh_TW]=不列塔尼語
[bs]
Name=Bosnian
Name[af]=Bosnies
Name[ar]=بوسنية
Name[as]=বোস্নীয়
Name[ast]=Bosniu
Name[be]=Баснійская
Name[be@latin]=Baśnijskaja
Name[bg]=Босненски
Name[bn]=বসনীয়
Name[bn_IN]=বসনিয়ান
Name[br]=Bosnieg
Name[bs]=Bosanski
Name[ca]=Bosnià
Name[ca@valencia]=Bosnià
Name[cs]=Bosenský
Name[csb]=Bósniany
Name[cy]=Bosneg
Name[da]=Bosnisk
Name[de]=Bosnisch
Name[el]=Βοσνιακά
Name[en_GB]=Bosnian
Name[eo]=Bosnia
Name[es]=Bosnio
Name[et]=Bosnia
Name[eu]=Bosniera
Name[fa]=بوسنیایی
Name[fi]=Bosnia
Name[fr]=Bosniaque
Name[fy]=Bosnysk
Name[ga]=Boisnis
Name[gl]=Bosnio
Name[gu]=બોસ્નીયન
Name[he]=בוסנית
Name[hi]=बोस्नियाई
Name[hne]=बोस्नियाई
Name[hr]=Bošnjački
Name[hsb]=Bosnisce
Name[hu]=Bosnyák
Name[ia]=Bosniano
Name[id]=Bosnia
Name[is]=Bosníska
Name[it]=Bosniaco
Name[ja]=ボスニア語
Name[kk]=Боснаша
Name[km]=បូស្នី
Name[kn]=ಬೋಸ್ನಿಯನ್
Name[ko]=보스니아어
Name[ku]=Bosnî
Name[lb]=Bosnesch
Name[lt]=Bosnių
Name[lv]=Bosniešu
Name[mai]=बोस्नियाइ
Name[mk]=Босански
Name[ml]=ബോസ്നിയന്‍
Name[mr]=बोस्नियाई
Name[ms]=Bosnia
Name[nb]=Bosnisk
Name[nds]=Bosnisch
Name[ne]=बोस्नियाली
Name[nl]=Bosnisch
Name[nn]=Bosnisk
Name[oc]=Bosniac
Name[or]=Bosnian
Name[pa]=ਬੋਸਨੀਆ
Name[pl]=Bośniacki
Name[ps]=بوسنيايي
Name[pt]=Bósnio
Name[pt_BR]=Bósnio
Name[ro]=Bosniacă
Name[ru]=Боснийский
Name[se]=Bosniagiella
Name[si]=බොස්නියානු
Name[sk]=Bosniačtina
Name[sl]=bosansko
Name[sq]=Boshnjake
Name[sr]=бошњачки
Name[sr@ijekavian]=бошњачки
Name[sr@ijekavianlatin]=bošnjački
Name[sr@latin]=bošnjački
Name[sv]=Bosniska
Name[ta]=பொஸ்னியன்
Name[te]=బొస్నియన్
Name[tg]=Босниявӣ
Name[th]=ภาษาบอสเนีย
Name[tr]=Boşnakça
Name[tt]=Босния
Name[ug]=بوسنىيەچە
Name[uk]=Боснійська
Name[uz]=Bosniyacha
Name[uz@cyrillic]=Боснияча
Name[vi]=Bosnia
Name[wa]=Bosnyin
Name[xh]=Bosnian
Name[x-test]=xxBosnianxx
Name[zh_CN]=波斯尼亚语
Name[zh_HK]=波斯尼亞語
Name[zh_TW]=波士尼亞語
[ca]
Name=Catalan
Name[af]=Katelaans
Name[ar]=كتلونية
Name[as]=কাটালান
Name[ast]=Catalán
Name[be]=Каталонская
Name[be@latin]=Katalanskaja
Name[bg]=Каталонски
Name[bn]=ক্যাটালান
Name[bn_IN]=ক্যাটালান
Name[br]=Katalaneg
Name[bs]=Katalonski
Name[ca]=Català
Name[ca@valencia]=Català
Name[cs]=Katalánský
Name[csb]=Katalońsczi
Name[cy]=Catalaneg
Name[da]=Catalansk
Name[de]=Katalanisch
Name[el]=Καταλανικά
Name[en_GB]=Catalan
Name[eo]=Kataluna
Name[es]=Catalán
Name[et]=Katalaani
Name[eu]=Katalaniera
Name[fa]=کاتالانی
Name[fi]=Katalaani
Name[fr]=Catalan
Name[fy]=Katalaansk
Name[ga]=Catalóinis
Name[gl]=Catalán
Name[gu]=કેટેલાન
Name[he]=קטלונית
Name[hi]=केटेलन
Name[hne]=केटेलन
Name[hr]=Katalonski
Name[hsb]=Katalansce
Name[hu]=Katalán
Name[ia]=Catalan
Name[id]=Catalan
Name[is]=Katalánska
Name[it]=Catalano
Name[ja]=カタロニア語
Name[kk]=Каталанша
Name[km]=កាតាឡាន
Name[kn]=ಕ್ಯಾಟಲಾನ್
Name[ko]=카탈루냐어
Name[ku]=Katalan
Name[lb]=Katalanesch
Name[lt]=Katalonų
Name[lv]=Kataloņu
Name[mai]=केटालान
Name[mk]=Каталонски
Name[ml]=കറ്റാലന്‍
Name[mr]=केटेलन
Name[ms]=Catalan
Name[nb]=Katalansk
Name[nds]=Katalaansch
Name[ne]=क्यातालान
Name[nl]=Catalaans
Name[nn]=Katalansk
Name[oc]=Catalan
Name[or]=Catalan
Name[pa]=ਕਾਟਾਲਾਨ
Name[pl]=Kataloński
Name[ps]=کېټېلېن
Name[pt]=Catalão
Name[pt_BR]=Catalão
Name[ro]=Catalană
Name[ru]=Каталонский
Name[se]=Katalánagiella
Name[si]=කැටලන්
Name[sk]=Katalánčina
Name[sl]=katalonsko
Name[sq]=Katalane
Name[sr]=каталонски
Name[sr@ijekavian]=каталонски
Name[sr@ijekavianlatin]=katalonski
Name[sr@latin]=katalonski
Name[sv]=Katalanska
Name[ta]=கடலான்
Name[te]=కెటలన్
Name[tg]=Каталанӣ
Name[th]=ภาษาคาตะลาน
Name[tr]=Katalan Dili
Name[tt]=Каталон
Name[ug]=كاتالانچە
Name[uk]=Каталонська
Name[uz]=Katalancha
Name[uz@cyrillic]=Каталанча
Name[vi]=Catalan
Name[wa]=Catalan
Name[xh]=Catalan
Name[x-test]=xxCatalanxx
Name[zh_CN]=加泰罗尼亚语
Name[zh_HK]=嘉泰羅尼亞語
Name[zh_TW]=嘉泰羅尼亞語
[ca@valencia]
Name=Catalan (Valencian)
Name[ar]=كتلونية(بلنسية)
Name[bg]=Каталонски (Валенсиански)
Name[bs]=Katalonski(Valensijski)
Name[ca]=Català (Valencià)
Name[ca@valencia]=Català (Valencià)
Name[cs]=Katalánský (Valencie)
Name[da]=Catalansk (Valenciansk)
Name[de]=Katalanisch (Valenciana)
Name[el]=Καταλανικά (Βαλένθια)
Name[en_GB]=Catalan (Valencian)
Name[es]=Catalán (Valenciano)
Name[et]=Katalaani (valencia)
Name[eu]=Katalana (Valentziera)
Name[fi]=Katalaani (Valencia)
Name[fr]=Catalan (Valence)
Name[ga]=Catalóinis (Vaileinsis)
Name[gl]=Catalán (valenciano)
Name[he]=קטלאנית (Valencian)
Name[hr]=Katalonski (valencijski)
Name[hu]=Katalán (valenciai)
Name[ia]=Catalan (Valencian)
Name[id]=Catalan (Valencia)
Name[is]=Katalónska (Valensía)
Name[it]=Catalano (Valenziano)
Name[ja]=バレンシア語
Name[kk]=Каталанша (Валенсиянша)
Name[km]=កាតាឡាំង (វ៉ាឡេនស៊ៀន)
Name[kn]=ಕೆಟಲಾನ್ (ವೆಲೆನ್ಶಿಯನ್)
Name[ko]=카탈루냐어 (발렌시아)
Name[ku]=Katalan (Valensî)
Name[lt]=Katalonų (Valencijos)
Name[lv]=Kataloņu (Valensijas)
Name[ms]=Catalan (Valencia)
Name[nb]=Katalansk (Valenciansk)
Name[nds]=Katalaansch (valenziaansch)
Name[nl]=Catalaans (Valentiaans)
Name[nn]=Katalansk (Valencia)
Name[pa]=ਕਾਟਾਲਾਨ (ਵਾਲਿਨਸੀਆਈ)
Name[pl]=Kataloński (walencki)
Name[pt]=Catalão (Valenciano)
Name[pt_BR]=Catalão (valenciano)
Name[ro]=Catalană (Valenciană)
Name[ru]=Каталонский (Валенсия)
Name[se]=Kataluniagiella (Valensalaš)
Name[sk]=Katalánčina (Valencia)
Name[sl]=katalonsko (valencijsko)
Name[sq]=Katalane (Valencian)
Name[sr]=каталонски (валенсијски)
Name[sr@ijekavian]=каталонски (валенсијски)
Name[sr@ijekavianlatin]=katalonski (valensijski)
Name[sr@latin]=katalonski (valensijski)
Name[sv]=Katalanska (valenciska)
Name[ta]=கடலான் (வேலன்சியன்)
Name[tg]=Каталан (Валенсӣ)
Name[th]=ภาษาคาตะลาน (วาเลนเซีย)
Name[tr]=Katalan (Valencia)
Name[tt]=Каталан (Валенсия)
Name[ug]=كاتالانچە(ۋالىنسىيە)
Name[uk]=Каталонська (валенсійський діалект)
Name[vi]=Catalan (Valencian)
Name[wa]=Catalan (Valince)
Name[x-test]=xxCatalan (Valencian)xx
Name[zh_CN]=加泰罗尼亚(瓦伦西亚)语
Name[zh_TW]=加泰羅尼亞(瓦倫西亞)語
[ce]
Name=Chechen
Name[af]=Chechen
Name[ar]=شيشانية
Name[as]=ছেছেন্‌
Name[ast]=Chechenu
Name[be]=Чачэнская
Name[be@latin]=Čačenskaja
Name[bg]=Чеченски
Name[bn]=চেচেন
Name[bn_IN]=চেচেন
Name[br]=Tchetcheneg
Name[bs]=Čečenski
Name[ca]=Txetxè
Name[ca@valencia]=Txetxè
Name[cs]=Čečenský
Name[csb]=Czeczeńsczi
Name[cy]=Checheneg
Name[da]=Chechen
Name[de]=Tschetschenisch
Name[el]=Τσετσενικά
Name[en_GB]=Chechen
Name[eo]=Ĉeĉena
Name[es]=Checheno
Name[et]=Tšetšeeni
Name[eu]=Chechen
Name[fa]=چچنی
Name[fi]=Tšetšeeni
Name[fr]=Tchétchène
Name[fy]=Tsjechysk
Name[ga]=Seisnis
Name[gl]=Checheno
Name[gu]=ચેચેન
Name[he]=צ'צ'נית
Name[hi]=चेचेन
Name[hne]=चेचेन
Name[hr]=Čečenski
Name[hsb]=Čečenisce
Name[hu]=Csecsen
Name[ia]=Checheno
Name[id]=Chechen
Name[is]=Tékkneska
Name[it]=Ceceno
Name[ja]=チェチェン語
Name[kk]=Шешенше
Name[km]=ចេចេន
Name[kn]=ಚೆಚೆನ್
Name[ko]=체첸어
Name[ku]=Çeçenî
Name[lb]=Tschetschenesch
Name[lt]=Čėčėnų
Name[lv]=Čečenu
Name[mai]=चेचेन
Name[mk]=Чеченски
Name[ml]=ചെച്ചന്‍
Name[mr]=चेचेन
Name[ms]=Chechen
Name[nb]=Tsjetsjensk
Name[nds]=Tschetscheensch
Name[ne]=चेचेन
Name[nl]=Chechen
Name[nn]=Tsjetsjensk
Name[oc]=Chechen
Name[or]=Chechen
Name[pa]=ਚੇਚਨ
Name[pl]=Czeczeński
Name[ps]=چيچنيايي
Name[pt]=Checheno
Name[pt_BR]=Checheno
Name[ro]=Cecenă
Name[ru]=Чеченский
Name[se]=Čečeniagiella
Name[si]=චෙචන්
Name[sk]=Čečenčina
Name[sl]=čečensko
Name[sq]=Çeçene
Name[sr]=чеченски
Name[sr@ijekavian]=чеченски
Name[sr@ijekavianlatin]=čečenski
Name[sr@latin]=čečenski
Name[sv]=Tjetjenska
Name[ta]=செச்சென்
Name[te]=చెచన్
Name[tg]=Чеченӣ
Name[th]=ภาษาเชเชน
Name[tr]=Çeçen
Name[tt]=Чеченчә
Name[ug]=چېچېنچە
Name[uk]=Чеченська
Name[uz]=Chechencha
Name[uz@cyrillic]=Чеченча
Name[vi]=Chechen
Name[wa]=Tchetchene
Name[xh]=Chechen
Name[x-test]=xxChechenxx
Name[zh_CN]=车臣语
Name[zh_HK]=Chechen語
Name[zh_TW]=車臣語
[ch]
Name=Chamorro
Name[af]=Chamorro
Name[ar]=شامورو
Name[as]=কেমোৰো
Name[ast]=Chamorru
Name[be]=Чаморская
Name[be@latin]=Čamora
Name[bg]=Чаморо
Name[bn]=চামোরো
Name[bn_IN]=চামোরো
Name[br]=Chamorro
Name[bs]=Čamoro
Name[ca]=Chamorro
Name[ca@valencia]=Chamorro
Name[cs]=Chamorro
Name[csb]=Chamorro
Name[cy]=Chamorreg
Name[da]=Chamorro
Name[de]=Chamorro
Name[el]=Chamorro
Name[en_GB]=Chamorro
Name[eo]=Ĉamora
Name[es]=Chamorro
Name[et]=Chamorro
Name[eu]=Chamorro
Name[fa]=کاماروئی
Name[fi]=Tšamorro
Name[fr]=Chamorro
Name[fy]=Chamorro
Name[ga]=Seamóróis
Name[gl]=Chamorro
Name[gu]=ચામોરો
Name[he]=צ'מורו
Name[hi]=केमोरो
Name[hne]=केमोरो
Name[hr]=Čamorski
Name[hsb]=Chamorrisce
Name[hu]=Chamorro
Name[ia]=Chamorro
Name[id]=Chamorro
Name[is]=Chamorró
Name[it]=Chamorro
Name[ja]=チャモロ語
Name[kk]=Чаморро
Name[km]=ចាម៉ូរ៉ូ
Name[kn]=ಚಾಮೊರೊ
Name[ko]=차모로어
Name[ku]=Xamoro
Name[lb]=Chamorro
Name[lt]=Chamorro
Name[lv]=Čamorru
Name[mai]=केमोरो
Name[mk]=Чаморо
Name[ml]=ഷമരോ
Name[mr]=केमोरो
Name[ms]=Chamorro
Name[nb]=Chamorro
Name[nds]=Chamorro
Name[ne]=क्यामोरो
Name[nl]=Chamorro
Name[nn]=Chamorro
Name[oc]=Chamorrá
Name[or]=Chamorro
Name[pa]=ਚਾਮੂਰੂ
Name[pl]=Chamorro
Name[ps]=چمورو
Name[pt]=Chamorro
Name[pt_BR]=Chamorro
Name[ro]=Camoro
Name[ru]=Чаморро
Name[se]=Chamorrogiella
Name[si]=චැමොරෝ
Name[sk]=Čamorčina
Name[sl]=chamorro
Name[sq]=Chamorro
Name[sr]=чаморски
Name[sr@ijekavian]=чаморски
Name[sr@ijekavianlatin]=čamorski
Name[sr@latin]=čamorski
Name[sv]=Chamorro
Name[ta]=சமொரோ
Name[te]=చమొర్రొ
Name[tg]=Чаморроӣ
Name[th]=ภาษาชามอโร
Name[tr]=Chamorro
Name[tt]=Каморроча
Name[ug]=چامورروچە
Name[uk]=Чаморо
Name[uz]=Chamorro
Name[uz@cyrillic]=Чаморро
Name[vi]=Chamorro
Name[wa]=Chamorro
Name[xh]=Chamorro
Name[x-test]=xxChamorroxx
Name[zh_CN]=查莫罗语
Name[zh_HK]=查摩洛語
Name[zh_TW]=查摩洛語
[co]
Name=Corsican
Name[af]=Corsican
Name[ar]=كورسيكي
Name[as]=ক'ৰ্চিয়ান
Name[ast]=Corsicanu
Name[be]=Карсіканская
Name[be@latin]=Karsikanskaja
Name[bg]=Корсикански
Name[bn]=কর্সিকান
Name[bn_IN]=কোর্সিকান
Name[br]=Korseg
Name[bs]=Korzikanski
Name[ca]=Cors
Name[ca@valencia]=Cors
Name[cs]=Korsický
Name[csb]=Kòrsykańsczi
Name[cy]=Corsiceg
Name[da]=Korsikansk
Name[de]=Korsisch
Name[el]=Κορσικανικά
Name[en_GB]=Corsican
Name[eo]=Korsika
Name[es]=Corso
Name[et]=Korsika
Name[eu]=Korsiera
Name[fa]=کورسیکانی
Name[fi]=Korsika
Name[fr]=Corse
Name[fy]=Korsikaansk
Name[ga]=Corsaicis
Name[gl]=Corso
Name[gu]=કોર્સિકન
Name[he]=קורסיקנית
Name[hi]=कॉर्सियन
Name[hne]=कार्सियन
Name[hr]=Korzikanski
Name[hsb]=Korsisce
Name[hu]=Korzikai
Name[ia]=Corsicano
Name[id]=Corsican
Name[is]=Korsíkanska
Name[it]=Corso
Name[ja]=コルシカ語
Name[kk]=Корсиканша
Name[km]=កូសីកា
Name[kn]=ಕೋರ್ಸಿಯನ್
Name[ko]=코르시카어
Name[ku]=Korsî
Name[lb]=Korsesch
Name[lt]=Korsikiečių
Name[lv]=Korsikāņu
Name[mai]=कार्सियन
Name[mk]=Корзикански
Name[ml]=കോര്‍സികന്‍
Name[mr]=कॉर्सियन
Name[ms]=Corsican
Name[nb]=Korsikansk
Name[nds]=Korssch
Name[ne]=कोर्सिकन
Name[nl]=Corsicaans
Name[nn]=Korsikansk
Name[oc]=Còrse
Name[or]=Corsican
Name[pa]=ਕੁਰਸੀਕੇਨ
Name[pl]=Korsykański
Name[ps]=کورسېکن
Name[pt]=Corso
Name[pt_BR]=Corso
Name[ro]=Corsicană
Name[ru]=Корсиканский
Name[se]=Korsikagiella
Name[si]=කෝසිකන්
Name[sk]=Korzičtina
Name[sl]=korzijško
Name[sq]=Korsikane
Name[sr]=корзикански
Name[sr@ijekavian]=корзикански
Name[sr@ijekavianlatin]=korzikanski
Name[sr@latin]=korzikanski
Name[sv]=Korsikanska
Name[ta]=கோர்சிகன்
Name[te]=కొర్సికన్
Name[tg]=Корсиканӣ
Name[th]=ภาษาคอร์ซิกัน
Name[tr]=Korsikaca
Name[tt]=Корсиканча
Name[ug]=كورسىكاچە
Name[uk]=Корсиканська
Name[uz]=Korsikancha
Name[uz@cyrillic]=Корсиканча
Name[vi]=Corsica
Name[wa]=Corse
Name[xh]=Corsican
Name[x-test]=xxCorsicanxx
Name[zh_CN]=科西嘉语
Name[zh_HK]=科西嘉語
Name[zh_TW]=科西嘉語
[crh]
Name=Crimean Tatar
Name[ar]=تتارية القرم
Name[as]=ক্ৰিমিয়ান টাটাৰ
Name[ast]=Tatar de Crimea
Name[be@latin]=Krymskich tataraŭ
Name[bg]=Кримейски татарски
Name[bn]=ক্রিমিয়ান টাটার
Name[bn_IN]=ক্রিমিয়ান তাতার
Name[bs]=Krimsko Tatarski
Name[ca]=Tàtar de Crimea
Name[ca@valencia]=Tàtar de Crimea
Name[cs]=Krymská tatarština
Name[csb]=Tatarsczi (krimsczi)
Name[da]=Krimsk tatar
Name[de]=Krimtatarisch
Name[el]=Ταταρικά Κριμαίας
Name[en_GB]=Crimean Tatar
Name[es]=Tártaro de Crimea
Name[et]=Krimmitatari
Name[eu]=Tatariera
Name[fi]=Krimin tataari
Name[fr]=Tatars de Crimée
Name[fy]=Crimean Tatar
Name[ga]=Tatairis na Crimé
Name[gl]=Tártaro da Crimea
Name[gu]=ક્રિમીઅન તાતાર
Name[he]=טטרית של קרים
Name[hi]=क्रिमियन तातर
Name[hne]=क्रिमियन तातर
Name[hr]=Krimsko-tatarski
Name[hsb]=Krimtatarsce
Name[hu]=Krími tatár
Name[ia]=Crimean Tatar
Name[id]=Crimean Tatar
Name[is]=Tataríska frá Krím
Name[it]=Tataro di Crimea
Name[ja]=クリミア・タタール語
Name[kk]=Қырым татарша
Name[km]=Crimean Tatar
Name[kn]=ಕ್ರಮಿಯನ್ ಟಟಾರ್
Name[ko]=크림 타타르어
Name[ku]=Tatarî ya Kirimî
Name[lt]=Krymo totorių
Name[lv]=Krimas tatāru
Name[mai]=क्रिमियन तातर
Name[mk]=Кримски татарски
Name[ml]=ക്രിമിയന്‍ ടട്ടാര്‍
Name[mr]=क्रिमीयन तातार
Name[ms]=Crimean Tatar
Name[nb]=Krimtatarsk
Name[nds]=Krimtataarsch
Name[nl]=Krim-Tataars
Name[nn]=Krimtatarsk
Name[pa]=ਕਰੀਮੀਨ ਤਾਤਾਰ
Name[pl]=Tatarski (Krymski)
Name[ps]=کريمين ټاټار
Name[pt]=Tatar da Crimeia
Name[pt_BR]=Tártaro da Crimeia
Name[ro]=Tătară din Crimeea
Name[ru]=Крымско-татарский
Name[se]=Krimtataralašgiella
Name[si]=ක්‍රිමීන් ටටාර්
Name[sk]=Krymská tatárčina
Name[sl]=Krimsko tatarsko
Name[sq]=Krimease Tatare
Name[sr]=кримски татарски
Name[sr@ijekavian]=кримски татарски
Name[sr@ijekavianlatin]=krimski tatarski
Name[sr@latin]=krimski tatarski
Name[sv]=Krimtatariska
Name[ta]=கிரீமியன் டாடர்
Name[tg]=Тоторӣ
Name[th]=ภาษาทาทาร์
Name[tr]=Kırım Tatarcası
Name[tt]=Татарча (Кырым)
Name[ug]=قىرىم تاتارچە
Name[uk]=Кримсько-татарська
Name[vi]=Crimean Tatar
Name[wa]=Tatår del Crimêye
Name[x-test]=xxCrimean Tatarxx
Name[zh_CN]=克里米亚鞑靼语
Name[zh_TW]=Crimean Tatar
[cs]
Name=Czech
Name[af]=Tsjeggië
Name[ar]=تشيكية
Name[as]=ছেক
Name[ast]=Checu
Name[be]=Чэшская
Name[be@latin]=Českaja
Name[bg]=Чешки
Name[bn]=চেক
Name[bn_IN]=চেক
Name[br]=Tchekeg
Name[bs]=Češki
Name[ca]=Txec
Name[ca@valencia]=Txec
Name[cs]=Český
Name[csb]=Czesczi
Name[cy]=Tsiec
Name[da]=Tjekkisk
Name[de]=Tschechisch
Name[el]=Τσέχικα
Name[en_GB]=Czech
Name[eo]=Ĉeĥa
Name[es]=Checo
Name[et]=Tšehhi
Name[eu]=Txekiera
Name[fa]=چک
Name[fi]=Tšekki
Name[fr]=Tchèque
Name[fy]=Tsjechysk
Name[ga]=Seicis
Name[gl]=Checo
Name[gu]=ચેક
Name[he]=צ'כית
Name[hi]=चेक
Name[hne]=चेक
Name[hr]=Češki
Name[hsb]=Čěsce
Name[hu]=Cseh
Name[ia]=Tchech
Name[id]=Ceko
Name[is]=Tékkneska
Name[it]=Ceco
Name[ja]=チェコ語
Name[kk]=Чехше
Name[km]=ឆេក
Name[kn]=ಚೆಕ್
Name[ko]=체코어
Name[ku]=Çekî
Name[lb]=Tschechesch
Name[lt]=Čekų
Name[lv]=Čehu
Name[mai]=चेक
Name[mk]=Чешки
Name[ml]=ചെക്ക്
Name[mr]=चेक
Name[ms]=Czech
Name[nb]=Tsjekkisk
Name[nds]=Tschechsch
Name[ne]=चेक
Name[nl]=Tsjechisch
Name[nn]=Tsjekkisk
Name[oc]=Chèc
Name[or]=Czech
Name[pa]=ਚੈੱਕ
Name[pl]=Czeski
Name[ps]=چېک
Name[pt]=Checo
Name[pt_BR]=Tcheco
Name[ro]=Cehă
Name[ru]=Чешский
Name[se]=Čehkagiella
Name[si]=චෙච්
Name[sk]=Čeština
Name[sl]=češko
Name[sq]=Çeke
Name[sr]=чешки
Name[sr@ijekavian]=чешки
Name[sr@ijekavianlatin]=češki
Name[sr@latin]=češki
Name[sv]=Tjeckiska
Name[ta]=செக்
Name[te]=చెక్
Name[tg]=Чехӣ
Name[th]=ภาษาเชค
Name[tr]=Çekçe
Name[tt]=Чех
Name[ug]=چېخچە
Name[uk]=Чеська
Name[uz]=Chexcha
Name[uz@cyrillic]=Чехча
Name[vi]=Séc
Name[wa]=Tcheke
Name[xh]=Czech
Name[x-test]=xxCzechxx
Name[zh_CN]=捷克语
Name[zh_HK]=捷克語
Name[zh_TW]=捷克語
[csb]
Name=Kashubian
Name[af]=Kashubian
Name[ar]=لغة الكاشبيان
Name[as]=কাছুবিয়ান
Name[ast]=Kashubianu
Name[be@latin]=Kašubskaja
Name[bg]=Кашуби
Name[bn]=কাশুবিয়ান
Name[bn_IN]=কাশুবিয়ান
Name[bs]=Kašupski
Name[ca]=Caixubi
Name[ca@valencia]=Caixubi
Name[cs]=Kašubský
Name[csb]=Kaszëbsczi
Name[da]=Kashubiansk
Name[de]=Kaschubisch
Name[el]=Kασουβιανά
Name[en_GB]=Kashubian
Name[eo]=Kaŝuba
Name[es]=Casubio
Name[et]=Kašuubi
Name[eu]=Kaxubiera
Name[fi]=Kašubi
Name[fr]=Kachoube
Name[fy]=Kashubian
Name[ga]=Caisiúibis
Name[gl]=Kashubí
Name[gu]=કોસુબિન
Name[he]=קאשובית
Name[hi]=काशुबियन
Name[hne]=कासुबियन
Name[hr]=Kašupski
Name[hsb]=Kašubsce
Name[hu]=Kasub
Name[ia]=Kashubian
Name[id]=Kashubian
Name[is]=Kashubian
Name[it]=Casciubico
Name[ja]=カシューブ語
Name[kk]=Кашубша
Name[km]=កាស៊ូបៀន
Name[kn]=ಕಷುಬಿಯನ್
Name[ko]=카슈비아어
Name[ku]=Kaşûbî
Name[lt]=Kašubų
Name[lv]=Kašubāņu
Name[mai]=काशुबिनयन
Name[mk]=Кашубски
Name[ml]=കശൂബ്യന്‍
Name[mr]=कासुबियाई
Name[ms]=Kashubian
Name[nb]=Kasjubisk
Name[nds]=Kaschuubsch
Name[nl]=Kashubian
Name[nn]=Kasjubisk
Name[oc]=Kashbian
Name[or]=Kashubian
Name[pa]=ਕਾਸ਼ੂਬੀਅਨ
Name[pl]=Kaszubski
Name[ps]=کشوبي
Name[pt]=Kashubian
Name[pt_BR]=Cassúbia
Name[ro]=Kashubian
Name[ru]=Кашубский
Name[se]=Kašubiagiella
Name[si]=කෂුබියන්
Name[sk]=Kašubčina
Name[sl]=kašubijsko
Name[sq]=Kashubian
Name[sr]=кашупски
Name[sr@ijekavian]=кашупски
Name[sr@ijekavianlatin]=kašupski
Name[sr@latin]=kašupski
Name[sv]=Kasjubiska
Name[ta]=கஷுபியன்
Name[tg]=Кошубиянӣ
Name[th]=ภาษาคาชูเบียน
Name[tr]=Kashubian
Name[tt]=Кашуб
Name[ug]=كاشۇبىچە
Name[uk]=Кашубська
Name[vi]=Kashubian
Name[wa]=Cachoube
Name[x-test]=xxKashubianxx
Name[zh_CN]=卡舒比语
Name[zh_TW]=Kashubian
[cu]
Name=Church Slavic
Name[af]=Church Slavic
Name[ar]=سلافية الكنيسة
Name[as]=চাৰ্চ স্লাভিক
Name[ast]=Eslavu eclesiásticu
Name[be]=Царкоўна-славянская
Name[be@latin]=Carkoŭna-słavianskaja
Name[bg]=Църковнославянски
Name[bn]=চার্চ স্লাভিক
Name[bn_IN]=চার্চ স্লাভিক
Name[br]=Slaveg an iliz
Name[bs]=Crkvenoslavenski
Name[ca]=Eslau eclesiàstic
Name[ca@valencia]=Eslau eclesiàstic
Name[cs]=Staroslověnský
Name[csb]=Stôrôcerwiowò-słowiańsczi
Name[cy]=Slaveg Eglwysol
Name[da]=Kirkeslavisk
Name[de]=Kirchenslawisch
Name[el]=Εκκλησιαστικά Σλαβικά (Σλαβονικά)
Name[en_GB]=Church Slavic
Name[eo]=Eklezia Slava
Name[es]=Eslavo eclesiástico
Name[et]=Kirikuslaavi
Name[eu]=Church Eslaviera
Name[fa]=یوگوسلاویایی
Name[fi]=Kirkkoslaavi
Name[fr]=Vieux-slave liturgique
Name[fy]=Tsjerk-slavysk
Name[ga]=Slaivis Eaglaise
Name[gl]=Eslavo eclesiástico
Name[gu]=ચર્ચ સાલ્વિક
Name[he]=סלבית כנסייתית
Name[hi]=चर्च स्लाविक
Name[hne]=चर्च स्लाविक
Name[hr]=Crkveni pravoslavni
Name[hsb]=Cyrkwinosłowjansce
Name[hu]=Szláv (egyházi)
Name[ia]=Slavo Ecclesial
Name[id]=Church Slavic
Name[is]=Kirkju-slavneska
Name[it]=Slavo ecclesiastico
Name[ja]=教会スラブ語
Name[kk]=Шірке славянша
Name[km]=ឆឺច​ស្លាវិច
Name[kn]=ಚರ್ಚ್ ಸ್ಲಾವಿಕ್
Name[ko]=교회 슬라브어
Name[ku]=Slavikiya Kilîseyî
Name[lb]=Kiircheslawesch
Name[lt]=Bažnytinė slavų
Name[lv]=Slāvu baznīcas
Name[mai]=चर्च स्लाविक
Name[mk]=Црковнословенски
Name[ml]=ചര്‍ച്ച് സ്ലാവിക്
Name[mr]=चर्च स्लाविक
Name[ms]=Church Slavic
Name[nb]=Kirkeslavisk
Name[nds]=Ooltslaawsch
Name[ne]=चर्च स्लाभिक
Name[nl]=Kerk Slavisch
Name[nn]=Kyrkjeslavisk
Name[or]=Church Slavic
Name[pa]=ਚੂਰਚ ਸਲਾਵਿਨ
Name[pl]=Starocerkiewno-słowiański
Name[ps]=چرچ سلاويکي
Name[pt]=Igreja Eslava
Name[pt_BR]=Eslavo eclesiástico
Name[ro]=Slavă bisericească
Name[ru]=Церковнославянский
Name[se]=Slávagiella
Name[si]=චර්ච් ස්ලැවික්
Name[sk]=Cirkevná slovančina
Name[sl]=cerkvena slovanščina
Name[sq]=Sllave Kishtare
Name[sr]=црквенословенски
Name[sr@ijekavian]=црквенословенски
Name[sr@ijekavianlatin]=crkvenoslovenski
Name[sr@latin]=crkvenoslovenski
Name[sv]=Kyrkoslaviska
Name[ta]=சர்ச் ஸ்லாவிக்
Name[te]=చర్చి స్లావిక్
Name[tg]=Славянии динӣ
Name[th]=ภาษาเชิร์ชสลาฟ
Name[tr]=Church Slavic
Name[tt]=Чиркәү Словакча
Name[ug]=چېركاۋ سلاۋيانچە
Name[uk]=Церковнослов'янська
Name[uz]=Cherkov-slovyancha
Name[uz@cyrillic]=Черков-словянча
Name[vi]=Nhà thờ Slavic
Name[wa]=Eslave d' eglijhe
Name[xh]=Church Slavic
Name[x-test]=xxChurch Slavicxx
Name[zh_CN]=古教会斯拉夫语
Name[zh_HK]=教堂斯拉夫語
Name[zh_TW]=教堂斯拉夫語
[cv]
Name=Chuvash
Name[af]=Chuvash
Name[ar]=شوفاش
Name[as]=ছুভাশ্ব
Name[ast]=Chuvash
Name[be]=Чувашская
Name[be@latin]=Čuvaskaja
Name[bg]=Чувашки
Name[bn]=চুভাশ
Name[bn_IN]=চুভাশ
Name[br]=Tchuvasheg
Name[bs]=Čuvaški
Name[ca]=Chuvash
Name[ca@valencia]=Chuvash
Name[cs]=Čuvašský
Name[csb]=Czuwasczi
Name[cy]=Chuvasheg
Name[da]=Chuvash
Name[de]=Tschuwaschisch
Name[el]=Chuvash
Name[en_GB]=Chuvash
Name[eo]=Ĉuvaŝa
Name[es]=Chuvasio
Name[et]=Tšuvaši
Name[eu]=Chuvash
Name[fa]=چواش
Name[fi]=Tšuvassi
Name[fr]=Tchouvache
Name[fy]=Chuvash
Name[ga]=Suvaisis
Name[gl]=Chuvash
Name[gu]=ચુવાસ
Name[he]=צ'ובשית
Name[hi]=चाउवेश
Name[hne]=चाउवेस
Name[hr]=Čuvaški
Name[hsb]=Čuwašisce
Name[hu]=Csuvas
Name[ia]=Chuvash
Name[id]=Chuvash
Name[is]=Chuvash
Name[it]=Ciuvascio
Name[ja]=チュワシュ語
Name[kk]=Шуашша
Name[km]=ចូវ៉ាស
Name[kn]=ಚುವಾಷ್
Name[ko]=추바시어
Name[ku]=Çuvaş
Name[lb]=Chuvas
Name[lt]=Čiuvašų
Name[lv]=Čuvašu
Name[mai]=चुवाश
Name[mk]=Чуваш
Name[ml]=ഷുവാഷ്
Name[mr]=चाउवेश
Name[ms]=Chuvash
Name[nb]=Chuvash
Name[nds]=Tschuwasch
Name[ne]=चुभास
Name[nl]=Chuvash
Name[nn]=Tsjuvasjisk
Name[oc]=Chuvash
Name[or]=Chuvash
Name[pa]=ਚੂਵਸ਼
Name[pl]=Czuwaski
Name[ps]=چووېش
Name[pt]=Chuvash
Name[pt_BR]=Tchuvache
Name[ro]=Ciuvașă
Name[ru]=Чувашский
Name[se]=Chuvashgiella
Name[si]=චුවෑෂ්
Name[sk]=Čuvaština
Name[sl]=chuvash
Name[sq]=Çuvashe
Name[sr]=чувашки
Name[sr@ijekavian]=чувашки
Name[sr@ijekavianlatin]=čuvaški
Name[sr@latin]=čuvaški
Name[sv]=Tjuvasjiska
Name[ta]=சுவாஷ்
Name[te]=చువాష్
Name[tg]=Чувашӣ
Name[th]=ภาษาชูวาช
Name[tr]=Çuvaşça
Name[tt]=Чувашский
Name[ug]=چۇۋاشچە
Name[uk]=Чуваська
Name[uz]=Chuvashcha
Name[uz@cyrillic]=Чувашча
Name[vi]=Chuvash
Name[wa]=Tchouvache
Name[xh]=Chuvash
Name[x-test]=xxChuvashxx
Name[zh_CN]=楚瓦什语
Name[zh_HK]=楚瓦士語
Name[zh_TW]=楚瓦士語
[cy]
Name=Welsh
Name[af]=Wallies
Name[ar]=غالية
Name[as]=ৱেল্‌শ্ব
Name[ast]=Galés
Name[be]=Уэльская
Name[be@latin]=Valijskaja
Name[bg]=Уелски
Name[bn]=ওয়েল্‌শ
Name[bn_IN]=ওয়েলশ
Name[br]=Kembraeg
Name[bs]=Velški
Name[ca]=Gal·lès
Name[ca@valencia]=Gal·lès
Name[cs]=Welšský
Name[csb]=Walijsczi
Name[cy]=Cymraeg
Name[da]=Walisisk
Name[de]=Walisisch
Name[el]=Ουαλικά
Name[en_GB]=Welsh
Name[eo]=Kimra
Name[es]=Galés
Name[et]=Uelsi
Name[eu]=Galesa
Name[fa]=ولزی
Name[fi]=Kymri
Name[fr]=Gallois
Name[fy]=Welsk
Name[ga]=Breatnais
Name[gl]=Galés
Name[gu]=વેલ્સ
Name[he]=וולשית
Name[hi]=वेल्श
Name[hne]=वेल्स
Name[hr]=Velški
Name[hsb]=Kymrisce
Name[hu]=Velszi
Name[ia]=Gallese
Name[id]=Welsh
Name[is]=Velska
Name[it]=Gallese
Name[ja]=ウェールズ語
Name[kk]=Уэлсше
Name[km]=វែល
Name[kn]=ವೆಲ್ಷ್
Name[ko]=웨일스어
Name[ku]=Galî
Name[lb]=Walisesch
Name[lt]=Velsiečių
Name[lv]=Velsiešu
Name[mai]=वेल्श
Name[mk]=Велшки
Name[ml]=വെല്‍ഷ്
Name[mr]=वेल्श
Name[ms]=Wales
Name[nb]=Walisisk
Name[nds]=Waliessch
Name[ne]=वेल्स
Name[nl]=Welsh
Name[nn]=Walisisk
Name[oc]=Galés
Name[or]=Welsh
Name[pa]=ਵਾਲਿਸ਼
Name[pl]=Walijski
Name[ps]=وېلش
Name[pt]=Galês
Name[pt_BR]=Galês
Name[ro]=Velșă
Name[ru]=Валлийский
Name[se]=Walesagiella
Name[si]=වෙල්ෂ්
Name[sk]=Waleština
Name[sl]=valižansko
Name[sq]=Uellsiane
Name[sr]=велшки
Name[sr@ijekavian]=велшки
Name[sr@ijekavianlatin]=velški
Name[sr@latin]=velški
Name[sv]=Walesiska
Name[ta]=வெல்ஷ்
Name[te]=వెల్ష్
Name[tg]=Уэлсӣ
Name[th]=ภาษาเวลช์
Name[tr]=Gallerce
Name[tt]=Уэльс
Name[ug]=ۋېلشچە
Name[uk]=Уельська
Name[uz]=Uelscha
Name[uz@cyrillic]=Уэлсча
Name[vi]=Welsh
Name[wa]=Walès
Name[xh]=Welsh
Name[x-test]=xxWelshxx
Name[zh_CN]=威尔士语
Name[zh_HK]=威爾斯語
Name[zh_TW]=威爾斯語
[da]
Name=Danish
Name[af]=Deens
Name[ar]=دنماركية
Name[as]=ডেনিশ্ব
Name[ast]=Danés
Name[be]=Дацкая
Name[be@latin]=Dackaja
Name[bg]=Датски
Name[bn]=ড্যানিশ
Name[bn_IN]=ডেনিশ
Name[br]=Daneg
Name[bs]=Danski
Name[ca]=Danès
Name[ca@valencia]=Danés
Name[cs]=Dánský
Name[csb]=Dëńsczi
Name[cy]=Daneg
Name[da]=Dansk
Name[de]=Dänisch
Name[el]=Δανέζικα
Name[en_GB]=Danish
Name[eo]=Dana
Name[es]=Danés
Name[et]=Taani
Name[eu]=Daniera
Name[fa]=دانمارکی
Name[fi]=Tanska
Name[fr]=Danois
Name[fy]=Deensk
Name[ga]=Danmhairgis
Name[gl]=Dinamarqués
Name[gu]=ડેનિશ
Name[he]=דנית
Name[hi]=दानिश
Name[hne]=दानिस
Name[hr]=Danski
Name[hsb]=Dansce
Name[hu]=Dán
Name[ia]=Danese
Name[id]=Denmark
Name[is]=Danska
Name[it]=Danese
Name[ja]=デンマーク語
Name[kk]=Датша
Name[km]=ដាណឺម៉ាក
Name[kn]=ಡ್ಯಾನಿಷ್
Name[ko]=덴마크어
Name[ku]=Danmarkî
Name[lb]=Dänesch
Name[lt]=Danų
Name[lv]=Dāņu
Name[mai]=डैनिश
Name[mk]=Дански
Name[ml]=ഡാനിഷ്
Name[mr]=दानिश
Name[ms]=Danish
Name[nb]=Dansk
Name[nds]=Däänsch
Name[ne]=डेनिश
Name[nl]=Deens
Name[nn]=Dansk
Name[oc]=Danés
Name[or]=Danish
Name[pa]=ਡੈਨਿਸ਼
Name[pl]=Duński
Name[ps]=ډېنېش
Name[pt]=Dinamarquês
Name[pt_BR]=Dinamarquês
Name[ro]=Daneză
Name[ru]=Датский
Name[se]=Dánskkagiella
Name[si]=ඩැනිෂ්
Name[sk]=Dánčina
Name[sl]=dansko
Name[sq]=Daneze
Name[sr]=дански
Name[sr@ijekavian]=дански
Name[sr@ijekavianlatin]=danski
Name[sr@latin]=danski
Name[sv]=Danska
Name[ta]=டேனிஷ்
Name[te]=డెనిష్
Name[tg]=Даниявӣ
Name[th]=ภาษาเดนมาร์ค
Name[tr]=Danimarka Dili
Name[tt]=Дания
Name[ug]=دانىشچە
Name[uk]=Данська
Name[uz]=Daniyacha
Name[uz@cyrillic]=Данияча
Name[vi]=Đan Mạch
Name[wa]=Daenwès
Name[xh]=Danish
Name[x-test]=xxDanishxx
Name[zh_CN]=丹麦语
Name[zh_HK]=丹麥語
Name[zh_TW]=丹麥語
[de]
Name=German
Name[af]=Duits
Name[ar]=ألمانية
Name[as]=জাৰ্মান
Name[ast]=Alemán
Name[be]=Нямецкая
Name[be@latin]=Niamieckaja
Name[bg]=Немски
Name[bn]=জার্মান
Name[bn_IN]=জার্মান
Name[br]=Alamaneg
Name[bs]=Njemački
Name[ca]=Alemany
Name[ca@valencia]=Alemany
Name[cs]=Německý
Name[csb]=Miemiecczi
Name[cy]=Almaeneg
Name[da]=Tysk
Name[de]=Deutsch
Name[el]=Γερμανικά
Name[en_GB]=German
Name[eo]=Germana
Name[es]=Alemán
Name[et]=Saksa
Name[eu]=Alemaniera
Name[fa]=آلمانی
Name[fi]=Saksa
Name[fr]=Allemand
Name[fy]=Dútsk
Name[ga]=Gearmáinis
Name[gl]=Alemán
Name[gu]=જર્મન
Name[he]=גרמנית
Name[hi]=जर्मन
Name[hne]=जर्मन
Name[hr]=Njemački
Name[hsb]=Němsce
Name[hu]=Német
Name[hy]=Գերմաներեն
Name[ia]=Germano
Name[id]=Jerman
Name[is]=Þýska
Name[it]=Tedesco
Name[ja]=ドイツ語
Name[kk]=Немісше
Name[km]=អាល្លឺម៉ង់
Name[kn]=ಜರ್ಮನ್
Name[ko]=독일어
Name[ku]=Almanî
Name[lb]=Däitsch
Name[lt]=Vokiečių
Name[lv]=Vācu
Name[mai]=जर्मन
Name[mk]=Германски
Name[ml]=ജര്‍മ്മന്‍
Name[mr]=जर्मन
Name[ms]=Jerman
Name[nb]=Tysk
Name[nds]=Hoochdüütsch
Name[ne]=जर्मन
Name[nl]=Duits
Name[nn]=Tysk
Name[oc]=Aleman
Name[or]=German
Name[pa]=ਜਰਮਨ
Name[pl]=Niemiecki
Name[ps]=جرمن
Name[pt]=Alemão
Name[pt_BR]=Alemão
Name[ro]=Germană
Name[ru]=Немецкий
Name[se]=Duiskkagiella
Name[si]=ජර්මානු
Name[sk]=Nemčina
Name[sl]=nemško
Name[sq]=Gjermanisht
Name[sr]=немачки
Name[sr@ijekavian]=њемачки
Name[sr@ijekavianlatin]=njemački
Name[sr@latin]=nemački
Name[sv]=Tyska
Name[ta]=ஜெர்மன்
Name[te]=జెర్మన్
Name[tg]=Немисӣ
Name[th]=ภาษาเยอรมัน
Name[tr]=Almanca
Name[tt]=Алман
Name[ug]=گېرمانچە
Name[uk]=Німецька
Name[uz]=Olmoncha
Name[uz@cyrillic]=Олмонча
Name[vi]=Đức
Name[wa]=Almand
Name[xh]=Isijamani
Name[x-test]=xxGermanxx
Name[zh_CN]=德语
Name[zh_HK]=德語
Name[zh_TW]=德語
[dsb]
Name=Lower Sorbian
Name[ar]=صوربية سفلى
Name[as]=লোৱাৰ ছৰ্বিয়ান
Name[ast]=Baxu Sorbiu
Name[be@latin]=Nižnie-łužyckaja
Name[bg]=Долносорбийски
Name[bn]=নিম্ন সার্বীয়
Name[bn_IN]=লোয়ার সোর্বিয়ান
Name[bs]=Donjolužičkosrpski
Name[ca]=Baix sòrab
Name[ca@valencia]=Baix sòrab
Name[cs]=Dolnolužicko srbský
Name[csb]=Dolnosorbsczi
Name[da]=Nedre sorbisk
Name[de]=Niedersorbisch
Name[el]=Κάτω Σορβικά
Name[en_GB]=Lower Sorbian
Name[eo]=Malsuprasoraba
Name[es]=Bajo sorabo
Name[et]=Alamsorbi
Name[eu]=Behe Serbiera
Name[fa]=صربی پایینی
Name[fi]=Alasorbi
Name[fr]=Bas-Sorabe
Name[fy]=Leech Sorbysk
Name[ga]=Sorbais Íochtarach
Name[gl]=Baixo sórabo
Name[gu]=લોઅર સોર્બિયન
Name[he]=סורבית תחתית
Name[hi]=निचला सॉर्बियाई
Name[hne]=लोअर सार्बियाई
Name[hr]=Donjesrpski
Name[hu]=Alsó szorb
Name[ia]=Alto Sorbiano
Name[id]=Sorbian Bawah
Name[is]=Lág-sorbian
Name[it]=Basso sorabo
Name[ja]=下ソルブ語
Name[kk]=Төменгі сорбше
Name[km]=សូបៀន ក្រោម
Name[kn]=ಕೆಳಗಿಲಿನ ಸೋರ್ಬಿಯನ್
Name[ko]=저지대 소르비아어
Name[ku]=Sorbiya Jêrîn
Name[lt]=Lower Sorbian
Name[lv]=Apakšsorbu
Name[mai]=नीच्चाँक सोर्बियन
Name[ml]=ലോവര്‍ സോര്‍ബിയന്‍
Name[ms]=Lower Sorbian
Name[nb]=Nedresorbisk
Name[nds]=Neddersorbsch
Name[nl]=Nedersorbisch
Name[nn]=Lågsorbisk
Name[pa]=ਲੋਅਰ ਸਰਬੀਆਈ
Name[pl]=Dolnołużycki
Name[pt]=Sérvio de Baixo
Name[pt_BR]=Baixo Sorábio
Name[ro]=Sîrbă de Jos
Name[ru]=Нижнелужицкий
Name[se]=Vuolle Sorbiagiella
Name[si]=පහළ සෝබියානු
Name[sk]=Dolnolužická srbčina
Name[sl]=Zgornjesorbijsko
Name[sq]=Sorbiane e Ulët
Name[sr]=доњолужичкосрпски
Name[sr@ijekavian]=доњолужичкосрпски
Name[sr@ijekavianlatin]=donjolužičkosrpski
Name[sr@latin]=donjolužičkosrpski
Name[sv]=Lågsorbiska
Name[ta]=சொர்பியன் சிற்றெழுத்து
Name[tg]=Сербияи Поён
Name[th]=ภาษาซอร์เบียนตอนล่าง
Name[tr]=Aşağı Sırpça
Name[tt]=Сорбча (Аскы)
Name[ug]=تۆۋەن سوربىيانچە
Name[uk]=Нижньолужицька
Name[vi]=Lower Sorbian
Name[wa]=Bas sorbyin
Name[x-test]=xxLower Sorbianxx
Name[zh_CN]=下索布语
Name[zh_TW]=下塞爾維亞語
[dz]
Name=Dzongkha
Name[af]=Dzongkha
Name[ar]=جونغا
Name[as]=জঙ্কা
Name[ast]=Dzongkha
Name[be]=Цонга
Name[be@latin]=Dzongkha
Name[bg]=Джонкгха
Name[bn]=জঙখা
Name[bn_IN]=জোংগা
Name[br]=Dzongkha
Name[bs]=Džongka
Name[ca]=Dzongkha
Name[ca@valencia]=Dzongkha
Name[cs]=Dzongkha
Name[csb]=Dzongkha
Name[cy]=Dzongkhaeg
Name[da]=Dzongkha
Name[de]=Dzongkha
Name[el]=Dzongkha
Name[en_GB]=Dzongkha
Name[eo]=Dzonka
Name[es]=Dzongkha
Name[et]=Dzongkha
Name[eu]=Dzongkha
Name[fa]=زونگخا
Name[fi]=Dzongkha
Name[fr]=Dzongkha
Name[fy]=Dzongkha
Name[ga]=Dzongkha
Name[gl]=Dzongca
Name[gu]=ઝોન્ખા
Name[he]=ג'ונקה
Name[hi]=झोंखा
Name[hne]=झोंखा
Name[hr]=Dzongkha
Name[hsb]=Dzongkhisce
Name[hu]=Dzongkha
Name[ia]=Dzongkha
Name[id]=Dzongkha
Name[is]=Dzongkha
Name[it]=Dzongkha
Name[ja]=ゾンカ語
Name[kk]=(Дзонгха) Бутанша
Name[km]=ដុងហ្កា
Name[kn]=ಡ್ಸೋಂಖಾ
Name[ko]=존카어
Name[ku]=Dzongxa
Name[lb]=Dzongkha
Name[lt]=Dzongkha
Name[lv]=Dzongke
Name[mai]=ड्जोंग्खा
Name[mk]=Џонгха
Name[ml]=സോങ്കാ
Name[mr]=झोंखा
Name[ms]=Dzongkha
Name[nb]=Dzongkha
Name[nds]=Dzongkha
Name[ne]=जोङ्खा
Name[nl]=Dzongkha
Name[nn]=Dzongkha
Name[oc]=Dzongkha
Name[or]=Dzongkha
Name[pa]=ਡਜ਼ੋਨਗਖਾ
Name[pl]=Dzongkha
Name[pt]=Dzongkha
Name[pt_BR]=Dzongkha
Name[ro]=Dzongkha
Name[ru]=Дзонгка (Бутан)
Name[se]=Dzongkhagiella
Name[si]=සොංඛා
Name[sk]=Dzongkä
Name[sl]=dzonkha
Name[sq]=Xonga
Name[sr]=џонка
Name[sr@ijekavian]=џонка
Name[sr@ijekavianlatin]=džonka
Name[sr@latin]=džonka
Name[sv]=Dzongkha
Name[ta]=டிசொங்க்ஹா
Name[te]=జొంగ్ఖ
Name[tg]=Дзонгка
Name[th]=ภาษาจงข่า
Name[tr]=Dzongkha
Name[tt]=Дзонг-кэ
Name[ug]=بۇتانچە
Name[uk]=Дзонг-ке
Name[uz]=Dzongxa
Name[uz@cyrillic]=Дзонгха
Name[vi]=Dzongkha
Name[wa]=Boutanès
Name[xh]=Dzongkha
Name[x-test]=xxDzongkhaxx
Name[zh_CN]=不丹语
Name[zh_HK]=Dzongkha語
Name[zh_TW]=Dzongkha語
[el]
Name=Greek
Name[af]=Grieks
Name[ar]=يونانية
Name[as]=গ্ৰীক
Name[ast]=Griegu
Name[be]=Грэцкая
Name[be@latin]=Hreckaja
Name[bg]=Гръцки
Name[bn]=গ্রীক
Name[bn_IN]=গ্রিক
Name[br]=Gresianeg
Name[bs]=Grčki
Name[ca]=Grec
Name[ca@valencia]=Grec
Name[cs]=Řecký
Name[csb]=Grecczi
Name[cy]=Groeg
Name[da]=Græsk
Name[de]=Griechisch
Name[el]=Ελληνικά
Name[en_GB]=Greek
Name[eo]=Greka
Name[es]=Griego
Name[et]=Kreeka
Name[eu]=Grekoa
Name[fa]=یونانی
Name[fi]=Kreikka
Name[fr]=Grec
Name[fy]=Gryks
Name[ga]=Gréigis
Name[gl]=Grego
Name[gu]=ગ્રીક
Name[he]=יוונית
Name[hi]=यूनानी
Name[hne]=यूनानी
Name[hr]=Grčki
Name[hsb]=Grjeksce
Name[hu]=Görög
Name[hy]=Հուներեն
Name[ia]=Greco
Name[id]=Yunani
Name[is]=Gríska
Name[it]=Greco
Name[ja]=ギリシャ語
Name[kk]=Грекше
Name[km]=ក្រិក
Name[kn]=ಗ್ರೀಕ್
Name[ko]=그리스어
Name[ku]=Yewnanî
Name[lb]=Griichesch
Name[lt]=Graikų
Name[lv]=Grieķu
Name[mai]=ग्रीक
Name[mk]=Грчки
Name[ml]=ഗ്രീക്ക്
Name[mr]=ग्रीक
Name[ms]=Greek
Name[nb]=Gresk
Name[nds]=Greeksch
Name[ne]=ग्रीक
Name[nl]=Grieks
Name[nn]=Gresk
Name[oc]=Grèc
Name[or]=Greek
Name[pa]=ਗਰੀਕ
Name[pl]=Grecki
Name[ps]=يوناني
Name[pt]=Grego
Name[pt_BR]=Grego
Name[ro]=Greacă
Name[ru]=Греческий
Name[se]=Greikkagiella
Name[si]=ග්‍රීක
Name[sk]=Gréčtina
Name[sl]=grško
Name[sq]=Greqisht
Name[sr]=грчки
Name[sr@ijekavian]=грчки
Name[sr@ijekavianlatin]=grčki
Name[sr@latin]=grčki
Name[sv]=Grekiska
Name[ta]=கிரேக்கம்
Name[te]=గ్రీక్
Name[tg]=Юнонӣ
Name[th]=ภาษากรีก
Name[tr]=Yunanca
Name[tt]=Грек
Name[ug]=گىرېكچە
Name[uk]=Грецька
Name[uz]=Yunoncha
Name[uz@cyrillic]=Юнонча
Name[vi]=Hy Lạp
Name[wa]=Grek
Name[xh]=isigrike
Name[x-test]=xxGreekxx
Name[zh_CN]=希腊语
Name[zh_HK]=希臘語
Name[zh_TW]=希臘語
[en]
Name=English
Name[af]=Engels
Name[ar]=إنجليزية
Name[as]=ইংৰাজী
Name[ast]=Inglés
Name[be]=Англійская
Name[be@latin]=Anhielskaja
Name[bg]=Английски
Name[bn]=ইংরেজ
Name[bn_IN]=ইংরেজি
Name[br]=Saozneg
Name[bs]=Engleski
Name[ca]=Anglès
Name[ca@valencia]=Anglés
Name[cs]=Anglický
Name[csb]=Anielsczi
Name[cy]=Saesneg
Name[da]=Engelsk
Name[de]=Englisch
Name[el]=Αγγλικά
Name[en_GB]=English
Name[eo]=Angla
Name[es]=Inglés
Name[et]=Inglise
Name[eu]=Ingelesa
Name[fa]=انگلیسی
Name[fi]=Englanti
Name[fr]=Anglais
Name[fy]=Ingelsk
Name[ga]=Béarla
Name[gl]=Inglés
Name[gu]=અંગ્રેજી
Name[he]=אנגלית
Name[hi]=अंग्रेज़ी
Name[hne]=अंगरेजी
Name[hr]=Engleski
Name[hsb]=Jendźelsce
Name[hu]=Angol
Name[hy]=Անգլերեն
Name[ia]=Anglese
Name[id]=Inggris
Name[is]=Enska
Name[it]=Inglese
Name[ja]=英語
Name[kk]=Ағылшынша
Name[km]=អង់គ្លេស
Name[kn]=ಇಂಗ್ಲೀಷ್
Name[ko]=영어
Name[ku]=Îngilîzî
Name[lb]=Englesch
Name[lt]=Anglų
Name[lv]=Angļu
Name[mai]=अंग्रेजी
Name[mk]=Англиски
Name[ml]=ഇംഗ്ലീഷ്
Name[mr]=अंग्रेज़ी
Name[ms]=Inggeris
Name[nb]=Engelsk
Name[nds]=Engelsch
Name[ne]=अङ्ग्रेजी
Name[nl]=Engels
Name[nn]=Engelsk
Name[oc]=Anglés
Name[or]=English
Name[pa]=ਅੰਗਰੇਜ਼ੀ
Name[pl]=Angielski
Name[ps]=انګريزي
Name[pt]=Inglês
Name[pt_BR]=Inglês
Name[ro]=Engleză
Name[ru]=Английский
Name[se]=Eŋgelasgiella
Name[si]=ඉංග්‍රීසි
Name[sk]=Angličtina
Name[sl]=angleško
Name[sq]=Anglisht
Name[sr]=енглески
Name[sr@ijekavian]=енглески
Name[sr@ijekavianlatin]=engleski
Name[sr@latin]=engleski
Name[sv]=Engelska
Name[ta]=ஆங்கிலம்
Name[te]=ఆంగ్లం
Name[tg]=Англисӣ
Name[th]=ภาษาอังกฤษ
Name[tr]=İngilizce
Name[tt]=Инглиз
Name[ug]=ئىنگلىزچە
Name[uk]=Англійська
Name[uz]=Inglizcha
Name[uz@cyrillic]=Инглизча
Name[vi]=Anh
Name[wa]=Inglès
Name[xh]=Isingesi
Name[x-test]=xxEnglishxx
Name[zh_CN]=英语
Name[zh_HK]=英語
Name[zh_TW]=英語
[en_GB]
Name=British English
Name[af]=Britse Engels
Name[ar]=إنجليزية بريطانية
Name[as]=বিটিশ্ব ইংৰাজী
Name[ast]=Inglés Británicu
Name[be]=Англійская брытанская
Name[be@latin]=Brytanskaja anhielskaja
Name[bg]=Британски английски
Name[bn]=বৃটিশ ইংরেজি
Name[bn_IN]=ব্রিটিশ ইংরাজি
Name[br]=Saozneg eus Bro Saoz
Name[bs]=Engleski(Britanski)
Name[ca]=Anglès britànic
Name[ca@valencia]=Anglés britànic
Name[cs]=Britská angličtina
Name[csb]=Britijsczi anielsczi
Name[cy]=Saesneg Prydain
Name[da]=Britisk engelsk
Name[de]=Englisch (UK)
Name[el]=Αγγλικά (Μ. Βρετανίας)
Name[en_GB]=British English
Name[eo]=Angla (Brita)
Name[es]=Inglés británico
Name[et]=Briti inglise
Name[eu]=Ingelesa (britainiarra)
Name[fa]=انگلیسی بریتانیایی
Name[fi]=Britannianenglanti
Name[fr]=Anglais britannique
Name[fy]=Britsk Ingelsk
Name[ga]=Béarla na Breataine
Name[gl]=Inglés británico
Name[gu]=બ્રિટિશ અંગ્રેજી
Name[he]=אנגלית בריטית
Name[hi]=ब्रितानी अंग्रेज़ी
Name[hne]=ब्रितानी अंगरेजी
Name[hr]=Britanski Engleski
Name[hsb]=Jendźelsce (WB)
Name[hu]=Angol (brit)
Name[hy]=Բրիտանական Անգլերեն
Name[ia]=Anglese Britannic
Name[id]=Inggris Britania
Name[is]=Bresk enska
Name[it]=Inglese britannico
Name[ja]=イギリス英語
Name[kk]=Британдық ағылшынша
Name[km]=អង់គ្លេស (អង់គ្លេស)
Name[kn]=ಬ್ರಿಟಿಷ್ ಇಂಗ್ಲಿಷ್
Name[ko]=영국 영어
Name[ku]=Îngilîziya Brîtanyayê
Name[lb]=Britescht Englesch
Name[lt]=Anglų (D.Britanijos)
Name[lv]=Britu angļu
Name[mai]=ब्रिटिश अंग्रेजी
Name[mk]=Англиски (британски)
Name[ml]=ബ്രിട്ടീഷ് ഇംഗ്ലീഷ്
Name[mr]=ब्रितानी अंग्रेज़ी
Name[ms]=British English
Name[nb]=Britisk engelsk
Name[nds]=Britsch Engelsch
Name[ne]=बेलायती अङ्ग्रेजी
Name[nl]=Engels (Brits)
Name[nn]=Engelsk (Storbritannia)
Name[oc]=Anglés britanic
Name[or]=British English
Name[pa]=ਅੰਗਰੇਜ਼ੀ ਬਰਤਾਨੀਆ
Name[pl]=Angielski brytyjski
Name[ps]=برېټانوي انګريزي
Name[pt]=Inglês da Grã-Bretanha
Name[pt_BR]=Inglês britânico
Name[ro]=Engleză britanică
Name[ru]=Английский (Великобритания)
Name[se]=Eŋgelasgiella (Stuorra Brittania)
Name[si]=බ්‍රිතාන්‍ය ඉංග්‍රීසි
Name[sk]=Britská angličtina
Name[sl]=britansko angleško
Name[sq]=Anglishte Britanike
Name[sr]=британски енглески
Name[sr@ijekavian]=британски енглески
Name[sr@ijekavianlatin]=britanski engleski
Name[sr@latin]=britanski engleski
Name[sv]=Brittisk engelska
Name[ta]=பிரிட்டிஷ் ஆங்கிலம்
Name[te]=బ్రిటిష్ ఆంగ్లం
Name[tg]=Англисӣ (Бритонӣ)
Name[th]=ภาษาอังกฤษ บริติช
Name[tr]=İngiliz İngilizcesi
Name[tt]=Инглиз (Британия)
Name[ug]=ئەنگلىيە ئىنگلىزچە
Name[uk]=Англійська (Великобританія)
Name[uz]=Inglizcha (Angliya)
Name[uz@cyrillic]=Инглизча (Англия)
Name[vi]=Vương quốc Anh
Name[wa]=Inglès britanike
Name[x-test]=xxBritish Englishxx
Name[zh_CN]=英国英语
Name[zh_HK]=英式英語
Name[zh_TW]=(英式)英語
[en_US]
Name=American English
Name[af]=Amerikaanse Engels
Name[ar]=إنجليزية أمريكية
Name[as]=আমেৰিকান ইংৰাজী
Name[ast]=nglés americanu
Name[be]=Англійская амерыканская
Name[be@latin]=Amerykanskaja anhielskaja
Name[bg]=Американски английски
Name[bn]=মার্কিন ইংরেজি
Name[bn_IN]=আমেরিকান ইংরেজি
Name[br]=Saozneg eus SUA
Name[bs]=Engleski(Američki)
Name[ca]=Anglès americà
Name[ca@valencia]=Anglés americà
Name[cs]=Americká angličtina
Name[csb]=Amerikańsczi anielsczi
Name[da]=Amerikansk engelsk
Name[de]=Englisch (US)
Name[el]=Αγγλικά (Αμερικής)
Name[en_GB]=American English
Name[eo]=Angla (Usona)
Name[es]=Inglés americano
Name[et]=Ameerika inglise
Name[eu]=Ingelesa (amerikarra)
Name[fa]=انگلیسی امریکایی
Name[fi]=Amerikanenglanti
Name[fr]=Anglais américain
Name[fy]=Amerikaansk Ingelsk
Name[ga]=Béarla Mheiriceá
Name[gl]=Inglés americano
Name[gu]=અમેરિકન અંગ્રેજી
Name[he]=אנגלית ארה"ב
Name[hi]=अमरीकी अंग्रेज़ी
Name[hne]=अमरीकी अंगरेजी
Name[hr]=Američki Engleski
Name[hsb]=Jendźelsce (USA)
Name[hu]=Angol (amerikai)
Name[hy]=Ամերիկյան Անգլերեն
Name[ia]=Anglese Americano
Name[id]=Inggris Amerika
Name[is]=Bandarísk enska
Name[it]=Inglese americano
Name[ja]=アメリカ英語
Name[kk]=Американдық ағылшынша
Name[km]=អង់គ្លេស អាមេរិក
Name[kn]=ಅಮೆರಿಕನ್ ಇಂಗ್ಲೀಷ್
Name[ko]=미국 영어
Name[ku]=Îngilîziya Amerîkayî
Name[lb]=Amerikanescht Englesch
Name[lt]=Anglų (JAV)
Name[lv]=Amerikāņu angļu
Name[mai]=अमरीकी अंग्रेजी
Name[mk]=Англиски (американски)
Name[ml]=അമേരിക്കന്‍ ഇംഗ്ലീഷ്
Name[mr]=अमेरिकन इंग्लिश
Name[ms]=American English
Name[nb]=Amerikansk engelsk
Name[nds]=Amerikaansch Engelsch
Name[ne]=अमेरेकी अङ्ग्रेजी
Name[nl]=Engels (Amerikaans)
Name[nn]=Engelsk (USA)
Name[oc]=Anglés american
Name[or]=American English
Name[pa]=ਅੰਗਰੇਜ਼ੀ ਅਮਰੀਕੀ
Name[pl]=Angielski amerykański
Name[ps]=امريکايي انګريزي
Name[pt]=Inglês Americano
Name[pt_BR]=Inglês americano
Name[ro]=Engleză americană
Name[ru]=Английский (США)
Name[se]=Eŋgelasgiella (Amerihkáhlaš)
Name[si]=ඇමරිකානු ඉංග්‍රීසි
Name[sk]=Americká angličtina
Name[sl]=ameriško angleško
Name[sq]=Anglishte Amerikane
Name[sr]=амерички енглески
Name[sr@ijekavian]=амерички енглески
Name[sr@ijekavianlatin]=američki engleski
Name[sr@latin]=američki engleski
Name[sv]=Amerikansk engelska
Name[ta]=அமெரிக்கன் ஆங்கிலம்
Name[te]=అమెరికన్ ఆంగ్లం
Name[tg]=Англисӣ (Амрикоӣ)
Name[th]=ภาษาอังกฤษ อเมริกัน
Name[tr]=Amerikan İngilizcesi
Name[tt]=Инглизчә (Америка)
Name[ug]=ئامېرىكا ئىنگلىزچە
Name[uk]=Англійська (США)
Name[uz]=Inglizcha (AQSH)
Name[uz@cyrillic]=Инглизча (АҚШ)
Name[vi]=Anh (Mỹ)
Name[wa]=Inglès des Estats-Unis
Name[x-test]=xxAmerican Englishxx
Name[zh_CN]=美国英语
Name[zh_HK]=美式英語
Name[zh_TW]=美語
[eo]
Name=Esperanto
Name[af]=Esperanto
Name[ar]=إسبرانتو
Name[as]=এস্পেৰান্টো
Name[ast]=Esperanto
Name[be]=Эсперанта
Name[be@latin]=Esperanta
Name[bg]=Есперанто
Name[bn]=এস্পারান্তো
Name[bn_IN]=এসপারান্তো
Name[br]=Esperanteg
Name[bs]=Esperanto
Name[ca]=Esperanto
Name[ca@valencia]=Esperanto
Name[cs]=Esperanto
Name[csb]=Esperanto
Name[cy]=Esperanto
Name[da]=Esperanto
Name[de]=Esperanto
Name[el]=Εσπεράντο
Name[en_GB]=Esperanto
Name[eo]=Esperanto
Name[es]=Esperanto
Name[et]=Esperanto
Name[eu]=Esperantoa
Name[fa]=اسپرانتور
Name[fi]=Esperanto
Name[fr]=Espéranto
Name[fy]=Esperanto
Name[ga]=Esperanto
Name[gl]=Esperanto
Name[gu]=એસ્પ્રાન્ટો
Name[he]=אספרנטו
Name[hi]=एस्परेन्तो
Name[hne]=एस्परेन्तो
Name[hr]=Esperanto
Name[hsb]=Esperantisce
Name[hu]=Eszperantó
Name[hy]=Էսպերանտո
Name[ia]=Esperanto
Name[id]=Esperanto
Name[is]=Esperanto
Name[it]=Esperanto
Name[ja]=エスペラント語
Name[kk]=Эсперанто
Name[km]=អេស្ពេរ៉ាន់តូ
Name[kn]=ಎಸ್ಪರಾನ್ಟೊ
Name[ko]=에스페란토
Name[ku]=Esperanto
Name[lb]=Esperanto
Name[lt]=Esperanto
Name[lv]=Esperanto
Name[mai]=एस्पेरांटो
Name[mk]=Есперанто
Name[ml]=എസ്പരാണ്ടോ
Name[mr]=एस्परेन्तो
Name[ms]=Esperanto
Name[nb]=Esperanto
Name[nds]=Esperanto
Name[ne]=इस्पेरान्तो
Name[nl]=Esperanto
Name[nn]=Esperanto
Name[oc]=Esperanto
Name[or]=Esperanto
Name[pa]=ਇਸਪੀਰਨਟੋ
Name[pl]=Esperanto
Name[ps]=اېسپېرېنټو
Name[pt]=Esperanto
Name[pt_BR]=Esperanto
Name[ro]=Esperanto
Name[ru]=Эсперанто
Name[se]=Esperanto
Name[si]=එස්පෙරැන්ටෝ
Name[sk]=Esperanto
Name[sl]=esperanto
Name[sq]=Esperanto
Name[sr]=есперанто
Name[sr@ijekavian]=есперанто
Name[sr@ijekavianlatin]=esperanto
Name[sr@latin]=esperanto
Name[sv]=Esperanto
Name[ta]=எஸ்பரான்டோ
Name[te]=ఎస్పరాన్టొ
Name[tg]=Эсперанто
Name[th]=ภาษาเอสเปอร์รันโต
Name[tr]=Esperanto
Name[tt]=Эсперанто
Name[ug]=ئېسپېرانتو (دۇنيا تىلى)
Name[uk]=Есперанто
Name[uz]=Esperanto
Name[uz@cyrillic]=Эсперанто
Name[vi]=Esperanto
Name[wa]=Esperanto
Name[xh]=Esperanto
Name[x-test]=xxEsperantoxx
Name[zh_CN]=世界语
Name[zh_HK]=世界語
Name[zh_TW]=世界語
[es]
Name=Spanish
Name[af]=Spaans
Name[ar]=إسبانية
Name[as]=স্পেনিশ্ব
Name[ast]=Castellanu
Name[be]=Іспанская
Name[be@latin]=Hišpanskaja
Name[bg]=Испански
Name[bn]=স্প্যানিশ
Name[bn_IN]=স্প্যানিশ
Name[br]=Spagnoleg
Name[bs]=Španski
Name[ca]=Espanyol
Name[ca@valencia]=Espanyol
Name[cs]=Španělský
Name[csb]=Szpańsczi
Name[cy]=Sbaeneg
Name[da]=Spansk
Name[de]=Spanisch
Name[el]=Ισπανικά
Name[en_GB]=Spanish
Name[eo]=Hispana
Name[es]=Español
Name[et]=Hispaania
Name[eu]=Gaztelera
Name[fa]=اسپانیایی
Name[fi]=Espanja
Name[fr]=Espagnol
Name[fy]=Spaansk
Name[ga]=Spáinnis
Name[gl]=Castelán
Name[gu]=સ્પેનીશ
Name[he]=ספרדית
Name[hi]=स्पेनी
Name[hne]=स्पेनी
Name[hr]=Španjolski
Name[hsb]=Španisce
Name[hu]=Spanyol
Name[hy]=Իսպաներեն
Name[ia]=Espaniol
Name[id]=Spanyol
Name[is]=Spánska
Name[it]=Spagnolo
Name[ja]=スペイン語
Name[kk]=Испанша
Name[km]=អេស្ប៉ាញ
Name[kn]=ಸ್ಪ್ಯಾನಿಷ್
Name[ko]=스페인어
Name[ku]=Spanî
Name[lb]=Spuenesch
Name[lt]=Ispanų
Name[lv]=Spāņu
Name[mai]=स्पेनी
Name[mk]=Шпански
Name[ml]=സ്പാനിഷ്
Name[mr]=स्पॅनिश
Name[ms]=Sepanyol
Name[nb]=Spansk
Name[nds]=Spaansch
Name[ne]=स्पेनिश
Name[nl]=Spaans
Name[nn]=Spansk
Name[oc]=Castelhan
Name[or]=Spanish
Name[pa]=ਸਪੇਨੀ
Name[pl]=Hiszpański
Name[ps]=سپېنش
Name[pt]=Espanhol
Name[pt_BR]=Espanhol
Name[ro]=Spaniolă
Name[ru]=Испанский
Name[se]=Spánskkagiella
Name[si]=ස්පාඤ්ඤ
Name[sk]=Španielčina
Name[sl]=špansko
Name[sq]=Spanjolle
Name[sr]=шпански
Name[sr@ijekavian]=шпански
Name[sr@ijekavianlatin]=španski
Name[sr@latin]=španski
Name[sv]=Spanska
Name[ta]=ஸ்பானிய
Name[te]=స్పెనిష్
Name[tg]=Испанӣ
Name[th]=ภาษาสเปน
Name[tr]=İspanyolca
Name[tt]=Испан
Name[ug]=ئىسپانچە
Name[uk]=Іспанська
Name[uz]=Ispancha
Name[uz@cyrillic]=Испанча
Name[vi]=Tây Ban Nha
Name[wa]=Castiyan
Name[xh]=Isipanishi
Name[x-test]=xxSpanishxx
Name[zh_CN]=西班牙语
Name[zh_HK]=西班牙語
Name[zh_TW]=西班牙語
[et]
Name=Estonian
Name[af]=Estonianse
Name[ar]=إستونية
Name[as]=এস্টোনিয়ান
Name[ast]=Estoniu
Name[be]=Эстонская
Name[be@latin]=Estonskaja
Name[bg]=Естонски
Name[bn]=এস্টোনীয়
Name[bn_IN]=এস্তোনিয়ান
Name[br]=Estoneg
Name[bs]=Estonski
Name[ca]=Estonià
Name[ca@valencia]=Estonià
Name[cs]=Estonský
Name[csb]=Estońsczi
Name[cy]=Estoneg
Name[da]=Estisk
Name[de]=Estnisch
Name[el]=Εσθονικά
Name[en_GB]=Estonian
Name[eo]=Estona
Name[es]=Estonio
Name[et]=Eesti
Name[eu]=Estoniera
Name[fa]=استونیایی
Name[fi]=Viro
Name[fr]=Estonien
Name[fy]=Estsk
Name[ga]=Eastóinis
Name[gl]=Estoniano
Name[gu]=ઇસ્ટોનિઅન
Name[he]=אסטונית
Name[hi]=एस्तोनियाई
Name[hne]=एस्तोनियाई
Name[hr]=Estonski
Name[hsb]=Estnisce
Name[hu]=Észt
Name[hy]=Եստոներեն
Name[ia]=Estonian
Name[id]=Estonia
Name[is]=Eistneska
Name[it]=Estone
Name[ja]=エストニア語
Name[kk]=Эстонша
Name[km]=អេស្តូនី
Name[kn]=ಎಸ್ಚೋನಿಯನ್
Name[ko]=에스토니아어
Name[ku]=Estonî
Name[lb]=Estnesch
Name[lt]=Estų
Name[lv]=Igauņu
Name[mai]=एस्तोनियाइ
Name[mk]=Естонски
Name[ml]=എസ്തോണിയന്‍
Name[mr]=एस्तोनियाई
Name[ms]=Estonia
Name[nb]=Estisk
Name[nds]=Eestlannsch
Name[ne]=इस्टोनियाली
Name[nl]=Ests
Name[nn]=Estisk
Name[oc]=Estonian
Name[or]=Estonian
Name[pa]=ਇਸਟੋਨੀਆ
Name[pl]=Estoński
Name[ps]=اېسټونيايي
Name[pt]=Estónio
Name[pt_BR]=Estoniano
Name[ro]=Estoniană
Name[ru]=Эстонский
Name[se]=Esttegiella
Name[si]=එස්තෝනියානු
Name[sk]=Estónčina
Name[sl]=estonsko
Name[sq]=Estoneze
Name[sr]=естонски
Name[sr@ijekavian]=естонски
Name[sr@ijekavianlatin]=estonski
Name[sr@latin]=estonski
Name[sv]=Estniska
Name[ta]=எஸ்டோனியன்
Name[te]=ఎస్టొనియన్
Name[tg]=Эстонӣ
Name[th]=ภาษาเอสโทเนีย
Name[tr]=Estonya Dili
Name[tt]=Эстон
Name[ug]=ئېستونچە
Name[uk]=Естонська
Name[uz]=Estoncha
Name[uz@cyrillic]=Эстонча
Name[vi]=Estonia
Name[wa]=Estonyin
Name[xh]=Estonian
Name[x-test]=xxEstonianxx
Name[zh_CN]=爱沙尼亚语
Name[zh_HK]=愛沙尼亞語
Name[zh_TW]=愛沙尼亞語
[eu]
Name=Basque
Name[af]=Basque
Name[ar]=باسكية
Name[as]=বাস্ক
Name[ast]=Vascu
Name[be]=Басцкая
Name[be@latin]=Baskaŭskaja
Name[bg]=Баскийски
Name[bn]=বাস্ক
Name[bn_IN]=বাস্ক
Name[br]=Euskareg
Name[bs]=Baskijski
Name[ca]=Basc
Name[ca@valencia]=Basc
Name[cs]=Baskický
Name[csb]=Baskijsczi
Name[cy]=Basgeg
Name[da]=Baskisk
Name[de]=Baskisch
Name[el]=Βασκικά
Name[en_GB]=Basque
Name[eo]=Eŭska
Name[es]=Euskera
Name[et]=Baski
Name[eu]=Euskara
Name[fa]=باسکی
Name[fi]=Baski
Name[fr]=Basque
Name[fy]=Baskysk
Name[ga]=Bascais
Name[gl]=Basco
Name[gu]=બાસ્ક
Name[he]=בסקית
Name[hi]=बास्क
Name[hne]=बास्क
Name[hr]=Baskijski
Name[hsb]=Baskisce
Name[hu]=Baszk
Name[ia]=Basco
Name[id]=Basque
Name[is]=Baskamál
Name[it]=Basco
Name[ja]=バスク語
Name[kk]=Баскша
Name[km]=​​បាស្កេ
Name[kn]=ಬಾಸ್ಕ್
Name[ko]=바스크어
Name[ku]=Baskî
Name[lb]=Baskesch
Name[lt]=Baskų
Name[lv]=Basku
Name[mai]=बास्क
Name[mk]=Баскиски
Name[ml]=ബാസ്ക്
Name[mr]=बास्क
Name[ms]=Basque
Name[nb]=Baskisk
Name[nds]=Basksch
Name[ne]=बास्क
Name[nl]=Baskisch
Name[nn]=Baskisk
Name[oc]=Basc
Name[or]=Basque
Name[pa]=ਬਸਕਿਉ
Name[pl]=Baskijski
Name[ps]=بېسک
Name[pt]=Basco
Name[pt_BR]=Basco
Name[ro]=Bască
Name[ru]=Баскский
Name[se]=Baskalašgiella
Name[si]=බැස්ක්
Name[sk]=Baskičtina
Name[sl]=baskovsko
Name[sq]=Baske
Name[sr]=баскијски
Name[sr@ijekavian]=баскијски
Name[sr@ijekavianlatin]=baskijski
Name[sr@latin]=baskijski
Name[sv]=Baskiska
Name[ta]=பாஸ்க்
Name[te]=బాస్క్
Name[tg]=Баскӣ
Name[th]=ภาษาบาสก์
Name[tr]=Bask Dili
Name[tt]=Баск
Name[ug]=باسكىچە
Name[uk]=Баскська
Name[uz]=Baskcha
Name[uz@cyrillic]=Баскча
Name[vi]=Basque
Name[wa]=Basse
Name[xh]=Basque
Name[x-test]=xxBasquexx
Name[zh_CN]=巴斯克语
Name[zh_HK]=巴斯克語
Name[zh_TW]=巴斯克語
[fa]
Name=Farsi (Persian)
Name[af]=Farsi (Persies)
Name[ar]=فارسية
Name[as]=ফাৰ্চী (পাৰ্চিয়ান)
Name[ast]=Farsi (Persa)
Name[be]=Персідская
Name[be@latin]=Farsi (persydzkaja)
Name[bg]=Фарси
Name[bn]=ফার্সি (পার্সিয়ান)
Name[bn_IN]=ফার্সি
Name[br]=Farsieg (Persieg)
Name[bs]=Farski(Perzijski)
Name[ca]=Farsi (Persa)
Name[ca@valencia]=Farsi (Persa)
Name[cs]=Farsi (Perský)
Name[csb]=Farsi (persczi)
Name[cy]=Farsi (Perseg)
Name[da]=Farsi (Persisk)
Name[de]=Farsi (Persisch)
Name[el]=Farsi (Περσικά)
Name[en_GB]=Farsi (Persian)
Name[eo]=Frisa (Persa)
Name[es]=Persa
Name[et]=Farsi (pärsia)
Name[eu]=Farsiera (Persiera)
Name[fa]=فارسی
Name[fi]=Farsi (persia)
Name[fr]=Persan
Name[fy]=Farsysk (Persysk)
Name[ga]=Fairsis (Peirsis)
Name[gl]=Frisón (Parsi)
Name[gu]=ફારસી (પર્શીયન)
Name[he]=פרסית
Name[hi]=फारसी (पर्सियाई)
Name[hne]=फारसी (पर्सियाई)
Name[hr]=Farsi (Perzijski)
Name[hsb]=Farsisce (Persisce)
Name[hu]=Fárszi (perzsa)
Name[ia]=Farsi (Perse)
Name[id]=Farsi (Persia)
Name[is]=Farsi (Persneska)
Name[it]=Farsi (Persiano)
Name[ja]=ペルシア語
Name[kk]=Фарси
Name[km]=ហ្វាស៊ី (ពឺស៊ាន)
Name[kn]=ಫಾರ್ಸಿ (ಪರ್ಶಿಯನ್)
Name[ko]=페르시아어
Name[ku]=Farsî
Name[lb]=Farsi (Persesch)
Name[lt]=Farsi (persų)
Name[lv]=Persiešu (Farsi)
Name[mai]=फारसी (पर्सियाइ)
Name[mk]=Фарси (персиски)
Name[ml]=പാര്‍സി (പേര്‍ഷ്യന്‍)
Name[mr]=फारसी (पर्शियन)
Name[ms]=Parsi
Name[nb]=Farsi (Persisk)
Name[nds]=Farsi (Persisch)
Name[ne]=फारसी (पर्सियन)
Name[nl]=Farsi (Perzisch)
Name[nn]=Persisk
Name[or]=Farsi (Persian)
Name[pa]=ਫਾਰਸੀ (ਪਰਸ਼ੀਆਈ)
Name[pl]=Farsi (Perski)
Name[ps]=پاړسي
Name[pt]=Farsi (Persa)
Name[pt_BR]=Farsi (Persa)
Name[ro]=Farsi (Persană)
Name[ru]=Персидский
Name[se]=Farsigiella (Persialaš)
Name[si]=ෆාර්සි (පර්සියානු)
Name[sk]=Fársí (perzština)
Name[sl]=Farsi (perzijsko)
Name[sq]=Persisht
Name[sr]=фарси (персијски)
Name[sr@ijekavian]=фарси (персијски)
Name[sr@ijekavianlatin]=farsi (persijski)
Name[sr@latin]=farsi (persijski)
Name[sv]=Persiska
Name[ta]=பார்சி (பெர்சியன்)
Name[te]=ఫార్సి (పెర్షియన్)
Name[tg]=Форсӣ
Name[th]=ภาษาฟาร์ซี (เปอร์เซียน)
Name[tr]=Farsça (İran)
Name[tt]=Фарсы (Иран)
Name[ug]=پارىسچە
Name[uk]=Фарсі (Перська)
Name[uz]=Forscha (Perscha)
Name[uz@cyrillic]=Форсча (Персча)
Name[vi]=Farsi (Ba Tư)
Name[wa]=Farsi
Name[x-test]=xxFarsi (Persian)xx
Name[zh_CN]=波斯语
Name[zh_HK]=波斯語
Name[zh_TW]=波斯語
[fi]
Name=Finnish
Name[af]=Feense
Name[ar]=فنلندية
Name[as]=ফিনিশ্ব
Name[ast]=Finlandés
Name[be]=Фінская
Name[be@latin]=Finskaja
Name[bg]=Фински
Name[bn]=ফিনিশ
Name[bn_IN]=ফিনিশ
Name[br]=Finneg
Name[bs]=Finski
Name[ca]=Finès
Name[ca@valencia]=Finés
Name[cs]=Finský
Name[csb]=Finsczi
Name[cy]=Ffineg
Name[da]=Finsk
Name[de]=Finnisch
Name[el]=Φιλανδικά
Name[en_GB]=Finnish
Name[eo]=Fina
Name[es]=Finés
Name[et]=Soome
Name[eu]=Finlandiera
Name[fa]=فنلاندی
Name[fi]=Suomi
Name[fr]=Finnois
Name[fy]=Finsk
Name[ga]=Fionlainnis
Name[gl]=Finlandés
Name[gu]=ફિનિશ
Name[he]=פינית
Name[hi]=फिनिश
Name[hne]=फिनिस
Name[hr]=Finski
Name[hsb]=Finsce
Name[hu]=Finn
Name[hy]=Ֆիներեն
Name[ia]=Finnese
Name[id]=Finlandia
Name[is]=Finnska
Name[it]=Finlandese
Name[ja]=フィンランド語
Name[kk]=Финнша
Name[km]=ហ្វាំងឡង់
Name[kn]=ಫಿನ್ನಿಷ್
Name[ko]=핀란드어
Name[ku]=Finkî
Name[lb]=Finnesch
Name[lt]=Suomių
Name[lv]=Somu
Name[mai]=फिनिश
Name[mk]=Фински
Name[ml]=ഫിന്നിഷ്
Name[mr]=फिनिश
Name[ms]=Finnish
Name[nb]=Finsk
Name[nds]=Finnsch
Name[ne]=फिनिश
Name[nl]=Fins
Name[nn]=Finsk
Name[oc]=Finés
Name[or]=Finnish
Name[pa]=ਫੈਨਿਸ਼
Name[pl]=Fiński
Name[ps]=فېنېش
Name[pt]=Finlandês
Name[pt_BR]=Finlandês
Name[ro]=Finlandeză
Name[ru]=Финский
Name[se]=Suomagiella
Name[si]=ෆින්ලන්තානු
Name[sk]=Fínčina
Name[sl]=finsko
Name[sq]=Finlandeze
Name[sr]=фински
Name[sr@ijekavian]=фински
Name[sr@ijekavianlatin]=finski
Name[sr@latin]=finski
Name[sv]=Finska
Name[ta]=பின்னிஷ்
Name[te]=ఫిన్నిష్
Name[tg]=Финӣ
Name[th]=ภาษาฟินแลนด์
Name[tr]=Fince
Name[tt]=Фин
Name[ug]=فىنچە
Name[uk]=Фінська
Name[uz]=Fincha
Name[uz@cyrillic]=Финча
Name[vi]=Phần Lan
Name[wa]=Finwès
Name[xh]=Finnish
Name[x-test]=xxFinnishxx
Name[zh_CN]=芬兰语
Name[zh_HK]=芬蘭語
Name[zh_TW]=芬蘭語
[fj]
Name=Fijian
Name[af]=Fijian
Name[ar]=فيجية
Name[as]=ফিজিয়ান
Name[ast]=Fijianu
Name[be]=Фіджыйская
Name[be@latin]=Fidžyjskaja
Name[bg]=Фиджи
Name[bn]=ফিজিয়ান
Name[bn_IN]=ফিজিয়ান
Name[br]=Fidjeg
Name[bs]=Fidžijski
Name[ca]=Fijià
Name[ca@valencia]=Fijià
Name[cs]=Fidžijský
Name[csb]=z Fiji
Name[cy]=Fijieg
Name[da]=Fijiansk
Name[de]=Fidschi
Name[el]=Fijian
Name[en_GB]=Fijian
Name[eo]=Fiĝia
Name[es]=Fiyiano
Name[et]=Fidži
Name[eu]=Fijiera
Name[fa]=فیجیایی
Name[fi]=Fidži
Name[fr]=Fidjien
Name[fy]=Fijdzjysk
Name[ga]=Fidsis
Name[gl]=Fixiano
Name[gu]=ફિજીઅન
Name[he]=פיגי'ת
Name[hi]=फिज़ी
Name[hne]=फिजी
Name[hr]=Fidžijski
Name[hsb]=Fidźisce
Name[hu]=Fidzsi
Name[ia]=Fijian
Name[id]=Fijian
Name[is]=Fijian
Name[it]=Figiano
Name[ja]=フィジー語
Name[kk]=Фиджише
Name[km]=ហ្វ៊ីហ្ស៊ី
Name[kn]=ಫಿಜಿಯನ್
Name[ko]=피지어
Name[ku]=Fîcî
Name[lb]=Fidschi-Sprooch
Name[lt]=Fidži
Name[lv]=Fidžiešu
Name[mai]=फिजियन
Name[mk]=Фиџиски
Name[ml]=ഫിജിയന്‍
Name[mr]=फिज़ी
Name[ms]=Fijian
Name[nb]=Fijisk
Name[nds]=Fidschi
Name[ne]=फिजियन
Name[nl]=Fijisch
Name[nn]=Fijiansk
Name[oc]=Fijian
Name[or]=Fijian
Name[pa]=ਫਿਜੀਨ
Name[pl]=z Fiji
Name[ps]=فيجي
Name[pt]=Fidjiano
Name[pt_BR]=Fijiano
Name[ro]=Fijiană
Name[ru]=Фиджи
Name[se]=Fižigiella
Name[si]=ෆීජි
Name[sk]=Fidžijčina
Name[sl]=fidžijsko
Name[sq]=Fixhiane
Name[sr]=фиџијски
Name[sr@ijekavian]=фиџијски
Name[sr@ijekavianlatin]=fidžijski
Name[sr@latin]=fidžijski
Name[sv]=Fijianska
Name[ta]=பிஜியன்
Name[te]=ఫిజియన్
Name[tg]=Фиҷӣ
Name[th]=ภาษาฟิจิ
Name[tr]=Fijian
Name[tt]=Фиджийский
Name[ug]=فىجىچە
Name[uk]=Фіджійська
Name[uz]=Fijicha
Name[uz@cyrillic]=Фижича
Name[vi]=Fijian
Name[wa]=Fidjyin
Name[xh]=Fijian
Name[x-test]=xxFijianxx
Name[zh_CN]=斐济语
Name[zh_HK]=斐濟語
Name[zh_TW]=斐濟語
[fo]
Name=Faroese
Name[af]=Faroese
Name[ar]=فروية
Name[as]=ফাৰোইছ
Name[ast]=Faroés
Name[be]=Фароская
Name[be@latin]=Faroskaja
Name[bg]=Фарьорски
Name[bn]=ফারোইস
Name[bn_IN]=ফেরোইজ
Name[br]=Faroeseg
Name[bs]=Farski
Name[ca]=Feroès
Name[ca@valencia]=Feroés
Name[cs]=Faerský
Name[csb]=z Òwczëch Òstrowów
Name[cy]=Faroeg
Name[da]=Færøsk
Name[de]=Färöisch
Name[el]=Faroese
Name[en_GB]=Faroese
Name[eo]=Feroa
Name[es]=Feroés
Name[et]=Fääri
Name[eu]=Faroera
Name[fa]=فاروسی
Name[fi]=Fääri
Name[fr]=Féroïen
Name[fy]=Faeroersk
Name[ga]=Faróis
Name[gl]=Feroés
Name[gu]=ફોરોસી
Name[he]=פארואית
Name[hi]=फारसी
Name[hne]=फारसी
Name[hr]=Farski
Name[hsb]=Ferejsce
Name[hu]=Faröei
Name[ia]=Faroese
Name[id]=Faroese
Name[is]=Færeyska
Name[it]=Faroese
Name[ja]=フェーロー語
Name[kk]=Фароэзше
Name[km]=ហ្វារ៉ូស
Name[kn]=ಫರೊಈಸ್
Name[ko]=페로어
Name[ku]=Faroyî
Name[lb]=Faröesch
Name[lt]=Farerų
Name[lv]=Fēru
Name[mai]=फारसी
Name[mk]=Фарски
Name[ml]=ഫരൂസ്
Name[mr]=फारसी
Name[ms]=Faroese
Name[nb]=Færøyisk
Name[nds]=Färöösch
Name[ne]=फारोसी
Name[nl]=Faroese
Name[nn]=Færøysk
Name[oc]=Feroés
Name[or]=Faroese
Name[pa]=ਫਾਰੋਇਸੀ
Name[pl]=z Wysp Owczych
Name[pt]=Faroês
Name[pt_BR]=Feroês
Name[ro]=Faroeză
Name[ru]=Фарерский
Name[se]=Fearagiella
Name[si]=ෆැරෝසි
Name[sk]=Faerčina
Name[sl]=fersko
Name[sq]=Faroeze
Name[sr]=фарски
Name[sr@ijekavian]=фарски
Name[sr@ijekavianlatin]=farski
Name[sr@latin]=farski
Name[sv]=Färöiska
Name[ta]=பாரோவீஸ்
Name[te]=ఫారొఈస్
Name[tg]=Фароесӣ
Name[th]=ภาษาฟาโรอีส
Name[tr]=Faroese
Name[tt]=Фарерский
Name[ug]=فاروئىسچە
Name[uk]=Фарерська
Name[uz]=Farercha
Name[uz@cyrillic]=Фарерча
Name[vi]=Faroe
Name[wa]=Faeroyès
Name[xh]=Faroese
Name[x-test]=xxFaroesexx
Name[zh_CN]=法罗群岛语
Name[zh_HK]=法羅語
Name[zh_TW]=法羅語
[fr]
Name=French
Name[af]=Franse
Name[ar]=فرنسية
Name[as]=ফৰাচী
Name[ast]=Francés
Name[be]=Французская
Name[be@latin]=Francuskaja
Name[bg]=Френски
Name[bn]=ফরাসী
Name[bn_IN]=ফ্রেঞ্চ
Name[br]=Galleg
Name[bs]=Francuski
Name[ca]=Francès
Name[ca@valencia]=Francés
Name[cs]=Francouzský
Name[csb]=Francësczi
Name[cy]=Frangeg
Name[da]=Fransk
Name[de]=Französisch
Name[el]=Γαλλικά
Name[en_GB]=French
Name[eo]=Franca
Name[es]=Francés
Name[et]=Prantsuse
Name[eu]=Frantsesa
Name[fa]=فرانسوی
Name[fi]=Ranska
Name[fr]=Français
Name[fy]=Frânsk
Name[ga]=Fraincis
Name[gl]=Francés
Name[gu]=ફ્રેંચ
Name[he]=צרפתית
Name[hi]=फ्रांसीसी
Name[hne]=फ्रांसीसी
Name[hr]=Francuski
Name[hsb]=Francosce
Name[hu]=Francia
Name[hy]=Ֆրանսերեն
Name[ia]=Francese
Name[id]=Prancis
Name[is]=Franska
Name[it]=Francese
Name[ja]=フランス語
Name[kk]=Французша
Name[km]=បារាំង
Name[kn]=ಫ್ರೆಂಚ್
Name[ko]=프랑스어
Name[ku]=Fransî
Name[lb]=Franzéisch
Name[lt]=Prancūzų
Name[lv]=Franču
Name[mai]=फ्रेंच
Name[mk]=Француски
Name[ml]=ഫ്രഞ്ച്
Name[mr]=फ्रेंच
Name[ms]=Perancis
Name[nb]=Fransk
Name[nds]=Franzöösch
Name[ne]=फ्रेन्च
Name[nl]=Frans
Name[nn]=Fransk
Name[oc]=Francés
Name[or]=French
Name[pa]=ਫਰੈਂਚ
Name[pl]=Francuski
Name[ps]=فرانسوي
Name[pt]=Francês
Name[pt_BR]=Francês
Name[ro]=Franceză
Name[ru]=Французский
Name[se]=Fránskkagiella
Name[si]=ප්‍රංශ
Name[sk]=Francúzština
Name[sl]=francosko
Name[sq]=Franceze
Name[sr]=француски
Name[sr@ijekavian]=француски
Name[sr@ijekavianlatin]=francuski
Name[sr@latin]=francuski
Name[sv]=Franska
Name[ta]=பிரென்சு
Name[te]=ఫ్రెంచ్
Name[tg]=Франсавӣ
Name[th]=ภาษาฝรั่งเศส
Name[tr]=Fransızca
Name[tt]=Француз
Name[ug]=فىرانسۇزچە
Name[uk]=Французька
Name[uz]=Fransuzcha
Name[uz@cyrillic]=Французча
Name[vi]=Pháp
Name[wa]=Francès
Name[xh]=Isifrentshi
Name[x-test]=xxFrenchxx
Name[zh_CN]=法语
Name[zh_HK]=法語
Name[zh_TW]=法語
[fy]
Name=Frisian
Name[af]=Frisian
Name[ar]=فريسية
Name[as]=ফ্ৰিচিয়ান
Name[ast]=Frisón
Name[be]=Фрысійская
Name[be@latin]=Fryskaja
Name[bg]=Фризийски
Name[bn]=ফ্রিসিয়ান
Name[bn_IN]=ফ্রিসিয়ান
Name[br]=Frisianeg
Name[bs]=Frizijski
Name[ca]=Frisó
Name[ca@valencia]=Frisó
Name[cs]=Fríský
Name[csb]=Frizëjsczi
Name[cy]=Frisieg
Name[da]=Frisisk
Name[de]=Friesisch
Name[el]=Frisian
Name[en_GB]=Frisian
Name[eo]=Frisa
Name[es]=Frisio
Name[et]=Friisi
Name[eu]=Frisiera
Name[fa]=فریسی
Name[fi]=Friisi
Name[fr]=Frison
Name[fy]=Frysk
Name[ga]=Freaslainnis
Name[gl]=Frisio
Name[gu]=ફ્રિસિયન
Name[he]=פריזית
Name[hi]=फ्रिसियन
Name[hne]=फ्रिसियन
Name[hr]=Frizijski
Name[hsb]=Frizisce
Name[hu]=Fríz
Name[ia]=Frisian
Name[id]=Frisian
Name[is]=Frísneska
Name[it]=Frisone
Name[ja]=フリジア語
Name[kk]=Фризше
Name[km]=ហ្វ្រីស៊ាន
Name[kn]=ಫ್ರಿಷಿಯನ್
Name[ko]=프리지아어
Name[ku]=Frîsî
Name[lb]=Friesesch
Name[lt]=Fryzų
Name[lv]=Frīzu
Name[mai]=फ्रिसियन
Name[mk]=Фризиски
Name[ml]=ഫ്രിഷ്യന്‍
Name[mr]=फ्रिसियन
Name[ms]=Frisia
Name[nb]=Frisisk
Name[nds]=Freesch
Name[ne]=फ्रिसियन
Name[nl]=Frysk
Name[nn]=Frisisk
Name[oc]=Frisian
Name[or]=Frisian
Name[pa]=ਫਰੀਸੀਨਿਸ
Name[pl]=Fryzyjski
Name[ps]=فرېشين
Name[pt]=Frísio
Name[pt_BR]=Frisão
Name[ro]=Frisiană
Name[ru]=Фризский
Name[se]=Frisagiella
Name[si]=ෆ්‍රිසියන්
Name[sk]=Frízština
Name[sl]=frizijsko
Name[sq]=Friziane
Name[sr]=фризијски
Name[sr@ijekavian]=фризијски
Name[sr@ijekavianlatin]=frizijski
Name[sr@latin]=frizijski
Name[sv]=Frisiska
Name[ta]=பிரிசியன்
Name[te]=ఫ్రిసియన్
Name[tg]=Фризианӣ
Name[th]=ภาษาฟรีเชียน
Name[tr]=Frisian
Name[tt]=Фриз
Name[ug]=فرىسونچە
Name[uk]=Фризька
Name[uz]=Frizcha
Name[uz@cyrillic]=Фризча
Name[vi]=Frisia
Name[wa]=Frizon
Name[xh]=Frisian
Name[x-test]=xxFrisianxx
Name[zh_CN]=弗里斯兰语
Name[zh_HK]=弗利然語
Name[zh_TW]=弗利然語
[ga]
Name=Irish Gaelic
Name[af]=Ierse Gaelic
Name[ar]=غايلية إرلاندية
Name[as]=আইৰিশ্ব গেলিক
Name[ast]=Gaélicu irlandés
Name[be]=Ірландская гальская
Name[be@latin]=Irlandzkaja gaelskaja
Name[bg]=Ирландски
Name[bn]=আইরিশ গেলিক
Name[bn_IN]=আইরিশ গেলিক
Name[br]=Iwerzhoneg eus Bro Iwerzhon
Name[bs]=Irski
Name[ca]=Gaèlic irlandès
Name[ca@valencia]=Gaèlic irlandés
Name[cs]=Galský (irština)
Name[csb]=Irlandzczi Gaelic
Name[da]=Irsk gælisk
Name[de]=Irisches Gälisch
Name[el]=Ιρλανδική (Gaeilge)
Name[en_GB]=Irish Gaelic
Name[eo]=Gaela (Irlanda)
Name[es]=Gaélico irlandés
Name[et]=Iiri gaeli
Name[eu]=Irlandar gaelikoa
Name[fa]=اسکاتلندی ایرلندی
Name[fi]=Iiri
Name[fr]=Gaélique irlandais
Name[fy]=Iersk Gaelic
Name[ga]=Gaeilge
Name[gl]=Gaélico irlandés
Name[gu]=આઈરીશ ગેલિક
Name[he]=גאלית אירית
Name[hi]=आइरिश गैलिक
Name[hne]=आइरिस गैलिक
Name[hr]=Irski Galski
Name[hsb]=Gaelisce (Irska)
Name[hu]=Gall (ír)
Name[ia]=Irlandese Gaelic
Name[id]=Gaelic Irlandia
Name[is]=Írsk gelíska
Name[it]=Gaelico irlandese
Name[ja]=アイルランド系ゲール語
Name[kk]=Ирланд галлша
Name[km]=អៀរឡង់ ហ្គាអេលិក
Name[kn]=ಐರಿಷ್ ಗ್ಯಾಲಿಕ್
Name[ko]=아일랜드 게일어
Name[ku]=Galiya Îrlandayê
Name[lb]=Irescht Gällesch
Name[lt]=Irish Gaelic
Name[lv]=Īru gēlu
Name[mai]=आइरिश गैलिक
Name[mk]=Ирски галски
Name[ml]=ഐറിഷ് ഗാലിക്
Name[mr]=आइरिश गैलिक
Name[ms]=Irish Gaelic
Name[nb]=Irsk gælisk
Name[nds]=Irsch Gäälsch
Name[ne]=आइरिस ग्यालिक
Name[nl]=Gaelic (Iers)
Name[nn]=Gælisk (Irland)
Name[or]=Irish Gaelic
Name[pa]=ਆਈਰਿਸ਼ ਗਾਈਲਿਕ
Name[pl]=Irlandzki Gaelic
Name[pt]=Galês da Irlanda
Name[pt_BR]=Gaélico irlandês
Name[ro]=Galeză irlandeză
Name[ru]=Ирландский
Name[se]=Irlánddalaš gaelagiella
Name[si]=අයිරිෂ් ගේලික්
Name[sk]=Írska gaelčina
Name[sl]=irsko galsko
Name[sq]=Irlandeze Gale
Name[sr]=ирски гелски
Name[sr@ijekavian]=ирски гелски
Name[sr@ijekavianlatin]=irski gelski
Name[sr@latin]=irski gelski
Name[sv]=Iriska
Name[ta]=ஐரிஷ் காலிக்
Name[te]=ఐరిష్ గెలిక్
Name[tg]=Ирландӣ (Галикӣ)
Name[th]=ภาษาไอริชเกลิค
Name[tr]=İrlanda Galik
Name[tt]=Галларча (Ирландия)
Name[ug]=ئىرېلاندىيە گالىكچە
Name[uk]=Гаельська (Ірландія)
Name[vi]=Irish Gaelic
Name[wa]=Irlandès
Name[x-test]=xxIrish Gaelicxx
Name[zh_CN]=爱尔兰盖尔语
Name[zh_TW]=(愛爾蘭)蓋爾語
[gd]
Name=Gaelic
Name[af]=Gaelic
Name[ar]=غايلية
Name[as]=গেলিক
Name[ast]=Gaélicu
Name[be]=Гальская
Name[be@latin]=Gaelskaja
Name[bg]=Гаелски
Name[bn]=গেলিক
Name[bn_IN]=গেলিক
Name[br]=Iwerzhoneg
Name[bs]=Keltski
Name[ca]=Gaèlic
Name[ca@valencia]=Gaèlic
Name[cs]=Galský
Name[csb]=Celtycczi (gaelic)
Name[cy]=Gaeleg
Name[da]=Gælisk
Name[de]=Gälisch
Name[el]=Σκωτική (Gaelic)
Name[en_GB]=Gaelic
Name[eo]=Gaela
Name[es]=Gaélico
Name[et]=Gaeli
Name[eu]=Gaelikoa
Name[fa]=اسکاتلندی
Name[fi]=Gaeli
Name[fr]=Gaélique
Name[fy]=Gaelic
Name[ga]=Gàidhlig
Name[gl]=Gaélico
Name[gu]=ગેલિક
Name[he]=גאלית
Name[hi]=गैलिक
Name[hne]=गैलिक
Name[hr]=Galski
Name[hsb]=Gaelisce
Name[hu]=Gall
Name[ia]=Gaelic
Name[id]=Gaelic
Name[is]=Gelíska
Name[it]=Gaelico
Name[ja]=ゲール語
Name[kk]=Галлша
Name[km]=ហ្គាអេលិក
Name[kn]=ಗ್ಯಾಲಿಕ್
Name[ko]=게일어
Name[ku]=Gaelî
Name[lb]=Gällesch
Name[lt]=Galų
Name[lv]=Gēlu
Name[mai]=गैलिक
Name[mk]=Галски
Name[ml]=ഗാലിക്
Name[mr]=गैलिक
Name[ms]=Gaelic
Name[nb]=Gælisk
Name[nds]=Gäälsch
Name[ne]=ग्यालिक
Name[nl]=Gaelic
Name[nn]=Gælisk
Name[oc]=Gaelic
Name[or]=Gaelic
Name[pa]=ਗਾਈਲਿਕ
Name[pl]=celtycki (gaelic)
Name[ps]=ګاېلېک
Name[pt]=Galês
Name[pt_BR]=Gaélico
Name[ro]=Galeză
Name[ru]=Шотландский
Name[se]=Gaelagiella
Name[si]=ගේලික්
Name[sk]=Gaelčina
Name[sl]=galsko
Name[sq]=Gale
Name[sr]=гелски
Name[sr@ijekavian]=гелски
Name[sr@ijekavianlatin]=gelski
Name[sr@latin]=gelski
Name[sv]=Gäliska
Name[ta]=கேலிக்
Name[te]=గెలిక్
Name[tg]=Галикӣ
Name[th]=ภาษาเกลิค
Name[tr]=Galik
Name[tt]=Галларча
Name[ug]=گالىكچە
Name[uk]=Гаельська
Name[uz]=Galikcha
Name[uz@cyrillic]=Галикча
Name[vi]=Gaelic
Name[wa]=Gayel
Name[xh]=Gaelic
Name[x-test]=xxGaelicxx
Name[zh_CN]=盖尔语
Name[zh_HK]=蓋爾語
Name[zh_TW]=蓋爾語
[gl]
Name=Galician
Name[af]=Galician
Name[ar]=جلقية
Name[as]=গেলিচিয়ান
Name[ast]=Gallegu
Name[be]=Галіцыйская
Name[be@latin]=Galisijskaja
Name[bg]=Галисийски
Name[bn]=গেলিসিয়ান
Name[bn_IN]=গেলিশিয়ান
Name[br]=Galiseg
Name[bs]=Galicijski
Name[ca]=Gallec
Name[ca@valencia]=Gallec
Name[cs]=Haličský
Name[csb]=Galicëjsczi
Name[cy]=Galiseg
Name[da]=Galicisk
Name[de]=Galicisch
Name[el]=Γαλικιανά (Ισπανία)
Name[en_GB]=Galician
Name[eo]=Galega
Name[es]=Gallego
Name[et]=Galeegi
Name[eu]=Galiziera
Name[fa]=اسکاتلندی
Name[fi]=Gallicia
Name[fr]=Galicien
Name[fy]=Galiciaansk
Name[ga]=Gailísis
Name[gl]=Galego
Name[gu]=ગેલિસીયન
Name[he]=גליסית
Name[hi]=गैलिसियाई
Name[hne]=गैलिसियाई
Name[hr]=Galicijski
Name[hsb]=Galicisce
Name[hu]=Galíciai
Name[ia]=Galleco
Name[id]=Galician
Name[is]=Gelíska
Name[it]=Gallego
Name[ja]=ガリシア語
Name[kk]=Галицияша
Name[km]=ហ្កាលីស៊ី
Name[kn]=ಗ್ಯಾಲಿಷಿಯನ್
Name[ko]=갈리시아어
Name[ku]=Galîkî
Name[lb]=Galizesch
Name[lt]=Galiciečių
Name[lv]=Galisiešu
Name[mai]=गेलिसियन
Name[mk]=Галски
Name[ml]=ഗലീഷ്യന്‍
Name[mr]=गैलिसियाई
Name[ms]=Galician
Name[nb]=Galicisk
Name[nds]=Galizsch
Name[ne]=ग्यालिसियन
Name[nl]=Galicisch
Name[nn]=Galisisk
Name[oc]=Galician
Name[or]=Galician
Name[pa]=ਗਾਈਲੀਆਈ
Name[pl]=Galicyjski
Name[ps]=ګېلېشين
Name[pt]=Galego
Name[pt_BR]=Galego
Name[ro]=Galiciană
Name[ru]=Галисийский
Name[se]=Galisiagiella
Name[si]=ගැලීසියානු
Name[sk]=Galícijčina
Name[sl]=galicijsko
Name[sq]=Galiciane
Name[sr]=галицијски
Name[sr@ijekavian]=галицијски
Name[sr@ijekavianlatin]=galicijski
Name[sr@latin]=galicijski
Name[sv]=Galiciska
Name[ta]=காலிசியன்
Name[te]=గలిచియన్
Name[tg]=Галлӣ
Name[th]=ภาษาแกลิเซีย
Name[tr]=Galce
Name[tt]=Галисия
Name[ug]=گالىتسىيانچە
Name[uk]=Галісійська
Name[uz]=Galikcha
Name[uz@cyrillic]=Галикча
Name[vi]=Galicia
Name[wa]=Galicyin
Name[xh]=isiGalacian
Name[x-test]=xxGalicianxx
Name[zh_CN]=加利西亚语
Name[zh_TW]=加利西亞語
[gn]
Name=Guarani
Name[af]=Guarani
Name[ar]=غوراني
Name[as]=গুৱাৰানি
Name[ast]=Guaraní
Name[be]=Гуярані
Name[be@latin]=Guarani
Name[bg]=Гуарани
Name[bn]=গুয়ারানি
Name[bn_IN]=গুয়ারানি
Name[br]=Guarani
Name[bs]=Guaranski
Name[ca]=Guaraní
Name[ca@valencia]=Guaraní
Name[cs]=Guarani
Name[csb]=Guarani
Name[cy]=Guarani
Name[da]=Guarani
Name[de]=Guarani
Name[el]=Γκουαράνι
Name[en_GB]=Guarani
Name[eo]=Gvarania
Name[es]=Guaraní
Name[et]=Guaranii
Name[eu]=Guaraniera
Name[fa]=گوارانی
Name[fi]=Guarani
Name[fr]=Guarani
Name[fy]=Guarani
Name[ga]=Guaráinis
Name[gl]=Guaraní
Name[gu]=ગુઆરાની
Name[he]=גוארני
Name[hi]=गौरानी
Name[hne]=गौरानी
Name[hr]=Gvaranski
Name[hsb]=Guarani
Name[hu]=Guarani
Name[ia]=Guarani
Name[id]=Guarani
Name[is]=Guarani
Name[it]=Guaraní
Name[ja]=グァラニ語
Name[kk]=Гуарани
Name[km]=ហ្គារ៉ានី
Name[kn]=ಗುವಾರಾನಿ
Name[ko]=과라니어
Name[ku]=Guaranî
Name[lb]=Guarani
Name[lt]=Guarani
Name[lv]=Gvaranu
Name[mai]=गौरानी
Name[mk]=Гуарани
Name[ml]=ഗുരാണി
Name[mr]=गौरानी
Name[ms]=Guarani
Name[nb]=Guarani
Name[nds]=Guarani
Name[ne]=गुवारानी
Name[nl]=Guarani
Name[nn]=Guarani
Name[oc]=Guarari
Name[or]=Guarani
Name[pa]=ਗੁਜਰਾਨੀ
Name[pl]=Guarani
Name[ps]=ګوراني
Name[pt]=Guarani
Name[pt_BR]=Guarani
Name[ro]=Guarană
Name[ru]=Гуарани
Name[se]=Guaránagiella
Name[si]=ගුවාරානි
Name[sk]=Guaraníjčina
Name[sl]=guarani
Name[sq]=Guarani
Name[sr]=гварани
Name[sr@ijekavian]=гварани
Name[sr@ijekavianlatin]=gvarani
Name[sr@latin]=gvarani
Name[sv]=Guarani
Name[ta]=குவரானி
Name[te]=గువారాని
Name[tg]=Гуаранӣ
Name[th]=ภาษากวารานี
Name[tr]=Guarani
Name[tt]=Гуаранча
Name[ug]=گۇئارانىچە
Name[uk]=Гуарані
Name[uz]=Guarani
Name[uz@cyrillic]=Гуарани
Name[vi]=Guarani
Name[wa]=Gwarani
Name[xh]=Guarani
Name[x-test]=xxGuaranixx
Name[zh_CN]=瓜拉尼语
Name[zh_HK]=瓜拉尼語
Name[zh_TW]=瓜拉尼語
[gu]
Name=Gujarati
Name[af]=Gujarati
Name[ar]=كوجراتي
Name[as]=গুজ্ৰাটি
Name[ast]=Gujaratí
Name[be]=Гуяраці
Name[be@latin]=Gujarati
Name[bg]=Гуджарати
Name[bn]=গুজরাতী
Name[bn_IN]=গুজরাতি
Name[br]=Gujaratieg
Name[bs]=Guđaratski
Name[ca]=Gujarati
Name[ca@valencia]=Gujarati
Name[cs]=Gujarati
Name[csb]=Gujarati
Name[cy]=Gujarati
Name[da]=Gujarati
Name[de]=Gujarati
Name[el]=Gujarati
Name[en_GB]=Gujarati
Name[eo]=Guĝarata
Name[es]=Gujarati
Name[et]=Gudžarati
Name[eu]=Gujaratera
Name[fa]=گجراتی
Name[fi]=Gudžarati
Name[fr]=Gujarati
Name[fy]=Gujarati
Name[ga]=Gúisearáitis
Name[gl]=Guxarati
Name[gu]=ગુજરાતી
Name[he]=גוג'ראטית
Name[hi]=गुजराती
Name[hne]=गुजराती
Name[hr]=Gudžaratski
Name[hsb]=Gujarati
Name[hu]=Gudzsarati
Name[ia]=Gujarati
Name[id]=Gujarati
Name[is]=Gujarati
Name[it]=Gujarati
Name[ja]=グジャラート語
Name[kk]=Гуджарати
Name[km]=ហ្គុយ៉ារ៉ាទី
Name[kn]=ಗುಜರಾತಿ
Name[ko]=구자라트어
Name[ku]=Guyaratî
Name[lb]=Gujarati
Name[lt]=Gudžarati
Name[lv]=Gudžarati
Name[mai]=गुजराती
Name[mk]=Гујарати
Name[ml]=ഗുജറാത്തി
Name[mr]=गुजराती
Name[ms]=Gujarati
Name[nb]=Gujarati
Name[nds]=Gudscharati
Name[ne]=गुजराती
Name[nl]=Gujarati
Name[nn]=Gujarati
Name[oc]=Gujarati
Name[or]=Gujarati
Name[pa]=ਗੁਜਰਾਤੀ
Name[pl]=Gujarati
Name[ps]=ګجراتي
Name[pt]=Gujarati
Name[pt_BR]=Guzerate
Name[ro]=Gujarati
Name[ru]=Гуджарати
Name[se]=Gujaratigiella
Name[si]=ගුජරාටි
Name[sk]=Gudžarátčina
Name[sl]=gujarati
Name[sq]=Gujarati
Name[sr]=гуџарати
Name[sr@ijekavian]=гуџарати
Name[sr@ijekavianlatin]=gudžarati
Name[sr@latin]=gudžarati
Name[sv]=Gujarati
Name[ta]=குஜராத்தி
Name[te]=గుజరాతీ
Name[tg]=Гӯҷаратӣ
Name[th]=ภาษากูจาราตี
Name[tr]=Gujarati
Name[tt]=Гөҗәрәти
Name[ug]=گۇجاراتچە
Name[uk]=Гуджараті
Name[uz]=Gujarati
Name[uz@cyrillic]=Гужарати
Name[vi]=Gujarati
Name[wa]=Goudjarati
Name[xh]=Gujarati
Name[x-test]=xxGujaratixx
Name[zh_CN]=古吉拉特语
Name[zh_HK]=古吉拉特語
Name[zh_TW]=古吉拉特語
[gv]
Name=Manx
Name[af]=Manx
Name[ar]=مانكس
Name[as]=মানক্স
Name[ast]=Manés
Name[be]=Мэнкс
Name[be@latin]=Manx
Name[bg]=Манкски
Name[bn]=মানক্স
Name[bn_IN]=ম্যাঙ্গস
Name[br]=Manav
Name[bs]=Manski(Keltski)
Name[ca]=Manx
Name[ca@valencia]=Manx
Name[cs]=Manx
Name[csb]=Manx
Name[cy]=Manneg
Name[da]=Mansk
Name[de]=Manx
Name[el]=Manx (Gaelg)
Name[en_GB]=Manx
Name[eo]=Manksa
Name[es]=Manés
Name[et]=Mänksi
Name[eu]=Manera
Name[fa]=مانکسی
Name[fi]=Manx
Name[fr]=Mannois
Name[fy]=Manx
Name[ga]=Manainnis
Name[gl]=Manés
Name[gu]=માન્ક્ષ
Name[he]=מאנית
Name[hi]=मांक्स
Name[hne]=मांक्स
Name[hr]=Manx
Name[hsb]=Manxowsce
Name[hu]=Manx
Name[ia]=Manx
Name[id]=Manx
Name[is]=Manx
Name[it]=Mannese
Name[ja]=マン島語
Name[kk]=Манксша
Name[km]=ម៉ង
Name[kn]=ಮಾನ್ಕ್ಸ್
Name[ko]=망스어
Name[ku]=Manks
Name[lb]=Manx
Name[lt]=Manx
Name[lv]=Meniešu
Name[mai]=मांक्स
Name[mk]=Манкс
Name[ml]=മാന്‍ക്സ്
Name[mr]=मांक्स
Name[ms]=Manx
Name[nb]=Manx
Name[nds]=Manx
Name[ne]=म्याङ्क्स
Name[nl]=Manx
Name[nn]=Manx
Name[or]=Manx
Name[pa]=ਮਾਲਸ
Name[pl]=Manx
Name[ps]=مېنکس
Name[pt]=Manx
Name[pt_BR]=Manês
Name[ro]=Manxă
Name[ru]=Мэнский
Name[se]=Mánksagiella
Name[si]=මැන්ක්ස්
Name[sk]=Mančina
Name[sl]=manx
Name[sq]=Manks
Name[sr]=манкс
Name[sr@ijekavian]=манкс
Name[sr@ijekavianlatin]=manks
Name[sr@latin]=manks
Name[sv]=Manx
Name[ta]=மான்க்ஸ்
Name[te]=మేన్క్స్
Name[tg]=Манксӣ
Name[th]=ภาษาแมงซ์
Name[tr]=Manx
Name[tt]=Манксча
Name[ug]=مانكىسچە
Name[uk]=Манкс
Name[uz]=Manks
Name[uz@cyrillic]=Манкс
Name[vi]=Manx
Name[wa]=Gayel del Iye di Man
Name[xh]=Manx
Name[x-test]=xxManxxx
Name[zh_CN]=马恩语
Name[zh_HK]=曼島語
Name[zh_TW]=曼島語
[ha]
Name=Hausa
Name[af]=Hausa
Name[ar]=هوس
Name[as]=হাউছা
Name[ast]=Hausa
Name[be]=Хаўса
Name[be@latin]=Hausa
Name[bg]=Хауса
Name[bn]=হাউসা
Name[bn_IN]=হাওসা
Name[br]=Haousa
Name[bs]=Hausa
Name[ca]=Haussa
Name[ca@valencia]=Haussa
Name[cs]=Hausa
Name[csb]=Hausa
Name[cy]=Hausa
Name[da]=Hausa
Name[de]=Hausa
Name[el]=Hausa
Name[en_GB]=Hausa
Name[eo]=Haŭsa
Name[es]=Hausa
Name[et]=Hausa
Name[eu]=Hausa
Name[fa]=هوسا
Name[fi]=Hausa
Name[fr]=Haoussa
Name[fy]=Hausa
Name[ga]=Hásais
Name[gl]=Hausa
Name[gu]=હુસા
Name[he]=האוסה
Name[hi]=हौसा
Name[hne]=हौसा
Name[hr]=Hausa
Name[hsb]=Hausasce
Name[hu]=Hausa
Name[ia]=Hausa
Name[id]=Hausa
Name[is]=Hausa
Name[it]=Hausa
Name[ja]=ハウサ語
Name[kk]=Хауса
Name[km]=ហូសា
Name[kn]=ಹೌಸಾ
Name[ko]=하우사어
Name[ku]=Hawsa
Name[lb]=Haussa-Sprooch
Name[lt]=Hausa
Name[lv]=Hausu
Name[mai]=हौसा
Name[mk]=Хауса
Name[ml]=ഹൌസാ
Name[mr]=हौसा
Name[ms]=Hausa
Name[nb]=Hausa
Name[nds]=Haussa
Name[ne]=हउसा
Name[nl]=Hausa
Name[nn]=Hausa
Name[oc]=Hausa
Name[or]=Hausa
Name[pa]=ਹਾਉਸਾ
Name[pl]=Hausa
Name[ps]=هاوسا
Name[pt]=Hausa
Name[pt_BR]=Haussá
Name[ro]=Hausă
Name[ru]=Хауса
Name[se]=Hausagiella
Name[si]=හවුසා
Name[sk]=Hauština
Name[sl]=hausa
Name[sq]=Hausa
Name[sr]=хауса
Name[sr@ijekavian]=хауса
Name[sr@ijekavianlatin]=hausa
Name[sr@latin]=hausa
Name[sv]=Hausa
Name[ta]=ஹவுசா
Name[te]=హౌసా
Name[tg]=Хаусагӣ
Name[th]=ภาษาเฮาซา
Name[tr]=Hausa
Name[tt]=Хауз
Name[ug]=خائۇساچە
Name[uk]=Гауса
Name[uz]=Xausa
Name[uz@cyrillic]=Хауса
Name[vi]=Hausa
Name[wa]=Hawsa
Name[xh]=Hausa
Name[x-test]=xxHausaxx
Name[zh_CN]=豪撒语
Name[zh_HK]=豪薩語
Name[zh_TW]=豪薩語
[he]
Name=Hebrew
Name[af]=Hibreüs
Name[ar]=عبرية
Name[as]=হিব্ৰিউ
Name[ast]=Hebreu
Name[be]=Габрэйская
Name[be@latin]=Habrejskaja
Name[bg]=Иврит
Name[bn]=হীব্রু
Name[bn_IN]=হিব্রু
Name[br]=Hebreeg
Name[bs]=Hebrejski
Name[ca]=Hebreu
Name[ca@valencia]=Hebreu
Name[cs]=Hebrejský
Name[csb]=Hebrejsczi
Name[cy]=Iddeweg
Name[da]=Hebraisk
Name[de]=Hebräisch
Name[el]=Εβραϊκά
Name[en_GB]=Hebrew
Name[eo]=Hebrea
Name[es]=Hebreo
Name[et]=Heebrea
Name[eu]=Hebreera
Name[fa]=عبری
Name[fi]=Heprea
Name[fr]=Hébreu
Name[fy]=Hebrieuwsk
Name[ga]=Eabhrais
Name[gl]=Hebreu
Name[gu]=હિબ્રુ
Name[he]=עברית
Name[hi]=हिब्रू
Name[hne]=हिब्रू
Name[hr]=Hebrejski
Name[hsb]=Hebrejsce
Name[hu]=Héber
Name[ia]=Hebreo
Name[id]=Ibrani
Name[is]=Hebreska
Name[it]=Ebraico
Name[ja]=ヘブライ語
Name[kk]=Ивритше
Name[km]=ហេប្រ៊ូ
Name[kn]=ಹೀಬ್ರೂ
Name[ko]=히브리어
Name[ku]=Îbranî
Name[lb]=Hebräesch
Name[lt]=Hebrajų
Name[lv]=Ivrits
Name[mai]=हिब्रू
Name[mk]=Еврејски
Name[ml]=ഹീബ്രു
Name[mr]=हिब्रू
Name[ms]=Hebrew
Name[nb]=Hebraisk
Name[nds]=Hebrääsch
Name[ne]=हिब्रु
Name[nl]=Hebreeuws
Name[nn]=Hebraisk
Name[oc]=Ebrèu
Name[or]=Hebrew
Name[pa]=ਹੈਬਰਿਊ
Name[pl]=Hebrajski
Name[ps]=هېبرو
Name[pt]=Hebreu
Name[pt_BR]=Hebraico
Name[ro]=Ebraică
Name[ru]=Иврит
Name[se]=Hebreagiella
Name[si]=හීබෲ
Name[sk]=Hebrejčina
Name[sl]=hebrejsko
Name[sq]=Hebraike
Name[sr]=хебрејски
Name[sr@ijekavian]=хебрејски
Name[sr@ijekavianlatin]=hebrejski
Name[sr@latin]=hebrejski
Name[sv]=Hebreiska
Name[ta]=எபிரேயம்
Name[te]=హీబ్రూ
Name[tg]=Яҳудӣ
Name[th]=ภาษาฮิบรู
Name[tr]=İbranice
Name[tt]=Иврит
Name[ug]=ئىبرانىچە
Name[uk]=Єврейська
Name[uz]=Yahudiycha
Name[uz@cyrillic]=Яҳудийча
Name[vi]=Do Thái
Name[wa]=Ebreu
Name[xh]=Isihebhere
Name[x-test]=xxHebrewxx
Name[zh_CN]=希伯来语
Name[zh_HK]=希伯來語
Name[zh_TW]=希伯來語
[hi]
Name=Hindi
Name[af]=Hindi
Name[ar]=هندية
Name[as]=হিন্দী
Name[ast]=Hindí
Name[be]=Хіндзі
Name[be@latin]=Hindzi
Name[bg]=Хинди
Name[bn]=হিন্দী
Name[bn_IN]=হিন্দি
Name[br]=Hindi
Name[bs]=Hindu
Name[ca]=Hindi
Name[ca@valencia]=Hindi
Name[cs]=Hindský
Name[csb]=Hindi
Name[cy]=Hindw^
Name[da]=Hindi
Name[de]=Hindi
Name[el]=Hindi
Name[en_GB]=Hindi
Name[eo]=Hinda
Name[es]=Hindi
Name[et]=Hindi
Name[eu]=Hindi
Name[fa]=هندی
Name[fi]=Hindi
Name[fr]=Hindi
Name[fy]=Hindy
Name[ga]=Hiondúis
Name[gl]=Hindi
Name[gu]=હિન્દી
Name[he]=הינדית
Name[hi]=हिन्दी
Name[hne]=हिन्दी
Name[hr]=Hinduski
Name[hsb]=Hindisce
Name[hu]=Hindi
Name[hy]=Հինդի
Name[ia]=Hindi
Name[id]=Hindi
Name[is]=Hindi
Name[it]=Hindi
Name[ja]=ヒンディー語
Name[kk]=Хинди
Name[km]=ហិណ្ឌូ
Name[kn]=ಹಿಂದಿ
Name[ko]=힌디어
Name[ku]=Hîndî
Name[lb]=Hindi
Name[lt]=Hindi
Name[lv]=Hindu
Name[mai]=हिन्दी
Name[mk]=Хинди
Name[ml]=ഹിന്ദി
Name[mr]=हिंदी
Name[ms]=Hindi
Name[nb]=Hindi
Name[nds]=Hindi
Name[ne]=हिन्दी
Name[nl]=Hindi
Name[nn]=Hindi
Name[oc]=Indi
Name[or]=Hindi
Name[pa]=ਹਿੰਦੀ
Name[pl]=Hindi
Name[ps]=هندي
Name[pt]=Hindu
Name[pt_BR]=Hindi
Name[ro]=Hindusă
Name[ru]=Хинди
Name[se]=Hindigiella
Name[si]=හින්දි
Name[sk]=Hindčina
Name[sl]=hindujsko
Name[sq]=Hindi
Name[sr]=хинду
Name[sr@ijekavian]=хинду
Name[sr@ijekavianlatin]=hindu
Name[sr@latin]=hindu
Name[sv]=Hindi
Name[ta]=ஹிந்தி
Name[te]=హింది
Name[tg]=Ҳиндӣ
Name[th]=ภาษาฮินดี
Name[tr]=Hintçe
Name[tt]=Һинд
Name[ug]=ھىندىچە
Name[uk]=Хінді
Name[uz]=Hindcha
Name[uz@cyrillic]=Ҳиндча
Name[vi]=Hindi
Name[wa]=Hindi
Name[xh]=Hindi
Name[x-test]=xxHindixx
Name[zh_CN]=印度语
Name[zh_HK]=北印度語
Name[zh_TW]=北印度語
[hne]
Name=Chhattisgarhi
Name[ar]=تشهتسجريا
Name[ast]=Chhattisgarhi
Name[be@latin]=Chhattisgarhi
Name[bg]=Чхатисгархи
Name[bn]=ছত্তিসগড়ি
Name[bs]=Čatigarski
Name[ca]=Chattisgarbi
Name[ca@valencia]=Chattisgarbi
Name[cs]=Chhattisgarhi
Name[da]=Chhattisgarhi
Name[de]=Chhattisgarhi
Name[el]=Χατισγκαρχί
Name[en_GB]=Chhattisgarhi
Name[eo]=Chhattisgarhi
Name[es]=Chhattisgarhi
Name[et]=Chhattisgarhi
Name[eu]=Chhattisgarhi
Name[fi]=Chhattisgarhi
Name[fr]=Chhattisgarhi
Name[fy]=Chhattisgarhi
Name[ga]=Chaitisgharhís
Name[gl]=Chhattisgarhi
Name[gu]=છત્તિસગઢી
Name[he]=צ'האטיסגארי
Name[hr]=Chhattisgarhi
Name[hu]=Cshattíszgarhi
Name[ia]=Chhattisgarhi
Name[id]=Chhattisgarhi
Name[is]=Chhattisgarhi
Name[it]=Chhattisgarhi
Name[ja]=チャッティースガリー語
Name[kk]=Чхаттисгарти
Name[km]=ឆាយទីសហ្គាស៊ី
Name[kn]=ಛತ್ತೀಸ್ಗರ್ಹಿ
Name[ko]=차티스가르어
Name[ku]=Şattisgarî
Name[lv]=Čatisgari
Name[ml]=ചത്തീസ്ഗരി
Name[ms]=Chhattisgarhi
Name[nb]=Chhattisgarhi
Name[nds]=Chhattisgarhi
Name[nl]=Chhattisgarhi
Name[nn]=Tsjhattisgarhi
Name[pa]=ਛੱਤੀਸਗੜ੍ਹੀ
Name[pl]=Chhattisgarhi
Name[pt]=Chhattisgarhi
Name[pt_BR]=Chhattisgarhi
Name[ro]=Chhattisgarhi
Name[ru]=Чхаттисгархи
Name[se]=Čattisgarhigiella
Name[sk]=Čatísgarh
Name[sq]=Chhattisgarhi
Name[sr]=чатисгархи
Name[sr@ijekavian]=чатисгархи
Name[sr@ijekavianlatin]=čatisgarhi
Name[sr@latin]=čatisgarhi
Name[sv]=Chhattisgarhi
Name[ta]=சத்தீஸ்கரி
Name[tg]=Chhattisgarhi
Name[th]=ภาษาฉัตติสครห์
Name[tr]=Chhattisgarhi
Name[tt]=Чхаттисгарх
Name[ug]=چاتتىسگارھىچە
Name[uk]=Чхатісгарі
Name[vi]=Chhattisgarhi
Name[wa]=Chhattisgarhi
Name[x-test]=xxChhattisgarhixx
Name[zh_CN]=恰蒂斯加尔语
Name[zh_TW]=Chhattisgarhi
[ho]
Name=Hiri Motu
Name[af]=Hiri Motu
Name[ar]=هيري موتو
Name[as]=হিৰি মোটু
Name[ast]=Hiri Motu
Name[be]=Хірымоту
Name[be@latin]=Hiri Motu
Name[bg]=Хири Моту
Name[bn]=হিরি মোটু
Name[bn_IN]=হিরি মোটু
Name[br]=Hiri Motu
Name[bs]=Hiri Motu
Name[ca]=Hiri Motu
Name[ca@valencia]=Hiri Motu
Name[cs]=Hiri Motu
Name[csb]=Hiri Motu
Name[cy]=Hiri Motu
Name[da]=Hiri Motu
Name[de]=Hiri Motu
Name[el]=Hiri Motu
Name[en_GB]=Hiri Motu
Name[eo]=Hirimotua
Name[es]=Hiri Motu
Name[et]=Hiri Motu
Name[eu]=Hiri Motu
Name[fa]=هیری موتو
Name[fi]=Hiri-motu
Name[fr]=Hiri motu
Name[fy]=Hiri Motu
Name[ga]=Hírí-Mótúis
Name[gl]=Hiri Motu
Name[gu]=હીરી મોટુ
Name[he]=הירי מוטו
Name[hi]=हिरी मोतू
Name[hne]=हिरी मोतू
Name[hr]=Hiri Motu
Name[hsb]=Hiri Motusce
Name[hu]=Hiri motu
Name[ia]=Hiri Motu
Name[id]=Hiri Motu
Name[is]=Hiri Motu
Name[it]=Hiri motu
Name[ja]=ヒリモトゥ語
Name[kk]=Хири Моту
Name[km]=ហ៊ីរី ម៉ូទូ
Name[kn]=ಹೀರೀ ಮೋಟೂ
Name[ko]=히리 모투어
Name[ku]=Hîrî Motu
Name[lb]=Hiri-Motu
Name[lt]=Hiri Motu
Name[lv]=Hirimotu
Name[mai]=हिरी मोतू
Name[mk]=Хири Моту
Name[ml]=ഹിരി മൊട്ടു
Name[mr]=हिरी मोतू
Name[ms]=Hiri Motu
Name[nb]=Hiri Motu
Name[nds]=Hiri Motu
Name[ne]=हिरी मोतु
Name[nl]=Hiri Motu
Name[nn]=Hiri motu
Name[oc]=Iri Motu
Name[or]=Hiri Motu
Name[pa]=ਹੀਰੀ ਮੋਟੂ
Name[pl]=Hiri Motu
Name[ps]=هيري موټو
Name[pt]=Hiri Motu
Name[pt_BR]=Hiri Motu
Name[ro]=Hiri Motu
Name[ru]=Хири-моту
Name[se]=Hiri Motu-giella
Name[si]=හිරි මොටු
Name[sk]=Hiri Motu
Name[sl]=hiri motu
Name[sq]=Hiri Motu
Name[sr]=хири‑моту
Name[sr@ijekavian]=хири‑моту
Name[sr@ijekavianlatin]=hiri‑motu
Name[sr@latin]=hiri‑motu
Name[sv]=Hirimotu
Name[ta]=ஹிரி மோடு
Name[te]=హిరి మోటు
Name[tg]=Хири Моту
Name[th]=ภาษาฮิริโมตู
Name[tr]=Hiri Motu
Name[tt]=Хири Моту
Name[ug]=خىرى موتۇچە
Name[uk]=Хірімоту
Name[uz]=Xiri Motu
Name[uz@cyrillic]=Хири Моту
Name[vi]=Hiri Motu
Name[wa]=Hiri Motu
Name[xh]=Hiri Motu
Name[x-test]=xxHiri Motuxx
Name[zh_CN]=希里莫图语
Name[zh_HK]=Hiri Motu語
Name[zh_TW]=Hiri Motu語
[hr]
Name=Croatian
Name[af]=Kroatiese
Name[ar]=كرواتية
Name[as]=ক্ৰোয়েচিয়ান
Name[ast]=Croata
Name[be]=Харвацкая
Name[be@latin]=Charvackaja
Name[bg]=Хърватски
Name[bn]=ক্রোয়েশীয়
Name[bn_IN]=ক্রোয়েশিয়ান
Name[br]=Kroateg
Name[bs]=Hrvatski
Name[ca]=Croat
Name[ca@valencia]=Croat
Name[cs]=Chorvatský
Name[csb]=Chòrwacczi
Name[cy]=Croatieg
Name[da]=Kroatisk
Name[de]=Kroatisch
Name[el]=Κροατικά
Name[en_GB]=Croatian
Name[eo]=Kroata
Name[es]=Croata
Name[et]=Horvaadi
Name[eu]=Kroaziera
Name[fa]=کروواسیایی
Name[fi]=Kroatia
Name[fr]=Croate
Name[fy]=Kroatysk
Name[ga]=Cróitis
Name[gl]=Croata
Name[gu]=ક્રોએશિયન
Name[he]=קרואטית
Name[hi]=क्रोएशियाई
Name[hne]=क्रोएसियाई
Name[hr]=Hrvatski
Name[hsb]=Chorwatsce
Name[hu]=Horvát
Name[ia]=Croato
Name[id]=Kroatia
Name[is]=Króatíska
Name[it]=Croato
Name[ja]=クロアチア語
Name[kk]=Хорватша
Name[km]=ក្រូអាត
Name[kn]=ಕ್ರೊಯೇಶಿಯನ್
Name[ko]=크로아티아어
Name[ku]=Kroatî
Name[lb]=Kroatesch
Name[lt]=Kroatų
Name[lv]=Horvātu
Name[mai]=क्रोएशियाइ
Name[mk]=Хрватски
Name[ml]=ക്രോയേഷ്യന്‍
Name[mr]=क्रोएशीयन
Name[ms]=Croatia
Name[nb]=Kroatisk
Name[nds]=Kroaatsch
Name[ne]=क्रोयसियाली
Name[nl]=Kroatisch
Name[nn]=Kroatisk
Name[oc]=Croat
Name[or]=Croatian
Name[pa]=ਕਰੋਟੀਅਨ
Name[pl]=Chorwacki
Name[ps]=کروټيايي
Name[pt]=Croata
Name[pt_BR]=Croata
Name[ro]=Croată
Name[ru]=Хорватский
Name[se]=Kroatiagiella
Name[si]=ක්‍රොඒෂියානු
Name[sk]=Chorvátčina
Name[sl]=hrvaško
Name[sq]=Kroate
Name[sr]=хрватски
Name[sr@ijekavian]=хрватски
Name[sr@ijekavianlatin]=hrvatski
Name[sr@latin]=hrvatski
Name[sv]=Kroatiska
Name[ta]=குரொயேசியன்
Name[te]=క్రొయెషియన్
Name[tg]=Хорватӣ
Name[th]=ภาษาโครเอเชีย
Name[tr]=Hırvatça
Name[tt]=Хорват
Name[ug]=خورۋاتچە
Name[uk]=Хорватська
Name[uz]=Xorvatcha
Name[uz@cyrillic]=Хорватча
Name[vi]=Croatia
Name[wa]=Crowåte
Name[xh]=Croatian
Name[x-test]=xxCroatianxx
Name[zh_CN]=克罗地亚语
Name[zh_HK]=克羅地亞語
Name[zh_TW]=克羅埃西亞語
[hsb]
Name=Upper Sorbian
Name[af]=Hoog Serbiese
Name[ar]=صوربية عُليا
Name[as]=আপাৰ ছৰ্বিয়ান
Name[ast]=Altu Sorbiu
Name[be]=Верхнялужыцкая
Name[be@latin]=Vierchnie-łužyckaja
Name[bg]=Горносорбийски
Name[bn]=উচ্চ সার্বীয়
Name[bn_IN]=আপার সোর্বিয়ান
Name[br]=Sorab uhel
Name[bs]=Gornjolužičkosrpski
Name[ca]=Alt sòrab
Name[ca@valencia]=Alt sòrab
Name[cs]=Hornolužicko srbský
Name[csb]=Górnosorbsczi
Name[cy]=Sorbieg Uchaf
Name[da]=Øvre sorbisk
Name[de]=Obersorbisch
Name[el]=Άνω Σορβικά
Name[en_GB]=Upper Sorbian
Name[eo]=Alta soraba
Name[es]=Alto sorabo
Name[et]=Ülemsorbi
Name[eu]=Goi Serbiera
Name[fa]=صربستان شمالی
Name[fi]=Yläsorbi
Name[fr]=Haut-Sorabe
Name[fy]=Heech Sorbysk
Name[ga]=Sorbais Uachtarach
Name[gl]=Alto sórabo
Name[gu]=અપર સોર્બિયન
Name[he]=סורבית עילית
Name[hi]=ऊपरी सॉर्बियाई
Name[hne]=ऊपरी सार्बियाई
Name[hr]=Gornjosrpski
Name[hsb]=Hornjoserbsce
Name[hu]=Felső szorb
Name[ia]=Alto Sorbiano
Name[id]=Sorbian Atas
Name[is]=Upper Sorbian
Name[it]=Alto sorabo
Name[ja]=上ソルブ語
Name[kk]=Жоғары сорбше
Name[km]=សូបៀន លើ
Name[kn]=ಮೇಲಿನ ಸೋರ್ಬಿಯನ್
Name[ko]=고지대 소르비아어
Name[ku]=Sorbiya Jorîn
Name[lb]=Uewersorbesch
Name[lt]=Upper Sorbian
Name[lv]=Augšsorbu
Name[mai]=ऊपरी सॉर्बियाइ
Name[mk]=Јужно лужички
Name[ml]=അപ്പര്‍ സോര്‍ബിയന്‍
Name[mr]=अप्पर सोर्बीयन
Name[ms]=Upper Sorbian
Name[nb]=Øvresorbisk
Name[nds]=Böversorbsch
Name[ne]=माथिल्लो सोर्बियाली
Name[nl]=Opper Sorbian
Name[nn]=Høgsorbisk
Name[or]=Upper Sorbian
Name[pa]=ਉੱਪਰੀ ਸਰਬੀਆਈ
Name[pl]=Górnołużycki
Name[ps]=بره سربي
Name[pt]=Sérvio de Cima
Name[pt_BR]=Alto Sorábio
Name[ro]=Sîrbă de Sus
Name[ru]=Верхнелужицкий
Name[se]=Bajil Sorbiagiella
Name[si]=ඉහළ සෝබියානු
Name[sk]=Hornolužická srbčina
Name[sl]=zgornjesorbijsko
Name[sq]=Sorbiane e Sipërme
Name[sr]=горњолужичкосрпски
Name[sr@ijekavian]=горњолужичкосрпски
Name[sr@ijekavianlatin]=gornjolužičkosrpski
Name[sr@latin]=gornjolužičkosrpski
Name[sv]=Högsorbiska
Name[ta]=செர்பியன் பெரியெழுத்து
Name[te]=అప్పర్ సొర్బియన్
Name[tg]=Сербияи Болоӣ
Name[th]=ภาษาซอร์เบียนตอนบน
Name[tr]=Yukarı Sırpça
Name[tt]=Сорбча (Өске)
Name[ug]=ئۈستى سوربىيانچە
Name[uk]=Верхньолужицька
Name[uz]=Yuqori Sorbcha
Name[uz@cyrillic]=Юқори Сорбча
Name[wa]=Hôt sorbyin
Name[x-test]=xxUpper Sorbianxx
Name[zh_CN]=上索布语
Name[zh_TW]=塞爾維亞語
[hu]
Name=Hungarian
Name[af]=Hongaars
Name[ar]=مجرية
Name[as]=হাঙ্গেৰীয়
Name[ast]=Húngaru
Name[be]=Венгерская
Name[be@latin]=Vuhorskaja
Name[bg]=Унгарски
Name[bn]=হাঙ্গারীয়
Name[bn_IN]=হাঙ্গেরিয়ান
Name[br]=Hungareg
Name[bs]=Mađarski
Name[ca]=Hongarès
Name[ca@valencia]=Hongarés
Name[cs]=Maďarský
Name[csb]=Madżarsczi
Name[cy]=Hwngareg
Name[da]=Ungarsk
Name[de]=Ungarisch
Name[el]=Ουγγρικά
Name[en_GB]=Hungarian
Name[eo]=Hungara
Name[es]=Húngaro
Name[et]=Ungari
Name[eu]=Hungariera
Name[fa]=مجارستانی
Name[fi]=Unkari
Name[fr]=Hongrois
Name[fy]=Hongaarsk
Name[ga]=Ungáiris
Name[gl]=Húngaro
Name[gu]=હંગેરિયન
Name[he]=הונגרית
Name[hi]=हंगेरियाई
Name[hne]=हंगेरियाई
Name[hr]=Mađarski
Name[hsb]=Madźarsce
Name[hu]=Magyar
Name[ia]=Hungaro
Name[id]=Hungaria
Name[is]=Ungverska
Name[it]=Ungherese
Name[ja]=ハンガリー語
Name[kk]=Мажарша
Name[km]=ហុងគ្រី
Name[kn]=ಹಂಗೇರಿಯನ್
Name[ko]=헝가리어
Name[ku]=Macarî
Name[lb]=Ungaresch
Name[lt]=Vengrų
Name[lv]=Ungāru
Name[mai]=हंगेरियाई
Name[mk]=Унгарски
Name[ml]=ഹംഗേറിയന്‍
Name[mr]=हंगेरियाई
Name[ms]=Hungaria
Name[nb]=Ungarsk
Name[nds]=Ungaarsch
Name[ne]=हङ्गेरियन
Name[nl]=Hongaars
Name[nn]=Ungarsk
Name[oc]=Ongrés
Name[or]=Hungarian
Name[pa]=ਹੰਗਰੀਆਈ
Name[pl]=Węgierski
Name[ps]=هنګريايي
Name[pt]=Húngaro
Name[pt_BR]=Húngaro
Name[ro]=Maghiară
Name[ru]=Венгерский
Name[se]=Ungárgiella
Name[si]=හංගේරියානු
Name[sk]=Maďarčina
Name[sl]=madžarsko
Name[sq]=Hungareze
Name[sr]=мађарски
Name[sr@ijekavian]=мађарски
Name[sr@ijekavianlatin]=mađarski
Name[sr@latin]=mađarski
Name[sv]=Ungerska
Name[ta]=ஹங்கேரியன்
Name[te]=హంగెరియన్
Name[tg]=Венгерӣ
Name[th]=ภาษาฮังการี
Name[tr]=Macarca
Name[tt]=Венгер
Name[ug]=ماجارچە
Name[uk]=Угорська
Name[uz]=Vengrcha
Name[uz@cyrillic]=Венгрча
Name[vi]=Hung-ga-ry
Name[wa]=Hongrwès
Name[xh]=Hungarian
Name[x-test]=xxHungarianxx
Name[zh_CN]=匈牙利语
Name[zh_HK]=匈牙利語
Name[zh_TW]=匈牙利語
[hy]
Name=Armenian
Name[af]=Armeens
Name[ar]=أرمنية
Name[as]=আৰ্মেনিয়ান
Name[ast]=Armeniu
Name[be]=Армянская
Name[be@latin]=Armianskaja
Name[bg]=Арменски
Name[bn]=আর্মেনীয়
Name[bn_IN]=আর্মেনিয়ান
Name[br]=Armenieg
Name[bs]=Jermenski
Name[ca]=Armeni
Name[ca@valencia]=Armeni
Name[cs]=Arménský
Name[csb]=Armeńsczi
Name[cy]=Armeineg
Name[da]=Armensk
Name[de]=Armenisch
Name[el]=Αρμενικά
Name[en_GB]=Armenian
Name[eo]=Armena
Name[es]=Armenio
Name[et]=Armeenia
Name[eu]=Armeniera
Name[fa]=ارمنی
Name[fi]=Armenia
Name[fr]=Arménien
Name[fy]=Armeensk
Name[ga]=Airméinis
Name[gl]=Armenio
Name[gu]=અર્મેનિયન
Name[he]=ארמנית
Name[hi]=आर्मेनियाई
Name[hne]=आर्मेनियाई
Name[hr]=Armenski
Name[hsb]=Armensce
Name[hu]=Örmény
Name[hy]=Հայերեն
Name[ia]=Armenio
Name[id]=Armenia
Name[is]=Armeskt
Name[it]=Armeno
Name[ja]=アルメニア語
Name[kk]=Арменше
Name[km]=អារមេនី
Name[kn]=ಅರ್ಮೇನಿಯನ್
Name[ko]=아르메니아어
Name[ku]=Ermenî
Name[lb]=Armenesch
Name[lt]=Armėnų
Name[lv]=Armēņu
Name[mai]=आर्मेनियाइ
Name[mk]=Ерменски
Name[ml]=അര്‍മീനിയന്‍
Name[mr]=आर्मेनिअन
Name[ms]=Armenia
Name[nb]=Armensk
Name[nds]=Armeensch
Name[ne]=आर्मेनियाली
Name[nl]=Armeens
Name[nn]=Armensk
Name[oc]=Armenian
Name[or]=Armenian
Name[pa]=ਅਰਮੀਆਈ
Name[pl]=Ormiański
Name[ps]=ارمينيايي
Name[pt]=Arménio
Name[pt_BR]=Armênio
Name[ro]=Armeană
Name[ru]=Армянский
Name[se]=Armeniagiella
Name[si]=ආමේනියානු
Name[sk]=Arménčina
Name[sl]=armensko
Name[sq]=Armenisht
Name[sr]=јерменски
Name[sr@ijekavian]=јерменски
Name[sr@ijekavianlatin]=jermenski
Name[sr@latin]=jermenski
Name[sv]=Armenska
Name[ta]=ஆர்மீனியன்
Name[te]=అర్మెనియన్
Name[tg]=Арманӣ
Name[th]=ภาษาอาร์เมเนีย
Name[tr]=Ermenice
Name[tt]=Әрмән
Name[ug]=ئەرمەنچە
Name[uk]=Вірменська
Name[uz]=Armancha
Name[uz@cyrillic]=Арманча
Name[vi]=Ác-mê-ni-a
Name[wa]=Årmenyin
Name[xh]=Armenian
Name[x-test]=xxArmenianxx
Name[zh_CN]=亚美尼亚语
Name[zh_HK]=亞美尼亞語
Name[zh_TW]=亞美尼亞語
[hz]
Name=Herero
Name[af]=Herero
Name[ar]=هيريرو
Name[as]=হেৰেৰো
Name[ast]=Herero
Name[be]=Хэрэра
Name[be@latin]=Herero
Name[bg]=Хереро
Name[bn]=হেরেরো
Name[bn_IN]=হেরেরো
Name[br]=Herero
Name[bs]=Herero
Name[ca]=Herero
Name[ca@valencia]=Herero
Name[cs]=Herero
Name[csb]=Herero
Name[cy]=Hausa
Name[da]=Herero
Name[de]=Herero
Name[el]=Herero
Name[en_GB]=Herero
Name[eo]=Herera
Name[es]=Herero
Name[et]=Herero
Name[eu]=Herero
Name[fa]=هررو
Name[fi]=Herero
Name[fr]=Herero
Name[fy]=Herero
Name[ga]=Heiréiróis
Name[gl]=Herero
Name[gu]=હિરેરો
Name[he]=הררו
Name[hi]=हेरेरो
Name[hne]=हेरेरो
Name[hr]=Herero
Name[hsb]=Hererosce
Name[hu]=Hereró
Name[ia]=Herero
Name[id]=Herero
Name[is]=Herero
Name[it]=Herero
Name[ja]=ヘレロ語
Name[kk]=Хереро
Name[km]=ហេរេអូ
Name[kn]=ಹೆರೆರೊ
Name[ko]=헤레로어
Name[ku]=Herero
Name[lb]=Herero
Name[lt]=Herero
Name[lv]=Hereru
Name[mai]=हेरेरो
Name[mk]=Хереро
Name[ml]=ഹിരീരോ
Name[mr]=हेरेरो
Name[ms]=Herero
Name[nb]=Herero
Name[nds]=Herero
Name[ne]=हेरेरो
Name[nl]=Herero
Name[nn]=Herero
Name[oc]=Ebrèu
Name[or]=Herero
Name[pa]=ਹੀਰੀਰੋ
Name[pl]=Herero
Name[ps]=هېرېرو
Name[pt]=Herero
Name[pt_BR]=Hereró
Name[ro]=Hereră
Name[ru]=Эреро
Name[se]=Hererogiella
Name[si]=හෙරිරෝ
Name[sk]=Hererčina
Name[sl]=herero
Name[sq]=Herero
Name[sr]=хереро
Name[sr@ijekavian]=хереро
Name[sr@ijekavianlatin]=herero
Name[sr@latin]=herero
Name[sv]=Herero
Name[ta]=ஹெரெரோ
Name[te]=హెరెరో
Name[tg]=Херэро
Name[th]=ภาษาเฮอเรโร
Name[tr]=Herero
Name[tt]=Херероча
Name[ug]=خېرېروچە
Name[uk]=Гереро
Name[uz]=Xerero
Name[uz@cyrillic]=Хереро
Name[vi]=Herero
Name[wa]=Herero
Name[xh]=Herero
Name[x-test]=xxHereroxx
Name[zh_CN]=赫雷罗语
Name[zh_HK]=Herero語
Name[zh_TW]=Herero語
[ia]
Name=Interlingua
Name[af]=Interlingua
Name[ar]=إنترلينغوا
Name[as]=ইন্টাৰ্লিঙ্গুৱা
Name[ast]=Interlingua
Name[be]=Інтэрлінгуа
Name[be@latin]=Interlingua
Name[bg]=Интерлингва
Name[bn]=ইন্টারলিঙ্গুয়া
Name[bn_IN]=ইন্টারলিঙ্গুয়া
Name[br]=Interlingua
Name[bs]=Interlingua
Name[ca]=Interlingua
Name[ca@valencia]=Interlingua
Name[cs]=Interlingua
Name[csb]=Interlingua
Name[cy]=Interlingua
Name[da]=Interlingua
Name[de]=Interlingua
Name[el]=Interlingua
Name[en_GB]=Interlingua
Name[eo]=Interlingvao
Name[es]=Interlingua
Name[et]=Interlingua
Name[eu]=Interlingua
Name[fa]=میان زبانی
Name[fi]=Interlingua
Name[fr]=Interlingua
Name[fy]=Interlingua
Name[ga]=Idirtheanga
Name[gl]=Interlingua
Name[gu]=ઈન્ટરલીગુઆ
Name[he]=אינטרלינגואָה
Name[hi]=अंतरभाषी
Name[hne]=इंटरलिंगुआ
Name[hr]=Interlingua
Name[hsb]=Interlingua
Name[hu]=Interlingua
Name[ia]=Interlingua
Name[id]=Interlingua
Name[is]=Interlingua
Name[it]=Interlingua
Name[ja]=インターリンガ
Name[kk]=Интерлингва
Name[km]=អ៊ីងតឺលីងគ័រ
Name[kn]=ಇಂಟರ್ಲಿಂಗ್ಯುವಾ
Name[ko]=국제어 (Interlingua)
Name[ku]=înterlîngua
Name[lb]=Interlingua
Name[lt]=Interlingua
Name[lv]=Interlingva
Name[mai]=अंतरभाषायी
Name[mk]=Интерлингва
Name[ml]=എല്ലാ ഭാഷകളിലും
Name[mr]=अंतरभाषी
Name[ms]=Interlingua
Name[nb]=Interlingua
Name[nds]=Interlingua
Name[ne]=इन्टरलिङ्गुवा
Name[nl]=Interlingua
Name[nn]=Interlingua
Name[oc]=Interlinguà
Name[or]=Interlingua
Name[pa]=ਇੰਟਰਲੀਗੂਆ
Name[pl]=Interlingua
Name[ps]=اېنټرلېنګوا
Name[pt]=Interlingua
Name[pt_BR]=Interlíngua
Name[ro]=Interlingua
Name[ru]=Интерлингва
Name[se]=Interlingua
Name[sk]=Interlingua
Name[sl]=interlingua
Name[sq]=Interlingua
Name[sr]=интерлингва
Name[sr@ijekavian]=интерлингва
Name[sr@ijekavianlatin]=interlingva
Name[sr@latin]=interlingva
Name[sv]=Interlingua
Name[ta]=இன்டெர்லிங்குவா
Name[te]=ఇంటర్ లింగువా
Name[tg]=Забони миёнрав
Name[th]=ภาษานานาชาติ
Name[tr]=Interlingua
Name[tt]=Интерлингва
Name[ug]=خەلقئارا تىل
Name[uk]=Інтерлінгва
Name[uz]=Interlingua
Name[uz@cyrillic]=Интерлингуа
Name[vi]=Interlingua
Name[wa]=Interlingua (noû latén)
Name[xh]=Interlingua
Name[x-test]=xxInterlinguaxx
Name[zh_CN]=拉丁国际语
Name[zh_HK]=科技共通語
Name[zh_TW]=科技共通語
[id]
Name=Indonesian
Name[af]=Indonesië
Name[ar]=إندونيسية
Name[as]=ইন্ডোনেচীয়
Name[ast]=Indonesiu
Name[be]=Інданэзійская
Name[be@latin]=Indanezijskaja
Name[bg]=Индонезийски
Name[bn]=ইন্দোনেশীয়
Name[bn_IN]=ইন্দোনেশিয়ান
Name[br]=Indoneseg
Name[bs]=Indonezijski
Name[ca]=Indonesi
Name[ca@valencia]=Indonesi
Name[cs]=Indonéský
Name[csb]=Indonezëjsczi
Name[cy]=Indonesieg
Name[da]=Indonesisk
Name[de]=Indonesisch
Name[el]=Ινδονησιακά
Name[en_GB]=Indonesian
Name[eo]=Indonezia
Name[es]=Indonesio
Name[et]=Indoneesia
Name[eu]=Indonesiera
Name[fa]=اندونزیایی
Name[fi]=Indonesia
Name[fr]=Indonésien
Name[fy]=Yndonesysk
Name[ga]=Indinéisis
Name[gl]=Indonesio
Name[gu]=ઈન્ડોનેશિયન
Name[he]=אינדונזית
Name[hi]=इंडोनेशियाई
Name[hne]=इंडोनेसियाई
Name[hr]=Indonezijski
Name[hsb]=Indonezisce
Name[hu]=Indonéz
Name[ia]=Indonesiano
Name[id]=Indonesia
Name[is]=Indónesíska
Name[it]=Indonesiano
Name[ja]=インドネシア語
Name[kk]=Индонезияша
Name[km]=ឥណ្ឌូនេស៊ី
Name[kn]=ಇಂಡೋನೇಷಿಯನ್
Name[ko]=인도네시아어
Name[ku]=Endonezî
Name[lb]=Indonesesch
Name[lt]=Indoneziečių
Name[lv]=Indonēziešu
Name[mai]=इंडोनेशियन
Name[mk]=Индонезиски
Name[ml]=ഇന്തോനേഷ്യന്‍
Name[mr]=इंडोनेशियाई
Name[ms]=Indonesia
Name[nb]=Indonesisk
Name[nds]=Indoneesch
Name[ne]=इन्डोनेसियाली
Name[nl]=Indonesisch
Name[nn]=Indonesisk
Name[oc]=Indonesian
Name[or]=Indonesian
Name[pa]=ਇੰਡੋਨੇਸ਼ੀਆਈ
Name[pl]=Indonezyjski
Name[ps]=انډونيشيايي
Name[pt]=Indonésio
Name[pt_BR]=Indonésio
Name[ro]=Indoneziană
Name[ru]=Индонезийский
Name[se]=Indonesiagiella
Name[si]=ඉන්දුනීසියානු
Name[sk]=Indonézština
Name[sl]=indonezijsko
Name[sq]=Indoneziane
Name[sr]=индонезијски
Name[sr@ijekavian]=индонезијски
Name[sr@ijekavianlatin]=indonezijski
Name[sr@latin]=indonezijski
Name[sv]=Indonesiska
Name[ta]=இந்தோனீசியன்
Name[te]=ఇన్డొనెషియన్
Name[tg]=Индонезӣ
Name[th]=ภาษาอินโดนีเซีย
Name[tr]=İndonezya Dili
Name[tt]=Индонезия
Name[ug]=ھىندونېزىيەچە
Name[uk]=Індонезійська
Name[uz]=Indonezcha
Name[uz@cyrillic]=Индонезча
Name[vi]=Indonesia
Name[wa]=Indonezyin
Name[xh]=Indonesian
Name[x-test]=xxIndonesianxx
Name[zh_CN]=印度尼西亚语
Name[zh_HK]=印尼語
Name[zh_TW]=印尼語
[ie]
Name=Interlingue
Name[af]=Interlingue
Name[ar]=إنترلينغوي
Name[as]=ইন্টাৰলিঙ্গ
Name[ast]=Interlingue
Name[be]=Інтэрлінг
Name[be@latin]=Interlingue
Name[bg]=Оксидентал
Name[bn]=ইন্টারলিং
Name[bn_IN]=ইন্টারলিঙ্গুই
Name[br]=Interlingeg
Name[bs]=Interlingva
Name[ca]=Interlingue
Name[ca@valencia]=Interlingue
Name[cs]=Interlingue
Name[csb]=Interlingue
Name[cy]=Interlingue
Name[da]=Interlingue
Name[de]=Interlingue
Name[el]=Interlingue
Name[en_GB]=Interlingue
Name[eo]=Interlingveo
Name[es]=Idioma occidental
Name[et]=Interlingue
Name[eu]=Interlingue
Name[fa]=میان زبانی
Name[fi]=Interlingue
Name[fr]=Interlingue
Name[fy]=Interlingua
Name[ga]=Idirtheanga iartharach
Name[gl]=Interlingua
Name[gu]=ઈન્ટરલીંગ
Name[he]=אינטרלינגואֶה
Name[hi]=इंटरलिंग
Name[hne]=इंटरलिंग
Name[hr]=Interlingue
Name[hsb]=Interlingue
Name[hu]=Interlingue
Name[ia]=Interlingue
Name[id]=Interlingue
Name[is]=Interlingue
Name[it]=Interlingue
Name[ja]=インターリング
Name[kk]=Интерлигве
Name[km]=អ៊ីងតឺលីង
Name[kn]=ಇಂಟರ್ಲಿಂಗ್ಯುವೇ
Name[ko]=국제어 (Interlingue)
Name[ku]=Înterlîngue
Name[lb]=Interlingua
Name[lt]=Interlingue
Name[lv]=Interlingve
Name[mai]=इंटरलिंग
Name[mk]=Интерлингва
Name[ml]=എല്ലാ ഭാഷയിലും
Name[mr]=अंतरभाषी
Name[ms]=Interlingue
Name[nb]=Interlingue
Name[nds]=Interlingue
Name[ne]=इन्टरलिङ्गुवा
Name[nl]=Interlingue
Name[nn]=Interlingue
Name[or]=Interlingue
Name[pa]=ਇੰਟਰਈਨੂਗੂਈ
Name[pl]=Interlingue
Name[ps]=اېنټرلېنګ
Name[pt]=Interlingue
Name[pt_BR]=Interlingue
Name[ro]=Interlingue
Name[ru]=Окциденталь
Name[se]=Interlingue
Name[sk]=Interlingue
Name[sl]=interlingue
Name[sq]=Interlingue
Name[sr]=интерлингве
Name[sr@ijekavian]=интерлингве
Name[sr@ijekavianlatin]=interlingve
Name[sr@latin]=interlingve
Name[sv]=Interlingua
Name[ta]=இன்டெர்லிங்குவே
Name[te]=ఇంటర్ లింగె
Name[tg]=Забони миёнрав
Name[th]=ภาษาอินเทอร์ลิงก์
Name[tr]=Interlingue
Name[tt]=Интерлингва
Name[ug]=خەلقئارا تىل
Name[uk]=Окциденталь
Name[uz]=Interlingue
Name[uz@cyrillic]=Интерлингуе
Name[vi]=Interlingue
Name[wa]=Interlingue
Name[xh]=Interlingue
Name[x-test]=xxInterlinguexx
Name[zh_CN]=国际语
Name[zh_HK]=Interlingue語
Name[zh_TW]=Interlingue語
[ik]
Name=Inupiaq
Name[af]=Inupiaq
Name[ar]=إنوبياك
Name[as]=ইনুপিয়াক
Name[ast]=Inupiaq
Name[be]=Інупіцкая
Name[be@latin]=Inupiaq
Name[bg]=Инупиак
Name[bn]=ইনুপিয়াক
Name[bn_IN]=ইনুপিয়াক
Name[br]=Inupiak
Name[bs]=Inupijski
Name[ca]=Inupiaq
Name[ca@valencia]=Inupiaq
Name[cs]=Inupiaq
Name[csb]=Inupiaq
Name[cy]=Inupiaq
Name[da]=Inupiaq
Name[de]=Inupiaq
Name[el]=Inupiaq
Name[en_GB]=Inupiaq
Name[eo]=Inupiaka
Name[es]=Inupiaq
Name[et]=Inupiaq
Name[eu]=Inupiaq
Name[fa]=اینوپیاک
Name[fi]=Inupiatun
Name[fr]=Inupiaq
Name[fy]=Inupiaq
Name[ga]=Iniúipiacais
Name[gl]=Inupiaq
Name[gu]=ઇનુપીઆક
Name[he]=אינופיאק
Name[hi]=इनुपिआक
Name[hne]=इनुपिआक
Name[hr]=Inupiaq
Name[hsb]=Inupiaksce
Name[hu]=Inupiak
Name[ia]=Inupiaq
Name[id]=Inupiaq
Name[is]=Inupiaq
Name[it]=Inupiak
Name[ja]=イヌピアック語
Name[kk]=Инупиакша
Name[km]=អ៊ីនូពីយ៉ាក
Name[kn]=ಇನೂಪಿಯಾಕ್
Name[ko]=이누피아크어
Name[ku]=Înupiyaq
Name[lb]=Inupiaq
Name[lt]=Inupiaq
Name[lv]=Inupiaku
Name[mai]=इनुपिआक
Name[mk]=Инупиак
Name[ml]=ഇനൂപിയാക്
Name[mr]=इनुपिआक
Name[ms]=Inupiaq
Name[nb]=Inupiaq
Name[nds]=Inupiak
Name[ne]=इनुपियाक
Name[nl]=Inupiaq
Name[nn]=Inupiak
Name[or]=Inupiaq
Name[pa]=ਇਨਪੀਕਾਉ
Name[pl]=Inupiaq
Name[ps]=اېنوپيک
Name[pt]=Inupiaq
Name[pt_BR]=Inupiaq
Name[ro]=Inupiacă
Name[ru]=Инупиак
Name[se]=Inupiaqgiella
Name[si]=ඉනුපියැක්
Name[sk]=Inupiakčina
Name[sl]=inupiaq
Name[sq]=Inupiaq
Name[sr]=инупијак
Name[sr@ijekavian]=инупијак
Name[sr@ijekavianlatin]=inupijak
Name[sr@latin]=inupijak
Name[sv]=Inupiaq
Name[ta]=இனுபியாக்
Name[te]=ఇనుపియాక్
Name[tg]=Инупиакӣ
Name[th]=ภาษาอินุพิอัค
Name[tr]=Inupiaq
Name[tt]=Униракча
Name[ug]=ئىنۇپىكچە
Name[uk]=Інупіак
Name[uz]=Inupiak
Name[uz@cyrillic]=Инупиак
Name[vi]=Inupiaq
Name[wa]=Inyupiak
Name[xh]=Inupiaq
Name[x-test]=xxInupiaqxx
Name[zh_CN]=因纽皮特语
Name[zh_HK]=Inupiaq語
Name[zh_TW]=Inupiaq語
[io]
Name=Ido
Name[af]=Ido
Name[ar]=إيدو
Name[as]=ইদো
Name[ast]=Ido
Name[be]=Ідо
Name[be@latin]=Ido
Name[bg]=Идо
Name[bn]=ইডো
Name[bn_IN]=আইডো
Name[br]=Ido
Name[bs]=Ido
Name[ca]=Ido
Name[ca@valencia]=Ido
Name[cs]=Ido
Name[csb]=Ido
Name[cy]=Ido
Name[da]=Ido
Name[de]=Ido
Name[el]=Ido
Name[en_GB]=Ido
Name[eo]=Ido
Name[es]=Ido
Name[et]=Ido
Name[eu]=Ido
Name[fa]=ایدو
Name[fi]=Ido
Name[fr]=Ido
Name[fy]=Ido
Name[ga]=Ido
Name[gl]=Ido
Name[gu]=ઈડો
Name[he]=אידו
Name[hi]=इदो
Name[hne]=इदो
Name[hr]=Ido
Name[hsb]=Idosce
Name[hu]=Ido
Name[ia]=Ido
Name[id]=Ido
Name[is]=Ido
Name[it]=Ido
Name[ja]=イド語
Name[kk]=Идо
Name[km]=អ៊ីឌូ
Name[kn]=ಇಡೋ
Name[ko]=이도어
Name[ku]=Îdo
Name[lb]=Ido
Name[lt]=Ido
Name[lv]=Ido
Name[mai]=इदो
Name[mk]=Идо
Name[ml]=എയ്ഡോ
Name[mr]=इदो
Name[ms]=Ido
Name[nb]=Ido
Name[nds]=Ido
Name[ne]=इडो
Name[nl]=Ido
Name[nn]=Ido
Name[oc]=Ido
Name[or]=Ido
Name[pa]=ਆਈਡੋ
Name[pl]=Ido
Name[ps]=اېډو
Name[pt]=Ido
Name[pt_BR]=Ido
Name[ro]=Ido
Name[ru]=Идо
Name[se]=Idogiella
Name[si]=ඉඩෝ
Name[sk]=Ido
Name[sl]=ido
Name[sq]=Ido
Name[sr]=идо
Name[sr@ijekavian]=идо
Name[sr@ijekavianlatin]=ido
Name[sr@latin]=ido
Name[sv]=Ido
Name[ta]=ஈடோ
Name[te]=ఇడొ
Name[tg]=Идо
Name[th]=ภาษาอิโด
Name[tr]=Ido
Name[tt]=Идо
Name[ug]=ئىدوچە
Name[uk]=Ідо
Name[uz]=Ido
Name[uz@cyrillic]=Идо
Name[vi]=Ido
Name[wa]=Ido
Name[xh]=Ido
Name[x-test]=xxIdoxx
Name[zh_CN]=伊多语
Name[zh_HK]=伊多語
Name[zh_TW]=伊多語
[is]
Name=Icelandic
Name[af]=Yslandies
Name[ar]=آيسلندية
Name[as]=আইচ্‌লেন্ডিক
Name[ast]=Islandés
Name[be]=Ісландская
Name[be@latin]=Iślandzkaja
Name[bg]=Исландски
Name[bn]=আইসল্যান্ডিক
Name[bn_IN]=আইসল্যান্ডিক
Name[br]=Islandeg
Name[bs]=Islandski
Name[ca]=Islandès
Name[ca@valencia]=Islandés
Name[cs]=Islandský
Name[csb]=Islandzczi
Name[cy]=Islandeg
Name[da]=Islandsk
Name[de]=Isländisch
Name[el]=Ισλανδικά
Name[en_GB]=Icelandic
Name[eo]=Islanda
Name[es]=Islandés
Name[et]=Islandi
Name[eu]=Islandiera
Name[fa]=ایسلندی
Name[fi]=Islanti
Name[fr]=Islandais
Name[fy]=Iislânsk
Name[ga]=Íoslainnis
Name[gl]=Islandés
Name[gu]=આઈલેન્ડિક
Name[he]=איסלנדית
Name[hi]=आइसलैंडिक
Name[hne]=आइसलैंडिक
Name[hr]=Islandski
Name[hsb]=Islandsce
Name[hu]=Izlandi
Name[ia]=Islandese
Name[id]=Icelandic
Name[is]=Íslenska
Name[it]=Islandese
Name[ja]=アイスランド語
Name[kk]=Исландша
Name[km]=អ៊ីស្លង់
Name[kn]=ಐಸ್ಲ್ಯಾಂಡಿಕ್
Name[ko]=아이슬란드어
Name[ku]=Îzlandî
Name[lb]=Islännesch
Name[lt]=Islandų
Name[lv]=Islandiešu
Name[mai]=आइसलैंडिक
Name[mk]=Исландски
Name[ml]=ഐസ്ലാന്‍ഡിക്
Name[mr]=आयलॅंडिक
Name[ms]=Icelandic
Name[nb]=Islandsk
Name[nds]=Islannsch
Name[ne]=आइसल्यान्डिक
Name[nl]=IJslands
Name[nn]=Islandsk
Name[oc]=Islandés
Name[or]=Icelandic
Name[pa]=ਆਇਸਲੈਂਡ
Name[pl]=Islandzki
Name[ps]=اېسلېنډي
Name[pt]=Islandês
Name[pt_BR]=Islandês
Name[ro]=Islandeză
Name[ru]=Исландский
Name[se]=Islánddagiella
Name[si]=අයිස්ලන්තානු
Name[sk]=Islandčina
Name[sl]=islandsko
Name[sq]=Islandeze
Name[sr]=исландски
Name[sr@ijekavian]=исландски
Name[sr@ijekavianlatin]=islandski
Name[sr@latin]=islandski
Name[sv]=Isländska
Name[ta]=ஐஸ்லாந்தியம்
Name[te]=ఐస్ లేండిక్
Name[tg]=Исландӣ
Name[th]=ภาษาไอซ์แลนด์
Name[tr]=İzlanda Dili
Name[tt]=Исланд
Name[ug]=ئىسلاندچە
Name[uk]=Ісландська
Name[uz]=Islandcha
Name[uz@cyrillic]=Исландча
Name[vi]=Iceland
Name[wa]=Izlandès
Name[xh]=Icelandic
Name[x-test]=xxIcelandicxx
Name[zh_CN]=冰岛语
Name[zh_HK]=冰島語
Name[zh_TW]=冰島語
[it]
Name=Italian
Name[af]=Italiaans
Name[ar]=إيطالية
Name[as]=ইটালীয়
Name[ast]=Italianu
Name[be]=Італьянская
Name[be@latin]=Italjanskaja
Name[bg]=Италиански
Name[bn]=ইতালীয়
Name[bn_IN]=ইটালিয়ান
Name[br]=Italianeg
Name[bs]=Italijanski
Name[ca]=Italià
Name[ca@valencia]=Italià
Name[cs]=Italský
Name[csb]=Italsczi
Name[cy]=Eidaleg
Name[da]=Italiensk
Name[de]=Italienisch
Name[el]=Ιταλικά
Name[en_GB]=Italian
Name[eo]=Itala
Name[es]=Italiano
Name[et]=Itaalia
Name[eu]=Italiera
Name[fa]=ایتالیایی
Name[fi]=Italia
Name[fr]=Italien
Name[fy]=Italiaansk
Name[ga]=Iodáilis
Name[gl]=Italiano
Name[gu]=ઈટાલીયન
Name[he]=איטלקית
Name[hi]=इतालवी
Name[hne]=इतालवी
Name[hr]=Talijanski
Name[hsb]=Italsce
Name[hu]=Olasz
Name[hy]=Իտալերեն
Name[ia]=Italiano
Name[id]=Italia
Name[is]=Ítalska
Name[it]=Italiano
Name[ja]=イタリア語
Name[kk]=Итальянша
Name[km]=អ៊ីតាលី
Name[kn]=ಇಟಾಲಿಯನ್
Name[ko]=이탈리아어
Name[ku]=Îtalî
Name[lb]=Italienesch
Name[lt]=Italų
Name[lv]=Itāļu
Name[mai]=इतालवी
Name[mk]=Италијански
Name[ml]=ഇറ്റാലിയന്‍
Name[mr]=इतालवी
Name[ms]=Italia
Name[nb]=Italiensk
Name[nds]=Italieensch
Name[ne]=इटालियन
Name[nl]=Italiaans
Name[nn]=Italiensk
Name[oc]=Italian
Name[or]=Italian
Name[pa]=ਇਤਾਲਵੀ
Name[pl]=Włoski
Name[ps]=اېټالوي
Name[pt]=Italiano
Name[pt_BR]=Italiano
Name[ro]=Italiană
Name[ru]=Итальянский
Name[se]=Itáliagiella
Name[si]=ඉතාලි
Name[sk]=Taliančina
Name[sl]=italijansko
Name[sq]=Italiane
Name[sr]=италијански
Name[sr@ijekavian]=италијански
Name[sr@ijekavianlatin]=italijanski
Name[sr@latin]=italijanski
Name[sv]=Italienska
Name[ta]=இத்தாலியன்
Name[te]=ఇటాలియన్
Name[tg]=Итолиёӣ
Name[th]=ภาษาอิตาเลียน
Name[tr]=İtalyanca
Name[tt]=Итальян
Name[ug]=ئىتاليانچە
Name[uk]=Італійська
Name[uz]=Italyancha
Name[uz@cyrillic]=Италянча
Name[vi]=Ý
Name[wa]=Itålyin
Name[xh]=isitaliyane
Name[x-test]=xxItalianxx
Name[zh_CN]=意大利语
Name[zh_HK]=意大利語
Name[zh_TW]=義大利語
[iu]
Name=Inuktitut
Name[af]=Inuktitut
Name[ar]=إنكتيتوت
Name[as]=ইনুক্টিটুট
Name[ast]=Inuktitut
Name[be]=Інуктытут
Name[be@latin]=Inuktitut
Name[bg]=Инуктитут
Name[bn]=ইনাক্টিটুট
Name[bn_IN]=ইনুকটিটুট
Name[br]=Inuktitut
Name[bs]=Inuktitut
Name[ca]=Inuktitut
Name[ca@valencia]=Inuktitut
Name[cs]=Inuktitut
Name[csb]=Inuktitut
Name[cy]=Inuktitut
Name[da]=Inuktitut
Name[de]=Inuktitut
Name[el]=Inuktitut
Name[en_GB]=Inuktitut
Name[eo]=Inuktituta
Name[es]=Inuktitut
Name[et]=Inuktitut
Name[eu]=Inuktitut
Name[fa]=اینوکتیتوت
Name[fi]=Inuktitut
Name[fr]=Inuktitut
Name[fy]=Inuktitut
Name[ga]=Ionúitis
Name[gl]=Inuktitut
Name[gu]=ઈનુક્ટિટુટ
Name[he]=אינוקטיטוט
Name[hi]=इनुक्तितुत
Name[hne]=इनुक्तितुत
Name[hr]=Inuktitut
Name[hsb]=Inuktitutsce
Name[hu]=Inuktitut
Name[ia]=Inuktitut
Name[id]=Inuktitut
Name[is]=Inuktitut
Name[it]=Inuktitut
Name[ja]=イヌイット語
Name[kk]=Инуктиут
Name[km]=អ៊ីនូកទីទូត
Name[kn]=ಇನುಕ್ಟಿಟುಟ್
Name[ko]=이눅티툿어
Name[ku]=Înûktîtut
Name[lb]=Inuktitut
Name[lt]=Inuktitut
Name[lv]=Inuītu
Name[mai]=इनुक्तितुत
Name[mk]=Инуктитут
Name[ml]=ഇന്‍കുട്ടിടുട്ട്
Name[mr]=इनुक्तितुत
Name[ms]=Inuktitut
Name[nb]=Inuktitut
Name[nds]=Inuktitut
Name[ne]=इनक्टिटुट
Name[nl]=Inuktitut
Name[nn]=Inuittisk
Name[oc]=Inuktitut
Name[or]=Inuktitut
Name[pa]=ਇਨੂਕਟੀਟੂਟ
Name[pl]=Inuktitut
Name[pt]=Inuktitut
Name[pt_BR]=Inuktitut
Name[ro]=Inuktitut
Name[ru]=Инуктитут
Name[se]=Inuhkagiella
Name[si]=ඉනුක්ටිටුට්
Name[sk]=Inuktitut
Name[sl]=inuktitut
Name[sq]=Inuktitut
Name[sr]=инуктитут
Name[sr@ijekavian]=инуктитут
Name[sr@ijekavianlatin]=inuktitut
Name[sr@latin]=inuktitut
Name[sv]=Inuktitut
Name[ta]=இனுக்டிடுட்
Name[te]=ఇనుక్తిటుట్
Name[tg]=Инуктитут
Name[th]=ภาษาอินุคทิทุท
Name[tr]=Inuktitut
Name[tt]=Инуктитутча
Name[ug]=ئىنۇكتىتۇتچە
Name[uk]=Інуктітут
Name[uz]=Inuktitut
Name[uz@cyrillic]=Инуктитут
Name[vi]=Inuktitut
Name[wa]=Inuktitut
Name[xh]=Inuktitut
Name[x-test]=xxInuktitutxx
Name[zh_CN]=因纽特语
Name[zh_HK]=Inuktitut語
Name[zh_TW]=因紐特語
[ja]
Name=Japanese
Name[af]=Japanees
Name[ar]=يابانية
Name[as]=জাপানী
Name[ast]=Xaponés
Name[be]=Японская
Name[be@latin]=Japonskaja
Name[bg]=Японски
Name[bn]=জাপানী
Name[bn_IN]=জাপানি
Name[br]=Japaneg
Name[bs]=Japanski
Name[ca]=Japonès
Name[ca@valencia]=Japonés
Name[cs]=Japonský
Name[csb]=Japòńsczi
Name[cy]=Japaneg
Name[da]=Japansk
Name[de]=Japanisch
Name[el]=Ιαπωνικά
Name[en_GB]=Japanese
Name[eo]=Japana
Name[es]=Japonés
Name[et]=Jaapani
Name[eu]=Japoniera
Name[fa]=ژاپنی
Name[fi]=Japani
Name[fr]=Japonais
Name[fy]=Japansk
Name[ga]=Seapáinis
Name[gl]=Xaponés
Name[gu]=જાપાનીઝ
Name[he]=יפנית
Name[hi]=जापानी
Name[hne]=जापानी
Name[hr]=Japanski
Name[hsb]=Japansce
Name[hu]=Japán
Name[ia]=Japonese
Name[id]=Jepang
Name[is]=Japanska
Name[it]=Giapponese
Name[ja]=日本語
Name[kk]=Жапонша
Name[km]=ជប៉ុន
Name[kn]=ಜಪಾನೀ
Name[ko]=일본어
Name[ku]=Japanese
Name[lb]=Japanesch
Name[lt]=Japonų
Name[lv]=Japāņu
Name[mai]=जापानी
Name[mk]=Јапонски
Name[ml]=ജാപ്പനീസ്
Name[mr]=जपानी
Name[ms]=Jepun
Name[nb]=Japansk
Name[nds]=Japaansch
Name[ne]=जापानी
Name[nl]=Japans
Name[nn]=Japansk
Name[oc]=Japonés
Name[or]=ଜାପାନୀ
Name[pa]=ਜਾਪਾਨੀ
Name[pl]=Japoński
Name[ps]=جاپاني
Name[pt]=Japonês
Name[pt_BR]=Japonês
Name[ro]=Japoneză
Name[ru]=Японский
Name[se]=Jáhpangiella
Name[si]=ජපන්
Name[sk]=Japončina
Name[sl]=japonsko
Name[sq]=Japoneze
Name[sr]=јапански
Name[sr@ijekavian]=јапански
Name[sr@ijekavianlatin]=japanski
Name[sr@latin]=japanski
Name[sv]=Japanska
Name[ta]=ஜப்பானியம்
Name[te]=జపనీస్
Name[tg]=Ҷопонӣ
Name[th]=ภาษาญี่ปุ่น
Name[tr]=Japonca
Name[tt]=Япон
Name[ug]=ياپونچە
Name[uk]=Японська
Name[uz]=Yaponcha
Name[uz@cyrillic]=Японча
Name[vi]=Nhật
Name[wa]=Djaponès
Name[xh]=Isijapani
Name[x-test]=xxJapanesexx
Name[zh_CN]=日语
Name[zh_HK]=日語
Name[zh_TW]=日語
[jv]
Name=Javanese
Name[af]=Javanees
Name[ar]=جاوية
Name[as]=জাভানী
Name[ast]=Xavanés
Name[be]=Яванская
Name[be@latin]=Javanskaja
Name[bg]=Явайски
Name[bn]=জাভানিস
Name[bn_IN]=জাভানিস
Name[br]=Javaneg
Name[bs]=Javanski
Name[ca]=Javanès
Name[ca@valencia]=Javanés
Name[cs]=Jávský
Name[csb]=Jawańsczi
Name[cy]=Javaneg
Name[da]=Javansk
Name[de]=Javanisch
Name[el]=Javanese
Name[en_GB]=Javanese
Name[eo]=Java
Name[es]=Javanés
Name[et]=Jaava
Name[eu]=Javera
Name[fa]=جاوانیز
Name[fi]=Jaava
Name[fr]=Javanais
Name[fy]=Javaansk
Name[ga]=Iávais
Name[gl]=Xavanés
Name[gu]=જાવાનીઝ
Name[he]=ג'אווה
Name[hi]=जावानी
Name[hne]=जावानी
Name[hr]=Javanski
Name[hsb]=Jawanisce
Name[hu]=Jávai
Name[ia]=Javanese
Name[id]=Jawa
Name[is]=Javanese
Name[it]=Giavanese
Name[ja]=ジャワ語
Name[kk]=Ява
Name[km]=យ៉ាវា
Name[kn]=ಜಾವನೀಸ್
Name[ko]=자바어
Name[ku]=Javayî
Name[lb]=Javanesesch
Name[lt]=Javiečių
Name[lv]=Javiešu
Name[mai]=जावानी
Name[mk]=Јавански
Name[ml]=ജാവനീസ്
Name[mr]=जावानी
Name[ms]=Jawa
Name[nb]=Javanesisk
Name[nds]=Javaneesch
Name[ne]=जाभानिज
Name[nl]=Javaans
Name[nn]=Javanesisk
Name[oc]=Javanés
Name[or]=Javanese
Name[pa]=ਜਾਵਾਨੀਆਈ
Name[pl]=Jawański
Name[ps]=جاواني
Name[pt]=Javanês
Name[pt_BR]=Javanês
Name[ro]=Iavaneză
Name[ru]=Яванский
Name[se]=Jávagiella
Name[si]=ජාවා
Name[sk]=Jávčina
Name[sl]=javansko
Name[sq]=Javaneze
Name[sr]=јавански
Name[sr@ijekavian]=јавански
Name[sr@ijekavianlatin]=javanski
Name[sr@latin]=javanski
Name[sv]=Javanesiska
Name[ta]=ஜாவானீயம்
Name[te]=జావానీస్
Name[tg]=Ёвонӣ
Name[th]=ภาษาชวา
Name[tr]=Javanese
Name[tt]=Яван язмасы
Name[ug]=ياۋاچە
Name[uk]=Яванська
Name[uz]=Yaavanez
Name[uz@cyrillic]=Яаванез
Name[vi]=Java
Name[wa]=Djavanès
Name[xh]=Javanese
Name[x-test]=xxJavanesexx
Name[zh_CN]=爪哇语
Name[zh_HK]=爪哇語
Name[zh_TW]=爪哇語
[ka]
Name=Georgian
Name[af]=Georgiën
Name[ar]=جورجية
Name[as]=জৰ্জিয়ান
Name[ast]=Xorxanu
Name[be]=Грузінская
Name[be@latin]=Hruzinskaja
Name[bg]=Грузински
Name[bn]=জর্জিয়ান
Name[bn_IN]=জর্জিয়ান
Name[br]=Jeorjieg
Name[bs]=Gruzijski
Name[ca]=Georgià
Name[ca@valencia]=Georgià
Name[cs]=Gruzínský
Name[csb]=Grëzóńsczi
Name[cy]=Georgeg
Name[da]=Georgisk
Name[de]=Georgisch
Name[el]=Γεωργιανά
Name[en_GB]=Georgian
Name[eo]=Kartvela
Name[es]=Georgiano
Name[et]=Gruusia
Name[eu]=Georgiera
Name[fa]=گرجی
Name[fi]=Georgia
Name[fr]=Géorgien
Name[fy]=Georgysk
Name[ga]=Seoirsis
Name[gl]=Xeorxiano
Name[gu]=જ્યોર્જિયન
Name[he]=גאורגית
Name[hi]=ज्यॉर्जियाई
Name[hne]=ज्यार्जियाई
Name[hr]=Gruzijski
Name[hsb]=Georgisce
Name[hu]=Grúz
Name[ia]=Georgian
Name[id]=Georgia
Name[is]=Georgíska
Name[it]=Georgiano
Name[ja]=グルジア語
Name[kk]=Гүржіше
Name[km]=ហ្សកហ្ស៊ី
Name[kn]=ಜೋರ್ಜಿಯನ್
Name[ko]=조지아어
Name[ku]=Gurcî
Name[lb]=Georgesch
Name[lt]=Gruzinų
Name[lv]=Gruzīnu
Name[mai]=ज्यॉर्जियाइ
Name[mk]=Грузиски
Name[ml]=ജോര്‍ജ്ജിയന്‍
Name[mr]=जॉर्जियन
Name[ms]=Georgia
Name[nb]=Georgisk
Name[nds]=Georgsch
Name[ne]=जर्जियाली
Name[nl]=Georgisch
Name[nn]=Georgisk
Name[oc]=Georgian
Name[or]=Georgian
Name[pa]=ਜਾਰਜੀਆਈ
Name[pl]=Gruziński
Name[ps]=ګورجين
Name[pt]=Geórgio
Name[pt_BR]=Georgiano
Name[ro]=Georgiană
Name[ru]=Грузинский
Name[se]=Grusiagiella
Name[si]=ජෝර්ජියානු
Name[sk]=Gruzínčina
Name[sl]=gruzijsko
Name[sq]=Gjorgjiane
Name[sr]=грузијски
Name[sr@ijekavian]=грузијски
Name[sr@ijekavianlatin]=gruzijski
Name[sr@latin]=gruzijski
Name[sv]=Georgiska
Name[ta]=ஜார்ஜியன்
Name[te]=జార్జియన్
Name[tg]=Гурҷӣ
Name[th]=ภาษาจอร์เจีย
Name[tr]=Gürcüce
Name[tt]=Грузин
Name[ug]=گىرۇزىنچە
Name[uk]=Грузинська
Name[uz]=Gruzincha
Name[uz@cyrillic]=Грузинча
Name[vi]=Georgia
Name[wa]=Djeyordjyin
Name[xh]=Georgian
Name[x-test]=xxGeorgianxx
Name[zh_CN]=乔治亚语
Name[zh_HK]=喬治亞語
Name[zh_TW]=喬治亞語
[ki]
Name=Kikuyu
Name[af]=Kikuyu
Name[ar]=كيكويو
Name[as]=কিকুয়ু
Name[ast]=Kikuyu
Name[be]=Кікую
Name[be@latin]=Kikuyu
Name[bg]=Кикю
Name[bn]=কিকুয়ু
Name[bn_IN]=কিকুয়ু
Name[br]=Kikuyu
Name[bs]=Kikuju
Name[ca]=Kikuyu
Name[ca@valencia]=Kikuyu
Name[cs]=Kikuyu
Name[csb]=Kikuyu
Name[cy]=Kikuyu
Name[da]=Kikuyu
Name[de]=Kikuyu
Name[el]=Kikuyu
Name[en_GB]=Kikuyu
Name[eo]=Gikuja
Name[es]=Kikuyu
Name[et]=Kikuju
Name[eu]=Kikuyu
Name[fa]=کیکویا
Name[fi]=Kikuju
Name[fr]=Kikuyu
Name[fy]=Kikuyu
Name[ga]=Ciocúis
Name[gl]=Kikuyu
Name[gu]=કિકુયુ
Name[he]=קיקויו
Name[hi]=किकूयू
Name[hne]=किकूयू
Name[hr]=Kikuyu
Name[hsb]=Kikuyu
Name[hu]=Kikuju
Name[ia]=Kikuyu
Name[id]=Kikuyu
Name[is]=Kikuyu
Name[it]=Kikuyu
Name[ja]=キクユ語
Name[kk]=Кикую
Name[km]=គីគូយូ
Name[kn]=ಕಿಯುಕು
Name[ko]=키쿠유어
Name[ku]=Kîkuyu
Name[lb]=Kikuyu-Sprooch
Name[lt]=Kikuyu
Name[lv]=Kikuju
Name[mai]=किकूयू
Name[mk]=Кикују
Name[ml]=കികുയു
Name[mr]=किकूयू
Name[ms]=Kikuyu
Name[nb]=Kikuyu
Name[nds]=Kikuyu
Name[ne]=किकुयु
Name[nl]=Kikuyu
Name[nn]=Kikuyu
Name[oc]=Kikuyu
Name[or]=Kikuyu
Name[pa]=ਕਿਕੂਯੂ
Name[pl]=Kikuyu
Name[ps]=کېکويو
Name[pt]=Kikuyu
Name[pt_BR]=Kikuyu
Name[ro]=Kikuyu
Name[ru]=Кикуйю
Name[se]=Kikujugiella
Name[si]=කිකුයු
Name[sk]=Kikuju
Name[sl]=kikuyu
Name[sq]=Kikuyu
Name[sr]=кикују
Name[sr@ijekavian]=кикују
Name[sr@ijekavianlatin]=kikuju
Name[sr@latin]=kikuju
Name[sv]=Kikuyu
Name[ta]=கிகுயு
Name[te]=కికుయు
Name[tg]=Кикуягӣ
Name[th]=ภาษาคิคุยุ
Name[tr]=Kikuyu
Name[tt]=Кекуча
Name[ug]=كىكۇيۇچە
Name[uk]=Кікуйю
Name[uz]=Kikuyu
Name[uz@cyrillic]=Кикуйу
Name[vi]=Kikuyu
Name[wa]=Kikuyu
Name[xh]=Kikuyu
Name[x-test]=xxKikuyuxx
Name[zh_CN]=基库尤语
Name[zh_HK]=吉庫猶語
Name[zh_TW]=吉庫猶語
[kk]
Name=Kazakh
Name[af]=Kazakh
Name[ar]=قزقية
Name[as]=কাজাখ
Name[ast]=Kazaxu
Name[be]=Казахская
Name[be@latin]=Kazaskaja
Name[bg]=Казахски
Name[bn]=কাজাখ
Name[bn_IN]=কাজাখ
Name[br]=Kazakstaneg
Name[bs]=Kazaški
Name[ca]=Kazakh
Name[ca@valencia]=Kazakh
Name[cs]=Kazašský
Name[csb]=Kazachsczi
Name[cy]=Kazakh
Name[da]=Kazakh
Name[de]=Kasachisch
Name[el]=Καζαχικά
Name[en_GB]=Kazakh
Name[eo]=Kazaĥa
Name[es]=Kazajo
Name[et]=Kasahhi
Name[eu]=Kazakhera
Name[fa]=قزاق
Name[fi]=Kazakki
Name[fr]=Kazakh
Name[fy]=Kazakh
Name[ga]=Casaicis
Name[gl]=Cazaxo
Name[gu]=કઝાખ
Name[he]=קזחית
Name[hi]=कज़ाख
Name[hne]=कजाख
Name[hr]=Kazački
Name[hsb]=Kazachisce
Name[hu]=Kazah
Name[ia]=Kazako
Name[id]=Kazakh
Name[is]=Kazakh
Name[it]=Kazako
Name[ja]=カザフ語
Name[kk]=Қазақша
Name[km]=កាហ្សាក់ស្ថាន
Name[kn]=ಕಝಾಕ್
Name[ko]=카자흐어
Name[ku]=Kazakî
Name[lb]=Kasachesch
Name[lt]=Kazachų
Name[lv]=Kazahu
Name[mai]=कजाख
Name[mk]=Казахстански
Name[ml]=ഖസാഖ്
Name[mr]=कज़ाख
Name[ms]=Kazakh
Name[nb]=Kazakh
Name[nds]=Kasachsch
Name[ne]=कजाक
Name[nl]=Kazakh
Name[nn]=Kasakhisk
Name[oc]=Kazakh
Name[or]=Kazakh
Name[pa]=ਕਾਜ਼ਾਖ
Name[pl]=Kazachski
Name[ps]=کزاکي
Name[pt]=Cazaque
Name[pt_BR]=Cazaque
Name[ro]=Cazacă
Name[ru]=Казахский
Name[se]=Kazakhagiella
Name[si]=කසාඛ්
Name[sk]=Kazaština
Name[sl]=kazaško
Name[sq]=Kazake
Name[sr]=казашки
Name[sr@ijekavian]=казашки
Name[sr@ijekavianlatin]=kazaški
Name[sr@latin]=kazaški
Name[sv]=Kazakiska
Name[ta]=கசாக்
Name[te]=కజాఖ్
Name[tg]=Қазоқӣ
Name[th]=ภาษาคาซัคสถาน
Name[tr]=Kazakça
Name[tt]=Казах
Name[ug]=قازاقچە
Name[uk]=Казахська
Name[uz]=Qozoqcha
Name[uz@cyrillic]=Қозоқча
Name[vi]=Kazakh
Name[wa]=Kazaxh
Name[xh]=Kazakh
Name[x-test]=xxKazakhxx
Name[zh_CN]=哈萨克语
Name[zh_HK]=哈薩克語
Name[zh_TW]=哈薩克語
[kl]
Name=Kalaallisut
Name[af]=Kalaallisut
Name[ar]=كلاليسوت
Name[as]=কালালিছুট
Name[ast]=Groenlandés
Name[be]=Калаалісут
Name[be@latin]=Kalaallisut
Name[bg]=Калисути
Name[bn]=কালালিসুট
Name[bn_IN]=কালালিসুট
Name[br]=Kalaallisut
Name[bs]=Kalalisut
Name[ca]=Kalaallisut
Name[ca@valencia]=Kalaallisut
Name[cs]=Kalaallisut
Name[csb]=Kalaallisut
Name[cy]=Kalaallisut
Name[da]=Kalaallisut
Name[de]=Kalaallisut
Name[el]=Kalaallisut
Name[en_GB]=Kalaallisut
Name[eo]=Gronlanda
Name[es]=Kalaallisut
Name[et]=Kalaallisut
Name[eu]=Kalaallisut
Name[fa]=کالالیسوت
Name[fi]=Kalaallisut (grönlanti)
Name[fr]=Kalaallisut
Name[fy]=Kalaallisut
Name[ga]=Graonlainnis
Name[gl]=Groenlandés
Name[gu]=કાલાલ્લિસુટ
Name[he]=קלאליסוט
Name[hi]=कलालिसुत
Name[hne]=कलालिसुत
Name[hr]=Kalaallisut
Name[hsb]=Kalaallisut
Name[hu]=Kalaalliszut
Name[ia]=Kalaallisut
Name[id]=Kalaallisut
Name[is]=Kalaallisut
Name[it]=Groenlandese
Name[ja]=グリーンランド語
Name[kk]=Калаалисутша
Name[km]=កាឡាអាលីសាត់
Name[kn]=ಕಲಾಲ್ಲಿಸುತ್
Name[ko]=그린란드어
Name[ku]=Kalaallîsut
Name[lb]=Kalaallisut
Name[lt]=Kalaallisut
Name[lv]=Grenlandiešu
Name[mai]=कालालिसुत
Name[mk]=Калалисут
Name[ml]=കലാല്ലിസട്ട്
Name[mr]=कलालिसुत
Name[ms]=Kalaallisut
Name[nb]=Kalaallisut
Name[nds]=Kalaallisut
Name[ne]=कलाल्लिसुट
Name[nl]=Kalaallisut
Name[nn]=Grønlandsk
Name[or]=Kalaallisut
Name[pa]=ਕਾਲਾਆਲਿਸੂਟ
Name[pl]=Kalaallisut
Name[pt]=Kalaallisut
Name[pt_BR]=Groenlandês
Name[ro]=Calalisută
Name[ru]=Гренландский
Name[se]=Kalállisutgiella
Name[si]=කලාලිසුට්
Name[sk]=Grónčina
Name[sl]=kalaallisut
Name[sq]=Kalaallisut
Name[sr]=калалисут
Name[sr@ijekavian]=калалисут
Name[sr@ijekavianlatin]=kalalisut
Name[sr@latin]=kalalisut
Name[sv]=Grönländska
Name[ta]=கலாலிசட்
Name[te]=కలాల్లిసుత్
Name[tg]=Калалисутӣ
Name[th]=ภาษาคาลัทลิซุท
Name[tr]=Kalaallisut
Name[tt]=Гренландский (калааллисут)
Name[ug]=كالائاللىسۇت
Name[uk]=Калаалісут
Name[uz]=Kalaallisut
Name[uz@cyrillic]=Калааллисут
Name[vi]=Kalaallisut
Name[wa]=Kalaallisut
Name[xh]=Kalaallisut
Name[x-test]=xxKalaallisutxx
Name[zh_CN]=格陵兰语
Name[zh_HK]=Kalaallisut語
Name[zh_TW]=Kalaallisut語
[km]
Name=Khmer
Name[af]=Khmer
Name[ar]=خمرية
Name[as]=খ্‌মেৰ
Name[ast]=Khmer
Name[be]=Хмерская
Name[be@latin]=Chmerskaja
Name[bg]=Кхмерски
Name[bn]=খমের
Name[bn_IN]=খমের
Name[br]=Kmereg
Name[bs]=Kmerski
Name[ca]=Khmer
Name[ca@valencia]=Khmer
Name[cs]=Kmérský
Name[csb]=Khmersczi
Name[cy]=Khmer
Name[da]=Khmer
Name[de]=Khmer
Name[el]=Khmer
Name[en_GB]=Khmer
Name[eo]=Kmera
Name[es]=Camboyano
Name[et]=Khmeeri
Name[eu]=Khmer
Name[fa]=خمری
Name[fi]=Khmer
Name[fr]=Khmer
Name[fy]=Khmer
Name[ga]=Ciméiris
Name[gl]=Khmer
Name[gu]=ખ્મેર
Name[he]=חמר
Name[hi]=ख्मेर
Name[hne]=ख्मेर
Name[hr]=Kmerski
Name[hsb]=Khmer
Name[hu]=Khmer
Name[ia]=Khmer
Name[id]=Khmer
Name[is]=Khmer
Name[it]=Khmer
Name[ja]=クメール語
Name[kk]=Кхмерше
Name[km]=ខ្មែរ
Name[kn]=ಖ್ಮೇರ್
Name[ko]=크메르어
Name[ku]=Khmer
Name[lb]=Khmer
Name[lt]=Khmerų
Name[lv]=Khmeru
Name[mai]=ख्मेर
Name[mk]=Кмерски
Name[ml]=ഖമര്‍
Name[mr]=ख्मेर
Name[ms]=Khmer
Name[nb]=Khmer
Name[nds]=Khmer
Name[ne]=खमेर
Name[nl]=Khmer
Name[nn]=Khmer
Name[oc]=Khmer
Name[or]=Khmer
Name[pa]=ਖਮੀਰ
Name[pl]=Khmerski
Name[ps]=کمير
Name[pt]=Khmer
Name[pt_BR]=Cambojano
Name[ro]=Kmeră
Name[ru]=Кхмерский
Name[se]=Khmeragiella
Name[si]=ඛමිර්
Name[sk]=Khmérčina
Name[sl]=kmersko
Name[sq]=Khmer
Name[sr]=кмерски
Name[sr@ijekavian]=кмерски
Name[sr@ijekavianlatin]=kmerski
Name[sr@latin]=kmerski
Name[sv]=Kambodjanska
Name[ta]=கெமர்
Name[te]=ఖ్మెర్
Name[tg]=Хмерӣ
Name[th]=ภาษาเขมร
Name[tr]=Khmer
Name[tt]=Кхмер
Name[ug]=كېخمېرچە
Name[uk]=Кхмерська
Name[uz]=Kxmercha
Name[uz@cyrillic]=Кхмерча
Name[vi]=Khơ-me
Name[wa]=Xhmer
Name[xh]=Khmer
Name[x-test]=xxKhmerxx
Name[zh_CN]=高棉语
Name[zh_HK]=高棉語
Name[zh_TW]=高棉語
[kn]
Name=Kannada
Name[af]=Kanadees
Name[ar]=كنادا
Name[as]=কন্নড়
Name[ast]=Canaresu
Name[be]=Канадская
Name[be@latin]=Kannada
Name[bg]=Каннада
Name[bn]=কন্নাডা
Name[bn_IN]=কন্নড়
Name[br]=Kanada
Name[bs]=Kannada
Name[ca]=Kanarès
Name[ca@valencia]=Kanarés
Name[cs]=Kannada
Name[csb]=Kannada
Name[cy]=Kannada
Name[da]=Kannada
Name[de]=Kannada
Name[el]=Kannada
Name[en_GB]=Kannada
Name[eo]=Kanara
Name[es]=Kannada
Name[et]=Kannada
Name[eu]=Kannada
Name[fa]=کانادایی
Name[fi]=Kannada
Name[fr]=Kannada
Name[fy]=Kannada
Name[ga]=Cannadais
Name[gl]=Kannada
Name[gu]=કન્નડ
Name[he]=קנאדה
Name[hi]=कन्नड
Name[hne]=कन्नड
Name[hr]=Kannada
Name[hsb]=Kannada
Name[hu]=Kannada
Name[ia]=Kannada
Name[id]=Kannada
Name[is]=Kannada
Name[it]=Kannada
Name[ja]=カンナダ語
Name[kk]=Каннада
Name[km]=កាណាដា
Name[kn]=ಕನ್ನಡ
Name[ko]=칸나다어
Name[ku]=Kannada
Name[lb]=Kannada
Name[lt]=Kannada
Name[lv]=Kannadu
Name[mai]=कन्नड
Name[mk]=Канада
Name[ml]=കന്നഡ
Name[mr]=कन्नड
Name[ms]=Kannada
Name[nb]=Kannada
Name[nds]=Kannada
Name[ne]=कन्नडा
Name[nl]=Kannada
Name[nn]=Kannada
Name[oc]=Kannadà
Name[or]=Kannada
Name[pa]=ਕੰਨੜ
Name[pl]=Kannada
Name[ps]=کناډا
Name[pt]=Kannada
Name[pt_BR]=Canarês
Name[ro]=Kanada
Name[ru]=Каннада
Name[se]=Kannadagiella
Name[si]=කන්නදා
Name[sk]=Kannadčina
Name[sl]=kannada
Name[sq]=Kannada
Name[sr]=канадски
Name[sr@ijekavian]=канадски
Name[sr@ijekavianlatin]=kanadski
Name[sr@latin]=kanadski
Name[sv]=Kanaresiska
Name[ta]=கன்னடம்
Name[te]=కన్నడ
Name[tg]=Канадӣ
Name[th]=ภาษากัณณาท
Name[tr]=Kannada
Name[tt]=Каннада
Name[ug]=كانناداچە
Name[uk]=Каннада
Name[uz]=Kannada
Name[uz@cyrillic]=Каннада
Name[vi]=Kannada
Name[wa]=Kannada
Name[xh]=Kannada
Name[x-test]=xxKannadaxx
Name[zh_CN]=卡纳达语
Name[zh_HK]=坎那達語
Name[zh_TW]=坎那達語
[ko]
Name=Korean
Name[af]=Koriaanse
Name[ar]=كورية
Name[as]=কোৰীয়
Name[ast]=Coreanu
Name[be]=Карэйская
Name[be@latin]=Karejskaja
Name[bg]=Корейски
Name[bn]=কোরীয়
Name[bn_IN]=কোরিয়ান
Name[br]=Koreeg
Name[bs]=Korejski
Name[ca]=Coreà
Name[ca@valencia]=Coreà
Name[cs]=Korejský
Name[csb]=Kòrejańsczi
Name[cy]=Koreëg
Name[da]=Koreansk
Name[de]=Koreanisch
Name[el]=Κορεάτικα
Name[en_GB]=Korean
Name[eo]=Korea
Name[es]=Coreano
Name[et]=Korea
Name[eu]=Koreera
Name[fa]=کره‌ای
Name[fi]=Korea
Name[fr]=Coréen
Name[fy]=Koareaansk
Name[ga]=Cóiréis
Name[gl]=Coreano
Name[gu]=કોરીયન
Name[he]=קוריאנית
Name[hi]=कोरियाई
Name[hne]=कोरियाई
Name[hr]=Korejski
Name[hsb]=Koreansce
Name[hu]=Koreai
Name[ia]=Coreano
Name[id]=Korea
Name[is]=Kóreska
Name[it]=Coreano
Name[ja]=韓国語・朝鮮語
Name[kk]=Корейше
Name[km]=កូរ៉េ
Name[kn]=ಕೊರಿಯನ್
Name[ko]=한국어
Name[ku]=Koreyî
Name[lb]=Koreanesch
Name[lt]=Korėjiečių
Name[lv]=Korejiešu
Name[mai]=कोरियाइ
Name[mk]=Корејски
Name[ml]=കൊറിയന്‍
Name[mr]=कोरियन
Name[ms]=Korea
Name[nb]=Koreansk
Name[nds]=Koreaansch
Name[ne]=कोरियाली
Name[nl]=Koreaans
Name[nn]=Koreansk
Name[oc]=Corean
Name[or]=Korean
Name[pa]=ਕੋਰੀਆਈ
Name[pl]=Koreański
Name[ps]=کوريايي
Name[pt]=Coreano
Name[pt_BR]=Coreano
Name[ro]=Coreană
Name[ru]=Корейский
Name[se]=Koreagiella
Name[si]=කොරියානු
Name[sk]=Kórejčina
Name[sl]=korejsko
Name[sq]=Koreane
Name[sr]=корејски
Name[sr@ijekavian]=корејски
Name[sr@ijekavianlatin]=korejski
Name[sr@latin]=korejski
Name[sv]=Koreanska
Name[ta]=கொரியன்
Name[te]=కొరియన్
Name[tg]=Кореягӣ
Name[th]=ภาษาเกาหลี
Name[tr]=Korece
Name[tt]=Корея
Name[ug]=كورېيەچە
Name[uk]=Корейська
Name[uz]=Koreyscha
Name[uz@cyrillic]=Корейсча
Name[vi]=Hàn Quốc
Name[wa]=Coreyin
Name[xh]=Isikorea
Name[x-test]=xxKoreanxx
Name[zh_CN]=韩语
Name[zh_HK]=韓國語
Name[zh_TW]=韓國語
[ks]
Name=Kashmiri
Name[af]=Kashmiri
Name[ar]=كشميرية
Name[as]=কাশ্মীৰি
Name[ast]=Cachemiranu
Name[be]=Кашмірская
Name[be@latin]=Kašmirskaja
Name[bg]=Кашмири
Name[bn]=কাশ্মীরি
Name[bn_IN]=কাশমিরী
Name[br]=Kashmiri
Name[bs]=Kašmirski
Name[ca]=Caixmiri
Name[ca@valencia]=Caixmiri
Name[cs]=Kašmírský
Name[csb]=Kaszmirsczi
Name[cy]=Kashmiri
Name[da]=Kashmirsk
Name[de]=Kashmiri
Name[el]=Kashmiri
Name[en_GB]=Kashmiri
Name[eo]=Kaŝmira
Name[es]=Kashmiri
Name[et]=Kašmiiri
Name[eu]=Kaxmirera
Name[fa]=کشمیری
Name[fi]=Kašmiri
Name[fr]=Kashmiri
Name[fy]=Kashmiri
Name[ga]=Caismíris
Name[gl]=Caxemir
Name[gu]=કાશ્મીરી
Name[he]=קשמירית
Name[hi]=कश्मीरी
Name[hne]=कस्मीरी
Name[hr]=Kašmirski
Name[hsb]=Kašmirsce
Name[hu]=Kasmír
Name[ia]=Kashmiri
Name[id]=Kashmiri
Name[is]=Kashmiri
Name[it]=Kašmiro
Name[ja]=カシュミール語
Name[kk]=Кашмирше
Name[km]=កាស្មៀរ
Name[kn]=ಕಶ್ಮೀರಿ
Name[ko]=카슈미르어
Name[ku]=Keşmîrî
Name[lb]=Kashmiri
Name[lt]=Kašmiro
Name[lv]=Kašmiriešu
Name[mai]=कश्मीरी
Name[mk]=Кашмирски
Name[ml]=കാഷ്മീരി
Name[mr]=कश्मीरी
Name[ms]=Kashmiri
Name[nb]=Kashmiri
Name[nds]=Kaschmiirsch
Name[ne]=काश्मिरी
Name[nl]=Kashmiri
Name[nn]=Kasjmiri
Name[oc]=Kashmiri
Name[or]=Kashmiri
Name[pa]=ਕਸ਼ਮੀਰੀ
Name[pl]=Kaszmirski
Name[ps]=کشميري
Name[pt]=Caxemir
Name[pt_BR]=Caxemira
Name[ro]=Cașmiră
Name[ru]=Кашмирский
Name[se]=Kašmirgiella
Name[si]=කාෂ්මීරි
Name[sk]=Kašmírčina
Name[sl]=kašmirsko
Name[sq]=Kashmiri
Name[sr]=кашмирски
Name[sr@ijekavian]=кашмирски
Name[sr@ijekavianlatin]=kašmirski
Name[sr@latin]=kašmirski
Name[sv]=Kashmiri
Name[ta]=காஷ்மீரி
Name[te]=కష్మీరీ
Name[tg]=Кашмирӣ
Name[th]=ภาษากัศมิรี
Name[tr]=Kashmiri
Name[tt]=Кашмири
Name[ug]=كەشمىرچە
Name[uk]=Кашмірська
Name[uz]=Kashmircha
Name[uz@cyrillic]=Кашмирча
Name[vi]=Kashmiri
Name[wa]=Cachmiri
Name[xh]=Kashmiri
Name[x-test]=xxKashmirixx
Name[zh_CN]=克什米尔语
Name[zh_HK]=喀什米爾語
Name[zh_TW]=喀什米爾語
[ku]
Name=Kurdish
Name[af]=Kurdish
Name[ar]=كردية
Name[as]=কুৰ্ডিশ্ব
Name[ast]=Kurdu
Name[be]=Курдская
Name[be@latin]=Kurdzkaja
Name[bg]=Кюрдски
Name[bn]=কুর্দিশ
Name[bn_IN]=কুর্ডিশ
Name[br]=Kurdeg
Name[bs]=Kurdski
Name[ca]=Kurd
Name[ca@valencia]=Kurd
Name[cs]=Kurdský
Name[csb]=Kùrdijsczi
Name[cy]=Kwrdeg
Name[da]=Kurdisk
Name[de]=Kurdisch
Name[el]=Κουρδικά
Name[en_GB]=Kurdish
Name[eo]=Kurda
Name[es]=Kurdo
Name[et]=Kurdi
Name[eu]=Turkiera
Name[fa]=کردی
Name[fi]=Kurdi
Name[fr]=Kurde
Name[fy]=Koerdysk
Name[ga]=Coirdis
Name[gl]=Kurdo
Name[gu]=કુર્દીશ
Name[he]=כורדית
Name[hi]=कुर्दिश
Name[hne]=कुर्दिस
Name[hr]=Kurdski
Name[hsb]=Kurdisce
Name[hu]=Kurd
Name[ia]=Kurdo
Name[id]=Kurdish
Name[is]=Kúrdíska
Name[it]=Curdo
Name[ja]=クルド語
Name[kk]=Курдше
Name[km]=ឃឺដ
Name[kn]=ಕುರ್ದಿಷ್
Name[ko]=쿠르드어
Name[ku]=Kurdî
Name[lb]=Kurdesch
Name[lt]=Kurdų
Name[lv]=Kurdu
Name[mai]=कुर्दिश
Name[mk]=Курдски
Name[ml]=കുര്‍ദ്ദിഷ്
Name[mr]=कुर्दिश
Name[ms]=Kurdish
Name[nb]=Kurdisk
Name[nds]=Kurdsch
Name[ne]=कुर्दिश
Name[nl]=Koerdisch
Name[nn]=Kurdisk
Name[oc]=Curd
Name[or]=Kurdish
Name[pa]=ਕੁਰਦ
Name[pl]=Kurdyjski
Name[ps]=کوردي
Name[pt]=Curdo
Name[pt_BR]=Curdo
Name[ro]=Curdă
Name[ru]=Курдский
Name[se]=Kurdigiella
Name[si]=කුර්දිෂ්
Name[sk]=Kurdčina
Name[sl]=kurdsko
Name[sq]=Kurde
Name[sr]=курдски
Name[sr@ijekavian]=курдски
Name[sr@ijekavianlatin]=kurdski
Name[sr@latin]=kurdski
Name[sv]=Kurdiska
Name[ta]=குர்திஷ்
Name[te]=కుర్దిష్
Name[tg]=Курдиш
Name[th]=ภาษาเคอร์ดิช
Name[tr]=Kürtçe
Name[tt]=Курд
Name[ug]=كۇردچە
Name[uk]=Курдська
Name[uz]=Kurdcha
Name[uz@cyrillic]=Курдча
Name[vi]=Kurd
Name[wa]=Kurdi
Name[xh]=Kurdish
Name[x-test]=xxKurdishxx
Name[zh_CN]=库尔德语
Name[zh_HK]=庫德語
Name[zh_TW]=庫德語
[kv]
Name=Komi
Name[af]=Komi
Name[ar]=كومي
Name[as]=কোমি
Name[ast]=Komi
Name[be]=Комі
Name[be@latin]=Komi
Name[bg]=Комизирянски
Name[bn]=কোমি
Name[bn_IN]=কোমি
Name[br]=Komi
Name[bs]=Komi
Name[ca]=Komi
Name[ca@valencia]=Komi
Name[cs]=Komi
Name[csb]=z Komi
Name[cy]=Komi
Name[da]=Komi
Name[de]=Komi
Name[el]=Komi
Name[en_GB]=Komi
Name[eo]=Komia
Name[es]=Komi
Name[et]=Komi
Name[eu]=Komoreera
Name[fa]=کمی
Name[fi]=Komi
Name[fr]=Komi
Name[fy]=Komi
Name[ga]=Coimí
Name[gl]=Komi
Name[gu]=કોમી
Name[he]=קומי
Name[hi]=कोमी
Name[hne]=कोमी
Name[hr]=Komi
Name[hsb]=Komi
Name[hu]=Komi
Name[ia]=Komi
Name[id]=Komi
Name[is]=Komi
Name[it]=Komi
Name[ja]=コミ語
Name[kk]=Коми
Name[km]=កូមី
Name[kn]=ಕೋಮಿ
Name[ko]=코미어
Name[ku]=Komî
Name[lb]=Komi-Sprooch
Name[lt]=Komi
Name[lv]=Komiešu
Name[mai]=कोमी
Name[mk]=Коми
Name[ml]=കോമി
Name[mr]=कोमी
Name[ms]=Komi
Name[nb]=Komi
Name[nds]=Komi
Name[ne]=कोमी
Name[nl]=Komi
Name[nn]=Komi
Name[oc]=Komi
Name[or]=Komi
Name[pa]=ਕੋਮੀ
Name[pl]=z Komi
Name[ps]=کومي
Name[pt]=Komi
Name[pt_BR]=Komi
Name[ro]=Komi
Name[ru]=Коми
Name[se]=Komigiella
Name[si]=කොමි
Name[sk]=Komijčina
Name[sl]=komi
Name[sq]=Komi
Name[sr]=коми
Name[sr@ijekavian]=коми
Name[sr@ijekavianlatin]=komi
Name[sr@latin]=komi
Name[sv]=Komi
Name[ta]=கோமி
Name[te]=కోమి
Name[tg]=Комӣ
Name[th]=ภาษาคอมี
Name[tr]=Komi
Name[tt]=Коми
Name[ug]=كومىچە
Name[uk]=Комі
Name[uz]=Komi
Name[uz@cyrillic]=Коми
Name[vi]=Komi
Name[wa]=Komi
Name[xh]=Komi
Name[x-test]=xxKomixx
Name[zh_CN]=科米语
Name[zh_HK]=Komi語
Name[zh_TW]=Komi語
[kw]
Name=Cornish
Name[af]=Cornish
Name[ar]=كورنية
Name[as]=ক'ৰ্নিশ্ব
Name[ast]=Córnicu
Name[be]=Корнская
Name[be@latin]=Karnijskaja
Name[bg]=Корнуълски
Name[bn]=কর্নিশ
Name[bn_IN]=কর্নিশ
Name[br]=Korneveg
Name[bs]=Korniški
Name[ca]=Còrnic
Name[ca@valencia]=Còrnic
Name[cs]=Kornišský
Name[csb]=Kòrnwalijsczi
Name[cy]=Cernyweg
Name[da]=Kornisk
Name[de]=Kornisch
Name[el]=Κορνουαλικά
Name[en_GB]=Cornish
Name[eo]=Kornvala
Name[es]=Cornuallés
Name[et]=Korni
Name[eu]=Cornish
Name[fa]=کرنیشی
Name[fi]=Korni
Name[fr]=Cornique
Name[fy]=Cornysk
Name[ga]=Coirnis
Name[gl]=Córnico
Name[gu]=કોર્નિશ
Name[he]=קורנית
Name[hi]=कॉर्निश
Name[hne]=कार्निस
Name[hr]=Kornski
Name[hsb]=Kornisce
Name[hu]=Korn
Name[ia]=Cornish
Name[id]=Cornish
Name[is]=Cornish
Name[it]=Cornico
Name[ja]=コーンウォール語
Name[kk]=Корнуолше
Name[km]=កូនីស
Name[kn]=ಕೋರ್ನಿಷ್
Name[ko]=콘월어
Name[ku]=Korniş
Name[lb]=Kornesch
Name[lt]=Kornų
Name[lv]=Korniešu
Name[mai]=कॉर्निश
Name[mk]=Корниш
Name[ml]=കോര്‍ണിഷ്
Name[mr]=कॉर्निश
Name[ms]=Cornish
Name[nb]=Cornisk
Name[nds]=Kornisch
Name[ne]=कर्निस
Name[nl]=Cornish
Name[nn]=Kornisk
Name[oc]=Cornic
Name[or]=Cornish
Name[pa]=ਕੋਰਨਿਸ਼
Name[pl]=Kornwalijski
Name[ps]=کورني
Name[pt]=Cornualho
Name[pt_BR]=Córnico
Name[ro]=Corneză
Name[ru]=Корнский
Name[se]=Kornagiella
Name[si]=කෝර්නිෂ්
Name[sk]=Kornčina
Name[sl]=cornish
Name[sq]=Kornish
Name[sr]=корнвалски
Name[sr@ijekavian]=корнвалски
Name[sr@ijekavianlatin]=kornvalski
Name[sr@latin]=kornvalski
Name[sv]=Korniska
Name[ta]=கோர்னிஷ்
Name[te]=కొర్నిష్
Name[tg]=Корниягӣ
Name[th]=ภาษาคอร์นิช
Name[tr]=Cornish
Name[tt]=Корнишча
Name[ug]=كورنىشچە
Name[uk]=Корнійська
Name[uz]=Korn
Name[uz@cyrillic]=Корн
Name[vi]=Cornwall
Name[wa]=Cornike
Name[xh]=Cornish
Name[x-test]=xxCornishxx
Name[zh_CN]=康沃尔语
Name[zh_HK]=康瓦耳語
Name[zh_TW]=康瓦耳語
[ky]
Name=Kirghiz
Name[af]=Kirghiz
Name[ar]=قرغيزية
Name[as]=কিৰ্গিজ
Name[ast]=Kirguisu
Name[be]=Кіргізская
Name[be@latin]=Kirhiskaja
Name[bg]=Киргизки
Name[bn]=কার্ঘিস
Name[bn_IN]=কির্গিজ
Name[br]=Kirgistaneg
Name[bs]=Kirgiski
Name[ca]=Kirguís
Name[ca@valencia]=Kirguís
Name[cs]=Kyrgizský
Name[csb]=Kirgijsczi
Name[cy]=Kirghiz
Name[da]=Kirghiz
Name[de]=Kirgisisch
Name[el]=Kirghiz
Name[en_GB]=Kirghiz
Name[eo]=Kirgiza
Name[es]=Kirguiz
Name[et]=Kirgiisi
Name[eu]=Kirgiera
Name[fa]=قرقیزی
Name[fi]=Kirgiisi
Name[fr]=Kirghize
Name[fy]=Kirgizysk
Name[ga]=Cirgisis
Name[gl]=Quirguiz
Name[gu]=કિર્ગીઝ
Name[he]=קירגיזית
Name[hi]=किर्गिज
Name[hne]=किर्गिज
Name[hr]=Kirgiški
Name[hsb]=Kirgisce
Name[hu]=Kirgiz
Name[ia]=Kirghizo
Name[id]=Kirghiz
Name[is]=Kirgíska
Name[it]=Kirghiso
Name[ja]=キルギス語
Name[kk]=Қырғызша
Name[km]=គៀរហ្គីស្តង់
Name[kn]=ಕಿರ್ಘಿಜ್
Name[ko]=키르키즈어
Name[ku]=Kirgîzî
Name[lb]=Kirghisesch
Name[lt]=Kirgizų
Name[lv]=Kirgīzu
Name[mai]=खिरगिज
Name[mk]=Киргизтански
Name[ml]=കിര്‍ഗ്ഗിഷ്
Name[mr]=किर्गिज
Name[ms]=Kirghiz
Name[nb]=Kirgisisk
Name[nds]=Kirgiisch
Name[ne]=क्रिगिज
Name[nl]=Kirghizisch
Name[nn]=Kirgisisk
Name[oc]=Kirghiz
Name[or]=Kirghiz
Name[pa]=ਕਿਰਘੀਜ਼
Name[pl]=Kirgiski
Name[ps]=کرګزي
Name[pt]=Quirguistanês
Name[pt_BR]=Quirguiz
Name[ro]=Chirghiză
Name[ru]=Киргизский
Name[se]=Kirgisiagiella
Name[si]=කිර්ඝිස්
Name[sk]=Kirgizština
Name[sl]=kirgizijsko
Name[sq]=Kirgize
Name[sr]=киргиски
Name[sr@ijekavian]=киргиски
Name[sr@ijekavianlatin]=kirgiski
Name[sr@latin]=kirgiski
Name[sv]=Kirghiziska
Name[ta]=கிர்கிஸ்
Name[te]=కిర్ఘిజ్
Name[tg]=Қирғизӣ
Name[th]=ภาษาเคอร์กิซ
Name[tr]=Kırgız
Name[tt]=Киргизский
Name[ug]=قىرغىزچە
Name[uk]=Киргизька
Name[uz]=Qirgʻizcha
Name[uz@cyrillic]=Қирғизча
Name[vi]=Kirghiz
Name[wa]=Kirguize
Name[xh]=Kirghiz
Name[x-test]=xxKirghizxx
Name[zh_CN]=吉尔吉斯语
Name[zh_HK]=吉爾吉斯語
Name[zh_TW]=吉爾吉斯語
[la]
Name=Latin
Name[af]=Latyn
Name[ar]=لاتينية
Name[as]=লেটিন
Name[ast]=Llatín
Name[be]=Лацінская
Name[be@latin]=Łacinskaja
Name[bg]=Латински
Name[bn]=লাতিন
Name[bn_IN]=লাতিন
Name[br]=Latin
Name[bs]=Latinski
Name[ca]=Llatí
Name[ca@valencia]=Llatí
Name[cs]=Latina
Name[csb]=Łacëzna
Name[cy]=Lladin
Name[da]=Latin
Name[de]=Latein
Name[el]=Λατινικά
Name[en_GB]=Latin
Name[eo]=Latino
Name[es]=Latín
Name[et]=Ladina
Name[eu]=Latina
Name[fa]=لاتین
Name[fi]=Latina
Name[fr]=Latin
Name[fy]=Latyn
Name[ga]=Laidin
Name[gl]=Latín
Name[gu]=લેટિન
Name[he]=לטינית
Name[hi]=लातिन
Name[hne]=लातिन
Name[hr]=Latinski
Name[hsb]=Łaćonsce
Name[hu]=Latin
Name[hy]=Լատիներեն
Name[ia]=Latino
Name[id]=Latin
Name[is]=Latína
Name[it]=Latino
Name[ja]=ラテン語
Name[kk]=Латынша
Name[km]=ឡាតាំង
Name[kn]=ಲ್ಯಾಟಿನ್
Name[ko]=라틴어
Name[ku]=Latînî
Name[lb]=Latäin
Name[lt]=Lotynų
Name[lv]=Latīņu
Name[mai]=लैटिन
Name[mk]=Латински
Name[ml]=ലത്തീന്‍
Name[mr]=लॅटिन
Name[ms]=Latin
Name[nb]=Latin
Name[nds]=Latiensch
Name[ne]=ल्याटिन
Name[nl]=Latijn
Name[nn]=Latin
Name[oc]=Latin
Name[or]=Latin
Name[pa]=ਲੈਟਿਨ
Name[pl]=Łaciński
Name[ps]=لاټيني
Name[pt]=Latim
Name[pt_BR]=Latim
Name[ro]=Latină
Name[ru]=Латинский
Name[se]=Láhtengiella
Name[si]=ලතින්
Name[sk]=Latinčina
Name[sl]=latinsko
Name[sq]=Latine
Name[sr]=латински
Name[sr@ijekavian]=латински
Name[sr@ijekavianlatin]=latinski
Name[sr@latin]=latinski
Name[sv]=Latin
Name[ta]=இலத்தீன்
Name[te]=లాటిన్
Name[tg]=Лотинӣ
Name[th]=ภาษาละติน
Name[tr]=Latin
Name[tt]=Латинский
Name[ug]=لاتىنچە
Name[uk]=Латинська
Name[uz]=Lotincha
Name[uz@cyrillic]=Лотинча
Name[vi]=Latin
Name[wa]=Latén
Name[xh]=Latin
Name[x-test]=xxLatinxx
Name[zh_CN]=拉丁语
Name[zh_HK]=拉丁語
Name[zh_TW]=拉丁語
[lb]
Name=Luxembourgish
Name[af]=Luxembourgish
Name[ar]=لوكسمبورغية
Name[as]=লাক্সেম্বৰ্গিছ
Name[ast]=Lluxemburgués
Name[be]=Люксембургская
Name[be@latin]=Luksemburskaja
Name[bg]=Люксембургски
Name[bn]=লাক্সেমবার্গিশ
Name[bn_IN]=লাক্সেমবুর্গিশ
Name[br]=Luksembourgeg
Name[bs]=Luksemburški
Name[ca]=Luxemburguès
Name[ca@valencia]=Luxemburgués
Name[cs]=Lucemburský
Name[csb]=Luksembùrsczi
Name[cy]=Luxembwrgeg
Name[da]=Luxembourgish
Name[de]=Luxemburgisch
Name[el]=Λουξεμβουργικά
Name[en_GB]=Luxembourgish
Name[eo]=Luksemburga
Name[es]=Luxemburgués
Name[et]=Luksemburgi
Name[eu]=Luxenburgera
Name[fa]=لوکزامبورگ
Name[fi]=Luxemburg
Name[fr]=Luxembourgeois
Name[fy]=Luksemboarchsk
Name[ga]=Lucsambuirgis
Name[gl]=Luxemburgués
Name[gu]=લક્ઝેમ્બર્ગીઝ
Name[he]=לוקסמבורגית
Name[hi]=लक्जमबर्गिश
Name[hne]=लक्जमबर्गिस
Name[hr]=Luksemburški
Name[hsb]=Luksemburgsce
Name[hu]=Luxemburgi
Name[ia]=Luxemburgese
Name[id]=Luxembourgish
Name[is]=Lúxemborgiska
Name[it]=Lussemburghese
Name[ja]=ルクセンブルク語
Name[kk]=Люксимбургша
Name[km]=លុចហ្សំបួរ
Name[kn]=ಲಕ್ಝೆಂಬರ್ಗಿಷ್
Name[ko]=룩셈부르크어
Name[ku]=Luksemburgî
Name[lb]=Lëtzebuergesch
Name[lt]=Liuksemburgiečių
Name[lv]=Luksemburgiešu
Name[mai]=लक्समबर्ग
Name[mk]=Луксембургски
Name[ml]=ലക്സംബര്‍ഗ്ഗിഷ്
Name[mr]=लक्जमबर्गिश
Name[ms]=Luxembourgish
Name[nb]=Luxemburgisk
Name[nds]=Luxemborgsch
Name[ne]=लग्जेम्बर्गिस
Name[nl]=Letzenburgs
Name[nn]=Luxembourgsk
Name[oc]=Luxemborgés
Name[or]=Luxembourgish
Name[pa]=ਲਕਸ਼ਬਰਗਸ਼
Name[pl]=Luksemburski
Name[ps]=لګسېمبرګيايي
Name[pt]=Luxemburguês
Name[pt_BR]=Luxemburguês
Name[ro]=Luxemburgheză
Name[ru]=Люксембургский
Name[se]=Luksenburggagiella
Name[si]=ලක්සම්බර්ගිෂ්
Name[sk]=Luxemburčina
Name[sl]=luksemburško
Name[sq]=Luksemburgeze
Name[sr]=луксембуршки
Name[sr@ijekavian]=луксембуршки
Name[sr@ijekavianlatin]=luksemburški
Name[sr@latin]=luksemburški
Name[sv]=Luxemburgiska
Name[ta]=லக்சம்போர்கிஷ்
Name[te]=లక్సెంబర్గిష్
Name[tg]=Люксембургӣ
Name[th]=ภาษาลักเซมเบอร์ก
Name[tr]=Luxembourgish
Name[tt]=Люксембург
Name[ug]=لىيۇكسېمبۇرگچە
Name[uk]=Люксембурзька
Name[uz]=Lyuksemburgcha
Name[uz@cyrillic]=Люксембургча
Name[vi]=Luxembourg
Name[wa]=Lussimbordjwès
Name[xh]=Luxembourgish
Name[x-test]=xxLuxembourgishxx
Name[zh_CN]=卢森堡语
Name[zh_HK]=盧森堡語
Name[zh_TW]=盧森堡語
[li]
Name=Limburgan
Name[af]=Limburgan
Name[ar]=لمبرغية
Name[as]=লিম্বুৰ্গান
Name[ast]=Limburganu
Name[be]=Лімбургская
Name[be@latin]=Limburskaja
Name[bg]=Лимбургански
Name[bn]=লিমবার্গান
Name[bn_IN]=লিমবুর্গান
Name[br]=Limburgeg
Name[bs]=Limburški
Name[ca]=Limburguès
Name[ca@valencia]=Limburgués
Name[cs]=Limburgan
Name[csb]=Limbùrgańsczi
Name[cy]=Limbwrgeg
Name[da]=Limburgansk
Name[de]=Limburgisch
Name[el]=Limburgan
Name[en_GB]=Limburgan
Name[eo]=Limburga
Name[es]=Limburgano
Name[et]=Limburgi
Name[eu]=Limburgan
Name[fa]=لیمبرگان
Name[fi]=Limburg
Name[fr]=Limbourgeois
Name[fy]=Limburgsk
Name[ga]=Liombuirgis
Name[gl]=Limburgano
Name[gu]=લિમ્બુર્ગી
Name[he]=לימבורגית
Name[hi]=लिम्बर्गन
Name[hne]=लिम्बर्गन
Name[hr]=Limburški
Name[hsb]=Limburgsce
Name[hu]=Limburgi
Name[ia]=Limburgano
Name[id]=Limburgan
Name[is]=Limburgian
Name[it]=Limburghese
Name[ja]=リグリア語
Name[kk]=Лимбургша
Name[km]=លីបួគ
Name[kn]=ಲಿಂಬುರ್ಗನ್
Name[ko]=림부르크어
Name[ku]=Lîmburgî
Name[lb]=Limburgesch
Name[lt]=Limburgan
Name[lv]=Limburgiešu
Name[mai]=लिम्बर्गन
Name[mk]=Лимбургански
Name[ml]=ലിമ്ബ്ബൂര്‍ഗ്ഗന്‍
Name[mr]=लिम्बर्गन
Name[ms]=Limburgan
Name[nb]=Limburgisk
Name[nds]=Limborgsch
Name[ne]=लिम्बुरगन
Name[nl]=Limburgs
Name[nn]=Limburgsk
Name[or]=Limburgan
Name[pa]=ਲੀਮਬੁਰਗਾਨ
Name[pl]=Limburgiański
Name[pt]=Limburguês
Name[pt_BR]=Limburguês
Name[ro]=Limburgană
Name[ru]=Лимбуржский
Name[se]=Limburggagiella
Name[si]=ලිම්බර්ගන්
Name[sk]=Limburčina
Name[sl]=limburgan
Name[sq]=Limburgan
Name[sr]=лимбуршки
Name[sr@ijekavian]=лимбуршки
Name[sr@ijekavianlatin]=limburški
Name[sr@latin]=limburški
Name[sv]=Limburgiska
Name[ta]=லிம்பர்கன்
Name[te]=లింబర్గన్
Name[tg]=Лимбурганӣ
Name[th]=ภาษาลิมเบอร์กัน
Name[tr]=Limburgan
Name[tt]=Лимбургча
Name[ug]=لىمبۇرگانچە
Name[uk]=Лімбурганська
Name[uz]=Limburgan
Name[uz@cyrillic]=Лимбурган
Name[vi]=Limburg
Name[wa]=Limbordjwès
Name[xh]=Limburgan
Name[x-test]=xxLimburganxx
Name[zh_CN]=林堡语
Name[zh_HK]=Limburgan語
Name[zh_TW]=Limburgan語
[ln]
Name=Lingala
Name[af]=Lingala
Name[ar]=لينغالا
Name[as]=লিঙ্গালা
Name[ast]=Lingala
Name[be]=Лінгальская
Name[be@latin]=Lingala
Name[bg]=Лингала
Name[bn]=লিঙ্গালা
Name[bn_IN]=লিঙ্গালা
Name[br]=Lingaleg
Name[bs]=Lingala
Name[ca]=Lingala
Name[ca@valencia]=Lingala
Name[cs]=Lingala
Name[csb]=Lingala
Name[cy]=Lingala
Name[da]=Lingala
Name[de]=Lingala
Name[el]=Lingala
Name[en_GB]=Lingala
Name[eo]=Lingala
Name[es]=Lingala
Name[et]=Lingala
Name[eu]=Lingala
Name[fa]=لینگالا
Name[fi]=Lingala
Name[fr]=Lingala
Name[fy]=Lingala
Name[ga]=Liongáilis
Name[gl]=Lingala
Name[gu]=લિંગાલા
Name[he]=לינגאלה
Name[hi]=लिंगला
Name[hne]=लिंगला
Name[hr]=Lingalski
Name[hsb]=Lingala
Name[hu]=Lingala
Name[ia]=Lingala
Name[id]=Lingala
Name[is]=Lingala
Name[it]=Lingala
Name[ja]=リンガラ語
Name[kk]=Лингата
Name[km]=លីនហ្គាឡា
Name[kn]=ಲಿಂಗಾಲ
Name[ko]=링갈라어
Name[ku]=Lîngala
Name[lb]=Lingala
Name[lt]=Lingala
Name[lv]=Lingala
Name[mai]=लिंगाला
Name[mk]=Лингала
Name[ml]=ലിന്‍ഗാല
Name[mr]=लिंगला
Name[ms]=Lingala
Name[nb]=Lingala
Name[nds]=Lingala
Name[ne]=लिङ्गाला
Name[nl]=Lingala
Name[nn]=Lingala
Name[oc]=Lingalà
Name[or]=Lingala
Name[pa]=ਲੀਨਗਾਲਾ
Name[pl]=Lingala
Name[ps]=لېنګالا
Name[pt]=Lingala
Name[pt_BR]=Lingala
Name[ro]=Lingală
Name[ru]=Лингала
Name[se]=Lingalagiella
Name[si]=ලින්ගලා
Name[sk]=Lingalčina
Name[sl]=lingala
Name[sq]=Lingala
Name[sr]=лингала
Name[sr@ijekavian]=лингала
Name[sr@ijekavianlatin]=lingala
Name[sr@latin]=lingala
Name[sv]=Lingala
Name[ta]=லிங்காலா
Name[te]=లింగాలా
Name[tg]=Лингалӣ
Name[th]=ภาษาลิงกาลา
Name[tr]=Lingala
Name[tt]=Лингала
Name[ug]=لىنگالاچە
Name[uk]=Лінгала
Name[uz]=Lingala
Name[uz@cyrillic]=Лингала
Name[vi]=Lingala
Name[wa]=Lingala
Name[xh]=Lingala
Name[x-test]=xxLingalaxx
Name[zh_CN]=林加拉语
Name[zh_HK]=Lingala語
Name[zh_TW]=Lingala語
[lo]
Name=Lao
Name[af]=Lao
Name[ar]=لاوية
Name[as]=লাও
Name[ast]=Laosianu
Name[be]=Лаоская
Name[be@latin]=Laoskaja
Name[bg]=Лао
Name[bn]=লাও
Name[bn_IN]=লাও
Name[br]=Lao
Name[bs]=Lao
Name[ca]=Laosià
Name[ca@valencia]=Laosià
Name[cs]=Laoský
Name[csb]=Lao
Name[cy]=Lao
Name[da]=Lao
Name[de]=Laotisch
Name[el]=Lao
Name[en_GB]=Lao
Name[eo]=Laosa
Name[es]=Laosiano
Name[et]=Lao
Name[eu]=Laosera
Name[fa]=لائویی
Name[fi]=Lao
Name[fr]=Lao
Name[fy]=Lao
Name[ga]=Láóis
Name[gl]=Lao
Name[gu]=લાઓ
Name[he]=לאו
Name[hi]=लाओ
Name[hne]=लाओ
Name[hr]=Lao
Name[hsb]=Lao
Name[hu]=Lao
Name[ia]=Lao
Name[id]=Lao
Name[is]=Lao
Name[it]=Lao
Name[ja]=ラオ語
Name[kk]=Лаосша
Name[km]=ឡាវ
Name[kn]=ಲಾಓ
Name[ko]=라오어
Name[ku]=Lao
Name[lb]=Lao
Name[lt]=Laosiečių
Name[lv]=Laosiešu
Name[mai]=लाओ
Name[mk]=Лао
Name[ml]=ലാവോ
Name[mr]=लाओ
Name[ms]=Lao
Name[nb]=Lao
Name[nds]=Laootsch
Name[ne]=लाओ
Name[nl]=Lao
Name[nn]=Laotisk
Name[oc]=Laossian
Name[or]=Lao
Name[pa]=ਲਿਓ
Name[pl]=Laotański
Name[ps]=لاو
Name[pt]=Lao
Name[pt_BR]=Laociano
Name[ro]=Lao
Name[ru]=Лаосский
Name[se]=Laogiella
Name[si]=ලාඕ
Name[sk]=Laoština
Name[sl]=laoško
Name[sq]=Lao
Name[sr]=лаоћански
Name[sr@ijekavian]=лаоћански
Name[sr@ijekavianlatin]=laoćanski
Name[sr@latin]=laoćanski
Name[sv]=Laotiska
Name[ta]=லாவோ
Name[te]=లావో
Name[tg]=Лаосӣ
Name[th]=ภาษาลาว
Name[tr]=Lao
Name[tt]=Лаос
Name[ug]=لائوسچە
Name[uk]=Лаоська
Name[uz]=Laoscha
Name[uz@cyrillic]=Лаосча
Name[vi]=Lào
Name[wa]=Lawocyin
Name[xh]=Lao
Name[x-test]=xxLaoxx
Name[zh_CN]=老挝语
Name[zh_HK]=寮國語
Name[zh_TW]=寮國語
[lt]
Name=Lithuanian
Name[af]=Lithuaniese
Name[ar]=ليتوانية
Name[as]=লিথুৱেনিয়ান
Name[ast]=Llituanu
Name[be]=Літоўская
Name[be@latin]=Litoŭskaja
Name[bg]=Литовски
Name[bn]=লিথুয়েনীয়
Name[bn_IN]=লিথুয়েনিয়ান
Name[br]=Lituaneg
Name[bs]=Litvanski
Name[ca]=Lituà
Name[ca@valencia]=Lituà
Name[cs]=Litevský
Name[csb]=Litewsczi
Name[cy]=Lithuaneg
Name[da]=Litauisk
Name[de]=Litauisch
Name[el]=Λιθουανικά
Name[en_GB]=Lithuanian
Name[eo]=Litova
Name[es]=Lituano
Name[et]=Leedu
Name[eu]=Lituaniera
Name[fa]=لیتوانی
Name[fi]=Liettua
Name[fr]=Lituanien
Name[fy]=Litouwsk
Name[ga]=Liotuáinis
Name[gl]=Lituano
Name[gu]=લિથુઆનિઅન
Name[he]=ליטאית
Name[hi]=लिथुआनियाई
Name[hne]=लिथुआनियाई
Name[hr]=Litvanski
Name[hsb]=Litawsce
Name[hu]=Litván
Name[hy]=Լիտվերեն
Name[ia]=Lituano
Name[id]=Lithuania
Name[is]=Litháíska
Name[it]=Lituano
Name[ja]=リトアニア語
Name[kk]=Литваша
Name[km]=លីទុយអានី
Name[kn]=ಲಿಥುವೇನಿಯನ್
Name[ko]=리투아니아어
Name[ku]=Lîtuwanî
Name[lb]=Litauesch
Name[lt]=Lietuvių
Name[lv]=Lietuviešu
Name[mai]=लिथुआनियाइ
Name[mk]=Литвански
Name[ml]=ലിത്തുവേനിയന്‍
Name[mr]=लिथुआनियाई
Name[ms]=Lithuania
Name[nb]=Litauisk
Name[nds]=Litausch
Name[ne]=लिथुनियाली
Name[nl]=Litouws
Name[nn]=Litauisk
Name[oc]=Lituanian
Name[or]=Lithuanian
Name[pa]=ਲੀਥੂਨੀਆਨ
Name[pl]=Litewski
Name[ps]=لېټوانيايي
Name[pt]=Lituano
Name[pt_BR]=Lituano
Name[ro]=Lituaniană
Name[ru]=Литовский
Name[se]=Lietuvagiella
Name[si]=ලිතුවේනියානු
Name[sk]=Litovčina
Name[sl]=litvansko
Name[sq]=Lituaneze
Name[sr]=литвански
Name[sr@ijekavian]=литвански
Name[sr@ijekavianlatin]=litvanski
Name[sr@latin]=litvanski
Name[sv]=Litauiska
Name[ta]=லிதுவேனியன்
Name[te]=లితువెనియన్
Name[tg]=Литвонӣ
Name[th]=ภาษาลิธัวเนีย
Name[tr]=Litvanya Dili
Name[tt]=Литва
Name[ug]=لىتۋاچە
Name[uk]=Литовська
Name[uz]=Litvacha
Name[uz@cyrillic]=Литвача
Name[vi]=Lithuani
Name[wa]=Litwanyin
Name[xh]=Lithuanian
Name[x-test]=xxLithuanianxx
Name[zh_CN]=立陶宛语
Name[zh_HK]=立陶宛語
Name[zh_TW]=立陶宛語
[lv]
Name=Latvian
Name[af]=Latvian
Name[ar]=لاتفية
Name[as]=লাট্‌ভিয়ান
Name[ast]=Letón
Name[be]=Латвійская
Name[be@latin]=Łatyskaja
Name[bg]=Латвийски
Name[bn]=লাটভিয়ান
Name[bn_IN]=লাটভিয়ান
Name[br]=Latvieg
Name[bs]=Latvijski
Name[ca]=Letó
Name[ca@valencia]=Letó
Name[cs]=Lotyšský
Name[csb]=Łotewsczi
Name[cy]=Latfieg
Name[da]=Lettisk
Name[de]=Lettisch
Name[el]=Λεττονικά
Name[en_GB]=Latvian
Name[eo]=Latvia
Name[es]=Letón
Name[et]=Läti
Name[eu]=Letoniera
Name[fa]=لاتوینی
Name[fi]=Latvia
Name[fr]=Letton
Name[fy]=Letsk
Name[ga]=Laitvis
Name[gl]=Letón
Name[gu]=લેટવિયન
Name[he]=לטבית
Name[hi]=लातवियाई
Name[hne]=लातवियाई
Name[hr]=Latvijski
Name[hsb]=Letisce
Name[hu]=Lett
Name[ia]=Letton
Name[id]=Latvia
Name[is]=Lettneska
Name[it]=Lettone
Name[ja]=ラトビア語
Name[kk]=Латвияша
Name[km]=ឡាតវៀ
Name[kn]=ಲಾಟ್ವಿಯನ್
Name[ko]=라트비아어
Name[ku]=Letonî
Name[lb]=Lettesch
Name[lt]=Latvių
Name[lv]=Latviešu
Name[mai]=लातवियाइ
Name[mk]=Латвиски
Name[ml]=ലാത്വിയന്‍
Name[mr]=लातवियाई
Name[ms]=Latvia
Name[nb]=Latvisk
Name[nds]=Lettsch
Name[ne]=लाट्भियन
Name[nl]=Lets
Name[nn]=Latvisk
Name[oc]=Leton
Name[or]=Latvian
Name[pa]=ਲਾਟਵੀਨ
Name[pl]=Łotewski
Name[ps]=لېټوين
Name[pt]=Letão
Name[pt_BR]=Letão
Name[ro]=Letonă
Name[ru]=Латышский
Name[se]=Látviagiella
Name[si]=ලැට්වියානු
Name[sk]=Lotyština
Name[sl]=latvijsko
Name[sq]=Letone
Name[sr]=летонски
Name[sr@ijekavian]=летонски
Name[sr@ijekavianlatin]=letonski
Name[sr@latin]=letonski
Name[sv]=Lettiska
Name[ta]=இலட்வியன்
Name[te]=లాటివ్యన్
Name[tg]=Латвиягӣ
Name[th]=ภาษาลัทเวีย
Name[tr]=Litvanya Dili
Name[tt]=Латыш
Name[ug]=لاتۋىيەچە
Name[uk]=Латвійська
Name[uz]=Latishcha
Name[uz@cyrillic]=Латишча
Name[vi]=Lát-vi-a
Name[wa]=Letonyin
Name[xh]=Isilatvian
Name[x-test]=xxLatvianxx
Name[zh_CN]=拉脱维亚语
Name[zh_HK]=拉脫維亞語
Name[zh_TW]=拉脫維亞語
[mai]
Name=Maithili
Name[ar]=مايثيلي
Name[as]=মৈথিলি
Name[ast]=Maithili
Name[be@latin]=Maithili
Name[bg]=Майтили
Name[bn]=মৈথিলী
Name[bn_IN]=মৈথিলি
Name[bs]=Maithili
Name[ca]=Maithili
Name[ca@valencia]=Maithili
Name[cs]=Maithili
Name[csb]=Maithili
Name[da]=Maithili
Name[de]=Maithili
Name[el]=Μαϊθίλι
Name[en_GB]=Maithili
Name[eo]=Maithili
Name[es]=Maithili
Name[et]=Maithili
Name[eu]=Maithiliera
Name[fi]=Maithili
Name[fr]=Maïthili
Name[fy]=Maithili
Name[ga]=Maitilis
Name[gl]=Maithili
Name[gu]=મૈથિલિ
Name[hi]=मैथिली
Name[hr]=Maithilski
Name[hu]=Maithili
Name[ia]=Maithili
Name[id]=Maithili
Name[is]=Maithili
Name[it]=Maithili
Name[ja]=マイティリー語
Name[kk]=Майтили
Name[km]=ម៉ារ៉ាទី
Name[kn]=ಮೈಥಿಲಿ
Name[ko]=마이틸리어
Name[ku]=Maratî
Name[lt]=Maithili
Name[lv]=Maithilu
Name[ml]=മൈഥിലി
Name[ms]=Maithili
Name[nb]=Maithili
Name[nds]=Maithili
Name[nl]=Maithili
Name[nn]=Maithili
Name[pa]=ਮੈਥਲੀ
Name[pl]=Maithili
Name[pt]=Maithili
Name[pt_BR]=Maithili
Name[ro]=Maithili
Name[ru]=Майтхили
Name[se]=Maitiligiella
Name[si]=මයිතිලි
Name[sk]=Maithilčina
Name[sl]=Maithili
Name[sq]=Maithili
Name[sr]=маитхили
Name[sr@ijekavian]=маитхили
Name[sr@ijekavianlatin]=maithili
Name[sr@latin]=maithili
Name[sv]=Maithili
Name[ta]=மைதிலி
Name[tg]=Майтифил
Name[th]=ภาษาไมถิลี
Name[tr]=Maithili
Name[tt]=Майтхили
Name[ug]=مايتىلىچە
Name[uk]=Майтхілі
Name[vi]=Maithili
Name[wa]=Maithili
Name[x-test]=xxMaithilixx
Name[zh_CN]=迈蒂利语
Name[zh_TW]=Maithili語
[mg]
Name=Malagasy
Name[af]=Malagasy
Name[ar]=ملغاسي
Name[as]=মালাগাছি
Name[ast]=Malgache
Name[be]=Малагасійская
Name[be@latin]=Malagasy
Name[bg]=Малагашки
Name[bn]=মালাগাসি
Name[bn_IN]=মালাগাসি
Name[br]=Malagacheg
Name[bs]=Malagaški
Name[ca]=Malgaix
Name[ca@valencia]=Malgaix
Name[cs]=Malagasy
Name[csb]=Madagarsczi
Name[cy]=Malagasy
Name[da]=Malagasy
Name[de]=Madagassisch
Name[el]=Μαλγασικά
Name[en_GB]=Malagasy
Name[eo]=Malagasa
Name[es]=Malagasio
Name[et]=Malagassi
Name[eu]=Madagaskarera
Name[fa]=مالاگازی
Name[fi]=Malagassi
Name[fr]=Malgache
Name[fy]=Malagasysk
Name[ga]=Malagásais
Name[gl]=Malgaxe
Name[gu]=માલાગાસી
Name[he]=מלגשית
Name[hi]=मलागासी
Name[hne]=मलागासी
Name[hr]=Malagaski
Name[hsb]=Malagasy
Name[hu]=Malagazi
Name[ia]=Malagasy
Name[id]=Malagasy
Name[is]=Malagasy
Name[it]=Malgascio
Name[ja]=マラガシ語
Name[kk]=Мальгашша
Name[km]=ម៉ាដាហ្កាសការ
Name[kn]=ಮಲಗೆಸಿ
Name[ko]=말라가시어
Name[ku]=Malagasî
Name[lb]=Madagassesch
Name[lt]=Malagasių
Name[lv]=Malagasu
Name[mai]=मलागासी
Name[mk]=Малагаси
Name[ml]=മലഗാസി
Name[mr]=मलागासी
Name[ms]=Malagasy
Name[nb]=Malagasy
Name[nds]=Madagass'sch
Name[ne]=मालागासे
Name[nl]=Malagasy
Name[nn]=Gassisk
Name[or]=Malagasy
Name[pa]=ਮਾਲਾਗਾਸਆ
Name[pl]=Madagaskarski
Name[ps]=ملاګاسي
Name[pt]=Malgaxe
Name[pt_BR]=Malgaxe
Name[ro]=Malgașă
Name[ru]=Мальгашский
Name[se]=Malagasigiella
Name[si]=මලගසි
Name[sk]=Malgaština
Name[sl]=malagaško
Name[sq]=Malagasy
Name[sr]=малагаси
Name[sr@ijekavian]=малагаси
Name[sr@ijekavianlatin]=malagasi
Name[sr@latin]=malagasi
Name[sv]=Malagassiska
Name[ta]=மலகாசி
Name[te]=మలగాసి
Name[tg]=Малагасӣ
Name[th]=ภาษามาลากาซี
Name[tr]=Malagasy
Name[tt]=Малагасийча
Name[ug]=ماداغاسقارچە
Name[uk]=Малагасійська
Name[uz]=Malagasi
Name[uz@cyrillic]=Малагаси
Name[vi]=Malagasy
Name[wa]=Malgache
Name[xh]=Malagasy
Name[x-test]=xxMalagasyxx
Name[zh_CN]=马尔加什语
Name[zh_HK]=馬拉加西語
Name[zh_TW]=馬拉加西語
[mh]
Name=Marshallese
Name[af]=Marshallese
Name[ar]=مارشالية
Name[as]=মাৰ্ছালিশ্ব
Name[ast]=Marshalés
Name[be]=Маршальская
Name[be@latin]=Maršaleskaja
Name[bg]=Ебон
Name[bn]=মার্শালিস
Name[bn_IN]=মার্শালিস
Name[br]=Yezh an enez Marshall
Name[bs]=Maršalski
Name[ca]=Marshallès
Name[ca@valencia]=Marshallés
Name[cs]=Marshallský
Name[csb]=z Òstrowów Marshalla
Name[cy]=Marshalleg
Name[da]=Marshallesisk
Name[de]=Marshallisch
Name[el]=Marshallese
Name[en_GB]=Marshallese
Name[eo]=Marŝala
Name[es]=Marshalés
Name[et]=Maršalli
Name[eu]=Marshallera
Name[fa]=مارشالی
Name[fi]=Marshall
Name[fr]=Marshall
Name[fy]=Marshalleesk
Name[ga]=Marascailis
Name[gl]=Marshalés
Name[gu]=માર્શલીઝ
Name[he]=מרשלית
Name[hi]=मार्शलीज
Name[hne]=मार्सलीज
Name[hr]=Maršaleski
Name[hsb]=Maršalesce
Name[hu]=Marsalli
Name[ia]=Marshallese
Name[id]=Marshallese
Name[is]=Maltneska
Name[it]=Marsciallese
Name[ja]=マーシャル語
Name[kk]=Маршаллша
Name[km]=ម៉ាស្យាលីស
Name[kn]=ಮಾರ್ಶಲ್ಲೀಸ್
Name[ko]=마셜어
Name[ku]=Marşalî
Name[lb]=Marschallesesch
Name[lt]=Maršaliečių
Name[lv]=Māršaliešu
Name[mai]=मार्शलीज
Name[mk]=Маршалски
Name[ml]=മാര്‍ഷാലീസ്
Name[mr]=मार्शलीज
Name[ms]=Marshallese
Name[nb]=Marshallesisk
Name[nds]=Marschalleesch
Name[ne]=मार्शलिज
Name[nl]=Marshallees
Name[nn]=Marshallesisk
Name[or]=Marshallese
Name[pa]=ਮਾਰਸ਼ਲੀਈਸੀ
Name[pl]=z Wysp Marshalla
Name[ps]=مارشالي
Name[pt]=Marselhês
Name[pt_BR]=Marchalês
Name[ro]=Marșaleză
Name[ru]=Маршалльский
Name[se]=Marshallagiella
Name[si]=මාෂලීස්
Name[sk]=Maršalčina
Name[sl]=maršalsko
Name[sq]=Marshalleze
Name[sr]=маршалески
Name[sr@ijekavian]=маршалески
Name[sr@ijekavianlatin]=maršaleski
Name[sr@latin]=maršaleski
Name[sv]=Marshall
Name[ta]=மார்ஷலீஸ்
Name[te]=మార్షలీస్
Name[tg]=Маршалезӣ
Name[th]=ภาษามาร์แชล
Name[tr]=Marshallese
Name[tt]=Маршаллча
Name[ug]=مارشالچە
Name[uk]=Маршальська
Name[uz]=Marshallez
Name[uz@cyrillic]=Маршаллез
Name[vi]=Marshall
Name[wa]=Marshalès
Name[xh]=Marshallese
Name[x-test]=xxMarshallesexx
Name[zh_CN]=马绍尔语
Name[zh_HK]=馬紹爾語
Name[zh_TW]=馬紹爾語
[mi]
Name=Maori
Name[af]=Maori
Name[ar]=ماوري
Name[as]=মাওৰি
Name[ast]=Maorí
Name[be]=Маорская
Name[be@latin]=Maory
Name[bg]=Маори
Name[bn]=মাওরি
Name[bn_IN]=মাওরি
Name[br]=Maorieg
Name[bs]=Maorski
Name[ca]=Maori
Name[ca@valencia]=Maori
Name[cs]=Maorský
Name[csb]=Maòrëjsczi
Name[cy]=Maori
Name[da]=Maori
Name[de]=Maori
Name[el]=Μαορί
Name[en_GB]=Maori
Name[eo]=Maoria
Name[es]=Maorí
Name[et]=Maoori
Name[eu]=Maoria
Name[fa]=مائوری
Name[fi]=Maori
Name[fr]=Maori
Name[fy]=Maori
Name[ga]=Maorais
Name[gl]=Maorí
Name[gu]=માઓરી
Name[he]=מאורית
Name[hi]=माओरी
Name[hne]=माओरी
Name[hr]=Maori
Name[hsb]=Maori
Name[hu]=Maori
Name[ia]=Maori
Name[id]=Maori
Name[is]=Maori
Name[it]=Maori
Name[ja]=マオリ語
Name[kk]=Маори
Name[km]=ម៉ោរី
Name[kn]=ಮವೋರಿ
Name[ko]=마오리어
Name[ku]=Maorî
Name[lb]=Maoresch
Name[lt]=Maorių
Name[lv]=Maoru
Name[mai]=माओरी
Name[mk]=Маорски
Name[ml]=മൌറി
Name[mr]=माओरी
Name[ms]=Maori
Name[nb]=Maori
Name[nds]=Maori
Name[ne]=माओरी
Name[nl]=Maori
Name[nn]=Maori
Name[oc]=Maòri
Name[or]=Maori
Name[pa]=ਮਾਓਰੀ
Name[pl]=Maoryjski
Name[ps]=ماوري
Name[pt]=Maori
Name[pt_BR]=Maori
Name[ro]=Maură
Name[ru]=Маори
Name[se]=Maoragiella
Name[si]=මඕරි
Name[sk]=Maorčina
Name[sl]=maorsko
Name[sq]=Maori
Name[sr]=маорски
Name[sr@ijekavian]=маорски
Name[sr@ijekavianlatin]=maorski
Name[sr@latin]=maorski
Name[sv]=Maoriska
Name[ta]=மாவோரி
Name[te]=మావొరి
Name[tg]=Маори
Name[th]=ภาษาเมารี
Name[tr]=Maori Dili
Name[tt]=Майорикча
Name[ug]=ماۋرىچە
Name[uk]=Маорі
Name[uz]=Maori
Name[uz@cyrillic]=Маори
Name[vi]=Maori
Name[wa]=Mawori
Name[xh]=Maori
Name[x-test]=xxMaorixx
Name[zh_CN]=毛利语
Name[zh_HK]=毛利語
Name[zh_TW]=毛利語
[mk]
Name=Macedonian
Name[af]=Macedonian
Name[ar]=مقدونية
Name[as]=মেচিডোনীয়
Name[ast]=Macedoniu
Name[be]=Македонская
Name[be@latin]=Makiedonskaja
Name[bg]=Македонски
Name[bn]=মাসিডোনীয়
Name[bn_IN]=মেসিডোনিয়ান
Name[br]=Makedoneg
Name[bs]=Makedonski
Name[ca]=Macedoni
Name[ca@valencia]=Macedoni
Name[cs]=Makedonský
Name[csb]=Macedońsczi
Name[cy]=Macedoneg
Name[da]=Makedonisk
Name[de]=Mazedonisch
Name[el]=Σλαβομακεδονικά
Name[en_GB]=Macedonian
Name[eo]=Makedona
Name[es]=Macedonio
Name[et]=Makedoonia
Name[eu]=Mazedoniera
Name[fa]=مقدونی
Name[fi]=Makedonia
Name[fr]=Macédonien
Name[fy]=Masedoanysk
Name[ga]=Macadóinis
Name[gl]=Macedonio
Name[gu]=મેસેડોનિયન
Name[he]=מקדונית
Name[hi]=मकदूनियाई
Name[hne]=मकदूनियाई
Name[hr]=Makedonski
Name[hsb]=Makedonsce
Name[hu]=Macedón
Name[ia]=Macedone
Name[id]=Macedonia
Name[is]=Makedóníska
Name[it]=Macedone
Name[ja]=マケドニア語
Name[kk]=Македонша
Name[km]=ម៉ាសេដូនី
Name[kn]=ಮೆಸಡೋನಿಯನ್
Name[ko]=마케도니아어
Name[ku]=Makedonî
Name[lb]=Mazedonesch
Name[lt]=Makedoniečių
Name[lv]=Maķedoniešu
Name[mai]=मेसिडोनियाइ
Name[mk]=Македонски
Name[ml]=മാസിഡോണിയന്‍
Name[mr]=मेसीडोनियन
Name[ms]=Macedonia
Name[nb]=Makedonsk
Name[nds]=Makedoonsch
Name[ne]=म्यासेडोनियाली
Name[nl]=Macedonisch
Name[nn]=Makedonsk
Name[oc]=Macedonian
Name[or]=Macedonian
Name[pa]=ਮੈਕਡੋਨੀਆ
Name[pl]=Macedoński
Name[ps]=مېسېډونين
Name[pt]=Macedónio
Name[pt_BR]=Macedônio
Name[ro]=Macedoneană
Name[ru]=Македонский
Name[se]=Makedoniagiella
Name[si]=මැසිඩෝනියානු
Name[sk]=Macedónčina
Name[sl]=makedonsko
Name[sq]=Maqedone
Name[sr]=македонски
Name[sr@ijekavian]=македонски
Name[sr@ijekavianlatin]=makedonski
Name[sr@latin]=makedonski
Name[sv]=Makedonska
Name[ta]=மாசிடோனியன்
Name[te]=మసిడొనియన్
Name[tg]=Мақдунӣ
Name[th]=ภาษามาเซโดเนีย
Name[tr]=Makedonca
Name[tt]=Македония
Name[ug]=ماكېدونچە
Name[uk]=Македонська
Name[uz]=Makedoniyacha
Name[uz@cyrillic]=Македонияча
Name[vi]=Macedonia
Name[wa]=Macedonyin
Name[xh]=Macedonian
Name[x-test]=xxMacedonianxx
Name[zh_CN]=马其顿语
Name[zh_HK]=馬其頓語
Name[zh_TW]=馬其頓語
[ml]
Name=Malayalam
Name[af]=Malayalam
Name[ar]=مالايالام
Name[as]=মলয়ালম
Name[ast]=Malayalam
Name[be]=Малаяламская
Name[be@latin]=Malayalam
Name[bg]=Малаялам
Name[bn]=মালয়লম
Name[bn_IN]=মালায়ালম
Name[br]=Malayalam
Name[bs]=Malajalam
Name[ca]=Malaiàlam
Name[ca@valencia]=Malaiàlam
Name[cs]=Malayalam
Name[csb]=Drawidańsczi (hindusczi)
Name[cy]=Malayalam
Name[da]=Malayalam
Name[de]=Malayalam
Name[el]=Malayalam
Name[en_GB]=Malayalam
Name[eo]=Malajala
Name[es]=Malabar
Name[et]=Malajalami
Name[eu]=Malayalam
Name[fa]=مالی
Name[fi]=Malajalam
Name[fr]=Malayalam
Name[fy]=Malayalam
Name[ga]=Mailéalaimis
Name[gl]=Malaialam
Name[gu]=મલયાલમ
Name[he]=מליאלאם
Name[hi]=मलयालम
Name[hne]=मलयालम
Name[hr]=Malajamski
Name[hsb]=Malayalam
Name[hu]=Malajalam
Name[ia]=Malayalam
Name[id]=Malayalam
Name[is]=Malayalam
Name[it]=Malayalam
Name[ja]=マラヤーラム語
Name[kk]=Малайамша
Name[km]=ម៉ាឡាយ៉ាឡាម
Name[kn]=ಮಲಯಾಳಂ
Name[ko]=말라얄람어
Name[ku]=Malayalam
Name[lb]=Malajalam
Name[lt]=Malajiečių
Name[lv]=Malajalu
Name[mai]=मलयालम
Name[mk]=Малајалам
Name[ml]=മലയാളം
Name[mr]=मल्यालम
Name[ms]=Malayalam
Name[nb]=Malayalam
Name[nds]=Malayalam
Name[ne]=मलेयालम
Name[nl]=Malayalam
Name[nn]=Malayalam
Name[oc]=Malayalam
Name[or]=Malayalam
Name[pa]=ਮਲਿਆਲਮ
Name[pl]=Malajalam
Name[ps]=ملايالم
Name[pt]=Malayalam
Name[pt_BR]=Malaiala
Name[ro]=Malailamă
Name[ru]=Малаялам
Name[se]=Malajalamagiella
Name[si]=මලයාලම්
Name[sk]=Malajálamčina
Name[sl]=malayalam
Name[sq]=Malajalam
Name[sr]=малајалам
Name[sr@ijekavian]=малајалам
Name[sr@ijekavianlatin]=malajalam
Name[sr@latin]=malajalam
Name[sv]=Malayalam
Name[ta]=மலையாளம்
Name[te]=మలయాళం
Name[tg]=Малаямӣ
Name[th]=ภาษามะละยาลัม
Name[tr]=Malayalam
Name[tt]=Малаялам
Name[ug]=مالايامچە
Name[uk]=Малайська
Name[uz]=Malayalam
Name[uz@cyrillic]=Малайалам
Name[vi]=Malayalam
Name[wa]=Malayalam
Name[xh]=Malayalam
Name[x-test]=xxMalayalamxx
Name[zh_CN]=马拉亚拉姆语
Name[zh_HK]=馬來亞拉姆語
Name[zh_TW]=馬來亞拉姆語
[mn]
Name=Mongolian
Name[af]=Mongolees
Name[ar]=منغولية
Name[as]=মঙ্গোলীয়
Name[ast]=Mongol
Name[be]=Мангольская
Name[be@latin]=Manholskaja
Name[bg]=Монголски
Name[bn]=মোঙ্গল
Name[bn_IN]=মঙ্গোলিয়ান
Name[br]=Mongolieg
Name[bs]=Mongolski
Name[ca]=Mongol
Name[ca@valencia]=Mongol
Name[cs]=Mongolský
Name[csb]=Mòngolsczi
Name[cy]=Mongoleg
Name[da]=Mongolsk
Name[de]=Mongolisch
Name[el]=Μογγολικά
Name[en_GB]=Mongolian
Name[eo]=Mongola
Name[es]=Mongol
Name[et]=Mongoolia
Name[eu]=Mongoliera
Name[fa]=مغول
Name[fi]=Mongoli
Name[fr]=Mongol
Name[fy]=Moangoalsk
Name[ga]=Mongóilis
Name[gl]=Mongol
Name[gu]=મોંગોલિયન
Name[he]=מונגולית
Name[hi]=मंगोलियन
Name[hne]=मंगोलियन
Name[hr]=Mongolski
Name[hsb]=Mongolsce
Name[hu]=Mongol
Name[ia]=Mongolico
Name[id]=Mongolia
Name[is]=Bosníska
Name[it]=Mongolo
Name[ja]=モンゴル語
Name[kk]=Моңғолша
Name[km]=ម៉ុងហ្គោលី
Name[kn]=ಮಂಗೋಲಿಯನ್
Name[ko]=몽골어
Name[ku]=Mongolî
Name[lb]=Mongolesch
Name[lt]=Mongolų
Name[lv]=Mongoļu
Name[mai]=मंगोलियाइ
Name[mk]=Монголски
Name[ml]=മംഗോളിയ
Name[mr]=मंगोलीयन
Name[ms]=Mongolia
Name[nb]=Mongolsk
Name[nds]=Mongoolsch
Name[ne]=मङ्गोलियन
Name[nl]=Mongolisch
Name[nn]=Mongolsk
Name[oc]=Mongolian
Name[or]=Mongolian
Name[pa]=ਮੰਗੋਲੀਆਈ
Name[pl]=Mongolski
Name[ps]=مغلي
Name[pt]=Mongol
Name[pt_BR]=Mongol
Name[ro]=Mongolă
Name[ru]=Монгольский
Name[se]=Mongoliagiella
Name[si]=මොංගෝලියානු
Name[sk]=Mongolčina
Name[sl]=mongolsko
Name[sq]=Mongole
Name[sr]=монголски
Name[sr@ijekavian]=монголски
Name[sr@ijekavianlatin]=mongolski
Name[sr@latin]=mongolski
Name[sv]=Mongoliska
Name[ta]=மங்கோலியன்
Name[te]=మంగోలియన్
Name[tg]=Муғулӣ
Name[th]=ภาษามองโกล
Name[tr]=Moğolca
Name[tt]=Монгол
Name[ug]=موڭغۇلچە
Name[uk]=Монгольська
Name[uz]=Mugʻulcha
Name[uz@cyrillic]=Муғулча
Name[vi]=Mông Cổ
Name[wa]=Mongol
Name[xh]=Mongolian
Name[x-test]=xxMongolianxx
Name[zh_CN]=蒙古语
Name[zh_HK]=蒙古語
Name[zh_TW]=蒙古語
[mo]
Name=Moldavian
Name[af]=Moldavian
Name[ar]=مولدافية
Name[as]=মোল্দাভিয়ান
Name[ast]=Moldavu
Name[be]=Малдаўская
Name[be@latin]=Małdaŭskaja
Name[bg]=Молдовски
Name[bn]=মলডেভীয়
Name[bn_IN]=মোল্ডাভিয়ান
Name[br]=Moldaveg
Name[bs]=Moldavski
Name[ca]=Moldau
Name[ca@valencia]=Moldau
Name[cs]=Moldavský
Name[csb]=Mòłdawsczi
Name[cy]=Moldafeg
Name[da]=Moldovisk
Name[de]=Moldawisch
Name[el]=Μολδαβικά
Name[en_GB]=Moldavian
Name[eo]=Moldava
Name[es]=Moldavo
Name[et]=Moldaavia
Name[eu]=Moldabiera
Name[fa]=مولداوی
Name[fi]=Moldavia
Name[fr]=Moldave
Name[fy]=Moldavysk
Name[ga]=Moldávais
Name[gl]=Moldavo
Name[gu]=મોલ્ડેવિયન
Name[he]=מולדבית
Name[hi]=मोल्दावियन
Name[hne]=मोल्दावियन
Name[hr]=Moldavski
Name[hsb]=Moldawsce
Name[hu]=Moldáv
Name[ia]=Moldaviano
Name[id]=Moldavian
Name[is]=Moldóvíska
Name[it]=Moldavo
Name[ja]=モルダビア語
Name[kk]=Молдовша
Name[km]=ម៉ុលដាវី
Name[kn]=ಮಾಲ್ಡೇವಿಯನ್
Name[ko]=몰도바어
Name[ku]=Moldavî
Name[lb]=Moldawesch
Name[lt]=Moldavų
Name[lv]=Moldāvu
Name[mai]=मोल्दावियन
Name[mk]=Молдавски
Name[ml]=മാള്‍ഡോവിയന്‍
Name[mr]=मोल्दावियन
Name[ms]=Moldavia
Name[nb]=Moldavisk
Name[nds]=Moldaawsch
Name[ne]=मोल्डाभियन
Name[nl]=Moldavisch
Name[nn]=Moldovsk
Name[oc]=Croacia
Name[or]=Moldavian
Name[pa]=ਮੋਲਡਾਵੀਆਈ
Name[pl]=Mołdawski
Name[ps]=مالډيوي
Name[pt]=Moldavo
Name[pt_BR]=Moldavo
Name[ro]=Moldovenească
Name[ru]=Молдавский
Name[se]=Moldáviagiella
Name[si]=මෝල්දාවියන්
Name[sk]=Moldavčina
Name[sl]=moldavsko
Name[sq]=Moldave
Name[sr]=молдавски
Name[sr@ijekavian]=молдавски
Name[sr@ijekavianlatin]=moldavski
Name[sr@latin]=moldavski
Name[sv]=Moldaviska
Name[ta]=மோல்டாவியன்
Name[te]=మొల్దావియన్
Name[tg]=Молдаванӣ
Name[th]=ภาษามอลดาเวียน
Name[tr]=Moldavya Dili
Name[tt]=Молдавча
Name[ug]=مولداۋىيىچە
Name[uk]=Молдовська
Name[uz]=Moldavcha
Name[uz@cyrillic]=Молдавча
Name[vi]=Moldava
Name[wa]=Moldåve
Name[xh]=Moldavian
Name[x-test]=xxMoldavianxx
Name[zh_CN]=摩尔多瓦语
Name[zh_HK]=摩爾達維亞語
Name[zh_TW]=摩爾達維亞語
[mr]
Name=Marathi
Name[af]=Marathi
Name[ar]=ماراثية
Name[as]=মাৰাথি
Name[ast]=Marathi
Name[be]=Мараўская
Name[be@latin]=Marathi
Name[bg]=Маратхи
Name[bn]=মরাঠী
Name[bn_IN]=মারাঠি
Name[br]=Marateg
Name[bs]=Marati
Name[ca]=Marathi
Name[ca@valencia]=Marathi
Name[cs]=Marathi
Name[csb]=Marathi (hindusczi)
Name[cy]=Marathi
Name[da]=Marathi
Name[de]=Marathi
Name[el]=Marathi
Name[en_GB]=Marathi
Name[eo]=Marata
Name[es]=Maratí
Name[et]=Marathi
Name[eu]=Marathera
Name[fa]=ماراتی
Name[fi]=Marathi
Name[fr]=Marathe
Name[fy]=Marathi
Name[ga]=Maraitis
Name[gl]=Marata
Name[gu]=મરાઠી
Name[he]=מאראתי
Name[hi]=मराठी
Name[hne]=मराठी
Name[hr]=Marathi
Name[hsb]=Marati
Name[hu]=Marati
Name[ia]=Marathi
Name[id]=Marathi
Name[is]=Marathi
Name[it]=Marathi
Name[ja]=マラーティー語
Name[kk]=Марати
Name[km]=ម៉ារ៉ាទី
Name[kn]=ಮರಾಠಿ
Name[ko]=마라티어
Name[ku]=Maratî
Name[lb]=Marathi
Name[lt]=Marathi
Name[lv]=Marathu
Name[mai]=मराठी
Name[mk]=Марати
Name[ml]=മറാത്തി
Name[mr]=मराठी
Name[ms]=Marathi
Name[nb]=Marathi
Name[nds]=Marathi
Name[ne]=मराठी
Name[nl]=Marathi
Name[nn]=Marathi
Name[oc]=Marati
Name[or]=ମରାଠି
Name[pa]=ਮਰਾਠੀ
Name[pl]=Marathi (Hinduski)
Name[ps]=مراټي
Name[pt]=Marathi
Name[pt_BR]=Marata
Name[ro]=Marathi
Name[ru]=Маратхи
Name[se]=Marathagiella
Name[si]=මරාති
Name[sk]=Maráthčina
Name[sl]=marathi
Name[sq]=Marathi
Name[sr]=марати
Name[sr@ijekavian]=марати
Name[sr@ijekavianlatin]=marati
Name[sr@latin]=marati
Name[sv]=Marathi
Name[ta]=மராத்தி
Name[te]=మరాఠి
Name[tg]=Маравӣ
Name[th]=ภาษามราฐี
Name[tr]=Marathi
Name[tt]=Маратхи
Name[ug]=ماراتىچە
Name[uk]=Мараті
Name[uz]=Marati
Name[uz@cyrillic]=Марати
Name[vi]=Marathi
Name[wa]=Marati
Name[xh]=Marathi
Name[x-test]=xxMarathixx
Name[zh_CN]=马拉提语
Name[zh_HK]=馬拉地語
Name[zh_TW]=馬拉地語
[ms]
Name=Malay
Name[af]=Malay
Name[ar]=مالوية
Name[as]=মালায়
Name[ast]=Malayu
Name[be]=Малайская
Name[be@latin]=Malajskaja
Name[bg]=Малайски
Name[bn]=মালয়
Name[bn_IN]=মালায়
Name[br]=Maya
Name[bs]=Malajski
Name[ca]=Malai
Name[ca@valencia]=Malai
Name[cs]=Malajský
Name[csb]=Malajsczi
Name[cy]=Malay
Name[da]=Malay
Name[de]=Malaiisch
Name[el]=Malay
Name[en_GB]=Malay
Name[eo]=Malaja
Name[es]=Malayo
Name[et]=Malaisia
Name[eu]=Malaysiera
Name[fa]=مالی
Name[fi]=Malaiji
Name[fr]=Malais
Name[fy]=Maleisk
Name[ga]=Malaeis
Name[gl]=Malaio
Name[gu]=મલય
Name[he]=מלאית
Name[hi]=मलय
Name[hne]=मलय
Name[hr]=Malajski
Name[hsb]=Malajsce
Name[hu]=Maláj
Name[ia]=Malay
Name[id]=Melayu
Name[is]=Malay
Name[it]=Malese
Name[ja]=マレー語
Name[kk]=Малайша
Name[km]=ម៉ាឡេស៊ី
Name[kn]=ಮಲೇಯ್
Name[ko]=말레이어
Name[ku]=Malayî
Name[lb]=Malaiesch
Name[lt]=Malajų
Name[lv]=Malajiešu
Name[mai]=मलय
Name[mk]=Малајски
Name[ml]=മലയ്
Name[mr]=मलय
Name[ms]=Malay
Name[nb]=Malaiisk
Name[nds]=Malaiisch
Name[ne]=मले
Name[nl]=Malay
Name[nn]=Malayisk
Name[oc]=Malés
Name[or]=Malay
Name[pa]=ਮਾਲਿਆ
Name[pl]=Malajski
Name[ps]=ملای
Name[pt]=Malaio
Name[pt_BR]=Malaio
Name[ro]=Malaeză
Name[ru]=Малайский
Name[se]=Malaigiella
Name[si]=මැලේ
Name[sk]=Malajčina
Name[sl]=malajsko
Name[sq]=Malaj
Name[sr]=малајски
Name[sr@ijekavian]=малајски
Name[sr@ijekavianlatin]=malajski
Name[sr@latin]=malajski
Name[sv]=Malajiska
Name[ta]=மலாய்
Name[te]=మలయ
Name[tg]=Малайӣ
Name[th]=ภาษามลายู
Name[tr]=Malay
Name[tt]=Малай
Name[ug]=مالايچە
Name[uk]=Малайська
Name[uz]=Malaycha
Name[uz@cyrillic]=Малайча
Name[vi]=Mã lai
Name[wa]=Malay
Name[xh]=Malay
Name[x-test]=xxMalayxx
Name[zh_CN]=马来语
Name[zh_HK]=馬來語
Name[zh_TW]=馬來語
[mt]
Name=Maltese
Name[af]=Maltees
Name[ar]=مالطية
Name[as]=মাল্টিছ
Name[ast]=Maltés
Name[be]=Мальтыйская
Name[be@latin]=Maltyjskaja
Name[bg]=Малтийски
Name[bn]=মল্টিস
Name[bn_IN]=মল্টিস
Name[br]=Malteg
Name[bs]=Malteški
Name[ca]=Maltès
Name[ca@valencia]=Maltés
Name[cs]=Maltézský
Name[csb]=Maltajsczi
Name[cy]=Malteg
Name[da]=Maltesisk
Name[de]=Maltesisch
Name[el]=Μαλτέζικα
Name[en_GB]=Maltese
Name[eo]=Malta
Name[es]=Maltés
Name[et]=Malta
Name[eu]=Maltera
Name[fa]=مالتز
Name[fi]=Malta
Name[fr]=Maltais
Name[fy]=Malteesk
Name[ga]=Máltais
Name[gl]=Maltés
Name[gu]=માલ્ટીઝ
Name[he]=מלטזית
Name[hi]=माल्टी
Name[hne]=माल्टी
Name[hr]=Malteški
Name[hsb]=Maltesce
Name[hu]=Máltai
Name[ia]=Maltese
Name[id]=Maltese
Name[is]=Maltneska
Name[it]=Maltese
Name[ja]=マルタ語
Name[kk]=Мальташа
Name[km]=ម៉ាល់តា
Name[kn]=ಮಾಲ್ಟೀಸ್
Name[ko]=몰타어
Name[ku]=Maltayî
Name[lb]=Maltesesch
Name[lt]=Maltiečių
Name[lv]=Maltiešu
Name[mai]=माल्टीज
Name[mk]=Малтешки
Name[ml]=മാള്‍ടീസ്
Name[mr]=माल्टी
Name[ms]=Maltese
Name[nb]=Maltesisk
Name[nds]=Malteesch
Name[ne]=माल्तिस
Name[nl]=Maltees
Name[nn]=Maltesisk
Name[oc]=Maltés
Name[or]=Maltese
Name[pa]=ਮਾਲਟੀਸੀ
Name[pl]=Maltański
Name[ps]=مالټي
Name[pt]=Maltês
Name[pt_BR]=Maltês
Name[ro]=Malteză
Name[ru]=Мальтийский
Name[se]=Maltagiella
Name[si]=මෝල්ටීස්
Name[sk]=Maltčina
Name[sl]=maltežansko
Name[sq]=Malteze
Name[sr]=малтешки
Name[sr@ijekavian]=малтешки
Name[sr@ijekavianlatin]=malteški
Name[sr@latin]=malteški
Name[sv]=Maltesiska
Name[ta]=மால்டீசு
Name[te]=మాల్టీస్
Name[tg]=Малтесӣ
Name[th]=ภาษามอลตีส
Name[tr]=Malta Dili
Name[tt]=Мальтийский
Name[ug]=مالتاچە
Name[uk]=Мальтійська
Name[uz]=Maltacha
Name[uz@cyrillic]=Малтача
Name[vi]=Malta
Name[wa]=Maltès
Name[xh]=Maltese
Name[x-test]=xxMaltesexx
Name[zh_CN]=马耳他语
Name[zh_HK]=馬爾他語
Name[zh_TW]=馬爾他語
[my]
Name=Burmese
Name[af]=Burmees
Name[ar]=بورمية
Name[as]=বাৰ্মিছ
Name[ast]=Birmanu
Name[be]=Бурмеская
Name[be@latin]=Burmeskaja
Name[bg]=Бирмански
Name[bn]=বর্মী
Name[bn_IN]=বার্মিজ
Name[br]=Birmaneg
Name[bs]=Burmanski
Name[ca]=Birmà
Name[ca@valencia]=Birmà
Name[cs]=Burmský
Name[csb]=Birmańsczi
Name[cy]=Burmeg
Name[da]=Burmese
Name[de]=Burmesisch
Name[el]=Βιρμανικά
Name[en_GB]=Burmese
Name[eo]=Birma
Name[es]=Burmés
Name[et]=Birma
Name[eu]=Birmaniera
Name[fa]=برمس
Name[fi]=Burma
Name[fr]=Birman
Name[fy]=Burmeesk
Name[ga]=Burmais
Name[gl]=Burmés
Name[gu]=બર્મીઝ
Name[he]=בורמזית
Name[hi]=बर्मी
Name[hne]=बर्मी
Name[hr]=Burmanski
Name[hsb]=Burmesce
Name[hu]=Burmai
Name[ia]=Burmese
Name[id]=Burmese
Name[is]=Búrmenska
Name[it]=Birmano
Name[ja]=ビルマ語
Name[kk]=Бирманша
Name[km]=ភូមា
Name[kn]=ಬರ್ಮೀಸ್
Name[ko]=버마어
Name[ku]=Burmayî
Name[lb]=Burmesesch
Name[lt]=Birmiečių
Name[lv]=Birmiešu
Name[mai]=बर्मीज
Name[mk]=Бурмански
Name[ml]=ബര്‍മ്മീസ്
Name[mr]=बर्मी
Name[ms]=Burma
Name[nb]=Burmesisk
Name[nds]=Burmeesch
Name[ne]=बर्मेली
Name[nl]=Burmees
Name[nn]=Burmesisk
Name[oc]=Birman
Name[or]=Burmese
Name[pa]=ਬੁਰਮੀਸੀ
Name[pl]=Birmański
Name[ps]=برمي
Name[pt]=Birmanês
Name[pt_BR]=Birmanês
Name[ro]=Burmeză
Name[ru]=Бирманский
Name[se]=Burmagiella
Name[si]=බුරුම
Name[sk]=Barmčina
Name[sl]=burmansko
Name[sq]=Burmeze
Name[sr]=бурмански
Name[sr@ijekavian]=бурмански
Name[sr@ijekavianlatin]=burmanski
Name[sr@latin]=burmanski
Name[sv]=Burmesiska
Name[ta]=பர்மியம்
Name[te]=బర్మీస్
Name[tg]=Бурмезӣ
Name[th]=ภาษาพม่า
Name[tr]=Burmese
Name[tt]=Бирманский
Name[ug]=بىرمىچە
Name[uk]=Бірманська
Name[uz]=Burmezcha
Name[uz@cyrillic]=Бурмезча
Name[vi]=Miến Điện
Name[wa]=Birman
Name[xh]=Burmese
Name[x-test]=xxBurmesexx
Name[zh_CN]=缅甸语
Name[zh_HK]=緬甸語
Name[zh_TW]=緬甸語
[na]
Name=Nauru
Name[af]=Nauru
Name[ar]=ناورو
Name[as]=নাউৰু
Name[ast]=Nauru
Name[be]=Наурскі
Name[be@latin]=Nauru
Name[bg]=Науру
Name[bn]=নাউরু
Name[bn_IN]=নাউরু
Name[br]=Naurueg
Name[bs]=Nauru
Name[ca]=Nauruà
Name[ca@valencia]=Nauruà
Name[cs]=Nauru
Name[csb]=Nauru
Name[cy]=Nauru
Name[da]=Nauru
Name[de]=Nauruisch
Name[el]=Ναουρού
Name[en_GB]=Nauru
Name[eo]=Naura
Name[es]=Nauru
Name[et]=Nauru
Name[eu]=Nauruera
Name[fa]=نائورو
Name[fi]=Nauru
Name[fr]=Nauruan
Name[fy]=Naurûaansk
Name[ga]=Nárúis
Name[gl]=Nauru
Name[gu]=નાઉરુ
Name[he]=נאורו
Name[hi]=नौरू
Name[hne]=नौरू
Name[hr]=Nauru
Name[hsb]=Nauru
Name[hu]=Nauru
Name[ia]=Nauru
Name[id]=Nauru
Name[is]=Nauru
Name[it]=Nauruano
Name[ja]=ナウル語
Name[kk]=Науру
Name[km]=ណូរុ
Name[kn]=ನೌರು
Name[ko]=나우루어
Name[ku]=Naûrû
Name[lb]=Nauruesch
Name[lt]=Nauru
Name[lv]=Nauruiešu
Name[mai]=नौरू
Name[mk]=Науру
Name[ml]=നൌറു
Name[mr]=नौरु
Name[ms]=Nauru
Name[nb]=Nauru
Name[nds]=Nauru
Name[ne]=नाउरू
Name[nl]=Nauru
Name[nn]=Nauru
Name[oc]=Nauru
Name[or]=Nauru
Name[pa]=ਨਾਉਰੂ
Name[pl]=Nauru
Name[ps]=ناورو
Name[pt]=Nauru
Name[pt_BR]=Nauruano
Name[ro]=Naură
Name[ru]=Науру
Name[se]=Naurugiella
Name[si]=නවුරු
Name[sk]=Nauruština
Name[sl]=nauru
Name[sq]=Nauru
Name[sr]=науру
Name[sr@ijekavian]=науру
Name[sr@ijekavianlatin]=nauru
Name[sr@latin]=nauru
Name[sv]=Nauru
Name[ta]=நவுரு
Name[te]=నౌరు
Name[tg]=Науру
Name[th]=ภาษานาอุรุ
Name[tr]=Nauru
Name[tt]=Науру
Name[ug]=ناۋرۇ
Name[uk]=Науру
Name[uz]=Nauru
Name[uz@cyrillic]=Науру
Name[vi]=Nauru
Name[wa]=Nawouro
Name[xh]=Nauru
Name[x-test]=xxNauruxx
Name[zh_CN]=瑙鲁语
Name[zh_HK]=諾魯語
Name[zh_TW]=諾魯語
[nb]
Name=Norwegian Bokmål
Name[af]=Noorweegs Bokmål
Name[ar]=نرويجية بوكمال
Name[as]=নোৰ্ৱেইয়ান বকমাল
Name[ast]=Noruegu Bokmål
Name[be]=Нарвежская (бокмаль)
Name[be@latin]=Narveskaja (Bokmål)
Name[bg]=Норвежки (букмол)
Name[bn]=নরওয়েজীয় বোকমাল
Name[bn_IN]=নরওয়েজিয়ান বোকমাল
Name[br]=Norvegeg Bokmål
Name[bs]=Norveški(Bukmol)
Name[ca]=Noruec Bokmal
Name[ca@valencia]=Noruec Bokmal
Name[cs]=Norský (Bokmål)
Name[csb]=Norwesczi Bokmål
Name[cy]=Bokmal Norwyeg
Name[da]=Norsk bokmål
Name[de]=Norwegisch (Bokmål)
Name[el]=Νορβηγικά Bokmål
Name[en_GB]=Norwegian Bokmål
Name[eo]=Norvega (Bokmal)
Name[es]=Noruego Bokmål
Name[et]=Norra bokmål
Name[eu]=Norvegiera (Bokmål)
Name[fa]=نروژی بوکمال
Name[fi]=Norjan bokmål
Name[fr]=Norvégien Bokmaal
Name[fy]=Noarsk, Bokmål
Name[ga]=Ioruais (Bokmål)
Name[gl]=Noruegués Bokmål
Name[gu]=નોર્વેયન બોકમલ
Name[he]=נורבגית Bokmål
Name[hi]=नार्वेजियाई बोकमाल
Name[hne]=नार्वेजियाई बोकमाल
Name[hr]=Norveški Bokmål
Name[hsb]=Norwegsce (Bokmål)
Name[hu]=Norvég (bokmal)
Name[ia]=Norvegiano Bokmål
Name[id]=Norwegian Bokmål
Name[is]=Norska (bókmál)
Name[it]=Norvegese bokmål
Name[ja]=ノルウェー語 (ブークモール)
Name[kk]=Норвег букмалша
Name[km]=ន័រវែស បុកម៉ាល់
Name[kn]=ನಾರ್ವೆಯ ಬೋಕ್ಮಾಲ್
Name[ko]=노르웨이어 (복말)
Name[ku]=Norwêcî Bokmal
Name[lb]=Norwegesch (Bokmål)
Name[lt]=Norvegų Bokmål
Name[lv]=Norvēģu (bukmols)
Name[mai]=नार्वेजियाई बोकमाल
Name[mk]=Норвешки Bokmål
Name[ml]=നോര്‍വ്വീജിയന്‍ ബോക്മാല്
Name[mr]=नार्वेजियन बोकमल
Name[ms]=Norwegian Bokmål
Name[nb]=Norsk (bokmål)
Name[nds]=Norweegsch (Bokmål)
Name[ne]=नर्वेली बोकमल
Name[nl]=Noors Bokmål
Name[nn]=Norsk (bokmål)
Name[oc]=Norvegian Bokmål
Name[or]=Norwegian Bokmål
Name[pa]=ਨੋਰਵਿਗੀਆਨ ਬੋਕਮਾਲ
Name[pl]=Norweski Bokmaal
Name[ps]=ناروېجني بوکمل
Name[pt]=Norueguês Bokmål
Name[pt_BR]=Dano-norueguês
Name[ro]=Norvegiană Bokmål
Name[ru]=Норвежский (литературный)
Name[se]=Girjedárogiella
Name[si]=නෝවීජියානු බොක්මැල්
Name[sk]=Nórčina (Bokmål)
Name[sl]=norveško (bokmaal)
Name[sq]=Norvegjeze Bokmål
Name[sr]=норвешки (књижевни)
Name[sr@ijekavian]=норвешки (књижевни)
Name[sr@ijekavianlatin]=norveški (književni)
Name[sr@latin]=norveški (književni)
Name[sv]=Norskt bokmål
Name[ta]=நார்வீஜியன் பொக்மால்
Name[te]=నార్వీజియన్ బోక్మాల్
Name[tg]=Норвегиягии Боқмалӣ
Name[th]=ภาษานอร์เวย์ (บ็อคมัล)
Name[tr]=Norveççe Bokmål
Name[tt]=Норвегия (әдәби)
Name[ug]=نورۋېگىيە بوكمالچە
Name[uk]=Норвезька (Bokmål)
Name[uz]=Norvegcha (Bokmaal)
Name[uz@cyrillic]=Норвегча (Бокмаал)
Name[vi]=Na Uy - Bokmål
Name[wa]=Norvedjin (Bokmål)
Name[xh]=Norwegian Bokmål
Name[x-test]=xxNorwegian Bokmålxx
Name[zh_CN]=挪威语(博克马尔语)
Name[zh_TW]=挪威 Bokmål
[nd]
Name=Ndebele, North
Name[af]=Ndebele, Noord
Name[ar]=نديبيلية شمالية
Name[as]=ডেবেল, নৰ্থ
Name[ast]=Ndebele, Norte
Name[be]=Ндэбель (поўнач)
Name[be@latin]=Ndebele (poŭnač)
Name[bg]=Северен Ндебеле
Name[bn]=ন্‌দেবেলে, উত্তর
Name[bn_IN]=এন্ডেবিলি, উত্তর
Name[br]=Ndebele, Norzh
Name[bs]=Ndebele, sjeverni
Name[ca]=Ndebele Nord
Name[ca@valencia]=Ndebele Nord
Name[cs]=Ndebele, Severní
Name[csb]=Ndebele, Norda
Name[cy]=Ndebele, Gogledd
Name[da]=Ndebele, nord
Name[de]=Ndebele (nördliches)
Name[el]=Ndebele, North
Name[en_GB]=Ndebele, North
Name[eo]=Ndebela, norda
Name[es]=Ndebele del norte
Name[et]=Põhja-ndebele
Name[eu]=Ndebelera (iparraldekoa)
Name[fa]=اندبل شمالی
Name[fi]=Pohjois-ndebele
Name[fr]=Ndébélé du Nord
Name[fy]=Ndebele, Noard
Name[ga]=Ndebele Thuaidh
Name[gl]=Ndebele do norte
Name[gu]=ડેબેલે, ઉત્તરી
Name[he]=נדבלה צפונית
Name[hi]=नेदेबेले, उत्तर
Name[hne]=नेदेबेले, उत्तर
Name[hr]=Ndebele, Sjeverni
Name[hsb]=Ndebele, Sewjerne
Name[hu]=Ndebele (északi)
Name[ia]=Ndebele Septentrional
Name[id]=Ndebele, Utara
Name[is]=Ndebele, norður
Name[it]=Ndebele settentrionale
Name[ja]=北ンデベレ語
Name[kk]=Солтүсік Ндебеле
Name[km]=អ៊ែនដិបិលិ ​ជើង
Name[kn]=ನೆಬೆಲೇ, ಉತ್ತರ
Name[ko]=북부 은데벨레어
Name[ku]=Ndebele, Bakur
Name[lb]=Ndebele (nördlech)
Name[lt]=Ndebele, Šiaurės
Name[lv]=Ziemeļndebelu
Name[mai]=नेदेबेले, उत्तर
Name[mk]=Ндебеле, северен
Name[ml]=വടക്കന്‍ ഡെബെലി
Name[mr]=एनडेबेले, उत्तर
Name[ms]=Ndebele, North
Name[nb]=Ndebele, Nord
Name[nds]=Ndebele, Noord
Name[ne]=डेबेले, उत्तर
Name[nl]=Ndebele, Noord
Name[nn]=Ndebele, nord
Name[or]=Ndebele, North
Name[pa]=ਨਡੀਬੀਲੀ, ਉੱਤਰੀ
Name[pl]=Ndebele, Północ
Name[pt]=Ndebele do Norte
Name[pt_BR]=Ndebele do norte
Name[ro]=Ndebelă nordică
Name[ru]=Северный ндебеле
Name[se]=Ndebelegiella, davvi
Name[sk]=Ndebelčina, severná
Name[sl]=ndebele, severno
Name[sq]=Ndebele, Veriore
Name[sr]=ндебеле, северни
Name[sr@ijekavian]=ндебеле, сјеверни
Name[sr@ijekavianlatin]=ndebele, sjeverni
Name[sr@latin]=ndebele, severni
Name[sv]=Nordndebele
Name[ta]=வட டெபெலே
Name[te]=నెబేలె, ఉత్తర
Name[tg]=Ндебелии Шимолӣ
Name[th]=ภาษาอึนเดเบเล ตอนเหนือ
Name[tr]=Ndebele, North
Name[tt]=Ндбелеча (Төньяк)
Name[ug]=ندىبېلې، شىمالىي
Name[uk]=Ндебелє, Північна
Name[uz]=Ndebele, Shimol
Name[uz@cyrillic]=Ндебеле, Шимол
Name[vi]=Ndebele, Bắc
Name[wa]=Ndebele (bijhe)
Name[xh]=Isindebele, Emntla
Name[x-test]=xxNdebele, Northxx
Name[zh_CN]=恩德贝莱语(北部)
Name[zh_HK]=Ndebele語,北部
Name[zh_TW]=Ndebele語,北部
[nds]
Name=Low Saxon
Name[af]=Lae Saxon
Name[ar]=ساكسونية منخفضة
Name[as]=লো' ছেক্সন
Name[ast]=Saxón baxu
Name[be]=Ніжнесаксонская
Name[be@latin]=Nižnie-saksonskaja
Name[bg]=Долносаксонски
Name[bn]=নিম্ন স্যাক্সন
Name[bn_IN]=লো স্যাক্সন
Name[br]=Saozeg izel
Name[bs]=Donjosaksonski
Name[ca]=Baix saxó
Name[ca@valencia]=Baix saxó
Name[cs]=Dolnosaský
Name[csb]=Dolnosaksońsczi
Name[cy]=Sacsoneg Isel
Name[da]=Nedersaksisk
Name[de]=Niederdeutsch
Name[el]=Κάτω Σαξωνικά
Name[en_GB]=Low Saxon
Name[eo]=Malalta saksa
Name[es]=Bajo sajón
Name[et]=Alamsaksi
Name[eu]=Beheko Sajoniera
Name[fa]=ساکسونی سفلی
Name[fi]=Alasaksa
Name[fr]=Bas saxon
Name[fy]=Neder Saksysk
Name[ga]=Sacsainis Íochtarach
Name[gl]=Baixo saxón
Name[gu]=નીચું સેક્સોન
Name[he]=סקסונית תחתית
Name[hi]=लो सेक्सन
Name[hne]=लो सेक्सन
Name[hr]=Donjosaksonski
Name[hsb]=Delnjosaksce
Name[hu]=Alsószász
Name[ia]=Basso Saxone
Name[id]=Low Saxon
Name[is]=Low Saxon
Name[it]=Basso sassone
Name[ja]=低ザクセン語
Name[kk]=Төмен саксонша
Name[km]=ឡូសាក់សុង
Name[kn]=ಕೆಳಗಿನ ಸಾಕ್ಸನ್
Name[ko]=저지대 색슨어
Name[ku]=Saksoniya Jêrîn
Name[lb]=Niddersächsesch
Name[lt]=Žemutinių saksonų
Name[lv]=Lejasvācu
Name[mai]=लो सेक्सन
Name[mk]=Долносаксонски
Name[ml]=ലോ സാക്സന്‍
Name[mr]=लो सॅक्सन
Name[ms]=Low Saxon
Name[nb]=Nedersaksisk
Name[nds]=Plattdüütsch
Name[ne]=लो साक्सोन
Name[nl]=Nedersaksisch
Name[nn]=Lågtysk
Name[or]=Low Saxon
Name[pa]=ਲੋਅ ਸਾਕੋਨ
Name[pl]=Dolnosaksoński
Name[ps]=ښکته سېګسن
Name[pt]=Baixo-Saxão
Name[pt_BR]=Baixo Saxão
Name[ro]=Saxona de Jos
Name[ru]=Нижнесаксонский
Name[se]=Vuolil Sáksonagiella
Name[si]=ලෝ සැක්සන්
Name[sk]=Dolná saština
Name[sl]=spodnjesaško
Name[sq]=Saksone e Ulët
Name[sr]=доњосаксонски
Name[sr@ijekavian]=доњосаксонски
Name[sr@ijekavianlatin]=donjosaksonski
Name[sr@latin]=donjosaksonski
Name[sv]=Lågsaxiska
Name[ta]=லோ சாக்ஸான்
Name[te]=లో సాక్సన్
Name[tg]=Саксонии Поёнӣ
Name[th]=ภาษาเยอรมัน ระดับล่าง
Name[tr]=Aşağı Saksonca
Name[tt]=Аскы Саксон
Name[ug]=تۆۋەن ساكسونچە
Name[uk]=Нижньосаксонська
Name[uz]=Past Saksoncha
Name[uz@cyrillic]=Паст Саксонча
Name[wa]=Bas sacson
Name[x-test]=xxLow Saxonxx
Name[zh_CN]=下撒克逊语
Name[zh_TW]=薩克遜語
[ne]
Name=Nepali
Name[af]=Nepalees
Name[ar]=نيبالية
Name[as]=নেপালী
Name[ast]=Nepalinu
Name[be]=Непальская
Name[be@latin]=Nepalskaja
Name[bg]=Непали
Name[bn]=নেপালী
Name[bn_IN]=নেপালি
Name[br]=Nepaleg
Name[bs]=Nepalski
Name[ca]=Nepalès
Name[ca@valencia]=Nepalés
Name[cs]=Nepálský
Name[csb]=Nepalsczi
Name[cy]=Nepali
Name[da]=Nepali
Name[de]=Nepali
Name[el]=Nepali
Name[en_GB]=Nepali
Name[eo]=Nepala
Name[es]=Nepalí
Name[et]=Nepaali
Name[eu]=Nepalera
Name[fa]=نپالی
Name[fi]=Nepali
Name[fr]=Népalais
Name[fy]=Nepaleesk
Name[ga]=Neipealais
Name[gl]=Nepalí
Name[gu]=નેપાળી
Name[he]=נפאלית
Name[hi]=नेपाली
Name[hne]=नेपाली
Name[hr]=Nepalski
Name[hsb]=Nepalesce
Name[hu]=Nepáli
Name[ia]=Nepali
Name[id]=Nepali
Name[is]=Nepali
Name[it]=Nepalese
Name[ja]=ネパール語
Name[kk]=Непалша
Name[km]=នេប៉ាល់
Name[kn]=ನೇಪಾಲಿ
Name[ko]=네팔어
Name[ku]=Nepalî
Name[lb]=Nepalesesch
Name[lt]=Nepaliečių
Name[lv]=Nepāliešu
Name[mai]=नेपाली
Name[mk]=Непалски
Name[ml]=നേപ്പാളി
Name[mr]=नेपाली
Name[ms]=Nepali
Name[nb]=Nepali
Name[nds]=Nepaleesch
Name[ne]=नेपाली
Name[nl]=Nepalees
Name[nn]=Nepali
Name[oc]=Nepali
Name[or]=Nepali
Name[pa]=ਨੇਪਾਲੀ
Name[pl]=Nepalski
Name[ps]=نيپالي
Name[pt]=Nepalês
Name[pt_BR]=Nepalês
Name[ro]=Nepaleză
Name[ru]=Непальский
Name[se]=Nepálagiella
Name[si]=නේපාල
Name[sk]=Nepálčina
Name[sl]=nepalsko
Name[sq]=Nepaleze
Name[sr]=непалски
Name[sr@ijekavian]=непалски
Name[sr@ijekavianlatin]=nepalski
Name[sr@latin]=nepalski
Name[sv]=Nepali
Name[ta]=நேபாளி
Name[te]=నేపాలి
Name[tg]=Непалӣ
Name[th]=ภาษาเนปาล
Name[tr]=Nepalce
Name[tt]=Непал
Name[ug]=نېپالچە
Name[uk]=Непальська
Name[uz]=Nepalcha
Name[uz@cyrillic]=Непалча
Name[vi]=Nê pan
Name[wa]=Nepalès
Name[xh]=Nepali
Name[x-test]=xxNepalixx
Name[zh_CN]=尼泊尔语
Name[zh_HK]=尼泊爾語
Name[zh_TW]=尼泊爾語
[ng]
Name=Ndonga
Name[af]=Ndonga
Name[ar]=ندونغا
Name[as]=ডঙ্গা
Name[ast]=Ndonga
Name[be]=Ндонга
Name[be@latin]=Ndonga
Name[bg]=Ндонга
Name[bn]=নডোঙ্গা
Name[bn_IN]=এনডোঙ্গা
Name[br]=Ndonga
Name[bs]=Ndonga
Name[ca]=Ndonga
Name[ca@valencia]=Ndonga
Name[cs]=Ndonga
Name[csb]=Ndonga
Name[cy]=Ndonga
Name[da]=Ndonga
Name[de]=Oshivambo
Name[el]=Ndonga
Name[en_GB]=Ndonga
Name[eo]=Ndonga
Name[es]=Ndonga
Name[et]=Ndonga
Name[eu]=Ndonga
Name[fa]=اندونگا
Name[fi]=Ndonga
Name[fr]=Ndonga
Name[fy]=Ndonga
Name[ga]=Ndongais
Name[gl]=Ndonga
Name[gu]=ડોંગા
Name[he]=נדונגה
Name[hi]=नदोन्गा
Name[hne]=नदोन्गा
Name[hr]=Ndonga
Name[hsb]=Ndonga
Name[hu]=Ndonga
Name[ia]=Ndonga
Name[id]=Ndonga
Name[is]=Ndonga
Name[it]=Ndonga
Name[ja]=ンドゥンガ語
Name[kk]=Ндонга
Name[km]=អ៊ែនដុងហ្កា
Name[kn]=ಡೋಂಗಾ
Name[ko]=은동가어
Name[ku]=Ndonga
Name[lb]=Ndonga
Name[lt]=Ndonga
Name[lv]=Ndongu
Name[mai]=नदोन्गा
Name[mk]=Ндонга
Name[ml]=ഡോങ്കാ
Name[mr]=नदोन्गा
Name[ms]=Ndonga
Name[nb]=Ndonga
Name[nds]=Ndonga
Name[ne]=डोङ्गा
Name[nl]=Ndonga
Name[nn]=Ndonga
Name[oc]=Ndonga
Name[or]=Ndonga
Name[pa]=ਨਡੋਨਗਾ
Name[pl]=Ndonga
Name[pt]=Ndonga
Name[pt_BR]=Ndonga
Name[ro]=Ndongă
Name[ru]=Ндонга
Name[se]=Ndongagiella
Name[si]=එන්ඩොංගා
Name[sk]=Ndonga
Name[sl]=ndonga
Name[sq]=Ndonga
Name[sr]=ндонга
Name[sr@ijekavian]=ндонга
Name[sr@ijekavianlatin]=ndonga
Name[sr@latin]=ndonga
Name[sv]=Ndonga
Name[ta]=டொங்கா
Name[te]=డొంగా
Name[tg]=Ндонга
Name[th]=ภาษาอึนดองกา
Name[tr]=Ndonga
Name[tt]=Ндонга
Name[ug]=ندونگا
Name[uk]=Ндонга
Name[uz]=Ndonga
Name[uz@cyrillic]=Ндонга
Name[vi]=Ndonga
Name[wa]=Ndonga
Name[xh]=Ndonga
Name[x-test]=xxNdongaxx
Name[zh_CN]=恩东加语
Name[zh_HK]=Ndonga語
Name[zh_TW]=Ndonga語
[nl]
Name=Dutch
Name[af]=Nederlands
Name[ar]=هولندية
Name[as]=ডাচ
Name[ast]=Holandés
Name[be]=Галандская
Name[be@latin]=Halandzkaja
Name[bg]=Холандски
Name[bn]=ওলন্দাজ
Name[bn_IN]=ডাচ
Name[br]=Nederlandeg
Name[bs]=Holandski
Name[ca]=Holandès
Name[ca@valencia]=Holandés
Name[cs]=Holandský
Name[csb]=Hòlandzczi
Name[cy]=Iseldireg
Name[da]=Hollandsk
Name[de]=Niederländisch
Name[el]=Ολλανδικά
Name[en_GB]=Dutch
Name[eo]=Nederlanda
Name[es]=Holandés
Name[et]=Hollandi
Name[eu]=Nederlandera
Name[fa]=هلندی
Name[fi]=Hollanti
Name[fr]=Hollandais
Name[fy]=Nederlânsk
Name[ga]=Ollainnis
Name[gl]=Holandés
Name[gu]=ડચ
Name[he]=הולנדית
Name[hi]=डच
Name[hne]=डच
Name[hr]=Nizozemski
Name[hsb]=Nižozemsce
Name[hu]=Holland
Name[ia]=Hollandese
Name[id]=Belanda
Name[is]=Hollenska
Name[it]=Nederlandese
Name[ja]=オランダ語
Name[kk]=Нидреландша
Name[km]=ហុល្លង់
Name[kn]=ಡಚ್
Name[ko]=네덜란드어
Name[ku]=Holendî
Name[lb]=Hollännesch
Name[lt]=Olandų
Name[lv]=Holandiešu
Name[mai]=डच
Name[mk]=Холандски
Name[ml]=ഡച്ച്
Name[mr]=डच
Name[ms]=Belanda
Name[nb]=Nederlandsk
Name[nds]=Nedderlannsch
Name[ne]=डच
Name[nl]=Nederlands
Name[nn]=Nederlandsk
Name[oc]=Olandés
Name[or]=Dutch
Name[pa]=ਡੱਚ
Name[pl]=Holenderski
Name[ps]=ډچ
Name[pt]=Holandês
Name[pt_BR]=Holandês
Name[ro]=Olandeză
Name[ru]=Голландский
Name[se]=Hollánddagiella
Name[si]=ඩච්
Name[sk]=Holandčina
Name[sl]=nizozemsko
Name[sq]=Hollandeze
Name[sr]=холандски
Name[sr@ijekavian]=холандски
Name[sr@ijekavianlatin]=holandski
Name[sr@latin]=holandski
Name[sv]=Holländska
Name[ta]=டச்சு
Name[te]=డచ్
Name[tg]=Олмонӣ
Name[th]=ภาษาดัทช์
Name[tr]=Flamanca
Name[tt]=Голланд
Name[ug]=گوللاندىيەچە
Name[uk]=Голландська
Name[uz]=Gollandcha
Name[uz@cyrillic]=Голландча
Name[vi]=Hà Lan
Name[wa]=Neyerlandès
Name[xh]=Isidatshi
Name[x-test]=xxDutchxx
Name[zh_CN]=荷兰语
Name[zh_HK]=荷蘭語
Name[zh_TW]=荷蘭語
[nn]
Name=Norwegian Nynorsk
Name[af]=Noörweegse Nynorsk
Name[ar]=نرويجية نينورسك
Name[as]=ন'ৰ্ৱেইয়ান নিন'ৰ্স্ক
Name[ast]=Nynorsk de Noruega
Name[be]=Нарвежская (нюнорск)
Name[be@latin]=Narveskaja (Nynorsk)
Name[bg]=Норвежки (нюнорск)
Name[bn]=নরওয়েজীয় নাইনর্স্ক
Name[bn_IN]=নরওয়েজিয়ান নিনোরস্ক
Name[br]=Norvegeg Nynorsk
Name[bs]=Norveški (Ninorsk)
Name[ca]=Noruec Nynorsk
Name[ca@valencia]=Noruec Nynorsk
Name[cs]=Norský (Nynorsk)
Name[csb]=Norwesczi Nynorsk
Name[cy]=Nynorsk Norwyeg
Name[da]=Nynorsk
Name[de]=Norwegisch (Nynorsk)
Name[el]=Νορβηγικά Nynorsk
Name[en_GB]=Norwegian Nynorsk
Name[eo]=Norvega (Nynorsk)
Name[es]=Noruego Nynorsk
Name[et]=Norra nynorsk
Name[eu]=Norvegiera (Nynorsk)
Name[fa]=نرس جدید نروژی
Name[fi]=Norjan nynorsk
Name[fr]=Norvégien Nynorsk
Name[fy]=Noarsk, Nynorsk
Name[ga]=Ioruais (Nynorsk)
Name[gl]=Noruegués Nynorsk
Name[gu]=નોર્વેયન નોર્સક
Name[he]=נורבגית Nynorsk
Name[hi]=नारवेजियाई नायनोर्सक
Name[hne]=नारवेजियाई नायनोर्सक
Name[hr]=Norveški Nynorsk
Name[hsb]=Norwegsce (Nynorsk)
Name[hu]=Norvég (nynorsk)
Name[ia]=Norvegiano Nynorsk
Name[id]=Norwegian Nynorsk
Name[is]=Norska (nýnorska)
Name[it]=Norvegese nynorsk
Name[ja]=ノルウェー語 (ニーノシュク)
Name[kk]=Новег нунорскша
Name[km]=ន័រវែស នីនូស
Name[kn]=ನಾರ್ವೆಯ ನೈನೋರ್ಕ್
Name[ko]=노르웨이어 (뉘노르스크)
Name[ku]=Norwêcî Nynorsk
Name[lb]=Norwegesch (Nynorsk)
Name[lt]=Norvegų Nynorsk
Name[lv]=Jaunnorvēģu
Name[mai]=नार्वेजियन न्योनोर्स्क
Name[mk]=Норвешки Nynorsk
Name[ml]=നോര്‍വ്വീജിയന്‍ നെയ്നോര്‍സ്ക്
Name[mr]=नारवेजियाई नायनोर्सक
Name[ms]=Norwegian Nynorsk
Name[nb]=Norsk (nynorsk)
Name[nds]=Norweegsch Nynorsk
Name[ne]=नर्वेली नाइनोर्सक
Name[nl]=Noors, Nynorsk
Name[nn]=Norsk (nynorsk)
Name[oc]=Nòrvegian (Nynorsk)
Name[or]=Norwegian Nynorsk
Name[pa]=ਨੋਰਵਿਗੀਆਨ ਨਯਾਨੋਰਸਕ
Name[pl]=Norweski Nynorsk
Name[ps]=ناروېجني نېنورسک
Name[pt]=Norueguês Nynorsk
Name[pt_BR]=Novo Norueguês
Name[ro]=Norvegiană Nynorsk
Name[ru]=Норвежский (нюнорск)
Name[se]=Ođđadárogiella
Name[sk]=Nórčina (Nynorsk)
Name[sl]=norveško (nyorsk)
Name[sq]=Norvegjeze Nynorsk
Name[sr]=новонорвешки
Name[sr@ijekavian]=новонорвешки
Name[sr@ijekavianlatin]=novonorveški
Name[sr@latin]=novonorveški
Name[sv]=Nynorska
Name[ta]=நார்வீஜியன் (நையோர்ஸ்க்)
Name[te]=నార్వీజియన్ న్యోర్స్క్
Name[tg]=Норвегиягии Нюнорсӣ
Name[th]=ภาษานอร์เวย์ (นูนอร์สคฺ)
Name[tr]=Norveççe Nynorsk
Name[tt]=Норвегия (Нинорск)
Name[ug]=نورۋېگىيە ناينوركس
Name[uk]=Норвезька (Nynorsk)
Name[uz]=Norvegiyacha (Nynorsk)
Name[uz@cyrillic]=Норвегияча (Нйнорск)
Name[vi]=Na Uy - Nynorsk
Name[wa]=Noû-Norvedjyin (Nynorsk)
Name[xh]=Norwegian Nynorsk
Name[x-test]=xxNorwegian Nynorskxx
Name[zh_CN]=挪威语(尼诺斯克语)
Name[zh_HK]=挪威 Nynorsk 語
Name[zh_TW]=挪威 Nynorsk 語
[nr]
Name=Ndebele, South
Name[af]=Ndebele, Suid
Name[ar]=نديبيلي جنوبية
Name[as]=ডেবেলে,ছাউথ
Name[ast]=Ndebele, Sur
Name[be]=Ндэбель (поўдзень)
Name[be@latin]=Ndebele (poŭdzień)
Name[bg]=Южен Ндебеле
Name[bn]=ন্‌দেবেলে, দক্ষিণ
Name[bn_IN]=এন্ডেবিলি, দক্ষিণ
Name[br]=Ndebele, Su
Name[bs]=Ndebele, južni
Name[ca]=Ndebele Sud
Name[ca@valencia]=Ndebele Sud
Name[cs]=Ndebele, Jižní
Name[csb]=Ndebele, Pôłnie
Name[cy]=Ndebele, De
Name[da]=Ndebele, syd
Name[de]=Ndebele (südliches)
Name[el]=Ndebele, South
Name[en_GB]=Ndebele, South
Name[eo]=Ndebela, suda
Name[es]=Ndebele del sur
Name[et]=Lõuna-ndebele
Name[eu]=Ndebelera (hegoaldekoa)
Name[fa]=اندبل جنوبی
Name[fi]=Etelä-ndebele
Name[fr]=Ndébélé du Sud
Name[fy]=Ndebele, Súd
Name[ga]=Ndebele Theas
Name[gl]=Ndebele do sur
Name[gu]=ડેબેલે, દક્ષિણી
Name[he]=נדבלה דרומית
Name[hi]=नेदेबेले,दक्षिण
Name[hne]=नेदेबेले,दक्छिन
Name[hr]=Ndebele, Južni
Name[hsb]=Ndebele, Južne
Name[hu]=Ndebele (déli)
Name[ia]=Ndebele Meridional
Name[id]=Ndebele, Utara
Name[is]=Ndebele, suður
Name[it]=Ndebele meridionale
Name[ja]=南ンデベレ語
Name[kk]=Оңтүстік ндебеле
Name[km]=អ៊ែនដិបិលិ ត្បូង
Name[kn]=ನೆಬೆಲೇ, ದಕ್ಷಿಣ
Name[ko]=남부 은데벨레어
Name[ku]=Ndebele, Başûr
Name[lb]=Ndebele (südlecht)
Name[lt]=Ndebele, Pietų
Name[lv]=Dienvidndebelu
Name[mai]=न्डेबेले, दक्षिण
Name[mk]=Ндебеле, јужен
Name[ml]=തെക്കന്‍ ഡെബെലി
Name[mr]=एनडेबेले, दक्षिण
Name[ms]=Ndebele, South
Name[nb]=Ndebele, Sør
Name[nds]=Ndebele, Sööd
Name[ne]=डेबेले, दक्षिण
Name[nl]=Ndebele, Zuid
Name[nn]=Ndebele, sør
Name[or]=Ndebele, South
Name[pa]=ਨਡੀਬੀਲੀ, ਦੱਖਣੀ
Name[pl]=Ndebele, Południe
Name[pt]=Ndebele do Sul
Name[pt_BR]=Ndebele do Sul
Name[ro]=Ndebelă sudică
Name[ru]=Южный ндебеле
Name[se]=Ndebelegiella, lulli
Name[si]=එන්බෙලෙ, දකුණු
Name[sk]=Ndebelčina, južná
Name[sl]=ndebele, južno
Name[sq]=Ndebele, Jugore
Name[sr]=ндебеле, јужни
Name[sr@ijekavian]=ндебеле, јужни
Name[sr@ijekavianlatin]=ndebele, južni
Name[sr@latin]=ndebele, južni
Name[sv]=Sydndebele
Name[ta]=தென் டெபெலே
Name[te]=నెబేలె, దక్షిణ
Name[tg]=Ндебелии Ҷанубӣ
Name[th]=ภาษาอึนเดเบเล ตอนใต้
Name[tr]=Ndebele, South
Name[tt]=Ндебеле (Южная Африка)
Name[ug]=ندېبېلې، جەنۇبى
Name[uk]=Ндебелє, Південна
Name[uz]=Ndebele, Janub
Name[uz@cyrillic]=Ндебеле, Жануб
Name[vi]=Ndebele, Nam
Name[wa]=Ndebele (nonne)
Name[xh]=Isindebele, Emazantsi
Name[x-test]=xxNdebele, Southxx
Name[zh_CN]=恩德贝莱语(南部)
Name[zh_HK]=Ndebele語,南部
Name[zh_TW]=Ndebele語,南部
[nso]
Name=Northern Sotho
Name[af]=Noord Sotho
Name[ar]=سوتو شمالية
Name[as]=নৰ্দাৰ্ণ ছ'থ'
Name[ast]=Sotho del Norte
Name[be]=Паўночнае Сота
Name[be@latin]=Paŭnočnaje Sota
Name[bg]=Северен Сото
Name[bn]=উত্তর সোথো
Name[bn_IN]=উত্তর সোথো
Name[br]=Soto, Norzh
Name[bs]=Soto(sjeverni)
Name[ca]=Sotho nord
Name[ca@valencia]=Sotho nord
Name[cs]=Severní Sotho
Name[csb]=Nordowé Sotho
Name[cy]=Sotho'r Gogledd
Name[da]=Nord Sotho
Name[de]=Nord-Sotho
Name[el]=Northern Sotho
Name[en_GB]=Northern Sotho
Name[eo]=Sota, norda
Name[es]=Sotho del norte
Name[et]=Põhja-sotho
Name[eu]=Sothoera (iparraldekoa)
Name[fa]=سوتوی شمالی
Name[fi]=Pohjoissotho
Name[fr]=Sotho du Nord
Name[fy]=Noard Sotho
Name[ga]=Seipidis
Name[gl]=Sotho do norte
Name[gu]=ઉત્તરી સોથો
Name[he]=סותו צפונית
Name[hi]=उत्तरी सोथो
Name[hne]=उत्तरी सोथो
Name[hr]=Sjeverni Sotho
Name[hsb]=Sewjerne Sotho
Name[hu]=Északi sotho
Name[ia]=Sotho Septentrional
Name[id]=Sotho Bagian Utara
Name[is]=Norður Sotho
Name[it]=Sesotho del nord
Name[ja]=北ソト語
Name[kk]=Солтүстік сото
Name[km]=សូធូ​ខាង​ជើង
Name[kn]=ಉತ್ತರ ಸೋಥೋ
Name[ko]=북부 소토어
Name[ku]=Sothoya Bakûrî
Name[lb]=Nördlecht Sotho
Name[lt]=Šiaurės Sotho
Name[lv]=Ziemeļsotu
Name[mai]=उत्तरी सोथो
Name[mk]=Северен Сото
Name[ml]=വടക്കന്‍ സോതോ
Name[mr]=उत्तरी सोथो
Name[ms]=Sotho Utara
Name[nb]=Nord-Sotho
Name[nds]=Noord-Sotho
Name[ne]=उत्तरी सोथो
Name[nl]=Noord-Sotho
Name[nn]=Nord-Sotho
Name[oc]=Sotho del Nòrd
Name[or]=Northern Sotho
Name[pa]=ਉੱਤਰੀ ਸੋਥੋ
Name[pl]=Północne Sotho
Name[ps]=شمالي سوټو
Name[pt]=Sotho do Norte
Name[pt_BR]=Sotho setentrional
Name[ro]=Soto nordică
Name[ru]=Северный сото
Name[se]=Davvi-sothogiella
Name[si]=උතුරු සෝලෝ
Name[sk]=Severná sothčina
Name[sl]=severni sotho
Name[sq]=Sotho Veriore
Name[sr]=сото, северни
Name[sr@ijekavian]=сото, сјеверни
Name[sr@ijekavianlatin]=soto, sjeverni
Name[sr@latin]=soto, severni
Name[sv]=Nordsotho
Name[ta]=நார்தன் சோத்தோ
Name[te]=ఉత్తర సొతొ
Name[tg]=Сотоии Шимолӣ
Name[th]=ภาษาโซโธ ตอนเหนือ
Name[tr]=Northern Sotho
Name[tt]=Северный сото
Name[ug]=شىمالىي سوتوچە
Name[uk]=Північне Сото
Name[uz]=Shimoliy Sotocha
Name[uz@cyrillic]=Шимолий Соточа
Name[wa]=Soto (bijhe)
Name[x-test]=xxNorthern Sothoxx
Name[zh_CN]=北索托语
Name[zh_TW]=Northern Sotho
[nv]
Name=Navajo
Name[af]=Navajo
Name[ar]=ناباهو
Name[as]=নাভাজো
Name[ast]=Navaxu
Name[be]=Наваё
Name[be@latin]=Navajo
Name[bg]=Навахо
Name[bn]=নাভায়ো
Name[bn_IN]=নাভাজো
Name[br]=Navajo
Name[bs]=Navaho
Name[ca]=Navaho
Name[ca@valencia]=Navaho
Name[cs]=Navajo
Name[csb]=Navajo
Name[cy]=Navajo
Name[da]=Navajo
Name[de]=Navajo
Name[el]=Νάβαχο
Name[en_GB]=Navajo
Name[eo]=Navaha
Name[es]=Navajo
Name[et]=Navaho
Name[eu]=Navajo
Name[fa]=ناواجو
Name[fi]=Navajo
Name[fr]=Navaho
Name[fy]=Navajo
Name[ga]=Navachóis
Name[gl]=Navaxo
Name[gu]=નાવાજો
Name[he]=נבאחו
Name[hi]=नवाजो
Name[hne]=नवाजो
Name[hr]=Navaho
Name[hsb]=Navajo
Name[hu]=Navajo
Name[ia]=Navajo
Name[id]=Navajo
Name[is]=Navajo
Name[it]=Navajo
Name[ja]=ナバホ語
Name[kk]=Навахо
Name[km]=ណាវាហ្សូ
Name[kn]=ನಾವಾಜೋ
Name[ko]=나바호어
Name[ku]=Navajo
Name[lb]=Navajo
Name[lt]=Navajo
Name[lv]=Navahu
Name[mai]=नवाजो
Name[mk]=Навахо
Name[ml]=നവാജോ
Name[mr]=नवाजो
Name[ms]=Navajo
Name[nb]=Navajo
Name[nds]=Navajo
Name[ne]=नेभाजो
Name[nl]=Navajo
Name[nn]=Navaho
Name[or]=Navajo
Name[pa]=ਨਾਵਾਜੋ
Name[pl]=Navajo
Name[ps]=نواجو
Name[pt]=Navajo
Name[pt_BR]=Navajo
Name[ro]=Navajo
Name[ru]=Навахо
Name[se]=Naváhogiella
Name[si]=නවාජෝ
Name[sk]=Navajo
Name[sl]=navajo
Name[sq]=Navajo
Name[sr]=навахо
Name[sr@ijekavian]=навахо
Name[sr@ijekavianlatin]=navaho
Name[sr@latin]=navaho
Name[sv]=Navajo
Name[ta]=நவாஜோ
Name[te]=నవాజొ
Name[tg]=Наваҷо
Name[th]=ภาษานาวาโฮ
Name[tr]=Navajo
Name[tt]=Навахо
Name[ug]=ناۋاجوچە
Name[uk]=Навахо
Name[uz]=Navajo
Name[uz@cyrillic]=Наважо
Name[vi]=Navajo
Name[wa]=Navaxho
Name[xh]=Navajo
Name[x-test]=xxNavajoxx
Name[zh_CN]=纳瓦霍语
Name[zh_HK]=納瓦伙語
Name[zh_TW]=納瓦伙語
[ny]
Name=Chichewa
Name[af]=Chichewa
Name[ar]=تشيشيوا
Name[as]=ছিছেৱা
Name[ast]=Chichewa
Name[be]=Чычэва
Name[be@latin]=Čyčeva
Name[bg]=Чеуа
Name[bn]=চিচেওয়া
Name[bn_IN]=চিচেওয়া
Name[br]=Chichewa
Name[bs]=Čičeva
Name[ca]=Chewa
Name[ca@valencia]=Chewa
Name[cs]=Chichewa
Name[csb]=Chichewa
Name[cy]=Chichewa
Name[da]=Chichewa
Name[de]=Nyanja
Name[el]=Chichewa
Name[en_GB]=Chichewa
Name[eo]=Njanĝa
Name[es]=Chichewa
Name[et]=Chichewa
Name[eu]=Chichewa
Name[fa]=چیچوا
Name[fi]=Njandža
Name[fr]=Chichewa
Name[fy]=Chichewa
Name[ga]=Siseivis
Name[gl]=Chichewa
Name[gu]=ચિકેવા
Name[he]=צ'יצ'ווה
Name[hi]=चिचेवा
Name[hne]=चिचेवा
Name[hr]=Chichewa
Name[hsb]=Chichewa
Name[hu]=Csicseva
Name[ia]=Chichewa
Name[id]=Chichewa
Name[is]=Chichewa
Name[it]=Chichewa
Name[ja]=チェワ語
Name[kk]=Чичева
Name[km]=ឈិចិវា
Name[kn]=ಚಿಚೇಛೇವಾ
Name[ko]=치체와어
Name[ku]=Çîçewa
Name[lb]=Nyanja
Name[lt]=Chichewa
Name[lv]=Čičeva
Name[mai]=चिचेवा
Name[mk]=Чичева
Name[ml]=ചിച്ചാവെ
Name[mr]=चिचेवा
Name[ms]=Chichewa
Name[nb]=Chichewa
Name[nds]=Chichewa
Name[ne]=चिचेवा
Name[nl]=Chichewa
Name[nn]=Chichewa
Name[or]=Chichewa
Name[pa]=ਚਿਚੀਵਾ
Name[pl]=Chichewa
Name[ps]=چېچيوا
Name[pt]=Chichewa
Name[pt_BR]=Cinianja
Name[ro]=Ciceuă
Name[ru]=Чичева
Name[se]=Chichevagiella
Name[si]=චිචෙවා
Name[sk]=Čičewa
Name[sl]=chichewa
Name[sq]=Chichewa
Name[sr]=чичева
Name[sr@ijekavian]=чичева
Name[sr@ijekavianlatin]=čičeva
Name[sr@latin]=čičeva
Name[sv]=Chewa
Name[ta]=சிச்செவா
Name[te]=చిచెవా
Name[tg]=Чичевагӣ
Name[th]=ภาษาชิเชวา
Name[tr]=Chichewa
Name[tt]=Ньянджа
Name[ug]=چىچېۋاچە
Name[uk]=Чічеванська
Name[uz]=Chicheva
Name[uz@cyrillic]=Чичева
Name[vi]=Chichewa
Name[wa]=Tchitchewa
Name[xh]=Chichewa
Name[x-test]=xxChichewaxx
Name[zh_CN]=齐佩瓦语
Name[zh_HK]=Chichewa語
Name[zh_TW]=Chichewa語
[oc]
Name=Occitan
Name[af]=Okkitaan
Name[ar]=أكستانية
Name[as]=অকিটান
Name[ast]=Occitanu
Name[be]=Акітанская
Name[be@latin]=Akitanskaja
Name[bg]=Окситански
Name[bn]=ওক্সিটান
Name[bn_IN]=অকসিটান
Name[br]=Oksitaneg
Name[bs]=Okcitanski
Name[ca]=Occità
Name[ca@valencia]=Occità
Name[cs]=Okcitánský
Name[csb]=Òkcitańsczi
Name[cy]=Occitaneg
Name[da]=Occitan
Name[de]=Okzitanisch
Name[el]=Occitan
Name[en_GB]=Occitan
Name[eo]=Okcitana
Name[es]=Occitano
Name[et]=Oktsitaani
Name[eu]=Okzitaniera
Name[fa]=اُکیتان
Name[fi]=Oksitaani
Name[fr]=Occitan
Name[fy]=Oksitaansk
Name[ga]=Ocatáinis
Name[gl]=Occitano
Name[gu]=ઓશિન
Name[he]=אוקסיטנית
Name[hi]=ओकितन
Name[hne]=ओकितन
Name[hr]=Occitan
Name[hsb]=Okcitansce
Name[hu]=Okcitán
Name[ia]=Occitano
Name[id]=Occitan
Name[is]=Occitan
Name[it]=Occitano
Name[ja]=オック語
Name[kk]=Осситанша
Name[km]=អុកស៊ីតង់
Name[kn]=ಒಸಿಟಾನ್
Name[ko]=오크어
Name[ku]=Oksîtan
Name[lb]=Okzitanesch
Name[lt]=Occitan
Name[lv]=Oksitāņu
Name[mai]=ओकितन
Name[mk]=Очитан
Name[ml]=ഒക്സിറ്റന്‍
Name[mr]=ओकितन
Name[ms]=Occitan
Name[nb]=Oksitansk
Name[nds]=Okzitaansch
Name[ne]=ओक्सिटान
Name[nl]=Occitan
Name[nn]=Oksitansk
Name[oc]=Occitan
Name[or]=Occitan
Name[pa]=ਓਸੀਟਾਨ
Name[pl]=Okcytański
Name[ps]=اوسيټي
Name[pt]=Occitano
Name[pt_BR]=Occitano
Name[ro]=Occitană
Name[ru]=Окситанский
Name[se]=Oksitánagiella
Name[si]=ඔක්සිටන්
Name[sk]=Okcitánčina
Name[sl]=očitansko
Name[sq]=Oçitane
Name[sr]=окситански
Name[sr@ijekavian]=окситански
Name[sr@ijekavianlatin]=oksitanski
Name[sr@latin]=oksitanski
Name[sv]=Occitanska
Name[ta]=ஒக்சிட்டான்
Name[te]=ఒస్సిటాన్
Name[tg]=Окитанӣ
Name[th]=ภาษาออคซิทัน
Name[tr]=Oksitan dili
Name[tt]=Провансальский
Name[ug]=ئوكسىتانچە
Name[uk]=Оксітанська
Name[uz]=Fransuzcha (Ossitan shevasi)
Name[uz@cyrillic]=Французча (Осситан шеваси)
Name[vi]=Occitan
Name[wa]=Occitan
Name[xh]=Occitan
Name[x-test]=xxOccitanxx
Name[zh_CN]=欧西坦语
Name[zh_HK]=Occitan語
Name[zh_TW]=Occitan語
[om]
Name=Oromo
Name[af]=Oromo
Name[ar]=أورومو
Name[as]=অ'ৰ'মো'
Name[ast]=Oromo
Name[be]=Арома
Name[be@latin]=Oromo
Name[bg]=Афан Оромо
Name[bn]=ওরোমো
Name[bn_IN]=ওরোমো
Name[br]=Oromo
Name[bs]=Oromo
Name[ca]=Oromo
Name[ca@valencia]=Oromo
Name[cs]=Oromo
Name[csb]=Oromo
Name[cy]=Oromo
Name[da]=Oromo
Name[de]=Oromo
Name[el]=Oromo
Name[en_GB]=Oromo
Name[eo]=Oroma
Name[es]=Oromo
Name[et]=Oromo
Name[eu]=Oromo
Name[fa]=اورومو
Name[fi]=Oromo
Name[fr]=Oromo
Name[fy]=Oromo
Name[ga]=Oraimis
Name[gl]=Oromo
Name[gu]=ઓરોમો
Name[he]=אורומו
Name[hi]=ओरोमो
Name[hne]=ओरोमो
Name[hr]=Oromski
Name[hsb]=Oromo
Name[hu]=Oromo
Name[ia]=Oromo
Name[id]=Oromo
Name[is]=Oromo
Name[it]=Oromo
Name[ja]=オロモ語
Name[kk]=Оромо
Name[km]=អូរ៉ូម៉ូ​
Name[kn]=ಓರೋಮೋ
Name[ko]=오로모어
Name[ku]=Oromo
Name[lb]=Oromo
Name[lt]=Oromo
Name[lv]=Oromu
Name[mai]=ओरोमो
Name[mk]=Оромо
Name[ml]=ഒരോമോ
Name[mr]=ओरोमो
Name[ms]=Oromo
Name[nb]=Oromo
Name[nds]=Oromo
Name[ne]=ओरोमो
Name[nl]=Oromo
Name[nn]=Oromo
Name[or]=Oromo
Name[pa]=ਓਰੋਮੋ
Name[pl]=Oromo
Name[ps]=اورومو
Name[pt]=Oromo
Name[pt_BR]=Oromo
Name[ro]=Oromă
Name[ru]=Оромо
Name[se]=Oromogiella
Name[si]=ඔරොමො
Name[sk]=Oromčina
Name[sl]=oromo
Name[sq]=Oromo
Name[sr]=оромо
Name[sr@ijekavian]=оромо
Name[sr@ijekavianlatin]=oromo
Name[sr@latin]=oromo
Name[sv]=Oromo
Name[ta]=ஒரோமோ
Name[te]=ఒరొమొ
Name[tg]=Оромо
Name[th]=ภาษาโอโรโม
Name[tr]=Oromo
Name[tt]=Оромо
Name[ug]=ئوروموچە
Name[uk]=Оромо
Name[uz]=Oromo
Name[uz@cyrillic]=Оромо
Name[vi]=Oromo
Name[wa]=Oromo
Name[xh]=Oromo
Name[x-test]=xxOromoxx
Name[zh_CN]=奥罗莫语
Name[zh_HK]=Oromo語
Name[zh_TW]=Oromo語
[or]
Name=Oriya
Name[af]=Oriya
Name[ar]=أوريا
Name[as]=ৱৰীয়
Name[ast]=Oriya
Name[be]=Орыя
Name[be@latin]=Oriya
Name[bg]=Ория
Name[bn]=ওড়িয়া
Name[bn_IN]=ওরিয়া
Name[br]=Oriya
Name[bs]=Oriya
Name[ca]=Oriya
Name[ca@valencia]=Oriya
Name[cs]=Oriya
Name[csb]=Oriya
Name[cy]=Oriya
Name[da]=Oriya
Name[de]=Oriya
Name[el]=Oriya
Name[en_GB]=Oriya
Name[eo]=Orijo
Name[es]=Oriya
Name[et]=Orija
Name[eu]=Oriya
Name[fa]=اوریا
Name[fi]=Orija
Name[fr]=Oriya
Name[fy]=Oriya
Name[ga]=Oirísis
Name[gl]=Orisa
Name[gu]=ઓરીયા
Name[he]=אורייה
Name[hi]=उड़िया
Name[hne]=उड़िया
Name[hr]=Oriya
Name[hsb]=Oriya
Name[hu]=Orija
Name[ia]=Oriya
Name[id]=Oriya
Name[is]=Oriya
Name[it]=Oriya
Name[ja]=オリヤー語
Name[kk]=Ория
Name[km]=អូរីយ៉ា
Name[kn]=ಒರಿಯಾ
Name[ko]=오리야어
Name[ku]=Oriya
Name[lb]=Orija
Name[lt]=Oriya
Name[lv]=Oriju
Name[mai]=उड़िया
Name[mk]=Орија
Name[ml]=ഒറിയന്‍
Name[mr]=ओरिया
Name[ms]=Oriya
Name[nb]=Oriya
Name[nds]=Oriya
Name[ne]=ओरिया
Name[nl]=Oriya
Name[nn]=Oriya
Name[oc]=Oriyà
Name[or]=Oriya
Name[pa]=ਉੜੀਆ
Name[pl]=Oriya
Name[ps]=وريا
Name[pt]=Oriya
Name[pt_BR]=Oriá
Name[ro]=Oriană
Name[ru]=Ория
Name[se]=Orijagiella
Name[si]=ඔරියා
Name[sk]=Uríjčina
Name[sl]=oriya
Name[sq]=Oriya
Name[sr]=оријски
Name[sr@ijekavian]=оријски
Name[sr@ijekavianlatin]=orijski
Name[sr@latin]=orijski
Name[sv]=Oriya
Name[ta]=ஒரியா
Name[te]=ఒరియా
Name[tg]=Ориёӣ
Name[th]=ภาษาโอริยา
Name[tr]=Oriya
Name[tt]=Ория
Name[ug]=ئورىياچە
Name[uk]=Орія
Name[uz]=Oriya
Name[uz@cyrillic]=Ория
Name[vi]=Oriya
Name[wa]=Oriya
Name[xh]=Oriya
Name[x-test]=xxOriyaxx
Name[zh_CN]=奥利亚语
Name[zh_HK]=Oriya語
Name[zh_TW]=Oriya語
[os]
Name=Ossetian
Name[af]=Ossetian
Name[ar]=أوسيتية
Name[as]=অচেটিয়ান
Name[ast]=Osetianu
Name[be]=Асетынская
Name[be@latin]=Asetynskaja
Name[bg]=Осетински
Name[bn]=ওসেটিয়ান
Name[bn_IN]=ওসেনশিয়ান
Name[br]=Ossetieg
Name[bs]=Osetski
Name[ca]=Osset
Name[ca@valencia]=Osset
Name[cs]=Osetský
Name[csb]=Òsetańsczi
Name[cy]=Oseteg
Name[da]=Ossetian
Name[de]=Ossetisch
Name[el]=Ossetian
Name[en_GB]=Ossetian
Name[eo]=Oseta
Name[es]=Osetio
Name[et]=Osseedi
Name[eu]=Osetiera
Name[fa]=اوستی قفقاز
Name[fi]=Osseetti
Name[fr]=Ossète
Name[fy]=Ossetysk
Name[ga]=Óiséitis
Name[gl]=Oseto
Name[gu]=ઓસેશિયન
Name[he]=אוסטית
Name[hi]=ओसेशियन
Name[hne]=ओसेसियन
Name[hr]=Osetski
Name[hsb]=Osetisce
Name[hu]=Oszét
Name[ia]=Ossetiano
Name[id]=Ossetian
Name[is]=Ossetian
Name[it]=Osseto
Name[ja]=オセット語
Name[kk]=Осетинше
Name[km]=អូសសិត្យង់
Name[kn]=ಓಸೆಟ್ಟಿಯಾನ್
Name[ko]=오세티안어
Name[ku]=Osetî
Name[lb]=Ossetesch
Name[lt]=Osetinų
Name[lv]=Osetīnu
Name[mai]=ओसेशियन
Name[mk]=Осетиски
Name[ml]=ഒസ്സെഷിയന്‍
Name[mr]=ओसेशियन
Name[ms]=Ossetian
Name[nb]=Ossetisk
Name[nds]=Osseetsch
Name[ne]=ओसेसियन
Name[nl]=Ossetisch
Name[nn]=Ossetisk
Name[or]=Ossetian
Name[pa]=ਓਸੀਟੀਅਨ
Name[pl]=Osetański
Name[ps]=اوسيټي
Name[pt]=Osseta
Name[pt_BR]=Osseto
Name[ro]=Osetiană
Name[ru]=Осетинский
Name[se]=Ossetiagiella
Name[si]=ඔසෙටියන්
Name[sk]=Osetčina
Name[sl]=osetijsko
Name[sq]=Ossetiane
Name[sr]=осетски
Name[sr@ijekavian]=осетски
Name[sr@ijekavianlatin]=osetski
Name[sr@latin]=osetski
Name[sv]=Ossetsiska
Name[ta]=ஒசெட்டியன்
Name[te]=ఒస్సెషియన్
Name[tg]=Осетинӣ
Name[th]=ภาษาโอซิเชียน
Name[tr]=Ossetian
Name[tt]=Осетинча
Name[ug]=ئوسسېتىكچە
Name[uk]=Осетинська
Name[uz]=Ossetincha
Name[uz@cyrillic]=Оссетинча
Name[vi]=Ossetian
Name[wa]=Ossete
Name[xh]=Ossetian
Name[x-test]=xxOssetianxx
Name[zh_CN]=奥塞梯语
Name[zh_HK]=Ossetian語
Name[zh_TW]=Ossetian語
[pa]
Name=Punjabi/Panjabi
Name[ar]=بنجابية
Name[as]=পাঞ্জাবী/পঞ্জাবী
Name[ast]=Punjabi/Panjabi
Name[be]=Панджабі
Name[be@latin]=Pandžabi
Name[bg]=Пенджаб
Name[bn]=পাঞ্জাবী
Name[bn_IN]=পাঞ্জাবি
Name[br]=Pendjabieg
Name[bs]=Pandžabi/Pandžabi
Name[ca]=Panjabi
Name[ca@valencia]=Panjabi
Name[cs]=Pandžábský
Name[csb]=Punjabi/Panjabi
Name[da]=Punjabi/Panjabi
Name[de]=Pandschabi
Name[el]=Punjabi/Panjabi
Name[en_GB]=Punjabi/Panjabi
Name[eo]=Panĝaba
Name[es]=Punjabi
Name[et]=Pandžabi
Name[eu]=Punjabi/Panjabi
Name[fa]=پنجابی/پنجابی
Name[fi]=Pandžabi
Name[fr]=Panjabi / Punjabi
Name[fy]=Panjabi/Panjabi
Name[ga]=Painseáibis
Name[gl]=Punxabí/Panxabí
Name[gu]=પંજાબી/પંજાબી
Name[he]=פנג'אבית
Name[hi]=पंजाबी
Name[hne]=पंजाबी
Name[hr]=Punjabi/Panjabi
Name[hsb]=Punjabi/Panjabi
Name[hu]=Pandzsabi
Name[ia]=Punjabi
Name[id]=Punjabi/Panjabi
Name[is]=Punjabi/Panjabi
Name[it]=Panjabi
Name[ja]=パンジャーブ語
Name[kk]=Пунджаби/Панждаби
Name[km]=ពូនយ៉ាប៊ី
Name[kn]=ಪಂಜಾಬಿ
Name[ko]=펀자브어
Name[ku]=Pêncabî
Name[lt]=Punjabi/Panjabi
Name[lv]=Pandžabu
Name[mai]=पंजाबी
Name[mk]=Пунџаби/Панџаби
Name[ml]=പഞ്ചാബി
Name[mr]=पंजाबी/पँजाबी
Name[ms]=Punjabi/Panjabi
Name[nb]=Punjabi/Panjabi
Name[nds]=Pandschaabsch
Name[ne]=पन्जाबी/पन्जाबी
Name[nl]=Panjabi
Name[nn]=Panjabi
Name[or]=Punjabi/Panjabi
Name[pa]=ਪੰਜਾਬੀ
Name[pl]=Pendżabski
Name[ps]=پنجابي
Name[pt]=Panjabi/Punjabi
Name[pt_BR]=Panjabi
Name[ro]=Punjabi
Name[ru]=Панджаби
Name[se]=Punjabi/Panjabi
Name[si]=පුන්ජාබි/පන්ජාබි
Name[sk]=Pandžábčina
Name[sl]=pandžabsko
Name[sq]=Punxhabisht
Name[sr]=панџаби/панџаби
Name[sr@ijekavian]=панџаби/панџаби
Name[sr@ijekavianlatin]=pandžabi/pandžabi
Name[sr@latin]=pandžabi/pandžabi
Name[sv]=Punjabi/Panjabi
Name[ta]=பஞ்சாபி
Name[te]=పంజాబి
Name[tg]=Пунҷабӣ/Панҷабӣ
Name[th]=ภาษาปัญจาบี/ปัญจาบ
Name[tr]=Punjabi/Panjabi
Name[tt]=Пенджабча
Name[ug]=پەنجابچە
Name[uk]=Панджабська
Name[vi]=Punjabi/Panjabi
Name[wa]=Pundjabi
Name[x-test]=xxPunjabi/Panjabixx
Name[zh_CN]=旁遮普语
Name[zh_TW]=Punjabi/Panjabi
[pi]
Name=Pali
Name[af]=Pali
Name[ar]=بالية
Name[as]=পালি
Name[ast]=Pali
Name[be]=Палі
Name[be@latin]=Pali
Name[bg]=Пали
Name[bn]=পালি
Name[bn_IN]=পালি
Name[br]=Palieg
Name[bs]=Pali
Name[ca]=Pali
Name[ca@valencia]=Pali
Name[cs]=Pali
Name[csb]=Pali
Name[cy]=Pali
Name[da]=Pali
Name[de]=Pali
Name[el]=Pali
Name[en_GB]=Pali
Name[eo]=Palia
Name[es]=Palí
Name[et]=Paali
Name[eu]=Pali
Name[fa]=پالی
Name[fi]=Paali
Name[fr]=Pali
Name[fy]=Pali
Name[ga]=Páilis
Name[gl]=Pali
Name[gu]=પાલી
Name[he]=פאלי
Name[hi]=पाली
Name[hne]=पाली
Name[hr]=Pali
Name[hsb]=Pali
Name[hu]=Pali
Name[ia]=Pali
Name[id]=Pali
Name[is]=Pali
Name[it]=Pali
Name[ja]=パーリ語
Name[kk]=Пали
Name[km]=ប៉ាលី
Name[kn]=ಪಾಲಿ
Name[ko]=팔리어
Name[ku]=Palî
Name[lb]=Pali
Name[lt]=Pali
Name[lv]=Pāli
Name[mai]=पाली
Name[mk]=Пали
Name[ml]=പാലി
Name[mr]=पाली
Name[ms]=Pali
Name[nb]=Pali
Name[nds]=Pali
Name[ne]=पाली
Name[nl]=Pali
Name[nn]=Pali
Name[oc]=Pòlònès
Name[or]=Pali
Name[pa]=ਪਾਲੀ
Name[pl]=Pali
Name[ps]=پالي
Name[pt]=Pali
Name[pt_BR]=Pali
Name[ro]=Pali
Name[ru]=Пали
Name[se]=Páligiella
Name[si]=පාලි
Name[sk]=Pálí
Name[sl]=pali
Name[sq]=Pali
Name[sr]=пали
Name[sr@ijekavian]=пали
Name[sr@ijekavianlatin]=pali
Name[sr@latin]=pali
Name[sv]=Pali
Name[ta]=பாலி
Name[te]=పాళి
Name[tg]=Палӣ
Name[th]=ภาษาบาลี
Name[tr]=Pali
Name[tt]=Пали
Name[ug]=پالىچە
Name[uk]=Палі
Name[uz]=Pali
Name[uz@cyrillic]=Пали
Name[vi]=Pa-li
Name[wa]=Pâli
Name[xh]=Pali
Name[x-test]=xxPalixx
Name[zh_CN]=巴利语
Name[zh_HK]=巴利語
Name[zh_TW]=巴利語
[pl]
Name=Polish
Name[af]=Poolse
Name[ar]=بولندية
Name[as]=প'লিশ্ব
Name[ast]=Polacu
Name[be]=Польская
Name[be@latin]=Polskaja
Name[bg]=Полски
Name[bn]=পোলিশ
Name[bn_IN]=পোলিশ
Name[br]=Poloneg
Name[bs]=Poljski
Name[ca]=Polonès
Name[ca@valencia]=Polonés
Name[cs]=Polský
Name[csb]=Pòlsczi
Name[cy]=Pwyleg
Name[da]=Polsk
Name[de]=Polnisch
Name[el]=Πολωνικά
Name[en_GB]=Polish
Name[eo]=Pola
Name[es]=Polaco
Name[et]=Poola
Name[eu]=Poloniera
Name[fa]=لهستانی
Name[fi]=Puola
Name[fr]=Polonais
Name[fy]=Poalsk
Name[ga]=Polainnis
Name[gl]=Polaco
Name[gu]=પોલિશ
Name[he]=פולנית
Name[hi]=पोलिश
Name[hne]=पोलिस
Name[hr]=Poljski
Name[hsb]=Pólsce
Name[hu]=Lengyel
Name[ia]=Polonese
Name[id]=Polandia
Name[is]=Pólska
Name[it]=Polacco
Name[ja]=ポーランド語
Name[kk]=Полякша
Name[km]=ប៉ូឡូញ
Name[kn]=ಪೋಲಿಷ್
Name[ko]=폴란드어
Name[ku]=Polish
Name[lb]=Polnesch
Name[lt]=Lenkų
Name[lv]=Poļu
Name[mai]=पोलिश
Name[mk]=Полски
Name[ml]=പോളിഷ്
Name[mr]=पोलिश
Name[ms]=Polish
Name[nb]=Polsk
Name[nds]=Poolsch
Name[ne]=पोलीस
Name[nl]=Pools
Name[nn]=Polsk
Name[oc]=Polonés
Name[or]=Polish
Name[pa]=ਪੋਲੈਂਡੀ
Name[pl]=Polski
Name[ps]=پولېش
Name[pt]=Polaco
Name[pt_BR]=Polonês
Name[ro]=Poloneză
Name[ru]=Польский
Name[se]=Polskkagiella
Name[si]=පෝලන්ත
Name[sk]=Poľština
Name[sl]=poljsko
Name[sq]=Polonisht
Name[sr]=пољски
Name[sr@ijekavian]=пољски
Name[sr@ijekavianlatin]=poljski
Name[sr@latin]=poljski
Name[sv]=Polska
Name[ta]=போலிஷ்
Name[te]=పోలిష్
Name[tg]=Полякӣ
Name[th]=ภาษาโปแลนด์
Name[tr]=Lehçe
Name[tt]=Польша
Name[ug]=پولەكچە
Name[uk]=Польська
Name[uz]=Polyakcha
Name[uz@cyrillic]=Полякча
Name[vi]=Ba Lan
Name[wa]=Polonès
Name[xh]=Polish
Name[x-test]=xxPolishxx
Name[zh_CN]=波兰语
Name[zh_HK]=波蘭語
Name[zh_TW]=波蘭語
[ps]
Name=Pushto
Name[af]=Pushto
Name[ar]=باشتو
Name[as]=পুষ্টো
Name[ast]=Pushto
Name[be]=Пушту
Name[be@latin]=Puštu
Name[bg]=Пущу
Name[bn]=পুস্ত
Name[bn_IN]=পুস্তু
Name[br]=Pushto
Name[bs]=Paštu
Name[ca]=Paixtu
Name[ca@valencia]=Paixtu
Name[cs]=Pushto
Name[csb]=Pushto
Name[cy]=Pushto
Name[da]=Pushto
Name[de]=Paschtu
Name[el]=Pushto
Name[en_GB]=Pushto
Name[eo]=Paŝtua
Name[es]=Pastún
Name[et]=Puštu
Name[eu]=Paxtuera
Name[fa]=پشتو
Name[fi]=Paštu
Name[fr]=Pushto
Name[fy]=Pushto
Name[ga]=Paistis
Name[gl]=Pushto
Name[gu]=પુશ્ટો
Name[he]=פאשטו
Name[hi]=पश्तो
Name[hne]=पस्तो
Name[hr]=Pushto
Name[hsb]=Pušto
Name[hu]=Pusto
Name[ia]=Pushto
Name[id]=Pushto
Name[is]=Pushto
Name[it]=Paštu
Name[ja]=パシュトー語
Name[kk]=Пушту
Name[km]=ពុហ្សតូ
Name[kn]=ಪುಷ್ತೋ
Name[ko]=파슈토어
Name[ku]=Paşto
Name[lb]=Paschtu
Name[lt]=Puštūnų
Name[lv]=Puštu
Name[mai]=पश्तो
Name[mk]=Пушто
Name[ml]=പുഷ്ട്ടോ
Name[mr]=पश्तो
Name[ms]=Pushto
Name[nb]=Pushto
Name[nds]=Paschtuunsch
Name[ne]=पुस्तो
Name[nl]=Pushto
Name[nn]=Pashto
Name[or]=Pushto
Name[pa]=ਪੁਸ਼ਤੋ
Name[pl]=Pushto
Name[ps]=پښتو
Name[pt]=Pushto
Name[pt_BR]=Afegão
Name[ro]=Puștă
Name[ru]=Пушту
Name[se]=Puštugiella
Name[si]=පුෂ්ටො
Name[sk]=Paštčina
Name[sl]=pushto
Name[sq]=Pushto
Name[sr]=паштунски
Name[sr@ijekavian]=паштунски
Name[sr@ijekavianlatin]=paštunski
Name[sr@latin]=paštunski
Name[sv]=Pashto
Name[ta]=பாஷ்டுன்
Name[te]=పుష్తొ
Name[tg]=Пушту
Name[th]=ภาษาพาชโต
Name[tr]=Pushto
Name[tt]=Пушту Фарсы
Name[ug]=پوشتۇچە
Name[uk]=Пуштунська
Name[uz]=Pushtuncha
Name[uz@cyrillic]=Пуштунча
Name[vi]=Pushto
Name[wa]=Pashto
Name[xh]=Pushto
Name[x-test]=xxPushtoxx
Name[zh_CN]=普什图语
Name[zh_HK]=普什圖語
Name[zh_TW]=普什圖語
[pt]
Name=Portuguese
Name[af]=Portugese
Name[ar]=برتغالية
Name[as]=প'ৰ্টুগিছ
Name[ast]=Portugués
Name[be]=Партугальская
Name[be@latin]=Partuhalskaja
Name[bg]=Португалски
Name[bn]=পর্তুগীজ
Name[bn_IN]=পোর্তুগিস
Name[br]=Portugaleg
Name[bs]=Portugalski
Name[ca]=Portuguès
Name[ca@valencia]=Portugués
Name[cs]=Portugalský
Name[csb]=Pòrtugalsczi
Name[cy]=Portiwgaleg
Name[da]=Portugisisk
Name[de]=Portugiesisch
Name[el]=Πορτογαλικά
Name[en_GB]=Portuguese
Name[eo]=Portugala
Name[es]=Portugués
Name[et]=Portugali
Name[eu]=Portugesa
Name[fa]=پرتغالی
Name[fi]=Portugali
Name[fr]=Portugais
Name[fy]=Portugeesk
Name[ga]=Portaingéilis
Name[gl]=Portugués
Name[gu]=પોર્ટુગીઝ
Name[he]=פורטוגזית
Name[hi]=पुर्तगाली
Name[hne]=पुर्तगाली
Name[hr]=Portugalski
Name[hsb]=Portugalsce
Name[hu]=Portugál
Name[ia]=Portugese
Name[id]=Portugis
Name[is]=Portúgalska
Name[it]=Portoghese
Name[ja]=ポルトガル語
Name[kk]=Португалша
Name[km]=ព័រទុយហ្គាល់
Name[kn]=ಪೋರ್ಚುಗೀಸ್
Name[ko]=포르투갈어
Name[ku]=Portûgalî
Name[lb]=Portugisesch
Name[lt]=Portugalų
Name[lv]=Portugāļu
Name[mai]=पुर्तगाली
Name[mk]=Португалски
Name[ml]=പോര്‍ച്ചുഗീസ്
Name[mr]=पुर्तगाली
Name[ms]=Portugis
Name[nb]=Portugisisk
Name[nds]=Portugeesch
Name[ne]=पोर्चुगाली
Name[nl]=Portugees
Name[nn]=Portugisisk
Name[oc]=Portugués
Name[or]=Portuguese
Name[pa]=ਪੁਰਤਗਾਲੀ
Name[pl]=Portugalski
Name[ps]=پورټګيز
Name[pt]=Português
Name[pt_BR]=Português
Name[ro]=Portugheză
Name[ru]=Португальский
Name[se]=Portugálagiella
Name[si]=පෘතුගීසි
Name[sk]=Portugalčina
Name[sl]=portugalsko
Name[sq]=Portugalisht
Name[sr]=португалски
Name[sr@ijekavian]=португалски
Name[sr@ijekavianlatin]=portugalski
Name[sr@latin]=portugalski
Name[sv]=Portugisiska
Name[ta]=போர்த்துக்கீசிய
Name[te]=పోర్ట్యుగీస్
Name[tg]=Португалӣ
Name[th]=ภาษาโปรตุเกส
Name[tr]=Portekizce
Name[tt]=Португал
Name[ug]=پورتۇگالچە
Name[uk]=Португальська
Name[uz]=Portugalcha
Name[uz@cyrillic]=Португалча
Name[vi]=Bồ Đào Nha
Name[wa]=Portuguès
Name[xh]=Portuguese
Name[x-test]=xxPortuguesexx
Name[zh_CN]=葡萄牙语
Name[zh_HK]=葡萄牙語
Name[zh_TW]=葡萄牙語
[pt_BR]
Name=Brazilian Portuguese
Name[af]=Brazilian Portuguese
Name[ar]=برتغالية البرازيل
Name[as]=ব্ৰাজিলীয় প'ৰ্টুগিছ
Name[ast]=Portugués de Brasil
Name[be]=Партугальская бразільская
Name[be@latin]=Brazylskaja partuhalskaja
Name[bg]=Бразилски португалски
Name[bn]=ব্রাজিলীয় পর্তুগীজ
Name[bn_IN]=ব্রাজিলিয়ান পোর্তুগিজ
Name[br]=Portugaleg Brazil
Name[bs]=Brazilski portugalski
Name[ca]=Portuguès Brasiler
Name[ca@valencia]=Portugués Brasiler
Name[cs]=Portugalský (Brazílie)
Name[csb]=Brazylsczi pòrtugalsczi
Name[cy]=Portiwgaleg Brasil
Name[da]=Brasiliansk portugisisk
Name[de]=Brasilianisches Portugiesisch
Name[el]=Πορτογαλικά Βραζιλίας
Name[en_GB]=Brazilian Portuguese
Name[eo]=Portugala (Brazilo)
Name[es]=Portugués brasileño
Name[et]=Brasiilia portugali
Name[eu]=Portugesa (Brasilgoa)
Name[fa]=پرتغالی برزیلی
Name[fi]=Brasilianportugali
Name[fr]=Portugais Brésilien
Name[fy]=Braziliaansk Portugeesk
Name[ga]=Portaingéilis na Brasaíle
Name[gl]=Portugués do Brasil
Name[gu]=બ્રાઝીલીયન પોર્ટુગીઝ
Name[he]=פורטוגזית ברזילאית
Name[hi]=पुर्तगाली (ब्राजीलीयाई)
Name[hne]=पुर्तगाली (ब्राजीलीयाई)
Name[hr]=Brazilski portugalski
Name[hsb]=Portugalsce (Brazilska)
Name[hu]=Portugál (brazil)
Name[ia]=Portugese Brasiliano
Name[id]=Portugis Brazil
Name[is]=Brasílísk Portúgalska
Name[it]=Portoghese brasiliano
Name[ja]=ブラジル ポルトガル語
Name[kk]=Бразилиялық португалша
Name[km]=ប្រេស៊ីល ព័រទុយហ្គាល់
Name[kn]=ಬ್ರೆಜಿಲಿಯನ್ ಪೋರ್ಚುಗೀಸ್
Name[ko]=브라질식 포르투갈어
Name[ku]=Portûgaliya Brezîl
Name[lb]=Brazilianescht Portugisesch
Name[lt]=Brazilijos portugalų
Name[lv]=Brazīlijas portugāļu
Name[mai]=पुर्तगाली (ब्राजीलीयाई)
Name[mk]=Бразилски португалски
Name[ml]=ബ്രസീലിലെപോര്‍ച്ചുഗീസ്
Name[mr]=ब्राजील पुर्तगाली
Name[ms]=Portugis Brazil
Name[nb]=Brasil-portugisisk
Name[nds]=Brasiliaansch Portugeesch
Name[ne]=ब्राजिली पोर्चुगाली
Name[nl]=Braziliaans Portugees
Name[nn]=Brasil-portugisisk
Name[oc]=Portugués de Brasil
Name[or]=Brazilian Portuguese
Name[pa]=ਬਰਾਜ਼ੀਲ ਪੁਰਤਗਾਲੀ
Name[pl]=Portugalski (brazylijski)
Name[ps]=برازيلي پرټګيز
Name[pt]=Português do Brasil
Name[pt_BR]=Português do Brasil
Name[ro]=Portugheză braziliană
Name[ru]=Португальский (Бразилия)
Name[se]=Brasilialaš portugálagiella
Name[si]=බ්‍රසීල පෘතුගීසි
Name[sk]=Portugalčina (Brazília)
Name[sl]=brazilska portugalščina
Name[sq]=Portugalisht e Brazilit
Name[sr]=бразилски португалски
Name[sr@ijekavian]=бразилски португалски
Name[sr@ijekavianlatin]=brazilski portugalski
Name[sr@latin]=brazilski portugalski
Name[sv]=Brasiliansk portugisiska
Name[ta]=பிரேஸிலிய போர்த்துக்கீசிய
Name[te]=బ్రాజిలియన్ పోర్ట్యుగీస్
Name[tg]=Бразилиявии Португалӣ
Name[th]=ภาษาโปรตุเกสบราซิล
Name[tr]=Brezilya Portekizcesi
Name[tt]=Португал (Бразилия)
Name[ug]=بىرازىلىيە پورتۇگالچىسى
Name[uk]=Бразильська португальська
Name[uz]=Portugalcha (Braziliya)
Name[uz@cyrillic]=Португалча (Бразилия)
Name[vi]=Bồ Đào Nha - Braxin
Name[wa]=Portuguès do Braezi
Name[x-test]=xxBrazilian Portuguesexx
Name[zh_CN]=巴西葡萄牙语
Name[zh_HK]=巴西葡萄牙語
Name[zh_TW]=巴西葡萄牙語
[qu]
Name=Quechua
Name[af]=Quechua
Name[ar]=كويتشيوا
Name[as]=কেছুৱা
Name[ast]=Quechua
Name[be]=Кэчуа
Name[be@latin]=Čečua
Name[bg]=Кечуа
Name[bn]=কেচুয়া
Name[bn_IN]=কেচুওয়া
Name[br]=Kechuaeg
Name[bs]=Kečuanski
Name[ca]=Quítxua
Name[ca@valencia]=Quítxua
Name[cs]=Quechua
Name[csb]=Quechua
Name[cy]=Quechua
Name[da]=Quechua
Name[de]=Quechua
Name[el]=Quechua
Name[en_GB]=Quechua
Name[eo]=Keĉua
Name[es]=Quechua
Name[et]=Ketšua
Name[eu]=Kitxua
Name[fa]=کوچوا
Name[fi]=Ketšua
Name[fr]=Quechua
Name[fy]=Quechua
Name[ga]=Ceatsuais
Name[gl]=Quechua
Name[gu]=ક્વેચા
Name[he]=קצ'ואה
Name[hi]=क्वेचुआ
Name[hne]=क्वेचुआ
Name[hr]=Quechua
Name[hsb]=Quechua
Name[hu]=Kecsua
Name[ia]=Quechua
Name[id]=Quechua
Name[is]=Quechua
Name[it]=Quechua
Name[ja]=ケチュア語
Name[kk]=Кечуа
Name[km]=កេច្វា
Name[kn]=ಕ್ವೆಚುವಾ
Name[ko]=케추아어
Name[ku]=Kweçua
Name[lb]=Quechua-Sprooch
Name[lt]=Quechua
Name[lv]=Keāvu
Name[mai]=क्वेचुआ
Name[mk]=Кечуа
Name[ml]=ക്യുച്ചുവ
Name[mr]=क्वेचुआ
Name[ms]=Quechua
Name[nb]=Quechua
Name[nds]=Ketschua
Name[ne]=क्वेचउ
Name[nl]=Quechua
Name[nn]=Quechua
Name[oc]=Quechua
Name[or]=Quechua
Name[pa]=ਕਿਉਚੁਆ
Name[pl]=Quechua
Name[ps]=کوېچوا
Name[pt]=Quechua
Name[pt_BR]=Quíchua
Name[ro]=Quechua
Name[ru]=Кечуа
Name[se]=Keččuagiella
Name[si]=ක්වෙචුවා
Name[sk]=Kečuánčina
Name[sl]=quechua
Name[sq]=Keçua
Name[sr]=квечва
Name[sr@ijekavian]=квечва
Name[sr@ijekavianlatin]=kvečva
Name[sr@latin]=kvečva
Name[sv]=Quechua
Name[ta]=குவெச்சா
Name[te]=క్వెచువా
Name[tg]=Квечуа
Name[th]=ภาษาเคชัว
Name[tr]=Quechua
Name[tt]=Кечуа
Name[ug]=كۇچۇاچە
Name[uk]=Кечуа
Name[uz]=Kvechua
Name[uz@cyrillic]=Квечуа
Name[vi]=Quechua
Name[wa]=Kitchwa
Name[xh]=Quechua
Name[x-test]=xxQuechuaxx
Name[zh_CN]=盖丘亚语
Name[zh_HK]=蓋楚瓦語
Name[zh_TW]=蓋楚瓦語
[rn]
Name=Rundi
Name[af]=Rundi
Name[ar]=روندية
Name[as]=ৰান্ডী
Name[ast]=Rundí
Name[be]=Рундзі
Name[be@latin]=Rundzi
Name[bg]=Руанда
Name[bn]=রুণ্ডি
Name[bn_IN]=রুন্ডি
Name[br]=Rundi
Name[bs]=Rundi
Name[ca]=Rundi
Name[ca@valencia]=Rundi
Name[cs]=Rundi
Name[csb]=Rundi
Name[cy]=Rwndi
Name[da]=Rundi
Name[de]=Rundi
Name[el]=Rundi
Name[en_GB]=Rundi
Name[eo]=Burunda
Name[es]=Rundi
Name[et]=Rundi
Name[eu]=Rundi
Name[fa]=روندی
Name[fi]=Rundi
Name[fr]=Rundi
Name[fy]=Rûandeesk
Name[ga]=Rundais
Name[gl]=Rundi
Name[gu]=રુન્ડી
Name[he]=רונדי
Name[hi]=रून्डी
Name[hne]=रून्डी
Name[hr]=Rundi
Name[hsb]=Rundi
Name[hu]=Rundi
Name[ia]=Rundi
Name[id]=Rundi
Name[is]=Rundi
Name[it]=Kirundi
Name[ja]=ルンディ語
Name[kk]=Рунди
Name[km]=រុន្ឌី
Name[kn]=ರುಂಡಿ
Name[ko]=룬디어
Name[ku]=Rundî
Name[lb]=Rundi-Sprooch
Name[lt]=Rundi
Name[lv]=Rundu
Name[mai]=रून्डी
Name[mk]=Рунди
Name[ml]=റന്‍ഡി
Name[mr]=रून्डी
Name[ms]=Rundi
Name[nb]=Rundi
Name[nds]=Rundi
Name[ne]=रुन्डी
Name[nl]=Rundi
Name[nn]=Rundi
Name[or]=Rundi
Name[pa]=ਰੂਡੀ
Name[pl]=Rundi
Name[ps]=رونډي
Name[pt]=Rundi
Name[pt_BR]=Rundi
Name[ro]=Rundi
Name[ru]=Рунди
Name[se]=Rundigiella
Name[si]=රුන්දි
Name[sk]=Rundčina
Name[sl]=rundi
Name[sq]=Rundi
Name[sr]=рунди
Name[sr@ijekavian]=рунди
Name[sr@ijekavianlatin]=rundi
Name[sr@latin]=rundi
Name[sv]=Rundi
Name[ta]=ருண்டி
Name[te]=రుండి
Name[tg]=Рундӣ
Name[th]=ภาษารุนดิ
Name[tr]=Rundi
Name[tt]=Рундича
Name[ug]=روندىچە
Name[uk]=Рунді
Name[uz]=Rundi
Name[uz@cyrillic]=Рунди
Name[vi]=Rundi
Name[wa]=Kirundi
Name[xh]=Rundi
Name[x-test]=xxRundixx
Name[zh_CN]=隆迪语
Name[zh_HK]=Rundi語
Name[zh_TW]=Rundi語
[ro]
Name=Romanian
Name[af]=Romeens
Name[ar]=رومانية
Name[as]=ৰোমানিয়
Name[ast]=Rumanu
Name[be]=Румынская
Name[be@latin]=Rumynskaja
Name[bg]=Румънски
Name[bn]=রোমানীয়
Name[bn_IN]=রোমেনিয়ান
Name[br]=Roumaneg
Name[bs]=Rumunski
Name[ca]=Romanès
Name[ca@valencia]=Romanés
Name[cs]=Rumunský
Name[csb]=Rumùńsczi
Name[cy]=Romaneg
Name[da]=Rumænsk
Name[de]=Rumänisch
Name[el]=Ρουμανικά
Name[en_GB]=Romanian
Name[eo]=Rumana
Name[es]=Rumano
Name[et]=Rumeenia
Name[eu]=Errumaniera
Name[fa]=رومانیایی
Name[fi]=Romania
Name[fr]=Roumain
Name[fy]=Roemeensk
Name[ga]=Rómáinis
Name[gl]=Romanés
Name[gu]=રોમેનિયન
Name[he]=רומנית
Name[hi]=रोमानियाई
Name[hne]=रोमानियाई
Name[hr]=Rumunjski
Name[hsb]=Rumunsce
Name[hu]=Román
Name[ia]=Romaniano
Name[id]=Rumania
Name[is]=Rúmenska
Name[it]=Rumeno
Name[ja]=ルーマニア語
Name[kk]=Румынша
Name[km]=រូម៉ានី
Name[kn]=ರೊಮೇನಿಯನ್
Name[ko]=루마니아어
Name[ku]=Romanî
Name[lb]=Rumänesch
Name[lt]=Rumunų
Name[lv]=Rumāņu
Name[mai]=रोमानियाइ
Name[mk]=Романски
Name[ml]=റൊമേനിയന്‍
Name[mr]=रोमानियन
Name[ms]=Romania
Name[nb]=Rumensk
Name[nds]=Rumäänsch
Name[ne]=रोमनियाली
Name[nl]=Roemeens
Name[nn]=Rumensk
Name[oc]=Romanian
Name[or]=Romanian
Name[pa]=ਰੋਮਾਨੀਆਈ
Name[pl]=Rumuński
Name[ps]=رومانيايي
Name[pt]=Romeno
Name[pt_BR]=Romeno
Name[ro]=Română
Name[ru]=Румынский
Name[se]=Romániagiella
Name[si]=රුමේනියානු
Name[sk]=Rumunčina
Name[sl]=romunsko
Name[sq]=Rumunisht
Name[sr]=румунски
Name[sr@ijekavian]=румунски
Name[sr@ijekavianlatin]=rumunski
Name[sr@latin]=rumunski
Name[sv]=Rumänska
Name[ta]=உருமேனியன்
Name[te]=రొమేనియన్
Name[tg]=Романӣ
Name[th]=ภาษาโรมาเนีย
Name[tr]=Romence
Name[tt]=Румын
Name[ug]=رۇمىنىيەچە
Name[uk]=Румунська
Name[uz]=Rumincha
Name[uz@cyrillic]=Руминча
Name[vi]=Romania
Name[wa]=Roumin
Name[xh]=Romanian
Name[x-test]=xxRomanianxx
Name[zh_CN]=罗马尼亚语
Name[zh_HK]=羅馬尼亞語
Name[zh_TW]=羅馬尼亞語
[rom]
Name=Romany
Name[ar]=رومانيا
Name[as]=ৰোমানি
Name[ast]=Romaní
Name[be]=Раманская
Name[be@latin]=Cyhanskaja
Name[bg]=Ромски
Name[bn]=রোমানী
Name[bn_IN]=রোমেনি
Name[br]=Jipsianek
Name[bs]=Romski
Name[ca]=Caló
Name[ca@valencia]=Caló
Name[cs]=Romský
Name[csb]=Rumùńsczi
Name[da]=Romany
Name[de]=Romani
Name[el]=Ρομάνι
Name[en_GB]=Romany
Name[eo]=Romaa
Name[es]=Rumaní
Name[et]=Mustlaskeel
Name[eu]=Errumaniera
Name[fa]=رومانی
Name[fi]=Romani
Name[fr]=Rromani
Name[fy]=Romany
Name[ga]=Romainis
Name[gl]=Romaní
Name[gu]=રોમાનિ
Name[he]=צוענית
Name[hi]=रोमन
Name[hne]=रोमन
Name[hr]=Romanski
Name[hsb]=Romany
Name[hu]=Lovári cigány
Name[ia]=Romany
Name[id]=Romany
Name[is]=Rúmenía
Name[it]=Romaní
Name[ja]=ロマ語
Name[kk]=Цыганша
Name[km]=រូម៉ានី
Name[kn]=ರೋಮನೀ
Name[ko]=로마니어
Name[ku]=Romî
Name[lb]=Romani
Name[lt]=Romany
Name[lv]=Čigānu
Name[mai]=रोमन
Name[mk]=Ромски
Name[ml]=റൊമാനി
Name[mr]=रोमन
Name[ms]=Romania
Name[nb]=Romani
Name[nds]=Romaneesch
Name[ne]=रोमानी
Name[nl]=Roma
Name[nn]=Romani
Name[oc]=Romanian
Name[or]=Romany
Name[pa]=ਰੋਮੇ
Name[pl]=Romski
Name[ps]=روماني
Name[pt]=Romani
Name[pt_BR]=Romani
Name[ro]=Țigănească
Name[ru]=Цыганский
Name[se]=Románigiella
Name[si]=රොමනි
Name[sk]=Rómčina
Name[sl]=romsko
Name[sq]=Romani
Name[sr]=ромски
Name[sr@ijekavian]=ромски
Name[sr@ijekavianlatin]=romski
Name[sr@latin]=romski
Name[sv]=Romanés
Name[ta]=உருமேனியன்
Name[te]=రొమని
Name[tg]=Романӣ
Name[th]=ภาษาโรมานี
Name[tr]=Çingene Dili
Name[tt]=Чегәнчә
Name[ug]=سىگانچە
Name[uk]=Циганська
Name[vi]=Romany
Name[wa]=Romani
Name[x-test]=xxRomanyxx
Name[zh_CN]=吉普赛语
Name[zh_TW]=吉普賽語
[ru]
Name=Russian
Name[af]=Russies
Name[ar]=روسية
Name[as]=ৰুচীয়
Name[ast]=Rusu
Name[be]=Расійская
Name[be@latin]=Rasiejskaja
Name[bg]=Руски
Name[bn]=রুশ
Name[bn_IN]=রুশি
Name[br]=Rusianeg
Name[bs]=Ruski
Name[ca]=Rus
Name[ca@valencia]=Rus
Name[cs]=Ruský
Name[csb]=Rusczi
Name[cy]=Rwsieg
Name[da]=Russisk
Name[de]=Russisch
Name[el]=Ρωσικά
Name[en_GB]=Russian
Name[eo]=Rusa
Name[es]=Ruso
Name[et]=Vene
Name[eu]=Errusiera
Name[fa]=روسی
Name[fi]=Venäjä
Name[fr]=Russe
Name[fy]=Russysk
Name[ga]=Rúisis
Name[gl]=Ruso
Name[gu]=રશિયન
Name[he]=רוסית
Name[hi]=रूसी
Name[hne]=रूसी
Name[hr]=Ruski
Name[hsb]=Rusce
Name[hu]=Orosz
Name[hy]=Ռուսերեն
Name[ia]=Russo
Name[id]=Rusia
Name[is]=Rússneska
Name[it]=Russo
Name[ja]=ロシア語
Name[kk]=Орысша
Name[km]=រុស្ស៊ី
Name[kn]=ರಷಿಯನ್
Name[ko]=러시아어
Name[ku]=Rûsî
Name[lb]=Russesch
Name[lt]=Rusų
Name[lv]=Krievu
Name[mai]=रूसी
Name[mk]=Руски
Name[ml]=റഷ്യന്‍
Name[mr]=रूसी
Name[ms]=Russia
Name[nb]=Russisk
Name[nds]=Russ'sch
Name[ne]=रसियाली
Name[nl]=Russisch
Name[nn]=Russisk
Name[oc]=Rus
Name[or]=Russian
Name[pa]=ਰੂਸੀ
Name[pl]=Rosyjski
Name[ps]=روسي
Name[pt]=Russo
Name[pt_BR]=Russo
Name[ro]=Rusă
Name[ru]=Русский
Name[se]=Ruoššagiella
Name[si]=රුසියානු
Name[sk]=Ruština
Name[sl]=rusko
Name[sq]=Rusisht
Name[sr]=руски
Name[sr@ijekavian]=руски
Name[sr@ijekavianlatin]=ruski
Name[sr@latin]=ruski
Name[sv]=Ryska
Name[ta]=இரஷியன்
Name[te]=రషియన్
Name[tg]=Русӣ
Name[th]=ภาษารัสเซีย
Name[tr]=Rusça
Name[tt]=Рус
Name[ug]=رۇسچە
Name[uk]=Російська
Name[uz]=Ruscha
Name[uz@cyrillic]=Русча
Name[vi]=Nga
Name[wa]=Rûsse
Name[xh]=Isirashiya
Name[x-test]=xxRussianxx
Name[zh_CN]=俄语
Name[zh_HK]=俄語
Name[zh_TW]=俄語
[rw]
Name=Kinyarwanda
Name[af]=Kinyarwanda
Name[ar]=كينيارواندا
Name[as]=কিন্যাৰ্বান্ডা
Name[ast]=Kinyarwanda
Name[be]=Кіньярванда
Name[be@latin]=Kinyarwanda
Name[bg]=Кинияруанда
Name[bn]=কিন্যারওয়াণ্ডা
Name[bn_IN]=কিনয়ারুয়ান্ডা
Name[br]=Kinyarwanda
Name[bs]=Kinjaruanda
Name[ca]=Ruanda
Name[ca@valencia]=Ruanda
Name[cs]=Kinyarwanda
Name[csb]=Kinyarwanda
Name[cy]=Kinyarwanda
Name[da]=Kinyarwanda
Name[de]=Kinyarwanda
Name[el]=Kinyarwanda
Name[en_GB]=Kinyarwanda
Name[eo]=Ruanda
Name[es]=Kinyarwanda
Name[et]=Ruanda
Name[eu]=Kinyaruanda
Name[fa]=کینیارواندایی
Name[fi]=Ruanda
Name[fr]=Kinyarwanda
Name[fy]=Kinyarwanda
Name[ga]=Cinearuáindis
Name[gl]=Quiñaruanda
Name[gu]=કિન્યારવાન્ડા
Name[he]=קיניירואנדה
Name[hi]=किन्यारवान्डा
Name[hne]=किन्यारवान्डा
Name[hr]=Kinidžaruandski
Name[hsb]=Kinyarwanda
Name[hu]=Kinjarvanda
Name[ia]=Kinyarwanda
Name[id]=Kinyarwanda
Name[is]=Kinyarwanda
Name[it]=Kinyarwanda
Name[ja]=ルワンダ語
Name[kk]=Киньяруанда
Name[km]=គីនយ៉ាវ៉ាន់ដា
Name[kn]=ಕಿನ್ಯರ್ವಾಂಡ
Name[ko]=키냐르완다어
Name[ku]=Kînyarwanda
Name[lb]=Ruanda-Sprooch
Name[lt]=Kinyarwanda
Name[lv]=Kiņaruanda
Name[mai]=किनयरवंडा
Name[mk]=Кинјарванда
Name[ml]=കിന്യാര്‍വാന്‍ഡാ
Name[mr]=किन्यारवान्डा
Name[ms]=Kinyarwanda
Name[nb]=Kinyarwanda
Name[nds]=Kinyarwanda
Name[ne]=किन्यरवान्डा
Name[nl]=Kinyarwanda
Name[nn]=Kinyarwanda
Name[oc]=Kinyarwandà
Name[or]=Kinyarwanda
Name[pa]=ਕਿਨਯਾਰਵਾਡਾਂ
Name[pl]=Kinyarwanda
Name[ps]=کينيارونډا
Name[pt]=Kinyarwanda
Name[pt_BR]=Kinyarwanda
Name[ro]=Chiniaruandeză
Name[ru]=Киньяруанда
Name[se]=Kinyarwandagiella
Name[si]=කින්යාවන්ඩා
Name[sk]=Rwandčina
Name[sl]=kinyarwanda
Name[sq]=Kinyarwanda
Name[sr]=кинијарванда
Name[sr@ijekavian]=кинијарванда
Name[sr@ijekavianlatin]=kinijarvanda
Name[sr@latin]=kinijarvanda
Name[sv]=Rwanda
Name[ta]=கின்யார்வாண்டா
Name[te]=కిన్న్యార్వాండా
Name[tg]=Кинуарвандӣ
Name[th]=ภาษาคินยาร์วันดา
Name[tr]=Kinyarwanda
Name[tt]=Киньяруанда
Name[ug]=كىنيارۋانداچە
Name[uk]=Кіньяруанда
Name[uz]=Kinyarvanda
Name[uz@cyrillic]=Кинярванда
Name[vi]=Kinyarwanda
Name[wa]=Kiniarwanda
Name[xh]=Kinyarwanda
Name[x-test]=xxKinyarwandaxx
Name[zh_CN]=基尼亚卢旺达语
Name[zh_HK]=Kinyarwanda語
Name[zh_TW]=Kinyarwanda語
[sa]
Name=Sanskrit
Name[af]=Sanskrit
Name[ar]=سنسكريتية
Name[as]=সংস্কৃত
Name[ast]=Sánscritu
Name[be]=Санскрыт
Name[be@latin]=Sanskryt
Name[bg]=Санскрит
Name[bn]=সংস্কৃত
Name[bn_IN]=সংস্কৃত
Name[br]=Sanskrit
Name[bs]=Sanskrt
Name[ca]=Sànscrit
Name[ca@valencia]=Sànscrit
Name[cs]=Sanskrit
Name[csb]=Sanskrit
Name[cy]=Sansgrit
Name[da]=Sanskrit
Name[de]=Sanskrit
Name[el]=Σανσκριτικά
Name[en_GB]=Sanskrit
Name[eo]=Sanskrito
Name[es]=Sánscrito
Name[et]=Sanskriti
Name[eu]=Sanskritoa
Name[fa]=سانسکریت
Name[fi]=Sanskrit
Name[fr]=Sanskrit
Name[fy]=Sanskryt
Name[ga]=Sanscrait
Name[gl]=Sánscrito
Name[gu]=સંસ્કૃત
Name[he]=סנסקריט
Name[hi]=संस्कृत
Name[hne]=संस्कृत
Name[hr]=Sanskrit
Name[hsb]=Sanskrit
Name[hu]=Szanszkrit
Name[ia]=Sanscrito
Name[id]=Sanskerta
Name[is]=Sanskrít
Name[it]=Sanscrito
Name[ja]=サンスクリット語
Name[kk]=Санскрит
Name[km]=សំស្ក្រឹត
Name[kn]=ಸಂಸ್ಕೃತ
Name[ko]=산스크리트어
Name[ku]=Sanskrît
Name[lb]=Sanskrit
Name[lt]=Sanskritas
Name[lv]=Sanskrits
Name[mai]=संस्कृत
Name[mk]=Санскрит
Name[ml]=സംസ്കൃതം
Name[mr]=संस्कृत
Name[ms]=Sanskrit
Name[nb]=Sanskrit
Name[nds]=Sanskrit
Name[ne]=संस्कृत
Name[nl]=Sanskrit
Name[nn]=Sanskrit
Name[oc]=Sanscrit
Name[or]=Sanskrit
Name[pa]=ਸੰਸਕ੍ਰਿਤ
Name[pl]=Sanskryt
Name[ps]=سانسکرېټ
Name[pt]=Sanscrito
Name[pt_BR]=Sânscrito
Name[ro]=Sanscrită
Name[ru]=Санскрит
Name[se]=Sanskrihtagiella
Name[si]=සංස්කෘත
Name[sk]=Sanskrit
Name[sl]=sanskrt
Name[sq]=Sanskrite
Name[sr]=санскрт
Name[sr@ijekavian]=санскрт
Name[sr@ijekavianlatin]=sanskrt
Name[sr@latin]=sanskrt
Name[sv]=Sanskrit
Name[ta]=சமஸ்கிருதம்
Name[te]=సంస్క్రుతం
Name[tg]=Санскрит
Name[th]=ภาษาสันสกฤต
Name[tr]=Sanskrit
Name[tt]=Санскрит
Name[ug]=سانسكرىتچە
Name[uk]=Санскрит
Name[uz]=Sanskrit
Name[uz@cyrillic]=Санскрит
Name[vi]=Sanskrit
Name[wa]=Sanskrit
Name[xh]=Sanskrit
Name[x-test]=xxSanskritxx
Name[zh_CN]=梵文
Name[zh_HK]=梵語
Name[zh_TW]=梵語
[sc]
Name=Sardinian
Name[af]=Sardinian
Name[ar]=ساردينية
Name[as]=চাৰ্ডিনিয়ান
Name[ast]=Sardu
Name[be]=Сардынская
Name[be@latin]=Sardynskaja
Name[bg]=Сардински
Name[bn]=সার্ডিনিয়ান
Name[bn_IN]=সার্ডিনিয়ান
Name[br]=Sardinieg
Name[bs]=Sardinijski
Name[ca]=Sard
Name[ca@valencia]=Sard
Name[cs]=Sardinský
Name[csb]=Sardińsczi
Name[cy]=Sardinieg
Name[da]=Sardinsk
Name[de]=Sardisch
Name[el]=Σαρδηνιακά
Name[en_GB]=Sardinian
Name[eo]=Sarda
Name[es]=Sardo
Name[et]=Sardiinia
Name[eu]=Sardiera
Name[fa]=ساردینی
Name[fi]=Sardi
Name[fr]=Sarde
Name[fy]=Sardinysk
Name[ga]=Sairdínis
Name[gl]=Sardo
Name[gu]=સાર્ડિનિયન
Name[he]=סרדינית
Name[hi]=सारदिनी
Name[hne]=सारदिनी
Name[hr]=Sardinijski
Name[hsb]=Sardisce
Name[hu]=Szardíniai
Name[ia]=Sardo
Name[id]=Sardinian
Name[is]=Sardíníska
Name[it]=Sardo
Name[ja]=サルデーニャ語
Name[kk]=Сардинше
Name[km]=សារឌីណង់
Name[kn]=ಸಾರ್ಡೀನಿಯನ್
Name[ko]=사르디니아어
Name[ku]=Sardunî
Name[lb]=Sardinesch
Name[lt]=Sardinijos
Name[lv]=Sardīniešu
Name[mai]=सारडिनी
Name[mk]=Сардиниски
Name[ml]=സര്‍ഡീനിയന്‍
Name[mr]=सारदिनी
Name[ms]=Sardinian
Name[nb]=Sardisk
Name[nds]=Sardiensch
Name[ne]=सर्दिनियन
Name[nl]=Sardinisch
Name[nn]=Sardisk
Name[oc]=Sard
Name[or]=Sardinian
Name[pa]=ਸਾਰਡੀਨੀਆਈ
Name[pl]=Sardyński
Name[ps]=سارډينيايي
Name[pt]=Sardenho
Name[pt_BR]=Sardo
Name[ro]=Sardiniană
Name[ru]=Сардинский
Name[se]=Sardiniagiella
Name[si]=සාර්ඩිනියන්
Name[sk]=Sardínčina
Name[sl]=sardinsko
Name[sq]=Sardiniane
Name[sr]=сардинијски
Name[sr@ijekavian]=сардинијски
Name[sr@ijekavianlatin]=sardinijski
Name[sr@latin]=sardinijski
Name[sv]=Sardinska
Name[ta]=சார்டீனியன்
Name[te]=సార్డినియన్
Name[tg]=Сардиниягӣ
Name[th]=ภาษาซาร์ดิเนียน
Name[tr]=Sardinian
Name[tt]=Сардинский
Name[ug]=ساردىنىيەچە
Name[uk]=Сардинська
Name[uz]=Sardincha
Name[uz@cyrillic]=Сардинча
Name[vi]=Sardinia
Name[wa]=Sardinyin
Name[xh]=Sardinian
Name[x-test]=xxSardinianxx
Name[zh_CN]=撒丁语
Name[zh_HK]=薩丁尼亞語
Name[zh_TW]=薩丁尼亞語
[sd]
Name=Sindhi
Name[af]=Sindhi
Name[ar]=سندية
Name[as]=ছিন্ধি
Name[ast]=Sindhi
Name[be]=Сіндхі
Name[be@latin]=Sindhi
Name[bg]=Синдхи
Name[bn]=সিন্ধি
Name[bn_IN]=সিন্ধি
Name[br]=Sindhieg
Name[bs]=Sindi
Name[ca]=Sindhi
Name[ca@valencia]=Sindhi
Name[cs]=Sindhi
Name[csb]=Sindhi
Name[cy]=Sindhi
Name[da]=Sindhi
Name[de]=Sindhi
Name[el]=Sindhi
Name[en_GB]=Sindhi
Name[eo]=Sinda
Name[es]=Sindhi
Name[et]=Sindhi
Name[eu]=Sindhi
Name[fa]=سیندهی
Name[fi]=Sindhi
Name[fr]=Sindhi
Name[fy]=Sindhi
Name[ga]=Sindis
Name[gl]=Sindhi
Name[gu]=સિંધી
Name[he]=סינדהי
Name[hi]=सिंधी
Name[hne]=सिंधी
Name[hr]=Sindhi
Name[hsb]=Sindhi
Name[hu]=Szindi
Name[ia]=Sindi
Name[id]=Sindhi
Name[is]=Shindi
Name[it]=Sindhi
Name[ja]=シンド語
Name[kk]=Синдхи
Name[km]=ស៊ីនឌី
Name[kn]=ಸಿಂಧಿ
Name[ko]=신디어
Name[ku]=Sîndhî
Name[lb]=Sindhi-Sprooch
Name[lt]=Sindhi
Name[lv]=Sindhu
Name[mai]=सिन्धी
Name[mk]=Синди
Name[ml]=സിന്ധി
Name[mr]=सिंधी
Name[ms]=Sindhi
Name[nb]=Sindhi
Name[nds]=Sindhi
Name[ne]=सिन्धी
Name[nl]=Sindhi
Name[nn]=Sindhi
Name[or]=Sindhi
Name[pa]=ਸਿੰਧੀ
Name[pl]=Sindhi
Name[ps]=سندهي
Name[pt]=Sindhi
Name[pt_BR]=Sindi
Name[ro]=Sindi
Name[ru]=Синдхи
Name[se]=Sindhigiella
Name[si]=සින්දි
Name[sk]=Sindhčina
Name[sl]=sindijsko
Name[sq]=Sindhi
Name[sr]=синди
Name[sr@ijekavian]=синди
Name[sr@ijekavianlatin]=sindi
Name[sr@latin]=sindi
Name[sv]=Sindhi
Name[ta]=சிந்தி
Name[te]=సింధి
Name[tg]=Синдхӣ
Name[th]=ภาษาสินธุ
Name[tr]=Sindhi
Name[tt]=Синдхи
Name[ug]=سىندىچە
Name[uk]=Синдхі
Name[uz]=Sindxi
Name[uz@cyrillic]=Синдхи
Name[vi]=Sindhi
Name[wa]=Sindi
Name[xh]=Sindhi
Name[x-test]=xxSindhixx
Name[zh_CN]=信德语
Name[zh_HK]=Sindhi語
Name[zh_TW]=Sindhi語
[se]
Name=Northern Sami
Name[af]=Noordelike Sami
Name[ar]=صامية شمالية
Name[as]=ন'ৰ্দাৰ্ণ ছামি
Name[ast]=Sami del Norte
Name[be]=Самі (поўнач)
Name[be@latin]=Paŭnočnaje Sami
Name[bg]=Северен Сами
Name[bn]=উত্তর সামি
Name[bn_IN]=উত্তর সামি
Name[br]=Sami, Norzh
Name[bs]=Sjeverni sami
Name[ca]=Sami nord
Name[ca@valencia]=Sami nord
Name[cs]=Severní Sami
Name[csb]=Nordowi Sami
Name[cy]=Sami'r Gogledd
Name[da]=Nordsamisk
Name[de]=Nördliches Sami
Name[el]=Northern Sami
Name[en_GB]=Northern Sami
Name[eo]=Norda Samea
Name[es]=Samí del norte
Name[et]=Põhjasaami
Name[eu]=Samia (iparrekoa)
Name[fa]=سامی شمالی
Name[fi]=Pohjoissaame
Name[fr]=Sami du Nord
Name[fy]=Noard-Koreaansk
Name[ga]=Sáimis Thuaidh
Name[gl]=Sami do norte
Name[gu]=ઉત્તરી સામી
Name[he]=סאמית צפונית
Name[hi]=उत्तरी सामी
Name[hne]=उत्तरी सामी
Name[hr]=Sjeverni Sami
Name[hsb]=Sewjernosamisce
Name[hu]=Északi szami
Name[ia]=Sami Septentrional
Name[id]=Sami Bagian Utara
Name[is]=Norður Sami
Name[it]=Sami settentrionale
Name[ja]=北サーミ語
Name[kk]=Солтүстік саами
Name[km]=សាមី​ខាងជើង
Name[kn]=ಉತ್ತರ ಸಾಮೀ
Name[ko]=북부 사미어
Name[ku]=Samiya Bakûrî
Name[lb]=Nördlecht Sami
Name[lt]=Šiaurės Sami
Name[lv]=Ziemeļsāmu
Name[mai]=उत्तरी सामी
Name[mk]=Северен Сами
Name[ml]=വടക്കന്‍ സാമി
Name[mr]=उत्तरी सामी
Name[ms]=Sami Utara
Name[nb]=Nordsamisk
Name[nds]=Noord-Saamsch
Name[ne]=उत्तरी सामी
Name[nl]=Noord Sami
Name[nn]=Nordsamisk
Name[or]=Northern Sami
Name[pa]=ਉੱਤਰੀ ਸਾਮੀ
Name[pl]=Północny Sami
Name[ps]=شمالي سامي
Name[pt]=Sami do Norte
Name[pt_BR]=Sami setentrional
Name[ro]=Sami nordică
Name[ru]=Северносаамский
Name[se]=Davvisámegiella
Name[si]=උතුරු සැමි
Name[sk]=Lapončina (severná)
Name[sl]=severno sami
Name[sq]=Sami Veriore
Name[sr]=северни сами
Name[sr@ijekavian]=сјеверни сами
Name[sr@ijekavianlatin]=sjeverni sami
Name[sr@latin]=severni sami
Name[sv]=Samiska
Name[ta]=வடக்கு சாமி
Name[te]=ఉత్తర సమి
Name[tg]=Саамаи Шимолӣ
Name[th]=ภาษาซามี ตอนเหนือ
Name[tr]=Northern Sami
Name[tt]=Төньяк Саами
Name[ug]=شىمالىي سامىچە
Name[uk]=Північна Саамі
Name[uz]=Shimoliy Sami
Name[uz@cyrillic]=Шимолий Сами
Name[vi]=Bắc Sami
Name[wa]=Bijhe såmi
Name[xh]=Sami Yasemntla
Name[x-test]=xxNorthern Samixx
Name[zh_CN]=北萨米语
Name[zh_HK]=北薩米語
Name[zh_TW]=北薩米語
[sg]
Name=Sango
Name[af]=Sango
Name[ar]=سانغو
Name[as]=ছাঙ্গো
Name[ast]=Sango
Name[be]=Санга
Name[be@latin]=Sango
Name[bg]=Сангро
Name[bn]=সাংগো
Name[bn_IN]=সাঙ্গো
Name[br]=Sango
Name[bs]=Sango
Name[ca]=Sango
Name[ca@valencia]=Sango
Name[cs]=Sango
Name[csb]=Sango
Name[cy]=Sango
Name[da]=Sango
Name[de]=Sango
Name[el]=Sango
Name[en_GB]=Sango
Name[eo]=Sangoa
Name[es]=Sango
Name[et]=Sango
Name[eu]=Sango
Name[fa]=سانگو
Name[fi]=Sango
Name[fr]=Sango
Name[fy]=Sango
Name[ga]=Sangóis
Name[gl]=Sango
Name[gu]=સાન્ગો
Name[he]=סאנגו
Name[hi]=सैन्गो
Name[hne]=सैन्गो
Name[hr]=Sango
Name[hsb]=Sango
Name[hu]=Szangó
Name[ia]=Sango
Name[id]=Sango
Name[is]=Sango
Name[it]=Sango
Name[ja]=サンゴ語
Name[kk]=Санго
Name[km]=សង់ហ្គោ
Name[kn]=ಸಾಂಗೋ
Name[ko]=상고어
Name[ku]=Sango
Name[lb]=Sango-Sprooch
Name[lt]=Sango
Name[lv]=Sangu
Name[mai]=सैंगो
Name[mk]=Санго
Name[ml]=സാന്‍ഗോ
Name[mr]=सैन्गो
Name[ms]=Sango
Name[nb]=Sango
Name[nds]=Sango
Name[ne]=साङ्गो
Name[nl]=Sango
Name[nn]=Sango
Name[or]=Sango
Name[pa]=ਸਾਂਗੋ
Name[pl]=Sango
Name[ps]=سانګو
Name[pt]=Sango
Name[pt_BR]=Sango
Name[ro]=Sangă
Name[ru]=Санго
Name[se]=Sangogiella
Name[si]=සැන්ගෝ
Name[sk]=Sango
Name[sl]=sango
Name[sq]=Sango
Name[sr]=санго
Name[sr@ijekavian]=санго
Name[sr@ijekavianlatin]=sango
Name[sr@latin]=sango
Name[sv]=Sango
Name[ta]=சாங்கோ
Name[te]=సాంగొ
Name[tg]=Санго
Name[th]=ภาษาแซงโก
Name[tr]=Sango
Name[tt]=Санго
Name[ug]=سانگوچە
Name[uk]=Санго
Name[uz]=Sango
Name[uz@cyrillic]=Санго
Name[vi]=Sango
Name[wa]=Sango
Name[xh]=Sango
Name[x-test]=xxSangoxx
Name[zh_CN]=桑戈语
Name[zh_HK]=Sango語
Name[zh_TW]=Sango語
[si]
Name=Sinhala
Name[ar]=صنهالية
Name[ast]=Sinhala
Name[be@latin]=Sinhaleskaja
Name[bg]=Сингалски
Name[bn]=সিংহল
Name[bs]=Sinhalski
Name[ca]=Singalès
Name[ca@valencia]=Singalés
Name[cs]=Sinhalský
Name[csb]=Sinhala
Name[da]=Sinhala
Name[de]=Singhalesisch
Name[el]=Σινχάλα
Name[en_GB]=Sinhala
Name[es]=Cingalés
Name[et]=Singali
Name[eu]=Sinhala
Name[fa]=Sinhala
Name[fi]=Sinhala
Name[fr]=Cingalais
Name[fy]=Singhale
Name[ga]=Siolóinis
Name[gl]=Sinhala
Name[gu]=સિંહાલા
Name[he]=סינהלזית
Name[hi]=सिंहला
Name[hr]=Sinhalski
Name[hu]=Szingaléz
Name[ia]=Sinhala
Name[id]=Sinhala
Name[is]=Sinhalíska
Name[it]=Singalese
Name[ja]=シンハラ語
Name[kk]=Сингалша
Name[km]=ស៊ីនហាឡា
Name[kn]=ಸಿಂಹಳ
Name[ko]=신할라어
Name[ku]=Sinhala
Name[lt]=Sinhala
Name[lv]=Singāļu
Name[ml]=സിംഹള
Name[ms]=Sinhala
Name[nb]=Singalesisk
Name[nds]=Singaleesch
Name[nl]=Singalees
Name[nn]=Sinhala
Name[pa]=ਸਿੰਹਾਲਾ
Name[pl]=Sinhala
Name[pt]=Sinhala
Name[pt_BR]=Cingalês
Name[ro]=Sinhala
Name[ru]=Сингальский
Name[se]=Sinhalagiella
Name[si]=සිංහල
Name[sk]=Sinhalčina
Name[sl]=sinhalsko
Name[sq]=Sinhala
Name[sr]=синхалески
Name[sr@ijekavian]=синхалески
Name[sr@ijekavianlatin]=sinhaleski
Name[sr@latin]=sinhaleski
Name[sv]=Singalesiska
Name[ta]=சிங்களம்
Name[tg]=Синхала
Name[th]=ภาษาสิงหล
Name[tr]=Sinhala
Name[tt]=Сингал
Name[ug]=سىنگالچە
Name[uk]=Сингала
Name[vi]=Sinhala
Name[wa]=Sinhala
Name[x-test]=xxSinhalaxx
Name[zh_CN]=僧伽罗语
Name[zh_TW]=Sinhala
[sk]
Name=Slovak
Name[af]=Slovakies
Name[ar]=سلوفاكية
Name[as]=স্লোভাক
Name[ast]=Eslovacu
Name[be]=Славацкая
Name[be@latin]=Słavackaja
Name[bg]=Словашки
Name[bn]=স্লোভাক
Name[bn_IN]=স্লোভাক
Name[br]=Sloveg
Name[bs]=Slovački
Name[ca]=Eslovac
Name[ca@valencia]=Eslovac
Name[cs]=Slovenský
Name[csb]=Słowacczi
Name[cy]=Sloveg
Name[da]=Slovakisk
Name[de]=Slowakisch
Name[el]=Σλοβακικά
Name[en_GB]=Slovak
Name[eo]=Slovaka
Name[es]=Eslovaco
Name[et]=Slovaki
Name[eu]=Eslovakiera
Name[fa]=اسلواکی
Name[fi]=Slovakki
Name[fr]=Slovaque
Name[fy]=Slovaaks
Name[ga]=Slóvaicis
Name[gl]=Eslovaco
Name[gu]=સ્લોવેક
Name[he]=סלובקית
Name[hi]=स्लोवाक
Name[hne]=स्लोवाक
Name[hr]=Slovački
Name[hsb]=Słowaksce
Name[hu]=Szlovák
Name[ia]=Slovaco
Name[id]=Slovak
Name[is]=Slóvenska
Name[it]=Slovacco
Name[ja]=スロバキア語
Name[kk]=Словакша
Name[km]=ស្លូវ៉ាគី
Name[kn]=ಸ್ಲೋವಾಕ್
Name[ko]=슬로바키아어
Name[ku]=Slowakî
Name[lb]=Slowakesch
Name[lt]=Slovakų
Name[lv]=Slovāku
Name[mai]=स्लोवाक
Name[mk]=Словачки
Name[ml]=സ്ലൊവാക്യന്‍
Name[mr]=स्लोवाक
Name[ms]=Slovak
Name[nb]=Slovakisk
Name[nds]=Slowaaksch
Name[ne]=स्लोभाकी
Name[nl]=Slowaaks
Name[nn]=Slovakisk
Name[oc]=Eslovac
Name[or]=Slovak
Name[pa]=ਸਲੋਵਾਕ
Name[pl]=Słowacki
Name[ps]=سلواک
Name[pt]=Eslovaco
Name[pt_BR]=Eslovaco
Name[ro]=Slovacă
Name[ru]=Словацкий
Name[se]=Slovákagiella
Name[si]=ස්ලෝවැක්
Name[sk]=Slovenčina
Name[sl]=slovaško
Name[sq]=Sllovake
Name[sr]=словачки
Name[sr@ijekavian]=словачки
Name[sr@ijekavianlatin]=slovački
Name[sr@latin]=slovački
Name[sv]=Slovakiska
Name[ta]=சுலோவாக்
Name[te]=స్లొవాక్
Name[tg]=Словакӣ
Name[th]=ภาษาสโลวัค
Name[tr]=Slovakça
Name[tt]=Словак
Name[ug]=سىلوۋاكچە
Name[uk]=Словацька
Name[uz]=Slovakcha
Name[uz@cyrillic]=Словакча
Name[vi]=Slovak
Name[wa]=Eslovake
Name[xh]=isiSlovak
Name[x-test]=xxSlovakxx
Name[zh_CN]=斯洛伐克语
Name[zh_HK]=斯洛伐克語
Name[zh_TW]=斯洛伐克語
[sl]
Name=Slovenian
Name[af]=Sloveniese
Name[ar]=سلوفينية
Name[as]=স্লোভেনিয়ান
Name[ast]=Eslovenu
Name[be]=Славенская
Name[be@latin]=Słavienskaja
Name[bg]=Словенски
Name[bn]=স্লোভেনীয়
Name[bn_IN]=স্লোভেনিয়ান
Name[br]=Slovenieg
Name[bs]=Slovenački
Name[ca]=Eslovè
Name[ca@valencia]=Eslovè
Name[cs]=Slovinský
Name[csb]=Slowensczi
Name[cy]=Sloveneg
Name[da]=Slovensk
Name[de]=Slowenisch
Name[el]=Σλοβενικά
Name[en_GB]=Slovenian
Name[eo]=Slovena
Name[es]=Esloveno
Name[et]=Sloveeni
Name[eu]=Esloveniera
Name[fa]=اسلونیایی
Name[fi]=Sloveeni
Name[fr]=Slovène
Name[fy]=Sloveensk
Name[ga]=Slóivéinis
Name[gl]=Esloveno
Name[gu]=સ્લોવેનિયન
Name[he]=סלובנית
Name[hi]=स्लोवेनियाई
Name[hne]=स्लोवेनियाई
Name[hr]=Slovenski
Name[hsb]=Słowjensce
Name[hu]=Szlovén
Name[ia]=Sloveno
Name[id]=Slovenia
Name[is]=Slóvenska
Name[it]=Sloveno
Name[ja]=スロベニア語
Name[kk]=Словенше
Name[km]=ស្លូវ៉ានី
Name[kn]=ಸ್ಲೋವೇನಿಯನ್
Name[ko]=슬로베니아어
Name[ku]=Slovênî
Name[lb]=Slowenesch
Name[lt]=Slovėnų
Name[lv]=Slovēņu
Name[mai]=स्लोवानियाइ
Name[mk]=Словенечки
Name[ml]=സ്ലൊവേനിയന്‍
Name[mr]=स्लोवेनियन
Name[ms]=Slovenia
Name[nb]=Slovensk
Name[nds]=Sloweensch
Name[ne]=स्लोभेनियन
Name[nl]=Sloweens
Name[nn]=Slovensk
Name[oc]=Eslovèn
Name[or]=Slovenian
Name[pa]=ਸਲੋਵੀਨੀਆਈ
Name[pl]=Słoweński
Name[ps]=سلووېنين
Name[pt]=Esloveno
Name[pt_BR]=Esloveno
Name[ro]=Slovenă
Name[ru]=Словенский
Name[se]=Slovenagiella
Name[si]=ස්ලෝවීනියානු
Name[sk]=Slovinčina
Name[sl]=slovensko
Name[sq]=Sllovenisht
Name[sr]=словеначки
Name[sr@ijekavian]=словеначки
Name[sr@ijekavianlatin]=slovenački
Name[sr@latin]=slovenački
Name[sv]=Slovenska
Name[ta]=சுலோவீனியன்
Name[te]=స్లొవేనియన్
Name[tg]=Словенӣ
Name[th]=ภาษาสโลเวเนีย
Name[tr]=Slovence
Name[tt]=Словен
Name[ug]=سىلوۋېنىيەچە
Name[uk]=Словенська
Name[uz]=Sloveniancha
Name[uz@cyrillic]=Словенианча
Name[vi]=Slovenia
Name[wa]=Eslovenyin
Name[xh]=Slovenian
Name[x-test]=xxSlovenianxx
Name[zh_CN]=斯洛文尼亚语
Name[zh_HK]=斯洛維尼亞語
Name[zh_TW]=斯洛維尼亞語
[sm]
Name=Samoan
Name[af]=Samoan
Name[ar]=ساموية
Name[as]=চামোৱান
Name[ast]=Samoanu
Name[be]=Самоа
Name[be@latin]=Samoa
Name[bg]=Самоански
Name[bn]=সামোয়ান
Name[bn_IN]=সামোয়ান
Name[br]=Samoan
Name[bs]=Samoanski
Name[ca]=Samoà
Name[ca@valencia]=Samoà
Name[cs]=Samojský
Name[csb]=Samoańsczi
Name[cy]=Samoeg
Name[da]=Samoansk
Name[de]=Samoanisch
Name[el]=Σαμοανικά
Name[en_GB]=Samoan
Name[eo]=Samoa
Name[es]=Samoano
Name[et]=Samoa
Name[eu]=Samoera
Name[fa]=ساموان
Name[fi]=Samoa
Name[fr]=Samoan
Name[fy]=Samoaansk
Name[ga]=Samóis
Name[gl]=Samoano
Name[gu]=સામોન
Name[he]=סמואית
Name[hi]=सामोन
Name[hne]=सामोन
Name[hr]=Samoanski
Name[hsb]=Samoasce
Name[hu]=Szamoai
Name[ia]=Samoano
Name[id]=Samoa
Name[is]=Samoan
Name[it]=Samoano
Name[ja]=サモア語
Name[kk]=Самоа
Name[km]=សាមូអា
Name[kn]=ಸಮೋವನ್
Name[ko]=사모아어
Name[ku]=Samoyî
Name[lb]=Samoanesch
Name[lt]=Samoa
Name[lv]=Samoāņu
Name[mai]=सामोन
Name[mk]=Самоански
Name[ml]=സമോവന്‍
Name[mr]=सामोन
Name[ms]=Samoan
Name[nb]=Samoansk
Name[nds]=Samoaansch
Name[ne]=सामोआन
Name[nl]=Samoaans
Name[nn]=Samoansk
Name[or]=Samoan
Name[pa]=ਸਾਮੋਆਨ
Name[pl]=Samoański
Name[ps]=سموان
Name[pt]=Samoano
Name[pt_BR]=Samoano
Name[ro]=Samoană
Name[ru]=Самоанский
Name[se]=Samoagiella
Name[si]=සැමෝවන්
Name[sk]=Samojčina
Name[sl]=samojsko
Name[sq]=Samoane
Name[sr]=самоански
Name[sr@ijekavian]=самоански
Name[sr@ijekavianlatin]=samoanski
Name[sr@latin]=samoanski
Name[sv]=Samoanska
Name[ta]=சமோவன்
Name[te]=సమొవన్
Name[tg]=Самоанӣ
Name[th]=ภาษาซาโมน
Name[tr]=Samoan
Name[tt]=Самоа
Name[ug]=ساموئاچە
Name[uk]=Самоанська
Name[uz]=Samoacha
Name[uz@cyrillic]=Самоача
Name[vi]=Samoan
Name[wa]=Samowan
Name[xh]=Samoan
Name[x-test]=xxSamoanxx
Name[zh_CN]=萨摩亚语
Name[zh_HK]=薩摩牙語
Name[zh_TW]=薩摩亞語
[sn]
Name=Shona
Name[af]=Shona
Name[ar]=شونا
Name[as]=ছোনা
Name[ast]=Shona
Name[be]=Шона
Name[be@latin]=Šona
Name[bg]=Шона
Name[bn]=শোনা
Name[bn_IN]=শোনা
Name[br]=Shona
Name[bs]=Šona
Name[ca]=Shona
Name[ca@valencia]=Shona
Name[cs]=Shona
Name[csb]=Shona
Name[cy]=Shona
Name[da]=Shona
Name[de]=Shona
Name[el]=Shona
Name[en_GB]=Shona
Name[eo]=Ŝona
Name[es]=Shona
Name[et]=Shona
Name[eu]=Shona
Name[fa]=شوها
Name[fi]=Šona
Name[fr]=Shona
Name[fy]=Shona
Name[ga]=Seoinis
Name[gl]=Shona
Name[gu]=શોના
Name[he]=שונה
Name[hi]=शोना
Name[hne]=सोना
Name[hr]=Shona
Name[hsb]=Shona
Name[hu]=Sona
Name[ia]=Shona
Name[id]=Shona
Name[is]=Shona
Name[it]=Shona
Name[ja]=ショナ語
Name[kk]=Схона
Name[km]=សូណា
Name[kn]=ಶೋನಾ
Name[ko]=쇼나어
Name[ku]=Şona
Name[lb]=Shona
Name[lt]=Shona
Name[lv]=Šonu
Name[mai]=शोना
Name[mk]=Шона
Name[ml]=ഷോണാ
Name[mr]=शोना
Name[ms]=Shona
Name[nb]=Shona
Name[nds]=Schona
Name[ne]=शोना
Name[nl]=Shona
Name[nn]=Shona
Name[oc]=XChat
Name[or]=Shona
Name[pa]=ਸ਼ੋਨਾ
Name[pl]=Shona
Name[ps]=شونا
Name[pt]=Shona
Name[pt_BR]=Chona
Name[ro]=Șonă
Name[ru]=Шона
Name[se]=Šonagiella
Name[si]=ෂොනා
Name[sk]=Šona
Name[sl]=shona
Name[sq]=Shona
Name[sr]=шона
Name[sr@ijekavian]=шона
Name[sr@ijekavianlatin]=šona
Name[sr@latin]=šona
Name[sv]=Shona
Name[ta]=ஷோனா
Name[te]=షొనా
Name[tg]=Шонӣ
Name[th]=ภาษาโชนา
Name[tr]=Shona
Name[tt]=Шонача
Name[ug]=شوناچە
Name[uk]=Шона
Name[uz]=Shona
Name[uz@cyrillic]=Шона
Name[vi]=Shona
Name[wa]=Shona
Name[xh]=Isishona
Name[x-test]=xxShonaxx
Name[zh_CN]=修纳语
Name[zh_HK]=Shona語
Name[zh_TW]=Shona語
[so]
Name=Somali
Name[af]=Somali
Name[ar]=صومالية
Name[as]=চোমালি
Name[ast]=Somalí
Name[be]=Самалійская
Name[be@latin]=Samalijskaja
Name[bg]=Сомали
Name[bn]=সোমালীয়
Name[bn_IN]=সোমালি
Name[br]=Somalieg
Name[bs]=Somalijski
Name[ca]=Somalí
Name[ca@valencia]=Somalí
Name[cs]=Somálský
Name[csb]=Somalijsczi
Name[cy]=Somali
Name[da]=Somalisk
Name[de]=Somali
Name[el]=Σομαλικά
Name[en_GB]=Somali
Name[eo]=Somala
Name[es]=Somalí
Name[et]=Somaali
Name[eu]=Somaliera
Name[fa]=سومالی
Name[fi]=Somali
Name[fr]=Somali
Name[fy]=Somalysk
Name[ga]=Somáilis
Name[gl]=Somalí
Name[gu]=સોમાલી
Name[he]=סומלית
Name[hi]=सोमाली
Name[hne]=सोमाली
Name[hr]=Somalijski
Name[hsb]=Somalisce
Name[hu]=Szomáli
Name[ia]=Somali
Name[id]=Somali
Name[is]=Sómalska
Name[it]=Somalo
Name[ja]=ソマリ語
Name[kk]=Сомали
Name[km]=សូម៉ាលី
Name[kn]=ಸೋಮಾಲಿ
Name[ko]=소말리아어
Name[ku]=Somalî
Name[lb]=Somalesch
Name[lt]=Somaliečių
Name[lv]=Somāļu
Name[mai]=सोमाली
Name[mk]=Сомалиски
Name[ml]=സോമാലി
Name[mr]=सोमाली
Name[ms]=Somali
Name[nb]=Somali
Name[nds]=Somali
Name[ne]=सोमाली
Name[nl]=Somalisch
Name[nn]=Somali
Name[oc]=Somali
Name[or]=Somali
Name[pa]=ਸੋਮਾਲੀ
Name[pl]=Somalijski
Name[ps]=سومالي
Name[pt]=Somálio
Name[pt_BR]=Somali
Name[ro]=Somaleză
Name[ru]=Сомалийский
Name[se]=Somálagiella
Name[si]=සෝමාලි
Name[sk]=Somálčina
Name[sl]=somalsko
Name[sq]=Somali
Name[sr]=сомалијски
Name[sr@ijekavian]=сомалијски
Name[sr@ijekavianlatin]=somalijski
Name[sr@latin]=somalijski
Name[sv]=Somali
Name[ta]=சோமாலி
Name[te]=సొమాలి
Name[tg]=Сомалӣ
Name[th]=ภาษาโซมาลี
Name[tr]=Somalice
Name[tt]=Сомалийский
Name[ug]=سومالىچە
Name[uk]=Сомалійська
Name[uz]=Somalicha
Name[uz@cyrillic]=Сомалича
Name[vi]=Somali
Name[wa]=Somalyin
Name[xh]=Somali
Name[x-test]=xxSomalixx
Name[zh_CN]=索马里语
Name[zh_HK]=索馬利語
Name[zh_TW]=索馬利語
[sq]
Name=Albanian
Name[af]=Albanees
Name[ar]=ألبانية
Name[as]=আল্বানিয়ান
Name[ast]=Albanés
Name[be]=Албанская
Name[be@latin]=Albanskaja
Name[bg]=Албански
Name[bn]=আলবেনীয়
Name[bn_IN]=আলবেনিয়ান
Name[br]=Albanieg
Name[bs]=Albanski
Name[ca]=Albanès
Name[ca@valencia]=Albanés
Name[cs]=Albánský
Name[csb]=Albańsczi
Name[cy]=Albaneg
Name[da]=Albansk
Name[de]=Albanisch
Name[el]=Αλβανικά
Name[en_GB]=Albanian
Name[eo]=Albana
Name[es]=Albanés
Name[et]=Albaania
Name[eu]=Albaniera
Name[fa]=آلبانی
Name[fi]=Albania
Name[fr]=Albanais
Name[fy]=Albaansk
Name[ga]=Albáinis
Name[gl]=Albanés
Name[gu]=અલ્બેનિયન
Name[he]=אלבנית
Name[hi]=अल्बानियाई
Name[hne]=अल्बानियाई
Name[hr]=Albanski
Name[hsb]=Albansce
Name[hu]=Albán
Name[ia]=Albanese
Name[id]=Albania
Name[is]=Albanskur
Name[it]=Albanese
Name[ja]=アルバニア語
Name[kk]=Албанша
Name[km]=អាល់បានី
Name[kn]=ಅಲ್ಬೇನಿಯನ್
Name[ko]=알바니아어
Name[ku]=Albanî
Name[lb]=Albanesch
Name[lt]=Albanų
Name[lv]=Albāņu
Name[mai]=अल्बानियाइ
Name[mk]=Албански
Name[ml]=അല്ബേനിയന്‍
Name[mr]=अल्बानियाई
Name[ms]=Albania
Name[nb]=Albansk
Name[nds]=Albaansch
Name[ne]=अल्बानियाली
Name[nl]=Albanisch
Name[nn]=Albansk
Name[oc]=Albanés
Name[or]=Albanian
Name[pa]=ਅਲਬਾਨੀਆਈ
Name[pl]=Albański
Name[ps]=الباني
Name[pt]=Albanês
Name[pt_BR]=Albanês
Name[ro]=Albaneză
Name[ru]=Албанский
Name[se]=Albániagiella
Name[si]=ඇල්බේනියානු
Name[sk]=Albánčina
Name[sl]=albansko
Name[sq]=Shqipe
Name[sr]=албански
Name[sr@ijekavian]=албански
Name[sr@ijekavianlatin]=albanski
Name[sr@latin]=albanski
Name[sv]=Albanska
Name[ta]=அல்பேனியன்
Name[te]=అల్బేనియన్
Name[tg]=Албанӣ
Name[th]=ภาษาอัลเบเนีย
Name[tr]=Albanian
Name[tt]=Албан
Name[ug]=ئالبانچە
Name[uk]=Албанська
Name[uz]=Albancha
Name[uz@cyrillic]=Албанча
Name[vi]=Albania
Name[wa]=Albanyin
Name[xh]=Albanian
Name[x-test]=xxAlbanianxx
Name[zh_CN]=阿尔巴尼亚语
Name[zh_HK]=阿爾巴尼亞語
Name[zh_TW]=阿爾巴尼亞語
[sr]
Name=Serbian
Name[af]=Serbiese
Name[ar]=صربية
Name[as]=ছাৰ্বিয়ান
Name[ast]=Serbiu
Name[be]=Сербская
Name[be@latin]=Serbskaja
Name[bg]=Сръбски
Name[bn]=সার্বীয়
Name[bn_IN]=সার্বিয়ান
Name[br]=Serbeg
Name[bs]=Srpski
Name[ca]=Serbi
Name[ca@valencia]=Serbi
Name[cs]=Srbský
Name[csb]=Serbsczi
Name[cy]=Serbieg
Name[da]=Serbisk
Name[de]=Serbisch
Name[el]=Σερβικά
Name[en_GB]=Serbian
Name[eo]=Serba
Name[es]=Serbio
Name[et]=Serbia
Name[eu]=Serbiera
Name[fa]=صربستانی
Name[fi]=Serbia
Name[fr]=Serbe
Name[fy]=Servysk
Name[ga]=Seirbis
Name[gl]=Serbio
Name[gu]=સર્બિયન
Name[he]=סרבית
Name[hi]=सर्बियाई
Name[hne]=सर्बियाई
Name[hr]=Srpski
Name[hsb]=Serbisce
Name[hu]=Szerb
Name[ia]=Serbo
Name[id]=Serbia
Name[is]=Serbneska
Name[it]=Serbo
Name[ja]=セルビア語
Name[kk]=Сербше
Name[km]=សែប៊ី
Name[kn]=ಸರ್ಬಿಯನ್
Name[ko]=세르비아어
Name[ku]=Sirbî
Name[lb]=Serbesch
Name[lt]=Serbų
Name[lv]=Serbu
Name[mai]=सर्बियन
Name[mk]=Српски
Name[ml]=സെര്‍ബിയന്‍
Name[mr]=सर्बियाई
Name[ms]=Serbia
Name[nb]=Serbisk
Name[nds]=Serbsch
Name[ne]=सर्बियाली
Name[nl]=Servisch
Name[nn]=Serbisk
Name[oc]=Serbian
Name[or]=Serbian
Name[pa]=ਸਰਬੀਆਈ
Name[pl]=Serbski
Name[ps]=سربين
Name[pt]=Sérvio
Name[pt_BR]=Sérvio
Name[ro]=Sîrbă
Name[ru]=Сербский
Name[se]=Serbiagiella
Name[si]=සරිබියානු
Name[sk]=Srbčina
Name[sl]=srbsko
Name[sq]=Serbe
Name[sr]=српски
Name[sr@ijekavian]=српски
Name[sr@ijekavianlatin]=srpski
Name[sr@latin]=srpski
Name[sv]=Serbiska
Name[ta]=செர்பியன்
Name[te]=సెర్బియన్
Name[tg]=Сербӣ
Name[th]=ภาษาเซอร์เบีย
Name[tr]=Sırpça
Name[tt]=Сербия
Name[ug]=سېربچە
Name[uk]=Сербська
Name[uz]=Serbcha
Name[uz@cyrillic]=Сербча
Name[vi]=Serbia
Name[wa]=Siebe
Name[xh]=Serbian
Name[x-test]=xxSerbianxx
Name[zh_CN]=塞尔维亚语
Name[zh_HK]=塞爾維亞語
Name[zh_TW]=塞爾維亞語
[sr@ijekavian]
Name=Serbian Ijekavian
Name[ar]=صربية اليكافيانية
Name[bg]=Сръбски йекавски
Name[bn]=সার্বীয় ইজেকাভিয়ান
Name[bs]=Srpski(ijekavski)
Name[ca]=Serbi ijekavski
Name[ca@valencia]=Serbi ijekavski
Name[cs]=Srbský Ijekavský
Name[da]=Serbisk Ijekaviansk
Name[de]=Štokavisch
Name[el]=Σερβικά Ijekavian
Name[en_GB]=Serbian Ijekavian
Name[es]=Serbio ijekaviano
Name[et]=Serbia ijekavi
Name[eu]=Serbiera ijekaviera
Name[fa]=صربی Ijekavian
Name[fi]=Serbia Ijekavian
Name[fr]=Serbe Ijékavien
Name[ga]=Seirbis Ijekavach
Name[gl]=Serbio ixtocavio
Name[hr]=Srpska ijekavica
Name[hu]=Szerb (ijekavica)
Name[ia]=Serbo Ljekavian
Name[id]=Ijekavian Serbia
Name[is]=Serbneska Ijekavian
Name[it]=Serbo ijekavo
Name[ja]=セルビア語イェ方言
Name[kk]=Иекав сербше
Name[km]=សែប៊ីយេកាវៀន
Name[ko]=세르비아어 (이예카비아)
Name[ku]=Sirbiya Ijekavian
Name[ms]=Serbian Ijekavian
Name[nb]=Serbisk ljekavisk
Name[nds]=Štokavisch
Name[nl]=Servisch Ijekavian
Name[nn]=Serbisk (ijekavisk)
Name[pa]=ਸਰਬੀਆਈ ਲਜੀਕਾਵਿਅਨ
Name[pl]=Serbski ijekawski
Name[pt]=Sérvio Ijekavian
Name[pt_BR]=Sérvio ijekavian
Name[ro]=Sîrbă jekaviană
Name[ru]=Сербский (иекавский)
Name[se]=Serbialaš ljekávagiella
Name[si]=සරිබියානු ල්ජෙකවියන්
Name[sk]=Srbčina (ijekavština)
Name[sq]=Serbe ijekave
Name[sr]=српски ијекавски
Name[sr@ijekavian]=српски ијекавски
Name[sr@ijekavianlatin]=srpski ijekavski
Name[sr@latin]=srpski ijekavski
Name[sv]=Ijekavisk serbiska
Name[ta]=செர்பியன் ஜெகாவியன்
Name[tg]=Сербиявӣ
Name[th]=ภาษาเซอร์เบียอิเจ็คคาเวีย
Name[tr]=Sırpça Ijekavian
Name[tt]=Сербия (Штокав д.)
Name[ug]=سېربچە ئىجېكاۋىيان
Name[uk]=Сербська (ієкавиця)
Name[vi]=Serbian Ijekavian
Name[wa]=Siebe Ijekavyin
Name[x-test]=xxSerbian Ijekavianxx
Name[zh_CN]=塞尔维亚 Ijekavian 口音语言
Name[zh_TW]=賽爾維亞耶卡語
[sr@ijekavianlatin]
Name=Serbian Ijekavian Latin
Name[ar]=الصربية اليكافيانية اللاتينيّة
Name[bg]=Сръбски йекавски (латиница)
Name[bn]=সার্বীয় ইজেকাভিয়ান লাতিন
Name[bs]=Srpski(ijekavski latinica)
Name[ca]=Serbi ijekavski llatí
Name[ca@valencia]=Serbi ijekavski llatí
Name[cs]=Srbský (Ijekavská latinka)
Name[da]=Serbisk Ijekaviansk latin
Name[de]=Štokavisch (lat. Alphabet)
Name[el]=Σερβικά Ijekavian (Λατινικά)
Name[en_GB]=Serbian Ijekavian Latin
Name[es]=Serbio latino ijekaviano
Name[et]=Serbia ijekavi (ladina)
Name[eu]=Serbiera ijekaviera latindarra
Name[fa]=صربی Ijekavian لاتین
Name[fi]=Serbia Ijekavian latinalainen
Name[fr]=Serbe Ijékavien Latin
Name[ga]=Seirbis Ijekavach (aibítir Laidineach)
Name[gl]=Serbio ishtocavio Latino
Name[hr]=Srpska ijekavica, latinica
Name[hu]=Szerb (ijekavica, latin betűs)
Name[ia]=Serbo Latino Ljekavian
Name[id]=Latin Ijekavian Serbia
Name[is]=Serbneska Ijekavian latneskt
Name[it]=Serbo ijekavo latino
Name[ja]=セルビア語イェ方言 (ラテン文字)
Name[kk]=Иекав сербше (Латын)
Name[km]=សែប៊ី​យេកាវៀន (ឡាតាំង)
Name[ko]=세르비아어 (이예카비아, 라틴 문자)
Name[ku]=Sirbiya Ijekavian ya Latînî
Name[ms]=Serbian Ijekavian Latin
Name[nb]=Serbisk ljekavisk latinsk
Name[nds]=Štokavisch (latiensch Schrift)
Name[nl]=Servisch Ijekavian Latijn
Name[nn]=Serbisk (ijekavisk – romanisert)
Name[pa]=ਸਰਬੀਆਈ ਲਜੀਕਾਵਿਅਨ ਲੈਟਿਨ
Name[pl]=Serbski ijekawski łaciński
Name[pt]=Sérvio Latino Ijekavian
Name[pt_BR]=Sérvio latino ijekavian
Name[ro]=Sîrbă jekaviană latină
Name[ru]=Сербский (иекавский, латиница)
Name[se]=Serbialaš ljekávagiella (Latiinnalaš čállinvuohki)
Name[si]=සරිබියානු ල්ජෙකවියන් ලතින්
Name[sk]=Srbčina (ijekavština - latinka)
Name[sq]=Serbe ijekave latine
Name[sr]=српски ијекавски (латиница)
Name[sr@ijekavian]=српски ијекавски (латиница)
Name[sr@ijekavianlatin]=srpski ijekavski (latinica)
Name[sr@latin]=srpski ijekavski (latinica)
Name[sv]=Latinsk ijekavisk serbiska
Name[ta]=செர்பியன் ஜெகாவியன் இலத்தீன்
Name[tg]=Лотинии Сербиявӣ
Name[th]=ภาษาละตินเซอร์เบียอิเจ็คคาเวีย
Name[tr]=Sırpça Ijekavian Latin
Name[tt]=Сербия (Штокав д., латин)
Name[ug]=سېربچە ئىجېكاۋىيان لاتىن
Name[uk]=Сербська (ієкавиця, латиниця)
Name[vi]=Serbian Ijekavian Latin
Name[wa]=Siebe Ijekavyin èn alfabet latén
Name[x-test]=xxSerbian Ijekavian Latinxx
Name[zh_CN]=塞尔维亚 Ijekavian 口音拉丁语
Name[zh_TW]=賽爾維亞耶卡拉丁語
[sr@latin]
Name=Serbian Latin
Name[ar]=صربية لاتينية
Name[as]=ছাৰ্বিয়ান লেটিন
Name[ast]=Serbiu Llatinu
Name[be]=Сербская (лацініца)
Name[be@latin]=Serbskaja łacinka
Name[bg]=Сръбски (латиница)
Name[bn]=সার্বীয় লাতিন
Name[bn_IN]=সার্বিয়ান লাতিন
Name[br]=Serbeg latin
Name[bs]=Srpski(latinica)
Name[ca]=Serbi llatí
Name[ca@valencia]=Serbi llatí
Name[cs]=Srbský (latinka)
Name[csb]=Serbsczi (łacëńsczi)
Name[cy]=Serbieg Lladin
Name[da]=Serbisk latin
Name[de]=Serbisch (lat. Alphabet)
Name[el]=Σερβικά (Λατινικά)
Name[en_GB]=Serbian Latin
Name[eo]=Serba-Latina
Name[es]=Serbio latino
Name[et]=Serbia (ladina)
Name[eu]=Serbiera latindarra
Name[fa]=صربستانی
Name[fi]=Serbia latinanalainen
Name[fr]=Serbe Latin
Name[fy]=Servysk Latijnsk
Name[ga]=Seirbis (aibítir Laidineach)
Name[gl]=Serbio latino
Name[gu]=સર્બિયન લેટિન
Name[he]=סרבית לטינית
Name[hi]=सर्बियाई लातिनी
Name[hne]=सर्बियाई लातिनी
Name[hr]=Srpski latinica
Name[hsb]=Serbisce (z łaćonskim pismom)
Name[hu]=Szerb (latin betűs)
Name[ia]=Serbo Latino
Name[id]=Latin Serbia
Name[is]=Serbnesk latína
Name[it]=Serbo latino
Name[ja]=セルビア語 (ラテン文字)
Name[kk]=Сербше (Латын)
Name[km]=សែប៊ី (ឡាតាំង)
Name[kn]=ಸರ್ಬಿಯನ್ ಲಾಟಿನ್
Name[ko]=세르비아어 (라틴 문자)
Name[ku]=Sirbî bi tîpên latînî
Name[lb]=Latäinescht Serbesch
Name[lt]=Serbų lotynų
Name[lv]=Serbu latīņu
Name[mai]=सर्बियाई लातिनी
Name[mk]=Српски (латиница)
Name[ml]=സെര്‍ബിയന്‍ ലത്തീന്‍
Name[mr]=सर्बीयन लॅटिन
Name[ms]=Latin Serbia
Name[nb]=Serbisk (latinsk)
Name[nds]=Serbsch (latiensch Schrift)
Name[ne]=सर्बियाली ल्याटिन
Name[nl]=Servisch Latijn
Name[nn]=Serbisk (romanisert)
Name[or]=Serbian Latin
Name[pa]=ਸਰਬੀਆਈ ਲੈਟਿਨ
Name[pl]=Serbski łaciński
Name[ps]=سربيايي لاټيني
Name[pt]=Sérvio Latino
Name[pt_BR]=Sérvio latino
Name[ro]=Sîrbă latină
Name[ru]=Сербский (латиница)
Name[se]=Serbialaš latiidnagiella
Name[si]=සරිබියානු ලතින්
Name[sk]=Srbčina (latinka)
Name[sl]=srbsko latinsko
Name[sq]=Serbe Latine
Name[sr]=српски (латиница)
Name[sr@ijekavian]=српски (латиница)
Name[sr@ijekavianlatin]=srpski (latinica)
Name[sr@latin]=srpski (latinica)
Name[sv]=Latinsk serbiska
Name[ta]=செர்பியன் இலத்தீன்
Name[te]=సెర్బియన్ లాటిన్
Name[tg]=Лотинии Сербиявӣ
Name[th]=ภาษาละตินเซอร์เบีย
Name[tr]=Sırpça Latin
Name[tt]=Сербия (Латин)
Name[ug]=سېربچە لاتىن
Name[uk]=Сербська (латиниця)
Name[uz]=Serbcha (Lotin)
Name[uz@cyrillic]=Сербча (Лотин)
Name[vi]=Xéc-bi (La-tinh)
Name[wa]=Siebe en alfabet latén
Name[x-test]=xxSerbian Latinxx
Name[zh_CN]=塞尔维亚拉丁语
Name[zh_TW]=賽爾維亞拉丁語
[ss]
Name=Swati
Name[af]=Swati
Name[ar]=سواتي
Name[as]=ছ্বাটি
Name[ast]=Swatí
Name[be]=Сваці
Name[be@latin]=Swati
Name[bg]=Суази
Name[bn]=সোয়াতি
Name[bn_IN]=সোয়াতি
Name[br]=Swati
Name[bs]=Svati
Name[ca]=Swati
Name[ca@valencia]=Swati
Name[cs]=Swati
Name[csb]=Swati
Name[cy]=Swati
Name[da]=Swati
Name[de]=Swati
Name[el]=Swati
Name[en_GB]=Swati
Name[eo]=Svazia
Name[es]=Swati
Name[et]=Svaasi
Name[eu]=Swati
Name[fa]=سواتی
Name[fi]=Swazi
Name[fr]=Swati
Name[fy]=Swati
Name[ga]=Suaisis
Name[gl]=Swati
Name[gu]=સ્વાતિ
Name[he]=סוואטי
Name[hi]=स्वाती
Name[hne]=स्वाती
Name[hr]=Swati
Name[hsb]=Swati
Name[hu]=Szvati
Name[ia]=Swati
Name[id]=Swati
Name[is]=Swati
Name[it]=Swati
Name[ja]=スワティ語
Name[kk]=Свати
Name[km]=ស្វាទី
Name[kn]=ಸ್ವಾಟಿ
Name[ko]=스와티어
Name[ku]=Swatî
Name[lb]=Swazi
Name[lt]=Swati
Name[lv]=Svatu
Name[mai]=स्वाती
Name[mk]=Свати
Name[ml]=സ്വാതി
Name[mr]=स्वाती
Name[ms]=Swati
Name[nb]=Swati
Name[nds]=Swati
Name[ne]=स्वाती
Name[nl]=Swati
Name[nn]=Swati
Name[oc]=Swati
Name[or]=Swati
Name[pa]=ਸਵਾਟੀ
Name[pl]=Swati
Name[ps]=سواټي
Name[pt]=Swati
Name[pt_BR]=Suázi
Name[ro]=Suată
Name[ru]=Свати
Name[se]=Svatigiella
Name[si]=ස්වැටි
Name[sk]=Swati
Name[sl]=swati
Name[sq]=Swati
Name[sr]=свати
Name[sr@ijekavian]=свати
Name[sr@ijekavianlatin]=svati
Name[sr@latin]=svati
Name[sv]=Swazi
Name[ta]=சுவாடி
Name[te]=స్వాతి
Name[tg]=Сватӣ
Name[th]=ภาษาสวาตี
Name[tr]=Swati
Name[tt]=Свати
Name[ug]=سىۋاتىچە
Name[uk]=Свазі
Name[uz]=Svati
Name[uz@cyrillic]=Свати
Name[vi]=Swati
Name[wa]=Suwati
Name[xh]=Isiswati
Name[x-test]=xxSwatixx
Name[zh_CN]=斯瓦蒂语
Name[zh_HK]=Swati語
Name[zh_TW]=Swati語
[st]
Name=Sotho, Southern
Name[af]=Sotho, Suid
Name[ar]=سوتو شمالية
Name[as]=ছো'থো', ছাউদাৰ্ণ
Name[ast]=Sotho del Sur
Name[be]=Сота (поўдзень)
Name[be@latin]=Sota (poŭdzień)
Name[bg]=Южен Сото
Name[bn]=সোথো, দক্ষিণ
Name[bn_IN]=সোথো, দক্ষিণ
Name[br]=Soto, Su
Name[bs]=Soto, južni
Name[ca]=Sotho sud
Name[ca@valencia]=Sotho sud
Name[cs]=Sotho, Jižní
Name[csb]=Sotho, Pôłniowi
Name[cy]=Sotho, De
Name[da]=Sotho, syd
Name[de]=Südliches Sotho
Name[el]=Sotho, Southern
Name[en_GB]=Sotho, Southern
Name[eo]=Sota, suda
Name[es]=Sotho del sur
Name[et]=Lõuna-sotho
Name[eu]=Sothoera (egoaldekoa)
Name[fa]=سوتوی جنوبی
Name[fi]=Eteläsotho
Name[fr]=Sotho du Sud
Name[fy]=Sotho, Súd
Name[ga]=Sótó Theas
Name[gl]=Sotho do sur
Name[gu]=સોથા, દક્ષિણી
Name[he]=סותו דרומית
Name[hi]=सोथो, दक्षिणी
Name[hne]=सोथो, दक्छिनी
Name[hr]=Sotho, Južni
Name[hsb]=Sotho (juh)
Name[hu]=Sotho (déli)
Name[ia]=Sotho Meridional
Name[id]=Sotho, Bagian Selatan
Name[is]=Sotho, suður
Name[it]=Sesotho meridionale
Name[ja]=南ソト語
Name[kk]=Оңтүстік сото
Name[km]=សូធូ​ ​ត្បូង
Name[kn]=ಸೋಥೋ, ದಕ್ಷಿಣ
Name[ko]=남부 소토어
Name[ku]=Sotho, Başûrî
Name[lb]=Südlecht Sotho
Name[lt]=Sotho, Pietų
Name[lv]=Dienvidsotu
Name[mai]=सोथो, दक्षिणी
Name[mk]=Сото, јужен
Name[ml]=തെക്കന്‍ സോതോ
Name[mr]=सोथो, दक्षिणी
Name[ms]=Sotho, Selatan
Name[nb]=Sotho, Sørlig
Name[nds]=Sotho, Sööd
Name[ne]=सोथो, दक्षिणी
Name[nl]=Sotho, Zuid
Name[nn]=Sør-Sotho
Name[or]=Sotho, Southern
Name[pa]=ਸੋਥੋ, ਦੱਖਣੀ
Name[pl]=Sotho, Południowy
Name[ps]=سوټو، سهېلي
Name[pt]=Sotho do Sul
Name[pt_BR]=Sotho meridional
Name[ro]=Soto sudică
Name[ru]=Южный сото
Name[se]=Sothogiella, lulli
Name[si]=සෝතෝ, දකුණු
Name[sk]=Sothčina, južná
Name[sl]=sotho, južni
Name[sq]=Sotho, Jugore
Name[sr]=сото, јужни
Name[sr@ijekavian]=сото, јужни
Name[sr@ijekavianlatin]=soto, južni
Name[sr@latin]=soto, južni
Name[sv]=Sydsotho
Name[ta]=தென்சோத்தோ
Name[te]=సోతొ, దక్షిణ
Name[tg]=Соттоии Ҷанубӣ
Name[th]=ภาษาโซโธ ตอนใต้
Name[tr]=Sotho, Southern
Name[tt]=Сотхоча (Көньяк)
Name[ug]=سوتوچە، شىمالىي
Name[uk]=Сото, Південна
Name[uz]=Sotxo, Janubiy
Name[uz@cyrillic]=Сотхо, Жанубий
Name[vi]=Sotho, Nam
Name[wa]=Soto (nonne)
Name[xh]=Isisuthu, Sasemzantsi
Name[x-test]=xxSotho, Southernxx
Name[zh_CN]=索托语(南部)
Name[zh_HK]=梭托語,南部
Name[zh_TW]=梭托語,南部
[su]
Name=Sundanese
Name[af]=Sundanese
Name[ar]=سندانية
Name[as]=ছান্দানিছ
Name[ast]=Sundanés
Name[be]=Сунданская
Name[be@latin]=Sundaneskaja
Name[bg]=Сундански
Name[bn]=সুন্দানিজ
Name[bn_IN]=সুদানিজ
Name[br]=Soudaneg
Name[bs]=Sundski
Name[ca]=Sundanès
Name[ca@valencia]=Sundanés
Name[cs]=Sundanský
Name[csb]=Sudańsczi
Name[cy]=Sundaneg
Name[da]=Sundansk
Name[de]=Sundanesisch
Name[el]=Sundanese
Name[en_GB]=Sundanese
Name[eo]=Sunda
Name[es]=Sudanés
Name[et]=Sunda
Name[eu]=Sundanera
Name[fa]=ساندانیز
Name[fi]=Sunda
Name[fr]=Soudanais
Name[fy]=Soendaneesk
Name[ga]=Sundais
Name[gl]=Sudanés
Name[gu]=સુદાનિઝ
Name[he]=סודנית
Name[hi]=सूडानी
Name[hne]=सूडानी
Name[hr]=Sundanese
Name[hsb]=Sundanesce
Name[hu]=Szundanéz
Name[ia]=Sundanese
Name[id]=Sunda
Name[is]=Sundanese
Name[it]=Sondanese
Name[ja]=スンダ語
Name[kk]=Суданша
Name[km]=ស៊ូដង់
Name[kn]=ಸುಡಾನೀಸ್
Name[ko]=수단어
Name[ku]=Sundî
Name[lb]=Sudanesesch
Name[lt]=Sudaniečių
Name[lv]=Zundu
Name[mai]=सूडानी
Name[mk]=Сундански
Name[ml]=സുഡാനീസ്
Name[mr]=सूडानी
Name[ms]=Sundanese
Name[nb]=Sundanesisk
Name[nds]=Sundaneesch
Name[ne]=सुडानी
Name[nl]=Sundanees
Name[nn]=Sundanesisk
Name[or]=Sundanese
Name[pa]=ਸੂਡਾਨੀਅਸ
Name[pl]=Sudański
Name[ps]=سوډاني
Name[pt]=Sudanês
Name[pt_BR]=Sudanês
Name[ro]=Sudaneză
Name[ru]=Сунданский
Name[se]=Sundanesagiella
Name[si]=සුන්දනීස්
Name[sk]=Sundčina
Name[sl]=sudansko
Name[sq]=Sundaneze
Name[sr]=сундански
Name[sr@ijekavian]=сундански
Name[sr@ijekavianlatin]=sundanski
Name[sr@latin]=sundanski
Name[sv]=Sundanesiska
Name[ta]=சூடானீயம்
Name[te]=సన్దనీస్
Name[tg]=Санданизӣ
Name[th]=ภาษาซูดาน
Name[tr]=Sundanese
Name[tt]=Сунданез
Name[ug]=سۇنداچە
Name[uk]=Сунданська
Name[uz]=Sundancha
Name[uz@cyrillic]=Сунданча
Name[vi]=Sundan
Name[wa]=Soudanès
Name[xh]=Sundanese
Name[x-test]=xxSundanesexx
Name[zh_CN]=巽他语
Name[zh_HK]=Sundanese語
Name[zh_TW]=Sundanese語
[sv]
Name=Swedish
Name[af]=Sweeds
Name[ar]=سويدية
Name[as]=ছুইদিশ্ব
Name[ast]=Suecu
Name[be]=Шведская
Name[be@latin]=Švedzkaja
Name[bg]=Шведски
Name[bn]=সুইডিশ
Name[bn_IN]=সুইডিশ
Name[br]=Svedeg
Name[bs]=Švedski
Name[ca]=Suec
Name[ca@valencia]=Suec
Name[cs]=Švédský
Name[csb]=Szwedzczi
Name[cy]=Swedeg
Name[da]=Svensk
Name[de]=Schwedisch
Name[el]=Σουηδικά
Name[en_GB]=Swedish
Name[eo]=Sveda
Name[es]=Sueco
Name[et]=Rootsi
Name[eu]=Suediera
Name[fa]=سوئدی
Name[fi]=Ruotsi
Name[fr]=Suédois
Name[fy]=Sweedsk
Name[ga]=Sualainnis
Name[gl]=Sueco
Name[gu]=સ્વિડિશ
Name[he]=שבדית
Name[hi]=स्वीडिश
Name[hne]=स्वीडिस
Name[hr]=Švedski
Name[hsb]=Šwedsce
Name[hu]=Svéd
Name[ia]=Svedo
Name[id]=Swedia
Name[is]=Sænska
Name[it]=Svedese
Name[ja]=スウェーデン語
Name[kk]=Шведше
Name[km]=ស៊ុយអែដ
Name[kn]=ಸ್ವೀಡಿಷ್
Name[ko]=스웨덴어
Name[ku]=Swêdî
Name[lb]=Schwedesch
Name[lt]=Švedų
Name[lv]=Zviedru
Name[mai]=स्वीडिश
Name[mk]=Шведски
Name[ml]=സ്വീഡിഷ്
Name[mr]=स्वीडिश
Name[ms]=Swedish
Name[nb]=Svensk
Name[nds]=Sweedsch
Name[ne]=स्विडेनी
Name[nl]=Zweeds
Name[nn]=Svensk
Name[oc]=Suedés
Name[or]=Swedish
Name[pa]=ਸਵੀਡਿਸ਼
Name[pl]=Szwedzki
Name[ps]=سوېډېش
Name[pt]=Sueco
Name[pt_BR]=Sueco
Name[ro]=Suedeză
Name[ru]=Шведский
Name[se]=Ruoŧagiella
Name[si]=ස්වීඩන්
Name[sk]=Švédčina
Name[sl]=švedsko
Name[sq]=Suedeze
Name[sr]=шведски
Name[sr@ijekavian]=шведски
Name[sr@ijekavianlatin]=švedski
Name[sr@latin]=švedski
Name[sv]=Svenska
Name[ta]=சுவீடிஷ்
Name[te]=స్వీడిష్
Name[tg]=Шведӣ
Name[th]=ภาษาสวีเดน
Name[tr]=İsveççe
Name[tt]=Швед
Name[ug]=شۋېدچە
Name[uk]=Шведська
Name[uz]=Shvedcha
Name[uz@cyrillic]=Шведча
Name[vi]=Thụy Điển
Name[wa]=Suwedwès
Name[xh]=Swedish
Name[x-test]=xxSwedishxx
Name[zh_CN]=瑞典语
Name[zh_HK]=瑞典語
Name[zh_TW]=瑞典語
[sw]
Name=Swahili
Name[af]=Swahili
Name[ar]=سواحيلية
Name[as]=ছ্বাহিলি
Name[ast]=Suaḥili
Name[be]=Суахілі
Name[be@latin]=Suahili
Name[bg]=Суахили
Name[bn]=সোয়াহিলি
Name[bn_IN]=সোয়াহিলি
Name[br]=Swahili
Name[bs]=Svahili
Name[ca]=Suahili
Name[ca@valencia]=Suahili
Name[cs]=Svahilský
Name[csb]=Swahili
Name[cy]=Swahili
Name[da]=Swahili
Name[de]=Swahili
Name[el]=Σουαχίλι
Name[en_GB]=Swahili
Name[eo]=Svahila
Name[es]=Suahili
Name[et]=Suahiili
Name[eu]=Swahiliera
Name[fa]=سواهیلی
Name[fi]=Swahili
Name[fr]=Swahili
Name[fy]=Swahili
Name[ga]=Svahaílis
Name[gl]=Swahili
Name[gu]=સ્વાહિલિ
Name[he]=סוואהילי
Name[hi]=स्वाहिली
Name[hne]=स्वाहिली
Name[hr]=Svahili
Name[hsb]=Swahili
Name[hu]=Szvahili
Name[ia]=Swahili
Name[id]=Swahili
Name[is]=Swahili
Name[it]=Swahili
Name[ja]=スワヒリ語
Name[kk]=Суахили
Name[km]=ស្វាហ៊ីលី
Name[kn]=ಸ್ವಾಹಿಲಿ
Name[ko]=스와힐리어
Name[ku]=Swahîlî
Name[lb]=Swahili
Name[lt]=Suahili
Name[lv]=Svahili
Name[mai]=स्वाहिली
Name[mk]=Свахили
Name[ml]=സ്വാഹിലി
Name[mr]=स्वाहिली
Name[ms]=Swahili
Name[nb]=Swahili
Name[nds]=Swahili
Name[ne]=स्वाहिली
Name[nl]=Swahili
Name[nn]=Swahili
Name[oc]=Swahili
Name[or]=Swahili
Name[pa]=ਸਵਾਹਿਲੀ
Name[pl]=Swahili
Name[ps]=سواهيلي
Name[pt]=Swahili
Name[pt_BR]=Suaíli
Name[ro]=Suahileză
Name[ru]=Суахили
Name[se]=Svahilagiella
Name[si]=ස්වාහිලි
Name[sk]=Svahilčina
Name[sl]=svahili
Name[sq]=Swahili
Name[sr]=свахили
Name[sr@ijekavian]=свахили
Name[sr@ijekavianlatin]=svahili
Name[sr@latin]=svahili
Name[sv]=Swahili
Name[ta]=ஸ்வாஹிலி
Name[te]=స్వాహిలి
Name[tg]=Свахилӣ
Name[th]=ภาษาสวาฮิลี
Name[tr]=Swahili
Name[tt]=Суахили
Name[ug]=سۋاھىلىچە
Name[uk]=Суахілі
Name[uz]=Svaxili
Name[uz@cyrillic]=Свахили
Name[vi]=Swahili
Name[wa]=Suwahili
Name[xh]=Swahili
Name[x-test]=xxSwahilixx
Name[zh_CN]=斯瓦希里语
Name[zh_HK]=斯華西里語
Name[zh_TW]=斯華西里語
[ta]
Name=Tamil
Name[af]=Tamilies
Name[ar]=تاميلية
Name[as]=তামিল
Name[ast]=Tamil
Name[be]=Тамільская
Name[be@latin]=Tamilskaja
Name[bg]=Тамилски
Name[bn]=তামিল
Name[bn_IN]=তামিল
Name[br]=Tamouleg
Name[bs]=Tamilski
Name[ca]=Tàmil
Name[ca@valencia]=Tàmil
Name[cs]=Tamilský
Name[csb]=Tamilsczi
Name[cy]=Tamil
Name[da]=Tamil
Name[de]=Tamil
Name[el]=Tamil
Name[en_GB]=Tamil
Name[eo]=Tamila
Name[es]=Tamil
Name[et]=Tamili
Name[eu]=Tamilera
Name[fa]=تامیل
Name[fi]=Tamil
Name[fr]=Tamoul
Name[fy]=Tamil
Name[ga]=Tamailis
Name[gl]=Tamil
Name[gu]=તમિલ
Name[he]=טמילית
Name[hi]=तमिल
Name[hne]=तमिल
Name[hr]=Tamilski
Name[hsb]=Tamilsce
Name[hu]=Tamil
Name[ia]=Tamil
Name[id]=Tamil
Name[is]=Tamílska
Name[it]=Tamil
Name[ja]=タミル語
Name[kk]=Тамилша
Name[km]=តាមីល
Name[kn]=ತಮಿಳು
Name[ko]=타밀어
Name[ku]=Tamîl
Name[lb]=Tamil
Name[lt]=Tamilų
Name[lv]=Tamilu
Name[mai]=तमिल
Name[mk]=Тамилски
Name[ml]=തമിഴ്
Name[mr]=तामिळ
Name[ms]=Tamil
Name[nb]=Tamil
Name[nds]=Tamielsch
Name[ne]=तामिल
Name[nl]=Tamil
Name[nn]=Tamil
Name[oc]=Tamil
Name[or]=Tamil
Name[pa]=ਤਾਮਿਲ
Name[pl]=Tamiljski
Name[ps]=ټامېل
Name[pt]=Tamil
Name[pt_BR]=Tâmil
Name[ro]=Tamilă
Name[ru]=Тамильский
Name[se]=Tamilgiella
Name[si]=දෙමළ
Name[sk]=Tamilčina
Name[sl]=tamilsko
Name[sq]=Tamileze
Name[sr]=тамилски
Name[sr@ijekavian]=тамилски
Name[sr@ijekavianlatin]=tamilski
Name[sr@latin]=tamilski
Name[sv]=Tamil
Name[ta]=தமிழ்
Name[te]=తమిళం
Name[tg]=Тамилӣ
Name[th]=ภาษาทมิฬ
Name[tr]=Tamil Dili
Name[tt]=Тамиль
Name[ug]=تامىلچە
Name[uk]=Тамільська
Name[uz]=Tamilcha
Name[uz@cyrillic]=Тамилча
Name[vi]=Tamil
Name[wa]=Tamoul
Name[xh]=Tamil
Name[x-test]=xxTamilxx
Name[zh_CN]=泰米尔语
Name[zh_HK]=坦米爾語
Name[zh_TW]=坦米爾語
[te]
Name=Telugu
Name[af]=Telugu
Name[ar]=تلوغو
Name[as]=তেলুগু
Name[ast]=Telugu
Name[be]=Тэлугу
Name[be@latin]=Telugu
Name[bg]=Телугу
Name[bn]=তেলেগু
Name[bn_IN]=তেলুগু
Name[br]=Telegu
Name[bs]=Telugu
Name[ca]=Telugu
Name[ca@valencia]=Telugu
Name[cs]=Telugu
Name[csb]=Telugu
Name[cy]=Telugu
Name[da]=Telugu
Name[de]=Telugu
Name[el]=Telugu
Name[en_GB]=Telugu
Name[eo]=Telugua
Name[es]=Telugu
Name[et]=Telugu
Name[eu]=Telugu
Name[fa]=تلوگو
Name[fi]=Telugu
Name[fr]=Télougou
Name[fy]=Telûgû
Name[ga]=Teileagúis
Name[gl]=Telugu
Name[gu]=તેલુગુ
Name[he]=טלוגו
Name[hi]=तेलुगु
Name[hne]=तेलुगु
Name[hr]=Telugu
Name[hsb]=Telugu
Name[hu]=Telugu
Name[ia]=Telugu
Name[id]=Telugu
Name[is]=Telugu
Name[it]=Telugu
Name[ja]=テルグ語
Name[kk]=Телугу
Name[km]=តេលូហ្គូ
Name[kn]=ತೆಲುಗು
Name[ko]=텔루구어
Name[ku]=Telugu
Name[lb]=Telugu
Name[lt]=Telugu
Name[lv]=Telugu
Name[mai]=तेलुगु
Name[mk]=Телугу
Name[ml]=തെലുങ്ക്
Name[mr]=तेलुगु
Name[ms]=Telugu
Name[nb]=Telugu
Name[nds]=Telugu
Name[ne]=तेलुगु
Name[nl]=Telugu
Name[nn]=Telugu
Name[oc]=Telugu
Name[or]=Telugu
Name[pa]=ਤੇਲਗੂ
Name[pl]=Telugu
Name[ps]=ټېلوګو
Name[pt]=Telugu
Name[pt_BR]=Telugu
Name[ro]=Telugă
Name[ru]=Телугу
Name[se]=Telugugiella
Name[si]=තෙළිඟු
Name[sk]=Telugčina
Name[sl]=telugu
Name[sq]=Telugu
Name[sr]=телугу
Name[sr@ijekavian]=телугу
Name[sr@ijekavianlatin]=telugu
Name[sr@latin]=telugu
Name[sv]=Telugu
Name[ta]=தெலுங்கு
Name[te]=తెలుగు
Name[tg]=Телугуягӣ
Name[th]=ภาษาเตลูกู
Name[tr]=Telugu
Name[tt]=Телугу
Name[ug]=تېلۇگۇچە
Name[uk]=Телугу
Name[uz]=Telugu
Name[uz@cyrillic]=Телугу
Name[vi]=Telugu
Name[wa]=Telougou
Name[xh]=Telugu
Name[x-test]=xxTeluguxx
Name[zh_CN]=泰卢固语
Name[zh_HK]=特拉古語
Name[zh_TW]=特拉古語
[tg]
Name=Tajik
Name[af]=Tajikees
Name[ar]=طاجيكية
Name[as]=টাজিক
Name[ast]=Tayicu
Name[be]=Таджыцкая
Name[be@latin]=Tadžyckaja
Name[bg]=Таджикски
Name[bn]=তাজিক
Name[bn_IN]=তাজিক
Name[br]=Tajiek
Name[bs]=Tadžički
Name[ca]=Tadjik
Name[ca@valencia]=Tadjik
Name[cs]=Tádžikský
Name[csb]=Tadżëcczi
Name[cy]=Tajik
Name[da]=Tajik
Name[de]=Tadschikisch
Name[el]=Τατζικικά
Name[en_GB]=Tajik
Name[eo]=Taĝika
Name[es]=Tayiko
Name[et]=Tadžiki
Name[eu]=Tadjikera
Name[fa]=تاجیک
Name[fi]=Tadžikki
Name[fr]=Tadjik
Name[fy]=Tadzjyksk
Name[ga]=Táidsícis
Name[gl]=Taxico
Name[gu]=તાજીક
Name[he]=טג'יקית
Name[hi]=ताजिक
Name[hne]=ताजिक
Name[hr]=Tadžik
Name[hsb]=Tadźikisce
Name[hu]=Tádzsik
Name[ia]=Tadzhiko
Name[id]=Tajik
Name[is]=Tajik
Name[it]=Tagico
Name[ja]=タジク語
Name[kk]=Тәжікше
Name[km]=តាដហ្ស៊ីគីស្តង់
Name[kn]=ಟಾಜಿಕ್
Name[ko]=타지크어
Name[ku]=Tacîkî
Name[lb]=Tadschikesch
Name[lt]=Tadžikų
Name[lv]=Tadžiku
Name[mai]=ताजिक
Name[mk]=Таџикистански
Name[ml]=താജിക്ക്
Name[mr]=ताजिक
Name[ms]=Tajik
Name[nb]=Tadsjikisk
Name[nds]=Tadschiiksch
Name[ne]=ताजिक
Name[nl]=Tajiks
Name[nn]=Tadsjikisk
Name[oc]=Tajik
Name[or]=Tajik
Name[pa]=ਤਾਜਿਕ
Name[pl]=Tadżycki
Name[ps]=تاجکي
Name[pt]=Tajique
Name[pt_BR]=Tajique
Name[ro]=Tadjică
Name[ru]=Таджикский
Name[se]=Tažihkagiella
Name[si]=ටජික්
Name[sk]=Tadžičtina
Name[sl]=tadžiško
Name[sq]=Taxhike
Name[sr]=таџички
Name[sr@ijekavian]=таџички
Name[sr@ijekavianlatin]=tadžički
Name[sr@latin]=tadžički
Name[sv]=Tadzjikiska
Name[ta]=தஜிக்
Name[te]=తాజిక్
Name[tg]=Тоҷикӣ
Name[th]=ภาษาทาจิกิสถาน
Name[tr]=Tajik
Name[tt]=Таҗик
Name[ug]=تاجىكچە
Name[uk]=Таджицька
Name[uz]=Tojikcha
Name[uz@cyrillic]=Тожикча
Name[vi]=Tajik
Name[wa]=Tadjik
Name[xh]=Tajik
Name[x-test]=xxTajikxx
Name[zh_CN]=塔吉克语
Name[zh_HK]=塔吉克語
Name[zh_TW]=塔吉克語
[th]
Name=Thai
Name[af]=Thaïs
Name[ar]=تيلندية
Name[as]=থাই
Name[ast]=Tailandés
Name[be]=Тайская
Name[be@latin]=Tajskaja
Name[bg]=Тайски
Name[bn]=থাই
Name[bn_IN]=থাই
Name[br]=Tailh
Name[bs]=Tajlandski
Name[ca]=Tai
Name[ca@valencia]=Tai
Name[cs]=Thajský
Name[csb]=Tajsczi
Name[cy]=Thai
Name[da]=Thailandsk
Name[de]=Thailändisch
Name[el]=Ταϊλανδικά
Name[en_GB]=Thai
Name[eo]=Taja
Name[es]=Tailandés
Name[et]=Tai
Name[eu]=Thailandiera
Name[fa]=تایلندی
Name[fi]=Thai
Name[fr]=Thaïlandais
Name[fy]=Taaisk
Name[ga]=Téalainnis
Name[gl]=Tailandés
Name[gu]=થાઈ
Name[he]=תאילנדית
Name[hi]=थाई
Name[hne]=थाई
Name[hr]=Tajlandski
Name[hsb]=Thai
Name[hu]=Thai
Name[ia]=Thai
Name[id]=Thai
Name[is]=Tælenska
Name[it]=Thailandese
Name[ja]=タイ語
Name[kk]=Тайша
Name[km]=ថៃ
Name[kn]=ಥಾಯ್
Name[ko]=타이어
Name[ku]=Tayî
Name[lb]=Thai
Name[lt]=Tailandiečių
Name[lv]=Taju
Name[mai]=थाइ
Name[mk]=Тајландски
Name[ml]=തായ്
Name[mr]=थाई
Name[ms]=Thai
Name[nb]=Thai
Name[nds]=Thai
Name[ne]=थाई
Name[nl]=Thais
Name[nn]=Thai
Name[oc]=Tailandés
Name[or]=Thai
Name[pa]=ਥਾਈ
Name[pl]=Tajski
Name[ps]=ټهايي
Name[pt]=Tailandês
Name[pt_BR]=Tailandês
Name[ro]=Tailandeză
Name[ru]=Тайский
Name[se]=Thaigiella
Name[si]=තායි
Name[sk]=Thajčina
Name[sl]=tajsko
Name[sq]=Tailandeze
Name[sr]=тајландски
Name[sr@ijekavian]=тајландски
Name[sr@ijekavianlatin]=tajlandski
Name[sr@latin]=tajlandski
Name[sv]=Thailändska
Name[ta]=தாய்
Name[te]=థాయి
Name[tg]=Тайландӣ
Name[th]=ภาษาไทย
Name[tr]=Thai
Name[tt]=Тай
Name[ug]=تايلاندچە
Name[uk]=Тайська
Name[uz]=Taycha
Name[uz@cyrillic]=Тайча
Name[vi]=Thái
Name[wa]=Taylandès
Name[xh]=Thai
Name[x-test]=xxThaixx
Name[zh_CN]=泰语
Name[zh_HK]=泰國語
Name[zh_TW]=泰國語
[ti]
Name=Tigrinya
Name[af]=Tigrinya
Name[ar]=تيغرينيا
Name[as]=টিগ্ৰিন্যা
Name[ast]=Tigrinya
Name[be]=Тыгрынья
Name[be@latin]=Tigrinya
Name[bg]=Тигриня
Name[bn]=টিগ্রিন্যা
Name[bn_IN]=টিগ্রিনিয়া
Name[br]=Tigrinya
Name[bs]=Tigrinija
Name[ca]=Tigrinya
Name[ca@valencia]=Tigrinya
Name[cs]=Tigrinya
Name[csb]=Tigrinya
Name[cy]=Tigrinya
Name[da]=Tigrinya
Name[de]=Tigrinja
Name[el]=Tigrinya
Name[en_GB]=Tigrinya
Name[eo]=Tigraja
Name[es]=Tigrinya
Name[et]=Tigrinja
Name[eu]=Tigrinya
Name[fa]=تیگرینیا
Name[fi]=Tigrinja
Name[fr]=Tigrigna
Name[fy]=Tigrysk
Name[ga]=Tigrínis
Name[gl]=Tigrignan
Name[gu]=તીજ્રીન્યા
Name[he]=טיגרינית
Name[hi]=टिग्रिन्या
Name[hne]=टिग्रिन्या
Name[hr]=Tigrinya
Name[hsb]=Tigrinya
Name[hu]=Tigrinja
Name[ia]=Tigrinya
Name[id]=Tigrinya
Name[is]=Tigrinya
Name[it]=Tigrino
Name[ja]=ティグリニア語
Name[kk]=Тигринья
Name[km]=ទីគ្រីនយ៉ា
Name[kn]=ಟಿಗ್ರಿನ್ಯಾ
Name[ko]=티그리냐어
Name[ku]=Tîngrînya
Name[lb]=Tigrinja-Sprooch
Name[lt]=Tigrinya
Name[lv]=Tigrinja
Name[mai]=टिग्रिंया
Name[mk]=Тигринја
Name[ml]=ടൈഗ്രിനിയ
Name[mr]=टिग्रिन्या
Name[ms]=Tigrinya
Name[nb]=Tigrinya
Name[nds]=Tigrinya
Name[ne]=तिग्रिन्य
Name[nl]=Tigrinya
Name[nn]=Tigrinja
Name[oc]=Tigrinya
Name[or]=Tigrinya
Name[pa]=ਤਿਗਰੀਅਨ
Name[pl]=Tigrinya
Name[ps]=ټېګرينيا
Name[pt]=Tigrinya
Name[pt_BR]=Tigrino
Name[ro]=Tigrină
Name[ru]=Тигринья
Name[se]=Tigrinjágiella
Name[si]=ටිග්‍රින්යා
Name[sk]=Tigriňa
Name[sl]=tigrinya
Name[sq]=Tigrinya
Name[sr]=тигрињи
Name[sr@ijekavian]=тигрињи
Name[sr@ijekavianlatin]=tigrinji
Name[sr@latin]=tigrinji
Name[sv]=Tigrinja
Name[ta]=திகிரின்யா
Name[te]=టిగ్రిన్యా
Name[tg]=Тигринягӣ
Name[th]=ภาษาทิกรินยา
Name[tr]=Tigrinya
Name[tt]=Тигринья
Name[ug]=تىگرىنياچە
Name[uk]=Тигринійська
Name[uz]=Tigrinya
Name[uz@cyrillic]=Тигриня
Name[vi]=Tigrinya
Name[wa]=Tigrinia
Name[xh]=Tigrinya
Name[x-test]=xxTigrinyaxx
Name[zh_CN]=提格里尼亚语
Name[zh_HK]=提格利尼亞語
Name[zh_TW]=提格利尼亞語
[tk]
Name=Turkmen
Name[af]=Turkmen
Name[ar]=تركمانية
Name[as]=টুৰ্কমেন
Name[ast]=Turkmenu
Name[be]=Туркменская
Name[be@latin]=Turkmenskaja
Name[bg]=Туркменски
Name[bn]=তুর্কমেন
Name[bn_IN]=তুর্কমেন
Name[br]=Turkmen
Name[bs]=Turkmenski
Name[ca]=Turcman
Name[ca@valencia]=Turcman
Name[cs]=Turkmenský
Name[csb]=Turkmeńsczi
Name[cy]=Twrcmeneg
Name[da]=Turkmen
Name[de]=Turkmenisch
Name[el]=Τουρκμενικά
Name[en_GB]=Turkmen
Name[eo]=Turkmena
Name[es]=Turkmenio
Name[et]=Turkmeeni
Name[eu]=Turkmeniera
Name[fa]=ترکمنی
Name[fi]=Turkmeeni
Name[fr]=Turkmène
Name[fy]=Turkmeensk
Name[ga]=Tuircméinis
Name[gl]=Turquemeno
Name[gu]=તુર્કમેન
Name[he]=טורקמנית
Name[hi]=तुर्कमेन
Name[hne]=तुर्कमेन
Name[hr]=Turkmenski
Name[hsb]=Turkmensce
Name[hu]=Türkmén
Name[ia]=Turkmeno
Name[id]=Turkmen
Name[is]=Turkmen
Name[it]=Turkmeno
Name[ja]=トルクメン語
Name[kk]=Түркменше
Name[km]=ទួគមេនីស្តង់
Name[kn]=ಟುರ್ಕಮೆನ್
Name[ko]=투르크멘어
Name[ku]=Tirkmenî
Name[lb]=Turkmenesch
Name[lt]=Turkmėnų
Name[lv]=Turkmēņu
Name[mai]=तुर्कमेन
Name[mk]=Туркменистански
Name[ml]=തുര്‍ക്കുമെന്‍
Name[mr]=तुर्कमेन
Name[ms]=Turkmen
Name[nb]=Turkmensk
Name[nds]=Turkmeensch
Name[ne]=टर्कम्यान
Name[nl]=Turkmeens
Name[nn]=Turkmensk
Name[or]=Turkmen
Name[pa]=ਤੁਰਕਮੀਨ
Name[pl]=Turkmeński
Name[ps]=ترکمني
Name[pt]=Turquemeno
Name[pt_BR]=Turcomeno
Name[ro]=Turcmenă
Name[ru]=Туркменский
Name[se]=Turkmenagiella
Name[si]=ටර්ක්මෙන්
Name[sk]=Turkménčina
Name[sl]=turkmensko
Name[sq]=Turkmene
Name[sr]=туркменски
Name[sr@ijekavian]=туркменски
Name[sr@ijekavianlatin]=turkmenski
Name[sr@latin]=turkmenski
Name[sv]=Turkmenska
Name[ta]=துருக்மென்
Name[te]=తుర్క్మెన్
Name[tg]=Туркманӣ
Name[th]=ภาษาเติร์กเมน
Name[tr]=Türkmence
Name[tt]=Туркменский
Name[ug]=تۈركمەنچە
Name[uk]=Туркменська
Name[uz]=Turkmancha
Name[uz@cyrillic]=Туркманча
Name[vi]=Turkmen
Name[wa]=Turkmene
Name[xh]=Turkmen
Name[x-test]=xxTurkmenxx
Name[zh_CN]=土库曼语
Name[zh_HK]=土庫曼語
Name[zh_TW]=土庫曼語
[tn]
Name=Tswana
Name[af]=Tswana
Name[ar]=تسوانا
Name[as]=ছ্বানা
Name[ast]=Tswana
Name[be]=Цвана
Name[be@latin]=Tswana
Name[bg]=Тсуана
Name[bn]=তসওয়ানা
Name[bn_IN]=সোওয়ানা
Name[br]=Tswana
Name[bs]=Cvana
Name[ca]=Tswana
Name[ca@valencia]=Tswana
Name[cs]=Tswana
Name[csb]=Tswana
Name[cy]=Tswana
Name[da]=Tswana
Name[de]=Tswana
Name[el]=Tswana
Name[en_GB]=Tswana
Name[eo]=Cvana
Name[es]=Tswana
Name[et]=Tsvana
Name[eu]=Tswanera
Name[fa]=تی سوانا
Name[fi]=Tswana
Name[fr]=Tswana
Name[fy]=Tswanaansk
Name[ga]=Suáinis
Name[gl]=Tswana
Name[gu]=ત્સવાના
Name[he]=צוואנה
Name[hi]=तस्वाना
Name[hne]=तस्वाना
Name[hr]=Tswana
Name[hsb]=Tswana
Name[hu]=Tszvana
Name[ia]=Tswana
Name[id]=Tswana
Name[is]=Tswana
Name[it]=Tswana
Name[ja]=ツワナ語
Name[kk]=Тсвана
Name[km]=ស្វាណា
Name[kn]=ಟ್ಸ್ವಾನಾ
Name[ko]=츠와나어
Name[ku]=Tswana
Name[lb]=Tswana-Sprooch
Name[lt]=Tswana
Name[lv]=Cvanu
Name[mai]=त्स्वाना
Name[mk]=Цвана
Name[ml]=സ്വാന
Name[mr]=तस्वाना
Name[ms]=Tswana
Name[nb]=Tswana
Name[nds]=Tswana
Name[ne]=स्वाना
Name[nl]=Tswana
Name[nn]=Setswana
Name[oc]=Tswana
Name[or]=Tswana
Name[pa]=ਤਸਵਾਨਾ
Name[pl]=Tswana
Name[ps]=ټسوانا
Name[pt]=Tswana
Name[pt_BR]=Tswana
Name[ro]=Țuană
Name[ru]=Тсвана
Name[se]=Tswanagiella
Name[si]=ට්ස්වානා
Name[sk]=Tswančina
Name[sl]=tswana
Name[sq]=Tswana
Name[sr]=цвана
Name[sr@ijekavian]=цвана
Name[sr@ijekavianlatin]=cvana
Name[sr@latin]=cvana
Name[sv]=Tswana
Name[ta]=ஸ்வானா
Name[te]=స్వానా
Name[tg]=Сванавӣ
Name[th]=ภาษาสวานา
Name[tr]=Tswana
Name[tt]=Тсвана
Name[ug]=تىسۋاناچە
Name[uk]=Тсвана
Name[uz]=Tsvana
Name[uz@cyrillic]=Тсвана
Name[vi]=Tswana
Name[wa]=Tswana
Name[xh]=Isitswana
Name[x-test]=xxTswanaxx
Name[zh_CN]=茨瓦纳语
Name[zh_HK]=班圖語
Name[zh_TW]=班圖語
[to]
Name=Tonga
Name[af]=Tonga
Name[ar]=تونغا
Name[as]=টঙ্গা
Name[ast]=Tonga
Name[be]=Тонга
Name[be@latin]=Tonga
Name[bg]=Тонга
Name[bn]=টংগা
Name[bn_IN]=টোঙ্গা
Name[br]=Inizi Tonga
Name[bs]=Tonga
Name[ca]=Tonga
Name[ca@valencia]=Tonga
Name[cs]=Tonga
Name[csb]=Tonga
Name[cy]=Tonga
Name[da]=Tonga
Name[de]=Tonga
Name[el]=Τόνγκα
Name[en_GB]=Tonga
Name[eo]=Tonga
Name[es]=Tonga
Name[et]=Tonga
Name[eu]=Tongera
Name[fa]=تونگا
Name[fi]=Tonga
Name[fr]=Tonga
Name[fy]=Tongaansk
Name[ga]=Tongais
Name[gl]=Tonga
Name[gu]=ટોન્ગા
Name[he]=טונגה
Name[hi]=टोन्गा
Name[hne]=टोन्गा
Name[hr]=Tonga
Name[hsb]=Tonga
Name[hu]=Tonga
Name[ia]=Tonga
Name[id]=Tonga
Name[is]=Tonga
Name[it]=Tonga
Name[ja]=トンガ語
Name[kk]=Тонга
Name[km]=តុងហ្គា
Name[kn]=ಟೊಂಗಾ
Name[ko]=통가어
Name[ku]=Tonga
Name[lb]=Tonga
Name[lt]=Tonga
Name[lv]=Tongiešu
Name[mai]=टोन्गा
Name[mk]=Тонга
Name[ml]=ട്വോങ്ങ
Name[mr]=टोन्गा
Name[ms]=Tonga
Name[nb]=Tonga
Name[nds]=Tonga
Name[ne]=टोङ्गा
Name[nl]=Tonga
Name[nn]=Tongansk
Name[oc]=Tònga
Name[or]=Tonga
Name[pa]=ਤੋਂਗਾ
Name[pl]=Tonga
Name[ps]=ټونګا
Name[pt]=Tonga
Name[pt_BR]=Tonganês
Name[ro]=Tongă
Name[ru]=Тонганский
Name[se]=Tongagiella
Name[si]=ටොන්ගා
Name[sk]=Tongčina
Name[sl]=tongaško
Name[sq]=Tonga
Name[sr]=тонга
Name[sr@ijekavian]=тонга
Name[sr@ijekavianlatin]=tonga
Name[sr@latin]=tonga
Name[sv]=Tonga
Name[ta]=டோங்கா
Name[te]=టొన్గా
Name[tg]=Тонгавӣ
Name[th]=ภาษาทองกา
Name[tr]=Tonga
Name[tt]=Тонга
Name[ug]=تونگا
Name[uk]=Тонга
Name[uz]=Tonga
Name[uz@cyrillic]=Тонга
Name[vi]=Tonga
Name[wa]=Tonga
Name[xh]=Tonga
Name[x-test]=xxTongaxx
Name[zh_CN]=汤加语
Name[zh_HK]=東加語
Name[zh_TW]=東加語
[tr]
Name=Turkish
Name[af]=Turks
Name[ar]=تركية
Name[as]=টুৰ্কিশ্ব
Name[ast]=Turcu
Name[be]=Турэцкая
Name[be@latin]=Tureckaja
Name[bg]=Турски
Name[bn]=তুর্কী
Name[bn_IN]=তুর্কি
Name[br]=Turkeg
Name[bs]=Turski
Name[ca]=Turc
Name[ca@valencia]=Turc
Name[cs]=Turecký
Name[csb]=Tërecczi
Name[cy]=Twrceg
Name[da]=Tyrkisk
Name[de]=Türkisch
Name[el]=Τουρκικά
Name[en_GB]=Turkish
Name[eo]=Turka
Name[es]=Turco
Name[et]=Türgi
Name[eu]=Turkiera
Name[fa]=ترکی
Name[fi]=Turkki
Name[fr]=Turc
Name[fy]=Turks
Name[ga]=Tuircis
Name[gl]=Turco
Name[gu]=તુર્કી
Name[he]=טורקית
Name[hi]=तुर्की
Name[hne]=तुर्की
Name[hr]=Turski
Name[hsb]=Turkowsce
Name[hu]=Török
Name[hy]=Թուրքերեն
Name[ia]=Turco
Name[id]=Turki
Name[is]=Tyrkneska
Name[it]=Turco
Name[ja]=トルコ語
Name[kk]=Түрікше
Name[km]=ទួរគី
Name[kn]=ತುರ್ಕಿಶ್
Name[ko]=터키어
Name[ku]=Tirkî
Name[lb]=Türkesch
Name[lt]=Turkų
Name[lv]=Turku
Name[mai]=तुर्की
Name[mk]=Турски
Name[ml]=തുര്‍ക്കി
Name[mr]=तुर्किश
Name[ms]=Turki
Name[nb]=Tyrkisk
Name[nds]=Törksch
Name[ne]=टर्किश
Name[nl]=Turks
Name[nn]=Tyrkisk
Name[oc]=Turc
Name[or]=Turkish
Name[pa]=ਤੁਰਕਿਸ਼
Name[pl]=Turecki
Name[ps]=ترکي
Name[pt]=Turco
Name[pt_BR]=Turco
Name[ro]=Turcă
Name[ru]=Турецкий
Name[se]=Turkiijagiella
Name[si]=තුර්කි
Name[sk]=Turečtina
Name[sl]=turško
Name[sq]=Turke
Name[sr]=турски
Name[sr@ijekavian]=турски
Name[sr@ijekavianlatin]=turski
Name[sr@latin]=turski
Name[sv]=Turkiska
Name[ta]=துருக்கிய
Name[te]=టర్కిష్
Name[tg]=Туркӣ
Name[th]=ภาษาตุรกี
Name[tr]=Türkçe
Name[tt]=Төрек
Name[ug]=تۈركچە
Name[uk]=Турецька
Name[uz]=Turkcha
Name[uz@cyrillic]=Туркча
Name[vi]=Thổ Nhĩ Kỳ
Name[wa]=Turk
Name[xh]=Turkish
Name[x-test]=xxTurkishxx
Name[zh_CN]=土耳其语
Name[zh_HK]=土耳其語
Name[zh_TW]=土耳其語
[ts]
Name=Tsonga
Name[af]=Tsonga
Name[ar]=تسونغا
Name[as]=ছ'ঙ্গা
Name[ast]=Tsonga
Name[be]=Цонга
Name[be@latin]=Tsonga
Name[bg]=Тсонга
Name[bn]=তসংগা
Name[bn_IN]=সোঙ্গা
Name[br]=Tsonga
Name[bs]=Conga
Name[ca]=Tsonga
Name[ca@valencia]=Tsonga
Name[cs]=Tsonga
Name[csb]=Tsonga
Name[cy]=Tsonga
Name[da]=Tsonga
Name[de]=Tsonga
Name[el]=Tsonga
Name[en_GB]=Tsonga
Name[eo]=Conga
Name[es]=Tsonga
Name[et]=Tsonga
Name[eu]=Tsongera
Name[fa]=تی سوانگا
Name[fi]=Tsonga
Name[fr]=Tsonga
Name[fy]=Tsongaansk
Name[ga]=Songais
Name[gl]=Tsonga
Name[gu]=સોંગા
Name[he]=צונגה
Name[hi]=त्सोन्गा
Name[hne]=त्सोन्गा
Name[hr]=Tsonga
Name[hsb]=Tsonga
Name[hu]=Tsonga
Name[ia]=Tsonga
Name[id]=Tsonga
Name[is]=Tsonga
Name[it]=Tsonga
Name[ja]=ツォンガ語
Name[kk]=Тсонга
Name[km]=សុងហ្កា
Name[kn]=ಟ್ಸೋಂಗಾ
Name[ko]=총가어
Name[ku]=Tsonga
Name[lb]=Tsonga-Sprooch
Name[lt]=Tsonga
Name[lv]=Congu
Name[mai]=त्सोंगा
Name[mk]=Цонга
Name[ml]=സോങ്ങ
Name[mr]=त्सोन्गा
Name[ms]=Tsonga
Name[nb]=Tsonga
Name[nds]=Tsonga
Name[ne]=सोङ्गा
Name[nl]=Tsonga
Name[nn]=Tsonga
Name[oc]=Tsonga
Name[or]=Tsonga
Name[pa]=ਤਸੋਂਗਾ
Name[pl]=Tsonga
Name[ps]=ټسونګا
Name[pt]=Tsonga
Name[pt_BR]=Tsonga
Name[ro]=Tsongă
Name[ru]=Тсонга
Name[se]=Tsongagiella
Name[si]=ට්සොන්ගා
Name[sk]=Tsongčina
Name[sl]=tsonga
Name[sq]=Tsonga
Name[sr]=цонга
Name[sr@ijekavian]=цонга
Name[sr@ijekavianlatin]=conga
Name[sr@latin]=conga
Name[sv]=Tsonga
Name[ta]=டிசோங்கா
Name[te]=సొంగా
Name[tg]=Сонгавӣ
Name[th]=ภาษาซองกา
Name[tr]=Tsonga
Name[tt]=Тсонго
Name[ug]=سونگاچە
Name[uk]=Цонґа
Name[uz]=Songa
Name[uz@cyrillic]=Цонга
Name[vi]=Tsonga
Name[wa]=Tsonga
Name[xh]=Tsonga
Name[x-test]=xxTsongaxx
Name[zh_CN]=聪加语
Name[zh_HK]=Tsonga語
Name[zh_TW]=Tsonga語
[tt]
Name=Tatar
Name[af]=Tatar
Name[ar]=تاترية
Name[as]=টাটাৰ
Name[ast]=Tatar
Name[be]=Татарская
Name[be@latin]=Tatarskaja
Name[bg]=Татарски
Name[bn]=তাতার
Name[bn_IN]=তাতার
Name[br]=Tatareg
Name[bs]=Tatarski
Name[ca]=Tàtar
Name[ca@valencia]=Tàtar
Name[cs]=Tatarský
Name[csb]=Tatarsczi
Name[cy]=Tatar
Name[da]=Tatar
Name[de]=Tatarisch
-Name[el]=Tatar
+Name[el]=Ταταρικά
Name[en_GB]=Tatar
Name[eo]=Tatara
Name[es]=Tatar
Name[et]=Tatari
Name[eu]=Tatariera
Name[fa]=تاتاری
Name[fi]=Tataari
Name[fr]=Tatar
Name[fy]=Tataarsk
Name[ga]=Tatairis
Name[gl]=Tártaro
Name[gu]=તાતાર
Name[he]=טאטרית
Name[hi]=तातार
Name[hne]=तातार
Name[hr]=Tatarski
Name[hsb]=Tatarsce
Name[hu]=Tatár
Name[ia]=Tataro
Name[id]=Tatar
Name[is]=Tatar
Name[it]=Tataro
Name[ja]=タタール語
Name[kk]=Татарша
Name[km]=តាតារ
Name[kn]=ಟಟಾರ್
Name[ko]=타타르어
Name[ku]=Tatarî
Name[lb]=Tataresch
Name[lt]=Totorių
Name[lv]=Tatāru
Name[mai]=ततार
Name[mk]=Татарски
Name[ml]=തത്തര്‍
Name[mr]=तातार
Name[ms]=Tatar
Name[nb]=Tatar
Name[nds]=Tartaarsch
Name[ne]=टाटर
Name[nl]=Tataars
Name[nn]=Tatarisk
Name[oc]=Tatar
Name[or]=Tatar
Name[pa]=ਤਾਟਾਰ
Name[pl]=Tatarski
Name[ps]=ټاټار
Name[pt]=Tatar
Name[pt_BR]=Tártaro
Name[ro]=Tătară
Name[ru]=Татарский
Name[se]=Tatáragiella
Name[si]=ටටාර්
Name[sk]=Tatárčina
Name[sl]=tatarsko
Name[sq]=Tatare
Name[sr]=татарски
Name[sr@ijekavian]=татарски
Name[sr@ijekavianlatin]=tatarski
Name[sr@latin]=tatarski
Name[sv]=Tatariska
Name[ta]=டாடார்
Name[te]=తతర్
Name[tg]=Тоторӣ
Name[th]=ภาษาทาทาร์
Name[tr]=Tatar
Name[tt]=Татарский
Name[ug]=تاتارچە
Name[uk]=Татарська
Name[uz]=Totarcha
Name[uz@cyrillic]=Тотарча
Name[vi]=Tatar
Name[wa]=Tatår
Name[xh]=Tatar
Name[x-test]=xxTatarxx
Name[zh_CN]=鞑靼语
Name[zh_HK]=韃靼語
Name[zh_TW]=韃靼語
[tw]
Name=Twi
Name[af]=Twi
Name[ar]=توي
Name[as]=ট্বি
Name[ast]=Twi
Name[be]=Тві
Name[be@latin]=Twi
Name[bg]=Тви
Name[bn]=টুয়ি
Name[bn_IN]=টুই
Name[br]=Twi
Name[bs]=Tvi
Name[ca]=Twi
Name[ca@valencia]=Twi
Name[cs]=Twi
Name[csb]=Twi
Name[cy]=Twi
Name[da]=Twi
Name[de]=Twi
Name[el]=Twi
Name[en_GB]=Twi
Name[eo]=Tvia
Name[es]=Twi
Name[et]=Tvii
Name[eu]=Twi
Name[fa]=توی
Name[fi]=Twi
Name[fr]=Twi
Name[fy]=Twi
Name[ga]=Tiuíais
Name[gl]=Twi
Name[gu]=ત્વી
Name[he]=צ'ווי
Name[hi]=त्वी
Name[hne]=त्वी
Name[hr]=Twi
Name[hsb]=Twi
Name[hu]=Tvi
Name[ia]=Twi
Name[id]=Twi
Name[is]=Twi
Name[it]=Twi
Name[ja]=チュイ語
Name[kk]=Тви
Name[km]=ទ្វី
Name[kn]=ಟ್ವಿ
Name[ko]=트위어
Name[ku]=Twî
Name[lb]=Akan-Sprooch
Name[lt]=Twi
Name[lv]=Tvī
Name[mai]=त्वी
Name[mk]=Тви
Name[ml]=ട്വി
Name[mr]=त्वी
Name[ms]=Twi
Name[nb]=Twi
Name[nds]=Twi
Name[ne]=तुइ
Name[nl]=Twi
Name[nn]=Twi
Name[or]=Twi
Name[pa]=ਤਵੀ
Name[pl]=Twi
Name[ps]=ټوي
Name[pt]=Twi
Name[pt_BR]=Twi
Name[ro]=Twi
Name[ru]=Тви
Name[se]=Twigiella
Name[si]=ටිවි
Name[sk]=Twi
Name[sl]=twi
Name[sq]=Twi
Name[sr]=тви
Name[sr@ijekavian]=тви
Name[sr@ijekavianlatin]=tvi
Name[sr@latin]=tvi
Name[sv]=Twi
Name[ta]=த்வீ
Name[te]=ట్వి
Name[tg]=Твиягӣ
Name[th]=ภาษาทรี
Name[tr]=Twi
Name[tt]=Тви
Name[ug]=تۋىچە
Name[uk]=Тві
Name[uz]=Tvi
Name[uz@cyrillic]=Тви
Name[vi]=Twi
Name[wa]=Twi
Name[xh]=Twi
Name[x-test]=xxTwixx
Name[zh_CN]=特威语
Name[zh_HK]=契維語
Name[zh_TW]=契維語
[ty]
Name=Tahitian
Name[af]=Tahitian
Name[ar]=تاهيتية
Name[as]=টাহিটিয়ান
Name[ast]=Tahitianu
Name[be]=Таіцкая
Name[be@latin]=Tahickaja
Name[bg]=Таитски
Name[bn]=তাহিশিয়ান
Name[bn_IN]=তাহিশিয়ান
Name[br]=Tahitieg
Name[bs]=Tahićanski
Name[ca]=Tahitià
Name[ca@valencia]=Tahitià
Name[cs]=Tahitský
Name[csb]=Tahitańsczi
Name[cy]=Tahitieg
Name[da]=Tahitisk
Name[de]=Tahitianisch
Name[el]=Tahitian
Name[en_GB]=Tahitian
Name[eo]=Tahitia
Name[es]=Tahitiano
Name[et]=Tahiti
Name[eu]=Tahitiera
Name[fa]=تاهیتی
Name[fi]=Tahiti
Name[fr]=Tahitien
Name[fy]=Tahitiaansk
Name[ga]=Taihítis
Name[gl]=Tahitiano
Name[gu]=તાહિટિયન
Name[he]=טהיטית
Name[hi]=ताहितियन
Name[hne]=ताहितियन
Name[hr]=Tahićanski
Name[hsb]=Tahitisce
Name[hu]=Tahiti
Name[ia]=Tahitiano
Name[id]=Tahiti
Name[is]=Tahitian
Name[it]=Tahitiano
Name[ja]=タヒチ語
Name[kk]=Таитише
Name[km]=តាហ៊ិតង់
Name[kn]=ತಹೀಟಿಯನ್
Name[ko]=타히티어
Name[ku]=Tahîtî
Name[lb]=Tahitesch
Name[lt]=Tahiti
Name[lv]=Taitiešu
Name[mai]=ताहितियन
Name[mk]=Тахитски
Name[ml]=താഹിതിയന്‍
Name[mr]=ताहितियन
Name[ms]=Tahiti
Name[nb]=Tahitisk
Name[nds]=Tahiitsch
Name[ne]=ताहितियन
Name[nl]=Tahitiaans
Name[nn]=Tahitisk
Name[oc]=Taician
Name[or]=Tahitian
Name[pa]=ਤਾਹੀਟੀਅਨ
Name[pl]=Tahitański
Name[pt]=Tahitiano
Name[pt_BR]=Taitiano
Name[ro]=Tahitiană
Name[ru]=Таитянский
Name[se]=Tahitigiella
Name[si]=තහිටියානු
Name[sk]=Tahitčina
Name[sl]=tahitijsko
Name[sq]=Tahitiane
Name[sr]=тахићански
Name[sr@ijekavian]=тахићански
Name[sr@ijekavianlatin]=tahićanski
Name[sr@latin]=tahićanski
Name[sv]=Tahitiska
Name[ta]=தஹிடியன்
Name[te]=టహితియన్
Name[tg]=Таитиягӣ
Name[th]=ภาษาตาฮิติ
Name[tr]=Tahitian
Name[tt]=Тахитиан (Таити)
Name[ug]=تاخىتىيانچە
Name[uk]=Таїтянська
Name[uz]=Taxitian
Name[uz@cyrillic]=Тахитиан
Name[vi]=Tahitia
Name[wa]=Tahityin
Name[xh]=Tahitian
Name[x-test]=xxTahitianxx
Name[zh_CN]=塔希提语
Name[zh_HK]=大溪地語
Name[zh_TW]=大溪地語
[ug]
Name=Uyghur
Name[ar]=أيغوريّة
Name[bg]=Уйгурски
Name[bs]=Uyghur
Name[ca]=Uigur
Name[ca@valencia]=Uigur
Name[cs]=Ujgurský
Name[da]=Uyghur
Name[de]=Uigurisch
Name[el]=Uyghur
Name[en_GB]=Uyghur
Name[es]=Uigur
Name[et]=Uiguuri
Name[eu]=Uigurrera
Name[fa]=یوگر
Name[fi]=Uiguuri
Name[fr]=Ouïghour
Name[ga]=Úígiúiris
Name[gl]=Uigur
Name[he]=אויגורים
Name[hr]=Ujgurski
Name[hu]=Ujgur
Name[ia]=Uighuro
+Name[id]=Uyghur
Name[is]=Uyghur
Name[it]=Uiguro
Name[ja]=ウイグル語
Name[kk]=Ұйғырша
Name[km]=អ៊ុយ​ហ្គៀរ
Name[ko]=위구르어
Name[lt]=Uigurų
Name[lv]=Uiguru
Name[ms]=Uighur
Name[nb]=Uigursk
Name[nds]=Uighuursch
Name[nl]=Oeigoers
Name[pa]=ਉਘੂਰ
Name[pl]=Ujgurski
Name[pt]=Uyghur
Name[pt_BR]=Uigur
Name[ro]=Uigură
Name[ru]=Уйгурский
Name[se]=Uiguragiella
Name[si]=උයිගුර්
Name[sk]=Ujgurčina
Name[sq]=Ujgur
Name[sr]=ујгурски
Name[sr@ijekavian]=ујгурски
Name[sr@ijekavianlatin]=ujgurski
Name[sr@latin]=ujgurski
Name[sv]=Uiguriska
Name[tg]=Уйгурӣ
Name[th]=ภาษาอุยกูร์
Name[tr]=Uygurca
Name[tt]=Уйгур
Name[ug]=ئۇيغۇرچە
Name[uk]=Уйгурська
Name[vi]=Uyghur
Name[wa]=Ouygour
Name[x-test]=xxUyghurxx
Name[zh_CN]=维吾尔语
Name[zh_TW]=Uyghur
[uk]
Name=Ukrainian
Name[af]=Ukraïnies
Name[ar]=أوكرانية
Name[as]=ইউক্ৰেনিয়ান
Name[ast]=Ucranianu
Name[be]=Украінская
Name[be@latin]=Ukrainskaja
Name[bg]=Украински
Name[bn]=ইউক্রেনীয়
Name[bn_IN]=ইউক্রেনিয়ান
Name[br]=Ukrainiek
Name[bs]=Ukrajinski
Name[ca]=Ucraïnès
Name[ca@valencia]=Ucraïnés
Name[cs]=Ukrajinský
Name[csb]=Ùkrajińsczi
Name[cy]=Ukraineg
Name[da]=Ukrainsk
Name[de]=Ukrainisch
Name[el]=Ουκρανικά
Name[en_GB]=Ukrainian
Name[eo]=Ukraina
Name[es]=Ucraniano
Name[et]=Ukraina
Name[eu]=Ukrainiera
Name[fa]=اکراینی
Name[fi]=Ukraina
Name[fr]=Ukrainien
Name[fy]=Oekraïnsk
Name[ga]=Úcráinis
Name[gl]=Ucraíno
Name[gu]=યુક્રેનિયન
Name[he]=אוקראינית
Name[hi]=उक्रेनियाई
Name[hne]=उक्रेनियाई
Name[hr]=Ukrajinski
Name[hsb]=Ukrainsce
Name[hu]=Ukrán
Name[ia]=Ucrainiano
Name[id]=Ukrainia
Name[is]=Úkraínska
Name[it]=Ucraino
Name[ja]=ウクライナ語
Name[kk]=Украинша
Name[km]=អ៊ុយក្រែន
Name[kn]=ಯುಕ್ರೇನಿಯನ್
Name[ko]=우크라이나어
Name[ku]=Ûkraynayî
Name[lb]=Ukrainesch
Name[lt]=Ukrainiečių
Name[lv]=Ukraiņu
Name[mai]=युक्रेनियन
Name[mk]=Украински
Name[ml]=ഉക്രേനിയന്‍
Name[mr]=यूक्रेनियन
Name[ms]=Ukrainia
Name[nb]=Ukrainsk
Name[nds]=Ukrainsch
Name[ne]=युक्रेनी
Name[nl]=Oekraïns
Name[nn]=Ukrainsk
Name[oc]=Ucraïnian
Name[or]=Ukrainian
Name[pa]=ਯੂਕਰੇਨ
Name[pl]=Ukraiński
Name[ps]=اکرېني
Name[pt]=Ucraniano
Name[pt_BR]=Ucraniano
Name[ro]=Ucrainiană
Name[ru]=Украинский
Name[se]=Ukrainagiella
Name[si]=යුක්රේනියානු
Name[sk]=Ukrajinčina
Name[sl]=ukrajinsko
Name[sq]=Ukrainase
Name[sr]=украјински
Name[sr@ijekavian]=украјински
Name[sr@ijekavianlatin]=ukrajinski
Name[sr@latin]=ukrajinski
Name[sv]=Ukrainska
Name[ta]=உக்ரேனியன்
Name[te]=ఉక్రేనియన్
Name[tg]=Украинӣ
Name[th]=ภาษายูเครน
Name[tr]=Ukrayna Dili
Name[tt]=Украин
Name[ug]=ئۇكرائىنچە
Name[uk]=Українська
Name[uz]=Ukraincha
Name[uz@cyrillic]=Украинча
Name[vi]=Ukraine
Name[wa]=Oucrinnyin
Name[xh]=Ukranian
Name[x-test]=xxUkrainianxx
Name[zh_CN]=乌克兰语
Name[zh_HK]=烏克蘭語
Name[zh_TW]=烏克蘭語
[ur]
Name=Urdu
Name[af]=Urdu
Name[ar]=أردو
Name[as]=উৰ্দু
Name[ast]=Urdu
Name[be]=Урду
Name[be@latin]=Urdu
Name[bg]=Урду
Name[bn]=উর্দু
Name[bn_IN]=উর্দু
Name[br]=Urdu
Name[bs]=Urdu
Name[ca]=Urdú
Name[ca@valencia]=Urdú
Name[cs]=Urdu
Name[csb]=Urdu
Name[cy]=Wrdw
Name[da]=Urdu
Name[de]=Urdu
Name[el]=Urdu
Name[en_GB]=Urdu
Name[eo]=Urdua
Name[es]=Urdu
Name[et]=Urdu
Name[eu]=Urdu
Name[fa]=اردو
Name[fi]=Urdu
Name[fr]=Urdu
Name[fy]=Urdû
Name[ga]=Urdais
Name[gl]=Urdú
Name[gu]=ઉર્દુ
Name[he]=אורדו
Name[hi]=उर्दू
Name[hne]=उर्दू
Name[hr]=Urdu
Name[hsb]=Urdu
Name[hu]=Urdu
Name[ia]=Hurdu
Name[id]=Urdu
Name[is]=Urdu
Name[it]=Urdu
Name[ja]=ウルドゥー語
Name[kk]=Урду
Name[km]=អ៊ូរ្ឌូ
Name[kn]=ಉರ್ದು
Name[ko]=우르두어
Name[ku]=Ûrdû
Name[lb]=Urdu
Name[lt]=Urdu
Name[lv]=Urdu
Name[mai]=उर्दू
Name[mk]=Урду
Name[ml]=ഉര്‍ദ്ദു
Name[mr]=उर्दू
Name[ms]=Urdu
Name[nb]=Urdu
Name[nds]=Urdu
Name[ne]=उर्दु
Name[nl]=Urdu
Name[nn]=Urdu
Name[oc]=Ordo
Name[or]=Urdu
Name[pa]=ਉਰਦੂ
Name[pl]=Urdu
Name[ps]=اردو
Name[pt]=Urdu
Name[pt_BR]=Urdu
Name[ro]=Urdu
Name[ru]=Урду
Name[se]=Urdugiella
Name[si]=උර්දු
Name[sk]=Urdčina
Name[sl]=urdu
Name[sq]=Urdu
Name[sr]=урду
Name[sr@ijekavian]=урду
Name[sr@ijekavianlatin]=urdu
Name[sr@latin]=urdu
Name[sv]=Urdu
Name[ta]=உருது
Name[te]=ఉర్దు
Name[tg]=Урду
Name[th]=ภาษาอูร์ดู
Name[tr]=Urdu
Name[tt]=Урду
Name[ug]=ئوردۇچە
Name[uk]=Урду
Name[uz]=Urdu
Name[uz@cyrillic]=Урду
Name[vi]=Urdu
Name[wa]=Ourdou
Name[xh]=Urdu
Name[x-test]=xxUrduxx
Name[zh_CN]=乌尔都语
Name[zh_HK]=烏都語
Name[zh_TW]=烏都語
[uz]
Name=Uzbek
Name[af]=Uzbek
Name[ar]=أوزبكية
Name[as]=উজ্‌বেক
Name[ast]=Uzbecu
Name[be]=Узбецкая
Name[be@latin]=Uzbeckaja
Name[bg]=Узбекски
Name[bn]=উজবেক
Name[bn_IN]=উজবেক
Name[br]=Ouzbeg
Name[bs]=Uzbečki
Name[ca]=Usbec
Name[ca@valencia]=Usbec
Name[cs]=Uzbecký
Name[csb]=Ùzbecczi
Name[cy]=Wzbec
Name[da]=Uzbek
Name[de]=Usbekisch
Name[el]=Ουζμπεκικά
Name[en_GB]=Uzbek
Name[eo]=Uzbeka
Name[es]=Uzbeko
Name[et]=Usbeki
Name[eu]=Uzbekera
Name[fa]=ازبکی
Name[fi]=Uzbekki
Name[fr]=Ouzbek
Name[fy]=Oezbeeksk
Name[ga]=Úisbéicis
Name[gl]=Usbeco
Name[gu]=ઉઝબેક
Name[he]=אוזבקית
Name[hi]=उज्बेक
Name[hne]=उज्बेक
Name[hr]=Uzbečki
Name[hsb]=Uzbekisce
Name[hu]=Üzbég
Name[ia]=Uzbeko
Name[id]=Uzbek
Name[is]=Úsbekíska
Name[it]=Usbeco
Name[ja]=ウズベク語
Name[kk]=Өзбекше
Name[km]=អ៊ូហ្សបេគីស្តង់
Name[kn]=ಉಝ್ಬೆಕ್
Name[ko]=우즈베크어
Name[ku]=Ozbekî
Name[lb]=Usbekesch
Name[lt]=Uzbekų
Name[lv]=Uzbeku
Name[mai]=उज्बेक
Name[mk]=Узбекистански
Name[ml]=ഉസ്ബെക്
Name[mr]=उज्बेक
Name[ms]=Uzbek
Name[nb]=Usbekisk
Name[nds]=Usbeeksch
Name[ne]=उज्बेक
Name[nl]=Oezbeeks
Name[nn]=Usbekisk
Name[oc]=Ozbèc
Name[or]=Uzbek
Name[pa]=ਊਜੇਬਕ
Name[pl]=Uzbecki
Name[ps]=ازبکي
Name[pt]=Usbequistanês
Name[pt_BR]=Uzbeque
Name[ro]=Uzbecă
Name[ru]=Узбекский
Name[se]=Uzbehkagiella
Name[si]=උස්බෙක්
Name[sk]=Uzbečtina
Name[sl]=uzbeško
Name[sq]=Uzbeke
Name[sr]=узбечки
Name[sr@ijekavian]=узбечки
Name[sr@ijekavianlatin]=uzbečki
Name[sr@latin]=uzbečki
Name[sv]=Uzbekiska
Name[ta]=உஸ்பெக்
Name[te]=ఉజ్బెక్
Name[tg]=Ӯзбакӣ
Name[th]=ภาษาอุซเบกิสถาน
Name[tr]=Özbek
Name[tt]=Үзбәк
Name[ug]=ئۆزبېكچە
Name[uk]=Узбецька
Name[uz]=Oʻzbekcha
Name[uz@cyrillic]=Ўзбекча
Name[vi]=Uzbek
Name[wa]=Ouzbeke
Name[xh]=Uzbek
Name[x-test]=xxUzbekxx
Name[zh_CN]=乌兹别克语
Name[zh_HK]=烏茲別克語
Name[zh_TW]=烏茲別克語
[uz@cyrillic]
Name=Uzbek (Cyrillic)
Name[ar]=أوزبكية (سريالية)
Name[as]=উজ্‌বেক (ছিৰিলিক)
Name[ast]=Uzbecu (Cirílicu)
Name[be@latin]=Uzbeckaja kirylica
Name[bg]=Узбекски (кирилица)
Name[bn]=উজবেক (কিরিলিক)
Name[bn_IN]=উজবেক (সিরিলিক)
Name[bs]=Uzbečki (ćirilica)
Name[ca]=Usbec (ciríl·lic)
Name[ca@valencia]=Usbec (ciríl·lic)
Name[cs]=Uzbecký (azbuka)
Name[csb]=Ùzbecczi (cërëlica)
Name[da]=Uzbek (Kyrillisk)
Name[de]=Usbekisch (Kyrillisch)
Name[el]=Ουζμπεκικά (Κυριλλικά)
Name[en_GB]=Uzbek (Cyrillic)
Name[eo]=Uzbeka (Cirila)
Name[es]=Uzbeko (Cirílico)
Name[et]=Usbeki (kirillitsa)
Name[eu]=Uzbekera (Zirilikoa)
Name[fi]=Uzbekki (kyrillinen)
Name[fr]=Ouzbek (Cyrillique)
Name[fy]=Uzbeesk (Cyrillysk)
Name[ga]=Úisbéicis (Coireallach)
Name[gl]=Usbeco (cirílico)
Name[gu]=ઉઝબેક (સિરીલિક)
Name[he]=אוזבקית (קירילית)
Name[hi]=उज़बैक (सिरिलिक)
Name[hne]=उजबैक (सिरिलिक)
Name[hr]=Uzbečki (ćirilica)
Name[hsb]=Uzbekisce (z kyriliskim pismom)
Name[hu]=Üzbég (cirill)
Name[ia]=Uzbeko (Cirillico)
Name[id]=Uzbek (Cyrillic)
Name[is]=Úsbekíska (Kyrilísk)
Name[it]=Usbeco (cirillico)
Name[ja]=ウズベク語 (キリル文字)
Name[kk]=Өзбекше (Кирилл жазуы)
Name[km]=អ៊ូហ្សបេគីស្តង់ (ស៊ីលីរីក)
Name[kn]=ಉಝ್ಬೆಕ್ (ಸಿರಿಲಿಕ್)
Name[ko]=우즈베크어 (키릴 문자)
Name[ku]=Ozbekî (Kîrîl)
Name[lt]=Uzbekų (Kirilica)
Name[lv]=Uzbeku (Kirilica)
Name[mai]=उस्बेक (सिरिलिक)
Name[mk]=Узбекистански (кириличен)
Name[ml]=ഉസ്ബെക്ക് (സിറിലിക്ക്)
Name[mr]=उज्बेक (सिरीलिक)
Name[ms]=Uzbek (Cyrillic)
Name[nb]=Usbekisk (Kyrillisk)
Name[nds]=Usbeeksch (kyrillsch)
Name[nl]=Oezbeeks (Cyrilisch)
Name[nn]=Usbekisk (kyrillisk)
Name[oc]=Ozbèc (cirillic)
Name[or]=Uzbek (Cyrillic)
Name[pa]=ਯੂਜ਼ਬਿਕ (ਸਿਰਲਿਕ)
Name[pl]=Uzbecki (cyrlica)
Name[ps]=ازبکي (سېرېلېک)
Name[pt]=Uzbeque (Cirílico)
Name[pt_BR]=Uzbeque (Cirílico)
Name[ro]=Uzbekă (Chirilic)
Name[ru]=Узбекский (кириллица)
Name[se]=Usbekalaš (Kyrillalaš)
Name[si]=උස්බෙක් (සිරිලික්)
Name[sk]=Uzbečtina (cyrilika)
Name[sl]=Uzbeško (cirilica)
Name[sq]=Uzbeke (Cirilike)
Name[sr]=узбечки (ћирилица)
Name[sr@ijekavian]=узбечки (ћирилица)
Name[sr@ijekavianlatin]=uzbečki (ćirilica)
Name[sr@latin]=uzbečki (ćirilica)
Name[sv]=Kyrillisk Uzbekiska
Name[ta]=உஸ்பெக் (சைரிலிக்)
Name[tg]=Ӯзбакӣ (Кирилликӣ)
Name[th]=ภาษาอุซเบกิสถาน (ไซริลลิค)
Name[tr]=Özbekçe (Kiril)
Name[tt]=Үзбәк (кирилл)
Name[ug]=ئۆزبېكچە(سلاۋيان)
Name[uk]=Узбецька (Кирилиця)
Name[vi]=Uzbek (Cyrillic)
Name[wa]=Ouzbeke (cirilike)
Name[x-test]=xxUzbek (Cyrillic)xx
Name[zh_CN]=乌兹别克语(西里尔语系)
Name[zh_TW]=烏茲別克(斯拉夫)
[ven]
Name=Venda
Name[af]=Venda
Name[ar]=فندا
Name[as]=ভেন্ডা
Name[ast]=Venda
Name[be]=Венда
Name[be@latin]=Venda
Name[bg]=Венда
Name[bn]=ভেণ্ডা
Name[bn_IN]=ভেন্ডা
Name[br]=Venda
Name[bs]=Venda
Name[ca]=Venda
Name[ca@valencia]=Venda
Name[cs]=Venda
Name[csb]=Venda
Name[cy]=Venda
Name[da]=Venda
Name[de]=Tshivenda
Name[el]=Venda
Name[en_GB]=Venda
Name[eo]=Vendaa
Name[es]=Venda
Name[et]=Venda
Name[eu]=Venda
Name[fa]=وندا
Name[fi]=Venda
Name[fr]=Venda
Name[fy]=Venda
Name[ga]=Veindis
Name[gl]=Venda
Name[gu]=વેન્ડા
Name[he]=ונדה
Name[hi]=वेंदा
Name[hne]=वेंदा
Name[hr]=Venda
Name[hsb]=Venda
Name[hu]=Venda
Name[ia]=Venda
Name[id]=Venda
Name[is]=Venda
Name[it]=Venda
Name[ja]=ベンダ語
Name[kk]=Венда
Name[km]=វង់ដា
Name[kn]=ವಂಡಾ
Name[ko]=벤다어
Name[ku]=Venda
Name[lb]=Venda
Name[lt]=Venda
Name[lv]=Vendu
Name[mai]=वेंडा
Name[mk]=Венда
Name[ml]=വെന്‍ഡാ
Name[mr]=वेंदा
Name[ms]=Venda
Name[nb]=Venda
Name[nds]=Venda
Name[ne]=भेन्डा
Name[nl]=Venda
Name[nn]=Venda
Name[oc]=Vendà
Name[or]=Venda
Name[pa]=ਵਾਂਡਾ
Name[pl]=Venda
Name[ps]=وېنډا
Name[pt]=Venda
Name[pt_BR]=Venda
Name[ro]=Venda
Name[ru]=Венда
Name[se]=Vendagiella
Name[si]=වෙන්ඩා
Name[sk]=Venda
Name[sl]=venda
Name[sq]=Venda
Name[sr]=венда
Name[sr@ijekavian]=венда
Name[sr@ijekavianlatin]=venda
Name[sr@latin]=venda
Name[sv]=Venda
Name[ta]=வெண்டா
Name[te]=వెండా
Name[tg]=Вендагӣ
Name[th]=ภาษาเวนดา
Name[tr]=Venda
Name[tt]=Венда
Name[ug]=ۋېنداچە
Name[uk]=Венда
Name[uz]=Venda
Name[uz@cyrillic]=Венда
Name[vi]=Venda
Name[wa]=Venda
Name[xh]=Venda
Name[x-test]=xxVendaxx
Name[zh_CN]=文达语
Name[zh_TW]=Venda
[vi]
Name=Vietnamese
Name[af]=Viëtnamees
Name[ar]=فيتنامية
Name[as]=ভিয়েট্নামি
Name[ast]=Vietnamita
Name[be]=В'етнамская
Name[be@latin]=Vietnamskaja
Name[bg]=Виетнамски
Name[bn]=ভিয়েতনামী
Name[bn_IN]=ভিয়েতনামিস
Name[br]=Viet-Nameg
Name[bs]=Vijetnamski
Name[ca]=Vietnamita
Name[ca@valencia]=Vietnamita
Name[cs]=Vietnamský
Name[csb]=Wietnamsczi
Name[cy]=Vietnameg
Name[da]=Vietnamesisk
Name[de]=Vietnamesisch
Name[el]=Βιετναμέζικα
Name[en_GB]=Vietnamese
Name[eo]=Vjetnama
Name[es]=Vietnamita
Name[et]=Vietnami
Name[eu]=Vietnamera
Name[fa]=ویتنامی
Name[fi]=Vietnam
Name[fr]=Vietnamien
Name[fy]=Vietnameesk
Name[ga]=Vítneamais
Name[gl]=Vietnamita
Name[gu]=વિયેટનામીઝ
Name[he]=וייטנאמית
Name[hi]=विएतनामी
Name[hne]=विएतनामी
Name[hr]=Vijetnamski
Name[hsb]=Vietnamsce
Name[hu]=Vietnami
Name[ia]=Vietnamese
Name[id]=Vietnam
Name[is]=Víetnamska
Name[it]=Vietnamita
Name[ja]=ベトナム語
Name[kk]=Вьетнамша
Name[km]=វៀតណាម
Name[kn]=ವಿಯೆಟ್ನಾಮೀಸ್
Name[ko]=베트남어
Name[ku]=Viyetnamî
Name[lb]=Vietnamesesch
Name[lt]=Vietnamiečių
Name[lv]=Vjetnamiešu
Name[mai]=विएतनामी
Name[mk]=Виетнамски
Name[ml]=വിയറ്റ്നാമീസ്
Name[mr]=वियेतनामी
Name[ms]=Vietnam
Name[nb]=Vietnamesisk
Name[nds]=Vietnameesch
Name[ne]=भियतनामी
Name[nl]=Vietnamees
Name[nn]=Vietnamesisk
Name[oc]=Vietnamés
Name[or]=Vietnamese
Name[pa]=ਵੀਅਤਨਾਮੀ
Name[pl]=Wietnamski
Name[ps]=وېټنامي
Name[pt]=Vietnamita
Name[pt_BR]=Vietnamita
Name[ro]=Vietnameză
Name[ru]=Вьетнамский
Name[se]=Vietnamagiella
Name[si]=වියට්නාම
Name[sk]=Vietnamčina
Name[sl]=vietnamsko
Name[sq]=Vietnameze
Name[sr]=вијетнамски
Name[sr@ijekavian]=вијетнамски
Name[sr@ijekavianlatin]=vijetnamski
Name[sr@latin]=vijetnamski
Name[sv]=Vietnamesiska
Name[ta]=வியட்னாமிய
Name[te]=వియత్నామీస్
Name[tg]=Ветнамӣ
Name[th]=ภาษาเวียดนาม
Name[tr]=Vietnamca
Name[tt]=Вьетнам
Name[ug]=ۋىيېتنامچە
Name[uk]=В'єтнамська
Name[uz]=Vetnamcha
Name[uz@cyrillic]=Ветнамча
Name[vi]=Việt Nam
Name[wa]=Vietnamyin
Name[xh]=Vietnamese
Name[x-test]=xxVietnamesexx
Name[zh_CN]=越南语
Name[zh_HK]=越南語
Name[zh_TW]=越南語
[vo]
Name=Volapük
Name[af]=Volapük
Name[ar]=فولابوك
Name[as]=ভ'লাপুক
Name[ast]=Volapük
Name[be]=Валапук
Name[be@latin]=Volapük
Name[bg]=Волапюк
Name[bn]=ভোলাপুক
Name[bn_IN]=ভোলাপুক
Name[br]=Volapuk
Name[bs]=Volapik
Name[ca]=Volapük
Name[ca@valencia]=Volapük
Name[cs]=Volapük
Name[csb]=Volapük
Name[cy]=Volapuk
Name[da]=Volapyk
Name[de]=Volapük
Name[el]=Volapük
Name[en_GB]=Volapük
Name[eo]=Volapuko
Name[es]=Volapük
Name[et]=Volapük
Name[eu]=Volapük
Name[fa]=ولاپوک
Name[fi]=Volapük
Name[fr]=Volapük
Name[fy]=Volapük
Name[ga]=Volapük
Name[gl]=Volapük
Name[gu]=વોલ્પુક
Name[he]=וולאפיק
Name[hi]=वोलापक
Name[hne]=वोलापक
Name[hr]=Volapük
Name[hsb]=Volapük
Name[hu]=Volapük
Name[ia]=Volapuk
Name[id]=Volapük
Name[is]=Volapük
Name[it]=Volapük
Name[ja]=ボラピューク語
Name[kk]=Волапюк
Name[km]=វ៉ូឡាភូក
Name[kn]=ವೋಲಾಪ್ಕ್
Name[ko]=볼라퓌크
Name[ku]=Volapük
Name[lb]=Volapük
Name[lt]=Volapük
Name[lv]=Volapiks
Name[mai]=वोलापक
Name[mk]=Волапук
Name[ml]=വോളാപുക്
Name[mr]=वोलापक
Name[ms]=Volapük
Name[nb]=Volapük
Name[nds]=Volapük
Name[ne]=भोलापुक
Name[nl]=Volapúk
Name[nn]=Volapyk
Name[or]=Volapük
Name[pa]=ਵੂਲਾਉਕ
Name[pl]=Volapük
Name[pt]=Volapük
Name[pt_BR]=Volapuque
Name[ro]=Volapucă
Name[ru]=Волапюк
Name[se]=Volapükgiella
Name[si]=වොලුපුක්
Name[sk]=Volapük
Name[sl]=volapük
Name[sq]=Volapük
Name[sr]=волапик
Name[sr@ijekavian]=волапик
Name[sr@ijekavianlatin]=volapik
Name[sr@latin]=volapik
Name[sv]=Volapük
Name[ta]=வொல்புக்
Name[te]=వొలపుక్
Name[tg]=Волапёкӣ
Name[th]=ภาษาวอลาพุค
Name[tr]=Volapük
Name[tt]=Волапюк
Name[ug]=ۋولاپۇكچە
Name[uk]=Волапюк
Name[uz]=Volapuk
Name[uz@cyrillic]=Волапук
Name[vi]=Volapük
Name[wa]=Volapük
Name[xh]=Volapük
Name[x-test]=xxVolapükxx
Name[zh_CN]=沃拉普克语
Name[zh_TW]=Volapük
[wa]
Name=Walloon
Name[af]=Walloon
Name[ar]=والونية
Name[as]=ৱেলুন
Name[ast]=Valón
Name[be]=Валонская
Name[be@latin]=Valonskaja
Name[bg]=Валонски
Name[bn]=ওয়ালুন
Name[bn_IN]=ওয়ালুন
Name[br]=Walloneg
Name[bs]=Valonski
Name[ca]=Való
Name[ca@valencia]=Való
Name[cs]=Wallonský
Name[csb]=Walońsczi
Name[cy]=Walloon
Name[da]=Vallonsk
Name[de]=Wallonisch
Name[el]=Walloon
Name[en_GB]=Walloon
Name[eo]=Valona
Name[es]=Walloon
Name[et]=Vallooni
Name[eu]=Waloiera
Name[fa]=والونی
Name[fi]=Valloni
Name[fr]=Wallon
Name[fy]=Waalsk
Name[ga]=Vallúnais
Name[gl]=Valón
Name[gu]=વોલુન
Name[he]=וולונית
Name[hi]=वालून
Name[hne]=वालून
Name[hr]=Valonski
Name[hsb]=Walloon
Name[hu]=Vallon
Name[ia]=Valloniano
Name[id]=Walloon
Name[is]=Vallónska
Name[it]=Vallone
Name[ja]=ワロン語
Name[kk]=Валонша
Name[km]=វ៉ាឡុង
Name[kn]=ವಾಲ್ಲೂನ್
Name[ko]=왈론어
Name[ku]=Walûn
Name[lb]=Wallounesch
Name[lt]=Valonų
Name[lv]=Valoņu
Name[mai]=वालून
Name[mk]=Валонски
Name[ml]=വലൂണ്‍
Name[mr]=वालून
Name[ms]=Walloon
Name[nb]=Vallonsk
Name[nds]=Walloonsch
Name[ne]=वालोन
Name[nl]=Waals
Name[nn]=Vallonsk
Name[oc]=Valon
Name[or]=Walloon
Name[pa]=ਵਾਲੂਨ
Name[pl]=Waloński
Name[ps]=والون
Name[pt]=Valão
Name[pt_BR]=Valão
Name[ro]=Valonă
Name[ru]=Валлонский
Name[se]=Vallonagiella
Name[si]=වැලූන්
Name[sk]=Valónčina
Name[sl]=walloonsko
Name[sq]=Valone
Name[sr]=валонски
Name[sr@ijekavian]=валонски
Name[sr@ijekavianlatin]=valonski
Name[sr@latin]=valonski
Name[sv]=Vallonska
Name[ta]=வாலூன்
Name[te]=వాలూన్
Name[tg]=Валунӣ
Name[th]=ภาษาวัลลูน
Name[tr]=Valonca
Name[tt]=Валлон
Name[ug]=ۋاللۇنچە
Name[uk]=Валлонська
Name[uz]=Valloncha
Name[uz@cyrillic]=Валлонча
Name[vi]=Walloon
Name[wa]=Walon
Name[xh]=Walloon
Name[x-test]=xxWalloonxx
Name[zh_CN]=瓦龙语
Name[zh_HK]=華隆語
Name[zh_TW]=華隆語
[wo]
Name=Wolof
Name[af]=Wolof
Name[ar]=وولوف
Name[as]=ৱ'ল'ফ
Name[ast]=Wolof
Name[be]=Волаф
Name[be@latin]=Wolof
Name[bg]=Уолоф
Name[bn]=ওয়োলফ
Name[bn_IN]=ওলুফ
Name[br]=Wolof
Name[bs]=Volof
Name[ca]=Wolof
Name[ca@valencia]=Wolof
Name[cs]=Wolof
Name[csb]=Wolof
Name[cy]=Woloff
Name[da]=Wolof
Name[de]=Wolof
Name[el]=Wolof
Name[en_GB]=Wolof
Name[eo]=Volofa
Name[es]=Wolof
Name[et]=Volofi
Name[eu]=Wolofera
Name[fa]=ولوف
Name[fi]=Wolof
Name[fr]=Wolof
Name[fy]=Wolof
Name[ga]=Volaifis
Name[gl]=Wolof
Name[gu]=વોલોફ
Name[he]=וולוף
Name[hi]=वालाफ़
Name[hne]=वालाफ
Name[hr]=Volofski
Name[hsb]=Wolof
Name[hu]=Volof
Name[ia]=Wolof
Name[id]=Wolof
Name[is]=Wolof
Name[it]=Wolof
Name[ja]=ウォロフ語
Name[kk]=Волофша
Name[km]=វូឡុហ្វ
Name[kn]=ವೋಲೋಫ್
Name[ko]=월로프어
Name[ku]=Wolof
Name[lb]=Wolof-Sprooch
Name[lt]=Wolof
Name[lv]=Volofu
Name[mai]=वालाफ़
Name[mk]=Волоф
Name[ml]=വൊലോ
Name[mr]=वालाफ़
Name[ms]=Wolof
Name[nb]=Wolof
Name[nds]=Wolof
Name[ne]=वलोफ
Name[nl]=Wolof
Name[nn]=Wolof
Name[oc]=Wolof
Name[or]=Wolof
Name[pa]=ਵੂਲੂਫ
Name[pl]=Wolof
Name[ps]=ولوف
Name[pt]=Wolof
Name[pt_BR]=Jalofo
Name[ro]=Volofă
Name[ru]=Волоф
Name[se]=Volofagiella
Name[si]=වොලොෆ්
Name[sk]=Wolof
Name[sl]=wolof
Name[sq]=Wolof
Name[sr]=волоф
Name[sr@ijekavian]=волоф
Name[sr@ijekavianlatin]=volof
Name[sr@latin]=volof
Name[sv]=Wolof
Name[ta]=வொலொப்
Name[te]=వొలొఫ్
Name[tg]=Волофӣ
Name[th]=ภาษาวอลอฟ
Name[tr]=Wolof
Name[tt]=Волофча
Name[ug]=ۋولوفچە
Name[uk]=Волоф
Name[uz]=Volof
Name[uz@cyrillic]=Волоф
Name[vi]=Wolof
Name[wa]=Wolof
Name[xh]=Wolof
Name[x-test]=xxWolofxx
Name[zh_CN]=沃洛夫语
Name[zh_HK]=Wolof語
Name[zh_TW]=Wolof語
[xh]
Name=Xhosa
Name[af]=Xhosa
Name[ar]=بهوسا
Name[as]=জ'ছা
Name[ast]=Xhosa
Name[be]=Кшоса
Name[be@latin]=Xhosa
Name[bg]=Ксоса
Name[bn]=জোসা
Name[bn_IN]=জোসা
Name[br]=Xhosa
Name[bs]=Ksosa
Name[ca]=Xosa
Name[ca@valencia]=Xosa
Name[cs]=Xhosa
Name[csb]=Xhosa
Name[cy]=Xhosa
Name[da]=Xhosa
Name[de]=Xhosa
Name[el]=Xhosa
Name[en_GB]=Xhosa
Name[eo]=Kosa
Name[es]=Xhosa
Name[et]=Xhosa
Name[eu]=Xhosera
Name[fa]=زسایی
Name[fi]=Xhosa
Name[fr]=Xhosa
Name[fy]=Xhosa
Name[ga]=Cóisis
Name[gl]=Xhosa
Name[gu]=હોસા
Name[he]=קוזה
Name[hi]=झ़ोसा
Name[hne]=झ़ोसा
Name[hr]=Xhosa
Name[hsb]=Xhosa
Name[hu]=Xhosza
Name[ia]=Xhosa
Name[id]=Xhosa
Name[is]=Xhosa
Name[it]=Xhosa
Name[ja]=コサ語
Name[kk]=Кхоса
Name[km]=ឃូសា
Name[kn]=ಗ್ಝೋಸಾ
Name[ko]=코사어
Name[ku]=hosa
Name[lb]=Xhosa
Name[lt]=Xhosa
Name[lv]=Khosu
Name[mai]=झ़ोसा
Name[mk]=Ксоса
Name[ml]=ഖോസ
Name[mr]=झ़ोसा
Name[ms]=Xhosa
Name[nb]=Xhosa
Name[nds]=Xhosa
Name[ne]=होसा
Name[nl]=Xhosa
Name[nn]=Xhosa
Name[oc]=Xhosa
Name[or]=Xhosa
Name[pa]=ਝੋਸਾ
Name[pl]=Xhosa
Name[ps]=کوسا
Name[pt]=Xhosa
Name[pt_BR]=Xhosa
Name[ro]=Xhosa
Name[ru]=Коса
Name[se]=Xhosagiella
Name[si]=ෂොසා
Name[sk]=Xhoština
Name[sl]=xhosa
Name[sq]=Xhosa
Name[sr]=кхоса
Name[sr@ijekavian]=кхоса
Name[sr@ijekavianlatin]=khosa
Name[sr@latin]=khosa
Name[sv]=Xhosa
Name[ta]=சோசா
Name[te]=క్జొసా
Name[tg]=Хоса
Name[th]=ภาษาโคซา
Name[tr]=Xhosa
Name[tt]=Коса
Name[ug]=كوساچە
Name[uk]=Хоза
Name[uz]=Xhosa
Name[uz@cyrillic]=Хҳоса
Name[vi]=Xhosa
Name[wa]=Xhossa
Name[xh]=isixhosa
Name[x-test]=xxXhosaxx
Name[zh_CN]=科萨语
Name[zh_HK]=科薩語
Name[zh_TW]=科薩語
[x-test]
Name=KDE Test Language
Name[ar]=لغة اختبار كدي
Name[bg]=Пробен език за KDE
Name[bs]=KDE Test jezik
Name[ca]=Idioma de proves del KDE
Name[ca@valencia]=Idioma de proves del KDE
Name[cs]=Testovací jazyk KDE
Name[da]=KDE-testsprog
Name[de]=KDE-Testsprache
Name[el]=Δοκιμαστική γλώσσα του KDE
Name[en_GB]=KDE Test Language
Name[es]=Idioma de pruebas de KDE
Name[et]=KDE testkeel
Name[eu]=KDE-ren proba hizkuntza
Name[fa]=آزمون زبان KDE
Name[fi]=KDE:n testikieli
Name[fr]=Langue de test de KDE
Name[ga]=Teanga Thástála KDE
Name[gl]=Lingua de probas de KDE
Name[he]=שפת בדיקה של KDE
Name[hr]=KDE-ov pokusni jezik
Name[hu]=KDE tesztnyelv
Name[ia]=Linguage de essayo de KDE
+Name[id]=Bahasa Tes KDE
Name[is]=KDE prófunartungumál
Name[it]=Lingua di prova KDE
Name[ja]=KDE テスト言語
Name[kk]=KDE сынақ тілі
Name[km]=ភាសា​សាកល្បង​របស់ KDE
Name[ko]=KDE 테스트 언어
Name[lt]=KDE bandomoji kalba
Name[lv]=KDE testēšanas valoda
Name[nb]=KDE test-språk
Name[nds]=KDE-Utprobeerspraak
Name[nl]=KDE-testtaal
Name[pa]=KDE ਟੈਸਟ ਭਾਸ਼ਾ
Name[pl]=Język testowy KDE
Name[pt]=Língua de Testes do KDE
Name[pt_BR]=Idioma de teste do KDE
Name[ro]=Limbă de test KDE
Name[ru]=Тестовый язык KDE
Name[se]=KDE-geahččalangiella
Name[sk]=Testovací jazyk KDE
Name[sl]=Poizkusni jezik KDE
Name[sq]=Gjuha KDE e testimit
Name[sr]=пробни језик
Name[sr@ijekavian]=пробни језик
Name[sr@ijekavianlatin]=probni jezik
Name[sr@latin]=probni jezik
Name[sv]=KDE testspråk
Name[tg]=Санҷиши забони KDE
Name[th]=ภาษาทดสอบ KDE
Name[tr]=KDE Test Dili
Name[tt]=KDE тикшертү теле
Name[ug]=KDE سىناق تىل
Name[uk]=Тестова мова KDE
Name[vi]=Ngôn ngữ kiểm tra KDE
Name[wa]=Saye di lingaedje KDE
Name[x-test]=xxKDE Test Languagexx
Name[zh_CN]=KDE 测试语言
Name[zh_TW]=KDE 測試語言
[yi]
Name=Yiddish
Name[af]=Yiddish
Name[ar]=يديش
Name[as]=যিদ্দিশ্ব
Name[ast]=Yidis
Name[be]=Ідыш
Name[be@latin]=Idyš
Name[bg]=Идиш
Name[bn]=য়িড্ডিশ
Name[bn_IN]=ইড্ডিশ
Name[br]=Yiddish
Name[bs]=Jidiš
Name[ca]=Jiddisch
Name[ca@valencia]=Jiddisch
Name[cs]=Jidiš
Name[csb]=Jidisz
Name[cy]=Yideg
Name[da]=Jiddisch
Name[de]=Jiddish
Name[el]=Yiddish
Name[en_GB]=Yiddish
Name[eo]=Jida
Name[es]=Yiddish
Name[et]=Jidiš
Name[eu]=Jiddish
Name[fa]=عبری
Name[fi]=Jiddiš
Name[fr]=Yiddish
Name[fy]=Jiddysk
Name[ga]=Giúdais
Name[gl]=Iídiche
Name[gu]=યિદ્દિશ
Name[he]=יידיש
Name[hi]=यिड्डिष
Name[hne]=यिड्डिस
Name[hr]=Jidiš
Name[hsb]=Jidisce
Name[hu]=Jiddis
Name[ia]=Yiddish
Name[id]=Yiddish
Name[is]=Jiddíska
Name[it]=Yiddish
Name[ja]=イディッシュ語
Name[kk]=Идиш
Name[km]=យីឌីហ្ស
Name[kn]=ಯಿದ್ದಿಷ್
Name[ko]=이디시어
Name[ku]=Yidî
Name[lb]=Jiddesch
Name[lt]=Jidiš
Name[lv]=Jidišs
Name[mai]=यिड्डिश
Name[mk]=Еврејски
Name[ml]=യിദ്ദിഷ്
Name[mr]=यिड्डिष
Name[ms]=Yiddish
Name[nb]=Jiddisk
Name[nds]=Jiddsch
Name[ne]=यीडिस
Name[nl]=Yiddish
Name[nn]=Jiddisch
Name[oc]=Yiddish
Name[or]=Yiddish
Name[pa]=ਯੀਡਿਸ਼
Name[pl]=Jidysz
Name[ps]=يډېش
Name[pt]=Yiddish
Name[pt_BR]=Iídiche
Name[ro]=Idiș
Name[ru]=Идиш
Name[se]=Jiddišgiella
Name[si]=යිඩිෂ්
Name[sk]=Jidiš
Name[sl]=yiddish
Name[sq]=Jidish
Name[sr]=јидиш
Name[sr@ijekavian]=јидиш
Name[sr@ijekavianlatin]=jidiš
Name[sr@latin]=jidiš
Name[sv]=Yiddish
Name[ta]=யிட்டிஷ்
Name[te]=యిడ్డిష్
Name[tg]=Юйдишӣ
Name[th]=ภาษายิดดิช
Name[tr]=Yiddish
Name[tt]=Идиш
Name[ug]=يىددىشچە
Name[uk]=Ідиш
Name[uz]=Yiddish
Name[uz@cyrillic]=Йиддиш
Name[vi]=Yiddish
Name[wa]=Yidish
Name[xh]=Yiddish
Name[x-test]=xxYiddishxx
Name[zh_CN]=依地语
Name[zh_HK]=意第緒語
Name[zh_TW]=意第緒語
[yo]
Name=Yoruba
Name[af]=Yoruba
Name[ar]=يوروبا
Name[as]=য'ৰুবা
Name[ast]=Yoruba
Name[be]=Яруба
Name[be@latin]=Yoruba
Name[bg]=Йоруба
Name[bn]=য়োরুবা
Name[bn_IN]=ইওরুবা
Name[br]=Yoruba
Name[bs]=Joruba
Name[ca]=Ioruba
Name[ca@valencia]=Ioruba
Name[cs]=Yoruba
Name[csb]=Yoruba
Name[cy]=Yoruba
Name[da]=Yoruba
Name[de]=Yoruba
Name[el]=Yoruba
Name[en_GB]=Yoruba
Name[eo]=Joruba
Name[es]=Yoruba
Name[et]=Joruba
Name[eu]=Jorubera
Name[fa]=یوروبا
Name[fi]=Joruba
Name[fr]=Yoruba
Name[fy]=Yoruba
Name[ga]=Iarúibis
Name[gl]=Yorubá
Name[gu]=યોરુબા
Name[he]=יורובה
Name[hi]=योरुबा
Name[hne]=योरुबा
Name[hr]=Yoruba
Name[hsb]=Yoruba
Name[hu]=Joruba
Name[ia]=Yoruba
Name[id]=Yoruba
Name[is]=Yoruba
Name[it]=Yoruba
Name[ja]=ヨルバ語
Name[kk]=Йоруба
Name[km]=យរូបា
Name[kn]=ಯೋರುಬಾ
Name[ko]=요루바어
Name[ku]=Yoruba
Name[lb]=Yoruba-Sprooch
Name[lt]=Yoruba
Name[lv]=Jorubu
Name[mai]=योरुबा
Name[mk]=Јоруба
Name[ml]=യോറൂബ
Name[mr]=योरुबा
Name[ms]=Yoruba
Name[nb]=Yoruba
Name[nds]=Yoruba
Name[ne]=योरुबा
Name[nl]=Yoruba
Name[nn]=Joruba
Name[or]=Yoruba
Name[pa]=ਯੂਰੋਬਾ
Name[pl]=Yoruba
Name[ps]=يوروبا
Name[pt]=Yoruba
Name[pt_BR]=Iorubá
Name[ro]=Iorubă
Name[ru]=Йоруба
Name[se]=Jorubagiella
Name[si]=යොරුබා
Name[sk]=Jorubčina
Name[sl]=yoruba
Name[sq]=Joruba
Name[sr]=јоруба
Name[sr@ijekavian]=јоруба
Name[sr@ijekavianlatin]=joruba
Name[sr@latin]=joruba
Name[sv]=Yoruba
Name[ta]=யொரூபா
Name[te]=యొరుబా
Name[tg]=Ёрубавӣ
Name[th]=ภาษาโยรูบา
Name[tr]=Yoruba
Name[tt]=Йоруба
Name[ug]=يورۇباچە
Name[uk]=Йоруба
Name[uz]=Yoruba
Name[uz@cyrillic]=Ёруба
Name[vi]=Yoruba
Name[wa]=Yorouba
Name[xh]=Yoruba
Name[x-test]=xxYorubaxx
Name[zh_CN]=约鲁巴语
Name[zh_HK]=優魯巴語
Name[zh_TW]=優魯巴語
[za]
Name=Zhuang
Name[af]=Zhuang
Name[ar]=جوانغ
Name[as]=ঝুৱাঙ
Name[ast]=Zhuang
Name[be]=Цванг
Name[be@latin]=Zhuang
Name[bg]=Жуанг
Name[bn]=ঝুয়াং
Name[bn_IN]=জুয়াং
Name[br]=Zhuang
Name[bs]=Žuang
Name[ca]=Zhuang
Name[ca@valencia]=Zhuang
Name[cs]=Zhuang
Name[csb]=Zhuang
Name[cy]=Zhuang
Name[da]=Zhuang
Name[de]=Zhuang
Name[el]=Zhuang
Name[en_GB]=Zhuang
Name[eo]=Ĝuanga
Name[es]=Zhuang
Name[et]=Zhuangi
Name[eu]=Zhuang
Name[fa]=هونگ
Name[fi]=Zhuang
Name[fr]=Zhuang
Name[fy]=Zhuang
Name[ga]=Siuáingis
Name[gl]=Chuan
Name[gu]=હુઆંગ
Name[he]=ג'ואנג
Name[hi]=झुआंग
Name[hne]=झुआंग
Name[hr]=Zhuang
Name[hsb]=Zhuang
Name[hu]=Zhuang
Name[ia]=Zhuang
Name[id]=Zhuang
Name[is]=Zhuang
Name[it]=Zhuang
Name[ja]=チュワン語
Name[kk]=Зуангша
Name[km]=ចួង
Name[kn]=ಝುವಾಂಗ್
Name[ko]=좡어
Name[ku]=Zhuang
Name[lb]=Zhuang
Name[lt]=Zhuang
Name[lv]=Džuanu
Name[mai]=झुआंग
Name[mk]=Жуанг
Name[ml]=ഹുവാങ്
Name[mr]=झुआंग
Name[ms]=Zhuang
Name[nb]=Zhuang
Name[nds]=Zhuang
Name[ne]=झिआङ
Name[nl]=Zhuang
Name[nn]=Zhuang
Name[or]=Zhuang
Name[pa]=ਜ਼ੂੰਗ
Name[pl]=Zhuang
Name[pt]=Zhuang
Name[pt_BR]=Zhuang
Name[ro]=Zhuang
Name[ru]=Чжуанский
Name[se]=Zhuangagiella
Name[si]=ෂුවෑං
Name[sk]=Čuangčina
Name[sl]=zhuang
Name[sq]=Zhuang
Name[sr]=жуанг
Name[sr@ijekavian]=жуанг
Name[sr@ijekavianlatin]=žuang
Name[sr@latin]=žuang
Name[sv]=Zhuang
Name[ta]=சுவாங்
Name[te]=జువాంగ్
Name[tg]=Ҷуангӣ
Name[th]=ภาษาจ้วง
Name[tr]=Zhuang
Name[tt]=Зулча
Name[ug]=جۇاڭچە
Name[uk]=Чжуань
Name[uz]=Zxuang
Name[uz@cyrillic]=Зхуанг
Name[vi]=Zhuang
Name[wa]=Zhuang
Name[xh]=Zhuang
Name[x-test]=xxZhuangxx
Name[zh_CN]=壮语
Name[zh_HK]=Zhuang語
Name[zh_TW]=Zhuang語
[zh]
Name=Chinese
Name[af]=Sjinees
Name[ar]=صينية
Name[as]=চীনা
Name[ast]=Chinu
Name[be]=Кітайская
Name[be@latin]=Kitajskaja
Name[bg]=Китайски
Name[bn]=চৈনিক
Name[bn_IN]=চিনা
Name[br]=Sinaeg
Name[bs]=Kineski
Name[ca]=Xinès
Name[ca@valencia]=Xinés
Name[cs]=Čínský
Name[csb]=Chińsczi
Name[cy]=Tseineeg
Name[da]=Kinesisk
Name[de]=Chinesisch
Name[el]=Κινέζικα
Name[en_GB]=Chinese
Name[eo]=Ĉina
Name[es]=Chino
Name[et]=Hiina
Name[eu]=Txinera
Name[fa]=چینی
Name[fi]=Kiina
Name[fr]=Chinois
Name[fy]=Sjineesk
Name[ga]=Sínis
Name[gl]=Chinés
Name[gu]=ચાઇનિઝ
Name[he]=סינית
Name[hi]=चीनी
Name[hne]=चीनी
Name[hr]=Kineski
Name[hsb]=Chinsce
Name[hu]=Kínai
Name[ia]=Chinese
Name[id]=China
Name[is]=Kínverska
Name[it]=Cinese
Name[ja]=中国語
Name[kk]=Қытайша
Name[km]=ចិន
Name[kn]=ಚೀನೀ
Name[ko]=중국어
Name[ku]= Çînî
Name[lb]=Chinesesch
Name[lt]=Kiniečių
Name[lv]=Ķīniešu
Name[mai]=चीनी
Name[mk]=Кинески
Name[ml]=ചൈനീസ്
Name[mr]=चायनिज
Name[ms]=China
Name[nb]=Kinesisk
Name[nds]=Chineesch
Name[ne]=चिनियाँ
Name[nl]=Chinees
Name[nn]=Kinesisk
Name[oc]=Chinés
Name[or]=Chinese
Name[pa]=ਚੀਨੀ
Name[pl]=Chiński
Name[ps]=چينايي
Name[pt]=Chinês
Name[pt_BR]=Chinês
Name[ro]=Chineză
Name[ru]=Китайский
Name[se]=Kiinnágiella
Name[si]=චීන
Name[sk]=Čínština
Name[sl]=kitajsko
Name[sq]=Kineze
Name[sr]=кинески
Name[sr@ijekavian]=кинески
Name[sr@ijekavianlatin]=kineski
Name[sr@latin]=kineski
Name[sv]=Kinesiska
Name[ta]=சீனம்
Name[te]=చైనీస్
Name[tg]=Хитоӣ
Name[th]=ภาษาจีน
Name[tr]=Çince
Name[tt]=Кытайча
Name[ug]=خەنزۇچە
Name[uk]=Китайська
Name[uz]=Xitoycha
Name[uz@cyrillic]=Хитойча
Name[vi]=Trung Quốc
Name[wa]=Chinwès
Name[xh]=Isitshayina
Name[x-test]=xxChinesexx
Name[zh_CN]=中文
Name[zh_HK]=中文
Name[zh_TW]=中文
[zh_CN]
Name=Chinese Simplified
Name[af]=Sjinees vereenvoudig
Name[ar]=صينية مبسطة
Name[as]=চীনা সৰল কৰা
Name[ast]=Chinu simplificáu
Name[be]=Кітайская спрошчаная
Name[be@latin]=Kitajskaja sproščanaja
Name[bg]=Китайски опростен
Name[bn]=সরলীকৃত চৈনিক
Name[bn_IN]=চিনা (সরলীকৃত)
Name[br]=Sineg eeun
Name[bs]=Kineski pojednostavljeni
Name[ca]=Xinès simplificat
Name[ca@valencia]=Xinés simplificat
Name[cs]=Čínský (zjednodušená)
Name[csb]=Chińsczi Prosti
Name[cy]=Tseineeg Syml
Name[da]=Kinesisk simplificeret
Name[de]=Chinesisch (Kurzzeichen)
Name[el]=Κινέζικα απλά
Name[en_GB]=Chinese Simplified
Name[eo]=Ĉina, simpligita
Name[es]=Chino simplificado
Name[et]=Hiina (lihtsustatud)
Name[eu]=Txinatar soildua
Name[fa]=زبان چینی ساده‌شده
Name[fi]=Yksinkertaistettu kiina
Name[fr]=Chinois simplifié
Name[fy]=Sjineesk (ynfaldich)
Name[ga]=Sínis Simplithe
Name[gl]=Chinés simplificado
Name[gu]=સરળ ચાઇનિઝ
Name[he]=סינית מפושטת
Name[hi]=चीनी सरल
Name[hne]=चीनी सरल
Name[hr]=Kineski pojednostavljen
Name[hsb]=Chinsce (zjednorjene)
Name[hu]=Kínai (egyszerűsített)
Name[ia]=Chinese Simplificate
Name[id]=China Disederhanakan
Name[is]=Einfölduð kínverska
Name[it]=Cinese semplificato
Name[ja]=中国語 簡体字
Name[kk]=Жеңілдеткен қытайша
Name[km]=ចិនសាមញ្ញ
Name[kn]=ಸರಳ ಚಿನೀ
Name[ko]=중국어 간체
Name[ku]=Çîniya Hêsankirî
Name[lb]=Einfacht Chinesesch
Name[lt]=Kinų supaprastinta
Name[lv]=Ķīniešu vienkāršotā
Name[mai]=चीनी (सरल)
Name[mk]=Кинески поедноставен
Name[ml]=ലളിതമായ ചൈനീസ്
Name[mr]=विश्लेषीत चिनी
Name[ms]=Chinese Simplified
Name[nb]=Forenklet kinesisk
Name[nds]=Vereenfacht Chineesch
Name[ne]=सरलीकृत चिनियाँ
Name[nl]=Chinees (Vereenvoudigd)
Name[nn]=Kinesisk (forenkla)
Name[oc]=Chinés simplificat
Name[or]=Chinese Simplified
Name[pa]=ਚੀਨੀ ਸਧਾਰਨ
Name[pl]=Chiński uproszczony
Name[ps]=چينايي ساده
Name[pt]=Chinês Simplificado
Name[pt_BR]=Chinês simplificado
Name[ro]=Chineză simplificată
Name[ru]=Китайский (Китай)
Name[se]=Álkiduvvon kiinnágiella
Name[si]=චීන සරළ
Name[sk]=Čínština (zjednodušená)
Name[sl]=poenostavljeno kitajsko
Name[sq]=Kineze e Thjeshtëzuar
Name[sr]=кинески, поједностављени
Name[sr@ijekavian]=кинески, поједностављени
Name[sr@ijekavianlatin]=kineski, pojednostavljeni
Name[sr@latin]=kineski, pojednostavljeni
Name[sv]=Förenklad kinesiska
Name[ta]=எளியமையான சீனம்
Name[te]=సరళికరించిన చైనీస్
Name[tg]=Хитоии Оддӣ
Name[th]=ภาษาจีนประยุกต์
Name[tr]=Basitleştirilmiş Çince
Name[tt]=Кытай (җиңел)
Name[ug]=ئاددىي خەنزۇچە
Name[uk]=Китайська (спрощена)
Name[uz]=Soddalashtirilgan Xitoycha
Name[uz@cyrillic]=Соддалаштирилган Хитойча
Name[vi]=Trung Quốc giản thể
Name[wa]=Chinwès simplifyî
Name[x-test]=xxChinese Simplifiedxx
Name[zh_CN]=简体中文
Name[zh_HK]=簡體中文
Name[zh_TW]=簡體中文
[zh_HK]
Name=Chinese (Hong Kong)
Name[af]=Sjinees (Hong Kong)
Name[ar]=صينية (هونغ كونغ)
Name[as]=চীনা (হং কং)
Name[ast]=Chinu (Hong Kong)
Name[be]=Кітайская (Гонг-Конг)
Name[be@latin]=Kitajskaja (Hong Kong)
Name[bg]=Китайски (Хонг Конг)
Name[bn]=চৈনিক (হংকং)
Name[bn_IN]=চিনা (হং কং)
Name[br]=Sinaeg (Hong Kong)
Name[bs]=Kineski(Hong Kong)
Name[ca]=Xinès (Hong Kong)
Name[ca@valencia]=Xinés (Hong Kong)
Name[cs]=Čínský (Hong Kong)
Name[csb]=Chińsczi (Hong Kong)
Name[da]=Kinesisk (Hong Kong)
Name[de]=Chinesisch (Hongkong)
Name[el]=Κινέζικα (Χονγκ Κονγκ)
Name[en_GB]=Chinese (Hong Kong)
Name[eo]=Ĉina (Honkongo)
Name[es]=Chino (Hong Kong)
Name[et]=Hiina (Hong Kong)
Name[eu]=Txinera (Hong Kong)
Name[fa]=چینی)هنگ کنگ(
Name[fi]=Kiina (Hongkong)
Name[fr]=Chinois (Hong Kong)
Name[fy]=Sjineesk (Hong Kong)
Name[ga]=Sínis (Hong Cong)
Name[gl]=Chinés (Hong Kong)
Name[gu]=ચાઇનિઝ (હોંગ કોંગ)
Name[he]=סינית (הונג קונג)
Name[hi]=चीनी (हांग कांग)
Name[hne]=चीनी (हांग कांग)
Name[hr]=Kineski (Hong Kong)
Name[hsb]=Chinsce (Hong Kong)
Name[hu]=Kínai (hongkongi)
Name[ia]=Chinese (Hong Kong)
Name[id]=China (Hong Kong)
Name[is]=Kínverska (Hong Kong)
Name[it]=Cinese (Hong Kong)
Name[ja]=中国語 (香港)
Name[kk]=Қытайша (Сянган)
Name[km]=ចិន (ហុងកុង)
Name[kn]=ಹಾಂಗ್ ಕಾಂಗ್ ಚೀನೀ
Name[ko]=중국어 (홍콩)
Name[ku]=Çînî (Hong Kong)
Name[lb]=Chinesesch (Hong Kong)
Name[lt]=Kiniečių (Honkongo)
Name[lv]=Ķīniešu (Honkongas)
Name[mai]=चीनी (हाँगकाँग)
Name[mk]=Кинески (Хонг Конг)
Name[ml]=ചൈനീസ് (ഹോങ് കോങ്)
Name[mr]=चायनिज (हॉन्ग कॉन्ग)
Name[ms]=China (Hong Kong)
Name[nb]=Kinesisk (Hong kong)
Name[nds]=Chineesch (Hong Kong)
Name[ne]=चिनियाँ (हङकङ)
Name[nl]=Chinees (Hong Kong)
Name[nn]=Kinesisk (Hong Kong)
Name[oc]=Chinés (Hong Kong)
Name[or]=Chinese (Hong Kong)
Name[pa]=ਚੀਨੀ (ਹਾਂਗਕਾਂਗ)
Name[pl]=Chiński (Hong Kong)
Name[ps]=چينايي (هانګ کانګ)
Name[pt]=Chinês (Hong Kong)
Name[pt_BR]=Chinês (Hong Kong)
Name[ro]=Chineză (Hong Kong)
Name[ru]=Китайский (Гонконг)
Name[se]=Kiinnágiella (Hong Kong)
Name[si]=චීන (හොං කොං)
Name[sk]=Čínština (Hongkong)
Name[sl]=kitajsko (Hong Kong)
Name[sq]=Kineze (Hong Kong)
Name[sr]=кинески (хонконшки)
Name[sr@ijekavian]=кинески (хонконшки)
Name[sr@ijekavianlatin]=kineski (honkonški)
Name[sr@latin]=kineski (honkonški)
Name[sv]=Kinesiska (Hong Kong)
Name[ta]=சீனம் (ஹாங் காங்)
Name[te]=చైనీస్ (హాంగ్ కాంగ్)
Name[tg]=Хитоии Гон Конгӣ
Name[th]=ภาษาจีน (ฮ่องกง)
Name[tr]=Çince (Hong Kong)
Name[tt]=Кытай (Гонконг)
Name[ug]=خەنزۇچە (شياڭگاڭ)
Name[uk]=Китайська (Гонконг)
Name[uz]=Xitoycha (Gongkong)
Name[uz@cyrillic]=Хитойча (Гонгконг)
Name[vi]=Tiếng Trung (Hồng Kông)
Name[wa]=Chinwès (Hong Kong)
Name[x-test]=xxChinese (Hong Kong)xx
Name[zh_CN]=繁体中文(香港)
Name[zh_TW]=正體中文(香港)
[zh_TW]
Name=Chinese Traditional
Name[af]=Sjinees tradisioneel
Name[ar]=صينية تقليدية
Name[as]=চীনা পাৰম্পৰিক
Name[ast]=Chinu tradicional
Name[be]=Кітайская традыцыйная
Name[be@latin]=Kitajskaja tradycyjnaja
Name[bg]=Китайски традиционен
Name[bn]=পারম্পরিক চৈনিক
Name[bn_IN]=চিনা(পারম্পরিক)
Name[br]=Sinaeg da gustum
Name[bs]=Kineski(tradicionalni)
Name[ca]=Xinès tradicional
Name[ca@valencia]=Xinés tradicional
Name[cs]=Čínský (tradiční)
Name[csb]=Chińsczi Tradicëjny
Name[cy]=Tsieineeg Traddodiadol
Name[da]=Kinesisk traditionel
Name[de]=Chinesisch (Langzeichen)
Name[el]=Κινέζικα παραδοσιακά
Name[en_GB]=Chinese Traditional
Name[eo]=Ĉina, tradicia
Name[es]=Chino tradicional
Name[et]=Hiina (traditsiooniline)
Name[eu]=Txinatar tradizionala
Name[fa]=چینی سنتی
Name[fi]=Perinteinen kiina
Name[fr]=Chinois traditionnel
Name[fy]=Sjineesk (tradisjoniel)
Name[ga]=Sínis Thraidisiúnta
Name[gl]=Chinés tradicional
Name[gu]=પરંપરાગત ચાઇનિઝ
Name[he]=סינית מסורתית
Name[hi]=चीनी परम्परिक
Name[hne]=चीनी परम्परिक
Name[hr]=Kineski tradicionalan
Name[hsb]=Chinsce (tradicionalnje)
Name[hu]=Kínai (hagyományos)
Name[ia]=Chinese Traditional
Name[id]=China Tradisional
Name[is]=Hefðbundin kínverska
Name[it]=Cinese tradizionale
Name[ja]=中国語 繁体字
Name[kk]=Дәстүрлі қытайша
Name[km]=ចិន​បុរាណ
Name[kn]=ಸಾಂಪ್ರದಾಯಿಕ ಚಿನೀ
Name[ko]=중국어 번체
Name[ku]=Çîniya Kevneşopî
Name[lb]=Traditionellt Chinesesch
Name[lt]=Kinų tradicinė
Name[lv]=Ķīniešu tradicionālā
Name[mai]=चीनी पारम्परिक
Name[mk]=Кинески традиционален
Name[ml]=പുരാതന ചൈനീസ്
Name[mr]=पारंपारिक चिनी
Name[ms]=Chinese Traditional
Name[nb]=Tradisjonell kinesisk
Name[nds]=Traditschonell Chineesch
Name[ne]=चिनियाँ परम्परागत
Name[nl]=Chinees (Traditioneel)
Name[nn]=Kinesisk (tradisjonell)
Name[oc]=Chinés tradicional
Name[or]=Chinese Traditional
Name[pa]=ਚੀਨੀ ਮੂਲ
Name[pl]=Chiński tradycyjny
Name[ps]=چينايي هڅوبي
Name[pt]=Chinês Tradicional
Name[pt_BR]=Chinês tradicional
Name[ro]=Chineză tradițională
Name[ru]=Китайский (Тайвань)
Name[se]=Árbevirolaš kiinnágiella
Name[si]=චීන සාම්ප්‍රදායික
Name[sk]=Čínština (tradičná)
Name[sl]=tradicionalno kitajsko
Name[sq]=Kineze Tradicionale
Name[sr]=кинески, традиционални
Name[sr@ijekavian]=кинески, традиционални
Name[sr@ijekavianlatin]=kineski, tradicionalni
Name[sr@latin]=kineski, tradicionalni
Name[sv]=Traditionell kinesiska
Name[ta]=பழஞ்சீனம்
Name[te]=సాంప్రదాయక చైనీస్
Name[tg]=Хитоии Анъанавӣ
Name[th]=ภาษาจีนดั้งเดิม
Name[tr]=Geleneksel Çince
Name[tt]=Кытай (гадәти)
Name[ug]=مۇرەككەپ خەنزۇچە
Name[uk]=Китайська (традиційна)
Name[uz]=Anʼanaviy Xitoycha
Name[uz@cyrillic]=Анъанавий Хитойча
Name[vi]=Trung Quốc phồn thể
Name[wa]=Chinwès tradicionel
Name[x-test]=xxChinese Traditionalxx
Name[zh_CN]=繁体中文
Name[zh_HK]=繁體中文
Name[zh_TW]=正體中文
[zu]
Name=Zulu
Name[af]=Zoeloe
Name[ar]=زولو
Name[as]=জুলু
Name[ast]=Zulú
Name[be]=Зулуская
Name[be@latin]=Zuluskaja
Name[bg]=Зулу
Name[bn]=জুলু
Name[bn_IN]=জুলু
Name[br]=Zouloued
Name[bs]=Zulu
Name[ca]=Zulu
Name[ca@valencia]=Zulu
Name[cs]=Zulu
Name[csb]=Zulusczi
Name[cy]=Zulu
Name[da]=Zulu
Name[de]=Zulu
Name[el]=Zulu
Name[en_GB]=Zulu
Name[eo]=Zulua
Name[es]=Zulú
Name[et]=Suulu
Name[eu]=Zuluera
Name[fa]=ناتالی
Name[fi]=Zulu
Name[fr]=Zoulou
Name[fy]=Zulu
Name[ga]=Súlúis
Name[gl]=Zulú
Name[gu]=ઝુલુ
Name[he]=זולו
Name[hi]=ज़ुलु
Name[hne]=जुलु
Name[hr]=Zulu
Name[hsb]=Zulu
Name[hu]=Zulu
Name[ia]=Zulu
Name[id]=Zulu
Name[is]=Zúlú
Name[it]=Zulu
Name[ja]=ズールー語
Name[kk]=Зулу
Name[km]=ហ្ស៊ូលូ
Name[kn]=ಜುಲು
Name[ko]=줄루어
Name[ku]=Zûlû
Name[lb]=Zulu
Name[lt]=Zulu
Name[lv]=Zulu
Name[mai]=जूलु
Name[mk]=Зулу
Name[ml]=സുലു
Name[mr]=ज़ुलु
Name[ms]=Zulu
Name[nb]=Zulu
Name[nds]=Zulu
Name[ne]=जुलु
Name[nl]=Zulu
Name[nn]=Zulu
Name[oc]=Zolo
Name[or]=ଜୁଲୁ
Name[pa]=ਜ਼ੂਲੂ
Name[pl]=Zuluski
Name[ps]=زولو
Name[pt]=Zulu
Name[pt_BR]=Zulu
Name[ro]=Zulu
Name[ru]=Зулусский
Name[se]=Zulugiella
Name[si]=සූලු
Name[sk]=Zuluština
Name[sl]=zulu
Name[sq]=Zulu
Name[sr]=зулу
Name[sr@ijekavian]=зулу
Name[sr@ijekavianlatin]=zulu
Name[sr@latin]=zulu
Name[sv]=Zulu
Name[ta]=சுலு
Name[te]=జూలు
Name[tg]=Зулу
Name[th]=ภาษาซูลู
Name[tr]=Zulu
Name[tt]=Зулусский
Name[ug]=زۇلۇچە
Name[uk]=Зулуська
Name[uz]=Zulucha
Name[uz@cyrillic]=Зулуча
Name[vi]=Zulu
Name[wa]=Zoulou
Name[xh]=Isizulu
Name[x-test]=xxZuluxx
Name[zh_CN]=祖鲁语
Name[zh_HK]=袓魯語
Name[zh_TW]=袓魯語
diff --git a/kdecore/localization/entry.desktop b/kdecore/localization/entry.desktop
index dfd27c53c0..9737291e73 100644
--- a/kdecore/localization/entry.desktop
+++ b/kdecore/localization/entry.desktop
@@ -1,61 +1,62 @@
[KCM Locale]
Name=US English
Name[ar]=إنجليزية (الولايات المتحدة)
Name[bg]=Американски английски
Name[bs]=Američki engleski
Name[ca]=Anglès US
Name[ca@valencia]=Anglés US
Name[cs]=Anglický (US)
Name[da]=Engelsk (US)
Name[de]=US-Englisch
Name[el]=Αγγλική ΗΠΑ
Name[en_GB]=US English
Name[es]=Inglés de EE. UU.
Name[et]=USA inglise
Name[eu]=AEBetako Ingelesa
Name[fa]=انگلیسی امریکایی
Name[fi]=Amerikanenglanti
Name[fr]=Anglais américain
Name[ga]=Béarla SAM
Name[gl]=Inglés americano
Name[he]=אנגלית ארה"ב
Name[hr]=Američki engleski
Name[hu]=Angol (amerikai)
Name[ia]=Anglese de SU
+Name[id]=Inggris AS
Name[is]=Bandarísk enska (en-us
Name[it]=Inglese americano
Name[ja]=米語
Name[kk]=Ағылшынша (АҚШ)
Name[km]=អង់គ្លេស សហរដ្ឋអាមេរិក
Name[ko]=미국 영어
Name[lt]=JAV anglų
Name[lv]=ASV angļu
Name[nb]=Engelsk (USA)
Name[nds]=Engelsch (US)
Name[nl]=VS Engels
Name[pa]=ਅਮਰੀਕੀ ਅੰਗਰੇਜ਼ੀ
Name[pl]=Angielski amerykański
Name[pt]=Inglês (EUA)
Name[pt_BR]=Inglês dos EUA
Name[ro]=Engleză SUA
Name[ru]=Английский (США)
Name[se]=Amerihkálaš eŋgelasgiella
Name[si]=එ.ජ. ඉංග්‍රීසි
Name[sk]=Americká angličtina
Name[sq]=Anglisht Amerikane
Name[sr]=амерички енглески
Name[sr@ijekavian]=амерички енглески
Name[sr@ijekavianlatin]=američki engleski
Name[sr@latin]=američki engleski
Name[sv]=Amerikansk engelska
Name[tg]=Англисӣ
Name[th]=ภาษาอังกฤษ อเมริกัน
Name[tr]=Amerikan İngilizcesi
Name[tt]=Инглизчә
Name[ug]=ئامېرىكا ئىنگلىزچە
Name[uk]=Англійська (США)
Name[vi]=Tiếng Anh (Mỹ)
Name[wa]=Inglès des Estats-Unis
Name[x-test]=xxUS Englishxx
Name[zh_CN]=美国英语
Name[zh_TW]=美式英語
diff --git a/kdecore/tests/CMakeLists.txt b/kdecore/tests/CMakeLists.txt
index 9173746a07..f19e563d5d 100644
--- a/kdecore/tests/CMakeLists.txt
+++ b/kdecore/tests/CMakeLists.txt
@@ -1,176 +1,177 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
include_directories( ${KDE4_KDECORE_INCLUDES} )
remove_definitions(-DQT_NO_CAST_FROM_ASCII)
MACRO(KDECORE_UNIT_TESTS)
FOREACH(_testname ${ARGN})
kde4_add_unit_test(${_testname} TESTNAME "kdecore-${_testname}" NOGUI ${_testname}.cpp)
target_link_libraries(${_testname} ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} ${QT_QTNETWORK_LIBRARY})
if(WINCE)
target_link_libraries(${_testname} ${WCECOMPAT_LIBRARIES})
endif(WINCE)
ENDFOREACH(_testname)
ENDMACRO(KDECORE_UNIT_TESTS)
MACRO(KDECORE_EXECUTABLE_TESTS)
FOREACH(_testname ${ARGN})
kde4_add_executable(${_testname} NOGUI TEST ${_testname}.cpp)
target_link_libraries(${_testname} ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY})
if(WINCE)
target_link_libraries(${_testname} ${WCECOMPAT_LIBRARIES})
endif(WINCE)
ENDFOREACH(_testname)
ENDMACRO(KDECORE_EXECUTABLE_TESTS)
########### next target ###############
KDECORE_UNIT_TESTS(
karchivetest
kdirwatch_unittest
klocaletimeformattest
klocalizedstringtest
kmountpointtest
kstandarddirstest
kaboutdatatest
kurltest
kstringhandlertest
cplusplustest
ksortablelisttest
kcharsetstest
kcalendartest
kmacroexpandertest
kshelltest
kasciitest
ktimezonestest
kentrymaptest
kconfigtest
kurlmimetest
klockfiletest
ktempdirtest
ksharedptrtest
+ kshareddatacachetest
kdatetimetest
ksavefiletest
kautosavefiletest
kdesktopfiletest
ktemporaryfiletest
kautostarttest
kjobtest
ksycocadicttest
kservicetest
kglobalstatictest
kglobaltest
globalcleanuptest
kprocesstest
kconfigafterkglobaltest1
kconfigafterkglobaltest2
ktcpsockettest
ksycocathreadtest
kdebug_unittest
kencodingdetectortest
qcoreapptest
kdebug_qcoreapptest
kmimetype_nomimetypes
kmd5benchmark
)
if(NOT KDE_NO_DEPRECATED)
KDECORE_UNIT_TESTS(
klibloadertest
)
endif(NOT KDE_NO_DEPRECATED)
if(UNIX)
KDECORE_UNIT_TESTS(
klocalsockettest
klocalsocketservertest
)
endif(UNIX)
KDECORE_EXECUTABLE_TESTS(
kdirwatchtest
krandomsequencetest
ktartest
kziptest
kdebugtest
kcmdlineargstest
kmemtest
dbuscalltest
kmdcodectest
startserviceby
klockfile_testlock # helper for klockfiletest
)
########### klocaletest ###############
# compile into the test since it's not exported
set(klocaletest_SRCS klocaletest.cpp ../date/kdayperiod.cpp)
kde4_add_unit_test(klocaletest ${klocaletest_SRCS})
target_link_libraries(klocaletest ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} )
########### kdatetimeformattertest ###############
# compile KDateTimeFormatter and KDayPeriod into the test since it's not exported
set(kdatetimeformattertest_SRCS kdatetimeformattertest.cpp ../date/kdatetimeformatter.cpp ../date/kdayperiod.cpp)
kde4_add_unit_test(kdatetimeformattertest ${kdatetimeformattertest_SRCS})
target_link_libraries(kdatetimeformattertest ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} )
########### kdirwatchtest_gui ###############
kde4_add_executable(kdirwatchtest_gui TEST kdirwatchtest_gui.cpp)
target_link_libraries(kdirwatchtest_gui ${KDE4_KDECORE_LIBS} ${QT_QTGUI_LIBRARY} ${QT_QTTEST_LIBRARY})
########### klimitediodevicetest ###############
kde4_add_unit_test(klimitediodevicetest TESTNAME kdecore-klimitediodevicetest klimitediodevicetest.cpp ../io/klimitediodevice.cpp)
target_link_libraries(klimitediodevicetest ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY})
########### kmimetypetest ###############
# compile kmimemagicrule.cpp into the test since it's not exported and we call match().
set(kmimetypetest_SRCS kmimetypetest.cpp ../services/kmimemagicrule.cpp)
kde4_add_unit_test(kmimetypetest TESTNAME kdecore-kmimetypetest ${kmimetypetest_SRCS})
target_link_libraries(kmimetypetest ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} )
########### kmimeglobsfileparsertest ###############
# compile kmimeglobsfileparser.cpp into the test since it's not exported
set(kmimeglobsfileparsertest_SRCS kmimeglobsfileparsertest.cpp ../services/kmimeglobsfileparser.cpp)
kde4_add_unit_test(kmimeglobsfileparsertest TESTNAME kdecore-kmimeglobsfileparsertest ${kmimeglobsfileparsertest_SRCS})
target_link_libraries(kmimeglobsfileparsertest ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} )
########### kfiltertest ###############
# compile httpfilter.cpp into the test since it's not part of kdelibs
# (only par of kio_http and kmultipart)
set(kfiltertest_SRCS kfiltertest.cpp ../../kio/httpfilter/httpfilter.cc)
include_directories( ${CMAKE_SOURCE_DIR}/kio/httpfilter )
kde4_add_unit_test(kfiltertest TESTNAME kdecore-kfiltertest ${kfiltertest_SRCS})
target_link_libraries(kfiltertest ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY} )
target_link_libraries(kfiltertest ${ZLIB_LIBRARIES})
########### module for klibloadertest ###############
if(NOT KDE_NO_DEPRECATED)
set(klibloadertestmodule_PART_SRCS klibloadertest_module.cpp )
kde4_add_plugin(klibloadertestmodule ${klibloadertestmodule_PART_SRCS})
target_link_libraries(klibloadertestmodule ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY})
set_target_properties(klibloadertestmodule PROPERTIES SKIP_BUILD_RPATH FALSE BUILD_WITH_INSTALL_RPATH FALSE)
endif(NOT KDE_NO_DEPRECATED)
########### module for klibloadertest4 ###############
if (NOT WIN32) # TODO: reenable for win32
set(klibloadertestmodule4_PART_SRCS klibloadertest4_module.cpp )
kde4_add_plugin(klibloadertestmodule4 ${klibloadertestmodule4_PART_SRCS})
target_link_libraries(klibloadertestmodule4 ${KDE4_KDECORE_LIBS} ${QT_QTTEST_LIBRARY})
set_target_properties(klibloadertestmodule4 PROPERTIES SKIP_BUILD_RPATH FALSE BUILD_WITH_INSTALL_RPATH FALSE)
endif (NOT WIN32)
diff --git a/kdecore/tests/global_header_test.tar.bz2 b/kdecore/tests/global_header_test.tar.bz2
new file mode 100644
index 0000000000..40c523a4d8
Binary files /dev/null and b/kdecore/tests/global_header_test.tar.bz2 differ
diff --git a/kdecore/tests/karchivetest.cpp b/kdecore/tests/karchivetest.cpp
index 6eab645275..1eb1dd2563 100644
--- a/kdecore/tests/karchivetest.cpp
+++ b/kdecore/tests/karchivetest.cpp
@@ -1,832 +1,899 @@
/* This file is part of the KDE project
Copyright (C) 2006, 2010 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include <config-compression.h>
#include "karchivetest.h"
#include <kmimetype.h>
#include "karchivetest.moc"
#include <ktar.h>
#include <kzip.h>
#include <qtest_kde.h>
#include <QtCore/QFileInfo>
#include <kdebug.h>
#include <kfilterdev.h>
#include <ktempdir.h>
#ifndef Q_OS_WIN
#include <unistd.h> // symlink
#include <errno.h>
#endif
QTEST_KDEMAIN_CORE( KArchiveTest )
static const int SIZE1 = 100;
/**
* Writes test fileset specified archive
* @param archive archive
*/
static void writeTestFilesToArchive( KArchive* archive )
{
QVERIFY( archive->writeFile( "empty", "weis", "users", "", 0 ) );
QVERIFY( archive->writeFile( "test1", "weis", "users", "Hallo", 5, 0100440 ) );
// Now let's try with the prepareWriting/writeData/finishWriting API
QVERIFY( archive->prepareWriting( "test2", "weis", "users", 8 ) );
QVERIFY( archive->writeData( "Hallo ", 6 ) );
QVERIFY( archive->writeData( "Du", 2 ) );
QVERIFY( archive->finishWriting( 8 ) );
// Add local file
QFile localFile( "test3" );
QVERIFY( localFile.open( QIODevice::WriteOnly ) );
QVERIFY( localFile.write( "Noch so einer", 13 ) == 13 );
localFile.close();
QVERIFY( archive->addLocalFile( "test3", "z/test3" ) );
// writeFile API
QVERIFY( archive->writeFile( "my/dir/test3", "dfaure", "hackers", "I do not speak German\nDavid.", 29 ) );
// Now a medium file : 100 null bytes
char medium[ SIZE1 ];
memset( medium, 0, SIZE1 );
QVERIFY( archive->writeFile( "mediumfile", "user", "group", medium, SIZE1 ) );
// Another one, with an absolute path
QVERIFY( archive->writeFile( "/dir/subdir/mediumfile2", "user", "group", medium, SIZE1 ) );
// Now a huge file : 20000 null bytes
int n = 20000;
char * huge = new char[ n ];
memset( huge, 0, n );
QVERIFY( archive->writeFile( "hugefile", "user", "group", huge, n ) );
delete [] huge;
// Now an empty directory
QVERIFY( archive->writeDir( "aaaemptydir", "user", "group" ) );
#ifndef Q_OS_WIN
// Add local symlink
QVERIFY( archive->addLocalFile( "test3_symlink", "z/test3_symlink") );
#endif
}
enum { WithUserGroup = 1 }; // ListingFlags
static QStringList recursiveListEntries( const KArchiveDirectory * dir, const QString & path, int listingFlags )
{
QStringList ret;
QStringList l = dir->entries();
l.sort();
Q_FOREACH(const QString& it, l) {
const KArchiveEntry* entry = dir->entry(it);
QString descr;
descr += QString("mode=") + QString::number( entry->permissions(), 8 ) + ' ';
if ( listingFlags & WithUserGroup )
{
descr += QString("user=") + entry->user() + ' ';
descr += QString("group=") + entry->group() + ' ';
}
descr += QString("path=") + path+(it) + ' ';
descr += QString("type=") + ( entry->isDirectory() ? "dir" : "file" );
if ( entry->isFile() )
descr += QString(" size=") + QString::number( static_cast<const KArchiveFile *>(entry)->size() );
if (!entry->symLinkTarget().isEmpty())
descr += QString(" symlink=") + entry->symLinkTarget();
// TODO add date and time
//kDebug() << descr;
ret.append( descr );
if (entry->isDirectory())
ret += recursiveListEntries( (KArchiveDirectory *)entry, path+it+'/', listingFlags );
}
return ret;
}
/**
* Verifies contents of specified archive against test fileset
* @param archive archive
*/
static void testFileData( KArchive* archive )
{
const KArchiveDirectory* dir = archive->directory();
const KArchiveEntry* e = dir->entry( "z/test3" );
QVERIFY( e );
QVERIFY( e->isFile() );
const KArchiveFile* f = static_cast<const KArchiveFile*>( e );
QByteArray arr( f->data() );
QCOMPARE( arr.size(), 13 );
QCOMPARE( arr, QByteArray( "Noch so einer" ) );
// Now test using createDevice()
QIODevice *dev = f->createDevice();
QByteArray contents = dev->readAll();
QCOMPARE( contents, arr );
delete dev;
dev = f->createDevice();
contents = dev->read(5); // test reading in two chunks
QCOMPARE(contents.size(), 5);
contents += dev->read(50);
QCOMPARE(contents.size(), 13);
QCOMPARE( QString::fromLatin1(contents), QString::fromLatin1(arr) );
delete dev;
e = dir->entry( "mediumfile" );
QVERIFY( e && e->isFile() );
f = (KArchiveFile*)e;
QCOMPARE( f->data().size(), SIZE1 );
e = dir->entry( "hugefile" );
QVERIFY( e && e->isFile() );
f = (KArchiveFile*)e;
QCOMPARE( f->data().size(), 20000 );
e = dir->entry( "aaaemptydir" );
QVERIFY( e && e->isDirectory() );
e = dir->entry( "my/dir/test3" );
QVERIFY( e && e->isFile() );
f = (KArchiveFile*)e;
dev = f->createDevice();
QByteArray firstLine = dev->readLine();
QCOMPARE(QString::fromLatin1(firstLine), QString::fromLatin1("I do not speak German\n"));
QByteArray secondLine = dev->read(100);
QCOMPARE(QString::fromLatin1(secondLine), QString::fromLatin1("David."));
delete dev;
#ifndef Q_OS_WIN
e = dir->entry( "z/test3_symlink" );
QVERIFY(e);
QVERIFY(e->isFile());
QCOMPARE(e->symLinkTarget(), QString("test3"));
#endif
// Test "./" prefix for KOffice (xlink:href="./ObjectReplacements/Object 1")
e = dir->entry( "./hugefile" );
QVERIFY( e && e->isFile() );
e = dir->entry( "./my/dir/test3" );
QVERIFY( e && e->isFile() );
// Test directory entries
e = dir->entry( "my" );
QVERIFY(e && e->isDirectory());
e = dir->entry( "my/" );
QVERIFY(e && e->isDirectory());
e = dir->entry( "./my/" );
QVERIFY(e && e->isDirectory());
}
static void testReadWrite( KArchive* archive )
{
QVERIFY(archive->writeFile("newfile", "dfaure", "users", "New File", 8, 0100440));
}
static void testCopyTo( KArchive* archive )
{
const KArchiveDirectory* dir = archive->directory();
KTempDir tmpDir;
const QString dirName = tmpDir.name();
dir->copyTo( dirName );
QVERIFY(QFile::exists(dirName+"dir"));
QVERIFY(QFileInfo(dirName+"dir").isDir());
QFileInfo fileInfo1(dirName+"dir/subdir/mediumfile2");
QVERIFY(fileInfo1.exists());
QVERIFY(fileInfo1.isFile());
QCOMPARE(fileInfo1.size(), Q_INT64_C(100));
QFileInfo fileInfo2(dirName+"hugefile");
QVERIFY(fileInfo2.exists());
QVERIFY(fileInfo2.isFile());
QCOMPARE(fileInfo2.size(), Q_INT64_C(20000));
QFileInfo fileInfo3(dirName+"mediumfile");
QVERIFY(fileInfo3.exists());
QVERIFY(fileInfo3.isFile());
QCOMPARE(fileInfo3.size(), Q_INT64_C(100));
QFileInfo fileInfo4(dirName+"my/dir/test3");
QVERIFY(fileInfo4.exists());
QVERIFY(fileInfo4.isFile());
QCOMPARE(fileInfo4.size(), Q_INT64_C(29));
#ifndef Q_OS_WIN
const QString fileName = dirName+"z/test3_symlink";
const QFileInfo fileInfo5(fileName);
QVERIFY(fileInfo5.exists());
QVERIFY(fileInfo5.isFile());
// Do not use fileInfo.readLink() for unix symlinks
// It returns the -full- path to the target, while we want the target string "as is".
QString symLinkTarget;
const QByteArray encodedFileName = QFile::encodeName(fileName);
QByteArray s;
#if defined(PATH_MAX)
s.resize(PATH_MAX+1);
#else
int path_max = pathconf(encodedFileName.data(), _PC_PATH_MAX);
if (path_max <= 0) {
path_max = 4096;
}
s.resize(path_max);
#endif
int len = readlink(encodedFileName.data(), s.data(), s.size() - 1);
if ( len >= 0 ) {
s[len] = '\0';
symLinkTarget = QFile::decodeName(s);
}
QCOMPARE(symLinkTarget, QString("test3"));
#endif
}
/**
* Prepares dataset for archive filter tests
*/
void KArchiveTest::setupData()
{
QTest::addColumn<QString>("fileName");
QTest::addColumn<QString>("mimeType");
QTest::newRow(".tar.gz") << "karchivetest.tar.gz" << "application/x-gzip";
#if HAVE_BZIP2_SUPPORT
QTest::newRow(".tar.bz2") << "karchivetest.tar.bz2" << "application/x-bzip";
#endif
#if HAVE_XZ_SUPPORT
QTest::newRow(".tar.lzma") << "karchivetest.tar.lzma" << "application/x-lzma";
QTest::newRow(".tar.xz") << "karchivetest.tar.xz" << "application/x-xz";
#endif
}
/**
* @see QTest::initTestCase()
*/
void KArchiveTest::initTestCase()
{
#ifndef Q_OS_WIN
// Prepare local symlink
QFile::remove("test3_symlink");
if (::symlink("test3", "test3_symlink") != 0) {
qDebug() << errno;
QVERIFY(false);
}
#endif
// For better benchmarks: initialize KMimeTypeFactory magic here
KMimeType::findByContent(QByteArray("hello"));
}
void KArchiveTest::testCreateTar_data()
{
QTest::addColumn<QString>("fileName");
QTest::newRow(".tar") << "karchivetest.tar";
}
/**
* @dataProvider testCreateTar_data
*/
void KArchiveTest::testCreateTar()
{
QFETCH(QString, fileName);
// With tempfile: 0.7-0.8 ms, 994236 instr. loads
// Without tempfile: 0.81 ms, 992541 instr. loads
// Note: use ./karchivetest 2>&1 | grep ms
// to avoid being slowed down by the kDebugs.
QBENCHMARK {
KTar tar(fileName);
QVERIFY(tar.open(QIODevice::WriteOnly));
writeTestFilesToArchive(&tar);
QVERIFY(tar.close());
QFileInfo fileInfo(QFile::encodeName(fileName));
QVERIFY(fileInfo.exists());
// We can't check for an exact size because of the addLocalFile, whose data is system-dependent
QVERIFY(fileInfo.size() > 450);
}
// NOTE The only .tar test, cleanup here
QFile::remove(fileName);
}
/**
* @dataProvider setupData
*/
void KArchiveTest::testCreateTarXXX()
{
QFETCH(QString, fileName);
// With tempfile: 1.3-1.7 ms, 2555089 instr. loads
// Without tempfile: 0.87 ms, 987915 instr. loads
QBENCHMARK {
KTar tar(fileName);
QVERIFY(tar.open(QIODevice::WriteOnly));
writeTestFilesToArchive(&tar);
QVERIFY(tar.close());
QFileInfo fileInfo(QFile::encodeName(fileName));
QVERIFY(fileInfo.exists());
// We can't check for an exact size because of the addLocalFile, whose data is system-dependent
QVERIFY(fileInfo.size() > 350);
}
}
/**
* @dataProvider setupData
*/
void KArchiveTest::testReadTar() // testCreateTarGz must have been run first.
{
QFETCH( QString, fileName );
// 1.6-1.7 ms per interaction, 2908428 instruction loads
// After the "no tempfile when writing fix" this went down
// to 0.9-1.0 ms, 1689059 instruction loads.
// I guess it finds the data in the kernel cache now that no KTempFile is
// used when writing.
QBENCHMARK {
KTar tar( fileName );
QVERIFY( tar.open( QIODevice::ReadOnly ) );
const KArchiveDirectory* dir = tar.directory();
QVERIFY( dir != 0 );
const QStringList listing = recursiveListEntries( dir, "", WithUserGroup );
QFileInfo localFileData("test3");
#ifndef Q_WS_WIN
QCOMPARE( listing.count(), 15 );
#else
QCOMPARE( listing.count(), 14 );
#endif
QCOMPARE( listing[ 0], QString("mode=40755 user=user group=group path=aaaemptydir type=dir") );
QCOMPARE( listing[ 1], QString("mode=40777 user=%1 group=%2 path=dir type=dir").arg(localFileData.owner()).arg(localFileData.group()) );
QCOMPARE( listing[ 2], QString("mode=40777 user=%1 group=%2 path=dir/subdir type=dir").arg(localFileData.owner()).arg(localFileData.group()) );
QCOMPARE( listing[ 3], QString("mode=100644 user=user group=group path=dir/subdir/mediumfile2 type=file size=100") );
QCOMPARE( listing[ 4], QString("mode=100644 user=weis group=users path=empty type=file size=0") );
QCOMPARE( listing[ 5], QString("mode=100644 user=user group=group path=hugefile type=file size=20000") );
QCOMPARE( listing[ 6], QString("mode=100644 user=user group=group path=mediumfile type=file size=100") );
QCOMPARE( listing[ 7], QString("mode=40777 user=%1 group=%2 path=my type=dir").arg(localFileData.owner()).arg(localFileData.group()) );
QCOMPARE( listing[ 8], QString("mode=40777 user=%1 group=%2 path=my/dir type=dir").arg(localFileData.owner()).arg(localFileData.group()) );
QCOMPARE( listing[ 9], QString("mode=100644 user=dfaure group=hackers path=my/dir/test3 type=file size=29") );
QCOMPARE( listing[10], QString("mode=100440 user=weis group=users path=test1 type=file size=5") );
QCOMPARE( listing[11], QString("mode=100644 user=weis group=users path=test2 type=file size=8") );
QCOMPARE( listing[12], QString("mode=40777 user=%1 group=%2 path=z type=dir").arg(localFileData.owner()).arg(localFileData.group()) );
// This one was added with addLocalFile, so ignore mode/user/group.
QString str = listing[13];
str.replace(QRegExp("mode.*path"), "path" );
QCOMPARE( str, QString("path=z/test3 type=file size=13") );
#ifndef Q_OS_WIN
str = listing[14];
str.replace(QRegExp("mode.*path"), "path" );
QCOMPARE( str, QString("path=z/test3_symlink type=file size=0 symlink=test3") );
#endif
QVERIFY( tar.close() );
}
}
/**
* This tests the decompression using kfilterdev, basically.
* To debug KTarPrivate::fillTempFile().
*
* @dataProvider setupData
*/
void KArchiveTest::testUncompress()
{
QFETCH(QString, fileName);
QFETCH(QString, mimeType);
// testCreateTar must have been run first.
QVERIFY(QFile::exists(fileName));
QIODevice *filterDev = KFilterDev::deviceForFile(fileName, mimeType, true);
QVERIFY(filterDev);
QByteArray buffer;
buffer.resize(8*1024);
kDebug() << "buffer.size()=" << buffer.size();
QVERIFY(filterDev->open(QIODevice::ReadOnly));
qint64 totalSize = 0;
qint64 len = -1;
while (!filterDev->atEnd() && len != 0) {
len = filterDev->read(buffer.data(), buffer.size());
QVERIFY(len >= 0);
totalSize += len;
// kDebug() << "read len=" << len << " totalSize=" << totalSize;
}
filterDev->close();
delete filterDev;
// kDebug() << "totalSize=" << totalSize;
QVERIFY(totalSize > 26000); // 27648 here when using gunzip
}
/**
* @dataProvider setupData
*/
void KArchiveTest::testTarFileData()
{
QFETCH(QString, fileName);
// testCreateTar must have been run first.
KTar tar(fileName);
QVERIFY(tar.open(QIODevice::ReadOnly));
testFileData(&tar);
QVERIFY(tar.close());
}
/**
* @dataProvider setupData
*/
void KArchiveTest::testTarCopyTo()
{
QFETCH(QString, fileName);
// testCreateTar must have been run first.
KTar tar(fileName);
QVERIFY(tar.open(QIODevice::ReadOnly));
testCopyTo(&tar);
QVERIFY(tar.close());
}
/**
* @dataProvider setupData
*/
void KArchiveTest::testTarReadWrite()
{
QFETCH(QString, fileName);
// testCreateTar must have been run first.
KTar tar(fileName);
QVERIFY(tar.open(QIODevice::ReadWrite));
testReadWrite(&tar);
testFileData(&tar);
QVERIFY(tar.close());
// Reopen it and check it
{
KTar tar(fileName);
QVERIFY(tar.open(QIODevice::ReadOnly));
testFileData( &tar );
const KArchiveDirectory* dir = tar.directory();
const KArchiveEntry* e = dir->entry("newfile");
QVERIFY(e && e->isFile());
const KArchiveFile* f = (KArchiveFile*)e;
QCOMPARE(f->data().size(), 8);
}
// NOTE This is the last test for this dataset. so cleanup here
QFile::remove(fileName);
}
void KArchiveTest::testTarMaxLength_data()
{
QTest::addColumn<QString>("fileName");
QTest::newRow("maxlength.tar.gz") << "karchivetest-maxlength.tar.gz";
}
/**
* @dataProvider testTarMaxLength_data
*/
void KArchiveTest::testTarMaxLength()
{
QFETCH( QString, fileName );
KTar tar( fileName );
QVERIFY( tar.open( QIODevice::WriteOnly ) );
// Generate long filenames of each possible length bigger than 98...
// Also exceed 512 byte block size limit to see how well the ././@LongLink
// implementation fares
for (int i = 98; i < 514 ; i++ )
{
QString str, num;
str.fill( 'a', i-10 );
num.setNum( i );
num = num.rightJustified( 10, '0' );
tar.writeFile( str+num, "testu", "testg", "hum", 3 );
}
// Result of this test : works perfectly now (failed at 482 formerly and
// before that at 154).
QVERIFY( tar.close() );
QVERIFY( tar.open( QIODevice::ReadOnly ) );
const KArchiveDirectory* dir = tar.directory();
QVERIFY( dir != 0 );
const QStringList listing = recursiveListEntries( dir, "", WithUserGroup );
QCOMPARE( listing[ 0], QString("mode=100644 user=testu group=testg path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000098 type=file size=3") );
QCOMPARE( listing[ 3], QString("mode=100644 user=testu group=testg path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000101 type=file size=3") );
QCOMPARE( listing[ 4], QString("mode=100644 user=testu group=testg path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000102 type=file size=3") );
- // TODO:
- // ################################################# BUG! ###########################
- // There seems to be a bug (which is in kde3 too), we miss 512 and 513.
- // But note that tar tvzf says "skipping next header" (and it skips 511),
- // so the bug is probably during writing...
- QCOMPARE( listing.count(), 414 ); // TODO investigate 514 - 98
+ QCOMPARE( listing.count(), 416 );
QVERIFY( tar.close() );
// NOTE Cleanup here
QFile::remove( fileName );
}
+void KArchiveTest::testTarGlobalHeader()
+{
+ KTar tar( QString::fromLatin1(KDESRCDIR) + QLatin1String( "global_header_test.tar.bz2" ) );
+ QVERIFY( tar.open( QIODevice::ReadOnly ) );
+
+ const KArchiveDirectory* dir = tar.directory();
+ QVERIFY( dir != 0 );
+
+ const QStringList listing = recursiveListEntries( dir, "", WithUserGroup );
+
+ QCOMPARE( listing[ 0], QString("mode=40775 user=root group=root path=Test type=dir") );
+ QCOMPARE( listing[ 1], QString("mode=664 user=root group=root path=Test/test.txt type=file size=0") );
+
+ QCOMPARE( listing.count(), 2 );
+
+ QVERIFY( tar.close() );
+}
+
+
+void KArchiveTest::testTarPrefix()
+{
+ KTar tar( QString::fromLatin1(KDESRCDIR) + QLatin1String( "tar_prefix_test.tar.bz2" ) );
+ QVERIFY( tar.open( QIODevice::ReadOnly ) );
+
+ const KArchiveDirectory* dir = tar.directory();
+ QVERIFY( dir != 0 );
+
+ const QStringList listing = recursiveListEntries( dir, "", WithUserGroup );
+
+ QCOMPARE( listing[ 0], QString("mode=40775 user=root group=root path=Test type=dir") );
+ QCOMPARE( listing[ 1], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7 type=dir") );
+ QCOMPARE( listing[ 2], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples type=dir") );
+ QCOMPARE( listing[ 3], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator type=dir") );
+ QCOMPARE( listing[ 4], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original type=dir") );
+ QCOMPARE( listing[ 5], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java type=dir") );
+ QCOMPARE( listing[ 6], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com type=dir") );
+ QCOMPARE( listing[ 7], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com/trolltech type=dir") );
+ QCOMPARE( listing[ 8], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com/trolltech/examples type=dir") );
+ QCOMPARE( listing[ 9], QString("mode=664 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com/trolltech/examples/GeneratorExample.html type=file size=43086") );
+
+ QCOMPARE( listing.count(), 10 );
+
+ QVERIFY( tar.close() );
+}
+
+void KArchiveTest::testTarDirectoryForgotten()
+{
+ KTar tar( QString::fromLatin1(KDESRCDIR) + QLatin1String( "tar_directory_forgotten.tar.bz2" ) );
+ QVERIFY( tar.open( QIODevice::ReadOnly ) );
+
+ const KArchiveDirectory* dir = tar.directory();
+ QVERIFY( dir != 0 );
+
+ const QStringList listing = recursiveListEntries( dir, "", WithUserGroup );
+
+ QCOMPARE( listing[ 0], QString("mode=40775 user=root group=root path=Test type=dir") );
+ QCOMPARE( listing[ 1], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7 type=dir") );
+ QCOMPARE( listing[ 2], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples type=dir") );
+ QCOMPARE( listing[ 3], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator type=dir") );
+ QCOMPARE( listing[ 4], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original type=dir") );
+ QCOMPARE( listing[ 5], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java type=dir") );
+ QCOMPARE( listing[ 6], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com type=dir") );
+ QCOMPARE( listing[ 7], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com/trolltech type=dir") );
+ QCOMPARE( listing[ 8], QString("mode=40775 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com/trolltech/examples type=dir") );
+ QCOMPARE( listing[ 9], QString("mode=40777 user=nef group=users path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com/trolltech/examples/generator type=dir") );
+ QCOMPARE( listing[ 10], QString("mode=664 user=root group=root path=Test/qt-jambi-qtjambi-4_7/examples/generator/trolltech_original/java/com/trolltech/examples/generator/GeneratorExample.html type=file size=43086") );
+
+ QCOMPARE( listing.count(), 11 );
+
+ QVERIFY( tar.close() );
+}
+
///
static const char s_zipFileName[] = "karchivetest.zip";
static const char s_zipMaxLengthFileName[] = "karchivetest-maxlength.zip";
static const char s_zipLocaleFileName[] = "karchivetest-locale.zip";
static const char s_zipMimeType[] = "application/vnd.oasis.opendocument.text";
void KArchiveTest::testCreateZip()
{
KZip zip( s_zipFileName );
QVERIFY( zip.open( QIODevice::WriteOnly ) );
zip.setExtraField( KZip::NoExtraField );
zip.setCompression( KZip::NoCompression );
QByteArray zipMimeType( s_zipMimeType );
zip.writeFile( "mimetype", "", "", zipMimeType.data(), zipMimeType.size() );
zip.setCompression( KZip::DeflateCompression );
writeTestFilesToArchive( &zip );
QVERIFY( zip.close() );
QFile zipFile( QFile::encodeName( s_zipFileName ) );
QFileInfo fileInfo( zipFile );
QVERIFY( fileInfo.exists() );
QVERIFY( fileInfo.size() > 300 );
// Check that the header with no-compression and no-extrafield worked.
// (This is for the "magic" for koffice documents)
QVERIFY( zipFile.open( QIODevice::ReadOnly ) );
QByteArray arr = zipFile.read( 4 );
QCOMPARE( arr, QByteArray( "PK\003\004" ) );
QVERIFY( zipFile.seek( 30 ) );
arr = zipFile.read( 8 );
QCOMPARE( arr, QByteArray( "mimetype" ) );
arr = zipFile.read( zipMimeType.size() );
QCOMPARE( arr, zipMimeType );
}
void KArchiveTest::testCreateZipError()
{
// Giving a directory name to kzip must give an error case in close(), see #136630.
// Otherwise we just lose data.
KZip zip(QDir::currentPath());
QVERIFY(zip.open(QIODevice::WriteOnly));
writeTestFilesToArchive(&zip);
// try to add something as a file that is no file
QVERIFY( !zip.addLocalFile( QDir::currentPath(), "bogusdir" ) );
QVERIFY(!zip.close());
}
void KArchiveTest::testReadZipError()
{
QFile brokenZip( "broken.zip" );
QVERIFY( brokenZip.open( QIODevice::WriteOnly ) );
// incomplete magic
brokenZip.write( QByteArray( "PK\003" ) );
brokenZip.close();
KZip zip( "broken.zip" );
QVERIFY( !zip.open(QIODevice::ReadOnly) );
QVERIFY( brokenZip.open( QIODevice::WriteOnly | QIODevice::Append ) );
// add rest of magic, but still incomplete header
brokenZip.write( QByteArray( "\004\000\000\000\000" ) );
brokenZip.close();
QVERIFY( !zip.open(QIODevice::ReadOnly) );
QVERIFY( brokenZip.remove() );
}
void KArchiveTest::testReadZip()
{
// testCreateZip must have been run first.
KZip zip( s_zipFileName );
QVERIFY( zip.open( QIODevice::ReadOnly ) );
const KArchiveDirectory* dir = zip.directory();
QVERIFY( dir != 0 );
// ZIP has no support for per-file user/group, so omit them from the listing
const QStringList listing = recursiveListEntries( dir, "", 0 );
#ifndef Q_WS_WIN
QCOMPARE( listing.count(), 16 );
#else
QCOMPARE( listing.count(), 15 );
#endif
QCOMPARE( listing[ 0], QString("mode=40755 path=aaaemptydir type=dir") );
QCOMPARE( listing[ 1], QString("mode=40777 path=dir type=dir") );
QCOMPARE( listing[ 2], QString("mode=40777 path=dir/subdir type=dir") );
QCOMPARE( listing[ 3], QString("mode=100644 path=dir/subdir/mediumfile2 type=file size=100") );
QCOMPARE( listing[ 4], QString("mode=100644 path=empty type=file size=0") );
QCOMPARE( listing[ 5], QString("mode=100644 path=hugefile type=file size=20000") );
QCOMPARE( listing[ 6], QString("mode=100644 path=mediumfile type=file size=100") );
QCOMPARE( listing[ 7], QString("mode=100644 path=mimetype type=file size=%1").arg(strlen(s_zipMimeType)) );
QCOMPARE( listing[ 8], QString("mode=40777 path=my type=dir") );
QCOMPARE( listing[ 9], QString("mode=40777 path=my/dir type=dir") );
QCOMPARE( listing[10], QString("mode=100644 path=my/dir/test3 type=file size=29") );
QCOMPARE( listing[11], QString("mode=100440 path=test1 type=file size=5") );
QCOMPARE( listing[12], QString("mode=100644 path=test2 type=file size=8") );
QCOMPARE( listing[13], QString("mode=40777 path=z type=dir") );
// This one was added with addLocalFile, so ignore mode
QString str = listing[14];
str.replace(QRegExp("mode.*path"), "path" );
QCOMPARE( str, QString("path=z/test3 type=file size=13") );
#ifndef Q_OS_WIN
str = listing[15];
str.replace(QRegExp("mode.*path"), "path" );
QCOMPARE( str, QString("path=z/test3_symlink type=file size=5 symlink=test3") );
#endif
QVERIFY( zip.close() );
}
void KArchiveTest::testZipFileData()
{
// testCreateZip must have been run first.
KZip zip(s_zipFileName);
QVERIFY(zip.open( QIODevice::ReadOnly));
testFileData(&zip);
QVERIFY(zip.close());
}
void KArchiveTest::testZipCopyTo()
{
// testCreateZip must have been run first.
KZip zip(s_zipFileName);
QVERIFY(zip.open(QIODevice::ReadOnly));
testCopyTo(&zip);
QVERIFY(zip.close());
}
void KArchiveTest::testZipMaxLength()
{
KZip zip( s_zipMaxLengthFileName );
QVERIFY( zip.open( QIODevice::WriteOnly ) );
// Similar to testTarMaxLength just to make sure, but of course zip doesn't have
// those limitations in the first place.
for (int i = 98; i < 514 ; i++ )
{
QString str, num;
str.fill( 'a', i-10 );
num.setNum( i );
num = num.rightJustified( 10, '0' );
zip.writeFile( str+num, "testu", "testg", "hum", 3 );
}
QVERIFY( zip.close() );
QVERIFY( zip.open( QIODevice::ReadOnly ) );
const KArchiveDirectory* dir = zip.directory();
QVERIFY( dir != 0 );
const QStringList listing = recursiveListEntries( dir, "", 0 );
QCOMPARE( listing[ 0], QString("mode=100644 path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000098 type=file size=3") );
QCOMPARE( listing[ 3], QString("mode=100644 path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000101 type=file size=3") );
QCOMPARE( listing[ 4], QString("mode=100644 path=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa0000000102 type=file size=3") );
QCOMPARE( listing.count(), 514 - 98 );
QVERIFY( zip.close() );
}
void KArchiveTest::testZipWithNonLatinFileNames()
{
KZip zip( s_zipLocaleFileName );
QVERIFY( zip.open( QIODevice::WriteOnly ) );
const QByteArray fileData("Test of data with a russian file name");
const QString fileName = QString::fromUtf8( "Архитектура.okular" );
const QString recodedFileName = QFile::decodeName( QFile::encodeName( fileName ) );
QVERIFY( zip.writeFile( fileName, "pino", "users", fileData.constData(), fileData.size() ) );
QVERIFY( zip.close() );
QVERIFY( zip.open( QIODevice::ReadOnly ) );
const KArchiveDirectory* dir = zip.directory();
QVERIFY( dir != 0 );
const QStringList listing = recursiveListEntries( dir, "", 0 );
QCOMPARE( listing.count(), 1 );
QCOMPARE( listing[0], QString::fromUtf8("mode=100644 path=%1 type=file size=%2").arg(recodedFileName).arg(fileData.size()) );
const KArchiveFile* fileEntry = static_cast< const KArchiveFile* >( dir->entry( dir->entries()[0] ) );
QCOMPARE( fileEntry->data(), fileData );
}
static bool writeFile(const QString& dirName, const QString& fileName, const QByteArray& data)
{
Q_ASSERT(dirName.endsWith('/'));
QFile file(dirName + fileName);
if (!file.open(QIODevice::WriteOnly))
return false;
file.write(data);
return true;
}
void KArchiveTest::testZipAddLocalDirectory()
{
// Prepare local dir
KTempDir tmpDir;
const QString dirName = tmpDir.name();
const QByteArray file1Data = "Hello Shantanu";
const QString file1 = QLatin1String("file1");
QVERIFY(writeFile(dirName, file1, file1Data));
{
KZip zip(s_zipFileName);
QVERIFY(zip.open(QIODevice::WriteOnly));
QVERIFY(zip.addLocalDirectory(dirName, "."));
QVERIFY(zip.close());
}
{
KZip zip(s_zipFileName);
QVERIFY(zip.open(QIODevice::ReadOnly));
const KArchiveDirectory* dir = zip.directory();
QVERIFY(dir != 0);
const KArchiveEntry* e = dir->entry(file1);
QVERIFY(e && e->isFile());
const KArchiveFile* f = (KArchiveFile*)e;
QCOMPARE(f->data(), file1Data);
}
}
/**
* @see QTest::cleanupTestCase()
*/
void KArchiveTest::cleanupTestCase()
{
QFile::remove(s_zipMaxLengthFileName);
QFile::remove(s_zipFileName);
QFile::remove(s_zipLocaleFileName);
#ifndef Q_OS_WIN
QFile::remove("test3_symlink");
#endif
}
diff --git a/kdecore/tests/karchivetest.h b/kdecore/tests/karchivetest.h
index f80d1aee91..a058ae488c 100644
--- a/kdecore/tests/karchivetest.h
+++ b/kdecore/tests/karchivetest.h
@@ -1,64 +1,67 @@
/* This file is part of the KDE project
Copyright (C) 2006 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KARCHIVETEST_H
#define KARCHIVETEST_H
#include <QtCore/QObject>
class KArchiveTest : public QObject
{
Q_OBJECT
void setupData();
private Q_SLOTS:
void initTestCase();
void testCreateTar_data();
void testCreateTar();
void testCreateTarXXX_data(){ setupData(); };
void testCreateTarXXX();
void testReadTar_data(){ setupData(); };
void testReadTar();
void testUncompress_data(){ setupData(); };
void testUncompress();
void testTarFileData_data(){ setupData(); };
void testTarFileData();
void testTarCopyTo_data(){ setupData(); };
void testTarCopyTo();
void testTarReadWrite_data(){ setupData(); };
void testTarReadWrite();
void testTarMaxLength_data();
void testTarMaxLength();
+ void testTarGlobalHeader();
+ void testTarPrefix();
+ void testTarDirectoryForgotten();
void testCreateZip();
void testCreateZipError();
void testReadZipError();
void testReadZip();
void testZipFileData();
void testZipCopyTo();
void testZipMaxLength();
void testZipWithNonLatinFileNames();
void testZipAddLocalDirectory();
void cleanupTestCase();
};
#endif
diff --git a/kdecore/tests/kservicetest.cpp b/kdecore/tests/kservicetest.cpp
index 24fbbae674..370ba19495 100644
--- a/kdecore/tests/kservicetest.cpp
+++ b/kdecore/tests/kservicetest.cpp
@@ -1,583 +1,586 @@
/*
* Copyright (C) 2006 David Faure <faure@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License version 2 as published by the Free Software Foundation;
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include <locale.h>
#include "kservicetest.h"
#include "kservicetest.moc"
#include <qtest_kde.h>
#include <kconfig.h>
#include <kconfiggroup.h>
#include <kdesktopfile.h>
#include <ksycoca.h>
#include <kglobal.h>
#include <kstandarddirs.h>
#include <kprotocolinfo.h>
#include <kdebug.h>
#include <kprocess.h>
#include <kservicegroup.h>
#include <kservicetypetrader.h>
#include <kservicetype.h>
#include <kservicetypeprofile.h>
#include <QtCore/Q_PID>
QTEST_KDEMAIN_CORE( KServiceTest )
static void eraseProfiles()
{
QString profilerc = KStandardDirs::locateLocal( "config", "profilerc" );
if ( !profilerc.isEmpty() )
QFile::remove( profilerc );
profilerc = KStandardDirs::locateLocal( "config", "servicetype_profilerc" );
if ( !profilerc.isEmpty() )
QFile::remove( profilerc );
}
void KServiceTest::initTestCase()
{
// A non-C locale is necessary for some tests.
// This locale must have the following properties:
// - some character other than dot as decimal separator
// If it cannot be set, locale-dependent tests are skipped.
setlocale(LC_ALL, "fr_FR.utf8");
m_hasNonCLocale = (setlocale(LC_ALL, NULL) == QByteArray("fr_FR.utf8"));
if (!m_hasNonCLocale) {
kDebug() << "Setting locale to fr_FR.utf8 failed";
}
m_hasKde4Konsole = false;
eraseProfiles();
// Create some fake services for the tests below, and ensure they are in ksycoca.
// fakeservice: deleted and recreated by testKSycocaUpdate, don't use in other tests
bool mustUpdateKSycoca = !KService::serviceByDesktopPath("fakeservice.desktop");
const QString fakeService = KStandardDirs::locateLocal("services", "fakeservice.desktop");
if (!QFile::exists(fakeService)) {
mustUpdateKSycoca = true;
createFakeService();
}
// fakepart: a readwrite part, like katepart
if (!KService::serviceByDesktopPath("fakepart.desktop")) {
mustUpdateKSycoca = true;
}
const QString fakePart = KStandardDirs::locateLocal("services", "fakepart.desktop");
if (!QFile::exists(fakePart)) {
mustUpdateKSycoca = true;
KDesktopFile file(fakePart);
KConfigGroup group = file.desktopGroup();
group.writeEntry("Name", "FakePart");
group.writeEntry("Type", "Service");
group.writeEntry("X-KDE-Library", "fakepart");
group.writeEntry("X-KDE-Protocols", "http,ftp");
group.writeEntry("X-KDE-ServiceTypes", "KParts/ReadOnlyPart,Browser/View,KParts/ReadWritePart");
group.writeEntry("MimeType", "text/plain;");
}
// faketextplugin: a ktexteditor plugin
if (!KService::serviceByDesktopPath("faketextplugin.desktop")) {
mustUpdateKSycoca = true;
}
const QString fakeTextplugin = KStandardDirs::locateLocal("services", "faketextplugin.desktop");
if (!QFile::exists(fakeTextplugin)) {
mustUpdateKSycoca = true;
KDesktopFile file(fakeTextplugin);
KConfigGroup group = file.desktopGroup();
group.writeEntry("Name", "FakeTextPlugin");
group.writeEntry("Type", "Service");
group.writeEntry("X-KDE-Library", "faketextplugin");
group.writeEntry("X-KDE-ServiceTypes", "KTextEditor/Plugin");
group.writeEntry("MimeType", "text/plain;");
}
if ( mustUpdateKSycoca ) {
// Update ksycoca in ~/.kde-unit-test after creating the above
QProcess::execute( KGlobal::dirs()->findExe(KBUILDSYCOCA_EXENAME), QStringList() << "--noincremental" );
kDebug() << "waiting for signal";
QVERIFY(QTest::kWaitForSignal(KSycoca::self(), SIGNAL(databaseChanged(QStringList)), 10000));
kDebug() << "got signal";
}
}
void KServiceTest::cleanupTestCase()
{
// If I want the konqueror unit tests to work, then I better not have a non-working part
// as the preferred part for text/plain...
QStringList services; services << "fakeservice.desktop" << "fakepart.desktop" << "faketextplugin.desktop";
Q_FOREACH(const QString& service, services) {
const QString fakeService = KStandardDirs::locateLocal("services", service);
QFile::remove(fakeService);
}
//QProcess::execute( KGlobal::dirs()->findExe(KBUILDSYCOCA_EXENAME) );
KProcess proc;
proc << KStandardDirs::findExe(KBUILDSYCOCA_EXENAME);
proc.setOutputChannelMode(KProcess::MergedChannels); // silence kbuildsycoca output
proc.execute();
}
void KServiceTest::testByName()
{
if ( !KSycoca::isAvailable() )
QSKIP( "ksycoca not available", SkipAll );
KServiceType::Ptr s0 = KServiceType::serviceType("KParts/ReadOnlyPart");
QVERIFY( s0 );
QCOMPARE( s0->name(), QString::fromLatin1("KParts/ReadOnlyPart") );
KService::Ptr khtml = KService::serviceByDesktopPath("khtml.desktop");
QCOMPARE( khtml->name(), QString::fromLatin1("KHTML"));
}
void KServiceTest::testProperty()
{
KService::Ptr kdedkcookiejar = KService::serviceByDesktopPath("kded/kcookiejar.desktop");
QVERIFY(kdedkcookiejar);
QCOMPARE(kdedkcookiejar->entryPath(), QString("kded/kcookiejar.desktop"));
QCOMPARE(kdedkcookiejar->property("ServiceTypes").toStringList().join(","), QString("KDEDModule"));
QCOMPARE(kdedkcookiejar->property("X-KDE-Kded-autoload").toBool(), false);
QCOMPARE(kdedkcookiejar->property("X-KDE-Kded-load-on-demand").toBool(), true);
QVERIFY(!kdedkcookiejar->property("Name").toString().isEmpty());
QVERIFY(!kdedkcookiejar->property("Name[fr]", QVariant::String).isValid());
KService::Ptr kjavaappletviewer = KService::serviceByDesktopPath("kjavaappletviewer.desktop");
QVERIFY(kjavaappletviewer);
QCOMPARE(kjavaappletviewer->property("X-KDE-BrowserView-PluginsInfo").toString(), QString("kjava/pluginsinfo"));
// Test property("X-KDE-Protocols"), which triggers the KServiceReadProperty code.
KService::Ptr fakePart = KService::serviceByDesktopPath("fakepart.desktop");
QVERIFY(fakePart); // see initTestCase; it should be found.
QVERIFY(fakePart->propertyNames().contains("X-KDE-Protocols"));
const QStringList protocols = fakePart->property("X-KDE-Protocols").toStringList();
QCOMPARE(protocols, QStringList() << "http" << "ftp");
}
void KServiceTest::testAllServiceTypes()
{
if ( !KSycoca::isAvailable() )
QSKIP( "ksycoca not available", SkipAll );
const KServiceType::List allServiceTypes = KServiceType::allServiceTypes();
// A bit of checking on the allServiceTypes list itself
KServiceType::List::ConstIterator stit = allServiceTypes.begin();
const KServiceType::List::ConstIterator stend = allServiceTypes.end();
for ( ; stit != stend; ++stit ) {
const KServiceType::Ptr servtype = (*stit);
const QString name = servtype->name();
QVERIFY( !name.isEmpty() );
QVERIFY( servtype->sycocaType() == KST_KServiceType );
}
}
void KServiceTest::testAllServices()
{
if ( !KSycoca::isAvailable() )
QSKIP( "ksycoca not available", SkipAll );
const KService::List lst = KService::allServices();
QVERIFY( !lst.isEmpty() );
for ( KService::List::ConstIterator it = lst.begin();
it != lst.end(); ++it ) {
const KService::Ptr service = (*it);
QVERIFY( service->isType( KST_KService ) );
const QString name = service->name();
const QString entryPath = service->entryPath();
//kDebug() << name << "entryPath=" << entryPath << "menuId=" << service->menuId();
QVERIFY( !name.isEmpty() );
QVERIFY( !entryPath.isEmpty() );
KService::Ptr lookedupService = KService::serviceByDesktopPath( entryPath );
QVERIFY( lookedupService ); // not null
QCOMPARE( lookedupService->entryPath(), entryPath );
if ( service->isApplication() )
{
const QString menuId = service->menuId();
if ( menuId.isEmpty() )
qWarning( "%s has an empty menuId!", qPrintable( entryPath ) );
else if ( menuId == "kde4-konsole.desktop" )
m_hasKde4Konsole = true;
QVERIFY( !menuId.isEmpty() );
lookedupService = KService::serviceByMenuId( menuId );
QVERIFY( lookedupService ); // not null
QCOMPARE( lookedupService->menuId(), menuId );
}
}
}
// Helper method for all the trader tests
static bool offerListHasService( const KService::List& offers,
const QString& entryPath )
{
bool found = false;
KService::List::const_iterator it = offers.begin();
for ( ; it != offers.end() ; it++ )
{
if ( (*it)->entryPath() == entryPath ) {
if( found ) { // should be there only once
qWarning( "ERROR: %s was found twice in the list", qPrintable( entryPath ) );
return false; // make test fail
}
found = true;
}
}
return found;
}
void KServiceTest::testDBUSStartupType()
{
if ( !KSycoca::isAvailable() )
QSKIP( "ksycoca not available", SkipAll );
if ( !m_hasKde4Konsole )
QSKIP( "kde4-konsole.desktop not available", SkipAll );
//KService::Ptr konsole = KService::serviceByMenuId( "kde4-konsole.desktop" );
KService::Ptr konsole = KService::serviceByDesktopName( "konsole" );
QVERIFY(konsole);
QCOMPARE(konsole->menuId(), QString("kde4-konsole.desktop"));
//qDebug() << konsole->entryPath();
QCOMPARE((int)konsole->dbusStartupType(), (int)KService::DBusUnique);
}
void KServiceTest::testByStorageId()
{
if ( !KSycoca::isAvailable() )
QSKIP("ksycoca not available", SkipAll);
+ if (KGlobal::dirs()->locate("xdgdata-apps", "kde4/kmailservice.desktop").isEmpty()) {
+ QSKIP("kde4/kmailservice.desktop not available", SkipAll);
+ }
QVERIFY(KService::serviceByMenuId("kde4-kmailservice.desktop"));
QVERIFY(!KService::serviceByMenuId("kde4-kmailservice")); // doesn't work, extension mandatory
QVERIFY(KService::serviceByStorageId("kde4-kmailservice.desktop"));
//QVERIFY(!KService::serviceByStorageId("kde4-kmailservice")); // doesn't work, extension mandatory; also shows a debug
// This one fails here; probably because there are two such files, so this would be too
// ambiguous... According to the testAllServices output, the entryPaths are
// entryPath="/d/kde/inst/kde4/share/applications/kde4/kmailservice.desktop"
// entryPath= "/usr/share/applications/kde4/kmailservice.desktop"
//
//QVERIFY(KService::serviceByDesktopPath("kmailservice.desktop"));
QVERIFY(KService::serviceByDesktopName("kmailservice"));
// This could fail if it finds the kde3 kmailservice from /usr/share. But who still has kde3 :-)
QCOMPARE(KService::serviceByDesktopName("kmailservice")->menuId(), QString("kde4-kmailservice.desktop"));
}
void KServiceTest::testServiceTypeTraderForReadOnlyPart()
{
if ( !KSycoca::isAvailable() )
QSKIP( "ksycoca not available", SkipAll );
// Querying trader for services associated with KParts/ReadOnlyPart
KService::List offers = KServiceTypeTrader::self()->query("KParts/ReadOnlyPart");
QVERIFY( offers.count() > 0 );
//foreach( KService::Ptr service, offers )
// qDebug( "%s %s", qPrintable( service->name() ), qPrintable( service->entryPath() ) );
m_firstOffer = offers[0]->entryPath();
// Only test for parts provided by kdelibs, or better, by this unittest:
QVERIFY( offerListHasService( offers, "fakepart.desktop" ) );
QVERIFY( offerListHasService( offers, "kmultipart.desktop" ) );
QVERIFY( offerListHasService( offers, "khtml.desktop" ) );
QVERIFY( offerListHasService( offers, "khtmlimage.desktop" ) );
QVERIFY( offerListHasService( offers, "kjavaappletviewer.desktop" ) );
// Check ordering according to InitialPreference
int lastPreference = -1;
bool lastAllowedAsDefault = true;
Q_FOREACH(KService::Ptr service, offers) {
const QString path = service->entryPath();
const int preference = service->initialPreference(); // ## might be wrong if we use per-servicetype preferences...
//qDebug( "%s has preference %d, allowAsDefault=%d", qPrintable( path ), preference, service->allowAsDefault() );
if ( lastAllowedAsDefault && !service->allowAsDefault() ) {
// first "not allowed as default" offer
lastAllowedAsDefault = false;
lastPreference = -1; // restart
}
if ( lastPreference != -1 )
QVERIFY( preference <= lastPreference );
lastPreference = preference;
}
// Now look for any KTextEditor/Plugin
offers = KServiceTypeTrader::self()->query("KTextEditor/Plugin");
QVERIFY( offerListHasService( offers, "fakeservice.desktop" ) );
QVERIFY( offerListHasService( offers, "faketextplugin.desktop" ) );
}
void KServiceTest::testTraderConstraints()
{
if ( !KSycoca::isAvailable() )
QSKIP( "ksycoca not available", SkipAll );
KService::List offers = KServiceTypeTrader::self()->query("KTextEditor/Plugin", "Library == 'faketextplugin'");
QCOMPARE(offers.count(), 1);
QVERIFY( offerListHasService( offers, "faketextplugin.desktop" ) );
if (m_hasNonCLocale) {
// Test float parsing, must use dot as decimal separator independent of locale.
offers = KServiceTypeTrader::self()->query("KTextEditor/Plugin", "([X-KDE-Version] > 4.559) and ([X-KDE-Version] < 4.561)");
QCOMPARE(offers.count(), 1);
QVERIFY(offerListHasService( offers, "fakeservice.desktop"));
}
// A test with an invalid query, to test for memleaks
offers = KServiceTypeTrader::self()->query("KTextEditor/Plugin", "A == B OR C == D AND OR Foo == 'Parse Error'");
QVERIFY(offers.isEmpty());
}
void KServiceTest::testHasServiceType1() // with services constructed with a full path (rare)
{
QString fakepartPath = KStandardDirs::locate( "services", "fakepart.desktop" );
QVERIFY( !fakepartPath.isEmpty() );
KService fakepart( fakepartPath );
QVERIFY( fakepart.hasServiceType( "KParts/ReadOnlyPart" ) );
QVERIFY( fakepart.hasServiceType( "KParts/ReadWritePart" ) );
QString faketextPluginPath = KStandardDirs::locate( "services", "faketextplugin.desktop" );
QVERIFY( !faketextPluginPath.isEmpty() );
KService faketextPlugin( faketextPluginPath );
QVERIFY( faketextPlugin.hasServiceType( "KTextEditor/Plugin" ) );
QVERIFY( !faketextPlugin.hasServiceType( "KParts/ReadOnlyPart" ) );
}
void KServiceTest::testHasServiceType2() // with services coming from ksycoca
{
KService::Ptr fakepart = KService::serviceByDesktopPath( "fakepart.desktop" );
QVERIFY( !fakepart.isNull() );
QVERIFY( fakepart->hasServiceType( "KParts/ReadOnlyPart" ) );
QVERIFY( fakepart->hasServiceType( "KParts/ReadWritePart" ) );
KService::Ptr faketextPlugin = KService::serviceByDesktopPath( "faketextplugin.desktop" );
QVERIFY( !faketextPlugin.isNull() );
QVERIFY( faketextPlugin->hasServiceType( "KTextEditor/Plugin" ) );
QVERIFY( !faketextPlugin->hasServiceType( "KParts/ReadOnlyPart" ) );
}
void KServiceTest::testWriteServiceTypeProfile()
{
const QString serviceType = "KParts/ReadOnlyPart";
KService::List services, disabledServices;
services.append(KService::serviceByDesktopPath("khtmlimage.desktop"));
services.append(KService::serviceByDesktopPath("fakepart.desktop"));
disabledServices.append(KService::serviceByDesktopPath("khtml.desktop"));
KService::List::ConstIterator servit = services.constBegin();
for( ; servit != services.constEnd(); ++servit) {
QVERIFY(!servit->isNull());
}
KServiceTypeProfile::writeServiceTypeProfile( serviceType, services, disabledServices );
// Check that the file got written
QString profilerc = KStandardDirs::locateLocal( "config", "servicetype_profilerc" );
QVERIFY(!profilerc.isEmpty());
QVERIFY(QFile::exists(profilerc));
KService::List offers = KServiceTypeTrader::self()->query( serviceType );
QVERIFY( offers.count() > 0 ); // not empty
//foreach( KService::Ptr service, offers )
// qDebug( "%s %s", qPrintable( service->name() ), qPrintable( service->entryPath() ) );
QVERIFY( offers.count() >= 3 ); // at least 3, even
QCOMPARE( offers[0]->entryPath(), QString("khtmlimage.desktop") );
QCOMPARE( offers[1]->entryPath(), QString("fakepart.desktop") );
QVERIFY( offerListHasService( offers, "kmultipart.desktop" ) ); // should still be somewhere in there
QVERIFY( !offerListHasService( offers, "khtml.desktop" ) ); // it got disabled above
}
void KServiceTest::testDefaultOffers()
{
// Now that we have a user-profile, let's see if defaultOffers indeed gives us the default ordering.
const QString serviceType = "KParts/ReadOnlyPart";
KService::List offers = KServiceTypeTrader::self()->defaultOffers( serviceType );
QVERIFY( offers.count() > 0 ); // not empty
QVERIFY( offerListHasService( offers, "khtml.desktop" ) ); // it's here even though it's disabled in the profile
if ( m_firstOffer.isEmpty() )
QSKIP( "testServiceTypeTraderForReadOnlyPart not run", SkipAll );
QCOMPARE( offers[0]->entryPath(), m_firstOffer );
}
void KServiceTest::testDeleteServiceTypeProfile()
{
const QString serviceType = "KParts/ReadOnlyPart";
KServiceTypeProfile::deleteServiceTypeProfile( serviceType );
KService::List offers = KServiceTypeTrader::self()->query( serviceType );
QVERIFY( offers.count() > 0 ); // not empty
QVERIFY( offerListHasService( offers, "khtml.desktop" ) ); // it's back
if ( m_firstOffer.isEmpty() )
QSKIP( "testServiceTypeTraderForReadOnlyPart not run", SkipAll );
QCOMPARE( offers[0]->entryPath(), m_firstOffer );
}
void KServiceTest::testActionsAndDataStream()
{
const QString servicePath = KStandardDirs::locate( "services", "ScreenSavers/krandom.desktop" );
if (servicePath.isEmpty() )
QSKIP("kdebase not installed, krandom.desktop not found", SkipAll);
KService service( servicePath );
QVERIFY(!service.property("Name[fr]", QVariant::String).isValid());
const QList<KServiceAction> actions = service.actions();
QCOMPARE(actions.count(), 3);
const KServiceAction setupAction = actions[0];
QCOMPARE(setupAction.name(), QString("Setup"));
QCOMPARE(setupAction.exec(), QString("krandom.kss -setup"));
QVERIFY(!setupAction.icon().isEmpty());
QCOMPARE(setupAction.noDisplay(), false);
QVERIFY(!setupAction.isSeparator());
const KServiceAction rootAction = actions[2];
QCOMPARE(rootAction.name(), QString("Root"));
QByteArray data;
QDataStream stream(&data, QIODevice::WriteOnly);
service.save(stream);
QVERIFY(!data.isEmpty());
// The binary size of that KService in ksycoca was 3700 when storing all Name[...] translations!
// Now down to 755. This is on x86, so make the assert for 1500 in case x86_64 needs more.
QVERIFY(data.size() < 1500);
QDataStream loadingStream(data);
// loading must first get type, see KSycocaEntryPrivate::save
// (the path that save writes out, is read by the KSycocaEntryPrivate ctor)
qint32 type;
loadingStream >> type;
KService loadedService(loadingStream, 0);
QCOMPARE(loadedService.name(), service.name());
QCOMPARE(loadedService.exec(), service.exec());
QCOMPARE(loadedService.actions().count(), 3);
}
void KServiceTest::testServiceGroups()
{
KServiceGroup::Ptr root = KServiceGroup::root();
QVERIFY(root);
qDebug() << root->groupEntries().count();
KServiceGroup::Ptr group = root;
QVERIFY(group);
const KServiceGroup::List list = group->entries(true /* sorted */,
true /* exclude no display entries */,
false /* allow separators */,
true /* sort by generic name */);
kDebug() << list.count();
Q_FOREACH(KServiceGroup::SPtr s, list) {
qDebug() << s->name() << s->entryPath();
}
// No unit test here yet, but at least this can be valgrinded for errors.
}
void KServiceTest::testKSycocaUpdate()
{
kWarning();
KService::Ptr fakeService = KService::serviceByDesktopPath("fakeservice.desktop");
QVERIFY(fakeService); // see initTestCase; it should be found.
// Test deleting a service
const QString servPath = KStandardDirs::locateLocal("services", "fakeservice.desktop");
QVERIFY(QFile::exists(servPath));
QSignalSpy spy(KSycoca::self(), SIGNAL(databaseChanged(QStringList)));
QVERIFY(spy.isValid());
QFile::remove(servPath);
kDebug() << QThread::currentThread() << "executing kbuildsycoca";
QProcess::execute( KGlobal::dirs()->findExe(KBUILDSYCOCA_EXENAME) );
kDebug() << QThread::currentThread() << "done";
while (spy.isEmpty())
QTest::qWait(50);
QVERIFY(!spy.isEmpty());
QVERIFY(!KService::serviceByDesktopPath("fakeservice.desktop")); // not in ksycoca anymore
QVERIFY(spy[0][0].toStringList().contains("services"));
kDebug() << QThread::currentThread() << "got signal ok";
spy.clear();
QVERIFY(fakeService); // the whole point of refcounting is that this KService instance is still valid.
QVERIFY(!QFile::exists(servPath));
// Recreate it, for future tests
createFakeService();
QVERIFY(QFile::exists(servPath));
kDebug() << QThread::currentThread() << "executing kbuildsycoca (2)";
QProcess::execute( KGlobal::dirs()->findExe(KBUILDSYCOCA_EXENAME) );
kDebug() << QThread::currentThread() << "done (2)";
while (spy.isEmpty())
QTest::qWait(50);
kDebug() << QThread::currentThread() << "got signal ok (2)";
QVERIFY(spy[0][0].toStringList().contains("services"));
if (QThread::currentThread() != QCoreApplication::instance()->thread())
m_sycocaUpdateDone.ref();
}
void KServiceTest::createFakeService()
{
const QString fakeService = KStandardDirs::locateLocal("services", "fakeservice.desktop");
KDesktopFile file(fakeService);
KConfigGroup group = file.desktopGroup();
group.writeEntry("Name", "FakePlugin");
group.writeEntry("Type", "Service");
group.writeEntry("X-KDE-Library", "fakeservice");
group.writeEntry("X-KDE-Version", "4.56");
group.writeEntry("ServiceTypes", "KTextEditor/Plugin");
group.writeEntry("MimeType", "text/plain;");
}
#include <QThreadPool>
#include <qtconcurrentrun.h>
// Testing for concurrent access to ksycoca from multiple threads
// It's especially interesting to run this test as ./kservicetest testThreads
// so that even the ksycoca initialization is happening from N threads at the same time.
// Use valgrind --tool=helgrind to see the race conditions.
void KServiceTest::testReaderThreads()
{
QThreadPool::globalInstance()->setMaxThreadCount(10);
QList<QFuture<void> > futures;
futures << QtConcurrent::run(this, &KServiceTest::testAllServices);
futures << QtConcurrent::run(this, &KServiceTest::testAllServices);
futures << QtConcurrent::run(this, &KServiceTest::testAllServices);
futures << QtConcurrent::run(this, &KServiceTest::testHasServiceType1);
futures << QtConcurrent::run(this, &KServiceTest::testAllServices);
futures << QtConcurrent::run(this, &KServiceTest::testAllServices);
Q_FOREACH(QFuture<void> f, futures) // krazy:exclude=foreach
f.waitForFinished();
QThreadPool::globalInstance()->setMaxThreadCount(1); // delete those threads
}
void KServiceTest::testThreads()
{
QThreadPool::globalInstance()->setMaxThreadCount(10);
QList<QFuture<void> > futures;
futures << QtConcurrent::run(this, &KServiceTest::testAllServices);
futures << QtConcurrent::run(this, &KServiceTest::testHasServiceType1);
futures << QtConcurrent::run(this, &KServiceTest::testKSycocaUpdate);
futures << QtConcurrent::run(this, &KServiceTest::testTraderConstraints);
while (m_sycocaUpdateDone == 0) // not using a bool, just to silence helgrind
QTest::qWait(100); // process D-Bus events!
kDebug() << "Joining all threads";
Q_FOREACH(QFuture<void> f, futures) // krazy:exclude=foreach
f.waitForFinished();
}
diff --git a/kdecore/tests/kshareddatacachetest.cpp b/kdecore/tests/kshareddatacachetest.cpp
new file mode 100644
index 0000000000..116cb1c4fd
--- /dev/null
+++ b/kdecore/tests/kshareddatacachetest.cpp
@@ -0,0 +1,76 @@
+/* This file is part of the KDE libraries
+ * Copyright (c) 2012 David Faure <faure@kde.org>
+ *
+ * This library is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2 of the License or ( at
+ * your option ) version 3 or, at the discretion of KDE e.V. ( which shall
+ * act as a proxy as in section 14 of the GPLv3 ), any later version.
+ *
+ * This library 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
+ * Library General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this library; see the file COPYING.LIB. If not, write to
+ * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+*/
+
+#include <kshareddatacache.h>
+
+#include <qtest_kde.h>
+
+#include <QtCore/QObject>
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+#include <QtCore/QDir>
+#include <kstandarddirs.h>
+#include <string.h> // strcpy
+
+class KSharedDataCacheTest : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void initTestCase();
+ void simpleInsert();
+};
+
+void KSharedDataCacheTest::initTestCase()
+{
+}
+
+void KSharedDataCacheTest::simpleInsert()
+{
+ const QLatin1String cacheName("myTestCache");
+ const QLatin1String key("mypic");
+ // clear the cache
+ QString cacheFile = KGlobal::dirs()->locateLocal("cache", cacheName + QLatin1String(".kcache"));
+ QFile file(cacheFile);
+ if (file.exists())
+ QVERIFY(file.remove());
+ // insert something into it
+ KSharedDataCache cache(cacheName, 5*1024*1024);
+ QVERIFY(file.exists()); // make sure we got the cache filename right
+ QByteArray data;
+ data.resize(9228);
+ strcpy(data.data(), "Hello world");
+ QVERIFY(cache.insert(key, data));
+ // read it out again
+ QByteArray result;
+ QVERIFY(cache.find(key, &result));
+ QCOMPARE(result, data);
+ // another insert
+ strcpy(data.data(), "Hello KDE");
+ QVERIFY(cache.insert(key, data));
+ // and another read
+ QVERIFY(cache.find(key, &result));
+ QEXPECT_FAIL("", "Bug in findNamedEntry!", Continue);
+ QCOMPARE(result, data);
+}
+
+QTEST_KDEMAIN_CORE(KSharedDataCacheTest)
+
+#include "kshareddatacachetest.moc"
diff --git a/kdecore/tests/tar_directory_forgotten.tar.bz2 b/kdecore/tests/tar_directory_forgotten.tar.bz2
new file mode 100644
index 0000000000..16a56e49d5
Binary files /dev/null and b/kdecore/tests/tar_directory_forgotten.tar.bz2 differ
diff --git a/kdecore/tests/tar_prefix_test.tar.bz2 b/kdecore/tests/tar_prefix_test.tar.bz2
new file mode 100644
index 0000000000..4d10e0c0b3
Binary files /dev/null and b/kdecore/tests/tar_prefix_test.tar.bz2 differ
diff --git a/kdecore/util/kshareddatacache.cpp b/kdecore/util/kshareddatacache.cpp
index ca1eda96fc..9fe399558a 100644
--- a/kdecore/util/kshareddatacache.cpp
+++ b/kdecore/util/kshareddatacache.cpp
@@ -1,1604 +1,1604 @@
/*
* This file is part of the KDE project.
* Copyright © 2010 Michael Pyne <mpyne@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License version 2 as published by the Free Software Foundation.
*
* This library includes "MurmurHash" code from Austin Appleby, which is
* placed in the public domain. See http://sites.google.com/site/murmurhash/
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kshareddatacache.h"
#include "kshareddatacache_p.h" // Various auxiliary support code
#include <kdebug.h>
#include <kglobal.h>
#include <kstandarddirs.h>
#include <krandom.h>
#include <QtCore/QDateTime>
#include <QtCore/QSharedPointer>
#include <QtCore/QByteArray>
#include <QtCore/QFile>
#include <QtCore/QAtomicInt>
#include <QtCore/QList>
#include <QtCore/QMutex>
#include <QtCore/QMutexLocker>
#include <sys/types.h>
#include <sys/mman.h>
#include <stdlib.h>
int ksdcArea()
{
static int s_ksdcArea = KDebug::registerArea("KSharedDataCache", false);
return s_ksdcArea;
}
//-----------------------------------------------------------------------------
// MurmurHashAligned, by Austin Appleby
// (Released to the public domain, or licensed under the MIT license where
// software may not be released to the public domain. See
// http://sites.google.com/site/murmurhash/)
// Same algorithm as MurmurHash, but only does aligned reads - should be safer
// on certain platforms.
static unsigned int MurmurHashAligned(const void *key, int len, unsigned int seed)
{
const unsigned int m = 0xc6a4a793;
const int r = 16;
const unsigned char * data = reinterpret_cast<const unsigned char *>(key);
unsigned int h = seed ^ (len * m);
int align = reinterpret_cast<quintptr>(data) & 3;
if(align & (len >= 4))
{
// Pre-load the temp registers
unsigned int t = 0, d = 0;
switch(align)
{
case 1: t |= data[2] << 16;
case 2: t |= data[1] << 8;
case 3: t |= data[0];
}
t <<= (8 * align);
data += 4-align;
len -= 4-align;
int sl = 8 * (4-align);
int sr = 8 * align;
// Mix
while(len >= 4)
{
d = *reinterpret_cast<const unsigned int *>(data);
t = (t >> sr) | (d << sl);
h += t;
h *= m;
h ^= h >> r;
t = d;
data += 4;
len -= 4;
}
// Handle leftover data in temp registers
int pack = len < align ? len : align;
d = 0;
switch(pack)
{
case 3: d |= data[2] << 16;
case 2: d |= data[1] << 8;
case 1: d |= data[0];
case 0: h += (t >> sr) | (d << sl);
h *= m;
h ^= h >> r;
}
data += pack;
len -= pack;
}
else
{
while(len >= 4)
{
h += *reinterpret_cast<const unsigned int *>(data);
h *= m;
h ^= h >> r;
data += 4;
len -= 4;
}
}
//----------
// Handle tail bytes
switch(len)
{
case 3: h += data[2] << 16;
case 2: h += data[1] << 8;
case 1: h += data[0];
h *= m;
h ^= h >> r;
};
h *= m;
h ^= h >> 10;
h *= m;
h ^= h >> 17;
return h;
}
/**
* This is the hash function used for our data to hopefully make the
* hashing used to place the QByteArrays as efficient as possible.
*/
static quint32 generateHash(const QByteArray &buffer)
{
// The final constant is the "seed" for MurmurHash. Do *not* change it
// without incrementing the cache version.
return MurmurHashAligned(buffer.data(), buffer.size(), 0xF0F00F0F);
}
// Alignment concerns become a big deal when we're dealing with shared memory,
// since trying to access a structure sized at, say 8 bytes at an address that
// is not evenly divisible by 8 is a crash-inducing error on some
// architectures. The compiler would normally take care of this, but with
// shared memory the compiler will not necessarily know the alignment expected,
// so make sure we account for this ourselves. To do so we need a way to find
// out the expected alignment. Enter ALIGNOF...
#ifndef ALIGNOF
#if defined(Q_CC_GNU) || defined(Q_CC_SUN)
#define ALIGNOF(x) (__alignof__ (x)) // GCC provides what we want directly
#else
#include <stddef.h> // offsetof
template<class T>
struct __alignmentHack
{
char firstEntry;
T obj;
static const size_t size = offsetof(__alignmentHack, obj);
};
#define ALIGNOF(x) (__alignmentHack<x>::size)
#endif // Non gcc
#endif // ALIGNOF undefined
// Returns a pointer properly aligned to handle size alignment.
// size should be a power of 2. start is assumed to be the lowest
// permissible address, therefore the return value will be >= start.
template<class T>
T* alignTo(const void *start, uint size = ALIGNOF(T))
{
quintptr mask = size - 1;
// Cast to int-type to handle bit-twiddling
quintptr basePointer = reinterpret_cast<quintptr>(start);
// If (and only if) we are already aligned, adding mask into basePointer
// will not increment any of the bits in ~mask and we get the right answer.
basePointer = (basePointer + mask) & ~mask;
return reinterpret_cast<T *>(basePointer);
}
/**
* Returns a pointer to a const object of type T, assumed to be @p offset
* *BYTES* greater than the base address. Note that in order to meet alignment
* requirements for T, it is possible that the returned pointer points greater
* than @p offset into @p base.
*/
template<class T>
const T *offsetAs(const void *const base, qint32 offset)
{
const char *ptr = reinterpret_cast<const char*>(base);
return alignTo<const T>(ptr + offset);
}
// Same as above, but for non-const objects
template<class T>
T *offsetAs(void *const base, qint32 offset)
{
char *ptr = reinterpret_cast<char *>(base);
return alignTo<T>(ptr + offset);
}
/**
* @return the smallest integer greater than or equal to (@p a / @p b).
* @param a Numerator, should be ≥ 0.
* @param b Denominator, should be > 0.
*/
template<class T>
T intCeil(T a, T b)
{
return (a + b - 1) / b;
}
typedef qint32 pageID;
// =========================================================================
// Description of the cache:
//
// The shared memory cache is designed to be handled as two separate objects,
// all contained in the same global memory segment. First off, there is the
// basic header data, consisting of the global header followed by the
// accounting data necessary to hold items (described in more detail
// momentarily). Following the accounting data is the start of the "page table"
// (essentially just as you'd see it in an Operating Systems text).
//
// The page table contains shared memory split into fixed-size pages, with a
// configurable page size. In the event that the data is too large to fit into
// a single logical page, it will need to occupy consecutive pages of memory.
//
// The accounting data that was referenced earlier is split into two:
//
// 1. index table, containing a fixed-size list of possible cache entries.
// Each index entry is of type IndexTableEntry (below), and holds the various
// accounting data and a pointer to the first page.
//
// 2. page table, which is used to speed up the process of searching for
// free pages of memory. There is one entry for every page in the page table,
// and it contains the index of the one entry in the index table actually
// holding the page (or <0 if the page is free).
//
// The entire segment looks like so:
// ?════════?═════════════?════════════?═══════?═══════?═══════?═══════?═══?
// ? Header │ Index Table │ Page Table ? Pages │ │ │ │...?
// ?════════?═════════════?════════════?═══════?═══════?═══════?═══════?═══?
// =========================================================================
// All elements of this struct must be "plain old data" (POD) types since it
// will be in shared memory. In addition, no pointers! To point to something
// you must use relative offsets since the pointer start addresses will be
// different in each process.
struct IndexTableEntry
{
uint fileNameHash;
uint totalItemSize; // in bytes
mutable uint useCount;
time_t addTime;
mutable time_t lastUsedTime;
pageID firstPage;
};
// Page table entry
struct PageTableEntry
{
// int so we can use values <0 for unassigned pages.
qint32 index;
};
// Each individual page contains the cached data. The first page starts off with
// the utf8-encoded key, a null '\0', and then the data follows immediately
// from the next byte, possibly crossing consecutive page boundaries to hold
// all of the data.
// There is, however, no specific struct for a page, it is simply a location in
// memory.
// This is effectively the layout of the shared memory segment. The variables
// contained within form the header, data contained afterwards is pointed to
// by using special accessor functions.
struct SharedMemory
{
/**
* Note to downstream packagers: This version flag is intended to be
* machine-specific. The KDE-provided source code will not set the lower
* two bits to allow for distribution-specific needs, with the exception
* of version 1 which was already defined in KDE Platform 4.5.
* e.g. the next version bump will be from 4 to 8, then 12, etc.
*/
enum {
PIXMAP_CACHE_VERSION = 12,
MINIMUM_CACHE_SIZE = 4096
};
// Note to those who follow me. You should not, under any circumstances, ever
// re-arrange the following two fields, even if you change the version number
// for later revisions of this code.
QAtomicInt ready; ///< DO NOT INITIALIZE
quint8 version;
// See kshareddatacache_p.h
SharedLock shmLock;
uint cacheSize;
uint cacheAvail;
QAtomicInt evictionPolicy;
// pageSize and cacheSize determine the number of pages. The number of
// pages determine the page table size and (indirectly) the index table
// size.
QAtomicInt pageSize;
// This variable is added to reserve space for later cache timestamping
// support. The idea is this variable will be updated when the cache is
// written to, to allow clients to detect a changed cache quickly.
QAtomicInt cacheTimestamp;
/**
* Converts the given average item size into an appropriate page size.
*/
static unsigned equivalentPageSize(unsigned itemSize)
{
if (itemSize == 0) {
return 4096; // Default average item size.
}
int log2OfSize = 0;
while ((itemSize >>= 1) != 0) {
log2OfSize++;
}
// Bound page size between 512 bytes and 256 KiB.
log2OfSize = qBound(9, log2OfSize, 18);
return (1 << log2OfSize);
}
// Returns pageSize in unsigned format.
unsigned cachePageSize() const
{
return static_cast<unsigned>(pageSize);
}
/**
* This is effectively the class ctor. But since we're in shared memory,
* there's a few rules:
*
* 1. To allow for some form of locking in the initial-setup case, we
* use an atomic int, which will be initialized to 0 by mmap(). Then to
* take the lock we atomically increment the 0 to 1. If we end up calling
* the QAtomicInt constructor we can mess that up, so we can't use a
* constructor for this class either.
* 2. Any member variable you add takes up space in shared memory as well,
* so make sure you need it.
*/
bool performInitialSetup(uint _cacheSize, uint _pageSize)
{
if (_cacheSize < MINIMUM_CACHE_SIZE) {
kError(ksdcArea()) << "Internal error: Attempted to create a cache sized < "
<< MINIMUM_CACHE_SIZE;
return false;
}
if (_pageSize == 0) {
kError(ksdcArea()) << "Internal error: Attempted to create a cache with 0-sized pages.";
return false;
}
shmLock.type = findBestSharedLock();
if (static_cast<int>(shmLock.type) == 0) {
kError(ksdcArea()) << "Unable to find an appropriate lock to guard the shared cache. "
<< "This *should* be essentially impossible. :(";
return false;
}
bool isProcessShared = false;
QSharedPointer<KSDCLock> tempLock(createLockFromId(shmLock.type, shmLock));
if (!tempLock->initialize(isProcessShared)) {
kError(ksdcArea()) << "Unable to initialize the lock for the cache!";
return false;
}
if (!isProcessShared) {
kWarning(ksdcArea()) << "Cache initialized, but does not support being"
<< "shared across processes.";
}
// These must be updated to make some of our auxiliary functions
// work right since their values will be based on the cache size.
cacheSize = _cacheSize;
pageSize = _pageSize;
version = PIXMAP_CACHE_VERSION;
cacheTimestamp = static_cast<unsigned>(::time(0));
clearInternalTables();
// Unlock the mini-lock, and introduce a total memory barrier to make
// sure all changes have propagated even without a mutex.
ready.ref();
return true;
}
void clearInternalTables()
{
// Assumes we're already locked somehow.
cacheAvail = pageTableSize();
// Setup page tables to point nowhere
PageTableEntry *table = pageTable();
for (uint i = 0; i < pageTableSize(); ++i) {
table[i].index = -1;
}
// Setup index tables to be accurate.
IndexTableEntry *indices = indexTable();
for (uint i = 0; i < indexTableSize(); ++i) {
indices[i].firstPage = -1;
indices[i].useCount = 0;
indices[i].fileNameHash = 0;
indices[i].totalItemSize = 0;
indices[i].addTime = 0;
indices[i].lastUsedTime = 0;
}
}
const IndexTableEntry *indexTable() const
{
// Index Table goes immediately after this struct, at the first byte
// where alignment constraints are met (accounted for by offsetAs).
return offsetAs<IndexTableEntry>(this, sizeof(*this));
}
const PageTableEntry *pageTable() const
{
const IndexTableEntry *base = indexTable();
base += indexTableSize();
// Let's call wherever we end up the start of the page table...
return alignTo<PageTableEntry>(base);
}
const void *cachePages() const
{
const PageTableEntry *tableStart = pageTable();
tableStart += pageTableSize();
// Let's call wherever we end up the start of the data...
return alignTo<void>(tableStart, cachePageSize());
}
const void *page(pageID at) const
{
if (static_cast<int>(at) >= static_cast<int>(pageTableSize())) {
return 0;
}
// We must manually calculate this one since pageSize varies.
const char *pageStart = reinterpret_cast<const char *>(cachePages());
pageStart += (at * cachePageSize());
return reinterpret_cast<const void *>(pageStart);
}
// The following are non-const versions of some of the methods defined
// above. They use const_cast<> because I feel that is better than
// duplicating the code. I suppose template member functions (?)
// may work, may investigate later.
IndexTableEntry *indexTable()
{
const SharedMemory *that = const_cast<const SharedMemory*>(this);
return const_cast<IndexTableEntry *>(that->indexTable());
}
PageTableEntry *pageTable()
{
const SharedMemory *that = const_cast<const SharedMemory*>(this);
return const_cast<PageTableEntry *>(that->pageTable());
}
void *cachePages()
{
const SharedMemory *that = const_cast<const SharedMemory*>(this);
return const_cast<void *>(that->cachePages());
}
void *page(pageID at)
{
const SharedMemory *that = const_cast<const SharedMemory*>(this);
return const_cast<void *>(that->page(at));
}
uint pageTableSize() const
{
return cacheSize / cachePageSize();
}
uint indexTableSize() const
{
// Assume 2 pages on average are needed -> the number of entries
// would be half of the number of pages.
return pageTableSize() / 2;
}
/**
* @return the index of the first page, for the set of contiguous
* pages that can hold @p pagesNeeded PAGES.
*/
pageID findEmptyPages(uint pagesNeeded) const
{
// Loop through the page table, find the first empty page, and just
// makes sure that there are enough free pages.
const PageTableEntry *table = pageTable();
uint contiguousPagesFound = 0;
pageID base = 0;
for (pageID i = 0; i < static_cast<int>(pageTableSize() - pagesNeeded + 1); ++i) {
if (table[i].index < 0) {
if (contiguousPagesFound == 0) {
base = i;
}
contiguousPagesFound++;
}
else {
contiguousPagesFound = 0;
}
if (contiguousPagesFound == pagesNeeded) {
return base;
}
}
return pageTableSize();
}
// left < right?
static bool lruCompare(const IndexTableEntry &l, const IndexTableEntry &r)
{
// Ensure invalid entries migrate to the end
if (l.firstPage < 0 && r.firstPage >= 0) {
return false;
}
if (l.firstPage >= 0 && r.firstPage < 0) {
return true;
}
// Most recently used will have the highest absolute time =>
// least recently used (lowest) should go first => use left < right
return l.lastUsedTime < r.lastUsedTime;
}
// left < right?
static bool seldomUsedCompare(const IndexTableEntry &l, const IndexTableEntry &r)
{
// Ensure invalid entries migrate to the end
if (l.firstPage < 0 && r.firstPage >= 0) {
return false;
}
if (l.firstPage >= 0 && r.firstPage < 0) {
return true;
}
// Put lowest use count at start by using left < right
return l.useCount < r.useCount;
}
// left < right?
static bool ageCompare(const IndexTableEntry &l, const IndexTableEntry &r)
{
// Ensure invalid entries migrate to the end
if (l.firstPage < 0 && r.firstPage >= 0) {
return false;
}
if (l.firstPage >= 0 && r.firstPage < 0) {
return true;
}
// Oldest entries die first -- they have the lowest absolute add time,
// so just like the others use left < right
return l.addTime < r.addTime;
}
void defragment()
{
if (cacheAvail * cachePageSize() == cacheSize) {
return; // That was easy
}
kDebug(ksdcArea()) << "Defragmenting the shared cache";
// Just do a linear scan, and anytime there is free space, swap it
// with the pages to its right. In order to meet the precondition
// we need to skip any used pages first.
pageID currentPage = 0;
pageID idLimit = static_cast<pageID>(pageTableSize());
PageTableEntry *pages = pageTable();
// Skip used pages
while (currentPage < idLimit && pages[currentPage].index >= 0) {
++currentPage;
}
pageID freeSpot = currentPage;
// Main loop, starting from a free page, skip to the used pages and
// move them back.
while (currentPage < idLimit) {
// Find the next used page
while (currentPage < idLimit && pages[currentPage].index < 0) {
++currentPage;
}
if (currentPage >= idLimit) {
break;
}
// Found an entry, move it.
qint32 affectedIndex = pages[currentPage].index;
Q_ASSERT(affectedIndex >= 0);
Q_ASSERT(indexTable()[affectedIndex].firstPage == currentPage);
indexTable()[affectedIndex].firstPage = freeSpot;
// Moving one page at a time guarantees we can use memcpy safely
// (in other words, the source and destination will not overlap).
while (currentPage < idLimit && pages[currentPage].index >= 0) {
::memcpy(page(freeSpot), page(currentPage), cachePageSize());
pages[freeSpot].index = affectedIndex;
pages[currentPage].index = -1;
++currentPage;
++freeSpot;
// If we've just moved the very last page and it happened to
// be at the very end of the cache then we're done.
if (currentPage >= idLimit) {
break;
}
// We're moving consecutive used pages whether they belong to
// our affected entry or not, so detect if we've started moving
// the data for a different entry and adjust if necessary.
if (affectedIndex != pages[currentPage].index) {
indexTable()[pages[currentPage].index].firstPage = freeSpot;
}
affectedIndex = pages[currentPage].index;
}
// At this point currentPage is on a page that is unused, and the
// cycle repeats. However, currentPage is not the first unused
// page, freeSpot is, so leave it alone.
}
}
/**
* Finds the index entry for a given key.
* @param key UTF-8 encoded key to search for.
* @return The index of the entry in the cache named by @p key. Returns
* <0 if no such entry is present.
*/
qint32 findNamedEntry(const QByteArray &key) const
{
uint keyHash = generateHash(key);
uint position = keyHash % indexTableSize();
int probeNumber = 1; // See insert() for description
while (indexTable()[position].fileNameHash != keyHash &&
indexTable()[position].useCount > 0 &&
probeNumber < 6)
{
position = (keyHash + (probeNumber + probeNumber * probeNumber) / 2)
% indexTableSize();
probeNumber++;
}
if (indexTable()[position].fileNameHash == keyHash) {
pageID firstPage = indexTable()[position].firstPage;
if (firstPage < 0 || static_cast<uint>(firstPage) >= pageTableSize()) {
return -1;
}
const void *resultPage = page(firstPage);
const char *utf8FileName = reinterpret_cast<const char *>(resultPage);
if (qstrncmp(utf8FileName, key.constData(), cachePageSize()) == 0) {
return position;
}
}
return -1; // Not found, or a different one found.
}
// Function to use with QSharedPointer in removeUsedPages below...
static void deleteTable(IndexTableEntry *table) {
delete [] table;
}
/**
* Removes the requested number of pages.
*
* @param numberNeeded the number of pages required to fulfill a current request.
* This number should be <0 and <= the number of pages in the cache.
* @return The identifier of the beginning of a consecutive block of pages able
* to fill the request. Returns a value >= pageTableSize() if no such
* request can be filled.
* @internal
*/
uint removeUsedPages(uint numberNeeded)
{
if (numberNeeded == 0) {
kError(ksdcArea()) << "Internal error: Asked to remove exactly 0 pages for some reason.";
return pageTableSize();
}
if (numberNeeded > pageTableSize()) {
kError(ksdcArea()) << "Internal error: Requested more space than exists in the cache.";
kError(ksdcArea()) << numberNeeded << "requested, " << pageTableSize() << "is the total possible.";
return pageTableSize();
}
// If the cache free space is large enough we will defragment first
// instead since it's likely we're highly fragmented.
// Otherwise, we will (eventually) simply remove entries per the
// eviction order set for the cache until there is enough room
// available to hold the number of pages we need.
kDebug(ksdcArea()) << "Removing old entries to free up" << numberNeeded << "pages,"
<< cacheAvail << "are already theoretically available.";
if (cacheAvail > 3 * numberNeeded) {
defragment();
uint result = findEmptyPages(numberNeeded);
if (result < pageTableSize()) {
return result;
}
else {
kError(ksdcArea()) << "Just defragmented a locked cache, but still there"
<< "isn't enough room for the current request.";
}
}
// At this point we know we'll have to free some space up, so sort our
// list of entries by whatever the current criteria are and start
// killing expired entries.
QSharedPointer<IndexTableEntry> tablePtr(new IndexTableEntry[indexTableSize()], deleteTable);
if (!tablePtr) {
kError(ksdcArea()) << "Unable to allocate temporary memory for sorting the cache!";
clearInternalTables();
return pageTableSize();
}
// We use tablePtr to ensure the data is destroyed, but do the access
// via a helper pointer to allow for array ops.
IndexTableEntry *table = tablePtr.data();
::memcpy(table, indexTable(), sizeof(IndexTableEntry) * indexTableSize());
// Our entry ID is simply its index into the
// index table, which qSort will rearrange all willy-nilly, so first
// we'll save the *real* entry ID into firstPage (which is useless in
// our copy of the index table). On the other hand if the entry is not
// used then we note that with -1.
for (uint i = 0; i < indexTableSize(); ++i) {
table[i].firstPage = table[i].useCount > 0 ? static_cast<pageID>(i)
: -1;
}
// Declare the comparison function that we'll use to pass to qSort,
// based on our cache eviction policy.
bool (*compareFunction)(const IndexTableEntry &, const IndexTableEntry &);
switch((int) evictionPolicy) {
case (int) KSharedDataCache::EvictLeastOftenUsed:
case (int) KSharedDataCache::NoEvictionPreference:
default:
compareFunction = seldomUsedCompare;
break;
case (int) KSharedDataCache::EvictLeastRecentlyUsed:
compareFunction = lruCompare;
break;
case (int) KSharedDataCache::EvictOldest:
compareFunction = ageCompare;
break;
}
qSort(table, table + indexTableSize(), compareFunction);
// Least recently used entries will be in the front.
// Start killing until we have room.
// Note on removeEntry: It expects an index into the index table,
// but our sorted list is all jumbled. But we stored the real index
// in the firstPage member.
// Remove entries until we've removed at least the required number
// of pages.
uint i = 0;
while (i < indexTableSize() && numberNeeded > cacheAvail) {
int curIndex = table[i++].firstPage; // Really an index, not a page
// Removed everything, still no luck. At *this* point,
// pagesRemoved < numberNeeded or in other words we can't fulfill
// the request even if we defragment. This is really a logic error.
if (curIndex < 0) {
kError(ksdcArea()) << "Unable to remove enough used pages to allocate"
<< numberNeeded << "pages. In theory the cache is empty, weird.";
return pageTableSize();
}
kDebug(ksdcArea()) << "Removing entry of" << indexTable()[curIndex].totalItemSize
<< "size";
removeEntry(curIndex);
}
// At this point let's see if we have freed up enough data by
// defragmenting first and seeing if we can find that free space.
defragment();
pageID result = pageTableSize();
while (i < indexTableSize() &&
(result = findEmptyPages(numberNeeded)) >= static_cast<int>(pageTableSize()))
{
int curIndex = table[i++].firstPage;
if (curIndex < 0) {
// One last shot.
defragment();
return findEmptyPages(numberNeeded);
}
removeEntry(curIndex);
}
// Whew.
return result;
}
// Returns the total size required for a given cache size.
static uint totalSize(uint cacheSize, uint effectivePageSize)
{
uint numberPages = intCeil(cacheSize, effectivePageSize);
uint indexTableSize = numberPages / 2;
// Knowing the number of pages, we can determine what addresses we'd be
// using (properly aligned), and from there determine how much memory
// we'd use.
IndexTableEntry *indexTableStart =
offsetAs<IndexTableEntry>(static_cast<void*>(0), sizeof (SharedMemory));
indexTableStart += indexTableSize;
PageTableEntry *pageTableStart = reinterpret_cast<PageTableEntry *>(indexTableStart);
pageTableStart = alignTo<PageTableEntry>(pageTableStart);
pageTableStart += numberPages;
// The weird part, we must manually adjust the pointer based on the page size.
char *cacheStart = reinterpret_cast<char *>(pageTableStart);
cacheStart += (numberPages * effectivePageSize);
// ALIGNOF gives pointer alignment
cacheStart = alignTo<char>(cacheStart, ALIGNOF(void*));
// We've traversed the header, index, page table, and cache.
// Wherever we're at now is the size of the enchilada.
return static_cast<uint>(reinterpret_cast<quintptr>(cacheStart));
}
uint fileNameHash(const QByteArray &utf8FileName) const
{
return generateHash(utf8FileName) % indexTableSize();
}
void clear()
{
clearInternalTables();
}
void removeEntry(uint index);
};
// The per-instance private data, such as map size, whether
// attached or not, pointer to shared memory, etc.
class KSharedDataCache::Private
{
public:
Private(const QString &name,
unsigned defaultCacheSize,
unsigned expectedItemSize
)
: m_cacheName(name)
, shm(0)
, m_lock(0)
, m_mapSize(0)
, m_defaultCacheSize(defaultCacheSize)
, m_expectedItemSize(expectedItemSize)
, m_expectedType(static_cast<SharedLockId>(0))
{
mapSharedMemory();
}
// Put the cache in a condition to be able to call mapSharedMemory() by
// completely detaching from shared memory (such as to respond to an
// unrecoverable error).
// m_mapSize must already be set to the amount of memory mapped to shm.
void detachFromSharedMemory()
{
// The lock holds a reference into shared memory, so this must be
// cleared before shm is removed.
m_lock.clear();
if (shm && !::munmap(shm, m_mapSize)) {
kError(ksdcArea()) << "Unable to unmap shared memory segment"
<< static_cast<void*>(shm);
}
shm = 0;
m_mapSize = 0;
}
// This function does a lot of the important work, attempting to connect to shared
// memory, a private anonymous mapping if that fails, and failing that, nothing (but
// the cache remains "valid", we just don't actually do anything).
void mapSharedMemory()
{
// 0-sized caches are fairly useless.
unsigned cacheSize = qMax(m_defaultCacheSize, uint(SharedMemory::MINIMUM_CACHE_SIZE));
unsigned pageSize = SharedMemory::equivalentPageSize(m_expectedItemSize);
// Ensure that the cache is sized such that there is a minimum number of
// pages available. (i.e. a cache consisting of only 1 page is fairly
// useless and probably crash-prone).
cacheSize = qMax(pageSize * 256, cacheSize);
// The m_cacheName is used to find the file to store the cache in.
QString cacheName = KGlobal::dirs()->locateLocal("cache", m_cacheName + QLatin1String(".kcache"));
QFile file(cacheName);
// The basic idea is to open the file that we want to map into shared
// memory, and then actually establish the mapping. Once we have mapped the
// file into shared memory we can close the file handle, the mapping will
// still be maintained (unless the file is resized to be shorter than
// expected, which we don't handle yet :-( )
// size accounts for the overhead over the desired cacheSize
uint size = SharedMemory::totalSize(cacheSize, pageSize);
void *mapAddress = MAP_FAILED;
if (size < cacheSize) {
kError(ksdcArea()) << "Asked for a cache size less than requested size somehow -- Logic Error :(";
return;
}
// We establish the shared memory mapping here, only if we will have appropriate
// mutex support (systemSupportsProcessSharing), then we:
// Open the file and resize to some sane value if the file is too small.
if (file.open(QIODevice::ReadWrite) &&
(file.size() >= size || file.resize(size)) &&
ensureFileAllocated(file.handle(), size))
{
// Use mmap directly instead of QFile::map since the QFile (and its
// shared mapping) will disappear unless we hang onto the QFile for no
// reason (see the note below, we don't care about the file per se...)
mapAddress = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, file.handle(), 0);
// So... it is possible that someone else has mapped this cache already
// with a larger size. If that's the case we need to at least match
// the size to be able to access every entry, so fixup the mapping.
if (mapAddress != MAP_FAILED) {
SharedMemory *mapped = reinterpret_cast<SharedMemory *>(mapAddress);
// First make sure that the version of the cache on disk is
// valid. We also need to check that version != 0 to
// disambiguate against an uninitialized cache.
if (mapped->version != SharedMemory::PIXMAP_CACHE_VERSION &&
mapped->version > 0)
{
kWarning(ksdcArea()) << "Deleting wrong version of cache" << cacheName;
// CAUTION: Potentially recursive since the recovery
// involves calling this function again.
m_mapSize = size;
shm = mapped;
recoverCorruptedCache();
return;
}
else if (mapped->cacheSize > cacheSize) {
// This order is very important. We must save the cache size
// before we remove the mapping, but unmap before overwriting
// the previous mapping size...
cacheSize = mapped->cacheSize;
unsigned actualPageSize = mapped->cachePageSize();
::munmap(mapAddress, size);
size = SharedMemory::totalSize(cacheSize, actualPageSize);
mapAddress = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, file.handle(), 0);
}
}
}
// We could be here without the mapping established if:
// 1) Process-shared synchronization is not supported, either at compile or run time,
// 2) Unable to open the required file.
// 3) Unable to resize the file to be large enough.
// 4) Establishing the mapping failed.
// 5) The mapping succeeded, but the size was wrong and we were unable to map when
// we tried again.
// 6) The incorrect version of the cache was detected.
// 7) The file could be created, but posix_fallocate failed to commit it fully to disk.
// In any of these cases, attempt to fallback to the
// better-supported anonymous private page style of mmap. This memory won't
// be shared, but our code will still work the same.
// NOTE: We never use the on-disk representation independently of the
// shared memory. If we don't get shared memory the disk info is ignored,
// if we do get shared memory we never look at disk again.
if (mapAddress == MAP_FAILED) {
kWarning(ksdcArea()) << "Failed to establish shared memory mapping, will fallback"
<< "to private memory -- memory usage will increase";
mapAddress = ::mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
}
// Well now we're really hosed. We can still work, but we can't even cache
// data.
if (mapAddress == MAP_FAILED) {
kError(ksdcArea()) << "Unable to allocate shared memory segment for shared data cache"
<< cacheName << "of size" << cacheSize;
return;
}
m_mapSize = size;
// We never actually construct shm, but we assign it the same address as the
// shared memory we just mapped, so effectively shm is now a SharedMemory that
// happens to be located at mapAddress.
shm = reinterpret_cast<SharedMemory *>(mapAddress);
// If we were first to create this memory map, all data will be 0.
// Therefore if ready == 0 we're not initialized. A fully initialized
// header will have ready == 2. Why?
// Because 0 means "safe to initialize"
// 1 means "in progress of initing"
// 2 means "ready"
uint usecSleepTime = 8; // Start by sleeping for 8 microseconds
while (shm->ready != 2) {
if (usecSleepTime >= (1 << 21)) {
// Didn't acquire within ~8 seconds? Assume an issue exists
kError(ksdcArea()) << "Unable to acquire shared lock, is the cache corrupt?";
file.remove(); // Unlink the cache in case it's corrupt.
detachFromSharedMemory();
return; // Fallback to QCache (later)
}
if (shm->ready.testAndSetAcquire(0, 1)) {
if (!shm->performInitialSetup(cacheSize, pageSize)) {
kError(ksdcArea()) << "Unable to perform initial setup, this system probably "
"does not really support process-shared pthreads or "
"semaphores, even though it claims otherwise.";
file.remove();
detachFromSharedMemory();
return;
}
}
else {
usleep(usecSleepTime); // spin
// Exponential fallback as in Ethernet and similar collision resolution methods
usecSleepTime *= 2;
}
}
m_expectedType = shm->shmLock.type;
m_lock = QSharedPointer<KSDCLock>(createLockFromId(m_expectedType, shm->shmLock));
bool isProcessSharingSupported = false;
if (!m_lock->initialize(isProcessSharingSupported)) {
kError(ksdcArea()) << "Unable to setup shared cache lock, although it worked when created.";
detachFromSharedMemory();
}
}
// Called whenever the cache is apparently corrupt (for instance, a timeout trying to
// lock the cache). In this situation it is safer just to destroy it all and try again.
void recoverCorruptedCache()
{
KSharedDataCache::deleteCache(m_cacheName);
detachFromSharedMemory();
// Do this even if we weren't previously cached -- it might work now.
mapSharedMemory();
}
bool lock() const
{
if (KDE_ISLIKELY(shm && shm->shmLock.type == m_expectedType)) {
return m_lock->lock();
}
return false;
}
void unlock() const
{
m_lock->unlock();
}
class CacheLocker
{
mutable Private * d;
bool cautiousLock()
{
int lockCount = 0;
// Locking can fail due to a timeout. If it happens too often even though
// we're taking corrective action assume there's some disastrous problem
// and give up.
while (!d->lock()) {
d->recoverCorruptedCache();
if (!d->shm) {
kWarning(ksdcArea()) << "Lost the connection to shared memory for cache"
<< d->m_cacheName;
return false;
}
if (lockCount++ > 4) {
kError(ksdcArea()) << "There is a very serious problem with the KDE data cache"
<< d->m_cacheName << "giving up trying to access cache.";
d->detachFromSharedMemory();
return false;
}
}
return true;
}
public:
CacheLocker(const Private *_d) : d(const_cast<Private *>(_d))
{
if (d->shm) {
if (!cautiousLock()) {
return;
}
uint testSize = SharedMemory::totalSize(d->shm->cacheSize, d->shm->cachePageSize());
// A while loop? Indeed, think what happens if this happens
// twice -- hard to debug race conditions.
while (testSize > d->m_mapSize) {
kDebug(ksdcArea()) << "Someone enlarged the cache on us,"
<< "attempting to match new configuration.";
// Protect against two threads accessing this same KSDC
// from trying to execute the following remapping at the
// same time.
QMutexLocker d_locker(&d->m_threadLock);
if (testSize == d->m_mapSize) {
break; // Bail if the other thread already solved.
}
// Linux supports mremap, but it's not portable. So,
// drop the map and (try to) re-establish.
d->unlock();
#ifdef KSDC_MSYNC_SUPPORTED
::msync(d->shm, d->m_mapSize, MS_INVALIDATE | MS_ASYNC);
#endif
::munmap(d->shm, d->m_mapSize);
d->m_mapSize = 0;
d->shm = 0;
QFile f(d->m_cacheName);
if (!f.open(QFile::ReadWrite)) {
kError(ksdcArea()) << "Unable to re-open cache, unfortunately"
<< "the connection had to be dropped for"
<< "crash safety -- things will be much"
<< "slower now.";
return;
}
void *newMap = ::mmap(0, testSize, PROT_READ | PROT_WRITE,
MAP_SHARED, f.handle(), 0);
if (newMap == MAP_FAILED) {
kError(ksdcArea()) << "Unopen to re-map the cache into memory"
<< "things will be much slower now";
return;
}
d->shm = reinterpret_cast<SharedMemory *>(newMap);
d->m_mapSize = testSize;
if (!cautiousLock()) {
return;
}
testSize = SharedMemory::totalSize(d->shm->cacheSize, d->shm->cachePageSize());
}
}
}
~CacheLocker()
{
if (d->shm) {
d->unlock();
}
}
bool failed() const
{
return d->shm == 0;
}
};
QString m_cacheName;
QMutex m_threadLock;
SharedMemory *shm;
QSharedPointer<KSDCLock> m_lock;
uint m_mapSize;
uint m_defaultCacheSize;
uint m_expectedItemSize;
SharedLockId m_expectedType;
};
// Must be called while the lock is already held!
void SharedMemory::removeEntry(uint index)
{
Q_ASSERT(index < indexTableSize());
Q_ASSERT(cacheAvail <= pageTableSize());
PageTableEntry *pageTableEntries = pageTable();
IndexTableEntry *entriesIndex = indexTable();
if (entriesIndex[index].firstPage < 0) {
kDebug(ksdcArea()) << "Trying to remove an entry which is already invalid. This "
<< "cache is likely corrupt.";
clearInternalTables(); // The nuclear option...
return;
}
// Update page table first
pageID firstPage = entriesIndex[index].firstPage;
if (firstPage < 0 || static_cast<quint32>(firstPage) >= pageTableSize()) {
kError(ksdcArea()) << "Removing" << index << "which is already marked as empty!";
clearInternalTables();
return;
}
if (index != static_cast<uint>(pageTableEntries[firstPage].index)) {
kError(ksdcArea()) << "Removing" << index << "will not work as it is assigned"
<< "to page" << firstPage << "which is itself assigned to"
<< "entry" << pageTableEntries[firstPage].index << "instead!";
clearInternalTables();
return;
}
uint entriesToRemove = intCeil(entriesIndex[index].totalItemSize, cachePageSize());
uint savedCacheSize = cacheAvail;
for (uint i = firstPage; i < pageTableSize() &&
(uint) pageTableEntries[i].index == index; ++i)
{
pageTableEntries[i].index = -1;
cacheAvail++;
}
if ((cacheAvail - savedCacheSize) != entriesToRemove) {
kError(ksdcArea()) << "We somehow did not remove" << entriesToRemove
<< "when removing entry" << index << ", instead we removed"
<< (cacheAvail - savedCacheSize);
}
// For debugging
#ifdef NDEBUG
QByteArray str((const char *)page(firstPage));
str.prepend(" REMOVED: ");
str.prepend(QByteArray::number(index));
str.prepend("ENTRY ");
::memcpy(page(firstPage), str.constData(), str.size() + 1);
#endif
// Update the index
entriesIndex[index].fileNameHash = 0;
entriesIndex[index].totalItemSize = 0;
entriesIndex[index].useCount = 0;
entriesIndex[index].lastUsedTime = 0;
entriesIndex[index].addTime = 0;
entriesIndex[index].firstPage = -1;
}
KSharedDataCache::KSharedDataCache(const QString &cacheName,
unsigned defaultCacheSize,
unsigned expectedItemSize)
: d(new Private(cacheName, defaultCacheSize, expectedItemSize))
{
}
KSharedDataCache::~KSharedDataCache()
{
// Note that there is no other actions required to separate from the
// shared memory segment, simply unmapping is enough. This makes things
// *much* easier so I'd recommend maintaining this ideal.
if (d->shm) {
#ifdef KSDC_MSYNC_SUPPORTED
::msync(d->shm, d->m_mapSize, MS_INVALIDATE | MS_ASYNC);
#endif
::munmap(d->shm, d->m_mapSize);
}
// Do not delete d->shm, it was never constructed, it's just an alias.
d->shm = 0;
delete d;
}
bool KSharedDataCache::insert(const QString &key, const QByteArray &data)
{
Private::CacheLocker lock(d);
if (lock.failed()) {
return false;
}
QByteArray encodedKey = key.toUtf8();
uint keyHash = generateHash(encodedKey);
uint position = keyHash % d->shm->indexTableSize();
// See if we're overwriting an existing entry.
IndexTableEntry *indices = d->shm->indexTable();
// In order to avoid the issue of a very long-lived cache having items
// with a use count of 1 near-permanently, we attempt to artifically
// reduce the use count of long-lived items when there is high load on
// the cache. We do this randomly, with a weighting that makes the event
// impossible if load < 0.5, and guaranteed if load >= 0.96.
static double startCullPoint = 0.5l;
static double mustCullPoint = 0.96l;
// cacheAvail is in pages, cacheSize is in bytes.
- double loadFactor = (1.0l * d->shm->cacheAvail * d->shm->cachePageSize()
+ double loadFactor = 1.0 - (1.0l * d->shm->cacheAvail * d->shm->cachePageSize()
/ d->shm->cacheSize);
bool cullCollisions = false;
if (KDE_ISUNLIKELY(loadFactor >= mustCullPoint)) {
cullCollisions = true;
}
- else {
- int tripWireValue = RAND_MAX * (loadFactor - startCullPoint) / (mustCullPoint - startCullPoint);
+ else if (loadFactor > startCullPoint) {
+ const int tripWireValue = RAND_MAX * (loadFactor - startCullPoint) / (mustCullPoint - startCullPoint);
if (KRandom::random() >= tripWireValue) {
cullCollisions = true;
}
}
// In case of collisions, use quadratic chaining to attempt to find an
// empty slot. The equation we use is
// position = (hash + (i + i*i) / 2) % size, where i is the probe number.
int probeNumber = 1;
while (indices[position].useCount > 0 && probeNumber < 6) {
// If we are "culling" old entries, see if this one is old and if so
// reduce its use count. If it reduces to zero then eliminate it and
// use its old spot.
if (cullCollisions && (::time(0) - indices[position].lastUsedTime) > 60) {
indices[position].useCount >>= 1;
if (indices[position].useCount == 0) {
kDebug(ksdcArea()) << "Overwriting existing old cached entry due to collision.";
d->shm->removeEntry(position); // Remove it first
break;
}
}
position = (keyHash + (probeNumber + probeNumber * probeNumber) / 2)
% d->shm->indexTableSize();
probeNumber++;
}
if (indices[position].useCount > 0 && indices[position].firstPage >= 0) {
kDebug(ksdcArea()) << "Overwriting existing cached entry due to collision.";
d->shm->removeEntry(position); // Remove it first
}
// Data will be stored as fileNamefoo\0PNGimagedata.....
// So total size required is the length of the encoded file name + 1
// for the trailing null, and then the length of the image data.
uint fileNameLength = 1 + encodedKey.length();
uint requiredSize = fileNameLength + data.size();
uint pagesNeeded = intCeil(requiredSize, d->shm->cachePageSize());
uint firstPage = (uint) -1;
if (pagesNeeded >= d->shm->pageTableSize()) {
kWarning(ksdcArea()) << key << "is too large to be cached.";
return false;
}
// If the cache has no room, or the fragmentation is too great to find
// the required number of consecutive free pages, take action.
if (pagesNeeded > d->shm->cacheAvail ||
(firstPage = d->shm->findEmptyPages(pagesNeeded)) >= d->shm->pageTableSize())
{
// If we have enough free space just defragment
uint freePagesDesired = 3 * qMax(1u, pagesNeeded / 2);
if (d->shm->cacheAvail > freePagesDesired) {
// TODO: How the hell long does this actually take on real
// caches?
d->shm->defragment();
firstPage = d->shm->findEmptyPages(pagesNeeded);
}
else {
// If we already have free pages we don't want to remove a ton
// extra. However we can't rely on the return value of
// removeUsedPages giving us a good location since we're not
// passing in the actual number of pages that we need.
d->shm->removeUsedPages(qMin(2 * freePagesDesired, d->shm->pageTableSize())
- d->shm->cacheAvail);
firstPage = d->shm->findEmptyPages(pagesNeeded);
}
if (firstPage >= d->shm->pageTableSize() ||
d->shm->cacheAvail < pagesNeeded)
{
kError(ksdcArea()) << "Unable to free up memory for" << key;
return false;
}
}
// Update page table
PageTableEntry *table = d->shm->pageTable();
for (uint i = 0; i < pagesNeeded; ++i) {
table[firstPage + i].index = position;
}
// Update index
indices[position].fileNameHash = keyHash;
indices[position].totalItemSize = requiredSize;
indices[position].useCount = 1;
indices[position].addTime = ::time(0);
indices[position].lastUsedTime = indices[position].addTime;
indices[position].firstPage = firstPage;
// Update cache
d->shm->cacheAvail -= pagesNeeded;
// Actually move the data in place
void *dataPage = d->shm->page(firstPage);
// Cast for byte-sized pointer arithmetic
uchar *startOfPageData = reinterpret_cast<uchar *>(dataPage);
::memcpy(startOfPageData, encodedKey.constData(), fileNameLength);
::memcpy(startOfPageData + fileNameLength, data.constData(), data.size());
return true;
}
bool KSharedDataCache::find(const QString &key, QByteArray *destination) const
{
if (!d->shm) {
return false;
}
Private::CacheLocker lock(d);
if (lock.failed()) {
return false;
}
// Search in the index for our data, hashed by key;
QByteArray encodedKey = key.toUtf8();
qint32 entry = d->shm->findNamedEntry(encodedKey);
if (entry >= 0) {
const IndexTableEntry *header = &d->shm->indexTable()[entry];
const void *resultPage = d->shm->page(header->firstPage);
header->useCount++;
header->lastUsedTime = ::time(0);
// Our item is the key followed immediately by the data, so skip
// past the key.
const char *cacheData = reinterpret_cast<const char *>(resultPage);
cacheData += encodedKey.size();
cacheData++; // Skip trailing null -- now we're pointing to start of data
if (destination) {
*destination = QByteArray(cacheData, header->totalItemSize - encodedKey.size() - 1);
}
return true;
}
return false;
}
void KSharedDataCache::clear()
{
Private::CacheLocker lock(d);
if(!lock.failed()) {
d->shm->clear();
}
}
bool KSharedDataCache::contains(const QString &key) const
{
Private::CacheLocker lock(d);
if (lock.failed()) {
return false;
}
return d->shm->findNamedEntry(key.toUtf8()) >= 0;
}
void KSharedDataCache::deleteCache(const QString &cacheName)
{
QString cachePath = KGlobal::dirs()->locateLocal("cache", cacheName + QLatin1String(".kcache"));
// Note that it is important to simply unlink the file, and not truncate it
// smaller first to avoid SIGBUS errors and similar with shared memory
// attached to the underlying inode.
kDebug(ksdcArea()) << "Removing cache at" << cachePath;
QFile::remove(cachePath);
}
unsigned KSharedDataCache::totalSize() const
{
Private::CacheLocker lock(d);
if (lock.failed()) {
return 0u;
}
return d->shm->cacheSize;
}
unsigned KSharedDataCache::freeSize() const
{
Private::CacheLocker lock(d);
if (lock.failed()) {
return 0u;
}
return d->shm->cacheAvail * d->shm->cachePageSize();
}
KSharedDataCache::EvictionPolicy KSharedDataCache::evictionPolicy() const
{
if (d->shm) {
return static_cast<EvictionPolicy>(d->shm->evictionPolicy.fetchAndAddAcquire(0));
}
return NoEvictionPreference;
}
void KSharedDataCache::setEvictionPolicy(EvictionPolicy newPolicy)
{
if (d->shm) {
d->shm->evictionPolicy.fetchAndStoreRelease(static_cast<int>(newPolicy));
}
}
unsigned KSharedDataCache::timestamp() const
{
if (d->shm) {
return static_cast<unsigned>(d->shm->cacheTimestamp.fetchAndAddAcquire(0));
}
return 0;
}
void KSharedDataCache::setTimestamp(unsigned newTimestamp)
{
if (d->shm) {
d->shm->cacheTimestamp.fetchAndStoreRelease(static_cast<int>(newTimestamp));
}
}
diff --git a/kdeui/dialogs/kconfigdialog.cpp b/kdeui/dialogs/kconfigdialog.cpp
index e815e54bc1..12ca3f2cf8 100644
--- a/kdeui/dialogs/kconfigdialog.cpp
+++ b/kdeui/dialogs/kconfigdialog.cpp
@@ -1,322 +1,331 @@
/*
* This file is part of the KDE libraries
* Copyright (C) 2003 Benjamin C Meyer (ben+kdelibs at meyerhome dot net)
* Copyright (C) 2003 Waldo Bastian <bastian@kde.org>
* Copyright (C) 2004 Michael Brade <brade@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kconfigdialog.h"
#include <kcomponentdata.h>
#include <kconfigdialogmanager.h>
#include <kconfigskeleton.h>
#include <kdebug.h>
#include <kicon.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kpagewidgetmodel.h>
#include <kvbox.h>
#include <QtGui/QLayout>
#include <QtCore/QMap>
class KConfigDialog::KConfigDialogPrivate
{
public:
- KConfigDialogPrivate(KConfigDialog *q)
- : q(q), shown(false), manager(0) { }
+ KConfigDialogPrivate(KConfigDialog *q, const QString& name, KCoreConfigSkeleton *config)
+ : q(q), shown(false), manager(0)
+ {
+ q->setCaption( i18n("Configure") );
+ q->setFaceType( List );
+ q->setButtons( Default|Ok|Apply|Cancel|Help );
+ q->setHelp( QString(), KGlobal::mainComponent().componentName() );
+ q->setDefaultButton( Ok );
+ q->setObjectName( name );
+
+ if ( !name.isEmpty() ) {
+ openDialogs.insert(name, q);
+ } else {
+ QString genericName;
+ genericName.sprintf("SettingsDialog-%p", static_cast<void*>(q));
+ openDialogs.insert(genericName, q);
+ q->setObjectName(genericName);
+ }
+
+ connect(q, SIGNAL(okClicked()), q, SLOT(updateSettings()));
+ connect(q, SIGNAL(applyClicked()), q, SLOT(updateSettings()));
+ connect(q, SIGNAL(applyClicked()), q, SLOT(_k_updateButtons()));
+ connect(q, SIGNAL(cancelClicked()), q, SLOT(updateWidgets()));
+ connect(q, SIGNAL(defaultClicked()), q, SLOT(updateWidgetsDefault()));
+ connect(q, SIGNAL(defaultClicked()), q, SLOT(_k_updateButtons()));
+ connect(q, SIGNAL(pageRemoved(KPageWidgetItem*)), q, SLOT(onPageRemoved(KPageWidgetItem*)));
+
+ manager = new KConfigDialogManager(q, config);
+ setupManagerConnections(manager);
+
+ q->enableButton(Apply, false);
+ }
KPageWidgetItem* addPageInternal(QWidget *page, const QString &itemName,
const QString &pixmapName, const QString &header);
void setupManagerConnections(KConfigDialogManager *manager);
void _k_updateButtons();
void _k_settingsChangedSlot();
KConfigDialog *q;
bool shown;
KConfigDialogManager *manager;
QMap<QWidget *, KConfigDialogManager *> managerForPage;
/**
* The list of existing dialogs.
*/
static QHash<QString,KConfigDialog *> openDialogs;
};
QHash<QString,KConfigDialog *> KConfigDialog::KConfigDialogPrivate::openDialogs;
KConfigDialog::KConfigDialog( QWidget *parent, const QString& name,
KConfigSkeleton *config ) :
KPageDialog( parent ),
- d(new KConfigDialogPrivate(this))
+ d(new KConfigDialogPrivate(this, name, config))
{
- setCaption( i18n("Configure") );
- setFaceType( List );
- setButtons( Default|Ok|Apply|Cancel|Help );
- setHelp( QString(), KGlobal::mainComponent().componentName() );
- setDefaultButton( Ok );
- setObjectName( name );
-
- if ( !name.isEmpty() ) {
- KConfigDialogPrivate::openDialogs.insert(name, this);
- } else {
- QString genericName;
- genericName.sprintf("SettingsDialog-%p", static_cast<void*>(this));
- KConfigDialogPrivate::openDialogs.insert(genericName, this);
- setObjectName(genericName);
- }
-
- connect(this, SIGNAL(okClicked()), this, SLOT(updateSettings()));
- connect(this, SIGNAL(applyClicked()), this, SLOT(updateSettings()));
- connect(this, SIGNAL(applyClicked()), this, SLOT(_k_updateButtons()));
- connect(this, SIGNAL(cancelClicked()), this, SLOT(updateWidgets()));
- connect(this, SIGNAL(defaultClicked()), this, SLOT(updateWidgetsDefault()));
- connect(this, SIGNAL(defaultClicked()), this, SLOT(_k_updateButtons()));
- connect(this, SIGNAL(pageRemoved(KPageWidgetItem*)), this, SLOT(onPageRemoved(KPageWidgetItem*)));
-
- d->manager = new KConfigDialogManager(this, config);
- d->setupManagerConnections(d->manager);
+}
- enableButton(Apply, false);
+KConfigDialog::KConfigDialog( QWidget *parent, const QString& name,
+ KCoreConfigSkeleton *config ) :
+ KPageDialog( parent ),
+ d(new KConfigDialogPrivate(this, name, config))
+{
}
KConfigDialog::~KConfigDialog()
{
KConfigDialogPrivate::openDialogs.remove(objectName());
delete d;
}
KPageWidgetItem* KConfigDialog::addPage(QWidget *page,
const QString &itemName,
const QString &pixmapName,
const QString &header,
bool manage)
{
Q_ASSERT(page);
if (!page) {
return 0;
}
KPageWidgetItem* item = d->addPageInternal(page, itemName, pixmapName, header);
if (manage) {
d->manager->addWidget(page);
}
if (d->shown && manage) {
// update the default button if the dialog is shown
bool is_default = isButtonEnabled(Default) && d->manager->isDefault();
enableButton(Default,!is_default);
}
return item;
}
KPageWidgetItem* KConfigDialog::addPage(QWidget *page,
KConfigSkeleton *config,
const QString &itemName,
const QString &pixmapName,
const QString &header)
{
Q_ASSERT(page);
if (!page) {
return 0;
}
KPageWidgetItem* item = d->addPageInternal(page, itemName, pixmapName, header);
d->managerForPage[page] = new KConfigDialogManager(page, config);
d->setupManagerConnections(d->managerForPage[page]);
if (d->shown)
{
// update the default button if the dialog is shown
bool is_default = isButtonEnabled(Default) && d->managerForPage[page]->isDefault();
enableButton(Default,!is_default);
}
return item;
}
KPageWidgetItem* KConfigDialog::KConfigDialogPrivate::addPageInternal(QWidget *page,
const QString &itemName,
const QString &pixmapName,
const QString &header)
{
KVBox *frame = new KVBox(q);
frame->setSpacing(-1);
page->setParent(frame);
KPageWidgetItem *item = new KPageWidgetItem( frame, itemName );
item->setHeader( header );
if ( !pixmapName.isEmpty() )
item->setIcon( KIcon( pixmapName ) );
q->KPageDialog::addPage( item );
return item;
}
void KConfigDialog::KConfigDialogPrivate::setupManagerConnections(KConfigDialogManager *manager)
{
q->connect(manager, SIGNAL(settingsChanged()), q, SLOT(_k_settingsChangedSlot()));
q->connect(manager, SIGNAL(widgetModified()), q, SLOT(_k_updateButtons()));
q->connect(q, SIGNAL(okClicked()), manager, SLOT(updateSettings()));
q->connect(q, SIGNAL(applyClicked()), manager, SLOT(updateSettings()));
q->connect(q, SIGNAL(cancelClicked()), manager, SLOT(updateWidgets()));
q->connect(q, SIGNAL(defaultClicked()), manager, SLOT(updateWidgetsDefault()));
}
void KConfigDialog::onPageRemoved( KPageWidgetItem *item )
{
QMap<QWidget *, KConfigDialogManager *>::iterator j = d->managerForPage.begin();
while (j != d->managerForPage.end())
{
// there is a manager for this page, so remove it
if (item->widget()->isAncestorOf(j.key()))
{
KConfigDialogManager* manager = j.value();
d->managerForPage.erase(j);
delete manager;
d->_k_updateButtons();
break;
}
j++;
}
}
KConfigDialog* KConfigDialog::exists(const QString& name)
{
QHash<QString,KConfigDialog *>::const_iterator it = KConfigDialogPrivate::openDialogs.constFind( name );
if ( it != KConfigDialogPrivate::openDialogs.constEnd() )
return *it;
return 0;
}
bool KConfigDialog::showDialog(const QString& name)
{
KConfigDialog *dialog = exists(name);
if(dialog)
dialog->show();
return (dialog != NULL);
}
void KConfigDialog::KConfigDialogPrivate::_k_updateButtons()
{
static bool only_once = false;
if (only_once) return;
only_once = true;
QMap<QWidget *, KConfigDialogManager *>::iterator it;
bool has_changed = manager->hasChanged() || q->hasChanged();
for (it = managerForPage.begin();
it != managerForPage.end() && !has_changed;
++it)
{
has_changed |= (*it)->hasChanged();
}
q->enableButton(KDialog::Apply, has_changed);
bool is_default = manager->isDefault() && q->isDefault();
for (it = managerForPage.begin();
it != managerForPage.end() && is_default;
++it)
{
is_default &= (*it)->isDefault();
}
q->enableButton(KDialog::Default, !is_default);
emit q->widgetModified();
only_once = false;
}
void KConfigDialog::KConfigDialogPrivate::_k_settingsChangedSlot()
{
// Update the buttons
_k_updateButtons();
emit q->settingsChanged(q->objectName());
}
void KConfigDialog::showEvent(QShowEvent *e)
{
if (!d->shown)
{
QMap<QWidget *, KConfigDialogManager *>::iterator it;
updateWidgets();
d->manager->updateWidgets();
for (it = d->managerForPage.begin(); it != d->managerForPage.end(); ++it)
(*it)->updateWidgets();
bool has_changed = d->manager->hasChanged() || hasChanged();
for (it = d->managerForPage.begin();
it != d->managerForPage.end() && !has_changed;
++it)
{
has_changed |= (*it)->hasChanged();
}
enableButton(Apply, has_changed);
bool is_default = d->manager->isDefault() && isDefault();
for (it = d->managerForPage.begin();
it != d->managerForPage.end() && is_default;
++it)
{
is_default &= (*it)->isDefault();
}
enableButton(Default, !is_default);
d->shown = true;
}
KPageDialog::showEvent(e);
}
void KConfigDialog::updateSettings()
{
}
void KConfigDialog::updateWidgets()
{
}
void KConfigDialog::updateWidgetsDefault()
{
}
bool KConfigDialog::hasChanged()
{
return false;
}
bool KConfigDialog::isDefault()
{
return true;
}
void KConfigDialog::updateButtons()
{
d->_k_updateButtons();
}
void KConfigDialog::settingsChangedSlot()
{
d->_k_settingsChangedSlot();
}
#include "kconfigdialog.moc"
diff --git a/kdeui/dialogs/kconfigdialog.h b/kdeui/dialogs/kconfigdialog.h
index 2ac0edaedf..76eb4874b8 100644
--- a/kdeui/dialogs/kconfigdialog.h
+++ b/kdeui/dialogs/kconfigdialog.h
@@ -1,253 +1,271 @@
/*
* This file is part of the KDE libraries
* Copyright (C) 2003 Benjamin C Meyer (ben+kdelibs at meyerhome dot net)
* Copyright (C) 2003 Waldo Bastian <bastian@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef KCONFIGDIALOG_H
#define KCONFIGDIALOG_H
#include <kpagedialog.h>
class KConfig;
+class KCoreConfigSkeleton;
class KConfigSkeleton;
class KConfigDialogManager;
/**
* \short Standard %KDE configuration dialog class
*
* The KConfigDialog class provides an easy and uniform means of displaying
* a settings dialog using KPageDialog, KConfigDialogManager and a
* KConfigSkeleton derived settings class.
*
* KConfigDialog handles the enabling and disabling of buttons, creation
* of the dialog, and deletion of the widgets. Because of
* KConfigDialogManager, this class also manages: restoring
* the settings, reseting them to the default values, and saving them. This
* requires that the names of the widgets corresponding to configuration entries
* have to have the same name plus an additional "kcfg_" prefix. For example the
* widget named "kcfg_MyOption" would be associated with the configuration entry
* "MyOption".
*
* Here is an example usage of KConfigDialog:
*
* \code
* void KCoolApp::showSettings(){
* if(KConfigDialog::showDialog("settings"))
* return;
* KConfigDialog *dialog = new KConfigDialog(this, "settings", MySettings::self());
* dialog->setFaceType(KPageDialog::List);
* dialog->addPage(new General(0, "General"), i18n("General") );
* dialog->addPage(new Appearance(0, "Style"), i18n("Appearance") );
* connect(dialog, SIGNAL(settingsChanged(const QString&)), mainWidget, SLOT(loadSettings()));
* connect(dialog, SIGNAL(settingsChanged(const QString&)), this, SLOT(loadSettings()));
* dialog->show();
* }
* \endcode
*
* Other than the above code, each class that has settings in the dialog should
* have a loadSettings() type slot to read settings and perform any
* necessary changes.
*
* For dialog appearance options (like buttons, default button, ...) please see
* @see KPageDialog
*
* @see KConfigSkeleton
* @author Waldo Bastian <bastian@kde.org>
*/
class KDEUI_EXPORT KConfigDialog : public KPageDialog {
Q_OBJECT
Q_SIGNALS:
/**
* A widget in the dialog was modified.
*/
void widgetModified();
/**
* One or more of the settings have been permanently changed such as if
* the user clicked on the Apply or Ok button.
* @param dialogName the name of the dialog.
*/
void settingsChanged(const QString& dialogName);
public:
/**
* @param parent - The parent of this object. Even though the class
* deletes itself the parent should be set so the dialog can be centered
* with the application on the screen.
*
* @param name - The name of this object. The name is used in determining if
* there can be more than one dialog at a time. Use names such as:
* "Font Settings" or "Color Settings" and not just "Settings" in
* applications where there is more than one dialog.
*
* @param config - Config object containing settings.
*/
KConfigDialog( QWidget *parent, const QString& name,
KConfigSkeleton *config );
+ /**
+ * @since 4.8.1
+ *
+ * @param parent - The parent of this object. Even though the class
+ * deletes itself the parent should be set so the dialog can be centered
+ * with the application on the screen.
+ *
+ * @param name - The name of this object. The name is used in determining if
+ * there can be more than one dialog at a time. Use names such as:
+ * "Font Settings" or "Color Settings" and not just "Settings" in
+ * applications where there is more than one dialog.
+ *
+ * @param config - Config object containing settings.
+ */
+ KConfigDialog( QWidget *parent, const QString& name,
+ KCoreConfigSkeleton *config );
+
/**
* Deconstructor, removes name from the list of open dialogs.
* Deletes private class.
* @see exists()
*/
~KConfigDialog();
/**
* Adds page to the dialog and to KConfigDialogManager. When an
* application is done adding pages show() should be called to
* display the dialog.
* @param page - Pointer to the page that is to be added to the dialog.
* This object is reparented.
* @param itemName - Name of the page.
* @param pixmapName - Name of the icon that should be used, if needed, when
* displaying the page. The string may either be the name of a themed
* icon (e.g. "document-save"), which the internal icon loader will be
* used to retrieve, or an absolute path to the pixmap on disk.
* @param header - Header text use in the list modes. Ignored in Tabbed
* mode. If empty, the itemName text is used when needed.
* @param manage - Whether KConfigDialogManager should manage the page or not.
* @returns The KPageWidgetItem associated with the page.
*/
KPageWidgetItem* addPage( QWidget *page, const QString &itemName,
const QString &pixmapName=QString(),
const QString &header=QString(),
bool manage=true );
/**
* Adds page to the dialog that is managed by a custom KConfigDialogManager.
* This is useful for dialogs that contain settings spread over more than
* one configuration file and thus have/need more than one KConfigSkeleton.
* When an application is done adding pages show() should be called to
* display the dialog.
* @param page - Pointer to the page that is to be added to the dialog.
* This object is reparented.
* @param config - Config object containing corresponding settings.
* @param itemName - Name of the page.
* @param pixmapName - Name of the icon that should be used, if needed, when
* displaying the page. The string may either be the name of a themed
* icon (e.g. "document-save"), which the internal icon loader will be
* used to retrieve, or an absolute path to the pixmap on disk.
* @param header - Header text use in the list modes. Ignored in Tabbed
* mode. If empty, the itemName text is used when needed.
* @returns The KPageWidgetItem associated with the page.
*/
KPageWidgetItem* addPage( QWidget *page, KConfigSkeleton *config,
const QString &itemName,
const QString &pixmapName=QString(),
const QString &header=QString() );
/**
* See if a dialog with the name 'name' already exists.
* @see showDialog()
* @param name - Dialog name to look for.
* @return Pointer to widget or NULL if it does not exist.
*/
static KConfigDialog* exists( const QString& name );
/**
* Attempts to show the dialog with the name 'name'.
* @see exists()
* @param name - The name of the dialog to show.
* @return True if the dialog 'name' exists and was shown.
*/
static bool showDialog( const QString& name );
protected Q_SLOTS:
/**
* Update the settings from the dialog.
* Virtual function for custom additions.
*
* Example use: User clicks Ok or Apply button in a configure dialog.
*/
virtual void updateSettings();
/**
* Update the dialog based on the settings.
* Virtual function for custom additions.
*
* Example use: Initialisation of dialog.
* Example use: User clicks Reset button in a configure dialog.
*/
virtual void updateWidgets();
/**
* Update the dialog based on the default settings.
* Virtual function for custom additions.
*
* Example use: User clicks Defaults button in a configure dialog.
*/
virtual void updateWidgetsDefault();
/**
* Updates the Apply and Default buttons.
* Connect to this slot if you implement you own hasChanged()
* or isDefault() methods for widgets not managed by KConfig.
* @since 4.3
*/
void updateButtons();
/**
* Some setting was changed. Emit the signal with the dialogs name.
* Connect to this slot if there are widgets not managed by KConfig.
* @since 4.3
*/
void settingsChangedSlot();
protected:
/**
* Returns whether the current state of the dialog is
* different from the current configuration.
* Virtual function for custom additions.
*/
virtual bool hasChanged();
/**
* Returns whether the current state of the dialog is
* the same as the default configuration.
*/
virtual bool isDefault();
/**
* @internal
*/
virtual void showEvent(QShowEvent *e);
private Q_SLOTS:
/**
* Slot which cleans up the KConfigDialogManager of the page.
* */
void onPageRemoved(KPageWidgetItem* item);
private:
class KConfigDialogPrivate;
friend class KConfigDialogPrivate;
KConfigDialogPrivate *const d;
Q_PRIVATE_SLOT( d, void _k_updateButtons() )
Q_PRIVATE_SLOT( d, void _k_settingsChangedSlot() )
Q_DISABLE_COPY(KConfigDialog)
};
#endif //KCONFIGDIALOG_H
diff --git a/kdeui/dialogs/kconfigdialogmanager.cpp b/kdeui/dialogs/kconfigdialogmanager.cpp
index 18bc44e866..dfa793d078 100644
--- a/kdeui/dialogs/kconfigdialogmanager.cpp
+++ b/kdeui/dialogs/kconfigdialogmanager.cpp
@@ -1,536 +1,548 @@
/*
* This file is part of the KDE libraries
* Copyright (C) 2003 Benjamin C Meyer (ben+kdelibs at meyerhome dot net)
* Copyright (C) 2003 Waldo Bastian <bastian@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "kconfigdialogmanager.h"
#include <QComboBox>
#include <QGroupBox>
#include <QLabel>
#include <QMetaObject>
#include <QMetaProperty>
#include <QTimer>
#include <QRadioButton>
//#include <QButtonGroup>
#include <kconfigskeleton.h>
#include <kdebug.h>
#include <kglobal.h>
#include <assert.h>
typedef QHash<QString, QByteArray> MyHash;
K_GLOBAL_STATIC(MyHash, s_propertyMap)
K_GLOBAL_STATIC(MyHash, s_changedMap)
class KConfigDialogManager::Private {
public:
Private(KConfigDialogManager *q) : q(q), insideGroupBox(false) { }
public:
KConfigDialogManager *q;
static int debugArea() { static int s_area = KDebug::registerArea("kdeui (KConfigDialogManager)"); return s_area; }
/**
* KConfigSkeleton object used to store settings
*/
KCoreConfigSkeleton *m_conf;
/**
* Dialog being managed
*/
QWidget *m_dialog;
QHash<QString, QWidget *> knownWidget;
QHash<QString, QWidget *> buddyWidget;
bool insideGroupBox : 1;
bool trackChanges : 1;
};
KConfigDialogManager::KConfigDialogManager(QWidget *parent, KCoreConfigSkeleton *conf)
: QObject(parent), d(new Private(this))
{
d->m_conf = conf;
d->m_dialog = parent;
init(true);
}
KConfigDialogManager::KConfigDialogManager(QWidget *parent, KConfigSkeleton *conf)
: QObject(parent), d(new Private(this))
{
d->m_conf = conf;
d->m_dialog = parent;
init(true);
}
KConfigDialogManager::~KConfigDialogManager()
{
delete d;
}
void KConfigDialogManager::initMaps()
{
if ( s_propertyMap->isEmpty() ) {
s_propertyMap->insert( "KButtonGroup", "current" );
s_propertyMap->insert( "KColorButton", "color" );
s_propertyMap->insert( "KColorCombo", "color" );
//s_propertyMap->insert( "KUrlRequester", "url" );
//s_propertyMap->insert( "KUrlComboRequester", "url" );
}
if( s_changedMap->isEmpty() )
{
// QT
s_changedMap->insert("QCheckBox", SIGNAL(stateChanged(int)));
s_changedMap->insert("QPushButton", SIGNAL(clicked(bool)));
s_changedMap->insert("QRadioButton", SIGNAL(toggled(bool)));
// We can only store one thing, so you can't have
// a ButtonGroup that is checkable.
// s_changedMap->insert("QButtonGroup", SIGNAL(buttonClicked(int)));
s_changedMap->insert("QGroupBox", SIGNAL(toggled(bool)));
s_changedMap->insert("QComboBox", SIGNAL(activated(int)));
//qsqlproperty map doesn't store the text, but the value!
//s_changedMap->insert("QComboBox", SIGNAL(textChanged(QString)));
s_changedMap->insert("QDateEdit", SIGNAL(dateChanged(QDate)));
s_changedMap->insert("QTimeEdit", SIGNAL(timeChanged(QTime)));
s_changedMap->insert("QDateTimeEdit", SIGNAL(dateTimeChanged(QDateTime)));
s_changedMap->insert("QDial", SIGNAL(valueChanged(int)));
s_changedMap->insert("QDoubleSpinBox", SIGNAL(valueChanged(double)));
s_changedMap->insert("QLineEdit", SIGNAL(textChanged(QString)));
s_changedMap->insert("QSlider", SIGNAL(valueChanged(int)));
s_changedMap->insert("QSpinBox", SIGNAL(valueChanged(int)));
s_changedMap->insert("QTextEdit", SIGNAL(textChanged()));
s_changedMap->insert("QTextBrowser", SIGNAL(sourceChanged(QString)));
s_changedMap->insert("QPlainTextEdit", SIGNAL(textChanged()));
s_changedMap->insert("QTabWidget", SIGNAL(currentChanged(int)));
// KDE
s_changedMap->insert( "KComboBox", SIGNAL(activated(int)));
s_changedMap->insert( "KFontComboBox", SIGNAL(activated(int)));
s_changedMap->insert( "KFontRequester", SIGNAL(fontSelected(QFont)));
s_changedMap->insert( "KFontChooser", SIGNAL(fontSelected(QFont)));
s_changedMap->insert( "KHistoryCombo", SIGNAL(activated(int)));
s_changedMap->insert( "KColorCombo", SIGNAL(activated(QColor)));
s_changedMap->insert( "KColorButton", SIGNAL(changed(QColor)));
s_changedMap->insert( "KDatePicker", SIGNAL(dateSelected(QDate)));
s_changedMap->insert( "KDateWidget", SIGNAL(changed(QDate)));
s_changedMap->insert( "KDateTimeWidget", SIGNAL(valueChanged(QDateTime)));
s_changedMap->insert( "KEditListBox", SIGNAL(changed()));
s_changedMap->insert( "KEditListWidget", SIGNAL(changed()));
s_changedMap->insert( "KListWidget", SIGNAL(itemSelectionChanged()));
s_changedMap->insert( "KLineEdit", SIGNAL(textChanged(QString)));
s_changedMap->insert( "KPasswordEdit", SIGNAL(textChanged(QString)));
s_changedMap->insert( "KRestrictedLine", SIGNAL(textChanged(QString)));
s_changedMap->insert( "KTextBrowser", SIGNAL(sourceChanged(QString)));
s_changedMap->insert( "KTextEdit", SIGNAL(textChanged()));
s_changedMap->insert( "KUrlRequester", SIGNAL(textChanged(QString)));
s_changedMap->insert( "KUrlComboRequester", SIGNAL(textChanged(QString)));
s_changedMap->insert( "KUrlComboBox", SIGNAL(urlActivated(KUrl)));
s_changedMap->insert( "KIntNumInput", SIGNAL(valueChanged(int)));
s_changedMap->insert( "KIntSpinBox", SIGNAL(valueChanged(int)));
s_changedMap->insert( "KDoubleNumInput", SIGNAL(valueChanged(double)));
s_changedMap->insert( "KButtonGroup", SIGNAL(changed(int)));
}
}
QHash<QString, QByteArray> *KConfigDialogManager::propertyMap()
{
initMaps();
return s_propertyMap;
}
QHash<QString, QByteArray> *KConfigDialogManager::changedMap()
{
initMaps();
return s_changedMap;
}
void KConfigDialogManager::init(bool trackChanges)
{
initMaps();
d->trackChanges = trackChanges;
// Go through all of the children of the widgets and find all known widgets
(void) parseChildren(d->m_dialog, trackChanges);
}
void KConfigDialogManager::addWidget(QWidget *widget)
{
(void) parseChildren(widget, true);
}
void KConfigDialogManager::setupWidget(QWidget *widget, KConfigSkeletonItem *item)
{
QVariant minValue = item->minValue();
if (minValue.isValid())
{
// Only q3datetimeedit is using this property we can remove it if we stop supporting Qt3Support
if (widget->metaObject()->indexOfProperty("minValue") != -1)
widget->setProperty("minValue", minValue);
if (widget->metaObject()->indexOfProperty("minimum") != -1)
widget->setProperty("minimum", minValue);
}
QVariant maxValue = item->maxValue();
if (maxValue.isValid())
{
// Only q3datetimeedit is using that property we can remove it if we stop supporting Qt3Support
if (widget->metaObject()->indexOfProperty("maxValue") != -1)
widget->setProperty("maxValue", maxValue);
if (widget->metaObject()->indexOfProperty("maximum") != -1)
widget->setProperty("maximum", maxValue);
}
if (widget->whatsThis().isEmpty())
{
QString whatsThis = item->whatsThis();
if ( !whatsThis.isEmpty() )
{
widget->setWhatsThis(whatsThis );
}
}
if (widget->toolTip().isEmpty())
{
QString toolTip = item->toolTip();
if ( !toolTip.isEmpty() )
{
widget->setToolTip(toolTip);
}
}
if(!item->isEqual( property(widget) ))
setProperty( widget, item->property() );
}
bool KConfigDialogManager::parseChildren(const QWidget *widget, bool trackChanges)
{
bool valueChanged = false;
const QList<QObject*> listOfChildren = widget->children();
if(listOfChildren.count()==0) //?? XXX
return valueChanged;
foreach ( QObject *object, listOfChildren )
{
if(!object->isWidgetType())
continue; // Skip non-widgets
QWidget *childWidget = static_cast<QWidget *>(object);
QString widgetName = childWidget->objectName();
bool bParseChildren = true;
bool bSaveInsideGroupBox = d->insideGroupBox;
if (widgetName.startsWith(QLatin1String("kcfg_")))
{
// This is one of our widgets!
QString configId = widgetName.mid(5);
KConfigSkeletonItem *item = d->m_conf->findItem(configId);
if (item)
{
d->knownWidget.insert(configId, childWidget);
setupWidget(childWidget, item);
if ( d->trackChanges ) {
QHash<QString, QByteArray>::const_iterator changedIt = s_changedMap->constFind(childWidget->metaObject()->className());
if (changedIt == s_changedMap->constEnd())
{
// If the class name of the widget wasn't in the monitored widgets map, then look for
// it again using the super class name. This fixes a problem with using QtRuby/Korundum
// widgets with KConfigXT where 'Qt::Widget' wasn't being seen a the real deal, even
// though it was a 'QWidget'.
if ( childWidget->metaObject()->superClass() )
changedIt = s_changedMap->constFind(childWidget->metaObject()->superClass()->className());
else
changedIt = s_changedMap->constFind(0);
}
if (changedIt == s_changedMap->constEnd())
{
kWarning(d->debugArea()) << "Don't know how to monitor widget '" << childWidget->metaObject()->className() << "' for changes!";
}
else
{
connect(childWidget, *changedIt,
this, SIGNAL(widgetModified()));
QComboBox *cb = qobject_cast<QComboBox *>(childWidget);
if (cb && cb->isEditable())
connect(cb, SIGNAL(editTextChanged(QString)),
this, SIGNAL(widgetModified()));
}
}
QGroupBox *gb = qobject_cast<QGroupBox *>(childWidget);
if (!gb)
bParseChildren = false;
else
d->insideGroupBox = true;
}
else
{
kFatal(d->debugArea()) << "A widget named '" << widgetName << "' was found but there is no setting named '" << configId << "'";
}
}
else if (QLabel *label = qobject_cast<QLabel*>(childWidget))
{
QWidget *buddy = label->buddy();
if (!buddy)
continue;
QString buddyName = buddy->objectName();
if (buddyName.startsWith(QLatin1String("kcfg_")))
{
// This is one of our widgets!
QString configId = buddyName.mid(5);
d->buddyWidget.insert(configId, childWidget);
}
}
#ifndef NDEBUG
else if (!widgetName.isEmpty() && d->trackChanges)
{
QHash<QString, QByteArray>::const_iterator changedIt = s_changedMap->constFind(childWidget->metaObject()->className());
if (changedIt != s_changedMap->constEnd())
{
if ((!d->insideGroupBox || !qobject_cast<QRadioButton*>(childWidget)) &&
!qobject_cast<QGroupBox*>(childWidget) &&!qobject_cast<QTabWidget*>(childWidget) )
kDebug(d->debugArea()) << "Widget '" << widgetName << "' (" << childWidget->metaObject()->className() << ") remains unmanaged.";
}
}
#endif
if(bParseChildren)
{
// this widget is not known as something we can store.
// Maybe we can store one of its children.
valueChanged |= parseChildren(childWidget, trackChanges);
}
d->insideGroupBox = bSaveInsideGroupBox;
}
return valueChanged;
}
void KConfigDialogManager::updateWidgets()
{
bool changed = false;
bool bSignalsBlocked = signalsBlocked();
blockSignals(true);
QWidget *widget;
QHashIterator<QString, QWidget *> it( d->knownWidget );
while(it.hasNext()) {
it.next();
widget = it.value();
KConfigSkeletonItem *item = d->m_conf->findItem(it.key());
if (!item)
{
kWarning(d->debugArea()) << "The setting '" << it.key() << "' has disappeared!";
continue;
}
if(!item->isEqual( property(widget) ))
{
setProperty( widget, item->property() );
// kDebug(d->debugArea()) << "The setting '" << it.key() << "' [" << widget->className() << "] has changed";
changed = true;
}
if (item->isImmutable())
{
widget->setEnabled(false);
QWidget *buddy = d->buddyWidget.value(it.key(), 0);
if (buddy)
buddy->setEnabled(false);
}
}
blockSignals(bSignalsBlocked);
if (changed)
QTimer::singleShot(0, this, SIGNAL(widgetModified()));
}
void KConfigDialogManager::updateWidgetsDefault()
{
bool bUseDefaults = d->m_conf->useDefaults(true);
updateWidgets();
d->m_conf->useDefaults(bUseDefaults);
}
void KConfigDialogManager::updateSettings()
{
bool changed = false;
QWidget *widget;
QHashIterator<QString, QWidget *> it( d->knownWidget );
while(it.hasNext()) {
it.next();
widget = it.value();
KConfigSkeletonItem *item = d->m_conf->findItem(it.key());
if (!item) {
kWarning(d->debugArea()) << "The setting '" << it.key() << "' has disappeared!";
continue;
}
QVariant fromWidget = property(widget);
if(!item->isEqual( fromWidget )) {
item->setProperty( fromWidget );
changed = true;
}
}
if (changed)
{
d->m_conf->writeConfig();
emit settingsChanged();
}
}
QByteArray KConfigDialogManager::getUserProperty(const QWidget *widget) const
{
if (!s_propertyMap->contains(widget->metaObject()->className())) {
const QMetaObject *metaObject = widget->metaObject();
const QMetaProperty user = metaObject->userProperty();
if ( user.isValid() ) {
s_propertyMap->insert( widget->metaObject()->className(), user.name() );
//kDebug(d->debugArea()) << "class name: '" << widget->metaObject()->className()
//<< " 's USER property: " << metaProperty.name() << endl;
}
else {
return QByteArray(); //no USER property
}
}
+ const QComboBox *cb = qobject_cast<const QComboBox *>(widget);
+ if (cb) {
+ const char *qcomboUserPropertyName = cb->QComboBox::metaObject()->userProperty().name();
+ const int qcomboUserPropertyIndex = qcomboUserPropertyName ? cb->QComboBox::metaObject()->indexOfProperty(qcomboUserPropertyName) : -1;
+ const char *widgetUserPropertyName = widget->metaObject()->userProperty().name();
+ const int widgetUserPropertyIndex = widgetUserPropertyName ? cb->metaObject()->indexOfProperty(widgetUserPropertyName) : -1;
+
+ if (qcomboUserPropertyIndex == widgetUserPropertyIndex) {
+ return QByteArray(); // use the q/kcombobox special code
+ }
+ }
+
return s_propertyMap->value( widget->metaObject()->className() );
}
QByteArray KConfigDialogManager::getCustomProperty(const QWidget *widget) const
{
QVariant prop(widget->property("kcfg_property"));
if (prop.isValid()) {
if (!prop.canConvert(QVariant::ByteArray)) {
kWarning(d->debugArea()) << "kcfg_property on" << widget->metaObject()->className()
<< "is not of type ByteArray";
} else {
return prop.toByteArray();
}
}
return QByteArray();
}
void KConfigDialogManager::setProperty(QWidget *w, const QVariant &v)
{
/* QButtonGroup *bg = qobject_cast<QButtonGroup *>(w);
if (bg)
{
QAbstractButton *b = bg->button(v.toInt());
if (b)
b->setDown(true);
return;
}*/
QByteArray userproperty = getCustomProperty(w);
+ if (userproperty.isEmpty()) {
+ userproperty = getUserProperty(w);
+ }
if (userproperty.isEmpty()) {
QComboBox *cb = qobject_cast<QComboBox *>(w);
if (cb) {
if (cb->isEditable()) {
int i = cb->findText(v.toString());
if (i != -1) {
cb->setCurrentIndex(i);
} else {
cb->setEditText(v.toString());
}
} else {
cb->setCurrentIndex(v.toInt());
}
return;
}
}
- if (userproperty.isEmpty()) {
- userproperty = getUserProperty(w);
- }
if (userproperty.isEmpty()) {
kWarning(d->debugArea()) << w->metaObject()->className() << " widget not handled!";
return;
}
w->setProperty(userproperty, v);
}
QVariant KConfigDialogManager::property(QWidget *w) const
{
/* QButtonGroup *bg = qobject_cast<QButtonGroup *>(w);
if (bg && bg->checkedButton())
return QVariant(bg->id(bg->checkedButton()));*/
QByteArray userproperty = getCustomProperty(w);
+ if (userproperty.isEmpty()) {
+ userproperty = getUserProperty(w);
+ }
if (userproperty.isEmpty()) {
QComboBox *cb = qobject_cast<QComboBox *>(w);
if (cb) {
if (cb->isEditable()) {
return QVariant(cb->currentText());
} else {
return QVariant(cb->currentIndex());
}
}
}
- if (userproperty.isEmpty()) {
- userproperty = getUserProperty(w);
- }
if (userproperty.isEmpty()) {
kWarning(d->debugArea()) << w->metaObject()->className() << " widget not handled!";
return QVariant();
}
return w->property(userproperty);
}
bool KConfigDialogManager::hasChanged() const
{
QWidget *widget;
QHashIterator<QString, QWidget *> it( d->knownWidget) ;
while(it.hasNext()) {
it.next();
widget = it.value();
KConfigSkeletonItem *item = d->m_conf->findItem(it.key());
if (!item) {
kWarning(d->debugArea()) << "The setting '" << it.key() << "' has disappeared!";
continue;
}
if(!item->isEqual( property(widget) )) {
// kDebug(d->debugArea()) << "Widget for '" << it.key() << "' has changed.";
return true;
}
}
return false;
}
bool KConfigDialogManager::isDefault() const
{
bool bUseDefaults = d->m_conf->useDefaults(true);
bool result = !hasChanged();
d->m_conf->useDefaults(bUseDefaults);
return result;
}
#include "kconfigdialogmanager.moc"
diff --git a/kdeui/plotting/kplotobject.cpp b/kdeui/plotting/kplotobject.cpp
index fa8afb7535..57573060ee 100644
--- a/kdeui/plotting/kplotobject.cpp
+++ b/kdeui/plotting/kplotobject.cpp
@@ -1,391 +1,391 @@
/* -*- C++ -*-
This file is part of the KDE libraries
Copyright (C) 2003 Jason Harris <kstars@30doradus.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kplotobject.h"
#include <QtAlgorithms>
#include <QPainter>
#include <kdebug.h>
#include "kplotpoint.h"
#include "kplotwidget.h"
class KPlotObject::Private
{
public:
Private( KPlotObject * qq )
: q( qq )
{
}
~Private()
{
qDeleteAll( pList );
}
KPlotObject *q;
QList<KPlotPoint*> pList;
PlotTypes type;
PointStyle pointStyle;
double size;
QPen pen, linePen, barPen, labelPen;
QBrush brush, barBrush;
};
KPlotObject::KPlotObject( const QColor &c, PlotType t, double size, PointStyle ps )
: d( new Private( this ) )
{
//By default, all pens and brushes are set to the given color
setBrush( c );
setBarBrush( c );
setPen( QPen( brush(), 1 ) );
setLinePen( pen() );
setBarPen( pen() );
setLabelPen( pen() );
d->type |= t;
setSize( size );
setPointStyle( ps );
}
KPlotObject::~KPlotObject()
{
delete d;
}
KPlotObject::PlotTypes KPlotObject::plotTypes() const
{
return d->type;
}
void KPlotObject::setShowPoints( bool b )
{
if ( b )
{
d->type |= KPlotObject::Points;
}
else
{
d->type &= ~KPlotObject::Points;
}
}
void KPlotObject::setShowLines( bool b )
{
if ( b )
{
d->type |= KPlotObject::Lines;
}
else
{
d->type &= ~KPlotObject::Lines;
}
}
void KPlotObject::setShowBars( bool b )
{
if ( b )
{
d->type |= KPlotObject::Bars;
}
else
{
d->type &= ~KPlotObject::Bars;
}
}
double KPlotObject::size() const
{
return d->size;
}
void KPlotObject::setSize( double s )
{
d->size = s;
}
KPlotObject::PointStyle KPlotObject::pointStyle() const
{
return d->pointStyle;
}
void KPlotObject::setPointStyle( PointStyle p )
{
d->pointStyle = p;
}
const QPen& KPlotObject::pen() const
{
return d->pen;
}
void KPlotObject::setPen( const QPen &p )
{
d->pen = p;
}
const QPen& KPlotObject::linePen() const
{
return d->linePen;
}
void KPlotObject::setLinePen( const QPen &p )
{
d->linePen = p;
}
const QPen& KPlotObject::barPen() const
{
return d->barPen;
}
void KPlotObject::setBarPen( const QPen &p )
{
d->barPen = p;
}
const QPen& KPlotObject::labelPen() const
{
return d->labelPen;
}
void KPlotObject::setLabelPen( const QPen &p )
{
d->labelPen = p;
}
const QBrush KPlotObject::brush() const
{
return d->brush;
}
void KPlotObject::setBrush( const QBrush &b )
{
d->brush = b;
}
const QBrush KPlotObject::barBrush() const
{
return d->barBrush;
}
void KPlotObject::setBarBrush( const QBrush &b )
{
d->barBrush = b;
}
QList< KPlotPoint* > KPlotObject::points() const
{
return d->pList;
}
void KPlotObject::addPoint( const QPointF &p, const QString &label, double barWidth )
{
addPoint( new KPlotPoint( p.x(), p.y(), label, barWidth ) );
}
void KPlotObject::addPoint( KPlotPoint *p )
{
if ( !p )
return;
d->pList.append( p );
}
void KPlotObject::addPoint( double x, double y, const QString &label, double barWidth )
{
addPoint( new KPlotPoint( x, y, label, barWidth ) );
}
void KPlotObject::removePoint( int index ) {
if ( ( index < 0 ) || ( index >= d->pList.count() ) ) {
kWarning() << "KPlotObject::removePoint(): index " << index << " out of range!";
return;
}
d->pList.removeAt( index );
}
void KPlotObject::clearPoints()
{
qDeleteAll( d->pList );
d->pList.clear();
}
void KPlotObject::draw( QPainter *painter, KPlotWidget *pw ) {
//Order of drawing determines z-distance: Bars in the back, then lines,
//then points, then labels.
if ( d->type & Bars ) {
painter->setPen( barPen() );
painter->setBrush( barBrush() );
+ double w = 0;
for ( int i=0; i<d->pList.size(); ++i ) {
- double w = 0;
if ( d->pList[i]->barWidth() == 0.0 ) {
if ( i<d->pList.size()-1 )
w = d->pList[i+1]->x() - d->pList[i]->x();
//For the last bin, we'll just keep the previous width
} else {
w = d->pList[i]->barWidth();
}
QPointF pp = d->pList[i]->position();
QPointF p1( pp.x() - 0.5*w, 0.0 );
QPointF p2( pp.x() + 0.5*w, pp.y() );
QPointF sp1 = pw->mapToWidget( p1 );
QPointF sp2 = pw->mapToWidget( p2 );
QRectF barRect = QRectF( sp1.x(), sp1.y(), sp2.x()-sp1.x(), sp2.y()-sp1.y() ).normalized();
painter->drawRect( barRect );
pw->maskRect( barRect, 0.25 );
}
}
//Draw lines:
if ( d->type & Lines ) {
painter->setPen( linePen() );
QPointF Previous = QPointF(); //Initialize to null
foreach ( KPlotPoint *pp, d->pList ) {
//q is the position of the point in screen pixel coordinates
QPointF q = pw->mapToWidget( pp->position() );
if ( ! Previous.isNull() ) {
painter->drawLine( Previous, q );
pw->maskAlongLine( Previous, q );
}
Previous = q;
}
}
//Draw points:
if ( d->type & Points ) {
foreach( KPlotPoint *pp, d->pList ) {
//q is the position of the point in screen pixel coordinates
QPointF q = pw->mapToWidget( pp->position() );
if ( pw->pixRect().contains( q.toPoint(), false ) ) {
double x1 = q.x() - size();
double y1 = q.y() - size();
QRectF qr = QRectF( x1, y1, 2*size(), 2*size() );
//Mask out this rect in the plot for label avoidance
pw->maskRect( qr, 2.0 );
painter->setPen( pen() );
painter->setBrush( brush() );
switch ( pointStyle() ) {
case Circle:
painter->drawEllipse( qr );
break;
case Letter:
painter->drawText( qr, Qt::AlignCenter, pp->label().left(1) );
break;
case Triangle:
{
QPolygonF tri;
tri << QPointF( q.x() - size(), q.y() + size() )
<< QPointF( q.x(), q.y() - size() )
<< QPointF( q.x() + size(), q.y() + size() );
painter->drawPolygon( tri );
break;
}
case Square:
painter->drawRect( qr );
break;
case Pentagon:
{
QPolygonF pent;
pent << QPointF( q.x(), q.y() - size() )
<< QPointF( q.x() + size(), q.y() - 0.309*size() )
<< QPointF( q.x() + 0.588*size(), q.y() + size() )
<< QPointF( q.x() - 0.588*size(), q.y() + size() )
<< QPointF( q.x() - size(), q.y() - 0.309*size() );
painter->drawPolygon( pent );
break;
}
case Hexagon:
{
QPolygonF hex;
hex << QPointF( q.x(), q.y() + size() )
<< QPointF( q.x() + size(), q.y() + 0.5*size() )
<< QPointF( q.x() + size(), q.y() - 0.5*size() )
<< QPointF( q.x(), q.y() - size() )
<< QPointF( q.x() - size(), q.y() + 0.5*size() )
<< QPointF( q.x() - size(), q.y() - 0.5*size() );
painter->drawPolygon( hex );
break;
}
case Asterisk:
painter->drawLine( q, QPointF( q.x(), q.y() + size() ) );
painter->drawLine( q, QPointF( q.x() + size(), q.y() + 0.5*size() ) );
painter->drawLine( q, QPointF( q.x() + size(), q.y() - 0.5*size() ) );
painter->drawLine( q, QPointF( q.x(), q.y() - size() ) );
painter->drawLine( q, QPointF( q.x() - size(), q.y() + 0.5*size() ) );
painter->drawLine( q, QPointF( q.x() - size(), q.y() - 0.5*size() ) );
break;
case Star:
{
QPolygonF star;
star << QPointF( q.x(), q.y() - size() )
<< QPointF( q.x() + 0.2245*size(), q.y() - 0.309*size() )
<< QPointF( q.x() + size(), q.y() - 0.309*size() )
<< QPointF( q.x() + 0.363*size(), q.y() + 0.118*size() )
<< QPointF( q.x() + 0.588*size(), q.y() + size() )
<< QPointF( q.x(), q.y() + 0.382*size() )
<< QPointF( q.x() - 0.588*size(), q.y() + size() )
<< QPointF( q.x() - 0.363*size(), q.y() + 0.118*size() )
<< QPointF( q.x() - size(), q.y() - 0.309*size() )
<< QPointF( q.x() - 0.2245*size(), q.y() - 0.309*size() );
painter->drawPolygon( star );
break;
}
default:
break;
}
}
}
}
//Draw labels
painter->setPen( labelPen() );
foreach ( KPlotPoint *pp, d->pList ) {
QPoint q = pw->mapToWidget( pp->position() ).toPoint();
if ( pw->pixRect().contains(q, false) && ! pp->label().isEmpty() ) {
pw->placeLabel( painter, pp );
}
}
}
diff --git a/kdeui/sonnet/configdialog.cpp b/kdeui/sonnet/configdialog.cpp
index a63f2acd67..ea953c8fb1 100644
--- a/kdeui/sonnet/configdialog.cpp
+++ b/kdeui/sonnet/configdialog.cpp
@@ -1,94 +1,102 @@
/**
* configdialog.cpp
*
* Copyright (C) 2004 Zack Rusin <zack@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#include "configdialog.h"
#include "configwidget.h"
#include <klocale.h>
#include <kvbox.h>
using namespace Sonnet;
class ConfigDialog::Private
{
public:
Private( ConfigDialog *parent )
: q( parent ) {}
ConfigWidget *ui;
ConfigDialog *q;
void slotConfigChanged();
};
void ConfigDialog::Private::slotConfigChanged()
{
emit q->languageChanged( ui->language() );
}
ConfigDialog::ConfigDialog(KConfig *config, QWidget *parent)
: KDialog(parent),
d(new Private(this))
{
setObjectName( "SonnetConfigDialog" );
setModal( true );
setCaption( i18n( "Spell Checking Configuration" ) );
setButtons( Help | Ok /*| Apply*/ | Cancel );
setDefaultButton( Ok );
init(config);
}
ConfigDialog::~ConfigDialog()
{
delete d;
}
void ConfigDialog::init(KConfig *config)
{
d->ui = new ConfigWidget(config, this);
setMainWidget(d->ui);
setHelp(QString(),"kcontrol/spellchecking");
connect(this, SIGNAL(okClicked()),
this, SLOT(slotOk()));
/*
connect(this, SIGNAL(applyClicked()),
this, SLOT(slotApply()));
*/
connect(d->ui, SIGNAL(configChanged()),
this, SLOT(slotConfigChanged()));
+
+ connect(d->ui, SIGNAL(configChanged()),
+ this, SIGNAL(configChanged()));
}
void ConfigDialog::slotOk()
{
d->ui->save();
accept();
}
void ConfigDialog::slotApply()
{
d->ui->save();
}
void ConfigDialog::setLanguage( const QString &language )
{
d->ui->setLanguage( language );
}
+QString ConfigDialog::language() const
+{
+ return d->ui->language();
+}
+
#include "configdialog.moc"
diff --git a/kdeui/sonnet/configdialog.h b/kdeui/sonnet/configdialog.h
index 547717d32c..0aefdf4fc5 100644
--- a/kdeui/sonnet/configdialog.h
+++ b/kdeui/sonnet/configdialog.h
@@ -1,74 +1,85 @@
/*
* configdialog.h
*
* Copyright (C) 2004 Zack Rusin <zack@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA
*/
#ifndef SONNET_CONFIGDIALOG_H
#define SONNET_CONFIGDIALOG_H
#include <kdialog.h>
#include <kconfig.h>
namespace Sonnet
{
/// The sonnet ConfigDialog
class KDEUI_EXPORT ConfigDialog : public KDialog
{
Q_OBJECT
public:
ConfigDialog(KConfig *config,
QWidget *parent);
~ConfigDialog();
/**
* Sets the language/dictionary that will be selected by default
* in this config dialog.
* This overrides the setting in the config file.
*
* @param language the language which will be selected by default.
* @since 4.1
*/
void setLanguage( const QString &language );
-
+ /**
+ * return selected language
+ * @since 4.8.1
+ */
+ QString language() const;
+
protected Q_SLOTS:
virtual void slotOk();
virtual void slotApply();
Q_SIGNALS:
/**
- * This is emitted when the user closed the dialog (and did not
- * cancel it).
+ * This is emitted all the time when we change config and not just language
*
* @param language the language which the user has selected
* @since 4.1
*/
+
void languageChanged( const QString &language );
+ /**
+ * This is emitted when configChanged
+ * @since 4.8.1
+ */
+ void configChanged();
+
private:
void init(KConfig *config);
private:
class Private;
friend class Private;
Private *const d;
Q_DISABLE_COPY(ConfigDialog)
Q_PRIVATE_SLOT(d, void slotConfigChanged())
};
}
#endif
diff --git a/kdeui/tests/CMakeLists.txt b/kdeui/tests/CMakeLists.txt
index 63788f65cf..c8b8c854c2 100644
--- a/kdeui/tests/CMakeLists.txt
+++ b/kdeui/tests/CMakeLists.txt
@@ -1,184 +1,185 @@
set( EXECUTABLE_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR} )
add_subdirectory(kconfig_compiler)
include_directories( ${KDE4_KDEUI_INCLUDES} proxymodeltestsuite )
# QtTest needs this in order to include the gui stuff.
# The alternative (which requires Qt>=4.6) is #include <QTestGui>
add_definitions(-DQT_GUI_LIB)
MACRO(KDEUI_UNIT_TESTS)
FOREACH(_testname ${ARGN})
kde4_add_unit_test(${_testname} TESTNAME kdeui-${_testname} ${_testname}.cpp)
target_link_libraries(${_testname} ${KDE4_KDEUI_LIBS} ${QT_QTTEST_LIBRARY} ${QT_QTXML_LIBRARY} ${KDEWIN_LIBRARIES})
ENDFOREACH(_testname)
ENDMACRO(KDEUI_UNIT_TESTS)
MACRO(KDEUI_EXECUTABLE_TESTS)
FOREACH(_testname ${ARGN})
kde4_add_executable(${_testname} TEST ${_testname}.cpp)
target_link_libraries(${_testname} ${KDE4_KDEUI_LIBS} ${QT_QTTEST_LIBRARY} ${QT_QTXML_LIBRARY} ${KDEWIN_LIBRARIES})
ENDFOREACH(_testname)
ENDMACRO(KDEUI_EXECUTABLE_TESTS)
SET(proxyModelTestSources
kselectionproxymodeltestsuite.cpp
)
add_subdirectory(proxymodeltestsuite)
MACRO(KDEUI_PROXYMODEL_TESTS)
FOREACH(_testname ${ARGN})
kde4_add_unit_test(${_testname} TESTNAME kdeui-${_testname} ${_testname}.cpp ${proxyModelTestSources} )
target_link_libraries(${_testname} ${KDE4_KDEUI_LIBS} ${QT_QTTEST_LIBRARY} ${QT_QTXML_LIBRARY} ${KDEWIN32_LIBRARIES} proxymodeltestsuite )
ENDFOREACH(_testname)
ENDMACRO(KDEUI_PROXYMODEL_TESTS)
KDEUI_UNIT_TESTS(
kactioncollectiontest
kactioncategorytest
kbuttongrouptest
kcompletioncoretest
kconfigguitest
kconfigskeletontest
kdualactiontest
kfadewidgeteffecttest
kfindtest
kglobalsettingstest
kglobalshortcuttest
kmainwindow_unittest
klineedit_unittest
ktextedit_unittest
kcombobox_unittest
kdialog_unittest
kreplacetest
kshortcuttest
kstandardactiontest
kstandardshortcuttest
kuniqueapptest
kwordwraptest
kapplication_unittest
kcolorutilstest
kxmlgui_unittest
ktimezonewidget_unittest
kiconloader_unittest
ktabwidget_unittest
ktoolbar_unittest
krichtextedittest
kselectaction_unittest
klistwidgetsearchlinetest
ktimecomboboxtest
kdatecomboboxtest
kdatetimeedittest
+ kconfigdialog_unittest
)
KDEUI_PROXYMODEL_TESTS(
kdescendantsproxymodeltest
kselectionproxymodeltest
testmodelqueuedconnections
)
KDEUI_EXECUTABLE_TESTS(
kaccelgentest
kactionselectortest
kanimatedbuttontest
kcharselecttest
kapptest
kassistantdialogtest
kbugreporttest
kcategorizedviewtest
kcodecactiontest
kcolorcollectiontest
kcolordlgtest
kcolorcombotest
kcomboboxtest
kcompletionuitest
kdatepicktest
kdatetabletest
kdatetimewidgettest
kdatewidgettest
kdebugtest_gui
kdialogbuttonboxtest
kdialogtest
kfontdialogtest
kglobalsettingsclient # helper program for kglobalsettingstest
khboxtest
kiconeffecttest
kiconloadertest
kinputdialogtest
kjobtrackerstest
kledtest
klineedittest
kmainwindowtest
kmainwindowrestoretest
kmessageboxtest
kmessagetest
kmodifierkeyinfotest
knewpassworddialogtest
kstatusnotifieritemtest
knotificationrestrictionstest
knuminputtest
kpagedialogtest
kpagewidgettest
kpassivepopuptest
kpassworddialogtest
kpixmapregionselectordialogtest
kpopuptest
kprogressdialogtest
krulertest
kselectactiontest
kseparatortest
kstatusbartest
ksqueezedtextlabeltest
ksystemtraytest
ktabwidgettest
ktextedittest
ktextbrowsertest
ktitlewidgettest
ktoolbartest
ktoolbarlabelactiontest
ktreewidgetsearchlinetest
kwallettest
kwidgetitemdelegatetest
kwindowtest
kxmlguitest
kxmlguiwindowtest
testqtargs
kpixmapsequenceoverlaypaintertest
)
if (NOT KDE_NO_DEPRECATED)
KDEUI_EXECUTABLE_TESTS(
keditlistboxtest
)
endif (NOT KDE_NO_DEPRECATED)
if (Q_WS_X11)
KDEUI_EXECUTABLE_TESTS(
fixx11h_test
fixx11h_test2
kmanagerselectiontest
kxerrorhandlertest
)
target_link_libraries(kmanagerselectiontest ${X11_X11_LIB})
target_link_libraries(kxerrorhandlertest ${X11_X11_LIB})
endif (Q_WS_X11)
## kplottest
set(kplottest_SRCS testplot_widget.cpp testplot_main.cpp)
kde4_add_executable(kplottest TEST ${kplottest_SRCS})
target_link_libraries(kplottest ${KDE4_KDEUI_LIBS})
## kcolorutilsdemo
SET(kcolorUtilsDemoSources kcolorutilsdemo.cpp kimageframe.cpp ../colors/kcolorspaces.cpp)
kde4_add_ui_files(kcolorUtilsDemoSources kcolorutilsdemo.ui)
kde4_add_executable(kcolorutilsdemo ${kcolorUtilsDemoSources})
target_link_libraries(kcolorutilsdemo ${KDE4_KDEUI_LIBS} ${KDEWIN_LIBRARIES})
add_subdirectory(proxymodeltestapp)
diff --git a/kdeui/tests/kconfigdialog_unittest.cpp b/kdeui/tests/kconfigdialog_unittest.cpp
new file mode 100644
index 0000000000..79c35e452e
--- /dev/null
+++ b/kdeui/tests/kconfigdialog_unittest.cpp
@@ -0,0 +1,132 @@
+/* This file is part of the KDE libraries
+
+ Copyright (c) 2012 Albert Astals Cid <aacid@kde.org>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License as published by the Free Software Foundation; either
+ version 2 of the License, or (at your option) any later version.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include <qtest_kde.h>
+#include <qtestevent.h>
+
+#include <kconfigdialog.h>
+#include <kconfigskeleton.h>
+#include <kcombobox.h>
+#include <kcolorcombo.h>
+
+class ComboBoxPage : public QWidget
+{
+public:
+ ComboBoxPage()
+ {
+ colorCombo = new KColorCombo(this);
+ colorCombo->setObjectName("kcfg_Color");
+ colorCombo->setColor(Qt::red);
+
+ enumCombo = new KComboBox(this);
+ enumCombo->setObjectName("kcfg_Enum");
+ enumCombo->addItems(QStringList() << "A" << "B" << "C");
+
+ textCombo = new KComboBox(this);
+ textCombo->setObjectName("kcfg_Text");
+ textCombo->setEditable(true);
+ textCombo->addItems(QStringList() << "A" << "B" << "C");
+ }
+
+ KColorCombo *colorCombo;
+ KComboBox *enumCombo;
+ KComboBox *textCombo;
+};
+
+class ComboSettings : public KConfigSkeleton
+{
+public:
+ ComboSettings()
+ {
+ colorItem = new ItemColor( currentGroup(), QLatin1String( "Color" ), color, Qt::white );
+ addItem( colorItem, QLatin1String( "Color" ) );
+
+ QList<ItemEnum::Choice2> textValues;
+ {
+ ItemEnum::Choice2 choice;
+ choice.name = QLatin1String("A");
+ textValues.append( choice );
+ }
+ {
+ ItemEnum::Choice2 choice;
+ choice.name = QLatin1String("B");
+ textValues.append( choice );
+ }
+ {
+ ItemEnum::Choice2 choice;
+ choice.name = QLatin1String("C");
+ textValues.append( choice );
+ }
+ enumItem = new ItemEnum( currentGroup(), QLatin1String( "Enum" ), enumIndex, textValues, 1 );
+ addItem( enumItem, QLatin1String( "Enum" ) );
+
+ stringItem = new ItemString( currentGroup(), QLatin1String( "Text" ), string, QLatin1String( "hh:mm" ) );
+ addItem( stringItem, QLatin1String( "Text" ) );
+ }
+
+ ItemColor *colorItem;
+ QColor color;
+
+ ItemEnum *enumItem;
+ int enumIndex;
+
+ ItemString *stringItem;
+ QString string;
+};
+
+class KConfigDialog_UnitTest : public QObject
+{
+ Q_OBJECT
+
+private Q_SLOTS:
+ void combosTest()
+ {
+ ComboSettings *skeleton = new ComboSettings();
+ KConfigDialog *dialog = new KConfigDialog(0, "settings", skeleton);
+ ComboBoxPage *page = new ComboBoxPage();
+
+ QCOMPARE(page->colorCombo->color(), QColor(Qt::red));
+ QCOMPARE(page->enumCombo->currentIndex(), 0);
+ QCOMPARE(page->textCombo->currentText(), QString("A"));
+
+ dialog->addPage(page, "General");
+
+ QCOMPARE(page->colorCombo->color(), QColor(Qt::white));
+ QCOMPARE(page->enumCombo->currentIndex(), 1);
+ QCOMPARE(page->textCombo->currentText(), QLatin1String( "hh:mm" ));
+
+ page->colorCombo->setColor(Qt::blue);
+ page->enumCombo->setCurrentIndex(2);
+ page->textCombo->setCurrentIndex(2);
+
+ QMetaObject::invokeMethod(dialog, "applyClicked");
+ QCOMPARE(skeleton->colorItem->property().value<QColor>(), QColor(Qt::blue));
+ QCOMPARE(skeleton->enumItem->property().toInt(), 2);
+ QCOMPARE(skeleton->stringItem->property().toString(), QLatin1String("C"));
+
+ delete dialog;
+ delete skeleton;
+ }
+
+};
+
+QTEST_KDEMAIN(KConfigDialog_UnitTest, GUI)
+
+#include "kconfigdialog_unittest.moc"
diff --git a/kdeui/widgets/kpushbutton.cpp b/kdeui/widgets/kpushbutton.cpp
index 6e45631277..6685390a04 100644
--- a/kdeui/widgets/kpushbutton.cpp
+++ b/kdeui/widgets/kpushbutton.cpp
@@ -1,385 +1,383 @@
/* This file is part of the KDE libraries
Copyright (C) 2000 Carsten Pfeiffer <pfeiffer@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kpushbutton.h"
#include <QStyleOptionToolButton>
#include <QStylePainter>
#include <QtGui/QDrag>
#include <QtGui/QActionEvent>
#include <QtGui/QMenu>
#include <QtCore/QPointer>
#include <QtGui/QStyle>
#include <QtCore/QTimer>
#include <config.h>
#include <kconfig.h>
#include <kglobal.h>
#include <kglobalsettings.h>
#include <kguiitem.h>
#include <kicon.h>
#include "auth/kauthaction.h"
#include "auth/kauthactionwatcher.h"
static bool s_useIcons = false;
class KPushButton::KPushButtonPrivate
{
public:
KPushButtonPrivate(KPushButton *_parent) : parent(_parent), m_dragEnabled( false ), authAction(0)
{
}
KPushButton *parent;
KGuiItem item;
KStandardGuiItem::StandardItem itemType;
QPointer<QMenu> delayedMenu;
QTimer * delayedMenuTimer;
bool m_dragEnabled;
QPoint startPos;
KAuth::Action *authAction;
// TODO: Remove whenever QIcon overlays will get fixed
KIcon oldIcon;
void slotSettingsChanged( int );
void slotPressedInternal();
void slotClickedInternal();
void authStatusChanged(int status);
void slotDelayedMenuTimeout();
void readSettings();
};
void KPushButton::KPushButtonPrivate::slotSettingsChanged( int /* category */ )
{
readSettings();
parent->setIcon( item.icon() );
}
void KPushButton::KPushButtonPrivate::slotPressedInternal()
{
if (!delayedMenu.isNull()) {
if (delayedMenuTimer==0) {
delayedMenuTimer=new QTimer(parent);
delayedMenuTimer->setSingleShot(true);
connect(delayedMenuTimer,SIGNAL(timeout()),parent,SLOT(slotDelayedMenuTimeout()));
}
const int delay=parent->style()->styleHint(QStyle::SH_ToolButton_PopupDelay, 0, parent);
delayedMenuTimer->start((delay<=0) ? 150:delay);
}
}
void KPushButton::KPushButtonPrivate::slotClickedInternal()
{
if (delayedMenuTimer)
delayedMenuTimer->stop();
if (authAction) {
KAuth::Action::AuthStatus s = authAction->earlyAuthorize();
switch(s) {
case KAuth::Action::Denied:
parent->setEnabled(false);
break;
case KAuth::Action::Authorized:
emit parent->authorized(authAction);
break;
default:
break;
}
}
}
void KPushButton::KPushButtonPrivate::slotDelayedMenuTimeout() {
delayedMenuTimer->stop();
if (!delayedMenu.isNull()) {
parent->setMenu(delayedMenu);
parent->showMenu();
parent->setMenu(0);
}
}
void KPushButton::KPushButtonPrivate::authStatusChanged(int status)
{
KAuth::Action::AuthStatus s = (KAuth::Action::AuthStatus)status;
switch(s) {
case KAuth::Action::Authorized:
parent->setEnabled(true);
if(!oldIcon.isNull()) {
parent->setIcon(oldIcon);
oldIcon = KIcon();
}
break;
case KAuth::Action::AuthRequired:
parent->setEnabled(true);
oldIcon = KIcon(parent->icon());
parent->setIcon(KIcon("dialog-password"));
break;
default:
parent->setEnabled(false);
if(!oldIcon.isNull()) {
parent->setIcon(oldIcon);
oldIcon = KIcon();
}
}
}
void KPushButton::KPushButtonPrivate::readSettings()
{
s_useIcons = KGlobalSettings::showIconsOnPushButtons();
}
KPushButton::KPushButton( QWidget *parent )
: QPushButton( parent ), d( new KPushButtonPrivate(this) )
{
init( KGuiItem( "" ) );
}
KPushButton::KPushButton( const QString &text, QWidget *parent )
: QPushButton( parent ), d( new KPushButtonPrivate(this) )
{
init( KGuiItem( text ) );
}
KPushButton::KPushButton( const KIcon &icon, const QString &text,
QWidget *parent )
: QPushButton( text, parent ), d( new KPushButtonPrivate(this) )
{
init( KGuiItem( text, icon ) );
}
KPushButton::KPushButton( const KGuiItem &item, QWidget *parent )
: QPushButton( parent ), d( new KPushButtonPrivate(this) )
{
init( item );
}
KPushButton::~KPushButton()
{
delete d;
}
void KPushButton::init( const KGuiItem &item )
{
d->item = item;
d->itemType = (KStandardGuiItem::StandardItem) 0;
d->delayedMenuTimer=0;
connect(this,SIGNAL(pressed()), this, SLOT(slotPressedInternal()));
connect(this,SIGNAL(clicked()), this, SLOT(slotClickedInternal()));
// call QPushButton's implementation since we don't need to
// set the GUI items text or check the state of the icon set
QPushButton::setText( d->item.text() );
static bool initialized = false;
if ( !initialized ) {
d->readSettings();
initialized = true;
}
setIcon( d->item.icon() );
setToolTip( item.toolTip() );
setWhatsThis(item.whatsThis());
connect( KGlobalSettings::self(), SIGNAL(settingsChanged(int)),
SLOT(slotSettingsChanged(int)) );
}
bool KPushButton::isDragEnabled() const
{
return d->m_dragEnabled;
}
void KPushButton::setGuiItem( const KGuiItem& item )
{
d->item = item;
// call QPushButton's implementation since we don't need to
// set the GUI items text or check the state of the icon set
QPushButton::setText( d->item.text() );
setIcon( d->item.icon() );
setToolTip( d->item.toolTip() );
setEnabled( d->item.isEnabled() );
setWhatsThis( d->item.whatsThis() );
}
void KPushButton::setGuiItem( KStandardGuiItem::StandardItem item )
{
setGuiItem( KStandardGuiItem::guiItem(item) );
d->itemType = item;
}
KStandardGuiItem::StandardItem KPushButton::guiItem() const
{
return d->itemType;
}
void KPushButton::setText( const QString &text )
{
QPushButton::setText(text);
// we need to re-evaluate the icon set when the text
// is removed, or when it is supplied
if (text.isEmpty() != d->item.text().isEmpty())
setIcon(d->item.icon());
d->item.setText(text);
}
void KPushButton::setIcon( const KIcon &icon )
{
d->item.setIcon(icon);
if ( s_useIcons || text().isEmpty() )
QPushButton::setIcon( icon );
else
QPushButton::setIcon( QIcon() );
}
-#ifndef KDE_NO_DEPRECATED
void KPushButton::setIcon( const QIcon &qicon )
{
d->item.setIcon(KIcon(qicon));
}
-#endif
void KPushButton::setDragEnabled( bool enable )
{
d->m_dragEnabled = enable;
}
void KPushButton::mousePressEvent( QMouseEvent *e )
{
if ( d->m_dragEnabled )
d->startPos = e->pos();
QPushButton::mousePressEvent( e );
}
void KPushButton::mouseMoveEvent( QMouseEvent *e )
{
if ( !d->m_dragEnabled )
{
QPushButton::mouseMoveEvent( e );
return;
}
if ( (e->buttons() & Qt::LeftButton) &&
(e->pos() - d->startPos).manhattanLength() >
KGlobalSettings::dndEventDelay() )
{
startDrag();
setDown( false );
}
}
QDrag * KPushButton::dragObject()
{
return 0;
}
void KPushButton::startDrag()
{
QDrag *d = dragObject();
if ( d )
d->start();
}
void KPushButton::setDelayedMenu(QMenu *delayedMenu)
{
d->delayedMenu=delayedMenu;
}
QMenu* KPushButton::delayedMenu()
{
return d->delayedMenu;
}
KAuth::Action *KPushButton::authAction() const
{
return d->authAction;
}
void KPushButton::setAuthAction(const QString &actionName)
{
if (actionName.isEmpty()) {
setAuthAction(0);
} else {
setAuthAction(new KAuth::Action(actionName));
}
}
void KPushButton::setAuthAction(KAuth::Action *action)
{
if (d->authAction == action) {
return;
}
if (d->authAction) {
disconnect(d->authAction->watcher(), SIGNAL(statusChanged(int)),
this, SLOT(authStatusChanged(int)));
//delete d->authAction;
d->authAction = 0;
if (!d->oldIcon.isNull()) {
setIcon(d->oldIcon);
d->oldIcon = KIcon();
}
}
if (action != 0) {
d->authAction = action;
// Set the parent widget
d->authAction->setParentWidget(this);
connect(d->authAction->watcher(), SIGNAL(statusChanged(int)),
this, SLOT(authStatusChanged(int)));
d->authStatusChanged(d->authAction->status());
}
}
QSize KPushButton::sizeHint() const
{
const bool tempSetMenu = !menu() && d->delayedMenu;
if (tempSetMenu)
const_cast<KPushButton *>(this)->setMenu(d->delayedMenu);
const QSize sz = QPushButton::sizeHint();
if (tempSetMenu)
const_cast<KPushButton *>(this)->setMenu(0);
return sz;
}
void KPushButton::paintEvent( QPaintEvent * )
{
QStylePainter p(this);
QStyleOptionButton option;
initStyleOption(&option);
if (d->delayedMenu)
option.features |= QStyleOptionButton::HasMenu;
p.drawControl(QStyle::CE_PushButton, option);
}
#include "kpushbutton.moc"
diff --git a/kdeui/widgets/kpushbutton.h b/kdeui/widgets/kpushbutton.h
index 279ac5e771..cb44197eb0 100644
--- a/kdeui/widgets/kpushbutton.h
+++ b/kdeui/widgets/kpushbutton.h
@@ -1,235 +1,231 @@
/* This file is part of the KDE libraries
Copyright (C) 2000 Carsten Pfeiffer <pfeiffer@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KPUSHBUTTON_H
#define KPUSHBUTTON_H
#include <QtGui/QPushButton>
#include <kstandardguiitem.h>
class QDrag;
class QMenu;
class KIcon;
namespace KAuth {
class Action;
}
/**
* @brief A QPushButton with drag-support and KGuiItem support
*
* This is nothing but a QPushButton with drag-support and KGuiItem support.
* You must call #setDragEnabled (true) and override the virtual method
* dragObject() to specify the QDragObject to be used.
*
* \image html kpushbutton.png "KDE Push Button"
*
* @author Carsten Pfeiffer <pfeiffer@kde.org>
*/
class KDEUI_EXPORT KPushButton : public QPushButton
{
Q_OBJECT
Q_PROPERTY(bool isDragEnabled READ isDragEnabled WRITE setDragEnabled)
public:
/**
* Default constructor.
*/
explicit KPushButton( QWidget *parent = 0 );
/**
* Constructor, that sets the button-text to @p text
*/
explicit KPushButton( const QString &text, QWidget *parent = 0 );
/**
* Constructor, that sets an icon and the button-text to @p text
*/
KPushButton( const KIcon &icon, const QString &text, QWidget *parent = 0 );
/**
* Constructor that takes a KGuiItem for the text, the icon, the tooltip
* and the what's this help
*/
explicit KPushButton( const KGuiItem &item, QWidget *parent = 0 );
/**
* Destructs the button.
*/
~KPushButton();
/**
* Enables/disables drag-support. Default is disabled.
*/
void setDragEnabled( bool enable );
/**
* @returns if drag support is enabled or not.
*/
bool isDragEnabled() const;
/**
* Sets the KGuiItem for this button.
*/
void setGuiItem( const KGuiItem& item );
/**
* Sets the standard KGuiItem for this button.
*/
void setGuiItem( KStandardGuiItem::StandardItem item );
/**
* Reads the standard KGuiItem for this button.
*/
KStandardGuiItem::StandardItem guiItem() const;
/**
* Sets the Icon Set for this button. It also takes into account the
* KGlobalSettings::showIconsOnPushButtons() setting.
*/
void setIcon( const KIcon &icon );
/**
- * Sets the pixmap for this button. Rarely used. This one exists mostly for usage in Qt designer,
- * with icons embedded into the ui file. But you should rather save them separately, and load them
- * with KIcon("name") so that the icons are themeable.
+ * Sets the pixmap for this button. This one exists mostly for usage in Qt designer.
*/
-#ifndef KDE_NO_DEPRECATED
- KDE_DEPRECATED void setIcon( const QIcon &pix );
-#endif
+ void setIcon( const QIcon &pix );
/**
* Sets the text of the button
*/
void setText( const QString &text );
/**
* Sets a delayed popup menu
* for consistency, since menu() isn't virtual
*/
void setDelayedMenu(QMenu *delayed_menu);
/**
* returns a delayed popup menu
* since menu() isn't virtual
*/
QMenu *delayedMenu();
/**
* Reimplemented to add arrow for delayed menu
* @since 4.4
*/
virtual QSize sizeHint() const;
/**
* Returns the action object associated with this button, or 0 if it does not have one
*
* @returns the KAuth::Action associated with this button.
*/
KAuth::Action *authAction() const;
/**
* Sets the action object associated with this button
*
* By setting a KAuth::Action, this button will become associated with it, and
* whenever it gets clicked, it will trigger the authorization and execution process
* for the action. The signal activated will also be emitted whenever the button gets
* clicked and the action gets authorized. Pass 0 to this function to disassociate the button
*
* @param action the KAuth::Action to associate with this button.
*/
void setAuthAction(KAuth::Action *action);
/**
* Sets the action object associated with this button
*
* Overloaded member to allow creating the action by name
*
* @param actionName the name of the action to associate
*/
void setAuthAction(const QString &actionName);
protected:
/**
* Reimplement this and return the QDrag object that should be used
* for the drag. Remember to give it "this" as parent.
*
* Default implementation returns 0, so that no drag is initiated.
*/
virtual QDrag * dragObject();
/**
* Reimplemented to add drag-support
*/
virtual void mousePressEvent( QMouseEvent * );
/**
* Reimplemented to add drag-support
*/
virtual void mouseMoveEvent( QMouseEvent * );
/**
* Reimplemented to add arrow for delayed menu
* @since 4.4
*/
virtual void paintEvent( QPaintEvent * );
/**
* Starts a drag (dragCopy() by default) using dragObject()
*/
virtual void startDrag();
Q_SIGNALS:
/**
* Signal emitted when the button is triggered and authorized
*
* If the button needs authorization, whenever the user triggers it,
* the authorization process automatically begins.
* If it succeeds, this signal is emitted. The KAuth::Action object is provided for convenience
* if you have multiple Action objects, but of course it's always the same set with
* setAuthAction().
*
* WARNING: If your button needs authorization you should connect eventual slots processing
* stuff to this signal, and NOT clicked. Clicked will be emitted even if the user has not
* been authorized
*
* @param action The object set with setAuthAction()
*/
void authorized(KAuth::Action *action);
private:
/**
* Internal.
* Initialize the KPushButton instance
*/
void init( const KGuiItem &item );
private:
class KPushButtonPrivate;
KPushButtonPrivate * const d;
Q_PRIVATE_SLOT(d, void slotSettingsChanged( int ))
Q_PRIVATE_SLOT(d, void slotPressedInternal())
Q_PRIVATE_SLOT(d, void slotClickedInternal())
Q_PRIVATE_SLOT(d, void slotDelayedMenuTimeout())
Q_PRIVATE_SLOT(d, void authStatusChanged(int))
};
#endif // KPUSHBUTTON_H
diff --git a/kdeui/widgets/ktextedit.cpp b/kdeui/widgets/ktextedit.cpp
index e6dda6c2dd..1e58706d1f 100644
--- a/kdeui/widgets/ktextedit.cpp
+++ b/kdeui/widgets/ktextedit.cpp
@@ -1,1131 +1,1131 @@
/* This file is part of the KDE libraries
Copyright (C) 2002 Carsten Pfeiffer <pfeiffer@kde.org>
2005 Michael Brade <brade@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "ktextedit.h"
#include <ktoolinvocation.h>
#include <kdebug.h>
#include <QApplication>
#include <QClipboard>
#include <QKeyEvent>
#include <QMenu>
#include <QPainter>
#include <QScrollBar>
#include <QTextCursor>
#include <QTextDocumentFragment>
#include <QDBusInterface>
#include <QDBusConnection>
#include <QDBusConnectionInterface>
#include <configdialog.h>
#include <dialog.h>
#include "backgroundchecker.h"
#include <kaction.h>
#include <kcursor.h>
#include <kglobalsettings.h>
#include <kstandardaction.h>
#include <kstandardshortcut.h>
#include <kicon.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kdialog.h>
#include <kreplacedialog.h>
#include <kfinddialog.h>
#include <kfind.h>
#include <kreplace.h>
#include <kmessagebox.h>
#include <kmenu.h>
#include <kwindowsystem.h>
#include <QDebug>
class KTextEdit::Private
{
public:
Private( KTextEdit *_parent )
: parent( _parent ),
customPalette( false ),
checkSpellingEnabled( false ),
findReplaceEnabled(true),
highlighter( 0 ), findDlg(0),find(0),repDlg(0),replace(0), findIndex(0), repIndex(0),
lastReplacedPosition(-1)
{
//Check the default sonnet settings to see if spellchecking should be enabled.
sonnetKConfig = new KConfig("sonnetrc");
KConfigGroup group(sonnetKConfig, "Spelling");
checkSpellingEnabled = group.readEntry("checkerEnabledByDefault", false);
// i18n: Placeholder text in text edit widgets is the text appearing
// before any user input, briefly explaining to the user what to type
// (e.g. "Enter message").
// By default the text is set in italic, which may not be appropriate
// for some languages and scripts (e.g. for CJK ideographs).
QString metaMsg = i18nc("Italic placeholder text in line edits: 0 no, 1 yes", "1");
italicizePlaceholder = (metaMsg.trimmed() != QString('0'));
}
~Private()
{
delete highlighter;
delete findDlg;
delete find;
delete replace;
delete repDlg;
delete sonnetKConfig;
}
/**
* Checks whether we should/should not consume a key used as a shortcut.
* This makes it possible to handle shortcuts in the focused widget before any
* window-global QAction is triggered.
*/
bool overrideShortcut(const QKeyEvent* e);
/**
* Actually handle a shortcut event.
*/
bool handleShortcut(const QKeyEvent* e);
void spellCheckerMisspelling( const QString &text, int pos );
void spellCheckerCorrected( const QString &, int,const QString &);
void spellCheckerAutoCorrect(const QString&,const QString&);
void spellCheckerCanceled();
void spellCheckerFinished();
void toggleAutoSpellCheck();
void slotFindHighlight(const QString& text, int matchingIndex, int matchingLength);
void slotReplaceText(const QString &text, int replacementIndex, int /*replacedLength*/, int matchedLength);
/**
* Similar to QTextEdit::clear(), only that it is possible to undo this
* action.
*/
void undoableClear();
void slotAllowTab();
void menuActivated( QAction* action );
void updateClickMessageRect();
void init();
KTextEdit *parent;
KTextEditSpellInterface *spellInterface;
QAction *autoSpellCheckAction;
QAction *allowTab;
QAction *spellCheckAction;
QString clickMessage;
bool italicizePlaceholder : 1;
bool customPalette : 1;
bool checkSpellingEnabled : 1;
bool findReplaceEnabled: 1;
QTextDocumentFragment originalDoc;
QString spellCheckingConfigFileName;
QString spellCheckingLanguage;
Sonnet::Highlighter *highlighter;
KFindDialog *findDlg;
KFind *find;
KReplaceDialog *repDlg;
KReplace *replace;
int findIndex, repIndex;
int lastReplacedPosition;
KConfig *sonnetKConfig;
};
void KTextEdit::Private::spellCheckerCanceled()
{
QTextDocument *doc = parent->document();
doc->clear();
QTextCursor cursor(doc);
cursor.insertFragment(originalDoc);
spellCheckerFinished();
}
void KTextEdit::Private::spellCheckerAutoCorrect(const QString&,const QString&)
{
//TODO
}
void KTextEdit::Private::spellCheckerMisspelling( const QString &text, int pos )
{
//kDebug()<<"TextEdit::Private::spellCheckerMisspelling :"<<text<<" pos :"<<pos;
parent->highlightWord( text.length(), pos );
}
void KTextEdit::Private::spellCheckerCorrected( const QString& oldWord, int pos,const QString& newWord)
{
//kDebug()<<" oldWord :"<<oldWord<<" newWord :"<<newWord<<" pos : "<<pos;
if (oldWord != newWord ) {
QTextCursor cursor(parent->document());
cursor.setPosition(pos);
cursor.setPosition(pos+oldWord.length(),QTextCursor::KeepAnchor);
cursor.insertText(newWord);
}
}
void KTextEdit::Private::spellCheckerFinished()
{
QTextCursor cursor(parent->document());
cursor.clearSelection();
parent->setTextCursor(cursor);
if (parent->highlighter())
parent->highlighter()->rehighlight();
}
void KTextEdit::Private::toggleAutoSpellCheck()
{
parent->setCheckSpellingEnabled( !parent->checkSpellingEnabled() );
}
void KTextEdit::Private::undoableClear()
{
QTextCursor cursor = parent->textCursor();
cursor.beginEditBlock();
cursor.movePosition(QTextCursor::Start);
cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor);
cursor.removeSelectedText();
cursor.endEditBlock();
}
void KTextEdit::Private::slotAllowTab()
{
parent->setTabChangesFocus( !parent->tabChangesFocus() );
}
void KTextEdit::Private::menuActivated( QAction* action )
{
if ( action == spellCheckAction )
parent->checkSpelling();
else if ( action == autoSpellCheckAction )
toggleAutoSpellCheck();
else if ( action == allowTab )
slotAllowTab();
}
void KTextEdit::Private::slotFindHighlight(const QString& text, int matchingIndex, int matchingLength)
{
Q_UNUSED(text)
//kDebug() << "Highlight: [" << text << "] mi:" << matchingIndex << " ml:" << matchingLength;
QTextCursor tc = parent->textCursor();
tc.setPosition(matchingIndex);
tc.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, matchingLength);
parent->setTextCursor(tc);
parent->ensureCursorVisible();
}
void KTextEdit::Private::slotReplaceText(const QString &text, int replacementIndex, int replacedLength, int matchedLength) {
//kDebug() << "Replace: [" << text << "] ri:" << replacementIndex << " rl:" << replacedLength << " ml:" << matchedLength;
QTextCursor tc = parent->textCursor();
tc.setPosition(replacementIndex);
tc.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, matchedLength);
tc.removeSelectedText();
tc.insertText(text.mid(replacementIndex, replacedLength));
if (replace->options() & KReplaceDialog::PromptOnReplace) {
parent->setTextCursor(tc);
parent->ensureCursorVisible();
}
lastReplacedPosition = replacementIndex;
}
void KTextEdit::Private::updateClickMessageRect()
{
int margin = int(parent->document()->documentMargin());
QRect rect = parent->viewport()->rect().adjusted(margin, margin, -margin, -margin);
rect = parent->fontMetrics().boundingRect(rect, Qt::AlignTop | Qt::TextWordWrap, clickMessage);
parent->viewport()->update(rect);
}
void KTextEdit::Private::init()
{
spellInterface = 0;
KCursor::setAutoHideCursor(parent, true, false);
parent->connect(parent, SIGNAL(languageChanged(QString)),
parent, SLOT(setSpellCheckingLanguage(QString)));
}
KTextEdit::KTextEdit( const QString& text, QWidget *parent )
: QTextEdit( text, parent ), d( new Private( this ) )
{
d->init();
}
KTextEdit::KTextEdit( QWidget *parent )
: QTextEdit( parent ), d( new Private( this ) )
{
d->init();
}
KTextEdit::~KTextEdit()
{
delete d;
}
void KTextEdit::setSpellCheckingConfigFileName(const QString &_fileName)
{
d->spellCheckingConfigFileName = _fileName;
}
const QString& KTextEdit::spellCheckingLanguage() const
{
return d->spellCheckingLanguage;
}
void KTextEdit::setSpellCheckingLanguage(const QString &_language)
{
if (highlighter()) {
highlighter()->setCurrentLanguage(_language);
highlighter()->rehighlight();
}
if (_language != d->spellCheckingLanguage) {
d->spellCheckingLanguage = _language;
emit languageChanged(_language);
}
else
d->spellCheckingLanguage = _language;
}
void KTextEdit::showSpellConfigDialog(const QString &configFileName,
const QString &windowIcon)
{
KConfig config(configFileName);
Sonnet::ConfigDialog dialog(&config, this);
if (!d->spellCheckingLanguage.isEmpty())
dialog.setLanguage(d->spellCheckingLanguage);
- connect(&dialog, SIGNAL(languageChanged(QString)),
- this, SLOT(setSpellCheckingLanguage(QString)));
if (!windowIcon.isEmpty())
dialog.setWindowIcon(KIcon(windowIcon));
- dialog.exec();
+ if(dialog.exec()) {
+ setSpellCheckingLanguage( dialog.language() );
+ }
}
bool KTextEdit::event(QEvent* ev)
{
if (ev->type() == QEvent::ShortcutOverride) {
QKeyEvent *e = static_cast<QKeyEvent *>( ev );
if (d->overrideShortcut(e)) {
e->accept();
return true;
}
}
return QTextEdit::event(ev);
}
bool KTextEdit::Private::handleShortcut(const QKeyEvent* event)
{
const int key = event->key() | event->modifiers();
if ( KStandardShortcut::copy().contains( key ) ) {
parent->copy();
return true;
} else if ( KStandardShortcut::paste().contains( key ) ) {
parent->paste();
return true;
} else if ( KStandardShortcut::cut().contains( key ) ) {
parent->cut();
return true;
} else if ( KStandardShortcut::undo().contains( key ) ) {
if(!parent->isReadOnly())
parent->undo();
return true;
} else if ( KStandardShortcut::redo().contains( key ) ) {
if(!parent->isReadOnly())
parent->redo();
return true;
} else if ( KStandardShortcut::deleteWordBack().contains( key ) ) {
parent->deleteWordBack();
return true;
} else if ( KStandardShortcut::deleteWordForward().contains( key ) ) {
parent->deleteWordForward();
return true;
} else if ( KStandardShortcut::backwardWord().contains( key ) ) {
QTextCursor cursor = parent->textCursor();
cursor.movePosition( QTextCursor::PreviousWord );
parent->setTextCursor( cursor );
return true;
} else if ( KStandardShortcut::forwardWord().contains( key ) ) {
QTextCursor cursor = parent->textCursor();
cursor.movePosition( QTextCursor::NextWord );
parent->setTextCursor( cursor );
return true;
} else if ( KStandardShortcut::next().contains( key ) ) {
QTextCursor cursor = parent->textCursor();
bool moved = false;
qreal lastY = parent->cursorRect(cursor).bottom();
qreal distance = 0;
do {
qreal y = parent->cursorRect(cursor).bottom();
distance += qAbs(y - lastY);
lastY = y;
moved = cursor.movePosition(QTextCursor::Down);
} while (moved && distance < parent->viewport()->height());
if (moved) {
cursor.movePosition(QTextCursor::Up);
parent->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepAdd);
}
parent->setTextCursor(cursor);
return true;
} else if ( KStandardShortcut::prior().contains( key ) ) {
QTextCursor cursor = parent->textCursor();
bool moved = false;
qreal lastY = parent->cursorRect(cursor).bottom();
qreal distance = 0;
do {
qreal y = parent->cursorRect(cursor).bottom();
distance += qAbs(y - lastY);
lastY = y;
moved = cursor.movePosition(QTextCursor::Up);
} while (moved && distance < parent->viewport()->height());
if (moved) {
cursor.movePosition(QTextCursor::Down);
parent->verticalScrollBar()->triggerAction(QAbstractSlider::SliderPageStepSub);
}
parent->setTextCursor(cursor);
return true;
} else if ( KStandardShortcut::begin().contains( key ) ) {
QTextCursor cursor = parent->textCursor();
cursor.movePosition( QTextCursor::Start );
parent->setTextCursor( cursor );
return true;
} else if ( KStandardShortcut::end().contains( key ) ) {
QTextCursor cursor = parent->textCursor();
cursor.movePosition( QTextCursor::End );
parent->setTextCursor( cursor );
return true;
} else if ( KStandardShortcut::beginningOfLine().contains( key ) ) {
QTextCursor cursor = parent->textCursor();
cursor.movePosition( QTextCursor::StartOfLine );
parent->setTextCursor( cursor );
return true;
} else if ( KStandardShortcut::endOfLine().contains( key ) ) {
QTextCursor cursor = parent->textCursor();
cursor.movePosition( QTextCursor::EndOfLine );
parent->setTextCursor( cursor );
return true;
} else if (findReplaceEnabled && KStandardShortcut::find().contains(key)) {
parent->slotFind();
return true;
} else if (findReplaceEnabled && KStandardShortcut::findNext().contains(key)) {
parent->slotFindNext();
return true;
} else if (findReplaceEnabled && KStandardShortcut::replace().contains(key)) {
if (!parent->isReadOnly())
parent->slotReplace();
return true;
} else if ( KStandardShortcut::pasteSelection().contains( key ) ) {
QString text = QApplication::clipboard()->text( QClipboard::Selection );
if ( !text.isEmpty() )
parent->insertPlainText( text ); // TODO: check if this is html? (MiB)
return true;
}
return false;
}
static void deleteWord(QTextCursor cursor, QTextCursor::MoveOperation op)
{
cursor.clearSelection();
cursor.movePosition( op, QTextCursor::KeepAnchor );
cursor.removeSelectedText();
}
void KTextEdit::deleteWordBack()
{
deleteWord(textCursor(), QTextCursor::PreviousWord);
}
void KTextEdit::deleteWordForward()
{
deleteWord(textCursor(), QTextCursor::WordRight);
}
QMenu *KTextEdit::mousePopupMenu()
{
QMenu *popup = createStandardContextMenu();
if (!popup) return 0;
connect( popup, SIGNAL(triggered(QAction*)),
this, SLOT(menuActivated(QAction*)) );
const bool emptyDocument = document()->isEmpty();
if( !isReadOnly() )
{
QList<QAction *> actionList = popup->actions();
enum { UndoAct, RedoAct, CutAct, CopyAct, PasteAct, ClearAct, SelectAllAct, NCountActs };
QAction *separatorAction = 0L;
int idx = actionList.indexOf( actionList[SelectAllAct] ) + 1;
if ( idx < actionList.count() )
separatorAction = actionList.at( idx );
if ( separatorAction )
{
KAction *clearAllAction = KStandardAction::clear(this, SLOT(undoableClear()), popup);
if ( emptyDocument )
clearAllAction->setEnabled( false );
popup->insertAction( separatorAction, clearAllAction );
}
}
KIconTheme::assignIconsToContextMenu( isReadOnly() ? KIconTheme::ReadOnlyText
: KIconTheme::TextEditor,
popup->actions() );
if( !isReadOnly() )
{
popup->addSeparator();
d->spellCheckAction = popup->addAction( KIcon( "tools-check-spelling" ),
i18n( "Check Spelling..." ) );
if ( emptyDocument )
d->spellCheckAction->setEnabled( false );
d->autoSpellCheckAction = popup->addAction( i18n( "Auto Spell Check" ) );
d->autoSpellCheckAction->setCheckable( true );
d->autoSpellCheckAction->setChecked( checkSpellingEnabled() );
popup->addSeparator();
d->allowTab = popup->addAction( i18n("Allow Tabulations") );
d->allowTab->setCheckable( true );
d->allowTab->setChecked( !tabChangesFocus() );
}
if (d->findReplaceEnabled) {
KAction *findAction = KStandardAction::find(this, SLOT(slotFind()), popup);
KAction *findNextAction = KStandardAction::findNext(this, SLOT(slotFindNext()), popup);
if (emptyDocument) {
findAction->setEnabled(false);
findNextAction->setEnabled(false);
} else {
findNextAction->setEnabled(d->find != 0);
}
popup->addSeparator();
popup->addAction(findAction);
popup->addAction(findNextAction);
if (!isReadOnly()) {
KAction *replaceAction = KStandardAction::replace(this, SLOT(slotReplace()), popup);
if (emptyDocument) {
replaceAction->setEnabled(false);
}
popup->addAction(replaceAction);
}
}
popup->addSeparator();
QAction *speakAction = popup->addAction(i18n("Speak Text"));
speakAction->setIcon(KIcon("preferences-desktop-text-to-speech"));
speakAction->setEnabled(!emptyDocument );
connect( speakAction, SIGNAL(triggered(bool)), this, SLOT(slotSpeakText()) );
return popup;
}
void KTextEdit::slotSpeakText()
{
// If KTTSD not running, start it.
if (!QDBusConnection::sessionBus().interface()->isServiceRegistered("org.kde.kttsd"))
{
QString error;
if (KToolInvocation::startServiceByDesktopName("kttsd", QStringList(), &error))
{
KMessageBox::error(this, i18n( "Starting Jovie Text-to-Speech Service Failed"), error );
return;
}
}
QDBusInterface ktts("org.kde.kttsd", "/KSpeech", "org.kde.KSpeech");
QString text;
if(textCursor().hasSelection())
text = textCursor().selectedText();
else
text = toPlainText();
ktts.asyncCall("say", text, 0);
}
void KTextEdit::contextMenuEvent(QContextMenuEvent *event)
{
// Obtain the cursor at the mouse position and the current cursor
QTextCursor cursorAtMouse = cursorForPosition(event->pos());
const int mousePos = cursorAtMouse.position();
QTextCursor cursor = textCursor();
// Check if the user clicked a selected word
const bool selectedWordClicked = cursor.hasSelection() &&
mousePos >= cursor.selectionStart() &&
mousePos <= cursor.selectionEnd();
// Get the word under the (mouse-)cursor and see if it is misspelled.
// Don't include apostrophes at the start/end of the word in the selection.
QTextCursor wordSelectCursor(cursorAtMouse);
wordSelectCursor.clearSelection();
wordSelectCursor.select(QTextCursor::WordUnderCursor);
QString selectedWord = wordSelectCursor.selectedText();
bool isMouseCursorInsideWord = true;
if ((mousePos < wordSelectCursor.selectionStart() ||
mousePos >= wordSelectCursor.selectionEnd())
&& (selectedWord.length() > 1)) {
isMouseCursorInsideWord = false;
}
// Clear the selection again, we re-select it below (without the apostrophes).
wordSelectCursor.setPosition(wordSelectCursor.position()-selectedWord.size());
if (selectedWord.startsWith('\'') || selectedWord.startsWith('\"')) {
selectedWord = selectedWord.right(selectedWord.size() - 1);
wordSelectCursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor);
}
if (selectedWord.endsWith('\'') || selectedWord.endsWith('\"'))
selectedWord.chop(1);
wordSelectCursor.movePosition(QTextCursor::NextCharacter,
QTextCursor::KeepAnchor, selectedWord.size());
const bool wordIsMisspelled = isMouseCursorInsideWord &&
checkSpellingEnabled() &&
!selectedWord.isEmpty() &&
highlighter() &&
highlighter()->isWordMisspelled(selectedWord);
// If the user clicked a selected word, do nothing.
// If the user clicked somewhere else, move the cursor there.
// If the user clicked on a misspelled word, select that word.
// Same behavior as in OpenOffice Writer.
bool inQuote = false;
if (d->spellInterface &&
!d->spellInterface->shouldBlockBeSpellChecked(cursorAtMouse.block().text()))
inQuote = true;
if (!selectedWordClicked) {
if (wordIsMisspelled && !inQuote)
setTextCursor(wordSelectCursor);
else
setTextCursor(cursorAtMouse);
cursor = textCursor();
}
// Use standard context menu for already selected words, correctly spelled
// words and words inside quotes.
if (!wordIsMisspelled || selectedWordClicked || inQuote) {
QMenu *popup = mousePopupMenu();
if ( popup ) {
aboutToShowContextMenu(popup);
popup->exec( event->globalPos() );
delete popup;
}
}
else {
QMenu menu; //don't use KMenu here we don't want auto management accelerator
//Add the suggestions to the menu
const QStringList reps = highlighter()->suggestionsForWord(selectedWord);
if (reps.isEmpty()) {
QAction *suggestionsAction = menu.addAction(i18n("No suggestions for %1", selectedWord));
suggestionsAction->setEnabled(false);
}
else {
for (QStringList::const_iterator it = reps.constBegin(); it != reps.constEnd(); ++it) {
menu.addAction(*it);
}
}
menu.addSeparator();
QAction *ignoreAction = menu.addAction(i18n("Ignore"));
QAction *addToDictAction = menu.addAction(i18n("Add to Dictionary"));
//Execute the popup inline
const QAction *selectedAction = menu.exec(event->globalPos());
if (selectedAction) {
Q_ASSERT(cursor.selectedText() == selectedWord);
if (selectedAction == ignoreAction) {
highlighter()->ignoreWord(selectedWord);
highlighter()->rehighlight();
}
else if (selectedAction == addToDictAction) {
highlighter()->addWordToDictionary(selectedWord);
highlighter()->rehighlight();
}
// Other actions can only be one of the suggested words
else {
const QString replacement = selectedAction->text();
Q_ASSERT(reps.contains(replacement));
cursor.insertText(replacement);
setTextCursor(cursor);
}
}
}
}
void KTextEdit::wheelEvent( QWheelEvent *event )
{
if ( KGlobalSettings::wheelMouseZooms() )
QTextEdit::wheelEvent( event );
else // thanks, we don't want to zoom, so skip QTextEdit's impl.
QAbstractScrollArea::wheelEvent( event );
}
void KTextEdit::createHighlighter()
{
setHighlighter(new Sonnet::Highlighter(this, d->spellCheckingConfigFileName));
}
Sonnet::Highlighter* KTextEdit::highlighter() const
{
return d->highlighter;
}
void KTextEdit::setHighlighter(Sonnet::Highlighter *_highLighter)
{
delete d->highlighter;
d->highlighter = _highLighter;
}
void KTextEdit::setCheckSpellingEnabled(bool check)
{
if (d->spellInterface)
d->spellInterface->setSpellCheckingEnabled(check);
else
setCheckSpellingEnabledInternal(check);
}
void KTextEdit::setCheckSpellingEnabledInternal( bool check )
{
emit checkSpellingChanged( check );
if ( check == d->checkSpellingEnabled )
return;
// From the above statment we know know that if we're turning checking
// on that we need to create a new highlighter and if we're turning it
// off we should remove the old one.
d->checkSpellingEnabled = check;
if ( check )
{
if ( hasFocus() ) {
createHighlighter();
if (!spellCheckingLanguage().isEmpty())
setSpellCheckingLanguage(spellCheckingLanguage());
}
}
else
{
delete d->highlighter;
d->highlighter = 0;
}
}
void KTextEdit::focusInEvent( QFocusEvent *event )
{
if ( d->checkSpellingEnabled && !isReadOnly() && !d->highlighter )
createHighlighter();
if (!d->clickMessage.isEmpty()) {
d->updateClickMessageRect();
}
QTextEdit::focusInEvent( event );
}
bool KTextEdit::checkSpellingEnabled() const
{
if (d->spellInterface)
return d->spellInterface->isSpellCheckingEnabled();
else
return checkSpellingEnabledInternal();
}
bool KTextEdit::checkSpellingEnabledInternal() const
{
return d->checkSpellingEnabled;
}
void KTextEdit::setReadOnly( bool readOnly )
{
if ( !readOnly && hasFocus() && d->checkSpellingEnabled && !d->highlighter )
createHighlighter();
if ( readOnly == isReadOnly() )
return;
if ( readOnly ) {
delete d->highlighter;
d->highlighter = 0;
d->customPalette = testAttribute( Qt::WA_SetPalette );
QPalette p = palette();
QColor color = p.color( QPalette::Disabled, QPalette::Background );
p.setColor( QPalette::Base, color );
p.setColor( QPalette::Background, color );
setPalette( p );
} else {
if ( d->customPalette && testAttribute( Qt::WA_SetPalette ) ) {
QPalette p = palette();
QColor color = p.color( QPalette::Normal, QPalette::Base );
p.setColor( QPalette::Base, color );
p.setColor( QPalette::Background, color );
setPalette( p );
} else
setPalette( QPalette() );
}
QTextEdit::setReadOnly( readOnly );
}
void KTextEdit::checkSpelling()
{
if(document()->isEmpty())
{
KMessageBox::information(this, i18n("Nothing to spell check."));
return;
}
Sonnet::BackgroundChecker *backgroundSpellCheck = new Sonnet::BackgroundChecker(this);
if(!d->spellCheckingLanguage.isEmpty())
backgroundSpellCheck->changeLanguage(d->spellCheckingLanguage);
Sonnet::Dialog *spellDialog = new Sonnet::Dialog(
backgroundSpellCheck, 0);
connect(spellDialog, SIGNAL(replace(QString,int,QString)),
this, SLOT(spellCheckerCorrected(QString,int,QString)));
connect(spellDialog, SIGNAL(misspelling(QString,int)),
this, SLOT(spellCheckerMisspelling(QString,int)));
connect(spellDialog, SIGNAL(autoCorrect(QString,QString)),
this, SLOT(spellCheckerAutoCorrect(QString,QString)));
connect(spellDialog, SIGNAL(done(QString)),
this, SLOT(spellCheckerFinished()));
connect(spellDialog, SIGNAL(cancel()),
this, SLOT(spellCheckerCanceled()));
connect(spellDialog, SIGNAL(stop()),
this, SLOT(spellCheckerFinished()));
connect(spellDialog, SIGNAL(spellCheckStatus(QString)),
this,SIGNAL(spellCheckStatus(QString)));
connect(spellDialog, SIGNAL(languageChanged(QString)),
this, SIGNAL(languageChanged(QString)));
d->originalDoc = QTextDocumentFragment(document());
spellDialog->setBuffer(toPlainText());
spellDialog->show();
}
void KTextEdit::highlightWord( int length, int pos )
{
QTextCursor cursor(document());
cursor.setPosition(pos);
cursor.setPosition(pos+length,QTextCursor::KeepAnchor);
setTextCursor (cursor);
ensureCursorVisible();
}
void KTextEdit::replace()
{
if( document()->isEmpty() ) // saves having to track the text changes
return;
if ( d->repDlg ) {
KWindowSystem::activateWindow( d->repDlg->winId() );
} else {
d->repDlg = new KReplaceDialog(this, 0,
QStringList(), QStringList(), false);
connect( d->repDlg, SIGNAL(okClicked()), this, SLOT(slotDoReplace()) );
}
d->repDlg->show();
}
void KTextEdit::slotDoReplace()
{
if (!d->repDlg) {
// Should really assert()
return;
}
if(d->repDlg->pattern().isEmpty()) {
delete d->replace;
d->replace = 0;
ensureCursorVisible();
return;
}
delete d->replace;
d->replace = new KReplace(d->repDlg->pattern(), d->repDlg->replacement(), d->repDlg->options(), this);
d->repIndex = 0;
if (d->replace->options() & KFind::FromCursor || d->replace->options() & KFind::FindBackwards) {
d->repIndex = textCursor().anchor();
}
// Connect highlight signal to code which handles highlighting
// of found text.
connect(d->replace, SIGNAL(highlight(QString,int,int)),
this, SLOT(slotFindHighlight(QString,int,int)));
connect(d->replace, SIGNAL(findNext()), this, SLOT(slotReplaceNext()));
connect(d->replace, SIGNAL(replace(QString,int,int,int)),
this, SLOT(slotReplaceText(QString,int,int,int)));
d->repDlg->close();
slotReplaceNext();
}
void KTextEdit::slotReplaceNext()
{
if (!d->replace)
return;
d->lastReplacedPosition = -1;
if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) {
textCursor().beginEditBlock(); // #48541
viewport()->setUpdatesEnabled(false);
}
KFind::Result res = KFind::NoMatch;
if (d->replace->needData())
d->replace->setData(toPlainText(), d->repIndex);
res = d->replace->replace();
if (!(d->replace->options() & KReplaceDialog::PromptOnReplace)) {
textCursor().endEditBlock(); // #48541
if (d->lastReplacedPosition >= 0) {
QTextCursor tc = textCursor();
tc.setPosition(d->lastReplacedPosition);
setTextCursor(tc);
ensureCursorVisible();
}
viewport()->setUpdatesEnabled(true);
viewport()->update();
}
if (res == KFind::NoMatch) {
d->replace->displayFinalDialog();
d->replace->disconnect(this);
d->replace->deleteLater(); // we are in a slot connected to m_replace, don't delete it right away
d->replace = 0;
ensureCursorVisible();
//or if ( m_replace->shouldRestart() ) { reinit (w/o FromCursor) and call slotReplaceNext(); }
} else {
//m_replace->closeReplaceNextDialog();
}
}
void KTextEdit::slotDoFind()
{
if (!d->findDlg) {
// Should really assert()
return;
}
if( d->findDlg->pattern().isEmpty())
{
delete d->find;
d->find = 0;
return;
}
delete d->find;
d->find = new KFind(d->findDlg->pattern(), d->findDlg->options(), this);
d->findIndex = 0;
if (d->find->options() & KFind::FromCursor || d->find->options() & KFind::FindBackwards) {
d->findIndex = textCursor().anchor();
}
// Connect highlight signal to code which handles highlighting
// of found text.
connect(d->find, SIGNAL(highlight(QString,int,int)),
this, SLOT(slotFindHighlight(QString,int,int)));
connect(d->find, SIGNAL(findNext()), this, SLOT(slotFindNext()));
d->findDlg->close();
d->find->closeFindNextDialog();
slotFindNext();
}
void KTextEdit::slotFindNext()
{
if (!d->find)
return;
if(document()->isEmpty())
{
d->find->disconnect(this);
d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away
d->find = 0;
return;
}
KFind::Result res = KFind::NoMatch;
if (d->find->needData())
d->find->setData(toPlainText(), d->findIndex);
res = d->find->find();
if (res == KFind::NoMatch) {
d->find->displayFinalDialog();
d->find->disconnect(this);
d->find->deleteLater(); // we are in a slot connected to m_find, don't delete right away
d->find = 0;
//or if ( m_find->shouldRestart() ) { reinit (w/o FromCursor) and call slotFindNext(); }
} else {
//m_find->closeFindNextDialog();
}
}
void KTextEdit::slotFind()
{
if( document()->isEmpty() ) // saves having to track the text changes
return;
if ( d->findDlg ) {
KWindowSystem::activateWindow( d->findDlg->winId() );
} else {
d->findDlg = new KFindDialog(this);
connect( d->findDlg, SIGNAL(okClicked()), this, SLOT(slotDoFind()) );
}
d->findDlg->show();
}
void KTextEdit::slotReplace()
{
if( document()->isEmpty() ) // saves having to track the text changes
return;
if ( d->repDlg ) {
KWindowSystem::activateWindow( d->repDlg->winId() );
} else {
d->repDlg = new KReplaceDialog(this, 0,
QStringList(), QStringList(), false);
connect( d->repDlg, SIGNAL(okClicked()), this, SLOT(slotDoReplace()) );
}
d->repDlg->show();
}
void KTextEdit::enableFindReplace( bool enabled )
{
d->findReplaceEnabled = enabled;
}
void KTextEdit::setSpellInterface(KTextEditSpellInterface *spellInterface)
{
d->spellInterface = spellInterface;
}
bool KTextEdit::Private::overrideShortcut(const QKeyEvent* event)
{
const int key = event->key() | event->modifiers();
if ( KStandardShortcut::copy().contains( key ) ) {
return true;
} else if ( KStandardShortcut::paste().contains( key ) ) {
return true;
} else if ( KStandardShortcut::cut().contains( key ) ) {
return true;
} else if ( KStandardShortcut::undo().contains( key ) ) {
return true;
} else if ( KStandardShortcut::redo().contains( key ) ) {
return true;
} else if ( KStandardShortcut::deleteWordBack().contains( key ) ) {
return true;
} else if ( KStandardShortcut::deleteWordForward().contains( key ) ) {
return true;
} else if ( KStandardShortcut::backwardWord().contains( key ) ) {
return true;
} else if ( KStandardShortcut::forwardWord().contains( key ) ) {
return true;
} else if ( KStandardShortcut::next().contains( key ) ) {
return true;
} else if ( KStandardShortcut::prior().contains( key ) ) {
return true;
} else if ( KStandardShortcut::begin().contains( key ) ) {
return true;
} else if ( KStandardShortcut::end().contains( key ) ) {
return true;
} else if ( KStandardShortcut::beginningOfLine().contains( key ) ) {
return true;
} else if ( KStandardShortcut::endOfLine().contains( key ) ) {
return true;
} else if ( KStandardShortcut::pasteSelection().contains( key ) ) {
return true;
} else if (findReplaceEnabled && KStandardShortcut::find().contains(key)) {
return true;
} else if (findReplaceEnabled && KStandardShortcut::findNext().contains(key)) {
return true;
} else if (findReplaceEnabled && KStandardShortcut::replace().contains(key)) {
return true;
} else if (event->matches(QKeySequence::SelectAll)) { // currently missing in QTextEdit
return true;
} else if (event->modifiers() == Qt::ControlModifier &&
(event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) &&
qobject_cast<KDialog*>(parent->window()) ) {
// ignore Ctrl-Return so that KDialogs can close the dialog
return true;
}
return false;
}
void KTextEdit::keyPressEvent( QKeyEvent *event )
{
if (d->handleShortcut(event)) {
event->accept();
}else if (event->modifiers() == Qt::ControlModifier &&
(event->key() == Qt::Key_Return || event->key() == Qt::Key_Enter) &&
qobject_cast<KDialog*>(window()) ) {
event->ignore();
} else {
QTextEdit::keyPressEvent(event);
}
}
void KTextEdit::setClickMessage(const QString &msg)
{
if (msg != d->clickMessage) {
if (!d->clickMessage.isEmpty()) {
d->updateClickMessageRect();
}
d->clickMessage = msg;
if (!d->clickMessage.isEmpty()) {
d->updateClickMessageRect();
}
}
}
QString KTextEdit::clickMessage() const
{
return d->clickMessage;
}
void KTextEdit::paintEvent(QPaintEvent *ev)
{
QTextEdit::paintEvent(ev);
if (!d->clickMessage.isEmpty() && !hasFocus() && document()->isEmpty()) {
QPainter p(viewport());
QFont f = font();
f.setItalic(d->italicizePlaceholder);
p.setFont(f);
QColor color(palette().color(foregroundRole()));
color.setAlphaF(0.5);
p.setPen(color);
int margin = int(document()->documentMargin());
QRect cr = viewport()->rect().adjusted(margin, margin, -margin, -margin);
p.drawText(cr, Qt::AlignTop | Qt::TextWordWrap, d->clickMessage);
}
}
void KTextEdit::focusOutEvent(QFocusEvent *ev)
{
if (!d->clickMessage.isEmpty()) {
d->updateClickMessageRect();
}
QTextEdit::focusOutEvent(ev);
}
#include "ktextedit.moc"
diff --git a/kdeui/widgets/ktoolbar.cpp b/kdeui/widgets/ktoolbar.cpp
index d17ff3964c..c6bd200607 100644
--- a/kdeui/widgets/ktoolbar.cpp
+++ b/kdeui/widgets/ktoolbar.cpp
@@ -1,1398 +1,1428 @@
/* This file is part of the KDE libraries
Copyright
(C) 2000 Reginald Stadlbauer (reggie@kde.org)
(C) 1997, 1998 Stephan Kulow (coolo@kde.org)
(C) 1997, 1998 Mark Donohoe (donohoe@kde.org)
(C) 1997, 1998 Sven Radej (radej@kde.org)
(C) 1997, 1998 Matthias Ettrich (ettrich@kde.org)
(C) 1999 Chris Schlaeger (cs@kde.org)
(C) 1999 Kurt Granroth (granroth@kde.org)
(C) 2005-2006 Hamish Rodda (rodda@kde.org)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "ktoolbar.h"
#include <config.h>
#include <QtCore/QPointer>
#include <QtGui/QDesktopWidget>
#include <QtGui/QFrame>
#include <QtGui/QLayout>
#include <QtGui/QMouseEvent>
#include <QtGui/QToolButton>
#include <QtXml/QDomElement>
#include <kaction.h>
#include <kactioncollection.h>
#include <kapplication.h>
#include <kauthorized.h>
#include <kconfig.h>
#include <kdebug.h>
#include <kedittoolbar.h>
#include <kglobalsettings.h>
#include <kguiitem.h>
#include <kicon.h>
#include <kiconloader.h>
#include <klocale.h>
#include <kxmlguiwindow.h>
#include <kmenu.h>
#include <kstandardaction.h>
#include <ktoggleaction.h>
#include <kxmlguifactory.h>
#include <kconfiggroup.h>
/*
Toolbar settings (e.g. icon size or toolButtonStyle)
=====================================================
We have the following stack of settings (in order of priority) :
- user-specified settings (loaded/saved in KConfig)
- developer-specified settings in the XMLGUI file (if using xmlgui) (cannot change at runtime)
- KDE-global default (user-configurable; can change at runtime)
and when switching between kparts, they are saved as xml in memory,
which, in the unlikely case of no-kmainwindow-autosaving, could be
different from the user-specified settings saved in KConfig and would have
priority over it.
So, in summary, without XML:
Global config / User settings (loaded/saved in kconfig)
and with XML:
Global config / App-XML attributes / User settings (loaded/saved in kconfig)
And all those settings (except the KDE-global defaults) have to be stored in memory
since we cannot retrieve them at random points in time, not knowing the xml document
nor config file that holds these settings. Hence the iconSizeSettings and toolButtonStyleSettings arrays.
For instance, if you change the KDE-global default, whether this makes a change
on a given toolbar depends on whether there are settings at Level_AppXML or Level_UserSettings.
Only if there are no settings at those levels, should the change of KDEDefault make a difference.
*/
enum SettingLevel { Level_KDEDefault, Level_AppXML, Level_UserSettings,
NSettingLevels };
enum { Unset = -1 };
class KToolBar::Private
{
public:
Private(KToolBar *qq)
: q(qq),
isMainToolBar(false),
#ifndef KDE_NO_DEPRECATED
enableContext(true),
#endif
unlockedMovable(true),
- xmlguiClient(0),
contextOrient(0),
contextMode(0),
contextSize(0),
contextButtonTitle(0),
contextShowText(0),
contextButtonAction(0),
contextTop(0),
contextLeft(0),
contextRight(0),
contextBottom(0),
contextIcons(0),
contextTextRight(0),
contextText(0),
contextTextUnder(0),
contextLockAction(0),
dropIndicatorAction(0),
context(0),
dragAction(0)
{
}
void slotAppearanceChanged();
void slotContextAboutToShow();
void slotContextAboutToHide();
void slotContextLeft();
void slotContextRight();
void slotContextShowText();
void slotContextTop();
void slotContextBottom();
void slotContextIcons();
void slotContextText();
void slotContextTextRight();
void slotContextTextUnder();
void slotContextIconSize();
void slotLockToolBars(bool lock);
void init(bool readConfig = true, bool isMainToolBar = false);
QString getPositionAsString() const;
KMenu *contextMenu(const QPoint &globalPos);
void setLocked(bool locked);
void adjustSeparatorVisibility();
void loadKDESettings();
void applyCurrentSettings();
+ QAction *findAction(const QString &actionName, KXMLGUIClient **client = 0) const;
+
static Qt::ToolButtonStyle toolButtonStyleFromString(const QString& style);
static QString toolButtonStyleToString(Qt::ToolButtonStyle);
static Qt::ToolBarArea positionFromString(const QString& position);
KToolBar *q;
bool isMainToolBar : 1;
#ifndef KDE_NO_DEPRECATED
bool enableContext : 1;
#endif
bool unlockedMovable : 1;
static bool s_editable;
static bool s_locked;
- KXMLGUIClient *xmlguiClient;
+ QSet<KXMLGUIClient *> xmlguiClients;
QMenu* contextOrient;
QMenu* contextMode;
QMenu* contextSize;
QAction* contextButtonTitle;
QAction* contextShowText;
QAction* contextButtonAction;
QAction* contextTop;
QAction* contextLeft;
QAction* contextRight;
QAction* contextBottom;
QAction* contextIcons;
QAction* contextTextRight;
QAction* contextText;
QAction* contextTextUnder;
KToggleAction* contextLockAction;
QMap<QAction*,int> contextIconSizes;
class IntSetting
{
public:
IntSetting() {
for (int level = 0; level < NSettingLevels; ++level) {
values[level] = Unset;
}
}
int currentValue() const {
int val = Unset;
for (int level = 0; level < NSettingLevels; ++level) {
if (values[level] != Unset)
val = values[level];
}
return val;
}
// Default value as far as the user is concerned is kde-global + app-xml.
// If currentValue()==defaultValue() then nothing to write into kconfig.
int defaultValue() const {
int val = Unset;
for (int level = 0; level < Level_UserSettings; ++level) {
if (values[level] != Unset)
val = values[level];
}
return val;
}
QString toString() const {
QString str;
for (int level = 0; level < NSettingLevels; ++level) {
str += QString::number(values[level]) + ' ';
}
return str;
}
int& operator[](int index) { return values[index]; }
private:
int values[NSettingLevels];
};
IntSetting iconSizeSettings;
IntSetting toolButtonStyleSettings; // either Qt::ToolButtonStyle or -1, hence "int".
QList<QAction*> actionsBeingDragged;
QAction* dropIndicatorAction;
KMenu* context;
KAction* dragAction;
QPoint dragStartPosition;
};
bool KToolBar::Private::s_editable = false;
bool KToolBar::Private::s_locked = true;
void KToolBar::Private::init(bool readConfig, bool _isMainToolBar)
{
isMainToolBar = _isMainToolBar;
loadKDESettings();
// also read in our configurable settings (for non-xmlgui toolbars)
// KDE5: we can probably remove this, if people save settings then they load them too, e.g. using KMainWindow's autosave.
if (readConfig) {
KConfigGroup cg(KGlobal::config(), QString());
q->applySettings(cg);
}
if (q->mainWindow()) {
// Get notified when settings change
connect(q, SIGNAL(allowedAreasChanged(Qt::ToolBarAreas)),
q->mainWindow(), SLOT(setSettingsDirty()));
connect(q, SIGNAL(iconSizeChanged(QSize)),
q->mainWindow(), SLOT(setSettingsDirty()));
connect(q, SIGNAL(toolButtonStyleChanged(Qt::ToolButtonStyle)),
q->mainWindow(), SLOT(setSettingsDirty()));
connect(q, SIGNAL(movableChanged(bool)),
q->mainWindow(), SLOT(setSettingsDirty()));
connect(q, SIGNAL(orientationChanged(Qt::Orientation)),
q->mainWindow(), SLOT(setSettingsDirty()));
}
if (!KAuthorized::authorize("movable_toolbars"))
q->setMovable(false);
else
q->setMovable(!KToolBar::toolBarsLocked());
connect(q, SIGNAL(movableChanged(bool)),
q, SLOT(slotMovableChanged(bool)));
q->setAcceptDrops(true);
connect(KGlobalSettings::self(), SIGNAL(toolbarAppearanceChanged(int)),
q, SLOT(slotAppearanceChanged()));
connect(KIconLoader::global(), SIGNAL(iconLoaderSettingsChanged()),
q, SLOT(slotAppearanceChanged()));
}
QString KToolBar::Private::getPositionAsString() const
{
// get all of the stuff to save
switch (q->mainWindow()->toolBarArea(const_cast<KToolBar*>(q))) {
case Qt::BottomToolBarArea:
return "Bottom";
case Qt::LeftToolBarArea:
return "Left";
case Qt::RightToolBarArea:
return "Right";
case Qt::TopToolBarArea:
default:
return "Top";
}
}
KMenu *KToolBar::Private::contextMenu(const QPoint &globalPos)
{
if (!context) {
context = new KMenu(q);
contextButtonTitle = context->addTitle(i18nc("@title:menu", "Show Text"));
contextShowText = context->addAction(QString(), q, SLOT(slotContextShowText()));
context->addTitle(i18nc("@title:menu", "Toolbar Settings"));
contextOrient = new KMenu(i18n("Orientation"), context);
contextTop = contextOrient->addAction(i18nc("toolbar position string", "Top"), q, SLOT(slotContextTop()));
contextTop->setChecked(true);
contextLeft = contextOrient->addAction(i18nc("toolbar position string", "Left"), q, SLOT(slotContextLeft()));
contextRight = contextOrient->addAction(i18nc("toolbar position string", "Right"), q, SLOT(slotContextRight()));
contextBottom = contextOrient->addAction(i18nc("toolbar position string", "Bottom"), q, SLOT(slotContextBottom()));
QActionGroup* positionGroup = new QActionGroup(contextOrient);
foreach (QAction* action, contextOrient->actions()) {
action->setActionGroup(positionGroup);
action->setCheckable(true);
}
contextMode = new KMenu(i18n("Text Position"), context);
contextIcons = contextMode->addAction(i18n("Icons Only"), q, SLOT(slotContextIcons()));
contextText = contextMode->addAction(i18n("Text Only"), q, SLOT(slotContextText()));
contextTextRight = contextMode->addAction(i18n("Text Alongside Icons"), q, SLOT(slotContextTextRight()));
contextTextUnder = contextMode->addAction(i18n("Text Under Icons"), q, SLOT(slotContextTextUnder()));
QActionGroup* textGroup = new QActionGroup(contextMode);
foreach (QAction* action, contextMode->actions()) {
action->setActionGroup(textGroup);
action->setCheckable(true);
}
contextSize = new KMenu(i18n("Icon Size"), context);
contextIconSizes.insert(contextSize->addAction(i18nc("@item:inmenu Icon size", "Default"), q, SLOT(slotContextIconSize())),
iconSizeSettings.defaultValue());
// Query the current theme for available sizes
KIconTheme *theme = KIconLoader::global()->theme();
QList<int> avSizes;
if (theme) {
avSizes = theme->querySizes(isMainToolBar ? KIconLoader::MainToolbar : KIconLoader::Toolbar);
}
qSort(avSizes);
if (avSizes.count() < 10) {
// Fixed or threshold type icons
foreach (int it, avSizes) {
QString text;
if (it < 19)
text = i18n("Small (%1x%2)", it, it);
else if (it < 25)
text = i18n("Medium (%1x%2)", it, it);
else if (it < 35)
text = i18n("Large (%1x%2)", it, it);
else
text = i18n("Huge (%1x%2)", it, it);
// save the size in the contextIconSizes map
contextIconSizes.insert(contextSize->addAction(text, q, SLOT(slotContextIconSize())), it);
}
} else {
// Scalable icons.
const int progression[] = { 16, 22, 32, 48, 64, 96, 128, 192, 256 };
for (uint i = 0; i < 9; i++) {
foreach (int it, avSizes) {
if (it >= progression[ i ]) {
QString text;
if (it < 19)
text = i18n("Small (%1x%2)", it, it);
else if (it < 25)
text = i18n("Medium (%1x%2)", it, it);
else if (it < 35)
text = i18n("Large (%1x%2)", it, it);
else
text = i18n("Huge (%1x%2)", it, it);
// save the size in the contextIconSizes map
contextIconSizes.insert(contextSize->addAction(text, q, SLOT(slotContextIconSize())), it);
break;
}
}
}
}
QActionGroup* sizeGroup = new QActionGroup(contextSize);
foreach (QAction* action, contextSize->actions()) {
action->setActionGroup(sizeGroup);
action->setCheckable(true);
}
if (!q->toolBarsLocked() && !q->isMovable())
unlockedMovable = false;
delete contextLockAction;
contextLockAction = new KToggleAction(KIcon("system-lock-screen"), i18n("Lock Toolbar Positions"), q);
contextLockAction->setChecked(q->toolBarsLocked());
connect(contextLockAction, SIGNAL(toggled(bool)), q, SLOT(slotLockToolBars(bool)));
// Now add the actions to the menu
context->addMenu(contextMode);
context->addMenu(contextSize);
context->addMenu(contextOrient);
context->addSeparator();
connect(context, SIGNAL(aboutToShow()), q, SLOT(slotContextAboutToShow()));
}
contextButtonAction = q->actionAt(q->mapFromGlobal(globalPos));
if (contextButtonAction) {
contextShowText->setText(contextButtonAction->text());
contextShowText->setIcon(contextButtonAction->icon());
contextShowText->setCheckable(true);
}
contextOrient->menuAction()->setVisible(!q->toolBarsLocked());
// Unplugging a submenu from abouttohide leads to the popupmenu floating around
// So better simply call that code from after exec() returns (DF)
//connect(context, SIGNAL(aboutToHide()), this, SLOT(slotContextAboutToHide()));
return context;
}
void KToolBar::Private::setLocked(bool locked)
{
if (unlockedMovable)
q->setMovable(!locked);
}
void KToolBar::Private::adjustSeparatorVisibility()
{
bool visibleNonSeparator = false;
int separatorToShow = -1;
for (int index = 0; index < q->actions().count(); ++index) {
QAction* action = q->actions()[ index ];
if (action->isSeparator()) {
if (visibleNonSeparator) {
separatorToShow = index;
visibleNonSeparator = false;
} else {
action->setVisible(false);
}
} else if (!visibleNonSeparator) {
if (action->isVisible()) {
visibleNonSeparator = true;
if (separatorToShow != -1) {
q->actions()[ separatorToShow ]->setVisible(true);
separatorToShow = -1;
}
}
}
}
if (separatorToShow != -1)
q->actions()[ separatorToShow ]->setVisible(false);
}
Qt::ToolButtonStyle KToolBar::Private::toolButtonStyleFromString(const QString & _style)
{
QString style = _style.toLower();
if (style == "textbesideicon" || style == "icontextright")
return Qt::ToolButtonTextBesideIcon;
else if (style == "textundericon" || style == "icontextbottom")
return Qt::ToolButtonTextUnderIcon;
else if (style == "textonly")
return Qt::ToolButtonTextOnly;
else
return Qt::ToolButtonIconOnly;
}
QString KToolBar::Private::toolButtonStyleToString(Qt::ToolButtonStyle style)
{
switch(style)
{
case Qt::ToolButtonIconOnly:
default:
return "IconOnly";
case Qt::ToolButtonTextBesideIcon:
return "TextBesideIcon";
case Qt::ToolButtonTextOnly:
return "TextOnly";
case Qt::ToolButtonTextUnderIcon:
return "TextUnderIcon";
}
}
Qt::ToolBarArea KToolBar::Private::positionFromString(const QString& position)
{
Qt::ToolBarArea newposition = Qt::TopToolBarArea;
if (position == QLatin1String("left")) {
newposition = Qt::LeftToolBarArea;
} else if (position == QLatin1String("bottom")) {
newposition = Qt::BottomToolBarArea;
} else if (position == QLatin1String("right")) {
newposition = Qt::RightToolBarArea;
}
return newposition;
}
// Global setting was changed
void KToolBar::Private::slotAppearanceChanged()
{
loadKDESettings();
applyCurrentSettings();
}
void KToolBar::Private::loadKDESettings()
{
iconSizeSettings[Level_KDEDefault] = q->iconSizeDefault();
if (isMainToolBar) {
toolButtonStyleSettings[Level_KDEDefault] = q->toolButtonStyleSetting();
} else {
const QString fallBack = toolButtonStyleToString(Qt::ToolButtonTextBesideIcon);
/**
TODO: if we get complaints about text beside icons on small screens,
try the following code out on such systems - aseigo.
// if we are on a small screen with a non-landscape ratio, then
// we revert to text under icons since width is probably not our
// friend in such cases
QDesktopWidget *desktop = QApplication::desktop();
QRect screenGeom = desktop->screenGeometry(desktop->primaryScreen());
qreal ratio = screenGeom.width() / qreal(screenGeom.height());
if (screenGeom.width() < 1024 && ratio <= 1.4) {
fallBack = "TextUnderIcon";
}
**/
KConfigGroup group(KGlobal::config(), "Toolbar style");
const QString value = group.readEntry("ToolButtonStyleOtherToolbars", fallBack);
toolButtonStyleSettings[Level_KDEDefault] = KToolBar::Private::toolButtonStyleFromString(value);
}
}
// Call this after changing something in d->iconSizeSettings or d->toolButtonStyleSettings
void KToolBar::Private::applyCurrentSettings()
{
//kDebug() << q->objectName() << "iconSizeSettings:" << iconSizeSettings.toString() << "->" << iconSizeSettings.currentValue();
const int currentIconSize = iconSizeSettings.currentValue();
q->setIconSize(QSize(currentIconSize, currentIconSize));
//kDebug() << q->objectName() << "toolButtonStyleSettings:" << toolButtonStyleSettings.toString() << "->" << toolButtonStyleSettings.currentValue();
q->setToolButtonStyle(static_cast<Qt::ToolButtonStyle>(toolButtonStyleSettings.currentValue()));
// And remember to save the new look later
KMainWindow *kmw = q->mainWindow();
if (kmw)
kmw->setSettingsDirty();
}
+QAction *KToolBar::Private::findAction(const QString &actionName, KXMLGUIClient **clientOut) const
+{
+ foreach (KXMLGUIClient* client, xmlguiClients) {
+ QAction* action = client->actionCollection()->action(actionName);
+ if (action) {
+ if (clientOut) {
+ *clientOut = client;
+ }
+ return action;
+ }
+ }
+ return 0;
+}
+
void KToolBar::Private::slotContextAboutToShow()
{
/**
* The idea here is to reuse the "static" part of the menu to save time.
* But the "Toolbars" action is dynamic (can be a single action or a submenu)
* and ToolBarHandler::setupActions() deletes it, so better not keep it around.
* So we currently plug/unplug the last two actions of the menu.
* Another way would be to keep around the actions and plug them all into a (new each time) popupmenu.
*/
KXmlGuiWindow *kmw = qobject_cast<KXmlGuiWindow *>(q->mainWindow());
// try to find "configure toolbars" action
QAction *configureAction = 0;
const char* actionName = KStandardAction::name(KStandardAction::ConfigureToolbars);
- if (xmlguiClient) {
- configureAction = xmlguiClient->actionCollection()->action(actionName);
- }
+ configureAction = findAction(actionName);
if (!configureAction && kmw) {
configureAction = kmw->actionCollection()->action(actionName);
}
if (configureAction) {
context->addAction(configureAction);
}
context->addAction(contextLockAction);
if (kmw) {
kmw->setupToolbarMenuActions();
// Only allow hiding a toolbar if the action is also plugged somewhere else (e.g. menubar)
QAction *tbAction = kmw->toolBarMenuAction();
if (!q->toolBarsLocked() && tbAction && tbAction->associatedWidgets().count() > 0)
context->addAction(tbAction);
}
KEditToolBar::setGlobalDefaultToolBar(q->QObject::objectName().toLatin1().constData());
// Check the actions that should be checked
switch (q->toolButtonStyle()) {
case Qt::ToolButtonIconOnly:
default:
contextIcons->setChecked(true);
break;
case Qt::ToolButtonTextBesideIcon:
contextTextRight->setChecked(true);
break;
case Qt::ToolButtonTextOnly:
contextText->setChecked(true);
break;
case Qt::ToolButtonTextUnderIcon:
contextTextUnder->setChecked(true);
break;
}
QMapIterator< QAction*, int > it = contextIconSizes;
while (it.hasNext()) {
it.next();
if (it.value() == q->iconSize().width()) {
it.key()->setChecked(true);
break;
}
}
switch (q->mainWindow()->toolBarArea(q)) {
case Qt::BottomToolBarArea:
contextBottom->setChecked(true);
break;
case Qt::LeftToolBarArea:
contextLeft->setChecked(true);
break;
case Qt::RightToolBarArea:
contextRight->setChecked(true);
break;
default:
case Qt::TopToolBarArea:
contextTop->setChecked(true);
break;
}
const bool showButtonSettings = contextButtonAction
&& !contextShowText->text().isEmpty()
&& contextTextRight->isChecked();
contextButtonTitle->setVisible(showButtonSettings);
contextShowText->setVisible(showButtonSettings);
if (showButtonSettings) {
contextShowText->setChecked(contextButtonAction->priority() >= QAction::NormalPriority);
}
}
void KToolBar::Private::slotContextAboutToHide()
{
// We have to unplug whatever slotContextAboutToShow plugged into the menu.
// Unplug the toolbar menu action
KXmlGuiWindow *kmw = qobject_cast<KXmlGuiWindow *>(q->mainWindow());
if (kmw && kmw->toolBarMenuAction()) {
if (kmw->toolBarMenuAction()->associatedWidgets().count() > 1) {
context->removeAction(kmw->toolBarMenuAction());
}
}
// Unplug the configure toolbars action too, since it's afterwards anyway
QAction *configureAction = 0;
const char* actionName = KStandardAction::name(KStandardAction::ConfigureToolbars);
- if (xmlguiClient) {
- configureAction = xmlguiClient->actionCollection()->action(actionName);
- }
+ configureAction = findAction(actionName);
if (!configureAction && kmw) {
configureAction = kmw->actionCollection()->action(actionName);
}
if (configureAction) {
context->removeAction(configureAction);
}
context->removeAction(contextLockAction);
}
void KToolBar::Private::slotContextLeft()
{
q->mainWindow()->addToolBar(Qt::LeftToolBarArea, q);
}
void KToolBar::Private::slotContextRight()
{
q->mainWindow()->addToolBar(Qt::RightToolBarArea, q);
}
void KToolBar::Private::slotContextShowText()
{
Q_ASSERT(contextButtonAction);
const QAction::Priority priority = contextShowText->isChecked()
? QAction::NormalPriority : QAction::LowPriority;
contextButtonAction->setPriority(priority);
+ // Find to which xml file and componentData the action belongs to
+ KComponentData componentData;
+ QString filename;
+ KXMLGUIClient *client;
+ if (findAction(contextButtonAction->objectName(), &client)) {
+ componentData = client->componentData();
+ filename = client->xmlFile();
+ }
+ if (filename.isEmpty()) {
+ componentData = KGlobal::mainComponent();
+ filename = componentData.componentName() + "ui.rc";
+ }
+
// Save the priority state of the action
- const KComponentData& componentData = KGlobal::mainComponent();
- const QString componentName = componentData.componentName() + "ui.rc";
- const QString configFile = KXMLGUIFactory::readConfigFile(componentName, componentData);
+ const QString configFile = KXMLGUIFactory::readConfigFile(filename, componentData);
QDomDocument document;
document.setContent(configFile);
QDomElement elem = KXMLGUIFactory::actionPropertiesElement(document);
QDomElement actionElem = KXMLGUIFactory::findActionByName(elem, contextButtonAction->objectName(), true);
actionElem.setAttribute("priority", priority);
- KXMLGUIFactory::saveConfigFile(document, componentName, componentData);
+ KXMLGUIFactory::saveConfigFile(document, filename, componentData);
}
void KToolBar::Private::slotContextTop()
{
q->mainWindow()->addToolBar(Qt::TopToolBarArea, q);
}
void KToolBar::Private::slotContextBottom()
{
q->mainWindow()->addToolBar(Qt::BottomToolBarArea, q);
}
void KToolBar::Private::slotContextIcons()
{
q->setToolButtonStyle(Qt::ToolButtonIconOnly);
toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle();
}
void KToolBar::Private::slotContextText()
{
q->setToolButtonStyle(Qt::ToolButtonTextOnly);
toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle();
}
void KToolBar::Private::slotContextTextUnder()
{
q->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle();
}
void KToolBar::Private::slotContextTextRight()
{
q->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
toolButtonStyleSettings[Level_UserSettings] = q->toolButtonStyle();
}
void KToolBar::Private::slotContextIconSize()
{
QAction* action = qobject_cast<QAction*>(q->sender());
if (action && contextIconSizes.contains(action)) {
const int iconSize = contextIconSizes.value(action);
q->setIconDimensions(iconSize);
}
}
void KToolBar::Private::slotLockToolBars(bool lock)
{
q->setToolBarsLocked(lock);
}
KToolBar::KToolBar(QWidget *parent, bool isMainToolBar, bool readConfig)
: QToolBar(parent),
d(new Private(this))
{
d->init(readConfig, isMainToolBar);
// KToolBar is auto-added to the top area of the main window if parent is a QMainWindow
if (QMainWindow* mw = qobject_cast<QMainWindow*>(parent))
mw->addToolBar(this);
}
KToolBar::KToolBar(const QString& objectName, QWidget *parent, bool readConfig)
: QToolBar(parent),
d(new Private(this))
{
setObjectName(objectName);
// mainToolBar -> isMainToolBar = true -> buttonStyle is configurable
// others -> isMainToolBar = false -> ### hardcoded default for buttonStyle !!! should be configurable? -> hidden key added
d->init(readConfig, objectName == "mainToolBar");
// KToolBar is auto-added to the top area of the main window if parent is a QMainWindow
if (QMainWindow* mw = qobject_cast<QMainWindow*>(parent))
mw->addToolBar(this);
}
KToolBar::KToolBar(const QString& objectName, QMainWindow* parent, Qt::ToolBarArea area,
bool newLine, bool isMainToolBar, bool readConfig)
: QToolBar(parent),
d(new Private(this))
{
setObjectName(objectName);
d->init(readConfig, isMainToolBar);
if (newLine)
mainWindow()->addToolBarBreak(area);
mainWindow()->addToolBar(area, this);
if (newLine)
mainWindow()->addToolBarBreak(area);
}
KToolBar::~KToolBar()
{
delete d->contextLockAction;
delete d;
}
#ifndef KDE_NO_DEPRECATED
void KToolBar::setContextMenuEnabled(bool enable)
{
d->enableContext = enable;
}
#endif
#ifndef KDE_NO_DEPRECATED
bool KToolBar::contextMenuEnabled() const
{
return d->enableContext;
}
#endif
void KToolBar::saveSettings(KConfigGroup &cg)
{
Q_ASSERT(!cg.name().isEmpty());
cg.deleteEntry("Hidden"); // remove old key to avoid bugs from the compat code in applySettings. KDE5: remove.
const int currentIconSize = iconSize().width();
//kDebug() << objectName() << currentIconSize << d->iconSizeSettings.toString() << "defaultValue=" << d->iconSizeSettings.defaultValue();
if (!cg.hasDefault("IconSize") && currentIconSize == d->iconSizeSettings.defaultValue()) {
cg.revertToDefault("IconSize");
d->iconSizeSettings[Level_UserSettings] = Unset;
} else {
cg.writeEntry("IconSize", currentIconSize);
d->iconSizeSettings[Level_UserSettings] = currentIconSize;
}
const Qt::ToolButtonStyle currentToolButtonStyle = toolButtonStyle();
if (!cg.hasDefault("ToolButtonStyle") && currentToolButtonStyle == d->toolButtonStyleSettings.defaultValue()) {
cg.revertToDefault("ToolButtonStyle");
d->toolButtonStyleSettings[Level_UserSettings] = Unset;
} else {
cg.writeEntry("ToolButtonStyle", d->toolButtonStyleToString(currentToolButtonStyle));
d->toolButtonStyleSettings[Level_UserSettings] = currentToolButtonStyle;
}
}
+#ifndef KDE_NO_DEPRECATED
void KToolBar::setXMLGUIClient(KXMLGUIClient *client)
{
- d->xmlguiClient = client;
+ d->xmlguiClients.clear();
+ d->xmlguiClients << client;
+}
+#endif
+
+void KToolBar::addXMLGUIClient( KXMLGUIClient *client )
+{
+ d->xmlguiClients << client;
}
void KToolBar::contextMenuEvent(QContextMenuEvent* event)
{
#ifndef KDE_NO_DEPRECATED
if (mainWindow() && d->enableContext) {
QPointer<KToolBar> guard(this);
const QPoint globalPos = event->globalPos();
d->contextMenu(globalPos)->exec(globalPos);
// "Configure Toolbars" recreates toolbars, so we might not exist anymore.
if (guard) {
d->slotContextAboutToHide();
}
return;
}
#endif
QToolBar::contextMenuEvent(event);
}
Qt::ToolButtonStyle KToolBar::toolButtonStyleSetting()
{
KConfigGroup group(KGlobal::config(), "Toolbar style");
const QString fallback = Private::toolButtonStyleToString(Qt::ToolButtonTextBesideIcon);
return KToolBar::Private::toolButtonStyleFromString(group.readEntry("ToolButtonStyle", fallback));
}
void KToolBar::loadState(const QDomElement &element)
{
QMainWindow *mw = mainWindow();
if (!mw)
return;
{
QDomNode textNode = element.namedItem("text");
QByteArray text;
QByteArray context;
if (textNode.isElement())
{
QDomElement textElement = textNode.toElement();
text = textElement.text().toUtf8();
context = textElement.attribute("context").toUtf8();
}
else
{
textNode = element.namedItem("Text");
if (textNode.isElement())
{
QDomElement textElement = textNode.toElement();
text = textElement.text().toUtf8();
context = textElement.attribute("context").toUtf8();
}
}
QString i18nText;
if (!text.isEmpty() && !context.isEmpty())
i18nText = i18nc(context, text);
else if (!text.isEmpty())
i18nText = i18n(text);
if (!i18nText.isEmpty())
setWindowTitle(i18nText);
}
/*
This method is called in order to load toolbar settings from XML.
However this can be used in two rather different cases:
- for the initial loading of the app's XML. In that case the settings
are only the defaults (Level_AppXML), the user's KConfig settings will override them
- for later re-loading when switching between parts in KXMLGUIFactory.
In that case the XML contains the final settings, not the defaults.
We do need the defaults, and the toolbar might have been completely
deleted and recreated meanwhile. So we store the app-default settings
into the XML.
*/
bool loadingAppDefaults = true;
if (element.hasAttribute("tempXml")) {
// this isn't the first time, so the app-xml defaults have been saved into the (in-memory) XML
loadingAppDefaults = false;
const QString iconSizeDefault = element.attribute("iconSizeDefault");
if (!iconSizeDefault.isEmpty()) {
d->iconSizeSettings[Level_AppXML] = iconSizeDefault.toInt();
}
const QString toolButtonStyleDefault = element.attribute("toolButtonStyleDefault");
if (!toolButtonStyleDefault.isEmpty()) {
d->toolButtonStyleSettings[Level_AppXML] = d->toolButtonStyleFromString(toolButtonStyleDefault);
}
} else {
// loading app defaults
bool newLine = false;
QString attrNewLine = element.attribute("newline").toLower();
if (!attrNewLine.isEmpty())
newLine = attrNewLine == "true";
if (newLine && mw)
mw->insertToolBarBreak(this);
}
int newIconSize = -1;
if (element.hasAttribute("iconSize")) {
bool ok;
newIconSize = element.attribute("iconSize").trimmed().toInt(&ok);
if (!ok)
newIconSize = -1;
}
if (newIconSize != -1)
d->iconSizeSettings[loadingAppDefaults ? Level_AppXML : Level_UserSettings] = newIconSize;
const QString newToolButtonStyle = element.attribute("iconText");
if (!newToolButtonStyle.isEmpty())
d->toolButtonStyleSettings[loadingAppDefaults ? Level_AppXML : Level_UserSettings] = d->toolButtonStyleFromString(newToolButtonStyle);
bool hidden = false;
{
QString attrHidden = element.attribute("hidden").toLower();
if (!attrHidden.isEmpty())
hidden = attrHidden == "true";
}
Qt::ToolBarArea pos = Qt::NoToolBarArea;
{
QString attrPosition = element.attribute("position").toLower();
if (!attrPosition.isEmpty())
pos = KToolBar::Private::positionFromString(attrPosition);
}
if (pos != Qt::NoToolBarArea)
mw->addToolBar(pos, this);
setVisible(!hidden);
d->applyCurrentSettings();
}
// Called when switching between xmlgui clients, in order to find any unsaved settings
// again when switching back to the current xmlgui client.
void KToolBar::saveState(QDomElement &current) const
{
Q_ASSERT(!current.isNull());
current.setAttribute("tempXml", "true");
current.setAttribute("noMerge", "1");
current.setAttribute("position", d->getPositionAsString().toLower());
current.setAttribute("hidden", isHidden() ? "true" : "false");
const int currentIconSize = iconSize().width();
if (currentIconSize == d->iconSizeSettings.defaultValue())
current.removeAttribute("iconSize");
else
current.setAttribute("iconSize", iconSize().width());
if (toolButtonStyle() == d->toolButtonStyleSettings.defaultValue())
current.removeAttribute("iconText");
else
current.setAttribute("iconText", d->toolButtonStyleToString(toolButtonStyle()));
// Note: if this method is used by more than KXMLGUIBuilder, e.g. to save XML settings to *disk*,
// then the stuff below shouldn't always be done. This is not the case currently though.
if (d->iconSizeSettings[Level_AppXML] != Unset) {
current.setAttribute("iconSizeDefault", d->iconSizeSettings[Level_AppXML]);
}
if (d->toolButtonStyleSettings[Level_AppXML] != Unset) {
const Qt::ToolButtonStyle bs = static_cast<Qt::ToolButtonStyle>(d->toolButtonStyleSettings[Level_AppXML]);
current.setAttribute("toolButtonStyleDefault", d->toolButtonStyleToString(bs));
}
}
// called by KMainWindow::applyMainWindowSettings to read from the user settings
void KToolBar::applySettings(const KConfigGroup &cg, bool forceGlobal)
{
Q_ASSERT(!cg.name().isEmpty());
Q_UNUSED(forceGlobal); // KDE5: remove
// a small leftover from kde3: separate bool for hidden/shown. But it's also part of saveMainWindowSettings,
// it is not really useful anymore, except in the unlikely case where someone would call this by hand.
// KDE5: remove the block below
if (cg.hasKey("Hidden")) {
const bool hidden = cg.readEntry("Hidden", false);
if (hidden)
hide();
else {
show();
}
}
if (cg.hasKey("IconSize")) {
d->iconSizeSettings[Level_UserSettings] = cg.readEntry("IconSize", 0);
}
if (cg.hasKey("ToolButtonStyle")) {
d->toolButtonStyleSettings[Level_UserSettings] = d->toolButtonStyleFromString(cg.readEntry("ToolButtonStyle", QString()));
}
d->applyCurrentSettings();
}
KMainWindow * KToolBar::mainWindow() const
{
return qobject_cast<KMainWindow*>(const_cast<QObject*>(parent()));
}
void KToolBar::setIconDimensions(int size)
{
QToolBar::setIconSize(QSize(size, size));
d->iconSizeSettings[Level_UserSettings] = size;
}
int KToolBar::iconSizeDefault() const
{
return KIconLoader::global()->currentSize(d->isMainToolBar ? KIconLoader::MainToolbar : KIconLoader::Toolbar);
}
void KToolBar::slotMovableChanged(bool movable)
{
if (movable && !KAuthorized::authorize("movable_toolbars"))
setMovable(false);
}
void KToolBar::dragEnterEvent(QDragEnterEvent *event)
{
if (toolBarsEditable() && event->proposedAction() & (Qt::CopyAction | Qt::MoveAction) &&
event->mimeData()->hasFormat("application/x-kde-action-list")) {
QByteArray data = event->mimeData()->data("application/x-kde-action-list");
QDataStream stream(data);
QStringList actionNames;
stream >> actionNames;
foreach (const QString& actionName, actionNames) {
foreach (KActionCollection* ac, KActionCollection::allCollections()) {
QAction* newAction = ac->action(actionName.toAscii().constData());
if (newAction) {
d->actionsBeingDragged.append(newAction);
break;
}
}
}
if (d->actionsBeingDragged.count()) {
QAction* overAction = actionAt(event->pos());
QFrame* dropIndicatorWidget = new QFrame(this);
dropIndicatorWidget->resize(8, height() - 4);
dropIndicatorWidget->setFrameShape(QFrame::VLine);
dropIndicatorWidget->setLineWidth(3);
d->dropIndicatorAction = insertWidget(overAction, dropIndicatorWidget);
insertAction(overAction, d->dropIndicatorAction);
event->acceptProposedAction();
return;
}
}
QToolBar::dragEnterEvent(event);
}
void KToolBar::dragMoveEvent(QDragMoveEvent *event)
{
if (toolBarsEditable())
forever {
if (d->dropIndicatorAction) {
QAction* overAction = 0L;
foreach (QAction* action, actions()) {
// want to make it feel that half way across an action you're dropping on the other side of it
QWidget* widget = widgetForAction(action);
if (event->pos().x() < widget->pos().x() + (widget->width() / 2)) {
overAction = action;
break;
}
}
if (overAction != d->dropIndicatorAction) {
// Check to see if the indicator is already in the right spot
int dropIndicatorIndex = actions().indexOf(d->dropIndicatorAction);
if (dropIndicatorIndex + 1 < actions().count()) {
if (actions()[ dropIndicatorIndex + 1 ] == overAction)
break;
} else if (!overAction) {
break;
}
insertAction(overAction, d->dropIndicatorAction);
}
event->accept();
return;
}
break;
}
QToolBar::dragMoveEvent(event);
}
void KToolBar::dragLeaveEvent(QDragLeaveEvent *event)
{
// Want to clear this even if toolBarsEditable was changed mid-drag (unlikey)
delete d->dropIndicatorAction;
d->dropIndicatorAction = 0L;
d->actionsBeingDragged.clear();
if (toolBarsEditable()) {
event->accept();
return;
}
QToolBar::dragLeaveEvent(event);
}
void KToolBar::dropEvent(QDropEvent *event)
{
if (toolBarsEditable()) {
foreach (QAction* action, d->actionsBeingDragged) {
if (actions().contains(action))
removeAction(action);
insertAction(d->dropIndicatorAction, action);
}
}
// Want to clear this even if toolBarsEditable was changed mid-drag (unlikey)
delete d->dropIndicatorAction;
d->dropIndicatorAction = 0L;
d->actionsBeingDragged.clear();
if (toolBarsEditable()) {
event->accept();
return;
}
QToolBar::dropEvent(event);
}
void KToolBar::mousePressEvent(QMouseEvent *event)
{
if (toolBarsEditable() && event->button() == Qt::LeftButton) {
if (KAction* action = qobject_cast<KAction*>(actionAt(event->pos()))) {
d->dragAction = action;
d->dragStartPosition = event->pos();
event->accept();
return;
}
}
QToolBar::mousePressEvent(event);
}
void KToolBar::mouseMoveEvent(QMouseEvent *event)
{
if (!toolBarsEditable() || !d->dragAction)
return QToolBar::mouseMoveEvent(event);
if ((event->pos() - d->dragStartPosition).manhattanLength() < QApplication::startDragDistance()) {
event->accept();
return;
}
QDrag *drag = new QDrag(this);
QMimeData *mimeData = new QMimeData;
QByteArray data;
{
QDataStream stream(&data, QIODevice::WriteOnly);
QStringList actionNames;
actionNames << d->dragAction->objectName();
stream << actionNames;
}
mimeData->setData("application/x-kde-action-list", data);
drag->setMimeData(mimeData);
Qt::DropAction dropAction = drag->start(Qt::MoveAction);
if (dropAction == Qt::MoveAction)
// Only remove from this toolbar if it was moved to another toolbar
// Otherwise the receiver moves it.
if (drag->target() != this)
removeAction(d->dragAction);
d->dragAction = 0L;
event->accept();
}
void KToolBar::mouseReleaseEvent(QMouseEvent *event)
{
// Want to clear this even if toolBarsEditable was changed mid-drag (unlikey)
if (d->dragAction) {
d->dragAction = 0L;
event->accept();
return;
}
QToolBar::mouseReleaseEvent(event);
}
bool KToolBar::eventFilter(QObject * watched, QEvent * event)
{
// Generate context menu events for disabled buttons too...
if (event->type() == QEvent::MouseButtonPress) {
QMouseEvent* me = static_cast<QMouseEvent*>(event);
if (me->buttons() & Qt::RightButton)
if (QWidget* ww = qobject_cast<QWidget*>(watched))
if (ww->parent() == this && !ww->isEnabled())
QCoreApplication::postEvent(this, new QContextMenuEvent(QContextMenuEvent::Mouse, me->pos(), me->globalPos()));
} else if (event->type() == QEvent::ParentChange) {
// Make sure we're not leaving stale event filters around,
// when a child is reparented somewhere else
if (QWidget* ww = qobject_cast<QWidget*>(watched)) {
if (!this->isAncestorOf(ww)) {
// New parent is not a subwidget - remove event filter
ww->removeEventFilter(this);
foreach (QWidget* child, ww->findChildren<QWidget*>())
child->removeEventFilter(this);
}
}
}
QToolButton* tb;
if ((tb = qobject_cast<QToolButton*>(watched))) {
const QList<QAction*> tbActions = tb->actions();
if (!tbActions.isEmpty()) {
// Handle MMB on toolbar buttons
if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonRelease) {
QMouseEvent* me = static_cast<QMouseEvent*>(event);
if (me->button() == Qt::MidButton /*&&
act->receivers(SIGNAL(triggered(Qt::MouseButtons,Qt::KeyboardModifiers)))*/) {
QAction* act = tbActions.first();
if (me->type() == QEvent::MouseButtonPress)
tb->setDown(act->isEnabled());
else {
tb->setDown(false);
if (act->isEnabled()) {
QMetaObject::invokeMethod(act, "triggered", Qt::DirectConnection,
Q_ARG(Qt::MouseButtons, me->button()),
Q_ARG(Qt::KeyboardModifiers, QApplication::keyboardModifiers()));
}
}
}
}
// CJK languages use more verbose accelerator marker: they add a Latin
// letter in parenthesis, and put accelerator on that. Hence, the default
// removal of ampersand only may not be enough there, instead the whole
// parenthesis construct should be removed. Use KLocale's method to do this.
if (event->type() == QEvent::Show || event->type() == QEvent::Paint || event->type() == QEvent::EnabledChange) {
QAction *act = tb->defaultAction();
if (act) {
const QString text = KGlobal::locale()->removeAcceleratorMarker(act->iconText().isEmpty() ? act->text() : act->iconText());
const QString toolTip = KGlobal::locale()->removeAcceleratorMarker(act->toolTip());
// Filtering messages requested by translators (scripting).
tb->setText(i18nc("@action:intoolbar Text label of toolbar button", "%1", text));
tb->setToolTip(i18nc("@info:tooltip Tooltip of toolbar button", "%1", toolTip));
}
}
}
}
// Redirect mouse events to the toolbar when drag + drop editing is enabled
if (toolBarsEditable()) {
if (QWidget* ww = qobject_cast<QWidget*>(watched)) {
switch (event->type()) {
case QEvent::MouseButtonPress: {
QMouseEvent* me = static_cast<QMouseEvent*>(event);
QMouseEvent newEvent(me->type(), mapFromGlobal(ww->mapToGlobal(me->pos())), me->globalPos(),
me->button(), me->buttons(), me->modifiers());
mousePressEvent(&newEvent);
return true;
}
case QEvent::MouseMove: {
QMouseEvent* me = static_cast<QMouseEvent*>(event);
QMouseEvent newEvent(me->type(), mapFromGlobal(ww->mapToGlobal(me->pos())), me->globalPos(),
me->button(), me->buttons(), me->modifiers());
mouseMoveEvent(&newEvent);
return true;
}
case QEvent::MouseButtonRelease: {
QMouseEvent* me = static_cast<QMouseEvent*>(event);
QMouseEvent newEvent(me->type(), mapFromGlobal(ww->mapToGlobal(me->pos())), me->globalPos(),
me->button(), me->buttons(), me->modifiers());
mouseReleaseEvent(&newEvent);
return true;
}
default:
break;
}
}
}
return QToolBar::eventFilter(watched, event);
}
void KToolBar::actionEvent(QActionEvent * event)
{
if (event->type() == QEvent::ActionRemoved) {
QWidget* widget = widgetForAction(event->action());
if (widget) {
widget->removeEventFilter(this);
foreach (QWidget* child, widget->findChildren<QWidget*>())
child->removeEventFilter(this);
}
}
QToolBar::actionEvent(event);
if (event->type() == QEvent::ActionAdded) {
QWidget* widget = widgetForAction(event->action());
if (widget) {
widget->installEventFilter(this);
foreach (QWidget* child, widget->findChildren<QWidget*>())
child->installEventFilter(this);
// Center widgets that do not have any use for more space. See bug 165274
if (!(widget->sizePolicy().horizontalPolicy() & QSizePolicy::GrowFlag)
// ... but do not center when using text besides icon in vertical toolbar. See bug 243196
&& !(orientation() == Qt::Vertical && toolButtonStyle() == Qt::ToolButtonTextBesideIcon)) {
const int index = layout()->indexOf(widget);
if (index != -1) {
layout()->itemAt(index)->setAlignment(Qt::AlignJustify);
}
}
}
}
d->adjustSeparatorVisibility();
}
bool KToolBar::toolBarsEditable()
{
return KToolBar::Private::s_editable;
}
void KToolBar::setToolBarsEditable(bool editable)
{
if (KToolBar::Private::s_editable != editable) {
KToolBar::Private::s_editable = editable;
}
}
void KToolBar::setToolBarsLocked(bool locked)
{
if (KToolBar::Private::s_locked != locked) {
KToolBar::Private::s_locked = locked;
foreach (KMainWindow* mw, KMainWindow::memberList()) {
foreach (KToolBar* toolbar, mw->findChildren<KToolBar*>()) {
toolbar->d->setLocked(locked);
}
}
}
}
bool KToolBar::toolBarsLocked()
{
return KToolBar::Private::s_locked;
}
#include "ktoolbar.moc"
diff --git a/kdeui/widgets/ktoolbar.h b/kdeui/widgets/ktoolbar.h
index 69c482eab0..c78263f206 100644
--- a/kdeui/widgets/ktoolbar.h
+++ b/kdeui/widgets/ktoolbar.h
@@ -1,248 +1,257 @@
/* This file is part of the KDE libraries
Copyright (C) 2000 Reginald Stadlbauer (reggie@kde.org)
(C) 1997, 1998 Stephan Kulow (coolo@kde.org)
(C) 1997, 1998 Sven Radej (radej@kde.org)
(C) 1997, 1998 Mark Donohoe (donohoe@kde.org)
(C) 1997, 1998 Matthias Ettrich (ettrich@kde.org)
(C) 1999, 2000 Kurt Granroth (granroth@kde.org)
(C) 2005-2006 Hamish Rodda (rodda@kde.org)
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KTOOLBAR_H
#define KTOOLBAR_H
#include <kdeui_export.h>
#include <QtGui/QToolBar>
class QDomElement;
class KConfigGroup;
class KConfig;
class KMainWindow;
class KXMLGUIClient;
/**
* @short Floatable toolbar with auto resize.
*
* A KDE-style toolbar.
*
* KToolBar can be used as a standalone widget, but KMainWindow
* provides easy factories and management of one or more toolbars.
*
* KToolBar uses a global config group to load toolbar settings on
* construction. It will reread this config group on a
* KApplication::appearanceChanged() signal.
*
* @author Reginald Stadlbauer <reggie@kde.org>, Stephan Kulow <coolo@kde.org>, Sven Radej <radej@kde.org>, Hamish Rodda <rodda@kde.org>.
*/
class KDEUI_EXPORT KToolBar : public QToolBar
{
Q_OBJECT
public:
/**
* Constructor.
*
* This constructor takes care of adding the toolbar to the mainwindow,
* if @p parent is a QMainWindow.
*
* Normally KDE applications do not call this directly, they either
* call KMainWindow::toolBar(name), or they use XML-GUI and specify
* toolbars using XML.
*
* @param parent The standard toolbar parent (usually a KMainWindow)
* @param isMainToolBar True for the "main toolbar", false for other toolbars. Different settings apply.
* @param readConfig whether to apply the configuration (global and application-specific)
*/
explicit KToolBar(QWidget *parent, bool isMainToolBar = false, bool readConfig = true);
// KDE5: remove. The one below is preferred so that all debug output from init() shows the right objectName already,
// and so that isMainToolBar() and iconSizeDefault() return correct values during loading too.
/**
* Constructor.
*
* This constructor takes care of adding the toolbar to the mainwindow,
* if @p parent is a QMainWindow.
*
* Normally KDE applications do not call this directly, they either
* call KMainWindow::toolBar(name), or they use XML-GUI and specify
* toolbars using XML.
*
* @param objectName The QObject name of this toolbar, required so that QMainWindow can save and load the toolbar position,
* and so that KToolBar can find out if it's the main toolbar.
* @param parent The standard toolbar parent (usually a KMainWindow)
* @param readConfig whether to apply the configuration (global and application-specific)
*/
explicit KToolBar(const QString& objectName, QWidget* parent, bool readConfig = true);
/**
* Alternate constructor with additional arguments, e.g. to choose in which area
* the toolbar should be auto-added. This is rarely used in KDE. When using XMLGUI
* you can specify this as an xml attribute instead.
*
* @param objectName The QObject name of this toolbar, required so that QMainWindow can save and load the toolbar position
* @param parentWindow The window that should be the parent of this toolbar
* @param area The position of the toolbar. Usually Qt::TopToolBarArea.
* @param newLine If true, start a new line in the dock for this toolbar.
* @param isMainToolBar True for the "main toolbar", false for other toolbars. Different settings apply.
* @param readConfig whether to apply the configuration (global and application-specific)
*/
KToolBar(const QString& objectName, QMainWindow* parentWindow, Qt::ToolBarArea area, bool newLine = false,
bool isMainToolBar = false, bool readConfig = true); // KDE5: remove, I don't think anyone is using this.
/**
* Destroys the toolbar.
*/
virtual ~KToolBar();
/**
* Returns the main window that this toolbar is docked with.
*/
KMainWindow* mainWindow() const;
/**
* Convenience function to set icon size
*/
void setIconDimensions( int size );
/**
* Returns the default size for this type of toolbar.
*
* @return the default size for this type of toolbar.
*/
int iconSizeDefault() const; // KDE5: hide from public API. Doesn't make sense to export this, and it isn't used.
/**
* This allows you to enable or disable the context menu.
*
* @param enable If false, then the context menu will be disabled
* @deprecated use setContextMenuPolicy
*/
#ifndef KDE_NO_DEPRECATED
KDE_DEPRECATED void setContextMenuEnabled( bool enable = true );
#endif
/**
* Returns the context menu enabled flag
* @return true if the context menu is disabled
* @deprecated use contextMenuPolicy
*/
#ifndef KDE_NO_DEPRECATED
KDE_DEPRECATED bool contextMenuEnabled() const;
#endif
/**
* Save the toolbar settings to group @p configGroup in @p config.
*/
void saveSettings( KConfigGroup &cg );
/**
* Read the toolbar settings from group @p configGroup in @p config
* and apply them.
*
* @param forceGlobal is deprecated. In kde3 it used to mean
* "force global settings, i.e. ignore @p cg", but only for visibility/position/index,
* not for icon size etc. Only visibility is still controlled by this.
*/
void applySettings( const KConfigGroup &cg, bool forceGlobal = false );
/**
* Sets the XML gui client.
+ * @deprecated use addXMLGUIClient.
*/
- void setXMLGUIClient( KXMLGUIClient *client );
+#ifndef KDE_NO_DEPRECATED
+ KDE_DEPRECATED void setXMLGUIClient( KXMLGUIClient *client );
+#endif
+
+ /**
+ * Adds an XML gui client that uses this toolbar
+ * @since 4.8.1
+ */
+ void addXMLGUIClient( KXMLGUIClient *client );
/**
* Load state from an XML @param element, called by KXMLGUIBuilder.
*/
void loadState( const QDomElement &element );
/**
* Save state into an XML @param element, called by KXMLGUIBuilder.
*/
void saveState( QDomElement &element ) const;
/**
* Reimplemented to support context menu activation on disabled tool buttons.
*/
bool eventFilter( QObject* watched, QEvent* event );
/**
* Returns the global setting for "Icon Text" for the main toolbar
* @return global setting for "Icon Text" for the main toolbar
*/
static Qt::ToolButtonStyle toolButtonStyleSetting(); // KDE5: make private and file-static, nobody is calling this
/**
* Returns whether the toolbars are currently editable (drag & drop of actions).
*/
static bool toolBarsEditable();
/**
* Enable or disable toolbar editing via drag & drop of actions. This is
* called by KEditToolbar and should generally be set to disabled whenever
* KEditToolbar is not active.
*/
static void setToolBarsEditable( bool editable );
/**
* Returns whether the toolbars are locked (i.e., moving of the toobars disallowed).
*/
static bool toolBarsLocked();
/**
* Allows you to lock and unlock all toolbars (i.e., disallow/allow moving of the toobars).
*/
static void setToolBarsLocked( bool locked );
protected Q_SLOTS:
virtual void slotMovableChanged(bool movable);
protected:
virtual void contextMenuEvent( QContextMenuEvent* );
virtual void actionEvent( QActionEvent* );
// Draggable toolbar configuration
virtual void dragEnterEvent( QDragEnterEvent* );
virtual void dragMoveEvent( QDragMoveEvent* );
virtual void dragLeaveEvent( QDragLeaveEvent* );
virtual void dropEvent( QDropEvent* );
virtual void mousePressEvent( QMouseEvent* );
virtual void mouseMoveEvent( QMouseEvent* );
virtual void mouseReleaseEvent( QMouseEvent* );
private:
class Private;
Private* const d;
Q_PRIVATE_SLOT( d, void slotAppearanceChanged() )
Q_PRIVATE_SLOT( d, void slotContextAboutToShow() )
Q_PRIVATE_SLOT( d, void slotContextAboutToHide() )
Q_PRIVATE_SLOT( d, void slotContextLeft() )
Q_PRIVATE_SLOT( d, void slotContextRight() )
Q_PRIVATE_SLOT( d, void slotContextShowText() )
Q_PRIVATE_SLOT( d, void slotContextTop() )
Q_PRIVATE_SLOT( d, void slotContextBottom() )
Q_PRIVATE_SLOT( d, void slotContextIcons() )
Q_PRIVATE_SLOT( d, void slotContextText() )
Q_PRIVATE_SLOT( d, void slotContextTextRight() )
Q_PRIVATE_SLOT( d, void slotContextTextUnder() )
Q_PRIVATE_SLOT( d, void slotContextIconSize() )
Q_PRIVATE_SLOT( d, void slotLockToolBars( bool ) )
};
#endif
diff --git a/kdeui/xmlgui/kxmlguibuilder.cpp b/kdeui/xmlgui/kxmlguibuilder.cpp
index 6773c313f1..1addb47554 100644
--- a/kdeui/xmlgui/kxmlguibuilder.cpp
+++ b/kdeui/xmlgui/kxmlguibuilder.cpp
@@ -1,422 +1,422 @@
/* This file is part of the KDE project
Copyright (C) 2000 Simon Hausmann <hausmann@kde.org>
David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kxmlguibuilder.h"
#include "kapplication.h"
#include "kauthorized.h"
#include "kxmlguiclient.h"
#include "kmenubar.h"
#include "kmenu.h"
#include "ktoolbar.h"
#include "kstatusbar.h"
#include "kmainwindow.h"
#include "kxmlguiwindow.h"
#include "kaction.h"
#include "kglobalsettings.h"
#include <klocale.h>
#include <kiconloader.h>
#include <kdebug.h>
#include <QtXml/QDomElement>
#include <QtCore/QObject>
#include <QtCore/QMutableStringListIterator>
#include "kmenumenuhandler_p.h"
#include <kcomponentdata.h>
using namespace KDEPrivate;
class KXMLGUIBuilderPrivate
{
public:
KXMLGUIBuilderPrivate() : m_client(0L) {}
~KXMLGUIBuilderPrivate() { }
QWidget *m_widget;
QString tagMainWindow;
QString tagMenuBar;
QString tagMenu;
QString tagToolBar;
QString tagStatusBar;
QString tagSeparator;
QString tagTearOffHandle;
QString tagMenuTitle;
QString attrName;
QString attrLineSeparator;
QString attrText1;
QString attrText2;
QString attrContext;
QString attrIcon;
KComponentData m_componentData;
KXMLGUIClient *m_client;
KMenuMenuHandler *m_menumenuhandler;
};
KXMLGUIBuilder::KXMLGUIBuilder( QWidget *widget )
: d( new KXMLGUIBuilderPrivate )
{
d->m_widget = widget;
d->tagMainWindow = QLatin1String( "mainwindow" );
d->tagMenuBar = QLatin1String( "menubar" );
d->tagMenu = QLatin1String( "menu" );
d->tagToolBar = QLatin1String( "toolbar" );
d->tagStatusBar = QLatin1String( "statusbar" );
d->tagSeparator = QLatin1String( "separator" );
d->tagTearOffHandle = QLatin1String( "tearoffhandle" );
d->tagMenuTitle = QLatin1String( "title" );
d->attrName = QLatin1String( "name" );
d->attrLineSeparator = QLatin1String( "lineseparator" );
d->attrText1 = QLatin1String( "text" );
d->attrText2 = QLatin1String( "Text" );
d->attrContext = QLatin1String( "context" );
d->attrIcon = QLatin1String( "icon" );
d->m_menumenuhandler=new KMenuMenuHandler(this);
}
KXMLGUIBuilder::~KXMLGUIBuilder()
{
delete d->m_menumenuhandler;
delete d;
}
QWidget *KXMLGUIBuilder::widget()
{
return d->m_widget;
}
QStringList KXMLGUIBuilder::containerTags() const
{
QStringList res;
res << d->tagMenu << d->tagToolBar << d->tagMainWindow << d->tagMenuBar << d->tagStatusBar;
return res;
}
QWidget *KXMLGUIBuilder::createContainer( QWidget *parent, int index, const QDomElement &element, QAction*& containerAction )
{
containerAction = 0;
if (element.attribute("deleted").toLower() == "true") {
return 0;
}
const QString tagName = element.tagName().toLower();
if ( tagName == d->tagMainWindow ) {
KMainWindow *mainwindow = qobject_cast<KMainWindow*>( d->m_widget ); // could be 0
return mainwindow;
}
if ( tagName == d->tagMenuBar ) {
KMainWindow *mainWin = qobject_cast<KMainWindow*>( d->m_widget );
KMenuBar *bar = 0;
if (mainWin)
bar = mainWin->menuBar();
if (!bar)
bar = new KMenuBar( d->m_widget );
bar->show();
return bar;
}
if ( tagName == d->tagMenu ) {
// Look up to see if we are inside a mainwindow. If yes, then
// use it as parent widget (to get kaction to plug itself into the
// mainwindow). Don't use a popupmenu as parent widget, otherwise
// the popup won't be hidden if it is used as a standalone menu as well.
// And we don't want to set the parent for a standalone popupmenu,
// otherwise its shortcuts appear.
//
// Note: menus with a parent of 0, coming from child clients, can be
// leaked if the child client is deleted without a proper removeClient call, though.
QWidget* p = parent;
while ( p && !qobject_cast<QMainWindow*>( p ) )
p = p->parentWidget();
QByteArray name = element.attribute( d->attrName ).toUtf8();
if (!KAuthorized::authorizeKAction(name))
return 0;
KMenu *popup = new KMenu(p);
popup->setObjectName(name);
d->m_menumenuhandler->insertKMenu(popup);
QString i18nText;
QDomElement textElem = element.namedItem( d->attrText1 ).toElement();
if ( textElem.isNull() ) // try with capital T
textElem = element.namedItem( d->attrText2 ).toElement();
const QByteArray text = textElem.text().toUtf8();
const QByteArray context = textElem.attribute(d->attrContext).toUtf8();
if ( text.isEmpty() ) // still no luck
i18nText = i18n( "No text" );
else if ( context.isEmpty() )
i18nText = i18n( text );
else
i18nText = i18nc( context, text );
const QString icon = element.attribute( d->attrIcon );
KIcon pix;
if (!icon.isEmpty()) {
pix = KIcon( icon );
}
if ( parent ) {
QAction* act = popup->menuAction();
if ( !icon.isEmpty() )
act->setIcon(pix);
act->setText(i18nText);
if (index == -1 || index >= parent->actions().count())
parent->addAction(act);
else
parent->insertAction(parent->actions().value(index), act);
containerAction = act;
containerAction->setObjectName( name );
}
return popup;
}
if ( tagName == d->tagToolBar ) {
QByteArray name = element.attribute( d->attrName ).toUtf8();
KToolBar *bar = static_cast<KToolBar*>(d->m_widget->findChild<KToolBar*>( name ));
if( !bar )
{
bar = new KToolBar(name, d->m_widget, false);
}
if ( qobject_cast<KMainWindow*>( d->m_widget ) )
{
if ( d->m_client && !d->m_client->xmlFile().isEmpty() )
- bar->setXMLGUIClient( d->m_client );
+ bar->addXMLGUIClient( d->m_client );
}
bar->loadState( element );
return bar;
}
if ( tagName == d->tagStatusBar ) {
KMainWindow *mainWin = qobject_cast<KMainWindow *>(d->m_widget);
if ( mainWin ) {
mainWin->statusBar()->show();
return mainWin->statusBar();
}
KStatusBar *bar = new KStatusBar( d->m_widget );
return bar;
}
return 0L;
}
void KXMLGUIBuilder::removeContainer( QWidget *container, QWidget *parent, QDomElement &element, QAction* containerAction )
{
// Warning parent can be 0L
if ( qobject_cast<QMenu*>( container ) )
{
if ( parent ) {
parent->removeAction( containerAction );
}
delete container;
}
else if ( qobject_cast<KToolBar*>( container ) )
{
KToolBar *tb = static_cast<KToolBar *>( container );
tb->saveState( element );
delete tb;
}
else if ( qobject_cast<KMenuBar*>( container ) )
{
KMenuBar *mb = static_cast<KMenuBar *>( container );
mb->hide();
// Don't delete menubar - it can be reused by createContainer.
// If you decide that you do need to delete the menubar, make
// sure that QMainWindow::d->mb does not point to a deleted
// menubar object.
}
else if ( qobject_cast<KStatusBar*>( container ) )
{
if ( qobject_cast<KMainWindow*>( d->m_widget ) )
container->hide();
else
delete static_cast<KStatusBar *>(container);
}
else
kWarning() << "Unhandled container to remove : " << container->metaObject()->className();
}
QStringList KXMLGUIBuilder::customTags() const
{
QStringList res;
res << d->tagSeparator << d->tagTearOffHandle << d->tagMenuTitle;
return res;
}
QAction* KXMLGUIBuilder::createCustomElement( QWidget *parent, int index, const QDomElement &element )
{
QAction* before = 0L;
if (index > 0 && index < parent->actions().count())
before = parent->actions().at(index);
const QString tagName = element.tagName().toLower();
if (tagName == d->tagSeparator)
{
if ( QMenu *menu = qobject_cast<QMenu*>( parent ) )
{
// QMenu already cares for leading/trailing/repeated separators
// no need to check anything
return menu->insertSeparator( before );
}
else if ( QMenuBar* bar = qobject_cast<QMenuBar*>( parent ) )
{
QAction* separatorAction = new QAction(bar);
separatorAction->setSeparator(true);
bar->insertAction( before, separatorAction );
return separatorAction;
}
else if ( KToolBar *bar = qobject_cast<KToolBar*>( parent ) )
{
/* FIXME KAction port - any need to provide a replacement for lineSeparator/normal separator?
bool isLineSep = true;
QDomNamedNodeMap attributes = element.attributes();
unsigned int i = 0;
for (; i < attributes.length(); i++ )
{
QDomAttr attr = attributes.item( i ).toAttr();
if ( attr.name().toLower() == d->attrLineSeparator &&
attr.value().toLower() == QLatin1String("false") )
{
isLineSep = false;
break;
}
}
if ( isLineSep )
return bar->insertSeparator( index ? bar->actions()[index - 1] : 0L );
else*/
return bar->insertSeparator( before );
}
}
else if (tagName == d->tagTearOffHandle)
{
static_cast<QMenu *>(parent)->setTearOffEnabled(true);
}
else if (tagName == d->tagMenuTitle)
{
if ( KMenu* m = qobject_cast<KMenu*>( parent ) )
{
QString i18nText;
QByteArray text = element.text().toUtf8();
if ( text.isEmpty() )
i18nText = i18n( "No text" );
else
i18nText = i18n( text );
QString icon = element.attribute( d->attrIcon );
KIcon pix;
if ( !icon.isEmpty() )
{
pix = KIcon( icon );
}
if ( !icon.isEmpty() ) {
return m->addTitle( pix, i18nText, before );
} else {
return m->addTitle( i18nText, before );
}
}
}
QAction* blank = new QAction(parent);
blank->setVisible(false);
parent->insertAction(before, blank);
return blank;
}
void KXMLGUIBuilder::removeCustomElement( QWidget *parent, QAction* action )
{
parent->removeAction(action);
}
KXMLGUIClient *KXMLGUIBuilder::builderClient() const
{
return d->m_client;
}
void KXMLGUIBuilder::setBuilderClient( KXMLGUIClient *client )
{
d->m_client = client;
if ( client )
setBuilderComponentData( client->componentData() );
}
KComponentData KXMLGUIBuilder::builderComponentData() const
{
return d->m_componentData;
}
void KXMLGUIBuilder::setBuilderComponentData(const KComponentData &componentData)
{
d->m_componentData = componentData;
}
void KXMLGUIBuilder::finalizeGUI( KXMLGUIClient * )
{
KXmlGuiWindow* window = qobject_cast<KXmlGuiWindow*>(d->m_widget);
if (!window)
return;
#if 0
KToolBar *toolbar = 0;
QListIterator<KToolBar> it( ( (KMainWindow*)d->m_widget )->toolBarIterator() );
while ( ( toolbar = it.current() ) ) {
kDebug(260) << "KXMLGUIBuilder::finalizeGUI toolbar=" << (void*)toolbar;
++it;
toolbar->positionYourself();
}
#else
window->finalizeGUI( false );
#endif
}
void KXMLGUIBuilder::virtual_hook( int, void* )
{ /*BASE::virtual_hook( id, data );*/ }
diff --git a/kdeui/xmlgui/kxmlguifactory_p.cpp b/kdeui/xmlgui/kxmlguifactory_p.cpp
index 2f81f18541..083ddf5eba 100644
--- a/kdeui/xmlgui/kxmlguifactory_p.cpp
+++ b/kdeui/xmlgui/kxmlguifactory_p.cpp
@@ -1,853 +1,864 @@
/* This file is part of the KDE libraries
Copyright (C) 2001 Simon Hausmann <hausmann@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kxmlguifactory_p.h"
#include "kxmlguiclient.h"
#include "kxmlguibuilder.h"
#include <QtGui/QWidget>
#include <kglobal.h>
#include <kdebug.h>
#include <assert.h>
#include <kcomponentdata.h>
+#include <ktoolbar.h>
using namespace KXMLGUI;
void ActionList::plug( QWidget *container, int index ) const
{
QAction* before = 0L; // Insert after end of widget's current actions (default).
if ((index < 0) || (index > container->actions().count()))
kWarning() << "Index " << index << " is not within range (0 - " << container->actions().count();
else if (index != container->actions().count())
before = container->actions().at(index); // Insert before indexed action.
foreach (QAction* action, *this) {
container->insertAction(before, action);
// before = action; // BUG FIX: do not insert actions in reverse order.
}
}
void ActionList::unplug( QWidget *container ) const
{
foreach (QAction* action, *this) {
if (container->actions().contains(action))
container->removeAction( action );
}
}
ContainerNode::ContainerNode( QWidget *_container, const QString &_tagName,
const QString &_name, ContainerNode *_parent,
KXMLGUIClient *_client, KXMLGUIBuilder *_builder,
QAction* _containerAction, const QString &_mergingName,
const QString &_groupName, const QStringList &customTags,
const QStringList &containerTags )
: parent( _parent ), client( _client ), builder( _builder ),
builderCustomTags( customTags ), builderContainerTags( containerTags ),
container( _container ), containerAction( _containerAction ), tagName( _tagName ), name( _name ),
groupName( _groupName ), index( 0 ), mergingName( _mergingName )
{
if ( parent )
parent->children.append( this );
}
ContainerNode::~ContainerNode()
{
qDeleteAll(children);
qDeleteAll(clients);
}
void ContainerNode::removeChild( ContainerNode* child )
{
MergingIndexList::Iterator mergingIt = findIndex( child->mergingName );
adjustMergingIndices( -1, mergingIt );
children.removeAll(child);
delete child;
}
void ContainerNode::removeChild( QMutableListIterator<ContainerNode*>& childIterator )
{
MergingIndexList::Iterator mergingIt = findIndex( childIterator.peekNext()->mergingName );
adjustMergingIndices( -1, mergingIt );
delete childIterator.next();
childIterator.remove();
}
/*
* Find a merging index with the given name. Used to find an index defined by <Merge name="blah"/>
* or by a <DefineGroup name="foo" /> tag.
*/
MergingIndexList::Iterator ContainerNode::findIndex( const QString &name )
{
MergingIndexList::Iterator it( mergingIndices.begin() );
MergingIndexList::Iterator end( mergingIndices.end() );
for (; it != end; ++it )
if ( (*it).mergingName == name )
return it;
return it;
}
/*
* Check if the given container widget is a child of this node and return the node structure
* if fonud.
*/
ContainerNode *ContainerNode::findContainerNode( QWidget *container )
{
foreach (ContainerNode* child, children )
if ( child->container == container )
return child;
return 0L;
}
/*
* Find a container recursively with the given name. Either compares _name with the
* container's tag name or the value of the container's name attribute. Specified by
* the tag bool .
*/
ContainerNode *ContainerNode::findContainer( const QString &_name, bool tag )
{
if ( ( tag && tagName == _name ) ||
( !tag && name == _name ) )
return this;
foreach (ContainerNode* child, children )
{
ContainerNode *res = child->findContainer( _name, tag );
if ( res )
return res;
}
return 0;
}
/*
* Finds a child container node (not recursively) with the given name and tagname. Explicitly
* leaves out container widgets specified in the exludeList . Also ensures that the containers
* belongs to currClient.
*/
ContainerNode *ContainerNode::findContainer( const QString &name, const QString &tagName,
const QList<QWidget*> *excludeList,
KXMLGUIClient * /*currClient*/ )
{
ContainerNode *res = 0L;
ContainerNodeList::ConstIterator nIt = children.constBegin();
if ( !name.isEmpty() )
{
for (; nIt != children.constEnd(); ++nIt )
if ( (*nIt)->name == name &&
!excludeList->contains( (*nIt)->container ) )
{
res = *nIt;
break;
}
return res;
}
if ( !tagName.isEmpty() )
for (; nIt != children.constEnd(); ++nIt )
{
if ( (*nIt)->tagName == tagName &&
!excludeList->contains( (*nIt)->container )
/*
* It is a bad idea to also compare the client, because
* we don't want to do so in situations like these:
*
* <MenuBar>
* <Menu>
* ...
*
* other client:
* <MenuBar>
* <Menu>
* ...
*
&& (*nIt)->client == currClient )
*/
)
{
res = *nIt;
break;
}
}
return res;
}
ContainerClient *ContainerNode::findChildContainerClient( KXMLGUIClient *currentGUIClient,
const QString &groupName,
const MergingIndexList::Iterator &mergingIdx )
{
if ( !clients.isEmpty() )
{
foreach (ContainerClient* client, clients)
if ( client->client == currentGUIClient )
{
if ( groupName.isEmpty() )
return client;
if ( groupName == client->groupName )
return client;
}
}
ContainerClient *client = new ContainerClient;
client->client = currentGUIClient;
client->groupName = groupName;
if ( mergingIdx != mergingIndices.end() )
client->mergingName = (*mergingIdx).mergingName;
clients.append( client );
return client;
}
void ContainerNode::plugActionList( BuildState &state )
{
MergingIndexList::Iterator mIt( mergingIndices.begin() );
MergingIndexList::Iterator mEnd( mergingIndices.end() );
for (; mIt != mEnd; ++mIt )
plugActionList( state, mIt );
foreach (ContainerNode* child, children)
child->plugActionList( state );
}
void ContainerNode::plugActionList( BuildState &state, const MergingIndexList::Iterator &mergingIdxIt )
{
static const QString &tagActionList = KGlobal::staticQString( "actionlist" );
MergingIndex mergingIdx = *mergingIdxIt;
QString k( mergingIdx.mergingName );
if ( k.indexOf( tagActionList ) == -1 )
return;
k = k.mid( tagActionList.length() );
if ( mergingIdx.clientName != state.clientName )
return;
if ( k != state.actionListName )
return;
ContainerClient *client = findChildContainerClient( state.guiClient,
QString(),
mergingIndices.end() );
client->actionLists.insert( k, state.actionList );
state.actionList.plug( container, mergingIdx.value );
adjustMergingIndices( state.actionList.count(), mergingIdxIt );
}
void ContainerNode::unplugActionList( BuildState &state )
{
MergingIndexList::Iterator mIt( mergingIndices.begin() );
MergingIndexList::Iterator mEnd( mergingIndices.end() );
for (; mIt != mEnd; ++mIt )
unplugActionList( state, mIt );
foreach (ContainerNode* child, children)
child->unplugActionList( state );
}
void ContainerNode::unplugActionList( BuildState &state, const MergingIndexList::Iterator &mergingIdxIt )
{
static const QString &tagActionList = KGlobal::staticQString( "actionlist" );
MergingIndex mergingIdx = *mergingIdxIt;
QString k = mergingIdx.mergingName;
if ( k.indexOf( tagActionList ) == -1 )
return;
k = k.mid( tagActionList.length() );
if ( mergingIdx.clientName != state.clientName )
return;
if ( k != state.actionListName )
return;
ContainerClient *client = findChildContainerClient( state.guiClient,
QString(),
mergingIndices.end() );
ActionListMap::Iterator lIt( client->actionLists.find( k ) );
if ( lIt == client->actionLists.end() )
return;
lIt.value().unplug( container );
adjustMergingIndices( -int(lIt.value().count()), mergingIdxIt );
client->actionLists.erase( lIt );
}
void ContainerNode::adjustMergingIndices( int offset,
const MergingIndexList::Iterator &it )
{
MergingIndexList::Iterator mergingIt = it;
MergingIndexList::Iterator mergingEnd = mergingIndices.end();
for (; mergingIt != mergingEnd; ++mergingIt )
(*mergingIt).value += offset;
index += offset;
}
bool ContainerNode::destruct( QDomElement element, BuildState &state ) //krazy:exclude=passbyvalue (this is correct QDom usage, and a ref wouldn't allow passing doc.documentElement() as argument)
{
destructChildren( element, state );
unplugActions( state );
// remove all merging indices the client defined
QMutableListIterator<MergingIndex> cmIt = mergingIndices;
while ( cmIt.hasNext() )
if ( cmIt.next().clientName == state.clientName )
cmIt.remove();
// ### check for merging index count, too?
if ( clients.count() == 0 && children.count() == 0 && container &&
client == state.guiClient )
{
QWidget *parentContainer = 0L;
if ( parent && parent->container )
parentContainer = parent->container;
assert( builder );
builder->removeContainer( container, parentContainer, element, containerAction );
client = 0L;
return true;
}
if ( client == state.guiClient )
client = 0L;
return false;
}
void ContainerNode::destructChildren( const QDomElement &element, BuildState &state )
{
QMutableListIterator<ContainerNode*> childIt = children;
while ( childIt.hasNext() )
{
ContainerNode *childNode = childIt.peekNext();
QDomElement childElement = findElementForChild( element, childNode );
// destruct returns true in case the container really got deleted
if ( childNode->destruct( childElement, state ) )
removeChild( childIt );
else
childIt.next();
}
}
QDomElement ContainerNode::findElementForChild( const QDomElement &baseElement,
ContainerNode *childNode )
{
static const QString &attrName = KGlobal::staticQString( "name" );
// ### slow
for ( QDomNode n = baseElement.firstChild(); !n.isNull();
n = n.nextSibling() )
{
QDomElement e = n.toElement();
if ( e.tagName().toLower() == childNode->tagName &&
e.attribute( attrName ) == childNode->name )
return e;
}
return QDomElement();
}
void ContainerNode::unplugActions( BuildState &state )
{
if ( !container )
return;
QMutableListIterator<ContainerClient*> clientIt( clients );
/*
Disabled because it means in KToolBar::saveState isHidden is always true then,
which is clearly wrong.
if ( clients.count() == 1 && clientIt.current()->client == client &&
client == state.guiClient )
container->hide(); // this container is going to die, that's for sure.
// in this case let's just hide it, which makes the
// destruction faster
*/
while ( clientIt.hasNext() )
//only unplug the actions of the client we want to remove, as the container might be owned
//by a different client
if ( clientIt.peekNext()->client == state.guiClient )
{
unplugClient( clientIt.peekNext() );
delete clientIt.next();
clientIt.remove();
}
else
clientIt.next();
}
void ContainerNode::unplugClient( ContainerClient *client )
{
static const QString &tagActionList = KGlobal::staticQString( "actionlist" );
assert( builder );
// now quickly remove all custom elements (i.e. separators) and unplug all actions
QList<QAction*>::ConstIterator custIt = client->customElements.constBegin();
QList<QAction*>::ConstIterator custEnd = client->customElements.constEnd();
for (; custIt != custEnd; ++custIt )
builder->removeCustomElement( container, *custIt );
client->actions.unplug( container );
// now adjust all merging indices
MergingIndexList::Iterator mergingIt = findIndex( client->mergingName );
adjustMergingIndices( - int( client->actions.count()
+ client->customElements.count() ),
mergingIt );
// unplug all actionslists
ActionListMap::ConstIterator alIt = client->actionLists.constBegin();
ActionListMap::ConstIterator alEnd = client->actionLists.constEnd();
for (; alIt != alEnd; ++alIt )
{
alIt.value().unplug( container );
// construct the merging index key (i.e. like named merging) , find the
// corresponding merging index and adjust all indices
QString mergingKey = alIt.key();
mergingKey.prepend( tagActionList );
MergingIndexList::Iterator mIt = findIndex( mergingKey );
if ( mIt == mergingIndices.end() )
continue;
adjustMergingIndices( -int(alIt.value().count()), mIt );
// remove the actionlists' merging index
// ### still needed? we clean up below anyway?
mergingIndices.erase( mIt );
}
}
void ContainerNode::reset()
{
foreach (ContainerNode* child, children)
child->reset();
if ( client )
client->setFactory( 0L );
}
int ContainerNode::calcMergingIndex( const QString &mergingName,
MergingIndexList::Iterator &it,
BuildState &state,
bool ignoreDefaultMergingIndex )
{
MergingIndexList::Iterator mergingIt;
if ( mergingName.isEmpty() )
mergingIt = findIndex( state.clientName );
else
mergingIt = findIndex( mergingName );
MergingIndexList::Iterator mergingEnd = mergingIndices.end();
it = mergingEnd;
if ( ( mergingIt == mergingEnd && state.currentDefaultMergingIt == mergingEnd ) ||
ignoreDefaultMergingIndex )
return index;
if ( mergingIt != mergingEnd )
it = mergingIt;
else
it = state.currentDefaultMergingIt;
return (*it).value;
}
int BuildHelper::calcMergingIndex( const QDomElement &element, MergingIndexList::Iterator &it, QString &group )
{
static const QString &attrGroup = KGlobal::staticQString( "group" );
bool haveGroup = false;
group = element.attribute( attrGroup );
if ( !group.isEmpty() ) {
group.prepend( attrGroup );
haveGroup = true;
}
int idx;
if ( haveGroup )
idx = parentNode->calcMergingIndex( group, it, m_state, ignoreDefaultMergingIndex );
else if ( m_state.currentClientMergingIt == parentNode->mergingIndices.end() )
idx = parentNode->index;
else
idx = (*m_state.currentClientMergingIt).value;
return idx;
}
BuildHelper::BuildHelper( BuildState &state, ContainerNode *node )
: containerClient( 0 ), ignoreDefaultMergingIndex( false ), m_state( state ),
parentNode( node )
{
static const QString &defaultMergingName = KGlobal::staticQString( "<default>" );
// create a list of supported container and custom tags
customTags = m_state.builderCustomTags;
containerTags = m_state.builderContainerTags;
if ( parentNode->builder != m_state.builder )
{
customTags += parentNode->builderCustomTags;
containerTags += parentNode->builderContainerTags;
}
if ( m_state.clientBuilder ) {
customTags = m_state.clientBuilderCustomTags + customTags;
containerTags = m_state.clientBuilderContainerTags + containerTags;
}
m_state.currentDefaultMergingIt = parentNode->findIndex( defaultMergingName );
parentNode->calcMergingIndex( QString(), m_state.currentClientMergingIt,
m_state, /*ignoreDefaultMergingIndex*/ false );
}
void BuildHelper::build( const QDomElement &element )
{
for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling() )
{
QDomElement e = n.toElement();
if (e.isNull()) continue;
processElement( e );
}
}
void BuildHelper::processElement( const QDomElement &e )
{
// some often used QStrings
static const QString &tagAction = KGlobal::staticQString( "action" );
static const QString &tagMerge = KGlobal::staticQString( "merge" );
static const QString &tagState = KGlobal::staticQString( "state" );
static const QString &tagDefineGroup = KGlobal::staticQString( "definegroup" );
static const QString &tagActionList = KGlobal::staticQString( "actionlist" );
static const QString &attrName = KGlobal::staticQString( "name" );
QString tag( e.tagName().toLower() );
QString currName( e.attribute( attrName ) );
bool isActionTag = ( tag == tagAction );
if ( isActionTag || customTags.indexOf( tag ) != -1 )
processActionOrCustomElement( e, isActionTag );
else if ( containerTags.indexOf( tag ) != -1 )
processContainerElement( e, tag, currName );
else if ( tag == tagMerge || tag == tagDefineGroup || tag == tagActionList )
processMergeElement( tag, currName, e );
else if ( tag == tagState )
processStateElement( e );
}
void BuildHelper::processActionOrCustomElement( const QDomElement &e, bool isActionTag )
{
if ( !parentNode->container )
return;
MergingIndexList::Iterator it( m_state.currentClientMergingIt );
QString group;
int idx = calcMergingIndex( e, it, group );
containerClient = parentNode->findChildContainerClient( m_state.guiClient, group, it );
bool guiElementCreated = false;
if ( isActionTag )
guiElementCreated = processActionElement( e, idx );
else
guiElementCreated = processCustomElement( e, idx );
if ( guiElementCreated )
// adjust any following merging indices and the current running index for the container
parentNode->adjustMergingIndices( 1, it );
}
bool BuildHelper::processActionElement( const QDomElement &e, int idx )
{
assert( m_state.guiClient );
// look up the action and plug it in
QAction *action = m_state.guiClient->action( e );
//kDebug(260) << "BuildHelper::processActionElement " << e.attribute( "name" ) << " -> " << action << " (in " << m_state.guiClient->actionCollection() << ")";
if ( !action )
return false;
QAction* before = 0L;
if (idx >= 0 && idx < parentNode->container->actions().count())
before = parentNode->container->actions()[idx];
parentNode->container->insertAction(before, action);
// save a reference to the plugged action, in order to properly unplug it afterwards.
containerClient->actions.append( action );
return true;
}
bool BuildHelper::processCustomElement( const QDomElement &e, int idx )
{
assert( parentNode->builder );
QAction* action = parentNode->builder->createCustomElement( parentNode->container, idx, e );
if ( !action )
return false;
containerClient->customElements.append( action );
return true;
}
void BuildHelper::processStateElement( const QDomElement &element )
{
QString stateName = element.attribute( "name" );
if ( stateName.isNull() || !stateName.length() ) return;
for (QDomNode n = element.firstChild(); !n.isNull(); n = n.nextSibling() )
{
QDomElement e = n.toElement();
if (e.isNull()) continue;
QString tagName = e.tagName().toLower();
if ( tagName != "enable" && tagName != "disable" )
continue;
bool processingActionsToEnable = (tagName == "enable");
// process action names
for (QDomNode n2 = n.firstChild(); !n2.isNull(); n2 = n2.nextSibling() )
{
QDomElement actionEl = n2.toElement();
if ( actionEl.tagName().toLower() != "action" ) continue;
QString actionName = actionEl.attribute( "name" );
if ( actionName.isEmpty() ) return;
if ( processingActionsToEnable )
m_state.guiClient->addStateActionEnabled( stateName, actionName );
else
m_state.guiClient->addStateActionDisabled( stateName, actionName );
}
}
}
void BuildHelper::processMergeElement( const QString &tag, const QString &name, const QDomElement &e )
{
static const QString &tagDefineGroup = KGlobal::staticQString( "definegroup" );
static const QString &tagActionList = KGlobal::staticQString( "actionlist" );
static const QString &defaultMergingName = KGlobal::staticQString( "<default>" );
static const QString &attrGroup = KGlobal::staticQString( "group" );
QString mergingName( name );
if ( mergingName.isEmpty() )
{
if ( tag == tagDefineGroup )
{
kError(240) << "cannot define group without name!" << endl;
return;
}
if ( tag == tagActionList )
{
kError(240) << "cannot define actionlist without name!" << endl;
return;
}
mergingName = defaultMergingName;
}
if ( tag == tagDefineGroup )
mergingName.prepend( attrGroup ); //avoid possible name clashes by prepending
// "group" to group definitions
else if ( tag == tagActionList )
mergingName.prepend( tagActionList );
if ( parentNode->findIndex( mergingName ) != parentNode->mergingIndices.end() )
return; //do not allow the redefinition of merging indices!
MergingIndexList::Iterator mIt( parentNode->mergingIndices.end() );
QString group( e.attribute( attrGroup ) );
if ( !group.isEmpty() )
group.prepend( attrGroup );
// calculate the index of the new merging index. Usually this does not need any calculation,
// we just want the last available index (i.e. append) . But in case the <Merge> tag appears
// "inside" another <Merge> tag from a previously build client, then we have to use the
// "parent's" index. That's why we call calcMergingIndex here.
MergingIndex newIdx;
newIdx.value = parentNode->calcMergingIndex( group, mIt, m_state, ignoreDefaultMergingIndex );
newIdx.mergingName = mergingName;
newIdx.clientName = m_state.clientName;
// if that merging index is "inside" another one, then append it right after the "parent" .
if ( mIt != parentNode->mergingIndices.end() )
parentNode->mergingIndices.insert( ++mIt, newIdx );
else
parentNode->mergingIndices.append( newIdx );
if ( mergingName == defaultMergingName )
ignoreDefaultMergingIndex = true;
// re-calculate the running default and client merging indices.
m_state.currentDefaultMergingIt = parentNode->findIndex( defaultMergingName );
parentNode->calcMergingIndex( QString(), m_state.currentClientMergingIt,
m_state, ignoreDefaultMergingIndex );
}
void BuildHelper::processContainerElement( const QDomElement &e, const QString &tag,
const QString &name )
{
static const QString &defaultMergingName = KGlobal::staticQString( "<default>" );
ContainerNode *containerNode = parentNode->findContainer( name, tag,
&containerList,
m_state.guiClient );
if ( !containerNode )
{
MergingIndexList::Iterator it( m_state.currentClientMergingIt );
QString group;
int idx = calcMergingIndex( e, it, group );
QAction* containerAction;
KXMLGUIBuilder *builder;
QWidget *container = createContainer( parentNode->container, idx, e, containerAction, &builder );
// no container? (probably some <text> tag or so ;-)
if ( !container )
return;
parentNode->adjustMergingIndices( 1, it );
assert( !parentNode->findContainerNode( container ) );
containerList.append( container );
QString mergingName;
if ( it != parentNode->mergingIndices.end() )
mergingName = (*it).mergingName;
QStringList cusTags = m_state.builderCustomTags;
QStringList conTags = m_state.builderContainerTags;
if ( builder != m_state.builder )
{
cusTags = m_state.clientBuilderCustomTags;
conTags = m_state.clientBuilderContainerTags;
}
containerNode = new ContainerNode( container, tag, name, parentNode,
m_state.guiClient, builder, containerAction,
mergingName, group, cusTags, conTags );
+ } else {
+ if ( tag == QLatin1String( "toolbar" ) ) {
+ KToolBar *bar = qobject_cast<KToolBar*>(containerNode->container);
+ if (bar) {
+ if ( m_state.guiClient && !m_state.guiClient->xmlFile().isEmpty() )
+ bar->addXMLGUIClient(m_state.guiClient);
+ } else {
+ kWarning() << "toolbar container is not a KToolBar";
+ }
+ }
}
BuildHelper( m_state, containerNode ).build( e );
// and re-calculate running values, for better performance
m_state.currentDefaultMergingIt = parentNode->findIndex( defaultMergingName );
parentNode->calcMergingIndex( QString(), m_state.currentClientMergingIt,
m_state, ignoreDefaultMergingIndex );
}
QWidget *BuildHelper::createContainer( QWidget *parent, int index,
const QDomElement &element, QAction*& containerAction,
KXMLGUIBuilder **builder )
{
QWidget *res = 0L;
if ( m_state.clientBuilder )
{
res = m_state.clientBuilder->createContainer( parent, index, element, containerAction );
if ( res )
{
*builder = m_state.clientBuilder;
return res;
}
}
KComponentData oldInstance = m_state.builder->builderComponentData();
KXMLGUIClient *oldClient = m_state.builder->builderClient();
m_state.builder->setBuilderClient( m_state.guiClient );
res = m_state.builder->createContainer( parent, index, element, containerAction );
m_state.builder->setBuilderComponentData(oldInstance);
m_state.builder->setBuilderClient( oldClient );
if ( res )
*builder = m_state.builder;
return res;
}
void BuildState::reset()
{
clientName.clear();
actionListName.clear();
actionList.clear();
guiClient = 0;
clientBuilder = 0;
currentDefaultMergingIt = currentClientMergingIt = MergingIndexList::Iterator();
}
/* vim: et sw=4
*/
diff --git a/kfile/kfilepreviewgenerator.cpp b/kfile/kfilepreviewgenerator.cpp
index 9d8c6e893d..59ff96f3a1 100644
--- a/kfile/kfilepreviewgenerator.cpp
+++ b/kfile/kfilepreviewgenerator.cpp
@@ -1,1295 +1,1305 @@
/*******************************************************************************
* Copyright (C) 2008-2009 by Peter Penz <peter.penz@gmx.at> *
* *
* This library is free software; you can redistribute it and/or *
* modify it under the terms of the GNU Library General Public *
* License as published by the Free Software Foundation; either *
* version 2 of the License, or (at your option) any later version. *
* *
* This library 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 *
* Library General Public License for more details. *
* *
* You should have received a copy of the GNU Library General Public License *
* along with this library; see the file COPYING.LIB. If not, write to *
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, *
* Boston, MA 02110-1301, USA. *
*******************************************************************************/
#include "kfilepreviewgenerator.h"
#include "../kio/kio/defaultviewadapter_p.h" // KDE5 TODO: move this class here
#include "../kio/kio/imagefilter_p.h"
#include <config.h> // for HAVE_XRENDER
#include <kconfiggroup.h>
#include <kfileitem.h>
#include <kiconeffect.h>
#include <kio/previewjob.h>
#include <kdirlister.h>
#include <kdirmodel.h>
#include <ksharedconfig.h>
#include <QApplication>
#include <QAbstractItemView>
#include <QAbstractProxyModel>
#include <QClipboard>
#include <QColor>
#include <QHash>
#include <QList>
#include <QListView>
#include <QPainter>
#include <QPixmap>
#include <QScrollBar>
#include <QIcon>
#if defined(Q_WS_X11) && defined(HAVE_XRENDER)
# include <QX11Info>
# include <X11/Xlib.h>
# include <X11/extensions/Xrender.h>
#endif
/**
* If the passed item view is an instance of QListView, expensive
* layout operations are blocked in the constructor and are unblocked
* again in the destructor.
*
* This helper class is a workaround for the following huge performance
* problem when having directories with several 1000 items:
* - each change of an icon emits a dataChanged() signal from the model
* - QListView iterates through all items on each dataChanged() signal
* and invokes QItemDelegate::sizeHint()
* - the sizeHint() implementation of KFileItemDelegate is quite complex,
* invoking it 1000 times for each icon change might block the UI
*
* QListView does not invoke QItemDelegate::sizeHint() when the
* uniformItemSize property has been set to true, so this property is
* set before exchanging a block of icons. It is important to reset
* it again before the event loop is entered, otherwise QListView
* would not get the correct size hints after dispatching the layoutChanged()
* signal.
*/
class KFilePreviewGenerator::LayoutBlocker
{
public:
LayoutBlocker(QAbstractItemView* view) :
m_uniformSizes(false),
m_view(qobject_cast<QListView*>(view))
{
if (m_view != 0) {
m_uniformSizes = m_view->uniformItemSizes();
m_view->setUniformItemSizes(true);
}
}
~LayoutBlocker()
{
if (m_view != 0) {
m_view->setUniformItemSizes(m_uniformSizes);
}
}
private:
bool m_uniformSizes;
QListView* m_view;
};
/** Helper class for drawing frames for image previews. */
class KFilePreviewGenerator::TileSet
{
public:
enum { LeftMargin = 3, TopMargin = 2, RightMargin = 3, BottomMargin = 4 };
enum Tile { TopLeftCorner = 0, TopSide, TopRightCorner, LeftSide,
RightSide, BottomLeftCorner, BottomSide, BottomRightCorner,
NumTiles };
TileSet()
{
QImage image(8 * 3, 8 * 3, QImage::Format_ARGB32_Premultiplied);
QPainter p(&image);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.fillRect(image.rect(), Qt::transparent);
p.fillRect(image.rect().adjusted(3, 3, -3, -3), Qt::black);
p.end();
KIO::ImageFilter::shadowBlur(image, 3, Qt::black);
QPixmap pixmap = QPixmap::fromImage(image);
m_tiles[TopLeftCorner] = pixmap.copy(0, 0, 8, 8);
m_tiles[TopSide] = pixmap.copy(8, 0, 8, 8);
m_tiles[TopRightCorner] = pixmap.copy(16, 0, 8, 8);
m_tiles[LeftSide] = pixmap.copy(0, 8, 8, 8);
m_tiles[RightSide] = pixmap.copy(16, 8, 8, 8);
m_tiles[BottomLeftCorner] = pixmap.copy(0, 16, 8, 8);
m_tiles[BottomSide] = pixmap.copy(8, 16, 8, 8);
m_tiles[BottomRightCorner] = pixmap.copy(16, 16, 8, 8);
}
void paint(QPainter* p, const QRect& r)
{
p->drawPixmap(r.topLeft(), m_tiles[TopLeftCorner]);
if (r.width() - 16 > 0) {
p->drawTiledPixmap(r.x() + 8, r.y(), r.width() - 16, 8, m_tiles[TopSide]);
}
p->drawPixmap(r.right() - 8 + 1, r.y(), m_tiles[TopRightCorner]);
if (r.height() - 16 > 0) {
p->drawTiledPixmap(r.x(), r.y() + 8, 8, r.height() - 16, m_tiles[LeftSide]);
p->drawTiledPixmap(r.right() - 8 + 1, r.y() + 8, 8, r.height() - 16, m_tiles[RightSide]);
}
p->drawPixmap(r.x(), r.bottom() - 8 + 1, m_tiles[BottomLeftCorner]);
if (r.width() - 16 > 0) {
p->drawTiledPixmap(r.x() + 8, r.bottom() - 8 + 1, r.width() - 16, 8, m_tiles[BottomSide]);
}
p->drawPixmap(r.right() - 8 + 1, r.bottom() - 8 + 1, m_tiles[BottomRightCorner]);
const QRect contentRect = r.adjusted(LeftMargin + 1, TopMargin + 1,
-(RightMargin + 1), -(BottomMargin + 1));
p->fillRect(contentRect, Qt::transparent);
}
private:
QPixmap m_tiles[NumTiles];
};
class KFilePreviewGenerator::Private
{
public:
Private(KFilePreviewGenerator* parent,
KAbstractViewAdapter* viewAdapter,
QAbstractItemModel* model);
~Private();
/**
* Requests a new icon for the item \a index.
* @param sequenceIndex If this is zero, the standard icon is requested, else another one.
*/
void requestSequenceIcon(const QModelIndex& index, int sequenceIndex);
/**
* Generates previews for the items \a items asynchronously.
*/
void updateIcons(const KFileItemList& items);
/**
* Generates previews for the indices within \a topLeft
* and \a bottomRight asynchronously.
*/
void updateIcons(const QModelIndex& topLeft, const QModelIndex& bottomRight);
/**
* Adds the preview \a pixmap for the item \a item to the preview
* queue and starts a timer which will dispatch the preview queue
* later.
*/
void addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap);
/**
* Is invoked when the preview job has been finished and
* removes the job from the m_previewJobs list.
*/
void slotPreviewJobFinished(KJob* job);
/** Synchronizes the icon of all items with the clipboard of cut items. */
void updateCutItems();
/**
* Reset all icons of the items from m_cutItemsCache and clear
* the cache.
*/
void clearCutItemsCache();
/**
* Dispatches the preview queue block by block within
* time slices.
*/
void dispatchIconUpdateQueue();
/**
* Pauses all icon updates and invokes KFilePreviewGenerator::resumeIconUpdates()
* after a short delay. Is invoked as soon as the user has moved
* a scrollbar.
*/
void pauseIconUpdates();
/**
* Resumes the icons updates that have been paused after moving the
* scrollbar. The previews for the current visible area are
* generated first.
*/
void resumeIconUpdates();
/**
* Starts the resolving of the MIME types from
* the m_pendingItems queue.
*/
void startMimeTypeResolving();
/**
* Resolves the MIME type for exactly one item of the
* m_pendingItems queue.
*/
void resolveMimeType();
/**
* Returns true, if the item \a item has been cut into
* the clipboard.
*/
bool isCutItem(const KFileItem& item) const;
/**
* Applies a cut-item effect to all given \a items, if they
* are marked as cut in the clipboard.
*/
void applyCutItemEffect(const KFileItemList& items);
/**
* Applies a frame around the icon. False is returned if
* no frame has been added because the icon is too small.
*/
bool applyImageFrame(QPixmap& icon);
/**
* Resizes the icon to \a maxSize if the icon size does not
* fit into the maximum size. The aspect ratio of the icon
* is kept.
*/
void limitToSize(QPixmap& icon, const QSize& maxSize);
/**
* Creates previews by starting new preview jobs for the items
* and triggers the preview timer.
*/
void createPreviews(const KFileItemList& items);
/**
* Helper method for createPreviews(): Starts a preview job for the given
* items. For each returned preview addToPreviewQueue() will get invoked.
*/
void startPreviewJob(const KFileItemList& items, int width, int height);
/** Kills all ongoing preview jobs. */
void killPreviewJobs();
/**
* Orders the items \a items in a way that the visible items
* are moved to the front of the list. When passing this
* list to a preview job, the visible items will get generated
* first.
*/
void orderItems(KFileItemList& items);
/**
* Returns true, if \a mimeData represents a selection that has
* been cut.
*/
bool decodeIsCutSelection(const QMimeData* mimeData);
/**
* Helper method for KFilePreviewGenerator::updateIcons(). Adds
* recursively all items from the model to the list \a list.
*/
void addItemsToList(const QModelIndex& index, KFileItemList& list);
/**
* Updates the icons of files that are constantly changed due to a copy
* operation. See m_changedItems and m_changedItemsTimer for details.
*/
void delayedIconUpdate();
/**
* Any items that are removed from the model are also removed from m_changedItems.
*/
void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end);
/** Remembers the pixmap for an item specified by an URL. */
struct ItemInfo
{
KUrl url;
QPixmap pixmap;
};
/**
* During the lifetime of a DataChangeObtainer instance changing
* the data of the model won't trigger generating a preview.
*/
class DataChangeObtainer
{
public:
DataChangeObtainer(KFilePreviewGenerator::Private* generator) :
m_gen(generator) { ++m_gen->m_internalDataChange; }
~DataChangeObtainer() { --m_gen->m_internalDataChange; }
private:
KFilePreviewGenerator::Private* m_gen;
};
bool m_previewShown;
/**
* True, if m_pendingItems and m_dispatchedItems should be
* cleared when the preview jobs have been finished.
*/
bool m_clearItemQueues;
/**
* True if a selection has been done which should cut items.
*/
bool m_hasCutSelection;
/**
* True if the updates of icons has been paused by pauseIconUpdates().
* The value is reset by resumeIconUpdates().
*/
bool m_iconUpdatesPaused;
/**
* If the value is 0, the slot
* updateIcons(const QModelIndex&, const QModelIndex&) has
* been triggered by an external data change.
*/
int m_internalDataChange;
int m_pendingVisibleIconUpdates;
KAbstractViewAdapter* m_viewAdapter;
QAbstractItemView* m_itemView;
QTimer* m_iconUpdateTimer;
QTimer* m_scrollAreaTimer;
QList<KJob*> m_previewJobs;
QWeakPointer<KDirModel> m_dirModel;
QAbstractProxyModel* m_proxyModel;
/**
* Set of all items that already have the 'cut' effect applied, together with the pixmap it was applied to
* This is used to make sure that the 'cut' effect is applied max. once for each pixmap
*
* Referencing the pixmaps here imposes no overhead, as they were also given to KDirModel::setData(),
* and thus are held anyway.
*/
QHash<KUrl, QPixmap> m_cutItemsCache;
QList<ItemInfo> m_previews;
QMap<KUrl, int> m_sequenceIndices;
/**
* When huge items are copied, it must be prevented that a preview gets generated
* for each item size change. m_changedItems keeps track of the changed items and it
* is assured that a final preview is only done if an item does not change within
* at least 5 seconds.
*/
QHash<KUrl, bool> m_changedItems;
QTimer* m_changedItemsTimer;
/**
* Contains all items where a preview must be generated, but
* where the preview job has not dispatched the items yet.
*/
KFileItemList m_pendingItems;
/**
* Contains all items, where a preview has already been
* generated by the preview jobs.
*/
KFileItemList m_dispatchedItems;
KFileItemList m_resolvedMimeTypes;
QStringList m_enabledPlugins;
TileSet* m_tileSet;
private:
KFilePreviewGenerator* const q;
};
KFilePreviewGenerator::Private::Private(KFilePreviewGenerator* parent,
KAbstractViewAdapter* viewAdapter,
QAbstractItemModel* model) :
m_previewShown(true),
m_clearItemQueues(true),
m_hasCutSelection(false),
m_iconUpdatesPaused(false),
m_internalDataChange(0),
m_pendingVisibleIconUpdates(0),
m_viewAdapter(viewAdapter),
m_itemView(0),
m_iconUpdateTimer(0),
m_scrollAreaTimer(0),
m_previewJobs(),
m_proxyModel(0),
m_cutItemsCache(),
m_previews(),
m_sequenceIndices(),
m_changedItems(),
m_changedItemsTimer(0),
m_pendingItems(),
m_dispatchedItems(),
m_resolvedMimeTypes(),
m_enabledPlugins(),
m_tileSet(0),
q(parent)
{
if (!m_viewAdapter->iconSize().isValid()) {
m_previewShown = false;
}
m_proxyModel = qobject_cast<QAbstractProxyModel*>(model);
m_dirModel = (m_proxyModel == 0) ?
qobject_cast<KDirModel*>(model) :
qobject_cast<KDirModel*>(m_proxyModel->sourceModel());
if (!m_dirModel) {
// previews can only get generated for directory models
m_previewShown = false;
} else {
KDirModel* dirModel = m_dirModel.data();
connect(dirModel->dirLister(), SIGNAL(newItems(KFileItemList)),
q, SLOT(updateIcons(KFileItemList)));
connect(dirModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
q, SLOT(updateIcons(QModelIndex,QModelIndex)));
connect(dirModel, SIGNAL(needSequenceIcon(QModelIndex,int)),
q, SLOT(requestSequenceIcon(QModelIndex,int)));
connect(dirModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)),
q, SLOT(rowsAboutToBeRemoved(QModelIndex,int,int)));
}
QClipboard* clipboard = QApplication::clipboard();
connect(clipboard, SIGNAL(dataChanged()),
q, SLOT(updateCutItems()));
m_iconUpdateTimer = new QTimer(q);
m_iconUpdateTimer->setSingleShot(true);
m_iconUpdateTimer->setInterval(200);
connect(m_iconUpdateTimer, SIGNAL(timeout()), q, SLOT(dispatchIconUpdateQueue()));
// Whenever the scrollbar values have been changed, the pending previews should
// be reordered in a way that the previews for the visible items are generated
// first. The reordering is done with a small delay, so that during moving the
// scrollbars the CPU load is kept low.
m_scrollAreaTimer = new QTimer(q);
m_scrollAreaTimer->setSingleShot(true);
m_scrollAreaTimer->setInterval(200);
connect(m_scrollAreaTimer, SIGNAL(timeout()),
q, SLOT(resumeIconUpdates()));
m_viewAdapter->connect(KAbstractViewAdapter::ScrollBarValueChanged,
q, SLOT(pauseIconUpdates()));
m_changedItemsTimer = new QTimer(q);
m_changedItemsTimer->setSingleShot(true);
m_changedItemsTimer->setInterval(5000);
connect(m_changedItemsTimer, SIGNAL(timeout()),
q, SLOT(delayedIconUpdate()));
KConfigGroup globalConfig(KGlobal::config(), "PreviewSettings");
m_enabledPlugins = globalConfig.readEntry("Plugins", QStringList()
<< "directorythumbnail"
<< "imagethumbnail"
<< "jpegthumbnail");
// If the user is upgrading from KDE <= 4.6, we must check if he had the 'jpegrotatedthumbnail' plugin enabled.
// This plugin does not exist any more in KDE >= 4.7, so we have to replace it with the 'jpegthumbnail' plugin.
if(m_enabledPlugins.contains(QLatin1String("jpegrotatedthumbnail"))) {
m_enabledPlugins.removeAll(QLatin1String("jpegrotatedthumbnail"));
m_enabledPlugins.append(QLatin1String("jpegthumbnail"));
globalConfig.writeEntry("Plugins", m_enabledPlugins);
globalConfig.sync();
}
}
KFilePreviewGenerator::Private::~Private()
{
killPreviewJobs();
m_pendingItems.clear();
m_dispatchedItems.clear();
delete m_tileSet;
}
void KFilePreviewGenerator::Private::requestSequenceIcon(const QModelIndex& index,
int sequenceIndex)
{
if (m_pendingItems.isEmpty() || (sequenceIndex == 0)) {
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
KFileItem item = dirModel->itemForIndex(index);
if (sequenceIndex == 0) {
m_sequenceIndices.remove(item.url());
} else {
m_sequenceIndices.insert(item.url(), sequenceIndex);
}
///@todo Update directly, without using m_sequenceIndices
updateIcons(KFileItemList() << item);
}
}
void KFilePreviewGenerator::Private::updateIcons(const KFileItemList& items)
{
if (items.isEmpty()) {
return;
}
applyCutItemEffect(items);
KFileItemList orderedItems = items;
orderItems(orderedItems);
foreach (const KFileItem& item, orderedItems) {
m_pendingItems.append(item);
}
if (m_previewShown) {
createPreviews(orderedItems);
} else {
startMimeTypeResolving();
}
}
void KFilePreviewGenerator::Private::updateIcons(const QModelIndex& topLeft,
const QModelIndex& bottomRight)
{
if (m_internalDataChange > 0) {
// QAbstractItemModel::setData() has been invoked internally by the KFilePreviewGenerator.
// The signal dataChanged() is connected with this method, but previews only need
// to be generated when an external data change has occurred.
return;
}
// dataChanged emitted for the root dir (e.g. permission changes)
if (!topLeft.isValid() || !bottomRight.isValid()) {
return;
}
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
KFileItemList itemList;
for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
const QModelIndex index = dirModel->index(row, 0);
if (!index.isValid()) {
continue;
}
const KFileItem item = dirModel->itemForIndex(index);
Q_ASSERT(!item.isNull());
if (m_previewShown) {
const KUrl url = item.url();
const bool hasChanged = m_changedItems.contains(url); // O(1)
m_changedItems.insert(url, hasChanged);
if (!hasChanged) {
// only update the icon if it has not been already updated within
// the last 5 seconds (the other icons will be updated later with
// the help of m_changedItemsTimer)
itemList.append(item);
}
} else {
itemList.append(item);
}
}
updateIcons(itemList);
m_changedItemsTimer->start();
}
void KFilePreviewGenerator::Private::addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap)
{
KIO::PreviewJob* senderJob = qobject_cast<KIO::PreviewJob*>(q->sender());
Q_ASSERT(senderJob != 0);
if (senderJob != 0) {
QMap<KUrl, int>::iterator it = m_sequenceIndices.find(item.url());
if (senderJob->sequenceIndex() && (it == m_sequenceIndices.end() || *it != senderJob->sequenceIndex())) {
return; // the sequence index does not match the one we want
}
if (!senderJob->sequenceIndex() && it != m_sequenceIndices.end()) {
return; // the sequence index does not match the one we want
}
m_sequenceIndices.erase(it);
}
if (!m_previewShown) {
// the preview has been canceled in the meantime
return;
}
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
// check whether the item is part of the directory lister (it is possible
// that a preview from an old directory lister is received)
bool isOldPreview = true;
KUrl itemParentDir(item.url());
itemParentDir.setPath(itemParentDir.directory());
foreach (const KUrl& dir, dirModel->dirLister()->directories()) {
if (dir == itemParentDir || !dir.hasPath()) {
isOldPreview = false;
break;
}
}
if (isOldPreview) {
return;
}
QPixmap icon = pixmap;
const QString mimeType = item.mimetype();
const int slashIndex = mimeType.indexOf(QLatin1Char('/'));
const QString mimeTypeGroup = mimeType.left(slashIndex);
if ((mimeTypeGroup != QLatin1String("image")) || !applyImageFrame(icon)) {
limitToSize(icon, m_viewAdapter->iconSize());
}
if (m_hasCutSelection && isCutItem(item)) {
// apply the disabled effect to the icon for marking it as "cut item"
// and apply the icon to the item
KIconEffect *iconEffect = KIconLoader::global()->iconEffect();
icon = iconEffect->apply(icon, KIconLoader::Desktop, KIconLoader::DisabledState);
}
KIconLoader::global()->drawOverlays(item.overlays(), icon, KIconLoader::Desktop);
// remember the preview and URL, so that it can be applied to the model
// in KFilePreviewGenerator::dispatchIconUpdateQueue()
ItemInfo preview;
preview.url = item.url();
preview.pixmap = icon;
m_previews.append(preview);
m_dispatchedItems.append(item);
}
void KFilePreviewGenerator::Private::slotPreviewJobFinished(KJob* job)
{
const int index = m_previewJobs.indexOf(job);
m_previewJobs.removeAt(index);
if (m_previewJobs.isEmpty()) {
if (m_clearItemQueues) {
m_pendingItems.clear();
m_dispatchedItems.clear();
m_pendingVisibleIconUpdates = 0;
QMetaObject::invokeMethod(q, "dispatchIconUpdateQueue", Qt::QueuedConnection);
}
m_sequenceIndices.clear(); // just to be sure that we don't leak anything
}
}
void KFilePreviewGenerator::Private::updateCutItems()
{
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
DataChangeObtainer obt(this);
clearCutItemsCache();
KFileItemList items;
KDirLister* dirLister = dirModel->dirLister();
const KUrl::List dirs = dirLister->directories();
foreach (const KUrl& url, dirs) {
items << dirLister->itemsForDir(url);
}
applyCutItemEffect(items);
}
void KFilePreviewGenerator::Private::clearCutItemsCache()
{
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
DataChangeObtainer obt(this);
KFileItemList previews;
// Reset the icons of all items that are stored in the cache
// to use their default MIME type icon.
foreach (const KUrl& url, m_cutItemsCache.keys()) {
const QModelIndex index = dirModel->indexForUrl(url);
if (index.isValid()) {
dirModel->setData(index, QIcon(), Qt::DecorationRole);
if (m_previewShown) {
previews.append(dirModel->itemForIndex(index));
}
}
}
m_cutItemsCache.clear();
if (previews.size() > 0) {
// assure that the previews gets restored
Q_ASSERT(m_previewShown);
orderItems(previews);
updateIcons(previews);
}
}
void KFilePreviewGenerator::Private::dispatchIconUpdateQueue()
{
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
const int count = m_previewShown ? m_previews.count()
: m_resolvedMimeTypes.count();
if (count > 0) {
LayoutBlocker blocker(m_itemView);
DataChangeObtainer obt(this);
if (m_previewShown) {
// dispatch preview queue
foreach (const ItemInfo& preview, m_previews) {
const QModelIndex idx = dirModel->indexForUrl(preview.url);
if (idx.isValid() && (idx.column() == 0)) {
dirModel->setData(idx, QIcon(preview.pixmap), Qt::DecorationRole);
}
}
m_previews.clear();
} else {
// dispatch mime type queue
foreach (const KFileItem& item, m_resolvedMimeTypes) {
const QModelIndex idx = dirModel->indexForItem(item);
dirModel->itemChanged(idx);
}
m_resolvedMimeTypes.clear();
}
m_pendingVisibleIconUpdates -= count;
if (m_pendingVisibleIconUpdates < 0) {
m_pendingVisibleIconUpdates = 0;
}
}
if (m_pendingVisibleIconUpdates > 0) {
// As long as there are pending previews for visible items, poll
// the preview queue periodically. If there are no pending previews,
// the queue is dispatched in slotPreviewJobFinished().
m_iconUpdateTimer->start();
}
}
void KFilePreviewGenerator::Private::pauseIconUpdates()
{
m_iconUpdatesPaused = true;
foreach (KJob* job, m_previewJobs) {
Q_ASSERT(job != 0);
job->suspend();
}
m_scrollAreaTimer->start();
}
void KFilePreviewGenerator::Private::resumeIconUpdates()
{
m_iconUpdatesPaused = false;
// Before creating new preview jobs the m_pendingItems queue must be
// cleaned up by removing the already dispatched items. Implementation
// note: The order of the m_dispatchedItems queue and the m_pendingItems
// queue is usually equal. So even when having a lot of elements the
// nested loop is no performance bottle neck, as the inner loop is only
// entered once in most cases.
foreach (const KFileItem& item, m_dispatchedItems) {
KFileItemList::iterator begin = m_pendingItems.begin();
KFileItemList::iterator end = m_pendingItems.end();
for (KFileItemList::iterator it = begin; it != end; ++it) {
if ((*it).url() == item.url()) {
m_pendingItems.erase(it);
break;
}
}
}
m_dispatchedItems.clear();
m_pendingVisibleIconUpdates = 0;
dispatchIconUpdateQueue();
if (m_previewShown) {
KFileItemList orderedItems = m_pendingItems;
orderItems(orderedItems);
// Kill all suspended preview jobs. Usually when a preview job
// has been finished, slotPreviewJobFinished() clears all item queues.
// This is not wanted in this case, as a new job is created afterwards
// for m_pendingItems.
m_clearItemQueues = false;
killPreviewJobs();
m_clearItemQueues = true;
createPreviews(orderedItems);
} else {
orderItems(m_pendingItems);
startMimeTypeResolving();
}
}
void KFilePreviewGenerator::Private::startMimeTypeResolving()
{
resolveMimeType();
m_iconUpdateTimer->start();
}
void KFilePreviewGenerator::Private::resolveMimeType()
{
if (m_pendingItems.isEmpty()) {
return;
}
// resolve at least one MIME type
bool resolved = false;
do {
KFileItem item = m_pendingItems.takeFirst();
if (item.isMimeTypeKnown()) {
if (m_pendingVisibleIconUpdates > 0) {
// The item is visible and the MIME type already known.
// Decrease the update counter for dispatchIconUpdateQueue():
--m_pendingVisibleIconUpdates;
}
} else {
// The MIME type is unknown and must get resolved. The
// directory model is not informed yet, as a single update
// would be very expensive. Instead the item is remembered in
// m_resolvedMimeTypes and will be dispatched later
// by dispatchIconUpdateQueue().
item.determineMimeType();
m_resolvedMimeTypes.append(item);
resolved = true;
}
} while (!resolved && !m_pendingItems.isEmpty());
if (m_pendingItems.isEmpty()) {
// All MIME types have been resolved now. Assure
// that the directory model gets informed about
// this, so that an update of the icons is done.
dispatchIconUpdateQueue();
} else if (!m_iconUpdatesPaused) {
// assure that the MIME type of the next
// item will be resolved asynchronously
QMetaObject::invokeMethod(q, "resolveMimeType", Qt::QueuedConnection);
}
}
bool KFilePreviewGenerator::Private::isCutItem(const KFileItem& item) const
{
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
const KUrl::List cutUrls = KUrl::List::fromMimeData(mimeData);
return cutUrls.contains(item.url());
}
void KFilePreviewGenerator::Private::applyCutItemEffect(const KFileItemList& items)
{
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
m_hasCutSelection = decodeIsCutSelection(mimeData);
if (!m_hasCutSelection) {
return;
}
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
const QSet<KUrl> cutUrls = KUrl::List::fromMimeData(mimeData).toSet();
DataChangeObtainer obt(this);
KIconEffect *iconEffect = KIconLoader::global()->iconEffect();
foreach (const KFileItem& item, items) {
if (cutUrls.contains(item.url())) {
const QModelIndex index = dirModel->indexForItem(item);
const QVariant value = dirModel->data(index, Qt::DecorationRole);
if (value.type() == QVariant::Icon) {
const QIcon icon(qvariant_cast<QIcon>(value));
const QSize actualSize = icon.actualSize(m_viewAdapter->iconSize());
QPixmap pixmap = icon.pixmap(actualSize);
const QHash<KUrl, QPixmap>::const_iterator cacheIt = m_cutItemsCache.constFind(item.url());
if ((cacheIt == m_cutItemsCache.constEnd()) || (cacheIt->cacheKey() != pixmap.cacheKey())) {
pixmap = iconEffect->apply(pixmap, KIconLoader::Desktop, KIconLoader::DisabledState);
dirModel->setData(index, QIcon(pixmap), Qt::DecorationRole);
m_cutItemsCache.insert(item.url(), pixmap);
}
}
}
}
}
bool KFilePreviewGenerator::Private::applyImageFrame(QPixmap& icon)
{
const QSize maxSize = m_viewAdapter->iconSize();
// The original size of an image is not exported by the thumbnail mechanism.
// Still it would be helpful to not apply an image frame for e. g. icons that
// fit into the given boundaries:
const bool isIconCandidate = (icon.width() == icon.height()) &&
((icon.width() & 0x7) == 0);
const bool applyFrame = (maxSize.width() > KIconLoader::SizeSmallMedium) &&
(maxSize.height() > KIconLoader::SizeSmallMedium) &&
!isIconCandidate;
if (!applyFrame) {
// the maximum size or the image itself is too small for a frame
return false;
}
// resize the icon to the maximum size minus the space required for the frame
const QSize size(maxSize.width() - TileSet::LeftMargin - TileSet::RightMargin,
maxSize.height() - TileSet::TopMargin - TileSet::BottomMargin);
limitToSize(icon, size);
if (m_tileSet == 0) {
m_tileSet = new TileSet();
}
QPixmap framedIcon(icon.size().width() + TileSet::LeftMargin + TileSet::RightMargin,
icon.size().height() + TileSet::TopMargin + TileSet::BottomMargin);
framedIcon.fill(Qt::transparent);
QPainter painter;
painter.begin(&framedIcon);
painter.setCompositionMode(QPainter::CompositionMode_Source);
m_tileSet->paint(&painter, framedIcon.rect());
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
painter.drawPixmap(TileSet::LeftMargin, TileSet::TopMargin, icon);
painter.end();
icon = framedIcon;
return true;
}
void KFilePreviewGenerator::Private::limitToSize(QPixmap& icon, const QSize& maxSize)
{
if ((icon.width() > maxSize.width()) || (icon.height() > maxSize.height())) {
#if defined(Q_WS_X11) && defined(HAVE_XRENDER)
// Assume that the texture size limit is 2048x2048
if ((icon.width() <= 2048) && (icon.height() <= 2048) && icon.x11PictureHandle()) {
QSize size = icon.size();
size.scale(maxSize, Qt::KeepAspectRatio);
const qreal factor = size.width() / qreal(icon.width());
XTransform xform = {{
{ XDoubleToFixed(1 / factor), 0, 0 },
{ 0, XDoubleToFixed(1 / factor), 0 },
{ 0, 0, XDoubleToFixed(1) }
}};
QPixmap pixmap(size);
pixmap.fill(Qt::transparent);
Display* dpy = QX11Info::display();
XRenderPictureAttributes attr;
attr.repeat = RepeatPad;
XRenderChangePicture(dpy, icon.x11PictureHandle(), CPRepeat, &attr);
XRenderSetPictureFilter(dpy, icon.x11PictureHandle(), FilterBilinear, 0, 0);
XRenderSetPictureTransform(dpy, icon.x11PictureHandle(), &xform);
XRenderComposite(dpy, PictOpOver, icon.x11PictureHandle(), None, pixmap.x11PictureHandle(),
0, 0, 0, 0, 0, 0, pixmap.width(), pixmap.height());
icon = pixmap;
} else {
icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation);
}
#else
icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation);
#endif
}
}
void KFilePreviewGenerator::Private::createPreviews(const KFileItemList& items)
{
if (items.count() == 0) {
return;
}
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
m_hasCutSelection = decodeIsCutSelection(mimeData);
// PreviewJob internally caches items always with the size of
// 128 x 128 pixels or 256 x 256 pixels. A downscaling is done
// by PreviewJob if a smaller size is requested. For images KFilePreviewGenerator must
// do a downscaling anyhow because of the frame, so in this case only the provided
// cache sizes are requested.
KFileItemList imageItems;
KFileItemList otherItems;
QString mimeType;
QString mimeTypeGroup;
foreach (const KFileItem& item, items) {
mimeType = item.mimetype();
const int slashIndex = mimeType.indexOf(QLatin1Char('/'));
mimeTypeGroup = mimeType.left(slashIndex);
if (mimeTypeGroup == QLatin1String("image")) {
imageItems.append(item);
} else {
otherItems.append(item);
}
}
const QSize size = m_viewAdapter->iconSize();
startPreviewJob(otherItems, size.width(), size.height());
const int cacheSize = (size.width() > 128) || (size.height() > 128) ? 256 : 128;
startPreviewJob(imageItems, cacheSize, cacheSize);
m_iconUpdateTimer->start();
}
void KFilePreviewGenerator::Private::startPreviewJob(const KFileItemList& items, int width, int height)
{
if (items.count() > 0) {
KIO::PreviewJob* job = KIO::filePreview(items, QSize(width, height), &m_enabledPlugins);
// Set the sequence index to the target. We only need to check if items.count() == 1,
// because requestSequenceIcon(..) creates exactly such a request.
if (!m_sequenceIndices.isEmpty() && (items.count() == 1)) {
QMap<KUrl, int>::iterator it = m_sequenceIndices.find(items[0].url());
if (it != m_sequenceIndices.end()) {
job->setSequenceIndex(*it);
}
}
connect(job, SIGNAL(gotPreview(KFileItem,QPixmap)),
q, SLOT(addToPreviewQueue(KFileItem,QPixmap)));
connect(job, SIGNAL(finished(KJob*)),
q, SLOT(slotPreviewJobFinished(KJob*)));
m_previewJobs.append(job);
}
}
void KFilePreviewGenerator::Private::killPreviewJobs()
{
foreach (KJob* job, m_previewJobs) {
Q_ASSERT(job != 0);
job->kill();
}
m_previewJobs.clear();
m_sequenceIndices.clear();
m_iconUpdateTimer->stop();
m_scrollAreaTimer->stop();
m_changedItemsTimer->stop();
}
void KFilePreviewGenerator::Private::orderItems(KFileItemList& items)
{
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
// Order the items in a way that the preview for the visible items
// is generated first, as this improves the feeled performance a lot.
const bool hasProxy = (m_proxyModel != 0);
const int itemCount = items.count();
const QRect visibleArea = m_viewAdapter->visibleArea();
QModelIndex dirIndex;
QRect itemRect;
int insertPos = 0;
for (int i = 0; i < itemCount; ++i) {
dirIndex = dirModel->indexForItem(items.at(i)); // O(n) (n = number of rows)
if (hasProxy) {
const QModelIndex proxyIndex = m_proxyModel->mapFromSource(dirIndex);
itemRect = m_viewAdapter->visualRect(proxyIndex);
} else {
itemRect = m_viewAdapter->visualRect(dirIndex);
}
if (itemRect.intersects(visibleArea)) {
// The current item is (at least partly) visible. Move it
// to the front of the list, so that the preview is
// generated earlier.
items.insert(insertPos, items.at(i));
items.removeAt(i + 1);
++insertPos;
++m_pendingVisibleIconUpdates;
}
}
}
bool KFilePreviewGenerator::Private::decodeIsCutSelection(const QMimeData* mimeData)
{
const QByteArray data = mimeData->data("application/x-kde-cutselection");
if (data.isEmpty()) {
return false;
} else {
return data.at(0) == QLatin1Char('1');
}
}
void KFilePreviewGenerator::Private::addItemsToList(const QModelIndex& index, KFileItemList& list)
{
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
const int rowCount = dirModel->rowCount(index);
for (int row = 0; row < rowCount; ++row) {
const QModelIndex subIndex = dirModel->index(row, 0, index);
KFileItem item = dirModel->itemForIndex(subIndex);
list.append(item);
if (dirModel->rowCount(subIndex) > 0) {
// the model is hierarchical (treeview)
addItemsToList(subIndex, list);
}
}
}
void KFilePreviewGenerator::Private::delayedIconUpdate()
{
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
// Precondition: No items have been changed within the last
// 5 seconds. This means that items that have been changed constantly
// due to a copy operation should be updated now.
KFileItemList itemList;
QHash<KUrl, bool>::const_iterator it = m_changedItems.constBegin();
while (it != m_changedItems.constEnd()) {
const bool hasChanged = it.value();
if (hasChanged) {
const QModelIndex index = dirModel->indexForUrl(it.key());
const KFileItem item = dirModel->itemForIndex(index);
itemList.append(item);
}
++it;
}
m_changedItems.clear();
updateIcons(itemList);
}
void KFilePreviewGenerator::Private::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end)
{
if (m_changedItems.isEmpty()) {
return;
}
KDirModel* dirModel = m_dirModel.data();
if (!dirModel) {
return;
}
for (int row = start; row <= end; row++) {
const QModelIndex index = dirModel->index(row, 0, parent);
const KFileItem item = dirModel->itemForIndex(index);
if (!item.isNull()) {
m_changedItems.remove(item.url());
}
if (dirModel->hasChildren(index)) {
rowsAboutToBeRemoved(index, 0, dirModel->rowCount(index) - 1);
}
}
}
KFilePreviewGenerator::KFilePreviewGenerator(QAbstractItemView* parent) :
QObject(parent),
d(new Private(this, new KIO::DefaultViewAdapter(parent, this), parent->model()))
{
d->m_itemView = parent;
}
KFilePreviewGenerator::KFilePreviewGenerator(KAbstractViewAdapter* parent, QAbstractProxyModel* model) :
QObject(parent),
d(new Private(this, parent, model))
{
}
KFilePreviewGenerator::~KFilePreviewGenerator()
{
delete d;
}
void KFilePreviewGenerator::setPreviewShown(bool show)
{
if (d->m_previewShown == show) {
return;
}
KDirModel* dirModel = d->m_dirModel.data();
if (show && (!d->m_viewAdapter->iconSize().isValid() || !dirModel)) {
// The view must provide an icon size and a directory model,
// otherwise the showing the previews will get ignored
return;
}
d->m_previewShown = show;
if (!show) {
// Clear the icon for all items so that the MIME type
// gets reloaded
KFileItemList itemList;
d->addItemsToList(QModelIndex(), itemList);
const bool blocked = dirModel->signalsBlocked();
dirModel->blockSignals(true);
+ QList<QModelIndex> indexesWithKnownMimeType;
foreach (const KFileItem& item, itemList) {
const QModelIndex index = dirModel->indexForItem(item);
+ if (item.isMimeTypeKnown()) {
+ indexesWithKnownMimeType.append(index);
+ }
dirModel->setData(index, QIcon(), Qt::DecorationRole);
}
dirModel->blockSignals(blocked);
+
+ // Items without a known mimetype will be handled (delayed) by updateIcons.
+ // So we need to update items with a known mimetype ourselves.
+ foreach (const QModelIndex& index, indexesWithKnownMimeType) {
+ dirModel->itemChanged(index);
+ }
}
updateIcons();
}
bool KFilePreviewGenerator::isPreviewShown() const
{
return d->m_previewShown;
}
// deprecated (use updateIcons() instead)
void KFilePreviewGenerator::updatePreviews()
{
updateIcons();
}
void KFilePreviewGenerator::updateIcons()
{
d->killPreviewJobs();
d->clearCutItemsCache();
d->m_pendingItems.clear();
d->m_dispatchedItems.clear();
KFileItemList itemList;
d->addItemsToList(QModelIndex(), itemList);
d->updateIcons(itemList);
}
void KFilePreviewGenerator::cancelPreviews()
{
d->killPreviewJobs();
d->m_pendingItems.clear();
d->m_dispatchedItems.clear();
updateIcons();
}
void KFilePreviewGenerator::setEnabledPlugins(const QStringList& plugins)
{
d->m_enabledPlugins = plugins;
}
QStringList KFilePreviewGenerator::enabledPlugins() const
{
return d->m_enabledPlugins;
}
#include "kfilepreviewgenerator.moc"
diff --git a/kfile/kfilewidget.cpp b/kfile/kfilewidget.cpp
index dbb553bfef..506c9024f6 100644
--- a/kfile/kfilewidget.cpp
+++ b/kfile/kfilewidget.cpp
@@ -1,2757 +1,2759 @@
// -*- c++ -*-
/* This file is part of the KDE libraries
Copyright (C) 1997, 1998 Richard Moore <rich@kde.org>
1998 Stephan Kulow <coolo@kde.org>
1998 Daniel Grana <grana@ie.iwi.unibe.ch>
1999,2000,2001,2002,2003 Carsten Pfeiffer <pfeiffer@kde.org>
2003 Clarence Dang <dang@kde.org>
2007 David Faure <faure@kde.org>
2008 Rafael Fernández López <ereslibre@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kfilewidget.h"
#include "kfileplacesview.h"
#include "kfileplacesmodel.h"
#include "kfilebookmarkhandler_p.h"
#include "kurlcombobox.h"
#include "kurlnavigator.h"
#include "kfilepreviewgenerator.h"
#include <config-kfile.h>
#include <kactioncollection.h>
#include <kdiroperator.h>
#include <kdirselectdialog.h>
#include <kfilefiltercombo.h>
#include <kimagefilepreview.h>
#include <kmenu.h>
#include <kmimetype.h>
#include <kpushbutton.h>
#include <krecentdocument.h>
#include <ktoolbar.h>
#include <kurlcompletion.h>
#include <kuser.h>
#include <kprotocolmanager.h>
#include <kio/job.h>
#include <kio/jobuidelegate.h>
#include <kio/netaccess.h>
#include <kio/scheduler.h>
#include <krecentdirs.h>
#include <kdebug.h>
#include <kio/kfileitemdelegate.h>
#include <kde_file.h>
#include <QtGui/QCheckBox>
#include <QtGui/QDockWidget>
#include <QtGui/QLayout>
#include <QtGui/QLabel>
#include <QtGui/QLineEdit>
#include <QtGui/QSplitter>
#include <QtGui/QAbstractProxyModel>
#include <QtGui/QHelpEvent>
#include <QtGui/QApplication>
#include <QtCore/QFSFileEngine>
#include <kshell.h>
#include <kmessagebox.h>
#include <kauthorized.h>
class KFileWidgetPrivate
{
public:
KFileWidgetPrivate(KFileWidget *widget)
: q(widget),
boxLayout(0),
placesDock(0),
placesView(0),
placesViewSplitter(0),
placesViewWidth(-1),
labeledCustomWidget(0),
bottomCustomWidget(0),
autoSelectExtCheckBox(0),
operationMode(KFileWidget::Opening),
bookmarkHandler(0),
toolbar(0),
locationEdit(0),
ops(0),
filterWidget(0),
autoSelectExtChecked(false),
keepLocation(false),
hasView(false),
hasDefaultFilter(false),
inAccept(false),
dummyAdded(false),
confirmOverwrite(false),
differentHierarchyLevelItemsEntered(false),
previewGenerator(0),
iconSizeSlider(0)
{
}
~KFileWidgetPrivate()
{
delete bookmarkHandler; // Should be deleted before ops!
delete ops;
}
void updateLocationWhatsThis();
void updateAutoSelectExtension();
void initSpeedbar();
void initGUI();
void readConfig(KConfigGroup &configGroup);
void writeConfig(KConfigGroup &configGroup);
void setNonExtSelection();
void setLocationText(const KUrl&);
void setLocationText(const KUrl::List&);
void appendExtension(KUrl &url);
void updateLocationEditExtension(const QString &);
void updateFilter();
KUrl::List& parseSelectedUrls();
/**
* Parses the string "line" for files. If line doesn't contain any ", the
* whole line will be interpreted as one file. If the number of " is odd,
* an empty list will be returned. Otherwise, all items enclosed in " "
* will be returned as correct urls.
*/
KUrl::List tokenize(const QString& line) const;
/**
* Reads the recent used files and inserts them into the location combobox
*/
void readRecentFiles(KConfigGroup &cg);
/**
* Saves the entries from the location combobox.
*/
void saveRecentFiles(KConfigGroup &cg);
/**
* called when an item is highlighted/selected in multiselection mode.
* handles setting the locationEdit.
*/
void multiSelectionChanged();
/**
* Returns the absolute version of the URL specified in locationEdit.
*/
KUrl getCompleteUrl(const QString&) const;
/**
* Sets the dummy entry on the history combo box. If the dummy entry
* already exists, it is overwritten with this information.
*/
void setDummyHistoryEntry(const QString& text, const QPixmap& icon = QPixmap(),
bool usePreviousPixmapIfNull = true);
/**
* Removes the dummy entry of the history combo box.
*/
void removeDummyHistoryEntry();
/**
* Asks for overwrite confirmation using a KMessageBox and returns
* true if the user accepts.
*
* @since 4.2
*/
bool toOverwrite(const KUrl&);
// private slots
void _k_slotLocationChanged( const QString& );
void _k_urlEntered( const KUrl& );
void _k_enterUrl( const KUrl& );
void _k_enterUrl( const QString& );
void _k_locationAccepted( const QString& );
void _k_slotFilterChanged();
void _k_fileHighlighted( const KFileItem& );
void _k_fileSelected( const KFileItem& );
void _k_slotLoadingFinished();
void _k_fileCompletion( const QString& );
void _k_toggleSpeedbar( bool );
void _k_toggleBookmarks( bool );
void _k_slotAutoSelectExtClicked();
void _k_placesViewSplitterMoved(int, int);
void _k_activateUrlNavigator();
void _k_zoomOutIconsSize();
void _k_zoomInIconsSize();
void _k_slotIconSizeSliderMoved(int);
void _k_slotIconSizeChanged(int);
void addToRecentDocuments();
QString locationEditCurrentText() const;
/**
* KIO::NetAccess::mostLocalUrl local replacement.
* This method won't show any progress dialogs for stating, since
* they are very annoying when stating.
*/
KUrl mostLocalUrl(const KUrl &url);
void setInlinePreviewShown(bool show);
KFileWidget* q;
// the last selected url
KUrl url;
// the selected filenames in multiselection mode -- FIXME
QString filenames;
// now following all kind of widgets, that I need to rebuild
// the geometry management
QBoxLayout *boxLayout;
QGridLayout *lafBox;
QVBoxLayout *vbox;
QLabel *locationLabel;
QWidget *opsWidget;
QWidget *pathSpacer;
QLabel *filterLabel;
KUrlNavigator *urlNavigator;
KPushButton *okButton, *cancelButton;
QDockWidget *placesDock;
KFilePlacesView *placesView;
QSplitter *placesViewSplitter;
// caches the places view width. This value will be updated when the splitter
// is moved. This allows us to properly set a value when the dialog itself
// is resized
int placesViewWidth;
QWidget *labeledCustomWidget;
QWidget *bottomCustomWidget;
// Automatically Select Extension stuff
QCheckBox *autoSelectExtCheckBox;
QString extension; // current extension for this filter
QList<KIO::StatJob*> statJobs;
KUrl::List urlList; //the list of selected urls
KFileWidget::OperationMode operationMode;
// The file class used for KRecentDirs
QString fileClass;
KFileBookmarkHandler *bookmarkHandler;
KActionMenu* bookmarkButton;
KToolBar *toolbar;
KUrlComboBox *locationEdit;
KDirOperator *ops;
KFileFilterCombo *filterWidget;
QTimer filterDelayTimer;
KFilePlacesModel *model;
// whether or not the _user_ has checked the above box
bool autoSelectExtChecked : 1;
// indicates if the location edit should be kept or cleared when changing
// directories
bool keepLocation : 1;
// the KDirOperators view is set in KFileWidget::show(), so to avoid
// setting it again and again, we have this nice little boolean :)
bool hasView : 1;
bool hasDefaultFilter : 1; // necessary for the operationMode
bool autoDirectoryFollowing : 1;
bool inAccept : 1; // true between beginning and end of accept()
bool dummyAdded : 1; // if the dummy item has been added. This prevents the combo from having a
// blank item added when loaded
bool confirmOverwrite : 1;
bool differentHierarchyLevelItemsEntered;
KFilePreviewGenerator *previewGenerator;
QSlider *iconSizeSlider;
};
K_GLOBAL_STATIC(KUrl, lastDirectory) // to set the start path
static const char autocompletionWhatsThisText[] = I18N_NOOP("<qt>While typing in the text area, you may be presented "
"with possible matches. "
"This feature can be controlled by clicking with the right mouse button "
"and selecting a preferred mode from the <b>Text Completion</b> menu.</qt>");
// returns true if the string contains "<a>:/" sequence, where <a> is at least 2 alpha chars
static bool containsProtocolSection( const QString& string )
{
int len = string.length();
static const char prot[] = ":/";
for (int i=0; i < len;) {
i = string.indexOf( QLatin1String(prot), i );
if (i == -1)
return false;
int j=i-1;
for (; j >= 0; j--) {
const QChar& ch( string[j] );
if (ch.toAscii() == 0 || !ch.isLetter())
break;
if (ch.isSpace() && (i-j-1) >= 2)
return true;
}
if (j < 0 && i >= 2)
return true; // at least two letters before ":/"
i += 3; // skip : and / and one char
}
return false;
}
KFileWidget::KFileWidget( const KUrl& _startDir, QWidget *parent )
: QWidget(parent), KAbstractFileWidget(), d(new KFileWidgetPrivate(this))
{
KUrl startDir(_startDir);
kDebug(kfile_area) << "startDir" << startDir;
QString filename;
d->okButton = new KPushButton(KStandardGuiItem::ok(), this);
d->okButton->setDefault(true);
d->cancelButton = new KPushButton(KStandardGuiItem::cancel(), this);
// The dialog shows them
d->okButton->hide();
d->cancelButton->hide();
d->opsWidget = new QWidget(this);
QVBoxLayout *opsWidgetLayout = new QVBoxLayout(d->opsWidget);
opsWidgetLayout->setMargin(0);
opsWidgetLayout->setSpacing(0);
//d->toolbar = new KToolBar(this, true);
d->toolbar = new KToolBar(d->opsWidget, true);
d->toolbar->setObjectName("KFileWidget::toolbar");
d->toolbar->setMovable(false);
opsWidgetLayout->addWidget(d->toolbar);
d->model = new KFilePlacesModel(this);
// Resolve this now so that a 'kfiledialog:' URL, if specified,
// does not get inserted into the urlNavigator history.
d->url = getStartUrl( startDir, d->fileClass, filename );
startDir = d->url;
// Don't pass startDir to the KUrlNavigator at this stage: as well as
// the above, it may also contain a file name which should not get
// inserted in that form into the old-style navigation bar history.
// Wait until the KIO::stat has been done later.
//
// The stat cannot be done before this point, bug 172678.
d->urlNavigator = new KUrlNavigator(d->model, KUrl(), d->opsWidget); //d->toolbar);
d->urlNavigator->setPlacesSelectorVisible(false);
opsWidgetLayout->addWidget(d->urlNavigator);
KUrl u;
KUrlComboBox *pathCombo = d->urlNavigator->editor();
#ifdef Q_WS_WIN
foreach( const QFileInfo &drive,QFSFileEngine::drives() )
{
u.setPath( drive.filePath() );
pathCombo->addDefaultUrl(u,
KIO::pixmapForUrl( u, 0, KIconLoader::Small ),
i18n("Drive: %1", u.toLocalFile()));
}
#else
u.setPath(QDir::rootPath());
pathCombo->addDefaultUrl(u,
KIO::pixmapForUrl(u, 0, KIconLoader::Small),
u.toLocalFile());
#endif
u.setPath(QDir::homePath());
pathCombo->addDefaultUrl(u, KIO::pixmapForUrl(u, 0, KIconLoader::Small),
u.path(KUrl::AddTrailingSlash));
KUrl docPath;
docPath.setPath( KGlobalSettings::documentPath() );
if ( (u.path(KUrl::AddTrailingSlash) != docPath.path(KUrl::AddTrailingSlash)) &&
QDir(docPath.path(KUrl::AddTrailingSlash)).exists() )
{
pathCombo->addDefaultUrl( docPath,
KIO::pixmapForUrl( docPath, 0, KIconLoader::Small ),
docPath.path(KUrl::AddTrailingSlash));
}
u.setPath( KGlobalSettings::desktopPath() );
pathCombo->addDefaultUrl(u,
KIO::pixmapForUrl(u, 0, KIconLoader::Small),
u.path(KUrl::AddTrailingSlash));
d->ops = new KDirOperator(KUrl(), d->opsWidget);
d->ops->setObjectName( "KFileWidget::ops" );
d->ops->setIsSaving(d->operationMode == Saving);
opsWidgetLayout->addWidget(d->ops);
connect(d->ops, SIGNAL(urlEntered(KUrl)),
SLOT(_k_urlEntered(KUrl)));
connect(d->ops, SIGNAL(fileHighlighted(KFileItem)),
SLOT(_k_fileHighlighted(KFileItem)));
connect(d->ops, SIGNAL(fileSelected(KFileItem)),
SLOT(_k_fileSelected(KFileItem)));
connect(d->ops, SIGNAL(finishedLoading()),
SLOT(_k_slotLoadingFinished()));
d->ops->setupMenu(KDirOperator::SortActions |
KDirOperator::FileActions |
KDirOperator::ViewActions);
KActionCollection *coll = d->ops->actionCollection();
coll->addAssociatedWidget(this);
// add nav items to the toolbar
//
// NOTE: The order of the button icons here differs from that
// found in the file manager and web browser, but has been discussed
// and agreed upon on the kde-core-devel mailing list:
//
// http://lists.kde.org/?l=kde-core-devel&m=116888382514090&w=2
coll->action( "up" )->setWhatsThis(i18n("<qt>Click this button to enter the parent folder.<br /><br />"
"For instance, if the current location is file:/home/%1 clicking this "
"button will take you to file:/home.</qt>", KUser().loginName() ));
coll->action( "back" )->setWhatsThis(i18n("Click this button to move backwards one step in the browsing history."));
coll->action( "forward" )->setWhatsThis(i18n("Click this button to move forward one step in the browsing history."));
coll->action( "reload" )->setWhatsThis(i18n("Click this button to reload the contents of the current location."));
coll->action( "mkdir" )->setShortcut( QKeySequence(Qt::Key_F10) );
coll->action( "mkdir" )->setWhatsThis(i18n("Click this button to create a new folder."));
KAction *goToNavigatorAction = coll->addAction( "gotonavigator", this, SLOT(_k_activateUrlNavigator()) );
goToNavigatorAction->setShortcut( QKeySequence(Qt::CTRL + Qt::Key_L) );
KToggleAction *showSidebarAction =
new KToggleAction(i18n("Show Places Navigation Panel"), this);
coll->addAction("toggleSpeedbar", showSidebarAction);
showSidebarAction->setShortcut( QKeySequence(Qt::Key_F9) );
connect( showSidebarAction, SIGNAL(toggled(bool)),
SLOT(_k_toggleSpeedbar(bool)) );
KToggleAction *showBookmarksAction =
new KToggleAction(i18n("Show Bookmarks"), this);
coll->addAction("toggleBookmarks", showBookmarksAction);
connect( showBookmarksAction, SIGNAL(toggled(bool)),
SLOT(_k_toggleBookmarks(bool)) );
KActionMenu *menu = new KActionMenu( KIcon("configure"), i18n("Options"), this);
coll->addAction("extra menu", menu);
menu->setWhatsThis(i18n("<qt>This is the preferences menu for the file dialog. "
"Various options can be accessed from this menu including: <ul>"
"<li>how files are sorted in the list</li>"
"<li>types of view, including icon and list</li>"
"<li>showing of hidden files</li>"
"<li>the Places navigation panel</li>"
"<li>file previews</li>"
"<li>separating folders from files</li></ul></qt>"));
menu->addAction(coll->action("sorting menu"));
menu->addAction(coll->action("view menu"));
menu->addSeparator();
menu->addAction(coll->action("decoration menu"));
menu->addSeparator();
KAction * showHidden = qobject_cast<KAction*>(coll->action( "show hidden" ));
if (showHidden) {
showHidden->setShortcut(
KShortcut( QKeySequence(Qt::ALT + Qt::Key_Period), QKeySequence(Qt::Key_F8) ) );
}
menu->addAction( showHidden );
menu->addAction( showSidebarAction );
menu->addAction( showBookmarksAction );
coll->action( "inline preview" )->setShortcut( QKeySequence(Qt::Key_F11) );
menu->addAction( coll->action( "preview" ));
menu->setDelayed( false );
connect( menu->menu(), SIGNAL(aboutToShow()),
d->ops, SLOT(updateSelectionDependentActions()));
d->iconSizeSlider = new QSlider(this);
d->iconSizeSlider->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Fixed);
d->iconSizeSlider->setOrientation(Qt::Horizontal);
d->iconSizeSlider->setMinimum(0);
d->iconSizeSlider->setMaximum(100);
d->iconSizeSlider->installEventFilter(this);
connect(d->iconSizeSlider, SIGNAL(valueChanged(int)),
d->ops, SLOT(setIconsZoom(int)));
connect(d->iconSizeSlider, SIGNAL(valueChanged(int)),
this, SLOT(_k_slotIconSizeChanged(int)));
connect(d->iconSizeSlider, SIGNAL(sliderMoved(int)),
this, SLOT(_k_slotIconSizeSliderMoved(int)));
connect(d->ops, SIGNAL(currentIconSizeChanged(int)),
d->iconSizeSlider, SLOT(setValue(int)));
KAction *furtherAction = new KAction(KIcon("file-zoom-out"), i18n("Zoom out"), this);
connect(furtherAction, SIGNAL(triggered()), SLOT(_k_zoomOutIconsSize()));
KAction *closerAction = new KAction(KIcon("file-zoom-in"), i18n("Zoom in"), this);
connect(closerAction, SIGNAL(triggered()), SLOT(_k_zoomInIconsSize()));
QWidget *midSpacer = new QWidget(this);
midSpacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
QAction *separator = new QAction(this);
separator->setSeparator(true);
QAction *separator2 = new QAction(this);
separator2->setSeparator(true);
d->toolbar->addAction(coll->action("back" ));
d->toolbar->addAction(coll->action("forward"));
d->toolbar->addAction(coll->action("up"));
d->toolbar->addAction(coll->action("reload"));
d->toolbar->addAction(separator);
d->toolbar->addAction(coll->action("inline preview"));
d->toolbar->addWidget(midSpacer);
d->toolbar->addAction(furtherAction);
d->toolbar->addWidget(d->iconSizeSlider);
d->toolbar->addAction(closerAction);
d->toolbar->addAction(separator2);
d->toolbar->addAction(coll->action("mkdir"));
d->toolbar->addAction(menu);
d->toolbar->setToolButtonStyle(Qt::ToolButtonIconOnly);
d->toolbar->setMovable(false);
KUrlCompletion *pathCompletionObj = new KUrlCompletion( KUrlCompletion::DirCompletion );
pathCombo->setCompletionObject( pathCompletionObj );
pathCombo->setAutoDeleteCompletionObject( true );
connect( d->urlNavigator, SIGNAL(urlChanged(KUrl)),
this, SLOT(_k_enterUrl(KUrl)));
connect( d->urlNavigator, SIGNAL(returnPressed()),
d->ops, SLOT(setFocus()));
QString whatsThisText;
// the Location label/edit
d->locationLabel = new QLabel(i18n("&Name:"), this);
d->locationEdit = new KUrlComboBox(KUrlComboBox::Files, true, this);
d->locationEdit->installEventFilter(this);
// Properly let the dialog be resized (to smaller). Otherwise we could have
// huge dialogs that can't be resized to smaller (it would be as big as the longest
// item in this combo box). (ereslibre)
d->locationEdit->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength);
connect( d->locationEdit, SIGNAL(editTextChanged(QString)),
SLOT(_k_slotLocationChanged(QString)) );
d->updateLocationWhatsThis();
d->locationLabel->setBuddy(d->locationEdit);
KUrlCompletion *fileCompletionObj = new KUrlCompletion( KUrlCompletion::FileCompletion );
d->locationEdit->setCompletionObject( fileCompletionObj );
d->locationEdit->setAutoDeleteCompletionObject( true );
connect( fileCompletionObj, SIGNAL(match(QString)),
SLOT(_k_fileCompletion(QString)) );
connect(d->locationEdit, SIGNAL(returnPressed(QString)),
this, SLOT(_k_locationAccepted(QString)));
// the Filter label/edit
whatsThisText = i18n("<qt>This is the filter to apply to the file list. "
"File names that do not match the filter will not be shown.<p>"
"You may select from one of the preset filters in the "
"drop down menu, or you may enter a custom filter "
"directly into the text area.</p><p>"
"Wildcards such as * and ? are allowed.</p></qt>");
d->filterLabel = new QLabel(i18n("&Filter:"), this);
d->filterLabel->setWhatsThis(whatsThisText);
d->filterWidget = new KFileFilterCombo(this);
// Properly let the dialog be resized (to smaller). Otherwise we could have
// huge dialogs that can't be resized to smaller (it would be as big as the longest
// item in this combo box). (ereslibre)
d->filterWidget->setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLength);
d->filterWidget->setWhatsThis(whatsThisText);
d->filterLabel->setBuddy(d->filterWidget);
connect(d->filterWidget, SIGNAL(filterChanged()), SLOT(_k_slotFilterChanged()));
d->filterDelayTimer.setSingleShot(true);
d->filterDelayTimer.setInterval(300);
connect(d->filterWidget, SIGNAL(editTextChanged(QString)), &d->filterDelayTimer, SLOT(start()));
connect(&d->filterDelayTimer, SIGNAL(timeout()), SLOT(_k_slotFilterChanged()));
// the Automatically Select Extension checkbox
// (the text, visibility etc. is set in updateAutoSelectExtension(), which is called by readConfig())
d->autoSelectExtCheckBox = new QCheckBox (this);
d->autoSelectExtCheckBox->setStyleSheet(QString("QCheckBox { padding-top: %1px; }").arg(KDialog::spacingHint()));
connect(d->autoSelectExtCheckBox, SIGNAL(clicked()), SLOT(_k_slotAutoSelectExtClicked()));
d->initGUI(); // activate GM
// read our configuration
KSharedConfig::Ptr config = KGlobal::config();
KConfigGroup viewConfigGroup(config, ConfigGroup);
d->readConfig(viewConfigGroup);
coll->action("inline preview")->setChecked(d->ops->isInlinePreviewShown());
d->iconSizeSlider->setValue(d->ops->iconsZoom());
KFilePreviewGenerator *pg = d->ops->previewGenerator();
if (pg) {
coll->action("inline preview")->setChecked(pg->isPreviewShown());
}
// getStartUrl() above will have resolved the startDir parameter into
// a directory and file name in the two cases: (a) where it is a
// special "kfiledialog:" URL, or (b) where it is a plain file name
// only without directory or protocol. For any other startDir
// specified, it is not possible to resolve whether there is a file name
// present just by looking at the URL; the only way to be sure is
// to stat it.
bool statRes = false;
if ( filename.isEmpty() )
{
KIO::StatJob *statJob = KIO::stat(startDir, KIO::HideProgressInfo);
statRes = KIO::NetAccess::synchronousRun(statJob, this);
kDebug(kfile_area) << "stat of" << startDir << "-> statRes" << statRes << "isDir" << statJob->statResult().isDir();
if (!statRes || !statJob->statResult().isDir()) {
filename = startDir.fileName();
startDir.setPath(startDir.directory());
kDebug(kfile_area) << "statJob -> startDir" << startDir << "filename" << filename;
}
}
d->ops->setUrl(startDir, true);
d->urlNavigator->setLocationUrl(startDir);
if (d->placesView) {
d->placesView->setUrl(startDir);
}
// We have a file name either explicitly specified, or have checked that
// we could stat it and it is not a directory. Set it.
if (!filename.isEmpty()) {
QLineEdit* lineEdit = d->locationEdit->lineEdit();
kDebug(kfile_area) << "selecting filename" << filename;
if (statRes) {
d->setLocationText(filename);
} else {
lineEdit->setText(filename);
// Preserve this filename when clicking on the view (cf _k_fileHighlighted)
lineEdit->setModified(true);
}
lineEdit->selectAll();
}
d->locationEdit->setFocus();
}
KFileWidget::~KFileWidget()
{
KSharedConfig::Ptr config = KGlobal::config();
config->sync();
delete d;
}
void KFileWidget::setLocationLabel(const QString& text)
{
d->locationLabel->setText(text);
}
void KFileWidget::setFilter(const QString& filter)
{
int pos = filter.indexOf('/');
// Check for an un-escaped '/', if found
// interpret as a MIME filter.
if (pos > 0 && filter[pos - 1] != '\\') {
QStringList filters = filter.split(' ', QString::SkipEmptyParts);
setMimeFilter( filters );
return;
}
// Strip the escape characters from
// escaped '/' characters.
QString copy (filter);
for (pos = 0; (pos = copy.indexOf("\\/", pos)) != -1; ++pos)
copy.remove(pos, 1);
d->ops->clearFilter();
d->filterWidget->setFilter(copy);
d->ops->setNameFilter(d->filterWidget->currentFilter());
d->ops->updateDir();
d->hasDefaultFilter = false;
d->filterWidget->setEditable( true );
d->updateAutoSelectExtension ();
}
QString KFileWidget::currentFilter() const
{
return d->filterWidget->currentFilter();
}
void KFileWidget::setMimeFilter( const QStringList& mimeTypes,
const QString& defaultType )
{
d->filterWidget->setMimeFilter( mimeTypes, defaultType );
QStringList types = d->filterWidget->currentFilter().split(' ', QString::SkipEmptyParts); //QStringList::split(" ", d->filterWidget->currentFilter());
types.append( QLatin1String( "inode/directory" ));
d->ops->clearFilter();
d->ops->setMimeFilter( types );
d->hasDefaultFilter = !defaultType.isEmpty();
d->filterWidget->setEditable( !d->hasDefaultFilter ||
d->operationMode != Saving );
d->updateAutoSelectExtension ();
}
void KFileWidget::clearFilter()
{
d->filterWidget->setFilter( QString() );
d->ops->clearFilter();
d->hasDefaultFilter = false;
d->filterWidget->setEditable( true );
d->updateAutoSelectExtension ();
}
QString KFileWidget::currentMimeFilter() const
{
int i = d->filterWidget->currentIndex();
if (d->filterWidget->showsAllTypes() && i == 0)
return QString(); // The "all types" item has no mimetype
return d->filterWidget->filters()[i];
}
KMimeType::Ptr KFileWidget::currentFilterMimeType()
{
return KMimeType::mimeType( currentMimeFilter() );
}
void KFileWidget::setPreviewWidget(KPreviewWidgetBase *w) {
d->ops->setPreviewWidget(w);
d->ops->clearHistory();
d->hasView = true;
}
KUrl KFileWidgetPrivate::getCompleteUrl(const QString &_url) const
{
// kDebug(kfile_area) << "got url " << _url;
const QString url = KShell::tildeExpand(_url);
KUrl u;
if (QDir::isAbsolutePath(url)) {
u = url;
} else {
KUrl relativeUrlTest(ops->url());
relativeUrlTest.addPath(url);
if (!ops->dirLister()->findByUrl(relativeUrlTest).isNull() ||
!KProtocolInfo::isKnownProtocol(relativeUrlTest)) {
u = relativeUrlTest;
} else {
u = url;
}
}
return u;
}
// Called by KFileDialog
void KFileWidget::slotOk()
{
// kDebug(kfile_area) << "slotOk\n";
const KFileItemList items = d->ops->selectedItems();
const QString locationEditCurrentText(KShell::tildeExpand(d->locationEditCurrentText()));
KUrl::List locationEditCurrentTextList(d->tokenize(locationEditCurrentText));
KFile::Modes mode = d->ops->mode();
// if there is nothing to do, just return from here
if (!locationEditCurrentTextList.count()) {
return;
}
// Make sure that one of the modes was provided
if (!((mode & KFile::File) || (mode & KFile::Directory) || (mode & KFile::Files))) {
mode |= KFile::File;
kDebug(kfile_area) << "No mode() provided";
}
// if we are on file mode, and the list of provided files/folder is greater than one, inform
// the user about it
if (locationEditCurrentTextList.count() > 1) {
if (mode & KFile::File) {
KMessageBox::sorry(this,
i18n("You can only select one file"),
i18n("More than one file provided"));
return;
}
/**
* Logic of the next part of code (ends at "end multi relative urls").
*
* We allow for instance to be at "/" and insert '"home/foo/bar.txt" "boot/grub/menu.lst"'.
* Why we need to support this ? Because we provide tree views, which aren't plain.
*
* Now, how does this logic work. It will get the first element on the list (with no filename),
* following the previous example say "/home/foo" and set it as the top most url.
*
* After this, it will iterate over the rest of items and check if this URL (topmost url)
* contains the url being iterated.
*
* As you might have guessed it will do "/home/foo" against "/boot/grub" (again stripping
* filename), and a false will be returned. Then we upUrl the top most url, resulting in
* "/home" against "/boot/grub", what will again return false, so we upUrl again. Now we
* have "/" against "/boot/grub", what returns true for us, so we can say that the closest
* common ancestor of both is "/".
*
* This example has been written for 2 urls, but this works for any number of urls.
*/
if (!d->differentHierarchyLevelItemsEntered) { // avoid infinite recursion. running this
KUrl::List urlList; // one time is always enough.
int start = 0;
KUrl topMostUrl;
KIO::StatJob *statJob = 0;
bool res = false;
// we need to check for a valid first url, so in theory we only iterate one time over
// this loop. However it can happen that the user did
// "home/foo/nonexistantfile" "boot/grub/menu.lst", so we look for a good first
// candidate.
while (!res && start < locationEditCurrentTextList.count()) {
topMostUrl = locationEditCurrentTextList.at(start);
statJob = KIO::stat(topMostUrl, KIO::HideProgressInfo);
res = KIO::NetAccess::synchronousRun(statJob, this);
start++;
}
Q_ASSERT(statJob);
// if this is not a dir, strip the filename. after this we have an existent and valid
// dir (if we stated correctly the file, setting a null filename won't make any bad).
if (!statJob->statResult().isDir()) {
topMostUrl.setFileName(QString());
}
// now the funny part. for the rest of filenames, go and look for the closest ancestor
// of all them.
for (int i = start; i < locationEditCurrentTextList.count(); ++i) {
KUrl currUrl = locationEditCurrentTextList.at(i);
KIO::StatJob *statJob = KIO::stat(currUrl, KIO::HideProgressInfo);
bool res = KIO::NetAccess::synchronousRun(statJob, this);
if (res) {
// again, we don't care about filenames
if (!statJob->statResult().isDir()) {
currUrl.setFileName(QString());
}
// iterate while this item is contained on the top most url
while (!topMostUrl.isParentOf(currUrl)) {
topMostUrl = topMostUrl.upUrl();
}
}
}
// now recalculate all paths for them being relative in base of the top most url
for (int i = 0; i < locationEditCurrentTextList.count(); ++i) {
locationEditCurrentTextList[i] = KUrl::relativeUrl(topMostUrl, locationEditCurrentTextList[i]);
}
d->ops->setUrl(topMostUrl, true);
const bool signalsBlocked = d->locationEdit->lineEdit()->blockSignals(true);
QStringList stringList;
foreach (const KUrl &url, locationEditCurrentTextList) {
stringList << url.prettyUrl();
}
d->locationEdit->lineEdit()->setText(QString("\"%1\"").arg(stringList.join("\" \"")));
d->locationEdit->lineEdit()->blockSignals(signalsBlocked);
d->differentHierarchyLevelItemsEntered = true;
slotOk();
return;
}
/**
* end multi relative urls
*/
} else if (locationEditCurrentTextList.count()) {
// if we are on file or files mode, and we have an absolute url written by
// the user, convert it to relative
if (!locationEditCurrentText.isEmpty() && !(mode & KFile::Directory) &&
(QDir::isAbsolutePath(locationEditCurrentText) ||
containsProtocolSection(locationEditCurrentText))) {
QString fileName;
KUrl url(locationEditCurrentText);
if (d->operationMode == Opening) {
KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
bool res = KIO::NetAccess::synchronousRun(statJob, this);
if (res) {
if (!statJob->statResult().isDir()) {
url.adjustPath(KUrl::RemoveTrailingSlash);
fileName = url.fileName();
url.setFileName(QString());
} else {
url.adjustPath(KUrl::AddTrailingSlash);
}
}
} else {
KUrl directory = url;
directory.setFileName(QString());
//Check if the folder exists
KIO::StatJob * statJob = KIO::stat(directory, KIO::HideProgressInfo);
bool res = KIO::NetAccess::synchronousRun(statJob, this);
if (res) {
if (statJob->statResult().isDir()) {
url.adjustPath(KUrl::RemoveTrailingSlash);
fileName = url.fileName();
url.setFileName(QString());
}
}
}
d->ops->setUrl(url, true);
const bool signalsBlocked = d->locationEdit->lineEdit()->blockSignals(true);
d->locationEdit->lineEdit()->setText(fileName);
d->locationEdit->lineEdit()->blockSignals(signalsBlocked);
slotOk();
return;
}
}
// restore it
d->differentHierarchyLevelItemsEntered = false;
// locationEditCurrentTextList contains absolute paths
// this is the general loop for the File and Files mode. Obviously we know
// that the File mode will iterate only one time here
bool directoryMode = (mode & KFile::Directory);
bool onlyDirectoryMode = directoryMode && !(mode & KFile::File) && !(mode & KFile::Files);
KUrl::List::ConstIterator it = locationEditCurrentTextList.constBegin();
bool filesInList = false;
while (it != locationEditCurrentTextList.constEnd()) {
KUrl url(*it);
if (d->operationMode == Saving && !directoryMode) {
d->appendExtension(url);
}
d->url = url;
KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
bool res = KIO::NetAccess::synchronousRun(statJob, this);
if (!KAuthorized::authorizeUrlAction("open", KUrl(), url)) {
QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, d->url.prettyUrl());
KMessageBox::error(this, msg);
return;
}
// if we are on local mode, make sure we haven't got a remote base url
if ((mode & KFile::LocalOnly) && !d->mostLocalUrl(d->url).isLocalFile()) {
KMessageBox::sorry(this,
i18n("You can only select local files"),
i18n("Remote files not accepted"));
return;
}
if ((d->operationMode == Saving) && d->confirmOverwrite && !d->toOverwrite(url)) {
return;
}
// if we are given a folder when not on directory mode, let's get into it
if (res && !directoryMode && statJob->statResult().isDir()) {
// check if we were given more than one folder, in that case we don't know to which one
// cd
++it;
while (it != locationEditCurrentTextList.constEnd()) {
KUrl checkUrl(*it);
KIO::StatJob *checkStatJob = KIO::stat(checkUrl, KIO::HideProgressInfo);
bool res = KIO::NetAccess::synchronousRun(checkStatJob, this);
if (res && checkStatJob->statResult().isDir()) {
KMessageBox::sorry(this, i18n("More than one folder has been selected and this dialog does not accept folders, so it is not possible to decide which one to enter. Please select only one folder to list it."), i18n("More than one folder provided"));
return;
} else if (res) {
filesInList = true;
}
++it;
}
if (filesInList) {
KMessageBox::information(this, i18n("At least one folder and one file has been selected. Selected files will be ignored and the selected folder will be listed"), i18n("Files and folders selected"));
}
d->ops->setUrl(url, true);
const bool signalsBlocked = d->locationEdit->lineEdit()->blockSignals(true);
d->locationEdit->lineEdit()->setText(QString());
d->locationEdit->lineEdit()->blockSignals(signalsBlocked);
return;
} else if (!(mode & KFile::ExistingOnly) || res) {
// if we don't care about ExistingOnly flag, add the file even if
// it doesn't exist. If we care about it, don't add it to the list
if (!onlyDirectoryMode || (res && statJob->statResult().isDir())) {
d->urlList << url;
}
filesInList = true;
} else {
KMessageBox::sorry(this, i18n("The file \"%1\" could not be found", url.pathOrUrl()), i18n("Cannot open file"));
return; // do not emit accepted() if we had ExistingOnly flag and stat failed
}
++it;
}
// if we have reached this point and we didn't return before, that is because
// we want this dialog to be accepted
emit accepted();
}
void KFileWidget::accept()
{
d->inAccept = true; // parseSelectedUrls() checks that
*lastDirectory = d->ops->url();
if (!d->fileClass.isEmpty())
KRecentDirs::add(d->fileClass, d->ops->url().url());
// clear the topmost item, we insert it as full path later on as item 1
d->locationEdit->setItemText( 0, QString() );
const KUrl::List list = selectedUrls();
QList<KUrl>::const_iterator it = list.begin();
int atmost = d->locationEdit->maxItems(); //don't add more items than necessary
for ( ; it != list.end() && atmost > 0; ++it ) {
const KUrl& url = *it;
// we strip the last slash (-1) because KUrlComboBox does that as well
// when operating in file-mode. If we wouldn't , dupe-finding wouldn't
// work.
QString file = url.isLocalFile() ? url.toLocalFile(KUrl::RemoveTrailingSlash) : url.prettyUrl(KUrl::RemoveTrailingSlash);
// remove dupes
for ( int i = 1; i < d->locationEdit->count(); i++ ) {
if ( d->locationEdit->itemText( i ) == file ) {
d->locationEdit->removeItem( i-- );
break;
}
}
//FIXME I don't think this works correctly when the KUrlComboBox has some default urls.
//KUrlComboBox should provide a function to add an url and rotate the existing ones, keeping
//track of maxItems, and we shouldn't be able to insert items as we please.
d->locationEdit->insertItem( 1,file);
atmost--;
}
KSharedConfig::Ptr config = KGlobal::config();
KConfigGroup grp(config,ConfigGroup);
d->writeConfig(grp);
d->saveRecentFiles(grp);
d->addToRecentDocuments();
if (!(mode() & KFile::Files)) { // single selection
emit fileSelected(d->url.url()); // old
emit fileSelected(d->url);
}
d->ops->close();
}
void KFileWidgetPrivate::_k_fileHighlighted(const KFileItem &i)
{
if ((!i.isNull() && i.isDir() ) ||
(locationEdit->hasFocus() && !locationEdit->currentText().isEmpty())) // don't disturb
return;
const bool modified = locationEdit->lineEdit()->isModified();
if (!(ops->mode() & KFile::Files)) {
if (i.isNull()) {
if (!modified) {
setLocationText(KUrl());
}
return;
}
url = i.url();
if (!locationEdit->hasFocus()) { // don't disturb while editing
setLocationText( url );
}
emit q->fileHighlighted(url.url()); // old
emit q->fileHighlighted(url);
} else {
multiSelectionChanged();
emit q->selectionChanged();
}
locationEdit->lineEdit()->setModified( false );
locationEdit->lineEdit()->selectAll();
}
void KFileWidgetPrivate::_k_fileSelected(const KFileItem &i)
{
if (!i.isNull() && i.isDir()) {
return;
}
if (!(ops->mode() & KFile::Files)) {
if (i.isNull()) {
setLocationText(KUrl());
return;
}
setLocationText(i.url());
} else {
multiSelectionChanged();
emit q->selectionChanged();
}
// if we are saving, let another chance to the user before accepting the dialog (or trying to
// accept). This way the user can choose a file and add a "_2" for instance to the filename
if (operationMode == KFileWidget::Saving) {
locationEdit->setFocus();
} else {
q->slotOk();
}
}
// I know it's slow to always iterate thru the whole filelist
// (d->ops->selectedItems()), but what can we do?
void KFileWidgetPrivate::multiSelectionChanged()
{
if (locationEdit->hasFocus() && !locationEdit->currentText().isEmpty()) { // don't disturb
return;
}
const KFileItemList list = ops->selectedItems();
if (list.isEmpty()) {
setLocationText(KUrl());
return;
}
KUrl::List urlList;
foreach (const KFileItem &fileItem, list) {
urlList << fileItem.url();
}
setLocationText(urlList);
}
void KFileWidgetPrivate::setDummyHistoryEntry( const QString& text, const QPixmap& icon,
bool usePreviousPixmapIfNull )
{
// setCurrentItem() will cause textChanged() being emitted,
// so slotLocationChanged() will be called. Make sure we don't clear
// the KDirOperator's view-selection in there
QObject::disconnect( locationEdit, SIGNAL(editTextChanged(QString)),
q, SLOT(_k_slotLocationChanged(QString)) );
bool dummyExists = dummyAdded;
int cursorPosition = locationEdit->lineEdit()->cursorPosition();
if ( dummyAdded ) {
if ( !icon.isNull() ) {
locationEdit->setItemIcon( 0, icon );
locationEdit->setItemText( 0, text );
} else {
if ( !usePreviousPixmapIfNull ) {
locationEdit->setItemIcon( 0, QPixmap() );
}
locationEdit->setItemText( 0, text );
}
} else {
if ( !text.isEmpty() ) {
if ( !icon.isNull() ) {
locationEdit->insertItem( 0, icon, text );
} else {
if ( !usePreviousPixmapIfNull ) {
locationEdit->insertItem( 0, QPixmap(), text );
} else {
locationEdit->insertItem( 0, text );
}
}
dummyAdded = true;
dummyExists = true;
}
}
if ( dummyExists && !text.isEmpty() ) {
locationEdit->setCurrentIndex( 0 );
}
locationEdit->lineEdit()->setCursorPosition( cursorPosition );
QObject::connect( locationEdit, SIGNAL(editTextChanged(QString)),
q, SLOT(_k_slotLocationChanged(QString)) );
}
void KFileWidgetPrivate::removeDummyHistoryEntry()
{
if ( !dummyAdded ) {
return;
}
// setCurrentItem() will cause textChanged() being emitted,
// so slotLocationChanged() will be called. Make sure we don't clear
// the KDirOperator's view-selection in there
QObject::disconnect( locationEdit, SIGNAL(editTextChanged(QString)),
q, SLOT(_k_slotLocationChanged(QString)) );
if (locationEdit->count()) {
locationEdit->removeItem( 0 );
}
locationEdit->setCurrentIndex( -1 );
dummyAdded = false;
QObject::connect( locationEdit, SIGNAL(editTextChanged(QString)),
q, SLOT(_k_slotLocationChanged(QString)) );
}
void KFileWidgetPrivate::setLocationText(const KUrl& url)
{
if (!url.isEmpty()) {
QPixmap mimeTypeIcon = KIconLoader::global()->loadMimeTypeIcon( KMimeType::iconNameForUrl( url ), KIconLoader::Small );
if (url.hasPath()) {
if (!url.directory().isEmpty())
{
KUrl u(url);
u.setPath(u.directory());
q->setUrl(u, false);
}
else {
q->setUrl(url.path(), false);
}
}
setDummyHistoryEntry(url.fileName() , mimeTypeIcon);
} else {
removeDummyHistoryEntry();
}
// don't change selection when user has clicked on an item
if (operationMode == KFileWidget::Saving && !locationEdit->isVisible()) {
setNonExtSelection();
}
}
void KFileWidgetPrivate::setLocationText( const KUrl::List& urlList )
{
const KUrl currUrl = ops->url();
if ( urlList.count() > 1 ) {
QString urls;
foreach (const KUrl &url, urlList) {
urls += QString( "\"%1\"" ).arg( KUrl::relativeUrl(currUrl, url) ) + ' ';
}
urls = urls.left( urls.size() - 1 );
setDummyHistoryEntry( urls, QPixmap(), false );
} else if ( urlList.count() ) {
const QPixmap mimeTypeIcon = KIconLoader::global()->loadMimeTypeIcon( KMimeType::iconNameForUrl( urlList[0] ), KIconLoader::Small );
setDummyHistoryEntry( KUrl::relativeUrl(currUrl, urlList[0]), mimeTypeIcon );
} else {
removeDummyHistoryEntry();
}
// don't change selection when user has clicked on an item
if ( operationMode == KFileWidget::Saving && !locationEdit->isVisible())
setNonExtSelection();
}
void KFileWidgetPrivate::updateLocationWhatsThis()
{
QString whatsThisText;
if (operationMode == KFileWidget::Saving)
{
whatsThisText = "<qt>" + i18n("This is the name to save the file as.") +
i18n (autocompletionWhatsThisText);
}
else if (ops->mode() & KFile::Files)
{
whatsThisText = "<qt>" + i18n("This is the list of files to open. More than "
"one file can be specified by listing several "
"files, separated by spaces.") +
i18n (autocompletionWhatsThisText);
}
else
{
whatsThisText = "<qt>" + i18n("This is the name of the file to open.") +
i18n (autocompletionWhatsThisText);
}
locationLabel->setWhatsThis(whatsThisText);
locationEdit->setWhatsThis(whatsThisText);
}
void KFileWidgetPrivate::initSpeedbar()
{
if (placesDock) {
return;
}
placesDock = new QDockWidget(i18nc("@title:window", "Places"), q);
placesDock->setFeatures(QDockWidget::DockWidgetClosable);
placesView = new KFilePlacesView(placesDock);
placesView->setModel(model);
placesView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
placesView->setObjectName(QLatin1String("url bar"));
QObject::connect(placesView, SIGNAL(urlChanged(KUrl)),
q, SLOT(_k_enterUrl(KUrl)));
// need to set the current url of the urlbar manually (not via urlEntered()
// here, because the initial url of KDirOperator might be the same as the
// one that will be set later (and then urlEntered() won't be emitted).
// TODO: KDE5 ### REMOVE THIS when KDirOperator's initial URL (in the c'tor) is gone.
placesView->setUrl(url);
placesDock->setWidget(placesView);
placesViewSplitter->insertWidget(0, placesDock);
// initialize the size of the splitter
KConfigGroup configGroup(KGlobal::config(), ConfigGroup);
placesViewWidth = configGroup.readEntry(SpeedbarWidth, placesView->sizeHint().width());
QList<int> sizes = placesViewSplitter->sizes();
if (placesViewWidth > 0) {
sizes[0] = placesViewWidth + 1;
sizes[1] = q->width() - placesViewWidth -1;
placesViewSplitter->setSizes(sizes);
}
QObject::connect(placesDock, SIGNAL(visibilityChanged(bool)),
q, SLOT(_k_toggleSpeedbar(bool)));
}
void KFileWidgetPrivate::initGUI()
{
delete boxLayout; // deletes all sub layouts
boxLayout = new QVBoxLayout( q);
boxLayout->setMargin(0); // no additional margin to the already existing
placesViewSplitter = new QSplitter(q);
placesViewSplitter->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
placesViewSplitter->setChildrenCollapsible(false);
boxLayout->addWidget(placesViewSplitter);
QObject::connect(placesViewSplitter, SIGNAL(splitterMoved(int,int)),
q, SLOT(_k_placesViewSplitterMoved(int,int)));
placesViewSplitter->insertWidget(0, opsWidget);
vbox = new QVBoxLayout();
vbox->setMargin(0);
boxLayout->addLayout(vbox);
lafBox = new QGridLayout();
lafBox->addWidget(locationLabel, 0, 0, Qt::AlignVCenter | Qt::AlignRight);
lafBox->addWidget(locationEdit, 0, 1, Qt::AlignVCenter);
lafBox->addWidget(okButton, 0, 2, Qt::AlignVCenter);
lafBox->addWidget(filterLabel, 1, 0, Qt::AlignVCenter | Qt::AlignRight);
lafBox->addWidget(filterWidget, 1, 1, Qt::AlignVCenter);
lafBox->addWidget(cancelButton, 1, 2, Qt::AlignVCenter);
lafBox->setColumnStretch(1, 4);
vbox->addLayout(lafBox);
// add the Automatically Select Extension checkbox
vbox->addWidget(autoSelectExtCheckBox);
q->setTabOrder(ops, autoSelectExtCheckBox);
q->setTabOrder(autoSelectExtCheckBox, locationEdit);
q->setTabOrder(locationEdit, filterWidget);
q->setTabOrder(filterWidget, okButton);
q->setTabOrder(okButton, cancelButton);
q->setTabOrder(cancelButton, urlNavigator);
q->setTabOrder(urlNavigator, ops);
q->setTabOrder(cancelButton, urlNavigator);
q->setTabOrder(urlNavigator, ops);
}
void KFileWidgetPrivate::_k_slotFilterChanged()
{
// kDebug(kfile_area);
filterDelayTimer.stop();
QString filter = filterWidget->currentFilter();
ops->clearFilter();
if ( filter.contains('/') ) {
QStringList types = filter.split(' ', QString::SkipEmptyParts);
types.prepend("inode/directory");
ops->setMimeFilter( types );
}
else if ( filter.contains('*') || filter.contains('?') || filter.contains('[') ) {
ops->setNameFilter( filter );
}
else {
ops->setNameFilter('*' + filter.replace(' ', '*') + '*');
}
ops->updateDir();
updateAutoSelectExtension();
emit q->filterChanged(filter);
}
void KFileWidget::setUrl(const KUrl& url, bool clearforward)
{
// kDebug(kfile_area);
d->ops->setUrl(url, clearforward);
}
// Protected
void KFileWidgetPrivate::_k_urlEntered(const KUrl& url)
{
// kDebug(kfile_area);
QString filename = locationEditCurrentText();
KUrlComboBox* pathCombo = urlNavigator->editor();
if (pathCombo->count() != 0) { // little hack
pathCombo->setUrl(url);
}
bool blocked = locationEdit->blockSignals(true);
if (keepLocation) {
locationEdit->changeUrl(0, KIcon(KMimeType::iconNameForUrl(filename)), filename);
locationEdit->lineEdit()->setModified(true);
}
locationEdit->blockSignals( blocked );
urlNavigator->setLocationUrl(url);
// is trigged in ctor before completion object is set
KUrlCompletion *completion = dynamic_cast<KUrlCompletion*>(locationEdit->completionObject());
if (completion) {
completion->setDir( url.path() );
}
if (placesView) {
placesView->setUrl( url );
}
}
void KFileWidgetPrivate::_k_locationAccepted(const QString &url)
{
Q_UNUSED(url);
// kDebug(kfile_area);
q->slotOk();
}
void KFileWidgetPrivate::_k_enterUrl( const KUrl& url )
{
// kDebug(kfile_area);
KUrl fixedUrl( url );
// append '/' if needed: url combo does not add it
// tokenize() expects it because uses KUrl::setFileName()
fixedUrl.adjustPath( KUrl::AddTrailingSlash );
q->setUrl( fixedUrl );
if (!locationEdit->hasFocus())
ops->setFocus();
}
void KFileWidgetPrivate::_k_enterUrl( const QString& url )
{
// kDebug(kfile_area);
_k_enterUrl( KUrl( KUrlCompletion::replacedPath( url, true, true )) );
}
bool KFileWidgetPrivate::toOverwrite(const KUrl &url)
{
// kDebug(kfile_area);
KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
bool res = KIO::NetAccess::synchronousRun(statJob, q);
if (res) {
int ret = KMessageBox::warningContinueCancel( q,
i18n( "The file \"%1\" already exists. Do you wish to overwrite it?" ,
url.fileName() ), i18n( "Overwrite File?" ), KStandardGuiItem::overwrite(),
KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous);
if (ret != KMessageBox::Continue) {
return false;
}
return true;
}
return true;
}
void KFileWidget::setSelection(const QString& url)
{
// kDebug(kfile_area) << "setSelection " << url;
if (url.isEmpty()) {
return;
}
KUrl u = d->getCompleteUrl(url);
if (!u.isValid()) { // if it still is
kWarning() << url << " is not a correct argument for setSelection!";
return;
}
// Honor protocols that do not support directory listing
if (!u.isRelative() && !KProtocolManager::supportsListing(u))
return;
d->setLocationText(url);
}
void KFileWidgetPrivate::_k_slotLoadingFinished()
{
if (locationEdit->currentText().isEmpty()) {
return;
}
ops->blockSignals(true);
KUrl url = ops->url();
url.adjustPath(KUrl::AddTrailingSlash);
url.setFileName(locationEdit->currentText());
ops->setCurrentItem(url.url());
ops->blockSignals(false);
}
void KFileWidgetPrivate::_k_fileCompletion( const QString& match )
{
// kDebug(kfile_area);
if (match.isEmpty() || locationEdit->currentText().contains('"')) {
return;
}
setDummyHistoryEntry(locationEdit->currentText(), KIconLoader::global()->loadMimeTypeIcon( KMimeType::iconNameForUrl( match ), KIconLoader::Small), !locationEdit->currentText().isEmpty());
}
void KFileWidgetPrivate::_k_slotLocationChanged( const QString& text )
{
// kDebug(kfile_area);
locationEdit->lineEdit()->setModified(true);
if (text.isEmpty() && ops->view()) {
ops->view()->clearSelection();
}
if (text.isEmpty()) {
removeDummyHistoryEntry();
} else {
setDummyHistoryEntry( text );
}
if (!locationEdit->lineEdit()->text().isEmpty()) {
const KUrl::List urlList(tokenize(text));
QStringList stringList;
foreach (const KUrl &url, urlList) {
stringList << url.url();
}
ops->setCurrentItems(stringList);
}
updateFilter();
}
KUrl KFileWidget::selectedUrl() const
{
// kDebug(kfile_area);
if ( d->inAccept )
return d->url;
else
return KUrl();
}
KUrl::List KFileWidget::selectedUrls() const
{
// kDebug(kfile_area);
KUrl::List list;
if ( d->inAccept ) {
if (d->ops->mode() & KFile::Files)
list = d->parseSelectedUrls();
else
list.append( d->url );
}
return list;
}
KUrl::List& KFileWidgetPrivate::parseSelectedUrls()
{
// kDebug(kfile_area);
if ( filenames.isEmpty() ) {
return urlList;
}
urlList.clear();
if ( filenames.contains( '/' )) { // assume _one_ absolute filename
KUrl u;
if ( containsProtocolSection( filenames ) )
u = filenames;
else
u.setPath( filenames );
if ( u.isValid() )
urlList.append( u );
else
KMessageBox::error( q,
i18n("The chosen filenames do not\n"
"appear to be valid."),
i18n("Invalid Filenames") );
}
else
urlList = tokenize( filenames );
filenames.clear(); // indicate that we parsed that one
return urlList;
}
// FIXME: current implementation drawback: a filename can't contain quotes
KUrl::List KFileWidgetPrivate::tokenize( const QString& line ) const
{
// kDebug(kfile_area);
KUrl::List urls;
KUrl u( ops->url() );
u.adjustPath(KUrl::AddTrailingSlash);
QString name;
const int count = line.count( QLatin1Char( '"' ) );
if ( count == 0 ) { // no " " -> assume one single file
if (!QDir::isAbsolutePath(line)) {
u.setFileName( line );
if ( u.isValid() )
urls.append( u );
} else {
urls << KUrl(line);
}
return urls;
}
int start = 0;
int index1 = -1, index2 = -1;
while ( true ) {
index1 = line.indexOf( '"', start );
index2 = line.indexOf( '"', index1 + 1 );
if ( index1 < 0 || index2 < 0 )
break;
// get everything between the " "
name = line.mid( index1 + 1, index2 - index1 - 1 );
// since we use setFileName we need to do this under a temporary url
KUrl _u( u );
KUrl currUrl( name );
if ( !QDir::isAbsolutePath(currUrl.url()) ) {
_u.setFileName( name );
} else {
// we allow to insert various absolute paths like:
// "/home/foo/bar.txt" "/boot/grub/menu.lst"
_u = currUrl;
}
if ( _u.isValid() ) {
urls.append( _u );
}
start = index2 + 1;
}
return urls;
}
QString KFileWidget::selectedFile() const
{
// kDebug(kfile_area);
if ( d->inAccept ) {
const KUrl url = d->mostLocalUrl(d->url);
if (url.isLocalFile())
return url.toLocalFile();
else {
KMessageBox::sorry( const_cast<KFileWidget*>(this),
i18n("You can only select local files."),
i18n("Remote Files Not Accepted") );
}
}
return QString();
}
QStringList KFileWidget::selectedFiles() const
{
// kDebug(kfile_area);
QStringList list;
if (d->inAccept) {
if (d->ops->mode() & KFile::Files) {
const KUrl::List urls = d->parseSelectedUrls();
QList<KUrl>::const_iterator it = urls.begin();
while (it != urls.end()) {
KUrl url = d->mostLocalUrl(*it);
if (url.isLocalFile())
list.append(url.toLocalFile());
++it;
}
}
else { // single-selection mode
if ( d->url.isLocalFile() )
list.append( d->url.toLocalFile() );
}
}
return list;
}
KUrl KFileWidget::baseUrl() const
{
return d->ops->url();
}
void KFileWidget::resizeEvent(QResizeEvent* event)
{
QWidget::resizeEvent(event);
if (d->placesDock) {
// we don't want our places dock actually changing size when we resize
// and qt doesn't make it easy to enforce such a thing with QSplitter
QList<int> sizes = d->placesViewSplitter->sizes();
sizes[0] = d->placesViewWidth + 1; // without this pixel, our places view is reduced 1 pixel each time is shown.
sizes[1] = width() - d->placesViewWidth - 1;
d->placesViewSplitter->setSizes( sizes );
}
}
void KFileWidget::showEvent(QShowEvent* event)
{
if ( !d->hasView ) { // delayed view-creation
Q_ASSERT( d );
Q_ASSERT( d->ops );
d->ops->setView( KFile::Default );
d->ops->view()->setSizePolicy( QSizePolicy( QSizePolicy::Maximum, QSizePolicy::Maximum ) );
d->hasView = true;
}
d->ops->clearHistory();
QWidget::showEvent(event);
}
bool KFileWidget::eventFilter(QObject* watched, QEvent* event)
{
const bool res = QWidget::eventFilter(watched, event);
QKeyEvent *keyEvent = dynamic_cast<QKeyEvent*>(event);
if (watched == d->iconSizeSlider && keyEvent) {
if (keyEvent->key() == Qt::Key_Left || keyEvent->key() == Qt::Key_Up ||
keyEvent->key() == Qt::Key_Right || keyEvent->key() == Qt::Key_Down) {
d->_k_slotIconSizeSliderMoved(d->iconSizeSlider->value());
}
} else if (watched == d->locationEdit && event->type() == QEvent::KeyPress) {
if (keyEvent->modifiers() & Qt::AltModifier) {
switch (keyEvent->key()) {
case Qt::Key_Up:
d->ops->actionCollection()->action("up")->trigger();
break;
case Qt::Key_Left:
d->ops->actionCollection()->action("back")->trigger();
break;
case Qt::Key_Right:
d->ops->actionCollection()->action("forward")->trigger();
break;
default:
break;
}
}
}
return res;
}
void KFileWidget::setMode( KFile::Modes m )
{
// kDebug(kfile_area);
d->ops->setMode(m);
if ( d->ops->dirOnlyMode() ) {
d->filterWidget->setDefaultFilter( i18n("*|All Folders") );
}
else {
d->filterWidget->setDefaultFilter( i18n("*|All Files") );
}
d->updateAutoSelectExtension();
}
KFile::Modes KFileWidget::mode() const
{
return d->ops->mode();
}
void KFileWidgetPrivate::readConfig(KConfigGroup &configGroup)
{
// kDebug(kfile_area);
readRecentFiles(configGroup);
ops->setViewConfig(configGroup);
ops->readConfig(configGroup);
KUrlComboBox *combo = urlNavigator->editor();
combo->setUrls( configGroup.readPathEntry( RecentURLs, QStringList() ), KUrlComboBox::RemoveTop );
combo->setMaxItems( configGroup.readEntry( RecentURLsNumber,
DefaultRecentURLsNumber ) );
combo->setUrl( ops->url() );
autoDirectoryFollowing = configGroup.readEntry(AutoDirectoryFollowing,
DefaultDirectoryFollowing);
KGlobalSettings::Completion cm = (KGlobalSettings::Completion)
configGroup.readEntry( PathComboCompletionMode,
static_cast<int>( KGlobalSettings::completionMode() ) );
if ( cm != KGlobalSettings::completionMode() )
combo->setCompletionMode( cm );
cm = (KGlobalSettings::Completion)
configGroup.readEntry( LocationComboCompletionMode,
static_cast<int>( KGlobalSettings::completionMode() ) );
if ( cm != KGlobalSettings::completionMode() )
locationEdit->setCompletionMode( cm );
// since we delayed this moment, initialize the directory of the completion object to
// our current directory (that was very probably set on the constructor)
KUrlCompletion *completion = dynamic_cast<KUrlCompletion*>(locationEdit->completionObject());
if (completion) {
completion->setDir(ops->url().url());
}
// show or don't show the speedbar
_k_toggleSpeedbar( configGroup.readEntry( ShowSpeedbar, true ) );
// show or don't show the bookmarks
_k_toggleBookmarks( configGroup.readEntry(ShowBookmarks, false) );
// does the user want Automatically Select Extension?
autoSelectExtChecked = configGroup.readEntry (AutoSelectExtChecked, DefaultAutoSelectExtChecked);
updateAutoSelectExtension();
// should the URL navigator use the breadcrumb navigation?
urlNavigator->setUrlEditable( !configGroup.readEntry(BreadcrumbNavigation, true) );
// should the URL navigator show the full path?
urlNavigator->setShowFullPath( configGroup.readEntry(ShowFullPath, false) );
int w1 = q->minimumSize().width();
int w2 = toolbar->sizeHint().width();
if (w1 < w2)
q->setMinimumWidth(w2);
}
void KFileWidgetPrivate::writeConfig(KConfigGroup &configGroup)
{
// kDebug(kfile_area);
// these settings are global settings; ALL instances of the file dialog
// should reflect them
KConfig config("kdeglobals");
KConfigGroup group(&config, configGroup.name());
KUrlComboBox *pathCombo = urlNavigator->editor();
group.writePathEntry( RecentURLs, pathCombo->urls() );
//saveDialogSize( group, KConfigGroup::Persistent | KConfigGroup::Global );
group.writeEntry( PathComboCompletionMode, static_cast<int>(pathCombo->completionMode()) );
group.writeEntry( LocationComboCompletionMode, static_cast<int>(locationEdit->completionMode()) );
const bool showSpeedbar = placesDock && !placesDock->isHidden();
group.writeEntry( ShowSpeedbar, showSpeedbar );
if (showSpeedbar) {
const QList<int> sizes = placesViewSplitter->sizes();
Q_ASSERT( sizes.count() > 0 );
group.writeEntry( SpeedbarWidth, sizes[0] );
}
group.writeEntry( ShowBookmarks, bookmarkHandler != 0 );
group.writeEntry( AutoSelectExtChecked, autoSelectExtChecked );
group.writeEntry( BreadcrumbNavigation, !urlNavigator->isUrlEditable() );
group.writeEntry( ShowFullPath, urlNavigator->showFullPath() );
ops->writeConfig(group);
}
void KFileWidgetPrivate::readRecentFiles(KConfigGroup &cg)
{
// kDebug(kfile_area);
QObject::disconnect(locationEdit, SIGNAL(editTextChanged(QString)),
q, SLOT(_k_slotLocationChanged(QString)));
locationEdit->setMaxItems(cg.readEntry(RecentFilesNumber, DefaultRecentURLsNumber));
locationEdit->setUrls(cg.readPathEntry(RecentFiles, QStringList()),
KUrlComboBox::RemoveBottom);
locationEdit->setCurrentIndex(-1);
QObject::connect(locationEdit, SIGNAL(editTextChanged(QString)),
q, SLOT(_k_slotLocationChanged(QString)));
}
void KFileWidgetPrivate::saveRecentFiles(KConfigGroup &cg)
{
// kDebug(kfile_area);
cg.writePathEntry(RecentFiles, locationEdit->urls());
}
KPushButton * KFileWidget::okButton() const
{
return d->okButton;
}
KPushButton * KFileWidget::cancelButton() const
{
return d->cancelButton;
}
// Called by KFileDialog
void KFileWidget::slotCancel()
{
// kDebug(kfile_area);
d->ops->close();
KConfigGroup grp(KGlobal::config(), ConfigGroup);
d->writeConfig(grp);
}
void KFileWidget::setKeepLocation( bool keep )
{
d->keepLocation = keep;
}
bool KFileWidget::keepsLocation() const
{
return d->keepLocation;
}
void KFileWidget::setOperationMode( OperationMode mode )
{
// kDebug(kfile_area);
d->operationMode = mode;
d->keepLocation = (mode == Saving);
d->filterWidget->setEditable( !d->hasDefaultFilter || mode != Saving );
if ( mode == Opening ) {
// don't use KStandardGuiItem::open() here which has trailing ellipsis!
d->okButton->setGuiItem( KGuiItem( i18n( "&Open" ), "document-open") );
// hide the new folder actions...usability team says they shouldn't be in open file dialog
actionCollection()->removeAction( actionCollection()->action("mkdir" ) );
} else if ( mode == Saving ) {
d->okButton->setGuiItem( KStandardGuiItem::save() );
d->setNonExtSelection();
} else {
d->okButton->setGuiItem( KStandardGuiItem::ok() );
}
d->updateLocationWhatsThis();
d->updateAutoSelectExtension();
if (d->ops) {
d->ops->setIsSaving(mode == Saving);
}
}
KFileWidget::OperationMode KFileWidget::operationMode() const
{
return d->operationMode;
}
void KFileWidgetPrivate::_k_slotAutoSelectExtClicked()
{
// kDebug (kfile_area) << "slotAutoSelectExtClicked(): "
// << autoSelectExtCheckBox->isChecked() << endl;
// whether the _user_ wants it on/off
autoSelectExtChecked = autoSelectExtCheckBox->isChecked();
// update the current filename's extension
updateLocationEditExtension (extension /* extension hasn't changed */);
}
void KFileWidgetPrivate::_k_placesViewSplitterMoved(int pos, int index)
{
// kDebug(kfile_area);
// we need to record the size of the splitter when the splitter changes size
// so we can keep the places box the right size!
if (placesDock && index == 1) {
placesViewWidth = pos;
// kDebug() << "setting lafBox minwidth to" << placesViewWidth;
lafBox->setColumnMinimumWidth(0, placesViewWidth);
}
}
void KFileWidgetPrivate::_k_activateUrlNavigator()
{
// kDebug(kfile_area);
urlNavigator->setUrlEditable(!urlNavigator->isUrlEditable());
if(urlNavigator->isUrlEditable()) {
urlNavigator->setFocus();
urlNavigator->editor()->lineEdit()->selectAll();
}
}
void KFileWidgetPrivate::_k_zoomOutIconsSize()
{
const int currValue = ops->iconsZoom();
const int futValue = qMax(0, currValue - 10);
iconSizeSlider->setValue(futValue);
_k_slotIconSizeSliderMoved(futValue);
}
void KFileWidgetPrivate::_k_zoomInIconsSize()
{
const int currValue = ops->iconsZoom();
const int futValue = qMin(100, currValue + 10);
iconSizeSlider->setValue(futValue);
_k_slotIconSizeSliderMoved(futValue);
}
void KFileWidgetPrivate::_k_slotIconSizeChanged(int _value)
{
int maxSize = KIconLoader::SizeEnormous - KIconLoader::SizeSmall;
int value = (maxSize * _value / 100) + KIconLoader::SizeSmall;
switch (value) {
case KIconLoader::SizeSmall:
case KIconLoader::SizeSmallMedium:
case KIconLoader::SizeMedium:
case KIconLoader::SizeLarge:
case KIconLoader::SizeHuge:
case KIconLoader::SizeEnormous:
iconSizeSlider->setToolTip(i18n("Icon size: %1 pixels (standard size)", value));
break;
default:
iconSizeSlider->setToolTip(i18n("Icon size: %1 pixels", value));
break;
}
}
void KFileWidgetPrivate::_k_slotIconSizeSliderMoved(int _value)
{
// Force this to be called in case this slot is called first on the
// slider move.
_k_slotIconSizeChanged(_value);
QPoint global(iconSizeSlider->rect().topLeft());
global.ry() += iconSizeSlider->height() / 2;
QHelpEvent toolTipEvent(QEvent::ToolTip, QPoint(0, 0), iconSizeSlider->mapToGlobal(global));
QApplication::sendEvent(iconSizeSlider, &toolTipEvent);
}
static QString getExtensionFromPatternList(const QStringList &patternList)
{
// kDebug(kfile_area);
QString ret;
// kDebug (kfile_area) << "\tgetExtension " << patternList;
QStringList::ConstIterator patternListEnd = patternList.end();
for (QStringList::ConstIterator it = patternList.begin();
it != patternListEnd;
++it)
{
// kDebug (kfile_area) << "\t\ttry: \'" << (*it) << "\'";
// is this pattern like "*.BMP" rather than useless things like:
//
// README
// *.
// *.*
// *.JP*G
// *.JP?
if ((*it).startsWith (QLatin1String("*.")) &&
(*it).length() > 2 &&
(*it).indexOf('*', 2) < 0 && (*it).indexOf ('?', 2) < 0)
{
ret = (*it).mid (1);
break;
}
}
return ret;
}
static QString stripUndisplayable (const QString &string)
{
QString ret = string;
ret.remove (':');
ret = KGlobal::locale()->removeAcceleratorMarker (ret);
return ret;
}
//QString KFileWidget::currentFilterExtension()
//{
// return d->extension;
//}
void KFileWidgetPrivate::updateAutoSelectExtension()
{
if (!autoSelectExtCheckBox) return;
//
// Figure out an extension for the Automatically Select Extension thing
// (some Windows users apparently don't know what to do when confronted
// with a text file called "COPYING" but do know what to do with
// COPYING.txt ...)
//
// kDebug (kfile_area) << "Figure out an extension: ";
QString lastExtension = extension;
extension.clear();
// Automatically Select Extension is only valid if the user is _saving_ a _file_
if ((operationMode == KFileWidget::Saving) && (ops->mode() & KFile::File))
{
//
// Get an extension from the filter
//
QString filter = filterWidget->currentFilter();
if (!filter.isEmpty())
{
// if the currently selected filename already has an extension which
// is also included in the currently allowed extensions, keep it
// otherwise use the default extension
QString currentExtension = KMimeType::extractKnownExtension(locationEditCurrentText());
if ( currentExtension.isEmpty() )
currentExtension = locationEditCurrentText().section(QLatin1Char('.'), -1, -1);
kDebug (kfile_area) << "filter:" << filter << "locationEdit:" << locationEditCurrentText()
<< "currentExtension:" << currentExtension;
QString defaultExtension;
QStringList extensionList;
// e.g. "*.cpp"
if (filter.indexOf ('/') < 0)
{
extensionList = filter.split(' ', QString::SkipEmptyParts);
defaultExtension = getExtensionFromPatternList(extensionList);
}
// e.g. "text/html"
else
{
KMimeType::Ptr mime = KMimeType::mimeType (filter);
if (mime)
{
extensionList = mime->patterns();
defaultExtension = mime->mainExtension();
}
}
if ( !currentExtension.isEmpty() && extensionList.contains(QLatin1String("*.") + currentExtension) )
extension = QLatin1Char('.') + currentExtension;
else
extension = defaultExtension;
kDebug (kfile_area) << "List:" << extensionList << "auto-selected extension:" << extension;
}
//
// GUI: checkbox
//
QString whatsThisExtension;
if (!extension.isEmpty())
{
// remember: sync any changes to the string with below
autoSelectExtCheckBox->setText (i18n ("Automatically select filename e&xtension (%1)", extension));
whatsThisExtension = i18n ("the extension <b>%1</b>", extension);
autoSelectExtCheckBox->setEnabled (true);
autoSelectExtCheckBox->setChecked (autoSelectExtChecked);
}
else
{
// remember: sync any changes to the string with above
autoSelectExtCheckBox->setText (i18n ("Automatically select filename e&xtension"));
whatsThisExtension = i18n ("a suitable extension");
autoSelectExtCheckBox->setChecked (false);
autoSelectExtCheckBox->setEnabled (false);
}
const QString locationLabelText = stripUndisplayable (locationLabel->text());
const QString filterLabelText = stripUndisplayable (filterLabel->text());
autoSelectExtCheckBox->setWhatsThis( "<qt>" +
i18n (
"This option enables some convenient features for "
"saving files with extensions:<br />"
"<ol>"
"<li>Any extension specified in the <b>%1</b> text "
"area will be updated if you change the file type "
"to save in.<br />"
"<br /></li>"
"<li>If no extension is specified in the <b>%2</b> "
"text area when you click "
"<b>Save</b>, %3 will be added to the end of the "
"filename (if the filename does not already exist). "
"This extension is based on the file type that you "
"have chosen to save in.<br />"
"<br />"
"If you do not want KDE to supply an extension for the "
"filename, you can either turn this option off or you "
"can suppress it by adding a period (.) to the end of "
"the filename (the period will be automatically "
"removed)."
"</li>"
"</ol>"
"If unsure, keep this option enabled as it makes your "
"files more manageable."
,
locationLabelText,
locationLabelText,
whatsThisExtension)
+ "</qt>"
);
autoSelectExtCheckBox->show();
// update the current filename's extension
updateLocationEditExtension (lastExtension);
}
// Automatically Select Extension not valid
else
{
autoSelectExtCheckBox->setChecked (false);
autoSelectExtCheckBox->hide();
}
}
// Updates the extension of the filename specified in d->locationEdit if the
// Automatically Select Extension feature is enabled.
// (this prevents you from accidently saving "file.kwd" as RTF, for example)
void KFileWidgetPrivate::updateLocationEditExtension (const QString &lastExtension)
{
if (!autoSelectExtCheckBox->isChecked() || extension.isEmpty())
return;
QString urlStr = locationEditCurrentText();
if (urlStr.isEmpty())
return;
KUrl url = getCompleteUrl(urlStr);
// kDebug (kfile_area) << "updateLocationEditExtension (" << url << ")";
const int fileNameOffset = urlStr.lastIndexOf ('/') + 1;
QString fileName = urlStr.mid (fileNameOffset);
const int dot = fileName.lastIndexOf ('.');
const int len = fileName.length();
if (dot > 0 && // has an extension already and it's not a hidden file
// like ".hidden" (but we do accept ".hidden.ext")
dot != len - 1 // and not deliberately suppressing extension
)
{
// exists?
KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
bool result = KIO::NetAccess::synchronousRun(statJob, q);
if (result)
{
// kDebug (kfile_area) << "\tfile exists";
if (statJob->statResult().isDir())
{
// kDebug (kfile_area) << "\tisDir - won't alter extension";
return;
}
// --- fall through ---
}
//
// try to get rid of the current extension
//
// catch "double extensions" like ".tar.gz"
if (lastExtension.length() && fileName.endsWith (lastExtension))
fileName.truncate (len - lastExtension.length());
else if (extension.length() && fileName.endsWith (extension))
fileName.truncate (len - extension.length());
// can only handle "single extensions"
else
fileName.truncate (dot);
// add extension
const QString newText = urlStr.left (fileNameOffset) + fileName + extension;
if ( newText != locationEditCurrentText() )
{
locationEdit->setItemText(locationEdit->currentIndex(),urlStr.left (fileNameOffset) + fileName + extension);
locationEdit->lineEdit()->setModified (true);
}
}
}
// Updates the filter if the extension of the filename specified in d->locationEdit is changed
// (this prevents you from accidently saving "file.kwd" as RTF, for example)
void KFileWidgetPrivate::updateFilter()
{
// kDebug(kfile_area);
if ((operationMode == KFileWidget::Saving) && (ops->mode() & KFile::File) ) {
QString urlStr = locationEditCurrentText();
if (urlStr.isEmpty())
return;
if( filterWidget->isMimeFilter()) {
KMimeType::Ptr mime = KMimeType::findByPath(urlStr, 0, true);
if (mime && mime->name() != KMimeType::defaultMimeType()) {
if (filterWidget->currentFilter() != mime->name() &&
filterWidget->filters().indexOf(mime->name()) != -1)
filterWidget->setCurrentFilter(mime->name());
}
} else {
QString filename = urlStr.mid( urlStr.lastIndexOf( KDIR_SEPARATOR ) + 1 ); // only filename
foreach( const QString& filter, filterWidget->filters()) {
QStringList patterns = filter.left( filter.indexOf( '|' )).split ( ' ', QString::SkipEmptyParts ); // '*.foo *.bar|Foo type' -> '*.foo', '*.bar'
foreach ( const QString& p, patterns ) {
if( KMimeType::matchFileName( filename, p )) {
if ( p != "*" ) { // never match the catch-all filter
filterWidget->setCurrentFilter( filter );
}
break;
}
}
}
}
}
}
// applies only to a file that doesn't already exist
void KFileWidgetPrivate::appendExtension (KUrl &url)
{
// kDebug(kfile_area);
if (!autoSelectExtCheckBox->isChecked() || extension.isEmpty())
return;
QString fileName = url.fileName();
if (fileName.isEmpty())
return;
// kDebug (kfile_area) << "appendExtension(" << url << ")";
const int len = fileName.length();
const int dot = fileName.lastIndexOf ('.');
const bool suppressExtension = (dot == len - 1);
const bool unspecifiedExtension = (dot <= 0);
// don't KIO::Stat if unnecessary
if (!(suppressExtension || unspecifiedExtension))
return;
// exists?
KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
bool res = KIO::NetAccess::synchronousRun(statJob, q);
if (res)
{
// kDebug (kfile_area) << "\tfile exists - won't append extension";
return;
}
// suppress automatically append extension?
if (suppressExtension)
{
//
// Strip trailing dot
// This allows lazy people to have autoSelectExtCheckBox->isChecked
// but don't want a file extension to be appended
// e.g. "README." will make a file called "README"
//
// If you really want a name like "README.", then type "README.."
// and the trailing dot will be removed (or just stop being lazy and
// turn off this feature so that you can type "README.")
//
// kDebug (kfile_area) << "\tstrip trailing dot";
url.setFileName (fileName.left (len - 1));
}
// evilmatically append extension :) if the user hasn't specified one
else if (unspecifiedExtension)
{
// kDebug (kfile_area) << "\tappending extension \'" << extension << "\'...";
url.setFileName (fileName + extension);
// kDebug (kfile_area) << "\tsaving as \'" << url << "\'";
}
}
// adds the selected files/urls to 'recent documents'
void KFileWidgetPrivate::addToRecentDocuments()
{
int m = ops->mode();
int atmost = KRecentDocument::maximumItems();
//don't add more than we need. KRecentDocument::add() is pretty slow
if (m & KFile::LocalOnly) {
const QStringList files = q->selectedFiles();
QStringList::ConstIterator it = files.begin();
for ( ; it != files.end() && atmost > 0; ++it ) {
KRecentDocument::add( *it );
atmost--;
}
}
else { // urls
const KUrl::List urls = q->selectedUrls();
KUrl::List::ConstIterator it = urls.begin();
for ( ; it != urls.end() && atmost > 0; ++it ) {
if ( (*it).isValid() ) {
KRecentDocument::add( *it );
atmost--;
}
}
}
}
KUrlComboBox* KFileWidget::locationEdit() const
{
return d->locationEdit;
}
KFileFilterCombo* KFileWidget::filterWidget() const
{
return d->filterWidget;
}
KActionCollection * KFileWidget::actionCollection() const
{
return d->ops->actionCollection();
}
void KFileWidgetPrivate::_k_toggleSpeedbar(bool show)
{
if (show) {
initSpeedbar();
placesDock->show();
lafBox->setColumnMinimumWidth(0, placesViewWidth);
// check to see if they have a home item defined, if not show the home button
KUrl homeURL;
homeURL.setPath( QDir::homePath() );
KFilePlacesModel *model = static_cast<KFilePlacesModel*>(placesView->model());
for (int rowIndex = 0 ; rowIndex < model->rowCount() ; rowIndex++) {
QModelIndex index = model->index(rowIndex, 0);
KUrl url = model->url(index);
if ( homeURL.equals( url, KUrl::CompareWithoutTrailingSlash ) ) {
toolbar->removeAction( ops->actionCollection()->action( "home" ) );
break;
}
}
} else {
if (q->sender() == placesDock && placesDock && placesDock->isVisibleTo(q)) {
// we didn't *really* go away! the dialog was simply hidden or
// we changed virtual desktops or ...
return;
}
if (placesDock) {
placesDock->hide();
}
QAction* homeAction = ops->actionCollection()->action("home");
QAction* reloadAction = ops->actionCollection()->action("reload");
if (!toolbar->actions().contains(homeAction)) {
toolbar->insertAction(reloadAction, homeAction);
}
// reset the lafbox to not follow the width of the splitter
lafBox->setColumnMinimumWidth(0, 0);
}
static_cast<KToggleAction *>(q->actionCollection()->action("toggleSpeedbar"))->setChecked(show);
}
void KFileWidgetPrivate::_k_toggleBookmarks(bool show)
{
if (show)
{
if (bookmarkHandler)
{
return;
}
bookmarkHandler = new KFileBookmarkHandler( q );
q->connect( bookmarkHandler, SIGNAL(openUrl(QString)),
SLOT(_k_enterUrl(QString)));
bookmarkButton = new KActionMenu(KIcon("bookmarks"),i18n("Bookmarks"), q);
bookmarkButton->setDelayed(false);
q->actionCollection()->addAction("bookmark", bookmarkButton);
bookmarkButton->setMenu(bookmarkHandler->menu());
bookmarkButton->setWhatsThis(i18n("<qt>This button allows you to bookmark specific locations. "
"Click on this button to open the bookmark menu where you may add, "
"edit or select a bookmark.<br /><br />"
"These bookmarks are specific to the file dialog, but otherwise operate "
"like bookmarks elsewhere in KDE.</qt>"));
toolbar->addAction(bookmarkButton);
}
else if (bookmarkHandler)
{
delete bookmarkHandler;
bookmarkHandler = 0;
delete bookmarkButton;
bookmarkButton = 0;
}
static_cast<KToggleAction *>(q->actionCollection()->action("toggleBookmarks"))->setChecked( show );
}
// static, overloaded
KUrl KFileWidget::getStartUrl( const KUrl& startDir,
QString& recentDirClass )
{
QString fileName; // result discarded
return getStartUrl( startDir, recentDirClass, fileName );
}
// static, overloaded
KUrl KFileWidget::getStartUrl( const KUrl& startDir,
QString& recentDirClass,
QString& fileName )
{
recentDirClass.clear();
fileName.clear();
KUrl ret;
bool useDefaultStartDir = startDir.isEmpty();
if ( !useDefaultStartDir )
{
if ( startDir.protocol() == "kfiledialog" )
{
// The startDir URL with this protocol may be in the format:
// directory() fileName()
// 1. kfiledialog:///keyword "/" keyword
// 2. kfiledialog:///keyword?global "/" keyword
// 3. kfiledialog:///keyword/ "/" keyword
// 4. kfiledialog:///keyword/?global "/" keyword
// 5. kfiledialog:///keyword/filename /keyword filename
// 6. kfiledialog:///keyword/filename?global /keyword filename
QString keyword;
QString urlDir = startDir.directory();
QString urlFile = startDir.fileName();
if ( urlDir == "/" ) // '1'..'4' above
{
keyword = urlFile;
fileName.clear();
}
else // '5' or '6' above
{
keyword = urlDir.mid( 1 );
fileName = urlFile;
}
if ( startDir.query() == "?global" )
recentDirClass = QString( "::%1" ).arg( keyword );
else
recentDirClass = QString( ":%1" ).arg( keyword );
ret = KUrl( KRecentDirs::dir(recentDirClass) );
}
else // not special "kfiledialog" URL
{
if (!startDir.directory().isEmpty()) // has directory, maybe with filename
{
ret = startDir; // will be checked by stat later
// If we won't be able to list it (e.g. http), then use default
- if ( !KProtocolManager::supportsListing( ret ) )
+ if ( !KProtocolManager::supportsListing( ret ) ) {
useDefaultStartDir = true;
+ fileName = startDir.fileName();
+ }
}
else // file name only
{
fileName = startDir.fileName();
useDefaultStartDir = true;
}
}
}
if ( useDefaultStartDir )
{
if (lastDirectory->isEmpty()) {
lastDirectory->setPath(KGlobalSettings::documentPath());
KUrl home;
home.setPath( QDir::homePath() );
// if there is no docpath set (== home dir), we prefer the current
// directory over it. We also prefer the homedir when our CWD is
// different from our homedirectory or when the document dir
// does not exist
if ( lastDirectory->path(KUrl::AddTrailingSlash) == home.path(KUrl::AddTrailingSlash) ||
QDir::currentPath() != QDir::homePath() ||
!QDir(lastDirectory->path(KUrl::AddTrailingSlash)).exists() )
lastDirectory->setPath(QDir::currentPath());
}
ret = *lastDirectory;
}
kDebug(kfile_area) << "for" << startDir << "->" << ret << "recentDirClass" << recentDirClass << "fileName" << fileName;
return ret;
}
void KFileWidget::setStartDir( const KUrl& directory )
{
if ( directory.isValid() )
*lastDirectory = directory;
}
void KFileWidgetPrivate::setNonExtSelection()
{
// Enhanced rename: Don't highlight the file extension.
QString filename = locationEditCurrentText();
QString extension = KMimeType::extractKnownExtension( filename );
if ( !extension.isEmpty() )
locationEdit->lineEdit()->setSelection( 0, filename.length() - extension.length() - 1 );
else
{
int lastDot = filename.lastIndexOf( '.' );
if ( lastDot > 0 )
locationEdit->lineEdit()->setSelection( 0, lastDot );
}
}
KToolBar * KFileWidget::toolBar() const
{
return d->toolbar;
}
void KFileWidget::setCustomWidget(QWidget* widget)
{
delete d->bottomCustomWidget;
d->bottomCustomWidget = widget;
// add it to the dialog, below the filter list box.
// Change the parent so that this widget is a child of the main widget
d->bottomCustomWidget->setParent( this );
d->vbox->addWidget( d->bottomCustomWidget );
//d->vbox->addSpacing(3); // can't do this every time...
// FIXME: This should adjust the tab orders so that the custom widget
// comes after the Cancel button. The code appears to do this, but the result
// somehow screws up the tab order of the file path combo box. Not a major
// problem, but ideally the tab order with a custom widget should be
// the same as the order without one.
setTabOrder(d->cancelButton, d->bottomCustomWidget);
setTabOrder(d->bottomCustomWidget, d->urlNavigator);
}
void KFileWidget::setCustomWidget(const QString& text, QWidget* widget)
{
delete d->labeledCustomWidget;
d->labeledCustomWidget = widget;
QLabel* label = new QLabel(text, this);
label->setAlignment(Qt::AlignRight);
d->lafBox->addWidget(label, 2, 0, Qt::AlignVCenter);
d->lafBox->addWidget(widget, 2, 1, Qt::AlignVCenter);
}
void KFileWidget::virtual_hook( int id, void* data )
{
// this is a workaround to avoid binary compatibility breakage
// since setConfirmOverwrite in kabstractfilewidget.h is a new function
// introduced for 4.2. As stated in kabstractfilewidget.h this workaround
// is going to become a virtual function for KDE5
switch (id) {
case 0: { // setConfirmOverwrite(bool)
bool *enable = static_cast<bool*>(data);
d->confirmOverwrite = *enable;
}
break;
case 1: { // setInlinePreviewShown(bool)
bool *show = static_cast<bool*>(data);
d->setInlinePreviewShown(*show);
}
break;
default:
break;
}
}
KDirOperator* KFileWidget::dirOperator()
{
return d->ops;
}
void KFileWidget::readConfig( KConfigGroup& group )
{
d->readConfig(group);
}
QString KFileWidgetPrivate::locationEditCurrentText() const
{
- return QDir::fromNativeSeparators(locationEdit->currentText().trimmed());
+ return QDir::fromNativeSeparators(locationEdit->currentText());
}
KUrl KFileWidgetPrivate::mostLocalUrl(const KUrl &url)
{
if (url.isLocalFile()) {
return url;
}
KIO::StatJob *statJob = KIO::stat(url, KIO::HideProgressInfo);
bool res = KIO::NetAccess::synchronousRun(statJob, q);
if (!res) {
return url;
}
const QString path = statJob->statResult().stringValue(KIO::UDSEntry::UDS_LOCAL_PATH);
if (!path.isEmpty()) {
KUrl newUrl;
newUrl.setPath(path);
return newUrl;
}
return url;
}
void KFileWidgetPrivate::setInlinePreviewShown(bool show)
{
ops->setInlinePreviewShown(show);
}
#include "kfilewidget.moc"
diff --git a/khtml/khtml_ext.cpp b/khtml/khtml_ext.cpp
index 6e8a846158..3a1da4a5d9 100644
--- a/khtml/khtml_ext.cpp
+++ b/khtml/khtml_ext.cpp
@@ -1,1214 +1,1284 @@
/* This file is part of the KDE project
*
* Copyright (C) 2000-2003 Simon Hausmann <hausmann@kde.org>
* 2001-2003 George Staikos <staikos@kde.org>
* 2001-2003 Laurent Montel <montel@kde.org>
* 2001-2003 Dirk Mueller <mueller@kde.org>
* 2001-2003 Waldo Bastian <bastian@kde.org>
* 2001-2003 David Faure <faure@kde.org>
* 2001-2003 Daniel Naber <dnaber@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "khtml_ext.h"
#include "khtmlview.h"
#include "khtml_pagecache.h"
#include "rendering/render_form.h"
#include "rendering/render_image.h"
#include "html/html_imageimpl.h"
#include "misc/loader.h"
#include "dom/html_form.h"
#include "dom/html_image.h"
#include "dom/dom_string.h"
#include "dom/html_document.h"
#include "dom/dom_element.h"
#include "xml/dom_elementimpl.h"
#include <QtGui/QClipboard>
#include <QtCore/QFileInfo>
#include <QtGui/QMenu>
#include <QtCore/QUrl>
#include <QtCore/QMetaEnum>
#include <assert.h>
#include <kdebug.h>
#include <klocale.h>
#include <kfiledialog.h>
#include <kjobuidelegate.h>
#include <kio/job.h>
#include <kshell.h>
#include <ktoolbar.h>
#include <ksavefile.h>
#include <kstringhandler.h>
#include <ktoolinvocation.h>
#include <kmessagebox.h>
#include <kstandarddirs.h>
#include <krun.h>
#include <kurifilter.h>
#include <kicon.h>
#include <kiconloader.h>
#include <kdesktopfile.h>
#include <kinputdialog.h>
#include <ktemporaryfile.h>
#include "khtml_global.h"
#include <kstandardaction.h>
#include <kactioncollection.h>
#include <kactionmenu.h>
#include "khtmlpart_p.h"
KHTMLPartBrowserExtension::KHTMLPartBrowserExtension( KHTMLPart *parent )
: KParts::BrowserExtension( parent )
{
m_part = parent;
setURLDropHandlingEnabled( true );
enableAction( "cut", false );
enableAction( "copy", false );
enableAction( "paste", false );
m_connectedToClipboard = false;
}
int KHTMLPartBrowserExtension::xOffset()
{
return m_part->view()->contentsX();
}
int KHTMLPartBrowserExtension::yOffset()
{
return m_part->view()->contentsY();
}
void KHTMLPartBrowserExtension::saveState( QDataStream &stream )
{
//kDebug( 6050 ) << "saveState!";
m_part->saveState( stream );
}
void KHTMLPartBrowserExtension::restoreState( QDataStream &stream )
{
//kDebug( 6050 ) << "restoreState!";
m_part->restoreState( stream );
}
void KHTMLPartBrowserExtension::editableWidgetFocused( QWidget *widget )
{
m_editableFormWidget = widget;
updateEditActions();
if ( !m_connectedToClipboard && m_editableFormWidget )
{
connect( QApplication::clipboard(), SIGNAL(dataChanged()),
this, SLOT(updateEditActions()) );
if ( m_editableFormWidget->inherits( "QLineEdit" ) || m_editableFormWidget->inherits( "QTextEdit" ) )
connect( m_editableFormWidget, SIGNAL(selectionChanged()),
this, SLOT(updateEditActions()) );
m_connectedToClipboard = true;
}
editableWidgetFocused();
}
void KHTMLPartBrowserExtension::editableWidgetBlurred( QWidget * /*widget*/ )
{
QWidget *oldWidget = m_editableFormWidget;
m_editableFormWidget = 0;
enableAction( "cut", false );
enableAction( "paste", false );
m_part->emitSelectionChanged();
if ( m_connectedToClipboard )
{
disconnect( QApplication::clipboard(), SIGNAL(dataChanged()),
this, SLOT(updateEditActions()) );
if ( oldWidget )
{
if ( oldWidget->inherits( "QLineEdit" ) || oldWidget->inherits( "QTextEdit" ) )
disconnect( oldWidget, SIGNAL(selectionChanged()),
this, SLOT(updateEditActions()) );
}
m_connectedToClipboard = false;
}
editableWidgetBlurred();
}
void KHTMLPartBrowserExtension::setExtensionProxy( KParts::BrowserExtension *proxy )
{
if ( m_extensionProxy )
{
disconnect( m_extensionProxy, SIGNAL(enableAction(const char*,bool)),
this, SLOT(extensionProxyActionEnabled(const char*,bool)) );
if ( m_extensionProxy->inherits( "KHTMLPartBrowserExtension" ) )
{
disconnect( m_extensionProxy, SIGNAL(editableWidgetFocused()),
this, SLOT(extensionProxyEditableWidgetFocused()) );
disconnect( m_extensionProxy, SIGNAL(editableWidgetBlurred()),
this, SLOT(extensionProxyEditableWidgetBlurred()) );
}
}
m_extensionProxy = proxy;
if ( m_extensionProxy )
{
connect( m_extensionProxy, SIGNAL(enableAction(const char*,bool)),
this, SLOT(extensionProxyActionEnabled(const char*,bool)) );
if ( m_extensionProxy->inherits( "KHTMLPartBrowserExtension" ) )
{
connect( m_extensionProxy, SIGNAL(editableWidgetFocused()),
this, SLOT(extensionProxyEditableWidgetFocused()) );
connect( m_extensionProxy, SIGNAL(editableWidgetBlurred()),
this, SLOT(extensionProxyEditableWidgetBlurred()) );
}
enableAction( "cut", m_extensionProxy->isActionEnabled( "cut" ) );
enableAction( "copy", m_extensionProxy->isActionEnabled( "copy" ) );
enableAction( "paste", m_extensionProxy->isActionEnabled( "paste" ) );
}
else
{
updateEditActions();
enableAction( "copy", false ); // ### re-check this
}
}
void KHTMLPartBrowserExtension::cut()
{
if ( m_extensionProxy )
{
callExtensionProxyMethod( "cut" );
return;
}
if ( !m_editableFormWidget )
return;
QLineEdit* lineEdit = qobject_cast<QLineEdit *>( m_editableFormWidget );
if ( lineEdit && !lineEdit->isReadOnly() )
lineEdit->cut();
QTextEdit* textEdit = qobject_cast<QTextEdit *>( m_editableFormWidget );
if ( textEdit && !textEdit->isReadOnly() )
textEdit->cut();
}
void KHTMLPartBrowserExtension::copy()
{
if ( m_extensionProxy )
{
callExtensionProxyMethod( "copy" );
return;
}
if ( !m_editableFormWidget )
{
// get selected text and paste to the clipboard
QString text = m_part->selectedText();
text.replace( QChar( 0xa0 ), ' ' );
//kDebug(6050) << text;
QClipboard *cb = QApplication::clipboard();
disconnect( cb, SIGNAL(selectionChanged()), m_part, SLOT(slotClearSelection()) );
#ifndef QT_NO_MIMECLIPBOARD
QString htmltext;
/*
* When selectionModeEnabled, that means the user has just selected
* the text, not ctrl+c to copy it. The selection clipboard
* doesn't seem to support mime type, so to save time, don't calculate
* the selected text as html.
* optomisation disabled for now until everything else works.
*/
//if(!cb->selectionModeEnabled())
htmltext = m_part->selectedTextAsHTML();
QMimeData *mimeData = new QMimeData;
mimeData->setText(text);
if(!htmltext.isEmpty()) {
htmltext.replace( QChar( 0xa0 ), ' ' );
mimeData->setHtml(htmltext);
}
cb->setMimeData(mimeData);
#else
cb->setText(text);
#endif
connect( cb, SIGNAL(selectionChanged()), m_part, SLOT(slotClearSelection()) );
}
else
{
QLineEdit* lineEdit = qobject_cast<QLineEdit *>( m_editableFormWidget );
if ( lineEdit )
lineEdit->copy();
QTextEdit* textEdit = qobject_cast<QTextEdit *>( m_editableFormWidget );
if ( textEdit )
textEdit->copy();
}
}
void KHTMLPartBrowserExtension::searchProvider()
{
KAction *action = qobject_cast<KAction*>(sender());
if (action) {
KUrl url = action->data().toUrl();
if (url.host().isEmpty()) {
KUriFilterData data(action->data().toString());
if (KUriFilter::self()->filterSearchUri(data, KUriFilter::WebShortcutFilter))
url = data.uri();
}
KParts::BrowserArguments browserArgs;
browserArgs.frameName = "_blank";
emit m_part->browserExtension()->openUrlRequest( url, KParts::OpenUrlArguments(), browserArgs );
}
}
void KHTMLPartBrowserExtension::paste()
{
if ( m_extensionProxy )
{
callExtensionProxyMethod( "paste" );
return;
}
if ( !m_editableFormWidget )
return;
QLineEdit* lineEdit = qobject_cast<QLineEdit *>( m_editableFormWidget );
if ( lineEdit && !lineEdit->isReadOnly() )
lineEdit->paste();
QTextEdit* textEdit = qobject_cast<QTextEdit *>( m_editableFormWidget );
if ( textEdit && !textEdit->isReadOnly() )
textEdit->paste();
}
void KHTMLPartBrowserExtension::callExtensionProxyMethod( const char *method )
{
if ( !m_extensionProxy )
return;
QMetaObject::invokeMethod(m_extensionProxy, method, Qt::DirectConnection);
}
void KHTMLPartBrowserExtension::updateEditActions()
{
if ( !m_editableFormWidget )
{
enableAction( "cut", false );
enableAction( "copy", false );
enableAction( "paste", false );
return;
}
// ### duplicated from KonqMainWindow::slotClipboardDataChanged
#ifndef QT_NO_MIMECLIPBOARD // Handle minimalized versions of Qt Embedded
const QMimeData *data = QApplication::clipboard()->mimeData();
enableAction( "paste", data->hasFormat( "text/plain" ) );
#else
QString data=QApplication::clipboard()->text();
enableAction( "paste", data.contains("://"));
#endif
bool hasSelection = false;
if( m_editableFormWidget) {
if ( qobject_cast<QLineEdit*>(m_editableFormWidget))
hasSelection = static_cast<QLineEdit *>( &(*m_editableFormWidget) )->hasSelectedText();
else if(qobject_cast<QTextEdit*>(m_editableFormWidget))
hasSelection = static_cast<QTextEdit *>( &(*m_editableFormWidget) )->textCursor().hasSelection();
}
enableAction( "copy", hasSelection );
enableAction( "cut", hasSelection );
}
void KHTMLPartBrowserExtension::extensionProxyEditableWidgetFocused() {
editableWidgetFocused();
}
void KHTMLPartBrowserExtension::extensionProxyEditableWidgetBlurred() {
editableWidgetBlurred();
}
void KHTMLPartBrowserExtension::extensionProxyActionEnabled( const char *action, bool enable )
{
// only forward enableAction calls for actions we actually do forward
if ( strcmp( action, "cut" ) == 0 ||
strcmp( action, "copy" ) == 0 ||
strcmp( action, "paste" ) == 0 ) {
enableAction( action, enable );
}
}
void KHTMLPartBrowserExtension::reparseConfiguration()
{
m_part->reparseConfiguration();
}
void KHTMLPartBrowserExtension::print()
{
m_part->view()->print();
}
void KHTMLPartBrowserExtension::disableScrolling()
{
QScrollArea *scrollArea = m_part->view();
if (scrollArea) {
scrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
}
}
class KHTMLPopupGUIClient::KHTMLPopupGUIClientPrivate
{
public:
KHTMLPart *m_khtml;
KUrl m_url;
KUrl m_imageURL;
QPixmap m_pixmap;
QString m_suggestedFilename;
KActionCollection* m_actionCollection;
KParts::BrowserExtension::ActionGroupMap actionGroups;
};
KHTMLPopupGUIClient::KHTMLPopupGUIClient( KHTMLPart *khtml, const KUrl &url )
: QObject( khtml ), d(new KHTMLPopupGUIClientPrivate)
{
d->m_khtml = khtml;
d->m_url = url;
d->m_actionCollection = new KActionCollection(this);
bool isImage = false;
bool hasSelection = khtml->hasSelection();
DOM::Element e = khtml->nodeUnderMouse();
if ( !e.isNull() && (e.elementId() == ID_IMG ||
(e.elementId() == ID_INPUT && !static_cast<DOM::HTMLInputElement>(e).src().isEmpty())))
{
if (e.elementId() == ID_IMG) {
DOM::HTMLImageElementImpl *ie = static_cast<DOM::HTMLImageElementImpl*>(e.handle());
khtml::RenderImage *ri = dynamic_cast<khtml::RenderImage*>(ie->renderer());
if (ri && ri->contentObject()) {
d->m_suggestedFilename = static_cast<khtml::CachedImage*>(ri->contentObject())->suggestedFilename();
}
}
isImage=true;
}
if (hasSelection) {
QList<QAction *> editActions;
QAction* copyAction = d->m_actionCollection->addAction( KStandardAction::Copy, "copy",
d->m_khtml->browserExtension(), SLOT(copy()) );
copyAction->setText(i18n("&Copy Text"));
copyAction->setEnabled(d->m_khtml->browserExtension()->isActionEnabled( "copy" ));
editActions.append(copyAction);
editActions.append(khtml->actionCollection()->action("selectAll"));
addSearchActions(editActions);
QString selectedTextURL = selectedTextAsOneLine(d->m_khtml);
if ( selectedTextURL.contains("://") && KUrl(selectedTextURL).isValid() ) {
if (selectedTextURL.length() > 18) {
selectedTextURL.truncate(15);
selectedTextURL += "...";
}
KAction *action = new KAction(i18n("Open '%1'", selectedTextURL), this);
d->m_actionCollection->addAction( "openSelection", action );
action->setIcon( KIcon( "window-new" ) );
connect( action, SIGNAL(triggered(bool)), this, SLOT(openSelection()) );
editActions.append(action);
}
KAction* separator = new KAction(d->m_actionCollection);
separator->setSeparator(true);
editActions.append(separator);
d->actionGroups.insert("editactions", editActions);
}
if (!url.isEmpty()) {
QList<QAction *> linkActions;
if (url.protocol() == "mailto") {
KAction *action = new KAction( i18n( "&Copy Email Address" ), this );
d->m_actionCollection->addAction( "copylinklocation", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotCopyLinkLocation()) );
linkActions.append(action);
} else {
KAction *action = new KAction( i18n( "&Save Link As..." ), this );
d->m_actionCollection->addAction( "savelinkas", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotSaveLinkAs()) );
linkActions.append(action);
action = new KAction( i18n( "&Copy Link Address" ), this );
d->m_actionCollection->addAction( "copylinklocation", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotCopyLinkLocation()) );
linkActions.append(action);
}
d->actionGroups.insert("linkactions", linkActions);
}
QList<QAction *> partActions;
// frameset? -> add "Reload Frame" etc.
if (!hasSelection) {
if ( khtml->parentPart() ) {
KActionMenu* menu = new KActionMenu( i18nc("@title:menu HTML frame/iframe", "Frame"), this);
KAction *action = new KAction( i18n( "Open in New &Window" ), this );
d->m_actionCollection->addAction( "frameinwindow", action );
action->setIcon( KIcon( "window-new" ) );
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotFrameInWindow()) );
menu->addAction(action);
action = new KAction( i18n( "Open in &This Window" ), this );
d->m_actionCollection->addAction( "frameintop", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotFrameInTop()) );
menu->addAction(action);
action = new KAction( i18n( "Open in &New Tab" ), this );
d->m_actionCollection->addAction( "frameintab", action );
action->setIcon( KIcon( "tab-new" ) );
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotFrameInTab()) );
menu->addAction(action);
action = new KAction(d->m_actionCollection);
action->setSeparator(true);
menu->addAction(action);
action = new KAction( i18n( "Reload Frame" ), this );
d->m_actionCollection->addAction( "reloadframe", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotReloadFrame()) );
menu->addAction(action);
action = new KAction( i18n( "Print Frame..." ), this );
d->m_actionCollection->addAction( "printFrame", action );
action->setIcon( KIcon( "document-print-frame" ) );
connect( action, SIGNAL(triggered(bool)), d->m_khtml->browserExtension(), SLOT(print()) );
menu->addAction(action);
action = new KAction( i18n( "Save &Frame As..." ), this );
d->m_actionCollection->addAction( "saveFrame", action );
connect( action, SIGNAL(triggered(bool)), d->m_khtml, SLOT(slotSaveFrame()) );
menu->addAction(action);
action = new KAction( i18n( "View Frame Source" ), this );
d->m_actionCollection->addAction( "viewFrameSource", action );
connect( action, SIGNAL(triggered(bool)), d->m_khtml, SLOT(slotViewDocumentSource()) );
menu->addAction(action);
action = new KAction( i18n( "View Frame Information" ), this );
d->m_actionCollection->addAction( "viewFrameInfo", action );
connect( action, SIGNAL(triggered(bool)), d->m_khtml, SLOT(slotViewPageInfo()) );
action = new KAction(d->m_actionCollection);
action->setSeparator(true);
menu->addAction(action);
if ( KHTMLGlobal::defaultHTMLSettings()->isAdFilterEnabled() ) {
if ( khtml->d->m_frame->m_type == khtml::ChildFrame::IFrame ) {
action = new KAction( i18n( "Block IFrame..." ), this );
d->m_actionCollection->addAction( "blockiframe", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotBlockIFrame()) );
menu->addAction(action);
}
}
partActions.append(menu);
}
}
if (isImage) {
if ( e.elementId() == ID_IMG ) {
d->m_imageURL = KUrl( static_cast<DOM::HTMLImageElement>( e ).src().string() );
DOM::HTMLImageElementImpl *imageimpl = static_cast<DOM::HTMLImageElementImpl *>( e.handle() );
Q_ASSERT(imageimpl);
if(imageimpl) // should be true always. right?
{
if(imageimpl->complete()) {
d->m_pixmap = imageimpl->currentPixmap();
}
}
}
else
d->m_imageURL = KUrl( static_cast<DOM::HTMLInputElement>( e ).src().string() );
KAction *action = new KAction( i18n( "Save Image As..." ), this );
d->m_actionCollection->addAction( "saveimageas", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotSaveImageAs()) );
partActions.append(action);
action = new KAction( i18n( "Send Image..." ), this );
d->m_actionCollection->addAction( "sendimage", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotSendImage()) );
partActions.append(action);
#ifndef QT_NO_MIMECLIPBOARD
action = new KAction( i18n( "Copy Image" ), this );
d->m_actionCollection->addAction( "copyimage", action );
action->setEnabled(!d->m_pixmap.isNull());
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotCopyImage()) );
partActions.append(action);
#endif
if(d->m_pixmap.isNull()) { //fallback to image location if still loading the image. this will always be true if ifdef QT_NO_MIMECLIPBOARD
action = new KAction( i18n( "Copy Image Location" ), this );
d->m_actionCollection->addAction( "copyimagelocation", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotCopyImageLocation()) );
partActions.append(action);
}
QString actionText = d->m_suggestedFilename.isEmpty() ?
KStringHandler::csqueeze(d->m_imageURL.fileName()+d->m_imageURL.query(), 25)
: d->m_suggestedFilename;
action = new KAction( i18n("View Image (%1)", actionText.replace("&", "&&")), this );
d->m_actionCollection->addAction( "viewimage", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotViewImage()) );
partActions.append(action);
if (KHTMLGlobal::defaultHTMLSettings()->isAdFilterEnabled()) {
action = new KAction( i18n( "Block Image..." ), this );
d->m_actionCollection->addAction( "blockimage", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotBlockImage()) );
partActions.append(action);
if (!d->m_imageURL.host().isEmpty() &&
!d->m_imageURL.protocol().isEmpty())
{
action = new KAction( i18n( "Block Images From %1" , d->m_imageURL.host()), this );
d->m_actionCollection->addAction( "blockhost", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotBlockHost()) );
partActions.append(action);
}
}
KAction* separator = new KAction(d->m_actionCollection);
separator->setSeparator(true);
partActions.append(separator);
}
if ( isImage || url.isEmpty() ) {
KAction *action = new KAction( i18n( "Stop Animations" ), this );
d->m_actionCollection->addAction( "stopanimations", action );
connect( action, SIGNAL(triggered(bool)), this, SLOT(slotStopAnimations()) );
partActions.append(action);
KAction* separator = new KAction(d->m_actionCollection);
separator->setSeparator(true);
partActions.append(separator);
}
if (!hasSelection && url.isEmpty()) { // only when right-clicking on the page itself
partActions.append(khtml->actionCollection()->action("viewDocumentSource"));
}
if (!hasSelection && url.isEmpty() && !isImage) {
partActions.append(khtml->actionCollection()->action("setEncoding"));
}
d->actionGroups.insert("partactions", partActions);
}
KHTMLPopupGUIClient::~KHTMLPopupGUIClient()
{
delete d->m_actionCollection;
delete d;
}
void KHTMLPopupGUIClient::addSearchActions(QList<QAction *>& editActions)
{
const QString selectedText = d->m_khtml->simplifiedSelectedText();
if (selectedText.isEmpty())
return;
KUriFilterData data(selectedText);
QStringList alternateProviders;
alternateProviders << "google" << "google_groups" << "google_news" << "webster" << "dmoz" << "wikipedia";
data.setAlternateSearchProviders(alternateProviders);
data.setAlternateDefaultSearchProvider("google");
if (KUriFilter::self()->filterSearchUri(data, KUriFilter::NormalTextFilter)) {
const QString squeezedText = KStringHandler::rsqueeze(selectedText, 21);
KAction *action = new KAction(i18n("Search for '%1' with %2",
squeezedText, data.searchProvider()), this);
action->setData(QUrl(data.uri()));
action->setIcon(KIcon(data.iconName()));
connect(action, SIGNAL(triggered(bool)), d->m_khtml->browserExtension(), SLOT(searchProvider()));
d->m_actionCollection->addAction("defaultSearchProvider", action);
editActions.append(action);
const QStringList preferredSearchProviders = data.preferredSearchProviders();
if (!preferredSearchProviders.isEmpty()) {
KActionMenu* providerList = new KActionMenu(i18n("Search for '%1' with", squeezedText), this);
Q_FOREACH(const QString &searchProvider, preferredSearchProviders) {
if (searchProvider == data.searchProvider())
continue;
KAction *action = new KAction(searchProvider, this);
action->setData(data.queryForPreferredSearchProvider(searchProvider));
d->m_actionCollection->addAction(searchProvider, action);
action->setIcon(KIcon(data.iconNameForPreferredSearchProvider(searchProvider)));
connect(action, SIGNAL(triggered(bool)), d->m_khtml->browserExtension(), SLOT(searchProvider()));
providerList->addAction(action);
}
d->m_actionCollection->addAction("searchProviderList", providerList);
editActions.append(providerList);
}
}
}
QString KHTMLPopupGUIClient::selectedTextAsOneLine(KHTMLPart* part)
{
QString text = part->simplifiedSelectedText();
// in addition to what simplifiedSelectedText does,
// remove linefeeds and any whitespace surrounding it (#113177),
// to get it all in a single line.
text.remove(QRegExp("[\\s]*\\n+[\\s]*"));
return text;
}
void KHTMLPopupGUIClient::openSelection()
{
KParts::BrowserArguments browserArgs;
browserArgs.frameName = "_blank";
emit d->m_khtml->browserExtension()->openUrlRequest(selectedTextAsOneLine(d->m_khtml), KParts::OpenUrlArguments(), browserArgs);
}
KParts::BrowserExtension::ActionGroupMap KHTMLPopupGUIClient::actionGroups() const
{
return d->actionGroups;
}
void KHTMLPopupGUIClient::slotSaveLinkAs()
{
KIO::MetaData metaData;
metaData["referrer"] = d->m_khtml->referrer();
saveURL( d->m_khtml->widget(), i18n( "Save Link As" ), d->m_url, metaData );
}
void KHTMLPopupGUIClient::slotSendImage()
{
QStringList urls;
urls.append( d->m_imageURL.url());
QString subject = d->m_imageURL.url();
KToolInvocation::invokeMailer(QString(), QString(), QString(), subject,
QString(), //body
QString(),
urls); // attachments
}
void KHTMLPopupGUIClient::slotSaveImageAs()
{
KIO::MetaData metaData;
metaData["referrer"] = d->m_khtml->referrer();
saveURL( d->m_khtml->widget(), i18n( "Save Image As" ), d->m_imageURL, metaData, QString(), 0, d->m_suggestedFilename );
}
void KHTMLPopupGUIClient::slotBlockHost()
{
QString name=d->m_imageURL.protocol()+"://"+d->m_imageURL.host()+"/*";
KHTMLGlobal::defaultHTMLSettings()->addAdFilter( name );
d->m_khtml->reparseConfiguration();
}
void KHTMLPopupGUIClient::slotBlockImage()
{
bool ok = false;
QString url = KInputDialog::getText( i18n("Add URL to Filter"),
i18n("Enter the URL:"),
d->m_imageURL.url(),
&ok);
if ( ok ) {
KHTMLGlobal::defaultHTMLSettings()->addAdFilter( url );
d->m_khtml->reparseConfiguration();
}
}
void KHTMLPopupGUIClient::slotBlockIFrame()
{
bool ok = false;
QString url = KInputDialog::getText( i18n( "Add URL to Filter"),
i18n("Enter the URL:"),
d->m_khtml->url().url(),
&ok );
if ( ok ) {
KHTMLGlobal::defaultHTMLSettings()->addAdFilter( url );
d->m_khtml->reparseConfiguration();
}
}
void KHTMLPopupGUIClient::slotCopyLinkLocation()
{
KUrl safeURL(d->m_url);
safeURL.setPass(QString());
#ifndef QT_NO_MIMECLIPBOARD
// Set it in both the mouse selection and in the clipboard
QMimeData* mimeData = new QMimeData;
safeURL.populateMimeData( mimeData );
QApplication::clipboard()->setMimeData( mimeData, QClipboard::Clipboard );
mimeData = new QMimeData;
safeURL.populateMimeData( mimeData );
QApplication::clipboard()->setMimeData( mimeData, QClipboard::Selection );
#else
QApplication::clipboard()->setText( safeURL.url() ); //FIXME(E): Handle multiple entries
#endif
}
void KHTMLPopupGUIClient::slotStopAnimations()
{
d->m_khtml->stopAnimations();
}
void KHTMLPopupGUIClient::slotCopyImage()
{
#ifndef QT_NO_MIMECLIPBOARD
KUrl safeURL(d->m_imageURL);
safeURL.setPass(QString());
// Set it in both the mouse selection and in the clipboard
QMimeData* mimeData = new QMimeData;
mimeData->setImageData( d->m_pixmap );
safeURL.populateMimeData( mimeData );
QApplication::clipboard()->setMimeData( mimeData, QClipboard::Clipboard );
mimeData = new QMimeData;
mimeData->setImageData( d->m_pixmap );
safeURL.populateMimeData( mimeData );
QApplication::clipboard()->setMimeData( mimeData, QClipboard::Selection );
#else
kDebug() << "slotCopyImage called when the clipboard does not support this. This should not be possible.";
#endif
}
void KHTMLPopupGUIClient::slotCopyImageLocation()
{
KUrl safeURL(d->m_imageURL);
safeURL.setPass(QString());
#ifndef QT_NO_MIMECLIPBOARD
// Set it in both the mouse selection and in the clipboard
QMimeData* mimeData = new QMimeData;
safeURL.populateMimeData( mimeData );
QApplication::clipboard()->setMimeData( mimeData, QClipboard::Clipboard );
mimeData = new QMimeData;
safeURL.populateMimeData( mimeData );
QApplication::clipboard()->setMimeData( mimeData, QClipboard::Selection );
#else
QApplication::clipboard()->setText( safeURL.url() ); //FIXME(E): Handle multiple entries
#endif
}
void KHTMLPopupGUIClient::slotViewImage()
{
d->m_khtml->browserExtension()->createNewWindow(d->m_imageURL);
}
void KHTMLPopupGUIClient::slotReloadFrame()
{
KParts::OpenUrlArguments args = d->m_khtml->arguments();
args.setReload( true );
args.metaData()["referrer"] = d->m_khtml->pageReferrer();
// reload document
d->m_khtml->closeUrl();
d->m_khtml->setArguments( args );
d->m_khtml->openUrl( d->m_khtml->url() );
}
void KHTMLPopupGUIClient::slotFrameInWindow()
{
KParts::OpenUrlArguments args = d->m_khtml->arguments();
args.metaData()["referrer"] = d->m_khtml->pageReferrer();
KParts::BrowserArguments browserArgs( d->m_khtml->browserExtension()->browserArguments() );
browserArgs.setForcesNewWindow(true);
emit d->m_khtml->browserExtension()->createNewWindow( d->m_khtml->url(), args, browserArgs );
}
void KHTMLPopupGUIClient::slotFrameInTop()
{
KParts::OpenUrlArguments args = d->m_khtml->arguments();
args.metaData()["referrer"] = d->m_khtml->pageReferrer();
KParts::BrowserArguments browserArgs( d->m_khtml->browserExtension()->browserArguments() );
browserArgs.frameName = "_top";
emit d->m_khtml->browserExtension()->openUrlRequest( d->m_khtml->url(), args, browserArgs );
}
void KHTMLPopupGUIClient::slotFrameInTab()
{
KParts::OpenUrlArguments args = d->m_khtml->arguments();
args.metaData()["referrer"] = d->m_khtml->pageReferrer();
KParts::BrowserArguments browserArgs( d->m_khtml->browserExtension()->browserArguments() );
browserArgs.setNewTab(true);
emit d->m_khtml->browserExtension()->createNewWindow( d->m_khtml->url(), args, browserArgs );
}
void KHTMLPopupGUIClient::saveURL( QWidget *parent, const QString &caption,
const KUrl &url,
const QMap<QString, QString> &metadata,
const QString &filter, long cacheId,
const QString & suggestedFilename )
{
QString name = QLatin1String( "index.html" );
if ( !suggestedFilename.isEmpty() )
name = suggestedFilename;
else if ( !url.fileName(KUrl::ObeyTrailingSlash).isEmpty() )
name = url.fileName(KUrl::ObeyTrailingSlash);
KUrl destURL;
int query;
do {
query = KMessageBox::Yes;
// convert filename to URL using fromPath to avoid trouble with ':' in filenames (#184202)
destURL = KFileDialog::getSaveUrl( KUrl::fromPath(name), filter, parent, caption );
if( destURL.isLocalFile() )
{
QFileInfo info( destURL.toLocalFile() );
if( info.exists() ) {
// TODO: use KIO::RenameDlg (shows more information)
query = KMessageBox::warningContinueCancel( parent, i18n( "A file named \"%1\" already exists. " "Are you sure you want to overwrite it?" , info.fileName() ), i18n( "Overwrite File?" ), KGuiItem(i18n( "Overwrite" )) );
}
}
} while ( query == KMessageBox::Cancel );
if ( destURL.isValid() )
saveURL(parent, url, destURL, metadata, cacheId);
}
void KHTMLPopupGUIClient::saveURL( QWidget* parent, const KUrl &url, const KUrl &destURL,
const QMap<QString, QString> &metadata,
long cacheId )
{
if ( destURL.isValid() )
{
bool saved = false;
if (KHTMLPageCache::self()->isComplete(cacheId))
{
if (destURL.isLocalFile())
{
KSaveFile destFile(destURL.toLocalFile());
if (destFile.open())
{
QDataStream stream ( &destFile );
KHTMLPageCache::self()->saveData(cacheId, &stream);
saved = true;
}
}
else
{
// save to temp file, then move to final destination.
KTemporaryFile destFile;
if (destFile.open())
{
QDataStream stream ( &destFile );
KHTMLPageCache::self()->saveData(cacheId, &stream);
KUrl url2 = KUrl();
url2.setPath(destFile.fileName());
KIO::file_move(url2, destURL, -1, KIO::Overwrite);
saved = true;
}
}
}
if(!saved)
{
// DownloadManager <-> konqueror integration
// find if the integration is enabled
// the empty key means no integration
// only use download manager for non-local urls!
bool downloadViaKIO = true;
if ( !url.isLocalFile() )
{
KConfigGroup cfg = KSharedConfig::openConfig("konquerorrc", KConfig::NoGlobals)->group("HTML Settings");
QString downloadManger = cfg.readPathEntry("DownloadManager", QString());
if (!downloadManger.isEmpty())
{
// then find the download manager location
kDebug(1000) << "Using: "<<downloadManger <<" as Download Manager";
QString cmd = KStandardDirs::findExe(downloadManger);
if (cmd.isEmpty())
{
QString errMsg=i18n("The Download Manager (%1) could not be found in your $PATH ", downloadManger);
QString errMsgEx= i18n("Try to reinstall it \n\nThe integration with Konqueror will be disabled.");
KMessageBox::detailedSorry(0,errMsg,errMsgEx);
cfg.writePathEntry("DownloadManager",QString());
cfg.sync ();
}
else
{
downloadViaKIO = false;
KUrl cleanDest = destURL;
cleanDest.setPass( QString() ); // don't put password into commandline
cmd += ' ' + KShell::quoteArg(url.url()) + ' ' +
KShell::quoteArg(cleanDest.url());
kDebug(1000) << "Calling command "<<cmd;
KRun::runCommand(cmd, parent->topLevelWidget());
}
}
}
if ( downloadViaKIO )
{
KParts::BrowserRun::saveUrlUsingKIO(url, destURL, parent, metadata);
}
} //end if(!saved)
}
}
KHTMLPartBrowserHostExtension::KHTMLPartBrowserHostExtension( KHTMLPart *part )
: KParts::BrowserHostExtension( part )
{
m_part = part;
}
KHTMLPartBrowserHostExtension::~KHTMLPartBrowserHostExtension()
{
}
QStringList KHTMLPartBrowserHostExtension::frameNames() const
{
return m_part->frameNames();
}
const QList<KParts::ReadOnlyPart*> KHTMLPartBrowserHostExtension::frames() const
{
return m_part->frames();
}
bool KHTMLPartBrowserHostExtension::openUrlInFrame(const KUrl &url, const KParts::OpenUrlArguments& arguments, const KParts::BrowserArguments &browserArguments)
{
return m_part->openUrlInFrame( url, arguments, browserArguments );
}
KParts::BrowserHostExtension* KHTMLPartBrowserHostExtension::findFrameParent( KParts::ReadOnlyPart
*callingPart, const QString &frame )
{
KHTMLPart *parentPart = m_part->d->findFrameParent(callingPart, frame, 0, true /* navigation*/);
if (parentPart)
return parentPart->browserHostExtension();
return 0;
}
// defined in khtml_part.cpp
extern const int KDE_NO_EXPORT fastZoomSizes[];
extern const int KDE_NO_EXPORT fastZoomSizeCount;
KHTMLZoomFactorAction::KHTMLZoomFactorAction( KHTMLPart *part, bool direction, const QString &icon, const QString &text, QObject *parent )
: KSelectAction( text, parent )
{
setIcon( KIcon( icon ) );
setToolBarMode(MenuMode);
setToolButtonPopupMode(QToolButton::DelayedPopup);
init(part, direction);
}
void KHTMLZoomFactorAction::init(KHTMLPart *part, bool direction)
{
m_direction = direction;
m_part = part;
// xgettext: no-c-format
addAction( i18n( "Default Font Size (100%)" ) );
int m = m_direction ? 1 : -1;
int ofs = fastZoomSizeCount / 2; // take index of 100%
// this only works if there is an odd number of elements in fastZoomSizes[]
for ( int i = m; i != m*(ofs+1); i += m )
{
int num = i * m;
QString numStr = QString::number( num );
if ( num > 0 ) numStr.prepend( QLatin1Char('+') );
// xgettext: no-c-format
addAction( i18n( "%1%" , fastZoomSizes[ofs + i] ) );
}
connect( selectableActionGroup(), SIGNAL(triggered(QAction*)), this, SLOT(slotTriggered(QAction*)) );
}
KHTMLZoomFactorAction::~KHTMLZoomFactorAction()
{
}
void KHTMLZoomFactorAction::slotTriggered(QAction* action)
{
int idx = selectableActionGroup()->actions().indexOf(action);
if (idx == 0)
m_part->setFontScaleFactor(100);
else
m_part->setFontScaleFactor(fastZoomSizes[fastZoomSizeCount/2 + (m_direction ? 1 : -1)*idx]);
setCurrentAction( 0L );
}
KHTMLTextExtension::KHTMLTextExtension(KHTMLPart* part)
: KParts::TextExtension(part)
{
connect(part, SIGNAL(selectionChanged()), this, SIGNAL(selectionChanged()));
}
KHTMLPart* KHTMLTextExtension::part() const
{
return static_cast<KHTMLPart*>(parent());
}
bool KHTMLTextExtension::hasSelection() const
{
return part()->hasSelection();
}
QString KHTMLTextExtension::selectedText(Format format) const
{
switch(format) {
case PlainText:
return part()->selectedText();
case HTML:
return part()->selectedTextAsHTML();
}
return QString();
}
QString KHTMLTextExtension::completeText(Format format) const
{
switch(format) {
case PlainText:
return part()->htmlDocument().body().innerText().string();
case HTML:
return part()->htmlDocument().body().innerHTML().string();
}
return QString();
}
////
KHTMLHtmlExtension::KHTMLHtmlExtension(KHTMLPart* part)
: KParts::HtmlExtension(part)
{
}
KUrl KHTMLHtmlExtension::baseUrl() const
{
return part()->baseURL();
}
bool KHTMLHtmlExtension::hasSelection() const
{
return part()->hasSelection();
}
KParts::SelectorInterface::QueryMethods KHTMLHtmlExtension::supportedQueryMethods() const
{
return (KParts::SelectorInterface::SelectedContent | KParts::SelectorInterface::EntireContent);
}
static KParts::SelectorInterface::Element convertDomElement(const DOM::ElementImpl* domElem)
{
KParts::SelectorInterface::Element elem;
elem.setTagName(domElem->tagName().string());
const DOM::NamedAttrMapImpl* attrMap = domElem->attributes(true /*readonly*/);
if (attrMap) {
for (unsigned i = 0; i < attrMap->length(); ++i) {
const DOM::AttributeImpl& attr = attrMap->attributeAt(i);
elem.setAttribute(attr.localName().string(), attr.value().string());
// we could have a setAttributeNS too.
}
}
return elem;
}
KParts::SelectorInterface::Element KHTMLHtmlExtension::querySelector(const QString& query, KParts::SelectorInterface::QueryMethod method) const
{
KParts::SelectorInterface::Element element;
// If the specified method is None, return an empty list; similarly
// if the document is null, which may be possible in case of an error
if (method == KParts::SelectorInterface::None || part()->document().isNull())
return element;
if (!(supportedQueryMethods() & method))
return element;
switch (method) {
case KParts::SelectorInterface::EntireContent: {
int ec = 0; // exceptions are ignored
WTF::RefPtr<DOM::ElementImpl> domElem = part()->document().handle()->querySelector(query, ec);
element = convertDomElement(domElem.get());
break;
- }
+ }
case KParts::SelectorInterface::SelectedContent:
if (part()->hasSelection()) {
DOM::Element domElem = part()->selection().cloneContents().querySelector(query);
element = convertDomElement(static_cast<DOM::ElementImpl*>(domElem.handle()));
}
- break;
+ break;
default:
break;
}
return element;
}
QList<KParts::SelectorInterface::Element> KHTMLHtmlExtension::querySelectorAll(const QString& query, KParts::SelectorInterface::QueryMethod method) const
{
QList<KParts::SelectorInterface::Element> elements;
// If the specified method is None, return an empty list; similarly
// if the document is null, which may be possible in case of an error
if (method == KParts::SelectorInterface::None || part()->document().isNull())
return elements;
// If the specified method is not supported, return an empty list...
if (!(supportedQueryMethods() & method))
return elements;
switch (method) {
case KParts::SelectorInterface::EntireContent: {
int ec = 0; // exceptions are ignored
WTF::RefPtr<DOM::NodeListImpl> nodes = part()->document().handle()->querySelectorAll(query, ec);
const unsigned long len = nodes->length();
elements.reserve(len);
for (unsigned long i = 0; i < len; ++i) {
DOM::NodeImpl* node = nodes->item(i);
if (node->isElementNode()) { // should be always true
elements.append(convertDomElement(static_cast<DOM::ElementImpl*>(node)));
}
}
break;
}
case KParts::SelectorInterface::SelectedContent:
if (part()->hasSelection()) {
DOM::NodeList nodes = part()->selection().cloneContents().querySelectorAll(query);
const unsigned long len = nodes.length();
for (unsigned long i = 0; i < len; ++i) {
DOM::NodeImpl* node = nodes.item(i).handle();
if (node->isElementNode())
elements.append(convertDomElement(static_cast<DOM::ElementImpl*>(node)));
}
}
break;
default:
break;
}
return elements;
}
+QVariant KHTMLHtmlExtension::htmlSettingsProperty(HtmlSettingsInterface::HtmlSettingsType type) const
+{
+ if (part()) {
+ switch (type) {
+ case KParts::HtmlSettingsInterface::AutoLoadImages:
+ return part()->autoloadImages();
+ case KParts::HtmlSettingsInterface::DnsPrefetchEnabled:
+ return (part()->dnsPrefetch() == KHTMLPart::DNSPrefetchEnabled);
+ case KParts::HtmlSettingsInterface::JavaEnabled:
+ return part()->javaEnabled();
+ case KParts::HtmlSettingsInterface::JavascriptEnabled:
+ return part()->jScriptEnabled();
+ case KParts::HtmlSettingsInterface::MetaRefreshEnabled:
+ return part()->metaRefreshEnabled();
+ case KParts::HtmlSettingsInterface::PluginsEnabled:
+ return part()->pluginsEnabled();
+ default:
+ break;
+ }
+ }
+ return QVariant();
+}
+
+bool KHTMLHtmlExtension::setHtmlSettingsProperty(HtmlSettingsInterface::HtmlSettingsType type, const QVariant& value)
+{
+ KHTMLPart* p = part();
+
+ if (p) {
+ switch (type) {
+ case KParts::HtmlSettingsInterface::AutoLoadImages:
+ p->setAutoloadImages(value.toBool());
+ return true;
+ case KParts::HtmlSettingsInterface::DnsPrefetchEnabled:
+ p->setDNSPrefetch((value.toBool() ? KHTMLPart::DNSPrefetchEnabled : KHTMLPart::DNSPrefetchDisabled));
+ return true;
+ case KParts::HtmlSettingsInterface::JavaEnabled:
+ p->setJavaEnabled(value.toBool());
+ return true;
+ case KParts::HtmlSettingsInterface::JavascriptEnabled:
+ p->setJScriptEnabled(value.toBool());
+ return true;
+ case KParts::HtmlSettingsInterface::MetaRefreshEnabled:
+ p->setMetaRefreshEnabled(value.toBool());
+ return true;
+ case KParts::HtmlSettingsInterface::PluginsEnabled:
+ p->setPluginsEnabled(value.toBool());
+ return true;
+ case KParts::HtmlSettingsInterface::UserDefinedStyleSheetURL: {
+ const KUrl url (value.toUrl());
+ if (url.protocol() == QLatin1String("data")) {
+ const QByteArray data (url.encodedPath());
+ if (!data.isEmpty()) {
+ const int index = data.indexOf(',');
+ const QByteArray decodedData ((index > -1 ? QByteArray::fromBase64(data.mid(index)) : QByteArray()));
+ part()->setUserStyleSheet(QString::fromUtf8(decodedData.constData(), decodedData.size()));
+ }
+ } else {
+ part()->setUserStyleSheet(url);
+ }
+ return true;
+ }
+ default:
+ break; // Unsupported property...
+ }
+ }
+
+ return false;
+}
+
+
KHTMLPart* KHTMLHtmlExtension::part() const
{
return static_cast<KHTMLPart*>(parent());
}
#include "khtml_ext.moc"
diff --git a/khtml/khtml_ext.h b/khtml/khtml_ext.h
index ced53a38df..571a6291ae 100644
--- a/khtml/khtml_ext.h
+++ b/khtml/khtml_ext.h
@@ -1,219 +1,225 @@
/* This file is part of the KDE project
*
* Copyright (C) 2000-2003 Simon Hausmann <hausmann@kde.org>
* 2001-2003 George Staikos <staikos@kde.org>
* 2001-2003 Laurent Montel <montel@kde.org>
* 2001-2003 Dirk Mueller <mueller@kde.org>
* 2001-2003 Waldo Bastian <bastian@kde.org>
* 2001-2003 David Faure <faure@kde.org>
* 2001-2003 Daniel Naber <dnaber@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef __khtml_ext_h__
#define __khtml_ext_h__
#include "khtml_part.h"
#include <QtCore/QPointer>
#include <kselectaction.h>
#include <kparts/textextension.h>
#include <kparts/htmlextension.h>
#include <kio/global.h>
/**
* This is the BrowserExtension for a KHTMLPart document. Please see the KParts documentation for
* more information about the BrowserExtension.
*/
class KHTMLPartBrowserExtension : public KParts::BrowserExtension
{
Q_OBJECT
friend class KHTMLPart;
friend class KHTMLView;
public:
KHTMLPartBrowserExtension( KHTMLPart *parent );
virtual int xOffset();
virtual int yOffset();
virtual void saveState( QDataStream &stream );
virtual void restoreState( QDataStream &stream );
// internal
void editableWidgetFocused( QWidget *widget );
void editableWidgetBlurred( QWidget *widget );
void setExtensionProxy( KParts::BrowserExtension *proxyExtension );
public Q_SLOTS:
void cut();
void copy();
void paste();
void searchProvider();
void reparseConfiguration();
void print();
void disableScrolling();
// internal . updates the state of the cut/copt/paste action based
// on whether data is available in the clipboard
void updateEditActions();
private Q_SLOTS:
// connected to a frame's browserextensions enableAction signal
void extensionProxyActionEnabled( const char *action, bool enable );
void extensionProxyEditableWidgetFocused();
void extensionProxyEditableWidgetBlurred();
Q_SIGNALS:
void editableWidgetFocused();
void editableWidgetBlurred();
private:
void callExtensionProxyMethod( const char *method );
KHTMLPart *m_part;
QPointer<QWidget> m_editableFormWidget;
QPointer<KParts::BrowserExtension> m_extensionProxy;
bool m_connectedToClipboard;
};
class KHTMLPartBrowserHostExtension : public KParts::BrowserHostExtension
{
public:
KHTMLPartBrowserHostExtension( KHTMLPart *part );
virtual ~KHTMLPartBrowserHostExtension();
virtual QStringList frameNames() const;
virtual const QList<KParts::ReadOnlyPart*> frames() const;
virtual BrowserHostExtension* findFrameParent( KParts::ReadOnlyPart *callingPart, const QString &frame );
virtual bool openUrlInFrame(const KUrl &url, const KParts::OpenUrlArguments& arguments, const KParts::BrowserArguments &browserArguments);
private:
KHTMLPart *m_part;
};
/**
* @internal
* INTERNAL class. *NOT* part of the public API.
*/
class KHTMLPopupGUIClient : public QObject
{
Q_OBJECT
public:
KHTMLPopupGUIClient( KHTMLPart *khtml, const KUrl &url );
virtual ~KHTMLPopupGUIClient();
KParts::BrowserExtension::ActionGroupMap actionGroups() const;
static void saveURL( QWidget *parent, const QString &caption, const KUrl &url,
const QMap<QString, QString> &metaData = KIO::MetaData(),
const QString &filter = QString(), long cacheId = 0,
const QString &suggestedFilename = QString() );
static void saveURL( QWidget* parent, const KUrl &url, const KUrl &destination,
const QMap<QString, QString> &metaData = KIO::MetaData(),
long cacheId = 0 );
static QString selectedTextAsOneLine(KHTMLPart* part);
private Q_SLOTS:
void slotSaveLinkAs();
void slotSaveImageAs();
void slotCopyLinkLocation();
void slotSendImage();
void slotStopAnimations();
void slotCopyImageLocation();
void slotCopyImage();
void slotViewImage();
void slotReloadFrame();
void slotFrameInWindow();
void slotFrameInTop();
void slotFrameInTab();
void slotBlockImage();
void slotBlockHost();
void slotBlockIFrame();
void openSelection();
private:
void addSearchActions(QList<QAction *>& editActions);
class KHTMLPopupGUIClientPrivate;
KHTMLPopupGUIClientPrivate* const d;
};
class KHTMLZoomFactorAction : public KSelectAction
{
Q_OBJECT
public:
KHTMLZoomFactorAction(KHTMLPart *part, bool direction, const QString& iconName, const QString& text, QObject *parent);
virtual ~KHTMLZoomFactorAction();
protected Q_SLOTS:
void slotTriggered(QAction* action);
private:
void init(KHTMLPart *part, bool direction);
private:
bool m_direction;
KHTMLPart *m_part;
};
/**
* @internal
* Implements the TextExtension interface
*/
class KHTMLTextExtension : public KParts::TextExtension
{
Q_OBJECT
public:
KHTMLTextExtension(KHTMLPart* part);
virtual bool hasSelection() const;
virtual QString selectedText(Format format) const;
virtual QString completeText(Format format) const;
KHTMLPart* part() const;
};
/**
* @internal
* Implements the HtmlExtension interface
*/
class KHTMLHtmlExtension : public KParts::HtmlExtension,
- public KParts::SelectorInterface
+ public KParts::SelectorInterface,
+ public KParts::HtmlSettingsInterface
{
Q_OBJECT
Q_INTERFACES(KParts::SelectorInterface)
+ Q_INTERFACES(KParts::HtmlSettingsInterface)
public:
KHTMLHtmlExtension(KHTMLPart* part);
// HtmlExtension
virtual KUrl baseUrl() const;
virtual bool hasSelection() const;
// SelectorInterface
virtual QueryMethods supportedQueryMethods() const;
virtual Element querySelector(const QString& query, QueryMethod method) const;
virtual QList<Element> querySelectorAll(const QString& query, QueryMethod method) const;
+ // SettingsInterface
+ virtual QVariant htmlSettingsProperty(HtmlSettingsType type) const;
+ virtual bool setHtmlSettingsProperty(HtmlSettingsType type, const QVariant& value);
+
KHTMLPart* part() const;
};
#endif
diff --git a/kinit/klauncher.cpp b/kinit/klauncher.cpp
index bed26f9ea2..0e055f5ddb 100644
--- a/kinit/klauncher.cpp
+++ b/kinit/klauncher.cpp
@@ -1,1350 +1,1350 @@
/*
This file is part of the KDE libraries
Copyright (c) 1999 Waldo Bastian <bastian@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#define QT_NO_CAST_FROM_ASCII
#include "klauncher.h"
#include "klauncher_cmds.h"
#include "klauncher_adaptor.h"
#include <config.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <signal.h>
#include <sys/time.h>
#ifdef Q_WS_X11
#include <kstartupinfo.h>
#include <X11/Xlib.h>
#endif
#include <QtCore/QFile>
#include <kconfig.h>
#include <kdebug.h>
#include <kde_file.h>
#include <klibrary.h>
#include <klocale.h>
#include <kprotocolmanager.h>
#include <kprotocolinfo.h>
#include <krun.h>
#include <kstandarddirs.h>
#include <ktemporaryfile.h>
#include <kdesktopfile.h>
#include <kurl.h>
#include <kio/global.h>
#include <kio/connection.h>
#include <kio/slaveinterface.h>
// Dispose slaves after being idle for SLAVE_MAX_IDLE seconds
#define SLAVE_MAX_IDLE 30
// #define KLAUNCHER_VERBOSE_OUTPUT
static const char* const s_DBusStartupTypeToString[] =
{ "DBusNone", "DBusUnique", "DBusMulti", "DBusWait", "ERROR" };
using namespace KIO;
IdleSlave::IdleSlave(QObject *parent)
: QObject(parent)
{
QObject::connect(&mConn, SIGNAL(readyRead()), this, SLOT(gotInput()));
// Send it a SLAVE_STATUS command.
mConn.send( CMD_SLAVE_STATUS );
mPid = 0;
mBirthDate = time(0);
mOnHold = false;
}
template<int T> struct PIDType { typedef pid_t PID_t; } ;
template<> struct PIDType<2> { typedef qint16 PID_t; } ;
template<> struct PIDType<4> { typedef qint32 PID_t; } ;
void
IdleSlave::gotInput()
{
int cmd;
QByteArray data;
if (mConn.read( &cmd, data) == -1)
{
// Communication problem with slave.
//kError(7016) << "SlavePool: No communication with slave." << endl;
deleteLater();
}
else if (cmd == MSG_SLAVE_ACK)
{
deleteLater();
}
else if (cmd != MSG_SLAVE_STATUS)
{
kError(7016) << "SlavePool: Unexpected data from slave." << endl;
deleteLater();
}
else
{
QDataStream stream( data );
PIDType<sizeof(pid_t)>::PID_t stream_pid;
pid_t pid;
QByteArray protocol;
QString host;
qint8 b;
stream >> stream_pid >> protocol >> host >> b;
pid = stream_pid;
// Overload with (bool) onHold, (KUrl) url.
if (!stream.atEnd())
{
KUrl url;
stream >> url;
mOnHold = true;
mUrl = url;
}
mPid = pid;
mConnected = (b != 0);
mProtocol = QString::fromLatin1(protocol);
mHost = host;
emit statusUpdate(this);
}
}
void
IdleSlave::connect(const QString &app_socket)
{
QByteArray data;
QDataStream stream( &data, QIODevice::WriteOnly);
stream << app_socket;
mConn.send( CMD_SLAVE_CONNECT, data );
// Timeout!
}
void
IdleSlave::reparseConfiguration()
{
mConn.send( CMD_REPARSECONFIGURATION );
}
bool
IdleSlave::match(const QString &protocol, const QString &host, bool needConnected) const
{
if (mOnHold || protocol != mProtocol) {
return false;
}
if (host.isEmpty()) {
return true;
}
return (host == mHost) && (!needConnected || mConnected);
}
bool
IdleSlave::onHold(const KUrl &url) const
{
if (!mOnHold) return false;
return (url == mUrl);
}
int
IdleSlave::age(time_t now) const
{
return (int) difftime(now, mBirthDate);
}
static KLauncher* g_klauncher_self;
#ifndef USE_KPROCESS_FOR_KIOSLAVES
KLauncher::KLauncher(int _kdeinitSocket)
: QObject(0),
kdeinitSocket(_kdeinitSocket)
#else
KLauncher::KLauncher()
: QObject(0)
#endif
{
#ifdef Q_WS_X11
mCached_dpy = NULL;
#endif
Q_ASSERT( g_klauncher_self == NULL );
g_klauncher_self = this;
mAutoTimer.setSingleShot(true);
new KLauncherAdaptor(this);
QDBusConnection::sessionBus().registerObject(QLatin1String("/KLauncher"), this); // same as ktoolinvocation.cpp
connect(&mAutoTimer, SIGNAL(timeout()), this, SLOT(slotAutoStart()));
connect(QDBusConnection::sessionBus().interface(),
SIGNAL(serviceOwnerChanged(QString,QString,QString)),
SLOT(slotNameOwnerChanged(QString,QString,QString)));
mConnectionServer.listenForRemote();
connect(&mConnectionServer, SIGNAL(newConnection()), SLOT(acceptSlave()));
if (!mConnectionServer.isListening())
{
// Severe error!
qDebug("KLauncher: Fatal error, can't create tempfile!");
::_exit(1);
}
connect(&mTimer, SIGNAL(timeout()), SLOT(idleTimeout()));
#ifndef USE_KPROCESS_FOR_KIOSLAVES
kdeinitNotifier = new QSocketNotifier(kdeinitSocket, QSocketNotifier::Read);
connect(kdeinitNotifier, SIGNAL(activated(int)),
this, SLOT(slotKDEInitData(int)));
kdeinitNotifier->setEnabled( true );
#endif
lastRequest = 0;
bProcessingQueue = false;
mSlaveDebug = QString::fromLocal8Bit(qgetenv("KDE_SLAVE_DEBUG_WAIT"));
if (!mSlaveDebug.isEmpty())
{
qWarning("Klauncher running in slave-debug mode for slaves of protocol '%s'", qPrintable(mSlaveDebug));
}
mSlaveValgrind = QString::fromLocal8Bit(qgetenv("KDE_SLAVE_VALGRIND"));
if (!mSlaveValgrind.isEmpty())
{
mSlaveValgrindSkin = QString::fromLocal8Bit(qgetenv("KDE_SLAVE_VALGRIND_SKIN"));
qWarning("Klauncher running slaves through valgrind for slaves of protocol '%s'", qPrintable(mSlaveValgrind));
}
#ifdef USE_KPROCESS_FOR_KIOSLAVES
kDebug(7016) << "LAUNCHER_OK";
#else
klauncher_header request_header;
request_header.cmd = LAUNCHER_OK;
request_header.arg_length = 0;
write(kdeinitSocket, &request_header, sizeof(request_header));
#endif
}
KLauncher::~KLauncher()
{
close();
g_klauncher_self = NULL;
}
void KLauncher::close()
{
#ifdef Q_WS_X11
if( mCached_dpy != NULL )
{
XCloseDisplay( mCached_dpy );
mCached_dpy = NULL;
}
#endif
}
void
KLauncher::destruct()
{
if (g_klauncher_self)
g_klauncher_self->close();
// We don't delete the app here, that's intentional.
::_exit(255);
}
void KLauncher::setLaunchEnv(const QString &name, const QString &value)
{
#ifndef USE_KPROCESS_FOR_KIOSLAVES
klauncher_header request_header;
QByteArray requestData;
requestData.append(name.toLocal8Bit()).append('\0').append(value.toLocal8Bit()).append('\0');
request_header.cmd = LAUNCHER_SETENV;
request_header.arg_length = requestData.size();
write(kdeinitSocket, &request_header, sizeof(request_header));
write(kdeinitSocket, requestData.data(), request_header.arg_length);
#else
Q_UNUSED(name);
Q_UNUSED(value);
#endif
}
#ifndef USE_KPROCESS_FOR_KIOSLAVES
/*
* Read 'len' bytes from 'sock' into buffer.
* returns -1 on failure, 0 on no data.
*/
static int
read_socket(int sock, char *buffer, int len)
{
ssize_t result;
int bytes_left = len;
while (bytes_left > 0) {
// in case we get a request to start an application and data arrive
// to kdeinitSocket at the same time, requestStart() will already
// call slotKDEInitData(), so we must check there's still something
// to read, otherwise this would block
// Same thing if kdeinit dies without warning.
fd_set in;
timeval tm = { 30, 0 }; // 30 seconds timeout, so we're not stuck in case kdeinit dies on us
FD_ZERO ( &in );
FD_SET( sock, &in );
select( sock + 1, &in, 0, 0, &tm );
if( !FD_ISSET( sock, &in )) {
kDebug(7016) << "read_socket" << sock << "nothing to read, kdeinit4 must be dead";
return -1;
}
result = read(sock, buffer, bytes_left);
if (result > 0)
{
buffer += result;
bytes_left -= result;
}
else if (result == 0)
return -1;
else if ((result == -1) && (errno != EINTR))
return -1;
}
return 0;
}
#endif
void
KLauncher::slotKDEInitData(int)
{
#ifndef USE_KPROCESS_FOR_KIOSLAVES
klauncher_header request_header;
QByteArray requestData;
int result = read_socket(kdeinitSocket, (char *) &request_header,
sizeof( request_header));
if (result == -1)
{
kDebug(7016) << "Exiting on read_socket errno:" << errno;
KDE_signal( SIGHUP, SIG_IGN);
KDE_signal( SIGTERM, SIG_IGN);
destruct(); // Exit!
}
requestData.resize(request_header.arg_length);
result = read_socket(kdeinitSocket, (char *) requestData.data(),
request_header.arg_length);
processRequestReturn(request_header.cmd,requestData);
#endif
}
void KLauncher::processRequestReturn(int status, const QByteArray &requestData)
{
if (status == LAUNCHER_CHILD_DIED)
{
long *request_data;
request_data = (long *) requestData.data();
processDied(request_data[0], request_data[1]);
return;
}
if (lastRequest && (status == LAUNCHER_OK))
{
long *request_data;
request_data = (long *) requestData.data();
lastRequest->pid = (pid_t) (*request_data);
kDebug(7016).nospace() << lastRequest->name << " (pid " << lastRequest->pid <<
") up and running.";
switch(lastRequest->dbus_startup_type)
{
case KService::DBusNone:
lastRequest->status = KLaunchRequest::Running;
break;
case KService::DBusUnique:
case KService::DBusWait:
case KService::DBusMulti:
lastRequest->status = KLaunchRequest::Launching;
break;
}
lastRequest = 0;
return;
}
if (lastRequest && (status == LAUNCHER_ERROR))
{
lastRequest->status = KLaunchRequest::Error;
kDebug(7016) << lastRequest->name << " failed." << endl;
if (!requestData.isEmpty())
lastRequest->errorMsg = QString::fromUtf8((char *) requestData.data());
lastRequest = 0;
return;
}
kWarning(7016)<< "Unexpected request return" << (unsigned int) status;
}
void
KLauncher::processDied(pid_t pid, long exitStatus)
{
#ifdef KLAUNCHER_VERBOSE_OUTPUT
kDebug(7016) << pid << "exitStatus=" << exitStatus;
#else
Q_UNUSED(exitStatus);
// We should probably check the exitStatus for the uniqueapp case?
#endif
foreach (KLaunchRequest *request, requestList)
{
#ifdef KLAUNCHER_VERBOSE_OUTPUT
kDebug(7016) << " had pending request" << request->pid;
#endif
if (request->pid == pid)
{
if (request->dbus_startup_type == KService::DBusWait)
request->status = KLaunchRequest::Done;
else if ((request->dbus_startup_type == KService::DBusUnique)
&& QDBusConnection::sessionBus().interface()->isServiceRegistered(request->dbus_name)) {
request->status = KLaunchRequest::Running;
#ifdef KLAUNCHER_VERBOSE_OUTPUT
kDebug(7016) << pid << "running as a unique app";
#endif
} else {
request->status = KLaunchRequest::Error;
#ifdef KLAUNCHER_VERBOSE_OUTPUT
kDebug(7016) << pid << "died, requestDone. status=" << request->status;
#endif
}
requestDone(request);
return;
}
}
#ifdef KLAUNCHER_VERBOSE_OUTPUT
kDebug(7016) << "found no pending requests for PID" << pid;
#endif
}
static bool matchesPendingRequest(const QString& appId, const QString& pendingAppId)
{
// appId just registered, e.g. org.koffice.kword-12345
// Let's see if this is what pendingAppId (e.g. org.koffice.kword or *.kword) was waiting for.
const QString newAppId = appId.left(appId.lastIndexOf(QLatin1Char('-'))); // strip out the -12345 if present.
//kDebug() << "appId=" << appId << "newAppId=" << newAppId << "pendingAppId=" << pendingAppId;
if (pendingAppId.startsWith(QLatin1String("*."))) {
const QString pendingName = pendingAppId.mid(2);
const QString appName = newAppId.mid(newAppId.lastIndexOf(QLatin1Char('.'))+1);
//kDebug() << "appName=" << appName;
return appName == pendingName;
}
return newAppId == pendingAppId;
}
void
KLauncher::slotNameOwnerChanged(const QString &appId, const QString &oldOwner,
const QString &newOwner)
{
Q_UNUSED(oldOwner);
if (appId.isEmpty() || newOwner.isEmpty())
return;
#ifdef KLAUNCHER_VERBOSE_OUTPUT
kDebug(7016) << "new app" << appId;
#endif
foreach (KLaunchRequest *request, requestList)
{
if (request->status != KLaunchRequest::Launching)
continue;
#ifdef KLAUNCHER_VERBOSE_OUTPUT
kDebug(7016) << "had pending request" << request->name << s_DBusStartupTypeToString[request->dbus_startup_type] << "dbus_name" << request->dbus_name << request->tolerant_dbus_name;
#endif
// For unique services check the requested service name first
if (request->dbus_startup_type == KService::DBusUnique) {
if ((appId == request->dbus_name) || // just started
QDBusConnection::sessionBus().interface()->isServiceRegistered(request->dbus_name)) { // was already running
request->status = KLaunchRequest::Running;
#ifdef KLAUNCHER_VERBOSE_OUTPUT
kDebug(7016) << "OK, unique app" << request->dbus_name << "is running";
#endif
requestDone(request);
continue;
} else {
#ifdef KLAUNCHER_VERBOSE_OUTPUT
kDebug(7016) << "unique app" << request->dbus_name << "not running yet";
#endif
}
}
const QString rAppId = !request->tolerant_dbus_name.isEmpty() ? request->tolerant_dbus_name : request->dbus_name;
#ifdef KLAUNCHER_VERBOSE_OUTPUT
//kDebug(7016) << "using" << rAppId << "for matching";
#endif
if (rAppId.isEmpty())
continue;
if (matchesPendingRequest(appId, rAppId)) {
#ifdef KLAUNCHER_VERBOSE_OUTPUT
kDebug(7016) << "ok, request done";
#endif
request->dbus_name = appId;
request->status = KLaunchRequest::Running;
requestDone(request);
continue;
}
}
}
void
KLauncher::autoStart(int phase)
{
if( mAutoStart.phase() >= phase )
return;
mAutoStart.setPhase(phase);
if (phase == 0)
mAutoStart.loadAutoStartList();
mAutoTimer.start(0);
}
void
KLauncher::slotAutoStart()
{
KService::Ptr s;
do
{
QString service = mAutoStart.startService();
if (service.isEmpty())
{
// Done
if( !mAutoStart.phaseDone())
{
mAutoStart.setPhaseDone();
switch( mAutoStart.phase())
{
case 0:
emit autoStart0Done();
break;
case 1:
emit autoStart1Done();
break;
case 2:
emit autoStart2Done();
break;
}
}
return;
}
s = new KService(service);
}
while (!start_service(s, QStringList(), QStringList(), "0", false, true, QDBusMessage()));
// Loop till we find a service that we can start.
}
void
KLauncher::requestDone(KLaunchRequest *request)
{
if ((request->status == KLaunchRequest::Running) ||
(request->status == KLaunchRequest::Done))
{
requestResult.result = 0;
requestResult.dbusName = request->dbus_name;
requestResult.error = QString::fromLatin1(""); // not null, cf assert further down
requestResult.pid = request->pid;
}
else
{
requestResult.result = 1;
requestResult.dbusName.clear();
- requestResult.error = i18n("KDEInit could not launch '%1'.", request->name);
+ requestResult.error = i18n("KDEInit could not launch '%1'", request->name);
if (!request->errorMsg.isEmpty())
requestResult.error += QString::fromLatin1(":\n") + request->errorMsg;
requestResult.pid = 0;
#ifdef Q_WS_X11
if (!request->startup_dpy.isEmpty())
{
Display* dpy = NULL;
if( (mCached_dpy != NULL) &&
(request->startup_dpy == XDisplayString( mCached_dpy )))
dpy = mCached_dpy;
if( dpy == NULL )
dpy = XOpenDisplay(request->startup_dpy);
if( dpy )
{
KStartupInfoId id;
id.initId(request->startup_id);
KStartupInfo::sendFinishX( dpy, id );
if( mCached_dpy != dpy && mCached_dpy != NULL )
XCloseDisplay( mCached_dpy );
mCached_dpy = dpy;
}
}
#endif
}
if (request->autoStart)
{
mAutoTimer.start(0);
}
if (request->transaction.type() != QDBusMessage::InvalidMessage)
{
if ( requestResult.dbusName.isNull() ) // null strings can't be sent
requestResult.dbusName.clear();
Q_ASSERT( !requestResult.error.isNull() );
PIDType<sizeof(pid_t)>::PID_t stream_pid = requestResult.pid;
QDBusConnection::sessionBus().send(request->transaction.createReply(QVariantList() << requestResult.result
<< requestResult.dbusName
<< requestResult.error
<< stream_pid));
}
#ifdef KLAUNCHER_VERBOSE_OUTPUT
kDebug(7016) << "removing done request" << request->name << "PID" << request->pid;
#endif
requestList.removeAll( request );
delete request;
}
static void appendLong(QByteArray &ba, long l)
{
const int sz = ba.size();
ba.resize(sz + sizeof(long));
memcpy(ba.data() + sz, &l, sizeof(long));
}
void
KLauncher::requestStart(KLaunchRequest *request)
{
#ifdef USE_KPROCESS_FOR_KIOSLAVES
requestList.append( request );
lastRequest = request;
KProcess *process = new KProcess;
process->setOutputChannelMode(KProcess::MergedChannels);
connect(process ,SIGNAL(readyReadStandardOutput()),this, SLOT(slotGotOutput()) );
connect(process ,SIGNAL(finished(int,QProcess::ExitStatus)),this, SLOT(slotFinished(int,QProcess::ExitStatus)) );
request->process = process;
// process.setEnvironment(envlist);
QStringList args;
foreach (const QString &arg, request->arg_list)
args << arg;
QString executable = request->name;
#ifdef Q_WS_MAC
const QString bundlepath = KStandardDirs::findExe(executable);
if (!bundlepath.isEmpty())
executable = bundlepath;
#endif
process->setProgram(executable,args);
process->start();
if (!process->waitForStarted())
{
processRequestReturn(LAUNCHER_ERROR,"");
}
else
{
request->pid = process->pid();
QByteArray data((char *)&request->pid, sizeof(int));
processRequestReturn(LAUNCHER_OK,data);
}
return;
#else
requestList.append( request );
// Send request to kdeinit.
klauncher_header request_header;
QByteArray requestData;
requestData.reserve(1024);
appendLong(requestData, request->arg_list.count() + 1);
requestData.append(request->name.toLocal8Bit());
requestData.append('\0');
foreach (const QString &arg, request->arg_list)
requestData.append(arg.toLocal8Bit()).append('\0');
appendLong(requestData, request->envs.count());
foreach (const QString &env, request->envs)
requestData.append(env.toLocal8Bit()).append('\0');
appendLong(requestData, 0); // avoid_loops, always false here
#ifdef Q_WS_X11
bool startup_notify = !request->startup_id.isNull() && request->startup_id != "0";
if( startup_notify )
requestData.append(request->startup_id).append('\0');
#endif
if (!request->cwd.isEmpty())
requestData.append(QFile::encodeName(request->cwd)).append('\0');
#ifdef Q_WS_X11
request_header.cmd = startup_notify ? LAUNCHER_EXT_EXEC : LAUNCHER_EXEC_NEW;
#else
request_header.cmd = LAUNCHER_EXEC_NEW;
#endif
request_header.arg_length = requestData.length();
#ifdef KLAUNCHER_VERBOSE_OUTPUT
kDebug(7016) << "Asking kdeinit to start" << request->name << request->arg_list
<< "cmd=" << commandToString(request_header.cmd);
#endif
write(kdeinitSocket, &request_header, sizeof(request_header));
write(kdeinitSocket, requestData.data(), requestData.length());
// Wait for pid to return.
lastRequest = request;
do {
slotKDEInitData( kdeinitSocket );
}
while (lastRequest != 0);
#endif
}
void KLauncher::exec_blind(const QString &name, const QStringList &arg_list, const QStringList &envs, const QString &startup_id)
{
KLaunchRequest *request = new KLaunchRequest;
request->autoStart = false;
request->name = name;
request->arg_list = arg_list;
request->dbus_startup_type = KService::DBusNone;
request->pid = 0;
request->status = KLaunchRequest::Launching;
request->envs = envs;
// Find service, if any - strip path if needed
KService::Ptr service = KService::serviceByDesktopName( name.mid( name.lastIndexOf(QLatin1Char('/')) + 1 ));
if (service)
send_service_startup_info(request, service, startup_id.toLocal8Bit(), QStringList());
else // no .desktop file, no startup info
cancel_service_startup_info( request, startup_id.toLocal8Bit(), envs );
requestStart(request);
// We don't care about this request any longer....
requestDone(request);
}
// KDE5: remove
bool
KLauncher::start_service_by_name(const QString &serviceName, const QStringList &urls,
const QStringList &envs, const QString& startup_id, bool blind, const QDBusMessage &msg)
{
KService::Ptr service;
// Find service
#ifndef KDE_NO_DEPRECATED
service = KService::serviceByName(serviceName);
#endif
if (!service)
{
requestResult.result = ENOENT;
requestResult.error = i18n("Could not find service '%1'.", serviceName);
cancel_service_startup_info( NULL, startup_id.toLocal8Bit(), envs ); // cancel it if any
return false;
}
return start_service(service, urls, envs, startup_id.toLocal8Bit(), blind, false, msg);
}
bool
KLauncher::start_service_by_desktop_path(const QString &serviceName, const QStringList &urls,
const QStringList &envs, const QString& startup_id, bool blind, const QDBusMessage &msg)
{
KService::Ptr service;
// Find service
const QFileInfo fi(serviceName);
if (fi.isAbsolute() && fi.exists())
{
// Full path
service = new KService(serviceName);
}
else
{
service = KService::serviceByDesktopPath(serviceName);
// TODO?
//if (!service)
// service = KService::serviceByStorageId(serviceName); // This method should be named start_service_by_storage_id ideally...
}
if (!service)
{
requestResult.result = ENOENT;
requestResult.error = i18n("Could not find service '%1'.", serviceName);
cancel_service_startup_info( NULL, startup_id.toLocal8Bit(), envs ); // cancel it if any
return false;
}
return start_service(service, urls, envs, startup_id.toLocal8Bit(), blind, false, msg);
}
bool
KLauncher::start_service_by_desktop_name(const QString &serviceName, const QStringList &urls,
const QStringList &envs, const QString& startup_id, bool blind, const QDBusMessage &msg)
{
KService::Ptr service = KService::serviceByDesktopName(serviceName);
if (!service)
{
requestResult.result = ENOENT;
requestResult.error = i18n("Could not find service '%1'.", serviceName);
cancel_service_startup_info( NULL, startup_id.toLocal8Bit(), envs ); // cancel it if any
return false;
}
return start_service(service, urls, envs, startup_id.toLocal8Bit(), blind, false, msg);
}
bool
KLauncher::start_service(KService::Ptr service, const QStringList &_urls,
const QStringList &envs, const QByteArray &startup_id,
bool blind, bool autoStart, const QDBusMessage &msg)
{
QStringList urls = _urls;
bool runPermitted = KDesktopFile::isAuthorizedDesktopFile(service->entryPath());
if (!service->isValid() || !runPermitted)
{
requestResult.result = ENOEXEC;
if (service->isValid())
requestResult.error = i18n("Service '%1' must be executable to run.", service->entryPath());
else
requestResult.error = i18n("Service '%1' is malformatted.", service->entryPath());
cancel_service_startup_info( NULL, startup_id, envs ); // cancel it if any
return false;
}
KLaunchRequest *request = new KLaunchRequest;
request->autoStart = autoStart;
if ((urls.count() > 1) && !service->allowMultipleFiles())
{
// We need to launch the application N times. That sucks.
// We ignore the result for application 2 to N.
// For the first file we launch the application in the
// usual way. The reported result is based on this
// application.
QStringList::ConstIterator it = urls.constBegin();
for(++it;
it != urls.constEnd();
++it)
{
QStringList singleUrl;
singleUrl.append(*it);
QByteArray startup_id2 = startup_id;
if( !startup_id2.isEmpty() && startup_id2 != "0" )
startup_id2 = "0"; // can't use the same startup_id several times // krazy:exclude=doublequote_chars
start_service( service, singleUrl, envs, startup_id2, true, false, msg);
}
QString firstURL = *(urls.begin());
urls.clear();
urls.append(firstURL);
}
createArgs(request, service, urls);
// We must have one argument at least!
if (!request->arg_list.count())
{
requestResult.result = ENOEXEC;
requestResult.error = i18n("Service '%1' is malformatted.", service->entryPath());
delete request;
cancel_service_startup_info( NULL, startup_id, envs );
return false;
}
request->name = request->arg_list.takeFirst();
if (request->name.endsWith(QLatin1String("/kioexec"))) {
// Special case for kioexec; if createArgs said we were going to use it,
// then we have to expect a kioexec-PID, not a org.kde.finalapp...
// Testcase: konqueror www.kde.org, RMB on link, open with, kruler.
request->dbus_startup_type = KService::DBusMulti;
request->dbus_name = QString::fromLatin1("org.kde.kioexec");
} else {
request->dbus_startup_type = service->dbusStartupType();
if ((request->dbus_startup_type == KService::DBusUnique) ||
(request->dbus_startup_type == KService::DBusMulti)) {
const QVariant v = service->property(QLatin1String("X-DBUS-ServiceName"));
if (v.isValid()) {
request->dbus_name = v.toString();
}
if (request->dbus_name.isEmpty()) {
const QString binName = KRun::binaryName(service->exec(), true);
request->dbus_name = QString::fromLatin1("org.kde.") + binName;
request->tolerant_dbus_name = QString::fromLatin1("*.") + binName;
}
}
}
#ifdef KLAUNCHER_VERBOSE_OUTPUT
kDebug(7016) << "name=" << request->name << "dbus_name=" << request->dbus_name
<< "startup type=" << s_DBusStartupTypeToString[request->dbus_startup_type];
#endif
request->pid = 0;
request->envs = envs;
send_service_startup_info( request, service, startup_id, envs );
// Request will be handled later.
if (!blind && !autoStart)
{
msg.setDelayedReply(true);
request->transaction = msg;
}
queueRequest(request);
return true;
}
void
KLauncher::send_service_startup_info( KLaunchRequest *request, KService::Ptr service, const QByteArray& startup_id,
const QStringList &envs )
{
#ifdef Q_WS_X11
request->startup_id = "0";// krazy:exclude=doublequote_chars
if (startup_id == "0")
return;
bool silent;
QByteArray wmclass;
if( !KRun::checkStartupNotify( QString(), service.data(), &silent, &wmclass ))
return;
KStartupInfoId id;
id.initId(startup_id);
QByteArray dpy_str;
foreach (const QString &env, envs) {
if (env.startsWith(QLatin1String("DISPLAY=")))
dpy_str = env.mid(8).toLocal8Bit();
}
Display* dpy = NULL;
if (!dpy_str.isEmpty() && mCached_dpy != NULL && dpy_str != XDisplayString(mCached_dpy))
dpy = mCached_dpy;
if (dpy == NULL)
dpy = XOpenDisplay(dpy_str);
request->startup_id = id.id();
if (dpy == NULL) {
cancel_service_startup_info( request, startup_id, envs );
return;
}
request->startup_dpy = dpy_str;
KStartupInfoData data;
data.setName( service->name());
data.setIcon( service->icon());
data.setDescription( i18n( "Launching %1" , service->name()));
if( !wmclass.isEmpty())
data.setWMClass( wmclass );
if( silent )
data.setSilent( KStartupInfoData::Yes );
data.setApplicationId( service->entryPath());
// the rest will be sent by kdeinit
KStartupInfo::sendStartupX( dpy, id, data );
if( mCached_dpy != dpy && mCached_dpy != NULL )
XCloseDisplay( mCached_dpy );
mCached_dpy = dpy;
return;
#else
return;
#endif
}
void
KLauncher::cancel_service_startup_info( KLaunchRequest* request, const QByteArray& startup_id,
const QStringList &envs )
{
#ifdef Q_WS_X11
if( request != NULL )
request->startup_id = "0"; // krazy:exclude=doublequote_chars
if( !startup_id.isEmpty() && startup_id != "0" )
{
QString dpy_str;
foreach (const QString &env, envs) {
if (env.startsWith(QLatin1String("DISPLAY=")))
dpy_str = env.mid(8);
}
Display* dpy = NULL;
if( !dpy_str.isEmpty() && mCached_dpy != NULL
&& dpy_str != QLatin1String(XDisplayString( mCached_dpy )) )
dpy = mCached_dpy;
if( dpy == NULL )
dpy = XOpenDisplay( dpy_str.toLatin1().constData() );
if( dpy == NULL )
return;
KStartupInfoId id;
id.initId(startup_id);
KStartupInfo::sendFinishX( dpy, id );
if( mCached_dpy != dpy && mCached_dpy != NULL )
XCloseDisplay( mCached_dpy );
mCached_dpy = dpy;
}
#endif
}
bool
KLauncher::kdeinit_exec(const QString &app, const QStringList &args,
const QString& workdir, const QStringList &envs,
const QString &startup_id, bool wait, const QDBusMessage &msg)
{
KLaunchRequest *request = new KLaunchRequest;
request->autoStart = false;
request->arg_list = args;
request->name = app;
if (wait)
request->dbus_startup_type = KService::DBusWait;
else
request->dbus_startup_type = KService::DBusNone;
request->pid = 0;
#ifdef Q_WS_X11
request->startup_id = startup_id.toLocal8Bit();
#endif
request->envs = envs;
request->cwd = workdir;
#ifdef Q_WS_X11
if (!app.endsWith(QLatin1String("kbuildsycoca4"))) { // avoid stupid loop
// Find service, if any - strip path if needed
const QString desktopName = app.mid(app.lastIndexOf(QLatin1Char('/')) + 1);
KService::Ptr service = KService::serviceByDesktopName(desktopName);
if (service)
send_service_startup_info(request, service,
request->startup_id, envs);
else // no .desktop file, no startup info
cancel_service_startup_info(request, request->startup_id, envs);
}
#endif
msg.setDelayedReply(true);
request->transaction = msg;
queueRequest(request);
return true;
}
void
KLauncher::queueRequest(KLaunchRequest *request)
{
requestQueue.append( request );
if (!bProcessingQueue)
{
bProcessingQueue = true;
QTimer::singleShot(0, this, SLOT(slotDequeue()));
}
}
void
KLauncher::slotDequeue()
{
do {
KLaunchRequest *request = requestQueue.takeFirst();
// process request
request->status = KLaunchRequest::Launching;
requestStart(request);
if (request->status != KLaunchRequest::Launching)
{
// Request handled.
#ifdef KLAUNCHER_VERBOSE_OUTPUT
kDebug(7016) << "Request handled already";
#endif
requestDone( request );
continue;
}
} while(requestQueue.count());
bProcessingQueue = false;
}
void
KLauncher::createArgs( KLaunchRequest *request, const KService::Ptr service ,
const QStringList &urls)
{
const QStringList params = KRun::processDesktopExec(*service, urls);
for(QStringList::ConstIterator it = params.begin();
it != params.end(); ++it)
{
request->arg_list.append(*it);
}
request->cwd = service->path();
}
///// IO-Slave functions
pid_t
KLauncher::requestHoldSlave(const KUrl &url, const QString &app_socket)
{
IdleSlave *slave = 0;
foreach (IdleSlave *p, mSlaveList)
{
if (p->onHold(url))
{
slave = p;
break;
}
}
if (slave)
{
mSlaveList.removeAll(slave);
slave->connect(app_socket);
return slave->pid();
}
return 0;
}
pid_t
KLauncher::requestSlave(const QString &protocol,
const QString &host,
const QString &app_socket,
QString &error)
{
IdleSlave *slave = 0;
foreach (IdleSlave *p, mSlaveList)
{
if (p->match(protocol, host, true))
{
slave = p;
break;
}
}
if (!slave)
{
foreach (IdleSlave *p, mSlaveList)
{
if (p->match(protocol, host, false))
{
slave = p;
break;
}
}
}
if (!slave)
{
foreach (IdleSlave *p, mSlaveList)
{
if (p->match(protocol, QString(), false))
{
slave = p;
break;
}
}
}
if (slave)
{
mSlaveList.removeAll(slave);
slave->connect(app_socket);
return slave->pid();
}
QString name = KProtocolInfo::exec(protocol);
if (name.isEmpty())
{
error = i18n("Unknown protocol '%1'.\n", protocol);
return 0;
}
QStringList arg_list;
#ifdef USE_KPROCESS_FOR_KIOSLAVES
arg_list << name;
arg_list << protocol;
arg_list << mConnectionServer.address();
arg_list << app_socket;
name = KStandardDirs::findExe(QLatin1String("kioslave"));
#else
QString arg1 = protocol;
QString arg2 = mConnectionServer.address();
QString arg3 = app_socket;
arg_list.append(arg1);
arg_list.append(arg2);
arg_list.append(arg3);
#endif
kDebug(7016) << "KLauncher: launching new slave " << name << " with protocol=" << protocol
<< " args=" << arg_list << endl;
#ifdef Q_OS_UNIX
if (mSlaveDebug == protocol)
{
#ifndef USE_KPROCESS_FOR_KIOSLAVES
klauncher_header request_header;
request_header.cmd = LAUNCHER_DEBUG_WAIT;
request_header.arg_length = 0;
write(kdeinitSocket, &request_header, sizeof(request_header));
#else
name = QString::fromLatin1("gdb");
#endif
}
if (mSlaveValgrind == protocol) {
#ifndef USE_KPROCESS_FOR_KIOSLAVES // otherwise we've already done this
KLibrary lib(name, KGlobal::mainComponent());
arg_list.prepend(lib.fileName());
arg_list.prepend(KStandardDirs::locate("exe", QString::fromLatin1("kioslave")));
#endif
name = QString::fromLatin1("valgrind");
if (!mSlaveValgrindSkin.isEmpty()) {
arg_list.prepend(QLatin1String("--tool=") + mSlaveValgrindSkin);
} else
arg_list.prepend(QLatin1String("--tool=memcheck"));
}
#endif
KLaunchRequest *request = new KLaunchRequest;
request->autoStart = false;
request->name = name;
request->arg_list = arg_list;
request->dbus_startup_type = KService::DBusNone;
request->pid = 0;
#ifdef Q_WS_X11
request->startup_id = "0"; // krazy:exclude=doublequote_chars
#endif
request->status = KLaunchRequest::Launching;
requestStart(request);
pid_t pid = request->pid;
// kDebug(7016) << "Slave launched, pid = " << pid;
// We don't care about this request any longer....
requestDone(request);
if (!pid)
{
error = i18n("Error loading '%1'.\n", name);
}
return pid;
}
bool KLauncher::checkForHeldSlave(const QString &url)
{
Q_FOREACH (const IdleSlave *p, mSlaveList) {
if (p->onHold(url)) {
return true;
}
}
return false;
}
void
KLauncher::waitForSlave(int pid, const QDBusMessage &msg)
{
foreach (IdleSlave *slave, mSlaveList)
{
if (slave->pid() == static_cast<pid_t>(pid))
return; // Already here.
}
SlaveWaitRequest *waitRequest = new SlaveWaitRequest;
msg.setDelayedReply(true);
waitRequest->transaction = msg;
waitRequest->pid = static_cast<pid_t>(pid);
mSlaveWaitRequest.append(waitRequest);
}
void
KLauncher::acceptSlave()
{
IdleSlave *slave = new IdleSlave(this);
mConnectionServer.setNextPendingConnection(&slave->mConn);
mSlaveList.append(slave);
connect(slave, SIGNAL(destroyed()), this, SLOT(slotSlaveGone()));
connect(slave, SIGNAL(statusUpdate(IdleSlave*)),
this, SLOT(slotSlaveStatus(IdleSlave*)));
if (!mTimer.isActive())
{
mTimer.start(1000*10);
}
}
void
KLauncher::slotSlaveStatus(IdleSlave *slave)
{
QMutableListIterator<SlaveWaitRequest *> it(mSlaveWaitRequest);
while(it.hasNext())
{
SlaveWaitRequest *waitRequest = it.next();
if (waitRequest->pid == slave->pid())
{
QDBusConnection::sessionBus().send(waitRequest->transaction.createReply());
it.remove();
delete waitRequest;
}
}
}
void
KLauncher::slotSlaveGone()
{
IdleSlave *slave = (IdleSlave *) sender();
mSlaveList.removeAll(slave);
if ((mSlaveList.count() == 0) && (mTimer.isActive()))
{
mTimer.stop();
}
}
void
KLauncher::idleTimeout()
{
bool keepOneFileSlave=true;
time_t now = time(0);
foreach (IdleSlave *slave, mSlaveList)
{
if ((slave->protocol()==QLatin1String("file")) && (keepOneFileSlave))
keepOneFileSlave=false;
else if (slave->age(now) > SLAVE_MAX_IDLE)
{
// killing idle slave
delete slave;
}
}
}
void KLauncher::reparseConfiguration()
{
KProtocolManager::reparseConfiguration();
foreach (IdleSlave *slave, mSlaveList)
slave->reparseConfiguration();
}
void
KLauncher::slotGotOutput()
{
#ifdef USE_KPROCESS_FOR_KIOSLAVES
KProcess *p = static_cast<KProcess *>(sender());
QByteArray _stdout = p->readAllStandardOutput();
kDebug(7016) << _stdout.data();
#endif
}
void
KLauncher::slotFinished(int exitCode, QProcess::ExitStatus exitStatus )
{
#ifdef USE_KPROCESS_FOR_KIOSLAVES
KProcess *p = static_cast<KProcess *>(sender());
kDebug(7016) << "process finished exitcode=" << exitCode << "exitStatus=" << exitStatus;
foreach (KLaunchRequest *request, requestList)
{
if (request->process == p)
{
#ifdef KLAUNCHER_VERBOSE_OUTPUT
kDebug(7016) << "found KProcess, request done";
#endif
if (exitCode == 0 && exitStatus == QProcess::NormalExit)
request->status = KLaunchRequest::Done;
else
request->status = KLaunchRequest::Error;
requestDone(request);
request->process = 0;
}
}
delete p;
#else
Q_UNUSED(exitCode);
Q_UNUSED(exitStatus);
#endif
}
void KLauncher::terminate_kdeinit()
{
kDebug(7016);
#ifndef USE_KPROCESS_FOR_KIOSLAVES
klauncher_header request_header;
request_header.cmd = LAUNCHER_TERMINATE_KDEINIT;
request_header.arg_length = 0;
write(kdeinitSocket, &request_header, sizeof(request_header));
#endif
}
#include "klauncher.moc"
diff --git a/kio/kfile/kopenwithdialog.cpp b/kio/kfile/kopenwithdialog.cpp
index d82f3bb897..42d5d82232 100644
--- a/kio/kfile/kopenwithdialog.cpp
+++ b/kio/kfile/kopenwithdialog.cpp
@@ -1,977 +1,981 @@
/* This file is part of the KDE libraries
Copyright (C) 1997 Torben Weis <weis@stud.uni-frankfurt.de>
Copyright (C) 1999 Dirk Mueller <mueller@kde.org>
Portions copyright (C) 1999 Preston Brown <pbrown@kde.org>
Copyright (C) 2007 Pino Toscano <pino@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "kopenwithdialog.h"
#include "kopenwithdialog_p.h"
#include <QtCore/QtAlgorithms>
#include <QtCore/QList>
#include <QtGui/QLabel>
#include <QtGui/QLayout>
#include <QtGui/QCheckBox>
#include <QtGui/QStyle>
#include <QtGui/QStyleOptionButton>
#include <kauthorized.h>
#include <khistorycombobox.h>
#include <kdesktopfile.h>
#include <klineedit.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kshell.h>
#include <krun.h>
#include <kstandarddirs.h>
#include <kstringhandler.h>
#include <kurlcompletion.h>
#include <kurlrequester.h>
#include <kmimetype.h>
#include <kservicegroup.h>
#include <kserviceoffer.h>
#include <kdebug.h>
#include <assert.h>
#include <stdlib.h>
#include <kbuildsycocaprogressdialog.h>
#include <kconfiggroup.h>
inline void writeEntry( KConfigGroup& group, const char* key,
const KGlobalSettings::Completion& aValue,
KConfigBase::WriteConfigFlags flags = KConfigBase::Normal )
{
group.writeEntry(key, int(aValue), flags);
}
namespace KDEPrivate {
class AppNode
{
public:
AppNode()
: isDir(false), parent(0), fetched(false)
{
}
~AppNode()
{
qDeleteAll(children);
}
QString icon;
QString text;
QString entryPath;
QString exec;
bool isDir;
AppNode *parent;
bool fetched;
QList<AppNode*> children;
};
bool AppNodeLessThan(KDEPrivate::AppNode *n1, KDEPrivate::AppNode *n2)
{
if (n1->isDir) {
if (n2->isDir) {
return n1->text.compare(n2->text, Qt::CaseInsensitive) < 0;
} else {
return true;
}
} else {
if (n2->isDir) {
return false;
} else {
return n1->text.compare(n2->text, Qt::CaseInsensitive) < 0;
}
}
return true;
}
}
class KApplicationModelPrivate
{
public:
KApplicationModelPrivate(KApplicationModel *qq)
: q(qq), root(new KDEPrivate::AppNode())
{
}
~KApplicationModelPrivate()
{
delete root;
}
void fillNode(const QString &entryPath, KDEPrivate::AppNode *node);
KApplicationModel *q;
KDEPrivate::AppNode *root;
};
void KApplicationModelPrivate::fillNode(const QString &_entryPath, KDEPrivate::AppNode *node)
{
KServiceGroup::Ptr root = KServiceGroup::group(_entryPath);
if (!root || !root->isValid()) return;
const KServiceGroup::List list = root->entries();
for( KServiceGroup::List::ConstIterator it = list.begin();
it != list.end(); ++it)
{
QString icon;
QString text;
QString entryPath;
QString exec;
bool isDir = false;
const KSycocaEntry::Ptr p = (*it);
if (p->isType(KST_KService))
{
const KService::Ptr service = KService::Ptr::staticCast(p);
if (service->noDisplay())
continue;
icon = service->icon();
text = service->name();
exec = service->exec();
entryPath = service->entryPath();
}
else if (p->isType(KST_KServiceGroup))
{
const KServiceGroup::Ptr serviceGroup = KServiceGroup::Ptr::staticCast(p);
if (serviceGroup->noDisplay() || serviceGroup->childCount() == 0)
continue;
icon = serviceGroup->icon();
text = serviceGroup->caption();
entryPath = serviceGroup->entryPath();
isDir = true;
}
else
{
kWarning(250) << "KServiceGroup: Unexpected object in list!";
continue;
}
KDEPrivate::AppNode *newnode = new KDEPrivate::AppNode();
newnode->icon = icon;
newnode->text = text;
newnode->entryPath = entryPath;
newnode->exec = exec;
newnode->isDir = isDir;
newnode->parent = node;
node->children.append(newnode);
}
qStableSort(node->children.begin(), node->children.end(), KDEPrivate::AppNodeLessThan);
}
KApplicationModel::KApplicationModel(QObject *parent)
: QAbstractItemModel(parent), d(new KApplicationModelPrivate(this))
{
d->fillNode(QString(), d->root);
}
KApplicationModel::~KApplicationModel()
{
delete d;
}
bool KApplicationModel::canFetchMore(const QModelIndex &parent) const
{
if (!parent.isValid())
return false;
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer());
return node->isDir && !node->fetched;
}
int KApplicationModel::columnCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return 1;
}
QVariant KApplicationModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer());
switch (role) {
case Qt::DisplayRole:
return node->text;
break;
case Qt::DecorationRole:
if (!node->icon.isEmpty()) {
return KIcon(node->icon);
}
break;
default:
;
}
return QVariant();
}
void KApplicationModel::fetchMore(const QModelIndex &parent)
{
if (!parent.isValid())
return;
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer());
if (!node->isDir)
return;
emit layoutAboutToBeChanged();
d->fillNode(node->entryPath, node);
node->fetched = true;
emit layoutChanged();
}
bool KApplicationModel::hasChildren(const QModelIndex &parent) const
{
if (!parent.isValid())
return true;
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer());
return node->isDir;
}
QVariant KApplicationModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation != Qt::Horizontal || section != 0)
return QVariant();
switch (role) {
case Qt::DisplayRole:
return i18n("Known Applications");
break;
default:
return QVariant();
}
}
QModelIndex KApplicationModel::index(int row, int column, const QModelIndex &parent) const
{
if (row < 0 || column != 0)
return QModelIndex();
KDEPrivate::AppNode *node = d->root;
if (parent.isValid())
node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer());
if (row >= node->children.count())
return QModelIndex();
else
return createIndex(row, 0, node->children.at(row));
}
QModelIndex KApplicationModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex();
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer());
if (node->parent->parent) {
int id = node->parent->parent->children.indexOf(node->parent);
if (id >= 0 && id < node->parent->parent->children.count())
return createIndex(id, 0, node->parent);
else
return QModelIndex();
}
else
return QModelIndex();
}
int KApplicationModel::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid())
return d->root->children.count();
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(parent.internalPointer());
return node->children.count();
}
QString KApplicationModel::entryPathFor(const QModelIndex &index) const
{
if (!index.isValid())
return QString();
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer());
return node->entryPath;
}
QString KApplicationModel::execFor(const QModelIndex &index) const
{
if (!index.isValid())
return QString();
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer());
return node->exec;
}
bool KApplicationModel::isDirectory(const QModelIndex &index) const
{
if (!index.isValid())
return false;
KDEPrivate::AppNode *node = static_cast<KDEPrivate::AppNode*>(index.internalPointer());
return node->isDir;
}
class KApplicationViewPrivate
{
public:
KApplicationViewPrivate()
: appModel(0)
{
}
KApplicationModel *appModel;
};
KApplicationView::KApplicationView(QWidget *parent)
: QTreeView(parent), d(new KApplicationViewPrivate)
{
}
KApplicationView::~KApplicationView()
{
delete d;
}
void KApplicationView::setModel(QAbstractItemModel *model)
{
if (d->appModel) {
disconnect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
this, SLOT(slotSelectionChanged(QItemSelection,QItemSelection)));
}
QTreeView::setModel(model);
d->appModel = qobject_cast<KApplicationModel*>(model);
if (d->appModel) {
connect(selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
this, SLOT(slotSelectionChanged(QItemSelection,QItemSelection)));
}
}
bool KApplicationView::isDirSel() const
{
if (d->appModel) {
QModelIndex index = selectionModel()->currentIndex();
return d->appModel->isDirectory(index);
}
return false;
}
void KApplicationView::currentChanged(const QModelIndex &current, const QModelIndex &previous)
{
QTreeView::currentChanged(current, previous);
if (d->appModel && !d->appModel->isDirectory(current)) {
QString exec = d->appModel->execFor(current);
if (!exec.isEmpty()) {
emit highlighted(d->appModel->entryPathFor(current), exec);
}
}
}
void KApplicationView::slotSelectionChanged(const QItemSelection &selected, const QItemSelection &deselected)
{
Q_UNUSED(deselected)
const QModelIndexList indexes = selected.indexes();
if (indexes.count() == 1 && !d->appModel->isDirectory(indexes.at(0))) {
QString exec = d->appModel->execFor(indexes.at(0));
if (!exec.isEmpty()) {
emit this->selected(d->appModel->entryPathFor(indexes.at(0)), exec);
}
}
}
/***************************************************************
*
* KOpenWithDialog
*
***************************************************************/
class KOpenWithDialogPrivate
{
public:
KOpenWithDialogPrivate(KOpenWithDialog *qq)
: q(qq), saveNewApps(false)
{
}
KOpenWithDialog *q;
/**
* Determine mime type from URLs
*/
void setMimeType(const KUrl::List &_urls);
void addToMimeAppsList(const QString& serviceId);
/**
* Create a dialog that asks for a application to open a given
* URL(s) with.
*
* @param text appears as a label on top of the entry box.
* @param value is the initial value of the line
*/
void init(const QString &text, const QString &value);
/**
* Called by checkAccept() in order to save the history of the combobox
*/
void saveComboboxHistory();
/**
* Process the choices made by the user, and return true if everything is OK.
* Called by KOpenWithDialog::accept(), i.e. when clicking on OK or typing Return.
*/
bool checkAccept();
// slots
void _k_slotDbClick();
void _k_slotFileSelected();
bool saveNewApps;
bool m_terminaldirty;
KService::Ptr curService;
KApplicationView *view;
KUrlRequester *edit;
QString m_command;
QLabel *label;
QString qMimeType;
QCheckBox *terminal;
QCheckBox *remember;
QCheckBox *nocloseonexit;
KService::Ptr m_pService;
};
KOpenWithDialog::KOpenWithDialog( const KUrl::List& _urls, QWidget* parent )
: KDialog(parent), d(new KOpenWithDialogPrivate(this))
{
setObjectName( QLatin1String( "openwith" ) );
setModal( true );
setCaption( i18n( "Open With" ) );
QString text;
if( _urls.count() == 1 )
{
text = i18n("<qt>Select the program that should be used to open <b>%1</b>. "
"If the program is not listed, enter the name or click "
"the browse button.</qt>", _urls.first().fileName() );
}
else
// Should never happen ??
text = i18n( "Choose the name of the program with which to open the selected files." );
d->setMimeType(_urls);
d->init(text, QString());
}
KOpenWithDialog::KOpenWithDialog( const KUrl::List& _urls, const QString&_text,
const QString& _value, QWidget *parent)
: KDialog(parent), d(new KOpenWithDialogPrivate(this))
{
setObjectName( QLatin1String( "openwith" ) );
setModal( true );
QString caption;
if (_urls.count()>0 && !_urls.first().isEmpty())
caption = KStringHandler::csqueeze( _urls.first().prettyUrl() );
if (_urls.count() > 1)
caption += QString::fromLatin1("...");
setCaption(caption);
d->setMimeType(_urls);
d->init(_text, _value);
}
KOpenWithDialog::KOpenWithDialog( const QString &mimeType, const QString& value,
QWidget *parent)
: KDialog(parent), d(new KOpenWithDialogPrivate(this))
{
setObjectName( QLatin1String( "openwith" ) );
setModal( true );
setCaption(i18n("Choose Application for %1", mimeType));
QString text = i18n("<qt>Select the program for the file type: <b>%1</b>. "
"If the program is not listed, enter the name or click "
"the browse button.</qt>", mimeType);
d->qMimeType = mimeType;
d->init(text, value);
if (d->remember) {
d->remember->hide();
}
}
KOpenWithDialog::KOpenWithDialog( QWidget *parent)
: KDialog(parent), d(new KOpenWithDialogPrivate(this))
{
setObjectName( QLatin1String( "openwith" ) );
setModal( true );
setCaption(i18n("Choose Application"));
QString text = i18n("<qt>Select a program. "
"If the program is not listed, enter the name or click "
"the browse button.</qt>");
d->qMimeType.clear();
d->init(text, QString());
}
void KOpenWithDialogPrivate::setMimeType(const KUrl::List &_urls)
{
if ( _urls.count() == 1 )
{
qMimeType = KMimeType::findByUrl( _urls.first())->name();
if (qMimeType == QLatin1String("application/octet-stream"))
qMimeType.clear();
}
else
qMimeType.clear();
}
void KOpenWithDialogPrivate::init(const QString &_text, const QString &_value)
{
bool bReadOnly = !KAuthorized::authorize("shell_access");
m_terminaldirty = false;
view = 0;
m_pService = 0;
curService = 0;
q->setButtons(KDialog::Ok | KDialog::Cancel);
QWidget *mainWidget = q->mainWidget();
QBoxLayout *topLayout = new QVBoxLayout( mainWidget );
topLayout->setMargin(0);
label = new QLabel(_text, q);
label->setWordWrap(true);
topLayout->addWidget(label);
if (!bReadOnly)
{
// init the history combo and insert it into the URL-Requester
KHistoryComboBox *combo = new KHistoryComboBox();
KLineEdit *lineEdit = new KLineEdit(q);
lineEdit->setClearButtonShown(true);
combo->setLineEdit(lineEdit);
combo->setDuplicatesEnabled( false );
KConfigGroup cg( KGlobal::config(), QString::fromLatin1("Open-with settings") );
int max = cg.readEntry( "Maximum history", 15 );
combo->setMaxCount( max );
int mode = cg.readEntry( "CompletionMode", int(KGlobalSettings::completionMode()));
combo->setCompletionMode((KGlobalSettings::Completion)mode);
const QStringList list = cg.readEntry( "History", QStringList() );
combo->setHistoryItems( list, true );
edit = new KUrlRequester( combo, mainWidget );
}
else
{
edit = new KUrlRequester( mainWidget );
edit->lineEdit()->setReadOnly(true);
edit->button()->hide();
}
edit->setText( _value );
edit->setWhatsThis(i18n(
"Following the command, you can have several place holders which will be replaced "
"with the actual values when the actual program is run:\n"
"%f - a single file name\n"
"%F - a list of files; use for applications that can open several local files at once\n"
"%u - a single URL\n"
"%U - a list of URLs\n"
"%d - the directory of the file to open\n"
"%D - a list of directories\n"
"%i - the icon\n"
"%m - the mini-icon\n"
"%c - the comment"));
topLayout->addWidget(edit);
if ( edit->comboBox() ) {
KUrlCompletion *comp = new KUrlCompletion( KUrlCompletion::ExeCompletion );
edit->comboBox()->setCompletionObject( comp );
edit->comboBox()->setAutoDeleteCompletionObject( true );
}
QObject::connect(edit, SIGNAL(textChanged(QString)), q, SLOT(slotTextChanged()));
QObject::connect(edit, SIGNAL(urlSelected(KUrl)), q, SLOT(_k_slotFileSelected()));
view = new KApplicationView(mainWidget);
view->setModel(new KApplicationModel(view));
topLayout->addWidget(view);
topLayout->setStretchFactor(view, 1);
QObject::connect(view, SIGNAL(selected(QString,QString)),
q, SLOT(slotSelected(QString,QString)));
QObject::connect(view, SIGNAL(highlighted(QString,QString)),
q, SLOT(slotHighlighted(QString,QString)));
QObject::connect(view, SIGNAL(doubleClicked(QModelIndex)),
q, SLOT(_k_slotDbClick()));
terminal = new QCheckBox( i18n("Run in &terminal"), mainWidget );
if (bReadOnly)
terminal->hide();
QObject::connect(terminal, SIGNAL(toggled(bool)), q, SLOT(slotTerminalToggled(bool)));
topLayout->addWidget(terminal);
QStyleOptionButton checkBoxOption;
checkBoxOption.initFrom(terminal);
int checkBoxIndentation = terminal->style()->pixelMetric( QStyle::PM_IndicatorWidth, &checkBoxOption, terminal );
checkBoxIndentation += terminal->style()->pixelMetric( QStyle::PM_CheckBoxLabelSpacing, &checkBoxOption, terminal );
QBoxLayout* nocloseonexitLayout = new QHBoxLayout();
nocloseonexitLayout->setMargin( 0 );
QSpacerItem* spacer = new QSpacerItem( checkBoxIndentation, 0, QSizePolicy::Fixed, QSizePolicy::Minimum );
nocloseonexitLayout->addItem( spacer );
nocloseonexit = new QCheckBox( i18n("&Do not close when command exits"), mainWidget );
nocloseonexit->setChecked( false );
nocloseonexit->setDisabled( true );
// check to see if we use konsole if not disable the nocloseonexit
// because we don't know how to do this on other terminal applications
KConfigGroup confGroup( KGlobal::config(), QString::fromLatin1("General") );
QString preferredTerminal = confGroup.readPathEntry("TerminalApplication", QString::fromLatin1("konsole"));
if (bReadOnly || preferredTerminal != "konsole")
nocloseonexit->hide();
nocloseonexitLayout->addWidget( nocloseonexit );
topLayout->addLayout( nocloseonexitLayout );
if (!qMimeType.isNull())
{
remember = new QCheckBox(i18n("&Remember application association for this type of file"), mainWidget);
// remember->setChecked(true);
topLayout->addWidget(remember);
}
else
remember = 0L;
q->setMinimumSize(q->minimumSizeHint());
//edit->setText( _value );
// This is what caused "can't click on items before clicking on Name header".
// Probably due to the resizeEvent handler using width().
//resize( minimumWidth(), sizeHint().height() );
edit->setFocus();
q->slotTextChanged();
}
// ----------------------------------------------------------------------
KOpenWithDialog::~KOpenWithDialog()
{
delete d;
}
// ----------------------------------------------------------------------
void KOpenWithDialog::slotSelected( const QString& /*_name*/, const QString& _exec )
{
KService::Ptr pService = d->curService;
d->edit->setText(_exec); // calls slotTextChanged :(
d->curService = pService;
}
// ----------------------------------------------------------------------
void KOpenWithDialog::slotHighlighted(const QString& entryPath, const QString&)
{
d->curService = KService::serviceByDesktopPath(entryPath);
if (!d->m_terminaldirty)
{
// ### indicate that default value was restored
d->terminal->setChecked(d->curService->terminal());
QString terminalOptions = d->curService->terminalOptions();
d->nocloseonexit->setChecked((terminalOptions.contains(QLatin1String("--noclose")) > 0));
d->m_terminaldirty = false; // slotTerminalToggled changed it
}
}
// ----------------------------------------------------------------------
void KOpenWithDialog::slotTextChanged()
{
// Forget about the service
d->curService = 0L;
enableButton(Ok, !d->edit->text().isEmpty());
}
// ----------------------------------------------------------------------
void KOpenWithDialog::slotTerminalToggled(bool)
{
// ### indicate that default value was overridden
d->m_terminaldirty = true;
d->nocloseonexit->setDisabled(!d->terminal->isChecked());
}
// ----------------------------------------------------------------------
void KOpenWithDialogPrivate::_k_slotDbClick()
{
// check if a directory is selected
if (view->isDirSel()) {
return;
}
q->accept();
}
void KOpenWithDialogPrivate::_k_slotFileSelected()
{
// quote the path to avoid unescaped whitespace, backslashes, etc.
edit->setText(KShell::quoteArg(edit->text()));
}
void KOpenWithDialog::setSaveNewApplications(bool b)
{
d->saveNewApps = b;
}
static QString simplifiedExecLineFromService(const QString& fullExec)
{
QString exec = fullExec;
exec.remove("%u", Qt::CaseInsensitive);
exec.remove("%f", Qt::CaseInsensitive);
exec.remove("-caption %c");
exec.remove("-caption \"%c\"");
exec.remove("%i");
exec.remove("%m");
return exec.simplified();
}
void KOpenWithDialogPrivate::addToMimeAppsList(const QString& serviceId /*menu id or storage id*/)
{
KSharedConfig::Ptr profile = KSharedConfig::openConfig("mimeapps.list", KConfig::NoGlobals, "xdgdata-apps");
KConfigGroup addedApps(profile, "Added Associations");
QStringList apps = addedApps.readXdgListEntry(qMimeType);
apps.removeAll(serviceId);
apps.prepend(serviceId); // make it the preferred app
addedApps.writeXdgListEntry(qMimeType, apps);
addedApps.sync();
// Also make sure the "auto embed" setting for this mimetype is off
KSharedConfig::Ptr fileTypesConfig = KSharedConfig::openConfig("filetypesrc", KConfig::NoGlobals);
fileTypesConfig->group("EmbedSettings").writeEntry(QString("embed-")+qMimeType, false);
fileTypesConfig->sync();
kDebug(250) << "rebuilding ksycoca...";
// kbuildsycoca is the one reading mimeapps.list, so we need to run it now
KBuildSycocaProgressDialog::rebuildKSycoca(q);
m_pService = KService::serviceByStorageId(serviceId);
Q_ASSERT( m_pService );
}
bool KOpenWithDialogPrivate::checkAccept()
{
const QString typedExec(edit->text());
if (typedExec.isEmpty())
return false;
QString fullExec(typedExec);
QString serviceName;
QString initialServiceName;
QString preferredTerminal;
QString binaryName;
m_pService = curService;
if (!m_pService) {
// No service selected - check the command line
// Find out the name of the service from the command line, removing args and paths
serviceName = KRun::binaryName( typedExec, true );
if (serviceName.isEmpty()) {
KMessageBox::error(q, i18n("Could not extract executable name from '%1', please type a valid program name.", serviceName));
return false;
}
initialServiceName = serviceName;
// Also remember the binaryName with a path, if any, for the
// check that the binary exists.
binaryName = KRun::binaryName(typedExec, false);
kDebug(250) << "initialServiceName=" << initialServiceName << "binaryName=" << binaryName;
int i = 1; // We have app, app-2, app-3... Looks better for the user.
bool ok = false;
// Check if there's already a service by that name, with the same Exec line
do {
kDebug(250) << "looking for service" << serviceName;
KService::Ptr serv = KService::serviceByDesktopName( serviceName );
ok = !serv; // ok if no such service yet
// also ok if we find the exact same service (well, "kwrite" == "kwrite %U")
if (serv) {
if (serv->isApplication()) {
/*kDebug(250) << "typedExec=" << typedExec
<< "serv->exec=" << serv->exec()
<< "simplifiedExecLineFromService=" << simplifiedExecLineFromService(fullExec);*/
if (typedExec == simplifiedExecLineFromService(serv->exec())) {
ok = true;
m_pService = serv;
kDebug(250) << "OK, found identical service: " << serv->entryPath();
} else {
kDebug(250) << "Exec line differs, service says:" << simplifiedExecLineFromService(fullExec);
}
} else {
kDebug(250) << "Found, but not an application:" << serv->entryPath();
}
}
if (!ok) { // service was found, but it was different -> keep looking
++i;
serviceName = initialServiceName + '-' + QString::number(i);
}
} while (!ok);
}
if ( m_pService ) {
// Existing service selected
serviceName = m_pService->name();
initialServiceName = serviceName;
fullExec = m_pService->exec();
} else {
// Ensure that the typed binary name actually exists (#81190)
if (KStandardDirs::findExe(binaryName).isEmpty()) {
KMessageBox::error(q, i18n("'%1' not found, please type a valid program name.", binaryName));
return false;
}
}
if (terminal->isChecked()) {
KConfigGroup confGroup( KGlobal::config(), QString::fromLatin1("General") );
preferredTerminal = confGroup.readPathEntry("TerminalApplication", QString::fromLatin1("konsole"));
m_command = preferredTerminal;
// only add --noclose when we are sure it is konsole we're using
if (preferredTerminal == "konsole" && nocloseonexit->isChecked())
m_command += QString::fromLatin1(" --noclose");
m_command += QString::fromLatin1(" -e ");
m_command += edit->text();
kDebug(250) << "Setting m_command to" << m_command;
}
if ( m_pService && terminal->isChecked() != m_pService->terminal() )
m_pService = 0; // It's not exactly this service we're running
const bool bRemember = remember && remember->isChecked();
kDebug(250) << "bRemember=" << bRemember << "service found=" << m_pService;
if (m_pService) {
if (bRemember) {
// Associate this app with qMimeType in mimeapps.list
Q_ASSERT(!qMimeType.isEmpty()); // we don't show the remember checkbox otherwise
addToMimeAppsList(m_pService->storageId());
}
} else {
const bool createDesktopFile = bRemember || saveNewApps;
if (!createDesktopFile) {
// Create temp service
m_pService = new KService(initialServiceName, fullExec, QString());
if (terminal->isChecked()) {
m_pService->setTerminal(true);
// only add --noclose when we are sure it is konsole we're using
if (preferredTerminal == "konsole" && nocloseonexit->isChecked())
m_pService->setTerminalOptions("--noclose");
}
} else {
// If we got here, we can't seem to find a service for what they wanted. Create one.
QString menuId;
+#ifdef Q_WS_WIN32
+ // on windows, do not use the complete path, but only the default name.
+ serviceName = QFileInfo(serviceName).fileName();
+#endif
QString newPath = KService::newServicePath(false /* ignored argument */, serviceName, &menuId);
kDebug(250) << "Creating new service" << serviceName << "(" << newPath << ")" << "menuId=" << menuId;
KDesktopFile desktopFile(newPath);
KConfigGroup cg = desktopFile.desktopGroup();
cg.writeEntry("Type", "Application");
cg.writeEntry("Name", initialServiceName);
cg.writeEntry("Exec", fullExec);
cg.writeEntry("NoDisplay", true); // don't make it appear in the K menu
if (terminal->isChecked()) {
cg.writeEntry("Terminal", true);
// only add --noclose when we are sure it is konsole we're using
if (preferredTerminal == "konsole" && nocloseonexit->isChecked())
cg.writeEntry("TerminalOptions", "--noclose");
}
cg.writeXdgListEntry("MimeType", QStringList() << qMimeType);
cg.sync();
addToMimeAppsList(menuId);
}
}
saveComboboxHistory();
return true;
}
void KOpenWithDialog::accept()
{
if (d->checkAccept())
KDialog::accept();
}
QString KOpenWithDialog::text() const
{
if (!d->m_command.isEmpty())
return d->m_command;
else
return d->edit->text();
}
void KOpenWithDialog::hideNoCloseOnExit()
{
// uncheck the checkbox because the value could be used when "Run in Terminal" is selected
d->nocloseonexit->setChecked(false);
d->nocloseonexit->hide();
}
void KOpenWithDialog::hideRunInTerminal()
{
d->terminal->hide();
hideNoCloseOnExit();
}
KService::Ptr KOpenWithDialog::service() const
{
return d->m_pService;
}
void KOpenWithDialogPrivate::saveComboboxHistory()
{
KHistoryComboBox *combo = static_cast<KHistoryComboBox*>(edit->comboBox());
if (combo) {
combo->addToHistory(edit->text());
KConfigGroup cg( KGlobal::config(), QString::fromLatin1("Open-with settings") );
cg.writeEntry( "History", combo->historyItems() );
writeEntry( cg, "CompletionMode", combo->completionMode() );
// don't store the completion-list, as it contains all of KUrlCompletion's
// executables
cg.sync();
}
}
#include "kopenwithdialog.moc"
#include "kopenwithdialog_p.moc"
diff --git a/kio/kio/kfileitemactionplugin.desktop b/kio/kio/kfileitemactionplugin.desktop
index e9b6eff9cb..53404745ab 100644
--- a/kio/kio/kfileitemactionplugin.desktop
+++ b/kio/kio/kfileitemactionplugin.desktop
@@ -1,58 +1,59 @@
[Desktop Entry]
Type=ServiceType
X-KDE-ServiceType=KFileItemAction/Plugin
Comment=Plugin for the KIO file item context menu
Comment[ar]=إضافة تستخدم لقائمة السياق لعناصر ملفات KIO
Comment[bg]=Приставка за контекстно файлово меню
Comment[bs]=Dodatak za kontekstni meni KIO datotečni objekt
Comment[ca]=Connector per a l'element KIO de fitxers del menú contextual
Comment[ca@valencia]=Connector per a l'element KIO de fitxers del menú contextual
Comment[cs]=Zásuvný modul pro kontextové menu položek KIO file
Comment[da]=Plugin til kontekstmenuen for KIO-filelementer
Comment[de]=Erweiterung für das Kontextmenü eines KIO-Datei-Elements
Comment[el]=Πρόσθετο για το αρχείο KIO αντικείμενο του σχετικού μενού
Comment[en_GB]=Plugin for the KIO file item context menu
Comment[es]=Complemento para el menú de contexto de elementos de archivo KIO
Comment[et]=KIO-failielemendi kontekstimenüü plugin
Comment[eu]=KIO fitxategi elementuen testuinguru menuarentzako plugina
Comment[fi]=KIO-tiedostotietueen kontekstivalikkoliitännäinen
Comment[fr]=Module pour les éléments du menu contextuel KIO
Comment[ga]=Breiseán le haghaidh roghchlár comhthéacs comhaid in KIO
Comment[gl]=Engadido para o menú de contexto do elemento ficheiro de KIO
Comment[he]=תוסף עבור פריט תפריט הקשר של KIO
Comment[hr]=Priključak za kontekstni izbornik KIO datoteke
Comment[hu]=Bővítmény a KIO fájlelem helyi menühöz
Comment[ia]=Plugin pro le menu de contexto de elemento de file de KIO
+Comment[id]=Plugin untuk menu konteks item berkas KIO
Comment[is]=Íforrit fyrir samhengisvalmynd KIO-skjalhluta
Comment[it]=Estensione per l'elemento file KIO del menu contestuale
Comment[kk]=KIO файл контекст мәзірінің плагині
Comment[km]=កម្មវិធី​ជំនួយ​សម្រាប់​​ម៉ឺនុយ​បរិបទ​ធាតុ​​ឯកសារ KIO
Comment[ko]=KIO 파일 항목 컨텍스트 메뉴 플러그인
Comment[lv]=KIO failu vienuma konteksta izvēlens spraudnis
Comment[nb]=Programtillegg for KIO filelements kontekstmeny
Comment[nds]=Moduul för dat Dateiindrag-Rechtsklickmenü vun KDE sien In-/Utgaavmoduul
Comment[nl]=Plugin voor het contextmenu van het KIO-bestandsitem
Comment[pa]=KIO ਫਾਇਲ ਆਈਟਮ ਸਮੱਗਰੀ ਮੇਨੂ ਲਈ ਪਲੱਗਇਨ
Comment[pl]=Wtyczka do menu kontekstowego elementu pliku KIO
Comment[pt]='Plugin' para o menu de contexto do item de ficheiros do KIO
Comment[pt_BR]=Plug-in para o menu de contexto do item de arquivos do KIO
Comment[ro]=Modul pentru meniul contextual al fișierelor KIO
Comment[ru]=Расширение контекстного меню файла KIO
Comment[se]=Lassemodula KIO-fiilamerkoša konteakstafállui
Comment[sk]=Modul pre položku kontextového menu pre KIO súbor
Comment[sr]=Прикључак за К‑У/И радњу над фајловима у контекстном менију
Comment[sr@ijekavian]=Прикључак за К‑У/И радњу над фајловима у контекстном менију
Comment[sr@ijekavianlatin]=Priključak za K‑U/I radnju nad fajlovima u kontekstnom meniju
Comment[sr@latin]=Priključak za K‑U/I radnju nad fajlovima u kontekstnom meniju
Comment[sv]=Insticksprogram för KIO-filobjektets sammanhangsberoende meny
Comment[tg]=Васлкунаки менюи файли мӯҳтавои KIO
Comment[th]=ส่วนเสริม KIO สำหรับเมนูเมื่อคลิกขวาบนแฟ้ม
Comment[tr]=KIO dosya ögesi sağlık menüsü için eklenti
Comment[tt]=KIO файлның контекст меню өстәлмәсе
Comment[ug]=KIO ھۆججەت تۈرىنىڭ تىل مۇھىت قىستۇرمىسى
Comment[uk]=Додаток пункту контекстного меню KIO
Comment[vi]=Phần bổ sung cho trình đơn ngữ cảnh mục tập tin KIO
Comment[wa]=Tchôke-divins pol KIO dressêye di contecse do cayet d' fitchî
Comment[x-test]=xxPlugin for the KIO file item context menuxx
Comment[zh_CN]=KIO 文件项的上下文菜单插件
Comment[zh_TW]=KIO 檔案項目內文選單的外掛程式
diff --git a/kio/kssl/kcm/kcm_ssl.desktop b/kio/kssl/kcm/kcm_ssl.desktop
index 6ce68a96e5..1bec8d4322 100644
--- a/kio/kssl/kcm/kcm_ssl.desktop
+++ b/kio/kssl/kcm/kcm_ssl.desktop
@@ -1,166 +1,170 @@
[Desktop Entry]
Exec=kcmshell4 kcm_ssl
Icon=preferences-system-ssl
Type=Service
X-KDE-ServiceTypes=KCModule
X-KDE-Library=kcm_ssl
X-KDE-ParentApp=kcontrol
X-KDE-System-Settings-Parent-Category=network-and-connectivity
Name=SSL Preferences
Name[ar]=تفضيلات SSL
Name[bg]=Настройки на SSL
Name[bs]=SSL postavke
Name[ca]=Preferències SSL
Name[ca@valencia]=Preferències SSL
Name[cs]=Nastavení SSL
Name[da]=SSL-indstillinger
Name[de]=SSL-Einstellungen
Name[el]=Προτιμήσεις SSL
Name[en_GB]=SSL Preferences
Name[es]=Preferencias de SSL
Name[et]=SSL-i seadistused
Name[eu]=SSL hobespenak
Name[fi]=SSL:n asetukset
Name[fr]=Préférences SSL
Name[ga]=Sainroghanna SSL
Name[gl]=Preferencias de SSL
Name[he]=הגדרות SSL
Name[hr]=Postavke SSL-a
Name[hu]=SSL-beállítások
Name[ia]=Preferentias de SSL
+Name[id]=Pengaturan SSL
Name[is]=Stillingar SSL
Name[it]=Preferenze SSL
Name[ja]=SSL 設定
Name[kk]=SSL параметрлері
Name[km]=ចំណូល​ចិត្ត SSL
Name[ko]=SSL 설정
Name[lt]=SSL nustatymai
Name[lv]=SSL iestatījumi
Name[nb]=SSL-innstillinger
Name[nds]=SSL-Instellen
Name[nl]=SSL-voorkeuren
Name[pa]=SSL ਪਸੰਦ
Name[pl]=Ustawienia SSL
Name[pt]=Preferências do SSL
Name[pt_BR]=Preferências do SSL
Name[ro]=Preferințe SSL
Name[ru]=Настройка SSL
Name[se]=SSL-oidimat
Name[sk]=Nastavenie SSL
Name[sl]=Možnosti SSL
Name[sr]=Поставке ССЛ‑а
Name[sr@ijekavian]=Поставке ССЛ‑а
Name[sr@ijekavianlatin]=Postavke SSL‑a
Name[sr@latin]=Postavke SSL‑a
Name[sv]=SSL-inställningar
Name[tg]=Хусусиятҳои SSL
Name[th]=ปรับแต่ง SSL
Name[tr]=SSL Tercihleri
Name[tt]=SSL көйләүләре
Name[ug]=SSL مايىللىق
Name[uk]=Параметри SSL
Name[vi]=Tùy thích SSL
Name[wa]=Preferinces SSL
Name[x-test]=xxSSL Preferencesxx
Name[zh_CN]=SSL 首选项
Name[zh_TW]=SSL 喜好設定
Comment=SSL Versions and Certificates
Comment[ar]=إصدارات وشهادات SSL
Comment[bg]=Версии и сертификати на SSL
Comment[bs]=SSL verzije i certifikati
Comment[ca]=Versions i certificats SSL
Comment[ca@valencia]=Versions i certificats SSL
Comment[cs]=SSL verze a certifikáty
Comment[da]=SSL-versioner og -certifikater
Comment[de]=SSL-Versionen und -Zertifikate
Comment[el]=Εκδόσεις και πιστοποιητικά SSL
Comment[en_GB]=SSL Versions and Certificates
Comment[es]=Versiones y certificados SSL
Comment[et]=SSL-i versioonid ja sertifikaadid
Comment[eu]=SSL bertsioak eta ziurtagiriak
Comment[fi]=SSL:n versiot ja varmenteet
Comment[fr]=Certificats et versions SSL
Comment[ga]=Leaganacha agus Teastais SSL
Comment[gl]=Versións e certificados de SSL
Comment[he]=גרסאות SSL ואישורים
Comment[hr]=Inačice i certifikati SSL-a
Comment[hu]=SSL-verziók és tanúsítványok
Comment[ia]=Versiones de SSL e Certificatos
+Comment[id]=Versi dan Sertifikat SSL
Comment[is]=Útgáfur og skilríki SSL
Comment[it]=Versioni SSL e certificati
Comment[kk]=SSL нұсқалары мен куәліктері
Comment[km]=វិញ្ញាបនបត្រ និង​កំណែ​របស់ SSL
Comment[ko]=SSL 버전과 인증서
Comment[lt]=SSL versijos ir liudijimai
Comment[lv]=SSL versijas un sertifikāti
Comment[nb]=SSL-versjoner og sertifikater
Comment[nds]=SSL-Verschonen un Zertifikaten
Comment[nl]=SSL-versies en certificaten
Comment[pa]=SSL ਵਰਜਨ ਤੇ ਸਰਟੀਫਿਕੇਟ
Comment[pl]=Wersje SSL i certyfikaty
Comment[pt]=Versões e Certificados de SSL
Comment[pt_BR]=Versões e certificados de SSL
Comment[ro]=Certificate și versiuni SSL partener
Comment[ru]=Версии и сертификаты SSL
Comment[se]=SSL-veršuvnnat ja -duođaštusat
Comment[sk]=SSL verzie a certifikáty
Comment[sl]=Različice SSL in certifikati
Comment[sr]=Сертификати и верзије ССЛ‑а
Comment[sr@ijekavian]=Сертификати и верзије ССЛ‑а
Comment[sr@ijekavianlatin]=Sertifikati i verzije SSL‑a
Comment[sr@latin]=Sertifikati i verzije SSL‑a
Comment[sv]=SSL-versioner och -certifikat
Comment[tg]=Версияҳо ва иҷозатномаҳои SSL
Comment[th]=รุ่นของ SSL และใบรับรอง
Comment[tr]=SSL Sürümleri ve Sertifikaları
Comment[tt]=SSL версияләре һәм Таныклыклары
Comment[ug]=SSL نەشر ۋە گۇۋاھنامە
Comment[uk]=Версія і сертифікати SSL
Comment[vi]=Các phiên bản và chứng chỉ SSL
Comment[wa]=Modêyes eyet acertineures SSL
Comment[x-test]=xxSSL Versions and Certificatesxx
Comment[zh_CN]=SSL 版本和证书
Comment[zh_TW]=SSL 版本與憑證
X-KDE-Keywords=SSL,Security,Network,Protocol,Certificates,Encryption,HTTPS
X-KDE-Keywords[ar]=SSL,الأمن,الشبكة,البرتوكول,الشهادات,التشفير,HTTPS
X-KDE-Keywords[bg]=SSL,Security,Network,Protocol,Certificates,Encryption,HTTPS,Сигурност,Мрежа,Протокол,Удостоверение,Сертификат,Шифриране
X-KDE-Keywords[ca]=SSL,Seguretat,Xarxa,Protocol,Certificats,Encriptatge,HTTPS
X-KDE-Keywords[ca@valencia]=SSL,Seguretat,Xarxa,Protocol,Certificats,Encriptatge,HTTPS
X-KDE-Keywords[da]=SSL,Sikkerhed,Netværk,Protokol,Certifikater,Kryptering,HTTPS
X-KDE-Keywords[de]=ssl,sicherheit,netzwerk,netz,protokoll,zertifikat,verschlüsselung,https
+X-KDE-Keywords[el]=SSL,Ασφάλεια,Δίκτυο,Πρωτόκολλο,Πιστοποιητικά,Κρυπτογράφηση,HTTPS
X-KDE-Keywords[es]=SSL,Seguridad,Red,Protocolo,Certificados,Cifrado,HTTPS
X-KDE-Keywords[et]=SSL,turvalisus,võrk,protokoll,sertifikaadid,serdid,krüptimine,krüpto,HTTPS
X-KDE-Keywords[fi]=SSL,Turvallisuus,Verkko,Yhteyskäytäntö,Protokolla,Varmenteet,Sertifikaatit,Salaus,HTTPS
X-KDE-Keywords[fr]=SSL,Sécurité,Réseau,Protocole,Certificats,Chiffrement,HTTPS
X-KDE-Keywords[ga]=SSL,Slándáil,Líonra,Prótacal,Teastais,Criptiú,HTTPS
X-KDE-Keywords[gl]=SSL,Seguridade,Rede,Protocolo,Certificados,Cifrado,HTTPS
X-KDE-Keywords[he]=אבטחה,רשת,פרוטוקול,אישורים,הצפנהSSL,Security,Network,Protocol,Certificates,Encryption,HTTPS
X-KDE-Keywords[hu]=SSL,Biztonság,Hálózat,Protokoll,Tanúsítványok,Titkosítás,HTTPS
X-KDE-Keywords[ia]=SSL,Securitate,Rete,Protocollo,Certificatos,Cryptation,HTTPS
+X-KDE-Keywords[id]=SSL,Keamanan,Jaringan,Protokol,Sertifikat,Enkripsi,HTTPS
X-KDE-Keywords[it]=SSL,sicurezza,rete,protocollo,certificati,cifratura,HTTPS
X-KDE-Keywords[kk]=SSL,Security,Network,Protocol,Certificates,Encryption,HTTPS
X-KDE-Keywords[km]=SSL,មូលប័ត្រ​,​បណ្ដាញ​,​ពិធីការ​,វិញ្ញាបនបត្រ,​ការ​អ៊ិនគ្រីប​​,HTTPS
X-KDE-Keywords[ko]=SSL,Security,Network,Protocol,Certificates,Encryption,HTTPS,암호,네트워크,프로토콜,인증서,암호화
X-KDE-Keywords[nb]=SSL,Sikkerhet,Nettverk,Protokoll,Sertifikater,Kryptering,HTTPS
X-KDE-Keywords[nds]=SSL,Sekerheit,Nettwark,Protokoll,Zertifikaten,Verslöteln,HTTPS
X-KDE-Keywords[nl]=SSL,beveiliging,netwerk,protocol,certificaten,versleuteling,HTTPS
X-KDE-Keywords[pa]=SSL,ਸੁਰੱਖਿਆ,ਨੈੱਟਵਰਕ,ਪ੍ਰੋਟੋਕਾਲ,ਸਰਟੀਫਿਕੇਟ,ਇੰਕ੍ਰਿਪਸ਼ਨ,HTTPS
X-KDE-Keywords[pl]=SSL,Bezpieczeństwo,Sieć,Protokół,Certyfikaty,Szyfrowanie,HTTPS
X-KDE-Keywords[pt]=SSL,Segurança,Rede,Protocolo,Certificados,Encriptação,HTTPS
X-KDE-Keywords[pt_BR]=SSL,segurança,rede,protocolo,certificados,criptografia,HTTPS
X-KDE-Keywords[ro]=SSL,Securitate,Rețea,Protocol,Certificate,Criptare,HTTPS
X-KDE-Keywords[sk]=SSL,Security,Network,Protocol,Certificates,Encryption,HTTPS
X-KDE-Keywords[sr]=SSL,Security,Network,Protocol,Certificates,Encryption,HTTPS,ССЛ,безбедност,мрежа,протокол,сертификат,шифровање,ХТТПС
X-KDE-Keywords[sr@ijekavian]=SSL,Security,Network,Protocol,Certificates,Encryption,HTTPS,ССЛ,безбедност,мрежа,протокол,сертификат,шифровање,ХТТПС
X-KDE-Keywords[sr@ijekavianlatin]=SSL,Security,Network,Protocol,Certificates,Encryption,HTTPS,SSL,bezbednost,mreža,protokol,sertifikat,šifrovanje,HTTPS
X-KDE-Keywords[sr@latin]=SSL,Security,Network,Protocol,Certificates,Encryption,HTTPS,SSL,bezbednost,mreža,protokol,sertifikat,šifrovanje,HTTPS
X-KDE-Keywords[sv]=SSL,Säkerhet,Nätverk,Protokoll,Certifikat,Kryptering,HTTPS
X-KDE-Keywords[tg]=SSL,Амният,Шабака,Протокол,иҷозатномаҳо,Рамзгузорӣ,HTTPS
X-KDE-Keywords[uk]=SSL,Security,Network,Protocol,Certificates,Encryption,HTTPS,захист,мережа,протокол,сертифікат,сертифікати,шифрування,криптографія
X-KDE-Keywords[vi]=SSL,Bảo mật,Mạng,Giao thức,Chứng thực,Mã hóa,HTTPS,Security,Network,Protocol,Certificates,Encryption
X-KDE-Keywords[x-test]=xxSSL,Security,Network,Protocol,Certificates,Encryption,HTTPSxx
X-KDE-Keywords[zh_CN]=SSL,Security,Network,Protocol,Certificates,Encryption,HTTPS,加密,安全,网络,协议,证书
X-KDE-Keywords[zh_TW]=SSL,Security,Network,Protocol,Certificates,Encryption,HTTPS
diff --git a/kio/misc/kmailservice.desktop b/kio/misc/kmailservice.desktop
index 716aafa313..1bb9b9c3cf 100755
--- a/kio/misc/kmailservice.desktop
+++ b/kio/misc/kmailservice.desktop
@@ -1,69 +1,70 @@
[Desktop Entry]
Type=Application
Exec=kmailservice %u
Icon=mail-message-new
MimeType=x-scheme-handler/mailto;
X-DocPath=kioslave/mailto/index.html
NoDisplay=true
# KMailService is the handler for mailto URLs
Name=KMailService
Name[ar]=KMailService
Name[bg]=KMailService
Name[bs]=KMailService
Name[ca]=KMailService
Name[ca@valencia]=KMailService
Name[cs]=Poštovní služba
Name[da]=KMailService
Name[de]=KMailService
Name[el]=Υπηρεσία Kmail
Name[en_GB]=KMailService
Name[es]=KMailService
Name[et]=KMailService
Name[eu]=KMailService
Name[fa]=KMailService
Name[fi]=KMailService
Name[fr]=KMailService
Name[ga]=KMailService
Name[gl]=KMailService
Name[he]=KMailService
Name[hr]=KMailService
Name[hu]=KMailService
Name[ia]=KMailService (Servicio de KMail)
+Name[id]=KMailService
Name[is]=KMailÞjónusta
Name[it]=KMailService
Name[ja]=KMailService
Name[kk]=KMailService
Name[km]=KMailService
Name[ko]=KMailService
Name[lt]=KMail tarnyba
Name[lv]=KMailService
Name[nb]=KMail-posttjeneste
Name[nds]=KMailService
Name[nl]=KMailService
Name[pa]=KMailService
Name[pl]=Usługa KMail
Name[pt]=KMailService
Name[pt_BR]=KMailService
Name[ro]=Serviciu KMail
Name[ru]=KMailService
Name[se]=KMailService
Name[si]=KMailService
Name[sk]=KMailService
Name[sr]=Сервис за пошту
Name[sr@ijekavian]=Сервис за пошту
Name[sr@ijekavianlatin]=Servis za poštu
Name[sr@latin]=Servis za poštu
Name[sv]=Kmail-tjänst
Name[tg]=Хадамоти KMailService
Name[th]=KMailService
Name[tr]=KMailService
Name[tt]=KMailService
Name[ug]=KMailService
Name[uk]=Служба пошти KMail
Name[vi]=KMailService
Name[wa]=KMailService
Name[x-test]=xxKMailServicexx
Name[zh_CN]=KMailService
Name[zh_TW]=KMail服務
diff --git a/kio/misc/ktelnetservice.desktop b/kio/misc/ktelnetservice.desktop
index 8764a3398b..8508ad3b18 100755
--- a/kio/misc/ktelnetservice.desktop
+++ b/kio/misc/ktelnetservice.desktop
@@ -1,67 +1,68 @@
[Desktop Entry]
Type=Application
Exec=ktelnetservice %u
Icon=utilities-terminal
MimeType=x-scheme-handler/telnet;x-scheme-handler/rlogin;x-scheme-handler/ssh;
X-DocPath=kioslave/telnet/index.html
NoDisplay=true
Name=KTelnetService
Name[ar]=KTelnetService
Name[bg]=KTelnetService
Name[bs]=KTelnetService
Name[ca]=KTelnetService
Name[ca@valencia]=KTelnetService
Name[cs]=KTelnetService
Name[da]=KTelnetService
Name[de]=KTelnetService
Name[el]=Υπηρεσία KTelnet
Name[en_GB]=KTelnetService
Name[es]=KTelnetService
Name[et]=KTelnetService
Name[eu]=KTelnetService
Name[fi]=KTelnetService
Name[fr]=KTelnetService
Name[ga]=KTelnetService
Name[gl]=KTelnetService
Name[he]=KTelnetService
Name[hr]=KTelnetService
Name[hu]=KTelnetService
Name[ia]=KTelnetService (Servicio de KTelnet)
+Name[id]=KTelnetService
Name[is]=KTelnetÞjónusta
Name[it]=KTelnetService
Name[ja]=KTelnetService
Name[kk]=KTelnetService
Name[km]=KTelnetService
Name[ko]=KTelnetService
Name[lt]=KTelnetTarnyba
Name[lv]=KTelnetService
Name[nb]=KTelnetService
Name[nds]=KTelnetService
Name[nl]=KTelnetService
Name[pa]=KTelnetService
Name[pl]=Usługa KTelnet
Name[pt]=KTelnetService
Name[pt_BR]=KTelnetService
Name[ro]=Serviciu KTelnet
Name[ru]=Служба telnet KTelnetService
Name[se]=KTelnetService
Name[si]=KTelnetService
Name[sk]=KTelnetService
Name[sr]=KMailService
Name[sr@ijekavian]=KMailService
Name[sr@ijekavianlatin]=KMailService
Name[sr@latin]=KMailService
Name[sv]=Ktelnet-tjänst
Name[tg]=Хадамоти KTelnetService
Name[th]=KTelnetService
Name[tr]=KTelnetService
Name[tt]=KTelnetService
Name[ug]=KTelnetService
Name[uk]=Служба telnet KTelnet
Name[vi]=KTelnetService
Name[wa]=KTelnetService
Name[x-test]=xxKTelnetServicexx
Name[zh_CN]=KTelnetService
Name[zh_TW]=KTelnet服務
diff --git a/kparts/genericfactory.h b/kparts/genericfactory.h
index 2e46d3421e..c4373e5b94 100644
--- a/kparts/genericfactory.h
+++ b/kparts/genericfactory.h
@@ -1,215 +1,215 @@
/* This file is part of the KDE project
Copyright (C) 2001 Simon Hausmann <hausmann@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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 Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to the
Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KPARTS_GENERICFACTORY_H
#define KPARTS_GENERICFACTORY_H
#include <kparts/factory.h>
#include <kparts/part.h>
#include <kgenericfactory.h>
#include <kaboutdata.h>
#include <kdebug.h>
namespace KParts
{
/**
* @internal
*/
template <class T>
class GenericFactoryBase : public KParts::Factory
{
public:
GenericFactoryBase()
{
if ( s_self )
{
kWarning() << "KParts::GenericFactory instantiated more than once!";
}
s_self = this;
}
virtual ~GenericFactoryBase()
{
delete s_aboutData;
delete s_componentData;
s_aboutData = 0;
s_componentData = 0;
s_self = 0;
}
static const KComponentData &componentData();
static KAboutData *aboutData();
virtual KComponentData partComponentData()
{
return componentData();
}
protected:
virtual KComponentData *createComponentData()
{
return new KComponentData(aboutData());
}
private:
static GenericFactoryBase<T> *s_self;
static KComponentData *s_componentData;
static KAboutData *s_aboutData;
};
/**
* A template for a KParts::Factory implementation. It implements the pure virtual
* createPartObject method by instantiating the template argument when requested
* through the className field. In addition it is a container for a part's KComponentData
* object, by providing a static KComponentData componentData() method.
*
* The template argument has to inherit from KParts::Part and has to implement two methods:
* 1) There needs to be a public constructor with the following signature:
* MyPart( QWidget *parentWidget, QObject *parent, const QStringList& args )
*
* 2) It needs to provide one static method to create a KAboutData object per
* request, holding information about the component's name, its authors, license, etc.
* The signature of that static method has to be
* KAboutData *createAboutData()
*
* The template will take care of memory management of the KComponentData and the KAboutData object,
* meaning ownership of what createAboutData returns is passed to the caller (this template) .
*
* For advanced use you can also inherit from the template and re-implement additionally the
* virtual KComponentData createComponentData() method, for example in case you want to extend the
* paths of your instance's KStandardDirs object.
*
* If a KParts::ReadOnlyPart is requested through this factory and the template argument
* implements a KParts::ReadWritePart then setReadWrite( false ) will automatically be
* called in createPartObject.
*
* Use the factory through the K_EXPORT_COMPONENT_FACTORY macro, like that:
* \code
* typedef KParts::GenericFactory&lt;YourKPart&gt; YourKPartFactory;
* K_EXPORT_COMPONENT_FACTORY( yourlibrary, YourKPartFactory )
* \endcode
* yourlibrary is the library name that you compiled your KPart into.
*/
template <class T>
- class GenericFactory : public GenericFactoryBase<T>
+ class KDE_DEPRECATED GenericFactory : public GenericFactoryBase<T>
{
public:
GenericFactory() { }
virtual KParts::Part *createPartObject( QWidget *parentWidget,
QObject *parent,
const char *className,
const QStringList &args )
{
T *part = KDEPrivate::ConcreteFactory<T>::create( parentWidget,
parent,
className,
args );
if ( part && !qstrcmp( className, "KParts::ReadOnlyPart" ) )
{
KParts::ReadWritePart *rwp = dynamic_cast<KParts::ReadWritePart *>( part );
if ( rwp )
rwp->setReadWrite( false );
}
return part;
}
};
template <class T1, class T2>
class GenericFactory< KTypeList<T1, T2> > : public GenericFactoryBase<T1>
{
public:
GenericFactory() { }
virtual KParts::Part *createPartObject( QWidget *parentWidget,
QObject *parent,
const char *className,
const QStringList &args )
{
QObject *object = KDEPrivate::MultiFactory< KTypeList<T1, T2> >::create( parentWidget,
parent,
className,
args );
// (this cast is guaranteed to work...)
KParts::Part *part = dynamic_cast<KParts::Part *>( object );
if ( part && !qstrcmp( className, "KParts::ReadOnlyPart" ) )
{
KParts::ReadWritePart *rwp = dynamic_cast<KParts::ReadWritePart *>( part );
if ( rwp )
rwp->setReadWrite( false );
}
return part;
}
};
/**
* @internal
*/
template <class T>
GenericFactoryBase<T> *GenericFactoryBase<T>::s_self = 0;
/**
* @internal
*/
template <class T>
KComponentData *GenericFactoryBase<T>::s_componentData = 0;
/**
* @internal
*/
template <class T>
KAboutData *GenericFactoryBase<T>::s_aboutData = 0;
/**
* @internal
*/
template <class T>
const KComponentData &GenericFactoryBase<T>::componentData()
{
if ( !s_componentData )
{
if ( s_self )
s_componentData = s_self->createComponentData();
else
s_componentData = new KComponentData(aboutData());
}
return *s_componentData;
}
/**
* @internal
*/
template <class T>
KAboutData *GenericFactoryBase<T>::aboutData()
{
if ( !s_aboutData )
s_aboutData = T::createAboutData();
return s_aboutData;
}
}
#endif
/**
* vim: et sw=4
*/
diff --git a/kparts/htmlextension.h b/kparts/htmlextension.h
index 9833d9a2cc..b91f2037a9 100644
--- a/kparts/htmlextension.h
+++ b/kparts/htmlextension.h
@@ -1,241 +1,298 @@
/* This file is part of the KDE project
Copyright (C) 2010 David Faure <faure@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License as published by the Free Software Foundation; either
version 2 of the License, or (at your option) any later version.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#ifndef KPARTS_HTMLEXTENSION_H
#define KPARTS_HTMLEXTENSION_H
#include <QtCore/QSharedDataPointer>
#include <QtCore/QObject>
#include <kparts/kparts_export.h>
class KUrl;
namespace KParts
{
class ReadOnlyPart;
class HtmlExtensionPrivate;
class SelectorInterfacePrivate;
/**
* @short an extension for KParts to provide HTML-related features
*
* Use qobject_cast to cast the extension to interesting interfaces, like
* qobject_cast<KParts::SelectorInterface>.
*
* @since 4.6
*/
class KPARTS_EXPORT HtmlExtension : public QObject
{
Q_OBJECT
public:
HtmlExtension(KParts::ReadOnlyPart* parent);
~HtmlExtension();
/**
* Queries @p obj for a child object which inherits from this
* HtmlExtension class.
*/
static HtmlExtension *childObject( QObject *obj );
/**
* Returns the current base url of the part that implements this extension.
*
* This function is mostly used to resolve any relative URLs that might be
* returned when querying the part for links.
*/
virtual KUrl baseUrl() const = 0;
/**
* Returns true if portions of the content in the part that implements
* this extension are selected.
*
* By default this function returns false.
*/
virtual bool hasSelection() const;
private:
// for future extensions
HtmlExtensionPrivate* const d;
};
/**
* Optional base class for HtmlExtension-derived classes
* Provides DOM Selector like API: querySelector and querySelectorAll,
* in order to find specific elements in an HTML document.
*
* Example:
* <code>
* const QList<SelectorInterface::Element> elements = selectorInterface->querySelectorAll("head > link[rel=\"alternate\"]");
* </code>
*/
class KPARTS_EXPORT SelectorInterface
{
public:
class ElementPrivate;
class Element;
/**
* Query methods.
*/
enum QueryMethod {
None = 0x00, /*!< Quering is not possible. */
EntireContent = 0x01, /*!< Query or can query the entire content. */
SelectedContent = 0x02 /*!< Query or can query only the user selected content, if any. */
};
Q_DECLARE_FLAGS(QueryMethods, QueryMethod)
/**
* Destructor
*/
virtual ~SelectorInterface() {}
/**
* Returns the supported query methods.
*
* By default this function returns None.
*
* @see QueryMethod
*/
virtual QueryMethods supportedQueryMethods() const;
/**
* Returns the first (in document order) element in this fragment matching
* the given CSS selector @p query and querying method @p method.
*
* Note that since the returned item is static snapshot, i.e. not live, it
* will not be updated when the document changes.
*
* If the quering method specified by @p method is not supported or cannot be
* handled, then a null element is returned.
*
* @see supportedQueryMethods
* @see QueryMethod
*/
virtual Element querySelector(const QString& query, QueryMethod method) const = 0;
/**
* Returns all (in document order) elements in this fragment matching the
* given CSS selector @p query and querying method @p method.
*
* Note that since the returned list is static snapshot, i.e. not live, it
* will not be updated when the document changes.
*
* If the quering method specified by @p method is not supported or cannot be
* handled, then an empty list is returned.
*
* @see supportedQueryMethods
* @see QueryMethod
*/
virtual QList<Element> querySelectorAll(const QString& query, QueryMethod method) const = 0;
class KPARTS_EXPORT Element {
public:
/**
* Constructor
*/
Element();
/**
* Copy constructor
*/
Element(const Element& other);
/**
* Destructor
*/
~Element();
/**
* Returns true if the element is null ; otherwise returns false.
*/
bool isNull() const;
/**
* Sets the tag name of this element.
*/
void setTagName(const QString& tag);
/**
* Returns the tag name of this element.
*/
QString tagName() const;
/**
* Adds an attribute with the given name and value.
* If an attribute with the same name exists, its value is replaced by value.
*/
void setAttribute(const QString& name, const QString& value);
/**
* Returns the list of attributes in this element.
*/
QStringList attributeNames() const;
/**
* Returns the attribute with the given name. If the attribute does not exist, defaultValue is returned.
*/
QString attribute(const QString& name, const QString& defaultValue = QString()) const;
/**
* Returns true if the attribute with the given @p name exists.
*/
bool hasAttribute(const QString& name) const;
// No namespace support yet, could be added with attributeNS, setAttributeNS
/**
* Swaps the contents of @p other with the contents of this.
*/
void swap( Element& other ) {
d.swap( other.d );
}
/**
* Assignment operator
*/
Element& operator=(const Element& other) {
if ( this != &other ) {
Element copy( other );
swap( copy );
}
return *this;
}
private:
QSharedDataPointer<ElementPrivate> d;
};
};
+/**
+ * @short An interface for modifying the settings of browser engines.
+ *
+ * This interface provides a generic means for querying or changing the
+ * settings of browser engines that implement it.
+ *
+ * To use this class simply cast an instance of the HTMLExtension object
+ * using qobject_cast<KParts::HtmlSettingsInterface>.
+ *
+ * Example:
+ * <code>
+ * KParts::HTMLExtension* extension = KParts::HTMLExtension::childObject(part);
+ * KParts::HtmlSettingsInterface* settings = qobject_cast&lt;KParts::HtmlSettingsInterface&gt;(extension);
+ * const bool autoLoadImages = settings->attribute(KParts::AutoLoadImages);
+ * </code>
+ *
+ * @since 4.9.0
+ */
+class KPARTS_EXPORT HtmlSettingsInterface
+{
+public:
+ /**
+ * Settings attribute types.
+ */
+ enum HtmlSettingsType {
+ AutoLoadImages,
+ DnsPrefetchEnabled,
+ JavaEnabled,
+ JavascriptEnabled,
+ MetaRefreshEnabled,
+ PluginsEnabled,
+ PrivateBrowsingEnabled,
+ OfflineStorageDatabaseEnabled,
+ OfflineWebApplicationCacheEnabled,
+ LocalStorageEnabled,
+ UserDefinedStyleSheetURL
+ };
+
+ /**
+ * Destructor
+ */
+ virtual ~HtmlSettingsInterface() {}
+
+ /**
+ * Returns the value of the browser engine's attribute @p type.
+ */
+ virtual QVariant htmlSettingsProperty(HtmlSettingsType type) const = 0;
+
+ /**
+ * Sets the value of the browser engine's attribute @p type to @p value.
+ */
+ virtual bool setHtmlSettingsProperty(HtmlSettingsType type, const QVariant& value) = 0;
+};
+
} // namespace KParts
inline void qSwap( KParts::SelectorInterface::Element & lhs, KParts::SelectorInterface::Element & rhs )
{
lhs.swap( rhs );
}
Q_DECLARE_OPERATORS_FOR_FLAGS(KParts::SelectorInterface::QueryMethods)
Q_DECLARE_TYPEINFO(KParts::SelectorInterface::Element, Q_MOVABLE_TYPE);
Q_DECLARE_INTERFACE(KParts::SelectorInterface,
"org.kde.KParts.SelectorInterface")
+Q_DECLARE_INTERFACE(KParts::HtmlSettingsInterface,
+ "org.kde.KParts.HtmlSettingsInterface")
+
#endif /* KPARTS_HTMLEXTENSION_H */
diff --git a/nepomuk/core/resourcedata.cpp b/nepomuk/core/resourcedata.cpp
index dde4fe13ec..abe55ea710 100644
--- a/nepomuk/core/resourcedata.cpp
+++ b/nepomuk/core/resourcedata.cpp
@@ -1,827 +1,838 @@
/*
* This file is part of the Nepomuk KDE project.
* Copyright (C) 2006-2010 Sebastian Trueg <trueg@kde.org>
* Copyright (C) 2010-2012 Vishesh Handa <handa.vish@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "resourcedata.h"
#include "resourcemanager.h"
#include "resourcemanager_p.h"
#include "resource.h"
#include "tools.h"
#include "nie.h"
#include "nfo.h"
#include "pimo.h"
#include "nepomukmainmodel.h"
#include "dbusconnectionpool.h"
#include "class.h"
#include "dbustypes.h"
#include "resourcewatcher.h"
#include <Soprano/Statement>
#include <Soprano/StatementIterator>
#include <Soprano/QueryResultIterator>
#include <Soprano/Model>
#include <Soprano/Vocabulary/RDFS>
#include <Soprano/Vocabulary/RDF>
#include <Soprano/Vocabulary/Xesam>
#include <Soprano/Vocabulary/NAO>
#include <QtCore/QFile>
#include <QtCore/QDateTime>
#include <QtCore/QMutexLocker>
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusInterface>
#include <QtDBus/QDBusReply>
#include <kdebug.h>
#include <kurl.h>
#include <kcomponentdata.h>
using namespace Soprano;
#define MAINMODEL (m_rm->m_manager->mainModel())
Nepomuk::ResourceData::ResourceData( const QUrl& uri, const QUrl& kickOffUri, const QUrl& type, ResourceManagerPrivate* rm )
: m_uri(uri),
m_mainType( type ),
m_modificationMutex(QMutex::Recursive),
m_cacheDirty(false),
+ m_addedToWatcher(false),
m_pimoThing(0),
m_groundingOccurence(0),
m_rm(rm)
{
if( m_mainType.isEmpty() ) {
m_mainType = Soprano::Vocabulary::RDFS::Resource();
}
m_types << m_mainType;
if( m_rm->dataCacheFull() )
m_rm->cleanupCache();
m_rm->dataCnt.ref();
if( !uri.isEmpty() ) {
m_kickoffUris.insert( uri );
}
if( !kickOffUri.isEmpty() ) {
m_kickoffUris.insert( kickOffUri );
if( kickOffUri.scheme().isEmpty() ) {
// Label
const QString label = kickOffUri.toString();
m_cache.insert( Soprano::Vocabulary::NAO::identifier(), label );
}
else if( kickOffUri.scheme() != QLatin1String("nepomuk") ) {
// It's probably the nie:url
m_cache.insert( Nepomuk::Vocabulary::NIE::url(), kickOffUri );
}
}
m_rm->addToKickOffList( this, m_kickoffUris );
}
Nepomuk::ResourceData::~ResourceData()
{
resetAll(true);
m_rm->dataCnt.deref();
}
bool Nepomuk::ResourceData::isFile()
{
return( m_uri.scheme() == QLatin1String("file") ||
m_nieUrl.scheme() == QLatin1String("file") ||
(!m_kickoffUris.isEmpty() && (*m_kickoffUris.begin()).scheme() == QLatin1String("file")) ||
constHasType( Soprano::Vocabulary::Xesam::File() ) ||
constHasType( Nepomuk::Vocabulary::NFO::FileDataObject() ) );
}
QUrl Nepomuk::ResourceData::uri() const
{
return m_uri;
}
QUrl Nepomuk::ResourceData::type()
{
load();
return m_mainType;
}
QList<QUrl> Nepomuk::ResourceData::allTypes()
{
load();
return m_types;
}
void Nepomuk::ResourceData::setTypes( const QList<QUrl>& types )
{
store();
QMutexLocker lock(&m_modificationMutex);
// reset types
m_types.clear();
m_mainType = Soprano::Vocabulary::RDFS::Resource();
QList<Node> nodes;
// load types (and set maintype)
foreach( const QUrl& url, types ) {
loadType( url );
nodes << Node( url );
}
// update the data store
setProperty(Soprano::Vocabulary::RDF::type(), Nepomuk::Variant(types) );
}
void Nepomuk::ResourceData::resetAll( bool isDelete )
{
// remove us from all caches (store() will re-insert us later if necessary)
m_rm->mutex.lock();
+
+ // IMPORTANT:
+ // Remove from the kickOffList before removing from the resource watcher
+ // This is required cause otherwise the Resource::fromResourceUri creates a new
+ // resource which is correctly identified to the ResourceData (this), and it is
+ // then deleted, which calls resetAll and this cycle continues.
+ Q_FOREACH( const KUrl& uri, m_kickoffUris )
+ m_rm->m_uriKickoffData.remove( uri );
+
if( !m_uri.isEmpty() ) {
m_rm->m_initializedData.remove( m_uri );
- if( m_rm->m_watcher ) {
- m_rm->m_watcher->removeResource(m_uri);
+ if( m_rm->m_watcher && m_addedToWatcher ) {
+ m_rm->m_watcher->removeResource(Resource::fromResourceUri(m_uri));
+ m_addedToWatcher = false;
}
}
- Q_FOREACH( const KUrl& uri, m_kickoffUris )
- m_rm->m_uriKickoffData.remove( uri );
m_rm->mutex.unlock();
// reset all variables
m_uri = QUrl();
m_nieUrl = KUrl();
m_kickoffUris.clear();
m_cache.clear();
m_cacheDirty = false;
m_types.clear();
delete m_pimoThing;
m_pimoThing = 0;
m_groundingOccurence = 0;
// when we are being deleted the value of m_mainType is not important
// anymore. Also since ResourceManager is a global static it might be
// deleted after the global static behind Soprano::Vocabulary::RDFS
// which results in a crash.
if( !isDelete )
m_mainType = Soprano::Vocabulary::RDFS::Resource();
}
QHash<QUrl, Nepomuk::Variant> Nepomuk::ResourceData::allProperties()
{
load();
return m_cache;
}
bool Nepomuk::ResourceData::hasProperty( const QUrl& uri )
{
load();
QHash<QUrl, Variant>::const_iterator it = m_cache.constFind( uri );
if( it == m_cache.constEnd() )
return false;
return true;
}
bool Nepomuk::ResourceData::hasProperty( const QUrl& p, const Variant& v )
{
QHash<QUrl, Variant>::const_iterator it = m_cache.constFind( p );
if( it == m_cache.constEnd() )
return false;
QList<Variant> thisVals = it.value().toVariantList();
QList<Variant> vals = v.toVariantList();
Q_FOREACH( const Variant& val, vals ) {
if( !thisVals.contains(val) )
return false;
}
return true;
}
bool Nepomuk::ResourceData::hasType( const QUrl& uri )
{
load();
return constHasType( uri );
}
bool Nepomuk::ResourceData::constHasType( const QUrl& uri ) const
{
// we need to protect the reading, too. setTypes may be triggered from another thread
QMutexLocker lock(&m_modificationMutex);
Types::Class requestedType( uri );
for ( QList<QUrl>::const_iterator it = m_types.constBegin();
it != m_types.constEnd(); ++it ) {
Types::Class availType( *it );
if ( availType == requestedType ||
availType.isSubClassOf( requestedType ) ) {
return true;
}
}
return false;
}
Nepomuk::Variant Nepomuk::ResourceData::property( const QUrl& uri )
{
load();
// we need to protect the reading, too. load my be triggered from another thread's
// connection to a Soprano statement signal
QMutexLocker lock(&m_modificationMutex);
QHash<QUrl, Variant>::const_iterator it = m_cache.constFind( uri );
if ( it == m_cache.constEnd() ) {
return Variant();
}
else {
return *it;
}
}
bool Nepomuk::ResourceData::store()
{
QMutexLocker lock(&m_modificationMutex);
if ( m_uri.isEmpty() ) {
QMutexLocker rmlock(&m_rm->mutex);
if ( m_nieUrl.isValid() &&
m_nieUrl.isLocalFile() &&
m_mainType == Soprano::Vocabulary::RDFS::Resource() ) {
m_mainType = Nepomuk::Vocabulary::NFO::FileDataObject();
m_types << m_mainType;
}
QDBusConnection bus = QDBusConnection::sessionBus();
QDBusMessage msg = QDBusMessage::createMethodCall( QLatin1String("org.kde.nepomuk.DataManagement"),
QLatin1String("/datamanagement"),
QLatin1String("org.kde.nepomuk.DataManagement"),
QLatin1String("createResource") );
QString app = KGlobal::mainComponent().componentName();
QVariantList arguments;
//FIXME: Maybe we should be setting the 'label' over here.
arguments << DBus::convertUriList(m_types) << QString() << QString() << app;
msg.setArguments( arguments );
QDBusMessage reply = bus.call( msg );
if( reply.type() == QDBusMessage::ErrorMessage ) {
//TODO: Set the error somehow
kWarning() << reply.errorMessage();
return false;
}
else if( reply.type() == QDBusMessage::ReplyMessage ) {
m_uri = reply.arguments().at(0).toUrl();
}
// Add us to the initialized data, i.e. make us "valid"
m_rm->m_initializedData.insert( m_uri, this );
// each initialized resource has to be in a kickoff list
// thus, we make sure that is the case.
if( m_kickoffUris.isEmpty() ) {
m_kickoffUris.insert( m_uri );
m_rm->addToKickOffList( this, m_kickoffUris );
}
// store our grounding occurrence in case we are a thing created by the pimoThing() method
if( m_groundingOccurence ) {
if( m_groundingOccurence != this )
m_groundingOccurence->store();
setProperty(Vocabulary::PIMO::groundingOccurrence(), Variant(m_groundingOccurence->uri()) );
}
foreach( const KUrl& url, m_kickoffUris ) {
if( url.scheme().isEmpty() )
setProperty( Soprano::Vocabulary::NAO::identifier(), Variant(url.url()) );
else
setProperty( Nepomuk::Vocabulary::NIE::url(), Variant(url.url()) );
}
}
return true;
}
void Nepomuk::ResourceData::loadType( const QUrl& storedType )
{
if ( !m_types.contains( storedType ) ) {
m_types << storedType;
}
if ( m_mainType == Soprano::Vocabulary::RDFS::Resource() ) {
Q_ASSERT( !storedType.isEmpty() );
m_mainType = storedType;
}
else {
Types::Class currentTypeClass = m_mainType;
Types::Class storedTypeClass = storedType;
// Keep the type that is further down the hierarchy
if ( storedTypeClass.isSubClassOf( currentTypeClass ) ) {
m_mainType = storedTypeClass.uri();
}
else {
// This is a little convenience hack since the user is most likely
// more interested in the file content than the actual file
Types::Class xesamContentClass( Soprano::Vocabulary::Xesam::Content() );
if ( m_mainType == Soprano::Vocabulary::Xesam::File() &&
( storedTypeClass == xesamContentClass ||
storedTypeClass.isSubClassOf( xesamContentClass ) ) ) {
m_mainType = storedTypeClass.uri();
}
else {
// the same is true for nie:DataObject vs. nie:InformationElement
Types::Class nieInformationElementClass( Vocabulary::NIE::InformationElement() );
Types::Class nieDataObjectClass( Vocabulary::NIE::DataObject() );
if( ( currentTypeClass == nieDataObjectClass ||
currentTypeClass.isSubClassOf( nieDataObjectClass ) ) &&
( storedTypeClass == nieInformationElementClass ||
storedTypeClass.isSubClassOf( nieInformationElementClass ) ) ) {
m_mainType = storedTypeClass.uri();
}
}
}
}
}
bool Nepomuk::ResourceData::load()
{
QMutexLocker lock(&m_modificationMutex);
if ( m_cacheDirty ) {
m_cache.clear();
+ if(!m_rm->m_watcher) {
+ m_rm->m_watcher = new ResourceWatcher(m_rm->m_manager);
+ QObject::connect( m_rm->m_watcher, SIGNAL(propertyAdded(Nepomuk::Resource, Nepomuk::Types::Property, QVariant)),
+ m_rm->m_manager, SLOT(slotPropertyAdded(Nepomuk::Resource, Nepomuk::Types::Property, QVariant)) );
+ QObject::connect( m_rm->m_watcher, SIGNAL(propertyRemoved(Nepomuk::Resource, Nepomuk::Types::Property, QVariant)),
+ m_rm->m_manager, SLOT(slotPropertyRemoved(Nepomuk::Resource, Nepomuk::Types::Property, QVariant)) );
+ m_rm->m_watcher->addResource( Nepomuk::Resource::fromResourceUri(m_uri) );
+ m_rm->m_watcher->start();
+ }
+ else {
+ m_rm->m_watcher->addResource( Nepomuk::Resource::fromResourceUri(m_uri) );
+ }
+ m_addedToWatcher = true;
+
if ( m_uri.isValid() ) {
//
// We exclude properties that are part of the inference graph
// It would only pollute the user interface
//
Soprano::QueryResultIterator it = MAINMODEL->executeQuery(QString("select distinct ?p ?o where { "
"graph ?g { %1 ?p ?o . } . FILTER(?g!=<urn:crappyinference2:inferredtriples>) . "
"}").arg(Soprano::Node::resourceToN3(m_uri)),
Soprano::Query::QueryLanguageSparql);
while ( it.next() ) {
QUrl p = it["p"].uri();
Soprano::Node o = it["o"];
if ( p == Soprano::Vocabulary::RDF::type() ) {
if ( o.isResource() ) {
loadType( o.uri() );
}
}
else {
Nepomuk::Variant var = Variant::fromNode( o );
updateKickOffLists( p, var );
m_cache[p].append( var );
}
}
m_cacheDirty = false;
delete m_pimoThing;
m_pimoThing = 0;
if( hasType( Vocabulary::PIMO::Thing() ) ) {
m_pimoThing = new Thing( m_uri );
}
else {
// TODO: somehow handle pimo:referencingOccurrence and pimo:occurrence
QueryResultIterator pimoIt = MAINMODEL->executeQuery( QString( "select ?r where { ?r <%1> <%2> . }")
.arg( Vocabulary::PIMO::groundingOccurrence().toString() )
.arg( QString::fromAscii( m_uri.toEncoded() ) ),
Soprano::Query::QueryLanguageSparql );
if( pimoIt.next() ) {
m_pimoThing = new Thing( pimoIt.binding("r").uri() );
}
}
return true;
}
else {
return false;
}
}
else {
return true;
}
}
void Nepomuk::ResourceData::setProperty( const QUrl& uri, const Nepomuk::Variant& value )
{
Q_ASSERT( uri.isValid() );
if( store() ) {
// step 0: make sure this resource is in the store
QMutexLocker lock(&m_modificationMutex);
// update the store
QDBusConnection bus = QDBusConnection::sessionBus();
QDBusMessage msg = QDBusMessage::createMethodCall( QLatin1String("org.kde.nepomuk.DataManagement"),
QLatin1String("/datamanagement"),
QLatin1String("org.kde.nepomuk.DataManagement"),
QLatin1String("setProperty") );
QVariantList varList;
foreach( const Nepomuk::Variant var, value.toVariantList() ) {
// make sure resource values are in the store
if( var.simpleType() == qMetaTypeId<Resource>() ) {
var.toResource().m_data->store();
varList << var.toUrl();
}
else {
varList << var.variant();
}
}
msg.setArguments( QVariantList()
<< DBus::convertUriList(QList<QUrl>() << m_uri)
<< DBus::convertUri(uri)
<< QVariant(DBus::normalizeVariantList(varList))
<< KGlobal::mainComponent().componentName() );
QDBusMessage reply = bus.call( msg );
if( reply.type() == QDBusMessage::ErrorMessage ) {
//TODO: Set the error somehow
kWarning() << reply.errorMessage();
return;
}
// update the cache for now
if( value.isValid() )
m_cache[uri] = value;
else
m_cache.remove(uri);
// update the kickofflists
updateKickOffLists( uri, value );
}
}
void Nepomuk::ResourceData::addProperty( const QUrl& uri, const Nepomuk::Variant& value )
{
Q_ASSERT( uri.isValid() );
if( value.isValid() && store() ) {
// step 0: make sure this resource is in the store
QMutexLocker lock(&m_modificationMutex);
// update the store
QDBusConnection bus = QDBusConnection::sessionBus();
QDBusMessage msg = QDBusMessage::createMethodCall( QLatin1String("org.kde.nepomuk.DataManagement"),
QLatin1String("/datamanagement"),
QLatin1String("org.kde.nepomuk.DataManagement"),
QLatin1String("addProperty") );
QVariantList varList;
foreach( const Nepomuk::Variant var, value.toVariantList() ) {
// make sure resource values are in the store
if( var.simpleType() == qMetaTypeId<Resource>() ) {
var.toResource().m_data->store();
varList << var.toUrl();
}
else {
varList << var.variant();
}
}
msg.setArguments( QVariantList()
<< DBus::convertUriList(QList<QUrl>() << m_uri)
<< DBus::convertUri(uri)
<< QVariant(DBus::normalizeVariantList(varList))
<< KGlobal::mainComponent().componentName() );
QDBusMessage reply = bus.call( msg );
if( reply.type() == QDBusMessage::ErrorMessage ) {
//TODO: Set the error somehow
kWarning() << reply.errorMessage();
return;
}
// update the cache for now
if( value.isValid() )
m_cache[uri].append(value);
// update the kickofflists
updateKickOffLists( uri, value );
}
}
void Nepomuk::ResourceData::removeProperty( const QUrl& uri )
{
Q_ASSERT( uri.isValid() );
if( !m_uri.isEmpty() ) {
QMutexLocker lock(&m_modificationMutex);
QDBusConnection bus = QDBusConnection::sessionBus();
QDBusMessage msg = QDBusMessage::createMethodCall( QLatin1String("org.kde.nepomuk.DataManagement"),
QLatin1String("/datamanagement"),
QLatin1String("org.kde.nepomuk.DataManagement"),
QLatin1String("removeProperties") );
msg.setArguments( QVariantList()
<< DBus::convertUri(m_uri)
<< DBus::convertUri(uri)
<< KGlobal::mainComponent().componentName() );
QDBusMessage reply = bus.call( msg );
if( reply.type() == QDBusMessage::ErrorMessage ) {
//TODO: Set the error somehow
kWarning() << reply.errorMessage();
return;
}
// Update the cache
m_cache.remove( uri );
// update the kickofflists
updateKickOffLists( uri, Variant() );
}
}
void Nepomuk::ResourceData::remove( bool recursive )
{
Q_UNUSED(recursive)
QMutexLocker lock(&m_modificationMutex);
if( !m_uri.isEmpty() ) {
QDBusConnection bus = QDBusConnection::sessionBus();
QDBusMessage msg = QDBusMessage::createMethodCall( QLatin1String("org.kde.nepomuk.DataManagement"),
QLatin1String("/datamanagement"),
QLatin1String("org.kde.nepomuk.DataManagement"),
QLatin1String("removeResources") );
// TODO: Set the flag over here
msg.setArguments( QVariantList()
<< DBus::convertUri(m_uri)
<< 0 /* no flags */
<< KGlobal::mainComponent().componentName());
QDBusMessage reply = bus.call( msg );
if( reply.type() == QDBusMessage::ErrorMessage ) {
//TODO: Set the error somehow
kWarning() << reply.errorMessage();
return;
}
}
resetAll();
}
bool Nepomuk::ResourceData::exists()
{
if( m_uri.isValid() ) {
const QString query = QString::fromLatin1("ask { %1 ?p ?o . }")
.arg( Soprano::Node::resourceToN3(m_uri) );
return MAINMODEL->executeQuery( query, Soprano::Query::QueryLanguageSparql ).boolValue();
}
else {
return false;
}
}
bool Nepomuk::ResourceData::isValid() const
{
return( !m_mainType.isEmpty() && ( !m_uri.isEmpty() || !m_kickoffUris.isEmpty() ) );
}
Nepomuk::ResourceData* Nepomuk::ResourceData::determineUri()
{
// We have the following possible situations:
// 1. m_uri is already valid
// -> simple, nothing to do
//
// 2. m_uri is not valid
// -> we need to determine the URI
//
// 2.1. m_kickoffUri is valid
// 2.1.1. it is a file URL
// 2.1.1.1. it is nie:url for r
// -> use r as m_uri
// 2.1.1.2. it points to a file on a removable device for which we have a filex:/ URL
// -> use the r in r nie:url filex:/...
// 2.1.1.3. it is a file which is not an object in some nie:url relation
// -> create new random m_uri and use kickoffUriOrId() as m_nieUrl
// 2.1.2. it is a resource URI
// -> use it as m_uri
//
// 2.2. m_kickOffUri is not valid
// 2.2.1. m_kickOffUri is a nao:identifier for r
// -> use r as m_uri
//
if( m_uri.isEmpty() ) {
Soprano::Model* model = MAINMODEL;
if( !m_kickoffUris.isEmpty() ) {
KUrl kickOffUri = *m_kickoffUris.begin();
if( kickOffUri.scheme().isEmpty() ) {
//
// Not valid. Checking for nao:identifier
//
QString query = QString::fromLatin1("select distinct ?r where { ?r %1 %2. } LIMIT 1")
.arg( Soprano::Node::resourceToN3(Soprano::Vocabulary::NAO::identifier()) )
.arg( Soprano::Node::literalToN3( kickOffUri.url() ) );
Soprano::QueryResultIterator it = model->executeQuery( query, Soprano::Query::QueryLanguageSparql );
if( it.next() ) {
m_uri = it["r"].uri();
it.close();
}
}
else {
//
// In one query determine if the URI is already used as resource URI or as
// nie:url
//
QString query = QString::fromLatin1("select distinct ?r ?o where { "
"{ ?r %1 %2 . FILTER(?r!=%2) . } "
"UNION "
"{ %2 ?p ?o . } "
"} LIMIT 1")
.arg( Soprano::Node::resourceToN3( Nepomuk::Vocabulary::NIE::url() ) )
.arg( Soprano::Node::resourceToN3( kickOffUri ) );
Soprano::QueryResultIterator it = model->executeQuery( query, Soprano::Query::QueryLanguageSparql );
if( it.next() ) {
QUrl uri = it["r"].uri();
if( uri.isEmpty() ) {
m_uri = kickOffUri;
}
else {
m_uri = uri;
m_nieUrl = kickOffUri;
}
it.close();
}
else if( kickOffUri.scheme() == QLatin1String("nepomuk") ) {
// for nepomuk URIs we simply use the kickoff URI as resource URI
m_uri = kickOffUri;
}
else {
// for everything else we use m_kickoffUri as nie:url with a new random m_uri
m_nieUrl = kickOffUri;
}
}
}
}
//
// Move us to the final data hash now that the URI is known
//
if( !m_uri.isEmpty() ) {
m_cacheDirty = true;
//vHanda: Is there some way to avoid this hash lookup every time?
// It sure would speed things up.
ResourceDataHash::iterator it = m_rm->m_initializedData.find(m_uri);
if( it == m_rm->m_initializedData.end() ) {
m_rm->m_initializedData.insert( m_uri, this );
- if(!m_rm->m_watcher) {
- m_rm->m_watcher = new ResourceWatcher(m_rm->m_manager);
- QObject::connect( m_rm->m_watcher, SIGNAL(propertyAdded(QUrl, Nepomuk::Types::Property, QVariant)),
- m_rm->m_manager, SLOT(slotPropertyAdded(QUrl, Nepomuk::Types::Property, QVariant)) );
- QObject::connect( m_rm->m_watcher, SIGNAL(propertyRemoved(QUrl, Nepomuk::Types::Property, QVariant)),
- m_rm->m_manager, SLOT(slotPropertyRemoved(QUrl, Nepomuk::Types::Property, QVariant)) );
- m_rm->m_watcher->addResource( m_uri );
- m_rm->m_watcher->start();
- }
- else {
- m_rm->m_watcher->addResource( m_uri );
- }
}
else {
return it.value();
}
}
return this;
}
void Nepomuk::ResourceData::invalidateCache()
{
m_cacheDirty = true;
}
Nepomuk::Thing Nepomuk::ResourceData::pimoThing()
{
load();
if( !m_pimoThing ) {
//
// We only create a new thing if we are a nie:InformationElement.
// All other resources will simply be converted into a pimo:Thing
//
// Files, however, are a special case in every aspect. this includes pimo things.
// Files are their own grounding occurrence. This makes a lot of things
// much simpler.
//
if( hasType( Vocabulary::PIMO::Thing() ) ||
isFile() ||
!hasType( Vocabulary::NIE::InformationElement() ) ) {
m_pimoThing = new Thing(this);
}
else {
m_pimoThing = new Thing();
}
m_pimoThing->m_data->m_groundingOccurence = this;
}
return *m_pimoThing;
}
bool Nepomuk::ResourceData::operator==( const ResourceData& other ) const
{
if( this == &other )
return true;
return( m_uri == other.m_uri &&
m_mainType == other.m_mainType &&
m_kickoffUris == other.m_kickoffUris );
}
QDebug Nepomuk::ResourceData::operator<<( QDebug dbg ) const
{
KUrl::List list = m_kickoffUris.toList();
dbg << QString::fromLatin1("[kickoffuri: %1; uri: %2; type: %3; ref: %4]")
.arg( list.toStringList().join(QLatin1String(",")),
m_uri.url(),
m_mainType.toString() )
.arg( m_ref );
return dbg;
}
QDebug operator<<( QDebug dbg, const Nepomuk::ResourceData& data )
{
return data.operator<<( dbg );
}
void Nepomuk::ResourceData::updateKickOffLists(const QUrl& prop, const Nepomuk::Variant& v)
{
KUrl oldUrl;
KUrl newUrl;
if( prop == Nepomuk::Vocabulary::NIE::url() ) {
oldUrl = m_nieUrl;
newUrl = v.toUrl();
m_nieUrl = newUrl;
}
else if( prop == Soprano::Vocabulary::NAO::identifier() ) {
Q_FOREACH( const KUrl& url, m_kickoffUris ) {
if( url.scheme().isEmpty() ) {
oldUrl = url;
break;
}
}
newUrl = KUrl( v.toString() );
}
else {
return;
}
if( oldUrl != newUrl ) {
QMutexLocker rmlock(&m_rm->mutex);
m_kickoffUris.remove( oldUrl );
m_rm->m_uriKickoffData.remove( oldUrl );
if( !newUrl.isEmpty() ) {
m_kickoffUris.insert( newUrl );
m_rm->m_uriKickoffData.insert( newUrl, this );
}
}
}
diff --git a/nepomuk/core/resourcedata.h b/nepomuk/core/resourcedata.h
index 3b2b2e57e8..89116f6a04 100644
--- a/nepomuk/core/resourcedata.h
+++ b/nepomuk/core/resourcedata.h
@@ -1,219 +1,220 @@
/*
* This file is part of the Nepomuk KDE project.
* Copyright (C) 2006-2010 Sebastian Trueg <trueg@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _NEPOMUK_RESOURCE_DATA_H_
#define _NEPOMUK_RESOURCE_DATA_H_
#include <QtCore/QString>
#include <QtCore/QList>
#include <QtCore/QHash>
#include <QtCore/QMutex>
#include <QtCore/QAtomicInt>
#include <QtCore/QSet>
#include "variant.h"
#include "thing.h"
#include <kurl.h>
#include <soprano/statement.h>
namespace Nepomuk {
class Resource;
class ResourceManagerPrivate;
class ResourceData
{
public:
explicit ResourceData( const QUrl& uri, const QUrl& kickOffUri, const QUrl& type_, ResourceManagerPrivate* rm );
~ResourceData();
inline bool ref(Nepomuk::Resource* res) {
m_resources.push_back( res );
return m_ref.ref();
}
inline bool deref(Nepomuk::Resource* res) {
m_resources.removeAll( res );
return m_ref.deref();
}
inline int cnt() const {
return m_ref;
}
/**
* Tries to determine if this resource represents a file by examining the type and the uri.
*/
bool isFile();
/**
* The URI of the resource. This might be empty if the resource was not synced yet.
*/
QUrl uri() const;
/**
* \return The main type of the resource. ResourceData tries hard to make this the
* most important type, i.e. that which is furthest down the hierachy.
*/
QUrl type();
QList<QUrl> allTypes();
void setTypes( const QList<QUrl>& types );
QHash<QUrl, Variant> allProperties();
bool hasProperty( const QUrl& uri );
bool hasProperty( const QUrl& p, const Variant& v );
/**
* Does also check for subClass relations.
*/
bool hasType( const QUrl& uri );
/**
* Check the type without loading data from the store.
*/
bool constHasType( const QUrl& type ) const;
Variant property( const QUrl& uri );
/**
* Set a property. The property will directly be saved to the RDF store.
* Calls store to make sure this resource and property resources are properly
* stored.
*/
void setProperty( const QUrl& uri, const Variant& value );
void addProperty( const QUrl& uri, const Variant& value );
void removeProperty( const QUrl& uri );
/**
* Makes sure the resource is present in the RDF store. This means that if it does
* not exist the type and the identifier (if one has been used to create the instance)
* are stored.
*
* This is also the only place where a new URI is generated via ResourceManager::generateUniqueUri()
* in case m_uri is empty.
*
* \sa exists, setProperty
*/
bool store();
bool load();
/**
* Remove this resource data from the store completely.
* \param recursive If true all statements that contain this
* resource as an object will be removed, too.
*/
void remove( bool recursive = true );
/**
* This method only works with a proper URI, i.e. it does
* not work on non-initialized resources that only know
* their kickoffUriOrId
*/
bool exists();
bool isValid() const;
/**
* Searches for the resource in the Nepomuk store using m_kickoffId and m_kickoffUri.
*
* This will either get the actual resource URI from the database
* and add m_data into ResourceManagerPrivate::m_initializedData
* or it will find another ResourceData instance in m_initializedData
* which represents the same resource. The ResourceData that should be
* used is returned.
*
* \returns The initialized ResourceData object representing the actual resource.
*
* m_determineUriMutex needs to be locked before calling this method
*/
ResourceData* determineUri();
void invalidateCache();
Thing pimoThing();
/**
* Compares the properties of two ResourceData objects taking into account the Deleted flag
*/
bool operator==( const ResourceData& other ) const;
QDebug operator<<( QDebug dbg ) const;
ResourceManagerPrivate* rm() const { return m_rm; }
/// Contains a list of resources which use this ResourceData
QList<Resource*> m_resources;
/// the URI that was used to construct the resource. Will be used by determineUri
/// to find the actual resource URI which is either m_kickoffUri itself or
/// a resource URI which relates to m_kickoffUri by nie:url
/// This is a set since Resource::determineFinalResourceData may add additional uris
QSet<KUrl> m_kickoffUris;
QHash<QUrl, Variant> m_cache;
/// Updates both m_kickoffUris and ResourceMangerPrivate's list
void updateKickOffLists( const QUrl & prop, const Variant & v );
private:
void loadType( const QUrl& type );
/// Will reset this instance to 0 as if constructed without parameters
/// Used by remove() and deleteData()
void resetAll( bool isDelete = false );
/// final resource URI created by determineUri
KUrl m_uri;
/// the URL of file resources
KUrl m_nieUrl;
QUrl m_mainType;
QList<QUrl> m_types;
QAtomicInt m_ref;
mutable QMutex m_modificationMutex;
bool m_cacheDirty;
+ bool m_addedToWatcher;
// using a pointer to avoid infinite creation loop
Thing* m_pimoThing;
// only used for delayed storage of the pimo thing relation
ResourceData* m_groundingOccurence;
ResourceManagerPrivate* m_rm;
};
}
QDebug operator<<( QDebug dbg, const Nepomuk::ResourceData& );
#endif
diff --git a/nepomuk/core/resourcemanager.cpp b/nepomuk/core/resourcemanager.cpp
index f800c443a4..1e0b5ff1c4 100644
--- a/nepomuk/core/resourcemanager.cpp
+++ b/nepomuk/core/resourcemanager.cpp
@@ -1,569 +1,569 @@
/*
* This file is part of the Nepomuk KDE project.
* Copyright (C) 2006-2012 Sebastian Trueg <trueg@kde.org>
* Copyright (C) 2010 Vishesh Handa <handa.vish@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "resourcemanager.h"
#include "resourcemanager_p.h"
#include "resourcedata.h"
#include "tools.h"
#include "nepomukmainmodel.h"
#include "resource.h"
#include "class.h"
#include "nie.h"
#include "dbustypes.h"
#include "resourcewatcher.h"
#include <kglobal.h>
#include <kdebug.h>
#include <krandom.h>
#include <Soprano/Node>
#include <Soprano/Statement>
#include <Soprano/Vocabulary/RDF>
#include <Soprano/StatementIterator>
#include <Soprano/QueryResultIterator>
#include <QtCore/QFileInfo>
#include <QtCore/QMutex>
#include <QtCore/QMutexLocker>
#include <QtCore/QUuid>
#include <QtCore/QMutableHashIterator>
#include <QtCore/QCoreApplication>
#include <QtDBus/QDBusConnection>
#include <QtDBus/QDBusConnectionInterface>
#include <QtDBus/QDBusServiceWatcher>
#include <QtDBus/QDBusMetaType>
using namespace Soprano;
Nepomuk::ResourceManager* Nepomuk::ResourceManager::s_instance = 0;
Nepomuk::ResourceManagerPrivate::ResourceManagerPrivate( ResourceManager* manager )
: mainModel( 0 ),
overrideModel( 0 ),
mutex(QMutex::Recursive),
dataCnt( 0 ),
m_manager( manager ),
m_watcher( 0 )
{
Nepomuk::DBus::registerDBusTypes();
}
Nepomuk::ResourceData* Nepomuk::ResourceManagerPrivate::data( const QUrl& uri, const QUrl& type )
{
if ( uri.isEmpty() ) {
// return an invalid resource which may be activated by calling setProperty
return new ResourceData( QUrl(), QUrl(), type, this );
}
if( ResourceData* data = findData( uri ) ) {
return data;
}
else {
return new ResourceData( QUrl(), uri, type, this );
}
}
Nepomuk::ResourceData* Nepomuk::ResourceManagerPrivate::data( const QString& uriOrId, const QUrl& type )
{
if ( !uriOrId.isEmpty() ) {
KUrl url(uriOrId);
return data( url, type );
}
return new ResourceData( QUrl(), QUrl(), type, this );
}
Nepomuk::ResourceData* Nepomuk::ResourceManagerPrivate::dataForResourceUri( const QUrl& uri, const QUrl& type )
{
if ( uri.isEmpty() ) {
// return an invalid resource which may be activated by calling setProperty
return new ResourceData( QUrl(), QUrl(), type, this );
}
if( ResourceData* data = findData( uri ) ) {
return data;
}
else {
return new ResourceData( uri, QUrl(), type, this );
}
}
QList<Nepomuk::ResourceData*> Nepomuk::ResourceManagerPrivate::allResourceDataOfType( const QUrl& type )
{
QList<ResourceData*> l;
if( !type.isEmpty() ) {
mutex.lock();
QSet<ResourceData*> rdl = m_uriKickoffData.values().toSet();
mutex.unlock();
for( QSet<ResourceData*>::iterator rdIt = rdl.begin();
rdIt != rdl.end(); ++rdIt ) {
ResourceData* rd = *rdIt;
//
// make sure we do not trigger a load here since
// 1. that could result in the deletion of values from the iterated list (m_cache.clear() in ResourceData::load)
// 2. We only need to check non-existing resources anyway, since the rest is queried from the db below
//
if( rd->constHasType( type ) ) {
l.append( rd );
}
}
}
return l;
}
QList<Nepomuk::ResourceData*> Nepomuk::ResourceManagerPrivate::allResourceDataWithProperty( const QUrl& uri, const Variant& v )
{
QList<ResourceData*> l;
//
// We need to cache m_uriKickoffData since it might be changed
// in the loop by ResourceData::load()
//
mutex.lock();
QSet<ResourceData*> rdl = m_uriKickoffData.values().toSet();
mutex.unlock();
//
// make sure none of the ResourceData objects are deleted by ResourceData::load below
// which would result in a crash since we have them cached.
//
QList<Resource> tmp;
foreach( ResourceData* rd, rdl ) {
tmp << Resource( rd );
}
for( QSet<ResourceData*>::iterator rdIt = rdl.begin();
rdIt != rdl.end(); ++rdIt ) {
ResourceData* rd = *rdIt;
if( rd->hasProperty( uri ) &&
rd->property( uri ) == v ) {
l.append( rd );
}
}
return l;
}
QList<Nepomuk::ResourceData*> Nepomuk::ResourceManagerPrivate::allResourceData()
{
return m_uriKickoffData.values().toSet().toList();
}
bool Nepomuk::ResourceManagerPrivate::dataCacheFull() const
{
return dataCnt >= 1000;
}
void Nepomuk::ResourceManagerPrivate::cleanupCache( int num )
{
QMutexLocker lock( &mutex );
QSet<ResourceData*> rdl = m_uriKickoffData.values().toSet() + m_initializedData.values().toSet();
for( QSet<ResourceData*>::iterator rdIt = rdl.begin();
rdIt != rdl.end(); ++rdIt ) {
ResourceData* data = *rdIt;
if ( !data->cnt() ) {
delete data;
if( num > 0 && --num == 0 )
break;
}
}
}
bool Nepomuk::ResourceManagerPrivate::shouldBeDeleted( ResourceData * rd ) const
{
//
// We delete data objects in one of two cases:
// 1. They are not valid and as such not in one of the ResourceManagerPrivate kickoff lists
// 2. The cache is already full and we need to clean up
//
return( !rd->cnt() && ( !rd->isValid() || dataCacheFull() ));
}
void Nepomuk::ResourceManagerPrivate::addToKickOffList( ResourceData* rd, const QSet<KUrl> & uris )
{
Q_FOREACH( const KUrl& uri, uris )
m_uriKickoffData.insert( uri, rd );
}
void Nepomuk::ResourceManagerPrivate::_k_storageServiceInitialized( bool success )
{
if( success ) {
kDebug() << "Nepomuk Storage service up and initialized.";
cleanupCache(-1);
m_manager->init();
emit m_manager->nepomukSystemStarted();
}
}
void Nepomuk::ResourceManagerPrivate::_k_dbusServiceUnregistered( const QString& serviceName )
{
if( serviceName == QLatin1String("org.kde.NepomukStorage") ) {
kDebug() << "Nepomuk Storage service went down.";
cleanupCache(-1);
emit m_manager->nepomukSystemStopped();
}
}
Nepomuk::ResourceData* Nepomuk::ResourceManagerPrivate::findData( const QUrl& uri )
{
if ( !uri.isEmpty() ) {
QMutexLocker lock( &mutex );
// look for the URI in the initialized and in the URI kickoff data
ResourceDataHash::iterator end = m_initializedData.end();
ResourceDataHash::iterator it = m_initializedData.find( uri );
if( it == end ) {
end = m_uriKickoffData.end();
it = m_uriKickoffData.find( uri );
}
if( it != end ) {
return it.value();
}
}
return 0;
}
Nepomuk::ResourceManager::ResourceManager()
: QObject(),
d( new ResourceManagerPrivate( this ) )
{
// connect to the storage service's initialized signal to be able to emit
// the nepomukSystemStarted signal
QDBusConnection::sessionBus().connect( QLatin1String("org.kde.NepomukStorage"),
QLatin1String("/servicecontrol"),
QLatin1String("org.kde.nepomuk.ServiceControl"),
QLatin1String("serviceInitialized"),
this,
SLOT(_k_storageServiceInitialized(bool)) );
// connect to the serviceUnregistered signal to be able to connect the nepomukSystemStopped
// signal once the storage service goes away
QDBusServiceWatcher *watcher = new QDBusServiceWatcher( QLatin1String("org.kde.NepomukStorage"),
QDBusConnection::sessionBus(),
QDBusServiceWatcher::WatchForUnregistration,
this );
connect( watcher, SIGNAL(serviceUnregistered(QString)),
this, SLOT(_k_dbusServiceUnregistered(QString)) );
init();
}
Nepomuk::ResourceManager::~ResourceManager()
{
clearCache();
delete d->mainModel;
delete d;
if(s_instance == this) {
s_instance = 0;
}
}
void Nepomuk::ResourceManager::deleteInstance()
{
delete this;
}
Nepomuk::ResourceManager* Nepomuk::ResourceManager::instance()
{
if(!s_instance) {
s_instance = new ResourceManager();
s_instance->setParent(QCoreApplication::instance());
}
return s_instance;
}
int Nepomuk::ResourceManager::init()
{
QMutexLocker lock( &d->initMutex );
if( !d->mainModel ) {
d->mainModel = new MainModel( this );
}
d->mainModel->init();
return d->mainModel->isValid() ? 0 : -1;
}
bool Nepomuk::ResourceManager::initialized() const
{
QMutexLocker lock( &d->initMutex );
return d->mainModel && d->mainModel->isValid();
}
#ifndef KDE_NO_DEPRECATED
Nepomuk::Resource Nepomuk::ResourceManager::createResourceFromUri( const QString& uri )
{
return Resource( uri, QUrl() );
}
#endif
void Nepomuk::ResourceManager::removeResource( const QString& uri )
{
Resource res( uri );
res.remove();
}
void Nepomuk::ResourceManager::notifyError( const QString& uri, int errorCode )
{
kDebug() << "(Nepomuk::ResourceManager) error: " << uri << " " << errorCode;
emit error( uri, errorCode );
}
#ifndef KDE_NO_DEPRECATED
QList<Nepomuk::Resource> Nepomuk::ResourceManager::allResourcesOfType( const QString& type )
{
return allResourcesOfType( QUrl(type) );
}
#endif
QList<Nepomuk::Resource> Nepomuk::ResourceManager::allResourcesOfType( const QUrl& type )
{
QSet<Resource> set;
if( !type.isEmpty() ) {
// check local data
QList<ResourceData*> localData = d->allResourceDataOfType( type );
for( QList<ResourceData*>::iterator rdIt = localData.begin();
rdIt != localData.end(); ++rdIt ) {
Resource res( *rdIt );
set.insert(res);
}
// kDebug() << " added local resources: " << l.count();
Soprano::Model* model = mainModel();
Soprano::StatementIterator it = model->listStatements( Soprano::Statement( Soprano::Node(), Soprano::Vocabulary::RDF::type(), type ) );
while( it.next() ) {
Statement s = *it;
Resource res( s.subject().uri() );
set.insert(res);
}
// kDebug() << " added remote resources: " << l.count();
}
return set.toList();
}
QList<Nepomuk::Resource> Nepomuk::ResourceManager::allResources()
{
QList<Nepomuk::Resource> l;
Q_FOREACH( ResourceData* data, d->allResourceData()) {
l << Resource( data );
}
Soprano::QueryResultIterator it = mainModel()->executeQuery( QLatin1String("select distinct ?r where { ?r a ?t . FILTER(?t != rdf:Property && ?t != rdfs:Class) . }"),
Soprano::Query::QueryLanguageSparql );
while( it.next() ) {
Resource r( it[0].uri() );
l << r;
}
return l;
}
#ifndef KDE_NO_DEPRECATED
QList<Nepomuk::Resource> Nepomuk::ResourceManager::allResourcesWithProperty( const QString& uri, const Variant& v )
{
return allResourcesWithProperty( QUrl(uri), v );
}
#endif
QList<Nepomuk::Resource> Nepomuk::ResourceManager::allResourcesWithProperty( const QUrl& uri, const Variant& v )
{
QSet<Resource> set;
if( v.isList() ) {
kDebug() << "(ResourceManager::allResourcesWithProperty) list values not supported.";
}
else {
// check local data
QList<ResourceData*> localData = d->allResourceDataWithProperty( uri, v );
for( QList<ResourceData*>::iterator rdIt = localData.begin();
rdIt != localData.end(); ++rdIt ) {
set.insert( Resource( *rdIt ) );
}
// check remote data
Soprano::Model* model = mainModel();
Soprano::StatementIterator it = model->listStatements( Soprano::Statement( Soprano::Node(), uri, v.toNode() ) );
while( it.next() ) {
Statement s = *it;
Resource res( s.subject().uri() );
set.insert( res );
}
}
return set.toList();
}
void Nepomuk::ResourceManager::clearCache()
{
d->cleanupCache( -1 );
}
#ifndef KDE_NO_DEPRECATED
QString Nepomuk::ResourceManager::generateUniqueUri()
{
return generateUniqueUri( QString() ).toString();
}
#endif
QUrl Nepomuk::ResourceManager::generateUniqueUri( const QString& name )
{
// default to res URIs
QString type = QLatin1String("res");
// ctx is the only used value for name
if(name == QLatin1String("ctx")) {
type = name;
}
Soprano::Model* model = mainModel();
while( 1 ) {
QString uuid = QUuid::createUuid().toString();
uuid = uuid.mid(1, uuid.length()-2);
QUrl uri = QUrl( QLatin1String("nepomuk:/") + type + QLatin1String("/") + uuid );
if ( !model->executeQuery( QString::fromLatin1("ask where { "
"{ <%1> ?p1 ?o1 . } "
"UNION "
"{ ?s2 <%1> ?o2 . } "
"UNION "
"{ ?s3 ?p3 <%1> . } "
"UNION "
"{ graph <%1> { ?s4 ?4 ?o4 . } . } "
"}")
.arg( QString::fromAscii( uri.toEncoded() ) ), Soprano::Query::QueryLanguageSparql ).boolValue() ) {
return uri;
}
}
}
Soprano::Model* Nepomuk::ResourceManager::mainModel()
{
// make sure we are initialized
if ( !d->overrideModel && !initialized() ) {
init();
}
return d->mainModel;
}
-void Nepomuk::ResourceManager::slotPropertyAdded(const QUrl &res, const Types::Property &prop, const QVariant &value)
+void Nepomuk::ResourceManager::slotPropertyAdded(const Resource &res, const Types::Property &prop, const QVariant &value)
{
- ResourceDataHash::iterator it = d->m_initializedData.find(res);
+ ResourceDataHash::iterator it = d->m_initializedData.find(res.resourceUri());
if(it != d->m_initializedData.end()) {
ResourceData* data = *it;
data->m_cache[prop.uri()].append(Variant(value));
data->updateKickOffLists(prop.uri(), Variant(value));
}
}
-void Nepomuk::ResourceManager::slotPropertyRemoved(const QUrl& res, const Nepomuk::Types::Property& prop, const QVariant& value_)
+void Nepomuk::ResourceManager::slotPropertyRemoved(const Resource &res, const Types::Property &prop, const QVariant &value_)
{
- ResourceDataHash::iterator it = d->m_initializedData.find(res);
+ ResourceDataHash::iterator it = d->m_initializedData.find(res.resourceUri());
if(it != d->m_initializedData.end()) {
ResourceData* data = *it;
QHash<QUrl, Variant>::iterator cacheIt = data->m_cache.find(prop.uri());
if(cacheIt != data->m_cache.end()) {
Variant v = *cacheIt;
const Variant value(value_);
QList<Variant> vl = v.toVariantList();
if(vl.contains(value)) {
vl.removeAll(value);
data->updateKickOffLists(prop.uri(), Variant());
if(vl.isEmpty()) {
data->m_cache.erase(cacheIt);
}
else {
cacheIt.value() = vl;
}
}
}
}
}
void Nepomuk::ResourceManager::setOverrideMainModel( Soprano::Model* model )
{
QMutexLocker lock( &d->mutex );
if( model != d->mainModel ) {
d->overrideModel = model;
// clear cache to make sure we do not mix data
Q_FOREACH( ResourceData* data, d->allResourceData()) {
data->invalidateCache();
}
}
}
Nepomuk::ResourceManager* Nepomuk::ResourceManager::createManagerForModel( Soprano::Model* model )
{
ResourceManager* manager = new ResourceManager();
manager->setOverrideMainModel( model );
return manager;
}
#include "resourcemanager.moc"
diff --git a/nepomuk/core/resourcemanager.h b/nepomuk/core/resourcemanager.h
index c87a803227..6b23b8f455 100644
--- a/nepomuk/core/resourcemanager.h
+++ b/nepomuk/core/resourcemanager.h
@@ -1,289 +1,289 @@
/*
* This file is part of the Nepomuk KDE project.
* Copyright (C) 2006-2009 Sebastian Trueg <trueg@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#ifndef _NEPOMUK_RESOURCE_MANAGER_H_
#define _NEPOMUK_RESOURCE_MANAGER_H_
#include "nepomuk_export.h"
#include <QtCore/QObject>
#include <QtCore/QUrl>
namespace Soprano {
class Model;
}
namespace Nepomuk {
class Resource;
class Variant;
class ResourceManagerHelper;
class ResourceManagerPrivate;
namespace Types {
class Class;
class Property;
}
/**
* \class ResourceManager resourcemanager.h Nepomuk/ResourceManager
*
* \brief The ResourceManager is the central \a %Nepomuk configuration point.
*
* Use the initialized() method to check the availabity of the %Nepomuk system.
* Signals nepomukSystemStarted() and nepomukSystemStopped() can be used to
* enable or disable Nepomuk-specific GUI elements.
*
* \author Sebastian Trueg <trueg@kde.org>
*/
class NEPOMUK_EXPORT ResourceManager : public QObject
{
Q_OBJECT
public:
static ResourceManager* instance();
/**
* In KDE 4.3 support for multiple ResourceManager instances
* has been introduced. To keep binary compatibility both the constructor's
* and destructor's access visibility could not be changed. Thus, instead of deleting
* a custom ResourceManager instance the standard way, one has to call this
* method or use QObject::deleteLater.
*
* \since 4.3
*/
void deleteInstance();
/**
* Initialize the Nepomuk framework. This method will initialize the communication with
* the local Nepomuk-KDE services, ie. the data repository. It will trigger a reconnect
* to the %Nepomuk database.
*
* There is normally no reason to call this method manually except when using multiple
* threads. In that case it is highly recommended to call this method in the main thread
* before doing anything else.
*
* \return 0 if all necessary components could be found and -1 otherwise.
*/
int init();
/**
* \return true if init() has been called successfully, ie. the KMetaData system is connected
* to the local RDF repository service and ready to work.
*/
bool initialized() const;
/**
* Retrieve the main data storage model.
*/
Soprano::Model* mainModel();
/**
* Override the main model used for all storage. By default the main model
* used is the Nepomuk server main model.
*
* \param model The model to use instead of the Nepomuk server or 0 to reset.
*
* \since 4.1
*/
void setOverrideMainModel( Soprano::Model* model );
/**
* \deprecated Use the Resource constructor directly.
*
* Creates a Resource object representing the data referenced by \a uri.
* The result is the same as from using the Resource::Resource( const QString&, const QString& )
* constructor with an empty type.
*
* \return The Resource representing the data at \a uri or an invalid Resource object if the local
* NEPOMUK RDF store does not contain an object with URI \a uri.
*/
#ifndef KDE_NO_DEPRECATED
KDE_DEPRECATED Resource createResourceFromUri( const QString& uri );
#endif
/**
* Remove the resource denoted by \a uri completely.
*
* This method is just a wrapper around Resource::remove. The result
* is the same.
*/
void removeResource( const QString& uri );
/**
* Retrieve a list of all resource managed by this manager.
*
* \warning This list will be very big. Usage of this method is
* discouraged. Use Query::QueryServiceClient in combination with an
* empty Query::Query instead.
*
* \since 4.3
*/
QList<Resource> allResources();
/**
* Retrieve a list of all resources of the specified \a type.
*
* This includes Resources that are not synced yet so it might
* not represent exactly the state as in the RDF store.
*
* \warning This list can be very big. Usage of this method is
* discouraged. Use Query::QueryServiceClient in combination with
* a Query::Query containing one Query::ResourceTypeTerm instead.
*/
QList<Resource> allResourcesOfType( const QUrl& type );
/**
* \deprecated Use allResourcesOfType( const QString& type )
*/
#ifndef KDE_NO_DEPRECATED
KDE_DEPRECATED QList<Resource> allResourcesOfType( const QString& type );
#endif
/**
* Retrieve a list of all resources that have property \a uri defined with a value of \a v.
*
* This includes Resources that are not synced yet so it might
* not represent exactly the state as in the RDF store.
*
* \param uri The URI identifying the property. If this URI does
* not include a namespace the default namespace is
* prepended.
* \param v The value all returned resources should have set as properts \a uri.
*
* \warning This list can be very big. Usage of this method is
* discouraged. Use Query::QueryServiceClient in combination with
* a Query::Query containing one Query::ComparisonTerm instead.
*/
QList<Resource> allResourcesWithProperty( const QUrl& uri, const Variant& v );
/**
* \deprecated Use allResourcesWithProperty( const QString& type )
*/
#ifndef KDE_NO_DEPRECATED
KDE_DEPRECATED QList<Resource> allResourcesWithProperty( const QString& uri, const Variant& v );
#endif
/**
* %ResourceManager caches resource locally so subsequent access is faster.
* This method clears this cache, deleting any Resource that is not used.
*
* \since 4.4
*/
void clearCache();
/**
* \deprecated Use generateUniqueUri(const QString&)
*
* Generates a unique URI that is not used in the store yet. This method ca be used to
* generate URIs for virtual types such as Tag.
*/
#ifndef KDE_NO_DEPRECATED
KDE_DEPRECATED QString generateUniqueUri();
#endif
/**
* Generates a unique URI that is not used in the store yet. This method can be used to
* generate URIs for virtual types such as Tag.
*
* \param label A label that the algorithm should use to try to create a more readable URI.
*
* \return A new unique URI which can be used to define a new resource.
*
* \since 4.2
*/
QUrl generateUniqueUri( const QString& label );
/**
* \internal Non-public API. Used by Resource to signalize errors.
*/
void notifyError( const QString& uri, int errorCode );
/**
* Create a new ResourceManager instance which uses model as its
* override model. This allows to use multiple instances of ResourceManager
* at the same time. Normally one does not need this method as the singleton
* accessed via instance() should be enough.
*
* \param model The model to read and write data from and to.
*
* \since 4.3
*/
static ResourceManager* createManagerForModel( Soprano::Model* model );
Q_SIGNALS:
/**
* This signal gets emitted whenever a Resource changes due to a sync procedure.
* Be aware that modifying resources locally via the Resource::setProperty method
* does not result in a resourceModified signal being emitted.
*
* \param uri The URI of the modified resource.
*
* NOT IMPLEMENTED YET
*/
void resourceModified( const QString& uri );
/**
* Whenever a problem occurs (like for example failed resource syncing) this
* signal is emitted.
*
* \param uri The resource related to the error.
* \param errorCode The type of the error (Resource::ErrorCode)
*/
void error( const QString& uri, int errorCode );
/**
* Emitted once the Nepomuk system is up and can be used.
*
* \warning This signal will not be emitted if the Nepomuk
* system is running when the ResourceManager is created.
* Use initialized() to check the status.
*
* \since 4.4
*/
void nepomukSystemStarted();
/**
* Emitted once the Nepomuk system goes down.
*
* \since 4.4
*/
void nepomukSystemStopped();
private Q_SLOTS:
- void slotPropertyAdded(const QUrl &res, const Nepomuk::Types::Property &prop, const QVariant &value);
- void slotPropertyRemoved(const QUrl &res, const Nepomuk::Types::Property &prop, const QVariant &value);
+ void slotPropertyAdded(const Nepomuk::Resource &res, const Nepomuk::Types::Property &prop, const QVariant &value);
+ void slotPropertyRemoved(const Nepomuk::Resource &res, const Nepomuk::Types::Property &prop, const QVariant &value);
private:
friend class Nepomuk::Resource;
friend class Nepomuk::ResourceManagerPrivate;
ResourceManager();
~ResourceManager();
static ResourceManager* s_instance;
ResourceManagerPrivate* const d;
Q_PRIVATE_SLOT( d, void _k_storageServiceInitialized(bool) )
Q_PRIVATE_SLOT( d, void _k_dbusServiceUnregistered(QString) )
};
}
#endif
diff --git a/nepomuk/core/resourcewatcher.cpp b/nepomuk/core/resourcewatcher.cpp
index d507e1e4ba..617950320a 100644
--- a/nepomuk/core/resourcewatcher.cpp
+++ b/nepomuk/core/resourcewatcher.cpp
@@ -1,274 +1,280 @@
/*
This file is part of the Nepomuk KDE project.
Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com>
Copyright (C) 2011 Sebastian Trueg <trueg@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include "resourcewatcher.h"
#include "resourcewatcherconnectioninterface.h"
#include "resourcewatchermanagerinterface.h"
#include <QtDBus/QDBusObjectPath>
#include "resource.h"
#include "kurl.h"
#include "kdebug.h"
namespace {
QString convertUri(const QUrl& uri) {
return KUrl(uri).url();
}
QStringList convertUris(const QList<QUrl>& uris) {
QStringList cs;
foreach(const QUrl& uri, uris) {
cs << convertUri(uri);
}
return cs;
}
QList<QUrl> convertUris(const QStringList& uris) {
QList<QUrl> us;
foreach(const QString& uri, uris) {
us << KUrl(uri);
}
return us;
}
}
class Nepomuk::ResourceWatcher::Private {
public:
QList<QUrl> m_types;
QList<QUrl> m_resources;
QList<QUrl> m_properties;
org::kde::nepomuk::ResourceWatcherConnection * m_connectionInterface;
org::kde::nepomuk::ResourceWatcher * m_watchManagerInterface;
};
Nepomuk::ResourceWatcher::ResourceWatcher(QObject* parent)
: QObject(parent),
d(new Private)
{
d->m_watchManagerInterface
= new org::kde::nepomuk::ResourceWatcher( "org.kde.nepomuk.DataManagement",
"/resourcewatcher",
QDBusConnection::sessionBus() );
d->m_connectionInterface = 0;
}
Nepomuk::ResourceWatcher::~ResourceWatcher()
{
stop();
delete d;
}
bool Nepomuk::ResourceWatcher::start()
{
//
// Convert to list of strings
//
QList<QString> uris = convertUris(d->m_resources);
QList<QString> props = convertUris(d->m_properties);
QList<QString> types_ = convertUris(d->m_types);
//
// Create the dbus object to watch
//
QDBusPendingReply<QDBusObjectPath> reply = d->m_watchManagerInterface->watch( uris, props, types_ );
QDBusObjectPath path = reply.value();
if(!path.path().isEmpty()) {
d->m_connectionInterface = new org::kde::nepomuk::ResourceWatcherConnection( "org.kde.nepomuk.DataManagement",
path.path(),
QDBusConnection::sessionBus() );
connect( d->m_connectionInterface, SIGNAL(propertyAdded(QString,QString,QVariantList)),
this, SLOT(slotPropertyAdded(QString,QString,QVariantList)) );
connect( d->m_connectionInterface, SIGNAL(propertyRemoved(QString,QString,QVariantList)),
this, SLOT(slotPropertyRemoved(QString,QString,QVariantList)) );
connect( d->m_connectionInterface, SIGNAL(resourceCreated(QString,QStringList)),
this, SLOT(slotResourceCreated(QString,QStringList)) );
connect( d->m_connectionInterface, SIGNAL(propertyChanged(QString,QString,QVariantList,QVariantList)),
this, SLOT(slotPropertyChanged(QString,QString,QVariantList,QVariantList)) );
connect( d->m_connectionInterface, SIGNAL(resourceRemoved(QString,QStringList)),
this, SLOT(slotResourceRemoved(QString,QStringList)) );
connect( d->m_connectionInterface, SIGNAL(resourceTypeAdded(QString,QString)),
this, SLOT(slotResourceTypeAdded(QString,QString)) );
connect( d->m_connectionInterface, SIGNAL(resourceTypeRemoved(QString,QString)),
this, SLOT(slotResourceTypeRemoved(QString,QString)) );
return true;
}
else {
return false;
}
}
void Nepomuk::ResourceWatcher::stop()
{
if (d->m_connectionInterface) {
d->m_connectionInterface->close();
delete d->m_connectionInterface;
d->m_connectionInterface = 0;
}
}
void Nepomuk::ResourceWatcher::addProperty(const Nepomuk::Types::Property& property)
{
d->m_properties << property.uri();
if(d->m_connectionInterface) {
d->m_connectionInterface->addProperty(convertUri(property.uri()));
}
}
-void Nepomuk::ResourceWatcher::addResource(const QUrl& res)
+void Nepomuk::ResourceWatcher::addResource(const Nepomuk::Resource& res)
{
- d->m_resources << res;
+ d->m_resources << res.resourceUri();
if(d->m_connectionInterface) {
- d->m_connectionInterface->addResource(convertUri(res));
+ d->m_connectionInterface->addResource(convertUri(res.resourceUri()));
}
}
void Nepomuk::ResourceWatcher::addType(const Nepomuk::Types::Class& type)
{
d->m_types << type.uri();
if(d->m_connectionInterface) {
d->m_connectionInterface->addType(convertUri(type.uri()));
}
}
void Nepomuk::ResourceWatcher::removeProperty(const Nepomuk::Types::Property& property)
{
d->m_properties.removeAll(property.uri());
if(d->m_connectionInterface) {
d->m_connectionInterface->removeProperty(convertUri(property.uri()));
}
}
-void Nepomuk::ResourceWatcher::removeResource(const QUrl& res)
+void Nepomuk::ResourceWatcher::removeResource(const Nepomuk::Resource& res)
{
- d->m_resources.removeAll(res);
+ d->m_resources.removeAll(res.resourceUri());
if(d->m_connectionInterface) {
- d->m_connectionInterface->removeResource(convertUri(res));
+ d->m_connectionInterface->removeResource(convertUri(res.resourceUri()));
}
}
void Nepomuk::ResourceWatcher::removeType(const Nepomuk::Types::Class& type)
{
d->m_types.removeAll(type.uri());
if(d->m_connectionInterface) {
d->m_connectionInterface->removeType(convertUri(type.uri()));
}
}
QList< Nepomuk::Types::Property > Nepomuk::ResourceWatcher::properties() const
{
QList< Nepomuk::Types::Property > props;
foreach(const QUrl& uri, d->m_properties)
props << Types::Property(uri);
return props;
}
-QList<QUrl> Nepomuk::ResourceWatcher::resources() const
+QList<Nepomuk::Resource> Nepomuk::ResourceWatcher::resources() const
{
- return d->m_resources;
+ QList<Nepomuk::Resource> resources;
+ foreach(const QUrl& uri, d->m_resources)
+ resources << Resource::fromResourceUri(uri);
+ return resources;
}
QList< Nepomuk::Types::Class > Nepomuk::ResourceWatcher::types() const
{
QList<Nepomuk::Types::Class> types;
foreach(const QUrl& uri, d->m_types)
types << Types::Class(uri);
return types;
}
void Nepomuk::ResourceWatcher::setProperties(const QList< Nepomuk::Types::Property >& properties_)
{
d->m_properties.clear();
foreach(const Nepomuk::Types::Property& p, properties_) {
d->m_properties << p.uri();
}
if(d->m_connectionInterface) {
d->m_connectionInterface->setProperties(convertUris(d->m_properties));
}
}
-void Nepomuk::ResourceWatcher::setResources(const QList<QUrl>& resources_)
+void Nepomuk::ResourceWatcher::setResources(const QList< Nepomuk::Resource >& resources_)
{
- d->m_resources = resources_;
+ d->m_resources.clear();
+ foreach(const Nepomuk::Resource& res, resources_) {
+ d->m_resources << res.resourceUri();
+ }
if(d->m_connectionInterface) {
d->m_connectionInterface->setResources(convertUris(d->m_resources));
}
}
void Nepomuk::ResourceWatcher::setTypes(const QList< Nepomuk::Types::Class >& types_)
{
d->m_types.clear();
foreach(const Nepomuk::Types::Class& t, types_) {
d->m_types << t.uri();
}
if(d->m_connectionInterface) {
d->m_connectionInterface->setTypes(convertUris(d->m_types));
}
}
void Nepomuk::ResourceWatcher::slotResourceCreated(const QString &res, const QStringList &types)
{
- emit resourceCreated((KUrl(res)), convertUris(types));
+ emit resourceCreated(Nepomuk::Resource::fromResourceUri(KUrl(res)), convertUris(types));
}
void Nepomuk::ResourceWatcher::slotResourceRemoved(const QString &res, const QStringList &types)
{
emit resourceRemoved(KUrl(res), convertUris(types));
}
void Nepomuk::ResourceWatcher::slotResourceTypeAdded(const QString &res, const QString &type)
{
emit resourceTypeAdded(KUrl(res), KUrl(type));
}
void Nepomuk::ResourceWatcher::slotResourceTypeRemoved(const QString &res, const QString &type)
{
emit resourceTypeRemoved(KUrl(res), KUrl(type));
}
void Nepomuk::ResourceWatcher::slotPropertyAdded(const QString& res, const QString& prop, const QVariantList &objects)
{
foreach(const QVariant& v, objects) {
- emit propertyAdded( KUrl(res), Types::Property( KUrl(prop) ), v );
+ emit propertyAdded( Resource::fromResourceUri(KUrl(res)), Types::Property( KUrl(prop) ), v );
}
}
void Nepomuk::ResourceWatcher::slotPropertyRemoved(const QString& res, const QString& prop, const QVariantList &objects)
{
foreach(const QVariant& v, objects) {
- emit propertyRemoved( KUrl(res), Types::Property( KUrl(prop) ), v );
+ emit propertyRemoved( Resource::fromResourceUri(KUrl(res)), Types::Property( KUrl(prop) ), v );
}
}
void Nepomuk::ResourceWatcher::slotPropertyChanged(const QString& res, const QString& prop, const QVariantList& oldObjs, const QVariantList& newObjs)
{
- emit propertyChanged( KUrl(res), Types::Property( KUrl(prop) ),
+ emit propertyChanged( Resource::fromResourceUri(KUrl(res)), Types::Property( KUrl(prop) ),
oldObjs, newObjs );
}
#include "resourcewatcher.moc"
diff --git a/nepomuk/core/resourcewatcher.h b/nepomuk/core/resourcewatcher.h
index 1392920a64..06b96229cd 100644
--- a/nepomuk/core/resourcewatcher.h
+++ b/nepomuk/core/resourcewatcher.h
@@ -1,315 +1,315 @@
/*
This file is part of the Nepomuk KDE project.
Copyright (C) 2011 Vishesh Handa <handa.vish@gmail.com>
Copyright (C) 2011 Sebastian Trueg <trueg@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef RESOURCEWATCHER_H
#define RESOURCEWATCHER_H
#include "../types/class.h"
#include "../types/property.h"
#include "resource.h"
#include <QtDBus/QDBusVariant>
#include <QtCore/QVariant>
namespace Nepomuk {
/**
* \class ResourceWatcher resourcewatcher.h
*
* \brief Selectively monitor the nepomuk repository for changes.
*
* Resources may be monitored on the basis of types, properties, and uris.
*
* Changes may be monitored in one of the following ways:
* -# By resources -
* Specify the exact resources that should be watched. Any changes made to the specified resources
* (Excluding \ref nepomuk_dms_metadata) will be notified through the propertyAdded() and propertyRemoved()
* signals. Notifications will also be sent if any of the watched resources is deleted.
* -# By resources and properties -
* Specify the exact resources and their properties. Any changes made to the specified resources
* which touch one of the specified properties will be notified through the propertyAdded() and propertyRemoved()
* signals.
* -# By types -
* Specific types may be specified via add/setType. If types are set, then notifications will be
* sent for all new resources of that type. This includes property changes and resource creation and removal.
* TODO: add flags that allow to only watch for resource creation and removal.
* -# By types and properties -
* Both the types and properties may be specified. Notifications will be sent for property changes
* in resource with the specified types.
*
* \section nepomuk_rw_examples Resource Watcher Usage Example
*
* The following code creates a new ResourceWatcher, configures it to listen to changes on the \c nmm:performer
* property on one specific resource \c res.
*
* \code
* Nepomuk::ResourceWatcher* watcher = new Nepomuk::ResourceWatcher(this);
* watcher->addResource(res);
* watcher->addProperty(NMM:performer());
* connect(watcher, SIGNAL(propertyAdded(Nepomuk::Resource, Nepomuk::Types::Property, QVariant)),
* this, SLOT(slotPropertyChanged()));
* connect(watcher, SIGNAL(propertyRemoved(Nepomuk::Resource, Nepomuk::Types::Property, QVariant)),
* this, SLOT(slotPropertyChanged()));
* rwatcher->start();
* \endcode
*
* \author Vishesh Handa <handa.vish@gmail.com>, Sebastian Trueg <trueg@kde.org>
*
* \ingroup nepomuk_datamanagement
*/
class ResourceWatcher : public QObject
{
Q_OBJECT
public:
/**
* \brief Create a new %ResourceWatcher instance.
*
* This instance will not emit any signals before it has been configured
* and started.
*/
ResourceWatcher( QObject* parent = 0 );
/**
* \brief Destructor.
*/
virtual ~ResourceWatcher();
/**
* \brief Add a type to be watched.
*
* Every resource of this type will be watched for changes.
*
* \sa setTypes()
*/
void addType( const Types::Class & type );
/**
* \brief Add a resource to be watched.
*
* Every change to this resource will be
* signalled, depending on the configured properties().
*
* \sa setResources()
*/
- void addResource( const QUrl & res );
+ void addResource( const Nepomuk::Resource & res );
/**
* \brief Add a property to be watched.
*
* Every change to a value of this property
* will be signalled, depending on the configured resources() or types().
*
* \sa setProperties()
*/
void addProperty( const Types::Property & property );
/**
* \brief Remove a type to be watched.
*
* Every resource of this type will be watched for changes.
*
* \sa setTypes()
*/
void removeType( const Types::Class & type );
/**
* \brief Remove a resource to be watched.
*
* Every change to this resource will be
* signalled, depending on the configured properties().
*
* \sa setResources()
*/
- void removeResource( const QUrl & res );
+ void removeResource( const Nepomuk::Resource & res );
/**
* \brief Remove a property to be watched.
*
* Every change to a value of this property
* will be signalled, depending on the configured resources() or types().
*
* \sa setProperties()
*/
void removeProperty( const Types::Property & property );
/**
* \brief Set the types to be watched.
*
* Every resource having one of these types will be watched for changes.
*
* \sa addType()
*/
void setTypes( const QList<Types::Class> & types_ );
/**
* \brief Set the resources to be watched.
*
* Every change to one of these resources will be
* signalled, depending on the configured properties().
*
* \sa addResource()
*/
- void setResources( const QList<QUrl> & resources_ );
+ void setResources( const QList<Nepomuk::Resource> & resources_ );
/**
* \brief Set the properties to be watched.
*
* Every change to a value of any of these properties
* will be signalled, depending on the configured resources() or types().
*
* \sa addProperty()
*/
void setProperties( const QList<Types::Property> & properties_ );
/**
* \brief The types that have been configured via addType() and setTypes().
*
* Every resource having one of these types will be watched
* for changes.
*/
QList<Types::Class> types() const;
/**
* \brief The resources that have been configured via addResource() and setResources().
*
* Every change to one of these resources will be
* signalled, depending on the configured properties().
*/
- QList<QUrl> resources() const;
+ QList<Nepomuk::Resource> resources() const;
/**
* \brief The properties that have been configured via addProperty() and setProperties().
*
* Every change to a value of any of these properties
* will be signalled, depending on the configured resources() or types().
*/
QList<Types::Property> properties() const;
public Q_SLOTS:
/**
* \brief Start the signalling of changes.
*
* Before calling this method no signal will be emitted. In
* combination with stop() this allows to suspend the watching.
* Calling start() multiple times has no effect.
*/
bool start();
/**
* \brief Stop the signalling of changes.
*
* Allows to stop the watcher which has been started
* via start(). Calling stop() multiple times has no effect.
*/
void stop();
Q_SIGNALS:
/**
* \brief This signal is emitted when a new resource is created.
* \param resource The newly created resource.
* \param types The types the new resource has. If types() have been configured this list will always
* contain one of the configured types.
*/
- void resourceCreated( const QUrl & resource, const QList<QUrl>& types ); //FIXME: Use either Resource or uri, not a mix
+ void resourceCreated( const Nepomuk::Resource & resource, const QList<QUrl>& types ); //FIXME: Use either Resource or uri, not a mix
/**
* \brief This signal is emitted when a resource is deleted.
* \param uri The resource URI of the removed resource.
* \param types The types the removed resource had. If types() have been configured this list will always
* contain one of the configured types.
*/
void resourceRemoved( const QUrl & uri, const QList<QUrl>& types );
/**
* \brief This signal is emitted when a type has been added to a resource. This does not include creation which
* is signalled via resourceCreated(). It only applies to changes in a resource's types.
* \param res The changed resource.
* \param type The newly added type. If types() have been configured it will be one of them.
*/
- void resourceTypeAdded( const QUrl & res, const Types::Class & type );
+ void resourceTypeAdded( const Nepomuk::Resource & res, const Types::Class & type );
/**
* \brief This signal is emitted when a type has been removed from a resource.
*
* This does not include removal of entire resources which is signalled via resourceRemoved().
* It only applies to changes in a resource's types.
* \param res The changed resource.
* \param type The removed type. If types() have been configured it will be one of them.
*/
- void resourceTypeRemoved( const QUrl & res, const Types::Class & type );
+ void resourceTypeRemoved( const Nepomuk::Resource & res, const Types::Class & type );
/**
* \brief This signal is emitted when a property value is added.
* \param resource The changed resource.
* \param property The property which has a new value.
* \param value The newly added property value.
*/
- void propertyAdded( const QUrl & resource,
+ void propertyAdded( const Nepomuk::Resource & resource,
const Nepomuk::Types::Property & property,
const QVariant & value );
/**
* \brief This signal is emitted when a property value is removed.
* \param resource The changed resource.
* \param property The property which was changed.
* \param value The removed property value.
*/
- void propertyRemoved( const QUrl & resource,
+ void propertyRemoved( const Nepomuk::Resource & resource,
const Nepomuk::Types::Property & property,
const QVariant & value );
/**
* \brief This signal is emitted when a property value is changed.
*
* This signal cannot be emitted for all changes. It doesn't work if a property is first
* removed and then set, cause the Data Mangement Service does not maintain an internal
* cache for the purpose of emitting the propertyChanged signal.
*
* Specially, since one could theoretically take forever between the removal and the
* setting of the property.
*
* \param resource The changed resource.
* \param property The property which was changed.
* \param oldValue The removed property value.
*/
- void propertyChanged( const QUrl & resource,
+ void propertyChanged( const Nepomuk::Resource & resource,
const Nepomuk::Types::Property & property,
const QVariantList & oldValue,
const QVariantList & newValue );
private Q_SLOTS:
void slotResourceCreated(const QString& res, const QStringList& types);
void slotResourceRemoved(const QString& res, const QStringList& types);
void slotResourceTypeAdded(const QString& res, const QString& type);
void slotResourceTypeRemoved(const QString& res, const QString& type);
void slotPropertyAdded(const QString& res, const QString& prop, const QVariantList& objects);
void slotPropertyRemoved(const QString& res, const QString& prop, const QVariantList& objects);
void slotPropertyChanged(const QString& res, const QString& prop,
const QVariantList & oldObjs,
const QVariantList & newObjs);
private:
class Private;
Private * d;
};
}
#endif // RESOURCEWATCHER_H
diff --git a/nepomuk/query/queryparser.cpp b/nepomuk/query/queryparser.cpp
index 08d432b110..1e9971b109 100644
--- a/nepomuk/query/queryparser.cpp
+++ b/nepomuk/query/queryparser.cpp
@@ -1,815 +1,815 @@
/*
This file is part of the Nepomuk KDE project.
Copyright (C) 2007-2011 Sebastian Trueg <trueg@kde.org>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Library General Public
License version 2 as published by the Free Software Foundation.
This library 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
Library General Public License for more details.
You should have received a copy of the GNU Library General Public License
along with this library; see the file COPYING.LIB. If not, write to
the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
Boston, MA 02110-1301, USA.
*/
#include "queryparser.h"
#include "query.h"
#include "query_p.h"
#include "literalterm.h"
#include "resourceterm.h"
#include "andterm.h"
#include "orterm.h"
#include "negationterm.h"
#include "comparisonterm.h"
#include "dateparser_p.h"
#include "nfo.h"
#include <QtCore/QRegExp>
#include <QtCore/QSet>
#include <QtCore/QMutex>
#include <QtCore/QMutexLocker>
#include <kdebug.h>
#include <klocale.h>
#include "resourcemanager.h"
#include "property.h"
#include "literal.h"
#include <Soprano/Node>
#include <Soprano/Model>
#include <Soprano/QueryResultIterator>
#include <Soprano/Vocabulary/RDFS>
#include <Soprano/Vocabulary/RDF>
using namespace Nepomuk::Query;
namespace {
Nepomuk::Query::ComparisonTerm::Comparator fieldTypeRelationFromString( const QString& s ) {
if ( s == "=" ) {
return Nepomuk::Query::ComparisonTerm::Equal;
}
else if ( s == ":" ) {
return Nepomuk::Query::ComparisonTerm::Contains;
}
else if ( s == ">" ) {
return Nepomuk::Query::ComparisonTerm::Greater;
}
else if ( s == "<" ) {
return Nepomuk::Query::ComparisonTerm::Smaller;
}
else if ( s == ">=" ) {
return Nepomuk::Query::ComparisonTerm::GreaterOrEqual;
}
else if ( s == "<=" ) {
return Nepomuk::Query::ComparisonTerm::SmallerOrEqual;
}
else {
kDebug() << "FIXME: Unsupported relation:" << s;
return Nepomuk::Query::ComparisonTerm::Equal;
}
}
QString stripQuotes( const QString& s, bool* hadQuotes = 0 ) {
if ( s[0] == '\'' ||
s[0] == '\"' ) {
if( hadQuotes )
*hadQuotes = true;
return s.mid( 1 ).left( s.length()-2 );
}
else {
if( hadQuotes )
*hadQuotes = false;
return s;
}
}
QUrl tryToBeIntelligentAboutParsingUrl( const QString& s ) {
if ( s.contains( '%' ) && !s.contains( '/' ) ) {
return QUrl::fromEncoded( s.toAscii() );
}
else {
return QUrl( s );
}
}
Soprano::LiteralValue createLiteral( const QString& s, bool globbing ) {
// 1. check if it is a number
QString clearString(s);
clearString.remove(QLatin1Char('\''));
clearString.remove(QLatin1Char('"'));
bool b = false;
int i = clearString.toInt( &b );
if ( b )
return Soprano::LiteralValue( i );
double d = clearString.toDouble( &b );
if ( b )
return Soprano::LiteralValue( d );
// 2. no number - continue with the original string
// no globbing if we have quotes or if there already is a wildcard
if ( s[0] == QLatin1Char('\'') ||
s[0] == QLatin1Char('\"') ) {
return s;
}
//
// we can only do query term globbing for strings longer than 3 chars
//
if( globbing && s.length() > 3 && !s.endsWith('*') && !s.endsWith('?') )
return QString(s + '*');
else
return s;
}
bool positiveTerm( const QString& s_ ) {
const QString s = s_.toLower().simplified();
if(s.isEmpty())
return true;
else if(s == "+")
return true;
else if(s == "-")
return false;
else if(s == "!")
return false;
else if(s == "not")
return false;
else //unrecognized capture
return true;
}
// A filename pattern needs to contain one dot and at least one '*' or '?':
// *.mp3
// hello?.txt
// hello?.*
// test*.???
bool isFilenamePattern( const QString& s )
{
return( !s.contains(' ') &&
s.count('.') == 1 &&
s.count('*') + s.count('?') > 0 );
}
Nepomuk::Query::ComparisonTerm createFilenamePatternTerm( const QString& s )
{
QString regex = QRegExp::escape(s);
regex.replace( "\\*", QLatin1String( ".*" ) );
regex.replace( "\\?", QLatin1String( "." ) );
regex.replace("\\", "\\\\");
regex.prepend('^');
regex.append('$');
return Nepomuk::Query::ComparisonTerm( Nepomuk::Vocabulary::NFO::fileName(),
Nepomuk::Query::LiteralTerm( regex ),
Nepomuk::Query::ComparisonTerm::Regexp );
}
/**
* Merging literal terms is an optimization which is based on the assumption that most
* users want to search for the full text terms they enter in the value of the same
* property.
* Since merging two literals "foo" and "bar" into one term "foo AND bar" effectively
* changes the result set (the former allows that "foo" occurs in a property value
* different from "bar" while the latter forces them to occur in the same.)
* But the resulting query is much faster.
*/
Nepomuk::Query::Term mergeLiteralTerms( const Nepomuk::Query::Term& term )
{
if( term.isAndTerm() ) {
AndTerm mergedTerm;
QStringList fullTextTerms;
Q_FOREACH( const Term& st, term.toAndTerm().subTerms() ) {
if( st.isLiteralTerm() ) {
fullTextTerms << st.toLiteralTerm().value().toString();
}
else {
mergedTerm.addSubTerm( st );
}
}
mergedTerm.addSubTerm( LiteralTerm( fullTextTerms.join( QString::fromLatin1(" AND ") ) ) );
return mergedTerm.optimized();
}
else {
return term;
}
}
/**
* Tries to setup the sub-term of the given comparisonterm according to its property.
* This can fail if the sub-term cannot be converted to the property's range.
*
* This is used in QueryParser::Private::resolveFields
*/
bool setupComparisonTermSubTerm(Nepomuk::Query::ComparisonTerm& ct)
{
// property with resource range
if(ct.property().range().isValid()) {
if(ct.subTerm().isLiteralTerm()) {
// here we need a string. everything else does not work since we match labels
return ct.subTerm().toLiteralTerm().value().isString();
}
else {
return true;
}
}
// property with literal range
else if(ct.subTerm().isLiteralTerm()) {
// only strings can be matched via bif:contains
if(ct.comparator() == Nepomuk::Query::ComparisonTerm::Contains &&
ct.property().literalRangeType().dataType() != QVariant::String) {
ct.setComparator(Nepomuk::Query::ComparisonTerm::Equal);
}
// try to convert the value via QVariant
QVariant v = ct.subTerm().toLiteralTerm().value().variant();
if(v.convert(ct.property().literalRangeType().dataType())) {
ct.setSubTerm(Nepomuk::Query::LiteralTerm(v));
return true;
}
// try some heuristics
else {
if(ct.property().literalRangeType().dataType() == QVariant::DateTime) {
// if it is an int with 4 digits it is very likely a year
if(ct.subTerm().toLiteralTerm().value().isInt() ) {
const int year = ct.subTerm().toLiteralTerm().value().toInt();
if( year >= 1000 && year <= 9999) {
QDate date(year, 1, 1);
QTime time(0,0);
// greater than last day in same year
// greater or equal than the first day in same year
// smaller than first day in same year
// smaller or equal than last day in same year
if(ct.comparator() == ComparisonTerm::Greater ||
ct.comparator() == ComparisonTerm::SmallerOrEqual) {
date.setYMD(date.year(), 12, 31);
time.setHMS(23,59,59,999);
}
ct.setSubTerm(Nepomuk::Query::LiteralTerm(QDateTime(date, time)));
return true;
}
}
}
}
}
// fallback
return false;
}
#ifndef Q_CC_MSVC
#warning Make the parser handle different data, time, and datetime encodings as well as suffixes like MB or GB
#endif
#if 0
QDateTime parseDateTime( const Soprano::LiteralValue& literal )
{
//TODO: change to DateTime parser once complete
Nepomuk::Search::DateParser date( literal.toString() );
if( date.hasDate() ) {
return QDateTime( date.getDate() );
}
else {
Nepomuk::Search::TimeParser time( literal.toString() );
if(time.hasTime() )
return QDateTime(QDate::currentDate(), time.next() );
else
return QDateTime(); //return invalid datetime
}
}
Soprano::LiteralValue parseSizeType( const Soprano::LiteralValue& literal )
{
const double KiB = 1024.0;
const double MiB = KiB * 1024.0;
const double GiB = MiB * 1024.0;
const double TiB = GiB * 1024.0;
const double KB = 1000.0;
const double MB = KB * 1000.0;
const double GB = MB * 1000.0;
const double TB = GB * 1000.0;
QHash<QString, double> sizes;
sizes.insert( "KiB", KiB );
sizes.insert( "MiB", MiB );
sizes.insert( "GiB", GiB );
sizes.insert( "TiB", TiB );
sizes.insert( "KB", KB );
sizes.insert( "MB", MB );
sizes.insert( "GB", GB );
sizes.insert ("TB", TB );
for ( QHash<QString, double>::const_iterator i = sizes.constBegin();
i != sizes.constEnd(); ++i ) {
QRegExp cur( QString("^([\\d]+.?[\\d]*)[\\s]*%1$").arg( i.key() ) );
if( cur.indexIn( literal.toString() ) != -1 ) {
double value = cur.cap( 1 ).toDouble();
double newValue = value * i.value();
kDebug() << "Found value" << value << i.key() << "->" << newValue;
return Soprano::LiteralValue( newValue );
}
}
return literal;
}
Nepomuk::Query::Term resolveLiteralValues( const Nepomuk::Query::Term& term )
{
switch( term.type() ) {
case Nepomuk::Query::Term::Comparison: {
Nepomuk::Query::ComparisonTerm cterm = term.toComparisonTerm();
Nepomuk::Types::Property p( cterm.property() );
if ( p.literalRangeType().isValid() ) {
Q_ASSERT( cterm.subTerm().isLiteralTerm() );
Nepomuk::Query::ComparisonTerm newTerm;
newTerm.setComparator( cterm.comparator() );
newTerm.setProperty( QUrl(cterm.property()) );
// now try to resolve the literal
const Nepomuk::Query::LiteralTerm subTerm = cterm.subTerm().toLiteralTerm();
const Nepomuk::Types::Literal lt = p.literalRangeType();
if ( lt.dataType() == QVariant::DateTime &&
!subTerm.value().isDateTime() ) {
QDateTime dateTime = parseDateTime( subTerm.value() );
if ( dateTime.isValid() ) {
newTerm.setSubTerm( Nepomuk::Query::LiteralTerm( dateTime ) );
return newTerm;
}
}
else if ( lt.dataType() == QVariant::Int &&
!subTerm.value().isInt() ) {
newTerm.setSubTerm( Nepomuk::Query::LiteralTerm( parseSizeType( subTerm.value() ) ) );
return newTerm;
}
}
return term;
}
case Nepomuk::Query::Term::And:
case Nepomuk::Query::Term::Or: {
QList<Nepomuk::Query::Term> newSubTerms;
foreach( const Nepomuk::Query::Term& t, static_cast<const Nepomuk::Query::GroupTerm&>( term ).subTerms() ) {
newSubTerms << resolveLiteralValues(t);
}
if ( term.isAndTerm() )
return Nepomuk::Query::AndTerm( newSubTerms );
else
return Nepomuk::Query::OrTerm( newSubTerms );
}
default:
return term;
}
}
#endif
// a field differs from a plain term in that it does never allow comparators
const char* s_fieldNamePattern = "([^\\s\"':=<>]+|(?:([\"'])[^\"':=<>]+\\%1))";
const char* s_plainTermPattern = "([^-][^\\s\"':=<>]*|(?:([\"'])[^\"']+\\%1))";
- const char* s_inExclusionPattern = "((?:[\\+\\-\\s*]|\\!\\s*|[nN][oO][tT]\\s+)?)";
+ const char* s_inExclusionPattern = "((?:[\\+\\-\\!]\\s*|[nN][oO][tT]\\s+)?)";
const char* s_uriPattern = "<([^<>]+)>";
const char* s_comparatorPattern = "(:|\\<=|\\>=|=|\\<|\\>)";
/**
* Creating QRegExp is expensive, copying them is cheap. Thus, we keep static
* instances of the regexps around which we only have to create once.
*/
class QueryParserRegExpPool
{
public:
QueryParserRegExpPool()
: plainTermRx( QLatin1String(s_inExclusionPattern)
+ QString::fromLatin1(s_plainTermPattern).arg( 3 ) ),
fieldRx( QLatin1String(s_inExclusionPattern)
+ QString::fromLatin1(s_fieldNamePattern).arg( 3 )
+ QLatin1String(s_comparatorPattern)
+ QString::fromLatin1(s_plainTermPattern).arg( 6 ) ),
propertyRx( QLatin1String(s_inExclusionPattern)
+ QLatin1String(s_uriPattern)
+ QLatin1String(s_comparatorPattern)
+ QString::fromLatin1(s_plainTermPattern).arg( 5 ) ),
resourceRx( QLatin1String(s_inExclusionPattern)
+ QLatin1String(s_uriPattern)
+ QLatin1String("(?::|=)")
+ QLatin1String(s_uriPattern) ),
fieldFieldRx( QLatin1String(s_inExclusionPattern)
+ QString::fromLatin1(s_fieldNamePattern).arg( 3 )
+ QLatin1String(s_comparatorPattern)
+ QLatin1String("\\(")
+ QString::fromLatin1(s_fieldNamePattern).arg( 6 )
+ QLatin1String(s_comparatorPattern)
+ QString::fromLatin1(s_plainTermPattern).arg( 9 )
+ QLatin1String("\\)") )
{
}
// match a simple search text
// captures: 1 - The optional + or - sign (may be empty)
// 2 - the search text (including optional paranthesis)
QRegExp plainTermRx;
// match a field search term: fieldname + relation (:, =, etc) + search text with optional paranthesis
// captures: 1 - The optional + or - sign (may be empty)
// 2 - fieldname
// 3 - relation
// 4 - search text (including optional paranthesis)
QRegExp fieldRx;
// match a property URI search term: property URI + relation (:, =, etc) + search text with optional paranthesis
// captures: 1 - The optional + or - sign (may be empty)
// 2 - property URI
// 3 - relation
// 4 - search text (including optional paranthesis)
QRegExp propertyRx;
// match a property URI search term: property URI + relation (:, =, etc) + resource URI
// captures: 1 - The optional + or - sign (may be empty)
// 2 - property URI
// 3 - resource URI
QRegExp resourceRx;
QRegExp fieldFieldRx;
};
// the one global instance used for the statis QueryParser methods
K_GLOBAL_STATIC( QueryParserRegExpPool, s_regExpPool )
}
class Nepomuk::Query::QueryParser::Private
{
public:
QueryParser* q;
QSet<QString> andKeywords;
QSet<QString> orKeywords;
mutable QHash<QString, QList<Types::Property> > fieldMatchCache;
QMutex fieldMatchCacheMutex;
/// set by resolveFields if the query is in fact invalid
bool m_invalidQuery;
/**
* Resolve the fields in all ComparisonTerms by looking up possible matching
* properties.
* This method will set m_invalidQuery in case there is one term for which
* no property can be matched.
*/
Nepomuk::Query::Term resolveFields( const Nepomuk::Query::Term& term );
};
Term QueryParser::Private::resolveFields(const Term &term)
{
switch( term.type() ) {
case Nepomuk::Query::Term::And:
case Nepomuk::Query::Term::Or: {
QList<Nepomuk::Query::Term> newSubTerms;
foreach( const Nepomuk::Query::Term& t, static_cast<const Nepomuk::Query::GroupTerm&>( term ).subTerms() ) {
Nepomuk::Query::Term resolvedTerm = resolveFields(t);
if ( resolvedTerm.isValid() )
newSubTerms << resolvedTerm;
else
return Nepomuk::Query::Term();
}
if ( term.isAndTerm() )
return Nepomuk::Query::AndTerm( newSubTerms );
else
return Nepomuk::Query::OrTerm( newSubTerms );
}
case Nepomuk::Query::Term::Negation: {
return Nepomuk::Query::NegationTerm::negateTerm( resolveFields( term.toNegationTerm().subTerm() ) );
}
case Nepomuk::Query::Term::Comparison: {
Nepomuk::Query::ComparisonTerm newTerm;
newTerm.setComparator( term.toComparisonTerm().comparator() );
newTerm.setProperty( term.toComparisonTerm().property() );
newTerm.setSubTerm( resolveFields( term.toComparisonTerm().subTerm() ) );
// A very dumb test to see if the property is set or not: does the URI have a scheme.
// With a proper parser and in-place property matching there will be no need for this anymore
if ( newTerm.property().uri().scheme().isEmpty() ) {
QList<Nepomuk::Types::Property> properties = q->matchProperty( term.toComparisonTerm().property().uri().toString() );
// we only use a max of 4 properties, otherwise the queries get too big
// in addition we try to exclude properties which do not make sense:
// - numerical values can never match a resource
Nepomuk::Query::OrTerm orTerm;
for( int i = 0; i < properties.count() && orTerm.subTerms().count() < 4; ++i ) {
const Nepomuk::Types::Property& property = properties[i];
Nepomuk::Query::ComparisonTerm t( newTerm );
t.setProperty( property );
if(setupComparisonTermSubTerm(t)) {
orTerm.addSubTerm( t );
}
}
if(!orTerm.isValid()) {
m_invalidQuery = true;
return Nepomuk::Query::Term();
}
return orTerm.optimized();
}
}
default:
return term;
}
}
Nepomuk::Query::QueryParser::QueryParser()
: d( new Private() )
{
d->q = this;
QString andListStr = i18nc( "Boolean AND keyword in desktop search strings. "
"You can add several variants separated by spaces, "
"e.g. retain the English one alongside the translation; "
"keywords are not case sensitive. Make sure there is "
"no conflict with the OR keyword.",
"and" );
foreach ( const QString &andKeyword, andListStr.split( ' ', QString::SkipEmptyParts ) ) {
d->andKeywords.insert( andKeyword.toLower() );
}
QString orListStr = i18nc( "Boolean OR keyword in desktop search strings. "
"You can add several variants separated by spaces, "
"e.g. retain the English one alongside the translation; "
"keywords are not case sensitive. Make sure there is "
"no conflict with the AND keyword.",
"or" );
foreach ( const QString &orKeyword, orListStr.split( ' ', QString::SkipEmptyParts ) ) {
d->orKeywords.insert( orKeyword.toLower() );
}
}
Nepomuk::Query::QueryParser::~QueryParser()
{
delete d;
}
QList<Nepomuk::Types::Property> Nepomuk::Query::QueryParser::matchProperty( const QString& fieldName ) const
{
kDebug() << fieldName;
QMutexLocker lock( &d->fieldMatchCacheMutex );
QHash<QString, QList<Types::Property> >::ConstIterator it = d->fieldMatchCache.constFind( fieldName );
if( it != d->fieldMatchCache.constEnd() ) {
return it.value();
}
else {
lock.unlock();
QList<Nepomuk::Types::Property> results;
//
// Due to the limited number of properties in the database a REGEX filter
// is actually faster than a fulltext query via bif:contains (this is what
// experiments showed).
//
QString query = QString( "select distinct ?p where { "
"graph ?g { "
"?p a %1 . "
"?p %2 ?l . "
"FILTER(REGEX(STR(?l),'%3*','i') || REGEX(STR(?p),'%3*','i')) . "
"} "
"}" )
.arg( Soprano::Node::resourceToN3( Soprano::Vocabulary::RDF::Property() ) )
.arg( Soprano::Node::resourceToN3( Soprano::Vocabulary::RDFS::label() ) )
.arg( fieldName );
kDebug() << "Match query:" << query;
Soprano::QueryResultIterator labelHits
= Nepomuk::ResourceManager::instance()->mainModel()->executeQuery( query, Soprano::Query::QueryLanguageSparql );
while ( labelHits.next() ) {
QUrl property = labelHits.binding( "p" ).uri();
results << property;
kDebug() << "Found property match" << property;
}
lock.relock();
d->fieldMatchCache.insert( fieldName, results );
return results;
}
}
Nepomuk::Query::Query Nepomuk::Query::QueryParser::parse( const QString& query ) const
{
return parse( query, NoParserFlags );
}
Nepomuk::Query::Query Nepomuk::Query::QueryParser::parse( const QString& query, ParserFlags flags ) const
{
// TODO: a "real" parser which can handle all of the Xesam user language
// This one for example does not handle nesting at all.
Nepomuk::Query::Query final;
QList<Term> terms;
bool inOrBlock = false;
bool inAndBlock = false;
int pos = 0;
// create local copies of the regexps for thread safety purposes
const QRegExp resourceRx = s_regExpPool->resourceRx;
const QRegExp propertyRx = s_regExpPool->propertyRx;
const QRegExp fieldFieldRx = s_regExpPool->fieldFieldRx;
const QRegExp fieldRx = s_regExpPool->fieldRx;
const QRegExp plainTermRx = s_regExpPool->plainTermRx;
while ( pos < query.length() ) {
// skip whitespace
while ( pos < query.length() && query[pos].isSpace() ) {
kDebug() << "Skipping space at" << pos;
++pos;
}
Term term;
if ( pos < query.length() ) {
if ( resourceRx.indexIn( query, pos ) == pos ) {
kDebug() << "matched resource term at" << pos << resourceRx.cap( 0 );
term = ComparisonTerm( tryToBeIntelligentAboutParsingUrl( resourceRx.cap( 2 ) ),
ResourceTerm( tryToBeIntelligentAboutParsingUrl( resourceRx.cap( 3 ) ) ),
ComparisonTerm::Equal );
if ( !positiveTerm( resourceRx.cap( 1 ) ) ) {
term = NegationTerm::negateTerm( term );
}
pos += resourceRx.matchedLength();
}
else if ( propertyRx.indexIn( query, pos ) == pos ) {
kDebug() << "matched property term at" << pos << propertyRx.cap( 0 );
ComparisonTerm ct;
ct.setProperty( tryToBeIntelligentAboutParsingUrl( propertyRx.cap( 2 ) ) );
ct.setSubTerm( LiteralTerm( createLiteral( propertyRx.cap( 4 ), flags&QueryTermGlobbing ) ) );
QString comparator = propertyRx.cap( 3 );
ct.setComparator( fieldTypeRelationFromString( comparator ) );
pos += propertyRx.matchedLength();
if ( !positiveTerm(propertyRx.cap( 1 ) ) ) {
term = NegationTerm::negateTerm( ct );
}
else {
term = ct;
}
}
else if ( fieldFieldRx.indexIn( query, pos ) == pos ) {
kDebug() << "matched field field term at" << pos
<< fieldFieldRx.cap( 0 )
<< fieldFieldRx.cap( 2 )
<< fieldFieldRx.cap( 4 )
<< fieldFieldRx.cap( 5 )
<< fieldFieldRx.cap( 7 )
<< fieldFieldRx.cap( 8 );
ComparisonTerm ct;
ct.setProperty( QUrl(stripQuotes( fieldFieldRx.cap( 2 ) )) );
QString comparator = fieldFieldRx.cap( 4 );
ct.setComparator( fieldTypeRelationFromString( comparator ) );
ct.setSubTerm( ComparisonTerm( QUrl(stripQuotes( fieldFieldRx.cap( 5 ) )),
LiteralTerm( createLiteral( fieldFieldRx.cap( 8 ), flags&QueryTermGlobbing ) ),
fieldTypeRelationFromString( fieldFieldRx.cap( 7 ) ) ) );
pos += fieldFieldRx.matchedLength();
if ( !positiveTerm( fieldFieldRx.cap( 1 ) ) ) {
term = NegationTerm::negateTerm( ct );
}
else {
term = ct;
}
}
else if ( fieldRx.indexIn( query, pos ) == pos ) {
kDebug() << "matched field term at" << pos << fieldRx.cap( 0 ) << fieldRx.cap( 2 ) << fieldRx.cap( 4 ) << fieldRx.cap( 5 );
if( stripQuotes ( fieldRx.cap( 2 ) ).compare( QString( "inFolder" ), Qt::CaseInsensitive ) == 0 ) {
KUrl url( fieldRx.cap( 5 ) );
kDebug() << "found include path" << url;
FileQuery fileQuery(final);
if ( positiveTerm( fieldRx.cap( 1 ) ) )
fileQuery.addIncludeFolder(url);
else
fileQuery.addExcludeFolder(url);
final = fileQuery;
pos += fieldRx.matchedLength();
}
else {
ComparisonTerm ct;
ct.setProperty( QUrl( stripQuotes( fieldRx.cap( 2 ) ) ) );
ct.setSubTerm( LiteralTerm( createLiteral( fieldRx.cap( 5 ), flags&QueryTermGlobbing ) ) );
QString comparator = fieldRx.cap( 4 );
ct.setComparator( fieldTypeRelationFromString( comparator ) );
pos += fieldRx.matchedLength();
if ( !positiveTerm(fieldRx.cap( 1 ) ) ) {
NegationTerm nt;
nt.setSubTerm( ct );
term = nt;
}
else {
term = ct;
}
}
}
else if ( plainTermRx.indexIn( query, pos ) == pos ) {
QString value = plainTermRx.cap( 2 );
if ( d->orKeywords.contains( value.toLower() ) ) {
inOrBlock = true;
}
else if ( d->andKeywords.contains( value.toLower() ) ) {
inAndBlock = true;
}
else {
kDebug() << "matched literal at" << pos << value;
if( flags&DetectFilenamePattern && isFilenamePattern(value) ) {
term = createFilenamePatternTerm( value );
}
else {
term = LiteralTerm( createLiteral( value, flags&QueryTermGlobbing ) );
}
if ( !positiveTerm(plainTermRx.cap( 1 ) ) ) {
term = NegationTerm::negateTerm( term );
}
}
pos += plainTermRx.matchedLength();
}
else {
kDebug() << "Invalid query at" << pos << query;
return Query();
}
if ( term.isValid() ) {
if ( inOrBlock && !terms.isEmpty() ) {
OrTerm orTerm;
orTerm.addSubTerm( terms.takeLast() );
orTerm.addSubTerm( term );
terms.append( orTerm );
}
else if ( inAndBlock && !terms.isEmpty() ) {
AndTerm andTerm;
andTerm.addSubTerm( terms.takeLast() );
andTerm.addSubTerm( term );
terms.append( andTerm );
}
else {
terms.append( term );
}
}
}
}
if ( terms.count() == 1 ) {
final.setTerm( terms[0] );
}
else if ( terms.count() > 0 ) {
AndTerm t;
t.setSubTerms( terms );
final.setTerm( t );
}
d->m_invalidQuery = false;
final.setTerm( mergeLiteralTerms( d->resolveFields( final.term() ) ) );
if(d->m_invalidQuery) {
return Query();
}
else {
return final;
}
}
// static
Nepomuk::Query::Query Nepomuk::Query::QueryParser::parseQuery( const QString& query )
{
QueryParser parser;
return parser.parse( query );
}
// static
Nepomuk::Query::Query Nepomuk::Query::QueryParser::parseQuery( const QString& query, ParserFlags flags )
{
QueryParser parser;
return parser.parse( query, flags );
}
diff --git a/plasma/data/services/plasma.protocol b/plasma/data/services/plasma.protocol
index f8bd812197..fc4ccc209d 100644
--- a/plasma/data/services/plasma.protocol
+++ b/plasma/data/services/plasma.protocol
@@ -1,65 +1,66 @@
[Protocol]
protocol=plasma
exec=plasma-remote-helper %u
input=none
output=none
Icon=plasma
Description=A protocol for Plasma services
Description[ar]=ميفاق لخدمات بلازما
Description[bg]=Протокол за услуги Plasma
Description[bs]=Protokol Plazma usluga
Description[ca]=Un protocol pels serveis del Plasma
Description[ca@valencia]=Un protocol pels serveis del Plasma
Description[cs]=Protokol pro služby Plasma
Description[da]=En protokol til Plasma-tjenester
Description[de]=Ein Protokoll für Plasma-Dienste
Description[el]=Ένα πρωτόκολλο υπηρεσιών Plasma
Description[es]=Un protocolo para los servicios de Plasma
Description[et]=Plasma teenuste protokoll
Description[eu]=Plasma zerbitzuentzako protokolo bat
Description[fi]=Plasma-palvelujen yhteyskäytäntö
Description[fr]=Un protocole pour les services Plasma
Description[ga]=Prótacal le haghaidh seirbhísí Plasma
Description[gl]=Un protocolo para servizos do Plasma
Description[he]=פרוטוקול עבור הרכיבים של Plasma
Description[hr]=Protokol za servise u Plasmi
Description[hu]=Protokoll a Plazma-szolgáltatáshoz
Description[ia]=un protocollo per servicios de Plasma
+Description[id]=Protokol untuk layanan Plasma
Description[is]=Samskiptamáti fyrir Plasma-þjónustur
Description[it]=Un protocollo per servizi plasma
Description[ja]=Plasma サービスのためのプロトコル
Description[kk]=Plasma қызметінің протоколы
Description[km]=ពិធីការ​សម្រាប់​សេវា​កម្ម​ប្លាស្មា
Description[ko]=Plasma 서비스 프로토콜
Description[lv]=Plasma servisu protokols
Description[nb]=En protokoll for plasma-tjenester
Description[nds]=En Protokoll för Plasma-Deensten
Description[nl]=Een protocol voor Plasma-services
Description[pa]=ਪਲਾਜ਼ਮਾ ਸਰਵਿਸ ਲਈ ਪਰੋਟੋਕਾਲ
Description[pl]=Protokół dla usług Plazmy
Description[pt]=Um protocolo para os serviços do Plasma
Description[pt_BR]=Protocolo para os serviços do Plasma
Description[ro]=Un protocol pentru servicii Plasma
Description[ru]=Протокол для служб Plasma
Description[se]=Protokolla Plasma-bálvalusaid várás
Description[sk]=Protokol pre Plasma služby
Description[sl]=Protokol za storitve Plasme
Description[sr]=Протокол за плазма сервисе
Description[sr@ijekavian]=Протокол за плазма сервисе
Description[sr@ijekavianlatin]=Protokol za plasma servise
Description[sr@latin]=Protokol za plasma servise
Description[sv]=Ett protokoll för Plasma-tjänster
Description[tg]=Протоколи хидматҳои Plasma
Description[tr]=Plasma servisleri için bir protokol
Description[tt]=Plasma хезмәте өчен беркетмә
Description[ug]=پلازما(Plasma) مۇلازىمىتىنىڭ كېلىشىمى
Description[uk]=Протокол для служб Плазми
Description[vi]=Một giao thức cho các dịch vụ Plasma
Description[x-test]=xxA protocol for Plasma servicesxx
Description[zh_CN]=Plasma 服务协议
Description[zh_TW]=Plasma 服務協定
helper=true
Class=:internet
diff --git a/plasma/data/servicetypes/plasma-service.desktop b/plasma/data/servicetypes/plasma-service.desktop
index 454722c77d..fb5ea61484 100644
--- a/plasma/data/servicetypes/plasma-service.desktop
+++ b/plasma/data/servicetypes/plasma-service.desktop
@@ -1,64 +1,65 @@
[Desktop Entry]
Type=ServiceType
X-KDE-ServiceType=Plasma/Service
Comment=Plasma service
Comment[ar]=خدمة بلازما
Comment[bg]=Услуга Plasma
Comment[bs]=Plazma usluga
Comment[ca]=Servei del Plasma
Comment[ca@valencia]=Servei del Plasma
Comment[cs]=Služba Plasma
Comment[da]=Plasma-tjeneste
Comment[de]=Plasma-Dienst
Comment[el]=Υπηρεσία plasma
Comment[en_GB]=Plasma service
Comment[es]=Servicio de Plasma
Comment[et]=Plasma teenus
Comment[eu]=Plasma zerbitzua
Comment[fi]=Plasma-palvelu
Comment[fr]=Service Plasma
Comment[ga]=Seirbhís Plasma
Comment[gl]=Servizo do Plasma
Comment[he]=שירות של Plasma
Comment[hr]=Servis u Plasmi
Comment[hu]=Plazma-szolgáltatás
Comment[ia]=Servicio de Plasma
+Comment[id]=Layanan Plasma
Comment[is]=Plasma-þjónusta
Comment[it]=Servizio plasma
Comment[ja]=Plasma サービス
Comment[kk]=Plasma қызметі
Comment[km]=សេវា​​ប្លាស្មា
Comment[ko]=Plasma 서비스
Comment[lt]=Plasma tarnyba
Comment[lv]=Plasma serviss
Comment[nb]=Plasma-tjeneste
Comment[nds]=Plasma-Deenst
Comment[nl]=Plasma-service
Comment[pa]=ਪਲਾਜ਼ਮਾ ਸਰਵਿਸ
Comment[pl]=Usługa Plazmy
Comment[pt]=Serviço do Plasma
Comment[pt_BR]=Serviço do Plasma
Comment[ro]=Servicu Plasma
Comment[ru]=Служба Plasma
Comment[se]=Plasma-bálvalus
Comment[si]=ප්ලස්මා සේවා
Comment[sk]=Služba Plasma
Comment[sl]=Storitev Plasme
Comment[sq]=Shërbimi plazma
Comment[sr]=Плазма сервис
Comment[sr@ijekavian]=Плазма сервис
Comment[sr@ijekavianlatin]=Plasma servis
Comment[sr@latin]=Plasma servis
Comment[sv]=Plasma-tjänst
Comment[tg]=Хидматҳои Plasma
Comment[th]=บริการของพลาสมา
Comment[tr]=Plasma servisi
Comment[tt]=Plasma хезмәте
Comment[ug]=Plasma مۇلازىمىتى
Comment[uk]=Служба Плазми
Comment[vi]=Dịch vụ Plasma
Comment[wa]=Siervice di Plasma
Comment[x-test]=xxPlasma servicexx
Comment[zh_CN]=Plasma 服务
Comment[zh_TW]=Plasma 服務
diff --git a/plasma/data/servicetypes/plasma-toolbox.desktop b/plasma/data/servicetypes/plasma-toolbox.desktop
index 612f32a882..812815fdff 100644
--- a/plasma/data/servicetypes/plasma-toolbox.desktop
+++ b/plasma/data/servicetypes/plasma-toolbox.desktop
@@ -1,65 +1,66 @@
[Desktop Entry]
Type=ServiceType
X-KDE-ServiceType=Plasma/ToolBox
Comment=Plasma toolbox
Comment[ar]=صندوق أدوات بلازما
Comment[bg]=Инструменти за Plasma
Comment[bs]=Plazma alatna traka
Comment[ca]=Caixa d'eines del Plasma
Comment[ca@valencia]=Caixa d'eines del Plasma
Comment[cs]=Plasma toolbox
Comment[da]=Plasma værktøjskasse
Comment[de]=Plasma-Werkzeugkasten
Comment[el]=Εργαλειοθήκη Plasma
Comment[en_GB]=Plasma toolbox
Comment[es]=Caja de herramientas de Plasma
Comment[et]=Plasma tööriistakast
Comment[eu]=Plasma tresna-kutxa
Comment[fi]=Plasma-työkalurivi
Comment[fr]=Boîte à outils Plasma
Comment[ga]=Bosca uirlisí Plasma
Comment[gl]=Barra de ferramentas do Plasma
Comment[he]=ארגז כלים של Plasma
Comment[hr]=Plasma alatni okvir
Comment[hu]=Plazma-eszközkészlet
Comment[ia]=Instrumentario de Plasma
+Comment[id]=Kotak alat Plasma
Comment[is]=Plasma verkfærasafn
Comment[it]=Barra degli strumenti plasma
Comment[ja]=Plasma ツールボックス
Comment[kk]=Plasma құралдары
Comment[km]=ប្រអប់​ឧបករណ៍​ប្លាស្មា
Comment[ko]=Plasma 도구 상자
Comment[ku]=Qutiya amûrên Plasma
Comment[lt]=Pasma įrankinė
Comment[lv]=Plasma rīkkopa
Comment[nb]=Plasma-verktøykasse
Comment[nds]=Plasma-Warktüüchkist
Comment[nl]=Plasma-hulpmiddelen
Comment[pa]=ਪਲਾਜ਼ਮਾ ਟੂਲਬਾਕਸ
Comment[pl]=Przybornik Plazmy
Comment[pt]=Barra de ferramentas do Plasma
Comment[pt_BR]=Barra de ferramentas do Plasma
Comment[ro]=Cutie de unelte Plasma
Comment[ru]=Панель инструментов Plasma
Comment[se]=Plasma-reaidoboksa
Comment[si]=ප්ලස්මා මෙවලම් පෙට්ටිය
Comment[sk]=Nástroje Plasma
Comment[sl]=Orodjana v Plasmi
Comment[sr]=Плазма алатница
Comment[sr@ijekavian]=Плазма алатница
Comment[sr@ijekavianlatin]=Plasma alatnica
Comment[sr@latin]=Plasma alatnica
Comment[sv]=Plasma verktygslåda
Comment[ta]=பிளாஸ்மா கருவிப்பட்டி
Comment[tg]=Лавҳаи воситаҳои Plasma
Comment[th]=กล่องเครื่องมือของพลาสมา
Comment[tr]=Plasma araç kutusu
Comment[tt]=Plasma корал панеле
Comment[ug]=Plasma قورال ساندۇقى
Comment[uk]=Набір інструментів Плазми
Comment[vi]=Hộp công cụ Plasma
Comment[wa]=Boesse ås usteyes di Plasma
Comment[x-test]=xxPlasma toolboxxx
Comment[zh_CN]=Plasma 工具箱
Comment[zh_TW]=Plasma 工具盒
diff --git a/plasma/paintutils.cpp b/plasma/paintutils.cpp
index b1d2ec5aa2..d2ef4d6917 100644
--- a/plasma/paintutils.cpp
+++ b/plasma/paintutils.cpp
@@ -1,333 +1,334 @@
/*
* Copyright 2005 by Aaron Seigo <aseigo@kde.org>
* Copyright 2008 by Andrew Lake <jamboarder@yahoo.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU Library General Public License as
* published by the Free Software Foundation; either version 2, 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 Library General Public
* License along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <paintutils.h>
#include <QImage>
#include <QPainter>
#include <QPaintEngine>
#include <QPixmap>
#include "private/effects/blur.cpp"
#include "private/effects/halopainter_p.h"
#include "svg.h"
namespace Plasma
{
namespace PaintUtils
{
void shadowBlur(QImage &image, int radius, const QColor &color)
{
if (radius < 1) {
return;
}
if (image.isNull()) {
return;
}
expblur<16, 7>(image, radius);
QPainter p(&image);
p.setCompositionMode(QPainter::CompositionMode_SourceIn);
p.fillRect(image.rect(), color);
p.end();
}
//TODO: we should have shadowText methods that paint the results directly into a QPainter passed in
QPixmap shadowText(QString text, QColor textColor, QColor shadowColor, QPoint offset, int radius)
{
return shadowText(text, qApp->font(), textColor, shadowColor, offset, radius);
}
QPixmap shadowText(QString text, const QFont &font, QColor textColor, QColor shadowColor, QPoint offset, int radius)
{
//don't try to paint stuff on a future null pixmap because the text is empty
if (text.isEmpty()) {
return QPixmap();
}
// Draw text
QFontMetrics fm(font);
QRect textRect = fm.boundingRect(text);
QPixmap textPixmap(textRect.width(), fm.height());
textPixmap.fill(Qt::transparent);
QPainter p(&textPixmap);
p.setPen(textColor);
p.setFont(font);
// FIXME: the center alignment here is odd: the rect should be the size needed by
// the text, but for some fonts and configurations this is off by a pixel or so
// and "centering" the text painting 'fixes' that. Need to research why
// this is the case and determine if we should be painting it differently here,
// doing soething different with the boundingRect call or if it's a problem
// in Qt itself
p.drawText(textPixmap.rect(), Qt::AlignCenter, text);
p.end();
//Draw blurred shadow
QImage img(textRect.size() + QSize(radius * 2, radius * 2), QImage::Format_ARGB32_Premultiplied);
img.fill(0);
p.begin(&img);
p.drawImage(QPoint(radius, radius), textPixmap.toImage());
p.end();
shadowBlur(img, radius, shadowColor);
//Compose text and shadow
int addSizeX = qMax(0, qAbs(offset.x()) - radius);
int addSizeY = qMax(0, qAbs(offset.y()) - radius);
QPixmap finalPixmap(img.size() + QSize(addSizeX, addSizeY));
finalPixmap.fill(Qt::transparent);
p.begin(&finalPixmap);
p.drawImage(qMax(0, offset.x()), qMax(0, offset.y()), img);
p.drawPixmap(radius + qMax(0, -offset.x()), radius + qMax(0, -offset.y()), textPixmap);
p.end();
return finalPixmap;
}
QPixmap texturedText(const QString &text, const QFont &font, Plasma::Svg *texture)
{
QFontMetrics fm(font);
//the text will be moved a bit from contentsRect
QRect contentsRect = fm.boundingRect(text).adjusted(0, 0, 2, 2);
contentsRect.moveTo(0,0);
QPixmap pixmap(contentsRect.size());
pixmap.fill(Qt::transparent);
QPainter buffPainter(&pixmap);
buffPainter.setPen(Qt::black);
buffPainter.setFont(font);
buffPainter.drawText(contentsRect, Qt::AlignCenter, text);
buffPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
texture->paint(&buffPainter, contentsRect, "foreground");
buffPainter.end();
//do the shadow
QImage image(pixmap.size() + QSize(2, 2), QImage::Format_ARGB32_Premultiplied);
image.fill(Qt::transparent);
buffPainter.begin(&image);
buffPainter.setFont(font);
buffPainter.drawText(contentsRect.translated(1, 1), Qt::AlignCenter, text);
buffPainter.setCompositionMode(QPainter::CompositionMode_SourceIn);
texture->paint(&buffPainter, contentsRect.adjusted(-1, -1, 1, 1), "shadow");
buffPainter.end();
expblur<16, 7>(image, 1);
//hole in the shadow
buffPainter.begin(&image);
buffPainter.setCompositionMode(QPainter::CompositionMode_DestinationOut);
buffPainter.setFont(font);
buffPainter.drawText(contentsRect.translated(1, 1), Qt::AlignCenter, text);
buffPainter.end();
QPixmap ret(image.size());
ret.fill(Qt::transparent);
buffPainter.begin(&ret);
buffPainter.setCompositionMode(QPainter::CompositionMode_SourceOver);
buffPainter.drawImage(QPoint(0,0), image);
buffPainter.drawPixmap(QPoint(1,1), pixmap);
+ buffPainter.end();
return ret;
}
void drawHalo(QPainter *painter, const QRectF &rect)
{
HaloPainter::instance()->drawHalo(painter, rect.toRect());
}
QPainterPath roundedRectangle(const QRectF &rect, qreal radius)
{
QPainterPath path(QPointF(rect.left(), rect.top() + radius));
path.quadTo(rect.left(), rect.top(), rect.left() + radius, rect.top()); // Top left corner
path.lineTo(rect.right() - radius, rect.top()); // Top side
path.quadTo(rect.right(), rect.top(), rect.right(), rect.top() + radius); // Top right corner
path.lineTo(rect.right(), rect.bottom() - radius); // Right side
path.quadTo(rect.right(), rect.bottom(), rect.right() - radius, rect.bottom()); // Bottom right corner
path.lineTo(rect.left() + radius, rect.bottom()); // Bottom side
path.quadTo(rect.left(), rect.bottom(), rect.left(), rect.bottom() - radius); // Bottom left corner
path.closeSubpath();
return path;
}
void centerPixmaps(QPixmap &from, QPixmap &to)
{
if (from.size() == to.size() && from.hasAlphaChannel() && to.hasAlphaChannel()) {
return;
}
QRect fromRect(from.rect());
QRect toRect(to.rect());
QRect actualRect = QRect(QPoint(0,0), fromRect.size().expandedTo(toRect.size()));
fromRect.moveCenter(actualRect.center());
toRect.moveCenter(actualRect.center());
if (from.size() != actualRect.size() || !from.hasAlphaChannel()) {
QPixmap result(actualRect.size());
result.fill(Qt::transparent);
QPainter p(&result);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.drawPixmap(fromRect.topLeft(), from);
p.end();
from = result;
}
if (to.size() != actualRect.size() || !to.hasAlphaChannel()) {
QPixmap result(actualRect.size());
result.fill(Qt::transparent);
QPainter p(&result);
p.setCompositionMode(QPainter::CompositionMode_Source);
p.drawPixmap(toRect.topLeft(), to);
p.end();
to = result;
}
}
QPixmap transition(const QPixmap &from, const QPixmap &to, qreal amount)
{
if (from.isNull() && to.isNull()) {
return from;
}
if (qFuzzyCompare(amount + 1, qreal(1.0))) {
return from;
}
QRect startRect(from.rect());
QRect targetRect(to.rect());
QSize pixmapSize = startRect.size().expandedTo(targetRect.size());
QRect toRect = QRect(QPoint(0,0), pixmapSize);
targetRect.moveCenter(toRect.center());
startRect.moveCenter(toRect.center());
//paint to in the center of from
QColor color;
color.setAlphaF(amount);
// If the native paint engine supports Porter/Duff compositing and CompositionMode_Plus
QPaintEngine *paintEngine = from.paintEngine();
if (paintEngine &&
paintEngine->hasFeature(QPaintEngine::PorterDuff) &&
paintEngine->hasFeature(QPaintEngine::BlendModes)) {
QPixmap startPixmap(pixmapSize);
startPixmap.fill(Qt::transparent);
QPixmap targetPixmap(pixmapSize);
targetPixmap.fill(Qt::transparent);
QPainter p;
p.begin(&targetPixmap);
p.drawPixmap(targetRect, to);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
p.fillRect(targetRect, color);
p.end();
p.begin(&startPixmap);
p.drawPixmap(startRect, from);
p.setCompositionMode(QPainter::CompositionMode_DestinationOut);
p.fillRect(startRect, color);
p.setCompositionMode(QPainter::CompositionMode_Plus);
p.drawPixmap(targetRect, targetPixmap);
p.end();
return startPixmap;
}
#if defined(Q_WS_X11) && defined(HAVE_XRENDER)
// We have Xrender support
else if (paintEngine && paintEngine->hasFeature(QPaintEngine::PorterDuff)) {
// QX11PaintEngine doesn't implement CompositionMode_Plus in Qt 4.3,
// which we need to be able to do a transition from one pixmap to
// another.
//
// In order to avoid the overhead of converting the pixmaps to images
// and doing the operation entirely in software, this function has a
// specialized path for X11 that uses Xrender directly to do the
// transition. This operation can be fully accelerated in HW.
//
// This specialization can be removed when QX11PaintEngine supports
// CompositionMode_Plus.
QPixmap source(targetPixmap), destination(startPixmap);
source.detach();
destination.detach();
Display *dpy = QX11Info::display();
XRenderPictFormat *format = XRenderFindStandardFormat(dpy, PictStandardA8);
XRenderPictureAttributes pa;
pa.repeat = 1; // RepeatNormal
// Create a 1x1 8 bit repeating alpha picture
Pixmap pixmap = XCreatePixmap(dpy, destination.handle(), 1, 1, 8);
Picture alpha = XRenderCreatePicture(dpy, pixmap, format, CPRepeat, &pa);
XFreePixmap(dpy, pixmap);
// Fill the alpha picture with the opacity value
XRenderColor xcolor;
xcolor.alpha = quint16(0xffff * amount);
XRenderFillRectangle(dpy, PictOpSrc, alpha, &xcolor, 0, 0, 1, 1);
// Reduce the alpha of the destination with 1 - opacity
XRenderComposite(dpy, PictOpOutReverse, alpha, None, destination.x11PictureHandle(),
0, 0, 0, 0, 0, 0, destination.width(), destination.height());
// Add source * opacity to the destination
XRenderComposite(dpy, PictOpAdd, source.x11PictureHandle(), alpha,
destination.x11PictureHandle(),
toRect.x(), toRect.y(), 0, 0, 0, 0, destination.width(), destination.height());
XRenderFreePicture(dpy, alpha);
return destination;
}
#endif
else {
// Fall back to using QRasterPaintEngine to do the transition.
QImage under(pixmapSize, QImage::Format_ARGB32_Premultiplied);
under.fill(Qt::transparent);
QImage over(pixmapSize, QImage::Format_ARGB32_Premultiplied);
over.fill(Qt::transparent);
QPainter p;
p.begin(&over);
p.drawPixmap(targetRect, to);
p.setCompositionMode(QPainter::CompositionMode_DestinationIn);
p.fillRect(over.rect(), color);
p.end();
p.begin(&under);
p.drawPixmap(startRect, from);
p.setCompositionMode(QPainter::CompositionMode_DestinationOut);
p.fillRect(startRect, color);
p.setCompositionMode(QPainter::CompositionMode_Plus);
p.drawImage(toRect.topLeft(), over);
p.end();
return QPixmap::fromImage(under);
}
}
} // PaintUtils namespace
} // Plasma namespace
diff --git a/plasma/tests/testcontainmentactionsplugin/plasma-containmentactions-test.desktop b/plasma/tests/testcontainmentactionsplugin/plasma-containmentactions-test.desktop
index 5488c7326a..36a85ec8fd 100644
--- a/plasma/tests/testcontainmentactionsplugin/plasma-containmentactions-test.desktop
+++ b/plasma/tests/testcontainmentactionsplugin/plasma-containmentactions-test.desktop
@@ -1,137 +1,139 @@
[Desktop Entry]
Name=Test
Name[ar]=اختبار
Name[bg]=Проба
Name[bs]=Test
Name[ca]=Prova
Name[ca@valencia]=Prova
Name[cs]=Test
Name[da]=Test
Name[de]=Test
Name[el]=Δοκιμή
Name[en_GB]=Test
Name[es]=Prueba
Name[et]=Test
Name[eu]=Proba
Name[fa]=آزمون
Name[fi]=Testi
Name[fr]=Test
Name[ga]=Tástáil
Name[gl]=Proba
Name[he]=בדיקה
Name[hr]=Test
Name[hu]=Teszt
Name[ia]=Essayo
+Name[id]=Tes
Name[is]=Prófun
Name[it]=Prova
Name[ja]=テスト
Name[kk]=Сынақ
Name[km]=សាកល្បង
Name[ko]=테스트
Name[ku]=Bicaribîne
Name[lt]=Testas
Name[lv]=Tests
Name[nb]=Test
Name[nds]=Utproberen
Name[nl]=Test
Name[pa]=ਟੈਸਟ
Name[pl]=Test
Name[pt]=Teste
Name[pt_BR]=Teste
Name[ro]=Test
Name[ru]=Проверка
Name[se]=Geahččaleapmi
Name[si]=පරීක්‍ෂණය
Name[sk]=Test
Name[sl]=Test
Name[sq]=Test
Name[sr]=Проба
Name[sr@ijekavian]=Проба
Name[sr@ijekavianlatin]=Proba
Name[sr@latin]=Proba
Name[sv]=Test
Name[tg]=Санҷиш
Name[th]=ทดสอบ
Name[tr]=Deneme
Name[tt]=Тикшерү
Name[ug]=سىنا
Name[uk]=Тест
Name[vi]=Kiểm tra
Name[wa]=Sayî
Name[x-test]=xxTestxx
Name[zh_CN]=测试
Name[zh_TW]=測試
Comment=A dummy plugin for testing
Comment[ar]=إضافة وهمية من أجل الاختبار
Comment[bg]=Празна приставка за изпробване
Comment[bs]=Lažni priključak za probe
Comment[ca]=Un connector fals per a provar
Comment[ca@valencia]=Un connector fals per a provar
Comment[cs]=Prázdný zásuvný modul pro testování
Comment[da]=Et attrap-plugin til testformål
Comment[de]=Ein Dummy-Modul zum Testen.
Comment[el]=Ένα "χαζό" πρόσθετο για δοκιμές
Comment[en_GB]=A dummy plugin for testing
Comment[es]=Un complemento de pruebas
Comment[et]=Libaplugin testimiseks
Comment[eu]=Probatarako gezurretazko plugin bat
Comment[fi]=Tyhjä testausliitännäinen
Comment[fr]=Un module factice de test
Comment[ga]=Bréagbhreiseán le haghaidh tástála
Comment[gl]=Un engadido para probas
Comment[he]=תוסף דמה לבדיקה
Comment[hr]=Lažan priključak za testiranje
Comment[hu]=Üres bővítmény teszteléshez
Comment[ia]=Un plugin vacue pro essayar
+Comment[id]=Plugin palsu untuk pengujian
Comment[is]=Tilraunaíforrit fyrir prófanir
Comment[it]=Una estensione fittizia di prova
Comment[ja]=テスト用のダミープラグイン
Comment[kk]=Сынақ үшін бос плагині
Comment[km]=កម្មវិធី​ជំនួយ​ដំបូង​សម្រាប់​សាកល្បង
Comment[ko]=테스트를 위한 뼈대 플러그인
Comment[ku]=Pêvekekî hêsan ji bo ceribandinê
Comment[lt]=Netikras priedas testavimui
Comment[lv]=Testēšanai paredzēts spraudnis
Comment[nb]=Et attrapp-programtillegg for testing
Comment[nds]=En Platzholler-Moduul för't Utproberen
Comment[nl]=Een dummy plugin voor testen
Comment[pa]=ਟੈਸਟ ਕਰਨ ਲਈ ਫਰਜ਼ੀ ਪਲੱਗਇਨ
Comment[pl]=Prosta wtyczka testowa
Comment[pt]=Um 'plugin' de exemplo para testes
Comment[pt_BR]=Um plug-in fictício para testes
Comment[ro]=Modul fictiv pentru testare
Comment[ru]=Расширение для тестирования
Comment[se]=Geahččalanlassemodula
Comment[sk]=Ukážkový modul pre testovanie
Comment[sl]=Prazen vstavek za testiranje
Comment[sr]=Лажни прикључак за пробе
Comment[sr@ijekavian]=Лажни прикључак за пробе
Comment[sr@ijekavianlatin]=Lažni priključak za probe
Comment[sr@latin]=Lažni priključak za probe
Comment[sv]=Ett exempelinsticksprogram för test
Comment[tg]=Воситаҳо барои санҷиш
Comment[th]=โปรแกรมเสริมเทียมสำหรับทดสอบ
Comment[tr]=Denemek için sahte bir eklenti
Comment[tt]=Тикшертү өчен ялган өстәмә
Comment[ug]=سىناشقا ئىشلىتىدىغان قىستۇرما
Comment[uk]=Додаток для тестування
Comment[vi]=Một phần bổ sung giả để kiểm thử
Comment[wa]=On fås tchôke-divins po l' asprouvaedje
Comment[x-test]=xxA dummy plugin for testingxx
Comment[zh_CN]=测试用插件
Comment[zh_TW]=測試用外掛程式
Type=Service
Icon=preferences-desktop-color
ServiceTypes=Plasma/ContainmentActions
X-KDE-Library=plasma_containmentactions_test
X-KDE-PluginInfo-Author=Chani
X-KDE-PluginInfo-Email=chani@kde.org
X-KDE-PluginInfo-Name=test
X-KDE-PluginInfo-Version=pre0.1
X-KDE-PluginInfo-Website=http://plasma.kde.org/
X-KDE-PluginInfo-Depends=
X-KDE-PluginInfo-License=GPL
X-KDE-PluginInfo-EnabledByDefault=true
X-Plasma-HasConfigurationInterface=true
diff --git a/security/crypto/crypto.desktop b/security/crypto/crypto.desktop
index 86ef570bda..5a5dc9c232 100644
--- a/security/crypto/crypto.desktop
+++ b/security/crypto/crypto.desktop
@@ -1,222 +1,224 @@
[Desktop Entry]
Icon=preferences-desktop-cryptography
Type=Service
X-KDE-ServiceTypes=KCModule
Exec=kcmshell4 crypto
X-DocPath=kcontrol/crypto/index.html
X-KDE-Library=kcm_crypto
X-KDE-ParentApp=kcontrol
Name=Crypto
Name[af]=Crypto
Name[ar]=التشفير
Name[as]=Crypto
Name[ast]=Cifráu
Name[be@latin]=Šyfravańnie
Name[bg]=Шифриране
Name[bn]=ক্রিপ্টো
Name[bn_IN]=Crypto
Name[bs]=Šifrovanje
Name[ca]=Criptografia
Name[ca@valencia]=Criptografia
Name[cs]=Šifrování
Name[csb]=Crypto
Name[da]=Kryptering
Name[de]=Krypto
Name[el]=Κρυπτογράφηση
Name[en_GB]=Crypto
Name[eo]=Crypto
Name[es]=Criptografía
Name[et]=Krüpto
Name[eu]=Kriptografia
Name[fa]=رمز
Name[fi]=Salaukset
Name[fr]=Chiffrement
Name[fy]=Crypto
Name[ga]=Crypto
Name[gl]=Criptografía
Name[gu]=ક્રાય્પ્ટો
Name[he]=הצפנה
Name[hi]=क्रिप्टो
Name[hne]=क्रिप्टो
Name[hr]=Crypto
Name[hsb]=Crypto
Name[hu]=Titkosítás
Name[ia]=Crypto
Name[id]=Crypto
Name[is]=Dulkóðun
Name[it]=Crittografia
Name[ja]=暗号
Name[kk]=Crypto
Name[km]=Crypto
Name[kn]=ಕ್ರಿಪ್ಟೋ
Name[ko]=암호화
Name[ku]=Veşartî
Name[lt]=Šifravimas
Name[lv]=Kriptogrāfija
Name[mai]=क्रिप्टो
Name[mk]=Криптографија
Name[ml]=ക്രിപ്റ്റോ
Name[mr]=क्रिप्टो
Name[nb]=Kryptering
Name[nds]=Crypto
Name[ne]=गुप्त
Name[nl]=Crypto
Name[nn]=Kryptotenester
Name[or]=ଗୁପ୍ତ
Name[pa]=ਕਰਿਪਟੂ
Name[pl]=Kryptografia
Name[pt]=Cifra
Name[pt_BR]=Criptografia
Name[ro]=Cripto
Name[ru]=Шифрование
Name[se]=Krypteren
Name[si]=Crypto
Name[sk]=Šifrovanie
Name[sl]=Šifriranje
Name[sq]=Crypto
Name[sr]=Шифровање
Name[sr@ijekavian]=Шифровање
Name[sr@ijekavianlatin]=Šifrovanje
Name[sr@latin]=Šifrovanje
Name[sv]=Krypto
Name[ta]=Crypto
Name[tg]=Рамзгузорӣ
Name[th]=การเข้ารหัส
Name[tr]=Şifreleme
Name[tt]=Шифрлау
Name[ug]=شىفىرلا
Name[uk]=Шифрування
Name[vi]=Mật mã
Name[wa]=Criptografeye
Name[x-test]=xxCryptoxx
Name[zh_CN]=加密算法
Name[zh_TW]=Crypto
Comment=Configure SSL, manage certificates, and other cryptography settings
Comment[af]=Konfigureer SSL, hanteer sertifikate, en ander kriptografieverstellings
Comment[ar]=اضبط SSL، وأدر الشهادات وإعدادات تشفير أخرى
Comment[as]=SSL বিন্যাস কৰক, প্ৰমাণপত্ৰ পৰিচালনা কৰক, আৰু অন্যান্য ক্ৰিপ্টোগ্ৰাফিৰ পছন্দ
Comment[ast]=Configurar SSL, xestionar certificaos, y otros axustes criptográficos
Comment[be@latin]=Naładžvaje SSL, kiruje sertyfikatami, inšyja nałady šyfravańnia
Comment[bg]=SSL, управление на сертификати и други настройки за шифроване
Comment[bn]=এস-এস-এল কনফিগারেশন, সার্টিফিকেট তত্বাবধান, এবং অন্যান্য ক্রিপ্টোগ্রাফি সেটিংস
Comment[bn_IN]=SSL কনফিগারেশন, সার্টিফিকেট পরিচালনা ও ক্রিপ্টোগ্রাফি সংক্রান্ত বিবিধ বৈশিষ্ট্য কনফিগার করুন
Comment[bs]=Podešavanje SSL‑a, upravljanje sertifikatima, i druge kriptografske postavke
Comment[ca]=Configura l'SSL, gestiona certificats, i altres arranjaments de criptografia
Comment[ca@valencia]=Configura l'SSL, gestiona certificats, i altres arranjaments de criptografia
Comment[cs]=Nastavení SSL, správa certifikátů a ostatní nastavení šifrování
Comment[csb]=Kònfigùracëjô SSL, sprôwianié certifikatama ë jinszëma ùstôwóma kriptografiji
Comment[da]=Konfigurér SSL, håndtér certifikater og andre kryptografiske indstillinger
Comment[de]=Einrichtung von SSL, Zertifikat-Verwaltung und weitere kryptografische Einstellungen
Comment[el]=Ρύθμιση SSL, διαχείριση πιστοποιητικών, και άλλες ρυθμίσεις κρυπτογραφίας
Comment[en_GB]=Configure SSL, manage certificates and other cryptography settings
Comment[eo]=Agordi SSLon, administri atestilojn, kaj aliajn kriptografiajn agordojn
Comment[es]=Configurar SSL, gestionar certificados y otras preferencias de criptografía
Comment[et]=SSL-i seadistamine, sertifikaatide haldamine ja muud krüptoseadistused
Comment[eu]=SSL konfiguratu, ziurtagiriak kudeatu eta bestelako kriptografiako ezarpenak
Comment[fa]=پیکربندی SSL، مدیریت گواهی‌نامه‌ها، و تنظیمات رمزنگاری دیگر
Comment[fi]=Muokkaa SSL-asetuksia ja hallitse varmenteita sekä muita salausasetuksia
Comment[fr]=Configure le chiffrement SSL, gère les certificats et autres paramètres liés au chiffrement
Comment[fy]=Stel SSL yn, behear sertifikaten en konfigurearje oare kryptografyske ynstellings
Comment[ga]=Cumraigh SSL, bainistigh teastais, agus socruithe cripteagrafaíochta eile
Comment[gl]=Configurar o SSL, xestionar os certificados e outras opcións de cifrado
Comment[gu]=SSL રુપરેખાંકન, પ્રમાણપત્ર સંચાલન અને અન્ય ક્રાય્પ્ટોગ્રાફી ગોઠવણીઓ કરો
Comment[he]=שינוי הגדרות SSL, ניהול תעודות והגדרות הצפנה אחרות
Comment[hi]=एसएसएल कॉन्फ़िगर, प्रमाणपत्र प्रबंधन, तथा अन्य क्रिप्टोग्राफ़िक सेटिंग
Comment[hne]=एसएसएल कान्फिगर, प्रमानपत्र प्रबंधन, अउ दुसरा क्रिप्टोग्राफी सेटिंग
Comment[hr]=Podešavanje SSL-a, upravljanje certifikatima i ostala kriptografska podešavanja
Comment[hsb]=Konfigurowanje SSL, zastaranje certifikatow a druhe kryptografiske nastajenja
Comment[hu]=SSL-beállítások, tanúsítványkezelés és további titkosítási lehetőségek
Comment[ia]=Configura SSL, manea certificatos e altere fixationes cryptographic
Comment[id]=Konfigurasi SSL, atur sertifikat, dan pengaturan kriptografi lainnya
Comment[is]=Stilla SSL, stýra skírteinum, og aðrar dulkóðunarstillingar
Comment[it]=Configura SSL, gestisce i certificati ed altre impostazioni di crittografia
Comment[ja]=SSL の設定、証明書の管理、その他の暗号作成形式の設定
Comment[kk]=SSL баптаулары, күаліктерді басқару және басқа криптография параметрлері
Comment[km]=កំណត់​រចនា​សម្ព័ន្ធ SSL, គ្រប់គ្រង​វិញ្ញាបនបត្រ និង​ការ​កំណត់​កូដសាស្ដ្រ
Comment[kn]=SSL ಅನ್ನು ಸಂರಚಿಸು, ಪ್ರಮಾಣಪತ್ರಗಳನ್ನು ನಿಭಾಯಿಸು, ಮತ್ತು ಇತರ ಗೂಢಲಿಪಿಶಾಸ್ತ್ರ (ಕ್ರಿಪ್ಟೋಗ್ರಫಿ) ಸಂಯೋಜನೆಗಳು
Comment[ko]=SSL과 같은 암호화를 설정하고, 인증서를 관리합니다
Comment[ku]=SSL, rêveberê belge kirinê, û mîhengên din yê bişîfre kirinê veava bike
Comment[lt]=Derina SSL, tvarko liudijimus ir kitus šifravimo parametrus
Comment[lv]=Konfigurēt SSL, pārvaldīt sertifikātus un citus kriptogrāfijas iestatījumus
Comment[mai]=एसएसएल बिन्यस्त करू, प्रमाणपत्र आओर आन क्रिप्टोग्राफिक सेटिंगक प्रबंधित करू
Comment[mk]=Конфигурација на SSL, менаџмент на сертификати\nи други криптографски поставувања
Comment[ml]=എസ്എസ്എല്‍ ക്രമീകരിയ്ക്കുക, സാക്ഷ്യപത്ര നടത്തിപ്പുകാരന്‍, പിന്നെ ക്രിപ്റ്റോഗ്രഫി സജ്ജീകരണം
Comment[mr]=SSL, प्रमाणपत्र प्रबंधन, तथा अन्य क्रिप्टोग्राफ़िक संयोजना संयोजीत करा
Comment[nb]=Oppsett av SSL, behandling av sertifikater og andre innstillinger for kryptering
Comment[nds]=SSL inrichten, Zertifikaten plegen un anner Verslötel-Instellen
Comment[ne]=SSL कन्फिगरेसन, प्रमाणपत्र प्रबन्ध, र अन्य गुप्तिकरण सेटिङ
Comment[nl]=Stel SSL in, beheer certificaten en configureer andere cryptografische instellingen
Comment[nn]=Set opp SSL, sertifikat og andre krypteringsinnstillingar
Comment[or]=SSL ବିନ୍ୟାସ କରନ୍ତୁ, ପ୍ରମାଣପତ୍ର ପରିଚାଳନ, ଏବଂ ଅନ୍ୟ ଗୁଢଲେଖନ ବିନ୍ୟାସଗୁଡିକ
Comment[pa]=SSL ਸੰਰਚਨਾ, ਸਰਟੀਫਿਕੇਟ ਪਰਬੰਧ, ਅਤੇ ਹੋਰ ਕਰਿਪਟੋਗਰਾਫ਼ੀ ਸੈਟਿੰਘ
Comment[pl]=Konfiguracja SSL, zarządzanie certyfikatami i inne ustawienia kryptografii
Comment[pt]=Configurar o SSL, gerir os certificados e outras opções de criptografia
Comment[pt_BR]=Configura SSL, gerencia certificados e outras configurações de criptografia
Comment[ro]=Configurează SSL, administrează certificate și alte setări criptografice
Comment[ru]=Настройка SSL, управление сертификатами и другие параметры криптографии
Comment[se]=Gieđahala SSL, sertifikáhtaid ja eará krypterenheivehusaid
Comment[si]=SSL සැකසීම, සහතික කළමණාකරනය සහ වෙනත් සංකේතාන්කන සැකසුම්
Comment[sk]=Nastavenie SSL, správa certifikátov a ostatné nastavenie šifrovania
Comment[sl]=Nastavljanje SSL, upravljanje s potrdili in ostale nastavitve šifriranja
Comment[sr]=Подешавање ССЛ‑а, управљање сертификатима, и друге криптографске поставке
Comment[sr@ijekavian]=Подешавање ССЛ‑а, управљање сертификатима, и друге криптографске поставке
Comment[sr@ijekavianlatin]=Podešavanje SSL‑a, upravljanje sertifikatima, i druge kriptografske postavke
Comment[sr@latin]=Podešavanje SSL‑a, upravljanje sertifikatima, i druge kriptografske postavke
Comment[sv]=Anpassa SSL, hantera certifikat och andra kryptografiska inställningar
Comment[ta]=SSL வடிவமைக்க, சான்றுகள் நிர்வகிக்க, மற்றும் ஏனைய உருமாற்ற அமைப்புகள்
Comment[tg]=Танзимоти SSL, идоракунии иҷозатномаҳо ва танзимотҳои рамзгузории дигар
Comment[th]=ปรับแต่ง SSL, จัดการใบรับรอง, และการตั้งค่าเกี่ยวกับการเข้ารหัสอื่น ๆ
Comment[tr]=SSL yapılandırması, sertifika yönetimi ve diğer şifreleme ayarları
Comment[tt]=SSL'ны көйләү, таныклыклар белән идарә итө һәм криптографиянең башка параметрлары
Comment[ug]=SSL سەپلىمە، گۇۋاھنامە باشقۇرۇش ۋە باشقا شىفىرلاش تەڭشىكى
Comment[uk]=Налаштування SSL, керування сертифікатами та інші параметри шифрування
Comment[vi]=Cấu hình SSL, quản lý chứng nhận và các thiết lập mật mã khác
Comment[wa]=Apontyî SSL, manaedjî les acertineures, eyet ds ôtes apontiaedjes del criptografeye
Comment[x-test]=xxConfigure SSL, manage certificates, and other cryptography settingsxx
Comment[zh_CN]=配置 SSL、管理证书及其它加密设置
Comment[zh_TW]=設定 SSL、管理憑證與其他加密設定
X-KDE-Keywords=Crypto,Krypto,Cryptography,encryption,SSL,https,certificates,ciphers,TLS,secure,security
X-KDE-Keywords[ar]=تشفير,كريبتو,التشفير,التشفير,SSL,https,الشهادات,الأصفار,TLS.سري,أمني
X-KDE-Keywords[bg]=Crypto,Krypto,Cryptography,encryption,SSL,https,certificates,ciphers,TLS,secure,security,криптография,шифриране,удостоверения,сертификати,шифри,сигурност
X-KDE-Keywords[ca]=Criptografia,encriptatge,SSL,https,certificats,xifres,TLS,segur,seguretat
X-KDE-Keywords[ca@valencia]=Criptografia,encriptatge,SSL,https,certificats,xifres,TLS,segur,seguretat
X-KDE-Keywords[da]=Crypto,Krypto,Kryptografi,kryptering,SSL,https,certifikater,chiffre,TLS,sikker,sikkerhed
X-KDE-Keywords[de]=crypto,krypto,kryptographie,verschlüsselung,ssl,https,Zertifikate,Chiffren,tls,sicher,sicherheit
+X-KDE-Keywords[el]=Crypto,Krypto,Κρυπτογράφηση,Κρυπτογραφία,SSL,https,πιστοποιητικά,κωδικοποίηση,TLS,ασφάλιση,ασφάλεια
X-KDE-Keywords[es]=Crypto,Krypto,Criptografía,cifrado,SSL,ttps,certificados,cifradores,TLS,seguro,seguridad
X-KDE-Keywords[et]=krüpto,krüptograafia,krüptimine,SSL,https,sertifikaadid,serdid,šifrid,TLS,turvalisus,turve
X-KDE-Keywords[fi]=Crypto,Krypto,Salaus,SSL,https,varmenteet,sertifikaatit,salakirjoitukset,salakirjoitus,salakirjoitusmenetelmät,salausmenetelmät,TLS,turvallinen,turvallisuus
X-KDE-Keywords[fr]=Chiffrement,Krypto,déchiffrement,SSL,https,certificats,encodage,TLS,sécuriser,sécurité
X-KDE-Keywords[ga]=Crypto,Krypto,cripteagrafaíocht,criptiú,SSL,https,teastais,sifir,TLS,daingean,slándáil
X-KDE-Keywords[gl]=Cripto,Kripto,Criptografía,cifrado,SSL,https,certificados,cifras,TLS,seguro,securidade
X-KDE-Keywords[he]=הצפנה,קריפטולוגיה,אבטחה,מאובטחCrypto,Krypto,Cryptography,encryption,SSL,https,certificates,ciphers,TLS,secure,security
X-KDE-Keywords[hu]=Titkosítás,Titkosítás,Titkosítás,titkosítás,SSL,https,tanúsítványok,titkosítók,TLS,biztonságos,biztonság
X-KDE-Keywords[ia]=Crypto,Krypto,Cryptographia,cryptation,SSL,https,certificatos,ciphras,TLS, secur,securitate
+X-KDE-Keywords[id]=Crypto,Krypto,Kriptografi,enkripsi,SSL,https,sertifikat,kode,TLS,aman,keamanan
X-KDE-Keywords[it]=Cripto,Kripto,Crittografia,cifratura,SSL,https,certificati,cifrari,TLS,sicuro,sicurezza
X-KDE-Keywords[kk]=Crypto,Krypto,Cryptography,encryption,SSL,https,certificates,ciphers,TLS,secure,security
X-KDE-Keywords[km]=ការ​គ្រីប​,​គ្រីបតូ​,ការ​គ្រីប,​ការ​អ៊ិនគ្រីប​,SSL,https,​វិញ្ញាបនបត្រ​,ciphers,TLS,មូលប័ត្រ​,​មូលប័ត្រ
X-KDE-Keywords[ko]=Crypto,Krypto,Cryptography,encryption,SSL,https,certificates,ciphers,TLS,secure,security,암호,암호화,인증서,보안
X-KDE-Keywords[nb]=Krypto,Crypto,Kryptografi,kryptering,SSL,https,sertifikater,chiffre,TLS,sikker,sikkerhet
X-KDE-Keywords[nds]=Verslöteln,SSL,https,Zertifikaten,Slötels,TLS,seker,Sekerheit
X-KDE-Keywords[nl]=Crypto,Krypto,cryptografie,versleuteling,SSL,https,certificaten,ciphers,TLS,veilig,beveiliging
X-KDE-Keywords[pa]=ਕ੍ਰਿਪਟੂ,ਕ੍ਰਿਪਟੂ,ਕ੍ਰਿਪਟੂਗਰਾਫੀ,ਇੰਕ੍ਰਿਪਸ਼ਨ,ਸਰਟੀਫਿਕੇਟ,ਸੀਫ਼ਰ,ਸੁਰੱਖਿਅਤ,ਸੁਰੱਖਿਆ,Crypto,Krypto,Cryptography,encryption,SSL,https,certificates,ciphers,TLS,secure,security
X-KDE-Keywords[pl]=Kryptografia,szyfrowanie,SSL,https,certyfikaty,szyfry,TLS,bezpieczeństwo,zabezpiecz
X-KDE-Keywords[pt]=Cripto,Criptografia,encriptação,SSL,HTTPS,certificados,cifras,TLS,seguro,segurança
X-KDE-Keywords[pt_BR]=criptografia,Krypto,criptografar,SSL,HTTPS,certificados,cifras,TLS,segurar,segurança
X-KDE-Keywords[ro]=Criptare,Cripto,Criptografie,SSL,https,certificate,cifru,TLS,sigur,securitate
X-KDE-Keywords[sk]=Crypto,Krypto,Cryptography,encryption,SSL,https,certificates,ciphers,TLS,secure,security
X-KDE-Keywords[sr]=Crypto,Krypto,Cryptography,encryption,SSL,https,certificates,ciphers,TLS,secure,security,криптографија,шифровање,ССЛ,ХТТПС,сертификат,шифрар,ТЛС,безбедност
X-KDE-Keywords[sr@ijekavian]=Crypto,Krypto,Cryptography,encryption,SSL,https,certificates,ciphers,TLS,secure,security,криптографија,шифровање,ССЛ,ХТТПС,сертификат,шифрар,ТЛС,безбедност
X-KDE-Keywords[sr@ijekavianlatin]=Crypto,Krypto,Cryptography,encryption,SSL,https,certificates,ciphers,TLS,secure,security,kriptografija,šifrovanje,SSL,HTTPS,sertifikat,šifrar,TLS,bezbednost
X-KDE-Keywords[sr@latin]=Crypto,Krypto,Cryptography,encryption,SSL,https,certificates,ciphers,TLS,secure,security,kriptografija,šifrovanje,SSL,HTTPS,sertifikat,šifrar,TLS,bezbednost
X-KDE-Keywords[sv]=Krypto,Kryptografi,kryptering,SSL,https,certifikat,chiffer,TLS,säker,säkerhet
X-KDE-Keywords[tg]=Crypto,Krypto,Cryptography,рамзгузорӣ,SSL,https,иҷозатномаҳо,ciphers,TLS,бехатар,амният
X-KDE-Keywords[uk]=Crypto,Krypto,Cryptography,encryption,SSL,https,certificates,ciphers,TLS,secure,security,криптографія,шифрування,сертифікат,сертифікати,захист,безпека,безпечний,захищений
X-KDE-Keywords[vi]=Crypto,mật mã,mã hoá,Krypto,Cryptography,encryption,SSL,https,certificates,chứng thực,ciphers,TLS,secure,bảo mật,security
X-KDE-Keywords[x-test]=xxCrypto,Krypto,Cryptography,encryption,SSL,https,certificates,ciphers,TLS,secure,securityxx
X-KDE-Keywords[zh_CN]=Crypto,Krypto,Cryptography,encryption,SSL,https,certificates,ciphers,TLS,secure,security,加密,证书,密码,安全
X-KDE-Keywords[zh_TW]=Crypto,Krypto,Cryptography,encryption,SSL,https,certificates,ciphers,TLS,secure,security
Categories=Qt;KDE;X-KDE-settings-security;
diff --git a/solid/solid/backends/fstab/fstabdevice.cpp b/solid/solid/backends/fstab/fstabdevice.cpp
index a65855494c..4a4dcee29d 100644
--- a/solid/solid/backends/fstab/fstabdevice.cpp
+++ b/solid/solid/backends/fstab/fstabdevice.cpp
@@ -1,117 +1,123 @@
/*
Copyright 2010 Mario Bensi <mbensi@ipsquad.net>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "fstabdevice.h"
#include "fstabhandling.h"
#include "fstabnetworkshare.h"
#include "fstabstorageaccess.h"
#include "fstabservice.h"
#include <QtCore/QStringList>
using namespace Solid::Backends::Fstab;
FstabDevice::FstabDevice(QString uid) :
Solid::Ifaces::Device(),
m_uid(uid)
{
m_device = m_uid;
m_device.remove(parentUdi() + "/");
if (m_device.startsWith("//")) {
m_product = m_device.left(m_device.indexOf("/", 2));
m_vendor = m_device.mid(m_device.indexOf("/", 2) + 1);
} else {
m_product = m_device.left(m_device.indexOf(":/"));
m_vendor = m_device.mid(m_device.indexOf(":/") + 1);
}
m_description = m_vendor + " on " + m_product;
}
FstabDevice::~FstabDevice()
{
}
QString FstabDevice::udi() const
{
return m_uid;
}
QString FstabDevice::parentUdi() const
{
return QString::fromLatin1(FSTAB_UDI_PREFIX);
}
QString FstabDevice::vendor() const
{
return m_vendor;
}
QString FstabDevice::product() const
{
return m_product;
}
QString FstabDevice::icon() const
{
return QString::fromLatin1("network-server");
}
QStringList FstabDevice::emblems() const
{
QStringList res;
const FstabStorageAccess accessIface(const_cast<FstabDevice *>(this));
if (accessIface.isAccessible()) {
res << "emblem-mounted";
} else {
res << "emblem-unmounted";
}
return res;
}
QString FstabDevice::description() const
{
return m_description;
}
bool FstabDevice::queryDeviceInterface(const Solid::DeviceInterface::Type &type) const
{
if (type == Solid::DeviceInterface::StorageAccess
|| type == Solid::DeviceInterface::NetworkShare) {
return true;
}
return false;
}
QObject* FstabDevice::createDeviceInterface(const Solid::DeviceInterface::Type &type)
{
if (type == Solid::DeviceInterface::StorageAccess) {
return new FstabStorageAccess(this);
} else if (type == Solid::DeviceInterface::NetworkShare) {
return new FstabNetworkShare(this);
}
return 0;
}
QString FstabDevice::device() const
{
return m_device;
}
+
+void FstabDevice::onMtabChanged(const QString& device)
+{
+ if (m_device == device)
+ emit mtabChanged(device);
+}
diff --git a/solid/solid/backends/fstab/fstabdevice.h b/solid/solid/backends/fstab/fstabdevice.h
index d2ca63feb6..07279f8121 100644
--- a/solid/solid/backends/fstab/fstabdevice.h
+++ b/solid/solid/backends/fstab/fstabdevice.h
@@ -1,73 +1,80 @@
/*
Copyright 2010 Mario Bensi <mbensi@ipsquad.net>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SOLID_BACKENDS_FSTAB_FSTAB_DEVICE_H
#define SOLID_BACKENDS_FSTAB_FSTAB_DEVICE_H
#include <solid/ifaces/device.h>
#include <QtCore/QStringList>
namespace Solid
{
namespace Backends
{
namespace Fstab
{
class FstabDevice : public Solid::Ifaces::Device
{
Q_OBJECT
public:
FstabDevice(QString uid);
virtual ~FstabDevice();
virtual QString udi() const;
virtual QString parentUdi() const;
virtual QString vendor() const;
virtual QString product() const;
virtual QString icon() const;
virtual QStringList emblems() const;
virtual QString description() const;
virtual bool queryDeviceInterface(const Solid::DeviceInterface::Type &type) const;
virtual QObject *createDeviceInterface(const Solid::DeviceInterface::Type &type);
QString device() const;
+
+ Q_SIGNALS:
+ void mtabChanged(const QString& device);
+
+ private Q_SLOTS:
+ void onMtabChanged(const QString& device);
+
private:
QString m_uid;
QString m_device;
QString m_product;
QString m_vendor;
QString m_description;
};
}
}
}
#endif // SOLID_BACKENDS_UPNP_UPNP_DEVICE_H
diff --git a/solid/solid/backends/fstab/fstabhandling.cpp b/solid/solid/backends/fstab/fstabhandling.cpp
index 90e673a0f3..e874ffd9b5 100644
--- a/solid/solid/backends/fstab/fstabhandling.cpp
+++ b/solid/solid/backends/fstab/fstabhandling.cpp
@@ -1,344 +1,371 @@
/*
Copyright 2006-2010 Kevin Ottens <ervin@kde.org>
Copyright 2010 Mario Bensi <mbensi@ipsquad.net>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "fstabhandling.h"
#include <QtCore/QFile>
-#include <QtCore/QMultiHash>
#include <QtCore/QObject>
#include <QtCore/QProcess>
#include <QtCore/QTextStream>
#include <QtCore/QTime>
#include <solid/soliddefs_p.h>
#include <config.h>
#include <stdlib.h>
#ifdef HAVE_SYS_MNTTAB_H
#include <sys/mnttab.h>
#endif
#ifdef HAVE_MNTENT_H
#include <mntent.h>
#elif defined(HAVE_SYS_MNTENT_H)
#include <sys/mntent.h>
#endif
// This is the *BSD branch
#ifdef HAVE_SYS_MOUNT_H
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#include <sys/mount.h>
#endif
#ifdef Q_OS_SOLARIS
#define FSTAB "/etc/vfstab"
#else
#define FSTAB "/etc/fstab"
#endif
#ifndef HAVE_GETMNTINFO
# ifdef _PATH_MOUNTED
// On some Linux, MNTTAB points to /etc/fstab !
# undef MNTTAB
# define MNTTAB _PATH_MOUNTED
# else
# ifndef MNTTAB
# ifdef MTAB_FILE
# define MNTTAB MTAB_FILE
# else
# define MNTTAB "/etc/mnttab"
# endif
# endif
# endif
#endif
// There are (at least) four kind of APIs:
// setmntent + getmntent + struct mntent (linux...)
// getmntent + struct mnttab
// mntctl + struct vmount (AIX)
// getmntinfo + struct statfs&flags (BSD 4.4 and friends)
// getfsent + char* (BSD 4.3 and friends)
#ifdef HAVE_SETMNTENT
#define SETMNTENT setmntent
#define ENDMNTENT endmntent
#define STRUCT_MNTENT struct mntent *
#define STRUCT_SETMNTENT FILE *
#define GETMNTENT(file, var) ((var = getmntent(file)) != 0)
#define MOUNTPOINT(var) var->mnt_dir
#define MOUNTTYPE(var) var->mnt_type
#define MOUNTOPTIONS(var) var->mnt_opts
#define FSNAME(var) var->mnt_fsname
#else
#define SETMNTENT fopen
#define ENDMNTENT fclose
#define STRUCT_MNTENT struct mnttab
#define STRUCT_SETMNTENT FILE *
#define GETMNTENT(file, var) (getmntent(file, &var) == 0)
#define MOUNTPOINT(var) var.mnt_mountp
#define MOUNTTYPE(var) var.mnt_fstype
#define MOUNTOPTIONS(var) var.mnt_mntopts
#define FSNAME(var) var.mnt_special
#endif
-typedef QMultiHash<QString, QString> QStringMultiHash;
-SOLID_GLOBAL_STATIC(QStringMultiHash, globalMountPointsCache)
+SOLID_GLOBAL_STATIC(Solid::Backends::Fstab::FstabHandling, globalFstabCache)
+
+Solid::Backends::Fstab::FstabHandling::FstabHandling()
+ : m_fstabCacheValid(false),
+ m_mtabCacheValid(false)
+{ }
bool _k_isFstabNetworkFileSystem(const QString &fstype, const QString &devName)
{
if (fstype == "nfs"
|| fstype == "nfs4"
|| fstype == "smbfs"
|| fstype == "cifs"
|| devName.startsWith(QLatin1String("//"))) {
return true;
}
return false;
}
-
-void _k_updateFstabMountPointsCache()
+void Solid::Backends::Fstab::FstabHandling::_k_updateFstabMountPointsCache()
{
- static bool firstCall = true;
- static QTime elapsedTime;
-
- if (firstCall) {
- firstCall = false;
- elapsedTime.start();
- } else if (elapsedTime.elapsed()>10000) {
- elapsedTime.restart();
- } else {
+ if (globalFstabCache->m_fstabCacheValid)
return;
- }
- globalMountPointsCache->clear();
+ globalFstabCache->m_fstabCache.clear();
#ifdef HAVE_SETMNTENT
FILE *fstab;
if ((fstab = setmntent(FSTAB, "r")) == 0) {
return;
}
struct mntent *fe;
while ((fe = getmntent(fstab)) != 0) {
if (_k_isFstabNetworkFileSystem(fe->mnt_type, fe->mnt_fsname)) {
const QString device = QFile::decodeName(fe->mnt_fsname);
const QString mountpoint = QFile::decodeName(fe->mnt_dir);
- globalMountPointsCache->insert(device, mountpoint);
+ globalFstabCache->m_fstabCache.insert(device, mountpoint);
}
}
endmntent(fstab);
#else
QFile fstab(FSTAB);
if (!fstab.open(QIODevice::ReadOnly)) {
return;
}
QTextStream stream(&fstab);
QString line;
while (!stream.atEnd()) {
line = stream.readLine().simplified();
if (line.isEmpty() || line.startsWith('#')) {
continue;
}
// not empty or commented out by '#'
const QStringList items = line.split(' ');
#ifdef Q_OS_SOLARIS
if (items.count() < 5) {
continue;
}
#else
if (items.count() < 4) {
continue;
}
#endif
//prevent accessing a blocking directory
if (_k_isFstabNetworkFileSystem(items.at(2), items.at(0))) {
const QString device = items.at(0);
const QString mountpoint = items.at(1);
- globalMountPointsCache->insert(device, mountpoint);
+ globalFstabCache->m_fstabCache.insert(device, mountpoint);
}
}
fstab.close();
#endif
+ globalFstabCache->m_fstabCacheValid = true;
}
QStringList Solid::Backends::Fstab::FstabHandling::deviceList()
{
_k_updateFstabMountPointsCache();
- return globalMountPointsCache->keys();
+ _k_updateMtabMountPointsCache();
+
+ QStringList devices = globalFstabCache->m_fstabCache.keys();
+ devices += globalFstabCache->m_mtabCache.keys();
+ devices.removeDuplicates();
+ return devices;
}
QStringList Solid::Backends::Fstab::FstabHandling::mountPoints(const QString &device)
{
_k_updateFstabMountPointsCache();
- const QString deviceToFind = device;
+ _k_updateMtabMountPointsCache();
- return globalMountPointsCache->values(deviceToFind);
+ QStringList mountpoints = globalFstabCache->m_fstabCache.values(device);
+ mountpoints += globalFstabCache->m_mtabCache.values(device);
+ mountpoints.removeDuplicates();
+ return mountpoints;
}
QProcess *Solid::Backends::Fstab::FstabHandling::callSystemCommand(const QString &commandName,
const QStringList &args,
QObject *obj, const char *slot)
{
QStringList env = QProcess::systemEnvironment();
env.replaceInStrings(QRegExp("^PATH=(.*)", Qt::CaseInsensitive), "PATH=/sbin:/bin:/usr/sbin/:/usr/bin");
QProcess *process = new QProcess(obj);
QObject::connect(process, SIGNAL(finished(int,QProcess::ExitStatus)),
obj, slot);
process->setEnvironment(env);
process->start(commandName, args);
if (process->waitForStarted()) {
return process;
} else {
delete process;
return 0;
}
}
QProcess *Solid::Backends::Fstab::FstabHandling::callSystemCommand(const QString &commandName,
const QString &device,
QObject *obj, const char *slot)
{
return callSystemCommand(commandName, QStringList() << device, obj, slot);
}
-QStringList Solid::Backends::Fstab::FstabHandling::currentMountPoints()
+void Solid::Backends::Fstab::FstabHandling::_k_updateMtabMountPointsCache()
{
- QStringList result;
+ if (globalFstabCache->m_mtabCacheValid)
+ return;
+
+ globalFstabCache->m_mtabCache.clear();
#ifdef HAVE_GETMNTINFO
#ifdef GETMNTINFO_USES_STATVFS
struct statvfs *mounted;
#else
struct statfs *mounted;
#endif
int num_fs = getmntinfo(&mounted, MNT_NOWAIT);
for (int i=0;i< num_fs;i++)
{
#ifdef __osf__
QString type = QFile::decodeName(mnt_names[mounted[i].f_type]);
#else
QString type = QFile::decodeName(mounted[i].f_fstypename);
#endif
if (_k_isFstabNetworkFileSystem(type, QString())) {
- result << QFile::decodeName(mounted[i].f_mntfromname);
+ const QString device = QFile::decodeName(mounted[i].f_mntfromname);
+ const QString mountpoint = QFile::decodeName(mounted[i].f_mntonname);
+ globalFstabCache->m_mtabCache.insert(device, mountpoint);
}
}
#elif defined(_AIX)
struct vmount *mntctl_buffer;
struct vmount *vm;
char *mountedfrom;
char *mountedto;
int fsname_len, num;
int buf_sz = 4096;
mntctl_buffer = (struct vmount*)malloc(buf_sz);
num = mntctl(MCTL_QUERY, buf_sz, mntctl_buffer);
if (num == 0)
{
buf_sz = *(int*)mntctl_buffer;
free(mntctl_buffer);
mntctl_buffer = (struct vmount*)malloc(buf_sz);
num = mntctl(MCTL_QUERY, buf_sz, mntctl_buffer);
}
if (num > 0)
{
/* iterate through items in the vmount structure: */
vm = (struct vmount *)mntctl_buffer;
for ( ; num > 0; --num )
{
/* get the name of the mounted file systems: */
fsname_len = vmt2datasize(vm, VMT_STUB);
mountedto = (char*)malloc(fsname_len + 1);
mountedto[fsname_len] = '\0';
strncpy(mountedto, (char *)vmt2dataptr(vm, VMT_STUB), fsname_len);
fsname_len = vmt2datasize(vm, VMT_OBJECT);
mountedfrom = (char*)malloc(fsname_len + 1);
mountedfrom[fsname_len] = '\0';
strncpy(mountedfrom, (char *)vmt2dataptr(vm, VMT_OBJECT), fsname_len);
/* Look up the string for the file system type,
* as listed in /etc/vfs.
* ex.: nfs,jfs,afs,cdrfs,sfs,cachefs,nfs3,autofs
*/
struct vfs_ent* ent = getvfsbytype(vm->vmt_gfstype);
QString type = QFile::decodeName(ent->vfsent_name);
if (_k_isFstabNetworkFileSystem(type, QString())) {
- result << QFile::decodeName(mountedfrom);
+ const QString device = QFile::decodeName(mountedfrom);
+ const QString mountpoint = QFile::decodeName(mountedto);
+ globalFstabCache->m_mtabCache.insert(device, mountpoint);
}
free(mountedfrom);
free(mountedto);
/* goto the next vmount structure: */
vm = (struct vmount *)((char *)vm + vm->vmt_length);
}
endvfsent( );
}
free( mntctl_buffer );
#else
STRUCT_SETMNTENT mnttab;
if ((mnttab = SETMNTENT(MNTTAB, "r")) == 0)
- return result;
+ return;
STRUCT_MNTENT fe;
while (GETMNTENT(mnttab, fe))
{
QString type = QFile::decodeName(MOUNTTYPE(fe));
if (_k_isFstabNetworkFileSystem(type, QString())) {
- result << QFile::decodeName(FSNAME(fe));
+ const QString device = QFile::decodeName(FSNAME(fe));
+ const QString mountpoint = QFile::decodeName(MOUNTPOINT(fe));
+ globalFstabCache->m_mtabCache.insert(device, mountpoint);
}
}
ENDMNTENT(mnttab);
#endif
- return result;
+
+ globalFstabCache->m_mtabCacheValid = true;
}
+QStringList Solid::Backends::Fstab::FstabHandling::currentMountPoints(const QString &device)
+{
+ _k_updateMtabMountPointsCache();
+ return globalFstabCache->m_mtabCache.values(device);
+}
+
+void Solid::Backends::Fstab::FstabHandling::flushMtabCache()
+{
+ globalFstabCache->m_mtabCacheValid = false;
+}
+
+void Solid::Backends::Fstab::FstabHandling::flushFstabCache()
+{
+ globalFstabCache->m_fstabCacheValid = false;
+}
diff --git a/solid/solid/backends/fstab/fstabhandling.h b/solid/solid/backends/fstab/fstabhandling.h
index 99cfc9b90c..4780a67596 100644
--- a/solid/solid/backends/fstab/fstabhandling.h
+++ b/solid/solid/backends/fstab/fstabhandling.h
@@ -1,55 +1,74 @@
/*
Copyright 2006-2010 Kevin Ottens <ervin@kde.org>
Copyright 2010 Mario Bensi <mbensi@ipsquad.net>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SOLID_BACKENDS_FSTAB_FSTABHANDLING_H
#define SOLID_BACKENDS_FSTAB_FSTABHANDLING_H
#include <QtCore/QString>
+#include <QtCore/QMultiHash>
class QProcess;
class QObject;
namespace Solid
{
namespace Backends
{
namespace Fstab
{
+
class FstabHandling
{
public:
+ FstabHandling();
+
static QStringList deviceList();
- static QStringList currentMountPoints();
+ static QStringList currentMountPoints(const QString &device);
static QStringList mountPoints(const QString &device);
static QProcess *callSystemCommand(const QString &commandName,
const QStringList &args,
QObject *obj, const char *slot);
static QProcess *callSystemCommand(const QString &commandName,
const QString &device,
QObject *obj, const char *slot);
+ static void flushMtabCache();
+ static void flushFstabCache();
+
+private:
+ static void _k_updateMtabMountPointsCache();
+ static void _k_updateFstabMountPointsCache();
+
+ typedef QMultiHash<QString, QString> QStringMultiHash;
+
+ QStringMultiHash m_mtabCache;
+ QStringMultiHash m_fstabCache;
+ bool m_fstabCacheValid;
+ bool m_mtabCacheValid;
+
};
+
}
}
}
#endif
diff --git a/solid/solid/backends/fstab/fstabmanager.cpp b/solid/solid/backends/fstab/fstabmanager.cpp
index 30b2ed1d0d..608159f87e 100644
--- a/solid/solid/backends/fstab/fstabmanager.cpp
+++ b/solid/solid/backends/fstab/fstabmanager.cpp
@@ -1,120 +1,146 @@
/*
Copyright 2010 Mario Bensi <mbensi@ipsquad.net>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "fstabmanager.h"
#include "fstabdevice.h"
#include "fstabhandling.h"
#include "../shared/rootdevice.h"
#include "fstabservice.h"
#include "fstabwatcher.h"
using namespace Solid::Backends::Fstab;
using namespace Solid::Backends::Shared;
FstabManager::FstabManager(QObject *parent)
: Solid::Ifaces::DeviceManager(parent)
{
m_supportedInterfaces << Solid::DeviceInterface::StorageAccess;
m_supportedInterfaces << Solid::DeviceInterface::NetworkShare;
m_deviceList = FstabHandling::deviceList();
connect(FstabWatcher::instance(), SIGNAL(fstabChanged()), this, SLOT(onFstabChanged()));
+ connect(FstabWatcher::instance(), SIGNAL(mtabChanged()), this, SLOT(onMtabChanged()));
}
QString FstabManager::udiPrefix() const
{
return QString::fromLatin1(FSTAB_UDI_PREFIX);
}
QSet<Solid::DeviceInterface::Type> FstabManager::supportedInterfaces() const
{
return m_supportedInterfaces;
}
QStringList FstabManager::allDevices()
{
QStringList result;
result << udiPrefix();
foreach (const QString &device, m_deviceList) {
result << udiPrefix() + "/" + device;
}
return result;
}
QStringList FstabManager::devicesFromQuery( const QString &parentUdi,
Solid::DeviceInterface::Type type)
{
if (type == Solid::DeviceInterface::StorageAccess
|| type == Solid::DeviceInterface::NetworkShare) {
if (parentUdi.isEmpty() || parentUdi == udiPrefix()) {
QStringList list = allDevices();
list.removeFirst();
return list;
} else {
QStringList list;
list << parentUdi;
return list;
}
}
return QStringList();
}
QObject *FstabManager::createDevice(const QString &udi)
{
if (udi == udiPrefix()) {
RootDevice *root = new RootDevice(FSTAB_UDI_PREFIX);
root->setProduct(tr("Network Shares"));
root->setDescription(tr("NFS and SMB shares declared in your system"));
root->setIcon("folder-remote");
return root;
+
} else {
- return new FstabDevice(udi);
+ // global device manager makes sure udi starts with udi prefix + '/'
+ QString internalName = udi.mid( udiPrefix().length()+1, -1 );
+ if (!m_deviceList.contains(internalName))
+ return 0;
+
+ QObject* device = new FstabDevice(udi);
+ connect (this, SIGNAL(mtabChanged(QString)), device, SLOT(onMtabChanged(QString)));
+ return device;
+
}
}
void FstabManager::onFstabChanged()
+{
+ FstabHandling::flushFstabCache();
+ _k_updateDeviceList();
+}
+
+void FstabManager::_k_updateDeviceList()
{
QStringList deviceList = FstabHandling::deviceList();
- if (deviceList.count() > m_deviceList.count()) {
- //new device
- foreach (const QString &device, deviceList) {
- if (!m_deviceList.contains(device)) {
- emit deviceAdded(udiPrefix() + "/" + device);
- }
- }
- } else {
- //device has been removed
- foreach (const QString &device, m_deviceList) {
- if (!deviceList.contains(device)) {
- emit deviceRemoved(udiPrefix() + "/" + device);
- }
- }
+ QSet<QString> newlist = deviceList.toSet();
+ QSet<QString> oldlist = m_deviceList.toSet();
+
+ foreach(const QString &device, newlist) {
+ if ( !oldlist.contains(device) )
+ emit deviceAdded(udiPrefix() + "/" + device);
}
+
+ foreach(const QString &device, oldlist) {
+ if ( !newlist.contains(device) )
+ emit deviceRemoved(udiPrefix() + "/" + device);
+ }
+
m_deviceList = deviceList;
}
+void FstabManager::onMtabChanged()
+{
+ FstabHandling::flushMtabCache();
+
+ _k_updateDeviceList(); // devicelist is union of mtab and fstab
+
+ foreach(const QString &device, m_deviceList) {
+ // notify storageaccess objects via device ...
+ emit mtabChanged(device);
+ }
+}
+
FstabManager::~FstabManager()
{
}
diff --git a/solid/solid/backends/fstab/fstabmanager.h b/solid/solid/backends/fstab/fstabmanager.h
index c4587f0004..81e176b971 100644
--- a/solid/solid/backends/fstab/fstabmanager.h
+++ b/solid/solid/backends/fstab/fstabmanager.h
@@ -1,63 +1,68 @@
/*
Copyright 2010 Mario Bensi <mbensi@ipsquad.net>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SOLID_BACKENDS_FSTAB_FSTABMANAGER_H
#define SOLID_BACKENDS_FSTAB_FSTABMANAGER_H
#include <solid/ifaces/devicemanager.h>
#include <solid/deviceinterface.h>
#include <QtCore/QStringList>
#include <QtCore/QSet>
namespace Solid
{
namespace Backends
{
namespace Fstab
{
class AbstractDeviceFactory;
class FstabManager : public Solid::Ifaces::DeviceManager
{
Q_OBJECT
public:
explicit FstabManager(QObject *parent);
virtual ~FstabManager();
virtual QString udiPrefix() const ;
virtual QSet<Solid::DeviceInterface::Type> supportedInterfaces() const;
virtual QStringList allDevices();
virtual QStringList devicesFromQuery(const QString &parentUdi, Solid::DeviceInterface::Type type);
virtual QObject *createDevice(const QString &udi);
+Q_SIGNALS:
+ void mtabChanged(const QString& device);
+
private Q_SLOTS:
void onFstabChanged();
+ void onMtabChanged();
private:
QSet<Solid::DeviceInterface::Type> m_supportedInterfaces;
QStringList m_deviceList;
+ void _k_updateDeviceList();
};
}
}
}
#endif
diff --git a/solid/solid/backends/fstab/fstabstorageaccess.cpp b/solid/solid/backends/fstab/fstabstorageaccess.cpp
index 5378d8f8a3..6af83cf8ba 100644
--- a/solid/solid/backends/fstab/fstabstorageaccess.cpp
+++ b/solid/solid/backends/fstab/fstabstorageaccess.cpp
@@ -1,169 +1,165 @@
/*
Copyright 2010 Mario Bensi <mbensi@ipsquad.net>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "fstabstorageaccess.h"
#include "fstabwatcher.h"
#include <solid/backends/fstab/fstabdevice.h>
#include <solid/backends/fstab/fstabhandling.h>
#include <solid/backends/fstab/fstabservice.h>
#include <QtCore/QStringList>
#include <QTimer>
#define MTAB "/etc/mtab"
using namespace Solid::Backends::Fstab;
FstabStorageAccess::FstabStorageAccess(Solid::Backends::Fstab::FstabDevice *device) :
QObject(device),
m_fstabDevice(device)
{
- m_currentMountPoints = FstabHandling::currentMountPoints();
+ QStringList currentMountPoints = FstabHandling::currentMountPoints(device->device());
+ if (currentMountPoints.isEmpty()) {
+ m_filePath = FstabHandling::mountPoints(device->device()).first();
+ m_isAccessible = false;
+ } else {
+ m_filePath = currentMountPoints.first();
+ m_isAccessible = true;
+ }
- connect(FstabWatcher::instance(), SIGNAL(mtabChanged()), this, SLOT(onMtabChanged()));
+ connect(device, SIGNAL(mtabChanged(QString)), this, SLOT(onMtabChanged(QString)));
QTimer::singleShot(0, this, SLOT(connectDBusSignals()));
}
FstabStorageAccess::~FstabStorageAccess()
{
}
void FstabStorageAccess::connectDBusSignals()
{
m_fstabDevice->registerAction("setup", this,
SLOT(slotSetupRequested()),
SLOT(slotSetupDone(int,QString)));
m_fstabDevice->registerAction("teardown", this,
SLOT(slotTeardownRequested()),
SLOT(slotTeardownDone(int,QString)));
}
const Solid::Backends::Fstab::FstabDevice *FstabStorageAccess::fstabDevice() const
{
return m_fstabDevice;
}
bool FstabStorageAccess::isAccessible() const
{
- if (m_currentMountPoints.contains(m_fstabDevice->device())) {
- return true;
- } else {
- return false;
- }
+ return m_isAccessible;
}
QString FstabStorageAccess::filePath() const
{
- QStringList points = FstabHandling::mountPoints(m_fstabDevice->device());
- if (!points.isEmpty()) {
- return points.first();
- } else {
- return QString();
- }
+ return m_filePath;
}
bool FstabStorageAccess::isIgnored() const
{
return false;
}
bool FstabStorageAccess::setup()
{
if (filePath().isEmpty()) {
return false;
}
m_fstabDevice->broadcastActionRequested("setup");
m_process = FstabHandling::callSystemCommand("mount", filePath(),
this, SLOT(slotSetupFinished(int,QProcess::ExitStatus)));
return m_process!=0;
}
void FstabStorageAccess::slotSetupRequested()
{
emit setupRequested(m_fstabDevice->udi());
}
bool FstabStorageAccess::teardown()
{
if (filePath().isEmpty()) {
return false;
}
m_fstabDevice->broadcastActionRequested("teardown");
m_process = FstabHandling::callSystemCommand("umount", filePath(),
this, SLOT(slotTeardownFinished(int,QProcess::ExitStatus)));
return m_process!=0;
}
void FstabStorageAccess::slotTeardownRequested()
{
emit teardownRequested(m_fstabDevice->udi());
}
void FstabStorageAccess::slotSetupFinished(int exitCode, QProcess::ExitStatus /*exitStatus*/)
{
if (exitCode==0) {
m_fstabDevice->broadcastActionDone("setup", Solid::NoError, QString());
} else {
m_fstabDevice->broadcastActionDone("setup", Solid::UnauthorizedOperation, m_process->readAllStandardError());
}
delete m_process;
}
void FstabStorageAccess::slotSetupDone(int error, const QString &errorString)
{
emit setupDone(static_cast<Solid::ErrorType>(error), errorString, m_fstabDevice->udi());
}
void FstabStorageAccess::slotTeardownFinished(int exitCode, QProcess::ExitStatus /*exitStatus*/)
{
if (exitCode==0) {
m_fstabDevice->broadcastActionDone("teardown", Solid::NoError, QString());
} else {
m_fstabDevice->broadcastActionDone("teardown", Solid::UnauthorizedOperation, m_process->readAllStandardError());
}
delete m_process;
}
void FstabStorageAccess::slotTeardownDone(int error, const QString &errorString)
{
emit teardownDone(static_cast<Solid::ErrorType>(error), errorString, m_fstabDevice->udi());
}
-void FstabStorageAccess::onMtabChanged()
+void FstabStorageAccess::onMtabChanged(const QString& device)
{
- QStringList currentMountPoints = FstabHandling::currentMountPoints();
- if (currentMountPoints.count() > m_currentMountPoints.count()) {
- // device mounted
- if (currentMountPoints.contains(m_fstabDevice->device()) && !m_currentMountPoints.contains(m_fstabDevice->device())) {
- emit accessibilityChanged(true, QString(FSTAB_UDI_PREFIX) + "/" + m_fstabDevice->device());
- }
- } else {
+ QStringList currentMountPoints = FstabHandling::currentMountPoints(device);
+ if (currentMountPoints.isEmpty()) {
// device umounted
- if (!currentMountPoints.contains(m_fstabDevice->device()) && m_currentMountPoints.contains(m_fstabDevice->device())) {
- emit accessibilityChanged(false, QString(FSTAB_UDI_PREFIX) + "/" + m_fstabDevice->device());
- }
+ m_filePath = FstabHandling::mountPoints(device).first();
+ m_isAccessible = false;
+ emit accessibilityChanged(false, QString(FSTAB_UDI_PREFIX) + "/" + device);
+ } else {
+ // device added
+ m_filePath = currentMountPoints.first();
+ m_isAccessible = true;
+ emit accessibilityChanged(true, QString(FSTAB_UDI_PREFIX) + "/" + device);
}
-
- m_currentMountPoints = currentMountPoints;
}
diff --git a/solid/solid/backends/fstab/fstabstorageaccess.h b/solid/solid/backends/fstab/fstabstorageaccess.h
index 5d2c4eb3d5..bbe93e3125 100644
--- a/solid/solid/backends/fstab/fstabstorageaccess.h
+++ b/solid/solid/backends/fstab/fstabstorageaccess.h
@@ -1,91 +1,92 @@
/*
Copyright 2010 Mario Bensi <mbensi@ipsquad.net>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SOLID_BACKENDS_FSTAB_STORAGEACCESS_H
#define SOLID_BACKENDS_FSTAB_STORAGEACCESS_H
#include <solid/ifaces/storageaccess.h>
#include <QtCore/QObject>
#include <QtCore/QProcess>
namespace Solid
{
namespace Backends
{
namespace Fstab
{
class FstabDevice;
class FstabStorageAccess : public QObject, public Solid::Ifaces::StorageAccess
{
Q_OBJECT
Q_INTERFACES(Solid::Ifaces::StorageAccess)
public:
explicit FstabStorageAccess(Solid::Backends::Fstab::FstabDevice *device);
virtual ~FstabStorageAccess();
virtual bool isAccessible() const;
virtual QString filePath() const;
virtual bool isIgnored() const;
virtual bool setup();
virtual bool teardown();
public:
const Solid::Backends::Fstab::FstabDevice* fstabDevice() const;
Q_SIGNALS:
void accessibilityChanged(bool accessible, const QString &udi);
void setupDone(Solid::ErrorType error, QVariant data, const QString &udi);
void teardownDone(Solid::ErrorType error, QVariant data, const QString &udi);
void setupRequested(const QString &udi);
void teardownRequested(const QString &udi);
private Q_SLOTS:
void slotSetupFinished(int exitCode, QProcess::ExitStatus exitStatus);
void slotTeardownFinished(int exitCode, QProcess::ExitStatus exitStatus);
- void onMtabChanged();
+ void onMtabChanged(const QString& device);
void connectDBusSignals();
void slotSetupRequested();
void slotSetupDone(int error, const QString &errorString);
void slotTeardownRequested();
void slotTeardownDone(int error, const QString &errorString);
private:
Solid::Backends::Fstab::FstabDevice *m_fstabDevice;
QProcess *m_process;
- QStringList m_currentMountPoints;
+ QString m_filePath;
+ bool m_isAccessible;
};
}
}
}
#endif // SOLID_BACKENDS_FSTAB_DEVICE_INTERFACE_H
diff --git a/solid/solid/backends/fstab/fstabwatcher.cpp b/solid/solid/backends/fstab/fstabwatcher.cpp
index 449d5ce417..f4e65b475f 100644
--- a/solid/solid/backends/fstab/fstabwatcher.cpp
+++ b/solid/solid/backends/fstab/fstabwatcher.cpp
@@ -1,103 +1,117 @@
/*
Copyright 2010 Mario Bensi <mbensi@ipsquad.net>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "fstabwatcher.h"
#include "soliddefs_p.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QFileSystemWatcher>
+#include <QtCore/QSocketNotifier>
+#include <QtCore/QFile>
#include <QtCore/QStringList>
using namespace Solid::Backends::Fstab;
SOLID_GLOBAL_STATIC(FstabWatcher, globalFstabWatcher)
#define MTAB "/etc/mtab"
#ifdef Q_OS_SOLARIS
#define FSTAB "/etc/vfstab"
#else
#define FSTAB "/etc/fstab"
#endif
FstabWatcher::FstabWatcher()
: m_isRoutineInstalled(false)
, m_fileSystemWatcher(new QFileSystemWatcher(this))
{
if (qApp) {
connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(orphanFileSystemWatcher()));
}
- m_fileSystemWatcher->addPath(MTAB);
+
+ m_mtabFile = new QFile(MTAB, this);
+ if (m_mtabFile && m_mtabFile->symLinkTarget().startsWith("/proc/")
+ && m_mtabFile->open(QIODevice::ReadOnly) ) {
+
+ m_mtabSocketNotifier = new QSocketNotifier(m_mtabFile->handle(),
+ QSocketNotifier::Exception, this);
+ connect(m_mtabSocketNotifier,
+ SIGNAL(activated(int)), this, SIGNAL(mtabChanged()) );
+ } else {
+ m_fileSystemWatcher->addPath(MTAB);
+ }
+
m_fileSystemWatcher->addPath(FSTAB);
connect(m_fileSystemWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onFileChanged(QString)));
}
FstabWatcher::~FstabWatcher()
{
// The QFileSystemWatcher doesn't work correctly in a singleton
// The solution so far was to destroy the QFileSystemWatcher when the application quits
// But we have some crash with this solution.
// For the moment to workaround the problem, we detach the QFileSystemWatcher from the parent
// effectively leaking it on purpose.
#if 0
//qRemovePostRoutine(globalFstabWatcher.destroy);
#else
m_fileSystemWatcher->setParent(0);
#endif
}
void FstabWatcher::orphanFileSystemWatcher()
{
m_fileSystemWatcher->setParent(0);
}
FstabWatcher *FstabWatcher::instance()
{
#if 0
FstabWatcher *fstabWatcher = globalFstabWatcher;
if (fstabWatcher && !fstabWatcher->m_isRoutineInstalled) {
qAddPostRoutine(globalFstabWatcher.destroy);
fstabWatcher->m_isRoutineInstalled = true;
}
return fstabWatcher;
#else
return globalFstabWatcher;
#endif
}
void FstabWatcher::onFileChanged(const QString &path)
{
if (path == MTAB) {
emit mtabChanged();
if (!m_fileSystemWatcher->files().contains(MTAB)) {
m_fileSystemWatcher->addPath(MTAB);
}
}
if (path == FSTAB) {
emit fstabChanged();
if (!m_fileSystemWatcher->files().contains(FSTAB)) {
m_fileSystemWatcher->addPath(FSTAB);
}
}
}
diff --git a/solid/solid/backends/fstab/fstabwatcher.h b/solid/solid/backends/fstab/fstabwatcher.h
index 2ca6511338..18df9bab23 100644
--- a/solid/solid/backends/fstab/fstabwatcher.h
+++ b/solid/solid/backends/fstab/fstabwatcher.h
@@ -1,59 +1,63 @@
/*
Copyright 2010 Mario Bensi <mbensi@ipsquad.net>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) version 3, or any
later version accepted by the membership of KDE e.V. (or its
successor approved by the membership of KDE e.V.), which shall
act as a proxy defined in Section 6 of version 3 of the license.
This library 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
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef SOLID_BACKENDS_FSTAB_WATCHER_H
#define SOLID_BACKENDS_FSTAB_WATCHER_H
#include <QObject>
class QFileSystemWatcher;
+class QFile;
+class QSocketNotifier;
namespace Solid
{
namespace Backends
{
namespace Fstab
{
class FstabWatcher : public QObject {
Q_OBJECT
public:
FstabWatcher();
virtual ~FstabWatcher();
static FstabWatcher *instance();
Q_SIGNALS:
void mtabChanged();
void fstabChanged();
private Q_SLOTS:
void onFileChanged(const QString &path);
void orphanFileSystemWatcher();
private:
bool m_isRoutineInstalled;
QFileSystemWatcher *m_fileSystemWatcher;
+ QSocketNotifier *m_mtabSocketNotifier;
+ QFile *m_mtabFile;
};
}
}
}
#endif // SOLID_BACKENDS_FSTAB_WATCHER_H

File Metadata

Mime Type
text/x-diff
Expires
Fri, Nov 1, 9:56 AM (1 d, 21 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
d8/03/98f6038ca9f33b6ab1fc5ec56f4e
Default Alt Text
(1 MB)

Event Timeline