Merge lp:~matttbe/ubuntu/precise/pdfshuffler/0.5.99-svn64 into lp:ubuntu/precise/pdfshuffler

Proposed by Matthieu Baerts
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
Reviewer Review Type Date Requested Status
Ubuntu Sponsors Pending
Review via email: mp+92648@code.launchpad.net

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://sourceforge.net/tracker/?func=detail&aid=3293749&group_id=235357&atid=1095819
 - https://bugzilla.redhat.com/show_bug.cgi?id=705153

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!

To post a comment you must log in.
Revision history for this message
Stefano Rivera (stefanor) wrote :

Eep, this breaks my bzr
bzr: ERROR: exceptions.AttributeError: 'DirStateRevisionTree' object has no attribute 'last_revision'

Revision history for this message
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/pdfshuffler-0.5.1/pdfshuffler is a regular file while file /tmp/rkd3EH77bx/pdfshuffler-0.5.99~svn64/pdfshuffler is a directory...

What can I do?

Revision history for this message
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

Revision history for this message
Stefano Rivera (stefanor) wrote :

Yeah, it's LP: #923688. I worked around it...

Revision history for this message
Matthieu Baerts (matttbe) wrote :

Oh ok, it's a known bug!
Thank you for your help :)

Revision history for this message
Stefano Rivera (stefanor) wrote :

