Problem
You want to allow users to insert links in a GtkTextView. The user will highlight some text and press the "Insert Link" button. A popup dialog box will appear where the user enters the URL address. On pressing return, the URL address will be registered, and the link highlighted in the standard blue underlined text.
- In Part 1, we have the link displayed as blue underlined text.
- In Part 2, we launch the link in the default browser when the user clicks on the link.
- In Part 3, we display the url address in the status bar when the user hovers the mouse over the link.
- In Part 4, we change the cursor when the user hovers the mouse over the link in the textview.
- In Part 5, we display a tooltip when the user hovers the mouse over the link in the textview.
In this article, we will display a context-senstivie popup menu when there is a right-mouse click on the link in the textview as shown below:
Solution
- We change the cursor using the technique as outlined in the article How to display context sensitive popup menu with right mouse click in GtkTreeView?
Sample Code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 17 20 24 25 26 27 28 29 30 31 32 33 34 41 42 44 45 46 47 50 51 52 53 54 55 56 57 59 60 61 62 63 64 65 66 68 69 70 71 72 73 74 75 76 77 84 85 86 87 88 89 90 91 93 94 95 96 97 99 101 102 103 107 108 109 111 112 113 115 116 117 118 119 120 121 123 124 126 135 136 137 138 139 140 141 142 145 146 148 149 151 154 155 156 160 161 162 163 164 166 167 169 170 171 173 174 175 176 179 180 181 182 183 184 185 186 188 189 192 193 194 195 196 197 198 199 204 205 206 207 208 209 210 211 215 216 217 218 219 220 221 222 223 224 225 227 228 230 231 232 233 234 235 236 237 238 240 244 245 246 247 249 250 251 252 253 255 256 257 258 259 260 261 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 281 282 283 284 285 286 287 288 289 290 292 293 294 295 296 297 298 299 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 346 347 348 349 350 351 352 356 357 358 359 360 361 362 363 364 365 366 | <?php $window = new GtkWindow(); $window->set_size_request(400, 240); $window->connect_simple('destroy', array('Gtk','main_quit')); $window->add($vbox = new GtkVBox()); // display title $title = new GtkLabel("Insert Links in GtkTextView - Part 6\n". "Display Context-Sensitive Menu"); $title->modify_font(new PangoFontDescription("Times New Roman Italic 10")); $title->modify_fg(Gtk::STATE_NORMAL, GdkColor::parse("#0000ff")); $title->set_size_request(-1, 40); $title->set_justify(Gtk::JUSTIFY_CENTER); $alignment = new GtkAlignment(0.5, 0.5, 0, 0); $alignment->add($title); $vbox->pack_start($alignment); // Setup TextBuffer and TextView $tag_count = 0; $buffer = new GtkTextBuffer(); $buffer->set_text('PHP-GTK2 resources: 1) manual: http://gtk.php.net/manual/en/gtkclasses.php 2) mailing list: http://www.nabble.com/Php---GTK---General-f171.html '); $view = new GtkTextView(); $view->set_buffer($buffer); $view->modify_font(new PangoFontDescription("Arial 10")); $view->set_wrap_mode(Gtk::WRAP_WORD); $view->add_events(Gdk::BUTTON_PRESS_MASK); $view->connect('button-press-event', 'on_button_press_in_textview'); // note 1 $buffer->connect('mark-set', 'on_mark_set'); $view->connect('motion-notify-event', 'on_motion_in_textview'); $view->set_events(Gdk::POINTER_MOTION_MASK); $hbox = new GtkHBox(); $hbox->pack_start($button = new GtkButton('Insert Link'), 0); $vbox->pack_start($hbox, 0); $button->connect('clicked', 'on_button', $buffer); $scrolled_win = new GtkScrolledWindow(); $scrolled_win->set_policy( Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); $scrolled_win->add($view); $vbox->pack_start($scrolled_win); // setup status area $status = new GtkStatusbar(); // note 1 $vbox->pack_start($status, 0); $window->show_all(); $standard_cursor = new GdkCursor(Gdk::XTERM); $link_cursor = new GdkCursor(Gdk::DRAFT_LARGE); $tooltips = new TreeviewTooltips(); Gtk::main(); // Setup highlight tag class LinkTag extends GtkTextTag { function __construct($link) { global $buffer; parent::__construct(); $tag_table = $buffer->get_tag_table(); $this->set_property('foreground', "#0000ff"); $this->set_property('underline', Pango::UNDERLINE_SINGLE); $tag_table->add($this); } } function on_button($button, $buffer) { global $view, $tag, $url, $tag_count; $cursor_pos = $buffer->get_mark('insert'); $iter = $buffer->get_iter_at_mark($cursor_pos); list($start, $end) = $buffer->get_selection_bounds(); if ($start==null || $end==null) return; // no selection $url[$tag_count] = prompt("Enter URL:"); $tag[$tag_count] = new LinkTag($url[$tag_count]); $buffer->apply_tag($tag[$tag_count], $start, $end); ++$tag_count; $buffer->place_cursor($end); $view->grab_focus(); } function on_mark_set($buffer, $textiter, $textmark) { global $tag, $url, $tag_count; if ($textmark->get_name()!='insert') return; $cursor_pos = $buffer->get_insert(); $iter = $buffer->get_iter_at_mark($cursor_pos); for ($i=0; $i<$tag_count; ++$i) { if ($iter->has_tag($tag[$i])) { launch_browser($url[$i]); } else { } } return true; } function on_button_press_in_textview($view, $event) { $buffer_location = $view->window_to_buffer_coords (Gtk::TEXT_WINDOW_TEXT, $event->x, $event->y); $iter = $view->get_iter_at_location( $buffer_location[0], $buffer_location[1]); if ($event->button==3) { // note 2 global $tag, $url, $tag_count; global $tooltips; for ($i=0; $i<$tag_count; ++$i) { if ($iter->has_tag($tag[$i])) { $tooltips->on_leave($event); // note 3 popup_menu($event, $url[$i]); // note 4 return true; }# else { } } else { return false; } } function on_motion_in_textview($view, $event) { global $standard_cursor, $link_cursor; $buffer_location = $view->window_to_buffer_coords (Gtk::TEXT_WINDOW_TEXT, $event->x, $event->y); $iter = $view->get_iter_at_location( $buffer_location[0], $buffer_location[1]); if ($iter==null) return; global $tag, $url, $tag_count, $status; for ($i=0; $i<$tag_count; ++$i) { $context_id = $status->get_context_id('msg1'); if ($iter->has_tag($tag[$i])) { $status->pop($context_id); $status->push($context_id, $url[$i]); $view->get_window(Gtk::TEXT_WINDOW_TEXT)->set_cursor($link_cursor); global $tooltips; $tooltips->on_motion($event, $url[$i]); break; } else { $status->pop($context_id); $view->get_window(Gtk::TEXT_WINDOW_TEXT)->set_cursor($standard_cursor); global $tooltips; $tooltips->on_leave($event); } } } //function to prompt for user data function prompt($str) { $prompt = new Prompt($str); $input = $prompt->entry->get_text(); return $input; } class Prompt{ var $entry; // the user input function Prompt($str) { $dialog = new GtkDialog('Prompt', null, Gtk::DIALOG_MODAL); $top_area = $dialog->vbox; $top_area->pack_start($hbox = new GtkHBox()); $stock = GtkImage::new_from_stock(Gtk::STOCK_DIALOG_QUESTION, Gtk::ICON_SIZE_DIALOG); $hbox->pack_start($stock, 0, 0); $hbox->pack_start(new GtkLabel($str)); $this->entry = new GtkEntry(); $hbox->pack_start($this->entry, 0, 0); $hbox->pack_start(new GtkLabel(' '), 0, 0); $dialog->add_button(Gtk::STOCK_OK, Gtk::RESPONSE_OK); $buttons = $dialog->action_area->get_children(); $button_ok = $buttons[0]; // get the ID of the OK button // simulate button click when user press enter $this->entry->connect('activate', array(&$this, 'on_enter'), $button_ok); $dialog->set_has_separator(false); $dialog->show_all(); $dialog->run(); $dialog->destroy(); } // simulate button click when user press enter function on_enter($entry, $button) { $button->clicked(); } } // the class to display tooltips in treeview class TreeviewTooltips { function TreeviewTooltips() { // create the tooltip window $this->tooltip_window = new GtkWindow(Gtk::WINDOW_POPUP); $this->tooltip_window->set_name('gtk-tooltips'); $this->tooltip_window->set_resizable(False); $this->tooltip_window->set_border_width(4); $this->tooltip_window->set_app_paintable(True); $this->tooltip_window->connect('expose-event', array(&$this, 'on_expose_event')); $label = new GtkLabel(''); $label->set_line_wrap(True); $label->set_alignment(0.5, 0.5); $label->set_use_markup(True); $label->show(); $this->tooltip_window->add($label); } function on_motion($event, $msg) { $size = $this->tooltip_window->size_request(); $this->tooltip_window->move($event->x_root - $size->width/2, $event->y_root + 12); $this->tooltip_window->child->set_text($msg); $this->tooltip_window->show(); } function on_leave($event) { $this->tooltip_window->hide(); } function on_expose_event($tooltip_window, $event) { $size = $tooltip_window->size_request(); $tooltip_window->style->paint_flat_box($tooltip_window->window, Gtk::STATE_NORMAL, Gtk::SHADOW_OUT, null, $tooltip_window, 'tooltip', 0, 0, $size->width, $size->height); } } function popup_menu($event, $url) { global $menu; $menu_definition = array('Launch in Browser', '<hr>', 'Copy Link', 'Edit Link', 'Delete Link'); // note 5 $menu = show_popup_menu($menu_definition, $url); } function show_popup_menu($menu_definition, $url) { $menu = new GtkMenu(); foreach($menu_definition as $menuitem_definition) { if ($menuitem_definition=='<hr>') { $menu->append(new GtkSeparatorMenuItem()); } else { $menu_item = new GtkMenuItem($menuitem_definition); $menu->append($menu_item); $menu_item->connect('activate', 'on_popup_menu_select', $url); } } $menu->show_all(); $menu->popup(); } function launch_browser($url) { if (PHP_SHLIB_SUFFIX=='dll') { $shell = new COM('WScript.Shell'); $shell->Run('cmd /c start "" "' . $url . '"', 0, FALSE); unset($shell); } else { exec("firefox $url > /dev/null &"); } } function on_popup_menu_select($menu_item, $url) { $item = $menu_item->child->get_label(); echo "popup menu selected: $item ($url)\n"; if ($item=='Launch in Browser') { launch_browser($url); // note 6 } } ?> |
Output
As shown above.Explanation
We make use of the code in Part 5.
We also make use of the code in How to display context sensitive popup menu with right mouse click in GtkTreeView? to display the popup menu.
What's new here:
- Set up the signal handler for right-mouse click.
- Make sure it's the right-mouse click.
- Hide the tooltip if there's any.
- Display the popup menu.
- The menu definitions.
- Process the menu selected. In this example, we only process "Launch in Broswer".
Note
This example works only on windows. It does not work on Linux. This is because for some reason, GtkTextView does not emit the signal motion-notify-event
in linux. So there's no way you can know if the current mouse location is over a link.
Related Links
- How to insert links in GtkTextView - Part 1 - Show Link?
- How to insert links in GtkTextView - Part 2 - Activate Link?
- How to insert links in GtkTextView - Part 3 - Display Link in Status Bar?
- How to insert links in GtkTextView - Part 4 - Change Cursor over Link?
- How to insert links in GtkTextView - Part 5 - Display Tooltip over Link?
- How to insert links in GtkTextView - Part 7 - Delete Link?
- How to display tooltips in GtkTreeView - Part 1?
- How to change cursor over clickable GtkLabel - Part 1 - using pre defined cursors?
- How to change cursor over clickable GtkLabel - Part 2 - using image file?
- How to change cursor over clickable GtkLabel - Part 3 - using xpm data?
- How to have a status area using GtkStatusbar?
- How to launch external app in winxp without the flashing of cmd window?
Read more...