1/* 2 * Notes - panel plugin for Xfce Desktop Environment 3 * Copyright (c) 2009-2010 Mike Massonnet <mmassonnet@xfce.org> 4 * 5 * TODO: 6 * - Follow GNOME bug #551184 to change accelerators hexa values 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 2 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU Library General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License 19 * along with this program; if not, write to the Free Software 20 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 21 */ 22 23using Gtk; 24using Pango; 25 26namespace Xnp { 27 28 public class Window : Gtk.Window { 29 30 private int width; 31 private int height; 32 private Gtk.Menu menu; 33 private Gtk.CheckMenuItem mi_above; 34 private Gtk.CheckMenuItem mi_sticky; 35 private Gtk.Image menu_image; 36 private Gdk.Pixbuf menu_pixbuf; 37 private Gdk.Pixbuf menu_hover_pixbuf; 38 private Gtk.Label title_label; 39 private Xnp.TitleBarButton refresh_button; 40 private Xnp.TitleBarButton left_arrow_button; 41 private Xnp.TitleBarButton right_arrow_button; 42 private Xnp.TitleBarButton close_button; 43 private Gtk.Box content_box; 44 private Gtk.Notebook notebook; 45 46 private Gtk.UIManager ui; 47 private const string ui_string = 48""" 49<ui> 50 <accelerator action="close-window" /> 51 <accelerator action="new-window" /> 52 <accelerator action="delete-window" /> 53 <accelerator action="rename-window" /> 54 <accelerator action="new-note" /> 55 <accelerator action="delete-note" /> 56 <accelerator action="rename-note" /> 57 <accelerator action="cancel" /> 58 <accelerator action="next-note" /> 59 <accelerator action="prev-note" /> 60</ui> 61"""; 62 private Gtk.ActionGroup action_group; 63 private const Gtk.ActionEntry[] action_entries = { 64 { "close-window", null, null, "Escape", null, hide }, 65 { "new-window", null, null, "<Ctrl><Shift>n", null, action_new_window }, 66 { "delete-window", null, null, "<Ctrl><Shift>w", null, action_delete_window }, 67 { "rename-window", null, null, "<Shift>F2", null, action_rename_window }, 68 { "new-note", null, null, "<Ctrl>n", null, action_new_note }, 69 { "delete-note", null, null, "<Ctrl>w", null, action_delete_note }, 70 { "rename-note", null, null, "F2", null, action_rename_note }, 71 { "cancel", null, null, "<Ctrl>z", null, action_cancel }, 72 { "next-note", null, null, "<Ctrl>Page_Down", null, action_next_note }, 73 { "prev-note", null, null, "<Ctrl>Page_Up", null, action_prev_note } 74 }; 75 76 private int CORNER_MARGIN = 20; 77 private Gdk.Cursor CURSOR_RIGHT = new Gdk.Cursor.for_display (Gdk.Display.get_default(), Gdk.CursorType.RIGHT_SIDE); 78 private Gdk.Cursor CURSOR_LEFT = new Gdk.Cursor.for_display (Gdk.Display.get_default(), Gdk.CursorType.LEFT_SIDE); 79 private Gdk.Cursor CURSOR_BOTTOM_RC = new Gdk.Cursor.for_display (Gdk.Display.get_default(), Gdk.CursorType.BOTTOM_RIGHT_CORNER); 80 private Gdk.Cursor CURSOR_BOTTOM = new Gdk.Cursor.for_display (Gdk.Display.get_default(), Gdk.CursorType.BOTTOM_SIDE); 81 private Gdk.Cursor CURSOR_BOTTOM_LC = new Gdk.Cursor.for_display (Gdk.Display.get_default(), Gdk.CursorType.BOTTOM_LEFT_CORNER); 82 83 private unowned SList<Xnp.Window> window_list; 84 85 public new string name { default = _("Notes"); get; set; } 86 public int n_pages { get; set; } 87 88 public bool show_tabs { 89 get { 90 return this.notebook.show_tabs; 91 } 92 set { 93 this.notebook.show_tabs = value; 94 } 95 } 96 97 private int _tabs_position; 98 public int tabs_position { 99 get { 100 return this._tabs_position; 101 } 102 set { 103 this._tabs_position = value; 104 if (this._tabs_position == 0) 105 show_tabs = false; 106 else { 107 show_tabs = true; 108 _notebook_update_tabs_angle (); 109 if (this._tabs_position == 1) 110 this.notebook.tab_pos = Gtk.PositionType.TOP; 111 else if (this._tabs_position == 2) 112 this.notebook.tab_pos = Gtk.PositionType.RIGHT; 113 else if (this._tabs_position == 3) 114 this.notebook.tab_pos = Gtk.PositionType.BOTTOM; 115 else if (this._tabs_position == 4) 116 this.notebook.tab_pos = Gtk.PositionType.LEFT; 117 else { 118 this.show_tabs = false; 119 warning ("Bad value for tabs-position"); 120 } 121 } 122 } 123 } 124 125 private bool _above; 126 public bool above { 127 get { 128 return this._above; 129 } 130 set { 131 this._above = value; 132 set_keep_above (value); 133 } 134 } 135 136 private bool _sticky; 137 public bool sticky { 138 get { 139 return this._sticky; 140 } 141 set { 142 this._sticky = value; 143 if (value == true) 144 stick (); 145 else 146 unstick (); 147 if (this.mi_sticky is Gtk.CheckMenuItem) 148 this.mi_sticky.active = this._sticky; 149 } 150 } 151 152 private bool _show_refresh_button; 153 public bool show_refresh_button { 154 get { 155 return this._show_refresh_button; 156 } 157 set { 158 this._show_refresh_button = value; 159 if (value == true) { 160 this.refresh_button.show (); 161 } 162 else { 163 this.refresh_button.hide (); 164 } 165 } 166 } 167 168 public signal void action (string action); 169 public signal void save_data (Xnp.Note note); 170 public signal void note_inserted (Xnp.Note note); 171 public signal void note_deleted (Xnp.Note note); 172 public signal void note_renamed (Xnp.Note note, string old_name); 173 174 construct { 175 ((Gtk.Widget)this).name = "notes-window"; 176 this.title = _("Notes"); 177 this.deletable = false; 178 this.skip_taskbar_hint = true; 179 this.default_height = 380; 180 this.default_width = 300; 181 this.decorated = false; 182 this.icon_name = "xfce4-notes-plugin"; 183 this.sticky = true; 184 this.opacity = 0.9; 185 } 186 187 public Window () { 188 /* Window responses on pointer motion */ 189 add_events (Gdk.EventMask.POINTER_MOTION_MASK|Gdk.EventMask.POINTER_MOTION_HINT_MASK|Gdk.EventMask.BUTTON_PRESS_MASK); 190 191 /* Build accelerators */ 192 this.action_group = new Gtk.ActionGroup ("XNP"); 193 this.action_group.add_actions (action_entries, this); 194 195 this.ui = new Gtk.UIManager (); 196 this.ui.insert_action_group (this.action_group, 0); 197 try { 198 this.ui.add_ui_from_string (this.ui_string , -1); 199 add_accel_group (this.ui.get_accel_group ()); 200 } 201 catch (Error e) { 202 warning ("%s", e.message); 203 } 204 205 /* Build Menu */ 206 this.menu = build_menu (); 207 this.menu.show_all (); 208 209 /* Build Frame */ 210 var frame = new Gtk.Frame (null); 211 frame.shadow_type = Gtk.ShadowType.NONE; 212 var style = frame.get_modifier_style (); 213 style.xthickness = 1; 214 style.ythickness = 3; 215 frame.modify_style (style); 216 frame.show (); 217 add (frame); 218 var vbox_frame = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); 219 220 vbox_frame.spacing = 1; 221 vbox_frame.show (); 222 frame.add (vbox_frame); 223 224 /* Build title bar */ 225 var title_box = new Gtk.Box (Gtk.Orientation.HORIZONTAL, 0); 226 var menu_evbox = new Gtk.EventBox (); 227 menu_evbox.tooltip_text = _("Menu"); 228 menu_evbox.set_visible_window (false); 229 try { 230 this.menu_pixbuf = new Gdk.Pixbuf.from_file ("%s/pixmaps/notes-menu.png".printf (Config.PKGDATADIR)); 231 this.menu_hover_pixbuf = new Gdk.Pixbuf.from_file ("%s/pixmaps/notes-menu-active.png".printf (Config.PKGDATADIR)); 232 } 233 catch (Error e) { 234 this.menu_pixbuf = this.menu_hover_pixbuf = null; 235 } 236 this.menu_image = new Gtk.Image.from_pixbuf (this.menu_pixbuf); 237 menu_evbox.add (this.menu_image); 238 menu_evbox.enter_notify_event.connect (() => { 239 this.menu_image.set_from_pixbuf (this.menu_hover_pixbuf); 240 return false; 241 }); 242 menu_evbox.leave_notify_event.connect (() => { 243 this.menu_image.set_from_pixbuf (this.menu_pixbuf); 244 return false; 245 }); 246 title_box.pack_start (menu_evbox, false, false, 2); 247 var title_evbox = new Gtk.EventBox (); 248 title_evbox.set_visible_window (false); 249 this.title_label = new Gtk.Label (null); 250 this.title_label.set_markup ("<b>"+this.title+"</b>"); 251 this.title_label.ellipsize = Pango.EllipsizeMode.END; 252 this.title_label.xalign = (float)0.0; 253 title_evbox.add (this.title_label); 254 title_box.pack_start (title_evbox, true, true, 6); 255 this.refresh_button = new Xnp.TitleBarButton (Xnp.TitleBarButtonType.REFRESH); 256 this.refresh_button.tooltip_text = _("Refresh notes"); 257 this.refresh_button.no_show_all = true; 258 this.refresh_button.sensitive = false; 259 title_box.pack_start (this.refresh_button, false, false, 2); 260 this.left_arrow_button = new Xnp.TitleBarButton (Xnp.TitleBarButtonType.LEFT_ARROW); 261 this.left_arrow_button.tooltip_text = Gtk.accelerator_get_label (0xff55, Gdk.ModifierType.CONTROL_MASK); // GDK_Page_Up 262 this.left_arrow_button.sensitive = false; 263 title_box.pack_start (this.left_arrow_button, false, false, 2); 264 this.right_arrow_button = new Xnp.TitleBarButton (Xnp.TitleBarButtonType.RIGHT_ARROW); 265 this.right_arrow_button.tooltip_text = Gtk.accelerator_get_label (0xff56, Gdk.ModifierType.CONTROL_MASK); // GDK_Page_Down 266 this.right_arrow_button.sensitive = false; 267 title_box.pack_start (this.right_arrow_button, false, false, 2); 268 this.close_button = new Xnp.TitleBarButton (Xnp.TitleBarButtonType.CLOSE); 269 this.close_button.tooltip_text = _("Hide (%s)").printf (Gtk.accelerator_get_label (0xff1b, 0)); // GDK_Escape 270 title_box.pack_start (this.close_button, false, false, 2); 271 title_box.show_all (); 272 vbox_frame.pack_start (title_box, false, false, 0); 273 274 /* Build content box */ 275 this.content_box = new Gtk.Box (Gtk.Orientation.VERTICAL, 0); 276 this.content_box.show (); 277 vbox_frame.pack_start (this.content_box, true, true, 0); 278 279 /* Build Notebook */ 280 this.notebook = new Gtk.Notebook (); 281 this.notebook.name = "notes-notebook"; 282 this.notebook.show_border = true; 283 this.notebook.show_tabs = false; 284 this.notebook.tab_pos = Gtk.PositionType.TOP; 285 this.notebook.scrollable = true; 286 this.notebook.show (); 287 this.content_box.pack_start (this.notebook, true, true, 0); 288 289 /* Connect mouse click signals */ 290 menu_evbox.button_press_event.connect (menu_evbox_pressed_cb); 291 this.refresh_button.clicked.connect (action_refresh_notes); 292 this.left_arrow_button.clicked.connect (action_prev_note); 293 this.right_arrow_button.clicked.connect (action_next_note); 294 this.close_button.clicked.connect (() => { hide (); }); 295 296 /* Connect extra signals */ 297 delete_event.connect (() => { 298 /* Replace ALT+F4 action */ 299 hide (); 300 return true; 301 }); 302 focus_in_event.connect (() => { 303 menu_image.sensitive = true; 304 title_label.sensitive = true; 305 refresh_button.sensitive = true; 306 update_navigation_sensitivity (this.notebook.get_current_page ()); 307 close_button.sensitive = true; 308 return false; 309 }); 310 focus_out_event.connect (() => { 311 menu_image.sensitive = false; 312 title_label.sensitive = false; 313 refresh_button.sensitive = false; 314 left_arrow_button.sensitive = false; 315 right_arrow_button.sensitive = false; 316 close_button.sensitive = false; 317 return false; 318 }); 319 leave_notify_event.connect (window_leaved_cb); 320 motion_notify_event.connect (window_motion_cb); 321 button_press_event.connect (window_pressed_cb); 322 window_state_event.connect (window_state_cb); 323 title_evbox.button_press_event.connect (title_evbox_pressed_cb); 324 title_evbox.scroll_event.connect (title_evbox_scrolled_cb); 325 this.notebook.page_added.connect ((n, c, p) => { 326 notebook.set_current_page ((int)p); 327 update_navigation_sensitivity ((int)p); 328 }); 329 this.notebook.page_removed.connect ((n, c, p) => { 330 update_navigation_sensitivity ((int)p); 331 }); 332 this.notebook.switch_page.connect ((n, c, p) => { 333 var note = (Xnp.Note)(notebook.get_nth_page ((int)p)); 334 update_title (note.name); 335 update_navigation_sensitivity ((int)p); 336 }); 337 notify["name"].connect (() => { 338 int page = this.notebook.get_current_page (); 339 if (page == -1) 340 return; 341 var current_note = (Xnp.Note)(this.notebook.get_nth_page (page)); 342 update_title (current_note.name); 343 }); 344 notify["title"].connect (() => { 345 title_label.set_markup ("<b>"+title+"</b>"); 346 }); 347 } 348 349 ~Window () { 350 } 351 352 /* 353 * Signal callbacks 354 */ 355 356 /** 357 * hide: 358 * 359 * Save position before hidding. 360 */ 361 public new void hide () { 362 int winx, winy; 363 get_position (out winx, out winy); 364 base.hide (); 365 deiconify (); 366 unshade (); 367 move (winx, winy); 368 set_keep_above (this.above); 369 } 370 371 /** 372 * window_leaved_cb: 373 * 374 * Reset the mouse cursor. 375 */ 376 private bool window_leaved_cb () { 377 get_window ().set_cursor (null); 378 return true; 379 } 380 381 /** 382 * window_motion_cb: 383 * 384 * Update mouse cursor. 385 */ 386 private bool window_motion_cb (Gdk.EventMotion event) { 387 Gtk.Allocation allocation; 388 389 get_allocation (out allocation); 390 391 if (event.x > 4 && event.y > 4 392 && event.x < allocation.width - 4 393 && event.y < allocation.height - 4) { 394 get_window ().set_cursor (null); 395 return false; 396 } 397 398 // Right 399 if (event.x >= allocation.width - this.CORNER_MARGIN 400 && event.y >= this.CORNER_MARGIN 401 && event.y < allocation.height - this.CORNER_MARGIN) 402 get_window ().set_cursor (this.CURSOR_RIGHT); 403 // Bottom right corner 404 else if (event.x >= allocation.width - this.CORNER_MARGIN 405 && event.y >= allocation.height - this.CORNER_MARGIN) 406 get_window ().set_cursor (this.CURSOR_BOTTOM_RC); 407 // Bottom 408 else if (event.x > this.CORNER_MARGIN 409 && event.y > allocation.height - this.CORNER_MARGIN 410 && event.x < allocation.width - this.CORNER_MARGIN) 411 get_window ().set_cursor (this.CURSOR_BOTTOM); 412 // Bottom left corner 413 else if (event.x <= this.CORNER_MARGIN 414 && event.y >= allocation.height - this.CORNER_MARGIN) 415 get_window ().set_cursor (this.CURSOR_BOTTOM_LC); 416 // Left 417 else if (event.x <= this.CORNER_MARGIN && event.y >= this.CORNER_MARGIN 418 && event.y < allocation.height - this.CORNER_MARGIN) 419 get_window ().set_cursor (this.CURSOR_LEFT); 420 // Default 421 else 422 get_window ().set_cursor (null); 423 424 return true; 425 } 426 427 /** 428 * window_pressed_cb: 429 * 430 * Start a window resize depending on mouse pointer location. 431 */ 432 private bool window_pressed_cb (Gdk.EventButton event) { 433 Gdk.WindowEdge edge; 434 Gtk.Allocation allocation; 435 436 get_allocation (out allocation); 437 438 if (event.x > 4 && event.y > 4 439 && event.x < allocation.width - 4 440 && event.y < allocation.height - 4) 441 return false; 442 443 // Right 444 if (event.y > this.CORNER_MARGIN 445 && event.x > allocation.width - this.CORNER_MARGIN 446 && event.y < allocation.height - this.CORNER_MARGIN) 447 edge = Gdk.WindowEdge.EAST; 448 // Bottom right corner 449 else if (event.x >= allocation.width - this.CORNER_MARGIN 450 && event.y >= allocation.height - this.CORNER_MARGIN) 451 edge = Gdk.WindowEdge.SOUTH_EAST; 452 // Bottom 453 else if (event.x > this.CORNER_MARGIN 454 && event.y > allocation.height - this.CORNER_MARGIN 455 && event.x < allocation.width - this.CORNER_MARGIN) 456 edge = Gdk.WindowEdge.SOUTH; 457 // Bottom left corner 458 else if (event.x <= this.CORNER_MARGIN 459 && event.y >= allocation.height - this.CORNER_MARGIN) 460 edge = Gdk.WindowEdge.SOUTH_WEST; 461 // Left 462 else if (event.y > this.CORNER_MARGIN && event.x < this.CORNER_MARGIN 463 && event.y < allocation.height - this.CORNER_MARGIN) 464 edge = Gdk.WindowEdge.WEST; 465 else 466 return false; 467 468 begin_resize_drag (edge, (int)event.button, 469 (int)event.x_root, (int)event.y_root, event.time); 470 471 return true; 472 } 473 474 /** 475 * window_state_cb: 476 * 477 * Watch window manager actions always on top and sticky 478 * window. 479 */ 480 private bool window_state_cb (Gdk.EventWindowState event) { 481 if ((bool)(event.changed_mask & Gdk.WindowState.ABOVE)) { 482 /* FIXME above state is never notified despit 483 * of xfwm4 switching the state */ 484 this.mi_above.active = (bool)(event.new_window_state & Gdk.WindowState.ABOVE); 485 } 486 if ((bool)(event.changed_mask & Gdk.WindowState.STICKY) && get_visible ()) { 487 this.sticky = (bool)((event.new_window_state & Gdk.WindowState.STICKY) != 0); 488 } 489 return false; 490 } 491 492 /** 493 * title_evbox_pressed_cb: 494 * 495 * Raise/lower the window and popup window menu. 496 */ 497 private bool title_evbox_pressed_cb (Gtk.Widget widget, Gdk.EventButton event) { 498 if (event.type != Gdk.EventType.BUTTON_PRESS) 499 return false; 500 if (event.button == 1) { 501 get_window ().show (); 502 int winx, winy, curx, cury; 503 get_position (out winx, out winy); 504 get_pointer (out curx, out cury); 505 winx += curx; 506 winy += cury; 507 begin_move_drag (1, winx, winy, Gtk.get_current_event_time ()); 508 } 509 else if (event.button == 2) { 510 get_window ().lower (); 511 } 512 else if (event.button == 3) { 513 this.menu.popup (null, null, null, 0, Gtk.get_current_event_time ()); 514 } 515 return false; 516 } 517 518 /** 519 * title_evbox_scrolled_cb: 520 * 521 * Shade/unshade the window and set transparency by holding ALT. 522 */ 523 private bool title_evbox_scrolled_cb (Gtk.Widget widget, Gdk.EventScroll event) { 524 if ((bool)(event.state & Gdk.ModifierType.MOD1_MASK)) { 525 if (event.direction == Gdk.ScrollDirection.UP) { 526 opacity += 0.1; 527 } else if (event.direction == Gdk.ScrollDirection.DOWN) { 528 if (opacity - 0.1 >= 0.1) 529 opacity -= 0.1; 530 } 531 } 532 else { 533 if (event.direction == Gdk.ScrollDirection.UP) { 534 shade (); 535 } 536 else if (event.direction == Gdk.ScrollDirection.DOWN) { 537 unshade (); 538 } 539 } 540 return false; 541 } 542 543 /** 544 * note_notify_name_cb: 545 * 546 */ 547 private void note_notify_name_cb (GLib.Object object, GLib.ParamSpec pspec) { 548 Xnp.Note note = object as Xnp.Note; 549 this.notebook.set_tab_label_text (note, note.name); 550 _notebook_update_tabs_angle (); 551 int page = this.notebook.get_current_page (); 552 var current_note = (Xnp.Note)(this.notebook.get_nth_page (page)); 553 if (note == current_note) 554 this.update_title (note.name); 555 } 556 557 /* 558 * Action callbacks 559 */ 560 561 private void action_new_window () { 562 action ("create-new-window"); 563 } 564 565 private void action_delete_window () { 566 action ("delete"); 567 } 568 569 private void action_rename_window () { 570 action ("rename"); 571 } 572 573 private void action_new_note () { 574 insert_note (); 575 } 576 577 private void action_delete_note () { 578 delete_current_note (); 579 } 580 581 private void action_rename_note () { 582 rename_current_note (); 583 } 584 585 private void action_cancel () { 586 int page = notebook.get_current_page (); 587 if (page < 0) 588 return; 589 Gtk.Widget child = notebook.get_nth_page (page); 590 ((Xnp.Note)child).text_view.undo (); 591 } 592 593 private void action_refresh_notes () { 594 action ("refresh-notes"); 595 } 596 597 private void action_next_note () { 598 notebook.next_page (); 599 } 600 601 private void action_prev_note () { 602 notebook.prev_page (); 603 } 604 605 /* 606 * Window menu 607 */ 608 609 /** 610 * menu_evbox_pressed_cb: 611 * 612 * Popup the window menu. 613 */ 614 private bool menu_evbox_pressed_cb (Gtk.Widget widget, Gdk.EventButton event) { 615 this.menu.popup (null, null, menu_position, 0, Gtk.get_current_event_time ()); 616 return false; 617 } 618 619 /** 620 * menu_position: 621 * 622 * Menu position function for the window menu. 623 */ 624 private void menu_position (Gtk.Menu menu, out int x, out int y, out bool push_in) { 625 int winx, winy, width, height; 626 Gtk.Requisition requisition; 627 Gtk.Allocation allocation; 628 629 get_window ().get_geometry (out winx, out winy, out width, out height); 630 menu.get_preferred_size (out requisition, null); 631 get_window ().get_origin (out x, out y); 632 push_in = false; 633 634 content_box.get_allocation (out allocation); 635 636 if (y + allocation.y + requisition.height > Gdk.Screen.height ()) { 637 /* Show menu above */ 638 y -= requisition.height; 639 } 640 else { 641 /* Show menu below */ 642 y += allocation.y; 643 } 644 if (x + requisition.width > Gdk.Screen.width ()) { 645 /* Adjust menu left */ 646 int menu_width; 647 menu.get_preferred_width (out menu_width, null); 648 x = x - menu_width + allocation.y; 649 } 650 } 651 652 /** 653 * build_menu: 654 * 655 * Build the window menu. 656 */ 657 private Gtk.Menu build_menu () { 658 var menu = new Gtk.Menu (); 659 menu.set_accel_group (this.ui.get_accel_group ()); 660 661 var mi = new Gtk.MenuItem.with_mnemonic (_("_Groups")); 662 menu.append (mi); 663 664 /* Navigation */ 665 var menu_go = new Gtk.Menu (); 666 menu_go.set_accel_group (this.ui.get_accel_group ()); 667 menu_go.show.connect (update_menu_go); 668 mi.set_submenu (menu_go); 669 670 /* Note items */ 671 mi = new Gtk.SeparatorMenuItem (); 672 menu.append (mi); 673 674 mi = new Gtk.MenuItem.with_mnemonic (_("_New")); 675 mi.set_accel_path (this.action_group.get_action ("new-note").get_accel_path ()); 676 mi.activate.connect (action_new_note); 677 menu.append (mi); 678 679 mi = new Gtk.MenuItem.with_mnemonic (_("_Delete")); 680 mi.set_accel_path (this.action_group.get_action ("delete-note").get_accel_path ()); 681 mi.activate.connect (action_delete_note); 682 menu.append (mi); 683 684 mi = new Gtk.MenuItem.with_mnemonic (_("_Rename")); 685 mi.set_accel_path (this.action_group.get_action ("rename-note").get_accel_path ()); 686 mi.activate.connect (action_rename_note); 687 menu.append (mi); 688 689 mi = new Gtk.MenuItem.with_mnemonic (_("_Undo")); 690 mi.set_accel_path (this.action_group.get_action ("cancel").get_accel_path ()); 691 mi.activate.connect (action_cancel); 692 menu.append (mi); 693 694 /* Window options */ 695 mi = new Gtk.SeparatorMenuItem (); 696 menu.append (mi); 697 698 mi = this.mi_above = new Gtk.CheckMenuItem.with_label (_("Always on top")); 699 ((Gtk.CheckMenuItem)mi).active = this.above; 700 ((Gtk.CheckMenuItem)mi).toggled.connect ((o) => { above = o.active; }); 701 menu.append (mi); 702 703 mi = this.mi_sticky = new Gtk.CheckMenuItem.with_label (_("Sticky window")); 704 ((Gtk.CheckMenuItem)mi).active = this.sticky; 705 ((Gtk.CheckMenuItem)mi).toggled.connect ((o) => { sticky = o.active; }); 706 menu.append (mi); 707 708 /* Settings/About dialog */ 709 mi = new Gtk.SeparatorMenuItem (); 710 menu.append (mi); 711 712 mi = new Gtk.MenuItem.with_mnemonic ("_Properties"); 713 mi.activate.connect (() => { action ("properties"); }); 714 menu.append (mi); 715 716 mi = new Gtk.MenuItem.with_mnemonic ("_About"); 717 mi.activate.connect (() => { action ("about"); }); 718 menu.append (mi); 719 720 return menu; 721 } 722 723 /** 724 * update_menu_go: 725 * 726 * Update the menu Go when it is shown. 727 */ 728 private void update_menu_go (Gtk.Widget widget) { 729 Gtk.Menu menu = widget as Gtk.Menu; 730 Gtk.MenuItem mi; 731 732 menu.@foreach ((w) => { 733 w.destroy (); 734 }); 735 736 foreach (var win in this.window_list) { 737 if (win == this) { 738 mi = new Gtk.MenuItem.with_label (win.name); 739 mi.sensitive = false; 740 menu.append (mi); 741 742 int current_page = this.notebook.get_current_page (); 743 var current_note = (Xnp.Note)(this.notebook.get_nth_page (current_page)); 744 int n_pages = this.notebook.get_n_pages (); 745 for (int p = 0; p < n_pages; p++) { 746 var note = (Xnp.Note)(this.notebook.get_nth_page (p)); 747 mi = new Gtk.MenuItem.with_label (note.name); 748 mi.set_data ("page", p.to_pointer ()); 749 mi.activate.connect ((i) => { 750 int page = i.get_data<int> ("page"); 751 notebook.set_current_page (page); 752 }); 753 menu.append (mi); 754 } 755 756 mi = new Gtk.SeparatorMenuItem (); 757 menu.append (mi); 758 } 759 else { 760 mi = new Gtk.MenuItem.with_label (win.name); 761 mi.set_data ("window", (void*)win); 762 mi.activate.connect ((i) => { 763 var w = i.get_data<Xnp.Window> ("window"); 764 w.present (); 765 }); 766 menu.append (mi); 767 768 mi = new Gtk.SeparatorMenuItem (); 769 menu.append (mi); 770 } 771 } 772 773 mi = new Gtk.MenuItem.with_mnemonic (_("_Rename group")); 774 mi.set_accel_path (this.action_group.get_action ("rename-window").get_accel_path ()); 775 mi.activate.connect (action_rename_window); 776 menu.append (mi); 777 778 mi = new Gtk.MenuItem.with_mnemonic (_("_Delete group")); 779 mi.set_accel_path (this.action_group.get_action ("delete-window").get_accel_path ()); 780 mi.activate.connect (action_delete_window); 781 menu.append (mi); 782 783 mi = new Gtk.MenuItem.with_mnemonic (_("_Add a new group")); 784 mi.set_accel_path (this.action_group.get_action ("new-window").get_accel_path ()); 785 mi.activate.connect (action_new_window); 786 menu.append (mi); 787 788 menu.show_all (); 789 } 790 791 /** 792 * get_geometry: 793 * 794 * Returns the X,Y position and width/height. 795 */ 796 public void get_geometry (out int winx, out int winy, out int width, out int height) { 797 // Window is shaded 798 if (!this.content_box.get_visible ()) { 799 get_size (out this.width, null); 800 } 801 else { 802 get_size (out this.width, out this.height); 803 } 804 get_position (out winx, out winy); 805 width = this.width; 806 height = this.height; 807 } 808 809 /** 810 * set_window_list: 811 * 812 * Saves a list of window inside window.window_list to be shown 813 * within the window menu. 814 */ 815 public void set_window_list (SList <Xnp.Window> list) { 816 this.window_list = list; 817 } 818 819 /** 820 * compare_func: 821 * 822 * Compare function for the window name to use with GLib.CompareFunc delegates. 823 */ 824 public int compare_func (Xnp.Window win2) { 825 return name.collate (win2.name); 826 } 827 828 /** 829 * get_current_page: 830 * 831 * Get the current page in the notebook. 832 */ 833 public int get_current_page () { 834 return this.notebook.get_current_page (); 835 } 836 837 /** 838 * set_current_page: 839 * 840 * Set the current page in the notebook. 841 */ 842 public void set_current_page (int page) { 843 this.notebook.set_current_page (page); 844 } 845 846 /* 847 * Window management 848 */ 849 850 /** 851 * shade: 852 * 853 * Shade the window (roll up) to show only the title bar. 854 */ 855 private void shade () { 856 if (this.content_box.get_visible ()) { 857 this.content_box.hide (); 858 get_size (out this.width, out this.height); 859 resize (this.width, 1); 860 } 861 } 862 863 /** 864 * unshade: 865 * 866 * Unshade the window (roll down). 867 */ 868 private void unshade () { 869 if (!this.content_box.get_visible ()) { 870 this.content_box.show (); 871 get_size (out this.width, null); 872 resize (this.width, this.height); 873 } 874 } 875 876 /** 877 * update_title: 878 * 879 * Updates the window title. 880 */ 881 private void update_title (string note_name) { 882 title = this.name + " - " + note_name; 883 } 884 885 /** 886 * update_navigation_sensitivity: 887 * 888 * Update the goleft/right sensitivities. 889 */ 890 private void update_navigation_sensitivity (int page_num) { 891 int n_pages = notebook.get_n_pages (); 892 if (n_pages <= 1) { 893 this.left_arrow_button.sensitive = false; 894 this.right_arrow_button.sensitive = false; 895 } 896 else { 897 this.left_arrow_button.sensitive = page_num > 0 ? true : false; 898 this.right_arrow_button.sensitive = page_num + 1 < n_pages ? true : false; 899 } 900 } 901 902 /* 903 * Note management 904 */ 905 906 /** 907 * insert_note: 908 * 909 * Create a new note and insert it inside the notebook after 910 * the current position. 911 */ 912 public Xnp.Note insert_note () { 913 int len = this.notebook.get_n_pages (); 914 string name = _("Notes"); 915 for (int id = 1; id <= len + 1; id++) { 916 if (id > 1) { 917 name = _("Notes %d").printf (id); 918 } 919 if (!note_name_exists (name)) { 920 break; 921 } 922 } 923 924 int page = this.notebook.get_current_page () + 1; 925 var note = new Xnp.Note (name); 926 927 note.notify["name"].connect (note_notify_name_cb); 928 note.save_data.connect ((note) => { save_data (note); }); 929 930 note.show (); 931 this.n_pages++; 932 this.notebook.insert_page (note, null, page); 933 this.notebook.set_tab_reorderable (note, true); 934 note.name = note.name; //note.notify ("name"); 935 this.note_inserted (note); 936 _notebook_update_tabs_angle (); 937 return note; 938 } 939 940 /** 941 * move_note: 942 * 943 * Moves the note named @note_name to position @page. 944 */ 945 public void move_note (string note_name, int page) { 946 int n_pages = this.notebook.get_n_pages (); 947 for (int p = 0; p < n_pages; p++) { 948 var note = (Xnp.Note)this.notebook.get_nth_page (p); 949 if (note.name == note_name) { 950 this.notebook.reorder_child (note, page); 951 update_navigation_sensitivity (page); 952 break; 953 } 954 } 955 } 956 957 /** 958 * get_note_names: 959 * 960 * Returns a string list of the note names in the order they are currently displayed 961 * in the notebook. 962 */ 963 public string[] get_note_names () { 964 string[] note_names = null; 965 int n_pages = this.notebook.get_n_pages (); 966 for (int p = 0; p < n_pages; p++) { 967 var note = (Xnp.Note)this.notebook.get_nth_page (p); 968 note_names += note.name; 969 } 970 return note_names; 971 } 972 973 /** 974 * delete_current_note: 975 * 976 * Delete the current note. 977 */ 978 public void delete_current_note () { 979 this.delete_note (this.notebook.get_current_page ()); 980 } 981 982 /** 983 * delete_note: 984 * 985 * Delete note at page @page. 986 */ 987 public void delete_note (int page) { 988 var note = (Xnp.Note)this.notebook.get_nth_page (page); 989 990 if (note.text_view.buffer.get_char_count () > 0) { 991 var dialog = new Gtk.MessageDialog (this, Gtk.DialogFlags.DESTROY_WITH_PARENT, 992 Gtk.MessageType.QUESTION, Gtk.ButtonsType.YES_NO, _("Are you sure you want to delete this note?")); 993 int res = dialog.run (); 994 dialog.destroy (); 995 if (res != Gtk.ResponseType.YES) 996 return; 997 } 998 999 this.n_pages--; 1000 this.notebook.remove_page (page); 1001 this.note_deleted (note); 1002 note.destroy (); 1003 if (this.notebook.get_n_pages () == 0) 1004 action ("delete"); 1005 } 1006 1007 /** 1008 * rename_current_note: 1009 * 1010 * Rename the current note. 1011 */ 1012 public void rename_current_note () { 1013 int page = this.notebook.get_current_page (); 1014 if (page == -1) 1015 return; 1016 var note = (Xnp.Note)(this.notebook.get_nth_page (page)); 1017 1018 var dialog = new Gtk.Dialog.with_buttons (_("Rename note"), (Gtk.Window)get_toplevel (), 1019 Gtk.DialogFlags.MODAL|Gtk.DialogFlags.DESTROY_WITH_PARENT, 1020 "_Cancel", Gtk.ResponseType.CANCEL, "_OK", Gtk.ResponseType.OK); 1021 Gtk.Box content_area = (Gtk.Box)dialog.get_content_area (); 1022 dialog.set_default_response (Gtk.ResponseType.OK); 1023 dialog.resizable = false; 1024 dialog.icon_name = "gtk-edit"; 1025 dialog.border_width = 4; 1026 content_area.border_width = 6; 1027 1028 var entry = new Gtk.Entry (); 1029 entry.text = note.name; 1030 entry.activates_default = true; 1031 content_area.add (entry); 1032 content_area.show_all (); 1033 1034 int res = dialog.run (); 1035 dialog.hide (); 1036 if (res == Gtk.ResponseType.OK) { 1037 string name = entry.text; 1038 if (note_name_exists (name)) { 1039 var error_dialog = new Gtk.MessageDialog (this, Gtk.DialogFlags.DESTROY_WITH_PARENT, 1040 Gtk.MessageType.ERROR, Gtk.ButtonsType.CLOSE, _("The name %s is already in use"), name); 1041 error_dialog.run (); 1042 error_dialog.destroy (); 1043 } 1044 else { 1045 string old_name = note.name; 1046 note.name = name; 1047 this.note_renamed (note, old_name); 1048 } 1049 } 1050 dialog.destroy (); 1051 } 1052 1053 /** 1054 * set_font: 1055 * 1056 * Set the font for the window. 1057 */ 1058 public void set_font () { 1059 int page = this.notebook.get_current_page (); 1060 if (page == -1) 1061 return; 1062 var note = (Xnp.Note)(this.notebook.get_nth_page (page)); 1063 1064 var dialog = new Gtk.FontChooserDialog ("Choose current note font", this); 1065 dialog.set_font (note.text_view.font); 1066 int res = dialog.run (); 1067 dialog.hide (); 1068 if (res == Gtk.ResponseType.OK) { 1069 note.text_view.font = dialog.get_font (); 1070 } 1071 dialog.destroy (); 1072 } 1073 1074 /** 1075 * note_name_exists: 1076 * 1077 * Verify if the given name already exists in the notebook. 1078 */ 1079 private bool note_name_exists (string name) { 1080 int n_pages = this.notebook.get_n_pages (); 1081 for (int p = 0; p < n_pages; p++) { 1082 var note = (Xnp.Note)this.notebook.get_nth_page (p); 1083 if (note.name == name) { 1084 return true; 1085 } 1086 } 1087 return false; 1088 } 1089 1090 /** 1091 * save_notes: 1092 * 1093 * Send the save-data signal on every dirty note. 1094 */ 1095 public void save_notes () { 1096 int n_pages = this.notebook.get_n_pages (); 1097 for (int p = 0; p < n_pages; p++) { 1098 var note = (Xnp.Note)this.notebook.get_nth_page (p); 1099 if (note.dirty) { 1100 note.dirty = false; 1101 save_data (note); 1102 } 1103 } 1104 } 1105 1106 /** 1107 * _notebook_update_tabs_angle: 1108 * 1109 * Set the angle of each label in the tab. 1110 */ 1111 private void _notebook_update_tabs_angle () { 1112 int angle = 0; 1113 if (_tabs_position == 2) 1114 angle = 270; 1115 else if (_tabs_position == 4) 1116 angle = 90; 1117 1118 int pages = this.notebook.get_n_pages (); 1119 for (int i = 0; i < pages; i++) { 1120 var widget = this.notebook.get_nth_page (i); 1121 var label = this.notebook.get_tab_label (widget) as Gtk.Label; 1122 if (label is Gtk.Label) { 1123 label.angle = angle; 1124 } 1125 } 1126 } 1127 1128/* valac -X '-I..' -X '-DGETTEXT_PACKAGE="xfce4-notes-plugin"' -X '-DPKGDATADIR="../data"' -D DEBUG_XNP_WINDOW --pkg=gtk+-3.0 --pkg=libxfce4util-1.0 --pkg=libxfconf-0 --pkg=color --pkg=config --vapidir=.. --vapidir=. window.vala note.vala hypertextview.vala icon-button.vala */ 1129#if DEBUG_XNP_WINDOW 1130 static int main (string[] args) { 1131 Gtk.init (ref args); 1132 var sample = new Xnp.Window (); 1133 sample.show (); 1134 Gtk.main (); 1135 return 0; 1136 } 1137#endif 1138 } 1139 1140} 1141