Files
yellownotes/yellownotes.cpp

2120 lines
59 KiB
C++

#include "yellownotes.h"
#include <iostream>
#include <string>
#include <filesystem>
#include <vector>
#if defined(__linux) || defined(TARGET_OS_OSX)
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>
#include <sys/stat.h>
#endif
#ifdef _WIN32
#include <windows.h>
#include <shobjidl.h>
#endif
#include <string.h>
extern "C" {
#include "gtk-imports.h"
}
#include "tr.h"
#include "info_over_me.h"
#include <functional>
#include <sstream>
#include <math.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 StoredCoords
{
public:
bool hidden;
int x;
int y;
int width;
int height;
std::string os_host;
};
typedef enum {
NONE,
BOTTOM_RIGHT,
BOTTOM,
RIGHT
} YellowNoteResize_t;
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;
GtkImage *_to_desktop_image;
GtkImage *_title_only_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;
std::list<StoredCoords> _stored_coords;
bool _hidden;
bool _hidden_loaded;
bool _title_only;
bool _title_only_loaded;
int _x;
int _y;
bool _pos_loaded;
int _width;
int _height;
bool _size_loaded;
bool _in_transaction;
bool _editing_title;
int _save_counter;
int _save_id;
bool _double_clicked;
ColorType_t _color;
bool _color_changed;
bool _moving;
bool _resize_right;
bool _resize_bottom;
bool _resize_edge;
YellowNoteResize_t _resize_type;
int _x_orig;
int _y_orig;
public:
void show();
void hide();
private:
void updateTitle();
void updatePosition();
void updateHidden();
void updateSize();
void updateTitleOnly();
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 resized(int width, int height);
void size_allocated(GtkWidget *sender, GtkAllocation *alloc);
void positioned(GtkWidget *sender, int x, int y);
public:
void toFront();
void toDesktop();
void fromDesktop();
void updateColor();
void toggleTitleOnly();
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);
void moveTo(int x, int y);
public:
std::string title();
bool isHidden();
void doubleClicked();
YellowNotes *notes();
public:
void load();
void save();
void setLoaded();
public:
YellowNote(YellowNotes *notes, const std::string &filename);
~YellowNote();
};
class SettingContainer
{
public:
ColorType_t color;
GtkLabel *font_label;
GtkSpinButton *font_size_btn;
YellowNotes *notes;
YellowNote *note;
bool bg;
public:
void colorSet(GtkColorButton *sender) {
GdkRGBA rgba;
gtk_color_chooser_get_rgba(sender, &rgba);
std::string c = notes->fromRGBA(rgba);
if (bg) {
notes->setBgColor(color, c);
} else {
notes->setFgColor(color, c);
}
}
void fontSizeSet(GtkWidget *Sender) {
int size = gtk_spin_button_get_value_as_int(font_size_btn);
notes->updateWidgetCss(font_label, ColorType_t::YELLOW);
notes->setFontSize(size);
}
SettingContainer(ColorType_t t, bool _bg, YellowNotes *n) {
color = t;
bg = _bg;
notes = n;
font_label = nullptr;
font_size_btn = nullptr;
}
SettingContainer(GtkSpinButton *btn, GtkLabel *fl, YellowNotes *n) {
color = FIRST;
font_label = fl;
font_size_btn = btn;
notes = n;
bg = false;
}
};
SIGNAL(SettingContainer, on_color_set, colorSet);
SIGNAL(SettingContainer, on_font_size_set, fontSizeSet);
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, notesFromDesktop);
SIGNAL(YellowNotes, on_to_back, notesToDesktop);
SIGNAL(YellowNotes, on_reload, reloadNotes);
SIGNAL(YellowNotes, on_setup, setup);
SIGNAL(YellowNotes, on_quit, quit)
SIGNAL(YellowNotes, on_reorder, reOrder);
SIGNAL(YellowNotes, on_about, about);
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);
SIGNAL(YellowNotes, on_monitors_changed, monitorsChanged);
std::string YellowNotes::imageFile(const char *name)
{
#if defined(_WIN32) || defined(TARGET_OS_OSX)
std::string ext = ".png";
#else
std::string ext = ".svg";
#endif
std::string file = name + ext;
std::string path1 = appDir() + "/images/" + file;
if (std::filesystem::is_regular_file(path1)) {
return path1;
} else {
std::string path2 = appDir() + "/../../" + file;
if (std::filesystem::is_regular_file(path2)) {
return path2;
} else {
std::cerr << "No such image file: " << path2 << std::endl;
throw std::string("No such image file: " + file);
}
}
}
std::string YellowNotes::appDir()
{
InfoOverMe info;
std::string base = info.containingFolder();
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
#ifdef TARGET_OS_OSX
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;
}
std::string YellowNotes::fromRGBA(const GdkRGBA &rgba)
{
char buf[100];
auto to255 = [](double c) {
return static_cast<unsigned int>(floor((c * 255) + 0.5));
};
sprintf(buf, "#%02x%02x%02x", to255(rgba.red), to255(rgba.green), to255(rgba.blue));
std::string col = buf;
return col;
}
void YellowNotes::toRGBA(const std::string &col, GdkRGBA &rgba)
{
auto from_hex = [](std::string hex_num) {
return strtol(hex_num.c_str(), NULL, 16);
};
double red = from_hex(col.substr(1, 2)) / 255.0;
double green = from_hex(col.substr(3, 2)) / 255.0;
double blue = from_hex(col.substr(5, 2)) / 255.0;
double alpha = 1.0;
rgba.alpha = alpha;
rgba.blue = blue;
rgba.green = green;
rgba.red = red;
}
void YellowNotes::updateWidgetCss(GtkWidget *w, ColorType_t col)
{
auto set_style = [this, col](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_p = gtk_css_provider_new();
gtk_style_context_add_provider(c, css_p, GTK_STYLE_PROVIDER_PRIORITY_USER);
std::string widget_css = css(col);
gtk_css_provider_load_from_data(css_p, widget_css.c_str(), widget_css.size(), nullptr);
};
set_style(w);
}
GtkWidget *YellowNotes::getWindow()
{
return topLevel();
}
std::string YellowNotes::getFgColor(ColorType_t type)
{
char buf[100];
sprintf(buf, "fg_color_%d", type);
std::string key = buf;
std::unordered_map<std::string, std::string>::iterator it = _cfg.find(key);
if (it != _cfg.end()) {
return _cfg[key];
} else {
const char *fgs[] = { "#ffffff",
"#000000",
"#000000",
"#000000",
"#000000",
"#000000",
"#000000",
"#000000"
};
std::string c(fgs[type]);
return c;
}
}
std::string YellowNotes::getBgColor(ColorType_t type)
{
char buf[100];
sprintf(buf, "bg_color_%d", type);
std::string key = buf;
std::unordered_map<std::string, std::string>::iterator it = _cfg.find(key);
if (it != _cfg.end()) {
return _cfg[key];
} else {
const char *bgs[] = {
"#404040", // dark
"#faf32a", // yellow
"#fcbf56", // orange,
"#5df0f5", // blue
"#fc77f4", // Cyaan
"#74fc94", // greeen
"#f7bcbc", // red
"#cdcfd1" // grey
};
std::string c(bgs[type]);
return c;
}
}
void YellowNotes::setFgColor(ColorType_t type, const std::string &col)
{
char buf[100];
sprintf(buf, "fg_color_%d", type);
std::string key = buf;
_cfg.erase(key);
_cfg.insert(std::pair<std::string, std::string>(key, col));
saveConfig();
}
void YellowNotes::setBgColor(ColorType_t type, const std::string &col)
{
char buf[100];
sprintf(buf, "bg_color_%d", type);
std::string key = buf;
_cfg.erase(key);
_cfg.insert(std::pair<std::string, std::string>(key, col));
saveConfig();
}
std::string YellowNotes::css(ColorType_t type)
{
if (type > LAST) { type = YELLOW; }
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: " + getBgColor(type) + ";\n"
" color: " + getFgColor(type) + ";\n"
" font-family: sans;\n"
" font-size: " + font_size + ";\n"
"}\n"
"frame border {\n"
" border: none;\n"
" box-shadow: 5px 5px 5px " + darker(getBgColor(type)) + ";\n"
" margin: 5px;\n"
"}\n";
return css;
}
int YellowNotes::fontSize()
{
std::string key("font_size");
std::unordered_map<std::string, std::string>::iterator it;
it = _cfg.find(key);
if (it != _cfg.end()) {
_font_size = atoi(it->second.c_str());
if (_font_size < 6 || _font_size > 30) { _font_size = 15; }
}
return _font_size;
}
void YellowNotes::setFontSize(int size)
{
_font_size = size;
char buf[100];
sprintf(buf, "%d", size);
std::string v = buf;
std::string key("font_size");
_cfg.erase(key);
_cfg.insert(std::pair<std::string, std::string>(key, v));
saveConfig();
}
int YellowNotes::iconSize()
{
return _font_size * 1.5;
}
bool YellowNotes::popupTrayMenuBtn(void *sender, GdkEventButton *evt)
{
popupTrayMenu(sender);
return true;
}
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 *notes_to_back = gtk_menu_item_new_with_label(_("Notes on desktop"));
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 *reorder = gtk_menu_item_new_with_label(_("Reorder Notes"));
GtkMenuItem *about = gtk_menu_item_new_with_label(_("About"));
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, notes_to_back);
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, reorder);
gtk_menu_shell_append(tray_menu, about);
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(notes_to_back, "activate", on_to_back, this);
g_signal_connect(reload_notes, "activate", on_reload, this);
g_signal_connect(setup, "activate", on_setup, this);
g_signal_connect(reorder, "activate", on_reorder, this);
g_signal_connect(about, "activate", on_about, 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);
}
}
gboolean load_notes_timeout(void *_notes)
{
YellowNotes *notes = static_cast<YellowNotes *>(_notes);
notes->notesToDesktop(nullptr);
return false;
}
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);
//note->fromDesktop();
_notes.push_back(note);
}
}
}
gtk_widget_hide(topLevel());
if (cfgOnDesktop()) {
g_timeout_add(500, load_notes_timeout, this);
}
}
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::notesToDesktop(void *sender)
{
std::list<YellowNote *>::const_iterator it = _notes.begin();
while (it != _notes.end()) {
YellowNote *note = *it;
note->toDesktop();
it++;
}
setCfgOnDesktop(true);
}
void YellowNotes::notesFromDesktop(void *sender)
{ std::list<YellowNote *>::const_iterator it = _notes.begin();
while (it != _notes.end()) {
YellowNote *note = *it;
note->fromDesktop();
it++;
}
setCfgOnDesktop(false);
}
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) {
gtk_dialog_response(_dlg, GTK_RESPONSE_OK);
std::string lang = std::string(gtk_combo_box_get_active_id(_langs));
setCurrentLang(lang);
gtk_widget_destroy(_dlg);
std::list<SettingContainer *>::iterator it;
for(it = _settings_containers.begin() ; it != _settings_containers.end(); it++) {
SettingContainer *s = *it;
delete s;
}
_settings_containers.clear();
_dlg = nullptr;
_langs = nullptr;
saveConfig();
std::list<YellowNote *>::iterator n_it;
for(n_it = _notes.begin(); n_it != _notes.end(); n_it++) {
YellowNote *n = *n_it;
n->updateColor();
}
} else {
}
}
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");
GtkWidget *lbl_langs = gtk_label_new(_("Language:"));
gtk_widget_set_size_request(lbl_langs, 150, -1);
std::string current_lang = currentLang();
gtk_combo_box_set_active_id(langs, current_lang.c_str());
gtk_widget_set_size_request(langs, 300, -1);
GtkGrid *grid = gtk_grid_new();
gtk_grid_attach(grid, lbl_langs, 0, 0, 1, 1);
gtk_grid_attach(grid, langs, 1, 0, 2, 1);
GtkLabel *lbl_fg = gtk_label_new(_("Foreground Color"));
GtkLabel *lbl_bg = gtk_label_new(_("Background Color"));
gtk_grid_attach(grid, lbl_fg, 1, 1, 1, 1);
gtk_grid_attach(grid, lbl_bg, 2, 1, 1, 1);
int i = ColorType_t::FIRST;
std::string colors[] = { _("Dark"), _("Yellow"), _("Orange"),
_("Blue"), _("Cyan"), _("Green"), _("Red"), _("Grey") };
int offset = 2;
for(;i <= ColorType_t::LAST; i++) {
GtkWidget *lbl = gtk_label_new(colors[i].c_str());
gtk_grid_attach(grid, lbl, 0, i + offset, 1, 1);
GdkRGBA bg_rgba, fg_rgba;
std::string fg_color = getFgColor(static_cast<ColorType_t>(i));
std::string bg_color = getBgColor(static_cast<ColorType_t>(i));
toRGBA(fg_color, fg_rgba);
toRGBA(bg_color, bg_rgba);
GtkColorButton *fg_btn = gtk_color_button_new();
gtk_color_chooser_set_rgba(fg_btn, &fg_rgba);
SettingContainer *fg = new SettingContainer(static_cast<ColorType_t>(i), false, this);
_settings_containers.push_back(fg);
g_signal_connect(fg_btn, "color_set", on_color_set, fg);
gtk_grid_attach(grid, fg_btn, 1, i + offset, 1, 1);
GtkColorButton *bg_btn = gtk_color_button_new();
gtk_color_chooser_set_rgba(bg_btn, &bg_rgba);
SettingContainer *bg = new SettingContainer(static_cast<ColorType_t>(i), true, this);
_settings_containers.push_back(bg);
g_signal_connect(bg_btn, "color_set", on_color_set, bg);
gtk_grid_attach(grid, bg_btn, 2, i + offset, 1, 1);
}
offset = i + offset;
gtk_grid_attach(grid, gtk_label_new(_("Font size:")), 0, offset, 1, 1);
GtkSpinButton *fs = gtk_spin_button_new_with_range(6.0, 20.0, 1.0);
gtk_spin_button_set_value(fs, fontSize());
gtk_grid_attach(grid, fs, 1, offset, 1, 1);
GtkLabel *font_label = gtk_label_new(_("Actual Font Size in Note"));
gtk_widget_set_size_request(font_label, 400, -1);
gtk_grid_attach(grid, font_label, 2, offset, 1, 1);
SettingContainer *fl = new SettingContainer(fs, font_label, this);
_settings_containers.push_back(fl);
g_signal_connect(fs, "value_changed", on_font_size_set, fl);
fl->fontSizeSet(fs);
gtk_container_add(content, grid);
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::about(void *sender)
{
const char *authors[] = { "Hans Dijkema", nullptr };
GtkWidget *dlg = gtk_about_dialog_new();
gtk_about_dialog_set_authors(dlg, authors);
gtk_about_dialog_set_copyright(dlg, _("(c) 2025"));
gtk_about_dialog_set_license_type(dlg, GTK_LICENSE_GPL_2_0);
char buf[200];
sprintf(buf, _("Version %s, file format version: %d"),
YELLOWNOTE_VERSION,
YELLOWNOTE_FILE_VERSION
);
gtk_about_dialog_set_version(dlg, buf);
int width = 100;
int height = 100;
GdkPixbuf *logo_pixbuf = gdk_pixbuf_new_from_file_at_size(imageFile("yellownotes").c_str(),
width, height, nullptr
);
gtk_about_dialog_set_logo(dlg, logo_pixbuf);
g_object_unref(logo_pixbuf);
gtk_about_dialog_set_program_name(dlg, _("Yellow Notes"));
gtk_dialog_run(dlg);
gtk_widget_destroy(dlg);
}
void YellowNotes::reOrder(void *sender)
{
int x = 50;
int y = 50;
std::list<YellowNote *>::iterator it;
for(it = _notes.begin(); it != _notes.end(); it++, x += 25, y += 25) {
(*it)->moveTo(x, y);
}
}
void YellowNotes::monitorsChanged(void *sender)
{
this->reloadNotes(sender);
}
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, "%d-%llu", r, milliseconds_since_epoch);
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->setLoaded();
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];
}
bool YellowNotes::cfgOnDesktop()
{
std::string key("on_desktop");
return (_cfg[key] == "true") ? true : false;
}
void YellowNotes::setCfgOnDesktop(bool y)
{
std::string key("on_desktop");
_cfg.erase(key);
std::string v = (y) ? "true" : "false";
_cfg.insert(std::pair<std::string, std::string>(key, v));
saveConfig();
}
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);
}
void YellowNotes::checkMonitors()
{
auto compare = [](const Geom_t &a, const Geom_t &b) {
if (a.x < b.x) {
return true;
} else if (a.x > b. x) {
return false;
} else if (a.y < b.y) {
return true;
} else {
return false;
}
};
GdkDisplay *d = gdk_display_get_default();
int n = gdk_display_get_n_monitors(d);
if (_monitors.empty()) {
int i;
for(i = 0; i < n; i++) {
GdkMonitor *m = gdk_display_get_monitor(d, i);
GdkRectangle r;
gdk_monitor_get_geometry(m, &r);
Geom_t g;
g.x = r.x;
g.y = r.y;
g.width = r.width;
g.height = r.height;
_monitors.push_back(g);
}
_monitors.sort(compare);
} else {
if (n != _monitors.size()) {
_monitors.clear();
checkMonitors();
monitorsChanged(nullptr);
} else {
std::list<Geom_t> l;
int i;
for(i = 0; i < n; i++) {
GdkMonitor *m = gdk_display_get_monitor(d, i);
GdkRectangle r;
gdk_monitor_get_geometry(m, &r);
Geom_t g;
g.x = r.x;
g.y = r.y;
g.width = r.width;
g.height = r.height;
l.push_back(g);
}
l.sort(compare);
std::list<Geom_t>::iterator m_it = _monitors.begin();
std::list<Geom_t>::iterator l_it = l.begin();
while(l_it != l.end()) {
Geom_t g_l = *l_it;
Geom_t g_m = *m_it;
if (g_l.x != g_m.x || g_l.y != g_m.y || g_l.width != g_m.width || g_l.height != g_m.height) {
_monitors.clear();
checkMonitors();
monitorsChanged(nullptr);
}
l_it++;
m_it++;
}
}
}
}
static YellowNotes *_the_notes = nullptr;
gboolean monitor_monitors(void *data)
{
YellowNotes *n = reinterpret_cast<YellowNotes *>(data);
if (_the_notes == nullptr) {
return false;
} else {
n->checkMonitors();
return true;
}
}
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;
_the_notes = this;
g_timeout_add(1000, monitor_monitors, this); // Beware monitor_monitors checks _the_notes.
loadConfig();
}
YellowNotes::~YellowNotes()
{
_the_notes = nullptr;
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;
_title_only = false;
_editing_title = false;
_save_counter = 0;
_save_id = -1;
_moving = false;
_resize_bottom = false;
_resize_edge = false;
_resize_right = false;
_resize_type = NONE;
_color = ColorType_t::YELLOW;
_color_changed = false;
_in_transaction = true;
_hidden_loaded = false;
_pos_loaded = false;
_size_loaded = false;
_double_clicked = false;
_note_widget = gtk_window_new(GTK_WINDOW_TOPLEVEL);
gtk_widget_set_can_focus(_note_widget, true);
gtk_window_set_decorated(_note_widget, false);
#ifdef _WIN32
gtk_window_set_type_hint(_note_widget, GDK_WINDOW_TYPE_HINT_POPUP_MENU);
gtk_window_set_transient_for(_note_widget, _notes->topLevel());
#endif
#ifdef __linux
gtk_window_set_type_hint(_note_widget, GdkWindowTypeHint::GDK_WINDOW_TYPE_HINT_UTILITY);
#endif
_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(_hide_image, GtkAlign::GTK_ALIGN_END);
g_object_unref(hide_pixbuf);
GdkPixbuf *to_desktop_pixbuf = gdk_pixbuf_new_from_file_at_size(notes->imageFile("to_desktop").c_str(),
width, height, nullptr
);
_to_desktop_image = gtk_image_new_from_pixbuf(to_desktop_pixbuf);
gtk_widget_set_halign(_to_desktop_image, GtkAlign::GTK_ALIGN_END);
g_object_unref(to_desktop_pixbuf);
GdkPixbuf *title_only_pixbuf = gdk_pixbuf_new_from_file_at_size(notes->imageFile("title_only").c_str(),
width, height, nullptr
);
_title_only_image = gtk_image_new_from_pixbuf(title_only_pixbuf);
gtk_widget_set_halign(_title_only_image, GtkAlign::GTK_ALIGN_END);
g_object_unref(title_only_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(_note_header, _to_desktop_image);
gtk_container_add(_note_header, _title_only_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_container_set_border_width(_evt_box, 0);
gtk_widget_show_all(_note_widget);
/*while (!gtk_widget_is_visible(_note_widget)) {
GdkEvent *e = gtk_get_current_event();
if (e != nullptr) {
gtk_main_do_event(e);
gdk_event_free(e);
}
}*/
//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);
g_object_ref(_to_desktop_image);
g_object_ref(_title_only_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);
g_object_unref(_to_desktop_image);
g_object_unref(_title_only_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);
_notes->updateWidgetCss(w, _color);
}
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());
gtk_window_set_title(_note_widget, _title.c_str());
}
void YellowNote::updateTitleOnly()
{
if (_title_only) {
gtk_widget_hide(_scroll_widget);
GtkAllocation alloc;
gtk_widget_get_allocation(_note_header, &alloc);
gtk_window_resize(_note_widget, _width, alloc.height);
} else {
gtk_widget_show_all(_scroll_widget);
gtk_window_resize(_note_widget, _width, _height);
}
}
void YellowNote::updateHidden()
{
if (_hidden) { hide(); }
else { show(); }
}
void YellowNote::updatePosition()
{
gtk_window_move(_note_widget, _x, _y);
}
void YellowNote::updateSize()
{
updateTitleOnly();
//gtk_window_resize(_note_widget, _width, _height);
}
void YellowNote::showNote(GtkWidget *sender)
{
show();
}
void YellowNote::moveTo(int x, int y)
{
_x = x;
_y = y;
updatePosition();
}
void YellowNote::setLoaded()
{
_pos_loaded = true;
_hidden_loaded = true;
_title_only_loaded = true;
_size_loaded = true;
_in_transaction = false;
}
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::toggleTitleOnly()
{
_title_only = !_title_only;
updateTitleOnly();
save();
}
void YellowNote::resized(int width, int height)
{
_width = width;
_height = height;
save();
}
void YellowNote::size_allocated(GtkWidget *sender, GtkAllocation *alloc)
{
int width = alloc->width;
int height = alloc->height;
if (!_moving) {
if (width != _width || height != _height) {
updateSize();
//gtk_window_resize(_note_widget, _width, _height);
}
}
}
void YellowNote::positioned(GtkWidget *sender, int x, int y)
{
if (_pos_loaded) {
_x = x;
_y = y;
save();
}
}
void YellowNote::toFront()
{
if (_hidden) {
gtk_window_move(_note_widget, _x, _y);
gtk_widget_hide(_note_widget);
} else {
int x = _x;
int y = _y;
gtk_window_present(_note_widget);
gtk_window_move(_note_widget, x, y);
}
}
void YellowNote::toDesktop()
{
#ifdef __linux
gtk_window_set_type_hint(_note_widget, GdkWindowTypeHint::GDK_WINDOW_TYPE_HINT_DESKTOP);
#endif
#ifdef _WIN32
GdkWindow *notes_win = gtk_widget_get_window(_notes->getWindow());
HWND nw = gdk_win32_window_get_handle(notes_win);
SetWindowPos(nw, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
GdkWindow *win = gtk_widget_get_window(_note_widget);
HWND w = gdk_win32_window_get_handle(win);
SetWindowPos(w, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
#endif
}
void YellowNote::fromDesktop()
{
#ifdef __linux
gtk_window_set_type_hint(_note_widget, GdkWindowTypeHint::GDK_WINDOW_TYPE_HINT_UTILITY);
#endif
toFront();
}
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);
gtk_container_remove(_note_header, _to_desktop_image);
gtk_container_add(_note_header, _to_desktop_image);
gtk_container_remove(_note_header, _title_only_image);
gtk_container_add(_note_header, _title_only_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;
_save_counter++;
if (_save_id == -1) {
_save_id = _save_counter;
g_timeout_add(1000, on_text_save_timeout, this);
}
}
bool YellowNote::textSaveTimeout()
{
if (_save_counter != _save_id) {
_save_id = _save_counter;
g_timeout_add(1000, on_text_save_timeout, this);
return false;
} else {
_save_id = -1;
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)))
gboolean is_dblclk(gpointer d)
{
YellowNote *n = static_cast<YellowNote *>(d);
n->doubleClicked();
return false;
}
void YellowNote::doubleClicked()
{
if (!_double_clicked) {
_notes->notesFromDesktop(nullptr);
}
}
YellowNotes *YellowNote::notes()
{
return _notes;
}
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);
int to_desktop_left, to_desktop_right;
get_screen_left_right(_to_desktop_image, to_desktop_left, to_desktop_right);
int title_only_left, title_only_right;
get_screen_left_right(_title_only_image, title_only_left, title_only_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 >= to_desktop_left && x <= to_desktop_right) {
_notes->notesToDesktop(this);
return true;
}
if (x >= title_only_left && x <= title_only_right) {
toggleTitleOnly();
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_container_remove(_note_header, _to_desktop_image);
gtk_container_add(_note_header, _to_desktop_image);
gtk_container_remove(_note_header, _title_only_image);
gtk_container_add(_note_header, _title_only_image);
gtk_widget_show(_title_entry);
gtk_entry_set_text(_title_entry, _title.c_str());
gtk_widget_grab_focus(_title_entry);
_editing_title = true;
_double_clicked = true;
return true;
}
_double_clicked = false;
if (y >= header_top && y <= header_bottom) {
_moving = true;
_x_orig = evt->x;
_y_orig = evt->y;
g_timeout_add(250, is_dblclk, this);
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;
}
*/
if (_resize_type == BOTTOM_RIGHT) {
_resize_edge = true;
} else if (_resize_type == BOTTOM) {
_resize_bottom = true;
} else if (_resize_type == 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);
positioned(_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);
resized(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);
resized(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);
resized(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 = 16;
if (y >= header_top && y <= header_bottom) {
c = gdk_cursor_new(GDK_LEFT_PTR);
_resize_type = NONE;
} else if (AROUND(x, frame_right) && AROUND(y, frame_bottom)) {
c = gdk_cursor_new(GDK_BOTTOM_RIGHT_CORNER);
_resize_type = BOTTOM_RIGHT;
} else if (AROUND(x, frame_right)) {
c = gdk_cursor_new(GDK_RIGHT_SIDE);
_resize_type = RIGHT;
} else if (AROUND(y, frame_bottom)) {
c = gdk_cursor_new(GDK_BOTTOM_SIDE);
_resize_type = BOTTOM;
} else {
c = nullptr;
_resize_type = NONE;
}
gdk_window_set_cursor(window, c);
if (c) g_object_unref(c);
return false;
}
void YellowNote::load()
{
auto readInt = [](FILE *f, int default_value) {
char buffer[100];
fgets(buffer, 100, f);
std::string s = buffer;
trim(s);
std::string::iterator sit = s.begin();
while(sit != s.end() && (*sit >= '0' && *sit <= '9')) { sit++; }
if (sit != s.end()) {
return default_value;
} else {
int v = atoi(buffer);
return v;
}
};
_in_transaction = true;
std::filesystem::path p(_filename);
int hidden, x, y, width, height;
ColorType_t c;
std::string title;
bool title_only;
InfoOverMe info;
std::string my_name = info.myId();
auto readKind = [my_name, this](std::string line, std::function<void (const std::string &os_host, int val)> f) {
// Split line in parts.
std::istringstream inp(line);
std::string part;
bool found = false;
while(std::getline(inp, part, ',')) {
std::string os_host;
int val;
int idx;
idx = part.find(":");
if (idx >= 0) {
os_host = part.substr(0, idx);
std::string s = part.substr(idx + 1);
val = atoi(s.c_str());
f(os_host,val);
if (os_host == my_name) { found = true; }
}
}
return found;
};
auto getStoredCoord = [this](const std::string &os_host, bool &created) {
std::list<StoredCoords>::iterator it = _stored_coords.begin();
while(it != _stored_coords.end() && it->os_host != os_host) {
it++;
}
if (it == _stored_coords.end()) {
StoredCoords c;
c.os_host = os_host;
_stored_coords.push_back(c);
StoredCoords *r = &(_stored_coords.back());
created = true;
return r;
} else {
StoredCoords *r = &(*it);
created = false;
return r;
}
};
auto setStoredCoordVar = [this, getStoredCoord, readKind](std::string line, std::function<void(StoredCoords *c, int v)> f) {
auto setter = [f, this, getStoredCoord](const std::string &os_name, int val) {
bool created;
StoredCoords *r = getStoredCoord(os_name, created);
f(r, val);
};
readKind(line, setter);
};
auto readLine = [](FILE *f) {
char buf[10240];
fgets(buf, 10240, f);
std::string l(buf);
trim(l);
return l;
};
_stored_coords.clear();
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);
if (version <= 2) {
hidden = readInt(f, -1);
x = readInt(f, 200);
y = readInt(f, 200);
width = readInt(f, 300);
height = readInt(f, 200);
} else {
// fill in the dots.
setStoredCoordVar(readLine(f), [](StoredCoords *c, int val) { c->hidden = val; });
setStoredCoordVar(readLine(f), [](StoredCoords *c, int val) { c->x = val; });
setStoredCoordVar(readLine(f), [](StoredCoords *c, int val) { c->y = val; });
setStoredCoordVar(readLine(f), [](StoredCoords *c, int val) { c->width = val; });
setStoredCoordVar(readLine(f), [](StoredCoords *c, int val) { c->height = val; });
bool created;
StoredCoords *r = getStoredCoord(my_name, created);
if (created) {
r->hidden = false;
r->x = 100;
r->y = 100;
r->width = 300;
r->height = 200;
}
hidden = r->hidden;
x = r->x;
y = r->y;
width = r->width;
height = r->height;
}
int color;
color = readInt(f, ColorType_t::YELLOW);
c = static_cast<ColorType_t>(color);
if (version >= 2) {
int t = readInt(f, 0);
title_only = (t) ? true : false;
}
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);
_title = title;
updateTitle();
_color = c;
updateColor();
_x = x; _y = y;
_pos_loaded = true;
updatePosition();
_hidden = hidden;
_hidden_loaded = true;
updateHidden();
_title_only = title_only;
_title_only_loaded = true;
updateTitleOnly();
_width = width; _height = height;
_size_loaded = true;
updateSize();
}
}
_in_transaction = false;
}
void YellowNote::save()
{
if (_in_transaction) {
return;
}
InfoOverMe info;
std::string my_name = info.myId();
auto storeKind = [my_name, this](int val, std::function<int (const StoredCoords &)> f) {
std::string line("");
std::list<StoredCoords>::iterator it;
std::string comma("");
bool stored = false;
for(it = _stored_coords.begin(); it != _stored_coords.end(); it++) {
const StoredCoords &c = *it;
int num = (my_name == c.os_host) ? val : f(c);
if (my_name == c.os_host) { stored = true; }
char buf[100];
sprintf(buf, "%d", num);
line += comma + c.os_host + ":" + buf;
comma = ",";
}
if (!stored) {
char buf[100];
sprintf(buf, "%d", val);
line += comma + my_name + ":" + buf;
}
return line;
};
std::filesystem::path p(_filename);
FILE *f = fopen(_filename.c_str(), "wt");
if (f) {
fprintf(f, "%d\n", YELLOWNOTE_FILE_VERSION);
fprintf(f, "%s\n", storeKind(_hidden, [](const StoredCoords &c) { return c.hidden; }).c_str());
fprintf(f, "%s\n", storeKind(_x, [](const StoredCoords &c) { return c.x; }).c_str());
fprintf(f, "%s\n", storeKind(_y, [](const StoredCoords &c) { return c.y; }).c_str());
fprintf(f, "%s\n", storeKind(_width, [](const StoredCoords &c) { return c.width; }).c_str());
fprintf(f, "%s\n", storeKind(_height, [](const StoredCoords &c) { return c.height; }).c_str());
fprintf(f, "%d\n", _color);
fprintf(f, "%d\n", _title_only);
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);
}
}