Changeset View
Changeset View
Standalone View
Standalone View
applets/systemtray/systemtraymodel.cpp
Show All 23 Lines | |||||
24 | 24 | | |||
25 | #include <Plasma/Applet> | 25 | #include <Plasma/Applet> | ||
26 | #include <Plasma/DataContainer> | 26 | #include <Plasma/DataContainer> | ||
27 | #include <Plasma/Service> | 27 | #include <Plasma/Service> | ||
28 | #include <PluginLoader> | 28 | #include <PluginLoader> | ||
29 | 29 | | |||
30 | #include <KLocalizedString> | 30 | #include <KLocalizedString> | ||
31 | 31 | | |||
32 | BaseModel::BaseModel(QObject *parent) | ||||
33 | : QStandardItemModel(parent), | ||||
34 | m_showAllItems(false) | ||||
35 | { | ||||
36 | connect(this, &BaseModel::dataChanged, this, &BaseModel::onDataChanged); | ||||
37 | } | ||||
38 | | ||||
39 | QHash<int, QByteArray> BaseModel::roleNames() const | ||||
40 | { | ||||
41 | QHash<int, QByteArray> roles = QStandardItemModel::roleNames(); | ||||
42 | | ||||
43 | roles.insert(static_cast<int>(BaseRole::ItemType), QByteArrayLiteral("itemType")); | ||||
44 | roles.insert(static_cast<int>(BaseRole::ItemId), QByteArrayLiteral("itemId")); | ||||
45 | roles.insert(static_cast<int>(BaseRole::CanRender), QByteArrayLiteral("canRender")); | ||||
46 | roles.insert(static_cast<int>(BaseRole::Category), QByteArrayLiteral("category")); | ||||
47 | roles.insert(static_cast<int>(BaseRole::Status), QByteArrayLiteral("status")); | ||||
48 | roles.insert(static_cast<int>(BaseRole::EffectiveStatus), QByteArrayLiteral("effectiveStatus")); | ||||
49 | | ||||
50 | return roles; | ||||
51 | } | ||||
52 | | ||||
53 | void BaseModel::onConfigurationChanged(const KConfigGroup &config) | ||||
54 | { | ||||
55 | if (!config.isValid()) { | ||||
56 | return; | ||||
57 | } | ||||
58 | | ||||
59 | const KConfigGroup generalGroup = config.group("General"); | ||||
60 | | ||||
61 | m_showAllItems = generalGroup.readEntry("showAllItems", false); | ||||
62 | m_shownItems = generalGroup.readEntry("shownItems", QStringList()); | ||||
63 | m_hiddenItems = generalGroup.readEntry("hiddenItems", QStringList()); | ||||
64 | | ||||
65 | for (int i = 0; i < rowCount(); i++) { | ||||
66 | QStandardItem *dataItem = item(i); | ||||
67 | updateEffectiveStatus(dataItem); | ||||
68 | } | ||||
69 | } | ||||
70 | | ||||
71 | void BaseModel::onDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles) | ||||
72 | { | ||||
73 | if (roles.contains(static_cast<int>(BaseRole::Status)) || roles.contains(static_cast<int>(BaseRole::CanRender))) { | ||||
74 | for (int i = topLeft.row(); i <= bottomRight.row(); i++) { | ||||
75 | QStandardItem *dataItem = item(i); | ||||
76 | updateEffectiveStatus(dataItem); | ||||
77 | } | ||||
78 | } | ||||
79 | } | ||||
80 | | ||||
81 | void BaseModel::updateEffectiveStatus(QStandardItem *dataItem) | ||||
82 | { | ||||
83 | Plasma::Types::ItemStatus status = calculateEffectiveStatus(dataItem); | ||||
84 | dataItem->setData(status, static_cast<int>(BaseModel::BaseRole::EffectiveStatus)); | ||||
85 | } | ||||
86 | | ||||
87 | Plasma::Types::ItemStatus BaseModel::calculateEffectiveStatus(QStandardItem *dataItem) | ||||
88 | { | ||||
89 | bool canRender = dataItem->data(static_cast<int>(BaseRole::CanRender)).toBool(); | ||||
90 | if (!canRender) { | ||||
91 | return Plasma::Types::ItemStatus::HiddenStatus; | ||||
92 | } | ||||
93 | | ||||
94 | Plasma::Types::ItemStatus status = readStatus(dataItem); | ||||
95 | if (status == Plasma::Types::ItemStatus::HiddenStatus) { | ||||
96 | return Plasma::Types::ItemStatus::HiddenStatus; | ||||
97 | } | ||||
98 | | ||||
99 | QString itemId = dataItem->data(static_cast<int>(BaseRole::ItemId)).toString(); | ||||
100 | | ||||
101 | bool forcedShown = m_showAllItems || m_shownItems.contains(itemId); | ||||
102 | bool forcedHidden = m_hiddenItems.contains(itemId); | ||||
103 | | ||||
104 | if (forcedShown || (!forcedHidden && status != Plasma::Types::ItemStatus::PassiveStatus)) { | ||||
105 | return Plasma::Types::ItemStatus::ActiveStatus; | ||||
106 | } else { | ||||
107 | return Plasma::Types::ItemStatus::PassiveStatus; | ||||
108 | } | ||||
109 | } | ||||
110 | | ||||
111 | Plasma::Types::ItemStatus BaseModel::readStatus(QStandardItem *dataItem) const | ||||
112 | { | ||||
113 | QVariant statusData = dataItem->data(static_cast<int>(BaseRole::Status)); | ||||
114 | if (statusData.isValid()) { | ||||
115 | return statusData.value<Plasma::Types::ItemStatus>(); | ||||
116 | } else { | ||||
117 | return Plasma::Types::ItemStatus::UnknownStatus; | ||||
118 | } | ||||
119 | } | ||||
120 | | ||||
32 | static QString plasmoidCategoryForMetadata(const KPluginMetaData &metadata) | 121 | static QString plasmoidCategoryForMetadata(const KPluginMetaData &metadata) | ||
33 | { | 122 | { | ||
34 | QString category = QStringLiteral("UnknownCategory"); | 123 | QString category = QStringLiteral("UnknownCategory"); | ||
35 | 124 | | |||
36 | if (metadata.isValid()) { | 125 | if (metadata.isValid()) { | ||
37 | const QString notificationAreaCategory = metadata.value(QStringLiteral("X-Plasma-NotificationAreaCategory")); | 126 | const QString notificationAreaCategory = metadata.value(QStringLiteral("X-Plasma-NotificationAreaCategory")); | ||
38 | if (!notificationAreaCategory.isEmpty()) { | 127 | if (!notificationAreaCategory.isEmpty()) { | ||
39 | category = notificationAreaCategory; | 128 | category = notificationAreaCategory; | ||
40 | } | 129 | } | ||
41 | } | 130 | } | ||
42 | 131 | | |||
43 | return category; | 132 | return category; | ||
44 | } | 133 | } | ||
45 | 134 | | |||
46 | PlasmoidModel::PlasmoidModel(QObject *parent) : QStandardItemModel(parent) | 135 | PlasmoidModel::PlasmoidModel(QObject *parent) : BaseModel(parent) | ||
47 | { | 136 | { | ||
48 | for (const auto &info : Plasma::PluginLoader::self()->listAppletMetaData(QString())) { | 137 | for (const auto &info : Plasma::PluginLoader::self()->listAppletMetaData(QString())) { | ||
49 | if (!info.isValid() || info.value(QStringLiteral("X-Plasma-NotificationArea")) != "true") { | 138 | if (!info.isValid() || info.value(QStringLiteral("X-Plasma-NotificationArea")) != "true") { | ||
50 | continue; | 139 | continue; | ||
51 | } | 140 | } | ||
52 | 141 | | |||
53 | QString name = info.name(); | 142 | QString name = info.name(); | ||
54 | const QString dbusactivation = | 143 | const QString dbusactivation = | ||
55 | info.rawData().value(QStringLiteral("X-Plasma-DBusActivationService")).toString(); | 144 | info.rawData().value(QStringLiteral("X-Plasma-DBusActivationService")).toString(); | ||
56 | 145 | | |||
57 | if (!dbusactivation.isEmpty()) { | 146 | if (!dbusactivation.isEmpty()) { | ||
58 | name += i18n(" (Automatic load)"); | 147 | name += i18n(" (Automatic load)"); | ||
59 | } | 148 | } | ||
60 | QStandardItem *item = new QStandardItem(QIcon::fromTheme(info.iconName()), name); | 149 | QStandardItem *item = new QStandardItem(QIcon::fromTheme(info.iconName()), name); | ||
61 | item->setData(info.pluginId(), static_cast<int>(BaseRole::ItemId)); | 150 | item->setData(info.pluginId(), static_cast<int>(BaseModel::BaseRole::ItemId)); | ||
62 | item->setData(QStringLiteral("Plasmoid"), static_cast<int>(BaseRole::ItemType)); | 151 | item->setData(QStringLiteral("Plasmoid"), static_cast<int>(BaseModel::BaseRole::ItemType)); | ||
63 | item->setData(false, static_cast<int>(BaseRole::CanRender)); | 152 | item->setData(false, static_cast<int>(BaseModel::BaseRole::CanRender)); | ||
64 | item->setData(plasmoidCategoryForMetadata(info), static_cast<int>(BaseRole::Category)); | 153 | item->setData(plasmoidCategoryForMetadata(info), static_cast<int>(BaseModel::BaseRole::Category)); | ||
65 | item->setData(false, static_cast<int>(Role::HasApplet)); | 154 | item->setData(false, static_cast<int>(Role::HasApplet)); | ||
66 | appendRow(item); | 155 | appendRow(item); | ||
67 | } | 156 | } | ||
68 | sort(0); | | |||
69 | } | 157 | } | ||
70 | 158 | | |||
71 | QHash<int, QByteArray> PlasmoidModel::roleNames() const | 159 | QHash<int, QByteArray> PlasmoidModel::roleNames() const | ||
72 | { | 160 | { | ||
73 | QHash<int, QByteArray> roles = QStandardItemModel::roleNames(); | 161 | QHash<int, QByteArray> roles = BaseModel::roleNames(); | ||
74 | | ||||
75 | roles.insert(static_cast<int>(BaseRole::ItemType), QByteArrayLiteral("itemType")); | | |||
76 | roles.insert(static_cast<int>(BaseRole::ItemId), QByteArrayLiteral("itemId")); | | |||
77 | roles.insert(static_cast<int>(BaseRole::CanRender), QByteArrayLiteral("canRender")); | | |||
78 | roles.insert(static_cast<int>(BaseRole::Category), QByteArrayLiteral("category")); | | |||
79 | 162 | | |||
80 | roles.insert(static_cast<int>(Role::Applet), QByteArrayLiteral("applet")); | 163 | roles.insert(static_cast<int>(Role::Applet), QByteArrayLiteral("applet")); | ||
81 | roles.insert(static_cast<int>(Role::HasApplet), QByteArrayLiteral("hasApplet")); | 164 | roles.insert(static_cast<int>(Role::HasApplet), QByteArrayLiteral("hasApplet")); | ||
82 | 165 | | |||
83 | return roles; | 166 | return roles; | ||
84 | } | 167 | } | ||
85 | 168 | | |||
86 | void PlasmoidModel::addApplet(Plasma::Applet *applet) | 169 | void PlasmoidModel::addApplet(Plasma::Applet *applet) | ||
87 | { | 170 | { | ||
88 | auto info = applet->pluginMetaData(); | 171 | auto pluginMetaData = applet->pluginMetaData(); | ||
89 | QStandardItem *dataItem = nullptr; | 172 | QStandardItem *dataItem = nullptr; | ||
90 | for (int i = 0; i < rowCount(); i++) { | 173 | for (int i = 0; i < rowCount(); i++) { | ||
91 | QStandardItem *currentItem = item(i); | 174 | QStandardItem *currentItem = item(i); | ||
92 | if (currentItem->data(static_cast<int>(BaseRole::ItemId)) == info.pluginId()) { | 175 | if (currentItem->data(static_cast<int>(BaseModel::BaseRole::ItemId)) == pluginMetaData.pluginId()) { | ||
93 | dataItem = currentItem; | 176 | dataItem = currentItem; | ||
94 | break; | 177 | break; | ||
95 | } | 178 | } | ||
96 | } | 179 | } | ||
97 | 180 | | |||
98 | if (!dataItem) { | 181 | if (!dataItem) { | ||
99 | dataItem = new QStandardItem(QIcon::fromTheme(info.iconName()), info.name()); | 182 | dataItem = new QStandardItem(); | ||
100 | appendRow(dataItem); | 183 | appendRow(dataItem); | ||
101 | } | 184 | } | ||
102 | 185 | | |||
103 | dataItem->setData(info.pluginId(), static_cast<int>(BaseRole::ItemId)); | 186 | dataItem->setData(applet->title(), Qt::DisplayRole); | ||
187 | connect(applet, &Plasma::Applet::titleChanged, this, [dataItem] (const QString &title) { | ||||
188 | dataItem->setData(title, static_cast<int>(Qt::DisplayRole)); | ||||
189 | }); | ||||
190 | dataItem->setData(QIcon::fromTheme(applet->icon()), Qt::DecorationRole); | ||||
191 | connect(applet, &Plasma::Applet::iconChanged, this, [dataItem] (const QString &icon) { | ||||
192 | dataItem->setData(QIcon::fromTheme(icon), Qt::DecorationRole); | ||||
193 | }); | ||||
194 | | ||||
195 | dataItem->setData(pluginMetaData.pluginId(), static_cast<int>(BaseModel::BaseRole::ItemId)); | ||||
196 | dataItem->setData(true, static_cast<int>(BaseModel::BaseRole::CanRender)); | ||||
197 | dataItem->setData(plasmoidCategoryForMetadata(pluginMetaData), static_cast<int>(BaseModel::BaseRole::Category)); | ||||
198 | dataItem->setData(applet->status(), static_cast<int>(BaseModel::BaseRole::Status)); | ||||
199 | connect(applet, &Plasma::Applet::statusChanged, this, [dataItem] (Plasma::Types::ItemStatus status) { | ||||
200 | dataItem->setData(status, static_cast<int>(BaseModel::BaseRole::Status)); | ||||
201 | }); | ||||
202 | | ||||
104 | dataItem->setData(applet->property("_plasma_graphicObject"), static_cast<int>(Role::Applet)); | 203 | dataItem->setData(applet->property("_plasma_graphicObject"), static_cast<int>(Role::Applet)); | ||
105 | dataItem->setData(true, static_cast<int>(Role::HasApplet)); | 204 | dataItem->setData(true, static_cast<int>(Role::HasApplet)); | ||
106 | dataItem->setData(true, static_cast<int>(BaseRole::CanRender)); | | |||
107 | dataItem->setData(plasmoidCategoryForMetadata(applet->pluginMetaData()), static_cast<int>(BaseRole::Category)); | | |||
108 | } | 205 | } | ||
109 | 206 | | |||
110 | void PlasmoidModel::removeApplet(Plasma::Applet *applet) | 207 | void PlasmoidModel::removeApplet(Plasma::Applet *applet) | ||
111 | { | 208 | { | ||
112 | int rows = rowCount(); | 209 | int rows = rowCount(); | ||
113 | for (int i = 0; i < rows; i++) { | 210 | for (int i = 0; i < rows; i++) { | ||
114 | QStandardItem *currentItem = item(i); | 211 | QStandardItem *currentItem = item(i); | ||
115 | QVariant plugin = currentItem->data(static_cast<int>(BaseRole::ItemId)); | 212 | QVariant plugin = currentItem->data(static_cast<int>(BaseModel::BaseRole::ItemId)); | ||
116 | if (plugin.isValid() && plugin.value<QString>() == applet->pluginMetaData().pluginId()) { | 213 | if (plugin.isValid() && plugin.value<QString>() == applet->pluginMetaData().pluginId()) { | ||
117 | currentItem->setData(false, static_cast<int>(BaseRole::CanRender)); | 214 | currentItem->setData(false, static_cast<int>(BaseModel::BaseRole::CanRender)); | ||
118 | currentItem->setData(QVariant(), static_cast<int>(Role::Applet)); | 215 | currentItem->setData(QVariant(), static_cast<int>(Role::Applet)); | ||
119 | currentItem->setData(false, static_cast<int>(Role::HasApplet)); | 216 | currentItem->setData(false, static_cast<int>(Role::HasApplet)); | ||
120 | return; | 217 | return; | ||
121 | } | 218 | } | ||
122 | } | 219 | } | ||
123 | } | 220 | } | ||
124 | 221 | | |||
125 | StatusNotifierModel::StatusNotifierModel(QObject *parent) : QStandardItemModel(parent) | 222 | StatusNotifierModel::StatusNotifierModel(QObject *parent) : BaseModel(parent) | ||
126 | { | 223 | { | ||
127 | m_dataEngine = dataEngine(QStringLiteral("statusnotifieritem")); | 224 | m_dataEngine = dataEngine(QStringLiteral("statusnotifieritem")); | ||
128 | 225 | | |||
129 | connect(m_dataEngine, &Plasma::DataEngine::sourceAdded, this, &StatusNotifierModel::addSource); | 226 | connect(m_dataEngine, &Plasma::DataEngine::sourceAdded, this, &StatusNotifierModel::addSource); | ||
130 | connect(m_dataEngine, &Plasma::DataEngine::sourceRemoved, this, &StatusNotifierModel::removeSource); | 227 | connect(m_dataEngine, &Plasma::DataEngine::sourceRemoved, this, &StatusNotifierModel::removeSource); | ||
131 | 228 | | |||
132 | m_dataEngine->connectAllSources(this); | 229 | m_dataEngine->connectAllSources(this); | ||
133 | } | 230 | } | ||
134 | 231 | | |||
135 | QHash<int, QByteArray> StatusNotifierModel::roleNames() const | 232 | QHash<int, QByteArray> StatusNotifierModel::roleNames() const | ||
136 | { | 233 | { | ||
137 | QHash<int, QByteArray> roles = QStandardItemModel::roleNames(); | 234 | QHash<int, QByteArray> roles = BaseModel::roleNames(); | ||
138 | | ||||
139 | roles.insert(static_cast<int>(BaseRole::ItemType), QByteArrayLiteral("itemType")); | | |||
140 | roles.insert(static_cast<int>(BaseRole::ItemId), QByteArrayLiteral("itemId")); | | |||
141 | roles.insert(static_cast<int>(BaseRole::CanRender), QByteArrayLiteral("canRender")); | | |||
142 | roles.insert(static_cast<int>(BaseRole::Category), QByteArrayLiteral("category")); | | |||
143 | 235 | | |||
144 | roles.insert(static_cast<int>(Role::DataEngineSource), QByteArrayLiteral("DataEngineSource")); | 236 | roles.insert(static_cast<int>(Role::DataEngineSource), QByteArrayLiteral("DataEngineSource")); | ||
145 | roles.insert(static_cast<int>(Role::AttentionIcon), QByteArrayLiteral("AttentionIcon")); | 237 | roles.insert(static_cast<int>(Role::AttentionIcon), QByteArrayLiteral("AttentionIcon")); | ||
146 | roles.insert(static_cast<int>(Role::AttentionIconName), QByteArrayLiteral("AttentionIconName")); | 238 | roles.insert(static_cast<int>(Role::AttentionIconName), QByteArrayLiteral("AttentionIconName")); | ||
147 | roles.insert(static_cast<int>(Role::AttentionMovieName), QByteArrayLiteral("AttentionMovieName")); | 239 | roles.insert(static_cast<int>(Role::AttentionMovieName), QByteArrayLiteral("AttentionMovieName")); | ||
148 | roles.insert(static_cast<int>(Role::Category), QByteArrayLiteral("Category")); | 240 | roles.insert(static_cast<int>(Role::Category), QByteArrayLiteral("Category")); | ||
149 | roles.insert(static_cast<int>(Role::Icon), QByteArrayLiteral("Icon")); | 241 | roles.insert(static_cast<int>(Role::Icon), QByteArrayLiteral("Icon")); | ||
150 | roles.insert(static_cast<int>(Role::IconName), QByteArrayLiteral("IconName")); | 242 | roles.insert(static_cast<int>(Role::IconName), QByteArrayLiteral("IconName")); | ||
▲ Show 20 Lines • Show All 51 Lines • ▼ Show 20 Line(s) | |||||
202 | 294 | | |||
203 | void StatusNotifierModel::dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data) | 295 | void StatusNotifierModel::dataUpdated(const QString &sourceName, const Plasma::DataEngine::Data &data) | ||
204 | { | 296 | { | ||
205 | QStandardItem *dataItem; | 297 | QStandardItem *dataItem; | ||
206 | if (m_sources.contains(sourceName)) { | 298 | if (m_sources.contains(sourceName)) { | ||
207 | dataItem = item(m_sources.indexOf(sourceName)); | 299 | dataItem = item(m_sources.indexOf(sourceName)); | ||
208 | } else { | 300 | } else { | ||
209 | dataItem = new QStandardItem(); | 301 | dataItem = new QStandardItem(); | ||
210 | dataItem->setData(QStringLiteral("StatusNotifier"), static_cast<int>(BaseRole::ItemType)); | 302 | dataItem->setData(QStringLiteral("StatusNotifier"), static_cast<int>(BaseModel::BaseRole::ItemType)); | ||
211 | dataItem->setData(true, static_cast<int>(BaseRole::CanRender)); | 303 | dataItem->setData(true, static_cast<int>(BaseModel::BaseRole::CanRender)); | ||
212 | } | 304 | } | ||
213 | 305 | | |||
214 | dataItem->setData(data.value("Title"), Qt::DisplayRole); | 306 | dataItem->setData(data.value("Title"), Qt::DisplayRole); | ||
215 | QVariant icon = data.value("Icon"); | 307 | QVariant icon = data.value("Icon"); | ||
216 | if (icon.isValid() && icon.canConvert<QIcon>() && !icon.value<QIcon>().isNull()) { | 308 | if (icon.isValid() && icon.canConvert<QIcon>() && !icon.value<QIcon>().isNull()) { | ||
217 | dataItem->setData(icon, Qt::DecorationRole); | 309 | dataItem->setData(icon, Qt::DecorationRole); | ||
218 | } else { | 310 | } else { | ||
219 | dataItem->setData(data.value("IconName"), Qt::DecorationRole); | 311 | dataItem->setData(data.value("IconName"), Qt::DecorationRole); | ||
220 | } | 312 | } | ||
221 | 313 | | |||
222 | dataItem->setData(data.value("Id"), static_cast<int>(BaseRole::ItemId)); | 314 | dataItem->setData(data.value("Id"), static_cast<int>(BaseModel::BaseRole::ItemId)); | ||
223 | dataItem->setData(data.value("Category"), static_cast<int>(BaseRole::Category)); | 315 | QVariant category = data.value("Category"); | ||
316 | dataItem->setData(category.isNull() ? QStringLiteral("UnknownCategory") : data.value("Category"), static_cast<int>(BaseModel::BaseRole::Category)); | ||||
317 | | ||||
318 | QString status = data.value("Status").toString(); | ||||
319 | if (status == QLatin1String("Active")) { | ||||
320 | dataItem->setData(Plasma::Types::ItemStatus::ActiveStatus, static_cast<int>(BaseModel::BaseRole::Status)); | ||||
321 | } else if (status == QLatin1String("NeedsAttention")) { | ||||
322 | dataItem->setData(Plasma::Types::ItemStatus::NeedsAttentionStatus, static_cast<int>(BaseModel::BaseRole::Status)); | ||||
323 | } else { | ||||
324 | dataItem->setData(Plasma::Types::ItemStatus::PassiveStatus, static_cast<int>(BaseModel::BaseRole::Status)); | ||||
325 | } | ||||
224 | 326 | | |||
225 | dataItem->setData(sourceName, static_cast<int>(Role::DataEngineSource)); | 327 | dataItem->setData(sourceName, static_cast<int>(Role::DataEngineSource)); | ||
226 | updateItemData(dataItem, data, Role::AttentionIcon); | 328 | updateItemData(dataItem, data, Role::AttentionIcon); | ||
227 | updateItemData(dataItem, data, Role::AttentionIconName); | 329 | updateItemData(dataItem, data, Role::AttentionIconName); | ||
228 | updateItemData(dataItem, data, Role::AttentionMovieName); | 330 | updateItemData(dataItem, data, Role::AttentionMovieName); | ||
229 | updateItemData(dataItem, data, Role::Category); | 331 | updateItemData(dataItem, data, Role::Category); | ||
230 | updateItemData(dataItem, data, Role::Icon); | 332 | updateItemData(dataItem, data, Role::Icon); | ||
231 | updateItemData(dataItem, data, Role::IconName); | 333 | updateItemData(dataItem, data, Role::IconName); | ||
▲ Show 20 Lines • Show All 51 Lines • Show Last 20 Lines |