Compare commits
	
		
			113 Commits
		
	
	
		
			ce2b8b39c0
			...
			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
	
				 | 
					
					
						
							
								
								
									
										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
 | 
			
		||||
							
								
								
									
										12
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -3,9 +3,9 @@
 | 
			
		||||
 | 
			
		||||
# Qt Creator files
 | 
			
		||||
/CMakeLists.txt.user*
 | 
			
		||||
/sdtui.cflags
 | 
			
		||||
/sdtui.cxxflags
 | 
			
		||||
/sdtui.config
 | 
			
		||||
/sdtui.files
 | 
			
		||||
/sdtui.creator*
 | 
			
		||||
/sdtui.includes
 | 
			
		||||
/tdv.cflags
 | 
			
		||||
/tdv.cxxflags
 | 
			
		||||
/tdv.config
 | 
			
		||||
/tdv.files
 | 
			
		||||
/tdv.creator*
 | 
			
		||||
/tdv.includes
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitmodules
									
									
									
									
										vendored
									
									
								
							@@ -1,3 +1,6 @@
 | 
			
		||||
[submodule "termo"]
 | 
			
		||||
	path = termo
 | 
			
		||||
	url = https://git.janouch.name/p/termo.git
 | 
			
		||||
[submodule "liberty"]
 | 
			
		||||
	path = liberty
 | 
			
		||||
	url = https://git.janouch.name/p/liberty.git
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										319
									
								
								CMakeLists.txt
									
									
									
									
									
								
							
							
						
						
									
										319
									
								
								CMakeLists.txt
									
									
									
									
									
								
							@@ -1,15 +1,42 @@
 | 
			
		||||
cmake_minimum_required (VERSION 3.0)
 | 
			
		||||
project (sdtui VERSION 0.1.0 LANGUAGES C)
 | 
			
		||||
cmake_minimum_required (VERSION 3.0...3.27)
 | 
			
		||||
project (tdv VERSION 0.1.0 LANGUAGES C)
 | 
			
		||||
 | 
			
		||||
# Moar warnings
 | 
			
		||||
# Adjust warnings
 | 
			
		||||
if ("${CMAKE_C_COMPILER_ID}" MATCHES "GNU" OR CMAKE_COMPILER_IS_GNUCC)
 | 
			
		||||
	set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99")
 | 
			
		||||
	set (CMAKE_C_FLAGS_DEBUG
 | 
			
		||||
		"${CMAKE_C_FLAGS_DEBUG} -Wall -Wextra -Wno-missing-field-initializers")
 | 
			
		||||
	set (ignores "-Wno-missing-field-initializers -Wno-cast-function-type")
 | 
			
		||||
	set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=gnu99 ${ignores}")
 | 
			
		||||
	set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Wall -Wextra")
 | 
			
		||||
endif ()
 | 
			
		||||
 | 
			
		||||
add_definitions (-DGLIB_VERSION_MIN_REQUIRED=GLIB_VERSION_2_38)
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
find_package (ZLIB REQUIRED)
 | 
			
		||||
@@ -18,7 +45,7 @@ find_package (PkgConfig REQUIRED)
 | 
			
		||||
pkg_check_modules (dependencies REQUIRED glib-2.0>=2.38 gio-2.0 pango)
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
	if (NOT icu_CONFIG_EXECUTABLE)
 | 
			
		||||
		message (FATAL_ERROR "ICU not found")
 | 
			
		||||
@@ -47,15 +74,16 @@ endif ()
 | 
			
		||||
find_package (Termo QUIET NO_MODULE)
 | 
			
		||||
option (USE_SYSTEM_TERMO
 | 
			
		||||
	"Don't compile our own termo library, use the system one" ${Termo_FOUND})
 | 
			
		||||
 | 
			
		||||
if (USE_SYSTEM_TERMO)
 | 
			
		||||
	if (NOT Termo_FOUND)
 | 
			
		||||
		message (FATAL_ERROR "System termo library not found")
 | 
			
		||||
	endif ()
 | 
			
		||||
else ()
 | 
			
		||||
	# We don't want the library to install, even though EXCLUDE_FROM_ALL
 | 
			
		||||
	# sabotages CTest -- those unbuilt tests need to be excluded in CTest runs
 | 
			
		||||
elseif (NOT WIN32)
 | 
			
		||||
	# We don't want the library to install, but EXCLUDE_FROM_ALL ignores tests
 | 
			
		||||
	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
 | 
			
		||||
	# (other possibilities: setting a variable in the parent scope, using
 | 
			
		||||
	# a cache variable, writing a special config file with build paths in it
 | 
			
		||||
@@ -67,18 +95,27 @@ endif ()
 | 
			
		||||
 | 
			
		||||
pkg_check_modules (xcb xcb xcb-xfixes)
 | 
			
		||||
option (WITH_X11 "Compile with X11 selection support using XCB" ${xcb_FOUND})
 | 
			
		||||
 | 
			
		||||
if (WITH_X11)
 | 
			
		||||
	if (NOT xcb_FOUND)
 | 
			
		||||
		message (FATAL_ERROR "XCB not found")
 | 
			
		||||
	endif ()
 | 
			
		||||
 | 
			
		||||
	list (APPEND dependencies_INCLUDE_DIRS ${xcb_INCLUDE_DIRS})
 | 
			
		||||
	list (APPEND dependencies_LIBRARY_DIRS ${xcb_LIBRARY_DIRS})
 | 
			
		||||
	list (APPEND dependencies_LIBRARIES ${xcb_LIBRARIES})
 | 
			
		||||
	include_directories (${xcb_INCLUDE_DIRS})
 | 
			
		||||
	link_directories (${xcb_LIBRARY_DIRS})
 | 
			
		||||
endif ()
 | 
			
		||||
 | 
			
		||||
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}
 | 
			
		||||
	${dependencies_INCLUDE_DIRS} ${Ncursesw_INCLUDE_DIRS}
 | 
			
		||||
	${Termo_INCLUDE_DIRS})
 | 
			
		||||
@@ -90,37 +127,61 @@ CHECK_FUNCTION_EXISTS ("resizeterm" HAVE_RESIZETERM)
 | 
			
		||||
 | 
			
		||||
# Localization
 | 
			
		||||
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 (
 | 
			
		||||
	${PROJECT_SOURCE_DIR}/po/${PROJECT_NAME}.pot
 | 
			
		||||
	"${PROJECT_SOURCE_DIR}/po/${PROJECT_NAME}.pot"
 | 
			
		||||
	ALL ${project_PO_FILES})
 | 
			
		||||
 | 
			
		||||
# Documentation
 | 
			
		||||
find_program (ASCIIDOCTOR_EXECUTABLE asciidoctor)
 | 
			
		||||
if (NOT ASCIIDOCTOR_EXECUTABLE)
 | 
			
		||||
	message (FATAL_ERROR "asciidoctor not found")
 | 
			
		||||
find_program (A2X_EXECUTABLE a2x)
 | 
			
		||||
if (NOT ASCIIDOCTOR_EXECUTABLE AND NOT A2X_EXECUTABLE)
 | 
			
		||||
	message (WARNING "Neither asciidoctor nor a2x were found, "
 | 
			
		||||
		"falling back to a substandard manual page generator")
 | 
			
		||||
endif ()
 | 
			
		||||
 | 
			
		||||
foreach (page "${PROJECT_NAME}.1")
 | 
			
		||||
	set (page_output "${PROJECT_BINARY_DIR}/${page}")
 | 
			
		||||
	list (APPEND project_MAN_PAGES "${page_output}")
 | 
			
		||||
	add_custom_command (OUTPUT ${page_output}
 | 
			
		||||
		COMMAND ${ASCIIDOCTOR_EXECUTABLE} -b manpage
 | 
			
		||||
			-a release-version=${PROJECT_VERSION}
 | 
			
		||||
			"${PROJECT_SOURCE_DIR}/docs/${page}.adoc"
 | 
			
		||||
			-o "${page_output}"
 | 
			
		||||
		DEPENDS "docs/${page}.adoc"
 | 
			
		||||
		COMMENT "Generating man page for ${page}" VERBATIM)
 | 
			
		||||
	if (ASCIIDOCTOR_EXECUTABLE)
 | 
			
		||||
		add_custom_command (OUTPUT "${page_output}"
 | 
			
		||||
			COMMAND ${ASCIIDOCTOR_EXECUTABLE} -b manpage
 | 
			
		||||
				-a release-version=${PROJECT_VERSION}
 | 
			
		||||
				-o "${page_output}"
 | 
			
		||||
				"${PROJECT_SOURCE_DIR}/docs/${page}.adoc"
 | 
			
		||||
			DEPENDS "docs/${page}.adoc"
 | 
			
		||||
			COMMENT "Generating man page for ${page}" VERBATIM)
 | 
			
		||||
	elseif (A2X_EXECUTABLE)
 | 
			
		||||
		add_custom_command (OUTPUT "${page_output}"
 | 
			
		||||
			COMMAND ${A2X_EXECUTABLE} --doctype manpage --format manpage
 | 
			
		||||
				-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})
 | 
			
		||||
 | 
			
		||||
