1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23 from constant import DEFAULT_FONT_SIZE, DEFAULT_FONT
24 from contextlib import contextmanager
25 from draw import draw_hlinear
26 from keymap import get_keyevent_name
27 from locales import _
28 from menu import Menu
29 from theme import ui_theme
30 import gobject
31 import gtk
32 import pango
33 import pangocairo
34 from utils import (propagate_expose, cairo_state, color_hex_to_cairo,
35 get_content_size, is_double_click, is_right_button,
36 is_left_button, alpha_color_hex_to_cairo, cairo_disable_antialias)
37
38 -class Entry(gtk.EventBox):
39 '''
40 Entry.
41
42 @undocumented: monitor_entry_content
43 @undocumented: realize_entry
44 @undocumented: key_press_entry
45 @undocumented: handle_key_press
46 @undocumented: handle_key_event
47 @undocumented: expose_entry
48 @undocumented: draw_entry_background
49 @undocumented: draw_entry_text
50 @undocumented: draw_entry_cursor
51 @undocumented: button_press_entry
52 @undocumented: handle_button_press
53 @undocumented: button_release_entry
54 @undocumented: motion_notify_entry
55 @undocumented: focus_in_entry
56 @undocumented: focus_out_entry
57 @undocumented: handle_focus_out
58 @undocumented: move_offsetx_right
59 @undocumented: move_offsetx_left
60 @undocumented: get_index_at_event
61 @undocumented: commit_entry
62 @undocumented: get_content_width
63 @undocumented: get_utf8_string
64 '''
65
66 MOVE_LEFT = 1
67 MOVE_RIGHT = 2
68 MOVE_NONE = 3
69
70 __gsignals__ = {
71 "edit-alarm" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
72 "press-return" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()),
73 "changed" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str,)),
74 "invalid-value" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str,)),
75 }
76
77 - def __init__(self,
78 content="",
79 padding_x=5,
80 padding_y=2,
81 text_color=ui_theme.get_color("entry_text"),
82 text_select_color=ui_theme.get_color("entry_select_text"),
83 background_select_color=ui_theme.get_shadow_color("entry_select_background"),
84 font_size=DEFAULT_FONT_SIZE,
85 ):
86 '''
87 Initialize Entry class.
88
89 @param content: Entry initialize content, default is \"\".
90 @param padding_x: Horizontal padding value, default is 5 pixel.
91 @param padding_y: Vertical padding value, default is 2 pixel.
92 @param text_color: Color of text in normal status.
93 @param text_select_color: Color of text in select status.
94 @param background_select_color: Color of background in select status.
95 @param font_size: Entry font size, default is DEFAULT_FONT_SIZE.
96 '''
97
98 gtk.EventBox.__init__(self)
99 self.set_visible_window(False)
100 self.set_can_focus(True)
101 self.im = gtk.IMMulticontext()
102 self.font_size = font_size
103 self.text_color = text_color
104 self.text_select_color = text_select_color
105 self.background_select_color = background_select_color
106 self.padding_x = padding_x
107 self.padding_y = padding_y
108 self.move_direction = self.MOVE_NONE
109 self.double_click_flag = False
110 self.left_click_flag = False
111 self.left_click_coordindate = None
112 self.drag_start_index = 0
113 self.drag_end_index = 0
114 self.grab_focus_flag = False
115 self.editable_flag = True
116 self.check_text = None
117 self.cursor_visible_flag = True
118 self.right_menu_visible_flag = True
119 self.select_area_visible_flag = True
120
121 self.content = content
122 self.cursor_index = len(self.content)
123 self.select_start_index = self.select_end_index = self.cursor_index
124 self.offset_x = 0
125
126
127 self.keymap = {
128 "Left" : self.move_to_left,
129 "Right" : self.move_to_right,
130 "Home" : self.move_to_start,
131 "End" : self.move_to_end,
132 "BackSpace" : self.backspace,
133 "Delete" : self.delete,
134 "Shift + Left" : self.select_to_left,
135 "Shift + Right" : self.select_to_right,
136 "Shift + Home" : self.select_to_start,
137 "Shift + End" : self.select_to_end,
138 "Ctrl + a" : self.select_all,
139 "Ctrl + x" : self.cut_to_clipboard,
140 "Ctrl + c" : self.copy_to_clipboard,
141 "Ctrl + v" : self.paste_from_clipboard,
142 "Return" : self.press_return}
143
144
145 self.right_menu = Menu(
146 [(None, _("Cut"), self.cut_to_clipboard),
147 (None, _("Copy"), self.copy_to_clipboard),
148 (None, _("Paste"), self.paste_from_clipboard),
149 (None, _("Select all"), self.select_all)],
150 True)
151
152
153 self.connect_after("realize", self.realize_entry)
154 self.connect("key-press-event", self.key_press_entry)
155 self.connect("expose-event", self.expose_entry)
156 self.connect("button-press-event", self.button_press_entry)
157 self.connect("button-release-event", self.button_release_entry)
158 self.connect("motion-notify-event", self.motion_notify_entry)
159 self.connect("focus-in-event", self.focus_in_entry)
160 self.connect("focus-out-event", self.focus_out_entry)
161
162 self.im.connect("commit", lambda im, input_text: self.commit_entry(input_text))
163
164 - def set_editable(self, editable):
165 '''
166 Set entry editable status.
167
168 @param editable: If it is True, entry can edit, else entry not allow edit.
169 '''
170 self.editable_flag = editable
171
172 @contextmanager
174 '''
175 Internal function to monitor entry content.
176 '''
177 old_text = self.get_text()
178 try:
179 yield
180 except Exception, e:
181 print 'monitor_entry_content error %s' % e
182 else:
183 new_text = self.get_text()
184 if self.check_text == None or self.check_text(new_text):
185 if old_text != new_text:
186 self.emit("changed", new_text)
187 else:
188 self.emit("invalid-value", new_text)
189 self.set_text(old_text)
190
191 - def is_editable(self):
192 '''
193 Whether entry is editable.
194
195 @return: Return True if entry editable, else return False.
196 '''
197 if not self.editable_flag:
198 self.emit("edit-alarm")
199
200 return self.editable_flag
201
202 - def set_text(self, text):
203 '''
204 Set entry text.
205
206 @param text: Entry text string.
207 '''
208 if self.is_editable():
209 with self.monitor_entry_content():
210 if text != None:
211 self.content = text
212 self.cursor_index = len(self.content)
213 self.select_start_index = self.select_end_index = self.cursor_index
214
215 text_width = self.get_content_width(self.content)
216 rect = self.get_allocation()
217
218 if text_width > rect.width - self.padding_x * 2 > 0:
219 self.offset_x = text_width - rect.width + self.padding_x * 2
220 else:
221 self.offset_x = 0
222
223 self.queue_draw()
224
225 - def get_text(self):
226 '''
227 Get entry text.
228
229 @return: Return entry text string.
230 '''
231 return self.content
232
233 - def realize_entry(self, widget):
234 '''
235 Internal callback for `realize` signal.
236 '''
237 text_width = self.get_content_width(self.content)
238 rect = self.get_allocation()
239
240 if text_width > rect.width - self.padding_x * 2 > 0:
241 self.offset_x = text_width - rect.width + self.padding_x * 2
242 else:
243 self.offset_x = 0
244
245 - def key_press_entry(self, widget, event):
246 '''
247 Internal callback for `key-press-event` signal.
248 '''
249 self.handle_key_press(widget, event)
250
251 - def handle_key_press(self, widget, event):
252 '''
253 Internal function to handle key press.
254 '''
255
256 input_method_filt = self.im.filter_keypress(event)
257 if not input_method_filt:
258 self.handle_key_event(event)
259
260 return False
261
262 - def handle_key_event(self, event):
263 '''
264 Internal function to handle key event.
265 '''
266 key_name = get_keyevent_name(event)
267
268 if self.keymap.has_key(key_name):
269 self.keymap[key_name]()
270
272 '''
273 Clear entry select status.
274 '''
275 self.select_start_index = self.select_end_index = self.cursor_index
276 self.move_direction = self.MOVE_NONE
277
278 - def move_to_start(self):
279 '''
280 Move cursor to start position of entry.
281 '''
282 self.offset_x = 0
283 self.cursor_index = 0
284
285 self.clear_select_status()
286
287 self.queue_draw()
288
289 - def move_to_end(self):
290 '''
291 Move cursor to end position of entry.
292 '''
293 text_width = self.get_content_width(self.content)
294 rect = self.get_allocation()
295 if text_width > rect.width - self.padding_x * 2 > 0:
296 self.offset_x = text_width - (rect.width - self.padding_x * 2)
297 self.cursor_index = len(self.content)
298
299 self.clear_select_status()
300
301 self.queue_draw()
302
303 - def move_to_left(self):
304 '''
305 Backward cursor one char.
306 '''
307
308 if self.keynav_failed(gtk.DIR_LEFT):
309 self.get_toplevel().set_focus_child(self)
310
311 if self.select_start_index != self.select_end_index:
312 self.cursor_index = self.select_start_index
313 select_start_width = self.get_content_width(self.content[0:self.select_start_index])
314
315 self.clear_select_status()
316
317 if select_start_width < self.offset_x:
318 self.offset_x = select_start_width
319
320 self.queue_draw()
321 elif self.cursor_index > 0:
322 self.cursor_index -= len(self.get_utf8_string(self.content[0:self.cursor_index], -1))
323
324 text_width = self.get_content_width(self.content[0:self.cursor_index])
325 if text_width - self.offset_x < 0:
326 self.offset_x = text_width
327
328 self.queue_draw()
329
330 - def move_to_right(self):
331 '''
332 Forward cursor one char.
333 '''
334
335 if self.keynav_failed(gtk.DIR_RIGHT):
336 self.get_toplevel().set_focus_child(self)
337
338 if self.select_start_index != self.select_end_index:
339 self.cursor_index = self.select_end_index
340 select_end_width = self.get_content_width(self.content[0:self.select_end_index])
341
342 self.clear_select_status()
343
344 rect = self.get_allocation()
345 if select_end_width > self.offset_x + rect.width - self.padding_x * 2:
346 self.offset_x = select_end_width - rect.width + self.padding_x * 2
347
348 self.queue_draw()
349 elif self.cursor_index < len(self.content):
350 self.cursor_index += len(self.content[self.cursor_index::].decode('utf-8')[0].encode('utf-8'))
351
352 text_width = self.get_content_width(self.content[0:self.cursor_index])
353 rect = self.get_allocation()
354 if text_width - self.offset_x > rect.width - self.padding_x * 2:
355 self.offset_x = text_width - (rect.width - self.padding_x * 2)
356
357 self.queue_draw()
358
359 - def backspace(self):
360 '''
361 Do backspace action.
362 '''
363 if self.is_editable():
364 with self.monitor_entry_content():
365 if self.select_start_index != self.select_end_index:
366 self.delete()
367 elif self.cursor_index > 0:
368 old_insert_width = self.get_content_width(self.content[0:self.cursor_index])
369 delete_char = self.get_utf8_string(self.content[0:self.cursor_index], -1)
370 self.cursor_index -= len(delete_char)
371
372 self.content = self.content[0:self.cursor_index] + self.content[self.cursor_index + len(delete_char)::]
373 text_width = self.get_content_width(self.content)
374 insert_width = self.get_content_width(self.content[0:self.cursor_index])
375 rect = self.get_allocation()
376 if text_width < rect.width - self.padding_x * 2:
377 self.offset_x = 0
378 else:
379 self.offset_x += insert_width - old_insert_width
380
381 self.queue_draw()
382
383 - def select_all(self):
384 '''
385 Select all text of entry.
386 '''
387 self.select_start_index = 0
388 self.select_end_index = len(self.content)
389
390 self.queue_draw()
391
393 '''
394 Cut selected text to clipboard.
395 '''
396 if self.select_start_index != self.select_end_index:
397 cut_text = self.content[self.select_start_index:self.select_end_index]
398
399 if self.is_editable():
400 with self.monitor_entry_content():
401 self.delete()
402
403 clipboard = gtk.Clipboard()
404 clipboard.set_text(cut_text)
405
407 '''
408 Copy selected text to clipboard.
409 '''
410 if self.select_start_index != self.select_end_index:
411 cut_text = self.content[self.select_start_index:self.select_end_index]
412
413 clipboard = gtk.Clipboard()
414 clipboard.set_text(cut_text)
415
417 '''
418 Paste text to entry from clipboard.
419 '''
420 if self.is_editable():
421 with self.monitor_entry_content():
422 clipboard = gtk.Clipboard()
423 clipboard.request_text(lambda clipboard, text, data: self.commit_entry('\\n'.join(text.split('\n'))))
424
425 - def press_return(self):
426 '''
427 Do return action.
428 '''
429 self.emit("press-return")
430
431 - def select_to_left(self):
432 '''
433 Select text to left char.
434 '''
435 if self.select_start_index != self.select_end_index:
436 if self.move_direction == self.MOVE_LEFT:
437 if self.select_start_index > 0:
438 self.select_start_index -= len(self.get_utf8_string(self.content[0:self.select_start_index], -1))
439 select_start_width = self.get_content_width(self.content[0:self.select_start_index])
440 if select_start_width < self.offset_x:
441 self.offset_x = select_start_width
442 else:
443 self.select_end_index -= len(self.get_utf8_string(self.content[0:self.select_end_index], -1))
444
445 select_end_width = self.get_content_width(self.content[0:self.select_end_index])
446 if select_end_width < self.offset_x:
447 self.offset_x = select_end_width
448 else:
449 self.select_end_index = self.cursor_index
450 self.select_start_index = self.cursor_index - len(self.get_utf8_string(self.content[0:self.cursor_index], -1))
451 self.move_direction = self.MOVE_LEFT
452
453 self.queue_draw()
454
455 - def select_to_right(self):
456 '''
457 Select text to right char.
458 '''
459 if self.select_start_index != self.select_end_index:
460 rect = self.get_allocation()
461
462 if self.move_direction == self.MOVE_RIGHT:
463 if self.select_end_index < len(self.content):
464 self.select_end_index += len(self.get_utf8_string(self.content[self.select_end_index::], 0))
465
466 select_end_width = self.get_content_width(self.content[0:self.select_end_index])
467 if select_end_width > self.offset_x + rect.width - self.padding_x * 2:
468 self.offset_x = select_end_width - rect.width + self.padding_x * 2
469 else:
470 self.select_start_index += len(self.get_utf8_string(self.content[self.select_start_index::], 0))
471 select_start_width = self.get_content_width(self.content[0:self.select_start_index])
472 if select_start_width > self.offset_x + rect.width - self.padding_x * 2:
473 self.offset_x = select_start_width - rect.width + self.padding_x * 2
474 else:
475 if self.select_end_index < len(self.content):
476 self.select_start_index = self.cursor_index
477 self.select_end_index = self.cursor_index + len(self.get_utf8_string(self.content[self.select_end_index::], 0))
478 self.move_direction = self.MOVE_RIGHT
479
480 self.queue_draw()
481
482 - def select_to_start(self):
483 '''
484 Select text to start position.
485 '''
486 if self.select_start_index != self.select_end_index:
487 if self.move_direction == self.MOVE_LEFT:
488 self.select_start_index = 0
489 else:
490 self.select_end_index = self.select_start_index
491 self.select_start_index = 0
492
493 self.move_direction = self.MOVE_LEFT
494 else:
495 self.select_start_index = 0
496 self.select_end_index = self.cursor_index
497
498 self.move_direction = self.MOVE_LEFT
499
500 self.offset_x = 0
501
502 self.queue_draw()
503
504 - def select_to_end(self):
505 '''
506 Select text to end position.
507 '''
508 if self.select_start_index != self.select_end_index:
509 if self.move_direction == self.MOVE_RIGHT:
510 self.select_end_index = len(self.content)
511 else:
512 self.select_start_index = self.select_end_index
513 self.select_end_index = len(self.content)
514
515 self.move_direction = self.MOVE_RIGHT
516 else:
517 self.select_start_index = self.cursor_index
518 self.select_end_index = len(self.content)
519
520 self.move_direction = self.MOVE_RIGHT
521
522 rect = self.get_allocation()
523 select_end_width = self.get_content_width(self.content)
524 if select_end_width > self.offset_x + rect.width - self.padding_x * 2:
525 self.offset_x = select_end_width - rect.width + self.padding_x * 2
526 else:
527 self.offset_x = 0
528
529 self.queue_draw()
530
532 '''
533 Delete selected text.
534 '''
535 if self.is_editable() and self.select_start_index != self.select_end_index:
536 with self.monitor_entry_content():
537 rect = self.get_allocation()
538
539 self.cursor_index = self.select_start_index
540
541 select_start_width = self.get_content_width(self.content[0:self.select_start_index])
542 select_end_width = self.get_content_width(self.content[0:self.select_end_index])
543
544 self.cursor_index = self.select_start_index
545 if select_start_width < self.offset_x:
546 if select_end_width < self.offset_x + rect.width - self.padding_x * 2:
547 self.offset_x = max(select_start_width + self.offset_x - select_end_width, 0)
548 else:
549 self.offset_x = 0
550
551 self.content = self.content[0:self.select_start_index] + self.content[self.select_end_index::]
552
553 self.select_start_index = self.select_end_index = self.cursor_index
554
555 self.queue_draw()
556
557 - def expose_entry(self, widget, event):
558 '''
559 Internal callback for `expose-event` signal.
560 '''
561
562 cr = widget.window.cairo_create()
563 rect = widget.allocation
564
565
566 if self.get_sensitive():
567 self.draw_entry_background(cr, rect)
568
569
570 self.draw_entry_text(cr, rect)
571
572
573 if self.cursor_visible_flag and self.get_sensitive():
574 self.draw_entry_cursor(cr, rect)
575
576
577 propagate_expose(widget, event)
578
579 return True
580
581 - def draw_entry_background(self, cr, rect):
582 '''
583 Internal function to draw entry background.
584 '''
585 x, y, w, h = rect.x, rect.y, rect.width, rect.height
586
587 if self.select_start_index != self.select_end_index and self.select_area_visible_flag:
588 select_start_width = self.get_content_width(self.content[0:self.select_start_index])
589 select_end_width = self.get_content_width(self.content[0:self.select_end_index])
590
591 draw_hlinear(cr,
592 x + self.padding_x + max(select_start_width - self.offset_x, 0),
593 y + self.padding_y,
594 min(select_end_width, self.offset_x + w - self.padding_x * 2) - max(select_start_width, self.offset_x),
595 h - self.padding_y * 2,
596 self.background_select_color.get_color_info())
597
598 - def draw_entry_text(self, cr, rect):
599 '''
600 Internal function to draw entry text.
601 '''
602 x, y, w, h = rect.x, rect.y, rect.width, rect.height
603 with cairo_state(cr):
604
605 draw_x = x + self.padding_x
606 draw_y = y + self.padding_y
607 draw_width = w - self.padding_x * 2
608 draw_height = h - self.padding_y * 2
609 cr.rectangle(draw_x, draw_y, draw_width, draw_height)
610 cr.clip()
611
612
613 context = pangocairo.CairoContext(cr)
614
615
616 layout = context.create_layout()
617 layout.set_font_description(pango.FontDescription("%s %s" % (DEFAULT_FONT, self.font_size)))
618
619 if not self.get_sensitive():
620
621 layout.set_text(self.content)
622
623
624 (text_width, text_height) = layout.get_pixel_size()
625
626
627 cr.move_to(draw_x - self.offset_x,
628 draw_y + (draw_height - text_height) / 2)
629
630
631 cr.set_source_rgb(*color_hex_to_cairo(ui_theme.get_color("disable_text").get_color()))
632 context.update_layout(layout)
633 context.show_layout(layout)
634 elif self.select_start_index != self.select_end_index and self.select_area_visible_flag:
635
636 before_select_str = self.content[0:self.select_start_index]
637 select_str = self.content[self.select_start_index:self.select_end_index]
638 after_select_str = self.content[self.select_end_index::]
639
640
641 render_list = []
642
643 layout.set_text(before_select_str)
644 (before_select_width, before_select_height) = layout.get_pixel_size()
645 render_list.append((before_select_str, 0, before_select_height, self.text_color))
646
647 layout.set_text(select_str)
648 (select_width, select_height) = layout.get_pixel_size()
649 render_list.append((select_str, before_select_width, select_height, self.text_select_color))
650
651 layout.set_text(after_select_str)
652 (after_select_width, after_select_height) = layout.get_pixel_size()
653 render_list.append((after_select_str, before_select_width + select_width, after_select_height, self.text_color))
654
655
656 for (string, offset, text_height, text_color) in render_list:
657 layout.set_text(string)
658 cr.move_to(draw_x - self.offset_x + offset,
659 draw_y + (draw_height - text_height) / 2)
660 cr.set_source_rgb(*color_hex_to_cairo(text_color.get_color()))
661 context.update_layout(layout)
662 context.show_layout(layout)
663 else:
664
665 layout.set_text(self.content)
666
667
668 (text_width, text_height) = layout.get_pixel_size()
669
670
671 cr.move_to(draw_x - self.offset_x,
672 draw_y + (draw_height - text_height) / 2)
673
674
675 cr.set_source_rgb(*color_hex_to_cairo(self.text_color.get_color()))
676 context.update_layout(layout)
677 context.show_layout(layout)
678
679 - def draw_entry_cursor(self, cr, rect):
680 '''
681 Internal function to draw entry cursor.
682 '''
683 if self.grab_focus_flag and self.select_start_index == self.select_end_index:
684
685 x, y, w, h = rect.x, rect.y, rect.width, rect.height
686 left_str = self.content[0:self.cursor_index]
687 left_str_width = self.get_content_width(left_str)
688 padding_y = (h - (get_content_size("Height", self.font_size)[-1])) / 2
689
690
691 cr.set_source_rgb(*color_hex_to_cairo(ui_theme.get_color("entry_cursor").get_color()))
692 cr.rectangle(x + self.padding_x + left_str_width - self.offset_x,
693 y + padding_y,
694 1,
695 h - padding_y * 2
696 )
697 cr.fill()
698
700 '''
701 Internal callback for `button-press-event` signal.
702 '''
703 self.handle_button_press(widget, event)
704
706 '''
707 Internal function to handle button press.
708 '''
709
710 self.grab_focus()
711
712
713 self.right_menu.hide()
714
715
716 if is_double_click(event):
717 self.double_click_flag = True
718 self.select_all()
719
720 elif is_right_button(event):
721 if self.right_menu_visible_flag:
722 (wx, wy) = self.window.get_root_origin()
723 (cx, cy, modifier) = self.window.get_pointer()
724 self.right_menu.show((cx + wx, cy + wy))
725
726 elif is_left_button(event):
727 self.left_click_flag = True
728 self.left_click_coordindate = (event.x, event.y)
729
730 self.drag_start_index = self.get_index_at_event(widget, event)
731
733 '''
734 Internal callback for `button-release-event` signal.
735 '''
736 if not self.double_click_flag and self.left_click_coordindate == (event.x, event.y):
737 self.cursor_index = self.get_index_at_event(widget, event)
738 self.select_start_index = self.select_end_index = self.cursor_index
739 self.queue_draw()
740
741 self.double_click_flag = False
742 self.left_click_flag = False
743
744 - def motion_notify_entry(self, widget, event):
745 '''
746 Internal callback for `motion-notify-event` signal.
747 '''
748 if not self.double_click_flag and self.left_click_flag:
749 self.cursor_index = self.drag_start_index
750 self.drag_end_index = self.get_index_at_event(widget, event)
751
752 self.select_start_index = min(self.drag_start_index, self.drag_end_index)
753 self.select_end_index = max(self.drag_start_index, self.drag_end_index)
754
755 if self.drag_start_index < self.drag_end_index:
756 rect = self.get_allocation()
757 if int(event.x) > rect.width:
758 self.move_offsetx_right(widget, event)
759 else:
760 if int(event.x) < 0:
761 self.move_offsetx_left(widget, event)
762
763 self.queue_draw()
764
765 - def focus_in_entry(self, widget, event):
766 '''
767 Internal callback for `focus-in-event` signal.
768 '''
769 self.grab_focus_flag = True
770
771
772 self.im.set_client_window(widget.window)
773 self.im.focus_in()
774
775 self.queue_draw()
776
777 - def focus_out_entry(self, widget, event):
778 '''
779 Internal callback for `focus-out-event` signal.
780 '''
781 self.handle_focus_out(widget, event)
782
783 - def handle_focus_out(self, widget, event):
784 '''
785 Internal function to handle focus out.
786 '''
787 self.grab_focus_flag = False
788
789
790 self.im.focus_out()
791
792 self.queue_draw()
793
794 - def move_offsetx_right(self, widget, event):
795 '''
796 Internal function to move offset_x to right.
797 '''
798 text_width = self.get_content_width(self.content)
799 rect = self.get_allocation()
800 if self.offset_x + rect.width - self.padding_x * 2 < text_width:
801 cr = widget.window.cairo_create()
802 context = pangocairo.CairoContext(cr)
803 layout = context.create_layout()
804 layout.set_font_description(pango.FontDescription("%s %s" % (DEFAULT_FONT, self.font_size)))
805 layout.set_text(self.content)
806 (text_width, text_height) = layout.get_pixel_size()
807 (x_index, y_index) = layout.xy_to_index((self.offset_x + rect.width - self.padding_x * 2) * pango.SCALE, 0)
808
809 self.offset_x += len(self.get_utf8_string(self.content[x_index::], 0))
810
811 - def move_offsetx_left(self, widget, event):
812 '''
813 Internal function to move offset_x to left.
814 '''
815 if self.offset_x > 0:
816 cr = widget.window.cairo_create()
817 context = pangocairo.CairoContext(cr)
818 layout = context.create_layout()
819 layout.set_font_description(pango.FontDescription("%s %s" % (DEFAULT_FONT, self.font_size)))
820 layout.set_text(self.content)
821 (text_width, text_height) = layout.get_pixel_size()
822 (x_index, y_index) = layout.xy_to_index((self.offset_x + self.padding_x) * pango.SCALE, 0)
823
824 self.offset_x -= len(self.get_utf8_string(self.content[0:x_index], -1))
825
826 - def get_index_at_event(self, widget, event):
827 '''
828 Internal function to get index at event.
829 '''
830 cr = widget.window.cairo_create()
831 context = pangocairo.CairoContext(cr)
832 layout = context.create_layout()
833 layout.set_font_description(pango.FontDescription("%s %s" % (DEFAULT_FONT, self.font_size)))
834 layout.set_text(self.content)
835 (text_width, text_height) = layout.get_pixel_size()
836 if int(event.x) + self.offset_x - self.padding_x > text_width:
837 return len(self.content)
838 else:
839 (x_index, y_index) = layout.xy_to_index((int(event.x) + self.offset_x - self.padding_x) * pango.SCALE, 0)
840 return x_index
841
842 - def commit_entry(self, input_text):
843 '''
844 Internal callback for `commit` signal.
845 '''
846 if self.is_editable():
847 with self.monitor_entry_content():
848 if self.select_start_index != self.select_end_index:
849 self.delete()
850
851 self.content = self.content[0:self.cursor_index] + input_text + self.content[self.cursor_index::]
852 self.cursor_index += len(input_text)
853
854 text_width = self.get_content_width(self.content)
855 rect = self.get_allocation()
856 if text_width <= rect.width - self.padding_x * 2:
857 self.offset_x = 0
858 elif self.cursor_index == len(self.content):
859 self.offset_x = text_width - (rect.width - self.padding_x * 2)
860 else:
861 old_text_width = self.get_content_width(self.content[0:self.cursor_index - len(input_text)])
862 input_text_width = self.get_content_width(input_text)
863 if old_text_width - self.offset_x + input_text_width > rect.width - self.padding_x * 2:
864 new_text_width = self.get_content_width(self.content[0:self.cursor_index])
865 self.offset_x = new_text_width - (rect.width - self.padding_x * 2)
866
867 self.queue_draw()
868
869 - def get_content_width(self, content):
870 '''
871 Internal function to get content width.
872 '''
873 (content_width, content_height) = get_content_size(content, self.font_size)
874 return content_width
875
876 - def get_utf8_string(self, content, index):
877 '''
878 Internal to get utf8 string.
879 '''
880 try:
881 return list(content.decode('utf-8'))[index].encode('utf-8')
882 except Exception, e:
883 print "get_utf8_string got error: %s" % (e)
884 return ""
885
886 gobject.type_register(Entry)
887
888 -class TextEntry(gtk.VBox):
889 '''
890 Text entry.
891
892 @undocumented: set_sensitive
893 @undocumented: emit_action_active_signal
894 @undocumented: expose_text_entry
895 '''
896
897 __gsignals__ = {
898 "action-active" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str,)),
899 }
900
901 - def __init__(self,
902 content="",
903 action_button=None,
904 background_color = ui_theme.get_alpha_color("text_entry_background"),
905 acme_color = ui_theme.get_alpha_color("text_entry_acme"),
906 point_color = ui_theme.get_alpha_color("text_entry_point"),
907 frame_point_color = ui_theme.get_alpha_color("text_entry_frame_point"),
908 frame_color = ui_theme.get_alpha_color("text_entry_frame"),
909 ):
910 '''
911 Initialize InputEntry class.
912
913 @param content: Initialize entry text, default is \"\".
914 @param action_button: Extra button add at right side of text entry, default is None.
915 @param background_color: Color of text entry background.
916 @param acme_color: Acme point color of text entry.
917 @param point_color: Pointer color of text entry.
918 @param frame_point_color: Frame pointer color of text entry.
919 @param frame_color: Frame color of text entry.
920 '''
921
922 gtk.VBox.__init__(self)
923 self.align = gtk.Alignment()
924 self.align.set(0.5, 0.5, 1.0, 1.0)
925 self.action_button = action_button
926 self.h_box = gtk.HBox()
927 self.entry = Entry(content)
928 self.background_color = background_color
929 self.acme_color = acme_color
930 self.point_color = point_color
931 self.frame_point_color = frame_point_color
932 self.frame_color = frame_color
933
934 self.pack_start(self.align, False, False)
935 self.align.add(self.h_box)
936 self.h_box.pack_start(self.entry)
937 if action_button:
938 self.action_align = gtk.Alignment()
939 self.action_align.set(0.0, 0.5, 0, 0)
940 self.action_align.set_padding(0, 0, 0, self.entry.padding_x)
941 self.action_align.add(self.action_button)
942
943 self.h_box.pack_start(self.action_align, False, False)
944
945 self.action_button.connect("clicked", lambda w: self.emit_action_active_signal())
946
947
948 self.align.connect("expose-event", self.expose_text_entry)
949
950 - def set_sensitive(self, sensitive):
951 '''
952 Internal function to wrap function `set_sensitive`.
953 '''
954 super(TextEntry, self).set_sensitive(sensitive)
955 self.entry.set_sensitive(sensitive)
956
958 '''
959 Internal callback for `action-active` signal.
960 '''
961 self.emit("action-active", self.get_text())
962
963 - def expose_text_entry(self, widget, event):
964 '''
965 Internal callback for `expose-event` signal.
966 '''
967
968 cr = widget.window.cairo_create()
969 rect = widget.allocation
970 x, y, w, h = rect.x, rect.y, rect.width, rect.height
971
972
973 with cairo_state(cr):
974 cr.rectangle(x + 2, y, w - 4, 1)
975 cr.rectangle(x + 1, y + 1, w - 2, 1)
976 cr.rectangle(x, y + 2, w, h - 4)
977 cr.rectangle(x + 2, y + h - 1, w - 4, 1)
978 cr.rectangle(x + 1, y + h - 2, w - 2, 1)
979 cr.clip()
980
981 cr.set_source_rgba(*alpha_color_hex_to_cairo(self.background_color.get_color_info()))
982 cr.rectangle(x, y, w, h)
983 cr.fill()
984
985
986 cr.set_source_rgba(*alpha_color_hex_to_cairo(self.acme_color.get_color_info()))
987 cr.rectangle(x, y, 1, 1)
988 cr.rectangle(x + w - 1, y, 1, 1)
989 cr.rectangle(x, y + h - 1, 1, 1)
990 cr.rectangle(x + w - 1, y + h - 1, 1, 1)
991 cr.fill()
992
993
994 cr.set_source_rgba(*alpha_color_hex_to_cairo(self.point_color.get_color_info()))
995
996 cr.rectangle(x + 1, y, 1, 1)
997 cr.rectangle(x, y + 1, 1, 1)
998
999 cr.rectangle(x + w - 2, y, 1, 1)
1000 cr.rectangle(x + w - 1, y + 1, 1, 1)
1001
1002 cr.rectangle(x, y + h - 2, 1, 1)
1003 cr.rectangle(x + 1, y + h - 1, 1, 1)
1004
1005 cr.rectangle(x + w - 1, y + h - 2, 1, 1)
1006 cr.rectangle(x + w - 2, y + h - 1, 1, 1)
1007
1008 cr.fill()
1009
1010
1011 cr.set_source_rgba(*alpha_color_hex_to_cairo(self.frame_point_color.get_color_info()))
1012
1013 cr.rectangle(x + 1, y, 1, 1)
1014 cr.rectangle(x, y + 1, 1, 1)
1015
1016 cr.rectangle(x + w - 2, y, 1, 1)
1017 cr.rectangle(x + w - 1, y + 1, 1, 1)
1018
1019 cr.rectangle(x, y + h - 2, 1, 1)
1020 cr.rectangle(x + 1, y + h - 1, 1, 1)
1021
1022 cr.rectangle(x + w - 1, y + h - 2, 1, 1)
1023 cr.rectangle(x + w - 2, y + h - 1, 1, 1)
1024
1025 cr.fill()
1026
1027
1028 cr.set_source_rgba(*alpha_color_hex_to_cairo(self.frame_color.get_color_info()))
1029
1030 cr.rectangle(x + 2, y, w - 4, 1)
1031 cr.rectangle(x, y + 2, 1, h - 4)
1032 cr.rectangle(x + 2, y + h - 1, w - 4, 1)
1033 cr.rectangle(x + w - 1, y + 2, 1, h - 4)
1034
1035 cr.fill()
1036
1037 propagate_expose(widget, event)
1038
1039 return True
1040
1041 - def set_size(self, width, height):
1042 '''
1043 Set text entry size with given value.
1044
1045 @param width: New width of text entry.
1046 @param height: New height of text entry.
1047 '''
1048 self.set_size_request(width, height)
1049
1050 action_button_width = 0
1051 if self.action_button:
1052 action_button_width = self.action_button.get_size_request()[-1] + self.entry.padding_x
1053
1054 self.entry.set_size_request(width - 2 - action_button_width, height - 2)
1055
1056 - def set_editable(self, editable):
1057 '''
1058 Set editable status of text entry.
1059
1060 @param editable: Text entry can editable if option is True, else can't edit.
1061 '''
1062 self.entry.set_editable(editable)
1063
1064 - def set_text(self, text):
1065 '''
1066 Set text of text entry.
1067
1068 @param text: Text entry string.
1069 '''
1070 self.entry.set_text(text)
1071
1072 - def get_text(self):
1073 '''
1074 Get text of text entry.
1075
1076 @return: Return text of text entry.
1077 '''
1078 return self.entry.get_text()
1079
1081 '''
1082 Focus input cursor.
1083 '''
1084 self.entry.grab_focus()
1085
1086 gobject.type_register(TextEntry)
1089 '''
1090 Text entry.
1091
1092 Generically speaking, InputEntry is similar L{ I{TextEntry} <TextEntry>},
1093
1094 only difference between two class is ui style, internal logic is same.
1095
1096 @undocumented: set_sensitive
1097 @undocumented: emit_action_active_signal
1098 @undocumented: expose_input_entry
1099 '''
1100
1101 __gsignals__ = {
1102 "action-active" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str,)),
1103 }
1104
1114 '''
1115
1116 Initialize InputEntry class.
1117
1118 @param content: Initialize entry text, default is \"\".
1119 @param action_button: Extra button add at right side of input entry, default is None.
1120 @param background_color: Color of input entry background.
1121 @param acme_color: Acme point color of input entry.
1122 @param point_color: Pointer color of input entry.
1123 @param frame_point_color: Frame pointer color of input entry.
1124 @param frame_color: Frame color of input entry.
1125 '''
1126
1127 gtk.VBox.__init__(self)
1128 self.align = gtk.Alignment()
1129 self.align.set(0.5, 0.5, 1.0, 1.0)
1130 self.action_button = action_button
1131 self.h_box = gtk.HBox()
1132 self.entry = Entry(content)
1133 self.background_color = background_color
1134 self.acme_color = acme_color
1135 self.point_color = point_color
1136 self.frame_point_color = frame_point_color
1137 self.frame_color = frame_color
1138
1139 self.pack_start(self.align, False, False)
1140 self.align.add(self.h_box)
1141 self.h_box.pack_start(self.entry)
1142 if action_button:
1143 self.action_align = gtk.Alignment()
1144 self.action_align.set(0.0, 0.5, 0, 0)
1145 self.action_align.set_padding(0, 0, 0, self.entry.padding_x)
1146 self.action_align.add(self.action_button)
1147
1148 self.h_box.pack_start(self.action_align, False, False)
1149
1150 self.action_button.connect("clicked", lambda w: self.emit_action_active_signal())
1151
1152
1153 self.align.connect("expose-event", self.expose_input_entry)
1154
1156 '''
1157 Internal function to wrap function `set_sensitive`.
1158 '''
1159 super(InputEntry, self).set_sensitive(sensitive)
1160 self.entry.set_sensitive(sensitive)
1161
1163 '''
1164 Internal callback for `action-active` signal.
1165 '''
1166 self.emit("action-active", self.get_text())
1167
1169 '''
1170 Internal callback for `expose-event` signal.
1171 '''
1172
1173 cr = widget.window.cairo_create()
1174 rect = widget.allocation
1175 x, y, w, h = rect.x, rect.y, rect.width, rect.height
1176
1177
1178 with cairo_disable_antialias(cr):
1179 cr.set_line_width(1)
1180 cr.set_source_rgb(*color_hex_to_cairo(ui_theme.get_color("combo_entry_frame").get_color()))
1181 cr.rectangle(rect.x, rect.y, rect.width, rect.height)
1182 cr.stroke()
1183
1184 cr.set_source_rgba(*alpha_color_hex_to_cairo((ui_theme.get_color("combo_entry_background").get_color(), 0.9)))
1185 cr.rectangle(rect.x, rect.y, rect.width - 1, rect.height - 1)
1186 cr.fill()
1187
1188 propagate_expose(widget, event)
1189
1190 return True
1191
1193 '''
1194 Set input entry size with given value.
1195
1196 @param width: New width of input entry.
1197 @param height: New height of input entry.
1198 '''
1199 self.set_size_request(width, height)
1200
1201 action_button_width = 0
1202 if self.action_button:
1203 action_button_width = self.action_button.get_size_request()[-1] + self.entry.padding_x
1204
1205 self.entry.set_size_request(width - 2 - action_button_width, height - 2)
1206
1208 '''
1209 Set editable status of input entry.
1210
1211 @param editable: input entry can editable if option is True, else can't edit.
1212 '''
1213 self.entry.set_editable(editable)
1214
1216 '''
1217 Set text of input entry.
1218
1219 @param text: input entry string.
1220 '''
1221 self.entry.set_text(text)
1222
1224 '''
1225 Get text of input entry.
1226
1227 @return: Return text of input entry.
1228 '''
1229 return self.entry.get_text()
1230
1232 '''
1233 Focus input cursor.
1234 '''
1235 self.entry.grab_focus()
1236
1237 gobject.type_register(InputEntry)
1238
1239 -class ShortcutKeyEntry(gtk.VBox):
1240 '''
1241 Shortcut key entry.
1242
1243 @undocumented: set_sensitive
1244 @undocumented: emit_action_active_signal
1245 @undocumented: expose_shortcutkey_entry
1246 @undocumented: handle_focus_out
1247 @undocumented: handle_key_press
1248 '''
1249
1250 __gsignals__ = {
1251 "action-active" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str,)),
1252 "wait-key-input" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str,)),
1253 "shortcut-key-change" : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (str,)),
1254 }
1255
1256 - def __init__(self,
1257 content="",
1258 action_button=None,
1259 background_color = ui_theme.get_alpha_color("text_entry_background"),
1260 acme_color = ui_theme.get_alpha_color("text_entry_acme"),
1261 point_color = ui_theme.get_alpha_color("text_entry_point"),
1262 frame_point_color = ui_theme.get_alpha_color("text_entry_frame_point"),
1263 frame_color = ui_theme.get_alpha_color("text_entry_frame"),
1264 ):
1265 '''
1266 Initialize ShortcutKeyEntry class.
1267
1268 @param content: Initialize entry text, default is \"\".
1269 @param action_button: Extra button add at right side of shortcutkey entry, default is None.
1270 @param background_color: Color of shortcutkey entry background.
1271 @param acme_color: Acme point color of shortcutkey entry.
1272 @param point_color: Pointer color of shortcutkey entry.
1273 @param frame_point_color: Frame pointer color of shortcutkey entry.
1274 @param frame_color: Frame color of shortcutkey entry.
1275 '''
1276
1277 gtk.VBox.__init__(self)
1278 self.align = gtk.Alignment()
1279 self.align.set(0.5, 0.5, 1.0, 1.0)
1280 self.action_button = action_button
1281 self.h_box = gtk.HBox()
1282 self.entry = Entry(content)
1283 self.background_color = background_color
1284 self.acme_color = acme_color
1285 self.point_color = point_color
1286 self.frame_point_color = frame_point_color
1287 self.frame_color = frame_color
1288
1289 self.pack_start(self.align, False, False)
1290 self.align.add(self.h_box)
1291 self.h_box.pack_start(self.entry)
1292 if action_button:
1293 self.action_align = gtk.Alignment()
1294 self.action_align.set(0.0, 0.5, 0, 0)
1295 self.action_align.set_padding(0, 0, 0, self.entry.padding_x)
1296 self.action_align.add(self.action_button)
1297
1298 self.h_box.pack_start(self.action_align)
1299
1300 self.action_button.connect("clicked", lambda w: self.emit_action_active_signal())
1301
1302
1303 self.align.connect("expose-event", self.expose_shortcutkey_entry)
1304
1305
1306 self.entry.cursor_visible_flag = False
1307 self.entry.right_menu_visible_flag = False
1308 self.entry.select_area_visible_flag = False
1309 self.entry.editable_flag = False
1310
1311
1312 self.entry.handle_button_press = self.handle_button_press
1313 self.entry.handle_focus_out = self.handle_focus_out
1314 self.entry.handle_key_press = self.handle_key_press
1315
1316 self.shortcut_key = content
1317 self.shortcut_key_record = None
1318
1319 - def set_sensitive(self, sensitive):
1320 '''
1321 Internal function to wrap function `set_sensitive`.
1322 '''
1323 super(ShortcutKeyEntry, self).set_sensitive(sensitive)
1324 self.entry.set_sensitive(sensitive)
1325
1327 '''
1328 Internal callback for `action-active` signal.
1329 '''
1330
1331 self.entry.grab_focus()
1332 self.shortcut_key_record = self.shortcut_key
1333
1334 if is_left_button(event):
1335 self.entry.editable_flag = True
1336 self.emit("wait-key-input", self.shortcut_key)
1337 self.set_text(_("Please input new shortcuts"))
1338 self.entry.editable_flag = False
1339
1340 self.entry.queue_draw()
1341
1342 - def handle_focus_out(self, widget, event):
1343 '''
1344 Internal function to handle focus out.
1345 '''
1346 if self.shortcut_key != None:
1347 self.entry.editable_flag = True
1348 self.set_text(self.shortcut_key)
1349 self.entry.editable_flag = False
1350
1351 self.entry.grab_focus_flag = False
1352 self.entry.im.focus_out()
1353 self.entry.queue_draw()
1354
1355 if self.shortcut_key != self.shortcut_key_record:
1356 self.emit("shortcut-key-change", self.shortcut_key)
1357 self.shortcut_key_record = None
1358
1359 - def handle_key_press(self, widget, event):
1360 '''
1361 Internal function to handle key press.
1362 '''
1363 keyname = get_keyevent_name(event)
1364 if keyname != "":
1365 if keyname == "BackSpace":
1366 self.set_shortcut_key(None)
1367 elif keyname != "":
1368 self.set_shortcut_key(keyname)
1369
1370 - def set_shortcut_key(self, shortcut_key):
1371 '''
1372 Set shortcut key.
1373
1374 @param shortcut_key: Key string that return by function `dtk.ui.keymap.get_keyevent_name`.
1375 '''
1376 self.shortcut_key = shortcut_key
1377
1378 self.entry.editable_flag = True
1379 if self.shortcut_key == None:
1380 self.set_text(_("Disabled"))
1381 else:
1382 self.set_text(self.shortcut_key)
1383 self.entry.editable_flag = False
1384
1385 - def get_shortcut_key(self):
1386 '''
1387 Get shortcut key.
1388
1389 @return: Return shortcut key string, string format look function `dtk.ui.keymap.get_keyevent_name`.
1390 '''
1391 return self.shortcut_key
1392
1394 '''
1395 Internal callback for `action-active` signal.
1396 '''
1397 self.emit("action-active", self.get_text())
1398
1399 - def expose_shortcutkey_entry(self, widget, event):
1400 '''
1401 Internal callback for `expose-event` signal.
1402 '''
1403
1404 cr = widget.window.cairo_create()
1405 rect = widget.allocation
1406 x, y, w, h = rect.x, rect.y, rect.width, rect.height
1407
1408
1409 with cairo_disable_antialias(cr):
1410 cr.set_line_width(1)
1411 cr.set_source_rgb(*color_hex_to_cairo(ui_theme.get_color("combo_entry_frame").get_color()))
1412 cr.rectangle(rect.x, rect.y, rect.width, rect.height)
1413 cr.stroke()
1414
1415 cr.set_source_rgba(*alpha_color_hex_to_cairo((ui_theme.get_color("combo_entry_background").get_color(), 0.9)))
1416 cr.rectangle(rect.x, rect.y, rect.width - 1, rect.height - 1)
1417 cr.fill()
1418
1419 propagate_expose(widget, event)
1420
1421 return True
1422
1423 - def set_size(self, width, height):
1424 '''
1425 Set shortcutkey entry size with given value.
1426
1427 @param width: New width of shortcutkey entry.
1428 @param height: New height of shortcutkey entry.
1429 '''
1430 self.set_size_request(width, height)
1431
1432 action_button_width = 0
1433 if self.action_button:
1434 action_button_width = self.action_button.get_size_request()[-1] + self.entry.padding_x
1435
1436 self.entry.set_size_request(width - 2 - action_button_width, height - 2)
1437
1438 - def set_editable(self, editable):
1439 '''
1440 Set editable status of shortcutkey entry.
1441
1442 @param editable: shortcutkey entry can editable if option is True, else can't edit.
1443 '''
1444 self.entry.set_editable(editable)
1445
1446 - def set_text(self, text):
1447 '''
1448 Set text of shortcutkey entry.
1449
1450 @param text: shortcutkey entry string.
1451 '''
1452 self.entry.set_text(text)
1453
1454 - def get_text(self):
1455 '''
1456 Get text of shortcutkey entry.
1457
1458 @return: Return text of shortcutkey entry.
1459 '''
1460 return self.entry.get_text()
1461
1463 '''
1464 Focus input cursor.
1465 '''
1466 self.entry.grab_focus()
1467
1468 gobject.type_register(ShortcutKeyEntry)
1469
1470 if __name__ == "__main__":
1471 window = gtk.Window()
1472 window.set_colormap(gtk.gdk.Screen().get_rgba_colormap())
1473 window.set_decorated(False)
1474 window.add_events(gtk.gdk.ALL_EVENTS_MASK)
1475 window.connect("destroy", lambda w: gtk.main_quit())
1476 window.set_size_request(300, -1)
1477 window.move(100, 100)
1478
1479 entry = Entry("Enter to search")
1480 window.add(entry)
1481
1482 window.show_all()
1483
1484 gtk.main()
1485