2 # -*- coding: utf-8; -*-
4 # PgWorksheet - PostgreSQL Front End
5 # http://pgworksheet.projects.postgresql.org/
7 # Copyright © 2004-2005 Henri Michelon & CML http://www.e-cml.org/
9 # This program is free software; you can redistribute it and/or
10 # modify it under the terms of the GNU General Public License
11 # as published by the Free Software Foundation; either version 2
12 # of the License, or (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details (read LICENSE.txt).
19 # You should have received a copy of the GNU General Public License
20 # along with this program; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 # $Id: pgworksheet,v 1.88 2006/01/10 21:31:35 hmichelon Exp $
31 #this is needed for py2exe
32 if sys
.platform
== 'win32':
33 #win32 platform, add the "lib" folder to the system path
34 os
.environ
['PATH'] += ";gtk/bin;gtk/lib;postgresql;"
38 #not win32, ensure version 2.0 of pygtk is imported
46 import pgw
.DBConnection
48 # maximum entries in the SQL queries history
50 # maximum entries in the connection parameters history
51 PGW_MAX_CONNECTION_HISTORY
= 5
56 def __init__(self
, app_name
, app_version
, pixmap_path
, locale_path
):
60 # try to get the default language
61 lang
= gettext
.translation(app_name
, locale_path
,
62 [ locale
.getdefaultlocale()[0] ])
65 # fallback to the default method
66 gettext
.bindtextdomain(app_name
, locale_path
)
67 gettext
.textdomain(app_name
)
68 gettext
.install(app_name
, locale_path
, unicode=1)
70 gettext
.bindtextdomain(app_name
, locale_path
)
71 gettext
.textdomain(app_name
)
72 gettext
.install(app_name
, locale_path
, unicode=1)
74 self
.ui
= pgw
.UI
.UI(self
, app_name
, app_version
, pixmap_path
)
75 # Default connection state : not connected
78 # Initialize prev/next query lifos
79 self
.prev_statements
= []
80 self
.next_statements
= []
83 # Display connection dialog on startup
84 self
.on_menu_connect(None)
89 def add_prevstatement(self
, sql
):
90 """Add a query to the previous queries lifo"""
91 # do not add the same query two times
92 if (len(self
.prev_statements
) > 0):
93 prev
= self
.prev_statements
[len(self
.prev_statements
)-1]
94 if (prev
== sql
): return
95 # add the query to the lifo
96 self
.prev_statements
.append(sql
)
97 self
.ui
.enable_prevsql(len(self
.prev_statements
) > 0)
100 def get_history_path(self
):
101 """Returns the path to the configuration file"""
102 return os
.path
.join(pgw
.get_user_configdir(), '.pgworksheet_history');
105 def save_history(self
):
106 """Save the history in a text file"""
108 fd
= open(self
.get_history_path(), 'w')
109 self
.add_prevstatement(self
.ui
.get_sqlbuffer_text())
110 for sql
in self
.prev_statements
:
112 fd
.write(string
.rstrip(sql
))
113 for sql
in self
.next_statements
:
115 fd
.write(string
.rstrip(sql
))
122 def load_history(self
):
123 """Load the history from a text file"""
125 fd
= open(self
.get_history_path(), 'r')
129 line
= string
.rstrip(line
)
133 line
= unicode(line
, 'UTF-8')
134 except UnicodeDecodeError:
136 line
= unicode(line
, pgw
.get_user_encoding())
137 except UnicodeDecodeError:
141 self
.prev_statements
.append(sql
)
148 self
.prev_statements
.append(sql
)
150 if (count
> PGW_MAX_HISTORY
):
151 self
.prev_statements
= self
.prev_statements
[count
- PGW_MAX_HISTORY
: count
]
152 self
.ui
.enable_prevsql(len(self
.prev_statements
) > 0)
157 def is_connected(self
):
158 """Return TRUE if connected to a database"""
159 if (self
.db
is None):
162 return self
.db
.is_connected()
165 def connect(self
, host
= None, port
= None, db
= None,
166 user
= None, password
= None):
167 """Connect to a database"""
168 self
.ui
.wndmain
.window
.set_cursor(gtk
.gdk
.Cursor(gtk
.gdk
.WATCH
))
169 self
.ui
.status(_('Trying to connect as <b>%(user)s</b> to <b>%(db)s</b> on <b>%(host)s</b>') %
170 {'user':user
, 'db':db
, 'host':host
}, _('connecting...'))
171 while (gtk
.events_pending() == True):
172 gtk
.main_iteration_do(False)
173 # Disconnect the re-connect
175 self
.db
= pgw
.DBConnection
.DBConnection(host
, port
, db
, user
, password
)
177 if (self
.is_connected()):
178 # update the UI to reflect the connection state
179 self
.ui
.enable_disconnect()
180 self
.ui
.enable_runsql()
181 self
.ui
.status(_('connected as <b>%(user)s</b> to <b>%(db)s</b> on <b>%(host)s</b>') %
182 {'user':user
, 'db':db
, 'host':host
}, self
.db
.pgversion())
183 new_conn
= "%s,%s,%s,%s" % (host
, port
, db
, user
)
184 # update the connection history
186 for conn
in self
.all_connections
:
187 # remove the connection from the history if it already exists
188 if (conn
== new_conn
):
189 self
.all_connections
.pop(n
)
192 # add the connection to the history, making it the first of the list
193 self
.all_connections
.insert(0, new_conn
)
194 # save the connection history in the config file
195 cp
= ConfigParser
.ConfigParser()
197 cp
.readfp(open(pgw
.get_config_path(), 'r'))
200 if (not cp
.has_section("connections")):
201 cp
.add_section("connections")
203 while ((n
<= PGW_MAX_CONNECTION_HISTORY
) and
204 (n
< len(self
.all_connections
))):
205 cp
.set("connections", "conn%d" % (n
+ 1), self
.all_connections
[n
])
208 cp
.write(open(pgw
.get_config_path(), 'w'))
211 # ready to type queries, give the focus to the text field
212 self
.ui
.setfocus_sqlbuffer()
213 # initialize the objects used to execute the queries
214 self
.execute
= pgw
.Execute
.Execute(self
.db
)
215 self
.run
= pgw
.RunSQL
.RunSQL(self
.execute
,
218 self
.ui
.wndmain
.window
.set_cursor(None)
221 def disconnect(self
):
222 """Disconnect from the current database"""
223 # disconnect from the database
224 if (self
.is_connected()): self
.db
.disconnect()
225 # destroy the objects used for this connection
229 # update the UI to reflect the connection state
230 self
.ui
.status(_('not connected'), 'PgWorksheet v' + app_version
)
231 self
.ui
.enable_disconnect(False)
232 self
.ui
.enable_runsql(False)
235 def on_wndmain_destroy(self
, widget
):
236 """Called when the application quits"""
243 def on_wndmain_delete(self
, widget
, event
):
244 """Called when the user wants to close the main window"""
248 def on_menu_connect(self
, widget
):
249 """Called when the user want the connection dialog box"""
250 # fill the connection dialog box with default parameters
251 if os
.environ
.has_key('USERNAME'):
252 self
.username
= os
.environ
['USERNAME']
253 elif os
.environ
.has_key('USER'):
254 self
.username
= os
.environ
['USER']
259 database
= 'template1'
261 self
.display_connect_dialog(host
, port
, username
, database
, 0)
264 def display_connect_dialog(self
, host
, port
, username
, database
, overwrite_entry
):
265 # display and execute the connection dialog box
266 params
= self
.ui
.connect_dialog(self
, host
, port
, username
, database
, overwrite_entry
)
267 # check if the user have clicked "Cancel"
268 if (params
is not None):
269 # connect to the database
270 host
, port
, username
, passwd
, database
= params
;
271 self
.connect(host
, port
, database
, username
, passwd
)
272 # error connecting to the database, retry
273 if (not self
.is_connected()):
274 self
.ui
.error_box(_('Error connecting to %s:%s@%s:%s') %
275 (username
, database
, host
, port
))
276 self
.display_connect_dialog(host
, port
, username
, database
, 1)
279 def on_dlgconnect_map(self
, widget
):
280 """Called when the connection dialog box is displayed"""
281 # clear the connections history
282 self
.all_connections
= []
283 # load the connections history from the config file
284 cp
= ConfigParser
.ConfigParser()
286 cp
.readfp(open(pgw
.get_config_path(), 'r'))
288 while n
<= PGW_MAX_CONNECTION_HISTORY
:
290 line
= cp
.get("connections", "conn%d" % n
)
291 # add the connection to the connections history
292 self
.all_connections
.append(line
)
293 host
, port
, db
, user
= string
.split(line
, ',')
294 # add the connections to the connections history list of the dialog box
295 self
.ui
.storeconn
.append(["%s:%s@%s" % (user
, db
, host
), line
])
299 # if we have at least one connection in the history, made it the default
301 # select the last used connection
302 self
.ui
.viewconn
.set_cursor(self
.ui
.storeconn
.get_path(
303 self
.ui
.storeconn
.get_iter_first()))
308 def on_dlgconnect_change(self
, treeview
):
309 """Called when the user choose a connection in the connection history list"""
310 # fill the connection dialog with the selected connection parameters
311 model
, iter = treeview
.get_selection().get_selected()
312 host
, port
, db
, user
= string
.split(model
.get(iter, 1)[0], ',')
313 self
.ui
.entry_host
.set_text(host
)
314 self
.ui
.entry_port
.set_text(port
)
315 self
.ui
.entry_database
.set_text(db
)
316 self
.ui
.entry_user
.set_text(user
)
317 self
.ui
.entry_password
.set_text('')
320 def on_menu_disconnect(self
, widget
):
321 """Called when the user wants to disconnect from the database"""
325 def on_menu_opensql(self
, widget
):
326 """The user wants to open a file with some queries"""
327 filename
= self
.ui
.file_dialog(_('Select a SQL text file'));
328 if (filename
is not None):
329 self
.ui
.undo
.lock
= True
330 for handler
in self
.ui
.buffer_handlers
:
331 self
.ui
.sqlbuffer
.handler_block(handler
)
332 self
.ui
.set_sqlbuffer_text('')
334 input = open(filename
, 'r')
337 self
.ui
.sqlbuffer
.insert_at_cursor(unicode(line
, 'UTF-8'))
338 except UnicodeDecodeError:
340 self
.ui
.sqlbuffer
.insert_at_cursor(unicode(line
, pgw
.get_user_encoding()))
341 except UnicodeDecodeError:
342 self
.ui
.sqlbuffer
.insert_at_cursor(line
)
344 self
.ui
.error_box(_('Error while opening or reading from %s') %filename
)
345 for handler
in self
.ui
.buffer_handlers
:
346 self
.ui
.sqlbuffer
.handler_unblock(handler
)
348 self
.ui
.undo
.lock
= False
349 self
.ui
.syntax
.refresh()
350 pgw
.set_proportional(self
.ui
.sqlbuffer
)
353 def file_overwrite(self
, title
):
354 """Display a "Save As" dialopg box and prompt a confirmation if the selected file exists"""
355 filename
= self
.ui
.file_dialog(title
, gtk
.FILE_CHOOSER_ACTION_SAVE
,
357 if (filename
is not None):
360 if (self
.ui
.yesno_box(_('%s already exists, overwrite ?') % filename
) ==
363 return self
.file_overwrite(title
)
364 except OSError: # file does not exists
369 def on_menu_savesql(self
, widget
):
370 """The user wants to save his queries"""
371 filename
= self
.file_overwrite(_('Save SQL queries'))
372 if (filename
is not None):
374 output
= open(filename
, 'w')
375 output
.write(self
.ui
.get_sqlbuffer_text())
377 self
.ui
.error_box(_('Error while creating or writing %s') % filename
)
380 def save_list_row(self
, model
, path
, iter, output
):
381 """Save a row of a TreeView in a tabular form"""
383 while (col
< model
.get_n_columns()):
384 val
= string
.replace(model
.get_value(iter, col
), '"', '\"')
385 output
.write('"' + val
+ '"')
387 if (col
< model
.get_n_columns()):
392 def saveresults(self
, widget
, output
):
393 """Save the content of a TreeView to a tab separated file"""
394 widget
= widget
.get_child()
395 if (isinstance(widget
, gtk
.TextView
)):
396 buffer = widget
.get_buffer()
397 output
.write(buffer.get_text(buffer.get_start_iter(),
398 buffer.get_end_iter()))
399 elif (isinstance(widget
, gtk
.TreeView
)):
400 widget
.get_model().foreach(self
.save_list_row
, output
)
403 def on_menu_saveallresults(self
, widget
):
404 """The user wants to save ALL the results"""
405 if (self
.ui
.resulttab
.get_n_pages() > 0):
406 filename
= self
.file_overwrite(_('Save all the results'))
407 if (filename
is not None):
409 output
= open(filename
, 'w')
411 while page
< self
.ui
.resulttab
.get_n_pages() :
412 self
.saveresults(self
.ui
.resulttab
.get_nth_page(page
), output
)
415 self
.ui
.error_box(_('Error while creating or writing %s') % filename
)
418 def on_menu_saveresults(self
, widget
):
419 """The user wants to save the current result"""
420 if (self
.ui
.resulttab
.get_n_pages() > 0):
421 filename
= self
.file_overwrite(_('Save the results'))
422 if (filename
is not None):
424 output
= open(filename
, 'w')
425 self
.saveresults(self
.ui
.resulttab
.get_nth_page(
426 self
.ui
.resulttab
.get_current_page()), output
)
428 self
.ui
.error_box(_('Error while creating or writing %s') % filename
)
431 def on_menu_cut(self
, widget
):
432 """Cut text to the clipboard"""
433 w
= self
.ui
.wndmain
.get_focus()
434 if (isinstance(w
, gtk
.TextView
)):
435 w
.emit('cut-clipboard')
438 def on_menu_copy(self
, widget
):
439 """Copy text to the clipboard"""
440 w
= self
.ui
.wndmain
.get_focus()
441 if (isinstance(w
, gtk
.TextView
)):
442 w
.emit('copy-clipboard')
443 elif (isinstance(w
, gtk
.TreeView
)):
444 model
, iter = w
.get_selection().get_selected()
445 if (iter is not None):
448 while (col
< model
.get_n_columns()):
449 val
= string
.replace(model
.get_value(iter, col
), '"', '\"')
450 result
= result
+ val
452 if (col
< model
.get_n_columns()):
453 result
= result
+ '\t'
454 clip
= gtk
.Clipboard()
455 clip
.set_text(result
)
458 def on_menu_paste(self
, widget
):
459 """Paste from the clipboard"""
460 w
= self
.ui
.wndmain
.get_focus()
461 if (isinstance(w
, gtk
.TextView
)):
462 w
.emit('paste-clipboard')
465 def on_menu_selectall(self
, widget
):
466 """Select the entire text"""
467 w
= self
.ui
.wndmain
.get_focus()
468 if (isinstance(w
, gtk
.TextView
)):
469 buffer = w
.get_buffer()
470 buffer.move_mark_by_name('selection_bound', buffer.get_start_iter())
471 buffer.move_mark_by_name('insert', buffer.get_end_iter())
474 def on_sqlview_focus_in(self
, widget
, event
):
476 self
.ui
.enable_paste()
479 def on_sqlview_focus_out(self
, widget
, event
):
480 self
.ui
.enable_cut(False)
481 self
.ui
.enable_paste(False)
484 def on_sqlview_keypress(self
, widget
, event
):
485 """Save the last statement in the history
486 if needed (after an execution"""
487 if (event
is None) : return
488 if (event
.keyval
!= 65507):
489 if (self
.prev_saved
== 0):
490 self
.add_prevstatement(self
.ui
.get_sqlbuffer_text())
494 def on_menu_about(self
, widget
):
495 self
.ui
.about_dialog()
498 def on_menu_runsql(self
, widget
):
499 """Execute the SQL queries"""
500 if (not self
.is_connected()):
501 if (self
.ui
.yesno_box(_('Not connected to a database.\nDo you want to connect now ?')) ==
504 self
.on_menu_connect(widget
)
505 if (not self
.is_connected()):
507 self
.on_text_change(widget
)
509 self
.ui
.wndmain
.window
.set_cursor(gtk
.gdk
.Cursor(gtk
.gdk
.WATCH
))
511 self
.ui
.enable_saveresult(self
.ui
.resulttab
.get_n_pages() > 0)
512 self
.ui
.wndmain
.window
.set_cursor(None)
515 def on_menu_prevsql(self
, widget
):
516 """Display the previous statement from the history"""
517 self
.ui
.undo
.lock
= True
518 if (len(self
.prev_statements
) > 0):
519 s
= self
.prev_statements
.pop()
520 self
.next_statements
.append(self
.ui
.get_sqlbuffer_text())
521 self
.ui
.set_sqlbuffer_text(s
)
523 self
.ui
.enable_prevsql(len(self
.prev_statements
) > 0)
524 self
.ui
.enable_nextsql(len(self
.next_statements
) > 0)
525 self
.ui
.undo
.lock
= False
528 def on_menu_nextsql(self
, widget
):
529 """Display the next statement from the history"""
530 self
.ui
.undo
.lock
= True
531 if (len(self
.next_statements
) > 0):
532 s
= self
.next_statements
.pop()
533 self
.prev_statements
.append(self
.ui
.get_sqlbuffer_text())
534 self
.ui
.set_sqlbuffer_text(s
)
536 self
.ui
.enable_prevsql(len(self
.prev_statements
) > 0)
537 self
.ui
.enable_nextsql(len(self
.next_statements
) > 0)
538 self
.ui
.undo
.lock
= False
541 def on_text_change(self
, widget
):
542 """The text have been changed after navigation the history"""
543 if (self
.ui
.undo
.lock
):
545 if (len(self
.next_statements
) > 0):
546 if (self
.next_statements
[0] == ''):
547 self
.next_statements
.pop(0)
548 self
.prev_statements
.append(self
.prev
)
549 for i
in reversed(self
.next_statements
):
550 self
.prev_statements
.append(i
)
551 self
.next_statements
= []
552 self
.ui
.enable_prevsql(len(self
.prev_statements
) > 0)
553 self
.ui
.enable_nextsql(len(self
.next_statements
) > 0)
556 # Application parameters
557 app_name
= 'pgworksheet'
558 app_version
= '1.8.1'
559 # Default pixmap path
560 pixmap_path
= '@PIXMAP_PATH@'
561 # Find current pixmap path
562 if (not os
.access(os
.path
.join(pixmap_path
, 'pgworksheet-32.png'), os
.F_OK
)):
563 pixmap_path
= os
.path
.join(sys
.prefix
, 'share/pixmaps/pgworksheet')
564 if (not os
.access(os
.path
.join(pixmap_path
, 'pgworksheet-32.png'), os
.F_OK
)):
565 pixmap_path
= os
.path
.join(os
.path
.dirname(sys
.argv
[0]), 'pixmaps/pgworksheet')
566 # Find current locale path
567 locale_path
= '@LOCALE_PATH@'
568 if (not os
.access(os
.path
.join(locale_path
, 'fr/LC_MESSAGES/pgworksheet.mo'), os
.F_OK
)):
569 locale_path
= os
.path
.join(sys
.prefix
, 'share/locale')
570 if (not os
.access(os
.path
.join(locale_path
, 'fr/LC_MESSAGES/pgworksheet.mo'), os
.F_OK
)):
571 locale_path
= os
.path
.join(os
.path
.dirname(sys
.argv
[0]), 'locale')
575 p
= PgWorksheet(app_name
, app_version
, pixmap_path
, locale_path
)
576 except KeyboardInterrupt:
578 p
.on_wndmain_destroy(None)