working save & save as

master
Melroy van den Berg 2021-03-29 22:03:14 +02:00
parent 02978f7565
commit 747ba7587f
7 changed files with 209 additions and 25 deletions

View File

@ -3,6 +3,7 @@
#include <stdexcept> #include <stdexcept>
#include <fstream> #include <fstream>
#include <sstream> #include <sstream>
#include <iostream>
#ifdef LEGACY_CXX #ifdef LEGACY_CXX
#include <experimental/filesystem> #include <experimental/filesystem>
@ -15,6 +16,7 @@ namespace n_fs = ::std::filesystem;
/** /**
* Get file from disk * Get file from disk
* \param path File path * \param path File path
* \throw std::runtime_error exception when file is not found (or not a regular file)
* \return AST model of markdown file (cmark_node) * \return AST model of markdown file (cmark_node)
*/ */
std::string const File::read(const std::string &path) std::string const File::read(const std::string &path)
@ -35,6 +37,20 @@ std::string const File::read(const std::string &path)
} }
} }
/**
* Write file to disk
* \param path File path location for storing the file
* \param content Content that needs to be written to file
* \throw std::ifstream::failure or std::ios_base::failure when file can't be open or can't be read/written
*/
void File::write(const std::string &path, const std::string &content)
{
std::ofstream file;
file.open(path.c_str());
file << content;
file.close();
}
/** /**
* Fetch file from IFPS network (create a new client connection for thread safety) * Fetch file from IFPS network (create a new client connection for thread safety)
* \param path File path * \param path File path
@ -48,3 +64,15 @@ std::string const File::fetch(const std::string &path)
client.FilesGet(path, &contents); client.FilesGet(path, &contents);
return contents.str(); return contents.str();
} }
/**
* Publish file to IPFS network (does *not* need to be thead-safe, but is thread-safe nevertheless now)
* \param filename Filename that gets stored in IPFS
* \param content Content that needs to be written to the IPFS network
* \return IPFS content-addressed identifier (CID)
*/
std::string const File::publish(const std::string &filename, const std::string &content)
{
// TODO: Publish file to IPFS
return "CID";
}

View File

@ -10,7 +10,9 @@
class File class File
{ {
public: public:
static std::string const read(const std::string &path); /*!< Read file from disk */ static std::string const read(const std::string &path);
static std::string const fetch(const std::string &path); /*!< Fetch file from IPFS network */ static void write(const std::string &path, const std::string &content);
static std::string const fetch(const std::string &path);
static std::string const publish(const std::string &filename, const std::string &content);
}; };
#endif #endif

View File

