diff --git a/src/guiQML/__init__.py b/src/guiQML/__init__.py new file mode 100644 index 000000000..9e0c4905b --- /dev/null +++ b/src/guiQML/__init__.py @@ -0,0 +1,29 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2010 Benny Malengier +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +Package init for the guiQML package. +""" + +# DO NOT IMPORT METHODS/CLASSES FROM src/guiQML HERE ! Only __all__ + +__all__ = [ ] diff --git a/src/guiQML/grampsqml.py b/src/guiQML/grampsqml.py new file mode 100644 index 000000000..089c1d63b --- /dev/null +++ b/src/guiQML/grampsqml.py @@ -0,0 +1,171 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2010 Benny Malengier +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +main file to start the QML application +""" +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import sys, os + +#------------------------------------------------------------------------- +# +# set up logging +# +#------------------------------------------------------------------------- +import logging +LOG = logging.getLogger(".grampsqml") + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +import constfunc +import config + +#------------------------------------------------------------------------- +# +# Main Gramps class +# +#------------------------------------------------------------------------- +class GrampsQML(object): + """ + Main class corresponding to a running gramps process. + + There can be only one instance of this class per gramps application + process. It may spawn several windows and control several databases. + """ + + def __init__(self, argparser): + import DbState + from guiQML.viewmanager import ViewManager + from cli.arghandler import ArgHandler + + from PySide import QtGui + self.app = QtGui.QApplication(sys.argv) + dbstate = DbState.DbState() + self.vm = ViewManager(dbstate) + + #act based on the given arguments + ah = ArgHandler(dbstate, argparser, self.vm, self.argerrorfunc, + gui=True) + ah.handle_args_gui() + if ah.open or ah.imp_db_path: + # if we opened or imported something, only show the interface + self.vm.post_init_interface() + elif config.get('paths.recent-file') and config.get('behavior.autoload'): + # if we need to autoload last seen file, do so + filename = config.get('paths.recent-file') + if os.path.isdir(filename) and \ + os.path.isfile(os.path.join(filename, "name.txt")) and \ + ah.check_db(filename): + self.vm.open_activate(filename) + self.vm.post_init_interface() + else: + self.vm.post_init_interface() + else: + # open without fam tree loaded + self.vm.post_init_interface() + + #start the QT loop + self.app.exec_() + + def argerrorfunc(self, string): + from guiQML.questiondialog import ErrorDialog + """ Show basic errors in argument handling in GUI fashion""" + ErrorDialog(_("Error parsing arguments"), string) + +def startqml(errors, argparser): + """ + Main startup function started via gobject.timeout_add + First action inside the gtk loop + """ + from guiQML.questiondialog import ErrorDialog, run_dialog_standalone + + #handle first existing errors in GUI fashion + if errors: + run_dialog_standalone(ErrorDialog,errors[0], errors[1]) + sys.exit() + + if argparser.errors: + run_dialog_standalone(ErrorDialog, argparser.errors[0], + argparser.errors[1]) + sys.exit() + + # add gui logger + from GrampsLogger import RotateHandler + form = logging.Formatter(fmt="%(relativeCreated)d: %(levelname)s: " + "%(filename)s: line %(lineno)d: %(message)s") + # Create the log handlers + rh = RotateHandler(capacity=20) + rh.setFormatter(form) + # Only error and critical log records should + # trigger the GUI handler. + #qmlh = QMLHandler(rotate_handler=rh) + #qmlh.setFormatter(form) + #qmlh.setLevel(logging.ERROR) + l = logging.getLogger() + l.addHandler(rh) + #l.addHandler(gmlh) + + # start GRAMPS, errors stop the gtk loop + try: + quit_now = False + openGL = True + exit_code = 0 + if constfunc.has_display(): + GrampsQML(argparser) + else: + print("Gramps terminated because of no DISPLAY") + sys.exit(exit_code) + + except SystemExit, e: + quit_now = True + if e.code: + exit_code = e.code + LOG.error("Gramps terminated with exit code: %d." \ + % e.code, exc_info=True) + except OSError, e: + quit_now = True + exit_code = e[0] or 1 + try: + fn = e.filename + except AttributeError: + fn = "" + LOG.error("Gramps terminated because of OS Error\n" + + "Error details: %s %s" % (repr(e), fn), exc_info=True) + + except: + quit_now = True + exit_code = 1 + LOG.error("Gramps failed to start.", exc_info=True) + + if quit_now: + #quit + sys.exit(exit_code) + + #function finished, return False to stop the timeout_add function calls + return False diff --git a/src/guiQML/qmlwidgets/TextButton.qml b/src/guiQML/qmlwidgets/TextButton.qml new file mode 100644 index 000000000..bf9fbab45 --- /dev/null +++ b/src/guiQML/qmlwidgets/TextButton.qml @@ -0,0 +1,77 @@ + /**************************************************************************** + ** + ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). + ** All rights reserved. + ** Contact: Nokia Corporation (qt-info@nokia.com) + ** + ** This file is part of the examples of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:BSD$ + ** You may use this file under the terms of the BSD license as follows: + ** + ** "Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions are + ** met: + ** * Redistributions of source code must retain the above copyright + ** notice, this list of conditions and the following disclaimer. + ** * Redistributions in binary form must reproduce the above copyright + ** notice, this list of conditions and the following disclaimer in + ** the documentation and/or other materials provided with the + ** distribution. + ** * Neither the name of Nokia Corporation and its Subsidiary(-ies) nor + ** the names of its contributors may be used to endorse or promote + ** products derived from this software without specific prior written + ** permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." + ** $QT_END_LICENSE$ + ** + ****************************************************************************/ + +import Qt 4.7 + + Rectangle { + id: container + + property alias text: label.text + + signal clicked + + width: label.width + 20; height: label.height + 6 + smooth: true + radius: 10 + + gradient: Gradient { + GradientStop { id: gradientStop; position: 0.0; color: palette.light } + GradientStop { position: 1.0; color: palette.button } + } + + SystemPalette { id: palette } + + MouseArea { + id: mouseArea + anchors.fill: parent + onClicked: { container.clicked() } + } + + Text { + id: label + anchors.centerIn: parent + } + + states: State { + name: "pressed" + when: mouseArea.pressed + PropertyChanges { target: gradientStop; color: palette.dark } + } + } \ No newline at end of file diff --git a/src/guiQML/qmlwidgets/TopBar.qml b/src/guiQML/qmlwidgets/TopBar.qml new file mode 100644 index 000000000..58bed1456 --- /dev/null +++ b/src/guiQML/qmlwidgets/TopBar.qml @@ -0,0 +1,44 @@ +/*# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2010 Benny Malengier +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id: __init__.py 13807 2009-12-15 05:56:12Z pez4brian $ +*/ + +import Qt 4.7 + +Item { + id: titlebar + + property alias text: titletext.text + width: parent.width + height: 20 + Rectangle { + anchors.fill: parent + color: "#343434" + } + Text { + id: titletext + anchors.horizontalCenter: parent.horizontalCenter + text: text + font.pixelSize: 15 + color: "white" + font.bold: true + } +} diff --git a/src/guiQML/questiondialog.py b/src/guiQML/questiondialog.py new file mode 100644 index 000000000..dd45e4de1 --- /dev/null +++ b/src/guiQML/questiondialog.py @@ -0,0 +1,66 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2010 Benny Malengier +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id: __init__.py 13807 2009-12-15 05:56:12Z pez4brian + +""" +Some often needed dialogs +""" + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import sys + +#------------------------------------------------------------------------- +# +# QT modules +# +#------------------------------------------------------------------------- +from PySide.QtCore import * +from PySide.QtGui import * + +#------------------------------------------------------------------------- +# +# Classes for the Dialogs +# +#------------------------------------------------------------------------- +class ErrorDialog(QDialog): + def __init__(self, msg1, msg2="", parent=None): + super(ErrorDialog, self).__init__(parent) + self.setWindowTitle("%s - Gramps" % msg1) + lbl1 = QLabel(msg1) + lbl2 = QLabel(msg2) + layout = QVBoxLayout() + layout.addWidget(lbl1) + layout.addWidget(lbl2) + # Set dialog layout + self.setLayout(layout) + self.setMinimumSize(350,300) + self.show() + +def run_dialog_standalone(dlgclass, *args, **keywords): + app = QApplication(sys.argv) + QObject.connect(app, SIGNAL('lastWindowClosed()'), app, SLOT('quit()')) + + win = dlgclass(*args, **keywords) + app.exec_() diff --git a/src/guiQML/viewmanager.py b/src/guiQML/viewmanager.py new file mode 100644 index 000000000..e73509af5 --- /dev/null +++ b/src/guiQML/viewmanager.py @@ -0,0 +1,156 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2010 Benny Malengier +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +The main view +""" + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- +OPENGL = True + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import sys, os + +#------------------------------------------------------------------------- +# +# set up logging +# +#------------------------------------------------------------------------- +import logging +LOG = logging.getLogger(".") + +#------------------------------------------------------------------------- +# +# QT modules +# +#------------------------------------------------------------------------- +from PySide import QtCore +from PySide import QtGui +from PySide import QtDeclarative +from PySide import QtOpenGL + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +import const +from cli.grampscli import CLIManager, CLIDbLoader +from gen.ggettext import gettext as _ +from guiQML.views.dbman import DbManager +from guiQML.questiondialog import ErrorDialog + +#------------------------------------------------------------------------- +# +# ViewManager +# +#------------------------------------------------------------------------- + +class ViewManager(CLIManager): + """ + Manages main widget by holding what state it is in. + """ + def __init__(self, dbstate): + """ + The viewmanager is initialised with a dbstate on which GRAMPS is + working. + """ + self.__centralview = None + CLIManager.__init__(self, dbstate, False) + self.db_loader = CLIDbLoader(self.dbstate) + #there is one DeclarativeEngine for global settings + self.__build_main_window() + + def __build_main_window(self): + """ + Builds the QML interface + """ + self.mainwindow = QtGui.QMainWindow() + self.mainview = QtDeclarative.QDeclarativeView() + if OPENGL: + glw = QtOpenGL.QGLWidget() + self.mainview.setViewport(glw) + self.mainview.setResizeMode(QtDeclarative.QDeclarativeView.SizeRootObjectToView) + self.engine = self.mainview.engine() + self.engine.rootContext().setBaseUrl(QtCore.QUrl.fromLocalFile( + os.path.join(const.ROOT_DIR, "guiQML"))) + + #set up the family tree list to select from + self.dbman = DbManager(self.dbstate, self.engine, self.load_db) + + def post_init_interface(self): + """ + Showing the main window is deferred so that + ArgHandler can work without it always shown + """ + if not self.dbstate.db.is_open(): + self.__open_dbman(None) + else: + self.__open_centralview(None) + + def __open_dbman(self, obj): + """ + Called when the Open button is clicked, opens the DbManager + """ + self.dbman.show(self.mainview, self.mainwindow) + + def _errordialog(self, title, errormessage): + """ + Show the error. + In the GUI, the error is shown, and a return happens + """ + ErrorDialog(title, errormessage, parent=self.mainwindow) + return 1 + + def load_db(self, path): + """ + Load the db and set the interface to the central widget + """ + self.db_loader.read_file(path) + self.__open_centralview(None) + + def __open_centralview(self, obj): + """ + set interface to the central widget + """ + if not self.__centralview: + from guiQML.views.centralview import CentralView + self.__centralview = CentralView(self.dbstate, self.engine, + self.open_view) + self.__centralview.show(self.mainview, self.mainwindow) + + def open_view(self, viewclass, *args): + """ + set interface to the given view: + """ + ##we should destroy views that are double + ##we should do some caching of views so as to move quickly? + newview = viewclass(self.dbstate, self.engine, *args) + newview.show(self.mainview, self.mainwindow) diff --git a/src/guiQML/views/__init__.py b/src/guiQML/views/__init__.py new file mode 100644 index 000000000..53298e077 --- /dev/null +++ b/src/guiQML/views/__init__.py @@ -0,0 +1,29 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2010 Benny Malengier +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +Package init for different views in guiQML. +""" + +# DO NOT IMPORT METHODS/CLASSES FROM src/guiQML HERE ! Only __all__ + +__all__ = [ ] diff --git a/src/guiQML/views/centralview.py b/src/guiQML/views/centralview.py new file mode 100644 index 000000000..63a79c0df --- /dev/null +++ b/src/guiQML/views/centralview.py @@ -0,0 +1,174 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2010 Benny Malengier +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +The main view from where other views are started +""" + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- + + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import sys, os + +#------------------------------------------------------------------------- +# +# set up logging +# +#------------------------------------------------------------------------- +import logging +LOG = logging.getLogger(".") + +#------------------------------------------------------------------------- +# +# QT modules +# +#------------------------------------------------------------------------- +from PySide import QtCore +from PySide import QtGui +from PySide import QtDeclarative +from PySide import QtOpenGL + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +import const +from gen.ggettext import gettext as _ + +#------------------------------------------------------------------------- +# +# Classes +# +#------------------------------------------------------------------------- + +class DetailView(QtCore.QObject): + """ + Data known about a detail view that can be launched + """ + def __init__(self, name): + QtCore.QObject.__init__(self) + self.__name = name + + def _name(self): + return self.__name + + changed = QtCore.Signal() + + #make Model.Column.property available in QML + name = QtCore.Property(unicode, _name, notify=changed) + +class DetViewSumModel(QtCore.QAbstractListModel): + """ + A simple ListModel for the different detailed views + """ + COLUMNS = ('name', ) + + def __init__(self, detviews): + QtCore.QAbstractListModel.__init__(self) + self._detviews = detviews + self.setRoleNames(dict(enumerate(DetViewSumModel.COLUMNS))) + + def rowCount(self, parent=QtCore.QModelIndex()): + return len(self._detviews) + + def data(self, index, role): + print 'role', role, DetViewSumModel.COLUMNS.index('name') + if index.isValid() and role == DetViewSumModel.COLUMNS.index('name'): + return self._detviews[index.row()] + return None + +#------------------------------------------------------------------------- +# +# CentralView +# +#------------------------------------------------------------------------- + +class CentralView(QtCore.QObject): + """ + Manages family tree list widget + """ + def __init__(self, dbstate, engine, viewshow): + """ + The manager is initialised with a dbstate on which GRAMPS is + working, and the engine to use context from. + """ + self.dbstate = dbstate + self.__viewshow = viewshow + QtCore.QObject.__init__(self) + self.const = { + 'titlelabel': unicode("%s" % self.dbstate.db.get_dbname()), + } + print self.const['titlelabel'] + #there is one DeclarativeEngine for global settings + self.__build_main_window(engine) + + def __build_main_window(self, engine): + """ + Builds the QML interface + """ + parentcontext = engine.rootContext() + #Create a context for the family tree list + self.centralviewcontext = QtDeclarative.QDeclarativeContext(parentcontext) + #Create ListModel to use + detviews = DetViewSumModel([DetailView('People')]) + + #register them in the context + self.centralviewcontext.setContextProperty('Const', self.const) + self.centralviewcontext.setContextProperty('CentralView', self) + self.centralviewcontext.setContextProperty('DetViewSumModel', detviews) + + #create a Component to show + self.centralview = QtDeclarative.QDeclarativeComponent(engine) + self.centralview.loadUrl(QtCore.QUrl.fromLocalFile( + os.path.join(const.ROOT_DIR, "guiQML", 'views', 'centralview.qml'))) + #and obtain the QObject of it + self.Qcentralview = self.centralview.create(self.centralviewcontext) + + def show(self, graphicsview, mainwindow): + """ + Paint the Component on the View and put it in the given mainwindow. + """ + #scene.addItem(self.Qfamtreeview) + graphicsview.setRootObject(self.Qcentralview) + graphicsview.show(); + mainwindow.setCentralWidget(graphicsview) + mainwindow.show() + + @QtCore.Slot(QtCore.QObject) + def detviewSelected(self, detview): + """ + We load the selected family tree + """ + print 'User clicked on:', detview.name + #now only Person piece to click on, so start that + from guiQML.views.personview import QMLPersonList + self.__viewshow(QMLPersonList) diff --git a/src/guiQML/views/centralview.qml b/src/guiQML/views/centralview.qml new file mode 100644 index 000000000..5113c83a8 --- /dev/null +++ b/src/guiQML/views/centralview.qml @@ -0,0 +1,65 @@ +/*# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2010 Benny Malengier +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id: __init__.py 13807 2009-12-15 05:56:12Z pez4brian $ +*/ + +import Qt 4.7 + +import "../qmlwidgets" as Widgets + +Rectangle{ + id: container + color: "#343434" + ListView { + id: detviewSumList + y: 25 + width: parent.width + height: parent.height-25 + model: DetViewSumModel + delegate: Component { + Rectangle { + width: detviewSumList.width + height: 40 + color: ((index % 2 == 0)?'#222':'#111') + Text { + id: detviewname + elide: Text.ElideRight + text: model.name.name + color: 'white' + font.bold: true + anchors.leftMargin: 10 + anchors.fill: parent + verticalAlignment: Text.AlignVCenter + } + MouseArea { + anchors.fill: parent + onClicked: { + CentralView.detviewSelected(model.name) + } + } + } + } + } + // TOP BAR + Widgets.TopBar { + text: Const.titlelabel + } +} diff --git a/src/guiQML/views/dbman.py b/src/guiQML/views/dbman.py new file mode 100644 index 000000000..74e0aae45 --- /dev/null +++ b/src/guiQML/views/dbman.py @@ -0,0 +1,225 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2010 Benny Malengier +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +The main view +""" + +#------------------------------------------------------------------------- +# +# Constants +# +#------------------------------------------------------------------------- +OPENGL = True + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import sys, os + +#------------------------------------------------------------------------- +# +# set up logging +# +#------------------------------------------------------------------------- +import logging +LOG = logging.getLogger(".") + +#------------------------------------------------------------------------- +# +# QT modules +# +#------------------------------------------------------------------------- +from PySide import QtCore +from PySide import QtGui +from PySide import QtDeclarative +from PySide import QtOpenGL + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +import const +from cli.clidbman import CLIDbManager, NAME_FILE, time_val +from gen.ggettext import gettext as _ + +#------------------------------------------------------------------------- +# +# Classes +# +#------------------------------------------------------------------------- + +#open_icon = QtGui.QIcon.fromTheme('open') +FAMTREE_ICONPATH = os.path.join(const.IMAGE_DIR, '22x22', 'gramps.png') + +class FamTreeWrapper(QtCore.QObject): + """ + A QObject wrapper + """ + def __init__(self, thing, dbman): + QtCore.QObject.__init__(self) + self.__dbman = dbman + self.__name = thing[CLIDbManager.IND_NAME] + self.__path = thing[CLIDbManager.IND_PATH] + self.__path_namefile = thing[CLIDbManager.IND_PATH_NAMEFILE] + self.__last_access = thing[CLIDbManager.IND_TVAL_STR] + self.__use_icon = thing[CLIDbManager.IND_USE_ICON_BOOL] + self.__icon = thing[CLIDbManager.IND_STOCK_ID] + + changed = QtCore.Signal() + changed_name = QtCore.Signal() + + def _name(self): return self.__name + def _path(self): return self.__path + def _last_access(self): return self.__last_access + def _use_icon(self): return self.__use_icon + def _icon(self): return self.__icon + + def _set_name(self, name): + self.__name = name + self.__dbman.rename_database(self.__path_namefile, name) + self.changed_name.emit() + + name = QtCore.Property(unicode, _name, _set_name, notify=changed_name) + path = QtCore.Property(unicode, _path, notify=changed) + last_access = QtCore.Property(unicode, _last_access, notify=changed) + use_icon = QtCore.Property(bool, _use_icon, notify=changed) + icon = QtCore.Property(unicode, _icon, notify=changed) + +class FamTreeListModel(QtCore.QAbstractListModel): + """ + A simple ListModel + """ + COLUMNS = ('name', 'path', 'last_access', 'use_icon', 'icon') + + def __init__(self, famtrees): + QtCore.QAbstractListModel.__init__(self) + self._famtrees = famtrees + self.setRoleNames(dict(enumerate(FamTreeListModel.COLUMNS))) + + def rowCount(self, parent=QtCore.QModelIndex()): + return len(self._famtrees) + + def data(self, index, role): + if index.isValid() and role == FamTreeListModel.COLUMNS.index('name'): + return self._famtrees[index.row()] + return None + + def append_famtree(self, famtree): + """ + Append a FamTreeWrapper to the family tree litsmodel + """ + self.beginInsertRows(QtCore.QModelIndex(), self.rowCount(), self.rowCount()) + self._famtrees.append(famtree) + self.endInsertRows() + +#------------------------------------------------------------------------- +# +# DbManager +# +#------------------------------------------------------------------------- + +class DbManager(CLIDbManager, QtCore.QObject): + """ + Manages family tree list widget + """ + def __init__(self, dbstate, engine, onselectcallback): + """ + The manager is initialised with a dbstate on which GRAMPS is + working, and the engine to use context from. + """ + self.__busy = False + self.__onselect = onselectcallback + QtCore.QObject.__init__(self) + CLIDbManager.__init__(self, dbstate) + #constants needed in the QML + self.const = { + 'titlelabel': "Gramps - %s" % _("Family Trees"), + 'addbtnlbl': _("Add a Family Tree"), + 'famtreeicon': FAMTREE_ICONPATH + } + #there is one DeclarativeEngine for global settings + self.__build_main_window(engine) + + def __build_main_window(self, engine): + """ + Builds the QML interface + """ + parentcontext = engine.rootContext() + #Create a context for the family tree list + self.famtreecontext = QtDeclarative.QDeclarativeContext(parentcontext) + #Create ListModel to use + famtreesQT = [FamTreeWrapper(obj, self) for obj in self.current_names] + self.famtrees = FamTreeListModel(famtreesQT) + + #register them in the context + self.famtreecontext.setContextProperty('Const', self.const) + self.famtreecontext.setContextProperty('DbManager', self) + self.famtreecontext.setContextProperty('FamTreeListModel', self.famtrees) + + #create a Component to show + self.famtreeview = QtDeclarative.QDeclarativeComponent(engine) + self.famtreeview.loadUrl(QtCore.QUrl.fromLocalFile( + os.path.join(const.ROOT_DIR, "guiQML", 'views', 'dbman.qml'))) + #and obtain the QObject of it + self.Qfamtreeview = self.famtreeview.create(self.famtreecontext) + + def show(self, graphicsview, mainwindow): + """ + Paint the Component on the View and put it in the given mainwindow. + """ + #scene.addItem(self.Qfamtreeview) + graphicsview.setRootObject(self.Qfamtreeview) + graphicsview.show(); + mainwindow.setCentralWidget(graphicsview) + mainwindow.show() + + @QtCore.Slot(QtCore.QObject) + def famtreeSelected(self, wrapper): + """ + We load the selected family tree + """ + if self.__busy: + return + self.__busy = True + self.__onselect(wrapper._path()) + self.__busy = False + + @QtCore.Slot(QtCore.QObject) + def addfamtree(self, _): + """ + We add a family tree + """ + if self.__busy: + return + self.__busy = True + print 'User clicked on:', 'add fam tree' + new_path, title = self.create_new_db_cli(None) + path_name = os.path.join(new_path, NAME_FILE) + (tval, last) = time_val(new_path) + self.famtrees.append_famtree(FamTreeWrapper([title, new_path, path_name, + last, tval, False, ''])) + self.__busy = False + diff --git a/src/guiQML/views/dbman.qml b/src/guiQML/views/dbman.qml new file mode 100644 index 000000000..74779132c --- /dev/null +++ b/src/guiQML/views/dbman.qml @@ -0,0 +1,200 @@ +/*# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2010 Benny Malengier +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id: __init__.py 13807 2009-12-15 05:56:12Z pez4brian $ +*/ + +import Qt 4.7 + +import "../qmlwidgets" as Widgets + +Rectangle{ + id: container + color: "#343434" + width: 400 + height: 600 + + //Delegate for a famtree entry. Two modes: + // 1. List mode (default): shows just the name and allows selection + // 2. Details mode: show extra information + Component { + id: famtreeDelegate + Item { + id: famtree + // Create a property to contain the visibility of the details. + // We bind multiple element's opacity to this one property, + property real detailsOpacity : 0 + + width: pythonList.width + height: 30 + + Rectangle { + id: background + anchors.fill: parent + color: ((index % 2 == 0)?'#222':'#111') + radius: 5 //rounded corners + } + //click shows details, close button must be clicked to go back to normal + MouseArea { + anchors.fill: parent + onClicked: { + famtree.state = 'Details' + } + } + //Now the data on the background, detailsOpacity for what to see + Row{ + id: topfamtreelayout + width: parent.width + anchors.verticalCenter: parent.verticalCenter + x:10 + spacing:10 + Column { + id: innerfamtreecol + width: parent.width - 70; + spacing: 5 + Text { + id: title + text: model.name.name + color: 'white' + font.bold: true + opacity: (famtree.detailsOpacity ? 0 : 1 ) + } + Item { + id: titleEdit + height: 20 + width: parent.width + opacity: famtree.detailsOpacity + + TextInput { + id: titleinput + text: model.name.name + anchors.fill: parent.fill + color: 'white' + cursorVisible: true; font.bold: true + } + Keys.forwardTo: [(returnKey), (titleinput)] + Item { + id: returnKey + Keys.onReturnPressed: model.name.name = titleinput.text + Keys.onEnterPressed: model.name.name = titleinput.text + Keys.onEscapePressed: titleinput.text = model.name.name + } + } + Text { + id: lastaccess + elide: Text.ElideRight + text: model.name.last_access + color: 'white' + font.bold: true + opacity: famtree.detailsOpacity + } + } + Image { + id:famtreeimage + anchors.verticalCenter: topfamtreelayout.verticalCenter + width: 22; height: 22 + anchors.rightMargin: 20 + source: Const.famtreeicon + opacity: famtree.detailsOpacity + } + } + Row{ + id: buttonfamtreelayout + anchors.top: topfamtreelayout.bottom + anchors.rightMargin: 20 + spacing: 20 + // A button to select the famtree + Widgets.TextButton { + y: 10 + opacity: famtree.detailsOpacity + text: "Open" + onClicked: { + DbManager.famtreeSelected(model.name) + } + }// A button to close the detailed view, i.e. set the state back to default (''). + Widgets.TextButton { + y: 10 + opacity: famtree.detailsOpacity + text: "Close" + onClicked: famtree.state = ''; + } + } + + states: State { + name: "Details" + PropertyChanges { target: famtree; detailsOpacity: 1; x: 0 } // Make details visible + PropertyChanges { target: famtree; height: 120 } // Fill the entire list area with the detailed view + + // Move the list so that this item is at the top. + PropertyChanges { target: famtree.ListView.view; explicit: true; contentY: famtree.y } + + // Disallow flicking while in in detailed view + PropertyChanges { target: famtree.ListView.view; interactive: false } + } + + transitions: Transition { + // Make the state changes smooth + ParallelAnimation { + ColorAnimation { property: "color"; duration: 500 } + NumberAnimation { duration: 300; properties: "detailsOpacity,x,contentY,height,width" } + } + } + } + } + ListView { + id: pythonList + y: 25 + width: parent.width + height: parent.height-20-40 + //anchors.top: container.top + //anchors.bottom: container.bottom + //anchors.fill: parent + //anchors.leftMargin: 5 + //anchors.rightMargin: 5 + model: FamTreeListModel + delegate: famtreeDelegate + } + // TOP BAR + Widgets.TopBar { + text: Const.titlelabel + } + // BOTTOM BAR + Item { + id: bottombar + y: parent.height-40 + width: parent.width + height: 40 + Rectangle { + anchors.fill: parent + color: "#343434" + } + Row { + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.verticalCenter: parent.verticalCenter + spacing: 10 + Widgets.TextButton { + text: Const.addbtnlbl + onClicked: {DbManager.addfamtree("") + } + } + } + } +} \ No newline at end of file diff --git a/src/guiQML/views/peopleview.qml b/src/guiQML/views/peopleview.qml new file mode 100644 index 000000000..d988e7b6c --- /dev/null +++ b/src/guiQML/views/peopleview.qml @@ -0,0 +1,66 @@ +/*# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2010 Benny Malengier +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id: __init__.py 13807 2009-12-15 05:56:12Z pez4brian $ +*/ + +import Qt 4.7 + +import "../qmlwidgets" as Widgets + +Rectangle{ + id: container + color: "#343434" + ListView { + id: personlist + y: 25 + width: parent.width + height: parent.height-25 + model: QMLPersonListModel + delegate: Component { + Rectangle { + width: personlist.width + height: 40 + radius: 10 + color: ((index % 2 == 0)?'#222':'#111') + Text { + id: personname + elide: Text.ElideRight + text: model.name.name + color: 'white' + font.bold: true + anchors.leftMargin: 15 + anchors.fill: parent + verticalAlignment: Text.AlignVCenter + } + MouseArea { + anchors.fill: parent + onClicked: { + QMLPersonList.detailsSelected(model.name) + } + } + } + } + } + // TOP BAR + Widgets.TopBar { + text: Const.titlelabel + } +} diff --git a/src/guiQML/views/personview.py b/src/guiQML/views/personview.py new file mode 100644 index 000000000..5d57b7b50 --- /dev/null +++ b/src/guiQML/views/personview.py @@ -0,0 +1,214 @@ +# +# Gramps - a GTK+/GNOME based genealogy program +# +# Copyright (C) 2010 Benny Malengier +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +# $Id$ + +""" +The listview with all people in the database +""" + +#------------------------------------------------------------------------- +# +# Standard python modules +# +#------------------------------------------------------------------------- +import sys, os + +#------------------------------------------------------------------------- +# +# set up logging +# +#------------------------------------------------------------------------- +import logging +LOG = logging.getLogger(".") + +#------------------------------------------------------------------------- +# +# QT modules +# +#------------------------------------------------------------------------- +from PySide import QtCore +from PySide import QtGui +from PySide import QtDeclarative +from PySide import QtOpenGL + +#------------------------------------------------------------------------- +# +# GRAMPS modules +# +#------------------------------------------------------------------------- +import const +from Utils import conv_unicode_tosrtkey_ongtk +from gen.ggettext import gettext as _ +from gen.display.name import displayer as name_displayer +from gen.lib import Name +##TODO: follow must be refractored so as not to require GTK +from gui.views.treemodels.flatbasemodel import FlatNodeMap + +#------------------------------------------------------------------------- +# +# Classes +# +#------------------------------------------------------------------------- + +## Copied from gui/views/models/peoplemodel, we need a GTK independent base! +COLUMN_NAME = 3 + +class QMLPerson(QtCore.QObject): + """ + Person object encapsulated for QML + We only store handle and ref to database, obtain data from db as needed + """ + def __init__(self, db, personhandle): + QtCore.QObject.__init__(self) + self.__handle = personhandle + self.__db = db + + def _name(self): + return name_displayer.display(self.__db.get_person_from_handle(self.__handle)) + + #dummy signal for things that change must not be tracked + dummychanged = QtCore.Signal() + + #make Model.Column.property available in QML + name = QtCore.Property(unicode, _name, notify=dummychanged) + +class QMLPersonListModel(QtCore.QAbstractListModel): + """ + A simple ListModel for the People in the database + """ + ROLE_NAME_COL = 0 + COLUMNS = ((ROLE_NAME_COL, 'name'), ) + + def __init__(self, db): + QtCore.QAbstractListModel.__init__(self) + self.__db = db + self.gen_cursor = db.get_person_cursor + self.sort_func = self.sort_name + self.node_map = FlatNodeMap() + self._reverse = False + #build node map with all peopls + allkeys = self.sort_keys() + ident = True + dlist = allkeys + self.node_map.set_path_map(dlist, allkeys, identical=ident, + reverse=self._reverse) + + #every column has a role from 0 to nrcol-1, and name as in COLUMNS + self.setRoleNames(dict(QMLPersonListModel.COLUMNS)) + #we create an array with all the QMLPerson that we need so + #that we can match a rowindex with correct QMLPerson + self._qmlpersons = [] + for _, handle in self.node_map.full_srtkey_hndl_map(): + self._qmlpersons.append(QMLPerson(self.__db, handle)) + + def sort_keys(self): + """ + Return the (sort_key, handle) list of all data that can maximally + be shown. + This list is sorted ascending, via localized string sort. + conv_unicode_tosrtkey_ongtk which uses strxfrm, which is apparently + broken in Win ?? --> they should fix base lib, we need strxfrm, fix it + in the Utils module. + """ + # use cursor as a context manager + with self.gen_cursor() as cursor: + #loop over database and store the sort field, and the handle + return sorted((map(conv_unicode_tosrtkey_ongtk, + self.sort_func(data)), key) for key, data in cursor) + + def sort_name(self, data): + n = Name() + n.unserialize(data[COLUMN_NAME]) + return (n.get_primary_surname().get_surname(), n.get_first_name()) + + def rowCount(self, parent=QtCore.QModelIndex()): + return self.__db.get_number_of_people() + + def data(self, index, role): + """ + Obtain QMLPerson to show. Role is a number that corresponds to a column, + different columns can obtain data from different objects + """ + if index.isValid() and role <= QMLPersonListModel.ROLE_NAME_COL: + return self._qmlpersons[index.row()] + return None + +#------------------------------------------------------------------------- +# +# CentralView +# +#------------------------------------------------------------------------- + +class QMLPersonList(QtCore.QObject): + """ + Manages family tree list widget + """ + def __init__(self, dbstate, engine): + """ + The manager is initialised with a dbstate on which GRAMPS is + working, and the engine to use context from. + """ + self.dbstate = dbstate + QtCore.QObject.__init__(self) + self.const = { + 'titlelabel': "%s" % self.dbstate.db.get_dbname(), + } + #there is one DeclarativeEngine for global settings + self.__build_main_window(engine) + + def __build_main_window(self, engine): + """ + Builds the QML interface + """ + parentcontext = engine.rootContext() + #Create a context for the family tree list + self.qmlpersonlistcontext = QtDeclarative.QDeclarativeContext(parentcontext) + #Create ListModel to use + personlistmodel = QMLPersonListModel(self.dbstate.db) + + #register them in the context + self.qmlpersonlistcontext.setContextProperty('Const', self.const) + self.qmlpersonlistcontext.setContextProperty('QMLPersonList', self) + self.qmlpersonlistcontext.setContextProperty('QMLPersonListModel', personlistmodel) + + #create a Component to show + self.qmlpersonlist = QtDeclarative.QDeclarativeComponent(engine) + self.qmlpersonlist.loadUrl(QtCore.QUrl.fromLocalFile( + os.path.join(const.ROOT_DIR, "guiQML", 'views', 'peopleview.qml'))) + #and obtain the QObject of it + self.Qpersonlist = self.qmlpersonlist.create(self.qmlpersonlistcontext) + + def show(self, graphicsview, mainwindow): + """ + Paint the Component on the View and put it in the given mainwindow. + """ + #scene.addItem(self.Qfamtreeview) + graphicsview.setRootObject(self.Qpersonlist) + graphicsview.show(); + mainwindow.setCentralWidget(graphicsview) + mainwindow.show() + + @QtCore.Slot(QtCore.QObject) + def detailsSelected(self, qmlperson): + """ + We load the selected family tree + """ + print 'User clicked on:', qmlperson.name