1 # -*- coding: utf-8 -*-
2 # -----------------------------------------------------------------------------
3 # PyRoom - A clone of WriteRoom
4 # Copyright (c) 2007 Nicolas P. Rougier & NoWhereMan
5 # Copyright (c) 2008 The Pyroom Team - See AUTHORS file for more information
7 # This program is free software: you can redistribute it and/or modify it under
8 # the terms of the GNU General Public License as published by the Free Software
9 # Foundation, either version 3 of the License, or (at your option) any later
12 # This program is distributed in the hope that it will be useful, but WITHOUT
13 # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 # FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
17 # You should have received a copy of the GNU General Public License along with
18 # this program. If not, see <http://www.gnu.org/licenses/>.
19 # -----------------------------------------------------------------------------
22 provide basic editor functionality
24 contains basic functions needed for pyroom - any core functionality is included
33 from pyroom_error
import PyroomError
35 from preferences
import Preferences
38 FILE_UNNAMED
= _('* Unnamed *')
40 KEY_BINDINGS
= '\n'.join([
41 _('Control-H: Show help in a new buffer'),
42 _('Control-I: Show buffer information'),
43 _('Control-P: Shows Preferences dialog'),
44 _('Control-N: Create a new buffer'),
45 _('Control-O: Open a file in a new buffer'),
47 _('Control-S: Save current buffer'),
48 _('Control-Shift-S: Save current buffer as'),
49 _('Control-W: Close buffer and exit if it was the last buffer'),
50 _('Control-Y: Redo last typing'),
51 _('Control-Z: Undo last typing'),
52 _('Control-Page Up: Switch to previous buffer'),
53 _('Control-Page Down: Switch to next buffer'), ])
56 _("""PyRoom - distraction free writing
57 Copyright (c) 2007 Nicolas Rougier, NoWhereMan
58 Copyright (c) 2008 Bruno Bord and the PyRoom team
60 Welcome to PyRoom and distraction-free writing.
62 To hide this help window, press Control-W.
64 PyRoom stays out of your way with formatting options and buttons,
65 it is largely keyboard controlled, via shortcuts. You can find a list
66 of available keyboard shortcuts later.
68 If enabled in preferences, pyroom will save your files automatically every
69 few minutes or when you press the keyboard shortcut.
77 def define_keybindings(edit_instance
):
78 """define keybindings, respectively to keyboard layout"""
79 keymap
= gtk
.gdk
.keymap_get_default()
81 gtk
.keysyms
.Page_Up
: edit_instance
.prev_buffer
,
82 gtk
.keysyms
.Page_Down
: edit_instance
.next_buffer
,
83 gtk
.keysyms
.H
: edit_instance
.show_help
,
84 gtk
.keysyms
.I
: edit_instance
.show_info
,
85 gtk
.keysyms
.N
: edit_instance
.new_buffer
,
86 gtk
.keysyms
.O
: edit_instance
.open_file
,
87 gtk
.keysyms
.P
: edit_instance
.preferences
.show
,
88 gtk
.keysyms
.Q
: edit_instance
.dialog_quit
,
89 gtk
.keysyms
.S
: edit_instance
.save_file
,
90 gtk
.keysyms
.W
: edit_instance
.close_dialog
,
91 gtk
.keysyms
.Y
: edit_instance
.redo
,
92 gtk
.keysyms
.Z
: edit_instance
.undo
,
94 translated_bindings
= {}
95 for key
, value
in basic_bindings
.items():
96 hardware_keycode
= keymap
.get_entries_for_keyval(key
)[0][0]
97 translated_bindings
[hardware_keycode
] = value
98 return translated_bindings
100 class BasicEdit(object):
101 """editing logic that gets passed around"""
103 def __init__(self
, style
, pyroom_config
):
107 self
.config
= pyroom_config
.config
108 self
.gui
= GUI(style
, pyroom_config
, self
)
109 self
.preferences
= Preferences(gui
=self
.gui
, style
=style
,
110 pyroom_config
=pyroom_config
)
111 self
.status
= self
.gui
.status
112 self
.window
= self
.gui
.window
113 self
.textbox
= self
.gui
.textbox
117 self
.textbox
.connect('key-press-event', self
.key_press_event
)
119 # Set line numbers visible, set linespacing
120 self
.textbox
.set_show_line_numbers(int(self
.config
.get("visual",
122 self
.textbox
.set_pixels_below_lines(int(self
.config
.get("visual", "linespacing")))
123 self
.textbox
.set_pixels_above_lines(int(self
.config
.get("visual", "linespacing")))
124 self
.textbox
.set_pixels_inside_wrap(int(self
.config
.get("visual", "linespacing")))
126 # Autosave timer object
127 autosave
.autosave_init(self
)
129 self
.window
.show_all()
130 self
.window
.fullscreen()
132 # Handle multiple monitors
133 screen
= gtk
.gdk
.screen_get_default()
134 root_window
= screen
.get_root_window()
135 mouse_x
, mouse_y
, mouse_mods
= root_window
.get_pointer()
136 current_monitor_number
= screen
.get_monitor_at_point(mouse_x
, mouse_y
)
137 monitor_geometry
= screen
.get_monitor_geometry(current_monitor_number
)
138 self
.window
.move(monitor_geometry
.x
, monitor_geometry
.y
)
139 self
.window
.set_geometry_hints(None, min_width
=monitor_geometry
.width
,
140 min_height
=monitor_geometry
.height
, max_width
=monitor_geometry
.width
,
141 max_height
=monitor_geometry
.height
144 # Defines the glade file functions for use on closing a buffer
145 self
.wTree
= gtk
.glade
.XML(os
.path
.join(
146 pyroom_config
.pyroom_absolute_path
, "interface.glade"),
148 self
.dialog
= self
.wTree
.get_widget("SaveBuffer")
149 self
.dialog
.set_transient_for(self
.window
)
151 "on_button-close_clicked": self
.unsave_dialog
,
152 "on_button-cancel_clicked": self
.cancel_dialog
,
153 "on_button-save_clicked": self
.save_dialog
,
155 self
.wTree
.signal_autoconnect(dic
)
157 #Defines the glade file functions for use on exit
158 self
.aTree
= gtk
.glade
.XML(os
.path
.join(
159 pyroom_config
.pyroom_absolute_path
, "interface.glade"),
161 self
.quitdialog
= self
.aTree
.get_widget("QuitSave")
162 self
.quitdialog
.set_transient_for(self
.window
)
164 "on_button-close2_clicked": self
.quit_quit
,
165 "on_button-cancel2_clicked": self
.cancel_quit
,
166 "on_button-save2_clicked": self
.save_quit
,
168 self
.aTree
.signal_autoconnect(dic
)
169 self
.keybindings
= define_keybindings(self
)
171 def key_press_event(self
, widget
, event
):
172 """ key press event dispatcher """
173 if event
.state
& gtk
.gdk
.CONTROL_MASK
:
174 if event
.hardware_keycode
in self
.keybindings
:
175 if self
.keybindings
[event
.hardware_keycode
] == self
.save_file\
176 and event
.state
& gtk
.gdk
.SHIFT_MASK
:
179 self
.keybindings
[event
.hardware_keycode
]()
184 """ Display buffer information on status label for 5 seconds """
186 buf
= self
.buffers
[self
.current
]
187 if buf
.can_undo() or buf
.can_redo():
188 status
= _(' (modified)')
191 self
.status
.set_text(_('Buffer %(buffer_id)d: %(buffer_name)s\
192 %(status)s, %(char_count)d byte(s), %(word_count)d word(s)\
193 , %(lines)d line(s)') % {
194 'buffer_id': self
.current
+ 1,
195 'buffer_name': buf
.filename
,
197 'char_count': buf
.get_char_count(),
198 'word_count': self
.word_count(buf
),
199 'lines': buf
.get_line_count(),
203 """ Undo last typing """
205 buf
= self
.textbox
.get_buffer()
209 self
.status
.set_text(_('Nothing more to undo!'))
212 """ Redo last typing """
214 buf
= self
.textbox
.get_buffer()
218 self
.status
.set_text(_('Nothing more to redo!'))
220 def toggle_lines(self
):
221 """ Toggle lines number """
222 opposite_state
= not self
.textbox
.get_show_line_numbers()
223 self
.textbox
.set_show_line_numbers(opposite_state
)
228 chooser
= gtk
.FileChooserDialog('PyRoom', self
.window
,
229 gtk
.FILE_CHOOSER_ACTION_OPEN
,
230 buttons
=(gtk
.STOCK_CANCEL
, gtk
.RESPONSE_CANCEL
,
231 gtk
.STOCK_OPEN
, gtk
.RESPONSE_OK
))
232 chooser
.set_default_response(gtk
.RESPONSE_OK
)
235 if res
== gtk
.RESPONSE_OK
:
236 buf
= self
.new_buffer()
237 buf
.filename
= chooser
.get_filename()
239 buffer_file
= open(buf
.filename
, 'r')
240 buf
= self
.buffers
[self
.current
]
241 buf
.begin_not_undoable_action()
242 utf8
= unicode(buffer_file
.read(), 'utf-8')
244 buf
.end_not_undoable_action()
246 self
.status
.set_text(_('File %s open')
248 except IOError, (errno
, strerror
):
249 errortext
= _('Unable to open %(filename)s.' % {
250 'filename': buf
.filename
})
252 errortext
+= _(' The file does not exist.')
254 errortext
+= _(' You do not have permission to \
256 raise PyroomError(errortext
)
258 raise PyroomError(_('Unable to open %s\n'
261 self
.status
.set_text(_('Closed, no files selected'))
264 def open_file_no_chooser(self
, filename
):
265 """ Open specified file """
266 buf
= self
.new_buffer()
267 buf
.filename
= filename
269 buffer_file
= open(buf
.filename
, 'r')
270 buf
= self
.buffers
[self
.current
]
271 buf
.begin_not_undoable_action()
272 utf8
= unicode(buffer_file
.read(), 'utf-8')
274 buf
.end_not_undoable_action()
276 self
.status
.set_text(_('File %s open')
278 except IOError, (errno
, strerror
):
279 errortext
= _('Unable to open %(filename)s.' % {
280 'filename': buf
.filename
})
282 errortext
+= _(' The file does not exist.')
284 errortext
+= _(' You do not have permission to open \
286 raise PyroomError(errortext
)
288 raise PyroomError(_('Unable to open %s\n'
293 buf
= self
.buffers
[self
.current
]
294 if buf
.filename
!= FILE_UNNAMED
:
295 buffer_file
= open(buf
.filename
, 'w')
296 txt
= buf
.get_text(buf
.get_start_iter(),
298 buffer_file
.write(txt
)
300 buf
.begin_not_undoable_action()
301 buf
.end_not_undoable_action()
302 self
.status
.set_text(_('File %s saved') % buf
.filename
)
305 except IOError, (errno
, strerror
):
306 errortext
= _('Unable to save %(filename)s.' % {
307 'filename': buf
.filename
})
309 errortext
+= _(' You do not have permission to write to \
311 raise PyroomError(errortext
)
313 raise PyroomError(_('Unable to save %s\n'
316 def save_file_as(self
):
319 buf
= self
.buffers
[self
.current
]
320 chooser
= gtk
.FileChooserDialog('PyRoom', self
.window
,
321 gtk
.FILE_CHOOSER_ACTION_SAVE
,
322 buttons
=(gtk
.STOCK_CANCEL
, gtk
.RESPONSE_CANCEL
,
323 gtk
.STOCK_SAVE
, gtk
.RESPONSE_OK
))
324 chooser
.set_default_response(gtk
.RESPONSE_OK
)
325 if buf
.filename
!= FILE_UNNAMED
:
326 chooser
.set_filename(buf
.filename
)
328 if res
== gtk
.RESPONSE_OK
:
329 buf
.filename
= chooser
.get_filename()
332 self
.status
.set_text(_('Closed, no files selected'))
335 def word_count(self
, buf
):
336 """ Word count in a text buffer """
338 iter1
= buf
.get_start_iter()
340 iter2
.forward_word_end()
342 while iter2
.get_offset() != iter1
.get_offset():
345 iter2
.forward_word_end()
349 """ Create a new buffer and inserts help """
350 buf
= self
.new_buffer()
351 buf
.begin_not_undoable_action()
353 buf
.end_not_undoable_action()
354 self
.status
.set_text("Displaying help. Press control W to exit and \
355 continue editing your document.")
357 def new_buffer(self
):
358 """ Create a new buffer """
360 buf
= gtksourceview
.SourceBuffer()
361 buf
.set_check_brackets(False)
362 buf
.set_highlight(False)
363 buf
.filename
= FILE_UNNAMED
364 self
.buffers
.insert(self
.current
+ 1, buf
)
365 buf
.place_cursor(buf
.get_end_iter())
369 def close_dialog(self
):
370 """ask for confirmation if there are unsaved contents"""
371 buf
= self
.buffers
[self
.current
]
372 if buf
.can_undo() or buf
.can_redo():
377 def cancel_dialog(self
, widget
, data
=None):
378 """dialog has been canceled"""
381 def unsave_dialog(self
, widget
, data
=None):
382 """don't save before closing"""
386 def save_dialog(self
, widget
, data
=None):
387 """save when closing"""
392 def close_buffer(self
):
393 """ Close current buffer """
396 if len(self
.buffers
) > 1:
398 self
.buffers
.pop(self
.current
)
399 self
.current
= min(len(self
.buffers
) - 1, self
.current
)
400 self
.set_buffer(self
.current
)
404 def set_buffer(self
, index
):
405 """ Set current buffer """
407 if index
>= 0 and index
< len(self
.buffers
):
409 buf
= self
.buffers
[index
]
410 self
.textbox
.set_buffer(buf
)
411 if hasattr(self
, 'status'):
412 self
.status
.set_text(
413 _('Switching to buffer %(buffer_id)d (%(buffer_name)s)'
414 % {'buffer_id': self
.current
+ 1,
415 'buffer_name': buf
.filename
}))
417 def next_buffer(self
):
418 """ Switch to next buffer """
420 if self
.current
< len(self
.buffers
) - 1:
424 self
.set_buffer(self
.current
)
425 self
.gui
.textbox
.scroll_to_mark(
426 self
.buffers
[self
.current
].get_insert(),
430 def prev_buffer(self
):
431 """ Switch to prev buffer """
436 self
.current
= len(self
.buffers
) - 1
437 self
.set_buffer(self
.current
)
438 self
.gui
.textbox
.scroll_to_mark(
439 self
.buffers
[self
.current
].get_insert(),
442 def dialog_quit(self
):
443 """the quit dialog"""
446 for buf
in self
.buffers
:
447 if (buf
.can_undo() or buf
.can_redo()) and \
448 not buf
.get_text(buf
.get_start_iter(),
449 buf
.get_end_iter()) == '':
452 self
.quitdialog
.show()
456 def cancel_quit(self
, widget
, data
=None):
458 self
.quitdialog
.hide()
460 def save_quit(self
, widget
, data
=None):
461 """save before quitting"""
462 self
.quitdialog
.hide()
463 for buf
in self
.buffers
:
464 if buf
.can_undo() or buf
.can_redo():
465 if buf
.filename
== FILE_UNNAMED
:
471 def quit_quit(self
, widget
, data
=None):
473 self
.quitdialog
.hide()
477 """cleanup before quitting"""
478 autosave
.autosave_quit()