@ -10,18 +10,15 @@
#include <glibmm/fileutils.h> #include <glibmm/fileutils.h>
#include <glibmm/miscutils.h> #include <glibmm/miscutils.h>
#include <glibmm/main.h> #include <glibmm/main.h>
#include <glibmm/convert.h>
#include <gdk-pixbuf/gdk-pixbuf.h> #include <gdk-pixbuf/gdk-pixbuf.h>
#include <glibmm/miscutils.h> #include <glibmm/miscutils.h>
#include <cmark-gfm.h> #include <cmark-gfm.h>
#include <pthread.h> #include <pthread.h>
#include <iostream> #include <iostream>
#include <fstream>
#include <nlohmann/json.hpp> #include <nlohmann/json.hpp>
/**
* For info: NDEBUG variable be used for debugging purpose, example:
* #ifdef NDEBUG
* #endif
*/
MainWindow::MainWindow() MainWindow::MainWindow()
: m_accelGroup(Gtk::AccelGroup::create()), : m_accelGroup(Gtk::AccelGroup::create()),
m_settings(), m_settings(),
@ -43,6 +40,7 @@ MainWindow::MainWindow()
requestPath(""), requestPath(""),
finalRequestPath(""), finalRequestPath(""),
currentContent(""), currentContent(""),
currentFileSavedPath(""),
currentHistoryIndex(0), currentHistoryIndex(0),
ipfs("localhost", 5001) // Connect to IPFS daemon ipfs("localhost", 5001) // Connect to IPFS daemon
{ {
@ -307,7 +305,7 @@ MainWindow::MainWindow()
m_homeButton.set_tooltip_text("Home page (Alt+Home)"); m_homeButton.set_tooltip_text("Home page (Alt+Home)");
m_statusButton.set_tooltip_text("IPFS Network Status"); m_statusButton.set_tooltip_text("IPFS Network Status");
// Disable back/forward button on start-up // Disable back/forward buttons on start-up
m_backButton.set_sensitive(false); m_backButton.set_sensitive(false);
m_forwardButton.set_sensitive(false); m_forwardButton.set_sensitive(false);
@ -396,14 +394,14 @@ MainWindow::MainWindow()
// timer will do the updates later // timer will do the updates later
this->update_connection_status(); this->update_connection_status();
// Show homepage if debugging is disabled // Show homepage if debugging is disabled
#ifdef NDEBUG #ifdef NDEBUG
go_home(); go_home();
#else #else
std::cout << "INFO: Running as Debug mode, opening test.md." << std::endl; std::cout << "INFO: Running as Debug mode, opening test.md." << std::endl;
// Load test file when developing // Load test file when developing
doRequest("file://../../test.md", true); doRequest("file://../../test.md", true);
#endif #endif
} }
/** /**
@ -647,17 +645,141 @@ void MainWindow::new_doc()
void MainWindow::open() void MainWindow::open()
{ {
std::cout << "INFO: TODO" << std::endl; auto dialog = new Gtk::FileChooserDialog("Open", Gtk::FILE_CHOOSER_ACTION_OPEN);
dialog->set_transient_for(*this);
dialog->set_modal(true);
dialog->signal_response().connect(sigc::bind(sigc::mem_fun(*this, &MainWindow::on_open_dialog_response), dialog));
dialog->add_button("_Cancel", Gtk::ResponseType::RESPONSE_CANCEL);
dialog->add_button("_Open", Gtk::ResponseType::RESPONSE_OK);
// Add filters, so that only certain file types can be selected:
auto filter_markdown = Gtk::FileFilter::create();
filter_markdown->set_name("Markdown files (.md)");
filter_markdown->add_mime_type("text/markdown");
dialog->add_filter(filter_markdown);
auto filter_any = Gtk::FileFilter::create();
filter_any->set_name("Any files");
filter_any->add_pattern("*");
dialog->add_filter(filter_any);
dialog->show();
}
void MainWindow::on_open_dialog_response(int response_id, Gtk::FileChooserDialog *dialog)
{
switch (response_id)
{
case Gtk::ResponseType::RESPONSE_OK:
{
auto filename = dialog->get_file()->get_path();
std::cout << "TODO. File selected: " << filename << std::endl;
break;
}
case Gtk::ResponseType::RESPONSE_CANCEL:
{
break;
}
default:
{
std::cerr << "ERROR: Unexpected button clicked." << std::endl;
break;
}
}
delete dialog;
} }
void MainWindow::save() void MainWindow::save()
{ {
std::cout << "INFO: TODO" << std::endl; if (currentFileSavedPath.empty())
{
this->save_as();
}
else
{
if (this->isEditorEnabled())
{
try
{
File::write(currentFileSavedPath, this->currentContent);
}
catch (std::ios_base::failure &e)
{
std::cerr << "ERROR: Could not write file: " << currentFileSavedPath << ". Error: " << e.what() << ".\nError code: " << e.code() << std::endl;
}
}
else
{
std::cerr << "ERROR: Saving while \"file saved path\" is filled and editor is disabled should not happen!?" << std::endl;
}
}
} }
void MainWindow::save_as() void MainWindow::save_as()
{ {
std::cout << "INFO: TODO" << std::endl; auto dialog = new Gtk::FileChooserDialog("Save", Gtk::FILE_CHOOSER_ACTION_SAVE);
dialog->set_transient_for(*this);
dialog->set_modal(true);
dialog->set_do_overwrite_confirmation(true);
dialog->signal_response().connect(sigc::bind(sigc::mem_fun(*this, &MainWindow::on_save_as_dialog_response), dialog));
dialog->add_button("_Cancel", Gtk::ResponseType::RESPONSE_CANCEL);
dialog->add_button("_Save", Gtk::ResponseType::RESPONSE_OK);
// Add filters, so that only certain file types can be selected:
auto filter_markdown = Gtk::FileFilter::create();
filter_markdown->set_name("Markdown files (.md)");
filter_markdown->add_mime_type("text/markdown");
dialog->add_filter(filter_markdown);
auto filter_any = Gtk::FileFilter::create();
filter_any->set_name("Any files");
filter_any->add_pattern("*");
dialog->add_filter(filter_any);
// If user is saving as an existing file, set the current uri path
if (!this->currentFileSavedPath.empty())
{
dialog->set_uri(Glib::filename_to_uri(currentFileSavedPath));
}
dialog->show();
}
void MainWindow::on_save_as_dialog_response(int response_id, Gtk::FileChooserDialog *dialog)
{
switch (response_id)
{
case Gtk::ResponseType::RESPONSE_OK:
{
auto filePath = dialog->get_file()->get_path();
if (!filePath.ends_with(".md"))
filePath.append(".md");
// Save current content to file path
try
{
File::write(filePath, this->currentContent);
// Set/update the current file saved path variable (used for the 'save' feature)
if (this->isEditorEnabled())
this->currentFileSavedPath = filePath;
}
catch (std::ios_base::failure &e)
{
std::cerr << "ERROR: Could not write file: " << filePath << ". Error: " << e.what() << ".\nError code: " << e.code() << std::endl;
}
break;
}
case Gtk::ResponseType::RESPONSE_CANCEL:
{
break;
}
default:
{
std::cerr << "ERROR: Unexpected button clicked." << std::endl;
break;
}
}
delete dialog;
} }
void MainWindow::publish() void MainWindow::publish()
@ -880,13 +1002,15 @@ void MainWindow::enableEdit()
this->m_draw_main.setViewSourceMenuItem(false); this->m_draw_main.setViewSourceMenuItem(false);
// Connect changed signal // Connect changed signal
this->textChangedSignalHandler = m_draw_main.get_buffer().get()->signal_changed().connect(sigc::mem_fun(this, &MainWindow::editor_changed_text)); this->textChangedSignalHandler = m_draw_main.get_buffer().get()->signal_changed().connect(sigc::mem_fun(this, &MainWindow::editor_changed_text));
// Enable publish button in menu
m_menu.setPublishMenuSensitive(true);
// Set new title // Set new title
set_title("Untitled * - " + m_appName); set_title("Untitled * - " + m_appName);
} }
void MainWindow::disableEdit() void MainWindow::disableEdit()
{ {
if (m_hboxStandardEditorToolbar.is_visible()) if (this->isEditorEnabled())
{ {
this->m_hboxStandardEditorToolbar.hide(); this->m_hboxStandardEditorToolbar.hide();
this->m_hboxFormattingEditorToolbar.hide(); this->m_hboxFormattingEditorToolbar.hide();
@ -896,11 +1020,24 @@ void MainWindow::disableEdit()
// Show "view source" menu item again // Show "view source" menu item again
this->m_draw_main.setViewSourceMenuItem(true); this->m_draw_main.setViewSourceMenuItem(true);
this->m_draw_secondary.clearText(); this->m_draw_secondary.clearText();
// Disable publish button in menu
this->m_menu.setPublishMenuSensitive(false);
// Empty current file saved path
this->currentFileSavedPath = "";
// Restore title // Restore title
set_title(m_appName); set_title(m_appName);
} }
} }
/**
* \brief Check if editor is enabled
* \return true if enabled, otherwise false
*/
bool MainWindow::isEditorEnabled()
{
return m_hboxStandardEditorToolbar.is_visible();
}
/** /**
* Get the file from disk or IPFS network, from the provided path, * Get the file from disk or IPFS network, from the provided path,
* parse the content, and display the document * parse the content, and display the document
@ -997,6 +1134,10 @@ void MainWindow::openFromDisk()
m_draw_main.processDocument(doc); m_draw_main.processDocument(doc);
cmark_node_free(doc); cmark_node_free(doc);
} }
catch (const std::ios_base::failure &e)
{
std::cerr << "ERROR: Could not read file: " << finalRequestPath << ". Error: " << e.what() << ".\nError code: " << e.code() << std::endl;
}
catch (const std::runtime_error &error) catch (const std::runtime_error &error)
{ {
std::cerr << "Error: File request failed, with message: " << error.what() << std::endl; std::cerr << "Error: File request failed, with message: " << error.what() << std::endl;
@ -1039,10 +1180,11 @@ std::string MainWindow::getIconImageFromTheme(const std::string &iconName, const
void MainWindow::editor_changed_text() void MainWindow::editor_changed_text()
{ {
// Retrieve text from text editor // Retrieve text from text editor
std::string text = m_draw_main.getText(); currentContent = m_draw_main.getText();
// Parse the markdown contents // Parse the markdown contents
cmark_node *doc = Parser::parseContent(text); cmark_node *doc = Parser::parseContent(currentContent);
/*std::string md = Parser::renderMarkdown(doc); /* Can be enabled to show the markdown format in terminal:
std::string md = Parser::renderMarkdown(doc);
std::cout << "Markdown:\n" << md << std::endl;*/ std::cout << "Markdown:\n" << md << std::endl;*/
// Show the document as a preview on the right side text-view panel // Show the document as a preview on the right side text-view panel

