9 from ConfigModel import *
11 # ================================================================
12 # Todos and known bugs
13 # ================================================================
15 # - compute PreviewPane constants PP_DX and PP_DY
16 # - test commandline interface
18 # ================================================================
20 # ================================================================
23 GUI_DESCRIPTION = 'tvout.glade'
24 PROFILES = '/etc/pandora/conf/tvout-profiles.conf'
26 # Shell command to change settings:
27 SET_CONFIG_CMD = 'sudo /usr/pandora/scripts/op_tvout.sh %s'
28 CONFIG_PARAMS = '-t %(encoding)s -c %(connection)s -l %(layer)s -%(type)ss %(width)s,%(height)s -%(type)sp %(x)s,%(y)s'
30 # Maximum vertical resolutions
34 # Profile header string, should be a single line only
35 # This does NOT conflict with profile names.
36 PROF_HEADER = 'Last written configuration:'
38 # Paths for reading different configuration options
40 size='/sys/devices/platform/omapdss/overlay2/output_size',
41 position='/sys/devices/platform/omapdss/overlay2/position',
42 enabled='/sys/devices/platform/omapdss/display1/enabled',
43 connection='/sys/devices/platform/omapdss/display1/venc_type', # if it exists
44 fb0='/sys/class/graphics/fb0/overlays',
45 fb1='/sys/class/graphics/fb1/overlays',
48 # format for saving/loading
50 "enabled", "encoding", "connection", "layer", "x", "y", "width", "height")
52 # Default configurations
53 DC_DISABLED = "Disabled"
54 DC_DISABLED_DICT = dict(
55 enabled="False", encoding="pal", connection="composite",
56 layer="0", width="658", height="520", x="35", y="35")
58 DC_DISABLED: DC_DISABLED_DICT,
62 W_RADIO_BUTTONS = ('pal', 'ntsc', 'composite', 'svideo', 'layer0', 'layer1')
63 W_ADJUSTMENTS = ('x', 'y', 'width', 'height')
64 W_WIDGETS = ('enabled', 'pal', 'ntsc', 'composite', 'svideo',
65 'layer0', 'layer1', 'width', 'height', 'x', 'y')
68 PP_WIDGET = 'previewAlignment'
69 PP_HANDLES = ('xPaned', 'yPaned', 'widthPaned', 'heightPaned')
70 PP_DX = 26.0 # Todo: compute these
71 PP_DY = 14.0 # (they might be theme dependent)
73 ENABLED_USERDATA = 'enabled'
74 RADIO_USERDATA = dict(
75 pal='encoding', ntsc='encoding',
76 composite='connection', svideo='connection',
77 layer0='layer', layer1='layer')
78 ADJUSTMENT_USERDATA = ('width', 'height', 'x', 'y')
80 # Documentation for commandline options
81 HELP_ENABLED = "Enable TV-out. Valid values: True, False"
82 HELP_ENCODING = "Set encoding type (pal or ntsc)."
83 HELP_CONNECTION = "Set connection type (composite or svideo)"
84 HELP_LAYER = "Sets video layer to either main (0) or HW scaler / overlay (1)"
85 HELP_WIDTH = "Screen width (max 720)"
86 HELP_HEIGHT = "Screen height (max %s for pal and %s for ntsc)" % (Y_RES_PAL, Y_RES_NTSC)
87 HELP_X = "X coordinate of top left corner of the display area. Max 720 - width."
88 HELP_Y = "Y coordinate of top left corner of the display area. Y + HEIGHT may not exceed the max values specified in the height option."
89 HELP_SAVE = "Store current configuration as specified profile."
90 HELP_APPLY = "Activate the currently specified configuration."
91 HELP_LOAD = "Load and apply the specified configuration profile."
92 HELP_DEL = "Delete specified configuration profile."
94 # Commandline input type checking
96 enabled='(True|False)', layer='(0|1)',
97 encoding='(pal|ntsc)', connection='(composite|svideo)',
98 width='(\d+)', height='(\d+)', x='(\d+)', y='(\d+)',
100 RE_FORMAT = ' '.join(TYPECHECK[k] for k in FILE_ORDER)
101 BOUNDS = dict(width=(0,720), height=(0, Y_RES_PAL),
102 x=(0,720), y=(0,Y_RES_PAL))
104 # ================================================================
105 # Misc. auxiliary functions
106 # ================================================================
109 """Verifies all values in a profile string (see TVoutModel).
111 This is used to validate inputs passed through the commandline interface.
113 @value: a profile string.
115 mo = re.match(RE_FORMAT, value)
118 else: # verify bounds
119 values = dict(zip(FILE_ORDER, mo.groups()))
123 w = int(values['width'])
124 h = int(values['height'])
125 Y = Y_RES_PAL if values['encoding'] == 'pal' else Y_RES_NTSC
128 print "Invalid position and/or size specified, x (%s) + width (%s) may not exceed 720." % (x, w)
131 print "Invalid position and/or size specified, y (%s) + height (%s) may not exceed %s." % (y, h, Y)
134 if not all(BOUNDS[k][0] <= int(values[k]) <= BOUNDS[k][1] for k in BOUNDS):
135 print "Invalid position and/or size bounds specified"
140 # ================================================================
141 # Main model & gui classes
142 # ================================================================
144 class TVoutModel(ConfigModel):
145 """Model for TV-out configuration.
147 Since not all TV-out settings can be easily read back from the system,
148 the last-saved profile is stored to fill in the gaps (mainly encoding).
150 This currently stores the following key/value pairs (all strings):
151 - "enabled": "True" | "False" # TV-out turned on/off
152 - "encoding": "pal" | "ntsc" # which colour encoding is utilized
153 - "connection": "composite" | "svideo"
154 - "layer": "0" | "1" # main video layer or hw scaler/overlay
155 - "x": "\d+" # left side of area displayed area
156 - "y": "\d+" # top side of the displayed area
157 - "width": "\d+" # right side of the displayed area,
159 - "height": "\d+" # bottom side of the displayed area,
162 def __init__(self, *views):
163 self.last_written = None
164 ConfigModel.__init__(self, PROFILES, *views, **DC_DEFAULTS)
166 def fetch_profiles(self):
167 """Loads the profiles.
169 The header line is ignored and is included for user documentation only.
170 The second line is the last written profile.
171 The remainder of the file consists of a series of lines which
172 alternating contain the profile name and a profile.
174 Profiles are stored as space separated values, in the following order:
175 <enabled> <encoding> <connection> <layer> <x> <y> <width> <height>
177 with open(self.profiles_file, 'r') as f:
178 f.readline() # header
179 self.last_written = self.string_to_profile(f.readline().strip()) # last_saved profile
186 self.profiles[name] = self.string_to_profile(line.rstrip())
189 def store_profiles(self):
190 """Write profiles to file.
192 Refer to the doc-string of fetch_profiles for the file format.
194 with open(self.profiles_file, 'w') as f:
195 f.write('%s\n%s\n' % (PROF_HEADER, self.profile_to_string(self.last_written)))
196 for name in sorted(self.profiles.keys()):
197 f.write('%s\n%s\n' % (name, self.profile_to_string(self.profiles[name])))
199 def read_settings(self):
200 """Reads current TV-out configuration from the system.
202 The way the encoding is stored is tricky to convert back to pal/ntsc
203 and might change in later hotfixes / firmware releases. Therefore this
204 value is read from the last written profile instead.
206 Layer is determined by interpreting the framebuffer values.
207 If an unknown configuration is encountered, which is quite possible,
208 then the last written configuration is read instead.
210 # Uncomment to test on desktop linux:
211 # self.load_profile(self.last_written)
215 for key, path in SETTINGS.iteritems():
217 with open(path, 'r') as f:
218 settings[key] = f.readline().strip()
220 settings[key] = self.last_written[key] if key in self.last_written else None
222 # This one is quite tricky to read directly from the system, yet unlikely to change often.
223 settings['encoding'] = self.last_written['encoding']
225 # These ones are stored as a single value
226 settings['width'], settings['height'] = settings.pop('size').split(',')
227 settings['x'], settings['y'] = settings.pop('position').split(',')
229 # The system may have 0,0 w,h; use last_written or defaults if it does
230 for key in 'width', 'height':
231 if settings[key] == '0':
232 settings[key] = (self.last_written[key] if self.last_written[key] != '0'
233 else DC_DISABLED_DICT[key])
235 # 0,0 pos is more likely to be intended, but still fallback to last_written
237 if settings[key] == '0':
238 settings[key] = self.last_written[key]
240 # To determine the layer we interpret the framebuffer values
241 fb0 = settings.pop('fb0')
242 fb1 = settings.pop('fb1')
243 settings['layer'] = ('1' if fb0 == '0' and fb1 == '1,2' else
244 ('0' if fb0 == '0,2' and fb1 == '1' else
245 self.last_written['layer']))
247 self.load_profile(settings)
249 def write_settings(self):
250 """Write configuration to the system.
252 This relies on op_tvout.sh to write the configuration, such that
253 the details can be easily changed in later firmware versions.
255 Documentation for op_tvout.sh:
256 op_tvout.sh [-d] [-t pal|ntsc] [-c composite|svideo] [-l 0|1]
257 [-{p|n}s w,h] [-{p|n}p x,y]
259 - op_tvout.sh -d # just disables tv-out
260 - t ntsc -c composite # enable NTSC/composite mode
262 layer, 0 is the main layer and 1 is hardware scaler/overlay
263 (video layer, used by some emus too), only one can be used at a time)
265 display position and size in 720xSomething space,
266 this has to be tuned for every TV by user for best results,
267 can't go out of range of 720xSomething (I think).
268 - np 0,0 -ns 640 480 # same for NTSC
270 The script expects you to supply everything at once.
272 self.last_written.update(self.settings)
273 if self.settings['enabled'] == 'False':
274 #print SET_CONFIG_CMD % '-d'
275 os.system(SET_CONFIG_CMD % '-d')
277 pp = self.settings.copy()
278 pp['type'] = self.settings['encoding'][0]
279 #print SET_CONFIG_CMD % (CONFIG_PARAMS % pp)
280 os.system(SET_CONFIG_CMD % (CONFIG_PARAMS % pp))
282 def profile_to_string(self, dct):
283 """Converts settings dictionary to a string.
285 Profiles are stored as space separated values, in the following order:
286 <enabled> <encoding> <connection> <layer> <x> <y> <width> <height>
288 return ' '.join(dct[k] for k in FILE_ORDER)
290 def string_to_profile(self, s):
291 """Converts a profile string back to a settings dictionary.
293 Profiles are stored as space separated values, in the following order:
294 <enabled> <encoding> <connection> <layer> <x> <y> <width> <height>
296 return dict(zip(FILE_ORDER, s.split(' ')))
298 class TVoutConfig(object):
299 """GUI application for modifying the pandora TV-out configuration
301 Design (tvout.glade):
302 +------------+---------------+--------------------+
303 | | Position----- | Size-------------- |
304 | Logo | X: <spin btn> | Width: <spin btn> |
305 | | Y: <spin btn> | Height: <spin btn> |
306 +------------+---------------+--------------------+
307 |Encoding | Overscan |
308 |. pal | +--------------------------------+ |
309 |. ntsc | | <undisplayed area> | |
310 +------------+ | +-----------------------+ | |
311 |Connection | | | | | |
312 |. composite | | | <displayed area> | | |
313 |. S-video | | | | | |
314 +------------+ | | | | |
315 |Layer | | +-----------------------+ | |
317 |. overlay | +--------------------------------+ |
318 +------------+------------------------------------+
319 |[v] enabled | [ Read settings ] [Write settings] |
320 |[delete p.] | {.............|v} [ Save profile ] |
321 +------------+------------------------------------+
323 Important widget names:
324 - X,Y,Width and Height spinbuttons: x,y,width and height
325 - Radio buttons (for Encoding, Connection, Layer):
326 pal, ntsc, composite, svideo, layer0, layer1
328 These names correspond to their values as they are written,
329 except for layer0 and layer1 which are stored as strings(!) 0 and 1.
330 - The checkbox is called: enabled
331 - The buttons are called:
332 readSettings, writeSettings, deleteProfile, saveProfile
333 - The combobox is called: ProfileComboEntry
334 - The displayed area is created by a set of GtkHPaned and GtkVPaned
335 widgets called: xPaned, yPaned, widthPaned, heightPaned
336 They are contained in a frame called previewAlignment which can be
337 used to determine the total allocated size.
341 """Initialize the GUI application.
343 Loads glade file, creates widget references, connects unbound handlers
344 (mainly those handlers which require userdata) and finally initializes
347 builder = gtk.Builder()
348 builder.add_from_file(
349 os.path.join(os.path.dirname(__file__), GUI_DESCRIPTION))
350 builder.connect_signals(self)
353 for widgetname in W_WIDGETS:
354 self.widgets[widgetname] = builder.get_object(widgetname)
356 # Bind radiobutton signals
357 for widget, setting in RADIO_USERDATA.iteritems():
358 self.widgets[widget].connect('clicked', self.on_clicked,
359 setting, widget.lstrip('layer'))
361 # Bind adjustment signals
362 for widget in ADJUSTMENT_USERDATA:
363 self.widgets[widget].get_adjustment().connect(
364 'value_changed', self.on_value_changed, widget)
367 # The allocated size of this widget is utilized to convert the
368 # slider positions to absolute x,y,width and height values.
369 self.previewPane = builder.get_object(PP_WIDGET)
371 self.panedwidgets = {}
372 # Bind previewpane handle signals
373 for key in PP_HANDLES:
374 w = builder.get_object(key)
375 self.panedwidgets[key] = w
376 w.connect("notify::position", self.on_paned_position_change, key)
378 self.model = TVoutModel(self)
380 # We have two sets of widgets manipulating position and size
381 # (spinbuttons and the handles in the previewpane).
382 # To prevent an update race between these sets, the following flags
383 # are set to block the handlers of the widgets which we are NOT
384 # manipulating. The spinbuttons are more precise and therefore leading.
385 self.suppress_handles = False
386 self.suppress_spinbuttons = False
388 self.statusbar = builder.get_object('statusbar')
389 self.contextid = self.statusbar.get_context_id('')
391 self.profiles = gtk.ListStore(str)
392 for profile in DC_DEFAULTS:
393 self.profiles.append([profile])
394 for name in self.model.profiles:
395 self.profiles.append([name])
397 self.comboentry = builder.get_object('ProfileComboEntry')
398 self.comboentry.set_model(self.profiles)
399 self.comboentry.set_text_column(0)
400 self.comboentry.connect('changed', self.on_combo_changed)
402 self.entry = self.comboentry.get_child()
403 self.entry.connect('activate', self.on_LoadProfile_clicked)
405 # we need a reference to change their sensitive
406 # setting according to the profile
407 self.delete = builder.get_object('deleteProfile')
408 self.save = builder.get_object('saveProfile')
410 # this also changes sensitive of self.save & self.load
411 self.comboentry.set_active(0)
413 self.window = builder.get_object("window")
414 self.window.show_all()
416 accelgrp = gtk.AccelGroup()
417 key, mod = gtk.accelerator_parse('<Control>Q')
418 accelgrp.connect_group(key, mod, 0, self.on_window_destroy)
419 self.window.add_accel_group(accelgrp)
421 # read current config
422 self.model.read_settings()
424 def Notify(self, message):
425 """Displays a message on the statusbar"""
426 self.statusbar.pop(self.contextid)
427 self.statusbar.push(self.contextid, message)
429 def get_y_resolution(self):
430 """Returns the (mode-dependent) maximum Y resolution"""
431 return Y_RES_PAL if self.widgets['pal'].get_active() else Y_RES_NTSC
433 # --------------------------------
434 # View updates when model changes
435 # --------------------------------
437 def _update_profile_list(self, new, profiles):
438 """Show all default profiles and then the custom profiles.
440 @new: name of newly selected profile
441 @profiles: dictionary of all profiles.
443 self.profiles.clear()
445 for i, key in enumerate(DC_DEFAULTS):
446 self.profiles.append([key])
449 offset = len(DC_DEFAULTS)
450 for i, key in enumerate(sorted(profiles.keys())):
451 self.profiles.append([key])
453 activate = i + offset
454 self.comboentry.set_active(activate)
456 def _update_widgets(self, settings):
457 """Update all widgets to reflect the configuration in settings"""
458 enabled = eval(settings['enabled'])
459 self.widgets['enabled'].set_active(enabled)
461 self.widgets[settings['encoding']].set_active(True)
462 self.widgets[settings['connection']].set_active(True)
463 self.widgets['layer'+settings['layer']].set_active(True)
465 for key in W_ADJUSTMENTS:
466 w = self.widgets[key]
467 w.get_adjustment().set_value(eval(settings[key]))
468 w.set_sensitive(enabled)
470 for key in W_RADIO_BUTTONS:
471 self.widgets[key].set_sensitive(enabled)
473 def update_view(self, reason, *data):
474 """Dispatcher for events generated by the model."""
475 if reason == MODEL_PROFILE_CHANGE:
476 self._update_profile_list(*data)
477 elif reason == MODEL_VALUE_CHANGE:
478 self._update_widgets(*data)
480 def update_adjustments(self):
481 """Update all Gtk adjustments to recompute their maximum.
483 The value of x+width should not exceed 720.
484 The value of y+height should not exceed the maximum Y resolution.
486 x = self.widgets['x'].get_adjustment()
487 y = self.widgets['y'].get_adjustment()
489 xval = int(self.model.settings['x'])
490 yval = int(self.model.settings['y'])
491 wval = int(self.model.settings['width'])
492 hval = int(self.model.settings['height'])
494 # update vertical resolution thresholds
495 y.set_upper(self.get_y_resolution())
496 y.set_value(min(yval, y.get_upper()))
498 # update width thresholds
499 width = self.widgets['width'].get_adjustment()
500 width.set_upper(x.get_upper()-xval)
501 width.set_value(min(wval, width.get_upper()))
503 # update height thresholds
504 height = self.widgets['height'].get_adjustment()
505 height.set_upper(y.get_upper()-yval)
506 height.set_value(min(hval, height.get_upper()))
508 # --------------------------------
509 # Handling of widget changes
510 # --------------------------------
512 def on_toggled(self, widget, *data):
513 """Handles changes of the enabled radio button."""
514 enabled = widget.get_active()
515 self.model.settings[ENABLED_USERDATA] = str(enabled)
516 for name, widget in self.widgets.iteritems():
517 if name != 'enabled':
518 widget.set_sensitive(enabled)
520 def on_clicked(self, widget, key=None, value=None):
521 """Handles changes of the radio buttons.
523 @key: the setting being changed, i.e.
524 - "encoding" for pal/ntsc,
525 - "connection" for composite/svideo
526 - "layer" for layer0/layer1.
527 These correspond to the keys in the profile settings dictionaries.
528 @value: the value to be written to the dictionary.
530 When the encoding changes, so does the maximum Y resolution.
531 Hence it requires the adjustments to be updated.
534 self.model.settings[key] = value
535 if key == 'encoding':
536 self.update_adjustments()
538 def on_value_changed(self, widget, data):
539 """Handles spinbutton events.
541 If the user manipulates the spinbutton, the model is updated.
542 Note that suppress_handles is set to prevent the GtkPaned widgets
543 from retriggering an update. The precision of the Paned widgets
544 depends on screen resolution and tends to be less precise than
545 that of the spinbuttons causing an update-race.
547 if self.suppress_spinbuttons:
550 self.model.settings[data] = str(int(widget.get_value()))
551 self.update_adjustments()
552 yresolution = self.get_y_resolution()
554 # update the handle displays, but don't generate a new signal.
555 self.suppress_handles = True
556 self.set_pane_position(data + 'Paned', yresolution)
557 self.suppress_handles = False
559 # --------------------------------
561 # --------------------------------
563 def on_readSettings_clicked(self, widget, *data):
564 """Handles a click on the readSettings button.
566 Since it doesn't require userdata, this is connected through glade.
568 self.model.read_settings()
569 self.Notify("Active TV-out configuration loaded.")
571 def on_writeSettings_clicked(self, widget, *data):
572 """Handles a click on the writeSettings button.
574 Since it doesn't require userdata, this is connected through glade.
576 self.model.write_settings()
577 self.Notify("TV-out configuration updated.")
579 def on_deleteProfile_clicked(self, widget, *data):
580 """Handles a click on the deleteProfile button.
582 Since it doesn't require userdata, this is connected through glade.
583 The touch-screen is not the highest precision input and has no
584 visual feedback before a click. Therefore we better prompt for
585 confirmation. Having an undo instead would probably be better.
587 name = self.entry.get_text()
588 if name not in self.model.profiles or name in DC_DEFAULTS:
589 self.Notify("Cannot remove profile, please select an existing non-default profile.")
591 dialog = gtk.MessageDialog(flags=gtk.DIALOG_MODAL, type=gtk.MESSAGE_WARNING,
592 buttons=gtk.BUTTONS_YES_NO,
593 message_format="Are you sure you want to delete profile %s?" % name)
594 if dialog.run() == gtk.RESPONSE_YES:
595 self.model.delete_profile(name)
598 def on_saveProfile_clicked(self, widget, *data):
599 """Handles a click on the saveProfile button.
601 Since it doesn't require userdata, this is connected through glade.
602 Default profiles cannot be overwritten.
604 name = self.entry.get_text()
605 if name == '' or name in DC_DEFAULTS:
606 self.Notify("Invalid profile name")
608 self.model.set_profile(name, self.model.settings)
609 self.Notify("Profile saved as: %s" % name)
611 # --------------------------------
613 # --------------------------------
615 def _lose_active(self):
616 """Forces the comboboxentry to lose its active selection.
618 This ensures that it also generates a changed signal when we
619 select the same value twice in a row such that a user can undo
620 any changes made to a loaded profile by loading the profile again.
622 temp = self.entry.get_text()
623 self.entry.set_text('')
624 self.entry.set_text(temp)
626 def on_combo_changed(self, widget, *data):
627 """Handles selection in the profile combo box."""
628 name = self.entry.get_text()
629 self.delete.set_sensitive(name in self.model.profiles)
630 self.save.set_sensitive(name != '' and name not in DC_DEFAULTS)
631 if self.comboentry.get_active() != -1:
632 self.on_LoadProfile_clicked(None)
634 def on_LoadProfile_clicked(self, widget, *data):
635 """Loads profile in the entry widget (by name).
637 It is directly activated by the activation event of the combobox entry
638 (i.e. when the user presses enter) and indirectly called by
639 on_combo_changed (when the user performs a mouse selection).
641 self._lose_active() # force widget to always emit changed signals,
642 name = self.entry.get_text() # ok since we always lookup by text value anyway
645 elif name not in self.model.profiles and name not in DC_DEFAULTS:
646 self.Notify("Cannot load profile, please select an existing profile.")
648 self.model.load_named_profile(name)
649 self.Notify("Profile loaded, hit 'Write settings' to make it active")
651 # --------------------------------
653 def set_pane_position(self, key, yresolution):
654 """Sync the GtkPaned widgets with the spinbuttons.
656 The Paned handles need their position specified in pixels.
657 This converts an absolute value to a relative position as the pane is
658 not actually 720 pixels wide (nor high).
660 PP_DX and PP_DY are corrections for the border around the client area.
661 They are measured for a specific theme, I'm not sure if changing theme
662 can invalidate this value but I guess it can.
664 value = int(self.model.settings[key.rstrip('Paned')])
665 widget = self.panedwidgets[key]
666 if key == 'widthPaned' or key == 'xPaned':
667 maxval = self.previewPane.allocation.width - PP_DX
668 newpos = (value / 720.) * maxval
670 maxval = self.previewPane.allocation.height - PP_DY
671 newpos = (value / float(yresolution)) * maxval
672 widget.set_position(int(newpos))
674 def on_paned_position_change(self, widget, param, key):
675 """Handle dragging of the GtkPaned widgets.
677 This turns of handling of the spinbuttons as otherwise the spinbutton
678 update would re-trigger this event handler causing an update race due
679 to differences in precision.
681 if self.suppress_handles:
684 yresolution = self.get_y_resolution()
685 self.suppress_spinbuttons = True
687 maxval = self.previewPane.allocation.width - PP_DX
688 for key in ('xPaned', 'widthPaned'):
689 pos = int((self.panedwidgets[key].get_position()/maxval)*720)
690 adj = self.widgets[key.rstrip('Paned')].get_adjustment()
692 self.model.settings[key.rstrip('Paned')] = str(int(pos))
694 maxval = self.previewPane.allocation.height - PP_DY
695 for key in ('yPaned', 'heightPaned'):
696 pos = (self.panedwidgets[key].get_position()/maxval) * yresolution
697 adj = self.widgets[key.rstrip('Paned')].get_adjustment()
698 adj.set_value(int(pos))
699 self.model.settings[key.rstrip('Paned')] = str(int(pos))
701 self.update_adjustments()
702 self.suppress_spinbuttons = False
704 def on_window_destroy(self, widget, *data):
705 """Handle application shutdown."""
706 self.Notify("Storing profiles...")
707 self.model.store_profiles()
710 # ================================================================
713 """Runs the application.
715 @args: command-line arguments (typically sys.argv).
717 if len(args) == 1: # no params: run gui app
720 else: # run command line app
721 parser = optparse.OptionParser()
723 parser.add_option('-e', '--enabled', default='', help=HELP_ENABLED)
724 parser.add_option('-t', '--encoding_type', default='', help=HELP_ENCODING)
725 parser.add_option('-c', '--connection_type', default='', help=HELP_CONNECTION)
726 parser.add_option('-l', '--layer', default='', help=HELP_LAYER)
727 parser.add_option('-w', '--width', default='', help=HELP_WIDTH)
728 parser.add_option('-g', '--height', default='', help=HELP_HEIGHT)
729 parser.add_option('-x', '--x_position', default='', help=HELP_X)
730 parser.add_option('-y', '--y_position', default='', help=HELP_Y)
732 parser.add_option('-s', '--save_profile', default='', help=HELP_SAVE)
733 parser.add_option('-a', '--apply', default=False, action='store_true', help=HELP_APPLY)
734 parser.add_option('-p', '--load_profile', default='', help=HELP_LOAD)
735 parser.add_option('-d', '--remove_profile', default='', help=HELP_DEL)
737 options, args = parser.parse_args()
740 model.read_settings()
742 model.settings['enabled'] = options.enabled
743 if options.encoding_type:
744 model.settings['encoding'] = options.encoding_type
745 if options.connection_type:
746 model.settings['connection'] = options.connection_type
748 model.settings['layer'] = options.layer
750 model.settings['width'] = options.width
752 model.settings['height'] = options.height
753 if options.x_position:
754 model.settings['x'] = options.x_position
755 if options.y_position:
756 model.settings['y'] = options.y_position
758 profile_string = model.profile_to_string(model.settings)
759 config = Validate(profile_string)
762 print "Invalid values encountered in profile (%s), terminating." % profile_string
765 if options.save_profile and options.save_profile not in DC_DEFAULTS:
766 model.set_profile(options.save_profile, self.model.settings)
768 model.write_settings()
769 if options.load_profile:
770 model.load_named_profile(options.load_profile)
771 model.write_settings()
772 if options.remove_profile:
773 model.delete_profile(options.remove_profile)
775 if options.save_profile or options.remove_profile:
776 model.store_profiles()
778 if __name__ == '__main__':