# Project libraries
 | 
			
		||||
set (project_common_libraries ${ZLIB_LIBRARIES} ${icu_LIBRARIES}
 | 
			
		||||
	${dependencies_LIBRARIES} ${Ncursesw_LIBRARIES} termo-static)
 | 
			
		||||
	${dependencies_LIBRARIES})
 | 
			
		||||
if (WIN32)
 | 
			
		||||
	find_package (LibIntl REQUIRED)
 | 
			
		||||
	list (APPEND project_common_libraries ${LibIntl_LIBRARIES})
 | 
			
		||||
endif (WIN32)
 | 
			
		||||
 | 
			
		||||
set (project_common_headers
 | 
			
		||||
	${PROJECT_BINARY_DIR}/config.h
 | 
			
		||||
	"${PROJECT_BINARY_DIR}/config.h"
 | 
			
		||||
	src/dictzip-input-stream.h
 | 
			
		||||
	src/stardict.h
 | 
			
		||||
	src/stardict-private.h
 | 
			
		||||
@@ -137,40 +198,80 @@ add_library (stardict OBJECT
 | 
			
		||||
set (project_common_sources $<TARGET_OBJECTS:stardict>)
 | 
			
		||||
 | 
			
		||||
# Generate a configuration file
 | 
			
		||||
configure_file (${PROJECT_SOURCE_DIR}/config.h.in
 | 
			
		||||
	${PROJECT_BINARY_DIR}/config.h)
 | 
			
		||||
include_directories (${PROJECT_SOURCE_DIR} ${PROJECT_BINARY_DIR})
 | 
			
		||||
configure_file ("${PROJECT_SOURCE_DIR}/config.h.in"
 | 
			
		||||
	"${PROJECT_BINARY_DIR}/config.h")
 | 
			
		||||
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
 | 
			
		||||
	${project_common_sources}
 | 
			
		||||
	src/${PROJECT_NAME}.c)
 | 
			
		||||
set (project_headers
 | 
			
		||||
	${project_common_headers})
 | 
			
		||||
 | 
			
		||||
# Build the main executable and link it
 | 
			
		||||
add_definitions (-DGLIB_DISABLE_DEPRECATION_WARNINGS)
 | 
			
		||||
add_executable (${PROJECT_NAME}
 | 
			
		||||
	${project_sources} ${project_headers} ${project_common_sources})
 | 
			
		||||
target_link_libraries (${PROJECT_NAME} ${project_common_libraries})
 | 
			
		||||
if (WITH_GUI)
 | 
			
		||||
	include (IconUtils)
 | 
			
		||||
 | 
			
		||||
# Experimental GTK+ frontend, we link it with ncurses but we don't care
 | 
			
		||||
pkg_check_modules (gtk gtk+-3.0)
 | 
			
		||||
if (gtk_FOUND)
 | 
			
		||||
	add_executable (sdgtk EXCLUDE_FROM_ALL
 | 
			
		||||
		src/sdgtk.c ${project_common_sources})
 | 
			
		||||
	target_include_directories (sdgtk PUBLIC ${gtk_INCLUDE_DIRS})
 | 
			
		||||
	target_link_libraries (sdgtk ${gtk_LIBRARIES} ${project_common_libraries})
 | 
			
		||||
	# The largest size is mainly for an appropriately sized Windows icon
 | 
			
		||||
	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
 | 
			
		||||
set (tools tabfile add-pronunciation query-tool transform)
 | 
			
		||||
set (tools tdv-tabfile tdv-add-pronunciation tdv-query-tool tdv-transform)
 | 
			
		||||
foreach (tool ${tools})
 | 
			
		||||
	add_executable (${tool} EXCLUDE_FROM_ALL
 | 
			
		||||
		src/${tool}.c ${project_common_sources})
 | 
			
		||||
	target_link_libraries (${tool} ${project_common_libraries})
 | 
			
		||||
endforeach ()
 | 
			
		||||
 | 
			
		||||
add_custom_target (tools DEPENDS ${tools})
 | 
			
		||||
option (WITH_TOOLS "Build and install some StarDict tools" ${UNIX})
 | 
			
		||||
if (WITH_TOOLS)
 | 
			
		||||
	add_custom_target (tools ALL DEPENDS ${tools})
 | 
			
		||||
endif ()
 | 
			
		||||
 | 
			
		||||
# Example dictionaries
 | 
			
		||||
file (GLOB dicts_scripts "${PROJECT_SOURCE_DIR}/dicts/*.*")
 | 
			
		||||
@@ -180,7 +281,7 @@ foreach (dict_script ${dicts_scripts})
 | 
			
		||||
	list (APPEND dicts_targets "dicts-${dict_name}")
 | 
			
		||||
	add_custom_target (dicts-${dict_name}
 | 
			
		||||
		COMMAND sh -c "PATH=.:$PATH \"$0\"" "${dict_script}"
 | 
			
		||||
		DEPENDS tabfile
 | 
			
		||||
		DEPENDS tdv-tabfile
 | 
			
		||||
		COMMENT "Generating sample dictionary ${dict_name}"
 | 
			
		||||
		VERBATIM)
 | 
			
		||||
endforeach ()
 | 
			
		||||
@@ -188,42 +289,134 @@ endforeach ()
 | 
			
		||||
add_custom_target (dicts DEPENDS ${dicts_targets})
 | 
			
		||||
 | 
			
		||||
# The files to be installed
 | 
			
		||||
include (GNUInstallDirs)
 | 
			
		||||
install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
 | 
			
		||||
install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
 | 
			
		||||
if (NOT WIN32)
 | 
			
		||||
	include (GNUInstallDirs)
 | 
			
		||||
	install (TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_BINDIR})
 | 
			
		||||
	install (FILES LICENSE DESTINATION ${CMAKE_INSTALL_DOCDIR})
 | 
			
		||||
 | 
			
		||||
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 ()
 | 
			
		||||
	if (WITH_TOOLS)
 | 
			
		||||
		install (TARGETS ${tools} DESTINATION ${CMAKE_INSTALL_BINDIR})
 | 
			
		||||
	endif ()
 | 
			
		||||
	if (WITH_GUI)
 | 
			
		||||
		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
 | 
			
		||||
option (BUILD_TESTING "Build tests" OFF)
 | 
			
		||||
set (project_tests stardict)
 | 
			
		||||
 | 
			
		||||
if (BUILD_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}
 | 
			
		||||
			src/test-${name}.c ${project_common_sources})
 | 
			
		||||
		target_link_libraries (test-${name} ${project_common_libraries})
 | 
			
		||||
		add_test (test-${name} test-${name})
 | 
			
		||||
		add_test (NAME test-${name} COMMAND test-${name})
 | 
			
		||||
	endforeach ()
 | 
			
		||||
endif ()
 | 
			
		||||
 | 
			
		||||
# 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_CONTACT "Přemysl Eric Janouch <p@janouch.name>")
 | 
			
		||||
