Compare commits
153 Commits
bbe22712fe
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
c204e4e094
|
|||
|
16e0ff2188
|
|||
|
2d6855445f
|
|||
|
531f18d827
|
|||
|
862cde36ae
|
|||
|
661dc85d45
|
|||
|
2fe846f09f
|
|||
|
33426992ec
|
|||
|
197d071160
|
|||
|
fafac22d60
|
|||
|
58f7ba55b3
|
|||
|
d2cfc2ee81
|
|||
|
5a9a446b9c
|
|||
|
39e2fc5142
|
|||
|
f94bb77091
|
|||
|
d3cfb12e16
|
|||
|
9aac2511d3
|
|||
|
8af337c83c
|
|||
|
951208c15b
|
|||
|
74d9acecb5
|
|||
|
d1ce97010e
|
|||
|
e7be281b58
|
|||
|
2aa6390146
|
|||
|
c77d994dc4
|
|||
|
238e7a2bb9
|
|||
|
7bcbc04b04
|
|||
|
59d7c4af17
|
|||
|
2ed1c005c9
|
|||
|
26e73711b1
|
|||
|
d13b4a793d
|
|||
|
0570a4d050
|
|||
|
e28e576fdb
|
|||
|
ae9952387a
|
|||
|
27dcf87a64
|
|||
|
7ef502759e
|
|||
|
ded899933c
|
|||
|
832842bf81
|
|||
|
49072f9d01
|
|||
|
b0f1d3d6ea
|
|||
|
4073749d3b
|
|||
|
2e684d2f4e
|
|||
|
57739ff81e
|
|||
|
2ff01f9fdb
|
|||
|
5ed881d25b
|
|||
|
259d0ac860
|
|||
|
aa985514a6
|
|||
|
6f5e32386e
|
|||
|
92556d5269
|
|||
|
2c69937ef5
|
|||
|
4e4ba67025
|
|||
|
13ddec0274
|
|||
|
c899ceff10
|
|||
|
4d95f46d36
|
|||
|
0d0ac40f96
|
|||
|
181df7fbae
|
|||
|
b36f185426
|
|||
|
726ecd83ac
|
|||
|
dd5e90a324
|
|||
|
ac6ac4f845
|
|||
|
093baaa034
|
|||
|
ecab86966f
|
|||
|
e383a50af7
|
|||
|
ed26259e6d
|
|||
|
8c80aa9da2
|
|||
|
9aa26e2807
|
|||
|
f7528a05a3
|
|||
|
46e9b7b584
|
|||
|
0eb935ad9f
|
|||
|
c75de30765
|
|||
|
451859e976
|
|||
|
0f45b9bf3b
|
|||
|
27a9869a6a
|
|||
|
2efad7453a
|
|||
|
dc36a88968
|
|||
|
0371dd95dd
|
|||
|
cbdb1cfaa6
|
|||
|
03ebaddff5
|
|||
|
5190601852
|
|||
|
b77395b931
|
|||
|
809304cbb3
|
|||
|
462428d0a2
|
|||
|
89580f2113
|
|||
|
c7b9d65797
|
|||
|
519d6bd108
|
|||
|
b721e26557
|
|||
|
ec89870a32
|
|||
|
6158f6e3b5
|
|||
|
dd7b258698
|
|||
|
aa3ad12d44
|
|||
|
82accaf200
|
|||
|
e461189f0e
|
|||
|
85a30d20c3
|
|||
|
54ef836eec
|
|||
|
26a3c0c825
|
|||
|
5216205056
|
|||
|
c364ec3b81
|
|||
|
a31d329754
|
|||
|
33e98881ad
|
|||
|
c0a094e473
|
|||
|
f147b54393
|
|||
|
10c05a2422
|
|||
|
8fb2ce29cf
|
|||
|
ce92a23551
|
|||
|
573554b9de
|
|||
|
9d7bc2a839
|
|||
|
f812fae922
|
|||
|
3d53b2c131
|
|||
|
627c296057
|
|||
|
13a16d1eb5
|
|||
|
b8e43c5d5a
|
|||
|
e2790b42f3
|
|||
|
9b220b74cc
|
|||
|
6f569e076e
|
|||
|
ce2b8b39c0
|
|||
|
e57751fe0e
|
|||
|
4d6cd247cb
|
|||
|
b9ba894cc9
|
|||
|
de7089d669
|
|||
|
16d6eaf012
|
|||
|
bc939712cb
|
|||
|
6f7fbbb438
|
|||
|
39ff4069c0
|
|||
|
fbfd8c7d02
|
|||
|
8b9c5e0460
|
|||
|
973d1d27ea
|
|||
|
55d0f53f7a
|
|||
|
20fcf2a0c7
|
|||
|
ed8b1bcdad
|
|||
|
3881725904
|
|||
|
6c364dc997
|
|||
|
690402f2e1
|
|||
|
03f2123447
|
|||
|
3c87b95c31
|
|||
|
fffa2906d8
|
|||
|
4245dc35df
|
|||
|
87f90f6420
|
|||
|
2b5eb86a9f
|
|||
|
85ca0c5857
|
|||
|
bb4e732a25
|
|||
|
911749475e
|
|||
|
f15fc0f00a
|
|||
|
d2fa9f3151
|
|||
|
d7f502a731
|
|||
|
7743e21bca
|
|||
|
504f1ce2f5
|
|||
|
e47667b28a
|
|||
|
aa19848499
|
|||
|
588b6ef8bb
|
|||
|
fda956093c
|
|||
|
74f2bcae34
|
|||
|
695f71d946
|
|||
|
8d19acd91a
|
|||
|
dd2bd04a07
|
32
.clang-format
Normal file
32
.clang-format
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# clang-format is fairly limited, and these rules are approximate:
|
||||||
|
# - array initializers can get terribly mangled with clang-format 12.0,
|
||||||
|
# - sometimes it still aligns with space characters,
|
||||||
|
# - struct name NL { NL ... NL } NL name; is unachievable.
|
||||||
|
BasedOnStyle: GNU
|
||||||
|
ColumnLimit: 80
|
||||||
|
IndentWidth: 4
|
||||||
|
TabWidth: 4
|
||||||
|
UseTab: ForContinuationAndIndentation
|
||||||
|
BreakBeforeBraces: Allman
|
||||||
|
SpaceAfterCStyleCast: true
|
||||||
|
AlignAfterOpenBracket: DontAlign
|
||||||
|
AlignEscapedNewlines: DontAlign
|
||||||
|
AlignOperands: DontAlign
|
||||||
|
AlignConsecutiveMacros: Consecutive
|
||||||
|
AllowAllArgumentsOnNextLine: false
|
||||||
|
AllowAllParametersOfDeclarationOnNextLine: false
|
||||||
|
IndentGotoLabels: false
|
||||||
|
MaxEmptyLinesToKeep: 2
|
||||||
|
|
||||||
|
# IncludeCategories has some potential, but it may also break the build.
|
||||||
|
# Note that the documentation says the value should be "Never".
|
||||||
|
SortIncludes: false
|
||||||
|
|
||||||
|
# Must be kept synchronized with gi18n.h and make-template.sh.
|
||||||
|
WhitespaceSensitiveMacros: ['_', 'Q_', 'N_', 'C_', 'NC_']
|
||||||
|
|
||||||
|
# This is a compromise, it generally works out aesthetically better.
|
||||||
|
BinPackArguments: false
|
||||||
|
|
||||||
|
# Unfortunately, this can't be told to align to column 40 or so.
|
||||||
|
SpacesBeforeTrailingComments: 2
|
||||||
10
.gitignore
vendored
10
.gitignore
vendored
@@ -3,7 +3,9 @@
|
|||||||
|
|
||||||
# Qt Creator files
|
# Qt Creator files
|
||||||
/CMakeLists.txt.user*
|
/CMakeLists.txt.user*
|
||||||
/sdtui.config
|
/tdv.cflags
|
||||||
/sdtui.files
|
/tdv.cxxflags
|
||||||
/sdtui.creator*
|
/tdv.config
|
||||||
/sdtui.includes
|
/tdv.files
|
||||||
|
/tdv.creator*
|
||||||
|
/tdv.includes
|
||||||
|
|||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,3 +1,6 @@
|
|||||||
[submodule "termo"]
|
[submodule "termo"]
|
||||||
path = termo
|
path = termo
|
||||||
url = https://git.janouch.name/p/termo.git
|
url = https://git.janouch.name/p/termo.git
|
||||||
|
[submodule "liberty"]
|
||||||
|
path = liberty
|
||||||
|
url = https://git.janouch.name/p/liberty.git
|
||||||
|
|||||||
428
CMakeLists.txt
428
CMakeLists.txt
@@ -1,37 +1,55 @@
|
|||||||
project (sdtui C)
|
cmake_minimum_required (VERSION 3.0...3.27)
|
||||||
cmake_minimum_required (VERSION 2.8.5)
|
project (tdv VERSION 0.1.0 LANGUAGES C)
|
||||||
|
|
||||||
# Moar warnings
|
# Adjust warnings
|
||||||
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
|
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
|
||||||
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99")
|
set (ignores "-Wno-missing-field-initializers -Wno-cast-function-type")
|
||||||
set (CMAKE_C_FLAGS_DEBUG
|
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 ${ignores}")
|
||||||
"${CMAKE_C_FLAGS_DEBUG} -Wall -Wextra -Wno-missing-field-initializers")
|
set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall -Wextra")
|
||||||
endif ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
|
endif ()
|
||||||
|
|
||||||
# Version
|
add_definitions (-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_38)
|
||||||
set (project_VERSION_MAJOR "0")
|
|
||||||
set (project_VERSION_MINOR "1")
|
|
||||||
set (project_VERSION_PATCH "0")
|
|
||||||
|
|
||||||
set (project_VERSION "${project_VERSION_MAJOR}")
|
|
||||||
set (project_VERSION "${project_VERSION}.${project_VERSION_MINOR}")
|
|
||||||
set (project_VERSION "${project_VERSION}.${project_VERSION_PATCH}")
|
|
||||||
|
|
||||||
# For custom modules
|
# For custom modules
|
||||||
set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
|
set (CMAKE_MODULE_PATH
|
||||||
|
"${PROJECT_SOURCE_DIR}/cmake;${PROJECT_SOURCE_DIR}/liberty/cmake")
|
||||||
|
|
||||||
|
# Cross-compilation for Windows, as a proof-of-concept pulled in from logdiag
|
||||||
|
if (WIN32)
|
||||||
|
if (NOT CMAKE_CROSSCOMPILING)
|
||||||
|
message (FATAL_ERROR "Win32 must be cross-compiled to build sensibly")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set (win32_deps_root "${PROJECT_SOURCE_DIR}")
|
||||||
|
set (win32_deps_prefix "${win32_deps_root}/mingw64")
|
||||||
|
list (APPEND CMAKE_PREFIX_PATH "${win32_deps_prefix}")
|
||||||
|
list (APPEND CMAKE_INCLUDE_PATH "${win32_deps_prefix}/lib")
|
||||||
|
set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mms-bitfields")
|
||||||
|
|
||||||
|
if (CMAKE_CROSSCOMPILING)
|
||||||
|
list (APPEND CMAKE_FIND_ROOT_PATH ${win32_deps_prefix})
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
# Relativize prefixes, and bar pkg-config from looking up host libraries
|
||||||
|
set (ENV{PKG_CONFIG_SYSROOT_DIR} "${win32_deps_root}")
|
||||||
|
set (win32_deps_pcpath
|
||||||
|
"${win32_deps_prefix}/share/pkgconfig:${win32_deps_prefix}/lib/pkgconfig")
|
||||||
|
set (ENV{PKG_CONFIG_PATH} "${win32_deps_pcpath}")
|
||||||
|
set (ENV{PKG_CONFIG_LIBDIR} "${win32_deps_pcpath}")
|
||||||
|
endif ()
|
||||||
|
|
||||||
# Dependencies
|
# Dependencies
|
||||||
find_package (ZLIB REQUIRED)
|
find_package (ZLIB REQUIRED)
|
||||||
find_package (Ncursesw REQUIRED)
|
find_package (Ncursesw REQUIRED)
|
||||||
find_package (PkgConfig REQUIRED)
|
find_package (PkgConfig REQUIRED)
|
||||||
pkg_check_modules (dependencies REQUIRED glib-2.0 gio-2.0 pango)
|
pkg_check_modules (dependencies REQUIRED glib-2.0>=2.38 gio-2.0 pango)
|
||||||
|
|
||||||
pkg_check_modules (icu icu-uc icu-i18n)
|
pkg_check_modules (icu icu-uc icu-i18n)
|
||||||
if (NOT icu_FOUND)
|
if (NOT icu_FOUND AND NOT WIN32)
|
||||||
find_program (icu_CONFIG_EXECUTABLE icu-config)
|
find_program (icu_CONFIG_EXECUTABLE icu-config)
|
||||||
if (NOT icu_CONFIG_EXECUTABLE)
|
if (NOT icu_CONFIG_EXECUTABLE)
|
||||||
message (FATAL_ERROR "ICU not found")
|
message (FATAL_ERROR "ICU not found")
|
||||||
endif (NOT icu_CONFIG_EXECUTABLE)
|
endif ()
|
||||||
|
|
||||||
execute_process (COMMAND ${icu_CONFIG_EXECUTABLE} --cppflags
|
execute_process (COMMAND ${icu_CONFIG_EXECUTABLE} --cppflags
|
||||||
OUTPUT_VARIABLE icu_CPPFLAGS OUTPUT_STRIP_TRAILING_WHITESPACE)
|
OUTPUT_VARIABLE icu_CPPFLAGS OUTPUT_STRIP_TRAILING_WHITESPACE)
|
||||||
@@ -47,24 +65,25 @@ if (NOT icu_FOUND)
|
|||||||
foreach (flag ${icu_CPPFLAGS})
|
foreach (flag ${icu_CPPFLAGS})
|
||||||
if (flag MATCHES "^-I(.*)")
|
if (flag MATCHES "^-I(.*)")
|
||||||
list (APPEND icu_INCLUDE_DIRS "${CMAKE_MATCH_1}")
|
list (APPEND icu_INCLUDE_DIRS "${CMAKE_MATCH_1}")
|
||||||
endif (flag MATCHES "^-I(.*)")
|
endif ()
|
||||||
endforeach (flag)
|
endforeach ()
|
||||||
|
|
||||||
# This should suffice most of the time, don't care about the rest
|
# This should suffice most of the time, don't care about the rest
|
||||||
endif (NOT icu_FOUND)
|
endif ()
|
||||||
|
|
||||||
find_package (Termo QUIET NO_MODULE)
|
find_package (Termo QUIET NO_MODULE)
|
||||||
option (USE_SYSTEM_TERMO
|
option (USE_SYSTEM_TERMO
|
||||||
"Don't compile our own termo library, use the system one" ${Termo_FOUND})
|
"Don't compile our own termo library, use the system one" ${Termo_FOUND})
|
||||||
|
|
||||||
if (USE_SYSTEM_TERMO)
|
if (USE_SYSTEM_TERMO)
|
||||||
if (NOT Termo_FOUND)
|
if (NOT Termo_FOUND)
|
||||||
message (FATAL_ERROR "System termo library not found")
|
message (FATAL_ERROR "System termo library not found")
|
||||||
endif (NOT Termo_FOUND)
|
endif ()
|
||||||
else (USE_SYSTEM_TERMO)
|
elseif (NOT WIN32)
|
||||||
# We don't want the library to install, even though EXCLUDE_FROM_ALL
|
# We don't want the library to install, but EXCLUDE_FROM_ALL ignores tests
|
||||||
# sabotages CTest -- those unbuilt tests need to be excluded in CTest runs
|
|
||||||
add_subdirectory (termo EXCLUDE_FROM_ALL)
|
add_subdirectory (termo EXCLUDE_FROM_ALL)
|
||||||
|
file (WRITE ${PROJECT_BINARY_DIR}/CTestCustom.cmake
|
||||||
|
"execute_process (COMMAND ${CMAKE_COMMAND} --build termo)")
|
||||||
|
|
||||||
# We don't have many good choices; this is a relatively clean approach
|
# We don't have many good choices; this is a relatively clean approach
|
||||||
# (other possibilities: setting a variable in the parent scope, using
|
# (other possibilities: setting a variable in the parent scope, using
|
||||||
# a cache variable, writing a special config file with build paths in it
|
# a cache variable, writing a special config file with build paths in it
|
||||||
@@ -72,165 +91,332 @@ else (USE_SYSTEM_TERMO)
|
|||||||
get_directory_property (Termo_INCLUDE_DIRS
|
get_directory_property (Termo_INCLUDE_DIRS
|
||||||
DIRECTORY termo INCLUDE_DIRECTORIES)
|
DIRECTORY termo INCLUDE_DIRECTORIES)
|
||||||
set (Termo_LIBRARIES termo-static)
|
set (Termo_LIBRARIES termo-static)
|
||||||
endif (USE_SYSTEM_TERMO)
|
endif ()
|
||||||
|
|
||||||
pkg_check_modules (xcb xcb xcb-xfixes)
|
pkg_check_modules (xcb xcb xcb-xfixes)
|
||||||
option (WITH_X11 "Compile with X11 selection support using XCB" ${xcb_FOUND})
|
option (WITH_X11 "Compile with X11 selection support using XCB" ${xcb_FOUND})
|
||||||
|
|
||||||
if (WITH_X11)
|
if (WITH_X11)
|
||||||
if (NOT xcb_FOUND)
|
if (NOT xcb_FOUND)
|
||||||
message (FATAL_ERROR "XCB not found")
|
message (FATAL_ERROR "XCB not found")
|
||||||
endif (NOT xcb_FOUND)
|
endif ()
|
||||||
|
|
||||||
list (APPEND dependencies_INCLUDE_DIRS ${xcb_INCLUDE_DIRS})
|
include_directories (${xcb_INCLUDE_DIRS})
|
||||||
list (APPEND dependencies_LIBRARY_DIRS ${xcb_LIBRARY_DIRS})
|
link_directories (${xcb_LIBRARY_DIRS})
|
||||||
list (APPEND dependencies_LIBRARIES ${xcb_LIBRARIES})
|
endif ()
|
||||||
endif (WITH_X11)
|
|
||||||
|
|
||||||
link_directories (${dependencies_LIBRARY_DIRS})
|
pkg_check_modules (gtk gtk+-3.0)
|
||||||
|
option (WITH_GUI "Build an alternative GTK+ UI" ${gtk_FOUND})
|
||||||
|
if (WITH_GUI)
|
||||||
|
if (NOT gtk_FOUND)
|
||||||
|
message (FATAL_ERROR "GTK+ not found")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
include_directories (${gtk_INCLUDE_DIRS})
|
||||||
|
link_directories (${gtk_LIBRARY_DIRS})
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
link_directories (${dependencies_LIBRARY_DIRS} ${icu_LIBRARY_DIRS})
|
||||||
include_directories (${ZLIB_INCLUDE_DIRS} ${icu_INCLUDE_DIRS}
|
include_directories (${ZLIB_INCLUDE_DIRS} ${icu_INCLUDE_DIRS}
|
||||||
${dependencies_INCLUDE_DIRS} ${NCURSESW_INCLUDE_DIRS}
|
${dependencies_INCLUDE_DIRS} ${Ncursesw_INCLUDE_DIRS}
|
||||||
${Termo_INCLUDE_DIRS})
|
${Termo_INCLUDE_DIRS})
|
||||||
|
|
||||||
# Configuration
|
# Configuration
|
||||||
include (CheckFunctionExists)
|
include (CheckFunctionExists)
|
||||||
set (CMAKE_REQUIRED_LIBRARIES ${NCURSESW_LIBRARIES})
|
set (CMAKE_REQUIRED_LIBRARIES ${Ncursesw_LIBRARIES})
|
||||||
CHECK_FUNCTION_EXISTS ("resizeterm" HAVE_RESIZETERM)
|
CHECK_FUNCTION_EXISTS ("resizeterm" HAVE_RESIZETERM)
|
||||||
|
|
||||||
# Localization
|
# Localization
|
||||||
find_package (Gettext REQUIRED)
|
find_package (Gettext REQUIRED)
|
||||||
file (GLOB project_PO_FILES ${PROJECT_SOURCE_DIR}/po/*.po)
|
file (GLOB project_PO_FILES "${PROJECT_SOURCE_DIR}/po/*.po")
|
||||||
GETTEXT_CREATE_TRANSLATIONS (
|
GETTEXT_CREATE_TRANSLATIONS (
|
||||||
${PROJECT_SOURCE_DIR}/po/${PROJECT_NAME}.pot
|
"${PROJECT_SOURCE_DIR}/po/${PROJECT_NAME}.pot"
|
||||||
ALL ${project_PO_FILES})
|
ALL ${project_PO_FILES})
|
||||||
|
|
||||||
# Documentation
|
# Documentation
|
||||||
find_program (XSLTPROC_EXECUTABLE xsltproc)
|
find_program (ASCIIDOCTOR_EXECUTABLE asciidoctor)
|
||||||
if (NOT XSLTPROC_EXECUTABLE)
|
find_program (A2X_EXECUTABLE a2x)
|
||||||
message (FATAL_ERROR "xsltproc not found")
|
if (NOT ASCIIDOCTOR_EXECUTABLE AND NOT A2X_EXECUTABLE)
|
||||||
endif (NOT XSLTPROC_EXECUTABLE)
|
message (WARNING "Neither asciidoctor nor a2x were found, "
|
||||||
|
"falling back to a substandard manual page generator")
|
||||||
|
endif ()
|
||||||
|
|
||||||
set (project_MAN_PAGES "${PROJECT_NAME}.1")
|
foreach (page "${PROJECT_NAME}.1")
|
||||||
foreach (page ${project_MAN_PAGES})
|
|
||||||
set (page_output "${PROJECT_BINARY_DIR}/${page}")
|
set (page_output "${PROJECT_BINARY_DIR}/${page}")
|
||||||
list (APPEND project_MAN_PAGES_OUTPUT "${page_output}")
|
list (APPEND project_MAN_PAGES "${page_output}")
|
||||||
add_custom_command (OUTPUT ${page_output}
|
if (ASCIIDOCTOR_EXECUTABLE)
|
||||||
COMMAND ${XSLTPROC_EXECUTABLE}
|
add_custom_command (OUTPUT "${page_output}"
|
||||||
--nonet
|
COMMAND ${ASCIIDOCTOR_EXECUTABLE} -b manpage
|
||||||
--param make.year.ranges 1
|
-a release-version=${PROJECT_VERSION}
|
||||||
--param make.single.year.ranges 1
|
-o "${page_output}"
|
||||||
--param man.charmap.use.subset 0
|
"${PROJECT_SOURCE_DIR}/docs/${page}.adoc"
|
||||||
--param man.authors.section.enabled 0
|
DEPENDS "docs/${page}.adoc"
|
||||||
http://docbook.sourceforge.net/release/xsl/current/manpages/docbook.xsl
|
COMMENT "Generating man page for ${page}" VERBATIM)
|
||||||
"${PROJECT_SOURCE_DIR}/docs/${page}.xml"
|
elseif (A2X_EXECUTABLE)
|
||||||
DEPENDS "docs/${page}.xml"
|
add_custom_command (OUTPUT "${page_output}"
|
||||||
COMMENT "Generating man page for ${page}" VERBATIM)
|
COMMAND ${A2X_EXECUTABLE} --doctype manpage --format manpage
|
||||||
endforeach (page)
|
-a release-version=${PROJECT_VERSION}
|
||||||
|
-D "${PROJECT_BINARY_DIR}"
|
||||||
|
"${PROJECT_SOURCE_DIR}/docs/${page}.adoc"
|
||||||
|
DEPENDS "docs/${page}.adoc"
|
||||||
|
COMMENT "Generating man page for ${page}" VERBATIM)
|
||||||
|
else ()
|
||||||
|
set (ASCIIMAN ${PROJECT_SOURCE_DIR}/liberty/tools/asciiman.awk)
|
||||||
|
add_custom_command (OUTPUT "${page_output}"
|
||||||
|
COMMAND env LC_ALL=C asciidoc-release-version=${PROJECT_VERSION}
|
||||||
|
awk -f ${ASCIIMAN} "${PROJECT_SOURCE_DIR}/docs/${page}.adoc"
|
||||||
|
> "${page_output}"
|
||||||
|
DEPENDS "docs/${page}.adoc" ${ASCIIMAN}
|
||||||
|
COMMENT "Generating man page for ${page}" VERBATIM)
|
||||||
|
endif ()
|
||||||
|
endforeach ()
|
||||||
|
|
||||||
add_custom_target (docs ALL DEPENDS ${project_MAN_PAGES_OUTPUT})
|
add_custom_target (docs ALL DEPENDS ${project_MAN_PAGES})
|
||||||
|
|
||||||
|
# Project libraries
|
||||||
|
set (project_common_libraries ${ZLIB_LIBRARIES} ${icu_LIBRARIES}
|
||||||
|
${dependencies_LIBRARIES})
|
||||||
|
if (WIN32)
|
||||||
|
find_package (LibIntl REQUIRED)
|
||||||
|
list (APPEND project_common_libraries ${LibIntl_LIBRARIES})
|
||||||
|
endif (WIN32)
|
||||||
|
|
||||||
# Project source files
|
|
||||||
set (project_common_sources
|
|
||||||
src/dictzip-input-stream.c
|
|
||||||
src/generator.c
|
|
||||||
src/stardict.c
|
|
||||||
src/utils.c)
|
|
||||||
set (project_common_headers
|
set (project_common_headers
|
||||||
${PROJECT_BINARY_DIR}/config.h
|
"${PROJECT_BINARY_DIR}/config.h"
|
||||||
src/dictzip-input-stream.h
|
src/dictzip-input-stream.h
|
||||||
src/stardict.h
|
src/stardict.h
|
||||||
src/stardict-private.h
|
src/stardict-private.h
|
||||||
src/generator.h
|
src/generator.h
|
||||||
src/utils.h)
|
src/utils.h)
|
||||||
|
|
||||||
# Project libraries
|
|
||||||
set (project_common_libraries ${ZLIB_LIBRARIES} ${icu_LIBRARIES}
|
|
||||||
${dependencies_LIBRARIES} ${NCURSESW_LIBRARIES} termo-static)
|
|
||||||
|
|
||||||
# Create a common project library so that source files are only compiled once
|
# Create a common project library so that source files are only compiled once
|
||||||
if (${CMAKE_VERSION} VERSION_GREATER "2.8.7")
|
add_library (stardict OBJECT
|
||||||
add_library (stardict OBJECT
|
${project_common_headers}
|
||||||
${project_common_sources}
|
src/dictzip-input-stream.c
|
||||||
${project_common_headers})
|
src/generator.c
|
||||||
set (project_common_sources $<TARGET_OBJECTS:stardict>)
|
src/stardict.c
|
||||||
else (${CMAKE_VERSION} VERSION_GREATER "2.8.7")
|
src/utils.c)
|
||||||
add_library (stardict STATIC
|
set (project_common_sources $<TARGET_OBJECTS:stardict>)
|
||||||
${project_common_sources}
|
|
||||||
${project_common_headers})
|
|
||||||
target_link_libraries (stardict ${project_common_libraries})
|
|
||||||
list (APPEND project_common_libraries stardict)
|
|
||||||
set (project_common_sources)
|
|
||||||
endif (${CMAKE_VERSION} VERSION_GREATER "2.8.7")
|
|
||||||
|
|
||||||
# Generate a configuration file
|
# Generate a configuration file
|
||||||
configure_file (${PROJECT_SOURCE_DIR}/config.h.in
|
configure_file ("${PROJECT_SOURCE_DIR}/config.h.in"
|
||||||
${PROJECT_BINARY_DIR}/config.h)
|
"${PROJECT_BINARY_DIR}/config.h")
|
||||||
include_directories (${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR})
|
include_directories ("${PROJECT_SOURCE_DIR}" "${PROJECT_BINARY_DIR}")
|
||||||
|
|
||||||
# Primary target source files
|
# Build the main executable and link it
|
||||||
|
set (project_libraries
|
||||||
|
${project_common_libraries})
|
||||||
set (project_sources
|
set (project_sources
|
||||||
|
${project_common_sources}
|
||||||
src/${PROJECT_NAME}.c)
|
src/${PROJECT_NAME}.c)
|
||||||
set (project_headers
|
set (project_headers
|
||||||
${project_common_headers})
|
${project_common_headers})
|
||||||
|
|
||||||
# Build the main executable and link it
|
if (WITH_GUI)
|
||||||
add_definitions (-DGLIB_DISABLE_DEPRECATION_WARNINGS)
|
include (IconUtils)
|
||||||
add_executable (${PROJECT_NAME}
|
|
||||||
${project_sources} ${project_headers} ${project_common_sources})
|
# The largest size is mainly for an appropriately sized Windows icon
|
||||||
target_link_libraries (${PROJECT_NAME} ${project_common_libraries})
|
set (icon_base "${PROJECT_BINARY_DIR}/icons")
|
||||||
|
set (icon_png_list)
|
||||||
|
foreach (icon_size 16 32 48 256)
|
||||||
|
icon_to_png (${PROJECT_NAME} "${PROJECT_SOURCE_DIR}/${PROJECT_NAME}.svg"
|
||||||
|
${icon_size} "${icon_base}" icon_png)
|
||||||
|
list (APPEND icon_png_list "${icon_png}")
|
||||||
|
endforeach ()
|
||||||
|
|
||||||
|
add_custom_target (icons ALL DEPENDS ${icon_png_list})
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
list (REMOVE_ITEM icon_png_list "${icon_png}")
|
||||||
|
set (icon_ico "${PROJECT_BINARY_DIR}/${PROJECT_NAME}.ico")
|
||||||
|
icon_for_win32 ("${icon_ico}" "${icon_png_list}" "${icon_png}")
|
||||||
|
|
||||||
|
set (resource_file "${PROJECT_BINARY_DIR}/${PROJECT_NAME}.rc")
|
||||||
|
list (APPEND project_sources "${resource_file}")
|
||||||
|
add_custom_command (OUTPUT "${resource_file}"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E echo "1 ICON \"${PROJECT_NAME}.ico\""
|
||||||
|
> "${resource_file}" VERBATIM)
|
||||||
|
set_property (SOURCE "${resource_file}"
|
||||||
|
APPEND PROPERTY OBJECT_DEPENDS "${icon_ico}")
|
||||||
|
else ()
|
||||||
|
list (APPEND project_libraries ${Ncursesw_LIBRARIES} ${Termo_LIBRARIES})
|
||||||
|
list (APPEND project_sources
|
||||||
|
src/${PROJECT_NAME}-tui.c)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (WITH_X11)
|
||||||
|
list (APPEND project_libraries ${xcb_LIBRARIES})
|
||||||
|
endif ()
|
||||||
|
if (WITH_GUI)
|
||||||
|
list (APPEND project_libraries ${gtk_LIBRARIES})
|
||||||
|
list (APPEND project_sources
|
||||||
|
src/${PROJECT_NAME}-gui.c
|
||||||
|
src/stardict-view.c)
|
||||||
|
|
||||||
|
add_executable (${PROJECT_NAME} WIN32 ${project_sources} ${project_headers})
|
||||||
|
else ()
|
||||||
|
add_executable (${PROJECT_NAME} ${project_sources} ${project_headers})
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
target_link_libraries (${PROJECT_NAME} ${project_libraries})
|
||||||
|
|
||||||
# Tools
|
# Tools
|
||||||
add_executable (query-tool EXCLUDE_FROM_ALL
|
set (tools tdv-tabfile tdv-add-pronunciation tdv-query-tool tdv-transform)
|
||||||
src/query-tool.c ${project_common_sources})
|
foreach (tool ${tools})
|
||||||
target_link_libraries (query-tool ${project_common_libraries})
|
add_executable (${tool} EXCLUDE_FROM_ALL
|
||||||
|
src/${tool}.c ${project_common_sources})
|
||||||
|
target_link_libraries (${tool} ${project_common_libraries})
|
||||||
|
endforeach ()
|
||||||
|
|
||||||
add_executable (add-pronunciation EXCLUDE_FROM_ALL
|
option (WITH_TOOLS "Build and install some StarDict tools" ${UNIX})
|
||||||
src/add-pronunciation.c ${project_common_sources})
|
if (WITH_TOOLS)
|
||||||
target_link_libraries (add-pronunciation ${project_common_libraries})
|
add_custom_target (tools ALL DEPENDS ${tools})
|
||||||
|
endif ()
|
||||||
|
|
||||||
add_custom_target (tools DEPENDS add-pronunciation query-tool)
|
# Example dictionaries
|
||||||
|
file (GLOB dicts_scripts "${PROJECT_SOURCE_DIR}/dicts/*.*")
|
||||||
|
set (dicts_targets)
|
||||||
|
foreach (dict_script ${dicts_scripts})
|
||||||
|
get_filename_component (dict_name "${dict_script}" NAME_WE)
|
||||||
|
list (APPEND dicts_targets "dicts-${dict_name}")
|
||||||
|
add_custom_target (dicts-${dict_name}
|
||||||
|
COMMAND sh -c "PATH=.:$PATH \"$0\"" "${dict_script}"
|
||||||
|
DEPENDS tdv-tabfile
|
||||||
|
COMMENT "Generating sample dictionary ${dict_name}"
|
||||||
|
VERBATIM)
|
||||||
|
endforeach ()
|
||||||
|
|
||||||
|
add_custom_target (dicts DEPENDS ${dicts_targets})
|
||||||
|
|
||||||
# The files to be installed
|
# The files to be installed
|
||||||
include (GNUInstallDirs)
|
if (NOT WIN32)
|
||||||
install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
include (GNUInstallDirs)
|
||||||
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
|
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
|
||||||
|
|
||||||
foreach (page ${project_MAN_PAGES_OUTPUT})
|
if (WITH_TOOLS)
|
||||||
string (REGEX MATCH "\\.([0-9])$" manpage_suffix "${page}")
|
install (TARGETS ${tools} DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||||
install (FILES "${page}"
|
endif ()
|
||||||
DESTINATION "${CMAKE_INSTALL_MANDIR}/man${CMAKE_MATCH_1}")
|
if (WITH_GUI)
|
||||||
endforeach (page)
|
install (FILES ${PROJECT_NAME}.svg
|
||||||
|
DESTINATION ${CMAKE_INSTALL_DATADIR}/icons/hicolor/scalable/apps)
|
||||||
|
install (DIRECTORY ${icon_base}
|
||||||
|
DESTINATION ${CMAKE_INSTALL_DATADIR})
|
||||||
|
install (FILES ${PROJECT_NAME}.desktop
|
||||||
|
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications)
|
||||||
|
install (FILES ${PROJECT_NAME}.xml
|
||||||
|
DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/packages)
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
foreach (page ${project_MAN_PAGES})
|
||||||
|
string (REGEX MATCH "\\.([0-9])$" manpage_suffix "${page}")
|
||||||
|
install (FILES "${page}"
|
||||||
|
DESTINATION "${CMAKE_INSTALL_MANDIR}/man${CMAKE_MATCH_1}")
|
||||||
|
endforeach ()
|
||||||
|
elseif (WITH_GUI)
|
||||||
|
# This rather crude filter has been mostly copied over from logdiag
|
||||||
|
install (TARGETS ${PROJECT_NAME} DESTINATION .)
|
||||||
|
install (DIRECTORY
|
||||||
|
${win32_deps_prefix}/bin/
|
||||||
|
DESTINATION .
|
||||||
|
FILES_MATCHING PATTERN "*.dll")
|
||||||
|
install (DIRECTORY
|
||||||
|
${win32_deps_prefix}/etc/
|
||||||
|
DESTINATION etc)
|
||||||
|
install (DIRECTORY
|
||||||
|
${win32_deps_prefix}/lib/gdk-pixbuf-2.0
|
||||||
|
DESTINATION lib
|
||||||
|
FILES_MATCHING PATTERN "*" PATTERN "*.a" EXCLUDE)
|
||||||
|
install (DIRECTORY
|
||||||
|
${win32_deps_prefix}/share/glib-2.0/schemas
|
||||||
|
DESTINATION share/glib-2.0)
|
||||||
|
|
||||||
|
install (DIRECTORY
|
||||||
|
${win32_deps_prefix}/share/icons/Adwaita
|
||||||
|
DESTINATION share/icons OPTIONAL)
|
||||||
|
install (FILES
|
||||||
|
${win32_deps_prefix}/share/icons/hicolor/index.theme
|
||||||
|
DESTINATION share/icons/hicolor)
|
||||||
|
install (DIRECTORY "${icon_base}" DESTINATION share)
|
||||||
|
|
||||||
|
install (SCRIPT cmake/Win32Cleanup.cmake)
|
||||||
|
|
||||||
|
find_program (GTK_UPDATE_ICON_CACHE_EXECUTABLE gtk-update-icon-cache)
|
||||||
|
if (NOT GTK_UPDATE_ICON_CACHE_EXECUTABLE)
|
||||||
|
message (FATAL_ERROR "gtk-update-icon-cache not found")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
install (CODE "execute_process (COMMAND
|
||||||
|
sh \"${PROJECT_SOURCE_DIR}/cmake/Win32CleanupAdwaita.sh\"
|
||||||
|
WORKING_DIRECTORY \${CMAKE_INSTALL_PREFIX})")
|
||||||
|
install (CODE " # This may speed up program start-up a little bit
|
||||||
|
execute_process (COMMAND \"${GTK_UPDATE_ICON_CACHE_EXECUTABLE}\"
|
||||||
|
\"\${CMAKE_INSTALL_PREFIX}/share/icons/Adwaita\")")
|
||||||
|
endif ()
|
||||||
|
|
||||||
# Do some unit tests
|
# Do some unit tests
|
||||||
option (BUILD_TESTING "Build tests" OFF)
|
option (BUILD_TESTING "Build tests" OFF)
|
||||||
set (project_tests stardict)
|
|
||||||
|
|
||||||
if (BUILD_TESTING)
|
if (BUILD_TESTING)
|
||||||
enable_testing ()
|
enable_testing ()
|
||||||
|
|
||||||
foreach (name ${project_tests})
|
find_program (xmlwf_EXECUTABLE xmlwf)
|
||||||
|
find_program (xmllint_EXECUTABLE xmllint)
|
||||||
|
foreach (xml ${PROJECT_NAME}.xml ${PROJECT_NAME}.svg)
|
||||||
|
if (xmlwf_EXECUTABLE)
|
||||||
|
add_test (test-xmlwf-${xml} ${xmlwf_EXECUTABLE}
|
||||||
|
"${PROJECT_SOURCE_DIR}/${xml}")
|
||||||
|
endif ()
|
||||||
|
if (xmllint_EXECUTABLE)
|
||||||
|
add_test (test-xmllint-${xml} ${xmllint_EXECUTABLE} --noout
|
||||||
|
"${PROJECT_SOURCE_DIR}/${xml}")
|
||||||
|
endif ()
|
||||||
|
endforeach ()
|
||||||
|
|
||||||
|
find_program (dfv_EXECUTABLE desktop-file-validate)
|
||||||
|
if (dfv_EXECUTABLE)
|
||||||
|
foreach (df ${PROJECT_NAME}.desktop)
|
||||||
|
add_test (test-dfv-${df} ${dfv_EXECUTABLE}
|
||||||
|
"${PROJECT_SOURCE_DIR}/${df}")
|
||||||
|
endforeach ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
foreach (name stardict)
|
||||||
add_executable (test-${name}
|
add_executable (test-${name}
|
||||||
src/test-${name}.c ${project_common_sources})
|
src/test-${name}.c ${project_common_sources})
|
||||||
target_link_libraries (test-${name} ${project_common_libraries})
|
target_link_libraries (test-${name} ${project_common_libraries})
|
||||||
add_test (test-${name} test-${name})
|
add_test (NAME test-${name} COMMAND test-${name})
|
||||||
endforeach (name)
|
endforeach ()
|
||||||
endif (BUILD_TESTING)
|
endif ()
|
||||||
|
|
||||||
# CPack
|
# CPack
|
||||||
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "StarDict terminal UI")
|
set (CPACK_PACKAGE_DESCRIPTION_SUMMARY "Translation dictionary viewer")
|
||||||
set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch")
|
set (CPACK_PACKAGE_VENDOR "Premysl Eric Janouch")
|
||||||
set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>")
|
set (CPACK_PACKAGE_CONTACT "Přemysl Eric Janouch <p@janouch.name>")
|
||||||
set (CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
|
set (CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
|
||||||
set (CPACK_PACKAGE_VERSION_MAJOR ${project_VERSION_MAJOR})
|
|
||||||
set (CPACK_PACKAGE_VERSION_MINOR ${project_VERSION_MINOR})
|
|
||||||
set (CPACK_PACKAGE_VERSION_PATCH ${project_VERSION_PATCH})
|
|
||||||
set (CPACK_GENERATOR "TGZ;ZIP")
|
set (CPACK_GENERATOR "TGZ;ZIP")
|
||||||
set (CPACK_PACKAGE_FILE_NAME
|
set (CPACK_PACKAGE_FILE_NAME
|
||||||
"${PROJECT_NAME}-${project_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
|
"${PROJECT_NAME}-${PROJECT_VERSION}-${CMAKE_SYSTEM_NAME}-${CMAKE_SYSTEM_PROCESSOR}")
|
||||||
set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME}-${project_VERSION}")
|
set (CPACK_PACKAGE_INSTALL_DIRECTORY "${PROJECT_NAME} ${PROJECT_VERSION}")
|
||||||
set (CPACK_SOURCE_GENERATOR "TGZ;ZIP")
|
set (CPACK_SOURCE_GENERATOR "TGZ;ZIP")
|
||||||
set (CPACK_SOURCE_IGNORE_FILES "/\\\\.git;/build;/CMakeLists.txt.user")
|
set (CPACK_SOURCE_IGNORE_FILES "/\\\\.git;/build;/CMakeLists.txt.user")
|
||||||
set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${project_VERSION}")
|
set (CPACK_SOURCE_PACKAGE_FILE_NAME "${PROJECT_NAME}-${PROJECT_VERSION}")
|
||||||
|
|
||||||
|
# XXX: It is still possible to install multiple copies, making commands collide.
|
||||||
|
set (CPACK_NSIS_ENABLE_UNINSTALL_BEFORE_INSTALL ON)
|
||||||
|
set (CPACK_PACKAGE_INSTALL_REGISTRY_KEY "${PROJECT_NAME}")
|
||||||
|
|
||||||
|
set (CPACK_NSIS_INSTALLED_ICON_NAME ${PROJECT_NAME}.exe)
|
||||||
|
set (CPACK_PACKAGE_EXECUTABLES ${PROJECT_NAME} ${PROJECT_NAME})
|
||||||
|
set (CPACK_NSIS_EXECUTABLES_DIRECTORY .)
|
||||||
|
set (CPACK_NSIS_EXTRA_INSTALL_COMMANDS [[
|
||||||
|
WriteRegStr HKCR '.ifo' '' 'tdv.Dictionary'
|
||||||
|
WriteRegStr HKCR 'tdv.Dictionary' '' 'StarDict Dictionary'
|
||||||
|
WriteRegStr HKCR 'tdv.Dictionary\\shell\\open\\command' '' '\"$INSTDIR\\tdv.exe\" \"%1\"'
|
||||||
|
System::Call 'shell32::SHChangeNotify(i,i,i,i) (0x08000000, 0x1000, 0, 0)'
|
||||||
|
]])
|
||||||
|
set (CPACK_NSIS_EXTRA_UNINSTALL_COMMANDS [[
|
||||||
|
DeleteRegKey HKCR 'tdv.Dictionary'
|
||||||
|
System::Call 'shell32::SHChangeNotify(i,i,i,i) (0x08000000, 0x1000, 0, 0)'
|
||||||
|
]])
|
||||||
|
|
||||||
include (CPack)
|
include (CPack)
|
||||||
|
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,4 +1,4 @@
|
|||||||
Copyright (c) 2013 - 2020, Přemysl Eric Janouch <p@janouch.name>
|
Copyright (c) 2013 - 2024, Přemysl Eric Janouch <p@janouch.name>
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
Permission to use, copy, modify, and/or distribute this software for any
|
||||||
purpose with or without fee is hereby granted.
|
purpose with or without fee is hereby granted.
|
||||||
|
|||||||
149
README.adoc
149
README.adoc
@@ -1,43 +1,51 @@
|
|||||||
StarDict Terminal UI
|
Translation dictionary viewer
|
||||||
====================
|
=============================
|
||||||
|
|
||||||
'sdtui' aims to provide an easy way of viewing translation as well as other
|
'tdv' aims to provide an easy way of viewing translation as well as other kinds
|
||||||
kinds of dictionaries in your terminal. I wasn't successful in finding any free
|
of StarDict dictionaries, and is inspired by the dictionary component
|
||||||
dictionary software of this kind, GUI or not, and thus decided to write my own.
|
of PC Translator. I was unsuccessful in finding any free software of this kind,
|
||||||
|
and thus decided to write my own.
|
||||||
|
|
||||||
|
The program offers both a terminal user interface, and a GTK+ 3 based UI.
|
||||||
|
The styling of the latter will follow your theme, and may be customized
|
||||||
|
from 'gtk.css'.
|
||||||
|
|
||||||
The project is covered by a permissive license, unlike vast majority of other
|
The project is covered by a permissive license, unlike vast majority of other
|
||||||
similar projects, and can serve as a base for implementing other dictionary
|
similar projects, and can serve as a base for implementing other dictionary
|
||||||
software. I wasn't able to reuse _anything_ for StarDict.
|
software.
|
||||||
|
|
||||||
Further Development
|
Screenshot
|
||||||
-------------------
|
----------
|
||||||
While I've been successfully using sdtui for a long time now, some work has to
|
image::tdv.png[align="center"]
|
||||||
be done yet before the software can be considered fit for inclusion in regular
|
|
||||||
Linux and/or BSD distributions. Help is much appreciated.
|
|
||||||
|
|
||||||
An approximate list of things that need to be resolved:
|
|
||||||
|
|
||||||
- rewrite the frontend using a proper TUI framework
|
|
||||||
- possibly make it work better with multiple dictionaries as now it's only
|
|
||||||
acceptable if you give them short names in the configuration so that they
|
|
||||||
all fit in the header
|
|
||||||
- figure out a way to become capable of displaying most dictionaries
|
|
||||||
|
|
||||||
Packages
|
Packages
|
||||||
--------
|
--------
|
||||||
Regular releases are sporadic. git master should be stable enough. You can get
|
Regular releases are sporadic. git master should be stable enough.
|
||||||
a package with the latest development version from Archlinux's AUR.
|
You can get a package with the latest development version using Arch Linux's
|
||||||
|
https://aur.archlinux.org/packages/tdv-git[AUR],
|
||||||
|
or as a https://git.janouch.name/p/nixexprs[Nix derivation].
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
-------------
|
||||||
|
See the link:docs/tdv.1.adoc[man page] for information about usage.
|
||||||
|
The rest of this README will concern itself with externalities.
|
||||||
|
|
||||||
Building and Running
|
Building and Running
|
||||||
--------------------
|
--------------------
|
||||||
Build dependencies: CMake, pkg-config, xsltproc, docbook-xsl +
|
Build-only dependencies:
|
||||||
Runtime dependencies: ncursesw, zlib, ICU, termo (included),
|
CMake, pkg-config, gettext utilities, asciidoctor or asciidoc +
|
||||||
glib-2.0, pango, xcb and xcb-xfixes (optional)
|
Optional build-only dependencies:
|
||||||
|
librsvg (for the GUI), icoutils (for the GUI, when targetting Windows) +
|
||||||
|
Runtime dependencies:
|
||||||
|
ncursesw, zlib, ICU, termo (included), glib-2.0 >= 2.38, pango +
|
||||||
|
Optional runtime dependencies:
|
||||||
|
xcb, xcb-xfixes (the first two for the TUI), gtk+-3.0 (for the GUI)
|
||||||
|
|
||||||
$ git clone --recursive https://git.janouch.name/p/sdtui.git
|
$ git clone --recursive https://git.janouch.name/p/tdv.git
|
||||||
$ mkdir sdtui/build
|
$ mkdir tdv/build
|
||||||
$ cd sdtui/build
|
$ cd tdv/build
|
||||||
$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug -DWITH_X11=ON
|
$ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug \
|
||||||
|
-DWITH_X11=ON -DWITH_GUI=ON
|
||||||
$ make
|
$ make
|
||||||
|
|
||||||
To install the application, you can do either the usual:
|
To install the application, you can do either the usual:
|
||||||
@@ -47,65 +55,52 @@ To install the application, you can do either the usual:
|
|||||||
Or you can try telling CMake to make a package for you. For Debian it is:
|
Or you can try telling CMake to make a package for you. For Debian it is:
|
||||||
|
|
||||||
$ cpack -G DEB
|
$ cpack -G DEB
|
||||||
# dpkg -i sdtui-*.deb
|
# dpkg -i tdv-*.deb
|
||||||
|
|
||||||
Note that for versions of CMake before 2.8.9, you need to prefix `cpack` with
|
Having the program installed, simply run it with a StarDict '.ifo' file as
|
||||||
`fakeroot` or file ownership will end up wrong.
|
an argument. It is, however, preferable to
|
||||||
|
link:docs/tdv.1.adoc#_configuration[configure it] to load your dictionaries
|
||||||
|
automatically.
|
||||||
|
|
||||||
Having the program installed, simply run it with a StarDict '.ifo' file as an
|
Windows
|
||||||
argument. It is however highly recommended to configure it, see below.
|
~~~~~~~
|
||||||
|
With the help of Mingw-w64 and WINE, 'tdv' will successfully cross-compile
|
||||||
|
for Windows. It isn't particularly usable on that system, if only because
|
||||||
|
selection watching is a very X11/Wayland-specific feature. Beware that build
|
||||||
|
dependencies take up almost a gigabyte of disk space.
|
||||||
|
|
||||||
Extensions
|
$ sh -e cmake/Win64Depends.sh
|
||||||
----------
|
$ cmake -DCMAKE_TOOLCHAIN_FILE=liberty/cmake/toolchains/MinGW-w64-x64.cmake \
|
||||||
As the original StarDict is a bit of a clusterfuck with regard to collation of
|
-DCMAKE_BUILD_TYPE=Release -B build
|
||||||
dictionary entries, I had to introduce an additional `collation` field into the
|
$ cmake --build build -- package
|
||||||
'.ifo' file. When sdtui discovers this field while reading the dictionary, it
|
|
||||||
automatically reorders the index according to that locale (e.g. "cs_CZ").
|
|
||||||
This operation may take a little while, in the order of seconds.
|
|
||||||
|
|
||||||
Configuration
|
|
||||||
-------------
|
|
||||||
To get a nicer look in 256color terminals, create _~/.config/sdtui/sdtui.conf_
|
|
||||||
with the following. Note that it is intended for black-on-white terminals.
|
|
||||||
|
|
||||||
....
|
|
||||||
[Settings]
|
|
||||||
center-search = true
|
|
||||||
underline-last = false
|
|
||||||
hl-common-prefix = true
|
|
||||||
watch-selection = true
|
|
||||||
|
|
||||||
[Colors]
|
|
||||||
header = reverse
|
|
||||||
header-active = ul
|
|
||||||
search = ul
|
|
||||||
even = 16 231
|
|
||||||
odd = 16 255
|
|
||||||
....
|
|
||||||
|
|
||||||
The `watch-selection` option makes the application watch the X11 primary
|
|
||||||
selection for changes and automatically search for selected text.
|
|
||||||
This feature requires XCB and it will never work on Wayland by its design.
|
|
||||||
|
|
||||||
You can also set up some dictionaries to be loaded at startup automatically:
|
|
||||||
|
|
||||||
....
|
|
||||||
[Dictionaries]
|
|
||||||
name1 = ~/path/to/dict.ifo
|
|
||||||
name2 = ~/another/dict.ifo
|
|
||||||
....
|
|
||||||
|
|
||||||
Dictionaries
|
Dictionaries
|
||||||
------------
|
------------
|
||||||
Unfortunately this application only really works with specific dictionaries.
|
This application is intended for use with specific dictionaries: each line
|
||||||
Word definitions have to be in plain text, separated by newlines.
|
should contain one short word definition. Moreover, the only supported content
|
||||||
|
types are plain text, Pango markup, and XDXF (the visual format works better).
|
||||||
|
|
||||||
https://mega.co.nz/#!axtD0QRK!sbtBgizksyfkPqKvKEgr8GQ11rsWhtqyRgUUV0B7pwg[
|
The `make dicts` command will build some examples from freely available sources:
|
||||||
CZ <--> { EN, DE, PL, RU } dictionaries]
|
|
||||||
|
- GNU/FDL Czech-English dictionary
|
||||||
|
- Czech foreign words (the site's export is broken as of 2022/08, no response)
|
||||||
|
- Czech WordNet 1.9 PDT (synonyms, hypernyms, hyponyms)
|
||||||
|
|
||||||
|
You can use the included 'tdv-transform' tool to convert already existing
|
||||||
|
StarDict dictionaries that are nearly good as they are. Remember that you can
|
||||||
|
change the `sametypesequence` of the resulting '.ifo' file to another format,
|
||||||
|
or run 'dictzip' on '.dict' files to make them compact.
|
||||||
|
|
||||||
|
https://mega.co.nz/#!axtD0QRK!sbtBgizksyfkPqKvKEgr8GQ11rsWhtqyRgUUV0B7pwg[CZ <--> EN/DE/PL/RU dictionaries]
|
||||||
|
|
||||||
|
Further Development
|
||||||
|
-------------------
|
||||||
|
Lacking configuration, standard StarDict locations should be scanned.
|
||||||
|
We should try harder to display arbitrary dictionaries sensibly.
|
||||||
|
|
||||||
Contributing and Support
|
Contributing and Support
|
||||||
------------------------
|
------------------------
|
||||||
Use https://git.janouch.name/p/sdtui to report any bugs, request features,
|
Use https://git.janouch.name/p/tdv to report any bugs, request features,
|
||||||
or submit pull requests. `git send-email` is tolerated. If you want to discuss
|
or submit pull requests. `git send-email` is tolerated. If you want to discuss
|
||||||
the project, feel free to join me at ircs://irc.janouch.name, channel #dev.
|
the project, feel free to join me at ircs://irc.janouch.name, channel #dev.
|
||||||
|
|
||||||
|
|||||||
8
cmake/FindLibIntl.cmake
Normal file
8
cmake/FindLibIntl.cmake
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
# Public Domain
|
||||||
|
|
||||||
|
find_library (LibIntl_LIBRARIES intl)
|
||||||
|
|
||||||
|
include (FindPackageHandleStandardArgs)
|
||||||
|
FIND_PACKAGE_HANDLE_STANDARD_ARGS (LibIntl DEFAULT_MSG LibIntl_LIBRARIES)
|
||||||
|
|
||||||
|
mark_as_advanced (LibIntl_LIBRARIES)
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
# Public Domain
|
|
||||||
|
|
||||||
find_package (PkgConfig REQUIRED)
|
|
||||||
pkg_check_modules (NCURSESW QUIET ncursesw)
|
|
||||||
|
|
||||||
# OpenBSD doesn't provide a pkg-config file
|
|
||||||
set (required_vars NCURSESW_LIBRARIES)
|
|
||||||
if (NOT NCURSESW_FOUND)
|
|
||||||
find_library (NCURSESW_LIBRARIES NAMES ncursesw)
|
|
||||||
find_path (NCURSESW_INCLUDE_DIRS ncurses.h)
|
|
||||||
list (APPEND required_vars NCURSESW_INCLUDE_DIRS)
|
|
||||||
endif (NOT NCURSESW_FOUND)
|
|
||||||
|
|
||||||
include (FindPackageHandleStandardArgs)
|
|
||||||
FIND_PACKAGE_HANDLE_STANDARD_ARGS (NCURSESW DEFAULT_MSG ${required_vars})
|
|
||||||
|
|
||||||
mark_as_advanced (NCURSESW_LIBRARIES NCURSESW_INCLUDE_DIRS)
|
|
||||||
37
cmake/Win32Cleanup.cmake
Normal file
37
cmake/Win32Cleanup.cmake
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# To be run from cmake_install.cmake, eradicates all unreferenced libraries.
|
||||||
|
# CMake 3.9.6 has a parsing bug with ENCODING UTF-8.
|
||||||
|
cmake_minimum_required (VERSION 3.10)
|
||||||
|
|
||||||
|
# CPack runs this almost without any CMake variables at all
|
||||||
|
# (cmStateSnapshot::SetDefaultDefinitions(), CMAKE_INSTALL_PREFIX, [DESTDIR])
|
||||||
|
set (installdir "${CMAKE_INSTALL_PREFIX}")
|
||||||
|
if (NOT installdir OR installdir MATCHES "^/usr(/|$)")
|
||||||
|
return ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
# The function is recursive and CMake has tragic scoping behaviour;
|
||||||
|
# environment variables are truly global there, in the absence of a cache
|
||||||
|
unset (ENV{seen})
|
||||||
|
function (expand path)
|
||||||
|
set (seen $ENV{seen})
|
||||||
|
if (path IN_LIST seen OR NOT EXISTS "${path}")
|
||||||
|
return ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set (ENV{seen} "$ENV{seen};${path}")
|
||||||
|
file (STRINGS "${path}" strings REGEX "[.][Dd][Ll][Ll]$" ENCODING UTF-8)
|
||||||
|
foreach (string ${strings})
|
||||||
|
string (REGEX MATCH "[-.+_a-zA-Z0-9]+$" word "${string}")
|
||||||
|
expand ("${installdir}/${word}")
|
||||||
|
endforeach ()
|
||||||
|
endfunction ()
|
||||||
|
|
||||||
|
file (GLOB roots LIST_DIRECTORIES false "${installdir}/*.[Ee][Xx][Ee]"
|
||||||
|
"${installdir}/lib/gdk-pixbuf-2.0/2.10.0/loaders/*.[Dd][Ll][Ll]")
|
||||||
|
foreach (binary ${roots})
|
||||||
|
expand ("${binary}")
|
||||||
|
endforeach ()
|
||||||
|
|
||||||
|
file (GLOB libraries LIST_DIRECTORIES false "${installdir}/*.[Dd][Ll][Ll]")
|
||||||
|
list (REMOVE_ITEM libraries $ENV{seen})
|
||||||
|
file (REMOVE ${libraries})
|
||||||
20
cmake/Win32CleanupAdwaita.sh
Normal file
20
cmake/Win32CleanupAdwaita.sh
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
# Removes unused icons from the Adwaita theme. It could be even more aggressive,
|
||||||
|
# since it keeps around lots of sizes and all the GTK+ stock icons.
|
||||||
|
export LC_ALL=C
|
||||||
|
find share/icons/Adwaita -type f | awk 'BEGIN {
|
||||||
|
while (("grep -aho \"[a-z][a-z-]*\" *.dll *.exe" | getline) > 0)
|
||||||
|
good[$0] = 1
|
||||||
|
} /[.](png|svg|cur|ani)$/ {
|
||||||
|
# Cut out the basename without extensions
|
||||||
|
match($0, /[^\/]+$/)
|
||||||
|
base = substr($0, RSTART)
|
||||||
|
sub(/[.].+$/, "", base)
|
||||||
|
|
||||||
|
# Try matching while cutting off suffixes
|
||||||
|
# Disregarding the not-much-used GTK_ICON_LOOKUP_GENERIC_FALLBACK
|
||||||
|
while (!(keep = good[base]) &&
|
||||||
|
sub(/-(ltr|rtl|symbolic)$/, "", base)) {}
|
||||||
|
if (!keep)
|
||||||
|
print
|
||||||
|
}' | xargs rm --
|
||||||
74
cmake/Win64Depends.sh
Normal file
74
cmake/Win64Depends.sh
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
# Win64Depends.sh: download dependencies from MSYS2 for cross-compilation.
|
||||||
|
# Dependencies: AWK, sed, sha256sum, cURL, bsdtar, wine64
|
||||||
|
repository=https://repo.msys2.org/mingw/mingw64/
|
||||||
|
|
||||||
|
status() {
|
||||||
|
echo "$(tput bold)-- $*$(tput sgr0)"
|
||||||
|
}
|
||||||
|
|
||||||
|
dbsync() {
|
||||||
|
status Fetching repository DB
|
||||||
|
[ -f db.tsv ] || curl -# "$repository/mingw64.db" | bsdtar -xOf- | awk '
|
||||||
|
function flush() { print f["%NAME%"] f["%FILENAME%"] f["%DEPENDS%"] }
|
||||||
|
NR > 1 && $0 == "%FILENAME%" { flush(); for (i in f) delete f[i] }
|
||||||
|
!/^[^%]/ { field = $0; next } { f[field] = f[field] $0 "\t" }
|
||||||
|
field == "%SHA256SUM%" { path = "*packages/" f["%FILENAME%"]
|
||||||
|
sub(/\t$/, "", path); print $0, path > "db.sums" } END { flush() }
|
||||||
|
' > db.tsv
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch() {
|
||||||
|
status Resolving "$@"
|
||||||
|
mkdir -p packages
|
||||||
|
awk -F'\t' 'function get(name, i, a) {
|
||||||
|
if (visited[name]++ || !(name in filenames)) return
|
||||||
|
print filenames[name]; split(deps[name], a); for (i in a) get(a[i])
|
||||||
|
} BEGIN { while ((getline < "db.tsv") > 0) {
|
||||||
|
filenames[$1] = $2; deps[$1] = ""; for (i = 3; i <= NF; i++) {
|
||||||
|
gsub(/[<=>].*/, "", $i); deps[$1] = deps[$1] $i FS }
|
||||||
|
} for (i = 0; i < ARGC; i++) get(ARGV[i]) }' "$@" | tee db.want | \
|
||||||
|
while IFS= read -r name
|
||||||
|
do
|
||||||
|
status Fetching "$name"
|
||||||
|
[ -f "packages/$name" ] || curl -#o "packages/$name" "$repository/$name"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
verify() {
|
||||||
|
status Verifying checksums
|
||||||
|
sha256sum --ignore-missing --quiet -c db.sums
|
||||||
|
}
|
||||||
|
|
||||||
|
extract() {
|
||||||
|
status Extracting packages
|
||||||
|
for subdir in *
|
||||||
|
do [ -d "$subdir" -a "$subdir" != packages ] && rm -rf -- "$subdir"
|
||||||
|
done
|
||||||
|
while IFS= read -r name
|
||||||
|
do bsdtar -xf "packages/$name" --strip-components 1
|
||||||
|
done < db.want
|
||||||
|
}
|
||||||
|
|
||||||
|
configure() {
|
||||||
|
status Configuring packages
|
||||||
|
glib-compile-schemas share/glib-2.0/schemas
|
||||||
|
wine64 bin/gdk-pixbuf-query-loaders.exe \
|
||||||
|
> lib/gdk-pixbuf-2.0/2.10.0/loaders.cache
|
||||||
|
}
|
||||||
|
|
||||||
|
# This directory name matches the prefix in .pc files, so we don't need to
|
||||||
|
# modify them (pkgconf has --prefix-variable, but CMake can't pass that option).
|
||||||
|
mkdir -p mingw64
|
||||||
|
cd mingw64
|
||||||
|
dbsync
|
||||||
|
fetch mingw-w64-x86_64-gtk3 mingw-w64-x86_64-icu \
|
||||||
|
mingw-w64-x86_64-libwinpthread-git # Because we don't do "provides"?
|
||||||
|
verify
|
||||||
|
extract
|
||||||
|
configure
|
||||||
|
|
||||||
|
status Success
|
||||||
|
|
||||||
|
# XXX: Why is this override needed to run some GLib-based things under wine64?
|
||||||
|
unset XDG_DATA_DIRS
|
||||||
@@ -2,13 +2,13 @@
|
|||||||
#define CONFIG_H
|
#define CONFIG_H
|
||||||
|
|
||||||
#define PROJECT_NAME "${PROJECT_NAME}"
|
#define PROJECT_NAME "${PROJECT_NAME}"
|
||||||
#define PROJECT_VERSION "${project_VERSION}"
|
#define PROJECT_VERSION "${PROJECT_VERSION}"
|
||||||
#define PROJECT_URL "${project_URL}"
|
|
||||||
|
|
||||||
#define GETTEXT_PACKAGE PROJECT_NAME
|
#define GETTEXT_PACKAGE PROJECT_NAME
|
||||||
#define GETTEXT_DIRNAME "${CMAKE_INSTALL_PREFIX}/share/locale"
|
#define GETTEXT_DIRNAME "${CMAKE_INSTALL_PREFIX}/share/locale"
|
||||||
|
|
||||||
#cmakedefine WITH_X11
|
#cmakedefine WITH_X11
|
||||||
|
#cmakedefine WITH_GUI
|
||||||
#cmakedefine HAVE_RESIZETERM
|
#cmakedefine HAVE_RESIZETERM
|
||||||
|
|
||||||
#endif // ! CONFIG_H
|
#endif // ! CONFIG_H
|
||||||
|
|||||||
85
dicts/czech-wordnet.pl
Executable file
85
dicts/czech-wordnet.pl
Executable file
@@ -0,0 +1,85 @@
|
|||||||
|
#!/usr/bin/env perl
|
||||||
|
# Czech WordNet 1.9 PDT, CC BY-NC-SA 3.0, newer versions available commercially;
|
||||||
|
# this one's IDs cannot be linked with any release of the Princeton WordNet
|
||||||
|
use warnings;
|
||||||
|
use strict;
|
||||||
|
|
||||||
|
# GNU Gzip can unpack a ZIP file, but not the BSD one, and unzip can't use stdin
|
||||||
|
my $zipcat = qx/(command -v bsdtar)/ ? 'bsdtar -xOf-' : 'zcat';
|
||||||
|
|
||||||
|
my $base = 'https://lindat.cz/repository/xmlui';
|
||||||
|
my $path = 'handle/11858/00-097C-0000-0001-4880-3';
|
||||||
|
open(my $doc, '-|',
|
||||||
|
"curl -Lo- '$base/bitstream/$path/Czech_WordNet_1.9_PDT.zip'"
|
||||||
|
. " | $zipcat | iconv -f latin2 -t UTF-8") or die $!;
|
||||||
|
|
||||||
|
# https://nlp.fi.muni.cz/trac/deb2/wiki/WordNetFormat but not quite;
|
||||||
|
# for terminology see https://wordnet.princeton.edu/documentation/wngloss7wn
|
||||||
|
my %synsets;
|
||||||
|
while (<$doc>) {
|
||||||
|
my $id = m|<ID>(.+?)</ID>| && $1; next unless defined $id;
|
||||||
|
my $pos = m|<POS>(.+?)</POS>| && $1; next if $pos eq 'e';
|
||||||
|
$synsets{$id} = {
|
||||||
|
literals => [map {s| \^\d+||gr} m|<LITERAL>(.+?)<|g],
|
||||||
|
rels => {
|
||||||
|
anto => [m^<ILR>(.+?)<TYPE>near_antonym<^g],
|
||||||
|
hyper => [m^<ILR>(.+?)<TYPE>hypernym<^g],
|
||||||
|
hypo => [m^<ILR>(.+?)<TYPE>hyponym<^g,
|
||||||
|
m^<SUBEVENT>(.+?)</SUBEVENT>^g],
|
||||||
|
super => [m^<ILR>(.+?)<TYPE>holo_part<^g],
|
||||||
|
sub => [m^<ILR>(.+?)<TYPE>(?:holo_member|mero_part|partonym)<^g],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
# Resolve all synset links to hash references, filtering out what can't be found
|
||||||
|
while (my ($id, $synset) = each %synsets) {
|
||||||
|
while (my ($name, $links) = each %{$synset->{rels}}) {
|
||||||
|
@$links = map {$synsets{$_} || ()} @$links;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Ensure symmetry in relationships, duplicates will be taken care of later
|
||||||
|
my %antitags = qw(anto anto hyper hypo hypo hyper super sub sub super);
|
||||||
|
while (my ($id, $synset) = each %synsets) {
|
||||||
|
while (my ($name, $links) = each %{$synset->{rels}}) {
|
||||||
|
push @{$_->{rels}->{$antitags{$name}}}, $synset for @$links;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create an inverse index from literals/keywords to their synsets
|
||||||
|
my %literals;
|
||||||
|
while (my ($id, $synset) = each %synsets) {
|
||||||
|
push @{$literals{$_}}, $synset for @{$synset->{literals}};
|
||||||
|
}
|
||||||
|
|
||||||
|
# Output synsets exploded to individual words, with expanded relationships
|
||||||
|
close($doc) or die $?;
|
||||||
|
open(my $tabfile, '|-', 'tdv-tabfile', 'czech-wordnet',
|
||||||
|
'--book-name=Czech WordNet 1.9 PDT', "--website=$base/$path",
|
||||||
|
'--date=2011-01-24', '--collation=cs_CZ') or die $!;
|
||||||
|
|
||||||
|
sub expand {
|
||||||
|
my %seen;
|
||||||
|
return grep {!$seen{$_}++} (map {@{$_->{literals}}} @_);
|
||||||
|
}
|
||||||
|
|
||||||
|
for my $keyword (sort {lc $a cmp lc $b} keys %literals) {
|
||||||
|
my @lines;
|
||||||
|
for my $synset (@{$literals{$keyword}}) {
|
||||||
|
my $rels = $synset->{rels};
|
||||||
|
push @lines,
|
||||||
|
(grep {$_ ne $keyword} @{$synset->{literals}}),
|
||||||
|
(map {"$_ ↑"} expand(@{$rels->{hyper}})),
|
||||||
|
(map {"$_ ↓"} expand(@{$rels->{hypo}})),
|
||||||
|
(map {"$_ ⊃"} expand(@{$rels->{super}})),
|
||||||
|
(map {"$_ ⊂"} expand(@{$rels->{sub}})),
|
||||||
|
(map {"$_ ≠"} expand(@{$rels->{anto}}));
|
||||||
|
}
|
||||||
|
if (@lines) {
|
||||||
|
print $tabfile "$keyword\t" . join('\n',
|
||||||
|
map { s/</</gr =~ s/>/>/gr =~ s/&/&/gr
|
||||||
|
=~ s/\\/\\\\/gr =~ s/\n/\\n/gr =~ s/\t/\\t/gr} @lines) . "\n";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
close($tabfile) or die $?;
|
||||||
34
dicts/gnu-fdl-de-cz.sh
Executable file
34
dicts/gnu-fdl-de-cz.sh
Executable file
@@ -0,0 +1,34 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
# GNU/FDL German-Czech dictionary, see https://gnu.nemeckoceskyslovnik.cz
|
||||||
|
|
||||||
|
# Sometimes the domain doesn't resolve, and the contents are close to useless
|
||||||
|
[ -n "$WANT_BAD_DICTS" ] || exit 0
|
||||||
|
|
||||||
|
curl -Lo- 'https://gnu.nemeckoceskyslovnik.cz/index.php?id=6&sablona=export&format=zcu' | \
|
||||||
|
grep -v ^# | sed 's/\\//g' | perl -CSD -F\\t -le '
|
||||||
|
sub tabesc { shift =~ s/\\/\\\\/gr =~ s/\n/\\n/gr =~ s/\t/\\t/gr }
|
||||||
|
sub w {
|
||||||
|
my ($name, $dict, $collation) = @_;
|
||||||
|
open(my $f, "|-", "tdv-tabfile", "--pango", "--collation=$collation",
|
||||||
|
"--website=https://gnu.nemeckoceskyslovnik.cz",
|
||||||
|
"gnu-fdl-$name") or die $!;
|
||||||
|
print $f tabesc($keyword) . "\t" . tabesc(join("\n", @$defs))
|
||||||
|
while ($keyword, $defs) = each %{$dict};
|
||||||
|
close($f);
|
||||||
|
}
|
||||||
|
sub xmlesc { shift =~ s/&/&/gr =~ s/</</gr =~ s/>/>/gr }
|
||||||
|
sub entry {
|
||||||
|
my ($definition, $notes) = map {xmlesc($_)} @_;
|
||||||
|
$notes ? "$definition <i>$notes</i>" : $definition;
|
||||||
|
}
|
||||||
|
next if !$_ .. 0;
|
||||||
|
my ($de, $cs, $notes, $special, $translator) = @F;
|
||||||
|
if ($cs) {
|
||||||
|
$notes =~ s/\w+:\s?//g; # remove word classes
|
||||||
|
$notes =~ s/(\w+\.)(?!])/($1)/; # quote "pl."
|
||||||
|
push(@{$decs{$de}}, entry($cs, $notes));
|
||||||
|
push(@{$csde{$cs}}, entry($de, $notes));
|
||||||
|
} END {
|
||||||
|
w("de-cz", \%decs, "de");
|
||||||
|
w("cz-de", \%csde, "cs");
|
||||||
|
}'
|
||||||
29
dicts/gnu-fdl-en-cz.sh
Executable file
29
dicts/gnu-fdl-en-cz.sh
Executable file
@@ -0,0 +1,29 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
# GNU/FDL English-Czech dictionary, see https://www.svobodneslovniky.cz/
|
||||||
|
curl -Lo- https://www.svobodneslovniky.cz/data/en-cs.txt.gz | \
|
||||||
|
zcat | grep -v ^# | sed 's/\\//g' | perl -CSD -F\\t -le '
|
||||||
|
sub tabesc { shift =~ s/\\/\\\\/gr =~ s/\n/\\n/gr =~ s/\t/\\t/gr }
|
||||||
|
sub w {
|
||||||
|
my ($name, $dict, $collation) = @_;
|
||||||
|
open(my $f, "|-", "tdv-tabfile", "--pango", "--collation=$collation",
|
||||||
|
"--website=https://www.svobodneslovniky.cz",
|
||||||
|
"gnu-fdl-$name") or die $!;
|
||||||
|
print $f tabesc($keyword) . "\t" . tabesc(join("\n", @$defs))
|
||||||
|
while ($keyword, $defs) = each %{$dict};
|
||||||
|
close($f);
|
||||||
|
}
|
||||||
|
sub xmlesc { shift =~ s/&/&/gr =~ s/</</gr =~ s/>/>/gr }
|
||||||
|
sub entry {
|
||||||
|
my ($definition, $notes) = map {xmlesc($_)} @_;
|
||||||
|
$notes ? "$definition <i>$notes</i>" : $definition;
|
||||||
|
}
|
||||||
|
my ($en, $cs, $notes, $special, $translator) = @F;
|
||||||
|
if ($cs) {
|
||||||
|
$notes =~ s/\w+:\s?//g; # remove word classes
|
||||||
|
$notes =~ s/(\w+\.)(?!])/($1)/; # quote "pl."
|
||||||
|
push(@{$encs{$en}}, entry($cs, $notes));
|
||||||
|
push(@{$csen{$cs}}, entry($en, $notes));
|
||||||
|
} END {
|
||||||
|
w("en-cz", \%encs, "en");
|
||||||
|
w("cz-en", \%csen, "cs");
|
||||||
|
}'
|
||||||
11
dicts/slovnik-cizich-slov.sh
Executable file
11
dicts/slovnik-cizich-slov.sh
Executable file
@@ -0,0 +1,11 @@
|
|||||||
|
#!/bin/sh -e
|
||||||
|
# Slovník cizích slov, see https://slovnik-cizich-slov.abz.cz/web.php/o-slovniku
|
||||||
|
# XXX: skipping the /optional/ pronunciation field, tabfile can't handle that
|
||||||
|
curl -Lo- https://slovnik-cizich-slov.abz.cz/export.php | \
|
||||||
|
iconv -f latin2 -t UTF-8 | perl -CSD -F\\\| -le '
|
||||||
|
print "$_\t" . $F[2] =~ s/\\/\\\\/gr =~ s/; /\\n/gr for split(", ", $F[0])
|
||||||
|
' | sort -u | tdv-tabfile slovnik-cizich-slov \
|
||||||
|
--book-name="Slovník cizích slov" \
|
||||||
|
--website=https://slovnik-cizich-slov.abz.cz \
|
||||||
|
--date="$(date +%F)" \
|
||||||
|
--collation=cs_CZ
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
<refentry>
|
|
||||||
|
|
||||||
<refentryinfo>
|
|
||||||
<title>sdtui</title>
|
|
||||||
<productname>sdtui</productname>
|
|
||||||
<author>
|
|
||||||
<firstname>Přemysl</firstname>
|
|
||||||
<surname>Janouch</surname>
|
|
||||||
</author>
|
|
||||||
</refentryinfo>
|
|
||||||
|
|
||||||
<refmeta>
|
|
||||||
<refentrytitle>sdtui</refentrytitle>
|
|
||||||
<manvolnum>1</manvolnum>
|
|
||||||
<refmiscinfo class="manual">User Commands</refmiscinfo>
|
|
||||||
</refmeta>
|
|
||||||
|
|
||||||
<refnamediv>
|
|
||||||
<refname>sdtui</refname>
|
|
||||||
<refpurpose>StarDict terminal UI</refpurpose>
|
|
||||||
</refnamediv>
|
|
||||||
|
|
||||||
<refsynopsisdiv>
|
|
||||||
<cmdsynopsis>
|
|
||||||
<command>sdtui</command>
|
|
||||||
<arg choice="opt" rep="repeat">
|
|
||||||
<option><replaceable>OPTION</replaceable></option>
|
|
||||||
</arg>
|
|
||||||
<arg choice="opt" rep="repeat">
|
|
||||||
<replaceable>dictionary.ifo</replaceable>
|
|
||||||
</arg>
|
|
||||||
</cmdsynopsis>
|
|
||||||
</refsynopsisdiv>
|
|
||||||
|
|
||||||
<refsect1><title>Description</title>
|
|
||||||
<para><command>sdtui</command> is a StarDict dictionary viewer custom tailored
|
|
||||||
for viewing translation dictionaries, using a simple curses-based terminal UI.
|
|
||||||
</para>
|
|
||||||
<para>The program expects to find on its command line the path to a dictionary's
|
|
||||||
.ifo file, which contains further information required for loading the
|
|
||||||
dictionary.</para>
|
|
||||||
<para>Some options as well as dictionaries to load on start by default can be
|
|
||||||
specified in a configuration file. See the README for an example.</para>
|
|
||||||
</refsect1>
|
|
||||||
|
|
||||||
<refsect1><title>Options</title>
|
|
||||||
<variablelist>
|
|
||||||
<varlistentry>
|
|
||||||
<term><option>-h</option>, <option>--help</option></term>
|
|
||||||
<listitem><para>
|
|
||||||
show help options
|
|
||||||
</para></listitem>
|
|
||||||
</varlistentry>
|
|
||||||
|
|
||||||
<varlistentry>
|
|
||||||
<term><option>-V</option>, <option>--version</option></term>
|
|
||||||
<listitem><para>
|
|
||||||
output version information and exit
|
|
||||||
</para></listitem>
|
|
||||||
</varlistentry>
|
|
||||||
</variablelist>
|
|
||||||
</refsect1>
|
|
||||||
|
|
||||||
</refentry>
|
|
||||||
100
docs/tdv.1.adoc
Normal file
100
docs/tdv.1.adoc
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
tdv(1)
|
||||||
|
======
|
||||||
|
:doctype: manpage
|
||||||
|
:manmanual: tdv Manual
|
||||||
|
:mansource: tdv {release-version}
|
||||||
|
|
||||||
|
Name
|
||||||
|
----
|
||||||
|
tdv - Translation dictionary viewer
|
||||||
|
|
||||||
|
Synopsis
|
||||||
|
--------
|
||||||
|
*tdv* [_OPTION_]... [_DICTIONARY_.ifo]...
|
||||||
|
|
||||||
|
Description
|
||||||
|
-----------
|
||||||
|
*tdv* is a StarDict dictionary viewer, custom-tailored for translation
|
||||||
|
dictionaries, with a simple curses-based terminal UI, and a GTK+ graphical UI.
|
||||||
|
|
||||||
|
Without any command line arguments, the program expects to find a list of
|
||||||
|
dictionaries to load on start-up in its configuration file. The _.ifo_ files
|
||||||
|
contain information required to load dictionaries from their accompanying
|
||||||
|
database files.
|
||||||
|
|
||||||
|
Options
|
||||||
|
-------
|
||||||
|
*--gui*::
|
||||||
|
Launch the GUI even when run from a terminal.
|
||||||
|
|
||||||
|
*-h*, *--help*::
|
||||||
|
Display a help message and exit.
|
||||||
|
|
||||||
|
*-V*, *--version*::
|
||||||
|
Output version information and exit.
|
||||||
|
|
||||||
|
Configuration
|
||||||
|
-------------
|
||||||
|
You can start your _tdv.conf_ file with the following snippet:
|
||||||
|
|
||||||
|
[Settings]
|
||||||
|
center-search = true # Ensure visibility of preceding entries?
|
||||||
|
underline-last = false # Underline the last line of entries?
|
||||||
|
hl-common-prefix = true # Highlight the longest common prefix?
|
||||||
|
watch-selection = true # Watch X11 selection for changes?
|
||||||
|
|
||||||
|
The _watch-selection_ option makes the application watch the X11 PRIMARY
|
||||||
|
selection for changes and automatically search for any selected text.
|
||||||
|
This feature requires XCB. Wayland is currently unsupported,
|
||||||
|
but would require a compositor supporting the wlr-data-control protocol.
|
||||||
|
Luckily, some compositors, such as Sway, synchronize selections with Xwayland.
|
||||||
|
|
||||||
|
To set up automatically loaded dictionaries, use the following scheme:
|
||||||
|
|
||||||
|
// AsciiDoc would otherwise like to process tildes as a long subscript.
|
||||||
|
:tilde: ~
|
||||||
|
|
||||||
|
[subs="normal"]
|
||||||
|
[Dictionaries]
|
||||||
|
_name 1_ = __{tilde}/path/to/dict.ifo__
|
||||||
|
_name 2_ = __{tilde}/another/dict.ifo__
|
||||||
|
|
||||||
|
The left-hand side keys define their appearance in the tab bar.
|
||||||
|
|
||||||
|
Finally, to make the program look nicer in 256color black-on-white terminals,
|
||||||
|
rather than rely on the universal default, try:
|
||||||
|
|
||||||
|
[Colors]
|
||||||
|
header = reverse
|
||||||
|
header-active = ul
|
||||||
|
search = ul
|
||||||
|
even = 16 231
|
||||||
|
odd = 16 255
|
||||||
|
|
||||||
|
Terminal attributes are accepted in a format similar to that of *git-config*(1),
|
||||||
|
only named colours aren't supported.
|
||||||
|
|
||||||
|
Extensions
|
||||||
|
----------
|
||||||
|
Because the StarDict file format is a bit of a clusterfuck with regard to
|
||||||
|
collation of dictionary entries, this software introduces an additional,
|
||||||
|
optional "collation" field into the _.ifo_ file. When *tdv* discovers this
|
||||||
|
field while reading a dictionary, it automatically reorders the index according
|
||||||
|
to that locale (e.g., "cs_CZ"). This operation may take a little while,
|
||||||
|
in the order of seconds.
|
||||||
|
|
||||||
|
Files
|
||||||
|
-----
|
||||||
|
*tdv* follows the XDG Base Directory Specification.
|
||||||
|
|
||||||
|
_~/.config/tdv/tdv.conf_::
|
||||||
|
The configuration file.
|
||||||
|
|
||||||
|
Reporting bugs
|
||||||
|
--------------
|
||||||
|
Use https://git.janouch.name/p/tdv to report bugs, request features,
|
||||||
|
or submit pull requests.
|
||||||
|
|
||||||
|
See also
|
||||||
|
--------
|
||||||
|
*dictzip*(1)
|
||||||
1
liberty
Submodule
1
liberty
Submodule
Submodule liberty added at 0f20cce9c8
112
po/cs.po
112
po/cs.po
@@ -1,131 +1,167 @@
|
|||||||
# SOME DESCRIPTIVE TITLE.
|
# SOME DESCRIPTIVE TITLE.
|
||||||
# Copyright (C) 2013 Přemysl Eric Janouch
|
# Copyright (C) 2013 Přemysl Eric Janouch
|
||||||
# This file is distributed under the same license as the sdtui package.
|
# This file is distributed under the same license as the tdv package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: sdtui 0.1.0\n"
|
"Project-Id-Version: tdv 0.1.0\n"
|
||||||
"Report-Msgid-Bugs-To: https://github.com/pjanouch/sdtui/issues\n"
|
"Report-Msgid-Bugs-To: https://git.janouch.name/p/tdv/issues\n"
|
||||||
"POT-Creation-Date: 2016-09-28 16:12+0200\n"
|
"POT-Creation-Date: 2023-06-11 17:47+0200\n"
|
||||||
"PO-Revision-Date: 2016-09-28 16:15+0200\n"
|
"PO-Revision-Date: 2023-06-11 17:53+0200\n"
|
||||||
"Last-Translator: Přemysl Eric Janouch <p@janouch.name>\n"
|
"Last-Translator: Přemysl Eric Janouch <p@janouch.name>\n"
|
||||||
"Language-Team: Czech <translation-team-cs@lists.sourceforge.net>\n"
|
"Language-Team: Czech <translation-team-cs@lists.sourceforge.net>\n"
|
||||||
"Language: cs\n"
|
"Language: cs\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"X-Generator: Poedit 1.8.9\n"
|
"X-Generator: Poedit 3.3.1\n"
|
||||||
|
|
||||||
#: ../src/sdtui.c:573
|
#: ../src/tdv-tui.c:572
|
||||||
msgid "Cannot load configuration"
|
msgid "Cannot load configuration"
|
||||||
msgstr "Nemohu načíst konfiguraci"
|
msgstr "Nemohu načíst konfiguraci"
|
||||||
|
|
||||||
#: ../src/sdtui.c:1997
|
#: ../src/tdv.c:77
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr "Chyba"
|
msgstr "Chyba"
|
||||||
|
|
||||||
#: ../src/sdtui.c:606
|
#: ../src/tdv-tui.c:592
|
||||||
msgid "Error loading dictionary"
|
msgid "Error loading dictionary"
|
||||||
msgstr "Chyba při načítání slovníku"
|
msgstr "Chyba při načítání slovníku"
|
||||||
|
|
||||||
#: ../src/sdtui.c:612
|
#: ../src/tdv.c:60
|
||||||
msgid ""
|
msgid "Launch the GUI even when run from a terminal"
|
||||||
"No dictionaries found either in the configuration or on the command line"
|
msgstr "Spustit GUI i při běhu z terminálu"
|
||||||
|
|
||||||
|
#: ../src/tdv-gui.c:467 ../src/tdv-tui.c:597
|
||||||
|
msgid "No dictionaries found either in the configuration or on the command line"
|
||||||
msgstr "V konfiguraci ani na příkazové řádce nebyly nalezeny žádné slovníky"
|
msgstr "V konfiguraci ani na příkazové řádce nebyly nalezeny žádné slovníky"
|
||||||
|
|
||||||
#: ../src/sdtui.c:1976
|
#: ../src/tdv-gui.c:367
|
||||||
|
msgid "Open dictionary"
|
||||||
|
msgstr "Otevřít slovník"
|
||||||
|
|
||||||
|
#: ../src/tdv.c:55
|
||||||
msgid "Output version information and exit"
|
msgid "Output version information and exit"
|
||||||
msgstr "Vypíše informace o verzi a ukončí se"
|
msgstr "Vypíše informace o verzi a ukončí se"
|
||||||
|
|
||||||
#: ../src/sdtui.c:548
|
#: ../src/tdv-tui.c:555
|
||||||
msgid "Search"
|
msgid "Search"
|
||||||
msgstr "Hledat"
|
msgstr "Hledat"
|
||||||
|
|
||||||
#: ../src/sdtui.c:966
|
#: ../src/tdv-tui.c:1068
|
||||||
msgid "Terminal UI for StarDict dictionaries"
|
msgid "Terminal UI for StarDict dictionaries"
|
||||||
msgstr "Terminálové UI pro stardictové slovníky"
|
msgstr "Terminálové UI pro stardictové slovníky"
|
||||||
|
|
||||||
#: ../src/sdtui.c:969
|
#: ../src/tdv-tui.c:1071
|
||||||
msgid "Type to search"
|
msgid "Type to search"
|
||||||
msgstr "Zadejte vyhledávaný výraz"
|
msgstr "Zadejte vyhledávaný výraz"
|
||||||
|
|
||||||
#: ../src/sdtui.c:1981
|
#: ../src/tdv.c:39
|
||||||
msgid "Warning"
|
msgid "Warning"
|
||||||
msgstr "Varování"
|
msgstr "Varování"
|
||||||
|
|
||||||
#: ../src/sdtui.c:1989
|
#: ../src/tdv-tui.c:2071
|
||||||
msgid "[dictionary.ifo...] - StarDict terminal UI"
|
#, c-format
|
||||||
msgstr "[slovník.ifo...] - terminálové UI pro StarDict"
|
msgid "X11 connection failed (error code %d)"
|
||||||
|
msgstr "Spojení s X11 selhalo (chybový kód %d)"
|
||||||
|
|
||||||
#: ../src/stardict.c:835
|
#: ../src/tdv-tui.c:2217
|
||||||
|
#, c-format
|
||||||
|
msgid "X11 request error (%d, major %d, minor %d)"
|
||||||
|
msgstr "Chyba X11 požadavku (%d, major %d, minor %d)"
|
||||||
|
|
||||||
|
#: ../src/tdv.c:67
|
||||||
|
msgid "[dictionary.ifo...] - Translation dictionary viewer"
|
||||||
|
msgstr "[slovník.ifo...] - Prohlížeč překladových slovníků"
|
||||||
|
|
||||||
|
#: ../src/tdv-gui.c:369
|
||||||
|
msgid "_Cancel"
|
||||||
|
msgstr "_Storno"
|
||||||
|
|
||||||
|
#: ../src/tdv-gui.c:509
|
||||||
|
msgid "_Follow selection"
|
||||||
|
msgstr "_Sledovat výběr"
|
||||||
|
|
||||||
|
#: ../src/tdv-gui.c:370
|
||||||
|
msgid "_Open"
|
||||||
|
msgstr "_Otevřít"
|
||||||
|
|
||||||
|
#: ../src/tdv-gui.c:504
|
||||||
|
msgid "_Open..."
|
||||||
|
msgstr "_Otevřít..."
|
||||||
|
|
||||||
|
#: ../src/stardict.c:850
|
||||||
msgid "cannot find .dict file"
|
msgid "cannot find .dict file"
|
||||||
msgstr "nemohu najít .dict soubor"
|
msgstr "nemohu najít .dict soubor"
|
||||||
|
|
||||||
#: ../src/stardict.c:812
|
#: ../src/stardict.c:827
|
||||||
msgid "cannot find .idx file"
|
msgid "cannot find .idx file"
|
||||||
msgstr "nemohu najít .idx soubor"
|
msgstr "nemohu najít .idx soubor"
|
||||||
|
|
||||||
#: ../src/sdtui.c:283
|
#: ../src/tdv-tui.c:258
|
||||||
msgid "error in entry"
|
msgid "error in entry"
|
||||||
msgstr "chyba v záznamu"
|
msgstr "chyba v záznamu"
|
||||||
|
|
||||||
#: ../src/sdtui.c:1981
|
#: ../src/tdv.c:39
|
||||||
msgid "failed to set the locale"
|
msgid "failed to set the locale"
|
||||||
msgstr "selhalo nastavení locale"
|
msgstr "selhalo nastavení locale"
|
||||||
|
|
||||||
#: ../src/stardict.c:308
|
#: ../src/stardict.c:328
|
||||||
msgid "index file size not specified"
|
msgid "index file size not specified"
|
||||||
msgstr "nebyla určena velikost rejstříku"
|
msgstr "nebyla určena velikost rejstříku"
|
||||||
|
|
||||||
#: ../src/stardict.c:1130 ../src/stardict.c:1155
|
#: ../src/stardict.c:1157 ../src/stardict.c:1182
|
||||||
msgid "invalid data entry"
|
msgid "invalid data entry"
|
||||||
msgstr "neplatná datová položka"
|
msgstr "neplatná datová položka"
|
||||||
|
|
||||||
#: ../src/stardict.c:259
|
#: ../src/stardict.c:281
|
||||||
msgid "invalid encoding, must be valid UTF-8"
|
msgid "invalid encoding, must be valid UTF-8"
|
||||||
msgstr "neplatné kódování, musí být validní UTF-8"
|
msgstr "neplatné kódování, musí být validní UTF-8"
|
||||||
|
|
||||||
#: ../src/stardict.c:91
|
#: ../src/stardict.c:89
|
||||||
msgid "invalid header format"
|
msgid "invalid header format"
|
||||||
msgstr "neplatný formát hlavičky"
|
msgstr "neplatný formát hlavičky"
|
||||||
|
|
||||||
#: ../src/stardict.c:317
|
#: ../src/stardict.c:337
|
||||||
msgid "invalid index offset bits"
|
msgid "invalid index offset bits"
|
||||||
msgstr "neplatný počet bitů pro offset v rejstříku"
|
msgstr "neplatný počet bitů pro offset v rejstříku"
|
||||||
|
|
||||||
#: ../src/stardict.c:276
|
#: ../src/stardict.c:298
|
||||||
msgid "invalid integer"
|
msgid "invalid integer"
|
||||||
msgstr "neplatné číslo"
|
msgstr "neplatné číslo"
|
||||||
|
|
||||||
#: ../src/stardict.c:238
|
#: ../src/stardict.c:260
|
||||||
msgid "invalid version"
|
msgid "invalid version"
|
||||||
msgstr "neplatná verze"
|
msgstr "neplatná verze"
|
||||||
|
|
||||||
#: ../src/stardict.c:296
|
#: ../src/stardict.c:316
|
||||||
msgid "no book name specified"
|
msgid "no book name specified"
|
||||||
msgstr "nebyl určen název knihy"
|
msgstr "nebyl určen název knihy"
|
||||||
|
|
||||||
#: ../src/sdtui.c:302
|
#: ../src/stardict-view.c:96 ../src/tdv-tui.c:340
|
||||||
msgid "no usable field found"
|
msgid "no usable field found"
|
||||||
msgstr "nenalezeno žádné použitelné pole"
|
msgstr "nenalezeno žádné použitelné pole"
|
||||||
|
|
||||||
#: ../src/stardict.c:286
|
#: ../src/stardict.c:308
|
||||||
msgid "option format error"
|
msgid "option format error"
|
||||||
msgstr "chyba v zápisu volby"
|
msgstr "chyba v zápisu volby"
|
||||||
|
|
||||||
#: ../src/sdtui.c:1997
|
#: ../src/tdv.c:77
|
||||||
msgid "option parsing failed"
|
msgid "option parsing failed"
|
||||||
msgstr "zpracování přepínačů selhalo"
|
msgstr "zpracování přepínačů selhalo"
|
||||||
|
|
||||||
#: ../src/stardict.c:252
|
#: ../src/stardict.c:274
|
||||||
msgid "unknown key, ignoring"
|
msgid "unknown key, ignoring"
|
||||||
msgstr "neznámý klíč, ignoruji"
|
msgstr "neznámý klíč, ignoruji"
|
||||||
|
|
||||||
#: ../src/stardict.c:227
|
#: ../src/stardict.c:249
|
||||||
msgid "version not specified"
|
msgid "version not specified"
|
||||||
msgstr "nebyla určena verze"
|
msgstr "nebyla určena verze"
|
||||||
|
|
||||||
#: ../src/stardict.c:302
|
#: ../src/stardict.c:322
|
||||||
msgid "word count not specified"
|
msgid "word count not specified"
|
||||||
msgstr "nebyl určen počet slov"
|
msgstr "nebyl určen počet slov"
|
||||||
|
|
||||||
|
#~ msgid "FILE..."
|
||||||
|
#~ msgstr "SOUBOR..."
|
||||||
|
|||||||
@@ -1,47 +1,21 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh -e
|
||||||
# This shell script generates the translation template.
|
# This shell script generates the translation template.
|
||||||
#
|
#
|
||||||
# The reason for this not being inside CMakeLists.txt
|
# The reason for this not being inside CMakeLists.txt is that the translator
|
||||||
# is that the translator should not need to run the whole
|
# should not need to run the whole configuration process just to get this file.
|
||||||
# configuration process just to get this single stupid file.
|
dir=$(dirname $0)
|
||||||
|
|
||||||
# Get the directory this script resides in so that the user
|
export LC_ALL=C
|
||||||
# doesn't have to run the script from there
|
|
||||||
DIR=$(dirname $0)
|
|
||||||
|
|
||||||
# Collect source files
|
re='^[ \t]*project *( *\([^ \t)]\{1,\}\) \{1,\}VERSION \{1,\}\([^ \t)]\{1,\}\).*'
|
||||||
SOURCES=$(echo $DIR/../src/*.c)
|
package=$(sed -n "s/$re/\\1/p" "$dir/../CMakeLists.txt")
|
||||||
|
version=$(sed -n "s/$re/\\2/p" "$dir/../CMakeLists.txt")
|
||||||
# Get the package name from CMakeLists.txt
|
if [ -z "$package" -o -z "$version" ]; then
|
||||||
PACKAGE=$(sed -n '/^[ \t]*project[ \t]*([ \t]*\([^ \t)]\{1,\}\).*).*/{s//\1/p;q}' \
|
|
||||||
$DIR/../CMakeLists.txt)
|
|
||||||
|
|
||||||
# Get the package version from CMakeLists.txt
|
|
||||||
EXP_BEG='/^[ \t]*set[ \t]*([ \t]*project_VERSION_'
|
|
||||||
EXP_END='[ \t]\{1,\}"\{0,1\}\([^)"]\{1,\}\)"\{0,1\}).*/{s//\1/p;q}'
|
|
||||||
|
|
||||||
MAJOR=$(sed -n "${EXP_BEG}MAJOR${EXP_END}" $DIR/../CMakeLists.txt)
|
|
||||||
MINOR=$(sed -n "${EXP_BEG}MINOR${EXP_END}" $DIR/../CMakeLists.txt)
|
|
||||||
PATCH=$(sed -n "${EXP_BEG}PATCH${EXP_END}" $DIR/../CMakeLists.txt)
|
|
||||||
|
|
||||||
if [ "$MAJOR" != "" ]; then
|
|
||||||
VERSION=$MAJOR
|
|
||||||
if [ "$MINOR" != "" ]; then
|
|
||||||
VERSION=$VERSION.$MINOR
|
|
||||||
if [ "$PATCH" != "" ]; then
|
|
||||||
VERSION=$VERSION.$PATCH
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "$PACKAGE" -o -z "$VERSION" ]; then
|
|
||||||
echo "Failed to get information from CMakeLists.txt"
|
echo "Failed to get information from CMakeLists.txt"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Finally make the template
|
xgettext -LC -k_ -kN_ "$dir"/../src/*.c -o "$dir/$package.pot" \
|
||||||
xgettext -LC -k_ -kN_ $SOURCES -o "$DIR/$PACKAGE.pot" \
|
--package-name="$package" --package-version="$version" \
|
||||||
--package-name="$PACKAGE" --package-version="$VERSION" \
|
|
||||||
--copyright-holder="Přemysl Eric Janouch" \
|
--copyright-holder="Přemysl Eric Janouch" \
|
||||||
--msgid-bugs-address="https://github.com/pjanouch/$PACKAGE/issues"
|
--msgid-bugs-address="https://git.janouch.name/p/$package/issues"
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
# SOME DESCRIPTIVE TITLE.
|
# SOME DESCRIPTIVE TITLE.
|
||||||
# Copyright (C) YEAR Přemysl Eric Janouch
|
# Copyright (C) YEAR Přemysl Eric Janouch
|
||||||
# This file is distributed under the same license as the sdtui package.
|
# This file is distributed under the same license as the tdv package.
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
#
|
#
|
||||||
#, fuzzy
|
#, fuzzy
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: sdtui 0.1.0\n"
|
"Project-Id-Version: tdv 0.1.0\n"
|
||||||
"Report-Msgid-Bugs-To: https://github.com/pjanouch/sdtui/issues\n"
|
"Report-Msgid-Bugs-To: https://git.janouch.name/p/tdv/issues\n"
|
||||||
"POT-Creation-Date: 2016-09-28 16:12+0200\n"
|
"POT-Creation-Date: 2023-06-11 17:47+0200\n"
|
||||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
@@ -17,115 +17,149 @@ msgstr ""
|
|||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
#: ../src/sdtui.c:283
|
#: ../src/stardict-view.c:96 ../src/tdv-tui.c:340
|
||||||
msgid "error in entry"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src/sdtui.c:302
|
|
||||||
msgid "no usable field found"
|
msgid "no usable field found"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ../src/sdtui.c:548
|
#: ../src/stardict.c:89
|
||||||
msgid "Search"
|
msgid "invalid header format"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ../src/sdtui.c:573
|
#: ../src/stardict.c:249
|
||||||
msgid "Cannot load configuration"
|
msgid "version not specified"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ../src/sdtui.c:606
|
#: ../src/stardict.c:260
|
||||||
msgid "Error loading dictionary"
|
msgid "invalid version"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ../src/sdtui.c:612
|
#: ../src/stardict.c:274
|
||||||
|
msgid "unknown key, ignoring"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/stardict.c:281
|
||||||
|
msgid "invalid encoding, must be valid UTF-8"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/stardict.c:298
|
||||||
|
msgid "invalid integer"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/stardict.c:308
|
||||||
|
msgid "option format error"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/stardict.c:316
|
||||||
|
msgid "no book name specified"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/stardict.c:322
|
||||||
|
msgid "word count not specified"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/stardict.c:328
|
||||||
|
msgid "index file size not specified"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/stardict.c:337
|
||||||
|
msgid "invalid index offset bits"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/stardict.c:827
|
||||||
|
msgid "cannot find .idx file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/stardict.c:850
|
||||||
|
msgid "cannot find .dict file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/stardict.c:1157 ../src/stardict.c:1182
|
||||||
|
msgid "invalid data entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/tdv-gui.c:367
|
||||||
|
msgid "Open dictionary"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/tdv-gui.c:369
|
||||||
|
msgid "_Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/tdv-gui.c:370
|
||||||
|
msgid "_Open"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/tdv-gui.c:467 ../src/tdv-tui.c:597
|
||||||
msgid ""
|
msgid ""
|
||||||
"No dictionaries found either in the configuration or on the command line"
|
"No dictionaries found either in the configuration or on the command line"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ../src/sdtui.c:966
|
#: ../src/tdv-gui.c:504
|
||||||
|
msgid "_Open..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/tdv-gui.c:509
|
||||||
|
msgid "_Follow selection"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/tdv-tui.c:258
|
||||||
|
msgid "error in entry"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/tdv-tui.c:555
|
||||||
|
msgid "Search"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/tdv-tui.c:572
|
||||||
|
msgid "Cannot load configuration"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/tdv-tui.c:592
|
||||||
|
msgid "Error loading dictionary"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/tdv-tui.c:1068
|
||||||
msgid "Terminal UI for StarDict dictionaries"
|
msgid "Terminal UI for StarDict dictionaries"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ../src/sdtui.c:969
|
#: ../src/tdv-tui.c:1071
|
||||||
msgid "Type to search"
|
msgid "Type to search"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ../src/sdtui.c:1976
|
#: ../src/tdv-tui.c:2071
|
||||||
msgid "Output version information and exit"
|
#, c-format
|
||||||
|
msgid "X11 connection failed (error code %d)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ../src/sdtui.c:1981
|
#: ../src/tdv-tui.c:2217
|
||||||
|
#, c-format
|
||||||
|
msgid "X11 request error (%d, major %d, minor %d)"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/tdv.c:39
|
||||||
msgid "Warning"
|
msgid "Warning"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ../src/sdtui.c:1981
|
#: ../src/tdv.c:39
|
||||||
msgid "failed to set the locale"
|
msgid "failed to set the locale"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ../src/sdtui.c:1989
|
#: ../src/tdv.c:55
|
||||||
msgid "[dictionary.ifo...] - StarDict terminal UI"
|
msgid "Output version information and exit"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ../src/sdtui.c:1997
|
#: ../src/tdv.c:60
|
||||||
|
msgid "Launch the GUI even when run from a terminal"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/tdv.c:67
|
||||||
|
msgid "[dictionary.ifo...] - Translation dictionary viewer"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: ../src/tdv.c:77
|
||||||
msgid "Error"
|
msgid "Error"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ../src/sdtui.c:1997
|
#: ../src/tdv.c:77
|
||||||
msgid "option parsing failed"
|
msgid "option parsing failed"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
#: ../src/stardict.c:91
|
|
||||||
msgid "invalid header format"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src/stardict.c:227
|
|
||||||
msgid "version not specified"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src/stardict.c:238
|
|
||||||
msgid "invalid version"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src/stardict.c:252
|
|
||||||
msgid "unknown key, ignoring"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src/stardict.c:259
|
|
||||||
msgid "invalid encoding, must be valid UTF-8"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src/stardict.c:276
|
|
||||||
msgid "invalid integer"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src/stardict.c:286
|
|
||||||
msgid "option format error"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src/stardict.c:296
|
|
||||||
msgid "no book name specified"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src/stardict.c:302
|
|
||||||
msgid "word count not specified"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src/stardict.c:308
|
|
||||||
msgid "index file size not specified"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src/stardict.c:317
|
|
||||||
msgid "invalid index offset bits"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src/stardict.c:812
|
|
||||||
msgid "cannot find .idx file"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src/stardict.c:835
|
|
||||||
msgid "cannot find .dict file"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: ../src/stardict.c:1130 ../src/stardict.c:1155
|
|
||||||
msgid "invalid data entry"
|
|
||||||
msgstr ""
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* generator.c: dictionary generator
|
* generator.c: dictionary generator
|
||||||
*
|
*
|
||||||
* Copyright (c) 2013, Přemysl Eric Janouch <p@janouch.name>
|
* Copyright (c) 2013 - 2020, Přemysl Eric Janouch <p@janouch.name>
|
||||||
*
|
*
|
||||||
* Permission to use, copy, modify, and/or distribute this software for any
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted.
|
* purpose with or without fee is hereby granted.
|
||||||
@@ -170,6 +170,34 @@ generator_write_string (Generator *self,
|
|||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write a list of data fields back to a dictionary. The list has to be
|
||||||
|
/// acceptable for the generated dictionary's sametypesequence (or lack of).
|
||||||
|
gboolean
|
||||||
|
generator_write_fields (Generator *self, const GList *fields, GError **error)
|
||||||
|
{
|
||||||
|
gboolean sts = self->info->same_type_sequence != NULL;
|
||||||
|
while (fields)
|
||||||
|
{
|
||||||
|
StardictEntryField *field = fields->data;
|
||||||
|
if (!sts && !generator_write_type (self, field->type, error))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
gboolean mark_end = !sts || fields->next != NULL;
|
||||||
|
if (g_ascii_islower (field->type))
|
||||||
|
{
|
||||||
|
if (!generator_write_string (self,
|
||||||
|
field->data, mark_end, error))
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
else if (!generator_write_raw (self,
|
||||||
|
field->data, field->data_size, mark_end, error))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
fields = fields->next;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
/// Finishes the current entry and writes it into the index.
|
/// Finishes the current entry and writes it into the index.
|
||||||
gboolean
|
gboolean
|
||||||
generator_finish_entry (Generator *self, const gchar *word, GError **error)
|
generator_finish_entry (Generator *self, const gchar *word, GError **error)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
* Nothing fancy. Just something moved out off the `stardict' test to be
|
* Nothing fancy. Just something moved out off the `stardict' test to be
|
||||||
* conveniently reused by the included tools.
|
* conveniently reused by the included tools.
|
||||||
*
|
*
|
||||||
* Copyright (c) 2013, Přemysl Eric Janouch <p@janouch.name>
|
* Copyright (c) 2013 - 2020, Přemysl Eric Janouch <p@janouch.name>
|
||||||
*
|
*
|
||||||
* Permission to use, copy, modify, and/or distribute this software for any
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted.
|
* purpose with or without fee is hereby granted.
|
||||||
@@ -42,12 +42,15 @@ Generator *generator_new (const gchar *base, GError **error);
|
|||||||
gboolean generator_finish (Generator *self, GError **error);
|
gboolean generator_finish (Generator *self, GError **error);
|
||||||
void generator_free (Generator *self);
|
void generator_free (Generator *self);
|
||||||
|
|
||||||
void generator_begin_entry (Generator *self);
|
|
||||||
gboolean generator_write_type (Generator *self, gchar type, GError **error);
|
gboolean generator_write_type (Generator *self, gchar type, GError **error);
|
||||||
gboolean generator_write_raw (Generator *self,
|
gboolean generator_write_raw (Generator *self,
|
||||||
gpointer data, gsize data_size, gboolean mark_end, GError **error);
|
gpointer data, gsize data_size, gboolean mark_end, GError **error);
|
||||||
gboolean generator_write_string (Generator *self,
|
gboolean generator_write_string (Generator *self,
|
||||||
const gchar *s, gboolean mark_end, GError **error);
|
const gchar *s, gboolean mark_end, GError **error);
|
||||||
|
|
||||||
|
void generator_begin_entry (Generator *self);
|
||||||
|
gboolean generator_write_fields (Generator *self,
|
||||||
|
const GList *fields, GError **error);
|
||||||
gboolean generator_finish_entry (Generator *self,
|
gboolean generator_finish_entry (Generator *self,
|
||||||
const gchar *word, GError **error);
|
const gchar *word, GError **error);
|
||||||
|
|
||||||
|
|||||||
174
src/query-tool.c
174
src/query-tool.c
@@ -1,174 +0,0 @@
|
|||||||
/*
|
|
||||||
* A tool to query multiple dictionaries for the specified word
|
|
||||||
*
|
|
||||||
* Intended for use in IRC bots and similar silly things---words go in, one
|
|
||||||
* on a line, and entries come out, one dictionary at a time, finalised with
|
|
||||||
* an empty line. Newlines are escaped with `\n', backslashes with `\\'.
|
|
||||||
*
|
|
||||||
* So far only the `m' field is supported. Feel free to extend the program
|
|
||||||
* according to your needs, it's not very complicated.
|
|
||||||
*
|
|
||||||
* Copyright (c) 2013, Přemysl Eric Janouch <p@janouch.name>
|
|
||||||
*
|
|
||||||
* Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
* purpose with or without fee is hereby granted.
|
|
||||||
*
|
|
||||||
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
||||||
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
||||||
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
|
||||||
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
||||||
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
|
||||||
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
|
||||||
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <errno.h>
|
|
||||||
|
|
||||||
#include <glib.h>
|
|
||||||
#include <gio/gio.h>
|
|
||||||
|
|
||||||
#include "stardict.h"
|
|
||||||
#include "stardict-private.h"
|
|
||||||
#include "generator.h"
|
|
||||||
|
|
||||||
static guint
|
|
||||||
count_equal_chars (const gchar *a, const gchar *b)
|
|
||||||
{
|
|
||||||
guint count = 0;
|
|
||||||
while (*a && *b)
|
|
||||||
if (*a++ == *b++)
|
|
||||||
count++;
|
|
||||||
return count;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
|
||||||
do_dictionary (StardictDict *dict, const gchar *word)
|
|
||||||
{
|
|
||||||
gboolean found;
|
|
||||||
StardictIterator *iter = stardict_dict_search (dict, word, &found);
|
|
||||||
if (!found)
|
|
||||||
goto out;
|
|
||||||
|
|
||||||
// Default Stardict ordering is ASCII case-insensitive.
|
|
||||||
// Try to find a better matching entry based on letter case:
|
|
||||||
|
|
||||||
gint64 best_offset = stardict_iterator_get_offset (iter);
|
|
||||||
guint best_score = count_equal_chars
|
|
||||||
(stardict_iterator_get_word (iter), word);
|
|
||||||
|
|
||||||
while (TRUE)
|
|
||||||
{
|
|
||||||
stardict_iterator_next (iter);
|
|
||||||
if (!stardict_iterator_is_valid (iter))
|
|
||||||
break;
|
|
||||||
|
|
||||||
const gchar *iter_word = stardict_iterator_get_word (iter);
|
|
||||||
if (g_ascii_strcasecmp (iter_word, word))
|
|
||||||
break;
|
|
||||||
|
|
||||||
guint score = count_equal_chars (iter_word, word);
|
|
||||||
if (score > best_score)
|
|
||||||
{
|
|
||||||
best_offset = stardict_iterator_get_offset (iter);
|
|
||||||
best_score = score;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stardict_iterator_set_offset (iter, best_offset, FALSE);
|
|
||||||
|
|
||||||
StardictEntry *entry = stardict_iterator_get_entry (iter);
|
|
||||||
StardictInfo *info = stardict_dict_get_info (dict);
|
|
||||||
const GList *list = stardict_entry_get_fields (entry);
|
|
||||||
for (; list; list = list->next)
|
|
||||||
{
|
|
||||||
StardictEntryField *field = list->data;
|
|
||||||
if (field->type == STARDICT_FIELD_MEANING)
|
|
||||||
{
|
|
||||||
const gchar *desc = field->data;
|
|
||||||
printf ("%s:", info->book_name);
|
|
||||||
for (; *desc; desc++)
|
|
||||||
{
|
|
||||||
if (*desc == '\\')
|
|
||||||
printf ("\\\\");
|
|
||||||
else if (*desc == '\n')
|
|
||||||
printf ("\\n");
|
|
||||||
else
|
|
||||||
putchar (*desc);
|
|
||||||
}
|
|
||||||
putchar ('\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
g_object_unref (entry);
|
|
||||||
out:
|
|
||||||
g_object_unref (iter);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main (int argc, char *argv[])
|
|
||||||
{
|
|
||||||
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
|
||||||
if (glib_check_version (2, 36, 0))
|
|
||||||
g_type_init ();
|
|
||||||
G_GNUC_END_IGNORE_DEPRECATIONS
|
|
||||||
|
|
||||||
GError *error = NULL;
|
|
||||||
GOptionContext *ctx = g_option_context_new
|
|
||||||
("DICTIONARY.ifo... - query multiple dictionaries");
|
|
||||||
if (!g_option_context_parse (ctx, &argc, &argv, &error))
|
|
||||||
{
|
|
||||||
g_printerr ("Error: option parsing failed: %s\n", error->message);
|
|
||||||
exit (EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
g_option_context_free (ctx);
|
|
||||||
|
|
||||||
if (argc < 2)
|
|
||||||
{
|
|
||||||
g_printerr ("Error: no dictionaries given\n");
|
|
||||||
exit (EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
guint n_dicts = argc - 1;
|
|
||||||
StardictDict **dicts = g_alloca (sizeof *dicts * n_dicts);
|
|
||||||
|
|
||||||
guint i;
|
|
||||||
for (i = 1; i <= n_dicts; i++)
|
|
||||||
{
|
|
||||||
dicts[i - 1] = stardict_dict_new (argv[i], &error);
|
|
||||||
if (error)
|
|
||||||
{
|
|
||||||
g_printerr ("Error: opening dictionary `%s' failed: %s\n",
|
|
||||||
argv[i], error->message);
|
|
||||||
exit (EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
while (TRUE)
|
|
||||||
{
|
|
||||||
GString *s = g_string_new (NULL);
|
|
||||||
|
|
||||||
gint c;
|
|
||||||
while ((c = getchar ()) != EOF && c != '\n')
|
|
||||||
if (c != '\r')
|
|
||||||
g_string_append_c (s, c);
|
|
||||||
|
|
||||||
if (s->len)
|
|
||||||
for (i = 0; i < n_dicts; i++)
|
|
||||||
do_dictionary (dicts[i], s->str);
|
|
||||||
|
|
||||||
printf ("\n");
|
|
||||||
fflush (NULL);
|
|
||||||
g_string_free (s, TRUE);
|
|
||||||
|
|
||||||
if (c == EOF)
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < n_dicts; i++)
|
|
||||||
g_object_unref (dicts[i]);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
@@ -79,4 +79,6 @@ extern const struct stardict_ifo_key _stardict_ifo_keys[];
|
|||||||
/// Denotes the length of _stardict_ifo_keys.
|
/// Denotes the length of _stardict_ifo_keys.
|
||||||
extern gsize _stardict_ifo_keys_length;
|
extern gsize _stardict_ifo_keys_length;
|
||||||
|
|
||||||
|
void stardict_info_copy (StardictInfo *dest, const StardictInfo *src);
|
||||||
|
|
||||||
#endif // ! STARDICTPRIVATE_H
|
#endif // ! STARDICTPRIVATE_H
|
||||||
|
|||||||
1221
src/stardict-view.c
Normal file
1221
src/stardict-view.c
Normal file
File diff suppressed because it is too large
Load Diff
36
src/stardict-view.h
Normal file
36
src/stardict-view.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* StarDict GTK+ UI - dictionary view component
|
||||||
|
*
|
||||||
|
* Copyright (c) 2021, Přemysl Eric Janouch <p@janouch.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef STARDICT_VIEW_H
|
||||||
|
#define STARDICT_VIEW_H
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
|
||||||
|
#include "stardict.h"
|
||||||
|
|
||||||
|
#define STARDICT_TYPE_VIEW (stardict_view_get_type ())
|
||||||
|
G_DECLARE_FINAL_TYPE (StardictView, stardict_view, STARDICT, VIEW, GtkWidget)
|
||||||
|
|
||||||
|
GtkWidget *stardict_view_new (void);
|
||||||
|
void stardict_view_set_position (StardictView *view,
|
||||||
|
StardictDict *dict, guint position);
|
||||||
|
void stardict_view_set_matched (StardictView *view, const gchar *matched);
|
||||||
|
void stardict_view_scroll (StardictView *view,
|
||||||
|
GtkScrollStep step, gdouble amount);
|
||||||
|
|
||||||
|
#endif // ! STARDICT_VIEW_H
|
||||||
119
src/stardict.c
119
src/stardict.c
@@ -209,6 +209,30 @@ const struct stardict_ifo_key _stardict_ifo_keys[] =
|
|||||||
|
|
||||||
gsize _stardict_ifo_keys_length = G_N_ELEMENTS (_stardict_ifo_keys);
|
gsize _stardict_ifo_keys_length = G_N_ELEMENTS (_stardict_ifo_keys);
|
||||||
|
|
||||||
|
/// Copy the contents of one StardictInfo object into another. Ignores path.
|
||||||
|
void
|
||||||
|
stardict_info_copy (StardictInfo *dest, const StardictInfo *src)
|
||||||
|
{
|
||||||
|
dest->version = src->version;
|
||||||
|
|
||||||
|
guint i;
|
||||||
|
for (i = 0; i < _stardict_ifo_keys_length; i++)
|
||||||
|
{
|
||||||
|
const struct stardict_ifo_key *key = &_stardict_ifo_keys[i];
|
||||||
|
if (key->type == IFO_STRING)
|
||||||
|
{
|
||||||
|
gchar **p = &G_STRUCT_MEMBER (gchar *, dest, key->offset);
|
||||||
|
gchar *q = G_STRUCT_MEMBER (gchar *, src, key->offset);
|
||||||
|
|
||||||
|
g_free (*p);
|
||||||
|
*p = q ? g_strdup (q) : NULL;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
G_STRUCT_MEMBER (gulong, dest, key->offset) =
|
||||||
|
G_STRUCT_MEMBER (gulong, src, key->offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
load_ifo (StardictInfo *sti, const gchar *path, GError **error)
|
load_ifo (StardictInfo *sti, const gchar *path, GError **error)
|
||||||
{
|
{
|
||||||
@@ -285,26 +309,24 @@ load_ifo (StardictInfo *sti, const gchar *path, GError **error)
|
|||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret_val = TRUE;
|
// FIXME check for zeros, don't assume that 0 means "not set"
|
||||||
|
|
||||||
// FIXME check for zeros, don't assume that 0 means for "not set"
|
|
||||||
if (!sti->book_name || !*sti->book_name)
|
if (!sti->book_name || !*sti->book_name)
|
||||||
{
|
{
|
||||||
g_set_error (error, STARDICT_ERROR, STARDICT_ERROR_INVALID_DATA,
|
g_set_error (error, STARDICT_ERROR, STARDICT_ERROR_INVALID_DATA,
|
||||||
"%s: %s", path, _("no book name specified"));
|
"%s: %s", path, _("no book name specified"));
|
||||||
ret_val = FALSE;
|
goto error;
|
||||||
}
|
}
|
||||||
if (!sti->word_count)
|
if (!sti->word_count)
|
||||||
{
|
{
|
||||||
g_set_error (error, STARDICT_ERROR, STARDICT_ERROR_INVALID_DATA,
|
g_set_error (error, STARDICT_ERROR, STARDICT_ERROR_INVALID_DATA,
|
||||||
"%s: %s", path, _("word count not specified"));
|
"%s: %s", path, _("word count not specified"));
|
||||||
ret_val = FALSE;
|
goto error;
|
||||||
}
|
}
|
||||||
if (!sti->idx_filesize)
|
if (!sti->idx_filesize)
|
||||||
{
|
{
|
||||||
g_set_error (error, STARDICT_ERROR, STARDICT_ERROR_INVALID_DATA,
|
g_set_error (error, STARDICT_ERROR, STARDICT_ERROR_INVALID_DATA,
|
||||||
"%s: %s", path, _("index file size not specified"));
|
"%s: %s", path, _("index file size not specified"));
|
||||||
ret_val = FALSE;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!sti->idx_offset_bits)
|
if (!sti->idx_offset_bits)
|
||||||
@@ -314,9 +336,11 @@ load_ifo (StardictInfo *sti, const gchar *path, GError **error)
|
|||||||
g_set_error (error, STARDICT_ERROR, STARDICT_ERROR_INVALID_DATA,
|
g_set_error (error, STARDICT_ERROR, STARDICT_ERROR_INVALID_DATA,
|
||||||
"%s: %s: %lu", path, _("invalid index offset bits"),
|
"%s: %s: %lu", path, _("invalid index offset bits"),
|
||||||
sti->idx_offset_bits);
|
sti->idx_offset_bits);
|
||||||
ret_val = FALSE;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ret_val = TRUE;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
if (!ret_val)
|
if (!ret_val)
|
||||||
{
|
{
|
||||||
@@ -333,6 +357,20 @@ error:
|
|||||||
return ret_val;
|
return ret_val;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Read an .ifo file.
|
||||||
|
/// @return StardictInfo *. Deallocate with stardict_info_free();
|
||||||
|
StardictInfo *
|
||||||
|
stardict_info_new (const gchar *filename, GError **error)
|
||||||
|
{
|
||||||
|
StardictInfo *ifo = g_new (StardictInfo, 1);
|
||||||
|
if (!load_ifo (ifo, filename, error))
|
||||||
|
{
|
||||||
|
g_free (ifo);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return ifo;
|
||||||
|
}
|
||||||
|
|
||||||
/// List all dictionary files located in a path.
|
/// List all dictionary files located in a path.
|
||||||
/// @return GList<StardictInfo *>. Deallocate the list with:
|
/// @return GList<StardictInfo *>. Deallocate the list with:
|
||||||
/// @code
|
/// @code
|
||||||
@@ -353,12 +391,10 @@ stardict_list_dictionaries (const gchar *path)
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
gchar *filename = g_build_filename (path, name, NULL);
|
gchar *filename = g_build_filename (path, name, NULL);
|
||||||
StardictInfo *ifo = g_new (StardictInfo, 1);
|
StardictInfo *ifo = stardict_info_new (filename, NULL);
|
||||||
if (load_ifo (ifo, filename, NULL))
|
|
||||||
dicts = g_list_append (dicts, ifo);
|
|
||||||
else
|
|
||||||
g_free (ifo);
|
|
||||||
g_free (filename);
|
g_free (filename);
|
||||||
|
if (ifo)
|
||||||
|
dicts = g_list_append (dicts, ifo);
|
||||||
}
|
}
|
||||||
g_dir_close (dir);
|
g_dir_close (dir);
|
||||||
g_pattern_spec_free (ps);
|
g_pattern_spec_free (ps);
|
||||||
@@ -867,11 +903,12 @@ stardict_dict_cmp_synonym (StardictDict *sd, const gchar *word, gint i)
|
|||||||
g_array_index (synonyms, StardictSynonymEntry, i).word);
|
g_array_index (synonyms, StardictSynonymEntry, i).word);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return words for which the argument is a synonym of or NULL
|
/// Return words of which the argument is a synonym or NULL
|
||||||
/// if there are no such words.
|
/// if there are no such words.
|
||||||
gchar **
|
gchar **
|
||||||
stardict_dict_get_synonyms (StardictDict *sd, const gchar *word)
|
stardict_dict_get_synonyms (StardictDict *sd, const gchar *word)
|
||||||
{
|
{
|
||||||
|
GArray *collated = sd->priv->collated_synonyms;
|
||||||
GArray *synonyms = sd->priv->synonyms;
|
GArray *synonyms = sd->priv->synonyms;
|
||||||
GArray *index = sd->priv->index;
|
GArray *index = sd->priv->index;
|
||||||
|
|
||||||
@@ -879,26 +916,32 @@ stardict_dict_get_synonyms (StardictDict *sd, const gchar *word)
|
|||||||
stardict_dict_cmp_synonym (sd, word, imid))
|
stardict_dict_cmp_synonym (sd, word, imid))
|
||||||
|
|
||||||
// Back off to the first matching entry
|
// Back off to the first matching entry
|
||||||
while (imid > 0 && !stardict_dict_cmp_synonym (sd, word, --imid))
|
while (imid > 0 && !stardict_dict_cmp_synonym (sd, word, imid - 1))
|
||||||
;
|
imid--;
|
||||||
|
|
||||||
GPtrArray *array = g_ptr_array_new ();
|
GPtrArray *array = g_ptr_array_new ();
|
||||||
|
|
||||||
// And add all matching entries from that position on to the array
|
// And add all matching entries from that position on to the array
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
guint32 i = g_array_index
|
guint32 i = sd->priv->collator
|
||||||
(synonyms, StardictSynonymEntry, ++imid).original_word;
|
? g_array_index (synonyms, StardictSynonymEntry,
|
||||||
|
g_array_index (collated, guint32, imid)).original_word
|
||||||
|
: g_array_index (synonyms, StardictSynonymEntry,
|
||||||
|
imid).original_word;
|
||||||
|
|
||||||
// When we use a collator this will point to the original entry,
|
// When we use a collator this will point to the original entry,
|
||||||
// otherwise it points to itself and this changes nothing
|
// otherwise it points to itself and this changes nothing
|
||||||
i = g_array_index
|
i = g_array_index (sd->priv->index, StardictIndexEntry,
|
||||||
(sd->priv->index, StardictIndexEntry, i).reverse_index;
|
i).reverse_index;
|
||||||
g_ptr_array_add (array, g_strdup (g_array_index
|
|
||||||
(index, StardictIndexEntry, i).name));
|
|
||||||
}
|
|
||||||
while ((guint) imid < synonyms->len - 1 && !stardict_strcmp (word,
|
|
||||||
g_array_index (synonyms, StardictSynonymEntry, imid + 1).word));
|
|
||||||
|
|
||||||
|
g_ptr_array_add (array,
|
||||||
|
g_strdup (g_array_index (index, StardictIndexEntry, i).name));
|
||||||
|
}
|
||||||
|
while ((guint) ++imid < synonyms->len
|
||||||
|
&& !stardict_dict_cmp_synonym (sd, word, imid));
|
||||||
|
|
||||||
|
g_ptr_array_add (array, NULL);
|
||||||
return (gchar **) g_ptr_array_free (array, FALSE);
|
return (gchar **) g_ptr_array_free (array, FALSE);
|
||||||
|
|
||||||
BINARY_SEARCH_END
|
BINARY_SEARCH_END
|
||||||
@@ -915,6 +958,15 @@ stardict_dict_cmp_index (StardictDict *sd, const gchar *word, gint i)
|
|||||||
return g_ascii_strcasecmp (word, target);
|
return g_ascii_strcasecmp (word, target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static size_t
|
||||||
|
prefix (StardictDict *sd, const gchar *word, gint i)
|
||||||
|
{
|
||||||
|
GArray *index = sd->priv->index;
|
||||||
|
return (guint) i >= index->len ? 0 :
|
||||||
|
stardict_longest_common_collation_prefix
|
||||||
|
(sd, word, g_array_index (index, StardictIndexEntry, i).name);
|
||||||
|
}
|
||||||
|
|
||||||
/// Search for a word. The search is ASCII-case-insensitive.
|
/// Search for a word. The search is ASCII-case-insensitive.
|
||||||
/// @param[in] word The word in utf-8 encoding
|
/// @param[in] word The word in utf-8 encoding
|
||||||
/// @param[out] success TRUE if found
|
/// @param[out] success TRUE if found
|
||||||
@@ -936,14 +988,11 @@ stardict_dict_search (StardictDict *sd, const gchar *word, gboolean *success)
|
|||||||
|
|
||||||
BINARY_SEARCH_END
|
BINARY_SEARCH_END
|
||||||
|
|
||||||
// Try to find a longer common prefix with a preceding entry
|
// Try to find a longer common prefix with a preceding entry.
|
||||||
#define PREFIX(i) stardict_longest_common_collation_prefix \
|
|
||||||
(sd, word, g_array_index (index, StardictIndexEntry, i).name)
|
|
||||||
|
|
||||||
// We need to take care not to step through the entire dictionary
|
// We need to take care not to step through the entire dictionary
|
||||||
// if not a single character matches, because it can be quite costly
|
// if not a single character matches, because it can be quite costly.
|
||||||
size_t probe, best = PREFIX (imin);
|
size_t probe, best = prefix (sd, word, imin);
|
||||||
while (best && imin > 0 && (probe = PREFIX (imin - 1)) >= best)
|
while (best && imin > 0 && (probe = prefix (sd, word, imin - 1)) >= best)
|
||||||
{
|
{
|
||||||
// TODO: take more care to not screw up exact matches,
|
// TODO: take more care to not screw up exact matches,
|
||||||
// use several "best"s according to quality
|
// use several "best"s according to quality
|
||||||
@@ -956,8 +1005,6 @@ stardict_dict_search (StardictDict *sd, const gchar *word, gboolean *success)
|
|||||||
imin--;
|
imin--;
|
||||||
}
|
}
|
||||||
|
|
||||||
#undef PREFIX
|
|
||||||
|
|
||||||
if (success) *success = FALSE;
|
if (success) *success = FALSE;
|
||||||
return stardict_iterator_new (sd, imin);
|
return stardict_iterator_new (sd, imin);
|
||||||
}
|
}
|
||||||
@@ -979,6 +1026,10 @@ stardict_longest_common_collation_prefix (StardictDict *sd,
|
|||||||
u_strFromUTF8 (NULL, 0, &uc2_len, s2, -1, &error);
|
u_strFromUTF8 (NULL, 0, &uc2_len, s2, -1, &error);
|
||||||
error = U_ZERO_ERROR;
|
error = U_ZERO_ERROR;
|
||||||
|
|
||||||
|
// Prevent undefined behaviour with VLAs.
|
||||||
|
if (!uc1_len || !uc2_len)
|
||||||
|
return 0;
|
||||||
|
|
||||||
UChar uc1[uc1_len];
|
UChar uc1[uc1_len];
|
||||||
UChar uc2[uc2_len];
|
UChar uc2[uc2_len];
|
||||||
u_strFromUTF8 (uc1, uc1_len, NULL, s1, -1, &error);
|
u_strFromUTF8 (uc1, uc1_len, NULL, s1, -1, &error);
|
||||||
@@ -1319,7 +1370,7 @@ stardict_iterator_get_entry (StardictIterator *sdi)
|
|||||||
{
|
{
|
||||||
g_return_val_if_fail (STARDICT_IS_ITERATOR (sdi), NULL);
|
g_return_val_if_fail (STARDICT_IS_ITERATOR (sdi), NULL);
|
||||||
if (!stardict_iterator_is_valid (sdi))
|
if (!stardict_iterator_is_valid (sdi))
|
||||||
return FALSE;
|
return NULL;
|
||||||
return stardict_dict_get_entry (sdi->owner, sdi->offset);
|
return stardict_dict_get_entry (sdi->owner, sdi->offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -108,6 +108,7 @@ GQuark stardict_error_quark (void);
|
|||||||
|
|
||||||
// --- Dictionary information --------------------------------------------------
|
// --- Dictionary information --------------------------------------------------
|
||||||
|
|
||||||
|
StardictInfo *stardict_info_new (const gchar *filename, GError **error);
|
||||||
const gchar *stardict_info_get_path (StardictInfo *sdi) G_GNUC_PURE;
|
const gchar *stardict_info_get_path (StardictInfo *sdi) G_GNUC_PURE;
|
||||||
const gchar *stardict_info_get_book_name (StardictInfo *sdi) G_GNUC_PURE;
|
const gchar *stardict_info_get_book_name (StardictInfo *sdi) G_GNUC_PURE;
|
||||||
gsize stardict_info_get_word_count (StardictInfo *sd) G_GNUC_PURE;
|
gsize stardict_info_get_word_count (StardictInfo *sd) G_GNUC_PURE;
|
||||||
@@ -192,13 +193,13 @@ struct stardict_entry_field
|
|||||||
{
|
{
|
||||||
gchar type; ///< Type of entry (EntryFieldType)
|
gchar type; ///< Type of entry (EntryFieldType)
|
||||||
gpointer data; ///< Raw data or null-terminated string
|
gpointer data; ///< Raw data or null-terminated string
|
||||||
gsize data_size; ///< Size of data, includding any \0
|
gsize data_size; ///< Size of data, including any \0
|
||||||
};
|
};
|
||||||
|
|
||||||
struct stardict_entry
|
struct stardict_entry
|
||||||
{
|
{
|
||||||
GObject parent_instance;
|
GObject parent_instance;
|
||||||
GList * fields; ///< List of StardictEntryField's
|
GList * fields; ///< List of StardictEntryField-s
|
||||||
};
|
};
|
||||||
|
|
||||||
struct stardict_entry_class
|
struct stardict_entry_class
|
||||||
@@ -209,4 +210,4 @@ struct stardict_entry_class
|
|||||||
GType stardict_entry_get_type (void);
|
GType stardict_entry_get_type (void);
|
||||||
const GList *stardict_entry_get_fields (StardictEntry *sde) G_GNUC_PURE;
|
const GList *stardict_entry_get_fields (StardictEntry *sde) G_GNUC_PURE;
|
||||||
|
|
||||||
#endif // ! STARDICT_H
|
#endif // ! STARDICT_H
|
||||||
|
|||||||
@@ -30,6 +30,7 @@
|
|||||||
#include "stardict.h"
|
#include "stardict.h"
|
||||||
#include "stardict-private.h"
|
#include "stardict-private.h"
|
||||||
#include "generator.h"
|
#include "generator.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
|
||||||
// --- Pronunciation generator -------------------------------------------------
|
// --- Pronunciation generator -------------------------------------------------
|
||||||
@@ -149,7 +150,7 @@ worker_writer (WorkerData *data)
|
|||||||
|
|
||||||
stardict_iterator_next (data->iterator);
|
stardict_iterator_next (data->iterator);
|
||||||
if (fprintf (data->child_stdin, "%s\n", x) < 0)
|
if (fprintf (data->child_stdin, "%s\n", x) < 0)
|
||||||
g_error ("write to eSpeak failed: %s", strerror (errno));
|
fatal ("write to eSpeak failed: %s\n", g_strerror (errno));
|
||||||
|
|
||||||
g_free (x);
|
g_free (x);
|
||||||
}
|
}
|
||||||
@@ -169,16 +170,10 @@ get_void_entry (gchar *cmdline[])
|
|||||||
if (!g_spawn_sync (NULL, cmdline, NULL,
|
if (!g_spawn_sync (NULL, cmdline, NULL,
|
||||||
G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL,
|
G_SPAWN_SEARCH_PATH | G_SPAWN_STDERR_TO_DEV_NULL, NULL, NULL,
|
||||||
&output, NULL, &exit_status, &error))
|
&output, NULL, &exit_status, &error))
|
||||||
{
|
fatal ("Error: couldn't spawn espeak: %s\n", error->message);
|
||||||
g_printerr ("Error: couldn't spawn espeak: %s", error->message);
|
|
||||||
exit (EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (exit_status)
|
if (exit_status)
|
||||||
{
|
fatal ("Error: espeak returned %d\n", exit_status);
|
||||||
g_printerr ("Error: espeak returned %d\n", exit_status);
|
|
||||||
exit (EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
@@ -193,7 +188,7 @@ worker (WorkerData *data)
|
|||||||
if (!g_spawn_async_with_pipes (NULL, data->cmdline, NULL,
|
if (!g_spawn_async_with_pipes (NULL, data->cmdline, NULL,
|
||||||
G_SPAWN_SEARCH_PATH, NULL, NULL,
|
G_SPAWN_SEARCH_PATH, NULL, NULL,
|
||||||
NULL, &child_in, &child_out, NULL, &error))
|
NULL, &child_in, &child_out, NULL, &error))
|
||||||
g_error ("g_spawn() failed: %s", error->message);
|
fatal ("g_spawn: %s\n", error->message);
|
||||||
|
|
||||||
data->child_stdin = fdopen (child_in, "wb");
|
data->child_stdin = fdopen (child_in, "wb");
|
||||||
if (!data->child_stdin)
|
if (!data->child_stdin)
|
||||||
@@ -228,7 +223,7 @@ worker (WorkerData *data)
|
|||||||
while ((c = fgetc (child_stdout)) != EOF && c != '\n')
|
while ((c = fgetc (child_stdout)) != EOF && c != '\n')
|
||||||
g_string_append_c (s, c);
|
g_string_append_c (s, c);
|
||||||
if (c == EOF)
|
if (c == EOF)
|
||||||
g_error ("eSpeak process died too soon");
|
fatal ("eSpeak process died too soon\n");
|
||||||
|
|
||||||
gchar *translation = g_string_free (s, FALSE);
|
gchar *translation = g_string_free (s, FALSE);
|
||||||
*output_end = translation;
|
*output_end = translation;
|
||||||
@@ -246,11 +241,8 @@ worker (WorkerData *data)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (fgetc (child_stdout) != EOF)
|
if (fgetc (child_stdout) != EOF)
|
||||||
{
|
fatal ("Error: eSpeak has written more lines than it should. "
|
||||||
g_printerr ("Error: eSpeak has written more lines than it should. "
|
|
||||||
"The output would be corrupt, aborting.\n");
|
"The output would be corrupt, aborting.\n");
|
||||||
exit (EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
fclose (child_stdout);
|
fclose (child_stdout);
|
||||||
return g_thread_join (writer);
|
return g_thread_join (writer);
|
||||||
@@ -258,56 +250,6 @@ worker (WorkerData *data)
|
|||||||
|
|
||||||
// --- Main --------------------------------------------------------------------
|
// --- Main --------------------------------------------------------------------
|
||||||
|
|
||||||
/// Copy the contents of one StardictInfo object into another. Ignores path.
|
|
||||||
static void
|
|
||||||
stardict_info_copy (StardictInfo *dest, const StardictInfo *src)
|
|
||||||
{
|
|
||||||
dest->version = src->version;
|
|
||||||
|
|
||||||
guint i;
|
|
||||||
for (i = 0; i < _stardict_ifo_keys_length; i++)
|
|
||||||
{
|
|
||||||
const struct stardict_ifo_key *key = &_stardict_ifo_keys[i];
|
|
||||||
if (key->type == IFO_STRING)
|
|
||||||
{
|
|
||||||
gchar **p = &G_STRUCT_MEMBER (gchar *, dest, key->offset);
|
|
||||||
gchar *q = G_STRUCT_MEMBER (gchar *, src, key->offset);
|
|
||||||
|
|
||||||
g_free (*p);
|
|
||||||
*p = q ? g_strdup (q) : NULL;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
G_STRUCT_MEMBER (gulong, dest, key->offset) =
|
|
||||||
G_STRUCT_MEMBER (gulong, src, key->offset);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Write a list of data fields back to a dictionary.
|
|
||||||
static gboolean
|
|
||||||
write_fields (Generator *generator, GList *fields, gboolean sts, GError **error)
|
|
||||||
{
|
|
||||||
while (fields)
|
|
||||||
{
|
|
||||||
StardictEntryField *field = fields->data;
|
|
||||||
if (!sts && !generator_write_type (generator, field->type, error))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
gboolean mark_end = !sts || fields->next != NULL;
|
|
||||||
if (g_ascii_islower (field->type))
|
|
||||||
{
|
|
||||||
if (!generator_write_string (generator,
|
|
||||||
field->data, mark_end, error))
|
|
||||||
return FALSE;
|
|
||||||
}
|
|
||||||
else if (!generator_write_raw (generator,
|
|
||||||
field->data, field->data_size, mark_end, error))
|
|
||||||
return FALSE;
|
|
||||||
|
|
||||||
fields = fields->next;
|
|
||||||
}
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
int
|
||||||
main (int argc, char *argv[])
|
main (int argc, char *argv[])
|
||||||
{
|
{
|
||||||
@@ -339,18 +281,10 @@ G_GNUC_END_IGNORE_DEPRECATIONS
|
|||||||
("input.ifo output-basename - add pronunciation to dictionaries");
|
("input.ifo output-basename - add pronunciation to dictionaries");
|
||||||
g_option_context_add_main_entries (ctx, entries, NULL);
|
g_option_context_add_main_entries (ctx, entries, NULL);
|
||||||
if (!g_option_context_parse (ctx, &argc, &argv, &error))
|
if (!g_option_context_parse (ctx, &argc, &argv, &error))
|
||||||
{
|
fatal ("Error: option parsing failed: %s\n", error->message);
|
||||||
g_printerr ("Error: option parsing failed: %s\n", error->message);
|
|
||||||
exit (EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argc != 3)
|
if (argc != 3)
|
||||||
{
|
fatal ("%s", g_option_context_get_help (ctx, TRUE, NULL));
|
||||||
gchar *help = g_option_context_get_help (ctx, TRUE, FALSE);
|
|
||||||
g_printerr ("%s", help);
|
|
||||||
g_free (help);
|
|
||||||
exit (EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_option_context_free (ctx);
|
g_option_context_free (ctx);
|
||||||
|
|
||||||
@@ -369,20 +303,13 @@ G_GNUC_END_IGNORE_DEPRECATIONS
|
|||||||
printf ("Loading the original dictionary...\n");
|
printf ("Loading the original dictionary...\n");
|
||||||
StardictDict *dict = stardict_dict_new (argv[1], &error);
|
StardictDict *dict = stardict_dict_new (argv[1], &error);
|
||||||
if (!dict)
|
if (!dict)
|
||||||
{
|
fatal ("Error: opening the dictionary failed: %s\n", error->message);
|
||||||
g_printerr ("Error: opening the dictionary failed: %s\n",
|
|
||||||
error->message);
|
|
||||||
exit (EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
gsize n_words = stardict_info_get_word_count
|
gsize n_words = stardict_info_get_word_count
|
||||||
(stardict_dict_get_info (dict));
|
(stardict_dict_get_info (dict));
|
||||||
|
|
||||||
if (n_processes <= 0)
|
if (n_processes <= 0)
|
||||||
{
|
fatal ("Error: there must be at least one process\n");
|
||||||
g_printerr ("Error: there must be at least one process\n");
|
|
||||||
exit (EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((gsize) n_processes > n_words * 1024)
|
if ((gsize) n_processes > n_words * 1024)
|
||||||
{
|
{
|
||||||
@@ -461,11 +388,8 @@ G_GNUC_END_IGNORE_DEPRECATIONS
|
|||||||
// Put extended entries into a new dictionary
|
// Put extended entries into a new dictionary
|
||||||
Generator *generator = generator_new (argv[2], &error);
|
Generator *generator = generator_new (argv[2], &error);
|
||||||
if (!generator)
|
if (!generator)
|
||||||
{
|
fatal ("Error: failed to create the output dictionary: %s\n",
|
||||||
g_printerr ("Error: failed to create the output dictionary: %s\n",
|
|
||||||
error->message);
|
error->message);
|
||||||
exit (EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
StardictInfo *info = generator->info;
|
StardictInfo *info = generator->info;
|
||||||
stardict_info_copy (info, stardict_dict_get_info (dict));
|
stardict_info_copy (info, stardict_dict_get_info (dict));
|
||||||
@@ -516,14 +440,10 @@ G_GNUC_END_IGNORE_DEPRECATIONS
|
|||||||
start_link.next = entry->fields;
|
start_link.next = entry->fields;
|
||||||
start_link.data = &field;
|
start_link.data = &field;
|
||||||
|
|
||||||
if (!write_fields (generator, &start_link,
|
if (!generator_write_fields (generator, &start_link, &error)
|
||||||
info->same_type_sequence != NULL, &error)
|
|
||||||
|| !generator_finish_entry (generator,
|
|| !generator_finish_entry (generator,
|
||||||
stardict_iterator_get_word (iterator), &error))
|
stardict_iterator_get_word (iterator), &error))
|
||||||
{
|
fatal ("Error: write failed: %s\n", error->message);
|
||||||
g_printerr ("Error: write failed: %s\n", error->message);
|
|
||||||
exit (EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
g_object_unref (entry);
|
g_object_unref (entry);
|
||||||
|
|
||||||
@@ -540,11 +460,7 @@ G_GNUC_END_IGNORE_DEPRECATIONS
|
|||||||
|
|
||||||
putchar ('\n');
|
putchar ('\n');
|
||||||
if (!generator_finish (generator, &error))
|
if (!generator_finish (generator, &error))
|
||||||
{
|
fatal ("Error: failed to write the dictionary: %s\n", error->message);
|
||||||
g_printerr ("Error: failed to write the dictionary: %s\n",
|
|
||||||
error->message);
|
|
||||||
exit (EXIT_FAILURE);
|
|
||||||
}
|
|
||||||
|
|
||||||
generator_free (generator);
|
generator_free (generator);
|
||||||
g_object_unref (dict);
|
g_object_unref (dict);
|
||||||
875
src/tdv-gui.c
Normal file
875
src/tdv-gui.c
Normal file
@@ -0,0 +1,875 @@
|
|||||||
|
/*
|
||||||
|
* StarDict GTK+ UI
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 - 2024, Přemysl Eric Janouch <p@janouch.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
#include <glib/gi18n.h>
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "stardict.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "stardict-view.h"
|
||||||
|
|
||||||
|
static struct
|
||||||
|
{
|
||||||
|
GtkWidget *window; ///< Top-level window
|
||||||
|
GtkWidget *notebook; ///< Notebook with tabs
|
||||||
|
GtkWidget *hamburger; ///< Hamburger menu
|
||||||
|
GtkWidget *entry; ///< Search entry widget
|
||||||
|
GtkWidget *view; ///< Entries view
|
||||||
|
|
||||||
|
gint dictionary; ///< Index of the current dictionary
|
||||||
|
gint last; ///< The last dictionary index
|
||||||
|
GPtrArray *dictionaries; ///< All open dictionaries
|
||||||
|
|
||||||
|
gboolean loading; ///< Dictionaries are being loaded
|
||||||
|
|
||||||
|
gboolean watch_selection; ///< Following X11 PRIMARY?
|
||||||
|
}
|
||||||
|
g;
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
static void
|
||||||
|
load_from_filenames (GPtrArray *out, gchar **filenames)
|
||||||
|
{
|
||||||
|
for (gsize i = 0; filenames[i]; i++)
|
||||||
|
{
|
||||||
|
Dictionary *dict = g_malloc0 (sizeof *dict);
|
||||||
|
dict->filename = g_strdup (filenames[i]);
|
||||||
|
g_ptr_array_add (out, dict);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: try to deduplicate, similar to app_load_config_values()
|
||||||
|
static gboolean
|
||||||
|
load_from_key_file (GPtrArray *out, GKeyFile *kf, GError **error)
|
||||||
|
{
|
||||||
|
const gchar *dictionaries = "Dictionaries";
|
||||||
|
gchar **names = g_key_file_get_keys (kf, dictionaries, NULL, NULL);
|
||||||
|
if (!names)
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
for (gsize i = 0; names[i]; i++)
|
||||||
|
{
|
||||||
|
Dictionary *dict = g_malloc0 (sizeof *dict);
|
||||||
|
dict->name = names[i];
|
||||||
|
g_ptr_array_add (out, dict);
|
||||||
|
}
|
||||||
|
g_free (names);
|
||||||
|
|
||||||
|
for (gsize i = 0; i < out->len; i++)
|
||||||
|
{
|
||||||
|
Dictionary *dict = g_ptr_array_index (out, i);
|
||||||
|
gchar *path =
|
||||||
|
g_key_file_get_string (kf, dictionaries, dict->name, error);
|
||||||
|
if (!path)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
// Try to resolve relative paths and expand tildes
|
||||||
|
if (!(dict->filename =
|
||||||
|
resolve_filename (path, resolve_relative_config_filename)))
|
||||||
|
dict->filename = path;
|
||||||
|
else
|
||||||
|
g_free (path);
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
load_from_config (GPtrArray *out, GError **error)
|
||||||
|
{
|
||||||
|
GKeyFile *key_file = load_project_config_file (error);
|
||||||
|
if (!key_file)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
gboolean result = load_from_key_file (out, key_file, error);
|
||||||
|
g_key_file_free (key_file);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
search (Dictionary *dict)
|
||||||
|
{
|
||||||
|
GtkEntryBuffer *buf = gtk_entry_get_buffer (GTK_ENTRY (g.entry));
|
||||||
|
const gchar *input_utf8 = gtk_entry_buffer_get_text (buf);
|
||||||
|
if (!dict->dict)
|
||||||
|
return;
|
||||||
|
|
||||||
|
StardictIterator *iterator =
|
||||||
|
stardict_dict_search (dict->dict, input_utf8, NULL);
|
||||||
|
stardict_view_set_position (STARDICT_VIEW (g.view),
|
||||||
|
dict->dict, stardict_iterator_get_offset (iterator));
|
||||||
|
g_object_unref (iterator);
|
||||||
|
|
||||||
|
stardict_view_set_matched (STARDICT_VIEW (g.view), input_utf8);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_changed (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gpointer data)
|
||||||
|
{
|
||||||
|
search (g_ptr_array_index (g.dictionaries, g.dictionary));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_send (G_GNUC_UNUSED StardictView *view,
|
||||||
|
const char *word, G_GNUC_UNUSED gpointer data)
|
||||||
|
{
|
||||||
|
GtkEntryBuffer *buf = gtk_entry_get_buffer (GTK_ENTRY (g.entry));
|
||||||
|
gtk_entry_buffer_set_text (buf, word, -1);
|
||||||
|
gtk_editable_select_region (GTK_EDITABLE (g.entry), 0, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_selection_received (G_GNUC_UNUSED GtkClipboard *clipboard, const gchar *text,
|
||||||
|
G_GNUC_UNUSED gpointer data)
|
||||||
|
{
|
||||||
|
if (!text)
|
||||||
|
return;
|
||||||
|
|
||||||
|
gchar *trimmed = g_strstrip (g_strdup (text));
|
||||||
|
gtk_entry_set_text (GTK_ENTRY (g.entry), trimmed);
|
||||||
|
g_free (trimmed);
|
||||||
|
g_signal_emit_by_name (g.entry,
|
||||||
|
"move-cursor", GTK_MOVEMENT_BUFFER_ENDS, 1, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_selection (GtkClipboard *clipboard, GdkEvent *event,
|
||||||
|
G_GNUC_UNUSED gpointer data)
|
||||||
|
{
|
||||||
|
if (g.watch_selection
|
||||||
|
&& !gtk_window_has_toplevel_focus (GTK_WINDOW (g.window))
|
||||||
|
&& event->owner_change.owner != NULL)
|
||||||
|
gtk_clipboard_request_text (clipboard, on_selection_received, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_selection_watch_toggle (GtkCheckMenuItem *item, G_GNUC_UNUSED gpointer data)
|
||||||
|
{
|
||||||
|
g.watch_selection = gtk_check_menu_item_get_active (item);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_switch_page (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED GtkWidget *page,
|
||||||
|
guint page_num, G_GNUC_UNUSED gpointer data)
|
||||||
|
{
|
||||||
|
g.last = g.dictionary;
|
||||||
|
g.dictionary = page_num;
|
||||||
|
search (g_ptr_array_index (g.dictionaries, g.dictionary));
|
||||||
|
|
||||||
|
// Hack: Make right-clicking notebook arrows also re-focus the entry.
|
||||||
|
GdkEvent *event = gtk_get_current_event ();
|
||||||
|
if (event && event->type == GDK_BUTTON_PRESS)
|
||||||
|
gtk_widget_grab_focus (g.entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
accelerate_hamburger (GdkEvent *event)
|
||||||
|
{
|
||||||
|
gchar *accelerator = NULL;
|
||||||
|
g_object_get (gtk_widget_get_settings (g.window), "gtk-menu-bar-accel",
|
||||||
|
&accelerator, NULL);
|
||||||
|
if (!accelerator)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
guint key = 0;
|
||||||
|
GdkModifierType mods = 0;
|
||||||
|
gtk_accelerator_parse (accelerator, &key, &mods);
|
||||||
|
g_free (accelerator);
|
||||||
|
|
||||||
|
guint mask = gtk_accelerator_get_default_mod_mask ();
|
||||||
|
if (!key || event->key.keyval != key || (event->key.state & mask) != mods)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
gtk_button_clicked (GTK_BUTTON (g.hamburger));
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
on_key_press (G_GNUC_UNUSED GtkWidget *widget, GdkEvent *event,
|
||||||
|
G_GNUC_UNUSED gpointer data)
|
||||||
|
{
|
||||||
|
// The "activate" signal of the GtkMenuButton cannot be used
|
||||||
|
// from a real accelerator, due to "no trigger event for menu popup".
|
||||||
|
if (accelerate_hamburger (event))
|
||||||
|
return TRUE;
|
||||||
|
|
||||||
|
GtkNotebook *notebook = GTK_NOTEBOOK (g.notebook);
|
||||||
|
guint mods = event->key.state & gtk_accelerator_get_default_mod_mask ();
|
||||||
|
if (mods == GDK_CONTROL_MASK)
|
||||||
|
{
|
||||||
|
// Can't use gtk_widget_add_accelerator() to change-current-page(-1/+1)
|
||||||
|
// because that signal has arguments, which cannot be passed.
|
||||||
|
gint current = gtk_notebook_get_current_page (notebook);
|
||||||
|
if (event->key.keyval == GDK_KEY_Page_Up)
|
||||||
|
return gtk_notebook_set_current_page (notebook, --current), TRUE;
|
||||||
|
if (event->key.keyval == GDK_KEY_Page_Down)
|
||||||
|
return gtk_notebook_set_current_page (notebook,
|
||||||
|
++current % gtk_notebook_get_n_pages (notebook)), TRUE;
|
||||||
|
}
|
||||||
|
if (mods == GDK_MOD1_MASK)
|
||||||
|
{
|
||||||
|
if (event->key.keyval >= GDK_KEY_0
|
||||||
|
&& event->key.keyval <= GDK_KEY_9)
|
||||||
|
{
|
||||||
|
gint n = event->key.keyval - GDK_KEY_0;
|
||||||
|
gtk_notebook_set_current_page (notebook, (n ? n : 10) - 1);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
if (event->key.keyval == GDK_KEY_Tab)
|
||||||
|
{
|
||||||
|
gtk_notebook_set_current_page (notebook, g.last);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mods == 0)
|
||||||
|
{
|
||||||
|
StardictView *view = STARDICT_VIEW (g.view);
|
||||||
|
if (event->key.keyval == GDK_KEY_Page_Up)
|
||||||
|
return stardict_view_scroll (view, GTK_SCROLL_PAGES, -0.5), TRUE;
|
||||||
|
if (event->key.keyval == GDK_KEY_Page_Down)
|
||||||
|
return stardict_view_scroll (view, GTK_SCROLL_PAGES, +0.5), TRUE;
|
||||||
|
if (event->key.keyval == GDK_KEY_Up)
|
||||||
|
return stardict_view_scroll (view, GTK_SCROLL_STEPS, -1), TRUE;
|
||||||
|
if (event->key.keyval == GDK_KEY_Down)
|
||||||
|
return stardict_view_scroll (view, GTK_SCROLL_STEPS, +1), TRUE;
|
||||||
|
}
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
on_tab_focus (G_GNUC_UNUSED GtkWidget *widget,
|
||||||
|
G_GNUC_UNUSED GtkDirectionType direction, G_GNUC_UNUSED gpointer user_data)
|
||||||
|
{
|
||||||
|
// Hack: Make it so that tab headers don't retain newly gained focus
|
||||||
|
// when clicked, re-focus the entry instead.
|
||||||
|
GdkEvent *event = gtk_get_current_event ();
|
||||||
|
if (!event || event->type != GDK_BUTTON_PRESS
|
||||||
|
|| event->button.button != GDK_BUTTON_PRIMARY)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
gtk_widget_grab_focus (g.entry);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
init_tabs (void)
|
||||||
|
{
|
||||||
|
for (gsize i = g.dictionaries->len; i--; )
|
||||||
|
{
|
||||||
|
Dictionary *dict = g_ptr_array_index (g.dictionaries, i);
|
||||||
|
GtkWidget *dummy = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
|
||||||
|
g_signal_connect (dummy, "focus", G_CALLBACK (on_tab_focus), NULL);
|
||||||
|
GtkWidget *label = gtk_label_new (dict->name);
|
||||||
|
gtk_notebook_insert_page (GTK_NOTEBOOK (g.notebook), dummy, label, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_widget_show_all (g.notebook);
|
||||||
|
gtk_widget_grab_focus (g.entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
show_error_dialog (GError *error)
|
||||||
|
{
|
||||||
|
GtkWidget *dialog = gtk_message_dialog_new (GTK_WINDOW (g.window), 0,
|
||||||
|
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", error->message);
|
||||||
|
gtk_dialog_run (GTK_DIALOG (dialog));
|
||||||
|
gtk_widget_destroy (dialog);
|
||||||
|
g_error_free (error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Loading -----------------------------------------------------------------
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_new_dictionaries_loaded (G_GNUC_UNUSED GObject* source_object,
|
||||||
|
GAsyncResult* res, G_GNUC_UNUSED gpointer user_data)
|
||||||
|
{
|
||||||
|
g.loading = FALSE;
|
||||||
|
|
||||||
|
GError *error = NULL;
|
||||||
|
GPtrArray *new_dictionaries =
|
||||||
|
g_task_propagate_pointer (G_TASK (res), &error);
|
||||||
|
if (!new_dictionaries)
|
||||||
|
{
|
||||||
|
show_error_dialog (error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (gtk_notebook_get_n_pages (GTK_NOTEBOOK (g.notebook)))
|
||||||
|
gtk_notebook_remove_page (GTK_NOTEBOOK (g.notebook), -1);
|
||||||
|
|
||||||
|
g.dictionary = -1;
|
||||||
|
if (g.dictionaries)
|
||||||
|
g_ptr_array_free (g.dictionaries, TRUE);
|
||||||
|
|
||||||
|
stardict_view_set_position (STARDICT_VIEW (g.view), NULL, 0);
|
||||||
|
g.dictionaries = new_dictionaries;
|
||||||
|
init_tabs ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_reload_dictionaries_task (GTask *task, G_GNUC_UNUSED gpointer source_object,
|
||||||
|
gpointer task_data, G_GNUC_UNUSED GCancellable *cancellable)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
if (load_dictionaries (task_data, &error))
|
||||||
|
{
|
||||||
|
g_task_return_pointer (task,
|
||||||
|
g_ptr_array_ref (task_data), (GDestroyNotify) g_ptr_array_unref);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
g_task_return_error (task, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
reload_dictionaries (GPtrArray *new_dictionaries, GError **error)
|
||||||
|
{
|
||||||
|
// TODO: We could cancel that task.
|
||||||
|
if (g.loading)
|
||||||
|
{
|
||||||
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||||
|
"already loading dictionaries");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Some other kind of indication.
|
||||||
|
// Note that "action widgets" aren't visible without GtkNotebook tabs.
|
||||||
|
g.loading = TRUE;
|
||||||
|
|
||||||
|
GTask *task = g_task_new (NULL, NULL, on_new_dictionaries_loaded, NULL);
|
||||||
|
g_task_set_name (task, __func__);
|
||||||
|
g_task_set_task_data (task,
|
||||||
|
new_dictionaries, (GDestroyNotify) g_ptr_array_unref);
|
||||||
|
g_task_run_in_thread (task, on_reload_dictionaries_task);
|
||||||
|
g_object_unref (task);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static GtkWidget *
|
||||||
|
new_open_dialog (void)
|
||||||
|
{
|
||||||
|
// The default is local-only. Paths are returned absolute.
|
||||||
|
GtkWidget *dialog = gtk_file_chooser_dialog_new (_("Open dictionary"),
|
||||||
|
GTK_WINDOW (g.window), GTK_FILE_CHOOSER_ACTION_OPEN,
|
||||||
|
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
||||||
|
_("_Open"), GTK_RESPONSE_ACCEPT, NULL);
|
||||||
|
|
||||||
|
GtkFileFilter *filter = gtk_file_filter_new ();
|
||||||
|
gtk_file_filter_add_pattern (filter, "*.ifo");
|
||||||
|
gtk_file_filter_set_name (filter, "*.ifo");
|
||||||
|
GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog);
|
||||||
|
gtk_file_chooser_add_filter (chooser, filter);
|
||||||
|
gtk_file_chooser_set_select_multiple (chooser, TRUE);
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_open (G_GNUC_UNUSED GtkMenuItem *item, G_GNUC_UNUSED gpointer data)
|
||||||
|
{
|
||||||
|
GtkWidget *dialog = new_open_dialog ();
|
||||||
|
GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog);
|
||||||
|
GPtrArray *new_dictionaries =
|
||||||
|
g_ptr_array_new_with_free_func ((GDestroyNotify) dictionary_destroy);
|
||||||
|
if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
|
||||||
|
{
|
||||||
|
GSList *paths = gtk_file_chooser_get_filenames (chooser);
|
||||||
|
for (GSList *iter = paths; iter; iter = iter->next)
|
||||||
|
{
|
||||||
|
Dictionary *dict = g_malloc0 (sizeof *dict);
|
||||||
|
dict->filename = iter->data;
|
||||||
|
g_ptr_array_add (new_dictionaries, dict);
|
||||||
|
}
|
||||||
|
g_slist_free (paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
gtk_widget_destroy (dialog);
|
||||||
|
|
||||||
|
GError *error = NULL;
|
||||||
|
if (!new_dictionaries->len
|
||||||
|
|| !reload_dictionaries (new_dictionaries, &error))
|
||||||
|
g_ptr_array_free (new_dictionaries, TRUE);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
show_error_dialog (error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_drag_data_received (G_GNUC_UNUSED GtkWidget *widget, GdkDragContext *context,
|
||||||
|
G_GNUC_UNUSED gint x, G_GNUC_UNUSED gint y, GtkSelectionData *data,
|
||||||
|
G_GNUC_UNUSED guint info, guint time, G_GNUC_UNUSED gpointer user_data)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
gchar **dropped_uris = gtk_selection_data_get_uris (data);
|
||||||
|
if (!dropped_uris)
|
||||||
|
return;
|
||||||
|
|
||||||
|
GPtrArray *new_dictionaries =
|
||||||
|
g_ptr_array_new_with_free_func ((GDestroyNotify) dictionary_destroy);
|
||||||
|
for (gsize i = 0; !error && dropped_uris[i]; i++)
|
||||||
|
{
|
||||||
|
Dictionary *dict = g_malloc0 (sizeof *dict);
|
||||||
|
dict->filename = g_filename_from_uri (dropped_uris[i], NULL, &error);
|
||||||
|
g_ptr_array_add (new_dictionaries, dict);
|
||||||
|
}
|
||||||
|
|
||||||
|
g_strfreev (dropped_uris);
|
||||||
|
if (!new_dictionaries->len
|
||||||
|
|| !reload_dictionaries (new_dictionaries, &error))
|
||||||
|
g_ptr_array_free (new_dictionaries, TRUE);
|
||||||
|
|
||||||
|
gtk_drag_finish (context, error == NULL, FALSE, time);
|
||||||
|
|
||||||
|
if (error)
|
||||||
|
show_error_dialog (error);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Settings ----------------------------------------------------------------
|
||||||
|
|
||||||
|
typedef struct settings_data SettingsData;
|
||||||
|
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
SETTINGS_COLUMN_NAME,
|
||||||
|
SETTINGS_COLUMN_PATH,
|
||||||
|
SETTINGS_COLUMN_COUNT
|
||||||
|
};
|
||||||
|
|
||||||
|
struct settings_data
|
||||||
|
{
|
||||||
|
GKeyFile *key_file; ///< Configuration file
|
||||||
|
GtkTreeModel *model; ///< GtkListStore
|
||||||
|
};
|
||||||
|
|
||||||
|
static void
|
||||||
|
settings_load (SettingsData *data)
|
||||||
|
{
|
||||||
|
// We want to keep original comments, as well as any other data.
|
||||||
|
GError *error = NULL;
|
||||||
|
data->key_file = load_project_config_file (&error);
|
||||||
|
if (!data->key_file)
|
||||||
|
{
|
||||||
|
if (error)
|
||||||
|
show_error_dialog (error);
|
||||||
|
data->key_file = g_key_file_new ();
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkListStore *list_store = gtk_list_store_new (SETTINGS_COLUMN_COUNT,
|
||||||
|
G_TYPE_STRING, G_TYPE_STRING);
|
||||||
|
data->model = GTK_TREE_MODEL (list_store);
|
||||||
|
|
||||||
|
const gchar *dictionaries = "Dictionaries";
|
||||||
|
gchar **names =
|
||||||
|
g_key_file_get_keys (data->key_file, dictionaries, NULL, NULL);
|
||||||
|
if (!names)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (gsize i = 0; names[i]; i++)
|
||||||
|
{
|
||||||
|
gchar *path = g_key_file_get_string (data->key_file,
|
||||||
|
dictionaries, names[i], NULL);
|
||||||
|
if (!path)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
GtkTreeIter iter = { 0 };
|
||||||
|
gtk_list_store_append (list_store, &iter);
|
||||||
|
gtk_list_store_set (list_store, &iter,
|
||||||
|
SETTINGS_COLUMN_NAME, names[i], SETTINGS_COLUMN_PATH, path, -1);
|
||||||
|
g_free (path);
|
||||||
|
}
|
||||||
|
g_strfreev (names);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
settings_save (SettingsData *data)
|
||||||
|
{
|
||||||
|
const gchar *dictionaries = "Dictionaries";
|
||||||
|
g_key_file_remove_group (data->key_file, dictionaries, NULL);
|
||||||
|
|
||||||
|
GtkTreeIter iter = { 0 };
|
||||||
|
gboolean valid = gtk_tree_model_get_iter_first (data->model, &iter);
|
||||||
|
while (valid)
|
||||||
|
{
|
||||||
|
gchar *name = NULL, *path = NULL;
|
||||||
|
gtk_tree_model_get (data->model, &iter,
|
||||||
|
SETTINGS_COLUMN_NAME, &name, SETTINGS_COLUMN_PATH, &path, -1);
|
||||||
|
if (name && path)
|
||||||
|
g_key_file_set_string (data->key_file, dictionaries, name, path);
|
||||||
|
g_free (name);
|
||||||
|
g_free (path);
|
||||||
|
|
||||||
|
valid = gtk_tree_model_iter_next (data->model, &iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
GError *e = NULL;
|
||||||
|
if (!save_project_config_file (data->key_file, &e))
|
||||||
|
show_error_dialog (e);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_settings_name_edited (G_GNUC_UNUSED GtkCellRendererText *cell,
|
||||||
|
const gchar *path_string, const gchar *new_text, gpointer data)
|
||||||
|
{
|
||||||
|
GtkTreeModel *model = GTK_TREE_MODEL (data);
|
||||||
|
GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
|
||||||
|
GtkTreeIter iter = { 0 };
|
||||||
|
gtk_tree_model_get_iter (model, &iter, path);
|
||||||
|
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
|
||||||
|
SETTINGS_COLUMN_NAME, new_text, -1);
|
||||||
|
gtk_tree_path_free (path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_settings_path_edited (G_GNUC_UNUSED GtkCellRendererText *cell,
|
||||||
|
const gchar *path_string, const gchar *new_text, gpointer data)
|
||||||
|
{
|
||||||
|
GtkTreeModel *model = GTK_TREE_MODEL (data);
|
||||||
|
GtkTreePath *path = gtk_tree_path_new_from_string (path_string);
|
||||||
|
GtkTreeIter iter = { 0 };
|
||||||
|
gtk_tree_model_get_iter (model, &iter, path);
|
||||||
|
gtk_list_store_set (GTK_LIST_STORE (model), &iter,
|
||||||
|
SETTINGS_COLUMN_PATH, new_text, -1);
|
||||||
|
gtk_tree_path_free (path);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_settings_add (G_GNUC_UNUSED GtkButton *button, gpointer user_data)
|
||||||
|
{
|
||||||
|
GtkWidget *dialog = new_open_dialog ();
|
||||||
|
GtkFileChooser *chooser = GTK_FILE_CHOOSER (dialog);
|
||||||
|
|
||||||
|
GSList *paths = NULL;
|
||||||
|
if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT)
|
||||||
|
paths = gtk_file_chooser_get_filenames (chooser);
|
||||||
|
gtk_widget_destroy (dialog);
|
||||||
|
// When the dialog is aborted, we simply add an empty list.
|
||||||
|
|
||||||
|
GtkTreeView *tree_view = GTK_TREE_VIEW (user_data);
|
||||||
|
gtk_tree_selection_unselect_all (gtk_tree_view_get_selection (tree_view));
|
||||||
|
GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
|
||||||
|
GtkListStore *list_store = GTK_LIST_STORE (model);
|
||||||
|
|
||||||
|
const gchar *home = g_get_home_dir ();
|
||||||
|
for (GSList *iter = paths; iter; iter = iter->next)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
StardictInfo *ifo = stardict_info_new (iter->data, &error);
|
||||||
|
g_free (iter->data);
|
||||||
|
if (!ifo)
|
||||||
|
{
|
||||||
|
show_error_dialog (error);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We also expand tildes, even on Windows, so no problem there.
|
||||||
|
const gchar *path = stardict_info_get_path (ifo);
|
||||||
|
gchar *tildified = g_str_has_prefix (stardict_info_get_path (ifo), home)
|
||||||
|
? g_strdup_printf ("~%s", path + strlen (home))
|
||||||
|
: g_strdup (path);
|
||||||
|
|
||||||
|
GtkTreeIter iter = { 0 };
|
||||||
|
gtk_list_store_append (list_store, &iter);
|
||||||
|
gtk_list_store_set (list_store, &iter,
|
||||||
|
SETTINGS_COLUMN_NAME, stardict_info_get_book_name (ifo),
|
||||||
|
SETTINGS_COLUMN_PATH, tildified, -1);
|
||||||
|
g_free (tildified);
|
||||||
|
stardict_info_free (ifo);
|
||||||
|
}
|
||||||
|
g_slist_free (paths);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_settings_remove (G_GNUC_UNUSED GtkButton *button, gpointer user_data)
|
||||||
|
{
|
||||||
|
GtkTreeView *tree_view = GTK_TREE_VIEW (user_data);
|
||||||
|
GtkTreeSelection *selection = gtk_tree_view_get_selection (tree_view);
|
||||||
|
GtkTreeModel *model = gtk_tree_view_get_model (tree_view);
|
||||||
|
GtkListStore *list_store = GTK_LIST_STORE (model);
|
||||||
|
|
||||||
|
GList *selected = gtk_tree_selection_get_selected_rows (selection, &model);
|
||||||
|
for (GList *iter = selected; iter; iter = iter->next)
|
||||||
|
{
|
||||||
|
GtkTreePath *path = iter->data;
|
||||||
|
iter->data = gtk_tree_row_reference_new (model, path);
|
||||||
|
gtk_tree_path_free (path);
|
||||||
|
}
|
||||||
|
for (GList *iter = selected; iter; iter = iter->next)
|
||||||
|
{
|
||||||
|
GtkTreePath *path = gtk_tree_row_reference_get_path (iter->data);
|
||||||
|
if (path)
|
||||||
|
{
|
||||||
|
GtkTreeIter tree_iter = { 0 };
|
||||||
|
if (gtk_tree_model_get_iter (model, &tree_iter, path))
|
||||||
|
gtk_list_store_remove (list_store, &tree_iter);
|
||||||
|
gtk_tree_path_free (path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
g_list_free_full (selected, (GDestroyNotify) gtk_tree_row_reference_free);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_settings_selection_changed
|
||||||
|
(GtkTreeSelection* selection, gpointer user_data)
|
||||||
|
{
|
||||||
|
GtkWidget *remove = GTK_WIDGET (user_data);
|
||||||
|
gtk_widget_set_sensitive (remove,
|
||||||
|
gtk_tree_selection_count_selected_rows (selection) > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_settings (G_GNUC_UNUSED GtkMenuItem *item, G_GNUC_UNUSED gpointer data)
|
||||||
|
{
|
||||||
|
SettingsData sd = {};
|
||||||
|
settings_load (&sd);
|
||||||
|
|
||||||
|
GtkWidget *treeview = gtk_tree_view_new_with_model (sd.model);
|
||||||
|
gtk_tree_view_set_reorderable (GTK_TREE_VIEW (treeview), TRUE);
|
||||||
|
g_object_unref (sd.model);
|
||||||
|
|
||||||
|
GtkCellRenderer *renderer = gtk_cell_renderer_text_new ();
|
||||||
|
g_object_set (renderer, "editable", TRUE, NULL);
|
||||||
|
g_signal_connect (renderer, "edited",
|
||||||
|
G_CALLBACK (on_settings_name_edited), sd.model);
|
||||||
|
GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes
|
||||||
|
(_("Name"), renderer, "text", SETTINGS_COLUMN_NAME, NULL);
|
||||||
|
gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
|
||||||
|
|
||||||
|
renderer = gtk_cell_renderer_text_new ();
|
||||||
|
g_object_set (renderer, "editable", TRUE, NULL);
|
||||||
|
g_signal_connect (renderer, "edited",
|
||||||
|
G_CALLBACK (on_settings_path_edited), sd.model);
|
||||||
|
column = gtk_tree_view_column_new_with_attributes
|
||||||
|
(_("Path"), renderer, "text", SETTINGS_COLUMN_PATH, NULL);
|
||||||
|
gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
|
||||||
|
|
||||||
|
GtkWidget *scrolled = gtk_scrolled_window_new (NULL, NULL);
|
||||||
|
gtk_scrolled_window_set_shadow_type
|
||||||
|
(GTK_SCROLLED_WINDOW (scrolled), GTK_SHADOW_ETCHED_IN);
|
||||||
|
gtk_container_add (GTK_CONTAINER (scrolled), treeview);
|
||||||
|
GtkWidget *dialog = gtk_dialog_new_with_buttons (_("Settings"),
|
||||||
|
GTK_WINDOW (g.window),
|
||||||
|
GTK_DIALOG_MODAL,
|
||||||
|
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
||||||
|
_("_Save"), GTK_RESPONSE_ACCEPT,
|
||||||
|
NULL);
|
||||||
|
gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_ACCEPT);
|
||||||
|
gtk_window_set_default_size (GTK_WINDOW (dialog), 600, 400);
|
||||||
|
|
||||||
|
GtkWidget *remove = gtk_button_new_with_mnemonic (_("_Remove"));
|
||||||
|
gtk_widget_set_sensitive (remove, FALSE);
|
||||||
|
g_signal_connect (remove, "clicked",
|
||||||
|
G_CALLBACK (on_settings_remove), treeview);
|
||||||
|
|
||||||
|
GtkWidget *add = gtk_button_new_with_mnemonic (_("_Add..."));
|
||||||
|
g_signal_connect (add, "clicked",
|
||||||
|
G_CALLBACK (on_settings_add), treeview);
|
||||||
|
|
||||||
|
GtkTreeSelection *selection =
|
||||||
|
gtk_tree_view_get_selection (GTK_TREE_VIEW (treeview));
|
||||||
|
gtk_tree_selection_set_mode (selection, GTK_SELECTION_MULTIPLE);
|
||||||
|
g_signal_connect (selection, "changed",
|
||||||
|
G_CALLBACK (on_settings_selection_changed), remove);
|
||||||
|
|
||||||
|
GtkWidget *box = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6);
|
||||||
|
gtk_box_pack_start (GTK_BOX (box),
|
||||||
|
gtk_label_new (_("Here you can configure the default dictionaries.")),
|
||||||
|
FALSE, FALSE, 0);
|
||||||
|
gtk_box_pack_end (GTK_BOX (box), remove, FALSE, FALSE, 0);
|
||||||
|
gtk_box_pack_end (GTK_BOX (box), add, FALSE, FALSE, 0);
|
||||||
|
|
||||||
|
GtkWidget *content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog));
|
||||||
|
g_object_set (content_area, "margin", 12, NULL);
|
||||||
|
gtk_box_pack_start (GTK_BOX (content_area), box, FALSE, FALSE, 0);
|
||||||
|
gtk_box_pack_start (GTK_BOX (content_area), scrolled, TRUE, TRUE, 12);
|
||||||
|
|
||||||
|
gtk_widget_show_all (dialog);
|
||||||
|
switch (gtk_dialog_run (GTK_DIALOG (dialog)))
|
||||||
|
{
|
||||||
|
case GTK_RESPONSE_NONE:
|
||||||
|
break;
|
||||||
|
case GTK_RESPONSE_ACCEPT:
|
||||||
|
settings_save (&sd);
|
||||||
|
// Fall through
|
||||||
|
default:
|
||||||
|
gtk_widget_destroy (dialog);
|
||||||
|
}
|
||||||
|
g_key_file_free (sd.key_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Main --------------------------------------------------------------------
|
||||||
|
|
||||||
|
static void
|
||||||
|
on_destroy (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gpointer data)
|
||||||
|
{
|
||||||
|
gtk_main_quit ();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
die_with_dialog (const gchar *message)
|
||||||
|
{
|
||||||
|
GtkWidget *dialog = gtk_message_dialog_new (NULL, 0,
|
||||||
|
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s", message);
|
||||||
|
gtk_dialog_run (GTK_DIALOG (dialog));
|
||||||
|
gtk_widget_destroy (dialog);
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
gui_main (char *argv[])
|
||||||
|
{
|
||||||
|
// Just like with GtkApplication, argv has been parsed by the option group.
|
||||||
|
gtk_init (NULL, NULL);
|
||||||
|
|
||||||
|
gtk_window_set_default_icon_name (PROJECT_NAME);
|
||||||
|
|
||||||
|
GError *error = NULL;
|
||||||
|
GPtrArray *new_dictionaries =
|
||||||
|
g_ptr_array_new_with_free_func ((GDestroyNotify) dictionary_destroy);
|
||||||
|
if (argv[0])
|
||||||
|
load_from_filenames (new_dictionaries, argv);
|
||||||
|
else if (!load_from_config (new_dictionaries, &error) && error)
|
||||||
|
die_with_dialog (error->message);
|
||||||
|
|
||||||
|
if (!new_dictionaries->len)
|
||||||
|
{
|
||||||
|
GtkWidget *dialog = gtk_message_dialog_new (NULL, 0,
|
||||||
|
GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE, "%s",
|
||||||
|
_("No dictionaries found either in "
|
||||||
|
"the configuration or on the command line"));
|
||||||
|
gtk_dialog_run (GTK_DIALOG (dialog));
|
||||||
|
gtk_widget_destroy (dialog);
|
||||||
|
|
||||||
|
// This is better than nothing.
|
||||||
|
// Our GtkNotebook action widget would be invisible without any tabs.
|
||||||
|
on_settings (NULL, NULL);
|
||||||
|
exit (EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Some Adwaita stupidity, plus defaults for our own widget.
|
||||||
|
// All the named colours have been there since GNOME 3.4
|
||||||
|
// (see gnome-extra-themes git history, Adwaita used to live there).
|
||||||
|
const char *style = "notebook header tab { padding: 2px 8px; margin: 0; }"
|
||||||
|
// `gsettings set org.gnome.desktop.interface gtk-key-theme "Emacs"`
|
||||||
|
// isn't quite what I want, and note that ^U works by default
|
||||||
|
"@binding-set Readline {"
|
||||||
|
"bind '<Control>H' { 'delete-from-cursor' (chars, -1) };"
|
||||||
|
"bind '<Control>W' { 'delete-from-cursor' (word-ends, -1) }; }"
|
||||||
|
"entry { -gtk-key-bindings: Readline; border-radius: 0; }"
|
||||||
|
"stardict-view { padding: 0 .25em; }"
|
||||||
|
"stardict-view.odd {"
|
||||||
|
"background: @theme_base_color; "
|
||||||
|
"color: @theme_text_color; }"
|
||||||
|
"stardict-view.odd:backdrop {"
|
||||||
|
"background: @theme_unfocused_base_color; "
|
||||||
|
"color: @theme_fg_color; /* should be more faded than 'text' */ }"
|
||||||
|
"stardict-view.even {"
|
||||||
|
"background: mix(@theme_base_color, @theme_text_color, 0.03); "
|
||||||
|
"color: @theme_text_color; }"
|
||||||
|
"stardict-view.even:backdrop {"
|
||||||
|
"background: mix(@theme_unfocused_base_color, "
|
||||||
|
"@theme_fg_color, 0.03); "
|
||||||
|
"color: @theme_fg_color; /* should be more faded than 'text' */ }"
|
||||||
|
"stardict-view:selected {"
|
||||||
|
"background-color: @theme_selected_bg_color; "
|
||||||
|
"color: @theme_selected_fg_color; }";
|
||||||
|
|
||||||
|
GdkScreen *screen = gdk_screen_get_default ();
|
||||||
|
GtkCssProvider *provider = gtk_css_provider_new ();
|
||||||
|
gtk_css_provider_load_from_data (provider, style, strlen (style), NULL);
|
||||||
|
gtk_style_context_add_provider_for_screen (screen,
|
||||||
|
GTK_STYLE_PROVIDER (provider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
|
||||||
|
|
||||||
|
GtkWidget *item_open = gtk_menu_item_new_with_mnemonic (_("_Open..."));
|
||||||
|
g_signal_connect (item_open, "activate", G_CALLBACK (on_open), NULL);
|
||||||
|
|
||||||
|
GtkWidget *item_settings = gtk_menu_item_new_with_mnemonic (_("_Settings"));
|
||||||
|
g_signal_connect (item_settings, "activate",
|
||||||
|
G_CALLBACK (on_settings), NULL);
|
||||||
|
|
||||||
|
g.watch_selection = TRUE;
|
||||||
|
GtkWidget *item_selection =
|
||||||
|
gtk_check_menu_item_new_with_mnemonic (_("_Follow selection"));
|
||||||
|
gtk_check_menu_item_set_active
|
||||||
|
(GTK_CHECK_MENU_ITEM (item_selection), g.watch_selection);
|
||||||
|
g_signal_connect (item_selection, "toggled",
|
||||||
|
G_CALLBACK (on_selection_watch_toggle), NULL);
|
||||||
|
|
||||||
|
GtkWidget *menu = gtk_menu_new ();
|
||||||
|
gtk_widget_set_halign (menu, GTK_ALIGN_END);
|
||||||
|
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item_open);
|
||||||
|
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item_settings);
|
||||||
|
#ifndef G_OS_WIN32
|
||||||
|
gtk_menu_shell_append (GTK_MENU_SHELL (menu), item_selection);
|
||||||
|
#endif // ! G_OS_WIN32
|
||||||
|
gtk_widget_show_all (menu);
|
||||||
|
|
||||||
|
g.hamburger = gtk_menu_button_new ();
|
||||||
|
gtk_button_set_relief (GTK_BUTTON (g.hamburger), GTK_RELIEF_NONE);
|
||||||
|
gtk_button_set_image (GTK_BUTTON (g.hamburger), gtk_image_new_from_icon_name
|
||||||
|
("open-menu-symbolic", GTK_ICON_SIZE_BUTTON));
|
||||||
|
gtk_menu_button_set_popup (GTK_MENU_BUTTON (g.hamburger), menu);
|
||||||
|
gtk_widget_show (g.hamburger);
|
||||||
|
|
||||||
|
g.notebook = gtk_notebook_new ();
|
||||||
|
g_signal_connect (g.notebook, "switch-page",
|
||||||
|
G_CALLBACK (on_switch_page), NULL);
|
||||||
|
gtk_notebook_set_scrollable (GTK_NOTEBOOK (g.notebook), TRUE);
|
||||||
|
gtk_notebook_set_action_widget
|
||||||
|
(GTK_NOTEBOOK (g.notebook), g.hamburger, GTK_PACK_END);
|
||||||
|
|
||||||
|
g.entry = gtk_search_entry_new ();
|
||||||
|
g_signal_connect (g.entry, "changed", G_CALLBACK (on_changed), g.view);
|
||||||
|
|
||||||
|
g.window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
|
||||||
|
gtk_window_set_title (GTK_WINDOW (g.window), PROJECT_NAME);
|
||||||
|
gtk_window_set_default_size (GTK_WINDOW (g.window), 300, 600);
|
||||||
|
g_signal_connect (g.window, "destroy",
|
||||||
|
G_CALLBACK (on_destroy), NULL);
|
||||||
|
g_signal_connect (g.window, "key-press-event",
|
||||||
|
G_CALLBACK (on_key_press), NULL);
|
||||||
|
|
||||||
|
GtkWidget *superbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 1);
|
||||||
|
gtk_container_add (GTK_CONTAINER (g.window), superbox);
|
||||||
|
gtk_container_add (GTK_CONTAINER (superbox), g.notebook);
|
||||||
|
gtk_container_add (GTK_CONTAINER (superbox), g.entry);
|
||||||
|
gtk_container_add (GTK_CONTAINER (superbox),
|
||||||
|
gtk_separator_new (GTK_ORIENTATION_HORIZONTAL));
|
||||||
|
|
||||||
|
g.view = stardict_view_new ();
|
||||||
|
gtk_box_pack_end (GTK_BOX (superbox), g.view, TRUE, TRUE, 0);
|
||||||
|
|
||||||
|
GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
|
||||||
|
g_signal_connect (clipboard, "owner-change",
|
||||||
|
G_CALLBACK (on_selection), NULL);
|
||||||
|
|
||||||
|
gtk_drag_dest_set (g.view,
|
||||||
|
GTK_DEST_DEFAULT_ALL, NULL, 0, GDK_ACTION_COPY);
|
||||||
|
gtk_drag_dest_add_uri_targets (g.view);
|
||||||
|
g_signal_connect (g.view, "drag-data-received",
|
||||||
|
G_CALLBACK (on_drag_data_received), NULL);
|
||||||
|
g_signal_connect (g.view, "send",
|
||||||
|
G_CALLBACK (on_send), NULL);
|
||||||
|
|
||||||
|
if (!reload_dictionaries (new_dictionaries, &error))
|
||||||
|
die_with_dialog (error->message);
|
||||||
|
|
||||||
|
gtk_widget_show_all (g.window);
|
||||||
|
gtk_main ();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
313
src/tdv-query-tool.c
Normal file
313
src/tdv-query-tool.c
Normal file
@@ -0,0 +1,313 @@
|
|||||||
|
/*
|
||||||
|
* A tool to query multiple dictionaries for the specified word
|
||||||
|
*
|
||||||
|
* Intended for use in IRC bots and similar silly things---words go in,
|
||||||
|
* one per each line, and entries come out, one dictionary at a time,
|
||||||
|
* finalised with an empty line. Newlines are escaped with `\n',
|
||||||
|
* backslashes with `\\'.
|
||||||
|
*
|
||||||
|
* So far only the `m', `g`, and `x` fields are supported, as in tdv.
|
||||||
|
*
|
||||||
|
* Copyright (c) 2013 - 2021, Přemysl Eric Janouch <p@janouch.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
#include <pango/pango.h>
|
||||||
|
|
||||||
|
#include "stardict.h"
|
||||||
|
#include "stardict-private.h"
|
||||||
|
#include "generator.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
|
||||||
|
// --- Output formatting -------------------------------------------------------
|
||||||
|
|
||||||
|
/// Transform Pango attributes to in-line formatting sequences (non-reentrant)
|
||||||
|
typedef const gchar *(*FormatterFunc) (PangoAttrIterator *);
|
||||||
|
|
||||||
|
static const gchar *
|
||||||
|
pango_attrs_ignore (G_GNUC_UNUSED PangoAttrIterator *iterator)
|
||||||
|
{
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static const gchar *
|
||||||
|
pango_attrs_to_irc (PangoAttrIterator *iterator)
|
||||||
|
{
|
||||||
|
static gchar buf[5];
|
||||||
|
gchar *p = buf;
|
||||||
|
*p++ = 0x0f;
|
||||||
|
|
||||||
|
if (!iterator)
|
||||||
|
goto reset_formatting;
|
||||||
|
|
||||||
|
PangoAttrInt *attr = NULL;
|
||||||
|
if ((attr = (PangoAttrInt *) pango_attr_iterator_get (iterator,
|
||||||
|
PANGO_ATTR_WEIGHT)) && attr->value >= PANGO_WEIGHT_BOLD)
|
||||||
|
*p++ = 0x02;
|
||||||
|
if ((attr = (PangoAttrInt *) pango_attr_iterator_get (iterator,
|
||||||
|
PANGO_ATTR_UNDERLINE)) && attr->value == PANGO_UNDERLINE_SINGLE)
|
||||||
|
*p++ = 0x1f;
|
||||||
|
if ((attr = (PangoAttrInt *) pango_attr_iterator_get (iterator,
|
||||||
|
PANGO_ATTR_STYLE)) && attr->value == PANGO_STYLE_ITALIC)
|
||||||
|
*p++ = 0x1d;
|
||||||
|
|
||||||
|
reset_formatting:
|
||||||
|
*p++ = 0;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const gchar *
|
||||||
|
pango_attrs_to_ansi (PangoAttrIterator *iterator)
|
||||||
|
{
|
||||||
|
static gchar buf[16];
|
||||||
|
g_strlcpy (buf, "\x1b[0", sizeof buf);
|
||||||
|
if (!iterator)
|
||||||
|
goto reset_formatting;
|
||||||
|
|
||||||
|
PangoAttrInt *attr = NULL;
|
||||||
|
if ((attr = (PangoAttrInt *) pango_attr_iterator_get (iterator,
|
||||||
|
PANGO_ATTR_WEIGHT)) && attr->value >= PANGO_WEIGHT_BOLD)
|
||||||
|
g_strlcat (buf, ";1", sizeof buf);
|
||||||
|
if ((attr = (PangoAttrInt *) pango_attr_iterator_get (iterator,
|
||||||
|
PANGO_ATTR_UNDERLINE)) && attr->value == PANGO_UNDERLINE_SINGLE)
|
||||||
|
g_strlcat (buf, ";4", sizeof buf);
|
||||||
|
if ((attr = (PangoAttrInt *) pango_attr_iterator_get (iterator,
|
||||||
|
PANGO_ATTR_STYLE)) && attr->value == PANGO_STYLE_ITALIC)
|
||||||
|
g_strlcat (buf, ";3", sizeof buf);
|
||||||
|
|
||||||
|
reset_formatting:
|
||||||
|
g_strlcat (buf, "m", sizeof buf);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gchar *
|
||||||
|
pango_to_output_text (const gchar *markup, FormatterFunc formatter)
|
||||||
|
{
|
||||||
|
// This function skips leading whitespace, but it's the canonical one
|
||||||
|
gchar *text = NULL;
|
||||||
|
PangoAttrList *attrs = NULL;
|
||||||
|
if (!pango_parse_markup (markup, -1, 0, &attrs, &text, NULL, NULL))
|
||||||
|
return g_strdup_printf ("<%s>", ("error in entry"));
|
||||||
|
|
||||||
|
PangoAttrIterator *iterator = pango_attr_list_get_iterator (attrs);
|
||||||
|
GString *result = g_string_new ("");
|
||||||
|
do
|
||||||
|
{
|
||||||
|
gint start = 0, end = 0;
|
||||||
|
pango_attr_iterator_range (iterator, &start, &end);
|
||||||
|
if (end == G_MAXINT)
|
||||||
|
end = strlen (text);
|
||||||
|
|
||||||
|
g_string_append (result, formatter (iterator));
|
||||||
|
g_string_append_len (result, text + start, end - start);
|
||||||
|
}
|
||||||
|
while (pango_attr_iterator_next (iterator));
|
||||||
|
g_string_append (result, formatter (NULL));
|
||||||
|
|
||||||
|
g_free (text);
|
||||||
|
pango_attr_iterator_destroy (iterator);
|
||||||
|
pango_attr_list_unref (attrs);
|
||||||
|
return g_string_free (result, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gchar *
|
||||||
|
field_to_output_text (const StardictEntryField *field, FormatterFunc formatter)
|
||||||
|
{
|
||||||
|
const gchar *definition = field->data;
|
||||||
|
if (field->type == STARDICT_FIELD_MEANING)
|
||||||
|
return g_strdup (definition);
|
||||||
|
if (field->type == STARDICT_FIELD_PANGO)
|
||||||
|
return pango_to_output_text (definition, formatter);
|
||||||
|
if (field->type == STARDICT_FIELD_XDXF)
|
||||||
|
{
|
||||||
|
gchar *markup = xdxf_to_pango_markup_with_reduced_effort (definition);
|
||||||
|
gchar *result = pango_to_output_text (markup, formatter);
|
||||||
|
g_free (markup);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Main --------------------------------------------------------------------
|
||||||
|
|
||||||
|
static guint
|
||||||
|
count_equal_chars (const gchar *a, const gchar *b)
|
||||||
|
{
|
||||||
|
guint count = 0;
|
||||||
|
while (*a && *b)
|
||||||
|
if (*a++ == *b++)
|
||||||
|
count++;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
do_dictionary (StardictDict *dict, const gchar *word, FormatterFunc formatter)
|
||||||
|
{
|
||||||
|
gboolean found;
|
||||||
|
StardictIterator *iter = stardict_dict_search (dict, word, &found);
|
||||||
|
if (!found)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
// Default Stardict ordering is ASCII case-insensitive,
|
||||||
|
// which may be further exacerbated by our own collation feature.
|
||||||
|
// Try to find a better matching entry:
|
||||||
|
|
||||||
|
gint64 best_offset = stardict_iterator_get_offset (iter);
|
||||||
|
guint best_score = count_equal_chars
|
||||||
|
(stardict_iterator_get_word (iter), word);
|
||||||
|
|
||||||
|
while (TRUE)
|
||||||
|
{
|
||||||
|
stardict_iterator_next (iter);
|
||||||
|
if (!stardict_iterator_is_valid (iter))
|
||||||
|
break;
|
||||||
|
|
||||||
|
const gchar *iter_word = stardict_iterator_get_word (iter);
|
||||||
|
if (g_ascii_strcasecmp (iter_word, word))
|
||||||
|
break;
|
||||||
|
|
||||||
|
guint score = count_equal_chars (iter_word, word);
|
||||||
|
if (score > best_score)
|
||||||
|
{
|
||||||
|
best_offset = stardict_iterator_get_offset (iter);
|
||||||
|
best_score = score;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stardict_iterator_set_offset (iter, best_offset, FALSE);
|
||||||
|
|
||||||
|
StardictEntry *entry = stardict_iterator_get_entry (iter);
|
||||||
|
StardictInfo *info = stardict_dict_get_info (dict);
|
||||||
|
const GList *list = stardict_entry_get_fields (entry);
|
||||||
|
for (; list; list = list->next)
|
||||||
|
{
|
||||||
|
StardictEntryField *field = list->data;
|
||||||
|
gchar *definitions = field_to_output_text (field, formatter);
|
||||||
|
if (!definitions)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
printf ("%s\t", info->book_name);
|
||||||
|
for (const gchar *p = definitions; *p; p++)
|
||||||
|
{
|
||||||
|
if (*p == '\\')
|
||||||
|
printf ("\\\\");
|
||||||
|
else if (*p == '\n')
|
||||||
|
printf ("\\n");
|
||||||
|
else
|
||||||
|
putchar (*p);
|
||||||
|
}
|
||||||
|
putchar ('\n');
|
||||||
|
g_free (definitions);
|
||||||
|
}
|
||||||
|
g_object_unref (entry);
|
||||||
|
out:
|
||||||
|
g_object_unref (iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
static FormatterFunc
|
||||||
|
parse_options (int *argc, char ***argv)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
GOptionContext *ctx = g_option_context_new
|
||||||
|
("DICTIONARY.ifo... - query multiple dictionaries");
|
||||||
|
|
||||||
|
gboolean format_with_ansi = FALSE;
|
||||||
|
gboolean format_with_irc = FALSE;
|
||||||
|
GOptionEntry entries[] =
|
||||||
|
{
|
||||||
|
{ "ansi", 'a', 0, G_OPTION_ARG_NONE, &format_with_ansi,
|
||||||
|
"Format with ANSI sequences", NULL },
|
||||||
|
{ "irc", 'i', 0, G_OPTION_ARG_NONE, &format_with_irc,
|
||||||
|
"Format with IRC codes", NULL },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
g_option_context_add_main_entries (ctx, entries, NULL);
|
||||||
|
if (!g_option_context_parse (ctx, argc, argv, &error))
|
||||||
|
{
|
||||||
|
g_printerr ("Error: option parsing failed: %s\n", error->message);
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
if (*argc < 2)
|
||||||
|
{
|
||||||
|
g_printerr ("%s\n", g_option_context_get_help (ctx, TRUE, NULL));
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
g_option_context_free (ctx);
|
||||||
|
|
||||||
|
if (format_with_ansi)
|
||||||
|
return pango_attrs_to_ansi;
|
||||||
|
if (format_with_irc)
|
||||||
|
return pango_attrs_to_irc;
|
||||||
|
|
||||||
|
return pango_attrs_ignore;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc, char *argv[])
|
||||||
|
{
|
||||||
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS
|
||||||
|
if (glib_check_version (2, 36, 0))
|
||||||
|
g_type_init ();
|
||||||
|
G_GNUC_END_IGNORE_DEPRECATIONS
|
||||||
|
|
||||||
|
FormatterFunc formatter = parse_options (&argc, &argv);
|
||||||
|
|
||||||
|
guint n_dicts = argc - 1;
|
||||||
|
StardictDict **dicts = g_alloca (sizeof *dicts * n_dicts);
|
||||||
|
|
||||||
|
guint i;
|
||||||
|
for (i = 1; i <= n_dicts; i++)
|
||||||
|
{
|
||||||
|
GError *error = NULL;
|
||||||
|
dicts[i - 1] = stardict_dict_new (argv[i], &error);
|
||||||
|
if (error)
|
||||||
|
{
|
||||||
|
g_printerr ("Error: opening dictionary `%s' failed: %s\n",
|
||||||
|
argv[i], error->message);
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
gint c;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
GString *s = g_string_new (NULL);
|
||||||
|
while ((c = getchar ()) != EOF && c != '\n')
|
||||||
|
if (c != '\r')
|
||||||
|
g_string_append_c (s, c);
|
||||||
|
|
||||||
|
if (s->len)
|
||||||
|
for (i = 0; i < n_dicts; i++)
|
||||||
|
do_dictionary (dicts[i], s->str, formatter);
|
||||||
|
|
||||||
|
printf ("\n");
|
||||||
|
fflush (NULL);
|
||||||
|
g_string_free (s, TRUE);
|
||||||
|
}
|
||||||
|
while (c != EOF);
|
||||||
|
|
||||||
|
for (i = 0; i < n_dicts; i++)
|
||||||
|
g_object_unref (dicts[i]);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
223
src/tdv-tabfile.c
Normal file
223
src/tdv-tabfile.c
Normal file
@@ -0,0 +1,223 @@
|
|||||||
|
/*
|
||||||
|
* A clean reimplementation of StarDict's tabfile
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020 - 2021, Přemysl Eric Janouch <p@janouch.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <locale.h>
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
#include <pango/pango.h>
|
||||||
|
|
||||||
|
#include <unicode/ucol.h>
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "stardict.h"
|
||||||
|
#include "stardict-private.h"
|
||||||
|
#include "generator.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
set_data_error (GError **error, const gchar *message)
|
||||||
|
{
|
||||||
|
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, message);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const gchar escapes[256] = { ['n'] = '\n', ['t'] = '\t', ['\\'] = '\\' };
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
inplace_unescape (gchar *line, GError **error)
|
||||||
|
{
|
||||||
|
gboolean escape = FALSE;
|
||||||
|
gchar *dest = line;
|
||||||
|
for (gchar *src = line; *src; src++)
|
||||||
|
{
|
||||||
|
if (escape)
|
||||||
|
{
|
||||||
|
escape = FALSE;
|
||||||
|
if (!(*dest++ = escapes[(guchar) *src]))
|
||||||
|
return set_data_error (error, "unsupported escape");
|
||||||
|
}
|
||||||
|
else if (*src == '\\')
|
||||||
|
escape = TRUE;
|
||||||
|
else
|
||||||
|
*dest++ = *src;
|
||||||
|
}
|
||||||
|
if (escape)
|
||||||
|
return set_data_error (error, "trailing escape character");
|
||||||
|
|
||||||
|
*dest = 0;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
import_line (Generator *generator, gchar *line, gsize len, GError **error)
|
||||||
|
{
|
||||||
|
if (!len)
|
||||||
|
return TRUE;
|
||||||
|
if (!g_utf8_validate_len (line, len, NULL))
|
||||||
|
return set_data_error (error, "not valid UTF-8");
|
||||||
|
|
||||||
|
gchar *separator = strchr (line, '\t');
|
||||||
|
if (!separator)
|
||||||
|
return set_data_error (error, "keyword separator not found");
|
||||||
|
|
||||||
|
*separator++ = 0;
|
||||||
|
if (strchr (line, '\\'))
|
||||||
|
// The index wouldn't be sorted correctly with our method
|
||||||
|
return set_data_error (error, "escapes not allowed in keywords");
|
||||||
|
|
||||||
|
gchar *newline = strpbrk (separator, "\r\n");
|
||||||
|
if (newline)
|
||||||
|
*newline = 0;
|
||||||
|
|
||||||
|
if (!inplace_unescape (line, error)
|
||||||
|
|| !inplace_unescape (separator, error))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (generator->info->same_type_sequence
|
||||||
|
&& *generator->info->same_type_sequence == STARDICT_FIELD_PANGO
|
||||||
|
&& !pango_parse_markup (separator, -1, 0, NULL, NULL, NULL, error))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
generator_begin_entry (generator);
|
||||||
|
return generator_write_string (generator, separator, TRUE, error)
|
||||||
|
&& generator_finish_entry (generator, line, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
transform (FILE *fsorted, Generator *generator, GError **error)
|
||||||
|
{
|
||||||
|
gchar *line = NULL;
|
||||||
|
gsize size = 0, ln = 1;
|
||||||
|
for (ssize_t read; (read = getline (&line, &size, fsorted)) >= 0; ln++)
|
||||||
|
if (!import_line (generator, line, read, error))
|
||||||
|
break;
|
||||||
|
|
||||||
|
free (line);
|
||||||
|
if (ferror (fsorted))
|
||||||
|
{
|
||||||
|
g_set_error_literal (error, G_IO_ERROR,
|
||||||
|
g_io_error_from_errno (errno), g_strerror (errno));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
if (!feof (fsorted))
|
||||||
|
{
|
||||||
|
// You'll only get good line number output with presorted input!
|
||||||
|
g_prefix_error (error, "line %zu: ", ln);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
validate_collation_locale (const gchar *locale)
|
||||||
|
{
|
||||||
|
UErrorCode error = U_ZERO_ERROR;
|
||||||
|
UCollator *collator = ucol_open (locale, &error);
|
||||||
|
if (!collator)
|
||||||
|
fatal ("failed to create a collator for %s: %s\n",
|
||||||
|
locale, u_errorName (error));
|
||||||
|
ucol_close (collator);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc, char *argv[])
|
||||||
|
{
|
||||||
|
// The GLib help includes an ellipsis character, for some reason
|
||||||
|
(void) setlocale (LC_ALL, "");
|
||||||
|
|
||||||
|
GError *error = NULL;
|
||||||
|
GOptionContext *ctx = g_option_context_new ("output-basename < input");
|
||||||
|
g_option_context_set_summary (ctx,
|
||||||
|
"Create a StarDict dictionary from plaintext.");
|
||||||
|
|
||||||
|
gboolean pango_markup = FALSE;
|
||||||
|
StardictInfo template = {};
|
||||||
|
GOptionEntry entries[] =
|
||||||
|
{
|
||||||
|
{ "pango", 'p', 0, G_OPTION_ARG_NONE, &pango_markup,
|
||||||
|
"Entries use Pango markup", NULL },
|
||||||
|
|
||||||
|
{ "book-name", 'b', 0, G_OPTION_ARG_STRING, &template.book_name,
|
||||||
|
"Set the book name field", "TEXT" },
|
||||||
|
{ "author", 'a', 0, G_OPTION_ARG_STRING, &template.author,
|
||||||
|
"Set the author field ", "NAME" },
|
||||||
|
{ "e-mail", 'e', 0, G_OPTION_ARG_STRING, &template.email,
|
||||||
|
"Set the e-mail field", "ADDRESS" },
|
||||||
|
{ "website", 'w', 0, G_OPTION_ARG_STRING, &template.website,
|
||||||
|
"Set the website field", "LINK" },
|
||||||
|
{ "description", 'd', 0, G_OPTION_ARG_STRING, &template.description,
|
||||||
|
"Set the description field (newlines supported)", "TEXT" },
|
||||||
|
{ "date", 'D', 0, G_OPTION_ARG_STRING, &template.date,
|
||||||
|
"Set the date field", "DATE" },
|
||||||
|
{ "collation", 'c', 0, G_OPTION_ARG_STRING, &template.collation,
|
||||||
|
"Set the collation field (for ICU)", "LOCALE" },
|
||||||
|
{ }
|
||||||
|
};
|
||||||
|
|
||||||
|
g_option_context_add_main_entries (ctx, entries, GETTEXT_PACKAGE);
|
||||||
|
if (!g_option_context_parse (ctx, &argc, &argv, &error))
|
||||||
|
fatal ("Error: option parsing failed: %s\n", error->message);
|
||||||
|
if (argc != 2)
|
||||||
|
fatal ("%s", g_option_context_get_help (ctx, TRUE, NULL));
|
||||||
|
g_option_context_free (ctx);
|
||||||
|
|
||||||
|
template.version = SD_VERSION_3_0_0;
|
||||||
|
template.same_type_sequence = pango_markup
|
||||||
|
? (char[]) { STARDICT_FIELD_PANGO, 0 }
|
||||||
|
: (char[]) { STARDICT_FIELD_MEANING, 0 };
|
||||||
|
|
||||||
|
if (!template.book_name)
|
||||||
|
template.book_name = argv[1];
|
||||||
|
if (template.description)
|
||||||
|
{
|
||||||
|
gchar **lines = g_strsplit (template.description, "\n", -1);
|
||||||
|
g_free (template.description);
|
||||||
|
gchar *in_one_line = g_strjoinv ("<br>", lines);
|
||||||
|
g_strfreev (lines);
|
||||||
|
template.description = in_one_line;
|
||||||
|
}
|
||||||
|
if (template.collation)
|
||||||
|
validate_collation_locale (template.collation);
|
||||||
|
|
||||||
|
// This actually implements stardict_strcmp(), POSIX-compatibly.
|
||||||
|
// Your sort(1) is not expected to be stable by default, like bsdsort is.
|
||||||
|
FILE *fsorted = popen ("LC_ALL=C sort -t'\t' -k1f,1", "r");
|
||||||
|
if (!fsorted)
|
||||||
|
fatal ("%s: %s\n", "popen", g_strerror (errno));
|
||||||
|
|
||||||
|
Generator *generator = generator_new (argv[1], &error);
|
||||||
|
if (!generator)
|
||||||
|
fatal ("Error: failed to create the output dictionary: %s\n",
|
||||||
|
error->message);
|
||||||
|
|
||||||
|
StardictInfo *info = generator->info;
|
||||||
|
stardict_info_copy (info, &template);
|
||||||
|
if (!transform (fsorted, generator, &error)
|
||||||
|
|| !generator_finish (generator, &error))
|
||||||
|
fatal ("Error: failed to write the dictionary: %s\n", error->message);
|
||||||
|
|
||||||
|
generator_free (generator);
|
||||||
|
pclose (fsorted);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
226
src/tdv-transform.c
Normal file
226
src/tdv-transform.c
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
/*
|
||||||
|
* A tool to transform dictionaries dictionaries by an external filter
|
||||||
|
*
|
||||||
|
* The external filter needs to process NUL-separated textual entries.
|
||||||
|
*
|
||||||
|
* Example: tdv-transform input.ifo output -- perl -p0e s/bullshit/soykaf/g
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020, Přemysl Eric Janouch <p@janouch.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <locale.h>
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <glib/gstdio.h>
|
||||||
|
#include <glib-unix.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
#include "stardict.h"
|
||||||
|
#include "stardict-private.h"
|
||||||
|
#include "generator.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
enum { PIPE_READ, PIPE_WRITE };
|
||||||
|
|
||||||
|
|
||||||
|
// --- Main --------------------------------------------------------------------
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
print_progress (gulong *last_percent, StardictIterator *iterator, gsize total)
|
||||||
|
{
|
||||||
|
gulong percent =
|
||||||
|
(gulong) stardict_iterator_get_offset (iterator) * 100 / total;
|
||||||
|
if (percent != *last_percent)
|
||||||
|
{
|
||||||
|
printf ("\r Writing entries... %3lu%%", percent);
|
||||||
|
*last_percent = percent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
write_to_filter (StardictDict *dict, gint fd, GError **error)
|
||||||
|
{
|
||||||
|
StardictInfo *info = stardict_dict_get_info (dict);
|
||||||
|
gsize n_words = stardict_info_get_word_count (info);
|
||||||
|
|
||||||
|
StardictIterator *iterator = stardict_iterator_new (dict, 0);
|
||||||
|
gulong last_percent = -1;
|
||||||
|
while (stardict_iterator_is_valid (iterator))
|
||||||
|
{
|
||||||
|
print_progress (&last_percent, iterator, n_words);
|
||||||
|
|
||||||
|
StardictEntry *entry = stardict_iterator_get_entry (iterator);
|
||||||
|
for (const GList *fields = stardict_entry_get_fields (entry);
|
||||||
|
fields; fields = fields->next)
|
||||||
|
{
|
||||||
|
StardictEntryField *field = fields->data;
|
||||||
|
if (!g_ascii_islower (field->type))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (write (fd, field->data, field->data_size)
|
||||||
|
!= (ssize_t) field->data_size)
|
||||||
|
{
|
||||||
|
g_set_error (error, G_IO_ERROR, g_io_error_from_errno (errno),
|
||||||
|
"%s", g_strerror (errno));
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
g_object_unref (entry);
|
||||||
|
stardict_iterator_next (iterator);
|
||||||
|
}
|
||||||
|
printf ("\n");
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
update_from_filter (StardictDict *dict, Generator *generator,
|
||||||
|
GMappedFile *filtered_file, GError **error)
|
||||||
|
{
|
||||||
|
gchar *filtered = g_mapped_file_get_contents (filtered_file);
|
||||||
|
gchar *filtered_end = filtered + g_mapped_file_get_length (filtered_file);
|
||||||
|
|
||||||
|
StardictInfo *info = stardict_dict_get_info (dict);
|
||||||
|
gsize n_words = stardict_info_get_word_count (info);
|
||||||
|
|
||||||
|
StardictIterator *iterator = stardict_iterator_new (dict, 0);
|
||||||
|
gulong last_percent = -1;
|
||||||
|
while (stardict_iterator_is_valid (iterator))
|
||||||
|
{
|
||||||
|
print_progress (&last_percent, iterator, n_words);
|
||||||
|
|
||||||
|
StardictEntry *entry = stardict_iterator_get_entry (iterator);
|
||||||
|
generator_begin_entry (generator);
|
||||||
|
|
||||||
|
for (GList *fields = entry->fields; fields; fields = fields->next)
|
||||||
|
{
|
||||||
|
StardictEntryField *field = fields->data;
|
||||||
|
if (!g_ascii_islower (field->type))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
gchar *end = memchr (filtered, 0, filtered_end - filtered);
|
||||||
|
if (!end)
|
||||||
|
{
|
||||||
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_PARTIAL_INPUT,
|
||||||
|
"filter seems to have ended too early");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_free (field->data);
|
||||||
|
field->data = g_strdup (filtered);
|
||||||
|
field->data_size = end - filtered + 1;
|
||||||
|
filtered = end + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!generator_write_fields (generator, entry->fields, error)
|
||||||
|
|| !generator_finish_entry (generator,
|
||||||
|
stardict_iterator_get_word (iterator), error))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
g_object_unref (entry);
|
||||||
|
stardict_iterator_next (iterator);
|
||||||
|
}
|
||||||
|
printf ("\n");
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc, char *argv[])
|
||||||
|
{
|
||||||
|
// The GLib help includes an ellipsis character, for some reason
|
||||||
|
(void) setlocale (LC_ALL, "");
|
||||||
|
|
||||||
|
GError *error = NULL;
|
||||||
|
GOptionContext *ctx = g_option_context_new
|
||||||
|
("input.ifo output-basename -- FILTER [ARG...]");
|
||||||
|
g_option_context_set_summary
|
||||||
|
(ctx, "Transform dictionaries using a filter program.");
|
||||||
|
if (!g_option_context_parse (ctx, &argc, &argv, &error))
|
||||||
|
fatal ("Error: option parsing failed: %s\n", error->message);
|
||||||
|
|
||||||
|
if (argc < 3)
|
||||||
|
fatal ("%s", g_option_context_get_help (ctx, TRUE, NULL));
|
||||||
|
|
||||||
|
// GLib is bullshit, getopt_long() always correctly removes this
|
||||||
|
gint program_argv_start = 3;
|
||||||
|
if (!strcmp (argv[program_argv_start], "--"))
|
||||||
|
program_argv_start++;
|
||||||
|
|
||||||
|
g_option_context_free (ctx);
|
||||||
|
|
||||||
|
printf ("Loading the original dictionary...\n");
|
||||||
|
StardictDict *dict = stardict_dict_new (argv[1], &error);
|
||||||
|
if (!dict)
|
||||||
|
fatal ("Error: opening the dictionary failed: %s\n", error->message);
|
||||||
|
|
||||||
|
printf ("Filtering entries...\n");
|
||||||
|
gint child_in[2];
|
||||||
|
if (!g_unix_open_pipe (child_in, 0, &error))
|
||||||
|
fatal ("g_unix_open_pipe: %s\n", error->message);
|
||||||
|
|
||||||
|
FILE *child_out = tmpfile ();
|
||||||
|
if (!child_out)
|
||||||
|
fatal ("tmpfile: %s\n", g_strerror (errno));
|
||||||
|
|
||||||
|
GPid pid = -1;
|
||||||
|
if (!g_spawn_async_with_fds (NULL /* working_directory */,
|
||||||
|
argv + program_argv_start /* forward a part of ours */, NULL /* envp */,
|
||||||
|
G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
|
||||||
|
NULL /* child_setup */, NULL /* user_data */,
|
||||||
|
&pid, child_in[PIPE_READ], fileno (child_out), STDERR_FILENO, &error))
|
||||||
|
fatal ("g_spawn: %s\n", error->message);
|
||||||
|
if (!write_to_filter (dict, child_in[PIPE_WRITE], &error))
|
||||||
|
fatal ("write_to_filter: %s\n", error->message);
|
||||||
|
if (!g_close (child_in[PIPE_READ], &error)
|
||||||
|
|| !g_close (child_in[PIPE_WRITE], &error))
|
||||||
|
fatal ("g_close: %s\n", error->message);
|
||||||
|
|
||||||
|
printf ("Waiting for the filter to finish...\n");
|
||||||
|
int wstatus = errno = 0;
|
||||||
|
if (waitpid (pid, &wstatus, 0) < 1
|
||||||
|
|| !WIFEXITED (wstatus) || WEXITSTATUS (wstatus) > 0)
|
||||||
|
fatal ("Filter failed (%s, status %d)\n", g_strerror (errno), wstatus);
|
||||||
|
|
||||||
|
GMappedFile *filtered = g_mapped_file_new_from_fd (fileno (child_out),
|
||||||
|
FALSE /* writable */, &error);
|
||||||
|
if (!filtered)
|
||||||
|
fatal ("g_mapped_file_new_from_fd: %s\n", error->message);
|
||||||
|
|
||||||
|
printf ("Writing the new dictionary...\n");
|
||||||
|
Generator *generator = generator_new (argv[2], &error);
|
||||||
|
if (!generator)
|
||||||
|
fatal ("Error: failed to create the output dictionary: %s\n",
|
||||||
|
error->message);
|
||||||
|
|
||||||
|
StardictInfo *info = generator->info;
|
||||||
|
stardict_info_copy (info, stardict_dict_get_info (dict));
|
||||||
|
|
||||||
|
// This gets incremented each time an entry is finished
|
||||||
|
info->word_count = 0;
|
||||||
|
|
||||||
|
if (!update_from_filter (dict, generator, filtered, &error)
|
||||||
|
|| !generator_finish (generator, &error))
|
||||||
|
fatal ("Error: failed to write the dictionary: %s\n", error->message);
|
||||||
|
|
||||||
|
g_mapped_file_unref (filtered);
|
||||||
|
fclose (child_out);
|
||||||
|
generator_free (generator);
|
||||||
|
g_object_unref (dict);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
98
src/tdv.c
Normal file
98
src/tdv.c
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/*
|
||||||
|
* Translation dictionary viewer
|
||||||
|
*
|
||||||
|
* Copyright (c) 2023, Přemysl Eric Janouch <p@janouch.name>
|
||||||
|
*
|
||||||
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
* purpose with or without fee is hereby granted.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
|
||||||
|
* SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
|
||||||
|
* OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
|
||||||
|
* CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <glib/gi18n.h>
|
||||||
|
#ifdef WITH_GUI
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <locale.h>
|
||||||
|
#ifndef G_OS_WIN32
|
||||||
|
#include <unistd.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int tui_main (char *[]);
|
||||||
|
int gui_main (char *[]);
|
||||||
|
|
||||||
|
int
|
||||||
|
main (int argc, char *argv[])
|
||||||
|
{
|
||||||
|
if (!setlocale (LC_ALL, ""))
|
||||||
|
g_printerr ("%s: %s\n", _("Warning"), _("failed to set the locale"));
|
||||||
|
|
||||||
|
bindtextdomain (GETTEXT_PACKAGE, GETTEXT_DIRNAME);
|
||||||
|
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
|
||||||
|
textdomain (GETTEXT_PACKAGE);
|
||||||
|
|
||||||
|
gboolean show_version = FALSE;
|
||||||
|
#ifdef WITH_GUI
|
||||||
|
# ifndef G_OS_WIN32
|
||||||
|
gboolean gui = FALSE;
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
GOptionEntry entries[] =
|
||||||
|
{
|
||||||
|
{ "version", 0, G_OPTION_FLAG_IN_MAIN,
|
||||||
|
G_OPTION_ARG_NONE, &show_version,
|
||||||
|
N_("Output version information and exit"), NULL },
|
||||||
|
#ifdef WITH_GUI
|
||||||
|
# ifndef G_OS_WIN32
|
||||||
|
{ "gui", 0, G_OPTION_FLAG_IN_MAIN,
|
||||||
|
G_OPTION_ARG_NONE, &gui,
|
||||||
|
N_("Launch the GUI even when run from a terminal"), NULL },
|
||||||
|
# endif
|
||||||
|
#endif
|
||||||
|
{ },
|
||||||
|
};
|
||||||
|
|
||||||
|
GOptionContext *ctx = g_option_context_new
|
||||||
|
(N_("[dictionary.ifo...] - Translation dictionary viewer"));
|
||||||
|
g_option_context_add_main_entries (ctx, entries, GETTEXT_PACKAGE);
|
||||||
|
#ifdef WITH_GUI
|
||||||
|
g_option_context_add_group (ctx, gtk_get_option_group (FALSE));
|
||||||
|
#endif
|
||||||
|
g_option_context_set_translation_domain (ctx, GETTEXT_PACKAGE);
|
||||||
|
|
||||||
|
GError *error = NULL;
|
||||||
|
if (!g_option_context_parse (ctx, &argc, &argv, &error))
|
||||||
|
{
|
||||||
|
g_printerr ("%s: %s: %s\n", _("Error"), _("option parsing failed"),
|
||||||
|
error->message);
|
||||||
|
exit (EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
g_option_context_free (ctx);
|
||||||
|
|
||||||
|
if (show_version)
|
||||||
|
{
|
||||||
|
g_print (PROJECT_NAME " " PROJECT_VERSION "\n");
|
||||||
|
exit (EXIT_SUCCESS);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef WITH_GUI
|
||||||
|
# ifndef G_OS_WIN32
|
||||||
|
if (gui || !isatty (STDIN_FILENO))
|
||||||
|
# endif
|
||||||
|
return gui_main (argv + 1);
|
||||||
|
#endif
|
||||||
|
#ifndef G_OS_WIN32
|
||||||
|
return tui_main (argv + 1);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
276
src/utils.c
276
src/utils.c
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* utils.c: miscellaneous utilities
|
* utils.c: miscellaneous utilities
|
||||||
*
|
*
|
||||||
* Copyright (c) 2013 - 2015, Přemysl Eric Janouch <p@janouch.name>
|
* Copyright (c) 2013 - 2021, Přemysl Eric Janouch <p@janouch.name>
|
||||||
*
|
*
|
||||||
* Permission to use, copy, modify, and/or distribute this software for any
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted.
|
* purpose with or without fee is hereby granted.
|
||||||
@@ -16,21 +16,49 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
// getpwnam_r, _SC_GETPW_R_SIZE_MAX
|
||||||
|
#ifndef _POSIX_C_SOURCE
|
||||||
|
#define _POSIX_C_SOURCE 200112L
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <glib.h>
|
#include <glib.h>
|
||||||
|
#include <glib/gprintf.h>
|
||||||
#include <gio/gio.h>
|
#include <gio/gio.h>
|
||||||
|
#include <glib/gstdio.h>
|
||||||
|
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
|
||||||
#include <curses.h>
|
#ifndef WIN32
|
||||||
#include <termios.h>
|
#include <pwd.h>
|
||||||
#ifndef TIOCGWINSZ
|
#endif // ! WIN32
|
||||||
#include <sys/ioctl.h>
|
|
||||||
#endif // ! TIOCGWINSZ
|
|
||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
|
||||||
|
/// Trivially filter out all tags that aren't part of the Pango markup language,
|
||||||
|
/// or no frontend can quite handle--this seems to work well.
|
||||||
|
/// Given the nature of our display, also skip whole keyword elements.
|
||||||
|
gchar *
|
||||||
|
xdxf_to_pango_markup_with_reduced_effort (const gchar *xml)
|
||||||
|
{
|
||||||
|
GString *filtered = g_string_new ("");
|
||||||
|
while (*xml)
|
||||||
|
{
|
||||||
|
// GMarkup can read some of the wilder XML constructs, Pango skips them
|
||||||
|
const gchar *p = NULL;
|
||||||
|
if (*xml != '<' || xml[1] == '!' || xml[1] == '?'
|
||||||
|
|| g_ascii_isspace (xml[1]) || !*(p = xml + 1 + (xml[1] == '/'))
|
||||||
|
|| (strchr ("biu", *p) && p[1] == '>') || !(p = strchr (p, '>')))
|
||||||
|
g_string_append_c (filtered, *xml++);
|
||||||
|
else if (xml[1] != 'k' || xml[2] != '>' || !(xml = strstr (p, "</k>")))
|
||||||
|
xml = ++p;
|
||||||
|
}
|
||||||
|
return g_string_free (filtered, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
/// Read the whole stream into a byte array.
|
/// Read the whole stream into a byte array.
|
||||||
gboolean
|
gboolean
|
||||||
stream_read_all (GByteArray *ba, GInputStream *is, GError **error)
|
stream_read_all (GByteArray *ba, GInputStream *is, GError **error)
|
||||||
@@ -78,24 +106,224 @@ xstrtoul (unsigned long *out, const char *s, int base)
|
|||||||
return errno == 0 && !*end && end != s;
|
return errno == 0 && !*end && end != s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Didn't want to have this ugly piece of code in the main source file;
|
/// Print a fatal error message and terminate the process immediately.
|
||||||
// the standard endwin/refresh sequence makes the terminal flicker.
|
|
||||||
void
|
void
|
||||||
update_curses_terminal_size (void)
|
fatal (const gchar *format, ...)
|
||||||
{
|
{
|
||||||
#if defined (HAVE_RESIZETERM) && defined (TIOCGWINSZ)
|
va_list ap;
|
||||||
struct winsize size;
|
va_start (ap, format);
|
||||||
if (!ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &size))
|
g_vfprintf (stderr, format, ap);
|
||||||
{
|
exit (EXIT_FAILURE);
|
||||||
char *row = getenv ("LINES");
|
va_end (ap);
|
||||||
char *col = getenv ("COLUMNS");
|
}
|
||||||
unsigned long tmp;
|
|
||||||
resizeterm (
|
// At times, GLib even with its sheer size is surprisingly useless,
|
||||||
(row && xstrtoul (&tmp, row, 10)) ? tmp : size.ws_row,
|
// and I need to port some code over from "liberty".
|
||||||
(col && xstrtoul (&tmp, col, 10)) ? tmp : size.ws_col);
|
|
||||||
}
|
static const gchar **
|
||||||
#else // HAVE_RESIZETERM && TIOCGWINSZ
|
get_xdg_config_dirs (void)
|
||||||
endwin ();
|
{
|
||||||
refresh ();
|
GPtrArray *paths = g_ptr_array_new ();
|
||||||
#endif // HAVE_RESIZETERM && TIOCGWINSZ
|
g_ptr_array_add (paths, (gpointer) g_get_user_config_dir ());
|
||||||
|
for (const gchar *const *system = g_get_system_config_dirs ();
|
||||||
|
*system; system++)
|
||||||
|
g_ptr_array_add (paths, (gpointer) *system);
|
||||||
|
g_ptr_array_add (paths, NULL);
|
||||||
|
return (const gchar **) g_ptr_array_free (paths, FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
gchar *
|
||||||
|
resolve_relative_filename_generic
|
||||||
|
(const gchar **paths, const gchar *tail, const gchar *filename)
|
||||||
|
{
|
||||||
|
for (; *paths; paths++)
|
||||||
|
{
|
||||||
|
// As per XDG spec, relative paths are ignored
|
||||||
|
if (**paths != '/')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
gchar *file = g_build_filename (*paths, tail, filename, NULL);
|
||||||
|
GStatBuf st;
|
||||||
|
if (!g_stat (file, &st))
|
||||||
|
return file;
|
||||||
|
g_free (file);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
gchar *
|
||||||
|
resolve_relative_config_filename (const gchar *filename)
|
||||||
|
{
|
||||||
|
const gchar **paths = get_xdg_config_dirs ();
|
||||||
|
gchar *result =
|
||||||
|
resolve_relative_filename_generic (paths, PROJECT_NAME, filename);
|
||||||
|
g_free (paths);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gchar *
|
||||||
|
try_expand_tilde (const gchar *filename)
|
||||||
|
{
|
||||||
|
size_t until_slash = strcspn (filename, "/");
|
||||||
|
if (!until_slash)
|
||||||
|
return g_build_filename (g_get_home_dir () ?: "", filename, NULL);
|
||||||
|
|
||||||
|
#ifdef WIN32
|
||||||
|
// TODO: also ensure that path separators are handled sensibly around here
|
||||||
|
return NULL;
|
||||||
|
#else // ! WIN32
|
||||||
|
long buf_len = sysconf (_SC_GETPW_R_SIZE_MAX);
|
||||||
|
if (buf_len < 0)
|
||||||
|
buf_len = 1024;
|
||||||
|
struct passwd pwd, *success = NULL;
|
||||||
|
|
||||||
|
gchar *user = g_strndup (filename, until_slash);
|
||||||
|
gchar *buf = g_malloc (buf_len);
|
||||||
|
while (getpwnam_r (user, &pwd, buf, buf_len, &success) == ERANGE)
|
||||||
|
buf = g_realloc (buf, buf_len <<= 1);
|
||||||
|
g_free (user);
|
||||||
|
|
||||||
|
gchar *result = NULL;
|
||||||
|
if (success)
|
||||||
|
result = g_strdup_printf ("%s%s", pwd.pw_dir, filename + until_slash);
|
||||||
|
g_free (buf);
|
||||||
|
return result;
|
||||||
|
#endif // ! WIN32
|
||||||
|
}
|
||||||
|
|
||||||
|
gchar *
|
||||||
|
resolve_filename (const gchar *filename, gchar *(*relative_cb) (const char *))
|
||||||
|
{
|
||||||
|
// Absolute path is absolute
|
||||||
|
if (*filename == '/')
|
||||||
|
return g_strdup (filename);
|
||||||
|
|
||||||
|
// We don't want to use wordexp() for this as it may execute /bin/sh
|
||||||
|
if (*filename == '~')
|
||||||
|
{
|
||||||
|
// Paths to home directories ought to be absolute
|
||||||
|
char *expanded = try_expand_tilde (filename + 1);
|
||||||
|
if (expanded)
|
||||||
|
return expanded;
|
||||||
|
g_debug ("failed to expand the home directory in `%s'", filename);
|
||||||
|
}
|
||||||
|
return relative_cb (filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
GKeyFile *
|
||||||
|
load_project_config_file (GError **error)
|
||||||
|
{
|
||||||
|
GKeyFile *key_file = g_key_file_new ();
|
||||||
|
const gchar **paths = get_xdg_config_dirs ();
|
||||||
|
GError *e = NULL;
|
||||||
|
|
||||||
|
// XXX: if there are dashes in the final path component,
|
||||||
|
// the function tries to replace them with directory separators,
|
||||||
|
// which is completely undocumented
|
||||||
|
g_key_file_load_from_dirs (key_file,
|
||||||
|
PROJECT_NAME G_DIR_SEPARATOR_S PROJECT_NAME ".conf",
|
||||||
|
paths, NULL, G_KEY_FILE_KEEP_COMMENTS, &e);
|
||||||
|
g_free (paths);
|
||||||
|
if (!e)
|
||||||
|
return key_file;
|
||||||
|
|
||||||
|
if (e->code == G_KEY_FILE_ERROR_NOT_FOUND)
|
||||||
|
g_error_free (e);
|
||||||
|
else
|
||||||
|
g_propagate_error (error, e);
|
||||||
|
|
||||||
|
g_key_file_free (key_file);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
save_project_config_file (GKeyFile *key_file, GError **error)
|
||||||
|
{
|
||||||
|
gchar *dirname =
|
||||||
|
g_build_filename (g_get_user_config_dir (), PROJECT_NAME, NULL);
|
||||||
|
(void) g_mkdir_with_parents (dirname, 0755);
|
||||||
|
gchar *path = g_build_filename (dirname, PROJECT_NAME ".conf", NULL);
|
||||||
|
g_free (dirname);
|
||||||
|
|
||||||
|
gsize length = 0;
|
||||||
|
gchar *data = g_key_file_to_data (key_file, &length, error);
|
||||||
|
if (!data)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
gboolean result = g_file_set_contents (path, data, length, error);
|
||||||
|
g_free (data);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Loading -----------------------------------------------------------------
|
||||||
|
|
||||||
|
void
|
||||||
|
dictionary_destroy (Dictionary *self)
|
||||||
|
{
|
||||||
|
g_free (self->name);
|
||||||
|
g_free (self->filename);
|
||||||
|
|
||||||
|
if (self->dict)
|
||||||
|
g_object_unref (self->dict);
|
||||||
|
|
||||||
|
g_free (self);
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
dictionary_load (Dictionary *self, GError **e)
|
||||||
|
{
|
||||||
|
if (!(self->dict = stardict_dict_new (self->filename, e)))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if (!self->name)
|
||||||
|
{
|
||||||
|
self->name = g_strdup (stardict_info_get_book_name
|
||||||
|
(stardict_dict_get_info (self->dict)));
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static gboolean
|
||||||
|
load_dictionaries_sequentially (GPtrArray *dictionaries, GError **e)
|
||||||
|
{
|
||||||
|
for (guint i = 0; i < dictionaries->len; i++)
|
||||||
|
if (!dictionary_load (g_ptr_array_index (dictionaries, i), e))
|
||||||
|
return FALSE;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parallelize dictionary loading if possible, because of collation reindexing
|
||||||
|
static void
|
||||||
|
load_worker (gpointer data, gpointer user_data)
|
||||||
|
{
|
||||||
|
GError *e = NULL;
|
||||||
|
dictionary_load (data, &e);
|
||||||
|
if (e)
|
||||||
|
g_async_queue_push (user_data, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
gboolean
|
||||||
|
load_dictionaries (GPtrArray *dictionaries, GError **e)
|
||||||
|
{
|
||||||
|
GAsyncQueue *error_queue =
|
||||||
|
g_async_queue_new_full ((GDestroyNotify) g_error_free);
|
||||||
|
GThreadPool *pool = g_thread_pool_new (load_worker, error_queue,
|
||||||
|
g_get_num_processors (), TRUE, NULL);
|
||||||
|
if G_UNLIKELY (!g_thread_pool_get_num_threads (pool))
|
||||||
|
{
|
||||||
|
g_thread_pool_free (pool, TRUE, TRUE);
|
||||||
|
g_async_queue_unref (error_queue);
|
||||||
|
return load_dictionaries_sequentially (dictionaries, e);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (guint i = 0; i < dictionaries->len; i++)
|
||||||
|
g_thread_pool_push (pool, g_ptr_array_index (dictionaries, i), NULL);
|
||||||
|
g_thread_pool_free (pool, FALSE, TRUE);
|
||||||
|
|
||||||
|
gboolean result = TRUE;
|
||||||
|
if ((*e = g_async_queue_try_pop (error_queue)))
|
||||||
|
result = FALSE;
|
||||||
|
|
||||||
|
g_async_queue_unref (error_queue);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
33
src/utils.h
33
src/utils.h
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
* utils.h: miscellaneous utilities
|
* utils.h: miscellaneous utilities
|
||||||
*
|
*
|
||||||
* Copyright (c) 2013 - 2015, Přemysl Eric Janouch <p@janouch.name>
|
* Copyright (c) 2013 - 2021, Přemysl Eric Janouch <p@janouch.name>
|
||||||
*
|
*
|
||||||
* Permission to use, copy, modify, and/or distribute this software for any
|
* Permission to use, copy, modify, and/or distribute this software for any
|
||||||
* purpose with or without fee is hereby granted.
|
* purpose with or without fee is hereby granted.
|
||||||
@@ -19,6 +19,11 @@
|
|||||||
#ifndef UTILS_H
|
#ifndef UTILS_H
|
||||||
#define UTILS_H
|
#define UTILS_H
|
||||||
|
|
||||||
|
#include <glib.h>
|
||||||
|
#include <gio/gio.h>
|
||||||
|
|
||||||
|
#include "stardict.h"
|
||||||
|
|
||||||
/// After this statement, the element has been found and its index is stored
|
/// After this statement, the element has been found and its index is stored
|
||||||
/// in the variable "imid".
|
/// in the variable "imid".
|
||||||
#define BINARY_SEARCH_BEGIN(max, compare) \
|
#define BINARY_SEARCH_BEGIN(max, compare) \
|
||||||
@@ -36,9 +41,33 @@
|
|||||||
} \
|
} \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gchar *xdxf_to_pango_markup_with_reduced_effort (const gchar *xml);
|
||||||
|
|
||||||
gboolean stream_read_all (GByteArray *ba, GInputStream *is, GError **error);
|
gboolean stream_read_all (GByteArray *ba, GInputStream *is, GError **error);
|
||||||
gchar *stream_read_string (GDataInputStream *dis, GError **error);
|
gchar *stream_read_string (GDataInputStream *dis, GError **error);
|
||||||
gboolean xstrtoul (unsigned long *out, const char *s, int base);
|
gboolean xstrtoul (unsigned long *out, const char *s, int base);
|
||||||
void update_curses_terminal_size (void);
|
void fatal (const gchar *format, ...) G_GNUC_PRINTF (1, 2) G_GNUC_NORETURN;
|
||||||
|
|
||||||
|
gchar *resolve_relative_filename_generic
|
||||||
|
(const gchar **paths, const gchar *tail, const gchar *filename);
|
||||||
|
gchar *resolve_relative_config_filename (const gchar *filename);
|
||||||
|
gchar *resolve_filename
|
||||||
|
(const gchar *filename, gchar *(*relative_cb) (const char *));
|
||||||
|
GKeyFile *load_project_config_file (GError **error);
|
||||||
|
gboolean save_project_config_file (GKeyFile *key_file, GError **error);
|
||||||
|
|
||||||
|
// --- Loading -----------------------------------------------------------------
|
||||||
|
|
||||||
|
typedef struct dictionary Dictionary;
|
||||||
|
|
||||||
|
struct dictionary
|
||||||
|
{
|
||||||
|
gchar *filename; ///< Path to the dictionary
|
||||||
|
StardictDict *dict; ///< StarDict dictionary data
|
||||||
|
gchar *name; ///< Name to show
|
||||||
|
};
|
||||||
|
|
||||||
|
void dictionary_destroy (Dictionary *self);
|
||||||
|
gboolean load_dictionaries (GPtrArray *dictionaries, GError **e);
|
||||||
|
|
||||||
#endif // ! UTILS_H
|
#endif // ! UTILS_H
|
||||||
|
|||||||
9
tdv.desktop
Normal file
9
tdv.desktop
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
[Desktop Entry]
|
||||||
|
Type=Application
|
||||||
|
Name=tdv
|
||||||
|
GenericName=Translation dictionary viewer
|
||||||
|
Icon=tdv
|
||||||
|
Exec=tdv %F
|
||||||
|
StartupNotify=true
|
||||||
|
MimeType=application/x-stardict-ifo;
|
||||||
|
Categories=Office;Dictionary;GTK;
|
||||||
51
tdv.svg
Normal file
51
tdv.svg
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<svg version="1.1" width="48" height="48" viewBox="0 0 48 48"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"
|
||||||
|
color-interpolation="linearRGB"
|
||||||
|
color-interpolation-filters="linearRGB">
|
||||||
|
|
||||||
|
<defs>
|
||||||
|
<rect id="text" width="13" height="3" fill="#d0d0d0" />
|
||||||
|
<filter id="shadow" x="-25%" y="-25%" width="150%" height="150%">
|
||||||
|
<feFlood flood-color="#000000" flood-opacity=".5" result="flood" />
|
||||||
|
<feComposite in="SourceGraphic" in2="flood" operator="in" />
|
||||||
|
<feGaussianBlur stdDeviation="1.25" />
|
||||||
|
<feOffset dx="1" dy="1" result="offset" />
|
||||||
|
<feComposite in="SourceGraphic" in2="offset" />
|
||||||
|
</filter>
|
||||||
|
<mask id="hole">
|
||||||
|
<rect x="-25%" y="-25%" width="150%" height="150%" fill="#ffffff" />
|
||||||
|
<circle r="10.5" />
|
||||||
|
</mask>
|
||||||
|
</defs>
|
||||||
|
|
||||||
|
<rect x="5" y="1" width="38" height="46" ry="2"
|
||||||
|
fill="#ffffff" stroke="#606060" stroke-width="1.25" />
|
||||||
|
|
||||||
|
<use xlink:href="#text" x="9" y="5" />
|
||||||
|
<use xlink:href="#text" x="9" y="10" />
|
||||||
|
<use xlink:href="#text" x="9" y="15" />
|
||||||
|
<use xlink:href="#text" x="9" y="20" />
|
||||||
|
<use xlink:href="#text" x="9" y="25" />
|
||||||
|
<use xlink:href="#text" x="9" y="30" />
|
||||||
|
<use xlink:href="#text" x="9" y="35" />
|
||||||
|
<use xlink:href="#text" x="9" y="40" />
|
||||||
|
|
||||||
|
<use xlink:href="#text" x="26" y="5" />
|
||||||
|
<use xlink:href="#text" x="26" y="10" />
|
||||||
|
<use xlink:href="#text" x="26" y="15" />
|
||||||
|
<use xlink:href="#text" x="26" y="20" />
|
||||||
|
<use xlink:href="#text" x="26" y="25" />
|
||||||
|
<use xlink:href="#text" x="26" y="30" />
|
||||||
|
<use xlink:href="#text" x="26" y="35" />
|
||||||
|
<use xlink:href="#text" x="26" y="40" />
|
||||||
|
|
||||||
|
<circle cx="21" cy="19" r="9" fill="#ffffff" fill-opacity=".5"
|
||||||
|
stroke-width="2" stroke="#000000" filter="url(#shadow)" />
|
||||||
|
<g filter="url(#shadow)">
|
||||||
|
<rect x="-1.75" y="0" width="3.5" height="22.5"
|
||||||
|
transform="translate(21 19) rotate(-30)" mask="url(#hole)" />
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 1.9 KiB |
10
tdv.xml
Normal file
10
tdv.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
||||||
|
<mime-type type="application/x-stardict-ifo">
|
||||||
|
<comment>StarDict dictionary main file</comment>
|
||||||
|
<magic>
|
||||||
|
<match type="string" offset="0" value="StarDict's dict ifo file"/>
|
||||||
|
</magic>
|
||||||
|
<glob pattern="*.ifo"/>
|
||||||
|
</mime-type>
|
||||||
|
</mime-info>
|
||||||
2
termo
2
termo
Submodule termo updated: 30e0eee1a8...f9a102456f
Reference in New Issue
Block a user