[gnome-documents] selections: New collections dialog
- From: Debarshi Ray <debarshir src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gnome-documents] selections: New collections dialog
- Date: Thu, 20 Aug 2015 05:32:07 +0000 (UTC)
commit b4088a5ba09351e329c5956d163f713c08030f3e
Author: Alessandro Bono <shadow openaliasbox org>
Date: Wed Aug 19 17:32:33 2015 +0200
selections: New collections dialog
https://bugzilla.gnome.org/show_bug.cgi?id=726450
data/application.css | 13 +
data/media/collections-placeholder.png | Bin 686 -> 0 bytes
data/org.gnome.Documents.data.gresource.xml | 2 +-
data/ui/organize-collection-dialog.ui | 186 ++++++++
po/POTFILES.in | 1 +
src/selections.js | 674 ++++++++++++++++-----------
src/view.js | 4 +
7 files changed, 595 insertions(+), 285 deletions(-)
---
diff --git a/data/application.css b/data/application.css
index edd68fe..b2a4725 100644
--- a/data/application.css
+++ b/data/application.css
@@ -100,3 +100,16 @@ GdMainIconView.content-view.cell:active {
/* when there's no pixbuf yet */
background-color: @osd_bg;
}
+
+.collection-dialog .frame { border-style: solid none none none; }
+
+.collection-dialog .delete-row { background-color: @theme_bg_color }
+
+.collection-dialog .frame GtkEntry {
+ padding: 12px 8px 13px;
+ border-left: none;
+ border-right: none;
+ border-top: none;
+ border-bottom: none;
+ border-radius: 0px;
+}
diff --git a/data/org.gnome.Documents.data.gresource.xml b/data/org.gnome.Documents.data.gresource.xml
index cd0d41e..506a0f0 100644
--- a/data/org.gnome.Documents.data.gresource.xml
+++ b/data/org.gnome.Documents.data.gresource.xml
@@ -3,10 +3,10 @@
<gresource prefix="/org/gnome/Documents">
<file>application.css</file>
<file preprocess="xml-stripblanks">ui/app-menu.ui</file>
+ <file preprocess="xml-stripblanks">ui/organize-collection-dialog.ui</file>
<file preprocess="xml-stripblanks">ui/preview-context-menu.ui</file>
<file preprocess="xml-stripblanks">ui/preview-menu.ui</file>
<file preprocess="xml-stripblanks">ui/selection-menu.ui</file>
- <file alias="ui/collections-placeholder.png"
preprocess="to-pixdata">media/collections-placeholder.png</file>
<file alias="ui/dnd-counter.svg" preprocess="to-pixdata">media/dnd-counter.svg</file>
<file alias="ui/thumbnail-frame.png" preprocess="to-pixdata">media/thumbnail-frame.png</file>
</gresource>
diff --git a/data/ui/organize-collection-dialog.ui b/data/ui/organize-collection-dialog.ui
new file mode 100644
index 0000000..3999e35
--- /dev/null
+++ b/data/ui/organize-collection-dialog.ui
@@ -0,0 +1,186 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<interface>
+ <requires lib="gtk+" version="3.12"/>
+ <template class="Gjs_OrganizeCollectionDialog" parent="GtkWindow">
+ <property name="visible">True</property>
+ <property name="modal">True</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="default_width">420</property>
+ <property name="default_height">500</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <child>
+ <object class="GtkStack" id="content">
+ <property name="visible">True</property>
+ <child>
+ <object class="GtkGrid" id="viewEmpty">
+ <property name="visible">True</property>
+ <property name="halign">center</property>
+ <property name="valign">center</property>
+ <property name="orientation">vertical</property>
+ <property name="row_spacing">6</property>
+ <child>
+ <object class="GtkImage" id="imageEmpty">
+ <property name="visible">True</property>
+ <property name="pixel_size">96</property>
+ <property name="icon_name">inode-directory-symbolic</property>
+ <property name="margin_bottom">18</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="labelEmpty">
+ <property name="visible">True</property>
+ <property name="label" translatable="yes">Enter a name for your first collection</property>
+ <property name="margin_bottom">6</property>
+ <style>
+ <class name="dim-label"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="addEntryEmpty">
+ <property name="visible">True</property>
+ <property name="has_focus">True</property>
+ <property name="activates_default">True</property>
+ <property name="placeholder_text" translatable="yes">New Collection…</property>
+ <property name="margin_bottom">6</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="addButtonEmpty">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="halign">center</property>
+ <child>
+ <object class="GtkLabel">
+ <property name="label" translatable="yes">Add</property>
+ <property name="visible">True</property>
+ <property name="margin_start">36</property>
+ <property name="margin_end">36</property>
+ </object>
+ </child>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkGrid" id="viewCollections">
+ <property name="visible">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkGrid" id="addGridCollections">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="margin">5</property>
+ <child>
+ <object class="GtkEntry" id="addEntryCollections">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="activates_default">True</property>
+ <property name="placeholder_text" translatable="yes">New Collection…</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="addButtonCollections">
+ <property name="label" translatable="yes">Add</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_default">True</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <style>
+ <class name="linked"/>
+ </style>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledWindowCollections">
+ <property name="visible">True</property>
+ <property name="hexpand">True</property>
+ <property name="vexpand">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="titlebar">
+ <object class="GtkHeaderBar" id="headerBar">
+ <property name="visible">True</property>
+ <property name="title">Collections</property>
+ <property name="show_close_button">True</property>
+ <child>
+ <object class="GtkButton" id="cancelButton">
+ <property name="label" translatable="yes">Cancel</property>
+ <property name="no_show_all">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton" id="doneButton">
+ <property name="label" translatable="yes">Done</property>
+ <property name="can_default">True</property>
+ <property name="no_show_all">True</property>
+ <property name="sensitive">False</property>
+ <style>
+ <class name="suggested-action"/>
+ </style>
+ </object>
+ <packing>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <style>
+ <class name="collection-dialog"/>
+ </style>
+ </template>
+</interface>
diff --git a/po/POTFILES.in b/po/POTFILES.in
index e4e3a53..5d673b0 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -6,6 +6,7 @@ data/org.gnome.Documents.appdata.xml.in
data/org.gnome.Documents.desktop.in
data/org.gnome.documents.gschema.xml
[type: gettext/glade]data/ui/app-menu.ui
+[type: gettext/glade]data/ui/organize-collection-dialog.ui
[type: gettext/glade]data/ui/preview-context-menu.ui
[type: gettext/glade]data/ui/preview-menu.ui
[type: gettext/glade]data/ui/selection-menu.ui
diff --git a/src/selections.js b/src/selections.js
index 004cee2..98821aa 100644
--- a/src/selections.js
+++ b/src/selections.js
@@ -1,4 +1,5 @@
/*
+ * Copyright © 2015 Alessandro Bono
* Copyright (c) 2011 Red Hat, Inc.
*
* Gnome Documents is free software; you can redistribute it and/or modify
@@ -22,6 +23,7 @@
const EvView = imports.gi.EvinceView;
const Gd = imports.gi.Gd;
const Gdk = imports.gi.Gdk;
+const Gio = imports.gi.Gio;
const GLib = imports.gi.GLib;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
@@ -31,19 +33,19 @@ const C_ = imports.gettext.pgettext;
const Application = imports.application;
const Documents = imports.documents;
+const Mainloop = imports.mainloop;
const Manager = imports.manager;
const Notifications = imports.notifications;
const Properties = imports.properties;
const Query = imports.query;
const Sharing = imports.sharing;
+const TrackerUtils = imports.trackerUtils;
const Utils = imports.utils;
+const WindowMode = imports.windowMode;
const Lang = imports.lang;
const Signals = imports.signals;
-const _COLLECTION_PLACEHOLDER_ID = 'collection-placeholder';
-const _SEPARATOR_PLACEHOLDER_ID = 'separator-placeholder';
-
// fetch all the collections a given item is part of
const FetchCollectionsJob = new Lang.Class({
Name: 'FetchCollectionsJob',
@@ -310,362 +312,469 @@ const CreateCollectionJob = new Lang.Class({
}
});
-const OrganizeModelColumns = {
- ID: 0,
- NAME: 1,
- STATE: 2
+const CollectionRowViews = {
+ DEFAULT: 'default-view',
+ DELETE: 'delete-view',
+ RENAME: 'rename-view'
};
-const OrganizeCollectionModel = new Lang.Class({
- Name: 'OrganizeCollectionModel',
- Extends: Gtk.ListStore,
+const CollectionRow = new Lang.Class({
+ Name: "CollectionRow",
+ Extends: Gtk.ListBoxRow,
- _init: function() {
+ _init: function(collection, collectionState) {
+ this.collection = collection;
+ this._collectionState = collectionState;
+ this._timeoutId = 0;
+ this.views = new Gtk.Stack();
this.parent();
- this.set_column_types(
- [ GObject.TYPE_STRING,
- GObject.TYPE_STRING,
- GObject.TYPE_INT ]);
-
- this._collAddedId = Application.documentManager.connect('item-added',
- Lang.bind(this, this._onCollectionAdded));
- this._collRemovedId = Application.documentManager.connect('item-removed',
- Lang.bind(this, this._onCollectionRemoved));
+ this.add(this.views);
+ this.setDefaultView();
+ },
- let iter;
+ _initDefaultView: function() {
+ let isActive = (this._collectionState & OrganizeCollectionState.ACTIVE);
+ let isInconsistent = (this._collectionState & OrganizeCollectionState.INCONSISTENT);
+
+ let grid = new Gtk.Grid({ margin_top: 6,
+ margin_bottom: 6,
+ margin_start: 12,
+ margin_end: 12,
+ orientation: Gtk.Orientation.HORIZONTAL });
+ this.checkButton = new Gtk.CheckButton({ label: this.collection.name,
+ expand: true,
+ active: isActive,
+ inconsistent: isInconsistent });
+ this.checkButton.get_child().set_ellipsize(Pango.EllipsizeMode.END);
+ this.checkButton.connect('toggled', Lang.bind(this,
+ function(checkButton) {
+ let collId = this.collection.id;
+ let state = checkButton.get_active();
+
+ let job = new SetCollectionForSelectionJob(collId, state);
+ job.run();
+ }));
+ let menu = new Gio.Menu();
+ if (this.collection.canEdit())
+ menu.append(_("Rename…"), 'dialog.rename-collection(\'' + this.collection.id + '\')');
+ else
+ menu.append(_("Rename…"), 'dialog.action-disabled');
- // add the placeholder
- iter = this.append();
- this.set(iter,
- [ 0, 1, 2 ],
- [ _COLLECTION_PLACEHOLDER_ID, '', OrganizeCollectionState.ACTIVE ]);
+ if (this.collection.canTrash())
+ menu.append(_("Delete"), 'dialog.delete-collection(\'' + this.collection.id + '\')');
+ else
+ menu.append(_("Delete"), 'dialog.action-disabled');
- // add the separator
- iter = this.append();
- this.set(iter,
- [ 0, 1, 2 ],
- [ _SEPARATOR_PLACEHOLDER_ID, '', OrganizeCollectionState.ACTIVE ]);
+ let menuButton = new Gtk.MenuButton({ image: new Gtk.Image({ icon_name: 'open-menu-symbolic' }),
+ menu_model: menu,
+ relief: Gtk.ReliefStyle.NONE });
- // populate the model
- let job = new FetchCollectionStateForSelectionJob();
- job.run(Lang.bind(this, this._onFetchCollectionStateForSelection));
+ grid.add(this.checkButton);
+ grid.add(menuButton);
+ grid.show_all();
+ this.views.add_named(grid, CollectionRowViews.DEFAULT);
},
- _findCollectionIter: function(item) {
- let collPath = null;
+ _initDeleteView: function() {
+ let grid = new Gtk.Grid({ margin: 6, orientation: Gtk.Orientation.HORIZONTAL });
+ let message = _("“%s” removed").format(this.collection.name);
+ let deleteLabel = new Gtk.Label({ label: message,
+ ellipsize: Pango.EllipsizeMode.MIDDLE,
+ expand: true,
+ halign: Gtk.Align.START });
+ let undoButton = new Gtk.Button({ label: _("Undo") });
+ undoButton.connect('clicked', Lang.bind(this,
+ function() {
+ this._resetTimeout()
+ this.views.set_transition_type(Gtk.StackTransitionType.SLIDE_RIGHT);
+ this.views.set_transition_duration(200);
+ this.setDefaultView();
+ }));
+ grid.add(deleteLabel);
+ grid.add(undoButton);
- this.foreach(Lang.bind(this,
- function(model, path, iter) {
- let id = model.get_value(iter, OrganizeModelColumns.ID);
+ grid.show_all();
+ this.views.add_named(grid, CollectionRowViews.DELETE);
+ },
- if (item.id == id) {
- collPath = path.copy();
- return true;
+ _initRenameView: function() {
+ this.renameEntry = new Gtk.Entry({ activates_default: true,
+ expand: true,
+ text: this.collection.name,
+ secondary_icon_name: 'edit-clear-symbolic'});
+ this.renameEntry.connect('icon-press', Lang.bind(this,
+ function(renameEntry, iconPos) {
+ if (iconPos == Gtk.EntryIconPosition.SECONDARY) {
+ renameEntry.set_text("");
}
-
- return false;
}));
-
- if (collPath)
- return this.get_iter(collPath)[1];
-
- return null;
+ this.renameEntry.connect('changed', Lang.bind(this,
+ function(renameEntry) {
+ if (renameEntry.get_text() != "")
+ renameEntry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY,
'edit-clear-symbolic');
+ else
+ renameEntry.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, null);
+ }));
+ this.renameEntry.show();
+ this.views.add_named(this.renameEntry, CollectionRowViews.RENAME);
},
- _onFetchCollectionStateForSelection: function(collectionState) {
- for (let idx in collectionState) {
- let item = Application.documentManager.getItemById(idx);
-
- if ((collectionState[item.id] & OrganizeCollectionState.HIDDEN) != 0)
- continue;
+ _resetTimeout: function() {
+ if (this._timeoutId != 0) {
+ Mainloop.source_remove(this._timeoutId);
+ this._timeoutId = 0;
+ }
+ },
- let iter = this._findCollectionIter(item);
+ applyRename: function() {
+ let newName = this.renameEntry.get_text();
+ this.collection.name = newName;
+ TrackerUtils.setEditedName(newName, this.collection.id, null);
+ this.checkButton.set_label(newName);
+ },
- if (!iter)
- iter = this.append();
+ conceal: function() {
+ let revealer = new Gtk.Revealer({ reveal_child: true, transition_duration: 500 });
+ revealer.show();
+ // inserting revealer between (this) and (this.views)
+ this.remove(this.views);
+ revealer.add(this.views);
+ this.add(revealer);
- this.set(iter,
- [ 0, 1, 2 ],
- [ item.id, item.name, collectionState[item.id] ]);
- }
+ revealer.connect("notify::child-revealed", Lang.bind(this, this.deleteCollection));
+ revealer.reveal_child = false;
},
- _refreshState: function() {
- let job = new FetchCollectionStateForSelectionJob();
- job.run(Lang.bind(this, this._onFetchCollectionStateForSelection));
+ deleteCollection: function() {
+ this._resetTimeout();
+ Application.documentManager.removeItem(this.collection);
+ this.collection.trash();
},
- _onCollectionAdded: function(manager, itemAdded) {
- this._refreshState();
+ setDefaultView: function() {
+ if (!this.views.get_child_by_name(CollectionRowViews.DEFAULT))
+ this._initDefaultView();
+
+ this.get_style_context().remove_class('delete-row');
+ this.views.set_visible_child_name(CollectionRowViews.DEFAULT);
},
- _onCollectionRemoved: function(manager, itemRemoved) {
- let iter = this._findCollectionIter(itemRemoved);
+ setDeleteView: function() {
+ if (!this.views.get_child_by_name(CollectionRowViews.DELETE))
+ this._initDeleteView();
- if (iter)
- this.remove(iter);
- },
+ this._timeoutId = Mainloop.timeout_add_seconds(Notifications.DELETE_TIMEOUT, Lang.bind(this,
+ function() {
+ this._timeoutId = 0;
+ this.conceal();
+ return false;
+ }));
+ this.views.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT);
+ this.views.set_transition_duration(500);
+ this.get_style_context().add_class('delete-row');
+ this.views.set_visible_child_name(CollectionRowViews.DELETE);
- refreshCollectionState: function() {
- this._refreshState();
},
- destroy: function() {
- if (this._collAddedId != 0) {
- Application.documentManager.disconnect(this._collAddedId);
- this._collAddedId = 0;
+ setRenameView: function(onTextChanged) {
+ if (!this.views.get_child_by_name(CollectionRowViews.RENAME)) {
+ this._initRenameView();
+ this.renameEntry.connect('changed', onTextChanged);
}
- if (this._collRemovedId != 0) {
- Application.documentManager.disconnect(this._collRemovedId);
- this._collRemovedId = 0;
- }
- }
+ this.views.set_transition_type(Gtk.StackTransitionType.CROSSFADE);
+ this.views.set_transition_duration(200);
+ this.renameEntry.set_text(this.collection.name);
+ this.views.set_visible_child_name(CollectionRowViews.RENAME);
+ },
+
});
-const OrganizeCollectionView = new Lang.Class({
- Name: 'OrganizeCollectionView',
- Extends: Gtk.Overlay,
+const CollectionList = new Lang.Class({
+ Name: 'CollectionList',
+ Extends: Gtk.ListBox,
_init: function() {
- this._choiceConfirmed = false;
+ this.parent({ vexpand: false,
+ margin: 0,
+ selection_mode: Gtk.SelectionMode.NONE });
- this.parent();
+ let collAddedId = Application.documentManager.connect('item-added',
+ Lang.bind(this, this._onCollectionAdded));
+ let collRemovedId = Application.documentManager.connect('item-removed',
+ Lang.bind(this, this._onCollectionRemoved));
- this._sw = new Gtk.ScrolledWindow({ shadow_type: Gtk.ShadowType.IN,
- margin_start: 5,
- margin_end: 5,
- margin_bottom: 3 });
- this.add(this._sw);
-
- this._model = new OrganizeCollectionModel();
- this._view = new Gtk.TreeView({ headers_visible: false,
- vexpand: true,
- hexpand: true });
- this._view.set_model(this._model);
- this._view.set_row_separator_func(Lang.bind(this,
- function(model, iter) {
- let id = model.get_value(iter, OrganizeModelColumns.ID);
- return (id == _SEPARATOR_PLACEHOLDER_ID);
- }));
- this._sw.add(this._view);
-
- this._msgGrid = new Gtk.Grid({ orientation: Gtk.Orientation.VERTICAL,
- row_spacing: 12,
- halign: Gtk.Align.CENTER,
- margin_top: 64 });
- this.add_overlay(this._msgGrid);
-
- this._icon = new Gtk.Image({ resource: '/org/gnome/Documents/ui/collections-placeholder.png' });
- this._msgGrid.add(this._icon);
-
- this._label = new Gtk.Label({
- justify: Gtk.Justification.CENTER,
- label: _("You don't have any collections yet. Enter a new collection name above."),
- max_width_chars: 32,
- wrap: true });
- this._label.get_style_context().add_class('dim-label');
- this._msgGrid.add(this._label);
-
- // show the overlay only if there aren't any collections in the model
- this._msgGrid.visible = (this._model.iter_n_children(null) < 2);
- this._model.connect('row-inserted', Lang.bind(this,
- function() {
- this._msgGrid.hide();
+ this.set_header_func(Lang.bind(this,
+ function(row, before) {
+ if (!before) {
+ row.set_header(null);
+ return;
+ }
+ let current = row.get_header();
+ if (!current) {
+ current = new Gtk.Separator({ orientation: Gtk.Orientation.HORIZONTAL });
+ row.set_header(current);
+ }
}));
- // force the editable row to be unselected
- this.selection = this._view.get_selection();
- let selectionChangedId = this.selection.connect('changed', Lang.bind(this,
- function() {
- this.selection.unselect_all();
- if (selectionChangedId != 0) {
- this.selection.disconnect(selectionChangedId);
- selectionChangedId = 0;
- }
+ this.set_sort_func(Lang.bind(this,
+ function(row1, row2) {
+ return row2.collection.mtime - row1.collection.mtime;
}));
- this._view.connect('destroy', Lang.bind(this,
+ this.connect('destroy', Lang.bind(this,
function() {
- this._model.destroy();
+ let rows = this.get_children();
+ rows.forEach(function(row) {
+ let currentView = row.views.get_visible_child_name();
+ if (currentView == CollectionRowViews.DELETE) {
+ row.deleteCollection();
+ }
+ });
+ Application.documentManager.disconnect(collAddedId);
+ Application.documentManager.disconnect(collRemovedId);
}));
- this._viewCol = new Gtk.TreeViewColumn();
- this._view.append_column(this._viewCol);
+ // populate the list
+ let job = new FetchCollectionStateForSelectionJob();
+ job.run(Lang.bind(this, this._onFetchCollectionStateForSelection));
+ },
- // checkbox
- this._rendererCheck = new Gtk.CellRendererToggle();
- this._viewCol.pack_start(this._rendererCheck, false);
- this._viewCol.set_cell_data_func(this._rendererCheck,
- Lang.bind(this, this._checkCellFunc));
- this._rendererCheck.connect('toggled', Lang.bind(this, this._onCheckToggled));
+ _onCollectionAdded: function(manager, itemAdded) {
+ let collection = new CollectionRow(itemAdded, OrganizeCollectionState.ACTIVE);
+ collection.show_all();
+ this.add(collection);
+ },
- // icon
- this._rendererIcon = new Gtk.CellRendererPixbuf();
- this._viewCol.pack_start(this._rendererIcon, false);
- this._viewCol.set_cell_data_func(this._rendererIcon,
- Lang.bind(this, this._iconCellFunc));
+ _onCollectionRemoved: function(manager, itemRemoved) {
+ let rows = this.get_children();
+ for (let i = 0; i < rows.length; i++) {
+ if (rows[i].collection.id == itemRemoved.id) {
+ this.remove(rows[i]);
+ return;
+ }
+ }
+ },
- // item name
- this._rendererText = new Gtk.CellRendererText();
- this._viewCol.pack_start(this._rendererText, true);
- this._viewCol.set_cell_data_func(this._rendererText,
- Lang.bind(this, this._textCellFunc));
+ _onFetchCollectionStateForSelection: function(collectionState) {
+ for (let idx in collectionState) {
+ let item = Application.documentManager.getItemById(idx);
- this._rendererDetail = new Gd.StyledTextRenderer({ xpad: 16 });
- this._rendererDetail.add_class('dim-label');
- this._viewCol.pack_start(this._rendererDetail, false);
- this._viewCol.set_cell_data_func(this._rendererDetail,
- Lang.bind(this, this._detailCellFunc));
+ if ((collectionState[item.id] & OrganizeCollectionState.HIDDEN) != 0)
+ continue;
- this._rendererText.connect('edited', Lang.bind(this, this._onTextEdited));
- this._rendererText.connect('editing-canceled', Lang.bind(this, this._onTextEditCanceled));
+ let collection = new CollectionRow(item, collectionState[item.id]);
+ collection.show_all();
- this._view.show();
+ this.add(collection);
+ }
},
- _onCheckToggled: function(renderer, pathStr) {
- let path = Gtk.TreePath.new_from_string(pathStr);
- let iter = this._model.get_iter(path)[1];
+ isEmpty: function() {
+ let rows = this.get_children();
+ return (rows.length == 0);
+ },
- let collUrn = this._model.get_value(iter, OrganizeModelColumns.ID);
- let state = this._rendererCheck.get_active();
+ isValidName: function(name) {
+ if (!name || name == '')
+ return false;
- let job = new SetCollectionForSelectionJob(collUrn, !state);
- job.run(Lang.bind(this,
- function() {
- this._model.refreshCollectionState();
+ let rows = this.get_children();
+ for (let i = 0; i < rows.length; i++) {
+ if (rows[i].collection.name == name)
+ return false;
+ }
+
+ return true;
+ }
+});
+
+const OrganizeCollectionDialog = new Lang.Class({
+ Name: 'OrganizeCollectionDialog',
+ Extends: Gtk.Window,
+ Template: 'resource:///org/gnome/Documents/ui/organize-collection-dialog.ui',
+ InternalChildren: [ 'content',
+ 'viewEmpty',
+ 'addEntryEmpty',
+ 'addButtonEmpty',
+ 'viewCollections',
+ 'addGridCollections',
+ 'addEntryCollections',
+ 'addButtonCollections',
+ 'scrolledWindowCollections',
+ 'headerBar',
+ 'cancelButton',
+ 'doneButton' ],
+
+ _init: function(toplevel) {
+ this.parent({ transient_for: toplevel });
+
+ this._renameMode = false;
+
+ this._keyPressEventId = this.connect('key-press-event', Lang.bind(this, this._onKeyPressed));
+ this._addButtonEmpty.connect('clicked', Lang.bind(this, this._onAddClicked));
+ this._addButtonCollections.connect('clicked', Lang.bind(this, this._onAddClicked));
+ this._addEntryEmpty.connect('changed', Lang.bind(this, this._onTextChanged));
+ this._addEntryCollections.connect('changed', Lang.bind(this, this._onTextChanged));
+
+ let actionGroup = new Gio.SimpleActionGroup();
+ let deleteAction = new Gio.SimpleAction({ name: 'delete-collection',
+ parameter_type: GLib.VariantType.new('s') });
+ let renameAction = new Gio.SimpleAction({ name: 'rename-collection',
+ parameter_type: GLib.VariantType.new('s') });
+ actionGroup.add_action(deleteAction);
+ actionGroup.add_action(renameAction);
+ this.insert_action_group('dialog', actionGroup);
+
+ renameAction.connect('activate', Lang.bind(this, this._renameModeStart));
+ deleteAction.connect('activate', Lang.bind(this,
+ function(action, parameter) {
+ let collId = parameter.get_string()[0];
+ let rows = this._collectionList.get_children();
+ rows.forEach(function(row) {
+ if (row.collection.id != collId)
+ return;
+
+ row.setDeleteView();
+ });
}));
- },
- _onTextEditedReal: function(cell, newText) {
- //cell.editable = false;
+ this._cancelButton.connect('clicked', Lang.bind(this, function() { this._renameModeStop(false); }));
+ this._doneButton.connect('clicked', Lang.bind(this, function() { this._renameModeStop(true); }));
- if (!newText || newText == '') {
- // don't insert collections with empty names
- return;
- }
+ this._collectionList = new CollectionList();
+ let addId = this._collectionList.connect('add', Lang.bind(this, this._onCollectionListChanged));
+ let removeId = this._collectionList.connect('remove', Lang.bind(this,
this._onCollectionListChanged));
+ this._scrolledWindowCollections.add(this._collectionList);
- // update the new name immediately
- let iter = this._model.append();
- this._model.set_value(iter, OrganizeModelColumns.NAME, newText);
+ this.show_all();
+
+ this.connect('destroy', Lang.bind(this,
+ function() {
+ this._collectionList.disconnect(addId);
+ this._collectionList.disconnect(removeId);
+ }));
- // force the editable row to be unselected
- this.selection.unselect_all();
+ /* We want a CROSSFADE effect when switching from ViewEmpty to ViewCollections (and the other way
around)
+ * but when we create the dialog we don't want to see the effect, so for the first second we don't
use
+ * any effect and after that we use the CROSSFADE effect.
+ */
+ Mainloop.timeout_add_seconds(1, Lang.bind(this,
+ function() {
+ this._content.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
+ return false;
+ }));
+ },
- // actually create the new collection
+ _onAddClicked: function() {
+ let addEntry = this._collectionList.isEmpty() ? this._addEntryEmpty : this._addEntryCollections;
+ let newText = addEntry.get_text();
let job = new CreateCollectionJob(newText);
job.run(Lang.bind(this,
function(createdUrn) {
if (!createdUrn)
return;
- this._model.set_value(iter, OrganizeModelColumns.ID, createdUrn);
-
+ addEntry.set_text('');
let job = new SetCollectionForSelectionJob(createdUrn, true);
job.run(null);
}));
+ if (!this._collectionList.isEmpty())
+ this._scrolledWindowCollections.get_vadjustment().set_value(0);
},
- _onTextEdited: function(cell, pathStr, newText) {
- this._onTextEditedReal(cell, newText);
+ _onTextChanged: function(entry) {
+ let sensitive = this._collectionList.isValidName(entry.get_text());
+ if (this._renameMode)
+ this._doneButton.set_sensitive(sensitive);
+ else {
+ let addButton = this._collectionList.isEmpty() ? this._addButtonEmpty :
this._addButtonCollections;
+ addButton.set_sensitive(sensitive);
+ }
},
- _onTextEditCanceled: function(cell) {
- if (this._choiceConfirmed) {
- this._choiceConfirmed = false;
+ _onKeyPressed: function (window, event) {
+ let keyval = event.get_keyval()[1];
+ if (keyval == Gdk.KEY_Escape) {
+ if (this._renameMode)
+ this._renameModeStop(false);
+ else
+ this.destroy();
- let entry = this._viewCol.cell_area.get_edit_widget();
- if (entry)
- this._onTextEditedReal(cell, entry.get_text());
+ return Gdk.EVENT_STOP;
}
+ return Gdk.EVENT_PROPAGATE;
},
- _checkCellFunc: function(col, cell, model, iter) {
- let state = model.get_value(iter, OrganizeModelColumns.STATE);
- let id = model.get_value(iter, OrganizeModelColumns.ID);
+ _renameModeStart: function(action, parameter) {
+ let collId = parameter.get_string()[0];
+ this._setRenameMode(true);
- cell.active = (state & OrganizeCollectionState.ACTIVE);
- cell.inconsistent = (state & OrganizeCollectionState.INCONSISTENT);
- cell.visible = (id != _COLLECTION_PLACEHOLDER_ID);
- },
+ let rows = this._collectionList.get_children();
+ rows.forEach(Lang.bind(this,
+ function(row) {
+ let currentView = row.views.get_visible_child_name();
+ if (currentView == CollectionRowViews.DELETE) {
+ row.conceal();
+ return;
+ }
- _iconCellFunc: function(col, cell, model, iter) {
- let id = model.get_value(iter, OrganizeModelColumns.ID);
+ if (row.collection.id != collId) {
+ row.set_sensitive(false);
+ return;
+ }
- cell.icon_name = "list-add";
- cell.visible = (id == _COLLECTION_PLACEHOLDER_ID);
+ row.setRenameView(Lang.bind(this, this._onTextChanged));
+ }));
},
- _textCellFunc: function(col, cell, model, iter) {
- let id = model.get_value(iter, OrganizeModelColumns.ID);
- let name = model.get_value(iter, OrganizeModelColumns.NAME);
+ _renameModeStop: function(rename) {
+ this._setRenameMode(false);
- if (id == _COLLECTION_PLACEHOLDER_ID) {
- cell.editable = true;
- cell.text = '';
- cell.placeholder_text = _("Create new collection");
- } else {
- cell.editable = false;
- cell.text = name;
- }
- },
+ let rows = this._collectionList.get_children();
+ rows.forEach(function(row) {
+ let currentView = row.views.get_visible_child_name();
+ if (currentView != CollectionRowViews.RENAME) {
+ row.set_sensitive(true);
+ return;
+ }
- _detailCellFunc: function(col, cell, model, iter) {
- let id = model.get_value(iter, OrganizeModelColumns.ID);
- let item = Application.documentManager.getItemById(id);
+ if (rename)
+ row.applyRename();
- if (item && item.identifier.indexOf(Query.LOCAL_COLLECTIONS_IDENTIFIER) == -1) {
- cell.text = Application.sourceManager.getItemById(item.resourceUrn).name;
- cell.visible = true;
- } else {
- cell.text = '';
- cell.visible = false;
- }
+ row.setDefaultView();
+ });
},
- confirmedChoice: function() {
- this._choiceConfirmed = true;
- }
-});
-
-const OrganizeCollectionDialog = new Lang.Class({
- Name: 'OrganizeCollectionDialog',
- Extends: Gtk.Dialog,
-
- _init: function(toplevel) {
- this.parent({ transient_for: toplevel,
- modal: true,
- destroy_with_parent: true,
- use_header_bar: true,
- default_width: 400,
- default_height: 300,
- // Translators: "Collections" refers to documents in this context
- title: C_("Dialog Title", "Collections") });
-
- let closeButton = this.add_button('gtk-close', Gtk.ResponseType.CLOSE);
- this.set_default_response(Gtk.ResponseType.CLOSE);
-
- let contentArea = this.get_content_area();
- let collView = new OrganizeCollectionView();
- contentArea.add(collView);
-
- // HACK:
- // - We want clicking on "Close" to add the typed-in collection if we're
- // editing.
- // - Unfortunately, since we focus out of the editable entry in order to
- // click the button, we'll get an editing-canceled signal on the renderer
- // from GTK. As this handler will run before focus-out, we here signal the
- // view to ignore the next editing-canceled signal and add the collection in
- // that case instead.
- //
- closeButton.connect('button-press-event', Lang.bind(this,
- function() {
- collView.confirmedChoice();
- return false;
- }));
+ _onCollectionListChanged: function() {
+ if (this._collectionList.isEmpty()) {
+ this._content.set_visible_child(this._viewEmpty);
+ this._addEntryEmpty.grab_focus();
+ this._addButtonEmpty.grab_default();
+ } else {
+ this._content.set_visible_child(this._viewCollections);
+ this._addEntryCollections.grab_focus();
+ this._addButtonCollections.grab_default();
+ }
+ },
- this.show_all();
+ _setRenameMode: function(renameMode) {
+ this._renameMode = renameMode;
+ if (this._renameMode) {
+ this._headerBar.set_title(_("Rename"));
+ this._cancelButton.show();
+ this._doneButton.show();
+ this._doneButton.grab_default();
+ } else {
+ // Translators: "Collections" refers to documents in this context
+ this._headerBar.set_title(C_("Dialog Title", "Collections"));
+ this._cancelButton.hide();
+ this._doneButton.hide();
+ let addButton = this._collectionList.isEmpty() ? this._addButtonEmpty :
this._addButtonCollections;
+ addButton.grab_default();
+ }
+ this._headerBar.set_show_close_button(!this._renameMode);
+ this._addGridCollections.set_sensitive(!this._renameMode);
}
});
@@ -689,7 +798,6 @@ const SelectionController = new Lang.Class({
return (item.id != value);
}));
-
if (changed) {
this._selection = filtered;
this.emit('selection-changed', this._selection);
@@ -775,8 +883,8 @@ const SelectionToolbar = new Lang.Class({
toolbar.pack_end(this._toolbarProperties);
this._toolbarProperties.connect('clicked', Lang.bind(this, this._onToolbarProperties));
- // organize button
- this._toolbarCollection = new Gtk.Button({ label: _("Add to Collection") });
+ // collections button
+ this._toolbarCollection = new Gtk.Button({ label: _("Collections") });
toolbar.pack_end(this._toolbarCollection);
this._toolbarCollection.connect('clicked', Lang.bind(this, this._onToolbarCollection));
@@ -885,10 +993,8 @@ const SelectionToolbar = new Lang.Class({
return;
let dialog = new OrganizeCollectionDialog(toplevel);
-
- dialog.connect('response', Lang.bind(this,
- function(widget, response) {
- dialog.destroy();
+ dialog.connect('destroy', Lang.bind(this,
+ function() {
Application.selectionController.setSelectionMode(false);
}));
},
diff --git a/src/view.js b/src/view.js
index 044b1d8..89a5d3c 100644
--- a/src/view.js
+++ b/src/view.js
@@ -582,6 +582,10 @@ const ViewContainer = new Lang.Class({
},
_onViewSelectionChanged: function() {
+ let mode = Application.modeController.getWindowMode();
+ if (this._mode != mode)
+ return;
+
// update the selection on the controller when the view signals a change
let selectedURNs = Utils.getURNsFromPaths(this.view.get_selection(),
this._model);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]