set (CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
 | 
			
		||||
set (CPACK_GENERATOR "TGZ;ZIP")
 | 
			
		||||
set (CPACK_PACKAGE_FILE_NAME
 | 
			
		||||
	"${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_IGNORE_FILES "/\\\\.git;/build;/CMakeLists.txt.user")
 | 
			
		||||
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)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							@@ -1,4 +1,4 @@
 | 
			
		||||
Copyright (c) 2013 - 2021, 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
 | 
			
		||||
purpose with or without fee is hereby granted.
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										87
									
								
								README.adoc
									
									
									
									
									
								
							
							
						
						
									
										87
									
								
								README.adoc
									
									
									
									
									
								
							@@ -1,37 +1,51 @@
 | 
			
		||||
StarDict Terminal UI
 | 
			
		||||
====================
 | 
			
		||||
Translation dictionary viewer
 | 
			
		||||
=============================
 | 
			
		||||
 | 
			
		||||
'sdtui' aims to provide an easy way of viewing translation as well as other
 | 
			
		||||
kinds of dictionaries in your terminal, and is inspired by the dictionary
 | 
			
		||||
component of PC Translator.  I wasn't successful in finding any free software
 | 
			
		||||
of this kind, GUI or not, and thus decided to write my own.
 | 
			
		||||
'tdv' aims to provide an easy way of viewing translation as well as other kinds
 | 
			
		||||
of StarDict dictionaries, and is inspired by the dictionary component
 | 
			
		||||
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
 | 
			
		||||
similar projects, and can serve as a base for implementing other dictionary
 | 
			
		||||
software.
 | 
			
		||||
 | 
			
		||||
image::sdtui.png[align="center"]
 | 
			
		||||
Screenshot
 | 
			
		||||
----------
 | 
			
		||||
image::tdv.png[align="center"]
 | 
			
		||||
 | 
			
		||||
Packages
 | 
			
		||||
--------
 | 
			
		||||
Regular releases are sporadic.  git master should be stable enough.  You can get
 | 
			
		||||
a package with the latest development version from Archlinux's AUR.
 | 
			
		||||
Regular releases are sporadic.  git master should be stable enough.
 | 
			
		||||
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/sdtui.1.adoc[man page] for information about usage.
 | 
			
		||||
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
 | 
			
		||||
--------------------
 | 
			
		||||
Build dependencies: CMake, pkg-config, asciidoctor +
 | 
			
		||||
Runtime dependencies: ncursesw, zlib, ICU, termo (included), glib-2.0 >= 2.38,
 | 
			
		||||
                      pango, xcb, xcb-xfixes (the latter two optional)
 | 
			
		||||
Build-only dependencies:
 | 
			
		||||
 CMake, pkg-config, gettext utilities, asciidoctor or asciidoc +
 | 
			
		||||
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
 | 
			
		||||
 $ mkdir sdtui/build
 | 
			
		||||
 $ cd sdtui/build
 | 
			
		||||
 $ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug -DWITH_X11=ON
 | 
			
		||||
 $ git clone --recursive https://git.janouch.name/p/tdv.git
 | 
			
		||||
 $ mkdir tdv/build
 | 
			
		||||
 $ cd tdv/build
 | 
			
		||||
 $ cmake .. -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug \
 | 
			
		||||
   -DWITH_X11=ON -DWITH_GUI=ON
 | 
			
		||||
 $ make
 | 
			
		||||
 | 
			
		||||
To install the application, you can do either the usual:
 | 
			
		||||
@@ -41,13 +55,25 @@ 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:
 | 
			
		||||
 | 
			
		||||
 $ cpack -G DEB
 | 
			
		||||
 # dpkg -i sdtui-*.deb
 | 
			
		||||
 # dpkg -i tdv-*.deb
 | 
			
		||||
 | 
			
		||||
Having the program installed, simply run it with a StarDict '.ifo' file as
 | 
			
		||||
an argument.  It is, however, preferable to
 | 
			
		||||
link:docs/sdtui.1.adoc#_configuration[configure it] to load your dictionaries
 | 
			
		||||
link:docs/tdv.1.adoc#_configuration[configure it] to load your dictionaries
 | 
			
		||||
automatically.
 | 
			
		||||
 | 
			
		||||
Windows
 | 
			
		||||
~~~~~~~
 | 
			
		||||
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.
 | 
			
		||||
 | 
			
		||||
 $ sh -e cmake/Win64Depends.sh
 | 
			
		||||
 $ cmake -DCMAKE_TOOLCHAIN_FILE=liberty/cmake/toolchains/MinGW-w64-x64.cmake \
 | 
			
		||||
   -DCMAKE_BUILD_TYPE=Release -B build
 | 
			
		||||
 $ cmake --build build -- package
 | 
			
		||||
 | 
			
		||||
Dictionaries
 | 
			
		||||
------------
 | 
			
		||||
This application is intended for use with specific dictionaries: each line
 | 
			
		||||
@@ -57,31 +83,24 @@ types are plain text, Pango markup, and XDXF (the visual format works better).
 | 
			
		||||
The `make dicts` command will build some examples from freely available sources:
 | 
			
		||||
 | 
			
		||||
 - GNU/FDL Czech-English dictionary
 | 
			
		||||
 - Czech foreign words
 | 
			
		||||
 - 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 '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.
 | 
			
		||||
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
 | 
			
		||||
-------------------
 | 
			
		||||
While I've been successfully using 'sdtui' for many years now, some work has to
 | 
			
		||||
be done yet before the software can be considered fit for inclusion in regular
 | 
			
		||||
Linux and/or BSD distributions:
 | 
			
		||||
 | 
			
		||||
 - The tab bar and the text input field don't handle overflows well.
 | 
			
		||||
 - Lacking configuration, standard StarDict locations should be scanned.
 | 
			
		||||
 | 
			
		||||
Given the entangledness of this codebase, issues with the file format,
 | 
			
		||||
and general undesirability of terminal UIs, it might be better to start anew.
 | 
			
		||||
Lacking configuration, standard StarDict locations should be scanned.
 | 
			
		||||
We should try harder to display arbitrary dictionaries sensibly.
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
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
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
#define GETTEXT_DIRNAME "${CMAKE_INSTALL_PREFIX}/share/locale"
 | 
			
		||||
 | 
			
		||||
#cmakedefine WITH_X11
 | 
			
		||||
#cmakedefine WITH_GUI
 | 
			
		||||
#cmakedefine HAVE_RESIZETERM
 | 
			
		||||
 | 
			
		||||
#endif  // ! CONFIG_H
 | 
			
		||||
 
 | 
			
		||||
@@ -4,11 +4,14 @@
 | 
			
		||||
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'"
 | 
			
		||||
		. ' | zcat | iconv -f latin2 -t UTF-8') or die $!;
 | 
			
		||||
		. " | $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
 | 
			
		||||
