#include "yellownotes.h" #include #include #include #include #ifdef __linux #include #include #include #include #endif #ifdef _WIN32 #include #endif #include extern "C" { #include "gtk-imports.h" } #include "tr.h" #include 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; GtkImage *_to_desktop_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; bool _hidden_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; 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 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(); 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(); void doubleClicked(); public: void load(); void save(); public: YellowNote(YellowNotes *notes, const std::string &filename); ~YellowNote(); }; class ColorSet { public: ColorType_t color; YellowNotes *notes; 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); } } ColorSet(ColorType_t t, bool _bg, YellowNotes *n) { color = t; bg = _bg; notes = n; } }; SIGNAL(ColorSet, on_color_set, colorSet); 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(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_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::fromRGBA(const GdkRGBA &rgba) { char buf[100]; auto to255 = [](double c) { return static_cast(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; } std::string YellowNotes::getFgColor(ColorType_t type) { char buf[100]; sprintf(buf, "fg_color_%d", type); std::string key = buf; std::unordered_map::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::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(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(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() { return _font_size; } int YellowNotes::iconSize() { return _font_size * 1.5; } void YellowNotes::popupTrayMenu(void *sender) { 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 *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 hidden; std::list h_notes; std::list::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, notes_to_back); gtk_menu_shell_append(tray_menu, reload_notes); gtk_menu_shell_append(tray_menu, sep); std::list::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(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(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(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(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::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(_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 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::const_iterator it = _notes.begin(); while (it != _notes.end()) { YellowNote *note = *it; note->toFront(); it++; } } void YellowNotes::notesToDesktop(void *sender) { std::list::const_iterator it = _notes.begin(); while (it != _notes.end()) { YellowNote *note = *it; note->toDesktop(); it++; } setCfgOnDesktop(true); } void YellowNotes::notesFromDesktop(void *sender) { std::list::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::iterator it; for(it = _color_sets.begin() ; it != _color_sets.end(); it++) { ColorSet *s = *it; delete s; } _color_sets.clear(); _dlg = nullptr; _langs = nullptr; saveConfig(); } 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(_("Forground 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(i)); std::string bg_color = getBgColor(static_cast(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); ColorSet *fg = new ColorSet(static_cast(i), false, this); 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); ColorSet *bg = new ColorSet(static_cast(i), true, this); g_signal_connect(bg_btn, "color_set", on_color_set, bg); gtk_grid_attach(grid, bg_btn, 2, i + offset, 1, 1); } 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::reloadNotes(void *sender) { std::list::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]; } 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(key, v)); saveConfig(); } void YellowNotes::setCurrentLang(const std::string &l) { std::string key("lang"); _cfg.erase(key); _cfg.insert(std::pair(key, l)); } static int show_notes_timed(void *user_data) { YellowNotes *notes = reinterpret_cast(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(_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; _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); 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(_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); /*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(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); 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); 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(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() { std::cout << "Update size to: " << _width << ", " << _height << std::endl; 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::resized(int width, int height) { _width = width; _height = height; save(); } void YellowNote::size_allocated(GtkWidget *sender, GtkAllocation *alloc) { std::cout << "loaded: " << _size_loaded << ", " << _pos_loaded << std::endl; int width = alloc->width; int height = alloc->height; //if (_size_loaded) { std::cout << "width, height = " << width << ", " << height << std::endl; // _width = width; // _height = height; // save(); // } if (!_moving) { if (width != _width || height != _height) { 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 { std::cout << "to_front x = " << _x << std::endl; int x = _x; int y = _y; gtk_window_present(_note_widget); gtk_window_move(_note_widget, x, y); } } void YellowNote::toDesktop() { #ifdef __linux std::cout << "todesktop: width: " << _width << ", height: " << _height << std::endl; gtk_window_set_type_hint(_note_widget, GdkWindowTypeHint::GDK_WINDOW_TYPE_HINT_DESKTOP); #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); } 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(d); n->doubleClicked(); return false; } void YellowNote::doubleClicked() { if (!_double_clicked) { _notes->notesFromDesktop(nullptr); } } 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); 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 >= 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_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; } 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; std::cout << "moving" << std::endl; 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); //size_allocated(_note_box, 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); //size_allocated(_note_box, 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); //size_allocated(_note_box, 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); int hidden, x, y, width, height; ColorType_t c; std::string title; 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); c = static_cast(color); char *buf = static_cast(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(); _width = width; _height = height; _size_loaded = true; updateSize(); _hidden = hidden; _hidden_loaded = true; updateHidden(); } } _in_transaction = false; } void YellowNote::save() { if (_in_transaction) { return; } std::cout << "Saving " << _title << "..." << _size_loaded << ", " << _width << ", h = " << _height << std::endl; 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); } }