Slightly modified the changelog and uploaded.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file '.pc/applied-patches'
--- .pc/applied-patches 2010-09-15 23:33:31 +0000
+++ .pc/applied-patches 2012-02-12 00:35:23 +0000
@@ -1,1 +1,1 @@
1fix-shebang1install_and_look_for_UI_file_in_right_dirs.patch
22
=== removed directory '.pc/fix-shebang'
=== removed file '.pc/fix-shebang/pdfshuffler'
--- .pc/fix-shebang/pdfshuffler 2011-02-15 00:43:28 +0000
+++ .pc/fix-shebang/pdfshuffler 1970-01-01 00:00:00 +0000
@@ -1,1031 +0,0 @@
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4"""
5 --------------------------------------------------------------------------
6
7 PDF-Shuffler 0.5.1 - pyGTK PDF Merging, Rearranging, and Splitting
8 Copyright (C) 2008-2010 Konstantinos Poulios
9 <https://sourceforge.net/projects/pdfshuffler>
10
11 --------------------------------------------------------------------------
12
13 This program is free software; you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation; either version 3 of the License, or
16 (at your option) any later version.
17
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License along
24 with this program; if not, write to the Free Software Foundation, Inc.,
25 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26
27 --------------------------------------------------------------------------
28"""
29
30import os
31import shutil #needed for file operations like whole directory deletion
32import sys #needed for proccessing of command line args
33import urllib #needed to parse filename information passed by DnD
34import threading
35import tempfile
36from copy import copy
37
38import locale #for multilanguage support
39import gettext
40gettext.install('pdfshuffler', unicode=1)
41
42try:
43 import pygtk
44 pygtk.require('2.0')
45 import gtk
46 assert gtk.gtk_version >= (2, 10, 0)
47 assert gtk.pygtk_version >= (2, 10, 0)
48except AssertionError:
49 print('You do not have the required versions of GTK+ and/or PyGTK ' +
50 'installed.\n\n' +
51 'Installed GTK+ version is ' +
52 '.'.join([str(n) for n in gtk.gtk_version]) + '\n' +
53 'Required GTK+ version is 2.10.0 or higher\n\n'
54 'Installed PyGTK version is ' +
55 '.'.join([str(n) for n in gtk.pygtk_version]) + '\n' +
56 'Required PyGTK version is 2.10.0 or higher')
57 sys.exit(1)
58except:
59 print('PyGTK version 2.10.0 or higher is required to run this program.')
60 print('No version of PyGTK was found on your system.')
61 sys.exit(1)
62
63gtk.gdk.threads_init()
64import gobject #to use custom signals
65import pango #to adjust the text alignment in CellRendererText
66import gio #to inquire mime types information
67
68import poppler #for the rendering of pdf pages
69from pyPdf import PdfFileWriter, PdfFileReader
70
71class PDFshuffler:
72 # =======================================================
73 # All the preferences are stored here.
74 # =======================================================
75 prefs = {
76 'window width': min (700, gtk.gdk.screen_get_default().get_width() / 2 ),
77 'window height': min(600, gtk.gdk.screen_get_default().get_height() - 50 ),
78 'window x': 0,
79 'window y': 0,
80 'initial thumbnail size': 530,
81 'initial zoom scale': 0.25,
82 }
83
84 MODEL_ROW_INTERN = 1001
85 MODEL_ROW_EXTERN = 1002
86 TEXT_URI_LIST = 1003
87 MODEL_ROW_MOTION = 1004
88 TARGETS_IV = [('MODEL_ROW_INTERN', gtk.TARGET_SAME_WIDGET, MODEL_ROW_INTERN),
89 ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN),
90 ('MODEL_ROW_MOTION', 0, MODEL_ROW_MOTION)]
91 TARGETS_SW = [('text/uri-list', 0, TEXT_URI_LIST),
92 ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN)]
93
94 def __init__(self):
95 # Create the temporary directory
96 self.tmp_dir = tempfile.mkdtemp("pdfshuffler")
97 os.chmod(self.tmp_dir, 0700)
98
99 pixmap = os.path.join(sys.prefix,'share','pixmaps','pdfshuffler.png')
100 try:
101 gtk.window_set_default_icon_from_file(pixmap)
102 except:
103 print(_('File %s does not exist') % pixmap)
104
105 # Create the main window, and attach delete_event signal to terminating
106 # the application
107 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
108 self.window.set_title('PDF-Shuffler')
109 self.window.set_border_width(0)
110 self.window.move(self.prefs['window x'], self.prefs['window y'])
111 self.window.set_size_request(self.prefs['window width'],
112 self.prefs['window height'])
113 self.window.connect('delete_event', self.close_application)
114 self.window.show()
115
116 # Create a vbox to hold the thumnails-container
117 vbox = gtk.VBox()
118 self.window.add(vbox)
119
120 # Create a scrolled window to hold the thumbnails-container
121 self.sw = gtk.ScrolledWindow()
122 self.sw.set_border_width(0)
123 self.sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
124 self.sw.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
125 gtk.DEST_DEFAULT_HIGHLIGHT |
126 gtk.DEST_DEFAULT_DROP |
127 gtk.DEST_DEFAULT_MOTION,
128 self.TARGETS_SW,
129 gtk.gdk.ACTION_COPY |
130 gtk.gdk.ACTION_MOVE )
131 self.sw.connect('drag_data_received', self.sw_dnd_received_data)
132 self.sw.connect('button_press_event', self.sw_button_press_event)
133 vbox.pack_start(self.sw)
134
135 # Create an alignment to keep the thumbnails center-aligned
136 align = gtk.Alignment(0.5, 0.5, 0, 0)
137 self.sw.add_with_viewport(align)
138
139 # Create ListStore model and IconView
140 self.model = gtk.ListStore(str, # 0.Text descriptor
141 gtk.gdk.Pixbuf, # 1.Thumbnail image
142 int, # 2.Document number
143 int, # 3.Page number
144 int, # 4.Thumbnail width
145 str, # 5.Document filename
146 bool, # 6.Rendered
147 int, # 7.Rotation angle
148 float, # 8.Crop left
149 float, # 9.Crop right
150 float, # 10.Crop top
151 float) # 11.Crop bottom
152
153 self.zoom_scale = self.prefs['initial zoom scale']
154 self.iv_col_width = self.prefs['initial thumbnail size']
155
156 self.iconview = gtk.IconView(self.model)
157 self.iconview.set_item_width(self.iv_col_width + 12)
158
159 self.iconview.set_pixbuf_column(1)
160# self.cellpb = gtk.CellRendererPixbuf()
161# self.cellpb.set_property('follow-state', True)
162# self.iconview.pack_start(self.cellpb, False)
163# self.iconview.set_attributes(self.cellpb, pixbuf=1)
164
165# self.iconview.set_text_column(0)
166 self.celltxt = gtk.CellRendererText()
167 self.celltxt.set_property('width', self.iv_col_width)
168 self.celltxt.set_property('wrap-width', self.iv_col_width)
169 self.celltxt.set_property('alignment', pango.ALIGN_CENTER)
170 self.iconview.pack_start(self.celltxt, False)
171 self.iconview.set_attributes(self.celltxt, text=0)
172
173 self.iconview.set_selection_mode(gtk.SELECTION_MULTIPLE)
174 self.iconview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
175 self.TARGETS_IV,
176 gtk.gdk.ACTION_COPY |
177 gtk.gdk.ACTION_MOVE )
178 self.iconview.enable_model_drag_dest(self.TARGETS_IV,
179 gtk.gdk.ACTION_DEFAULT)
180 self.iconview.connect('drag_begin', self.iv_drag_begin)
181 self.iconview.connect('drag_data_get', self.iv_dnd_get_data)
182 self.iconview.connect('drag_data_received', self.iv_dnd_received_data)
183 self.iconview.connect('drag_data_delete', self.iv_dnd_data_delete)
184 self.iconview.connect('drag_motion', self.iv_dnd_motion)
185 self.iconview.connect('drag_leave', self.iv_dnd_leave_end)
186 self.iconview.connect('drag_end', self.iv_dnd_leave_end)
187 self.iconview.connect('button_press_event', self.iv_button_press_event)
188 self.iv_auto_scroll_direction = 0
189
190 style = self.iconview.get_style().copy()
191 style_sw = self.sw.get_style()
192 for state in (gtk.STATE_NORMAL, gtk.STATE_PRELIGHT, gtk.STATE_ACTIVE):
193 style.base[state] = style_sw.bg[gtk.STATE_NORMAL]
194 self.iconview.set_style(style)
195
196 align.add(self.iconview)
197
198 # Create a horizontal box to hold the buttons
199 hbox = gtk.HBox()
200 vbox.pack_start(hbox, expand=False, fill=False)
201
202 # Create buttons
203 self.button_exit = gtk.Button(stock=gtk.STOCK_QUIT)
204 self.button_exit.connect('clicked', self.close_application)
205 hbox.pack_start(self.button_exit, expand=True, fill=True, padding=20)
206
207 self.button_del = gtk.Button(_('Delete Page(s)'))
208 image = gtk.Image()
209 image.set_from_stock(gtk.STOCK_DELETE, gtk.ICON_SIZE_BUTTON)
210 self.button_del.set_image(image)
211 self.button_del.connect('clicked', self.clear_selected)
212 hbox.pack_start(self.button_del, expand=True, fill=True, padding=20)
213
214 self.button_import = gtk.Button(_('Import pdf'))
215 image = gtk.Image()
216 image.set_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON)
217 self.button_import.set_image(image)
218 self.button_import.connect('clicked', self.on_action_add_doc_activate)
219 hbox.pack_start(self.button_import, expand=True, fill=True, padding=20)
220
221 self.button_export = gtk.Button(_('Export pdf'))
222 image = gtk.Image()
223 image.set_from_stock(gtk.STOCK_SAVE_AS, gtk.ICON_SIZE_BUTTON)
224 self.button_export.set_image(image)
225 self.button_export.connect('clicked', self.choose_export_pdf_name)
226 hbox.pack_start(self.button_export, expand=True, fill=True, padding=20)
227
228 self.button_export = gtk.Button(_('About'))
229 image = gtk.Image()
230 image.set_from_stock(gtk.STOCK_ABOUT, gtk.ICON_SIZE_BUTTON)
231 self.button_export.set_image(image)
232 self.button_export.connect('clicked', self.about_dialog)
233 hbox.pack_start(self.button_export, expand=True, fill=True, padding=20)
234
235 # Define window callback function and show window
236 self.window.connect('size_allocate', self.on_window_size_request) # resize
237 self.window.connect('key_press_event', self.on_keypress_event ) # keypress
238 self.window.show_all()
239
240 #Creating the popup menu
241 self.popup = gtk.Menu()
242 popup_rotate_right = gtk.MenuItem(_('Rotate Page(s) Clockwise'))
243 popup_rotate_left = gtk.MenuItem(_('Rotate Page(s) Counterclockwise'))
244 popup_crop = gtk.MenuItem(_('Crop Page(s)'))
245 popup_delete = gtk.MenuItem(_('Delete Page(s)'))
246 popup_rotate_right.connect('activate', self.rotate_page_right)
247 popup_rotate_left.connect('activate', self.rotate_page_left)
248 popup_crop.connect('activate', self.crop_page_dialog)
249 popup_delete.connect('activate', self.clear_selected)
250 popup_rotate_right.show()
251 popup_rotate_left.show()
252 popup_crop.show()
253 popup_delete.show()
254 self.popup.append(popup_rotate_right)
255 self.popup.append(popup_rotate_left)
256 self.popup.append(popup_crop)
257 self.popup.append(popup_delete)
258
259 # Initializing variables
260 self.export_directory = os.getenv('HOME')
261 self.import_directory = os.getenv('HOME')
262 self.nfile = 0
263 self.iv_auto_scroll_timer = None
264 self.pdfqueue = []
265
266 gobject.type_register(PDF_Renderer)
267 gobject.signal_new('reset_iv_width', PDF_Renderer,
268 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
269 self.rendering_thread = PDF_Renderer(self.model, self.pdfqueue,
270 self.zoom_scale, self.iv_col_width)
271 self.rendering_thread.connect('reset_iv_width', self.reset_iv_width)
272 self.rendering_thread.start()
273
274 # Importing documents passed as command line arguments
275 for filename in sys.argv[1:]:
276 self.add_pdf_pages(filename)
277
278 # =======================================================
279 def render(self):
280 if self.rendering_thread.paused:
281 self.rendering_thread.paused = False
282 self.rendering_thread.evnt.set()
283 self.rendering_thread.evnt.clear()
284
285 # =======================================================
286 def on_window_size_request(self, window, event):
287 """Main Window resize - workaround for autosetting of
288 iconview cols no."""
289
290 #add 12 because of: http://bugzilla.gnome.org/show_bug.cgi?id=570152
291 col_num = 9 * window.get_size()[0] / (10 * (self.iv_col_width + 12))
292 self.iconview.set_columns(col_num)
293
294 # =======================================================
295 def reset_iv_width(self, renderer=None):
296 """Reconfigures the width of the iconview columns"""
297
298 max_w = max(row[4] for row in self.model)
299 if max_w != self.iv_col_width:
300 self.iv_col_width = max_w
301 self.celltxt.set_property('width', self.iv_col_width)
302 self.celltxt.set_property('wrap-width', self.iv_col_width)
303 self.iconview.set_item_width(self.iv_col_width + 12) #-1)
304 self.on_window_size_request(self.window, None)
305
306 # =======================================================
307 def on_keypress_event(self, widget, event):
308 """Keypress events in Main Window"""
309
310 #keyname = gtk.gdk.keyval_name(event.keyval)
311 if event.keyval == 65535: # Delete keystroke
312 self.clear_selected()
313
314 # =======================================================
315 def close_application(self, widget, event=None, data=None):
316 """Termination"""
317
318 #gtk.gdk.threads_leave()
319 self.rendering_thread.quit = True
320 #gtk.gdk.threads_enter()
321 if self.rendering_thread.paused == True:
322 self.rendering_thread.evnt.set()
323 self.rendering_thread.evnt.clear()
324 if os.path.isdir(self.tmp_dir):
325 shutil.rmtree(self.tmp_dir)
326 if gtk.main_level():
327 gtk.main_quit()
328 else:
329 sys.exit(0)
330 return False
331
332 # =======================================================
333 def add_pdf_pages(self, filename,
334 firstpage=None, lastpage=None,
335 angle=0, crop=[0.,0.,0.,0.] ):
336 """Add pages of a pdf document to the model"""
337
338 res = False
339 # Check if the document has already been loaded
340 pdfdoc = None
341 for it_pdfdoc in self.pdfqueue:
342 if os.path.isfile(it_pdfdoc.filename) and \
343 os.path.samefile(filename, it_pdfdoc.filename) and \
344 os.path.getmtime(filename) is it_pdfdoc.mtime:
345 pdfdoc = it_pdfdoc
346 break
347
348 if not pdfdoc:
349 pdfdoc = PDF_Doc(filename, self.nfile, self.tmp_dir)
350 self.import_directory = os.path.split(filename)[0]
351 self.export_directory = self.import_directory
352 if pdfdoc.nfile != 0 and pdfdoc != []:
353 self.nfile = pdfdoc.nfile
354 self.pdfqueue.append(pdfdoc)
355 else:
356 return res
357
358 n_start = 1
359 n_end = pdfdoc.npage
360 if firstpage:
361 n_start = min(n_end, max(1, firstpage))
362 if lastpage:
363 n_end = max(n_start, min(n_end, lastpage))
364
365 for npage in range(n_start, n_end + 1):
366 descriptor = ''.join([pdfdoc.shortname, '\n', _('page'), ' ', str(npage)])
367 width = self.iv_col_width
368 thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False,
369 8, width, width)
370 self.model.append((descriptor, # 0
371 thumbnail, # 1
372 pdfdoc.nfile, # 2
373 npage, # 3
374 width, # 4
375 pdfdoc.filename, # 5
376 False, # 6
377 angle, # 7
378 crop[0],crop[1], # 8-9
379 crop[2],crop[3] )) # 10-11
380 res = True
381
382 if res:
383 self.render()
384 return res
385
386 # =======================================================
387 def choose_export_pdf_name(self, widget=None):
388 """Handles choosing a name for exporting """
389
390 chooser = gtk.FileChooserDialog(title=_('Export ...'),
391 action=gtk.FILE_CHOOSER_ACTION_SAVE,
392 buttons=(gtk.STOCK_CANCEL,
393 gtk.RESPONSE_CANCEL,
394 gtk.STOCK_SAVE,
395 gtk.RESPONSE_OK))
396 chooser.set_do_overwrite_confirmation(True)
397 chooser.set_current_folder(self.export_directory)
398 filter_pdf = gtk.FileFilter()
399 filter_pdf.set_name(_('PDF files'))
400 filter_pdf.add_mime_type('application/pdf')
401 chooser.add_filter(filter_pdf)
402
403 filter_all = gtk.FileFilter()
404 filter_all.set_name(_('All files'))
405 filter_all.add_pattern('*')
406 chooser.add_filter(filter_all)
407
408 while True:
409 response = chooser.run()
410 if response == gtk.RESPONSE_OK:
411 file_out = chooser.get_filename()
412 (path, shortname) = os.path.split(file_out)
413 (shortname, ext) = os.path.splitext(shortname)
414 if ext.lower() != '.pdf':
415 file_out = file_out + '.pdf'
416 try:
417 self.export_to_file(file_out)
418 self.export_directory = path
419 except IOError:
420 error_msg_win = gtk.MessageDialog(flags=gtk.DIALOG_MODAL,
421 type=gtk.MESSAGE_ERROR,
422 message_format=_("Error writing file: %s") % file_out,
423 buttons=gtk.BUTTONS_OK)
424 response = error_msg_win.run()
425 if response == gtk.RESPONSE_OK:
426 error_msg_win.destroy()
427 continue
428 break
429 chooser.destroy()
430
431 # =======================================================
432 def export_to_file(self, file_out):
433 """Export to file"""
434
435 pdf_output = PdfFileWriter()
436 pdf_input = []
437 for pdfdoc in self.pdfqueue:
438 pdfdoc_inp = PdfFileReader(file(pdfdoc.copyname, 'rb'))
439 if pdfdoc_inp.getIsEncrypted():
440 if (pdfdoc_inp.decrypt('')!=1): # Workaround for lp:#355479
441 print(_('File %s is encrypted.') % pdfdoc.filename)
442 print(_('Support for such files has not been implemented yet.'))
443 print(_('File export failed.'))
444 return
445 #FIXME
446 #else
447 # ask for password and decrypt file
448 pdf_input.append(pdfdoc_inp)
449
450 for row in self.model:
451 # add pages from input to output document
452 nfile = row[2]
453 npage = row[3]
454 current_page = copy(pdf_input[nfile-1].getPage(npage-1))
455 angle = row[7]
456 angle0 = current_page.get("/Rotate",0)
457 crop = [row[8],row[9],row[10],row[11]]
458 if angle is not 0:
459 current_page.rotateClockwise(angle)
460 if crop != [0.,0.,0.,0.]:
461 rotate_times = ( ( ( angle + angle0 ) % 360 + 45 ) / 90 ) % 4
462 crop_init = crop
463 if rotate_times is not 0:
464 perm = [0,2,1,3]
465 for it in range(rotate_times):
466 perm.append(perm.pop(0))
467 perm.insert(1,perm.pop(2))
468 crop = [crop_init[perm[side]] for side in range(4)]
469 #(x1, y1) = current_page.cropBox.lowerLeft
470 #(x2, y2) = current_page.cropBox.upperRight
471 (x1, y1) = [float(xy) for xy in current_page.mediaBox.lowerLeft]
472 (x2, y2) = [float(xy) for xy in current_page.mediaBox.upperRight]
473 x1_new = int( x1 + (x2-x1) * crop[0] )
474 x2_new = int( x2 - (x2-x1) * crop[1] )
475 y1_new = int( y1 + (y2-y1) * crop[3] )
476 y2_new = int( y2 - (y2-y1) * crop[2] )
477 #current_page.cropBox.lowerLeft = (x1_new, y1_new)
478 #current_page.cropBox.upperRight = (x2_new, y2_new)
479 current_page.mediaBox.lowerLeft = (x1_new, y1_new)
480 current_page.mediaBox.upperRight = (x2_new, y2_new)
481
482 pdf_output.addPage(current_page)
483
484 # finally, write "output" to document-output.pdf
485 print(_('exporting to:'), file_out)
486 pdf_output.write(file(file_out, 'wb'))
487
488 # =======================================================
489 def on_action_add_doc_activate(self, widget, data=None):
490 """Import doc"""
491
492 chooser = gtk.FileChooserDialog(title=_('Import...'),
493 action=gtk.FILE_CHOOSER_ACTION_OPEN,
494 buttons=(gtk.STOCK_CANCEL,
495 gtk.RESPONSE_CANCEL,
496 gtk.STOCK_OPEN,
497 gtk.RESPONSE_OK))
498 chooser.set_current_folder(self.import_directory)
499 chooser.set_select_multiple(True)
500
501 filter_all = gtk.FileFilter()
502 filter_all.set_name(_('All files'))
503 filter_all.add_pattern('*')
504 chooser.add_filter(filter_all)
505
506 filter_pdf = gtk.FileFilter()
507 filter_pdf.set_name(_('PDF files'))
508 filter_pdf.add_mime_type('application/pdf')
509 chooser.add_filter(filter_pdf)
510 chooser.set_filter(filter_pdf)
511
512 response = chooser.run()
513 if response == gtk.RESPONSE_OK:
514 for filename in chooser.get_filenames():
515 if os.path.isfile(filename):
516 # FIXME
517 f = gio.File(filename)
518 f_info = f.query_info('standard::content-type')
519 mime_type = f_info.get_content_type()
520 if mime_type == 'application/pdf':
521 self.add_pdf_pages(filename)
522 elif mime_type[:34] == 'application/vnd.oasis.opendocument':
523 print(_('OpenDocument not supported yet!'))
524 elif mime_type[:5] == 'image':
525 print(_('Image file not supported yet!'))
526 else:
527 print(_('File type not supported!'))
528 else:
529 print(_('File %s does not exist') % filename)
530 elif response == gtk.RESPONSE_CANCEL:
531 print(_('Closed, no files selected'))
532 chooser.destroy()
533
534 # =======================================================
535 def clear_selected(self, button=None):
536 """Removes the selected Element in the IconView"""
537
538 model = self.iconview.get_model()
539 selection = self.iconview.get_selected_items()
540 if selection:
541 selection.sort(reverse=True)
542 for path in selection:
543 iter = model.get_iter(path)
544 model.remove(iter)
545 path = selection[-1]
546 self.iconview.select_path(path)
547 if not self.iconview.path_is_selected(path):
548 if len(model) > 0: # select the last row
549 row = model[-1]
550 path = row.path
551 self.iconview.select_path(path)
552 self.iconview.grab_focus()
553
554 # =======================================================
555 def iv_drag_begin(self, iconview, context):
556 """Sets custom icon on drag begin for multiple items selected"""
557
558 if len(iconview.get_selected_items()) > 1:
559 iconview.stop_emission('drag_begin')
560 context.set_icon_stock(gtk.STOCK_DND_MULTIPLE, 0, 0)
561
562 # =======================================================
563 def iv_dnd_get_data(self, iconview, context,
564 selection_data, target_id, etime):
565 """Handles requests for data by drag and drop in iconview"""
566
567 model = iconview.get_model()
568 selection = self.iconview.get_selected_items()
569 selection.sort(key=lambda x: x[0])
570 data = []
571 for path in selection:
572 if selection_data.target == 'MODEL_ROW_INTERN':
573 data.append( str(path[0]) )
574 elif selection_data.target == 'MODEL_ROW_EXTERN':
575 iter = model.get_iter(path)
576 nfile, npage, angle = model.get(iter, 2, 3, 7)
577 crop = model.get(iter, 8, 9, 10, 11)
578 pdfdoc = self.pdfqueue[nfile - 1]
579 data.append('\n'.join([pdfdoc.filename,
580 str(npage),
581 str(angle) ] +
582 [str(side) for side in crop] ) )
583 if data:
584 data = '\n;\n'.join(data)
585 selection_data.set(selection_data.target, 8, data)
586
587 # =======================================================
588 def iv_dnd_received_data(self, iconview, context, x, y,
589 selection_data, target_id, etime):
590 """Handles received data by drag and drop in iconview"""
591
592 model = iconview.get_model()
593 data = selection_data.data
594 if data:
595 data = data.split('\n;\n')
596 drop_info = iconview.get_dest_item_at_pos(x, y)
597 iter_to = None
598 if drop_info:
599 path, position = drop_info
600 ref_to = gtk.TreeRowReference(model,path)
601 else:
602 position = gtk.ICON_VIEW_DROP_RIGHT
603 if len(model) > 0: #find the iterator of the last row
604 row = model[-1]
605 path = row.path
606 ref_to = gtk.TreeRowReference(model,path)
607 if ref_to:
608 before = ( position == gtk.ICON_VIEW_DROP_LEFT
609 or position == gtk.ICON_VIEW_DROP_ABOVE)
610 #if target_id == self.MODEL_ROW_INTERN:
611 if selection_data.target == 'MODEL_ROW_INTERN':
612 if before:
613 data.sort(key=int)
614 else:
615 data.sort(key=int,reverse=True)
616 ref_from_list = [gtk.TreeRowReference(model,path)
617 for path in data]
618 for ref_from in ref_from_list:
619 path = ref_to.get_path()
620 iter_to = model.get_iter(path)
621 path = ref_from.get_path()
622 iter_from = model.get_iter(path)
623 row = model[iter_from]
624 if before:
625 model.insert_before(iter_to, row)
626 else:
627 model.insert_after(iter_to, row)
628 if context.action == gtk.gdk.ACTION_MOVE:
629 for ref_from in ref_from_list:
630 path = ref_from.get_path()
631 iter_from = model.get_iter(path)
632 model.remove(iter_from)
633
634 #elif target_id == self.MODEL_ROW_EXTERN:
635 elif selection_data.target == 'MODEL_ROW_EXTERN':
636 if not before:
637 data.reverse()
638 while data:
639 tmp = data.pop(0).split('\n')
640 filename = tmp[0]
641 npage, angle = [int(k) for k in tmp[1:3]]
642 crop = [float(side) for side in tmp[3:7]]
643 if self.add_pdf_pages(filename, npage, npage,
644 angle, crop ):
645 if len(model) > 0:
646 path = ref_to.get_path()
647 iter_to = model.get_iter(path)
648 row = model[-1] #the last row
649 path = row.path
650 iter_from = model.get_iter(path)
651 if before:
652 model.move_before(iter_from, iter_to)
653 else:
654 model.move_after(iter_from, iter_to)
655 if context.action == gtk.gdk.ACTION_MOVE:
656 context.finish(True, True, etime)
657
658 # =======================================================
659 def iv_dnd_data_delete(self, widget, context):
660 """Deletes dnd items after a successful move operation"""
661
662 model = self.iconview.get_model()
663 selection = self.iconview.get_selected_items()
664 ref_del_list = [gtk.TreeRowReference(model,path) for path in selection]
665 for ref_del in ref_del_list:
666 path = ref_del.get_path()
667 iter = model.get_iter(path)
668 model.remove(iter)
669
670 # =======================================================
671 def iv_dnd_motion(self, iconview, context, x, y, etime):
672 """Handles the drag-motion signal in order to auto-scroll the view"""
673
674 autoscroll_area = 40
675 sw_vadj = self.sw.get_vadjustment()
676 sw_height = self.sw.get_allocation().height
677 if y -sw_vadj.get_value() < autoscroll_area:
678 if not self.iv_auto_scroll_timer:
679 self.iv_auto_scroll_direction = gtk.DIR_UP
680 self.iv_auto_scroll_timer = gobject.timeout_add(150,
681 self.iv_auto_scroll)
682 elif y -sw_vadj.get_value() > sw_height - autoscroll_area:
683 if not self.iv_auto_scroll_timer:
684 self.iv_auto_scroll_direction = gtk.DIR_DOWN
685 self.iv_auto_scroll_timer = gobject.timeout_add(150,
686 self.iv_auto_scroll)
687 elif self.iv_auto_scroll_timer:
688 gobject.source_remove(self.iv_auto_scroll_timer)
689 self.iv_auto_scroll_timer = None
690
691 # =======================================================
692 def iv_dnd_leave_end(self, widget, context, ignored=None):
693 """Ends the auto-scroll during DND"""
694
695 if self.iv_auto_scroll_timer:
696 gobject.source_remove(self.iv_auto_scroll_timer)
697 self.iv_auto_scroll_timer = None
698
699 # =======================================================
700 def iv_auto_scroll(self):
701 """Timeout routine for auto-scroll"""
702
703 sw_vadj = self.sw.get_vadjustment()
704 sw_vpos = sw_vadj.get_value()
705 if self.iv_auto_scroll_direction == gtk.DIR_UP:
706 sw_vpos -= sw_vadj.step_increment
707 sw_vadj.set_value( max(sw_vpos, sw_vadj.lower) )
708 elif self.iv_auto_scroll_direction == gtk.DIR_DOWN:
709 sw_vpos += sw_vadj.step_increment
710 sw_vadj.set_value( min(sw_vpos, sw_vadj.upper - sw_vadj.page_size) )
711 return True #call me again
712
713 # =======================================================
714 def iv_button_press_event(self, iconview, event):
715 """Manages mouse clicks on the iconview"""
716
717 if event.button == 3:
718 x = int(event.x)
719 y = int(event.y)
720 time = event.time
721 path = iconview.get_path_at_pos(x, y)
722 selection = iconview.get_selected_items()
723 if path:
724 if path not in selection:
725 iconview.unselect_all()
726 iconview.select_path(path)
727 iconview.grab_focus()
728 self.popup.popup( None, None, None, event.button, time)
729 return 1
730
731 # =======================================================
732 def sw_dnd_received_data(self, scrolledwindow, context, x, y,
733 selection_data, target_id, etime):
734 """Handles received data by drag and drop in scrolledwindow"""
735
736 data = selection_data.data
737 if target_id == self.MODEL_ROW_EXTERN:
738 self.model
739 if data:
740 data = data.split('\n;\n')
741 while data:
742 tmp = data.pop(0).split('\n')
743 filename = tmp[0]
744 npage, angle = [int(k) for k in tmp[1:3]]
745 crop = [float(side) for side in tmp[3:7]]
746 if self.add_pdf_pages(filename, npage, npage, angle, crop):
747 if context.action == gtk.gdk.ACTION_MOVE:
748 context.finish(True, True, etime)
749 elif target_id == self.TEXT_URI_LIST:
750 uri = data.strip()
751 uri_splitted = uri.split() # we may have more than one file dropped
752 for uri in uri_splitted:
753 filename = self.get_file_path_from_dnd_dropped_uri(uri)
754 if os.path.isfile(filename): # is it file?
755 self.add_pdf_pages(filename)
756
757 # =======================================================
758 def sw_button_press_event(self, scrolledwindow, event):
759 """Unselects all items in iconview on mouse click in scrolledwindow"""
760
761 if event.button == 1:
762 self.iconview.unselect_all()
763
764 # =======================================================
765 def get_file_path_from_dnd_dropped_uri(self, uri):
766 """Extracts the path from an uri"""
767
768 path = urllib.url2pathname(uri) # escape special chars
769 path = path.strip('\r\n\x00') # remove \r\n and NULL
770
771 # get the path to file
772 if path.startswith('file:\\\\\\'): # windows
773 path = path[8:] # 8 is len('file:///')
774 elif path.startswith('file://'): # nautilus, rox
775 path = path[7:] # 7 is len('file://')
776 elif path.startswith('file:'): # xffm
777 path = path[5:] # 5 is len('file:')
778 return path
779
780 # =======================================================
781 def rotate_page_right(self, widget, data=None):
782 self.rotate_page(90)
783
784 def rotate_page_left(self, widget, data=None):
785 self.rotate_page(-90)
786
787 def rotate_page(self, angle):
788 """Rotates the selected page in the IconView"""
789
790 model = self.iconview.get_model()
791 selection = self.iconview.get_selected_items()
792 for path in selection:
793 iter = model.get_iter(path)
794 nfile = model.get_value(iter, 2)
795 npage = model.get_value(iter, 3)
796
797 rotate_times = ( ( (-angle) % 360 + 45 ) / 90 ) % 4
798 crop = [0.,0.,0.,0.]
799 if rotate_times is not 0:
800 perm = [0,2,1,3]
801 for it in range(rotate_times):
802 perm.append(perm.pop(0))
803 perm.insert(1,perm.pop(2))
804 crop = [model.get_value(iter, 8 + perm[side]) for side in range(4)]
805 for side in range(4):
806 model.set_value(iter, 8 + side, crop[side])
807
808 new_angle = model.get_value(iter, 7) + angle
809 model.set_value(iter, 7, new_angle)
810 model.set_value(iter, 6, False) #rendering request
811
812 self.render()
813
814 # =======================================================
815 def crop_page_dialog(self, widget):
816 """Opens a dialog box to define margins for page cropping"""
817
818 sides = ('L', 'R', 'T', 'B')
819 side_names = {'L':_('Left'), 'R':_('Right'),
820 'T':_('Top'), 'B':_('Bottom') }
821 opposite_sides = {'L':'R', 'R':'L', 'T':'B', 'B':'T' }
822
823 def set_crop_value(spinbutton, side):
824 opp_side = opposite_sides[side]
825 pos = sides.index(opp_side)
826 adj = spin_list[pos].get_adjustment()
827 adj.set_upper(99.0 - spinbutton.get_value())
828
829 model = self.iconview.get_model()
830 selection = self.iconview.get_selected_items()
831
832 crop = [0.,0.,0.,0.]
833 if selection:
834 path = selection[0]
835 pos = model.get_iter(path)
836 crop = [model.get_value(pos, 8 + side) for side in range(4)]
837
838 dialog = gtk.Dialog(title=(_('Crop Selected Page(s)')),
839 parent=self.window,
840 flags=gtk.DIALOG_MODAL,
841 buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
842 gtk.STOCK_OK, gtk.RESPONSE_OK))
843 dialog.set_size_request(340, 250)
844 dialog.set_default_response(gtk.RESPONSE_OK)
845
846 frame = gtk.Frame(_('Crop Margins'))
847 dialog.vbox.pack_start(frame, False, False, 20)
848
849 vbox = gtk.VBox(False, 0)
850 frame.add(vbox)
851
852 spin_list = []
853 units = 2 * [_('% of width')] + 2 * [_('% of height')]
854 for side in sides:
855 hbox = gtk.HBox(True, 0)
856 vbox.pack_start(hbox, False, False, 5)
857
858 label = gtk.Label(side_names[side])
859 label.set_alignment(0, 0.0)
860 hbox.pack_start(label, True, True, 20)
861
862 adj = gtk.Adjustment(100.*crop.pop(0), 0.0, 99.0, 1.0, 5.0, 0.0)
863 spin = gtk.SpinButton(adj, 0, 1)
864 spin.set_activates_default(True)
865 spin.connect('value-changed', set_crop_value, side)
866 spin_list.append(spin)
867 hbox.pack_start(spin, False, False, 30)
868
869 label = gtk.Label(units.pop(0))
870 label.set_alignment(0, 0.0)
871 hbox.pack_start(label, True, True, 0)
872
873 dialog.show_all()
874 result = dialog.run()
875
876 if result == gtk.RESPONSE_OK:
877 crop = []
878 for spin in spin_list:
879 crop.append( spin.get_value()/100. )
880 for path in selection:
881 pos = model.get_iter(path)
882 for it in range(4):
883 model.set_value(pos, 8 + it, crop[it])
884 model.set_value(pos, 6, False) #rendering request
885 self.render()
886 elif result == gtk.RESPONSE_CANCEL:
887 print(_('Dialog closed'))
888 dialog.destroy()
889
890 # =======================================================
891 def about_dialog(self, widget, data=None):
892 about_dialog = gtk.AboutDialog()
893 try:
894 about_dialog.set_transient_for(self.window)
895 about_dialog.set_modal(True)
896 except:
897 pass
898 # FIXME
899 about_dialog.set_name('PDF-Shuffler')
900 about_dialog.set_version('0.5.1')
901 about_dialog.set_comments(_(
902 'PDF-Shuffler is a simple pyGTK utility which lets you merge, \
903split and rearrange PDF documents. You can also rotate and crop individual \
904pages of a pdf document.'))
905 about_dialog.set_authors(['Konstantinos Poulios',])
906 about_dialog.set_website_label('http://pdfshuffler.sourceforge.net/')
907 about_dialog.set_logo_icon_name('pdfshuffler')
908 about_dialog.connect('response', lambda w, a: about_dialog.destroy())
909 about_dialog.connect('delete_event', lambda w, a: about_dialog.destroy())
910 about_dialog.show_all()
911
912# =======================================================
913class PDF_Doc:
914 """Class handling pdf documents"""
915
916 def __init__(self, filename, nfile, tmp_dir):
917
918 self.filename = os.path.abspath(filename)
919 (self.path, self.shortname) = os.path.split(self.filename)
920 (self.shortname, self.ext) = os.path.splitext(self.shortname)
921 f = gio.File(filename)
922 mime_type = f.query_info('standard::content-type').get_content_type()
923 if mime_type == 'application/pdf':
924 self.nfile = nfile + 1
925 self.mtime = os.path.getmtime(filename)
926 self.copyname = os.path.join(tmp_dir, '%02d_' % self.nfile +
927 self.shortname + '.pdf')
928 shutil.copy(self.filename, self.copyname)
929 self.document = poppler.document_new_from_file("file://" + self.copyname, None)
930 self.npage = self.document.get_n_pages()
931 else:
932 self.nfile = 0
933
934
935# =======================================================
936class PDF_Renderer(threading.Thread,gobject.GObject):
937
938 def __init__(self, model, pdfqueue, scale=1., width=100):
939 threading.Thread.__init__(self)
940 gobject.GObject.__init__(self)
941 self.model = model
942 self.scale = scale
943 self.default_width = width
944 self.pdfqueue = pdfqueue
945 self.quit = False
946 self.evnt = threading.Event()
947 self.paused = False
948
949 def run(self):
950 while not self.quit:
951 rendered_all = True
952 for row in self.model:
953 if self.quit:
954 break
955 if not row[6]:
956 rendered_all = False
957 gtk.gdk.threads_enter()
958 try:
959 nfile = row[2]
960 npage = row[3]
961 angle = row[7]
962 crop = [row[8],row[9],row[10],row[11]]
963 pdfdoc = self.pdfqueue[nfile - 1]
964 thumbnail = self.load_pdf_thumbnail(pdfdoc, npage, angle, crop)
965 row[6] = True
966 row[4] = thumbnail.get_width()
967 row[1] = thumbnail
968 finally:
969 gtk.gdk.threads_leave()
970 if rendered_all:
971 if self.model.get_iter_first(): #just checking if model isn't empty
972 self.emit('reset_iv_width')
973 self.paused = True
974 self.evnt.wait()
975
976 # =======================================================
977 def load_pdf_thumbnail(self, pdfdoc, npage, rotation=0, crop=[0.,0.,0.,0.]):
978 """Create pdf pixbuf"""
979
980 page = pdfdoc.document.get_page(npage-1)
981 try:
982 pix_w, pix_h = page.get_size()
983 pix_w = int(pix_w * self.scale)
984 pix_h = int(pix_h * self.scale)
985 thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False,
986 8, pix_w , pix_h)
987 page.render_to_pixbuf(0,0,pix_w,pix_h,self.scale,0,thumbnail)
988 rotation = (-rotation) % 360
989 rotation = ((rotation + 45) / 90) * 90
990 thumbnail = thumbnail.rotate_simple(rotation)
991 pix_w = thumbnail.get_width()
992 pix_h = thumbnail.get_height()
993 if crop != [0.,0.,0.,0.]:
994 src_x = int( crop[0] * pix_w )
995 src_y = int( crop[2] * pix_h )
996 width = int( (1. - crop[0] - crop[1]) * pix_w )
997 height = int( (1. - crop[2] - crop[3]) * pix_h )
998 new_thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False,
999 8, width, height)
1000 thumbnail.copy_area(src_x, src_y, width, height,
1001 new_thumbnail, 0, 0)
1002 thumbnail = new_thumbnail
1003 pix_w = thumbnail.get_width()
1004 pix_h = thumbnail.get_height()
1005 except:
1006 pix_w = self.default_width
1007 pix_h = pix_w
1008 thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False,
1009 8, pix_w, pix_h)
1010 pixbuf.fill(0xffffffff)
1011
1012 #add border
1013 thickness = 3
1014 color = 0x000000FF
1015 canvas = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8,
1016 pix_w + thickness + 1,
1017 pix_h + thickness + 1)
1018 canvas.fill(color)
1019 thumbnail.copy_area(0, 0, pix_w, pix_h, canvas, 1, 1)
1020 thumbnail = canvas
1021
1022 return thumbnail
1023
1024
1025# =======================================================
1026if __name__ == '__main__':
1027 PDFshuffler()
1028 gtk.gdk.threads_enter()
1029 gtk.main()
1030 gtk.gdk.threads_leave()
1031
10320
=== added directory '.pc/install_and_look_for_UI_file_in_right_dirs.patch'
=== added directory '.pc/install_and_look_for_UI_file_in_right_dirs.patch/pdfshuffler'
=== added file '.pc/install_and_look_for_UI_file_in_right_dirs.patch/pdfshuffler/pdfshuffler.py'
--- .pc/install_and_look_for_UI_file_in_right_dirs.patch/pdfshuffler/pdfshuffler.py 1970-01-01 00:00:00 +0000
+++ .pc/install_and_look_for_UI_file_in_right_dirs.patch/pdfshuffler/pdfshuffler.py 2012-02-12 00:35:23 +0000
@@ -0,0 +1,1057 @@
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4"""
5
6 PdfShuffler 0.6.0 - GTK+ based utility for splitting, rearrangement and
7 modification of PDF documents.
8 Copyright (C) 2008-2011 Konstantinos Poulios
9 <https://sourceforge.net/projects/pdfshuffler>
10
11 This file is part of PdfShuffler.
12
13 PdfShuffler is free software; you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation; either version 3 of the License, or
16 (at your option) any later version.
17
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License along
24 with this program; if not, write to the Free Software Foundation, Inc.,
25 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26
27"""
28
29import os
30import shutil #needed for file operations like whole directory deletion
31import sys #needed for proccessing of command line args
32import urllib #needed to parse filename information passed by DnD
33import threading
34import tempfile
35from copy import copy
36
37import locale #for multilanguage support
38import gettext
39gettext.install('pdfshuffler', unicode=1)
40
41
42APPNAME = 'PdfShuffler' # PDF-Shuffler, PDFShuffler, pdfshuffler
43VERSION = '0.6.0'
44WEBSITE = 'http://pdfshuffler.sourceforge.net/'
45LICENSE = 'GNU General Public License (GPL) Version 3.'
46
47try:
48 import pygtk
49 pygtk.require('2.0')
50 import gtk
51 assert gtk.gtk_version >= (2, 10, 0)
52 assert gtk.pygtk_version >= (2, 10, 0)
53except AssertionError:
54 print('You do not have the required versions of GTK+ and PyGTK ' +
55 'installed.\n\n' +
56 'Installed GTK+ version is ' +
57 '.'.join([str(n) for n in gtk.gtk_version]) + '\n' +
58 'Required GTK+ version is 2.10.0 or higher\n\n'
59 'Installed PyGTK version is ' +
60 '.'.join([str(n) for n in gtk.pygtk_version]) + '\n' +
61 'Required PyGTK version is 2.10.0 or higher')
62 sys.exit(1)
63except:
64 print('PyGTK version 2.10.0 or higher is required to run this program.')
65 print('No version of PyGTK was found on your system.')
66 sys.exit(1)
67
68import gobject #to use custom signals
69import pango #to adjust the text alignment in CellRendererText
70import gio #to inquire mime types information
71import cairo
72
73import poppler #for the rendering of pdf pages
74from pyPdf import PdfFileWriter, PdfFileReader
75
76from cairorendering import CellRendererImage, CairoImage
77gobject.type_register(CellRendererImage)
78
79import time
80
81class PdfShuffler:
82 prefs = {
83 'window width': min(700, gtk.gdk.screen_get_default().get_width() / 2),
84 'window height': min(600, gtk.gdk.screen_get_default().get_height() - 50),
85 'window x': 0,
86 'window y': 0,
87 'initial thumbnail size': 300,
88 'initial zoom level': -14,
89 }
90
91 MODEL_ROW_INTERN = 1001
92 MODEL_ROW_EXTERN = 1002
93 TEXT_URI_LIST = 1003
94 MODEL_ROW_MOTION = 1004
95 TARGETS_IV = [('MODEL_ROW_INTERN', gtk.TARGET_SAME_WIDGET, MODEL_ROW_INTERN),
96 ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN),
97 ('MODEL_ROW_MOTION', 0, MODEL_ROW_MOTION)]
98 TARGETS_SW = [('text/uri-list', 0, TEXT_URI_LIST),
99 ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN)]
100
101 def __init__(self):
102 # Create the temporary directory
103 self.tmp_dir = tempfile.mkdtemp("pdfshuffler")
104 os.chmod(self.tmp_dir, 0700)
105
106 icon_theme = gtk.icon_theme_get_default()
107 try:
108 gtk.window_set_default_icon(icon_theme.load_icon("pdfshuffler", 64, 0))
109 except:
110 print(_("Can't load icon. Application is not installed correctly."))
111
112 # Import the user interface file, trying different possible locations
113 ui_path = '/usr/share/pdfshuffler/pdfshuffler.ui'
114 if not os.path.exists(ui_path):
115 ui_path = '/usr/local/share/applications/pdfshuffler/pdfshuffler.ui'
116 if not os.path.exists(ui_path):
117 parent_dir = os.path.dirname( \
118 os.path.dirname(os.path.realpath(__file__)))
119 ui_path = os.path.join(parent_dir, 'data', 'pdfshuffler.ui')
120 self.uiXML = gtk.Builder()
121 self.uiXML.add_from_file(ui_path)
122 self.uiXML.connect_signals(self)
123
124 # Create the main window, and attach delete_event signal to terminating
125 # the application
126 self.window = self.uiXML.get_object('main_window')
127 self.window.set_title(APPNAME)
128 self.window.set_border_width(0)
129 self.window.move(self.prefs['window x'], self.prefs['window y'])
130 self.window.set_default_size(self.prefs['window width'],
131 self.prefs['window height'])
132 self.window.connect('delete_event', self.close_application)
133 self.window.show_all()
134
135 # Create a scrolled window to hold the thumbnails-container
136 self.sw = self.uiXML.get_object('scrolledwindow')
137 self.sw.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
138 gtk.DEST_DEFAULT_HIGHLIGHT |
139 gtk.DEST_DEFAULT_DROP |
140 gtk.DEST_DEFAULT_MOTION,
141 self.TARGETS_SW,
142 gtk.gdk.ACTION_COPY |
143 gtk.gdk.ACTION_MOVE)
144 self.sw.connect('drag_data_received', self.sw_dnd_received_data)
145 self.sw.connect('button_press_event', self.sw_button_press_event)
146 self.sw.connect('scroll_event', self.sw_scroll_event)
147
148 # Create an alignment to keep the thumbnails center-aligned
149 align = gtk.Alignment(0.5, 0.5, 0, 0)
150 self.sw.add_with_viewport(align)
151
152 # Create ListStore model and IconView
153 self.model = gtk.ListStore(str, # 0.Text descriptor
154 CairoImage, # 1.Cached page image
155 int, # 2.Document number
156 int, # 3.Page number
157 float, # 4.Scale
158 str, # 5.Document filename
159 int, # 6.Rotation angle
160 float, # 7.Crop left
161 float, # 8.Crop right
162 float, # 9.Crop top
163 float, # 10.Crop bottom
164 int, # 11.Page width
165 int) # 12.Page height
166
167 self.zoom_set(self.prefs['initial zoom level'])
168 self.iv_col_width = self.prefs['initial thumbnail size']
169
170 self.iconview = gtk.IconView(self.model)
171 self.iconview.set_item_width(self.iv_col_width + 12)
172
173 self.cellthmb = CellRendererImage()
174 self.iconview.pack_start(self.cellthmb, False)
175 self.iconview.set_attributes(self.cellthmb, image=1,
176 scale=4, rotation=6, cropL=7, cropR=8, cropT=9, cropB=10,
177 width=11, height=12)
178
179# self.iconview.set_text_column(0)
180 self.celltxt = gtk.CellRendererText()
181 self.celltxt.set_property('width', self.iv_col_width)
182 self.celltxt.set_property('wrap-width', self.iv_col_width)
183 self.celltxt.set_property('alignment', pango.ALIGN_CENTER)
184 self.iconview.pack_start(self.celltxt, False)
185 self.iconview.set_attributes(self.celltxt, text=0)
186
187 self.iconview.set_selection_mode(gtk.SELECTION_MULTIPLE)
188 self.iconview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
189 self.TARGETS_IV,
190 gtk.gdk.ACTION_COPY |
191 gtk.gdk.ACTION_MOVE)
192 self.iconview.enable_model_drag_dest(self.TARGETS_IV,
193 gtk.gdk.ACTION_DEFAULT)
194 self.iconview.connect('drag_begin', self.iv_drag_begin)
195 self.iconview.connect('drag_data_get', self.iv_dnd_get_data)
196 self.iconview.connect('drag_data_received', self.iv_dnd_received_data)
197 self.iconview.connect('drag_data_delete', self.iv_dnd_data_delete)
198 self.iconview.connect('drag_motion', self.iv_dnd_motion)
199 self.iconview.connect('drag_leave', self.iv_dnd_leave_end)
200 self.iconview.connect('drag_end', self.iv_dnd_leave_end)
201 self.iconview.connect('button_press_event', self.iv_button_press_event)
202 self.iv_auto_scroll_direction = 0
203
204 style = self.iconview.get_style().copy()
205 style_sw = self.sw.get_style()
206 for state in (gtk.STATE_NORMAL, gtk.STATE_PRELIGHT, gtk.STATE_ACTIVE):
207 style.base[state] = style_sw.bg[gtk.STATE_NORMAL]
208 self.iconview.set_style(style)
209
210 align.add(self.iconview)
211
212 # Progress bar
213 self.progress_bar = self.uiXML.get_object('progressbar')
214 self.progress_bar_timeout_id = 0
215
216 # Define window callback function and show window
217 self.window.connect('size_allocate', self.on_window_size_request) # resize
218 self.window.connect('key_press_event', self.on_keypress_event ) # keypress
219 self.window.show_all()
220 self.progress_bar.hide_all()
221
222 # Creating the popup menu
223 self.popup = gtk.Menu()
224 popup_rotate_right = gtk.ImageMenuItem(_('_Rotate Right'))
225 popup_rotate_left = gtk.ImageMenuItem(_('Rotate _Left'))
226 popup_crop = gtk.MenuItem(_('C_rop...'))
227 popup_delete = gtk.ImageMenuItem(gtk.STOCK_DELETE)
228 popup_rotate_right.connect('activate', self.rotate_page_right)
229 popup_rotate_left.connect('activate', self.rotate_page_left)
230 popup_crop.connect('activate', self.crop_page_dialog)
231 popup_delete.connect('activate', self.clear_selected)
232 popup_rotate_right.show()
233 popup_rotate_left.show()
234 popup_crop.show()
235 popup_delete.show()
236 self.popup.append(popup_rotate_right)
237 self.popup.append(popup_rotate_left)
238 self.popup.append(popup_crop)
239 self.popup.append(popup_delete)
240
241 # Initializing variables
242 self.export_directory = os.getenv('HOME')
243 self.import_directory = self.export_directory
244 self.nfile = 0
245 self.iv_auto_scroll_timer = None
246 self.pdfqueue = []
247
248 gobject.type_register(PDF_Renderer)
249 gobject.signal_new('update_thumbnail', PDF_Renderer,
250 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
251 [gobject.TYPE_INT, gobject.TYPE_PYOBJECT])
252 self.rendering_thread = 0
253
254 self.set_unsaved(False)
255
256 # Importing documents passed as command line arguments
257 for filename in sys.argv[1:]:
258 self.add_pdf_pages(filename)
259
260 def render(self):
261 if self.rendering_thread:
262 self.rendering_thread.quit = True
263 self.rendering_thread = PDF_Renderer(self.model, self.pdfqueue)
264 self.rendering_thread.connect('update_thumbnail', self.update_thumbnail)
265 self.rendering_thread.start()
266
267 if self.progress_bar_timeout_id:
268 gobject.source_remove(self.progress_bar_timeout_id)
269 self.progress_bar_timout_id = \
270 gobject.timeout_add(50, self.progress_bar_timeout)
271
272 def set_unsaved(self, flag):
273 self.is_unsaved = flag
274 gobject.idle_add(self.retitle)
275
276 def retitle(self):
277 title = ''
278 if len(self.pdfqueue) == 1:
279 title += self.pdfqueue[0].filename
280 elif len(self.pdfqueue) == 0:
281 title += _("No document")
282 else:
283 title += _("Several documents")
284 if self.is_unsaved:
285 title += '*'
286 title += ' - ' + APPNAME
287 self.window.set_title(title)
288
289 def progress_bar_timeout(self):
290 cnt_finished = 0
291 cnt_all = 0
292 for row in self.model:
293 cnt_all += 1
294 if row[1].surface:
295 cnt_finished += 1
296 fraction = float(cnt_finished)/float(cnt_all)
297
298 self.progress_bar.set_fraction(fraction)
299 self.progress_bar.set_text(_('Rendering thumbnails... [%(i1)s/%(i2)s]')
300 % {'i1' : cnt_finished, 'i2' : cnt_all})
301 if fraction >= 0.999:
302 self.progress_bar.hide_all()
303 return False
304 elif not self.progress_bar.flags() & gtk.VISIBLE:
305 self.progress_bar.show_all()
306
307 return True
308
309 def update_thumbnail(self, object, num, thumbnail):
310 row = self.model[num]
311 row[4] = self.zoom_scale
312 row[1] = thumbnail
313
314 def on_window_size_request(self, window, event):
315 """Main Window resize - workaround for autosetting of
316 iconview cols no."""
317
318 #add 12 because of: http://bugzilla.gnome.org/show_bug.cgi?id=570152
319 col_num = 9 * window.get_size()[0] \
320 / (10 * (self.iv_col_width + self.iconview.get_column_spacing() * 2))
321 self.iconview.set_columns(col_num)
322
323 def update_geometry(self, iter):
324 """Recomputes the width and height of the rotated page and saves
325 the result in the ListStore"""
326
327 if not self.model.iter_is_valid(iter):
328 return
329
330 nfile, npage, rotation = self.model.get(iter, 2, 3, 6)
331 crop = self.model.get(iter, 7, 8, 9, 10)
332 page = self.pdfqueue[nfile-1].document.get_page(npage-1)
333 w0, h0 = page.get_size()
334
335 rotation = int(rotation) % 360
336 rotation = ((rotation + 45) / 90) * 90
337 if rotation == 90 or rotation == 270:
338 w1, h1 = h0, w0
339 else:
340 w1, h1 = w0, h0
341
342 self.model.set(iter, 11, w1, 12, h1)
343
344 def reset_iv_width(self, renderer=None):
345 """Reconfigures the width of the iconview columns"""
346
347 if not self.model.get_iter_first(): #just checking if model is empty
348 return
349
350 max_w = 10 + int(max(row[4]*row[11]*(1.-row[7]-row[8]) for row in self.model))
351 if max_w != self.iv_col_width:
352 self.iv_col_width = max_w
353 self.celltxt.set_property('width', self.iv_col_width)
354 self.celltxt.set_property('wrap-width', self.iv_col_width)
355 self.iconview.set_item_width(self.iv_col_width + 12) #-1)
356 self.on_window_size_request(self.window, None)
357
358 def on_keypress_event(self, widget, event):
359 """Keypress events in Main Window"""
360
361 #keyname = gtk.gdk.keyval_name(event.keyval)
362 if event.keyval == 65535: # Delete keystroke
363 self.clear_selected()
364
365 def close_application(self, widget, event=None, data=None):
366 """Termination"""
367
368 if self.rendering_thread:
369 self.rendering_thread.quit = True
370 while self.rendering_thread.isAlive():
371 time.sleep(0.5)
372
373 if os.path.isdir(self.tmp_dir):
374 shutil.rmtree(self.tmp_dir)
375 if gtk.main_level():
376 gtk.main_quit()
377 else:
378 sys.exit(0)
379 return False
380
381 def add_pdf_pages(self, filename,
382 firstpage=None, lastpage=None,
383 angle=0, crop=[0.,0.,0.,0.]):
384 """Add pages of a pdf document to the model"""
385
386 res = False
387 # Check if the document has already been loaded
388 pdfdoc = None
389 for it_pdfdoc in self.pdfqueue:
390 if os.path.isfile(it_pdfdoc.filename) and \
391 os.path.samefile(filename, it_pdfdoc.filename) and \
392 os.path.getmtime(filename) is it_pdfdoc.mtime:
393 pdfdoc = it_pdfdoc
394 break
395
396 if not pdfdoc:
397 pdfdoc = PDF_Doc(filename, self.nfile, self.tmp_dir)
398 self.import_directory = os.path.split(filename)[0]
399 self.export_directory = self.import_directory
400 if pdfdoc.nfile != 0 and pdfdoc != []:
401 self.nfile = pdfdoc.nfile
402 self.pdfqueue.append(pdfdoc)
403 else:
404 return res
405
406 n_start = 1
407 n_end = pdfdoc.npage
408 if firstpage:
409 n_start = min(n_end, max(1, firstpage))
410 if lastpage:
411 n_end = max(n_start, min(n_end, lastpage))
412
413 for npage in range(n_start, n_end + 1):
414 descriptor = ''.join([pdfdoc.shortname, '\n', _('page'), ' ', str(npage)])
415 page = pdfdoc.document.get_page(npage-1)
416 w, h = page.get_size()
417 iter = self.model.append((descriptor, # 0
418 CairoImage(), # 1
419 pdfdoc.nfile, # 2
420 npage, # 3
421 self.zoom_scale, # 4
422 pdfdoc.filename, # 5
423 angle, # 6
424 crop[0],crop[1], # 7-8
425 crop[2],crop[3], # 9-10
426 w,h )) # 11-12
427 self.update_geometry(iter)
428 res = True
429
430 self.reset_iv_width()
431 gobject.idle_add(self.retitle)
432 if res:
433 gobject.idle_add(self.render)
434 return res
435
436 def choose_export_pdf_name(self, widget=None):
437 """Handles choosing a name for exporting """
438
439 chooser = gtk.FileChooserDialog(title=_('Export ...'),
440 action=gtk.FILE_CHOOSER_ACTION_SAVE,
441 buttons=(gtk.STOCK_CANCEL,
442 gtk.RESPONSE_CANCEL,
443 gtk.STOCK_SAVE,
444 gtk.RESPONSE_OK))
445 chooser.set_do_overwrite_confirmation(True)
446 chooser.set_current_folder(self.export_directory)
447 filter_pdf = gtk.FileFilter()
448 filter_pdf.set_name(_('PDF files'))
449 filter_pdf.add_mime_type('application/pdf')
450 chooser.add_filter(filter_pdf)
451
452 filter_all = gtk.FileFilter()
453 filter_all.set_name(_('All files'))
454 filter_all.add_pattern('*')
455 chooser.add_filter(filter_all)
456
457 while True:
458 response = chooser.run()
459 if response == gtk.RESPONSE_OK:
460 file_out = chooser.get_filename()
461 (path, shortname) = os.path.split(file_out)
462 (shortname, ext) = os.path.splitext(shortname)
463 if ext.lower() != '.pdf':
464 file_out = file_out + '.pdf'
465 try:
466 self.export_to_file(file_out)
467 self.export_directory = path
468 self.set_unsaved(False)
469 except Exception, e:
470 chooser.destroy()
471 error_msg_dlg = gtk.MessageDialog(flags=gtk.DIALOG_MODAL,
472 type=gtk.MESSAGE_ERROR,
473 message_format=str(e),
474 buttons=gtk.BUTTONS_OK)
475 response = error_msg_dlg.run()
476 if response == gtk.RESPONSE_OK:
477 error_msg_dlg.destroy()
478 return
479 break
480 chooser.destroy()
481
482 def export_to_file(self, file_out):
483 """Export to file"""
484
485 pdf_output = PdfFileWriter()
486 pdf_input = []
487 for pdfdoc in self.pdfqueue:
488 pdfdoc_inp = PdfFileReader(file(pdfdoc.copyname, 'rb'))
489 if pdfdoc_inp.getIsEncrypted():
490 try: # Workaround for lp:#355479
491 stat = pdfdoc_inp.decrypt('')
492 except:
493 stat = 0
494 if (stat!=1):
495 errmsg = _('File %s is encrypted.\n'
496 'Support for encrypted files has not been implemented yet.\n'
497 'File export failed.') % pdfdoc.filename
498 raise Exception, errmsg
499 #FIXME
500 #else
501 # ask for password and decrypt file
502 pdf_input.append(pdfdoc_inp)
503
504 for row in self.model:
505 # add pages from input to output document
506 nfile = row[2]
507 npage = row[3]
508 current_page = copy(pdf_input[nfile-1].getPage(npage-1))
509 angle = row[6]
510 angle0 = current_page.get("/Rotate",0)
511 crop = [row[7],row[8],row[9],row[10]]
512 if angle != 0:
513 current_page.rotateClockwise(angle)
514 if crop != [0.,0.,0.,0.]:
515 rotate_times = (((angle + angle0) % 360 + 45) / 90) % 4
516 crop_init = crop
517 if rotate_times != 0:
518 perm = [0,2,1,3]
519 for it in range(rotate_times):
520 perm.append(perm.pop(0))
521 perm.insert(1,perm.pop(2))
522 crop = [crop_init[perm[side]] for side in range(4)]
523 #(x1, y1) = current_page.cropBox.lowerLeft
524 #(x2, y2) = current_page.cropBox.upperRight
525 (x1, y1) = [float(xy) for xy in current_page.mediaBox.lowerLeft]
526 (x2, y2) = [float(xy) for xy in current_page.mediaBox.upperRight]
527 x1_new = int(x1 + (x2-x1) * crop[0])
528 x2_new = int(x2 - (x2-x1) * crop[1])
529 y1_new = int(y1 + (y2-y1) * crop[3])
530 y2_new = int(y2 - (y2-y1) * crop[2])
531 #current_page.cropBox.lowerLeft = (x1_new, y1_new)
532 #current_page.cropBox.upperRight = (x2_new, y2_new)
533 current_page.mediaBox.lowerLeft = (x1_new, y1_new)
534 current_page.mediaBox.upperRight = (x2_new, y2_new)
535
536 pdf_output.addPage(current_page)
537
538 # finally, write "output" to document-output.pdf
539 print(_('exporting to:'), file_out)
540 pdf_output.write(file(file_out, 'wb'))
541
542 def on_action_add_doc_activate(self, widget, data=None):
543 """Import doc"""
544
545 chooser = gtk.FileChooserDialog(title=_('_Import...'),
546 action=gtk.FILE_CHOOSER_ACTION_OPEN,
547 buttons=(gtk.STOCK_CANCEL,
548 gtk.RESPONSE_CANCEL,
549 gtk.STOCK_OPEN,
550 gtk.RESPONSE_OK))
551 chooser.set_current_folder(self.import_directory)
552 chooser.set_select_multiple(True)
553
554 filter_all = gtk.FileFilter()
555 filter_all.set_name(_('All files'))
556 filter_all.add_pattern('*')
557 chooser.add_filter(filter_all)
558
559 filter_pdf = gtk.FileFilter()
560 filter_pdf.set_name(_('PDF files'))
561 filter_pdf.add_mime_type('application/pdf')
562 chooser.add_filter(filter_pdf)
563 chooser.set_filter(filter_pdf)
564
565 response = chooser.run()
566 if response == gtk.RESPONSE_OK:
567 for filename in chooser.get_filenames():
568 if os.path.isfile(filename):
569 # FIXME
570 f = gio.File(filename)
571 f_info = f.query_info('standard::content-type')
572 mime_type = f_info.get_content_type()
573 expected_mime_type = 'application/pdf'
574
575 if mime_type == expected_mime_type:
576 self.add_pdf_pages(filename)
577 elif mime_type[:34] == 'application/vnd.oasis.opendocument':
578 print(_('OpenDocument not supported yet!'))
579 elif mime_type[:5] == 'image':
580 print(_('Image file not supported yet!'))
581 else:
582 print(_('File type not supported!'))
583 else:
584 print(_('File %s does not exist') % filename)
585 elif response == gtk.RESPONSE_CANCEL:
586 print(_('Closed, no files selected'))
587 chooser.destroy()
588 gobject.idle_add(self.retitle)
589
590 def clear_selected(self, button=None):
591 """Removes the selected elements in the IconView"""
592
593 model = self.iconview.get_model()
594 selection = self.iconview.get_selected_items()
595 if selection:
596 selection.sort(reverse=True)
597 self.set_unsaved(True)
598 for path in selection:
599 iter = model.get_iter(path)
600 model.remove(iter)
601 path = selection[-1]
602 self.iconview.select_path(path)
603 if not self.iconview.path_is_selected(path):
604 if len(model) > 0: # select the last row
605 row = model[-1]
606 path = row.path
607 self.iconview.select_path(path)
608 self.iconview.grab_focus()
609
610 def iv_drag_begin(self, iconview, context):
611 """Sets custom icon on drag begin for multiple items selected"""
612
613 if len(iconview.get_selected_items()) > 1:
614 iconview.stop_emission('drag_begin')
615 context.set_icon_stock(gtk.STOCK_DND_MULTIPLE, 0, 0)
616
617 def iv_dnd_get_data(self, iconview, context,
618 selection_data, target_id, etime):
619 """Handles requests for data by drag and drop in iconview"""
620
621 model = iconview.get_model()
622 selection = self.iconview.get_selected_items()
623 selection.sort(key=lambda x: x[0])
624 data = []
625 for path in selection:
626 if selection_data.target == 'MODEL_ROW_INTERN':
627 data.append(str(path[0]))
628 elif selection_data.target == 'MODEL_ROW_EXTERN':
629 iter = model.get_iter(path)
630 nfile, npage, angle = model.get(iter, 2, 3, 6)
631 crop = model.get(iter, 7, 8, 9, 10)
632 pdfdoc = self.pdfqueue[nfile - 1]
633 data.append('\n'.join([pdfdoc.filename,
634 str(npage),
635 str(angle)] +
636 [str(side) for side in crop]))
637 if data:
638 data = '\n;\n'.join(data)
639 selection_data.set(selection_data.target, 8, data)
640
641 def iv_dnd_received_data(self, iconview, context, x, y,
642 selection_data, target_id, etime):
643 """Handles received data by drag and drop in iconview"""
644
645 model = iconview.get_model()
646 data = selection_data.data
647 if data:
648 data = data.split('\n;\n')
649 drop_info = iconview.get_dest_item_at_pos(x, y)
650 iter_to = None
651 if drop_info:
652 path, position = drop_info
653 ref_to = gtk.TreeRowReference(model,path)
654 else:
655 position = gtk.ICON_VIEW_DROP_RIGHT
656 if len(model) > 0: #find the iterator of the last row
657 row = model[-1]
658 path = row.path
659 ref_to = gtk.TreeRowReference(model,path)
660 if ref_to:
661 before = (position == gtk.ICON_VIEW_DROP_LEFT
662 or position == gtk.ICON_VIEW_DROP_ABOVE)
663 #if target_id == self.MODEL_ROW_INTERN:
664 if selection_data.target == 'MODEL_ROW_INTERN':
665 if before:
666 data.sort(key=int)
667 else:
668 data.sort(key=int,reverse=True)
669 ref_from_list = [gtk.TreeRowReference(model,path)
670 for path in data]
671 for ref_from in ref_from_list:
672 path = ref_to.get_path()
673 iter_to = model.get_iter(path)
674 path = ref_from.get_path()
675 iter_from = model.get_iter(path)
676 row = model[iter_from]
677 if before:
678 model.insert_before(iter_to, row)
679 else:
680 model.insert_after(iter_to, row)
681 if context.action == gtk.gdk.ACTION_MOVE:
682 for ref_from in ref_from_list:
683 path = ref_from.get_path()
684 iter_from = model.get_iter(path)
685 model.remove(iter_from)
686
687 #elif target_id == self.MODEL_ROW_EXTERN:
688 elif selection_data.target == 'MODEL_ROW_EXTERN':
689 if not before:
690 data.reverse()
691 while data:
692 tmp = data.pop(0).split('\n')
693 filename = tmp[0]
694 npage, angle = [int(k) for k in tmp[1:3]]
695 crop = [float(side) for side in tmp[3:7]]
696 if self.add_pdf_pages(filename, npage, npage,
697 angle, crop):
698 if len(model) > 0:
699 path = ref_to.get_path()
700 iter_to = model.get_iter(path)
701 row = model[-1] #the last row
702 path = row.path
703 iter_from = model.get_iter(path)
704 if before:
705 model.move_before(iter_from, iter_to)
706 else:
707 model.move_after(iter_from, iter_to)
708 if context.action == gtk.gdk.ACTION_MOVE:
709 context.finish(True, True, etime)
710
711 def iv_dnd_data_delete(self, widget, context):
712 """Deletes dnd items after a successful move operation"""
713
714 model = self.iconview.get_model()
715 selection = self.iconview.get_selected_items()
716 ref_del_list = [gtk.TreeRowReference(model,path) for path in selection]
717 for ref_del in ref_del_list:
718 path = ref_del.get_path()
719 iter = model.get_iter(path)
720 model.remove(iter)
721
722 def iv_dnd_motion(self, iconview, context, x, y, etime):
723 """Handles the drag-motion signal in order to auto-scroll the view"""
724
725 autoscroll_area = 40
726 sw_vadj = self.sw.get_vadjustment()
727 sw_height = self.sw.get_allocation().height
728 if y -sw_vadj.get_value() < autoscroll_area:
729 if not self.iv_auto_scroll_timer:
730 self.iv_auto_scroll_direction = gtk.DIR_UP
731 self.iv_auto_scroll_timer = gobject.timeout_add(150,
732 self.iv_auto_scroll)
733 elif y -sw_vadj.get_value() > sw_height - autoscroll_area:
734 if not self.iv_auto_scroll_timer:
735 self.iv_auto_scroll_direction = gtk.DIR_DOWN
736 self.iv_auto_scroll_timer = gobject.timeout_add(150,
737 self.iv_auto_scroll)
738 elif self.iv_auto_scroll_timer:
739 gobject.source_remove(self.iv_auto_scroll_timer)
740 self.iv_auto_scroll_timer = None
741
742 def iv_dnd_leave_end(self, widget, context, ignored=None):
743 """Ends the auto-scroll during DND"""
744
745 if self.iv_auto_scroll_timer:
746 gobject.source_remove(self.iv_auto_scroll_timer)
747 self.iv_auto_scroll_timer = None
748
749 def iv_auto_scroll(self):
750 """Timeout routine for auto-scroll"""
751
752 sw_vadj = self.sw.get_vadjustment()
753 sw_vpos = sw_vadj.get_value()
754 if self.iv_auto_scroll_direction == gtk.DIR_UP:
755 sw_vpos -= sw_vadj.step_increment
756 sw_vadj.set_value(max(sw_vpos, sw_vadj.lower))
757 elif self.iv_auto_scroll_direction == gtk.DIR_DOWN:
758 sw_vpos += sw_vadj.step_increment
759 sw_vadj.set_value(min(sw_vpos, sw_vadj.upper - sw_vadj.page_size))
760 return True #call me again
761
762 def iv_button_press_event(self, iconview, event):
763 """Manages mouse clicks on the iconview"""
764
765 if event.button == 3:
766 x = int(event.x)
767 y = int(event.y)
768 time = event.time
769 path = iconview.get_path_at_pos(x, y)
770 selection = iconview.get_selected_items()
771 if path:
772 if path not in selection:
773 iconview.unselect_all()
774 iconview.select_path(path)
775 iconview.grab_focus()
776 self.popup.popup(None, None, None, event.button, time)
777 return 1
778
779 def sw_dnd_received_data(self, scrolledwindow, context, x, y,
780 selection_data, target_id, etime):
781 """Handles received data by drag and drop in scrolledwindow"""
782
783 data = selection_data.data
784 if target_id == self.MODEL_ROW_EXTERN:
785 self.model
786 if data:
787 data = data.split('\n;\n')
788 while data:
789 tmp = data.pop(0).split('\n')
790 filename = tmp[0]
791 npage, angle = [int(k) for k in tmp[1:3]]
792 crop = [float(side) for side in tmp[3:7]]
793 if self.add_pdf_pages(filename, npage, npage, angle, crop):
794 if context.action == gtk.gdk.ACTION_MOVE:
795 context.finish(True, True, etime)
796 elif target_id == self.TEXT_URI_LIST:
797 uri = data.strip()
798 uri_splitted = uri.split() # we may have more than one file dropped
799 for uri in uri_splitted:
800 filename = self.get_file_path_from_dnd_dropped_uri(uri)
801 if os.path.isfile(filename): # is it file?
802 self.add_pdf_pages(filename)
803
804 def sw_button_press_event(self, scrolledwindow, event):
805 """Unselects all items in iconview on mouse click in scrolledwindow"""
806
807 if event.button == 1:
808 self.iconview.unselect_all()
809
810 def sw_scroll_event(self, scrolledwindow, event):
811 """Manages mouse scroll events in scrolledwindow"""
812
813 if event.state & gtk.gdk.CONTROL_MASK:
814 if event.direction == gtk.gdk.SCROLL_UP:
815 self.zoom_change(1)
816 return 1
817 elif event.direction == gtk.gdk.SCROLL_DOWN:
818 self.zoom_change(-1)
819 return 1
820
821 def zoom_set(self, level):
822 """Sets the zoom level"""
823 self.zoom_level = max(min(level, 5), -24)
824 self.zoom_scale = 1.1 ** self.zoom_level
825 for row in self.model:
826 row[4] = self.zoom_scale
827 self.reset_iv_width()
828
829 def zoom_change(self, step=5):
830 """Modifies the zoom level"""
831 self.zoom_set(self.zoom_level + step)
832
833 def zoom_in(self, widget=None):
834 """Increases the zoom level by 5 steps"""
835 self.zoom_change(5)
836
837 def zoom_out(self, widget=None, step=5):
838 """Reduces the zoom level by 5 steps"""
839 self.zoom_change(-5)
840
841 def get_file_path_from_dnd_dropped_uri(self, uri):
842 """Extracts the path from an uri"""
843
844 path = urllib.url2pathname(uri) # escape special chars
845 path = path.strip('\r\n\x00') # remove \r\n and NULL
846
847 # get the path to file
848 if path.startswith('file:\\\\\\'): # windows
849 path = path[8:] # 8 is len('file:///')
850 elif path.startswith('file://'): # nautilus, rox
851 path = path[7:] # 7 is len('file://')
852 elif path.startswith('file:'): # xffm
853 path = path[5:] # 5 is len('file:')
854 return path
855
856 def rotate_page_right(self, widget, data=None):
857 self.rotate_page(90)
858
859 def rotate_page_left(self, widget, data=None):
860 self.rotate_page(-90)
861
862 def rotate_page(self, angle):
863 """Rotates the selected page in the IconView"""
864
865 model = self.iconview.get_model()
866 selection = self.iconview.get_selected_items()
867 if len(selection) > 0:
868 self.set_unsaved(True)
869 rotate_times = (((-angle) % 360 + 45) / 90) % 4
870 if rotate_times is not 0:
871 for path in selection:
872 iter = model.get_iter(path)
873 nfile = model.get_value(iter, 2)
874 npage = model.get_value(iter, 3)
875
876 crop = [0.,0.,0.,0.]
877 perm = [0,2,1,3]
878 for it in range(rotate_times):
879 perm.append(perm.pop(0))
880 perm.insert(1,perm.pop(2))
881 crop = [model.get_value(iter, 7 + perm[side]) for side in range(4)]
882 for side in range(4):
883 model.set_value(iter, 7 + side, crop[side])
884
885 new_angle = model.get_value(iter, 6) + int(angle)
886 new_angle = new_angle % 360
887 model.set_value(iter, 6, new_angle)
888 self.update_geometry(iter)
889 self.reset_iv_width()
890
891 def crop_page_dialog(self, widget):
892 """Opens a dialog box to define margins for page cropping"""
893
894 sides = ('L', 'R', 'T', 'B')
895 side_names = {'L':_('Left'), 'R':_('Right'),
896 'T':_('Top'), 'B':_('Bottom') }
897 opposite_sides = {'L':'R', 'R':'L', 'T':'B', 'B':'T' }
898
899 def set_crop_value(spinbutton, side):
900 opp_side = opposite_sides[side]
901 pos = sides.index(opp_side)
902 adj = spin_list[pos].get_adjustment()
903 adj.set_upper(99.0 - spinbutton.get_value())
904
905 model = self.iconview.get_model()
906 selection = self.iconview.get_selected_items()
907
908 crop = [0.,0.,0.,0.]
909 if selection:
910 path = selection[0]
911 pos = model.get_iter(path)
912 crop = [model.get_value(pos, 7 + side) for side in range(4)]
913
914 dialog = gtk.Dialog(title=(_('Crop Selected Pages')),
915 parent=self.window,
916 flags=gtk.DIALOG_MODAL,
917 buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
918 gtk.STOCK_OK, gtk.RESPONSE_OK))
919 dialog.set_size_request(340, 250)
920 dialog.set_default_response(gtk.RESPONSE_OK)
921
922 frame = gtk.Frame(_('Crop Margins'))
923 dialog.vbox.pack_start(frame, False, False, 20)
924
925 vbox = gtk.VBox(False, 0)
926 frame.add(vbox)
927
928 spin_list = []
929 units = 2 * [_('% of width')] + 2 * [_('% of height')]
930 for side in sides:
931 hbox = gtk.HBox(True, 0)
932 vbox.pack_start(hbox, False, False, 5)
933
934 label = gtk.Label(side_names[side])
935 label.set_alignment(0, 0.0)
936 hbox.pack_start(label, True, True, 20)
937
938 adj = gtk.Adjustment(100.*crop.pop(0), 0.0, 99.0, 1.0, 5.0, 0.0)
939 spin = gtk.SpinButton(adj, 0, 1)
940 spin.set_activates_default(True)
941 spin.connect('value-changed', set_crop_value, side)
942 spin_list.append(spin)
943 hbox.pack_start(spin, False, False, 30)
944
945 label = gtk.Label(units.pop(0))
946 label.set_alignment(0, 0.0)
947 hbox.pack_start(label, True, True, 0)
948
949 dialog.show_all()
950 result = dialog.run()
951
952 if result == gtk.RESPONSE_OK:
953 modified = False
954 crop = [spin.get_value()/100. for spin in spin_list]
955 for path in selection:
956 pos = model.get_iter(path)
957 for it in range(4):
958 old_val = model.get_value(pos, 7 + it)
959 model.set_value(pos, 7 + it, crop[it])
960 if crop[it] != old_val:
961 modified = True
962 self.update_geometry(pos)
963 if modified:
964 self.set_unsaved(True)
965 self.reset_iv_width()
966 elif result == gtk.RESPONSE_CANCEL:
967 print(_('Dialog closed'))
968 dialog.destroy()
969
970 def about_dialog(self, widget, data=None):
971 about_dialog = gtk.AboutDialog()
972 try:
973 about_dialog.set_transient_for(self.window)
974 about_dialog.set_modal(True)
975 except:
976 pass
977 # FIXME
978 about_dialog.set_name(APPNAME)
979 about_dialog.set_version(VERSION)
980 about_dialog.set_comments(_(
981 '%s is a tool for rearranging and modifying PDF files.' \
982 'Developed using GTK+ and Python') % APPNAME)
983 about_dialog.set_authors(['Konstantinos Poulios',])
984 about_dialog.set_website_label(WEBSITE)
985 about_dialog.set_logo_icon_name('pdfshuffler')
986 about_dialog.set_license(LICENSE)
987 about_dialog.connect('response', lambda w, a: about_dialog.destroy())
988 about_dialog.connect('delete_event', lambda w, a: about_dialog.destroy())
989 about_dialog.show_all()
990
991
992class PDF_Doc:
993 """Class handling PDF documents"""
994
995 def __init__(self, filename, nfile, tmp_dir):
996
997 self.filename = os.path.abspath(filename)
998 (self.path, self.shortname) = os.path.split(self.filename)
999 (self.shortname, self.ext) = os.path.splitext(self.shortname)
1000 f = gio.File(filename)
1001 mime_type = f.query_info('standard::content-type').get_content_type()
1002 expected_mime_type = 'application/pdf'
1003 file_prefix = 'file://'
1004
1005 if mime_type == expected_mime_type:
1006 self.nfile = nfile + 1
1007 self.mtime = os.path.getmtime(filename)
1008 self.copyname = os.path.join(tmp_dir, '%02d_' % self.nfile +
1009 self.shortname + '.pdf')
1010 shutil.copy(self.filename, self.copyname)
1011 self.document = poppler.document_new_from_file (file_prefix + self.copyname, None)
1012 self.npage = self.document.get_n_pages()
1013 else:
1014 self.nfile = 0
1015 self.npage = 0
1016
1017
1018class PDF_Renderer(threading.Thread,gobject.GObject):
1019
1020 def __init__(self, model, pdfqueue):
1021 threading.Thread.__init__(self)
1022 gobject.GObject.__init__(self)
1023 self.model = model
1024 self.pdfqueue = pdfqueue
1025 self.quit = False
1026
1027 def run(self):
1028 for idx, row in enumerate(self.model):
1029 if self.quit:
1030 return
1031 if not row[1].surface:
1032 try:
1033 nfile = row[2]
1034 npage = row[3]
1035 pdfdoc = self.pdfqueue[nfile - 1]
1036 page = pdfdoc.document.get_page(npage-1)
1037 w, h = page.get_size()
1038 thumbnail = CairoImage(int(w), int(h))
1039 cr = cairo.Context(thumbnail.surface)
1040 page.render(cr)
1041 time.sleep(0.003)
1042 gobject.idle_add(self.emit,'update_thumbnail', idx, thumbnail,
1043 priority=gobject.PRIORITY_LOW)
1044 except Exception,e:
1045 print e
1046
1047
1048def main():
1049 """This function starts PdfShuffler"""
1050 gtk.gdk.threads_init()
1051 gobject.threads_init()
1052 PdfShuffler()
1053 gtk.main()
1054
1055if __name__ == '__main__':
1056 main()
1057
01058
=== added file '.pc/install_and_look_for_UI_file_in_right_dirs.patch/setup.py'
--- .pc/install_and_look_for_UI_file_in_right_dirs.patch/setup.py 1970-01-01 00:00:00 +0000
+++ .pc/install_and_look_for_UI_file_in_right_dirs.patch/setup.py 2012-02-12 00:35:23 +0000
@@ -0,0 +1,66 @@
1#!/usr/bin/python
2
3#
4# PdfShuffler 0.6.0 - GTK+ based utility for splitting, rearrangement and
5# modification of PDF documents.
6# Copyright (C) 2008-2011 Konstantinos Poulios
7# <https://sourceforge.net/projects/pdfshuffler>
8#
9# This program is free software; you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation; either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License along
20# with this program; if not, write to the Free Software Foundation, Inc.,
21# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22#
23
24import os
25import re
26from distutils.core import setup
27
28data_files=[('share/applications/pdfshuffler', ['data/pdfshuffler.ui']),
29 ('share/applications', ['data/pdfshuffler.desktop']),
30 ('share/man/man1', ['doc/pdfshuffler.1']),
31 ('share/pixmaps', ['data/pdfshuffler.svg']),
32 ('share/pixmaps', ['data/pdfshuffler.png']) ]
33
34
35# Freshly generate .mo from .po, add to data_files:
36if os.path.isdir('mo/'):
37 os.system ('rm -r mo/')
38for name in os.listdir('po'):
39 m = re.match(r'(.+)\.po$', name)
40 if m != None:
41 lang = m.group(1)
42 out_dir = 'mo/%s/LC_MESSAGES' % lang
43 out_name = os.path.join(out_dir, 'pdfshuffler.mo')
44 install_dir = 'share/locale/%s/LC_MESSAGES/' % lang
45 os.makedirs(out_dir)
46 os.system('msgfmt -o %s po/%s' % (out_name, name))
47 data_files.append((install_dir, [out_name]))
48
49setup(name='pdfshuffler',
50 version='0.6.0',
51 author='Konstantinos Poulios',
52 author_email='logari81 at gmail dot com',
53 description='A simple application for PDF Merging, Rearranging, and Splitting',
54 url = 'https://sourceforge.net/projects/pdfshuffler',
55 license='GNU GPL-3',
56 scripts=['bin/pdfshuffler'],
57 packages=['pdfshuffler'],
58 data_files=data_files
59 )
60
61# Clean up temporary files
62if os.path.isdir('mo/'):
63 os.system ('rm -r mo/')
64if os.path.isdir('build/'):
65 os.system ('rm -r build/')
66
067
=== modified file 'TODO'
--- TODO 2011-02-15 00:43:28 +0000
+++ TODO 2012-02-12 00:35:23 +0000
@@ -11,5 +11,3 @@
1111
12* Add support for encrypted files.12* Add support for encrypted files.
1313
14* Show a progress bar while loading a document.
15
1614
=== added directory 'bin'
=== added file 'bin/pdfshuffler'
--- bin/pdfshuffler 1970-01-01 00:00:00 +0000
+++ bin/pdfshuffler 2012-02-12 00:35:23 +0000
@@ -0,0 +1,33 @@
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4"""
5
6 PdfShuffler 0.6.0 - GTK+ based utility for splitting, rearrangement and
7 modification of PDF documents.
8 Copyright (C) 2008-2011 Konstantinos Poulios
9 <https://sourceforge.net/projects/pdfshuffler>
10
11 This file is part of PdfShuffler.
12
13 PdfShuffler is free software; you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation; either version 3 of the License, or
16 (at your option) any later version.
17
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License along
24 with this program; if not, write to the Free Software Foundation, Inc.,
25 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26
27"""
28
29try:
30 from pdfshuffler.pdfshuffler import main
31 main()
32except ImportError:
33 print('Error: could not import pdfshuffler')
034
=== added file 'data/pdfshuffler.ui'
--- data/pdfshuffler.ui 1970-01-01 00:00:00 +0000
+++ data/pdfshuffler.ui 2012-02-12 00:35:23 +0000
@@ -0,0 +1,359 @@
1<?xml version="1.0"?>
2<interface>
3 <requires lib="gtk+" version="2.16"/>
4 <!-- interface-naming-policy project-wide -->
5 <object class="GtkWindow" id="main_window">
6 <child>
7 <object class="GtkVBox" id="main_vbox">
8 <property name="visible">True</property>
9 <child>
10 <object class="GtkMenuBar" id="menubar">
11 <property name="visible">True</property>
12 <child>
13 <object class="GtkMenuItem" id="menuitem_file">
14 <property name="visible">True</property>
15 <property name="label" translatable="yes">_File</property>
16 <property name="use_underline">True</property>
17 <child type="submenu">
18 <object class="GtkMenu" id="menu_file">
19 <property name="visible">True</property>
20 <child>
21 <object class="GtkImageMenuItem" id="imagemenuitem_import">
22 <property name="label">gtk-open</property>
23 <property name="visible">True</property>
24 <property name="use_underline">True</property>
25 <property name="use_stock">True</property>
26 <signal name="activate" handler="on_action_add_doc_activate"/>
27 </object>
28 </child>
29 <child>
30 <object class="GtkImageMenuItem" id="imagemenuitem_save">
31 <property name="label">gtk-save</property>
32 <property name="visible">True</property>
33 <property name="sensitive">False</property>
34 <property name="use_underline">True</property>
35 <property name="use_stock">True</property>
36 </object>
37 </child>
38 <child>
39 <object class="GtkImageMenuItem" id="imagemenuitem_save_as">
40 <property name="label">gtk-save-as</property>
41 <property name="visible">True</property>
42 <property name="use_underline">True</property>
43 <property name="use_stock">True</property>
44 <signal name="activate" handler="choose_export_pdf_name"/>
45 </object>
46 </child>
47 <child>
48 <object class="GtkSeparatorMenuItem" id="separatormenuitem_file">
49 <property name="visible">True</property>
50 </object>
51 </child>
52 <child>
53 <object class="GtkImageMenuItem" id="imagemenuitem_quit">
54 <property name="label">gtk-quit</property>
55 <property name="visible">True</property>
56 <property name="use_underline">True</property>
57 <property name="use_stock">True</property>
58 <signal name="activate" handler="close_application"/>
59 </object>
60 </child>
61 </object>
62 </child>
63 </object>
64 </child>
65 <child>
66 <object class="GtkMenuItem" id="menuitem_edit">
67 <property name="visible">True</property>
68 <property name="label" translatable="yes">_Edit</property>
69 <property name="use_underline">True</property>
70 <child type="submenu">
71 <object class="GtkMenu" id="menu_edit">
72 <property name="visible">True</property>
73 <child>
74 <object class="GtkImageMenuItem" id="imagemenuitem_undo">
75 <property name="label">gtk-undo</property>
76 <property name="sensitive">False</property>
77 <property name="use_underline">True</property>
78 <property name="use_stock">True</property>
79 </object>
80 </child>
81 <child>
82 <object class="GtkImageMenuItem" id="imagemenuitem_redo">
83 <property name="label">gtk-redo</property>
84 <property name="sensitive">False</property>
85 <property name="use_underline">True</property>
86 <property name="use_stock">True</property>
87 </object>
88 </child>
89 <child>
90 <object class="GtkSeparatorMenuItem" id="separatormenuitem_edit1">
91 <property name="sensitive">False</property>
92 </object>
93 </child>
94 <child>
95 <object class="GtkImageMenuItem" id="imagemenuitem_rotate_left">
96 <property name="label" translatable="yes">Rotate left</property>
97 <property name="visible">True</property>
98 <property name="use_stock">False</property>
99 <signal name="activate" handler="rotate_page_left"/>
100 </object>
101 </child>
102 <child>
103 <object class="GtkImageMenuItem" id="imagemenuitem_rotate_right">
104 <property name="label" translatable="yes">Rotate right</property>
105 <property name="visible">True</property>
106 <property name="use_stock">False</property>
107 <signal name="activate" handler="rotate_page_right"/>
108 </object>
109 </child>
110 <child>
111 <object class="GtkSeparatorMenuItem" id="separatormenuitem_edit2">
112 <property name="visible">True</property>
113 </object>
114 </child>
115 <child>
116 <object class="GtkImageMenuItem" id="imagemenuitem_crop">
117 <property name="label" translatable="yes">Crop</property>
118 <property name="visible">True</property>
119 <property name="use_stock">False</property>
120 <signal name="activate" handler="crop_page_dialog"/>
121 </object>
122 </child>
123 <child>
124 <object class="GtkImageMenuItem" id="imagemenuitem_delete">
125 <property name="label">gtk-delete</property>
126 <property name="visible">True</property>
127 <property name="use_underline">True</property>
128 <property name="use_stock">True</property>
129 <signal name="activate" handler="clear_selected"/>
130 </object>
131 </child>
132 </object>
133 </child>
134 </object>
135 </child>
136 <child>
137 <object class="GtkMenuItem" id="menuitem_view">
138 <property name="visible">True</property>
139 <property name="label" translatable="yes">_View</property>
140 <property name="use_underline">True</property>
141 <child type="submenu">
142 <object class="GtkMenu" id="menu_view">
143 <property name="visible">True</property>
144 <child>
145 <object class="GtkImageMenuItem" id="imagemenuitem_zoomin">
146 <property name="label">Zoom in</property>
147 <property name="visible">True</property>
148 <property name="use_underline">True</property>
149 <property name="use_stock">True</property>
150 <signal name="activate" handler="zoom_in"/>
151 </object>
152 </child>
153 <child>
154 <object class="GtkImageMenuItem" id="imagemenuitem_zoomout">
155 <property name="label">Zoom out</property>
156 <property name="visible">True</property>
157 <property name="use_underline">True</property>
158 <property name="use_stock">True</property>
159 <signal name="activate" handler="zoom_out"/>
160 </object>
161 </child>
162 </object>
163 </child>
164 </object>
165 </child>
166 <child>
167 <object class="GtkMenuItem" id="menuitem_help">
168 <property name="visible">True</property>
169 <property name="label" translatable="yes">_Help</property>
170 <property name="use_underline">True</property>
171 <child type="submenu">
172 <object class="GtkMenu" id="menu_help">
173 <property name="visible">True</property>
174 <child>
175 <object class="GtkImageMenuItem" id="imagemenuitem_about">
176 <property name="label">gtk-about</property>
177 <property name="visible">True</property>
178 <property name="use_underline">True</property>
179 <property name="use_stock">True</property>
180 <signal name="activate" handler="about_dialog"/>
181 </object>
182 </child>
183 </object>
184 </child>
185 </object>
186 </child>
187 </object>
188 <packing>
189 <property name="expand">False</property>
190 <property name="position">0</property>
191 </packing>
192 </child>
193 <child>
194 <object class="GtkHBox" id="hbox_toolbar">
195 <property name="visible">True</property>
196 <child>
197 <object class="GtkToolbar" id="toolbar">
198 <property name="visible">True</property>
199 <child>
200 <object class="GtkToolButton" id="toolbutton_open">
201 <property name="visible">True</property>
202 <property name="label" translatable="yes">Open</property>
203 <property name="use_underline">True</property>
204 <property name="stock_id">gtk-open</property>
205 <signal name="clicked" handler="on_action_add_doc_activate"/>
206 </object>
207 <packing>
208 <property name="expand">False</property>
209 <property name="homogeneous">True</property>
210 </packing>
211 </child>
212 <child>
213 <object class="GtkToolButton" id="toolbutton_save">
214 <property name="visible">True</property>
215 <property name="label" translatable="yes">Save</property>
216 <property name="use_underline">True</property>
217 <property name="stock_id">gtk-save</property>
218 </object>
219 <packing>
220 <property name="expand">False</property>
221 <property name="homogeneous">True</property>
222 </packing>
223 </child>
224 <child>
225 <object class="GtkToolButton" id="toolbutton_save_as">
226 <property name="visible">True</property>
227 <property name="label" translatable="yes">Save as</property>
228 <property name="use_underline">True</property>
229 <property name="stock_id">gtk-save-as</property>
230 <signal name="clicked" handler="choose_export_pdf_name"/>
231 </object>
232 <packing>
233 <property name="expand">False</property>
234 <property name="homogeneous">True</property>
235 </packing>
236 </child>
237 <child>
238 <object class="GtkSeparatorToolItem" id="toolbutton_separator1">
239 <property name="visible">True</property>
240 </object>
241 <packing>
242 <property name="expand">False</property>
243 <property name="homogeneous">True</property>
244 </packing>
245 </child>
246 <child>
247 <object class="GtkToolButton" id="toolbutton_zoom_in">
248 <property name="visible">True</property>
249 <property name="label" translatable="yes">Zoom in</property>
250 <property name="use_underline">True</property>
251 <property name="stock_id">gtk-zoom-in</property>
252 <signal name="clicked" handler="zoom_in"/>
253 </object>
254 <packing>
255 <property name="expand">False</property>
256 <property name="homogeneous">True</property>
257 </packing>
258 </child>
259 <child>
260 <object class="GtkToolButton" id="toolbutton_zoom_out">
261 <property name="visible">True</property>
262 <property name="label" translatable="yes">Zoom out</property>
263 <property name="use_underline">True</property>
264 <property name="stock_id">gtk-zoom-out</property>
265 <signal name="clicked" handler="zoom_out"/>
266 </object>
267 <packing>
268 <property name="expand">False</property>
269 <property name="homogeneous">True</property>
270 </packing>
271 </child>
272 <child>
273 <object class="GtkSeparatorToolItem" id="toolbutton_separator2">
274 <property name="visible">True</property>
275 </object>
276 <packing>
277 <property name="expand">False</property>
278 <property name="homogeneous">True</property>
279 </packing>
280 </child>
281 <child>
282 <object class="GtkToolButton" id="toolbutton_rotate_left">
283 <property name="visible">True</property>
284 <property name="label" translatable="yes">Rotate left</property>
285 <property name="use_underline">True</property>
286 <property name="icon_name">object-rotate-left</property>
287 <signal name="clicked" handler="rotate_page_left"/>
288 </object>
289 <packing>
290 <property name="expand">False</property>
291 <property name="homogeneous">True</property>
292 </packing>
293 </child>
294 <child>
295 <object class="GtkToolButton" id="toolbutton_rotate_right">
296 <property name="visible">True</property>
297 <property name="label" translatable="yes">Rotate right</property>
298 <property name="use_underline">True</property>
299 <property name="icon_name">object-rotate-right</property>
300 <signal name="clicked" handler="rotate_page_right"/>
301 </object>
302 <packing>
303 <property name="expand">False</property>
304 <property name="homogeneous">True</property>
305 </packing>
306 </child>
307 <child>
308 <object class="GtkToolButton" id="toolbutton_delete">
309 <property name="visible">True</property>
310 <property name="label" translatable="yes">Delete</property>
311 <property name="use_underline">True</property>
312 <property name="stock_id">gtk-delete</property>
313 <signal name="clicked" handler="clear_selected"/>
314 </object>
315 <packing>
316 <property name="expand">False</property>
317 <property name="homogeneous">True</property>
318 </packing>
319 </child>
320 </object>
321 <packing>
322 <property name="position">0</property>
323 </packing>
324 </child>
325 </object>
326 <packing>
327 <property name="expand">False</property>
328 <property name="position">1</property>
329 </packing>
330 </child>
331 <child>
332 <object class="GtkScrolledWindow" id="scrolledwindow">
333 <property name="visible">True</property>
334 <property name="can_focus">True</property>
335 <property name="hscrollbar_policy">never</property>
336 <property name="vscrollbar_policy">automatic</property>
337 <child>
338 <placeholder/>
339 </child>
340 </object>
341 <packing>
342 <property name="position">2</property>
343 </packing>
344 </child>
345 <child>
346 <object class="GtkProgressBar" id="progressbar">
347 <property name="height_request">20</property>
348 <property name="visible">True</property>
349 </object>
350 <packing>
351 <property name="expand">False</property>
352 <property name="padding">5</property>
353 <property name="position">3</property>
354 </packing>
355 </child>
356 </object>
357 </child>
358 </object>
359</interface>
0360
=== modified file 'debian/changelog'
--- debian/changelog 2011-12-31 02:06:27 +0000
+++ debian/changelog 2012-02-12 00:35:23 +0000
@@ -1,3 +1,14 @@
1pdfshuffler (0.5.99~svn64-0ubuntu1) UNRELEASED; urgency=low
2
3 * New upstream snapshot. (LP: #930819)
4 * This version fixes a annoying bug due to the new version of poppler
5 * debian/patches:
6 - Removed the previous patch (fix-shebang: now in upstream)
7 - Added a patch to install pdfshuffler in /usr/share/data as required
8 by pdfshuffler.py
9
10 -- Matthieu Baerts (matttbe) <matttbe@gmail.com> Sun, 12 Feb 2012 01:27:07 +0100
11
1pdfshuffler (0.5.1-2build1) precise; urgency=low12pdfshuffler (0.5.1-2build1) precise; urgency=low
213
3 * Rebuild to drop python2.6 dependencies.14 * Rebuild to drop python2.6 dependencies.
415
=== removed file 'debian/patches/fix-shebang'
--- debian/patches/fix-shebang 2010-01-24 17:04:08 +0000
+++ debian/patches/fix-shebang 1970-01-01 00:00:00 +0000
@@ -1,12 +0,0 @@
1# Description: fix shebang
2# Forward: not applicable
3# Author: Serafeim Zanikolas <serzan@hellug.gr>
4# Last-Update: 2010-01-24
5--- pdfshuffler-0.3.1.orig/pdfshuffler 2009-02-08 20:59:26.000000000 +0000
6+++ pdfshuffler-0.3.1/pdfshuffler 2009-02-08 20:59:27.000000000 +0000
7@@ -1,4 +1,4 @@
8-#!/usr/bin/env python
9+#!/usr/bin/python
10 # -*- coding: utf-8 -*-
11
12 """
130
=== added file 'debian/patches/install_and_look_for_UI_file_in_right_dirs.patch'
--- debian/patches/install_and_look_for_UI_file_in_right_dirs.patch 1970-01-01 00:00:00 +0000
+++ debian/patches/install_and_look_for_UI_file_in_right_dirs.patch 2012-02-12 00:35:23 +0000
@@ -0,0 +1,31 @@
1# Description: Install and look for the UI file in the right dirs
2# Forward: yes
3# Author: Matthieu Baerts (matttbe) <matttbe@gmail.com>
4# Last-Update: 2012-02-12
5
6Index: pdfshuffler/pdfshuffler/pdfshuffler.py
7===================================================================
8--- pdfshuffler.orig/pdfshuffler/pdfshuffler.py 2012-02-12 01:15:27.115323000 +0100
9+++ pdfshuffler/pdfshuffler/pdfshuffler.py 2012-02-12 01:24:18.630058194 +0100
10@@ -112,7 +112,7 @@
11 # Import the user interface file, trying different possible locations
12 ui_path = '/usr/share/pdfshuffler/pdfshuffler.ui'
13 if not os.path.exists(ui_path):
14- ui_path = '/usr/local/share/applications/pdfshuffler/pdfshuffler.ui'
15+ ui_path = '/usr/local/share/pdfshuffler/pdfshuffler.ui'
16 if not os.path.exists(ui_path):
17 parent_dir = os.path.dirname( \
18 os.path.dirname(os.path.realpath(__file__)))
19Index: pdfshuffler/setup.py
20===================================================================
21--- pdfshuffler.orig/setup.py 2012-02-12 01:15:27.115323000 +0100
22+++ pdfshuffler/setup.py 2012-02-12 01:24:18.630058194 +0100
23@@ -25,7 +25,7 @@
24 import re
25 from distutils.core import setup
26
27-data_files=[('share/applications/pdfshuffler', ['data/pdfshuffler.ui']),
28+data_files=[('share/pdfshuffler', ['data/pdfshuffler.ui']),
29 ('share/applications', ['data/pdfshuffler.desktop']),
30 ('share/man/man1', ['doc/pdfshuffler.1']),
31 ('share/pixmaps', ['data/pdfshuffler.svg']),
032
=== modified file 'debian/patches/series'
--- debian/patches/series 2009-05-16 23:26:32 +0000
+++ debian/patches/series 2012-02-12 00:35:23 +0000
@@ -1,1 +1,1 @@
1fix-shebang1install_and_look_for_UI_file_in_right_dirs.patch
22
=== modified file 'doc/pdfshuffler.1'
--- doc/pdfshuffler.1 2011-02-15 00:43:28 +0000
+++ doc/pdfshuffler.1 2012-02-12 00:35:23 +0000
@@ -1,4 +1,4 @@
1.TH PDFSHUFFLER 1 "December 2009" "version 0.5.1" "User Manuals"1.TH PDFSHUFFLER 1 "December 2011" "version 0.6" "User Manuals"
2.SH "NAME"2.SH "NAME"
3PDF-Shuffler \- Application for PDF Merging, Rearranging, and Splitting3PDF-Shuffler \- Application for PDF Merging, Rearranging, and Splitting
4.SH "SYNOPSIS"4.SH "SYNOPSIS"
@@ -19,5 +19,5 @@
19.SH "DIAGNOSTICS"19.SH "DIAGNOSTICS"
20Common python and gtk diagnostics.20Common python and gtk diagnostics.
21.SH "AUTHOR"21.SH "AUTHOR"
22Konstantinos Poulios <poulios.konstantinos@gmail.com>22Konstantinos Poulios <logari81@gmail.com>
2323
2424
=== modified file 'pdfshuffler'
--- pdfshuffler 2011-02-15 00:43:28 +0000
+++ pdfshuffler 1970-01-01 00:00:00 +0000
@@ -1,1031 +0,0 @@
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4"""
5 --------------------------------------------------------------------------
6
7 PDF-Shuffler 0.5.1 - pyGTK PDF Merging, Rearranging, and Splitting
8 Copyright (C) 2008-2010 Konstantinos Poulios
9 <https://sourceforge.net/projects/pdfshuffler>
10
11 --------------------------------------------------------------------------
12
13 This program is free software; you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation; either version 3 of the License, or
16 (at your option) any later version.
17
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License along
24 with this program; if not, write to the Free Software Foundation, Inc.,
25 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26
27 --------------------------------------------------------------------------
28"""
29
30import os
31import shutil #needed for file operations like whole directory deletion
32import sys #needed for proccessing of command line args
33import urllib #needed to parse filename information passed by DnD
34import threading
35import tempfile
36from copy import copy
37
38import locale #for multilanguage support
39import gettext
40gettext.install('pdfshuffler', unicode=1)
41
42try:
43 import pygtk
44 pygtk.require('2.0')
45 import gtk
46 assert gtk.gtk_version >= (2, 10, 0)
47 assert gtk.pygtk_version >= (2, 10, 0)
48except AssertionError:
49 print('You do not have the required versions of GTK+ and/or PyGTK ' +
50 'installed.\n\n' +
51 'Installed GTK+ version is ' +
52 '.'.join([str(n) for n in gtk.gtk_version]) + '\n' +
53 'Required GTK+ version is 2.10.0 or higher\n\n'
54 'Installed PyGTK version is ' +
55 '.'.join([str(n) for n in gtk.pygtk_version]) + '\n' +
56 'Required PyGTK version is 2.10.0 or higher')
57 sys.exit(1)
58except:
59 print('PyGTK version 2.10.0 or higher is required to run this program.')
60 print('No version of PyGTK was found on your system.')
61 sys.exit(1)
62
63gtk.gdk.threads_init()
64import gobject #to use custom signals
65import pango #to adjust the text alignment in CellRendererText
66import gio #to inquire mime types information
67
68import poppler #for the rendering of pdf pages
69from pyPdf import PdfFileWriter, PdfFileReader
70
71class PDFshuffler:
72 # =======================================================
73 # All the preferences are stored here.
74 # =======================================================
75 prefs = {
76 'window width': min (700, gtk.gdk.screen_get_default().get_width() / 2 ),
77 'window height': min(600, gtk.gdk.screen_get_default().get_height() - 50 ),
78 'window x': 0,
79 'window y': 0,
80 'initial thumbnail size': 530,
81 'initial zoom scale': 0.25,
82 }
83
84 MODEL_ROW_INTERN = 1001
85 MODEL_ROW_EXTERN = 1002
86 TEXT_URI_LIST = 1003
87 MODEL_ROW_MOTION = 1004
88 TARGETS_IV = [('MODEL_ROW_INTERN', gtk.TARGET_SAME_WIDGET, MODEL_ROW_INTERN),
89 ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN),
90 ('MODEL_ROW_MOTION', 0, MODEL_ROW_MOTION)]
91 TARGETS_SW = [('text/uri-list', 0, TEXT_URI_LIST),
92 ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN)]
93
94 def __init__(self):
95 # Create the temporary directory
96 self.tmp_dir = tempfile.mkdtemp("pdfshuffler")
97 os.chmod(self.tmp_dir, 0700)
98
99 pixmap = os.path.join(sys.prefix,'share','pixmaps','pdfshuffler.png')
100 try:
101 gtk.window_set_default_icon_from_file(pixmap)
102 except:
103 print(_('File %s does not exist') % pixmap)
104
105 # Create the main window, and attach delete_event signal to terminating
106 # the application
107 self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
108 self.window.set_title('PDF-Shuffler')
109 self.window.set_border_width(0)
110 self.window.move(self.prefs['window x'], self.prefs['window y'])
111 self.window.set_size_request(self.prefs['window width'],
112 self.prefs['window height'])
113 self.window.connect('delete_event', self.close_application)
114 self.window.show()
115
116 # Create a vbox to hold the thumnails-container
117 vbox = gtk.VBox()
118 self.window.add(vbox)
119
120 # Create a scrolled window to hold the thumbnails-container
121 self.sw = gtk.ScrolledWindow()
122 self.sw.set_border_width(0)
123 self.sw.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC)
124 self.sw.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
125 gtk.DEST_DEFAULT_HIGHLIGHT |
126 gtk.DEST_DEFAULT_DROP |
127 gtk.DEST_DEFAULT_MOTION,
128 self.TARGETS_SW,
129 gtk.gdk.ACTION_COPY |
130 gtk.gdk.ACTION_MOVE )
131 self.sw.connect('drag_data_received', self.sw_dnd_received_data)
132 self.sw.connect('button_press_event', self.sw_button_press_event)
133 vbox.pack_start(self.sw)
134
135 # Create an alignment to keep the thumbnails center-aligned
136 align = gtk.Alignment(0.5, 0.5, 0, 0)
137 self.sw.add_with_viewport(align)
138
139 # Create ListStore model and IconView
140 self.model = gtk.ListStore(str, # 0.Text descriptor
141 gtk.gdk.Pixbuf, # 1.Thumbnail image
142 int, # 2.Document number
143 int, # 3.Page number
144 int, # 4.Thumbnail width
145 str, # 5.Document filename
146 bool, # 6.Rendered
147 int, # 7.Rotation angle
148 float, # 8.Crop left
149 float, # 9.Crop right
150 float, # 10.Crop top
151 float) # 11.Crop bottom
152
153 self.zoom_scale = self.prefs['initial zoom scale']
154 self.iv_col_width = self.prefs['initial thumbnail size']
155
156 self.iconview = gtk.IconView(self.model)
157 self.iconview.set_item_width(self.iv_col_width + 12)
158
159 self.iconview.set_pixbuf_column(1)
160# self.cellpb = gtk.CellRendererPixbuf()
161# self.cellpb.set_property('follow-state', True)
162# self.iconview.pack_start(self.cellpb, False)
163# self.iconview.set_attributes(self.cellpb, pixbuf=1)
164
165# self.iconview.set_text_column(0)
166 self.celltxt = gtk.CellRendererText()
167 self.celltxt.set_property('width', self.iv_col_width)
168 self.celltxt.set_property('wrap-width', self.iv_col_width)
169 self.celltxt.set_property('alignment', pango.ALIGN_CENTER)
170 self.iconview.pack_start(self.celltxt, False)
171 self.iconview.set_attributes(self.celltxt, text=0)
172
173 self.iconview.set_selection_mode(gtk.SELECTION_MULTIPLE)
174 self.iconview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
175 self.TARGETS_IV,
176 gtk.gdk.ACTION_COPY |
177 gtk.gdk.ACTION_MOVE )
178 self.iconview.enable_model_drag_dest(self.TARGETS_IV,
179 gtk.gdk.ACTION_DEFAULT)
180 self.iconview.connect('drag_begin', self.iv_drag_begin)
181 self.iconview.connect('drag_data_get', self.iv_dnd_get_data)
182 self.iconview.connect('drag_data_received', self.iv_dnd_received_data)
183 self.iconview.connect('drag_data_delete', self.iv_dnd_data_delete)
184 self.iconview.connect('drag_motion', self.iv_dnd_motion)
185 self.iconview.connect('drag_leave', self.iv_dnd_leave_end)
186 self.iconview.connect('drag_end', self.iv_dnd_leave_end)
187 self.iconview.connect('button_press_event', self.iv_button_press_event)
188 self.iv_auto_scroll_direction = 0
189
190 style = self.iconview.get_style().copy()
191 style_sw = self.sw.get_style()
192 for state in (gtk.STATE_NORMAL, gtk.STATE_PRELIGHT, gtk.STATE_ACTIVE):
193 style.base[state] = style_sw.bg[gtk.STATE_NORMAL]
194 self.iconview.set_style(style)
195
196 align.add(self.iconview)
197
198 # Create a horizontal box to hold the buttons
199 hbox = gtk.HBox()
200 vbox.pack_start(hbox, expand=False, fill=False)
201
202 # Create buttons
203 self.button_exit = gtk.Button(stock=gtk.STOCK_QUIT)
204 self.button_exit.connect('clicked', self.close_application)
205 hbox.pack_start(self.button_exit, expand=True, fill=True, padding=20)
206
207 self.button_del = gtk.Button(_('Delete Page(s)'))
208 image = gtk.Image()
209 image.set_from_stock(gtk.STOCK_DELETE, gtk.ICON_SIZE_BUTTON)
210 self.button_del.set_image(image)
211 self.button_del.connect('clicked', self.clear_selected)
212 hbox.pack_start(self.button_del, expand=True, fill=True, padding=20)
213
214 self.button_import = gtk.Button(_('Import pdf'))
215 image = gtk.Image()
216 image.set_from_stock(gtk.STOCK_OPEN, gtk.ICON_SIZE_BUTTON)
217 self.button_import.set_image(image)
218 self.button_import.connect('clicked', self.on_action_add_doc_activate)
219 hbox.pack_start(self.button_import, expand=True, fill=True, padding=20)
220
221 self.button_export = gtk.Button(_('Export pdf'))
222 image = gtk.Image()
223 image.set_from_stock(gtk.STOCK_SAVE_AS, gtk.ICON_SIZE_BUTTON)
224 self.button_export.set_image(image)
225 self.button_export.connect('clicked', self.choose_export_pdf_name)
226 hbox.pack_start(self.button_export, expand=True, fill=True, padding=20)
227
228 self.button_export = gtk.Button(_('About'))
229 image = gtk.Image()
230 image.set_from_stock(gtk.STOCK_ABOUT, gtk.ICON_SIZE_BUTTON)
231 self.button_export.set_image(image)
232 self.button_export.connect('clicked', self.about_dialog)
233 hbox.pack_start(self.button_export, expand=True, fill=True, padding=20)
234
235 # Define window callback function and show window
236 self.window.connect('size_allocate', self.on_window_size_request) # resize
237 self.window.connect('key_press_event', self.on_keypress_event ) # keypress
238 self.window.show_all()
239
240 #Creating the popup menu
241 self.popup = gtk.Menu()
242 popup_rotate_right = gtk.MenuItem(_('Rotate Page(s) Clockwise'))
243 popup_rotate_left = gtk.MenuItem(_('Rotate Page(s) Counterclockwise'))
244 popup_crop = gtk.MenuItem(_('Crop Page(s)'))
245 popup_delete = gtk.MenuItem(_('Delete Page(s)'))
246 popup_rotate_right.connect('activate', self.rotate_page_right)
247 popup_rotate_left.connect('activate', self.rotate_page_left)
248 popup_crop.connect('activate', self.crop_page_dialog)
249 popup_delete.connect('activate', self.clear_selected)
250 popup_rotate_right.show()
251 popup_rotate_left.show()
252 popup_crop.show()
253 popup_delete.show()
254 self.popup.append(popup_rotate_right)
255 self.popup.append(popup_rotate_left)
256 self.popup.append(popup_crop)
257 self.popup.append(popup_delete)
258
259 # Initializing variables
260 self.export_directory = os.getenv('HOME')
261 self.import_directory = os.getenv('HOME')
262 self.nfile = 0
263 self.iv_auto_scroll_timer = None
264 self.pdfqueue = []
265
266 gobject.type_register(PDF_Renderer)
267 gobject.signal_new('reset_iv_width', PDF_Renderer,
268 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE, ())
269 self.rendering_thread = PDF_Renderer(self.model, self.pdfqueue,
270 self.zoom_scale, self.iv_col_width)
271 self.rendering_thread.connect('reset_iv_width', self.reset_iv_width)
272 self.rendering_thread.start()
273
274 # Importing documents passed as command line arguments
275 for filename in sys.argv[1:]:
276 self.add_pdf_pages(filename)
277
278 # =======================================================
279 def render(self):
280 if self.rendering_thread.paused:
281 self.rendering_thread.paused = False
282 self.rendering_thread.evnt.set()
283 self.rendering_thread.evnt.clear()
284
285 # =======================================================
286 def on_window_size_request(self, window, event):
287 """Main Window resize - workaround for autosetting of
288 iconview cols no."""
289
290 #add 12 because of: http://bugzilla.gnome.org/show_bug.cgi?id=570152
291 col_num = 9 * window.get_size()[0] / (10 * (self.iv_col_width + 12))
292 self.iconview.set_columns(col_num)
293
294 # =======================================================
295 def reset_iv_width(self, renderer=None):
296 """Reconfigures the width of the iconview columns"""
297
298 max_w = max(row[4] for row in self.model)
299 if max_w != self.iv_col_width:
300 self.iv_col_width = max_w
301 self.celltxt.set_property('width', self.iv_col_width)
302 self.celltxt.set_property('wrap-width', self.iv_col_width)
303 self.iconview.set_item_width(self.iv_col_width + 12) #-1)
304 self.on_window_size_request(self.window, None)
305
306 # =======================================================
307 def on_keypress_event(self, widget, event):
308 """Keypress events in Main Window"""
309
310 #keyname = gtk.gdk.keyval_name(event.keyval)
311 if event.keyval == 65535: # Delete keystroke
312 self.clear_selected()
313
314 # =======================================================
315 def close_application(self, widget, event=None, data=None):
316 """Termination"""
317
318 #gtk.gdk.threads_leave()
319 self.rendering_thread.quit = True
320 #gtk.gdk.threads_enter()
321 if self.rendering_thread.paused == True:
322 self.rendering_thread.evnt.set()
323 self.rendering_thread.evnt.clear()
324 if os.path.isdir(self.tmp_dir):
325 shutil.rmtree(self.tmp_dir)
326 if gtk.main_level():
327 gtk.main_quit()
328 else:
329 sys.exit(0)
330 return False
331
332 # =======================================================
333 def add_pdf_pages(self, filename,
334 firstpage=None, lastpage=None,
335 angle=0, crop=[0.,0.,0.,0.] ):
336 """Add pages of a pdf document to the model"""
337
338 res = False
339 # Check if the document has already been loaded
340 pdfdoc = None
341 for it_pdfdoc in self.pdfqueue:
342 if os.path.isfile(it_pdfdoc.filename) and \
343 os.path.samefile(filename, it_pdfdoc.filename) and \
344 os.path.getmtime(filename) is it_pdfdoc.mtime:
345 pdfdoc = it_pdfdoc
346 break
347
348 if not pdfdoc:
349 pdfdoc = PDF_Doc(filename, self.nfile, self.tmp_dir)
350 self.import_directory = os.path.split(filename)[0]
351 self.export_directory = self.import_directory
352 if pdfdoc.nfile != 0 and pdfdoc != []:
353 self.nfile = pdfdoc.nfile
354 self.pdfqueue.append(pdfdoc)
355 else:
356 return res
357
358 n_start = 1
359 n_end = pdfdoc.npage
360 if firstpage:
361 n_start = min(n_end, max(1, firstpage))
362 if lastpage:
363 n_end = max(n_start, min(n_end, lastpage))
364
365 for npage in range(n_start, n_end + 1):
366 descriptor = ''.join([pdfdoc.shortname, '\n', _('page'), ' ', str(npage)])
367 width = self.iv_col_width
368 thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False,
369 8, width, width)
370 self.model.append((descriptor, # 0
371 thumbnail, # 1
372 pdfdoc.nfile, # 2
373 npage, # 3
374 width, # 4
375 pdfdoc.filename, # 5
376 False, # 6
377 angle, # 7
378 crop[0],crop[1], # 8-9
379 crop[2],crop[3] )) # 10-11
380 res = True
381
382 if res:
383 self.render()
384 return res
385
386 # =======================================================
387 def choose_export_pdf_name(self, widget=None):
388 """Handles choosing a name for exporting """
389
390 chooser = gtk.FileChooserDialog(title=_('Export ...'),
391 action=gtk.FILE_CHOOSER_ACTION_SAVE,
392 buttons=(gtk.STOCK_CANCEL,
393 gtk.RESPONSE_CANCEL,
394 gtk.STOCK_SAVE,
395 gtk.RESPONSE_OK))
396 chooser.set_do_overwrite_confirmation(True)
397 chooser.set_current_folder(self.export_directory)
398 filter_pdf = gtk.FileFilter()
399 filter_pdf.set_name(_('PDF files'))
400 filter_pdf.add_mime_type('application/pdf')
401 chooser.add_filter(filter_pdf)
402
403 filter_all = gtk.FileFilter()
404 filter_all.set_name(_('All files'))
405 filter_all.add_pattern('*')
406 chooser.add_filter(filter_all)
407
408 while True:
409 response = chooser.run()
410 if response == gtk.RESPONSE_OK:
411 file_out = chooser.get_filename()
412 (path, shortname) = os.path.split(file_out)
413 (shortname, ext) = os.path.splitext(shortname)
414 if ext.lower() != '.pdf':
415 file_out = file_out + '.pdf'
416 try:
417 self.export_to_file(file_out)
418 self.export_directory = path
419 except IOError:
420 error_msg_win = gtk.MessageDialog(flags=gtk.DIALOG_MODAL,
421 type=gtk.MESSAGE_ERROR,
422 message_format=_("Error writing file: %s") % file_out,
423 buttons=gtk.BUTTONS_OK)
424 response = error_msg_win.run()
425 if response == gtk.RESPONSE_OK:
426 error_msg_win.destroy()
427 continue
428 break
429 chooser.destroy()
430
431 # =======================================================
432 def export_to_file(self, file_out):
433 """Export to file"""
434
435 pdf_output = PdfFileWriter()
436 pdf_input = []
437 for pdfdoc in self.pdfqueue:
438 pdfdoc_inp = PdfFileReader(file(pdfdoc.copyname, 'rb'))
439 if pdfdoc_inp.getIsEncrypted():
440 if (pdfdoc_inp.decrypt('')!=1): # Workaround for lp:#355479
441 print(_('File %s is encrypted.') % pdfdoc.filename)
442 print(_('Support for such files has not been implemented yet.'))
443 print(_('File export failed.'))
444 return
445 #FIXME
446 #else
447 # ask for password and decrypt file
448 pdf_input.append(pdfdoc_inp)
449
450 for row in self.model:
451 # add pages from input to output document
452 nfile = row[2]
453 npage = row[3]
454 current_page = copy(pdf_input[nfile-1].getPage(npage-1))
455 angle = row[7]
456 angle0 = current_page.get("/Rotate",0)
457 crop = [row[8],row[9],row[10],row[11]]
458 if angle is not 0:
459 current_page.rotateClockwise(angle)
460 if crop != [0.,0.,0.,0.]:
461 rotate_times = ( ( ( angle + angle0 ) % 360 + 45 ) / 90 ) % 4
462 crop_init = crop
463 if rotate_times is not 0:
464 perm = [0,2,1,3]
465 for it in range(rotate_times):
466 perm.append(perm.pop(0))
467 perm.insert(1,perm.pop(2))
468 crop = [crop_init[perm[side]] for side in range(4)]
469 #(x1, y1) = current_page.cropBox.lowerLeft
470 #(x2, y2) = current_page.cropBox.upperRight
471 (x1, y1) = [float(xy) for xy in current_page.mediaBox.lowerLeft]
472 (x2, y2) = [float(xy) for xy in current_page.mediaBox.upperRight]
473 x1_new = int( x1 + (x2-x1) * crop[0] )
474 x2_new = int( x2 - (x2-x1) * crop[1] )
475 y1_new = int( y1 + (y2-y1) * crop[3] )
476 y2_new = int( y2 - (y2-y1) * crop[2] )
477 #current_page.cropBox.lowerLeft = (x1_new, y1_new)
478 #current_page.cropBox.upperRight = (x2_new, y2_new)
479 current_page.mediaBox.lowerLeft = (x1_new, y1_new)
480 current_page.mediaBox.upperRight = (x2_new, y2_new)
481
482 pdf_output.addPage(current_page)
483
484 # finally, write "output" to document-output.pdf
485 print(_('exporting to:'), file_out)
486 pdf_output.write(file(file_out, 'wb'))
487
488 # =======================================================
489 def on_action_add_doc_activate(self, widget, data=None):
490 """Import doc"""
491
492 chooser = gtk.FileChooserDialog(title=_('Import...'),
493 action=gtk.FILE_CHOOSER_ACTION_OPEN,
494 buttons=(gtk.STOCK_CANCEL,
495 gtk.RESPONSE_CANCEL,
496 gtk.STOCK_OPEN,
497 gtk.RESPONSE_OK))
498 chooser.set_current_folder(self.import_directory)
499 chooser.set_select_multiple(True)
500
501 filter_all = gtk.FileFilter()
502 filter_all.set_name(_('All files'))
503 filter_all.add_pattern('*')
504 chooser.add_filter(filter_all)
505
506 filter_pdf = gtk.FileFilter()
507 filter_pdf.set_name(_('PDF files'))
508 filter_pdf.add_mime_type('application/pdf')
509 chooser.add_filter(filter_pdf)
510 chooser.set_filter(filter_pdf)
511
512 response = chooser.run()
513 if response == gtk.RESPONSE_OK:
514 for filename in chooser.get_filenames():
515 if os.path.isfile(filename):
516 # FIXME
517 f = gio.File(filename)
518 f_info = f.query_info('standard::content-type')
519 mime_type = f_info.get_content_type()
520 if mime_type == 'application/pdf':
521 self.add_pdf_pages(filename)
522 elif mime_type[:34] == 'application/vnd.oasis.opendocument':
523 print(_('OpenDocument not supported yet!'))
524 elif mime_type[:5] == 'image':
525 print(_('Image file not supported yet!'))
526 else:
527 print(_('File type not supported!'))
528 else:
529 print(_('File %s does not exist') % filename)
530 elif response == gtk.RESPONSE_CANCEL:
531 print(_('Closed, no files selected'))
532 chooser.destroy()
533
534 # =======================================================
535 def clear_selected(self, button=None):
536 """Removes the selected Element in the IconView"""
537
538 model = self.iconview.get_model()
539 selection = self.iconview.get_selected_items()
540 if selection:
541 selection.sort(reverse=True)
542 for path in selection:
543 iter = model.get_iter(path)
544 model.remove(iter)
545 path = selection[-1]
546 self.iconview.select_path(path)
547 if not self.iconview.path_is_selected(path):
548 if len(model) > 0: # select the last row
549 row = model[-1]
550 path = row.path
551 self.iconview.select_path(path)
552 self.iconview.grab_focus()
553
554 # =======================================================
555 def iv_drag_begin(self, iconview, context):
556 """Sets custom icon on drag begin for multiple items selected"""
557
558 if len(iconview.get_selected_items()) > 1:
559 iconview.stop_emission('drag_begin')
560 context.set_icon_stock(gtk.STOCK_DND_MULTIPLE, 0, 0)
561
562 # =======================================================
563 def iv_dnd_get_data(self, iconview, context,
564 selection_data, target_id, etime):
565 """Handles requests for data by drag and drop in iconview"""
566
567 model = iconview.get_model()
568 selection = self.iconview.get_selected_items()
569 selection.sort(key=lambda x: x[0])
570 data = []
571 for path in selection:
572 if selection_data.target == 'MODEL_ROW_INTERN':
573 data.append( str(path[0]) )
574 elif selection_data.target == 'MODEL_ROW_EXTERN':
575 iter = model.get_iter(path)
576 nfile, npage, angle = model.get(iter, 2, 3, 7)
577 crop = model.get(iter, 8, 9, 10, 11)
578 pdfdoc = self.pdfqueue[nfile - 1]
579 data.append('\n'.join([pdfdoc.filename,
580 str(npage),
581 str(angle) ] +
582 [str(side) for side in crop] ) )
583 if data:
584 data = '\n;\n'.join(data)
585 selection_data.set(selection_data.target, 8, data)
586
587 # =======================================================
588 def iv_dnd_received_data(self, iconview, context, x, y,
589 selection_data, target_id, etime):
590 """Handles received data by drag and drop in iconview"""
591
592 model = iconview.get_model()
593 data = selection_data.data
594 if data:
595 data = data.split('\n;\n')
596 drop_info = iconview.get_dest_item_at_pos(x, y)
597 iter_to = None
598 if drop_info:
599 path, position = drop_info
600 ref_to = gtk.TreeRowReference(model,path)
601 else:
602 position = gtk.ICON_VIEW_DROP_RIGHT
603 if len(model) > 0: #find the iterator of the last row
604 row = model[-1]
605 path = row.path
606 ref_to = gtk.TreeRowReference(model,path)
607 if ref_to:
608 before = ( position == gtk.ICON_VIEW_DROP_LEFT
609 or position == gtk.ICON_VIEW_DROP_ABOVE)
610 #if target_id == self.MODEL_ROW_INTERN:
611 if selection_data.target == 'MODEL_ROW_INTERN':
612 if before:
613 data.sort(key=int)
614 else:
615 data.sort(key=int,reverse=True)
616 ref_from_list = [gtk.TreeRowReference(model,path)
617 for path in data]
618 for ref_from in ref_from_list:
619 path = ref_to.get_path()
620 iter_to = model.get_iter(path)
621 path = ref_from.get_path()
622 iter_from = model.get_iter(path)
623 row = model[iter_from]
624 if before:
625 model.insert_before(iter_to, row)
626 else:
627 model.insert_after(iter_to, row)
628 if context.action == gtk.gdk.ACTION_MOVE:
629 for ref_from in ref_from_list:
630 path = ref_from.get_path()
631 iter_from = model.get_iter(path)
632 model.remove(iter_from)
633
634 #elif target_id == self.MODEL_ROW_EXTERN:
635 elif selection_data.target == 'MODEL_ROW_EXTERN':
636 if not before:
637 data.reverse()
638 while data:
639 tmp = data.pop(0).split('\n')
640 filename = tmp[0]
641 npage, angle = [int(k) for k in tmp[1:3]]
642 crop = [float(side) for side in tmp[3:7]]
643 if self.add_pdf_pages(filename, npage, npage,
644 angle, crop ):
645 if len(model) > 0:
646 path = ref_to.get_path()
647 iter_to = model.get_iter(path)
648 row = model[-1] #the last row
649 path = row.path
650 iter_from = model.get_iter(path)
651 if before:
652 model.move_before(iter_from, iter_to)
653 else:
654 model.move_after(iter_from, iter_to)
655 if context.action == gtk.gdk.ACTION_MOVE:
656 context.finish(True, True, etime)
657
658 # =======================================================
659 def iv_dnd_data_delete(self, widget, context):
660 """Deletes dnd items after a successful move operation"""
661
662 model = self.iconview.get_model()
663 selection = self.iconview.get_selected_items()
664 ref_del_list = [gtk.TreeRowReference(model,path) for path in selection]
665 for ref_del in ref_del_list:
666 path = ref_del.get_path()
667 iter = model.get_iter(path)
668 model.remove(iter)
669
670 # =======================================================
671 def iv_dnd_motion(self, iconview, context, x, y, etime):
672 """Handles the drag-motion signal in order to auto-scroll the view"""
673
674 autoscroll_area = 40
675 sw_vadj = self.sw.get_vadjustment()
676 sw_height = self.sw.get_allocation().height
677 if y -sw_vadj.get_value() < autoscroll_area:
678 if not self.iv_auto_scroll_timer:
679 self.iv_auto_scroll_direction = gtk.DIR_UP
680 self.iv_auto_scroll_timer = gobject.timeout_add(150,
681 self.iv_auto_scroll)
682 elif y -sw_vadj.get_value() > sw_height - autoscroll_area:
683 if not self.iv_auto_scroll_timer:
684 self.iv_auto_scroll_direction = gtk.DIR_DOWN
685 self.iv_auto_scroll_timer = gobject.timeout_add(150,
686 self.iv_auto_scroll)
687 elif self.iv_auto_scroll_timer:
688 gobject.source_remove(self.iv_auto_scroll_timer)
689 self.iv_auto_scroll_timer = None
690
691 # =======================================================
692 def iv_dnd_leave_end(self, widget, context, ignored=None):
693 """Ends the auto-scroll during DND"""
694
695 if self.iv_auto_scroll_timer:
696 gobject.source_remove(self.iv_auto_scroll_timer)
697 self.iv_auto_scroll_timer = None
698
699 # =======================================================
700 def iv_auto_scroll(self):
701 """Timeout routine for auto-scroll"""
702
703 sw_vadj = self.sw.get_vadjustment()
704 sw_vpos = sw_vadj.get_value()
705 if self.iv_auto_scroll_direction == gtk.DIR_UP:
706 sw_vpos -= sw_vadj.step_increment
707 sw_vadj.set_value( max(sw_vpos, sw_vadj.lower) )
708 elif self.iv_auto_scroll_direction == gtk.DIR_DOWN:
709 sw_vpos += sw_vadj.step_increment
710 sw_vadj.set_value( min(sw_vpos, sw_vadj.upper - sw_vadj.page_size) )
711 return True #call me again
712
713 # =======================================================
714 def iv_button_press_event(self, iconview, event):
715 """Manages mouse clicks on the iconview"""
716
717 if event.button == 3:
718 x = int(event.x)
719 y = int(event.y)
720 time = event.time
721 path = iconview.get_path_at_pos(x, y)
722 selection = iconview.get_selected_items()
723 if path:
724 if path not in selection:
725 iconview.unselect_all()
726 iconview.select_path(path)
727 iconview.grab_focus()
728 self.popup.popup( None, None, None, event.button, time)
729 return 1
730
731 # =======================================================
732 def sw_dnd_received_data(self, scrolledwindow, context, x, y,
733 selection_data, target_id, etime):
734 """Handles received data by drag and drop in scrolledwindow"""
735
736 data = selection_data.data
737 if target_id == self.MODEL_ROW_EXTERN:
738 self.model
739 if data:
740 data = data.split('\n;\n')
741 while data:
742 tmp = data.pop(0).split('\n')
743 filename = tmp[0]
744 npage, angle = [int(k) for k in tmp[1:3]]
745 crop = [float(side) for side in tmp[3:7]]
746 if self.add_pdf_pages(filename, npage, npage, angle, crop):
747 if context.action == gtk.gdk.ACTION_MOVE:
748 context.finish(True, True, etime)
749 elif target_id == self.TEXT_URI_LIST:
750 uri = data.strip()
751 uri_splitted = uri.split() # we may have more than one file dropped
752 for uri in uri_splitted:
753 filename = self.get_file_path_from_dnd_dropped_uri(uri)
754 if os.path.isfile(filename): # is it file?
755 self.add_pdf_pages(filename)
756
757 # =======================================================
758 def sw_button_press_event(self, scrolledwindow, event):
759 """Unselects all items in iconview on mouse click in scrolledwindow"""
760
761 if event.button == 1:
762 self.iconview.unselect_all()
763
764 # =======================================================
765 def get_file_path_from_dnd_dropped_uri(self, uri):
766 """Extracts the path from an uri"""
767
768 path = urllib.url2pathname(uri) # escape special chars
769 path = path.strip('\r\n\x00') # remove \r\n and NULL
770
771 # get the path to file
772 if path.startswith('file:\\\\\\'): # windows
773 path = path[8:] # 8 is len('file:///')
774 elif path.startswith('file://'): # nautilus, rox
775 path = path[7:] # 7 is len('file://')
776 elif path.startswith('file:'): # xffm
777 path = path[5:] # 5 is len('file:')
778 return path
779
780 # =======================================================
781 def rotate_page_right(self, widget, data=None):
782 self.rotate_page(90)
783
784 def rotate_page_left(self, widget, data=None):
785 self.rotate_page(-90)
786
787 def rotate_page(self, angle):
788 """Rotates the selected page in the IconView"""
789
790 model = self.iconview.get_model()
791 selection = self.iconview.get_selected_items()
792 for path in selection:
793 iter = model.get_iter(path)
794 nfile = model.get_value(iter, 2)
795 npage = model.get_value(iter, 3)
796
797 rotate_times = ( ( (-angle) % 360 + 45 ) / 90 ) % 4
798 crop = [0.,0.,0.,0.]
799 if rotate_times is not 0:
800 perm = [0,2,1,3]
801 for it in range(rotate_times):
802 perm.append(perm.pop(0))
803 perm.insert(1,perm.pop(2))
804 crop = [model.get_value(iter, 8 + perm[side]) for side in range(4)]
805 for side in range(4):
806 model.set_value(iter, 8 + side, crop[side])
807
808 new_angle = model.get_value(iter, 7) + angle
809 model.set_value(iter, 7, new_angle)
810 model.set_value(iter, 6, False) #rendering request
811
812 self.render()
813
814 # =======================================================
815 def crop_page_dialog(self, widget):
816 """Opens a dialog box to define margins for page cropping"""
817
818 sides = ('L', 'R', 'T', 'B')
819 side_names = {'L':_('Left'), 'R':_('Right'),
820 'T':_('Top'), 'B':_('Bottom') }
821 opposite_sides = {'L':'R', 'R':'L', 'T':'B', 'B':'T' }
822
823 def set_crop_value(spinbutton, side):
824 opp_side = opposite_sides[side]
825 pos = sides.index(opp_side)
826 adj = spin_list[pos].get_adjustment()
827 adj.set_upper(99.0 - spinbutton.get_value())
828
829 model = self.iconview.get_model()
830 selection = self.iconview.get_selected_items()
831
832 crop = [0.,0.,0.,0.]
833 if selection:
834 path = selection[0]
835 pos = model.get_iter(path)
836 crop = [model.get_value(pos, 8 + side) for side in range(4)]
837
838 dialog = gtk.Dialog(title=(_('Crop Selected Page(s)')),
839 parent=self.window,
840 flags=gtk.DIALOG_MODAL,
841 buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
842 gtk.STOCK_OK, gtk.RESPONSE_OK))
843 dialog.set_size_request(340, 250)
844 dialog.set_default_response(gtk.RESPONSE_OK)
845
846 frame = gtk.Frame(_('Crop Margins'))
847 dialog.vbox.pack_start(frame, False, False, 20)
848
849 vbox = gtk.VBox(False, 0)
850 frame.add(vbox)
851
852 spin_list = []
853 units = 2 * [_('% of width')] + 2 * [_('% of height')]
854 for side in sides:
855 hbox = gtk.HBox(True, 0)
856 vbox.pack_start(hbox, False, False, 5)
857
858 label = gtk.Label(side_names[side])
859 label.set_alignment(0, 0.0)
860 hbox.pack_start(label, True, True, 20)
861
862 adj = gtk.Adjustment(100.*crop.pop(0), 0.0, 99.0, 1.0, 5.0, 0.0)
863 spin = gtk.SpinButton(adj, 0, 1)
864 spin.set_activates_default(True)
865 spin.connect('value-changed', set_crop_value, side)
866 spin_list.append(spin)
867 hbox.pack_start(spin, False, False, 30)
868
869 label = gtk.Label(units.pop(0))
870 label.set_alignment(0, 0.0)
871 hbox.pack_start(label, True, True, 0)
872
873 dialog.show_all()
874 result = dialog.run()
875
876 if result == gtk.RESPONSE_OK:
877 crop = []
878 for spin in spin_list:
879 crop.append( spin.get_value()/100. )
880 for path in selection:
881 pos = model.get_iter(path)
882 for it in range(4):
883 model.set_value(pos, 8 + it, crop[it])
884 model.set_value(pos, 6, False) #rendering request
885 self.render()
886 elif result == gtk.RESPONSE_CANCEL:
887 print(_('Dialog closed'))
888 dialog.destroy()
889
890 # =======================================================
891 def about_dialog(self, widget, data=None):
892 about_dialog = gtk.AboutDialog()
893 try:
894 about_dialog.set_transient_for(self.window)
895 about_dialog.set_modal(True)
896 except:
897 pass
898 # FIXME
899 about_dialog.set_name('PDF-Shuffler')
900 about_dialog.set_version('0.5.1')
901 about_dialog.set_comments(_(
902 'PDF-Shuffler is a simple pyGTK utility which lets you merge, \
903split and rearrange PDF documents. You can also rotate and crop individual \
904pages of a pdf document.'))
905 about_dialog.set_authors(['Konstantinos Poulios',])
906 about_dialog.set_website_label('http://pdfshuffler.sourceforge.net/')
907 about_dialog.set_logo_icon_name('pdfshuffler')
908 about_dialog.connect('response', lambda w, a: about_dialog.destroy())
909 about_dialog.connect('delete_event', lambda w, a: about_dialog.destroy())
910 about_dialog.show_all()
911
912# =======================================================
913class PDF_Doc:
914 """Class handling pdf documents"""
915
916 def __init__(self, filename, nfile, tmp_dir):
917
918 self.filename = os.path.abspath(filename)
919 (self.path, self.shortname) = os.path.split(self.filename)
920 (self.shortname, self.ext) = os.path.splitext(self.shortname)
921 f = gio.File(filename)
922 mime_type = f.query_info('standard::content-type').get_content_type()
923 if mime_type == 'application/pdf':
924 self.nfile = nfile + 1
925 self.mtime = os.path.getmtime(filename)
926 self.copyname = os.path.join(tmp_dir, '%02d_' % self.nfile +
927 self.shortname + '.pdf')
928 shutil.copy(self.filename, self.copyname)
929 self.document = poppler.document_new_from_file("file://" + self.copyname, None)
930 self.npage = self.document.get_n_pages()
931 else:
932 self.nfile = 0
933
934
935# =======================================================
936class PDF_Renderer(threading.Thread,gobject.GObject):
937
938 def __init__(self, model, pdfqueue, scale=1., width=100):
939 threading.Thread.__init__(self)
940 gobject.GObject.__init__(self)
941 self.model = model
942 self.scale = scale
943 self.default_width = width
944 self.pdfqueue = pdfqueue
945 self.quit = False
946 self.evnt = threading.Event()
947 self.paused = False
948
949 def run(self):
950 while not self.quit:
951 rendered_all = True
952 for row in self.model:
953 if self.quit:
954 break
955 if not row[6]:
956 rendered_all = False
957 gtk.gdk.threads_enter()
958 try:
959 nfile = row[2]
960 npage = row[3]
961 angle = row[7]
962 crop = [row[8],row[9],row[10],row[11]]
963 pdfdoc = self.pdfqueue[nfile - 1]
964 thumbnail = self.load_pdf_thumbnail(pdfdoc, npage, angle, crop)
965 row[6] = True
966 row[4] = thumbnail.get_width()
967 row[1] = thumbnail
968 finally:
969 gtk.gdk.threads_leave()
970 if rendered_all:
971 if self.model.get_iter_first(): #just checking if model isn't empty
972 self.emit('reset_iv_width')
973 self.paused = True
974 self.evnt.wait()
975
976 # =======================================================
977 def load_pdf_thumbnail(self, pdfdoc, npage, rotation=0, crop=[0.,0.,0.,0.]):
978 """Create pdf pixbuf"""
979
980 page = pdfdoc.document.get_page(npage-1)
981 try:
982 pix_w, pix_h = page.get_size()
983 pix_w = int(pix_w * self.scale)
984 pix_h = int(pix_h * self.scale)
985 thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False,
986 8, pix_w , pix_h)
987 page.render_to_pixbuf(0,0,pix_w,pix_h,self.scale,0,thumbnail)
988 rotation = (-rotation) % 360
989 rotation = ((rotation + 45) / 90) * 90
990 thumbnail = thumbnail.rotate_simple(rotation)
991 pix_w = thumbnail.get_width()
992 pix_h = thumbnail.get_height()
993 if crop != [0.,0.,0.,0.]:
994 src_x = int( crop[0] * pix_w )
995 src_y = int( crop[2] * pix_h )
996 width = int( (1. - crop[0] - crop[1]) * pix_w )
997 height = int( (1. - crop[2] - crop[3]) * pix_h )
998 new_thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False,
999 8, width, height)
1000 thumbnail.copy_area(src_x, src_y, width, height,
1001 new_thumbnail, 0, 0)
1002 thumbnail = new_thumbnail
1003 pix_w = thumbnail.get_width()
1004 pix_h = thumbnail.get_height()
1005 except:
1006 pix_w = self.default_width
1007 pix_h = pix_w
1008 thumbnail = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, False,
1009 8, pix_w, pix_h)
1010 pixbuf.fill(0xffffffff)
1011
1012 #add border
1013 thickness = 3
1014 color = 0x000000FF
1015 canvas = gtk.gdk.Pixbuf(gtk.gdk.COLORSPACE_RGB, True, 8,
1016 pix_w + thickness + 1,
1017 pix_h + thickness + 1)
1018 canvas.fill(color)
1019 thumbnail.copy_area(0, 0, pix_w, pix_h, canvas, 1, 1)
1020 thumbnail = canvas
1021
1022 return thumbnail
1023
1024
1025# =======================================================
1026if __name__ == '__main__':
1027 PDFshuffler()
1028 gtk.gdk.threads_enter()
1029 gtk.main()
1030 gtk.gdk.threads_leave()
1031
10320
=== added file 'pdfshuffler/__init__.py'
=== added file 'pdfshuffler/cairorendering.py'
--- pdfshuffler/cairorendering.py 1970-01-01 00:00:00 +0000
+++ pdfshuffler/cairorendering.py 2012-02-12 00:35:23 +0000
@@ -0,0 +1,168 @@
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4"""
5
6 PdfShuffler 0.6.0 - GTK+ based utility for splitting, rearrangement and
7 modification of PDF documents.
8 Copyright (C) 2008-2011 Konstantinos Poulios
9 <https://sourceforge.net/projects/pdfshuffler>
10
11 This file is part of PdfShuffler.
12
13 PdfShuffler is free software; you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation; either version 3 of the License, or
16 (at your option) any later version.
17
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License along
24 with this program; if not, write to the Free Software Foundation, Inc.,
25 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26
27"""
28
29import gtk
30import gobject
31import cairo
32
33from math import pi as M_PI
34
35class CellRendererImage(gtk.GenericCellRenderer):
36 __gproperties__ = {
37 "image": (gobject.TYPE_OBJECT, "Image", "Image",
38 gobject.PARAM_READWRITE),
39 "width": (gobject.TYPE_DOUBLE, "Width", "Width",
40 0., 1.e4, 0., gobject.PARAM_READWRITE),
41 "height": (gobject.TYPE_DOUBLE, "Height", "Height",
42 0., 1.e4, 0., gobject.PARAM_READWRITE),
43 "rotation": (gobject.TYPE_INT, "Rotation", "Rotation",
44 0, 360, 0, gobject.PARAM_READWRITE),
45 "scale": (gobject.TYPE_DOUBLE, "Scale", "Scale",
46 0.01, 100., 1., gobject.PARAM_READWRITE),
47 "cropL": (gobject.TYPE_DOUBLE, "CropL", "CropL",
48 0., 1., 0., gobject.PARAM_READWRITE),
49 "cropR": (gobject.TYPE_DOUBLE, "CropR", "CropR",
50 0., 1., 0., gobject.PARAM_READWRITE),
51 "cropT": (gobject.TYPE_DOUBLE, "CropT", "CropT",
52 0., 1., 0., gobject.PARAM_READWRITE),
53 "cropB": (gobject.TYPE_DOUBLE, "CropB", "CropB",
54 0., 1., 0., gobject.PARAM_READWRITE),
55 }
56
57 def __init__(self):
58 self.__gobject_init__()
59 self.th1 = 2. # border thickness
60 self.th2 = 3. # shadow thickness
61
62 def get_geometry(self):
63
64 rotation = int(self.rotation) % 360
65 rotation = ((rotation + 45) / 90) * 90
66 if not self.image.surface:
67 w0 = w1 = self.width
68 h0 = h1 = self.height
69 else:
70 w0 = self.image.surface.get_width()
71 h0 = self.image.surface.get_height()
72 if rotation == 90 or rotation == 270:
73 w1, h1 = h0, w0
74 else:
75 w1, h1 = w0, h0
76
77 x = self.cropL * w1
78 y = self.cropT * h1
79
80 w2 = int(self.scale * (1. - self.cropL - self.cropR) * w1)
81 h2 = int(self.scale * (1. - self.cropT - self.cropB) * h1)
82
83 return w0,h0,w1,h1,w2,h2,rotation
84
85 def do_set_property(self, pspec, value):
86 setattr(self, pspec.name, value)
87
88 def do_get_property(self, pspec):
89 return getattr(self, pspec.name)
90
91 def on_render(self, window, widget, background_area, cell_area, \
92 expose_area, flags):
93 if not self.image.surface:
94 return
95
96 w0,h0,w1,h1,w2,h2,rotation = self.get_geometry()
97 th = int(2*self.th1+self.th2)
98 w = w2 + th
99 h = h2 + th
100
101 x = cell_area.x
102 y = cell_area.y
103 if cell_area and w > 0 and h > 0:
104 x += self.get_property('xalign') * \
105 (cell_area.width - w - self.get_property('xpad'))
106 y += self.get_property('yalign') * \
107 (cell_area.height - h - self.get_property('ypad'))
108
109 cr = window.cairo_create()
110 cr.translate(x,y)
111
112 x = self.cropL * w1
113 y = self.cropT * h1
114
115 #shadow
116 cr.set_source_rgb(0.5, 0.5, 0.5)
117 cr.rectangle(th, th, w2, h2)
118 cr.fill()
119
120 #border
121 cr.set_source_rgb(0, 0, 0)
122 cr.rectangle(0, 0, w2+2*self.th1, h2+2*self.th1)
123 cr.fill()
124
125 #image
126 cr.set_source_rgb(1, 1, 1)
127 cr.rectangle(self.th1, self.th1, w2, h2)
128 cr.fill_preserve()
129 cr.clip()
130
131 cr.translate(self.th1,self.th1)
132 cr.scale(self.scale, self.scale)
133 cr.translate(-x,-y)
134 if rotation > 0:
135 cr.translate(w1/2,h1/2)
136 cr.rotate(rotation * M_PI / 180)
137 cr.translate(-w0/2,-h0/2)
138
139 cr.set_source_surface(self.image.surface)
140 cr.paint()
141
142 def on_get_size(self, widget, cell_area=None):
143 x = y = 0
144 w0,h0,w1,h1,w2,h2,rotation = self.get_geometry()
145 th = int(2*self.th1+self.th2)
146 w = w2 + th
147 h = h2 + th
148
149 if cell_area and w > 0 and h > 0:
150 x = self.get_property('xalign') * \
151 (cell_area.width - w - self.get_property('xpad'))
152 y = self.get_property('yalign') * \
153 (cell_area.height - h - self.get_property('ypad'))
154 w += 2 * self.get_property('xpad')
155 h += 2 * self.get_property('ypad')
156 return int(x), int(y), w, h
157
158
159class CairoImage(gobject.GObject):
160
161 def __init__(self, width=0, height=0):
162 gobject.GObject.__init__(self)
163 if width > 0 and height > 0:
164 self.surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
165 else:
166 self.surface = None
167
168
0169
=== added file 'pdfshuffler/pdfshuffler.py'
--- pdfshuffler/pdfshuffler.py 1970-01-01 00:00:00 +0000
+++ pdfshuffler/pdfshuffler.py 2012-02-12 00:35:23 +0000
@@ -0,0 +1,1057 @@
1#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4"""
5
6 PdfShuffler 0.6.0 - GTK+ based utility for splitting, rearrangement and
7 modification of PDF documents.
8 Copyright (C) 2008-2011 Konstantinos Poulios
9 <https://sourceforge.net/projects/pdfshuffler>
10
11 This file is part of PdfShuffler.
12
13 PdfShuffler is free software; you can redistribute it and/or modify
14 it under the terms of the GNU General Public License as published by
15 the Free Software Foundation; either version 3 of the License, or
16 (at your option) any later version.
17
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 GNU General Public License for more details.
22
23 You should have received a copy of the GNU General Public License along
24 with this program; if not, write to the Free Software Foundation, Inc.,
25 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
26
27"""
28
29import os
30import shutil #needed for file operations like whole directory deletion
31import sys #needed for proccessing of command line args
32import urllib #needed to parse filename information passed by DnD
33import threading
34import tempfile
35from copy import copy
36
37import locale #for multilanguage support
38import gettext
39gettext.install('pdfshuffler', unicode=1)
40
41
42APPNAME = 'PdfShuffler' # PDF-Shuffler, PDFShuffler, pdfshuffler
43VERSION = '0.6.0'
44WEBSITE = 'http://pdfshuffler.sourceforge.net/'
45LICENSE = 'GNU General Public License (GPL) Version 3.'
46
47try:
48 import pygtk
49 pygtk.require('2.0')
50 import gtk
51 assert gtk.gtk_version >= (2, 10, 0)
52 assert gtk.pygtk_version >= (2, 10, 0)
53except AssertionError:
54 print('You do not have the required versions of GTK+ and PyGTK ' +
55 'installed.\n\n' +
56 'Installed GTK+ version is ' +
57 '.'.join([str(n) for n in gtk.gtk_version]) + '\n' +
58 'Required GTK+ version is 2.10.0 or higher\n\n'
59 'Installed PyGTK version is ' +
60 '.'.join([str(n) for n in gtk.pygtk_version]) + '\n' +
61 'Required PyGTK version is 2.10.0 or higher')
62 sys.exit(1)
63except:
64 print('PyGTK version 2.10.0 or higher is required to run this program.')
65 print('No version of PyGTK was found on your system.')
66 sys.exit(1)
67
68import gobject #to use custom signals
69import pango #to adjust the text alignment in CellRendererText
70import gio #to inquire mime types information
71import cairo
72
73import poppler #for the rendering of pdf pages
74from pyPdf import PdfFileWriter, PdfFileReader
75
76from cairorendering import CellRendererImage, CairoImage
77gobject.type_register(CellRendererImage)
78
79import time
80
81class PdfShuffler:
82 prefs = {
83 'window width': min(700, gtk.gdk.screen_get_default().get_width() / 2),
84 'window height': min(600, gtk.gdk.screen_get_default().get_height() - 50),
85 'window x': 0,
86 'window y': 0,
87 'initial thumbnail size': 300,
88 'initial zoom level': -14,
89 }
90
91 MODEL_ROW_INTERN = 1001
92 MODEL_ROW_EXTERN = 1002
93 TEXT_URI_LIST = 1003
94 MODEL_ROW_MOTION = 1004
95 TARGETS_IV = [('MODEL_ROW_INTERN', gtk.TARGET_SAME_WIDGET, MODEL_ROW_INTERN),
96 ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN),
97 ('MODEL_ROW_MOTION', 0, MODEL_ROW_MOTION)]
98 TARGETS_SW = [('text/uri-list', 0, TEXT_URI_LIST),
99 ('MODEL_ROW_EXTERN', gtk.TARGET_OTHER_APP, MODEL_ROW_EXTERN)]
100
101 def __init__(self):
102 # Create the temporary directory
103 self.tmp_dir = tempfile.mkdtemp("pdfshuffler")
104 os.chmod(self.tmp_dir, 0700)
105
106 icon_theme = gtk.icon_theme_get_default()
107 try:
108 gtk.window_set_default_icon(icon_theme.load_icon("pdfshuffler", 64, 0))
109 except:
110 print(_("Can't load icon. Application is not installed correctly."))
111
112 # Import the user interface file, trying different possible locations
113 ui_path = '/usr/share/pdfshuffler/pdfshuffler.ui'
114 if not os.path.exists(ui_path):
115 ui_path = '/usr/local/share/pdfshuffler/pdfshuffler.ui'
116 if not os.path.exists(ui_path):
117 parent_dir = os.path.dirname( \
118 os.path.dirname(os.path.realpath(__file__)))
119 ui_path = os.path.join(parent_dir, 'data', 'pdfshuffler.ui')
120 self.uiXML = gtk.Builder()
121 self.uiXML.add_from_file(ui_path)
122 self.uiXML.connect_signals(self)
123
124 # Create the main window, and attach delete_event signal to terminating
125 # the application
126 self.window = self.uiXML.get_object('main_window')
127 self.window.set_title(APPNAME)
128 self.window.set_border_width(0)
129 self.window.move(self.prefs['window x'], self.prefs['window y'])
130 self.window.set_default_size(self.prefs['window width'],
131 self.prefs['window height'])
132 self.window.connect('delete_event', self.close_application)
133 self.window.show_all()
134
135 # Create a scrolled window to hold the thumbnails-container
136 self.sw = self.uiXML.get_object('scrolledwindow')
137 self.sw.drag_dest_set(gtk.DEST_DEFAULT_MOTION |
138 gtk.DEST_DEFAULT_HIGHLIGHT |
139 gtk.DEST_DEFAULT_DROP |
140 gtk.DEST_DEFAULT_MOTION,
141 self.TARGETS_SW,
142 gtk.gdk.ACTION_COPY |
143 gtk.gdk.ACTION_MOVE)
144 self.sw.connect('drag_data_received', self.sw_dnd_received_data)
145 self.sw.connect('button_press_event', self.sw_button_press_event)
146 self.sw.connect('scroll_event', self.sw_scroll_event)
147
148 # Create an alignment to keep the thumbnails center-aligned
149 align = gtk.Alignment(0.5, 0.5, 0, 0)
150 self.sw.add_with_viewport(align)
151
152 # Create ListStore model and IconView
153 self.model = gtk.ListStore(str, # 0.Text descriptor
154 CairoImage, # 1.Cached page image
155 int, # 2.Document number
156 int, # 3.Page number
157 float, # 4.Scale
158 str, # 5.Document filename
159 int, # 6.Rotation angle
160 float, # 7.Crop left
161 float, # 8.Crop right
162 float, # 9.Crop top
163 float, # 10.Crop bottom
164 int, # 11.Page width
165 int) # 12.Page height
166
167 self.zoom_set(self.prefs['initial zoom level'])
168 self.iv_col_width = self.prefs['initial thumbnail size']
169
170 self.iconview = gtk.IconView(self.model)
171 self.iconview.set_item_width(self.iv_col_width + 12)
172
173 self.cellthmb = CellRendererImage()
174 self.iconview.pack_start(self.cellthmb, False)
175 self.iconview.set_attributes(self.cellthmb, image=1,
176 scale=4, rotation=6, cropL=7, cropR=8, cropT=9, cropB=10,
177 width=11, height=12)
178
179# self.iconview.set_text_column(0)
180 self.celltxt = gtk.CellRendererText()
181 self.celltxt.set_property('width', self.iv_col_width)
182 self.celltxt.set_property('wrap-width', self.iv_col_width)
183 self.celltxt.set_property('alignment', pango.ALIGN_CENTER)
184 self.iconview.pack_start(self.celltxt, False)
185 self.iconview.set_attributes(self.celltxt, text=0)
186
187 self.iconview.set_selection_mode(gtk.SELECTION_MULTIPLE)
188 self.iconview.enable_model_drag_source(gtk.gdk.BUTTON1_MASK,
189 self.TARGETS_IV,
190 gtk.gdk.ACTION_COPY |
191 gtk.gdk.ACTION_MOVE)
192 self.iconview.enable_model_drag_dest(self.TARGETS_IV,
193 gtk.gdk.ACTION_DEFAULT)
194 self.iconview.connect('drag_begin', self.iv_drag_begin)
195 self.iconview.connect('drag_data_get', self.iv_dnd_get_data)
196 self.iconview.connect('drag_data_received', self.iv_dnd_received_data)
197 self.iconview.connect('drag_data_delete', self.iv_dnd_data_delete)
198 self.iconview.connect('drag_motion', self.iv_dnd_motion)
199 self.iconview.connect('drag_leave', self.iv_dnd_leave_end)
200 self.iconview.connect('drag_end', self.iv_dnd_leave_end)
201 self.iconview.connect('button_press_event', self.iv_button_press_event)
202 self.iv_auto_scroll_direction = 0
203
204 style = self.iconview.get_style().copy()
205 style_sw = self.sw.get_style()
206 for state in (gtk.STATE_NORMAL, gtk.STATE_PRELIGHT, gtk.STATE_ACTIVE):
207 style.base[state] = style_sw.bg[gtk.STATE_NORMAL]
208 self.iconview.set_style(style)
209
210 align.add(self.iconview)
211
212 # Progress bar
213 self.progress_bar = self.uiXML.get_object('progressbar')
214 self.progress_bar_timeout_id = 0
215
216 # Define window callback function and show window
217 self.window.connect('size_allocate', self.on_window_size_request) # resize
218 self.window.connect('key_press_event', self.on_keypress_event ) # keypress
219 self.window.show_all()
220 self.progress_bar.hide_all()
221
222 # Creating the popup menu
223 self.popup = gtk.Menu()
224 popup_rotate_right = gtk.ImageMenuItem(_('_Rotate Right'))
225 popup_rotate_left = gtk.ImageMenuItem(_('Rotate _Left'))
226 popup_crop = gtk.MenuItem(_('C_rop...'))
227 popup_delete = gtk.ImageMenuItem(gtk.STOCK_DELETE)
228 popup_rotate_right.connect('activate', self.rotate_page_right)
229 popup_rotate_left.connect('activate', self.rotate_page_left)
230 popup_crop.connect('activate', self.crop_page_dialog)
231 popup_delete.connect('activate', self.clear_selected)
232 popup_rotate_right.show()
233 popup_rotate_left.show()
234 popup_crop.show()
235 popup_delete.show()
236 self.popup.append(popup_rotate_right)
237 self.popup.append(popup_rotate_left)
238 self.popup.append(popup_crop)
239 self.popup.append(popup_delete)
240
241 # Initializing variables
242 self.export_directory = os.getenv('HOME')
243 self.import_directory = self.export_directory
244 self.nfile = 0
245 self.iv_auto_scroll_timer = None
246 self.pdfqueue = []
247
248 gobject.type_register(PDF_Renderer)
249 gobject.signal_new('update_thumbnail', PDF_Renderer,
250 gobject.SIGNAL_RUN_FIRST, gobject.TYPE_NONE,
251 [gobject.TYPE_INT, gobject.TYPE_PYOBJECT])
252 self.rendering_thread = 0
253
254 self.set_unsaved(False)
255
256 # Importing documents passed as command line arguments
257 for filename in sys.argv[1:]:
258 self.add_pdf_pages(filename)
259
260 def render(self):
261 if self.rendering_thread:
262 self.rendering_thread.quit = True
263 self.rendering_thread = PDF_Renderer(self.model, self.pdfqueue)
264 self.rendering_thread.connect('update_thumbnail', self.update_thumbnail)
265 self.rendering_thread.start()
266
267 if self.progress_bar_timeout_id:
268 gobject.source_remove(self.progress_bar_timeout_id)
269 self.progress_bar_timout_id = \
270 gobject.timeout_add(50, self.progress_bar_timeout)
271
272 def set_unsaved(self, flag):
273 self.is_unsaved = flag
274 gobject.idle_add(self.retitle)
275
276 def retitle(self):
277 title = ''
278 if len(self.pdfqueue) == 1:
279 title += self.pdfqueue[0].filename
280 elif len(self.pdfqueue) == 0:
281 title += _("No document")
282 else:
283 title += _("Several documents")
284 if self.is_unsaved:
285 title += '*'
286 title += ' - ' + APPNAME
287 self.window.set_title(title)
288
289 def progress_bar_timeout(self):
290 cnt_finished = 0
291 cnt_all = 0
292 for row in self.model:
293 cnt_all += 1
294 if row[1].surface:
295 cnt_finished += 1
296 fraction = float(cnt_finished)/float(cnt_all)
297
298 self.progress_bar.set_fraction(fraction)
299 self.progress_bar.set_text(_('Rendering thumbnails... [%(i1)s/%(i2)s]')
300 % {'i1' : cnt_finished, 'i2' : cnt_all})
301 if fraction >= 0.999:
302 self.progress_bar.hide_all()
303 return False
304 elif not self.progress_bar.flags() & gtk.VISIBLE:
305 self.progress_bar.show_all()
306
307 return True
308
309 def update_thumbnail(self, object, num, thumbnail):
310 row = self.model[num]
311 row[4] = self.zoom_scale
312 row[1] = thumbnail
313
314 def on_window_size_request(self, window, event):
315 """Main Window resize - workaround for autosetting of
316 iconview cols no."""
317
318 #add 12 because of: http://bugzilla.gnome.org/show_bug.cgi?id=570152
319 col_num = 9 * window.get_size()[0] \
320 / (10 * (self.iv_col_width + self.iconview.get_column_spacing() * 2))
321 self.iconview.set_columns(col_num)
322
323 def update_geometry(self, iter):
324 """Recomputes the width and height of the rotated page and saves
325 the result in the ListStore"""
326
327 if not self.model.iter_is_valid(iter):
328 return
329
330 nfile, npage, rotation = self.model.get(iter, 2, 3, 6)
331 crop = self.model.get(iter, 7, 8, 9, 10)
332 page = self.pdfqueue[nfile-1].document.get_page(npage-1)
333 w0, h0 = page.get_size()
334
335 rotation = int(rotation) % 360
336 rotation = ((rotation + 45) / 90) * 90
337 if rotation == 90 or rotation == 270:
338 w1, h1 = h0, w0
339 else:
340 w1, h1 = w0, h0
341
342 self.model.set(iter, 11, w1, 12, h1)
343
344 def reset_iv_width(self, renderer=None):
345 """Reconfigures the width of the iconview columns"""
346
347 if not self.model.get_iter_first(): #just checking if model is empty
348 return
349
350 max_w = 10 + int(max(row[4]*row[11]*(1.-row[7]-row[8]) for row in self.model))
351 if max_w != self.iv_col_width:
352 self.iv_col_width = max_w
353 self.celltxt.set_property('width', self.iv_col_width)
354 self.celltxt.set_property('wrap-width', self.iv_col_width)
355 self.iconview.set_item_width(self.iv_col_width + 12) #-1)
356 self.on_window_size_request(self.window, None)
357
358 def on_keypress_event(self, widget, event):
359 """Keypress events in Main Window"""
360
361 #keyname = gtk.gdk.keyval_name(event.keyval)
362 if event.keyval == 65535: # Delete keystroke
363 self.clear_selected()
364
365 def close_application(self, widget, event=None, data=None):
366 """Termination"""
367
368 if self.rendering_thread:
369 self.rendering_thread.quit = True
370 while self.rendering_thread.isAlive():
371 time.sleep(0.5)
372
373 if os.path.isdir(self.tmp_dir):
374 shutil.rmtree(self.tmp_dir)
375 if gtk.main_level():
376 gtk.main_quit()
377 else:
378 sys.exit(0)
379 return False
380
381 def add_pdf_pages(self, filename,
382 firstpage=None, lastpage=None,
383 angle=0, crop=[0.,0.,0.,0.]):
384 """Add pages of a pdf document to the model"""
385
386 res = False
387 # Check if the document has already been loaded
388 pdfdoc = None
389 for it_pdfdoc in self.pdfqueue:
390 if os.path.isfile(it_pdfdoc.filename) and \
391 os.path.samefile(filename, it_pdfdoc.filename) and \
392 os.path.getmtime(filename) is it_pdfdoc.mtime:
393 pdfdoc = it_pdfdoc
394 break
395
396 if not pdfdoc:
397 pdfdoc = PDF_Doc(filename, self.nfile, self.tmp_dir)
398 self.import_directory = os.path.split(filename)[0]
399 self.export_directory = self.import_directory
400 if pdfdoc.nfile != 0 and pdfdoc != []:
401 self.nfile = pdfdoc.nfile
402 self.pdfqueue.append(pdfdoc)
403 else:
404 return res
405
406 n_start = 1
407 n_end = pdfdoc.npage
408 if firstpage:
409 n_start = min(n_end, max(1, firstpage))
410 if lastpage:
411 n_end = max(n_start, min(n_end, lastpage))
412
413 for npage in range(n_start, n_end + 1):
414 descriptor = ''.join([pdfdoc.shortname, '\n', _('page'), ' ', str(npage)])
415 page = pdfdoc.document.get_page(npage-1)
416 w, h = page.get_size()
417 iter = self.model.append((descriptor, # 0
418 CairoImage(), # 1
419 pdfdoc.nfile, # 2
420 npage, # 3
421 self.zoom_scale, # 4
422 pdfdoc.filename, # 5
423 angle, # 6
424 crop[0],crop[1], # 7-8
425 crop[2],crop[3], # 9-10
426 w,h )) # 11-12
427 self.update_geometry(iter)
428 res = True
429
430 self.reset_iv_width()
431 gobject.idle_add(self.retitle)
432 if res:
433 gobject.idle_add(self.render)
434 return res
435
436 def choose_export_pdf_name(self, widget=None):
437 """Handles choosing a name for exporting """
438
439 chooser = gtk.FileChooserDialog(title=_('Export ...'),
440 action=gtk.FILE_CHOOSER_ACTION_SAVE,
441 buttons=(gtk.STOCK_CANCEL,
442 gtk.RESPONSE_CANCEL,
443 gtk.STOCK_SAVE,
444 gtk.RESPONSE_OK))
445 chooser.set_do_overwrite_confirmation(True)
446 chooser.set_current_folder(self.export_directory)
447 filter_pdf = gtk.FileFilter()
448 filter_pdf.set_name(_('PDF files'))
449 filter_pdf.add_mime_type('application/pdf')
450 chooser.add_filter(filter_pdf)
451
452 filter_all = gtk.FileFilter()
453 filter_all.set_name(_('All files'))
454 filter_all.add_pattern('*')
455 chooser.add_filter(filter_all)
456
457 while True:
458 response = chooser.run()
459 if response == gtk.RESPONSE_OK:
460 file_out = chooser.get_filename()
461 (path, shortname) = os.path.split(file_out)
462 (shortname, ext) = os.path.splitext(shortname)
463 if ext.lower() != '.pdf':
464 file_out = file_out + '.pdf'
465 try:
466 self.export_to_file(file_out)
467 self.export_directory = path
468 self.set_unsaved(False)
469 except Exception, e:
470 chooser.destroy()
471 error_msg_dlg = gtk.MessageDialog(flags=gtk.DIALOG_MODAL,
472 type=gtk.MESSAGE_ERROR,
473 message_format=str(e),
474 buttons=gtk.BUTTONS_OK)
475 response = error_msg_dlg.run()
476 if response == gtk.RESPONSE_OK:
477 error_msg_dlg.destroy()
478 return
479 break
480 chooser.destroy()
481
482 def export_to_file(self, file_out):
483 """Export to file"""
484
485 pdf_output = PdfFileWriter()
486 pdf_input = []
487 for pdfdoc in self.pdfqueue:
488 pdfdoc_inp = PdfFileReader(file(pdfdoc.copyname, 'rb'))
489 if pdfdoc_inp.getIsEncrypted():
490 try: # Workaround for lp:#355479
491 stat = pdfdoc_inp.decrypt('')
492 except:
493 stat = 0
494 if (stat!=1):
495 errmsg = _('File %s is encrypted.\n'
496 'Support for encrypted files has not been implemented yet.\n'
497 'File export failed.') % pdfdoc.filename
498 raise Exception, errmsg
499 #FIXME
500 #else
501 # ask for password and decrypt file
502 pdf_input.append(pdfdoc_inp)
503
504 for row in self.model:
505 # add pages from input to output document
506 nfile = row[2]
507 npage = row[3]
508 current_page = copy(pdf_input[nfile-1].getPage(npage-1))
509 angle = row[6]
510 angle0 = current_page.get("/Rotate",0)
511 crop = [row[7],row[8],row[9],row[10]]
512 if angle != 0:
513 current_page.rotateClockwise(angle)
514 if crop != [0.,0.,0.,0.]:
515 rotate_times = (((angle + angle0) % 360 + 45) / 90) % 4
516 crop_init = crop
517 if rotate_times != 0:
518 perm = [0,2,1,3]
519 for it in range(rotate_times):
520 perm.append(perm.pop(0))
521 perm.insert(1,perm.pop(2))
522 crop = [crop_init[perm[side]] for side in range(4)]
523 #(x1, y1) = current_page.cropBox.lowerLeft
524 #(x2, y2) = current_page.cropBox.upperRight
525 (x1, y1) = [float(xy) for xy in current_page.mediaBox.lowerLeft]
526 (x2, y2) = [float(xy) for xy in current_page.mediaBox.upperRight]
527 x1_new = int(x1 + (x2-x1) * crop[0])
528 x2_new = int(x2 - (x2-x1) * crop[1])
529 y1_new = int(y1 + (y2-y1) * crop[3])
530 y2_new = int(y2 - (y2-y1) * crop[2])
531 #current_page.cropBox.lowerLeft = (x1_new, y1_new)
532 #current_page.cropBox.upperRight = (x2_new, y2_new)
533 current_page.mediaBox.lowerLeft = (x1_new, y1_new)
534 current_page.mediaBox.upperRight = (x2_new, y2_new)
535
536 pdf_output.addPage(current_page)
537
538 # finally, write "output" to document-output.pdf
539 print(_('exporting to:'), file_out)
540 pdf_output.write(file(file_out, 'wb'))
541
542 def on_action_add_doc_activate(self, widget, data=None):
543 """Import doc"""
544
545 chooser = gtk.FileChooserDialog(title=_('_Import...'),
546 action=gtk.FILE_CHOOSER_ACTION_OPEN,
547 buttons=(gtk.STOCK_CANCEL,
548 gtk.RESPONSE_CANCEL,
549 gtk.STOCK_OPEN,
550 gtk.RESPONSE_OK))
551 chooser.set_current_folder(self.import_directory)
552 chooser.set_select_multiple(True)
553
554 filter_all = gtk.FileFilter()
555 filter_all.set_name(_('All files'))
556 filter_all.add_pattern('*')
557 chooser.add_filter(filter_all)
558
559 filter_pdf = gtk.FileFilter()
560 filter_pdf.set_name(_('PDF files'))
561 filter_pdf.add_mime_type('application/pdf')
562 chooser.add_filter(filter_pdf)
563 chooser.set_filter(filter_pdf)
564
565 response = chooser.run()
566 if response == gtk.RESPONSE_OK:
567 for filename in chooser.get_filenames():
568 if os.path.isfile(filename):
569 # FIXME
570 f = gio.File(filename)
571 f_info = f.query_info('standard::content-type')
572 mime_type = f_info.get_content_type()
573 expected_mime_type = 'application/pdf'
574
575 if mime_type == expected_mime_type:
576 self.add_pdf_pages(filename)
577 elif mime_type[:34] == 'application/vnd.oasis.opendocument':
578 print(_('OpenDocument not supported yet!'))
579 elif mime_type[:5] == 'image':
580 print(_('Image file not supported yet!'))
581 else:
582 print(_('File type not supported!'))
583 else:
584 print(_('File %s does not exist') % filename)
585 elif response == gtk.RESPONSE_CANCEL:
586 print(_('Closed, no files selected'))
587 chooser.destroy()
588 gobject.idle_add(self.retitle)
589
590 def clear_selected(self, button=None):
591 """Removes the selected elements in the IconView"""
592
593 model = self.iconview.get_model()
594 selection = self.iconview.get_selected_items()
595 if selection:
596 selection.sort(reverse=True)
597 self.set_unsaved(True)
598 for path in selection:
599 iter = model.get_iter(path)
600 model.remove(iter)
601 path = selection[-1]
602 self.iconview.select_path(path)
603 if not self.iconview.path_is_selected(path):
604 if len(model) > 0: # select the last row
605 row = model[-1]
606 path = row.path
607 self.iconview.select_path(path)
608 self.iconview.grab_focus()
609
610 def iv_drag_begin(self, iconview, context):
611 """Sets custom icon on drag begin for multiple items selected"""
612
613 if len(iconview.get_selected_items()) > 1:
614 iconview.stop_emission('drag_begin')
615 context.set_icon_stock(gtk.STOCK_DND_MULTIPLE, 0, 0)
616
617 def iv_dnd_get_data(self, iconview, context,
618 selection_data, target_id, etime):
619 """Handles requests for data by drag and drop in iconview"""
620
621 model = iconview.get_model()
622 selection = self.iconview.get_selected_items()
623 selection.sort(key=lambda x: x[0])
624 data = []
625 for path in selection:
626 if selection_data.target == 'MODEL_ROW_INTERN':
627 data.append(str(path[0]))
628 elif selection_data.target == 'MODEL_ROW_EXTERN':
629 iter = model.get_iter(path)
630 nfile, npage, angle = model.get(iter, 2, 3, 6)
631 crop = model.get(iter, 7, 8, 9, 10)
632 pdfdoc = self.pdfqueue[nfile - 1]
633 data.append('\n'.join([pdfdoc.filename,
634 str(npage),
635 str(angle)] +
636 [str(side) for side in crop]))
637 if data:
638 data = '\n;\n'.join(data)
639 selection_data.set(selection_data.target, 8, data)
640
641 def iv_dnd_received_data(self, iconview, context, x, y,
642 selection_data, target_id, etime):
643 """Handles received data by drag and drop in iconview"""
644
645 model = iconview.get_model()
646 data = selection_data.data
647 if data:
648 data = data.split('\n;\n')
649 drop_info = iconview.get_dest_item_at_pos(x, y)
650 iter_to = None
651 if drop_info:
652 path, position = drop_info
653 ref_to = gtk.TreeRowReference(model,path)
654 else:
655 position = gtk.ICON_VIEW_DROP_RIGHT
656 if len(model) > 0: #find the iterator of the last row
657 row = model[-1]
658 path = row.path
659 ref_to = gtk.TreeRowReference(model,path)
660 if ref_to:
661 before = (position == gtk.ICON_VIEW_DROP_LEFT
662 or position == gtk.ICON_VIEW_DROP_ABOVE)
663 #if target_id == self.MODEL_ROW_INTERN:
664 if selection_data.target == 'MODEL_ROW_INTERN':
665 if before:
666 data.sort(key=int)
667 else:
668 data.sort(key=int,reverse=True)
669 ref_from_list = [gtk.TreeRowReference(model,path)
670 for path in data]
671 for ref_from in ref_from_list:
672 path = ref_to.get_path()
673 iter_to = model.get_iter(path)
674 path = ref_from.get_path()
675 iter_from = model.get_iter(path)
676 row = model[iter_from]
677 if before:
678 model.insert_before(iter_to, row)
679 else:
680 model.insert_after(iter_to, row)
681 if context.action == gtk.gdk.ACTION_MOVE:
682 for ref_from in ref_from_list:
683 path = ref_from.get_path()
684 iter_from = model.get_iter(path)
685 model.remove(iter_from)
686
687 #elif target_id == self.MODEL_ROW_EXTERN:
688 elif selection_data.target == 'MODEL_ROW_EXTERN':
689 if not before:
690 data.reverse()
691 while data:
692 tmp = data.pop(0).split('\n')
693 filename = tmp[0]
694 npage, angle = [int(k) for k in tmp[1:3]]
695 crop = [float(side) for side in tmp[3:7]]
696 if self.add_pdf_pages(filename, npage, npage,
697 angle, crop):
698 if len(model) > 0:
699 path = ref_to.get_path()
700 iter_to = model.get_iter(path)
701 row = model[-1] #the last row
702 path = row.path
703 iter_from = model.get_iter(path)
704 if before:
705 model.move_before(iter_from, iter_to)
706 else:
707 model.move_after(iter_from, iter_to)
708 if context.action == gtk.gdk.ACTION_MOVE:
709 context.finish(True, True, etime)
710
711 def iv_dnd_data_delete(self, widget, context):
712 """Deletes dnd items after a successful move operation"""
713
714 model = self.iconview.get_model()
715 selection = self.iconview.get_selected_items()
716 ref_del_list = [gtk.TreeRowReference(model,path) for path in selection]
717 for ref_del in ref_del_list:
718 path = ref_del.get_path()
719 iter = model.get_iter(path)
720 model.remove(iter)
721
722 def iv_dnd_motion(self, iconview, context, x, y, etime):
723 """Handles the drag-motion signal in order to auto-scroll the view"""
724
725 autoscroll_area = 40
726 sw_vadj = self.sw.get_vadjustment()
727 sw_height = self.sw.get_allocation().height
728 if y -sw_vadj.get_value() < autoscroll_area:
729 if not self.iv_auto_scroll_timer:
730 self.iv_auto_scroll_direction = gtk.DIR_UP
731 self.iv_auto_scroll_timer = gobject.timeout_add(150,
732 self.iv_auto_scroll)
733 elif y -sw_vadj.get_value() > sw_height - autoscroll_area:
734 if not self.iv_auto_scroll_timer:
735 self.iv_auto_scroll_direction = gtk.DIR_DOWN
736 self.iv_auto_scroll_timer = gobject.timeout_add(150,
737 self.iv_auto_scroll)
738 elif self.iv_auto_scroll_timer:
739 gobject.source_remove(self.iv_auto_scroll_timer)
740 self.iv_auto_scroll_timer = None
741
742 def iv_dnd_leave_end(self, widget, context, ignored=None):
743 """Ends the auto-scroll during DND"""
744
745 if self.iv_auto_scroll_timer:
746 gobject.source_remove(self.iv_auto_scroll_timer)
747 self.iv_auto_scroll_timer = None
748
749 def iv_auto_scroll(self):
750 """Timeout routine for auto-scroll"""
751
752 sw_vadj = self.sw.get_vadjustment()
753 sw_vpos = sw_vadj.get_value()
754 if self.iv_auto_scroll_direction == gtk.DIR_UP:
755 sw_vpos -= sw_vadj.step_increment
756 sw_vadj.set_value(max(sw_vpos, sw_vadj.lower))
757 elif self.iv_auto_scroll_direction == gtk.DIR_DOWN:
758 sw_vpos += sw_vadj.step_increment
759 sw_vadj.set_value(min(sw_vpos, sw_vadj.upper - sw_vadj.page_size))
760 return True #call me again
761
762 def iv_button_press_event(self, iconview, event):
763 """Manages mouse clicks on the iconview"""
764
765 if event.button == 3:
766 x = int(event.x)
767 y = int(event.y)
768 time = event.time
769 path = iconview.get_path_at_pos(x, y)
770 selection = iconview.get_selected_items()
771 if path:
772 if path not in selection:
773 iconview.unselect_all()
774 iconview.select_path(path)
775 iconview.grab_focus()
776 self.popup.popup(None, None, None, event.button, time)
777 return 1
778
779 def sw_dnd_received_data(self, scrolledwindow, context, x, y,
780 selection_data, target_id, etime):
781 """Handles received data by drag and drop in scrolledwindow"""
782
783 data = selection_data.data
784 if target_id == self.MODEL_ROW_EXTERN:
785 self.model
786 if data:
787 data = data.split('\n;\n')
788 while data:
789 tmp = data.pop(0).split('\n')
790 filename = tmp[0]
791 npage, angle = [int(k) for k in tmp[1:3]]
792 crop = [float(side) for side in tmp[3:7]]
793 if self.add_pdf_pages(filename, npage, npage, angle, crop):
794 if context.action == gtk.gdk.ACTION_MOVE:
795 context.finish(True, True, etime)
796 elif target_id == self.TEXT_URI_LIST:
797 uri = data.strip()
798 uri_splitted = uri.split() # we may have more than one file dropped
799 for uri in uri_splitted:
800 filename = self.get_file_path_from_dnd_dropped_uri(uri)
801 if os.path.isfile(filename): # is it file?
802 self.add_pdf_pages(filename)
803
804 def sw_button_press_event(self, scrolledwindow, event):
805 """Unselects all items in iconview on mouse click in scrolledwindow"""
806
807 if event.button == 1:
808 self.iconview.unselect_all()
809
810 def sw_scroll_event(self, scrolledwindow, event):
811 """Manages mouse scroll events in scrolledwindow"""
812
813 if event.state & gtk.gdk.CONTROL_MASK:
814 if event.direction == gtk.gdk.SCROLL_UP:
815 self.zoom_change(1)
816 return 1
817 elif event.direction == gtk.gdk.SCROLL_DOWN:
818 self.zoom_change(-1)
819 return 1
820
821 def zoom_set(self, level):
822 """Sets the zoom level"""
823 self.zoom_level = max(min(level, 5), -24)
824 self.zoom_scale = 1.1 ** self.zoom_level
825 for row in self.model:
826 row[4] = self.zoom_scale
827 self.reset_iv_width()
828
829 def zoom_change(self, step=5):
830 """Modifies the zoom level"""
831 self.zoom_set(self.zoom_level + step)
832
833 def zoom_in(self, widget=None):
834 """Increases the zoom level by 5 steps"""
835 self.zoom_change(5)
836
837 def zoom_out(self, widget=None, step=5):
838 """Reduces the zoom level by 5 steps"""
839 self.zoom_change(-5)
840
841 def get_file_path_from_dnd_dropped_uri(self, uri):
842 """Extracts the path from an uri"""
843
844 path = urllib.url2pathname(uri) # escape special chars
845 path = path.strip('\r\n\x00') # remove \r\n and NULL
846
847 # get the path to file
848 if path.startswith('file:\\\\\\'): # windows
849 path = path[8:] # 8 is len('file:///')
850 elif path.startswith('file://'): # nautilus, rox
851 path = path[7:] # 7 is len('file://')
852 elif path.startswith('file:'): # xffm
853 path = path[5:] # 5 is len('file:')
854 return path
855
856 def rotate_page_right(self, widget, data=None):
857 self.rotate_page(90)
858
859 def rotate_page_left(self, widget, data=None):
860 self.rotate_page(-90)
861
862 def rotate_page(self, angle):
863 """Rotates the selected page in the IconView"""
864
865 model = self.iconview.get_model()
866 selection = self.iconview.get_selected_items()
867 if len(selection) > 0:
868 self.set_unsaved(True)
869 rotate_times = (((-angle) % 360 + 45) / 90) % 4
870 if rotate_times is not 0:
871 for path in selection:
872 iter = model.get_iter(path)
873 nfile = model.get_value(iter, 2)
874 npage = model.get_value(iter, 3)
875
876 crop = [0.,0.,0.,0.]
877 perm = [0,2,1,3]
878 for it in range(rotate_times):
879 perm.append(perm.pop(0))
880 perm.insert(1,perm.pop(2))
881 crop = [model.get_value(iter, 7 + perm[side]) for side in range(4)]
882 for side in range(4):
883 model.set_value(iter, 7 + side, crop[side])
884
885 new_angle = model.get_value(iter, 6) + int(angle)
886 new_angle = new_angle % 360
887 model.set_value(iter, 6, new_angle)
888 self.update_geometry(iter)
889 self.reset_iv_width()
890
891 def crop_page_dialog(self, widget):
892 """Opens a dialog box to define margins for page cropping"""
893
894 sides = ('L', 'R', 'T', 'B')
895 side_names = {'L':_('Left'), 'R':_('Right'),
896 'T':_('Top'), 'B':_('Bottom') }
897 opposite_sides = {'L':'R', 'R':'L', 'T':'B', 'B':'T' }
898
899 def set_crop_value(spinbutton, side):
900 opp_side = opposite_sides[side]
901 pos = sides.index(opp_side)
902 adj = spin_list[pos].get_adjustment()
903 adj.set_upper(99.0 - spinbutton.get_value())
904
905 model = self.iconview.get_model()
906 selection = self.iconview.get_selected_items()
907
908 crop = [0.,0.,0.,0.]
909 if selection:
910 path = selection[0]
911 pos = model.get_iter(path)
912 crop = [model.get_value(pos, 7 + side) for side in range(4)]
913
914 dialog = gtk.Dialog(title=(_('Crop Selected Pages')),
915 parent=self.window,
916 flags=gtk.DIALOG_MODAL,
917 buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
918 gtk.STOCK_OK, gtk.RESPONSE_OK))
919 dialog.set_size_request(340, 250)
920 dialog.set_default_response(gtk.RESPONSE_OK)
921
922 frame = gtk.Frame(_('Crop Margins'))
923 dialog.vbox.pack_start(frame, False, False, 20)
924
925 vbox = gtk.VBox(False, 0)
926 frame.add(vbox)
927
928 spin_list = []
929 units = 2 * [_('% of width')] + 2 * [_('% of height')]
930 for side in sides:
931 hbox = gtk.HBox(True, 0)
932 vbox.pack_start(hbox, False, False, 5)
933
934 label = gtk.Label(side_names[side])
935 label.set_alignment(0, 0.0)
936 hbox.pack_start(label, True, True, 20)
937
938 adj = gtk.Adjustment(100.*crop.pop(0), 0.0, 99.0, 1.0, 5.0, 0.0)
939 spin = gtk.SpinButton(adj, 0, 1)
940 spin.set_activates_default(True)
941 spin.connect('value-changed', set_crop_value, side)
942 spin_list.append(spin)
943 hbox.pack_start(spin, False, False, 30)
944
945 label = gtk.Label(units.pop(0))
946 label.set_alignment(0, 0.0)
947 hbox.pack_start(label, True, True, 0)
948
949 dialog.show_all()
950 result = dialog.run()
951
952 if result == gtk.RESPONSE_OK:
953 modified = False
954 crop = [spin.get_value()/100. for spin in spin_list]
955 for path in selection:
956 pos = model.get_iter(path)
957 for it in range(4):
958 old_val = model.get_value(pos, 7 + it)
959 model.set_value(pos, 7 + it, crop[it])
960 if crop[it] != old_val:
961 modified = True
962 self.update_geometry(pos)
963 if modified:
964 self.set_unsaved(True)
965 self.reset_iv_width()
966 elif result == gtk.RESPONSE_CANCEL:
967 print(_('Dialog closed'))
968 dialog.destroy()
969
970 def about_dialog(self, widget, data=None):
971 about_dialog = gtk.AboutDialog()
972 try:
973 about_dialog.set_transient_for(self.window)
974 about_dialog.set_modal(True)
975 except:
976 pass
977 # FIXME
978 about_dialog.set_name(APPNAME)
979 about_dialog.set_version(VERSION)
980 about_dialog.set_comments(_(
981 '%s is a tool for rearranging and modifying PDF files.' \
982 'Developed using GTK+ and Python') % APPNAME)
983 about_dialog.set_authors(['Konstantinos Poulios',])
984 about_dialog.set_website_label(WEBSITE)
985 about_dialog.set_logo_icon_name('pdfshuffler')
986 about_dialog.set_license(LICENSE)
987 about_dialog.connect('response', lambda w, a: about_dialog.destroy())
988 about_dialog.connect('delete_event', lambda w, a: about_dialog.destroy())
989 about_dialog.show_all()
990
991
992class PDF_Doc:
993 """Class handling PDF documents"""
994
995 def __init__(self, filename, nfile, tmp_dir):
996
997 self.filename = os.path.abspath(filename)
998 (self.path, self.shortname) = os.path.split(self.filename)
999 (self.shortname, self.ext) = os.path.splitext(self.shortname)
1000 f = gio.File(filename)
1001 mime_type = f.query_info('standard::content-type').get_content_type()
1002 expected_mime_type = 'application/pdf'
1003 file_prefix = 'file://'
1004
1005 if mime_type == expected_mime_type:
1006 self.nfile = nfile + 1
1007 self.mtime = os.path.getmtime(filename)
1008 self.copyname = os.path.join(tmp_dir, '%02d_' % self.nfile +
1009 self.shortname + '.pdf')
1010 shutil.copy(self.filename, self.copyname)
1011 self.document = poppler.document_new_from_file (file_prefix + self.copyname, None)
1012 self.npage = self.document.get_n_pages()
1013 else:
1014 self.nfile = 0
1015 self.npage = 0
1016
1017
1018class PDF_Renderer(threading.Thread,gobject.GObject):
1019
1020 def __init__(self, model, pdfqueue):
1021 threading.Thread.__init__(self)
1022 gobject.GObject.__init__(self)
1023 self.model = model
1024 self.pdfqueue = pdfqueue
1025 self.quit = False
1026
1027 def run(self):
1028 for idx, row in enumerate(self.model):
1029 if self.quit:
1030 return
1031 if not row[1].surface:
1032 try:
1033 nfile = row[2]
1034 npage = row[3]
1035 pdfdoc = self.pdfqueue[nfile - 1]
1036 page = pdfdoc.document.get_page(npage-1)
1037 w, h = page.get_size()
1038 thumbnail = CairoImage(int(w), int(h))
1039 cr = cairo.Context(thumbnail.surface)
1040 page.render(cr)
1041 time.sleep(0.003)
1042 gobject.idle_add(self.emit,'update_thumbnail', idx, thumbnail,
1043 priority=gobject.PRIORITY_LOW)
1044 except Exception,e:
1045 print e
1046
1047
1048def main():
1049 """This function starts PdfShuffler"""
1050 gtk.gdk.threads_init()
1051 gobject.threads_init()
1052 PdfShuffler()
1053 gtk.main()
1054
1055if __name__ == '__main__':
1056 main()
1057
01058
=== modified file 'po/genpot.sh'
--- po/genpot.sh 2009-05-16 23:26:32 +0000
+++ po/genpot.sh 2012-02-12 00:35:23 +0000
@@ -21,4 +21,5 @@
21#21#
2222
23# Make translation files23# Make translation files
24xgettext -L python -o po/pdfshuffler.pot pdfshuffler24intltool-extract --type=gettext/glade data/pdfshuffler.ui
25xgettext --language=Python --keyword=_ --keyword=N_ --output=po/pdfshuffler.pot pdfshuffler data/pdfshuffler.ui.h
2526
=== added file 'po/ja.po'
--- po/ja.po 1970-01-01 00:00:00 +0000
+++ po/ja.po 2012-02-12 00:35:23 +0000
@@ -0,0 +1,156 @@
1# SOME DESCRIPTIVE TITLE.
2# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3# This file is distributed under the same license as the PACKAGE package.
4# Toshiharu Kudoh <toshi.kd2@gmail.com>, 2010.
5#
6
7msgid ""
8msgstr ""
9"Project-Id-Version: 0.5.1\n"
10"Report-Msgid-Bugs-To: \n"
11"POT-Creation-Date: 2010-02-11 12:51+JST\n"
12"PO-Revision-Date: 2010-11-20 01:48+JST\n"
13"Last-Translator: Toshiharu Kudoh <toshi.kd2@gmail.com>\n"
14"Language-Team: Japanese <LL@li.org>\n"
15"MIME-Version: 1.0\n"
16"Content-Type: text/plain; charset=UTF-8\n"
17"Content-Transfer-Encoding: 8bit\n"
18
19#: pdfshuffler:101 pdfshuffler:520
20#, python-format
21msgid "File %s does not exist"
22msgstr "%s は存在しません"
23
24#: pdfshuffler:205 pdfshuffler:243
25msgid "Delete Page(s)"
The diff has been truncated for viewing.

Subscribers

People subscribed via source and target branches