diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..567609b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index fbf41c6..0000000 --- a/.gitmodules +++ /dev/null @@ -1,3 +0,0 @@ -[submodule "lib/libkbbi"] - path = lib/libkbbi - url = git@github.com:misterabdul/libkbbi.git diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..01ef59b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,112 @@ +cmake_minimum_required(VERSION 3.9) + +# Project description +project( + kbbi-gtk + VERSION 1.0.0 + DESCRIPTION "KBBI Offline with GTK3" +) + +# Check required package & libraries +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK3 REQUIRED gtk+-3.0) +pkg_check_modules(WEBKIT2GTK4 REQUIRED webkit2gtk-4.0) +pkg_check_modules(LIBKBBI REQUIRED libkbbi) + +# Set C standard +set(CMAKE_C_STANDARD 99) + +# Targets +add_executable( + ${PROJECT_NAME} + src/main.c + src/ui.c +) + +# Properties +set_target_properties( + ${PROJECT_NAME} PROPERTIES + VERSION ${PROJECT_VERSION} +) + +# Includes +target_include_directories( + ${PROJECT_NAME} + PRIVATE src +) + +# Link required libraries +target_link_libraries( + ${PROJECT_NAME} + ${GTK3_LIBRARIES} + ${WEBKIT2GTK4_LIBRARIES} + ${LIBKBBI_LIBRARIES} +) + +# Include library's public header directory +target_include_directories( + ${PROJECT_NAME} + PUBLIC ${GTK_INCLUDE_DIRS} + PUBLIC ${WEBKIT2GTK4_INCLUDE_DIRS} + PUBLIC ${LIBKBBI_INCLUDE_DIRS} +) + +# Add library's extra compiler flags +target_compile_options( + ${PROJECT_NAME} + PUBLIC ${GTK3_CLFAGS_OTHER} + PUBLIC ${WEBKIT2GTK4_CLFAGS_OTHER} + PUBLIC ${LIBKBBI_CLFAGS_OTHER} +) + +# CMake setup +if(NOT CMAKE_BUILD_TYPE) + set(CMAKE_BUILD_TYPE Release) +endif() +set(CMAKE_C_FLAGS "-Wall -Wextra -fvisibility=hidden") +set(CMAKE_C_FLAGS_DEBUG "-g") +set(CMAKE_C_FLAGS_RELEASE "-O3") + +set(DESKTOP_PATH "share/applications/") +set(DESKTOP_ICON_PATH "share/icons/hicolor/128x128/apps") +set(DESKTOP_NAME "KBBI Offline") +set(DESKTOP_EXEC ${PROJECT_NAME}) +set(DESKTOP_ICON_REF "${CMAKE_INSTALL_PREFIX}/${DESKTOP_ICON_PATH}/${PROJECT_NAME}.png") + +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/application.desktop.in" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.desktop" + @ONLY +) +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/assets/icons/icon.png" + "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.png" + COPYONLY +) + +# Install the file +install( + TARGETS ${PROJECT_NAME} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} +) +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.desktop + DESTINATION ${DESKTOP_PATH} +) +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.png + DESTINATION ${DESKTOP_ICON_PATH} +) + +# Expand uninstall script +configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/uninstall.cmake" + IMMEDIATE @ONLY +) + +# Add uninstall target +add_custom_target( + uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/uninstall.cmake +) diff --git a/README.md b/README.md index d927c8c..be85e7b 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,29 @@ KBBI offline remake with GTK3. This is my hobby project to learn more about C programming, widget programming using GTK, and dynamic library. Feel free to explore, fork, or create a pull request if you interested. ![Alt text](./assets/screenshots/screenshot_1.png "KBBI Offline") + +## Todo + +- [ ] AppImage packaging +- [ ] Unit testing with CMake (?) + +## Building + +```sh +# Create build directory +$ mkdir -p build +$ cd build + +# Run cmake to generate build files +$ cmake .. + +# Run make to start building +$ make + +# Optional: run install script to install the apps +$ sudo make install + +# Optional: run uninstall script to uninstall the apps +$ sudo make uninstall + +``` diff --git a/application.desktop.in b/application.desktop.in new file mode 100644 index 0000000..5835fa0 --- /dev/null +++ b/application.desktop.in @@ -0,0 +1,7 @@ +[Desktop Entry] +Version=1.0 +Type=Application +Name=@DESKTOP_NAME@ +Exec=@DESKTOP_EXEC@ +Icon=@DESKTOP_ICON_REF@ +Categories=Utility; diff --git a/assets/icons/icon.png b/assets/icons/icon.png new file mode 100644 index 0000000..f42b552 Binary files /dev/null and b/assets/icons/icon.png differ diff --git a/bin/.gitignore b/bin/.gitignore deleted file mode 100644 index d6b7ef3..0000000 --- a/bin/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/lib/libkbbi b/lib/libkbbi deleted file mode 160000 index 0c1af35..0000000 --- a/lib/libkbbi +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 0c1af351af6c68d4f267a6efb9c2b388c6ab5841 diff --git a/makefile b/makefile deleted file mode 100644 index 52b9e6c..0000000 --- a/makefile +++ /dev/null @@ -1,42 +0,0 @@ -BIN := kbbi-gtk - -SRCDIR := src -OBJDIR := obj -BINDIR := bin -LIBDIR := lib - -LIB_KBBI := libkbbi - -PKG_CFG_GTK4 := $(shell pkg-config --cflags --libs gtk+-3.0 webkit2gtk-4.0) -ldl - -CFLAGS ?= -Wall -Wextra -g - -BIN_SRCS := $(shell find $(SRCDIR) -name *.c) -BIN_OBJS := $(subst $(SRCDIR)/,$(SRCDIR)_,$(BIN_SRCS:%=$(OBJDIR)/%.o)) - -.PHONY: all - -all: $(BINDIR)/$(BIN) $(BINDIR)/$(LIB_KBBI).so - -clean: - @$(RM) -rf bin/* - @$(RM) -rf obj/* - -# Link all the objects to create main binary -$(BINDIR)/$(BIN): $(BIN_OBJS) - @echo LINK $(BIN) - @$(CC) $(BIN_OBJS) -o $@ $(PKG_CFG_GTK4) $(CFLAGS) - -# Compile each main source code -$(OBJDIR)/$(SRCDIR)_%.c.o: $(SRCDIR)/%.c - @echo CC $< - @$(CC) -c $< -o $@ $(PKG_CFG_GTK4) $(CFLAGS) - -# Move libkbbi.so -$(BINDIR)/$(LIB_KBBI).so: $(LIBDIR)/$(LIB_KBBI)/bin/$(LIB_KBBI).so - @mv $(LIBDIR)/$(LIB_KBBI)/bin/$(LIB_KBBI).so $(BINDIR)/ - -# Compile libkbbi.so -$(LIBDIR)/$(LIB_KBBI)/bin/$(LIB_KBBI).so: - @cd $(LIBDIR)/$(LIB_KBBI); \ - $(MAKE) all diff --git a/obj/.gitignore b/obj/.gitignore deleted file mode 100644 index d6b7ef3..0000000 --- a/obj/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore diff --git a/src/lib.c b/src/lib.c deleted file mode 100644 index 59f2efb..0000000 --- a/src/lib.c +++ /dev/null @@ -1,157 +0,0 @@ -#include -#include -#include - -#include "lib.h" - -/** - * The Lib's private data struct. - */ -typedef struct _private -{ - void* soHandler; - Results (*initResult)(void); - void (*freeResult)(Results); - int (*search)(Results*, int*, char*, int); - int (*count)(void); - char* (*version)(void); -} * Private; - -int -Lib_load(Lib* lib, const char* path) -{ - void* soHandler = dlopen(path, RTLD_LAZY); - if (!soHandler) - return 0; - - /* clear any existing eror */ - dlerror(); - - Results (*initResult)(void); - void (*freeResult)(Results); - int (*search)(Results*, int*, char*, int); - int (*count)(void); - char* (*version)(void); - - *(void**)(&initResult) = dlsym(soHandler, "init_result"); - *(void**)(&freeResult) = dlsym(soHandler, "free_result"); - *(void**)(&search) = dlsym(soHandler, "search"); - *(void**)(&count) = dlsym(soHandler, "count"); - *(void**)(&version) = dlsym(soHandler, "version"); - - if (dlerror()) - return 0; - - Private libPrivateInstance = malloc(sizeof(struct _private)); - libPrivateInstance->soHandler = soHandler; - libPrivateInstance->initResult = initResult; - libPrivateInstance->freeResult = freeResult; - libPrivateInstance->search = search; - libPrivateInstance->count = count; - libPrivateInstance->version = version; - - Lib libInstance = malloc(sizeof(struct _lib)); - libInstance->results = NULL; - libInstance->resultSize = 0; - libInstance->private = libPrivateInstance; - *lib = libInstance; - - return 1; -} - -/** - * Custom function to free the result recursively. - * - * @param[in] result - */ -void -freeResult(Results result) -{ - if (result) { - // if (result->artikata) - // free(result->artikata); - // if (result->katakunci) - // free(result->katakunci); - freeResult(result->next); - free(result); - } -} - -void -Lib_freeResult(Lib* libInstance) -{ - if (*libInstance) - freeResult((*libInstance)->results); - - (*libInstance)->results = NULL; - (*libInstance)->resultSize = 0; -} - -void -Lib_close(Lib* libInstance) -{ - if (*libInstance) { - Private libPrivateInstance = (Private)(*libInstance)->private; - - if (libPrivateInstance && libPrivateInstance->soHandler) { - dlclose(libPrivateInstance->soHandler); - free(libPrivateInstance); - } - } - - free(*libInstance); - *libInstance = NULL; -} - -int -Lib_search(Lib libInstance, const char* query) -{ - int status = 0; - - if (libInstance && query) { - Private libPrivateInstance = (Private)libInstance->private; - - if (libPrivateInstance && libPrivateInstance->search && - libPrivateInstance->initResult && libPrivateInstance->freeResult) { - Results results = libPrivateInstance->initResult(); - int resultSize = 0; - status = - libPrivateInstance->search(&results, &resultSize, query, strlen(query)); - - if (libInstance->results) - libPrivateInstance->freeResult(libInstance->results); - libInstance->results = results; - libInstance->resultSize = resultSize; - } - } - - return status; -} - -int -Lib_count(Lib libInstance) -{ - if (libInstance) { - Private libPrivateInstance = (Private)libInstance->private; - - if (libPrivateInstance && libPrivateInstance->count) { - return libPrivateInstance->count(); - } - } - - return 0; -} - -char* -Lib_version(Lib libInstance) -{ - if (libInstance) { - Private libPrivateInstance = (Private)libInstance->private; - - if (libPrivateInstance && libPrivateInstance->version) { - return libPrivateInstance->version(); - } - } - - return NULL; -} diff --git a/src/lib.h b/src/lib.h deleted file mode 100644 index a65a5e2..0000000 --- a/src/lib.h +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef _LIB_H -#define _LIB_H - -/** - * The results struct. - */ -typedef struct _results -{ - char* katakunci; - char* artikata; - struct _results* next; -} * Results; - -/** - * The lib struct. - */ -typedef struct _lib -{ - Results results; - int resultSize; - void* private; -} * Lib; - -/** - * Load the lib from given path. - * - * @param[out] lib The lib instance. - * @param[in] path The lib's path. - * @return Result of the load lib process. - */ -int -Lib_load(Lib* lib, const char* path); - -/** - * Close the lib. - * - * @param[out] lib The lib instance. - */ -void -Lib_close(Lib* lib); - -/** - * Free the result instance. - * - * @param[out] lib The lib instance. - */ -void -Lib_freeResult(Lib* lib); - -/** - * Do search on lib with given query. - * - * @param[in] lib The lib instance. - * @param[in] query The search query. - * @return Status of the search (found or not found). - */ -int -Lib_search(Lib lib, const char* query); - -/** - * Count all words inside the lib. - * - * @param[in] lib The lib instance. - * @return Total number of words inside the lib. - */ -int -Lib_count(Lib lib); - -/** - * Get the current lib version. - * - * @param[in] lib The lib instance. - * @return The current lib version string. - */ -char* -Lib_version(Lib lib); - -#endif diff --git a/src/main.c b/src/main.c index 16c3843..937e751 100644 --- a/src/main.c +++ b/src/main.c @@ -1,6 +1,8 @@ #include +#include + +#include -#include "lib.h" #include "ui.h" #define APP_ID "io.github.misterabdul.kbbi-gtk" @@ -8,12 +10,7 @@ #define WINDOW_WIDTH 500 #define WINDOW_HEIGHT 400 -#define LIB_KBBI_SO_PATH "./libkbbi.so" - -/** - * The lib instance. - */ -Lib lib = NULL; +KBBI_Results results = NULL; /** * Result words array. @@ -45,39 +42,43 @@ onDialogResponded(UI ui) void onSearchButtonClicked(UI ui, char* query) { - UI_setWebViewContent(ui, "
"); + if (strlen(query) > 0) { + UI_setWebViewContent(ui, "
"); - int found = Lib_search(lib, query); + if (!results) + results = KBBI_resultInit(); - if (found && lib->results) { - char** words = malloc(lib->resultSize * sizeof(char*)); - char** means = malloc(lib->resultSize * sizeof(char*)); + int resultSize = 0; + int found = KBBI_search(&results, &resultSize, query, strlen(query)); - Results results = lib->results; - for (int i = 0; i < lib->resultSize; i++) { - if (!results) - break; + if (found && results) { + char** words = malloc(resultSize * sizeof(char*)); + char** means = malloc(resultSize * sizeof(char*)); - if (results->katakunci) - words[i] = results->katakunci; + KBBI_Results resultNode = results; + for (int i = 0; i < resultSize; i++) { + if (!resultNode) + break; - if (results->artikata) - means[i] = results->artikata; + if (resultNode->katakunci) + words[i] = resultNode->katakunci; - results = results->next; - } + if (resultNode->artikata) + means[i] = resultNode->artikata; - UI_setListViewItems(ui, words, lib->resultSize); + resultNode = resultNode->next; + } - if (resultWords) - free(resultWords); - resultWords = words; + UI_setListViewItems(ui, words, resultSize); - if (resultMeans) - free(resultMeans); - resultMeans = means; + if (resultWords) + free(resultWords); + resultWords = words; - Lib_freeResult(&lib); + if (resultMeans) + free(resultMeans); + resultMeans = means; + } } } @@ -102,13 +103,6 @@ onListViewItemClicked(UI ui, char* word, int index) void onAppRunning(UI ui) { - int isLoaded = Lib_load(&lib, LIB_KBBI_SO_PATH); - if (!isLoaded) - UI_showDialog(ui, - "Kesalahan", - "Tidak dapat memuat file \"libkbbi.so\"", - onDialogResponded); - UI_onListViewItemClicked(ui, onListViewItemClicked); UI_onSearchButtonClicked(ui, onSearchButtonClicked); } @@ -127,11 +121,15 @@ main(int argc, char** argv) int status = UI_run(ui, argc, argv, onAppRunning); - if (lib) - Lib_close(&lib); - if (ui) UI_destroy(&ui); + if (results) + KBBI_resultFree(results); + if (resultWords) + free(resultWords); + if (resultMeans) + free(resultMeans); + return status; } diff --git a/uninstall.cmake.in b/uninstall.cmake.in new file mode 100644 index 0000000..c2d34d4 --- /dev/null +++ b/uninstall.cmake.in @@ -0,0 +1,21 @@ +if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") +endif() + +file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +foreach(file ${files}) + message(STATUS "Uninstalling $ENV{DESTDIR}${file}") + if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + exec_program( + "@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval + ) + if(NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") + endif() + else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File $ENV{DESTDIR}${file} does not exist.") + endif() +endforeach()