@@ -52,7 +55,7 @@ while (my ($id, $synset) = each %synsets) {
 | 
			
		||||
 | 
			
		||||
# Output synsets exploded to individual words, with expanded relationships
 | 
			
		||||
close($doc) or die $?;
 | 
			
		||||
open(my $tabfile, '|-', 'tabfile', 'czech-wordnet',
 | 
			
		||||
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 $!;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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");
 | 
			
		||||
	}'
 | 
			
		||||
@@ -5,7 +5,7 @@ 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, "|-", "tabfile", "--pango", "--collation=$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))
 | 
			
		||||
 
 | 
			
		||||
@@ -4,7 +4,7 @@
 | 
			
		||||
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 | tabfile slovnik-cizich-slov \
 | 
			
		||||
' | sort -u | tdv-tabfile slovnik-cizich-slov \
 | 
			
		||||
	--book-name="Slovník cizích slov" \
 | 
			
		||||
	--website=https://slovnik-cizich-slov.abz.cz \
 | 
			
		||||
	--date="$(date +%F)" \
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,21 @@
 | 
			
		||||
sdtui(1)
 | 
			
		||||
========
 | 
			
		||||
tdv(1)
 | 
			
		||||
======
 | 
			
		||||
:doctype: manpage
 | 
			
		||||
:manmanual: sdtui Manual
 | 
			
		||||
:mansource: sdtui {release-version}
 | 
			
		||||
:manmanual: tdv Manual
 | 
			
		||||
:mansource: tdv {release-version}
 | 
			
		||||
 | 
			
		||||
Name
 | 
			
		||||
----
 | 
			
		||||
sdtui - StarDict terminal UI
 | 
			
		||||
tdv - Translation dictionary viewer
 | 
			
		||||
 | 
			
		||||
Synopsis
 | 
			
		||||
--------
 | 
			
		||||
*sdtui* [_OPTION_]... [_DICTIONARY_.ifo]...
 | 
			
		||||
*tdv* [_OPTION_]... [_DICTIONARY_.ifo]...
 | 
			
		||||
 | 
			
		||||
Description
 | 
			
		||||
-----------
 | 
			
		||||
*sdtui* is a StarDict dictionary viewer, custom-tailored for translation
 | 
			
		||||
dictionaries, with a simple curses-based terminal UI.
 | 
			
		||||
*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
 | 
			
		||||
@@ -24,6 +24,9 @@ database files.
 | 
			
		||||
 | 
			
		||||
Options
 | 
			
		||||
-------
 | 
			
		||||
*--gui*::
 | 
			
		||||
	Launch the GUI even when run from a terminal.
 | 
			
		||||
 | 
			
		||||
*-h*, *--help*::
 | 
			
		||||
	Display a help message and exit.
 | 
			
		||||
 | 
			
		||||
@@ -32,7 +35,7 @@ Options
 | 
			
		||||
 | 
			
		||||
Configuration
 | 
			
		||||
-------------
 | 
			
		||||
You can start your _sdtui.conf_ file with the following snippet:
 | 
			
		||||
You can start your _tdv.conf_ file with the following snippet:
 | 
			
		||||
 | 
			
		||||
 [Settings]
 | 
			
		||||
 center-search = true     # Ensure visibility of preceding entries?
 | 
			
		||||
@@ -48,10 +51,13 @@ 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_ = __~/path/to/dict.ifo__
 | 
			
		||||
 _name 2_ = __~/another/dict.ifo__
 | 
			
		||||
 _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.
 | 
			
		||||
 | 
			
		||||
@@ -72,21 +78,21 @@ 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 *sdtui* discovers this
 | 
			
		||||
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
 | 
			
		||||
-----
 | 
			
		||||
*sdtui* follows the XDG Base Directory Specification.
 | 
			
		||||
*tdv* follows the XDG Base Directory Specification.
 | 
			
		||||
 | 
			
		||||
_~/.config/sdtui/sdtui.conf_::
 | 
			
		||||
_~/.config/tdv/tdv.conf_::
 | 
			
		||||
	The configuration file.
 | 
			
		||||
 | 
			
		||||
Reporting bugs
 | 
			
		||||
--------------
 | 
			
		||||
Use https://git.janouch.name/p/sdtui to report bugs, request features,
 | 
			
		||||
Use https://git.janouch.name/p/tdv to report bugs, request features,
 | 
			
		||||
or submit pull requests.
 | 
			
		||||
 | 
			
		||||
See also
 | 
			
		||||
							
								
								
									
										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.
 | 
			
		||||
# 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.
 | 
			
		||||
#
 | 
			
		||||
msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: sdtui 0.1.0\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: https://github.com/pjanouch/sdtui/issues\n"
 | 
			
		||||
"POT-Creation-Date: 2016-09-28 16:12+0200\n"
 | 
			
		||||
"PO-Revision-Date: 2016-09-28 16:15+0200\n"
 | 
			
		||||
"Project-Id-Version: tdv 0.1.0\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: https://git.janouch.name/p/tdv/issues\n"
 | 
			
		||||
"POT-Creation-Date: 2023-06-11 17:47+0200\n"
 | 
			
		||||
"PO-Revision-Date: 2023-06-11 17:53+0200\n"
 | 
			
		||||
"Last-Translator: Přemysl Eric Janouch <p@janouch.name>\n"
 | 
			
		||||
"Language-Team: Czech <translation-team-cs@lists.sourceforge.net>\n"
 | 
			
		||||
"Language: cs\n"
 | 
			
		||||
"MIME-Version: 1.0\n"
 | 
			
		||||
"Content-Type: text/plain; charset=UTF-8\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"
 | 
			
		||||
msgstr "Nemohu načíst konfiguraci"
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:1997
 | 
			
		||||
#: ../src/tdv.c:77
 | 
			
		||||
msgid "Error"
 | 
			
		||||
msgstr "Chyba"
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:606
 | 
			
		||||
#: ../src/tdv-tui.c:592
 | 
			
		||||
msgid "Error loading dictionary"
 | 
			
		||||
msgstr "Chyba při načítání slovníku"
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:612
 | 
			
		||||
msgid ""
 | 
			
		||||
"No dictionaries found either in the configuration or on the command line"
 | 
			
		||||
#: ../src/tdv.c:60
 | 
			
		||||
msgid "Launch the GUI even when run from a terminal"
 | 
			
		||||
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"
 | 
			
		||||
 | 
			
		||||
#: ../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"
 | 
			
		||||
msgstr "Vypíše informace o verzi a ukončí se"
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:548
 | 
			
		||||
#: ../src/tdv-tui.c:555
 | 
			
		||||
msgid "Search"
 | 
			
		||||
msgstr "Hledat"
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:966
 | 
			
		||||
#: ../src/tdv-tui.c:1068
 | 
			
		||||
msgid "Terminal UI for StarDict dictionaries"
 | 
			
		||||
msgstr "Terminálové UI pro stardictové slovníky"
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:969
 | 
			
		||||
#: ../src/tdv-tui.c:1071
 | 
			
		||||
msgid "Type to search"
 | 
			
		||||
msgstr "Zadejte vyhledávaný výraz"
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:1981
 | 
			
		||||
#: ../src/tdv.c:39
 | 
			
		||||
msgid "Warning"
 | 
			
		||||
msgstr "Varování"
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:1989
 | 
			
		||||
msgid "[dictionary.ifo...] - StarDict terminal UI"
 | 
			
		||||
msgstr "[slovník.ifo...] - terminálové UI pro StarDict"
 | 
			
		||||
#: ../src/tdv-tui.c:2071
 | 
			
		||||
#, c-format
 | 
			
		||||
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"
 | 
			
		||||
msgstr "nemohu najít .dict soubor"
 | 
			
		||||
 | 
			
		||||
#: ../src/stardict.c:812
 | 
			
		||||
#: ../src/stardict.c:827
 | 
			
		||||
msgid "cannot find .idx file"
 | 
			
		||||
msgstr "nemohu najít .idx soubor"
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:283
 | 
			
		||||
#: ../src/tdv-tui.c:258
 | 
			
		||||
msgid "error in entry"
 | 
			
		||||
msgstr "chyba v záznamu"
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:1981
 | 
			
		||||
#: ../src/tdv.c:39
 | 
			
		||||
msgid "failed to set the locale"
 | 
			
		||||
msgstr "selhalo nastavení locale"
 | 
			
		||||
 | 
			
		||||
#: ../src/stardict.c:308
 | 
			
		||||
#: ../src/stardict.c:328
 | 
			
		||||
msgid "index file size not specified"
 | 
			
		||||
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"
 | 
			
		||||
msgstr "neplatná datová položka"
 | 
			
		||||
 | 
			
		||||
#: ../src/stardict.c:259
 | 
			
		||||
#: ../src/stardict.c:281
 | 
			
		||||
msgid "invalid encoding, must be valid 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"
 | 
			
		||||
msgstr "neplatný formát hlavičky"
 | 
			
		||||
 | 
			
		||||
#: ../src/stardict.c:317
 | 
			
		||||
#: ../src/stardict.c:337
 | 
			
		||||
msgid "invalid index offset bits"
 | 
			
		||||
msgstr "neplatný počet bitů pro offset v rejstříku"
 | 
			
		||||
 | 
			
		||||
#: ../src/stardict.c:276
 | 
			
		||||
#: ../src/stardict.c:298
 | 
			
		||||
msgid "invalid integer"
 | 
			
		||||
msgstr "neplatné číslo"
 | 
			
		||||
 | 
			
		||||
#: ../src/stardict.c:238
 | 
			
		||||
#: ../src/stardict.c:260
 | 
			
		||||
msgid "invalid version"
 | 
			
		||||
msgstr "neplatná verze"
 | 
			
		||||
 | 
			
		||||
#: ../src/stardict.c:296
 | 
			
		||||
#: ../src/stardict.c:316
 | 
			
		||||
msgid "no book name specified"
 | 
			
		||||
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"
 | 
			
		||||
msgstr "nenalezeno žádné použitelné pole"
 | 
			
		||||
 | 
			
		||||
#: ../src/stardict.c:286
 | 
			
		||||
#: ../src/stardict.c:308
 | 
			
		||||
msgid "option format error"
 | 
			
		||||
msgstr "chyba v zápisu volby"
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:1997
 | 
			
		||||
#: ../src/tdv.c:77
 | 
			
		||||
msgid "option parsing failed"
 | 
			
		||||
msgstr "zpracování přepínačů selhalo"
 | 
			
		||||
 | 
			
		||||
#: ../src/stardict.c:252
 | 
			
		||||
#: ../src/stardict.c:274
 | 
			
		||||
msgid "unknown key, ignoring"
 | 
			
		||||
msgstr "neznámý klíč, ignoruji"
 | 
			
		||||
 | 
			
		||||
#: ../src/stardict.c:227
 | 
			
		||||
#: ../src/stardict.c:249
 | 
			
		||||
msgid "version not specified"
 | 
			
		||||
msgstr "nebyla určena verze"
 | 
			
		||||
 | 
			
		||||
#: ../src/stardict.c:302
 | 
			
		||||
#: ../src/stardict.c:322
 | 
			
		||||
msgid "word count not specified"
 | 
			
		||||
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.
 | 
			
		||||
#
 | 
			
		||||
# The reason for this not being inside CMakeLists.txt
 | 
			
		||||
# is that the translator should not need to run the whole
 | 
			
		||||
# configuration process just to get this single stupid file.
 | 
			
		||||
# The reason for this not being inside CMakeLists.txt is that the translator
 | 
			
		||||
# should not need to run the whole configuration process just to get this file.
 | 
			
		||||
dir=$(dirname $0)
 | 
			
		||||
 | 
			
		||||
# Get the directory this script resides in so that the user
 | 
			
		||||
# doesn't have to run the script from there
 | 
			
		||||
DIR=$(dirname $0)
 | 
			
		||||
export LC_ALL=C
 | 
			
		||||
 | 
			
		||||
# Collect source files
 | 
			
		||||
SOURCES=$(echo $DIR/../src/*.c)
 | 
			
		||||
 | 
			
		||||
# Get the package name from CMakeLists.txt
 | 
			
		||||
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
 | 
			
		||||
re='^[ \t]*project *( *\([^ \t)]\{1,\}\) \{1,\}VERSION \{1,\}\([^ \t)]\{1,\}\).*'
 | 
			
		||||
package=$(sed -n "s/$re/\\1/p" "$dir/../CMakeLists.txt")
 | 
			
		||||
version=$(sed -n "s/$re/\\2/p" "$dir/../CMakeLists.txt")
 | 
			
		||||
if [ -z "$package" -o -z "$version" ]; then
 | 
			
		||||
	echo "Failed to get information from CMakeLists.txt"
 | 
			
		||||
	exit 1
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
# Finally make the template
 | 
			
		||||
xgettext -LC -k_ -kN_ $SOURCES -o "$DIR/$PACKAGE.pot" \
 | 
			
		||||
	--package-name="$PACKAGE" --package-version="$VERSION" \
 | 
			
		||||
xgettext -LC -k_ -kN_ "$dir"/../src/*.c -o "$dir/$package.pot" \
 | 
			
		||||
	--package-name="$package" --package-version="$version" \
 | 
			
		||||
	--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.
 | 
			
		||||
# 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.
 | 
			
		||||
#
 | 
			
		||||
#, fuzzy
 | 
			
		||||
msgid ""
 | 
			
		||||
msgstr ""
 | 
			
		||||
"Project-Id-Version: sdtui 0.1.0\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: https://github.com/pjanouch/sdtui/issues\n"
 | 
			
		||||
"POT-Creation-Date: 2016-09-28 16:12+0200\n"
 | 
			
		||||
"Project-Id-Version: tdv 0.1.0\n"
 | 
			
		||||
"Report-Msgid-Bugs-To: https://git.janouch.name/p/tdv/issues\n"
 | 
			
		||||
"POT-Creation-Date: 2023-06-11 17:47+0200\n"
 | 
			
		||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 | 
			
		||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 | 
			
		||||
"Language-Team: LANGUAGE <LL@li.org>\n"
 | 
			
		||||
@@ -17,115 +17,149 @@ msgstr ""
 | 
			
		||||
"Content-Type: text/plain; charset=UTF-8\n"
 | 
			
		||||
"Content-Transfer-Encoding: 8bit\n"
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:283
 | 
			
		||||
msgid "error in entry"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:302
 | 
			
		||||
#: ../src/stardict-view.c:96 ../src/tdv-tui.c:340
 | 
			
		||||
msgid "no usable field found"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:548
 | 
			
		||||
msgid "Search"
 | 
			
		||||
#: ../src/stardict.c:89
 | 
			
		||||
msgid "invalid header format"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:573
 | 
			
		||||
msgid "Cannot load configuration"
 | 
			
		||||
#: ../src/stardict.c:249
 | 
			
		||||
msgid "version not specified"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:606
 | 
			
		||||
msgid "Error loading dictionary"
 | 
			
		||||
#: ../src/stardict.c:260
 | 
			
		||||
msgid "invalid version"
 | 
			
		||||
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 ""
 | 
			
		||||
"No dictionaries found either in the configuration or on the command line"
 | 
			
		||||
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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:969
 | 
			
		||||
#: ../src/tdv-tui.c:1071
 | 
			
		||||
msgid "Type to search"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:1976
 | 
			
		||||
msgid "Output version information and exit"
 | 
			
		||||
#: ../src/tdv-tui.c:2071
 | 
			
		||||
#, c-format
 | 
			
		||||
msgid "X11 connection failed (error code %d)"
 | 
			
		||||
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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:1981
 | 
			
		||||
#: ../src/tdv.c:39
 | 
			
		||||
msgid "failed to set the locale"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:1989
 | 
			
		||||
msgid "[dictionary.ifo...] - StarDict terminal UI"
 | 
			
		||||
#: ../src/tdv.c:55
 | 
			
		||||
msgid "Output version information and exit"
 | 
			
		||||
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"
 | 
			
		||||
msgstr ""
 | 
			
		||||
 | 
			
		||||
#: ../src/sdtui.c:1997
 | 
			
		||||
#: ../src/tdv.c:77
 | 
			
		||||
msgid "option parsing failed"
 | 
			
		||||
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 ""
 | 
			
		||||
							
								
								
									
										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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										419
									
								
								src/sdgtk.c
									
									
									
									
									
								
							
							
						
						
									
										419
									
								
								src/sdgtk.c
									
									
									
									
									
								
							@@ -1,419 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * StarDict GTK+ UI
 | 
			
		||||
 *
 | 
			
		||||
 * 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 <gtk/gtk.h>
 | 
			
		||||
#include <glib/gi18n.h>
 | 
			
		||||
 | 
			
		||||
#include <locale.h>
 | 
			
		||||
 | 
			
		||||
#include "config.h"
 | 
			
		||||
#include "stardict.h"
 | 
			
		||||
#include "utils.h"
 | 
			
		||||
 | 
			
		||||
typedef struct dictionary Dictionary;
 | 
			
		||||
 | 
			
		||||
struct dictionary
 | 
			
		||||
{
 | 
			
		||||
	const gchar  *filename;          ///< Filename
 | 
			
		||||
	StardictDict *dict;              ///< Stardict dictionary data
 | 
			
		||||
	gchar        *name;              ///< Name to show
 | 
			
		||||
	guint         position;          ///< Current position
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
static struct
 | 
			
		||||
{
 | 
			
		||||
	GtkWidget    *window;            ///< Top-level window
 | 
			
		||||
	GtkWidget    *notebook;          ///< Notebook with tabs
 | 
			
		||||
	GtkWidget    *entry;             ///< Search entry widget
 | 
			
		||||
	GtkWidget    *grid;              ///< Entries container
 | 
			
		||||
 | 
			
		||||
	gint          dictionary;        ///< Index of the current dictionary
 | 
			
		||||
	Dictionary   *dictionaries;      ///< All open dictionaries
 | 
			
		||||
	gsize         dictionaries_len;  ///< Total number of dictionaries
 | 
			
		||||
 | 
			
		||||
	gboolean      watch_selection;   ///< Following X11 PRIMARY?
 | 
			
		||||
}
 | 
			
		||||
g;
 | 
			
		||||
 | 
			
		||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
			
		||||
 | 
			
		||||
static gboolean
 | 
			
		||||
dictionary_load (Dictionary *self, gchar *filename, GError **e)
 | 
			
		||||
{
 | 
			
		||||
	self->filename = filename;
 | 
			
		||||
	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
 | 
			
		||||
init (gchar **filenames, GError **e)
 | 
			
		||||
{
 | 
			
		||||
	while (filenames[g.dictionaries_len])
 | 
			
		||||
		g.dictionaries_len++;
 | 
			
		||||
 | 
			
		||||
	g.dictionaries = g_malloc0_n (sizeof *g.dictionaries, g.dictionaries_len);
 | 
			
		||||
	for (gsize i = 0; i < g.dictionaries_len; i++)
 | 
			
		||||
	{
 | 
			
		||||
		Dictionary *dict = &g.dictionaries[i];
 | 
			
		||||
		if (!dictionary_load (dict, filenames[i], e))
 | 
			
		||||
			return FALSE;
 | 
			
		||||
	}
 | 
			
		||||
	return TRUE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
add_row (StardictIterator *iterator, gint row, gint *height_acc)
 | 
			
		||||
{
 | 
			
		||||
	Dictionary *dict = &g.dictionaries[g.dictionary];
 | 
			
		||||
 | 
			
		||||
	StardictEntry *entry = stardict_iterator_get_entry (iterator);
 | 
			
		||||
	g_return_if_fail (entry != NULL);
 | 
			
		||||
	StardictEntryField *field = entry->fields->data;
 | 
			
		||||
	g_return_if_fail (g_ascii_islower (field->type));
 | 
			
		||||
 | 
			
		||||
	GtkEntryBuffer *buf = gtk_entry_get_buffer (GTK_ENTRY (g.entry));
 | 
			
		||||
	const gchar *input_utf8 = gtk_entry_buffer_get_text (buf);
 | 
			
		||||
	g_return_if_fail (input_utf8 != NULL);
 | 
			
		||||
 | 
			
		||||
	const gchar *word_str = stardict_iterator_get_word (iterator);
 | 
			
		||||
	gsize common_prefix = stardict_longest_common_collation_prefix
 | 
			
		||||
		(dict->dict, word_str, input_utf8);
 | 
			
		||||
	gchar *pre = g_markup_escape_text (word_str, common_prefix),
 | 
			
		||||
		*post = g_markup_escape_text (word_str + common_prefix, -1),
 | 
			
		||||
		*marked_up = g_strdup_printf ("<u>%s</u>%s", pre, post);
 | 
			
		||||
 | 
			
		||||
	GtkWidget *word = gtk_label_new (marked_up);
 | 
			
		||||
	gtk_label_set_use_markup (GTK_LABEL (word), TRUE);
 | 
			
		||||
	gtk_label_set_ellipsize (GTK_LABEL (word), PANGO_ELLIPSIZE_END);
 | 
			
		||||
	gtk_label_set_selectable (GTK_LABEL (word), TRUE);
 | 
			
		||||
	gtk_label_set_xalign (GTK_LABEL (word), 0);
 | 
			
		||||
	gtk_label_set_yalign (GTK_LABEL (word), 0);
 | 
			
		||||
	// FIXME: they can't be deselected by just clicking outside of them
 | 
			
		||||
	gtk_widget_set_can_focus (word, FALSE);
 | 
			
		||||
 | 
			
		||||
	g_free (pre);
 | 
			
		||||
	g_free (post);
 | 
			
		||||
	g_free (marked_up);
 | 
			
		||||
 | 
			
		||||
	GtkWidget *desc = gtk_label_new (field->data);
 | 
			
		||||
	gtk_label_set_ellipsize (GTK_LABEL (desc), PANGO_ELLIPSIZE_END);
 | 
			
		||||
	gtk_label_set_selectable (GTK_LABEL (desc), TRUE);
 | 
			
		||||
	gtk_label_set_xalign (GTK_LABEL (desc), 0);
 | 
			
		||||
	gtk_widget_set_can_focus (desc, FALSE);
 | 
			
		||||
 | 
			
		||||
	g_object_unref (entry);
 | 
			
		||||
 | 
			
		||||
	if (iterator->offset % 2 == 0)
 | 
			
		||||
	{
 | 
			
		||||
		GtkStyleContext *ctx;
 | 
			
		||||
		ctx = gtk_widget_get_style_context (word);
 | 
			
		||||
		gtk_style_context_add_class (ctx, "odd");
 | 
			
		||||
		ctx = gtk_widget_get_style_context (desc);
 | 
			
		||||
		gtk_style_context_add_class (ctx, "odd");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	gtk_grid_attach (GTK_GRID (g.grid), word, 0, row, 1, 1);
 | 
			
		||||
	gtk_grid_attach (GTK_GRID (g.grid), desc, 1, row, 1, 1);
 | 
			
		||||
 | 
			
		||||
	gtk_widget_show (word);
 | 
			
		||||
	gtk_widget_show (desc);
 | 
			
		||||
 | 
			
		||||
	gint minimum_word = 0, minimum_desc = 0;
 | 
			
		||||
	gtk_widget_get_preferred_height (word, &minimum_word, NULL);
 | 
			
		||||
	gtk_widget_get_preferred_height (desc, &minimum_desc, NULL);
 | 
			
		||||
	*height_acc += MAX (minimum_word, minimum_desc);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
reload (GtkWidget *grid)
 | 
			
		||||
{
 | 
			
		||||
	Dictionary *dict = &g.dictionaries[g.dictionary];
 | 
			
		||||
 | 
			
		||||
	GList *children = gtk_container_get_children (GTK_CONTAINER (grid));
 | 
			
		||||
	for (GList *iter = children; iter != NULL; iter = g_list_next (iter))
 | 
			
		||||
		gtk_widget_destroy (GTK_WIDGET (iter->data));
 | 
			
		||||
	g_list_free (children);
 | 
			
		||||
 | 
			
		||||
	gint window_height = 0;
 | 
			
		||||
	gtk_window_get_size (GTK_WINDOW (g.window), NULL, &window_height);
 | 
			
		||||
	if (window_height <= 0)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	StardictIterator *iterator =
 | 
			
		||||
		stardict_iterator_new (dict->dict, dict->position);
 | 
			
		||||
	gint row = 0, height_acc = 0;
 | 
			
		||||
	while (stardict_iterator_is_valid (iterator))
 | 
			
		||||
	{
 | 
			
		||||
		add_row (iterator, row++, &height_acc);
 | 
			
		||||
		if (height_acc >= window_height)
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		stardict_iterator_next (iterator);
 | 
			
		||||
	}
 | 
			
		||||
	gtk_widget_show_all (grid);
 | 
			
		||||
	g_object_unref (iterator);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
 | 
			
		||||
	StardictIterator *iterator =
 | 
			
		||||
		stardict_dict_search (dict->dict, input_utf8, NULL);
 | 
			
		||||
	dict->position = stardict_iterator_get_offset (iterator);
 | 
			
		||||
	g_object_unref (iterator);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
on_changed (G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gpointer data)
 | 
			
		||||
{
 | 
			
		||||
	search (&g.dictionaries[g.dictionary]);
 | 
			
		||||
	reload (g.grid);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void
 | 
			
		||||
on_selection_received (G_GNUC_UNUSED GtkClipboard *clipboard, const gchar *text,
 | 
			
		||||
	G_GNUC_UNUSED gpointer data)
 | 
			
		||||
{
 | 
			
		||||
	if (!text)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	gtk_entry_set_text (GTK_ENTRY (g.entry), text);
 | 
			
		||||
	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.dictionary = page_num;
 | 
			
		||||
	search (&g.dictionaries[g.dictionary]);
 | 
			
		||||
	reload (g.grid);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static gboolean
 | 
			
		||||
on_key_press (G_GNUC_UNUSED GtkWidget *widget, GdkEvent *event,
 | 
			
		||||
	G_GNUC_UNUSED gpointer data)
 | 
			
		||||
{
 | 
			
		||||
	if (event->key.state == GDK_CONTROL_MASK)
 | 
			
		||||
	{
 | 
			
		||||
		if (event->key.keyval == GDK_KEY_Page_Up)
 | 
			
		||||
		{
 | 
			
		||||
			gtk_notebook_prev_page (GTK_NOTEBOOK (g.notebook));
 | 
			
		||||
			return TRUE;
 | 
			
		||||
		}
 | 
			
		||||
		if (event->key.keyval == GDK_KEY_Page_Down)
 | 
			
		||||
		{
 | 
			
		||||
			gtk_notebook_next_page (GTK_NOTEBOOK (g.notebook));
 | 
			
		||||
			return TRUE;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if (event->key.state == 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
 | 
			
		||||
				(GTK_NOTEBOOK (g.notebook), n ? (n - 1) : 10);
 | 
			
		||||
			return TRUE;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return FALSE;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
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);
 | 
			
		||||
 | 
			
		||||
	gchar **filenames = NULL;
 | 
			
		||||
	GOptionEntry option_entries[] =
 | 
			
		||||
	{
 | 
			
		||||
		{G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &filenames,
 | 
			
		||||
			NULL, N_("FILE...")},
 | 
			
		||||
		{},
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	GError *error = NULL;
 | 
			
		||||
	gtk_init_with_args (&argc, &argv, N_("- StarDict GTK+ UI"),
 | 
			
		||||
		option_entries, GETTEXT_PACKAGE, &error);
 | 
			
		||||
	if (error)
 | 
			
		||||
	{
 | 
			
		||||
		g_warning ("%s", error->message);
 | 
			
		||||
		g_error_free (error);
 | 
			
		||||
		return 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!filenames)
 | 
			
		||||
	{
 | 
			
		||||
		// TODO: eventually just load all dictionaries from configuration
 | 
			
		||||
		die_with_dialog ("No arguments have been passed.");
 | 
			
		||||
	}
 | 
			
		||||
	if (!init (filenames, &error))
 | 
			
		||||
		die_with_dialog (error->message);
 | 
			
		||||
 | 
			
		||||
	// Some Adwaita stupidity and our own additions
 | 
			
		||||
	const char *style = "notebook header tab { padding: 2px 8px; margin: 0; }"
 | 
			
		||||
		"grid { border-top: 1px solid rgba(0, 0, 0, 0.2); background: white; }"
 | 
			
		||||
		"grid label { padding: 0 5px; "
 | 
			
		||||
			"/*border-bottom: 1px solid rgba(0, 0, 0, 0.2);*/ }"
 | 
			
		||||
		"grid label.odd { background: rgba(0, 0, 0, 0.05); }";
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
 | 
			
		||||
	g.grid = gtk_grid_new ();
 | 
			
		||||
	gtk_grid_set_column_homogeneous (GTK_GRID (g.grid), TRUE);
 | 
			
		||||
 | 
			
		||||
	// FIXME: we'd rather like to trim the contents, not make it scrollable.
 | 
			
		||||
	// This just limits the allocation.
 | 
			
		||||
	// TODO: probably create a whole new custom widget, everything is text
 | 
			
		||||
	// anyway and mostly handled by Pango, including pango_layout_xy_to_index()
 | 
			
		||||
	//  - I don't know where to get selection colour but inversion works, too
 | 
			
		||||
	GtkWidget *scrolled_window = gtk_scrolled_window_new (NULL, NULL);
 | 
			
		||||
	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
 | 
			
		||||
		GTK_POLICY_NEVER, GTK_POLICY_EXTERNAL);
 | 
			
		||||
	gtk_widget_set_can_focus (scrolled_window, FALSE);
 | 
			
		||||
	gtk_container_add (GTK_CONTAINER (scrolled_window), g.grid);
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
 | 
			
		||||
	g.watch_selection = TRUE;
 | 
			
		||||
	GtkWidget *item =
 | 
			
		||||
		gtk_check_menu_item_new_with_label (_("Follow selection"));
 | 
			
		||||
	gtk_check_menu_item_set_active
 | 
			
		||||
		(GTK_CHECK_MENU_ITEM (item), g.watch_selection);
 | 
			
		||||
	g_signal_connect (item, "toggled",
 | 
			
		||||
		G_CALLBACK (on_selection_watch_toggle), NULL);
 | 
			
		||||
 | 
			
		||||
	GtkWidget *menu = gtk_menu_new ();
 | 
			
		||||
	gtk_menu_shell_append (GTK_MENU_SHELL (menu), item);
 | 
			
		||||
	gtk_widget_show_all (menu);
 | 
			
		||||
 | 
			
		||||
	GtkWidget *hamburger = gtk_menu_button_new ();
 | 
			
		||||
	gtk_menu_button_set_direction (GTK_MENU_BUTTON (hamburger), GTK_ARROW_NONE);
 | 
			
		||||
	gtk_menu_button_set_popup (GTK_MENU_BUTTON (hamburger), menu);
 | 
			
		||||
	gtk_button_set_relief (GTK_BUTTON (hamburger), GTK_RELIEF_NONE);
 | 
			
		||||
	gtk_widget_show (hamburger);
 | 
			
		||||
 | 
			
		||||
	gtk_notebook_set_action_widget
 | 
			
		||||
		(GTK_NOTEBOOK (g.notebook), hamburger, GTK_PACK_END);
 | 
			
		||||
 | 
			
		||||
	// FIXME: when the clear icon shows, the widget changes in height
 | 
			
		||||
	g.entry = gtk_search_entry_new ();
 | 
			
		||||
	// TODO: attach to the "key-press-event" signal and implement ^W at least,
 | 
			
		||||
	// though ^U is working already!  Note that bindings can be done in CSS
 | 
			
		||||
	// as well, if we have any extra specially for the editor
 | 
			
		||||
	g_signal_connect (g.entry, "changed", G_CALLBACK (on_changed), g.grid);
 | 
			
		||||
	gtk_entry_set_has_frame (GTK_ENTRY (g.entry), FALSE);
 | 
			
		||||
 | 
			
		||||
	// TODO: supposedly attach to "key-press-event" here and react to
 | 
			
		||||
	// PageUp/PageDown and up/down arrow keys... either here or in the Entry
 | 
			
		||||
	g.window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
 | 
			
		||||
	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_box_pack_end (GTK_BOX (superbox), scrolled_window, TRUE, TRUE, 0);
 | 
			
		||||
 | 
			
		||||
	for (gsize i = 0; i < g.dictionaries_len; i++)
 | 
			
		||||
	{
 | 
			
		||||
		Dictionary *dict = &g.dictionaries[i];
 | 
			
		||||
		GtkWidget *dummy = gtk_box_new (GTK_ORIENTATION_VERTICAL, 0);
 | 
			
		||||
		GtkWidget *label = gtk_label_new (dict->name);
 | 
			
		||||
		gtk_notebook_append_page (GTK_NOTEBOOK (g.notebook), dummy, label);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	GtkClipboard *clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY);
 | 
			
		||||
	g_signal_connect (clipboard, "owner-change",
 | 
			
		||||
		G_CALLBACK (on_selection), NULL);
 | 
			
		||||
 | 
			
		||||
	// Make sure to fill up the window with entries once we're resized
 | 
			
		||||
	// XXX: this is rather inefficient as we rebuild everything each time
 | 
			
		||||
	g_signal_connect (g.window, "configure-event",
 | 
			
		||||
		G_CALLBACK (on_changed), NULL);
 | 
			
		||||
	g_signal_connect (g.window, "map-event",
 | 
			
		||||
		G_CALLBACK (on_changed), NULL);
 | 
			
		||||
 | 
			
		||||
	gtk_widget_grab_focus (g.entry);
 | 
			
		||||
	gtk_widget_show_all (g.window);
 | 
			
		||||
	gtk_main ();
 | 
			
		||||
 | 
			
		||||
	g_strfreev (filenames);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