View File

@ -15,6 +15,7 @@
#include <gtkmm/togglebutton.h> #include <gtkmm/togglebutton.h>
#include <gtkmm/comboboxtext.h> #include <gtkmm/comboboxtext.h>
#include <gtkmm/popover.h> #include <gtkmm/popover.h>
#include <gtkmm/filechooserdialog.h>
#include <gtkmm/entry.h> #include <gtkmm/entry.h>
#include <gtkmm/searchbar.h> #include <gtkmm/searchbar.h>
#include <gtkmm/searchentry.h> #include <gtkmm/searchentry.h>
@ -43,8 +44,10 @@ protected:
void selectAll(); void selectAll();
void new_doc(); void new_doc();
void open(); void open();
void on_open_dialog_response(int response_id, Gtk::FileChooserDialog* dialog);
void save(); void save();
void save_as(); void save_as();
void on_save_as_dialog_response(int response_id, Gtk::FileChooserDialog* dialog);
void publish(); void publish();
void go_home(); void go_home();
void show_status(); void show_status();
@ -155,6 +158,7 @@ private:
std::string requestPath; std::string requestPath;
std::string finalRequestPath; std::string finalRequestPath;
std::string currentContent; std::string currentContent;
std::string currentFileSavedPath;
std::size_t currentHistoryIndex; std::size_t currentHistoryIndex;
std::vector<std::string> history; std::vector<std::string> history;
sigc::connection textChangedSignalHandler; sigc::connection textChangedSignalHandler;
@ -164,6 +168,7 @@ private:
bool isInstalled(); bool isInstalled();
void enableEdit(); void enableEdit();
void disableEdit(); void disableEdit();
bool isEditorEnabled();
void postDoRequest(const std::string &path, bool setAddressBar, bool isHistoryRequest); void postDoRequest(const std::string &path, bool setAddressBar, bool isHistoryRequest);
void processRequest(const std::string &path); void processRequest(const std::string &path);
void fetchFromIPFS(); void fetchFromIPFS();

