gtg team mailing list archive
-
gtg team
-
Mailing list archive
-
Message #03507
[Merge] lp:~izidor/gtg/export-rework into lp:gtg
Izidor Matušov has proposed merging lp:~izidor/gtg/export-rework into lp:gtg.
Requested reviews:
Gtg developers (gtg)
Related bugs:
Bug #896988 in Getting Things GNOME!: "Export and print: Generating PDF does not work"
https://bugs.launchpad.net/gtg/+bug/896988
For more details, see:
https://code.launchpad.net/~izidor/gtg/export-rework/+merge/104637
Major rework of export plugin:
- export to PDF works now. You have to have packages pdflatex, pdftk and pdfjam
- icon shows again in the toolbar
- clean codebase, pylint compatible
- when exporting tasks finished last week, it shows only done tasks, not dismissed tasks (for status report I need list of accomplished things)
Looking forward to feedback!
--
https://code.launchpad.net/~izidor/gtg/export-rework/+merge/104637
Your team Gtg developers is requested to review the proposed merge of lp:~izidor/gtg/export-rework into lp:gtg.
=== modified file 'CHANGELOG'
--- CHANGELOG 2012-04-22 15:16:24 +0000
+++ CHANGELOG 2012-05-03 22:12:18 +0000
@@ -18,6 +18,7 @@
* Reimplement the tag context menu as a widget, in order to - hopefully - be able to implement an enhanced context menu with, for instance, a color picker.
* Background Colors option was moved from View menu into preferences dialog
* Reorganised notification area menu (New task, Show browser, <tasks>, Quit)
+ * Improved Export plugin, export to PDF works now, by Izidor Matušov
2012-02-13 Getting Things GNOME! 0.2.9
* Big refractorization of code, now using liblarch
=== modified file 'GTG/plugins/export.gtg-plugin'
--- GTG/plugins/export.gtg-plugin 2012-03-05 15:23:05 +0000
+++ GTG/plugins/export.gtg-plugin 2012-05-03 22:12:18 +0000
@@ -5,7 +5,7 @@
Description="""Exports the tasks in the current view into
a variety of formats. You can also personalize the format
of your tasks by writing your own template"""
-Authors=Luca Invernizzi <invernizzi.l@xxxxxxxxx>
-Version=0.1.1
+Authors=Luca Invernizzi <invernizzi.l@xxxxxxxxx>, Izidor Matušov <izidor.matusov@xxxxxxxxx>
+Version=0.2
Enabled=False
Dependencies=Cheetah,
=== modified file 'GTG/plugins/export/__init__.py'
--- GTG/plugins/export/__init__.py 2012-03-05 15:23:05 +0000
+++ GTG/plugins/export/__init__.py 2012-05-03 22:12:18 +0000
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2009 - Luca Invernizzi <invernizzi.l@xxxxxxxxx>
+# 2012 - Izidor Matušov <izidor.matusov@xxxxxxxxx>
#
# 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
@@ -14,8 +15,10 @@
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
-from GTG.plugins.export.export import pluginExport
-
-
-#suppress pyflakes warning (given by make lint)
-if False == True: pluginExport()
+""" Initialize export pugin """
+
+from GTG.plugins.export.export import PluginExport
+
+# Make pyflakes happy
+if False:
+ PluginExport()
=== modified file 'GTG/plugins/export/export.py'
--- GTG/plugins/export/export.py 2012-04-01 12:40:28 +0000
+++ GTG/plugins/export/export.py 2012-05-03 22:12:18 +0000
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2009 - Luca Invernizzi <invernizzi.l@xxxxxxxxx>
+# 2012 - Izidor Matušov <izidor.matusov@xxxxxxxxx>
#
# 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
@@ -14,329 +15,292 @@
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
+""" Export plugin
+Plugin for exporting into nice lists in TXT, HTML or PDF """
+
import os
-import gtk
import shutil
+import webbrowser
+
+from xdg.BaseDirectory import xdg_config_home
import gobject
-import tempfile
-import threading
-import subprocess
-from Cheetah.Template import Template as CheetahTemplate
-from xdg.BaseDirectory import xdg_config_home
-
-from GTG import _
-from GTG.plugins.export.task_str import tree_to_TaskStr
-from GTG.plugins.export.templates import TemplateFactory
-from GTG.tools.logger import Log
-
-
-
-class pluginExport:
-
+import gtk
+
+from GTG import _
+from GTG.plugins.export.task_str import get_task_wrappers
+from GTG.plugins.export.templates import Template, get_templates_paths
+
+
+def get_user_dir(key):
+ """
+ http://www.freedesktop.org/wiki/Software/xdg-user-dirs
+ XDG_DESKTOP_DIR
+ XDG_DOWNLOAD_DIR
+ XDG_TEMPLATES_DIR
+ XDG_PUBLICSHARE_DIR
+ XDG_DOCUMENTS_DIR
+ XDG_MUSIC_DIR
+ XDG_PICTURES_DIR
+ XDG_VIDEOS_DIR
+
+ Taken from FrontBringer
+ (distributed under the GNU GPL v3 license),
+ courtesy of Jean-François Fortin Tam.
+ """
+ user_dirs_dirs = os.path.join(xdg_config_home, "user-dirs.dirs")
+ user_dirs_dirs = os.path.expanduser(user_dirs_dirs)
+ if not os.path.exists(user_dirs_dirs):
+ return
+ for line in open(user_dirs_dirs, "r"):
+ if line.startswith(key):
+ return os.path.expandvars(line[len(key)+2:-2])
+
+
+def get_desktop_dir():
+ """ Returns path to desktop dir based on XDG.
+
+ If XDG is not setup corectly, use home directory instead """
+ desktop_dir = get_user_dir("XDG_DESKTOP_DIR")
+ if desktop_dir is not None and os.path.exists(desktop_dir):
+ return desktop_dir
+ else:
+ return os.path.expanduser('~')
+
+
+class PluginExport:
+ """ Export plugin - handle UI and trigger exporting tasks """
+
+ # Allow initilization outside __init__() and don't complain
+ # about too many attributes
+ # pylint: disable-msg=W0201,R0902
+
+ PLUGIN_NAME = "export"
+
+ DEFAULT_PREFERENCES = {
+ "menu_entry": True,
+ "toolbar_entry": True,
+ }
def __init__(self):
- '''Initialize all the GTK widgets'''
- self.__init_gtk()
+ self.filename = None
+ self.template = None
def activate(self, plugin_api):
- '''loads the saved preferences'''
+ """ Loads saved preferences """
self.plugin_api = plugin_api
- self.preferences_load()
- self.preferences_apply()
+ self._init_gtk()
+ self._preferences_load()
+ self._preferences_apply()
- def deactivate(self, plugin_api):
- '''Removes the gtk widgets before quitting'''
- self.__gtk_deactivate()
+ def deactivate(self, plugin_api): # pylint: disable-msg=W0613
+ """ Removes the gtk widgets before quitting """
+ self._gtk_deactivate()
## CALLBACK AND CORE FUNCTIONS ################################################
-
- def load_template(self):
- self.template = TemplateFactory().create_template(\
- self.combo_get_path(self.combo))
- return self.template
-
- def export_generate(self, document_ready):
- #Template loading and cutting
+ def on_export_start(self, saving):
+ """ Start generating a document.
+ If saving == True, ask user where to store the document. Otherwise,
+ open it afterwards. """
+
+ model = self.combo.get_model()
+ active = self.combo.get_active()
+ self.template = Template(model[active][0])
+
+ self.filename = None
+ if saving:
+ self.filename = self.choose_file()
+ if self.filename is None:
+ return
+
+ self.save_button.set_sensitive(False)
+ self.open_button.set_sensitive(False)
+
+ try:
+ tasks = self.get_selected_tasks()
+ self.template.generate(tasks, self.plugin_api,
+ self.on_export_finished)
+ except Exception, err:
+ self.show_error_dialog(
+ _("GTG could not generate the document: %s") % err)
+ raise
+
+ def on_export_finished(self):
+ """ Save generated file or open it, reenable buttons
+ and hide dialog """
+ document_path = self.template.get_document_path()
+ if document_path:
+ if self.filename:
+ shutil.copyfile(document_path, self.filename)
+ else:
+ webbrowser.open(document_path)
+ else:
+ self.show_error_dialog("Document creation failed. "
+ "Ensure you have all needed programs.")
+
+ self.save_button.set_sensitive(True)
+ self.open_button.set_sensitive(True)
+ self.export_dialog.hide()
+
+ def get_selected_tasks(self):
+ """ Filter tasks based on user option """
timespan = None
+ req = self.plugin_api.get_requester()
+
if self.export_all_active.get_active():
- tree = self.plugin_api.get_requester().get_tasks_tree(name='active')
- tree.apply_filter('active')
+ treename = 'active'
elif self.export_all_finished.get_active():
- tree = self.plugin_api.get_requester().get_tasks_tree(name='closed')
- tree.apply_filter('closed')
+ treename = 'closed'
elif self.export_finished_last_week.get_active():
- tree = self.plugin_api.get_requester().get_tasks_tree(name='closed')
- tree.apply_filter('closed')
+ treename = 'closed'
timespan = -7
- meta_root_node = tree.get_root()
- root_nodes = [tree.get_node(c)
- for c in meta_root_node.get_children()]
- tasks_str = tree_to_TaskStr(tree, root_nodes, self.plugin_api, timespan)
- document = str(CheetahTemplate(
- file = self.template.get_path(),
- searchList = [{'tasks': tasks_str,
- 'plugin_api': self.plugin_api}]))
- self.__purge_saved_document()
- #we save the created document in a temporary file with the same suffix
- #as the template (it's script-friendly)
- with tempfile.NamedTemporaryFile(\
- suffix = ".%s" % self.template.get_suffix(),
- delete = False) as f:
- f.write(document)
- self.document_path = f.name
- if self.template.get_script_path():
- def __script_worker(self):
- try:
- self.document_path = \
- subprocess.Popen(args = ['/bin/sh',
- '-c',
- self.template.get_script_path() + \
- " " + self.document_path],
- shell = False,
- stdout = subprocess.PIPE\
- ).communicate()[0]
- except:
- pass
- if self.document_path == "ERROR":
- Log.debug("Document creation failed")
- self.document_path = None
- document_ready.set()
- worker_thread = threading.Thread(
- target = __script_worker,
- args = (self, ))
- worker_thread.setDaemon(True)
- worker_thread.start()
- else:
- document_ready.set()
-
- def export_execute_with_ui(self, document_ready):
- if not self.load_template():
- self.show_error_dialog(_("Template not found"))
- return False
- #REMOVE ME
- try:
- self.export_generate(document_ready)
- except Exception, e:
- self.show_error_dialog( \
- _("GTG could not generate the document: %s") % e)
- return False
- return True
-
- def on_export_open(self, widget = None, saving = False):
- document_ready = threading.Event()
- self.export_execute_with_ui(document_ready)
- self.save_button.set_sensitive(False)
- self.open_button.set_sensitive(False)
- if saving:
- filename = self.__get_filename_from_gtk_dialog()
- else:
- filename = None
- def __wait_for_document_ready(self, document_ready, filename, saving):
- document_ready.wait()
- if filename:
- if saving:
- shutil.copyfile(self.document_path, filename)
- else:
- subprocess.Popen(['xdg-open', self.document_path])
- gobject.idle_add(self.save_button.set_sensitive, True)
- gobject.idle_add(self.open_button.set_sensitive, True)
-
- event_thread = threading.Thread( \
- target = __wait_for_document_ready,
- args = (self, document_ready, filename, saving))
- event_thread.setDaemon(True)
- event_thread.start()
-
- def on_export_save(self, widget = None):
- self.on_export_open(saving = True)
-
- def hide(self):
- self.__gtk_hide()
- self.__purge_saved_document()
-
- def __purge_saved_document(self):
- try:
- os.remove(self.document_path)
- except:
- pass
-
-## GTK FUNCTIONS #############################################################
-
- def __init_gtk(self):
+
+ tree = req.get_tasks_tree(name=treename)
+ if treename not in tree.list_applied_filters():
+ tree.apply_filter(treename)
+
+ return get_task_wrappers(tree, timespan)
+
+
+## GTK FUNCTIONS ##############################################################
+ def _init_gtk(self):
+ """ Initialize all the GTK widgets """
self.menu_entry = False
self.toolbar_entry = False
- self.path = os.path.dirname(os.path.abspath(__file__))
+
self.menu_item = gtk.MenuItem(_("Export the tasks currently listed"))
- self.menu_item.connect('activate', self.__gtk_activate)
+ self.menu_item.connect('activate', self.show_dialog)
+ self.menu_item.show()
+
self.tb_button = gtk.ToolButton(gtk.STOCK_PRINT)
- self.tb_button.connect('clicked', self.__gtk_activate)
- self.builder = gtk.Builder()
- self.builder.add_from_file(os.path.join(
- os.path.dirname(os.path.abspath(__file__)) + \
- "/export.ui"))
- self.combo = self.builder.get_object("export_combo_templ")
- self.export_dialog = self.builder.get_object("export_dialog")
- self.export_image = self.builder.get_object("export_image")
- self.preferences_dialog = self.builder.get_object("preferences_dialog")
- self.pref_chbox_menu = self.builder.get_object("pref_chbox_menu")
- self.pref_chbox_toolbar = self.builder.get_object("pref_chbox_toolbar")
- self.description_label = self.builder.get_object("label_description")
- self.save_button = self.builder.get_object("export_btn_save")
- self.open_button = self.builder.get_object("export_btn_open")
-
- self.export_all_active = self.builder.get_object("export_all_active_rb")
- self.export_finished_last_week = self.builder.get_object("export_finished_last_week_rb")
- self.export_all_finished = self.builder.get_object("export_all_finished_rb")
-
- SIGNAL_CONNECTIONS_DIC = {
- "on_export_btn_open_clicked":
- self.on_export_open,
- "on_export_btn_save_clicked":
- self.on_export_save,
- "on_export_dialog_delete_event":
- self.__gtk_hide,
+ self.tb_button.connect('clicked', self.show_dialog)
+ self.tb_button.show()
+
+ builder = gtk.Builder()
+ cur_dir = os.path.dirname(os.path.abspath(__file__))
+ builder_file = os.path.join(cur_dir, "export.ui")
+ builder.add_from_file(builder_file)
+
+ self.combo = builder.get_object("export_combo_templ")
+ templates_list = gtk.ListStore(gobject.TYPE_STRING,
+ gobject.TYPE_STRING, gobject.TYPE_STRING, gobject.TYPE_STRING)
+ self.combo.set_model(templates_list)
+ cell = gtk.CellRendererText()
+ self.combo.pack_start(cell, True)
+ self.combo.add_attribute(cell, 'text', 1)
+
+ self.export_dialog = builder.get_object("export_dialog")
+ self.export_image = builder.get_object("export_image")
+ self.preferences_dialog = builder.get_object("preferences_dialog")
+ self.pref_menu = builder.get_object("pref_chbox_menu")
+ self.pref_toolbar = builder.get_object("pref_chbox_toolbar")
+ self.description_label = builder.get_object("label_description")
+ self.save_button = builder.get_object("export_btn_save")
+ self.open_button = builder.get_object("export_btn_open")
+
+ self.export_all_active = builder.get_object(
+ "export_all_active_rb")
+ self.export_finished_last_week = builder.get_object(
+ "export_finished_last_week_rb")
+ self.export_all_finished = builder.get_object(
+ "export_all_finished_rb")
+
+ builder.connect_signals({
+ "on_export_btn_open_clicked":
+ lambda widget: self.on_export_start(False),
+ "on_export_btn_save_clicked":
+ lambda widget: self.on_export_start(True),
+ "on_export_dialog_delete_event":
+ self._hide_dialog,
"on_export_combo_templ_changed":
- self.__on_combo_changed,
+ self.on_combo_changed,
"on_preferences_dialog_delete_event":
self.on_preferences_cancel,
"on_btn_preferences_cancel_clicked":
self.on_preferences_cancel,
"on_btn_preferences_ok_clicked":
- self.on_preferences_ok
- }
- self.builder.connect_signals(SIGNAL_CONNECTIONS_DIC)
-
- def __gtk_activate(self, widget):
- #Populating combo boxes
- self.export_dialog.set_transient_for(\
- self.plugin_api.get_ui().get_window())
- self.combo_decorator(self.combo, TemplateFactory().get_templates_paths())
+ self.on_preferences_ok,
+ })
+
+ def _gtk_deactivate(self):
+ """ Remove Toolbar Button and Menu item for this plugin """
+ if self.menu_entry:
+ self.plugin_api.remove_menu_item(self.menu_item)
+ self.menu_entry = False
+
+ if self.toolbar_entry:
+ self.plugin_api.remove_toolbar_item(self.tb_button)
+ self.toolbar_entry = False
+
+ def show_dialog(self, widget): # pylint: disable-msg=W0613
+ """ Show dialog with options for export """
+ parent_window = self.plugin_api.get_ui().get_window()
+ self.export_dialog.set_transient_for(parent_window)
+ self._update_combobox()
self.export_dialog.show_all()
- def __gtk_deactivate(self):
- try:
- self.plugin_api.remove_menu_item(self.menu_item)
- except:
- pass
- self.menu_entry = False
- try:
- self.plugin_api.remove_toolbar_item(self.tb_button)
- except:
- pass
- self.toolbar_entry = False
-
- def __gtk_hide(self, sender = None, data = None):
+ def _hide_dialog(self, sender=None, data=None): # pylint: disable-msg=W0613
+ """ Hide dialog """
self.export_dialog.hide()
return True
- def __on_combo_changed(self, widget = None):
- if self.load_template():
- image_path = self.template.get_image_path()
- if image_path:
- pixbuf = gtk.gdk.pixbuf_new_from_file(image_path)
- [w,h] = self.export_image.get_size_request()
- pixbuf = pixbuf.scale_simple(w, h, gtk.gdk.INTERP_BILINEAR)
- self.export_image.set_from_pixbuf(pixbuf)
- else:
- self.export_image.clear()
- description = self.template.get_description()
+ def _update_combobox(self):
+ """ Reload list of templates """
+ model = self.combo.get_model()
+ model.clear()
+
+ templates = get_templates_paths()
+ for path in templates:
+ template = Template(path)
+ model.append((path,
+ template.get_title(),
+ template.get_description(),
+ template.get_image_path()))
+
+ # wrap the combo-box if it's too long
+ if len(templates) > 15:
+ self.combo.set_wrap_width(5)
+ self.combo.set_active(0)
+
+ def on_combo_changed(self, combo):
+ """ Display details about the selected template """
+ model = combo.get_model()
+ active = combo.get_active()
+ if not 0 <= active < len(model):
+ return
+ description, image = model[active][2], model[active][3]
+
+ if image:
+ pixbuf = gtk.gdk.pixbuf_new_from_file(image)
+ width, height = self.export_image.get_size_request()
+ pixbuf = pixbuf.scale_simple(width, height,
+ gtk.gdk.INTERP_BILINEAR)
+ self.export_image.set_from_pixbuf(pixbuf)
+ else:
+ self.export_image.clear()
self.description_label.set_markup("<i>%s</i>" % description)
- def empty_tree_model(self, model):
- if model == None:
- return
- iter = model.get_iter_first()
- while iter:
- this_iter = iter
- iter = model.iter_next(iter)
- model.remove(this_iter)
-
- def combo_list_store(self, list_store, a_list):
- if list_store == None:
- list_store = gtk.ListStore(gobject.TYPE_STRING,
- gobject.TYPE_STRING)
- self.empty_tree_model(list_store)
- for template_path in a_list:
- iter = list_store.append()
- list_store.set(iter, 0,
- TemplateFactory().create_template(template_path).get_title())
- list_store.set(iter, 1, template_path)
- return self.export_list_store
-
- def combo_completion(self, list_store):
- completion = gtk.EntryCompletion()
- completion.set_minimum_key_length(0)
- completion.set_text_column(0)
- completion.set_inline_completion(True)
- completion.set_model(list_store)
-
- def combo_set_text(self, combobox, entry):
- model = combobox.get_model()
- index = combobox.get_active()
- if index > -1:
- entry.set_text(model[index][0])
-
- def combo_get_path(self, combobox):
- model = combobox.get_model()
- active = combobox.get_active()
- if active < 0:
- return None
- return model[active][1]
-
- def combo_decorator(self, combobox, a_list):
- first_run = not hasattr(self, "combo_templ_entry")
- if first_run:
- self.combo_templ_entry = gtk.Entry()
- combobox.add(self.combo_templ_entry)
- self.export_list_store = gtk.ListStore(gobject.TYPE_STRING,
- gobject.TYPE_STRING)
- self.combo_templ_entry.set_completion(
- self.combo_completion(self.export_list_store))
- combobox.set_model(self.export_list_store)
- combobox.connect('changed', self.combo_set_text,
- self.combo_templ_entry)
- #render the combo-box drop down menu
- cell = gtk.CellRendererText()
- combobox.pack_start(cell, True)
- combobox.add_attribute(cell, 'text', 0)
- #wrap the combo-box if it's too long
- if len(a_list) > 15:
- combobox.set_wrap_width(5)
- #populate the combo-box
- self.combo_list_store(self.export_list_store, a_list)
- if not hasattr(self, "combo_active"):
- self.combo_active = 0
- combobox.set_active(self.combo_active)
-
def show_error_dialog(self, message):
+ """ Display an error """
dialog = gtk.MessageDialog(
- parent = self.export_dialog,
- flags = gtk.DIALOG_DESTROY_WITH_PARENT,
- type = gtk.MESSAGE_ERROR,
- buttons = gtk.BUTTONS_OK,
+ parent = self.export_dialog,
+ flags = gtk.DIALOG_DESTROY_WITH_PARENT,
+ type = gtk.MESSAGE_ERROR,
+ buttons = gtk.BUTTONS_OK,
message_format = message)
- dialog.run()
+ dialog.run()
dialog.destroy()
- def __get_filename_from_gtk_dialog(self):
- chooser = gtk.FileChooserDialog(\
+ def choose_file(self):
+ """ Let user choose a file to save and return its path """
+ chooser = gtk.FileChooserDialog(
title = _("Choose where to save your list"),
parent = self.export_dialog,
action = gtk.FILE_CHOOSER_ACTION_SAVE,
- buttons = (gtk.STOCK_CANCEL,
- gtk.RESPONSE_CANCEL,
- gtk.STOCK_SAVE,
- gtk.RESPONSE_OK))
+ buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
+ gtk.STOCK_SAVE, gtk.RESPONSE_OK))
chooser.set_do_overwrite_confirmation(True)
- desktop_dir = self.get_user_dir("XDG_DESKTOP_DIR")
- #NOTE: using ./scripts/debug.sh, it doesn't detect the Desktop
- # dir, as the XDG directories are changed. That is why during
- # debug it defaults to the Home directory ~~Invernizzi~~
- if desktop_dir != None and os.path.exists(desktop_dir):
- chooser.set_current_folder(desktop_dir)
- else:
- chooser.set_current_folder(os.environ['HOME'])
chooser.set_default_response(gtk.RESPONSE_OK)
+ chooser.set_current_folder(get_desktop_dir())
response = chooser.run()
filename = chooser.get_filename()
chooser.destroy()
@@ -345,84 +309,61 @@
else:
return None
-## Helper methods ############################################################
-
- def get_user_dir(self, key):
- """
- http://www.freedesktop.org/wiki/Software/xdg-user-dirs
- XDG_DESKTOP_DIR
- XDG_DOWNLOAD_DIR
- XDG_TEMPLATES_DIR
- XDG_PUBLICSHARE_DIR
- XDG_DOCUMENTS_DIR
- XDG_MUSIC_DIR
- XDG_PICTURES_DIR
- XDG_VIDEOS_DIR
-
- Taken from FrontBringer
- (distributed under the GNU GPL v3 license),
- courtesy of Jean-François Fortin Tam.
- """
- user_dirs_dirs = os.path.expanduser(xdg_config_home + "/user-dirs.dirs")
- if os.path.exists(user_dirs_dirs):
- f = open(user_dirs_dirs, "r")
- for line in f.readlines():
- if line.startswith(key):
- return os.path.expandvars(line[len(key)+2:-2])
-
-## Preferences methods #########################################################
-
- DEFAULT_PREFERENCES = {"menu_entry": True,
- "toolbar_entry": True}
- PLUGIN_NAME = "export"
-
- def is_configurable(self):
+## Preferences methods ########################################################
+ @classmethod
+ def is_configurable(cls):
"""A configurable plugin should have this method and return True"""
return True
def configure_dialog(self, manager_dialog):
- self.preferences_load()
+ """ Display configuration dialog """
+ self._preferences_load()
self.preferences_dialog.set_transient_for(manager_dialog)
- self.pref_chbox_menu.set_active(self.preferences["menu_entry"])
- self.pref_chbox_toolbar.set_active(self.preferences["toolbar_entry"])
+ self.pref_menu.set_active(self.preferences["menu_entry"])
+ self.pref_toolbar.set_active(self.preferences["toolbar_entry"])
self.preferences_dialog.show_all()
- def on_preferences_cancel(self, widget = None, data = None):
+ def on_preferences_cancel(self, widget, data=None):
+ # pylint: disable-msg=W0613
+ """ Only hide the dialog """
self.preferences_dialog.hide()
return True
- def on_preferences_ok(self, widget = None, data = None):
- self.preferences["menu_entry"] = self.pref_chbox_menu.get_active()
- self.preferences["toolbar_entry"] = self.pref_chbox_toolbar.get_active()
- self.preferences_apply()
- self.preferences_store()
+ def on_preferences_ok(self, widget): # pylint: disable-msg=W0613
+ """ Apply and store new preferences """
+ self.preferences["menu_entry"] = self.pref_menu.get_active()
+ self.preferences["toolbar_entry"] = self.pref_toolbar.get_active()
self.preferences_dialog.hide()
- def preferences_load(self):
- data = self.plugin_api.load_configuration_object(self.PLUGIN_NAME,\
- "preferences")
- if data == None or type(data) != type (dict()):
+ self._preferences_apply()
+ self._preferences_store()
+
+ def _preferences_load(self):
+ """ Restore user preferences """
+ data = self.plugin_api.load_configuration_object(
+ self.PLUGIN_NAME, "preferences")
+ if type(data) != type(dict()):
self.preferences = self.DEFAULT_PREFERENCES
else:
self.preferences = data
- def preferences_store(self):
- self.plugin_api.save_configuration_object(self.PLUGIN_NAME,\
- "preferences", \
- self.preferences)
+ def _preferences_store(self):
+ """ Store user preferences """
+ self.plugin_api.save_configuration_object(
+ self.PLUGIN_NAME, "preferences", self.preferences)
- def preferences_apply(self):
- if self.preferences["menu_entry"] and self.menu_entry == False:
+ def _preferences_apply(self):
+ """ Add/remove menu entry/toolbar entry """
+ if self.preferences["menu_entry"] and not self.menu_entry:
self.plugin_api.add_menu_item(self.menu_item)
self.menu_entry = True
- elif self.preferences["menu_entry"]==False and self.menu_entry == True:
+ elif not self.preferences["menu_entry"] and self.menu_entry:
self.plugin_api.remove_menu_item(self.menu_item)
self.menu_entry = False
- if self.preferences["toolbar_entry"] and self.toolbar_entry == False:
+ if self.preferences["toolbar_entry"] and not self.toolbar_entry:
self.plugin_api.add_toolbar_item(self.tb_button)
self.toolbar_entry = True
- elif self.preferences["toolbar_entry"]==False and self.toolbar_entry == True:
+ elif not self.preferences["toolbar_entry"] and self.toolbar_entry:
self.plugin_api.remove_toolbar_item(self.tb_button)
self.toolbar_entry = False
-
=== modified file 'GTG/plugins/export/export_templates/description_pocketmod.py'
--- GTG/plugins/export/export_templates/description_pocketmod.py 2012-03-05 15:23:05 +0000
+++ GTG/plugins/export/export_templates/description_pocketmod.py 2012-05-03 22:12:18 +0000
@@ -16,6 +16,7 @@
from GTG import _
title = _("Foldable booklet (PDF)")
-description = _("A template to create a <a"
- " href=\"http://www.pocketmod.com\">PocketMod</a>, which is a "
- " small foldable booklet.")
+description = _("""A template to create
+<a href="http://www.pocketmod.com">PocketMod</a>, which is a small foldable
+booklet. Packages <b>pdflatex</b>, <b>pdftk</b> and <b>pdfjam</b>
+are required.""")
=== modified file 'GTG/plugins/export/export_templates/description_textual.py'
--- GTG/plugins/export/export_templates/description_textual.py 2012-03-05 15:23:05 +0000
+++ GTG/plugins/export/export_templates/description_textual.py 2012-05-03 22:12:18 +0000
@@ -14,6 +14,9 @@
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
+""" Text only template """
+
from GTG import _
+
title = _("Text-only")
description = _("A template to create a simple text file with some tasks.")
=== modified file 'GTG/plugins/export/export_templates/script_pocketmod'
--- GTG/plugins/export/export_templates/script_pocketmod 2011-11-19 21:41:08 +0000
+++ GTG/plugins/export/export_templates/script_pocketmod 2012-05-03 22:12:18 +0000
@@ -1,11 +1,25 @@
#/bin/sh
#
-#Script to create a pocketmod form a latex source file
-#Author: Jan Girlich <vollkorn@xxxxxxxxxx>,
-# Luca Invernizzi <invernizzi.l@xxxxxxxxx>
-#
-#Note that the GTG export plugin passes the script the path of the source file
-#and expects to be given the path of the generated file in the standard output
+# Copyright (c) 2009 - Jan Girlich <vollkorn@xxxxxxxxxx>, Luca Invernizzi <invernizzi.l@xxxxxxxxx>
+#
+# 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 3 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, see <http://www.gnu.org/licenses/>.
+#
+# Script to create a pocketmod form a latex source file
+#
+# Note that the GTG export plugin passes the script the path of the source file
+# and expects to be given the path of the generated file in the standard output
+
TMPFOLDER=`mktemp -d /tmp/pocketmod.XXXXXXXXXX`
OUTFILE=`mktemp /tmp/pockemod.XXXXXXXXXXXXX`.pdf
SOURCEFILE=$1
@@ -16,21 +30,6 @@
pdf90 $TMPFOLDER/seite5432.pdf --outfile $TMPFOLDER/seite5432-r.pdf 1>&2
pdf90 $TMPFOLDER/seite5432-r.pdf --outfile $TMPFOLDER/seite5432-r2.pdf 1>&2
pdftk $TMPFOLDER/seite6781.pdf $TMPFOLDER/seite5432-r2.pdf cat output $TMPFOLDER/gesamt.pdf 1>&2
-pdfnup $TMPFOLDER/gesamt.pdf --nup 4x2 --orient landscape --outfile $OUTFILE 1>&2
+pdfnup $TMPFOLDER/gesamt.pdf --nup 4x2 --landscape --outfile $OUTFILE 1>&2
#rm -rf $TMPFOLDER 1>&2
echo -n $OUTFILE
-# -*- coding: utf-8 -*-
-# Copyright (c) 2009 - Jan Girlich <vollkorn@xxxxxxxxxx>, Luca Invernizzi <invernizzi.l@xxxxxxxxx>
-#
-# 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 3 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, see <http://www.gnu.org/licenses/>.
=== modified file 'GTG/plugins/export/export_templates/template_pocketmod.tex'
--- GTG/plugins/export/export_templates/template_pocketmod.tex 2012-03-05 15:23:05 +0000
+++ GTG/plugins/export/export_templates/template_pocketmod.tex 2012-05-03 22:12:18 +0000
@@ -20,10 +20,10 @@
#def task_template($task)
\item[\Square]
#if $task.has_title
- \textbf{$task.title}
+ \textbf{$task.title.replace('_', '\_')}
#if $task.has_tags
#for $tag in $task.tags:
- \textit{$tag}
+ \textit{$tag.replace('_', '\_')}
#end for
#end if
#end if
=== modified file 'GTG/plugins/export/task_str.py'
--- GTG/plugins/export/task_str.py 2012-03-17 02:20:46 +0000
+++ GTG/plugins/export/task_str.py 2012-05-03 22:12:18 +0000
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2009 - Luca Invernizzi <invernizzi.l@xxxxxxxxx>
+# 2012 - Izidor Matušov <izidor.matusov@xxxxxxxxx>
#
# 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
@@ -14,35 +15,29 @@
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
-class TaskStr:
- '''
- This class is a wrapper around the classic GTG.core.task.Task. It provides
- access to the task various attributes directly via python attributes
- instead of method calls. This makes writing Cheetah templates easier
- '''
-
- def __init__(self,
- title,
- text,
- subtasks,
- status,
- modified,
- due_date,
- closed_date,
- start_date,
- days_left,
- tags,
- ):
- self.title = title
- self.text = text
- self.subtasks = subtasks
- self.status = status
- self.modified = modified
- self.due_date = due_date
- self.closed_date = closed_date
- self.start_date = start_date
- self.days_left = days_left
- self.tags = tags
+""" Text representation of GTG task for easier work in templates """
+
+
+class TaskStr(object):
+ """ Wrapper around GTG Task.
+ It provides access to the task various attributes directly via python
+ attributes instead of method calls and makes writing Cheetah
+ templates easier. """
+ # Ignore big number of properties and small number of public methods
+ # pylint: disable-msg=R0902,R0903
+ def __init__(self, task, subtasks):
+ self.title = task.get_title()
+ self.text = str(task.get_text())
+ self.status = task.get_status()
+ self.modified = str(task.get_modified_string())
+ self.due_date = str(task.get_due_date())
+ self.closed_date = str(task.get_closed_date())
+ self.start_date = str(task.get_start_date())
+ self.days_left = str(task.get_days_left())
+ self.tags = [t.get_id() for t in task.get_tags()]
+
+ self.subtasks = subtasks
+
has_title = property(lambda s: s.title != "")
has_text = property(lambda s: s.text != "")
has_subtasks = property(lambda s: s.subtasks != [])
@@ -54,51 +49,40 @@
has_days_left = property(lambda s: s.days_left != "")
has_tags = property(lambda s: s.tags != [])
-def TaskStr_factory(task):
- '''
- Creates a TaskStr object given a gtg task
- '''
- return TaskStr(title = task.get_title(),
- text = str(task.get_text()),
- subtasks = [],
- status = task.get_status(),
- modified = str(task.get_modified_string()),
- due_date = str(task.get_due_date()),
- closed_date = str(task.get_closed_date()),
- start_date = str(task.get_start_date()),
- days_left = str(task.get_days_left()),
- tags = [t.get_name() for t in task.get_tags()])
-
-def tree_to_TaskStr(tree, nodes, plugin_api, days = None):
- """This function performs a depth-first tree visits on a tree
- using the given nodes as root. For each node of the tree it
- encounters, it generates a TaskStr object and returns that.
- The resulting TaskStr will be linked to its subtasks in the
- same way as the tree"""
- tasks_str = []
- for node in nodes:
- if not tree.is_displayed(node.get_id()):
- continue
- task = plugin_api.get_requester().get_task(node.get_id())
- #The task_str is added to the result only if it satisfies the time
- # limit imposed with the @days parameter of this function
- if days and not _is_task_in_timespan(task, days):
- continue
- task_str = TaskStr_factory(task)
- tasks_str.append(task_str)
- if node.has_child():
- children = [tree.get_node(c) for c in node.get_children()]
- task_str.subtasks = tree_to_TaskStr(tree,
- children,
- plugin_api,
- days)
- return tasks_str
-
-def _is_task_in_timespan(task, days):
- '''If days < 0, returns True if the task has been closed in the last
- #abs(days). If days >= 0, returns True if the task is due in the next
- #days'''
- return (days < 0 and task.get_closed_date() and \
- (task.get_closed_date().days_left() >= days)) or \
- (days >= 0 and (task.get_days_left() <= days))
-
+
+def get_task_wrappers(tree, days = None, task_id = None):
+ """ Recursively find all task on given tree and
+ convert them into TaskStr
+
+ tree - tree of tasks
+ days - filter days in certain timespan
+ task_id - return subtasks of this tasks. If not set, use root node """
+
+ def _is_in_timespan(task):
+ """ Return True if days is not set.
+ If days < 0, returns True if the task has been done in the last
+ #abs(days).
+ If days >= 0, returns True if the task is due in the next #days """
+ if days is None:
+ return True
+ elif days < 0:
+ done = task.get_status() == task.STA_DONE
+ closed_date = task.get_closed_date()
+ return done and closed_date and closed_date.days_left() >= days
+ else:
+ return task.get_days_left() <= days
+
+ subtasks = []
+ for sub_id in tree.node_all_children(task_id):
+ subtask = get_task_wrappers(tree, days, sub_id)
+ if subtask is not None:
+ subtasks.append(subtask)
+
+ if task_id is None:
+ return subtasks
+ else:
+ task = tree.get_node(task_id)
+ if task is None or not _is_in_timespan(task):
+ return None
+
+ return TaskStr(task, subtasks)
=== modified file 'GTG/plugins/export/templates.py'
--- GTG/plugins/export/templates.py 2012-03-17 02:20:46 +0000
+++ GTG/plugins/export/templates.py 2012-05-03 22:12:18 +0000
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2010 - Luca Invernizzi <invernizzi.l@xxxxxxxxx>
+# 2012 - Izidor Matušov <izidor.matusov@xxxxxxxxx>
#
# 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
@@ -14,109 +15,150 @@
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
+""" Module for discovering templates and work with templates """
+
+from glob import glob
+import os.path
+import subprocess
import sys
-import glob
-import os.path
+import tempfile
+import threading
+
+from Cheetah.Template import Template as CheetahTemplate
from xdg.BaseDirectory import xdg_config_home
-
-from GTG.tools.borg import Borg
-
-
-class TemplateFactory(Borg):
- '''
- A Factory which provides an easy access to the templates. It caches
- templates for the sake of speed.
- '''
-
- TEMPLATE_PATHS = [\
- os.path.join(xdg_config_home,
- "gtg/plugins/export/export_templates"),
- os.path.join(os.path.dirname(os.path.abspath(__file__)),
- "export_templates/")]
-
- def __init__(self):
- super(TemplateFactory, self).__init__()
- if hasattr(self, "_cache"):
- #The borg has already been initialized
- return
- self._cache = {}
-
- @classmethod
- def get_templates_paths(cls):
- '''
- Returns a list containing the full path for all the
- available templates
- '''
- template_list = []
- for a_dir in TemplateFactory.TEMPLATE_PATHS:
- template_list += glob.glob(os.path.join(a_dir, "template_*"))
- return template_list
-
- def create_template(self, path):
+import gobject
+
+TEMPLATE_PATHS = [
+ os.path.join(xdg_config_home, "gtg/plugins/export/export_templates"),
+ os.path.join(os.path.dirname(os.path.abspath(__file__)), "export_templates"),
+]
+
+
+def get_templates_paths():
+ """ Returns a list containing the full path for all the
+ available templates. """
+ template_list = []
+ for a_dir in TEMPLATE_PATHS:
+ template_list += glob(os.path.join(a_dir, "template_*"))
+ return template_list
+
+
+class Template:
+ """ Representation of a template """
+
+ def __init__(self, path):
+ self._template = path
+ self._document_path = None
+
+ self._image_path = self._find_file("thumbnail_")
+ self._script_path = self._find_file("script_")
+
+ self._title, self._description = self._load_description()
+
+ def _find_file(self, prefix, suffix = ""):
+ """ Find a file for the template given prefix and suffix """
+ basename = os.path.basename(self._template)
+ basename = basename.replace("template_", prefix)
+ path = os.path.join(os.path.dirname(self._template), basename)
+ path = os.path.splitext(path)[0] + '*' + suffix
+ possible_filles = glob(path)
+ if len(possible_filles) > 0:
+ return possible_filles[0]
+ else:
+ return None
+
+ def _load_description(self):
+ """ Returns title and description of the template
+ template description are stored in python module for easier l10n.
+ thus, we need to import the module given its path """
+ path = self._find_file("description_", ".py")
if not path:
- return None
- if not path in self._cache:
- self._cache[path] = Template(path)
- return self._cache[path]
-
-
-
-class Template(object):
-
-
- def __init__(self, path):
- self.__template_path = path
- self.__image_path = self.__find_template_file(path, "thumbnail_")
- self.__script_path = self.__find_template_file(path, "script_")
- self.__description = ""
- self.__title = ""
- description_path = \
- self.__find_template_file(path, "description_", ".py")
- if description_path:
- #template description are stored in python module for easier l10n.
- #thus, we need to import the module given its path
- directory_path= os.path.dirname(description_path)
- if directory_path not in sys.path:
- sys.path.append(directory_path)
- module_name = os.path.basename(\
- description_path).replace(".py", "")
- try:
- module = __import__(module_name,
- globals(),
- locals(),
- ['description'],
- 0)
- self.__description = module.description
- self.__title = module.title
- except (ImportError, AttributeError):
- pass
-
- @classmethod
- def __find_template_file(cls, path, prefix, suffix = ""):
- basename = os.path.basename(path).replace("template_", prefix)
- path = os.path.join(os.path.dirname(path), basename)
-
- path = "%s*" % path[: path.rindex(".") - 1]
+ return "", ""
+ dir_path = os.path.dirname(path)
+ if dir_path not in sys.path:
+ sys.path.append(dir_path)
+ module_name = os.path.basename(path).replace(".py", "")
try:
- possible_paths = glob.glob(path)
- return filter(lambda p: p.endswith(suffix), possible_paths)[0]
- except:
- return None
+ module = __import__(module_name, globals(), locals(),
+ ['description'], 0)
+ return module.title, module.description
+ except (ImportError, AttributeError):
+ return "", ""
+
+ def _get_suffix(self):
+ """ Return suffix of the template """
+ return os.path.splitext(self._template)[1]
def get_path(self):
- return self.__template_path
+ """ Return path to the template """
+ return self._template
def get_image_path(self):
- return self.__image_path
-
- def get_script_path(self):
- return self.__script_path
-
- def get_suffix(self):
- return self.__template_path[self.__template_path.rindex(".") +1 :]
+ """ Return path to the image """
+ return self._image_path
+
+ def get_title(self):
+ """ Return title of the template """
+ return self._title
def get_description(self):
- return self.__description
-
- def get_title(self):
- return self.__title
+ """ Return description of the template """
+ return self._description
+
+ def get_document_path(self):
+ """ Return path to generated document.
+ Return None until generate() was successful."""
+ return self._document_path
+
+ def generate(self, tasks, plugin_api, callback):
+ """ Fill template and run callback when finished.
+
+ Created files are saved with the same suffix as the template. Opening
+ the final file determines its type based on suffix. """
+ document = CheetahTemplate(file = self.get_path(),
+ searchList = [{'tasks': tasks, 'plugin_api': plugin_api}])
+
+ suffix = ".%s" % self._get_suffix()
+ output = tempfile.NamedTemporaryFile(suffix = suffix, delete = False)
+ output.write(str(document))
+ self._document_path = output.name
+ output.close()
+
+ if self._script_path:
+ self._run_script(callback)
+ else:
+ callback()
+
+ def _run_script(self, callback):
+ """ Run script in its own thread and in other thread wait
+ for the result. """
+ document_ready = threading.Event()
+
+ def script():
+ """ Run script using /bin/sh.
+
+ The script gets path to a document as it only argument and
+ this thread expects resulting file as the only output of
+ the script. """
+
+ cmd = self._script_path + " " + self._document_path
+ self._document_path = None
+ try:
+ self._document_path = subprocess.Popen(
+ args = ['/bin/sh', '-c', cmd],
+ shell = False, stdout = subprocess.PIPE)\
+ .communicate()[0]
+ except Exception: # pylint: disable-msg=W0703
+ pass
+
+ if self._document_path and not os.path.exists(self._document_path):
+ self._document_path = None
+ document_ready.set()
+
+ def wait_for_document():
+ """ Wait for the completion of the script and finish generation """
+ document_ready.wait()
+ gobject.idle_add(callback)
+
+ threading.Thread(target = script).start()
+ threading.Thread(target = wait_for_document).start()