@@ -309,26 +309,24 @@ load_ifo (StardictInfo *sti, const gchar *path, GError **error)
 | 
			
		||||
		goto error;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ret_val = TRUE;
 | 
			
		||||
 | 
			
		||||
	// FIXME check for zeros, don't assume that 0 means for "not set"
 | 
			
		||||
	// FIXME check for zeros, don't assume that 0 means "not set"
 | 
			
		||||
	if (!sti->book_name || !*sti->book_name)
 | 
			
		||||
	{
 | 
			
		||||
		g_set_error (error, STARDICT_ERROR, STARDICT_ERROR_INVALID_DATA,
 | 
			
		||||
			"%s: %s", path, _("no book name specified"));
 | 
			
		||||
		ret_val = FALSE;
 | 
			
		||||
		goto error;
 | 
			
		||||
	}
 | 
			
		||||
	if (!sti->word_count)
 | 
			
		||||
	{
 | 
			
		||||
		g_set_error (error, STARDICT_ERROR, STARDICT_ERROR_INVALID_DATA,
 | 
			
		||||
			"%s: %s", path, _("word count not specified"));
 | 
			
		||||
		ret_val = FALSE;
 | 
			
		||||
		goto error;
 | 
			
		||||
	}
 | 
			
		||||
	if (!sti->idx_filesize)
 | 
			
		||||
	{
 | 
			
		||||
		g_set_error (error, STARDICT_ERROR, STARDICT_ERROR_INVALID_DATA,
 | 
			
		||||
			"%s: %s", path, _("index file size not specified"));
 | 
			
		||||
		ret_val = FALSE;
 | 
			
		||||
		goto error;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!sti->idx_offset_bits)
 | 
			
		||||