View File

@ -19,7 +19,8 @@ Menu::Menu(const Glib::RefPtr<Gtk::AccelGroup> &accelgroup)
auto saveAsMenuItem = createMenuItem("Save _As..."); auto saveAsMenuItem = createMenuItem("Save _As...");
saveAsMenuItem->add_accelerator("activate", accelgroup, GDK_KEY_S, Gdk::ModifierType::CONTROL_MASK | Gdk::ModifierType::SHIFT_MASK, Gtk::AccelFlags::ACCEL_VISIBLE); saveAsMenuItem->add_accelerator("activate", accelgroup, GDK_KEY_S, Gdk::ModifierType::CONTROL_MASK | Gdk::ModifierType::SHIFT_MASK, Gtk::AccelFlags::ACCEL_VISIBLE);
saveAsMenuItem->signal_activate().connect(save_as); saveAsMenuItem->signal_activate().connect(save_as);
auto publishMenuItem = createMenuItem("_Publish..."); publishMenuItem = createMenuItem("_Publish...");
publishMenuItem->set_sensitive(false); // disable
publishMenuItem->add_accelerator("activate", accelgroup, GDK_KEY_P, Gdk::ModifierType::CONTROL_MASK, Gtk::AccelFlags::ACCEL_VISIBLE); publishMenuItem->add_accelerator("activate", accelgroup, GDK_KEY_P, Gdk::ModifierType::CONTROL_MASK, Gtk::AccelFlags::ACCEL_VISIBLE);
publishMenuItem->signal_activate().connect(publish); publishMenuItem->signal_activate().connect(publish);
auto quitMenuItem = createMenuItem("_Quit"); auto quitMenuItem = createMenuItem("_Quit");
@ -132,6 +133,11 @@ void Menu::setForwardMenuSensitive(bool sensitive)
forwardMenuItem->set_sensitive(sensitive); forwardMenuItem->set_sensitive(sensitive);
} }
void Menu::setPublishMenuSensitive(bool sensitive)
{
publishMenuItem->set_sensitive(sensitive);
}
/** /**
* \brief Helper method for creating a menu with an image * \brief Helper method for creating a menu with an image
* \return GTKWidget menu item pointer * \return GTKWidget menu item pointer

View File

@ -40,6 +40,7 @@ public:
virtual ~Menu(); virtual ~Menu();
void setBackMenuSensitive(bool sensitive); void setBackMenuSensitive(bool sensitive);
void setForwardMenuSensitive(bool sensitive); void setForwardMenuSensitive(bool sensitive);
void setPublishMenuSensitive(bool sensitive);
protected: protected:
// Child widgets // Child widgets
@ -62,5 +63,6 @@ private:
Gtk::MenuItem *createMenuItem(const Glib::ustring &label_text); Gtk::MenuItem *createMenuItem(const Glib::ustring &label_text);
Gtk::MenuItem *backMenuItem; Gtk::MenuItem *backMenuItem;
Gtk::MenuItem *forwardMenuItem; Gtk::MenuItem *forwardMenuItem;
Gtk::MenuItem *publishMenuItem;
}; };
#endif #endif

View File

@ -1,4 +1,3 @@
# This is just an example page # This is just an example page
We will show some of the features below. We will show some of the features below.