diff --git a/pyfpdb/DatabaseManager.py b/pyfpdb/DatabaseManager.py new file mode 100644 index 00000000..efe0c49c --- /dev/null +++ b/pyfpdb/DatabaseManager.py @@ -0,0 +1,390 @@ + +import os +import pygtk +pygtk.require('2.0') +import gtk + +#******************************************************************************************************* +class DatabaseManager(object): + DatabaseTypes = {} + + def __init__(self, defaultDatabaseType=None): + self._defaultDatabaseType = defaultDatabaseType + def set_default_database_type(self, databaseType): + self._defaultDatabaseType = defaultDatabaseType + def get_default_database_type(self): + return self._defaultDatabaseType + +class DatabaseTypeMeta(type): + def __new__(klass, name, bases, kws): + newKlass = type.__new__(klass, name, bases, kws) + if newKlass.Type is not None: + if newKlass.Type in DatabaseManager.DatabaseTypes: + raise ValueError('data base type already registered for: %s' % newKlass.Type) + DatabaseManager.DatabaseTypes[newKlass.Type] = newKlass + return newKlass + +class DatabaseTypeBase(object): + __metaclass__ = DatabaseTypeMeta + Type = None + + DBHasHost = 0x1 + DBHasFile = 0x2 + DBHasPort = 0x4 + DBHasUser = 0x8 + DBHasPassword = 0x10 + DBHasDatabase = 0x20 + DBHasName = 0x40 + + DBFlagsFileSystem = DBHasFile|DBHasName + DBFlagsServer = DBHasHost|DBHasPort|DBHasUser|DBHasPassword|DBHasDatabase|DBHasName + + def __init__(self, host='', file='', port=0, user='', password='', database='', table='', name=''): + self.host = host + self.file = file + self.port = port + self.user = user + self.password = password + self.database = database + self.name = name + @classmethod + def display_name(klass): + raise NotImplementedError() + +class DatabaseTypePostgres(DatabaseTypeBase): + Type = 'postgres' + Flags = DatabaseTypeBase.DBFlagsServer + @classmethod + def display_name(klass): + return 'Postgres' + def __init__(self, host='localhost', file='', port=5432, user='postgres', password='', database='fpdb', name=''): + DatabaseTypeBase.__init__(self, host=host, file=file, port=port, user=user, password=password, database=database, name=name) + +class DatabaseTypeMysql(DatabaseTypeBase): + Type = 'mysql' + Flags = DatabaseTypeBase.DBFlagsServer + @classmethod + def display_name(klass): + return 'MySql' + def __init__(self, host='localhost', file='root', port=3306, user='', password='', database='fpdb', name=''): + DatabaseTypeBase.__init__(self, host=host, file=file, port=port, user=user, password=password, database=database, name=name) + +class DatabaseTypeSqLite(DatabaseTypeBase): + Type = 'sqlie' + Flags = DatabaseTypeBase.DBFlagsFileSystem + @classmethod + def display_name(klass): + return 'SqLite' + def __init__(self, host='', file='/home/me2/winetricks', port=0, user='', password='',database='', name=''): + DatabaseTypeBase.__init__(self, host=host, file=file, port=port, user=user, password=password, database=database, name=name) + +#*************************************************************************************************************************** +class MyFileChooserButton(gtk.HBox): + #NOTE: for some weird reason it is impossible to let the user choose a non exiting filename with gtk.FileChooserButton, so impl our own on the fly + def __init__(self): + gtk.HBox.__init__(self) + self.set_homogeneous(False) + + self.entry = gtk.Entry() + self.button = gtk.Button('...') + self.button.connect('clicked', self.on_button_clicked) + + # layout widgets + self.pack_start(self.entry, True, True) + self.pack_start(self.button, False, False) + + def get_filename(self): + return self.entry.get_text() + + def set_filename(self, name): + self.entry.set_text(name) + + #TODO: we got three possible actions here + # 1. user types in a new filename. easy one, create the file + # 2. user selectes a file with the intention to overwrite it + # 3. user selects a file with the intention to plug an existing database file in + #IDEA: impl open_existing as plug in, never overwrite, cos we can not guess + #PROBLEMS: how to validate an existing file is a database? + def on_button_clicked(self, button): + dlg = gtk.FileChooserDialog( + title='Choose an exiting database file or type in name of a new one', + parent=None, + action=gtk.FILE_CHOOSER_ACTION_SAVE, + buttons=( + gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, + gtk.STOCK_OK, gtk.RESPONSE_OK, + ), + backend=None + ) + dlg.connect('confirm-overwrite', self.on_dialog_confirm_overwrite) + dlg.set_default_response(gtk.RESPONSE_OK) + dlg.set_do_overwrite_confirmation(True) + if dlg.run() == gtk.RESPONSE_OK: + self.set_filename(dlg.get_filename()) + dlg.destroy() + + def on_dialog_confirm_overwrite(self, dlg): + print dlg.get_filename() + + gtk.FILE_CHOOSER_CONFIRMATION_CONFIRM + #The file chooser will present its stock dialog to confirm overwriting an existing file. + + gtk.FILE_CHOOSER_CONFIRMATION_ACCEPT_FILENAME + #The file chooser will terminate and accept the user's choice of a file name. + + gtk.FILE_CHOOSER_CONFIRMATION_SELECT_AGAIN + # + + + + + +class DialogDatabaseProperties(gtk.Dialog): + def __init__(self, databaseManager, database=None,parent=None): + gtk.Dialog.__init__(self, + title="My dialog", + parent=parent, + flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, + buttons=( + gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, + gtk.STOCK_OK, gtk.RESPONSE_ACCEPT, + ) + ) + self.connect('response', self.on_dialog_response) + + # setup widget + self.widgetDatabaseProperties = WidgetDatabaseProperties(databaseManager,database=database) + self.vbox.pack_start(self.widgetDatabaseProperties, True, True) + self.widgetDatabaseProperties.show_all() + + def on_dialog_response(self, dlg, responseId): + if responseId == gtk.RESPONSE_REJECT: + pass + elif responseId == gtk.RESPONSE_ACCEPT: + pass + + +class WidgetDatabaseProperties(gtk.VBox): + def __init__(self, databaseManager, database=None): + gtk.VBox.__init__(self) + + self.fieldWidgets = ( #fieldName--> fieldHandler + { + 'label': gtk.Label('Name:'), + 'widget': gtk.Entry(), + 'getter': lambda widget, database: setattr(database, 'name', widget.get_text() ), + 'setter': lambda widget, database: widget.set_text(database.name), + 'isSensitive': lambda database: bool(database.Flags & database.DBHasName), + 'tooltip': '', + }, + { + 'label': gtk.Label('File:'), + 'widget': MyFileChooserButton(), + 'getter': lambda widget: lambda widget, database: setattr(database, 'file', widget.get_filename() ), + 'setter': lambda widget, database: widget.set_filename(database.file), + 'isSensitive': lambda database: bool(database.Flags & database.DBHasFile), + 'tooltip': '', + }, + { + 'label': gtk.Label('Host:'), + 'widget': gtk.Entry(), + 'getter': lambda widget, database: setattr(database, 'host', widget.get_text() ), + 'setter': lambda widget, database: widget.set_text(database.host), + 'isSensitive': lambda database: bool(database.Flags & database.DBHasHost), + 'tooltip': '', + }, + { + 'label': gtk.Label('Port:'), + 'widget': gtk.SpinButton(adjustment=gtk.Adjustment(value=0, lower=0, upper=999999, step_incr=1, page_incr=10) ), + 'getter': lambda widget, database: setattr(database, 'port', widget.get_value() ), + 'setter': lambda widget, database: widget.set_value(database.port), + 'isSensitive': lambda database: bool(database.Flags & database.DBHasPort), + 'tooltip': '', + }, + { + 'label': gtk.Label('User:'), + 'widget': gtk.Entry(), + 'getter': lambda widget, database: setattr(database, 'user', widget.get_text() ), + 'setter': lambda widget, database: widget.set_text(database.user), + 'isSensitive': lambda database: bool(database.Flags & database.DBHasUser), + 'tooltip': '', + }, + { + 'label': gtk.Label('Pwd:'), + 'widget': gtk.Entry(), + 'getter': lambda widget, database: setattr(database, 'password', widget.get_text() ), + 'setter': lambda widget, database: widget.set_text(database.password), + 'isSensitive': lambda database: bool(database.Flags & database.DBHasPassword), + 'tooltip': '', + }, + { + 'label': gtk.Label('DB:'), + 'widget': gtk.Entry(), + 'getter': lambda widget, database: setattr(database, 'database', widget.get_text() ), + 'setter': lambda widget, database: widget.set_text(database.database), + 'isSensitive': lambda database: bool(database.Flags & database.DBHasDatabase), + 'tooltip': 'enter name of the database to create', + }, + ) + + # setup database type combo + self.comboType = gtk.ComboBox() + listStore= gtk.ListStore(str, str) + self.comboType.set_model(listStore) + cell = gtk.CellRendererText() + self.comboType.pack_start(cell, True) + self.comboType.add_attribute(cell, 'text', 0) + # fill out combo with database type. we store (displayName, databaseType) in our model for later lookup + for dbType, dbDisplayName in sorted([(klass.Type, klass.display_name()) for klass in databaseManager.DatabaseTypes.values()]): + listStore.append( (dbDisplayName, dbType) ) + self.comboType.connect('changed', self.on_combo_type_changed) + + # init and layout field widgets + self.pack_start(self.comboType, False, False, 2) + table = gtk.Table(rows=len(self.fieldWidgets) +1, columns=2, homogeneous=False) + self.pack_start(table, False, False, 2) + for i,fieldWidget in enumerate(self.fieldWidgets): + fieldWidget['widget'].set_tooltip_text(fieldWidget['tooltip']) + + table.attach(fieldWidget['label'], 0, 1, i, i+1, xoptions=gtk.FILL) + table.attach(fieldWidget['widget'], 1, 2, i, i+1) + + # init widget + + # if a database has been passed user is not allowed to change database type + if database is None: + self.comboType.set_button_sensitivity(gtk.SENSITIVITY_ON) + else: + self.comboType.set_button_sensitivity(gtk.SENSITIVITY_OFF) + + # set current database + self.databaseManager = databaseManager + self.database= None + if database is None: + databaseType = self.databaseManager.get_default_database_type() + if databaseType is not None: + database = databaseType() + if database is not None: + self.set_database(database) + + def on_combo_type_changed(self, combo): + i = self.comboType.get_active() + if i > -1: + # change database if necessary + currentDatabaseType = self.comboType.get_model()[i][1] + if currentDatabaseType != self.database.Type: + newDatabase = self.databaseManager.DatabaseTypes[currentDatabaseType]() + self.set_database(newDatabase) + + def set_database(self, database): + self.database = database + + # adjust database type combo if necessary + i = self.comboType.get_active() + if i == -1: + currentDatabaseType = None + else: + currentDatabaseType = self.comboType.get_model()[i][1] + if currentDatabaseType != self.database.Type: + for i, row in enumerate(self.comboType.get_model()): + if row[1] == self.database.Type: + self.comboType.set_active(i) + break + else: + raise ValueError('unknown database type') + + # adjust field widgets to database + for fieldWidget in self.fieldWidgets: + isSensitive = fieldWidget['isSensitive'](self.database) + fieldWidget['widget'].set_sensitive(isSensitive) + fieldWidget['label'].set_sensitive(isSensitive) + fieldWidget['setter'](fieldWidget['widget'], self.database) + + def get_database(self): + return self.database + + + + + +#TODO: just boilerplate code +class DialogDatabase(gtk.Dialog): + def __init__(self, databaseManager, parent=None): + gtk.Dialog.__init__(self, + title="My dialog", + parent=parent, + flags=gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT, + buttons=( + gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT, + gtk.STOCK_OK, gtk.RESPONSE_ACCEPT, + )) + #self.set_size_request(260, 250) + + self.databaseManager = databaseManager + + label = gtk.Label('database stuff') + label.set_line_wrap(True) + label.set_selectable(True) + label.set_single_line_mode(False) + label.set_alignment(0, 0) + self.vbox.pack_start(label, False, False, 2) + self.vbox.pack_start(gtk.HSeparator(), False, False, 2) + + hbox = gtk.HBox() + self.vbox.add(hbox) + hbox.set_homogeneous(False) + + + # database management buttons + vbox = gtk.VBox() + hbox.pack_start(vbox, False, False, 2) + self.buttonDatabaseNew = gtk.Button("New...") + self.buttonDatabaseNew.connect('clicked', self.onButtonDatabaseNewClicked) + vbox.pack_start(self.buttonDatabaseNew, False, False, 2) + self.buttonDatabaseEdit = gtk.Button("Edit...") + vbox.pack_start(self.buttonDatabaseEdit, False, False, 2) + self.buttonDatabaseDelete = gtk.Button("Delete") + vbox.pack_start(self.buttonDatabaseDelete, False, False, 2) + box = gtk.VBox() + vbox.pack_start(box, True, True, 0) + + hbox.pack_start(gtk.VSeparator(), False, False, 2) + + # database tree + self.treeDatabases = gtk.TreeView() + hbox.pack_end(self.treeDatabases, True, True, 2) + + self.show_all() + + # fill database tree + store = gtk.ListStore(str, str) + self.treeDatabases.set_model(store) + columns = ('Name', 'Status', 'Type') + for column in columns: + col = gtk.TreeViewColumn(column) + self.treeDatabases.append_column(col) + + + def onButtonDatabaseNewClicked(self, button): + dlg = DialogDatabaseProperties(self.databaseManager, parent=self) + if dlg.run() == gtk.RESPONSE_REJECT: + pass + if dlg.run() == gtk.RESPONSE_ACCEPT: + pass + + dlg.destroy() + + +#************************************************************************************************** +if __name__ == '__main__': + d = DialogDatabaseProperties( + DatabaseManager(defaultDatabaseType=DatabaseTypeSqLite), + #database=DatabaseTypePostgres(), + database=None, + ) + #d = DialogDatabase(DatabaseManager(defaultDatabaseType=DatabaseTypeSqLite)) + d.connect("destroy", gtk.main_quit) + d.run() + #gtk.main() + +