@@ -338,9 +336,11 @@ load_ifo (StardictInfo *sti, const gchar *path, GError **error)
 | 
			
		||||
		g_set_error (error, STARDICT_ERROR, STARDICT_ERROR_INVALID_DATA,
 | 
			
		||||
			"%s: %s: %lu", path, _("invalid index offset bits"),
 | 
			
		||||
			sti->idx_offset_bits);
 | 
			
		||||
		ret_val = FALSE;
 | 
			
		||||
		goto error;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	ret_val = TRUE;
 | 
			
		||||
 | 
			
		||||
error:
 | 
			
		||||
	if (!ret_val)
 | 
			
		||||
	{
 | 
			
		||||
@@ -357,6 +357,20 @@ error:
 | 
			
		||||
	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.
 | 
			
		||||
/// @return GList<StardictInfo *>. Deallocate the list with:
 | 
			
		||||
/// @code
 | 
			
		||||
@@ -377,12 +391,10 @@ stardict_list_dictionaries (const gchar *path)
 | 
			
		||||
			continue;
 | 
			
		||||
 | 
			
		||||
		gchar *filename = g_build_filename (path, name, NULL);
 | 
			
		||||
		StardictInfo *ifo = g_new (StardictInfo, 1);
 | 
			
		||||
		if (load_ifo (ifo, filename, NULL))
 | 
			
		||||
			dicts = g_list_append (dicts, ifo);
 | 
			
		||||
		else
 | 
			
		||||
			g_free (ifo);
 | 
			
		||||
		StardictInfo *ifo = stardict_info_new (filename, NULL);
 | 
			
		||||
		g_free (filename);
 | 
			
		||||
		if (ifo)
 | 
			
		||||
			dicts = g_list_append (dicts, ifo);
 | 
			
		||||
	}
 | 
			
		||||
	g_dir_close (dir);
 | 
			
		||||
	g_pattern_spec_free (ps);
 | 
			
		||||
