Files
yellownotes/yellownotes.cpp

1335 lines
36 KiB
C++

#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
#ifdef _WIN32
#include <windows.h>
#endif
#include <string.h>
extern "C" {
#include "gtk-imports.h"
}
#include "tr.h"
static void trim(std::string &s) {
size_t start = s.find_first_not_of(" \t\n\r");
size_t end = s.find_last_not_of(" \t\n\r");
if (start == std::string::npos)
s.clear(); // String contains only whitespace
else
s = s.substr(start, end - start + 1);
}
class YellowNote
{
private:
YellowNotes *_notes;
GtkWindow *_note_widget;
GtkWidget *_evt_box;
GtkWidget *_note_box;
GtkWidget *_frame;
GtkWidget *_note_header;
GtkImage *_color_image;
GtkImage *_delete_image;
GtkImage *_plus_image;
GtkImage *_hide_image;
GtkWidget *_title_label;
GtkWidget *_title_entry;
GtkWidget *_title_separator;
GtkWidget *_scroll_widget;
GtkWidget *_text_widget;
GtkTextBuffer *_buffer;
std::string _filename;
std::string _note;
std::string _title;
bool _hidden;
int _x;
int _y;
int _width;
int _height;
bool _in_transaction;
bool _editing_title;
int _save_counter;
int _save_id;
ColorType_t _color;
bool _color_changed;
bool _moving;
bool _resize_right;
bool _resize_bottom;
bool _resize_edge;
int _x_orig;
int _y_orig;
public:
void show();
void hide();
private:
void updateTitle();
void updatePosition();
void updateHidden();
void updateSize();
void updateColor();
void adjustTitle(bool mutate);
void get_header_screen_coords(int &header_top, int &header_bottom);
void get_frame_screen_coords(int &frame_bottom, int &frame_right);
void get_screen_left_right(GtkWidget *widget, int &left, int &right);
void nextColor();
void deleteMe();
void addNew();
void updateWidgetColors(GtkWidget *w);
public:
void changed(GtkWidget *sender);
void size_allocated(GtkWidget *sender, GtkAllocation *a);
public:
void toFront();
public:
bool move_begin(GtkWidget *sender, GdkEventButton *evt);
bool move_end(GtkWidget *sender, GdkEventButton *evt);
bool moving(GtkWidget *sender, GdkEventMotion *evt);
bool titleEscape(GtkWidget *sender, GdkEventKey *key);
void titleEnter(GtkWidget *sender);
bool titleFocusOut(GtkWidget *sender, GdkEventFocus *evt);
void textChanged(void *sender);
bool textSaveTimeout();
bool windowPresented(void *sender, GdkEventVisibility *evt);
void showNote(GtkWidget *sender);
public:
std::string title();
bool isHidden();
public:
void load();
void save();
public:
YellowNote(YellowNotes *notes, 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(YellowNote, on_title_activate, titleEnter);
BSIGNAL2(YellowNote, on_title_escape, titleEscape, GdkEventKey);
BSIGNAL2(YellowNote, on_title_focus_out, titleFocusOut, GdkEventFocus);
BSIGNAL2(YellowNote, on_window_present, windowPresented, GdkEventVisibility);
SIGNAL(YellowNote, on_text_change, textChanged);
SIGNAL(YellowNote, on_show_note, showNote);
static gboolean on_text_save_timeout(gpointer data)
{
YellowNote *n = reinterpret_cast<YellowNote *>(data);
return n->textSaveTimeout();
}
SIGNAL(YellowNotes, on_new_yellow, newNote)
SIGNAL(YellowNotes, on_show, showNotes);
SIGNAL(YellowNotes, on_reload, reloadNotes);
SIGNAL(YellowNotes, on_setup, setup);
SIGNAL(YellowNotes, on_quit, quit)
SIGNAL(YellowNotes, on_hide_toplevel, topLevelHidden)
SIGNAL(YellowNotes, on_setup_ok, setupClose);
SIGNAL(YellowNotes, on_setup_close, setupCancel);
BSIGNAL2(YellowNotes, on_setup_del, setupDel, void);
std::string YellowNotes::imageFile(const char *name)
{
#ifdef _WIN32
std::string ext = ".png";
#else
std::string ext = ".svg";
#endif
return appDir() + "/" + name + ext;
}
std::string YellowNotes::appDir()
{
#ifdef __linux
std::string base = "/home/hans/src/yellownotes";
#endif
#ifdef _WIN32
std::string base = "c:/devel/yellownotes";
#endif
return base;
}
std::string YellowNotes::notesDir()
{
#ifdef __linux
struct passwd *pw = getpwuid(getuid());
const char *homedir = pw->pw_dir;
#endif
#ifdef _WIN32
char homedir[10240];
snprintf(homedir, 10240, "%s%s", getenv("HOMEDRIVE"), getenv("HOMEPATH"));
#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;
}
std::string YellowNotes::css(ColorType_t type)
{
const char *bgs[] = { "#404040", // dark
"#faf32a", // yellow
"#fcbf56", // orange,
"#5df0f5", // blue
"#fc77f4", // Cyaan
"#74fc94", // greeen
"#f7bcbc", // red
"#cdcfd1" // grey
};
const char *fgs[] = { "white",
"black",
"black",
"black",
"black",
"black",
"black",
"black"
};
auto from_hex = [](std::string hex_num) {
return strtol(hex_num.c_str(), NULL, 16);
};
auto darker = [from_hex](std::string color) {
int red = from_hex(color.substr(1, 2));
int green = from_hex(color.substr(3, 2));
int blue = from_hex(color.substr(5, 2));
float factor = 0.9;
red *= factor;
green *= factor;
blue *= factor;
char buf[20];
sprintf(buf, "#%02x%02x%02x", red, green, blue);
std::string s = buf;
return s;
};
char font_size[20];
sprintf(font_size, "%dpx", _font_size);
std::string css = std::string() +
"label, box.horizontal, textview.view, textview.view text, frame, messagedialog.background {\n"
" background-color: " + bgs[type] + ";\n"
" color: " + fgs[type] + ";\n"
" font-family: sans;\n"
" font-size: " + font_size + ";\n"
"}\n"
"frame border {\n"
" border: none;\n"
" box-shadow: 5px 5px 5px " + darker(bgs[type]) + ";\n"
" margin: 5px;\n"
"}\n";
return css;
}
int YellowNotes::fontSize()
{
return _font_size;
}
int YellowNotes::iconSize()
{
return _font_size * 1.5;
}
void YellowNotes::popupTrayMenu(void *sender)
{
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 *show_notes = gtk_menu_item_new_with_label(_("Show Notes"));
GtkMenuItem *reload_notes = gtk_menu_item_new_with_label(_("Reload Notes"));
GtkWidget *sep = gtk_separator_menu_item_new();
std::list<GtkWidget *> hidden;
std::list<YellowNote *> h_notes;
std::list<YellowNote *>::iterator it = _notes.begin();
while (it != _notes.end()) {
YellowNote *n = *it;
GtkWidget *m = gtk_menu_item_new_with_label(n->title().c_str());
if (n->isHidden()) {
hidden.push_back(m);
h_notes.push_back(n);
}
it++;
}
GtkWidget *sep1 = gtk_separator_menu_item_new();
GtkMenuItem *setup = gtk_menu_item_new_with_label(_("Setup"));
GtkMenuItem *quit = gtk_menu_item_new_with_label(_("Quit"));
gtk_menu_shell_append(tray_menu, new_yellow);
gtk_menu_shell_append(tray_menu, show_notes);
gtk_menu_shell_append(tray_menu, reload_notes);
gtk_menu_shell_append(tray_menu, sep);
std::list<GtkWidget *>::iterator w_it = hidden.begin();
while (w_it != hidden.end()) {
gtk_menu_shell_append(tray_menu, *w_it);
w_it++;
}
gtk_menu_shell_append(tray_menu, sep1);
gtk_menu_shell_append(tray_menu, setup);
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(show_notes, "activate", on_show, this);
g_signal_connect(reload_notes, "activate", on_reload, this);
g_signal_connect(setup, "activate", on_setup, this);
g_signal_connect(quit, "activate", on_quit, this);
w_it = hidden.begin();
it = h_notes.begin();
while(w_it != hidden.end()) {
g_signal_connect(*w_it, "activate", on_show_note, *it);
w_it++;
it++;
}
#ifdef __linux
gtk_menu_popup_at_pointer(tray_menu, nullptr);
#endif
#ifdef _WIN32
gtk_menu_popup(tray_menu, nullptr, nullptr, nullptr, nullptr, 0, 0);
#endif
}
void YellowNotes::clearNotes()
{
while (!_notes.empty()) {
YellowNote *note = _notes.front();
_notes.pop_front();
delete note;
}
}
void YellowNotes::loadConfig()
{
std::string notes_dir = notesDir();
std::string cfg_file = notes_dir + "/yellownotes.cfg";
std::filesystem::path p(cfg_file);
auto add = [this](const char *k, const char *v) {
_cfg.insert(std::pair<std::string, std::string>(std::string(k), std::string(v)));
};
_cfg.clear();
add("lang", "en");
if (std::filesystem::is_regular_file(p)) {
FILE *f = fopen(cfg_file.c_str(), "rt");
char buf[10240];
char *b = fgets(buf, 10240, f);
while (b != nullptr) {
std::string e = std::string(b);
trim(e);
if (e != "") {
size_t pos = e.find("=");
std::string k = e.substr(0, pos);
std::string v = e.substr(pos + 1);
trim(k);
trim(v);
_cfg.erase(k);
_cfg.insert(std::pair<std::string, std::string>(k, v));
}
b = fgets(buf, 10240, f);
}
fclose (f);
}
setLang(currentLang());
}
void YellowNotes::saveConfig()
{
std::string notes_dir = notesDir();
std::string cfg_file = notes_dir + "/yellownotes.cfg";
FILE *f = fopen(cfg_file.c_str(), "wt");
if (f) {
std::unordered_map<std::string, std::string>::iterator it;
it = _cfg.begin();
while (it != _cfg.end()) {
fprintf(f, "%s=%s\n", it->first.c_str(), it->second.c_str());
it++;
}
fclose(f);
}
}
void YellowNotes::loadNotes()
{
gtk_widget_show_all(topLevel());
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() && full_name.rfind(".note") != std::string::npos)
{
const auto base_name = entry.path().filename().string();
if (base_name.rfind(".note") > 0) {
YellowNote *note = new YellowNote(this, full_name);
_notes.push_back(note);
}
}
}
gtk_widget_hide(topLevel());
}
void YellowNotes::showNotes(void *sender)
{
std::list<YellowNote *>::const_iterator it = _notes.begin();
while (it != _notes.end()) {
YellowNote *note = *it;
note->toFront();
it++;
}
}
void YellowNotes::setupCancel(GtkWidget *sender)
{
if (_dlg) {
gtk_widget_destroy(_dlg);
_dlg = nullptr;
_langs = nullptr;
}
}
bool YellowNotes::setupDel(GtkWidget *sender, void *evt)
{
setupCancel(sender);
return true;
}
void YellowNotes::setupClose(GtkWidget *sender)
{
if (_dlg != nullptr) {
std::cout << "hey" << std::endl;
gtk_dialog_response(_dlg, GTK_RESPONSE_OK);
std::string lang = std::string(gtk_combo_box_get_active_id(_langs));
std::cout << "lang " << lang << std::endl;
setCurrentLang(lang);
gtk_widget_destroy(_dlg);
_dlg = nullptr;
_langs = nullptr;
saveConfig();
} else {
std::cout << "close button" << std::endl;
}
}
void YellowNotes::setup(void *sender)
{
loadConfig();
GtkWidget *dlg = gtk_dialog_new();
gtk_window_set_title(dlg, _("Yellownotes Setup"));
_dlg = dlg;
GtkWidget *ok_btn = gtk_dialog_add_button(dlg, _("Ok"), GTK_RESPONSE_OK);
GtkWidget *content = gtk_dialog_get_content_area(dlg);
GtkWidget *langs = gtk_combo_box_text_new();
_langs = langs;
gtk_combo_box_text_append(langs, "en", "English");
gtk_combo_box_text_append(langs, "nl", "Nederlands");
std::string current_lang = currentLang();
gtk_combo_box_set_active_id(langs, current_lang.c_str());
GtkWidget *hbox = gtk_box_new(GtkOrientation::GTK_ORIENTATION_HORIZONTAL, 5);
gtk_container_add(hbox, langs);
gtk_container_add(content, hbox);
g_signal_connect(ok_btn, "clicked", on_setup_ok, this);
g_signal_connect(dlg, "close", on_setup_close, this);
g_signal_connect(dlg, "delete-event", on_setup_del, this);
gtk_widget_show_all(dlg);
}
void YellowNotes::reloadNotes(void *sender)
{
std::list<YellowNote *>::const_iterator it = _notes.begin();
while (it != _notes.end()) {
YellowNote *note = *it;
delete note;
it++;
}
_notes.clear();
loadNotes();
showNotes(sender);
}
void YellowNotes::newNote(void *sender)
{
unsigned long long milliseconds_since_epoch =
std::chrono::system_clock::now().time_since_epoch() /
std::chrono::milliseconds(1);
char buf[200];
int r = rand() % 1000;
sprintf(buf, "%llu-%d", milliseconds_since_epoch, r);
std::string new_note_file = buf;
std::string notes_dir = notesDir();
auto filename = [notes_dir](const std::string & fname, int i) {
char s[20];
sprintf(s, "%d", i);
std::string fn = notes_dir + "/" + fname + "-" + s + ".note";
return fn;
};
int i = 1;
std::filesystem::path p(filename(new_note_file, i));
while(std::filesystem::is_regular_file(p)) {
i += 1;
p = std::filesystem::path(filename(new_note_file, i));
}
YellowNote *note = new YellowNote(this, p.string());
_notes.push_back(note);
note->show();
note->save();
}
void YellowNotes::quit(void *sender)
{
gtk_main_quit();
}
void YellowNotes::remove(YellowNote *n)
{
_notes.remove(n);
}
std::string YellowNotes::currentLang()
{
std::string key("lang");
return _cfg[key];
}
void YellowNotes::setCurrentLang(const std::string &l)
{
std::string key("lang");
_cfg.erase(key);
_cfg.insert(std::pair<std::string, std::string>(key, l));
}
static int show_notes_timed(void *user_data)
{
YellowNotes *notes = reinterpret_cast<YellowNotes *>(user_data);
notes->showNotes(nullptr);
return false;
}
void YellowNotes::topLevelHidden(GtkWidget *sender)
{
g_timeout_add(500, show_notes_timed, this);
}
GtkWindow *YellowNotes::topLevel()
{
if (_toplevel == nullptr) {
_toplevel = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_show_all(_toplevel);
g_signal_connect(_toplevel, "hide", on_hide_toplevel, this);
}
return _toplevel;
}
YellowNotes::YellowNotes(void *app)
{
_app = app;
_tray_menu = nullptr;
_font_size = 15;
_toplevel = nullptr;
loadConfig();
}
YellowNotes::~YellowNotes()
{
if (_tray_menu) { gtk_widget_destroy(reinterpret_cast<GtkMenu *>(_tray_menu)); }
clearNotes();
gtk_widget_destroy(_toplevel);
}
////////////////////////////////////////////////////////////////////////////////////////////////////
/// YellowNote implementation
////////////////////////////////////////////////////////////////////////////////////////////////////
YellowNote::YellowNote(YellowNotes *notes, const std::string &filename)
{
_notes = notes;
_filename = filename;
_title = _("New Note");
_x = 200;
_y = 300;
_width = 300;
_height = 200;
_hidden = false;
_editing_title = false;
_save_counter = 0;
_save_id = -1;
_moving = false;
_resize_bottom = false;
_resize_edge = false;
_resize_right = false;
_color = ColorType_t::YELLOW;
_color_changed = false;
_note_widget = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_set_can_focus(_note_widget, true);
gtk_window_set_decorated(_note_widget, false);
gtk_window_set_type_hint(_note_widget, GDK_WINDOW_TYPE_HINT_POPUP_MENU);
gtk_window_set_transient_for(_note_widget, _notes->topLevel());
_scroll_widget = gtk_scrolled_window_new(nullptr, nullptr);
gtk_widget_set_vexpand(_scroll_widget, true);
_buffer = gtk_text_buffer_new(nullptr);
_text_widget = gtk_text_view_new_with_buffer(_buffer);
gtk_text_view_set_wrap_mode(_text_widget, GTK_WRAP_WORD);
_evt_box = gtk_event_box_new();
_note_header = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 5);
_note_box = gtk_box_new(GTK_ORIENTATION_VERTICAL, 5);
_title_label = gtk_label_new(_title.c_str());
gtk_label_set_ellipsize(_title_label, PANGO_ELLIPSIZE_END);
gtk_widget_set_hexpand(_title_label, true);
_title_entry = gtk_entry_new();
gtk_widget_set_hexpand(_title_entry, true);
_title_separator = gtk_separator_new(GTK_ORIENTATION_HORIZONTAL);
_frame = gtk_frame_new(nullptr);
gtk_frame_set_shadow_type(_frame, GtkShadowType::GTK_SHADOW_OUT);
int height = _notes->iconSize();
int width = height;
GdkPixbuf *color_pixbuf = gdk_pixbuf_new_from_file_at_size(notes->imageFile("color").c_str(),
width, height, nullptr
);
_color_image = gtk_image_new_from_pixbuf(color_pixbuf);
g_object_unref(color_pixbuf);
GdkPixbuf *delete_pixbuf = gdk_pixbuf_new_from_file_at_size(notes->imageFile("delete").c_str(),
width, height, nullptr
);
_delete_image = gtk_image_new_from_pixbuf(delete_pixbuf);
gtk_widget_set_halign(_delete_image, GtkAlign::GTK_ALIGN_END);
g_object_unref(delete_pixbuf);
GdkPixbuf *plus_pixbuf = gdk_pixbuf_new_from_file_at_size(notes->imageFile("plus").c_str(),
width, height, nullptr
);
_plus_image = gtk_image_new_from_pixbuf(plus_pixbuf);
gtk_widget_set_halign(_plus_image, GtkAlign::GTK_ALIGN_END);
g_object_unref(plus_pixbuf);
GdkPixbuf *hide_pixbuf = gdk_pixbuf_new_from_file_at_size(notes->imageFile("hide").c_str(),
width, height, nullptr
);
_hide_image = gtk_image_new_from_pixbuf(hide_pixbuf);
gtk_widget_set_halign(_plus_image, GtkAlign::GTK_ALIGN_END);
g_object_unref(hide_pixbuf);
gtk_container_add(_note_header, _color_image);
gtk_container_add(_note_header, _title_label);
gtk_container_add(_note_header, _plus_image);
gtk_container_add(_note_header, _delete_image);
gtk_container_add(_note_header, _hide_image);
gtk_container_add(_scroll_widget, _text_widget);
gtk_container_add(_note_box, _note_header);
gtk_container_add(_note_box, _title_separator);
gtk_container_add(_note_box, _scroll_widget);
gtk_container_add(_frame, _note_box);
gtk_container_add(_evt_box, _frame);
gtk_container_add(_note_widget, _evt_box);
gtk_widget_show_all(_note_widget);
//GdkWindow *window = gdk_window_get_effective_toplevel(gtk_widget_get_window(_note_widget));
//gdk_window_set_events(window, static_cast<GdkEventMask>(gdk_window_get_events(window) | GDK_VISIBILITY_NOTIFY_MASK));
//gdk_window_set_override_redirect(window, true);
g_signal_connect(_note_widget, "size_allocate", on_size_allocated, this);
g_signal_connect(_evt_box, "button_press_event", on_button_press, this);
g_signal_connect(_evt_box, "button_release_event", on_button_release, this);
g_signal_connect(_evt_box, "motion_notify_event", on_mouse_move, this);
g_signal_connect(_title_entry, "activate", on_title_activate, this);
g_signal_connect(_title_entry, "key_press_event", on_title_escape, this);
g_signal_connect(_title_entry, "focus_out_event", on_title_focus_out, this);
g_signal_connect(_buffer, "changed", on_text_change, this);
g_signal_connect(_note_widget, "visibility_notify_event", on_window_present, this);
// Keep label and entry of title alive
g_object_ref(_title_label);
g_object_ref(_title_entry);
g_object_ref(_delete_image);
g_object_ref(_plus_image);
g_object_ref(_hide_image);
load();
_in_transaction = false;
}
YellowNote::~YellowNote()
{
g_object_unref(_title_label);
g_object_unref(_title_entry);
g_object_unref(_delete_image);
g_object_unref(_plus_image);
g_object_unref(_hide_image);
gtk_widget_destroy(_note_widget);
_note_widget = nullptr;
}
void YellowNote::updateWidgetColors(GtkWidget *w)
{
auto set_style = [this](GtkWidget *widget) {
GtkStyleContext *c = gtk_widget_get_style_context(widget);
const char *style = gtk_style_context_to_string(c, GTK_STYLE_CONTEXT_PRINT_RECURSE);
GtkCssProvider *css = gtk_css_provider_new();
gtk_style_context_add_provider(c, css, GTK_STYLE_PROVIDER_PRIORITY_USER);
std::string widget_css = _notes->css(_color);
gtk_css_provider_load_from_data(css, widget_css.c_str(), widget_css.size(), nullptr);
};
set_style(w);
}
void YellowNote::updateColor()
{
auto set_style = [this](GtkWidget *w) {
this->updateWidgetColors(w);
};
set_style(_title_label);
set_style(_note_header);
set_style(_text_widget);
set_style(_note_widget);
set_style(_frame);
}
void YellowNote::nextColor()
{
int c = _color;
c += 1;
_color = static_cast<ColorType_t>(c);
if (_color > ColorType_t::LAST) {
_color = ColorType_t::FIRST;
}
save();
updateColor();
}
void YellowNote::addNew()
{
_notes->newNote(this);
}
void YellowNote::deleteMe()
{
GtkWidget *msg = gtk_message_dialog_new(_note_widget,
GtkDialogFlags::GTK_DIALOG_MODAL,
GtkMessageType::GTK_MESSAGE_QUESTION,
GtkButtonsType::GTK_BUTTONS_YES_NO,
"%s", "Are you sure you want to delete this note?"
);
updateWidgetColors(msg);
int response = gtk_dialog_run(msg);
gtk_widget_destroy (msg);
if (response == GTK_RESPONSE_YES) {
_notes->remove(this);
std::filesystem::path p(_filename);
std::filesystem::remove(p);
delete this;
}
}
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::showNote(GtkWidget *sender)
{
show();
}
void YellowNote::show()
{
gtk_widget_show_all(_note_widget);
_hidden = false;
save();
}
void YellowNote::hide()
{
gtk_widget_hide(_note_widget);
_hidden = true;
save();
}
void YellowNote::changed(GtkWidget *sender)
{
bool sv = false;
if (_color_changed) {
_color_changed = true;
sv = true;
}
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(GtkWidget *sender, GtkAllocation *a)
{
changed(sender);
}
void YellowNote::toFront()
{
if (_hidden) {
gtk_window_move(_note_widget, _x, _y);
gtk_widget_hide(_note_widget);
} else {
gtk_window_present(_note_widget);
gtk_window_move(_note_widget, _x, _y);
}
}
void YellowNote::get_header_screen_coords(int &header_top, int &header_bottom)
{
int left, top;
gtk_window_get_position(_note_widget, &left, &top);
header_top = top;
GtkAllocation alloc;
gtk_widget_get_allocation(_note_header, &alloc);
header_bottom = alloc.y + alloc.height + top;
}
void YellowNote::get_frame_screen_coords(int &frame_bottom, int &frame_right)
{
int left, top;
gtk_window_get_position(_note_widget, &left, &top);
GtkAllocation alloc;
gtk_widget_get_allocation(_scroll_widget, &alloc);
frame_bottom = alloc.y + alloc.height + top;
frame_right = alloc.x + alloc.width + left;
}
void YellowNote::get_screen_left_right(GtkWidget *widget, int &left, int &right)
{
int wleft, wtop;
gtk_window_get_position(_note_widget, &wleft, &wtop);
GtkAllocation alloc;
gtk_widget_get_allocation(widget, &alloc);
left = wleft + alloc.x;
right = left + alloc.width;
}
void YellowNote::adjustTitle(bool mutate)
{
if (!_editing_title) { return; }
_editing_title = false;
if (mutate) {
std::string _old_title = _title;
_title = gtk_entry_get_text(_title_entry);
trim(_title);
gtk_label_set_label(_title_label, _title.c_str());
save();
}
gtk_container_remove(_note_header, _title_entry);
gtk_container_add(_note_header, _title_label);
gtk_container_remove(_note_header, _plus_image);
gtk_container_add(_note_header, _plus_image);
gtk_container_remove(_note_header, _delete_image);
gtk_container_add(_note_header, _delete_image);
gtk_container_remove(_note_header, _hide_image);
gtk_container_add(_note_header, _hide_image);
}
bool YellowNote::windowPresented(void *sender, GdkEventVisibility *evt) // TODO according to docs this must not be a pointer
{
return false;
}
std::string YellowNote::title()
{
return _title;
}
bool YellowNote::isHidden()
{
return _hidden;
}
void YellowNote::textChanged(void *sender)
{
if (_in_transaction) return;
std::cout << "Changed " << _save_id << std::endl;
_save_counter++;
if (_save_id == -1) {
std::cout << "Starting save timer" << std::endl;
_save_id = _save_counter;
g_timeout_add(1000, on_text_save_timeout, this);
}
}
bool YellowNote::textSaveTimeout()
{
if (_save_counter != _save_id) {
std::cout << "Something changed" << std::endl;
_save_id = _save_counter;
g_timeout_add(1000, on_text_save_timeout, this);
return false;
} else {
_save_id = -1;
std::cout << "Saving" << std::endl;
save();
return false;
}
}
void YellowNote::titleEnter(GtkWidget *sender)
{
adjustTitle(true);
}
bool YellowNote::titleEscape(GtkWidget *sender, GdkEventKey *key)
{
if (key->keyval == GDK_KEY_Escape && _editing_title) {
adjustTitle(false);
return true;
}
return false;
}
bool YellowNote::titleFocusOut(GtkWidget *sender, GdkEventFocus *evt)
{
if (_editing_title) {
adjustTitle(false);
return true;
}
return false;
}
#define AROUND(c, n) ((c >= (n - threshold)) && (c <= (n + threshold)))
bool YellowNote::move_begin(GtkWidget *sender, GdkEventButton *evt)
{
int x = evt->x_root;
int y = evt->y_root;
int header_top, header_bottom;
get_header_screen_coords(header_top, header_bottom);
int frame_bottom, frame_right;
get_frame_screen_coords(frame_bottom, frame_right);
int color_left, color_right;
get_screen_left_right(_color_image, color_left, color_right);
int delete_left, delete_right;
get_screen_left_right(_delete_image, delete_left, delete_right);
int plus_left, plus_right;
get_screen_left_right(_plus_image, plus_left, plus_right);
int hide_left, hide_right;
get_screen_left_right(_hide_image, hide_left, hide_right);
if (y >= header_top && y <= header_bottom) {
if (x >= color_left && x <= color_right) {
nextColor();
return true;
}
if (x >= plus_left && x <= plus_right) {
addNew();
return true;
}
if (x >= hide_left && x <= hide_right) {
hide();
return true;
}
if (x >= delete_left && x <= delete_right) {
deleteMe();
return true;
}
}
if (evt->type == GDK_2BUTTON_PRESS) {
gtk_container_remove(_note_header, _title_label);
gtk_container_add(_note_header, _title_entry);
gtk_container_remove(_note_header, _plus_image);
gtk_container_add(_note_header, _plus_image);
gtk_container_remove(_note_header, _delete_image);
gtk_container_add(_note_header, _delete_image);
gtk_container_remove(_note_header, _hide_image);
gtk_container_add(_note_header, _hide_image);
gtk_widget_show(_title_entry);
gtk_entry_set_text(_title_entry, _title.c_str());
gtk_widget_grab_focus(_title_entry);
_editing_title = true;
return true;
}
if (y >= header_top && y <= header_bottom) {
_moving = true;
_x_orig = evt->x;
_y_orig = evt->y;
return true;
}
int threshold = 8;
if (AROUND(y, frame_bottom) && AROUND(x, frame_right)) {
_resize_edge = true;
} else if (AROUND(y, frame_bottom)) {
_resize_bottom = true;
} else if (AROUND(x, frame_right)) {
_resize_right = true;
}
return false;
}
bool YellowNote::move_end(GtkWidget *sender, GdkEventButton *evt)
{
if (_moving) {
_moving = false;
return true;
}
if (_resize_edge || _resize_bottom || _resize_right) {
_resize_edge = false;
_resize_bottom = false;
_resize_right = false;
return true;
}
return false;
}
bool YellowNote::moving(GtkWidget *sender, GdkEventMotion *evt)
{
int x = evt->x_root;
int y = evt->y_root;
if (_moving) {
int the_x = x - _x_orig;
int the_y = y - _y_orig;
gtk_window_move(_note_widget, the_x, the_y);
return true;
}
if (_resize_bottom || _resize_right || _resize_edge) {
if (_resize_edge) {
int left, top;
gtk_window_get_position(_note_widget, &left, &top);
int width = x - left;
int height = y - top;
if (width < 100) { width = 100; }
if (height < 60) { height = 60; }
gtk_window_resize(_note_widget, width, height);
return true;
}
if (_resize_right) {
int left, top;
gtk_window_get_position(_note_widget, &left, &top);
int w, h;
gtk_window_get_size(_note_widget, &w, &h);
int width = x - left;
if (width < 100) { width = 100; }
gtk_window_resize(_note_widget, width, h);
return true;
}
if (_resize_bottom) {
int left, top;
gtk_window_get_position(_note_widget, &left, &top);
int w, h;
gtk_window_get_size(_note_widget, &w, &h);
int height = y - top;
if (height < 60) { height = 60; }
gtk_window_resize(_note_widget, w, height);
return true;
}
}
int frame_bottom, frame_right;
get_frame_screen_coords(frame_bottom, frame_right);
int header_top, header_bottom;
get_header_screen_coords(header_top, header_bottom);
GdkWindow *window = gtk_widget_get_window(_frame);
GdkCursor *c;
int threshold = 8;
if (y >= header_top && y <= header_bottom) {
c = gdk_cursor_new(GDK_LEFT_PTR);
} else if (AROUND(x, frame_right) && AROUND(y, frame_bottom)) {
c = gdk_cursor_new(GDK_BOTTOM_RIGHT_CORNER);
} else if (AROUND(x, frame_right)) {
c = gdk_cursor_new(GDK_RIGHT_SIDE);
} else if (AROUND(y, frame_bottom)) {
c = gdk_cursor_new(GDK_BOTTOM_SIDE);
} else {
c = nullptr;
}
gdk_window_set_cursor(window, c);
if (c) g_object_unref(c);
return false;
}
#define YELLOWNOTE_VERSION 1
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);
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) {
int version = readInt(f, -1);
_hidden = readInt(f, -1);
_x = readInt(f, 200);
_y = readInt(f, 200);
_width = readInt(f, 300);
_height = readInt(f, 200);
int color;
color = readInt(f, ColorType_t::YELLOW);
_color = static_cast<ColorType_t>(color);
char *buf = static_cast<char *>(malloc(s));
memset(buf, 0, s);
fgets(buf, s, f);
_title = buf;
trim(_title);
memset(buf, 0, s);
int pos = ftell(f);
int bytes = s - pos;
fread(buf, bytes, 1, f);
fclose(f);
gtk_text_buffer_set_text(_buffer, buf, strlen(buf));
free(buf);
}
updateTitle();
updateColor();
updatePosition();
updateSize();
updateHidden();
_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", YELLOWNOTE_VERSION);
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);
fprintf(f, "%d\n", _color);
fprintf(f, "%s\n", _title.c_str());
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);
g_free(text);
}
}