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 <fstream>
#include <sstream>
#include <iostream>
#ifdef LEGACY_CXX
#include <experimental/filesystem>
@ -15,6 +16,7 @@ namespace n_fs = ::std::filesystem;
/**
* Get file from disk
* \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)
*/
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)
* \param path File path
@ -48,3 +64,15 @@ std::string const File::fetch(const std::string &path)
client.FilesGet(path, &contents);
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
{
public:
static std::string const read(const std::string &path); /*!< Read file from disk */
static std::string const fetch(const std::string &path); /*!< Fetch file from IPFS network */
static std::string const read(const std::string &path);
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

View File

@ -10,18 +10,15 @@
#include <glibmm/fileutils.h>
#include <glibmm/miscutils.h>
#include <glibmm/main.h>
#include <glibmm/convert.h>
#include <gdk-pixbuf/gdk-pixbuf.h>
#include <glibmm/miscutils.h>
#include <cmark-gfm.h>
#include <pthread.h>
#include <iostream>
#include <fstream>
#include <nlohmann/json.hpp>
/**
* For info: NDEBUG variable be used for debugging purpose, example:
* #ifdef NDEBUG
* #endif
*/
MainWindow::MainWindow()
: m_accelGroup(Gtk::AccelGroup::create()),
m_settings(),
@ -43,6 +40,7 @@ MainWindow::MainWindow()
requestPath(""),
finalRequestPath(""),
currentContent(""),
currentFileSavedPath(""),
currentHistoryIndex(0),
ipfs("localhost", 5001) // Connect to IPFS daemon
{
@ -307,7 +305,7 @@ MainWindow::MainWindow()
m_homeButton.set_tooltip_text("Home page (Alt+Home)");
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_forwardButton.set_sensitive(false);
@ -396,14 +394,14 @@ MainWindow::MainWindow()
// timer will do the updates later
this->update_connection_status();
// Show homepage if debugging is disabled
#ifdef NDEBUG
go_home();
#else
std::cout << "INFO: Running as Debug mode, opening test.md." << std::endl;
// Load test file when developing
doRequest("file://../../test.md", true);
#endif
// Show homepage if debugging is disabled
#ifdef NDEBUG
go_home();
#else
std::cout << "INFO: Running as Debug mode, opening test.md." << std::endl;
// Load test file when developing
doRequest("file://../../test.md", true);
#endif
}
/**
@ -647,17 +645,141 @@ void MainWindow::new_doc()
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()
{
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()
{
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()
@ -880,13 +1002,15 @@ void MainWindow::enableEdit()
this->m_draw_main.setViewSourceMenuItem(false);
// Connect changed signal
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_title("Untitled * - " + m_appName);
}
void MainWindow::disableEdit()
{
if (m_hboxStandardEditorToolbar.is_visible())
if (this->isEditorEnabled())
{
this->m_hboxStandardEditorToolbar.hide();
this->m_hboxFormattingEditorToolbar.hide();
@ -896,11 +1020,24 @@ void MainWindow::disableEdit()
// Show "view source" menu item again
this->m_draw_main.setViewSourceMenuItem(true);
this->m_draw_secondary.clearText();
// Disable publish button in menu
this->m_menu.setPublishMenuSensitive(false);
// Empty current file saved path
this->currentFileSavedPath = "";
// Restore title
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,
* parse the content, and display the document
@ -997,6 +1134,10 @@ void MainWindow::openFromDisk()
m_draw_main.processDocument(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)
{
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()
{
// Retrieve text from text editor
std::string text = m_draw_main.getText();
currentContent = m_draw_main.getText();
// Parse the markdown contents
cmark_node *doc = Parser::parseContent(text);
/*std::string md = Parser::renderMarkdown(doc);
cmark_node *doc = Parser::parseContent(currentContent);
/* Can be enabled to show the markdown format in terminal:
std::string md = Parser::renderMarkdown(doc);
std::cout << "Markdown:\n" << md << std::endl;*/
// 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/comboboxtext.h>
#include <gtkmm/popover.h>
#include <gtkmm/filechooserdialog.h>
#include <gtkmm/entry.h>
#include <gtkmm/searchbar.h>
#include <gtkmm/searchentry.h>
@ -43,8 +44,10 @@ protected:
void selectAll();
void new_doc();
void open();
void on_open_dialog_response(int response_id, Gtk::FileChooserDialog* dialog);
void save();
void save_as();
void on_save_as_dialog_response(int response_id, Gtk::FileChooserDialog* dialog);
void publish();
void go_home();
void show_status();
@ -155,6 +158,7 @@ private:
std::string requestPath;
std::string finalRequestPath;
std::string currentContent;
std::string currentFileSavedPath;
std::size_t currentHistoryIndex;
std::vector<std::string> history;
sigc::connection textChangedSignalHandler;
@ -164,6 +168,7 @@ private:
bool isInstalled();
void enableEdit();
void disableEdit();
bool isEditorEnabled();
void postDoRequest(const std::string &path, bool setAddressBar, bool isHistoryRequest);
void processRequest(const std::string &path);
void fetchFromIPFS();

View File

@ -19,7 +19,8 @@ Menu::Menu(const Glib::RefPtr<Gtk::AccelGroup> &accelgroup)
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->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->signal_activate().connect(publish);
auto quitMenuItem = createMenuItem("_Quit");
@ -132,6 +133,11 @@ void Menu::setForwardMenuSensitive(bool sensitive)
forwardMenuItem->set_sensitive(sensitive);
}
void Menu::setPublishMenuSensitive(bool sensitive)
{
publishMenuItem->set_sensitive(sensitive);
}
/**
* \brief Helper method for creating a menu with an image
* \return GTKWidget menu item pointer

View File

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

View File

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