Merge lp:~oem-community-qa/checkbox-editor/bug619720 into lp:checkbox-editor

Proposed by Javier Collado
Status: Merged
Approved by: Javier Collado
Approved revision: 59
Merge reported by: Javier Collado
Merged at revision: not available
Proposed branch: lp:~oem-community-qa/checkbox-editor/bug619720
Merge into: lp:checkbox-editor
Diff against target: 927 lines (+378/-185)
9 files modified
checkbox_editor/editor.py (+107/-47)
checkbox_editor/form.py (+6/-10)
checkbox_editor/glade/editor.glade (+31/-2)
checkbox_editor/model.py (+122/-43)
checkbox_editor/preferences.py (+17/-31)
checkbox_editor/treeview.py (+19/-34)
checkbox_editor/util.py (+32/-0)
checkbox_editor/vc.py (+37/-18)
debian/changelog (+7/-0)
To merge this branch: bzr merge lp:~oem-community-qa/checkbox-editor/bug619720
Reviewer Review Type Date Requested Status
Javier Collado (community) Approve
Review via email: mp+34447@code.launchpad.net

Description of the change

Message dialog warns user on shared directory insufficient permissions
'Save as' functionality implemented

To post a comment you must log in.
Revision history for this message
Javier Collado (javier.collado) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'checkbox_editor/editor.py'
2--- checkbox_editor/editor.py 2010-08-31 10:10:41 +0000
3+++ checkbox_editor/editor.py 2010-09-02 16:41:06 +0000
4@@ -17,7 +17,7 @@
5 from .treeview import Treeview
6 from .form import Form
7 from .statusbar import StatusBar
8-from .util import CommandLauncher, ExceptionHandled, exception_handler
9+from .util import CommandLauncher, ExceptionHandled, exception_handler, MessageDialogRunner
10 from . import build
11
12
13@@ -63,6 +63,7 @@
14 'close_menuitem_activate_cb': self.close_menuitem_activate_cb,
15 'revert_menuitem_activate_cb': self.revert_menuitem_activate_cb,
16 'save_menuitem_activate_cb': self.save_menuitem_activate_cb,
17+ 'save_as_menuitem_activate_cb': self.save_as_menuitem_activate_cb,
18 'open_testplan_menuitem_activate_cb': self.open_testplan_menuitem_activate_cb,
19 'save_testplan_menuitem_activate_cb': self.save_testplan_menuitem_activate_cb,
20 'save_testplan_as_menuitem_activate_cb': self.save_testplan_as_menuitem_activate_cb,
21@@ -109,6 +110,7 @@
22 for widget_name in widget_names:
23 self.builder.get_object(widget_name).set_sensitive(True)
24
25+ self.set_title(options.directory)
26 self.statusbar._write_tmp_message('Shared directory opened')
27
28 treeview = self.builder.get_object('treeview')
29@@ -173,19 +175,20 @@
30 window.add_accel_group(group)
31
32 accelerators_data = [
33- ('open_menuitem', 'o'),
34- ('close_menuitem', 'w'),
35- ('quit_menuitem', 'q'),
36- ('save_menuitem', 's'),
37- ('add_menuitem', 'a'),
38- ('remove_menuitem', 'r'),
39- ('preferences_menuitem', 'p'),
40- ('contents_menuitem', 'F1'),
41+ ('open_menuitem', '<Control>o'),
42+ ('close_menuitem', '<Control>w'),
43+ ('quit_menuitem', '<Control>q'),
44+ ('save_menuitem', '<Control>s'),
45+ ('save_as_menuitem', '<Shift><Control>s'),
46+ ('add_menuitem', '<Control>a'),
47+ ('remove_menuitem', '<Control>r'),
48+ ('preferences_menuitem', '<Control>p'),
49+ ('contents_menuitem', '<Control>F1'),
50 ]
51
52- for widget_name, key in accelerators_data:
53+ for widget_name, keys in accelerators_data:
54 menuitem = self.builder.get_object(widget_name)
55- accel_key, accel_mod = gtk.accelerator_parse("<Control>%s" % key)
56+ accel_key, accel_mod = gtk.accelerator_parse(keys)
57 menuitem.add_accelerator("activate",
58 group,
59 accel_key,
60@@ -238,6 +241,19 @@
61 return options
62
63
64+ def set_title(self, directory=None):
65+ """
66+ Set main window title based on the opened directory
67+ """
68+ title = 'Checkbox Editor'
69+ if directory:
70+ title = '{0} - '.format(directory) + title
71+ if not os.access(directory, os.F_OK | os.W_OK):
72+ title = '[read only] ' + title
73+ window = self.builder.get_object('window')
74+ window.set_title(title)
75+
76+
77 def window_delete_event_cb(self, window, event):
78 """
79 Delete main window
80@@ -294,23 +310,26 @@
81 directory = dialog.get_filename()
82 dialog.destroy()
83
84- if response == gtk.RESPONSE_ACCEPT:
85- if model.is_valid_directory(directory):
86- self.preferences.load(directory)
87- if ExceptionHandled == model.load(directory):
88- model.clear()
89- else:
90- for widget_name in ('close_menuitem', 'close_toolbutton',
91- 'open_testplan_menuitem'):
92- widget = self.builder.get_object(widget_name)
93- widget.set_sensitive(True)
94-
95- for widget_name in ('push_menuitem', 'push_toolbutton',
96- 'commit_menuitem', 'commit_toolbutton'):
97- widget = self.builder.get_object(widget_name)
98- widget.set_sensitive(False)
99-
100- self.statusbar._write_tmp_message('Shared directory opened')
101+ if response != gtk.RESPONSE_ACCEPT:
102+ return False
103+
104+ if model.is_valid_directory(directory):
105+ self.preferences.load(directory)
106+ if ExceptionHandled == model.load(directory):
107+ model.clear()
108+ else:
109+ for widget_name in ('close_menuitem', 'close_toolbutton',
110+ 'open_testplan_menuitem'):
111+ widget = self.builder.get_object(widget_name)
112+ widget.set_sensitive(True)
113+
114+ for widget_name in ('push_menuitem', 'push_toolbutton',
115+ 'commit_menuitem', 'commit_toolbutton'):
116+ widget = self.builder.get_object(widget_name)
117+ widget.set_sensitive(False)
118+
119+ self.set_title(directory)
120+ self.statusbar._write_tmp_message('Shared directory opened')
121
122 return False
123
124@@ -322,7 +341,6 @@
125 if not self._continue_without_saving():
126 return False
127
128-
129 treeview = self.builder.get_object('treeview')
130 model = treeview.get_model()
131 model.clear()
132@@ -332,13 +350,12 @@
133 self.preferences.load()
134
135 widget_names = ['close_menuitem', 'close_toolbutton',
136- 'save_menuitem', 'save_toolbutton',
137- 'revert_menuitem', 'revert_toolbutton',
138 'open_testplan_menuitem', 'save_testplan_menuitem',
139 'save_testplan_as_menuitem', 'export_testplan_menuitem']
140 for widget_name in widget_names:
141 self.builder.get_object(widget_name).set_sensitive(False)
142
143+ self.set_title()
144 self.statusbar._write_tmp_message('Shared directory closed')
145
146 return False
147@@ -354,6 +371,54 @@
148 if saved is True:
149 self.statusbar._write_tmp_message('Changes saved')
150
151+ return False
152+
153+
154+ def save_as_menuitem_activate_cb(self, widget):
155+ """
156+ Save changes to a different directory
157+ """
158+ # Choose a destination directory
159+ dialog = gtk.FileChooserDialog('Choose Destination Shared Directory',
160+ buttons = (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
161+ gtk.STOCK_OPEN, gtk.RESPONSE_ACCEPT),
162+ action = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
163+
164+ while True:
165+ response = dialog.run()
166+ directory = dialog.get_filename()
167+
168+ if response != gtk.RESPONSE_ACCEPT:
169+ dialog.destroy()
170+ return False
171+
172+ if not os.path.isdir(directory):
173+ message = ("Selected directory doesn't seem to be a valid "
174+ 'directory:\n{0}\n\n'
175+ 'Please make sure that the path <b>exists</b> '
176+ "and that it's a directory"
177+ .format(directory))
178+ MessageDialogRunner('Invalid directory', message).run()
179+
180+ elif not os.access(directory, os.F_OK | os.R_OK | os.W_OK | os.X_OK):
181+ message = ('Not enough <b>permissions</b> '
182+ 'to read/write/execute selected directory:\n{0}\n\n'
183+ .format(directory))
184+ MessageDialogRunner('Insuficcient permissions', message).run()
185+
186+ elif bool(os.listdir(directory)):
187+ message = ("Selected directory isn't <b>empty</b>:\n{0}\n\n"
188+ 'Please select an empty directory, '
189+ 'so that it can be populated with new data properly'
190+ .format(directory))
191+ MessageDialogRunner('Not empty directory', message).run()
192+ else:
193+ break
194+ dialog.destroy()
195+
196+ model = self.builder.get_object('treeview').get_model()
197+ model.save_as(directory)
198+ self.set_title(directory)
199
200 return False
201
202@@ -363,18 +428,16 @@
203 Check if there are pending changes and ask user
204 if he wants to continue
205 """
206- # If save menuitem is sensitive,
207+ # If save_as_menuitem is sensitive,
208 # it means there are pending (not saved) changes
209- if self.builder.get_object('save_menuitem').get_property('sensitive'):
210- message = ('Changes have not been saved.\n\n'
211+ if self.builder.get_object('save_as_menuitem').get_property('sensitive'):
212+ message = ('Changes have <b>not</b> been saved.\n\n'
213 'Would you like to continue?')
214- dialog = gtk.MessageDialog(type=gtk.MESSAGE_WARNING,
215- buttons=gtk.BUTTONS_YES_NO,
216- message_format=message)
217- response = dialog.run()
218- dialog.destroy()
219+ response = MessageDialogRunner('Changes not saved', message,
220+ type=gtk.MESSAGE_WARNING,
221+ buttons=gtk.BUTTONS_YES_NO).run()
222
223- if response == gtk.RESPONSE_NO:
224+ if response != gtk.RESPONSE_YES:
225 return False
226
227 return True
228@@ -557,13 +620,10 @@
229 'Please note that they are not going to be available\n'
230 'in checkbox.\n\n'
231 'Would you like to continue?')
232- dialog = gtk.MessageDialog(flags=gtk.DIALOG_MODAL,
233- type=gtk.MESSAGE_WARNING,
234- buttons=gtk.BUTTONS_YES_NO)
235- dialog.set_markup(message)
236- response = dialog.run()
237- dialog.destroy()
238- if response == gtk.RESPONSE_NO:
239+ response = MessageDialogRunner('Changes not saved', message,
240+ type=gtk.MESSAGE_WARNING,
241+ buttons=gtk.BUTTONS_YES_NO).run()
242+ if response != gtk.RESPONSE_YES:
243 return
244
245 treeview = self.builder.get_object('treeview')
246
247=== modified file 'checkbox_editor/form.py'
248--- checkbox_editor/form.py 2010-08-31 09:13:14 +0000
249+++ checkbox_editor/form.py 2010-09-02 16:41:06 +0000
250@@ -8,7 +8,7 @@
251 import os, re, logging, shlex
252
253 from .model import TreeStore
254-from .util import CommandLauncher, SignalBlocker
255+from .util import CommandLauncher, SignalBlocker, MessageDialogRunner
256
257 class Form(object):
258 """
259@@ -374,16 +374,12 @@
260 description = row[TreeStore.DESCRIPTION]
261
262 # Prompt user to confirm change
263- message=('Jobs that use the {0} plugin cannot contain '
264+ message=('Jobs that use the {0!r} plugin cannot contain '
265 'children jobs definitions, so they will <b>removed</b>. '
266- 'Is that what you would like to do?'.format(repr(value)))
267- dialog = gtk.MessageDialog(type=gtk.MESSAGE_WARNING,
268- buttons=gtk.BUTTONS_YES_NO)
269- dialog.set_title('Cannot have children')
270- dialog.set_markup(message)
271- response = dialog.run()
272- dialog.destroy()
273-
274+ 'Is that what you would like to do?'.format(value))
275+ response = MessageDialogRunner('Cannot have children', message,
276+ type=gtk.MESSAGE_WARNING,
277+ buttons=gtk.BUTTONS_YES_NO).run()
278 change_cancelled = False if response == gtk.RESPONSE_YES else True
279
280 if change_cancelled:
281
282=== modified file 'checkbox_editor/glade/editor.glade'
283--- checkbox_editor/glade/editor.glade 2010-08-31 09:13:14 +0000
284+++ checkbox_editor/glade/editor.glade 2010-09-02 16:41:06 +0000
285@@ -76,6 +76,16 @@
286 </object>
287 </child>
288 <child>
289+ <object class="GtkImageMenuItem" id="save_as_menuitem">
290+ <property name="label" translatable="yes">Save as...</property>
291+ <property name="visible">True</property>
292+ <property name="sensitive">False</property>
293+ <property name="image">save_as_image_1</property>
294+ <property name="use_stock">False</property>
295+ <signal name="activate" handler="save_as_menuitem_activate_cb"/>
296+ </object>
297+ </child>
298+ <child>
299 <object class="GtkImageMenuItem" id="revert_menuitem">
300 <property name="label">gtk-revert-to-saved</property>
301 <property name="visible">True</property>
302@@ -115,7 +125,7 @@
303 <property name="label" translatable="yes">Save test plan as...</property>
304 <property name="visible">True</property>
305 <property name="sensitive">False</property>
306- <property name="image">save_as_image</property>
307+ <property name="image">save_as_image_2</property>
308 <property name="use_stock">False</property>
309 <signal name="activate" handler="save_testplan_as_menuitem_activate_cb"/>
310 </object>
311@@ -354,6 +364,21 @@
312 </packing>
313 </child>
314 <child>
315+ <object class="GtkToolButton" id="save_as_toolbutton">
316+ <property name="visible">True</property>
317+ <property name="sensitive">False</property>
318+ <property name="tooltip_text" translatable="yes">Save changes to a different directory</property>
319+ <property name="label" translatable="yes">Save as...</property>
320+ <property name="use_underline">True</property>
321+ <property name="stock_id">gtk-save-as</property>
322+ <signal name="clicked" handler="save_as_menuitem_activate_cb"/>
323+ </object>
324+ <packing>
325+ <property name="expand">False</property>
326+ <property name="homogeneous">True</property>
327+ </packing>
328+ </child>
329+ <child>
330 <object class="GtkToolButton" id="revert_toolbutton">
331 <property name="visible">True</property>
332 <property name="sensitive">False</property>
333@@ -916,7 +941,11 @@
334 <property name="visible">True</property>
335 <property name="stock">gtk-save</property>
336 </object>
337- <object class="GtkImage" id="save_as_image">
338+ <object class="GtkImage" id="save_as_image_1">
339+ <property name="visible">True</property>
340+ <property name="stock">gtk-save-as</property>
341+ </object>
342+ <object class="GtkImage" id="save_as_image_2">
343 <property name="visible">True</property>
344 <property name="stock">gtk-save-as</property>
345 </object>
346
347=== modified file 'checkbox_editor/model.py'
348--- checkbox_editor/model.py 2010-08-31 10:10:41 +0000
349+++ checkbox_editor/model.py 2010-09-02 16:41:06 +0000
350@@ -10,7 +10,7 @@
351
352 from .data import DataParser, DataEncoder, InternalParsingError, Reporter
353 from .vc import VersionControl
354-from .util import handle_exceptions, ExceptionDialog, ExceptionHandled
355+from .util import handle_exceptions, ExceptionDialog, ExceptionHandled, MessageDialogRunner
356
357
358 class RenameOverwriteError(Exception):
359@@ -58,13 +58,6 @@
360
361 self.clear()
362
363- # Widgets that must be enabled/disabled
364- # when some change happens to the model
365- on_change_widget_names = ['save_menuitem', 'save_toolbutton',
366- 'revert_menuitem', 'revert_toolbutton']
367- self.on_change_widgets = [builder.get_object(widget_name)
368- for widget_name in on_change_widget_names]
369-
370
371 def clear(self):
372 """
373@@ -80,6 +73,13 @@
374 self.files_to_rename = {}
375 gtk.TreeStore.clear(self)
376
377+ # Disable all save/revert options
378+ for widget_name in ['save_menuitem', 'save_toolbutton',
379+ 'save_as_menuitem', 'save_as_toolbutton',
380+ 'revert_menuitem', 'revert_toolbutton']:
381+ widget = self.builder.get_object(widget_name)
382+ widget.set_sensitive(False)
383+
384
385 def get_all_rows(self, parent = None):
386 """
387@@ -126,55 +126,84 @@
388 Discard changes and reload data from directory
389 """
390 logging.info('Reverting changes')
391- self.load()
392-
393- # Disable save/revert options
394- for widget in self.on_change_widgets:
395- widget.set_sensitive(False)
396-
397- self.files_to_remove = []
398- self.files_to_rename = {}
399+ self.load(self.directory)
400
401
402 def is_valid_directory(self, directory):
403+ """
404+ Return True if it's a valid directory and data should be loaded
405+ In case of not enough permissions, the user might be prompted
406+ to use read-only mode
407+ """
408+ # Make sure directory exists
409+ if not os.path.isdir(directory):
410+ message = ("Selected directory doesn't seem to be a valid "
411+ 'shared checkbox directory:\n{0}\n\n'
412+ 'Please make sure that the path <b>exists</b> '
413+ "and that it's a directory"
414+ .format(directory))
415+ MessageDialogRunner('Invalid directory', message).run()
416+ return False
417+
418+ # Make sure that it's possible to access to selected directory
419+ if not os.access(directory, os.F_OK | os.R_OK | os.X_OK):
420+ message = ('Not enough <b>permissions</b> to access selected directory:\n{0}\n\n'
421+ 'Please change permissions accordingly or run checkbox-editor '
422+ 'with from a different user account with sufficient permissions.'
423+ .format(directory))
424+ MessageDialogRunner('Insuficcient permissions', message).run()
425+ return False
426+
427+ # Make sure that directory contains a jobs subdirectory
428 jobs_dir = os.path.join(directory, 'jobs')
429- if not (os.path.isdir(directory)
430- and os.path.isdir(jobs_dir)):
431- dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
432- buttons=gtk.BUTTONS_OK)
433- dialog.set_title('Invalid directory')
434+ if not os.path.isdir(jobs_dir):
435 message = ("Selected directory doesn't seem to be a valid "
436- 'shared checkbox directory. Please make sure '
437- 'to select a directory that at least contains '
438- 'a <b>jobs</b> subdirectory.')
439- dialog.set_markup(message)
440- dialog.run()
441- dialog.destroy()
442+ 'shared checkbox directory:\n{0}\n\n'
443+ 'Please make sure to select a directory '
444+ 'that at least contains a <b>jobs</b> subdirectory.'
445+ .format(directory))
446+ MessageDialogRunner('Invalid directory', message).run()
447 return False
448
449+ # Make sure that it's possible to create/delete files
450+ # in the selected directory
451+ if not os.access(directory, os.F_OK | os.W_OK):
452+ message = ('Not enough <b>permissions</b> to write files '
453+ 'to selected directory:\n{0}\n\n'
454+ 'Unless permissions are changed accordingly '
455+ 'or checkbox-editor is executed '
456+ 'from a different user account with sufficient permissions, '
457+ "it won't be possible to save changes to the same directory.\n\n"
458+ 'Would you like to continue?'
459+ .format(directory))
460+ response = MessageDialogRunner('Insuficcient permissions',
461+ message,
462+ type=gtk.MESSAGE_WARNING,
463+ buttons=gtk.BUTTONS_YES_NO).run()
464+ if response != gtk.RESPONSE_YES:
465+ return False
466+
467 return True
468
469
470 @handle_exceptions(InternalParsingError,
471 'Load operation <b>failed</b>')
472- def load(self, directory = None):
473+ def load(self, directory):
474 """
475 Load data from directory
476 """
477 self.clear()
478- self.testplan = None
479-
480- if directory:
481- self.directory = directory
482- self.jobs_dir = os.path.join(directory, 'jobs')
483- self.scripts_dir = os.path.join(directory, 'scripts')
484- self.vc = VersionControl.get(directory,
485- self.preferences.version_control['exclude'])
486-
487- if self.vc.has_changes():
488- for widget_name in ['commit_menuitem', 'commit_toolbutton']:
489- widget = self.builder.get_object(widget_name)
490- widget.set_sensitive(True)
491+
492+ self.directory = directory
493+ self.jobs_dir = os.path.join(directory, 'jobs')
494+ self.scripts_dir = os.path.join(directory, 'scripts')
495+ self.vc = VersionControl.get(directory,
496+ self.preferences.version_control['exclude'])
497+
498+ if self.vc.has_changes():
499+ for widget_name in ['commit_menuitem', 'commit_toolbutton']:
500+ widget = self.builder.get_object(widget_name)
501+ widget.set_sensitive(True)
502
503 if hasattr(self.vc, 'pull'):
504 for widget_name in ['pull_menuitem', 'pull_toolbutton']:
505@@ -187,12 +216,12 @@
506 if not pulled:
507 return ExceptionHandled
508
509+ self._set_on_change_widgets()
510 logging.info('Loading jobs: {0}'.format(self.directory))
511
512 # Little trick that makes parsing work
513 # when relative directories are in preferences
514- if directory:
515- os.chdir(directory)
516+ os.chdir(directory)
517
518 job_filename_pattern = re.compile('\.txt(\.in)?$')
519 jobs_filenames = [os.path.join(self.jobs_dir, file)
520@@ -286,6 +315,19 @@
521 load_job_file(file_row, job_filename)
522
523
524+ def _set_on_change_widgets(self):
525+ """
526+ Set the names of the widgets that are subject to be activated
527+ on any change in the model based on the opened directory
528+ """
529+ on_change_widget_names = ['save_as_menuitem', 'save_as_toolbutton',
530+ 'revert_menuitem', 'revert_toolbutton']
531+ if os.access(self.directory, os.F_OK | os.W_OK):
532+ on_change_widget_names.extend(['save_menuitem', 'save_toolbutton'])
533+ self.on_change_widgets = [self.builder.get_object(widget_name)
534+ for widget_name in on_change_widget_names]
535+
536+
537 @handle_exceptions((EnvironmentError, RenameOverwriteError),
538 'Save operation <b>failed</b>')
539 def save(self):
540@@ -502,6 +544,43 @@
541 row[self.FONT_DESCRIPTION] = pango.FontDescription()
542
543
544+ def save_as(self, destination_directory):
545+ """
546+ Save changes to a different directory
547+ """
548+ source_directory = self.directory
549+ self.vc.clone(destination_directory)
550+
551+ # Update all directory dependent variables in model
552+ logging.debug('Updating internal state to use new directory: {0!r}'
553+ .format(destination_directory))
554+ self.directory = destination_directory
555+ self.jobs_dir = os.path.join(destination_directory, 'jobs')
556+ self.scripts_dir = os.path.join(destination_directory, 'scripts')
557+ self.vc = VersionControl.get(destination_directory,
558+ self.preferences.version_control['exclude'])
559+ def change_base_directory(filename):
560+ return os.path.join(destination_directory,
561+ os.path.relpath(filename, source_directory))
562+
563+ self.files_to_remove = [change_base_directory(filename)
564+ for filename in self.files_to_remove]
565+ self.files_to_rename = dict([(change_base_directory(old_filename),
566+ change_base_directory(new_filename))
567+ for old_filename, new_filename
568+ in self.files_to_rename.items()])
569+
570+ # Update all rows filenames
571+ for row in self.get_all_rows():
572+ filename = row[self.FILENAME]
573+ if filename:
574+ row[self.FILENAME] = change_base_directory(filename)
575+
576+ self.save()
577+ self.preferences.directory = destination_directory
578+ self._set_on_change_widgets()
579+
580+
581 def add_row(self, iterator):
582 """
583 Add a new row to the model
584
585=== modified file 'checkbox_editor/preferences.py'
586--- checkbox_editor/preferences.py 2010-08-31 10:10:41 +0000
587+++ checkbox_editor/preferences.py 2010-09-02 16:41:06 +0000
588@@ -9,7 +9,7 @@
589 import shlex
590 from glob import glob
591
592-from .util import CommandConfigParser as ConfigParser
593+from .util import MessageDialogRunner, CommandConfigParser as ConfigParser
594
595 VC_WIDGET_NAMES = ('pull_on_open', 'commit_on_save', 'push_on_close',
596 'pull_branch', 'push_branch')
597@@ -339,12 +339,7 @@
598 version_control)
599 except IOError as exception:
600 logging.error(exception)
601- dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
602- buttons=gtk.BUTTONS_OK,
603- message_format=str(exception))
604- dialog.set_title('Error')
605- dialog.run()
606- dialog.destroy()
607+ MessageDialogRunner('Error', str(exception)).run()
608 else:
609 self.dialog.destroy()
610
611@@ -470,42 +465,33 @@
612 filename = None
613 response = dialog.run()
614 if response == gtk.RESPONSE_ACCEPT:
615+ abs_filename = dialog.get_filename()
616 filename = os.path.relpath(dialog.get_filename(),
617 self.preferences.directory)
618 if filename.startswith(os.path.pardir):
619- log_message = ('Selected filename must be '
620- 'under opened directory: {0}'
621- .format(self.preferences.directory))
622+ log_message = ('Selected filename {0!r} must be '
623+ 'under opened directory {1!r}'
624+ .format(abs_filename,
625+ self.preferences.directory))
626 logging.error(log_message)
627
628
629- dialog_message = ('Selected filename must be '
630- 'under opened directory:\n<b>{0}</b>'
631- .format(self.preferences.directory))
632- error_dialog = gtk.MessageDialog(flags=gtk.DIALOG_MODAL,
633- type=gtk.MESSAGE_ERROR,
634- buttons=gtk.BUTTONS_OK)
635- error_dialog.set_title('Error')
636- error_dialog.set_markup(dialog_message)
637- error_dialog.run()
638- error_dialog.destroy()
639+ dialog_message = ('Selected filename:\n{0}\n'
640+ 'must be under opened directory:\n{1}'
641+ .format(abs_filename,
642+ self.preferences.directory))
643+ MessageDialogRunner('Error', dialog_message).run()
644 continue
645
646 if filename in filenames and filename != initial_filename:
647- log_message = ('Filename is already selected: {0}'
648- .format(filename))
649+ log_message = ('Filename is already selected: {0!r}'
650+ .format(abs_filename))
651 logging.error(log_message)
652
653
654- dialog_message = ('Filename is already selected:\n<b>{0}</b>'
655- .format(filename))
656- error_dialog = gtk.MessageDialog(flags=gtk.DIALOG_MODAL,
657- type=gtk.MESSAGE_ERROR,
658- buttons=gtk.BUTTONS_OK)
659- error_dialog.set_title('Error')
660- error_dialog.set_markup(dialog_message)
661- error_dialog.run()
662- error_dialog.destroy()
663+ MessageDialogRunner('Error',
664+ 'Filename is already selected:\n{0}'
665+ .format(abs_filename)).run()
666 continue
667 break
668
669
670=== modified file 'checkbox_editor/treeview.py'
671--- checkbox_editor/treeview.py 2010-06-03 08:49:41 +0000
672+++ checkbox_editor/treeview.py 2010-09-02 16:41:06 +0000
673@@ -5,6 +5,7 @@
674 pygtk.require("2.0")
675 import gtk
676 from .model import TreeStore
677+from .util import MessageDialogRunner
678
679 import os, re, logging
680
681@@ -99,16 +100,16 @@
682 self.builder.get_object('job_command_edit').set_sensitive(False)
683
684 logging.debug('Selection changed:\n'
685- '- display_name: {0}\n'
686- '- description: {1}\n'
687- '- filename: {2}\n'
688+ '- display_name: {0!r}\n'
689+ '- description: {1!r}\n'
690+ '- filename: {2!r}\n'
691 '- modified: {3}\n'
692 '- children_modified: {4}\n'
693 '- children_removed: {5}\n'
694 '- selected: {6}'
695- .format(repr(row[TreeStore.DISPLAY_NAME]),
696- repr(row[TreeStore.DESCRIPTION]),
697- repr(row[TreeStore.FILENAME]),
698+ .format(row[TreeStore.DISPLAY_NAME],
699+ row[TreeStore.DESCRIPTION],
700+ row[TreeStore.FILENAME],
701 row[TreeStore.MODIFIED],
702 row[TreeStore.CHILDREN_MODIFIED],
703 row[TreeStore.CHILDREN_REMOVED],
704@@ -285,30 +286,20 @@
705
706 # Validate filename
707 if depth == 0 and not re.search('\.txt(\.in)?$', new_name):
708- dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
709- buttons=gtk.BUTTONS_OK)
710- dialog.set_title('Invalid filename')
711- message = ('Invalid filename: <b>{0}</b>\n'
712- 'The extension should be either <b>.txt.in</b> or <b>.txt</b>'
713- .format(new_name))
714- dialog.set_markup(message)
715- dialog.run()
716- dialog.destroy()
717- return
718+ MessageDialogRunner('Invalid filename',
719+ 'Invalid filename: <b>{0}</b>\n'
720+ 'The extension should be either <b>.txt.in</b> or <b>.txt</b>'
721+ .format(new_name)).run()
722+ return
723
724 # Validate name
725 new_name_exists = any(r for r in model.get_all_rows()
726 if (r[TreeStore.DISPLAY_NAME] == new_name
727 and r.path != row.path))
728 if new_name_exists:
729- dialog = gtk.MessageDialog(type=gtk.MESSAGE_ERROR,
730- buttons=gtk.BUTTONS_OK)
731- dialog.set_title('Invalid name')
732- message = ('<b>{0}</b> is already being used in another row'
733- .format(new_name))
734- dialog.set_markup(message)
735- dialog.run()
736- dialog.destroy()
737+ MessageDialogRunner('Invalid name',
738+ '<b>{0}</b> is already being used in another row'
739+ .format(new_name)).run()
740 return
741
742 # Set display name
743@@ -354,27 +345,21 @@
744 and (len(destination_row.path) > 1
745 or position in (gtk.TREE_VIEW_DROP_INTO_OR_BEFORE,
746 gtk.TREE_VIEW_DROP_INTO_OR_AFTER))):
747- error_message = ('Root nodes can only be moved\n'
748+ error_message = ('Root nodes can <b>only</b> be moved\n'
749 'to other position at the same level')
750 elif (len(origin_row.path) > 1 and len(destination_row.path) <= 1
751 and position in (gtk.TREE_VIEW_DROP_BEFORE,
752 gtk.TREE_VIEW_DROP_AFTER)):
753- error_message = ("Job nodes can't be moved\n"
754+ error_message = ("Job nodes <b>can't</b> be moved\n"
755 'to the root of the tree')
756 elif (origin_row.path == destination_row.path[:len(origin_row.path)]):
757- error_message = ("Job nodes can't be moved\n"
758+ error_message = ("Job nodes <b>can't</b> be moved\n"
759 'to a position under themselves')
760
761 # If error_message is set, then display message
762 # and cancel drag&drop operation
763 if error_message:
764- dialog = gtk.MessageDialog(flags = gtk.DIALOG_MODAL,
765- type = gtk.MESSAGE_ERROR,
766- buttons = gtk.BUTTONS_OK,
767- message_format = error_message)
768- dialog.set_title('Error')
769- dialog.run()
770- dialog.destroy()
771+ MessageDialogRunner('Error', error_message).run()
772 context.finish(False, False, timestamp)
773 return
774
775
776=== modified file 'checkbox_editor/util.py'
777--- checkbox_editor/util.py 2010-06-03 08:49:41 +0000
778+++ checkbox_editor/util.py 2010-09-02 16:41:06 +0000
779@@ -262,3 +262,35 @@
780 stdout, _ = process.communicate()
781 stdout = stdout.splitlines()[0]
782 return stdout
783+
784+
785+class MessageDialogRunner(object):
786+ """
787+ Create a MessageDialog, run it, destroy it and return response
788+ """
789+ def __init__(self, title, message,
790+ type=gtk.MESSAGE_ERROR,
791+ buttons=gtk.BUTTONS_OK,
792+ flags=gtk.DIALOG_MODAL):
793+ """
794+ Create MessageDialog object
795+ """
796+ self.title = title
797+ self.message = message
798+ self.type = type
799+ self.buttons = buttons
800+ self.flags = flags
801+
802+
803+ def run(self):
804+ """
805+ Run dialog, destroy it and return response
806+ """
807+ dialog = gtk.MessageDialog(type=self.type,
808+ buttons=self.buttons,
809+ flags=self.flags)
810+ dialog.set_title(self.title)
811+ dialog.set_markup(self.message)
812+ response = dialog.run()
813+ dialog.destroy()
814+ return response
815
816=== modified file 'checkbox_editor/vc.py'
817--- checkbox_editor/vc.py 2010-06-03 08:49:41 +0000
818+++ checkbox_editor/vc.py 2010-09-02 16:41:06 +0000
819@@ -5,7 +5,7 @@
820 pygtk.require("2.0")
821 import gtk
822
823-import os, logging, threading
824+import os, logging, threading, shutil
825 from cStringIO import StringIO
826
827 from contextlib import contextmanager
828@@ -17,7 +17,7 @@
829 import bzrlib.plugin
830 bzrlib.plugin.load_plugins()
831
832-from .util import handle_exceptions
833+from .util import handle_exceptions, MessageDialogRunner
834
835
836 class VersionControl:
837@@ -209,14 +209,9 @@
838 Push changes
839 """
840 if not branch_url:
841- dialog = gtk.MessageDialog(type = gtk.MESSAGE_ERROR,
842- flags = gtk.DIALOG_MODAL,
843- buttons = gtk.BUTTONS_OK)
844- dialog.set_title('Error')
845- dialog.set_markup('No push branch defined in preferences.')
846- dialog.show_all()
847- dialog.run()
848- dialog.destroy()
849+ MessageDialogRunner('Error',
850+ 'Push branch <b>not</b> defined '
851+ 'in preferences.').run()
852 return False
853
854 logging.debug('Pushing changes to: {0}'.format(repr(branch_url)))
855@@ -243,14 +238,9 @@
856 Pull changes
857 """
858 if not branch_url:
859- dialog = gtk.MessageDialog(type = gtk.MESSAGE_ERROR,
860- flags = gtk.DIALOG_MODAL,
861- buttons = gtk.BUTTONS_OK)
862- dialog.set_title('Error')
863- dialog.set_markup('No pull branch defined in preferences.')
864- dialog.show_all()
865- dialog.run()
866- dialog.destroy()
867+ MessageDialogRunner('Error',
868+ 'Pull branch <b>not</b> defined '
869+ 'in preferences.').run()
870 return False
871
872 logging.debug('Pulling changes from: {0}'.format(repr(branch_url)))
873@@ -356,6 +346,15 @@
874 dialog.destroy()
875
876
877+ def clone(self, destination_directory):
878+ """
879+ Clone working directory to destination
880+ """
881+ logging.debug('Cloning bzr repository from {0!r} to {1!r}'
882+ .format(self.directory, destination_directory))
883+ self.working_tree.bzrdir.sprout(destination_directory)
884+
885+
886 class NullVC(VersionControl):
887 """
888 No version control at all, just files
889@@ -393,3 +392,23 @@
890 Rename file in the file system
891 """
892 os.rename(old_name, new_name)
893+
894+
895+ def clone(self, destination_directory):
896+ """
897+ Copy all files to a remote directory
898+ """
899+ # Directory must not exist for copytree to work
900+ # but FileChooserDialog creates it, so here it's
901+ # removed before trying to copy the contents
902+ # of the original directory
903+ os.rmdir(destination_directory)
904+
905+ # Copy all files in the old shared directory to the new one
906+ source_directory = self.directory
907+ logging.debug('Copying files from {0!r} to {1!r}'
908+ .format(source_directory, destination_directory))
909+ shutil.copytree(source_directory, destination_directory, symlinks=True)
910+
911+
912+5175
913
914=== modified file 'debian/changelog'
915--- debian/changelog 2010-08-31 10:22:18 +0000
916+++ debian/changelog 2010-09-02 16:41:06 +0000
917@@ -1,3 +1,10 @@
918+checkbox-editor (0.9-0ubuntu1~ppa43) lucid; urgency=low
919+
920+ * Message dialog warns user on shared directory insufficient permissions (LP: #619720)
921+ * 'Save as' functionality implemented (LP: #619720)
922+
923+ -- Javier Collado <javier.collado@canonical.com> Thu, 02 Sep 2010 18:29:13 +0200
924+
925 checkbox-editor (0.9-0ubuntu1~ppa42) lucid; urgency=low
926
927 * Added tooltips to toolbuttons (LP: #619710)

Subscribers

People subscribed via source and target branches

to all changes: