working save & save as
parent
02978f7565
commit
747ba7587f
28
src/file.cc
28
src/file.cc
|
@ -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";
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
Loading…
Reference in New Issue