Mercurial > minori
changeset 250:c130f47f6f48
*: many many changes
e.g. the search page is actually implemented now!
author | Paper <paper@paper.us.eu.org> |
---|---|
date | Sun, 04 Feb 2024 21:17:17 -0500 |
parents | 6b2441c776dd |
children | 4d635d3e168a |
files | Makefile.am configure.ac include/core/anime.h include/core/config.h include/core/session.h include/gui/pages/search.h include/gui/pages/statistics.h include/gui/window.h include/services/anilist.h include/services/services.h m4/autotroll.m4 m4/m4_ax_have_qt.m4 src/core/config.cc src/core/filesystem.cc src/core/strings.cc src/gui/dialog/information.cc src/gui/pages/anime_list.cc src/gui/pages/search.cc src/gui/pages/statistics.cc src/gui/pages/torrents.cc src/gui/widgets/anime_info.cc src/gui/widgets/sidebar.cc src/gui/window.cc src/main.cc src/services/anilist.cc src/services/services.cc src/sys/glib/dark_theme.cc src/sys/osx/filesystem.cc |
diffstat | 28 files changed, 1502 insertions(+), 584 deletions(-) [+] |
line wrap: on
line diff
--- a/Makefile.am Wed Jan 24 20:18:59 2024 -0500 +++ b/Makefile.am Sun Feb 04 21:17:17 2024 -0500 @@ -5,23 +5,23 @@ rc/locale/es.ts .ts.qm: - @MKDIR_P@ `dirname $@`; \ - @QT_LRELEASE@ $< -qm $@ + $(MKDIR_P) `dirname $@`; \ + $(LRELEASE) $< -qm $@ minori_locale_qm = $(minori_locale_ts:.ts=.qm) # this has to be in the root build folder -translations.qrc: $(minori_locale_qm) +rc/locale/translations.qrc: $(minori_locale_qm) printf "<!DOCTYPE rcc><RCC version=\"1.0\">\n\t<qresource prefix=\"locale/\">\n" > $@; \ for q in $(minori_locale_qm); do \ - printf "\t\t<file alias=\"%s\">%s</file>\n" "`basename $$q`" "$$q" >> $@; \ - done; \ + printf "\t\t<file>%s</file>\n" "`basename $$q`" >> $@; \ + done; printf "\t</qresource>\n</RCC>\n" >> $@; minori_qtrc = \ $(top_srcdir)/rc/icons/icons.qrc \ $(top_srcdir)/rc/player_data.qrc \ - translations.qrc + rc/locale/translations.qrc if BUILD_WIN @@ -30,7 +30,7 @@ endif rc/final_qrc.cc: $(minori_qtrc) - @QT_RCC@ -o $@ $(minori_qtrc) + $(RCC) -o $@ $(minori_qtrc) minori_qtheaders = \ include/core/http.h \ @@ -84,8 +84,8 @@ if BUILD_GLIB files_glib = src/sys/glib/dark_theme.cc -cflags_glib = @GIO_CFLAGS@ -libs_glib = @GIO_LIBS@ +cflags_glib = $(GLIB_CFLAGS) +libs_glib = $(GLIB_LIBS) endif if BUILD_WIN @@ -101,8 +101,9 @@ .rc.$(OBJEXT): $(WINDRES) $(WRCFLAGS) -i $< -o $@ files_windres=rc/win32/version.rc rc/win32/resource.rc -endif -endif +endif # BUILD_WINDRES + +endif # BUILD_WIN if BUILD_OSX files_osx = src/sys/osx/dark_theme.cc src/sys/osx/filesystem.cc src/sys/osx/permissions.cc @@ -156,9 +157,11 @@ $(files_osx) \ $(files_glib) \ $(files_win) \ + $(files_windres) + +nodist_minori_SOURCES = \ $(minori_moc_sources) \ - rc/final_qrc.cc \ - $(files_windres) + rc/final_qrc.cc minori_includes = \ -I$(top_srcdir)/include \ @@ -167,16 +170,16 @@ -I$(top_srcdir)/dep/anitomy \ -I$(top_srcdir)/dep -minori_CPPFLAGS = @LIBCURL_CPPFLAGS@ $(minori_includes) -minori_CXXFLAGS = @QT_CXXFLAGS@ $(cflags_osx) $(cflags_glib) $(cflags_win) -std=c++17 -minori_LDFLAGS = $(ldflags_osx) $(ldflags_win) +minori_CPPFLAGS = $(QT_CPPFLAGS) $(LIBCURL_CPPFLAGS) $(minori_includes) +minori_CXXFLAGS = $(cflags_osx) $(cflags_glib) $(cflags_win) +minori_LDFLAGS = $(QT_LDFLAGS) $(ldflags_osx) $(ldflags_win) minori_DEPENDENCIES = dep/pugixml/libpugixml.la dep/animia/libanimia.la dep/anitomy/libanitomy.la -minori_LDADD = $(libs_glib) $(libs_osx) $(libs_win) @LIBCURL@ @QT_LIBS@ dep/pugixml/libpugixml.la dep/animia/libanimia.la dep/anitomy/libanitomy.la +minori_LDADD = $(libs_glib) $(LIBCURL) $(QT_LIBS) $(libs_osx) $(libs_win) $(minori_DEPENDENCIES) .h_moc.cc: - @MKDIR_P@ -- `dirname $@` - @QT_MOC@ -o $@ $(minori_includes) $< + $(MKDIR_P) -- `dirname $@` + $(MOC) -o $@ $(minori_includes) $< SUFFIXES = .h _moc.cc .ts .qm SUBDIRS = $(subdirs)
--- a/configure.ac Wed Jan 24 20:18:59 2024 -0500 +++ b/configure.ac Sun Feb 04 21:17:17 2024 -0500 @@ -9,61 +9,59 @@ AM_INIT_AUTOMAKE([-Wall -Wportability foreign subdir-objects]) -# Do we have a C++17 compiler +dnl Do we have a C++17 compiler : ${CXXFLAGS=""} AC_PROG_CXX AX_CXX_COMPILE_STDCXX([17], [noext], [mandatory]) -# Init libtool +dnl Init libtool AM_PROG_AR LT_INIT -# Qt? -AX_HAVE_QT +dnl Qt? +AT_WITH_QT([widgets gui core], [], [], [have_qt=no], [have_qt=yes]) + +AS_IF([test "x$have_qt" = "xno"], [AC_MSG_ERROR([*** Qt not found.])]) -if test "x$have_qt" = "xno"; then - AC_MSG_ERROR([*** Qt not found.]) -fi +dnl need this for moc +AC_PROG_MKDIR_P +AC_SUBST([MKDIR_P]) -# need this for moc -AC_PROG_MKDIR_P - -# libcurl? +dnl libcurl? LIBCURL_CHECK_CONFIG([yes], [7.7.2], [have_libcurl=yes], [have_libcurl=no]) -if test "x$have_libcurl" = "xno"; then - AC_MSG_ERROR([*** libcurl not found.]) -fi +AS_IF([test "x$have_libcurl" = "xno"], [AC_MSG_ERROR([*** libcurl not found.])]) build_windows=no build_osx=no +build_linux=no build_glib=no -case "${host_os}" in - cygwin*|mingw*) - # Windows - build_windows=yes - AC_CHECK_TOOL([WINDRES], [windres]) - AC_SUBST(WINDRES) - AC_PROG_SED # We need sed for version numbers in windres - AC_SUBST(SED) - AC_DEFINE(WIN32) - ;; - darwin*) - # Mac OS X - build_osx=yes - AC_DEFINE(MACOSX) - ;; - *) - if test "x$host_os" = "xlinux"; then - AC_DEFINE(LINUX) - fi - # Everything else - AC_SUBST([GIO_CFLAGS]) - AC_SUBST([GIO_LIBS]) - PKG_CHECK_MODULES([GIO], [gio-2.0], [build_glib=yes], []) - ;; -esac +AS_CASE(["$host_os"], + [cygwin*|mingw*], [build_windows=yes], + [darwin*], [build_osx=yes], + [linux*], [build_linux=yes], + []) + +if test "x$build_windows" = "xyes"; then + AC_DEFINE([WIN32]) + + dnl Check for windres + AC_CHECK_TOOL([WINDRES], [windres]) + AC_SUBST([WINDRES]) +elif test "x$build_osx" = "xyes"; then + AC_DEFINE([MACOSX]) +else + AS_IF([test "x$build_linux" = "xyes"], [AC_DEFINE([linux])]) + + PKG_CHECK_MODULES([GLIB], [gio-2.0 glib-2.0], [build_glib=yes], [build_glib=no]) + if test "x$build_glib" = "xyes"; then + AC_DEFINE([GLIB]) + + AC_SUBST([GLIB_CFLAGS]) + AC_SUBST([GLIB_LIBS]) + fi +fi AM_CONDITIONAL([BUILD_WIN], [test "x$build_windows" = "xyes"]) AM_CONDITIONAL([BUILD_OSX], [test "x$build_osx" = "xyes"])
--- a/include/core/anime.h Wed Jan 24 20:18:59 2024 -0500 +++ b/include/core/anime.h Sun Feb 04 21:17:17 2024 -0500 @@ -147,7 +147,7 @@ std::vector<std::string> GetProducers() const; SeriesFormat GetFormat() const; SeriesSeason GetSeason() const; - int GetAudienceScore() const; /* should be double once MAL and Kitsu are implemented */ + int GetAudienceScore() const; std::string GetSynopsis() const; int GetDuration() const; std::string GetPosterUrl() const;
--- a/include/core/config.h Wed Jan 24 20:18:59 2024 -0500 +++ b/include/core/config.h Sun Feb 04 21:17:17 2024 -0500 @@ -20,7 +20,7 @@ class Config { public: int Load(); - int Save() const; + int Save(); Anime::Services service; Theme::Theme theme;
--- a/include/core/session.h Wed Jan 24 20:18:59 2024 -0500 +++ b/include/core/session.h Sun Feb 04 21:17:17 2024 -0500 @@ -7,6 +7,8 @@ #include "semver/semver.hpp" +class MainWindow; + struct Session { public: Session() { timer.start(); }
--- a/include/gui/pages/search.h Wed Jan 24 20:18:59 2024 -0500 +++ b/include/gui/pages/search.h Sun Feb 04 21:17:17 2024 -0500 @@ -1,12 +1,66 @@ #ifndef __gui__pages__search_h #define __gui__pages__search_h -#include <QWidget> + +#include "core/anime.h" + +#include <QFrame> +#include <QAbstractListModel> +#include <QSortFilterProxyModel> +#include <QItemSelection> + +class QTreeView; + +class SearchPageListSortFilter final : public QSortFilterProxyModel { + Q_OBJECT + + public: + SearchPageListSortFilter(QObject* parent = nullptr); + + protected: + bool lessThan(const QModelIndex& l, const QModelIndex& r) const override; +}; + +class SearchPageListModel final : public QAbstractListModel { + Q_OBJECT -class SearchPage final : public QWidget { + public: + enum columns { + SR_TITLE, + SR_TYPE, + SR_EPISODES, + SR_SCORE, + SR_SEASON, + + NB_COLUMNS + }; + + SearchPageListModel(QObject* parent); + ~SearchPageListModel() override = default; + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + int columnCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role) const override; + QVariant headerData(const int section, const Qt::Orientation orientation, const int role) const override; + Qt::ItemFlags flags(const QModelIndex& index) const override; + + void ParseSearch(const std::vector<int>& ids); + Anime::Anime* GetAnimeFromIndex(const QModelIndex& index) const; + + private: + std::vector<int> ids; +}; + +class SearchPage final : public QFrame { Q_OBJECT public: SearchPage(QWidget* parent = nullptr); -}; + void Search(const std::string& search); + void DisplayListMenu(); + void ItemDoubleClicked(); + private: + SearchPageListModel* model = nullptr; + SearchPageListSortFilter* sort_model = nullptr; + QTreeView* treeview = nullptr; +}; #endif // __gui__pages__search_h
--- a/include/gui/pages/statistics.h Wed Jan 24 20:18:59 2024 -0500 +++ b/include/gui/pages/statistics.h Sun Feb 04 21:17:17 2024 -0500 @@ -22,9 +22,6 @@ void showEvent(QShowEvent*) override; private: - std::string MinutesToDateString(int minutes); - std::string SecondsToDateString(int seconds); - std::shared_ptr<TextWidgets::LabelledSection> _anime_list; std::shared_ptr<Graph<int>> _score_distribution_graph; std::shared_ptr<TextWidgets::LabelledSection> _application;
--- a/include/gui/window.h Wed Jan 24 20:18:59 2024 -0500 +++ b/include/gui/window.h Sun Feb 04 21:17:17 2024 -0500 @@ -13,6 +13,8 @@ #include <QThread> #include "gui/widgets/sidebar.h" +class QMenu; + Q_DECLARE_METATYPE(std::vector<std::string>); class PlayingThread : public QThread { @@ -32,11 +34,24 @@ Q_OBJECT public: + enum class Pages { + NOW_PLAYING, + + ANIME_LIST, + HISTORY, + STATISTICS, + + SEARCH, + SEASONS, + TORRENTS + }; + MainWindow(QWidget* parent = nullptr); void SetActivePage(QWidget* page); void CreateBars(); void AddMainWidgets(); void RetranslateUI(); + void UpdateFolderMenu(); void AsyncSynchronize(QAction* action, QStackedWidget* stack); void changeEvent(QEvent* event) override; void showEvent(QShowEvent* event) override; @@ -47,7 +62,9 @@ std::unique_ptr<QStackedWidget> stack = nullptr; std::unique_ptr<SideBar> sidebar = nullptr; - std::unique_ptr<PlayingThread> thread = nullptr; + std::unique_ptr<PlayingThread> thread = nullptr; + + QMenu* folder_menu = nullptr; }; #endif // __window_h
--- a/include/services/anilist.h Wed Jan 24 20:18:59 2024 -0500 +++ b/include/services/anilist.h Sun Feb 04 21:17:17 2024 -0500 @@ -1,9 +1,8 @@ #ifndef __services__anilist_h #define __services__anilist_h -#include "core/anime.h" -#include "core/json.h" -#include <curl/curl.h> +#include <vector> +#include <string> namespace Services { namespace AniList { @@ -13,6 +12,9 @@ /* Read queries */ int GetAnimeList(); +/* Search query */ +std::vector<int> Search(const std::string& search); + /* Write queries (mutations) */ int UpdateAnimeEntry(int id);
--- a/include/services/services.h Wed Jan 24 20:18:59 2024 -0500 +++ b/include/services/services.h Sun Feb 04 21:17:17 2024 -0500 @@ -1,9 +1,13 @@ #ifndef __services__services_h #define __services__services_h +#include <vector> +#include <string> + namespace Services { void Synchronize(); +std::vector<int> Search(const std::string& search); void UpdateAnimeEntry(int id); bool Authorize();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/m4/autotroll.m4 Sun Feb 04 21:17:17 2024 -0500 @@ -0,0 +1,720 @@ +# Build Qt apps with the autotools (Autoconf/Automake). +# M4 macros. +# +# This file is part of AutoTroll. +# +# Copyright (C) 2006-2018 Benoit Sigoure <benoit.sigoure@lrde.epita.fr> +# Copyright (C) 2012-2023 Werner Lemberg <wl@gnu.org> +# +# AutoTroll is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +# 02110-1301, USA. +# +# In addition, as a special exception, the copyright holders of +# AutoTroll give you unlimited permission to copy, distribute and +# modify the configure scripts that are the output of Autoconf when +# processing the macros of AutoTroll. You need not follow the terms +# of the GNU General Public License when using or distributing such +# scripts, even though portions of the text of AutoTroll appear in +# them. The GNU General Public License (GPL) does govern all other +# use of the material that constitutes AutoTroll. +# +# This special exception to the GPL applies to versions of AutoTroll +# released by the copyright holders of AutoTroll. Note that people +# who make modified versions of AutoTroll are not obligated to grant +# this special exception for their modified versions; it is their +# choice whether to do so. The GNU General Public License gives +# permission to release a modified version without this exception; +# this exception also makes it possible to release a modified version +# which carries forward this exception. + + # ------------- # + # DOCUMENTATION # + # ------------- # + +# Disclaimer: Tested with Qt 4.2, 4.8, Qt 5.x, and Qt 6 only. +# Feedback welcome. Simply invoke AT_WITH_QT in your configure.ac. +# AT_WITH_QT can take arguments which are documented in depth below. +# The default arguments are equivalent to the default .pro file +# generated by qmake. +# +# Invoking AT_WITH_QT will do the following: +# +# - Add option `--with-qt[=ARG]' to your configure script. Possible +# values for ARG are `yes' (which is the default) and `no' to +# enable and disable Qt support, respectively, or a path to the +# directory which contains the Qt binaries in case you have a +# non-stardard location. +# +# - Add option `--without-qt', which is equivalent to `--with-qt=no'. +# +# - On MacOS, add `-spec macx-g++' or `-spec macx-lang' (if `$CXX +# --version' output contains `clang'). This can be overridden with +# the QMAKESPEC environment variable, for example +# +# QMAKESPEC='macx-clang' ./configure ... +# +# (The QMAKESPEC variable is honoured for non-MacOS builds also.) +# +# - If Qt support is enabled, define C preprocessor macro HAVE_QT. +# +# - Find the programs `qmake', `moc', `uic', and `rcc' and save them +# in the make variables $(QMAKE), $(MOC), $(UIC), and $(RCC). +# +# - Save the path to Qt binaries in $(QT_PATH). +# +# - Find the flags necessary to compile and link Qt, that is: +# +# * $(QT_DEFINES): -D's defined by qmake. +# * $(QT_CFLAGS): CFLAGS as defined by qmake (C?!) +# * $(QT_CXXFLAGS): CXXFLAGS as defined by qmake. +# * $(QT_INCPATH): -I's defined by qmake. +# * $(QT_CPPFLAGS): Same as $(QT_DEFINES) + $(QT_INCPATH). +# * $(QT_LFLAGS): LFLAGS defined by qmake. +# * $(QT_LDFLAGS): Same thing as $(QT_LFLAGS). +# * $(QT_LIBS): LIBS defined by qmake. +# +# - Provide @QT_STATIC_PLUGINS@, which holds some additional C++ +# declarations necessary for linking with static Qt plugins (for +# dynamic Qt builds it contains a dummy typedef declaration +# instead). Use this substitution in a `foo.cpp.in' C++ template +# file or something similar, which must be registered in +# configure.ac's call to AC_CONFIG_FILES so that a proper `foo.cpp' +# file gets created. Then compile and link `foo.cpp' with your +# program in the usual automake way. +# +# NOTE: It is not possible to automatically detect whether a Qt +# release earlier than version 5 is built as a static library! For +# this reason, @QT_STATIC_PLUGINS@ always contains the dummy +# typedef declaration if not using Qt5. +# +# You *MUST* invoke $(MOC) and/or $(UIC) by yourself where necessary. +# AutoTroll provides you with Makerules to ease this; here is a sample +# Makefile.am to use with AutoTroll which builds the code given in +# chapter 7 of the Qt Tutorial +# (http://doc.trolltech.com/4.2/tutorial-t7.html). +# +# ------------------------------------------------------------------------- +# include $(top_srcdir)/build-aux/autotroll.mk +# +# ACLOCAL_AMFLAGS = -I build-aux +# +# bin_PROGRAMS = lcdrange +# lcdrange_SOURCES = $(BUILT_SOURCES) lcdrange.cpp lcdrange.h main.cpp +# lcdrange_CXXFLAGS = $(QT_CXXFLAGS) $(AM_CXXFLAGS) +# lcdrange_CPPFLAGS = $(QT_CPPFLAGS) $(AM_CPPFLAGS) +# lcdrange_LDFLAGS = $(QT_LDFLAGS) $(LDFLAGS) +# lcdrange_LDADD = $(QT_LIBS) $(LDADD) +# +# BUILT_SOURCES = lcdrange.moc.cpp +# ------------------------------------------------------------------------- +# +# Note that your MOC, UIC, and RCC files *MUST* be listed explicitly +# in BUILT_SOURCES. If you name them properly (e.g. `.moc.cc', +# `.qrc.cc', `.ui.cc' -- of course you can use `.cpp' or `.cxx' or +# `.C' rather than `.cc') AutoTroll will build them automagically for +# you, using implicit rules defined in `autotroll.mk'. + +m4_define([_AUTOTROLL_SERIAL], + [m4_translit([ +# serial 17 +], [# +], [])]) + + +m4_ifdef([AX_INSTEAD_IF], + [], + [AC_DEFUN([AX_INSTEAD_IF], + [m4_ifval([$1], + [AC_MSG_WARN([$2]) + [$1]], + [AC_MSG_ERROR([$2])])])]) + + +# AX_PATH_TOOLS(VARIABLE, PROGS-TO-CHECK-FOR, [VALUE-IF-NOT-FOUND], [PATH]) +# ------------------------------------------------------------------------- +AC_DEFUN([AX_PATH_TOOLS], + [for ax_tool in $2; do + AC_PATH_TOOL([$1], [$ax_tool], [], [$4]) + test -n "$$1" && break + done + m4_ifval([$3], [test -n "$$1" || $1="$3"]) + ]) + + +m4_pattern_forbid([^AT_]) +m4_pattern_forbid([^_AT_]) + + +# AT_WITH_QT([QT_modules], [QT_config], [QT_misc], [RUN-IF-FAILED], [RUN-IF-OK]) +# ------------------------------------------------------------------------------ +# Enable Qt support and add an option --with-qt to the configure +# script. +# +# The QT_modules argument is optional and defines extra modules to +# enable or disable (it's equivalent to the QT variable in .pro +# files). Modules can be specified as follows: +# +# AT_WITH_QT => No argument -> No QT value. +# Qmake sets it to "core gui" by +# default. +# AT_WITH_QT([xml]) => QT += xml +# AT_WITH_QT([+xml]) => QT += xml +# AT_WITH_QT([-gui]) => QT -= gui +# AT_WITH_QT([xml -gui +sql svg]) => QT += xml sql svg +# QT -= gui +# +# The QT_config argument is also optional and follows the same +# convention as QT_modules. Instead of changing the QT variable, it +# changes the CONFIG variable, which is used to tweak configuration +# and compiler options. +# +# The last argument, QT_misc (also optional) will be copied as-is the +# .pro file used to guess how to compile Qt apps. You may use it to +# further tweak the build process of Qt apps if tweaking the QT or +# CONFIG variables isn't enough for you (for example, to control which +# static plugins get used). +# +# RUN-IF-FAILED is arbitrary code to execute if Qt cannot be found or +# if any problem happens. If this argument is omitted, then +# AC_MSG_ERROR will be called. RUN-IF-OK is arbitrary code to execute +# if Qt was successfully found. + +AC_DEFUN([AT_WITH_QT], + [AC_REQUIRE([AC_CANONICAL_HOST]) + AC_REQUIRE([AC_CANONICAL_BUILD]) + AC_REQUIRE([AC_PROG_CXX]) + + echo "$as_me: this is autotroll.m4[]_AUTOTROLL_SERIAL" \ + >& AS_MESSAGE_LOG_FD + + # This is a hack to get decent flow control with `break'. + for _qt_ignored in once; do + + AC_ARG_WITH([qt], + AS_HELP_STRING([--with-qt@<:@=ARG@:>@], + [Qt support. ARG can be `yes' (the default), `no', + or a path to Qt binaries; if `yes' or empty, + use PATH and some default directories to find Qt binaries])) + + if test x"$with_qt" = x"no"; then + break + else + AC_DEFINE([HAVE_QT],[1], + [Define if the Qt framework is available.]) + fi + + if test x"$with_qt" = x"yes"; then + QT_PATH= + else + QT_PATH=$with_qt + fi + + # Find Qt. + AC_ARG_VAR([QT_PATH], + [path to Qt binaries]) + QT_TOOL_PATH=$QT_PATH:$PATH + + # Find qmake. + AC_ARG_VAR([QMAKE], + [Qt Makefile generator command]) + AX_PATH_TOOLS([QMAKE], + [qmake qmake-qt5 qmake-qt4 qmake-qt3], + [missing], + [$QT_TOOL_PATH]) + if test x"$QMAKE" = xmissing; then + if test x"$with_qt" = "x"; then + with_qt="no" + else + AX_INSTEAD_IF([$4], + [Cannot find qmake. Try --with-qt=PATH.]) + fi + break + fi + + # Find moc (Meta Object Compiler). + AC_ARG_VAR([MOC], + [Qt Meta Object Compiler command]) + AX_PATH_TOOLS([MOC], + [moc moc-qt5 moc-qt4 moc-qt3], + [missing], + [$QT_TOOL_PATH]) + if test x"$MOC" = xmissing; then + AX_INSTEAD_IF([$4], + [Cannot find moc (Meta Object Compiler). Try --with-qt=PATH.]) + break + fi + + # Find uic (User Interface Compiler). + AC_ARG_VAR([UIC], + [Qt User Interface Compiler command]) + AX_PATH_TOOLS([UIC], + [uic uic-qt5 uic-qt4 uic-qt3 uic3], + [missing], + [$QT_TOOL_PATH]) + if test x"$UIC" = xmissing; then + AX_INSTEAD_IF([$4], + [Cannot find uic (User Interface Compiler). Try --with-qt=PATH.]) + break + fi + + # Find rcc (Qt Resource Compiler). + AC_ARG_VAR([RCC], + [Qt Resource Compiler command]) + AX_PATH_TOOLS([RCC], + [rcc rcc-qt5], + [missing], + [$QT_TOOL_PATH]) + if test x"$RCC" = xmissing; then + AC_MSG_WARN( + [Cannot find rcc (Qt Resource Compiler). Try --with-qt=PATH.]) + fi + + AC_ARG_VAR([LUPDATE], + [Qt Linguist updater command]) + AX_PATH_TOOLS([LUPDATE], + [lupdate lupdate-qt5 lrelease-qt6], + [missing], + [$QT_TOOL_PATH]) + if test "x$LUPDATE" = "xmissing"; then + AC_MSG_WARN( + [Cannot find lupdate (Qt Linguist updater). Try --with-qt=PATH.]) + fi + + AC_ARG_VAR([LRELEASE], + [Qt Linguist compiler command]) + AX_PATH_TOOLS([LRELEASE], + [lrelease lrelease-qt5 lrelease-qt6], + [missing], + [$QT_TOOL_PATH]) + if test "x$LRELEASE" = "xmissing"; then + AC_MSG_WARN( + [Cannot find lrelease (Qt Linguist compiler). Try --with-qt=PATH.]) + fi + + AC_MSG_CHECKING([whether host operating system is Darwin]) + at_darwin=no + at_qmake_args= + case $host_os in + dnl ( + darwin*) + at_darwin=yes + ;; + esac + AC_MSG_RESULT([$at_darwin]) + + AC_MSG_CHECKING([whether QMAKESPEC environment variable is set]) + if test x"$QMAKESPEC" = x; then + if test x"$at_darwin" = xyes; then + if $CXX --version | grep -q -i clang; then + at_qmake_args='-spec macx-clang' + else + at_qmake_args='-spec macx-g++' + fi + AC_MSG_RESULT([no, using $at_qmake_args]) + else + AC_MSG_RESULT([no]) + fi + else + AC_MSG_RESULT([yes, using $QMAKESPEC]) + fi + + # If we don't know the path to Qt, guess it from the path to + # qmake. + if test x"$QT_PATH" = x; then + QT_PATH=`dirname "$QMAKE"` + fi + if test x"$QT_PATH" = x; then + AX_INSTEAD_IF([$4], + [Cannot find your Qt installation. Try --with-qt=PATH.]) + break + fi + AC_SUBST([QT_PATH]) + + # Get ready to build a test-app with Qt. + if mkdir conftest.dir \ + && cd conftest.dir; then + : + else + AX_INSTEAD_IF([$4], + [Cannot mkdir conftest.dir or cd to that directory.]) + break + fi + + cat >conftest.h <<_ASEOF + +#include <QObject> + +class Foo: public QObject +{ + Q_OBJECT; +public: + Foo(); + ~Foo() {} +public Q_SLOTS: + void setValue(int value); +Q_SIGNALS: + void valueChanged(int newValue); +private: + int value_; +}; + +_ASEOF + + cat >conftest.cpp <<_ASEOF + +#include "conftest.h" + +Foo::Foo() + : value_ (42) +{ + connect(this, SIGNAL(valueChanged(int)), + this, SLOT(setValue(int))); +} + +void Foo::setValue(int value) +{ + value_ = value; +} + +int main() +{ + Foo f; +} + +_ASEOF + + if $QMAKE -project; then + : + else + AX_INSTEAD_IF([$4], + [Calling $QMAKE -project failed.]) + break + fi + + # Find the .pro file generated by qmake. + pro_file=conftest.dir.pro + test -f $pro_file || pro_file=`echo *.pro` + if test -f "$pro_file"; then + : + else + AX_INSTEAD_IF([$4], + [Can't find the .pro file generated by Qmake.]) + break + fi + + dnl This is for Qt5; for Qt4 it does nothing special. + _AT_TWEAK_PRO_FILE([QT], [+widgets]) + + dnl Undocumented qmake: always use absolute paths. + dnl Defaults to 4. + _AT_TWEAK_PRO_FILE([QMAKE_PROJECT_DEPTH], [0]) + + dnl Tweak the value of QT in the .pro file if we have a first + dnl argument. + m4_ifval([$1], + [_AT_TWEAK_PRO_FILE([QT], [$1])]) + + dnl Tweak the value of CONFIG in the .pro file if we have a + dnl second argument. + m4_ifval([$2], + [_AT_TWEAK_PRO_FILE([CONFIG], [$2])]) + + m4_ifval([$3], + [ # Add the extra-settings the user wants to set in the .pro + # file. + echo "$3" >>"$pro_file" + ]) + + echo "$as_me:$LINENO: Invoking $QMAKE on $pro_file" \ + >& AS_MESSAGE_LOG_FD + sed 's/^/| /' "$pro_file" >& AS_MESSAGE_LOG_FD + + if $QMAKE $at_qmake_args; then + : + else + AX_INSTEAD_IF([$4], + [Calling $QMAKE $at_qmake_args failed.]) + break + fi + + # Try to compile a simple Qt app. + AC_CACHE_CHECK([whether we can build a simple Qt application], + [at_cv_qt_build], + [at_cv_qt_build=ko + : ${MAKE=make} + + if $MAKE >& AS_MESSAGE_LOG_FD 2>&1; then + at_cv_qt_build='ok, looks like Qt 4, Qt 5, or Qt 6' + else + echo "$as_me:$LINENO: Build failed, trying to #include <qobject.h> instead" \ + >& AS_MESSAGE_LOG_FD + sed 's/<QObject>/<qobject.h>/' conftest.h > tmp.h \ + && mv tmp.h conftest.h + if $MAKE >& AS_MESSAGE_LOG_FD 2>&1; then + at_cv_qt_build='ok, looks like Qt 3' + else + # Sometimes (such as on Debian) build will fail because Qt + # hasn't been installed in debug mode and qmake tries (by + # default) to build apps in debug mode => Try again in + # release mode. + echo "$as_me:$LINENO: Build failed, trying to enforce release mode" \ + >& AS_MESSAGE_LOG_FD + + _AT_TWEAK_PRO_FILE([CONFIG], [+release]) + + sed 's/<qobject.h>/<QObject>/' conftest.h > tmp.h \ + && mv tmp.h conftest.h + if $MAKE >& AS_MESSAGE_LOG_FD 2>&1; then + at_cv_qt_build='ok, looks like Qt 4 or Qt 5, release mode forced' + else + echo "$as_me:$LINENO: Build failed, trying to #include <qobject.h> instead" \ + >& AS_MESSAGE_LOG_FD + sed 's/<QObject>/<qobject.h>/' conftest.h > tmp.h \ + && mv tmp.h conftest.h + if $MAKE >& AS_MESSAGE_LOG_FD 2>&1; then + at_cv_qt_build='ok, looks like Qt 3, release mode forced' + else + at_cv_qt_build=ko + echo "$as_me:$LINENO: failed program was:" \ + >& AS_MESSAGE_LOG_FD + sed 's/^/| /' conftest.h >& AS_MESSAGE_LOG_FD + echo "$as_me:$LINENO: failed program was:" \ + >& AS_MESSAGE_LOG_FD + sed 's/^/| /' conftest.cpp >& AS_MESSAGE_LOG_FD + fi # if make with Qt3-style #include and release mode forced. + fi # if make with Qt4/5-style #include and release mode forced. + fi # if make with Qt3-style #include. + fi # if make with Qt4/5-style #include. + ])dnl end: AC_CACHE_CHECK(at_cv_qt_build) + + if test x"$at_cv_qt_build" = xko; then + AX_INSTEAD_IF([$4], + [Cannot build a test Qt program]) + cd .. + break + fi + + QT_VERSION_MAJOR=`echo "$at_cv_qt_build" | sed 's/[[^0-9]]*//g'` + AC_SUBST([QT_VERSION_MAJOR]) + + # This sed filter is applied after an expression of the form + # /^FOO.*=/!d; it starts by removing the beginning of the line + # (using the empty regular expression //, which repeats the last + # regular expression match), removing references to SUBLIBS, + # removing unnecessary whitespace at the beginning, then prefixing + # our exported variables with QT_. Note that `LDFLAGS' is + # intentionally omitted. + qt_sed_filter='s///; + s/$(SUBLIBS)//g; + s/^ *//; + s/\$(DEFINES)/$(QT_DEFINES)/g; + s/\$(CFLAGS)/$(QT_CFLAGS)/g; + s/\$(CXXFLAGS)/$(QT_CXXFLAGS)/g; + s/\$(INCPATH)/$(QT_INCPATH)/g; + s/\$(CPPFLAGS)/$(QT_CPPFLAGS)/g; + s/\$(LFLAGS)/$(QT_LFLAGS)/g; + s/\$(LIBS)/$(QT_LIBS)/g' + + # Find the Makefile (qmake happens to generate a fake Makefile + # which invokes a Makefile.Debug or Makefile.Release). If we + # have both, we'll pick the Makefile.Release. The reason is that + # this release uses -Os and debug -g. We can override -Os by + # passing another -O but we usually don't override -g. + if test -f Makefile.Release; then + at_mfile='Makefile.Release' + else + at_mfile='Makefile' + fi + if test -f $at_mfile; then + : + else + AX_INSTEAD_IF([$4], + [Cannot find the Makefile generated by qmake.]) + cd .. + break + fi + + # Find the DEFINES of Qt (should have been named CPPFLAGS). + AC_CACHE_CHECK([for the DEFINES to use with Qt], + [at_cv_env_QT_DEFINES], + [at_cv_env_QT_DEFINES=`sed "/^DEFINES@<:@^A-Z=@:>@*=/!d; + $qt_sed_filter" $at_mfile`]) + AC_SUBST([QT_DEFINES], + [$at_cv_env_QT_DEFINES]) + + # Find the CFLAGS of Qt. (We can use Qt in C?!) + AC_CACHE_CHECK([for the CFLAGS to use with Qt], + [at_cv_env_QT_CFLAGS], + [at_cv_env_QT_CFLAGS=`sed "/^CFLAGS@<:@^A-Z=@:>@*=/!d; + $qt_sed_filter" $at_mfile`]) + AC_SUBST([QT_CFLAGS], + [$at_cv_env_QT_CFLAGS]) + + # Find the CXXFLAGS of Qt. + AC_CACHE_CHECK([for the CXXFLAGS to use with Qt], + [at_cv_env_QT_CXXFLAGS], + [at_cv_env_QT_CXXFLAGS=`sed "/^CXXFLAGS@<:@^A-Z=@:>@*=/!d; + $qt_sed_filter" $at_mfile`]) + AC_SUBST([QT_CXXFLAGS], + [$at_cv_env_QT_CXXFLAGS]) + + # Find the INCPATH of Qt. + AC_CACHE_CHECK([for the INCPATH to use with Qt], + [at_cv_env_QT_INCPATH], + [at_cv_env_QT_INCPATH=`sed "/^INCPATH@<:@^A-Z=@:>@*=/!d; + $qt_sed_filter" $at_mfile`]) + AC_SUBST([QT_INCPATH], + [$at_cv_env_QT_INCPATH]) + + AC_SUBST([QT_CPPFLAGS], + ["$at_cv_env_QT_DEFINES $at_cv_env_QT_INCPATH"]) + + # Find the LFLAGS of Qt (should have been named LDFLAGS). + AC_CACHE_CHECK([for the LDFLAGS to use with Qt], + [at_cv_env_QT_LDFLAGS], + [at_cv_env_QT_LDFLAGS=`sed "/^LFLAGS@<:@^A-Z=@:>@*=/!d; + $qt_sed_filter" $at_mfile`]) + AC_SUBST([QT_LFLAGS], + [$at_cv_env_QT_LDFLAGS]) + AC_SUBST([QT_LDFLAGS], + [$at_cv_env_QT_LDFLAGS]) + + # Find the LIBS of Qt. + AC_CACHE_CHECK([for the LIBS to use with Qt], + [at_cv_env_QT_LIBS], + [at_cv_env_QT_LIBS=`sed "/^LIBS@<:@^A-Z@:>@*=/!d; + $qt_sed_filter" $at_mfile` + if test x$at_darwin = xyes; then + # Fix QT_LIBS: as of today Libtool (GNU Libtool 1.5.23a) + # doesn't handle -F properly. The "bug" has been fixed on 22 + # October 2006 by Peter O'Gorman but we provide backward + # compatibility here. + at_cv_env_QT_LIBS=`echo "$at_cv_env_QT_LIBS" \ + | sed 's/^-F/-Wl,-F/; + s/ -F/ -Wl,-F/g'` + fi]) + AC_SUBST([QT_LIBS], + [$at_cv_env_QT_LIBS]) + + # We can't use AC_CACHE_CHECK for data that contains newlines. + AC_MSG_CHECKING([for necessary static plugin code]) + # find static plugin data generated by qmake + if test -f conftest.dir_plugin_import.cpp; then + QT_STATIC_PLUGINS=`cat conftest.dir_plugin_import.cpp` + else + QT_STATIC_PLUGINS="\ +// We have Qt earlier than version 5 or a dynamic build. +// Provide dummy typedef to avoid empty source code. +typedef int _qt_not_a_static_build;" + fi + AC_SUBST([QT_STATIC_PLUGINS]) + AM_SUBST_NOTMAKE([QT_STATIC_PLUGINS]) + AC_MSG_RESULT([$QT_STATIC_PLUGINS]) + + cd .. && rm -rf conftest.dir + + # Run the user code + $5 + + done # end hack (useless FOR to be able to use break) + ]) + + +# AT_REQUIRE_QT_VERSION(QT_version, [RUN-IF-FAILED], [RUN-IF-OK]) +# --------------------------------------------------------------- +# Check (using qmake) that Qt's version "matches" QT_version. Must be +# run *AFTER* AT_WITH_QT. Requires autoconf 2.60. +# +# This macro is ignored if Qt support has been disabled (using +# `--with-qt=no' or `--without-qt'). +# +# RUN-IF-FAILED is arbitrary code to execute if Qt cannot be found or +# if any problem happens. If this argument is omitted, then +# AC_MSG_ERROR will be called. RUN-IF-OK is arbitrary code to execute +# if Qt was successfully found. +# +# This macro provides the Qt version in $(QT_VERSION). + +AC_DEFUN([AT_REQUIRE_QT_VERSION], + [AC_PREREQ([2.60]) + + # This is a hack to get decent flow control with `break'. + for _qt_ignored in once; do + + if test x"$with_qt" = x"no"; then + break + fi + + if test x"$QMAKE" = x; then + AX_INSTEAD_IF([$2], + [\$QMAKE is empty. Did you invoke AT@&t@_WITH_QT before AT@&t@_REQUIRE_QT_VERSION?]) + break + fi + + AC_CACHE_CHECK([for Qt's version], + [at_cv_QT_VERSION], + [echo "$as_me:$LINENO: Running $QMAKE --version:" \ + >& AS_MESSAGE_LOG_FD + $QMAKE --version >& AS_MESSAGE_LOG_FD 2>&1 + qmake_version_sed=['/^.*\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*$/!d;s//\1/'] + at_cv_QT_VERSION=`$QMAKE --version 2>&1 \ + | sed "$qmake_version_sed"`]) + if test x"$at_cv_QT_VERSION" = x; then + AX_INSTEAD_IF([$2], + [Cannot detect Qt's version.]) + break + fi + AC_SUBST([QT_VERSION], + [$at_cv_QT_VERSION]) + AS_VERSION_COMPARE([$QT_VERSION], [$1], + [AX_INSTEAD_IF([$2], + [This package requires Qt $1 or above.]) + break]) + + # Run the user code + $3 + + done # end hack (useless FOR to be able to use break) + ]) + + +# _AT_TWEAK_PRO_FILE(QT_VAR, VALUE) +# --------------------------------- +# @internal. Tweak the variable QT_VAR in the .pro file. VALUE is an +# IFS-separated list of values, and each value is rewritten as +# follows: +# +# +value => QT_VAR += value +# -value => QT_VAR -= value +# value => QT_VAR += value + +AC_DEFUN([_AT_TWEAK_PRO_FILE], + [ # Tweak the value of $1 in the .pro file for $2. + qt_conf='' + for at_mod in $2; do + at_mod=`echo "$at_mod" | sed 's/^-//; tough + s/^+//; beef + :ough + s/^/$1 -= /;n + :eef + s/^/$1 += /'` + qt_conf="\ +$qt_conf +$at_mod" + done + echo "$qt_conf" | sed 1d >>"$pro_file" + ]) + +# eof
--- a/m4/m4_ax_have_qt.m4 Wed Jan 24 20:18:59 2024 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,268 +0,0 @@ -# =========================================================================== -# https://www.gnu.org/software/autoconf-archive/ax_have_qt.html -# =========================================================================== -# -# SYNOPSIS -# -# AX_HAVE_QT -# -# DESCRIPTION -# -# Searches $PATH and queries qmake for Qt include files, libraries and Qt -# binary utilities. The macro only supports Qt5 or later. -# -# The following shell variable is set to either "yes" or "no": -# -# have_qt -# -# Additionally, the following variables are exported: -# -# QT_CXXFLAGS -# QT_LIBS -# QT_MOC -# QT_UIC -# QT_RCC -# QT_LRELEASE -# QT_LUPDATE -# QT_DIR -# QMAKE -# -# which respectively contain an "-I" flag pointing to the Qt include -# directory, link flags necessary to link with Qt and X, the full path to -# the meta object compiler and the user interface compiler both, and -# finally the variable QTDIR as Qt likes to see it defined. -# -# Example lines for Makefile.in: -# -# CXXFLAGS = @QT_CXXFLAGS@ -# MOC = @QT_MOC@ -# -# After the variables have been set, a trial compile and link is performed -# to check the correct functioning of the meta object compiler. This test -# may fail when the different detected elements stem from different -# releases of the Qt framework. In that case, an error message is emitted -# and configure stops. -# -# No common variables such as $LIBS or $CFLAGS are polluted. -# -# LICENSE -# -# Copyright (c) 2008 Bastiaan Veelo <Bastiaan@Veelo.net> -# Copyright (c) 2014 Alex Henrie <alexhenrie24@gmail.com> -# Copyright (c) 2024 Paper <mrpapersonic@gmail.com> -# -# Copying and distribution of this file, with or without modification, are -# permitted in any medium without royalty provided the copyright notice -# and this notice are preserved. This file is offered as-is, without any -# warranty. - -#serial 25 - -AU_ALIAS([BNV_HAVE_QT], [AX_HAVE_QT]) -AC_DEFUN([AX_HAVE_QT], -[ - AC_REQUIRE([AC_PROG_CXX]) - AC_REQUIRE([AC_PATH_X]) - AC_REQUIRE([AC_PATH_XTRA]) - # openSUSE leap 15.3 installs qmake-qt5, not qmake, for example. - # Store the full name (like qmake-qt5) into QMAKE - # and the specifier (like -qt5 or empty) into am_have_qt_qmexe_suff. - AC_ARG_VAR([QMAKE],"Qt make tool") - AC_CHECK_TOOLS([QMAKE],[qmake qmake-qt6 qmake-qt5],[false]) - - AC_MSG_CHECKING(for Qt) - am_have_qt_qmexe_suff=`echo $QMAKE | sed 's,^.*qmake,,'` - # If we have Qt5 or later in the path, we're golden - ver=`$QMAKE --version | grep -o "Qt version ."` - - if test "$ver" ">" "Qt version 4"; then - have_qt=yes - # This pro file dumps qmake's variables, but it only works on Qt 5 or later - am_have_qt_dir=`mktemp -d` - am_have_qt_pro="$am_have_qt_dir/test.pro" - am_have_qt_stash="$am_have_qt_dir/.qmake.stash" - am_have_qt_makefile="$am_have_qt_dir/Makefile" - # http://qt-project.org/doc/qt-5/qmake-variable-reference.html#qt - cat > $am_have_qt_pro << EOF -win32 { - CONFIG -= debug_and_release - CONFIG += release -} - -CONFIG += sdk_no_version_check - -# use absolute paths, useful on windows -# where qmake really loves giving relative paths -QMAKE_PROJECT_DEPTH = 0 - -# commented out all the modules we don't use -#qtHaveModule(axcontainer): QT += axcontainer -#qtHaveModule(axserver): QT += axserver -#qtHaveModule(concurrent): QT += concurrent -qtHaveModule(core): QT += core -#qtHaveModule(dbus): QT += dbus -#qtHaveModule(declarative): QT += declarative -#qtHaveModule(designer): QT += designer -qtHaveModule(gui): QT += gui -#qtHaveModule(help): QT += help -#qtHaveModule(multimedia): QT += multimedia -#qtHaveModule(multimediawidgets): QT += multimediawidgets -#qtHaveModule(network): QT += network -#qtHaveModule(opengl): QT += opengl -#qtHaveModule(printsupport): QT += printsupport -#qtHaveModule(qml): QT += qml -#qtHaveModule(qmltest): QT += qmltest -#qtHaveModule(x11extras): QT += x11extras -#qtHaveModule(script): QT += script -#qtHaveModule(scripttools): QT += scripttools -#qtHaveModule(sensors): QT += sensors -#qtHaveModule(serialport): QT += serialport -#qtHaveModule(sql): QT += sql -#qtHaveModule(svg): QT += svg -#qtHaveModule(testlib): QT += testlib -#qtHaveModule(uitools): QT += uitools -#qtHaveModule(webkit): QT += webkit -#qtHaveModule(webkitwidgets): QT += webkitwidgets -#qtHaveModule(xml): QT += xml -#qtHaveModule(xmlpatterns): QT += xmlpatterns -qtHaveModule(widgets): QT += widgets -percent.target = % -percent.commands = @echo -n "\$(\$(@))\ " -QMAKE_EXTRA_TARGETS += percent -EOF - am_have_qt_makefile_cxxflags=`cat << EOF -include $am_have_qt_makefile - -VAR: - @echo \\$(CXXFLAGS) \\$(INCPATH) -EOF` - am_have_qt_makefile_libs=`cat << EOF -include $am_have_qt_makefile - -VAR: - @echo \\$(LIBS) -EOF` - $QMAKE "$am_have_qt_pro" -o "$am_have_qt_makefile" - QT_CXXFLAGS=`cd $am_have_qt_dir; echo "\$am_have_qt_makefile_cxxflags" | make -s -f - VAR` - QT_LIBS=`cd $am_have_qt_dir; echo "\$am_have_qt_makefile_libs" | make -s -f - VAR` - rm $am_have_qt_pro $am_have_qt_stash $am_have_qt_makefile - rmdir $am_have_qt_dir - - # Look for specific tools in $PATH - QT_MOC=`which moc$am_have_qt_qmexe_suff` - QT_UIC=`which uic$am_have_qt_qmexe_suff` - QT_RCC=`which rcc$am_have_qt_qmexe_suff` - QT_LRELEASE=`which lrelease$am_have_qt_qmexe_suff` - QT_LUPDATE=`which lupdate$am_have_qt_qmexe_suff` - - # Get Qt version from qmake - QT_DIR=`$QMAKE --version | grep -o -E /.+` - - # All variables are defined, report the result - AC_MSG_RESULT([$have_qt: - QT_CXXFLAGS=$QT_CXXFLAGS - QT_DIR=$QT_DIR - QT_LIBS=$QT_LIBS - QT_UIC=$QT_UIC - QT_MOC=$QT_MOC - QT_RCC=$QT_RCC - QT_LRELEASE=$QT_LRELEASE - QT_LUPDATE=$QT_LUPDATE]) - else - # Qt was not found - have_qt=no - QT_CXXFLAGS= - QT_DIR= - QT_LIBS= - QT_UIC= - QT_MOC= - QT_RCC= - QT_LRELEASE= - QT_LUPDATE= - AC_MSG_RESULT($have_qt) - fi - AC_SUBST(QT_CXXFLAGS) - AC_SUBST(QT_DIR) - AC_SUBST(QT_LIBS) - AC_SUBST(QT_UIC) - AC_SUBST(QT_MOC) - AC_SUBST(QT_RCC) - AC_SUBST(QT_LRELEASE) - AC_SUBST(QT_LUPDATE) - AC_SUBST(QMAKE) - - #### Being paranoid: - if test x"$have_qt" = xyes; then - AC_MSG_CHECKING(correct functioning of Qt installation) - AC_CACHE_VAL(ax_cv_qt_test_result, - [ - cat > ax_qt_test.h << EOF -#include <qobject.h> -class Test : public QObject -{ -Q_OBJECT -public: - Test() {} - ~Test() {} -public slots: - void receive() {} -signals: - void send(); -}; -EOF - - cat > ax_qt_main.$ac_ext << EOF -#include "ax_qt_test.h" -#include <qapplication.h> -int main( int argc, char **argv ) -{ - QApplication app( argc, argv ); - Test t; - QObject::connect( &t, SIGNAL(send()), &t, SLOT(receive()) ); -} -EOF - - ax_cv_qt_test_result="failure" - ax_try_1="$QT_MOC ax_qt_test.h -o moc_ax_qt_test.$ac_ext >/dev/null 2>/dev/null" - AC_TRY_EVAL(ax_try_1) - if test x"$ac_status" != x0; then - echo "$ax_err_1" >&AS_MESSAGE_LOG_FD - echo "configure: could not run $QT_MOC on:" >&AS_MESSAGE_LOG_FD - cat ax_qt_test.h >&AS_MESSAGE_LOG_FD - else - ax_try_2="$CXX $QT_CXXFLAGS -c $CXXFLAGS -o moc_ax_qt_test.o moc_ax_qt_test.$ac_ext >/dev/null 2>/dev/null" - AC_TRY_EVAL(ax_try_2) - if test x"$ac_status" != x0; then - echo "$ax_err_2" >&AS_MESSAGE_LOG_FD - echo "configure: could not compile:" >&AS_MESSAGE_LOG_FD - cat moc_ax_qt_test.$ac_ext >&AS_MESSAGE_LOG_FD - else - ax_try_3="$CXX $QT_CXXFLAGS -c $CXXFLAGS -o ax_qt_main.o ax_qt_main.$ac_ext >/dev/null 2>/dev/null" - AC_TRY_EVAL(ax_try_3) - if test x"$ac_status" != x0; then - echo "$ax_err_3" >&AS_MESSAGE_LOG_FD - echo "configure: could not compile:" >&AS_MESSAGE_LOG_FD - cat ax_qt_main.$ac_ext >&AS_MESSAGE_LOG_FD - else - ax_try_4="$CXX -o ax_qt_main ax_qt_main.o moc_ax_qt_test.o $QT_LIBS $LIBS >/dev/null 2>/dev/null" - AC_TRY_EVAL(ax_try_4) - if test x"$ac_status" != x0; then - echo "$ax_err_4" >&AS_MESSAGE_LOG_FD - else - ax_cv_qt_test_result="success" - fi - fi - fi - fi - ])dnl AC_CACHE_VAL ax_cv_qt_test_result - AC_MSG_RESULT([$ax_cv_qt_test_result]) - if test x"$ax_cv_qt_test_result" = "xfailure"; then - AC_MSG_ERROR([Failed to find matching components of a complete - Qt installation. Try using more options, - see ./configure --help.]) - fi - - rm -f ax_qt_test.h moc_ax_qt_test.$ac_ext moc_ax_qt_test.o \ - ax_qt_main.$ac_ext ax_qt_main.o ax_qt_main - fi -])
--- a/src/core/config.cc Wed Jan 24 20:18:59 2024 -0500 +++ b/src/core/config.cc Sun Feb 04 21:17:17 2024 -0500 @@ -23,6 +23,8 @@ #include <QFile> #include <QTextStream> +#include <iostream> + /* I'll use an INI-based config file instead of using an * XML file like Taiga. * @@ -89,14 +91,16 @@ } } + locale.RefreshAvailableLocales(); locale.SetActiveLocale(QLocale(Strings::ToQString(INI::GetIniValue<std::string>(ini, "General", "Locale", "en_US")))); theme.SetTheme(Translate::ToTheme(INI::GetIniValue<std::string>(ini, "Appearance", "Theme", "Default"))); { std::vector<std::string> v = Strings::Split(INI::GetIniValue<std::string>(ini, "Library", "Folders", ""), ";"); - library.paths = std::set(std::make_move_iterator(v.begin()), - std::make_move_iterator(v.end())); + for (const auto& s : v) + if (!library.paths.count(s)) + library.paths.insert(s); } library.real_time_monitor = INI::GetIniValue<bool>(ini, "Library", "Real-time monitor", true); @@ -104,7 +108,7 @@ return 0; } -int Config::Save() const { +int Config::Save() { std::filesystem::path cfg_path = Filesystem::GetConfigPath(); Filesystem::CreateDirectories(cfg_path);
--- a/src/core/filesystem.cc Wed Jan 24 20:18:59 2024 -0500 +++ b/src/core/filesystem.cc Sun Feb 04 21:17:17 2024 -0500 @@ -1,23 +1,10 @@ -#ifdef WIN32 -# include <shlobj.h> -#elif defined(MACOSX) -# include "sys/osx/filesystem.h" -#elif defined(__linux__) -# include <pwd.h> -# include <sys/types.h> -#endif - -#ifndef WIN32 -# include <errno.h> -# include <unistd.h> -# include <sys/stat.h> -#endif - #include "core/filesystem.h" #include "core/config.h" #include "core/strings.h" + +#include <QStandardPaths> + #include <filesystem> -#include <limits.h> namespace Filesystem { @@ -33,39 +20,18 @@ } std::filesystem::path GetDotPath() { + /* + * Windows: ~/AppData/Roaming/Minori + * macOS: ~/Library/Application Support/Minori + * ...: ~/.config/minori + * + * FIXME: are windows and mac properly cased? + */ #ifdef WIN32 - std::filesystem::path path; - wchar_t* buf; - - if (SHGetKnownFolderPath(FOLDERID_RoamingAppData, KF_FLAG_CREATE, NULL, &buf) == S_OK) - path = buf; - else - return std::filesystem::path(); - - CoTaskMemFree(buf); - - return path / CONFIG_DIR; -#elif defined(MACOSX) - std::string appsupport; - if (!osx::GetApplicationSupportDirectory(appsupport)) - return ""; - - return std::filesystem::path(appsupport) / CONFIG_DIR; -#else // just assume POSIX - std::filesystem::path path; - const char* home = getenv("HOME"); - -# ifdef __linux__ - if (!home) - home = getpwuid(getuid())->pw_dir; -# endif // __linux__ - - /* only do this if the home directory was really found */ - if (home) - return std::filesystem::path(home) / ".config" / CONFIG_DIR; - else - return std::filesystem::path(); -#endif // !WIN32 && !MACOSX + return Strings::ToUtf8String(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)); +#else + return Strings::ToUtf8String(QStandardPaths::writableLocation(QStandardPaths::AppConfigLocation)); +#endif } std::filesystem::path GetConfigPath() {
--- a/src/core/strings.cc Wed Jan 24 20:18:59 2024 -0500 +++ b/src/core/strings.cc Sun Feb 04 21:17:17 2024 -0500 @@ -23,7 +23,7 @@ /* ew */ std::string Implode(const std::vector<std::string>& vector, const std::string& delimiter) { if (vector.size() < 1) - return "-"; + return ""; std::string out; @@ -38,7 +38,7 @@ std::string Implode(const std::set<std::string>& set, const std::string& delimiter) { if (set.size() < 1) - return "-"; + return ""; std::string out; @@ -52,6 +52,9 @@ } std::vector<std::string> Split(const std::string &text, const std::string& delimiter) { + if (text.length() < 1) + return {}; + std::vector<std::string> tokens; std::size_t start = 0, end = 0; @@ -91,7 +94,8 @@ } /* removes dumb HTML tags because anilist is aids and - gives us HTML for synopses :/ */ + * gives us HTML for synopses :/ +*/ std::string RemoveHtmlTags(std::string string) { while (string.find("<") != std::string::npos) { auto startpos = string.find("<");
--- a/src/gui/dialog/information.cc Wed Jan 24 20:18:59 2024 -0500 +++ b/src/gui/dialog/information.cc Sun Feb 04 21:17:17 2024 -0500 @@ -30,6 +30,9 @@ /* TODO: Taiga disables rendering of the tab widget entirely when the anime is not part of a list, which sucks. Think of a better way to implement this later. */ void InformationDialog::SaveData(Anime::Anime& anime) { + if (!anime.IsInUserList()) + return; + anime.SetUserProgress(_progress); anime.SetUserScore(_score); anime.SetUserIsRewatching(_rewatching); @@ -98,7 +101,7 @@ tabbed_widget->addTab(main_information_widget, tr("Main information")); } - { + if (anime.IsInUserList()) { /* My list and settings */ QWidget* settings_widget = new QWidget(tabbed_widget); settings_widget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
--- a/src/gui/pages/anime_list.cc Wed Jan 24 20:18:59 2024 -0500 +++ b/src/gui/pages/anime_list.cc Sun Feb 04 21:17:17 2024 -0500 @@ -275,7 +275,6 @@ void AnimeListPage::DisplayListMenu() { QMenu* menu = new QMenu(this); menu->setAttribute(Qt::WA_DeleteOnClose); - menu->setTitle(tr("Column visibility")); menu->setToolTipsVisible(true); AnimeListPageModel* source_model = @@ -424,6 +423,7 @@ void AnimeListPage::showEvent(QShowEvent*) { SetupLayout(); + Refresh(); } /* --------- QTabWidget replication end ---------- */ @@ -497,6 +497,4 @@ SetColumnDefaults(); setFocusPolicy(Qt::TabFocus); setFocusProxy(tab_bar); - - Refresh(); }
--- a/src/gui/pages/search.cc Wed Jan 24 20:18:59 2024 -0500 +++ b/src/gui/pages/search.cc Sun Feb 04 21:17:17 2024 -0500 @@ -1,4 +1,346 @@ #include "gui/pages/search.h" +#include "core/anime.h" +#include "core/anime_db.h" +#include "core/strings.h" +#include "core/http.h" +#include "core/session.h" +#include "core/filesystem.h" +#include "gui/widgets/text.h" +#include "gui/dialog/information.h" +#include "track/media.h" +#include "gui/translate/anime.h" +#include "services/services.h" + +#include <QHeaderView> +#include <QVBoxLayout> +#include <QToolBar> +#include <QTreeView> +#include <QDate> +#include <QMenu> + +#include <iostream> +#include <sstream> +#include <fstream> +#include <algorithm> + +#include "pugixml.hpp" +#include "anitomy/anitomy.h" + +SearchPageListSortFilter::SearchPageListSortFilter(QObject* parent) : QSortFilterProxyModel(parent) { +} + +bool SearchPageListSortFilter::lessThan(const QModelIndex& l, const QModelIndex& r) const { + QVariant left = sourceModel()->data(l, sortRole()); + QVariant right = sourceModel()->data(r, sortRole()); + + switch (left.userType()) { + case QMetaType::Int: + case QMetaType::UInt: + case QMetaType::LongLong: + case QMetaType::ULongLong: + return left.toInt() < right.toInt(); + case QMetaType::QDate: + return left.toDate() < right.toDate(); + case QMetaType::QString: + default: // meh + return QString::compare(left.toString(), right.toString(), Qt::CaseInsensitive) < 0; + } +} + +/* -------------------------------------------- */ + +SearchPageListModel::SearchPageListModel(QObject* parent) : QAbstractListModel(parent) { +} + +void SearchPageListModel::ParseSearch(const std::vector<int>& ids) { + /* hack!!! */ + if (!rowCount(index(0))) { + beginInsertRows(QModelIndex(), 0, 0); + endInsertRows(); + } + + beginResetModel(); + + this->ids = ids; + + endResetModel(); +} + +int SearchPageListModel::rowCount(const QModelIndex& parent) const { + return ids.size(); + (void)(parent); +} + +int SearchPageListModel::columnCount(const QModelIndex& parent) const { + return NB_COLUMNS; + (void)(parent); +} + +QVariant SearchPageListModel::headerData(const int section, const Qt::Orientation orientation, const int role) const { + switch (role) { + case Qt::DisplayRole: { + switch (section) { + case SR_TITLE: return tr("Anime title"); + case SR_EPISODES: return tr("Episode"); + case SR_TYPE: return tr("Type"); + case SR_SCORE: return tr("Score"); + case SR_SEASON: return tr("Season"); + default: return {}; + } + break; + } + case Qt::TextAlignmentRole: { + switch (section) { + case SR_TITLE: return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + case SR_TYPE: return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); + case SR_EPISODES: + case SR_SCORE: + case SR_SEASON: return QVariant(Qt::AlignRight | Qt::AlignVCenter); + default: return {}; + } + break; + } + } + return QAbstractListModel::headerData(section, orientation, role); +} + +QVariant SearchPageListModel::data(const QModelIndex& index, int role) const { + if (!index.isValid()) + return QVariant(); + + const Anime::Anime& anime = Anime::db.items[ids[index.row()]]; + + switch (role) { + case Qt::DisplayRole: + switch (index.column()) { + case SR_TITLE: return Strings::ToQString(anime.GetUserPreferredTitle()); + case SR_TYPE: return Strings::ToQString(Translate::ToLocalString(anime.GetFormat())); + case SR_EPISODES: return anime.GetEpisodes(); + case SR_SCORE: return QString::number(anime.GetAudienceScore()) + "%"; + case SR_SEASON: return Strings::ToQString(Translate::ToLocalString(anime.GetSeason())) + " " + QString::number(anime.GetAirDate().GetYear().value_or(2000)); + default: return {}; + } + break; + case Qt::UserRole: + switch (index.column()) { + case SR_SCORE: return anime.GetAudienceScore(); + case SR_EPISODES: return anime.GetEpisodes(); + case SR_SEASON: return anime.GetAirDate().GetAsQDate(); + /* We have to use this to work around some stupid + * "conversion ambiguous" error on Linux + */ + default: return data(index, Qt::DisplayRole); + } + break; + case Qt::SizeHintRole: { + switch (index.column()) { + default: { + /* max horizontal size of 100, height size = size of current font */ + const QString d = data(index, Qt::DisplayRole).toString(); + const QFontMetrics metric = QFontMetrics(QFont()); + + return QSize(std::max(metric.horizontalAdvance(d), 100), metric.height()); + } + } + break; + } + case Qt::TextAlignmentRole: + return headerData(index.column(), Qt::Horizontal, Qt::TextAlignmentRole); + } + return QVariant(); +} + +Qt::ItemFlags SearchPageListModel::flags(const QModelIndex& index) const { + if (!index.isValid()) + return Qt::NoItemFlags; + + return Qt::ItemIsEnabled | Qt::ItemIsSelectable; +} + +Anime::Anime* SearchPageListModel::GetAnimeFromIndex(const QModelIndex& index) const { + return &Anime::db.items[ids[index.row()]]; +} + +void SearchPage::DisplayListMenu() { + QMenu* menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); + menu->setToolTipsVisible(true); + + const QItemSelection selection = sort_model->mapSelectionToSource(treeview->selectionModel()->selection()); + + bool add_to_list_enable = true; -SearchPage::SearchPage(QWidget* parent) : QWidget(parent) { + std::set<Anime::Anime*> animes; + for (const auto& index : selection.indexes()) { + if (!index.isValid()) + continue; + + Anime::Anime* anime = model->GetAnimeFromIndex(index); + if (anime) { + animes.insert(anime); + if (anime->IsInUserList()) + add_to_list_enable = false; + } + } + + menu->addAction(tr("Information"), [this, animes] { + for (auto& anime : animes) { + InformationDialog* dialog = new InformationDialog(*anime, [this, anime] { + //UpdateAnime(anime->GetId()); + }, InformationDialog::PAGE_MAIN_INFO, this); + + dialog->show(); + dialog->raise(); + dialog->activateWindow(); + } + }); + menu->addSeparator(); + { + QMenu* submenu = menu->addMenu(tr("Add to list...")); + submenu->addAction(tr("Currently watching"), [animes]{ + for (auto& anime : animes) { + if (!anime->IsInUserList()) + anime->AddToUserList(); + anime->SetUserStatus(Anime::ListStatus::CURRENT); + Services::UpdateAnimeEntry(anime->GetId()); + } + }); + submenu->addAction(tr("Completed"), [animes]{ + for (auto& anime : animes) { + if (!anime->IsInUserList()) + anime->AddToUserList(); + anime->SetUserStatus(Anime::ListStatus::COMPLETED); + Services::UpdateAnimeEntry(anime->GetId()); + } + }); + submenu->addAction(tr("On hold"), [animes]{ + for (auto& anime : animes) { + if (!anime->IsInUserList()) + anime->AddToUserList(); + anime->SetUserStatus(Anime::ListStatus::PAUSED); + Services::UpdateAnimeEntry(anime->GetId()); + } + }); + submenu->addAction(tr("Dropped"), [animes]{ + for (auto& anime : animes) { + if (!anime->IsInUserList()) + anime->AddToUserList(); + anime->SetUserStatus(Anime::ListStatus::DROPPED); + Services::UpdateAnimeEntry(anime->GetId()); + } + }); + submenu->addAction(tr("Plan to watch"), [animes]{ + for (auto& anime : animes) { + if (!anime->IsInUserList()) + anime->AddToUserList(); + anime->SetUserStatus(Anime::ListStatus::PLANNING); + Services::UpdateAnimeEntry(anime->GetId()); + } + }); + submenu->setEnabled(add_to_list_enable); + } + menu->popup(QCursor::pos()); } + +void SearchPage::ItemDoubleClicked() { + /* throw out any other garbage */ + const QItemSelection selection = sort_model->mapSelectionToSource(treeview->selectionModel()->selection()); + if (!selection.indexes().first().isValid()) + return; + + const QModelIndex index = model->index(selection.indexes().first().row()); + Anime::Anime* anime = model->GetAnimeFromIndex(index); + + InformationDialog* dialog = new InformationDialog(*anime, [this, anime] { + //UpdateAnime(anime->GetId()); + }, InformationDialog::PAGE_MAIN_INFO, this); + + dialog->show(); + dialog->raise(); + dialog->activateWindow(); +} + +SearchPage::SearchPage(QWidget* parent) : QFrame(parent) { + setFrameShape(QFrame::Box); + setFrameShadow(QFrame::Sunken); + + QVBoxLayout* layout = new QVBoxLayout(this); + layout->setContentsMargins(0, 0, 0, 0); + layout->setSpacing(0); + + { + /* Toolbar */ + QToolBar* toolbar = new QToolBar(this); + toolbar->setMovable(false); + + { + QLineEdit* line_edit = new QLineEdit("", toolbar); + connect(line_edit, &QLineEdit::returnPressed, this, [this, line_edit]{ + /* static thread here. */ + static QThread* thread = nullptr; + + if (thread) + return; + + thread = QThread::create([this, line_edit]{ + model->ParseSearch(Services::Search(Strings::ToUtf8String(line_edit->text()))); + }); + + connect(thread, &QThread::finished, this, []{ + thread->deleteLater(); + thread = nullptr; + }); + + thread->start(); + }); + toolbar->addWidget(line_edit); + } + + layout->addWidget(toolbar); + } + + { + QFrame* line = new QFrame(this); + line->setFrameShape(QFrame::HLine); + line->setFrameShadow(QFrame::Sunken); + line->setLineWidth(1); + layout->addWidget(line); + } + + { + treeview = new QTreeView(this); + treeview->setUniformRowHeights(true); + treeview->setAllColumnsShowFocus(false); + treeview->setAlternatingRowColors(true); + treeview->setSortingEnabled(true); + treeview->setSelectionMode(QAbstractItemView::ExtendedSelection); + treeview->setItemsExpandable(false); + treeview->setRootIsDecorated(false); + treeview->setContextMenuPolicy(Qt::CustomContextMenu); + treeview->setFrameShape(QFrame::NoFrame); + + { + sort_model = new SearchPageListSortFilter(treeview); + model = new SearchPageListModel(treeview); + sort_model->setSourceModel(model); + sort_model->setSortRole(Qt::UserRole); + sort_model->setSortCaseSensitivity(Qt::CaseInsensitive); + treeview->setModel(sort_model); + } + + // set column sizes + treeview->setColumnWidth(SearchPageListModel::SR_TITLE, 400); + treeview->setColumnWidth(SearchPageListModel::SR_TYPE, 60); + treeview->setColumnWidth(SearchPageListModel::SR_EPISODES, 60); + treeview->setColumnWidth(SearchPageListModel::SR_SCORE, 60); + treeview->setColumnWidth(SearchPageListModel::SR_SEASON, 100); + + treeview->header()->setStretchLastSection(false); + + /* Double click stuff */ + connect(treeview, &QAbstractItemView::doubleClicked, this, &SearchPage::ItemDoubleClicked); + connect(treeview, &QWidget::customContextMenuRequested, this, &SearchPage::DisplayListMenu); + + layout->addWidget(treeview); + } +}
--- a/src/gui/pages/statistics.cc Wed Jan 24 20:18:59 2024 -0500 +++ b/src/gui/pages/statistics.cc Sun Feb 04 21:17:17 2024 -0500 @@ -14,6 +14,12 @@ #include <QWidget> #include <sstream> +#include <cmath> + +enum class TimeUnits { + SECONDS, + MINUTES +}; StatisticsPage::StatisticsPage(QWidget* parent) : QFrame(parent) { QVBoxLayout* layout = new QVBoxLayout(this); @@ -74,47 +80,49 @@ UpdateStatistics(); } -/* me abusing macros :) */ -static void add_time_segment(std::ostringstream& str, int x, const std::string_view& s, const std::string_view& p) { - if (x > 0) - str << x << ((x == 1) ? s : p); -} +/* [in] enum TimeUnits unit: + * which unit to stop on + * [in] int amount: + * amount of units to parse + * [in, defaults to 1.0] double unit_in_seconds: + * equivalent of one of 'amount' in seconds, e.g. minutes would be 60.0 +*/ +static std::string TimeToDateString(TimeUnits unit, int amount, double unit_in_seconds = 1.0) { + /* avoid calculating this twice */ + const double years_conv = (31556952.0 / unit_in_seconds); + const double months_conv = (2629746.0 / unit_in_seconds); + const double days_conv = (86400.0 / unit_in_seconds); + const double hours_conv = (3600.0 / unit_in_seconds); + const double minutes_conv = (60.0 / unit_in_seconds); + const double seconds_conv = (1.0 / unit_in_seconds); -std::string StatisticsPage::MinutesToDateString(const int minutes) { - /* ew */ - int years = (minutes * (1 / 525949.2F)); - int months = (minutes * (1 / 43829.1F)) - (years * 12); - int days = (minutes * (1 / 1440.0F)) - (years * 365.2425F) - (months * 30.436875F); - int hours = (minutes * (1 / 60.0F)) - (years * 8765.82F) - (months * 730.485F) - (days * 24); - int rest_minutes = (minutes) - (years * 525949.2F) - (months * 43829.1F) - (days * 1440) - (hours * 60); - std::ostringstream return_stream; - add_time_segment(return_stream, years, " year ", " years "); - add_time_segment(return_stream, months, " month ", " months "); - add_time_segment(return_stream, days, " day ", " days "); - add_time_segment(return_stream, hours, " hour ", " hours "); - if (rest_minutes > 0 || return_stream.str().size() == 0) - return_stream << rest_minutes << ((rest_minutes == 1) ? " minute" : " minutes"); - return return_stream.str(); -} + const int years = amount / years_conv; + const int months = std::fmod(amount, years_conv) / months_conv; + const int days = std::fmod(amount, months_conv) / days_conv; + const int hours = std::fmod(amount, days_conv) / hours_conv; + const int minutes = std::fmod(amount, hours_conv) / minutes_conv; + const int seconds = std::fmod(amount, minutes_conv) / seconds_conv; + + const auto add_time_segment = [](std::ostringstream& str, int amount, const std::string_view& singular, const std::string_view& plural, bool always = false) { + if (amount > 0 || always) + str << amount << ((amount == 1) ? singular : plural); + }; -std::string StatisticsPage::SecondsToDateString(const int sec) { - /* this is all fairly unnecessary, but works:tm: */ - int years = sec * (1 / 31556952.0F); - int months = sec * (1 / 2629746.0F) - (years * 12); - int days = sec * (1 / 86400.0F) - (years * 365.2425F) - (months * 30.436875F); - int hours = sec * (1 / 3600.0F) - (years * 8765.82F) - (months * 730.485F) - (days * 24); - int minutes = (sec) * (1 / 60.0F) - (years * 525949.2F) - (months * 43829.1F) - (days * 1440.0F) - (hours * 60.0F); - int seconds = - sec - (years * 31556952.0F) - (months * 2629746.0F) - (days * 86400.0F) - (hours * 3600.0F) - (minutes * 60.0F); - std::ostringstream return_stream; - add_time_segment(return_stream, years, " year ", " years "); - add_time_segment(return_stream, months, " month ", " months "); - add_time_segment(return_stream, days, " day ", " days "); - add_time_segment(return_stream, hours, " hour ", " hours "); - add_time_segment(return_stream, minutes, " minute ", " minutes "); - if (seconds > 0 || return_stream.str().size() == 0) - return_stream << seconds << ((seconds == 1) ? " second" : " seconds"); - return return_stream.str(); + std::ostringstream string; + add_time_segment(string, years, " year ", " years "); + add_time_segment(string, months, " month ", " months "); + add_time_segment(string, days, " day ", " days "); + add_time_segment(string, hours, " hour ", " hours "); + + if (unit == TimeUnits::MINUTES) { + add_time_segment(string, minutes, " minute", " minutes", true); + return string.str(); + } else { + add_time_segment(string, minutes, " minute ", " minutes "); + } + + add_time_segment(string, seconds, " second", " seconds", true); + return string.str(); } inline int GetTotalWithScore(const int score) { @@ -131,8 +139,8 @@ QTextStream ts(&string); ts << Anime::db.GetTotalAnimeAmount() << '\n'; ts << Anime::db.GetTotalEpisodeAmount() << '\n'; - ts << MinutesToDateString(Anime::db.GetTotalWatchedAmount()).c_str() << '\n'; - ts << MinutesToDateString(Anime::db.GetTotalPlannedAmount()).c_str() << '\n'; + ts << Strings::ToQString(TimeToDateString(TimeUnits::MINUTES, Anime::db.GetTotalWatchedAmount(), 60.0)) << '\n'; + ts << Strings::ToQString(TimeToDateString(TimeUnits::MINUTES, Anime::db.GetTotalPlannedAmount(), 60.0)) << '\n'; ts << Anime::db.GetAverageScore() << '\n'; ts << Anime::db.GetScoreDeviation(); _anime_list->GetParagraph()->SetText(string); @@ -142,7 +150,7 @@ _score_distribution_graph->AddItem(i, GetTotalWithScore(i)); string = ""; - ts << Strings::ToQString(SecondsToDateString(session.uptime() / 1000)) << '\n'; + ts << Strings::ToQString(TimeToDateString(TimeUnits::SECONDS, session.uptime() / 1000)) << '\n'; ts << session.GetRequests(); /* Application */ // UiUtils::SetPlainTextEditData(application_data, QString::number(session.uptime() / 1000));
--- a/src/gui/pages/torrents.cc Wed Jan 24 20:18:59 2024 -0500 +++ b/src/gui/pages/torrents.cc Sun Feb 04 21:17:17 2024 -0500 @@ -6,10 +6,10 @@ #include "gui/widgets/text.h" #include "track/media.h" +#include <QHeaderView> #include <QVBoxLayout> #include <QToolBar> #include <QTreeView> -#include <QMainWindow> #include <QByteArray> #include <QDataStream> #include <QThread> @@ -24,10 +24,11 @@ #include "anitomy/anitomy.h" /* This file is very, very similar to the anime list page. - - It differs from Taiga in that it uses tabs instead of - those "groups", but those are custom painted and a pain in the ass to - maintain over multiple platforms. */ + * + * It differs from Taiga in that it uses tabs instead of + * those "groups", but those are custom painted and a pain in the ass to + * maintain over multiple platforms. +*/ TorrentsPageListSortFilter::TorrentsPageListSortFilter(QObject* parent) : QSortFilterProxyModel(parent) { } @@ -398,6 +399,8 @@ treeview->setColumnWidth(TorrentsPageListModel::TL_FILENAME, 200); treeview->setColumnWidth(TorrentsPageListModel::TL_RELEASEDATE, 190); + treeview->header()->setStretchLastSection(false); + layout->addWidget(treeview); } }
--- a/src/gui/widgets/anime_info.cc Wed Jan 24 20:18:59 2024 -0500 +++ b/src/gui/widgets/anime_info.cc Sun Feb 04 21:17:17 2024 -0500 @@ -35,12 +35,13 @@ * QString because QTextStream sucks and assumes * Latin1 (on Windows?) */ + const auto genres = anime.GetGenres(); details_data_s << Strings::ToQString(Translate::ToLocalString(anime.GetFormat())) << "\n" << anime.GetEpisodes() << "\n" - << Strings::ToQString(Translate::ToLocalString(anime.GetUserStatus())) << "\n" + << Strings::ToQString(Translate::ToLocalString(anime.GetAiringStatus())) << "\n" << Strings::ToQString(Translate::ToLocalString(anime.GetSeason())) << " " << anime.GetAirDate().GetYear().value_or(2000) << "\n" - << Strings::ToQString(Strings::Implode(anime.GetGenres(), ", ")) << "\n" + << Strings::ToQString((genres.size() > 1) ? Strings::Implode(genres, ", ") : "-") << "\n" << anime.GetAudienceScore() << "%"; _details->GetParagraph()->SetText(details_data);
--- a/src/gui/widgets/sidebar.cc Wed Jan 24 20:18:59 2024 -0500 +++ b/src/gui/widgets/sidebar.cc Sun Feb 04 21:17:17 2024 -0500 @@ -4,6 +4,8 @@ #include <QListWidgetItem> #include <QMouseEvent> +#include <iostream> + SideBar::SideBar(QWidget* parent) : QListWidget(parent) { setFrameShape(QFrame::NoFrame); setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); @@ -30,6 +32,7 @@ void SideBar::SetBackgroundColor(QColor color) { viewport()->setAutoFillBackground(color != Qt::transparent); + QPalette pal(palette()); pal.setColor(QPalette::Window, color); setPalette(pal); @@ -66,26 +69,30 @@ } int SideBar::AddSeparatorsToIndex(int index) { - int i, j; - for (i = 0, j = 0; i < index;) { - i++; - if (IndexIsSeparator(indexFromItem(item(i)))) - j++; + int i = 0, separators = 0, items = 0; + + for (; items <= index; ) { + if (IndexIsSeparator(indexFromItem(item(i++)))) { + separators++; + } else { + items++; + } } - return i + j; + + return index + separators; } int SideBar::RemoveSeparatorsFromIndex(int index) { - int i, j; - for (i = 0, j = 0; i < index; i++) { + int i = 0, items = 0; + for (; i < index; i++) { if (!IndexIsSeparator(indexFromItem(item(i)))) - j++; + items++; } - return j; + return items; } bool SideBar::IndexIsSeparator(QModelIndex index) const { - return !(index.isValid() && index.flags() & Qt::ItemIsEnabled); + return !index.isValid() || !(index.flags() & Qt::ItemIsEnabled); } QItemSelectionModel::SelectionFlags SideBar::selectionCommand(const QModelIndex& index, const QEvent*) const {
--- a/src/gui/window.cc Wed Jan 24 20:18:59 2024 -0500 +++ b/src/gui/window.cc Sun Feb 04 21:17:17 2024 -0500 @@ -48,18 +48,6 @@ # include "sys/win32/dark_theme.h" #endif -enum class Pages { - NOW_PLAYING, - - ANIME_LIST, - HISTORY, - STATISTICS, - - SEARCH, - SEASONS, - TORRENTS -}; - void PlayingThread::run() { std::vector<std::string> files; Track::Media::GetCurrentlyPlaying(files); @@ -124,6 +112,7 @@ void MainWindow::AddMainWidgets() { int page = static_cast<int>(Pages::ANIME_LIST); + if (sidebar.get()) { main_widget->layout()->removeWidget(sidebar.get()); sidebar.reset(); @@ -149,10 +138,13 @@ sidebar->AddItem(tr("Torrents"), SideBar::CreateIcon(":/icons/16x16/feed.png")); stack.reset(new QStackedWidget(main_widget.get())); + stack->addWidget(new NowPlayingPage(main_widget.get())); + /* ---- */ stack->addWidget(new AnimeListPage(main_widget.get())); stack->addWidget(new HistoryPage(main_widget.get())); stack->addWidget(new StatisticsPage(main_widget.get())); + /* ---- */ stack->addWidget(new SearchPage(main_widget.get())); stack->addWidget(new SeasonsPage(main_widget.get())); stack->addWidget(new TorrentsPage(main_widget.get())); @@ -166,7 +158,6 @@ void MainWindow::CreateBars() { QMenuBar* menubar = new QMenuBar(this); - QMenu* folder_menu; /* this is used twice, so we declare it here */ QAction* sync_action; { @@ -176,36 +167,7 @@ { folder_menu = menu->addMenu(tr("&Library folders")); - /* add in all of our existing folders... */ - std::size_t i = 0; - for (const auto& path : session.config.library.paths) { - const QString folder = Strings::ToQString(path); - QAction* action = folder_menu->addAction(folder, [folder]{ - QDesktopServices::openUrl(QUrl::fromLocalFile(folder)); - }); - if (i < 9) - action->setShortcut(QKeySequence(Qt::ALT | static_cast<Qt::Modifier>(Qt::Key_1 + i))); - else if (i == 9) - action->setShortcut(QKeySequence(Qt::ALT | Qt::Key_0)); - /* don't bother with a shortcut in case of more... */ - i++; - } - - folder_menu->addSeparator(); - - { - folder_menu->addAction(tr("&Add new folder..."), [this]{ - const QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"), - QDir::homePath(), - QFileDialog::ShowDirsOnly - | QFileDialog::DontResolveSymlinks); - if (dir.isEmpty()) - return; - session.config.library.paths.insert(Strings::ToUtf8String(dir)); - /* we have to recreate the menu bar to add the new folder */ - CreateBars(); - }); - } + UpdateFolderMenu(); } { @@ -229,7 +191,7 @@ menu->addSeparator(); { - QAction* action = menu->addAction(tr("E&xit"), qApp, &QApplication::quit); + QAction* action = menu->addAction(tr("E&xit"), this, &MainWindow::close); action->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_Q)); } } @@ -314,7 +276,7 @@ QAction* action = menu->addAction(tr("&Settings"), [this] { SettingsDialog dialog(this); dialog.exec(); - CreateBars(); + UpdateFolderMenu(); }); action->setMenuRole(QAction::PreferencesRole); } @@ -326,60 +288,78 @@ { /* Pages... */ - std::map<QAction*, int> page_to_index_map = {}; - - QActionGroup* pages_group = new QActionGroup(this); + QActionGroup* pages_group = new QActionGroup(menu); pages_group->setExclusive(true); { QAction* action = pages_group->addAction(menu->addAction(tr("&Now Playing"))); action->setCheckable(true); - page_to_index_map[action] = 0; + connect(action, &QAction::toggled, this, [this] { + sidebar->SetCurrentItem(0); + }); } { QAction* action = pages_group->addAction(menu->addAction(tr("&Anime List"))); action->setCheckable(true); action->setChecked(true); - page_to_index_map[action] = 1; + connect(action, &QAction::toggled, this, [this] { + sidebar->SetCurrentItem(1); + }); } { QAction* action = pages_group->addAction(menu->addAction(tr("&History"))); action->setCheckable(true); - page_to_index_map[action] = 2; + connect(action, &QAction::toggled, this, [this] { + sidebar->SetCurrentItem(2); + }); } { QAction* action = pages_group->addAction(menu->addAction(tr("&Statistics"))); action->setCheckable(true); - page_to_index_map[action] = 3; + connect(action, &QAction::toggled, this, [this] { + sidebar->SetCurrentItem(3); + }); } { QAction* action = pages_group->addAction(menu->addAction(tr("S&earch"))); action->setCheckable(true); - page_to_index_map[action] = 4; + connect(action, &QAction::toggled, this, [this] { + sidebar->SetCurrentItem(4); + }); } { QAction* action = pages_group->addAction(menu->addAction(tr("Se&asons"))); action->setCheckable(true); - page_to_index_map[action] = 5; + connect(action, &QAction::toggled, this, [this] { + sidebar->SetCurrentItem(5); + }); } { QAction* action = pages_group->addAction(menu->addAction(tr("&Torrents"))); action->setCheckable(true); - page_to_index_map[action] = 6; + connect(action, &QAction::toggled, this, [this] { + sidebar->SetCurrentItem(6); + }); } /* pain in my ass */ - connect(sidebar.get(), &SideBar::CurrentItemChanged, this, - [pages_group](int index) { pages_group->actions()[index]->setChecked(true); }); + connect(sidebar.get(), &SideBar::CurrentItemChanged, this, [pages_group](int index) { + QAction* checked = pages_group->checkedAction(); - connect(pages_group, &QActionGroup::triggered, this, - [this, page_to_index_map](QAction* action) { sidebar->SetCurrentItem(page_to_index_map.at(action)); }); + const QList<QAction*>& actions = pages_group->actions(); + if (index > actions.size()) + return; + + if (checked) + checked->setChecked(false); + actions[index]->setChecked(true); + }); } menu->addSeparator(); @@ -479,12 +459,55 @@ toolbar->addAction(QIcon(":/icons/24x24/gear.png"), tr("S&ettings"), [this] { SettingsDialog dialog(this); dialog.exec(); - CreateBars(); + /* library folders might have changed! */ + UpdateFolderMenu(); }); addToolBar(toolbar); } } +void MainWindow::UpdateFolderMenu() { + if (!folder_menu) + return; + + folder_menu->clear(); + + /* add in all of our existing folders... */ + std::size_t i = 0; + for (const auto& path : session.config.library.paths) { + const QString folder = Strings::ToQString(path); + QAction* action = folder_menu->addAction(folder, [folder]{ + QDesktopServices::openUrl(QUrl::fromLocalFile(folder)); + }); + + if (i < 9) { + /* Qt::Key_1 is equivalent to 1 in ASCII, so we can use the same + * stupid `'0' + i` trick here + */ + action->setShortcut(QKeySequence(Qt::ALT | static_cast<Qt::Modifier>(Qt::Key_1 + i))); + } else if (i == 9) { + action->setShortcut(QKeySequence(Qt::ALT | Qt::Key_0)); + } + /* don't bother with a shortcut in case of more... */ + i++; + } + + folder_menu->addSeparator(); + + { + folder_menu->addAction(tr("&Add new folder..."), [this]{ + const QString dir = QFileDialog::getExistingDirectory(this, tr("Open Directory"), + QDir::homePath(), + QFileDialog::ShowDirsOnly + | QFileDialog::DontResolveSymlinks); + if (dir.isEmpty()) + return; + session.config.library.paths.insert(Strings::ToUtf8String(dir)); + UpdateFolderMenu(); + }); + } +} + void MainWindow::SetActivePage(QWidget* page) { this->setCentralWidget(page); }
--- a/src/main.cc Wed Jan 24 20:18:59 2024 -0500 +++ b/src/main.cc Sun Feb 04 21:17:17 2024 -0500 @@ -1,7 +1,9 @@ #include "core/session.h" #include "core/anime_db.h" #include "core/strings.h" +#include "services/anilist.h" #include "gui/window.h" + #include <QApplication> #include <QStyleFactory> #include <QTranslator> @@ -13,10 +15,11 @@ int main(int argc, char** argv) { QApplication app(argc, argv); + app.setApplicationName("minori"); + app.setApplicationDisplayName("Minori"); app.setAttribute(Qt::AA_DontShowIconsInMenus, true); session.config.Load(); - session.config.locale.RefreshAvailableLocales(); Anime::db.LoadDatabaseFromDisk(); MainWindow window;
--- a/src/services/anilist.cc Wed Jan 24 20:18:59 2024 -0500 +++ b/src/services/anilist.cc Sun Feb 04 21:17:17 2024 -0500 @@ -244,6 +244,59 @@ return 1; } +/* return is a vector of anime ids */ +std::vector<int> Search(const std::string& search) { + constexpr std::string_view query = + "query ($search: String) {\n" + " Page (page: 1, perPage: 50) {\n" + " media (search: $search, type: ANIME) {\n" + " coverImage {\n" + " large\n" + " }\n" + " id\n" + " title {\n" + " romaji\n" + " english\n" + " native\n" + " }\n" + " format\n" + " status\n" + " averageScore\n" + " season\n" + " startDate {\n" + " year\n" + " month\n" + " day\n" + " }\n" + " genres\n" + " episodes\n" + " duration\n" + " synonyms\n" + " description(asHtml: false)\n" + " }\n" + " }\n" + "}\n"; + + // clang-format off + nlohmann::json json = { + {"query", query}, + {"variables", { + {"search", search} + }} + }; + // clang-format on + + auto res = SendJSONRequest(json); + + std::vector<int> ret; + ret.reserve(res["data"]["Page"]["media"].size()); + + for (const auto& media : res["data"]["Page"]["media"].items()) + ret.push_back(ParseMediaJson(media.value())); + + return ret; +} + int UpdateAnimeEntry(int id) { /** * possible values: @@ -263,18 +316,17 @@ * float[] advancedScores, * Date startedAt, * Date completedAt - **/ + **/ Anime::Anime& anime = Anime::db.items[id]; if (!anime.IsInUserList()) return 0; - constexpr std::string_view query = "mutation ($media_id: Int, $progress: Int, $status: MediaListStatus, $score: Int, " - "$notes: String, $start: FuzzyDateInput, $comp: FuzzyDateInput, $repeat: Int) {\n" - " SaveMediaListEntry (mediaId: $media_id, progress: $progress, status: $status, " - "scoreRaw: $score, notes: $notes, startedAt: $start, completedAt: $comp, repeat: $repeat) {\n" - " id\n" - " }\n" - "}\n"; + constexpr std::string_view query = + "mutation ($media_id: Int, $progress: Int, $status: MediaListStatus, $score: Int, $notes: String, $start: FuzzyDateInput, $comp: FuzzyDateInput, $repeat: Int) {\n" + " SaveMediaListEntry (mediaId: $media_id, progress: $progress, status: $status, scoreRaw: $score, notes: $notes, startedAt: $start, completedAt: $comp, repeat: $repeat) {\n" + " id\n" + " }\n" + "}\n"; // clang-format off nlohmann::json json = { {"query", query},
--- a/src/services/services.cc Wed Jan 24 20:18:59 2024 -0500 +++ b/src/services/services.cc Sun Feb 04 21:17:17 2024 -0500 @@ -2,7 +2,6 @@ #include "core/session.h" #include "gui/dialog/settings.h" #include "services/anilist.h" -#include <QMessageBox> namespace Services { @@ -13,6 +12,13 @@ } } +std::vector<int> Search(const std::string& search) { + switch (session.config.service) { + case Anime::Services::ANILIST: return AniList::Search(search); + default: return {}; + } +} + void UpdateAnimeEntry(int id) { switch (session.config.service) { case Anime::Services::ANILIST: AniList::UpdateAnimeEntry(id); break;
--- a/src/sys/glib/dark_theme.cc Wed Jan 24 20:18:59 2024 -0500 +++ b/src/sys/glib/dark_theme.cc Sun Feb 04 21:17:17 2024 -0500 @@ -1,11 +1,12 @@ #include <gio/gio.h> #include <cstring> - -#include <iostream> +#include <string_view> namespace glib { bool IsInDarkTheme() { + bool success = false; + GSettings* settings = ::g_settings_new("org.gnome.desktop.interface"); if (!settings) return false; @@ -19,12 +20,31 @@ if (!str) /* how */ return false; - bool success = !std::strcmp(str, "prefer-dark"); + success |= !std::strcmp(str, "prefer-dark"); + + ::g_variant_unref(val); + + if (success) { + ::g_object_unref(settings); + return success; + } + + GVariant* gtk_theme = ::g_settings_get_value(settings, "gtk-theme"); + if (!gtk_theme) + return false; - /* unref these */ - ::g_variant_unref(val); + const gchar* gtk_theme_str; + ::g_variant_get(gtk_theme, "&s", gtk_theme_str); + if (!gtk_theme_str) + return false; + + static constexpr std::string_view suffix = "-dark"; + + size_t gtk_theme_len = strlen(gtk_theme_str); + + success |= !std::strncmp(gtk_theme_str + gtk_theme_len - suffix.length(), suffix.data(), suffix.length()); + ::g_object_unref(settings); - return success; }
--- a/src/sys/osx/filesystem.cc Wed Jan 24 20:18:59 2024 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,51 +0,0 @@ -#include "sys/osx/filesystem.h" - -#include <CoreFoundation/CoreFoundation.h> -#include <objc/runtime.h> - -#include <string> - -/* These constants are defined in Foundation but not - * exposed to CoreFoundation users. -*/ -static constexpr unsigned long NSApplicationSupportDirectory = 14; -static constexpr unsigned long NSUserDomainMask = 1; - -extern "C" { - CFArrayRef NSSearchPathForDirectoriesInDomains(unsigned long directory, unsigned long domainMask, BOOL expandTilde); -} - -namespace osx { - -bool GetApplicationSupportDirectory(std::string& result) { - // NSArray* strings = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, ON); - const CFArrayRef strings = NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, true); - if (!strings) - return false; - - // NSIndex index = [strings count]; - const CFIndex count = CFArrayGetCount(strings); - if (count < 1) { - CFRelease(strings); - return false; - } - - // NSString* string = [strings objectAtIndex: 0]; - const CFStringRef string = reinterpret_cast<CFStringRef>(CFArrayGetValueAtIndex(strings, 0)); - if (!string) { - CFRelease(strings); - return false; - } - - // result = [string UTF8String]; - result.resize(CFStringGetMaximumSizeForEncoding(CFStringGetLength(string), kCFStringEncodingUTF8) + 1); - if (!CFStringGetCString(string, &result.front(), result.length(), kCFStringEncodingUTF8)) { - CFRelease(strings); - return false; - } - result.resize(result.find_first_of('\0')); - - return true; -} - -} // namespace osx