diff --git a/yellownotes.cpp b/yellownotes.cpp new file mode 100644 index 0000000..57aeb22 --- /dev/null +++ b/yellownotes.cpp @@ -0,0 +1,472 @@ +#include "yellownotes.h" +#include +#include +#include +#include + +#ifdef __linux +#include +#include +#include +#include +#endif + +#include +#include "gtk-imports.h" + +class YellowNote +{ +private: + GtkWindow *_note_widget; + GtkWidget *_note_box; + GtkTextBuffer *_buffer; + GtkWidget *_text_widget; + GtkWidget *_title_box; + GtkWidget *_title_label; + GtkHeaderBar *_note_header; + GtkWidget *_header_evt_box; + std::string _filename; + std::string _note; + std::string _title; + bool _hidden; + int _x; + int _y; + int _width; + int _height; + bool _in_transaction; + + bool _moving; + int _x_orig; + int _y_orig; + +public: + void show(); + void hide(); + +private: + void updateTitle(); + void updatePosition(); + void updateHidden(); + void updateSize(); + +public: + void changed(); + void size_allocated(GtkAllocation *a); + +public: + bool move_begin(GdkEventButton *evt); + bool move_end(GdkEventButton *evt); + bool moving(GdkEventMotion *evt); + +public: + void load(); + void save(); + +public: + YellowNote(const std::string &filename);; + ~YellowNote(); +}; + +SIGNAL2(YellowNote, on_size_allocated, size_allocated, GtkAllocation) +BSIGNAL2(YellowNote, on_button_press, move_begin, GdkEventButton); +BSIGNAL2(YellowNote, on_button_release, move_end, GdkEventButton); +BSIGNAL2(YellowNote, on_mouse_move, moving, GdkEventMotion); + +SIGNAL(YellowNotes, on_new_yellow, newNote) +SIGNAL(YellowNotes, on_quit, quit) + +std::string YellowNotes::imageFile(const char *name) +{ + + return appDir() + "/" + name; +} + +std::string YellowNotes::appDir() +{ + std::string base = "/home/hans/src/yellownotes"; + return base; +} + +std::string YellowNotes::notesDir() +{ +#ifdef __linux + struct passwd *pw = getpwuid(getuid()); + const char *homedir = pw->pw_dir; +#endif + + std::string home_dir = homedir; + std::string notes_dir = home_dir + "/yellownotes"; + + std::filesystem::path nd(notes_dir); + if (!std::filesystem::is_directory(nd)) { + std::filesystem::create_directory(nd); + } + + return notes_dir; +} + +void YellowNotes::popupTrayMenu() +{ + GtkMenu *tray_menu = reinterpret_cast(_tray_menu); + + if (tray_menu) { gtk_widget_destroy(tray_menu); } + + tray_menu = gtk_menu_new(); + _tray_menu = reinterpret_cast(tray_menu); + + GtkMenuItem *new_yellow = gtk_menu_item_new_with_label("New Note"); + GtkMenuItem *handle_notes = gtk_menu_item_new_with_label("Organize Notes"); + GtkWidget *sep = gtk_separator_menu_item_new(); + + GtkMenuItem *quit = gtk_menu_item_new_with_label("Quit"); + + gtk_menu_shell_append(tray_menu, new_yellow); + gtk_menu_shell_append(tray_menu, handle_notes); + gtk_menu_shell_append(tray_menu, sep); + gtk_menu_shell_append(tray_menu, quit); + gtk_widget_show_all(tray_menu); + + g_signal_connect(new_yellow, "activate", on_new_yellow, this); + g_signal_connect(quit, "activate", on_quit, this); + + gtk_menu_popup_at_pointer(tray_menu, nullptr); +} + +void YellowNotes::clearNotes() +{ + while (!_notes.empty()) { + YellowNote *note = _notes.front(); + _notes.pop_front(); + delete note; + } +} + +void YellowNotes::loadNotes() +{ + clearNotes(); + + std::string notes_dir = notesDir(); + + std::filesystem::path folder(notes_dir); + + if(!std::filesystem::is_directory(folder)) + { + throw std::runtime_error(folder.string() + " is not a folder"); + } + + std::vector file_list; + for (const auto& entry : std::filesystem::directory_iterator(folder)) + { + const auto full_name = entry.path().string(); + + if (entry.is_regular_file()) + { + const auto base_name = entry.path().filename().string(); + if (base_name.rfind(".note") > 0) { + YellowNote *note = new YellowNote(full_name); + _notes.push_back(note); + } + } + } +} + +void YellowNotes::newNote() +{ + std::string new_note_title = "New Note"; + std::string notes_dir = notesDir(); + + auto filename = [notes_dir](const std::string & title, int i) { + char s[20]; + sprintf(s, " %d", i); + std::string fn = notes_dir + "/" + title + s + ".note"; + return fn; + }; + + + int i = 1; + std::filesystem::path p(filename(new_note_title, i)); + while(std::filesystem::is_regular_file(p)) { + i += 1; + p = std::filesystem::path(filename(new_note_title, i)); + } + + YellowNote *note = new YellowNote(p); + _notes.push_back(note); + note->show(); + note->save(); +} + +void YellowNotes::quit() +{ + gtk_main_quit(); +} + +YellowNotes::YellowNotes(void *app) +{ + _app = app; + _tray_menu = nullptr; +} + +YellowNotes::~YellowNotes() +{ + if (_tray_menu) { gtk_widget_destroy(reinterpret_cast(_tray_menu)); } + clearNotes(); +} + +//////////////////////////////////////////////////////////////////////////////////////////////////// +/// YellowNote implementation +//////////////////////////////////////////////////////////////////////////////////////////////////// + +YellowNote::YellowNote(const std::string &filename) { + _filename = filename; + + _title = ""; + _x = -1; + _y = -1; + _width = -1; + _height = -1; + _hidden = false; + + _moving = false; + + _note_widget = gtk_window_new(GtkWindowType::GTK_WINDOW_TOPLEVEL); + _buffer = gtk_text_buffer_new(nullptr); + _text_widget = gtk_text_view_new_with_buffer(_buffer); + + _header_evt_box = gtk_event_box_new(); + _note_header = gtk_header_bar_new(); + + gtk_window_set_decorated(_note_widget, false); + _note_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5); + + //_title_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5); + _title_label = gtk_label_new(_title.c_str()); + gtk_label_set_ellipsize(_title_label, PANGO_ELLIPSIZE_END); + gtk_header_bar_set_custom_title(_note_header, _title_label); + + //gtk_container_add(_title_box, _title_label); + gtk_container_add(_note_box, _header_evt_box); + gtk_container_add(_header_evt_box, _note_header); + gtk_container_add(_note_box, _text_widget); + gtk_container_add(_note_widget, _note_box); + + gtk_widget_show_all(_note_widget); + + { + GdkWindow *w = gtk_widget_get_window(_title_label); + int mask = GDK_BUTTON1_MOTION_MASK + GDK_BUTTON_PRESS_MASK + GDK_BUTTON_RELEASE_MASK; + gdk_window_set_events(w, static_cast(mask)); + } + + + g_signal_connect(_note_widget, "size_allocate", on_size_allocated, this); + g_signal_connect(_header_evt_box, "button_press_event", on_button_press, this); + g_signal_connect(_header_evt_box, "button_release_event", on_button_release, this); + g_signal_connect(_header_evt_box, "motion_notify_event", on_mouse_move, this); + + load(); + + _in_transaction = false; +} + +YellowNote::~YellowNote() +{ + gtk_widget_destroy(_note_widget); + _note_widget = nullptr; +} + +void YellowNote::updateTitle() +{ + gtk_label_set_label(_title_label, _title.c_str()); +} + +void YellowNote::updateHidden() +{ + if (_hidden) { hide(); } + else { show(); } +} + +void YellowNote::updatePosition() +{ + gtk_window_move(_note_widget, _x, _y); +} + +void YellowNote::updateSize() +{ + gtk_window_resize(_note_widget, _width, _height); +} + +void YellowNote::show() +{ + gtk_widget_show_all(_note_widget); +} + +void YellowNote::hide() +{ + gtk_widget_hide(_note_widget); +} + + +void YellowNote::changed() +{ + bool sv = false; + + bool hidden = !gtk_widget_is_visible(_note_widget); + if (hidden != _hidden) { + _hidden = hidden; + sv = true; + } + + int w, h; + gtk_window_get_size(_note_widget, &w, &h); + if (w < 50) { w = 50; } + if (_width != w) { + _width = w; + sv = true; + } + + if (h < 50) { h = 50; } + if (_height != h) { + _height = h; + sv = true; + } + + int x, y; + gtk_window_get_position(_note_widget, &x, &y); + if (x <= 0) { x = 1; } + if (y <= 0) { y = 1; } + if (_x != x) { + _x = x; + sv = true; + } + if (_y != y) { + _y = y; + sv = true; + } + + + const char *c_t = gtk_label_get_text (_title_label); + if (c_t != nullptr) { + std::string t(c_t); + if (t != _title) { + _title = t; + sv = true; + } + } + + if (sv) { save(); } +} + +void YellowNote::size_allocated(GtkAllocation *a) +{ + changed(); +} + +bool YellowNote::move_begin(GdkEventButton *evt) +{ + std::cout << "move_begin" << std::endl; + _moving = true; + _x_orig = evt->x_root; + _y_orig = evt->y_root; + return true; +} + +bool YellowNote::move_end(GdkEventButton *evt) +{ + std::cout << "move_end" << std::endl; + _moving = false; + return true; +} + +bool YellowNote::moving(GdkEventMotion *evt) +{ + int x = evt->x_root; + int y = evt->y_root; + + int dx = x - _x_orig; + int dy = y - _y_orig; + + std::cout << "moving " << x << ", " << y << ", dx = " << dx << ", dy = " << dy << ", _x = " << _x << ", _y = " << y << std::endl; + + //gtk_window_move(_note_widget, _x + dx, _y + dy); + + return false; +} + + +void YellowNote::load() +{ + auto readInt = [](FILE *f, int default_value) { + char buffer[100]; + fgets(buffer, 100, f); + int v = atoi(buffer); + if (default_value >= 0 && v == 0) { + return default_value; + } + return v; + }; + + _in_transaction = true; + + std::filesystem::path p(_filename); + const auto base_name = p.filename().string(); + _title = base_name.substr(0, base_name.rfind(".note")); + + size_t s = 0; + if (std::filesystem::is_regular_file(p)) { + s = std::filesystem::file_size(p); + } + FILE *f = fopen(_filename.c_str(), "rt"); + + if (f) { + _hidden = readInt(f, -1); + _x = readInt(f, 200); + _y = readInt(f, 200); + _width = readInt(f, 300); + _height = readInt(f, 200); + + int pos = ftell(f); + char *buf = static_cast(malloc(s)); + memset(buf, 0, s); + int bytes = s - pos; + fread(buf, bytes, 1, f); + + fclose(f); + gtk_text_buffer_set_text(_buffer, buf, strlen(buf)); + free(buf); + } + + updateTitle(); + updateHidden(); + updatePosition(); + updateSize(); + + _in_transaction = false; +} + +void YellowNote::save() +{ + if (_in_transaction) { + return; + } + + std::filesystem::path p(_filename); + FILE *f = fopen(_filename.c_str(), "wt"); + if (f) { + fprintf(f, "%d\n", _hidden); + fprintf(f, "%d\n", _x); + fprintf(f, "%d\n", _y); + fprintf(f, "%d\n", _width); + fprintf(f, "%d\n", _height); + + GtkTextIter start, end; + gtk_text_buffer_get_start_iter(_buffer, &start); + gtk_text_buffer_get_end_iter(_buffer, &end); + gchar *text = gtk_text_buffer_get_text(_buffer, &start, &end, false); + fprintf(f, "%s", text); + fclose(f); + free(text); + } +} + + diff --git a/yellownotes.h b/yellownotes.h new file mode 100644 index 0000000..91ee34f --- /dev/null +++ b/yellownotes.h @@ -0,0 +1,45 @@ +#ifndef YELLOWNOTES_H +#define YELLOWNOTES_H + +#include +#include + +class YellowNote; + +class YellowNotes +{ +private: + void *_tray_menu; + void *_app; + std::list _notes; + +public: + void loadNotes(); + void clearNotes(); + +public: + std::string imageFile(const char *name); + std::string appDir(); + std::string notesDir(); + +public: + void popupTrayMenu(); + void newNote(); + void quit(); + +public: + YellowNotes(void *app); + ~YellowNotes(); +}; + +#define YELLOWNOTES(obj) reinterpret_cast(obj) +#define SIGNAL(type, func, member) \ + static void func(GObject *obj, gpointer user_data) { type *o = reinterpret_cast(user_data); o->member(); } + +#define SIGNAL2(type, func, member, argtype) \ + static void func(GObject *obj, void *arg, gpointer user_data) { type *o = reinterpret_cast(user_data); o->member(reinterpret_cast(arg)); } + +#define BSIGNAL2(type, func, member, argtype) \ + static gboolean func(GObject *obj, void *arg, gpointer user_data) { type *o = reinterpret_cast(user_data); return o->member(reinterpret_cast(arg)); } + +#endif // YELLOWNOTES_H