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 Part 6, display a context-senstivie popup menu when there is a right-mouse click on the link in the textview.
In this article, we will delete a link when the user selects the menu item from the popup menu as shown below:
Solution
- We remove a texttag with GtkTextbuffer::remove_tag().
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 45 46 47 50 51 52 53 54 55 56 57 58 60 61 62 63 64 65 66 67 69 70 71 72 73 74 75 76 77 78 85 86 87 88 89 90 91 92 94 95 96 97 98 100 102 103 104 108 109 110 112 113 114 115 117 118 119 120 121 122 123 124 128 129 130 131 132 134 135 137 138 139 140 141 142 143 145 146 147 148 149 152 153 154 155 156 157 158 159 161 162 165 166 167 168 169 170 171 172 177 178 179 180 181 182 183 184 185 189 190 191 192 193 194 195 196 197 198 199 200 202 203 205 206 207 208 209 210 211 212 213 215 219 220 221 222 224 225 226 227 228 230 231 232 233 234 235 236 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 256 257 258 259 260 261 262 263 264 265 266 268 269 270 271 272 273 274 275 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 325 326 327 328 329 330 331 335 336 337 338 339 340 341 342 343 344 345 346 348 349 350 351 352 353 354 355 356 357 358 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 7\n". "Delete Link"); $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'); $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_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==1 || $event->button==3) { // note 1 global $tag, $url, $tag_count; global $tooltips; for ($i=0; $i<$tag_count; ++$i) { if ($iter->has_tag($tag[$i])) { if ($event->button==1) { launch_browser($url[$i]); } else if ($event->button==3) { $tooltips->on_leave($event); popup_menu($event, $url[$i]); } 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'); $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, $the_url) { $item = $menu_item->child->get_label(); echo "popup menu selected: $item ($the_url)\n"; if ($item=='Launch in Browser') { launch_browser($the_url); } elseif ($item=='Delete Link') { global $buffer, $view, $standard_cursor; global $tag, $url, $tag_count; for ($i=0; $i<$tag_count; ++$i) { if ($url[$i]==$the_url) { $start = $buffer->get_start_iter(); $end = $buffer->get_end_iter(); $buffer->remove_tag($tag[$i], $start, $end); // note 2 $view->get_window( Gtk::TEXT_WINDOW_TEXT)->set_cursor($standard_cursor); unset($url[$i]); // note 3 unset($tag[$i]); // note 3 $tag = array_values($tag); $url = array_values($url); $tag_count = count($url); break; } } } } ?> |
Output
As shown above.Explanation
We make use of the code in Part 6.
What's new here:
- Note that in this example, we used the signal handler
on_button_press_in_textview()
to process both button 1 and button 3. The original signal handleron_mark_set()
is no longer necessary. - Remove the tag from textbuffer.
- Delete the link from the array.
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 6 - Display Context Sensitive Menu?
- 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...