Signed-off-by: Hans Dijkema <hans@dijkewijk.nl>
This commit is contained in:
2025-11-18 18:04:37 +01:00
parent 6784ba6ebc
commit 36aba3a381
2 changed files with 517 additions and 0 deletions

472
yellownotes.cpp Normal file
View File

@@ -0,0 +1,472 @@
#include "yellownotes.h"
#include <iostream>
#include <string>
#include <filesystem>
#include <vector>
#ifdef __linux
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <sys/stat.h>
#endif
#include <string.h>
#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<GtkMenu *>(_tray_menu);
if (tray_menu) { gtk_widget_destroy(tray_menu); }
tray_menu = gtk_menu_new();
_tray_menu = reinterpret_cast<void *>(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<std::string> 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<GtkMenu *>(_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<GdkEventMask>(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<char *>(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);
}
}

45
yellownotes.h Normal file
View File

@@ -0,0 +1,45 @@
#ifndef YELLOWNOTES_H
#define YELLOWNOTES_H
#include <string>
#include <list>
class YellowNote;
class YellowNotes
{
private:
void *_tray_menu;
void *_app;
std::list<YellowNote *> _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<YellowNotes *>(obj)
#define SIGNAL(type, func, member) \
static void func(GObject *obj, gpointer user_data) { type *o = reinterpret_cast<type *>(user_data); o->member(); }
#define SIGNAL2(type, func, member, argtype) \
static void func(GObject *obj, void *arg, gpointer user_data) { type *o = reinterpret_cast<type *>(user_data); o->member(reinterpret_cast<argtype *>(arg)); }
#define BSIGNAL2(type, func, member, argtype) \
static gboolean func(GObject *obj, void *arg, gpointer user_data) { type *o = reinterpret_cast<type *>(user_data); return o->member(reinterpret_cast<argtype *>(arg)); }
#endif // YELLOWNOTES_H