Merge lp:~matttbe/ubuntu/precise/pdfshuffler/0.5.99-svn64 into lp:ubuntu/precise/pdfshuffler
- Precise (12.04)
- 0.5.99-svn64
- Merge into precise
Status: | Merged |
---|---|
Merge reported by: | Stefano Rivera |
Merged at revision: | not available |
Proposed branch: | lp:~matttbe/ubuntu/precise/pdfshuffler/0.5.99-svn64 |
Merge into: | lp:ubuntu/precise/pdfshuffler |
Diff against target: |
5610 lines (+3230/-2150) 20 files modified
.pc/applied-patches (+1/-1) .pc/fix-shebang/pdfshuffler (+0/-1031) .pc/install_and_look_for_UI_file_in_right_dirs.patch/pdfshuffler/pdfshuffler.py (+1057/-0) .pc/install_and_look_for_UI_file_in_right_dirs.patch/setup.py (+66/-0) TODO (+0/-2) bin/pdfshuffler (+33/-0) data/pdfshuffler.ui (+359/-0) debian/changelog (+11/-0) debian/patches/fix-shebang (+0/-12) debian/patches/install_and_look_for_UI_file_in_right_dirs.patch (+31/-0) debian/patches/series (+1/-1) doc/pdfshuffler.1 (+2/-2) pdfshuffler (+0/-1031) pdfshuffler/cairorendering.py (+168/-0) pdfshuffler/pdfshuffler.py (+1057/-0) po/genpot.sh (+2/-1) po/ja.po (+156/-0) po/pdfshuffler.pot (+118/-60) po/zh_CN.po (+156/-0) setup.py (+12/-9) |
To merge this branch: | bzr merge lp:~matttbe/ubuntu/precise/pdfshuffler/0.5.99-svn64 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ubuntu Sponsors | Pending | ||
Review via email: mp+92648@code.launchpad.net |
Commit message
Description of the change
Hello,
Is it possible to upload this new version because it fixes this annoying bug: PDFShuffler displays only black squares
- http://
- https:/
This version has been tested and it is available on this ppa:matttbe/ppa
This branch should be ready to be pushed on lp:ubuntu/pdfshuffler (except the "UNRELEASED" version in debian/changelog)
Please upload this package before the FF :)
Thank you for your help!
Stefano Rivera (stefanor) wrote : | # |
Matthieu Baerts (matttbe) wrote : | # |
It's strange! I simply used 'bzr merge-upstream' with the tarball...
And if I want to give you a debdiff, I have this error: File /tmp/ThBQcppeQT
What can I do?
Matthieu Baerts (matttbe) wrote : | # |
This is strange... These two commands work fine:
bzr branch lp:~matttbe/ubuntu/precise/pdfshuffler/0.5.99-svn64
bzr bd lp:~matttbe/ubuntu/precise/pdfshuffler/0.5.99-svn64
But not this one:
bzr merge lp:~matttbe/ubuntu/precise/pdfshuffler/0.5.99-svn64
Stefano Rivera (stefanor) wrote : | # |
Yeah, it's LP: #923688. I worked around it...
Matthieu Baerts (matttbe) wrote : | # |
Oh ok, it's a known bug!
Thank you for your help :)
Stefano Rivera (stefanor) wrote : | # |
Slightly modified the changelog and uploaded.
Preview Diff
1 | === modified file '.pc/applied-patches' |
2 | --- .pc/applied-patches 2010-09-15 23:33:31 +0000 |
3 | +++ .pc/applied-patches 2012-02-12 00:35:23 +0000 |
4 | @@ -1,1 +1,1 @@ |
5 | -fix-shebang |
6 | +install_and_look_for_UI_file_in_right_dirs.patch |
7 | |
8 | === removed directory '.pc/fix-shebang' |
9 | === removed file '.pc/fix-shebang/pdfshuffler' |
10 | --- .pc/fix-shebang/pdfshuffler 2011-02-15 00:43:28 +0000 |
11 | +++ .pc/fix-shebang/pdfshuffler 1970-01-01 00:00:00 +0000 |
12 | @@ -1,1031 +0,0 @@ |
13 | -#!/usr/bin/env python |
14 | -# -*- coding: utf-8 -*- |
15 | - |
16 | -""" |
17 | - -------------------------------------------------------------------------- |
18 | - |
19 | - PDF-Shuffler 0.5.1 - pyGTK PDF Merging, Rearranging, and Splitting |
20 | - Copyright (C) 2008-2010 Konstantinos Poulios |
21 | - <https://sourceforge.net/projects/pdfshuffler> |
22 | - |
23 | - -------------------------------------------------------------------------- |
24 | - |
25 | - This program is free software; you can redistribute it and/or modify |
26 | - it under the terms of the GNU General Public License as published by |
27 | - the Free Software Foundation; either version 3 of the License, or |
28 | - (at your option) any later version. |
29 | - |
30 | - This program is distributed in the hope that it will be useful, |
31 | - but WITHOUT ANY WARRANTY; without even the implied warranty of |
32 | - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
33 | - GNU General Public License for more details. |
34 | - |
35 | - You should have received a copy of the GNU General Public License along |
36 | - with this program; if not, write to the Free Software Foundation, Inc., |
37 | - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
38 | - |
39 | - -------------------------------------------------------------------------- |
40 | -""" |
41 | - |
42 | -import os |
43 | -import shutil #needed for file operations like whole directory deletion |
44 | -import sys #needed for proccessing of command line args |
45 | -import urllib #needed to parse filename information passed by DnD |
46 | -import threading |
47 | -import tempfile |
48 | -from copy import copy |
49 | - |
50 | -import locale #for multilanguage support |
51 | -import gettext |
52 | -gettext.install('pdfshuffler', unicode=1) |
53 | - |
54 | -try: |
55 | - import pygtk |
56 | - pygtk.require('2.0') |
57 | - import gtk |
58 | - assert gtk.gtk_version >= (2, 10, 0) |
59 | - assert gtk.pygtk_version >= (2, 10, 0) |
60 | -except AssertionError: |
61 | - print('You do not have the required versions of GTK+ and/or PyGTK ' + |
62 | - 'installed.\n\n' + |
63 | - 'Installed GTK+ version is ' + |
64 | - '.'.join([str(n) for n in gtk.gtk_version]) + '\n' + |
65 | - 'Required GTK+ version is 2.10.0 or higher\n\n' |
66 | - 'Installed PyGTK version is ' + |
67 | - '.'.join([str(n) for n in gtk.pygtk_version]) + '\n' + |
68 | - 'Required PyGTK version is 2.10.0 or higher') |
69 | - sys.exit(1) |
70 | -except: |
71 | - print('PyGTK version 2.10.0 or higher is required to run this program.') |
72 | - print('No version of PyGTK was found on your system.') |
73 | - sys.exit(1) |
74 | - |
75 | -gtk.gdk.threads_init() |
76 | -import gobject #to use custom signals |
77 | -import pango #to adjust the text alignment in CellRendererText |
78 | -import gio #to inquire mime types information |
79 | - |
80 | -import poppler #for the rendering of pdf pages |
81 | -from pyPdf import PdfFileWriter, PdfFileReader |
82 | - |
83 | -class PDFshuffler: |
84 | - # ======================================================= |
85 | - # All the preferences are stored here. |
86 | - # ======================================================= |
87 | - prefs = { |
88 | - 'window width': min (700, gtk.gdk.screen_get_default().get_width() / 2 ), |
89 | - 'window height': min(600, gtk.gdk.screen_get_default().get_height() - 50 ), |
90 | - 'window x': 0, |
91 | - 'window y': 0, |
92 | - 'initial thumbnail size': 530, |
93 | - 'initial zoom scale': 0.25, |
94 | - } |
95 | - |
96 | - MODEL_ROW_INTERN = 1001 |
97 | - MODEL_ROW_EXTERN = 1002 |
98 | - TEXT_URI_LIST = 1003 |
99 | - MODEL_ROW_MOTION = 1004 |
100 | - TARGETS_IV = [('MODEL_ROW_INTERN', gtk.TARGET_SAME_WIDGET, MODEL_ROW_INTERN), |
101 | - ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN), |
102 | - ('MODEL_ROW_MOTION', 0, MODEL_ROW_MOTION)] |
103 | - TARGETS_SW = [('text/uri-list', 0, TEXT_URI_LIST), |
104 | - ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN)] |
105 | - |
106 | - def __init__(self): |
107 | - # Create the temporary directory |
108 | - self.tmp_dir = tempfile.mkdtemp("pdfshuffler") |
109 | - os.chmod(self.tmp_dir, 0700) |
110 | - |
111 | - pixmap = os.path.join(sys.prefix,'share','pixmaps','pdfshuffler.png') |
112 | - try: |
113 | - gtk.window_set_default_icon_from_file(pixmap) |
114 | - except: |
115 | - print(_('File %s does not exist') % pixmap) |
116 | - |
117 | - # Create the main window, and attach delete_event signal to terminating |
118 | - # the application |
119 | - self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) |
120 | - self.window.set_title('PDF-Shuffler') |
121 | - self.window.set_border_width(0) |
122 | - self.window.move(self.prefs['window x'], self.prefs['window y']) |
123 | - self.window.set_size_request(self.prefs['window width'], |
124 | - self.prefs['window height']) |
125 | - self.window.connect('delete_event', self.close_application) |
126 | - self.window.show() |
127 | - |
128 | - # Create a vbox to hold the thumnails-container |
129 | - vbox = gtk.VBox() |
130 | - self.window.add(vbox) |
131 | - |
132 | - # Create a scrolled window to hold the thumbnails-container |
133 | - self.sw = gtk.ScrolledWindow() |
134 | - self.sw.set_border_width(0) |
135 | - self.sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) |
136 | - self.sw.drag_dest_set(gtk.DEST_DEFAULT_MOTION | |
137 | - gtk.DEST_DEFAULT_HIGHLIGHT | |
138 | - gtk.DEST_DEFAULT_DROP | |
139 | - gtk.DEST_DEFAULT_MOTION, |
140 | - self.TARGETS_SW, |
141 | - gtk.gdk.ACTION_COPY | |
142 | - gtk.gdk.ACTION_MOVE ) |
143 | - self.sw.connect('drag_data_received', self.sw_dnd_received_data) |
144 | - self.sw.connect('button_press_event', self.sw_button_press_event) |
145 | - vbox.pack_start(self.sw) |
146 | - |
147 | - # Create an alignment to keep the thumbnails center-aligned |
148 | - align = gtk.Alignment(0.5, 0.5, 0, 0) |
149 | - self.sw.add_with_viewport(align) |
150 | - |
151 | - # Create ListStore model and IconView |
152 | - self.model = gtk.ListStore(str, # 0.Text descriptor |
153 | - gtk.gdk.Pixbuf, # 1.Thumbnail image |
154 | - int, # 2.Document number |
155 | - int, # 3.Page number |
156 | - int, # 4.Thumbnail width |
157 | - str, # 5.Document filename |
158 | - bool, # 6.Rendered |
159 | - int, # 7.Rotation angle |
160 | - float, # 8.Crop left |
161 | - float, # 9.Crop right |
162 | - float, # 10.Crop top |
163 | - float) # 11.Crop bottom |
164 | - |
165 | - self.zoom_scale = self.prefs['initial zoom scale'] |
166 | - self.iv_col_width = self.prefs['initial thumbnail size'] |
167 | - |
168 | - self.iconview = gtk.IconView(self.model) |
169 | - self.iconview.set_item_width(self.iv_col_width + 12) |
170 | - |
171 | - self.iconview.set_pixbuf_column(1) |
172 | -# self.cellpb = gtk.CellRendererPixbuf() |
173 | -# self.cellpb.set_property('follow-state', True) |
174 | -# self.iconview.pack_start(self.cellpb, False) |
175 | -# self.iconview.set_attributes(self.cellpb, pixbuf=1) |
176 | - |
177 | -# self.iconview.set_text_column(0) |
178 | - self.celltxt = gtk.CellRendererText() |
179 | - self.celltxt.set_property('width', self.iv_col_width) |
180 | - self.celltxt.set_property('wrap-width', self.iv_col_width) |
181 | - self.celltxt.set_property('alignment', pango.ALIGN_CENTER) |
182 | - self.iconview.pack_start(self.celltxt, False) |
183 | - self.iconview.set_attributes(self.celltxt, text=0) |
184 | - |
185 | - self.iconview.set_selection_mode(gtk.SELECTION_MULTIPLE) |
186 | - self.iconview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, |
187 | - self.TARGETS_IV, |
188 | - gtk.gdk.ACTION_COPY | |
189 | - gtk.gdk.ACTION_MOVE ) |
190 | - self.iconview.enable_model_drag_dest(self.TARGETS_IV, |
191 | - gtk.gdk.ACTION_DEFAULT) |
192 | - self.iconview.connect('drag_begin', self.iv_drag_begin) |
193 | - self.iconview.connect('drag_data_get', self.iv_dnd_get_data) |
194 | - self.iconview.connect('drag_data_received', self.iv_dnd_received_data) |
195 | - self.iconview.connect('drag_data_delete', self.iv_dnd_data_delete) |
196 | - self.iconview.connect('drag_motion', self.iv_dnd_motion) |
197 | - self.iconview.connect('drag_leave', self.iv_dnd_leave_end) |
198 | - self.iconview.connect('drag_end', self.iv_dnd_leave_end) |
199 | - self.iconview.connect('button_press_event', self.iv_button_press_event) |
200 | - self.iv_auto_scroll_direction = 0 |
201 | - |
202 | - style = self.iconview.get_style().copy() |
203 | - style_sw = self.sw.get_style() |
204 | - for state in (gtk.STATE_NORMAL, gtk.STATE_PRELIGHT, gtk.STATE_ACTIVE): |
205 | - style.base[state] = style_sw.bg[gtk.STATE_NORMAL] |
206 | - self.iconview.set_style(style) |
207 | - |
208 | - align.add(self.iconview) |
209 | - |
210 | - # Create a horizontal box to hold the buttons |
211 | - hbox = gtk.HBox() |
212 | - vbox.pack_start(hbox, expand=False, fill=False) |
213 | - |
214 | - # Create buttons |
215 | - self.button_exit = gtk.Button(stock=gtk.STOCK_QUIT) |
216 | - self.button_exit.connect('clicked', self.close_application) |
217 | - hbox.pack_start(self.button_exit, expand=True, fill=True, padding=20) |
218 | - |
219 | - self.button_del = gtk.Button(_('Delete Page(s)')) |
220 | - image = gtk.Image() |
221 | - image.set_from_stock(gtk.STOCK_DELETE, gtk.ICON_SIZE_BUTTON) |
222 | - self.button_del.set_image(image) |
223 | - self.button_del.connect('clicked', self.clear_selected) |
224 | - hbox.pack_start(self.button_del, expand=True, fill=True, padding=20) |
225 | - |
226 | - self.button_import = gtk.Button(_('Import pdf')) |
227 | - image = gtk.Image() |
228 | - image.set_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON) |
229 | - self.button_import.set_image(image) |
230 | - self.button_import.connect('clicked', self.on_action_add_doc_activate) |
231 | - hbox.pack_start(self.button_import, expand=True, fill=True, padding=20) |
232 | - |
233 | - self.button_export = gtk.Button(_('Export pdf')) |
234 | - image = gtk.Image() |
235 | - image.set_from_stock(gtk.STOCK_SAVE_AS, gtk.ICON_SIZE_BUTTON) |
236 | - self.button_export.set_image(image) |
237 | - self.button_export.connect('clicked', self.choose_export_pdf_name) |
238 | - hbox.pack_start(self.button_export, expand=True, fill=True, padding=20) |
239 | - |
240 | - self.button_export = gtk.Button(_('About')) |
241 | - image = gtk.Image() |
242 | - image.set_from_stock(gtk.STOCK_ABOUT, gtk.ICON_SIZE_BUTTON) |
243 | - self.button_export.set_image(image) |
244 | - self.button_export.connect('clicked', self.about_dialog) |
245 | - hbox.pack_start(self.button_export, expand=True, fill=True, padding=20) |
246 | - |
247 | - # Define window callback function and show window |
248 | - self.window.connect('size_allocate', self.on_window_size_request) # resize |
249 | - self.window.connect('key_press_event', self.on_keypress_event ) # keypress |
250 | - self.window.show_all() |
251 | - |
252 | - #Creating the popup menu |
253 | - self.popup = gtk.Menu() |
254 | - popup_rotate_right = gtk.MenuItem(_('Rotate Page(s) Clockwise')) |
255 | - popup_rotate_left = gtk.MenuItem(_('Rotate Page(s) Counterclockwise')) |
256 | - popup_crop = gtk.MenuItem(_('Crop Page(s)')) |
257 | - popup_delete = gtk.MenuItem(_('Delete Page(s)')) |
258 | - popup_rotate_right.connect('activate', self.rotate_page_right) |
259 | - popup_rotate_left.connect('activate', self.rotate_page_left) |
260 | - popup_crop.connect('activate', self.crop_page_dialog) |
261 | - popup_delete.connect('activate', self.clear_selected) |
262 | - popup_rotate_right.show() |
263 | - popup_rotate_left.show() |
264 | - popup_crop.show() |
265 | - popup_delete.show() |
266 | - self.popup.append(popup_rotate_right) |
267 | - self.popup.append(popup_rotate_left) |
268 | - self.popup.append(popup_crop) |
269 | - self.popup.append(popup_delete) |
270 | - |
271 | - # Initializing variables |
272 | - self.export_directory = os.getenv('HOME') |
273 | - self.import_directory = os.getenv('HOME') |
274 | - self.nfile = 0 |
275 | - self.iv_auto_scroll_timer = None |
276 | - self.pdfqueue = [] |
277 | - |
278 | - gobject.type_register(PDF_Renderer) |
279 | - gobject.signal_new('reset_iv_width', PDF_Renderer, |
280 | - gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ()) |
281 | - self.rendering_thread = PDF_Renderer(self.model, self.pdfqueue, |
282 | - self.zoom_scale, self.iv_col_width) |
283 | - self.rendering_thread.connect('reset_iv_width', self.reset_iv_width) |
284 | - self.rendering_thread.start() |
285 | - |
286 | - # Importing documents passed as command line arguments |
287 | - for filename in sys.argv[1:]: |
288 | - self.add_pdf_pages(filename) |
289 | - |
290 | - # ======================================================= |
291 | - def render(self): |
292 | - if self.rendering_thread.paused: |
293 | - self.rendering_thread.paused = False |
294 | - self.rendering_thread.evnt.set() |
295 | - self.rendering_thread.evnt.clear() |
296 | - |
297 | - # ======================================================= |
298 | - def on_window_size_request(self, window, event): |
299 | - """Main Window resize - workaround for autosetting of |
300 | - iconview cols no.""" |
301 | - |
302 | - #add 12 because of: http://bugzilla.gnome.org/show_bug.cgi?id=570152 |
303 | - col_num = 9 * window.get_size()[0] / (10 * (self.iv_col_width + 12)) |
304 | - self.iconview.set_columns(col_num) |
305 | - |
306 | - # ======================================================= |
307 | - def reset_iv_width(self, renderer=None): |
308 | - """Reconfigures the width of the iconview columns""" |
309 | - |
310 | - max_w = max(row[4] for row in self.model) |
311 | - if max_w != self.iv_col_width: |
312 | - self.iv_col_width = max_w |
313 | - self.celltxt.set_property('width', self.iv_col_width) |
314 | - self.celltxt.set_property('wrap-width', self.iv_col_width) |
315 | - self.iconview.set_item_width(self.iv_col_width + 12) #-1) |
316 | - self.on_window_size_request(self.window, None) |
317 | - |
318 | - # ======================================================= |
319 | - def on_keypress_event(self, widget, event): |
320 | - """Keypress events in Main Window""" |
321 | - |
322 | - #keyname = gtk.gdk.keyval_name(event.keyval) |
323 | - if event.keyval == 65535: # Delete keystroke |
324 | - self.clear_selected() |
325 | - |
326 | - # ======================================================= |
327 | - def close_application(self, widget, event=None, data=None): |
328 | - """Termination""" |
329 | - |
330 | - #gtk.gdk.threads_leave() |
331 | - self.rendering_thread.quit = True |
332 | - #gtk.gdk.threads_enter() |
333 | - if self.rendering_thread.paused == True: |
334 | - self.rendering_thread.evnt.set() |
335 | - self.rendering_thread.evnt.clear() |
336 | - if os.path.isdir(self.tmp_dir): |
337 | - shutil.rmtree(self.tmp_dir) |
338 | - if gtk.main_level(): |
339 | - gtk.main_quit() |
340 | - else: |
341 | - sys.exit(0) |
342 | - return False |
343 | - |
344 | - # ======================================================= |
345 | - def add_pdf_pages(self, filename, |
346 | - firstpage=None, lastpage=None, |
347 | - angle=0, crop=[0.,0.,0.,0.] ): |
348 | - """Add pages of a pdf document to the model""" |
349 | - |
350 | - res = False |
351 | - # Check if the document has already been loaded |
352 | - pdfdoc = None |
353 | - for it_pdfdoc in self.pdfqueue: |
354 | - if os.path.isfile(it_pdfdoc.filename) and \ |
355 | - os.path.samefile(filename, it_pdfdoc.filename) and \ |
356 | - os.path.getmtime(filename) is it_pdfdoc.mtime: |
357 | - pdfdoc = it_pdfdoc |
358 | - break |
359 | - |
360 | - if not pdfdoc: |
361 | - pdfdoc = PDF_Doc(filename, self.nfile, self.tmp_dir) |
362 | - self.import_directory = os.path.split(filename)[0] |
363 | - self.export_directory = self.import_directory |
364 | - if pdfdoc.nfile != 0 and pdfdoc != []: |
365 | - self.nfile = pdfdoc.nfile |
366 | - self.pdfqueue.append(pdfdoc) |
367 | - else: |
368 | - return res |
369 | - |
370 | - n_start = 1 |
371 | - n_end = pdfdoc.npage |
372 | - if firstpage: |
373 | - n_start = min(n_end, max(1, firstpage)) |
374 | - if lastpage: |
375 | - n_end = max(n_start, min(n_end, lastpage)) |
376 | - |
377 | - for npage in range(n_start, n_end + 1): |
378 | - descriptor = ''.join([pdfdoc.shortname, '\n', _('page'), ' ', str(npage)]) |
379 | - width = self.iv_col_width |
380 | - thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, |
381 | - 8, width, width) |
382 | - self.model.append((descriptor, # 0 |
383 | - thumbnail, # 1 |
384 | - pdfdoc.nfile, # 2 |
385 | - npage, # 3 |
386 | - width, # 4 |
387 | - pdfdoc.filename, # 5 |
388 | - False, # 6 |
389 | - angle, # 7 |
390 | - crop[0],crop[1], # 8-9 |
391 | - crop[2],crop[3] )) # 10-11 |
392 | - res = True |
393 | - |
394 | - if res: |
395 | - self.render() |
396 | - return res |
397 | - |
398 | - # ======================================================= |
399 | - def choose_export_pdf_name(self, widget=None): |
400 | - """Handles choosing a name for exporting """ |
401 | - |
402 | - chooser = gtk.FileChooserDialog(title=_('Export ...'), |
403 | - action=gtk.FILE_CHOOSER_ACTION_SAVE, |
404 | - buttons=(gtk.STOCK_CANCEL, |
405 | - gtk.RESPONSE_CANCEL, |
406 | - gtk.STOCK_SAVE, |
407 | - gtk.RESPONSE_OK)) |
408 | - chooser.set_do_overwrite_confirmation(True) |
409 | - chooser.set_current_folder(self.export_directory) |
410 | - filter_pdf = gtk.FileFilter() |
411 | - filter_pdf.set_name(_('PDF files')) |
412 | - filter_pdf.add_mime_type('application/pdf') |
413 | - chooser.add_filter(filter_pdf) |
414 | - |
415 | - filter_all = gtk.FileFilter() |
416 | - filter_all.set_name(_('All files')) |
417 | - filter_all.add_pattern('*') |
418 | - chooser.add_filter(filter_all) |
419 | - |
420 | - while True: |
421 | - response = chooser.run() |
422 | - if response == gtk.RESPONSE_OK: |
423 | - file_out = chooser.get_filename() |
424 | - (path, shortname) = os.path.split(file_out) |
425 | - (shortname, ext) = os.path.splitext(shortname) |
426 | - if ext.lower() != '.pdf': |
427 | - file_out = file_out + '.pdf' |
428 | - try: |
429 | - self.export_to_file(file_out) |
430 | - self.export_directory = path |
431 | - except IOError: |
432 | - error_msg_win = gtk.MessageDialog(flags=gtk.DIALOG_MODAL, |
433 | - type=gtk.MESSAGE_ERROR, |
434 | - message_format=_("Error writing file: %s") % file_out, |
435 | - buttons=gtk.BUTTONS_OK) |
436 | - response = error_msg_win.run() |
437 | - if response == gtk.RESPONSE_OK: |
438 | - error_msg_win.destroy() |
439 | - continue |
440 | - break |
441 | - chooser.destroy() |
442 | - |
443 | - # ======================================================= |
444 | - def export_to_file(self, file_out): |
445 | - """Export to file""" |
446 | - |
447 | - pdf_output = PdfFileWriter() |
448 | - pdf_input = [] |
449 | - for pdfdoc in self.pdfqueue: |
450 | - pdfdoc_inp = PdfFileReader(file(pdfdoc.copyname, 'rb')) |
451 | - if pdfdoc_inp.getIsEncrypted(): |
452 | - if (pdfdoc_inp.decrypt('')!=1): # Workaround for lp:#355479 |
453 | - print(_('File %s is encrypted.') % pdfdoc.filename) |
454 | - print(_('Support for such files has not been implemented yet.')) |
455 | - print(_('File export failed.')) |
456 | - return |
457 | - #FIXME |
458 | - #else |
459 | - # ask for password and decrypt file |
460 | - pdf_input.append(pdfdoc_inp) |
461 | - |
462 | - for row in self.model: |
463 | - # add pages from input to output document |
464 | - nfile = row[2] |
465 | - npage = row[3] |
466 | - current_page = copy(pdf_input[nfile-1].getPage(npage-1)) |
467 | - angle = row[7] |
468 | - angle0 = current_page.get("/Rotate",0) |
469 | - crop = [row[8],row[9],row[10],row[11]] |
470 | - if angle is not 0: |
471 | - current_page.rotateClockwise(angle) |
472 | - if crop != [0.,0.,0.,0.]: |
473 | - rotate_times = ( ( ( angle + angle0 ) % 360 + 45 ) / 90 ) % 4 |
474 | - crop_init = crop |
475 | - if rotate_times is not 0: |
476 | - perm = [0,2,1,3] |
477 | - for it in range(rotate_times): |
478 | - perm.append(perm.pop(0)) |
479 | - perm.insert(1,perm.pop(2)) |
480 | - crop = [crop_init[perm[side]] for side in range(4)] |
481 | - #(x1, y1) = current_page.cropBox.lowerLeft |
482 | - #(x2, y2) = current_page.cropBox.upperRight |
483 | - (x1, y1) = [float(xy) for xy in current_page.mediaBox.lowerLeft] |
484 | - (x2, y2) = [float(xy) for xy in current_page.mediaBox.upperRight] |
485 | - x1_new = int( x1 + (x2-x1) * crop[0] ) |
486 | - x2_new = int( x2 - (x2-x1) * crop[1] ) |
487 | - y1_new = int( y1 + (y2-y1) * crop[3] ) |
488 | - y2_new = int( y2 - (y2-y1) * crop[2] ) |
489 | - #current_page.cropBox.lowerLeft = (x1_new, y1_new) |
490 | - #current_page.cropBox.upperRight = (x2_new, y2_new) |
491 | - current_page.mediaBox.lowerLeft = (x1_new, y1_new) |
492 | - current_page.mediaBox.upperRight = (x2_new, y2_new) |
493 | - |
494 | - pdf_output.addPage(current_page) |
495 | - |
496 | - # finally, write "output" to document-output.pdf |
497 | - print(_('exporting to:'), file_out) |
498 | - pdf_output.write(file(file_out, 'wb')) |
499 | - |
500 | - # ======================================================= |
501 | - def on_action_add_doc_activate(self, widget, data=None): |
502 | - """Import doc""" |
503 | - |
504 | - chooser = gtk.FileChooserDialog(title=_('Import...'), |
505 | - action=gtk.FILE_CHOOSER_ACTION_OPEN, |
506 | - buttons=(gtk.STOCK_CANCEL, |
507 | - gtk.RESPONSE_CANCEL, |
508 | - gtk.STOCK_OPEN, |
509 | - gtk.RESPONSE_OK)) |
510 | - chooser.set_current_folder(self.import_directory) |
511 | - chooser.set_select_multiple(True) |
512 | - |
513 | - filter_all = gtk.FileFilter() |
514 | - filter_all.set_name(_('All files')) |
515 | - filter_all.add_pattern('*') |
516 | - chooser.add_filter(filter_all) |
517 | - |
518 | - filter_pdf = gtk.FileFilter() |
519 | - filter_pdf.set_name(_('PDF files')) |
520 | - filter_pdf.add_mime_type('application/pdf') |
521 | - chooser.add_filter(filter_pdf) |
522 | - chooser.set_filter(filter_pdf) |
523 | - |
524 | - response = chooser.run() |
525 | - if response == gtk.RESPONSE_OK: |
526 | - for filename in chooser.get_filenames(): |
527 | - if os.path.isfile(filename): |
528 | - # FIXME |
529 | - f = gio.File(filename) |
530 | - f_info = f.query_info('standard::content-type') |
531 | - mime_type = f_info.get_content_type() |
532 | - if mime_type == 'application/pdf': |
533 | - self.add_pdf_pages(filename) |
534 | - elif mime_type[:34] == 'application/vnd.oasis.opendocument': |
535 | - print(_('OpenDocument not supported yet!')) |
536 | - elif mime_type[:5] == 'image': |
537 | - print(_('Image file not supported yet!')) |
538 | - else: |
539 | - print(_('File type not supported!')) |
540 | - else: |
541 | - print(_('File %s does not exist') % filename) |
542 | - elif response == gtk.RESPONSE_CANCEL: |
543 | - print(_('Closed, no files selected')) |
544 | - chooser.destroy() |
545 | - |
546 | - # ======================================================= |
547 | - def clear_selected(self, button=None): |
548 | - """Removes the selected Element in the IconView""" |
549 | - |
550 | - model = self.iconview.get_model() |
551 | - selection = self.iconview.get_selected_items() |
552 | - if selection: |
553 | - selection.sort(reverse=True) |
554 | - for path in selection: |
555 | - iter = model.get_iter(path) |
556 | - model.remove(iter) |
557 | - path = selection[-1] |
558 | - self.iconview.select_path(path) |
559 | - if not self.iconview.path_is_selected(path): |
560 | - if len(model) > 0: # select the last row |
561 | - row = model[-1] |
562 | - path = row.path |
563 | - self.iconview.select_path(path) |
564 | - self.iconview.grab_focus() |
565 | - |
566 | - # ======================================================= |
567 | - def iv_drag_begin(self, iconview, context): |
568 | - """Sets custom icon on drag begin for multiple items selected""" |
569 | - |
570 | - if len(iconview.get_selected_items()) > 1: |
571 | - iconview.stop_emission('drag_begin') |
572 | - context.set_icon_stock(gtk.STOCK_DND_MULTIPLE, 0, 0) |
573 | - |
574 | - # ======================================================= |
575 | - def iv_dnd_get_data(self, iconview, context, |
576 | - selection_data, target_id, etime): |
577 | - """Handles requests for data by drag and drop in iconview""" |
578 | - |
579 | - model = iconview.get_model() |
580 | - selection = self.iconview.get_selected_items() |
581 | - selection.sort(key=lambda x: x[0]) |
582 | - data = [] |
583 | - for path in selection: |
584 | - if selection_data.target == 'MODEL_ROW_INTERN': |
585 | - data.append( str(path[0]) ) |
586 | - elif selection_data.target == 'MODEL_ROW_EXTERN': |
587 | - iter = model.get_iter(path) |
588 | - nfile, npage, angle = model.get(iter, 2, 3, 7) |
589 | - crop = model.get(iter, 8, 9, 10, 11) |
590 | - pdfdoc = self.pdfqueue[nfile - 1] |
591 | - data.append('\n'.join([pdfdoc.filename, |
592 | - str(npage), |
593 | - str(angle) ] + |
594 | - [str(side) for side in crop] ) ) |
595 | - if data: |
596 | - data = '\n;\n'.join(data) |
597 | - selection_data.set(selection_data.target, 8, data) |
598 | - |
599 | - # ======================================================= |
600 | - def iv_dnd_received_data(self, iconview, context, x, y, |
601 | - selection_data, target_id, etime): |
602 | - """Handles received data by drag and drop in iconview""" |
603 | - |
604 | - model = iconview.get_model() |
605 | - data = selection_data.data |
606 | - if data: |
607 | - data = data.split('\n;\n') |
608 | - drop_info = iconview.get_dest_item_at_pos(x, y) |
609 | - iter_to = None |
610 | - if drop_info: |
611 | - path, position = drop_info |
612 | - ref_to = gtk.TreeRowReference(model,path) |
613 | - else: |
614 | - position = gtk.ICON_VIEW_DROP_RIGHT |
615 | - if len(model) > 0: #find the iterator of the last row |
616 | - row = model[-1] |
617 | - path = row.path |
618 | - ref_to = gtk.TreeRowReference(model,path) |
619 | - if ref_to: |
620 | - before = ( position == gtk.ICON_VIEW_DROP_LEFT |
621 | - or position == gtk.ICON_VIEW_DROP_ABOVE) |
622 | - #if target_id == self.MODEL_ROW_INTERN: |
623 | - if selection_data.target == 'MODEL_ROW_INTERN': |
624 | - if before: |
625 | - data.sort(key=int) |
626 | - else: |
627 | - data.sort(key=int,reverse=True) |
628 | - ref_from_list = [gtk.TreeRowReference(model,path) |
629 | - for path in data] |
630 | - for ref_from in ref_from_list: |
631 | - path = ref_to.get_path() |
632 | - iter_to = model.get_iter(path) |
633 | - path = ref_from.get_path() |
634 | - iter_from = model.get_iter(path) |
635 | - row = model[iter_from] |
636 | - if before: |
637 | - model.insert_before(iter_to, row) |
638 | - else: |
639 | - model.insert_after(iter_to, row) |
640 | - if context.action == gtk.gdk.ACTION_MOVE: |
641 | - for ref_from in ref_from_list: |
642 | - path = ref_from.get_path() |
643 | - iter_from = model.get_iter(path) |
644 | - model.remove(iter_from) |
645 | - |
646 | - #elif target_id == self.MODEL_ROW_EXTERN: |
647 | - elif selection_data.target == 'MODEL_ROW_EXTERN': |
648 | - if not before: |
649 | - data.reverse() |
650 | - while data: |
651 | - tmp = data.pop(0).split('\n') |
652 | - filename = tmp[0] |
653 | - npage, angle = [int(k) for k in tmp[1:3]] |
654 | - crop = [float(side) for side in tmp[3:7]] |
655 | - if self.add_pdf_pages(filename, npage, npage, |
656 | - angle, crop ): |
657 | - if len(model) > 0: |
658 | - path = ref_to.get_path() |
659 | - iter_to = model.get_iter(path) |
660 | - row = model[-1] #the last row |
661 | - path = row.path |
662 | - iter_from = model.get_iter(path) |
663 | - if before: |
664 | - model.move_before(iter_from, iter_to) |
665 | - else: |
666 | - model.move_after(iter_from, iter_to) |
667 | - if context.action == gtk.gdk.ACTION_MOVE: |
668 | - context.finish(True, True, etime) |
669 | - |
670 | - # ======================================================= |
671 | - def iv_dnd_data_delete(self, widget, context): |
672 | - """Deletes dnd items after a successful move operation""" |
673 | - |
674 | - model = self.iconview.get_model() |
675 | - selection = self.iconview.get_selected_items() |
676 | - ref_del_list = [gtk.TreeRowReference(model,path) for path in selection] |
677 | - for ref_del in ref_del_list: |
678 | - path = ref_del.get_path() |
679 | - iter = model.get_iter(path) |
680 | - model.remove(iter) |
681 | - |
682 | - # ======================================================= |
683 | - def iv_dnd_motion(self, iconview, context, x, y, etime): |
684 | - """Handles the drag-motion signal in order to auto-scroll the view""" |
685 | - |
686 | - autoscroll_area = 40 |
687 | - sw_vadj = self.sw.get_vadjustment() |
688 | - sw_height = self.sw.get_allocation().height |
689 | - if y -sw_vadj.get_value() < autoscroll_area: |
690 | - if not self.iv_auto_scroll_timer: |
691 | - self.iv_auto_scroll_direction = gtk.DIR_UP |
692 | - self.iv_auto_scroll_timer = gobject.timeout_add(150, |
693 | - self.iv_auto_scroll) |
694 | - elif y -sw_vadj.get_value() > sw_height - autoscroll_area: |
695 | - if not self.iv_auto_scroll_timer: |
696 | - self.iv_auto_scroll_direction = gtk.DIR_DOWN |
697 | - self.iv_auto_scroll_timer = gobject.timeout_add(150, |
698 | - self.iv_auto_scroll) |
699 | - elif self.iv_auto_scroll_timer: |
700 | - gobject.source_remove(self.iv_auto_scroll_timer) |
701 | - self.iv_auto_scroll_timer = None |
702 | - |
703 | - # ======================================================= |
704 | - def iv_dnd_leave_end(self, widget, context, ignored=None): |
705 | - """Ends the auto-scroll during DND""" |
706 | - |
707 | - if self.iv_auto_scroll_timer: |
708 | - gobject.source_remove(self.iv_auto_scroll_timer) |
709 | - self.iv_auto_scroll_timer = None |
710 | - |
711 | - # ======================================================= |
712 | - def iv_auto_scroll(self): |
713 | - """Timeout routine for auto-scroll""" |
714 | - |
715 | - sw_vadj = self.sw.get_vadjustment() |
716 | - sw_vpos = sw_vadj.get_value() |
717 | - if self.iv_auto_scroll_direction == gtk.DIR_UP: |
718 | - sw_vpos -= sw_vadj.step_increment |
719 | - sw_vadj.set_value( max(sw_vpos, sw_vadj.lower) ) |
720 | - elif self.iv_auto_scroll_direction == gtk.DIR_DOWN: |
721 | - sw_vpos += sw_vadj.step_increment |
722 | - sw_vadj.set_value( min(sw_vpos, sw_vadj.upper - sw_vadj.page_size) ) |
723 | - return True #call me again |
724 | - |
725 | - # ======================================================= |
726 | - def iv_button_press_event(self, iconview, event): |
727 | - """Manages mouse clicks on the iconview""" |
728 | - |
729 | - if event.button == 3: |
730 | - x = int(event.x) |
731 | - y = int(event.y) |
732 | - time = event.time |
733 | - path = iconview.get_path_at_pos(x, y) |
734 | - selection = iconview.get_selected_items() |
735 | - if path: |
736 | - if path not in selection: |
737 | - iconview.unselect_all() |
738 | - iconview.select_path(path) |
739 | - iconview.grab_focus() |
740 | - self.popup.popup( None, None, None, event.button, time) |
741 | - return 1 |
742 | - |
743 | - # ======================================================= |
744 | - def sw_dnd_received_data(self, scrolledwindow, context, x, y, |
745 | - selection_data, target_id, etime): |
746 | - """Handles received data by drag and drop in scrolledwindow""" |
747 | - |
748 | - data = selection_data.data |
749 | - if target_id == self.MODEL_ROW_EXTERN: |
750 | - self.model |
751 | - if data: |
752 | - data = data.split('\n;\n') |
753 | - while data: |
754 | - tmp = data.pop(0).split('\n') |
755 | - filename = tmp[0] |
756 | - npage, angle = [int(k) for k in tmp[1:3]] |
757 | - crop = [float(side) for side in tmp[3:7]] |
758 | - if self.add_pdf_pages(filename, npage, npage, angle, crop): |
759 | - if context.action == gtk.gdk.ACTION_MOVE: |
760 | - context.finish(True, True, etime) |
761 | - elif target_id == self.TEXT_URI_LIST: |
762 | - uri = data.strip() |
763 | - uri_splitted = uri.split() # we may have more than one file dropped |
764 | - for uri in uri_splitted: |
765 | - filename = self.get_file_path_from_dnd_dropped_uri(uri) |
766 | - if os.path.isfile(filename): # is it file? |
767 | - self.add_pdf_pages(filename) |
768 | - |
769 | - # ======================================================= |
770 | - def sw_button_press_event(self, scrolledwindow, event): |
771 | - """Unselects all items in iconview on mouse click in scrolledwindow""" |
772 | - |
773 | - if event.button == 1: |
774 | - self.iconview.unselect_all() |
775 | - |
776 | - # ======================================================= |
777 | - def get_file_path_from_dnd_dropped_uri(self, uri): |
778 | - """Extracts the path from an uri""" |
779 | - |
780 | - path = urllib.url2pathname(uri) # escape special chars |
781 | - path = path.strip('\r\n\x00') # remove \r\n and NULL |
782 | - |
783 | - # get the path to file |
784 | - if path.startswith('file:\\\\\\'): # windows |
785 | - path = path[8:] # 8 is len('file:///') |
786 | - elif path.startswith('file://'): # nautilus, rox |
787 | - path = path[7:] # 7 is len('file://') |
788 | - elif path.startswith('file:'): # xffm |
789 | - path = path[5:] # 5 is len('file:') |
790 | - return path |
791 | - |
792 | - # ======================================================= |
793 | - def rotate_page_right(self, widget, data=None): |
794 | - self.rotate_page(90) |
795 | - |
796 | - def rotate_page_left(self, widget, data=None): |
797 | - self.rotate_page(-90) |
798 | - |
799 | - def rotate_page(self, angle): |
800 | - """Rotates the selected page in the IconView""" |
801 | - |
802 | - model = self.iconview.get_model() |
803 | - selection = self.iconview.get_selected_items() |
804 | - for path in selection: |
805 | - iter = model.get_iter(path) |
806 | - nfile = model.get_value(iter, 2) |
807 | - npage = model.get_value(iter, 3) |
808 | - |
809 | - rotate_times = ( ( (-angle) % 360 + 45 ) / 90 ) % 4 |
810 | - crop = [0.,0.,0.,0.] |
811 | - if rotate_times is not 0: |
812 | - perm = [0,2,1,3] |
813 | - for it in range(rotate_times): |
814 | - perm.append(perm.pop(0)) |
815 | - perm.insert(1,perm.pop(2)) |
816 | - crop = [model.get_value(iter, 8 + perm[side]) for side in range(4)] |
817 | - for side in range(4): |
818 | - model.set_value(iter, 8 + side, crop[side]) |
819 | - |
820 | - new_angle = model.get_value(iter, 7) + angle |
821 | - model.set_value(iter, 7, new_angle) |
822 | - model.set_value(iter, 6, False) #rendering request |
823 | - |
824 | - self.render() |
825 | - |
826 | - # ======================================================= |
827 | - def crop_page_dialog(self, widget): |
828 | - """Opens a dialog box to define margins for page cropping""" |
829 | - |
830 | - sides = ('L', 'R', 'T', 'B') |
831 | - side_names = {'L':_('Left'), 'R':_('Right'), |
832 | - 'T':_('Top'), 'B':_('Bottom') } |
833 | - opposite_sides = {'L':'R', 'R':'L', 'T':'B', 'B':'T' } |
834 | - |
835 | - def set_crop_value(spinbutton, side): |
836 | - opp_side = opposite_sides[side] |
837 | - pos = sides.index(opp_side) |
838 | - adj = spin_list[pos].get_adjustment() |
839 | - adj.set_upper(99.0 - spinbutton.get_value()) |
840 | - |
841 | - model = self.iconview.get_model() |
842 | - selection = self.iconview.get_selected_items() |
843 | - |
844 | - crop = [0.,0.,0.,0.] |
845 | - if selection: |
846 | - path = selection[0] |
847 | - pos = model.get_iter(path) |
848 | - crop = [model.get_value(pos, 8 + side) for side in range(4)] |
849 | - |
850 | - dialog = gtk.Dialog(title=(_('Crop Selected Page(s)')), |
851 | - parent=self.window, |
852 | - flags=gtk.DIALOG_MODAL, |
853 | - buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, |
854 | - gtk.STOCK_OK, gtk.RESPONSE_OK)) |
855 | - dialog.set_size_request(340, 250) |
856 | - dialog.set_default_response(gtk.RESPONSE_OK) |
857 | - |
858 | - frame = gtk.Frame(_('Crop Margins')) |
859 | - dialog.vbox.pack_start(frame, False, False, 20) |
860 | - |
861 | - vbox = gtk.VBox(False, 0) |
862 | - frame.add(vbox) |
863 | - |
864 | - spin_list = [] |
865 | - units = 2 * [_('% of width')] + 2 * [_('% of height')] |
866 | - for side in sides: |
867 | - hbox = gtk.HBox(True, 0) |
868 | - vbox.pack_start(hbox, False, False, 5) |
869 | - |
870 | - label = gtk.Label(side_names[side]) |
871 | - label.set_alignment(0, 0.0) |
872 | - hbox.pack_start(label, True, True, 20) |
873 | - |
874 | - adj = gtk.Adjustment(100.*crop.pop(0), 0.0, 99.0, 1.0, 5.0, 0.0) |
875 | - spin = gtk.SpinButton(adj, 0, 1) |
876 | - spin.set_activates_default(True) |
877 | - spin.connect('value-changed', set_crop_value, side) |
878 | - spin_list.append(spin) |
879 | - hbox.pack_start(spin, False, False, 30) |
880 | - |
881 | - label = gtk.Label(units.pop(0)) |
882 | - label.set_alignment(0, 0.0) |
883 | - hbox.pack_start(label, True, True, 0) |
884 | - |
885 | - dialog.show_all() |
886 | - result = dialog.run() |
887 | - |
888 | - if result == gtk.RESPONSE_OK: |
889 | - crop = [] |
890 | - for spin in spin_list: |
891 | - crop.append( spin.get_value()/100. ) |
892 | - for path in selection: |
893 | - pos = model.get_iter(path) |
894 | - for it in range(4): |
895 | - model.set_value(pos, 8 + it, crop[it]) |
896 | - model.set_value(pos, 6, False) #rendering request |
897 | - self.render() |
898 | - elif result == gtk.RESPONSE_CANCEL: |
899 | - print(_('Dialog closed')) |
900 | - dialog.destroy() |
901 | - |
902 | - # ======================================================= |
903 | - def about_dialog(self, widget, data=None): |
904 | - about_dialog = gtk.AboutDialog() |
905 | - try: |
906 | - about_dialog.set_transient_for(self.window) |
907 | - about_dialog.set_modal(True) |
908 | - except: |
909 | - pass |
910 | - # FIXME |
911 | - about_dialog.set_name('PDF-Shuffler') |
912 | - about_dialog.set_version('0.5.1') |
913 | - about_dialog.set_comments(_( |
914 | - 'PDF-Shuffler is a simple pyGTK utility which lets you merge, \ |
915 | -split and rearrange PDF documents. You can also rotate and crop individual \ |
916 | -pages of a pdf document.')) |
917 | - about_dialog.set_authors(['Konstantinos Poulios',]) |
918 | - about_dialog.set_website_label('http://pdfshuffler.sourceforge.net/') |
919 | - about_dialog.set_logo_icon_name('pdfshuffler') |
920 | - about_dialog.connect('response', lambda w, a: about_dialog.destroy()) |
921 | - about_dialog.connect('delete_event', lambda w, a: about_dialog.destroy()) |
922 | - about_dialog.show_all() |
923 | - |
924 | -# ======================================================= |
925 | -class PDF_Doc: |
926 | - """Class handling pdf documents""" |
927 | - |
928 | - def __init__(self, filename, nfile, tmp_dir): |
929 | - |
930 | - self.filename = os.path.abspath(filename) |
931 | - (self.path, self.shortname) = os.path.split(self.filename) |
932 | - (self.shortname, self.ext) = os.path.splitext(self.shortname) |
933 | - f = gio.File(filename) |
934 | - mime_type = f.query_info('standard::content-type').get_content_type() |
935 | - if mime_type == 'application/pdf': |
936 | - self.nfile = nfile + 1 |
937 | - self.mtime = os.path.getmtime(filename) |
938 | - self.copyname = os.path.join(tmp_dir, '%02d_' % self.nfile + |
939 | - self.shortname + '.pdf') |
940 | - shutil.copy(self.filename, self.copyname) |
941 | - self.document = poppler.document_new_from_file("file://" + self.copyname, None) |
942 | - self.npage = self.document.get_n_pages() |
943 | - else: |
944 | - self.nfile = 0 |
945 | - |
946 | - |
947 | -# ======================================================= |
948 | -class PDF_Renderer(threading.Thread,gobject.GObject): |
949 | - |
950 | - def __init__(self, model, pdfqueue, scale=1., width=100): |
951 | - threading.Thread.__init__(self) |
952 | - gobject.GObject.__init__(self) |
953 | - self.model = model |
954 | - self.scale = scale |
955 | - self.default_width = width |
956 | - self.pdfqueue = pdfqueue |
957 | - self.quit = False |
958 | - self.evnt = threading.Event() |
959 | - self.paused = False |
960 | - |
961 | - def run(self): |
962 | - while not self.quit: |
963 | - rendered_all = True |
964 | - for row in self.model: |
965 | - if self.quit: |
966 | - break |
967 | - if not row[6]: |
968 | - rendered_all = False |
969 | - gtk.gdk.threads_enter() |
970 | - try: |
971 | - nfile = row[2] |
972 | - npage = row[3] |
973 | - angle = row[7] |
974 | - crop = [row[8],row[9],row[10],row[11]] |
975 | - pdfdoc = self.pdfqueue[nfile - 1] |
976 | - thumbnail = self.load_pdf_thumbnail(pdfdoc, npage, angle, crop) |
977 | - row[6] = True |
978 | - row[4] = thumbnail.get_width() |
979 | - row[1] = thumbnail |
980 | - finally: |
981 | - gtk.gdk.threads_leave() |
982 | - if rendered_all: |
983 | - if self.model.get_iter_first(): #just checking if model isn't empty |
984 | - self.emit('reset_iv_width') |
985 | - self.paused = True |
986 | - self.evnt.wait() |
987 | - |
988 | - # ======================================================= |
989 | - def load_pdf_thumbnail(self, pdfdoc, npage, rotation=0, crop=[0.,0.,0.,0.]): |
990 | - """Create pdf pixbuf""" |
991 | - |
992 | - page = pdfdoc.document.get_page(npage-1) |
993 | - try: |
994 | - pix_w, pix_h = page.get_size() |
995 | - pix_w = int(pix_w * self.scale) |
996 | - pix_h = int(pix_h * self.scale) |
997 | - thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, |
998 | - 8, pix_w , pix_h) |
999 | - page.render_to_pixbuf(0,0,pix_w,pix_h,self.scale,0,thumbnail) |
1000 | - rotation = (-rotation) % 360 |
1001 | - rotation = ((rotation + 45) / 90) * 90 |
1002 | - thumbnail = thumbnail.rotate_simple(rotation) |
1003 | - pix_w = thumbnail.get_width() |
1004 | - pix_h = thumbnail.get_height() |
1005 | - if crop != [0.,0.,0.,0.]: |
1006 | - src_x = int( crop[0] * pix_w ) |
1007 | - src_y = int( crop[2] * pix_h ) |
1008 | - width = int( (1. - crop[0] - crop[1]) * pix_w ) |
1009 | - height = int( (1. - crop[2] - crop[3]) * pix_h ) |
1010 | - new_thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, |
1011 | - 8, width, height) |
1012 | - thumbnail.copy_area(src_x, src_y, width, height, |
1013 | - new_thumbnail, 0, 0) |
1014 | - thumbnail = new_thumbnail |
1015 | - pix_w = thumbnail.get_width() |
1016 | - pix_h = thumbnail.get_height() |
1017 | - except: |
1018 | - pix_w = self.default_width |
1019 | - pix_h = pix_w |
1020 | - thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, |
1021 | - 8, pix_w, pix_h) |
1022 | - pixbuf.fill(0xffffffff) |
1023 | - |
1024 | - #add border |
1025 | - thickness = 3 |
1026 | - color = 0x000000FF |
1027 | - canvas = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, |
1028 | - pix_w + thickness + 1, |
1029 | - pix_h + thickness + 1) |
1030 | - canvas.fill(color) |
1031 | - thumbnail.copy_area(0, 0, pix_w, pix_h, canvas, 1, 1) |
1032 | - thumbnail = canvas |
1033 | - |
1034 | - return thumbnail |
1035 | - |
1036 | - |
1037 | -# ======================================================= |
1038 | -if __name__ == '__main__': |
1039 | - PDFshuffler() |
1040 | - gtk.gdk.threads_enter() |
1041 | - gtk.main() |
1042 | - gtk.gdk.threads_leave() |
1043 | - |
1044 | |
1045 | === added directory '.pc/install_and_look_for_UI_file_in_right_dirs.patch' |
1046 | === added directory '.pc/install_and_look_for_UI_file_in_right_dirs.patch/pdfshuffler' |
1047 | === added file '.pc/install_and_look_for_UI_file_in_right_dirs.patch/pdfshuffler/pdfshuffler.py' |
1048 | --- .pc/install_and_look_for_UI_file_in_right_dirs.patch/pdfshuffler/pdfshuffler.py 1970-01-01 00:00:00 +0000 |
1049 | +++ .pc/install_and_look_for_UI_file_in_right_dirs.patch/pdfshuffler/pdfshuffler.py 2012-02-12 00:35:23 +0000 |
1050 | @@ -0,0 +1,1057 @@ |
1051 | +#!/usr/bin/python |
1052 | +# -*- coding: utf-8 -*- |
1053 | + |
1054 | +""" |
1055 | + |
1056 | + PdfShuffler 0.6.0 - GTK+ based utility for splitting, rearrangement and |
1057 | + modification of PDF documents. |
1058 | + Copyright (C) 2008-2011 Konstantinos Poulios |
1059 | + <https://sourceforge.net/projects/pdfshuffler> |
1060 | + |
1061 | + This file is part of PdfShuffler. |
1062 | + |
1063 | + PdfShuffler is free software; you can redistribute it and/or modify |
1064 | + it under the terms of the GNU General Public License as published by |
1065 | + the Free Software Foundation; either version 3 of the License, or |
1066 | + (at your option) any later version. |
1067 | + |
1068 | + This program is distributed in the hope that it will be useful, |
1069 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
1070 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1071 | + GNU General Public License for more details. |
1072 | + |
1073 | + You should have received a copy of the GNU General Public License along |
1074 | + with this program; if not, write to the Free Software Foundation, Inc., |
1075 | + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
1076 | + |
1077 | +""" |
1078 | + |
1079 | +import os |
1080 | +import shutil #needed for file operations like whole directory deletion |
1081 | +import sys #needed for proccessing of command line args |
1082 | +import urllib #needed to parse filename information passed by DnD |
1083 | +import threading |
1084 | +import tempfile |
1085 | +from copy import copy |
1086 | + |
1087 | +import locale #for multilanguage support |
1088 | +import gettext |
1089 | +gettext.install('pdfshuffler', unicode=1) |
1090 | + |
1091 | + |
1092 | +APPNAME = 'PdfShuffler' # PDF-Shuffler, PDFShuffler, pdfshuffler |
1093 | +VERSION = '0.6.0' |
1094 | +WEBSITE = 'http://pdfshuffler.sourceforge.net/' |
1095 | +LICENSE = 'GNU General Public License (GPL) Version 3.' |
1096 | + |
1097 | +try: |
1098 | + import pygtk |
1099 | + pygtk.require('2.0') |
1100 | + import gtk |
1101 | + assert gtk.gtk_version >= (2, 10, 0) |
1102 | + assert gtk.pygtk_version >= (2, 10, 0) |
1103 | +except AssertionError: |
1104 | + print('You do not have the required versions of GTK+ and PyGTK ' + |
1105 | + 'installed.\n\n' + |
1106 | + 'Installed GTK+ version is ' + |
1107 | + '.'.join([str(n) for n in gtk.gtk_version]) + '\n' + |
1108 | + 'Required GTK+ version is 2.10.0 or higher\n\n' |
1109 | + 'Installed PyGTK version is ' + |
1110 | + '.'.join([str(n) for n in gtk.pygtk_version]) + '\n' + |
1111 | + 'Required PyGTK version is 2.10.0 or higher') |
1112 | + sys.exit(1) |
1113 | +except: |
1114 | + print('PyGTK version 2.10.0 or higher is required to run this program.') |
1115 | + print('No version of PyGTK was found on your system.') |
1116 | + sys.exit(1) |
1117 | + |
1118 | +import gobject #to use custom signals |
1119 | +import pango #to adjust the text alignment in CellRendererText |
1120 | +import gio #to inquire mime types information |
1121 | +import cairo |
1122 | + |
1123 | +import poppler #for the rendering of pdf pages |
1124 | +from pyPdf import PdfFileWriter, PdfFileReader |
1125 | + |
1126 | +from cairorendering import CellRendererImage, CairoImage |
1127 | +gobject.type_register(CellRendererImage) |
1128 | + |
1129 | +import time |
1130 | + |
1131 | +class PdfShuffler: |
1132 | + prefs = { |
1133 | + 'window width': min(700, gtk.gdk.screen_get_default().get_width() / 2), |
1134 | + 'window height': min(600, gtk.gdk.screen_get_default().get_height() - 50), |
1135 | + 'window x': 0, |
1136 | + 'window y': 0, |
1137 | + 'initial thumbnail size': 300, |
1138 | + 'initial zoom level': -14, |
1139 | + } |
1140 | + |
1141 | + MODEL_ROW_INTERN = 1001 |
1142 | + MODEL_ROW_EXTERN = 1002 |
1143 | + TEXT_URI_LIST = 1003 |
1144 | + MODEL_ROW_MOTION = 1004 |
1145 | + TARGETS_IV = [('MODEL_ROW_INTERN', gtk.TARGET_SAME_WIDGET, MODEL_ROW_INTERN), |
1146 | + ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN), |
1147 | + ('MODEL_ROW_MOTION', 0, MODEL_ROW_MOTION)] |
1148 | + TARGETS_SW = [('text/uri-list', 0, TEXT_URI_LIST), |
1149 | + ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN)] |
1150 | + |
1151 | + def __init__(self): |
1152 | + # Create the temporary directory |
1153 | + self.tmp_dir = tempfile.mkdtemp("pdfshuffler") |
1154 | + os.chmod(self.tmp_dir, 0700) |
1155 | + |
1156 | + icon_theme = gtk.icon_theme_get_default() |
1157 | + try: |
1158 | + gtk.window_set_default_icon(icon_theme.load_icon("pdfshuffler", 64, 0)) |
1159 | + except: |
1160 | + print(_("Can't load icon. Application is not installed correctly.")) |
1161 | + |
1162 | + # Import the user interface file, trying different possible locations |
1163 | + ui_path = '/usr/share/pdfshuffler/pdfshuffler.ui' |
1164 | + if not os.path.exists(ui_path): |
1165 | + ui_path = '/usr/local/share/applications/pdfshuffler/pdfshuffler.ui' |
1166 | + if not os.path.exists(ui_path): |
1167 | + parent_dir = os.path.dirname( \ |
1168 | + os.path.dirname(os.path.realpath(__file__))) |
1169 | + ui_path = os.path.join(parent_dir, 'data', 'pdfshuffler.ui') |
1170 | + self.uiXML = gtk.Builder() |
1171 | + self.uiXML.add_from_file(ui_path) |
1172 | + self.uiXML.connect_signals(self) |
1173 | + |
1174 | + # Create the main window, and attach delete_event signal to terminating |
1175 | + # the application |
1176 | + self.window = self.uiXML.get_object('main_window') |
1177 | + self.window.set_title(APPNAME) |
1178 | + self.window.set_border_width(0) |
1179 | + self.window.move(self.prefs['window x'], self.prefs['window y']) |
1180 | + self.window.set_default_size(self.prefs['window width'], |
1181 | + self.prefs['window height']) |
1182 | + self.window.connect('delete_event', self.close_application) |
1183 | + self.window.show_all() |
1184 | + |
1185 | + # Create a scrolled window to hold the thumbnails-container |
1186 | + self.sw = self.uiXML.get_object('scrolledwindow') |
1187 | + self.sw.drag_dest_set(gtk.DEST_DEFAULT_MOTION | |
1188 | + gtk.DEST_DEFAULT_HIGHLIGHT | |
1189 | + gtk.DEST_DEFAULT_DROP | |
1190 | + gtk.DEST_DEFAULT_MOTION, |
1191 | + self.TARGETS_SW, |
1192 | + gtk.gdk.ACTION_COPY | |
1193 | + gtk.gdk.ACTION_MOVE) |
1194 | + self.sw.connect('drag_data_received', self.sw_dnd_received_data) |
1195 | + self.sw.connect('button_press_event', self.sw_button_press_event) |
1196 | + self.sw.connect('scroll_event', self.sw_scroll_event) |
1197 | + |
1198 | + # Create an alignment to keep the thumbnails center-aligned |
1199 | + align = gtk.Alignment(0.5, 0.5, 0, 0) |
1200 | + self.sw.add_with_viewport(align) |
1201 | + |
1202 | + # Create ListStore model and IconView |
1203 | + self.model = gtk.ListStore(str, # 0.Text descriptor |
1204 | + CairoImage, # 1.Cached page image |
1205 | + int, # 2.Document number |
1206 | + int, # 3.Page number |
1207 | + float, # 4.Scale |
1208 | + str, # 5.Document filename |
1209 | + int, # 6.Rotation angle |
1210 | + float, # 7.Crop left |
1211 | + float, # 8.Crop right |
1212 | + float, # 9.Crop top |
1213 | + float, # 10.Crop bottom |
1214 | + int, # 11.Page width |
1215 | + int) # 12.Page height |
1216 | + |
1217 | + self.zoom_set(self.prefs['initial zoom level']) |
1218 | + self.iv_col_width = self.prefs['initial thumbnail size'] |
1219 | + |
1220 | + self.iconview = gtk.IconView(self.model) |
1221 | + self.iconview.set_item_width(self.iv_col_width + 12) |
1222 | + |
1223 | + self.cellthmb = CellRendererImage() |
1224 | + self.iconview.pack_start(self.cellthmb, False) |
1225 | + self.iconview.set_attributes(self.cellthmb, image=1, |
1226 | + scale=4, rotation=6, cropL=7, cropR=8, cropT=9, cropB=10, |
1227 | + width=11, height=12) |
1228 | + |
1229 | +# self.iconview.set_text_column(0) |
1230 | + self.celltxt = gtk.CellRendererText() |
1231 | + self.celltxt.set_property('width', self.iv_col_width) |
1232 | + self.celltxt.set_property('wrap-width', self.iv_col_width) |
1233 | + self.celltxt.set_property('alignment', pango.ALIGN_CENTER) |
1234 | + self.iconview.pack_start(self.celltxt, False) |
1235 | + self.iconview.set_attributes(self.celltxt, text=0) |
1236 | + |
1237 | + self.iconview.set_selection_mode(gtk.SELECTION_MULTIPLE) |
1238 | + self.iconview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, |
1239 | + self.TARGETS_IV, |
1240 | + gtk.gdk.ACTION_COPY | |
1241 | + gtk.gdk.ACTION_MOVE) |
1242 | + self.iconview.enable_model_drag_dest(self.TARGETS_IV, |
1243 | + gtk.gdk.ACTION_DEFAULT) |
1244 | + self.iconview.connect('drag_begin', self.iv_drag_begin) |
1245 | + self.iconview.connect('drag_data_get', self.iv_dnd_get_data) |
1246 | + self.iconview.connect('drag_data_received', self.iv_dnd_received_data) |
1247 | + self.iconview.connect('drag_data_delete', self.iv_dnd_data_delete) |
1248 | + self.iconview.connect('drag_motion', self.iv_dnd_motion) |
1249 | + self.iconview.connect('drag_leave', self.iv_dnd_leave_end) |
1250 | + self.iconview.connect('drag_end', self.iv_dnd_leave_end) |
1251 | + self.iconview.connect('button_press_event', self.iv_button_press_event) |
1252 | + self.iv_auto_scroll_direction = 0 |
1253 | + |
1254 | + style = self.iconview.get_style().copy() |
1255 | + style_sw = self.sw.get_style() |
1256 | + for state in (gtk.STATE_NORMAL, gtk.STATE_PRELIGHT, gtk.STATE_ACTIVE): |
1257 | + style.base[state] = style_sw.bg[gtk.STATE_NORMAL] |
1258 | + self.iconview.set_style(style) |
1259 | + |
1260 | + align.add(self.iconview) |
1261 | + |
1262 | + # Progress bar |
1263 | + self.progress_bar = self.uiXML.get_object('progressbar') |
1264 | + self.progress_bar_timeout_id = 0 |
1265 | + |
1266 | + # Define window callback function and show window |
1267 | + self.window.connect('size_allocate', self.on_window_size_request) # resize |
1268 | + self.window.connect('key_press_event', self.on_keypress_event ) # keypress |
1269 | + self.window.show_all() |
1270 | + self.progress_bar.hide_all() |
1271 | + |
1272 | + # Creating the popup menu |
1273 | + self.popup = gtk.Menu() |
1274 | + popup_rotate_right = gtk.ImageMenuItem(_('_Rotate Right')) |
1275 | + popup_rotate_left = gtk.ImageMenuItem(_('Rotate _Left')) |
1276 | + popup_crop = gtk.MenuItem(_('C_rop...')) |
1277 | + popup_delete = gtk.ImageMenuItem(gtk.STOCK_DELETE) |
1278 | + popup_rotate_right.connect('activate', self.rotate_page_right) |
1279 | + popup_rotate_left.connect('activate', self.rotate_page_left) |
1280 | + popup_crop.connect('activate', self.crop_page_dialog) |
1281 | + popup_delete.connect('activate', self.clear_selected) |
1282 | + popup_rotate_right.show() |
1283 | + popup_rotate_left.show() |
1284 | + popup_crop.show() |
1285 | + popup_delete.show() |
1286 | + self.popup.append(popup_rotate_right) |
1287 | + self.popup.append(popup_rotate_left) |
1288 | + self.popup.append(popup_crop) |
1289 | + self.popup.append(popup_delete) |
1290 | + |
1291 | + # Initializing variables |
1292 | + self.export_directory = os.getenv('HOME') |
1293 | + self.import_directory = self.export_directory |
1294 | + self.nfile = 0 |
1295 | + self.iv_auto_scroll_timer = None |
1296 | + self.pdfqueue = [] |
1297 | + |
1298 | + gobject.type_register(PDF_Renderer) |
1299 | + gobject.signal_new('update_thumbnail', PDF_Renderer, |
1300 | + gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, |
1301 | + [gobject.TYPE_INT, gobject.TYPE_PYOBJECT]) |
1302 | + self.rendering_thread = 0 |
1303 | + |
1304 | + self.set_unsaved(False) |
1305 | + |
1306 | + # Importing documents passed as command line arguments |
1307 | + for filename in sys.argv[1:]: |
1308 | + self.add_pdf_pages(filename) |
1309 | + |
1310 | + def render(self): |
1311 | + if self.rendering_thread: |
1312 | + self.rendering_thread.quit = True |
1313 | + self.rendering_thread = PDF_Renderer(self.model, self.pdfqueue) |
1314 | + self.rendering_thread.connect('update_thumbnail', self.update_thumbnail) |
1315 | + self.rendering_thread.start() |
1316 | + |
1317 | + if self.progress_bar_timeout_id: |
1318 | + gobject.source_remove(self.progress_bar_timeout_id) |
1319 | + self.progress_bar_timout_id = \ |
1320 | + gobject.timeout_add(50, self.progress_bar_timeout) |
1321 | + |
1322 | + def set_unsaved(self, flag): |
1323 | + self.is_unsaved = flag |
1324 | + gobject.idle_add(self.retitle) |
1325 | + |
1326 | + def retitle(self): |
1327 | + title = '' |
1328 | + if len(self.pdfqueue) == 1: |
1329 | + title += self.pdfqueue[0].filename |
1330 | + elif len(self.pdfqueue) == 0: |
1331 | + title += _("No document") |
1332 | + else: |
1333 | + title += _("Several documents") |
1334 | + if self.is_unsaved: |
1335 | + title += '*' |
1336 | + title += ' - ' + APPNAME |
1337 | + self.window.set_title(title) |
1338 | + |
1339 | + def progress_bar_timeout(self): |
1340 | + cnt_finished = 0 |
1341 | + cnt_all = 0 |
1342 | + for row in self.model: |
1343 | + cnt_all += 1 |
1344 | + if row[1].surface: |
1345 | + cnt_finished += 1 |
1346 | + fraction = float(cnt_finished)/float(cnt_all) |
1347 | + |
1348 | + self.progress_bar.set_fraction(fraction) |
1349 | + self.progress_bar.set_text(_('Rendering thumbnails... [%(i1)s/%(i2)s]') |
1350 | + % {'i1' : cnt_finished, 'i2' : cnt_all}) |
1351 | + if fraction >= 0.999: |
1352 | + self.progress_bar.hide_all() |
1353 | + return False |
1354 | + elif not self.progress_bar.flags() & gtk.VISIBLE: |
1355 | + self.progress_bar.show_all() |
1356 | + |
1357 | + return True |
1358 | + |
1359 | + def update_thumbnail(self, object, num, thumbnail): |
1360 | + row = self.model[num] |
1361 | + row[4] = self.zoom_scale |
1362 | + row[1] = thumbnail |
1363 | + |
1364 | + def on_window_size_request(self, window, event): |
1365 | + """Main Window resize - workaround for autosetting of |
1366 | + iconview cols no.""" |
1367 | + |
1368 | + #add 12 because of: http://bugzilla.gnome.org/show_bug.cgi?id=570152 |
1369 | + col_num = 9 * window.get_size()[0] \ |
1370 | + / (10 * (self.iv_col_width + self.iconview.get_column_spacing() * 2)) |
1371 | + self.iconview.set_columns(col_num) |
1372 | + |
1373 | + def update_geometry(self, iter): |
1374 | + """Recomputes the width and height of the rotated page and saves |
1375 | + the result in the ListStore""" |
1376 | + |
1377 | + if not self.model.iter_is_valid(iter): |
1378 | + return |
1379 | + |
1380 | + nfile, npage, rotation = self.model.get(iter, 2, 3, 6) |
1381 | + crop = self.model.get(iter, 7, 8, 9, 10) |
1382 | + page = self.pdfqueue[nfile-1].document.get_page(npage-1) |
1383 | + w0, h0 = page.get_size() |
1384 | + |
1385 | + rotation = int(rotation) % 360 |
1386 | + rotation = ((rotation + 45) / 90) * 90 |
1387 | + if rotation == 90 or rotation == 270: |
1388 | + w1, h1 = h0, w0 |
1389 | + else: |
1390 | + w1, h1 = w0, h0 |
1391 | + |
1392 | + self.model.set(iter, 11, w1, 12, h1) |
1393 | + |
1394 | + def reset_iv_width(self, renderer=None): |
1395 | + """Reconfigures the width of the iconview columns""" |
1396 | + |
1397 | + if not self.model.get_iter_first(): #just checking if model is empty |
1398 | + return |
1399 | + |
1400 | + max_w = 10 + int(max(row[4]*row[11]*(1.-row[7]-row[8]) for row in self.model)) |
1401 | + if max_w != self.iv_col_width: |
1402 | + self.iv_col_width = max_w |
1403 | + self.celltxt.set_property('width', self.iv_col_width) |
1404 | + self.celltxt.set_property('wrap-width', self.iv_col_width) |
1405 | + self.iconview.set_item_width(self.iv_col_width + 12) #-1) |
1406 | + self.on_window_size_request(self.window, None) |
1407 | + |
1408 | + def on_keypress_event(self, widget, event): |
1409 | + """Keypress events in Main Window""" |
1410 | + |
1411 | + #keyname = gtk.gdk.keyval_name(event.keyval) |
1412 | + if event.keyval == 65535: # Delete keystroke |
1413 | + self.clear_selected() |
1414 | + |
1415 | + def close_application(self, widget, event=None, data=None): |
1416 | + """Termination""" |
1417 | + |
1418 | + if self.rendering_thread: |
1419 | + self.rendering_thread.quit = True |
1420 | + while self.rendering_thread.isAlive(): |
1421 | + time.sleep(0.5) |
1422 | + |
1423 | + if os.path.isdir(self.tmp_dir): |
1424 | + shutil.rmtree(self.tmp_dir) |
1425 | + if gtk.main_level(): |
1426 | + gtk.main_quit() |
1427 | + else: |
1428 | + sys.exit(0) |
1429 | + return False |
1430 | + |
1431 | + def add_pdf_pages(self, filename, |
1432 | + firstpage=None, lastpage=None, |
1433 | + angle=0, crop=[0.,0.,0.,0.]): |
1434 | + """Add pages of a pdf document to the model""" |
1435 | + |
1436 | + res = False |
1437 | + # Check if the document has already been loaded |
1438 | + pdfdoc = None |
1439 | + for it_pdfdoc in self.pdfqueue: |
1440 | + if os.path.isfile(it_pdfdoc.filename) and \ |
1441 | + os.path.samefile(filename, it_pdfdoc.filename) and \ |
1442 | + os.path.getmtime(filename) is it_pdfdoc.mtime: |
1443 | + pdfdoc = it_pdfdoc |
1444 | + break |
1445 | + |
1446 | + if not pdfdoc: |
1447 | + pdfdoc = PDF_Doc(filename, self.nfile, self.tmp_dir) |
1448 | + self.import_directory = os.path.split(filename)[0] |
1449 | + self.export_directory = self.import_directory |
1450 | + if pdfdoc.nfile != 0 and pdfdoc != []: |
1451 | + self.nfile = pdfdoc.nfile |
1452 | + self.pdfqueue.append(pdfdoc) |
1453 | + else: |
1454 | + return res |
1455 | + |
1456 | + n_start = 1 |
1457 | + n_end = pdfdoc.npage |
1458 | + if firstpage: |
1459 | + n_start = min(n_end, max(1, firstpage)) |
1460 | + if lastpage: |
1461 | + n_end = max(n_start, min(n_end, lastpage)) |
1462 | + |
1463 | + for npage in range(n_start, n_end + 1): |
1464 | + descriptor = ''.join([pdfdoc.shortname, '\n', _('page'), ' ', str(npage)]) |
1465 | + page = pdfdoc.document.get_page(npage-1) |
1466 | + w, h = page.get_size() |
1467 | + iter = self.model.append((descriptor, # 0 |
1468 | + CairoImage(), # 1 |
1469 | + pdfdoc.nfile, # 2 |
1470 | + npage, # 3 |
1471 | + self.zoom_scale, # 4 |
1472 | + pdfdoc.filename, # 5 |
1473 | + angle, # 6 |
1474 | + crop[0],crop[1], # 7-8 |
1475 | + crop[2],crop[3], # 9-10 |
1476 | + w,h )) # 11-12 |
1477 | + self.update_geometry(iter) |
1478 | + res = True |
1479 | + |
1480 | + self.reset_iv_width() |
1481 | + gobject.idle_add(self.retitle) |
1482 | + if res: |
1483 | + gobject.idle_add(self.render) |
1484 | + return res |
1485 | + |
1486 | + def choose_export_pdf_name(self, widget=None): |
1487 | + """Handles choosing a name for exporting """ |
1488 | + |
1489 | + chooser = gtk.FileChooserDialog(title=_('Export ...'), |
1490 | + action=gtk.FILE_CHOOSER_ACTION_SAVE, |
1491 | + buttons=(gtk.STOCK_CANCEL, |
1492 | + gtk.RESPONSE_CANCEL, |
1493 | + gtk.STOCK_SAVE, |
1494 | + gtk.RESPONSE_OK)) |
1495 | + chooser.set_do_overwrite_confirmation(True) |
1496 | + chooser.set_current_folder(self.export_directory) |
1497 | + filter_pdf = gtk.FileFilter() |
1498 | + filter_pdf.set_name(_('PDF files')) |
1499 | + filter_pdf.add_mime_type('application/pdf') |
1500 | + chooser.add_filter(filter_pdf) |
1501 | + |
1502 | + filter_all = gtk.FileFilter() |
1503 | + filter_all.set_name(_('All files')) |
1504 | + filter_all.add_pattern('*') |
1505 | + chooser.add_filter(filter_all) |
1506 | + |
1507 | + while True: |
1508 | + response = chooser.run() |
1509 | + if response == gtk.RESPONSE_OK: |
1510 | + file_out = chooser.get_filename() |
1511 | + (path, shortname) = os.path.split(file_out) |
1512 | + (shortname, ext) = os.path.splitext(shortname) |
1513 | + if ext.lower() != '.pdf': |
1514 | + file_out = file_out + '.pdf' |
1515 | + try: |
1516 | + self.export_to_file(file_out) |
1517 | + self.export_directory = path |
1518 | + self.set_unsaved(False) |
1519 | + except Exception, e: |
1520 | + chooser.destroy() |
1521 | + error_msg_dlg = gtk.MessageDialog(flags=gtk.DIALOG_MODAL, |
1522 | + type=gtk.MESSAGE_ERROR, |
1523 | + message_format=str(e), |
1524 | + buttons=gtk.BUTTONS_OK) |
1525 | + response = error_msg_dlg.run() |
1526 | + if response == gtk.RESPONSE_OK: |
1527 | + error_msg_dlg.destroy() |
1528 | + return |
1529 | + break |
1530 | + chooser.destroy() |
1531 | + |
1532 | + def export_to_file(self, file_out): |
1533 | + """Export to file""" |
1534 | + |
1535 | + pdf_output = PdfFileWriter() |
1536 | + pdf_input = [] |
1537 | + for pdfdoc in self.pdfqueue: |
1538 | + pdfdoc_inp = PdfFileReader(file(pdfdoc.copyname, 'rb')) |
1539 | + if pdfdoc_inp.getIsEncrypted(): |
1540 | + try: # Workaround for lp:#355479 |
1541 | + stat = pdfdoc_inp.decrypt('') |
1542 | + except: |
1543 | + stat = 0 |
1544 | + if (stat!=1): |
1545 | + errmsg = _('File %s is encrypted.\n' |
1546 | + 'Support for encrypted files has not been implemented yet.\n' |
1547 | + 'File export failed.') % pdfdoc.filename |
1548 | + raise Exception, errmsg |
1549 | + #FIXME |
1550 | + #else |
1551 | + # ask for password and decrypt file |
1552 | + pdf_input.append(pdfdoc_inp) |
1553 | + |
1554 | + for row in self.model: |
1555 | + # add pages from input to output document |
1556 | + nfile = row[2] |
1557 | + npage = row[3] |
1558 | + current_page = copy(pdf_input[nfile-1].getPage(npage-1)) |
1559 | + angle = row[6] |
1560 | + angle0 = current_page.get("/Rotate",0) |
1561 | + crop = [row[7],row[8],row[9],row[10]] |
1562 | + if angle != 0: |
1563 | + current_page.rotateClockwise(angle) |
1564 | + if crop != [0.,0.,0.,0.]: |
1565 | + rotate_times = (((angle + angle0) % 360 + 45) / 90) % 4 |
1566 | + crop_init = crop |
1567 | + if rotate_times != 0: |
1568 | + perm = [0,2,1,3] |
1569 | + for it in range(rotate_times): |
1570 | + perm.append(perm.pop(0)) |
1571 | + perm.insert(1,perm.pop(2)) |
1572 | + crop = [crop_init[perm[side]] for side in range(4)] |
1573 | + #(x1, y1) = current_page.cropBox.lowerLeft |
1574 | + #(x2, y2) = current_page.cropBox.upperRight |
1575 | + (x1, y1) = [float(xy) for xy in current_page.mediaBox.lowerLeft] |
1576 | + (x2, y2) = [float(xy) for xy in current_page.mediaBox.upperRight] |
1577 | + x1_new = int(x1 + (x2-x1) * crop[0]) |
1578 | + x2_new = int(x2 - (x2-x1) * crop[1]) |
1579 | + y1_new = int(y1 + (y2-y1) * crop[3]) |
1580 | + y2_new = int(y2 - (y2-y1) * crop[2]) |
1581 | + #current_page.cropBox.lowerLeft = (x1_new, y1_new) |
1582 | + #current_page.cropBox.upperRight = (x2_new, y2_new) |
1583 | + current_page.mediaBox.lowerLeft = (x1_new, y1_new) |
1584 | + current_page.mediaBox.upperRight = (x2_new, y2_new) |
1585 | + |
1586 | + pdf_output.addPage(current_page) |
1587 | + |
1588 | + # finally, write "output" to document-output.pdf |
1589 | + print(_('exporting to:'), file_out) |
1590 | + pdf_output.write(file(file_out, 'wb')) |
1591 | + |
1592 | + def on_action_add_doc_activate(self, widget, data=None): |
1593 | + """Import doc""" |
1594 | + |
1595 | + chooser = gtk.FileChooserDialog(title=_('_Import...'), |
1596 | + action=gtk.FILE_CHOOSER_ACTION_OPEN, |
1597 | + buttons=(gtk.STOCK_CANCEL, |
1598 | + gtk.RESPONSE_CANCEL, |
1599 | + gtk.STOCK_OPEN, |
1600 | + gtk.RESPONSE_OK)) |
1601 | + chooser.set_current_folder(self.import_directory) |
1602 | + chooser.set_select_multiple(True) |
1603 | + |
1604 | + filter_all = gtk.FileFilter() |
1605 | + filter_all.set_name(_('All files')) |
1606 | + filter_all.add_pattern('*') |
1607 | + chooser.add_filter(filter_all) |
1608 | + |
1609 | + filter_pdf = gtk.FileFilter() |
1610 | + filter_pdf.set_name(_('PDF files')) |
1611 | + filter_pdf.add_mime_type('application/pdf') |
1612 | + chooser.add_filter(filter_pdf) |
1613 | + chooser.set_filter(filter_pdf) |
1614 | + |
1615 | + response = chooser.run() |
1616 | + if response == gtk.RESPONSE_OK: |
1617 | + for filename in chooser.get_filenames(): |
1618 | + if os.path.isfile(filename): |
1619 | + # FIXME |
1620 | + f = gio.File(filename) |
1621 | + f_info = f.query_info('standard::content-type') |
1622 | + mime_type = f_info.get_content_type() |
1623 | + expected_mime_type = 'application/pdf' |
1624 | + |
1625 | + if mime_type == expected_mime_type: |
1626 | + self.add_pdf_pages(filename) |
1627 | + elif mime_type[:34] == 'application/vnd.oasis.opendocument': |
1628 | + print(_('OpenDocument not supported yet!')) |
1629 | + elif mime_type[:5] == 'image': |
1630 | + print(_('Image file not supported yet!')) |
1631 | + else: |
1632 | + print(_('File type not supported!')) |
1633 | + else: |
1634 | + print(_('File %s does not exist') % filename) |
1635 | + elif response == gtk.RESPONSE_CANCEL: |
1636 | + print(_('Closed, no files selected')) |
1637 | + chooser.destroy() |
1638 | + gobject.idle_add(self.retitle) |
1639 | + |
1640 | + def clear_selected(self, button=None): |
1641 | + """Removes the selected elements in the IconView""" |
1642 | + |
1643 | + model = self.iconview.get_model() |
1644 | + selection = self.iconview.get_selected_items() |
1645 | + if selection: |
1646 | + selection.sort(reverse=True) |
1647 | + self.set_unsaved(True) |
1648 | + for path in selection: |
1649 | + iter = model.get_iter(path) |
1650 | + model.remove(iter) |
1651 | + path = selection[-1] |
1652 | + self.iconview.select_path(path) |
1653 | + if not self.iconview.path_is_selected(path): |
1654 | + if len(model) > 0: # select the last row |
1655 | + row = model[-1] |
1656 | + path = row.path |
1657 | + self.iconview.select_path(path) |
1658 | + self.iconview.grab_focus() |
1659 | + |
1660 | + def iv_drag_begin(self, iconview, context): |
1661 | + """Sets custom icon on drag begin for multiple items selected""" |
1662 | + |
1663 | + if len(iconview.get_selected_items()) > 1: |
1664 | + iconview.stop_emission('drag_begin') |
1665 | + context.set_icon_stock(gtk.STOCK_DND_MULTIPLE, 0, 0) |
1666 | + |
1667 | + def iv_dnd_get_data(self, iconview, context, |
1668 | + selection_data, target_id, etime): |
1669 | + """Handles requests for data by drag and drop in iconview""" |
1670 | + |
1671 | + model = iconview.get_model() |
1672 | + selection = self.iconview.get_selected_items() |
1673 | + selection.sort(key=lambda x: x[0]) |
1674 | + data = [] |
1675 | + for path in selection: |
1676 | + if selection_data.target == 'MODEL_ROW_INTERN': |
1677 | + data.append(str(path[0])) |
1678 | + elif selection_data.target == 'MODEL_ROW_EXTERN': |
1679 | + iter = model.get_iter(path) |
1680 | + nfile, npage, angle = model.get(iter, 2, 3, 6) |
1681 | + crop = model.get(iter, 7, 8, 9, 10) |
1682 | + pdfdoc = self.pdfqueue[nfile - 1] |
1683 | + data.append('\n'.join([pdfdoc.filename, |
1684 | + str(npage), |
1685 | + str(angle)] + |
1686 | + [str(side) for side in crop])) |
1687 | + if data: |
1688 | + data = '\n;\n'.join(data) |
1689 | + selection_data.set(selection_data.target, 8, data) |
1690 | + |
1691 | + def iv_dnd_received_data(self, iconview, context, x, y, |
1692 | + selection_data, target_id, etime): |
1693 | + """Handles received data by drag and drop in iconview""" |
1694 | + |
1695 | + model = iconview.get_model() |
1696 | + data = selection_data.data |
1697 | + if data: |
1698 | + data = data.split('\n;\n') |
1699 | + drop_info = iconview.get_dest_item_at_pos(x, y) |
1700 | + iter_to = None |
1701 | + if drop_info: |
1702 | + path, position = drop_info |
1703 | + ref_to = gtk.TreeRowReference(model,path) |
1704 | + else: |
1705 | + position = gtk.ICON_VIEW_DROP_RIGHT |
1706 | + if len(model) > 0: #find the iterator of the last row |
1707 | + row = model[-1] |
1708 | + path = row.path |
1709 | + ref_to = gtk.TreeRowReference(model,path) |
1710 | + if ref_to: |
1711 | + before = (position == gtk.ICON_VIEW_DROP_LEFT |
1712 | + or position == gtk.ICON_VIEW_DROP_ABOVE) |
1713 | + #if target_id == self.MODEL_ROW_INTERN: |
1714 | + if selection_data.target == 'MODEL_ROW_INTERN': |
1715 | + if before: |
1716 | + data.sort(key=int) |
1717 | + else: |
1718 | + data.sort(key=int,reverse=True) |
1719 | + ref_from_list = [gtk.TreeRowReference(model,path) |
1720 | + for path in data] |
1721 | + for ref_from in ref_from_list: |
1722 | + path = ref_to.get_path() |
1723 | + iter_to = model.get_iter(path) |
1724 | + path = ref_from.get_path() |
1725 | + iter_from = model.get_iter(path) |
1726 | + row = model[iter_from] |
1727 | + if before: |
1728 | + model.insert_before(iter_to, row) |
1729 | + else: |
1730 | + model.insert_after(iter_to, row) |
1731 | + if context.action == gtk.gdk.ACTION_MOVE: |
1732 | + for ref_from in ref_from_list: |
1733 | + path = ref_from.get_path() |
1734 | + iter_from = model.get_iter(path) |
1735 | + model.remove(iter_from) |
1736 | + |
1737 | + #elif target_id == self.MODEL_ROW_EXTERN: |
1738 | + elif selection_data.target == 'MODEL_ROW_EXTERN': |
1739 | + if not before: |
1740 | + data.reverse() |
1741 | + while data: |
1742 | + tmp = data.pop(0).split('\n') |
1743 | + filename = tmp[0] |
1744 | + npage, angle = [int(k) for k in tmp[1:3]] |
1745 | + crop = [float(side) for side in tmp[3:7]] |
1746 | + if self.add_pdf_pages(filename, npage, npage, |
1747 | + angle, crop): |
1748 | + if len(model) > 0: |
1749 | + path = ref_to.get_path() |
1750 | + iter_to = model.get_iter(path) |
1751 | + row = model[-1] #the last row |
1752 | + path = row.path |
1753 | + iter_from = model.get_iter(path) |
1754 | + if before: |
1755 | + model.move_before(iter_from, iter_to) |
1756 | + else: |
1757 | + model.move_after(iter_from, iter_to) |
1758 | + if context.action == gtk.gdk.ACTION_MOVE: |
1759 | + context.finish(True, True, etime) |
1760 | + |
1761 | + def iv_dnd_data_delete(self, widget, context): |
1762 | + """Deletes dnd items after a successful move operation""" |
1763 | + |
1764 | + model = self.iconview.get_model() |
1765 | + selection = self.iconview.get_selected_items() |
1766 | + ref_del_list = [gtk.TreeRowReference(model,path) for path in selection] |
1767 | + for ref_del in ref_del_list: |
1768 | + path = ref_del.get_path() |
1769 | + iter = model.get_iter(path) |
1770 | + model.remove(iter) |
1771 | + |
1772 | + def iv_dnd_motion(self, iconview, context, x, y, etime): |
1773 | + """Handles the drag-motion signal in order to auto-scroll the view""" |
1774 | + |
1775 | + autoscroll_area = 40 |
1776 | + sw_vadj = self.sw.get_vadjustment() |
1777 | + sw_height = self.sw.get_allocation().height |
1778 | + if y -sw_vadj.get_value() < autoscroll_area: |
1779 | + if not self.iv_auto_scroll_timer: |
1780 | + self.iv_auto_scroll_direction = gtk.DIR_UP |
1781 | + self.iv_auto_scroll_timer = gobject.timeout_add(150, |
1782 | + self.iv_auto_scroll) |
1783 | + elif y -sw_vadj.get_value() > sw_height - autoscroll_area: |
1784 | + if not self.iv_auto_scroll_timer: |
1785 | + self.iv_auto_scroll_direction = gtk.DIR_DOWN |
1786 | + self.iv_auto_scroll_timer = gobject.timeout_add(150, |
1787 | + self.iv_auto_scroll) |
1788 | + elif self.iv_auto_scroll_timer: |
1789 | + gobject.source_remove(self.iv_auto_scroll_timer) |
1790 | + self.iv_auto_scroll_timer = None |
1791 | + |
1792 | + def iv_dnd_leave_end(self, widget, context, ignored=None): |
1793 | + """Ends the auto-scroll during DND""" |
1794 | + |
1795 | + if self.iv_auto_scroll_timer: |
1796 | + gobject.source_remove(self.iv_auto_scroll_timer) |
1797 | + self.iv_auto_scroll_timer = None |
1798 | + |
1799 | + def iv_auto_scroll(self): |
1800 | + """Timeout routine for auto-scroll""" |
1801 | + |
1802 | + sw_vadj = self.sw.get_vadjustment() |
1803 | + sw_vpos = sw_vadj.get_value() |
1804 | + if self.iv_auto_scroll_direction == gtk.DIR_UP: |
1805 | + sw_vpos -= sw_vadj.step_increment |
1806 | + sw_vadj.set_value(max(sw_vpos, sw_vadj.lower)) |
1807 | + elif self.iv_auto_scroll_direction == gtk.DIR_DOWN: |
1808 | + sw_vpos += sw_vadj.step_increment |
1809 | + sw_vadj.set_value(min(sw_vpos, sw_vadj.upper - sw_vadj.page_size)) |
1810 | + return True #call me again |
1811 | + |
1812 | + def iv_button_press_event(self, iconview, event): |
1813 | + """Manages mouse clicks on the iconview""" |
1814 | + |
1815 | + if event.button == 3: |
1816 | + x = int(event.x) |
1817 | + y = int(event.y) |
1818 | + time = event.time |
1819 | + path = iconview.get_path_at_pos(x, y) |
1820 | + selection = iconview.get_selected_items() |
1821 | + if path: |
1822 | + if path not in selection: |
1823 | + iconview.unselect_all() |
1824 | + iconview.select_path(path) |
1825 | + iconview.grab_focus() |
1826 | + self.popup.popup(None, None, None, event.button, time) |
1827 | + return 1 |
1828 | + |
1829 | + def sw_dnd_received_data(self, scrolledwindow, context, x, y, |
1830 | + selection_data, target_id, etime): |
1831 | + """Handles received data by drag and drop in scrolledwindow""" |
1832 | + |
1833 | + data = selection_data.data |
1834 | + if target_id == self.MODEL_ROW_EXTERN: |
1835 | + self.model |
1836 | + if data: |
1837 | + data = data.split('\n;\n') |
1838 | + while data: |
1839 | + tmp = data.pop(0).split('\n') |
1840 | + filename = tmp[0] |
1841 | + npage, angle = [int(k) for k in tmp[1:3]] |
1842 | + crop = [float(side) for side in tmp[3:7]] |
1843 | + if self.add_pdf_pages(filename, npage, npage, angle, crop): |
1844 | + if context.action == gtk.gdk.ACTION_MOVE: |
1845 | + context.finish(True, True, etime) |
1846 | + elif target_id == self.TEXT_URI_LIST: |
1847 | + uri = data.strip() |
1848 | + uri_splitted = uri.split() # we may have more than one file dropped |
1849 | + for uri in uri_splitted: |
1850 | + filename = self.get_file_path_from_dnd_dropped_uri(uri) |
1851 | + if os.path.isfile(filename): # is it file? |
1852 | + self.add_pdf_pages(filename) |
1853 | + |
1854 | + def sw_button_press_event(self, scrolledwindow, event): |
1855 | + """Unselects all items in iconview on mouse click in scrolledwindow""" |
1856 | + |
1857 | + if event.button == 1: |
1858 | + self.iconview.unselect_all() |
1859 | + |
1860 | + def sw_scroll_event(self, scrolledwindow, event): |
1861 | + """Manages mouse scroll events in scrolledwindow""" |
1862 | + |
1863 | + if event.state & gtk.gdk.CONTROL_MASK: |
1864 | + if event.direction == gtk.gdk.SCROLL_UP: |
1865 | + self.zoom_change(1) |
1866 | + return 1 |
1867 | + elif event.direction == gtk.gdk.SCROLL_DOWN: |
1868 | + self.zoom_change(-1) |
1869 | + return 1 |
1870 | + |
1871 | + def zoom_set(self, level): |
1872 | + """Sets the zoom level""" |
1873 | + self.zoom_level = max(min(level, 5), -24) |
1874 | + self.zoom_scale = 1.1 ** self.zoom_level |
1875 | + for row in self.model: |
1876 | + row[4] = self.zoom_scale |
1877 | + self.reset_iv_width() |
1878 | + |
1879 | + def zoom_change(self, step=5): |
1880 | + """Modifies the zoom level""" |
1881 | + self.zoom_set(self.zoom_level + step) |
1882 | + |
1883 | + def zoom_in(self, widget=None): |
1884 | + """Increases the zoom level by 5 steps""" |
1885 | + self.zoom_change(5) |
1886 | + |
1887 | + def zoom_out(self, widget=None, step=5): |
1888 | + """Reduces the zoom level by 5 steps""" |
1889 | + self.zoom_change(-5) |
1890 | + |
1891 | + def get_file_path_from_dnd_dropped_uri(self, uri): |
1892 | + """Extracts the path from an uri""" |
1893 | + |
1894 | + path = urllib.url2pathname(uri) # escape special chars |
1895 | + path = path.strip('\r\n\x00') # remove \r\n and NULL |
1896 | + |
1897 | + # get the path to file |
1898 | + if path.startswith('file:\\\\\\'): # windows |
1899 | + path = path[8:] # 8 is len('file:///') |
1900 | + elif path.startswith('file://'): # nautilus, rox |
1901 | + path = path[7:] # 7 is len('file://') |
1902 | + elif path.startswith('file:'): # xffm |
1903 | + path = path[5:] # 5 is len('file:') |
1904 | + return path |
1905 | + |
1906 | + def rotate_page_right(self, widget, data=None): |
1907 | + self.rotate_page(90) |
1908 | + |
1909 | + def rotate_page_left(self, widget, data=None): |
1910 | + self.rotate_page(-90) |
1911 | + |
1912 | + def rotate_page(self, angle): |
1913 | + """Rotates the selected page in the IconView""" |
1914 | + |
1915 | + model = self.iconview.get_model() |
1916 | + selection = self.iconview.get_selected_items() |
1917 | + if len(selection) > 0: |
1918 | + self.set_unsaved(True) |
1919 | + rotate_times = (((-angle) % 360 + 45) / 90) % 4 |
1920 | + if rotate_times is not 0: |
1921 | + for path in selection: |
1922 | + iter = model.get_iter(path) |
1923 | + nfile = model.get_value(iter, 2) |
1924 | + npage = model.get_value(iter, 3) |
1925 | + |
1926 | + crop = [0.,0.,0.,0.] |
1927 | + perm = [0,2,1,3] |
1928 | + for it in range(rotate_times): |
1929 | + perm.append(perm.pop(0)) |
1930 | + perm.insert(1,perm.pop(2)) |
1931 | + crop = [model.get_value(iter, 7 + perm[side]) for side in range(4)] |
1932 | + for side in range(4): |
1933 | + model.set_value(iter, 7 + side, crop[side]) |
1934 | + |
1935 | + new_angle = model.get_value(iter, 6) + int(angle) |
1936 | + new_angle = new_angle % 360 |
1937 | + model.set_value(iter, 6, new_angle) |
1938 | + self.update_geometry(iter) |
1939 | + self.reset_iv_width() |
1940 | + |
1941 | + def crop_page_dialog(self, widget): |
1942 | + """Opens a dialog box to define margins for page cropping""" |
1943 | + |
1944 | + sides = ('L', 'R', 'T', 'B') |
1945 | + side_names = {'L':_('Left'), 'R':_('Right'), |
1946 | + 'T':_('Top'), 'B':_('Bottom') } |
1947 | + opposite_sides = {'L':'R', 'R':'L', 'T':'B', 'B':'T' } |
1948 | + |
1949 | + def set_crop_value(spinbutton, side): |
1950 | + opp_side = opposite_sides[side] |
1951 | + pos = sides.index(opp_side) |
1952 | + adj = spin_list[pos].get_adjustment() |
1953 | + adj.set_upper(99.0 - spinbutton.get_value()) |
1954 | + |
1955 | + model = self.iconview.get_model() |
1956 | + selection = self.iconview.get_selected_items() |
1957 | + |
1958 | + crop = [0.,0.,0.,0.] |
1959 | + if selection: |
1960 | + path = selection[0] |
1961 | + pos = model.get_iter(path) |
1962 | + crop = [model.get_value(pos, 7 + side) for side in range(4)] |
1963 | + |
1964 | + dialog = gtk.Dialog(title=(_('Crop Selected Pages')), |
1965 | + parent=self.window, |
1966 | + flags=gtk.DIALOG_MODAL, |
1967 | + buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, |
1968 | + gtk.STOCK_OK, gtk.RESPONSE_OK)) |
1969 | + dialog.set_size_request(340, 250) |
1970 | + dialog.set_default_response(gtk.RESPONSE_OK) |
1971 | + |
1972 | + frame = gtk.Frame(_('Crop Margins')) |
1973 | + dialog.vbox.pack_start(frame, False, False, 20) |
1974 | + |
1975 | + vbox = gtk.VBox(False, 0) |
1976 | + frame.add(vbox) |
1977 | + |
1978 | + spin_list = [] |
1979 | + units = 2 * [_('% of width')] + 2 * [_('% of height')] |
1980 | + for side in sides: |
1981 | + hbox = gtk.HBox(True, 0) |
1982 | + vbox.pack_start(hbox, False, False, 5) |
1983 | + |
1984 | + label = gtk.Label(side_names[side]) |
1985 | + label.set_alignment(0, 0.0) |
1986 | + hbox.pack_start(label, True, True, 20) |
1987 | + |
1988 | + adj = gtk.Adjustment(100.*crop.pop(0), 0.0, 99.0, 1.0, 5.0, 0.0) |
1989 | + spin = gtk.SpinButton(adj, 0, 1) |
1990 | + spin.set_activates_default(True) |
1991 | + spin.connect('value-changed', set_crop_value, side) |
1992 | + spin_list.append(spin) |
1993 | + hbox.pack_start(spin, False, False, 30) |
1994 | + |
1995 | + label = gtk.Label(units.pop(0)) |
1996 | + label.set_alignment(0, 0.0) |
1997 | + hbox.pack_start(label, True, True, 0) |
1998 | + |
1999 | + dialog.show_all() |
2000 | + result = dialog.run() |
2001 | + |
2002 | + if result == gtk.RESPONSE_OK: |
2003 | + modified = False |
2004 | + crop = [spin.get_value()/100. for spin in spin_list] |
2005 | + for path in selection: |
2006 | + pos = model.get_iter(path) |
2007 | + for it in range(4): |
2008 | + old_val = model.get_value(pos, 7 + it) |
2009 | + model.set_value(pos, 7 + it, crop[it]) |
2010 | + if crop[it] != old_val: |
2011 | + modified = True |
2012 | + self.update_geometry(pos) |
2013 | + if modified: |
2014 | + self.set_unsaved(True) |
2015 | + self.reset_iv_width() |
2016 | + elif result == gtk.RESPONSE_CANCEL: |
2017 | + print(_('Dialog closed')) |
2018 | + dialog.destroy() |
2019 | + |
2020 | + def about_dialog(self, widget, data=None): |
2021 | + about_dialog = gtk.AboutDialog() |
2022 | + try: |
2023 | + about_dialog.set_transient_for(self.window) |
2024 | + about_dialog.set_modal(True) |
2025 | + except: |
2026 | + pass |
2027 | + # FIXME |
2028 | + about_dialog.set_name(APPNAME) |
2029 | + about_dialog.set_version(VERSION) |
2030 | + about_dialog.set_comments(_( |
2031 | + '%s is a tool for rearranging and modifying PDF files.' \ |
2032 | + 'Developed using GTK+ and Python') % APPNAME) |
2033 | + about_dialog.set_authors(['Konstantinos Poulios',]) |
2034 | + about_dialog.set_website_label(WEBSITE) |
2035 | + about_dialog.set_logo_icon_name('pdfshuffler') |
2036 | + about_dialog.set_license(LICENSE) |
2037 | + about_dialog.connect('response', lambda w, a: about_dialog.destroy()) |
2038 | + about_dialog.connect('delete_event', lambda w, a: about_dialog.destroy()) |
2039 | + about_dialog.show_all() |
2040 | + |
2041 | + |
2042 | +class PDF_Doc: |
2043 | + """Class handling PDF documents""" |
2044 | + |
2045 | + def __init__(self, filename, nfile, tmp_dir): |
2046 | + |
2047 | + self.filename = os.path.abspath(filename) |
2048 | + (self.path, self.shortname) = os.path.split(self.filename) |
2049 | + (self.shortname, self.ext) = os.path.splitext(self.shortname) |
2050 | + f = gio.File(filename) |
2051 | + mime_type = f.query_info('standard::content-type').get_content_type() |
2052 | + expected_mime_type = 'application/pdf' |
2053 | + file_prefix = 'file://' |
2054 | + |
2055 | + if mime_type == expected_mime_type: |
2056 | + self.nfile = nfile + 1 |
2057 | + self.mtime = os.path.getmtime(filename) |
2058 | + self.copyname = os.path.join(tmp_dir, '%02d_' % self.nfile + |
2059 | + self.shortname + '.pdf') |
2060 | + shutil.copy(self.filename, self.copyname) |
2061 | + self.document = poppler.document_new_from_file (file_prefix + self.copyname, None) |
2062 | + self.npage = self.document.get_n_pages() |
2063 | + else: |
2064 | + self.nfile = 0 |
2065 | + self.npage = 0 |
2066 | + |
2067 | + |
2068 | +class PDF_Renderer(threading.Thread,gobject.GObject): |
2069 | + |
2070 | + def __init__(self, model, pdfqueue): |
2071 | + threading.Thread.__init__(self) |
2072 | + gobject.GObject.__init__(self) |
2073 | + self.model = model |
2074 | + self.pdfqueue = pdfqueue |
2075 | + self.quit = False |
2076 | + |
2077 | + def run(self): |
2078 | + for idx, row in enumerate(self.model): |
2079 | + if self.quit: |
2080 | + return |
2081 | + if not row[1].surface: |
2082 | + try: |
2083 | + nfile = row[2] |
2084 | + npage = row[3] |
2085 | + pdfdoc = self.pdfqueue[nfile - 1] |
2086 | + page = pdfdoc.document.get_page(npage-1) |
2087 | + w, h = page.get_size() |
2088 | + thumbnail = CairoImage(int(w), int(h)) |
2089 | + cr = cairo.Context(thumbnail.surface) |
2090 | + page.render(cr) |
2091 | + time.sleep(0.003) |
2092 | + gobject.idle_add(self.emit,'update_thumbnail', idx, thumbnail, |
2093 | + priority=gobject.PRIORITY_LOW) |
2094 | + except Exception,e: |
2095 | + print e |
2096 | + |
2097 | + |
2098 | +def main(): |
2099 | + """This function starts PdfShuffler""" |
2100 | + gtk.gdk.threads_init() |
2101 | + gobject.threads_init() |
2102 | + PdfShuffler() |
2103 | + gtk.main() |
2104 | + |
2105 | +if __name__ == '__main__': |
2106 | + main() |
2107 | + |
2108 | |
2109 | === added file '.pc/install_and_look_for_UI_file_in_right_dirs.patch/setup.py' |
2110 | --- .pc/install_and_look_for_UI_file_in_right_dirs.patch/setup.py 1970-01-01 00:00:00 +0000 |
2111 | +++ .pc/install_and_look_for_UI_file_in_right_dirs.patch/setup.py 2012-02-12 00:35:23 +0000 |
2112 | @@ -0,0 +1,66 @@ |
2113 | +#!/usr/bin/python |
2114 | + |
2115 | +# |
2116 | +# PdfShuffler 0.6.0 - GTK+ based utility for splitting, rearrangement and |
2117 | +# modification of PDF documents. |
2118 | +# Copyright (C) 2008-2011 Konstantinos Poulios |
2119 | +# <https://sourceforge.net/projects/pdfshuffler> |
2120 | +# |
2121 | +# This program is free software; you can redistribute it and/or modify |
2122 | +# it under the terms of the GNU General Public License as published by |
2123 | +# the Free Software Foundation; either version 3 of the License, or |
2124 | +# (at your option) any later version. |
2125 | +# |
2126 | +# This program is distributed in the hope that it will be useful, |
2127 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2128 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2129 | +# GNU General Public License for more details. |
2130 | +# |
2131 | +# You should have received a copy of the GNU General Public License along |
2132 | +# with this program; if not, write to the Free Software Foundation, Inc., |
2133 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
2134 | +# |
2135 | + |
2136 | +import os |
2137 | +import re |
2138 | +from distutils.core import setup |
2139 | + |
2140 | +data_files=[('share/applications/pdfshuffler', ['data/pdfshuffler.ui']), |
2141 | + ('share/applications', ['data/pdfshuffler.desktop']), |
2142 | + ('share/man/man1', ['doc/pdfshuffler.1']), |
2143 | + ('share/pixmaps', ['data/pdfshuffler.svg']), |
2144 | + ('share/pixmaps', ['data/pdfshuffler.png']) ] |
2145 | + |
2146 | + |
2147 | +# Freshly generate .mo from .po, add to data_files: |
2148 | +if os.path.isdir('mo/'): |
2149 | + os.system ('rm -r mo/') |
2150 | +for name in os.listdir('po'): |
2151 | + m = re.match(r'(.+)\.po$', name) |
2152 | + if m != None: |
2153 | + lang = m.group(1) |
2154 | + out_dir = 'mo/%s/LC_MESSAGES' % lang |
2155 | + out_name = os.path.join(out_dir, 'pdfshuffler.mo') |
2156 | + install_dir = 'share/locale/%s/LC_MESSAGES/' % lang |
2157 | + os.makedirs(out_dir) |
2158 | + os.system('msgfmt -o %s po/%s' % (out_name, name)) |
2159 | + data_files.append((install_dir, [out_name])) |
2160 | + |
2161 | +setup(name='pdfshuffler', |
2162 | + version='0.6.0', |
2163 | + author='Konstantinos Poulios', |
2164 | + author_email='logari81 at gmail dot com', |
2165 | + description='A simple application for PDF Merging, Rearranging, and Splitting', |
2166 | + url = 'https://sourceforge.net/projects/pdfshuffler', |
2167 | + license='GNU GPL-3', |
2168 | + scripts=['bin/pdfshuffler'], |
2169 | + packages=['pdfshuffler'], |
2170 | + data_files=data_files |
2171 | + ) |
2172 | + |
2173 | +# Clean up temporary files |
2174 | +if os.path.isdir('mo/'): |
2175 | + os.system ('rm -r mo/') |
2176 | +if os.path.isdir('build/'): |
2177 | + os.system ('rm -r build/') |
2178 | + |
2179 | |
2180 | === modified file 'TODO' |
2181 | --- TODO 2011-02-15 00:43:28 +0000 |
2182 | +++ TODO 2012-02-12 00:35:23 +0000 |
2183 | @@ -11,5 +11,3 @@ |
2184 | |
2185 | * Add support for encrypted files. |
2186 | |
2187 | -* Show a progress bar while loading a document. |
2188 | - |
2189 | |
2190 | === added directory 'bin' |
2191 | === added file 'bin/pdfshuffler' |
2192 | --- bin/pdfshuffler 1970-01-01 00:00:00 +0000 |
2193 | +++ bin/pdfshuffler 2012-02-12 00:35:23 +0000 |
2194 | @@ -0,0 +1,33 @@ |
2195 | +#!/usr/bin/python |
2196 | +# -*- coding: utf-8 -*- |
2197 | + |
2198 | +""" |
2199 | + |
2200 | + PdfShuffler 0.6.0 - GTK+ based utility for splitting, rearrangement and |
2201 | + modification of PDF documents. |
2202 | + Copyright (C) 2008-2011 Konstantinos Poulios |
2203 | + <https://sourceforge.net/projects/pdfshuffler> |
2204 | + |
2205 | + This file is part of PdfShuffler. |
2206 | + |
2207 | + PdfShuffler is free software; you can redistribute it and/or modify |
2208 | + it under the terms of the GNU General Public License as published by |
2209 | + the Free Software Foundation; either version 3 of the License, or |
2210 | + (at your option) any later version. |
2211 | + |
2212 | + This program is distributed in the hope that it will be useful, |
2213 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
2214 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2215 | + GNU General Public License for more details. |
2216 | + |
2217 | + You should have received a copy of the GNU General Public License along |
2218 | + with this program; if not, write to the Free Software Foundation, Inc., |
2219 | + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
2220 | + |
2221 | +""" |
2222 | + |
2223 | +try: |
2224 | + from pdfshuffler.pdfshuffler import main |
2225 | + main() |
2226 | +except ImportError: |
2227 | + print('Error: could not import pdfshuffler') |
2228 | |
2229 | === added file 'data/pdfshuffler.ui' |
2230 | --- data/pdfshuffler.ui 1970-01-01 00:00:00 +0000 |
2231 | +++ data/pdfshuffler.ui 2012-02-12 00:35:23 +0000 |
2232 | @@ -0,0 +1,359 @@ |
2233 | +<?xml version="1.0"?> |
2234 | +<interface> |
2235 | + <requires lib="gtk+" version="2.16"/> |
2236 | + <!-- interface-naming-policy project-wide --> |
2237 | + <object class="GtkWindow" id="main_window"> |
2238 | + <child> |
2239 | + <object class="GtkVBox" id="main_vbox"> |
2240 | + <property name="visible">True</property> |
2241 | + <child> |
2242 | + <object class="GtkMenuBar" id="menubar"> |
2243 | + <property name="visible">True</property> |
2244 | + <child> |
2245 | + <object class="GtkMenuItem" id="menuitem_file"> |
2246 | + <property name="visible">True</property> |
2247 | + <property name="label" translatable="yes">_File</property> |
2248 | + <property name="use_underline">True</property> |
2249 | + <child type="submenu"> |
2250 | + <object class="GtkMenu" id="menu_file"> |
2251 | + <property name="visible">True</property> |
2252 | + <child> |
2253 | + <object class="GtkImageMenuItem" id="imagemenuitem_import"> |
2254 | + <property name="label">gtk-open</property> |
2255 | + <property name="visible">True</property> |
2256 | + <property name="use_underline">True</property> |
2257 | + <property name="use_stock">True</property> |
2258 | + <signal name="activate" handler="on_action_add_doc_activate"/> |
2259 | + </object> |
2260 | + </child> |
2261 | + <child> |
2262 | + <object class="GtkImageMenuItem" id="imagemenuitem_save"> |
2263 | + <property name="label">gtk-save</property> |
2264 | + <property name="visible">True</property> |
2265 | + <property name="sensitive">False</property> |
2266 | + <property name="use_underline">True</property> |
2267 | + <property name="use_stock">True</property> |
2268 | + </object> |
2269 | + </child> |
2270 | + <child> |
2271 | + <object class="GtkImageMenuItem" id="imagemenuitem_save_as"> |
2272 | + <property name="label">gtk-save-as</property> |
2273 | + <property name="visible">True</property> |
2274 | + <property name="use_underline">True</property> |
2275 | + <property name="use_stock">True</property> |
2276 | + <signal name="activate" handler="choose_export_pdf_name"/> |
2277 | + </object> |
2278 | + </child> |
2279 | + <child> |
2280 | + <object class="GtkSeparatorMenuItem" id="separatormenuitem_file"> |
2281 | + <property name="visible">True</property> |
2282 | + </object> |
2283 | + </child> |
2284 | + <child> |
2285 | + <object class="GtkImageMenuItem" id="imagemenuitem_quit"> |
2286 | + <property name="label">gtk-quit</property> |
2287 | + <property name="visible">True</property> |
2288 | + <property name="use_underline">True</property> |
2289 | + <property name="use_stock">True</property> |
2290 | + <signal name="activate" handler="close_application"/> |
2291 | + </object> |
2292 | + </child> |
2293 | + </object> |
2294 | + </child> |
2295 | + </object> |
2296 | + </child> |
2297 | + <child> |
2298 | + <object class="GtkMenuItem" id="menuitem_edit"> |
2299 | + <property name="visible">True</property> |
2300 | + <property name="label" translatable="yes">_Edit</property> |
2301 | + <property name="use_underline">True</property> |
2302 | + <child type="submenu"> |
2303 | + <object class="GtkMenu" id="menu_edit"> |
2304 | + <property name="visible">True</property> |
2305 | + <child> |
2306 | + <object class="GtkImageMenuItem" id="imagemenuitem_undo"> |
2307 | + <property name="label">gtk-undo</property> |
2308 | + <property name="sensitive">False</property> |
2309 | + <property name="use_underline">True</property> |
2310 | + <property name="use_stock">True</property> |
2311 | + </object> |
2312 | + </child> |
2313 | + <child> |
2314 | + <object class="GtkImageMenuItem" id="imagemenuitem_redo"> |
2315 | + <property name="label">gtk-redo</property> |
2316 | + <property name="sensitive">False</property> |
2317 | + <property name="use_underline">True</property> |
2318 | + <property name="use_stock">True</property> |
2319 | + </object> |
2320 | + </child> |
2321 | + <child> |
2322 | + <object class="GtkSeparatorMenuItem" id="separatormenuitem_edit1"> |
2323 | + <property name="sensitive">False</property> |
2324 | + </object> |
2325 | + </child> |
2326 | + <child> |
2327 | + <object class="GtkImageMenuItem" id="imagemenuitem_rotate_left"> |
2328 | + <property name="label" translatable="yes">Rotate left</property> |
2329 | + <property name="visible">True</property> |
2330 | + <property name="use_stock">False</property> |
2331 | + <signal name="activate" handler="rotate_page_left"/> |
2332 | + </object> |
2333 | + </child> |
2334 | + <child> |
2335 | + <object class="GtkImageMenuItem" id="imagemenuitem_rotate_right"> |
2336 | + <property name="label" translatable="yes">Rotate right</property> |
2337 | + <property name="visible">True</property> |
2338 | + <property name="use_stock">False</property> |
2339 | + <signal name="activate" handler="rotate_page_right"/> |
2340 | + </object> |
2341 | + </child> |
2342 | + <child> |
2343 | + <object class="GtkSeparatorMenuItem" id="separatormenuitem_edit2"> |
2344 | + <property name="visible">True</property> |
2345 | + </object> |
2346 | + </child> |
2347 | + <child> |
2348 | + <object class="GtkImageMenuItem" id="imagemenuitem_crop"> |
2349 | + <property name="label" translatable="yes">Crop</property> |
2350 | + <property name="visible">True</property> |
2351 | + <property name="use_stock">False</property> |
2352 | + <signal name="activate" handler="crop_page_dialog"/> |
2353 | + </object> |
2354 | + </child> |
2355 | + <child> |
2356 | + <object class="GtkImageMenuItem" id="imagemenuitem_delete"> |
2357 | + <property name="label">gtk-delete</property> |
2358 | + <property name="visible">True</property> |
2359 | + <property name="use_underline">True</property> |
2360 | + <property name="use_stock">True</property> |
2361 | + <signal name="activate" handler="clear_selected"/> |
2362 | + </object> |
2363 | + </child> |
2364 | + </object> |
2365 | + </child> |
2366 | + </object> |
2367 | + </child> |
2368 | + <child> |
2369 | + <object class="GtkMenuItem" id="menuitem_view"> |
2370 | + <property name="visible">True</property> |
2371 | + <property name="label" translatable="yes">_View</property> |
2372 | + <property name="use_underline">True</property> |
2373 | + <child type="submenu"> |
2374 | + <object class="GtkMenu" id="menu_view"> |
2375 | + <property name="visible">True</property> |
2376 | + <child> |
2377 | + <object class="GtkImageMenuItem" id="imagemenuitem_zoomin"> |
2378 | + <property name="label">Zoom in</property> |
2379 | + <property name="visible">True</property> |
2380 | + <property name="use_underline">True</property> |
2381 | + <property name="use_stock">True</property> |
2382 | + <signal name="activate" handler="zoom_in"/> |
2383 | + </object> |
2384 | + </child> |
2385 | + <child> |
2386 | + <object class="GtkImageMenuItem" id="imagemenuitem_zoomout"> |
2387 | + <property name="label">Zoom out</property> |
2388 | + <property name="visible">True</property> |
2389 | + <property name="use_underline">True</property> |
2390 | + <property name="use_stock">True</property> |
2391 | + <signal name="activate" handler="zoom_out"/> |
2392 | + </object> |
2393 | + </child> |
2394 | + </object> |
2395 | + </child> |
2396 | + </object> |
2397 | + </child> |
2398 | + <child> |
2399 | + <object class="GtkMenuItem" id="menuitem_help"> |
2400 | + <property name="visible">True</property> |
2401 | + <property name="label" translatable="yes">_Help</property> |
2402 | + <property name="use_underline">True</property> |
2403 | + <child type="submenu"> |
2404 | + <object class="GtkMenu" id="menu_help"> |
2405 | + <property name="visible">True</property> |
2406 | + <child> |
2407 | + <object class="GtkImageMenuItem" id="imagemenuitem_about"> |
2408 | + <property name="label">gtk-about</property> |
2409 | + <property name="visible">True</property> |
2410 | + <property name="use_underline">True</property> |
2411 | + <property name="use_stock">True</property> |
2412 | + <signal name="activate" handler="about_dialog"/> |
2413 | + </object> |
2414 | + </child> |
2415 | + </object> |
2416 | + </child> |
2417 | + </object> |
2418 | + </child> |
2419 | + </object> |
2420 | + <packing> |
2421 | + <property name="expand">False</property> |
2422 | + <property name="position">0</property> |
2423 | + </packing> |
2424 | + </child> |
2425 | + <child> |
2426 | + <object class="GtkHBox" id="hbox_toolbar"> |
2427 | + <property name="visible">True</property> |
2428 | + <child> |
2429 | + <object class="GtkToolbar" id="toolbar"> |
2430 | + <property name="visible">True</property> |
2431 | + <child> |
2432 | + <object class="GtkToolButton" id="toolbutton_open"> |
2433 | + <property name="visible">True</property> |
2434 | + <property name="label" translatable="yes">Open</property> |
2435 | + <property name="use_underline">True</property> |
2436 | + <property name="stock_id">gtk-open</property> |
2437 | + <signal name="clicked" handler="on_action_add_doc_activate"/> |
2438 | + </object> |
2439 | + <packing> |
2440 | + <property name="expand">False</property> |
2441 | + <property name="homogeneous">True</property> |
2442 | + </packing> |
2443 | + </child> |
2444 | + <child> |
2445 | + <object class="GtkToolButton" id="toolbutton_save"> |
2446 | + <property name="visible">True</property> |
2447 | + <property name="label" translatable="yes">Save</property> |
2448 | + <property name="use_underline">True</property> |
2449 | + <property name="stock_id">gtk-save</property> |
2450 | + </object> |
2451 | + <packing> |
2452 | + <property name="expand">False</property> |
2453 | + <property name="homogeneous">True</property> |
2454 | + </packing> |
2455 | + </child> |
2456 | + <child> |
2457 | + <object class="GtkToolButton" id="toolbutton_save_as"> |
2458 | + <property name="visible">True</property> |
2459 | + <property name="label" translatable="yes">Save as</property> |
2460 | + <property name="use_underline">True</property> |
2461 | + <property name="stock_id">gtk-save-as</property> |
2462 | + <signal name="clicked" handler="choose_export_pdf_name"/> |
2463 | + </object> |
2464 | + <packing> |
2465 | + <property name="expand">False</property> |
2466 | + <property name="homogeneous">True</property> |
2467 | + </packing> |
2468 | + </child> |
2469 | + <child> |
2470 | + <object class="GtkSeparatorToolItem" id="toolbutton_separator1"> |
2471 | + <property name="visible">True</property> |
2472 | + </object> |
2473 | + <packing> |
2474 | + <property name="expand">False</property> |
2475 | + <property name="homogeneous">True</property> |
2476 | + </packing> |
2477 | + </child> |
2478 | + <child> |
2479 | + <object class="GtkToolButton" id="toolbutton_zoom_in"> |
2480 | + <property name="visible">True</property> |
2481 | + <property name="label" translatable="yes">Zoom in</property> |
2482 | + <property name="use_underline">True</property> |
2483 | + <property name="stock_id">gtk-zoom-in</property> |
2484 | + <signal name="clicked" handler="zoom_in"/> |
2485 | + </object> |
2486 | + <packing> |
2487 | + <property name="expand">False</property> |
2488 | + <property name="homogeneous">True</property> |
2489 | + </packing> |
2490 | + </child> |
2491 | + <child> |
2492 | + <object class="GtkToolButton" id="toolbutton_zoom_out"> |
2493 | + <property name="visible">True</property> |
2494 | + <property name="label" translatable="yes">Zoom out</property> |
2495 | + <property name="use_underline">True</property> |
2496 | + <property name="stock_id">gtk-zoom-out</property> |
2497 | + <signal name="clicked" handler="zoom_out"/> |
2498 | + </object> |
2499 | + <packing> |
2500 | + <property name="expand">False</property> |
2501 | + <property name="homogeneous">True</property> |
2502 | + </packing> |
2503 | + </child> |
2504 | + <child> |
2505 | + <object class="GtkSeparatorToolItem" id="toolbutton_separator2"> |
2506 | + <property name="visible">True</property> |
2507 | + </object> |
2508 | + <packing> |
2509 | + <property name="expand">False</property> |
2510 | + <property name="homogeneous">True</property> |
2511 | + </packing> |
2512 | + </child> |
2513 | + <child> |
2514 | + <object class="GtkToolButton" id="toolbutton_rotate_left"> |
2515 | + <property name="visible">True</property> |
2516 | + <property name="label" translatable="yes">Rotate left</property> |
2517 | + <property name="use_underline">True</property> |
2518 | + <property name="icon_name">object-rotate-left</property> |
2519 | + <signal name="clicked" handler="rotate_page_left"/> |
2520 | + </object> |
2521 | + <packing> |
2522 | + <property name="expand">False</property> |
2523 | + <property name="homogeneous">True</property> |
2524 | + </packing> |
2525 | + </child> |
2526 | + <child> |
2527 | + <object class="GtkToolButton" id="toolbutton_rotate_right"> |
2528 | + <property name="visible">True</property> |
2529 | + <property name="label" translatable="yes">Rotate right</property> |
2530 | + <property name="use_underline">True</property> |
2531 | + <property name="icon_name">object-rotate-right</property> |
2532 | + <signal name="clicked" handler="rotate_page_right"/> |
2533 | + </object> |
2534 | + <packing> |
2535 | + <property name="expand">False</property> |
2536 | + <property name="homogeneous">True</property> |
2537 | + </packing> |
2538 | + </child> |
2539 | + <child> |
2540 | + <object class="GtkToolButton" id="toolbutton_delete"> |
2541 | + <property name="visible">True</property> |
2542 | + <property name="label" translatable="yes">Delete</property> |
2543 | + <property name="use_underline">True</property> |
2544 | + <property name="stock_id">gtk-delete</property> |
2545 | + <signal name="clicked" handler="clear_selected"/> |
2546 | + </object> |
2547 | + <packing> |
2548 | + <property name="expand">False</property> |
2549 | + <property name="homogeneous">True</property> |
2550 | + </packing> |
2551 | + </child> |
2552 | + </object> |
2553 | + <packing> |
2554 | + <property name="position">0</property> |
2555 | + </packing> |
2556 | + </child> |
2557 | + </object> |
2558 | + <packing> |
2559 | + <property name="expand">False</property> |
2560 | + <property name="position">1</property> |
2561 | + </packing> |
2562 | + </child> |
2563 | + <child> |
2564 | + <object class="GtkScrolledWindow" id="scrolledwindow"> |
2565 | + <property name="visible">True</property> |
2566 | + <property name="can_focus">True</property> |
2567 | + <property name="hscrollbar_policy">never</property> |
2568 | + <property name="vscrollbar_policy">automatic</property> |
2569 | + <child> |
2570 | + <placeholder/> |
2571 | + </child> |
2572 | + </object> |
2573 | + <packing> |
2574 | + <property name="position">2</property> |
2575 | + </packing> |
2576 | + </child> |
2577 | + <child> |
2578 | + <object class="GtkProgressBar" id="progressbar"> |
2579 | + <property name="height_request">20</property> |
2580 | + <property name="visible">True</property> |
2581 | + </object> |
2582 | + <packing> |
2583 | + <property name="expand">False</property> |
2584 | + <property name="padding">5</property> |
2585 | + <property name="position">3</property> |
2586 | + </packing> |
2587 | + </child> |
2588 | + </object> |
2589 | + </child> |
2590 | + </object> |
2591 | +</interface> |
2592 | |
2593 | === modified file 'debian/changelog' |
2594 | --- debian/changelog 2011-12-31 02:06:27 +0000 |
2595 | +++ debian/changelog 2012-02-12 00:35:23 +0000 |
2596 | @@ -1,3 +1,14 @@ |
2597 | +pdfshuffler (0.5.99~svn64-0ubuntu1) UNRELEASED; urgency=low |
2598 | + |
2599 | + * New upstream snapshot. (LP: #930819) |
2600 | + * This version fixes a annoying bug due to the new version of poppler |
2601 | + * debian/patches: |
2602 | + - Removed the previous patch (fix-shebang: now in upstream) |
2603 | + - Added a patch to install pdfshuffler in /usr/share/data as required |
2604 | + by pdfshuffler.py |
2605 | + |
2606 | + -- Matthieu Baerts (matttbe) <matttbe@gmail.com> Sun, 12 Feb 2012 01:27:07 +0100 |
2607 | + |
2608 | pdfshuffler (0.5.1-2build1) precise; urgency=low |
2609 | |
2610 | * Rebuild to drop python2.6 dependencies. |
2611 | |
2612 | === removed file 'debian/patches/fix-shebang' |
2613 | --- debian/patches/fix-shebang 2010-01-24 17:04:08 +0000 |
2614 | +++ debian/patches/fix-shebang 1970-01-01 00:00:00 +0000 |
2615 | @@ -1,12 +0,0 @@ |
2616 | -# Description: fix shebang |
2617 | -# Forward: not applicable |
2618 | -# Author: Serafeim Zanikolas <serzan@hellug.gr> |
2619 | -# Last-Update: 2010-01-24 |
2620 | ---- pdfshuffler-0.3.1.orig/pdfshuffler 2009-02-08 20:59:26.000000000 +0000 |
2621 | -+++ pdfshuffler-0.3.1/pdfshuffler 2009-02-08 20:59:27.000000000 +0000 |
2622 | -@@ -1,4 +1,4 @@ |
2623 | --#!/usr/bin/env python |
2624 | -+#!/usr/bin/python |
2625 | - # -*- coding: utf-8 -*- |
2626 | - |
2627 | - """ |
2628 | |
2629 | === added file 'debian/patches/install_and_look_for_UI_file_in_right_dirs.patch' |
2630 | --- debian/patches/install_and_look_for_UI_file_in_right_dirs.patch 1970-01-01 00:00:00 +0000 |
2631 | +++ debian/patches/install_and_look_for_UI_file_in_right_dirs.patch 2012-02-12 00:35:23 +0000 |
2632 | @@ -0,0 +1,31 @@ |
2633 | +# Description: Install and look for the UI file in the right dirs |
2634 | +# Forward: yes |
2635 | +# Author: Matthieu Baerts (matttbe) <matttbe@gmail.com> |
2636 | +# Last-Update: 2012-02-12 |
2637 | + |
2638 | +Index: pdfshuffler/pdfshuffler/pdfshuffler.py |
2639 | +=================================================================== |
2640 | +--- pdfshuffler.orig/pdfshuffler/pdfshuffler.py 2012-02-12 01:15:27.115323000 +0100 |
2641 | ++++ pdfshuffler/pdfshuffler/pdfshuffler.py 2012-02-12 01:24:18.630058194 +0100 |
2642 | +@@ -112,7 +112,7 @@ |
2643 | + # Import the user interface file, trying different possible locations |
2644 | + ui_path = '/usr/share/pdfshuffler/pdfshuffler.ui' |
2645 | + if not os.path.exists(ui_path): |
2646 | +- ui_path = '/usr/local/share/applications/pdfshuffler/pdfshuffler.ui' |
2647 | ++ ui_path = '/usr/local/share/pdfshuffler/pdfshuffler.ui' |
2648 | + if not os.path.exists(ui_path): |
2649 | + parent_dir = os.path.dirname( \ |
2650 | + os.path.dirname(os.path.realpath(__file__))) |
2651 | +Index: pdfshuffler/setup.py |
2652 | +=================================================================== |
2653 | +--- pdfshuffler.orig/setup.py 2012-02-12 01:15:27.115323000 +0100 |
2654 | ++++ pdfshuffler/setup.py 2012-02-12 01:24:18.630058194 +0100 |
2655 | +@@ -25,7 +25,7 @@ |
2656 | + import re |
2657 | + from distutils.core import setup |
2658 | + |
2659 | +-data_files=[('share/applications/pdfshuffler', ['data/pdfshuffler.ui']), |
2660 | ++data_files=[('share/pdfshuffler', ['data/pdfshuffler.ui']), |
2661 | + ('share/applications', ['data/pdfshuffler.desktop']), |
2662 | + ('share/man/man1', ['doc/pdfshuffler.1']), |
2663 | + ('share/pixmaps', ['data/pdfshuffler.svg']), |
2664 | |
2665 | === modified file 'debian/patches/series' |
2666 | --- debian/patches/series 2009-05-16 23:26:32 +0000 |
2667 | +++ debian/patches/series 2012-02-12 00:35:23 +0000 |
2668 | @@ -1,1 +1,1 @@ |
2669 | -fix-shebang |
2670 | +install_and_look_for_UI_file_in_right_dirs.patch |
2671 | |
2672 | === modified file 'doc/pdfshuffler.1' |
2673 | --- doc/pdfshuffler.1 2011-02-15 00:43:28 +0000 |
2674 | +++ doc/pdfshuffler.1 2012-02-12 00:35:23 +0000 |
2675 | @@ -1,4 +1,4 @@ |
2676 | -.TH PDFSHUFFLER 1 "December 2009" "version 0.5.1" "User Manuals" |
2677 | +.TH PDFSHUFFLER 1 "December 2011" "version 0.6" "User Manuals" |
2678 | .SH "NAME" |
2679 | PDF-Shuffler \- Application for PDF Merging, Rearranging, and Splitting |
2680 | .SH "SYNOPSIS" |
2681 | @@ -19,5 +19,5 @@ |
2682 | .SH "DIAGNOSTICS" |
2683 | Common python and gtk diagnostics. |
2684 | .SH "AUTHOR" |
2685 | -Konstantinos Poulios <poulios.konstantinos@gmail.com> |
2686 | +Konstantinos Poulios <logari81@gmail.com> |
2687 | |
2688 | |
2689 | === modified file 'pdfshuffler' |
2690 | --- pdfshuffler 2011-02-15 00:43:28 +0000 |
2691 | +++ pdfshuffler 1970-01-01 00:00:00 +0000 |
2692 | @@ -1,1031 +0,0 @@ |
2693 | -#!/usr/bin/python |
2694 | -# -*- coding: utf-8 -*- |
2695 | - |
2696 | -""" |
2697 | - -------------------------------------------------------------------------- |
2698 | - |
2699 | - PDF-Shuffler 0.5.1 - pyGTK PDF Merging, Rearranging, and Splitting |
2700 | - Copyright (C) 2008-2010 Konstantinos Poulios |
2701 | - <https://sourceforge.net/projects/pdfshuffler> |
2702 | - |
2703 | - -------------------------------------------------------------------------- |
2704 | - |
2705 | - This program is free software; you can redistribute it and/or modify |
2706 | - it under the terms of the GNU General Public License as published by |
2707 | - the Free Software Foundation; either version 3 of the License, or |
2708 | - (at your option) any later version. |
2709 | - |
2710 | - This program is distributed in the hope that it will be useful, |
2711 | - but WITHOUT ANY WARRANTY; without even the implied warranty of |
2712 | - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2713 | - GNU General Public License for more details. |
2714 | - |
2715 | - You should have received a copy of the GNU General Public License along |
2716 | - with this program; if not, write to the Free Software Foundation, Inc., |
2717 | - 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
2718 | - |
2719 | - -------------------------------------------------------------------------- |
2720 | -""" |
2721 | - |
2722 | -import os |
2723 | -import shutil #needed for file operations like whole directory deletion |
2724 | -import sys #needed for proccessing of command line args |
2725 | -import urllib #needed to parse filename information passed by DnD |
2726 | -import threading |
2727 | -import tempfile |
2728 | -from copy import copy |
2729 | - |
2730 | -import locale #for multilanguage support |
2731 | -import gettext |
2732 | -gettext.install('pdfshuffler', unicode=1) |
2733 | - |
2734 | -try: |
2735 | - import pygtk |
2736 | - pygtk.require('2.0') |
2737 | - import gtk |
2738 | - assert gtk.gtk_version >= (2, 10, 0) |
2739 | - assert gtk.pygtk_version >= (2, 10, 0) |
2740 | -except AssertionError: |
2741 | - print('You do not have the required versions of GTK+ and/or PyGTK ' + |
2742 | - 'installed.\n\n' + |
2743 | - 'Installed GTK+ version is ' + |
2744 | - '.'.join([str(n) for n in gtk.gtk_version]) + '\n' + |
2745 | - 'Required GTK+ version is 2.10.0 or higher\n\n' |
2746 | - 'Installed PyGTK version is ' + |
2747 | - '.'.join([str(n) for n in gtk.pygtk_version]) + '\n' + |
2748 | - 'Required PyGTK version is 2.10.0 or higher') |
2749 | - sys.exit(1) |
2750 | -except: |
2751 | - print('PyGTK version 2.10.0 or higher is required to run this program.') |
2752 | - print('No version of PyGTK was found on your system.') |
2753 | - sys.exit(1) |
2754 | - |
2755 | -gtk.gdk.threads_init() |
2756 | -import gobject #to use custom signals |
2757 | -import pango #to adjust the text alignment in CellRendererText |
2758 | -import gio #to inquire mime types information |
2759 | - |
2760 | -import poppler #for the rendering of pdf pages |
2761 | -from pyPdf import PdfFileWriter, PdfFileReader |
2762 | - |
2763 | -class PDFshuffler: |
2764 | - # ======================================================= |
2765 | - # All the preferences are stored here. |
2766 | - # ======================================================= |
2767 | - prefs = { |
2768 | - 'window width': min (700, gtk.gdk.screen_get_default().get_width() / 2 ), |
2769 | - 'window height': min(600, gtk.gdk.screen_get_default().get_height() - 50 ), |
2770 | - 'window x': 0, |
2771 | - 'window y': 0, |
2772 | - 'initial thumbnail size': 530, |
2773 | - 'initial zoom scale': 0.25, |
2774 | - } |
2775 | - |
2776 | - MODEL_ROW_INTERN = 1001 |
2777 | - MODEL_ROW_EXTERN = 1002 |
2778 | - TEXT_URI_LIST = 1003 |
2779 | - MODEL_ROW_MOTION = 1004 |
2780 | - TARGETS_IV = [('MODEL_ROW_INTERN', gtk.TARGET_SAME_WIDGET, MODEL_ROW_INTERN), |
2781 | - ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN), |
2782 | - ('MODEL_ROW_MOTION', 0, MODEL_ROW_MOTION)] |
2783 | - TARGETS_SW = [('text/uri-list', 0, TEXT_URI_LIST), |
2784 | - ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN)] |
2785 | - |
2786 | - def __init__(self): |
2787 | - # Create the temporary directory |
2788 | - self.tmp_dir = tempfile.mkdtemp("pdfshuffler") |
2789 | - os.chmod(self.tmp_dir, 0700) |
2790 | - |
2791 | - pixmap = os.path.join(sys.prefix,'share','pixmaps','pdfshuffler.png') |
2792 | - try: |
2793 | - gtk.window_set_default_icon_from_file(pixmap) |
2794 | - except: |
2795 | - print(_('File %s does not exist') % pixmap) |
2796 | - |
2797 | - # Create the main window, and attach delete_event signal to terminating |
2798 | - # the application |
2799 | - self.window = gtk.Window(gtk.WINDOW_TOPLEVEL) |
2800 | - self.window.set_title('PDF-Shuffler') |
2801 | - self.window.set_border_width(0) |
2802 | - self.window.move(self.prefs['window x'], self.prefs['window y']) |
2803 | - self.window.set_size_request(self.prefs['window width'], |
2804 | - self.prefs['window height']) |
2805 | - self.window.connect('delete_event', self.close_application) |
2806 | - self.window.show() |
2807 | - |
2808 | - # Create a vbox to hold the thumnails-container |
2809 | - vbox = gtk.VBox() |
2810 | - self.window.add(vbox) |
2811 | - |
2812 | - # Create a scrolled window to hold the thumbnails-container |
2813 | - self.sw = gtk.ScrolledWindow() |
2814 | - self.sw.set_border_width(0) |
2815 | - self.sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) |
2816 | - self.sw.drag_dest_set(gtk.DEST_DEFAULT_MOTION | |
2817 | - gtk.DEST_DEFAULT_HIGHLIGHT | |
2818 | - gtk.DEST_DEFAULT_DROP | |
2819 | - gtk.DEST_DEFAULT_MOTION, |
2820 | - self.TARGETS_SW, |
2821 | - gtk.gdk.ACTION_COPY | |
2822 | - gtk.gdk.ACTION_MOVE ) |
2823 | - self.sw.connect('drag_data_received', self.sw_dnd_received_data) |
2824 | - self.sw.connect('button_press_event', self.sw_button_press_event) |
2825 | - vbox.pack_start(self.sw) |
2826 | - |
2827 | - # Create an alignment to keep the thumbnails center-aligned |
2828 | - align = gtk.Alignment(0.5, 0.5, 0, 0) |
2829 | - self.sw.add_with_viewport(align) |
2830 | - |
2831 | - # Create ListStore model and IconView |
2832 | - self.model = gtk.ListStore(str, # 0.Text descriptor |
2833 | - gtk.gdk.Pixbuf, # 1.Thumbnail image |
2834 | - int, # 2.Document number |
2835 | - int, # 3.Page number |
2836 | - int, # 4.Thumbnail width |
2837 | - str, # 5.Document filename |
2838 | - bool, # 6.Rendered |
2839 | - int, # 7.Rotation angle |
2840 | - float, # 8.Crop left |
2841 | - float, # 9.Crop right |
2842 | - float, # 10.Crop top |
2843 | - float) # 11.Crop bottom |
2844 | - |
2845 | - self.zoom_scale = self.prefs['initial zoom scale'] |
2846 | - self.iv_col_width = self.prefs['initial thumbnail size'] |
2847 | - |
2848 | - self.iconview = gtk.IconView(self.model) |
2849 | - self.iconview.set_item_width(self.iv_col_width + 12) |
2850 | - |
2851 | - self.iconview.set_pixbuf_column(1) |
2852 | -# self.cellpb = gtk.CellRendererPixbuf() |
2853 | -# self.cellpb.set_property('follow-state', True) |
2854 | -# self.iconview.pack_start(self.cellpb, False) |
2855 | -# self.iconview.set_attributes(self.cellpb, pixbuf=1) |
2856 | - |
2857 | -# self.iconview.set_text_column(0) |
2858 | - self.celltxt = gtk.CellRendererText() |
2859 | - self.celltxt.set_property('width', self.iv_col_width) |
2860 | - self.celltxt.set_property('wrap-width', self.iv_col_width) |
2861 | - self.celltxt.set_property('alignment', pango.ALIGN_CENTER) |
2862 | - self.iconview.pack_start(self.celltxt, False) |
2863 | - self.iconview.set_attributes(self.celltxt, text=0) |
2864 | - |
2865 | - self.iconview.set_selection_mode(gtk.SELECTION_MULTIPLE) |
2866 | - self.iconview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, |
2867 | - self.TARGETS_IV, |
2868 | - gtk.gdk.ACTION_COPY | |
2869 | - gtk.gdk.ACTION_MOVE ) |
2870 | - self.iconview.enable_model_drag_dest(self.TARGETS_IV, |
2871 | - gtk.gdk.ACTION_DEFAULT) |
2872 | - self.iconview.connect('drag_begin', self.iv_drag_begin) |
2873 | - self.iconview.connect('drag_data_get', self.iv_dnd_get_data) |
2874 | - self.iconview.connect('drag_data_received', self.iv_dnd_received_data) |
2875 | - self.iconview.connect('drag_data_delete', self.iv_dnd_data_delete) |
2876 | - self.iconview.connect('drag_motion', self.iv_dnd_motion) |
2877 | - self.iconview.connect('drag_leave', self.iv_dnd_leave_end) |
2878 | - self.iconview.connect('drag_end', self.iv_dnd_leave_end) |
2879 | - self.iconview.connect('button_press_event', self.iv_button_press_event) |
2880 | - self.iv_auto_scroll_direction = 0 |
2881 | - |
2882 | - style = self.iconview.get_style().copy() |
2883 | - style_sw = self.sw.get_style() |
2884 | - for state in (gtk.STATE_NORMAL, gtk.STATE_PRELIGHT, gtk.STATE_ACTIVE): |
2885 | - style.base[state] = style_sw.bg[gtk.STATE_NORMAL] |
2886 | - self.iconview.set_style(style) |
2887 | - |
2888 | - align.add(self.iconview) |
2889 | - |
2890 | - # Create a horizontal box to hold the buttons |
2891 | - hbox = gtk.HBox() |
2892 | - vbox.pack_start(hbox, expand=False, fill=False) |
2893 | - |
2894 | - # Create buttons |
2895 | - self.button_exit = gtk.Button(stock=gtk.STOCK_QUIT) |
2896 | - self.button_exit.connect('clicked', self.close_application) |
2897 | - hbox.pack_start(self.button_exit, expand=True, fill=True, padding=20) |
2898 | - |
2899 | - self.button_del = gtk.Button(_('Delete Page(s)')) |
2900 | - image = gtk.Image() |
2901 | - image.set_from_stock(gtk.STOCK_DELETE, gtk.ICON_SIZE_BUTTON) |
2902 | - self.button_del.set_image(image) |
2903 | - self.button_del.connect('clicked', self.clear_selected) |
2904 | - hbox.pack_start(self.button_del, expand=True, fill=True, padding=20) |
2905 | - |
2906 | - self.button_import = gtk.Button(_('Import pdf')) |
2907 | - image = gtk.Image() |
2908 | - image.set_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON) |
2909 | - self.button_import.set_image(image) |
2910 | - self.button_import.connect('clicked', self.on_action_add_doc_activate) |
2911 | - hbox.pack_start(self.button_import, expand=True, fill=True, padding=20) |
2912 | - |
2913 | - self.button_export = gtk.Button(_('Export pdf')) |
2914 | - image = gtk.Image() |
2915 | - image.set_from_stock(gtk.STOCK_SAVE_AS, gtk.ICON_SIZE_BUTTON) |
2916 | - self.button_export.set_image(image) |
2917 | - self.button_export.connect('clicked', self.choose_export_pdf_name) |
2918 | - hbox.pack_start(self.button_export, expand=True, fill=True, padding=20) |
2919 | - |
2920 | - self.button_export = gtk.Button(_('About')) |
2921 | - image = gtk.Image() |
2922 | - image.set_from_stock(gtk.STOCK_ABOUT, gtk.ICON_SIZE_BUTTON) |
2923 | - self.button_export.set_image(image) |
2924 | - self.button_export.connect('clicked', self.about_dialog) |
2925 | - hbox.pack_start(self.button_export, expand=True, fill=True, padding=20) |
2926 | - |
2927 | - # Define window callback function and show window |
2928 | - self.window.connect('size_allocate', self.on_window_size_request) # resize |
2929 | - self.window.connect('key_press_event', self.on_keypress_event ) # keypress |
2930 | - self.window.show_all() |
2931 | - |
2932 | - #Creating the popup menu |
2933 | - self.popup = gtk.Menu() |
2934 | - popup_rotate_right = gtk.MenuItem(_('Rotate Page(s) Clockwise')) |
2935 | - popup_rotate_left = gtk.MenuItem(_('Rotate Page(s) Counterclockwise')) |
2936 | - popup_crop = gtk.MenuItem(_('Crop Page(s)')) |
2937 | - popup_delete = gtk.MenuItem(_('Delete Page(s)')) |
2938 | - popup_rotate_right.connect('activate', self.rotate_page_right) |
2939 | - popup_rotate_left.connect('activate', self.rotate_page_left) |
2940 | - popup_crop.connect('activate', self.crop_page_dialog) |
2941 | - popup_delete.connect('activate', self.clear_selected) |
2942 | - popup_rotate_right.show() |
2943 | - popup_rotate_left.show() |
2944 | - popup_crop.show() |
2945 | - popup_delete.show() |
2946 | - self.popup.append(popup_rotate_right) |
2947 | - self.popup.append(popup_rotate_left) |
2948 | - self.popup.append(popup_crop) |
2949 | - self.popup.append(popup_delete) |
2950 | - |
2951 | - # Initializing variables |
2952 | - self.export_directory = os.getenv('HOME') |
2953 | - self.import_directory = os.getenv('HOME') |
2954 | - self.nfile = 0 |
2955 | - self.iv_auto_scroll_timer = None |
2956 | - self.pdfqueue = [] |
2957 | - |
2958 | - gobject.type_register(PDF_Renderer) |
2959 | - gobject.signal_new('reset_iv_width', PDF_Renderer, |
2960 | - gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ()) |
2961 | - self.rendering_thread = PDF_Renderer(self.model, self.pdfqueue, |
2962 | - self.zoom_scale, self.iv_col_width) |
2963 | - self.rendering_thread.connect('reset_iv_width', self.reset_iv_width) |
2964 | - self.rendering_thread.start() |
2965 | - |
2966 | - # Importing documents passed as command line arguments |
2967 | - for filename in sys.argv[1:]: |
2968 | - self.add_pdf_pages(filename) |
2969 | - |
2970 | - # ======================================================= |
2971 | - def render(self): |
2972 | - if self.rendering_thread.paused: |
2973 | - self.rendering_thread.paused = False |
2974 | - self.rendering_thread.evnt.set() |
2975 | - self.rendering_thread.evnt.clear() |
2976 | - |
2977 | - # ======================================================= |
2978 | - def on_window_size_request(self, window, event): |
2979 | - """Main Window resize - workaround for autosetting of |
2980 | - iconview cols no.""" |
2981 | - |
2982 | - #add 12 because of: http://bugzilla.gnome.org/show_bug.cgi?id=570152 |
2983 | - col_num = 9 * window.get_size()[0] / (10 * (self.iv_col_width + 12)) |
2984 | - self.iconview.set_columns(col_num) |
2985 | - |
2986 | - # ======================================================= |
2987 | - def reset_iv_width(self, renderer=None): |
2988 | - """Reconfigures the width of the iconview columns""" |
2989 | - |
2990 | - max_w = max(row[4] for row in self.model) |
2991 | - if max_w != self.iv_col_width: |
2992 | - self.iv_col_width = max_w |
2993 | - self.celltxt.set_property('width', self.iv_col_width) |
2994 | - self.celltxt.set_property('wrap-width', self.iv_col_width) |
2995 | - self.iconview.set_item_width(self.iv_col_width + 12) #-1) |
2996 | - self.on_window_size_request(self.window, None) |
2997 | - |
2998 | - # ======================================================= |
2999 | - def on_keypress_event(self, widget, event): |
3000 | - """Keypress events in Main Window""" |
3001 | - |
3002 | - #keyname = gtk.gdk.keyval_name(event.keyval) |
3003 | - if event.keyval == 65535: # Delete keystroke |
3004 | - self.clear_selected() |
3005 | - |
3006 | - # ======================================================= |
3007 | - def close_application(self, widget, event=None, data=None): |
3008 | - """Termination""" |
3009 | - |
3010 | - #gtk.gdk.threads_leave() |
3011 | - self.rendering_thread.quit = True |
3012 | - #gtk.gdk.threads_enter() |
3013 | - if self.rendering_thread.paused == True: |
3014 | - self.rendering_thread.evnt.set() |
3015 | - self.rendering_thread.evnt.clear() |
3016 | - if os.path.isdir(self.tmp_dir): |
3017 | - shutil.rmtree(self.tmp_dir) |
3018 | - if gtk.main_level(): |
3019 | - gtk.main_quit() |
3020 | - else: |
3021 | - sys.exit(0) |
3022 | - return False |
3023 | - |
3024 | - # ======================================================= |
3025 | - def add_pdf_pages(self, filename, |
3026 | - firstpage=None, lastpage=None, |
3027 | - angle=0, crop=[0.,0.,0.,0.] ): |
3028 | - """Add pages of a pdf document to the model""" |
3029 | - |
3030 | - res = False |
3031 | - # Check if the document has already been loaded |
3032 | - pdfdoc = None |
3033 | - for it_pdfdoc in self.pdfqueue: |
3034 | - if os.path.isfile(it_pdfdoc.filename) and \ |
3035 | - os.path.samefile(filename, it_pdfdoc.filename) and \ |
3036 | - os.path.getmtime(filename) is it_pdfdoc.mtime: |
3037 | - pdfdoc = it_pdfdoc |
3038 | - break |
3039 | - |
3040 | - if not pdfdoc: |
3041 | - pdfdoc = PDF_Doc(filename, self.nfile, self.tmp_dir) |
3042 | - self.import_directory = os.path.split(filename)[0] |
3043 | - self.export_directory = self.import_directory |
3044 | - if pdfdoc.nfile != 0 and pdfdoc != []: |
3045 | - self.nfile = pdfdoc.nfile |
3046 | - self.pdfqueue.append(pdfdoc) |
3047 | - else: |
3048 | - return res |
3049 | - |
3050 | - n_start = 1 |
3051 | - n_end = pdfdoc.npage |
3052 | - if firstpage: |
3053 | - n_start = min(n_end, max(1, firstpage)) |
3054 | - if lastpage: |
3055 | - n_end = max(n_start, min(n_end, lastpage)) |
3056 | - |
3057 | - for npage in range(n_start, n_end + 1): |
3058 | - descriptor = ''.join([pdfdoc.shortname, '\n', _('page'), ' ', str(npage)]) |
3059 | - width = self.iv_col_width |
3060 | - thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, |
3061 | - 8, width, width) |
3062 | - self.model.append((descriptor, # 0 |
3063 | - thumbnail, # 1 |
3064 | - pdfdoc.nfile, # 2 |
3065 | - npage, # 3 |
3066 | - width, # 4 |
3067 | - pdfdoc.filename, # 5 |
3068 | - False, # 6 |
3069 | - angle, # 7 |
3070 | - crop[0],crop[1], # 8-9 |
3071 | - crop[2],crop[3] )) # 10-11 |
3072 | - res = True |
3073 | - |
3074 | - if res: |
3075 | - self.render() |
3076 | - return res |
3077 | - |
3078 | - # ======================================================= |
3079 | - def choose_export_pdf_name(self, widget=None): |
3080 | - """Handles choosing a name for exporting """ |
3081 | - |
3082 | - chooser = gtk.FileChooserDialog(title=_('Export ...'), |
3083 | - action=gtk.FILE_CHOOSER_ACTION_SAVE, |
3084 | - buttons=(gtk.STOCK_CANCEL, |
3085 | - gtk.RESPONSE_CANCEL, |
3086 | - gtk.STOCK_SAVE, |
3087 | - gtk.RESPONSE_OK)) |
3088 | - chooser.set_do_overwrite_confirmation(True) |
3089 | - chooser.set_current_folder(self.export_directory) |
3090 | - filter_pdf = gtk.FileFilter() |
3091 | - filter_pdf.set_name(_('PDF files')) |
3092 | - filter_pdf.add_mime_type('application/pdf') |
3093 | - chooser.add_filter(filter_pdf) |
3094 | - |
3095 | - filter_all = gtk.FileFilter() |
3096 | - filter_all.set_name(_('All files')) |
3097 | - filter_all.add_pattern('*') |
3098 | - chooser.add_filter(filter_all) |
3099 | - |
3100 | - while True: |
3101 | - response = chooser.run() |
3102 | - if response == gtk.RESPONSE_OK: |
3103 | - file_out = chooser.get_filename() |
3104 | - (path, shortname) = os.path.split(file_out) |
3105 | - (shortname, ext) = os.path.splitext(shortname) |
3106 | - if ext.lower() != '.pdf': |
3107 | - file_out = file_out + '.pdf' |
3108 | - try: |
3109 | - self.export_to_file(file_out) |
3110 | - self.export_directory = path |
3111 | - except IOError: |
3112 | - error_msg_win = gtk.MessageDialog(flags=gtk.DIALOG_MODAL, |
3113 | - type=gtk.MESSAGE_ERROR, |
3114 | - message_format=_("Error writing file: %s") % file_out, |
3115 | - buttons=gtk.BUTTONS_OK) |
3116 | - response = error_msg_win.run() |
3117 | - if response == gtk.RESPONSE_OK: |
3118 | - error_msg_win.destroy() |
3119 | - continue |
3120 | - break |
3121 | - chooser.destroy() |
3122 | - |
3123 | - # ======================================================= |
3124 | - def export_to_file(self, file_out): |
3125 | - """Export to file""" |
3126 | - |
3127 | - pdf_output = PdfFileWriter() |
3128 | - pdf_input = [] |
3129 | - for pdfdoc in self.pdfqueue: |
3130 | - pdfdoc_inp = PdfFileReader(file(pdfdoc.copyname, 'rb')) |
3131 | - if pdfdoc_inp.getIsEncrypted(): |
3132 | - if (pdfdoc_inp.decrypt('')!=1): # Workaround for lp:#355479 |
3133 | - print(_('File %s is encrypted.') % pdfdoc.filename) |
3134 | - print(_('Support for such files has not been implemented yet.')) |
3135 | - print(_('File export failed.')) |
3136 | - return |
3137 | - #FIXME |
3138 | - #else |
3139 | - # ask for password and decrypt file |
3140 | - pdf_input.append(pdfdoc_inp) |
3141 | - |
3142 | - for row in self.model: |
3143 | - # add pages from input to output document |
3144 | - nfile = row[2] |
3145 | - npage = row[3] |
3146 | - current_page = copy(pdf_input[nfile-1].getPage(npage-1)) |
3147 | - angle = row[7] |
3148 | - angle0 = current_page.get("/Rotate",0) |
3149 | - crop = [row[8],row[9],row[10],row[11]] |
3150 | - if angle is not 0: |
3151 | - current_page.rotateClockwise(angle) |
3152 | - if crop != [0.,0.,0.,0.]: |
3153 | - rotate_times = ( ( ( angle + angle0 ) % 360 + 45 ) / 90 ) % 4 |
3154 | - crop_init = crop |
3155 | - if rotate_times is not 0: |
3156 | - perm = [0,2,1,3] |
3157 | - for it in range(rotate_times): |
3158 | - perm.append(perm.pop(0)) |
3159 | - perm.insert(1,perm.pop(2)) |
3160 | - crop = [crop_init[perm[side]] for side in range(4)] |
3161 | - #(x1, y1) = current_page.cropBox.lowerLeft |
3162 | - #(x2, y2) = current_page.cropBox.upperRight |
3163 | - (x1, y1) = [float(xy) for xy in current_page.mediaBox.lowerLeft] |
3164 | - (x2, y2) = [float(xy) for xy in current_page.mediaBox.upperRight] |
3165 | - x1_new = int( x1 + (x2-x1) * crop[0] ) |
3166 | - x2_new = int( x2 - (x2-x1) * crop[1] ) |
3167 | - y1_new = int( y1 + (y2-y1) * crop[3] ) |
3168 | - y2_new = int( y2 - (y2-y1) * crop[2] ) |
3169 | - #current_page.cropBox.lowerLeft = (x1_new, y1_new) |
3170 | - #current_page.cropBox.upperRight = (x2_new, y2_new) |
3171 | - current_page.mediaBox.lowerLeft = (x1_new, y1_new) |
3172 | - current_page.mediaBox.upperRight = (x2_new, y2_new) |
3173 | - |
3174 | - pdf_output.addPage(current_page) |
3175 | - |
3176 | - # finally, write "output" to document-output.pdf |
3177 | - print(_('exporting to:'), file_out) |
3178 | - pdf_output.write(file(file_out, 'wb')) |
3179 | - |
3180 | - # ======================================================= |
3181 | - def on_action_add_doc_activate(self, widget, data=None): |
3182 | - """Import doc""" |
3183 | - |
3184 | - chooser = gtk.FileChooserDialog(title=_('Import...'), |
3185 | - action=gtk.FILE_CHOOSER_ACTION_OPEN, |
3186 | - buttons=(gtk.STOCK_CANCEL, |
3187 | - gtk.RESPONSE_CANCEL, |
3188 | - gtk.STOCK_OPEN, |
3189 | - gtk.RESPONSE_OK)) |
3190 | - chooser.set_current_folder(self.import_directory) |
3191 | - chooser.set_select_multiple(True) |
3192 | - |
3193 | - filter_all = gtk.FileFilter() |
3194 | - filter_all.set_name(_('All files')) |
3195 | - filter_all.add_pattern('*') |
3196 | - chooser.add_filter(filter_all) |
3197 | - |
3198 | - filter_pdf = gtk.FileFilter() |
3199 | - filter_pdf.set_name(_('PDF files')) |
3200 | - filter_pdf.add_mime_type('application/pdf') |
3201 | - chooser.add_filter(filter_pdf) |
3202 | - chooser.set_filter(filter_pdf) |
3203 | - |
3204 | - response = chooser.run() |
3205 | - if response == gtk.RESPONSE_OK: |
3206 | - for filename in chooser.get_filenames(): |
3207 | - if os.path.isfile(filename): |
3208 | - # FIXME |
3209 | - f = gio.File(filename) |
3210 | - f_info = f.query_info('standard::content-type') |
3211 | - mime_type = f_info.get_content_type() |
3212 | - if mime_type == 'application/pdf': |
3213 | - self.add_pdf_pages(filename) |
3214 | - elif mime_type[:34] == 'application/vnd.oasis.opendocument': |
3215 | - print(_('OpenDocument not supported yet!')) |
3216 | - elif mime_type[:5] == 'image': |
3217 | - print(_('Image file not supported yet!')) |
3218 | - else: |
3219 | - print(_('File type not supported!')) |
3220 | - else: |
3221 | - print(_('File %s does not exist') % filename) |
3222 | - elif response == gtk.RESPONSE_CANCEL: |
3223 | - print(_('Closed, no files selected')) |
3224 | - chooser.destroy() |
3225 | - |
3226 | - # ======================================================= |
3227 | - def clear_selected(self, button=None): |
3228 | - """Removes the selected Element in the IconView""" |
3229 | - |
3230 | - model = self.iconview.get_model() |
3231 | - selection = self.iconview.get_selected_items() |
3232 | - if selection: |
3233 | - selection.sort(reverse=True) |
3234 | - for path in selection: |
3235 | - iter = model.get_iter(path) |
3236 | - model.remove(iter) |
3237 | - path = selection[-1] |
3238 | - self.iconview.select_path(path) |
3239 | - if not self.iconview.path_is_selected(path): |
3240 | - if len(model) > 0: # select the last row |
3241 | - row = model[-1] |
3242 | - path = row.path |
3243 | - self.iconview.select_path(path) |
3244 | - self.iconview.grab_focus() |
3245 | - |
3246 | - # ======================================================= |
3247 | - def iv_drag_begin(self, iconview, context): |
3248 | - """Sets custom icon on drag begin for multiple items selected""" |
3249 | - |
3250 | - if len(iconview.get_selected_items()) > 1: |
3251 | - iconview.stop_emission('drag_begin') |
3252 | - context.set_icon_stock(gtk.STOCK_DND_MULTIPLE, 0, 0) |
3253 | - |
3254 | - # ======================================================= |
3255 | - def iv_dnd_get_data(self, iconview, context, |
3256 | - selection_data, target_id, etime): |
3257 | - """Handles requests for data by drag and drop in iconview""" |
3258 | - |
3259 | - model = iconview.get_model() |
3260 | - selection = self.iconview.get_selected_items() |
3261 | - selection.sort(key=lambda x: x[0]) |
3262 | - data = [] |
3263 | - for path in selection: |
3264 | - if selection_data.target == 'MODEL_ROW_INTERN': |
3265 | - data.append( str(path[0]) ) |
3266 | - elif selection_data.target == 'MODEL_ROW_EXTERN': |
3267 | - iter = model.get_iter(path) |
3268 | - nfile, npage, angle = model.get(iter, 2, 3, 7) |
3269 | - crop = model.get(iter, 8, 9, 10, 11) |
3270 | - pdfdoc = self.pdfqueue[nfile - 1] |
3271 | - data.append('\n'.join([pdfdoc.filename, |
3272 | - str(npage), |
3273 | - str(angle) ] + |
3274 | - [str(side) for side in crop] ) ) |
3275 | - if data: |
3276 | - data = '\n;\n'.join(data) |
3277 | - selection_data.set(selection_data.target, 8, data) |
3278 | - |
3279 | - # ======================================================= |
3280 | - def iv_dnd_received_data(self, iconview, context, x, y, |
3281 | - selection_data, target_id, etime): |
3282 | - """Handles received data by drag and drop in iconview""" |
3283 | - |
3284 | - model = iconview.get_model() |
3285 | - data = selection_data.data |
3286 | - if data: |
3287 | - data = data.split('\n;\n') |
3288 | - drop_info = iconview.get_dest_item_at_pos(x, y) |
3289 | - iter_to = None |
3290 | - if drop_info: |
3291 | - path, position = drop_info |
3292 | - ref_to = gtk.TreeRowReference(model,path) |
3293 | - else: |
3294 | - position = gtk.ICON_VIEW_DROP_RIGHT |
3295 | - if len(model) > 0: #find the iterator of the last row |
3296 | - row = model[-1] |
3297 | - path = row.path |
3298 | - ref_to = gtk.TreeRowReference(model,path) |
3299 | - if ref_to: |
3300 | - before = ( position == gtk.ICON_VIEW_DROP_LEFT |
3301 | - or position == gtk.ICON_VIEW_DROP_ABOVE) |
3302 | - #if target_id == self.MODEL_ROW_INTERN: |
3303 | - if selection_data.target == 'MODEL_ROW_INTERN': |
3304 | - if before: |
3305 | - data.sort(key=int) |
3306 | - else: |
3307 | - data.sort(key=int,reverse=True) |
3308 | - ref_from_list = [gtk.TreeRowReference(model,path) |
3309 | - for path in data] |
3310 | - for ref_from in ref_from_list: |
3311 | - path = ref_to.get_path() |
3312 | - iter_to = model.get_iter(path) |
3313 | - path = ref_from.get_path() |
3314 | - iter_from = model.get_iter(path) |
3315 | - row = model[iter_from] |
3316 | - if before: |
3317 | - model.insert_before(iter_to, row) |
3318 | - else: |
3319 | - model.insert_after(iter_to, row) |
3320 | - if context.action == gtk.gdk.ACTION_MOVE: |
3321 | - for ref_from in ref_from_list: |
3322 | - path = ref_from.get_path() |
3323 | - iter_from = model.get_iter(path) |
3324 | - model.remove(iter_from) |
3325 | - |
3326 | - #elif target_id == self.MODEL_ROW_EXTERN: |
3327 | - elif selection_data.target == 'MODEL_ROW_EXTERN': |
3328 | - if not before: |
3329 | - data.reverse() |
3330 | - while data: |
3331 | - tmp = data.pop(0).split('\n') |
3332 | - filename = tmp[0] |
3333 | - npage, angle = [int(k) for k in tmp[1:3]] |
3334 | - crop = [float(side) for side in tmp[3:7]] |
3335 | - if self.add_pdf_pages(filename, npage, npage, |
3336 | - angle, crop ): |
3337 | - if len(model) > 0: |
3338 | - path = ref_to.get_path() |
3339 | - iter_to = model.get_iter(path) |
3340 | - row = model[-1] #the last row |
3341 | - path = row.path |
3342 | - iter_from = model.get_iter(path) |
3343 | - if before: |
3344 | - model.move_before(iter_from, iter_to) |
3345 | - else: |
3346 | - model.move_after(iter_from, iter_to) |
3347 | - if context.action == gtk.gdk.ACTION_MOVE: |
3348 | - context.finish(True, True, etime) |
3349 | - |
3350 | - # ======================================================= |
3351 | - def iv_dnd_data_delete(self, widget, context): |
3352 | - """Deletes dnd items after a successful move operation""" |
3353 | - |
3354 | - model = self.iconview.get_model() |
3355 | - selection = self.iconview.get_selected_items() |
3356 | - ref_del_list = [gtk.TreeRowReference(model,path) for path in selection] |
3357 | - for ref_del in ref_del_list: |
3358 | - path = ref_del.get_path() |
3359 | - iter = model.get_iter(path) |
3360 | - model.remove(iter) |
3361 | - |
3362 | - # ======================================================= |
3363 | - def iv_dnd_motion(self, iconview, context, x, y, etime): |
3364 | - """Handles the drag-motion signal in order to auto-scroll the view""" |
3365 | - |
3366 | - autoscroll_area = 40 |
3367 | - sw_vadj = self.sw.get_vadjustment() |
3368 | - sw_height = self.sw.get_allocation().height |
3369 | - if y -sw_vadj.get_value() < autoscroll_area: |
3370 | - if not self.iv_auto_scroll_timer: |
3371 | - self.iv_auto_scroll_direction = gtk.DIR_UP |
3372 | - self.iv_auto_scroll_timer = gobject.timeout_add(150, |
3373 | - self.iv_auto_scroll) |
3374 | - elif y -sw_vadj.get_value() > sw_height - autoscroll_area: |
3375 | - if not self.iv_auto_scroll_timer: |
3376 | - self.iv_auto_scroll_direction = gtk.DIR_DOWN |
3377 | - self.iv_auto_scroll_timer = gobject.timeout_add(150, |
3378 | - self.iv_auto_scroll) |
3379 | - elif self.iv_auto_scroll_timer: |
3380 | - gobject.source_remove(self.iv_auto_scroll_timer) |
3381 | - self.iv_auto_scroll_timer = None |
3382 | - |
3383 | - # ======================================================= |
3384 | - def iv_dnd_leave_end(self, widget, context, ignored=None): |
3385 | - """Ends the auto-scroll during DND""" |
3386 | - |
3387 | - if self.iv_auto_scroll_timer: |
3388 | - gobject.source_remove(self.iv_auto_scroll_timer) |
3389 | - self.iv_auto_scroll_timer = None |
3390 | - |
3391 | - # ======================================================= |
3392 | - def iv_auto_scroll(self): |
3393 | - """Timeout routine for auto-scroll""" |
3394 | - |
3395 | - sw_vadj = self.sw.get_vadjustment() |
3396 | - sw_vpos = sw_vadj.get_value() |
3397 | - if self.iv_auto_scroll_direction == gtk.DIR_UP: |
3398 | - sw_vpos -= sw_vadj.step_increment |
3399 | - sw_vadj.set_value( max(sw_vpos, sw_vadj.lower) ) |
3400 | - elif self.iv_auto_scroll_direction == gtk.DIR_DOWN: |
3401 | - sw_vpos += sw_vadj.step_increment |
3402 | - sw_vadj.set_value( min(sw_vpos, sw_vadj.upper - sw_vadj.page_size) ) |
3403 | - return True #call me again |
3404 | - |
3405 | - # ======================================================= |
3406 | - def iv_button_press_event(self, iconview, event): |
3407 | - """Manages mouse clicks on the iconview""" |
3408 | - |
3409 | - if event.button == 3: |
3410 | - x = int(event.x) |
3411 | - y = int(event.y) |
3412 | - time = event.time |
3413 | - path = iconview.get_path_at_pos(x, y) |
3414 | - selection = iconview.get_selected_items() |
3415 | - if path: |
3416 | - if path not in selection: |
3417 | - iconview.unselect_all() |
3418 | - iconview.select_path(path) |
3419 | - iconview.grab_focus() |
3420 | - self.popup.popup( None, None, None, event.button, time) |
3421 | - return 1 |
3422 | - |
3423 | - # ======================================================= |
3424 | - def sw_dnd_received_data(self, scrolledwindow, context, x, y, |
3425 | - selection_data, target_id, etime): |
3426 | - """Handles received data by drag and drop in scrolledwindow""" |
3427 | - |
3428 | - data = selection_data.data |
3429 | - if target_id == self.MODEL_ROW_EXTERN: |
3430 | - self.model |
3431 | - if data: |
3432 | - data = data.split('\n;\n') |
3433 | - while data: |
3434 | - tmp = data.pop(0).split('\n') |
3435 | - filename = tmp[0] |
3436 | - npage, angle = [int(k) for k in tmp[1:3]] |
3437 | - crop = [float(side) for side in tmp[3:7]] |
3438 | - if self.add_pdf_pages(filename, npage, npage, angle, crop): |
3439 | - if context.action == gtk.gdk.ACTION_MOVE: |
3440 | - context.finish(True, True, etime) |
3441 | - elif target_id == self.TEXT_URI_LIST: |
3442 | - uri = data.strip() |
3443 | - uri_splitted = uri.split() # we may have more than one file dropped |
3444 | - for uri in uri_splitted: |
3445 | - filename = self.get_file_path_from_dnd_dropped_uri(uri) |
3446 | - if os.path.isfile(filename): # is it file? |
3447 | - self.add_pdf_pages(filename) |
3448 | - |
3449 | - # ======================================================= |
3450 | - def sw_button_press_event(self, scrolledwindow, event): |
3451 | - """Unselects all items in iconview on mouse click in scrolledwindow""" |
3452 | - |
3453 | - if event.button == 1: |
3454 | - self.iconview.unselect_all() |
3455 | - |
3456 | - # ======================================================= |
3457 | - def get_file_path_from_dnd_dropped_uri(self, uri): |
3458 | - """Extracts the path from an uri""" |
3459 | - |
3460 | - path = urllib.url2pathname(uri) # escape special chars |
3461 | - path = path.strip('\r\n\x00') # remove \r\n and NULL |
3462 | - |
3463 | - # get the path to file |
3464 | - if path.startswith('file:\\\\\\'): # windows |
3465 | - path = path[8:] # 8 is len('file:///') |
3466 | - elif path.startswith('file://'): # nautilus, rox |
3467 | - path = path[7:] # 7 is len('file://') |
3468 | - elif path.startswith('file:'): # xffm |
3469 | - path = path[5:] # 5 is len('file:') |
3470 | - return path |
3471 | - |
3472 | - # ======================================================= |
3473 | - def rotate_page_right(self, widget, data=None): |
3474 | - self.rotate_page(90) |
3475 | - |
3476 | - def rotate_page_left(self, widget, data=None): |
3477 | - self.rotate_page(-90) |
3478 | - |
3479 | - def rotate_page(self, angle): |
3480 | - """Rotates the selected page in the IconView""" |
3481 | - |
3482 | - model = self.iconview.get_model() |
3483 | - selection = self.iconview.get_selected_items() |
3484 | - for path in selection: |
3485 | - iter = model.get_iter(path) |
3486 | - nfile = model.get_value(iter, 2) |
3487 | - npage = model.get_value(iter, 3) |
3488 | - |
3489 | - rotate_times = ( ( (-angle) % 360 + 45 ) / 90 ) % 4 |
3490 | - crop = [0.,0.,0.,0.] |
3491 | - if rotate_times is not 0: |
3492 | - perm = [0,2,1,3] |
3493 | - for it in range(rotate_times): |
3494 | - perm.append(perm.pop(0)) |
3495 | - perm.insert(1,perm.pop(2)) |
3496 | - crop = [model.get_value(iter, 8 + perm[side]) for side in range(4)] |
3497 | - for side in range(4): |
3498 | - model.set_value(iter, 8 + side, crop[side]) |
3499 | - |
3500 | - new_angle = model.get_value(iter, 7) + angle |
3501 | - model.set_value(iter, 7, new_angle) |
3502 | - model.set_value(iter, 6, False) #rendering request |
3503 | - |
3504 | - self.render() |
3505 | - |
3506 | - # ======================================================= |
3507 | - def crop_page_dialog(self, widget): |
3508 | - """Opens a dialog box to define margins for page cropping""" |
3509 | - |
3510 | - sides = ('L', 'R', 'T', 'B') |
3511 | - side_names = {'L':_('Left'), 'R':_('Right'), |
3512 | - 'T':_('Top'), 'B':_('Bottom') } |
3513 | - opposite_sides = {'L':'R', 'R':'L', 'T':'B', 'B':'T' } |
3514 | - |
3515 | - def set_crop_value(spinbutton, side): |
3516 | - opp_side = opposite_sides[side] |
3517 | - pos = sides.index(opp_side) |
3518 | - adj = spin_list[pos].get_adjustment() |
3519 | - adj.set_upper(99.0 - spinbutton.get_value()) |
3520 | - |
3521 | - model = self.iconview.get_model() |
3522 | - selection = self.iconview.get_selected_items() |
3523 | - |
3524 | - crop = [0.,0.,0.,0.] |
3525 | - if selection: |
3526 | - path = selection[0] |
3527 | - pos = model.get_iter(path) |
3528 | - crop = [model.get_value(pos, 8 + side) for side in range(4)] |
3529 | - |
3530 | - dialog = gtk.Dialog(title=(_('Crop Selected Page(s)')), |
3531 | - parent=self.window, |
3532 | - flags=gtk.DIALOG_MODAL, |
3533 | - buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, |
3534 | - gtk.STOCK_OK, gtk.RESPONSE_OK)) |
3535 | - dialog.set_size_request(340, 250) |
3536 | - dialog.set_default_response(gtk.RESPONSE_OK) |
3537 | - |
3538 | - frame = gtk.Frame(_('Crop Margins')) |
3539 | - dialog.vbox.pack_start(frame, False, False, 20) |
3540 | - |
3541 | - vbox = gtk.VBox(False, 0) |
3542 | - frame.add(vbox) |
3543 | - |
3544 | - spin_list = [] |
3545 | - units = 2 * [_('% of width')] + 2 * [_('% of height')] |
3546 | - for side in sides: |
3547 | - hbox = gtk.HBox(True, 0) |
3548 | - vbox.pack_start(hbox, False, False, 5) |
3549 | - |
3550 | - label = gtk.Label(side_names[side]) |
3551 | - label.set_alignment(0, 0.0) |
3552 | - hbox.pack_start(label, True, True, 20) |
3553 | - |
3554 | - adj = gtk.Adjustment(100.*crop.pop(0), 0.0, 99.0, 1.0, 5.0, 0.0) |
3555 | - spin = gtk.SpinButton(adj, 0, 1) |
3556 | - spin.set_activates_default(True) |
3557 | - spin.connect('value-changed', set_crop_value, side) |
3558 | - spin_list.append(spin) |
3559 | - hbox.pack_start(spin, False, False, 30) |
3560 | - |
3561 | - label = gtk.Label(units.pop(0)) |
3562 | - label.set_alignment(0, 0.0) |
3563 | - hbox.pack_start(label, True, True, 0) |
3564 | - |
3565 | - dialog.show_all() |
3566 | - result = dialog.run() |
3567 | - |
3568 | - if result == gtk.RESPONSE_OK: |
3569 | - crop = [] |
3570 | - for spin in spin_list: |
3571 | - crop.append( spin.get_value()/100. ) |
3572 | - for path in selection: |
3573 | - pos = model.get_iter(path) |
3574 | - for it in range(4): |
3575 | - model.set_value(pos, 8 + it, crop[it]) |
3576 | - model.set_value(pos, 6, False) #rendering request |
3577 | - self.render() |
3578 | - elif result == gtk.RESPONSE_CANCEL: |
3579 | - print(_('Dialog closed')) |
3580 | - dialog.destroy() |
3581 | - |
3582 | - # ======================================================= |
3583 | - def about_dialog(self, widget, data=None): |
3584 | - about_dialog = gtk.AboutDialog() |
3585 | - try: |
3586 | - about_dialog.set_transient_for(self.window) |
3587 | - about_dialog.set_modal(True) |
3588 | - except: |
3589 | - pass |
3590 | - # FIXME |
3591 | - about_dialog.set_name('PDF-Shuffler') |
3592 | - about_dialog.set_version('0.5.1') |
3593 | - about_dialog.set_comments(_( |
3594 | - 'PDF-Shuffler is a simple pyGTK utility which lets you merge, \ |
3595 | -split and rearrange PDF documents. You can also rotate and crop individual \ |
3596 | -pages of a pdf document.')) |
3597 | - about_dialog.set_authors(['Konstantinos Poulios',]) |
3598 | - about_dialog.set_website_label('http://pdfshuffler.sourceforge.net/') |
3599 | - about_dialog.set_logo_icon_name('pdfshuffler') |
3600 | - about_dialog.connect('response', lambda w, a: about_dialog.destroy()) |
3601 | - about_dialog.connect('delete_event', lambda w, a: about_dialog.destroy()) |
3602 | - about_dialog.show_all() |
3603 | - |
3604 | -# ======================================================= |
3605 | -class PDF_Doc: |
3606 | - """Class handling pdf documents""" |
3607 | - |
3608 | - def __init__(self, filename, nfile, tmp_dir): |
3609 | - |
3610 | - self.filename = os.path.abspath(filename) |
3611 | - (self.path, self.shortname) = os.path.split(self.filename) |
3612 | - (self.shortname, self.ext) = os.path.splitext(self.shortname) |
3613 | - f = gio.File(filename) |
3614 | - mime_type = f.query_info('standard::content-type').get_content_type() |
3615 | - if mime_type == 'application/pdf': |
3616 | - self.nfile = nfile + 1 |
3617 | - self.mtime = os.path.getmtime(filename) |
3618 | - self.copyname = os.path.join(tmp_dir, '%02d_' % self.nfile + |
3619 | - self.shortname + '.pdf') |
3620 | - shutil.copy(self.filename, self.copyname) |
3621 | - self.document = poppler.document_new_from_file("file://" + self.copyname, None) |
3622 | - self.npage = self.document.get_n_pages() |
3623 | - else: |
3624 | - self.nfile = 0 |
3625 | - |
3626 | - |
3627 | -# ======================================================= |
3628 | -class PDF_Renderer(threading.Thread,gobject.GObject): |
3629 | - |
3630 | - def __init__(self, model, pdfqueue, scale=1., width=100): |
3631 | - threading.Thread.__init__(self) |
3632 | - gobject.GObject.__init__(self) |
3633 | - self.model = model |
3634 | - self.scale = scale |
3635 | - self.default_width = width |
3636 | - self.pdfqueue = pdfqueue |
3637 | - self.quit = False |
3638 | - self.evnt = threading.Event() |
3639 | - self.paused = False |
3640 | - |
3641 | - def run(self): |
3642 | - while not self.quit: |
3643 | - rendered_all = True |
3644 | - for row in self.model: |
3645 | - if self.quit: |
3646 | - break |
3647 | - if not row[6]: |
3648 | - rendered_all = False |
3649 | - gtk.gdk.threads_enter() |
3650 | - try: |
3651 | - nfile = row[2] |
3652 | - npage = row[3] |
3653 | - angle = row[7] |
3654 | - crop = [row[8],row[9],row[10],row[11]] |
3655 | - pdfdoc = self.pdfqueue[nfile - 1] |
3656 | - thumbnail = self.load_pdf_thumbnail(pdfdoc, npage, angle, crop) |
3657 | - row[6] = True |
3658 | - row[4] = thumbnail.get_width() |
3659 | - row[1] = thumbnail |
3660 | - finally: |
3661 | - gtk.gdk.threads_leave() |
3662 | - if rendered_all: |
3663 | - if self.model.get_iter_first(): #just checking if model isn't empty |
3664 | - self.emit('reset_iv_width') |
3665 | - self.paused = True |
3666 | - self.evnt.wait() |
3667 | - |
3668 | - # ======================================================= |
3669 | - def load_pdf_thumbnail(self, pdfdoc, npage, rotation=0, crop=[0.,0.,0.,0.]): |
3670 | - """Create pdf pixbuf""" |
3671 | - |
3672 | - page = pdfdoc.document.get_page(npage-1) |
3673 | - try: |
3674 | - pix_w, pix_h = page.get_size() |
3675 | - pix_w = int(pix_w * self.scale) |
3676 | - pix_h = int(pix_h * self.scale) |
3677 | - thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, |
3678 | - 8, pix_w , pix_h) |
3679 | - page.render_to_pixbuf(0,0,pix_w,pix_h,self.scale,0,thumbnail) |
3680 | - rotation = (-rotation) % 360 |
3681 | - rotation = ((rotation + 45) / 90) * 90 |
3682 | - thumbnail = thumbnail.rotate_simple(rotation) |
3683 | - pix_w = thumbnail.get_width() |
3684 | - pix_h = thumbnail.get_height() |
3685 | - if crop != [0.,0.,0.,0.]: |
3686 | - src_x = int( crop[0] * pix_w ) |
3687 | - src_y = int( crop[2] * pix_h ) |
3688 | - width = int( (1. - crop[0] - crop[1]) * pix_w ) |
3689 | - height = int( (1. - crop[2] - crop[3]) * pix_h ) |
3690 | - new_thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, |
3691 | - 8, width, height) |
3692 | - thumbnail.copy_area(src_x, src_y, width, height, |
3693 | - new_thumbnail, 0, 0) |
3694 | - thumbnail = new_thumbnail |
3695 | - pix_w = thumbnail.get_width() |
3696 | - pix_h = thumbnail.get_height() |
3697 | - except: |
3698 | - pix_w = self.default_width |
3699 | - pix_h = pix_w |
3700 | - thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False, |
3701 | - 8, pix_w, pix_h) |
3702 | - pixbuf.fill(0xffffffff) |
3703 | - |
3704 | - #add border |
3705 | - thickness = 3 |
3706 | - color = 0x000000FF |
3707 | - canvas = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8, |
3708 | - pix_w + thickness + 1, |
3709 | - pix_h + thickness + 1) |
3710 | - canvas.fill(color) |
3711 | - thumbnail.copy_area(0, 0, pix_w, pix_h, canvas, 1, 1) |
3712 | - thumbnail = canvas |
3713 | - |
3714 | - return thumbnail |
3715 | - |
3716 | - |
3717 | -# ======================================================= |
3718 | -if __name__ == '__main__': |
3719 | - PDFshuffler() |
3720 | - gtk.gdk.threads_enter() |
3721 | - gtk.main() |
3722 | - gtk.gdk.threads_leave() |
3723 | - |
3724 | |
3725 | === added file 'pdfshuffler/__init__.py' |
3726 | === added file 'pdfshuffler/cairorendering.py' |
3727 | --- pdfshuffler/cairorendering.py 1970-01-01 00:00:00 +0000 |
3728 | +++ pdfshuffler/cairorendering.py 2012-02-12 00:35:23 +0000 |
3729 | @@ -0,0 +1,168 @@ |
3730 | +#!/usr/bin/python |
3731 | +# -*- coding: utf-8 -*- |
3732 | + |
3733 | +""" |
3734 | + |
3735 | + PdfShuffler 0.6.0 - GTK+ based utility for splitting, rearrangement and |
3736 | + modification of PDF documents. |
3737 | + Copyright (C) 2008-2011 Konstantinos Poulios |
3738 | + <https://sourceforge.net/projects/pdfshuffler> |
3739 | + |
3740 | + This file is part of PdfShuffler. |
3741 | + |
3742 | + PdfShuffler is free software; you can redistribute it and/or modify |
3743 | + it under the terms of the GNU General Public License as published by |
3744 | + the Free Software Foundation; either version 3 of the License, or |
3745 | + (at your option) any later version. |
3746 | + |
3747 | + This program is distributed in the hope that it will be useful, |
3748 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
3749 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3750 | + GNU General Public License for more details. |
3751 | + |
3752 | + You should have received a copy of the GNU General Public License along |
3753 | + with this program; if not, write to the Free Software Foundation, Inc., |
3754 | + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
3755 | + |
3756 | +""" |
3757 | + |
3758 | +import gtk |
3759 | +import gobject |
3760 | +import cairo |
3761 | + |
3762 | +from math import pi as M_PI |
3763 | + |
3764 | +class CellRendererImage(gtk.GenericCellRenderer): |
3765 | + __gproperties__ = { |
3766 | + "image": (gobject.TYPE_OBJECT, "Image", "Image", |
3767 | + gobject.PARAM_READWRITE), |
3768 | + "width": (gobject.TYPE_DOUBLE, "Width", "Width", |
3769 | + 0., 1.e4, 0., gobject.PARAM_READWRITE), |
3770 | + "height": (gobject.TYPE_DOUBLE, "Height", "Height", |
3771 | + 0., 1.e4, 0., gobject.PARAM_READWRITE), |
3772 | + "rotation": (gobject.TYPE_INT, "Rotation", "Rotation", |
3773 | + 0, 360, 0, gobject.PARAM_READWRITE), |
3774 | + "scale": (gobject.TYPE_DOUBLE, "Scale", "Scale", |
3775 | + 0.01, 100., 1., gobject.PARAM_READWRITE), |
3776 | + "cropL": (gobject.TYPE_DOUBLE, "CropL", "CropL", |
3777 | + 0., 1., 0., gobject.PARAM_READWRITE), |
3778 | + "cropR": (gobject.TYPE_DOUBLE, "CropR", "CropR", |
3779 | + 0., 1., 0., gobject.PARAM_READWRITE), |
3780 | + "cropT": (gobject.TYPE_DOUBLE, "CropT", "CropT", |
3781 | + 0., 1., 0., gobject.PARAM_READWRITE), |
3782 | + "cropB": (gobject.TYPE_DOUBLE, "CropB", "CropB", |
3783 | + 0., 1., 0., gobject.PARAM_READWRITE), |
3784 | + } |
3785 | + |
3786 | + def __init__(self): |
3787 | + self.__gobject_init__() |
3788 | + self.th1 = 2. # border thickness |
3789 | + self.th2 = 3. # shadow thickness |
3790 | + |
3791 | + def get_geometry(self): |
3792 | + |
3793 | + rotation = int(self.rotation) % 360 |
3794 | + rotation = ((rotation + 45) / 90) * 90 |
3795 | + if not self.image.surface: |
3796 | + w0 = w1 = self.width |
3797 | + h0 = h1 = self.height |
3798 | + else: |
3799 | + w0 = self.image.surface.get_width() |
3800 | + h0 = self.image.surface.get_height() |
3801 | + if rotation == 90 or rotation == 270: |
3802 | + w1, h1 = h0, w0 |
3803 | + else: |
3804 | + w1, h1 = w0, h0 |
3805 | + |
3806 | + x = self.cropL * w1 |
3807 | + y = self.cropT * h1 |
3808 | + |
3809 | + w2 = int(self.scale * (1. - self.cropL - self.cropR) * w1) |
3810 | + h2 = int(self.scale * (1. - self.cropT - self.cropB) * h1) |
3811 | + |
3812 | + return w0,h0,w1,h1,w2,h2,rotation |
3813 | + |
3814 | + def do_set_property(self, pspec, value): |
3815 | + setattr(self, pspec.name, value) |
3816 | + |
3817 | + def do_get_property(self, pspec): |
3818 | + return getattr(self, pspec.name) |
3819 | + |
3820 | + def on_render(self, window, widget, background_area, cell_area, \ |
3821 | + expose_area, flags): |
3822 | + if not self.image.surface: |
3823 | + return |
3824 | + |
3825 | + w0,h0,w1,h1,w2,h2,rotation = self.get_geometry() |
3826 | + th = int(2*self.th1+self.th2) |
3827 | + w = w2 + th |
3828 | + h = h2 + th |
3829 | + |
3830 | + x = cell_area.x |
3831 | + y = cell_area.y |
3832 | + if cell_area and w > 0 and h > 0: |
3833 | + x += self.get_property('xalign') * \ |
3834 | + (cell_area.width - w - self.get_property('xpad')) |
3835 | + y += self.get_property('yalign') * \ |
3836 | + (cell_area.height - h - self.get_property('ypad')) |
3837 | + |
3838 | + cr = window.cairo_create() |
3839 | + cr.translate(x,y) |
3840 | + |
3841 | + x = self.cropL * w1 |
3842 | + y = self.cropT * h1 |
3843 | + |
3844 | + #shadow |
3845 | + cr.set_source_rgb(0.5, 0.5, 0.5) |
3846 | + cr.rectangle(th, th, w2, h2) |
3847 | + cr.fill() |
3848 | + |
3849 | + #border |
3850 | + cr.set_source_rgb(0, 0, 0) |
3851 | + cr.rectangle(0, 0, w2+2*self.th1, h2+2*self.th1) |
3852 | + cr.fill() |
3853 | + |
3854 | + #image |
3855 | + cr.set_source_rgb(1, 1, 1) |
3856 | + cr.rectangle(self.th1, self.th1, w2, h2) |
3857 | + cr.fill_preserve() |
3858 | + cr.clip() |
3859 | + |
3860 | + cr.translate(self.th1,self.th1) |
3861 | + cr.scale(self.scale, self.scale) |
3862 | + cr.translate(-x,-y) |
3863 | + if rotation > 0: |
3864 | + cr.translate(w1/2,h1/2) |
3865 | + cr.rotate(rotation * M_PI / 180) |
3866 | + cr.translate(-w0/2,-h0/2) |
3867 | + |
3868 | + cr.set_source_surface(self.image.surface) |
3869 | + cr.paint() |
3870 | + |
3871 | + def on_get_size(self, widget, cell_area=None): |
3872 | + x = y = 0 |
3873 | + w0,h0,w1,h1,w2,h2,rotation = self.get_geometry() |
3874 | + th = int(2*self.th1+self.th2) |
3875 | + w = w2 + th |
3876 | + h = h2 + th |
3877 | + |
3878 | + if cell_area and w > 0 and h > 0: |
3879 | + x = self.get_property('xalign') * \ |
3880 | + (cell_area.width - w - self.get_property('xpad')) |
3881 | + y = self.get_property('yalign') * \ |
3882 | + (cell_area.height - h - self.get_property('ypad')) |
3883 | + w += 2 * self.get_property('xpad') |
3884 | + h += 2 * self.get_property('ypad') |
3885 | + return int(x), int(y), w, h |
3886 | + |
3887 | + |
3888 | +class CairoImage(gobject.GObject): |
3889 | + |
3890 | + def __init__(self, width=0, height=0): |
3891 | + gobject.GObject.__init__(self) |
3892 | + if width > 0 and height > 0: |
3893 | + self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) |
3894 | + else: |
3895 | + self.surface = None |
3896 | + |
3897 | + |
3898 | |
3899 | === added file 'pdfshuffler/pdfshuffler.py' |
3900 | --- pdfshuffler/pdfshuffler.py 1970-01-01 00:00:00 +0000 |
3901 | +++ pdfshuffler/pdfshuffler.py 2012-02-12 00:35:23 +0000 |
3902 | @@ -0,0 +1,1057 @@ |
3903 | +#!/usr/bin/python |
3904 | +# -*- coding: utf-8 -*- |
3905 | + |
3906 | +""" |
3907 | + |
3908 | + PdfShuffler 0.6.0 - GTK+ based utility for splitting, rearrangement and |
3909 | + modification of PDF documents. |
3910 | + Copyright (C) 2008-2011 Konstantinos Poulios |
3911 | + <https://sourceforge.net/projects/pdfshuffler> |
3912 | + |
3913 | + This file is part of PdfShuffler. |
3914 | + |
3915 | + PdfShuffler is free software; you can redistribute it and/or modify |
3916 | + it under the terms of the GNU General Public License as published by |
3917 | + the Free Software Foundation; either version 3 of the License, or |
3918 | + (at your option) any later version. |
3919 | + |
3920 | + This program is distributed in the hope that it will be useful, |
3921 | + but WITHOUT ANY WARRANTY; without even the implied warranty of |
3922 | + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
3923 | + GNU General Public License for more details. |
3924 | + |
3925 | + You should have received a copy of the GNU General Public License along |
3926 | + with this program; if not, write to the Free Software Foundation, Inc., |
3927 | + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
3928 | + |
3929 | +""" |
3930 | + |
3931 | +import os |
3932 | +import shutil #needed for file operations like whole directory deletion |
3933 | +import sys #needed for proccessing of command line args |
3934 | +import urllib #needed to parse filename information passed by DnD |
3935 | +import threading |
3936 | +import tempfile |
3937 | +from copy import copy |
3938 | + |
3939 | +import locale #for multilanguage support |
3940 | +import gettext |
3941 | +gettext.install('pdfshuffler', unicode=1) |
3942 | + |
3943 | + |
3944 | +APPNAME = 'PdfShuffler' # PDF-Shuffler, PDFShuffler, pdfshuffler |
3945 | +VERSION = '0.6.0' |
3946 | +WEBSITE = 'http://pdfshuffler.sourceforge.net/' |
3947 | +LICENSE = 'GNU General Public License (GPL) Version 3.' |
3948 | + |
3949 | +try: |
3950 | + import pygtk |
3951 | + pygtk.require('2.0') |
3952 | + import gtk |
3953 | + assert gtk.gtk_version >= (2, 10, 0) |
3954 | + assert gtk.pygtk_version >= (2, 10, 0) |
3955 | +except AssertionError: |
3956 | + print('You do not have the required versions of GTK+ and PyGTK ' + |
3957 | + 'installed.\n\n' + |
3958 | + 'Installed GTK+ version is ' + |
3959 | + '.'.join([str(n) for n in gtk.gtk_version]) + '\n' + |
3960 | + 'Required GTK+ version is 2.10.0 or higher\n\n' |
3961 | + 'Installed PyGTK version is ' + |
3962 | + '.'.join([str(n) for n in gtk.pygtk_version]) + '\n' + |
3963 | + 'Required PyGTK version is 2.10.0 or higher') |
3964 | + sys.exit(1) |
3965 | +except: |
3966 | + print('PyGTK version 2.10.0 or higher is required to run this program.') |
3967 | + print('No version of PyGTK was found on your system.') |
3968 | + sys.exit(1) |
3969 | + |
3970 | +import gobject #to use custom signals |
3971 | +import pango #to adjust the text alignment in CellRendererText |
3972 | +import gio #to inquire mime types information |
3973 | +import cairo |
3974 | + |
3975 | +import poppler #for the rendering of pdf pages |
3976 | +from pyPdf import PdfFileWriter, PdfFileReader |
3977 | + |
3978 | +from cairorendering import CellRendererImage, CairoImage |
3979 | +gobject.type_register(CellRendererImage) |
3980 | + |
3981 | +import time |
3982 | + |
3983 | +class PdfShuffler: |
3984 | + prefs = { |
3985 | + 'window width': min(700, gtk.gdk.screen_get_default().get_width() / 2), |
3986 | + 'window height': min(600, gtk.gdk.screen_get_default().get_height() - 50), |
3987 | + 'window x': 0, |
3988 | + 'window y': 0, |
3989 | + 'initial thumbnail size': 300, |
3990 | + 'initial zoom level': -14, |
3991 | + } |
3992 | + |
3993 | + MODEL_ROW_INTERN = 1001 |
3994 | + MODEL_ROW_EXTERN = 1002 |
3995 | + TEXT_URI_LIST = 1003 |
3996 | + MODEL_ROW_MOTION = 1004 |
3997 | + TARGETS_IV = [('MODEL_ROW_INTERN', gtk.TARGET_SAME_WIDGET, MODEL_ROW_INTERN), |
3998 | + ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN), |
3999 | + ('MODEL_ROW_MOTION', 0, MODEL_ROW_MOTION)] |
4000 | + TARGETS_SW = [('text/uri-list', 0, TEXT_URI_LIST), |
4001 | + ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN)] |
4002 | + |
4003 | + def __init__(self): |
4004 | + # Create the temporary directory |
4005 | + self.tmp_dir = tempfile.mkdtemp("pdfshuffler") |
4006 | + os.chmod(self.tmp_dir, 0700) |
4007 | + |
4008 | + icon_theme = gtk.icon_theme_get_default() |
4009 | + try: |
4010 | + gtk.window_set_default_icon(icon_theme.load_icon("pdfshuffler", 64, 0)) |
4011 | + except: |
4012 | + print(_("Can't load icon. Application is not installed correctly.")) |
4013 | + |
4014 | + # Import the user interface file, trying different possible locations |
4015 | + ui_path = '/usr/share/pdfshuffler/pdfshuffler.ui' |
4016 | + if not os.path.exists(ui_path): |
4017 | + ui_path = '/usr/local/share/pdfshuffler/pdfshuffler.ui' |
4018 | + if not os.path.exists(ui_path): |
4019 | + parent_dir = os.path.dirname( \ |
4020 | + os.path.dirname(os.path.realpath(__file__))) |
4021 | + ui_path = os.path.join(parent_dir, 'data', 'pdfshuffler.ui') |
4022 | + self.uiXML = gtk.Builder() |
4023 | + self.uiXML.add_from_file(ui_path) |
4024 | + self.uiXML.connect_signals(self) |
4025 | + |
4026 | + # Create the main window, and attach delete_event signal to terminating |
4027 | + # the application |
4028 | + self.window = self.uiXML.get_object('main_window') |
4029 | + self.window.set_title(APPNAME) |
4030 | + self.window.set_border_width(0) |
4031 | + self.window.move(self.prefs['window x'], self.prefs['window y']) |
4032 | + self.window.set_default_size(self.prefs['window width'], |
4033 | + self.prefs['window height']) |
4034 | + self.window.connect('delete_event', self.close_application) |
4035 | + self.window.show_all() |
4036 | + |
4037 | + # Create a scrolled window to hold the thumbnails-container |
4038 | + self.sw = self.uiXML.get_object('scrolledwindow') |
4039 | + self.sw.drag_dest_set(gtk.DEST_DEFAULT_MOTION | |
4040 | + gtk.DEST_DEFAULT_HIGHLIGHT | |
4041 | + gtk.DEST_DEFAULT_DROP | |
4042 | + gtk.DEST_DEFAULT_MOTION, |
4043 | + self.TARGETS_SW, |
4044 | + gtk.gdk.ACTION_COPY | |
4045 | + gtk.gdk.ACTION_MOVE) |
4046 | + self.sw.connect('drag_data_received', self.sw_dnd_received_data) |
4047 | + self.sw.connect('button_press_event', self.sw_button_press_event) |
4048 | + self.sw.connect('scroll_event', self.sw_scroll_event) |
4049 | + |
4050 | + # Create an alignment to keep the thumbnails center-aligned |
4051 | + align = gtk.Alignment(0.5, 0.5, 0, 0) |
4052 | + self.sw.add_with_viewport(align) |
4053 | + |
4054 | + # Create ListStore model and IconView |
4055 | + self.model = gtk.ListStore(str, # 0.Text descriptor |
4056 | + CairoImage, # 1.Cached page image |
4057 | + int, # 2.Document number |
4058 | + int, # 3.Page number |
4059 | + float, # 4.Scale |
4060 | + str, # 5.Document filename |
4061 | + int, # 6.Rotation angle |
4062 | + float, # 7.Crop left |
4063 | + float, # 8.Crop right |
4064 | + float, # 9.Crop top |
4065 | + float, # 10.Crop bottom |
4066 | + int, # 11.Page width |
4067 | + int) # 12.Page height |
4068 | + |
4069 | + self.zoom_set(self.prefs['initial zoom level']) |
4070 | + self.iv_col_width = self.prefs['initial thumbnail size'] |
4071 | + |
4072 | + self.iconview = gtk.IconView(self.model) |
4073 | + self.iconview.set_item_width(self.iv_col_width + 12) |
4074 | + |
4075 | + self.cellthmb = CellRendererImage() |
4076 | + self.iconview.pack_start(self.cellthmb, False) |
4077 | + self.iconview.set_attributes(self.cellthmb, image=1, |
4078 | + scale=4, rotation=6, cropL=7, cropR=8, cropT=9, cropB=10, |
4079 | + width=11, height=12) |
4080 | + |
4081 | +# self.iconview.set_text_column(0) |
4082 | + self.celltxt = gtk.CellRendererText() |
4083 | + self.celltxt.set_property('width', self.iv_col_width) |
4084 | + self.celltxt.set_property('wrap-width', self.iv_col_width) |
4085 | + self.celltxt.set_property('alignment', pango.ALIGN_CENTER) |
4086 | + self.iconview.pack_start(self.celltxt, False) |
4087 | + self.iconview.set_attributes(self.celltxt, text=0) |
4088 | + |
4089 | + self.iconview.set_selection_mode(gtk.SELECTION_MULTIPLE) |
4090 | + self.iconview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, |
4091 | + self.TARGETS_IV, |
4092 | + gtk.gdk.ACTION_COPY | |
4093 | + gtk.gdk.ACTION_MOVE) |
4094 | + self.iconview.enable_model_drag_dest(self.TARGETS_IV, |
4095 | + gtk.gdk.ACTION_DEFAULT) |
4096 | + self.iconview.connect('drag_begin', self.iv_drag_begin) |
4097 | + self.iconview.connect('drag_data_get', self.iv_dnd_get_data) |
4098 | + self.iconview.connect('drag_data_received', self.iv_dnd_received_data) |
4099 | + self.iconview.connect('drag_data_delete', self.iv_dnd_data_delete) |
4100 | + self.iconview.connect('drag_motion', self.iv_dnd_motion) |
4101 | + self.iconview.connect('drag_leave', self.iv_dnd_leave_end) |
4102 | + self.iconview.connect('drag_end', self.iv_dnd_leave_end) |
4103 | + self.iconview.connect('button_press_event', self.iv_button_press_event) |
4104 | + self.iv_auto_scroll_direction = 0 |
4105 | + |
4106 | + style = self.iconview.get_style().copy() |
4107 | + style_sw = self.sw.get_style() |
4108 | + for state in (gtk.STATE_NORMAL, gtk.STATE_PRELIGHT, gtk.STATE_ACTIVE): |
4109 | + style.base[state] = style_sw.bg[gtk.STATE_NORMAL] |
4110 | + self.iconview.set_style(style) |
4111 | + |
4112 | + align.add(self.iconview) |
4113 | + |
4114 | + # Progress bar |
4115 | + self.progress_bar = self.uiXML.get_object('progressbar') |
4116 | + self.progress_bar_timeout_id = 0 |
4117 | + |
4118 | + # Define window callback function and show window |
4119 | + self.window.connect('size_allocate', self.on_window_size_request) # resize |
4120 | + self.window.connect('key_press_event', self.on_keypress_event ) # keypress |
4121 | + self.window.show_all() |
4122 | + self.progress_bar.hide_all() |
4123 | + |
4124 | + # Creating the popup menu |
4125 | + self.popup = gtk.Menu() |
4126 | + popup_rotate_right = gtk.ImageMenuItem(_('_Rotate Right')) |
4127 | + popup_rotate_left = gtk.ImageMenuItem(_('Rotate _Left')) |
4128 | + popup_crop = gtk.MenuItem(_('C_rop...')) |
4129 | + popup_delete = gtk.ImageMenuItem(gtk.STOCK_DELETE) |
4130 | + popup_rotate_right.connect('activate', self.rotate_page_right) |
4131 | + popup_rotate_left.connect('activate', self.rotate_page_left) |
4132 | + popup_crop.connect('activate', self.crop_page_dialog) |
4133 | + popup_delete.connect('activate', self.clear_selected) |
4134 | + popup_rotate_right.show() |
4135 | + popup_rotate_left.show() |
4136 | + popup_crop.show() |
4137 | + popup_delete.show() |
4138 | + self.popup.append(popup_rotate_right) |
4139 | + self.popup.append(popup_rotate_left) |
4140 | + self.popup.append(popup_crop) |
4141 | + self.popup.append(popup_delete) |
4142 | + |
4143 | + # Initializing variables |
4144 | + self.export_directory = os.getenv('HOME') |
4145 | + self.import_directory = self.export_directory |
4146 | + self.nfile = 0 |
4147 | + self.iv_auto_scroll_timer = None |
4148 | + self.pdfqueue = [] |
4149 | + |
4150 | + gobject.type_register(PDF_Renderer) |
4151 | + gobject.signal_new('update_thumbnail', PDF_Renderer, |
4152 | + gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, |
4153 | + [gobject.TYPE_INT, gobject.TYPE_PYOBJECT]) |
4154 | + self.rendering_thread = 0 |
4155 | + |
4156 | + self.set_unsaved(False) |
4157 | + |
4158 | + # Importing documents passed as command line arguments |
4159 | + for filename in sys.argv[1:]: |
4160 | + self.add_pdf_pages(filename) |
4161 | + |
4162 | + def render(self): |
4163 | + if self.rendering_thread: |
4164 | + self.rendering_thread.quit = True |
4165 | + self.rendering_thread = PDF_Renderer(self.model, self.pdfqueue) |
4166 | + self.rendering_thread.connect('update_thumbnail', self.update_thumbnail) |
4167 | + self.rendering_thread.start() |
4168 | + |
4169 | + if self.progress_bar_timeout_id: |
4170 | + gobject.source_remove(self.progress_bar_timeout_id) |
4171 | + self.progress_bar_timout_id = \ |
4172 | + gobject.timeout_add(50, self.progress_bar_timeout) |
4173 | + |
4174 | + def set_unsaved(self, flag): |
4175 | + self.is_unsaved = flag |
4176 | + gobject.idle_add(self.retitle) |
4177 | + |
4178 | + def retitle(self): |
4179 | + title = '' |
4180 | + if len(self.pdfqueue) == 1: |
4181 | + title += self.pdfqueue[0].filename |
4182 | + elif len(self.pdfqueue) == 0: |
4183 | + title += _("No document") |
4184 | + else: |
4185 | + title += _("Several documents") |
4186 | + if self.is_unsaved: |
4187 | + title += '*' |
4188 | + title += ' - ' + APPNAME |
4189 | + self.window.set_title(title) |
4190 | + |
4191 | + def progress_bar_timeout(self): |
4192 | + cnt_finished = 0 |
4193 | + cnt_all = 0 |
4194 | + for row in self.model: |
4195 | + cnt_all += 1 |
4196 | + if row[1].surface: |
4197 | + cnt_finished += 1 |
4198 | + fraction = float(cnt_finished)/float(cnt_all) |
4199 | + |
4200 | + self.progress_bar.set_fraction(fraction) |
4201 | + self.progress_bar.set_text(_('Rendering thumbnails... [%(i1)s/%(i2)s]') |
4202 | + % {'i1' : cnt_finished, 'i2' : cnt_all}) |
4203 | + if fraction >= 0.999: |
4204 | + self.progress_bar.hide_all() |
4205 | + return False |
4206 | + elif not self.progress_bar.flags() & gtk.VISIBLE: |
4207 | + self.progress_bar.show_all() |
4208 | + |
4209 | + return True |
4210 | + |
4211 | + def update_thumbnail(self, object, num, thumbnail): |
4212 | + row = self.model[num] |
4213 | + row[4] = self.zoom_scale |
4214 | + row[1] = thumbnail |
4215 | + |
4216 | + def on_window_size_request(self, window, event): |
4217 | + """Main Window resize - workaround for autosetting of |
4218 | + iconview cols no.""" |
4219 | + |
4220 | + #add 12 because of: http://bugzilla.gnome.org/show_bug.cgi?id=570152 |
4221 | + col_num = 9 * window.get_size()[0] \ |
4222 | + / (10 * (self.iv_col_width + self.iconview.get_column_spacing() * 2)) |
4223 | + self.iconview.set_columns(col_num) |
4224 | + |
4225 | + def update_geometry(self, iter): |
4226 | + """Recomputes the width and height of the rotated page and saves |
4227 | + the result in the ListStore""" |
4228 | + |
4229 | + if not self.model.iter_is_valid(iter): |
4230 | + return |
4231 | + |
4232 | + nfile, npage, rotation = self.model.get(iter, 2, 3, 6) |
4233 | + crop = self.model.get(iter, 7, 8, 9, 10) |
4234 | + page = self.pdfqueue[nfile-1].document.get_page(npage-1) |
4235 | + w0, h0 = page.get_size() |
4236 | + |
4237 | + rotation = int(rotation) % 360 |
4238 | + rotation = ((rotation + 45) / 90) * 90 |
4239 | + if rotation == 90 or rotation == 270: |
4240 | + w1, h1 = h0, w0 |
4241 | + else: |
4242 | + w1, h1 = w0, h0 |
4243 | + |
4244 | + self.model.set(iter, 11, w1, 12, h1) |
4245 | + |
4246 | + def reset_iv_width(self, renderer=None): |
4247 | + """Reconfigures the width of the iconview columns""" |
4248 | + |
4249 | + if not self.model.get_iter_first(): #just checking if model is empty |
4250 | + return |
4251 | + |
4252 | + max_w = 10 + int(max(row[4]*row[11]*(1.-row[7]-row[8]) for row in self.model)) |
4253 | + if max_w != self.iv_col_width: |
4254 | + self.iv_col_width = max_w |
4255 | + self.celltxt.set_property('width', self.iv_col_width) |
4256 | + self.celltxt.set_property('wrap-width', self.iv_col_width) |
4257 | + self.iconview.set_item_width(self.iv_col_width + 12) #-1) |
4258 | + self.on_window_size_request(self.window, None) |
4259 | + |
4260 | + def on_keypress_event(self, widget, event): |
4261 | + """Keypress events in Main Window""" |
4262 | + |
4263 | + #keyname = gtk.gdk.keyval_name(event.keyval) |
4264 | + if event.keyval == 65535: # Delete keystroke |
4265 | + self.clear_selected() |
4266 | + |
4267 | + def close_application(self, widget, event=None, data=None): |
4268 | + """Termination""" |
4269 | + |
4270 | + if self.rendering_thread: |
4271 | + self.rendering_thread.quit = True |
4272 | + while self.rendering_thread.isAlive(): |
4273 | + time.sleep(0.5) |
4274 | + |
4275 | + if os.path.isdir(self.tmp_dir): |
4276 | + shutil.rmtree(self.tmp_dir) |
4277 | + if gtk.main_level(): |
4278 | + gtk.main_quit() |
4279 | + else: |
4280 | + sys.exit(0) |
4281 | + return False |
4282 | + |
4283 | + def add_pdf_pages(self, filename, |
4284 | + firstpage=None, lastpage=None, |
4285 | + angle=0, crop=[0.,0.,0.,0.]): |
4286 | + """Add pages of a pdf document to the model""" |
4287 | + |
4288 | + res = False |
4289 | + # Check if the document has already been loaded |
4290 | + pdfdoc = None |
4291 | + for it_pdfdoc in self.pdfqueue: |
4292 | + if os.path.isfile(it_pdfdoc.filename) and \ |
4293 | + os.path.samefile(filename, it_pdfdoc.filename) and \ |
4294 | + os.path.getmtime(filename) is it_pdfdoc.mtime: |
4295 | + pdfdoc = it_pdfdoc |
4296 | + break |
4297 | + |
4298 | + if not pdfdoc: |
4299 | + pdfdoc = PDF_Doc(filename, self.nfile, self.tmp_dir) |
4300 | + self.import_directory = os.path.split(filename)[0] |
4301 | + self.export_directory = self.import_directory |
4302 | + if pdfdoc.nfile != 0 and pdfdoc != []: |
4303 | + self.nfile = pdfdoc.nfile |
4304 | + self.pdfqueue.append(pdfdoc) |
4305 | + else: |
4306 | + return res |
4307 | + |
4308 | + n_start = 1 |
4309 | + n_end = pdfdoc.npage |
4310 | + if firstpage: |
4311 | + n_start = min(n_end, max(1, firstpage)) |
4312 | + if lastpage: |
4313 | + n_end = max(n_start, min(n_end, lastpage)) |
4314 | + |
4315 | + for npage in range(n_start, n_end + 1): |
4316 | + descriptor = ''.join([pdfdoc.shortname, '\n', _('page'), ' ', str(npage)]) |
4317 | + page = pdfdoc.document.get_page(npage-1) |
4318 | + w, h = page.get_size() |
4319 | + iter = self.model.append((descriptor, # 0 |
4320 | + CairoImage(), # 1 |
4321 | + pdfdoc.nfile, # 2 |
4322 | + npage, # 3 |
4323 | + self.zoom_scale, # 4 |
4324 | + pdfdoc.filename, # 5 |
4325 | + angle, # 6 |
4326 | + crop[0],crop[1], # 7-8 |
4327 | + crop[2],crop[3], # 9-10 |
4328 | + w,h )) # 11-12 |
4329 | + self.update_geometry(iter) |
4330 | + res = True |
4331 | + |
4332 | + self.reset_iv_width() |
4333 | + gobject.idle_add(self.retitle) |
4334 | + if res: |
4335 | + gobject.idle_add(self.render) |
4336 | + return res |
4337 | + |
4338 | + def choose_export_pdf_name(self, widget=None): |
4339 | + """Handles choosing a name for exporting """ |
4340 | + |
4341 | + chooser = gtk.FileChooserDialog(title=_('Export ...'), |
4342 | + action=gtk.FILE_CHOOSER_ACTION_SAVE, |
4343 | + buttons=(gtk.STOCK_CANCEL, |
4344 | + gtk.RESPONSE_CANCEL, |
4345 | + gtk.STOCK_SAVE, |
4346 | + gtk.RESPONSE_OK)) |
4347 | + chooser.set_do_overwrite_confirmation(True) |
4348 | + chooser.set_current_folder(self.export_directory) |
4349 | + filter_pdf = gtk.FileFilter() |
4350 | + filter_pdf.set_name(_('PDF files')) |
4351 | + filter_pdf.add_mime_type('application/pdf') |
4352 | + chooser.add_filter(filter_pdf) |
4353 | + |
4354 | + filter_all = gtk.FileFilter() |
4355 | + filter_all.set_name(_('All files')) |
4356 | + filter_all.add_pattern('*') |
4357 | + chooser.add_filter(filter_all) |
4358 | + |
4359 | + while True: |
4360 | + response = chooser.run() |
4361 | + if response == gtk.RESPONSE_OK: |
4362 | + file_out = chooser.get_filename() |
4363 | + (path, shortname) = os.path.split(file_out) |
4364 | + (shortname, ext) = os.path.splitext(shortname) |
4365 | + if ext.lower() != '.pdf': |
4366 | + file_out = file_out + '.pdf' |
4367 | + try: |
4368 | + self.export_to_file(file_out) |
4369 | + self.export_directory = path |
4370 | + self.set_unsaved(False) |
4371 | + except Exception, e: |
4372 | + chooser.destroy() |
4373 | + error_msg_dlg = gtk.MessageDialog(flags=gtk.DIALOG_MODAL, |
4374 | + type=gtk.MESSAGE_ERROR, |
4375 | + message_format=str(e), |
4376 | + buttons=gtk.BUTTONS_OK) |
4377 | + response = error_msg_dlg.run() |
4378 | + if response == gtk.RESPONSE_OK: |
4379 | + error_msg_dlg.destroy() |
4380 | + return |
4381 | + break |
4382 | + chooser.destroy() |
4383 | + |
4384 | + def export_to_file(self, file_out): |
4385 | + """Export to file""" |
4386 | + |
4387 | + pdf_output = PdfFileWriter() |
4388 | + pdf_input = [] |
4389 | + for pdfdoc in self.pdfqueue: |
4390 | + pdfdoc_inp = PdfFileReader(file(pdfdoc.copyname, 'rb')) |
4391 | + if pdfdoc_inp.getIsEncrypted(): |
4392 | + try: # Workaround for lp:#355479 |
4393 | + stat = pdfdoc_inp.decrypt('') |
4394 | + except: |
4395 | + stat = 0 |
4396 | + if (stat!=1): |
4397 | + errmsg = _('File %s is encrypted.\n' |
4398 | + 'Support for encrypted files has not been implemented yet.\n' |
4399 | + 'File export failed.') % pdfdoc.filename |
4400 | + raise Exception, errmsg |
4401 | + #FIXME |
4402 | + #else |
4403 | + # ask for password and decrypt file |
4404 | + pdf_input.append(pdfdoc_inp) |
4405 | + |
4406 | + for row in self.model: |
4407 | + # add pages from input to output document |
4408 | + nfile = row[2] |
4409 | + npage = row[3] |
4410 | + current_page = copy(pdf_input[nfile-1].getPage(npage-1)) |
4411 | + angle = row[6] |
4412 | + angle0 = current_page.get("/Rotate",0) |
4413 | + crop = [row[7],row[8],row[9],row[10]] |
4414 | + if angle != 0: |
4415 | + current_page.rotateClockwise(angle) |
4416 | + if crop != [0.,0.,0.,0.]: |
4417 | + rotate_times = (((angle + angle0) % 360 + 45) / 90) % 4 |
4418 | + crop_init = crop |
4419 | + if rotate_times != 0: |
4420 | + perm = [0,2,1,3] |
4421 | + for it in range(rotate_times): |
4422 | + perm.append(perm.pop(0)) |
4423 | + perm.insert(1,perm.pop(2)) |
4424 | + crop = [crop_init[perm[side]] for side in range(4)] |
4425 | + #(x1, y1) = current_page.cropBox.lowerLeft |
4426 | + #(x2, y2) = current_page.cropBox.upperRight |
4427 | + (x1, y1) = [float(xy) for xy in current_page.mediaBox.lowerLeft] |
4428 | + (x2, y2) = [float(xy) for xy in current_page.mediaBox.upperRight] |
4429 | + x1_new = int(x1 + (x2-x1) * crop[0]) |
4430 | + x2_new = int(x2 - (x2-x1) * crop[1]) |
4431 | + y1_new = int(y1 + (y2-y1) * crop[3]) |
4432 | + y2_new = int(y2 - (y2-y1) * crop[2]) |
4433 | + #current_page.cropBox.lowerLeft = (x1_new, y1_new) |
4434 | + #current_page.cropBox.upperRight = (x2_new, y2_new) |
4435 | + current_page.mediaBox.lowerLeft = (x1_new, y1_new) |
4436 | + current_page.mediaBox.upperRight = (x2_new, y2_new) |
4437 | + |
4438 | + pdf_output.addPage(current_page) |
4439 | + |
4440 | + # finally, write "output" to document-output.pdf |
4441 | + print(_('exporting to:'), file_out) |
4442 | + pdf_output.write(file(file_out, 'wb')) |
4443 | + |
4444 | + def on_action_add_doc_activate(self, widget, data=None): |
4445 | + """Import doc""" |
4446 | + |
4447 | + chooser = gtk.FileChooserDialog(title=_('_Import...'), |
4448 | + action=gtk.FILE_CHOOSER_ACTION_OPEN, |
4449 | + buttons=(gtk.STOCK_CANCEL, |
4450 | + gtk.RESPONSE_CANCEL, |
4451 | + gtk.STOCK_OPEN, |
4452 | + gtk.RESPONSE_OK)) |
4453 | + chooser.set_current_folder(self.import_directory) |
4454 | + chooser.set_select_multiple(True) |
4455 | + |
4456 | + filter_all = gtk.FileFilter() |
4457 | + filter_all.set_name(_('All files')) |
4458 | + filter_all.add_pattern('*') |
4459 | + chooser.add_filter(filter_all) |
4460 | + |
4461 | + filter_pdf = gtk.FileFilter() |
4462 | + filter_pdf.set_name(_('PDF files')) |
4463 | + filter_pdf.add_mime_type('application/pdf') |
4464 | + chooser.add_filter(filter_pdf) |
4465 | + chooser.set_filter(filter_pdf) |
4466 | + |
4467 | + response = chooser.run() |
4468 | + if response == gtk.RESPONSE_OK: |
4469 | + for filename in chooser.get_filenames(): |
4470 | + if os.path.isfile(filename): |
4471 | + # FIXME |
4472 | + f = gio.File(filename) |
4473 | + f_info = f.query_info('standard::content-type') |
4474 | + mime_type = f_info.get_content_type() |
4475 | + expected_mime_type = 'application/pdf' |
4476 | + |
4477 | + if mime_type == expected_mime_type: |
4478 | + self.add_pdf_pages(filename) |
4479 | + elif mime_type[:34] == 'application/vnd.oasis.opendocument': |
4480 | + print(_('OpenDocument not supported yet!')) |
4481 | + elif mime_type[:5] == 'image': |
4482 | + print(_('Image file not supported yet!')) |
4483 | + else: |
4484 | + print(_('File type not supported!')) |
4485 | + else: |
4486 | + print(_('File %s does not exist') % filename) |
4487 | + elif response == gtk.RESPONSE_CANCEL: |
4488 | + print(_('Closed, no files selected')) |
4489 | + chooser.destroy() |
4490 | + gobject.idle_add(self.retitle) |
4491 | + |
4492 | + def clear_selected(self, button=None): |
4493 | + """Removes the selected elements in the IconView""" |
4494 | + |
4495 | + model = self.iconview.get_model() |
4496 | + selection = self.iconview.get_selected_items() |
4497 | + if selection: |
4498 | + selection.sort(reverse=True) |
4499 | + self.set_unsaved(True) |
4500 | + for path in selection: |
4501 | + iter = model.get_iter(path) |
4502 | + model.remove(iter) |
4503 | + path = selection[-1] |
4504 | + self.iconview.select_path(path) |
4505 | + if not self.iconview.path_is_selected(path): |
4506 | + if len(model) > 0: # select the last row |
4507 | + row = model[-1] |
4508 | + path = row.path |
4509 | + self.iconview.select_path(path) |
4510 | + self.iconview.grab_focus() |
4511 | + |
4512 | + def iv_drag_begin(self, iconview, context): |
4513 | + """Sets custom icon on drag begin for multiple items selected""" |
4514 | + |
4515 | + if len(iconview.get_selected_items()) > 1: |
4516 | + iconview.stop_emission('drag_begin') |
4517 | + context.set_icon_stock(gtk.STOCK_DND_MULTIPLE, 0, 0) |
4518 | + |
4519 | + def iv_dnd_get_data(self, iconview, context, |
4520 | + selection_data, target_id, etime): |
4521 | + """Handles requests for data by drag and drop in iconview""" |
4522 | + |
4523 | + model = iconview.get_model() |
4524 | + selection = self.iconview.get_selected_items() |
4525 | + selection.sort(key=lambda x: x[0]) |
4526 | + data = [] |
4527 | + for path in selection: |
4528 | + if selection_data.target == 'MODEL_ROW_INTERN': |
4529 | + data.append(str(path[0])) |
4530 | + elif selection_data.target == 'MODEL_ROW_EXTERN': |
4531 | + iter = model.get_iter(path) |
4532 | + nfile, npage, angle = model.get(iter, 2, 3, 6) |
4533 | + crop = model.get(iter, 7, 8, 9, 10) |
4534 | + pdfdoc = self.pdfqueue[nfile - 1] |
4535 | + data.append('\n'.join([pdfdoc.filename, |
4536 | + str(npage), |
4537 | + str(angle)] + |
4538 | + [str(side) for side in crop])) |
4539 | + if data: |
4540 | + data = '\n;\n'.join(data) |
4541 | + selection_data.set(selection_data.target, 8, data) |
4542 | + |
4543 | + def iv_dnd_received_data(self, iconview, context, x, y, |
4544 | + selection_data, target_id, etime): |
4545 | + """Handles received data by drag and drop in iconview""" |
4546 | + |
4547 | + model = iconview.get_model() |
4548 | + data = selection_data.data |
4549 | + if data: |
4550 | + data = data.split('\n;\n') |
4551 | + drop_info = iconview.get_dest_item_at_pos(x, y) |
4552 | + iter_to = None |
4553 | + if drop_info: |
4554 | + path, position = drop_info |
4555 | + ref_to = gtk.TreeRowReference(model,path) |
4556 | + else: |
4557 | + position = gtk.ICON_VIEW_DROP_RIGHT |
4558 | + if len(model) > 0: #find the iterator of the last row |
4559 | + row = model[-1] |
4560 | + path = row.path |
4561 | + ref_to = gtk.TreeRowReference(model,path) |
4562 | + if ref_to: |
4563 | + before = (position == gtk.ICON_VIEW_DROP_LEFT |
4564 | + or position == gtk.ICON_VIEW_DROP_ABOVE) |
4565 | + #if target_id == self.MODEL_ROW_INTERN: |
4566 | + if selection_data.target == 'MODEL_ROW_INTERN': |
4567 | + if before: |
4568 | + data.sort(key=int) |
4569 | + else: |
4570 | + data.sort(key=int,reverse=True) |
4571 | + ref_from_list = [gtk.TreeRowReference(model,path) |
4572 | + for path in data] |
4573 | + for ref_from in ref_from_list: |
4574 | + path = ref_to.get_path() |
4575 | + iter_to = model.get_iter(path) |
4576 | + path = ref_from.get_path() |
4577 | + iter_from = model.get_iter(path) |
4578 | + row = model[iter_from] |
4579 | + if before: |
4580 | + model.insert_before(iter_to, row) |
4581 | + else: |
4582 | + model.insert_after(iter_to, row) |
4583 | + if context.action == gtk.gdk.ACTION_MOVE: |
4584 | + for ref_from in ref_from_list: |
4585 | + path = ref_from.get_path() |
4586 | + iter_from = model.get_iter(path) |
4587 | + model.remove(iter_from) |
4588 | + |
4589 | + #elif target_id == self.MODEL_ROW_EXTERN: |
4590 | + elif selection_data.target == 'MODEL_ROW_EXTERN': |
4591 | + if not before: |
4592 | + data.reverse() |
4593 | + while data: |
4594 | + tmp = data.pop(0).split('\n') |
4595 | + filename = tmp[0] |
4596 | + npage, angle = [int(k) for k in tmp[1:3]] |
4597 | + crop = [float(side) for side in tmp[3:7]] |
4598 | + if self.add_pdf_pages(filename, npage, npage, |
4599 | + angle, crop): |
4600 | + if len(model) > 0: |
4601 | + path = ref_to.get_path() |
4602 | + iter_to = model.get_iter(path) |
4603 | + row = model[-1] #the last row |
4604 | + path = row.path |
4605 | + iter_from = model.get_iter(path) |
4606 | + if before: |
4607 | + model.move_before(iter_from, iter_to) |
4608 | + else: |
4609 | + model.move_after(iter_from, iter_to) |
4610 | + if context.action == gtk.gdk.ACTION_MOVE: |
4611 | + context.finish(True, True, etime) |
4612 | + |
4613 | + def iv_dnd_data_delete(self, widget, context): |
4614 | + """Deletes dnd items after a successful move operation""" |
4615 | + |
4616 | + model = self.iconview.get_model() |
4617 | + selection = self.iconview.get_selected_items() |
4618 | + ref_del_list = [gtk.TreeRowReference(model,path) for path in selection] |
4619 | + for ref_del in ref_del_list: |
4620 | + path = ref_del.get_path() |
4621 | + iter = model.get_iter(path) |
4622 | + model.remove(iter) |
4623 | + |
4624 | + def iv_dnd_motion(self, iconview, context, x, y, etime): |
4625 | + """Handles the drag-motion signal in order to auto-scroll the view""" |
4626 | + |
4627 | + autoscroll_area = 40 |
4628 | + sw_vadj = self.sw.get_vadjustment() |
4629 | + sw_height = self.sw.get_allocation().height |
4630 | + if y -sw_vadj.get_value() < autoscroll_area: |
4631 | + if not self.iv_auto_scroll_timer: |
4632 | + self.iv_auto_scroll_direction = gtk.DIR_UP |
4633 | + self.iv_auto_scroll_timer = gobject.timeout_add(150, |
4634 | + self.iv_auto_scroll) |
4635 | + elif y -sw_vadj.get_value() > sw_height - autoscroll_area: |
4636 | + if not self.iv_auto_scroll_timer: |
4637 | + self.iv_auto_scroll_direction = gtk.DIR_DOWN |
4638 | + self.iv_auto_scroll_timer = gobject.timeout_add(150, |
4639 | + self.iv_auto_scroll) |
4640 | + elif self.iv_auto_scroll_timer: |
4641 | + gobject.source_remove(self.iv_auto_scroll_timer) |
4642 | + self.iv_auto_scroll_timer = None |
4643 | + |
4644 | + def iv_dnd_leave_end(self, widget, context, ignored=None): |
4645 | + """Ends the auto-scroll during DND""" |
4646 | + |
4647 | + if self.iv_auto_scroll_timer: |
4648 | + gobject.source_remove(self.iv_auto_scroll_timer) |
4649 | + self.iv_auto_scroll_timer = None |
4650 | + |
4651 | + def iv_auto_scroll(self): |
4652 | + """Timeout routine for auto-scroll""" |
4653 | + |
4654 | + sw_vadj = self.sw.get_vadjustment() |
4655 | + sw_vpos = sw_vadj.get_value() |
4656 | + if self.iv_auto_scroll_direction == gtk.DIR_UP: |
4657 | + sw_vpos -= sw_vadj.step_increment |
4658 | + sw_vadj.set_value(max(sw_vpos, sw_vadj.lower)) |
4659 | + elif self.iv_auto_scroll_direction == gtk.DIR_DOWN: |
4660 | + sw_vpos += sw_vadj.step_increment |
4661 | + sw_vadj.set_value(min(sw_vpos, sw_vadj.upper - sw_vadj.page_size)) |
4662 | + return True #call me again |
4663 | + |
4664 | + def iv_button_press_event(self, iconview, event): |
4665 | + """Manages mouse clicks on the iconview""" |
4666 | + |
4667 | + if event.button == 3: |
4668 | + x = int(event.x) |
4669 | + y = int(event.y) |
4670 | + time = event.time |
4671 | + path = iconview.get_path_at_pos(x, y) |
4672 | + selection = iconview.get_selected_items() |
4673 | + if path: |
4674 | + if path not in selection: |
4675 | + iconview.unselect_all() |
4676 | + iconview.select_path(path) |
4677 | + iconview.grab_focus() |
4678 | + self.popup.popup(None, None, None, event.button, time) |
4679 | + return 1 |
4680 | + |
4681 | + def sw_dnd_received_data(self, scrolledwindow, context, x, y, |
4682 | + selection_data, target_id, etime): |
4683 | + """Handles received data by drag and drop in scrolledwindow""" |
4684 | + |
4685 | + data = selection_data.data |
4686 | + if target_id == self.MODEL_ROW_EXTERN: |
4687 | + self.model |
4688 | + if data: |
4689 | + data = data.split('\n;\n') |
4690 | + while data: |
4691 | + tmp = data.pop(0).split('\n') |
4692 | + filename = tmp[0] |
4693 | + npage, angle = [int(k) for k in tmp[1:3]] |
4694 | + crop = [float(side) for side in tmp[3:7]] |
4695 | + if self.add_pdf_pages(filename, npage, npage, angle, crop): |
4696 | + if context.action == gtk.gdk.ACTION_MOVE: |
4697 | + context.finish(True, True, etime) |
4698 | + elif target_id == self.TEXT_URI_LIST: |
4699 | + uri = data.strip() |
4700 | + uri_splitted = uri.split() # we may have more than one file dropped |
4701 | + for uri in uri_splitted: |
4702 | + filename = self.get_file_path_from_dnd_dropped_uri(uri) |
4703 | + if os.path.isfile(filename): # is it file? |
4704 | + self.add_pdf_pages(filename) |
4705 | + |
4706 | + def sw_button_press_event(self, scrolledwindow, event): |
4707 | + """Unselects all items in iconview on mouse click in scrolledwindow""" |
4708 | + |
4709 | + if event.button == 1: |
4710 | + self.iconview.unselect_all() |
4711 | + |
4712 | + def sw_scroll_event(self, scrolledwindow, event): |
4713 | + """Manages mouse scroll events in scrolledwindow""" |
4714 | + |
4715 | + if event.state & gtk.gdk.CONTROL_MASK: |
4716 | + if event.direction == gtk.gdk.SCROLL_UP: |
4717 | + self.zoom_change(1) |
4718 | + return 1 |
4719 | + elif event.direction == gtk.gdk.SCROLL_DOWN: |
4720 | + self.zoom_change(-1) |
4721 | + return 1 |
4722 | + |
4723 | + def zoom_set(self, level): |
4724 | + """Sets the zoom level""" |
4725 | + self.zoom_level = max(min(level, 5), -24) |
4726 | + self.zoom_scale = 1.1 ** self.zoom_level |
4727 | + for row in self.model: |
4728 | + row[4] = self.zoom_scale |
4729 | + self.reset_iv_width() |
4730 | + |
4731 | + def zoom_change(self, step=5): |
4732 | + """Modifies the zoom level""" |
4733 | + self.zoom_set(self.zoom_level + step) |
4734 | + |
4735 | + def zoom_in(self, widget=None): |
4736 | + """Increases the zoom level by 5 steps""" |
4737 | + self.zoom_change(5) |
4738 | + |
4739 | + def zoom_out(self, widget=None, step=5): |
4740 | + """Reduces the zoom level by 5 steps""" |
4741 | + self.zoom_change(-5) |
4742 | + |
4743 | + def get_file_path_from_dnd_dropped_uri(self, uri): |
4744 | + """Extracts the path from an uri""" |
4745 | + |
4746 | + path = urllib.url2pathname(uri) # escape special chars |
4747 | + path = path.strip('\r\n\x00') # remove \r\n and NULL |
4748 | + |
4749 | + # get the path to file |
4750 | + if path.startswith('file:\\\\\\'): # windows |
4751 | + path = path[8:] # 8 is len('file:///') |
4752 | + elif path.startswith('file://'): # nautilus, rox |
4753 | + path = path[7:] # 7 is len('file://') |
4754 | + elif path.startswith('file:'): # xffm |
4755 | + path = path[5:] # 5 is len('file:') |
4756 | + return path |
4757 | + |
4758 | + def rotate_page_right(self, widget, data=None): |
4759 | + self.rotate_page(90) |
4760 | + |
4761 | + def rotate_page_left(self, widget, data=None): |
4762 | + self.rotate_page(-90) |
4763 | + |
4764 | + def rotate_page(self, angle): |
4765 | + """Rotates the selected page in the IconView""" |
4766 | + |
4767 | + model = self.iconview.get_model() |
4768 | + selection = self.iconview.get_selected_items() |
4769 | + if len(selection) > 0: |
4770 | + self.set_unsaved(True) |
4771 | + rotate_times = (((-angle) % 360 + 45) / 90) % 4 |
4772 | + if rotate_times is not 0: |
4773 | + for path in selection: |
4774 | + iter = model.get_iter(path) |
4775 | + nfile = model.get_value(iter, 2) |
4776 | + npage = model.get_value(iter, 3) |
4777 | + |
4778 | + crop = [0.,0.,0.,0.] |
4779 | + perm = [0,2,1,3] |
4780 | + for it in range(rotate_times): |
4781 | + perm.append(perm.pop(0)) |
4782 | + perm.insert(1,perm.pop(2)) |
4783 | + crop = [model.get_value(iter, 7 + perm[side]) for side in range(4)] |
4784 | + for side in range(4): |
4785 | + model.set_value(iter, 7 + side, crop[side]) |
4786 | + |
4787 | + new_angle = model.get_value(iter, 6) + int(angle) |
4788 | + new_angle = new_angle % 360 |
4789 | + model.set_value(iter, 6, new_angle) |
4790 | + self.update_geometry(iter) |
4791 | + self.reset_iv_width() |
4792 | + |
4793 | + def crop_page_dialog(self, widget): |
4794 | + """Opens a dialog box to define margins for page cropping""" |
4795 | + |
4796 | + sides = ('L', 'R', 'T', 'B') |
4797 | + side_names = {'L':_('Left'), 'R':_('Right'), |
4798 | + 'T':_('Top'), 'B':_('Bottom') } |
4799 | + opposite_sides = {'L':'R', 'R':'L', 'T':'B', 'B':'T' } |
4800 | + |
4801 | + def set_crop_value(spinbutton, side): |
4802 | + opp_side = opposite_sides[side] |
4803 | + pos = sides.index(opp_side) |
4804 | + adj = spin_list[pos].get_adjustment() |
4805 | + adj.set_upper(99.0 - spinbutton.get_value()) |
4806 | + |
4807 | + model = self.iconview.get_model() |
4808 | + selection = self.iconview.get_selected_items() |
4809 | + |
4810 | + crop = [0.,0.,0.,0.] |
4811 | + if selection: |
4812 | + path = selection[0] |
4813 | + pos = model.get_iter(path) |
4814 | + crop = [model.get_value(pos, 7 + side) for side in range(4)] |
4815 | + |
4816 | + dialog = gtk.Dialog(title=(_('Crop Selected Pages')), |
4817 | + parent=self.window, |
4818 | + flags=gtk.DIALOG_MODAL, |
4819 | + buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, |
4820 | + gtk.STOCK_OK, gtk.RESPONSE_OK)) |
4821 | + dialog.set_size_request(340, 250) |
4822 | + dialog.set_default_response(gtk.RESPONSE_OK) |
4823 | + |
4824 | + frame = gtk.Frame(_('Crop Margins')) |
4825 | + dialog.vbox.pack_start(frame, False, False, 20) |
4826 | + |
4827 | + vbox = gtk.VBox(False, 0) |
4828 | + frame.add(vbox) |
4829 | + |
4830 | + spin_list = [] |
4831 | + units = 2 * [_('% of width')] + 2 * [_('% of height')] |
4832 | + for side in sides: |
4833 | + hbox = gtk.HBox(True, 0) |
4834 | + vbox.pack_start(hbox, False, False, 5) |
4835 | + |
4836 | + label = gtk.Label(side_names[side]) |
4837 | + label.set_alignment(0, 0.0) |
4838 | + hbox.pack_start(label, True, True, 20) |
4839 | + |
4840 | + adj = gtk.Adjustment(100.*crop.pop(0), 0.0, 99.0, 1.0, 5.0, 0.0) |
4841 | + spin = gtk.SpinButton(adj, 0, 1) |
4842 | + spin.set_activates_default(True) |
4843 | + spin.connect('value-changed', set_crop_value, side) |
4844 | + spin_list.append(spin) |
4845 | + hbox.pack_start(spin, False, False, 30) |
4846 | + |
4847 | + label = gtk.Label(units.pop(0)) |
4848 | + label.set_alignment(0, 0.0) |
4849 | + hbox.pack_start(label, True, True, 0) |
4850 | + |
4851 | + dialog.show_all() |
4852 | + result = dialog.run() |
4853 | + |
4854 | + if result == gtk.RESPONSE_OK: |
4855 | + modified = False |
4856 | + crop = [spin.get_value()/100. for spin in spin_list] |
4857 | + for path in selection: |
4858 | + pos = model.get_iter(path) |
4859 | + for it in range(4): |
4860 | + old_val = model.get_value(pos, 7 + it) |
4861 | + model.set_value(pos, 7 + it, crop[it]) |
4862 | + if crop[it] != old_val: |
4863 | + modified = True |
4864 | + self.update_geometry(pos) |
4865 | + if modified: |
4866 | + self.set_unsaved(True) |
4867 | + self.reset_iv_width() |
4868 | + elif result == gtk.RESPONSE_CANCEL: |
4869 | + print(_('Dialog closed')) |
4870 | + dialog.destroy() |
4871 | + |
4872 | + def about_dialog(self, widget, data=None): |
4873 | + about_dialog = gtk.AboutDialog() |
4874 | + try: |
4875 | + about_dialog.set_transient_for(self.window) |
4876 | + about_dialog.set_modal(True) |
4877 | + except: |
4878 | + pass |
4879 | + # FIXME |
4880 | + about_dialog.set_name(APPNAME) |
4881 | + about_dialog.set_version(VERSION) |
4882 | + about_dialog.set_comments(_( |
4883 | + '%s is a tool for rearranging and modifying PDF files.' \ |
4884 | + 'Developed using GTK+ and Python') % APPNAME) |
4885 | + about_dialog.set_authors(['Konstantinos Poulios',]) |
4886 | + about_dialog.set_website_label(WEBSITE) |
4887 | + about_dialog.set_logo_icon_name('pdfshuffler') |
4888 | + about_dialog.set_license(LICENSE) |
4889 | + about_dialog.connect('response', lambda w, a: about_dialog.destroy()) |
4890 | + about_dialog.connect('delete_event', lambda w, a: about_dialog.destroy()) |
4891 | + about_dialog.show_all() |
4892 | + |
4893 | + |
4894 | +class PDF_Doc: |
4895 | + """Class handling PDF documents""" |
4896 | + |
4897 | + def __init__(self, filename, nfile, tmp_dir): |
4898 | + |
4899 | + self.filename = os.path.abspath(filename) |
4900 | + (self.path, self.shortname) = os.path.split(self.filename) |
4901 | + (self.shortname, self.ext) = os.path.splitext(self.shortname) |
4902 | + f = gio.File(filename) |
4903 | + mime_type = f.query_info('standard::content-type').get_content_type() |
4904 | + expected_mime_type = 'application/pdf' |
4905 | + file_prefix = 'file://' |
4906 | + |
4907 | + if mime_type == expected_mime_type: |
4908 | + self.nfile = nfile + 1 |
4909 | + self.mtime = os.path.getmtime(filename) |
4910 | + self.copyname = os.path.join(tmp_dir, '%02d_' % self.nfile + |
4911 | + self.shortname + '.pdf') |
4912 | + shutil.copy(self.filename, self.copyname) |
4913 | + self.document = poppler.document_new_from_file (file_prefix + self.copyname, None) |
4914 | + self.npage = self.document.get_n_pages() |
4915 | + else: |
4916 | + self.nfile = 0 |
4917 | + self.npage = 0 |
4918 | + |
4919 | + |
4920 | +class PDF_Renderer(threading.Thread,gobject.GObject): |
4921 | + |
4922 | + def __init__(self, model, pdfqueue): |
4923 | + threading.Thread.__init__(self) |
4924 | + gobject.GObject.__init__(self) |
4925 | + self.model = model |
4926 | + self.pdfqueue = pdfqueue |
4927 | + self.quit = False |
4928 | + |
4929 | + def run(self): |
4930 | + for idx, row in enumerate(self.model): |
4931 | + if self.quit: |
4932 | + return |
4933 | + if not row[1].surface: |
4934 | + try: |
4935 | + nfile = row[2] |
4936 | + npage = row[3] |
4937 | + pdfdoc = self.pdfqueue[nfile - 1] |
4938 | + page = pdfdoc.document.get_page(npage-1) |
4939 | + w, h = page.get_size() |
4940 | + thumbnail = CairoImage(int(w), int(h)) |
4941 | + cr = cairo.Context(thumbnail.surface) |
4942 | + page.render(cr) |
4943 | + time.sleep(0.003) |
4944 | + gobject.idle_add(self.emit,'update_thumbnail', idx, thumbnail, |
4945 | + priority=gobject.PRIORITY_LOW) |
4946 | + except Exception,e: |
4947 | + print e |
4948 | + |
4949 | + |
4950 | +def main(): |
4951 | + """This function starts PdfShuffler""" |
4952 | + gtk.gdk.threads_init() |
4953 | + gobject.threads_init() |
4954 | + PdfShuffler() |
4955 | + gtk.main() |
4956 | + |
4957 | +if __name__ == '__main__': |
4958 | + main() |
4959 | + |
4960 | |
4961 | === modified file 'po/genpot.sh' |
4962 | --- po/genpot.sh 2009-05-16 23:26:32 +0000 |
4963 | +++ po/genpot.sh 2012-02-12 00:35:23 +0000 |
4964 | @@ -21,4 +21,5 @@ |
4965 | # |
4966 | |
4967 | # Make translation files |
4968 | -xgettext -L python -o po/pdfshuffler.pot pdfshuffler |
4969 | +intltool-extract --type=gettext/glade data/pdfshuffler.ui |
4970 | +xgettext --language=Python --keyword=_ --keyword=N_ --output=po/pdfshuffler.pot pdfshuffler data/pdfshuffler.ui.h |
4971 | |
4972 | === added file 'po/ja.po' |
4973 | --- po/ja.po 1970-01-01 00:00:00 +0000 |
4974 | +++ po/ja.po 2012-02-12 00:35:23 +0000 |
4975 | @@ -0,0 +1,156 @@ |
4976 | +# SOME DESCRIPTIVE TITLE. |
4977 | +# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER |
4978 | +# This file is distributed under the same license as the PACKAGE package. |
4979 | +# Toshiharu Kudoh <toshi.kd2@gmail.com>, 2010. |
4980 | +# |
4981 | + |
4982 | +msgid "" |
4983 | +msgstr "" |
4984 | +"Project-Id-Version: 0.5.1\n" |
4985 | +"Report-Msgid-Bugs-To: \n" |
4986 | +"POT-Creation-Date: 2010-02-11 12:51+JST\n" |
4987 | +"PO-Revision-Date: 2010-11-20 01:48+JST\n" |
4988 | +"Last-Translator: Toshiharu Kudoh <toshi.kd2@gmail.com>\n" |
4989 | +"Language-Team: Japanese <LL@li.org>\n" |
4990 | +"MIME-Version: 1.0\n" |
4991 | +"Content-Type: text/plain; charset=UTF-8\n" |
4992 | +"Content-Transfer-Encoding: 8bit\n" |
4993 | + |
4994 | +#: pdfshuffler:101 pdfshuffler:520 |
4995 | +#, python-format |
4996 | +msgid "File %s does not exist" |
4997 | +msgstr "%s は存在しません" |
4998 | + |
4999 | +#: pdfshuffler:205 pdfshuffler:243 |
5000 | +msgid "Delete Page(s)" |
Eep, this breaks my bzr AttributeError: 'DirStateRevisi onTree' object has no attribute 'last_revision'
bzr: ERROR: exceptions.