@@ -1014,6 +1026,10 @@ stardict_longest_common_collation_prefix (StardictDict *sd,
 | 
			
		||||
	u_strFromUTF8 (NULL, 0, &uc2_len, s2, -1, &error);
 | 
			
		||||
	error = U_ZERO_ERROR;
 | 
			
		||||
 | 
			
		||||
	// Prevent undefined behaviour with VLAs.
 | 
			
		||||
	if (!uc1_len || !uc2_len)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	UChar uc1[uc1_len];
 | 
			
		||||
	UChar uc2[uc2_len];
 | 
			
		||||
	u_strFromUTF8 (uc1, uc1_len, NULL, s1, -1, &error);
 | 
			
		||||
@@ -1354,7 +1370,7 @@ stardict_iterator_get_entry (StardictIterator *sdi)
 | 
			
		||||
{
 | 
			
		||||
	g_return_val_if_fail (STARDICT_IS_ITERATOR (sdi), NULL);
 | 
			
		||||
	if (!stardict_iterator_is_valid (sdi))
 | 
			
		||||
		return FALSE;
 | 
			
		||||
		return NULL;
 | 
			
		||||
	return stardict_dict_get_entry (sdi->owner, sdi->offset);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -108,6 +108,7 @@ GQuark stardict_error_quark (void);
 | 
			
		||||
 | 
			
		||||
// --- 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_book_name (StardictInfo *sdi) G_GNUC_PURE;
 | 
			
		||||
gsize stardict_info_get_word_count (StardictInfo *sd) G_GNUC_PURE;
 | 
			
		||||
@@ -198,7 +199,7 @@ struct stardict_entry_field
 | 
			
		||||
struct stardict_entry
 | 
			
		||||
{
 | 
			
		||||
	GObject         parent_instance;
 | 
			
		||||
	GList         * fields;             ///< List of StardictEntryField's
 | 
			
		||||
	GList         * fields;             ///< List of StardictEntryField-s
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
struct stardict_entry_class
 | 
			
		||||
@@ -209,4 +210,4 @@ struct stardict_entry_class
 | 
			
		||||
GType stardict_entry_get_type (void);
 | 
			
		||||
const GList *stardict_entry_get_fields (StardictEntry *sde) G_GNUC_PURE;
 | 
			
		||||
 | 
			
		||||
 #endif  // ! STARDICT_H
 | 
			
		||||
#endif  // ! STARDICT_H
 | 
			
		||||
 
 | 
			
		||||
@@ -284,7 +284,7 @@ G_GNUC_END_IGNORE_DEPRECATIONS
 | 
			
		||||
		fatal ("Error: option parsing failed: %s\n", error->message);
 | 
			
		||||
 | 
			
		||||
	if (argc != 3)
 | 
			
		||||
		fatal ("%s", g_option_context_get_help (ctx, TRUE, FALSE));
 | 
			
		||||
		fatal ("%s", g_option_context_get_help (ctx, TRUE, NULL));
 | 
			
		||||
 | 
			
		||||
	g_option_context_free (ctx);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										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;
 | 
			
		||||
}
 | 
			
		||||
@@ -179,7 +179,7 @@ main (int argc, char *argv[])
 | 
			
		||||
	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, FALSE));
 | 
			
		||||
		fatal ("%s", g_option_context_get_help (ctx, TRUE, NULL));
 | 
			
		||||
	g_option_context_free (ctx);
 | 
			
		||||
 | 
			
		||||
	template.version = SD_VERSION_3_0_0;
 | 
			
		||||
