This is my sumbission for Capstone project in the Udacity C++ Nanodegree Program.
The application is a simple Dictionary that uses the JSON API from Merriam-Webster online dictionary to retrieve the definitions of words and display them.
Given the complexities of the aforementioned API, this implementation is far from perfect. For instance, I have not implemented all the tokens required (only {bc}
and {sx}
are parsed accordingly, the others are rendered as simple text).
Moreover, I cannot garantee that all the terms can be correctly searched. Here I provide a list of terms garanteed to works (feel free to open an issue if you found a bug):
- no
- jet
- plot
- pet
- as is
- asdf (trigger autocompletion drop down menu)
Start the application, write the word you want to find in the header bar search entry, press enter and wait the response from the Merrian-Webster online service that will appear in the central widget of the application window.
In case a term is not found and the service can provide some suggestions, a drop-down menu is shown, and the you can select the term if it match your expectations.
This file define the namespace app
with the following classes:
- Window : public Gtk::ApplicationWindow
- Layout : public Gtk::Box
- Search : public Gtk::SearchEntry
- ResultView : public Gtk::Label
And the following output stream operators:
- std::ostream& operator<< (std::ostream& out, dict::result const& r)
- std::ostream& operator<< (std::ostream& out, dict::entry const& e)
- std::ostream& operator<< (std::ostream& out, dict::sense const& s)
The classes are used to create the user interface, while the stream operators produces the "pango markup" used to render the ResultView
(ie: a Gtk::Label
able to render "pango markup").
The Window
is constructed by passing a title
, a width
and an height
. During the constructor, an instance of the Layout
is used as a child widget and a signal for Gtk::Window::close-request
is attached to the app::Window::shutdown
.
The shutdown
member function ask the Gio::Application
to quit
so that everything is correctly closed and then return true
to signal the Window
that it can close now.
The Layout
is a vertical Gtk::Box
that contains an Gtk::HeaderBar
on the head, an expanded Gtk::Stack
with a Gtk::ScrolledWindow
that forms the central widget where the ResultView
will be rendered, and a Gtk::Statusbar
on the bottom.
To construct a Layout
you need to pass a Gtk::Window
that is used to render a Gtk::MessageDialog
in case an exception is thrown during the search for a term. The constructor setup a dict::api
class by passing the content of the DICTIONARY_API_KEY
environment variable then setup the header, central and bottom widgets and finally it initialize the signals by calling init_signals
.
The init_signals
member function is used to setup all the signal handlers that are needed for the application to function as expected. In particular, there are four signals that we are using.
The Layout::realize
signal is attached to a lambda function that will render the message "Application Started!" for 2.5 seconds before deleting it.
The Search::activate
is attached to a lambda function that extract the term
using Search::get_text
and call define(term)
in order to perform the lookup in the dictionary.
The Search::term-selected
signal is attached to a lambda function that receive the term
as a parameter and call define(term)
. This last signal is the one that handle the term selection from a suggestion drop-down menu.
Finally a 'Glib::Dispatcher' is used to render the result from the worker thread. We need this because Gtk render needs to be done in the main thread.
The define
member funciton start a thread to perform the lookup of the term
passed as parameter. If anything is found, the result will be shown in the central widget; otherwise, if the service will respond with some suggestions, those terms are shown in a drop-down menu.
If anything goes wrong (like we are not able to parse the response, or to contact the service) a message dialog will be shown.
During construction, a Search
will construct a Gio::Menu
and a Gio::SimpleAction
objects that are used to show the drop-down menu and to handle the user click on one of the suggestions, if any.
This is a simple proxy that expose the signal activate
of the member m_action
. This signal is fired when a drop-down menu entry is clicked by the user, and is the one used in Layout::init_signal
to connect the handler for this kind of user action.
This method is called during a Layout::define
if the result thread will raise a dict::suggestions
exception. This way a drop-down menu is shown with term suggestions.
During construction the ResultView
will setup all the parameters needed to be rendered correctly as a central widget.
This function will render the res
to a string by using a std::ostringstream
and set the result as "pango markup" by calling set_markup
on itself.
This file define the namespace dict
with the following classes:
- api
- suggestions : public std::exception, public std::vector<std::string>
- result
- entry
- sense
The api
class is used to send requests to the Merrian-Webster online service. You can construct an instance of this class by calling api (std::string api_key)
.
This class contains a single member function std::unique_ptr<result> request (std::string word)
that given a word
either return a unique result
object or throws a suggestions
exception.
In order to construct a result
you need to pass a json::value
object using move semantics so that the json data will be moved into result
.
You can construct one object of the other classes (suggestions
, result
, entry
and sense
) by passing a json::value
by reference that contains a valid structure for that kind of object. The sense
object also expect you to pass a sense::type
enumerator class instance.
This file was taken from Boost json library example and is used to get the body of the HTTP response as JSON data. It contains the struct json_body
that is made by a writer
struct and a reader
struct. Only the reader
is used by this application.
- cmake >= 3.22
- make >= 4.3
- gcc/g++ >= 11.3
- PkgConfig
- OpenSSL >= 3.0
- Boost >= 1.80
- Gtkmm >= 4.6
Follow the instructions for your OS below in order to fulfill dependencies for building. Only Ubuntu 22.10, Ubuntu 22.04.1 LTS and Arch Linux are supported.
Note The procedure for building on Ubuntu 22.10 is shorter than Ubuntu 22.04.1 LTS; I advise you to use Ubuntu 22.10 to shorten the time needed to build.
- Install the required packages:
sudo apt-get update && sudo apt-get -y upgrade
sudo apt-get install -y \
cmake make gcc g++ pkgconf build-essential wget \
libtls-dev libssl-dev libgtkmm-4.0-dev
- Create and enter
deps
directory:
mkdir deps
cd deps
- Build and install Boost 1.80:
wget -O boost_1_80_0.tar.gz https://sourceforge.net/projects/boost/files/boost/1.80.0/boost_1_80_0.tar.gz/download
tar xf boost_1_80_0.tar.gz
cd boost_1_80_0/
./bootstrap.sh --prefix=/usr/local
sudo ./b2 --with-system --with-json --with-log -j$(nproc) install
cd ..
- Install the required packages:
sudo apt-get update && sudo apt-get -y upgrade
sudo apt-get install -y \
cmake make gcc pkgconf libtls-dev libssl-dev \
build-essential g++ python-dev-is-python3 autotools-dev libicu-dev libbz2-dev \
autotools-dev mm-common libgtk-4-bin libgtk-4-common libgtk-4-dev libgtk-4-doc \
libxml-parser-perl wget
sudo apt-get reinstall -y docbook-xsl
- Create and enter
deps
directory:
mkdir deps
cd deps
- Build and install Boost 1.80:
wget -O boost_1_80_0.tar.gz https://sourceforge.net/projects/boost/files/boost/1.80.0/boost_1_80_0.tar.gz/download
tar xf boost_1_80_0.tar.gz
cd boost_1_80_0/
./bootstrap.sh --prefix=/usr/local
sudo ./b2 --with-system --with-json --with-log -j$(nproc) install
cd ..
- Build and install sigc++ 3.2:
wget https://github.com/libsigcplusplus/libsigcplusplus/releases/download/3.2.0/libsigc++-3.2.0.tar.xz
tar xf libsigc++-3.2.0.tar.xz
cd libsigc++-3.2.0/
./autogen.sh --prefix=/usr/local
sudo make -j$(nproc) install
cd ..
- Build and install cairomm-1.16
wget -O cairomm-1.16.tar.gz https://github.com/freedesktop/cairomm/archive/refs/heads/cairomm-1-16.tar.gz
tar xf cairomm-1.16.tar.gz
cd cairomm-cairomm-1-16/
./autogen.sh --prefix=/usr/local
sudo make -j$(nproc) install
cd ..
- Build and install glibmm-2.68.2
wget -O glibmm-2.68.2.tar.gz https://github.com/GNOME/glibmm/archive/refs/tags/2.68.2.tar.gz
tar xzf glibmm-2.68.2.tar.gz
cd glibmm-2.68.2/
./autogen.sh --prefix=/usr/local
sudo make -j$(nproc) install
cd ..
- Build and install pangomm-2.50.1
wget -O pangomm-2.50.1.tar.gz https://github.com/GNOME/pangomm/archive/refs/tags/2.50.1.tar.gz
tar xf pangomm-2.50.1.tar.gz
cd pangomm-2.50.1/
./autogen.sh --prefix=/usr/local
sudo make -j$(nproc) install
cd ..
- Build and install GtkMM 4.6.1
wget -O gtkmm-4.6.1.tar.gz https://github.com/GNOME/gtkmm/archive/refs/tags/4.6.1.tar.gz
tar xf gtkmm-4.6.1.tar.gz
cd gtkmm-4.6.1/
./autogen.sh --prefix=/usr/local
sudo make -j$(nproc) install
cd ..
- Install all the development requirements:
sudo pacman -S cmake make gcc pkgconf openssl boost gtkmm-4.0
- Export variables:
export PKG_CONFIG_PATH="/usr/lib/libressl/pkgconfig"
- Clone this repo:
git clone https://github.com/grobx/udacity-CppND-final-project
- Make a build directory in the top level directory:
mkdir build && cd build
- Compile:
cmake .. && make
- Set the dictionary API key (go to https://www.dictionaryapi.com/ to obtain one):
export DICTIONARY_API_KEY="aaa-bbb-ccc"
- Run it:
./Dictionary
- The README.md is included with the project and has instructions for building/running the project.
- If any additional libraries are needed to run the project, these are indicated with cross-platform installation instructions.
- The README describes the project you have built.
- The README indicates which rubric points are addressed. The README also indicates where in the code (i.e. files and line numbers) that the rubric points are addressed.
- The README also indicates the file and class structure, along with the expected behavior or output of the program.
- The project code must compile and run without errors.
- The project accepts input from a user as part of the necessary operation of the program.
- a
Gtk::SearchEntry
(namely app::Search) is used by the user to input the term to lookup
- a
- The project code is organized into classes with class attributes to hold the data, and class methods to perform tasks.
- At least two variables are defined as references, or two functions use pass-by-reference in the project code.
- A promise and future is used to pass data from a worker thread to a parent thread in the project code.
- in app::Layout::define the worker thread that connects to the Merriam-Webster dictionary to lookup for the term is started by passing a promise to it; once the request is done, the worker thread will pass the result to the parent thread using
promise::set_value
or it will callpromise::set_exception
- in app::Layout::define the worker thread that connects to the Merriam-Webster dictionary to lookup for the term is started by passing a promise to it; once the request is done, the worker thread will pass the result to the parent thread using
- The project uses at least one smart pointer: unique_ptr, shared_ptr, or weak_ptr. The project does not use raw pointers.
- dict::api::request return a std::unique_ptr<dict::result> created by mooving the json::value obtained via web API