midori/extensions/notes.vala

460 lines
17 KiB
Vala
Raw Permalink Normal View History

2015-09-12 00:47:06 +00:00
/*
Copyright (C) 2013 Paweł Forysiuk <tuxator@o2.pl>
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
See the file COPYING for the full license text.
*/
namespace ClipNotes {
Midori.Database database;
unowned Sqlite.Database db;
Gtk.ListStore notes_list_store;
Note? current_note;
class Note : GLib.Object {
public int64 id { get; set; }
public string title { get; set; }
public string? uri { get; set; default = null; }
public string content { get; set; default = ""; }
public void add (string title, string? uri, string note_content)
{
GLib.DateTime time = new DateTime.now_local ();
string sqlcmd = "INSERT INTO `notes` (`uri`, `title`, `note_content`, `tstamp` ) VALUES (:uri, :title, :note_content, :tstamp);";
Midori.DatabaseStatement statement;
try {
statement = database.prepare (sqlcmd,
":uri", typeof (string), uri,
":title", typeof (string), title,
":note_content", typeof (string), note_content,
":tstamp", typeof (int64), time.to_unix ());
statement.step ();
append_note (this);
} catch (Error error) {
critical (_("Failed to add new note to database: %s\n"), error.message);
}
this.id = db.last_insert_rowid ();
this.uri = uri;
this.title = title;
this.content = note_content;
}
public void remove ()
{
string sqlcmd = "DELETE FROM `notes` WHERE id= :id;";
Midori.DatabaseStatement statement;
try {
statement = database.prepare (sqlcmd,
":id", typeof (int64), this.id);
statement.step ();
remove_note (this.id);
} catch (Error error) {
critical (_("Falied to remove note from database: %s\n"), error.message);
}
}
public void rename (string new_title)
{
string sqlcmd = "UPDATE `notes` SET title= :title WHERE id = :id;";
Midori.DatabaseStatement statement;
try {
statement = database.prepare (sqlcmd,
":id", typeof (int64), this.id,
":title", typeof (string), new_title);
statement.step ();
} catch (Error error) {
critical (_("Falied to rename note: %s\n"), error.message);
}
this.title = new_title;
}
public void update (string new_content)
{
string sqlcmd = "UPDATE `notes` SET note_content= :content WHERE id = :id;";
Midori.DatabaseStatement statement;
try {
statement = database.prepare (sqlcmd,
":id", typeof (int64), this.id,
":content", typeof (string), new_content);
statement.step ();
} catch (Error error) {
critical (_("Falied to update note: %s\n"), error.message);
}
this.content = new_content;
}
}
void append_note (Note note)
{
/* Strip LRE leading character */
if (note.title != null && note.title.has_prefix (""))
note.title = note.title.replace ("", "");
Gtk.TreeIter iter;
notes_list_store.append (out iter);
notes_list_store.set (iter, 0, note);
}
void remove_note (int64 id)
{
Gtk.TreeIter iter;
if (notes_list_store.iter_children (out iter, null)) {
do {
Note note;
notes_list_store.get (iter, 0, out note);
if (id == note.id) {
if (current_note == note) {
current_note = null;
}
notes_list_store.remove (iter);
break;
}
} while (notes_list_store.iter_next (ref iter));
}
}
private class Sidebar : Gtk.VBox, Midori.Viewable {
Gtk.Toolbar? toolbar = null;
Gtk.Label note_label;
Gtk.TreeView notes_tree_view;
Gtk.TextView note_text_view = new Gtk.TextView ();
public unowned string get_stock_id () {
return Gtk.STOCK_EDIT;
}
public unowned string get_label () {
return _("Notes");
}
public Gtk.Widget get_toolbar () {
if (toolbar == null) {
toolbar = new Gtk.Toolbar ();
var new_note_button = new Gtk.ToolButton.from_stock (Gtk.STOCK_EDIT);
new_note_button.label = _("New Note");
new_note_button.tooltip_text = _("Creates a new empty note, unrelated to opened pages");
new_note_button.use_underline = true;
new_note_button.is_important = true;
new_note_button.show ();
new_note_button.clicked.connect (() => {
var note = new Note ();
note.add (_("New note"), null, "");
});
toolbar.insert (new_note_button, -1);
}
return toolbar;
}
internal void title_edited (Gtk.CellRendererText renderer, string? path_str, string? new_title) {
var path = new Gtk.TreePath.from_string (path_str);
Gtk.TreeIter iter;
notes_list_store.get_iter (out iter, path);
Note note;
notes_list_store.get (iter, 0, out note);
note.rename (new_title);
notes_list_store.set (iter, 0, note);
}
public Sidebar () {
Gtk.TreeViewColumn column;
notes_list_store = new Gtk.ListStore (1, typeof (Note));
notes_tree_view = new Gtk.TreeView.with_model (notes_list_store);
notes_tree_view.headers_visible = true;
notes_tree_view.button_press_event.connect (button_pressed);
notes_tree_view.get_selection().changed.connect (selection_changed);
notes_list_store.set_sort_column_id (0, Gtk.SortType.ASCENDING);
notes_list_store.set_sort_func (0, tree_sort_func);
column = new Gtk.TreeViewColumn ();
Gtk.CellRendererPixbuf renderer_icon = new Gtk.CellRendererPixbuf ();
column.pack_start (renderer_icon, false);
column.set_cell_data_func (renderer_icon, on_render_icon);
notes_tree_view.append_column (column);
column = new Gtk.TreeViewColumn ();
Gtk.CellRendererText renderer_title = new Gtk.CellRendererText ();
renderer_title.editable = true;
renderer_title.edited.connect (title_edited);
column.set_title (_("Notes"));
column.pack_start (renderer_title, true);
column.set_cell_data_func (renderer_title, on_render_note_title);
notes_tree_view.append_column (column);
try {
string sqlcmd = "SELECT id, uri, title, note_content FROM notes";
var statement = database.prepare (sqlcmd);
while (statement.step ()) {
var note = new Note ();
note.id = statement.get_int64 ("id");
note.uri = statement.get_string ("uri");
note.title = statement.get_string ("title");
note.content = statement.get_string ("note_content");
append_note (note);
}
} catch (Error error) {
critical (_("Failed to select from notes database: %s\n"), error.message);
}
notes_tree_view.show ();
pack_start (notes_tree_view, false, false, 0);
note_label = new Gtk.Label (null);
note_label.show ();
pack_start (note_label, false, false, 0);
note_text_view.set_wrap_mode (Gtk.WrapMode.WORD);
note_text_view.show ();
note_text_view.focus_out_event.connect (focus_lost);
pack_start (note_text_view, true, true, 0);
}
int tree_sort_func (Gtk.TreeModel model, Gtk.TreeIter a, Gtk.TreeIter b) {
Note note1, note2;
model.get (a, 0, out note1);
model.get (b, 0, out note2);
return strcmp (note1.title, note2.title);
}
void save_current_note () {
if (current_note != null) {
string note_content = note_text_view.buffer.text;
if (note_content != current_note.content)
current_note.update (note_content);
}
}
bool focus_lost (Gdk.EventFocus event) {
save_current_note ();
return false;
}
private void on_render_note_title (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
Note note;
model.get (iter, 0, out note);
renderer.set ("markup", GLib.Markup.printf_escaped ("%s", note.title),
"ellipsize", Pango.EllipsizeMode.END);
}
private void on_render_icon (Gtk.CellLayout column, Gtk.CellRenderer renderer,
Gtk.TreeModel model, Gtk.TreeIter iter) {
Note note;
model.get (iter, 0, out note);
var pixbuf = Midori.Paths.get_icon (note.uri, null);
if (pixbuf != null) {
int icon_width = 16, icon_height = 16;
Gtk.icon_size_lookup_for_settings (get_settings (),
Gtk.IconSize.MENU, out icon_width, out icon_height);
pixbuf = pixbuf.scale_simple (icon_width, icon_height, Gdk.InterpType.TILES);
}
renderer.set ("pixbuf", pixbuf);
}
private void selection_changed (Gtk.TreeSelection selection)
{
save_current_note ();
show_note_content (selection);
}
bool button_pressed (Gdk.EventButton event) {
if (event.button == 1) {
if (event.type == Gdk.EventType.2BUTTON_PRESS) {
return show_note_webpage_in_new_tab (event, false);
}
}
if (event.button == 2)
return show_note_webpage_in_new_tab (event, true);
if (event.button == 3)
return show_popup_menu (event);
return false;
}
bool show_note_content (Gtk.TreeSelection selection) {
Gtk.TreeIter iter;
if (selection.get_selected (null, out iter)) {
Note note;
notes_list_store.get (iter, 0, out note);
if (note != current_note) {
note_text_view.buffer.text = note.content;
current_note = note;
}
return true;
} else {
note_text_view.buffer.text = "";
}
return false;
}
bool show_note_webpage_in_new_tab (Gdk.EventButton? event, bool new_tab) {
Gtk.TreeIter iter;
if (notes_tree_view.get_selection ().get_selected (null, out iter)) {
Note note;
notes_list_store.get (iter, 0, out note);
if (note.uri != null) {
var browser = Midori.Browser.get_for_widget (notes_tree_view);
if (new_tab) {
browser.add_uri (note.uri);
} else {
var tab = browser.tab as Midori.View;
tab.set_uri (note.uri);
}
return true;
}
}
return false;
}
bool show_popup_menu (Gdk.EventButton? event) {
return_val_if_fail (event.window == notes_tree_view.get_bin_window(), false);
Gtk.TreePath path = null;
notes_tree_view.get_path_at_pos ((int)event.x, (int)event.y, out path,
null, null, null);
if (path != null) {
Gtk.TreeIter iter;
notes_list_store.get_iter (out iter, path);
Note note;
notes_list_store.get (iter, 0, out note);
var menu = new Gtk.Menu ();
var menuitem = new Gtk.ImageMenuItem.with_label (_("Rename note"));
var image = new Gtk.Image.from_stock (Gtk.STOCK_EDIT, Gtk.IconSize.MENU);
menuitem.always_show_image = true;
menuitem.set_image (image);
menuitem.activate.connect (() => {
notes_tree_view.set_cursor (path,
notes_tree_view.get_column (1), true);
});
menu.append (menuitem);
menuitem = new Gtk.ImageMenuItem.with_label (_("Copy note to clipboard"));
image = new Gtk.Image.from_stock (Gtk.STOCK_COPY, Gtk.IconSize.MENU);
menuitem.always_show_image = true;
menuitem.set_image (image);
menuitem.activate.connect (() => {
get_clipboard (Gdk.SELECTION_CLIPBOARD).set_text (note.content, -1);
});
menu.append (menuitem);
menuitem = new Gtk.ImageMenuItem.with_label (_("Remove note"));
image = new Gtk.Image.from_stock (Gtk.STOCK_DELETE, Gtk.IconSize.MENU);
menuitem.always_show_image = true;
menuitem.set_image (image);
menuitem.activate.connect (() => {
note.remove ();
});
menu.append (menuitem);
menu.show_all ();
Katze.widget_popup (notes_tree_view, menu, null, Katze.MenuPos.CURSOR);
return true;
}
return false;
}
}
private class Manager : Midori.Extension {
internal GLib.List<Gtk.Widget> widgets;
void tab_added (Midori.Browser browser, Midori.Tab tab) {
tab.context_menu.connect (add_menu_items);
}
void add_menu_items (Midori.Tab tab, WebKit.HitTestResult hit_test_result, Midori.ContextAction menu) {
#if !HAVE_WEBKIT2
if ((hit_test_result.context & WebKit.HitTestResultContext.SELECTION) == 0)
return;
#endif
var view = tab as Midori.View;
var action = new Gtk.Action ("Notes", _("Copy selection as note"), null, null);
action.activate.connect ((action)=> {
if (view.has_selection () == true)
{
string selected_text = view.get_selected_text ();
string uri = view.get_display_uri ();
string title = view.get_display_title ();
var note = new Note();
note.add (title, uri, selected_text);
}
});
menu.add (action);
}
void browser_added (Midori.Browser browser) {
var viewable = new Sidebar ();
viewable.show ();
browser.panel.append_page (viewable);
widgets.append (viewable);
foreach (var tab in browser.get_tabs ())
tab_added (browser, tab);
browser.add_tab.connect (tab_added);
}
void activated (Midori.App app) {
string config_path = this.get_config_dir () ?? ":memory:";
string db_path = GLib.Path.build_path (Path.DIR_SEPARATOR_S, config_path, "notes.db");
try {
database = new Midori.Database (db_path);
} catch (Midori.DatabaseError schema_error) {
error (schema_error.message);
}
db = database.db;
widgets = new GLib.List<Gtk.Widget> ();
app.add_browser.connect (browser_added);
foreach (var browser in app.get_browsers ())
browser_added (browser);
}
void deactivated () {
var app = get_app ();
app.add_browser.disconnect (browser_added);
foreach (var widget in widgets)
widget.destroy ();
}
internal Manager () {
GLib.Object (name: _("Notes"),
description: _("Save text clips from websites as notes"),
version: "0.1" + Midori.VERSION_SUFFIX,
authors: "Paweł Forysiuk");
this.activate.connect (activated);
this.deactivate.connect (deactivated);
}
}
}
public Midori.Extension extension_init () {
return new ClipNotes.Manager ();
}