@@ -218,6 +218,6 @@ main (int argc, char *argv[])
 | 
			
		||||
		fatal ("Error: failed to write the dictionary: %s\n", error->message);
 | 
			
		||||
 | 
			
		||||
	generator_free (generator);
 | 
			
		||||
	fclose (fsorted);
 | 
			
		||||
	pclose (fsorted);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
@@ -3,7 +3,7 @@
 | 
			
		||||
 *
 | 
			
		||||
 * The external filter needs to process NUL-separated textual entries.
 | 
			
		||||
 *
 | 
			
		||||
 * Example: transform input.ifo output -- perl -p0e s/bullshit/soykaf/g
 | 
			
		||||
 * Example: tdv-transform input.ifo output -- perl -p0e s/bullshit/soykaf/g
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2020, Přemysl Eric Janouch <p@janouch.name>
 | 
			
		||||
 *
 | 
			
		||||
@@ -155,7 +155,7 @@ main (int argc, char *argv[])
 | 
			
		||||
		fatal ("Error: option parsing failed: %s\n", error->message);
 | 
			
		||||
 | 
			
		||||
	if (argc < 3)
 | 
			
		||||
		fatal ("%s", g_option_context_get_help (ctx, TRUE, FALSE));
 | 
			
		||||
		fatal ("%s", g_option_context_get_help (ctx, TRUE, NULL));
 | 
			
		||||
 | 
			
		||||
	// GLib is bullshit, getopt_long() always correctly removes this
 | 
			
		||||
	gint program_argv_start = 3;
 | 
			
		||||
										
											
												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
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										271
									
								
								src/utils.c
									
									
									
									
									
								
							
							
						
						
									
										271
									
								
								src/utils.c
									
									
									
									
									
								
							@@ -1,7 +1,7 @@
 | 
			
		||||
/*
 | 
			
		||||
 * utils.c: miscellaneous utilities
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2013 - 2020, 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
 | 
			
		||||
 * purpose with or without fee is hereby granted.
 | 
			
		||||
@@ -16,23 +16,49 @@
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
// getpwnam_r, _SC_GETPW_R_SIZE_MAX
 | 
			
		||||
#ifndef _POSIX_C_SOURCE
 | 
			
		||||
#define _POSIX_C_SOURCE 200112L
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
#include <glib.h>
 | 
			
		||||
#include <glib/gprintf.h>
 | 
			
		||||
#include <gio/gio.h>
 | 
			
		||||
#include <glib/gstdio.h>
 | 
			
		||||
 | 
			
		||||
#include <stdlib.h>
 | 
			
		||||
#include <errno.h>
 | 
			
		||||
#include <stdarg.h>
 | 
			
		||||
 | 
			
		||||
#include <curses.h>
 | 
			
		||||
#include <termios.h>
 | 
			
		||||
#ifndef TIOCGWINSZ
 | 
			
		||||
#include <sys/ioctl.h>
 | 
			
		||||
#endif  // ! TIOCGWINSZ
 | 
			
		||||
#ifndef WIN32
 | 
			
		||||
#include <pwd.h>
 | 
			
		||||
#endif  // ! WIN32
 | 
			
		||||
 | 
			
		||||
#include "config.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.
 | 
			
		||||
gboolean
 | 
			
		||||
stream_read_all (GByteArray *ba, GInputStream *is, GError **error)
 | 
			
		||||
@@ -80,28 +106,6 @@ xstrtoul (unsigned long *out, const char *s, int base)
 | 
			
		||||
	return errno == 0 && !*end && end != s;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Didn't want to have this ugly piece of code in the main source file;
 | 
			
		||||
// the standard endwin/refresh sequence makes the terminal flicker.
 | 
			
		||||
void
 | 
			
		||||
update_curses_terminal_size (void)
 | 
			
		||||
{
 | 
			
		||||
#if defined (HAVE_RESIZETERM) && defined (TIOCGWINSZ)
 | 
			
		||||
	struct winsize size;
 | 
			
		||||
	if (!ioctl (STDOUT_FILENO, TIOCGWINSZ, (char *) &size))
 | 
			
		||||
	{
 | 
			
		||||
		char *row = getenv ("LINES");
 | 
			
		||||
		char *col = getenv ("COLUMNS");
 | 
			
		||||
		unsigned long tmp;
 | 
			
		||||
		resizeterm (
 | 
			
		||||
			(row && xstrtoul (&tmp, row, 10)) ? tmp : size.ws_row,
 | 
			
		||||
			(col && xstrtoul (&tmp, col, 10)) ? tmp : size.ws_col);
 | 
			
		||||
	}
 | 
			
		||||
#else  // HAVE_RESIZETERM && TIOCGWINSZ
 | 
			
		||||
	endwin ();
 | 
			
		||||
	refresh ();
 | 
			
		||||
#endif  // HAVE_RESIZETERM && TIOCGWINSZ
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/// Print a fatal error message and terminate the process immediately.
 | 
			
		||||
void
 | 
			
		||||
fatal (const gchar *format, ...)
 | 
			
		||||
@@ -112,3 +116,214 @@ fatal (const gchar *format, ...)
 | 
			
		||||
	exit (EXIT_FAILURE);
 | 
			
		||||
	va_end (ap);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// At times, GLib even with its sheer size is surprisingly useless,
 | 
			
		||||
// and I need to port some code over from "liberty".
 | 
			
		||||
 | 
			
		||||
static const gchar **
 | 
			
		||||
get_xdg_config_dirs (void)
 | 
			
		||||
{
 | 
			
		||||
	GPtrArray *paths = g_ptr_array_new ();
 | 
			
		||||
	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;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										32
									
								
								src/utils.h
									
									
									
									
									
								
							
							
						
						
									
										32
									
								
								src/utils.h
									
									
									
									
									
								
							@@ -1,7 +1,7 @@
 | 
			
		||||
/*
 | 
			
		||||
 * utils.h: miscellaneous utilities
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright (c) 2013 - 2020, 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
 | 
			
		||||
 * purpose with or without fee is hereby granted.
 | 
			
		||||
@@ -19,6 +19,11 @@
 | 
			
		||||
#ifndef 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
 | 
			
		||||
/// in the variable "imid".
 | 
			
		||||
#define BINARY_SEARCH_BEGIN(max, compare)                                     \
 | 
			
		||||
@@ -36,10 +41,33 @@
 | 
			
		||||
		}                                                                     \
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
gchar *xdxf_to_pango_markup_with_reduced_effort (const gchar *xml);
 | 
			
		||||
 | 
			
		||||
gboolean stream_read_all (GByteArray *ba, GInputStream *is, GError **error);
 | 
			
		||||
gchar *stream_read_string (GDataInputStream *dis, GError **error);
 | 
			
		||||
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
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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: 94a77a10d8...f9a102456f
									
								
							
		Reference in New Issue
	
	Block a user